diff --git a/bench/bench.gradle.kts b/bench/bench.gradle.kts index a4d68e9f6..d23feb875 100644 --- a/bench/bench.gradle.kts +++ b/bench/bench.gradle.kts @@ -31,13 +31,17 @@ dependencies { } jmh { - // include = ["fib_class_java"] - // include = ["fib_class_constrained1", "fib_class_constrained2"] + // includes = listOf("ParserBenchmark") jmhVersion.set(libs.versions.jmh) // jvmArgsAppend = "-Dgraal.TruffleCompilationExceptionsAreFatal=true " + // "-Dgraal.Dump=Truffle,TruffleTree -Dgraal.TraceTruffleCompilation=true " + // "-Dgraal.TruffleFunctionInlining=false" - jvm.set("${buildInfo.graalVmAmd64.baseDir}/bin/java") + val graalVm = + when (buildInfo.arch) { + Target.Arch.AARCH64 -> buildInfo.graalVmAarch64 + Target.Arch.AMD64 -> buildInfo.graalVmAmd64 + } + jvm.set("${graalVm.baseDir}/bin/java") // see: // https://docs.oracle.com/en/graalvm/enterprise/20/docs/graalvm-as-a-platform/implement-language/#disable-class-path-separation jvmArgs.set( @@ -51,7 +55,13 @@ jmh { // synchronizeIterations = false } -tasks.named("jmh") { dependsOn(":installGraalVmAmd64") } +tasks.named("jmh") { + if (buildInfo.arch == Target.Arch.AARCH64) { + dependsOn(":installGraalVmAarch64") + } else if (buildInfo.arch == Target.Arch.AMD64) { + dependsOn(":installGraalVmAmd64") + } +} // Prevent this error which occurs when building in IntelliJ: // "Entry org/pkl/core/fib_class_typed.pkl is a duplicate but no duplicate handling strategy has diff --git a/docs/modules/language-reference/pages/index.adoc b/docs/modules/language-reference/pages/index.adoc index e166fd07d..5edf09fea 100644 --- a/docs/modules/language-reference/pages/index.adoc +++ b/docs/modules/language-reference/pages/index.adoc @@ -6022,7 +6022,7 @@ class Domain extends ref.AbstractDomain { // <1> typealias Ref = ref.Reference // <2> -const function Ref(data: Any): Ref = ref.Reference::(new Domain {}, data) // <3> +const function Ref(data: Any): Ref = ref.Reference(new Domain {}, data) // <3> abstract class Task { // <4> name: String @@ -6032,7 +6032,7 @@ class TaskA extends Task { input1: String | Ref // <5> input2: Int | Ref - hidden fixed outputs: Ref = Ref::(this) // <6> + hidden fixed outputs: Ref = Ref(this) // <6> } abstract class TaskAOutputs { // <7> @@ -6113,7 +6113,7 @@ tasks { Methods may be declared as "generic", with types that are themselves parameters. Method type parameters may be used within the method anywhere else a type may be used: in a parameter's type, in the return type, or within the method body (e.g. <>, <>, <>). -When calling a generic method, a caller may manually specify type arguments by writing `methodName::(arg1, arg2)`. +When calling a generic method, a caller may manually specify type arguments by writing `methodName(arg1, arg2)`. When no type arguments are provided, type checks of parameter types behave like <> and always succeed. [source,pkl] @@ -6132,11 +6132,11 @@ function makeFlock(names: Listing): Listing = new { } } -pigeonFlock = makeFlock::(new { "Alice"; "Bill"; "Ruth" }) // <1> -hawkFlock = makeFlock::(new { "Florence"; "Holly"; "Jim" }) // <2> +pigeonFlock = makeFlock(new { "Alice"; "Bill"; "Ruth" }) // <1> +hawkFlock = makeFlock(new { "Florence"; "Holly"; "Jim" }) // <2> ---- <1> Result: a `Listing` where every element is a `Pigeon`. <2> Result: a `Listing` where every element is a `Hawk`. -NOTE: The `::<>` syntax is colloquially known as the "turbofish", after https://doc.rust-lang.org/reference/glossary.html#turbofish[the same syntax in Rust]. -Pkl requires this syntax for the same reason as Rust: to avoid parser ambiguity. +NOTE: A `<` following an identifier is parsed as the start of a type argument list when it is well-formed and directly followed by `(`. +Otherwise, it is parsed as the less-than operator; for example, `foo(baz)` is a method call with type arguments, whereas `foo(baz)` is a comparison. diff --git a/pkl-core/src/main/java/org/pkl/core/ast/MemberLookupMode.java b/pkl-core/src/main/java/org/pkl/core/ast/MemberLookupMode.java index 5d9e25743..5c5b44f76 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/MemberLookupMode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/MemberLookupMode.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,5 +29,12 @@ public enum MemberLookupMode { IMPLICIT_THIS, /** Member lookup with explicit receiver (e.g., {@code foo.bar}). */ - EXPLICIT_RECEIVER + EXPLICIT_RECEIVER, + + /** Member lookup with explicit {@code super.}. */ + EXPLICIT_SUPER; + + public boolean isImplicitReceiver() { + return this != EXPLICIT_RECEIVER && this != EXPLICIT_SUPER; + } } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorMemberNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorMemberNode.java index ad3698d95..16eb5c04c 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorMemberNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorMemberNode.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.dsl.Idempotent; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.source.SourceSection; +import org.pkl.core.ast.MemberLookupMode; import org.pkl.core.ast.PklNode; import org.pkl.core.ast.member.ObjectMember; import org.pkl.core.runtime.Identifier; @@ -63,7 +64,13 @@ public abstract class GeneratorMemberNode extends PklNode { if (classProperty == null) { var exception = exceptionBuilder() - .cannotFindProperty(clazz.getPrototype(), memberName, false, false) + .cannotFindProperty( + clazz.getPrototype(), + clazz.getPrototype(), + memberName, + false, + MemberLookupMode.EXPLICIT_RECEIVER, + null) .build(); if (member.getHeaderSection().isAvailable()) { exception diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/SpecializedObjectLiteralNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/SpecializedObjectLiteralNode.java index b3cb4e933..fa01f51de 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/SpecializedObjectLiteralNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/SpecializedObjectLiteralNode.java @@ -29,6 +29,7 @@ import org.graalvm.collections.EconomicMap; import org.graalvm.collections.UnmodifiableEconomicMap; import org.jspecify.annotations.Nullable; import org.pkl.core.ast.ExpressionNode; +import org.pkl.core.ast.MemberLookupMode; import org.pkl.core.ast.member.ObjectMember; import org.pkl.core.ast.type.UnresolvedTypeNode; import org.pkl.core.runtime.*; @@ -80,7 +81,13 @@ public abstract class SpecializedObjectLiteralNode extends ObjectLiteralNode { var memberName = member.getName(); if (!parentClass.hasProperty(memberName)) { throw exceptionBuilder() - .cannotFindProperty(parentClass.getPrototype(), memberName, false, false) + .cannotFindProperty( + parentClass.getPrototype(), + parentClass.getPrototype(), + memberName, + false, + MemberLookupMode.EXPLICIT_RECEIVER, + null) .withSourceSection(member.getHeaderSection()) .build(); } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeMethodVirtualNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeMethodVirtualNode.java index 718e2f96d..b0006ecb2 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeMethodVirtualNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeMethodVirtualNode.java @@ -37,6 +37,7 @@ import org.pkl.core.ast.type.UnresolvedTypeNode; import org.pkl.core.runtime.Identifier; import org.pkl.core.runtime.VmClass; import org.pkl.core.runtime.VmFunction; +import org.pkl.core.runtime.VmUtils; /** A virtual method call. */ @SuppressWarnings("DuplicatedCode") @@ -89,6 +90,8 @@ public abstract class InvokeMethodVirtualNode extends AbstractInvokeMethodNode { */ public abstract Object executeWith(VirtualFrame frame, Object value, VmClass clazz); + protected abstract ExpressionNode getReceiverNode(); + /** Intrinsifies `FunctionN.apply()` calls. */ @Specialization(guards = {"methodName == APPLY", "receiver.getCallTarget() == cachedCallTarget"}) protected Object evalFunctionCached( @@ -119,7 +122,7 @@ public abstract class InvokeMethodVirtualNode extends AbstractInvokeMethodNode { Object receiver, @SuppressWarnings("unused") VmClass receiverClass, @Cached("receiverClass") @SuppressWarnings("unused") VmClass cachedReceiverClass, - @Cached("resolveMethod(receiverClass)") ClassMethod method, + @Cached("resolveMethod(frame, receiverClass)") ClassMethod method, @Cached("create(method.getCallTarget(sourceSection))") DirectCallNode callNode) { var args = evalArgs(frame, method, method.getOwner(), receiver); return callNode.call(args); @@ -131,7 +134,7 @@ public abstract class InvokeMethodVirtualNode extends AbstractInvokeMethodNode { Object receiver, VmClass receiverClass, @Exclusive @Cached("create()") IndirectCallNode callNode) { - var method = resolveMethod(receiverClass); + var method = resolveMethod(frame, receiverClass); var args = evalArgs(frame, method, method.getOwner(), receiver); // Deprecation should not report here (getCallTarget(sourceSection)), as this happens for each @@ -139,7 +142,7 @@ public abstract class InvokeMethodVirtualNode extends AbstractInvokeMethodNode { return callNode.call(method.getCallTarget(), args); } - protected ClassMethod resolveMethod(VmClass receiverClass) { + protected ClassMethod resolveMethod(VirtualFrame frame, VmClass receiverClass) { var method = receiverClass.getMethod(methodName); if (method != null) { checkConst(method); @@ -151,9 +154,12 @@ public abstract class InvokeMethodVirtualNode extends AbstractInvokeMethodNode { throw exceptionBuilder() .cannotFindMethod( receiverClass.getPrototype(), + VmUtils.getOwner(frame), methodName, argumentNodes.length, - lookupMode != MemberLookupMode.EXPLICIT_RECEIVER) + lookupMode, + unresolvedTypeArgumentNodes != null, + getReceiverNode()) .build(); } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeSuperMethodNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeSuperMethodNode.java index c70f4cbfb..2826661b2 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeSuperMethodNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeSuperMethodNode.java @@ -23,6 +23,7 @@ import com.oracle.truffle.api.nodes.DirectCallNode; import com.oracle.truffle.api.source.SourceSection; import org.jspecify.annotations.Nullable; import org.pkl.core.ast.ExpressionNode; +import org.pkl.core.ast.MemberLookupMode; import org.pkl.core.ast.member.ClassMethod; import org.pkl.core.ast.type.UnresolvedTypeNode; import org.pkl.core.runtime.Identifier; @@ -84,7 +85,14 @@ public abstract class InvokeSuperMethodNode extends AbstractInvokeMethodNode { var parent = owner.getParent(); assert parent != null; throw exceptionBuilder() - .cannotFindMethod(parent, methodName, argumentNodes.length, false) + .cannotFindMethod( + parent, + VmUtils.getOwner(frame), + methodName, + argumentNodes.length, + MemberLookupMode.EXPLICIT_SUPER, + unresolvedTypeArgumentNodes != null, + null) .build(); } } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/ReadPropertyNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/ReadPropertyNode.java index 42138dd00..e8aaaa4c8 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/ReadPropertyNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/ReadPropertyNode.java @@ -19,6 +19,7 @@ import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.dsl.*; +import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.DirectCallNode; import com.oracle.truffle.api.nodes.IndirectCallNode; import com.oracle.truffle.api.nodes.NodeInfo; @@ -32,7 +33,7 @@ import org.pkl.core.runtime.VmReference.VmReferenceAccessError; import org.pkl.core.util.ErrorMessages; @NodeInfo(shortName = ".") -@ImportStatic(BaseModule.class) +@ImportStatic({BaseModule.class, VmUtils.class}) @NodeChild(value = "receiverNode", type = ExpressionNode.class) public abstract class ReadPropertyNode extends ExpressionNode { protected final Identifier propertyName; @@ -63,6 +64,8 @@ public abstract class ReadPropertyNode extends ExpressionNode { this(sourceSection, propertyName, MemberLookupMode.EXPLICIT_RECEIVER, false); } + protected abstract ExpressionNode getReceiverNode(); + protected String getReferenceErrorHint(VmReference reference, VmReferenceAccessError err) { var myType = reference.getReferentType(); return switch (err.getErrorType()) { @@ -102,6 +105,7 @@ public abstract class ReadPropertyNode extends ExpressionNode { // https://www.graalvm.org/22.0/graalvm-as-a-platform/language-implementation-framework/TruffleLibraries/#strategy-2-java-interfaces @Specialization(guards = "receiver.getClass() == cachedClass", limit = "99") protected Object evalObject( + VirtualFrame frame, Object receiver, @Cached("getVmObjectSubclassOrNull(receiver)") Class cachedClass, @Cached("create()") IndirectCallNode callNode) { @@ -112,15 +116,16 @@ public abstract class ReadPropertyNode extends ExpressionNode { if (result != null) return result; CompilerDirectives.transferToInterpreter(); - throw cannotFindProperty(object); + throw cannotFindProperty(VmUtils.getOwner(frame), object); } // specializations for all other types @Specialization(guards = "receiver.getClass() == cachedClass", limit = "99") protected Object evalOther( + VirtualFrame frame, Object receiver, @Cached("receiver.getClass()") @SuppressWarnings("unused") Class cachedClass, - @Cached("resolveProperty(receiver)") ClassProperty resolvedProperty, + @Cached("resolveProperty(getOwner(frame), receiver)") ClassProperty resolvedProperty, @Cached("createCallNode(resolvedProperty)") DirectCallNode callNode) { return callNode.call(receiver, resolvedProperty.getOwner(), resolvedProperty.getName()); @@ -131,13 +136,13 @@ public abstract class ReadPropertyNode extends ExpressionNode { return value instanceof VmObjectLike objectLike ? objectLike.getClass() : null; } - protected ClassProperty resolveProperty(Object value) { + protected ClassProperty resolveProperty(VmObjectLike owner, Object value) { var clazz = VmUtils.getClass(value); var propertyDef = clazz.getProperty(propertyName); if (propertyDef != null) return propertyDef; CompilerDirectives.transferToInterpreter(); - throw cannotFindProperty(clazz.getPrototype()); + throw cannotFindProperty(owner, clazz.getPrototype()); } // This method should only be used for standard library properties implemented as nodes. @@ -147,10 +152,9 @@ public abstract class ReadPropertyNode extends ExpressionNode { } @TruffleBoundary - private VmException cannotFindProperty(VmObjectLike receiver) { + private VmException cannotFindProperty(VmObjectLike owner, VmObjectLike receiver) { return exceptionBuilder() - .cannotFindProperty( - receiver, propertyName, true, lookupMode != MemberLookupMode.EXPLICIT_RECEIVER) + .cannotFindProperty(receiver, owner, propertyName, true, lookupMode, getReceiverNode()) .build(); } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/member/FunctionNode.java b/pkl-core/src/main/java/org/pkl/core/ast/member/FunctionNode.java index ab0e64e80..006ee846c 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/member/FunctionNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/member/FunctionNode.java @@ -152,7 +152,7 @@ public final class FunctionNode extends RegularMemberNode { // this behavior needs to be overridden so the type arg check on return type still runs. // otherwise, this code would succeed instead of failing: // ``` - // res = List("foo", "hello", "a").map::((it) -> it.length) + // res = List("foo", "hello", "a").map((it) -> it.length) // ``` // Assumption: this does not need to account for type args in enclosing frames because // no type parameters exist in the stdlib that would require this. diff --git a/pkl-core/src/main/java/org/pkl/core/ast/type/TypeNode.java b/pkl-core/src/main/java/org/pkl/core/ast/type/TypeNode.java index 341e61039..addf2b030 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/type/TypeNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/type/TypeNode.java @@ -2300,7 +2300,7 @@ public abstract class TypeNode extends PklNode { // e.g. // ``` // function foo(arg: T) - // res = foo::(new {}) // `Person` is passed in via `typeArgumentOverrides` + // res = foo(new {}) // `Person` is passed in via `typeArgumentOverrides` // ``` if (levelsUp == 0 && isPreCall) { return typeArgumentOverrides == null ? null : typeArgumentOverrides[typeParameter.index()]; diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/MemberLookupSuggestions.java b/pkl-core/src/main/java/org/pkl/core/runtime/MemberLookupSuggestions.java index db67f5d77..a62c16954 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/MemberLookupSuggestions.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/MemberLookupSuggestions.java @@ -18,6 +18,8 @@ package org.pkl.core.runtime; import java.util.*; import java.util.stream.Collectors; import org.jspecify.annotations.Nullable; +import org.pkl.core.ast.MemberLookupMode; +import org.pkl.core.ast.PklNode; import org.pkl.core.ast.member.ClassMethod; import org.pkl.core.ast.member.Member; import org.pkl.core.runtime.MemberLookupSuggestions.Candidate.Kind; @@ -30,7 +32,8 @@ public final class MemberLookupSuggestions { // names that differ in their first character similar private static final double SIMILARITY_THRESHOLD = 0.77; - private final VmObjectLike composite; + private final VmObjectLike receiver; + private final VmObjectLike owner; private final Object memberName; // -1 for property, #arguments for function private final int memberArity; @@ -39,8 +42,13 @@ public final class MemberLookupSuggestions { private final Set candidates = new LinkedHashSet<>(); public MemberLookupSuggestions( - VmObjectLike composite, Object memberName, int memberArity, Set memberKinds) { - this.composite = composite; + VmObjectLike receiver, + VmObjectLike owner, + Object memberName, + int memberArity, + Set memberKinds) { + this.receiver = receiver; + this.owner = owner; this.memberName = memberName; this.memberArity = memberArity; this.memberKinds = memberKinds; @@ -49,45 +57,76 @@ public final class MemberLookupSuggestions { // use same search order as in member lookup, // so that in case of members with same name, // (only) the correct one is suggested - public List find(boolean isImplicitReceiver) { + public List find(MemberLookupMode memberLookupMode, @Nullable PklNode receiverNode) { candidates.clear(); - - if (isImplicitReceiver) { - for (var curr = composite; curr != null; curr = curr.getEnclosingOwner()) { - addPropertyCandidates(curr, true); + if (memberLookupMode.isImplicitReceiver()) { + for (var curr = receiver; curr != null; curr = curr.getEnclosingOwner()) { + addPropertyCandidates(curr, true, memberLookupMode, receiverNode); if (curr.isPrototype()) { - addMethodCandidates(curr.getVmClass().getDeclaredMethods(), true); + addMethodCandidates( + curr.getVmClass().getDeclaredMethods(), true, memberLookupMode, receiverNode); } } - addPropertyCandidates(BaseModule.getModule(), false); - addMethodCandidates(BaseModule.getModule().getVmClass().getMethods(), false); + // if we resolved to implicit `this` at parse time, try searching the lexical scope of the + // owner too. + for (var curr = owner; curr != null; curr = curr.getEnclosingOwner()) { + addPropertyCandidates(curr, true, memberLookupMode, receiverNode); + if (curr.isPrototype()) { + addMethodCandidates( + curr.getVmClass().getDeclaredMethods(), true, memberLookupMode, receiverNode); + } + } + addPropertyCandidates(BaseModule.getModule(), false, memberLookupMode, receiverNode); + addMethodCandidates( + BaseModule.getModule().getVmClass().getMethods(), false, memberLookupMode, receiverNode); } - for (var curr = composite; curr != null; curr = curr.getParent()) { - addPropertyCandidates(curr, false); + for (var curr = receiver; curr != null; curr = curr.getParent()) { + addPropertyCandidates(curr, false, memberLookupMode, receiverNode); } - addMethodCandidates(composite.getVmClass().getMethods(), false); + addMethodCandidates(receiver.getVmClass().getMethods(), false, memberLookupMode, receiverNode); return candidates.stream().sorted(Comparator.naturalOrder()).collect(Collectors.toList()); } - private void addPropertyCandidates(VmObjectLike object, boolean includeLocal) { + private void addPropertyCandidates( + VmObjectLike object, + boolean includeLocal, + MemberLookupMode memberLookupMode, + @Nullable PklNode receiverNode) { if (!memberKinds.contains(Kind.PROPERTY)) return; for (var member : EconomicMaps.getValues(object.getMembers())) { - addIfSimilar(member, Candidate.Kind.PROPERTY, -1, includeLocal); + addIfSimilar( + member, Candidate.Kind.PROPERTY, -1, includeLocal, memberLookupMode, receiverNode); } } - private void addMethodCandidates(Iterable methods, boolean includeLocal) { + private void addMethodCandidates( + Iterable methods, + boolean includeLocal, + MemberLookupMode memberLookupMode, + @Nullable PklNode receiverNode) { if (!memberKinds.contains(Kind.METHOD)) return; for (var method : methods) { - addIfSimilar(method, Candidate.Kind.METHOD, method.getParameterCount(), includeLocal); + addIfSimilar( + method, + Candidate.Kind.METHOD, + method.getParameterCount(), + includeLocal, + memberLookupMode, + receiverNode); } } - private void addIfSimilar(Member member, Candidate.Kind kind, int arity, boolean includeLocal) { + private void addIfSimilar( + Member member, + Kind kind, + int arity, + boolean includeLocal, + MemberLookupMode memberLookupMode, + @Nullable PklNode receiverNode) { var memberName = member.getNameOrNull(); if (memberName == null) return; @@ -99,15 +138,26 @@ public final class MemberLookupSuggestions { var arityDifference = Math.abs(arity - memberArity); var signature = member.getCallSignature(); assert signature != null; + var nameWithReceiver = memberName.toString(); + // `receiverNode` is not necessarily null; we use `MemberLookupMode.EXPLICIT_RECEIVER` + // in places that are not property/method calls, e.g. + // `org.pkl.core.ast.expression.literal.SpecializedObjectLiteralNode.checkIsValidTypedAmendment` + if (memberLookupMode == MemberLookupMode.EXPLICIT_RECEIVER && receiverNode != null) { + nameWithReceiver = + receiverNode.getSourceSection().getCharacters().toString() + "." + memberName; + } else if (memberLookupMode == MemberLookupMode.EXPLICIT_SUPER) { + nameWithReceiver = "super." + memberName; + } if (nameSimilarity < 1 || memberArity == 0) { candidates.add( new Candidate( - kind, memberName.toString(), signature, nameSimilarity, arityDifference)); + kind, memberName, nameWithReceiver, signature, nameSimilarity, arityDifference)); } else if (nameSimilarity == 1 && memberArity >= 0) { candidates.add( new Candidate( kind, - memberName.toString(), + memberName, + nameWithReceiver, signature + ".apply(...)", nameSimilarity, arityDifference)); @@ -116,22 +166,14 @@ public final class MemberLookupSuggestions { } } - public static final class Candidate implements Comparable { - private final Kind kind; - private final String name; - private final String callSignature; - private final double nameSimilarity; - private final int arityDifference; - - public Candidate( - Kind kind, String name, String callSignature, double nameSimilarity, int arityDifference) { - this.kind = kind; - this.name = name; - this.callSignature = callSignature; - this.nameSimilarity = nameSimilarity; - this.arityDifference = arityDifference; - } - + public record Candidate( + Kind kind, + Identifier memberName, + String memberNameWithReceiver, + String callSignature, + double nameSimilarity, + int arityDifference) + implements Comparable { // note: not consistent with equals (hence cannot use TreeSet) @Override public int compareTo(Candidate other) { @@ -146,12 +188,12 @@ public final class MemberLookupSuggestions { if (this == obj) return true; if (!(obj instanceof Candidate other)) return false; // member lookup is name rather than signature based (but distinguishes kind) - return kind == other.kind && name.equals(other.name); + return kind == other.kind && memberNameWithReceiver.equals(other.memberNameWithReceiver); } @Override public int hashCode() { - return kind.hashCode() * 31 + name.hashCode(); + return kind.hashCode() * 31 + memberNameWithReceiver.hashCode(); } @Override diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmExceptionBuilder.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmExceptionBuilder.java index cd0fc298b..ac5cdd898 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmExceptionBuilder.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmExceptionBuilder.java @@ -24,6 +24,8 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import org.jspecify.annotations.Nullable; import org.pkl.core.StackFrame; +import org.pkl.core.ast.MemberLookupMode; +import org.pkl.core.ast.PklNode; import org.pkl.core.runtime.MemberLookupSuggestions.Candidate.Kind; import org.pkl.core.runtime.VmException.ProgramValue; import org.pkl.core.util.AnsiStringBuilder; @@ -137,7 +139,8 @@ public final class VmExceptionBuilder { public VmExceptionBuilder cannotFindMember(VmObjectLike receiver, Object memberKey) { if (memberKey instanceof Identifier identifier) { - return cannotFindProperty(receiver, identifier, true, false); + return cannotFindProperty( + receiver, receiver, identifier, true, MemberLookupMode.EXPLICIT_RECEIVER, null); } if (memberKey instanceof String string) { var candidates = KeyLookupSuggestions.forObject(receiver, string); @@ -153,19 +156,21 @@ public final class VmExceptionBuilder { public VmExceptionBuilder cannotFindProperty( VmObjectLike receiver, + VmObjectLike owner, Identifier propertyName, // true -> property read, false -> property definition boolean isRead, - boolean isImplicitReceiver) { + MemberLookupMode memberLookupMode, + @Nullable PklNode receiverNode) { var memberKinds = isRead ? EnumSet.allOf(Kind.class) : EnumSet.of(Kind.PROPERTY); var candidates = - new MemberLookupSuggestions(receiver, propertyName, -1, memberKinds) - .find(isImplicitReceiver); + new MemberLookupSuggestions(receiver, owner, propertyName, -1, memberKinds) + .find(memberLookupMode, receiverNode); if (candidates.isEmpty()) { - if (isImplicitReceiver) { + if (memberLookupMode.isImplicitReceiver()) { // probably wouldn't be useful to list all members in scope (includes all pkl.base members) evalError("cannotFindPropertyInScope", propertyName); } else if (receiver.isModuleObject()) { @@ -183,7 +188,7 @@ public final class VmExceptionBuilder { propertyNames.isEmpty() ? "(none)" : new MultilineValue(propertyNames)); } } else { - if (isImplicitReceiver) { + if (memberLookupMode.isImplicitReceiver()) { evalError( "cannotFindPropertyInScopeListCandidates", propertyName, @@ -208,14 +213,20 @@ public final class VmExceptionBuilder { // `foo.bar()` public VmExceptionBuilder cannotFindMethod( - VmObjectLike receiver, Identifier methodName, int arity, boolean isImplicitReceiver) { + VmObjectLike receiver, + VmObjectLike owner, + Identifier methodName, + int arity, + MemberLookupMode memberLookupMode, + boolean hasTypeArgs, + @Nullable PklNode receiverNode) { var candidates = - new MemberLookupSuggestions(receiver, methodName, arity, EnumSet.allOf(Kind.class)) - .find(isImplicitReceiver); + new MemberLookupSuggestions(receiver, owner, methodName, arity, EnumSet.allOf(Kind.class)) + .find(memberLookupMode, receiverNode); if (candidates.isEmpty()) { - if (isImplicitReceiver) { + if (memberLookupMode.isImplicitReceiver()) { // probably not useful to list all members in scope (includes all pkl.base members) evalError("cannotFindMethodInScope", methodName); } else if (receiver.isModuleObject()) { @@ -232,7 +243,15 @@ public final class VmExceptionBuilder { new MultilineValue(collectMethodNames(receiver.getVmClass()))); } } else { - if (isImplicitReceiver) { + var exactPropertyMatch = + candidates.stream() + .filter((it) -> it.nameSimilarity() == 1 && it.kind() == Kind.PROPERTY) + .findFirst(); + if (hasTypeArgs && exactPropertyMatch.isPresent()) { + var candidate = exactPropertyMatch.get(); + evalError( + "cannotFindMethodMaybeComparisonExpr", methodName, candidate.memberNameWithReceiver()); + } else if (memberLookupMode.isImplicitReceiver()) { evalError( "cannotFindMethodInScopeListCandidates", methodName, new MultilineValue(candidates)); } else if (receiver.isModuleObject()) { diff --git a/pkl-core/src/main/java/org/pkl/core/util/StringSimilarity.java b/pkl-core/src/main/java/org/pkl/core/util/StringSimilarity.java index 3b7350740..ef1f8c5ef 100644 --- a/pkl-core/src/main/java/org/pkl/core/util/StringSimilarity.java +++ b/pkl-core/src/main/java/org/pkl/core/util/StringSimilarity.java @@ -21,7 +21,7 @@ import java.util.Arrays; /** * The Jaro–Winkler distance metric is designed and best suited for short strings such as person * names, and to detect typos; it is (roughly) a variation of Damerau-Levenshtein, where the - * substitution of 2 close characters is considered less important then the substitution of 2 + * substitution of 2 close characters is considered less important than the substitution of 2 * characters that a far from each other. Jaro-Winkler was developed in the area of record linkage * (duplicate detection) (Winkler, 1990). It returns a value in the interval [0.0, 1.0]. The * distance is computed as 1 - Jaro-Winkler similarity. diff --git a/pkl-core/src/main/resources/org/pkl/core/errorMessages.properties b/pkl-core/src/main/resources/org/pkl/core/errorMessages.properties index eda95bd55..4127c5d68 100644 --- a/pkl-core/src/main/resources/org/pkl/core/errorMessages.properties +++ b/pkl-core/src/main/resources/org/pkl/core/errorMessages.properties @@ -419,6 +419,14 @@ Cannot reference property `{0}` because it is defined inside external class `{1} cannotFindMethodInScope=\ Cannot find method `{0}`. +cannotFindMethodMaybeComparisonExpr=\ +Cannot find method `{0}`.\n\ +\n\ +This code is interpreted as a method call with type arguments.\n\ +If you meant to write a comparison expression instead, wrap the left-hand side with parentheses:\n\ +\n\ +({1}) < ... + cannotFindMethodInScopeListCandidates=\ Cannot find method `{0}`.\n\ \n\ diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/api/benchmarkModule.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/api/benchmarkModule.pkl index d4a2cf3ce..df310c113 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/api/benchmarkModule.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/api/benchmarkModule.pkl @@ -30,8 +30,8 @@ facts { isVerbose = false }.run() - run::().iterations == 3 - run::().iterations == 3 + run().iterations == 3 + run().iterations == 3 } ["run output benchmark (in verbose mode)"] { diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/api/reference.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/api/reference.pkl index 39ff79611..7056ebd64 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/api/reference.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/api/reference.pkl @@ -17,11 +17,11 @@ class D extends ref.AbstractDomain { local const d: D = new {} typealias Ref = ref.Reference -const function Ref(data: Any): Ref = ref.Reference::(d, data) +const function Ref(data: Any): Ref = ref.Reference(d, data) abstract class Resource { name: String - hidden fixed $: Ref = Ref::(this) + hidden fixed $: Ref = Ref(this) } class A extends Resource { @@ -123,32 +123,32 @@ kInterpolation = "\(k)" aValuesJoined = k.aValues.join("\n") // ensure that type arguments that are unions are handled correctly -typeArgs = Ref::(null).prop as Ref> +typeArgs = Ref(null).prop as Ref> class TypeHolder { prop: Listing mappingAnyKey: Mapping mappingTypeArgKey: Mapping, Int> } -typeArgs2 = Ref::>(null) as Ref> +typeArgs2 = Ref>(null) as Ref> class BadResource extends Resource { // return a reference with referent that isn't `this` - fixed $ = Ref::(this) + fixed $ = Ref(this) } badThisRef = test.catch(() -> new BadResource { name = "bad" }.$.toString()) // test that a bare function type toStrings/exports okay -bareFunctionType = test.catch(() -> ref.Reference::(d, null) as Ref) +bareFunctionType = test.catch(() -> ref.Reference(d, null) as Ref) // test for allowing constraints during reference construction -constraintInConstructor = Ref:: 5)>(null) +constraintInConstructor = Ref 5)>(null) -mappingAnyKey = Ref::(null).mappingAnyKey["foo"] +mappingAnyKey = Ref(null).mappingAnyKey["foo"] // this one should be an editor error but can't be caught in the runtime // because VmListing/VmMapping don't track their type args -mappingTypeArgKey = Ref ::(null).mappingTypeArgKey[new Listing { "foo" }] +mappingTypeArgKey = Ref(null).mappingTypeArgKey[new Listing { "foo" }] output { renderer { diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/api/referenceWithFinalModuleType.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/api/referenceWithFinalModuleType.pkl index 71efab512..dec2a8a11 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/api/referenceWithFinalModuleType.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/api/referenceWithFinalModuleType.pkl @@ -14,7 +14,7 @@ hidden children: List typealias R = ref.Reference -res0 = ref.Reference::(new D {}, null) +res0 = ref.Reference(new D {}, null) res1: R = res0.parent res2: R = res0.parent.parent res3: R = res0.children[0] diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/api/referenceWithOpenModuleType.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/api/referenceWithOpenModuleType.pkl index 62976ce5d..b578c3088 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/api/referenceWithOpenModuleType.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/api/referenceWithOpenModuleType.pkl @@ -16,7 +16,7 @@ hidden children: List typealias R = ref.Reference -res0 = ref.Reference::(new D {}, null) +res0 = ref.Reference(new D {}, null) res1: R = res0.parent res2: R = res0.parent.parent res3: R = res0.children[0] diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/api/referenceWithThisType.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/api/referenceWithThisType.pkl index 868f011c7..dd437cff9 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/api/referenceWithThisType.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/api/referenceWithThisType.pkl @@ -22,14 +22,14 @@ open class Node2 { typealias R = ref.Reference typealias R2 = ref.Reference -res0: R = ref.Reference::(new D {}, null) +res0: R = ref.Reference(new D {}, null) res1: R = res0.parent res2: R = res0.parent.parent res3: R = res0.children[0] res4: R = res0.children[0].parent res5: R = res0.children[0].children[0] -res0a: R2 = ref.Reference::(new D {}, null) +res0a: R2 = ref.Reference(new D {}, null) res1a: R2 = res0a.parent res2a: R2 = res0a.parent.parent res3a: R2 = res0a.children[0] diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/basic/reference.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/basic/reference.pkl index 992d56ad4..24d02811c 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/basic/reference.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/basic/reference.pkl @@ -25,9 +25,9 @@ local class OtherHolder { constrainedType: String(isEmpty) } -local holderRef: Ref = ref.Reference::(d, null) -local holderRef2: Ref = ref.Reference::(d, null) -local otherHolderRef: Ref = ref.Reference::(d, null) +local holderRef: Ref = ref.Reference(d, null) +local holderRef2: Ref = ref.Reference(d, null) +local otherHolderRef: Ref = ref.Reference(d, null) facts { ["equality"] { diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/functionNotFoundMaybeComparison.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/functionNotFoundMaybeComparison.pkl new file mode 100644 index 000000000..ca5a7aabd --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/functionNotFoundMaybeComparison.pkl @@ -0,0 +1,9 @@ +abstract class Base { + prop = 10 +} + +class Child extends Base { + result = prop < foo > (1) +} + +result = new Child {}.result diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/functionNotFoundMaybeComparison2.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/functionNotFoundMaybeComparison2.pkl new file mode 100644 index 000000000..007801c98 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/functionNotFoundMaybeComparison2.pkl @@ -0,0 +1,9 @@ +abstract class Base { + prop = 10 +} + +class Child extends Base { + result = super.prop < foo > (1) +} + +result = new Child {}.result diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/functionNotFoundMaybeComparison3.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/functionNotFoundMaybeComparison3.pkl new file mode 100644 index 000000000..09615dd10 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/functionNotFoundMaybeComparison3.pkl @@ -0,0 +1,11 @@ +abstract class Base { + prop = 10 +} + +class Child extends Base { + local self = this + + result = self.prop < foo > (1) +} + +result = new Child {}.result diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/functionNotFoundMaybeComparison4.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/functionNotFoundMaybeComparison4.pkl new file mode 100644 index 000000000..a6de1071f --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/functionNotFoundMaybeComparison4.pkl @@ -0,0 +1,10 @@ +// `fooz` and `fool` both fit the string similarity threshold, +// but `fooz` is an exact match. So, still show the specialized error +// message about comparisons instead of generic method call +fooz = true + +fool = false + +res { + fooz < bar > (fool) +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/functionNotFoundMaybeComparison5.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/functionNotFoundMaybeComparison5.pkl new file mode 100644 index 000000000..6b2b9fee2 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/functionNotFoundMaybeComparison5.pkl @@ -0,0 +1,19 @@ +fooz = true + +fool = false + +class Foo { + prop1: Int + prop2: Int + prop3: Int + qux: Boolean +} + +res { + // exercise super long receivers + new Foo { + prop1 = 1 + prop2 = 2 + prop3 = 3 + }.qux < bar > (fool) +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference1.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference1.pkl index 5c7ec40be..758869b43 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference1.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference1.pkl @@ -6,4 +6,4 @@ class D extends ref.AbstractDomain { local d: D = new {} typealias Ref = ref.Reference -test = ref.Reference::(d, "") as Ref +test = ref.Reference(d, "") as Ref diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference13.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference13.pkl index b56ccae56..e1f033b49 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference13.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference13.pkl @@ -5,4 +5,4 @@ class D extends ref.AbstractDomain { } local d: D = new {} -test = ref.Reference::(d, "").default +test = ref.Reference(d, "").default diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference14.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference14.pkl index 6e7ee1bd3..c08ccfc9b 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference14.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference14.pkl @@ -5,4 +5,4 @@ class D extends ref.AbstractDomain { } local d: D = new {} -test = ref.Reference::(d, "").default +test = ref.Reference(d, "").default diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference15.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference15.pkl index b639c52c6..3af70c24d 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference15.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference15.pkl @@ -5,4 +5,4 @@ class D extends ref.AbstractDomain { } local d: D = new {} -test = ref.Reference::(d, "").default +test = ref.Reference(d, "").default diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference16.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference16.pkl index 56b98d5e0..dece76b99 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference16.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference16.pkl @@ -7,4 +7,4 @@ class D extends ref.AbstractDomain { } local d: D = new {} -test = ref.Reference::(d, "").output +test = ref.Reference(d, "").output diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference17.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference17.pkl index 104185d84..58f327b0d 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference17.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference17.pkl @@ -7,4 +7,4 @@ class D extends ref.AbstractDomain { } local d: D = new {} -test = ref.Reference::(d, "").output +test = ref.Reference(d, "").output diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference18.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference18.pkl index fea785237..ea9db50e3 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference18.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference18.pkl @@ -9,4 +9,4 @@ class D extends ref.AbstractDomain { } local d: D = new {} -test = ref.Reference::(d, "").output +test = ref.Reference(d, "").output diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference19.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference19.pkl index 0c94fab71..c16aa444a 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference19.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference19.pkl @@ -9,4 +9,4 @@ class D extends ref.AbstractDomain { } local d: D = new {} -test = ref.Reference::(d, "").foo as ref.Reference +test = ref.Reference(d, "").foo as ref.Reference diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference2.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference2.pkl index 3831d8083..64c46ce73 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference2.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference2.pkl @@ -6,4 +6,4 @@ class D extends ref.AbstractDomain { local d: D = new {} typealias Ref = ref.Reference -test = ref.Reference::(d, "") as Ref +test = ref.Reference(d, "") as Ref diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference20.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference20.pkl index 2cfc93e6b..62d6f9c76 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference20.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference20.pkl @@ -9,7 +9,7 @@ class D extends ref.AbstractDomain { } local d: D = new {} -local test = ref.Reference::(d, "").foo +local test = ref.Reference(d, "").foo testInterpolation = "test:\(test)" // this tests that the interpolation appears in the output when renderReference throws diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference24.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference24.pkl index 25fa5d958..28d528b1f 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference24.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference24.pkl @@ -8,6 +8,6 @@ class Bird { name: String } -myRef: ref.Reference = ref.Reference::(new D {}, null) +myRef: ref.Reference = ref.Reference(new D {}, null) res = myRef.name diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference25.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference25.pkl index e528f3354..1171b545c 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference25.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference25.pkl @@ -4,6 +4,6 @@ class D extends ref.AbstractDomain { function renderReference(_): String = throw("not implemented") } -local myRef = ref.Reference::>(new D {}, null) +local myRef = ref.Reference>(new D {}, null) res = myRef[0] diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference26.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference26.pkl index b3117c248..c4d920dc1 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference26.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference26.pkl @@ -4,6 +4,6 @@ class D extends ref.AbstractDomain { function renderReference(_): String = throw("not implemented") } -local myRef = ref.Reference::(new D {}, null) +local myRef = ref.Reference(new D {}, null) res = myRef.name diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference27.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference27.pkl index df2c992e4..a8d61494a 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference27.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference27.pkl @@ -4,6 +4,6 @@ class D extends ref.AbstractDomain { function renderReference(_): String = throw("not implemented") } -local myRef = ref.Reference:: Boolean>(new D {}, null) +local myRef = ref.Reference Boolean>(new D {}, null) res = myRef.name diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference3.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference3.pkl index dbbf8150f..d2fa27d7a 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference3.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference3.pkl @@ -6,4 +6,4 @@ class D extends ref.AbstractDomain { local d: D = new {} typealias Ref = ref.Reference -test = ref.Reference::(d, "") as Ref> +test = ref.Reference(d, "") as Ref> diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference4.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference4.pkl index 95ce36cf8..011171b25 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference4.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference4.pkl @@ -6,7 +6,7 @@ class D extends ref.AbstractDomain { local d: D = new {} typealias Ref = ref.Reference -test = ref.Reference::(d, "") as Ref +test = ref.Reference(d, "") as Ref typealias Alias1 = Int | Alias2 typealias Alias2 = Boolean diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference5.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference5.pkl index 31cca0418..9a4ecf4ad 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference5.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference5.pkl @@ -11,4 +11,4 @@ class D2 extends ref.AbstractDomain { } local d2: D2 = new {} -test = ref.Reference::(d2, "") as Ref +test = ref.Reference(d2, "") as Ref diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference6.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference6.pkl index 524800df3..32960badd 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference6.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference6.pkl @@ -10,4 +10,4 @@ class A { b: String } -test = ref.Reference::(d, new A {}).c +test = ref.Reference(d, new A {}).c diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference7.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference7.pkl index 2feb3a5d3..7e4ea0d9e 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference7.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference7.pkl @@ -6,4 +6,4 @@ class D extends ref.AbstractDomain { local d: D = new {} typealias Ref = ref.Reference -test = ref.Reference::(d, List())["hi"] +test = ref.Reference(d, List())["hi"] diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArguments.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArguments.pkl index fbd9b6c2e..4190630d8 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArguments.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArguments.pkl @@ -8,14 +8,14 @@ class Domain extends ref.AbstractDomain { } directCall { - pair = Pair::, Set>(List("foo"), Set("bar")) - pairSelf = Pair::(new module {}, new module {}).first.getClass().toString() - pairErr = test.catch(() -> Pair::, Set>(List(0), Set("bar"))) - pairSelfErr = test.catch(() -> Pair::(List(), new module {})) - listMap = List("hi", "foo", "a").map::((it) -> it.length) - listMapErr = test.catch(() -> List("hi", "foo", "a").map::((it) -> it.length)) - wrongTypeArgCount = test.catch(() -> Pair:: ("foo", 10)) - nonGenericMethod = test.catch(() -> Regex:: ("foo")) + pair = Pair, Set>(List("foo"), Set("bar")) + pairSelf = Pair(new module {}, new module {}).first.getClass().toString() + pairErr = test.catch(() -> Pair, Set>(List(0), Set("bar"))) + pairSelfErr = test.catch(() -> Pair(List(), new module {})) + listMap = List("hi", "foo", "a").map((it) -> it.length) + listMapErr = test.catch(() -> List("hi", "foo", "a").map((it) -> it.length)) + wrongTypeArgCount = test.catch(() -> Pair ("foo", 10)) + nonGenericMethod = test.catch(() -> Regex ("foo")) } open class A { @@ -23,12 +23,12 @@ open class A { } class B extends A { - function doIt(arg: List): Boolean = super.doIt::(arg) || super.doIt::>(arg) + function doIt(arg: List): Boolean = super.doIt(arg) || super.doIt>(arg) } superCall { local function doIt(a: A, arg: List): Boolean = - a.doIt::(arg) + a.doIt(arg) // should be true: doIt(new A {}, List("")) @@ -45,16 +45,16 @@ local num = 5 objectMethod { local function foo(): T = "hi" - res = test.catch(() -> foo:: num)>()) + res = test.catch(() -> foo num)>()) } function lazy1(it): Listing = new Listing { it } local function lazy2(it): Listing = new Listing { it } lazyTypeCheck { local function lazy3(it): Listing = new Listing { it } - res1 = test.catch(() -> lazy1::(5)) - res2 = test.catch(() -> lazy2::(5)) - res3 = test.catch(() -> lazy3::(5)) + res1 = test.catch(() -> lazy1(5)) + res2 = test.catch(() -> lazy2(5)) + res3 = test.catch(() -> lazy3(5)) eager { local myListing: Listing = new { "Hello, world!" } @@ -62,7 +62,7 @@ lazyTypeCheck { result = myListing is T } // should be false: - res = myFunc::>().result + res = myFunc>().result } closure { @@ -70,7 +70,7 @@ lazyTypeCheck { local function myFunc() = new { result = myListing as T } - res = test.catch(() -> myFunc::>()) + res = test.catch(() -> myFunc>()) } } @@ -86,13 +86,13 @@ typeDefaults { new { name = val } } local function makeFromParam(val: T): Listing = new { val } - res1 = makeNamed::("bob") + res1 = makeNamed("bob") res1a = res1.getClass().toString() - res2 = makeNamedListing::("bob") + res2 = makeNamedListing("bob") res2a = res2.first.getClass().toString() - res3 = makeFromParam::(new { name = "bob" }) + res3 = makeFromParam(new { name = "bob" }) res3a = res3.first.getClass().toString() - res3b = test.catch(() -> makeFromParam::(new Bird { name = "joe" })) + res3b = test.catch(() -> makeFromParam(new Bird { name = "joe" })) } class Bird { @@ -104,10 +104,10 @@ class Bird { polymorphism { // test that method type parameters are considered non-final local function make(): T = new T { name = "fred" } - res1 = make::() - res2 = make::() + res1 = make() + res2 = make() - local function call(obj: Bird | Person) = obj.greet::() + local function call(obj: Bird | Person) = obj.greet() res3 = call(new Person { name = "fred" }) res4 = test.catch(() -> call(new Bird { name = "pigeon" })) res5 = call(new Person { name = "bob" }) @@ -116,21 +116,21 @@ polymorphism { typeCast { local function cast(l: Listing): Listing = l - res1 = cast::(new Listing { "Bobbert" }) + res1 = cast(new Listing { "Bobbert" }) - res2 = test.catch(() -> cast:: 10)>(res1).toString()) + res2 = test.catch(() -> cast 10)>(res1).toString()) } constraintReadsFrameSlot { local function id(x) = x is T - local function f(n: Int) = id:: n)>("hi") + local function f(n: Int) = id n)>("hi") res1 = f(1) - res2 = let (n = 1) id:: n)>("hi") - res3 = ((n) -> id:: n)>("hi")).apply(1) + res2 = let (n = 1) id n)>("hi") + res3 = ((n) -> id n)>("hi")).apply(1) res4 = new Listing { for (n in List(1)) { - id:: n)>("hi") + id n)>("hi") } } } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArgumentsConstrainedType.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArgumentsConstrainedType.pkl index 8bd4fcc7b..42c08bd04 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArgumentsConstrainedType.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArgumentsConstrainedType.pkl @@ -7,23 +7,23 @@ local function prefix4() = "hi" class Bar { x: Int = 10 - function baz(val): Boolean = foo::(val) - function qux(val): Boolean = foo::(val) + function baz(val): Boolean = foo(val) + function qux(val): Boolean = foo(val) } res { - res1a = foo::("high five") - res1b = foo::("high five") - res1c = foo::("high five") - res1d = foo::("high five") + res1a = foo("high five") + res1b = foo("high five") + res1c = foo("high five") + res1d = foo("high five") - res2a = new Bar {}.baz::("high five") - res2b = new Bar {}.baz::("high five") - res2c = new Bar {}.baz::("high five") - res2d = new Bar {}.baz::("high five") + res2a = new Bar {}.baz("high five") + res2b = new Bar {}.baz("high five") + res2c = new Bar {}.baz("high five") + res2d = new Bar {}.baz("high five") - res3a = new Bar {}.qux::("high five") - res3b = new Bar {}.qux::("high five") - res3c = new Bar {}.qux::("high five") - res3d = new Bar {}.qux::("high five") + res3a = new Bar {}.qux("high five") + res3b = new Bar {}.qux("high five") + res3c = new Bar {}.qux("high five") + res3d = new Bar {}.qux("high five") } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArgumentsLexical.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArgumentsLexical.pkl index 108f45df5..5ccb90414 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArgumentsLexical.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArgumentsLexical.pkl @@ -15,13 +15,13 @@ class Person { lexical { local function f(): Dynamic = new { res = 5 as T } - res1 = test.catch(() -> f::().res) + res1 = test.catch(() -> f().res) local function g(): Dynamic = new { local myProp: T = 5 res = myProp } - res2 = test.catch(() -> g::().res) + res2 = test.catch(() -> g().res) local function nested() = new Dynamic { local function inner(x: Listing, y: U) = x @@ -30,40 +30,40 @@ lexical { local function inner4() = new Dynamic { local function innest1(x: T, y: U, z: V) = x local function innest2(x: T) = x - res1 = innest1::(new { name = "x" }, 1, "hi") + res1 = innest1(new { name = "x" }, 1, "hi") res2 = innest1(new { name = "x" }, 1, "hi") res3 = test.catch(() -> innest2(1)) } - res1 = inner::(new { new { name = "x" } }, 1) - res2 = inner2::(new { name = "x" }, 1) + res1 = inner(new { new { name = "x" } }, 1) + res2 = inner2(new { name = "x" }, 1) res3 = inner(new { new { name = "x" } }, 1) res4 = inner2(new { name = "x" }, 1) res5 = test.catch(() -> inner3(1)) - nested = inner4::() + nested = inner4() } - nested = nested::() + nested = nested() inEagerScope { local function k1() = new { [let (elem = "key" as T) elem] = "value" } - test.catch(() -> k1::()) + test.catch(() -> k1()) local function k2() = new { for (i in List(1, 2, 3)) { [let (elem = "key".repeat(i) as T) elem as T] = "value" } } - k2::() + k2() } reify { local function makeMyObj() = new { - res = ref.Reference::(new {}, null) + res = ref.Reference(new {}, null) } - res = makeMyObj::().res.name + res = makeMyObj().res.name } } @@ -74,18 +74,18 @@ local function nested(foo) = new Dynamic { local function inner4() = new Dynamic { local function innest1(x: T, y: U, z: V) = x local function innest2(x: T) = x - res1 = innest1::(new { name = "x" }, 1, "hi") + res1 = innest1(new { name = "x" }, 1, "hi") res2 = innest1(new { name = "x" }, 1, "hi") res3 = test.catch(() -> innest2(1)) } - res1 = inner::(new { new { name = "x" } }, foo) - res2 = inner2::(new { name = "x" }, 1) + res1 = inner(new { new { name = "x" } }, foo) + res2 = inner2(new { name = "x" }, 1) res3 = inner(new { new { name = "x" } }, 1) res4 = inner2(new { name = "x" }, 1) res5 = test.catch(() -> inner3(1)) - nested = inner4::() + nested = inner4() } -nested = nested::(1) +nested = nested(1) output { renderer { diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArgumentsNestedWithOffset.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArgumentsNestedWithOffset.pkl index 94b1ce46d..97146ff8b 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArgumentsNestedWithOffset.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArgumentsNestedWithOffset.pkl @@ -3,6 +3,6 @@ local function make(): Listing = new Listing { 1; 2 } local function amend(l: Listing): Listing = (l) { 3 } as Listing -local parent = make::() -local child = amend::(parent) +local parent = make() +local child = amend(parent) res = child[0] diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArgumentsSelfType.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArgumentsSelfType.pkl index 5927263d6..454a8243e 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArgumentsSelfType.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArgumentsSelfType.pkl @@ -1,18 +1,18 @@ open class A { function foo(val): List = List(val is T, val is U, val is V) - function a(val): List = this.foo::(val) + function a(val): List = this.foo(val) } open class B extends A { - function b1(val): List = this.a::(val) - function b2(val): List = super.a::(val) + function b1(val): List = this.a(val) + function b2(val): List = super.a(val) } class C extends B { - function c1(val): List = this.b1::(val) - function c2(val): List = super.b1::(val) - function c3(val): List = this.b2::(val) - function c4(val): List = super.b2::(val) + function c1(val): List = this.b1(val) + function c2(val): List = super.b1(val) + function c3(val): List = this.b2(val) + function c4(val): List = super.b2(val) } resAaA = new A {}.a(new A {}) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArgumentsStacktrace.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArgumentsStacktrace.pkl index a515758a1..7dfadc313 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArgumentsStacktrace.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArgumentsStacktrace.pkl @@ -1,5 +1,5 @@ function foo(bar) = bar as T -function baz(qux) = foo::>(qux) +function baz(qux) = foo>(qux) -res = baz::(List("a")) +res = baz(List("a")) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArgumentsStacktrace2.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArgumentsStacktrace2.pkl index 757786f35..bb9d78c47 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArgumentsStacktrace2.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArgumentsStacktrace2.pkl @@ -4,4 +4,4 @@ function bar() = new { prop = foo as T } -res = bar::() +res = bar() diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/functionNotFoundMaybeComparison.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/functionNotFoundMaybeComparison.err new file mode 100644 index 000000000..b64662530 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/functionNotFoundMaybeComparison.err @@ -0,0 +1,23 @@ +–– Pkl Error –– +Cannot find method `prop`. + +x | result = prop < foo > (1) + ^^^^^^^^^^^^^^^^ +at functionNotFoundMaybeComparison#Child.result (file:///$snippetsDir/input/errors/functionNotFoundMaybeComparison.pkl) + +This code is interpreted as a method call with type arguments. +If you meant to write a comparison expression instead, wrap the left-hand side with parentheses: + +(prop) < ... + +x | result = new Child {}.result + ^^^^^^^^^^^^^^^^^^^ +at functionNotFoundMaybeComparison#result (file:///$snippetsDir/input/errors/functionNotFoundMaybeComparison.pkl) + +xxx | renderer.renderDocument(value) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +at pkl.base#Module.output.text (pkl:base) + +xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8") + ^^^^ +at pkl.base#Module.output.bytes (pkl:base) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/functionNotFoundMaybeComparison2.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/functionNotFoundMaybeComparison2.err new file mode 100644 index 000000000..240f3be08 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/functionNotFoundMaybeComparison2.err @@ -0,0 +1,23 @@ +–– Pkl Error –– +Cannot find method `prop`. + +x | result = super.prop < foo > (1) + ^^^^^^^^^^^^^^^^^^^^^^ +at functionNotFoundMaybeComparison2#Child.result (file:///$snippetsDir/input/errors/functionNotFoundMaybeComparison2.pkl) + +This code is interpreted as a method call with type arguments. +If you meant to write a comparison expression instead, wrap the left-hand side with parentheses: + +(super.prop) < ... + +x | result = new Child {}.result + ^^^^^^^^^^^^^^^^^^^ +at functionNotFoundMaybeComparison2#result (file:///$snippetsDir/input/errors/functionNotFoundMaybeComparison2.pkl) + +xxx | renderer.renderDocument(value) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +at pkl.base#Module.output.text (pkl:base) + +xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8") + ^^^^ +at pkl.base#Module.output.bytes (pkl:base) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/functionNotFoundMaybeComparison3.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/functionNotFoundMaybeComparison3.err new file mode 100644 index 000000000..21e49bbea --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/functionNotFoundMaybeComparison3.err @@ -0,0 +1,23 @@ +–– Pkl Error –– +Cannot find method `prop`. + +x | result = self.prop < foo > (1) + ^^^^^^^^^^^^^^^^^^^^^ +at functionNotFoundMaybeComparison3#Child.result (file:///$snippetsDir/input/errors/functionNotFoundMaybeComparison3.pkl) + +This code is interpreted as a method call with type arguments. +If you meant to write a comparison expression instead, wrap the left-hand side with parentheses: + +(self.prop) < ... + +xx | result = new Child {}.result + ^^^^^^^^^^^^^^^^^^^ +at functionNotFoundMaybeComparison3#result (file:///$snippetsDir/input/errors/functionNotFoundMaybeComparison3.pkl) + +xxx | renderer.renderDocument(value) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +at pkl.base#Module.output.text (pkl:base) + +xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8") + ^^^^ +at pkl.base#Module.output.bytes (pkl:base) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/functionNotFoundMaybeComparison4.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/functionNotFoundMaybeComparison4.err new file mode 100644 index 000000000..c1a6ea883 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/functionNotFoundMaybeComparison4.err @@ -0,0 +1,19 @@ +–– Pkl Error –– +Cannot find method `fooz`. + +x | fooz < bar > (fool) + ^^^^^^^^^^^^^^^^^^^ +at functionNotFoundMaybeComparison4#res[#1] (file:///$snippetsDir/input/errors/functionNotFoundMaybeComparison4.pkl) + +This code is interpreted as a method call with type arguments. +If you meant to write a comparison expression instead, wrap the left-hand side with parentheses: + +(fooz) < ... + +xxx | renderer.renderDocument(value) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +at pkl.base#Module.output.text (pkl:base) + +xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8") + ^^^^ +at pkl.base#Module.output.bytes (pkl:base) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/functionNotFoundMaybeComparison5.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/functionNotFoundMaybeComparison5.err new file mode 100644 index 000000000..6c373a648 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/functionNotFoundMaybeComparison5.err @@ -0,0 +1,23 @@ +–– Pkl Error –– +Cannot find method `qux`. + +xx | new Foo { + ^^^^^^^^^ +at functionNotFoundMaybeComparison5#res[#1] (file:///$snippetsDir/input/errors/functionNotFoundMaybeComparison5.pkl) + +This code is interpreted as a method call with type arguments. +If you meant to write a comparison expression instead, wrap the left-hand side with parentheses: + +(new Foo { + prop1 = 1 + prop2 = 2 + prop3 = 3 + }.qux) < ... + +xxx | renderer.renderDocument(value) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +at pkl.base#Module.output.text (pkl:base) + +xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8") + ^^^^ +at pkl.base#Module.output.bytes (pkl:base) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference1.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference1.err index 0e0692538..2f79a436c 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference1.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference1.err @@ -6,8 +6,8 @@ x | typealias Ref = ref.Reference ^^^^^^^^^^^^^^^^^^^ at reference1#test (file:///$snippetsDir/input/errors/reference1.pkl) -x | test = ref.Reference::(d, "") as Ref - ^^^^^^^^ +x | test = ref.Reference(d, "") as Ref + ^^^^^^^^ at reference1#test (file:///$snippetsDir/input/errors/reference1.pkl) xxx | renderer.renderDocument(value) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference13.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference13.err index a12b25b05..9d49f38cf 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference13.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference13.err @@ -1,8 +1,8 @@ –– Pkl Error –– Cannot find property `default` in object of type `pkl.ref#Reference`. -x | test = ref.Reference::(d, "").default - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +x | test = ref.Reference(d, "").default + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ at reference13#test (file:///$snippetsDir/input/errors/reference13.pkl) Cannot reference property `default` in class `Mapping`. diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference14.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference14.err index 4e8004dbb..d5496afbb 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference14.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference14.err @@ -1,8 +1,8 @@ –– Pkl Error –– Cannot find property `default` in object of type `pkl.ref#Reference`. -x | test = ref.Reference::(d, "").default - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +x | test = ref.Reference(d, "").default + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ at reference14#test (file:///$snippetsDir/input/errors/reference14.pkl) Cannot reference property `default` in class `Listing`. diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference15.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference15.err index 99403efe8..ea80bfeff 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference15.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference15.err @@ -1,8 +1,8 @@ –– Pkl Error –– Cannot find property `default` in object of type `pkl.ref#Reference`. -x | test = ref.Reference::(d, "").default - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +x | test = ref.Reference(d, "").default + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ at reference15#test (file:///$snippetsDir/input/errors/reference15.pkl) Cannot reference property `default` in class `Dynamic`. diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference16.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference16.err index d13fb50de..866dfe848 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference16.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference16.err @@ -1,8 +1,8 @@ –– Pkl Error –– Cannot find property `output` in object of type `pkl.ref#Reference`. -xx | test = ref.Reference::(d, "").output - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +xx | test = ref.Reference(d, "").output + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ at reference16#test (file:///$snippetsDir/input/errors/reference16.pkl) Cannot reference property `output` because it is defined inside external class `Module`. diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference17.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference17.err index 6cb83f716..ca9244b9c 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference17.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference17.err @@ -1,8 +1,8 @@ –– Pkl Error –– Cannot find property `output` in object of type `pkl.ref#Reference`. -xx | test = ref.Reference::(d, "").output - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +xx | test = ref.Reference(d, "").output + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ at reference17#test (file:///$snippetsDir/input/errors/reference17.pkl) Cannot reference property `output` because it is defined inside external class `Module`. diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference18.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference18.err index 0e82888d7..894710682 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference18.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference18.err @@ -1,8 +1,8 @@ –– Pkl Error –– Cannot find property `output` in object of type `pkl.ref#Reference`. -xx | test = ref.Reference::(d, "").output - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +xx | test = ref.Reference(d, "").output + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ at reference18#test (file:///$snippetsDir/input/errors/reference18.pkl) Cannot reference property `output` because it is defined inside external class `Module`. diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference19.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference19.err index 7fb0dae99..98c333642 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference19.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference19.err @@ -2,8 +2,8 @@ Expected value of type `pkl.ref#Reference`, but got type `pkl.ref#Reference`. Value: Reference(new reference19#D {}, Boolean | Int | String, "").foo -xx | test = ref.Reference::(d, "").foo as ref.Reference - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +xx | test = ref.Reference(d, "").foo as ref.Reference + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ at reference19#test (file:///$snippetsDir/input/errors/reference19.pkl) xxx | renderer.renderDocument(value) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference2.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference2.err index 5d0132e0a..0aba64178 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference2.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference2.err @@ -6,8 +6,8 @@ x | typealias Ref = ref.Reference ^^^^^^^^^^^^^^^^^^^ at reference2#test (file:///$snippetsDir/input/errors/reference2.pkl) -x | test = ref.Reference::(d, "") as Ref - ^^^^^^^^^^^^^^^^^^ +x | test = ref.Reference(d, "") as Ref + ^^^^^^^^^^^^^^^^^^ at reference2#test (file:///$snippetsDir/input/errors/reference2.pkl) xxx | renderer.renderDocument(value) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference3.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference3.err index 58ee5ec48..a8636d164 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference3.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference3.err @@ -6,8 +6,8 @@ x | typealias Ref = ref.Reference ^^^^^^^^^^^^^^^^^^^ at reference3#test (file:///$snippetsDir/input/errors/reference3.pkl) -x | test = ref.Reference::(d, "") as Ref> - ^^^^^^^^^^^^^^^^^^^^ +x | test = ref.Reference(d, "") as Ref> + ^^^^^^^^^^^^^^^^^^^^ at reference3#test (file:///$snippetsDir/input/errors/reference3.pkl) xxx | renderer.renderDocument(value) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference4.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference4.err index eb826b1d7..865cf3ca9 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference4.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference4.err @@ -6,8 +6,8 @@ x | typealias Ref = ref.Reference ^^^^^^^^^^^^^^^^^^^ at reference4#test (file:///$snippetsDir/input/errors/reference4.pkl) -x | test = ref.Reference::(d, "") as Ref - ^^^^^^^^^^^ +x | test = ref.Reference(d, "") as Ref + ^^^^^^^^^^^ at reference4#test (file:///$snippetsDir/input/errors/reference4.pkl) xxx | renderer.renderDocument(value) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference5.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference5.err index f3ee1c67d..318079dfb 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference5.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference5.err @@ -6,8 +6,8 @@ x | typealias Ref = ref.Reference ^^^^^^^^^^^^^^^^^^^ at reference5#test (file:///$snippetsDir/input/errors/reference5.pkl) -xx | test = ref.Reference::(d2, "") as Ref - ^^^^^^^^^^^ +xx | test = ref.Reference(d2, "") as Ref + ^^^^^^^^^^^ at reference5#test (file:///$snippetsDir/input/errors/reference5.pkl) xxx | renderer.renderDocument(value) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference6.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference6.err index 0e360c3b0..a7d294217 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference6.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference6.err @@ -1,8 +1,8 @@ –– Pkl Error –– Cannot find property `c` in object of type `pkl.ref#Reference`. -xx | test = ref.Reference::(d, new A {}).c - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +xx | test = ref.Reference(d, new A {}).c + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ at reference6#test (file:///$snippetsDir/input/errors/reference6.pkl) Cannot find property `c` in type `String`. diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference7.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference7.err index 37612a1a7..94deaeadc 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference7.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference7.err @@ -3,8 +3,8 @@ Operator `[]` is not defined for operand types `pkl.ref#Reference(d, List())["hi"] - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +x | test = ref.Reference(d, List())["hi"] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ at reference7#test (file:///$snippetsDir/input/errors/reference7.pkl) Operator `[]` is not defined for operand types `List` and `String`. diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/methods/callWithTypeArgumentsStacktrace.err b/pkl-core/src/test/files/LanguageSnippetTests/output/methods/callWithTypeArgumentsStacktrace.err index d68cabc08..de5d9eb70 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/methods/callWithTypeArgumentsStacktrace.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/methods/callWithTypeArgumentsStacktrace.err @@ -1,24 +1,24 @@ –– Pkl Error –– oops -x | res = baz::(List("a")) - ^^^^^^^^^^^^^ +x | res = baz(List("a")) + ^^^^^^^^^^^^^ at callWithTypeArgumentsStacktrace#res. (file:///$snippetsDir/input/methods/callWithTypeArgumentsStacktrace.pkl) -x | function baz(qux) = foo::>(qux) - ^ +x | function baz(qux) = foo>(qux) + ^ at callWithTypeArgumentsStacktrace#baz. (file:///$snippetsDir/input/methods/callWithTypeArgumentsStacktrace.pkl) x | function foo(bar) = bar as T ^ at callWithTypeArgumentsStacktrace#foo (file:///$snippetsDir/input/methods/callWithTypeArgumentsStacktrace.pkl) -x | function baz(qux) = foo::>(qux) - ^^^^^^^^^^^^^^^^^^^ +x | function baz(qux) = foo>(qux) + ^^^^^^^^^^^^^^^^^ at callWithTypeArgumentsStacktrace#baz (file:///$snippetsDir/input/methods/callWithTypeArgumentsStacktrace.pkl) -x | res = baz::(List("a")) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +x | res = baz(List("a")) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ at callWithTypeArgumentsStacktrace#res (file:///$snippetsDir/input/methods/callWithTypeArgumentsStacktrace.pkl) xxx | renderer.renderDocument(value) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/methods/callWithTypeArgumentsStacktrace2.err b/pkl-core/src/test/files/LanguageSnippetTests/output/methods/callWithTypeArgumentsStacktrace2.err index efa6a3fa4..b6760f402 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/methods/callWithTypeArgumentsStacktrace2.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/methods/callWithTypeArgumentsStacktrace2.err @@ -2,8 +2,8 @@ Expected value of type `Duration`, but got type `Int`. Value: 5 -x | res = bar::() - ^^^^^^^^ +x | res = bar() + ^^^^^^^^ at callWithTypeArgumentsStacktrace2#bar.prop (file:///$snippetsDir/input/methods/callWithTypeArgumentsStacktrace2.pkl) x | prop = foo as T diff --git a/pkl-core/src/test/kotlin/org/pkl/core/EvaluatorTest.kt b/pkl-core/src/test/kotlin/org/pkl/core/EvaluatorTest.kt index 9d7015733..b626e327a 100644 --- a/pkl-core/src/test/kotlin/org/pkl/core/EvaluatorTest.kt +++ b/pkl-core/src/test/kotlin/org/pkl/core/EvaluatorTest.kt @@ -930,7 +930,7 @@ class EvaluatorTest { evaluator.evaluateOutputText( text( """ - res = List(1, 2, 3).map::((it) -> it + 1) + res = List(1, 2, 3).map((it) -> it + 1) """ .trimIndent() ) diff --git a/pkl-formatter/src/main/java/org/pkl/formatter/Builder.java b/pkl-formatter/src/main/java/org/pkl/formatter/Builder.java index 6f6242284..3f5d495cc 100644 --- a/pkl-formatter/src/main/java/org/pkl/formatter/Builder.java +++ b/pkl-formatter/src/main/java/org/pkl/formatter/Builder.java @@ -261,7 +261,7 @@ final class Builder { * {@code foo.bar.baz( * new { qux = 1 } * )} - * {@code foo.bar.baz::< + * {@code foo.bar.baz< * Quux * >( * new { qux = 1 } @@ -368,7 +368,7 @@ final class Builder { var argListIdx = -1; for (var i = 0; i < lastNode.children.size(); i++) { var node = lastNode.children.get(i); - if (node.type == NodeType.DCOLON) { + if (node.type == NodeType.TYPE_ARGUMENT_LIST) { typeArgListIdx = i; continue; } diff --git a/pkl-formatter/src/test/files/FormatterSnippetTests/input/method-call-type-args.pkl b/pkl-formatter/src/test/files/FormatterSnippetTests/input/method-call-type-args.pkl index 0e5fccf27..a9a21e00e 100644 --- a/pkl-formatter/src/test/files/FormatterSnippetTests/input/method-call-type-args.pkl +++ b/pkl-formatter/src/test/files/FormatterSnippetTests/input/method-call-type-args.pkl @@ -1,14 +1,14 @@ -res1 = foo::(baz) -res1a = foo::(baz, baz, reallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongArg) +res1 = foo(baz) +res1a = foo(baz, baz, reallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongArg) -res2 = qux.foo::(baz, baz2) -res2a = qux.foo::(baz, baz, reallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongArg) -res2b = qux.quux::().foo::(baz, baz2) -res2c = qux.quux::().foo::(baz, baz, reallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongArg) -res2d = qux::().foo::(baz, baz2) -res2e = qux::().foo::(baz, baz, reallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongArg) -res2f = grault.qux.quux::().foo::(baz, baz2) -res2g = grault.qux.quux::().foo::(baz, baz, reallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongArg) +res2 = qux.foo(baz, baz2) +res2a = qux.foo(baz, baz, reallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongArg) +res2b = qux.quux().foo(baz, baz2) +res2c = qux.quux().foo(baz, baz, reallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongArg) +res2d = qux().foo(baz, baz2) +res2e = qux().foo(baz, baz, reallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongArg) +res2f = grault.qux.quux().foo(baz, baz2) +res2g = grault.qux.quux().foo(baz, baz, reallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongArg) -res3 = super.foo::(baz, baz2) -res3a = super.foo::(baz, baz, reallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongArg) +res3 = super.foo(baz, baz2) +res3a = super.foo(baz, baz, reallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongArg) diff --git a/pkl-formatter/src/test/files/FormatterSnippetTests/output/method-call-type-args.pkl b/pkl-formatter/src/test/files/FormatterSnippetTests/output/method-call-type-args.pkl index 5b0fc4e60..b3e2b6262 100644 --- a/pkl-formatter/src/test/files/FormatterSnippetTests/output/method-call-type-args.pkl +++ b/pkl-formatter/src/test/files/FormatterSnippetTests/output/method-call-type-args.pkl @@ -1,6 +1,6 @@ -res1 = foo::(baz) +res1 = foo(baz) res1a = - foo::< + foo< Bar, Baz, ReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongType, @@ -10,9 +10,9 @@ res1a = reallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongArg, ) -res2 = qux.foo::(baz, baz2) +res2 = qux.foo(baz, baz2) res2a = - qux.foo::< + qux.foo< Bar, Baz, ReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongType, @@ -21,11 +21,11 @@ res2a = baz, reallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongArg, ) -res2b = qux.quux::().foo::(baz, baz2) +res2b = qux.quux().foo(baz, baz2) res2c = qux - .quux::() - .foo::< + .quux() + .foo< Bar, Baz, ReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongType, @@ -34,10 +34,10 @@ res2c = baz, reallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongArg, ) -res2d = qux::().foo::(baz, baz2) +res2d = qux().foo(baz, baz2) res2e = - qux::() - .foo::< + qux() + .foo< Bar, Baz, ReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongType, @@ -46,11 +46,11 @@ res2e = baz, reallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongArg, ) -res2f = grault.qux.quux::().foo::(baz, baz2) +res2f = grault.qux.quux().foo(baz, baz2) res2g = grault.qux - .quux::() - .foo::< + .quux() + .foo< Bar, Baz, ReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongType, @@ -60,9 +60,9 @@ res2g = reallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongArg, ) -res3 = super.foo::(baz, baz2) +res3 = super.foo(baz, baz2) res3a = - super.foo::< + super.foo< Bar, Baz, ReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongType, diff --git a/pkl-parser/src/main/java/org/pkl/parser/Constants.java b/pkl-parser/src/main/java/org/pkl/parser/Constants.java new file mode 100644 index 000000000..ccd46794b --- /dev/null +++ b/pkl-parser/src/main/java/org/pkl/parser/Constants.java @@ -0,0 +1,28 @@ +/* + * Copyright © 2026 Apple Inc. and the Pkl project authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pkl.parser; + +final class Constants { + private Constants() {} + + /** + * Max number of tokens consumed during speculative parsing before failure. + * + *

5 million token budget takes about 300ms before reaching the error case (tested on a MacBook + * Pro M4 Max). + */ + static final int MAX_SPECULATIVE_TOKEN_COUNT = 5_000_000; +} diff --git a/pkl-parser/src/main/java/org/pkl/parser/GenericParser.java b/pkl-parser/src/main/java/org/pkl/parser/GenericParser.java index b6bad75a7..fc457c044 100644 --- a/pkl-parser/src/main/java/org/pkl/parser/GenericParser.java +++ b/pkl-parser/src/main/java/org/pkl/parser/GenericParser.java @@ -22,12 +22,21 @@ public final class GenericParser { var parser = new GenericParserImpl(source); try { return parser.parseModule(); - } catch (StackOverflowError e) { + } catch (MaxBacktrackCountError err) { + throw err.toGenericParserError(); + } catch (StackOverflowError err) { throw parser.tooDeeplyNested(); } } public Node parseExpressionInput(String source) { - return new GenericParserImpl(source).parseExpressionInput(); + var parser = new GenericParserImpl(source); + try { + return parser.parseExpressionInput(); + } catch (MaxBacktrackCountError err) { + throw err.toGenericParserError(); + } catch (StackOverflowError err) { + throw parser.tooDeeplyNested(); + } } } diff --git a/pkl-parser/src/main/java/org/pkl/parser/GenericParserImpl.java b/pkl-parser/src/main/java/org/pkl/parser/GenericParserImpl.java index 8718b86b5..e0dd53ac2 100644 --- a/pkl-parser/src/main/java/org/pkl/parser/GenericParserImpl.java +++ b/pkl-parser/src/main/java/org/pkl/parser/GenericParserImpl.java @@ -15,9 +15,13 @@ */ package org.pkl.parser; +import static org.pkl.parser.Constants.MAX_SPECULATIVE_TOKEN_COUNT; + import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Objects; +import java.util.Set; import java.util.function.Supplier; import org.jspecify.annotations.Nullable; import org.pkl.parser.syntax.Operator; @@ -25,6 +29,7 @@ import org.pkl.parser.syntax.generic.FullSpan; import org.pkl.parser.syntax.generic.Node; import org.pkl.parser.syntax.generic.NodeType; import org.pkl.parser.util.ErrorMessages; +import org.pkl.parser.util.SortedIntArray; @SuppressWarnings("DuplicatedCode") class GenericParserImpl { @@ -35,6 +40,11 @@ class GenericParserImpl { private FullToken _lookahead; private int cursor = 0; private final List tokens = new ArrayList<>(); + private int speculativeTokenCount = 0; + private boolean isSpeculativeParse = false; + private final Set failedTypeArgAttempts = new HashSet<>(); + // char indices of `<` that might start a type argument list + private @Nullable SortedIntArray typeArgCandidates; GenericParserImpl(String source) { this.lexer = new Lexer(source); @@ -745,18 +755,12 @@ class GenericParserImpl { private void parseAccess(List children) { children.add(parseIdentifier()); - if (lookahead() == Token.DCOLON && noSemicolonInbetween() && _lookahead.newLinesBetween == 0) { - children.add(makeTerminal(next())); + var typeArgs = tryParseTypeArgumentList(); + if (typeArgs != null) { + // `tryParseTypeArgumentList` guarantees that an argument list follows + children.add(typeArgs); ff(children); - children.add(parseTypeArgumentList()); - if (lookahead() == Token.LPAREN - && noSemicolonInbetween() - && _lookahead.newLinesBetween == 0) { - ff(children); - children.add(parseArgumentList()); - } else { - throw parserError(ErrorMessages.create("unexpectedToken", "("), spanLookahead); - } + children.add(parseArgumentList()); } else if (lookahead() == Token.LPAREN && noSemicolonInbetween() && _lookahead.newLinesBetween == 0) { @@ -765,6 +769,48 @@ class GenericParserImpl { } } + private @Nullable Node tryParseTypeArgumentList() { + if (lookahead() != Token.LT + || !noSemicolonInbetween() + || _lookahead.newLinesBetween != 0 + || failedTypeArgAttempts.contains(spanLookahead.charIndex()) + || !isTypeArgCandidate(spanLookahead.charIndex())) { + return null; + } + var savedCursor = cursor; + var savedCharIndex = spanLookahead.charIndex(); + var savedIsSpeculativeParse = isSpeculativeParse; + isSpeculativeParse = true; + try { + var typeArgs = parseTypeArgumentList(); + if (lookahead() == Token.LPAREN + && noSemicolonInbetween() + && _lookahead.newLinesBetween == 0) { + return typeArgs; + } + } catch (GenericParserError ignored) { + // not a type argument list + } finally { + isSpeculativeParse = savedIsSpeculativeParse; + } + failedTypeArgAttempts.add(savedCharIndex); + backtrackTo(savedCursor); + return null; + } + + private boolean isTypeArgCandidate(int charIndex) { + if (typeArgCandidates == null) { + var scanner = new TypeArgumentCandidatesScanner(); + scanner.addPending(charIndex); + for (var i = cursor + 1; i < tokens.size(); i++) { + var tk = tokens.get(i); + scanner.accept(tk.token, tk.span.charIndex()); + } + typeArgCandidates = scanner.candidates(); + } + return typeArgCandidates.contains(charIndex); + } + private Node parseExprAtom(@Nullable String expectation) { var expr = switch (lookahead) { @@ -1506,6 +1552,12 @@ class GenericParserImpl { } private FullToken next() { + if (isSpeculativeParse) { + if (speculativeTokenCount > MAX_SPECULATIVE_TOKEN_COUNT) { + throw new MaxBacktrackCountError(spanLookahead); + } + speculativeTokenCount++; + } var tmp = tokens.get(cursor++); _lookahead = tokens.get(cursor); lookahead = _lookahead.token; @@ -1517,15 +1569,10 @@ class GenericParserImpl { return tokens.get(cursor - 1).token != Token.SEMICOLON; } - private void backtrack() { - var tmp = tokens.get(--cursor); - lookahead = tmp.token; - spanLookahead = tmp.span; - } - private void backtrackTo(int point) { cursor = point; var tmp = tokens.get(cursor); + _lookahead = tmp; lookahead = tmp.token; spanLookahead = tmp.span; } @@ -1611,7 +1658,6 @@ class GenericParserImpl { case ASSIGN -> NodeType.ASSIGN; case AT -> NodeType.AT; case COLON -> NodeType.COLON; - case DCOLON -> NodeType.DCOLON; case COMMA -> NodeType.COMMA; case DOT -> NodeType.DOT; case GT -> NodeType.GT; diff --git a/pkl-parser/src/main/java/org/pkl/parser/Lexer.java b/pkl-parser/src/main/java/org/pkl/parser/Lexer.java index 01556d0ab..b1a0ad9ab 100644 --- a/pkl-parser/src/main/java/org/pkl/parser/Lexer.java +++ b/pkl-parser/src/main/java/org/pkl/parser/Lexer.java @@ -85,6 +85,49 @@ public final class Lexer { return new String(source, offset, size); } + /** Captures the lexer's current state so that it can later be rewound via {@link #restore}. */ + public Snapshot snapshot() { + var scopes = new ArrayDeque(); + // `ArrayDeque.push` adds to the front, so iterate in reverse to preserve order. + var it = interpolationStack.descendingIterator(); + while (it.hasNext()) { + scopes.push(it.next().copy()); + } + return new Snapshot( + cursor, + sCursor, + line, + sLine, + col, + sCol, + lookahead, + state, + scopes, + stringEnded, + isEscape, + newLinesBetween); + } + + public void restore(Snapshot snapshot) { + cursor = snapshot.cursor; + sCursor = snapshot.sCursor; + line = snapshot.line; + sLine = snapshot.sLine; + col = snapshot.col; + sCol = snapshot.sCol; + lookahead = snapshot.lookahead; + state = snapshot.state; + interpolationStack.clear(); + // copy again so that the snapshot can be restored more than once + var it = snapshot.interpolationStack.descendingIterator(); + while (it.hasNext()) { + interpolationStack.push(it.next().copy()); + } + stringEnded = snapshot.stringEnded; + isEscape = snapshot.isEscape; + newLinesBetween = snapshot.newLinesBetween; + } + public Token next() { sCursor = cursor; sLine = line; @@ -137,12 +180,7 @@ public final class Lexer { case '}' -> Token.RBRACE; case ',' -> Token.COMMA; case '@' -> Token.AT; - case ':' -> { - if (lookahead == ':') { - nextChar(); - yield Token.DCOLON; - } else yield Token.COLON; - } + case ':' -> Token.COLON; case '+' -> Token.PLUS; case '%' -> Token.MOD; case '[' -> { @@ -864,8 +902,28 @@ public final class Lexer { this.quotes = quotes; this.pounds = pounds; } + + InterpolationScope copy() { + var res = new InterpolationScope(quotes, pounds); + res.parens = parens; + return res; + } } + public record Snapshot( + int cursor, + int sCursor, + int line, + int sLine, + int col, + int sCol, + int lookahead, + State state, + Deque interpolationStack, + boolean stringEnded, + boolean isEscape, + int newLinesBetween) {} + private enum State { DEFAULT, STRING diff --git a/pkl-parser/src/main/java/org/pkl/parser/MaxBacktrackCountError.java b/pkl-parser/src/main/java/org/pkl/parser/MaxBacktrackCountError.java new file mode 100644 index 000000000..2243b4d05 --- /dev/null +++ b/pkl-parser/src/main/java/org/pkl/parser/MaxBacktrackCountError.java @@ -0,0 +1,51 @@ +/* + * Copyright © 2026 Apple Inc. and the Pkl project authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pkl.parser; + +import org.jspecify.annotations.Nullable; +import org.pkl.parser.syntax.generic.FullSpan; +import org.pkl.parser.util.ErrorMessages; + +final class MaxBacktrackCountError extends RuntimeException { + + private final @Nullable Span span; + private final @Nullable FullSpan fullSpan; + + public MaxBacktrackCountError(Span span) { + this.span = span; + this.fullSpan = null; + } + + public MaxBacktrackCountError(FullSpan span) { + this.span = null; + this.fullSpan = span; + } + + @Override + public String getMessage() { + return ErrorMessages.create("maxBacktrackCount"); + } + + public ParserError toParserError() { + assert span != null; + return new ParserError(getMessage(), span); + } + + public GenericParserError toGenericParserError() { + assert fullSpan != null; + return new GenericParserError(getMessage(), fullSpan); + } +} diff --git a/pkl-parser/src/main/java/org/pkl/parser/Parser.java b/pkl-parser/src/main/java/org/pkl/parser/Parser.java index edfa76203..74ba5cdcd 100644 --- a/pkl-parser/src/main/java/org/pkl/parser/Parser.java +++ b/pkl-parser/src/main/java/org/pkl/parser/Parser.java @@ -26,7 +26,9 @@ public final class Parser { var parser = new ParserImpl(source); try { return parser.parseModule(); - } catch (StackOverflowError e) { + } catch (MaxBacktrackCountError err) { + throw err.toParserError(); + } catch (StackOverflowError err) { throw parser.tooDeeplyNested(); } } @@ -35,7 +37,9 @@ public final class Parser { var parser = new ParserImpl(source); try { return parser.parseExpressionInput(); - } catch (StackOverflowError e) { + } catch (MaxBacktrackCountError err) { + throw err.toParserError(); + } catch (StackOverflowError err) { throw parser.tooDeeplyNested(); } } @@ -44,7 +48,9 @@ public final class Parser { var parser = new ParserImpl(source); try { return parser.parseReplInput(); - } catch (StackOverflowError e) { + } catch (MaxBacktrackCountError err) { + throw err.toParserError(); + } catch (StackOverflowError err) { throw parser.tooDeeplyNested(); } } diff --git a/pkl-parser/src/main/java/org/pkl/parser/ParserImpl.java b/pkl-parser/src/main/java/org/pkl/parser/ParserImpl.java index 614401503..dbc12fb68 100644 --- a/pkl-parser/src/main/java/org/pkl/parser/ParserImpl.java +++ b/pkl-parser/src/main/java/org/pkl/parser/ParserImpl.java @@ -15,10 +15,14 @@ */ package org.pkl.parser; +import static org.pkl.parser.Constants.MAX_SPECULATIVE_TOKEN_COUNT; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.function.Supplier; import org.jspecify.annotations.Nullable; import org.pkl.parser.syntax.Annotation; @@ -86,10 +90,10 @@ import org.pkl.parser.syntax.TypeArgumentList; import org.pkl.parser.syntax.TypeParameter; import org.pkl.parser.syntax.TypeParameterList; import org.pkl.parser.util.ErrorMessages; +import org.pkl.parser.util.SortedIntArray; @SuppressWarnings("DuplicatedCode") final class ParserImpl { - private final Lexer lexer; private Token lookahead; private Span spanLookahead; @@ -97,8 +101,15 @@ final class ParserImpl { private FullToken prev = new FullToken(Token.SOF, new Span(0, 0), 0); private FullToken _lookahead; private boolean precededBySemicolon = false; + private int speculativeTokenCount = 0; + private final Set failedTypeArgAttempts = new HashSet<>(); + private boolean isSpeculativeParse = false; + private final String source; + // char indices of `<` that might start a type argument list + private @Nullable SortedIntArray typeArgCandidates; ParserImpl(String source) { + this.source = source; this.lexer = new Lexer(source); _lookahead = forceNext(); lookahead = _lookahead.token; @@ -831,21 +842,12 @@ final class ParserImpl { case DOT, QDOT -> { var rhs = parseIdentifier(); var isNullable = op == Operator.QDOT; - TypeArgumentList typeArgumentList = null; - if (lookahead == Token.DCOLON - && !precededBySemicolon - && _lookahead.newLinesBetween == 0) { - next(); - typeArgumentList = parseTypeArgumentList(); - } + var typeArgumentList = tryParseTypeArgumentList(); ArgumentList argumentList = null; if (lookahead == Token.LPAREN && !precededBySemicolon && _lookahead.newLinesBetween == 0) { argumentList = parseArgumentList(); - } else if (typeArgumentList != null) { - // type args but no args - throw new ParserError(ErrorMessages.create("unexpectedToken", "("), spanLookahead); } var lastSpan = argumentList != null ? argumentList.span() : rhs.span(); expr = @@ -992,25 +994,14 @@ final class ParserImpl { if (lookahead == Token.DOT) { next(); var identifier = parseIdentifier(); - TypeArgumentList typeArgs = null; - if (lookahead == Token.DCOLON - && !precededBySemicolon - && _lookahead.newLinesBetween == 0) { - next(); - typeArgs = parseTypeArgumentList(); - } + var typeArgs = tryParseTypeArgumentList(); if (lookahead == Token.LPAREN && !precededBySemicolon && _lookahead.newLinesBetween == 0) { var args = parseArgumentList(); yield new SuperAccessExpr(identifier, typeArgs, args, start.endWith(args.span())); } - if (typeArgs != null) { - // type args but no args - throw new ParserError(ErrorMessages.create("unexpectedToken", "("), spanLookahead); - } else { - yield new SuperAccessExpr(identifier, null, null, start.endWith(identifier.span())); - } + yield new SuperAccessExpr(identifier, null, null, start.endWith(identifier.span())); } else { expect(Token.LBRACK, "unexpectedToken", "["); var exp = parseExpr("]"); @@ -1052,13 +1043,7 @@ final class ParserImpl { case STRING_MULTI_START -> parseMultiLineStringLiteralExpr(); case IDENTIFIER -> { var identifier = parseIdentifier(); - TypeArgumentList typeArgs = null; - if (lookahead == Token.DCOLON - && !precededBySemicolon - && _lookahead.newLinesBetween == 0) { - next(); - typeArgs = parseTypeArgumentList(); - } + var typeArgs = tryParseTypeArgumentList(); if (lookahead == Token.LPAREN && !precededBySemicolon && _lookahead.newLinesBetween == 0) { @@ -1066,12 +1051,7 @@ final class ParserImpl { yield new UnqualifiedAccessExpr( identifier, typeArgs, args, identifier.span().endWith(args.span())); } - if (typeArgs != null) { - // type args but no args - throw new ParserError(ErrorMessages.create("unexpectedToken", "("), spanLookahead); - } else { - yield new UnqualifiedAccessExpr(identifier, null, null, identifier.span()); - } + yield new UnqualifiedAccessExpr(identifier, null, null, identifier.span()); } case EOF -> throw new ParserError( @@ -1111,17 +1091,10 @@ final class ParserImpl { else if (lookahead == Token.DOT || lookahead == Token.QDOT) { var isNullable = next().token == Token.QDOT; var identifier = parseIdentifier(); - TypeArgumentList typeArgumentList = null; - if (lookahead == Token.DCOLON && !precededBySemicolon && _lookahead.newLinesBetween == 0) { - next(); - typeArgumentList = parseTypeArgumentList(); - } + var typeArgumentList = tryParseTypeArgumentList(); ArgumentList argumentList = null; if (lookahead == Token.LPAREN && !precededBySemicolon && _lookahead.newLinesBetween == 0) { argumentList = parseArgumentList(); - } else if (typeArgumentList != null) { - // type args but no args - throw new ParserError(ErrorMessages.create("unexpectedToken", "("), spanLookahead); } var lastSpan = argumentList != null ? argumentList.span() : identifier.span(); expr = @@ -1603,6 +1576,53 @@ final class ParserImpl { return new TypeArgumentList(pars, start.endWith(end)); } + private @Nullable TypeArgumentList tryParseTypeArgumentList() { + if (lookahead != Token.LT + || precededBySemicolon + || _lookahead.newLinesBetween != 0 + || failedTypeArgAttempts.contains(spanLookahead.charIndex()) + || !isTypeArgCandidate(spanLookahead.charIndex())) { + return null; + } + var lexerSnapshot = lexer.snapshot(); + var savedLookahead = lookahead; + var savedSpanLookahead = spanLookahead; + var savedBacktracking = backtracking; + var savedPrev = prev; + var savedFullLookahead = _lookahead; + var savedPrecededBySemicolon = precededBySemicolon; + var savedIsSpeculativeParse = isSpeculativeParse; + isSpeculativeParse = true; + try { + var typeArgs = parseTypeArgumentList(); + if (lookahead == Token.LPAREN && !precededBySemicolon && _lookahead.newLinesBetween == 0) { + return typeArgs; + } + } catch (ParserError ignored) { + // not a type argument list + } finally { + isSpeculativeParse = savedIsSpeculativeParse; + } + failedTypeArgAttempts.add(savedSpanLookahead.charIndex()); + lexer.restore(lexerSnapshot); + lookahead = savedLookahead; + spanLookahead = savedSpanLookahead; + backtracking = savedBacktracking; + prev = savedPrev; + _lookahead = savedFullLookahead; + precededBySemicolon = savedPrecededBySemicolon; + return null; + } + + private boolean isTypeArgCandidate(int charIndex) { + if (typeArgCandidates == null) { + var snapshot = lexer.snapshot(); + typeArgCandidates = TypeArgumentCandidatesScanner.scanFromPending(lexer); + lexer.restore(snapshot); + } + return typeArgCandidates.contains(charIndex); + } + private ArgumentList parseArgumentList() { var start = expect(Token.LPAREN, "unexpectedToken", "(").span; if (lookahead == Token.RPAREN) { @@ -1877,6 +1897,12 @@ final class ParserImpl { } private FullToken next() { + if (isSpeculativeParse) { + if (speculativeTokenCount > MAX_SPECULATIVE_TOKEN_COUNT) { + throw new MaxBacktrackCountError(spanLookahead); + } + speculativeTokenCount++; + } if (backtracking) { backtracking = false; lookahead = _lookahead.token; diff --git a/pkl-parser/src/main/java/org/pkl/parser/Token.java b/pkl-parser/src/main/java/org/pkl/parser/Token.java index ba0034eae..dcdcfb672 100644 --- a/pkl-parser/src/main/java/org/pkl/parser/Token.java +++ b/pkl-parser/src/main/java/org/pkl/parser/Token.java @@ -89,7 +89,6 @@ public enum Token { NOT, QUESTION, COLON, - DCOLON, ARROW, EQUAL, NOT_EQUAL, diff --git a/pkl-parser/src/main/java/org/pkl/parser/TypeArgumentCandidatesScanner.java b/pkl-parser/src/main/java/org/pkl/parser/TypeArgumentCandidatesScanner.java new file mode 100644 index 000000000..4a6054bbe --- /dev/null +++ b/pkl-parser/src/main/java/org/pkl/parser/TypeArgumentCandidatesScanner.java @@ -0,0 +1,117 @@ +/* + * Copyright © 2026 Apple Inc. and the Pkl project authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pkl.parser; + +import org.pkl.parser.util.IntArray; +import org.pkl.parser.util.SortedIntArray; + +/** + * Finds all {@code <} tokens might be the start of a method call's type argument list, e.g. the + * {@code } in {@code foo(5)}. + * + *

A candidate must have balanced {@code <} and {@code >} pairs and followed by {@code (}. + * + *

Candidates are not necessarily starts of type argument lists. For example, {@code + * foo<5>()} is not one because {@code 5} is not a type. + * + *

However, this class doesn't know what a type is; this is meant to be a quick scan of the whole + * file to help minimize speculative parsing in the underlying parser. + */ +final class TypeArgumentCandidatesScanner { + // marks a `(`, `[`, `{` or string interpolation on the stack; `<` is stored as its char index + private static final int BRACKET = -100; + + // char index of every candidate, in the order they were discovered + private final IntArray found = new IntArray(); + // `<` that are still open, and the brackets that enclose them + private final IntArray pending = new IntArray(); + // char index of the `<` matching the most recent `>`, if we haven't seen the next token yet + private int currentPending = -1; + + static SortedIntArray scan(Lexer lexer) { + var scanner = new TypeArgumentCandidatesScanner(); + try { + while (true) { + var token = lexer.next(); + if (token == Token.EOF) break; + scanner.accept(token, lexer.getStartCursor()); + } + } catch (ParserError ignored) { + // Lexing error. The parser will report it when it gets there; `<` that are still open at + // this point are simply not candidates. + } + return scanner.candidates(); + } + + static SortedIntArray scanFromPending(Lexer lexer) { + var scanner = new TypeArgumentCandidatesScanner(); + scanner.pending.push(lexer.getStartCursor()); + try { + while (true) { + var token = lexer.next(); + if (token == Token.EOF) break; + scanner.accept(token, lexer.getStartCursor()); + } + } catch (ParserError ignored) { + // Lexing error. The parser will report it when it gets there; `<` that are still open at + // this point are simply not candidates. + } + return scanner.candidates(); + } + + /** + * The indices of the {@code <} tokens that might be type arg calls. + * + *

Must only call this after feeding all tokens. + */ + SortedIntArray candidates() { + return new SortedIntArray(found); + } + + /** Feeds the next token. Call in source order, for every token including affixes. */ + void accept(Token token, int charIndex) { + if (token.isAffix()) return; + if (currentPending >= 0) { + if (token == Token.LPAREN) { + found.push(currentPending); + } + currentPending = -1; + } + switch (token) { + case LT -> pending.push(charIndex); + case GT -> { + // a `>` without a matching `<` at the same level is a comparison operator + if (pending.isNotEmpty() && pending.get(pending.size() - 1) != BRACKET) { + currentPending = pending.pop(); + } + } + case LPAREN, LBRACK, LBRACE, INTERPOLATION_START -> pending.push(BRACKET); + case RPAREN, RBRACK, RBRACE -> { + // discard unmatched `<` (comparison operators) down to the enclosing bracket + while (true) { + if (pending.isEmpty() || pending.pop() == BRACKET) { + break; + } + } + } + default -> {} + } + } + + public void addPending(int charIndex) { + pending.push(charIndex); + } +} diff --git a/pkl-parser/src/main/java/org/pkl/parser/syntax/generic/NodeType.java b/pkl-parser/src/main/java/org/pkl/parser/syntax/generic/NodeType.java index 91ed1440c..f5e10923a 100644 --- a/pkl-parser/src/main/java/org/pkl/parser/syntax/generic/NodeType.java +++ b/pkl-parser/src/main/java/org/pkl/parser/syntax/generic/NodeType.java @@ -52,7 +52,6 @@ public enum NodeType { ASSIGN(NodeKind.TERMINAL), AT(NodeKind.TERMINAL), COLON(NodeKind.TERMINAL), - DCOLON(NodeKind.TERMINAL), COMMA(NodeKind.TERMINAL), DOT(NodeKind.TERMINAL), GT(NodeKind.TERMINAL), diff --git a/pkl-parser/src/main/java/org/pkl/parser/util/IntArray.java b/pkl-parser/src/main/java/org/pkl/parser/util/IntArray.java new file mode 100644 index 000000000..2d71d27c1 --- /dev/null +++ b/pkl-parser/src/main/java/org/pkl/parser/util/IntArray.java @@ -0,0 +1,61 @@ +/* + * Copyright © 2026 Apple Inc. and the Pkl project authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pkl.parser.util; + +import java.util.Arrays; + +public class IntArray { + int[] store = new int[16]; + private int size = 0; + + public void push(int value) { + if (size == store.length) { + store = Arrays.copyOf(store, size * 2); + } + store[size++] = value; + } + + public int pop() { + if (size == 0) return -1; + size--; + return store[size]; + } + + public int size() { + return size; + } + + public void clear() { + this.size = 0; + } + + public void sort() { + Arrays.sort(store, 0, size); + } + + public boolean isEmpty() { + return size == 0; + } + + public boolean isNotEmpty() { + return size != 0; + } + + public int get(int index) { + assert index < size; + return store[index]; + } +} diff --git a/pkl-parser/src/main/java/org/pkl/parser/util/SortedIntArray.java b/pkl-parser/src/main/java/org/pkl/parser/util/SortedIntArray.java new file mode 100644 index 000000000..74b25ffcc --- /dev/null +++ b/pkl-parser/src/main/java/org/pkl/parser/util/SortedIntArray.java @@ -0,0 +1,41 @@ +/* + * Copyright © 2026 Apple Inc. and the Pkl project authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pkl.parser.util; + +import java.util.Arrays; + +public class SortedIntArray { + private final int[] store; + private final int size; + + public SortedIntArray(IntArray array) { + array.sort(); + this.store = array.store; + this.size = array.size(); + } + + public boolean contains(int value) { + if (size == 0) return false; + if (value < store[0]) return false; + if (value > store[size - 1]) return false; + return Arrays.binarySearch(store, 0, size, value) >= 0; + } + + // for testing only + public int[] toArray() { + return Arrays.copyOf(store, size); + } +} diff --git a/pkl-parser/src/main/resources/org/pkl/parser/errorMessages.properties b/pkl-parser/src/main/resources/org/pkl/parser/errorMessages.properties index 254c94ab0..29c25534c 100644 --- a/pkl-parser/src/main/resources/org/pkl/parser/errorMessages.properties +++ b/pkl-parser/src/main/resources/org/pkl/parser/errorMessages.properties @@ -114,3 +114,6 @@ Shebangs are only allowed at the very beginning of a file. typeParameterVarianceNotAllowed=\ Variance modifiers may only be applied to type parameters on classes or typealiases. + +maxBacktrackCount=\ +Too much backtracking occurred during parsing. diff --git a/pkl-parser/src/test/kotlin/org/pkl/parser/GenericParserTest.kt b/pkl-parser/src/test/kotlin/org/pkl/parser/GenericParserTest.kt new file mode 100644 index 000000000..016934355 --- /dev/null +++ b/pkl-parser/src/test/kotlin/org/pkl/parser/GenericParserTest.kt @@ -0,0 +1,47 @@ +/* + * Copyright © 2026 Apple Inc. and the Pkl project authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pkl.parser + +import java.nio.charset.StandardCharsets +import java.time.Duration +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.api.assertTimeoutPreemptively + +class GenericParserTest { + @Test + fun `parser fails if exceeds max backtracking count`() { + val code = + javaClass + .getResourceAsStream("large_backtracking.pkl.txt")!! + .readAllBytes() + .toString(StandardCharsets.UTF_8) + val err = assertThrows { GenericParser().parseModule(code) } + assertThat(err).hasMessageContaining("Too much backtracking occurred during parsing.") + } + + // this is kind of a smoke test to make sure that we parse these cases reasonably quickly. + @Test + fun `parser succeeds if nested backtracking stays within max depth`() { + val code = + """ + result = a(a < a(a < a(a < a(a < a(a < a(a < a(a < a(a < a(a < 1))))))))) + """ + .trimIndent() + assertTimeoutPreemptively(Duration.ofMillis(10)) { GenericParser().parseModule(code) } + } +} diff --git a/pkl-parser/src/test/kotlin/org/pkl/parser/ParserComparisonTest.kt b/pkl-parser/src/test/kotlin/org/pkl/parser/ParserComparisonTest.kt index fe0e2d385..10781aaeb 100644 --- a/pkl-parser/src/test/kotlin/org/pkl/parser/ParserComparisonTest.kt +++ b/pkl-parser/src/test/kotlin/org/pkl/parser/ParserComparisonTest.kt @@ -21,58 +21,42 @@ import kotlin.io.path.extension import kotlin.io.path.pathString import kotlin.io.path.readText import org.assertj.core.api.Assertions.assertThat -import org.assertj.core.api.SoftAssertions -import org.junit.jupiter.api.Test +import org.junit.jupiter.api.DynamicTest +import org.junit.jupiter.api.TestFactory import org.junit.jupiter.api.parallel.Execution import org.junit.jupiter.api.parallel.ExecutionMode import org.pkl.commons.walk @Execution(ExecutionMode.CONCURRENT) class ParserComparisonTest { - - @Test - fun compareSnippetTests() { - SoftAssertions.assertSoftly { softly -> - getSnippets() - .parallelStream() - .map { Pair(it.pathString, it.readText()) } - .forEach { (path, snippet) -> - try { - compare(snippet, path, softly) - } catch (e: GenericParserError) { - softly.fail("path: $path. Message: ${e.message}", e) - } catch (e: ParserError) { - softly.fail("path: $path. Message: ${e.message}", e) - } - } - } - } - - fun getSnippets(): List { - return Path("../pkl-core/src/test/files/LanguageSnippetTests/input") - .walk() - .filter { path -> - val pathStr = path.toString().replace("\\", "/") - path.extension == "pkl" && - !exceptions.any { pathStr.endsWith(it) } && - !regexExceptions.any { it.matches(pathStr) } + @TestFactory + fun compareSnippetTests(): List { + val root = Path("../pkl-core/src/test/files/LanguageSnippetTests/input") + return getSnippets().map { path -> + DynamicTest.dynamicTest(root.relativize(path).pathString, path.toUri()) { + val (sexp, genSexp) = renderBoth(path.readText()) + assertThat(genSexp).isEqualTo(sexp) } - .toList() - } - - fun compare(code: String, path: String? = null, softly: SoftAssertions? = null) { - val (sexp, genSexp) = renderBoth(code) - when { - (path != null && softly != null) -> - softly.assertThat(genSexp).`as`("path: $path").isEqualTo(sexp) - else -> assertThat(genSexp).`as`("path: $path").isEqualTo(sexp) } } - fun renderBoth(code: String): Pair = + private fun renderBoth(code: String): Pair = Pair(renderCode(code), renderGenericCode(code)) companion object { + @JvmStatic + fun getSnippets(): List { + return Path("../pkl-core/src/test/files/LanguageSnippetTests/input") + .walk() + .filter { path -> + val pathStr = path.toString().replace("\\", "/") + path.extension == "pkl" && + !exceptions.any { pathStr.endsWith(it) } && + !regexExceptions.any { it.matches(pathStr) } + } + .toList() + } + private fun renderCode(code: String): String { val parser = Parser() val mod = parser.parseModule(code) diff --git a/pkl-parser/src/test/kotlin/org/pkl/parser/ParserTest.kt b/pkl-parser/src/test/kotlin/org/pkl/parser/ParserTest.kt new file mode 100644 index 000000000..08b7347f6 --- /dev/null +++ b/pkl-parser/src/test/kotlin/org/pkl/parser/ParserTest.kt @@ -0,0 +1,48 @@ +/* + * Copyright © 2026 Apple Inc. and the Pkl project authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pkl.parser + +import java.nio.charset.StandardCharsets +import java.time.Duration +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.api.assertTimeoutPreemptively + +class ParserTest { + @Test + fun `parser fails if exceeds max backtracking count`() { + val code = + javaClass + .getResourceAsStream("large_backtracking.pkl.txt")!! + .readAllBytes() + .toString(StandardCharsets.UTF_8) + val err = assertThrows { Parser().parseModule(code) } + assertThat(err).hasMessageContaining("Too much backtracking occurred during parsing.") + } + + // this is kind of a smoke test to make sure that we parse these cases reasonably quickly. + @Test + fun `parser succeeds if nested backtracking stays within max depth`() { + val code = + """ + result = a(a < a(a < a(a < a(a < a(a < a(a < a(a < a(a < a(a < 1))))))))) + """ + .trimIndent() + // 1 second should still catch if we see exponential regressions + assertTimeoutPreemptively(Duration.ofSeconds(1)) { Parser().parseModule(code) } + } +} diff --git a/pkl-parser/src/test/kotlin/org/pkl/parser/TypeArgumentCandidatesScannerTest.kt b/pkl-parser/src/test/kotlin/org/pkl/parser/TypeArgumentCandidatesScannerTest.kt new file mode 100644 index 000000000..b5452bc78 --- /dev/null +++ b/pkl-parser/src/test/kotlin/org/pkl/parser/TypeArgumentCandidatesScannerTest.kt @@ -0,0 +1,262 @@ +/* + * Copyright © 2026 Apple Inc. and the Pkl project authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pkl.parser + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +class TypeArgumentCandidatesScannerTest { + private fun candidates(source: String): IntArray = + TypeArgumentCandidatesScanner.scan(Lexer(source)).toArray() + + @Test + fun `empty input`() { + assertThat(candidates("")).isEmpty() + } + + @Test + fun `simple method call with type arguments`() { + assertThat(candidates("x = foo(5)")).isEqualTo(intArrayOf(7)) + } + + @Test + fun `comparison without closing angle bracket`() { + assertThat(candidates("x = a < b")).isEmpty() + } + + @Test + fun `closing angle bracket not followed by open paren`() { + assertThat(candidates("x = foo bar")).isEmpty() + } + + @Test + fun `less-than-or-equal and greater-than-or-equal are not angle brackets`() { + assertThat(candidates("x = a <= b > (c)")).isEmpty() + assertThat(candidates("x = a < b >= (c)")).isEmpty() + } + + @Test + fun `stray greater-than is ignored`() { + assertThat(candidates("x = a > (b)")).isEmpty() + // the first `>` has no matching `<`; the second one matches + assertThat(candidates("x = a > b < c > (d)")).isEqualTo(intArrayOf(10)) + } + + @Test + fun `nested generics - only the outermost is followed by open paren`() { + val source = "x = foo>>(5)" + assertThat(candidates(source)).isEqualTo(intArrayOf(7)) + } + + @Test + fun `multiple candidates`() { + assertThat(candidates("x = foo(bar(1))")).isEqualTo(intArrayOf(7, 16)) + assertThat(candidates("x = foo(1)\ny = bar(2)")).isEqualTo(intArrayOf(7, 23)) + } + + @Test + fun `function types - arrow is not a closing angle bracket`() { + assertThat(candidates("x = foo<(Int) -> String>(f)")).isEqualTo(intArrayOf(7)) + } + + @Test + fun `union, nullable and default types`() { + assertThat(candidates("x = foo<*Int|String?>(f)")).isEqualTo(intArrayOf(7)) + } + + // constrained types + + @Test + fun `greater-than inside a constraint does not close the type argument list`() { + assertThat(candidates("x = foo 3)>(5)")).isEqualTo(intArrayOf(7)) + } + + @Test + fun `less-than inside a constraint is not a candidate`() { + // only the outer `<` is + assertThat(candidates("x = foo(5)")).isEqualTo(intArrayOf(7)) + } + + @Test + fun `comparisons in both directions inside a constraint`() { + assertThat(candidates("x = foo 1)>(5)")).isEqualTo(intArrayOf(7)) + } + + @Test + fun `constraint with a call followed by open paren`() { + val source = "x = foo(this))>(5)" + assertThat(candidates(source)).isEqualTo(intArrayOf(7, 15)) + } + + @Test + fun `constraint in a nested type argument`() { + assertThat(candidates("x = foo 3)>>(5)")).isEqualTo(intArrayOf(7)) + } + + @Test + fun `multiple constrained type arguments`() { + assertThat(candidates("x = foo 3), String(length < 5)>(5)")).isEqualTo(intArrayOf(7)) + } + + @Test + fun `comparison inside parens does not match a greater-than outside`() { + // the `>` after `)` is at a different nesting level than the `<` + assertThat(candidates("x = (a < b) > (c)")).isEmpty() + } + + // brackets and braces + + @Test + fun `comparison inside subscript does not match a greater-than outside`() { + assertThat(candidates("x = list[a < b] > (c)")).isEmpty() + } + + @Test + fun `comparison inside object body does not match a greater-than outside`() { + assertThat(candidates("x = new Foo { y = a < b } > (c)")).isEmpty() + } + + @Test + fun `greater-than inside object body does not close an outer less-than`() { + assertThat(candidates("x = foo 0 }.length > 0)>(5)")) + .isEqualTo(intArrayOf(7)) + } + + @Test + fun `unmatched less-than inside parens is discarded when the paren closes`() { + // `<` pushed after `(` must not be left on the stack for later `>` + assertThat(candidates("x = (a < b) c > (d)")).isEmpty() + } + + @Test + fun `stray closing bracket does not break the scan`() { + assertThat(candidates(") x = foo(5)")).isEqualTo(intArrayOf(9)) + } + + // string interpolation + + @Test + fun `comparison inside string interpolation is not a candidate`() { + assertThat(candidates("""x = "\(a < b)" > (c)""")).isEmpty() + } + + @Test + fun `method call inside string interpolation`() { + assertThat(candidates("""x = "hello \(foo(1)) world"""")).isEqualTo(intArrayOf(16)) + } + + @Test + fun `greater-than inside string interpolation does not close an outer less-than`() { + assertThat(candidates("""x = foo<"\(a > b)">(5)""")).isEqualTo(intArrayOf(7)) + } + + @Test + fun `nested string interpolation`() { + assertThat(candidates("""x = "\("\(foo(1))")"""")).isEqualTo(intArrayOf(13)) + } + + @Test + fun `multiline string with interpolation`() { + val source = "x = \"\"\"\n \\(foo(1))\n \"\"\"" + assertThat(candidates(source)).isEqualTo(intArrayOf(15)) + } + + @Test + fun `angle brackets in string literal text are ignored`() { + assertThat(candidates("""x = "a < b > (c)"""")).isEmpty() + } + + // affixes between the closing angle bracket and open paren + + @Test + fun `comments between closing angle bracket and open paren`() { + assertThat(candidates("x = foo /* c */ (5)")).isEqualTo(intArrayOf(7)) + assertThat(candidates("x = foo // c\n(5)")).isEqualTo(intArrayOf(7)) + } + + @Test + fun `newline and semicolon before open paren - parser re-checks these`() { + // these are intentionally lenient; `ParserImpl` and `GenericParserImpl` reject them. + assertThat(candidates("x = foo\n(5)")).isEqualTo(intArrayOf(7)) + assertThat(candidates("x = foo; (5)")).isEqualTo(intArrayOf(7)) + } + + // robustness + + @Test + fun `lexing error ends scan without throwing`() { + // candidates found before the error are retained + assertThat(candidates("x = foo(1)\ny = \u0001")).isEqualTo(intArrayOf(7)) + // `<` still open when the error occurs is not a candidate + assertThat(candidates("x = foo(1)")).isEmpty() + } + + @Test + fun `deeply nested angle brackets`() { + val depth = 1000 + val source = "x = foo" + "".repeat(depth) + "(1)" + // only the outermost `<` has a `>` that is followed by `(` + assertThat(candidates(source)).isEqualTo(intArrayOf(7)) + } + + @Test + fun `many nested brackets`() { + val depth = 1000 + val source = "x = " + "(".repeat(depth) + "a < b" + ")".repeat(depth) + " > (c)" + assertThat(candidates(source)).isEmpty() + } + + @Test + fun `triply nested brackets`() { + val source = "x = foo < bar < baz < biz >() >() >()" + assertThat(candidates(source)).isEqualTo(intArrayOf(8, 14, 20)) + } + + @Test + fun `triply nested brackets followed by more brackets later`() { + val source = + """ + x = foo < bar < baz < biz > () > () > () + y = foo < bar > () + """ + .trimIndent() + assertThat(candidates(source)).isEqualTo(intArrayOf(8, 14, 20, 49)) + } + + @Test + fun `nested candidate within a non-candidate`() { + val source = + """ + x = a < (foo < A > (1)) > b + // ^ this is the candidate + y = bar(2) + """ + .trimIndent() + assertThat(candidates(source)).isEqualTo(intArrayOf(13, 77)) + } + + @Test + fun `sibling candidates within brackets`() { + val source = + """ + x = a < foo < A > (), bar < B > () > b + // ^ this is the candidate + y = bar(2) + """ + .trimIndent() + assertThat(candidates(source)).isEqualTo(intArrayOf(12, 26, 88)) + } +} diff --git a/pkl-parser/src/test/resources/org/pkl/parser/large_backtracking.pkl.txt b/pkl-parser/src/test/resources/org/pkl/parser/large_backtracking.pkl.txt new file mode 100644 index 000000000..e081b11e0 --- /dev/null +++ b/pkl-parser/src/test/resources/org/pkl/parser/large_backtracking.pkl.txt @@ -0,0 +1 @@ +result = a(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0)>(0) diff --git a/stdlib/ref.pkl b/stdlib/ref.pkl index 6793573a1..ad1ec4bd8 100644 --- a/stdlib/ref.pkl +++ b/stdlib/ref.pkl @@ -35,7 +35,7 @@ module pkl.ref /// /// Example: /// ``` -/// myReference: Reference> = Reference::>(domain, data) +/// myReference: Reference> = Reference>(domain, data) /// ``` external const function Reference( domain: Domain(this is AbstractDomain), @@ -153,7 +153,7 @@ external const function Reference( /// properties: Mapping /// } /// -/// local outputs: FooReference = ref.Reference::(new Foo {}, "outputs") +/// local outputs: FooReference = ref.Reference(new Foo {}, "outputs") /// /// // Dot access; gives a `Reference` /// myFile = outputs.file diff --git a/stdlib/syntax.pkl b/stdlib/syntax.pkl index 68ddea3e3..5466a4afc 100644 --- a/stdlib/syntax.pkl +++ b/stdlib/syntax.pkl @@ -305,7 +305,6 @@ typealias TerminalNodeType = | "assign" | "at" | "colon" - | "dcolon" | "comma" | "dot" | "gt"