diff --git a/docs/modules/language-reference/pages/index.adoc b/docs/modules/language-reference/pages/index.adoc index 7af41707a..e166fd07d 100644 --- a/docs/modules/language-reference/pages/index.adoc +++ b/docs/modules/language-reference/pages/index.adoc @@ -2100,6 +2100,8 @@ The xref:blog:ROOT:class-as-a-function.adoc[Class-as-a-function] pattern may be TIP: In most cases, methods without parameters should not be defined. Instead, use <> on the module or class. +Methods may also be <>. + [[modules]] == Modules @@ -3282,7 +3284,8 @@ This section discusses language features that are generally more relevant to tem <> + <> + <> + -<> +<> + +<> [[meaning-of-new]] === Meaning of `new` @@ -5994,32 +5997,13 @@ There are some restrictions on properties that may be referenced; attempting to * Properties originally defined in `external` classes (this only includes `Module.output`) pkldoc:Reference[pkl:ref] instances are created using the constructor method pkldoc:#Reference()[pkl:ref]. -This method accepts three parameters: +This method accepts two type arguments and two parameters: -* `domain` - An instance of a `ref.Domain` subclass; its type becomes the `D` type argument of the resulting `Reference` instance. -* `class` - A `Class` instance that will be the referent type of the returned `Reference`; it is the `T` type argument of the resulting instance. +* `D` - The type of the reference's domain. Must be a subclass of `ref.Domain`. +* `Referent` - The type the reference refers to. +* `domain` - An instance of `D` that becomes the domain resulting `Reference` instance. * `data` - A domain-specific value used to identify the root or context of the reference. -[NOTE] -==== -It is only possible to construct new references with a referent type that is a simple (non-generic) class type. -To obtain a reference to values of other types (generic, typealias, nullable, union, etc.), libraries must wrap those types in a class: - -[source%parsed,pkl] ----- -import "pkl:ref" - -class TypeHolder { - $: *Listing? | Mapping // <1> -} - -local r: ref.Reference? | Mapping> = - ref.Reference(myDomain, TypeHolder, null).$ // <2> ----- -<1> Define a "holder" class with a property of the desired type. -<2> Access the `$` property of the reference to get a reference to the desired non-class type. -==== - Here's a realistic example of how references might be used: [source,pkl] @@ -6027,7 +6011,7 @@ Here's a realistic example of how references might be used: ---- import "pkl:ref" -class Domain extends ref.Domain { // <1> +class Domain extends ref.AbstractDomain { // <1> function renderReference(reference: ref.Reference): String = if (reference.getData() is Task) let (path = reference.getPath().map((access) -> access.property ?? access.key.toString())) @@ -6038,18 +6022,20 @@ class Domain extends ref.Domain { // <1> typealias Ref = ref.Reference // <2> -abstract class Task { // <3> +const function Ref(data: Any): Ref = ref.Reference::(new Domain {}, data) // <3> + +abstract class Task { // <4> name: String } class TaskA extends Task { - input1: String | Ref // <4> + input1: String | Ref // <5> input2: Int | Ref - hidden fixed outputs: Ref = ref.Reference(new Domain {}, TaskAOutputs, this) // <5> + hidden fixed outputs: Ref = Ref::(this) // <6> } -abstract class TaskAOutputs { // <6> +abstract class TaskAOutputs { // <7> foo: String bar: Int } @@ -6059,24 +6045,25 @@ class TaskB extends Task { inputY: Int | Ref } -tasks: Listing(isDistinctBy((it) -> it.name)) // <7> +tasks: Listing(isDistinctBy((it) -> it.name)) // <8> output { renderer { converters { - [ref.Reference] = (it) -> it.toString() // <8> + [ref.Reference] = (it) -> it.toString() // <9> } } } ---- <1> Define a `ref.Domain` subclass that determines how `Reference` instances are rendered as strings. <2> For convenience, create a type alias for references of this domain. -<3> Create a new base class that can be referenced. `Task` instances will be identified by their `name` value. -<4> In a specific `Task` subclass, input values accept both literal values and references to the same type. -<5> `Task` subclasses that provide output values define an `outputs` property where the referent type is `TaskAOutputs`. -<6> The `TaskAOutputs` class defines the possible runtime outputs of `TaskA` tasks. It is `abstract` and never instantiated, only used for producing references. -<7> Users provide `Task` instances with unique `name` values. -<8> Delegate to `Domain` for rendering `Reference` values. +<3> For convenience, wrap the `Reference` constructor to pass appropriate the domain type and value. +<4> Create a new base class that can be referenced. `Task` instances will be identified by their `name` value. +<5> In a specific `Task` subclass, input values accept both literal values and references to the same type. +<6> `Task` subclasses that provide output values define an `outputs` property where the referent type is `TaskAOutputs`. +<7> The `TaskAOutputs` class defines the possible runtime outputs of `TaskA` tasks. It is `abstract` and never instantiated, only used for producing references. +<8> Users provide `Task` instances with unique `name` values. +<9> Delegate to `Domain` for rendering `Reference` values. [source,pkl] .myWorkflow.pkl @@ -6119,3 +6106,37 @@ tasks { } } ---- + +[[generic-methods]] +=== Generic Methods + +Methods may be declared as "generic", with types that are themselves parameters. +Method type parameters may be used within the method anywhere else a type may be used: in a parameter's type, in the return type, or within the method body (e.g. <>, <>, <>). + +When calling a generic method, a caller may manually specify type arguments by writing `methodName::(arg1, arg2)`. +When no type arguments are provided, type checks of parameter types behave like <> and always succeed. + +[source,pkl] +---- +class Pigeon { + name: String +} + +class Hawk { + name: String +} + +function makeFlock(names: Listing): Listing = new { + for (n in names) { + new { name = n } + } +} + +pigeonFlock = makeFlock::(new { "Alice"; "Bill"; "Ruth" }) // <1> +hawkFlock = makeFlock::(new { "Florence"; "Holly"; "Jim" }) // <2> +---- +<1> Result: a `Listing` where every element is a `Pigeon`. +<2> Result: a `Listing` where every element is a `Hawk`. + +NOTE: The `::<>` syntax is colloquially known as the "turbofish", after https://doc.rust-lang.org/reference/glossary.html#turbofish[the same syntax in Rust]. +Pkl requires this syntax for the same reason as Rust: to avoid parser ambiguity. diff --git a/docs/modules/release-notes/pages/0.32.adoc b/docs/modules/release-notes/pages/0.32.adoc index 49005b069..b2702b136 100644 --- a/docs/modules/release-notes/pages/0.32.adoc +++ b/docs/modules/release-notes/pages/0.32.adoc @@ -62,7 +62,7 @@ Library authors describe types as `ref.Reference`, where `D` is the _domai ---- import "pkl:ref" -class MyDomain extends ref.Domain { // <1> +class MyDomain extends ref.AbstractDomain { // <1> function renderReference(ref: ref.Reference): String = "${ " + ref.getData() + "." + ref.getPath().join(".") + " }" } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/PklRootNode.java b/pkl-core/src/main/java/org/pkl/core/ast/PklRootNode.java index 9e4b57d40..16e611653 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/PklRootNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/PklRootNode.java @@ -29,8 +29,18 @@ import org.pkl.core.runtime.*; @NodeInfo(language = "Pkl") @TypeSystemReference(VmTypes.class) public abstract class PklRootNode extends RootNode { + private final boolean propagateTypeMismatchExceptions; + protected PklRootNode(@Nullable VmLanguage language, FrameDescriptor descriptor) { + this(language, descriptor, false); + } + + protected PklRootNode( + @Nullable VmLanguage language, + FrameDescriptor descriptor, + boolean propagateTypeMismatchExceptions) { super(language, descriptor); + this.propagateTypeMismatchExceptions = propagateTypeMismatchExceptions; } public abstract SourceSection getSourceSection(); @@ -46,7 +56,7 @@ public abstract class PklRootNode extends RootNode { return executeImpl(frame); } catch (VmTypeMismatchException e) { CompilerDirectives.transferToInterpreter(); - throw e.toVmException(); + throw propagateTypeMismatchExceptions ? e : e.toVmException(); } catch (VmException e) { CompilerDirectives.transferToInterpreter(); throw e; diff --git a/pkl-core/src/main/java/org/pkl/core/ast/SimpleRootNode.java b/pkl-core/src/main/java/org/pkl/core/ast/SimpleRootNode.java index 77b21c2c8..5769a0536 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/SimpleRootNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/SimpleRootNode.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,8 +31,7 @@ public final class SimpleRootNode extends PklRootNode { SourceSection sourceSection, String qualifiedName, ExpressionNode bodyNode) { - - super(language, descriptor); + super(language, descriptor, false); this.sourceSection = sourceSection; this.qualifiedName = qualifiedName; this.bodyNode = bodyNode; diff --git a/pkl-core/src/main/java/org/pkl/core/ast/builder/AstBuilder.java b/pkl-core/src/main/java/org/pkl/core/ast/builder/AstBuilder.java index 661702fcd..38d588750 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/builder/AstBuilder.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/builder/AstBuilder.java @@ -36,7 +36,6 @@ import org.jspecify.annotations.Nullable; import org.pkl.core.PClassInfo; import org.pkl.core.PklBugException; import org.pkl.core.SecurityManagerException; -import org.pkl.core.TypeParameter; import org.pkl.core.TypeParameter.Variance; import org.pkl.core.ast.ByteConstantValueNode; import org.pkl.core.ast.ConstantNode; @@ -189,6 +188,7 @@ import org.pkl.core.runtime.FrameDescriptorBuilder; import org.pkl.core.runtime.FrameSlotVariable; import org.pkl.core.runtime.ModuleInfo; import org.pkl.core.runtime.ModuleResolver; +import org.pkl.core.runtime.TypeParameter; import org.pkl.core.runtime.VmBytes; import org.pkl.core.runtime.VmClass; import org.pkl.core.runtime.VmContext; @@ -285,6 +285,7 @@ import org.pkl.parser.syntax.Type.UnionType; import org.pkl.parser.syntax.Type.UnknownType; import org.pkl.parser.syntax.TypeAlias; import org.pkl.parser.syntax.TypeAnnotation; +import org.pkl.parser.syntax.TypeArgumentList; import org.pkl.parser.syntax.TypeParameterList; public class AstBuilder extends AbstractAstBuilder { @@ -487,9 +488,13 @@ public class AstBuilder extends AbstractAstBuilder { if (args == null) { if (identifier.getIdentifiers().size() == 1) { var text = identifier.getIdentifiers().get(0).getValue(); - var typeParameter = symbolTable.findTypeParameter(text); - if (typeParameter != null) { - return new UnresolvedTypeNode.TypeVariable(createSourceSection(type), typeParameter); + var typeParameterResolution = symbolTable.getCurrentScope().resolveTypeParameter(text); + if (typeParameterResolution != null) { + return new UnresolvedTypeNode.TypeVariable( + createSourceSection(type), + typeParameterResolution.typeParameter(), + typeParameterResolution.ownerType(), + typeParameterResolution.levelsUp()); } } @@ -872,8 +877,20 @@ public class AstBuilder extends AbstractAstBuilder { } } + private UnresolvedTypeNode @Nullable [] doVisitMethodTypeArguments( + @Nullable TypeArgumentList typeArgumentList) { + if (typeArgumentList == null) return null; + var types = typeArgumentList.getTypes(); + var res = new UnresolvedTypeNode[types.size()]; + for (var i = 0; i < res.length; i++) { + res[i] = visitType(types.get(i)); + } + return res; + } + private ExpressionNode resolvedMethodCall(UnqualifiedAccessExpr expr, ArgumentList argList) { var name = expr.getIdentifier().getValue(); + var typeArgs = doVisitMethodTypeArguments(expr.getTypeArgumentList()); var scope = symbolTable.getCurrentScope(); var sourceSection = createSourceSection(expr); var constLevel = scope.getConstLevel(); @@ -897,45 +914,67 @@ public class AstBuilder extends AbstractAstBuilder { return new InvokeQualifiedObjectMethodNode( sourceSection, identifier, + typeArgs, argInfo.arguments, needsConst, getModuleNode, - argInfo.methodSlot); + argInfo.methodSlot, + scope.getQualifiedName()); } if (method.isOnClosedClass() || method.isLocal() || method.isExternal()) { return new InvokeQualifiedClassMethodNode( sourceSection, identifier, + typeArgs, argInfo.arguments, needsConst, getModuleNode, - argInfo.methodSlot); + argInfo.methodSlot, + scope.getQualifiedName()); } return InvokeMethodVirtualNodeGen.create( sourceSection, identifier, + typeArgs, argInfo.arguments, MemberLookupMode.IMPLICIT_LEXICAL, needsConst, argInfo.methodSlot, + scope.getQualifiedName(), getModuleNode, GetClassNodeGen.create(null)); } if (method.isObjectMethod()) { return new InvokeLexicalObjectMethodNode( - sourceSection, identifier, levelsUp, argInfo.arguments, needsConst, argInfo.methodSlot); + sourceSection, + identifier, + levelsUp, + typeArgs, + argInfo.arguments, + needsConst, + argInfo.methodSlot, + scope.getQualifiedName()); } if (method.isOnClosedClass() || method.isLocal() || method.isExternal()) { return new InvokeLexicalClassMethodNode( - sourceSection, identifier, levelsUp, argInfo.arguments, needsConst, argInfo.methodSlot); + sourceSection, + identifier, + levelsUp, + typeArgs, + argInfo.arguments, + needsConst, + argInfo.methodSlot, + scope.getQualifiedName()); } return InvokeMethodVirtualNodeGen.create( sourceSection, identifier, + typeArgs, argInfo.arguments, MemberLookupMode.IMPLICIT_LEXICAL, needsConst, argInfo.methodSlot, + scope.getQualifiedName(), levelsUp == 0 ? new GetReceiverNode() : new GetEnclosingReceiverNode(levelsUp), GetClassNodeGen.create(null)); } else if (resolution instanceof ImplicitBaseMethod) { @@ -962,8 +1001,10 @@ public class AstBuilder extends AbstractAstBuilder { createSourceSection(expr), method, new ConstantValueNode(baseModule), + typeArgs, argInfo.arguments, - argInfo.methodSlot); + argInfo.methodSlot, + scope.getQualifiedName()); } } else if (resolution instanceof ImplicitThisMethod) { var isCustomThis = scope.isCustomThisScope(); @@ -974,10 +1015,12 @@ public class AstBuilder extends AbstractAstBuilder { return InvokeMethodVirtualNodeGen.create( sourceSection, org.pkl.core.runtime.Identifier.get(name), + typeArgs, arguments, MemberLookupMode.IMPLICIT_THIS, needsConst, methodSlot, + scope.getQualifiedName(), VmUtils.createThisNode(VmUtils.unavailableSourceSection(), isCustomThis), GetClassNodeGen.create(null)); } else { @@ -1154,9 +1197,16 @@ public class AstBuilder extends AbstractAstBuilder { .build(); } + var typeArgs = doVisitMethodTypeArguments(expr.getTypeArgumentList()); var argInfo = visitArgumentList(argCtx); return InvokeSuperMethodNodeGen.create( - sourceSection, memberName, argInfo.arguments, needsConst, argInfo.methodSlot); + sourceSection, + memberName, + typeArgs, + argInfo.arguments, + needsConst, + argInfo.methodSlot, + currentScope.getQualifiedName()); } // superproperty call @@ -1318,10 +1368,10 @@ public class AstBuilder extends AbstractAstBuilder { public ExpressionNode visitFunctionLiteralExpr(FunctionLiteralExpr expr) { var sourceSection = createSourceSection(expr); var params = expr.getParameterList(); - var descriptorBuilderAndBindings = createFrameDescriptorBuilderAndSlotVariables(params); + var descriptorBuilderAndBindings = createFrameDescriptorBuilderAndSlotVariables(params, null); var paramCount = params.getParameters().size(); - var descriptorBuilder = descriptorBuilderAndBindings.first; - var bindings = descriptorBuilderAndBindings.second; + var descriptorBuilder = descriptorBuilderAndBindings.builder; + var bindings = descriptorBuilderAndBindings.bindings; if (paramCount > 5) { throw exceptionBuilder() .evalError("tooManyFunctionParameters") @@ -1688,7 +1738,7 @@ public class AstBuilder extends AbstractAstBuilder { modifiers, PClassInfo.forModuleClass( moduleInfo.getModuleName(), moduleInfo.getModuleKey().getUri()), - List.of(), + new TypeParameter[0], moduleInfo, supertypeNode, moduleProperties, @@ -1855,10 +1905,18 @@ public class AstBuilder extends AbstractAstBuilder { @Override public ObjectMember visitClass(Class clazz) { + var sourceSection = createSourceSection(clazz); var headerSection = createSourceSection(clazz.getHeaderSpan()); - var typeParameters = visitTypeParameterList(clazz.getTypeParameterList()); + var typeParameterList = clazz.getTypeParameterList(); + if (clazz.getTypeParameterList() != null && !isStdLibModule) { + throw exceptionBuilder() + .evalError("cannotDeclareTypeParameter") + .withSourceSection(createSourceSection(typeParameterList.getParameters().get(0))) + .build(); + } + var typeParameters = visitTypeParameterList(typeParameterList, null); var modifiers = doVisitModifiers( @@ -2108,8 +2166,6 @@ public class AstBuilder extends AbstractAstBuilder { public UnresolvedMethodNode visitClassMethod(ClassMethod entry) { var headerSection = createSourceSection(entry.getHeaderSpan()); - var typeParameters = visitTypeParameterList(entry.getTypeParameterList()); - var modifiers = doVisitModifiers( entry.getModifiers(), VmModifier.VALID_METHOD_MODIFIERS, "invalidMethodModifier"); @@ -2119,11 +2175,15 @@ public class AstBuilder extends AbstractAstBuilder { var bodyContext = entry.getExpr(); var paramListCtx = entry.getParameterList(); - var descriptorBuilderAndBindings = createFrameDescriptorBuilderAndSlotVariables(paramListCtx); + var descriptorBuilderAndBindings = + createFrameDescriptorBuilderAndSlotVariables(paramListCtx, entry.getTypeParameterList()); var paramCount = paramListCtx.getParameters().size(); - var descriptorBuilder = descriptorBuilderAndBindings.first; - var bindings = descriptorBuilderAndBindings.second; + var descriptorBuilder = descriptorBuilderAndBindings.builder; + var bindings = descriptorBuilderAndBindings.bindings; + var typeParameters = + visitTypeParameterList( + entry.getTypeParameterList(), descriptorBuilderAndBindings.typeBindings); var annotations = doVisitAnnotations(entry.getAnnotations(), methodName); @@ -2204,7 +2264,7 @@ public class AstBuilder extends AbstractAstBuilder { var isLocal = VmModifier.isLocal(modifiers); var name = org.pkl.core.runtime.Identifier.property(typeAlias.getName().getValue(), isLocal); - var typeParameters = visitTypeParameterList(typeAlias.getTypeParameterList()); + var typeParameters = visitTypeParameterList(typeAlias.getTypeParameterList(), null); var objectMemberModifiers = isLocal ? VmModifier.LOCAL_TYPEALIAS_OBJECT_MEMBER : VmModifier.TYPEALIAS_OBJECT_MEMBER; @@ -2274,41 +2334,43 @@ public class AstBuilder extends AbstractAstBuilder { return (ExpressionNode) expr.accept(this); } - @Override - public List visitTypeParameterList(@Nullable TypeParameterList ctx) { - if (ctx == null) return List.of(); - - if (!(ctx.parent() instanceof TypeAlias) && !isStdLibModule) { - throw exceptionBuilder() - .evalError("cannotDeclareTypeParameter") - .withSourceSection(createSourceSection(ctx.getParameters().get(0))) - .build(); - } + public TypeParameter[] visitTypeParameterList( + @Nullable TypeParameterList ctx, FrameSlotVariable @Nullable [] typeBindings) { + if (ctx == null) return new TypeParameter[0]; var params = ctx.getParameters(); var size = params.size(); - var result = new ArrayList(size); + var result = new TypeParameter[size]; + for (var i = 0; i < size; i++) { var paramCtx = params.get(i); Variance variance; var nodeVariance = paramCtx.getVariance(); if (nodeVariance == null) { - variance = TypeParameter.Variance.INVARIANT; + variance = org.pkl.core.TypeParameter.Variance.INVARIANT; } else { variance = switch (nodeVariance) { - case IN -> TypeParameter.Variance.CONTRAVARIANT; - case OUT -> TypeParameter.Variance.COVARIANT; + case IN -> org.pkl.core.TypeParameter.Variance.CONTRAVARIANT; + case OUT -> org.pkl.core.TypeParameter.Variance.COVARIANT; }; } var parameterName = paramCtx.getIdentifier().getValue(); - if (result.stream().anyMatch(it -> it.getName().equals(parameterName))) { - throw exceptionBuilder() - .evalError("duplicateTypeParameter", parameterName) - .withSourceSection(createSourceSection(paramCtx)) - .build(); + for (var j = 0; j < i; j++) { + if (result[j].name().equals(parameterName)) { + throw exceptionBuilder() + .evalError("duplicateTypeParameter", parameterName) + .withSourceSection(createSourceSection(paramCtx)) + .build(); + } } - result.add(new TypeParameter(variance, parameterName, i)); + result[i] = + new TypeParameter( + createSourceSection(paramCtx), + typeBindings != null ? typeBindings[i].slot() : -1, + i, + parameterName, + variance); } return result; } @@ -2417,9 +2479,9 @@ public class AstBuilder extends AbstractAstBuilder { var bindings = parametersDescriptorAndBindings == null ? new FrameSlotVariable[0] - : parametersDescriptorAndBindings.second; + : parametersDescriptorAndBindings.bindings; var parametersFrameDescriptorBuilder = - parametersDescriptorAndBindings == null ? null : parametersDescriptorAndBindings.first; + parametersDescriptorAndBindings == null ? null : parametersDescriptorAndBindings.builder; return symbolTable.enterObjectScope( bindings, @@ -2437,9 +2499,9 @@ public class AstBuilder extends AbstractAstBuilder { // constraints (e.g. containing `let` expressions) may add further slots to it var parameterTypes = doVisitParameterTypes(body); var parametersDescriptor = - parametersDescriptorAndBindings == null + parametersFrameDescriptorBuilder == null ? null - : parametersDescriptorAndBindings.first.build(); + : parametersFrameDescriptorBuilder.build(); var members = EconomicMaps.create(); var elements = new ArrayList(); @@ -2782,6 +2844,7 @@ public class AstBuilder extends AbstractAstBuilder { Expr expr, @Nullable TypeAnnotation typeAnnotation, boolean isModuleMethod) { + var modifiers = doVisitModifiers( modifierNodes, VmModifier.VALID_OBJECT_MEMBER_MODIFIERS, "invalidObjectMemberModifier"); @@ -2795,25 +2858,21 @@ public class AstBuilder extends AbstractAstBuilder { var methodName = org.pkl.core.runtime.Identifier.method(identifier.getValue(), true); - var frameDescriptorBuilderAndBindings = createFrameDescriptorBuilderAndSlotVariables(paramList); + var frameDescriptorBuilderAndBindings = + createFrameDescriptorBuilderAndSlotVariables(paramList, typeParamList); - var frameDescriptorBuilder = frameDescriptorBuilderAndBindings.first; - var bindings = frameDescriptorBuilderAndBindings.second; + var frameDescriptorBuilder = frameDescriptorBuilderAndBindings.builder; + var bindings = frameDescriptorBuilderAndBindings.bindings; + var typeParameters = + visitTypeParameterList(typeParamList, frameDescriptorBuilderAndBindings.typeBindings); return symbolTable.enterMethod( methodName, getConstLevel(modifiers), bindings, frameDescriptorBuilder, - List.of(), + typeParameters, scope -> { - if (typeParamList != null) { - throw exceptionBuilder() - .evalError("cannotDeclareTypeParameter") - .withSourceSection(createSourceSection(typeParamList)) - .build(); - } - var member = new ObjectMember( createSourceSection(method), @@ -2831,6 +2890,7 @@ public class AstBuilder extends AbstractAstBuilder { member, body, paramList.getParameters().size(), + typeParameters, parameterTypeNodes, typeNode); @@ -2919,9 +2979,11 @@ public class AstBuilder extends AbstractAstBuilder { private ExpressionNode doVisitMethodAccessExpr(QualifiedAccessExpr expr, ArgumentList argList) { var sourceSection = createSourceSection(expr); var functionName = toIdentifier(expr.getIdentifier().getValue()); + var typeArgs = doVisitMethodTypeArguments(expr.getTypeArgumentList()); var receiver = visitExpr(expr.getExpr()); var needsConst = needsConst(receiver); var argInfo = visitArgumentList(argList); + var currentScope = symbolTable.getCurrentScope(); if (expr.isNullable()) { //noinspection ConstantConditions @@ -2930,10 +2992,12 @@ public class AstBuilder extends AbstractAstBuilder { InvokeMethodVirtualNodeGen.create( sourceSection, functionName, + typeArgs, argInfo.arguments, MemberLookupMode.EXPLICIT_RECEIVER, needsConst, argInfo.methodSlot, + currentScope.getQualifiedName(), PropagateNullReceiverNodeGen.create(unavailableSourceSection(), receiver), GetClassNodeGen.create(null))); } @@ -2942,10 +3006,12 @@ public class AstBuilder extends AbstractAstBuilder { return InvokeMethodVirtualNodeGen.create( sourceSection, functionName, + typeArgs, argInfo.arguments, MemberLookupMode.EXPLICIT_RECEIVER, needsConst, argInfo.methodSlot, + currentScope.getQualifiedName(), receiver, GetClassNodeGen.create(null)); } @@ -3140,18 +3206,44 @@ public class AstBuilder extends AbstractAstBuilder { return slotVariables; } - private Pair - createFrameDescriptorBuilderAndSlotVariables(ParameterList params) { - var builder = new FrameDescriptorBuilder(params.getParameters().size()); - return Pair.of(builder, getSlotVariables(params.getParameters(), builder)); + private FrameSlotVariable[] getTypeSlotVariables( + List typeParams, + FrameDescriptorBuilder frameDescriptorBuilder) { + var slotVariables = new FrameSlotVariable[typeParams.size()]; + for (var i = 0; i < typeParams.size(); i++) { + var param = typeParams.get(i); + slotVariables[i] = + frameDescriptorBuilder.addSlot( + FrameSlotKind.Illegal, toIdentifier(param.getIdentifier().getValue()), null); + } + return slotVariables; } - private @Nullable Pair<@Nullable FrameDescriptorBuilder, FrameSlotVariable[]> - createFrameDescriptorBuilderAndSlotVariables(ObjectBody body) { + private record FrameBindings( + FrameDescriptorBuilder builder, + FrameSlotVariable[] bindings, + FrameSlotVariable[] typeBindings) {} + + private FrameBindings createFrameDescriptorBuilderAndSlotVariables( + ParameterList params, @Nullable TypeParameterList typeParamList) { + List typeParams = + typeParamList != null ? typeParamList.getParameters() : List.of(); + var builder = new FrameDescriptorBuilder(params.getParameters().size() + typeParams.size()); + // NB: param slots are allocated before type param slots intentionally + // despite type args coming before args in FunctionNode calls + // this is because many places assume that param index == slot + // if desired, we could change this in the future since it's just the internal frame layout + var paramSlots = getSlotVariables(params.getParameters(), builder); + var typeParamSlots = getTypeSlotVariables(typeParams, builder); + return new FrameBindings(builder, paramSlots, typeParamSlots); + } + + private @Nullable FrameBindings createFrameDescriptorBuilderAndSlotVariables(ObjectBody body) { if (body.getParameters().isEmpty()) return null; var builder = new FrameDescriptorBuilder(body.getParameters().size()); - return Pair.of(builder, getSlotVariables(body.getParameters(), builder)); + return new FrameBindings( + builder, getSlotVariables(body.getParameters(), builder), new FrameSlotVariable[0]); } private void checkNotInsideForGenerator(Node ctx, String errorMessageKey) { diff --git a/pkl-core/src/main/java/org/pkl/core/ast/builder/SymbolTable.java b/pkl-core/src/main/java/org/pkl/core/ast/builder/SymbolTable.java index 0a4eb8065..dc14c854a 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/builder/SymbolTable.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/builder/SymbolTable.java @@ -21,7 +21,7 @@ import com.oracle.truffle.api.frame.FrameDescriptor; import java.util.*; import java.util.function.Function; import org.jspecify.annotations.Nullable; -import org.pkl.core.TypeParameter; +import org.pkl.core.PklBugException; import org.pkl.core.ast.ConstantNode; import org.pkl.core.ast.ExpressionNode; import org.pkl.core.ast.VmModifier; @@ -38,6 +38,7 @@ import org.pkl.core.runtime.FrameDescriptorBuilder; import org.pkl.core.runtime.FrameSlotVariable; import org.pkl.core.runtime.Identifier; import org.pkl.core.runtime.ModuleInfo; +import org.pkl.core.runtime.TypeParameter; import org.pkl.core.runtime.VmDataSize; import org.pkl.core.runtime.VmDuration; import org.pkl.core.runtime.VmUtils; @@ -59,19 +60,10 @@ public final class SymbolTable { return currentScope; } - public @Nullable TypeParameter findTypeParameter(String name) { - TypeParameter result; - for (var scope = currentScope; scope != null; scope = scope.getParent()) { - result = scope.getTypeParameter(name); - if (result != null) return result; - } - return null; - } - public ObjectMember enterClass( Identifier name, int modifiers, - List typeParameters, + TypeParameter[] typeParameters, Function nodeFactory) { return doEnter( new ClassScope( @@ -86,7 +78,7 @@ public final class SymbolTable { public ObjectMember enterTypeAlias( Identifier name, - List typeParameters, + TypeParameter[] typeParameters, Function nodeFactory) { try { this.isInTypeAliasScope = true; @@ -108,7 +100,7 @@ public final class SymbolTable { ConstLevel constLevel, FrameSlotVariable[] bindings, FrameDescriptorBuilder frameDescriptorBuilder, - List typeParameters, + TypeParameter[] typeParameters, Function nodeFactory) { return doEnter( new MethodScope( @@ -256,6 +248,7 @@ public final class SymbolTable { protected final Map properties = new HashMap<>(); // The methods defined on this (lexical) scope protected final Map methods = new HashMap<>(); + protected final Set knownTypeParameterNames; static int[] getSlots(FrameSlotVariable[] bindings) { if (bindings.length == 0) { @@ -275,7 +268,8 @@ public final class SymbolTable { ConstLevel constLevel, FrameDescriptorBuilder frameDescriptorBuilder, int[] forGeneratorSlots, - int[] parameterSlots) { + int[] parameterSlots, + Set knownTypeParameterNames) { this.parent = parent; this.name = name; this.qualifiedName = qualifiedName; @@ -290,6 +284,7 @@ public final class SymbolTable { : constLevel; this.forGeneratorSlots = forGeneratorSlots; this.parameterSlots = parameterSlots; + this.knownTypeParameterNames = knownTypeParameterNames; } public final @Nullable Scope getParent() { @@ -509,7 +504,8 @@ public final class SymbolTable { } public final VariableResolution resolveVariable(String name) { - var resolved = resolveLexical((scope, levelUp) -> scope.doResolveProperty(name, levelUp)); + var resolved = + resolveLexical((scope, levelUp) -> scope.doResolveProperty(name, levelUp), null); if (resolved != null) { return resolved; } @@ -522,7 +518,7 @@ public final class SymbolTable { } public final MethodResolution resolveMethod(String name) { - var resolved = resolveLexical((scope, levelUp) -> scope.doResolveMethod(name, levelUp)); + var resolved = resolveLexical((scope, levelUp) -> scope.doResolveMethod(name, levelUp), null); if (resolved != null) { return resolved; } @@ -534,13 +530,29 @@ public final class SymbolTable { return new ImplicitThisMethod(); } + public @Nullable TypeParameterResolution resolveTypeParameter(String name) { + if (!knownTypeParameterNames.contains(name)) { + return null; + } + + return resolveLexical( + (scope, levelUp) -> ((Scope) scope).doResolveTypeParameter(name, levelUp), + (scope, levelUp) -> scope.doResolveTypeParameter(name, levelUp)); + } + + protected @Nullable TypeParameterResolution doResolveTypeParameter(String name, int levelsUp) { + return null; + } + @FunctionalInterface - private interface ResolutionFunction { - @Nullable T apply(LexicalScope scope, int levelUp); + private interface ResolutionFunction { + @Nullable T apply(Scope scope, int levelUp); } @SuppressWarnings("unchecked") - private @Nullable R resolveLexical(ResolutionFunction fun) { + private @Nullable R resolveLexical( + ResolutionFunction fun, + @Nullable ResolutionFunction aliasFun) { var levelsUp = 0; var shouldSkip = false; var skippedObjectScope = false; @@ -554,7 +566,11 @@ public final class SymbolTable { if (scope instanceof AnnotationScope && scope.getParent() instanceof ClassScope) { levelsUp--; } - if (scope instanceof LexicalScope lex) { + if (aliasFun != null && scope instanceof TypeAliasScope typeAliasScope) { + // when resolving type params, also visit TypeAliasScopes with aliasFun + var result = aliasFun.apply(typeAliasScope, levelsUp); + if (result != null) return result; + } else if (scope instanceof LexicalScope lex) { if (shouldSkip && !(scope instanceof ForGeneratorScope)) { if (scope instanceof ObjectScope objectScope && objectScope.hasParams()) { // skipping the object scope should now see the object body params, because the params @@ -638,7 +654,8 @@ public final class SymbolTable { ConstLevel.NONE, frameDescriptorBuilder, parent.forGeneratorSlots, - EMPTY_INT_ARRAY); + EMPTY_INT_ARRAY, + parent.knownTypeParameterNames); this.bindings = bindings; } @@ -676,7 +693,7 @@ public final class SymbolTable { } public abstract static class TypeParameterizableScope extends Scope { - private final List typeParameters; + private final TypeParameter[] typeParameters; public TypeParameterizableScope( Scope parent, @@ -684,7 +701,7 @@ public final class SymbolTable { String qualifiedName, ConstLevel constLevel, FrameDescriptorBuilder frameDescriptorBuilder, - List typeParameters, + TypeParameter[] typeParameters, int[] forGeneratorSlots, int[] parameterSlots) { super( @@ -694,14 +711,27 @@ public final class SymbolTable { constLevel, frameDescriptorBuilder, forGeneratorSlots, - parameterSlots); + parameterSlots, + extendTypeParams(parent.knownTypeParameterNames, typeParameters)); this.typeParameters = typeParameters; } + private static Set extendTypeParams(Set base, TypeParameter[] typeParams) { + var res = new HashSet<>(base); + for (var typeParam : typeParams) { + res.add(typeParam.name()); + } + return res; + } + + public int getTypeParameterCount() { + return typeParameters.length; + } + @Override public @Nullable TypeParameter getTypeParameter(String name) { for (var param : typeParameters) { - if (name.equals(param.getName())) return param; + if (name.equals(param.name())) return param; } return null; } @@ -721,7 +751,8 @@ public final class SymbolTable { ConstLevel.NONE, new FrameDescriptorBuilder(), EMPTY_INT_ARRAY, - EMPTY_INT_ARRAY); + EMPTY_INT_ARRAY, + Set.of()); this.isBaseModule = isBaseModule; this.moduleInfo = moduleInfo; this.isAmend = moduleInfo.isAmend(); @@ -759,7 +790,7 @@ public final class SymbolTable { ConstLevel constLevel, FrameSlotVariable[] bindings, FrameDescriptorBuilder frameDescriptorBuilder, - List typeParameters) { + TypeParameter[] typeParameters) { super( parent, name, @@ -781,6 +812,16 @@ public final class SymbolTable { public @Nullable MethodResolution doResolveMethod(String name, int levelsUp) { return null; } + + public @Nullable TypeParameterResolution doResolveTypeParameter(String name, int levelsUp) { + var result = getTypeParameter(name); + if (result == null) return null; + if (result.variance() != org.pkl.core.TypeParameter.Variance.INVARIANT) { + throw new PklBugException( + "found type parameter variance modifier found on a method declaration"); + } + return new TypeParameterResolution(result, TypeParameter.OwnerType.METHOD, levelsUp); + } } public static final class LambdaScope extends Scope implements LexicalScope { @@ -798,7 +839,8 @@ public final class SymbolTable { parent.getConstLevel(), frameDescriptorBuilder, EMPTY_INT_ARRAY, - getSlots(bindings)); + getSlots(bindings), + parent.knownTypeParameterNames); this.bindings = bindings; } @@ -841,7 +883,8 @@ public final class SymbolTable { parent.getConstLevel(), parent.frameDescriptorBuilder, parent.forGeneratorSlots, - getMyParameterSlots(parent, binding)); + getMyParameterSlots(parent, binding), + parent.knownTypeParameterNames); this.binding = binding; } @@ -893,7 +936,8 @@ public final class SymbolTable { ConstLevel.NONE, getFrameDescriptorBuilder(parent), getForGeneratorSlots(parent), - getParameterSlots(parent)); + getParameterSlots(parent), + parent.knownTypeParameterNames); } } @@ -951,7 +995,8 @@ public final class SymbolTable { ConstLevel.NONE, frameDescriptorBuilder, getMyForGeneratorSlots(parent, keyBinding, valueBinding), - getMyParameterSlots(parent)); + getMyParameterSlots(parent), + parent.knownTypeParameterNames); this.keyBinding = keyBinding; this.valueBinding = valueBinding; } @@ -996,7 +1041,8 @@ public final class SymbolTable { // object members inherit the for-generator slots of the parent for-generator, if it // exists. parent instanceof ForGeneratorScope ? parent.forGeneratorSlots : EMPTY_INT_ARRAY, - parent.parameterSlots); + parent.parameterSlots, + parent.knownTypeParameterNames); } } @@ -1010,7 +1056,8 @@ public final class SymbolTable { ConstLevel.NONE, frameDescriptorBuilder, parent instanceof ForGeneratorScope ? parent.forGeneratorSlots : EMPTY_INT_ARRAY, - parent.parameterSlots); + parent.parameterSlots, + parent.knownTypeParameterNames); } } @@ -1023,7 +1070,7 @@ public final class SymbolTable { String qualifiedName, int modifiers, FrameDescriptorBuilder frameDescriptorBuilder, - List typeParameters) { + TypeParameter[] typeParameters) { super( parent, name, @@ -1049,6 +1096,12 @@ public final class SymbolTable { if (member == null) return null; return new LexicalMethod(false, isClosed, false, member.modifiers, levelsUp); } + + public @Nullable TypeParameterResolution doResolveTypeParameter(String name, int levelsUp) { + var result = getTypeParameter(name); + if (result == null) return null; + return new TypeParameterResolution(result, TypeParameter.OwnerType.CLASS, -1); + } } public static final class TypeAliasScope extends TypeParameterizableScope { @@ -1057,7 +1110,7 @@ public final class SymbolTable { Identifier name, String qualifiedName, FrameDescriptorBuilder frameDescriptorBuilder, - List typeParameters) { + TypeParameter[] typeParameters) { super( parent, name, @@ -1068,6 +1121,12 @@ public final class SymbolTable { parent.forGeneratorSlots, parent.parameterSlots); } + + public @Nullable TypeParameterResolution doResolveTypeParameter(String name, int levelsUp) { + var result = getTypeParameter(name); + if (result == null) return null; + return new TypeParameterResolution(result, TypeParameter.OwnerType.TYPEALIAS, -1); + } } /** @@ -1086,7 +1145,8 @@ public final class SymbolTable { ConstLevel.NONE, frameDescriptorBuilder, parent.forGeneratorSlots, - parent.parameterSlots); + parent.parameterSlots, + parent.knownTypeParameterNames); } } @@ -1100,7 +1160,8 @@ public final class SymbolTable { ConstLevel.MODULE, frameDescriptorBuilder, EMPTY_INT_ARRAY, - EMPTY_INT_ARRAY); + EMPTY_INT_ARRAY, + parent.knownTypeParameterNames); } } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/builder/TypeParameterResolution.java b/pkl-core/src/main/java/org/pkl/core/ast/builder/TypeParameterResolution.java new file mode 100644 index 000000000..c16766002 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/ast/builder/TypeParameterResolution.java @@ -0,0 +1,21 @@ +/* + * 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.core.ast.builder; + +import org.pkl.core.runtime.TypeParameter; + +public record TypeParameterResolution( + TypeParameter typeParameter, TypeParameter.OwnerType ownerType, int levelsUp) {} diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/AmendFunctionNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/AmendFunctionNode.java index a00d158bd..154c78caa 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/AmendFunctionNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/AmendFunctionNode.java @@ -31,6 +31,7 @@ import org.pkl.core.ast.frame.ReadExactFrameSlotNodeGen; import org.pkl.core.ast.member.FunctionNode; import org.pkl.core.ast.member.Lambda; import org.pkl.core.ast.type.TypeNode; +import org.pkl.core.ast.type.TypeParameterNode; import org.pkl.core.runtime.*; public final class AmendFunctionNode extends PklNode { @@ -87,6 +88,7 @@ public final class AmendFunctionNode extends PklNode { parameterTypeNodes, null, true, + new TypeParameterNode[0], new AmendFunctionBodyNode( sourceSection, hostNode.copy( diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInferParentFromMethodNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInferParentFromMethodNode.java index 7ce6c6695..23332293b 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInferParentFromMethodNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInferParentFromMethodNode.java @@ -32,7 +32,7 @@ public abstract class AbstractInferParentFromMethodNode extends AbstractInferPar protected abstract Method getMethod(VirtualFrame frame); - protected abstract @Nullable TypeNode getTypeNode(VirtualFrame frame, Method method); + protected abstract @Nullable TypeNode getTypeNode(Method method); @Idempotent protected boolean isFinalType(Method method, @Nullable TypeNode typeNode) { diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInferParentNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInferParentNode.java index 4d57173ec..0b52435ba 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInferParentNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInferParentNode.java @@ -25,6 +25,7 @@ import org.pkl.core.ast.type.TypeNode.TypeVariableNode; import org.pkl.core.ast.type.TypeNode.UnknownTypeNode; import org.pkl.core.runtime.VmDynamic; import org.pkl.core.runtime.VmLanguage; +import org.pkl.core.runtime.VmTypeArgument; import org.pkl.core.runtime.VmUtils; public abstract class AbstractInferParentNode extends ExpressionNode { @@ -41,11 +42,23 @@ public abstract class AbstractInferParentNode extends ExpressionNode { @Nullable TypeNode typeNode, SourceSection headerSection, String qualifiedName) { + return getDefaultValue(frame, typeNode, headerSection, qualifiedName, false, null); + } + + protected Object getDefaultValue( + VirtualFrame frame, + @Nullable TypeNode typeNode, + SourceSection headerSection, + String qualifiedName, + boolean isPreCall, + VmTypeArgument @Nullable [] typeArgumentOverrides) { if (typeNode == null || typeNode instanceof UnknownTypeNode) { return VmDynamic.empty(); } - var defaultValue = typeNode.createDefaultValue(frame, language, headerSection, qualifiedName); + var defaultValue = + typeNode.createDefaultValue( + frame, language, headerSection, qualifiedName, isPreCall, typeArgumentOverrides); if (defaultValue != null) { return defaultValue; } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInvokeLexicalMethodNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInvokeLexicalMethodNode.java index 49fd9b312..8a5f5f3bf 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInvokeLexicalMethodNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInvokeLexicalMethodNode.java @@ -17,23 +17,33 @@ package org.pkl.core.ast.expression.member; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.source.SourceSection; +import org.jspecify.annotations.Nullable; import org.pkl.core.ast.ExpressionNode; +import org.pkl.core.ast.type.UnresolvedTypeNode; import org.pkl.core.runtime.Identifier; import org.pkl.core.runtime.VmUtils; -public abstract sealed class AbstractInvokeLexicalMethodNode - extends AbstractInvokeLexicalOrQualifiedMethodNode - permits InvokeLexicalClassMethodNode, InvokeLexicalObjectMethodNode { +public abstract class AbstractInvokeLexicalMethodNode + extends AbstractInvokeLexicalOrQualifiedMethodNode { private final int levelsUp; public AbstractInvokeLexicalMethodNode( SourceSection sourceSection, Identifier methodName, int levelsUp, + UnresolvedTypeNode @Nullable [] unresolvedTypeArgumentNodes, ExpressionNode[] argumentNodes, boolean needsConst, - int methodSlot) { - super(sourceSection, methodName, argumentNodes, needsConst, methodSlot); + int methodSlot, + String qualifiedName) { + super( + sourceSection, + methodName, + unresolvedTypeArgumentNodes, + argumentNodes, + needsConst, + methodSlot, + qualifiedName); this.levelsUp = levelsUp; } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInvokeLexicalOrQualifiedMethodNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInvokeLexicalOrQualifiedMethodNode.java index ca040da9e..eaf9f72f4 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInvokeLexicalOrQualifiedMethodNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInvokeLexicalOrQualifiedMethodNode.java @@ -23,6 +23,7 @@ import com.oracle.truffle.api.source.SourceSection; import org.jspecify.annotations.Nullable; import org.pkl.core.ast.ExpressionNode; import org.pkl.core.ast.member.Method; +import org.pkl.core.ast.type.UnresolvedTypeNode; import org.pkl.core.runtime.Identifier; import org.pkl.core.runtime.VmObjectLike; @@ -34,9 +35,7 @@ import org.pkl.core.runtime.VmObjectLike; * InvokeLexicalObjectMethodNode}), or off of an explicit receiver expression ({@link * InvokeQualifiedClassMethodNode}, {@link InvokeQualifiedObjectMethodNode}). */ -public abstract sealed class AbstractInvokeLexicalOrQualifiedMethodNode - extends AbstractInvokeMethodNode - permits AbstractInvokeQualifiedMethodNode, AbstractInvokeLexicalMethodNode { +public abstract class AbstractInvokeLexicalOrQualifiedMethodNode extends AbstractInvokeMethodNode { protected final Identifier methodName; private final boolean needsConst; @@ -46,10 +45,12 @@ public abstract sealed class AbstractInvokeLexicalOrQualifiedMethodNode protected AbstractInvokeLexicalOrQualifiedMethodNode( SourceSection sourceSection, Identifier methodName, + UnresolvedTypeNode @Nullable [] unresolvedTypeArgumentNodes, ExpressionNode[] argumentNodes, boolean needsConst, - int methodSlot) { - super(sourceSection, argumentNodes, methodSlot); + int methodSlot, + String qualifiedName) { + super(sourceSection, unresolvedTypeArgumentNodes, argumentNodes, methodSlot, qualifiedName); this.methodName = methodName; this.needsConst = needsConst; this.isConstChecked = false; diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInvokeMethodNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInvokeMethodNode.java index cabfbf9ca..adf976b09 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInvokeMethodNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInvokeMethodNode.java @@ -15,6 +15,8 @@ */ package org.pkl.core.ast.expression.member; +import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; import com.oracle.truffle.api.TruffleSafepoint; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.ExplodeLoop; @@ -22,36 +24,115 @@ import com.oracle.truffle.api.source.SourceSection; import org.jspecify.annotations.Nullable; import org.pkl.core.ast.ExpressionNode; import org.pkl.core.ast.member.Method; +import org.pkl.core.ast.type.TypeArgumentTypeNode; +import org.pkl.core.ast.type.UnresolvedTypeNode; +import org.pkl.core.runtime.VmLanguage; +import org.pkl.core.runtime.VmObjectLike; +import org.pkl.core.runtime.VmTypeArgument; public abstract class AbstractInvokeMethodNode extends ExpressionNode { @Children protected final ExpressionNode[] argumentNodes; protected final int methodSlot; + protected final String qualifiedName; - public AbstractInvokeMethodNode( - SourceSection sourceSection, ExpressionNode[] argumentNodes, int methodSlot) { + @Children protected UnresolvedTypeNode @Nullable [] unresolvedTypeArgumentNodes; + + @CompilationFinal(dimensions = 1) + protected TypeArgumentTypeNode @Nullable [] typeArgumentRootNodes; + + @CompilationFinal boolean typeArgumentsNeedMaterializedFrame = false; + + protected AbstractInvokeMethodNode( + SourceSection sourceSection, + UnresolvedTypeNode @Nullable [] unresolvedTypeArgumentNodes, + ExpressionNode[] argumentNodes, + int methodSlot, + String qualifiedName) { super(sourceSection); + this.unresolvedTypeArgumentNodes = unresolvedTypeArgumentNodes; this.argumentNodes = argumentNodes; this.methodSlot = methodSlot; + this.qualifiedName = qualifiedName; + } + + protected TypeArgumentTypeNode @Nullable [] getTypeArgumentRootNodes(VirtualFrame frame) { + if (typeArgumentRootNodes == null) { + if (unresolvedTypeArgumentNodes != null) { + var language = VmLanguage.get(this); + var frameDescriptor = frame.getFrameDescriptor(); + CompilerDirectives.transferToInterpreterAndInvalidate(); + + var rootNodes = new TypeArgumentTypeNode[unresolvedTypeArgumentNodes.length]; + for (var i = 0; i < rootNodes.length; i++) { + var typeNode = unresolvedTypeArgumentNodes[i].execute(frame); + rootNodes[i] = + new TypeArgumentTypeNode( + language, + frameDescriptor, + sourceSection, + qualifiedName + ".", + typeNode); + typeArgumentsNeedMaterializedFrame = + typeArgumentsNeedMaterializedFrame || typeNode.getTypeArgumentRequiresFrame(); + } + typeArgumentRootNodes = rootNodes; + unresolvedTypeArgumentNodes = null; + } else { + return null; + } + } + return typeArgumentRootNodes; + } + + protected VmTypeArgument @Nullable [] getTypeArguments( + VirtualFrame frame, @Nullable Method method) { + var rootNodes = getTypeArgumentRootNodes(frame); + if (rootNodes == null) return null; + + var typeParameterCount = method == null ? 0 : method.getTypeParameterCount(); + if (rootNodes.length != typeParameterCount) { + CompilerDirectives.transferToInterpreter(); + throw exceptionBuilder() + .evalError("wrongTypeArgumentCount", typeParameterCount, rootNodes.length) + .build(); + } + + var argFrame = typeArgumentsNeedMaterializedFrame ? frame.materialize() : null; + var typeArgs = new VmTypeArgument[rootNodes.length]; + for (var i = 0; i < typeArgs.length; i++) { + var rootNode = rootNodes[i]; + typeArgs[i] = new VmTypeArgument(rootNode, argFrame); + } + return typeArgs; } @ExplodeLoop protected Object[] evalArgs( - VirtualFrame frame, @Nullable Method method, Object owner, @Nullable Object receiver) { + VirtualFrame frame, @Nullable Method method, VmObjectLike owner, @Nullable Object receiver) { Object prevMethod = null; + var typeArgs = getTypeArguments(frame, method); if (methodSlot > -1) { + if (method != null) { + method.ensureInitialized(owner); + } prevMethod = frame.getObject(methodSlot); - frame.setObject(methodSlot, method); + frame.setObject(methodSlot, new MethodCall(method, typeArgs)); } - var args = new Object[2 + argumentNodes.length]; + var typeParameterCount = method != null ? method.getTypeParameterCount() : 0; + var args = new Object[2 + argumentNodes.length + typeParameterCount]; args[0] = receiver; args[1] = owner; + if (typeArgs != null) { + System.arraycopy(typeArgs, 0, args, 2, typeParameterCount); + } + try { for (var i = 0; i < argumentNodes.length; i++) { TruffleSafepoint.poll(this); - args[2 + i] = argumentNodes[i].executeGeneric(frame); + args[2 + typeParameterCount + i] = argumentNodes[i].executeGeneric(frame); } } finally { if (methodSlot > -1) { @@ -61,4 +142,6 @@ public abstract class AbstractInvokeMethodNode extends ExpressionNode { return args; } + + public record MethodCall(@Nullable Method method, VmTypeArgument @Nullable [] typeArguments) {} } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInvokeQualifiedMethodNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInvokeQualifiedMethodNode.java index 9fd619d50..9104e42d7 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInvokeQualifiedMethodNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInvokeQualifiedMethodNode.java @@ -17,7 +17,9 @@ package org.pkl.core.ast.expression.member; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.source.SourceSection; +import org.jspecify.annotations.Nullable; import org.pkl.core.ast.ExpressionNode; +import org.pkl.core.ast.type.UnresolvedTypeNode; import org.pkl.core.runtime.Identifier; import org.pkl.core.runtime.VmTyped; @@ -29,11 +31,20 @@ public abstract sealed class AbstractInvokeQualifiedMethodNode protected AbstractInvokeQualifiedMethodNode( SourceSection sourceSection, Identifier methodName, + UnresolvedTypeNode @Nullable [] unresolvedTypeArgumentNodes, ExpressionNode[] argumentNodes, boolean needsConst, ExpressionNode getReceiverNode, - int methodSlot) { - super(sourceSection, methodName, argumentNodes, needsConst, methodSlot); + int methodSlot, + String qualifiedName) { + super( + sourceSection, + methodName, + unresolvedTypeArgumentNodes, + argumentNodes, + needsConst, + methodSlot, + qualifiedName); this.getReceiverNode = getReceiverNode; } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InferParentWithinMethodArgumentNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InferParentWithinMethodArgumentNode.java index cf540768f..a84a9e844 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InferParentWithinMethodArgumentNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InferParentWithinMethodArgumentNode.java @@ -21,9 +21,11 @@ import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.source.SourceSection; import org.jspecify.annotations.Nullable; +import org.pkl.core.ast.expression.member.AbstractInvokeMethodNode.MethodCall; import org.pkl.core.ast.member.Method; import org.pkl.core.ast.type.TypeNode; import org.pkl.core.runtime.VmLanguage; +import org.pkl.core.runtime.VmTypeArgument; public abstract class InferParentWithinMethodArgumentNode extends AbstractInferParentFromMethodNode { @@ -39,9 +41,14 @@ public abstract class InferParentWithinMethodArgumentNode @Override protected Method getMethod(VirtualFrame frame) { - var method = (Method) frame.getObject(methodSlot); + var methodCall = (MethodCall) frame.getObject(methodSlot); + if (methodCall == null) { + CompilerDirectives.transferToInterpreter(); + throw exceptionBuilder().evalError("cannotInferParent").build(); + } + + var method = methodCall.method(); if (method == null) { - // used in FunctionN.apply() CompilerDirectives.transferToInterpreter(); throw exceptionBuilder().evalError("cannotInferParent").build(); } @@ -49,9 +56,18 @@ public abstract class InferParentWithinMethodArgumentNode return method; } + protected VmTypeArgument @Nullable [] getTypeArgumentOverrides(VirtualFrame frame) { + var methodCall = (MethodCall) frame.getObject(methodSlot); + if (methodCall == null) { + CompilerDirectives.transferToInterpreter(); + throw exceptionBuilder().evalError("cannotInferParent").build(); + } + return methodCall.typeArguments(); + } + @Override - protected @Nullable TypeNode getTypeNode(VirtualFrame frame, Method method) { - return method.getParameterTypeNode(frame, argIndex); + protected @Nullable TypeNode getTypeNode(Method method) { + return method.getFunctionNode().getParameterTypeNode(argIndex); } // keep specializations in sync with other AbstractInferParentFromMethodNode subclasses @@ -61,9 +77,9 @@ public abstract class InferParentWithinMethodArgumentNode protected final Object evalCached( @SuppressWarnings("unused") VirtualFrame frame, @Cached("getMethod(frame)") @SuppressWarnings("unused") Method cachedMethod, - @Cached("getTypeNode(frame, cachedMethod)") @SuppressWarnings("unused") TypeNode typeNode, + @Cached("getTypeNode(cachedMethod)") @SuppressWarnings("unused") TypeNode typeNode, @Cached( - "getDefaultValue(frame, typeNode, cachedMethod.getHeaderSection(), cachedMethod.getQualifiedName())") + "getDefaultValue(frame, typeNode, cachedMethod.getHeaderSection(), cachedMethod.getQualifiedName(), true, getTypeArgumentOverrides(frame))") Object defaultValue) { return defaultValue; } @@ -71,21 +87,30 @@ public abstract class InferParentWithinMethodArgumentNode @Specialization(replaces = "evalCached") protected final Object eval(VirtualFrame frame) { var method = getMethod(frame); - var typeNode = getTypeNode(frame, method); - return getDefaultValue(frame, typeNode, method.getHeaderSection(), method.getQualifiedName()); + var typeNode = getTypeNode(method); + return getDefaultValue( + frame, + typeNode, + method.getHeaderSection(), + method.getQualifiedName(), + true, + getTypeArgumentOverrides(frame)); } @Override - protected Object getDefaultValue( + protected final Object getDefaultValue( VirtualFrame frame, @Nullable TypeNode typeNode, SourceSection headerSection, - String qualifiedName) { + String qualifiedName, + boolean isPreCall, + VmTypeArgument @Nullable [] typeArgumentOverrides) { if (typeNode != null && typeNode.isSelfType()) { CompilerDirectives.transferToInterpreter(); throw exceptionBuilder().evalError("cannotInferParent").build(); } - return super.getDefaultValue(frame, typeNode, headerSection, qualifiedName); + return super.getDefaultValue( + frame, typeNode, headerSection, qualifiedName, isPreCall, typeArgumentOverrides); } } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InferParentWithinMethodNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InferParentWithinMethodNode.java index ddf4a2231..ccaaa8cbf 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InferParentWithinMethodNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InferParentWithinMethodNode.java @@ -52,8 +52,8 @@ public abstract class InferParentWithinMethodNode extends AbstractInferParentFro } @Override - protected @Nullable TypeNode getTypeNode(VirtualFrame frame, Method method) { - return method.getReturnTypeNode(frame); + protected @Nullable TypeNode getTypeNode(Method method) { + return method.getFunctionNode().getReturnTypeNode(); } // keep specializations in sync with other AbstractInferParentFromMethodNode subclasses @@ -63,7 +63,7 @@ public abstract class InferParentWithinMethodNode extends AbstractInferParentFro protected final Object evalCached( @SuppressWarnings("unused") VirtualFrame frame, @Cached("getMethod(frame)") @SuppressWarnings("unused") Method cachedMethod, - @Cached("getTypeNode(frame, cachedMethod)") @SuppressWarnings("unused") TypeNode typeNode, + @Cached("getTypeNode(cachedMethod)") @SuppressWarnings("unused") TypeNode typeNode, @Cached( "getDefaultValue(frame, typeNode, cachedMethod.getHeaderSection(), cachedMethod.getQualifiedName())") Object defaultValue) { @@ -73,7 +73,7 @@ public abstract class InferParentWithinMethodNode extends AbstractInferParentFro @Specialization(replaces = "evalCached") protected final Object eval(VirtualFrame frame) { var method = getMethod(frame); - var typeNode = getTypeNode(frame, method); + var typeNode = getTypeNode(method); return getDefaultValue(frame, typeNode, method.getHeaderSection(), method.getQualifiedName()); } } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InferParentWithinObjectMethodNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InferParentWithinObjectMethodNode.java index 7f0151127..c2c3208b6 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InferParentWithinObjectMethodNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InferParentWithinObjectMethodNode.java @@ -59,8 +59,8 @@ public abstract class InferParentWithinObjectMethodNode extends AbstractInferPar } @Override - protected @Nullable TypeNode getTypeNode(VirtualFrame frame, Method method) { - return method.getReturnTypeNode(frame); + protected @Nullable TypeNode getTypeNode(Method method) { + return method.getFunctionNode().getReturnTypeNode(); } // keep specializations in sync with other AbstractInferParentFromMethodNode subclasses @@ -70,7 +70,7 @@ public abstract class InferParentWithinObjectMethodNode extends AbstractInferPar protected final Object evalCached( @SuppressWarnings("unused") VirtualFrame frame, @Cached("getMethod(frame)") @SuppressWarnings("unused") Method cachedMethod, - @Cached("getTypeNode(frame, cachedMethod)") @SuppressWarnings("unused") TypeNode typeNode, + @Cached("getTypeNode(cachedMethod)") @SuppressWarnings("unused") TypeNode typeNode, @Cached( "getDefaultValue(frame, typeNode, cachedMethod.getHeaderSection(), cachedMethod.getQualifiedName())") Object defaultValue) { @@ -80,7 +80,7 @@ public abstract class InferParentWithinObjectMethodNode extends AbstractInferPar @Specialization(replaces = "evalCached") protected final Object eval(VirtualFrame frame) { var method = getMethod(frame); - var typeNode = getTypeNode(frame, method); + var typeNode = getTypeNode(method); return getDefaultValue(frame, typeNode, method.getHeaderSection(), method.getQualifiedName()); } } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeLexicalClassMethodNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeLexicalClassMethodNode.java index c25be41e8..0f086644c 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeLexicalClassMethodNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeLexicalClassMethodNode.java @@ -16,8 +16,10 @@ package org.pkl.core.ast.expression.member; import com.oracle.truffle.api.source.SourceSection; +import org.jspecify.annotations.Nullable; import org.pkl.core.ast.ExpressionNode; import org.pkl.core.ast.member.Method; +import org.pkl.core.ast.type.UnresolvedTypeNode; import org.pkl.core.runtime.Identifier; import org.pkl.core.runtime.VmObjectLike; @@ -30,10 +32,20 @@ public final class InvokeLexicalClassMethodNode extends AbstractInvokeLexicalMet SourceSection sourceSection, Identifier methodName, int levelsUp, + UnresolvedTypeNode @Nullable [] unresolvedTypeArgumentNodes, ExpressionNode[] argumentNodes, boolean needsConst, - int methodSlot) { - super(sourceSection, methodName, levelsUp, argumentNodes, needsConst, methodSlot); + int methodSlot, + String qualifiedName) { + super( + sourceSection, + methodName, + levelsUp, + unresolvedTypeArgumentNodes, + argumentNodes, + needsConst, + methodSlot, + qualifiedName); } @Override diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeLexicalObjectMethodNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeLexicalObjectMethodNode.java index 924cdd84e..af77c1383 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeLexicalObjectMethodNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeLexicalObjectMethodNode.java @@ -16,10 +16,12 @@ package org.pkl.core.ast.expression.member; import com.oracle.truffle.api.source.SourceSection; +import org.jspecify.annotations.Nullable; import org.pkl.core.ast.ExpressionNode; import org.pkl.core.ast.VmModifier; import org.pkl.core.ast.member.Method; import org.pkl.core.ast.member.ObjectMethodNode; +import org.pkl.core.ast.type.UnresolvedTypeNode; import org.pkl.core.runtime.Identifier; import org.pkl.core.runtime.VmObjectLike; @@ -29,10 +31,20 @@ public final class InvokeLexicalObjectMethodNode extends AbstractInvokeLexicalMe SourceSection sourceSection, Identifier methodName, int levelsUp, + UnresolvedTypeNode @Nullable [] unresolvedTypeArgumentNodes, ExpressionNode[] argumentNodes, boolean needsConst, - int methodSlot) { - super(sourceSection, methodName, levelsUp, argumentNodes, needsConst, methodSlot); + int methodSlot, + String qualifiedName) { + super( + sourceSection, + methodName, + levelsUp, + unresolvedTypeArgumentNodes, + argumentNodes, + needsConst, + methodSlot, + qualifiedName); } @Override diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeMethodDirectNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeMethodDirectNode.java index 78283cb89..ff55476b4 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeMethodDirectNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeMethodDirectNode.java @@ -18,8 +18,10 @@ package org.pkl.core.ast.expression.member; import com.oracle.truffle.api.frame.VirtualFrame; 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.member.ClassMethod; +import org.pkl.core.ast.type.UnresolvedTypeNode; import org.pkl.core.runtime.VmObjectLike; /** A non-virtual ("direct") method call. Used only for methods on {@code pkl:base}. */ @@ -34,9 +36,11 @@ public final class InvokeMethodDirectNode extends AbstractInvokeMethodNode { SourceSection sourceSection, ClassMethod method, ExpressionNode receiverNode, + UnresolvedTypeNode @Nullable [] unresolvedTypeArgumentNodes, ExpressionNode[] argumentNodes, - int methodSlot) { - super(sourceSection, argumentNodes, methodSlot); + int methodSlot, + String qualifiedName) { + super(sourceSection, unresolvedTypeArgumentNodes, argumentNodes, methodSlot, qualifiedName); this.method = method; this.owner = method.getOwner(); this.receiverNode = receiverNode; diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeMethodVirtualNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeMethodVirtualNode.java index 1b61b58e4..718e2f96d 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeMethodVirtualNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeMethodVirtualNode.java @@ -28,10 +28,12 @@ import com.oracle.truffle.api.instrumentation.ProbeNode; import com.oracle.truffle.api.nodes.DirectCallNode; import com.oracle.truffle.api.nodes.IndirectCallNode; 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.internal.GetClassNode; import org.pkl.core.ast.member.ClassMethod; +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; @@ -50,11 +52,13 @@ public abstract class InvokeMethodVirtualNode extends AbstractInvokeMethodNode { protected InvokeMethodVirtualNode( SourceSection sourceSection, Identifier methodName, + UnresolvedTypeNode @Nullable [] unresolvedTypeArgumentNodes, ExpressionNode[] argumentNodes, MemberLookupMode lookupMode, boolean needsConst, - int methodSlot) { - super(sourceSection, argumentNodes, methodSlot); + int methodSlot, + String qualifiedName) { + super(sourceSection, unresolvedTypeArgumentNodes, argumentNodes, methodSlot, qualifiedName); this.methodName = methodName; this.lookupMode = lookupMode; this.needsConst = needsConst; @@ -63,10 +67,20 @@ public abstract class InvokeMethodVirtualNode extends AbstractInvokeMethodNode { protected InvokeMethodVirtualNode( SourceSection sourceSection, Identifier methodName, + UnresolvedTypeNode @Nullable [] unresolvedTypeArgumentNodes, ExpressionNode[] argumentNodes, MemberLookupMode lookupMode, - int methodSlot) { - this(sourceSection, methodName, argumentNodes, lookupMode, false, methodSlot); + int methodSlot, + String qualifiedName) { + this( + sourceSection, + methodName, + unresolvedTypeArgumentNodes, + argumentNodes, + lookupMode, + false, + methodSlot, + qualifiedName); } /** @@ -146,7 +160,16 @@ public abstract class InvokeMethodVirtualNode extends AbstractInvokeMethodNode { @Override public WrapperNode createWrapper(ProbeNode probe) { return new InvokeMethodVirtualNodeWrapper( - sourceSection, methodName, argumentNodes, lookupMode, needsConst, methodSlot, this, probe); + sourceSection, + methodName, + unresolvedTypeArgumentNodes, + argumentNodes, + lookupMode, + needsConst, + methodSlot, + qualifiedName, + this, + probe); } private void checkConst(ClassMethod method) { diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeQualifiedClassMethodNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeQualifiedClassMethodNode.java index 9dd8b4159..46901d870 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeQualifiedClassMethodNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeQualifiedClassMethodNode.java @@ -16,8 +16,10 @@ package org.pkl.core.ast.expression.member; import com.oracle.truffle.api.source.SourceSection; +import org.jspecify.annotations.Nullable; import org.pkl.core.ast.ExpressionNode; import org.pkl.core.ast.member.Method; +import org.pkl.core.ast.type.UnresolvedTypeNode; import org.pkl.core.runtime.Identifier; import org.pkl.core.runtime.VmObjectLike; @@ -26,11 +28,21 @@ public final class InvokeQualifiedClassMethodNode extends AbstractInvokeQualifie public InvokeQualifiedClassMethodNode( SourceSection sourceSection, Identifier methodName, + UnresolvedTypeNode @Nullable [] unresolvedTypeArgumentNodes, ExpressionNode[] argumentNodes, boolean needsConst, ExpressionNode getReceiverNode, - int methodSlot) { - super(sourceSection, methodName, argumentNodes, needsConst, getReceiverNode, methodSlot); + int methodSlot, + String qualifiedName) { + super( + sourceSection, + methodName, + unresolvedTypeArgumentNodes, + argumentNodes, + needsConst, + getReceiverNode, + methodSlot, + qualifiedName); } @Override diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeQualifiedObjectMethodNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeQualifiedObjectMethodNode.java index 9aa288ab9..50e8b17b0 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeQualifiedObjectMethodNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeQualifiedObjectMethodNode.java @@ -16,10 +16,12 @@ package org.pkl.core.ast.expression.member; import com.oracle.truffle.api.source.SourceSection; +import org.jspecify.annotations.Nullable; import org.pkl.core.ast.ExpressionNode; import org.pkl.core.ast.VmModifier; import org.pkl.core.ast.member.Method; import org.pkl.core.ast.member.ObjectMethodNode; +import org.pkl.core.ast.type.UnresolvedTypeNode; import org.pkl.core.runtime.Identifier; import org.pkl.core.runtime.VmObjectLike; @@ -28,11 +30,21 @@ public final class InvokeQualifiedObjectMethodNode extends AbstractInvokeQualifi public InvokeQualifiedObjectMethodNode( SourceSection sourceSection, Identifier methodName, + UnresolvedTypeNode @Nullable [] unresolvedTypeArgumentNodes, ExpressionNode[] argumentNodes, boolean needsConst, ExpressionNode getReceiverNode, - int methodSlot) { - super(sourceSection, methodName, argumentNodes, needsConst, getReceiverNode, methodSlot); + int methodSlot, + String qualifiedName) { + super( + sourceSection, + methodName, + unresolvedTypeArgumentNodes, + argumentNodes, + needsConst, + getReceiverNode, + methodSlot, + qualifiedName); } @Override diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeSuperMethodNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeSuperMethodNode.java index ee7075cbe..c70f4cbfb 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeSuperMethodNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeSuperMethodNode.java @@ -21,8 +21,10 @@ import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.frame.VirtualFrame; 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.member.ClassMethod; +import org.pkl.core.ast.type.UnresolvedTypeNode; import org.pkl.core.runtime.Identifier; import org.pkl.core.runtime.VmFunction; import org.pkl.core.runtime.VmUtils; @@ -34,10 +36,12 @@ public abstract class InvokeSuperMethodNode extends AbstractInvokeMethodNode { protected InvokeSuperMethodNode( SourceSection sourceSection, Identifier methodName, + UnresolvedTypeNode @Nullable [] unresolvedTypeArgumentNodes, ExpressionNode[] argumentNodes, boolean needsConst, - int methodSlot) { - super(sourceSection, argumentNodes, methodSlot); + int methodSlot, + String qualifiedName) { + super(sourceSection, unresolvedTypeArgumentNodes, argumentNodes, methodSlot, qualifiedName); this.needsConst = needsConst; assert !methodName.isLocalMethod(); diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/primary/GetModuleNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/primary/GetModuleNode.java index ea1abb8fc..30d2fb743 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/primary/GetModuleNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/primary/GetModuleNode.java @@ -35,6 +35,12 @@ public final class GetModuleNode extends ExpressionNode { public Object executeGeneric(VirtualFrame frame) { CompilerDirectives.transferToInterpreter(); + var levelsUp = getLevelsUp(frame); + return replace(levelsUp == 0 ? new GetReceiverNode() : new GetEnclosingReceiverNode(levelsUp)) + .executeGeneric(frame); + } + + public static int getLevelsUp(VirtualFrame frame) { var levelsUp = -1; for (var current = VmUtils.getOwner(frame); current != null; @@ -43,8 +49,6 @@ public final class GetModuleNode extends ExpressionNode { levelsUp += 1; } } - - return replace(levelsUp == 0 ? new GetReceiverNode() : new GetEnclosingReceiverNode(levelsUp)) - .executeGeneric(frame); + return levelsUp; } } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/internal/ToStringNode.java b/pkl-core/src/main/java/org/pkl/core/ast/internal/ToStringNode.java index 570ca8179..b16b824a5 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/internal/ToStringNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/internal/ToStringNode.java @@ -90,9 +90,11 @@ public abstract class ToStringNode extends UnaryExpressionNode { return InvokeMethodVirtualNodeGen.create( sourceSection, Identifier.TO_STRING, + null, new ExpressionNode[] {}, MemberLookupMode.EXPLICIT_RECEIVER, -1, + "", null, null); } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/member/ClassMethod.java b/pkl-core/src/main/java/org/pkl/core/ast/member/ClassMethod.java index 0f51712e9..c75130d53 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/member/ClassMethod.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/member/ClassMethod.java @@ -18,19 +18,17 @@ package org.pkl.core.ast.member; import com.oracle.truffle.api.CallTarget; import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; -import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.source.SourceSection; import java.util.List; import org.jspecify.annotations.Nullable; import org.pkl.core.PClass; -import org.pkl.core.TypeParameter; import org.pkl.core.ast.VmModifier; -import org.pkl.core.ast.type.TypeNode; import org.pkl.core.runtime.*; +import org.pkl.core.runtime.TypeParameter; import org.pkl.core.util.LateInit; public final class ClassMethod extends ClassMember implements Method { - private final List typeParameters; + private final TypeParameter[] typeParameters; // null = not deprecated, "" = no/empty message in the @Deprecated body private final @Nullable String deprecation; @@ -46,7 +44,7 @@ public final class ClassMethod extends ClassMember implements Method { SourceSection @Nullable [] docComment, List annotations, VmTyped owner, - List typeParameters, + TypeParameter[] typeParameters, @Nullable String deprecation) { super( @@ -68,6 +66,14 @@ public final class ClassMethod extends ClassMember implements Method { this.functionNode = functionNode; } + @Override + public FunctionNode getFunctionNode(@Nullable SourceSection callSite) { + if (callSite != null && deprecation != null) { + reportDeprecation(callSite); + } + return functionNode; + } + public CallTarget getCallTarget() { return functionNode.getCallTarget(); } @@ -101,13 +107,14 @@ public final class ClassMethod extends ClassMember implements Method { return functionNode.getParameterCount(); } - public @Nullable TypeNode getReturnTypeNode() { - return functionNode.getReturnTypeNode(); + @Override + public int getTypeParameterCount() { + return functionNode.getTypeParameterCount(); } @Override - public @Nullable TypeNode getReturnTypeNode(VirtualFrame frame) { - return functionNode.getReturnTypeNode(); + public void ensureInitialized(VmObjectLike owner) { + // do nothing; class methods are always initialized } @Override @@ -140,10 +147,7 @@ public final class ClassMethod extends ClassMember implements Method { } public PClass.Method export(PClass owner) { - return functionNode.export(owner, docComment, annotations, modifiers, typeParameters); - } - - public @Nullable TypeNode getParameterTypeNode(VirtualFrame frame, int idx) { - return functionNode.getParameterTypeNode(idx); + return functionNode.export( + owner, docComment, annotations, modifiers, TypeParameter.export(typeParameters)); } } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/member/ClassNode.java b/pkl-core/src/main/java/org/pkl/core/ast/member/ClassNode.java index c05d1afe4..e68f7b83a 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/member/ClassNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/member/ClassNode.java @@ -21,15 +21,14 @@ import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.NodeInfo; import com.oracle.truffle.api.source.SourceSection; import java.util.ArrayList; -import java.util.List; import org.graalvm.collections.EconomicMap; import org.jspecify.annotations.Nullable; import org.pkl.core.PClassInfo; -import org.pkl.core.TypeParameter; import org.pkl.core.ast.ExpressionNode; import org.pkl.core.ast.type.TypeNode; import org.pkl.core.ast.type.UnresolvedTypeNode; import org.pkl.core.runtime.*; +import org.pkl.core.runtime.TypeParameter; import org.pkl.core.util.LateInit; @NodeInfo(shortName = "class") @@ -39,7 +38,7 @@ public final class ClassNode extends ExpressionNode { @Children private final ExpressionNode[] annotationNodes; private final int modifiers; private final PClassInfo classInfo; - private final List typeParameters; + private final TypeParameter[] typeParameters; private final @Nullable ModuleInfo moduleInfo; // null iff this class is pkl.base#Any @Child private @Nullable UnresolvedTypeNode unresolvedSupertypeNode; @@ -57,7 +56,7 @@ public final class ClassNode extends ExpressionNode { ExpressionNode[] annotationNodes, int modifiers, PClassInfo classInfo, - List typeParameters, + TypeParameter[] typeParameters, @Nullable ModuleInfo moduleInfo, @Nullable UnresolvedTypeNode unresolvedSupertypeNode, EconomicMap prototypeMembers, diff --git a/pkl-core/src/main/java/org/pkl/core/ast/member/FunctionNode.java b/pkl-core/src/main/java/org/pkl/core/ast/member/FunctionNode.java index cdb607bee..0faa80992 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/member/FunctionNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/member/FunctionNode.java @@ -26,10 +26,10 @@ import org.jspecify.annotations.Nullable; import org.pkl.core.Member.SourceLocation; import org.pkl.core.PClass; import org.pkl.core.PType; -import org.pkl.core.TypeParameter; import org.pkl.core.ast.ExpressionNode; import org.pkl.core.ast.VmModifier; import org.pkl.core.ast.type.TypeNode; +import org.pkl.core.ast.type.TypeParameterNode; import org.pkl.core.runtime.*; import org.pkl.core.runtime.VmType.UnknownType; import org.pkl.core.util.CollectionUtils; @@ -37,21 +37,25 @@ import org.pkl.core.util.Pair; public final class FunctionNode extends RegularMemberNode { // Every function (and property) call passes two implicit arguments at positions - // frame.getArguments()[0] and [1]: + // frame.getArguments()[0], [1]: // - the receiver (target) of the call, of type Object (see VmUtils.getReceiver()) // - the owner (lexically enclosing object) of the function/property definition, of type VmTyped // (see VmUtils.getOwner()) - // For VmObject receivers, the owner is the same as or an ancestor of the receiver. - // For other receivers, the owner is the prototype of the receiver's class. - // The chain of enclosing owners forms a function/property's lexical scope. + // + // After the implicit args, the method's type args are passed, in order. + // If the call does not contain type arguments, these args are null. + // + // Finally, the method's arguments are passed, in order. private static final int IMPLICIT_PARAM_COUNT = 2; private final int paramCount; + private final int typeParamCount; private final int totalParamCount; + @Children private TypeParameterNode[] typeParameterNodes; @Children private final TypeNode[] parameterTypeNodes; - @Child private @Nullable TypeNode checkedReturnTypeNode; - private @Nullable TypeNode returnTypeNode; + private @Nullable TypeNode checkedReturnTypeNode; + @Child private @Nullable TypeNode returnTypeNode; @TruffleBoundary public FunctionNode( @@ -62,6 +66,7 @@ public final class FunctionNode extends RegularMemberNode { TypeNode[] parameterTypeNodes, @Nullable TypeNode returnTypeNode, boolean isReturnTypeChecked, + TypeParameterNode[] typeParameterNodes, ExpressionNode bodyNode) { super(language, descriptor, member, bodyNode); @@ -74,14 +79,21 @@ public final class FunctionNode extends RegularMemberNode { this.parameterTypeNodes = parameterTypeNodes; this.checkedReturnTypeNode = isReturnTypeChecked ? returnTypeNode : null; this.returnTypeNode = returnTypeNode; + this.typeParameterNodes = typeParameterNodes; - totalParamCount = Math.addExact(IMPLICIT_PARAM_COUNT, paramCount); + typeParamCount = typeParameterNodes.length; + totalParamCount = + Math.addExact(IMPLICIT_PARAM_COUNT, Math.addExact(paramCount, typeParamCount)); } public int getParameterCount() { return paramCount; } + public int getTypeParameterCount() { + return typeParameterNodes.length; + } + public @Nullable TypeNode getParameterTypeNode(int idx) { if (idx >= paramCount) return null; return parameterTypeNodes[idx]; @@ -112,14 +124,20 @@ public final class FunctionNode extends RegularMemberNode { @Override @ExplodeLoop protected Object executeImpl(VirtualFrame frame) { - var totalArgCount = frame.getArguments().length; + var args = frame.getArguments(); + var totalArgCount = args.length; if (totalArgCount != totalParamCount) { CompilerDirectives.transferToInterpreter(); - throw wrongArgumentCount(totalArgCount - IMPLICIT_PARAM_COUNT); + throw wrongArgumentCount(totalArgCount - typeParamCount - IMPLICIT_PARAM_COUNT); + } + + for (var i = 0; i < typeParamCount; i++) { + var typeArg = args[IMPLICIT_PARAM_COUNT + i]; + typeParameterNodes[i].executeAndSet(frame, typeArg); } for (var i = 0; i < parameterTypeNodes.length; i++) { - var argument = frame.getArguments()[IMPLICIT_PARAM_COUNT + i]; + var argument = args[IMPLICIT_PARAM_COUNT + typeParamCount + i]; parameterTypeNodes[i].executeAndSet(frame, argument); } @@ -127,6 +145,19 @@ public final class FunctionNode extends RegularMemberNode { if (checkedReturnTypeNode != null) { return checkedReturnTypeNode.execute(frame, result); + } else if (returnTypeNode != null && typeParamCount > 0 && args[IMPLICIT_PARAM_COUNT] != null) { + // Return types are not typically checked for stdlib methods (isReturnTypeChecked == false) + // but for generic stdlib methods (e.g. `List.map()`) when they are called with type args, + // this behavior needs to be overridden so the type arg check on return type still runs. + // otherwise, this code would succeed instead of failing: + // ``` + // res = List("foo", "hello", "a").map::((it) -> it.length) + // ``` + // 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. + // Assumption-breaking pattern example: snippet `callWithTypeArguments.pkl` property + // `lexical.nestedInMethodParam`. + return returnTypeNode.execute(frame, result); } return result; @@ -153,7 +184,7 @@ public final class FunctionNode extends RegularMemberNode { SourceSection @Nullable [] docComment, List annotations, int modifiers, - List typeParameters) { + List typeParameters) { var parameters = CollectionUtils.newLinkedHashMap(paramCount); for (var i = 0; i < paramCount; i++) { diff --git a/pkl-core/src/main/java/org/pkl/core/ast/member/ListingOrMappingTypeCastNode.java b/pkl-core/src/main/java/org/pkl/core/ast/member/ListingOrMappingTypeCastNode.java index 21de62431..44b5c74bc 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/member/ListingOrMappingTypeCastNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/member/ListingOrMappingTypeCastNode.java @@ -16,6 +16,7 @@ package org.pkl.core.ast.member; import com.oracle.truffle.api.frame.FrameDescriptor; +import com.oracle.truffle.api.frame.MaterializedFrame; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.source.SourceSection; import org.pkl.core.ast.PklRootNode; @@ -51,6 +52,7 @@ public final class ListingOrMappingTypeCastNode extends PklRootNode { @Override protected Object executeImpl(VirtualFrame frame) { - return typeNode.execute(frame, frame.getArguments()[2]); + var enclosingFrame = (MaterializedFrame) frame.getArguments()[2]; + return typeNode.execute(enclosingFrame, frame.getArguments()[3]); } } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/member/Method.java b/pkl-core/src/main/java/org/pkl/core/ast/member/Method.java index 4ca4d0bf6..44d3da825 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/member/Method.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/member/Method.java @@ -16,20 +16,24 @@ package org.pkl.core.ast.member; import com.oracle.truffle.api.CallTarget; -import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.source.SourceSection; import org.jspecify.annotations.Nullable; -import org.pkl.core.ast.type.TypeNode; import org.pkl.core.runtime.VmObjectLike; public interface Method { - CallTarget getCallTarget(SourceSection callSite, VmObjectLike owner); + FunctionNode getFunctionNode(@Nullable SourceSection callSite); - @Nullable TypeNode getParameterTypeNode(VirtualFrame frame, int idx); - - @Nullable TypeNode getReturnTypeNode(VirtualFrame frame); + default FunctionNode getFunctionNode() { + return getFunctionNode(null); + } SourceSection getHeaderSection(); String getQualifiedName(); + + void ensureInitialized(VmObjectLike owner); + + int getTypeParameterCount(); + + CallTarget getCallTarget(SourceSection callSite, VmObjectLike owner); } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/member/ObjectMethodNode.java b/pkl-core/src/main/java/org/pkl/core/ast/member/ObjectMethodNode.java index 34d91f9ca..6d93cee28 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/member/ObjectMethodNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/member/ObjectMethodNode.java @@ -18,18 +18,20 @@ package org.pkl.core.ast.member; import com.oracle.truffle.api.CallTarget; 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.frame.FrameDescriptor; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.source.SourceSection; import org.jspecify.annotations.Nullable; import org.pkl.core.ast.ExpressionNode; -import org.pkl.core.ast.type.TypeNode; import org.pkl.core.ast.type.UnresolvedTypeNode; import org.pkl.core.runtime.*; +import org.pkl.core.runtime.TypeParameter; public final class ObjectMethodNode extends RegularMemberNode implements Method { private final VmLanguage language; private final int parameterCount; + private final TypeParameter[] typeParameters; @Children private final @Nullable UnresolvedTypeNode[] unresolvedParameterTypeNodes; @Child private @Nullable UnresolvedTypeNode unresolvedReturnTypeNode; @@ -41,26 +43,38 @@ public final class ObjectMethodNode extends RegularMemberNode implements Method ObjectMember member, ExpressionNode bodyNode, int parameterCount, + TypeParameter[] typeParameters, @Nullable UnresolvedTypeNode[] unresolvedParameterTypeNodes, @Nullable UnresolvedTypeNode unresolvedReturnTypeNode) { - super(language, descriptor, member, bodyNode); this.language = language; this.parameterCount = parameterCount; + this.typeParameters = typeParameters; this.unresolvedParameterTypeNodes = unresolvedParameterTypeNodes; this.unresolvedReturnTypeNode = unresolvedReturnTypeNode; } - public @Nullable TypeNode getReturnTypeNode() { - // this method is only called from child nodes - assert functionNode != null; - return functionNode.getReturnTypeNode(); + @Override + public FunctionNode getFunctionNode(@Nullable SourceSection callSite) { + assert isInitialized() : "getFunctionNode() called when not initialized"; + return functionNode; } @Override - public @Nullable TypeNode getReturnTypeNode(VirtualFrame frame) { - return getFunctionNode(frame, true).getReturnTypeNode(); + protected CallTarget executeImpl(VirtualFrame frame) { + return getFunctionNode(frame).getCallTarget(); + } + + @Override + public void ensureInitialized(VmObjectLike owner) { + if (!isInitialized()) { + getCallTarget().call(owner, owner); + } + } + + public boolean isInitialized() { + return functionNode != null; } @Override @@ -69,28 +83,20 @@ public final class ObjectMethodNode extends RegularMemberNode implements Method } @Override - public @Nullable TypeNode getParameterTypeNode(VirtualFrame frame, int idx) { - return getFunctionNode(frame, true).getParameterTypeNode(idx); + @TruffleBoundary + public int getTypeParameterCount() { + return typeParameters.length; } - @Override - protected CallTarget executeImpl(VirtualFrame frame) { - return getFunctionNode(frame, false).getCallTarget(); - } - - private FunctionNode getFunctionNode(VirtualFrame frame, boolean isPreInit) { + public FunctionNode getFunctionNode(VirtualFrame frame) { if (functionNode != null) return functionNode; CompilerDirectives.transferToInterpreterAndInvalidate(); - if (isPreInit) { - // TODO: with VmType/CreateDefaultValueNode this may be removable - adoptChildren(); - } var parameterTypeNodes = VmUtils.resolveParameterTypes(frame, getFrameDescriptor(), unresolvedParameterTypeNodes); - var returnTypeNode = unresolvedReturnTypeNode != null ? unresolvedReturnTypeNode.execute(frame) : null; + var typeParamterNodes = VmUtils.resolveTypeParameters(typeParameters); functionNode = new FunctionNode( @@ -101,6 +107,7 @@ public final class ObjectMethodNode extends RegularMemberNode implements Method parameterTypeNodes, returnTypeNode, true, + typeParamterNodes, bodyNode); return functionNode; } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/member/TypeAliasNode.java b/pkl-core/src/main/java/org/pkl/core/ast/member/TypeAliasNode.java index 394389db0..1e4a53823 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/member/TypeAliasNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/member/TypeAliasNode.java @@ -21,13 +21,12 @@ import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.source.SourceSection; import java.util.ArrayList; -import java.util.List; import org.jspecify.annotations.Nullable; -import org.pkl.core.TypeParameter; import org.pkl.core.ast.ExpressionNode; import org.pkl.core.ast.expression.primary.ExecuteCustomThisWithRootNode; import org.pkl.core.ast.expression.primary.GetTypeAliasModuleNode; import org.pkl.core.ast.type.UnresolvedTypeNode; +import org.pkl.core.runtime.TypeParameter; import org.pkl.core.runtime.VmTypeAlias; import org.pkl.core.runtime.VmTyped; import org.pkl.core.runtime.VmUtils; @@ -39,7 +38,7 @@ public final class TypeAliasNode extends ExpressionNode { private final int modifiers; private final String simpleName; private final String qualifiedName; - private final List typeParameters; + private final TypeParameter[] typeParameters; private @Child UnresolvedTypeNode typeAnnotationNode; // use same caching scheme as ClassNode @@ -53,7 +52,7 @@ public final class TypeAliasNode extends ExpressionNode { int modifiers, String simpleName, String qualifiedName, - List typeParameters, + TypeParameter[] typeParameters, UnresolvedTypeNode typeAnnotationNode) { super(sourceSection); this.headerSection = headerSection; diff --git a/pkl-core/src/main/java/org/pkl/core/ast/member/UnresolvedFunctionNode.java b/pkl-core/src/main/java/org/pkl/core/ast/member/UnresolvedFunctionNode.java index 99f24b528..77ef3d09d 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/member/UnresolvedFunctionNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/member/UnresolvedFunctionNode.java @@ -21,6 +21,7 @@ import com.oracle.truffle.api.frame.VirtualFrame; import org.jspecify.annotations.Nullable; import org.pkl.core.ast.ExpressionNode; import org.pkl.core.ast.PklNode; +import org.pkl.core.ast.type.TypeParameterNode; import org.pkl.core.ast.type.UnresolvedTypeNode; import org.pkl.core.runtime.*; @@ -69,6 +70,7 @@ public final class UnresolvedFunctionNode extends PklNode { parameterTypeNodes, returnTypeNode, true, + new TypeParameterNode[0], bodyNode); } } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/member/UnresolvedMethodNode.java b/pkl-core/src/main/java/org/pkl/core/ast/member/UnresolvedMethodNode.java index 7ffea73f9..bc4ea1d9c 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/member/UnresolvedMethodNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/member/UnresolvedMethodNode.java @@ -19,17 +19,16 @@ import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.frame.FrameDescriptor; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.source.SourceSection; -import java.util.List; import org.jspecify.annotations.Nullable; -import org.pkl.core.TypeParameter; import org.pkl.core.ast.ExpressionNode; import org.pkl.core.ast.VmModifier; import org.pkl.core.ast.type.UnresolvedTypeNode; import org.pkl.core.runtime.*; +import org.pkl.core.runtime.TypeParameter; public final class UnresolvedMethodNode extends UnresolvedClassMemberNode { private final int parameterCount; - private final List typeParameters; + private final TypeParameter[] typeParameters; @Children private final @Nullable UnresolvedTypeNode[] unresolvedParameterTypeNodes; @Child private @Nullable UnresolvedTypeNode unresolvedReturnTypeNode; private final boolean isReturnTypeChecked; @@ -46,7 +45,7 @@ public final class UnresolvedMethodNode extends UnresolvedClassMemberNode { Identifier name, String qualifiedName, int parameterCount, - List typeParameters, + TypeParameter[] typeParameters, @Nullable UnresolvedTypeNode[] unresolvedParameterTypeNodes, @Nullable UnresolvedTypeNode unresolvedReturnTypeNode, boolean isReturnTypeChecked, @@ -92,6 +91,7 @@ public final class UnresolvedMethodNode extends UnresolvedClassMemberNode { VmUtils.resolveParameterTypes(frame, descriptor, unresolvedParameterTypeNodes); var returnTypeNode = unresolvedReturnTypeNode != null ? unresolvedReturnTypeNode.execute(frame) : null; + var typeParameterNodes = VmUtils.resolveTypeParameters(typeParameters); String deprecation = null; for (var annotation : annotations) { @@ -124,6 +124,7 @@ public final class UnresolvedMethodNode extends UnresolvedClassMemberNode { parameterTypeNodes, returnTypeNode, isReturnTypeChecked, + typeParameterNodes, bodyNode); method.initFunctionNode(functionNode); diff --git a/pkl-core/src/main/java/org/pkl/core/ast/type/CallTypeArgumentNode.java b/pkl-core/src/main/java/org/pkl/core/ast/type/CallTypeArgumentNode.java new file mode 100644 index 000000000..997cb36ee --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/ast/type/CallTypeArgumentNode.java @@ -0,0 +1,69 @@ +/* + * 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.core.ast.type; + +import com.oracle.truffle.api.CallTarget; +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.Specialization; +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.source.SourceSection; +import org.pkl.core.ast.PklNode; +import org.pkl.core.runtime.VmTypeArgument; + +public abstract class CallTypeArgumentNode extends PklNode { + + public CallTypeArgumentNode(SourceSection sourceSection) { + super(sourceSection); + } + + public abstract Object execute( + VirtualFrame frame, VmTypeArgument typeArgument, Object value, boolean lazy); + + @Specialization(guards = {"typeArgument.getCallTarget() == cachedCallTarget"}) + protected Object evalDirect( + @SuppressWarnings("unused") VmTypeArgument typeArgument, + Object value, + boolean lazy, + @Cached("typeArgument.getCallTarget()") @SuppressWarnings("unused") + CallTarget cachedCallTarget, + @Cached("create(cachedCallTarget)") DirectCallNode callNode) { + var enclosingFrame = typeArgument.getEnclosingFrame(); + if (enclosingFrame == null) { + return callNode.call(null, null, null, value, lazy); + } + + var arguments = enclosingFrame.getArguments(); + return callNode.call(arguments[0], arguments[1], enclosingFrame, value, lazy); + } + + @Specialization(replaces = "evalDirect") + protected Object eval( + VmTypeArgument typeArgument, + Object value, + boolean lazy, + @Cached("create()") IndirectCallNode callNode) { + var enclosingFrame = typeArgument.getEnclosingFrame(); + if (enclosingFrame == null) { + return callNode.call(typeArgument.getCallTarget(), null, null, null, value, lazy); + } + + var arguments = enclosingFrame.getArguments(); + return callNode.call( + typeArgument.getCallTarget(), arguments[0], arguments[1], enclosingFrame, value, lazy); + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/ast/type/TypeArgumentTypeNode.java b/pkl-core/src/main/java/org/pkl/core/ast/type/TypeArgumentTypeNode.java new file mode 100644 index 000000000..cf8afb685 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/ast/type/TypeArgumentTypeNode.java @@ -0,0 +1,69 @@ +/* + * 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.core.ast.type; + +import com.oracle.truffle.api.frame.FrameDescriptor; +import com.oracle.truffle.api.frame.MaterializedFrame; +import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.source.SourceSection; +import org.pkl.core.ast.PklRootNode; +import org.pkl.core.runtime.VmLanguage; + +public final class TypeArgumentTypeNode extends PklRootNode { + + private final SourceSection sourceSection; + private final String qualifiedName; + @Child private TypeNode typeNode; + + public TypeArgumentTypeNode( + VmLanguage language, + FrameDescriptor frameDescriptor, + SourceSection sourceSection, + String qualifiedName, + TypeNode typeNode) { + super(language, frameDescriptor, true); + this.sourceSection = sourceSection; + this.qualifiedName = qualifiedName; + this.typeNode = typeNode; + } + + @Override + public SourceSection getSourceSection() { + return sourceSection; + } + + @Override + public String getName() { + return qualifiedName; + } + + public TypeNode getTypeNode() { + return typeNode; + } + + @Override + protected Object executeImpl(VirtualFrame frame) { + var enclosingFrame = (MaterializedFrame) frame.getArguments()[2]; + var value = frame.getArguments()[3]; + var lazy = (boolean) frame.getArguments()[4]; + if (enclosingFrame == null) { + return lazy ? typeNode.executeLazily(frame, value) : typeNode.executeEagerly(frame, value); + } + return lazy + ? typeNode.executeLazily(enclosingFrame, value) + : typeNode.executeEagerly(enclosingFrame, value); + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/ast/type/TypeNode.java b/pkl-core/src/main/java/org/pkl/core/ast/type/TypeNode.java index 06c951cfc..49d4b5f80 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/type/TypeNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/type/TypeNode.java @@ -30,10 +30,8 @@ import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.ExplodeLoop; import com.oracle.truffle.api.nodes.LoopNode; import com.oracle.truffle.api.nodes.Node; -import com.oracle.truffle.api.nodes.NodeUtil; import com.oracle.truffle.api.profiles.LoopConditionProfile; import com.oracle.truffle.api.source.SourceSection; -import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; @@ -43,8 +41,6 @@ import java.util.function.Function; import org.jspecify.annotations.Nullable; import org.pkl.core.PType; import org.pkl.core.PklBugException; -import org.pkl.core.StackFrame; -import org.pkl.core.TypeParameter; import org.pkl.core.ast.*; import org.pkl.core.ast.expression.primary.GetModuleNode; import org.pkl.core.ast.expression.primary.GetReceiverClassNode; @@ -57,12 +53,12 @@ import org.pkl.core.ast.member.ListingOrMappingTypeCastNode; import org.pkl.core.ast.member.ObjectMember; import org.pkl.core.ast.member.UntypedObjectMemberNode; import org.pkl.core.runtime.*; +import org.pkl.core.runtime.TypeParameter.OwnerType; import org.pkl.core.stdlib.VmObjectFactory; import org.pkl.core.util.EconomicMaps; import org.pkl.core.util.EconomicSets; import org.pkl.core.util.LateInit; import org.pkl.core.util.MutableBoolean; -import org.pkl.core.util.MutableReference; public abstract class TypeNode extends PklNode { @CompilationFinal private @Nullable VmType type; @@ -167,10 +163,23 @@ public abstract class TypeNode extends PklNode { // header section of the property or method that carries the type annotation SourceSection headerSection, // qualified name of the property or method that carries the type annotation - String qualifiedName) { + String qualifiedName, + boolean isPreCall, + VmTypeArgument @Nullable [] typeArgumentOverrides) { return null; } + // method arguments are used when default value contains a root node + public final @Nullable Object createDefaultValue( + VirtualFrame frame, + VmLanguage language, + // header section of the property or method that carries the type annotation + SourceSection headerSection, + // qualified name of the property or method that carries the type annotation + String qualifiedName) { + return createDefaultValue(frame, language, headerSection, qualifiedName, false, null); + } + @Idempotent public final boolean isFinalType() { var ret = new MutableBoolean(true); @@ -178,7 +187,9 @@ public abstract class TypeNode extends PklNode { true, typeNode -> { // assumption: don't need to worry about `NonFinalClassTypeNode` - if (typeNode instanceof NonFinalSelfTypeNode) { + if (typeNode instanceof NonFinalSelfTypeNode + || (typeNode instanceof TypeVariableNode typeVariable + && typeVariable.ownerType == TypeParameter.OwnerType.METHOD)) { ret.set(false); return false; } @@ -201,6 +212,24 @@ public abstract class TypeNode extends PklNode { return ret.get(); } + public final boolean getTypeArgumentRequiresFrame() { + var ret = new MutableBoolean(false); + acceptTypeNode( + true, + typeNode -> { + if (typeNode instanceof ConstrainedTypeNode + || typeNode instanceof NonFinalSelfTypeNode + || typeNode instanceof ListingOrMappingTypeNode // lazy checks need captured frame + || (typeNode instanceof TypeVariableNode typeVar + && typeVar.ownerType == TypeParameter.OwnerType.METHOD)) { + ret.set(true); + return false; + } + return true; + }); + return ret.get(); + } + /** Visit child type nodes of this type. */ protected abstract boolean acceptTypeNode(boolean visitTypeArguments, TypeNodeConsumer consumer); @@ -451,6 +480,7 @@ public abstract class TypeNode extends PklNode { return mirrorFactory.create(null); } + @Override protected boolean acceptTypeNode(boolean visitTypeArguments, TypeNodeConsumer consumer) { return consumer.accept(this); } @@ -460,7 +490,9 @@ public abstract class TypeNode extends PklNode { VirtualFrame frame, VmLanguage language, SourceSection headerSection, - String qualifiedName) { + String qualifiedName, + boolean isPreCall, + VmTypeArgument @Nullable [] typeArgumentOverrides) { return TypeNode.createDefaultValue(clazz); } } @@ -530,7 +562,9 @@ public abstract class TypeNode extends PklNode { VirtualFrame frame, VmLanguage language, SourceSection headerSection, - String qualifiedName) { + String qualifiedName, + boolean isPreCall, + VmTypeArgument @Nullable [] typeArgumentOverrides) { var clazz = ((VmObjectLike) getTargetNode.executeGeneric(frame)).getVmClass(); return TypeNode.createDefaultValue(clazz); } @@ -571,7 +605,9 @@ public abstract class TypeNode extends PklNode { VirtualFrame frame, VmLanguage language, SourceSection headerSection, - String qualifiedName) { + String qualifiedName, + boolean isPreCall, + VmTypeArgument @Nullable [] typeArgumentOverrides) { return literal; } @@ -625,7 +661,9 @@ public abstract class TypeNode extends PklNode { VirtualFrame frame, VmLanguage language, SourceSection headerSection, - String qualifiedName) { + String qualifiedName, + boolean isPreCall, + VmTypeArgument @Nullable [] typeArgumentOverrides) { return VmDynamic.empty(); } @@ -672,7 +710,9 @@ public abstract class TypeNode extends PklNode { VirtualFrame frame, VmLanguage language, SourceSection headerSection, - String qualifiedName) { + String qualifiedName, + boolean isPreCall, + VmTypeArgument @Nullable [] typeArgumentOverrides) { return TypeNode.createDefaultValue(clazz); } @@ -736,7 +776,9 @@ public abstract class TypeNode extends PklNode { VirtualFrame frame, VmLanguage language, SourceSection headerSection, - String qualifiedName) { + String qualifiedName, + boolean isPreCall, + VmTypeArgument @Nullable [] typeArgumentOverrides) { return TypeNode.createDefaultValue(clazz); } @@ -777,9 +819,12 @@ public abstract class TypeNode extends PklNode { VirtualFrame frame, VmLanguage language, SourceSection headerSection, - String qualifiedName) { + String qualifiedName, + boolean isPreCall, + VmTypeArgument @Nullable [] typeArgumentOverrides) { return VmNull.withDefault( - elementTypeNode.createDefaultValue(frame, language, headerSection, qualifiedName)); + elementTypeNode.createDefaultValue( + frame, language, headerSection, qualifiedName, isPreCall, typeArgumentOverrides)); } @Override @@ -849,12 +894,14 @@ public abstract class TypeNode extends PklNode { VirtualFrame frame, VmLanguage language, SourceSection headerSection, - String qualifiedName) { + String qualifiedName, + boolean isPreCall, + VmTypeArgument @Nullable [] typeArgumentOverrides) { return defaultIndex == -1 ? null : elementTypeNodes[defaultIndex].createDefaultValue( - frame, language, headerSection, qualifiedName); + frame, language, headerSection, qualifiedName, isPreCall, typeArgumentOverrides); } /** @@ -1190,7 +1237,9 @@ public abstract class TypeNode extends PklNode { VirtualFrame frame, VmLanguage language, SourceSection headerSection, - String qualifiedName) { + String qualifiedName, + boolean isPreCall, + VmTypeArgument @Nullable [] typeArgumentOverrides) { if (defaultIndex == -1) { return null; } @@ -1247,7 +1296,9 @@ public abstract class TypeNode extends PklNode { VirtualFrame frame, VmLanguage language, SourceSection headerSection, - String qualifiedName) { + String qualifiedName, + boolean isPreCall, + VmTypeArgument @Nullable [] typeArgumentOverrides) { return VmList.EMPTY; } @@ -1330,7 +1381,9 @@ public abstract class TypeNode extends PklNode { VirtualFrame frame, VmLanguage language, SourceSection headerSection, - String qualifiedName) { + String qualifiedName, + boolean isPreCall, + VmTypeArgument @Nullable [] typeArgumentOverrides) { return VmList.EMPTY; } @@ -1401,7 +1454,9 @@ public abstract class TypeNode extends PklNode { VirtualFrame frame, VmLanguage language, SourceSection headerSection, - String qualifiedName) { + String qualifiedName, + boolean isPreCall, + VmTypeArgument @Nullable [] typeArgumentOverrides) { return VmSet.EMPTY; } @@ -1481,7 +1536,9 @@ public abstract class TypeNode extends PklNode { VirtualFrame frame, VmLanguage language, SourceSection headerSection, - String qualifiedName) { + String qualifiedName, + boolean isPreCall, + VmTypeArgument @Nullable [] typeArgumentOverrides) { return VmMap.EMPTY; } @@ -1554,7 +1611,7 @@ public abstract class TypeNode extends PklNode { return vmListing; } return new VmListing( - vmListing.getEnclosingFrame(), + frame.materialize(), vmListing, EconomicMaps.emptyMap(), vmListing.getLength(), @@ -1613,7 +1670,7 @@ public abstract class TypeNode extends PklNode { return vmMapping; } return new VmMapping( - vmMapping.getEnclosingFrame(), + frame.materialize(), vmMapping, EconomicMaps.emptyMap(), getValueTypeCastNode(frame.getFrameDescriptor()), @@ -1775,14 +1832,17 @@ public abstract class TypeNode extends PklNode { VirtualFrame frame, VmLanguage language, SourceSection headerSection, - String qualifiedName) { + String qualifiedName, + boolean isPreCall, + VmTypeArgument @Nullable [] typeArgumentOverrides) { if (valueTypeNode instanceof UnknownTypeNode) { return newEmptyListingOrMapping(); } var defaultMemberValue = - valueTypeNode.createDefaultValue(frame, language, headerSection, qualifiedName); + valueTypeNode.createDefaultValue( + frame, language, headerSection, qualifiedName, isPreCall, typeArgumentOverrides); var defaultMember = createDefaultMember(headerSection, qualifiedName, defaultMemberValue); @@ -1984,7 +2044,7 @@ public abstract class TypeNode extends PklNode { } } - public abstract static class ReferenceTypeNode extends ValidatingObjectSlotTypeNode { + public abstract static class ReferenceTypeNode extends ObjectSlotTypeNode { @Child private TypeNode domainTypeNode; @Child private TypeNode referentTypeNode; @Child private ExpressionNode getReceiverClassNode; @@ -1997,7 +2057,6 @@ public abstract class TypeNode extends PklNode { this.referentTypeNode = referentTypeNode; this.getReceiverClassNode = new GetReceiverClassNode(sourceSection); this.getModuleNode = new GetModuleNode(sourceSection); - validate(); } @Override @@ -2006,55 +2065,29 @@ public abstract class TypeNode extends PklNode { RefModule.getReferenceClass(), domainTypeNode.getType(), referentTypeNode.getType()); } - @Override - public final String getValidationErrorKey() { - return "invalidReferenceTypeAnnotationWithConstraint"; - } - - @Override - protected final @Nullable Node getViolatingNode() { - // constraints may not be used in Reference type annotation referents - // walk the type and throw if any part of the referent is constrained - var violation = new MutableReference(null); - referentTypeNode.acceptTypeNode( - true, - (typeNode) -> { - if (typeNode instanceof ConstrainedTypeNode) { - violation.set(typeNode); - return false; - } - return true; - }); - return violation.getOrNull(); - } - - @Override - protected final boolean isIncludedInTrace(Node node) { - return node instanceof ReferenceTypeNode || node instanceof ConstrainedTypeNode; - } - @Specialization protected Object eval(VirtualFrame frame, VmReference value) { if (domainTypeNode.isNoopTypeCheck() && referentTypeNode.isNoopTypeCheck()) { return value; } + var realType = (VmType.ClassType) getType().reify(frame); try { domainTypeNode.execute(frame, value.getDomain()); } catch (VmTypeMismatchException e) { CompilerDirectives.transferToInterpreter(); - throw typeMismatch(value, getType()); + throw typeMismatch(value, realType); } var module = (VmTyped) getModuleNode.executeGeneric(frame); if (value.referentTypeIsSubtypeOf( - referentTypeNode.getType(), + realType.getTypeArguments()[1], (VmClass) getReceiverClassNode.executeGeneric(frame), module.getVmClass())) { return value; } - throw typeMismatch(value, getType()); + throw typeMismatch(value, realType); } @Fallback @@ -2151,7 +2184,9 @@ public abstract class TypeNode extends PklNode { VirtualFrame frame, VmLanguage language, SourceSection headerSection, - String qualifiedName) { + String qualifiedName, + boolean isPreCall, + VmTypeArgument @Nullable [] typeArgumentOverrides) { CompilerDirectives.transferToInterpreter(); throw exceptionBuilder() .evalError("internalStdLibClass", "VarArgs") @@ -2173,24 +2208,34 @@ public abstract class TypeNode extends PklNode { public static final class TypeVariableNode extends WriteFrameSlotTypeNode { private final TypeParameter typeParameter; + private final int levelsUp; + private final TypeParameter.OwnerType ownerType; + @Child private CallTypeArgumentNode callTypeArgumentNode; - public TypeVariableNode(SourceSection sourceSection, TypeParameter typeParameter) { + public TypeVariableNode( + SourceSection sourceSection, + TypeParameter typeParameter, + TypeParameter.OwnerType ownerType, + int levelsUp) { super(sourceSection); this.typeParameter = typeParameter; + this.ownerType = ownerType; + this.levelsUp = levelsUp; + callTypeArgumentNode = CallTypeArgumentNodeGen.create(sourceSection); } @Override protected VmType doGetType() { - return new VmType.TypeVariableType(typeParameter); + return new VmType.TypeVariableType(typeParameter, ownerType, levelsUp); } - public int getTypeParameterIndex() { - return typeParameter.getIndex(); + public TypeParameter getTypeParameter() { + return typeParameter; } @Override public boolean isNoopTypeCheck() { - return true; + return levelsUp < 0 || ownerType != TypeParameter.OwnerType.METHOD; } @Override @@ -2202,16 +2247,77 @@ public abstract class TypeNode extends PklNode { return MirrorFactories.typeParameterFactory.create(typeParameter); } + @Override + public Object executeEagerly(VirtualFrame frame, Object value) { + return doExecute(frame, value, false); + } + @Override protected Object executeLazily(VirtualFrame frame, Object value) { - // do nothing - return value; + return doExecute(frame, value, true); + } + + private Object doExecute(VirtualFrame frame, Object value, boolean lazy) { + if (ownerType != OwnerType.METHOD) return value; + + var typeArg = VmUtils.getTypeArgumentOrNull(frame, typeParameter.slot(), levelsUp); + if (typeArg == null) return value; + + try { + return callTypeArgumentNode.execute(frame, typeArg, value, lazy); + } catch (VmTypeMismatchException e) { + CompilerDirectives.transferToInterpreter(); + if (!VmUtils.sourceSectionContains(sourceSection, e.sourceSection)) { + var rootNode = getRootNode(); + e.putInsertedStackFrame( + rootNode.getCallTarget(), + VmUtils.createStackFrame(sourceSection, rootNode.getName())); + } + throw e; + } } @Override protected boolean acceptTypeNode(boolean visitTypeArguments, TypeNodeConsumer consumer) { return consumer.accept(this); } + + private @Nullable VmTypeArgument getTypeArgumentForDefaultValue( + VirtualFrame frame, boolean isPreCall, VmTypeArgument @Nullable [] typeArgumentOverrides) { + // when creating default value for a method argument, the type node is 1 level _lower_ than + // what was resolved lexically. + // + // edge case: if `levelsUp == 0`, we need to resolve the type argument from + // `typeArgumentOverrides` rather than the frame. + // + // e.g. + // ``` + // function foo(arg: T) + // res = foo::(new {}) // `Person` is passed in via `typeArgumentOverrides` + // ``` + if (levelsUp == 0 && isPreCall) { + return typeArgumentOverrides == null ? null : typeArgumentOverrides[typeParameter.index()]; + } + return VmUtils.getTypeArgumentOrNull(frame, typeParameter.slot(), levelsUp); + } + + @Override + public @Nullable Object createDefaultValue( + VirtualFrame frame, + VmLanguage language, + SourceSection headerSection, + String qualifiedName, + boolean isPreCall, + VmTypeArgument @Nullable [] typeArgumentOverrides) { + if (ownerType != OwnerType.METHOD) return null; + + var typeArg = getTypeArgumentForDefaultValue(frame, isPreCall, typeArgumentOverrides); + if (typeArg != null) { + return typeArg.createDefaultValue(language, headerSection, qualifiedName); + } + + return null; + } } public static final class NonNullTypeAliasTypeNode extends WriteFrameSlotTypeNode { @@ -2418,13 +2524,6 @@ public abstract class TypeNode extends PklNode { this.typeAlias = typeAlias; this.typeArgumentNodes = typeArgumentNodes; aliasedTypeNode = typeAlias.instantiate(typeArgumentNodes); - aliasedTypeNode.accept( - node -> { - if (node instanceof ValidatingObjectSlotTypeNode typeNode) { - typeNode.validate(this); - } - return true; - }); } @Override @@ -2521,12 +2620,15 @@ public abstract class TypeNode extends PklNode { VirtualFrame frame, VmLanguage language, SourceSection headerSection, - String qualifiedName) { + String qualifiedName, + boolean isPreCall, + VmTypeArgument @Nullable [] typeArgumentOverrides) { if (typeAlias == BaseModule.getMixinTypeAlias()) { return newMixin(language, qualifiedName); } - return aliasedTypeNode.createDefaultValue(frame, language, headerSection, qualifiedName); + return aliasedTypeNode.createDefaultValue( + frame, language, headerSection, qualifiedName, isPreCall, typeArgumentOverrides); } @Override @@ -2613,9 +2715,11 @@ public abstract class TypeNode extends PklNode { VirtualFrame frame, VmLanguage language, SourceSection headerSection, - String qualifiedName) { - - return childNode.createDefaultValue(frame, language, headerSection, qualifiedName); + String qualifiedName, + boolean isPreCall, + VmTypeArgument @Nullable [] typeArgumentOverrides) { + return childNode.createDefaultValue( + frame, language, headerSection, qualifiedName, isPreCall, typeArgumentOverrides); } public SourceSection getBaseTypeSection() { @@ -2914,78 +3018,6 @@ public abstract class TypeNode extends PklNode { } } - public abstract static class ValidatingObjectSlotTypeNode extends ObjectSlotTypeNode { - - protected ValidatingObjectSlotTypeNode(SourceSection sourceSection) { - super(sourceSection); - } - - protected abstract String getValidationErrorKey(); - - protected abstract @Nullable Node getViolatingNode(); - - protected final void validate() { - var violation = getViolatingNode(); - if (violation == null) return; - throw exceptionBuilder() - .evalError(getValidationErrorKey()) - .withLeadingStackFrames(buildLeadingFrames(violation, getSourceSection(), null)) - .build(); - } - - public final void validate(TypeAliasTypeNode outermostAliasNode) { - var violation = getViolatingNode(); - if (violation == null) return; - - throw exceptionBuilder() - .withLocation(outermostAliasNode) - .evalError(getValidationErrorKey()) - .withLeadingStackFrames( - buildLeadingFrames( - violation, - outermostAliasNode.getSourceSection(), - outermostAliasNode.getTypeAlias())) - .build(); - } - - protected abstract boolean isIncludedInTrace(Node node); - - private List buildLeadingFrames( - Node violatingNode, SourceSection usageSection, @Nullable VmTypeAlias outermostAlias) { - var frames = new ArrayList(); - for (var node = violatingNode; node != null; node = node.getParent()) { - if (!(node instanceof TypeAliasTypeNode || isIncludedInTrace(node))) continue; - var section = node.getSourceSection(); - if (section == null || !section.isAvailable() || isWithin(usageSection, section)) { - continue; - } - var owner = ownerAlias(node, outermostAlias); - if (owner != null) { - frames.add(VmUtils.createStackFrame(section, owner.getQualifiedName())); - } - } - return frames; - } - - /** - * The type alias whose body contains {@code node}: the nearest enclosing alias, else the - * outermost alias being instantiated (which is {@code null} when no alias is used). - */ - @SuppressWarnings("DataFlowIssue") - private static @Nullable VmTypeAlias ownerAlias( - Node node, @Nullable VmTypeAlias outermostAlias) { - var parent = NodeUtil.findParent(node, TypeAliasTypeNode.class); - //noinspection ConstantValue - return parent != null ? parent.getTypeAlias() : outermostAlias; - } - - private static boolean isWithin(SourceSection outer, SourceSection inner) { - return inner.getSource().equals(outer.getSource()) - && inner.getCharIndex() >= outer.getCharIndex() - && inner.getCharEndIndex() <= outer.getCharEndIndex(); - } - } - public static VmType[] toTypes(TypeNode[] typeNodes) { return toTypes(typeNodes, typeNodes.length); } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/type/TypeParameterNode.java b/pkl-core/src/main/java/org/pkl/core/ast/type/TypeParameterNode.java new file mode 100644 index 000000000..c8e396909 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/ast/type/TypeParameterNode.java @@ -0,0 +1,34 @@ +/* + * 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.core.ast.type; + +import com.oracle.truffle.api.frame.VirtualFrame; +import org.pkl.core.ast.PklNode; +import org.pkl.core.runtime.TypeParameter; + +public final class TypeParameterNode extends PklNode { + + private final TypeParameter typeParameter; + + public TypeParameterNode(TypeParameter typeParameter) { + super(typeParameter.sourceSection()); + this.typeParameter = typeParameter; + } + + public void executeAndSet(VirtualFrame frame, Object arg) { + frame.setObject(typeParameter.slot(), arg); + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/ast/type/UnresolvedTypeNode.java b/pkl-core/src/main/java/org/pkl/core/ast/type/UnresolvedTypeNode.java index ea6940b5c..24a567955 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/type/UnresolvedTypeNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/type/UnresolvedTypeNode.java @@ -22,7 +22,6 @@ import com.oracle.truffle.api.source.SourceSection; import java.util.ArrayList; import java.util.List; import org.pkl.core.PklBugException; -import org.pkl.core.TypeParameter; import org.pkl.core.ast.ExpressionNode; import org.pkl.core.ast.PklNode; import org.pkl.core.ast.expression.primary.GetModuleOwnerNode; @@ -499,21 +498,29 @@ public abstract class UnresolvedTypeNode extends PklNode { public static final class TypeVariable extends UnresolvedTypeNode { private final TypeParameter typeParameter; + private final int levelsUp; + private final TypeParameter.OwnerType ownerType; - public TypeVariable(SourceSection sourceSection, TypeParameter typeParameter) { + public TypeVariable( + SourceSection sourceSection, + TypeParameter typeParameter, + TypeParameter.OwnerType ownerType, + int levelsUp) { super(sourceSection); this.typeParameter = typeParameter; + this.ownerType = ownerType; + this.levelsUp = levelsUp; } - public int getTypeParameterIndex() { - return typeParameter.getIndex(); + public TypeParameter getTypeParameter() { + return typeParameter; } @Override public TypeNode execute(VirtualFrame frame) { CompilerDirectives.transferToInterpreter(); - return new TypeVariableNode(sourceSection, typeParameter); + return new TypeVariableNode(sourceSection, typeParameter, ownerType, levelsUp); } } diff --git a/pkl-core/src/main/java/org/pkl/core/repl/ReplServer.java b/pkl-core/src/main/java/org/pkl/core/repl/ReplServer.java index 3d9503919..754fcdb1e 100644 --- a/pkl-core/src/main/java/org/pkl/core/repl/ReplServer.java +++ b/pkl-core/src/main/java/org/pkl/core/repl/ReplServer.java @@ -46,6 +46,7 @@ import org.pkl.core.repl.ReplResponse.EvalSuccess; import org.pkl.core.repl.ReplResponse.InvalidRequest; import org.pkl.core.resource.ResourceReader; import org.pkl.core.runtime.*; +import org.pkl.core.runtime.TypeParameter; import org.pkl.core.util.AnsiStringBuilder; import org.pkl.core.util.EconomicMaps; import org.pkl.core.util.IoUtils; @@ -478,7 +479,7 @@ public class ReplServer implements AutoCloseable { List.of(), VmModifier.NONE, classInfo, - List.of(), + new TypeParameter[0], module); if (parent != null) { var superclass = parent.getVmClass(); diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/MirrorFactories.java b/pkl-core/src/main/java/org/pkl/core/runtime/MirrorFactories.java index 8b322b26e..9f1e437e7 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/MirrorFactories.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/MirrorFactories.java @@ -16,7 +16,6 @@ package org.pkl.core.runtime; import com.oracle.truffle.api.source.SourceSection; -import org.pkl.core.TypeParameter; import org.pkl.core.ast.member.ClassMethod; import org.pkl.core.ast.member.ClassProperty; import org.pkl.core.ast.type.TypeNode; @@ -209,11 +208,11 @@ public final class MirrorFactories { .addTypedProperty("type", Pair::getSecond); typeParameterFactory - .addStringProperty("name", TypeParameter::getName) + .addStringProperty("name", TypeParameter::name) .addProperty( "variance", typeParameter -> - switch (typeParameter.getVariance()) { + switch (typeParameter.variance()) { case COVARIANT -> "out"; case CONTRAVARIANT -> "in"; default -> VmNull.withoutDefault(); diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/TypeParameter.java b/pkl-core/src/main/java/org/pkl/core/runtime/TypeParameter.java new file mode 100644 index 000000000..7d5588bde --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/runtime/TypeParameter.java @@ -0,0 +1,38 @@ +/* + * 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.core.runtime; + +import com.oracle.truffle.api.source.SourceSection; +import java.util.Arrays; +import java.util.List; +import org.pkl.core.TypeParameter.Variance; + +public record TypeParameter( + SourceSection sourceSection, int slot, int index, String name, Variance variance) { + public org.pkl.core.TypeParameter export() { + return new org.pkl.core.TypeParameter(variance, name, index); + } + + public static List export(TypeParameter[] typeParameters) { + return Arrays.stream(typeParameters).map(TypeParameter::export).toList(); + } + + public enum OwnerType { + CLASS, + METHOD, + TYPEALIAS + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmClass.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmClass.java index 6620a8eec..13094a4f7 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmClass.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmClass.java @@ -29,7 +29,6 @@ import org.pkl.core.Member.SourceLocation; import org.pkl.core.PClass; import org.pkl.core.PClassInfo; import org.pkl.core.PObject; -import org.pkl.core.TypeParameter; import org.pkl.core.ast.*; import org.pkl.core.ast.member.*; import org.pkl.core.ast.type.TypeNode; @@ -51,7 +50,7 @@ public final class VmClass extends VmValue { private final List annotations; private final int modifiers; private final PClassInfo classInfo; - private final List typeParameters; + private final TypeParameter[] typeParameters; private final VmTyped prototype; private final EconomicMap declaredProperties = EconomicMaps.create(); @@ -127,7 +126,7 @@ public final class VmClass extends VmValue { List annotations, int modifiers, PClassInfo classInfo, - List typeParameters, + TypeParameter[] typeParameters, VmTyped prototype) { this.sourceSection = sourceSection; @@ -190,6 +189,41 @@ public final class VmClass extends VmValue { return result; } + @TruffleBoundary + private void checkMethodOverrides() { + var methodCursor = declaredMethods.getEntries(); + while (methodCursor.advance()) { + var method = methodCursor.getValue(); + if (method.isLocal()) continue; + var name = methodCursor.getKey(); + ClassMethod parent = null; + for (var clazz = superclass; clazz != null; clazz = clazz.superclass) { + parent = clazz.getDeclaredMethod(name); + if (parent != null) break; + } + if (parent == null) continue; + + checkMethodOverride(method, parent); + } + } + + private void checkMethodOverride(ClassMethod method, ClassMethod parent) { + var typeParamCount = method.getTypeParameterCount(); + var parentTypeParamCount = parent.getTypeParameterCount(); + + if (typeParamCount != parentTypeParamCount) { + throw new VmExceptionBuilder() + .evalError( + "methodTypeParameterCountMismatch", + method.getQualifiedName(), + parent.getQualifiedName(), + parentTypeParamCount, + typeParamCount) + .withSourceSection(method.getHeaderSection()) + .build(); + } + } + @TruffleBoundary public void addProperty(ClassProperty property) { prototype.addProperty(property.getInitializer()); @@ -242,14 +276,15 @@ public final class VmClass extends VmValue { /** Called when the entire class hierarchy is completely initialized, including superclasses. */ public void onFullyInitialized() { checkAbstractMethods(); + checkMethodOverrides(); } @TruffleBoundary public int getTypeParameterCount() { - return typeParameters.size(); + return typeParameters.length; } - public List getTypeParameters() { + public TypeParameter[] getTypeParameters() { return typeParameters; } @@ -723,12 +758,12 @@ public final class VmClass extends VmValue { VmModifier.export(modifiers, true), exportedAnnotations, classInfo, - typeParameters, + TypeParameter.export(typeParameters), properties, methods, moduleClass); - for (var parameter : typeParameters) { + for (var parameter : __pClass.getTypeParameters()) { parameter.initOwner(__pClass); } diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmListingOrMapping.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmListingOrMapping.java index b6652bce3..e42e75f30 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmListingOrMapping.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmListingOrMapping.java @@ -71,7 +71,13 @@ public abstract class VmListingOrMapping extends VmObject { if (obj.typeCastNode != null && obj.typeCastNode != prevTypeCastNode) { var callTarget = obj.typeCastNode.getCallTarget(); try { - result = callNode.call(callTarget, obj.typeCheckReceiver, obj.typeCheckOwner, result); + result = + callNode.call( + callTarget, + VmUtils.getReceiver(enclosingFrame), + VmUtils.getOwner(enclosingFrame), + obj.enclosingFrame, + result); prevTypeCastNode = obj.typeCastNode; } catch (VmException e) { CompilerDirectives.transferToInterpreter(); @@ -109,7 +115,8 @@ public abstract class VmListingOrMapping extends VmObject { var callNode = IndirectCallNode.getUncached(); var callTarget = typeCastNode.getCallTarget(); try { - result = callNode.call(callTarget, typeCheckReceiver, typeCheckOwner, result); + result = + callNode.call(callTarget, typeCheckReceiver, typeCheckOwner, enclosingFrame, result); } catch (VmException e) { var member = VmUtils.findMember(parent, key); assert member != null; // already found the member's cached value @@ -133,6 +140,9 @@ public abstract class VmListingOrMapping extends VmObject { if (typeCastNode == null) { return false; } - return typeCastNode.getTypeNode().getType().equals(typeNode.getType()); + var castTypeNode = typeCastNode.getTypeNode(); + // if cast type is not final, we can't know if typeNode is a subtype + if (!castTypeNode.isFinalType()) return false; + return castTypeNode.getType().equals(typeNode.getType()); } } diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmReference.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmReference.java index 6e0a25798..bf3d1d3b6 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmReference.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmReference.java @@ -56,12 +56,8 @@ public final class VmReference extends VmValue { .toTyped(RefModule.getAccessClass()); } - public VmReference(VmTyped domain, VmClass clazz, Object data) { - this( - domain, - data, - RrbTree.empty(), - normalizeTypes(new VmType.ClassType(clazz), clazz, clazz.getModuleClass())); + public VmReference(VmTyped domain, VmType referentType, Object data) { + this(domain, data, RrbTree.empty(), normalizeTypes(referentType, null, null)); } public VmReference(VmTyped domain, Object data, ImRrbt path, VmType referentType) { @@ -95,7 +91,8 @@ public final class VmReference extends VmValue { // * replace VmType.ModuleType with appropriate VmType.ClassType // * drop VmType.FunctionType and VmType.TypeVariableType @TruffleBoundary - private static VmType normalizeTypes(VmType type, VmClass thisClass, VmClass moduleClass) { + private static VmType normalizeTypes( + VmType type, @Nullable VmClass thisClass, @Nullable VmClass moduleClass) { var types = new HashSet(); normalizeTypes(type, thisClass, moduleClass, types); return minimizeTypes(types); @@ -115,7 +112,7 @@ public final class VmReference extends VmValue { } private static void normalizeTypes( - VmType type, VmClass thisClass, VmClass moduleClass, Set result) { + VmType type, @Nullable VmClass thisClass, @Nullable VmClass moduleClass, Set result) { if (type == UnknownType.INSTANCE || type == NothingType.INSTANCE || type instanceof VmType.StringLiteralType) { @@ -154,17 +151,19 @@ public final class VmReference extends VmValue { } } else if (type instanceof NonFinalThisType) { // there are 4 entrypoints here: - // 1. init via the Reference constructor can only normalize an unparameterized PType.Class - // 2. typecheck via ReferenceTypeNode erases self types to their actual PType.Class + // 1. init via the Reference constructor, which pre-reifies the VmType + // 2. typecheck via ReferenceTypeNode, which pre-reifies the VmType // 3. subscript access can only be achieved by first performing property access, at which time // self types are erased // 4. property access uses the enclosing receiver's class to substitute for these self types // only property access and typecheck can produce THIS or MODULE. - // getCandidatePropertyType and referentTypeIsSubtypeOf always pass non-null `thisClass`. + // getCandidatePropertyType always passes non-null `thisClass`. + assert thisClass != null; result.add(new VmType.ClassType(thisClass)); } else if (type instanceof NonFinalModuleType) { // this can be incorrect for usage of the module type in a class's property type annotation, // which is deprecated!! + assert moduleClass != null; result.add(new VmType.ClassType(moduleClass)); } else { // remaining types: PType.Function, PType.TypeVariable. no normalizing needed; TypeVariable diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmType.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmType.java index 8544eec71..fdaf94d1e 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmType.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmType.java @@ -15,6 +15,7 @@ */ package org.pkl.core.runtime; +import com.oracle.truffle.api.frame.VirtualFrame; import java.util.Arrays; import java.util.List; import java.util.function.Function; @@ -25,8 +26,8 @@ import org.pkl.core.PType.Constrained; import org.pkl.core.PType.StringLiteral; import org.pkl.core.PType.TypeVariable; import org.pkl.core.PType.Union; -import org.pkl.core.TypeParameter; import org.pkl.core.ValueFormatter; +import org.pkl.core.ast.expression.primary.GetModuleNode; public abstract sealed class VmType { @@ -73,6 +74,15 @@ public abstract sealed class VmType { || (other instanceof ConstrainedType ct && isSupertypeOf(ct.baseType)); } + /** + * Where possible, substitute types for their "real" type. Self types become {@link ClassType}. + * Where possible, {@link TypeVariableType} instances are replaced with the corresponding type + * argument type. + * + *

