From a859c1f7dcfb79f6db8494338f33fb3c52794eb2 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Thu, 12 Dec 2024 14:16:01 +0100 Subject: [PATCH 1/9] case of and intersection types --- .../service/ExecutionCallbacks.java | 2 +- .../interpreter/node/BinaryOperatorNode.java | 11 +-- .../caseexpr/CatchTypeBranchNode.java | 2 +- .../builtin/error/CatchPanicNode.java | 2 +- .../builtin/meta/EqualsSimpleNode.java | 2 +- .../node/expression/builtin/meta/IsANode.java | 2 +- .../builtin/meta/IsValueOfTypeNode.java | 71 ++++++++++++------- .../node/typecheck/MetaTypeCheckNode.java | 2 +- .../node/typecheck/SingleTypeCheckNode.java | 4 +- .../runtime/library/dispatch/TypeOfNode.java | 46 ++++++++---- .../src/Semantic/Multi_Value_Spec.enso | 24 +++++++ 11 files changed, 113 insertions(+), 55 deletions(-) diff --git a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionCallbacks.java b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionCallbacks.java index 2900d878fe48..43adb215bd69 100644 --- a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionCallbacks.java +++ b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionCallbacks.java @@ -220,7 +220,7 @@ private String[] typeOf(Object value) { } var typeOfNode = TypeOfNode.getUncached(); - Type[] allTypes = value == null ? null : typeOfNode.findAllTypesOrNull(value); + Type[] allTypes = value == null ? null : typeOfNode.findAllTypesOrNull(value, true); if (allTypes != null) { String[] result = new String[allTypes.length]; for (var i = 0; i < allTypes.length; i++) { diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/BinaryOperatorNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/BinaryOperatorNode.java index e7106442abee..cd076538fca9 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/BinaryOperatorNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/BinaryOperatorNode.java @@ -152,20 +152,13 @@ final Object doThatConversionUncached( InvokeFunctionNode invokeNode) { var selfType = findType(typeOfNode, self); if (that instanceof EnsoMultiValue multi) { - var all = typeOfNode.findAllTypesOrNull(multi); + var all = typeOfNode.findAllTypesOrNull(multi, false); assert all != null; for (var thatType : all) { var fn = findSymbol(symbol, thatType); if (fn != null) { - var thatCasted = - EnsoMultiValue.CastToNode.getUncached() - .findTypeOrNull(thatType, multi, false, false); - if (thatCasted == null) { - continue; - } var result = - doDispatch( - frame, self, thatCasted, selfType, thatType, fn, convertNode, invokeNode); + doDispatch(frame, self, multi, selfType, thatType, fn, convertNode, invokeNode); if (result != null) { return result; } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/CatchTypeBranchNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/CatchTypeBranchNode.java index f29b5876fe00..88b7e6c9b672 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/CatchTypeBranchNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/CatchTypeBranchNode.java @@ -33,7 +33,7 @@ public static CatchTypeBranchNode build( } public void execute(VirtualFrame frame, Object state, Object value) { - if (profile.profile(isValueOfTypeNode.execute(expectedType, value))) { + if (profile.profile(isValueOfTypeNode.execute(expectedType, value, true))) { accept(frame, state, new Object[] {value}); } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/CatchPanicNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/CatchPanicNode.java index 6d29469fad89..e3ea9d1ef3f6 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/CatchPanicNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/CatchPanicNode.java @@ -86,7 +86,7 @@ private Object executeCallbackOrRethrow( AbstractTruffleException originalException, InteropLibrary interopLibrary) { - if (profile.profile(isValueOfTypeNode.execute(panicType, payload))) { + if (profile.profile(isValueOfTypeNode.execute(panicType, payload, true))) { var builtins = EnsoContext.get(this).getBuiltins(); var cons = builtins.caughtPanic().getUniqueConstructor(); var caughtPanic = diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/EqualsSimpleNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/EqualsSimpleNode.java index f7aedb3f7464..aaf800c5d961 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/EqualsSimpleNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/EqualsSimpleNode.java @@ -331,7 +331,7 @@ EqualsAndInfo equalsMultiValue( @Shared("multiCast") @Cached EnsoMultiValue.CastToNode castNode, @Shared("multiType") @Cached TypeOfNode typesNode, @Shared("multiEquals") @Cached EqualsSimpleNode delegate) { - var types = typesNode.findAllTypesOrNull(self); + var types = typesNode.findAllTypesOrNull(self, false); assert types != null; for (var t : types) { var value = castNode.findTypeOrNull(t, self, false, false); diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/IsANode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/IsANode.java index 753b369d3bed..51a5dbe2c858 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/IsANode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/IsANode.java @@ -13,6 +13,6 @@ public class IsANode extends Node { private @Child IsValueOfTypeNode check = IsValueOfTypeNode.build(); public boolean execute(@AcceptsError Object value, Object type) { - return check.execute(type, value); + return check.execute(type, value, true); } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/IsValueOfTypeNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/IsValueOfTypeNode.java index 46431e3d1679..c6324716969e 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/IsValueOfTypeNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/IsValueOfTypeNode.java @@ -22,46 +22,62 @@ /** An implementation of the payload check against the expected panic type. */ @NodeInfo(shortName = "IsValueOfTypeNode") public abstract class IsValueOfTypeNode extends Node { + IsValueOfTypeNode() {} + public static IsValueOfTypeNode build() { 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 types value has already been + * case to, specify {@code true} to return all types value can be cast to + */ + public abstract boolean execute(Object expectedType, Object obj, boolean includeExtraTypes); @Specialization(guards = {"types.hasType(payload)"}) boolean doTyped( Object expectedType, Object payload, + boolean allTypes, @Shared("types") @CachedLibrary(limit = "3") TypesLibrary types, @Cached Typed typed) { - return typed.execute(expectedType, payload); + return typed.execute(expectedType, payload, allTypes); } @Specialization(guards = {"!types.hasType(payload)"}) boolean doPolyglot( Object expectedType, Object payload, + boolean allTypes, @Shared("types") @CachedLibrary(limit = "3") TypesLibrary types, @Cached Untyped untyped) { - return untyped.execute(expectedType, payload); + return untyped.execute(expectedType, payload, allTypes); } private static boolean typeAndCheck( Object payload, Object expectedType, + boolean allTypes, TypeOfNode typeOfNode, IsSameObjectNode isSameObject, CountingConditionProfile isSameObjectProfile) { - Object tpeOfPayload = typeOfNode.findTypeOrError(payload); - if (isSameObjectProfile.profile(isSameObject.execute(expectedType, tpeOfPayload))) { - return true; - } else if (TypesGen.isType(tpeOfPayload)) { - Type tpe = TypesGen.asType(tpeOfPayload); - var ctx = EnsoContext.get(typeOfNode); - for (var superTpe : tpe.allTypes(ctx)) { - boolean testSuperTpe = isSameObject.execute(expectedType, superTpe); - if (testSuperTpe) { - return true; + var arr = typeOfNode.findAllTypesOrNull(payload, allTypes); + if (arr == null) { + return false; + } + for (var tpeOfPayload : arr) { + if (isSameObjectProfile.profile(isSameObject.execute(expectedType, tpeOfPayload))) { + return true; + } else if (TypesGen.isType(tpeOfPayload)) { + Type tpe = TypesGen.asType(tpeOfPayload); + var ctx = EnsoContext.get(typeOfNode); + for (var superTpe : tpe.allTypes(ctx)) { + boolean testSuperTpe = isSameObject.execute(expectedType, superTpe); + if (testSuperTpe) { + return true; + } } } } @@ -73,33 +89,33 @@ abstract static class Typed extends Node { private @Child TypeOfNode typeOfNode = TypeOfNode.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)") - boolean doAnyType(Object expectedType, Object payload) { + boolean doAnyType(Object expectedType, Object payload, boolean allTypes) { return true; } @Specialization - boolean doLongCheck(Type expectedType, long payload) { + boolean doLongCheck(Type expectedType, long payload, boolean allTypes) { var numbers = EnsoContext.get(this).getBuiltins().number(); return checkParentTypes(numbers.getInteger(), expectedType); } @Specialization - boolean doDoubleCheck(Type expectedType, double payload) { + boolean doDoubleCheck(Type expectedType, double payload, boolean allTypes) { var numbers = EnsoContext.get(this).getBuiltins().number(); return checkParentTypes(numbers.getFloat(), expectedType); } @Specialization - boolean doBigIntegerCheck(Type expectedType, EnsoBigInteger value) { + boolean doBigIntegerCheck(Type expectedType, EnsoBigInteger value, boolean allTypes) { var numbers = EnsoContext.get(this).getBuiltins().number(); return checkParentTypes(numbers.getInteger(), expectedType); } @Specialization - boolean doUnresolvedSymbol(Type expectedType, UnresolvedSymbol value) { + boolean doUnresolvedSymbol(Type expectedType, UnresolvedSymbol value, boolean allTypes) { var funTpe = EnsoContext.get(this).getBuiltins().function(); return expectedType == funTpe; } @@ -119,8 +135,9 @@ private boolean checkParentTypes(Type actual, Type expected) { boolean doType( Type expectedType, Object payload, + boolean allTypes, @Shared("types") @CachedLibrary(limit = "3") TypesLibrary types) { - return typeAndCheck(payload, expectedType, typeOfNode, isSameObject, profile); + return typeAndCheck(payload, expectedType, allTypes, typeOfNode, isSameObject, profile); } @Specialization( @@ -131,13 +148,14 @@ boolean doType( public boolean doArrayViaType( Object expectedType, Object payload, + boolean allTypes, @CachedLibrary(limit = "3") InteropLibrary interop, @Shared("types") @CachedLibrary(limit = "3") TypesLibrary types) { return EnsoContext.get(this).getBuiltins().array() == types.getType(payload); } @Fallback - boolean doOther(Object expectedType, Object payload) { + boolean doOther(Object expectedType, Object payload, boolean allTypes) { return false; } @@ -155,7 +173,7 @@ abstract static class Untyped extends Node { private @Child TypeOfNode typeOfNode = TypeOfNode.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 = { @@ -163,7 +181,10 @@ abstract static class Untyped extends Node { "isMetaInstance(interop, expectedType, payload)" }) boolean doPolyglotType( - Object expectedType, Object payload, @CachedLibrary(limit = "3") InteropLibrary interop) { + Object expectedType, + Object payload, + boolean allTypes, + @CachedLibrary(limit = "3") InteropLibrary interop) { return true; } @@ -176,8 +197,8 @@ static boolean isMetaInstance(InteropLibrary interop, Object expectedType, Objec } @Fallback - public boolean doOther(Object expectedType, Object payload) { - return typeAndCheck(payload, expectedType, typeOfNode, isSameObject, profile); + public boolean doOther(Object expectedType, Object payload, boolean allTypes) { + return typeAndCheck(payload, expectedType, allTypes, typeOfNode, isSameObject, profile); } } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/typecheck/MetaTypeCheckNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/typecheck/MetaTypeCheckNode.java index 2e59365ddd50..fcd2ac1ab4ce 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/typecheck/MetaTypeCheckNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/typecheck/MetaTypeCheckNode.java @@ -34,7 +34,7 @@ Object verifyMetaObject(VirtualFrame frame, Object v, @Cached IsValueOfTypeNode if (isAllFitValue(v)) { return v; } - if (isA.execute(expectedSupplier.get(), v)) { + if (isA.execute(expectedSupplier.get(), v, true)) { return v; } else { return null; diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/typecheck/SingleTypeCheckNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/typecheck/SingleTypeCheckNode.java index e868bdbc327d..cb5f26b10249 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/typecheck/SingleTypeCheckNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/typecheck/SingleTypeCheckNode.java @@ -123,7 +123,7 @@ final Object findDirectMatch(VirtualFrame frame, Object v) { return result; } } - if (checkType.execute(expectedType, v)) { + if (checkType.execute(expectedType, v, isAllTypes())) { return v; } return null; @@ -176,7 +176,7 @@ Type[] findType(TypeOfNode typeOfNode, Object v) { Type[] findType(TypeOfNode typeOfNode, Object v, Type[] previous) { if (v instanceof EnsoMultiValue multi) { - var all = typeOfNode.findAllTypesOrNull(multi); + var all = typeOfNode.findAllTypesOrNull(multi, isAllTypes()); assert all != null; var to = 0; // only consider methodDispatchTypes and not "all types" of the multi value diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/library/dispatch/TypeOfNode.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/library/dispatch/TypeOfNode.java index 0649a0de805c..d6e237302ed0 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/library/dispatch/TypeOfNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/library/dispatch/TypeOfNode.java @@ -12,6 +12,7 @@ import com.oracle.truffle.api.interop.UnsupportedMessageException; import com.oracle.truffle.api.library.CachedLibrary; import com.oracle.truffle.api.nodes.Node; +import java.util.Arrays; import org.enso.interpreter.node.expression.builtin.meta.AtomWithAHoleNode; import org.enso.interpreter.runtime.EnsoContext; import org.enso.interpreter.runtime.builtin.Builtins; @@ -53,7 +54,7 @@ public final boolean hasType(Object value) { */ public final TruffleObject findTypeOrError(Object value) { try { - var types = executeTypes(value); + var types = executeTypes(value, false); return types[0]; } catch (NonTypeResult plain) { return plain.result; @@ -76,11 +77,13 @@ public final Type findTypeOrNull(Object value) { * 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 includeExtraTypes specify {@code false} to return only types value has already been + * case to, specify {@code true} to return all types value can be cast to * @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 { - var types = executeTypes(value); + var types = executeTypes(value, includeExtraTypes); assert types != null && types.length > 0; return types; } catch (NonTypeResult ex) { @@ -92,11 +95,13 @@ public final Type[] findAllTypesOrNull(Object value) { * Internal implementation call to delegate to various {@link Specialization} methods. * * @param value the value to find type for + * @param includeExtraTypes {@code false} to return only types value has already been case + * to, {@code true} to return all types value can be cast to * @return array of types with at least one element, but possibly more * @throws NonTypeResult when there is a interop value result, but it is not a {@link * Type} */ - abstract Type[] executeTypes(Object value) throws NonTypeResult; + abstract Type[] executeTypes(Object value, boolean includeExtraTypes) throws NonTypeResult; /** * Creates new optimizing instance of this node. @@ -123,38 +128,39 @@ private static Type[] fromType(Type single) { } @Specialization - Type[] doUnresolvedSymbol(UnresolvedSymbol value) { + Type[] doUnresolvedSymbol(UnresolvedSymbol value, boolean allTypes) { return fromType(EnsoContext.get(this).getBuiltins().function()); } @Specialization - Type[] doDouble(double value) { + Type[] doDouble(double value, boolean allTypes) { return fromType(EnsoContext.get(this).getBuiltins().number().getFloat()); } @Specialization - Type[] doLong(long value) { + Type[] doLong(long value, boolean allTypes) { return fromType(EnsoContext.get(this).getBuiltins().number().getInteger()); } @Specialization - Type[] doBigInteger(EnsoBigInteger value) { + Type[] doBigInteger(EnsoBigInteger value, boolean allTypes) { return fromType(EnsoContext.get(this).getBuiltins().number().getInteger()); } @Specialization - Type[] doPanicException(PanicException value) { + Type[] doPanicException(PanicException value, boolean allTypes) { return fromType(EnsoContext.get(this).getBuiltins().panic()); } @Specialization - Type[] doPanicSentinel(PanicSentinel value) { + Type[] doPanicSentinel(PanicSentinel value, boolean allTypes) { return fromType(EnsoContext.get(this).getBuiltins().panic()); } @Specialization - Type[] doWarning(WithWarnings value, @Cached TypeOfNode withoutWarning) throws NonTypeResult { - return withoutWarning.executeTypes(value.getValue()); + Type[] doWarning(WithWarnings value, boolean includeExtraTypes, @Cached TypeOfNode withoutWarning) + throws NonTypeResult { + return withoutWarning.executeTypes(value.getValue(), includeExtraTypes); } static boolean isWithType(Object value, TypesLibrary types, InteropLibrary iop) { @@ -180,6 +186,7 @@ static boolean isWithoutType(Object value, TypesLibrary types) { @Specialization(guards = {"isWithoutType(value, types)"}) Type[] withoutType( Object value, + boolean allTypes, @Shared("interop") @CachedLibrary(limit = "3") InteropLibrary interop, @Shared("types") @CachedLibrary(limit = "3") TypesLibrary types, @Cached WithoutType delegate) @@ -201,14 +208,27 @@ Type[] withoutType( @Specialization(guards = {"isWithType(value, types, interop)"}) Type[] doType( Object value, + boolean allTypes, @Shared("interop") @CachedLibrary(limit = "3") InteropLibrary interop, @Shared("types") @CachedLibrary(limit = "3") TypesLibrary types) { + if (value instanceof EnsoMultiValue multi && !allTypes) { + var arr = types.allTypes(value); + var to = 0; + for (var i = 0; i < arr.length; i++) { + var typ = + EnsoMultiValue.CastToNode.getUncached().findTypeOrNull(arr[i], multi, false, false); + if (typ != null) { + arr[to++] = arr[i]; + } + } + return Arrays.copyOf(arr, to); + } return types.allTypes(value); } @Fallback @CompilerDirectives.TruffleBoundary - Type[] doAny(Object value) throws NonTypeResult { + Type[] doAny(Object value, boolean allTypes) throws NonTypeResult { var err = DataflowError.withDefaultTrace( EnsoContext.get(this) diff --git a/test/Base_Tests/src/Semantic/Multi_Value_Spec.enso b/test/Base_Tests/src/Semantic/Multi_Value_Spec.enso index 5c4774d693bd..7eb0890c6132 100644 --- a/test/Base_Tests/src/Semantic/Multi_Value_Spec.enso +++ b/test/Base_Tests/src/Semantic/Multi_Value_Spec.enso @@ -87,9 +87,13 @@ add_specs suite_builder = (x:A).a . should_equal "a" call_a obj:A = obj.a + call_b obj:B = obj.b # A&X type has attribute 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 # https://github.com/enso-org/enso/pull/11600#discussion_r1867584107 @@ -97,10 +101,30 @@ add_specs suite_builder = Test.expect_panic Type_Error <| 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 Test.expect_panic Type_Error <| 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" + group_builder.specify "Intersection type of unrelated types is not possible" <| Test.expect_panic Type_Error <| _ = X:X&B From 6e581c6bb81886a07e2e729967ad965a9262fa53 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Thu, 12 Dec 2024 16:38:49 +0100 Subject: [PATCH 2/9] Testing both values of allTypes --- .../meta/TypeOfNodeMultiValueTest.java | 17 +++++++--- .../builtin/meta/TypeOfNodeValueTest.java | 34 +++++++++++++++++-- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/node/expression/builtin/meta/TypeOfNodeMultiValueTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/node/expression/builtin/meta/TypeOfNodeMultiValueTest.java index 51c9eb632d25..835c24f62f24 100644 --- a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/node/expression/builtin/meta/TypeOfNodeMultiValueTest.java +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/node/expression/builtin/meta/TypeOfNodeMultiValueTest.java @@ -49,8 +49,9 @@ private static Context ctx() { new TestRootNode( (frame) -> { var arg = frame.getArguments()[0]; + var allTypes = (boolean) frame.getArguments()[1]; var t = node.findTypeOrError(arg); - var all = node.findAllTypesOrNull(arg); + var all = node.findAllTypesOrNull(arg, allTypes); return new Object[] {t, all}; }); root.insertChildren(node); @@ -116,16 +117,22 @@ public static void disposeCtx() throws Exception { } @Test - public void typeOfCheck() { - assertType(value, type, typeIndex); + public void typeOfCheckAllTypes() { + 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); ContextUtils.executeInContext( ctx(), () -> { - var pairResult = (Object[]) testTypesCall.call(value); + var pairResult = (Object[]) testTypesCall.call(value, allTypes); var t = pairResult[0]; var all = (Object[]) pairResult[1]; diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/node/expression/builtin/meta/TypeOfNodeValueTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/node/expression/builtin/meta/TypeOfNodeValueTest.java index 2633448001c0..3c22ef041617 100644 --- a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/node/expression/builtin/meta/TypeOfNodeValueTest.java +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/node/expression/builtin/meta/TypeOfNodeValueTest.java @@ -3,8 +3,10 @@ import static org.junit.Assert.assertEquals; 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.UnresolvedSymbol; +import org.enso.interpreter.runtime.data.EnsoMultiValue; import org.enso.interpreter.runtime.data.Type; import org.enso.interpreter.runtime.library.dispatch.TypeOfNode; import org.enso.test.utils.ContextUtils; @@ -28,8 +30,9 @@ private static Context ctx() { new TestRootNode( (frame) -> { var arg = frame.getArguments()[0]; + var allTypes = (boolean) frame.getArguments()[1]; var t = node.findTypeOrError(arg); - var all = node.findAllTypesOrNull(arg); + var all = node.findAllTypesOrNull(arg, allTypes); return new Object[] {t, all}; }); root.insertChildren(node); @@ -54,7 +57,7 @@ public void typeOfUnresolvedConstructor() { ctx(), () -> { 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 allTypes = (Type[]) arr[1]; assertEquals("Function", type.getName()); @@ -70,7 +73,7 @@ public void typeOfUnresolvedSymbol() { ctx(), () -> { 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 allTypes = (Type[]) arr[1]; assertEquals("Function", type.getName()); @@ -79,4 +82,29 @@ public void typeOfUnresolvedSymbol() { 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; + }); + } } From 419c38ecba6e22958174dae9e00220be85bcf095 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Thu, 12 Dec 2024 17:39:07 +0100 Subject: [PATCH 3/9] When the expectedMeta isn't type, stick to the old good typeOfNode.findTypeOrError --- .../builtin/meta/IsValueOfTypeNode.java | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/IsValueOfTypeNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/IsValueOfTypeNode.java index c6324716969e..2f9fac66c36a 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/IsValueOfTypeNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/IsValueOfTypeNode.java @@ -17,7 +17,6 @@ import org.enso.interpreter.runtime.library.dispatch.TypeOfNode; import org.enso.interpreter.runtime.library.dispatch.TypesLibrary; 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. */ @NodeInfo(shortName = "IsValueOfTypeNode") @@ -58,28 +57,32 @@ boolean doPolyglot( private static boolean typeAndCheck( Object payload, - Object expectedType, + Object expectedMeta, boolean allTypes, TypeOfNode typeOfNode, IsSameObjectNode isSameObject, CountingConditionProfile isSameObjectProfile) { - var arr = typeOfNode.findAllTypesOrNull(payload, allTypes); - if (arr == null) { - return false; - } - for (var tpeOfPayload : arr) { - if (isSameObjectProfile.profile(isSameObject.execute(expectedType, tpeOfPayload))) { - return true; - } else if (TypesGen.isType(tpeOfPayload)) { - Type tpe = TypesGen.asType(tpeOfPayload); - var ctx = EnsoContext.get(typeOfNode); - for (var superTpe : tpe.allTypes(ctx)) { - boolean testSuperTpe = isSameObject.execute(expectedType, superTpe); - if (testSuperTpe) { - return true; + if (expectedMeta instanceof Type expectedType) { + var arr = typeOfNode.findAllTypesOrNull(payload, allTypes); + if (arr == null) { + return false; + } + for (var tpeOfPayload : arr) { + if (isSameObjectProfile.profile(isSameObject.execute(expectedType, tpeOfPayload))) { + 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; } From 391af30bc0549e936031c7c4e9d30c42cdd231dc Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Thu, 12 Dec 2024 17:41:39 +0100 Subject: [PATCH 4/9] Fixing typo Co-authored-by: Gregory Michael Travis --- .../node/expression/builtin/meta/IsValueOfTypeNode.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/IsValueOfTypeNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/IsValueOfTypeNode.java index 2f9fac66c36a..da3ab5653e46 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/IsValueOfTypeNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/IsValueOfTypeNode.java @@ -31,7 +31,7 @@ public static IsValueOfTypeNode build() { * @param expectedType the type to check * @param obj the object to check * @param includeExtraTypes specify {@code false} to return only types value has already been - * case to, specify {@code true} to return all types value can be cast to + * cast to, specify {@code true} to return all types value can be cast to */ public abstract boolean execute(Object expectedType, Object obj, boolean includeExtraTypes); From e22a52e43383121efb0b0b180ed5425b6c671c8c Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Thu, 12 Dec 2024 18:00:59 +0100 Subject: [PATCH 5/9] Extract extra type from a multi value before executing the branch code --- .../node/controlflow/caseexpr/CatchTypeBranchNode.java | 5 +++++ test/Base_Tests/src/Semantic/Multi_Value_Spec.enso | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/CatchTypeBranchNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/CatchTypeBranchNode.java index 88b7e6c9b672..d09b2ed40967 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/CatchTypeBranchNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/CatchTypeBranchNode.java @@ -5,6 +5,7 @@ import com.oracle.truffle.api.nodes.NodeInfo; import com.oracle.truffle.api.profiles.CountingConditionProfile; import org.enso.interpreter.node.expression.builtin.meta.IsValueOfTypeNode; +import org.enso.interpreter.runtime.data.EnsoMultiValue; import org.enso.interpreter.runtime.data.Type; /** An implementation of the case expression specialised to working on types. */ @@ -34,6 +35,10 @@ public static CatchTypeBranchNode build( public void execute(VirtualFrame frame, Object state, Object value) { if (profile.profile(isValueOfTypeNode.execute(expectedType, value, true))) { + if (value instanceof EnsoMultiValue multi) { + value = + EnsoMultiValue.CastToNode.getUncached().findTypeOrNull(expectedType, multi, true, true); + } accept(frame, state, new Object[] {value}); } } diff --git a/test/Base_Tests/src/Semantic/Multi_Value_Spec.enso b/test/Base_Tests/src/Semantic/Multi_Value_Spec.enso index 7eb0890c6132..4cee0a32e6b2 100644 --- a/test/Base_Tests/src/Semantic/Multi_Value_Spec.enso +++ b/test/Base_Tests/src/Semantic/Multi_Value_Spec.enso @@ -125,6 +125,11 @@ add_specs suite_builder = 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" <| Test.expect_panic Type_Error <| _ = X:X&B From 2a83bf4a91963d3049a069af265fb7ce9a265906 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Fri, 13 Dec 2024 06:16:08 +0100 Subject: [PATCH 6/9] Updating case of documentation --- docs/types/intersection-types.md | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/docs/types/intersection-types.md b/docs/types/intersection-types.md index 5d75b6e0873a..2a5b8b0ba4b9 100644 --- a/docs/types/intersection-types.md +++ b/docs/types/intersection-types.md @@ -80,18 +80,12 @@ method calls will also only accept the value if it satisfies the type it _has been cast to_. Any additional remaining _hidden_ types can only be brought back through an _explicit_ cast. 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 + f : Float -> f.sqrt + _ -> "Not a float" +``` > [!WARNING] > Keep in mind that while both argument type check in method definitions and a From d12bcee93a216581c613d2f00e37d3dd4e099549 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Fri, 13 Dec 2024 06:44:59 +0100 Subject: [PATCH 7/9] Prefer getURI path when gathering location of a source --- .../expression/builtin/error/GetStackTraceTextNode.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/GetStackTraceTextNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/GetStackTraceTextNode.java index a4b0c9027792..8dce7b82ed1e 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/GetStackTraceTextNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/GetStackTraceTextNode.java @@ -4,6 +4,7 @@ import com.oracle.truffle.api.TruffleStackTrace; import com.oracle.truffle.api.TruffleStackTraceElement; import com.oracle.truffle.api.nodes.Node; +import java.io.File; import java.util.ArrayList; import java.util.Collections; import org.enso.interpreter.dsl.BuiltinMethod; @@ -76,6 +77,11 @@ String printStackTrace(Throwable throwable) { var sourceLoc = errorFrame.getLocation().getEncapsulatingSourceSection(); if (sourceLoc != null) { 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 loc = (sourceLoc.getStartLine() == sourceLoc.getEndLine()) From 571cce6001bcedd419a0aa80226e8d5929b8324c Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Fri, 13 Dec 2024 07:17:40 +0100 Subject: [PATCH 8/9] Use matched value in case of instead of scrutinee --- .../Test/0.0.0-dev/src/Extensions.enso | 2 +- docs/types/intersection-types.md | 2 ++ .../caseexpr/CatchTypeBranchNode.java | 4 ++- .../runtime/data/EnsoMultiValue.java | 25 +++++++++++-------- 4 files changed, 20 insertions(+), 13 deletions(-) diff --git a/distribution/lib/Standard/Test/0.0.0-dev/src/Extensions.enso b/distribution/lib/Standard/Test/0.0.0-dev/src/Extensions.enso index bafe3431a303..46741a56a5c0 100644 --- a/distribution/lib/Standard/Test/0.0.0-dev/src/Extensions.enso +++ b/distribution/lib/Standard/Test/0.0.0-dev/src/Extensions.enso @@ -295,7 +295,7 @@ Error.should_equal self that frames_to_skip=0 = Number.should_equal : Float -> Float -> Integer -> Spec_Result Number.should_equal self that epsilon=0 frames_to_skip=0 = matches = case that of - _ : Number -> self.equals that epsilon + n : Number -> self.equals n epsilon _ -> self==that case matches of True -> Spec_Result.Success diff --git a/docs/types/intersection-types.md b/docs/types/intersection-types.md index 2a5b8b0ba4b9..919e138a0cc1 100644 --- a/docs/types/intersection-types.md +++ b/docs/types/intersection-types.md @@ -86,6 +86,8 @@ case c of f : Float -> f.sqrt _ -> "Not a float" ``` +Remember to use `f.sqrt` and not `c.sqrt`. `f` in the case branch _has been cast to_ `Float` while +`c` in the case branch only _can be cast to_. > [!WARNING] > Keep in mind that while both argument type check in method definitions and a diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/CatchTypeBranchNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/CatchTypeBranchNode.java index d09b2ed40967..d74721bf7486 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/CatchTypeBranchNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/CatchTypeBranchNode.java @@ -36,8 +36,10 @@ public static CatchTypeBranchNode build( public void execute(VirtualFrame frame, Object state, Object value) { if (profile.profile(isValueOfTypeNode.execute(expectedType, value, true))) { if (value instanceof EnsoMultiValue multi) { - value = + 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}); } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoMultiValue.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoMultiValue.java index 4ed82b616e6d..6576fb1a9491 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoMultiValue.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoMultiValue.java @@ -452,19 +452,22 @@ public static CastToNode getUncached() { @Specialization Object castsToAType(Type type, EnsoMultiValue mv, boolean reorderOnly, boolean allTypes) { + var ctx = EnsoContext.get(this); var max = allTypes ? mv.types.length : mv.methodDispatchTypes; for (var i = 0; i < max; i++) { - if (mv.types[i] == type) { - if (reorderOnly) { - var copyTypes = mv.types.clone(); - var copyValues = mv.values.clone(); - copyTypes[i] = mv.types[0]; - copyValues[i] = mv.values[0]; - copyTypes[0] = mv.types[i]; - copyValues[0] = mv.values[i]; - return EnsoMultiValue.create(copyTypes, 1, copyValues); - } else { - return mv.values[i]; + for (var t : mv.types[i].allTypes(ctx)) { + if (t == type) { + if (reorderOnly) { + var copyTypes = mv.types.clone(); + var copyValues = mv.values.clone(); + copyTypes[i] = mv.types[0]; + copyValues[i] = mv.values[0]; + copyTypes[0] = mv.types[i]; + copyValues[0] = mv.values[i]; + return EnsoMultiValue.create(copyTypes, 1, copyValues); + } else { + return mv.values[i]; + } } } } From 32bdcebec4f4a4ac40eb8ba97af73855cf4bdd7b Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Fri, 13 Dec 2024 08:01:32 +0100 Subject: [PATCH 9/9] Let EnsoMultiValue.allTypes(includeExtraTypes) decide whether to include all types or not --- .../runtime/data/EnsoMultiValue.java | 8 ++++++-- .../runtime/library/dispatch/TypeOfNode.java | 17 ++--------------- .../runtime/library/dispatch/TypesLibrary.java | 4 +++- 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoMultiValue.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoMultiValue.java index 6576fb1a9491..22f31f090bd1 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoMultiValue.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoMultiValue.java @@ -88,8 +88,12 @@ final Type getType() { } @ExportMessage - final Type[] allTypes() { - return types.clone(); + final Type[] allTypes(boolean includeExtraTypes) { + if (includeExtraTypes || methodDispatchTypes == types.length) { + return types.clone(); + } else { + return Arrays.copyOf(types, methodDispatchTypes); + } } @ExportMessage diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/library/dispatch/TypeOfNode.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/library/dispatch/TypeOfNode.java index d6e237302ed0..88360c843e6c 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/library/dispatch/TypeOfNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/library/dispatch/TypeOfNode.java @@ -12,7 +12,6 @@ import com.oracle.truffle.api.interop.UnsupportedMessageException; import com.oracle.truffle.api.library.CachedLibrary; import com.oracle.truffle.api.nodes.Node; -import java.util.Arrays; import org.enso.interpreter.node.expression.builtin.meta.AtomWithAHoleNode; import org.enso.interpreter.runtime.EnsoContext; import org.enso.interpreter.runtime.builtin.Builtins; @@ -208,22 +207,10 @@ Type[] withoutType( @Specialization(guards = {"isWithType(value, types, interop)"}) Type[] doType( Object value, - boolean allTypes, + boolean includeExtraTypes, @Shared("interop") @CachedLibrary(limit = "3") InteropLibrary interop, @Shared("types") @CachedLibrary(limit = "3") TypesLibrary types) { - if (value instanceof EnsoMultiValue multi && !allTypes) { - var arr = types.allTypes(value); - var to = 0; - for (var i = 0; i < arr.length; i++) { - var typ = - EnsoMultiValue.CastToNode.getUncached().findTypeOrNull(arr[i], multi, false, false); - if (typ != null) { - arr[to++] = arr[i]; - } - } - return Arrays.copyOf(arr, to); - } - return types.allTypes(value); + return types.allTypes(value, includeExtraTypes); } @Fallback diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/library/dispatch/TypesLibrary.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/library/dispatch/TypesLibrary.java index eea5cd8a5231..f87c02aab94e 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/library/dispatch/TypesLibrary.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/library/dispatch/TypesLibrary.java @@ -74,9 +74,11 @@ public Type getType(Object receiver) { * #getType(java.lang.Object)} and returns array with one element of that type * * @param receiver the typed object + * @param includeExtraTypes specify {@code false} to return only types value has already been + * case to, specify {@code true} to return all types value can be cast to * @return the corresponding types for the {@code receiver} */ - public Type[] allTypes(Object receiver) { + public Type[] allTypes(Object receiver, boolean includeExtraTypes) { var t = getType(receiver); assert t != null : "null type for " + receiver; return new Type[] {t};