diff --git a/CHANGELOG.md b/CHANGELOG.md index 3544fcd6d43b..f117d9342f35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -945,6 +945,7 @@ - [Use `numpy` & co. from Enso!][7678] - [Changed layout of local libraries directory, making it easier to reference projects next to each other][7634] +- [Support runtime checks of intersection types][7769] - [Merge `Small_Integer` and `Big_Integer` types][7636] [3227]: https://github.com/enso-org/enso/pull/3227 @@ -1083,6 +1084,7 @@ [7613]: https://github.com/enso-org/enso/pull/7613 [7678]: https://github.com/enso-org/enso/pull/7678 [7634]: https://github.com/enso-org/enso/pull/7634 +[7769]: https://github.com/enso-org/enso/pull/7769 [7636]: https://github.com/enso-org/enso/pull/7636 # Enso 2.0.0-alpha.18 (2021-10-12) diff --git a/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/type/Set.scala b/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/type/Set.scala index dc0c6c523902..86117617a62c 100644 --- a/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/type/Set.scala +++ b/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/type/Set.scala @@ -650,108 +650,4 @@ object Set { object Intersection extends Info { override val name: String = "&" } - - /** The typeset subtraction operator `\`. - * - * @param left the left operand - * @param right the right operand - * @param location the source location that the node corresponds to - * @param passData the pass metadata associated with this node - * @param diagnostics compiler diagnostics for this node - */ - sealed case class Subtraction( - left: Expression, - right: Expression, - override val location: Option[IdentifiedLocation], - override val passData: MetadataStorage = MetadataStorage(), - override val diagnostics: DiagnosticStorage = DiagnosticStorage() - ) extends Set - with IRKind.Primitive { - override protected var id: Identifier = randomId - - /** Creates a copy of `this`. - * - * @param left the left operand - * @param right the right operand - * @param location the source location that the node corresponds to - * @param passData the pass metadata associated with this node - * @param diagnostics compiler diagnostics for this node - * @param id the identifier for the new node - * @return a copy of `this`, updated with the specified values - */ - def copy( - left: Expression = left, - right: Expression = right, - location: Option[IdentifiedLocation] = location, - passData: MetadataStorage = passData, - diagnostics: DiagnosticStorage = diagnostics, - id: Identifier = id - ): Subtraction = { - val res = Subtraction(left, right, location, passData, diagnostics) - res.id = id - res - } - - /** @inheritdoc */ - override def duplicate( - keepLocations: Boolean = true, - keepMetadata: Boolean = true, - keepDiagnostics: Boolean = true, - keepIdentifiers: Boolean = false - ): Subtraction = - copy( - left = left.duplicate( - keepLocations, - keepMetadata, - keepDiagnostics, - keepIdentifiers - ), - right = right.duplicate( - keepLocations, - keepMetadata, - keepDiagnostics, - keepIdentifiers - ), - location = if (keepLocations) location else None, - passData = if (keepMetadata) passData.duplicate else MetadataStorage(), - diagnostics = - if (keepDiagnostics) diagnostics.copy else DiagnosticStorage(), - id = if (keepIdentifiers) id else randomId - ) - - /** @inheritdoc */ - override def setLocation( - location: Option[IdentifiedLocation] - ): Subtraction = copy(location = location) - - /** @inheritdoc */ - override def mapExpressions( - fn: Expression => Expression - ): Subtraction = { - copy(left = fn(left), right = fn(right)) - } - - /** @inheritdoc */ - override def toString: String = - s""" - |`type`.Set.Subtraction( - |left = $left, - |right = $right, - |location = $location, - |passData = ${this.showPassData}, - |diagnostics = $diagnostics, - |id = $id - |""".toSingleLine - - /** @inheritdoc */ - override def children: List[IR] = List(left, right) - - /** @inheritdoc */ - override def showCode(indent: Int): String = - s"(${left.showCode(indent)} \\ ${right.showCode(indent)})" - } - - object Subtraction extends Info { - override val name: String = "\\" - } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeMethodNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeMethodNode.java index d3c9da728029..d171e90fda8a 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeMethodNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeMethodNode.java @@ -30,6 +30,7 @@ import org.enso.interpreter.runtime.data.EnsoDate; import org.enso.interpreter.runtime.data.EnsoDateTime; import org.enso.interpreter.runtime.data.EnsoDuration; +import org.enso.interpreter.runtime.data.EnsoMultiValue; import org.enso.interpreter.runtime.data.EnsoTimeOfDay; import org.enso.interpreter.runtime.data.EnsoTimeZone; import org.enso.interpreter.runtime.data.Type; @@ -208,9 +209,7 @@ Object doFunctionalDispatchUncachedSymbol( Type selfTpe = typesLibrary.getType(self); Function function = resolveFunction(symbol, selfTpe, methodResolverNode); if (function == null) { - var cause = onBoundary ? UnknownIdentifierException.create(symbol.getName()) : null; - var payload = EnsoContext.get(this).getBuiltins().error().makeNoSuchMethod(self, symbol); - throw new PanicException(payload, cause, this); + throw methodNotFound(symbol, self); } var resolvedFuncArgCount = function.getSchema().getArgumentsCount(); CallArgumentInfo[] invokeFuncSchema = invokeFunctionNode.getSchema(); @@ -271,6 +270,32 @@ Object doFunctionalDispatchUncachedSymbol( return invokeFunctionNode.execute(function, frame, state, arguments); } + private PanicException methodNotFound(UnresolvedSymbol symbol, Object self) throws PanicException { + var cause = onBoundary ? UnknownIdentifierException.create(symbol.getName()) : null; + var payload = EnsoContext.get(this).getBuiltins().error().makeNoSuchMethod(self, symbol); + throw new PanicException(payload, cause, this); + } + + @Specialization + Object doMultiValue( + VirtualFrame frame, + State state, + UnresolvedSymbol symbol, + EnsoMultiValue self, + Object[] arguments, + @Shared("methodResolverNode") @Cached MethodResolverNode methodResolverNode + ) { + var fnAndType = self.resolveSymbol(methodResolverNode, symbol); + if (fnAndType != null) { + var unwrapSelf = self.castTo(fnAndType.getRight()); + assert unwrapSelf != null; + assert arguments[0] == self; + arguments[0] = unwrapSelf; + return execute(frame, state, symbol, unwrapSelf, arguments); + } + throw methodNotFound(symbol, self); + } + @Specialization Object doDataflowError( VirtualFrame frame, @@ -327,7 +352,7 @@ Function resolveWarningFunction( e); } - Type typeOfSymbol = symbol.resolveDeclaringType(types.getType(selfWithoutWarnings)); + Type typeOfSymbol = symbol.resolveDeclaringType(this, types.getType(selfWithoutWarnings)); Builtins builtins = EnsoContext.get(this).getBuiltins(); if (typeOfSymbol == builtins.any()) { return symbol diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/argument/ReadArgumentCheckNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/argument/ReadArgumentCheckNode.java index 72ab3475d5fb..1464fbd4e1b1 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/argument/ReadArgumentCheckNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/argument/ReadArgumentCheckNode.java @@ -1,8 +1,11 @@ package org.enso.interpreter.node.callable.argument; import java.util.Arrays; +import java.util.List; import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.enso.compiler.core.ir.Name; import org.enso.interpreter.EnsoLanguage; import org.enso.interpreter.node.BaseNode.TailStatus; import org.enso.interpreter.node.EnsoRootNode; @@ -22,9 +25,11 @@ import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo; import org.enso.interpreter.runtime.callable.function.Function; import org.enso.interpreter.runtime.callable.function.FunctionSchema; +import org.enso.interpreter.runtime.data.EnsoMultiValue; import org.enso.interpreter.runtime.data.Type; import org.enso.interpreter.runtime.error.DataflowError; import org.enso.interpreter.runtime.error.PanicException; +import org.enso.interpreter.runtime.library.dispatch.TypesLibrary; import org.graalvm.collections.Pair; import com.oracle.truffle.api.CompilerAsserts; @@ -38,33 +43,69 @@ import com.oracle.truffle.api.nodes.ExplodeLoop; import com.oracle.truffle.api.nodes.InvalidAssumptionException; import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.nodes.NodeUtil; import com.oracle.truffle.api.nodes.RootNode; public abstract class ReadArgumentCheckNode extends Node { private final String name; - @Child IsValueOfTypeNode checkType; - @CompilerDirectives.CompilationFinal(dimensions = 1) - private final Type[] expectedTypes; @CompilerDirectives.CompilationFinal private String expectedTypeMessage; - @CompilerDirectives.CompilationFinal - private LazyCheckRootNode lazyCheck; - ReadArgumentCheckNode(String name, Type[] expectedTypes) { + ReadArgumentCheckNode(String name) { this.name = name; - this.checkType = IsValueOfTypeNode.build(); - this.expectedTypes = expectedTypes; } - public static ReadArgumentCheckNode build(String argumentName, Type[] expectedTypes) { - if (expectedTypes == null || expectedTypes.length == 0) { - return null; - } else { - return ReadArgumentCheckNodeGen.create(argumentName, expectedTypes); + /** Executes check or conversion of the value.abstract + * @param frame frame requesting the conversion + * @param value the value to convert + * @return {@code null} when the check isn't satisfied and conversion isn't possible or non-{@code null} value that can be used as a result + */ + public final Object handleCheckOrConversion(VirtualFrame frame, Object value) { + var result = executeCheckOrConversion(frame, value); + if (result == null) { + throw panicAtTheEnd(value); + } + return result; + } + + abstract Object executeCheckOrConversion(VirtualFrame frame, Object value); + abstract String expectedTypeMessage(); + + final PanicException panicAtTheEnd(Object v) { + if (expectedTypeMessage == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + expectedTypeMessage = expectedTypeMessage(); } + var ctx = EnsoContext.get(this); + var msg = name == null ? "expression" : name; + var err = ctx.getBuiltins().error().makeTypeError(expectedTypeMessage, v, msg); + throw new PanicException(err, this); + } + + public static ReadArgumentCheckNode allOf(Name argumentName, ReadArgumentCheckNode... checks) { + var list = Arrays.asList(checks); + var flatten = list.stream().flatMap(n -> n instanceof AllOfNode all ? Arrays.asList(all.checks).stream() : Stream.of(n)).toList(); + var arr = toArray(flatten); + return switch (arr.length) { + case 0 -> null; + case 1 -> arr[0]; + default -> new AllOfNode(argumentName.name(), arr); + }; + } + + public static ReadArgumentCheckNode oneOf(Name argumentName, List checks) { + var arr = toArray(checks); + return switch (arr.length) { + case 0 -> null; + case 1 -> arr[0]; + default -> new OneOfNode(argumentName.name(), arr); + }; } - public abstract Object executeCheckOrConversion(VirtualFrame frame, Object value); + public static ReadArgumentCheckNode build(Name argumentName, Type expectedType) { + var n = argumentName == null ? null : argumentName.name(); + return ReadArgumentCheckNodeFactory.TypeCheckNodeGen.create(n, expectedType); + } public static boolean isWrappedThunk(Function fn) { if (fn.getSchema() == LazyCheckRootNode.SCHEMA) { @@ -73,155 +114,247 @@ public static boolean isWrappedThunk(Function fn) { return false; } - @Specialization(rewriteOn = InvalidAssumptionException.class) - Object doCheckNoConversionNeeded(VirtualFrame frame, Object v) throws InvalidAssumptionException { - var ret = findAmongTypes(v); - if (ret != null) { - return ret; - } else { - throw new InvalidAssumptionException(); + private static ReadArgumentCheckNode[] toArray(List list) { + if (list == null) { + return new ReadArgumentCheckNode[0]; + } + var cnt = (int) list.stream().filter(n -> n != null).count(); + var arr = new ReadArgumentCheckNode[cnt]; + var it = list.iterator(); + for (int i = 0; i < cnt;) { + var element = it.next(); + if (element != null) { + arr[i++] = element; + } } + return arr; } - @Specialization(limit = "10", guards = { - "cachedType != null", - "findType(typeOfNode, v) == cachedType" - }) - Object doWithConversionCached( - VirtualFrame frame, Object v, - @Shared("typeOfNode") @Cached TypeOfNode typeOfNode, - @Cached("findType(typeOfNode, v)") Type cachedType, - @Cached("findConversionNode(cachedType)") ApplicationNode convertNode - ) { - return handleWithConversion(frame, v, convertNode); - } + static final class AllOfNode extends ReadArgumentCheckNode { + @Children + private ReadArgumentCheckNode[] checks; + @Child + private TypesLibrary types; - @Specialization(replaces = "doWithConversionCached") - Object doWithConversionUncached( - VirtualFrame frame, Object v, - @Shared("typeOfNode") @Cached TypeOfNode typeOfNode - ) { - var type = findType(typeOfNode, v); - return doWithConversionUncachedBoundary(frame == null ? null : frame.materialize(), v, type); + AllOfNode(String name, ReadArgumentCheckNode[] checks) { + super(name); + this.checks = checks; + this.types = TypesLibrary.getFactory().createDispatched(checks.length); + } + + @Override + @ExplodeLoop + Object executeCheckOrConversion(VirtualFrame frame, Object value) { + var values = new Object[checks.length]; + var valueTypes = new Type[checks.length]; + var at = 0; + for (var n : checks) { + var result = n.executeCheckOrConversion(frame, value); + if (result == null) { + return null; + } + values[at] = result; + valueTypes[at] = types.getType(result); + at++; + } + return EnsoMultiValue.create(valueTypes, values); + } + + @Override + String expectedTypeMessage() { + return Arrays.stream(checks).map(n -> n.expectedTypeMessage()).collect(Collectors.joining(" & ")); + } } - private static boolean isAllFitValue(Object v) { - return v instanceof DataflowError || AtomWithAHoleNode.isHole(v); + static final class OneOfNode extends ReadArgumentCheckNode { + @Children + private ReadArgumentCheckNode[] checks; + + OneOfNode(String name, ReadArgumentCheckNode[] checks) { + super(name); + this.checks = checks; + } + + @Override + @ExplodeLoop + Object executeCheckOrConversion(VirtualFrame frame, Object value) { + for (var n : checks) { + var result = n.executeCheckOrConversion(frame, value); + if (result != null) { + return result; + } + } + return null; + } + + @Override + String expectedTypeMessage() { + return Arrays.stream(checks).map(n -> n.expectedTypeMessage()).collect(Collectors.joining(" | ")); + } } - @ExplodeLoop - private Object findAmongTypes(Object v) { - if (isAllFitValue(v)) { - return v; - } - if (v instanceof Function fn && fn.isThunk()) { - if (lazyCheck == null) { - CompilerDirectives.transferToInterpreter(); - var enso = EnsoLanguage.get(this); - var node = (ReadArgumentCheckNode) copy(); - lazyCheck = new LazyCheckRootNode(enso, node); + static abstract class TypeCheckNode extends ReadArgumentCheckNode { + private final Type expectedType; + @Child + IsValueOfTypeNode checkType; + @CompilerDirectives.CompilationFinal + private String expectedTypeMessage; + @CompilerDirectives.CompilationFinal + private LazyCheckRootNode lazyCheck; + + TypeCheckNode(String name, Type expectedType) { + super(name); + this.checkType = IsValueOfTypeNode.build(); + this.expectedType = expectedType; + } + + @Specialization(rewriteOn = InvalidAssumptionException.class) + Object doCheckNoConversionNeeded(VirtualFrame frame, Object v) throws InvalidAssumptionException { + var ret = findAmongTypes(v); + if (ret != null) { + return ret; + } else { + throw new InvalidAssumptionException(); } - var lazyCheckFn = lazyCheck.wrapThunk(fn); - return lazyCheckFn; } - for (Type t : expectedTypes) { - if (checkType.execute(t, v)) { + + @Specialization(limit = "10", guards = { + "cachedType != null", + "findType(typeOfNode, v) == cachedType" + }) + Object doWithConversionCached( + VirtualFrame frame, Object v, + @Shared("typeOfNode") + @Cached TypeOfNode typeOfNode, + @Cached("findType(typeOfNode, v)") Type cachedType, + @Cached("findConversionNode(cachedType)") ApplicationNode convertNode + ) { + return handleWithConversion(frame, v, convertNode); + } + + @Specialization(replaces = "doWithConversionCached") + Object doWithConversionUncached( + VirtualFrame frame, Object v, + @Shared("typeOfNode") + @Cached TypeOfNode typeOfNode + ) { + var type = findType(typeOfNode, v); + return doWithConversionUncachedBoundary(frame == null ? null : frame.materialize(), v, type); + } + + private static boolean isAllFitValue(Object v) { + return v instanceof DataflowError || AtomWithAHoleNode.isHole(v); + } + + @ExplodeLoop + private Object findAmongTypes(Object v) { + if (isAllFitValue(v)) { return v; } + if (v instanceof Function fn && fn.isThunk()) { + if (lazyCheck == null) { + CompilerDirectives.transferToInterpreter(); + var enso = EnsoLanguage.get(this); + var node = (ReadArgumentCheckNode) copy(); + lazyCheck = new LazyCheckRootNode(enso, node); + } + var lazyCheckFn = lazyCheck.wrapThunk(fn); + return lazyCheckFn; + } + if (v instanceof EnsoMultiValue mv) { + var result = mv.castTo(expectedType); + if (result != null) { + return result; + } + } + if (checkType.execute(expectedType, v)) { + return v; + } + return null; } - return null; - } - @ExplodeLoop - private Pair findConversion(Type from) { - var ctx = EnsoContext.get(this); + private Pair findConversion(Type from) { + var ctx = EnsoContext.get(this); - if (getRootNode() instanceof EnsoRootNode root) { - var convert = UnresolvedConversion.build(root.getModuleScope()); - for (Type into : expectedTypes) { - var conv = convert.resolveFor(ctx, into, from); + if (getRootNode() instanceof EnsoRootNode root) { + var convert = UnresolvedConversion.build(root.getModuleScope()); + var conv = convert.resolveFor(ctx, expectedType, from); if (conv != null) { - return Pair.create(conv, into); + return Pair.create(conv, expectedType); } } + return null; } - return null; - } - ApplicationNode findConversionNode(Type from) { - var convAndType = findConversion(from); - - if (convAndType != null && getParent() instanceof ReadArgumentNode ran) { - CompilerAsserts.neverPartOfCompilation(); - var convNode = LiteralNode.build(convAndType.getLeft()); - var intoNode = LiteralNode.build(convAndType.getRight()); - var valueNode = ran.plainRead(); - var args = new CallArgument[]{ - new CallArgument(null, intoNode), - new CallArgument(null, valueNode) - }; - return ApplicationNode.build(convNode, args, DefaultsExecutionMode.EXECUTE); - } - return null; - } + ApplicationNode findConversionNode(Type from) { + var convAndType = findConversion(from); - Type findType(TypeOfNode typeOfNode, Object v) { - if (typeOfNode.execute(v) instanceof Type from) { - return from; + if (convAndType != null && NodeUtil.findParent(this, ReadArgumentNode.class) instanceof ReadArgumentNode ran) { + CompilerAsserts.neverPartOfCompilation(); + var convNode = LiteralNode.build(convAndType.getLeft()); + var intoNode = LiteralNode.build(convAndType.getRight()); + var valueNode = ran.plainRead(); + var args = new CallArgument[]{ + new CallArgument(null, intoNode), + new CallArgument(null, valueNode) + }; + return ApplicationNode.build(convNode, args, DefaultsExecutionMode.EXECUTE); + } + return null; } - return null; - } - private Object handleWithConversion( - VirtualFrame frame, Object v, ApplicationNode convertNode - ) throws PanicException { - if (convertNode == null) { - var ret = findAmongTypes(v); - if (ret != null) { - return ret; + Type findType(TypeOfNode typeOfNode, Object v) { + if (typeOfNode.execute(v) instanceof Type from) { + return from; } - throw panicAtTheEnd(v); - } else { - var converted = convertNode.executeGeneric(frame); - return converted; + return null; } - } - @CompilerDirectives.TruffleBoundary - private Object doWithConversionUncachedBoundary(MaterializedFrame frame, Object v, Type type) { - var convertNode = findConversionNode(type); - return handleWithConversion(frame, v, convertNode); - } + private Object handleWithConversion( + VirtualFrame frame, Object v, ApplicationNode convertNode + ) throws PanicException { + if (convertNode == null) { + var ret = findAmongTypes(v); + if (ret != null) { + return ret; + } + return null; + } else { + var converted = convertNode.executeGeneric(frame); + return converted; + } + } - private PanicException panicAtTheEnd(Object v) { - var ctx = EnsoContext.get(this); - var err = ctx.getBuiltins().error().makeTypeError(expectedTypeMessage(), v, name); - throw new PanicException(err, this); - } + @CompilerDirectives.TruffleBoundary + private Object doWithConversionUncachedBoundary(MaterializedFrame frame, Object v, Type type) { + var convertNode = findConversionNode(type); + return handleWithConversion(frame, v, convertNode); + } - private String expectedTypeMessage() { - if (expectedTypeMessage != null) { + @Override + String expectedTypeMessage() { + if (expectedTypeMessage != null) { + return expectedTypeMessage; + } + CompilerDirectives.transferToInterpreterAndInvalidate(); + expectedTypeMessage = expectedType.toString(); return expectedTypeMessage; } - CompilerDirectives.transferToInterpreterAndInvalidate(); - expectedTypeMessage = expectedTypes.length == 1 ? - expectedTypes[0].toString() : - Arrays.stream(expectedTypes).map(Type::toString).collect(Collectors.joining(" | ")); - return expectedTypeMessage; } private static final class LazyCheckRootNode extends RootNode { + @Child private ThunkExecutorNode evalThunk; @Child private ReadArgumentCheckNode check; static final FunctionSchema SCHEMA = new FunctionSchema( - FunctionSchema.CallerFrameAccess.NONE, - new ArgumentDefinition[] { new ArgumentDefinition(0, "delegate", null, null, ExecutionMode.EXECUTE) }, - new boolean[] { true }, - new CallArgumentInfo[0], - new Annotation[0] + FunctionSchema.CallerFrameAccess.NONE, + new ArgumentDefinition[]{new ArgumentDefinition(0, "delegate", null, null, ExecutionMode.EXECUTE)}, + new boolean[]{true}, + new CallArgumentInfo[0], + new Annotation[0] ); LazyCheckRootNode(TruffleLanguage language, ReadArgumentCheckNode check) { @@ -231,7 +364,7 @@ private static final class LazyCheckRootNode extends RootNode { } Function wrapThunk(Function thunk) { - return new Function(getCallTarget(), thunk.getScope(), SCHEMA, new Object[] { thunk }, null); + return new Function(getCallTarget(), thunk.getScope(), SCHEMA, new Object[]{thunk}, null); } @Override @@ -241,7 +374,7 @@ public Object execute(VirtualFrame frame) { assert args.length == 1; assert args[0] instanceof Function fn && fn.isThunk(); var raw = evalThunk.executeThunk(frame, args[0], state, TailStatus.NOT_TAIL); - var result = check.executeCheckOrConversion(frame, raw); + var result = check.handleCheckOrConversion(frame, raw); return result; } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/argument/ReadArgumentNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/argument/ReadArgumentNode.java index 7690d010bc7b..c2b7c122e697 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/argument/ReadArgumentNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/argument/ReadArgumentNode.java @@ -3,10 +3,8 @@ import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.NodeInfo; import com.oracle.truffle.api.profiles.CountingConditionProfile; -import java.util.List; import org.enso.interpreter.node.ExpressionNode; import org.enso.interpreter.runtime.callable.function.Function; -import org.enso.interpreter.runtime.data.Type; /** * Reads and evaluates the expression provided as a function argument. It handles the case where @@ -19,30 +17,23 @@ public final class ReadArgumentNode extends ExpressionNode { @Child ReadArgumentCheckNode checkType; private final CountingConditionProfile defaultingProfile = CountingConditionProfile.create(); - private ReadArgumentNode( - String name, int position, ExpressionNode defaultValue, Type[] expectedTypes) { + private ReadArgumentNode(int position, ExpressionNode defaultValue, ReadArgumentCheckNode check) { this.index = position; this.defaultValue = defaultValue; - var argName = name != null ? name : "Argument #" + (index + 1); - this.checkType = ReadArgumentCheckNode.build(argName, expectedTypes); + this.checkType = check; } /** * Creates an instance of this node. * - * @param name argument name * @param position the argument's position at the definition site * @param defaultValue the default value provided for that argument - * @param expectedTypes {@code null} or expected types to check input for + * @param check {@code null} or node to check type of input * @return a node representing the argument at position {@code idx} */ public static ReadArgumentNode build( - String name, int position, ExpressionNode defaultValue, List expectedTypes) { - var arr = - expectedTypes == null || expectedTypes.isEmpty() - ? null - : expectedTypes.toArray(Type[]::new); - return new ReadArgumentNode(name, position, defaultValue, arr); + int position, ExpressionNode defaultValue, ReadArgumentCheckNode check) { + return new ReadArgumentNode(position, defaultValue, check); } ReadArgumentNode plainRead() { @@ -78,7 +69,7 @@ public Object executeGeneric(VirtualFrame frame) { } } if (checkType != null) { - v = checkType.executeCheckOrConversion(frame, v); + v = checkType.handleCheckOrConversion(frame, v); } return v; } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/resolver/MethodResolverNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/resolver/MethodResolverNode.java index 188e4ccffd0f..3e8b4dde42bb 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/resolver/MethodResolverNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/resolver/MethodResolverNode.java @@ -51,6 +51,6 @@ Function resolveCached( @Specialization(replaces = "resolveCached") Function resolveUncached(Type self, UnresolvedSymbol symbol) { - return symbol.resolveFor(self); + return symbol.resolveFor(this, self); } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/GetAnnotationNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/GetAnnotationNode.java index 01ba9cbcdc5f..da3ec5454fce 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/GetAnnotationNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/GetAnnotationNode.java @@ -46,7 +46,7 @@ Object doExecute( if (targetTypeResult instanceof Type targetType) { Function methodFunction; if (method instanceof UnresolvedSymbol symbol) { - methodFunction = symbol.resolveFor(targetType); + methodFunction = symbol.resolveFor(this, targetType); } else { CompilerDirectives.transferToInterpreter(); var ctx = EnsoContext.get(this); 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 49b228417518..09863d258d13 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 @@ -56,15 +56,12 @@ private static boolean typeAndCheck( return true; } else if (TypesGen.isType(tpeOfPayload)) { Type tpe = TypesGen.asType(tpeOfPayload); - Type superTpe = tpe.getSupertype(); - - while (tpe != superTpe && superTpe != null) { + var ctx = EnsoContext.get(typeOfNode); + for (var superTpe : tpe.allTypes(ctx)) { boolean testSuperTpe = isSameObject.execute(expectedType, superTpe); if (testSuperTpe) { return true; } - tpe = superTpe; - superTpe = superTpe.getSupertype(); } } return false; @@ -108,15 +105,13 @@ boolean doUnresolvedSymbol(Type expectedType, UnresolvedSymbol value) { @ExplodeLoop private boolean checkParentTypes(Type actual, Type expected) { - for (; ; ) { - if (actual == null) { - return false; - } - if (actual == expected) { + var ctx = EnsoContext.get(this); + for (var tpe : actual.allTypes(ctx)) { + if (tpe == expected) { return true; } - actual = actual.getSupertype(); } + return false; } @Specialization(guards = {"!isArrayType(expectedType)", "!isAnyType(expectedType)"}) diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/UnresolvedConversion.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/UnresolvedConversion.java index 0c3fd98b0053..439fdc9ab908 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/UnresolvedConversion.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/UnresolvedConversion.java @@ -3,7 +3,8 @@ import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.ImportStatic; import com.oracle.truffle.api.dsl.Specialization; -import com.oracle.truffle.api.interop.*; +import com.oracle.truffle.api.interop.ArityException; +import com.oracle.truffle.api.interop.InteropLibrary; import com.oracle.truffle.api.library.CachedLibrary; import com.oracle.truffle.api.library.ExportLibrary; import com.oracle.truffle.api.library.ExportMessage; @@ -44,13 +45,12 @@ public ModuleScope getScope() { * @return the resolved function definition, or null if not found */ public Function resolveFor(EnsoContext ctx, Type into, Type from) { - Type current = from; - while (current != null) { - Function candidate = scope.lookupConversionDefinition(current, into); - if (candidate != null) { - return candidate; - } else { - current = current.getSupertype(); + if (from != null) { + for (var current : from.allTypes(ctx)) { + Function candidate = scope.lookupConversionDefinition(current, into); + if (candidate != null) { + return candidate; + } } } return scope.lookupConversionDefinition(ctx.getBuiltins().any(), into); diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/UnresolvedSymbol.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/UnresolvedSymbol.java index 68d5d85c9b44..1788b413b236 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/UnresolvedSymbol.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/UnresolvedSymbol.java @@ -9,6 +9,7 @@ import com.oracle.truffle.api.library.CachedLibrary; import com.oracle.truffle.api.library.ExportLibrary; import com.oracle.truffle.api.library.ExportMessage; +import com.oracle.truffle.api.nodes.Node; import org.enso.interpreter.Constants; import org.enso.interpreter.node.callable.InteropMethodCallNode; import org.enso.interpreter.runtime.EnsoContext; @@ -58,14 +59,12 @@ public ModuleScope getScope() { * @param type the type for which this symbol should be resolved * @return the resolved function definition, or null if not found */ - public Function resolveFor(Type type) { - Type current = type; - while (current != null) { + public Function resolveFor(Node node, Type type) { + for (var current : type.allTypes(EnsoContext.get(node))) { Function candidate = scope.lookupMethodDefinition(current, name); if (candidate != null) { return candidate; } - current = current.getSupertype(); } return null; } @@ -76,14 +75,15 @@ public Function resolveFor(Type type) { * @param type the type for which this symbol should be resolved * @return the resolved function definition, or null if not found */ - public Type resolveDeclaringType(Type type) { - Type current = type; - while (current != null) { - Function candidate = scope.lookupMethodDefinition(current, name); - if (candidate != null) { - return current; + public Type resolveDeclaringType(Node node, Type type) { + var ctx = EnsoContext.get(node); + if (type != null) { + for (var current : type.allTypes(ctx)) { + Function candidate = scope.lookupMethodDefinition(current, name); + if (candidate != null) { + return current; + } } - current = current.getSupertype(); } return null; } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/argument/ArgumentDefinition.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/argument/ArgumentDefinition.java index cab2d2d0c887..b35121f767eb 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/argument/ArgumentDefinition.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/argument/ArgumentDefinition.java @@ -2,7 +2,7 @@ import java.util.Optional; import org.enso.interpreter.node.ExpressionNode; -import org.enso.interpreter.runtime.data.Type; +import org.enso.interpreter.node.callable.argument.ReadArgumentCheckNode; /** Tracks the specifics about how arguments are defined at the callable definition site. */ public final class ArgumentDefinition { @@ -26,7 +26,7 @@ public boolean isSuspended() { private final int position; private final String name; - private final Type[] checkType; + private final ReadArgumentCheckNode checkType; private final ExpressionNode defaultValue; private final boolean isSuspended; @@ -42,7 +42,7 @@ public boolean isSuspended() { public ArgumentDefinition( int position, String name, - Type[] checkType, + ReadArgumentCheckNode checkType, ExpressionNode defaultValue, ExecutionMode executionMode) { this.position = position; @@ -103,7 +103,7 @@ public boolean isSuspended() { * * @return {@code null} or list of types to check argument against */ - public Type[] getCheckType() { + public ReadArgumentCheckNode getCheckType() { return checkType; } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/AtomConstructor.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/AtomConstructor.java index 0f2fbcf35895..38a8a2cbc2ee 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/AtomConstructor.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/AtomConstructor.java @@ -96,7 +96,7 @@ public boolean isBuiltin() { public AtomConstructor initializeFields(EnsoLanguage language, ArgumentDefinition... args) { ExpressionNode[] reads = new ExpressionNode[args.length]; for (int i = 0; i < args.length; i++) { - reads[i] = ReadArgumentNode.build(args[i].getName(), i, null, null); + reads[i] = ReadArgumentNode.build(i, null, null); } return initializeFields( language, null, LocalScope.root(), new ExpressionNode[0], reads, new Annotation[0], args); diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/unboxing/Layout.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/unboxing/Layout.java index 73e0ceed27b9..6f01f1060803 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/unboxing/Layout.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/unboxing/Layout.java @@ -12,7 +12,6 @@ import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition; import org.enso.interpreter.runtime.callable.atom.Atom; import org.enso.interpreter.runtime.callable.atom.AtomConstructor; -import org.enso.interpreter.runtime.data.Type; /** * This class mediates the use of {@link UnboxingAtom} instances. It is responsible for describing @@ -301,20 +300,22 @@ long computeFlags(Object[] arguments) { private static final class SetterTypeCheckFactory implements NodeFactory { private final String argName; - private final Type[] type; + private final ReadArgumentCheckNode typeCheck; private final NodeFactory delegate; private SetterTypeCheckFactory( - ArgumentDefinition arg, Type[] type, NodeFactory factory) { + ArgumentDefinition arg, + ReadArgumentCheckNode typeCheck, + NodeFactory factory) { assert factory != null; this.argName = arg.getName(); - this.type = type; + this.typeCheck = typeCheck; this.delegate = factory; } @Override public UnboxingAtom.FieldSetterNode createNode(Object... arguments) { - var checkNode = ReadArgumentCheckNode.build(argName, type); + var checkNode = (ReadArgumentCheckNode) typeCheck.copy(); var setterNode = delegate.createNode(arguments); return checkNode == null ? setterNode : new CheckFieldSetterNode(setterNode, checkNode); } @@ -352,7 +353,7 @@ private CheckFieldSetterNode( @Override public void execute(Atom atom, Object value) { - var valueOrConvertedValue = checkNode.executeCheckOrConversion(null, value); + var valueOrConvertedValue = checkNode.handleCheckOrConversion(null, value); setterNode.execute(atom, valueOrConvertedValue); } } 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 new file mode 100644 index 000000000000..94ddf41bdfed --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoMultiValue.java @@ -0,0 +1,93 @@ +package org.enso.interpreter.runtime.data; + +import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.library.ExportLibrary; +import com.oracle.truffle.api.library.ExportMessage; +import java.util.Arrays; +import java.util.stream.Collectors; +import org.enso.interpreter.node.callable.resolver.MethodResolverNode; +import org.enso.interpreter.runtime.callable.UnresolvedSymbol; +import org.enso.interpreter.runtime.callable.function.Function; +import org.enso.interpreter.runtime.library.dispatch.TypesLibrary; +import org.graalvm.collections.Pair; + +@ExportLibrary(TypesLibrary.class) +@ExportLibrary(InteropLibrary.class) +public final class EnsoMultiValue implements EnsoObject { + @CompilationFinal(dimensions = 1) + private final Type[] types; + + @CompilationFinal(dimensions = 1) + private final Object[] values; + + private EnsoMultiValue(Type[] types, Object[] values) { + this.types = types; + assert types.length == values.length; + this.values = values; + } + + public static EnsoObject create(Type[] types, Object[] values) { + return new EnsoMultiValue(types, values); + } + + @ExportMessage + boolean hasType() { + return true; + } + + @ExportMessage + boolean hasSpecialDispatch() { + return true; + } + + @ExportMessage + public final Type getType() { + return types[0]; + } + + @ExportMessage + String toDisplayString(boolean ignore) { + return toString(); + } + + @TruffleBoundary + @Override + public String toString() { + return Arrays.stream(types).map(t -> t.getName()).collect(Collectors.joining(" & ")); + } + + /** + * Casts value in this multi value into specific t. + * + * @param type the requested t + * @return instance of the {@code t} or {@code null} if no suitable value was found + */ + public final Object castTo(Type type) { + for (var i = 0; i < types.length; i++) { + if (types[i] == type) { + return values[i]; + } + } + return null; + } + + /** + * Tries to resolve the symbol in one of multi value types. + * + * @param node resolution node to use + * @param symbol symbol to resolve + * @return {@code null} when no resolution was found or pair of function and type solved + */ + public final Pair resolveSymbol( + MethodResolverNode node, UnresolvedSymbol symbol) { + for (Type t : types) { + var fn = node.execute(t, symbol); + if (fn != null) { + return Pair.create(fn, t); + } + } + return null; + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Type.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Type.java index 58f889cff39b..57050f98ca01 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Type.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Type.java @@ -123,7 +123,7 @@ public boolean isBuiltin() { return builtin; } - public Type getSupertype() { + private Type getSupertype() { if (supertype == null) { if (builtin) { return null; @@ -134,6 +134,23 @@ public Type getSupertype() { return supertype; } + public final Type[] allTypes(EnsoContext ctx) { + if (supertype == null) { + if (builtin) { + return new Type[] {this}; + } + return new Type[] {this, ctx.getBuiltins().any()}; + } + if (supertype == ctx.getBuiltins().any()) { + return new Type[] {this, ctx.getBuiltins().any()}; + } + var superTypes = supertype.allTypes(ctx); + var allTypes = new Type[superTypes.length + 1]; + System.arraycopy(superTypes, 0, allTypes, 1, superTypes.length); + allTypes[0] = this; + return allTypes; + } + public void generateGetters(EnsoLanguage language) { if (gettersGenerated) return; gettersGenerated = true; diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/PanicException.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/PanicException.java index 13712ed27538..d811f86de09f 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/PanicException.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/PanicException.java @@ -82,7 +82,7 @@ private String computeMessage() { InteropLibrary library = InteropLibrary.getUncached(); try { msg = library.asString(library.getExceptionMessage(this)); - } catch (UnsupportedMessageException e) { + } catch (AssertionError | UnsupportedMessageException e) { msg = TypeToDisplayTextNodeGen.getUncached().execute(payload); } cacheMessage = msg; diff --git a/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala b/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala index 4821547a4d31..1cfb1be38a9f 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala @@ -60,6 +60,7 @@ import org.enso.compiler.pass.resolve.{ } import org.enso.polyglot.ForeignLanguage import org.enso.interpreter.node.callable.argument.ReadArgumentNode +import org.enso.interpreter.node.callable.argument.ReadArgumentCheckNode import org.enso.interpreter.node.callable.function.{ BlockNode, CreateFunctionNode @@ -271,8 +272,8 @@ class IrToTruffle( for (idx <- atomDefn.arguments.indices) { val unprocessedArg = atomDefn.arguments(idx) - val runtimeTypes = checkRuntimeTypes(unprocessedArg) - val arg = argFactory.run(unprocessedArg, idx, runtimeTypes) + val checkNode = checkRuntimeTypes(unprocessedArg) + val arg = argFactory.run(unprocessedArg, idx, checkNode) val occInfo = unprocessedArg .unsafeGetMetadata( AliasAnalysis, @@ -283,10 +284,9 @@ class IrToTruffle( argDefs(idx) = arg val readArg = ReadArgumentNode.build( - arg.getName(), idx, arg.getDefaultValue.orElse(null), - runtimeTypes.asJava + checkNode ) val assignmentArg = AssignmentNode.build(readArg, slotIdx) val argRead = @@ -680,25 +680,43 @@ class IrToTruffle( // === Utility Functions ==================================================== // ========================================================================== - private def checkRuntimeTypes(arg: DefinitionArgument): List[Type] = { - def extractAscribedType(t: Expression): List[Type] = t match { - case u: `type`.Set.Union => u.operands.flatMap(extractAscribedType) + private def checkRuntimeTypes( + arg: DefinitionArgument + ): ReadArgumentCheckNode = { + def extractAscribedType(t: Expression): ReadArgumentCheckNode = t match { + case u: `type`.Set.Union => + ReadArgumentCheckNode.oneOf( + arg.name, + u.operands.map(extractAscribedType).asJava + ) + case i: `type`.Set.Intersection => + ReadArgumentCheckNode.allOf( + arg.name, + extractAscribedType(i.left), + extractAscribedType(i.right) + ) case p: Application.Prefix => extractAscribedType(p.function) case _: Tpe.Function => - List(context.getTopScope().getBuiltins().function()) + ReadArgumentCheckNode.build( + arg.name, + context.getTopScope().getBuiltins().function() + ) case t => { t.getMetadata(TypeNames) match { case Some( BindingsMap .Resolution(BindingsMap.ResolvedType(mod, tpe)) ) => - List(mod.unsafeAsModule().getScope.getTypes.get(tpe.name)) - case _ => List() + ReadArgumentCheckNode.build( + arg.name, + mod.unsafeAsModule().getScope.getTypes.get(tpe.name) + ) + case _ => null } } } - arg.ascribedType.map(extractAscribedType).getOrElse(List()) + arg.ascribedType.map(extractAscribedType).getOrElse(null) } /** Checks if the expression has a @Builtin_Method annotation @@ -1789,8 +1807,8 @@ class IrToTruffle( // Note [Rewriting Arguments] val argSlots = arguments.zipWithIndex.map { case (unprocessedArg, idx) => - val runtimeTypes = checkRuntimeTypes(unprocessedArg) - val arg = argFactory.run(unprocessedArg, idx, runtimeTypes) + val checkNode = checkRuntimeTypes(unprocessedArg) + val arg = argFactory.run(unprocessedArg, idx, checkNode) argDefinitions(idx) = arg val occInfo = unprocessedArg .unsafeGetMetadata( @@ -1802,10 +1820,9 @@ class IrToTruffle( val slotIdx = scope.getVarSlotIdx(occInfo.id) val readArg = ReadArgumentNode.build( - arg.getName(), idx, arg.getDefaultValue.orElse(null), - runtimeTypes.asJava + checkNode ) val assignArg = AssignmentNode.build(readArg, slotIdx) @@ -2123,14 +2140,14 @@ class IrToTruffle( * * @param inputArg the argument to generate code for * @param position the position of `arg` at the function definition site - * @param types null or types to check the argument for + * @param checkNode null or node to check the argument type for * @return a truffle entity corresponding to the definition of `arg` for a * given function */ def run( inputArg: DefinitionArgument, position: Int, - types: List[Type] + types: ReadArgumentCheckNode ): ArgumentDefinition = inputArg match { case arg: DefinitionArgument.Specified => @@ -2167,7 +2184,7 @@ class IrToTruffle( new ArgumentDefinition( position, arg.name.name, - if (types == null || types.length == 0) null else types.toArray, + types, defaultedValue, executionMode ) diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/DataflowAnalysis.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/DataflowAnalysis.scala index 689ccec62bb1..e9f1b8198f49 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/DataflowAnalysis.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/DataflowAnalysis.scala @@ -525,20 +525,6 @@ case object DataflowAnalysis extends IRPass { right = analyseExpression(right, info) ) .updateMetadata(this -->> info) - case subtraction @ `type`.Set.Subtraction(left, right, _, _, _) => - val subDep = asStatic(subtraction) - val leftDep = asStatic(left) - val rightDep = asStatic(right) - info.dependents.updateAt(leftDep, Set(subDep)) - info.dependents.updateAt(rightDep, Set(subDep)) - info.dependencies.updateAt(subDep, Set(leftDep, rightDep)) - - subtraction - .copy( - left = analyseExpression(left, info), - right = analyseExpression(right, info) - ) - .updateMetadata(this -->> info) } } diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/TypeFunctions.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/TypeFunctions.scala index 2acd01d109b8..20eabdf7147d 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/TypeFunctions.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/TypeFunctions.scala @@ -96,8 +96,7 @@ case object TypeFunctions extends IRPass { `type`.Set.Subsumption.name, `type`.Set.Equality.name, `type`.Set.Union.name, - `type`.Set.Intersection.name, - `type`.Set.Subtraction.name + `type`.Set.Intersection.name ) /** Performs resolution of typing functions in an arbitrary expression. @@ -197,8 +196,6 @@ case object TypeFunctions extends IRPass { `type`.Set.Equality(leftArg, rightArg, location) case `type`.Set.Intersection.name => `type`.Set.Intersection(leftArg, rightArg, location) - case `type`.Set.Subtraction.name => - `type`.Set.Subtraction(leftArg, rightArg, location) } } else { Error.InvalidIR(originalIR) diff --git a/engine/runtime/src/test/java/org/enso/interpreter/test/SignatureTest.java b/engine/runtime/src/test/java/org/enso/interpreter/test/SignatureTest.java index c447fa29fa96..23740c4337c2 100644 --- a/engine/runtime/src/test/java/org/enso/interpreter/test/SignatureTest.java +++ b/engine/runtime/src/test/java/org/enso/interpreter/test/SignatureTest.java @@ -10,6 +10,7 @@ import org.graalvm.polyglot.Value; import org.junit.AfterClass; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -35,7 +36,7 @@ public void wrongFunctionSignature() throws Exception { final Source src = Source.newBuilder("enso", """ neg : Xyz -> Abc neg a = 0 - a - """, uri.getHost()) + """,uri.getAuthority()) .uri(uri) .buildLiteral(); @@ -53,7 +54,7 @@ public void wrongAscribedTypeSignature() throws Exception { final URI uri = new URI("memory://neg.enso"); final Source src = Source.newBuilder("enso", """ neg (a : Xyz) = 0 - a - """, uri.getHost()) + """,uri.getAuthority()) .uri(uri) .buildLiteral(); @@ -74,7 +75,7 @@ public void runtimeCheckOfAscribedFunctionSignature() throws Exception { err msg = Error.throw msg neg (a : Integer) = 0 - a - """, uri.getHost()) + """,uri.getAuthority()) .uri(uri) .buildLiteral(); @@ -116,7 +117,7 @@ public void lazyIntegerInConstructor() throws Exception { simple v = Int.Simple v complex x y = Int.Complex (x+y) - """, uri.getHost()) + """,uri.getAuthority()) .uri(uri) .buildLiteral(); @@ -159,7 +160,7 @@ public void runtimeCheckOfLazyAscribedFunctionSignature() throws Exception { make arr = build <| arr.at 0 - """, uri.getHost()) + """,uri.getAuthority()) .uri(uri) .buildLiteral(); @@ -209,7 +210,7 @@ neg self (~a : Integer) = self.zero - a make arr = Lazy.Value <| Polyglot.invoke arr "add" [ arr.length ] arr.at 0 - """, uri.getHost()) + """,uri.getAuthority()) .uri(uri) .buildLiteral(); @@ -250,7 +251,7 @@ public void runtimeCheckOfAscribedInstanceMethodSignature() throws Exception { Singleton twice self (a : Integer) = a + a - """, uri.getHost()) + """,uri.getAuthority()) .uri(uri) .buildLiteral(); @@ -276,7 +277,7 @@ public void runtimeCheckOfAscribedStaticMethodSignature() throws Exception { from Standard.Base import Integer type Neg twice (a : Integer) = a + a - """, uri.getHost()) + """,uri.getAuthority()) .uri(uri) .buildLiteral(); @@ -303,7 +304,7 @@ public void runtimeCheckOfAscribedLocalMethodSignature() throws Exception { call_twice x = twice (a : Integer) = a + a twice x - """, uri.getHost()) + """,uri.getAuthority()) .uri(uri) .buildLiteral(); @@ -329,7 +330,7 @@ public void wrongAscribedInConstructor() throws Exception { Val (a : Xyz) neg = Neg.Val 10 - """, uri.getHost()) + """,uri.getAuthority()) .uri(uri) .buildLiteral(); @@ -349,7 +350,7 @@ public void ascribedWithAParameter() throws Exception { type Maybe a Nothing Some unwrap:a - """, uri.getHost()) + """,uri.getAuthority()) .uri(uri) .buildLiteral(); @@ -367,7 +368,7 @@ public void suspendedAscribedParameter() throws Exception { type Maybe a Nothing Some (~unwrap : Integer) - """, uri.getHost()) + """,uri.getAuthority()) .uri(uri) .buildLiteral(); @@ -416,7 +417,7 @@ mul self (other : V) = V.Val self.a*other.a # invokes V.mul with Integer parameter, not V! mix a:V b:Integer = a.mul b - """, uri.getHost()) + """,uri.getAuthority()) .uri(uri) .buildLiteral(); @@ -444,7 +445,7 @@ private Value exampleWithBinary() throws URISyntaxException { One (v:One) Either v:(Zero | One) Vec v:(Integer | Range | Vector (Integer | Range)) - """, uri.getHost()).uri(uri).buildLiteral(); + """,uri.getAuthority()).uri(uri).buildLiteral(); return ctx.eval(src); } @@ -513,7 +514,7 @@ public void partiallyAppliedConstructor() throws Exception { mix a = partial = V.Val 1 a create partial - """, uri.getHost()) + """,uri.getAuthority()) .uri(uri) .buildLiteral(); @@ -547,7 +548,7 @@ public void oversaturatedFunction() throws Exception { neg x:Integer = -x mix n = neg (fn 2 a=4 n) - """, uri.getHost()) + """,uri.getAuthority()) .uri(uri) .buildLiteral(); @@ -581,7 +582,7 @@ public void suspendedArgumentsUnappliedFunction() throws Exception { neg x:Integer = -x mix a = neg (fn c=(2/0) b=(a/0)) - """, uri.getHost()) + """,uri.getAuthority()) .uri(uri) .buildLiteral(); @@ -602,6 +603,49 @@ public void suspendedArgumentsUnappliedFunction() throws Exception { } } + @Test + public void andConversions() throws Exception { + final URI uri = new URI("memory://and_conv.enso"); + final Source src = Source.newBuilder("enso", """ + from Standard.Base import all + + type Plus + Impl value dict + + + self (that:Plus) = if self.dict != that.dict then Panic.throw "panic!" else + self.dict.plus self.value that.value + type Mul + Impl value dict + + * self (that:Mul) = if self.dict != that.dict then Panic.throw "panic!" else + self.dict.mul self.value that.value + + compute (a : Plus & Mul) (b : Plus & Mul) = + add (x:Plus) (y:Plus) = x+y + p = add a b + m = a*b + add p m + + type BooleanPlus + plus a:Boolean b:Boolean = a || b + Plus.from(that:Boolean) = Plus.Impl that BooleanPlus + + type BooleanMul + mul a:Boolean b:Boolean = a && b + Mul.from(that:Boolean) = Mul.Impl that BooleanMul + + """,uri.getAuthority()) + .uri(uri) + .buildLiteral(); + + var module = ctx.eval(src); + var compute = module.invokeMember("eval_expression", "compute"); + + assertTrue("true & true", compute.execute(true, true).asBoolean()); + assertTrue("true & false", compute.execute(true, false).asBoolean()); + assertFalse("false & false", compute.execute(false, false).asBoolean()); + } + private static void assertTypeError(String expArg, String expType, String realType, String msg) { if (!msg.contains(expArg)) { fail("Expecting value " + expArg + " in " + msg); diff --git a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/SugaredTypeFunctionsTest.scala b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/SugaredTypeFunctionsTest.scala index bbb233b035ee..3cdc0d3cc85f 100644 --- a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/SugaredTypeFunctionsTest.scala +++ b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/SugaredTypeFunctionsTest.scala @@ -196,15 +196,5 @@ class SugaredTypeFunctionsTest extends CompilerTest { ir shouldBe an[`type`.Set.Intersection] } - - "resolve subtraction" ignore { - // FIXME: Not supported by new parser--needs triage (#6165). - val ir = - """ - |T \ P - |""".stripMargin.preprocessExpression.get.resolve - - ir shouldBe an[`type`.Set.Subtraction] - } } } diff --git a/test/Tests/src/Semantic/Conversion_Spec.enso b/test/Tests/src/Semantic/Conversion_Spec.enso index ef7991668eaf..6a7aee345687 100644 --- a/test/Tests/src/Semantic/Conversion_Spec.enso +++ b/test/Tests/src/Semantic/Conversion_Spec.enso @@ -1,5 +1,6 @@ from Standard.Base import all import Standard.Base.Errors.Common.No_Such_Conversion +import Standard.Base.Errors.Common.Type_Error import project.Semantic.Conversion.Methods import project.Semantic.Conversion.Types @@ -39,6 +40,13 @@ Not_Foo.from (_:Any) = Not_Foo.Value "ANY!!!" Foo.from (_:Quaffle) = Foo.Value "quaffle" Foo.from (_:Error) = Foo.Value "oops" +type MultiNumber + Value v + +Integer.from (that:MultiNumber) = that.v * 19 +Number.from (that:MultiNumber) = that.v * 0.3 +Decimal.from (that:MultiNumber) = that.v * 0.7 + foreign js make_str x = """ return "js string" @@ -144,6 +152,57 @@ spec = Hello.formulate_with_to [ Hello.Say "Proper", Hello.Say "Type" ] . should_equal "ProperType" Hello.formulate_with_to [ Foo.Value "Perform", Bar.Value "Conversion" ] . should_equal "PERFORM conversion!" + Test.specify "Requesting Text & Foo" <| + check a (n : Text & Foo) = case a of + 0 -> n.foo + 1 -> n.take (First 3) + + check 0 "Ahoj" . should_equal 4 + check 1 "Ahoj" . should_equal "Aho" + + # Boolean can be converted to Foo, but is not Text + fail = Panic.recover Type_Error <| check 0 True + fail . should_fail_with Type_Error + + Test.specify "Requesting Foo & Not_Foo & Boolean" <| + check a (n : Foo & Not_Foo & Boolean) = case a of + 0 -> n.foo + 1 -> n.not + 2 -> n.notfoo + + check 0 True . should_be_true + check 1 True . should_be_false + check 2 True . should_be_true + + fail = Panic.recover Type_Error <| check 0 "not a boolean" + fail . should_fail_with Type_Error + + Test.specify "Requesting Number & Integer & Decimal" <| + m = MultiNumber.Value 5 + + m.to Number . should_equal 1.5 + m.to Integer . should_equal 95 + m.to Decimal . should_equal 3.5 + + to_1 (v : Number & Integer & Decimal) = v + to_1 m . should_equal 1.5 + + to_2 (v : Integer & Decimal & Number) = v + to_2 m . should_equal 95 + + to_3 (v : Decimal & Number & Integer) = v + to_3 m . should_equal 3.5 + + to_4 (v : Integer & Number & Decimal) = v + to_4 m . should_equal 95 + + to_5 (v : Decimal & Integer & Number) = v + to_5 m . should_equal 3.5 + + to_6 (v : Number & Decimal & Integer) = v + to_6 m . should_equal 1.5 + + Hello.from (that:Foo) suffix=" " = Hello.Say <| (that.foo.to_case Case.Upper) + suffix Hello.from (that:Bar) suffix="!" = Hello.Say <| (that.bar.to_case Case.Lower) + suffix