Contract: when no substitutions were made, returns the identical type (same object). + */ + public abstract VmType reify(VirtualFrame frame); + public static final class UnknownType extends VmType { public static final UnknownType INSTANCE = new UnknownType(); @@ -99,6 +109,11 @@ public abstract sealed class VmType { public String toString() { return "unknown"; } + + @Override + public VmType reify(VirtualFrame frame) { + return this; + } } public static final class NothingType extends VmType { @@ -127,6 +142,11 @@ public abstract sealed class VmType { public String toString() { return "nothing"; } + + @Override + public VmType reify(VirtualFrame frame) { + return this; + } } public static final class StringLiteralType extends VmType { @@ -166,6 +186,11 @@ public abstract sealed class VmType { public int hashCode() { return 31 * literal.hashCode(); } + + @Override + public VmType reify(VirtualFrame frame) { + return this; + } } public abstract static sealed class AbstractClassType extends VmType { @@ -265,7 +290,7 @@ public abstract sealed class VmType { var newState = cSupertype.typeArguments.clone(); for (var i = 0; i < newState.length; i++) { if (!(newState[i] instanceof TypeVariableType tvt)) continue; - newState[i] = state[tvt.typeParameter.getIndex()]; + newState[i] = state[tvt.typeParameter.index()]; } state = newState; } @@ -274,7 +299,7 @@ public abstract sealed class VmType { assert goalState.length == state.length; var params = clazz.getTypeParameters(); for (var i = 0; i < goalState.length; i++) { - if (!switch (params.get(i).getVariance()) { + if (!switch (params[i].variance()) { case INVARIANT -> state[i].equals(goalState[i]); case COVARIANT -> state[i].isSubtypeOf(goalState[i]); case CONTRAVARIANT -> state[i].isSupertypeOf(goalState[i]); @@ -340,6 +365,18 @@ public abstract sealed class VmType { return appendTypeArguments(new StringBuilder(clazz.getDisplayName()), typeArguments) .toString(); } + + @Override + public VmType reify(VirtualFrame frame) { + var typeArgs = new VmType[typeArguments.length]; + var changed = false; + for (var i = 0; i < typeArgs.length; i++) { + typeArgs[i] = typeArguments[i].reify(frame); + changed = changed || typeArguments[i] != typeArgs[i]; + } + + return changed ? new ClassType(clazz, typeArgs) : this; + } } public static final class FinalModuleType extends AbstractClassType { @@ -356,6 +393,11 @@ public abstract sealed class VmType { public String toString() { return "module"; } + + @Override + public VmType reify(VirtualFrame frame) { + return this; + } } public static final class FinalThisType extends AbstractClassType { @@ -372,6 +414,11 @@ public abstract sealed class VmType { public String toString() { return "this"; } + + @Override + public VmType reify(VirtualFrame frame) { + return this; + } } private abstract static sealed class NonFinalSelfType extends VmType { @@ -416,6 +463,14 @@ public abstract sealed class VmType { public String toString() { return "module"; } + + @Override + public VmType reify(VirtualFrame frame) { + var levelsUp = GetModuleNode.getLevelsUp(frame); + return new ClassType( + VmUtils.getClass( + levelsUp == 0 ? VmUtils.getReceiver(frame) : VmUtils.getReceiver(frame, levelsUp))); + } } public static final class NonFinalThisType extends NonFinalSelfType { @@ -432,6 +487,11 @@ public abstract sealed class VmType { public String toString() { return "this"; } + + @Override + public VmType reify(VirtualFrame frame) { + return new ClassType(VmUtils.getClass(VmUtils.getReceiver(frame))); + } } public static final class NullableType extends VmType { @@ -473,6 +533,12 @@ public abstract sealed class VmType { public int hashCode() { return 31 * elementType.hashCode(); } + + @Override + public VmType reify(VirtualFrame frame) { + var elem = elementType.reify(frame); + return elem == elementType ? this : new NullableType(elem); + } } public static final class ConstrainedType extends VmType { @@ -522,6 +588,12 @@ public abstract sealed class VmType { public int hashCode() { return 31 * identity; } + + @Override + public VmType reify(VirtualFrame frame) { + var base = baseType.reify(frame); + return base == baseType ? this : new ConstrainedType(base, constraints, identity); + } } public static final class AliasType extends VmType { @@ -554,6 +626,18 @@ public abstract sealed class VmType { this.aliasedType = aliasedType; } + /** For reification */ + private AliasType( + VmTypeAlias typeAlias, + @Nullable VmClass clazz, + VmType[] typeArguments, + VmType aliasedType) { + this.typeAlias = typeAlias; + this.typeArguments = typeArguments; + this.clazz = clazz; + this.aliasedType = aliasedType; + } + public VmTypeAlias getVmTypeAlias() { return typeAlias; } @@ -618,6 +702,14 @@ public abstract sealed class VmType { public int hashCode() { return aliasedType.hashCode(); } + + @Override + public VmType reify(VirtualFrame frame) { + var aliased = aliasedType.reify(frame); + return aliased == aliasedType + ? this + : new AliasType(typeAlias, clazz, typeArguments, aliased); + } } public static final class UnionType extends VmType { @@ -692,13 +784,30 @@ public abstract sealed class VmType { } return true; } + + @Override + public VmType reify(VirtualFrame frame) { + var elems = new VmType[elementTypes.length]; + var changed = false; + for (var i = 0; i < elems.length; i++) { + elems[i] = elementTypes[i].reify(frame); + changed = changed || elementTypes[i] != elems[i]; + } + + return changed ? new UnionType(defaultIndex, elems) : this; + } } public static final class TypeVariableType extends VmType { private final TypeParameter typeParameter; + private final TypeParameter.OwnerType ownerType; + private final int levelsUp; - public TypeVariableType(TypeParameter typeParameter) { + public TypeVariableType( + TypeParameter typeParameter, TypeParameter.OwnerType ownerType, int levelsUp) { this.typeParameter = typeParameter; + this.ownerType = ownerType; + this.levelsUp = levelsUp; } public TypeParameter getTypeParameter() { @@ -717,18 +826,30 @@ public abstract sealed class VmType { @Override public PType export() { - return new TypeVariable(typeParameter); + return new TypeVariable(typeParameter.export()); } @Override public String toString() { - return typeParameter.getName(); + return typeParameter.name(); } @Override public int hashCode() { return -318019; } + + @Override + public VmType reify(VirtualFrame frame) { + if (ownerType != TypeParameter.OwnerType.METHOD) return this; + + var typeArg = VmUtils.getTypeArgumentOrNull(frame, typeParameter.slot(), levelsUp); + if (typeArg != null) { + return typeArg.reify(); + } + + return this; + } } private static boolean typesEquals(VmType[] a, VmType[] b) { diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmTypeAlias.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmTypeAlias.java index 6bcdf429c..ddcf3b635 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmTypeAlias.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmTypeAlias.java @@ -29,7 +29,6 @@ import org.pkl.core.Member.SourceLocation; import org.pkl.core.PClassInfo; import org.pkl.core.PObject; import org.pkl.core.TypeAlias; -import org.pkl.core.TypeParameter; import org.pkl.core.ast.VmModifier; import org.pkl.core.ast.expression.primary.ExecuteCustomThisWithRootNode; import org.pkl.core.ast.type.TypeNode; @@ -47,7 +46,7 @@ public final class VmTypeAlias extends VmValue { private final String simpleName; private final VmTyped module; private final String qualifiedName; - private final List typeParameters; + private final TypeParameter[] typeParameters; private final MaterializedFrame enclosingFrame; private @Nullable TypeNode typeNode; @@ -71,7 +70,7 @@ public final class VmTypeAlias extends VmValue { String simpleName, VmTyped module, String qualifiedName, - List typeParameters, + TypeParameter[] typeParameters, MaterializedFrame enclosingFrame) { this.sourceSection = sourceSection; this.headerSection = headerSection; @@ -170,7 +169,7 @@ public final class VmTypeAlias extends VmValue { } public int getTypeParameterCount() { - return typeParameters.size(); + return typeParameters.length; } public TypeNode getTypeNode() { @@ -190,14 +189,14 @@ public final class VmTypeAlias extends VmValue { // * Fewer root nodes to call // * ControlFlowException used to implement union types doesn't escape root node - if (typeParameters.isEmpty()) return deepCopy(typeNode); + if (typeParameters.length == 0) return deepCopy(typeNode); // handle if alias root is itself a type variable (https://github.com/apple/pkl/issues/1711) if (typeNode instanceof TypeVariableNode typeVarNode) { // no need to run validation since the arg itself has already been checked return typeArgumentNodes.length == 0 ? new UnknownTypeNode(sourceSection) - : typeArgumentNodes[typeVarNode.getTypeParameterIndex()]; + : typeArgumentNodes[typeVarNode.getTypeParameter().index()]; } var clone = (TypeNode) typeNode.deepCopy(); @@ -206,7 +205,7 @@ public final class VmTypeAlias extends VmValue { @Override public boolean visit(com.oracle.truffle.api.nodes.Node node) { if (node instanceof TypeVariableNode typeVarNode) { - var index = typeVarNode.getTypeParameterIndex(); + var index = typeVarNode.getTypeParameter().index(); node.replace( typeArgumentNodes.length == 0 ? new UnknownTypeNode(sourceSection) @@ -215,7 +214,7 @@ public final class VmTypeAlias extends VmValue { // Type variables inside constraint expressions (e.g. `every((it) -> it is T)`) // are still unresolved at instantiation time. Replace them with a resolved // unresolved type node that returns the concrete type argument. - var index = unresolvedTypeVar.getTypeParameterIndex(); + var index = unresolvedTypeVar.getTypeParameter().index(); node.replace( typeArgumentNodes.length == 0 ? new UnresolvedTypeNode.Unknown(sourceSection) @@ -258,10 +257,10 @@ public final class VmTypeAlias extends VmValue { simpleName, getModuleName(), qualifiedName, - typeParameters, + TypeParameter.export(typeParameters), module.getVmClass().export()); - for (var parameter : typeParameters) { + for (var parameter : __pTypeAlias.getTypeParameters()) { parameter.initOwner(__pTypeAlias); } diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmTypeArgument.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmTypeArgument.java new file mode 100644 index 000000000..a757b5558 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmTypeArgument.java @@ -0,0 +1,63 @@ +/* + * 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.core.runtime; + +import com.oracle.truffle.api.CallTarget; +import com.oracle.truffle.api.frame.MaterializedFrame; +import com.oracle.truffle.api.source.SourceSection; +import org.jspecify.annotations.Nullable; +import org.pkl.core.ast.member.FunctionNode; +import org.pkl.core.ast.type.TypeArgumentTypeNode; +import org.pkl.core.ast.type.TypeNode; + +/** + * Type argument information passed from a type argument (in a method call) as an argument to a + * {@link FunctionNode}. + */ +public class VmTypeArgument { + + private final @Nullable MaterializedFrame enclosingFrame; + private final TypeArgumentTypeNode rootNode; + + public VmTypeArgument(TypeArgumentTypeNode rootNode, @Nullable MaterializedFrame enclosingFrame) { + this.enclosingFrame = enclosingFrame; + this.rootNode = rootNode; + } + + public @Nullable MaterializedFrame getEnclosingFrame() { + return enclosingFrame; + } + + public CallTarget getCallTarget() { + return rootNode.getCallTarget(); + } + + private TypeNode getTypeNode() { + return rootNode.getTypeNode(); + } + + public VmType reify() { + var type = getTypeNode().getType(); + var frame = enclosingFrame != null ? enclosingFrame : VmUtils.createEmptyMaterializedFrame(); + return type.reify(frame); + } + + public @Nullable Object createDefaultValue( + VmLanguage language, SourceSection headerSection, String qualifiedName) { + var frame = enclosingFrame != null ? enclosingFrame : VmUtils.createEmptyMaterializedFrame(); + return getTypeNode().createDefaultValue(frame, language, headerSection, qualifiedName); + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmUtils.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmUtils.java index 232339404..6101997df 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmUtils.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmUtils.java @@ -57,6 +57,7 @@ import org.pkl.core.ast.expression.primary.CustomThisNode; import org.pkl.core.ast.expression.primary.ThisNode; import org.pkl.core.ast.member.*; import org.pkl.core.ast.type.TypeNode; +import org.pkl.core.ast.type.TypeParameterNode; import org.pkl.core.ast.type.UnresolvedTypeNode; import org.pkl.core.module.ModuleKey; import org.pkl.core.module.ModuleKeys; @@ -182,6 +183,21 @@ public final class VmUtils { return (VmTyped) getReceiver(frame); } + /** Returns the type arguments of the currently executing code. */ + public static @Nullable VmTypeArgument getTypeArgumentOrNull(Frame frame, int slot) { + if (slot < 0) return null; + return (VmTypeArgument) frame.getObject(slot); + } + + public static @Nullable VmTypeArgument getTypeArgumentOrNull( + Frame frame, int slot, int levelsUp) { + if (slot < 0) return null; + if (levelsUp == 0) { + return getTypeArgumentOrNull(frame, slot); + } + return getTypeArgumentOrNull(getEnclosingFrame(getOwner(frame), levelsUp), slot); + } + /** Returns the owner of the currently executing code. */ public static @Nullable VmObjectLike getOwnerOrNull(Frame frame) { return (VmObjectLike) frame.getArguments()[1]; @@ -903,6 +919,14 @@ public final class VmUtils { return resolvedNodes; } + public static TypeParameterNode[] resolveTypeParameters(TypeParameter[] typeParameters) { + var typeParameterNodes = new TypeParameterNode[typeParameters.length]; + for (var i = 0; i < typeParameters.length; i++) { + typeParameterNodes[i] = new TypeParameterNode(typeParameters[i]); + } + return typeParameterNodes; + } + public static void checkIsInstantiable(VmClass parentClass, Node parentNode, Node callerNode) { if (parentClass.isInstantiable()) return; diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/ref/RefNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/ref/RefNodes.java index c8f6b2649..f63f74b98 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/ref/RefNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/ref/RefNodes.java @@ -17,17 +17,21 @@ package org.pkl.core.stdlib.ref; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.frame.VirtualFrame; -import org.pkl.core.runtime.VmClass; import org.pkl.core.runtime.VmReference; +import org.pkl.core.runtime.VmType; import org.pkl.core.runtime.VmTyped; -import org.pkl.core.stdlib.ExternalMethod3Node; +import org.pkl.core.runtime.VmUtils; +import org.pkl.core.stdlib.ExternalMethod2Node; public class RefNodes { - public abstract static class Reference extends ExternalMethod3Node { + public abstract static class Reference extends ExternalMethod2Node { @Specialization protected VmReference eval( - VirtualFrame frame, VmTyped self, VmTyped domain, VmClass clazz, Object data) { - return new VmReference(domain, clazz, data); + VirtualFrame frame, @SuppressWarnings("unused") VmTyped self, VmTyped domain, Object data) { + var referentTypeArg = VmUtils.getTypeArgumentOrNull(frame, 3); + var referentType = + referentTypeArg == null ? VmType.UnknownType.INSTANCE : referentTypeArg.reify(); + return new VmReference(domain, referentType, data); } } } diff --git a/pkl-core/src/main/resources/org/pkl/core/errorMessages.properties b/pkl-core/src/main/resources/org/pkl/core/errorMessages.properties index f16567415..505141297 100644 --- a/pkl-core/src/main/resources/org/pkl/core/errorMessages.properties +++ b/pkl-core/src/main/resources/org/pkl/core/errorMessages.properties @@ -189,7 +189,7 @@ cannotSpreadObject=\ Cannot spread value of type `{0}` into object of type `{1}`. cannotDeclareTypeParameter=\ -Only standard library members can have type parameters. +Only standard library classes can have type parameters. duplicateTypeParameter=\ Duplicate type parameter `{0}`. @@ -1223,9 +1223,6 @@ Cannot follow redirect from ''https:'' URL to ''http:'' URL.\ HTTP Request: `GET {0}`\n\ Redirected to: `{1}` -invalidReferenceTypeAnnotationWithConstraint=\ -`Reference` referent type argument may not include type constraints. - parserError=\ Could not parse Pkl source. @@ -1254,6 +1251,9 @@ noImplementationForAbstractMethods=\ Class `{0}` should either be declared `abstract`, or should implement the following methods:\n\ {1} +methodTypeParameterCountMismatch=\ +Expected method `{0}` overriding `{1}` to have {2} type parameter{2,choice,0#s|1#|1= 1 result.samples == null @@ -21,6 +21,19 @@ facts { result.mean <= result.max } + ["run microbenchmark (in generic method)"] { + local function run() = + new Benchmark.Microbenchmark { + expression = 123 is T + iterations = 3 + iterationTime = 1.ms + isVerbose = false + }.run() + + run::().iterations == 3 + run::().iterations == 3 + } + ["run output benchmark (in verbose mode)"] { local benchmark = new Benchmark.OutputBenchmark { sourceModule = import("../../input-helper/api/benchmark/moduleToBenchmark.pkl") @@ -84,6 +97,7 @@ facts { local text = benchmark.output.text text.contains("[\"micro\"]") + text.contains("[\"generic method\"]") text.contains("[\"output\"]") text.contains("[\"parser\"]") } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/api/reference.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/api/reference.pkl index 37f2f00f3..39ff79611 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/api/reference.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/api/reference.pkl @@ -2,7 +2,7 @@ import "pkl:math" import "pkl:ref" import "pkl:test" -class D extends ref.Domain { +class D extends ref.AbstractDomain { function renderReference(reference: ref.Reference): String = let (data = reference.getData()) let (root = if (data is Resource) data.name else data.toString()) @@ -17,10 +17,11 @@ class D extends ref.Domain { local const d: D = new {} typealias Ref = ref.Reference +const function Ref(data: Any): Ref = ref.Reference::(d, data) abstract class Resource { name: String - hidden fixed $: Ref = ref.Reference(d, getClass(), this) + hidden fixed $: Ref = Ref::(this) } class A extends Resource { @@ -122,28 +123,32 @@ kInterpolation = "\(k)" aValuesJoined = k.aValues.join("\n") // ensure that type arguments that are unions are handled correctly -typeArgs = ref.Reference(d, TypeHolder, null).prop as Ref> +typeArgs = Ref::(null).prop as Ref> class TypeHolder { prop: Listing mappingAnyKey: Mapping mappingTypeArgKey: Mapping, Int> } +typeArgs2 = Ref::>(null) as Ref> class BadResource extends Resource { // return a reference with referent that isn't `this` - fixed $ = ref.Reference(d, A, this) + fixed $ = Ref::(this) } badThisRef = test.catch(() -> new BadResource { name = "bad" }.$.toString()) // test that a bare function type toStrings/exports okay -bareFunctionType = test.catch(() -> ref.Reference(d, Function4, null) as Ref) +bareFunctionType = test.catch(() -> ref.Reference::(d, null) as Ref) -mappingAnyKey = ref.Reference(d, TypeHolder, null).mappingAnyKey["foo"] +// test for allowing constraints during reference construction +constraintInConstructor = Ref:: 5)>(null) + +mappingAnyKey = Ref::(null).mappingAnyKey["foo"] // this one should be an editor error but can't be caught in the runtime // because VmListing/VmMapping don't track their type args -mappingTypeArgKey = ref.Reference(d, TypeHolder, null).mappingTypeArgKey[new Listing { "foo" }] +mappingTypeArgKey = Ref ::(null).mappingTypeArgKey[new Listing { "foo" }] output { renderer { diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference30.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/api/referenceWithFinalModuleType.pkl similarity index 74% rename from pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference30.pkl rename to pkl-core/src/test/files/LanguageSnippetTests/input/api/referenceWithFinalModuleType.pkl index 662fec0da..71efab512 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference30.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/api/referenceWithFinalModuleType.pkl @@ -2,9 +2,9 @@ // module type in a final module import "pkl:ref" -import "reference30.pkl" +import "referenceWithFinalModuleType.pkl" -class D extends ref.Domain { +class D extends ref.AbstractDomain { function renderReference(r: ref.Reference): String = r.getPath().map((it) -> it.property ?? it.key.toString()).join(".") } @@ -12,9 +12,9 @@ class D extends ref.Domain { hidden parent: module? hidden children: List -typealias R = ref.Reference +typealias R = ref.Reference -res0 = ref.Reference(new D {}, getClass(), null) +res0 = ref.Reference::(new D {}, null) res1: R = res0.parent res2: R = res0.parent.parent res3: R = res0.children[0] diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference29.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/api/referenceWithOpenModuleType.pkl similarity index 70% rename from pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference29.pkl rename to pkl-core/src/test/files/LanguageSnippetTests/input/api/referenceWithOpenModuleType.pkl index ebcdf8bba..62976ce5d 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference29.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/api/referenceWithOpenModuleType.pkl @@ -1,12 +1,12 @@ -open module reference29 +open module referenceWithOpenModuleType // interactions between references and self types // module type in an open module import "pkl:ref" -import "reference29.pkl" +import "referenceWithOpenModuleType.pkl" -class D extends ref.Domain { +class D extends ref.AbstractDomain { function renderReference(r: ref.Reference): String = r.getPath().map((it) -> it.property ?? it.key.toString()).join(".") } @@ -14,9 +14,9 @@ class D extends ref.Domain { hidden parent: module? hidden children: List -typealias R = ref.Reference +typealias R = ref.Reference -res0 = ref.Reference(new D {}, getClass(), null) +res0 = ref.Reference::(new D {}, null) res1: R = res0.parent res2: R = res0.parent.parent res3: R = res0.children[0] diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference28.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/api/referenceWithThisType.pkl similarity index 86% rename from pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference28.pkl rename to pkl-core/src/test/files/LanguageSnippetTests/input/api/referenceWithThisType.pkl index bb700ede3..868f011c7 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference28.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/api/referenceWithThisType.pkl @@ -2,7 +2,7 @@ import "pkl:ref" -class D extends ref.Domain { +class D extends ref.AbstractDomain { function renderReference(r: ref.Reference): String = r.getPath().map((it) -> it.property ?? it.key.toString()).join(".") } @@ -22,14 +22,14 @@ open class Node2 { typealias R = ref.Reference typealias R2 = ref.Reference -res0: R = ref.Reference(new D {}, Node, null) +res0: R = ref.Reference::(new D {}, null) res1: R = res0.parent res2: R = res0.parent.parent res3: R = res0.children[0] res4: R = res0.children[0].parent res5: R = res0.children[0].children[0] -res0a: R2 = ref.Reference(new D {}, Node2, null) +res0a: R2 = ref.Reference::(new D {}, null) res1a: R2 = res0a.parent res2a: R2 = res0a.parent.parent res3a: R2 = res0a.children[0] diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/basic/reference.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/basic/reference.pkl index 650afc846..992d56ad4 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/basic/reference.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/basic/reference.pkl @@ -2,7 +2,7 @@ amends "../snippetTest.pkl" import "pkl:ref" -local class D extends ref.Domain { +local class D extends ref.AbstractDomain { function renderReference(reference: ref.Reference): String = reference.getData().toString() } @@ -25,9 +25,9 @@ local class OtherHolder { constrainedType: String(isEmpty) } -local holderRef: Ref = ref.Reference(d, Holder, null) -local holderRef2: Ref = ref.Reference(d, Holder, null) -local otherHolderRef: Ref = ref.Reference(d, OtherHolder, null) +local holderRef: Ref = ref.Reference::(d, null) +local holderRef2: Ref = ref.Reference::(d, null) +local otherHolderRef: Ref = ref.Reference::(d, null) facts { ["equality"] { diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/invalidVarianceModifierClassMethod.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/invalidVarianceModifierClassMethod.pkl new file mode 100644 index 000000000..5093ac045 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/invalidVarianceModifierClassMethod.pkl @@ -0,0 +1 @@ +function foo() = null diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/invalidVarianceModifierObjectMethod.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/invalidVarianceModifierObjectMethod.pkl new file mode 100644 index 000000000..fa5e84396 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/invalidVarianceModifierObjectMethod.pkl @@ -0,0 +1,3 @@ +obj { + local function foo() = null +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/localFunctionWithTypeParameter.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/localFunctionWithTypeParameter.pkl deleted file mode 100644 index 93d299be2..000000000 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/localFunctionWithTypeParameter.pkl +++ /dev/null @@ -1 +0,0 @@ -local function x() = x \ No newline at end of file diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/overrideMethodTypeParameterCount.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/overrideMethodTypeParameterCount.pkl new file mode 100644 index 000000000..f0f72f8be --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/overrideMethodTypeParameterCount.pkl @@ -0,0 +1,7 @@ +open class A { + function foo() = null +} + +class B extends A { + function foo() = null +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference1.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference1.pkl index b61c0ac5f..5c7ec40be 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference1.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference1.pkl @@ -1,9 +1,9 @@ import "pkl:ref" -class D extends ref.Domain { +class D extends ref.AbstractDomain { function renderReference(reference: ref.Reference): String = throw("not supported") } local d: D = new {} typealias Ref = ref.Reference -test = ref.Reference(d, String, "") as Ref +test = ref.Reference::(d, "") as Ref diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference10.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference10.pkl deleted file mode 100644 index 485f5b440..000000000 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference10.pkl +++ /dev/null @@ -1,9 +0,0 @@ -import "pkl:ref" - -class D extends ref.Domain { - function renderReference(reference: ref.Reference): String = throw("not supported") -} -typealias Ref = ref.Reference -local d: D = new {} - -test = ref.Reference(d, String, "") as Ref> diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference11.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference11.pkl deleted file mode 100644 index b6f276202..000000000 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference11.pkl +++ /dev/null @@ -1,12 +0,0 @@ -import "pkl:ref" - -class D extends ref.Domain { - function renderReference(reference: ref.Reference): String = throw("not supported") -} -typealias Ref = ref.Reference -local d: D = new {} - -test = ref.Reference(d, String, "") as Ref - -typealias Alias1 = Int | Alias2 -typealias Alias2 = String(length < 5) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference12.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference12.pkl deleted file mode 100644 index ce04958f7..000000000 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference12.pkl +++ /dev/null @@ -1,12 +0,0 @@ -import "pkl:ref" - -class D extends ref.Domain { - function renderReference(reference: ref.Reference): String = throw("not supported") -} -typealias RefAlias1 = ref.Reference -local d: D = new {} - -test = ref.Reference(d, String, "") as RefAlias1 - -typealias Alias1 = Int | Alias2? -typealias Alias2 = String(length < 5) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference13.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference13.pkl index 87cb0615a..b56ccae56 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference13.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference13.pkl @@ -1,8 +1,8 @@ import "pkl:ref" -class D extends ref.Domain { +class D extends ref.AbstractDomain { function renderReference(reference: ref.Reference): String = throw("not supported") } local d: D = new {} -test = ref.Reference(d, Mapping, "").default +test = ref.Reference::(d, "").default diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference14.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference14.pkl index b19940d3b..6e7ee1bd3 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference14.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference14.pkl @@ -1,8 +1,8 @@ import "pkl:ref" -class D extends ref.Domain { +class D extends ref.AbstractDomain { function renderReference(reference: ref.Reference): String = throw("not supported") } local d: D = new {} -test = ref.Reference(d, Listing, "").default +test = ref.Reference::(d, "").default diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference15.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference15.pkl index 2c3b7ab2a..b639c52c6 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference15.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference15.pkl @@ -1,8 +1,8 @@ import "pkl:ref" -class D extends ref.Domain { +class D extends ref.AbstractDomain { function renderReference(reference: ref.Reference): String = throw("not supported") } local d: D = new {} -test = ref.Reference(d, Dynamic, "").default +test = ref.Reference::(d, "").default diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference16.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference16.pkl index 3f80f1112..56b98d5e0 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference16.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference16.pkl @@ -2,9 +2,9 @@ import "pkl:ref" import ".../input-helper/errors/ReferencedModule.pkl" -class D extends ref.Domain { +class D extends ref.AbstractDomain { function renderReference(reference: ref.Reference): String = throw("not supported") } local d: D = new {} -test = ref.Reference(d, ReferencedModule.getClass(), "").output +test = ref.Reference::(d, "").output diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference17.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference17.pkl index af693df83..104185d84 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference17.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference17.pkl @@ -2,9 +2,9 @@ import "pkl:ref" import ".../input-helper/errors/ReferencedModuleWithOutputOverride.pkl" -class D extends ref.Domain { +class D extends ref.AbstractDomain { function renderReference(reference: ref.Reference): String = throw("not supported") } local d: D = new {} -test = ref.Reference(d, ReferencedModuleWithOutputOverride.getClass(), "").output +test = ref.Reference::(d, "").output diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference18.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference18.pkl index a8a418f8c..fea785237 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference18.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference18.pkl @@ -4,9 +4,9 @@ import ".../input-helper/errors/ReferencedModuleWithOutputOverride.pkl" class ModuleSubclass extends ReferencedModuleWithOutputOverride -class D extends ref.Domain { +class D extends ref.AbstractDomain { function renderReference(reference: ref.Reference): String = throw("not supported") } local d: D = new {} -test = ref.Reference(d, ModuleSubclass, "").output +test = ref.Reference::(d, "").output diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference19.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference19.pkl index 042272748..0c94fab71 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference19.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference19.pkl @@ -4,9 +4,9 @@ class A { foo: String | Int | Boolean } -class D extends ref.Domain { +class D extends ref.AbstractDomain { function renderReference(reference: ref.Reference): String = throw("not supported") } local d: D = new {} -test = ref.Reference(d, A, "").foo as ref.Reference +test = ref.Reference::(d, "").foo as ref.Reference diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference2.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference2.pkl index a5519badf..3831d8083 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference2.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference2.pkl @@ -1,9 +1,9 @@ import "pkl:ref" -class D extends ref.Domain { +class D extends ref.AbstractDomain { function renderReference(reference: ref.Reference): String = throw("not supported") } local d: D = new {} typealias Ref = ref.Reference -test = ref.Reference(d, String, "") as Ref +test = ref.Reference::(d, "") as Ref diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference20.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference20.pkl index d0311d6d0..2cfc93e6b 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference20.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference20.pkl @@ -4,12 +4,12 @@ class A { foo: String | Int | Boolean } -class D extends ref.Domain { +class D extends ref.AbstractDomain { function renderReference(reference: ref.Reference): String = throw("not supported") } local d: D = new {} -local test = ref.Reference(d, A, "").foo +local test = ref.Reference::(d, "").foo testInterpolation = "test:\(test)" // this tests that the interpolation appears in the output when renderReference throws diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference21.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference21.pkl deleted file mode 100644 index 9c2699b47..000000000 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference21.pkl +++ /dev/null @@ -1,7 +0,0 @@ -import "pkl:ref" - -typealias Ref = ref.Reference - -typealias Ref2 = Ref - -foo: Ref2 diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference22.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference22.pkl deleted file mode 100644 index ff2ebb1a2..000000000 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference22.pkl +++ /dev/null @@ -1,6 +0,0 @@ -import "pkl:ref" - -typealias Ref = ref.Reference -typealias Ref2 = Ref - -foo: Ref2 diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference23.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference23.pkl deleted file mode 100644 index d57c86120..000000000 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference23.pkl +++ /dev/null @@ -1,7 +0,0 @@ - -import "pkl:ref" - -typealias Foo1 = Foo2 -typealias Foo2 = String(isEmpty) - -foo: ref.Reference diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference24.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference24.pkl index 3877f6208..25fa5d958 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference24.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference24.pkl @@ -1,6 +1,6 @@ import "pkl:ref" -class D extends ref.Domain { +class D extends ref.AbstractDomain { function renderReference(_): String = throw("not implemented") } @@ -8,10 +8,6 @@ class Bird { name: String } -class Holder { - $: Bird | Int -} - -myRef: ref.Reference = ref.Reference(new D {}, Holder, null).$ +myRef: ref.Reference = ref.Reference::(new D {}, null) res = myRef.name diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference25.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference25.pkl index 8d6e3d8a1..e528f3354 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference25.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference25.pkl @@ -1,13 +1,9 @@ import "pkl:ref" -class D extends ref.Domain { +class D extends ref.AbstractDomain { function renderReference(_): String = throw("not implemented") } -class Holder { - $: Duration | Listing -} - -local myRef = ref.Reference(new D {}, Holder, null).$ +local myRef = ref.Reference::>(new D {}, null) res = myRef[0] diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference26.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference26.pkl index e0ecf6479..b3117c248 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference26.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference26.pkl @@ -1,9 +1,9 @@ import "pkl:ref" -class D extends ref.Domain { +class D extends ref.AbstractDomain { function renderReference(_): String = throw("not implemented") } -local myRef = ref.Reference(new D {}, Listing, null) +local myRef = ref.Reference::(new D {}, null) res = myRef.name diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference27.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference27.pkl index 96d883165..df2c992e4 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference27.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference27.pkl @@ -1,13 +1,9 @@ import "pkl:ref" -class D extends ref.Domain { +class D extends ref.AbstractDomain { function renderReference(_): String = throw("not implemented") } -class Holder { - func: (Int) -> Boolean -} - -local myRef = ref.Reference(new D {}, Holder, null).func +local myRef = ref.Reference:: Boolean>(new D {}, null) res = myRef.name diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference3.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference3.pkl index 3037c05b0..dbbf8150f 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference3.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference3.pkl @@ -1,9 +1,9 @@ import "pkl:ref" -class D extends ref.Domain { +class D extends ref.AbstractDomain { function renderReference(reference: ref.Reference): String = throw("not supported") } local d: D = new {} typealias Ref = ref.Reference -test = ref.Reference(d, String, "") as Ref> +test = ref.Reference::(d, "") as Ref> diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference4.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference4.pkl index b6eefabb6..95ce36cf8 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference4.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference4.pkl @@ -1,12 +1,12 @@ import "pkl:ref" -class D extends ref.Domain { +class D extends ref.AbstractDomain { function renderReference(reference: ref.Reference): String = throw("not supported") } local d: D = new {} typealias Ref = ref.Reference -test = ref.Reference(d, String, "") as Ref +test = ref.Reference::(d, "") as Ref typealias Alias1 = Int | Alias2 typealias Alias2 = Boolean diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference5.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference5.pkl index a570ea49e..31cca0418 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference5.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference5.pkl @@ -1,14 +1,14 @@ import "pkl:ref" -class D extends ref.Domain { +class D extends ref.AbstractDomain { function renderReference(reference: ref.Reference): String = throw("not supported") } local d: D = new {} typealias Ref = ref.Reference -class D2 extends ref.Domain { +class D2 extends ref.AbstractDomain { function renderReference(reference: ref.Reference): String = throw("not supported") } local d2: D2 = new {} -test = ref.Reference(d2, String, "") as Ref +test = ref.Reference::(d2, "") as Ref diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference6.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference6.pkl index 088305c3e..524800df3 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference6.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference6.pkl @@ -1,6 +1,6 @@ import "pkl:ref" -class D extends ref.Domain { +class D extends ref.AbstractDomain { function renderReference(reference: ref.Reference): String = throw("not supported") } local d: D = new {} @@ -10,4 +10,4 @@ class A { b: String } -test = ref.Reference(d, String, new A {}).c +test = ref.Reference::(d, new A {}).c diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference7.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference7.pkl index 6bd109fec..2feb3a5d3 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference7.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference7.pkl @@ -1,9 +1,9 @@ import "pkl:ref" -class D extends ref.Domain { +class D extends ref.AbstractDomain { function renderReference(reference: ref.Reference): String = throw("not supported") } local d: D = new {} typealias Ref = ref.Reference -test = ref.Reference(d, List, List())["hi"] +test = ref.Reference::(d, List())["hi"] diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference8.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference8.pkl deleted file mode 100644 index fcf66b49d..000000000 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference8.pkl +++ /dev/null @@ -1,9 +0,0 @@ -import "pkl:ref" - -class D extends ref.Domain { - function renderReference(reference: ref.Reference): String = throw("not supported") -} -typealias Ref = ref.Reference -local d: D = new {} - -test = ref.Reference(d, String, "") as Ref diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference9.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference9.pkl deleted file mode 100644 index 66faa0409..000000000 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/reference9.pkl +++ /dev/null @@ -1,9 +0,0 @@ -import "pkl:ref" - -class D extends ref.Domain { - function renderReference(reference: ref.Reference): String = throw("not supported") -} -typealias Ref = ref.Reference -local d: D = new {} - -test = ref.Reference(d, String, "") as Ref diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/userDefinedTypeParameter2.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/userDefinedTypeParameter2.pkl deleted file mode 100644 index 3d354be71..000000000 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/userDefinedTypeParameter2.pkl +++ /dev/null @@ -1,3 +0,0 @@ -class DoIt { - function doit(a: A): A = a -} \ No newline at end of file diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArguments.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArguments.pkl new file mode 100644 index 000000000..fbd9b6c2e --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArguments.pkl @@ -0,0 +1,144 @@ +open module callWithTypeArguments + +import "pkl:test" +import "pkl:ref" + +class Domain extends ref.AbstractDomain { + function renderReference(reference: ref.Reference) = "" +} + +directCall { + pair = Pair::, Set>(List("foo"), Set("bar")) + pairSelf = Pair::(new module {}, new module {}).first.getClass().toString() + pairErr = test.catch(() -> Pair::, Set>(List(0), Set("bar"))) + pairSelfErr = test.catch(() -> Pair::(List(), new module {})) + listMap = List("hi", "foo", "a").map::((it) -> it.length) + listMapErr = test.catch(() -> List("hi", "foo", "a").map::((it) -> it.length)) + wrongTypeArgCount = test.catch(() -> Pair:: ("foo", 10)) + nonGenericMethod = test.catch(() -> Regex:: ("foo")) +} + +open class A { + function doIt(arg: List): Boolean = arg.first is T +} + +class B extends A { + function doIt(arg: List): Boolean = super.doIt::(arg) || super.doIt::>(arg) +} + +superCall { + local function doIt(a: A, arg: List): Boolean = + a.doIt::(arg) + +// should be true: + doIt(new A {}, List("")) + doIt(new A {}, List("")) + doIt(new B {}, List("")) + doIt(new B {}, List(List(""))) +// should be false: + doIt(new A {}, List(0)) + doIt(new A {}, List(List(0))) + doIt(new B {}, List(List(0))) +} + +local num = 5 + +objectMethod { + local function foo(): T = "hi" + res = test.catch(() -> foo:: num)>()) +} + +function lazy1(it): Listing = new Listing { it } +local function lazy2(it): Listing = new Listing { it } +lazyTypeCheck { + local function lazy3(it): Listing = new Listing { it } + res1 = test.catch(() -> lazy1::(5)) + res2 = test.catch(() -> lazy2::(5)) + res3 = test.catch(() -> lazy3::(5)) + + eager { + local myListing: Listing = new { "Hello, world!" } + local function myFunc() = new { + result = myListing is T + } + // should be false: + res = myFunc::>().result + } + + closure { + local myListing: Listing = new { "Hello, world!" } + local function myFunc() = new { + result = myListing as T + } + res = test.catch(() -> myFunc::>()) + } +} + +class Person { + name: String + + function greet() = "hi \(name)" +} + +typeDefaults { + local function makeNamed(val: String): T = new { name = val } + local function makeNamedListing(val: String): Listing = new { + new { name = val } + } + local function makeFromParam(val: T): Listing = new { val } + res1 = makeNamed::("bob") + res1a = res1.getClass().toString() + res2 = makeNamedListing::("bob") + res2a = res2.first.getClass().toString() + res3 = makeFromParam::(new { name = "bob" }) + res3a = res3.first.getClass().toString() + res3b = test.catch(() -> makeFromParam::(new Bird { name = "joe" })) +} + +class Bird { + name: String + + function greet() = "hi \(name)" +} + +polymorphism { + // test that method type parameters are considered non-final + local function make(): T = new T { name = "fred" } + res1 = make::() + res2 = make::() + + local function call(obj: Bird | Person) = obj.greet::() + res3 = call(new Person { name = "fred" }) + res4 = test.catch(() -> call(new Bird { name = "pigeon" })) + res5 = call(new Person { name = "bob" }) +} + +typeCast { + local function cast(l: Listing): Listing = l + + res1 = cast::(new Listing { "Bobbert" }) + + res2 = test.catch(() -> cast:: 10)>(res1).toString()) +} + +constraintReadsFrameSlot { + local function id(x) = x is T + local function f(n: Int) = id:: n)>("hi") + + res1 = f(1) + res2 = let (n = 1) id:: n)>("hi") + res3 = ((n) -> id:: n)>("hi")).apply(1) + res4 = new Listing { + for (n in List(1)) { + id:: n)>("hi") + } + } +} + +output { + renderer { + converters { + [ref.Reference] = (it) -> it.toString() + } + } +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArgumentsConstrainedType.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArgumentsConstrainedType.pkl new file mode 100644 index 000000000..8bd4fcc7b --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArgumentsConstrainedType.pkl @@ -0,0 +1,29 @@ +const function foo(val): Boolean = val is T + +local prefix1 = "hi" +hidden prefix2 = "hi" +function prefix3() = "hi" +local function prefix4() = "hi" + +class Bar { + x: Int = 10 + function baz(val): Boolean = foo::(val) + function qux(val): Boolean = foo::(val) +} + +res { + res1a = foo::("high five") + res1b = foo::("high five") + res1c = foo::("high five") + res1d = foo::("high five") + + res2a = new Bar {}.baz::("high five") + res2b = new Bar {}.baz::("high five") + res2c = new Bar {}.baz::("high five") + res2d = new Bar {}.baz::("high five") + + res3a = new Bar {}.qux::("high five") + res3b = new Bar {}.qux::("high five") + res3c = new Bar {}.qux::("high five") + res3d = new Bar {}.qux::("high five") +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArgumentsLexical.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArgumentsLexical.pkl new file mode 100644 index 000000000..108f45df5 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArgumentsLexical.pkl @@ -0,0 +1,96 @@ +open module callWithTypeArguments + +import "pkl:test" +import "pkl:ref" + +class Domain extends ref.AbstractDomain { + function renderReference(reference: ref.Reference) = "" +} + +class Person { + name: String + + function greet() = "hi \(name)" +} + +lexical { + local function f(): Dynamic = new { res = 5 as T } + res1 = test.catch(() -> f::().res) + + local function g(): Dynamic = new { + local myProp: T = 5 + res = myProp + } + res2 = test.catch(() -> g::().res) + + local function nested() = new Dynamic { + local function inner(x: Listing, y: U) = x + local function inner2(x: T, y: U) = x + local function inner3(x: T) = x + local function inner4() = new Dynamic { + local function innest1(x: T, y: U, z: V) = x + local function innest2(x: T) = x + res1 = innest1::(new { name = "x" }, 1, "hi") + res2 = innest1(new { name = "x" }, 1, "hi") + res3 = test.catch(() -> innest2(1)) + } + + res1 = inner::(new { new { name = "x" } }, 1) + res2 = inner2::(new { name = "x" }, 1) + res3 = inner(new { new { name = "x" } }, 1) + res4 = inner2(new { name = "x" }, 1) + res5 = test.catch(() -> inner3(1)) + nested = inner4::() + } + nested = nested::() + + inEagerScope { + local function k1() = new { + [let (elem = "key" as T) elem] = "value" + } + test.catch(() -> k1::()) + + local function k2() = new { + for (i in List(1, 2, 3)) { + [let (elem = "key".repeat(i) as T) elem as T] = "value" + } + } + k2::() + } + + reify { + local function makeMyObj() = new { + res = ref.Reference::(new {}, null) + } + + res = makeMyObj::().res.name + } +} + +local function nested(foo) = new Dynamic { + local function inner(x: Listing, y: U) = x + local function inner2(x: T, y: U) = x + local function inner3(x: T) = x + local function inner4() = new Dynamic { + local function innest1(x: T, y: U, z: V) = x + local function innest2(x: T) = x + res1 = innest1::(new { name = "x" }, 1, "hi") + res2 = innest1(new { name = "x" }, 1, "hi") + res3 = test.catch(() -> innest2(1)) + } + res1 = inner::(new { new { name = "x" } }, foo) + res2 = inner2::(new { name = "x" }, 1) + res3 = inner(new { new { name = "x" } }, 1) + res4 = inner2(new { name = "x" }, 1) + res5 = test.catch(() -> inner3(1)) + nested = inner4::() +} +nested = nested::(1) + +output { + renderer { + converters { + [ref.Reference] = (it) -> it.toString() + } + } +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArgumentsNestedWithOffset.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArgumentsNestedWithOffset.pkl new file mode 100644 index 000000000..94b1ce46d --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArgumentsNestedWithOffset.pkl @@ -0,0 +1,8 @@ + + +local function make(): Listing = new Listing { 1; 2 } +local function amend(l: Listing): Listing = (l) { 3 } as Listing + +local parent = make::() +local child = amend::(parent) +res = child[0] diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArgumentsSelfType.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArgumentsSelfType.pkl new file mode 100644 index 000000000..5927263d6 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArgumentsSelfType.pkl @@ -0,0 +1,55 @@ +open class A { + function foo(val): List = List(val is T, val is U, val is V) + function a(val): List = this.foo::(val) +} + +open class B extends A { + function b1(val): List = this.a::(val) + function b2(val): List = super.a::(val) +} + +class C extends B { + function c1(val): List = this.b1::(val) + function c2(val): List = super.b1::(val) + function c3(val): List = this.b2::(val) + function c4(val): List = super.b2::(val) +} + +resAaA = new A {}.a(new A {}) +resAaB = new A {}.a(new B {}) +resAaC = new A {}.a(new C {}) + +resBaA = new B {}.a(new A {}) +resBaB = new B {}.a(new B {}) +resBaC = new B {}.a(new C {}) + +resBb1A = new B {}.b1(new A {}) +resBb1B = new B {}.b1(new B {}) +resBb1C = new B {}.b1(new C {}) +resBb2A = new B {}.b2(new A {}) +resBb2B = new B {}.b2(new B {}) +resBb2C = new B {}.b2(new C {}) + +resCaA = new C {}.a(new A {}) +resCaB = new C {}.a(new B {}) +resCaC = new C {}.a(new C {}) + +resCb1A = new C {}.b1(new A {}) +resCb1B = new C {}.b1(new B {}) +resCb1C = new C {}.b1(new C {}) +resCb2A = new C {}.b2(new A {}) +resCb2B = new C {}.b2(new B {}) +resCb2C = new C {}.b2(new C {}) + +resCc1A = new C {}.c1(new A {}) +resCc1B = new C {}.c1(new B {}) +resCc1C = new C {}.c1(new C {}) +resCc2A = new C {}.c2(new A {}) +resCc2B = new C {}.c2(new B {}) +resCc2C = new C {}.c2(new C {}) +resCc3A = new C {}.c3(new A {}) +resCc3B = new C {}.c3(new B {}) +resCc3C = new C {}.c3(new C {}) +resCc4A = new C {}.c4(new A {}) +resCc4B = new C {}.c4(new B {}) +resCc4C = new C {}.c4(new C {}) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArgumentsStacktrace.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArgumentsStacktrace.pkl new file mode 100644 index 000000000..a515758a1 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArgumentsStacktrace.pkl @@ -0,0 +1,5 @@ +function foo(bar) = bar as T + +function baz(qux) = foo::>(qux) + +res = baz::(List("a")) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArgumentsStacktrace2.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArgumentsStacktrace2.pkl new file mode 100644 index 000000000..757786f35 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/methods/callWithTypeArgumentsStacktrace2.pkl @@ -0,0 +1,7 @@ +foo = 5 + +function bar() = new { + prop = foo as T +} + +res = bar::() diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/methods/genericWithLocalOverride.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/methods/genericWithLocalOverride.pkl new file mode 100644 index 000000000..99d806d04 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/methods/genericWithLocalOverride.pkl @@ -0,0 +1,16 @@ +// when no function override (parent or child are local) then varying number of type params is okay + +open class P { + function f(x) = x + local function g(x) = x + a = f(1) + b = g(2) +} +class C extends P { + local function f(x) = x + function g(x) = x + c = f(2) + d = g(3) +} + +ok = true diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/api/benchmarkModule.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/api/benchmarkModule.pcf index 50963e80f..71fbb177c 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/api/benchmarkModule.pcf +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/api/benchmarkModule.pcf @@ -8,6 +8,10 @@ facts { true true } + ["run microbenchmark (in generic method)"] { + true + true + } ["run output benchmark (in verbose mode)"] { true true @@ -29,6 +33,7 @@ facts { } ["evaluate entire benchmark module"] { true + false true true } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/api/reference.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/api/reference.pcf index f5bf9150c..01b7f4eb4 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/api/reference.pcf +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/api/reference.pcf @@ -60,7 +60,9 @@ aValuesJoined = """ ${b.outputs.nonString} """ typeArgs = "${null.prop}" -badThisRef = "Expected value of type `pkl.ref#Reference`, but got type `pkl.ref#Reference`. Value: Reference(new reference#D {}, reference#A, new reference#BadResource { name =..." +typeArgs2 = "${null}" +badThisRef = "Expected value of type `pkl.ref#Reference`, but got type `pkl.ref#Reference`. Value: Reference(new reference#D {}, reference#A, new reference#BadResource { name =..." bareFunctionType = "Expected value of type `pkl.ref#Reference`, but got type `pkl.ref#Reference`. Value: Reference(new reference#D {}, Function4, null)" +constraintInConstructor = "${null}" mappingAnyKey = "${null.mappingAnyKey[foo]}" mappingTypeArgKey = "${null.mappingTypeArgKey[new Listing { \"foo\" }]}" diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference29.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/api/referenceWithFinalModuleType.pcf similarity index 100% rename from pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference29.pcf rename to pkl-core/src/test/files/LanguageSnippetTests/output/api/referenceWithFinalModuleType.pcf diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference30.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/api/referenceWithOpenModuleType.pcf similarity index 100% rename from pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference30.pcf rename to pkl-core/src/test/files/LanguageSnippetTests/output/api/referenceWithOpenModuleType.pcf diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference28.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/api/referenceWithThisType.pcf similarity index 100% rename from pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference28.pcf rename to pkl-core/src/test/files/LanguageSnippetTests/output/api/referenceWithThisType.pcf diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/invalidVarianceModifierClassMethod.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/invalidVarianceModifierClassMethod.err new file mode 100644 index 000000000..fde20d04c --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/invalidVarianceModifierClassMethod.err @@ -0,0 +1,6 @@ +–– Pkl Error –– +Variance modifiers may only be applied to type parameters on classes or typealiases. + +x | function foo() = null + ^^ +at invalidVarianceModifierClassMethod (file:///$snippetsDir/input/errors/invalidVarianceModifierClassMethod.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/invalidVarianceModifierObjectMethod.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/invalidVarianceModifierObjectMethod.err new file mode 100644 index 000000000..8e870a68b --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/invalidVarianceModifierObjectMethod.err @@ -0,0 +1,6 @@ +–– Pkl Error –– +Variance modifiers may only be applied to type parameters on classes or typealiases. + +x | local function foo() = null + ^^ +at invalidVarianceModifierObjectMethod (file:///$snippetsDir/input/errors/invalidVarianceModifierObjectMethod.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/localFunctionWithTypeParameter.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/localFunctionWithTypeParameter.err deleted file mode 100644 index 420f4dd0e..000000000 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/localFunctionWithTypeParameter.err +++ /dev/null @@ -1,6 +0,0 @@ -–– Pkl Error –– -Only standard library members can have type parameters. - -x | local function x() = x - ^ -at localFunctionWithTypeParameter (file:///$snippetsDir/input/errors/localFunctionWithTypeParameter.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/overrideMethodTypeParameterCount.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/overrideMethodTypeParameterCount.err new file mode 100644 index 000000000..e3c9f7f03 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/overrideMethodTypeParameterCount.err @@ -0,0 +1,14 @@ +–– Pkl Error –– +Expected method `overrideMethodTypeParameterCount#B.foo` overriding `overrideMethodTypeParameterCount#A.foo` to have 1 type parameter, but got 2. + +x | function foo() = null + ^^^^^^^^^^^^^^^^^^^^ +at overrideMethodTypeParameterCount#B (file:///$snippetsDir/input/errors/overrideMethodTypeParameterCount.pkl) + +xxx | renderer.renderDocument(value) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +at pkl.base#Module.output.text (pkl:base) + +xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8") + ^^^^ +at pkl.base#Module.output.bytes (pkl:base) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference1.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference1.err index eb46aa990..0e0692538 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference1.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference1.err @@ -6,8 +6,8 @@ x | typealias Ref = ref.Reference ^^^^^^^^^^^^^^^^^^^ at reference1#test (file:///$snippetsDir/input/errors/reference1.pkl) -x | test = ref.Reference(d, String, "") as Ref - ^^^^^^^^ +x | test = ref.Reference::(d, "") as Ref + ^^^^^^^^ at reference1#test (file:///$snippetsDir/input/errors/reference1.pkl) xxx | renderer.renderDocument(value) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference10.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference10.err deleted file mode 100644 index 6511baf11..000000000 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference10.err +++ /dev/null @@ -1,18 +0,0 @@ -–– Pkl Error –– -`Reference` referent type argument may not include type constraints. - -x | typealias Ref = ref.Reference - ^^^^^^^^^^^^^^^^^^^ -at reference10#Ref (file:///$snippetsDir/input/errors/reference10.pkl) - -x | test = ref.Reference(d, String, "") as Ref> - ^^^^^^^^^^^^^^^^^^^^^^^^^^ -at reference10#test (file:///$snippetsDir/input/errors/reference10.pkl) - -xxx | renderer.renderDocument(value) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -at pkl.base#Module.output.text (pkl:base) - -xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8") - ^^^^ -at pkl.base#Module.output.bytes (pkl:base) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference11.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference11.err deleted file mode 100644 index 4c2455e20..000000000 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference11.err +++ /dev/null @@ -1,26 +0,0 @@ -–– Pkl Error –– -`Reference` referent type argument may not include type constraints. - -xx | typealias Alias2 = String(length < 5) - ^^^^^^^^^^^^^^^^^^ -at reference11#Alias2 (file:///$snippetsDir/input/errors/reference11.pkl) - -xx | typealias Alias1 = Int | Alias2 - ^^^^^^ -at reference11#Alias1 (file:///$snippetsDir/input/errors/reference11.pkl) - -x | typealias Ref = ref.Reference - ^^^^^^^^^^^^^^^^^^^ -at reference11#Ref (file:///$snippetsDir/input/errors/reference11.pkl) - -x | test = ref.Reference(d, String, "") as Ref - ^^^^^^^^^^^ -at reference11#test (file:///$snippetsDir/input/errors/reference11.pkl) - -xxx | renderer.renderDocument(value) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -at pkl.base#Module.output.text (pkl:base) - -xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8") - ^^^^ -at pkl.base#Module.output.bytes (pkl:base) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference12.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference12.err deleted file mode 100644 index b14383b5f..000000000 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference12.err +++ /dev/null @@ -1,18 +0,0 @@ -–– Pkl Error –– -`Reference` referent type argument may not include type constraints. - -xx | typealias Alias2 = String(length < 5) - ^^^^^^^^^^^^^^^^^^ -at reference12#Alias2 (file:///$snippetsDir/input/errors/reference12.pkl) - -x | typealias RefAlias1 = ref.Reference - ^^^^^^^^^^^^^^^^^^^^^^^^^ -at reference12#RefAlias1 (file:///$snippetsDir/input/errors/reference12.pkl) - -xxx | renderer.renderDocument(value) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -at pkl.base#Module.output.text (pkl:base) - -xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8") - ^^^^ -at pkl.base#Module.output.bytes (pkl:base) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference13.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference13.err index 93ab1a44f..a12b25b05 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference13.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference13.err @@ -1,8 +1,8 @@ –– Pkl Error –– Cannot find property `default` in object of type `pkl.ref#Reference`. -x | test = ref.Reference(d, Mapping, "").default - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +x | test = ref.Reference::(d, "").default + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ at reference13#test (file:///$snippetsDir/input/errors/reference13.pkl) Cannot reference property `default` in class `Mapping`. diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference14.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference14.err index 4b2d09c29..4e8004dbb 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference14.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference14.err @@ -1,8 +1,8 @@ –– Pkl Error –– Cannot find property `default` in object of type `pkl.ref#Reference`. -x | test = ref.Reference(d, Listing, "").default - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +x | test = ref.Reference::(d, "").default + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ at reference14#test (file:///$snippetsDir/input/errors/reference14.pkl) Cannot reference property `default` in class `Listing`. diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference15.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference15.err index d46c8ad1f..99403efe8 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference15.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference15.err @@ -1,8 +1,8 @@ –– Pkl Error –– Cannot find property `default` in object of type `pkl.ref#Reference`. -x | test = ref.Reference(d, Dynamic, "").default - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +x | test = ref.Reference::(d, "").default + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ at reference15#test (file:///$snippetsDir/input/errors/reference15.pkl) Cannot reference property `default` in class `Dynamic`. diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference16.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference16.err index 01c7b2fde..d13fb50de 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference16.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference16.err @@ -1,8 +1,8 @@ –– Pkl Error –– Cannot find property `output` in object of type `pkl.ref#Reference`. -xx | test = ref.Reference(d, ReferencedModule.getClass(), "").output - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +xx | test = ref.Reference::(d, "").output + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ at reference16#test (file:///$snippetsDir/input/errors/reference16.pkl) Cannot reference property `output` because it is defined inside external class `Module`. diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference17.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference17.err index b04be664a..6cb83f716 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference17.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference17.err @@ -1,8 +1,8 @@ –– Pkl Error –– Cannot find property `output` in object of type `pkl.ref#Reference`. -xx | test = ref.Reference(d, ReferencedModuleWithOutputOverride.getClass(), "").output - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +xx | test = ref.Reference::(d, "").output + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ at reference17#test (file:///$snippetsDir/input/errors/reference17.pkl) Cannot reference property `output` because it is defined inside external class `Module`. diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference18.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference18.err index be4744eef..0e82888d7 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference18.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference18.err @@ -1,8 +1,8 @@ –– Pkl Error –– Cannot find property `output` in object of type `pkl.ref#Reference`. -xx | test = ref.Reference(d, ModuleSubclass, "").output - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +xx | test = ref.Reference::(d, "").output + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ at reference18#test (file:///$snippetsDir/input/errors/reference18.pkl) Cannot reference property `output` because it is defined inside external class `Module`. diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference19.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference19.err index 057797809..7fb0dae99 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference19.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference19.err @@ -2,8 +2,8 @@ Expected value of type `pkl.ref#Reference`, but got type `pkl.ref#Reference`. Value: Reference(new reference19#D {}, Boolean | Int | String, "").foo -xx | test = ref.Reference(d, A, "").foo as ref.Reference - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +xx | test = ref.Reference::(d, "").foo as ref.Reference + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ at reference19#test (file:///$snippetsDir/input/errors/reference19.pkl) xxx | renderer.renderDocument(value) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference2.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference2.err index adbffbceb..5d0132e0a 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference2.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference2.err @@ -6,8 +6,8 @@ x | typealias Ref = ref.Reference ^^^^^^^^^^^^^^^^^^^ at reference2#test (file:///$snippetsDir/input/errors/reference2.pkl) -x | test = ref.Reference(d, String, "") as Ref - ^^^^^^^^^^^^^^^^^^ +x | test = ref.Reference::(d, "") as Ref + ^^^^^^^^^^^^^^^^^^ at reference2#test (file:///$snippetsDir/input/errors/reference2.pkl) xxx | renderer.renderDocument(value) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference21.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference21.err deleted file mode 100644 index 681349cee..000000000 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference21.err +++ /dev/null @@ -1,14 +0,0 @@ -–– Pkl Error –– -`Reference` referent type argument may not include type constraints. - -x | typealias Ref = ref.Reference - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -at reference21#Ref (file:///$snippetsDir/input/errors/reference21.pkl) - -x | typealias Ref2 = Ref - ^^^^^^ -at reference21#Ref2 (file:///$snippetsDir/input/errors/reference21.pkl) - -x | foo: Ref2 - ^^^^^^^^^^^^^^^^^^^^^ -at reference21 (file:///$snippetsDir/input/errors/reference21.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference22.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference22.err deleted file mode 100644 index fe0507e48..000000000 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference22.err +++ /dev/null @@ -1,14 +0,0 @@ -–– Pkl Error –– -`Reference` referent type argument may not include type constraints. - -x | typealias Ref = ref.Reference - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -at reference22#Ref (file:///$snippetsDir/input/errors/reference22.pkl) - -x | typealias Ref2 = Ref - ^^^^^^^^^^^^^^^ -at reference22#Ref2 (file:///$snippetsDir/input/errors/reference22.pkl) - -x | foo: Ref2 - ^^^^ -at reference22 (file:///$snippetsDir/input/errors/reference22.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference23.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference23.err deleted file mode 100644 index 2ea4f4c17..000000000 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference23.err +++ /dev/null @@ -1,10 +0,0 @@ -–– Pkl Error –– -`Reference` referent type argument may not include type constraints. - -x | typealias Foo2 = String(isEmpty) - ^^^^^^^^^^^^^^^ -at reference23#Foo2 (file:///$snippetsDir/input/errors/reference23.pkl) - -x | foo: ref.Reference - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -at reference23 (file:///$snippetsDir/input/errors/reference23.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference25.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference25.err index dc5e60341..87b9bf796 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference25.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference25.err @@ -1,10 +1,10 @@ –– Pkl Error –– Operator `[]` is not defined for operand types `pkl.ref#Reference>` and `Int`. -Left operand : Reference(new reference25#D {}, Duration | Listing, null).$ +Left operand : Reference(new reference25#D {}, Duration | Listing, null) Right operand: 0 -xx | res = myRef[0] - ^^^^^^^^ +x | res = myRef[0] + ^^^^^^^^ at reference25#res (file:///$snippetsDir/input/errors/reference25.pkl) Operator `[]` is not defined for operand types `Duration | Listing` and `Int`. diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference27.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference27.err index 0f60c0e6e..978c9097c 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference27.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference27.err @@ -1,8 +1,8 @@ –– Pkl Error –– Cannot find property `name` in object of type `pkl.ref#Reference Boolean>`. -xx | res = myRef.name - ^^^^^^^^^^ +x | res = myRef.name + ^^^^^^^^^^ at reference27#res (file:///$snippetsDir/input/errors/reference27.pkl) Cannot find property `name` in type `(Int) -> Boolean`. diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference3.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference3.err index 97a1c30d2..58ee5ec48 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference3.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference3.err @@ -6,8 +6,8 @@ x | typealias Ref = ref.Reference ^^^^^^^^^^^^^^^^^^^ at reference3#test (file:///$snippetsDir/input/errors/reference3.pkl) -x | test = ref.Reference(d, String, "") as Ref> - ^^^^^^^^^^^^^^^^^^^^ +x | test = ref.Reference::(d, "") as Ref> + ^^^^^^^^^^^^^^^^^^^^ at reference3#test (file:///$snippetsDir/input/errors/reference3.pkl) xxx | renderer.renderDocument(value) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference4.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference4.err index 71ab9b99a..eb826b1d7 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference4.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference4.err @@ -6,8 +6,8 @@ x | typealias Ref = ref.Reference ^^^^^^^^^^^^^^^^^^^ at reference4#test (file:///$snippetsDir/input/errors/reference4.pkl) -x | test = ref.Reference(d, String, "") as Ref - ^^^^^^^^^^^ +x | test = ref.Reference::(d, "") as Ref + ^^^^^^^^^^^ at reference4#test (file:///$snippetsDir/input/errors/reference4.pkl) xxx | renderer.renderDocument(value) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference5.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference5.err index 134ce5efd..f3ee1c67d 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference5.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference5.err @@ -6,8 +6,8 @@ x | typealias Ref = ref.Reference ^^^^^^^^^^^^^^^^^^^ at reference5#test (file:///$snippetsDir/input/errors/reference5.pkl) -xx | test = ref.Reference(d2, String, "") as Ref - ^^^^^^^^^^^ +xx | test = ref.Reference::(d2, "") as Ref + ^^^^^^^^^^^ at reference5#test (file:///$snippetsDir/input/errors/reference5.pkl) xxx | renderer.renderDocument(value) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference6.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference6.err index 4312d1351..0e360c3b0 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference6.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference6.err @@ -1,8 +1,8 @@ –– Pkl Error –– Cannot find property `c` in object of type `pkl.ref#Reference`. -xx | test = ref.Reference(d, String, new A {}).c - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +xx | test = ref.Reference::(d, new A {}).c + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ at reference6#test (file:///$snippetsDir/input/errors/reference6.pkl) Cannot find property `c` in type `String`. diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference7.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference7.err index b3bd5e6f7..37612a1a7 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference7.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference7.err @@ -3,8 +3,8 @@ Operator `[]` is not defined for operand types `pkl.ref#Reference(d, List())["hi"] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ at reference7#test (file:///$snippetsDir/input/errors/reference7.pkl) Operator `[]` is not defined for operand types `List` and `String`. diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference8.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference8.err deleted file mode 100644 index 0202ab797..000000000 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference8.err +++ /dev/null @@ -1,18 +0,0 @@ -–– Pkl Error –– -`Reference` referent type argument may not include type constraints. - -x | typealias Ref = ref.Reference - ^^^^^^^^^^^^^^^^^^^ -at reference8#Ref (file:///$snippetsDir/input/errors/reference8.pkl) - -x | test = ref.Reference(d, String, "") as Ref - ^^^^^^^^^^^^^^^^^ -at reference8#test (file:///$snippetsDir/input/errors/reference8.pkl) - -xxx | renderer.renderDocument(value) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -at pkl.base#Module.output.text (pkl:base) - -xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8") - ^^^^ -at pkl.base#Module.output.bytes (pkl:base) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference9.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference9.err deleted file mode 100644 index 4e48cfb79..000000000 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/reference9.err +++ /dev/null @@ -1,18 +0,0 @@ -–– Pkl Error –– -`Reference` referent type argument may not include type constraints. - -x | typealias Ref = ref.Reference - ^^^^^^^^^^^^^^^^^^^ -at reference9#Ref (file:///$snippetsDir/input/errors/reference9.pkl) - -x | test = ref.Reference(d, String, "") as Ref - ^^^^^^^^^^^^^^^^^^^^^^^ -at reference9#test (file:///$snippetsDir/input/errors/reference9.pkl) - -xxx | renderer.renderDocument(value) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -at pkl.base#Module.output.text (pkl:base) - -xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8") - ^^^^ -at pkl.base#Module.output.bytes (pkl:base) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/userDefinedTypeParameter1.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/userDefinedTypeParameter1.err index 7518b1af3..dca5f12fb 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/userDefinedTypeParameter1.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/userDefinedTypeParameter1.err @@ -1,5 +1,5 @@ –– Pkl Error –– -Only standard library members can have type parameters. +Only standard library classes can have type parameters. x | class Box { ^ diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/userDefinedTypeParameter2.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/userDefinedTypeParameter2.err deleted file mode 100644 index 8eb1297e8..000000000 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/userDefinedTypeParameter2.err +++ /dev/null @@ -1,6 +0,0 @@ -–– Pkl Error –– -Only standard library members can have type parameters. - -x | function doit(a: A): A = a - ^ -at userDefinedTypeParameter2#DoIt (file:///$snippetsDir/input/errors/userDefinedTypeParameter2.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/methods/callWithTypeArguments.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/methods/callWithTypeArguments.pcf new file mode 100644 index 000000000..7a7268dac --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/methods/callWithTypeArguments.pcf @@ -0,0 +1,77 @@ +directCall { + pair = Pair(List("foo"), Set("bar")) + pairSelf = "callWithTypeArguments" + pairErr = "Expected value of type `String`, but got type `Int`. Value: 0" + pairSelfErr = "Expected value of type `callWithTypeArguments`, but got type `List`. Value: List()" + listMap = List(2, 3, 1) + listMapErr = "Expected value of type `String`, but got type `Int`. Value: 2" + wrongTypeArgCount = "Expected 2 type arguments but got 3." + nonGenericMethod = "Expected 0 type arguments but got 1." +} +superCall { + true + true + true + true + false + false + false +} +objectMethod { + res = "Type constraint `length > num` violated. Value: \"hi\"" +} +lazyTypeCheck { + res1 = "Expected value of type `String`, but got type `Int`. Value: 5" + res2 = "Expected value of type `String`, but got type `Int`. Value: 5" + res3 = "Expected value of type `String`, but got type `Int`. Value: 5" + eager { + res = false + } + closure { + res = "Expected value of type `Duration`, but got type `String`. Value: \"Hello, world!\"" + } +} +typeDefaults { + res1 { + name = "bob" + } + res1a = "callWithTypeArguments#Person" + res2 { + new { + name = "bob" + } + } + res2a = "callWithTypeArguments#Person" + res3 { + new { + name = "bob" + } + } + res3a = "callWithTypeArguments#Person" + res3b = "Expected value of type `callWithTypeArguments#Person`, but got type `callWithTypeArguments#Bird`. Value: new callWithTypeArguments#Bird { name = ? }" +} +polymorphism { + res1 { + name = "fred" + } + res2 { + name = "fred" + } + res3 = "hi fred" + res4 = "Expected 1 type argument but got 2." + res5 = "hi bob" +} +typeCast { + res1 { + "Bobbert" + } + res2 = "Type constraint `length > 10` violated. Value: \"Bobbert\"" +} +constraintReadsFrameSlot { + res1 = true + res2 = true + res3 = true + res4 { + true + } +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/methods/callWithTypeArgumentsConstrainedType.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/methods/callWithTypeArgumentsConstrainedType.pcf new file mode 100644 index 000000000..ed281f859 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/methods/callWithTypeArgumentsConstrainedType.pcf @@ -0,0 +1,14 @@ +res { + res1a = true + res1b = true + res1c = true + res1d = true + res2a = true + res2b = true + res2c = true + res2d = true + res3a = true + res3b = true + res3c = true + res3d = true +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/methods/callWithTypeArgumentsLexical.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/methods/callWithTypeArgumentsLexical.pcf new file mode 100644 index 000000000..479220413 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/methods/callWithTypeArgumentsLexical.pcf @@ -0,0 +1,71 @@ +lexical { + res1 = "Expected value of type `String`, but got type `Int`. Value: 5" + res2 = "Expected value of type `String`, but got type `Int`. Value: 5" + nested { + res1 { + new { + name = "x" + } + } + res2 { + name = "x" + } + res3 { + new { + name = "x" + } + } + res4 { + name = "x" + } + res5 = "Expected value of type `callWithTypeArguments#Person`, but got type `Int`. Value: 1" + nested { + res1 { + name = "x" + } + res2 { + name = "x" + } + res3 = "Expected value of type `callWithTypeArguments#Person`, but got type `Int`. Value: 1" + } + } + inEagerScope { + "Expected value of type `Int`, but got type `String`. Value: \"key\"" + new { + ["key"] = "value" + ["keykey"] = "value" + ["keykeykey"] = "value" + } + } + reify { + res = "" + } +} +nested { + res1 { + new { + name = "x" + } + } + res2 { + name = "x" + } + res3 { + new { + name = "x" + } + } + res4 { + name = "x" + } + res5 = "Expected value of type `callWithTypeArguments#Person`, but got type `Int`. Value: 1" + nested { + res1 { + name = "x" + } + res2 { + name = "x" + } + res3 = "Expected value of type `callWithTypeArguments#Person`, but got type `Int`. Value: 1" + } +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/methods/callWithTypeArgumentsNestedWithOffset.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/methods/callWithTypeArgumentsNestedWithOffset.pcf new file mode 100644 index 000000000..ead6a55a9 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/methods/callWithTypeArgumentsNestedWithOffset.pcf @@ -0,0 +1 @@ +res = 1 diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/methods/callWithTypeArgumentsSelfType.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/methods/callWithTypeArgumentsSelfType.pcf new file mode 100644 index 000000000..afed169a4 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/methods/callWithTypeArgumentsSelfType.pcf @@ -0,0 +1,33 @@ +resAaA = List(true, true, true) +resAaB = List(true, true, true) +resAaC = List(true, true, true) +resBaA = List(true, true, false) +resBaB = List(true, true, true) +resBaC = List(true, true, true) +resBb1A = List(true, false, false) +resBb1B = List(true, true, true) +resBb1C = List(true, true, true) +resBb2A = List(true, false, false) +resBb2B = List(true, true, true) +resBb2C = List(true, true, true) +resCaA = List(true, true, false) +resCaB = List(true, true, false) +resCaC = List(true, true, true) +resCb1A = List(true, false, false) +resCb1B = List(true, false, false) +resCb1C = List(true, true, true) +resCb2A = List(true, false, false) +resCb2B = List(true, false, false) +resCb2C = List(true, true, true) +resCc1A = List(false, false, false) +resCc1B = List(false, false, false) +resCc1C = List(true, true, true) +resCc2A = List(false, false, false) +resCc2B = List(false, false, false) +resCc2C = List(true, true, true) +resCc3A = List(false, false, false) +resCc3B = List(false, false, false) +resCc3C = List(true, true, true) +resCc4A = List(false, false, false) +resCc4B = List(false, false, false) +resCc4C = List(true, true, true) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/methods/callWithTypeArgumentsStacktrace.err b/pkl-core/src/test/files/LanguageSnippetTests/output/methods/callWithTypeArgumentsStacktrace.err new file mode 100644 index 000000000..d68cabc08 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/methods/callWithTypeArgumentsStacktrace.err @@ -0,0 +1,30 @@ +–– Pkl Error –– +oops + +x | res = baz::(List("a")) + ^^^^^^^^^^^^^ +at callWithTypeArgumentsStacktrace#res. (file:///$snippetsDir/input/methods/callWithTypeArgumentsStacktrace.pkl) + +x | function baz(qux) = foo::>(qux) + ^ +at callWithTypeArgumentsStacktrace#baz. (file:///$snippetsDir/input/methods/callWithTypeArgumentsStacktrace.pkl) + +x | function foo(bar) = bar as T + ^ +at callWithTypeArgumentsStacktrace#foo (file:///$snippetsDir/input/methods/callWithTypeArgumentsStacktrace.pkl) + +x | function baz(qux) = foo::>(qux) + ^^^^^^^^^^^^^^^^^^^ +at callWithTypeArgumentsStacktrace#baz (file:///$snippetsDir/input/methods/callWithTypeArgumentsStacktrace.pkl) + +x | res = baz::(List("a")) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +at callWithTypeArgumentsStacktrace#res (file:///$snippetsDir/input/methods/callWithTypeArgumentsStacktrace.pkl) + +xxx | renderer.renderDocument(value) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +at pkl.base#Module.output.text (pkl:base) + +xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8") + ^^^^ +at pkl.base#Module.output.bytes (pkl:base) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/methods/callWithTypeArgumentsStacktrace2.err b/pkl-core/src/test/files/LanguageSnippetTests/output/methods/callWithTypeArgumentsStacktrace2.err new file mode 100644 index 000000000..efa6a3fa4 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/methods/callWithTypeArgumentsStacktrace2.err @@ -0,0 +1,19 @@ +–– Pkl Error –– +Expected value of type `Duration`, but got type `Int`. +Value: 5 + +x | res = bar::() + ^^^^^^^^ +at callWithTypeArgumentsStacktrace2#bar.prop (file:///$snippetsDir/input/methods/callWithTypeArgumentsStacktrace2.pkl) + +x | prop = foo as T + ^ +at callWithTypeArgumentsStacktrace2#bar.prop (file:///$snippetsDir/input/methods/callWithTypeArgumentsStacktrace2.pkl) + +xxx | renderer.renderDocument(value) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +at pkl.base#Module.output.text (pkl:base) + +xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8") + ^^^^ +at pkl.base#Module.output.bytes (pkl:base) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/methods/genericWithLocalOverride.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/methods/genericWithLocalOverride.pcf new file mode 100644 index 000000000..0198e4182 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/methods/genericWithLocalOverride.pcf @@ -0,0 +1 @@ +ok = true diff --git a/pkl-core/src/test/kotlin/org/pkl/core/EvaluatorTest.kt b/pkl-core/src/test/kotlin/org/pkl/core/EvaluatorTest.kt index c38cb64cd..a1b2123d3 100644 --- a/pkl-core/src/test/kotlin/org/pkl/core/EvaluatorTest.kt +++ b/pkl-core/src/test/kotlin/org/pkl/core/EvaluatorTest.kt @@ -788,6 +788,22 @@ class EvaluatorTest { exceptions.firstOrNull()?.let { throw it } } + @Test + fun `method call type args are obeyed for stdlib methods when test mode is off`() { + val evaluator = Evaluator.preconfigured() + assertThatCode { + evaluator.evaluateOutputText( + text( + """ + res = List(1, 2, 3).map::((it) -> it + 1) + """ + .trimIndent() + ) + ) + } + .hasMessageContaining("Expected value of type `String`, but got type `Int`.") + } + private fun checkModule(module: PModule) { assertThat(module.properties.size).isEqualTo(2) assertThat(module.getProperty("name")).isEqualTo("pigeon") diff --git a/pkl-core/src/test/kotlin/org/pkl/core/runtime/VmTypeTest.kt b/pkl-core/src/test/kotlin/org/pkl/core/runtime/VmTypeTest.kt index 530244f2d..c3b7b301f 100644 --- a/pkl-core/src/test/kotlin/org/pkl/core/runtime/VmTypeTest.kt +++ b/pkl-core/src/test/kotlin/org/pkl/core/runtime/VmTypeTest.kt @@ -444,7 +444,17 @@ class VmTypeTest { @Test fun `type variable - treated like unknown, supertype and subtype to all`() { - VmType.TypeVariableType(TypeParameter(TypeParameter.Variance.INVARIANT, "Foo", 0)) + VmType.TypeVariableType( + TypeParameter( + VmUtils.unavailableSourceSection(), + -1, + 0, + "Foo", + TypeParameter.Variance.INVARIANT, + ), + org.pkl.core.runtime.TypeParameter.OwnerType.METHOD, + 0, + ) .bidi(stringClass) } } diff --git a/pkl-formatter/src/main/java/org/pkl/formatter/Builder.java b/pkl-formatter/src/main/java/org/pkl/formatter/Builder.java index 1db2f0316..6f6242284 100644 --- a/pkl-formatter/src/main/java/org/pkl/formatter/Builder.java +++ b/pkl-formatter/src/main/java/org/pkl/formatter/Builder.java @@ -261,6 +261,11 @@ final class Builder { * {@code foo.bar.baz( * new { qux = 1 } * )} + * {@code foo.bar.baz::< + * Quux + * >( + * new { qux = 1 } + * )} * * *

Case 2: Dot calls, then method calls: group the leading access together. @@ -306,15 +311,14 @@ final class Builder { // lift argument list into its own node var splitResult = splitFunctionCallNode(flat); var callChain = splitResult[0]; - var argsList = splitResult[1]; + var typeArgList = splitResult[1]; + var argsList = splitResult[2]; var leadingNodes = indentAfterFirstNewline(formatGeneric(callChain, leadingSeparator), true); + List middleNodes = + typeArgList.isEmpty() ? null : formatGeneric(typeArgList, trailingSeparator); var trailingNodes = formatGeneric(argsList, trailingSeparator); var sep = getBaseSeparator(callChain.get(callChain.size() - 1), argsList.get(0)); - if (sep != null) { - nodes = concat(leadingNodes, sep, trailingNodes); - } else { - nodes = concat(leadingNodes, trailingNodes); - } + nodes = concat(leadingNodes, sep, middleNodes, sep, trailingNodes); } else if (methodCallCount[0] > 0 && indexBeforeFirstMethodCall[0] > 0) { var leading = flat.subList(0, indexBeforeFirstMethodCall[0]); var trailing = flat.subList(indexBeforeFirstMethodCall[0], flat.size()); @@ -360,17 +364,26 @@ final class Builder { private List[] splitFunctionCallNode(List nodes) { assert !nodes.isEmpty(); var lastNode = nodes.get(nodes.size() - 1); + var typeArgListIdx = -1; var argListIdx = -1; for (var i = 0; i < lastNode.children.size(); i++) { - if (lastNode.children.get(i).type == NodeType.ARGUMENT_LIST) { + var node = lastNode.children.get(i); + if (node.type == NodeType.DCOLON) { + typeArgListIdx = i; + continue; + } + if (node.type == NodeType.ARGUMENT_LIST) { argListIdx = i; break; } } + var leading = new ArrayList<>(nodes.subList(0, nodes.size() - 1)); - leading.addAll(lastNode.children.subList(0, argListIdx)); + leading.addAll(lastNode.children.subList(0, typeArgListIdx < 0 ? argListIdx : typeArgListIdx)); + var middle = + typeArgListIdx < 0 ? List.of() : lastNode.children.subList(typeArgListIdx, argListIdx); var trailing = lastNode.children.subList(argListIdx, lastNode.children.size()); - return new List[] {leading, trailing}; + return new List[] {leading, middle, trailing}; } private static boolean isMethodCall(@Nullable Node node) { @@ -1718,14 +1731,39 @@ final class Builder { return result; } - private static List concat(List a, T elem, List b) { + private static List concat(List a, @Nullable T elem, List b) { var result = new ArrayList(a.size() + 1 + b.size()); result.addAll(a); - result.add(elem); + if (elem != null) { + result.add(elem); + } result.addAll(b); return result; } + private static List concat( + List a, @Nullable T elem, @Nullable List b, @Nullable T elem2, List c) { + var result = + new ArrayList( + a.size() + + (elem != null ? 1 : 0) + + (b != null ? b.size() : 0) + + (elem2 != null ? 1 : 0) + + c.size()); + result.addAll(a); + if (elem != null) { + result.add(elem); + } + if (b != null) { + result.addAll(b); + if (elem2 != null) { + result.add(elem2); + } + } + result.addAll(c); + return result; + } + static final class PeekableIterator implements Iterator { private final Iterator iterator; private @Nullable T peeked; diff --git a/pkl-formatter/src/test/files/FormatterSnippetTests/input/method-call-type-args.pkl b/pkl-formatter/src/test/files/FormatterSnippetTests/input/method-call-type-args.pkl new file mode 100644 index 000000000..0e5fccf27 --- /dev/null +++ b/pkl-formatter/src/test/files/FormatterSnippetTests/input/method-call-type-args.pkl @@ -0,0 +1,14 @@ +res1 = foo::(baz) +res1a = foo::(baz, baz, reallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongArg) + +res2 = qux.foo::(baz, baz2) +res2a = qux.foo::(baz, baz, reallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongArg) +res2b = qux.quux::().foo::(baz, baz2) +res2c = qux.quux::().foo::(baz, baz, reallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongArg) +res2d = qux::().foo::(baz, baz2) +res2e = qux::().foo::(baz, baz, reallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongArg) +res2f = grault.qux.quux::().foo::(baz, baz2) +res2g = grault.qux.quux::().foo::(baz, baz, reallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongArg) + +res3 = super.foo::(baz, baz2) +res3a = super.foo::(baz, baz, reallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongArg) diff --git a/pkl-formatter/src/test/files/FormatterSnippetTests/output/method-call-type-args.pkl b/pkl-formatter/src/test/files/FormatterSnippetTests/output/method-call-type-args.pkl new file mode 100644 index 000000000..5b0fc4e60 --- /dev/null +++ b/pkl-formatter/src/test/files/FormatterSnippetTests/output/method-call-type-args.pkl @@ -0,0 +1,73 @@ +res1 = foo::(baz) +res1a = + foo::< + Bar, + Baz, + ReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongType, + >( + baz, + baz, + reallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongArg, + ) + +res2 = qux.foo::(baz, baz2) +res2a = + qux.foo::< + Bar, + Baz, + ReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongType, + >( + baz, + baz, + reallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongArg, + ) +res2b = qux.quux::().foo::(baz, baz2) +res2c = + qux + .quux::() + .foo::< + Bar, + Baz, + ReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongType, + >( + baz, + baz, + reallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongArg, + ) +res2d = qux::().foo::(baz, baz2) +res2e = + qux::() + .foo::< + Bar, + Baz, + ReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongType, + >( + baz, + baz, + reallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongArg, + ) +res2f = grault.qux.quux::().foo::(baz, baz2) +res2g = + grault.qux + .quux::() + .foo::< + Bar, + Baz, + ReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongType, + >( + baz, + baz, + reallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongArg, + ) + +res3 = super.foo::(baz, baz2) +res3a = + super.foo::< + Bar, + Baz, + ReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongType, + >( + baz, + baz, + reallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongArg, + ) diff --git a/pkl-parser/src/main/java/org/pkl/parser/GenericParserImpl.java b/pkl-parser/src/main/java/org/pkl/parser/GenericParserImpl.java index 93ec8793e..865b500ff 100644 --- a/pkl-parser/src/main/java/org/pkl/parser/GenericParserImpl.java +++ b/pkl-parser/src/main/java/org/pkl/parser/GenericParserImpl.java @@ -237,9 +237,10 @@ class GenericParserImpl { headers.add(makeTerminal(next())); ff(headers); headers.add(parseIdentifier()); - if (lookahead() == Token.LT) { + ff(headers); + if (lookahead == Token.LT) { + headers.add(parseTypeParameterList(true)); ff(headers); - headers.add(parseTypeParameterList()); } children.add(new Node(NodeType.TYPEALIAS_HEADER, headers)); ff(children); @@ -264,7 +265,7 @@ class GenericParserImpl { headers.add(parseIdentifier()); if (lookahead() == Token.LT) { ff(headers); - headers.add(parseTypeParameterList()); + headers.add(parseTypeParameterList(true)); } if (lookahead() == Token.EXTENDS) { var extend = new ArrayList(); @@ -361,7 +362,7 @@ class GenericParserImpl { children.add(new Node(NodeType.METHOD_HEADER, headers)); ff(children); if (lookahead == Token.LT) { - children.add(parseTypeParameterList()); + children.add(parseTypeParameterList(false)); ff(children); } children.add(parseParameterList()); @@ -533,7 +534,7 @@ class GenericParserImpl { children.add(new Node(NodeType.METHOD_HEADER, headers)); ff(children); if (lookahead == Token.LT) { - children.add(parseTypeParameterList()); + children.add(parseTypeParameterList(false)); ff(children); } children.add(parseParameterList()); @@ -738,12 +739,30 @@ class GenericParserImpl { private Node parseUnqualifiedAccessExpr() { var children = new ArrayList(); + parseAccess(children); + return new Node(NodeType.UNQUALIFIED_ACCESS_EXPR, children); + } + + private void parseAccess(List children) { children.add(parseIdentifier()); - if (lookahead() == Token.LPAREN && noSemicolonInbetween() && _lookahead.newLinesBetween == 0) { + if (lookahead() == Token.DCOLON && noSemicolonInbetween() && _lookahead.newLinesBetween == 0) { + children.add(makeTerminal(next())); + 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); + } + } else if (lookahead() == Token.LPAREN + && noSemicolonInbetween() + && _lookahead.newLinesBetween == 0) { ff(children); children.add(parseArgumentList()); } - return new Node(NodeType.UNQUALIFIED_ACCESS_EXPR, children); } private Node parseExprAtom(@Nullable String expectation) { @@ -843,11 +862,7 @@ class GenericParserImpl { if (lookahead == Token.DOT) { children.add(makeTerminal(next())); ff(children); - children.add(parseIdentifier()); - if (lookahead() == Token.LPAREN) { - ff(children); - children.add(parseArgumentList()); - } + parseAccess(children); yield new Node(NodeType.SUPER_ACCESS_EXPR, children); } else { expect(Token.LBRACK, children, "unexpectedToken", "["); @@ -1317,12 +1332,12 @@ class GenericParserImpl { return bodies; } - private Node parseTypeParameterList() { + private Node parseTypeParameterList(boolean allowVarianceModifiers) { var children = new ArrayList(); expect(Token.LT, children, "unexpectedToken", "<"); ff(children); var elements = new ArrayList(); - parseListOf(Token.GT, elements, this::parseTypeParameter); + parseListOf(Token.GT, elements, () -> parseTypeParameter(allowVarianceModifiers)); children.add(new Node(NodeType.TYPE_PARAMETER_LIST_ELEMENTS, elements)); expect(Token.GT, children, "unexpectedToken2", ",", ">"); return new Node(NodeType.TYPE_PARAMETER_LIST, children); @@ -1356,11 +1371,17 @@ class GenericParserImpl { return new Node(NodeType.ARGUMENT_LIST, children); } - private Node parseTypeParameter() { + private Node parseTypeParameter(boolean allowVarianceModifier) { var children = new ArrayList(); if (lookahead == Token.IN) { + if (!allowVarianceModifier) { + throw parserError("typeParameterVarianceNotAllowed"); + } children.add(makeTerminal(next())); } else if (lookahead == Token.OUT) { + if (!allowVarianceModifier) { + throw parserError("typeParameterVarianceNotAllowed"); + } children.add(makeTerminal(next())); } children.add(parseIdentifier()); @@ -1583,6 +1604,7 @@ class GenericParserImpl { case ASSIGN -> NodeType.ASSIGN; case AT -> NodeType.AT; case COLON -> NodeType.COLON; + case DCOLON -> NodeType.DCOLON; case COMMA -> NodeType.COMMA; case DOT -> NodeType.DOT; case GT -> NodeType.GT; diff --git a/pkl-parser/src/main/java/org/pkl/parser/Lexer.java b/pkl-parser/src/main/java/org/pkl/parser/Lexer.java index 802fca159..01556d0ab 100644 --- a/pkl-parser/src/main/java/org/pkl/parser/Lexer.java +++ b/pkl-parser/src/main/java/org/pkl/parser/Lexer.java @@ -137,7 +137,12 @@ public final class Lexer { case '}' -> Token.RBRACE; case ',' -> Token.COMMA; case '@' -> Token.AT; - case ':' -> Token.COLON; + case ':' -> { + if (lookahead == ':') { + nextChar(); + yield Token.DCOLON; + } else yield Token.COLON; + } case '+' -> Token.PLUS; case '%' -> Token.MOD; case '[' -> { diff --git a/pkl-parser/src/main/java/org/pkl/parser/ParserImpl.java b/pkl-parser/src/main/java/org/pkl/parser/ParserImpl.java index 7576e29bc..c262deb13 100644 --- a/pkl-parser/src/main/java/org/pkl/parser/ParserImpl.java +++ b/pkl-parser/src/main/java/org/pkl/parser/ParserImpl.java @@ -365,7 +365,7 @@ final class ParserImpl { var identifier = parseIdentifier(); TypeParameterList typePars = null; if (lookahead == Token.LT) { - typePars = parseTypeParameterList(); + typePars = parseTypeParameterList(true); } expect(Token.ASSIGN, "unexpectedToken", "="); var type = parseType(); @@ -398,7 +398,7 @@ final class ParserImpl { TypeParameterList typePars = null; var end = name.span(); if (lookahead == Token.LT) { - typePars = parseTypeParameterList(); + typePars = parseTypeParameterList(true); end = typePars.span(); } children.add(typePars); @@ -500,7 +500,7 @@ final class ParserImpl { children.add(name); TypeParameterList typePars = null; if (lookahead == Token.LT) { - typePars = parseTypeParameterList(); + typePars = parseTypeParameterList(false); } children.add(typePars); var parameterList = parseParameterList(); @@ -693,7 +693,7 @@ final class ParserImpl { var identifier = parseIdentifier(); TypeParameterList params = null; if (lookahead == Token.LT) { - params = parseTypeParameterList(); + params = parseTypeParameterList(false); } var args = parseParameterList(); TypeAnnotation typeAnnotation = null; @@ -831,16 +831,31 @@ 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(); + } 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 = new QualifiedAccessExpr( - expr, rhs, isNullable, argumentList, expr.span().endWith(lastSpan)); + expr, + rhs, + isNullable, + typeArgumentList, + argumentList, + expr.span().endWith(lastSpan)); } default -> { var nextMinPrec = op.isLeftAssoc() ? op.getPrec() + 1 : op.getPrec(); @@ -977,13 +992,24 @@ 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(); + } if (lookahead == Token.LPAREN && !precededBySemicolon && _lookahead.newLinesBetween == 0) { var args = parseArgumentList(); - yield new SuperAccessExpr(identifier, args, start.endWith(args.span())); + 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, start.endWith(identifier.span())); + yield new SuperAccessExpr(identifier, null, null, start.endWith(identifier.span())); } } else { expect(Token.LBRACK, "unexpectedToken", "["); @@ -1026,14 +1052,25 @@ 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(); + } if (lookahead == Token.LPAREN && !precededBySemicolon && _lookahead.newLinesBetween == 0) { var args = parseArgumentList(); yield new UnqualifiedAccessExpr( - identifier, args, identifier.span().endWith(args.span())); + 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, identifier.span()); + yield new UnqualifiedAccessExpr(identifier, null, null, identifier.span()); } } case EOF -> @@ -1072,14 +1109,27 @@ final class ParserImpl { 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(); + } 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(); var res = new QualifiedAccessExpr( - expr, identifier, isNullable, argumentList, expr.span().endWith(lastSpan)); + expr, + identifier, + isNullable, + typeArgumentList, + argumentList, + expr.span().endWith(lastSpan)); return parseExprRest(res); } // subscript (needs to be in the same line as the expression) @@ -1317,7 +1367,7 @@ final class ParserImpl { var paramList = new ParameterList(params, start.endWith(end)); yield new FunctionLiteralExpr(paramList, expr, start.endWith(expr.span())); } else { - var exp = new UnqualifiedAccessExpr(identifier, null, identifier.span()); + var exp = new UnqualifiedAccessExpr(identifier, null, null, identifier.span()); yield new ParenthesizedExpr(exp, start.endWith(end)); } } @@ -1532,9 +1582,9 @@ final class ParserImpl { return bodies; } - private TypeParameterList parseTypeParameterList() { + private TypeParameterList parseTypeParameterList(boolean allowVarianceModifiers) { var start = expect(Token.LT, "unexpectedToken", "<").span; - var pars = parseListOf(Token.COMMA, Token.GT, this::parseTypeParameter); + var pars = parseListOf(Token.COMMA, Token.GT, () -> parseTypeParameter(allowVarianceModifiers)); var end = expect(Token.GT, "unexpectedToken2", ",", ">").span; return new TypeParameterList(pars, start.endWith(end)); } @@ -1556,13 +1606,19 @@ final class ParserImpl { return new ArgumentList(exprs, start.endWith(end)); } - private TypeParameter parseTypeParameter() { + private TypeParameter parseTypeParameter(boolean allowVarianceModifier) { TypeParameter.Variance variance = null; var start = spanLookahead; if (lookahead == Token.IN) { + if (!allowVarianceModifier) { + throw parserError("typeParameterVarianceNotAllowed"); + } next(); variance = TypeParameter.Variance.IN; } else if (lookahead == Token.OUT) { + if (!allowVarianceModifier) { + throw parserError("typeParameterVarianceNotAllowed"); + } next(); variance = TypeParameter.Variance.OUT; } diff --git a/pkl-parser/src/main/java/org/pkl/parser/Token.java b/pkl-parser/src/main/java/org/pkl/parser/Token.java index dcdcfb672..ba0034eae 100644 --- a/pkl-parser/src/main/java/org/pkl/parser/Token.java +++ b/pkl-parser/src/main/java/org/pkl/parser/Token.java @@ -89,6 +89,7 @@ public enum Token { NOT, QUESTION, COLON, + DCOLON, ARROW, EQUAL, NOT_EQUAL, diff --git a/pkl-parser/src/main/java/org/pkl/parser/syntax/ClassMethod.java b/pkl-parser/src/main/java/org/pkl/parser/syntax/ClassMethod.java index 19bd7b880..956213623 100644 --- a/pkl-parser/src/main/java/org/pkl/parser/syntax/ClassMethod.java +++ b/pkl-parser/src/main/java/org/pkl/parser/syntax/ClassMethod.java @@ -20,7 +20,7 @@ import org.jspecify.annotations.Nullable; import org.pkl.parser.ParserVisitor; import org.pkl.parser.Span; -public final class ClassMethod extends AbstractNode { +public final class ClassMethod extends AbstractNode implements Method { private final int modifiersOffset; private final int nameOffset; private final Span headerSpan; diff --git a/pkl-parser/src/main/java/org/pkl/parser/syntax/Expr.java b/pkl-parser/src/main/java/org/pkl/parser/syntax/Expr.java index 50c1920e0..20ffc7904 100644 --- a/pkl-parser/src/main/java/org/pkl/parser/syntax/Expr.java +++ b/pkl-parser/src/main/java/org/pkl/parser/syntax/Expr.java @@ -291,8 +291,13 @@ public abstract sealed class Expr extends AbstractNode { public static final class UnqualifiedAccessExpr extends Expr { public UnqualifiedAccessExpr( - Identifier identifier, @Nullable ArgumentList argumentList, Span span) { - super(span, Arrays.asList(identifier, argumentList)); + Identifier identifier, + @Nullable TypeArgumentList typeArgumentList, + @Nullable ArgumentList argumentList, + Span span) { + super(span, Arrays.asList(identifier, typeArgumentList, argumentList)); + assert typeArgumentList == null || argumentList != null + : "UnqualifiedAccessExpr created with typeArgumentList but no argumentList"; } @Override @@ -306,8 +311,12 @@ public abstract sealed class Expr extends AbstractNode { return ret; } + public @Nullable TypeArgumentList getTypeArgumentList() { + return (TypeArgumentList) children.get(1); + } + public @Nullable ArgumentList getArgumentList() { - return (ArgumentList) children.get(1); + return (ArgumentList) children.get(2); } } @@ -318,9 +327,12 @@ public abstract sealed class Expr extends AbstractNode { Expr expr, Identifier identifier, boolean isNullable, + @Nullable TypeArgumentList typeArgumentList, @Nullable ArgumentList argumentList, Span span) { - super(span, Arrays.asList(expr, identifier, argumentList)); + super(span, Arrays.asList(expr, identifier, typeArgumentList, argumentList)); + assert typeArgumentList == null || argumentList != null + : "QualifiedAccessExpr created with typeArgumentList but no argumentList"; this.isNullable = isNullable; } @@ -345,14 +357,24 @@ public abstract sealed class Expr extends AbstractNode { return isNullable; } + public @Nullable TypeArgumentList getTypeArgumentList() { + return (TypeArgumentList) children.get(2); + } + public @Nullable ArgumentList getArgumentList() { - return (ArgumentList) children.get(2); + return (ArgumentList) children.get(3); } } public static final class SuperAccessExpr extends Expr { - public SuperAccessExpr(Identifier identifier, @Nullable ArgumentList argumentList, Span span) { - super(span, Arrays.asList(identifier, argumentList)); + public SuperAccessExpr( + Identifier identifier, + @Nullable TypeArgumentList typeArgumentList, + @Nullable ArgumentList argumentList, + Span span) { + super(span, Arrays.asList(identifier, typeArgumentList, argumentList)); + assert typeArgumentList == null || argumentList != null + : "SuperAccessExpr created with typeArgumentList but no argumentList"; } @Override @@ -366,8 +388,12 @@ public abstract sealed class Expr extends AbstractNode { return ret; } + public @Nullable TypeArgumentList getTypeArgumentList() { + return (TypeArgumentList) children.get(1); + } + public @Nullable ArgumentList getArgumentList() { - return (ArgumentList) children.get(1); + return (ArgumentList) children.get(2); } } diff --git a/pkl-parser/src/main/java/org/pkl/parser/syntax/Method.java b/pkl-parser/src/main/java/org/pkl/parser/syntax/Method.java new file mode 100644 index 000000000..d7ae560e0 --- /dev/null +++ b/pkl-parser/src/main/java/org/pkl/parser/syntax/Method.java @@ -0,0 +1,24 @@ +/* + * 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.syntax; + +import org.jspecify.annotations.Nullable; + +public interface Method { + @Nullable Expr getExpr(); + + @Nullable TypeAnnotation getTypeAnnotation(); +} diff --git a/pkl-parser/src/main/java/org/pkl/parser/syntax/ObjectMember.java b/pkl-parser/src/main/java/org/pkl/parser/syntax/ObjectMember.java index a88cfd94d..ef1688e18 100644 --- a/pkl-parser/src/main/java/org/pkl/parser/syntax/ObjectMember.java +++ b/pkl-parser/src/main/java/org/pkl/parser/syntax/ObjectMember.java @@ -84,7 +84,7 @@ public abstract sealed class ObjectMember extends AbstractNode { } } - public static final class ObjectMethod extends ObjectMember { + public static final class ObjectMethod extends ObjectMember implements Method { private final int identifierOffset; public ObjectMethod(List<@Nullable Node> nodes, int identifierOffset, Span span) { diff --git a/pkl-parser/src/main/java/org/pkl/parser/syntax/generic/NodeType.java b/pkl-parser/src/main/java/org/pkl/parser/syntax/generic/NodeType.java index f5e10923a..91ed1440c 100644 --- a/pkl-parser/src/main/java/org/pkl/parser/syntax/generic/NodeType.java +++ b/pkl-parser/src/main/java/org/pkl/parser/syntax/generic/NodeType.java @@ -52,6 +52,7 @@ public enum NodeType { ASSIGN(NodeKind.TERMINAL), AT(NodeKind.TERMINAL), COLON(NodeKind.TERMINAL), + DCOLON(NodeKind.TERMINAL), COMMA(NodeKind.TERMINAL), DOT(NodeKind.TERMINAL), GT(NodeKind.TERMINAL), diff --git a/pkl-parser/src/main/resources/org/pkl/parser/errorMessages.properties b/pkl-parser/src/main/resources/org/pkl/parser/errorMessages.properties index fa1a16d9e..3f0997f25 100644 --- a/pkl-parser/src/main/resources/org/pkl/parser/errorMessages.properties +++ b/pkl-parser/src/main/resources/org/pkl/parser/errorMessages.properties @@ -108,3 +108,6 @@ Documentation comments must be attached to modules, classes, typealiases, method wrongShebangPosition=\ 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. diff --git a/pkl-parser/src/test/kotlin/org/pkl/parser/ParserComparisonTest.kt b/pkl-parser/src/test/kotlin/org/pkl/parser/ParserComparisonTest.kt index 524b0b720..fe0e2d385 100644 --- a/pkl-parser/src/test/kotlin/org/pkl/parser/ParserComparisonTest.kt +++ b/pkl-parser/src/test/kotlin/org/pkl/parser/ParserComparisonTest.kt @@ -99,6 +99,8 @@ class ParserComparisonTest { "errors/invalidCharacterEscape.pkl", "errors/invalidCharacterEscape2.pkl", "errors/invalidUnicodeEscape.pkl", + "errors/invalidVarianceModifierClassMethod.pkl", + "errors/invalidVarianceModifierObjectMethod.pkl", "errors/letExpressionError3.pkl", "errors/unterminatedUnicodeEscape.pkl", "errors/keywordNotAllowedHere1.pkl", diff --git a/pkl-parser/src/test/kotlin/org/pkl/parser/SexpRenderer.kt b/pkl-parser/src/test/kotlin/org/pkl/parser/SexpRenderer.kt index fbf6c9423..1d10dac67 100644 --- a/pkl-parser/src/test/kotlin/org/pkl/parser/SexpRenderer.kt +++ b/pkl-parser/src/test/kotlin/org/pkl/parser/SexpRenderer.kt @@ -507,6 +507,10 @@ class SexpRenderer { buf.append('\n') buf.append(tab) buf.append("(identifier)") + if (expr.typeArgumentList !== null) { + buf.append('\n') + renderTypeArgumentList(expr.typeArgumentList!!) + } if (expr.argumentList !== null) { buf.append('\n') renderArgumentList(expr.argumentList!!) @@ -522,6 +526,10 @@ class SexpRenderer { buf.append('\n') buf.append(tab) buf.append("(identifier)") + if (expr.typeArgumentList !== null) { + buf.append('\n') + renderTypeArgumentList(expr.typeArgumentList!!) + } if (expr.argumentList !== null) { buf.append('\n') renderArgumentList(expr.argumentList!!) @@ -549,6 +557,10 @@ class SexpRenderer { buf.append('\n') buf.append(tab) buf.append("(identifier)") + if (expr.typeArgumentList !== null) { + buf.append('\n') + renderTypeArgumentList(expr.typeArgumentList!!) + } if (expr.argumentList !== null) { buf.append('\n') renderArgumentList(expr.argumentList!!) @@ -958,6 +970,7 @@ class SexpRenderer { buf.append("(identifier)") val tparList = method.typeParameterList if (tparList !== null) { + buf.append('\n') renderTypeParameterList(tparList) } buf.append('\n') diff --git a/stdlib/ref.pkl b/stdlib/ref.pkl index c7f9d0f3c..6793573a1 100644 --- a/stdlib/ref.pkl +++ b/stdlib/ref.pkl @@ -31,25 +31,16 @@ @Since { version = "0.32.0" } module pkl.ref -/// Creates a deferred [Reference] to a value of type [`class`] in the given [domain] and [data]. -/// -/// References may only be constructed for single, non-generic class types. -/// To create a reference to other types (generic classes, union, nullable, constrained, -/// typealiases, etc.), use a wrapper class with a property of the desired type. +/// Creates a deferred [Reference] to a value of type [Type] in the given [domain] and [data]. /// /// Example: /// ``` -/// myReference: Reference> = Reference(domain, Holder, data).$ -/// -/// class Holder { -/// $: Listing -/// } +/// myReference: Reference> = Reference::>(domain, data) /// ``` -external const function Reference( - domain: D(this is Domain), - `class`: Class, +external const function Reference( + domain: Domain(this is AbstractDomain), data: Any, -): Reference +): Reference /// References are an advanced API design tool that enables library authors to express /// domain-specific values, whose actual underlying values are not known to Pkl during evaluation. @@ -116,14 +107,14 @@ external const function Reference( /// /// ## Domain /// -/// The [Domain] parameter identifies the system that this reference exists inside. +/// The domain identifies the system that this reference exists inside. /// Additionally, it defines how references should be rendered when Pkl produces textual output. /// /// Example: /// ``` /// import "pkl:ref" /// -/// class MyDomain extends ref.Domain { +/// class MyDomain extends ref.AbstractDomain { /// // Define how this domain renders references to strings: /// function renderReference(reference: ref.Reference): String = /// reference.getData().toString() @@ -143,7 +134,7 @@ external const function Reference( /// ``` /// import "pkl:ref" /// -/// class Foo extends ref.Domain { +/// class Foo extends ref.AbstractDomain { /// function renderReference(reference: ref.Reference) = /// "{{ " /// + reference.getData().toString() @@ -162,7 +153,7 @@ external const function Reference( /// properties: Mapping /// } /// -/// local outputs: FooReference = ref.Reference(new Foo {}, Outputs, "outputs") +/// local outputs: FooReference = ref.Reference::(new Foo {}, "outputs") /// /// // Dot access; gives a `Reference` /// myFile = outputs.file @@ -184,9 +175,9 @@ external const function Reference( /// myFile = "{{ outputs/file }}" /// myProperty = "{{ outputs/properties[myProperty] }}" /// ``` -external class Reference { +external class Reference { /// Returns the domain this reference belongs to. - external function getDomain(): D + external function getDomain(): Domain /// Returns arbitrary data attached to the reference during creation. /// @@ -197,17 +188,17 @@ external class Reference { external function getPath(): List /// Returns a string representation of this reference using its domain's - /// [Domain.renderReference()] method. + /// [AbstractDomain.renderReference()] method. function toString(): String = getDomain().renderReference(this) } /// A configuration domain that a [Reference] may exist within. /// -/// Only references with the same [Domain] are compatible with each other. +/// Only references with the same [AbstractDomain] are compatible with each other. /// /// A domain also determines how [Reference] values are transformed into strings; see /// [renderReference()]. -abstract class Domain { +abstract class AbstractDomain { /// Determines how [Reference] instances are transformed to strings by [Reference.toString()] and /// string interpolation. abstract function renderReference(reference: Reference): String diff --git a/stdlib/syntax.pkl b/stdlib/syntax.pkl index 5466a4afc..68ddea3e3 100644 --- a/stdlib/syntax.pkl +++ b/stdlib/syntax.pkl @@ -305,6 +305,7 @@ typealias TerminalNodeType = | "assign" | "at" | "colon" + | "dcolon" | "comma" | "dot" | "gt"