Switch type argument syntax (#1940)
This changes type argument syntax to "standard" style, and away from turbofish. The motivation here is that turbofish-style type arguments are hard to read, and developers who are not already deeply knowledgeable about Pkl might struggle to understand such code. In most languages, the grammar for "method call with type args" don't introduce any additional symbols between the identifier and the type argument list (e.g. C++, TypeScript, C#, Scala, Go).
This commit is contained in:
1 parent
6ad50eb589
commit
a9b7fe3a33
96 files changed
+1448
-376
No files matched your search
+14
-4
@@ -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
|
||||
|
||||
@@ -6022,7 +6022,7 @@ class Domain extends ref.AbstractDomain { // <1>
|
||||
|
||||
typealias Ref<T> = ref.Reference<Domain, T> // <2>
|
||||
|
||||
const function Ref<T>(data: Any): Ref<T> = ref.Reference::<Domain, T>(new Domain {}, data) // <3>
|
||||
const function Ref<T>(data: Any): Ref<T> = ref.Reference<Domain, T>(new Domain {}, data) // <3>
|
||||
|
||||
abstract class Task { // <4>
|
||||
name: String
|
||||
@@ -6032,7 +6032,7 @@ class TaskA extends Task {
|
||||
input1: String | Ref<String> // <5>
|
||||
input2: Int | Ref<Int>
|
||||
|
||||
hidden fixed outputs: Ref<TaskAOutputs> = Ref::<TaskAOutputs>(this) // <6>
|
||||
hidden fixed outputs: Ref<TaskAOutputs> = Ref<TaskAOutputs>(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. <<defining-objects,object instantiation>>, <<type-tests,type tests>>, <<type-casts,type casts>>).
|
||||
|
||||
When calling a generic method, a caller may manually specify type arguments by writing `methodName::<TypeArg1, TypeArg2>(arg1, arg2)`.
|
||||
When calling a generic method, a caller may manually specify type arguments by writing `methodName<TypeArg1, TypeArg2>(arg1, arg2)`.
|
||||
When no type arguments are provided, type checks of parameter types behave like <<unknown-type,`unknown`>> and always succeed.
|
||||
|
||||
[source,pkl]
|
||||
@@ -6132,11 +6132,11 @@ function makeFlock<Bird>(names: Listing<String>): Listing<Bird> = new {
|
||||
}
|
||||
}
|
||||
|
||||
pigeonFlock = makeFlock::<Pigeon>(new { "Alice"; "Bill"; "Ruth" }) // <1>
|
||||
hawkFlock = makeFlock::<Hawk>(new { "Florence"; "Holly"; "Jim" }) // <2>
|
||||
pigeonFlock = makeFlock<Pigeon>(new { "Alice"; "Bill"; "Ruth" }) // <1>
|
||||
hawkFlock = makeFlock<Hawk>(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<bar>(baz)` is a method call with type arguments, whereas `foo<bar + 5>(baz)` is a comparison.
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
+9
-2
@@ -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
|
||||
|
||||
+8
-1
@@ -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();
|
||||
}
|
||||
|
||||
+10
-4
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
+9
-1
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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<? extends VmObjectLike> 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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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::<String>((it) -> it.length)
|
||||
// res = List("foo", "hello", "a").map<String>((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.
|
||||
|
||||
@@ -2300,7 +2300,7 @@ public abstract class TypeNode extends PklNode {
|
||||
// e.g.
|
||||
// ```
|
||||
// function foo<T>(arg: T)
|
||||
// res = foo::<Person>(new {}) // `Person` is passed in via `typeArgumentOverrides`
|
||||
// res = foo<Person>(new {}) // `Person` is passed in via `typeArgumentOverrides`
|
||||
// ```
|
||||
if (levelsUp == 0 && isPreCall) {
|
||||
return typeArgumentOverrides == null ? null : typeArgumentOverrides[typeParameter.index()];
|
||||
|
||||
@@ -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<Candidate> candidates = new LinkedHashSet<>();
|
||||
|
||||
public MemberLookupSuggestions(
|
||||
VmObjectLike composite, Object memberName, int memberArity, Set<Candidate.Kind> memberKinds) {
|
||||
this.composite = composite;
|
||||
VmObjectLike receiver,
|
||||
VmObjectLike owner,
|
||||
Object memberName,
|
||||
int memberArity,
|
||||
Set<Kind> 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<Candidate> find(boolean isImplicitReceiver) {
|
||||
public List<Candidate> 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<ClassMethod> methods, boolean includeLocal) {
|
||||
private void addMethodCandidates(
|
||||
Iterable<ClassMethod> 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<Candidate> {
|
||||
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<Candidate> {
|
||||
// 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
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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\
|
||||
|
||||
@@ -30,8 +30,8 @@ facts {
|
||||
isVerbose = false
|
||||
}.run()
|
||||
|
||||
run::<Int>().iterations == 3
|
||||
run::<String>().iterations == 3
|
||||
run<Int>().iterations == 3
|
||||
run<String>().iterations == 3
|
||||
}
|
||||
|
||||
["run output benchmark (in verbose mode)"] {
|
||||
|
||||
@@ -17,11 +17,11 @@ class D extends ref.AbstractDomain {
|
||||
|
||||
local const d: D = new {}
|
||||
typealias Ref<T> = ref.Reference<D, T>
|
||||
const function Ref<T>(data: Any): Ref<T> = ref.Reference::<D, T>(d, data)
|
||||
const function Ref<T>(data: Any): Ref<T> = ref.Reference<D, T>(d, data)
|
||||
|
||||
abstract class Resource {
|
||||
name: String
|
||||
hidden fixed $: Ref<this> = Ref::<this>(this)
|
||||
hidden fixed $: Ref<this> = Ref<this>(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::<TypeHolder>(null).prop as Ref<Listing<Number | Boolean | String>>
|
||||
typeArgs = Ref<TypeHolder>(null).prop as Ref<Listing<Number | Boolean | String>>
|
||||
class TypeHolder {
|
||||
prop: Listing<String | Boolean | Number>
|
||||
mappingAnyKey: Mapping<Any, Int>
|
||||
mappingTypeArgKey: Mapping<Listing<Int>, Int>
|
||||
}
|
||||
typeArgs2 = Ref::<Listing<Number | Boolean | String>>(null) as Ref<Listing<Number | Boolean | String>>
|
||||
typeArgs2 = Ref<Listing<Number | Boolean | String>>(null) as Ref<Listing<Number | Boolean | String>>
|
||||
|
||||
class BadResource extends Resource {
|
||||
// return a reference with referent that isn't `this`
|
||||
fixed $ = Ref::<A>(this)
|
||||
fixed $ = Ref<A>(this)
|
||||
}
|
||||
|
||||
badThisRef = test.catch(() -> new BadResource { name = "bad" }.$.toString())
|
||||
|
||||
// test that a bare function type toStrings/exports okay
|
||||
bareFunctionType = test.catch(() -> ref.Reference::<D, Function4>(d, null) as Ref<String>)
|
||||
bareFunctionType = test.catch(() -> ref.Reference<D, Function4>(d, null) as Ref<String>)
|
||||
|
||||
// test for allowing constraints during reference construction
|
||||
constraintInConstructor = Ref::<String(length > 5)>(null)
|
||||
constraintInConstructor = Ref<String(length > 5)>(null)
|
||||
|
||||
mappingAnyKey = Ref::<TypeHolder>(null).mappingAnyKey["foo"]
|
||||
mappingAnyKey = Ref<TypeHolder>(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 ::<TypeHolder>(null).mappingTypeArgKey[new Listing<String> { "foo" }]
|
||||
mappingTypeArgKey = Ref<TypeHolder>(null).mappingTypeArgKey[new Listing<String> { "foo" }]
|
||||
|
||||
output {
|
||||
renderer {
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ hidden children: List<module>
|
||||
|
||||
typealias R = ref.Reference<D, referenceWithFinalModuleType?>
|
||||
|
||||
res0 = ref.Reference::<D, module>(new D {}, null)
|
||||
res0 = ref.Reference<D, module>(new D {}, null)
|
||||
res1: R = res0.parent
|
||||
res2: R = res0.parent.parent
|
||||
res3: R = res0.children[0]
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ hidden children: List<module>
|
||||
|
||||
typealias R = ref.Reference<D, referenceWithOpenModuleType?>
|
||||
|
||||
res0 = ref.Reference::<D, module>(new D {}, null)
|
||||
res0 = ref.Reference<D, module>(new D {}, null)
|
||||
res1: R = res0.parent
|
||||
res2: R = res0.parent.parent
|
||||
res3: R = res0.children[0]
|
||||
|
||||
+2
-2
@@ -22,14 +22,14 @@ open class Node2 {
|
||||
typealias R = ref.Reference<D, Node?>
|
||||
typealias R2 = ref.Reference<D, Node2?>
|
||||
|
||||
res0: R = ref.Reference::<D, Node>(new D {}, null)
|
||||
res0: R = ref.Reference<D, Node>(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::<D, Node2>(new D {}, null)
|
||||
res0a: R2 = ref.Reference<D, Node2>(new D {}, null)
|
||||
res1a: R2 = res0a.parent
|
||||
res2a: R2 = res0a.parent.parent
|
||||
res3a: R2 = res0a.children[0]
|
||||
|
||||
@@ -25,9 +25,9 @@ local class OtherHolder {
|
||||
constrainedType: String(isEmpty)
|
||||
}
|
||||
|
||||
local holderRef: Ref<Holder> = ref.Reference::<D, Holder>(d, null)
|
||||
local holderRef2: Ref<Holder> = ref.Reference::<D, Holder>(d, null)
|
||||
local otherHolderRef: Ref<OtherHolder> = ref.Reference::<D, OtherHolder>(d, null)
|
||||
local holderRef: Ref<Holder> = ref.Reference<D, Holder>(d, null)
|
||||
local holderRef2: Ref<Holder> = ref.Reference<D, Holder>(d, null)
|
||||
local otherHolderRef: Ref<OtherHolder> = ref.Reference<D, OtherHolder>(d, null)
|
||||
|
||||
facts {
|
||||
["equality"] {
|
||||
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
abstract class Base {
|
||||
prop = 10
|
||||
}
|
||||
|
||||
class Child extends Base {
|
||||
result = prop < foo > (1)
|
||||
}
|
||||
|
||||
result = new Child {}.result
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
abstract class Base {
|
||||
prop = 10
|
||||
}
|
||||
|
||||
class Child extends Base {
|
||||
result = super.prop < foo > (1)
|
||||
}
|
||||
|
||||
result = new Child {}.result
|
||||
Vendored
+11
@@ -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
|
||||
Vendored
+10
@@ -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)
|
||||
}
|
||||
Vendored
+19
@@ -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)
|
||||
}
|
||||
@@ -6,4 +6,4 @@ class D extends ref.AbstractDomain {
|
||||
local d: D = new {}
|
||||
typealias Ref<T> = ref.Reference<D, T>
|
||||
|
||||
test = ref.Reference::<D, String>(d, "") as Ref<Int>
|
||||
test = ref.Reference<D, String>(d, "") as Ref<Int>
|
||||
@@ -5,4 +5,4 @@ class D extends ref.AbstractDomain {
|
||||
}
|
||||
local d: D = new {}
|
||||
|
||||
test = ref.Reference::<D, Mapping>(d, "").default
|
||||
test = ref.Reference<D, Mapping>(d, "").default
|
||||
@@ -5,4 +5,4 @@ class D extends ref.AbstractDomain {
|
||||
}
|
||||
local d: D = new {}
|
||||
|
||||
test = ref.Reference::<D, Listing>(d, "").default
|
||||
test = ref.Reference<D, Listing>(d, "").default
|
||||
@@ -5,4 +5,4 @@ class D extends ref.AbstractDomain {
|
||||
}
|
||||
local d: D = new {}
|
||||
|
||||
test = ref.Reference::<D, Dynamic>(d, "").default
|
||||
test = ref.Reference<D, Dynamic>(d, "").default
|
||||
@@ -7,4 +7,4 @@ class D extends ref.AbstractDomain {
|
||||
}
|
||||
local d: D = new {}
|
||||
|
||||
test = ref.Reference::<D, ReferencedModule>(d, "").output
|
||||
test = ref.Reference<D, ReferencedModule>(d, "").output
|
||||
@@ -7,4 +7,4 @@ class D extends ref.AbstractDomain {
|
||||
}
|
||||
local d: D = new {}
|
||||
|
||||
test = ref.Reference::<D, ReferencedModuleWithOutputOverride>(d, "").output
|
||||
test = ref.Reference<D, ReferencedModuleWithOutputOverride>(d, "").output
|
||||
@@ -9,4 +9,4 @@ class D extends ref.AbstractDomain {
|
||||
}
|
||||
local d: D = new {}
|
||||
|
||||
test = ref.Reference::<D, ModuleSubclass>(d, "").output
|
||||
test = ref.Reference<D, ModuleSubclass>(d, "").output
|
||||
@@ -9,4 +9,4 @@ class D extends ref.AbstractDomain {
|
||||
}
|
||||
local d: D = new {}
|
||||
|
||||
test = ref.Reference::<D, A>(d, "").foo as ref.Reference<D, Int | Boolean>
|
||||
test = ref.Reference<D, A>(d, "").foo as ref.Reference<D, Int | Boolean>
|
||||
@@ -6,4 +6,4 @@ class D extends ref.AbstractDomain {
|
||||
local d: D = new {}
|
||||
typealias Ref<T> = ref.Reference<D, T>
|
||||
|
||||
test = ref.Reference::<D, String>(d, "") as Ref<Boolean | Int>
|
||||
test = ref.Reference<D, String>(d, "") as Ref<Boolean | Int>
|
||||
@@ -9,7 +9,7 @@ class D extends ref.AbstractDomain {
|
||||
}
|
||||
local d: D = new {}
|
||||
|
||||
local test = ref.Reference::<D, A>(d, "").foo
|
||||
local test = ref.Reference<D, A>(d, "").foo
|
||||
testInterpolation = "test:\(test)"
|
||||
|
||||
// this tests that the interpolation appears in the output when renderReference throws
|
||||
@@ -8,6 +8,6 @@ class Bird {
|
||||
name: String
|
||||
}
|
||||
|
||||
myRef: ref.Reference<D, Bird | Int> = ref.Reference::<D, Bird | Int>(new D {}, null)
|
||||
myRef: ref.Reference<D, Bird | Int> = ref.Reference<D, Bird | Int>(new D {}, null)
|
||||
|
||||
res = myRef.name
|
||||
@@ -4,6 +4,6 @@ class D extends ref.AbstractDomain {
|
||||
function renderReference(_): String = throw("not implemented")
|
||||
}
|
||||
|
||||
local myRef = ref.Reference::<D, Duration | Listing<String>>(new D {}, null)
|
||||
local myRef = ref.Reference<D, Duration | Listing<String>>(new D {}, null)
|
||||
|
||||
res = myRef[0]
|
||||
@@ -4,6 +4,6 @@ class D extends ref.AbstractDomain {
|
||||
function renderReference(_): String = throw("not implemented")
|
||||
}
|
||||
|
||||
local myRef = ref.Reference::<D, Listing>(new D {}, null)
|
||||
local myRef = ref.Reference<D, Listing>(new D {}, null)
|
||||
|
||||
res = myRef.name
|
||||
@@ -4,6 +4,6 @@ class D extends ref.AbstractDomain {
|
||||
function renderReference(_): String = throw("not implemented")
|
||||
}
|
||||
|
||||
local myRef = ref.Reference::<D, (Int) -> Boolean>(new D {}, null)
|
||||
local myRef = ref.Reference<D, (Int) -> Boolean>(new D {}, null)
|
||||
|
||||
res = myRef.name
|
||||
@@ -6,4 +6,4 @@ class D extends ref.AbstractDomain {
|
||||
local d: D = new {}
|
||||
typealias Ref<T> = ref.Reference<D, T>
|
||||
|
||||
test = ref.Reference::<D, String>(d, "") as Ref<Listing<String>>
|
||||
test = ref.Reference<D, String>(d, "") as Ref<Listing<String>>
|
||||
@@ -6,7 +6,7 @@ class D extends ref.AbstractDomain {
|
||||
local d: D = new {}
|
||||
typealias Ref<T> = ref.Reference<D, T>
|
||||
|
||||
test = ref.Reference::<D, String>(d, "") as Ref<Alias1>
|
||||
test = ref.Reference<D, String>(d, "") as Ref<Alias1>
|
||||
|
||||
typealias Alias1 = Int | Alias2
|
||||
typealias Alias2 = Boolean
|
||||
@@ -11,4 +11,4 @@ class D2 extends ref.AbstractDomain {
|
||||
}
|
||||
local d2: D2 = new {}
|
||||
|
||||
test = ref.Reference::<D2, String>(d2, "") as Ref<String>
|
||||
test = ref.Reference<D2, String>(d2, "") as Ref<String>
|
||||
@@ -10,4 +10,4 @@ class A {
|
||||
b: String
|
||||
}
|
||||
|
||||
test = ref.Reference::<D, String>(d, new A {}).c
|
||||
test = ref.Reference<D, String>(d, new A {}).c
|
||||
@@ -6,4 +6,4 @@ class D extends ref.AbstractDomain {
|
||||
local d: D = new {}
|
||||
typealias Ref<T> = ref.Reference<D, T>
|
||||
|
||||
test = ref.Reference::<D, List>(d, List())["hi"]
|
||||
test = ref.Reference<D, List>(d, List())["hi"]
|
||||
+29
-29
@@ -8,14 +8,14 @@ class Domain extends ref.AbstractDomain {
|
||||
}
|
||||
|
||||
directCall {
|
||||
pair = Pair::<List<String>, Set<String>>(List("foo"), Set("bar"))
|
||||
pairSelf = Pair::<module, module>(new module {}, new module {}).first.getClass().toString()
|
||||
pairErr = test.catch(() -> Pair::<List<String>, Set<String>>(List(0), Set("bar")))
|
||||
pairSelfErr = test.catch(() -> Pair::<module, module>(List(), new module {}))
|
||||
listMap = List("hi", "foo", "a").map::<Int>((it) -> it.length)
|
||||
listMapErr = test.catch(() -> List("hi", "foo", "a").map::<String>((it) -> it.length))
|
||||
wrongTypeArgCount = test.catch(() -> Pair::<String, Int, Boolean> ("foo", 10))
|
||||
nonGenericMethod = test.catch(() -> Regex::<String> ("foo"))
|
||||
pair = Pair<List<String>, Set<String>>(List("foo"), Set("bar"))
|
||||
pairSelf = Pair<module, module>(new module {}, new module {}).first.getClass().toString()
|
||||
pairErr = test.catch(() -> Pair<List<String>, Set<String>>(List(0), Set("bar")))
|
||||
pairSelfErr = test.catch(() -> Pair<module, module>(List(), new module {}))
|
||||
listMap = List("hi", "foo", "a").map<Int>((it) -> it.length)
|
||||
listMapErr = test.catch(() -> List("hi", "foo", "a").map<String>((it) -> it.length))
|
||||
wrongTypeArgCount = test.catch(() -> Pair<String, Int, Boolean> ("foo", 10))
|
||||
nonGenericMethod = test.catch(() -> Regex<String> ("foo"))
|
||||
}
|
||||
|
||||
open class A {
|
||||
@@ -23,12 +23,12 @@ open class A {
|
||||
}
|
||||
|
||||
class B extends A {
|
||||
function doIt<T>(arg: List): Boolean = super.doIt::<T>(arg) || super.doIt::<List<T>>(arg)
|
||||
function doIt<T>(arg: List): Boolean = super.doIt<T>(arg) || super.doIt<List<T>>(arg)
|
||||
}
|
||||
|
||||
superCall {
|
||||
local function doIt(a: A, arg: List): Boolean =
|
||||
a.doIt::<String>(arg)
|
||||
a.doIt<String>(arg)
|
||||
|
||||
// should be true:
|
||||
doIt(new A {}, List(""))
|
||||
@@ -45,16 +45,16 @@ local num = 5
|
||||
|
||||
objectMethod {
|
||||
local function foo<T>(): T = "hi"
|
||||
res = test.catch(() -> foo::<String(length > num)>())
|
||||
res = test.catch(() -> foo<String(length > num)>())
|
||||
}
|
||||
|
||||
function lazy1<T>(it): Listing<T> = new Listing { it }
|
||||
local function lazy2<T>(it): Listing<T> = new Listing { it }
|
||||
lazyTypeCheck {
|
||||
local function lazy3<T>(it): Listing<T> = new Listing { it }
|
||||
res1 = test.catch(() -> lazy1::<String>(5))
|
||||
res2 = test.catch(() -> lazy2::<String>(5))
|
||||
res3 = test.catch(() -> lazy3::<String>(5))
|
||||
res1 = test.catch(() -> lazy1<String>(5))
|
||||
res2 = test.catch(() -> lazy2<String>(5))
|
||||
res3 = test.catch(() -> lazy3<String>(5))
|
||||
|
||||
eager {
|
||||
local myListing: Listing<String> = new { "Hello, world!" }
|
||||
@@ -62,7 +62,7 @@ lazyTypeCheck {
|
||||
result = myListing is T
|
||||
}
|
||||
// should be false:
|
||||
res = myFunc::<Listing<Duration>>().result
|
||||
res = myFunc<Listing<Duration>>().result
|
||||
}
|
||||
|
||||
closure {
|
||||
@@ -70,7 +70,7 @@ lazyTypeCheck {
|
||||
local function myFunc<T>() = new {
|
||||
result = myListing as T
|
||||
}
|
||||
res = test.catch(() -> myFunc::<Listing<Duration>>())
|
||||
res = test.catch(() -> myFunc<Listing<Duration>>())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,13 +86,13 @@ typeDefaults {
|
||||
new { name = val }
|
||||
}
|
||||
local function makeFromParam<T>(val: T): Listing<T> = new { val }
|
||||
res1 = makeNamed::<Person>("bob")
|
||||
res1 = makeNamed<Person>("bob")
|
||||
res1a = res1.getClass().toString()
|
||||
res2 = makeNamedListing::<Person>("bob")
|
||||
res2 = makeNamedListing<Person>("bob")
|
||||
res2a = res2.first.getClass().toString()
|
||||
res3 = makeFromParam::<Person>(new { name = "bob" })
|
||||
res3 = makeFromParam<Person>(new { name = "bob" })
|
||||
res3a = res3.first.getClass().toString()
|
||||
res3b = test.catch(() -> makeFromParam::<Person>(new Bird { name = "joe" }))
|
||||
res3b = test.catch(() -> makeFromParam<Person>(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>(): T = new T { name = "fred" }
|
||||
res1 = make::<Person>()
|
||||
res2 = make::<Bird>()
|
||||
res1 = make<Person>()
|
||||
res2 = make<Bird>()
|
||||
|
||||
local function call(obj: Bird | Person) = obj.greet::<Int, String>()
|
||||
local function call(obj: Bird | Person) = obj.greet<Int, String>()
|
||||
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<U>(l: Listing<Any>): Listing<U> = l
|
||||
|
||||
res1 = cast::<String(startsWith("Bob"))>(new Listing { "Bobbert" })
|
||||
res1 = cast<String(startsWith("Bob"))>(new Listing { "Bobbert" })
|
||||
|
||||
res2 = test.catch(() -> cast::<String(length > 10)>(res1).toString())
|
||||
res2 = test.catch(() -> cast<String(length > 10)>(res1).toString())
|
||||
}
|
||||
|
||||
constraintReadsFrameSlot {
|
||||
local function id<T>(x) = x is T
|
||||
local function f(n: Int) = id::<String(length > n)>("hi")
|
||||
local function f(n: Int) = id<String(length > n)>("hi")
|
||||
|
||||
res1 = f(1)
|
||||
res2 = let (n = 1) id::<String(length > n)>("hi")
|
||||
res3 = ((n) -> id::<String(length > n)>("hi")).apply(1)
|
||||
res2 = let (n = 1) id<String(length > n)>("hi")
|
||||
res3 = ((n) -> id<String(length > n)>("hi")).apply(1)
|
||||
res4 = new Listing {
|
||||
for (n in List(1)) {
|
||||
id::<String(length > n)>("hi")
|
||||
id<String(length > n)>("hi")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+14
-14
@@ -7,23 +7,23 @@ local function prefix4() = "hi"
|
||||
|
||||
class Bar {
|
||||
x: Int = 10
|
||||
function baz<T>(val): Boolean = foo::<T>(val)
|
||||
function qux<T>(val): Boolean = foo::<T(length < x)>(val)
|
||||
function baz<T>(val): Boolean = foo<T>(val)
|
||||
function qux<T>(val): Boolean = foo<T(length < x)>(val)
|
||||
}
|
||||
|
||||
res {
|
||||
res1a = foo::<String(startsWith(prefix1))>("high five")
|
||||
res1b = foo::<String(startsWith(prefix2))>("high five")
|
||||
res1c = foo::<String(startsWith(prefix3()))>("high five")
|
||||
res1d = foo::<String(startsWith(prefix4()))>("high five")
|
||||
res1a = foo<String(startsWith(prefix1))>("high five")
|
||||
res1b = foo<String(startsWith(prefix2))>("high five")
|
||||
res1c = foo<String(startsWith(prefix3()))>("high five")
|
||||
res1d = foo<String(startsWith(prefix4()))>("high five")
|
||||
|
||||
res2a = new Bar {}.baz::<String(startsWith(prefix1))>("high five")
|
||||
res2b = new Bar {}.baz::<String(startsWith(prefix2))>("high five")
|
||||
res2c = new Bar {}.baz::<String(startsWith(prefix3()))>("high five")
|
||||
res2d = new Bar {}.baz::<String(startsWith(prefix4()))>("high five")
|
||||
res2a = new Bar {}.baz<String(startsWith(prefix1))>("high five")
|
||||
res2b = new Bar {}.baz<String(startsWith(prefix2))>("high five")
|
||||
res2c = new Bar {}.baz<String(startsWith(prefix3()))>("high five")
|
||||
res2d = new Bar {}.baz<String(startsWith(prefix4()))>("high five")
|
||||
|
||||
res3a = new Bar {}.qux::<String(startsWith(prefix1))>("high five")
|
||||
res3b = new Bar {}.qux::<String(startsWith(prefix2))>("high five")
|
||||
res3c = new Bar {}.qux::<String(startsWith(prefix3()))>("high five")
|
||||
res3d = new Bar {}.qux::<String(startsWith(prefix4()))>("high five")
|
||||
res3a = new Bar {}.qux<String(startsWith(prefix1))>("high five")
|
||||
res3b = new Bar {}.qux<String(startsWith(prefix2))>("high five")
|
||||
res3c = new Bar {}.qux<String(startsWith(prefix3()))>("high five")
|
||||
res3d = new Bar {}.qux<String(startsWith(prefix4()))>("high five")
|
||||
}
|
||||
Vendored
+16
-16
@@ -15,13 +15,13 @@ class Person {
|
||||
|
||||
lexical {
|
||||
local function f<T>(): Dynamic = new { res = 5 as T }
|
||||
res1 = test.catch(() -> f::<String>().res)
|
||||
res1 = test.catch(() -> f<String>().res)
|
||||
|
||||
local function g<T>(): Dynamic = new {
|
||||
local myProp: T = 5
|
||||
res = myProp
|
||||
}
|
||||
res2 = test.catch(() -> g::<String>().res)
|
||||
res2 = test.catch(() -> g<String>().res)
|
||||
|
||||
local function nested<T>() = new Dynamic {
|
||||
local function inner<U>(x: Listing<T>, y: U) = x
|
||||
@@ -30,40 +30,40 @@ lexical {
|
||||
local function inner4<U>() = new Dynamic {
|
||||
local function innest1<V>(x: T, y: U, z: V) = x
|
||||
local function innest2(x: T) = x
|
||||
res1 = innest1::<String>(new { name = "x" }, 1, "hi")
|
||||
res1 = innest1<String>(new { name = "x" }, 1, "hi")
|
||||
res2 = innest1(new { name = "x" }, 1, "hi")
|
||||
res3 = test.catch(() -> innest2(1))
|
||||
}
|
||||
|
||||
res1 = inner::<Int>(new { new { name = "x" } }, 1)
|
||||
res2 = inner2::<Int>(new { name = "x" }, 1)
|
||||
res1 = inner<Int>(new { new { name = "x" } }, 1)
|
||||
res2 = inner2<Int>(new { name = "x" }, 1)
|
||||
res3 = inner(new { new { name = "x" } }, 1)
|
||||
res4 = inner2(new { name = "x" }, 1)
|
||||
res5 = test.catch(() -> inner3(1))
|
||||
nested = inner4::<Int>()
|
||||
nested = inner4<Int>()
|
||||
}
|
||||
nested = nested::<Person>()
|
||||
nested = nested<Person>()
|
||||
|
||||
inEagerScope {
|
||||
local function k1<T>() = new {
|
||||
[let (elem = "key" as T) elem] = "value"
|
||||
}
|
||||
test.catch(() -> k1::<Int>())
|
||||
test.catch(() -> k1<Int>())
|
||||
|
||||
local function k2<T>() = new {
|
||||
for (i in List(1, 2, 3)) {
|
||||
[let (elem = "key".repeat(i) as T) elem as T] = "value"
|
||||
}
|
||||
}
|
||||
k2::<String(length < 10)>()
|
||||
k2<String(length < 10)>()
|
||||
}
|
||||
|
||||
reify {
|
||||
local function makeMyObj<T>() = new {
|
||||
res = ref.Reference::<Domain, T>(new {}, null)
|
||||
res = ref.Reference<Domain, T>(new {}, null)
|
||||
}
|
||||
|
||||
res = makeMyObj::<Person>().res.name
|
||||
res = makeMyObj<Person>().res.name
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,18 +74,18 @@ local function nested<T>(foo) = new Dynamic {
|
||||
local function inner4<U>() = new Dynamic {
|
||||
local function innest1<V>(x: T, y: U, z: V) = x
|
||||
local function innest2(x: T) = x
|
||||
res1 = innest1::<String>(new { name = "x" }, 1, "hi")
|
||||
res1 = innest1<String>(new { name = "x" }, 1, "hi")
|
||||
res2 = innest1(new { name = "x" }, 1, "hi")
|
||||
res3 = test.catch(() -> innest2(1))
|
||||
}
|
||||
res1 = inner::<Int>(new { new { name = "x" } }, foo)
|
||||
res2 = inner2::<Int>(new { name = "x" }, 1)
|
||||
res1 = inner<Int>(new { new { name = "x" } }, foo)
|
||||
res2 = inner2<Int>(new { name = "x" }, 1)
|
||||
res3 = inner(new { new { name = "x" } }, 1)
|
||||
res4 = inner2(new { name = "x" }, 1)
|
||||
res5 = test.catch(() -> inner3(1))
|
||||
nested = inner4::<Int>()
|
||||
nested = inner4<Int>()
|
||||
}
|
||||
nested = nested::<Person>(1)
|
||||
nested = nested<Person>(1)
|
||||
|
||||
output {
|
||||
renderer {
|
||||
|
||||
pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArgumentsNestedWithOffset.pkl
Vendored
+2
-2
@@ -3,6 +3,6 @@
|
||||
local function make<T>(): Listing<T> = new Listing<T> { 1; 2 }
|
||||
local function amend<T, U>(l: Listing<Any>): Listing<U> = (l) { 3 } as Listing<U>
|
||||
|
||||
local parent = make::<Int>()
|
||||
local child = amend::<String, Int>(parent)
|
||||
local parent = make<Int>()
|
||||
local child = amend<String, Int>(parent)
|
||||
res = child[0]
|
||||
Vendored
+7
-7
@@ -1,18 +1,18 @@
|
||||
open class A {
|
||||
function foo<T, U, V>(val): List<Boolean> = List(val is T, val is U, val is V)
|
||||
function a<T, U>(val): List<Boolean> = this.foo::<T, U, this>(val)
|
||||
function a<T, U>(val): List<Boolean> = this.foo<T, U, this>(val)
|
||||
}
|
||||
|
||||
open class B extends A {
|
||||
function b1<T>(val): List<Boolean> = this.a::<T, this>(val)
|
||||
function b2<T>(val): List<Boolean> = super.a::<T, this>(val)
|
||||
function b1<T>(val): List<Boolean> = this.a<T, this>(val)
|
||||
function b2<T>(val): List<Boolean> = super.a<T, this>(val)
|
||||
}
|
||||
|
||||
class C extends B {
|
||||
function c1(val): List<Boolean> = this.b1::<this>(val)
|
||||
function c2(val): List<Boolean> = super.b1::<this>(val)
|
||||
function c3(val): List<Boolean> = this.b2::<this>(val)
|
||||
function c4(val): List<Boolean> = super.b2::<this>(val)
|
||||
function c1(val): List<Boolean> = this.b1<this>(val)
|
||||
function c2(val): List<Boolean> = super.b1<this>(val)
|
||||
function c3(val): List<Boolean> = this.b2<this>(val)
|
||||
function c4(val): List<Boolean> = super.b2<this>(val)
|
||||
}
|
||||
|
||||
resAaA = new A {}.a(new A {})
|
||||
|
||||
Vendored
+2
-2
@@ -1,5 +1,5 @@
|
||||
function foo<T>(bar) = bar as T
|
||||
|
||||
function baz<T>(qux) = foo::<List<T>>(qux)
|
||||
function baz<T>(qux) = foo<List<T>>(qux)
|
||||
|
||||
res = baz::<String(throw("oops"))>(List("a"))
|
||||
res = baz<String(throw("oops"))>(List("a"))
|
||||
Vendored
+1
-1
@@ -4,4 +4,4 @@ function bar<T>() = new {
|
||||
prop = foo as T
|
||||
}
|
||||
|
||||
res = bar::<Duration>()
|
||||
res = bar<Duration>()
|
||||
Vendored
+23
@@ -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)
|
||||
Vendored
+23
@@ -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)
|
||||
Vendored
+23
@@ -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)
|
||||
Vendored
+19
@@ -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)
|
||||
Vendored
+23
@@ -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)
|
||||
@@ -6,8 +6,8 @@ x | typealias Ref<T> = ref.Reference<D, T>
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
at reference1#test (file:///$snippetsDir/input/errors/reference1.pkl)
|
||||
|
||||
x | test = ref.Reference::<D, String>(d, "") as Ref<Int>
|
||||
^^^^^^^^
|
||||
x | test = ref.Reference<D, String>(d, "") as Ref<Int>
|
||||
^^^^^^^^
|
||||
at reference1#test (file:///$snippetsDir/input/errors/reference1.pkl)
|
||||
|
||||
xxx | renderer.renderDocument(value)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
–– Pkl Error ––
|
||||
Cannot find property `default` in object of type `pkl.ref#Reference<reference13#D, Mapping>`.
|
||||
|
||||
x | test = ref.Reference::<D, Mapping>(d, "").default
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
x | test = ref.Reference<D, Mapping>(d, "").default
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
at reference13#test (file:///$snippetsDir/input/errors/reference13.pkl)
|
||||
|
||||
Cannot reference property `default` in class `Mapping`.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
–– Pkl Error ––
|
||||
Cannot find property `default` in object of type `pkl.ref#Reference<reference14#D, Listing>`.
|
||||
|
||||
x | test = ref.Reference::<D, Listing>(d, "").default
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
x | test = ref.Reference<D, Listing>(d, "").default
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
at reference14#test (file:///$snippetsDir/input/errors/reference14.pkl)
|
||||
|
||||
Cannot reference property `default` in class `Listing`.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
–– Pkl Error ––
|
||||
Cannot find property `default` in object of type `pkl.ref#Reference<reference15#D, Dynamic>`.
|
||||
|
||||
x | test = ref.Reference::<D, Dynamic>(d, "").default
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
x | test = ref.Reference<D, Dynamic>(d, "").default
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
at reference15#test (file:///$snippetsDir/input/errors/reference15.pkl)
|
||||
|
||||
Cannot reference property `default` in class `Dynamic`.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
–– Pkl Error ––
|
||||
Cannot find property `output` in object of type `pkl.ref#Reference<reference16#D, ReferencedModule>`.
|
||||
|
||||
xx | test = ref.Reference::<D, ReferencedModule>(d, "").output
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
xx | test = ref.Reference<D, ReferencedModule>(d, "").output
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
at reference16#test (file:///$snippetsDir/input/errors/reference16.pkl)
|
||||
|
||||
Cannot reference property `output` because it is defined inside external class `Module`.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
–– Pkl Error ––
|
||||
Cannot find property `output` in object of type `pkl.ref#Reference<reference17#D, ReferencedModuleWithOutputOverride>`.
|
||||
|
||||
xx | test = ref.Reference::<D, ReferencedModuleWithOutputOverride>(d, "").output
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
xx | test = ref.Reference<D, ReferencedModuleWithOutputOverride>(d, "").output
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
at reference17#test (file:///$snippetsDir/input/errors/reference17.pkl)
|
||||
|
||||
Cannot reference property `output` because it is defined inside external class `Module`.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
–– Pkl Error ––
|
||||
Cannot find property `output` in object of type `pkl.ref#Reference<reference18#D, reference18#ModuleSubclass>`.
|
||||
|
||||
xx | test = ref.Reference::<D, ModuleSubclass>(d, "").output
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
xx | test = ref.Reference<D, ModuleSubclass>(d, "").output
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
at reference18#test (file:///$snippetsDir/input/errors/reference18.pkl)
|
||||
|
||||
Cannot reference property `output` because it is defined inside external class `Module`.
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
Expected value of type `pkl.ref#Reference<reference19#D, Int | Boolean>`, but got type `pkl.ref#Reference<reference19#D, Boolean | Int | String>`.
|
||||
Value: Reference(new reference19#D {}, Boolean | Int | String, "").foo
|
||||
|
||||
xx | test = ref.Reference::<D, A>(d, "").foo as ref.Reference<D, Int | Boolean>
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
xx | test = ref.Reference<D, A>(d, "").foo as ref.Reference<D, Int | Boolean>
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
at reference19#test (file:///$snippetsDir/input/errors/reference19.pkl)
|
||||
|
||||
xxx | renderer.renderDocument(value)
|
||||
|
||||
@@ -6,8 +6,8 @@ x | typealias Ref<T> = ref.Reference<D, T>
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
at reference2#test (file:///$snippetsDir/input/errors/reference2.pkl)
|
||||
|
||||
x | test = ref.Reference::<D, String>(d, "") as Ref<Boolean | Int>
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
x | test = ref.Reference<D, String>(d, "") as Ref<Boolean | Int>
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
at reference2#test (file:///$snippetsDir/input/errors/reference2.pkl)
|
||||
|
||||
xxx | renderer.renderDocument(value)
|
||||
|
||||
@@ -6,8 +6,8 @@ x | typealias Ref<T> = ref.Reference<D, T>
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
at reference3#test (file:///$snippetsDir/input/errors/reference3.pkl)
|
||||
|
||||
x | test = ref.Reference::<D, String>(d, "") as Ref<Listing<String>>
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
x | test = ref.Reference<D, String>(d, "") as Ref<Listing<String>>
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
at reference3#test (file:///$snippetsDir/input/errors/reference3.pkl)
|
||||
|
||||
xxx | renderer.renderDocument(value)
|
||||
|
||||
@@ -6,8 +6,8 @@ x | typealias Ref<T> = ref.Reference<D, T>
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
at reference4#test (file:///$snippetsDir/input/errors/reference4.pkl)
|
||||
|
||||
x | test = ref.Reference::<D, String>(d, "") as Ref<Alias1>
|
||||
^^^^^^^^^^^
|
||||
x | test = ref.Reference<D, String>(d, "") as Ref<Alias1>
|
||||
^^^^^^^^^^^
|
||||
at reference4#test (file:///$snippetsDir/input/errors/reference4.pkl)
|
||||
|
||||
xxx | renderer.renderDocument(value)
|
||||
|
||||
@@ -6,8 +6,8 @@ x | typealias Ref<T> = ref.Reference<D, T>
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
at reference5#test (file:///$snippetsDir/input/errors/reference5.pkl)
|
||||
|
||||
xx | test = ref.Reference::<D2, String>(d2, "") as Ref<String>
|
||||
^^^^^^^^^^^
|
||||
xx | test = ref.Reference<D2, String>(d2, "") as Ref<String>
|
||||
^^^^^^^^^^^
|
||||
at reference5#test (file:///$snippetsDir/input/errors/reference5.pkl)
|
||||
|
||||
xxx | renderer.renderDocument(value)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
–– Pkl Error ––
|
||||
Cannot find property `c` in object of type `pkl.ref#Reference<reference6#D, String>`.
|
||||
|
||||
xx | test = ref.Reference::<D, String>(d, new A {}).c
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
xx | test = ref.Reference<D, String>(d, new A {}).c
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
at reference6#test (file:///$snippetsDir/input/errors/reference6.pkl)
|
||||
|
||||
Cannot find property `c` in type `String`.
|
||||
|
||||
@@ -3,8 +3,8 @@ Operator `[]` is not defined for operand types `pkl.ref#Reference<reference7#D,
|
||||
Left operand : Reference(new reference7#D {}, List, List())
|
||||
Right operand: "hi"
|
||||
|
||||
x | test = ref.Reference::<D, List>(d, List())["hi"]
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
x | test = ref.Reference<D, List>(d, List())["hi"]
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
at reference7#test (file:///$snippetsDir/input/errors/reference7.pkl)
|
||||
|
||||
Operator `[]` is not defined for operand types `List` and `String`.
|
||||
|
||||
Vendored
+8
-8
@@ -1,24 +1,24 @@
|
||||
–– Pkl Error ––
|
||||
oops
|
||||
|
||||
x | res = baz::<String(throw("oops"))>(List("a"))
|
||||
^^^^^^^^^^^^^
|
||||
x | res = baz<String(throw("oops"))>(List("a"))
|
||||
^^^^^^^^^^^^^
|
||||
at callWithTypeArgumentsStacktrace#res.<typearg#1> (file:///$snippetsDir/input/methods/callWithTypeArgumentsStacktrace.pkl)
|
||||
|
||||
x | function baz<T>(qux) = foo::<List<T>>(qux)
|
||||
^
|
||||
x | function baz<T>(qux) = foo<List<T>>(qux)
|
||||
^
|
||||
at callWithTypeArgumentsStacktrace#baz.<typearg#1> (file:///$snippetsDir/input/methods/callWithTypeArgumentsStacktrace.pkl)
|
||||
|
||||
x | function foo<T>(bar) = bar as T
|
||||
^
|
||||
at callWithTypeArgumentsStacktrace#foo (file:///$snippetsDir/input/methods/callWithTypeArgumentsStacktrace.pkl)
|
||||
|
||||
x | function baz<T>(qux) = foo::<List<T>>(qux)
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
x | function baz<T>(qux) = foo<List<T>>(qux)
|
||||
^^^^^^^^^^^^^^^^^
|
||||
at callWithTypeArgumentsStacktrace#baz (file:///$snippetsDir/input/methods/callWithTypeArgumentsStacktrace.pkl)
|
||||
|
||||
x | res = baz::<String(throw("oops"))>(List("a"))
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
x | res = baz<String(throw("oops"))>(List("a"))
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
at callWithTypeArgumentsStacktrace#res (file:///$snippetsDir/input/methods/callWithTypeArgumentsStacktrace.pkl)
|
||||
|
||||
xxx | renderer.renderDocument(value)
|
||||
|
||||
Vendored
+2
-2
@@ -2,8 +2,8 @@
|
||||
Expected value of type `Duration`, but got type `Int`.
|
||||
Value: 5
|
||||
|
||||
x | res = bar::<Duration>()
|
||||
^^^^^^^^
|
||||
x | res = bar<Duration>()
|
||||
^^^^^^^^
|
||||
at callWithTypeArgumentsStacktrace2#bar.prop (file:///$snippetsDir/input/methods/callWithTypeArgumentsStacktrace2.pkl)
|
||||
|
||||
x | prop = foo as T
|
||||
|
||||
@@ -930,7 +930,7 @@ class EvaluatorTest {
|
||||
evaluator.evaluateOutputText(
|
||||
text(
|
||||
"""
|
||||
res = List(1, 2, 3).map::<String>((it) -> it + 1)
|
||||
res = List(1, 2, 3).map<String>((it) -> it + 1)
|
||||
"""
|
||||
.trimIndent()
|
||||
)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
+12
-12
@@ -1,14 +1,14 @@
|
||||
res1 = foo::<Bar>(baz)
|
||||
res1a = foo::<Bar, Baz, ReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongType>(baz, baz, reallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongArg)
|
||||
res1 = foo<Bar>(baz)
|
||||
res1a = foo<Bar, Baz, ReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongType>(baz, baz, reallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongArg)
|
||||
|
||||
res2 = qux.foo::<Bar, Bar2,>(baz, baz2)
|
||||
res2a = qux.foo::<Bar, Baz, ReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongType>(baz, baz, reallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongArg)
|
||||
res2b = qux.quux::<Corge>().foo::<Bar, Bar2,>(baz, baz2)
|
||||
res2c = qux.quux::<Corge>().foo::<Bar, Baz, ReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongType>(baz, baz, reallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongArg)
|
||||
res2d = qux::<Corge>().foo::<Bar, Bar2,>(baz, baz2)
|
||||
res2e = qux::<Corge>().foo::<Bar, Baz, ReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongType>(baz, baz, reallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongArg)
|
||||
res2f = grault.qux.quux::<Corge>().foo::<Bar, Bar2,>(baz, baz2)
|
||||
res2g = grault.qux.quux::<Corge>().foo::<Bar, Baz, ReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongType>(baz, baz, reallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongArg)
|
||||
res2 = qux.foo<Bar, Bar2,>(baz, baz2)
|
||||
res2a = qux.foo<Bar, Baz, ReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongType>(baz, baz, reallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongArg)
|
||||
res2b = qux.quux<Corge>().foo<Bar, Bar2,>(baz, baz2)
|
||||
res2c = qux.quux<Corge>().foo<Bar, Baz, ReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongType>(baz, baz, reallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongArg)
|
||||
res2d = qux<Corge>().foo<Bar, Bar2,>(baz, baz2)
|
||||
res2e = qux<Corge>().foo<Bar, Baz, ReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongType>(baz, baz, reallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongArg)
|
||||
res2f = grault.qux.quux<Corge>().foo<Bar, Bar2,>(baz, baz2)
|
||||
res2g = grault.qux.quux<Corge>().foo<Bar, Baz, ReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongType>(baz, baz, reallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongArg)
|
||||
|
||||
res3 = super.foo::<Bar, Bar2,>(baz, baz2)
|
||||
res3a = super.foo::<Bar, Baz, ReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongType>(baz, baz, reallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongArg)
|
||||
res3 = super.foo<Bar, Bar2,>(baz, baz2)
|
||||
res3a = super.foo<Bar, Baz, ReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongType>(baz, baz, reallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongArg)
|
||||
+15
-15
@@ -1,6 +1,6 @@
|
||||
res1 = foo::<Bar>(baz)
|
||||
res1 = foo<Bar>(baz)
|
||||
res1a =
|
||||
foo::<
|
||||
foo<
|
||||
Bar,
|
||||
Baz,
|
||||
ReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongType,
|
||||
@@ -10,9 +10,9 @@ res1a =
|
||||
reallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongArg,
|
||||
)
|
||||
|
||||
res2 = qux.foo::<Bar, Bar2>(baz, baz2)
|
||||
res2 = qux.foo<Bar, Bar2>(baz, baz2)
|
||||
res2a =
|
||||
qux.foo::<
|
||||
qux.foo<
|
||||
Bar,
|
||||
Baz,
|
||||
ReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongType,
|
||||
@@ -21,11 +21,11 @@ res2a =
|
||||
baz,
|
||||
reallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongArg,
|
||||
)
|
||||
res2b = qux.quux::<Corge>().foo::<Bar, Bar2>(baz, baz2)
|
||||
res2b = qux.quux<Corge>().foo<Bar, Bar2>(baz, baz2)
|
||||
res2c =
|
||||
qux
|
||||
.quux::<Corge>()
|
||||
.foo::<
|
||||
.quux<Corge>()
|
||||
.foo<
|
||||
Bar,
|
||||
Baz,
|
||||
ReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongType,
|
||||
@@ -34,10 +34,10 @@ res2c =
|
||||
baz,
|
||||
reallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongArg,
|
||||
)
|
||||
res2d = qux::<Corge>().foo::<Bar, Bar2>(baz, baz2)
|
||||
res2d = qux<Corge>().foo<Bar, Bar2>(baz, baz2)
|
||||
res2e =
|
||||
qux::<Corge>()
|
||||
.foo::<
|
||||
qux<Corge>()
|
||||
.foo<
|
||||
Bar,
|
||||
Baz,
|
||||
ReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongType,
|
||||
@@ -46,11 +46,11 @@ res2e =
|
||||
baz,
|
||||
reallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongArg,
|
||||
)
|
||||
res2f = grault.qux.quux::<Corge>().foo::<Bar, Bar2>(baz, baz2)
|
||||
res2f = grault.qux.quux<Corge>().foo<Bar, Bar2>(baz, baz2)
|
||||
res2g =
|
||||
grault.qux
|
||||
.quux::<Corge>()
|
||||
.foo::<
|
||||
.quux<Corge>()
|
||||
.foo<
|
||||
Bar,
|
||||
Baz,
|
||||
ReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongType,
|
||||
@@ -60,9 +60,9 @@ res2g =
|
||||
reallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongArg,
|
||||
)
|
||||
|
||||
res3 = super.foo::<Bar, Bar2>(baz, baz2)
|
||||
res3 = super.foo<Bar, Bar2>(baz, baz2)
|
||||
res3a =
|
||||
super.foo::<
|
||||
super.foo<
|
||||
Bar,
|
||||
Baz,
|
||||
ReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongType,
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
* <p>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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<FullToken> tokens = new ArrayList<>();
|
||||
private int speculativeTokenCount = 0;
|
||||
private boolean isSpeculativeParse = false;
|
||||
private final Set<Integer> 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<Node> 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;
|
||||
|
||||
@@ -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<InterpolationScope>();
|
||||
// `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<InterpolationScope> interpolationStack,
|
||||
boolean stringEnded,
|
||||
boolean isEscape,
|
||||
int newLinesBetween) {}
|
||||
|
||||
private enum State {
|
||||
DEFAULT,
|
||||
STRING
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Integer> 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;
|
||||
|
||||
@@ -89,7 +89,6 @@ public enum Token {
|
||||
NOT,
|
||||
QUESTION,
|
||||
COLON,
|
||||
DCOLON,
|
||||
ARROW,
|
||||
EQUAL,
|
||||
NOT_EQUAL,
|
||||
|
||||
@@ -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 <Int>} in {@code foo<Int>(5)}.
|
||||
*
|
||||
* <p>A candidate must have balanced {@code <} and {@code >} pairs and followed by {@code (}.
|
||||
*
|
||||
* <p>Candidates are not <i>necessarily</i> starts of type argument lists. For example, {@code
|
||||
* foo<5>()} is not one because {@code 5} is not a type.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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);
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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<GenericParserError> { 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) }
|
||||
}
|
||||
}
|
||||
@@ -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<Path> {
|
||||
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<DynamicTest> {
|
||||
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<String, String> =
|
||||
private fun renderBoth(code: String): Pair<String, String> =
|
||||
Pair(renderCode(code), renderGenericCode(code))
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun getSnippets(): List<Path> {
|
||||
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)
|
||||
|
||||
@@ -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<ParserError> { 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) }
|
||||
}
|
||||
}
|
||||
@@ -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<Int>(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<Int> 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<Map<String, List<Int>>>(5)"
|
||||
assertThat(candidates(source)).isEqualTo(intArrayOf(7))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `multiple candidates`() {
|
||||
assertThat(candidates("x = foo<Int>(bar<String>(1))")).isEqualTo(intArrayOf(7, 16))
|
||||
assertThat(candidates("x = foo<Int>(1)\ny = bar<String>(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<Int(this > 3)>(5)")).isEqualTo(intArrayOf(7))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `less-than inside a constraint is not a candidate`() {
|
||||
// only the outer `<` is
|
||||
assertThat(candidates("x = foo<Int(this < 3)>(5)")).isEqualTo(intArrayOf(7))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `comparisons in both directions inside a constraint`() {
|
||||
assertThat(candidates("x = foo<Int(this < 3 && this > 1)>(5)")).isEqualTo(intArrayOf(7))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `constraint with a call followed by open paren`() {
|
||||
val source = "x = foo<Int(bar<Int>(this))>(5)"
|
||||
assertThat(candidates(source)).isEqualTo(intArrayOf(7, 15))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `constraint in a nested type argument`() {
|
||||
assertThat(candidates("x = foo<List<Int(this > 3)>>(5)")).isEqualTo(intArrayOf(7))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `multiple constrained type arguments`() {
|
||||
assertThat(candidates("x = foo<Int(this > 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<Foo(new Listing { 1 > 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<Int>(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<Int>(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<Int>(1))")"""")).isEqualTo(intArrayOf(13))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `multiline string with interpolation`() {
|
||||
val source = "x = \"\"\"\n \\(foo<Int>(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<Int> /* c */ (5)")).isEqualTo(intArrayOf(7))
|
||||
assertThat(candidates("x = foo<Int> // 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<Int>\n(5)")).isEqualTo(intArrayOf(7))
|
||||
assertThat(candidates("x = foo<Int>; (5)")).isEqualTo(intArrayOf(7))
|
||||
}
|
||||
|
||||
// robustness
|
||||
|
||||
@Test
|
||||
fun `lexing error ends scan without throwing`() {
|
||||
// candidates found before the error are retained
|
||||
assertThat(candidates("x = foo<Int>(1)\ny = \u0001")).isEqualTo(intArrayOf(7))
|
||||
// `<` still open when the error occurs is not a candidate
|
||||
assertThat(candidates("x = foo<Int \u0001 >(1)")).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `deeply nested angle brackets`() {
|
||||
val depth = 1000
|
||||
val source = "x = foo" + "<A".repeat(depth) + ">".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<B>(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<B>(2)
|
||||
"""
|
||||
.trimIndent()
|
||||
assertThat(candidates(source)).isEqualTo(intArrayOf(12, 26, 88))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
result = a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<a<T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,T,Line truncated
|
||||
+2
-2
@@ -35,7 +35,7 @@ module pkl.ref
|
||||
///
|
||||
/// Example:
|
||||
/// ```
|
||||
/// myReference: Reference<MyDomain, Listing<String>> = Reference::<MyDomain, Listing<String>>(domain, data)
|
||||
/// myReference: Reference<MyDomain, Listing<String>> = Reference<MyDomain, Listing<String>>(domain, data)
|
||||
/// ```
|
||||
external const function Reference<Domain, Type>(
|
||||
domain: Domain(this is AbstractDomain),
|
||||
@@ -153,7 +153,7 @@ external const function Reference<Domain, Type>(
|
||||
/// properties: Mapping<String, String>
|
||||
/// }
|
||||
///
|
||||
/// local outputs: FooReference<Outputs> = ref.Reference::<Foo, Outputs>(new Foo {}, "outputs")
|
||||
/// local outputs: FooReference<Outputs> = ref.Reference<Foo, Outputs>(new Foo {}, "outputs")
|
||||
///
|
||||
/// // Dot access; gives a `Reference<Foo, String>`
|
||||
/// myFile = outputs.file
|
||||
|
||||
@@ -305,7 +305,6 @@ typealias TerminalNodeType =
|
||||
| "assign"
|
||||
| "at"
|
||||
| "colon"
|
||||
| "dcolon"
|
||||
| "comma"
|
||||
| "dot"
|
||||
| "gt"
|
||||
|
||||
Reference in new issue
Block a user