case of and intersection types (#11850)

Also: Prefer `getURI` path when gathering location of a source
This commit is contained in:
Jaroslav Tulach 2024-12-13 16:02:51 +01:00 committed by GitHub
parent e9b0ba95b0
commit 7998ed5218
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 198 additions and 92 deletions

View File

@ -295,7 +295,7 @@ Error.should_equal self that frames_to_skip=0 =
Number.should_equal : Float -> Float -> Integer -> Spec_Result Number.should_equal : Float -> Float -> Integer -> Spec_Result
Number.should_equal self that epsilon=0 frames_to_skip=0 = Number.should_equal self that epsilon=0 frames_to_skip=0 =
matches = case that of matches = case that of
_ : Number -> self.equals that epsilon n : Number -> self.equals n epsilon
_ -> self==that _ -> self==that
case matches of case matches of
True -> Spec_Result.Success True -> Spec_Result.Success

View File

@ -80,18 +80,14 @@ method calls will also only accept the value if it satisfies the type it
_has been cast to_. Any additional remaining _hidden_ types _has been cast to_. Any additional remaining _hidden_ types
can only be brought back through an _explicit_ cast. can only be brought back through an _explicit_ cast.
To perform an explicit cast that can uncover the 'hidden' part of a type write To perform an explicit cast that can uncover the 'hidden' part of a type write
`f = c:Float`. `f = c:Float` or inspect the types in a `case` expression, e.g.
```ruby
<!-- case c of
When #11828 is fixed. f : Float -> f.sqrt
_ -> "Not a float"
- or inspect the types in a `case` expression, e.g. ```
``` Remember to use `f.sqrt` and not `c.sqrt`. `f` in the case branch _has been cast to_ `Float` while
case c of `c` in the case branch only _can be cast to_.
f : Float -> f.sqrt
_ -> "Not a float"
```
-->
> [!WARNING] > [!WARNING]
> Keep in mind that while both argument type check in method definitions and a > Keep in mind that while both argument type check in method definitions and a

View File

@ -220,7 +220,7 @@ final class ExecutionCallbacks implements IdExecutionService.Callbacks {
} }
var typeOfNode = TypeOfNode.getUncached(); var typeOfNode = TypeOfNode.getUncached();
Type[] allTypes = value == null ? null : typeOfNode.findAllTypesOrNull(value); Type[] allTypes = value == null ? null : typeOfNode.findAllTypesOrNull(value, true);
if (allTypes != null) { if (allTypes != null) {
String[] result = new String[allTypes.length]; String[] result = new String[allTypes.length];
for (var i = 0; i < allTypes.length; i++) { for (var i = 0; i < allTypes.length; i++) {

View File

@ -49,8 +49,9 @@ public class TypeOfNodeMultiValueTest {
new TestRootNode( new TestRootNode(
(frame) -> { (frame) -> {
var arg = frame.getArguments()[0]; var arg = frame.getArguments()[0];
var allTypes = (boolean) frame.getArguments()[1];
var t = node.findTypeOrError(arg); var t = node.findTypeOrError(arg);
var all = node.findAllTypesOrNull(arg); var all = node.findAllTypesOrNull(arg, allTypes);
return new Object[] {t, all}; return new Object[] {t, all};
}); });
root.insertChildren(node); root.insertChildren(node);
@ -116,16 +117,22 @@ public class TypeOfNodeMultiValueTest {
} }
@Test @Test
public void typeOfCheck() { public void typeOfCheckAllTypes() {
assertType(value, type, typeIndex); assertType(value, type, typeIndex, true);
} }
private static void assertType(Object value, String expectedTypeName, int typeIndex) { @Test
public void typeOfCheckHasBeenCastToTypes() {
assertType(value, type, typeIndex, false);
}
private static void assertType(
Object value, String expectedTypeName, int typeIndex, boolean allTypes) {
assertNotNull("Value " + value + " should have a type", expectedTypeName); assertNotNull("Value " + value + " should have a type", expectedTypeName);
ContextUtils.executeInContext( ContextUtils.executeInContext(
ctx(), ctx(),
() -> { () -> {
var pairResult = (Object[]) testTypesCall.call(value); var pairResult = (Object[]) testTypesCall.call(value, allTypes);
var t = pairResult[0]; var t = pairResult[0];
var all = (Object[]) pairResult[1]; var all = (Object[]) pairResult[1];

View File

@ -3,8 +3,10 @@ package org.enso.interpreter.node.expression.builtin.meta;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import com.oracle.truffle.api.RootCallTarget; import com.oracle.truffle.api.RootCallTarget;
import org.enso.interpreter.runtime.EnsoContext;
import org.enso.interpreter.runtime.callable.UnresolvedConstructor; import org.enso.interpreter.runtime.callable.UnresolvedConstructor;
import org.enso.interpreter.runtime.callable.UnresolvedSymbol; import org.enso.interpreter.runtime.callable.UnresolvedSymbol;
import org.enso.interpreter.runtime.data.EnsoMultiValue;
import org.enso.interpreter.runtime.data.Type; import org.enso.interpreter.runtime.data.Type;
import org.enso.interpreter.runtime.library.dispatch.TypeOfNode; import org.enso.interpreter.runtime.library.dispatch.TypeOfNode;
import org.enso.test.utils.ContextUtils; import org.enso.test.utils.ContextUtils;
@ -28,8 +30,9 @@ public class TypeOfNodeValueTest {
new TestRootNode( new TestRootNode(
(frame) -> { (frame) -> {
var arg = frame.getArguments()[0]; var arg = frame.getArguments()[0];
var allTypes = (boolean) frame.getArguments()[1];
var t = node.findTypeOrError(arg); var t = node.findTypeOrError(arg);
var all = node.findAllTypesOrNull(arg); var all = node.findAllTypesOrNull(arg, allTypes);
return new Object[] {t, all}; return new Object[] {t, all};
}); });
root.insertChildren(node); root.insertChildren(node);
@ -54,7 +57,7 @@ public class TypeOfNodeValueTest {
ctx(), ctx(),
() -> { () -> {
var cnstr = UnresolvedConstructor.build(null, "Unknown_Name"); var cnstr = UnresolvedConstructor.build(null, "Unknown_Name");
var arr = (Object[]) testTypesCall.call(cnstr); var arr = (Object[]) testTypesCall.call(cnstr, true);
var type = (Type) arr[0]; var type = (Type) arr[0];
var allTypes = (Type[]) arr[1]; var allTypes = (Type[]) arr[1];
assertEquals("Function", type.getName()); assertEquals("Function", type.getName());
@ -70,7 +73,7 @@ public class TypeOfNodeValueTest {
ctx(), ctx(),
() -> { () -> {
var cnstr = UnresolvedSymbol.build("Unknown_Name", null); var cnstr = UnresolvedSymbol.build("Unknown_Name", null);
var arr = (Object[]) testTypesCall.call(cnstr); var arr = (Object[]) testTypesCall.call(cnstr, true);
var type = (Type) arr[0]; var type = (Type) arr[0];
var allTypes = (Type[]) arr[1]; var allTypes = (Type[]) arr[1];
assertEquals("Function", type.getName()); assertEquals("Function", type.getName());
@ -79,4 +82,29 @@ public class TypeOfNodeValueTest {
return null; return null;
}); });
} }
@Test
public void multiValueWithHiddenType() {
ContextUtils.executeInContext(
ctx(),
() -> {
var ensoCtx = EnsoContext.get(testTypesCall.getRootNode());
var types =
new Type[] {
ensoCtx.getBuiltins().number().getInteger(), ensoCtx.getBuiltins().text()
};
var multi = EnsoMultiValue.create(types, 1, new Object[] {42L, "Meaning"});
var arr = (Object[]) testTypesCall.call(multi, true);
var allTypes = (Type[]) arr[1];
assertEquals("Two types", 2, allTypes.length);
assertEquals("Integer", types[0], allTypes[0]);
assertEquals("Text", types[1], allTypes[1]);
var arr1 = (Object[]) testTypesCall.call(multi, false);
var allTypes1 = (Type[]) arr1[1];
assertEquals("Just one type", 1, allTypes1.length);
assertEquals("Integer", types[0], allTypes1[0]);
return null;
});
}
} }

View File

@ -152,20 +152,13 @@ final class BinaryOperatorNode extends ExpressionNode {
InvokeFunctionNode invokeNode) { InvokeFunctionNode invokeNode) {
var selfType = findType(typeOfNode, self); var selfType = findType(typeOfNode, self);
if (that instanceof EnsoMultiValue multi) { if (that instanceof EnsoMultiValue multi) {
var all = typeOfNode.findAllTypesOrNull(multi); var all = typeOfNode.findAllTypesOrNull(multi, false);
assert all != null; assert all != null;
for (var thatType : all) { for (var thatType : all) {
var fn = findSymbol(symbol, thatType); var fn = findSymbol(symbol, thatType);
if (fn != null) { if (fn != null) {
var thatCasted =
EnsoMultiValue.CastToNode.getUncached()
.findTypeOrNull(thatType, multi, false, false);
if (thatCasted == null) {
continue;
}
var result = var result =
doDispatch( doDispatch(frame, self, multi, selfType, thatType, fn, convertNode, invokeNode);
frame, self, thatCasted, selfType, thatType, fn, convertNode, invokeNode);
if (result != null) { if (result != null) {
return result; return result;
} }

View File

@ -5,6 +5,7 @@ import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.NodeInfo; import com.oracle.truffle.api.nodes.NodeInfo;
import com.oracle.truffle.api.profiles.CountingConditionProfile; import com.oracle.truffle.api.profiles.CountingConditionProfile;
import org.enso.interpreter.node.expression.builtin.meta.IsValueOfTypeNode; import org.enso.interpreter.node.expression.builtin.meta.IsValueOfTypeNode;
import org.enso.interpreter.runtime.data.EnsoMultiValue;
import org.enso.interpreter.runtime.data.Type; import org.enso.interpreter.runtime.data.Type;
/** An implementation of the case expression specialised to working on types. */ /** An implementation of the case expression specialised to working on types. */
@ -33,7 +34,13 @@ public class CatchTypeBranchNode extends BranchNode {
} }
public void execute(VirtualFrame frame, Object state, Object value) { public void execute(VirtualFrame frame, Object state, Object value) {
if (profile.profile(isValueOfTypeNode.execute(expectedType, value))) { if (profile.profile(isValueOfTypeNode.execute(expectedType, value, true))) {
if (value instanceof EnsoMultiValue multi) {
var replacement =
EnsoMultiValue.CastToNode.getUncached().findTypeOrNull(expectedType, multi, true, true);
assert replacement != null : "Must find the type, when isValueOfTypeNode is true";
value = replacement;
}
accept(frame, state, new Object[] {value}); accept(frame, state, new Object[] {value});
} }
} }

View File

@ -86,7 +86,7 @@ public abstract class CatchPanicNode extends Node {
AbstractTruffleException originalException, AbstractTruffleException originalException,
InteropLibrary interopLibrary) { InteropLibrary interopLibrary) {
if (profile.profile(isValueOfTypeNode.execute(panicType, payload))) { if (profile.profile(isValueOfTypeNode.execute(panicType, payload, true))) {
var builtins = EnsoContext.get(this).getBuiltins(); var builtins = EnsoContext.get(this).getBuiltins();
var cons = builtins.caughtPanic().getUniqueConstructor(); var cons = builtins.caughtPanic().getUniqueConstructor();
var caughtPanic = var caughtPanic =

View File

@ -4,6 +4,7 @@ import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.TruffleStackTrace; import com.oracle.truffle.api.TruffleStackTrace;
import com.oracle.truffle.api.TruffleStackTraceElement; import com.oracle.truffle.api.TruffleStackTraceElement;
import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.nodes.Node;
import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import org.enso.interpreter.dsl.BuiltinMethod; import org.enso.interpreter.dsl.BuiltinMethod;
@ -76,6 +77,11 @@ public class GetStackTraceTextNode extends Node {
var sourceLoc = errorFrame.getLocation().getEncapsulatingSourceSection(); var sourceLoc = errorFrame.getLocation().getEncapsulatingSourceSection();
if (sourceLoc != null) { if (sourceLoc != null) {
var path = sourceLoc.getSource().getPath(); var path = sourceLoc.getSource().getPath();
try {
path = new File(sourceLoc.getSource().getURI()).getPath();
} catch (IllegalArgumentException | NullPointerException ignore) {
// keep original value of path
}
var ident = (path != null) ? path : sourceLoc.getSource().getName(); var ident = (path != null) ? path : sourceLoc.getSource().getName();
var loc = var loc =
(sourceLoc.getStartLine() == sourceLoc.getEndLine()) (sourceLoc.getStartLine() == sourceLoc.getEndLine())

View File

@ -331,7 +331,7 @@ abstract class EqualsSimpleNode extends Node {
@Shared("multiCast") @Cached EnsoMultiValue.CastToNode castNode, @Shared("multiCast") @Cached EnsoMultiValue.CastToNode castNode,
@Shared("multiType") @Cached TypeOfNode typesNode, @Shared("multiType") @Cached TypeOfNode typesNode,
@Shared("multiEquals") @Cached EqualsSimpleNode delegate) { @Shared("multiEquals") @Cached EqualsSimpleNode delegate) {
var types = typesNode.findAllTypesOrNull(self); var types = typesNode.findAllTypesOrNull(self, false);
assert types != null; assert types != null;
for (var t : types) { for (var t : types) {
var value = castNode.findTypeOrNull(t, self, false, false); var value = castNode.findTypeOrNull(t, self, false, false);

View File

@ -13,6 +13,6 @@ public class IsANode extends Node {
private @Child IsValueOfTypeNode check = IsValueOfTypeNode.build(); private @Child IsValueOfTypeNode check = IsValueOfTypeNode.build();
public boolean execute(@AcceptsError Object value, Object type) { public boolean execute(@AcceptsError Object value, Object type) {
return check.execute(type, value); return check.execute(type, value, true);
} }
} }

View File

@ -17,53 +17,72 @@ import org.enso.interpreter.runtime.data.Type;
import org.enso.interpreter.runtime.library.dispatch.TypeOfNode; import org.enso.interpreter.runtime.library.dispatch.TypeOfNode;
import org.enso.interpreter.runtime.library.dispatch.TypesLibrary; import org.enso.interpreter.runtime.library.dispatch.TypesLibrary;
import org.enso.interpreter.runtime.number.EnsoBigInteger; import org.enso.interpreter.runtime.number.EnsoBigInteger;
import org.enso.interpreter.runtime.type.TypesGen;
/** An implementation of the payload check against the expected panic type. */ /** An implementation of the payload check against the expected panic type. */
@NodeInfo(shortName = "IsValueOfTypeNode") @NodeInfo(shortName = "IsValueOfTypeNode")
public abstract class IsValueOfTypeNode extends Node { public abstract class IsValueOfTypeNode extends Node {
IsValueOfTypeNode() {}
public static IsValueOfTypeNode build() { public static IsValueOfTypeNode build() {
return IsValueOfTypeNodeGen.create(); return IsValueOfTypeNodeGen.create();
} }
public abstract boolean execute(Object expectedType, Object payload); /**
* @param expectedType the type to check
* @param obj the object to check
* @param includeExtraTypes specify {@code false} to return only <em>types value has already been
* cast to</em>, specify {@code true} to return all <em>types value can be cast to</em>
*/
public abstract boolean execute(Object expectedType, Object obj, boolean includeExtraTypes);
@Specialization(guards = {"types.hasType(payload)"}) @Specialization(guards = {"types.hasType(payload)"})
boolean doTyped( boolean doTyped(
Object expectedType, Object expectedType,
Object payload, Object payload,
boolean allTypes,
@Shared("types") @CachedLibrary(limit = "3") TypesLibrary types, @Shared("types") @CachedLibrary(limit = "3") TypesLibrary types,
@Cached Typed typed) { @Cached Typed typed) {
return typed.execute(expectedType, payload); return typed.execute(expectedType, payload, allTypes);
} }
@Specialization(guards = {"!types.hasType(payload)"}) @Specialization(guards = {"!types.hasType(payload)"})
boolean doPolyglot( boolean doPolyglot(
Object expectedType, Object expectedType,
Object payload, Object payload,
boolean allTypes,
@Shared("types") @CachedLibrary(limit = "3") TypesLibrary types, @Shared("types") @CachedLibrary(limit = "3") TypesLibrary types,
@Cached Untyped untyped) { @Cached Untyped untyped) {
return untyped.execute(expectedType, payload); return untyped.execute(expectedType, payload, allTypes);
} }
private static boolean typeAndCheck( private static boolean typeAndCheck(
Object payload, Object payload,
Object expectedType, Object expectedMeta,
boolean allTypes,
TypeOfNode typeOfNode, TypeOfNode typeOfNode,
IsSameObjectNode isSameObject, IsSameObjectNode isSameObject,
CountingConditionProfile isSameObjectProfile) { CountingConditionProfile isSameObjectProfile) {
Object tpeOfPayload = typeOfNode.findTypeOrError(payload); if (expectedMeta instanceof Type expectedType) {
if (isSameObjectProfile.profile(isSameObject.execute(expectedType, tpeOfPayload))) { var arr = typeOfNode.findAllTypesOrNull(payload, allTypes);
return true; if (arr == null) {
} else if (TypesGen.isType(tpeOfPayload)) { return false;
Type tpe = TypesGen.asType(tpeOfPayload); }
var ctx = EnsoContext.get(typeOfNode); for (var tpeOfPayload : arr) {
for (var superTpe : tpe.allTypes(ctx)) { if (isSameObjectProfile.profile(isSameObject.execute(expectedType, tpeOfPayload))) {
boolean testSuperTpe = isSameObject.execute(expectedType, superTpe);
if (testSuperTpe) {
return true; return true;
} else {
var ctx = EnsoContext.get(typeOfNode);
for (var superTpe : tpeOfPayload.allTypes(ctx)) {
boolean testSuperTpe = isSameObject.execute(expectedType, superTpe);
if (testSuperTpe) {
return true;
}
}
} }
} }
} else {
var tpe = typeOfNode.findTypeOrError(payload);
return isSameObject.execute(expectedMeta, tpe);
} }
return false; return false;
} }
@ -73,33 +92,33 @@ public abstract class IsValueOfTypeNode extends Node {
private @Child TypeOfNode typeOfNode = TypeOfNode.create(); private @Child TypeOfNode typeOfNode = TypeOfNode.create();
private final CountingConditionProfile profile = CountingConditionProfile.create(); private final CountingConditionProfile profile = CountingConditionProfile.create();
abstract boolean execute(Object expectedType, Object payload); abstract boolean execute(Object expectedType, Object payload, boolean allTypes);
@Specialization(guards = "isAnyType(expectedType)") @Specialization(guards = "isAnyType(expectedType)")
boolean doAnyType(Object expectedType, Object payload) { boolean doAnyType(Object expectedType, Object payload, boolean allTypes) {
return true; return true;
} }
@Specialization @Specialization
boolean doLongCheck(Type expectedType, long payload) { boolean doLongCheck(Type expectedType, long payload, boolean allTypes) {
var numbers = EnsoContext.get(this).getBuiltins().number(); var numbers = EnsoContext.get(this).getBuiltins().number();
return checkParentTypes(numbers.getInteger(), expectedType); return checkParentTypes(numbers.getInteger(), expectedType);
} }
@Specialization @Specialization
boolean doDoubleCheck(Type expectedType, double payload) { boolean doDoubleCheck(Type expectedType, double payload, boolean allTypes) {
var numbers = EnsoContext.get(this).getBuiltins().number(); var numbers = EnsoContext.get(this).getBuiltins().number();
return checkParentTypes(numbers.getFloat(), expectedType); return checkParentTypes(numbers.getFloat(), expectedType);
} }
@Specialization @Specialization
boolean doBigIntegerCheck(Type expectedType, EnsoBigInteger value) { boolean doBigIntegerCheck(Type expectedType, EnsoBigInteger value, boolean allTypes) {
var numbers = EnsoContext.get(this).getBuiltins().number(); var numbers = EnsoContext.get(this).getBuiltins().number();
return checkParentTypes(numbers.getInteger(), expectedType); return checkParentTypes(numbers.getInteger(), expectedType);
} }
@Specialization @Specialization
boolean doUnresolvedSymbol(Type expectedType, UnresolvedSymbol value) { boolean doUnresolvedSymbol(Type expectedType, UnresolvedSymbol value, boolean allTypes) {
var funTpe = EnsoContext.get(this).getBuiltins().function(); var funTpe = EnsoContext.get(this).getBuiltins().function();
return expectedType == funTpe; return expectedType == funTpe;
} }
@ -119,8 +138,9 @@ public abstract class IsValueOfTypeNode extends Node {
boolean doType( boolean doType(
Type expectedType, Type expectedType,
Object payload, Object payload,
boolean allTypes,
@Shared("types") @CachedLibrary(limit = "3") TypesLibrary types) { @Shared("types") @CachedLibrary(limit = "3") TypesLibrary types) {
return typeAndCheck(payload, expectedType, typeOfNode, isSameObject, profile); return typeAndCheck(payload, expectedType, allTypes, typeOfNode, isSameObject, profile);
} }
@Specialization( @Specialization(
@ -131,13 +151,14 @@ public abstract class IsValueOfTypeNode extends Node {
public boolean doArrayViaType( public boolean doArrayViaType(
Object expectedType, Object expectedType,
Object payload, Object payload,
boolean allTypes,
@CachedLibrary(limit = "3") InteropLibrary interop, @CachedLibrary(limit = "3") InteropLibrary interop,
@Shared("types") @CachedLibrary(limit = "3") TypesLibrary types) { @Shared("types") @CachedLibrary(limit = "3") TypesLibrary types) {
return EnsoContext.get(this).getBuiltins().array() == types.getType(payload); return EnsoContext.get(this).getBuiltins().array() == types.getType(payload);
} }
@Fallback @Fallback
boolean doOther(Object expectedType, Object payload) { boolean doOther(Object expectedType, Object payload, boolean allTypes) {
return false; return false;
} }
@ -155,7 +176,7 @@ public abstract class IsValueOfTypeNode extends Node {
private @Child TypeOfNode typeOfNode = TypeOfNode.create(); private @Child TypeOfNode typeOfNode = TypeOfNode.create();
private final CountingConditionProfile profile = CountingConditionProfile.create(); private final CountingConditionProfile profile = CountingConditionProfile.create();
abstract boolean execute(Object expectedType, Object payload); abstract boolean execute(Object expectedType, Object payload, boolean allTypes);
@Specialization( @Specialization(
guards = { guards = {
@ -163,7 +184,10 @@ public abstract class IsValueOfTypeNode extends Node {
"isMetaInstance(interop, expectedType, payload)" "isMetaInstance(interop, expectedType, payload)"
}) })
boolean doPolyglotType( boolean doPolyglotType(
Object expectedType, Object payload, @CachedLibrary(limit = "3") InteropLibrary interop) { Object expectedType,
Object payload,
boolean allTypes,
@CachedLibrary(limit = "3") InteropLibrary interop) {
return true; return true;
} }
@ -176,8 +200,8 @@ public abstract class IsValueOfTypeNode extends Node {
} }
@Fallback @Fallback
public boolean doOther(Object expectedType, Object payload) { public boolean doOther(Object expectedType, Object payload, boolean allTypes) {
return typeAndCheck(payload, expectedType, typeOfNode, isSameObject, profile); return typeAndCheck(payload, expectedType, allTypes, typeOfNode, isSameObject, profile);
} }
} }
} }

View File

@ -34,7 +34,7 @@ non-sealed abstract class MetaTypeCheckNode extends AbstractTypeCheckNode {
if (isAllFitValue(v)) { if (isAllFitValue(v)) {
return v; return v;
} }
if (isA.execute(expectedSupplier.get(), v)) { if (isA.execute(expectedSupplier.get(), v, true)) {
return v; return v;
} else { } else {
return null; return null;

View File

@ -123,7 +123,7 @@ non-sealed abstract class SingleTypeCheckNode extends AbstractTypeCheckNode {
return result; return result;
} }
} }
if (checkType.execute(expectedType, v)) { if (checkType.execute(expectedType, v, isAllTypes())) {
return v; return v;
} }
return null; return null;
@ -176,7 +176,7 @@ non-sealed abstract class SingleTypeCheckNode extends AbstractTypeCheckNode {
Type[] findType(TypeOfNode typeOfNode, Object v, Type[] previous) { Type[] findType(TypeOfNode typeOfNode, Object v, Type[] previous) {
if (v instanceof EnsoMultiValue multi) { if (v instanceof EnsoMultiValue multi) {
var all = typeOfNode.findAllTypesOrNull(multi); var all = typeOfNode.findAllTypesOrNull(multi, isAllTypes());
assert all != null; assert all != null;
var to = 0; var to = 0;
// only consider methodDispatchTypes and not "all types" of the multi value // only consider methodDispatchTypes and not "all types" of the multi value

View File

@ -88,8 +88,12 @@ public final class EnsoMultiValue extends EnsoObject {
} }
@ExportMessage @ExportMessage
final Type[] allTypes() { final Type[] allTypes(boolean includeExtraTypes) {
return types.clone(); if (includeExtraTypes || methodDispatchTypes == types.length) {
return types.clone();
} else {
return Arrays.copyOf(types, methodDispatchTypes);
}
} }
@ExportMessage @ExportMessage
@ -452,19 +456,22 @@ public final class EnsoMultiValue extends EnsoObject {
@Specialization @Specialization
Object castsToAType(Type type, EnsoMultiValue mv, boolean reorderOnly, boolean allTypes) { Object castsToAType(Type type, EnsoMultiValue mv, boolean reorderOnly, boolean allTypes) {
var ctx = EnsoContext.get(this);
var max = allTypes ? mv.types.length : mv.methodDispatchTypes; var max = allTypes ? mv.types.length : mv.methodDispatchTypes;
for (var i = 0; i < max; i++) { for (var i = 0; i < max; i++) {
if (mv.types[i] == type) { for (var t : mv.types[i].allTypes(ctx)) {
if (reorderOnly) { if (t == type) {
var copyTypes = mv.types.clone(); if (reorderOnly) {
var copyValues = mv.values.clone(); var copyTypes = mv.types.clone();
copyTypes[i] = mv.types[0]; var copyValues = mv.values.clone();
copyValues[i] = mv.values[0]; copyTypes[i] = mv.types[0];
copyTypes[0] = mv.types[i]; copyValues[i] = mv.values[0];
copyValues[0] = mv.values[i]; copyTypes[0] = mv.types[i];
return EnsoMultiValue.create(copyTypes, 1, copyValues); copyValues[0] = mv.values[i];
} else { return EnsoMultiValue.create(copyTypes, 1, copyValues);
return mv.values[i]; } else {
return mv.values[i];
}
} }
} }
} }

View File

@ -53,7 +53,7 @@ public abstract class TypeOfNode extends Node {
*/ */
public final TruffleObject findTypeOrError(Object value) { public final TruffleObject findTypeOrError(Object value) {
try { try {
var types = executeTypes(value); var types = executeTypes(value, false);
return types[0]; return types[0];
} catch (NonTypeResult plain) { } catch (NonTypeResult plain) {
return plain.result; return plain.result;
@ -76,11 +76,13 @@ public abstract class TypeOfNode extends Node {
* that the returned array is going to have at least one element, if it is non-{@code null}. * that the returned array is going to have at least one element, if it is non-{@code null}.
* *
* @param value the value to check * @param value the value to check
* @param includeExtraTypes specify {@code false} to return only <em>types value has already been
* case to</em>, specify {@code true} to return all <em>types value can be cast to</em>
* @return either types associated with the value or {@code null} if there is no such type * @return either types associated with the value or {@code null} if there is no such type
*/ */
public final Type[] findAllTypesOrNull(Object value) { public final Type[] findAllTypesOrNull(Object value, boolean includeExtraTypes) {
try { try {
var types = executeTypes(value); var types = executeTypes(value, includeExtraTypes);
assert types != null && types.length > 0; assert types != null && types.length > 0;
return types; return types;
} catch (NonTypeResult ex) { } catch (NonTypeResult ex) {
@ -92,11 +94,13 @@ public abstract class TypeOfNode extends Node {
* Internal implementation call to delegate to various {@link Specialization} methods. * Internal implementation call to delegate to various {@link Specialization} methods.
* *
* @param value the value to find type for * @param value the value to find type for
* @param includeExtraTypes {@code false} to return only <em>types value has already been case
* to</em>, {@code true} to return all <em>types value can be cast to</em>
* @return array of types with at least one element, but possibly more * @return array of types with at least one element, but possibly more
* @throws NonTypeResult when there is a <em>interop value</em> result, but it is not a {@link * @throws NonTypeResult when there is a <em>interop value</em> result, but it is not a {@link
* Type} * Type}
*/ */
abstract Type[] executeTypes(Object value) throws NonTypeResult; abstract Type[] executeTypes(Object value, boolean includeExtraTypes) throws NonTypeResult;
/** /**
* Creates new optimizing instance of this node. * Creates new optimizing instance of this node.
@ -123,38 +127,39 @@ public abstract class TypeOfNode extends Node {
} }
@Specialization @Specialization
Type[] doUnresolvedSymbol(UnresolvedSymbol value) { Type[] doUnresolvedSymbol(UnresolvedSymbol value, boolean allTypes) {
return fromType(EnsoContext.get(this).getBuiltins().function()); return fromType(EnsoContext.get(this).getBuiltins().function());
} }
@Specialization @Specialization
Type[] doDouble(double value) { Type[] doDouble(double value, boolean allTypes) {
return fromType(EnsoContext.get(this).getBuiltins().number().getFloat()); return fromType(EnsoContext.get(this).getBuiltins().number().getFloat());
} }
@Specialization @Specialization
Type[] doLong(long value) { Type[] doLong(long value, boolean allTypes) {
return fromType(EnsoContext.get(this).getBuiltins().number().getInteger()); return fromType(EnsoContext.get(this).getBuiltins().number().getInteger());
} }
@Specialization @Specialization
Type[] doBigInteger(EnsoBigInteger value) { Type[] doBigInteger(EnsoBigInteger value, boolean allTypes) {
return fromType(EnsoContext.get(this).getBuiltins().number().getInteger()); return fromType(EnsoContext.get(this).getBuiltins().number().getInteger());
} }
@Specialization @Specialization
Type[] doPanicException(PanicException value) { Type[] doPanicException(PanicException value, boolean allTypes) {
return fromType(EnsoContext.get(this).getBuiltins().panic()); return fromType(EnsoContext.get(this).getBuiltins().panic());
} }
@Specialization @Specialization
Type[] doPanicSentinel(PanicSentinel value) { Type[] doPanicSentinel(PanicSentinel value, boolean allTypes) {
return fromType(EnsoContext.get(this).getBuiltins().panic()); return fromType(EnsoContext.get(this).getBuiltins().panic());
} }
@Specialization @Specialization
Type[] doWarning(WithWarnings value, @Cached TypeOfNode withoutWarning) throws NonTypeResult { Type[] doWarning(WithWarnings value, boolean includeExtraTypes, @Cached TypeOfNode withoutWarning)
return withoutWarning.executeTypes(value.getValue()); throws NonTypeResult {
return withoutWarning.executeTypes(value.getValue(), includeExtraTypes);
} }
static boolean isWithType(Object value, TypesLibrary types, InteropLibrary iop) { static boolean isWithType(Object value, TypesLibrary types, InteropLibrary iop) {
@ -180,6 +185,7 @@ public abstract class TypeOfNode extends Node {
@Specialization(guards = {"isWithoutType(value, types)"}) @Specialization(guards = {"isWithoutType(value, types)"})
Type[] withoutType( Type[] withoutType(
Object value, Object value,
boolean allTypes,
@Shared("interop") @CachedLibrary(limit = "3") InteropLibrary interop, @Shared("interop") @CachedLibrary(limit = "3") InteropLibrary interop,
@Shared("types") @CachedLibrary(limit = "3") TypesLibrary types, @Shared("types") @CachedLibrary(limit = "3") TypesLibrary types,
@Cached WithoutType delegate) @Cached WithoutType delegate)
@ -201,14 +207,15 @@ public abstract class TypeOfNode extends Node {
@Specialization(guards = {"isWithType(value, types, interop)"}) @Specialization(guards = {"isWithType(value, types, interop)"})
Type[] doType( Type[] doType(
Object value, Object value,
boolean includeExtraTypes,
@Shared("interop") @CachedLibrary(limit = "3") InteropLibrary interop, @Shared("interop") @CachedLibrary(limit = "3") InteropLibrary interop,
@Shared("types") @CachedLibrary(limit = "3") TypesLibrary types) { @Shared("types") @CachedLibrary(limit = "3") TypesLibrary types) {
return types.allTypes(value); return types.allTypes(value, includeExtraTypes);
} }
@Fallback @Fallback
@CompilerDirectives.TruffleBoundary @CompilerDirectives.TruffleBoundary
Type[] doAny(Object value) throws NonTypeResult { Type[] doAny(Object value, boolean allTypes) throws NonTypeResult {
var err = var err =
DataflowError.withDefaultTrace( DataflowError.withDefaultTrace(
EnsoContext.get(this) EnsoContext.get(this)

View File

@ -74,9 +74,11 @@ public abstract class TypesLibrary extends Library {
* #getType(java.lang.Object)} and returns array with one element of that type * #getType(java.lang.Object)} and returns array with one element of that type
* *
* @param receiver the typed object * @param receiver the typed object
* @param includeExtraTypes specify {@code false} to return only <em>types value has already been
* case to</em>, specify {@code true} to return all <em>types value can be cast to</em>
* @return the corresponding types for the {@code receiver} * @return the corresponding types for the {@code receiver}
*/ */
public Type[] allTypes(Object receiver) { public Type[] allTypes(Object receiver, boolean includeExtraTypes) {
var t = getType(receiver); var t = getType(receiver);
assert t != null : "null type for " + receiver; assert t != null : "null type for " + receiver;
return new Type[] {t}; return new Type[] {t};

View File

@ -87,9 +87,13 @@ add_specs suite_builder =
(x:A).a . should_equal "a" (x:A).a . should_equal "a"
call_a obj:A = obj.a call_a obj:A = obj.a
call_b obj:B = obj.b
# A&X type has attribute a # A&X type has attribute a
call_a ax . should_equal "a" call_a ax . should_equal "a"
# A&X can be converted to B
call_b ax . should_equal "b"
call_b (ax:X&A) . should_equal "b"
# according to "static typing" discussion at # according to "static typing" discussion at
# https://github.com/enso-org/enso/pull/11600#discussion_r1867584107 # https://github.com/enso-org/enso/pull/11600#discussion_r1867584107
@ -97,10 +101,35 @@ add_specs suite_builder =
Test.expect_panic Type_Error <| Test.expect_panic Type_Error <|
call_a x . should_equal "a" call_a x . should_equal "a"
Test.expect_panic Type_Error <|
call_b x . should_equal "b"
call_b (x:X&A) . should_equal "b"
# multivalue ax restricted to X cannot be converted to B in to_b_to_c # multivalue ax restricted to X cannot be converted to B in to_b_to_c
Test.expect_panic Type_Error <| Test.expect_panic Type_Error <|
to_b_to_c x to_b_to_c x
msg1 = case ax of
b:B -> "Not a "+b.to_text
a:A -> "==="+a.a
msg1 . should_equal "===a"
msg2 = case (ax:X&A) of
b:B -> "Not a "+b.to_text
a:A -> "==="+a.a
msg2 . should_equal "===a"
msg3 = case x of
b:B -> "Not a "+b.to_text
a:A -> "==="+a.a
msg3 . should_equal "===a"
msg4 = case x of
b:B -> "Not a "+b.to_text
a:A -> "A but also "+(a:X).to_text
msg4 . should_equal "A but also X"
group_builder.specify "Intersection type of unrelated types is not possible" <| group_builder.specify "Intersection type of unrelated types is not possible" <|
Test.expect_panic Type_Error <| Test.expect_panic Type_Error <|
_ = X:X&B _ = X:X&B