Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
119 changes: 103 additions & 16 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 @@ -27,7 +27,10 @@
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.source.SourceSection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
Expand All @@ -38,6 +41,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 All @@ -54,6 +58,7 @@
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 {

Expand Down Expand Up @@ -2138,7 +2143,17 @@ public ReferenceTypeNode(
this.domainTypeNode = domainTypeNode;
this.referentTypeNode = referentTypeNode;
this.getModuleNode = new GetModuleNode(sourceSection);
validateTypeArguments(sourceSection);
// A type constraint anywhere in the referent is forbidden, including one reached through a
// type alias used in the referent.
var constraint = findReferentConstraint();
if (constraint != null) {
CompilerDirectives.transferToInterpreter();
throw exceptionBuilder()
.evalError("invalidReferenceTypeAnnotationWithConstraint")
.withLeadingStackFrames(
buildReferentConstraintFrames(constraint, getSourceSection(), null))
.build();
}
Comment thread
stackoverflow marked this conversation as resolved.
}

@Specialization
Expand Down Expand Up @@ -2173,26 +2188,69 @@ 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 anywhere in a {@code Reference}'s referent type argument.
* Walks the referent type and returns the first offending {@link ConstrainedTypeNode} , or
* {@code null} if the referent is constraint-free.
*/
public @Nullable ConstrainedTypeNode findReferentConstraint() {
var found = new MutableReference<@Nullable ConstrainedTypeNode>(null);
referentTypeNode.acceptTypeNode(
true,
(typeNode) -> {
if (typeNode instanceof ConstrainedTypeNode) {
CompilerDirectives.transferToInterpreter();
var err =
exceptionBuilder().evalError("invalidReferenceTypeAnnotationWithConstraint");
if (aliasSourceSection != null) {
err.withSourceSection(aliasSourceSection);
}
throw err.build();
if (typeNode instanceof ConstrainedTypeNode constrainedTypeNode) {
found.set(constrainedTypeNode);
return false;
}
return true;
});
return found.getOrNull();
}

/** Builds the frames to show ahead of an "invalid referent constraint" error. */
public static List<StackFrame> buildReferentConstraintFrames(
ConstrainedTypeNode constraintNode,
SourceSection usageSection,
@Nullable VmTypeAlias outermostAlias) {
var frames = new ArrayList<StackFrame>();
for (Node node = constraintNode; node != null; node = node.getParent()) {
if (!(node instanceof ConstrainedTypeNode
|| node instanceof TypeAliasTypeNode
|| node instanceof ReferenceTypeNode)) {
continue;
}
var section = node.getSourceSection();
//noinspection ConstantValue
if (section == null || !section.isAvailable() || isWithin(usageSection, section)) {
Comment thread
stackoverflow marked this conversation as resolved.
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} for a directly-used Reference).
*/
@SuppressWarnings("DataFlowIssue")
private static @Nullable VmTypeAlias ownerAlias(
Node node, @Nullable VmTypeAlias outermostAlias) {
var parent = NodeUtil.findParent(node, TypeAliasTypeNode.class);
//noinspection ConstantValue
if (parent != null) {
return parent.typeAlias;
}
return outermostAlias;
}
Comment thread
stackoverflow marked this conversation as resolved.

private static boolean isWithin(SourceSection outer, SourceSection inner) {
return inner.getSource().equals(outer.getSource())
&& inner.getCharIndex() >= outer.getCharIndex()
&& inner.getCharEndIndex() <= outer.getCharEndIndex();
}

@Fallback
Expand Down Expand Up @@ -2703,13 +2761,42 @@ public TypeAliasTypeNode(

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

/**
* Reports a forbidden type constraint that a type argument introduced into a {@code
* Reference}'s referent through this (generic) alias. The error is reported at this usage type
* expression, with leading frames for the constraint and every alias layer it passed through.
*/
private void checkReferentConstraints(VmTypeAlias outermostAlias) {
aliasedTypeNode.accept(
node -> {
if (node instanceof ReferenceTypeNode referenceTypeNode) {
var constraint = referenceTypeNode.findReferentConstraint();
if (constraint != null) {
CompilerDirectives.transferToInterpreter();
throw exceptionBuilder()
.evalError("invalidReferenceTypeAnnotationWithConstraint")
.withLeadingStackFrames(
ReferenceTypeNode.buildReferentConstraintFrames(
constraint, getSourceSection(), outermostAlias))
.build();
}
}
return true;
});
}

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 @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ public VmBugException(
@Nullable SourceSection sourceSection,
@Nullable String memberName,
@Nullable BiConsumer<AnsiStringBuilder, Boolean> hintBuilder,
Map<CallTarget, StackFrame> insertedStackFrames) {
Map<CallTarget, StackFrame> insertedStackFrames,
List<StackFrame> leadingStackFrames) {

super(
message,
Expand All @@ -51,7 +52,8 @@ public VmBugException(
sourceSection,
memberName,
hintBuilder,
insertedStackFrames);
insertedStackFrames,
leadingStackFrames);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ public VmEvalException(
@Nullable SourceSection sourceSection,
@Nullable String memberName,
@Nullable BiConsumer<AnsiStringBuilder, Boolean> hintBuilder,
Map<CallTarget, StackFrame> insertedStackFrames) {
Map<CallTarget, StackFrame> insertedStackFrames,
List<StackFrame> leadingStackFrames) {

super(
message,
Expand All @@ -50,6 +51,7 @@ public VmEvalException(
sourceSection,
memberName,
hintBuilder,
insertedStackFrames);
insertedStackFrames,
leadingStackFrames);
}
}
13 changes: 12 additions & 1 deletion 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 final List<StackFrame> leadingStackFrames;
@Nullable private final BiConsumer<AnsiStringBuilder, Boolean> messageBuilder;
@Nullable protected BiConsumer<AnsiStringBuilder, Boolean> hintBuilder;

Expand All @@ -48,7 +49,8 @@ public VmException(
@Nullable SourceSection sourceSection,
@Nullable String memberName,
@Nullable BiConsumer<AnsiStringBuilder, Boolean> hintBuilder,
Map<CallTarget, StackFrame> insertedStackFrames) {
Map<CallTarget, StackFrame> insertedStackFrames,
List<StackFrame> leadingStackFrames) {
super(message, cause, UNLIMITED_STACK_TRACE, location);
assert message != null || messageBuilder != null;
this.messageBuilder = messageBuilder;
Expand All @@ -58,6 +60,7 @@ public VmException(
this.sourceSection = sourceSection;
this.memberName = memberName;
this.insertedStackFrames = insertedStackFrames;
this.leadingStackFrames = leadingStackFrames;
this.hintBuilder = hintBuilder;
}

Expand Down Expand Up @@ -89,6 +92,14 @@ 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 @Nullable BiConsumer<AnsiStringBuilder, Boolean> getMessageBuilder() {
return messageBuilder;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ public String toString() {
private @Nullable Node location;
private @Nullable SourceSection sourceSection;
private @Nullable String memberName;
private List<StackFrame> leadingStackFrames = List.of();

public VmExceptionBuilder typeMismatch(Object value, VmClass expectedType) {
if (value instanceof VmNull) {
Expand Down Expand Up @@ -359,6 +360,15 @@ public VmExceptionBuilder withInsertedStackFrames(
return this;
}

/**
* Frames to show ahead of the captured stack trace (see {@link
* VmException#getLeadingStackFrames()}).
*/
public VmExceptionBuilder withLeadingStackFrames(List<StackFrame> leadingStackFrames) {
this.leadingStackFrames = leadingStackFrames;
return this;
}

public VmException build() {
if (message != null && messageBuilder != null) {
throw new IllegalStateException("Both message and messageBuilder are set");
Expand Down Expand Up @@ -387,7 +397,8 @@ public VmException build() {
sourceSection,
memberName,
hintBuilder,
effectiveInsertedStackFrames);
effectiveInsertedStackFrames,
leadingStackFrames);
case UNDEFINED_VALUE ->
new VmUndefinedValueException(
message,
Expand All @@ -401,7 +412,8 @@ public VmException build() {
memberName,
hintBuilder,
receiver,
effectiveInsertedStackFrames);
effectiveInsertedStackFrames,
leadingStackFrames);
case BUG ->
new VmBugException(
message,
Expand All @@ -414,7 +426,8 @@ public VmException build() {
sourceSection,
memberName,
hintBuilder,
effectiveInsertedStackFrames);
effectiveInsertedStackFrames,
leadingStackFrames);
case WRAPPED -> {
assert wrappedException != null;
yield new VmWrappedEvalException(
Expand All @@ -429,6 +442,7 @@ yield new VmWrappedEvalException(
memberName,
hintBuilder,
effectiveInsertedStackFrames,
leadingStackFrames,
wrappedException);
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public VmStackOverflowException(StackOverflowError e) {
null,
null,
null,
new HashMap<>());
new HashMap<>(),
List.of());
}
}
11 changes: 1 addition & 10 deletions pkl-core/src/main/java/org/pkl/core/runtime/VmTypeAlias.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
import org.pkl.core.ast.VmModifier;
import org.pkl.core.ast.type.TypeNode;
import org.pkl.core.ast.type.TypeNode.ConstrainedTypeNode;
import org.pkl.core.ast.type.TypeNode.ReferenceTypeNode;
import org.pkl.core.ast.type.TypeNode.TypeVariableNode;
import org.pkl.core.ast.type.TypeNode.UnknownTypeNode;
import org.pkl.core.util.LateInit;
Expand Down Expand Up @@ -178,8 +177,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 @@ -201,13 +199,6 @@ public TypeNode instantiate(
}
return true;
});
clone.accept(
node -> {
if (node instanceof ReferenceTypeNode referenceTypeNode) {
referenceTypeNode.validateTypeArguments(typeAliasTypeNodeSourceSection);
Comment thread
stackoverflow marked this conversation as resolved.
}
return true;
});

return clone;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ public VmUndefinedValueException(
@Nullable String memberName,
@Nullable BiConsumer<AnsiStringBuilder, Boolean> hintBuilder,
@Nullable Object receiver,
@Nullable Map<CallTarget, StackFrame> insertedStackFrames) {
@Nullable Map<CallTarget, StackFrame> insertedStackFrames,
List<StackFrame> leadingStackFrames) {

super(
message,
Expand All @@ -56,7 +57,8 @@ public VmUndefinedValueException(
sourceSection,
memberName,
hintBuilder,
insertedStackFrames == null ? Collections.emptyMap() : insertedStackFrames);
insertedStackFrames == null ? Collections.emptyMap() : insertedStackFrames,
leadingStackFrames);

this.receiver = receiver;
}
Expand Down
Loading
Loading