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" />
</inspection_tool>
<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">
<option name="myConfigurations">
<list>
@@ -107,4 +108,4 @@
<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" />
</profile>
</component>
</component>
@@ -124,7 +124,7 @@ public abstract class SubscriptNode extends BinaryExpressionNode {
CompilerDirectives.transferToInterpreter();
throw exceptionBuilder()
.evalError(
"operatorNotDefined2", getShortName(), reference.exportType(), VmUtils.getClass(key))
"operatorNotDefined2", getShortName(), reference.getType(), VmUtils.getClass(key))
.withProgramValue("Left operand", reference)
.withProgramValue("Right operand", key)
.withHint(getReferenceHint(reference, err, key))
@@ -41,9 +41,9 @@ public final class CheckIsAnnotationClassNode extends ExpressionNode {
typeNode = insert(unresolvedTypeNode.execute(frame));
unresolvedTypeNode = null;
}
var clazz = typeNode.getVmClass();
var clazz = typeNode.getType().getVmClass();
if (clazz != null && clazz.isSubclassOf(BaseModule.getAnnotationClass())) {
return typeNode.getVmClass();
return clazz;
}
CompilerDirectives.transferToInterpreter();
@@ -57,7 +57,7 @@ public abstract class AbstractInferParentNode extends ExpressionNode {
}
// try to produce a more specific error message than "cannotInstantiateType"
var clazz = typeNode.getVmClass();
var clazz = typeNode.getType().getVmClass();
if (clazz != null) {
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.source.SourceSection;
import org.jspecify.annotations.Nullable;
import org.pkl.core.PType;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.MemberLookupMode;
import org.pkl.core.ast.member.ClassProperty;
@@ -77,7 +76,7 @@ public abstract class ReadPropertyNode extends ExpressionNode {
case DEFAULT_MEMBER ->
ErrorMessages.create(
"cannotReferenceDefaultProperty",
((PType.Class) err.getType()).getPClass().getSimpleName());
((VmType.ClassType) err.getType()).getVmClass().getSimpleName());
case EXTERNAL_CLASS ->
ErrorMessages.create(
"cannotReferencePropertyInExternalClass", propertyName, err.getType());
@@ -92,7 +91,7 @@ public abstract class ReadPropertyNode extends ExpressionNode {
} catch (VmReferenceAccessError err) {
CompilerDirectives.transferToInterpreter();
throw exceptionBuilder()
.evalError("cannotFindPropertyInObjectNoHint", propertyName, receiver.exportType())
.evalError("cannotFindPropertyInObjectNoHint", propertyName, receiver.getType())
.withHint(getReferenceErrorHint(receiver, err))
.build();
}
@@ -129,7 +129,7 @@ public final class ClassNode extends ExpressionNode {
try {
if (unresolvedSupertypeNode != null) {
var supertypeNode = unresolvedSupertypeNode.execute(frame);
var superclass = supertypeNode.getVmClass();
var superclass = supertypeNode.getType().getVmClass();
checkSupertype(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.type.TypeNode;
import org.pkl.core.runtime.*;
import org.pkl.core.runtime.VmType.UnknownType;
import org.pkl.core.util.CollectionUtils;
import org.pkl.core.util.Pair;
@@ -86,6 +87,10 @@ public final class FunctionNode extends RegularMemberNode {
return parameterTypeNodes[idx];
}
public VmType getReturnType() {
return returnTypeNode == null ? UnknownType.INSTANCE : returnTypeNode.getType();
}
public @Nullable TypeNode getReturnTypeNode() {
return returnTypeNode;
}
File diff suppressed because it is too large. Load diff
@@ -198,13 +198,13 @@ public abstract class UnresolvedTypeNode extends PklNode {
case "Int16":
return new Int16TypeAliasTypeNode();
case "UInt16":
return new UIntTypeAliasTypeNode(alias, 0x000000000000FFFFL);
return new IntMaskSlotTypeNode(alias, 0x000000000000FFFFL);
case "Int32":
return new Int32TypeAliasTypeNode();
case "UInt32":
return new UIntTypeAliasTypeNode(alias, 0x00000000FFFFFFFFL);
return new IntMaskSlotTypeNode(alias, 0x00000000FFFFFFFFL);
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.stream.Collectors;
import org.jspecify.annotations.Nullable;
import org.pkl.core.PType;
import org.pkl.core.StackFrame;
import org.pkl.core.ValueFormatter;
import org.pkl.core.ast.type.TypeNode.UnionTypeNode;
@@ -95,6 +94,7 @@ public abstract class VmTypeMismatchException extends ControlFlowException {
assert expectedType instanceof VmClass
|| expectedType instanceof VmTypeAlias
|| expectedType instanceof VmType
|| expectedType instanceof String // string literal type
|| 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
.append(
ErrorMessages.createIndented(
"typeMismatch",
indent,
renderedType,
VmUtils.getClass(actualValue).toPklString()))
.append(ErrorMessages.createIndented("typeMismatch", indent, renderedType, valueType))
.append("\n")
.append(indent)
.append("Value: ")
@@ -183,10 +181,10 @@ public abstract class VmTypeMismatchException extends ControlFlowException {
renderedExpected = "Class<" + expectedClass.toPklString() + ">";
}
public ClassType(SourceSection sourceSection, VmClass actualClass, PType expectedType) {
public ClassType(SourceSection sourceSection, VmClass actualClass, VmType expectedType) {
super(sourceSection, actualClass);
this.expectedClass = null;
renderedExpected = "Class<" + expectedType + ">";
renderedExpected = expectedType.toString();
}
@Override
@@ -400,44 +398,4 @@ public abstract class VmTypeMismatchException extends ControlFlowException {
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.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
@@ -175,12 +176,12 @@ public final class CommandSpecParser {
}
if (!(optionsTypeNode instanceof TypeNode.UserClassTypeNode node)) {
throw exceptionBuilder()
.withSourceSection(optionsTypeNode.getSourceSection())
.withSourceSection(optionsPropertyTypeNode.getSourceSection())
.evalError(
"commandOptionsTypeNotClass", optionsTypeNode.getSourceSection().getCharacters())
.build();
}
var clazz = node.getVmClass();
var clazz = ((VmType.ClassType) node.getType()).getVmClass();
if (clazz.isAbstract()) {
throw exceptionBuilder()
.withSourceSection(clazz.getHeaderSection())
@@ -308,14 +309,14 @@ public final class CommandSpecParser {
// assert type is Boolean
var typeInfo = resolveType(prop);
if (!(typeInfo.getFirst() instanceof TypeNode.BooleanTypeNode)) {
if (typeInfo.getFirst().getVmClass() != BaseModule.getBooleanClass()) {
throw exceptionBuilder()
.withSourceSection(prop.getHeaderSection())
.evalError(
"commandFlagInvalidType",
prop.getName(),
"BooleanFlag",
typeInfo.getFirst().getSourceSection().getCharacters(),
typeInfo.getFirst().toString(),
"Boolean")
.build();
}
@@ -342,7 +343,7 @@ public final class CommandSpecParser {
"commandFlagInvalidType",
prop.getName(),
"CountedFlag",
typeInfo.getFirst().getSourceSection().getCharacters(),
typeInfo.getFirst().toString(),
"Int")
.build();
}
@@ -385,10 +386,10 @@ public final class CommandSpecParser {
}
/** 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();
if (propertyTypeNode != null) {
return resolveType(propertyTypeNode.getTypeNode());
return resolveType(propertyTypeNode.getTypeNode().getType());
}
throw exceptionBuilder()
.withSourceSection(prop.getHeaderSection())
@@ -397,23 +398,23 @@ public final class CommandSpecParser {
}
/** 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;
while (true) {
if (typeNode instanceof TypeNode.NullableTypeNode nullableTypeNode) {
if (type instanceof VmType.NullableType nullableType) {
isNullable = true;
typeNode = nullableTypeNode.getElementTypeNode();
} else if (typeNode instanceof TypeNode.ConstrainedTypeNode constrainedTypeNode) {
typeNode = constrainedTypeNode.getChildTypeNode();
} else if (typeNode instanceof TypeNode.TypeAliasTypeNode typeAliasTypeNode) {
if (typeAliasTypeNode.getVmTypeAlias() == BaseModule.getCharTypeAlias()) break;
typeNode = typeAliasTypeNode.getAliasedTypeNode();
type = nullableType.getElementType();
} else if (type instanceof VmType.ConstrainedType constrainedType) {
type = constrainedType.getBaseType();
} else if (type instanceof VmType.AliasType aliasType) {
if (aliasType.getVmTypeAlias() == BaseModule.getCharTypeAlias()) break;
type = aliasType.getAliasedType();
} else {
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.
@@ -447,10 +448,12 @@ public final class CommandSpecParser {
// wrapped by a nullable, no default value
return null;
}
if (resolved.getFirst() instanceof TypeNode.UnionOfStringLiteralsTypeNode union
&& union.getUnionDefault() != null) {
return union.getUnionDefault();
} else if (resolved.getFirst() instanceof TypeNode.StringLiteralTypeNode literal) {
if (resolved.getFirst() instanceof VmType.UnionType union
&& union.getDefaultIndex() != -1
&& union.isUnionOfStringLiterals()) {
return ((VmType.StringLiteralType) union.getElementTypes()[union.getDefaultIndex()])
.getLiteral();
} else if (resolved.getFirst() instanceof VmType.StringLiteralType literal) {
return literal.getLiteral();
}
}
@@ -509,130 +512,151 @@ public final class CommandSpecParser {
public OptionBehavior resolve(ClassProperty prop, boolean requireExplicitDefault) {
var resolved = resolveType(prop);
var typeNode = resolved.getFirst();
var type = resolved.getFirst();
isNullable = resolved.getSecond();
defaultValue = CommandSpecParser.this.getDefaultValue(prop, requireExplicitDefault);
resolve(prop, typeNode);
resolve(prop, type);
return this;
}
private void resolve(ClassProperty prop, TypeNode typeNode) {
if (resolvePrimitive(typeNode)) {
private void resolve(ClassProperty prop, VmType type) {
if (resolvePrimitive(type)) {
return;
}
if (typeNode instanceof TypeNode.ListingTypeNode listingTypeNode) {
handleElement(listingTypeNode.getValueTypeNode(), prop);
if (multiple == null) multiple = true;
if (all == null)
all =
!multiple
? this::allChooseLast
: (values, workingDirUri) -> {
if (values.isEmpty()) return null;
var builder = new VmObjectBuilder();
values.forEach(builder::addElement);
return builder.toListing();
};
} else if (typeNode instanceof TypeNode.MappingTypeNode mappingTypeNode) {
assert mappingTypeNode.getKeyTypeNode() != null;
handleEntry(mappingTypeNode.getKeyTypeNode(), mappingTypeNode.getValueTypeNode(), prop);
if (multiple == null) multiple = true;
if (all == null)
all =
!multiple
? this::allChooseLast
: (values, workingDirUri) -> {
if (values.isEmpty()) return null;
var builder = new VmObjectBuilder();
values.forEach(
(entry) ->
builder.addEntry(
((VmPair) entry).getFirst(), ((VmPair) entry).getSecond()));
return builder.toMapping();
};
} else if (typeNode instanceof TypeNode.ListTypeNode listTypeNode) {
handleElement(listTypeNode.getElementTypeNode(), prop);
if (multiple == null) multiple = true;
if (all == null)
all =
!multiple
? this::allChooseLast
: (values, workingDirUri) -> values.isEmpty() ? null : VmList.create(values);
} else if (typeNode instanceof TypeNode.SetTypeNode setTypeNode) {
handleElement(setTypeNode.getElementTypeNode(), prop);
if (multiple == null) multiple = true;
if (all == null)
all =
!multiple
? this::allChooseLast
: (values, workingDirUri) -> values.isEmpty() ? null : VmSet.create(values);
} else if (typeNode instanceof TypeNode.MapTypeNode mapTypeNode) {
handleEntry(mapTypeNode.getKeyTypeNode(), mapTypeNode.getValueTypeNode(), prop);
if (multiple == null) multiple = true;
if (all == null)
all =
!multiple
? this::allChooseLast
: (values, workingDirUri) -> {
if (values.isEmpty()) return null;
var builder = VmMap.builder();
values.forEach(
(entry) ->
builder.add(((VmPair) entry).getFirst(), ((VmPair) entry).getSecond()));
return builder.build();
};
} else if (typeNode instanceof TypeNode.PairTypeNode pairTypeNode) {
handleEntry(pairTypeNode.getFirstTypeNode(), pairTypeNode.getSecondTypeNode(), prop);
if (all == null) all = this::allChooseLast;
if (multiple == null) multiple = false;
} else if (typeNode instanceof TypeNode.FinalClassTypeNode finalClassTypeNode
&& (finalClassTypeNode.getVmClass() == BaseModule.getListingClass()
|| finalClassTypeNode.getVmClass() == BaseModule.getMappingClass()
|| finalClassTypeNode.getVmClass() == BaseModule.getListClass()
|| finalClassTypeNode.getVmClass() == BaseModule.getSetClass()
|| finalClassTypeNode.getVmClass() == BaseModule.getMapClass()
|| finalClassTypeNode.getVmClass() == BaseModule.getPairClass())) {
// if a supported type is provided without type arguments
throw exceptionBuilder()
.withSourceSection(prop.getHeaderSection())
.evalError(
"commandOptionUnsupportedType",
prop.getName(),
"",
typeNode.getSourceSection().getCharacters())
.withHint(
finalClassTypeNode.getVmClass().getSimpleName()
+ " options must provide "
+ switch (finalClassTypeNode.getVmClass().getTypeParameterCount()) {
case 1 -> "one type argument.";
case 2 -> "two type arguments.";
default -> throw PklBugException.unreachableCode();
})
.build();
} else if (each == null && all == null) {
if (type instanceof VmType.ClassType ct) {
if (ct.getVmClass() == BaseModule.getListingClass()) {
handleElement(prop, ct);
if (multiple == null) {
multiple = true;
}
if (all == null) {
all =
!multiple
? this::allChooseLast
: (values, workingDirUri) -> {
if (values.isEmpty()) return null;
var builder = new VmObjectBuilder();
values.forEach(builder::addElement);
return builder.toListing();
};
}
} else if (ct.getVmClass() == BaseModule.getMappingClass()) {
handleEntry(prop, ct);
if (multiple == null) {
multiple = true;
}
if (all == null) {
all =
!multiple
? this::allChooseLast
: (values, workingDirUri) -> {
if (values.isEmpty()) return null;
var builder = new VmObjectBuilder();
values.forEach(
(entry) ->
builder.addEntry(
((VmPair) entry).getFirst(), ((VmPair) entry).getSecond()));
return builder.toMapping();
};
}
} else if (ct.getVmClass() == BaseModule.getListClass()) {
handleElement(prop, ct);
if (multiple == null) {
multiple = true;
}
if (all == null) {
all =
!multiple
? this::allChooseLast
: (values, workingDirUri) -> values.isEmpty() ? null : VmList.create(values);
}
} else if (ct.getVmClass() == BaseModule.getSetClass()) {
handleElement(prop, ct);
if (multiple == null) {
multiple = true;
}
if (all == null) {
all =
!multiple
? this::allChooseLast
: (values, workingDirUri) -> values.isEmpty() ? null : VmSet.create(values);
}
} else if (ct.getVmClass() == BaseModule.getMapClass()) {
handleEntry(prop, ct);
if (multiple == null) {
multiple = true;
}
if (all == null) {
all =
!multiple
? this::allChooseLast
: (values, workingDirUri) -> {
if (values.isEmpty()) return null;
var builder = VmMap.builder();
values.forEach(
(entry) ->
builder.add(
((VmPair) entry).getFirst(), ((VmPair) entry).getSecond()));
return builder.build();
};
}
} else if (ct.getVmClass() == BaseModule.getPairClass()) {
handleEntry(prop, ct);
if (all == null) {
all = this::allChooseLast;
}
if (multiple == null) {
multiple = false;
}
}
}
if (each == null && all == null) {
// if another type and no transform functions are provided, that's an error
throw exceptionBuilder()
.withSourceSection(prop.getHeaderSection())
.evalError(
"commandOptionUnsupportedType",
prop.getName(),
"",
typeNode.getSourceSection().getCharacters())
.withHint("Use a supported type or define a transformEach and/or transformAll function")
.build();
throw unsupportedOptionNoTransform(prop, type);
} else {
// if we have at least one transform then allow the type and fill in reasonable defaults
if (each == null) each = (rawValue, workingDirUri) -> rawValue;
if (all == null) all = this::allChooseLast;
if (multiple == null) multiple = false;
if (metavar == null) metavar = METAVAR_VALUE;
if (each == null) {
each = (rawValue, workingDirUri) -> rawValue;
}
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(
ClassProperty prop, TypeNode typeNode, String typeArgumentName) {
if (resolvePrimitive(typeNode)) {
ClassProperty prop, VmType type, String typeArgumentName) {
if (resolvePrimitive(type)) {
return this;
}
@@ -644,7 +668,7 @@ public final class CommandSpecParser {
"commandOptionUnsupportedType",
prop.getName(),
typeArgumentName + " ",
typeNode.getSourceSection().getCharacters())
type.toString())
.withHint("Use a supported type or define a transformEach and/or transformAll function")
.build();
} else if (metavar == null) {
@@ -656,138 +680,252 @@ public final class CommandSpecParser {
return this;
}
private boolean resolvePrimitive(TypeNode typeNode) {
if (typeNode instanceof TypeNode.NumberTypeNode) {
if (each == null)
each =
(rawValue, workingDirUri) -> {
try {
return Long.parseLong(rawValue);
} catch (NumberFormatException e) {
private boolean resolvePrimitive(VmType type) {
if (type instanceof VmType.ClassType ct) {
if (ct.getVmClass() == BaseModule.getNumberClass()) {
if (each == null) {
each =
(rawValue, workingDirUri) -> {
try {
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 {
return Double.parseDouble(rawValue);
} catch (NumberFormatException e2) {
throw BadValue.invalid(rawValue, METAVAR_NUMBER);
} catch (NumberFormatException e) {
throw BadValue.invalid(rawValue, METAVAR_FLOAT);
}
}
};
if (all == null) all = this::allChooseLast;
if (multiple == null) multiple = false;
if (metavar == null) metavar = METAVAR_NUMBER;
return true;
} else if (typeNode instanceof TypeNode.FloatTypeNode) {
if (each == null)
each =
(rawValue, workingDirUri) -> {
try {
return Double.parseDouble(rawValue);
} catch (NumberFormatException e) {
throw BadValue.invalid(rawValue, METAVAR_FLOAT);
}
};
if (all == null) all = this::allChooseLast;
if (multiple == null) multiple = false;
if (metavar == null) metavar = METAVAR_FLOAT;
return true;
} else if (typeNode instanceof TypeNode.IntTypeNode) {
if (each == null) each = eachLong(Long.MIN_VALUE, Long.MAX_VALUE, METAVAR_INT);
if (all == null) all = this::allChooseLast;
if (multiple == null) multiple = false;
if (metavar == null) metavar = METAVAR_INT;
return true;
} else if (typeNode instanceof TypeNode.Int8TypeAliasTypeNode) {
if (each == null) each = eachLong(Byte.MIN_VALUE, Byte.MAX_VALUE, METAVAR_INT8);
if (all == null) all = this::allChooseLast;
if (multiple == null) multiple = false;
if (metavar == null) metavar = METAVAR_INT8;
return true;
} else if (typeNode instanceof TypeNode.Int16TypeAliasTypeNode) {
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 (typeNode instanceof TypeNode.Int32TypeAliasTypeNode) {
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 (typeNode instanceof TypeNode.UIntTypeAliasTypeNode uIntTypeAliasTypeNode) {
var mask = uIntTypeAliasTypeNode.getMask();
if (all == null) all = this::allChooseLast;
if (multiple == null) multiple = false;
if (mask == 0x000000000000FFFFL) {
if (each == null) each = eachLong(0, 0x000000000000FFFFL, METAVAR_UINT16);
if (metavar == null) metavar = METAVAR_UINT16;
} else if (mask == 0x00000000FFFFFFFFL) {
if (each == null) each = eachLong(0, 0x00000000FFFFFFFFL, METAVAR_UINT32);
if (metavar == null) metavar = METAVAR_UINT32;
} else {
if (each == null) each = eachLong(0, Long.MAX_VALUE, METAVAR_UINT);
if (metavar == null) metavar = METAVAR_UINT;
};
}
if (all == null) {
all = this::allChooseLast;
}
if (multiple == null) {
multiple = false;
}
if (metavar == null) {
metavar = METAVAR_FLOAT;
}
return true;
} else if (ct.getVmClass() == BaseModule.getIntClass()) {
if (each == null) {
each = eachLong(Long.MIN_VALUE, Long.MAX_VALUE, METAVAR_INT);
}
if (all == null) {
all = this::allChooseLast;
}
if (multiple == null) {
multiple = false;
}
if (metavar == null) {
metavar = METAVAR_INT;
}
return true;
} else if (ct.getVmClass() == BaseModule.getBooleanClass()) {
if (each == null) {
each =
(rawValue, workingDirUri) -> {
var value = rawValue.toLowerCase(Locale.ROOT);
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 (ct.getVmClass() == BaseModule.getStringClass()) {
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;
}
return true;
} else if (typeNode instanceof TypeNode.UInt8TypeAliasTypeNode) {
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 (typeNode instanceof TypeNode.BooleanTypeNode) {
if (each == null)
} else if (type instanceof VmType.AliasType at) {
if (at.getVmTypeAlias() == BaseModule.getInt8TypeAlias()) {
if (each == null) {
each = eachLong(Byte.MIN_VALUE, Byte.MAX_VALUE, METAVAR_INT8);
}
if (all == null) {
all = this::allChooseLast;
}
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 =
(rawValue, workingDirUri) -> {
var value = rawValue.toLowerCase(Locale.ROOT);
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)) {
if (!choiceSet.contains(rawValue)) {
throw BadValue.invalidChoice(rawValue, choices);
}
return rawValue;
};
if (all == null) all = this::allChooseLast;
if (multiple == null) multiple = false;
if (metavar == null) metavar = "[" + String.join(", ", choices) + "]";
if (completionCandidates == null)
completionCandidates = new Fixed(unionOfStringLiteralsTypeNode.getStringLiterals());
}
if (all == null) {
all = this::allChooseLast;
}
if (multiple == null) {
multiple = false;
}
if (metavar == null) {
metavar = "[" + String.join(", ", choices) + "]";
}
if (completionCandidates == null) {
completionCandidates = new Fixed(choiceSet);
}
return true;
} else if (typeNode instanceof TypeNode.StringLiteralTypeNode stringLiteralTypeNode) {
var choice = stringLiteralTypeNode.getLiteral();
if (each == null)
} else if (type instanceof VmType.StringLiteralType slt) {
var choice = slt.getLiteral();
if (each == null) {
each =
(rawValue, workingDirUri) -> {
if (!rawValue.equals(choice)) {
@@ -795,10 +933,19 @@ public final class CommandSpecParser {
}
return rawValue;
};
if (all == null) all = this::allChooseLast;
if (multiple == null) multiple = false;
if (metavar == null) metavar = "[" + choice + "]";
if (completionCandidates == null) completionCandidates = new Fixed(Set.of(choice));
}
if (all == null) {
all = this::allChooseLast;
}
if (multiple == null) {
multiple = false;
}
if (metavar == null) {
metavar = "[" + choice + "]";
}
if (completionCandidates == null) {
completionCandidates = new Fixed(Set.of(choice));
}
return true;
}
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");
/** 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;
var transformValue =
new OptionBehavior(each, all, multiple, metavar, completionCandidates)
@@ -833,7 +983,11 @@ public final class CommandSpecParser {
}
/** 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;
var transformKey =
new OptionBehavior(each, all, multiple, metavar, completionCandidates)
@@ -841,7 +995,7 @@ public final class CommandSpecParser {
var transformValue =
new OptionBehavior(each, all, multiple, metavar, completionCandidates)
.resolveTypeArgument(prop, resolveType(valueType).getFirst(), "value");
if (each == null)
if (each == null) {
each =
(rawValue, workingDirUri) -> {
var split = rawValue.split("=", 2);
@@ -852,7 +1006,10 @@ public final class CommandSpecParser {
transformKey.getEach().apply(split[0], 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) {
@@ -223,7 +223,7 @@ public final class MirrorFactories {
.addTypedProperty(
"referent",
typeNode -> {
var clazz = typeNode.getVmClass();
var clazz = typeNode.getType().getVmClass();
assert clazz != null;
return clazz.getMirror();
})
@@ -232,11 +232,7 @@ public final class MirrorFactories {
typeAliasTypeFactory
.addTypedProperty(
"referent",
typeNode -> {
var alias = typeNode.getVmTypeAlias();
assert alias != null;
return alias.getMirror();
})
typeNode -> ((VmType.AliasType) typeNode.getType()).getVmTypeAlias().getMirror())
.addListProperty("typeArguments", TypeNode::getTypeArgumentMirrors);
declaredTypeFactory
@@ -251,7 +247,8 @@ public final class MirrorFactories {
.addListProperty("parameterTypes", Pair::getFirst)
.addTypedProperty("returnType", Pair::getSecond);
stringLiteralTypeFactory.addStringProperty("value", StringLiteralTypeNode::getLiteral);
stringLiteralTypeFactory.addStringProperty(
"value", typeNode -> ((VmType.StringLiteralType) typeNode.getType()).getLiteral());
stringLiteralTypeFactory2.addStringProperty("value", Property.identity());
@@ -244,10 +244,15 @@ public final class VmClass extends VmValue {
checkAbstractMethods();
}
@TruffleBoundary
public int getTypeParameterCount() {
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
* property was found. Does return local properties.
@@ -298,6 +303,13 @@ public final class VmClass extends VmValue {
return (VmTyped) module;
}
public VmClass getModuleClass() {
if (classInfo.isModuleClass()) return this;
var module = prototype.getEnclosingOwner();
assert module != null;
return module.getVmClass();
}
public VmTyped getModuleMirror() {
return getModule().getModuleInfo().getMirror(getModule());
}
@@ -393,6 +405,11 @@ public final class VmClass extends VmValue {
return superclass;
}
public @Nullable VmType getSupertype() {
if (supertypeNode == null) return null;
return supertypeNode.getType();
}
@Override
public VmTyped getPrototype() {
return prototype;
@@ -752,7 +769,7 @@ public final class VmClass extends VmValue {
return getDisplayName();
}
private UnmodifiableEconomicMap<Identifier, ClassProperty> getAllProperties() {
public UnmodifiableEconomicMap<Identifier, ClassProperty> getAllProperties() {
synchronized (allPropertiesLock) {
if (__allProperties == null) {
// 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) {
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.nodes.DirectCallNode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
@@ -27,12 +27,11 @@ import java.util.function.BiConsumer;
import java.util.function.Supplier;
import org.jspecify.annotations.Nullable;
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.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.ImRrbt;
@@ -41,10 +40,12 @@ public final class VmReference extends VmValue {
private final VmTyped domain;
private final Object data;
private final ImRrbt<VmTyped> path;
// candidate types can only be: PType.Class, PType.Alias (only preservedAliasTypes),
// PType.StringLiteral, PType.UNKNOWN, PType.Function, PType.TypeVariable, or PType.Union
// candidate types can only be: VmType.ClassType, VmType.AliasType (only preservedAliasTypes),
// VmType.StringLiteralType, VmType.Unknown.INSTANCE, VmType.FunctionType,
// VmType.TypeVariableTybe, or
// VmType.UnionType
// (containing only the previous; flattened)
private final PType referentType;
private final VmType referentType;
private boolean forced = false;
@@ -55,12 +56,15 @@ public final class VmReference extends VmValue {
.toTyped(RefModule.getAccessClass());
}
@TruffleBoundary
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.data = data;
this.referentType = referentType;
@@ -79,7 +83,7 @@ public final class VmReference extends VmValue {
return path;
}
public PType getReferentType() {
public VmType getReferentType() {
return referentType;
}
@@ -88,71 +92,67 @@ public final class VmReference extends VmValue {
// * transforming T? into T|Null
// * dereferencing aliases (except for well-known stdlib alias types)
// * flattening unions
// * drop PType.Function and PType.TypeVariable
private static PType normalizeTypes(
PType type, @Nullable PType thisClass, @Nullable PType moduleClass) {
var types = new HashSet<PType>();
normalizeTypes(type, types, thisClass, moduleClass);
// * replace VmType.ModuleType with appropriate VmType.ClassType
// * drop VmType.FunctionType and VmType.TypeVariableType
@TruffleBoundary
private static VmType normalizeTypes(VmType type, VmClass thisClass, VmClass moduleClass) {
var types = new HashSet<VmType>();
normalizeTypes(type, thisClass, moduleClass, types);
return minimizeTypes(types);
}
private static PType normalizeTypes(PType type) {
return normalizeTypes(type, null, null);
}
private static PType minimizeTypes(Set<PType> types) {
private static VmType minimizeTypes(Set<VmType> types) {
if (types.size() == 1) return types.iterator().next();
// 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
if (containsClass(types, BaseModule.getAnyClass().export()))
return new PType.Class(BaseModule.getAnyClass().export());
var typesList = new ArrayList<>(types);
typesList.sort(Comparator.comparing(Object::toString));
return new PType.Union(typesList);
if (containsClass(types, BaseModule.getAnyClass())) {
return new VmType.ClassType(BaseModule.getAnyClass());
}
var typeArray = types.toArray(new VmType[0]);
Arrays.sort(typeArray, Comparator.comparing(Object::toString));
return new VmType.UnionType(-1, typeArray);
}
private static void normalizeTypes(
PType type, Set<PType> result, @Nullable PType thisClass, @Nullable PType moduleClass) {
if (type == PType.UNKNOWN || type == PType.NOTHING || type instanceof PType.StringLiteral) {
VmType type, VmClass thisClass, VmClass moduleClass, Set<VmType> result) {
if (type == UnknownType.INSTANCE
|| type == NothingType.INSTANCE
|| type instanceof VmType.StringLiteralType) {
result.add(type);
} else if (type instanceof PType.Class clazz) {
if (clazz.getTypeArguments().isEmpty()) {
// if a generic type is used without type arguments, it needs to be normalized so all args
// 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 if (type instanceof VmType.ClassType ct) {
if (!ct.isParametric()) {
result.add(ct);
} else {
var typeArgs = new ArrayList<PType>(clazz.getTypeArguments().size());
for (var arg : clazz.getTypeArguments()) {
typeArgs.add(normalizeTypes(arg, thisClass, moduleClass));
}
result.add(new PType.Class(clazz.getPClass(), typeArgs));
result.add(
ct.withTypeArguments(
Arrays.stream(ct.getTypeArguments())
.map(arg -> normalizeTypes(arg, thisClass, moduleClass))
.toArray(VmType[]::new)));
}
}
// normalize `T?` to `T | Null`
else if (type instanceof PType.Nullable nullable) {
normalizeTypes(nullable.getBaseType(), result, thisClass, moduleClass);
result.add(new PType.Class(BaseModule.getNullClass().export()));
else if (type instanceof VmType.NullableType nullable) {
normalizeTypes(nullable.getElementType(), thisClass, moduleClass, result);
result.add(new VmType.ClassType(BaseModule.getNullClass()));
// erase `T(someConstraint)` to `T`
} else if (type instanceof PType.Constrained constrained) {
normalizeTypes(constrained.getBaseType(), result, thisClass, moduleClass);
} else if (type instanceof PType.Alias alias) {
if (isPreservedTypeAlias(alias.getTypeAlias())) {
} else if (type instanceof VmType.ConstrainedType constrained) {
normalizeTypes(constrained.getBaseType(), thisClass, moduleClass, result);
} else if (type instanceof VmType.AliasType alias) {
if (isPreservedTypeAlias(alias.getVmTypeAlias())) {
result.add(alias);
} 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()) {
normalizeTypes(t, result, thisClass, moduleClass);
normalizeTypes(t, thisClass, moduleClass, result);
}
} else if (type == PType.THIS) {
assert thisClass != null;
} else if (type instanceof NonFinalThisType) {
// there are 4 entrypoints here:
// 1. init via the Reference constructor can only normalize an unparameterized PType.Class
// 2. typecheck via ReferenceTypeNode erases self types to their actual PType.Class
@@ -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
// only property access and typecheck can produce THIS or MODULE.
// getCandidatePropertyType and referentTypeIsSubtypeOf always pass non-null `thisClass`.
result.add(thisClass);
} else if (type == PType.MODULE) {
result.add(new VmType.ClassType(thisClass));
} else if (type instanceof NonFinalModuleType) {
// this can be incorrect for usage of the module type in a class's property type annotation,
// which is deprecated!!
assert moduleClass != null;
// see PType.THIS case above
result.add(moduleClass);
result.add(new VmType.ClassType(moduleClass));
} else {
// 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
@@ -177,9 +175,9 @@ public final class VmReference extends VmValue {
}
}
private static Iterable<PType> iterateTypes(PType t) {
if (t instanceof PType.Union union) return union.getElementTypes();
return Collections.singleton(t);
private static VmType[] iterateTypes(VmType t) {
if (t instanceof VmType.UnionType union) return union.getElementTypes();
return new VmType[] {t};
}
public VmReference withPropertyAccess(Identifier property) {
@@ -197,8 +195,8 @@ public final class VmReference extends VmValue {
@TruffleBoundary
private VmReference withAccess(
BiConsumer<PType, Set<PType>> checkCandidate, Supplier<VmTyped> makeAccess) {
Set<PType> candidates = new HashSet<>();
BiConsumer<VmType, Set<VmType>> checkCandidate, Supplier<VmTyped> makeAccess) {
Set<VmType> candidates = new HashSet<>();
for (var t : iterateTypes(referentType)) {
checkCandidate.accept(t, candidates);
}
@@ -206,51 +204,51 @@ public final class VmReference extends VmValue {
}
@SuppressWarnings("DuplicatedCode")
private static void getCandidatePropertyType(PType type, String property, Set<PType> result) {
if (type == PType.UNKNOWN) {
private static void getCandidatePropertyType(VmType type, String property, Set<VmType> result) {
if (type == UnknownType.INSTANCE) {
result.add(type);
return;
}
// 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);
}
if (clazz.getPClass().getInfo() == PClassInfo.Dynamic) {
if (ct.getVmClass().isDynamicClass()) {
if (property.equals("default")) {
// restriction: cannot reference Dynamic.default
throw new VmReferenceAccessError(type, VmReferenceAccessErrorType.DEFAULT_MEMBER);
}
result.add(PType.UNKNOWN);
result.add(UnknownType.INSTANCE);
return;
}
// restriction: cannot reference Listing/Mapping.default
if (clazz.getPClass().getInfo() == PClassInfo.Listing
|| clazz.getPClass().getInfo() == PClassInfo.Mapping) {
if (ct.getVmClass().isListingClass() || ct.getVmClass().isMappingClass()) {
var errorType =
property.equals("default")
? VmReferenceAccessErrorType.DEFAULT_MEMBER
: VmReferenceAccessErrorType.CANNOT_FIND_MEMBER;
throw new VmReferenceAccessError(type, errorType);
}
var baseModule = BaseModule.getModuleClass().export();
var baseModule = BaseModule.getModuleClass();
// restriction: cannot reference Module.output.
// 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
// containing a property can be subclassed.
// And this can't check prop.getOwner().isExternal() because fully overriding the property with
// 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(
new PType.Class(baseModule), VmReferenceAccessErrorType.EXTERNAL_CLASS);
new VmType.ClassType(baseModule), VmReferenceAccessErrorType.EXTERNAL_CLASS);
}
// dot access on `Reference<D, Null>` gives `Reference<D, Null>`
if (clazz.getPClass().getInfo() == PClassInfo.Null) {
result.add(clazz);
if (ct.getVmClass().isNullClass()) {
result.add(ct);
return;
}
var prop = clazz.getPClass().getAllProperties().get(property);
var prop = ct.getVmClass().getAllProperties().get(Identifier.get(property));
//noinspection ConstantValue
if (prop == null) {
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);
}
normalizeTypes(
prop.getType(), result, clazz, new PType.Class(clazz.getPClass().getModuleClass()));
}
private static PClassInfo<?> getClassInfo(Object value) {
if (value instanceof VmValue vmValue) return vmValue.getVmClass().getPClassInfo();
if (value instanceof String) return PClassInfo.String;
if (value instanceof Boolean) return PClassInfo.Boolean;
if (value instanceof Long) return PClassInfo.Int;
if (value instanceof Double) return PClassInfo.Float;
throw new PklBugException("Not a Pkl value: " + value.getClass());
// this handles the object-prop-in-class case because VmClass.getAllProperties() omits
// properties without type annotations that are defined in a superclass. e.g.:
// ```
// open class A { prop: String }
// class B extends A { prop = "foo" }
// ```
// In this case `VmClass[B].getAllProperties().get(<prop>)` will be A's prop, not B's
var propTypeNode = prop.getTypeNode();
var propType =
propTypeNode == null ? VmType.UnknownType.INSTANCE : propTypeNode.getTypeNode().getType();
normalizeTypes(propType, ct.getVmClass(), ct.getVmClass().getModuleClass(), result);
}
@SuppressWarnings("DuplicatedCode")
private static void getCandidateSubscriptType(PType type, Object key, Set<PType> result) {
if (type == PType.UNKNOWN) {
private static void getCandidateSubscriptType(VmType type, Object key, Set<VmType> result) {
if (type == UnknownType.INSTANCE) {
result.add(type);
return;
}
if (!(type instanceof PType.Class clazz)) {
if (!(type instanceof VmType.ClassType ct)) {
throw new VmReferenceAccessError(type, VmReferenceAccessErrorType.CANNOT_FIND_MEMBER);
}
if (clazz.getPClass().getInfo() == PClassInfo.Dynamic) {
result.add(PType.UNKNOWN);
var clazz = ct.getVmClass();
if (clazz.isDynamicClass()) {
result.add(UnknownType.INSTANCE);
return;
}
if (clazz.getPClass().getInfo() == PClassInfo.Listing
|| clazz.getPClass().getInfo() == PClassInfo.List) {
if (clazz.isListingClass() || clazz.isListClass()) {
if (!(key instanceof Long)) {
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;
}
if (clazz.getPClass().getInfo() == PClassInfo.Mapping
|| clazz.getPClass().getInfo() == PClassInfo.Map) {
var typeArgs = clazz.getTypeArguments();
var keyTypes = normalizeTypes(typeArgs.get(0));
if (clazz.isMappingClass() || clazz.isMapClass()) {
var typeArgs = ct.getTypeArguments();
var keyTypes = normalizeTypes(typeArgs[0], clazz, clazz.getModuleClass());
for (var kt : iterateTypes(keyTypes)) {
if (kt == PType.UNKNOWN
|| (kt instanceof PType.Class klazz && klazz.getPClass().getInfo() == getClassInfo(key))
|| (kt instanceof PType.StringLiteral stringLiteral
if (kt == UnknownType.INSTANCE
|| (kt instanceof VmType.ClassType klazz && klazz.getVmClass() == VmUtils.getClass(key))
|| (kt instanceof VmType.StringLiteralType stringLiteral
&& stringLiteral.getLiteral().equals(key))) {
normalizeTypes(typeArgs.get(1), result, null, null);
normalizeTypes(typeArgs[1], clazz, clazz.getModuleClass(), result);
return;
}
}
}
// subscript access on `Reference<D, Null>` gives `Reference<D, Null>`
if (clazz.getPClass().getInfo() == PClassInfo.Null) {
result.add(clazz);
if (clazz == BaseModule.getNullClass()) {
result.add(ct);
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.
*/
@TruffleBoundary
public boolean referentTypeIsSubtypeOf(PType type, PClass thisClass, PClass moduleClass) {
public boolean referentTypeIsSubtypeOf(VmType type, VmClass thisClass, VmClass moduleClass) {
// fast path: if referent is unknown it can match any type check
if (referentType == PType.UNKNOWN) {
if (referentType == UnknownType.INSTANCE) {
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
if (checkType == PType.UNKNOWN || isClass(checkType, BaseModule.getAnyClass().export())) {
if (checkType == UnknownType.INSTANCE || isClass(checkType, BaseModule.getAnyClass())) {
return true;
}
// fast path: short circuit if nothing is accepted
if (checkType == PType.NOTHING) {
if (checkType == NothingType.INSTANCE) {
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) {
if (isClass(t, pClass)) return true;
if (isClass(t, clazz)) return true;
}
return false;
}
private static boolean isClass(PType t, PClass pClass) {
return t instanceof PType.Class clazz && clazz.getPClass() == pClass;
private static boolean isClass(VmType t, VmClass clazz) {
return t instanceof VmType.ClassType ct && ct.getVmClass() == clazz;
}
private static boolean isSubtype(PType a, PType b) {
// checks if A is a subtype of B
// cases (A -> B)
// * A == B
// * StringLiteral -> StringLiteral: if literals are the same
// * StringLiteral -> Class: B is String
// * Char Alias -> Char Alias, StringLiteral (known single character)
// * Int Alias -> Class: B is a subtype of Number (Int|Float|Number)
// * 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(VmTypeAlias t) {
return t == BaseModule.getInt8TypeAlias()
|| t == BaseModule.getInt16TypeAlias()
|| t == BaseModule.getInt32TypeAlias()
|| t == BaseModule.getUInt8TypeAlias()
|| t == BaseModule.getUInt16TypeAlias()
|| t == BaseModule.getUInt32TypeAlias()
|| t == BaseModule.getUIntTypeAlias();
}
private static boolean isIntTypeAlias(TypeAlias 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) {
private static boolean isPreservedTypeAlias(VmTypeAlias t) {
return isIntTypeAlias(t);
}
@@ -494,13 +392,14 @@ public final class VmReference extends VmValue {
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() {
return new PType.Class(
RefModule.getReferenceClass().export(),
new PType.Class(domain.getVmClass().export()),
public VmType getType() {
return new VmType.ClassType(
RefModule.getReferenceClass(),
new VmType.ClassType(domain.getVmClass()),
getReferentType());
}
@@ -554,15 +453,15 @@ public final class VmReference extends VmValue {
}
public static final class VmReferenceAccessError extends RuntimeException {
private final PType type;
private final VmType type;
private final VmReferenceAccessErrorType errorType;
public VmReferenceAccessError(PType type, VmReferenceAccessErrorType errorType) {
public VmReferenceAccessError(VmType type, VmReferenceAccessErrorType errorType) {
this.type = type;
this.errorType = errorType;
}
public PType getType() {
public VmType getType() {
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;
}
public VmClass getModuleClass() {
return module.getVmClass();
}
public String getModuleName() {
return module.getVmClass().getModuleName();
}
@@ -22,6 +22,7 @@ import com.oracle.truffle.api.dsl.TypeSystem;
long.class,
double.class,
boolean.class,
VmType.class,
VmDuration.class,
VmDataSize.class,
VmIntSeq.class,
@@ -62,6 +62,7 @@ import org.pkl.core.runtime.VmPair;
import org.pkl.core.runtime.VmReference;
import org.pkl.core.runtime.VmRegex;
import org.pkl.core.runtime.VmSet;
import org.pkl.core.runtime.VmType;
import org.pkl.core.runtime.VmTyped;
import org.pkl.core.runtime.VmUtils;
import org.pkl.core.runtime.VmValue;
@@ -118,7 +119,9 @@ public final class RendererNodes {
var clazz =
value instanceof VmValue vmValue
? vmValue.getVmClass()
: value instanceof TypeNode typeNode ? typeNode.getVmClass() : value.getClass();
: value instanceof TypeNode typeNode
? typeNode.getType().getVmClass()
: value.getClass();
if (clazz == null) {
throw new VmExceptionBuilder()
.evalError("cannotResolveTypeForProtobuf")
@@ -345,7 +348,7 @@ public final class RendererNodes {
return computedName;
}
} else if (type instanceof NonFinalClassTypeNode) {
var clazz = type.getVmClass();
var clazz = type.getType().getVmClass();
if (value instanceof VmValue vmValue) {
if (clazz == vmValue.getVmClass()) {
return clazz.getSimpleName();
@@ -402,9 +405,9 @@ public final class RendererNodes {
hasCollection = true;
collectionType = setType.getElementTypeNode();
} else if (type instanceof NonFinalClassTypeNode) {
if (type.getVmClass() != clazz) {
if (type.getType().getVmClass() != clazz) {
throw new VmExceptionBuilder()
.evalError("cannotRenderSubtypeForProtobuf", type.getVmClass(), clazz)
.evalError("cannotRenderSubtypeForProtobuf", type.getType().getVmClass(), clazz)
.build();
}
}
@@ -581,7 +584,8 @@ public final class RendererNodes {
} else if (type instanceof NullableTypeNode nullableType) {
return resolveType(nullableType.getElementTypeNode());
} 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) {
var valueType = resolveType(listingType.getValueTypeNode());
assert valueType != null : "Failed to resolve type node.";
@@ -633,8 +637,8 @@ public final class RendererNodes {
elements.sort(
(o1, o2) -> {
if (o1 instanceof ObjectSlotTypeNode && o2 instanceof ObjectSlotTypeNode) {
var t1 = o1.getVmClass();
var t2 = o2.getVmClass();
var t1 = o1.getType().getVmClass();
var t2 = o2.getType().getVmClass();
if (t1 == null || t2 == null) return 0;
return t1.isSubclassOf(t2) ? -1 : t2.isSubclassOf(t1) ? 1 : 0;
}
@@ -359,7 +359,7 @@ typeMismatchBecause=\
* Value is not of type `{0}` because:
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=\
Type constraint `{0}` violated.
@@ -134,6 +134,9 @@ class BadResource extends Resource {
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 {
renderer {
converters {
@@ -61,3 +61,4 @@ aValuesJoined = """
"""
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 = ? })"
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 ––
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
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -1,5 +1,5 @@
–– 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
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -1,11 +1,11 @@
–– 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
^^^^^^^^^^
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)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -1,13 +1,13 @@
–– Pkl Error ––
Operator `[]` is not defined for operand types `pkl.ref#Reference<reference7#D, List<unknown>>` and `String`.
Left operand : Reference(new D {}, List<unknown>, List())
Operator `[]` is not defined for operand types `pkl.ref#Reference<reference7#D, List>` and `String`.
Left operand : Reference(new D {}, List, List())
Right operand: "hi"
x | test = ref.Reference(d, List, List())["hi"]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
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)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -5,7 +5,7 @@ xx | typealias F<T> = List<Class<T>>
^^^^^^^^
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)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -5,7 +5,7 @@ xx | typealias F<T> = List<Class<T>>
^^^^^^^^
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)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -5,7 +5,7 @@ xx | typealias F<T> = List<Class<T>>
^^^^^^^^
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)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -5,7 +5,7 @@ x | res8 = C as Class<A | B>
^^^^^^^^^^^^
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)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -5,7 +5,7 @@ x | res9 = C as Class<A?>
^^^^^^^^^
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)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -5,7 +5,7 @@ x | res10 = C as Class<"foo">
^^^^^^^^^^^^
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)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -5,7 +5,7 @@ x | res11 = C as Class<nothing>
^^^^^^^^^^^^^^
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)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -5,7 +5,7 @@ x | res12 = C as Class<A(true)>
^^^^^^^^^^^^^^
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)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -651,7 +651,8 @@ class CommandSpecParserTest {
val exc = assertThrows<PklException> { parse(moduleUri) }
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
@@ -805,7 +806,7 @@ class CommandSpecParserTest {
val spec = parse(moduleUri)
val flag = spec.options.first() as CommandSpec.Flag
assertThat(flag.metavar()).isEqualTo("[json, toml, yaml]")
assertThat(flag.metavar()).isEqualTo("[json, yaml, toml]")
val apply =
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)
}
}