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 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

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
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`.
<!--
When #11828 is fixed.
- or inspect the types in a `case` expression, e.g.
```
case c of
`f = c:Float` or inspect the types in a `case` expression, e.g.
```ruby
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

View File

@ -220,7 +220,7 @@ final class ExecutionCallbacks implements IdExecutionService.Callbacks {
}
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++) {

View File

@ -49,8 +49,9 @@ public class TypeOfNodeMultiValueTest {
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 class TypeOfNodeMultiValueTest {
}
@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];

View File

@ -3,8 +3,10 @@ package org.enso.interpreter.node.expression.builtin.meta;
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 @@ public class TypeOfNodeValueTest {
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 class TypeOfNodeValueTest {
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 class TypeOfNodeValueTest {
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 class TypeOfNodeValueTest {
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) {
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;
}

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.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. */
@ -33,7 +34,13 @@ public class CatchTypeBranchNode extends BranchNode {
}
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});
}
}

View File

@ -86,7 +86,7 @@ public abstract class CatchPanicNode extends Node {
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 =

View File

@ -4,6 +4,7 @@ import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
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 @@ public class GetStackTraceTextNode extends Node {
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())

View File

@ -331,7 +331,7 @@ abstract class EqualsSimpleNode extends Node {
@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);

View File

@ -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);
}
}

View File

@ -17,54 +17,73 @@ import org.enso.interpreter.runtime.data.Type;
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")
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 <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)"})
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,
Object expectedMeta,
boolean allTypes,
TypeOfNode typeOfNode,
IsSameObjectNode isSameObject,
CountingConditionProfile isSameObjectProfile) {
Object tpeOfPayload = typeOfNode.findTypeOrError(payload);
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 if (TypesGen.isType(tpeOfPayload)) {
Type tpe = TypesGen.asType(tpeOfPayload);
} else {
var ctx = EnsoContext.get(typeOfNode);
for (var superTpe : tpe.allTypes(ctx)) {
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;
}
@ -73,33 +92,33 @@ public abstract class IsValueOfTypeNode 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 +138,9 @@ public abstract class IsValueOfTypeNode extends Node {
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 +151,14 @@ public abstract class IsValueOfTypeNode extends Node {
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 +176,7 @@ public abstract class IsValueOfTypeNode 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 +184,10 @@ public abstract class IsValueOfTypeNode 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 +200,8 @@ public abstract class IsValueOfTypeNode extends Node {
}
@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);
}
}
}

View File

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

View File

@ -123,7 +123,7 @@ non-sealed abstract class SingleTypeCheckNode extends AbstractTypeCheckNode {
return result;
}
}
if (checkType.execute(expectedType, v)) {
if (checkType.execute(expectedType, v, isAllTypes())) {
return v;
}
return null;
@ -176,7 +176,7 @@ non-sealed abstract class SingleTypeCheckNode extends AbstractTypeCheckNode {
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

View File

@ -88,8 +88,12 @@ public final class EnsoMultiValue extends EnsoObject {
}
@ExportMessage
final Type[] allTypes() {
final Type[] allTypes(boolean includeExtraTypes) {
if (includeExtraTypes || methodDispatchTypes == types.length) {
return types.clone();
} else {
return Arrays.copyOf(types, methodDispatchTypes);
}
}
@ExportMessage
@ -452,9 +456,11 @@ public final class EnsoMultiValue extends EnsoObject {
@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) {
for (var t : mv.types[i].allTypes(ctx)) {
if (t == type) {
if (reorderOnly) {
var copyTypes = mv.types.clone();
var copyValues = mv.values.clone();
@ -468,6 +474,7 @@ public final class EnsoMultiValue extends EnsoObject {
}
}
}
}
return null;
}
}

View File

@ -53,7 +53,7 @@ public abstract class TypeOfNode extends Node {
*/
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 +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}.
*
* @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
*/
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 +94,13 @@ public abstract class TypeOfNode extends Node {
* 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 <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
* @throws NonTypeResult when there is a <em>interop value</em> 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 +127,39 @@ public abstract class TypeOfNode extends Node {
}
@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 +185,7 @@ public abstract class TypeOfNode extends Node {
@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 +207,15 @@ public abstract class TypeOfNode extends Node {
@Specialization(guards = {"isWithType(value, types, interop)"})
Type[] doType(
Object value,
boolean includeExtraTypes,
@Shared("interop") @CachedLibrary(limit = "3") InteropLibrary interop,
@Shared("types") @CachedLibrary(limit = "3") TypesLibrary types) {
return types.allTypes(value);
return types.allTypes(value, includeExtraTypes);
}
@Fallback
@CompilerDirectives.TruffleBoundary
Type[] doAny(Object value) throws NonTypeResult {
Type[] doAny(Object value, boolean allTypes) throws NonTypeResult {
var err =
DataflowError.withDefaultTrace(
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
*
* @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}
*/
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};

View File

@ -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,35 @@ 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"
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