Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 69 additions & 15 deletions pkl-core/src/main/java/org/pkl/core/ast/type/TypeNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import com.oracle.truffle.api.nodes.ExplodeLoop;
import com.oracle.truffle.api.nodes.LoopNode;
import com.oracle.truffle.api.source.SourceSection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
Expand All @@ -38,6 +39,7 @@
import org.pkl.core.PType;
import org.pkl.core.PType.StringLiteral;
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.builder.SymbolTable.CustomThisScope;
Expand Down Expand Up @@ -2138,7 +2140,13 @@ public ReferenceTypeNode(
this.domainTypeNode = domainTypeNode;
this.referentTypeNode = referentTypeNode;
this.getModuleNode = new GetModuleNode(sourceSection);
validateTypeArguments(sourceSection);
// A constraint written directly in this annotation's referent is reported against this
// annotation. Constraints introduced through a generic type alias's type argument are caught
// later.
if (findReferentConstraint() != null) {
CompilerDirectives.transferToInterpreter();
throw exceptionBuilder().evalError("invalidReferenceTypeAnnotationWithConstraint").build();
}
Comment thread
stackoverflow marked this conversation as resolved.
}

@Specialization
Expand Down Expand Up @@ -2173,26 +2181,68 @@ private Object doEval(VmReference value, VmTyped module) {
sourceSection, value, TypeNode.export(domainTypeNode), referentType);
}

