Add VmType intermediate representation for types (#1838)

This commit is contained in:
Jen Basch authored and GitHub committed 2026-09-22 08:26:03 -07:00
1 parent cbffd6c114
commit 7ed88e250b
36 files changed
+2162 -1478

No files matched your search

+2 -1
View File
@@ -12,6 +12,7 @@
<option name="REPORT_FIELDS" value="true" /> <option name="REPORT_FIELDS" value="true" />
</inspection_tool> </inspection_tool>
<inspection_tool class="ClassCanBeRecord" enabled="false" level="WEAK WARNING" enabled_by_default="false" /> <inspection_tool class="ClassCanBeRecord" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="ClassEscapesItsScope" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="CustomRegExpInspection" enabled="true" level="WARNING" enabled_by_default="true"> <inspection_tool class="CustomRegExpInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="myConfigurations"> <option name="myConfigurations">
<list> <list>
@@ -107,4 +108,4 @@
<inspection_tool class="StringToUpperWithoutLocale" enabled="true" level="WARNING" enabled_by_default="true" editorAttributes="WARNING_ATTRIBUTES" /> <inspection_tool class="StringToUpperWithoutLocale" enabled="true" level="WARNING" enabled_by_default="true" editorAttributes="WARNING_ATTRIBUTES" />
<inspection_tool class="dd497f47-d38f-3fab-9ed7-eabe699620c8" enabled="true" level="ERROR" enabled_by_default="true" editorAttributes="ERRORS_ATTRIBUTES" /> <inspection_tool class="dd497f47-d38f-3fab-9ed7-eabe699620c8" enabled="true" level="ERROR" enabled_by_default="true" editorAttributes="ERRORS_ATTRIBUTES" />
</profile> </profile>
</component> </component>
@@ -124,7 +124,7 @@ public abstract class SubscriptNode extends BinaryExpressionNode {
CompilerDirectives.transferToInterpreter(); CompilerDirectives.transferToInterpreter();
throw exceptionBuilder() throw exceptionBuilder()
.evalError( .evalError(
"operatorNotDefined2", getShortName(), reference.exportType(), VmUtils.getClass(key)) "operatorNotDefined2", getShortName(), reference.getType(), VmUtils.getClass(key))
.withProgramValue("Left operand", reference) .withProgramValue("Left operand", reference)
.withProgramValue("Right operand", key) .withProgramValue("Right operand", key)
.withHint(getReferenceHint(reference, err, key)) .withHint(getReferenceHint(reference, err, key))
@@ -41,9 +41,9 @@ public final class CheckIsAnnotationClassNode extends ExpressionNode {
typeNode = insert(unresolvedTypeNode.execute(frame)); typeNode = insert(unresolvedTypeNode.execute(frame));
unresolvedTypeNode = null; unresolvedTypeNode = null;
} }
var clazz = typeNode.getVmClass(); var clazz = typeNode.getType().getVmClass();
if (clazz != null && clazz.isSubclassOf(BaseModule.getAnnotationClass())) { if (clazz != null && clazz.isSubclassOf(BaseModule.getAnnotationClass())) {
return typeNode.getVmClass(); return clazz;
} }
CompilerDirectives.transferToInterpreter(); CompilerDirectives.transferToInterpreter();
@@ -57,7 +57,7 @@ public abstract class AbstractInferParentNode extends ExpressionNode {
} }
// try to produce a more specific error message than "cannotInstantiateType" // try to produce a more specific error message than "cannotInstantiateType"
var clazz = typeNode.getVmClass(); var clazz = typeNode.getType().getVmClass();
if (clazz != null) { if (clazz != null) {
VmUtils.checkIsInstantiable(clazz, typeNode); VmUtils.checkIsInstantiable(clazz, typeNode);
} }
@@ -24,7 +24,6 @@ import com.oracle.truffle.api.nodes.IndirectCallNode;
import com.oracle.truffle.api.nodes.NodeInfo; import com.oracle.truffle.api.nodes.NodeInfo;
import com.oracle.truffle.api.source.SourceSection; import com.oracle.truffle.api.source.SourceSection;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.pkl.core.PType;
import org.pkl.core.ast.ExpressionNode; import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.MemberLookupMode; import org.pkl.core.ast.MemberLookupMode;
import org.pkl.core.ast.member.ClassProperty; import org.pkl.core.ast.member.ClassProperty;
@@ -77,7 +76,7 @@ public abstract class ReadPropertyNode extends ExpressionNode {
case DEFAULT_MEMBER -> case DEFAULT_MEMBER ->
ErrorMessages.create( ErrorMessages.create(
"cannotReferenceDefaultProperty", "cannotReferenceDefaultProperty",
((PType.Class) err.getType()).getPClass().getSimpleName()); ((VmType.ClassType) err.getType()).getVmClass().getSimpleName());
case EXTERNAL_CLASS -> case EXTERNAL_CLASS ->
ErrorMessages.create( ErrorMessages.create(
"cannotReferencePropertyInExternalClass", propertyName, err.getType()); "cannotReferencePropertyInExternalClass", propertyName, err.getType());
@@ -92,7 +91,7 @@ public abstract class ReadPropertyNode extends ExpressionNode {
} catch (VmReferenceAccessError err) { } catch (VmReferenceAccessError err) {
CompilerDirectives.transferToInterpreter(); CompilerDirectives.transferToInterpreter();
throw exceptionBuilder() throw exceptionBuilder()
.evalError("cannotFindPropertyInObjectNoHint", propertyName, receiver.exportType()) .evalError("cannotFindPropertyInObjectNoHint", propertyName, receiver.getType())
.withHint(getReferenceErrorHint(receiver, err)) .withHint(getReferenceErrorHint(receiver, err))
.build(); .build();
} }
@@ -129,7 +129,7 @@ public final class ClassNode extends ExpressionNode {
try { try {
if (unresolvedSupertypeNode != null) { if (unresolvedSupertypeNode != null) {
var supertypeNode = unresolvedSupertypeNode.execute(frame); var supertypeNode = unresolvedSupertypeNode.execute(frame);
var superclass = supertypeNode.getVmClass(); var superclass = supertypeNode.getType().getVmClass();
checkSupertype(supertypeNode, superclass); checkSupertype(supertypeNode, superclass);
cachedClass.initSupertype(supertypeNode, superclass); cachedClass.initSupertype(supertypeNode, superclass);
@@ -31,6 +31,7 @@ import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.VmModifier; import org.pkl.core.ast.VmModifier;
import org.pkl.core.ast.type.TypeNode; import org.pkl.core.ast.type.TypeNode;
import org.pkl.core.runtime.*; import org.pkl.core.runtime.*;
import org.pkl.core.runtime.VmType.UnknownType;
import org.pkl.core.util.CollectionUtils; import org.pkl.core.util.CollectionUtils;
import org.pkl.core.util.Pair; import org.pkl.core.util.Pair;
@@ -86,6 +87,10 @@ public final class FunctionNode extends RegularMemberNode {
return parameterTypeNodes[idx]; return parameterTypeNodes[idx];
} }
public VmType getReturnType() {
return returnTypeNode == null ? UnknownType.INSTANCE : returnTypeNode.getType();
}
public @Nullable TypeNode getReturnTypeNode() { public @Nullable TypeNode getReturnTypeNode() {
return returnTypeNode; return returnTypeNode;
} }
File diff suppressed because it is too large. Load diff
@@ -198,13 +198,13 @@ public abstract class UnresolvedTypeNode extends PklNode {
case "Int16": case "Int16":
return new Int16TypeAliasTypeNode(); return new Int16TypeAliasTypeNode();
case "UInt16": case "UInt16":
return new UIntTypeAliasTypeNode(alias, 0x000000000000FFFFL); return new IntMaskSlotTypeNode(alias, 0x000000000000FFFFL);
case "Int32": case "Int32":
return new Int32TypeAliasTypeNode(); return new Int32TypeAliasTypeNode();
case "UInt32": case "UInt32":
return new UIntTypeAliasTypeNode(alias, 0x00000000FFFFFFFFL); return new IntMaskSlotTypeNode(alias, 0x00000000FFFFFFFFL);
case "UInt": case "UInt":
return new UIntTypeAliasTypeNode(alias, 0x7FFFFFFFFFFFFFFFL); return new IntMaskSlotTypeNode(alias, 0x7FFFFFFFFFFFFFFFL);
} }
} }
@@ -23,7 +23,6 @@ import com.oracle.truffle.api.source.SourceSection;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.pkl.core.PType;
import org.pkl.core.StackFrame; import org.pkl.core.StackFrame;
import org.pkl.core.ValueFormatter; import org.pkl.core.ValueFormatter;
import org.pkl.core.ast.type.TypeNode.UnionTypeNode; import org.pkl.core.ast.type.TypeNode.UnionTypeNode;
@@ -95,6 +94,7 @@ public abstract class VmTypeMismatchException extends ControlFlowException {
assert expectedType instanceof VmClass assert expectedType instanceof VmClass
|| expectedType instanceof VmTypeAlias || expectedType instanceof VmTypeAlias
|| expectedType instanceof VmType
|| expectedType instanceof String // string literal type || expectedType instanceof String // string literal type
|| expectedType instanceof Set; // union of string literal types || expectedType instanceof Set; // union of string literal types
@@ -153,13 +153,11 @@ public abstract class VmTypeMismatchException extends ControlFlowException {
} }
} }
var valueType =
actualValue instanceof VmReference ref ? ref.getType() : VmUtils.getClass(actualValue);
builder builder
.append( .append(ErrorMessages.createIndented("typeMismatch", indent, renderedType, valueType))
ErrorMessages.createIndented(
"typeMismatch",
indent,
renderedType,
VmUtils.getClass(actualValue).toPklString()))
.append("\n") .append("\n")
.append(indent) .append(indent)
.append("Value: ") .append("Value: ")
@@ -183,10 +181,10 @@ public abstract class VmTypeMismatchException extends ControlFlowException {
renderedExpected = "Class<" + expectedClass.toPklString() + ">"; renderedExpected = "Class<" + expectedClass.toPklString() + ">";
} }
public ClassType(SourceSection sourceSection, VmClass actualClass, PType expectedType) { public ClassType(SourceSection sourceSection, VmClass actualClass, VmType expectedType) {
super(sourceSection, actualClass); super(sourceSection, actualClass);
this.expectedClass = null; this.expectedClass = null;
renderedExpected = "Class<" + expectedType + ">"; renderedExpected = expectedType.toString();
} }
@Override @Override
@@ -400,44 +398,4 @@ public abstract class VmTypeMismatchException extends ControlFlowException {
return false; return false;
} }
} }
public static final class Reference extends VmTypeMismatchException {
private final PType expectedDomainType;
private final PType expectedReferentType;
public Reference(
SourceSection sourceSection,
VmReference actualValue,
PType expectedDomainType,
PType expectedReferentType) {
super(sourceSection, actualValue);
this.expectedDomainType = expectedDomainType;
this.expectedReferentType = expectedReferentType;
}
@Override
public void buildMessage(
AnsiStringBuilder builder, String indent, boolean withPowerAssertions) {
builder
.append(
ErrorMessages.createIndented(
"typeMismatch",
indent,
new PType.Class(
RefModule.getReferenceClass().export(),
expectedDomainType,
expectedReferentType),
((VmReference) actualValue).exportType()))
.append("\n")
.append(indent)
.append("Value: ")
.append(VmValueRenderer.singleLine(80 - indent.length()).render(actualValue));
}
@Override
protected Boolean hasHint() {
return false;
}
}
} }
@@ -25,6 +25,7 @@ import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@@ -175,12 +176,12 @@ public final class CommandSpecParser {
} }
if (!(optionsTypeNode instanceof TypeNode.UserClassTypeNode node)) { if (!(optionsTypeNode instanceof TypeNode.UserClassTypeNode node)) {
throw exceptionBuilder() throw exceptionBuilder()
.withSourceSection(optionsTypeNode.getSourceSection()) .withSourceSection(optionsPropertyTypeNode.getSourceSection())
.evalError( .evalError(
"commandOptionsTypeNotClass", optionsTypeNode.getSourceSection().getCharacters()) "commandOptionsTypeNotClass", optionsTypeNode.getSourceSection().getCharacters())
.build(); .build();
} }
var clazz = node.getVmClass(); var clazz = ((VmType.ClassType) node.getType()).getVmClass();
if (clazz.isAbstract()) { if (clazz.isAbstract()) {
throw exceptionBuilder() throw exceptionBuilder()
.withSourceSection(clazz.getHeaderSection()) .withSourceSection(clazz.getHeaderSection())
@@ -308,14 +309,14 @@ public final class CommandSpecParser {
// assert type is Boolean // assert type is Boolean
var typeInfo = resolveType(prop); var typeInfo = resolveType(prop);
if (!(typeInfo.getFirst() instanceof TypeNode.BooleanTypeNode)) { if (typeInfo.getFirst().getVmClass() != BaseModule.getBooleanClass()) {
throw exceptionBuilder() throw exceptionBuilder()
.withSourceSection(prop.getHeaderSection()) .withSourceSection(prop.getHeaderSection())
.evalError( .evalError(
"commandFlagInvalidType", "commandFlagInvalidType",
prop.getName(), prop.getName(),
"BooleanFlag", "BooleanFlag",
typeInfo.getFirst().getSourceSection().getCharacters(), typeInfo.getFirst().toString(),
"Boolean") "Boolean")
.build(); .build();
} }
@@ -342,7 +343,7 @@ public final class CommandSpecParser {
"commandFlagInvalidType", "commandFlagInvalidType",
prop.getName(), prop.getName(),
"CountedFlag", "CountedFlag",
typeInfo.getFirst().getSourceSection().getCharacters(), typeInfo.getFirst().toString(),
"Int") "Int")
.build(); .build();
} }
@@ -385,10 +386,10 @@ public final class CommandSpecParser {
} }
/** Unwrap nullables, constraints, and aliases and return Pair(underlying type, is nullable) */ /** Unwrap nullables, constraints, and aliases and return Pair(underlying type, is nullable) */
private Pair<TypeNode, Boolean> resolveType(ClassProperty prop) { private Pair<VmType, Boolean> resolveType(ClassProperty prop) {
var propertyTypeNode = prop.getTypeNode(); var propertyTypeNode = prop.getTypeNode();
if (propertyTypeNode != null) { if (propertyTypeNode != null) {
return resolveType(propertyTypeNode.getTypeNode()); return resolveType(propertyTypeNode.getTypeNode().getType());
} }
throw exceptionBuilder() throw exceptionBuilder()
.withSourceSection(prop.getHeaderSection()) .withSourceSection(prop.getHeaderSection())
@@ -397,23 +398,23 @@ public final class CommandSpecParser {
} }
/** Unwrap nullables, constraints, and aliases and return Pair(underlying type, is nullable) */ /** Unwrap nullables, constraints, and aliases and return Pair(underlying type, is nullable) */
private Pair<TypeNode, Boolean> resolveType(TypeNode typeNode) { private Pair<VmType, Boolean> resolveType(VmType type) {
var isNullable = false; var isNullable = false;
while (true) { while (true) {
if (typeNode instanceof TypeNode.NullableTypeNode nullableTypeNode) { if (type instanceof VmType.NullableType nullableType) {
isNullable = true; isNullable = true;
typeNode = nullableTypeNode.getElementTypeNode(); type = nullableType.getElementType();
} else if (typeNode instanceof TypeNode.ConstrainedTypeNode constrainedTypeNode) { } else if (type instanceof VmType.ConstrainedType constrainedType) {
typeNode = constrainedTypeNode.getChildTypeNode(); type = constrainedType.getBaseType();
} else if (typeNode instanceof TypeNode.TypeAliasTypeNode typeAliasTypeNode) { } else if (type instanceof VmType.AliasType aliasType) {
if (typeAliasTypeNode.getVmTypeAlias() == BaseModule.getCharTypeAlias()) break; if (aliasType.getVmTypeAlias() == BaseModule.getCharTypeAlias()) break;
typeNode = typeAliasTypeNode.getAliasedTypeNode(); type = aliasType.getAliasedType();
} else { } else {
break; break;
} }
} }
return Pair.of(typeNode, isNullable); return Pair.of(type, isNullable);
} }
// This sigil used to indicate that an option has a default value that is a non-constant expr. // This sigil used to indicate that an option has a default value that is a non-constant expr.
@@ -447,10 +448,12 @@ public final class CommandSpecParser {
// wrapped by a nullable, no default value // wrapped by a nullable, no default value
return null; return null;
} }
if (resolved.getFirst() instanceof TypeNode.UnionOfStringLiteralsTypeNode union if (resolved.getFirst() instanceof VmType.UnionType union
&& union.getUnionDefault() != null) { && union.getDefaultIndex() != -1
return union.getUnionDefault(); && union.isUnionOfStringLiterals()) {
} else if (resolved.getFirst() instanceof TypeNode.StringLiteralTypeNode literal) { return ((VmType.StringLiteralType) union.getElementTypes()[union.getDefaultIndex()])
.getLiteral();
} else if (resolved.getFirst() instanceof VmType.StringLiteralType literal) {
return literal.getLiteral(); return literal.getLiteral();
} }
} }
@@ -509,130 +512,151 @@ public final class CommandSpecParser {
public OptionBehavior resolve(ClassProperty prop, boolean requireExplicitDefault) { public OptionBehavior resolve(ClassProperty prop, boolean requireExplicitDefault) {
var resolved = resolveType(prop); var resolved = resolveType(prop);
var typeNode = resolved.getFirst(); var type = resolved.getFirst();
isNullable = resolved.getSecond(); isNullable = resolved.getSecond();
defaultValue = CommandSpecParser.this.getDefaultValue(prop, requireExplicitDefault); defaultValue = CommandSpecParser.this.getDefaultValue(prop, requireExplicitDefault);
resolve(prop, typeNode); resolve(prop, type);
return this; return this;
} }
private void resolve(ClassProperty prop, TypeNode typeNode) { private void resolve(ClassProperty prop, VmType type) {
if (resolvePrimitive(typeNode)) { if (resolvePrimitive(type)) {
return; return;
} }
if (typeNode instanceof TypeNode.ListingTypeNode listingTypeNode) { if (type instanceof VmType.ClassType ct) {
handleElement(listingTypeNode.getValueTypeNode(), prop); if (ct.getVmClass() == BaseModule.getListingClass()) {
if (multiple == null) multiple = true; handleElement(prop, ct);
if (all == null) if (multiple == null) {
all = multiple = true;
!multiple }
? this::allChooseLast if (all == null) {
: (values, workingDirUri) -> { all =
if (values.isEmpty()) return null; !multiple
var builder = new VmObjectBuilder(); ? this::allChooseLast
values.forEach(builder::addElement); : (values, workingDirUri) -> {
return builder.toListing(); if (values.isEmpty()) return null;
}; var builder = new VmObjectBuilder();
} else if (typeNode instanceof TypeNode.MappingTypeNode mappingTypeNode) { values.forEach(builder::addElement);
assert mappingTypeNode.getKeyTypeNode() != null; return builder.toListing();
handleEntry(mappingTypeNode.getKeyTypeNode(), mappingTypeNode.getValueTypeNode(), prop); };
if (multiple == null) multiple = true; }
if (all == null) } else if (ct.getVmClass() == BaseModule.getMappingClass()) {
all = handleEntry(prop, ct);
!multiple if (multiple == null) {
? this::allChooseLast multiple = true;
: (values, workingDirUri) -> { }
if (values.isEmpty()) return null; if (all == null) {
var builder = new VmObjectBuilder(); all =
values.forEach( !multiple
(entry) -> ? this::allChooseLast
builder.addEntry( : (values, workingDirUri) -> {
((VmPair) entry).getFirst(), ((VmPair) entry).getSecond())); if (values.isEmpty()) return null;
return builder.toMapping(); var builder = new VmObjectBuilder();
}; values.forEach(
} else if (typeNode instanceof TypeNode.ListTypeNode listTypeNode) { (entry) ->
handleElement(listTypeNode.getElementTypeNode(), prop); builder.addEntry(
if (multiple == null) multiple = true; ((VmPair) entry).getFirst(), ((VmPair) entry).getSecond()));
if (all == null) return builder.toMapping();
all = };
!multiple }
? this::allChooseLast } else if (ct.getVmClass() == BaseModule.getListClass()) {
: (values, workingDirUri) -> values.isEmpty() ? null : VmList.create(values); handleElement(prop, ct);
} else if (typeNode instanceof TypeNode.SetTypeNode setTypeNode) { if (multiple == null) {
handleElement(setTypeNode.getElementTypeNode(), prop); multiple = true;
if (multiple == null) multiple = true; }
if (all == null) if (all == null) {
all = all =
!multiple !multiple
? this::allChooseLast ? this::allChooseLast
: (values, workingDirUri) -> values.isEmpty() ? null : VmSet.create(values); : (values, workingDirUri) -> values.isEmpty() ? null : VmList.create(values);
} else if (typeNode instanceof TypeNode.MapTypeNode mapTypeNode) { }
handleEntry(mapTypeNode.getKeyTypeNode(), mapTypeNode.getValueTypeNode(), prop); } else if (ct.getVmClass() == BaseModule.getSetClass()) {
if (multiple == null) multiple = true; handleElement(prop, ct);
if (all == null) if (multiple == null) {
all = multiple = true;
!multiple }
? this::allChooseLast if (all == null) {
: (values, workingDirUri) -> { all =
if (values.isEmpty()) return null; !multiple
var builder = VmMap.builder(); ? this::allChooseLast
values.forEach( : (values, workingDirUri) -> values.isEmpty() ? null : VmSet.create(values);
(entry) -> }
builder.add(((VmPair) entry).getFirst(), ((VmPair) entry).getSecond())); } else if (ct.getVmClass() == BaseModule.getMapClass()) {
return builder.build(); handleEntry(prop, ct);
}; if (multiple == null) {
} else if (typeNode instanceof TypeNode.PairTypeNode pairTypeNode) { multiple = true;
handleEntry(pairTypeNode.getFirstTypeNode(), pairTypeNode.getSecondTypeNode(), prop); }
if (all == null) all = this::allChooseLast; if (all == null) {
if (multiple == null) multiple = false; all =
} else if (typeNode instanceof TypeNode.FinalClassTypeNode finalClassTypeNode !multiple
&& (finalClassTypeNode.getVmClass() == BaseModule.getListingClass() ? this::allChooseLast
|| finalClassTypeNode.getVmClass() == BaseModule.getMappingClass() : (values, workingDirUri) -> {
|| finalClassTypeNode.getVmClass() == BaseModule.getListClass() if (values.isEmpty()) return null;
|| finalClassTypeNode.getVmClass() == BaseModule.getSetClass() var builder = VmMap.builder();
|| finalClassTypeNode.getVmClass() == BaseModule.getMapClass() values.forEach(
|| finalClassTypeNode.getVmClass() == BaseModule.getPairClass())) { (entry) ->
// if a supported type is provided without type arguments builder.add(
throw exceptionBuilder() ((VmPair) entry).getFirst(), ((VmPair) entry).getSecond()));
.withSourceSection(prop.getHeaderSection()) return builder.build();
.evalError( };
"commandOptionUnsupportedType", }
prop.getName(), } else if (ct.getVmClass() == BaseModule.getPairClass()) {
"", handleEntry(prop, ct);
typeNode.getSourceSection().getCharacters()) if (all == null) {
.withHint( all = this::allChooseLast;
finalClassTypeNode.getVmClass().getSimpleName() }
+ " options must provide " if (multiple == null) {
+ switch (finalClassTypeNode.getVmClass().getTypeParameterCount()) { multiple = false;
case 1 -> "one type argument."; }
case 2 -> "two type arguments."; }
default -> throw PklBugException.unreachableCode(); }
})
.build(); if (each == null && all == null) {
} else if (each == null && all == null) {
// if another type and no transform functions are provided, that's an error // if another type and no transform functions are provided, that's an error
throw exceptionBuilder() throw unsupportedOptionNoTransform(prop, type);
.withSourceSection(prop.getHeaderSection())
.evalError(
"commandOptionUnsupportedType",
prop.getName(),
"",
typeNode.getSourceSection().getCharacters())
.withHint("Use a supported type or define a transformEach and/or transformAll function")
.build();
} else { } else {
// if we have at least one transform then allow the type and fill in reasonable defaults // if we have at least one transform then allow the type and fill in reasonable defaults
if (each == null) each = (rawValue, workingDirUri) -> rawValue; if (each == null) {
if (all == null) all = this::allChooseLast; each = (rawValue, workingDirUri) -> rawValue;
if (multiple == null) multiple = false; }
if (metavar == null) metavar = METAVAR_VALUE; if (all == null) {
all = this::allChooseLast;
}
if (multiple == null) {
multiple = false;
}
if (metavar == null) {
metavar = METAVAR_VALUE;
}
} }
} }
private VmException unsupportedOptionTypeArguments(ClassProperty prop, VmType.ClassType type) {
var typeArgCountText =
switch (type.getVmClass().getTypeParameterCount()) {
case 1 -> "one type argument.";
case 2 -> "two type arguments.";
default -> throw PklBugException.unreachableCode();
};
return exceptionBuilder()
.withSourceSection(prop.getHeaderSection())
.evalError("commandOptionUnsupportedType", prop.getName(), "", type.toString())
.withHint(type.getVmClass().getSimpleName() + " options must provide " + typeArgCountText)
.build();
}
private VmException unsupportedOptionNoTransform(ClassProperty prop, VmType type) {
return exceptionBuilder()
.withSourceSection(prop.getHeaderSection())
.evalError("commandOptionUnsupportedType", prop.getName(), "", type.toString())
.withHint("Use a supported type or define a transformEach and/or transformAll function")
.build();
}
private OptionBehavior resolveTypeArgument( private OptionBehavior resolveTypeArgument(
ClassProperty prop, TypeNode typeNode, String typeArgumentName) { ClassProperty prop, VmType type, String typeArgumentName) {
if (resolvePrimitive(typeNode)) { if (resolvePrimitive(type)) {
return this; return this;
} }
@@ -644,7 +668,7 @@ public final class CommandSpecParser {
"commandOptionUnsupportedType", "commandOptionUnsupportedType",
prop.getName(), prop.getName(),
typeArgumentName + " ", typeArgumentName + " ",
typeNode.getSourceSection().getCharacters()) type.toString())
.withHint("Use a supported type or define a transformEach and/or transformAll function") .withHint("Use a supported type or define a transformEach and/or transformAll function")
.build(); .build();
} else if (metavar == null) { } else if (metavar == null) {
@@ -656,138 +680,252 @@ public final class CommandSpecParser {
return this; return this;
} }
private boolean resolvePrimitive(TypeNode typeNode) { private boolean resolvePrimitive(VmType type) {
if (typeNode instanceof TypeNode.NumberTypeNode) { if (type instanceof VmType.ClassType ct) {
if (each == null) if (ct.getVmClass() == BaseModule.getNumberClass()) {
each = if (each == null) {
(rawValue, workingDirUri) -> { each =
try { (rawValue, workingDirUri) -> {
return Long.parseLong(rawValue); try {
} catch (NumberFormatException e) { return Long.parseLong(rawValue);
} catch (NumberFormatException e) {
try {
return Double.parseDouble(rawValue);
} catch (NumberFormatException e2) {
throw BadValue.invalid(rawValue, METAVAR_NUMBER);
}
}
};
}
if (all == null) {
all = this::allChooseLast;
}
if (multiple == null) {
multiple = false;
}
if (metavar == null) {
metavar = METAVAR_NUMBER;
}
return true;
} else if (ct.getVmClass() == BaseModule.getFloatClass()) {
if (each == null) {
each =
(rawValue, workingDirUri) -> {
try { try {
return Double.parseDouble(rawValue); return Double.parseDouble(rawValue);
} catch (NumberFormatException e2) { } catch (NumberFormatException e) {
throw BadValue.invalid(rawValue, METAVAR_NUMBER); throw BadValue.invalid(rawValue, METAVAR_FLOAT);
} }
} };
}; }
if (all == null) all = this::allChooseLast; if (all == null) {
if (multiple == null) multiple = false; all = this::allChooseLast;
if (metavar == null) metavar = METAVAR_NUMBER; }
return true; if (multiple == null) {
} else if (typeNode instanceof TypeNode.FloatTypeNode) { multiple = false;
if (each == null) }
each = if (metavar == null) {
(rawValue, workingDirUri) -> { metavar = METAVAR_FLOAT;
try { }
return Double.parseDouble(rawValue); return true;
} catch (NumberFormatException e) { } else if (ct.getVmClass() == BaseModule.getIntClass()) {
throw BadValue.invalid(rawValue, METAVAR_FLOAT); if (each == null) {
} each = eachLong(Long.MIN_VALUE, Long.MAX_VALUE, METAVAR_INT);
}; }
if (all == null) all = this::allChooseLast; if (all == null) {
if (multiple == null) multiple = false; all = this::allChooseLast;
if (metavar == null) metavar = METAVAR_FLOAT; }
return true; if (multiple == null) {
} else if (typeNode instanceof TypeNode.IntTypeNode) { multiple = false;
if (each == null) each = eachLong(Long.MIN_VALUE, Long.MAX_VALUE, METAVAR_INT); }
if (all == null) all = this::allChooseLast; if (metavar == null) {
if (multiple == null) multiple = false; metavar = METAVAR_INT;
if (metavar == null) metavar = METAVAR_INT; }
return true; return true;
} else if (typeNode instanceof TypeNode.Int8TypeAliasTypeNode) { } else if (ct.getVmClass() == BaseModule.getBooleanClass()) {
if (each == null) each = eachLong(Byte.MIN_VALUE, Byte.MAX_VALUE, METAVAR_INT8); if (each == null) {
if (all == null) all = this::allChooseLast; each =
if (multiple == null) multiple = false; (rawValue, workingDirUri) -> {
if (metavar == null) metavar = METAVAR_INT8; var value = rawValue.toLowerCase(Locale.ROOT);
return true; if (TRUE_VALUES.contains(value)) {
} else if (typeNode instanceof TypeNode.Int16TypeAliasTypeNode) { return true;
if (each == null) each = eachLong(Short.MIN_VALUE, Short.MAX_VALUE, METAVAR_INT16); } else if (FALSE_VALUES.contains(value)) {
if (all == null) all = this::allChooseLast; return false;
if (multiple == null) multiple = false; }
if (metavar == null) metavar = METAVAR_INT16; throw BadValue.invalid(rawValue, "boolean");
return true; };
} else if (typeNode instanceof TypeNode.Int32TypeAliasTypeNode) { }
if (each == null) each = eachLong(Integer.MIN_VALUE, Integer.MAX_VALUE, METAVAR_INT32); if (all == null) {
if (all == null) all = this::allChooseLast; all = this::allChooseLast;
if (multiple == null) multiple = false; }
if (metavar == null) metavar = METAVAR_INT32; if (multiple == null) {
return true; multiple = false;
} else if (typeNode instanceof TypeNode.UIntTypeAliasTypeNode uIntTypeAliasTypeNode) { }
var mask = uIntTypeAliasTypeNode.getMask(); if (metavar == null) {
if (all == null) all = this::allChooseLast; metavar = METAVAR_BOOLEAN;
if (multiple == null) multiple = false; }
if (mask == 0x000000000000FFFFL) { return true;
if (each == null) each = eachLong(0, 0x000000000000FFFFL, METAVAR_UINT16); } else if (ct.getVmClass() == BaseModule.getStringClass()) {
if (metavar == null) metavar = METAVAR_UINT16; if (each == null) {
} else if (mask == 0x00000000FFFFFFFFL) { each = (rawValue, workingDirUri) -> rawValue;
if (each == null) each = eachLong(0, 0x00000000FFFFFFFFL, METAVAR_UINT32); }
if (metavar == null) metavar = METAVAR_UINT32; if (all == null) {
} else { all = this::allChooseLast;
if (each == null) each = eachLong(0, Long.MAX_VALUE, METAVAR_UINT); }
if (metavar == null) metavar = METAVAR_UINT; if (multiple == null) {
multiple = false;
}
if (metavar == null) {
metavar = METAVAR_STRING;
}
return true;
} }
return true; } else if (type instanceof VmType.AliasType at) {
} else if (typeNode instanceof TypeNode.UInt8TypeAliasTypeNode) { if (at.getVmTypeAlias() == BaseModule.getInt8TypeAlias()) {
if (each == null) each = eachLong(0, 0x00000000000000FFL, METAVAR_UINT8); if (each == null) {
if (all == null) all = this::allChooseLast; each = eachLong(Byte.MIN_VALUE, Byte.MAX_VALUE, METAVAR_INT8);
if (multiple == null) multiple = false; }
if (metavar == null) metavar = METAVAR_UINT8; if (all == null) {
return true; all = this::allChooseLast;
} else if (typeNode instanceof TypeNode.BooleanTypeNode) { }
if (each == null) if (multiple == null) {
multiple = false;
}
if (metavar == null) {
metavar = METAVAR_INT8;
}
return true;
} else if (at.getVmTypeAlias() == BaseModule.getInt16TypeAlias()) {
if (each == null) {
each = eachLong(Short.MIN_VALUE, Short.MAX_VALUE, METAVAR_INT16);
}
if (all == null) {
all = this::allChooseLast;
}
if (multiple == null) {
multiple = false;
}
if (metavar == null) {
metavar = METAVAR_INT16;
}
return true;
} else if (at.getVmTypeAlias() == BaseModule.getInt32TypeAlias()) {
if (each == null) {
each = eachLong(Integer.MIN_VALUE, Integer.MAX_VALUE, METAVAR_INT32);
}
if (all == null) {
all = this::allChooseLast;
}
if (multiple == null) {
multiple = false;
}
if (metavar == null) {
metavar = METAVAR_INT32;
}
return true;
} else if (at.getVmTypeAlias() == BaseModule.getUInt8TypeAlias()) {
if (each == null) {
each = eachLong(0, 0x00000000000000FFL, METAVAR_UINT8);
}
if (all == null) {
all = this::allChooseLast;
}
if (multiple == null) {
multiple = false;
}
if (metavar == null) {
metavar = METAVAR_UINT8;
}
return true;
} else if (at.getVmTypeAlias() == BaseModule.getUInt16TypeAlias()) {
if (each == null) {
each = eachLong(0, 0x000000000000FFFFL, METAVAR_UINT16);
}
if (all == null) {
all = this::allChooseLast;
}
if (multiple == null) {
multiple = false;
}
if (metavar == null) {
metavar = METAVAR_UINT16;
}
} else if (at.getVmTypeAlias() == BaseModule.getUInt32TypeAlias()) {
if (each == null) {
each = eachLong(0, 0x00000000FFFFFFFFL, METAVAR_UINT32);
}
if (all == null) {
all = this::allChooseLast;
}
if (multiple == null) {
multiple = false;
}
if (metavar == null) {
metavar = METAVAR_UINT32;
}
} else if (at.getVmTypeAlias() == BaseModule.getUIntTypeAlias()) {
if (each == null) {
each = eachLong(0, Long.MAX_VALUE, METAVAR_UINT);
}
if (all == null) {
all = this::allChooseLast;
}
if (multiple == null) {
multiple = false;
}
if (metavar == null) {
metavar = METAVAR_UINT;
}
} else if (at.getVmTypeAlias() == BaseModule.getCharTypeAlias()) {
if (each == null) {
each =
(rawValue, workingDirUri) -> {
if (rawValue.length() != 1) throw BadValue.invalid(rawValue, METAVAR_CHAR);
return rawValue;
};
}
if (all == null) {
all = this::allChooseLast;
}
if (multiple == null) {
multiple = false;
}
if (metavar == null) {
metavar = METAVAR_CHAR;
}
return true;
}
} else if (type instanceof VmType.UnionType ut && ut.isUnionOfStringLiterals()) {
var choices =
Arrays.stream(ut.getElementTypes())
.map(it -> ((VmType.StringLiteralType) it).getLiteral())
.toList();
var choiceSet = new HashSet<>(choices);
if (each == null) {
each = each =
(rawValue, workingDirUri) -> { (rawValue, workingDirUri) -> {
var value = rawValue.toLowerCase(Locale.ROOT); if (!choiceSet.contains(rawValue)) {
if (TRUE_VALUES.contains(value)) {
return true;
} else if (FALSE_VALUES.contains(value)) {
return false;
}
throw BadValue.invalid(rawValue, "boolean");
};
if (all == null) all = this::allChooseLast;
if (multiple == null) multiple = false;
if (metavar == null) metavar = METAVAR_BOOLEAN;
return true;
} else if (typeNode instanceof TypeNode.StringTypeNode) {
if (each == null) each = (rawValue, workingDirUri) -> rawValue;
if (all == null) all = this::allChooseLast;
if (multiple == null) multiple = false;
if (metavar == null) metavar = METAVAR_STRING;
return true;
} else if (typeNode instanceof TypeNode.TypeAliasTypeNode typeAliasTypeNode
&& typeAliasTypeNode.getVmTypeAlias() == BaseModule.getCharTypeAlias()) {
if (each == null)
each =
(rawValue, workingDirUri) -> {
if (rawValue.length() != 1) throw BadValue.invalid(rawValue, METAVAR_CHAR);
return rawValue;
};
if (all == null) all = this::allChooseLast;
if (multiple == null) multiple = false;
if (metavar == null) metavar = METAVAR_CHAR;
return true;
} else if (typeNode
instanceof TypeNode.UnionOfStringLiteralsTypeNode unionOfStringLiteralsTypeNode) {
var choices = unionOfStringLiteralsTypeNode.getStringLiterals().stream().sorted().toList();
if (each == null)
each =
(rawValue, workingDirUri) -> {
if (!unionOfStringLiteralsTypeNode.getStringLiterals().contains(rawValue)) {
throw BadValue.invalidChoice(rawValue, choices); throw BadValue.invalidChoice(rawValue, choices);
} }
return rawValue; return rawValue;
}; };
if (all == null) all = this::allChooseLast; }
if (multiple == null) multiple = false; if (all == null) {
if (metavar == null) metavar = "[" + String.join(", ", choices) + "]"; all = this::allChooseLast;
if (completionCandidates == null) }
completionCandidates = new Fixed(unionOfStringLiteralsTypeNode.getStringLiterals()); if (multiple == null) {
multiple = false;
}
if (metavar == null) {
metavar = "[" + String.join(", ", choices) + "]";
}
if (completionCandidates == null) {
completionCandidates = new Fixed(choiceSet);
}
return true; return true;
} else if (typeNode instanceof TypeNode.StringLiteralTypeNode stringLiteralTypeNode) { } else if (type instanceof VmType.StringLiteralType slt) {
var choice = stringLiteralTypeNode.getLiteral(); var choice = slt.getLiteral();
if (each == null) if (each == null) {
each = each =
(rawValue, workingDirUri) -> { (rawValue, workingDirUri) -> {
if (!rawValue.equals(choice)) { if (!rawValue.equals(choice)) {
@@ -795,10 +933,19 @@ public final class CommandSpecParser {
} }
return rawValue; return rawValue;
}; };
if (all == null) all = this::allChooseLast; }
if (multiple == null) multiple = false; if (all == null) {
if (metavar == null) metavar = "[" + choice + "]"; all = this::allChooseLast;
if (completionCandidates == null) completionCandidates = new Fixed(Set.of(choice)); }
if (multiple == null) {
multiple = false;
}
if (metavar == null) {
metavar = "[" + choice + "]";
}
if (completionCandidates == null) {
completionCandidates = new Fixed(Set.of(choice));
}
return true; return true;
} }
return false; return false;
@@ -823,7 +970,10 @@ public final class CommandSpecParser {
private static final Set<String> FALSE_VALUES = Set.of("false", "f", "0", "no", "n", "off"); private static final Set<String> FALSE_VALUES = Set.of("false", "f", "0", "no", "n", "off");
/** Sets each and metavar if they're not set */ /** Sets each and metavar if they're not set */
private void handleElement(TypeNode valueType, ClassProperty prop) { private void handleElement(ClassProperty prop, VmType.ClassType type) {
if (!type.isParametric()) throw unsupportedOptionTypeArguments(prop, type);
var valueType = type.getTypeArguments()[0];
if (each != null && metavar != null) return; if (each != null && metavar != null) return;
var transformValue = var transformValue =
new OptionBehavior(each, all, multiple, metavar, completionCandidates) new OptionBehavior(each, all, multiple, metavar, completionCandidates)
@@ -833,7 +983,11 @@ public final class CommandSpecParser {
} }
/** Sets each and metavar if they're not set */ /** Sets each and metavar if they're not set */
private void handleEntry(TypeNode keyType, TypeNode valueType, ClassProperty prop) { private void handleEntry(ClassProperty prop, VmType.ClassType type) {
if (!type.isParametric()) throw unsupportedOptionTypeArguments(prop, type);
var keyType = type.getTypeArguments()[0];
var valueType = type.getTypeArguments()[1];
if (each != null && metavar != null) return; if (each != null && metavar != null) return;
var transformKey = var transformKey =
new OptionBehavior(each, all, multiple, metavar, completionCandidates) new OptionBehavior(each, all, multiple, metavar, completionCandidates)
@@ -841,7 +995,7 @@ public final class CommandSpecParser {
var transformValue = var transformValue =
new OptionBehavior(each, all, multiple, metavar, completionCandidates) new OptionBehavior(each, all, multiple, metavar, completionCandidates)
.resolveTypeArgument(prop, resolveType(valueType).getFirst(), "value"); .resolveTypeArgument(prop, resolveType(valueType).getFirst(), "value");
if (each == null) if (each == null) {
each = each =
(rawValue, workingDirUri) -> { (rawValue, workingDirUri) -> {
var split = rawValue.split("=", 2); var split = rawValue.split("=", 2);
@@ -852,7 +1006,10 @@ public final class CommandSpecParser {
transformKey.getEach().apply(split[0], workingDirUri), transformKey.getEach().apply(split[0], workingDirUri),
transformValue.getEach().apply(split[1], workingDirUri)); transformValue.getEach().apply(split[1], workingDirUri));
}; };
if (metavar == null) metavar = transformKey.getMetavar() + "=" + transformValue.getMetavar(); }
if (metavar == null) {
metavar = transformKey.getMetavar() + "=" + transformValue.getMetavar();
}
} }
private @Nullable Object allChooseLast(List<Object> values, URI workingDirUri) { private @Nullable Object allChooseLast(List<Object> values, URI workingDirUri) {
@@ -223,7 +223,7 @@ public final class MirrorFactories {
.addTypedProperty( .addTypedProperty(
"referent", "referent",
typeNode -> { typeNode -> {
var clazz = typeNode.getVmClass(); var clazz = typeNode.getType().getVmClass();
assert clazz != null; assert clazz != null;
return clazz.getMirror(); return clazz.getMirror();
}) })
@@ -232,11 +232,7 @@ public final class MirrorFactories {
typeAliasTypeFactory typeAliasTypeFactory
.addTypedProperty( .addTypedProperty(
"referent", "referent",
typeNode -> { typeNode -> ((VmType.AliasType) typeNode.getType()).getVmTypeAlias().getMirror())
var alias = typeNode.getVmTypeAlias();
assert alias != null;
return alias.getMirror();
})
.addListProperty("typeArguments", TypeNode::getTypeArgumentMirrors); .addListProperty("typeArguments", TypeNode::getTypeArgumentMirrors);
declaredTypeFactory declaredTypeFactory
@@ -251,7 +247,8 @@ public final class MirrorFactories {
.addListProperty("parameterTypes", Pair::getFirst) .addListProperty("parameterTypes", Pair::getFirst)
.addTypedProperty("returnType", Pair::getSecond); .addTypedProperty("returnType", Pair::getSecond);
stringLiteralTypeFactory.addStringProperty("value", StringLiteralTypeNode::getLiteral); stringLiteralTypeFactory.addStringProperty(
"value", typeNode -> ((VmType.StringLiteralType) typeNode.getType()).getLiteral());
stringLiteralTypeFactory2.addStringProperty("value", Property.identity()); stringLiteralTypeFactory2.addStringProperty("value", Property.identity());
@@ -244,10 +244,15 @@ public final class VmClass extends VmValue {
checkAbstractMethods(); checkAbstractMethods();
} }
@TruffleBoundary
public int getTypeParameterCount() { public int getTypeParameterCount() {
return typeParameters.size(); return typeParameters.size();
} }
public List<TypeParameter> getTypeParameters() {
return typeParameters;
}
/** /**
* Returns the property with the given name declared in this class, or {@code null} if no such * Returns the property with the given name declared in this class, or {@code null} if no such
* property was found. Does return local properties. * property was found. Does return local properties.
@@ -298,6 +303,13 @@ public final class VmClass extends VmValue {
return (VmTyped) module; return (VmTyped) module;
} }
public VmClass getModuleClass() {
if (classInfo.isModuleClass()) return this;
var module = prototype.getEnclosingOwner();
assert module != null;
return module.getVmClass();
}
public VmTyped getModuleMirror() { public VmTyped getModuleMirror() {
return getModule().getModuleInfo().getMirror(getModule()); return getModule().getModuleInfo().getMirror(getModule());
} }
@@ -393,6 +405,11 @@ public final class VmClass extends VmValue {
return superclass; return superclass;
} }
public @Nullable VmType getSupertype() {
if (supertypeNode == null) return null;
return supertypeNode.getType();
}
@Override @Override
public VmTyped getPrototype() { public VmTyped getPrototype() {
return prototype; return prototype;
@@ -752,7 +769,7 @@ public final class VmClass extends VmValue {
return getDisplayName(); return getDisplayName();
} }
private UnmodifiableEconomicMap<Identifier, ClassProperty> getAllProperties() { public UnmodifiableEconomicMap<Identifier, ClassProperty> getAllProperties() {
synchronized (allPropertiesLock) { synchronized (allPropertiesLock) {
if (__allProperties == null) { if (__allProperties == null) {
// can't do this in ClassNode because it requires a fully initialized inheritance hierarchy // can't do this in ClassNode because it requires a fully initialized inheritance hierarchy
@@ -133,6 +133,6 @@ public abstract class VmListingOrMapping extends VmObject {
if (typeCastNode == null) { if (typeCastNode == null) {
return false; return false;
} }
return typeCastNode.getTypeNode().isEquivalentTo(typeNode); return typeCastNode.getTypeNode().getType().equals(typeNode.getType());
} }
} }
@@ -18,7 +18,7 @@ package org.pkl.core.runtime;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.nodes.DirectCallNode; import com.oracle.truffle.api.nodes.DirectCallNode;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Arrays;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@@ -27,12 +27,11 @@ import java.util.function.BiConsumer;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.pkl.core.Composite; import org.pkl.core.Composite;
import org.pkl.core.PClass;
import org.pkl.core.PClassInfo;
import org.pkl.core.PType;
import org.pkl.core.PklBugException;
import org.pkl.core.Reference; import org.pkl.core.Reference;
import org.pkl.core.TypeAlias; import org.pkl.core.runtime.VmType.NonFinalModuleType;
import org.pkl.core.runtime.VmType.NonFinalThisType;
import org.pkl.core.runtime.VmType.NothingType;
import org.pkl.core.runtime.VmType.UnknownType;
import org.pkl.core.util.paguro.RrbTree; import org.pkl.core.util.paguro.RrbTree;
import org.pkl.core.util.paguro.RrbTree.ImRrbt; import org.pkl.core.util.paguro.RrbTree.ImRrbt;
@@ -41,10 +40,12 @@ public final class VmReference extends VmValue {
private final VmTyped domain; private final VmTyped domain;
private final Object data; private final Object data;
private final ImRrbt<VmTyped> path; private final ImRrbt<VmTyped> path;
// candidate types can only be: PType.Class, PType.Alias (only preservedAliasTypes), // candidate types can only be: VmType.ClassType, VmType.AliasType (only preservedAliasTypes),
// PType.StringLiteral, PType.UNKNOWN, PType.Function, PType.TypeVariable, or PType.Union // VmType.StringLiteralType, VmType.Unknown.INSTANCE, VmType.FunctionType,
// VmType.TypeVariableTybe, or
// VmType.UnionType
// (containing only the previous; flattened) // (containing only the previous; flattened)
private final PType referentType; private final VmType referentType;
private boolean forced = false; private boolean forced = false;
@@ -55,12 +56,15 @@ public final class VmReference extends VmValue {
.toTyped(RefModule.getAccessClass()); .toTyped(RefModule.getAccessClass());
} }
@TruffleBoundary
public VmReference(VmTyped domain, VmClass clazz, Object data) { public VmReference(VmTyped domain, VmClass clazz, Object data) {
this(domain, data, RrbTree.empty(), normalizeTypes(new PType.Class(clazz.export()))); this(
domain,
data,
RrbTree.empty(),
normalizeTypes(new VmType.ClassType(clazz), clazz, clazz.getModuleClass()));
} }
public VmReference(VmTyped domain, Object data, ImRrbt<VmTyped> path, PType referentType) { public VmReference(VmTyped domain, Object data, ImRrbt<VmTyped> path, VmType referentType) {
this.domain = domain; this.domain = domain;
this.data = data; this.data = data;
this.referentType = referentType; this.referentType = referentType;
@@ -79,7 +83,7 @@ public final class VmReference extends VmValue {
return path; return path;
} }
public PType getReferentType() { public VmType getReferentType() {
return referentType; return referentType;
} }
@@ -88,71 +92,67 @@ public final class VmReference extends VmValue {
// * transforming T? into T|Null // * transforming T? into T|Null
// * dereferencing aliases (except for well-known stdlib alias types) // * dereferencing aliases (except for well-known stdlib alias types)
// * flattening unions // * flattening unions
// * drop PType.Function and PType.TypeVariable // * replace VmType.ModuleType with appropriate VmType.ClassType
private static PType normalizeTypes( // * drop VmType.FunctionType and VmType.TypeVariableType
PType type, @Nullable PType thisClass, @Nullable PType moduleClass) { @TruffleBoundary
var types = new HashSet<PType>(); private static VmType normalizeTypes(VmType type, VmClass thisClass, VmClass moduleClass) {
normalizeTypes(type, types, thisClass, moduleClass); var types = new HashSet<VmType>();
normalizeTypes(type, thisClass, moduleClass, types);
return minimizeTypes(types); return minimizeTypes(types);
} }
private static PType normalizeTypes(PType type) { private static VmType minimizeTypes(Set<VmType> types) {
return normalizeTypes(type, null, null);
}
private static PType minimizeTypes(Set<PType> types) {
if (types.size() == 1) return types.iterator().next(); if (types.size() == 1) return types.iterator().next();
// optimization: unknown allows all references, erase all candidates to only unknown // optimization: unknown allows all references, erase all candidates to only unknown
if (types.contains(PType.UNKNOWN)) return PType.UNKNOWN; if (types.contains(UnknownType.INSTANCE)) return UnknownType.INSTANCE;
// optimization: All allows all references, erase all candidates to only All // optimization: All allows all references, erase all candidates to only All
if (containsClass(types, BaseModule.getAnyClass().export())) if (containsClass(types, BaseModule.getAnyClass())) {
return new PType.Class(BaseModule.getAnyClass().export()); return new VmType.ClassType(BaseModule.getAnyClass());
var typesList = new ArrayList<>(types); }
typesList.sort(Comparator.comparing(Object::toString)); var typeArray = types.toArray(new VmType[0]);
return new PType.Union(typesList); Arrays.sort(typeArray, Comparator.comparing(Object::toString));
return new VmType.UnionType(-1, typeArray);
} }
private static void normalizeTypes( private static void normalizeTypes(
PType type, Set<PType> result, @Nullable PType thisClass, @Nullable PType moduleClass) { VmType type, VmClass thisClass, VmClass moduleClass, Set<VmType> result) {
if (type == PType.UNKNOWN || type == PType.NOTHING || type instanceof PType.StringLiteral) { if (type == UnknownType.INSTANCE
|| type == NothingType.INSTANCE
|| type instanceof VmType.StringLiteralType) {
result.add(type); result.add(type);
} else if (type instanceof PType.Class clazz) { } else if (type instanceof VmType.ClassType ct) {
if (clazz.getTypeArguments().isEmpty()) { if (!ct.isParametric()) {
// if a generic type is used without type arguments, it needs to be normalized so all args result.add(ct);
// are unknown; i.e. with bare List/Map/etc. type annotations (via FinalClassTypeNode).
var typeParameterCount = clazz.getPClass().getTypeParameters().size();
result.add(
typeParameterCount == 0
? clazz
: new PType.Class(
clazz.getPClass(), Collections.nCopies(typeParameterCount, PType.UNKNOWN)));
} else { } else {
var typeArgs = new ArrayList<PType>(clazz.getTypeArguments().size()); result.add(
for (var arg : clazz.getTypeArguments()) { ct.withTypeArguments(
typeArgs.add(normalizeTypes(arg, thisClass, moduleClass)); Arrays.stream(ct.getTypeArguments())
} .map(arg -> normalizeTypes(arg, thisClass, moduleClass))
result.add(new PType.Class(clazz.getPClass(), typeArgs)); .toArray(VmType[]::new)));
} }
} }
// normalize `T?` to `T | Null` // normalize `T?` to `T | Null`
else if (type instanceof PType.Nullable nullable) { else if (type instanceof VmType.NullableType nullable) {
normalizeTypes(nullable.getBaseType(), result, thisClass, moduleClass); normalizeTypes(nullable.getElementType(), thisClass, moduleClass, result);
result.add(new PType.Class(BaseModule.getNullClass().export())); result.add(new VmType.ClassType(BaseModule.getNullClass()));
// erase `T(someConstraint)` to `T` // erase `T(someConstraint)` to `T`
} else if (type instanceof PType.Constrained constrained) { } else if (type instanceof VmType.ConstrainedType constrained) {
normalizeTypes(constrained.getBaseType(), result, thisClass, moduleClass); normalizeTypes(constrained.getBaseType(), thisClass, moduleClass, result);
} else if (type instanceof PType.Alias alias) { } else if (type instanceof VmType.AliasType alias) {
if (isPreservedTypeAlias(alias.getTypeAlias())) { if (isPreservedTypeAlias(alias.getVmTypeAlias())) {
result.add(alias); result.add(alias);
} else { } else {
normalizeTypes(alias.getAliasedType(), result, thisClass, moduleClass); normalizeTypes(
alias.getAliasedType(),
alias.getVmTypeAlias().getModuleClass(),
alias.getVmTypeAlias().getModuleClass(),
result);
} }
} else if (type instanceof PType.Union union) { } else if (type instanceof VmType.UnionType union) {
for (var t : union.getElementTypes()) { for (var t : union.getElementTypes()) {
normalizeTypes(t, result, thisClass, moduleClass); normalizeTypes(t, thisClass, moduleClass, result);
} }
} else if (type == PType.THIS) { } else if (type instanceof NonFinalThisType) {
assert thisClass != null;
// there are 4 entrypoints here: // there are 4 entrypoints here:
// 1. init via the Reference constructor can only normalize an unparameterized PType.Class // 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 // 2. typecheck via ReferenceTypeNode erases self types to their actual PType.Class
@@ -161,13 +161,11 @@ public final class VmReference extends VmValue {
// 4. property access uses the enclosing receiver's class to substitute for these self types // 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. // only property access and typecheck can produce THIS or MODULE.
// getCandidatePropertyType and referentTypeIsSubtypeOf always pass non-null `thisClass`. // getCandidatePropertyType and referentTypeIsSubtypeOf always pass non-null `thisClass`.
result.add(thisClass); result.add(new VmType.ClassType(thisClass));
} else if (type == PType.MODULE) { } else if (type instanceof NonFinalModuleType) {
// this can be incorrect for usage of the module type in a class's property type annotation, // this can be incorrect for usage of the module type in a class's property type annotation,
// which is deprecated!! // which is deprecated!!
assert moduleClass != null; result.add(new VmType.ClassType(moduleClass));
// see PType.THIS case above
result.add(moduleClass);
} else { } else {
// remaining types: PType.Function, PType.TypeVariable. no normalizing needed; TypeVariable // remaining types: PType.Function, PType.TypeVariable. no normalizing needed; TypeVariable
// gets replaced upon instantiation, and Function can bubble up to users as a reference error // gets replaced upon instantiation, and Function can bubble up to users as a reference error
@@ -177,9 +175,9 @@ public final class VmReference extends VmValue {
} }
} }
private static Iterable<PType> iterateTypes(PType t) { private static VmType[] iterateTypes(VmType t) {
if (t instanceof PType.Union union) return union.getElementTypes(); if (t instanceof VmType.UnionType union) return union.getElementTypes();
return Collections.singleton(t); return new VmType[] {t};
} }
public VmReference withPropertyAccess(Identifier property) { public VmReference withPropertyAccess(Identifier property) {
@@ -197,8 +195,8 @@ public final class VmReference extends VmValue {
@TruffleBoundary @TruffleBoundary
private VmReference withAccess( private VmReference withAccess(
BiConsumer<PType, Set<PType>> checkCandidate, Supplier<VmTyped> makeAccess) { BiConsumer<VmType, Set<VmType>> checkCandidate, Supplier<VmTyped> makeAccess) {
Set<PType> candidates = new HashSet<>(); Set<VmType> candidates = new HashSet<>();
for (var t : iterateTypes(referentType)) { for (var t : iterateTypes(referentType)) {
checkCandidate.accept(t, candidates); checkCandidate.accept(t, candidates);
} }
@@ -206,51 +204,51 @@ public final class VmReference extends VmValue {
} }
@SuppressWarnings("DuplicatedCode") @SuppressWarnings("DuplicatedCode")
private static void getCandidatePropertyType(PType type, String property, Set<PType> result) { private static void getCandidatePropertyType(VmType type, String property, Set<VmType> result) {
if (type == PType.UNKNOWN) { if (type == UnknownType.INSTANCE) {
result.add(type); result.add(type);
return; return;
} }
// restriction: only class types can have their properties referenced // restriction: only class types can have their properties referenced
if (!(type instanceof PType.Class clazz)) { if (!(type instanceof VmType.AbstractClassType ct)) {
throw new VmReferenceAccessError(type, VmReferenceAccessErrorType.CANNOT_FIND_MEMBER); throw new VmReferenceAccessError(type, VmReferenceAccessErrorType.CANNOT_FIND_MEMBER);
} }
if (clazz.getPClass().getInfo() == PClassInfo.Dynamic) { if (ct.getVmClass().isDynamicClass()) {
if (property.equals("default")) { if (property.equals("default")) {
// restriction: cannot reference Dynamic.default // restriction: cannot reference Dynamic.default
throw new VmReferenceAccessError(type, VmReferenceAccessErrorType.DEFAULT_MEMBER); throw new VmReferenceAccessError(type, VmReferenceAccessErrorType.DEFAULT_MEMBER);
} }
result.add(PType.UNKNOWN); result.add(UnknownType.INSTANCE);
return; return;
} }
// restriction: cannot reference Listing/Mapping.default // restriction: cannot reference Listing/Mapping.default
if (clazz.getPClass().getInfo() == PClassInfo.Listing if (ct.getVmClass().isListingClass() || ct.getVmClass().isMappingClass()) {
|| clazz.getPClass().getInfo() == PClassInfo.Mapping) {
var errorType = var errorType =
property.equals("default") property.equals("default")
? VmReferenceAccessErrorType.DEFAULT_MEMBER ? VmReferenceAccessErrorType.DEFAULT_MEMBER
: VmReferenceAccessErrorType.CANNOT_FIND_MEMBER; : VmReferenceAccessErrorType.CANNOT_FIND_MEMBER;
throw new VmReferenceAccessError(type, errorType); throw new VmReferenceAccessError(type, errorType);
} }
var baseModule = BaseModule.getModuleClass().export(); var baseModule = BaseModule.getModuleClass();
// restriction: cannot reference Module.output. // restriction: cannot reference Module.output.
// generalized: properties originally defined in external classes; the only extant example. // generalized: properties originally defined in external classes; the only extant example.
// This is implemented specifically because this is the only case where an external class // This is implemented specifically because this is the only case where an external class
// containing a property can be subclassed. // containing a property can be subclassed.
// And this can't check prop.getOwner().isExternal() because fully overriding the property with // And this can't check prop.getOwner().isExternal() because fully overriding the property with
// a new type annotation means the owner isn't Module. // a new type annotation means the owner isn't Module.
if (clazz.getPClass().isSubclassOf(baseModule) && property.equals("output")) { if (ct.getVmClass().isSubclassOf(baseModule) && property.equals("output")) {
throw new VmReferenceAccessError( throw new VmReferenceAccessError(
new PType.Class(baseModule), VmReferenceAccessErrorType.EXTERNAL_CLASS); new VmType.ClassType(baseModule), VmReferenceAccessErrorType.EXTERNAL_CLASS);
} }
// dot access on `Reference<D, Null>` gives `Reference<D, Null>` // dot access on `Reference<D, Null>` gives `Reference<D, Null>`
if (clazz.getPClass().getInfo() == PClassInfo.Null) { if (ct.getVmClass().isNullClass()) {
result.add(clazz); result.add(ct);
return; return;
} }
var prop = clazz.getPClass().getAllProperties().get(property); var prop = ct.getVmClass().getAllProperties().get(Identifier.get(property));
//noinspection ConstantValue
if (prop == null) { if (prop == null) {
throw new VmReferenceAccessError(type, VmReferenceAccessErrorType.CANNOT_FIND_MEMBER); throw new VmReferenceAccessError(type, VmReferenceAccessErrorType.CANNOT_FIND_MEMBER);
} }
@@ -260,58 +258,57 @@ public final class VmReference extends VmValue {
throw new VmReferenceAccessError(type, VmReferenceAccessErrorType.EXTERNAL_MEMBER); throw new VmReferenceAccessError(type, VmReferenceAccessErrorType.EXTERNAL_MEMBER);
} }
normalizeTypes( // this handles the object-prop-in-class case because VmClass.getAllProperties() omits
prop.getType(), result, clazz, new PType.Class(clazz.getPClass().getModuleClass())); // properties without type annotations that are defined in a superclass. e.g.:
} // ```
// open class A { prop: String }
private static PClassInfo<?> getClassInfo(Object value) { // class B extends A { prop = "foo" }
if (value instanceof VmValue vmValue) return vmValue.getVmClass().getPClassInfo(); // ```
if (value instanceof String) return PClassInfo.String; // In this case `VmClass[B].getAllProperties().get(<prop>)` will be A's prop, not B's
if (value instanceof Boolean) return PClassInfo.Boolean; var propTypeNode = prop.getTypeNode();
if (value instanceof Long) return PClassInfo.Int; var propType =
if (value instanceof Double) return PClassInfo.Float; propTypeNode == null ? VmType.UnknownType.INSTANCE : propTypeNode.getTypeNode().getType();
throw new PklBugException("Not a Pkl value: " + value.getClass()); normalizeTypes(propType, ct.getVmClass(), ct.getVmClass().getModuleClass(), result);
} }
@SuppressWarnings("DuplicatedCode") @SuppressWarnings("DuplicatedCode")
private static void getCandidateSubscriptType(PType type, Object key, Set<PType> result) { private static void getCandidateSubscriptType(VmType type, Object key, Set<VmType> result) {
if (type == PType.UNKNOWN) { if (type == UnknownType.INSTANCE) {
result.add(type); result.add(type);
return; return;
} }
if (!(type instanceof PType.Class clazz)) { if (!(type instanceof VmType.ClassType ct)) {
throw new VmReferenceAccessError(type, VmReferenceAccessErrorType.CANNOT_FIND_MEMBER); throw new VmReferenceAccessError(type, VmReferenceAccessErrorType.CANNOT_FIND_MEMBER);
} }
if (clazz.getPClass().getInfo() == PClassInfo.Dynamic) { var clazz = ct.getVmClass();
result.add(PType.UNKNOWN); if (clazz.isDynamicClass()) {
result.add(UnknownType.INSTANCE);
return; return;
} }
if (clazz.getPClass().getInfo() == PClassInfo.Listing if (clazz.isListingClass() || clazz.isListClass()) {
|| clazz.getPClass().getInfo() == PClassInfo.List) {
if (!(key instanceof Long)) { if (!(key instanceof Long)) {
throw new VmReferenceAccessError(type, VmReferenceAccessErrorType.CANNOT_FIND_MEMBER); throw new VmReferenceAccessError(type, VmReferenceAccessErrorType.CANNOT_FIND_MEMBER);
} }
normalizeTypes(clazz.getTypeArguments().get(0), result, null, null); normalizeTypes(ct.getTypeArguments()[0], clazz, clazz.getModuleClass(), result);
return; return;
} }
if (clazz.getPClass().getInfo() == PClassInfo.Mapping if (clazz.isMappingClass() || clazz.isMapClass()) {
|| clazz.getPClass().getInfo() == PClassInfo.Map) { var typeArgs = ct.getTypeArguments();
var typeArgs = clazz.getTypeArguments(); var keyTypes = normalizeTypes(typeArgs[0], clazz, clazz.getModuleClass());
var keyTypes = normalizeTypes(typeArgs.get(0));
for (var kt : iterateTypes(keyTypes)) { for (var kt : iterateTypes(keyTypes)) {
if (kt == PType.UNKNOWN if (kt == UnknownType.INSTANCE
|| (kt instanceof PType.Class klazz && klazz.getPClass().getInfo() == getClassInfo(key)) || (kt instanceof VmType.ClassType klazz && klazz.getVmClass() == VmUtils.getClass(key))
|| (kt instanceof PType.StringLiteral stringLiteral || (kt instanceof VmType.StringLiteralType stringLiteral
&& stringLiteral.getLiteral().equals(key))) { && stringLiteral.getLiteral().equals(key))) {
normalizeTypes(typeArgs.get(1), result, null, null); normalizeTypes(typeArgs[1], clazz, clazz.getModuleClass(), result);
return; return;
} }
} }
} }
// subscript access on `Reference<D, Null>` gives `Reference<D, Null>` // subscript access on `Reference<D, Null>` gives `Reference<D, Null>`
if (clazz.getPClass().getInfo() == PClassInfo.Null) { if (clazz == BaseModule.getNullClass()) {
result.add(clazz); result.add(ct);
return; return;
} }
@@ -321,151 +318,52 @@ public final class VmReference extends VmValue {
/** /**
* Tells if this reference's referent type is a subtype of {@code type}. Does not check domain. * Tells if this reference's referent type is a subtype of {@code type}. Does not check domain.
*/ */
@TruffleBoundary public boolean referentTypeIsSubtypeOf(VmType type, VmClass thisClass, VmClass moduleClass) {
public boolean referentTypeIsSubtypeOf(PType type, PClass thisClass, PClass moduleClass) {
// fast path: if referent is unknown it can match any type check // fast path: if referent is unknown it can match any type check
if (referentType == PType.UNKNOWN) { if (referentType == UnknownType.INSTANCE) {
return true; return true;
} }
var checkType = normalizeTypes(type, new PType.Class(thisClass), new PType.Class(moduleClass)); var checkType = normalizeTypes(type, thisClass, moduleClass);
// fast path: short circuit if any referent is accepted // fast path: short circuit if any referent is accepted
if (checkType == PType.UNKNOWN || isClass(checkType, BaseModule.getAnyClass().export())) { if (checkType == UnknownType.INSTANCE || isClass(checkType, BaseModule.getAnyClass())) {
return true; return true;
} }
// fast path: short circuit if nothing is accepted // fast path: short circuit if nothing is accepted
if (checkType == PType.NOTHING) { if (checkType == NothingType.INSTANCE) {
return false; return false;
} }
return isSubtype(referentType, checkType); return doReferentTypeIsSubtypeOf(checkType);
} }
private static boolean containsClass(Set<PType> types, PClass pClass) { @TruffleBoundary
private boolean doReferentTypeIsSubtypeOf(VmType checkType) {
return referentType.isSubtypeOf(checkType);
}
private static boolean containsClass(Set<VmType> types, VmClass clazz) {
for (var t : types) { for (var t : types) {
if (isClass(t, pClass)) return true; if (isClass(t, clazz)) return true;
} }
return false; return false;
} }
private static boolean isClass(PType t, PClass pClass) { private static boolean isClass(VmType t, VmClass clazz) {
return t instanceof PType.Class clazz && clazz.getPClass() == pClass; return t instanceof VmType.ClassType ct && ct.getVmClass() == clazz;
} }
private static boolean isSubtype(PType a, PType b) { private static boolean isIntTypeAlias(VmTypeAlias t) {
// checks if A is a subtype of B return t == BaseModule.getInt8TypeAlias()
// cases (A -> B) || t == BaseModule.getInt16TypeAlias()
// * A == B || t == BaseModule.getInt32TypeAlias()
// * StringLiteral -> StringLiteral: if literals are the same || t == BaseModule.getUInt8TypeAlias()
// * StringLiteral -> Class: B is String || t == BaseModule.getUInt16TypeAlias()
// * Char Alias -> Char Alias, StringLiteral (known single character) || t == BaseModule.getUInt32TypeAlias()
// * Int Alias -> Class: B is a subtype of Number (Int|Float|Number) || t == BaseModule.getUIntTypeAlias();
// * Int Alias -> Alias
// * same alias
// * Int8 is Int16|Int32
// * Int16 is Int32
// * UInt8 is Int16|Int32|Uint16|UInt32|UInt
// * UInt16 is Int32|UInt32|UInt
// * UInt32 is UInt
// * Class -> Class: if same class or A is a subclass of B
// * if type args are present, must have equal number of them
// * for each pair of type args, check variance
// * invariant: A_i must be identical to B_i
// * covariant: A_i must be a subtype of B_i
// * contravariant: B_i must be a subtype of A_i
// * Union -> Union: Each elem of A must be a subtype of at least one elem of B
// * Non-union -> Union: A must be a subtype of at least one elem of B
if (a == b) return true;
if (a instanceof PType.StringLiteral aStr) {
if (b instanceof PType.StringLiteral bStr) {
return aStr.getLiteral().equals(bStr.getLiteral());
} else if (b instanceof PType.Class bClass) {
return bClass.getPClass() == BaseModule.getStringClass().export();
}
} else if (a instanceof PType.Alias aAlias) {
var aa = aAlias.getTypeAlias();
if (isIntTypeAlias(aa)) {
// special casing for stdlib Int typealiases
if (b instanceof PType.Class bClass) {
// A is an int alias, B is a Number (sub)class
return bClass.getPClass().isSubclassOf(BaseModule.getNumberClass().export());
} else if (b instanceof PType.Alias bAlias) {
var bb = bAlias.getTypeAlias();
if (aa == bb) {
return true;
}
if (aa == BaseModule.getInt8TypeAlias().export()) {
return bb == BaseModule.getInt16TypeAlias().export()
|| bb == BaseModule.getInt32TypeAlias().export();
} else if (aa == BaseModule.getInt16TypeAlias().export()) {
return bb == BaseModule.getInt32TypeAlias().export();
} else if (aa == BaseModule.getUInt8TypeAlias().export()) {
return bb == BaseModule.getInt16TypeAlias().export()
|| bb == BaseModule.getInt32TypeAlias().export()
|| bb == BaseModule.getUInt16TypeAlias().export()
|| bb == BaseModule.getUInt32TypeAlias().export()
|| bb == BaseModule.getUIntTypeAlias().export();
} else if (aa == BaseModule.getUInt16TypeAlias().export()) {
return bb == BaseModule.getInt32TypeAlias().export()
|| bb == BaseModule.getUInt32TypeAlias().export()
|| bb == BaseModule.getUIntTypeAlias().export();
} else if (aa == BaseModule.getUInt32TypeAlias().export()) {
return bb == BaseModule.getUIntTypeAlias().export();
}
}
}
} else if (a instanceof PType.Class aClass && b instanceof PType.Class bClass) {
if (!aClass.getPClass().isSubclassOf(bClass.getPClass())) {
return false;
}
var aArgs = aClass.getTypeArguments();
var bArgs = bClass.getTypeArguments();
var bParams = bClass.getPClass().getTypeParameters();
if (aArgs.size() != bArgs.size()) {
return false;
}
// check variance of type args pairwise
for (var i = 0; i < aArgs.size(); i++) {
if (!switch (bParams.get(i).getVariance()) {
case INVARIANT -> aArgs.get(i) == bArgs.get(i);
case COVARIANT -> isSubtype(aArgs.get(i), bArgs.get(i));
case CONTRAVARIANT -> isSubtype(bArgs.get(i), aArgs.get(i));
}) {
return false;
}
}
return true;
} else if (b instanceof PType.Union bUnion) {
if (a instanceof PType.Union aUnion) {
a:
for (var aElem : aUnion.getElementTypes()) {
for (var bElem : bUnion.getElementTypes()) {
if (isSubtype(aElem, bElem)) continue a;
}
return false;
}
return true;
} else {
for (var bElem : bUnion.getElementTypes()) {
if (isSubtype(a, bElem)) return true;
}
}
}
return false;
} }
private static boolean isIntTypeAlias(TypeAlias t) { private static boolean isPreservedTypeAlias(VmTypeAlias t) {
return t == BaseModule.getInt8TypeAlias().export()
|| t == BaseModule.getInt16TypeAlias().export()
|| t == BaseModule.getInt32TypeAlias().export()
|| t == BaseModule.getUInt8TypeAlias().export()
|| t == BaseModule.getUInt16TypeAlias().export()
|| t == BaseModule.getUInt32TypeAlias().export()
|| t == BaseModule.getUIntTypeAlias().export();
}
private static boolean isPreservedTypeAlias(TypeAlias t) {
return isIntTypeAlias(t); return isIntTypeAlias(t);
} }
@@ -494,13 +392,14 @@ public final class VmReference extends VmValue {
pathList.add(elem.export()); pathList.add(elem.export());
} }
return new Reference(domain.export(), VmValue.export(data), pathList, getReferentType()); return new Reference(
domain.export(), VmValue.export(data), pathList, getReferentType().export());
} }
public PType exportType() { public VmType getType() {
return new PType.Class( return new VmType.ClassType(
RefModule.getReferenceClass().export(), RefModule.getReferenceClass(),
new PType.Class(domain.getVmClass().export()), new VmType.ClassType(domain.getVmClass()),
getReferentType()); getReferentType());
} }
@@ -554,15 +453,15 @@ public final class VmReference extends VmValue {
} }
public static final class VmReferenceAccessError extends RuntimeException { public static final class VmReferenceAccessError extends RuntimeException {
private final PType type; private final VmType type;
private final VmReferenceAccessErrorType errorType; private final VmReferenceAccessErrorType errorType;
public VmReferenceAccessError(PType type, VmReferenceAccessErrorType errorType) { public VmReferenceAccessError(VmType type, VmReferenceAccessErrorType errorType) {
this.type = type; this.type = type;
this.errorType = errorType; this.errorType = errorType;
} }
public PType getType() { public VmType getType() {
return type; return type;
} }
@@ -0,0 +1,767 @@
/*
* 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 java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import org.jspecify.annotations.Nullable;
import org.pkl.core.PType;
import org.pkl.core.PType.Alias;
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;
public abstract sealed class VmType {
public abstract PType export();
/** Checks if two types are equivalent. {@code other} will never be an {@link AliasType}. */
protected abstract boolean doIsEquivalentTo(VmType other);
/** Checks if this type is a supertype of {@code other}. */
protected abstract boolean doIsSupertypeOf(VmType other);
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public boolean isParametric() {
return false;
}
public @Nullable VmClass getVmClass() {
return null;
}
/** Tells if this type is the same typecheck as the other type. */
@Override
public final boolean equals(@Nullable Object value) {
if (this == value) return true;
if (!(value instanceof VmType other)) return false;
while (other instanceof AliasType at) {
other = at.aliasedType;
}
return doIsEquivalentTo(other);
}
public final boolean isSubtypeOf(VmType other) {
return other.isSupertypeOf(this);
}
public final boolean isSupertypeOf(VmType other) {
return equals(other)
|| doIsSupertypeOf(other)
|| other == UnknownType.INSTANCE // all types are supertypes/subtypes of unknown
|| other == NothingType.INSTANCE
|| other instanceof TypeVariableType
|| (other instanceof UnionType ut && ut.allElementsMatch(this::isSupertypeOf))
|| (other instanceof AliasType at && isSupertypeOf(at.aliasedType))
|| (other instanceof ConstrainedType ct && isSupertypeOf(ct.baseType));
}
public static final class UnknownType extends VmType {
public static final UnknownType INSTANCE = new UnknownType();
private UnknownType() {}
@Override
protected boolean doIsEquivalentTo(VmType other) {
// equality checked by equals()
return false;
}
@Override
protected boolean doIsSupertypeOf(VmType other) {
// unknown is supertype of everything!
return true;
}
@Override
public PType export() {
return PType.UNKNOWN;
}
@Override
public String toString() {
return "unknown";
}
}
public static final class NothingType extends VmType {
public static final NothingType INSTANCE = new NothingType();
private NothingType() {}
@Override
protected boolean doIsEquivalentTo(VmType other) {
// equality checked by equals()
return false;
}
@Override
protected boolean doIsSupertypeOf(VmType other) {
// nothing is a supertype of nothing (except itself and unknown, handled by callers)
return false;
}
@Override
public PType export() {
return PType.NOTHING;
}
@Override
public String toString() {
return "nothing";
}
}
public static final class StringLiteralType extends VmType {
private final String literal;
public StringLiteralType(String literal) {
super();
this.literal = literal;
}
public String getLiteral() {
return literal;
}
@Override
public PType export() {
return new StringLiteral(literal);
}
@Override
protected boolean doIsEquivalentTo(VmType other) {
return other instanceof StringLiteralType t && literal.equals(t.literal);
}
@Override
protected boolean doIsSupertypeOf(VmType other) {
// equivalence handled by caller
return false;
}
@Override
public String toString() {
return ValueFormatter.basic().formatStringValue(literal, "");
}
@Override
public int hashCode() {
return 31 * literal.hashCode();
}
}
public abstract static sealed class AbstractClassType extends VmType {
protected final VmClass clazz;
protected final VmType[] typeArguments;
protected AbstractClassType(VmClass clazz, VmType[] typeArguments) {
this.clazz = clazz;
this.typeArguments = typeArguments;
}
public final VmType[] getTypeArguments() {
return typeArguments;
}
@Override
public final VmClass getVmClass() {
return clazz;
}
@Override
public final boolean isParametric() {
return typeArguments.length > 0;
}
public final boolean isFunctionNClass() {
return clazz.isFunctionNClass();
}
@Override
protected final boolean doIsEquivalentTo(VmType other) {
if (!(other instanceof AbstractClassType t)) return false;
if (clazz != t.clazz) return false;
return typesEquals(typeArguments, t.typeArguments);
}
@Override
protected final boolean doIsSupertypeOf(VmType other) {
// special case: Any is a supertype of everything
if (clazz == BaseModule.getAnyClass()) return true;
// special case: String is a supertype of all string literals and also Char
if (clazz == BaseModule.getStringClass()
&& (other instanceof StringLiteralType
|| (other instanceof AliasType at && at.typeAlias == BaseModule.getCharTypeAlias())))
return true;
// special case: Int is a supertype of range-constrained aliases
if ((clazz == BaseModule.getIntClass() || clazz == BaseModule.getNumberClass())
&& other instanceof AliasType at
&& (at.typeAlias == BaseModule.getInt8TypeAlias()
|| at.typeAlias == BaseModule.getInt16TypeAlias()
|| at.typeAlias == BaseModule.getInt32TypeAlias()
|| at.typeAlias == BaseModule.getUIntTypeAlias()
|| at.typeAlias == BaseModule.getUInt8TypeAlias()
|| at.typeAlias == BaseModule.getUInt16TypeAlias()
|| at.typeAlias == BaseModule.getUInt32TypeAlias())) return true;
// special case: class types are the supertype of non-final self types "rooted" to a subclass
if (other instanceof NonFinalSelfType st) {
return clazz.isSuperclassOf(st.clazz);
}
// standard case: other is a AbstractClassType
if (!(other instanceof AbstractClassType ct)) return false;
// if clazz isn't a superclass of other's we're not a supertype
if (!clazz.isSuperclassOf(ct.clazz)) return false;
// if our class has no type params, we are a supertype
if (clazz.getTypeParameterCount() == 0) return true;
// check generic type args: walk ct.clazz to clazz, substituting type arguments as we go
// handles arbitrary generics like Function2<A, B, R> -> Function<R>
var goalState =
typeArguments.length > 0
? typeArguments
: nCopies(clazz.getTypeParameterCount(), ClassType.ANY);
var state =
ct.typeArguments.length > 0
? ct.typeArguments
: nCopies(ct.clazz.getTypeParameterCount(), ClassType.ANY);
for (var c = ct.clazz; c != clazz; c = c.getSuperclass()) {
assert c != null; // we know walking parents reaches clazz before null
var cSuperclass = c.getSuperclass();
assert cSuperclass != null; // we know c has a superclass
var cSupertype = (AbstractClassType) c.getSupertype();
assert cSupertype != null; // we know c has a supertype
if (cSupertype.typeArguments.length == 0) {
// supertype args could be omitted, e.g. class MyList<T> extends List
state = nCopies(cSuperclass.getTypeParameterCount(), ClassType.ANY);
continue;
} else if (state.length == 0) {
// subclass may not have type args, e.g. class A extends B<Int>
state = cSupertype.typeArguments;
continue;
}
// otherwise, clone supertype args and substitute any type vars
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()];
}
state = newState;
}
// do variance checking
assert goalState.length == state.length;
var params = clazz.getTypeParameters();
for (var i = 0; i < goalState.length; i++) {
if (!switch (params.get(i).getVariance()) {
case INVARIANT -> state[i].equals(goalState[i]);
case COVARIANT -> state[i].isSubtypeOf(goalState[i]);
case CONTRAVARIANT -> state[i].isSupertypeOf(goalState[i]);
}) return false;
}
return true;
}
@Override
public final int hashCode() {
return 31 * clazz.hashCode() + Arrays.hashCode(typeArguments);
}
}
public static final class ClassType extends AbstractClassType {
public ClassType(VmClass clazz) {
super(clazz, new VmType[0]);
}
public ClassType(VmClass clazz, VmType typeArgument) {
super(clazz, new VmType[] {typeArgument});
}
public ClassType(VmClass clazz, VmType typeArgument1, VmType typeArgument2) {
super(clazz, new VmType[] {typeArgument1, typeArgument2});
}
public ClassType(VmClass clazz, VmType[] typeArguments) {
super(clazz, typeArguments);
}
private static final ClassType ANY = new ClassType(BaseModule.getAnyClass());
public VmType.ClassType withTypeArguments(VmType[] typeArguments) {
return new VmType.ClassType(clazz, typeArguments);
}
@Override
public PType export() {
return typeArguments.length > 0 && clazz.isFunctionNClass()
? new PType.Function(
exportTypes(typeArguments, 1), typeArguments[typeArguments.length - 1].export())
: new PType.Class(clazz.export(), exportTypes(typeArguments));
}
@Override
public String toString() {
if (typeArguments.length > 0 && clazz.isFunctionNClass()) {
var paramCount = typeArguments.length - 1;
var sb = new StringBuilder("(");
for (var i = 0; i < paramCount; i++) {
sb.append(typeArguments[i]);
if (i < paramCount - 1) {
sb.append(", ");
}
}
sb.append(") -> ");
sb.append(typeArguments[typeArguments.length - 1]);
return sb.toString();
}
if (typeArguments.length == 0) return clazz.getDisplayName();
return appendTypeArguments(new StringBuilder(clazz.getDisplayName()), typeArguments)
.toString();
}
}
public static final class FinalModuleType extends AbstractClassType {
public FinalModuleType(VmClass clazz) {
super(clazz, new VmType[0]);
}
@Override
public PType export() {
return PType.MODULE;
}
@Override
public String toString() {
return "module";
}
}
public static final class FinalThisType extends AbstractClassType {
public FinalThisType(VmClass clazz) {
super(clazz, new VmType[0]);
}
@Override
public PType export() {
return PType.THIS;
}
@Override
public String toString() {
return "this";
}
}
private abstract static sealed class NonFinalSelfType extends VmType {
private final VmClass clazz;
protected NonFinalSelfType(VmClass clazz) {
this.clazz = clazz;
}
@Override
public final VmClass getVmClass() {
return clazz;
}
@Override
protected final boolean doIsEquivalentTo(VmType other) {
return other instanceof NonFinalSelfType st && clazz == st.clazz;
}
@Override
protected final boolean doIsSupertypeOf(VmType other) {
return other instanceof NonFinalSelfType st && clazz.isSuperclassOf(st.clazz);
}
@Override
public final int hashCode() {
return 31 * clazz.hashCode();
}
}
public static final class NonFinalModuleType extends NonFinalSelfType {
public NonFinalModuleType(VmClass clazz) {
super(clazz);
}
@Override
public PType export() {
return PType.MODULE;
}
@Override
public String toString() {
return "module";
}
}
public static final class NonFinalThisType extends NonFinalSelfType {
public NonFinalThisType(VmClass clazz) {
super(clazz);
}
@Override
public PType export() {
return PType.THIS;
}
@Override
public String toString() {
return "this";
}
}
public static final class NullableType extends VmType {
private final VmType elementType;
public NullableType(VmType elementType) {
this.elementType = elementType;
}
public VmType getElementType() {
return elementType;
}
@Override
protected boolean doIsEquivalentTo(VmType other) {
return other instanceof NullableType t && elementType.equals(t.elementType);
}
@Override
protected boolean doIsSupertypeOf(VmType other) {
if (other instanceof NullableType nt) return elementType.isSupertypeOf(nt.elementType);
return elementType.isSupertypeOf(other);
}
@Override
public PType export() {
return new PType.Nullable(elementType.export());
}
@Override
public String toString() {
return (elementType instanceof ClassType ct && ct.isFunctionNClass())
|| elementType instanceof UnionType
? "(" + elementType + ")?"
: elementType + "?";
}
@Override
public int hashCode() {
return 31 * elementType.hashCode();
}
}
public static final class ConstrainedType extends VmType {
private final VmType baseType;
private final String[] constraints;
private final int identity;
public ConstrainedType(VmType baseType, String[] constraints, int identity) {
this.baseType = baseType;
this.constraints = constraints;
this.identity = identity;
}
public VmType getBaseType() {
return baseType;
}
@Override
protected boolean doIsEquivalentTo(VmType other) {
// consider constrained equivalent only on identity
return other instanceof ConstrainedType t && t.identity == identity;
}
@Override
protected boolean doIsSupertypeOf(VmType other) {
// constrained types can never be supertypes (except when equal by identity, handled above)
return false;
}
@Override
public PType export() {
return new Constrained(baseType.export(), Arrays.asList(constraints));
}
@Override
public String toString() {
return ((baseType instanceof ClassType ct && ct.isFunctionNClass())
|| baseType instanceof UnionType
? "(" + baseType + ")"
: baseType)
+ "("
+ String.join(", ", constraints)
+ ")";
}
@Override
public int hashCode() {
return 31 * identity;
}
}
public static final class AliasType extends VmType {
private final VmTypeAlias typeAlias;
private final @Nullable VmClass clazz;
private final VmType[] typeArguments;
private final VmType aliasedType;
/** For intrinsified typealiases with an undetermined class (e.g. NonNull) */
public AliasType(VmTypeAlias typeAlias) {
this.typeAlias = typeAlias;
this.aliasedType = typeAlias.getTypeNode().getType();
this.typeArguments = new VmType[0];
this.clazz = aliasedType.getVmClass();
}
/** For intrinsified typealiases with an inherent class (e.g. Int aliases) */
public AliasType(VmTypeAlias typeAlias, VmClass clazz) {
this.typeAlias = typeAlias;
this.aliasedType = typeAlias.getTypeNode().getType();
this.typeArguments = new VmType[0];
this.clazz = clazz;
}
/** For arbitrary typealiases */
public AliasType(VmTypeAlias typeAlias, VmType[] typeArguments, VmType aliasedType) {
this.typeAlias = typeAlias;
this.typeArguments = typeArguments;
this.clazz = aliasedType.getVmClass();
this.aliasedType = aliasedType;
}
public VmTypeAlias getVmTypeAlias() {
return typeAlias;
}
public VmType getAliasedType() {
return aliasedType;
}
@Override
public @Nullable VmClass getVmClass() {
return clazz;
}
@Override
protected boolean doIsEquivalentTo(VmType other) {
return aliasedType.equals(other);
}
@Override
protected boolean doIsSupertypeOf(VmType other) {
if (other instanceof AliasType at) {
if (at.typeAlias == BaseModule.getInt8TypeAlias()
&& (typeAlias == BaseModule.getInt16TypeAlias()
|| typeAlias == BaseModule.getInt32TypeAlias())) return true;
if (at.typeAlias == BaseModule.getInt16TypeAlias()
&& typeAlias == BaseModule.getInt32TypeAlias()) return true;
if (at.typeAlias == BaseModule.getUInt8TypeAlias()
&& (typeAlias == BaseModule.getInt16TypeAlias()
|| typeAlias == BaseModule.getInt32TypeAlias()
|| typeAlias == BaseModule.getUInt16TypeAlias()
|| typeAlias == BaseModule.getUInt32TypeAlias()
|| typeAlias == BaseModule.getUIntTypeAlias())) return true;
if (at.typeAlias == BaseModule.getUInt16TypeAlias()
&& (typeAlias == BaseModule.getInt32TypeAlias()
|| typeAlias == BaseModule.getUInt32TypeAlias()
|| typeAlias == BaseModule.getUIntTypeAlias())) return true;
if (at.typeAlias == BaseModule.getUInt32TypeAlias()
&& typeAlias == BaseModule.getUIntTypeAlias()) return true;
}
return aliasedType.isSupertypeOf(other);
}
@Override
public PType export() {
return new Alias(typeAlias.export(), exportTypes(typeArguments), aliasedType.export());
}
@Override
public boolean isParametric() {
return typeArguments.length > 0;
}
@Override
public String toString() {
if (typeArguments.length == 0) return typeAlias.getDisplayName();
return appendTypeArguments(new StringBuilder(typeAlias.getDisplayName()), typeArguments)
.toString();
}
@Override
public int hashCode() {
return aliasedType.hashCode();
}
}
public static final class UnionType extends VmType {
private final int defaultIndex;
private final VmType[] elementTypes;
public UnionType(int defaultIndex, VmType[] elementTypes) {
this.defaultIndex = defaultIndex;
this.elementTypes = elementTypes;
}
public UnionType(int defaultIndex, String[] stringLiterals) {
this.defaultIndex = defaultIndex;
elementTypes = new VmType[stringLiterals.length];
for (var i = 0; i < elementTypes.length; i++) {
elementTypes[i] = new StringLiteralType(stringLiterals[i]);
}
}
public int getDefaultIndex() {
return defaultIndex;
}
public VmType[] getElementTypes() {
return elementTypes;
}
@Override
public PType export() {
return new Union(exportTypes(elementTypes));
}
@Override
protected boolean doIsEquivalentTo(VmType other) {
if (!(other instanceof UnionType t)) return false;
// TODO: handle A | B as equivalent to B | A
return typesEquals(elementTypes, t.elementTypes);
}
@Override
protected boolean doIsSupertypeOf(VmType other) {
for (var member : elementTypes) {
if (member.isSupertypeOf(other)) return true;
}
return false;
}
public boolean isUnionOfStringLiterals() {
return allElementsMatch(it -> it instanceof StringLiteralType);
}
@Override
public String toString() {
var sb = new StringBuilder();
for (var i = 0; i < elementTypes.length; i++) {
sb.append(elementTypes[i]);
if (i < elementTypes.length - 1) {
sb.append(" | ");
}
}
return sb.toString();
}
@Override
public int hashCode() {
return Arrays.hashCode(elementTypes);
}
boolean allElementsMatch(Function<VmType, Boolean> predicate) {
for (var member : elementTypes) {
if (!predicate.apply(member)) return false;
}
return true;
}
}
public static final class TypeVariableType extends VmType {
private final TypeParameter typeParameter;
public TypeVariableType(TypeParameter typeParameter) {
this.typeParameter = typeParameter;
}
public TypeParameter getTypeParameter() {
return typeParameter;
}
@Override
protected boolean doIsEquivalentTo(VmType other) {
return other instanceof TypeVariableType;
}
@Override
protected boolean doIsSupertypeOf(VmType other) {
return true;
}
@Override
public PType export() {
return new TypeVariable(typeParameter);
}
@Override
public String toString() {
return typeParameter.getName();
}
@Override
public int hashCode() {
return -318019;
}
}
private static boolean typesEquals(VmType[] a, VmType[] b) {
if (a.length != b.length) return false;
for (var i = 0; i < a.length; i++) {
if (!a[i].equals(b[i])) return false;
}
return true;
}
private static List<PType> exportTypes(VmType[] types) {
return Arrays.stream(types).map(VmType::export).toList();
}
private static List<PType> exportTypes(VmType[] types, int drop) {
return Arrays.stream(types).limit(types.length - drop).map(VmType::export).toList();
}
private static VmType[] nCopies(int len, VmType type) {
var ret = new VmType[len];
Arrays.fill(ret, type);
return ret;
}
private static StringBuilder appendTypeArguments(StringBuilder sb, VmType[] typeArguments) {
sb.append('<');
for (var i = 0; i < typeArguments.length; i++) {
sb.append(typeArguments[i]);
if (i < typeArguments.length - 1) {
sb.append(", ");
}
}
sb.append('>');
return sb;
}
}
@@ -137,6 +137,10 @@ public final class VmTypeAlias extends VmValue {
return annotations; return annotations;
} }
public VmClass getModuleClass() {
return module.getVmClass();
}
public String getModuleName() { public String getModuleName() {
return module.getVmClass().getModuleName(); return module.getVmClass().getModuleName();
} }
@@ -22,6 +22,7 @@ import com.oracle.truffle.api.dsl.TypeSystem;
long.class, long.class,
double.class, double.class,
boolean.class, boolean.class,
VmType.class,
VmDuration.class, VmDuration.class,
VmDataSize.class, VmDataSize.class,
VmIntSeq.class, VmIntSeq.class,
@@ -62,6 +62,7 @@ import org.pkl.core.runtime.VmPair;
import org.pkl.core.runtime.VmReference; import org.pkl.core.runtime.VmReference;
import org.pkl.core.runtime.VmRegex; import org.pkl.core.runtime.VmRegex;
import org.pkl.core.runtime.VmSet; import org.pkl.core.runtime.VmSet;
import org.pkl.core.runtime.VmType;
import org.pkl.core.runtime.VmTyped; import org.pkl.core.runtime.VmTyped;
import org.pkl.core.runtime.VmUtils; import org.pkl.core.runtime.VmUtils;
import org.pkl.core.runtime.VmValue; import org.pkl.core.runtime.VmValue;
@@ -118,7 +119,9 @@ public final class RendererNodes {
var clazz = var clazz =
value instanceof VmValue vmValue value instanceof VmValue vmValue
? vmValue.getVmClass() ? vmValue.getVmClass()
: value instanceof TypeNode typeNode ? typeNode.getVmClass() : value.getClass(); : value instanceof TypeNode typeNode
? typeNode.getType().getVmClass()
: value.getClass();
if (clazz == null) { if (clazz == null) {
throw new VmExceptionBuilder() throw new VmExceptionBuilder()
.evalError("cannotResolveTypeForProtobuf") .evalError("cannotResolveTypeForProtobuf")
@@ -345,7 +348,7 @@ public final class RendererNodes {
return computedName; return computedName;
} }
} else if (type instanceof NonFinalClassTypeNode) { } else if (type instanceof NonFinalClassTypeNode) {
var clazz = type.getVmClass(); var clazz = type.getType().getVmClass();
if (value instanceof VmValue vmValue) { if (value instanceof VmValue vmValue) {
if (clazz == vmValue.getVmClass()) { if (clazz == vmValue.getVmClass()) {
return clazz.getSimpleName(); return clazz.getSimpleName();
@@ -402,9 +405,9 @@ public final class RendererNodes {
hasCollection = true; hasCollection = true;
collectionType = setType.getElementTypeNode(); collectionType = setType.getElementTypeNode();
} else if (type instanceof NonFinalClassTypeNode) { } else if (type instanceof NonFinalClassTypeNode) {
if (type.getVmClass() != clazz) { if (type.getType().getVmClass() != clazz) {
throw new VmExceptionBuilder() throw new VmExceptionBuilder()
.evalError("cannotRenderSubtypeForProtobuf", type.getVmClass(), clazz) .evalError("cannotRenderSubtypeForProtobuf", type.getType().getVmClass(), clazz)
.build(); .build();
} }
} }
@@ -581,7 +584,8 @@ public final class RendererNodes {
} else if (type instanceof NullableTypeNode nullableType) { } else if (type instanceof NullableTypeNode nullableType) {
return resolveType(nullableType.getElementTypeNode()); return resolveType(nullableType.getElementTypeNode());
} else if (type instanceof TypeAliasTypeNode typeAliasType) { } else if (type instanceof TypeAliasTypeNode typeAliasType) {
return resolveType(typeAliasType.getVmTypeAlias().getTypeNode()); return resolveType(
((VmType.AliasType) typeAliasType.getType()).getVmTypeAlias().getTypeNode());
} else if (type instanceof ListingTypeNode listingType) { } else if (type instanceof ListingTypeNode listingType) {
var valueType = resolveType(listingType.getValueTypeNode()); var valueType = resolveType(listingType.getValueTypeNode());
assert valueType != null : "Failed to resolve type node."; assert valueType != null : "Failed to resolve type node.";
@@ -633,8 +637,8 @@ public final class RendererNodes {
elements.sort( elements.sort(
(o1, o2) -> { (o1, o2) -> {
if (o1 instanceof ObjectSlotTypeNode && o2 instanceof ObjectSlotTypeNode) { if (o1 instanceof ObjectSlotTypeNode && o2 instanceof ObjectSlotTypeNode) {
var t1 = o1.getVmClass(); var t1 = o1.getType().getVmClass();
var t2 = o2.getVmClass(); var t2 = o2.getType().getVmClass();
if (t1 == null || t2 == null) return 0; if (t1 == null || t2 == null) return 0;
return t1.isSubclassOf(t2) ? -1 : t2.isSubclassOf(t1) ? 1 : 0; return t1.isSubclassOf(t2) ? -1 : t2.isSubclassOf(t1) ? 1 : 0;
} }
@@ -359,7 +359,7 @@ typeMismatchBecause=\
* Value is not of type `{0}` because: * Value is not of type `{0}` because:
classTypeMismatchHint=\ classTypeMismatchHint=\
A `Class` type check can only succeed when its type argument is an un-parameterized class, `unknown`, `module`, or an alias to one of those types. A `Class` type check can only succeed when its type argument is an un-parameterized class, `unknown`, `module`, `this`, or an alias to one of those types.
typeConstraintViolated=\ typeConstraintViolated=\
Type constraint `{0}` violated. Type constraint `{0}` violated.
@@ -134,6 +134,9 @@ class BadResource extends Resource {
badThisRef = test.catch(() -> new BadResource { name = "bad" }.$.toString()) 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<String>)
output { output {
renderer { renderer {
converters { converters {
@@ -61,3 +61,4 @@ aValuesJoined = """
""" """
typeArgs = "${null.prop}" typeArgs = "${null.prop}"
badThisRef = "Expected value of type `pkl.ref#Reference<reference#D, this>`, but got type `pkl.ref#Reference<reference#D, reference#A>`. Value: Reference(new D {}, reference#A, new BadResource { name = ? })" badThisRef = "Expected value of type `pkl.ref#Reference<reference#D, this>`, but got type `pkl.ref#Reference<reference#D, reference#A>`. Value: Reference(new D {}, reference#A, new BadResource { name = ? })"
bareFunctionType = "Expected value of type `pkl.ref#Reference<reference#D, String>`, but got type `pkl.ref#Reference<reference#D, Function4>`. Value: Reference(new D {}, Function4, null)"
@@ -1,5 +1,5 @@
–– Pkl Error –– –– Pkl Error ––
Cannot find property `default` in object of type `pkl.ref#Reference<reference13#D, Mapping<unknown, unknown>>`. Cannot find property `default` in object of type `pkl.ref#Reference<reference13#D, Mapping>`.
x | test = ref.Reference(d, Mapping, "").default x | test = ref.Reference(d, Mapping, "").default
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -1,5 +1,5 @@
–– Pkl Error –– –– Pkl Error ––
Cannot find property `default` in object of type `pkl.ref#Reference<reference14#D, Listing<unknown>>`. Cannot find property `default` in object of type `pkl.ref#Reference<reference14#D, Listing>`.
x | test = ref.Reference(d, Listing, "").default x | test = ref.Reference(d, Listing, "").default
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -1,11 +1,11 @@
–– Pkl Error –– –– Pkl Error ––
Cannot find property `name` in object of type `pkl.ref#Reference<reference26#D, Listing<unknown>>`. Cannot find property `name` in object of type `pkl.ref#Reference<reference26#D, Listing>`.
x | res = myRef.name x | res = myRef.name
^^^^^^^^^^ ^^^^^^^^^^
at reference26#res (file:///$snippetsDir/input/errors/reference26.pkl) at reference26#res (file:///$snippetsDir/input/errors/reference26.pkl)
Cannot find property `name` in type `Listing<unknown>`. Cannot find property `name` in type `Listing`.
xxx | renderer.renderDocument(value) xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -1,13 +1,13 @@
–– Pkl Error –– –– Pkl Error ––
Operator `[]` is not defined for operand types `pkl.ref#Reference<reference7#D, List<unknown>>` and `String`. Operator `[]` is not defined for operand types `pkl.ref#Reference<reference7#D, List>` and `String`.
Left operand : Reference(new D {}, List<unknown>, List()) Left operand : Reference(new D {}, List, List())
Right operand: "hi" Right operand: "hi"
x | test = ref.Reference(d, List, List())["hi"] x | test = ref.Reference(d, List, List())["hi"]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at reference7#test (file:///$snippetsDir/input/errors/reference7.pkl) at reference7#test (file:///$snippetsDir/input/errors/reference7.pkl)
Operator `[]` is not defined for operand types `List<unknown>` and `String`. Operator `[]` is not defined for operand types `List` and `String`.
xxx | renderer.renderDocument(value) xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -5,7 +5,7 @@ xx | typealias F<T> = List<Class<T>>
^^^^^^^^ ^^^^^^^^
at classTypeErr11#res22 (file:///$snippetsDir/input/types/classType.pkl) at classTypeErr11#res22 (file:///$snippetsDir/input/types/classType.pkl)
A `Class` type check can only succeed when its type argument is an un-parameterized class, `unknown`, `module`, or an alias to one of those types. A `Class` type check can only succeed when its type argument is an un-parameterized class, `unknown`, `module`, `this`, or an alias to one of those types.
xxx | renderer.renderDocument(value) xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -5,7 +5,7 @@ xx | typealias F<T> = List<Class<T>>
^^^^^^^^ ^^^^^^^^
at classTypeErr12#res22 (file:///$snippetsDir/input/types/classType.pkl) at classTypeErr12#res22 (file:///$snippetsDir/input/types/classType.pkl)
A `Class` type check can only succeed when its type argument is an un-parameterized class, `unknown`, `module`, or an alias to one of those types. A `Class` type check can only succeed when its type argument is an un-parameterized class, `unknown`, `module`, `this`, or an alias to one of those types.
xxx | renderer.renderDocument(value) xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -5,7 +5,7 @@ xx | typealias F<T> = List<Class<T>>
^^^^^^^^ ^^^^^^^^
at classTypeErr13#res22 (file:///$snippetsDir/input/types/classType.pkl) at classTypeErr13#res22 (file:///$snippetsDir/input/types/classType.pkl)
A `Class` type check can only succeed when its type argument is an un-parameterized class, `unknown`, `module`, or an alias to one of those types. A `Class` type check can only succeed when its type argument is an un-parameterized class, `unknown`, `module`, `this`, or an alias to one of those types.
xxx | renderer.renderDocument(value) xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -5,7 +5,7 @@ x | res8 = C as Class<A | B>
^^^^^^^^^^^^ ^^^^^^^^^^^^
at classTypeErr4#res8 (file:///$snippetsDir/input/types/classTypeErr4.pkl) at classTypeErr4#res8 (file:///$snippetsDir/input/types/classTypeErr4.pkl)
A `Class` type check can only succeed when its type argument is an un-parameterized class, `unknown`, `module`, or an alias to one of those types. A `Class` type check can only succeed when its type argument is an un-parameterized class, `unknown`, `module`, `this`, or an alias to one of those types.
xxx | renderer.renderDocument(value) xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -5,7 +5,7 @@ x | res9 = C as Class<A?>
^^^^^^^^^ ^^^^^^^^^
at classTypeErr5#res9 (file:///$snippetsDir/input/types/classTypeErr5.pkl) at classTypeErr5#res9 (file:///$snippetsDir/input/types/classTypeErr5.pkl)
A `Class` type check can only succeed when its type argument is an un-parameterized class, `unknown`, `module`, or an alias to one of those types. A `Class` type check can only succeed when its type argument is an un-parameterized class, `unknown`, `module`, `this`, or an alias to one of those types.
xxx | renderer.renderDocument(value) xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -5,7 +5,7 @@ x | res10 = C as Class<"foo">
^^^^^^^^^^^^ ^^^^^^^^^^^^
at classTypeErr6#res10 (file:///$snippetsDir/input/types/classTypeErr6.pkl) at classTypeErr6#res10 (file:///$snippetsDir/input/types/classTypeErr6.pkl)
A `Class` type check can only succeed when its type argument is an un-parameterized class, `unknown`, `module`, or an alias to one of those types. A `Class` type check can only succeed when its type argument is an un-parameterized class, `unknown`, `module`, `this`, or an alias to one of those types.
xxx | renderer.renderDocument(value) xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -5,7 +5,7 @@ x | res11 = C as Class<nothing>
^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^
at classTypeErr7#res11 (file:///$snippetsDir/input/types/classTypeErr7.pkl) at classTypeErr7#res11 (file:///$snippetsDir/input/types/classTypeErr7.pkl)
A `Class` type check can only succeed when its type argument is an un-parameterized class, `unknown`, `module`, or an alias to one of those types. A `Class` type check can only succeed when its type argument is an un-parameterized class, `unknown`, `module`, `this`, or an alias to one of those types.
xxx | renderer.renderDocument(value) xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -5,7 +5,7 @@ x | res12 = C as Class<A(true)>
^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^
at classTypeErr8#res12 (file:///$snippetsDir/input/types/classTypeErr8.pkl) at classTypeErr8#res12 (file:///$snippetsDir/input/types/classTypeErr8.pkl)
A `Class` type check can only succeed when its type argument is an un-parameterized class, `unknown`, `module`, or an alias to one of those types. A `Class` type check can only succeed when its type argument is an un-parameterized class, `unknown`, `module`, `this`, or an alias to one of those types.
xxx | renderer.renderDocument(value) xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -651,7 +651,8 @@ class CommandSpecParserTest {
val exc = assertThrows<PklException> { parse(moduleUri) } val exc = assertThrows<PklException> { parse(moduleUri) }
assertThat(exc.message).contains("foo: Foo") assertThat(exc.message).contains("foo: Foo")
assertThat(exc.message).contains("Command option property `foo` has unsupported type `Foo`.") assertThat(exc.message)
.contains("Command option property `foo` has unsupported type `cmd#Foo`.")
} }
@Test @Test
@@ -805,7 +806,7 @@ class CommandSpecParserTest {
val spec = parse(moduleUri) val spec = parse(moduleUri)
val flag = spec.options.first() as CommandSpec.Flag val flag = spec.options.first() as CommandSpec.Flag
assertThat(flag.metavar()).isEqualTo("[json, toml, yaml]") assertThat(flag.metavar()).isEqualTo("[json, yaml, toml]")
val apply = val apply =
assertThrows<CommandSpec.Option.BadValue> { assertThrows<CommandSpec.Option.BadValue> {
@@ -0,0 +1,450 @@
/*
* 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 java.net.URI
import java.nio.file.Path
import kotlin.io.path.writeText
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.io.TempDir
import org.pkl.core.Loggers
import org.pkl.core.SecurityManagers
import org.pkl.core.StackFrameTransformers
import org.pkl.core.TypeParameter
import org.pkl.core.evaluatorSettings.TraceMode
import org.pkl.core.http.HttpClient
import org.pkl.core.module.ModuleKey
import org.pkl.core.module.ModuleKeyFactories
import org.pkl.core.module.ModuleKeys.file
import org.pkl.core.module.ModuleKeys.synthetic
class VmTypeTest {
companion object {
val stringClass = VmType.ClassType(BaseModule.getStringClass())
val boolClass = VmType.ClassType(BaseModule.getBooleanClass())
val intClass = VmType.ClassType(BaseModule.getIntClass())
val numberClass = VmType.ClassType(BaseModule.getNumberClass())
}
private fun makeModule(rootDir: Path?, moduleKey: ModuleKey): VmTyped {
val securityManager =
SecurityManagers.standard(
SecurityManagers.defaultAllowedModules,
SecurityManagers.defaultAllowedResources,
SecurityManagers.defaultTrustLevels,
rootDir,
)
var ret: VmTyped? = null
VmUtils.createContext {
val vmContext = VmContext.get(null)
vmContext.initialize(
VmContext.Holder(
StackFrameTransformers.defaultTransformer,
securityManager,
HttpClient.dummyClient(),
ModuleResolver(
listOfNotNull(
ModuleKeyFactories.standardLibrary,
rootDir?.let { ModuleKeyFactories.file },
)
),
ResourceManager(securityManager, listOf()),
Loggers.noop(),
mapOf(),
mapOf(),
null,
null,
null,
null,
TraceMode.COMPACT,
false,
)
)
ret = VmLanguage.get(null).loadModule(moduleKey)
}
.close()
return ret!!
}
private fun makeModule(text: String): VmTyped =
makeModule(null, synthetic(URI("repl:text"), text))
private fun makeModule(dir: Path, path: Path): VmTyped = makeModule(dir, file(path.toUri()))
private fun VmTyped.getTypeForProperty(property: String): VmType =
vmClass.getProperty(Identifier.get(property))!!.typeNode!!.typeNode.type
private fun VmTyped.getTypeForNestedProperty(prop1: String, prop2: String): VmType =
vmClass
.getProperty(Identifier.get(prop1))!!
.typeNode!!
.typeNode
.type
.vmClass!!
.getProperty(Identifier.get(prop2))!!
.typeNode!!
.typeNode
.type
/** Assert this is a subtype of [other] and [other] is a supertype of this, but not equivalent */
private fun VmType.sub(other: VmType) {
assertThat(this.isSubtypeOf(other)).`as`("%s isSubtypeOf %s", this, other).isTrue
assertThat(other.isSupertypeOf(this)).`as`("%s isSupertypeOf %s", other, this).isTrue
assertThat(other.isSubtypeOf(this)).`as`("%s isSubtypeOf %s", other, this).isFalse
assertThat(this.isSupertypeOf(other)).`as`("%s isSupertypeOf %s", this, other).isFalse
assertThat(this == other).`as`("%s == %s", this, other).isFalse
assertThat(other == this).`as`("%s == %s", other, this).isFalse
}
/**
* Assert this is a subtype of [other] and [other] is a supertype of this and they are equivalent
*/
private fun VmType.eq(other: VmType) {
assertThat(this.isSubtypeOf(other)).`as`("%s isSubtypeOf %s", this, other).isTrue
assertThat(other.isSupertypeOf(this)).`as`("%s isSupertypeOf %s", other, this).isTrue
assertThat(other.isSubtypeOf(this)).`as`("%s isSubtypeOf %s", other, this).isTrue
assertThat(this.isSupertypeOf(other)).`as`("%s isSupertypeOf %s", this, other).isTrue
assertThat(this == other).`as`("%s == %s", this, other).isTrue
assertThat(other == this).`as`("%s == %s", other, this).isTrue
}
/** Assert this is a subtype of [other] and [other] is a subtype of this, but not equivalent */
private fun VmType.bidi(other: VmType) {
assertThat(this.isSubtypeOf(other)).`as`("%s isSubtypeOf %s", this, other).isTrue
assertThat(other.isSupertypeOf(this)).`as`("%s isSupertypeOf %s", other, this).isTrue
assertThat(other.isSubtypeOf(this)).`as`("%s isSubtypeOf %s", other, this).isTrue
assertThat(this.isSupertypeOf(other)).`as`("%s isSupertypeOf %s", this, other).isTrue
assertThat(this == other).`as`("%s == %s", this, other).isFalse
assertThat(other == this).`as`("%s == %s", other, this).isFalse
}
@Test
fun `unknown - equality (referential)`() {
VmType.UnknownType.INSTANCE.eq(VmType.UnknownType.INSTANCE)
}
@Test
fun `unknown - subtype and supertype of everything`() {
VmType.UnknownType.INSTANCE.bidi(VmType.ClassType(BaseModule.getStringClass()))
}
@Test
fun `nothing - equality (referential)`() {
VmType.NothingType.INSTANCE.eq(VmType.NothingType.INSTANCE)
}
@Test
fun `nothing - subtype of everything`() {
VmType.NothingType.INSTANCE.sub(VmType.ClassType(BaseModule.getStringClass()))
VmType.NothingType.INSTANCE.sub(VmType.StringLiteralType("foo"))
}
@Test
fun `final self - equality`(@TempDir tempDir: Path) {
val modPath = tempDir.resolve("test.pkl")
modPath.writeText(
"""
module mod
import "test.pkl" as Declared
hidden moduleType: module
hidden moduleType2: module
hidden thisType: this
hidden thisType2: this
hidden declaredType: Declared
"""
.trimIndent()
)
val mod = makeModule(tempDir, modPath)
assertThat(mod.vmClass.isOpen).isFalse
mod.getTypeForProperty("moduleType").eq(mod.getTypeForProperty("moduleType2"))
mod.getTypeForProperty("moduleType").eq(mod.getTypeForProperty("thisType"))
mod.getTypeForProperty("moduleType").eq(mod.getTypeForProperty("declaredType"))
mod.getTypeForProperty("thisType").eq(mod.getTypeForProperty("thisType2"))
mod.getTypeForProperty("thisType").eq(mod.getTypeForProperty("declaredType"))
}
@Test
fun `non-final self - equality`() {
val mod =
makeModule(
"""
open module mod
hidden moduleType: module
hidden moduleType2: module
hidden thisType: this
hidden thisType2: this
"""
.trimIndent()
)
assertThat(mod.vmClass.isOpen).isTrue
mod.getTypeForProperty("moduleType").eq(mod.getTypeForProperty("moduleType2"))
mod.getTypeForProperty("moduleType").eq(mod.getTypeForProperty("thisType"))
mod.getTypeForProperty("thisType").eq(mod.getTypeForProperty("thisType2"))
}
@Test
fun `non-final self - comparison to extended type`(@TempDir tempDir: Path) {
val parentPath = tempDir.resolve("parent.pkl")
parentPath.writeText(
"""
open module parent
moduleType: module
thisType: this
"""
.trimIndent()
)
val modPath = tempDir.resolve("test.pkl")
modPath.writeText(
"""
open module mod
extends "parent.pkl"
import "parent.pkl" as Parent
parent: Parent
child: Child
moduleType2: module
thisType2: this
open class Child extends module {
// no moduleType3 because module type usage in class bodies is deprecated
thisType3: this
}
"""
.trimIndent()
)
val mod = makeModule(tempDir, modPath)
mod.getTypeForProperty("moduleType").eq(mod.getTypeForNestedProperty("parent", "moduleType"))
mod.getTypeForProperty("thisType").eq(mod.getTypeForNestedProperty("parent", "moduleType"))
mod.getTypeForProperty("moduleType").eq(mod.getTypeForNestedProperty("parent", "thisType"))
mod.getTypeForProperty("thisType").eq(mod.getTypeForNestedProperty("parent", "thisType"))
mod.getTypeForProperty("moduleType2").sub(mod.getTypeForNestedProperty("parent", "moduleType"))
mod.getTypeForProperty("thisType2").sub(mod.getTypeForNestedProperty("parent", "moduleType"))
mod.getTypeForProperty("moduleType2").sub(mod.getTypeForNestedProperty("parent", "thisType"))
mod.getTypeForProperty("thisType2").sub(mod.getTypeForNestedProperty("parent", "thisType"))
mod
.getTypeForNestedProperty("child", "moduleType")
.eq(mod.getTypeForNestedProperty("parent", "moduleType"))
mod
.getTypeForNestedProperty("child", "thisType")
.eq(mod.getTypeForNestedProperty("parent", "moduleType"))
mod
.getTypeForNestedProperty("child", "moduleType")
.eq(mod.getTypeForNestedProperty("parent", "thisType"))
mod
.getTypeForNestedProperty("child", "thisType")
.eq(mod.getTypeForNestedProperty("parent", "thisType"))
mod
.getTypeForNestedProperty("child", "thisType3")
.sub(mod.getTypeForNestedProperty("parent", "moduleType"))
mod
.getTypeForNestedProperty("child", "thisType3")
.sub(mod.getTypeForNestedProperty("parent", "thisType"))
mod.getTypeForNestedProperty("child", "moduleType").eq(mod.getTypeForProperty("moduleType"))
mod.getTypeForNestedProperty("child", "thisType").eq(mod.getTypeForProperty("moduleType"))
mod.getTypeForNestedProperty("child", "moduleType").eq(mod.getTypeForProperty("thisType"))
mod.getTypeForNestedProperty("child", "thisType").eq(mod.getTypeForProperty("thisType"))
mod.getTypeForNestedProperty("child", "thisType3").sub(mod.getTypeForProperty("moduleType2"))
mod.getTypeForNestedProperty("child", "thisType3").sub(mod.getTypeForProperty("thisType2"))
// non-final self type subtype of declared type that is superclass of self type's "base"
// class types are never subtypes of non-final self types
mod.getTypeForProperty("moduleType").sub(mod.getTypeForProperty("parent"))
mod.getTypeForProperty("thisType").sub(mod.getTypeForProperty("parent"))
mod.getTypeForProperty("moduleType2").sub(mod.getTypeForProperty("parent"))
mod.getTypeForProperty("thisType2").sub(mod.getTypeForProperty("parent"))
mod.getTypeForNestedProperty("child", "thisType3").sub(mod.getTypeForProperty("parent"))
}
@Test
fun `string literal - equality`() {
VmType.StringLiteralType("foo").eq(VmType.StringLiteralType("foo"))
}
@Test
fun `string literal - subtype of String`() {
VmType.StringLiteralType("foo").sub(VmType.ClassType(BaseModule.getStringClass()))
}
@Test
fun `class - equality, unparameterized`() {
VmType.ClassType(BaseModule.getBooleanClass())
.eq(VmType.ClassType(BaseModule.getBooleanClass()))
}
@Test
fun `class - equality, unparameterized generic`() {
VmType.ClassType(BaseModule.getListClass()).eq(VmType.ClassType(BaseModule.getListClass()))
}
@Test
fun `class - equality, parameterized generic`() {
VmType.ClassType(BaseModule.getMappingClass(), stringClass, boolClass)
.eq(VmType.ClassType(BaseModule.getMappingClass(), stringClass, boolClass))
}
@Test
fun `class - subtype, unparameterized generic`() {
VmType.ClassType(BaseModule.getListClass())
.sub(VmType.ClassType(BaseModule.getCollectionClass()))
}
@Test
fun `class - subtype of any`() {
VmType.ClassType(BaseModule.getListClass()).sub(VmType.ClassType(BaseModule.getAnyClass()))
VmType.ClassType(BaseModule.getListClass(), stringClass)
.sub(VmType.ClassType(BaseModule.getAnyClass()))
}
@Test
fun `class - subtype of unparameterized`() {
VmType.ClassType(BaseModule.getListClass(), stringClass)
.sub(VmType.ClassType(BaseModule.getListClass()))
}
@Test
fun `class - subtype, generic with substitution`() {
VmType.ClassType(BaseModule.getFunction1Class(), stringClass, boolClass)
.sub(VmType.ClassType(BaseModule.getFunctionClass(), boolClass))
VmType.ClassType(BaseModule.getFunction1Class(), stringClass, intClass)
.sub(VmType.ClassType(BaseModule.getFunctionClass(), numberClass))
}
@Test
fun `class - special cases`() {
VmType.AliasType(BaseModule.getCharTypeAlias()).sub(stringClass)
for (cls in listOf(intClass, numberClass)) {
VmType.AliasType(BaseModule.getInt8TypeAlias()).sub(cls)
VmType.AliasType(BaseModule.getInt16TypeAlias()).sub(cls)
VmType.AliasType(BaseModule.getInt32TypeAlias()).sub(cls)
VmType.AliasType(BaseModule.getUIntTypeAlias()).sub(cls)
VmType.AliasType(BaseModule.getUInt8TypeAlias()).sub(cls)
VmType.AliasType(BaseModule.getUInt16TypeAlias()).sub(cls)
VmType.AliasType(BaseModule.getUInt32TypeAlias()).sub(cls)
}
}
@Test
fun `nullable - class is subtype of nullable class`() {
stringClass.sub(VmType.NullableType(stringClass))
intClass.sub(VmType.NullableType(numberClass))
}
@Test
fun `nullable - subtype of nullable supertype`() {
VmType.NullableType(intClass).sub(VmType.NullableType(numberClass))
}
@Test
fun `constrained - equality (identity)`() {
VmType.ConstrainedType(stringClass, arrayOf("true"), 0)
.eq(VmType.ConstrainedType(stringClass, arrayOf("true"), 0))
}
@Test
fun `constrained - subtype when base type is supertype`() {
VmType.ConstrainedType(stringClass, arrayOf("true"), 0).sub(stringClass)
VmType.ConstrainedType(intClass, arrayOf("true"), 0).sub(numberClass)
}
@Test
fun `alias - equality`() {
val mod =
makeModule(
"""
typealias A = Int
typealias B = A
typealias C = B
int: Int
a: A
b: B
c: C
"""
.trimIndent()
)
mod.getTypeForProperty("int").eq(mod.getTypeForProperty("a"))
mod.getTypeForProperty("int").eq(mod.getTypeForProperty("b"))
mod.getTypeForProperty("int").eq(mod.getTypeForProperty("c"))
mod.getTypeForProperty("a").eq(mod.getTypeForProperty("b"))
mod.getTypeForProperty("a").eq(mod.getTypeForProperty("c"))
intClass.eq(mod.getTypeForProperty("b"))
}
@Test
fun `alias - subtype`() {
val mod =
makeModule(
"""
typealias A = Int
typealias B = A
typealias C = B
int: Int
a: A
b: B
c: C
"""
.trimIndent()
)
mod.getTypeForProperty("c").sub(numberClass)
}
@Test
fun `union - equality (ignores default index)`() {
VmType.UnionType(-1, arrayOf("foo", "bar", "baz"))
.eq(VmType.UnionType(0, arrayOf("foo", "bar", "baz")))
}
@Test
@Disabled("TODO: support order-independent union type equivalence")
fun `union - equality, order-independent`() {
VmType.UnionType(-1, arrayOf("foo", "bar", "baz"))
.eq(VmType.UnionType(-1, arrayOf("bar", "baz", "foo")))
}
@Test
fun `union - subtype when all elements are subtype`() {
VmType.UnionType(-1, arrayOf("foo", "bar", "baz")).sub(stringClass)
}
@Test
fun `union - subtype of union when all elements are contained in supertype`() {
VmType.UnionType(-1, arrayOf("foo", "bar", "baz"))
.sub(VmType.UnionType(-1, arrayOf("foo", "bar", "baz", "qux")))
}
@Test
fun `type variable - treated like unknown, supertype and subtype to all`() {
VmType.TypeVariableType(TypeParameter(TypeParameter.Variance.INVARIANT, "Foo", 0))
.bidi(stringClass)
}
}