Skip to content

Commit 2f0e1e0

Browse files
committed
Implement delete (SPICE-0008)
1 parent 47f2143 commit 2f0e1e0

File tree

18 files changed

+614
-48
lines changed

18 files changed

+614
-48
lines changed

docs/modules/language-reference/pages/index.adoc

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3178,6 +3178,7 @@ pkl: TRACE: num1 * num2 = 672 (at file:///some/module.pkl, line 42)
31783178
This section discusses language features that are generally more relevant to template and library authors than template consumers.
31793179

31803180
<<meaning-of-new,Meaning of `new`>> +
3181+
<<member-deletion,Deleting members with `delete`>> +
31813182
<<let-expressions,Let Expressions>> +
31823183
<<type-tests,Type Tests>> +
31833184
<<type-casts,Type Casts>> +
@@ -3357,6 +3358,45 @@ swiftHatchlings = typedProperty.listHatchlings(new { "Poppy"; "Chirpy" }) // <8>
33573358
<7> Admending the property default value `new Listing { new Bird { name = "Osprey" } }`; the result contains both birds.
33583359
<8> Error: Cannot tell which parent to amend.
33593360

3361+
[[member-deletions]]
3362+
=== Deleting members with `delete`
3363+
3364+
Members can be deleted when amending the object containing them.
3365+
This is done using the `delete` keyword.
3366+
By itself, `delete` is not an expression; it can be used only in specific places, namely in a member
3367+
assignment.
3368+
These are all the positions where `delete` can be used:
3369+
3370+
[source,pkl]
3371+
----
3372+
foo {
3373+
bar = delete // <1>
3374+
["baz"] = delete // <2>
3375+
[42] = delete // <3>
3376+
[[qux == 1337]] = delete // <4>
3377+
}
3378+
----
3379+
<1> Property deletion.
3380+
<2> Entry deletion.
3381+
<3> Element deletion.
3382+
<4> Member predicate deletion (can include both entries and elements).
3383+
3384+
Properties can only be deleted from `Dynamic`s, because a `Typed` must have precisely the properties defined in its class.
3385+
Since elements are contiguously indices, deleting elements effectively renames other elements.
3386+
This means that the indices used in the definition of an object can differ from those used in subscripting.
3387+
In the following, `result = true`:
3388+
3389+
[source,pkl]
3390+
----
3391+
result = (new Listing {
3392+
/* [0] */ "foo"
3393+
/* [1] */ "bar"
3394+
/* [2] */ "baz"
3395+
}) {
3396+
[1] = delete
3397+
}[1] == "baz"
3398+
----
3399+
33603400
[[let-expressions]]
33613401
=== Let Expressions
33623402

pkl-core/src/main/antlr/PklParser.g4

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -217,14 +217,14 @@ objectBody
217217
;
218218

219219
objectMember
220-
: modifier* Identifier (typeAnnotation? '=' expr | objectBody+) # objectProperty
221-
| methodHeader '=' expr # objectMethod
222-
| t='[[' k=expr err1=']'? err2=']'? ('=' v=expr | objectBody+) # memberPredicate
223-
| t='[' k=expr err1=']'? err2=']'? ('=' v=expr | objectBody+) # objectEntry
224-
| expr # objectElement
225-
| ('...' | '...?') expr # objectSpread
226-
| 'when' '(' e=expr err=')'? (b1=objectBody ('else' b2=objectBody)?) # whenGenerator
227-
| 'for' '(' t1=parameter (',' t2=parameter)? 'in' e=expr err=')'? objectBody # forGenerator
220+
: modifier* Identifier (typeAnnotation? '=' (v=expr | d='delete') | objectBody+) # objectProperty
221+
| methodHeader '=' expr # objectMethod
222+
| t='[[' k=expr err1=']'? err2=']'? ('=' (v=expr | d='delete') | objectBody+) # memberPredicate
223+
| t='[' k=expr err1=']'? err2=']'? ('=' (v=expr | d='delete') | objectBody+) # objectEntry
224+
| expr # objectElement
225+
| ('...' | '...?') expr # objectSpread
226+
| 'when' '(' e=expr err=')'? (b1=objectBody ('else' b2=objectBody)?) # whenGenerator
227+
| 'for' '(' t1=parameter (',' t2=parameter)? 'in' e=expr err=')'? objectBody # forGenerator
228228
;
229229

230230
stringConstant
@@ -247,7 +247,6 @@ reservedKeyword
247247
: 'protected'
248248
| 'override'
249249
| 'record'
250-
| 'delete'
251250
| 'case'
252251
| 'switch'
253252
| 'vararg'

pkl-core/src/main/java/org/pkl/core/ast/VmModifier.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ private VmModifier() {}
5555

5656
public static final int GLOB = 0x1000;
5757

58+
public static final int DELETE = 0x2000;
59+
5860
// modifier sets
5961

6062
public static final int NONE = 0;
@@ -134,16 +136,20 @@ public static boolean isEntry(int modifiers) {
134136
return (modifiers & ENTRY) != 0;
135137
}
136138

139+
public static boolean isDelete(int modifiers) {
140+
return (modifiers & DELETE) != 0;
141+
}
142+
137143
public static boolean isType(int modifiers) {
138-
return (modifiers & (CLASS | TYPE_ALIAS | IMPORT)) != 0 && (modifiers & GLOB) == 0;
144+
return (modifiers & (CLASS | TYPE_ALIAS | IMPORT)) != 0 && (modifiers & (GLOB | DELETE)) == 0;
139145
}
140146

141147
public static boolean isLocalOrExternalOrHidden(int modifiers) {
142148
return (modifiers & (LOCAL | EXTERNAL | HIDDEN)) != 0;
143149
}
144150

145-
public static boolean isLocalOrExternalOrAbstract(int modifiers) {
146-
return (modifiers & (LOCAL | EXTERNAL | ABSTRACT)) != 0;
151+
public static boolean isLocalOrExternalOrAbstractOrDelete(int modifiers) {
152+
return (modifiers & (LOCAL | EXTERNAL | ABSTRACT | DELETE)) != 0;
147153
}
148154

149155
public static boolean isConstOrFixed(int modifiers) {

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

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -651,7 +651,13 @@ private VmException missingLocalPropertyValue(TypeAnnotationContext typeAnnCtx)
651651

652652
private ObjectMember doVisitObjectProperty(ObjectPropertyContext ctx) {
653653
return doVisitObjectProperty(
654-
ctx, ctx.modifier(), ctx.Identifier(), ctx.typeAnnotation(), ctx.expr(), ctx.objectBody());
654+
ctx,
655+
ctx.modifier(),
656+
ctx.Identifier(),
657+
ctx.typeAnnotation(),
658+
ctx.v,
659+
ctx.d,
660+
ctx.objectBody());
655661
}
656662

657663
private ObjectMember doVisitObjectMethod(ObjectMethodContext ctx) {
@@ -720,6 +726,7 @@ private ObjectMember doVisitObjectProperty(
720726
TerminalNode propertyName,
721727
@Nullable TypeAnnotationContext typeAnnCtx,
722728
@Nullable ExprContext exprCtx,
729+
@Nullable Token deleteToken,
723730
@Nullable List<? extends ObjectBodyContext> bodyCtx) {
724731

725732
return doVisitObjectProperty(
@@ -730,18 +737,21 @@ private ObjectMember doVisitObjectProperty(
730737
propertyName.getText(),
731738
typeAnnCtx,
732739
exprCtx,
740+
deleteToken,
733741
bodyCtx);
734742
}
735743

736744
private ObjectMember doVisitObjectProperty(
737745
SourceSection sourceSection,
738746
SourceSection headerSection,
739-
int modifiers,
747+
int propertyModifiers,
740748
String propertyName,
741749
@Nullable TypeAnnotationContext typeAnnCtx,
742750
@Nullable ExprContext exprCtx,
751+
@Nullable Token deleteToken,
743752
@Nullable List<? extends ObjectBodyContext> bodyCtx) {
744753

754+
var modifiers = propertyModifiers | (deleteToken == null ? 0 : VmModifier.DELETE);
745755
var isLocal = VmModifier.isLocal(modifiers);
746756
var identifier = Identifier.property(propertyName, isLocal);
747757

@@ -783,6 +793,15 @@ private ObjectMember doVisitObjectProperty(
783793
// 2. if in a const scope (i.e. `const bar = new { foo { ... } }`),
784794
// `super.foo` does not reference something outside the scope.
785795
false));
796+
} else if (deleteToken != null) {
797+
var deleteSourceSection = createSourceSection(deleteToken);
798+
if (isLocal) {
799+
throw exceptionBuilder()
800+
.evalError("cannotDeleteLocalProperty")
801+
.withSourceSection(deleteSourceSection)
802+
.build();
803+
}
804+
bodyNode = VmUtils.DELETE_MARKER;
786805
} else { // foo = ...
787806
assert exprCtx != null;
788807
bodyNode = visitExpr(exprCtx);
@@ -1218,7 +1237,8 @@ private Pair<ExpressionNode, ObjectMember> doVisitMemberPredicate(MemberPredicat
12181237
var keyNode = symbolTable.enterCustomThisScope(scope -> visitExpr(ctx.k));
12191238

12201239
return symbolTable.enterEntry(
1221-
keyNode, objectMemberInserter(createSourceSection(ctx), keyNode, ctx.v, ctx.objectBody()));
1240+
keyNode,
1241+
objectMemberInserter(createSourceSection(ctx), keyNode, ctx.v, ctx.d, ctx.objectBody()));
12221242
}
12231243

12241244
private Pair<ExpressionNode, ObjectMember> doVisitObjectEntry(ObjectEntryContext ctx) {
@@ -1232,20 +1252,22 @@ private Pair<ExpressionNode, ObjectMember> doVisitObjectEntry(ObjectEntryContext
12321252
var keyNode = visitExpr(ctx.k);
12331253

12341254
return symbolTable.enterEntry(
1235-
keyNode, objectMemberInserter(createSourceSection(ctx), keyNode, ctx.v, ctx.objectBody()));
1255+
keyNode,
1256+
objectMemberInserter(createSourceSection(ctx), keyNode, ctx.v, ctx.d, ctx.objectBody()));
12361257
}
12371258

12381259
private Function<EntryScope, Pair<ExpressionNode, ObjectMember>> objectMemberInserter(
12391260
SourceSection sourceSection,
12401261
ExpressionNode keyNode,
12411262
@Nullable ExprContext valueCtx,
1263+
@Nullable Token deleteToken,
12421264
List<? extends ObjectBodyContext> objectBodyCtxs) {
12431265
return scope -> {
12441266
var member =
12451267
new ObjectMember(
12461268
sourceSection,
12471269
keyNode.getSourceSection(),
1248-
VmModifier.ENTRY,
1270+
VmModifier.ENTRY | (deleteToken == null ? 0 : VmModifier.DELETE),
12491271
null,
12501272
scope.getQualifiedName());
12511273

@@ -2610,6 +2632,7 @@ private EconomicMap<Object, ObjectMember> doVisitModuleProperties(
26102632
ctx.Identifier(),
26112633
ctx.typeAnnotation(),
26122634
ctx.expr(),
2635+
null,
26132636
ctx.objectBody());
26142637

26152638
if (moduleInfo.isAmend() && !member.isLocal() && ctx.typeAnnotation() != null) {

pkl-core/src/main/java/org/pkl/core/ast/member/Member.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,10 @@ public final boolean isType() {
119119
return VmModifier.isType(modifiers);
120120
}
121121

122+
public final boolean isDelete() {
123+
return VmModifier.isDelete(modifiers);
124+
}
125+
122126
public final boolean isLocalOrExternalOrHidden() {
123127
return VmModifier.isLocalOrExternalOrHidden(modifiers);
124128
}
@@ -127,7 +131,7 @@ public final boolean isConstOrFixed() {
127131
return VmModifier.isConstOrFixed(modifiers);
128132
}
129133

130-
public final boolean isLocalOrExternalOrAbstract() {
131-
return VmModifier.isLocalOrExternalOrAbstract(modifiers);
134+
public final boolean isLocalOrExternalOrAbstractOrDelete() {
135+
return VmModifier.isLocalOrExternalOrAbstractOrDelete(modifiers);
132136
}
133137
}

pkl-core/src/main/java/org/pkl/core/runtime/TestRunner.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,12 +247,13 @@ private void doRunAndValidateExamples(
247247
return true;
248248
}
249249
if (examples.getCachedValue(groupKey) == null) {
250+
var key = String.valueOf(groupKey);
250251
allGroupsSucceeded.set(false);
251252
results
252-
.newResult(String.valueOf(groupKey))
253+
.newResult(key)
253254
.addFailure(
254255
Failure.buildExamplePropertyMismatchFailure(
255-
getDisplayUri(groupMember), String.valueOf(groupKey), false));
256+
getDisplayUri(groupMember), key, false));
256257
}
257258
return true;
258259
});

pkl-core/src/main/java/org/pkl/core/runtime/VmDynamic.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public int getLength() {
6262
return length;
6363
}
6464

65-
/** Tells whether this object has any elements. */
65+
@Override
6666
public boolean hasElements() {
6767
return length != 0;
6868
}

pkl-core/src/main/java/org/pkl/core/runtime/VmFunction.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
2020
import com.oracle.truffle.api.RootCallTarget;
2121
import com.oracle.truffle.api.frame.MaterializedFrame;
22-
import java.util.function.BiFunction;
2322
import org.graalvm.collections.UnmodifiableEconomicMap;
2423
import org.pkl.core.ast.PklRootNode;
2524
import org.pkl.core.ast.member.ObjectMember;
@@ -142,7 +141,7 @@ public boolean iterateAlreadyForcedMemberValues(ForcedMemberValueConsumer consum
142141
}
143142

144143
@Override
145-
public boolean iterateMembers(BiFunction<Object, ObjectMember, Boolean> consumer) {
144+
public boolean iterateMembers(MemberConsumer consumer) {
146145
return true;
147146
}
148147

pkl-core/src/main/java/org/pkl/core/runtime/VmListing.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,11 @@ public int getLength() {
7777
return length;
7878
}
7979

80+
@Override
81+
public boolean hasElements() {
82+
return !isEmpty();
83+
}
84+
8085
public boolean isEmpty() {
8186
return length == 0;
8287
}

pkl-core/src/main/java/org/pkl/core/runtime/VmMapping.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@ public VmClass getVmClass() {
7979
return BaseModule.getMappingClass();
8080
}
8181

82+
@Override
83+
public boolean hasElements() {
84+
return false;
85+
}
86+
8287
@TruffleBoundary
8388
public VmSet getAllKeys() {
8489
synchronized (this) {

0 commit comments

Comments
 (0)