public void validateTypeArguments(@Nullable SourceSection aliasSourceSection) {
// constraints may not be used in Reference type annotation referents
// walk the type and throw if any part of the referent is constrained

// TODO improve error message when this type node and/or referent constraint are behind type
// aliases
/**
* Type constraints may not appear in a {@code Reference}'s referent type argument. Walks the
* referent type and returns the source section of the first offending constraint, or {@code
* null} if the referent is constraint-free.
*/
public @Nullable SourceSection findReferentConstraint() {
var found = new SourceSection[1];
referentTypeNode.acceptTypeNode(
true,
(typeNode) -> {
if (typeNode instanceof ConstrainedTypeNode) {
CompilerDirectives.transferToInterpreter();
var err =
exceptionBuilder().evalError("invalidReferenceTypeAnnotationWithConstraint");
if (aliasSourceSection != null) {
err.withSourceSection(aliasSourceSection);
}
throw err.build();
found[0] = typeNode.getSourceSection();
return false;
}
return true;
});
return found[0];
Comment thread
stackoverflow marked this conversation as resolved.
Outdated
}

/**
* Builds the chain of stack frames that lead a type constraint into this {@code Reference}'s
* referent through one or more generic type aliases.
*/
public List<StackFrame> buildReferentConstraintStackFrames(VmTypeAlias outermostAlias) {
var aliasLayers = new ArrayList<TypeAliasTypeNode>();
for (var node = getParent(); node != null; node = node.getParent()) {
if (node instanceof TypeAliasTypeNode aliasNode) {
aliasLayers.add(aliasNode);
}
}

var frames = new ArrayList<StackFrame>(aliasLayers.size() + 1);
var annotationOwner =
aliasLayers.isEmpty() ? outermostAlias : aliasLayers.get(0).getTypeAlias();
frames.add(VmUtils.createStackFrame(getSourceSection(), annotationOwner.getQualifiedName()));

for (var i = 0; i < aliasLayers.size(); i++) {
var owner =
i + 1 < aliasLayers.size() ? aliasLayers.get(i + 1).getTypeAlias() : outermostAlias;
frames.add(
VmUtils.createStackFrame(
aliasLayers.get(i).getSourceSection(), owner.getQualifiedName()));
}
return frames;
}

/**
* Signals that a {@code Reference} reached through one or more generic type aliases has a
* constrained referent.
*/
public static final class ReferentConstraintException extends RuntimeException {
Comment thread
stackoverflow marked this conversation as resolved.
Outdated
private final List<StackFrame> leadingStackFrames;

public ReferentConstraintException(List<StackFrame> leadingStackFrames) {
super(null, null, false, false);
this.leadingStackFrames = leadingStackFrames;
}

/** Frames for the {@code Reference} annotation and each enclosing type alias layer. */
public List<StackFrame> getLeadingStackFrames() {
return leadingStackFrames;
}
}

@Fallback
Expand Down Expand Up @@ -2703,13 +2753,17 @@ public TypeAliasTypeNode(

this.typeAlias = typeAlias;
this.typeArgumentNodes = typeArgumentNodes;
aliasedTypeNode = typeAlias.instantiate(typeArgumentNodes, sourceSection);
aliasedTypeNode = typeAlias.instantiate(typeArgumentNodes);
}

public TypeNode getAliasedTypeNode() {
return aliasedTypeNode;
}

public VmTypeAlias getTypeAlias() {
return typeAlias;
}

@Override
public FrameSlotKind getFrameSlotKind() {
return aliasedTypeNode.getFrameSlotKind();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,15 @@ public TypeNode execute(VirtualFrame frame) {
for (var i = 0; i < argLength; i++) {
resolvedTypeArgumentNodes[i] = typeArgumentNodes[i].execute(frame);
}
return new TypeAliasTypeNode(sourceSection, typeAlias, resolvedTypeArgumentNodes);
try {
return new TypeAliasTypeNode(sourceSection, typeAlias, resolvedTypeArgumentNodes);
} catch (ReferenceTypeNode.ReferentConstraintException e) {
// a constraint reached a `Reference` referent through this generic alias.
var exception =
exceptionBuilder().evalError("invalidReferenceTypeAnnotationWithConstraint").build();
exception.setLeadingStackFrames(e.getLeadingStackFrames());
Comment thread
stackoverflow marked this conversation as resolved.
Outdated
throw exception;
}
}

var module = (VmTyped) baseType;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ private StackTraceGenerator(VmException exception) {
}

private List<StackFrame> capture() {
// frames that aren't part of the runtime call stack are
// shown ahead of the captured frames.
frames.addAll(exception.getLeadingStackFrames());

var truffleElements = TruffleStackTrace.getStackTrace(exception);
if (truffleElements.isEmpty()) {
addFrame(exception.getSourceSection(), exception.getMemberName());
Expand Down
13 changes: 13 additions & 0 deletions pkl-core/src/main/java/org/pkl/core/runtime/VmException.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public abstract class VmException extends AbstractTruffleException {
private final @Nullable SourceSection sourceSection;
private final @Nullable String memberName;
private final Map<CallTarget, StackFrame> insertedStackFrames;
private List<StackFrame> leadingStackFrames = List.of();
@Nullable private final BiConsumer<AnsiStringBuilder, Boolean> messageBuilder;
@Nullable protected BiConsumer<AnsiStringBuilder, Boolean> hintBuilder;

Expand Down Expand Up @@ -89,6 +90,18 @@ public final Map<CallTarget, StackFrame> getInsertedStackFrames() {
return insertedStackFrames;
}

/**
* Stack frames to prepend to the rendered stack trace, ahead of the captured frames. Used to show
* source locations that aren't part of the runtime call stack.
*/
public final List<StackFrame> getLeadingStackFrames() {
return leadingStackFrames;
}

public final void setLeadingStackFrames(List<StackFrame> leadingStackFrames) {
this.leadingStackFrames = leadingStackFrames;
}

public @Nullable BiConsumer<AnsiStringBuilder, Boolean> getMessageBuilder() {
return messageBuilder;
}
Expand Down
11 changes: 7 additions & 4 deletions pkl-core/src/main/java/org/pkl/core/runtime/VmTypeAlias.java
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,7 @@ public Frame getEnclosingFrame() {
}

@TruffleBoundary
public TypeNode instantiate(
TypeNode[] typeArgumentNodes, SourceSection typeAliasTypeNodeSourceSection) {
public TypeNode instantiate(TypeNode[] typeArgumentNodes) {
// Cloning the type node means that the entire type check remains within a single root node,
// which should be good for interpreted and compiled performance alike:
// * Fewer root nodes to call
Expand All @@ -203,8 +202,12 @@ public TypeNode instantiate(
});
clone.accept(
node -> {
if (node instanceof ReferenceTypeNode referenceTypeNode) {
referenceTypeNode.validateTypeArguments(typeAliasTypeNodeSourceSection);
Comment thread
stackoverflow marked this conversation as resolved.
if (node instanceof ReferenceTypeNode referenceTypeNode
&& referenceTypeNode.findReferentConstraint() != null) {
// A type argument supplied at the alias usage site introduced a constraint into this
// `Reference`'s referent.
throw new ReferenceTypeNode.ReferentConstraintException(
referenceTypeNode.buildReferentConstraintStackFrames(this));
}
return true;
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import "pkl:ref"

typealias Ref<T> = ref.Reference<ref.Domain, T>

typealias Ref2<T> = Ref<T>

foo: Ref2<String(isEmpty)>
Comment thread
stackoverflow marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
–– Pkl Error ––
`Reference` referent type argument may not include type constraints.

x | typealias Ref<T> = ref.Reference<D, T>
^^^^^^^^^^^^^^^^^^^
at reference10#Ref (file:///$snippetsDir/input/errors/reference10.pkl)

x | test = ref.Reference(d, String, "") as Ref<Listing<String(true)>>
^^^^^^^^^^^^^^^^^^^^^^^^^^
at reference10#test (file:///$snippetsDir/input/errors/reference10.pkl)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
–– Pkl Error ––
`Reference` referent type argument may not include type constraints.

x | typealias Ref<T> = ref.Reference<D, T>
^^^^^^^^^^^^^^^^^^^
at reference11#Ref (file:///$snippetsDir/input/errors/reference11.pkl)

x | test = ref.Reference(d, String, "") as Ref<Alias1?>
^^^^^^^^^^^^
at reference11#test (file:///$snippetsDir/input/errors/reference11.pkl)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
–– Pkl Error ––
`Reference` referent type argument may not include type constraints.

x | typealias Ref<T> = ref.Reference<ref.Domain, T>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at reference21#Ref (file:///$snippetsDir/input/errors/reference21.pkl)

x | typealias Ref2<T> = Ref<T>
^^^^^^
at reference21#Ref2 (file:///$snippetsDir/input/errors/reference21.pkl)

x | foo: Ref2<String(isEmpty)>
^^^^^^^^^^^^^^^^^^^^^
at reference21 (file:///$snippetsDir/input/errors/reference21.pkl)
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
–– Pkl Error ––
`Reference` referent type argument may not include type constraints.

x | typealias Ref<T> = ref.Reference<D, T>
^^^^^^^^^^^^^^^^^^^
at reference8#Ref (file:///$snippetsDir/input/errors/reference8.pkl)

x | test = ref.Reference(d, String, "") as Ref<String(true)>
^^^^^^^^^^^^^^^^^
at reference8#test (file:///$snippetsDir/input/errors/reference8.pkl)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
–– Pkl Error ––
`Reference` referent type argument may not include type constraints.

x | typealias Ref<T> = ref.Reference<D, T>
^^^^^^^^^^^^^^^^^^^
at reference9#Ref (file:///$snippetsDir/input/errors/reference9.pkl)

x | test = ref.Reference(d, String, "") as Ref<String(true) | Int>
^^^^^^^^^^^^^^^^^^^^^^^
at reference9#test (file:///$snippetsDir/input/errors/reference9.pkl)
Expand Down
Loading