Skip to content

Commit b3baf81

Browse files
committed
Add import destructuring and amends/extends of classes within modules
1 parent 51bb1a5 commit b3baf81

File tree

27 files changed

+390
-61
lines changed

27 files changed

+390
-61
lines changed

pkl-core/src/main/java/org/pkl/core/ast/builder/AstBuilder.java

Lines changed: 84 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1325,11 +1325,22 @@ public PklRootNode visitModule(Module mod) {
13251325

13261326
var extendsOrAmendsClause = moduleDecl != null ? moduleDecl.getExtendsOrAmendsDecl() : null;
13271327

1328-
var supermoduleNode =
1329-
extendsOrAmendsClause == null
1330-
? resolveBaseModuleClass(
1331-
org.pkl.core.runtime.Identifier.MODULE, BaseModule::getModuleClass)
1332-
: doVisitImport(false, extendsOrAmendsClause, extendsOrAmendsClause.getUrl());
1328+
ExpressionNode supermoduleNode;
1329+
if (extendsOrAmendsClause == null) {
1330+
supermoduleNode =
1331+
resolveBaseModuleClass(
1332+
org.pkl.core.runtime.Identifier.MODULE, BaseModule::getModuleClass);
1333+
} else {
1334+
supermoduleNode = doVisitImport(false, extendsOrAmendsClause, extendsOrAmendsClause.getUrl());
1335+
if (extendsOrAmendsClause.getParentTypeName() != null) {
1336+
supermoduleNode =
1337+
ReadPropertyNodeGen.create(
1338+
createSourceSection(extendsOrAmendsClause),
1339+
org.pkl.core.runtime.Identifier.get(
1340+
extendsOrAmendsClause.getParentTypeName().getValue()),
1341+
supermoduleNode);
1342+
}
1343+
}
13331344

13341345
var propertyNames =
13351346
CollectionUtils.<String>newHashSet(
@@ -1421,9 +1432,12 @@ private EconomicMap<Object, ObjectMember> doVisitModuleProperties(
14211432
var result = EconomicMaps.<Object, ObjectMember>create(totalSize);
14221433

14231434
for (var _import : imports) {
1424-
var member = visitImportClause(_import);
1425-
checkDuplicateMember(member.getName(), member.getHeaderSection(), propertyNames);
1426-
EconomicMaps.put(result, member.getName(), member);
1435+
visitImportClause(_import)
1436+
.forEach(
1437+
(member) -> {
1438+
checkDuplicateMember(member.getName(), member.getHeaderSection(), propertyNames);
1439+
EconomicMaps.put(result, member.getName(), member);
1440+
});
14271441
}
14281442

14291443
for (var clazz : classes) {
@@ -1479,36 +1493,76 @@ private EconomicMap<Object, ObjectMember> doVisitModuleProperties(
14791493
}
14801494

14811495
@Override
1482-
public ObjectMember visitImportClause(ImportClause imp) {
1496+
public List<ObjectMember> visitImportClause(ImportClause imp) {
14831497
var importNode = doVisitImport(imp.isGlob(), imp, imp.getImportStr());
14841498
var moduleKey = moduleResolver.resolve(importNode.getImportUri());
14851499
var importName =
14861500
org.pkl.core.runtime.Identifier.property(
14871501
imp.getAlias() != null ? imp.getAlias().getValue() : IoUtils.inferModuleName(moduleKey),
14881502
true);
14891503

1490-
return symbolTable.enterProperty(
1491-
importName,
1492-
ConstLevel.NONE,
1493-
scope -> {
1494-
var modifiers = VmModifier.IMPORT | VmModifier.LOCAL | VmModifier.CONST;
1495-
if (imp.isGlob()) {
1496-
modifiers = modifiers | VmModifier.GLOB;
1497-
}
1498-
var result =
1499-
new ObjectMember(
1500-
importNode.getSourceSection(),
1501-
importNode.getSourceSection(),
1502-
modifiers,
1503-
scope.getName(),
1504-
scope.getQualifiedName());
1505-
1506-
result.initMemberNode(
1507-
new UntypedObjectMemberNode(
1508-
language, scope.buildFrameDescriptor(), result, importNode));
1504+
var imports =
1505+
new ArrayList<ObjectMember>(
1506+
1
1507+
+ (imp.getDeconstructions() != null
1508+
? imp.getDeconstructions().getDeconstructions().size()
1509+
: 0));
1510+
imports.add(
1511+
symbolTable.enterProperty(
1512+
importName,
1513+
ConstLevel.NONE,
1514+
scope -> handleImportClauseItem(imp, importNode, null, scope)));
1515+
1516+
if (imp.getDeconstructions() == null) return imports;
1517+
assert !imp.isGlob();
1518+
for (var deconstruction : imp.getDeconstructions().getDeconstructions()) {
1519+
var deconstructionName =
1520+
org.pkl.core.runtime.Identifier.property(
1521+
deconstruction.getAlias() != null
1522+
? deconstruction.getAlias().getValue()
1523+
: deconstruction.getName().getValue(),
1524+
true);
1525+
imports.add(
1526+
symbolTable.enterProperty(
1527+
deconstructionName,
1528+
ConstLevel.NONE,
1529+
scope ->
1530+
handleImportClauseItem(
1531+
imp,
1532+
importNode,
1533+
org.pkl.core.runtime.Identifier.get(deconstruction.getName().getValue()),
1534+
scope)));
1535+
}
1536+
return imports;
1537+
}
1538+
1539+
private ObjectMember handleImportClauseItem(
1540+
ImportClause imp,
1541+
AbstractImportNode importNode,
1542+
@Nullable org.pkl.core.runtime.Identifier memberName,
1543+
SymbolTable.PropertyScope scope) {
1544+
var modifiers = VmModifier.IMPORT | VmModifier.LOCAL | VmModifier.CONST;
1545+
if (imp.isGlob()) {
1546+
modifiers = modifiers | VmModifier.GLOB;
1547+
}
1548+
var result =
1549+
new ObjectMember(
1550+
importNode.getSourceSection(),
1551+
importNode.getSourceSection(),
1552+
modifiers,
1553+
scope.getName(),
1554+
scope.getQualifiedName());
1555+
1556+
result.initMemberNode(
1557+
new UntypedObjectMemberNode(
1558+
language,
1559+
scope.buildFrameDescriptor(),
1560+
result,
1561+
memberName == null
1562+
? importNode
1563+
: ReadPropertyNodeGen.create(createSourceSection(imp), memberName, importNode)));
15091564

1510-
return result;
1511-
});
1565+
return result;
15121566
}
15131567

15141568
@Override

pkl-core/src/main/java/org/pkl/core/ast/expression/literal/AmendModuleNode.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.pkl.core.ast.type.UnresolvedTypeNode;
2727
import org.pkl.core.runtime.ModuleInfo;
2828
import org.pkl.core.runtime.VmLanguage;
29+
import org.pkl.core.runtime.VmClass;
2930
import org.pkl.core.runtime.VmTyped;
3031
import org.pkl.core.runtime.VmUtils;
3132

@@ -78,4 +79,10 @@ protected VmTyped eval(VirtualFrame frame, VmTyped supermodule) {
7879

7980
return module;
8081
}
82+
83+
// when using `amends Foo in "bar.pkl"`
84+
@Specialization
85+
protected VmTyped eval(VirtualFrame frame, VmClass superclass) {
86+
return eval(frame, superclass.getPrototype());
87+
}
8188
}

pkl-core/src/main/java/org/pkl/core/ast/type/UnresolvedTypeNode.java

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
2+
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -191,13 +191,17 @@ public TypeNode execute(VirtualFrame frame) {
191191
return new TypeAliasTypeNode(sourceSection, alias, new TypeNode[0]);
192192
}
193193

194-
var module = (VmTyped) type;
195-
assert module.isModuleObject();
196-
var clazz = module.getVmClass();
197-
if (!module.isPrototype()) {
198-
throw exceptionBuilder().evalError("notAModuleType", clazz.getModuleName()).build();
194+
var moduleOrClass = (VmTyped) type;
195+
var clazz = moduleOrClass.getVmClass();
196+
if (!moduleOrClass.isPrototype()) {
197+
throw exceptionBuilder()
198+
.evalError(
199+
"notAModuleOrClassType",
200+
moduleOrClass.isModuleObject() ? "Module" : "Class",
201+
clazz.getDisplayName())
202+
.build();
199203
}
200-
return TypeNode.forClass(sourceSection, module.getVmClass());
204+
return TypeNode.forClass(sourceSection, clazz);
201205
}
202206
}
203207

pkl-core/src/main/java/org/pkl/core/repl/ReplServer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ private List<Object> evaluate(
222222
var exprNode = builder.visitExpr(expr);
223223
evaluateExpr(replState, exprNode, forceResults, results);
224224
} else if (tree instanceof ImportClause importClause) {
225-
addStaticModuleProperty(builder.visitImportClause(importClause));
225+
builder.visitImportClause(importClause).forEach(this::addStaticModuleProperty);
226226
} else if (tree instanceof ClassProperty classProperty) {
227227
var propertyNode = builder.visitClassProperty(classProperty);
228228
var property = addModuleProperty(propertyNode);

pkl-core/src/main/resources/org/pkl/core/errorMessages.properties

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ Cannot invoke abstract property `{0}`.
4949
cannotInvokeSupermethodFromHere=\
5050
Cannot invoke a supermethod from here.
5151

52-
notAModuleType=\
53-
Module `{0}` cannot be extended or used as type because it amends another module.
52+
notAModuleOrClassType=\
53+
{0} `{1}` cannot be extended or used as type because it amends another module or class.
5454

5555
invalidSupertype=\
5656
`{0}` is not a valid supertype.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
amends Widget in ".../input/basic/imported.pkl"
2+
3+
qux = 5
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
amends baz in ".../input/basic/imported.pkl"
2+
3+
qux = 5
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
extends Widget in ".../input/basic/imported.pkl"
2+
3+
corge = quux * qux
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
extends baz in ".../input/basic/imported.pkl"
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import "imported.pkl" as _imported, { Widget as _Widget, baz as _baz }
2+
import "imported2.pkl" as imported2, { baz }
3+
import "pkl:test"
4+
5+
a = _imported
6+
b: _imported = new {
7+
foo = 7
8+
}
9+
c = _baz
10+
d: _Widget = new {
11+
qux = 5
12+
}
13+
e = test.catch(() -> new _baz {}) // TODO provide a better error here than an assertion failure
14+
f = test.catch(() -> new baz {}) // TODO provide a better error here than an assertion failure
15+
g = test.catch(() -> new imported2 {}) // TODO provide a better error here than an assertion failure

0 commit comments

Comments
 (0)