Add VmType intermediate representation for types (#1838)
This commit is contained in:
1 parent
cbffd6c114
commit
7ed88e250b
36 files changed
+2161
-1477
No files matched your search
+1
@@ -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>
|
||||
|
||||
@@ -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))
|
||||
|
||||
+2
-2
@@ -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();
|
||||
|
||||
+1
-1
@@ -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)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
+1
-1
@@ -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)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
+1
-1
@@ -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)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
+1
-1
@@ -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)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
+1
-1
@@ -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)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
+1
-1
@@ -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)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
+1
-1
@@ -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)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
+1
-1
@@ -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)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
+1
-1
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user