mirror of
https://github.com/enso-org/enso.git
synced 2024-12-18 16:51:41 +03:00
Intersection types & type checks (#11600)
Implementation of **type checks** for **intersection types**. The idea is to split the list of `types[]` in `EnsoMultiValue` into two parts: - first `methodDispatchTypes` represent the types the value _"has been cast to"_ - the rest of the types represent the types the value _"can be cast to"_ By performing this separation we address the #10882 requirements. After a type check only methods available on the `methodDispatchTypes` can be invoked. However the value can still be cast to all the possible types.
This commit is contained in:
parent
ffd0de4661
commit
2964457d48
@ -1,3 +1,11 @@
|
||||
# Next Next Release
|
||||
|
||||
#### Enso Language & Runtime
|
||||
|
||||
- [Intersection types & type checks][11600]
|
||||
|
||||
[11600]: https://github.com/enso-org/enso/pull/11600
|
||||
|
||||
# Next Release
|
||||
|
||||
#### Enso IDE
|
||||
|
@ -43,6 +43,7 @@ The various components of Enso's syntax are described below:
|
||||
- [**Functions:**](./functions.md) The syntax for writing functions in Enso.
|
||||
- [**Function Arguments:**](./function-arguments.md) The syntax for function
|
||||
arguments in Enso.
|
||||
- [**Conversions:**](./conversions.md) The syntax of special _conversion functions_ in Enso.
|
||||
- [**Field Access:**](./projections.md) The syntax for working with fields of
|
||||
data types in Enso.
|
||||
- [**Comments:**](./comments.md) The syntax for writing comments in Enso.
|
||||
|
35
docs/syntax/conversions.md
Normal file
35
docs/syntax/conversions.md
Normal file
@ -0,0 +1,35 @@
|
||||
---
|
||||
layout: developer-doc
|
||||
title: Defining Functions
|
||||
category: syntax
|
||||
tags: [syntax, functions]
|
||||
order: 10
|
||||
---
|
||||
|
||||
# Conversions
|
||||
|
||||
Conversions are special [functions](./functions.md) associated with a [type](../types/hierarchy.md),
|
||||
named `from` and taking single `that` argument. Following example:
|
||||
```ruby
|
||||
type Complex
|
||||
Num re:Float im:Float
|
||||
|
||||
Complex.from (that:Float) = Complex.Num that 0
|
||||
```
|
||||
defines type `Complex` and a **conversion** from `Float` which uses the provided `Float` value as
|
||||
real part of a complex number while setting the imaginary part to zero.
|
||||
|
||||
## Type Checks
|
||||
|
||||
Conversions are integral part of [type checking](../types/inference-and-checking.md#type-checking-algorithm)
|
||||
during runtime. Having the right conversion in scope one can write:
|
||||
```ruby
|
||||
complex_pi = 3.14:Complex
|
||||
```
|
||||
to create a new instance of type `Complex`. The Enso runtime represents
|
||||
the `3.14` literal as `Float` value and that would fail the `Complex` type check if there was no
|
||||
conversion method available. However as there is one, the runtime uses `Complex.from` behind the
|
||||
scene and `complex_pi` then becomes `Complex.Num 3.14 0` value.
|
||||
|
||||
Type checks may perform no, one or multiple conversions (like in case of
|
||||
[intersection types](../types/intersection-types.md)).
|
@ -48,6 +48,7 @@ Information on the type system is broken up into the following sections:
|
||||
- [**Goals for the Type System:**](./goals.md) The goals for the Enso type
|
||||
system, particularly around usability and user experience.
|
||||
- [**The Type Hierarchy:**](./hierarchy.md) The type hierarchy in Enso.
|
||||
- [**Intersection Types:**](./intersection-types.md) intersection types in Enso.
|
||||
- [**Function Types:**](./function-types.md) Function types in Enso.
|
||||
- [**Access Modification:**](./access-modifiers.md) Access modifiers in Enso
|
||||
(e.g. `private` and `unsafe`),
|
||||
|
@ -142,7 +142,7 @@ They are as follows:
|
||||
- **Union - `|`:** This operator creates a typeset that contains the members in
|
||||
the union of its operands.
|
||||
- **Intersection - `&`:** This operator creates a typeset that contains the
|
||||
members in the intersection of its operands.
|
||||
members in the [intersection of its operands](./intersection-types.md).
|
||||
|
||||
> [!WARNING]
|
||||
> These operators _don't seem to be supported_. There is no plan to
|
||||
|
157
docs/types/intersection-types.md
Normal file
157
docs/types/intersection-types.md
Normal file
@ -0,0 +1,157 @@
|
||||
---
|
||||
layout: developer-doc
|
||||
title: The Enso Type Hierarchy
|
||||
category: types
|
||||
tags: [types, hierarchy, typeset]
|
||||
order: 2
|
||||
---
|
||||
|
||||
# Intersection Types
|
||||
|
||||
Intersection types play an important role in Enso [type hierarchy](./hierarchy.md)
|
||||
and its visual representation. Having a value that can play _multiple roles_
|
||||
at once is essential for smooth _live programming_ manipulation of such a value.
|
||||
|
||||
Intersections types are created with the use of [`&` operator](./hierarchy.md#typeset-operators).
|
||||
In an attempt to represent `Complex` numbers (with real and imaginary component)
|
||||
one may decide to create a type that is both `Complex` and `Float` when the
|
||||
imaginary part is `0`:
|
||||
```ruby
|
||||
type Complex
|
||||
Num re:Float im:Float
|
||||
|
||||
plain_or_both self =
|
||||
if self.im != 0 then self else
|
||||
both = self.re : Complex&Float
|
||||
both # value with both types: Complex and Float
|
||||
```
|
||||
Having a value with such _intersection type_ allows the IDE to offer operations
|
||||
available on all individual types.
|
||||
|
||||
## Creating
|
||||
|
||||
Creating a value of _intersection types_ is as simple as performing a type check:
|
||||
```ruby
|
||||
self : Complex&Float
|
||||
```
|
||||
However such a _type check_ is closely associated with [conversions](../syntax/conversions.md).
|
||||
If the value doesn't represent all the desired types yet, then the system looks
|
||||
for [conversion methods](../syntax/conversions.md) being available in the scope like:
|
||||
```
|
||||
Complex.from (that:Float) = Complex.Num that 0
|
||||
```
|
||||
and uses them to create all the values the _intersection type_ represents.
|
||||
|
||||
> [!NOTE]
|
||||
> Note that if a `Float.from (that:Complex)` conversion were available in the scope,
|
||||
> any `Complex` instance would be convertible to `Float` regardless of how it was constructed.
|
||||
> To ensure that such mix-ins are only available on values that opt-in to being
|
||||
> an intersection type (like in the `Complex` example above where we include
|
||||
> the `Float` mix-in only if `self.im == 0`), we need to ensure that the conversion
|
||||
> used to create the intersection type is not available in the default
|
||||
> conversion resolution scope. Thus it cannot be defined in the same module
|
||||
> as `Complex` or `Float` types, but instead it should be defined in a separate
|
||||
> module that is only imported in the place that will be constructing the multi-values.
|
||||
|
||||
<!--
|
||||
Just as demonstrated at
|
||||
https://github.com/enso-org/enso/commit/3d8a0e1b90b20cfdfe5da8d2d3950f644a4b45b8#diff-c6ef852899778b52ce6a11ebf9564d102c273021b212a4848b7678e120776287R23
|
||||
-->
|
||||
|
||||
## Narrowing Type Check
|
||||
|
||||
When an _intersection type_ value is being downcast to _some of the types it already represents_,
|
||||
these types become its _visible_ types. Any additional types it represents become _hidden_.
|
||||
|
||||
The following operations consider only the _visible_ part of the type:
|
||||
- [dynamic dispatch](../types/dynamic-dispatch.md)
|
||||
- cases when value is passed as an argument
|
||||
|
||||
However the value still keeps internal knowledge of all the types it represents.
|
||||
|
||||
Thus, after casting a value `cf:Complex&Float` to just `Complex`, e.g. `c = cf:Complex`:
|
||||
- method calls on `c` will only consider methods defined on `Complex`
|
||||
- the value `c` can only be passed as argument to methods expecting `Complex` type
|
||||
- a type error is raised when a `Float` parameter is expected
|
||||
|
||||
As such a _static analysis_ knows the type a value _has been cast to_ (the _visible_ part)
|
||||
and can deduce the set of operations that can be performed with it. Moreover, any
|
||||
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 : Float -> f.sqrt
|
||||
_ -> "Not a float"
|
||||
```
|
||||
-->
|
||||
|
||||
> [!WARNING]
|
||||
> Keep in mind that while both argument type check in method definitions and a
|
||||
> 'type asserting' expression look similarly, they have slightly different behaviour.
|
||||
> ```
|
||||
> f a:Float = a
|
||||
> g a = a:Float
|
||||
> ```
|
||||
> These two functions, while very similar, will have different behaviour when
|
||||
> passed a value like the value `c` above. The function `f` will fail with
|
||||
> a type error, because the visible type of `c` is just `Complex` (assuming
|
||||
> the conversion to `Float` is not available in the current scope).
|
||||
> However, the function `g` will accept the same value and return it as
|
||||
> a `Float` value, based on the 'hidden' part of its type.
|
||||
|
||||
> [!NOTE]
|
||||
> In the **object oriented terminology** we can think of
|
||||
> a type `Complex&Float` as being a subclass of `Complex` and subclass of `Float` types.
|
||||
> As such a value of type `Complex&Float` may be used wherever `Complex` or `Float` types
|
||||
> are used. Let there, for example, be a function:
|
||||
> ```ruby
|
||||
> use_complex c:Complex callback:(Any -> Any) = callback c
|
||||
> ```
|
||||
> that accepts `Complex` value and passes it back to a provided callback function.
|
||||
> It is possible to pass a value of `Complex&Float` type to such a function. Only
|
||||
> operations available on type `Complex` can be performed on value in variable `c`.
|
||||
>
|
||||
> However the `callback` function may still explicitly cast the value to `Float`.
|
||||
> E.g. the following is valid:
|
||||
> ```ruby
|
||||
> both = v : Complex&Float
|
||||
> use_complex both (v-> v:Float . sqrt)
|
||||
> ```
|
||||
> This behavior is often described as being **open to subclasses**. E.g. the `c:Complex`
|
||||
> check allows values with _intersection types_ that include `Complex` to pass thru with
|
||||
> all their runtime information available,
|
||||
> but one has to perform an explicit cast to extract the other types associated with
|
||||
> such a value.
|
||||
|
||||
This behavior allows creation of values with types like `Table&Column` to represent a table
|
||||
with a single column - something the users of visual _live programming_ environment of Enso find
|
||||
very useful.
|
||||
```ruby
|
||||
Table.join self right:Table -> Table = ...
|
||||
```
|
||||
Such a `Table&Column` value can be returned from the above `Table.join` function and while
|
||||
having only `Table` behavior by default, still being able to be explicitly casted by the visual environment
|
||||
to `Column`.
|
||||
|
||||
## Converting Type Check
|
||||
|
||||
When an _intersection type_ is being checked against a type it doesn't represent,
|
||||
any of its component types can be used for [conversion](../syntax/conversions.md).
|
||||
Should there be a `Float` to `Text` conversion:
|
||||
```ruby
|
||||
Text.from (that:Float) = Float.to_text
|
||||
```
|
||||
then `Complex&Float` value `cf` can be typed as `cf:Text`. The value can also
|
||||
be converted to another _intersection type_ like `ct = cf:Complex&Text`. In such case
|
||||
it looses its `Float` type and `ct:Float` would fail.
|
||||
|
||||
In short: when a [conversion](../syntax/conversions.md) is needed to satisfy a type check
|
||||
a new value is created to satisfy just the types requested in the check.
|
@ -89,15 +89,15 @@ public class TypeOfNodeMultiValueTest {
|
||||
var rawValue = ContextUtils.unwrapValue(ctx(), polyValue);
|
||||
var rawType = ContextUtils.unwrapValue(ctx(), t);
|
||||
if (rawType instanceof Type type) {
|
||||
var singleMultiValue = EnsoMultiValue.create(new Type[] {type}, new Object[] {rawValue});
|
||||
var singleMultiValue = EnsoMultiValue.create(new Type[] {type}, 1, new Object[] {rawValue});
|
||||
var n = t.getMetaSimpleName();
|
||||
data.add(new Object[] {singleMultiValue, n, 0});
|
||||
var rawInt = (Type) ContextUtils.unwrapValue(ctx(), g.typeInteger());
|
||||
var secondMultiValue =
|
||||
EnsoMultiValue.create(new Type[] {rawInt, type}, new Object[] {5L, rawValue});
|
||||
EnsoMultiValue.create(new Type[] {rawInt, type}, 2, new Object[] {5L, rawValue});
|
||||
data.add(new Object[] {secondMultiValue, n, 1});
|
||||
var firstMultiValue =
|
||||
EnsoMultiValue.create(new Type[] {type, rawInt}, new Object[] {rawValue, 6L});
|
||||
EnsoMultiValue.create(new Type[] {type, rawInt}, 2, new Object[] {rawValue, 6L});
|
||||
data.add(new Object[] {firstMultiValue, n, 0});
|
||||
} else {
|
||||
if (!t.isHostObject()) {
|
||||
|
@ -0,0 +1,79 @@
|
||||
package org.enso.interpreter.node.typecheck;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import com.oracle.truffle.api.CallTarget;
|
||||
import org.enso.interpreter.runtime.data.EnsoMultiValue;
|
||||
import org.enso.interpreter.runtime.data.Type;
|
||||
import org.enso.test.utils.ContextUtils;
|
||||
import org.enso.test.utils.TestRootNode;
|
||||
import org.graalvm.polyglot.Context;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Test;
|
||||
|
||||
public class TypeCheckValueTest {
|
||||
private static Context ctx;
|
||||
|
||||
private static Context ctx() {
|
||||
if (ctx == null) {
|
||||
ctx = ContextUtils.defaultContextBuilder().build();
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void closeCtx() {
|
||||
if (ctx != null) {
|
||||
ctx.close();
|
||||
}
|
||||
ctx = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void avoidDoubleWrappingOfEnsoMultiValue() {
|
||||
var convert = allOfIntegerAndText();
|
||||
|
||||
ContextUtils.executeInContext(
|
||||
ctx(),
|
||||
() -> {
|
||||
var builtins = ContextUtils.leakContext(ctx).getBuiltins();
|
||||
var m1 =
|
||||
EnsoMultiValue.create(
|
||||
new Type[] {builtins.text(), builtins.number().getInteger()},
|
||||
2,
|
||||
new Object[] {"Hi", 42});
|
||||
assertEquals("Text & Integer", m1.toDisplayString(true));
|
||||
|
||||
var res = convert.call(m1);
|
||||
assertTrue("Got multivalue again", res instanceof EnsoMultiValue);
|
||||
var emv = (EnsoMultiValue) res;
|
||||
|
||||
assertEquals("Integer & Text", emv.toDisplayString(true));
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private static CallTarget allOfIntegerAndText() {
|
||||
var call = new CallTarget[1];
|
||||
ContextUtils.executeInContext(
|
||||
ctx(),
|
||||
() -> {
|
||||
var builtins = ContextUtils.leakContext(ctx).getBuiltins();
|
||||
var intNode = TypeCheckValueNode.single("int", builtins.number().getInteger());
|
||||
var textNode = TypeCheckValueNode.single("text", builtins.text());
|
||||
var bothNode = TypeCheckValueNode.allOf("int&text", intNode, textNode);
|
||||
var root =
|
||||
new TestRootNode(
|
||||
(frame) -> {
|
||||
var arg = frame.getArguments()[0];
|
||||
var res = bothNode.handleCheckOrConversion(frame, arg, null);
|
||||
return res;
|
||||
});
|
||||
root.insertChildren(bothNode);
|
||||
call[0] = root.getCallTarget();
|
||||
return null;
|
||||
});
|
||||
return call[0];
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
package org.enso.interpreter.test;
|
||||
|
||||
import com.oracle.truffle.api.interop.InteropLibrary;
|
||||
import java.util.ArrayList;
|
||||
import org.enso.interpreter.runtime.data.EnsoMultiValue;
|
||||
import org.enso.interpreter.runtime.data.Type;
|
||||
import org.enso.test.utils.ContextUtils;
|
||||
import org.graalvm.polyglot.Context;
|
||||
import org.graalvm.polyglot.Value;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public class EnsoMultiValueInteropTest {
|
||||
|
||||
@Parameterized.Parameter(0)
|
||||
public Object value;
|
||||
|
||||
private static Context ctx;
|
||||
|
||||
private static Context ctx() {
|
||||
if (ctx == null) {
|
||||
ctx = ContextUtils.defaultContextBuilder().build();
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
@Parameterized.Parameters
|
||||
public static Object[][] allEnsoMultiValuePairs() throws Exception {
|
||||
var g = ValuesGenerator.create(ctx());
|
||||
var typeOf =
|
||||
ContextUtils.evalModule(
|
||||
ctx(),
|
||||
"""
|
||||
from Standard.Base import all
|
||||
|
||||
typ obj = Meta.type_of obj
|
||||
main = typ
|
||||
""");
|
||||
var data = new ArrayList<Object[]>();
|
||||
for (var v1 : g.allValues()) {
|
||||
for (var v2 : g.allValues()) {
|
||||
registerValue(g, typeOf, v1, v2, data);
|
||||
}
|
||||
}
|
||||
return data.toArray(new Object[0][]);
|
||||
}
|
||||
|
||||
private static void registerValue(
|
||||
ValuesGenerator g, Value typeOf, Value v1, Value v2, ArrayList<Object[]> data) {
|
||||
var t1 = typeOf.execute(v1);
|
||||
var t2 = typeOf.execute(v2);
|
||||
if (!t1.isNull() && !t2.isNull()) {
|
||||
var rawT1 = ContextUtils.unwrapValue(ctx(), t1);
|
||||
var rawT2 = ContextUtils.unwrapValue(ctx(), t2);
|
||||
if (rawT1 instanceof Type typ1 && rawT2 instanceof Type typ2) {
|
||||
var r1 = ContextUtils.unwrapValue(ctx, v1);
|
||||
var r2 = ContextUtils.unwrapValue(ctx, v2);
|
||||
var both = EnsoMultiValue.create(new Type[] {typ1, typ2}, 2, new Object[] {r1, r2});
|
||||
data.add(new Object[] {both});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void disposeCtx() throws Exception {
|
||||
if (ctx != null) {
|
||||
ctx.close();
|
||||
ctx = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isStringDoesntFail() {
|
||||
ContextUtils.executeInContext(
|
||||
ctx,
|
||||
() -> {
|
||||
return InteropLibrary.getUncached().isString(value);
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,270 @@
|
||||
package org.enso.interpreter.test;
|
||||
|
||||
import static org.enso.test.utils.ContextUtils.createDefaultContext;
|
||||
import static org.enso.test.utils.ContextUtils.executeInContext;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import org.enso.common.MethodNames;
|
||||
import org.enso.interpreter.node.expression.builtin.meta.EqualsNode;
|
||||
import org.enso.interpreter.node.expression.foreign.HostValueToEnsoNode;
|
||||
import org.enso.interpreter.runtime.data.EnsoMultiValue;
|
||||
import org.enso.interpreter.runtime.data.Type;
|
||||
import org.enso.interpreter.runtime.data.text.Text;
|
||||
import org.enso.test.utils.ContextUtils;
|
||||
import org.enso.test.utils.TestRootNode;
|
||||
import org.graalvm.polyglot.Context;
|
||||
import org.graalvm.polyglot.Source;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
public class EqualsMultiValueTest {
|
||||
private static Context context;
|
||||
private static EqualsNode equalsNode;
|
||||
private static TestRootNode testRootNode;
|
||||
private static HostValueToEnsoNode hostValueToEnsoNode;
|
||||
|
||||
@BeforeClass
|
||||
public static void initContextAndData() {
|
||||
context = createDefaultContext();
|
||||
executeInContext(
|
||||
context,
|
||||
() -> {
|
||||
testRootNode = new TestRootNode(EqualsMultiValueTest::equalityCheck);
|
||||
equalsNode = EqualsNode.create();
|
||||
hostValueToEnsoNode = HostValueToEnsoNode.build();
|
||||
testRootNode.insertChildren(equalsNode, hostValueToEnsoNode);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void disposeContext() {
|
||||
context.close();
|
||||
context = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEqualityIntegerAndMultiValue() {
|
||||
executeInContext(
|
||||
context,
|
||||
() -> {
|
||||
var builtins = ContextUtils.leakContext(context).getBuiltins();
|
||||
var intType = builtins.number().getInteger();
|
||||
var textText = builtins.text();
|
||||
var fourExtraText =
|
||||
EnsoMultiValue.create(
|
||||
new Type[] {intType, textText}, 1, new Object[] {4L, Text.create("Hi")});
|
||||
|
||||
assertTrue("4 == 4t", equalityCheck(4L, fourExtraText));
|
||||
assertFalse("5 != 4t", equalityCheck(5L, fourExtraText));
|
||||
assertTrue("4t == 4", equalityCheck(fourExtraText, 4L));
|
||||
assertFalse("4t != 5", equalityCheck(fourExtraText, 5L));
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEqualityTextAndExtraIntegerMultiValue() {
|
||||
executeInContext(
|
||||
context,
|
||||
() -> {
|
||||
var builtins = ContextUtils.leakContext(context).getBuiltins();
|
||||
var intType = builtins.number().getInteger();
|
||||
var textType = builtins.text();
|
||||
var bothTypes = new Type[] {textType, intType};
|
||||
|
||||
var text = Text.create("Hi");
|
||||
var ahoj = Text.create("Ahoj");
|
||||
var integer = 4L;
|
||||
//
|
||||
// following variable represents result of
|
||||
// x = _ : (Text & Integer) : Text
|
||||
// e.g. multi value with Text and Integer, casted to Text only
|
||||
//
|
||||
var multiV = EnsoMultiValue.create(bothTypes, 1, text, integer);
|
||||
|
||||
assertTrue("'Hi' == multiV", equalityCheck(text, multiV));
|
||||
assertFalse("'Ahoj' != multiV", equalityCheck(ahoj, multiV));
|
||||
assertFalse(
|
||||
"Don't consider extra Integer type in equals", equalityCheck(integer, multiV));
|
||||
assertFalse("5 != t4", equalityCheck(5L, multiV));
|
||||
assertFalse(
|
||||
"Don't consider extra Integer type in equals", equalityCheck(multiV, integer));
|
||||
assertFalse("4 != t5", equalityCheck(multiV, 5L));
|
||||
assertTrue("multiV == 'Hi'", equalityCheck(multiV, text));
|
||||
assertFalse("multiV != 'Ahoj'", equalityCheck(multiV, ahoj));
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEqualityIntegerAndMultiValueWithBoth() {
|
||||
executeInContext(
|
||||
context,
|
||||
() -> {
|
||||
var builtins = ContextUtils.leakContext(context).getBuiltins();
|
||||
var intType = builtins.number().getInteger();
|
||||
var textText = builtins.text();
|
||||
var hi = Text.create("Hi");
|
||||
var fourExtraText =
|
||||
EnsoMultiValue.create(new Type[] {textText, intType}, 2, new Object[] {hi, 4L});
|
||||
|
||||
assertTrue("4 == 4t", equalityCheck(4L, fourExtraText));
|
||||
assertFalse("5 != 4t", equalityCheck(5L, fourExtraText));
|
||||
assertTrue("4t == 4", equalityCheck(fourExtraText, 4L));
|
||||
assertFalse("4t != 5", equalityCheck(fourExtraText, 5L));
|
||||
assertTrue("4t == 'Hi'", equalityCheck(fourExtraText, hi));
|
||||
assertTrue("'Hi' == 4t", equalityCheck(hi, fourExtraText));
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEqualityIntegerAndMultiValueWithIntText() {
|
||||
executeInContext(
|
||||
context,
|
||||
() -> {
|
||||
var builtins = ContextUtils.leakContext(context).getBuiltins();
|
||||
var intType = builtins.number().getInteger();
|
||||
var textText = builtins.text();
|
||||
var fourExtraText =
|
||||
EnsoMultiValue.create(
|
||||
new Type[] {intType, textText}, 2, new Object[] {4L, Text.create("Hi")});
|
||||
|
||||
assertTrue("4 == 4t", equalityCheck(4L, fourExtraText));
|
||||
assertFalse("5 != 4t", equalityCheck(5L, fourExtraText));
|
||||
assertTrue("4t == 4", equalityCheck(fourExtraText, 4L));
|
||||
assertFalse("4t != 5", equalityCheck(fourExtraText, 5L));
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void twoMultiValues() {
|
||||
executeInContext(
|
||||
context,
|
||||
() -> {
|
||||
var builtins = ContextUtils.leakContext(context).getBuiltins();
|
||||
var intType = builtins.number().getInteger();
|
||||
var textText = builtins.text();
|
||||
var fourExtraText =
|
||||
EnsoMultiValue.create(
|
||||
new Type[] {intType, textText}, 1, new Object[] {4L, Text.create("Hi")});
|
||||
var fourExtraText2 =
|
||||
EnsoMultiValue.create(
|
||||
new Type[] {intType, textText}, 1, new Object[] {4L, Text.create("Hi")});
|
||||
var fiveExtraText =
|
||||
EnsoMultiValue.create(
|
||||
new Type[] {intType, textText}, 1, new Object[] {5L, Text.create("Hi")});
|
||||
|
||||
assertFalse("!= for sure #1", equalityCheck(fiveExtraText, fourExtraText));
|
||||
assertFalse("!= for sure #2", equalityCheck(fourExtraText, fiveExtraText));
|
||||
assertTrue("equals #1", equalityCheck(fourExtraText, fourExtraText2));
|
||||
assertTrue("equals #2", equalityCheck(fourExtraText2, fourExtraText));
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEqualityIntegerNoMultiValueWithConversion() throws Exception {
|
||||
assertEqualityIntegerWithConversion("c:Complex");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEqualityIntegerAndMultiValueWithConversion() throws Exception {
|
||||
assertEqualityIntegerWithConversion("c.as_complex_and_float");
|
||||
}
|
||||
|
||||
private void assertEqualityIntegerWithConversion(String complexNew) throws Exception {
|
||||
var code =
|
||||
"""
|
||||
import Standard.Base.Data.Numbers.Float
|
||||
import Standard.Base.Data.Numbers.Number
|
||||
import Standard.Base.Data.Ordering.Comparable
|
||||
import Standard.Base.Data.Ordering.Ordering
|
||||
import Standard.Base.Nothing
|
||||
import Standard.Base.Error.Error
|
||||
import Standard.Base.Errors.Illegal_Argument.Illegal_Argument
|
||||
|
||||
## Sample definition of a complex number with conversions
|
||||
from Number and implementation of a comparator.
|
||||
type Complex
|
||||
private Value re:Float im:Float
|
||||
|
||||
new re=0:Float im=0:Float -> Complex =
|
||||
c = Complex.Value re 0
|
||||
if im != 0 then c:Complex else
|
||||
${complexNew}
|
||||
|
||||
+ self (that:Complex) = Complex.new self.re+that.re self.im+that.im
|
||||
|
||||
< self (that:Complex) = Complex_Comparator.compare self that == Ordering.Less
|
||||
> self (that:Complex) = Complex_Comparator.compare self that == Ordering.Greater
|
||||
>= self (that:Complex) =
|
||||
ordering = Complex_Comparator.compare self that
|
||||
ordering == Ordering.Greater || ordering == Ordering.Equal
|
||||
<= self (that:Complex) =
|
||||
ordering = Complex_Comparator.compare self that
|
||||
ordering == Ordering.Less || ordering == Ordering.Equal
|
||||
|
||||
Complex.from (that:Number) = Complex.new that
|
||||
|
||||
|
||||
Comparable.from (that:Complex) = Comparable.new that Complex_Comparator
|
||||
Comparable.from (that:Number) = Comparable.new that Complex_Comparator
|
||||
|
||||
type Complex_Comparator
|
||||
compare x:Complex y:Complex = if x.re==y.re && x.im==y.im then Ordering.Equal else
|
||||
if x.im==0 && y.im==0 then Ordering.compare x.re y.re else
|
||||
Nothing
|
||||
hash x:Complex = if x.im == 0 then Ordering.hash x.re else
|
||||
7*x.re + 11*x.im
|
||||
|
||||
## uses the explicit conversion defined in this private module
|
||||
Complex.as_complex_and_float self =
|
||||
self : Complex&Float
|
||||
|
||||
## explicit "conversion" of `Complex` to `Float` in a private module
|
||||
used in `as_complex_and_float`
|
||||
Float.from (that:Complex) =
|
||||
if that.im == 0 then that.re else
|
||||
Error.throw <| Illegal_Argument.Error "Cannot convert Complex with imaginary part to Float"
|
||||
"""
|
||||
.replace("${complexNew}", complexNew);
|
||||
|
||||
var src = Source.newBuilder("enso", code, "complex.enso").build();
|
||||
var complexModule = context.eval(src);
|
||||
var complexFourValue =
|
||||
complexModule.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "Complex.new 4");
|
||||
|
||||
executeInContext(
|
||||
context,
|
||||
() -> {
|
||||
var complexFour = ContextUtils.unwrapValue(context, complexFourValue);
|
||||
|
||||
assertTrue("4 == 4t", equalityCheck(4L, complexFour));
|
||||
assertFalse("5 != 4t", equalityCheck(5L, complexFour));
|
||||
assertTrue("4t == 4", equalityCheck(complexFour, 4L));
|
||||
assertFalse("4t != 5", equalityCheck(complexFour, 5L));
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private static boolean equalityCheck(VirtualFrame frame) {
|
||||
var args = frame.getArguments();
|
||||
return equalsNode.execute(frame, args[0], args[1]).isTrue();
|
||||
}
|
||||
|
||||
private boolean equalityCheck(Object first, Object second) {
|
||||
return (Boolean) testRootNode.getCallTarget().call(first, second);
|
||||
}
|
||||
}
|
@ -1027,7 +1027,20 @@ public class SignatureTest extends ContextTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void returnTypeCheckByLastStatementOfMain() throws Exception {
|
||||
public void returnTypeCheckByLastStatementOfMainTextFirst() throws Exception {
|
||||
var main = assertTypeCheckByLastStatementOfMain("Text & Integer");
|
||||
assertTrue(main.isString());
|
||||
assertEquals("42", main.asString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void returnTypeCheckByLastStatementOfMainIntFirst() throws Exception {
|
||||
var main = assertTypeCheckByLastStatementOfMain("Integer & Text");
|
||||
assertTrue(main.fitsInInt());
|
||||
assertEquals(42, main.asInt());
|
||||
}
|
||||
|
||||
private Value assertTypeCheckByLastStatementOfMain(String cast) throws Exception {
|
||||
final URI uri = new URI("memory://rts.enso");
|
||||
final Source src =
|
||||
Source.newBuilder(
|
||||
@ -1036,18 +1049,17 @@ public class SignatureTest extends ContextTest {
|
||||
from Standard.Base import all
|
||||
|
||||
fn =
|
||||
(42 : Text & Integer)
|
||||
(42 : ${cast})
|
||||
|
||||
Text.from (that:Integer) = that.to_text
|
||||
""",
|
||||
"""
|
||||
.replace("${cast}", cast),
|
||||
uri.getAuthority())
|
||||
.uri(uri)
|
||||
.buildLiteral();
|
||||
|
||||
var module = ctx.eval(src);
|
||||
var main = module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "fn");
|
||||
assertEquals(42, main.asInt());
|
||||
assertEquals("42", main.asString());
|
||||
return module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "fn");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -157,7 +157,12 @@ final class BinaryOperatorNode extends ExpressionNode {
|
||||
for (var thatType : all) {
|
||||
var fn = findSymbol(symbol, thatType);
|
||||
if (fn != null) {
|
||||
var thatCasted = EnsoMultiValue.CastToNode.getUncached().executeCast(thatType, multi);
|
||||
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);
|
||||
|
@ -183,7 +183,7 @@ public abstract class InvokeConversionNode extends BaseNode {
|
||||
Object[] arguments,
|
||||
@Cached EnsoMultiValue.CastToNode castTo) {
|
||||
var type = extractType(self);
|
||||
var result = castTo.executeCast(type, that);
|
||||
var result = castTo.findTypeOrNull(type, that, true, true);
|
||||
if (result == null) {
|
||||
throw new PanicException(
|
||||
EnsoContext.get(this).getBuiltins().error().makeNoSuchConversion(type, self, conversion),
|
||||
|
@ -299,7 +299,7 @@ public abstract class InvokeMethodNode extends BaseNode {
|
||||
@Cached EnsoMultiValue.CastToNode castTo) {
|
||||
var fnAndType = self.resolveSymbol(methodResolverNode, symbol);
|
||||
if (fnAndType != null) {
|
||||
var unwrapSelf = castTo.executeCast(fnAndType.getRight(), self);
|
||||
var unwrapSelf = castTo.findTypeOrNull(fnAndType.getRight(), self, false, false);
|
||||
if (unwrapSelf != null) {
|
||||
assert arguments[0] == self;
|
||||
arguments[0] = unwrapSelf;
|
||||
|
@ -1,6 +1,7 @@
|
||||
package org.enso.interpreter.node.expression.builtin.meta;
|
||||
|
||||
import com.oracle.truffle.api.Assumption;
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.Truffle;
|
||||
import org.enso.interpreter.runtime.data.hash.EnsoHashMap;
|
||||
|
||||
@ -76,4 +77,10 @@ public final class EqualsAndInfo {
|
||||
return new EqualsAndInfo(result, warnings);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@CompilerDirectives.TruffleBoundary
|
||||
public String toString() {
|
||||
return "EqualsAndInfo{" + "equals=" + equals + ", hasWarnings=" + (getWarnings() != null) + '}';
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import org.enso.interpreter.runtime.EnsoContext;
|
||||
import org.enso.interpreter.runtime.callable.UnresolvedConversion;
|
||||
import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo;
|
||||
import org.enso.interpreter.runtime.callable.function.Function;
|
||||
import org.enso.interpreter.runtime.data.EnsoMultiValue;
|
||||
import org.enso.interpreter.runtime.data.Type;
|
||||
import org.enso.interpreter.runtime.data.atom.Atom;
|
||||
import org.enso.interpreter.runtime.data.atom.StructsLibrary;
|
||||
@ -109,7 +110,7 @@ public final class EqualsNode extends Node {
|
||||
}
|
||||
|
||||
private static boolean isDefinedIn(ModuleScope scope, Function fn) {
|
||||
if (fn.getCallTarget().getRootNode() instanceof EnsoRootNode ensoRoot) {
|
||||
if (fn != null && fn.getCallTarget().getRootNode() instanceof EnsoRootNode ensoRoot) {
|
||||
return ensoRoot.getModuleScope().getModule() == scope.getModule();
|
||||
} else {
|
||||
return false;
|
||||
@ -240,6 +241,13 @@ public final class EqualsNode extends Node {
|
||||
var state = State.create(ctx);
|
||||
try {
|
||||
var thatAsSelf = convertNode.execute(convert, state, new Object[] {selfType, that});
|
||||
if (thatAsSelf instanceof EnsoMultiValue emv) {
|
||||
thatAsSelf =
|
||||
EnsoMultiValue.CastToNode.getUncached().findTypeOrNull(selfType, emv, false, false);
|
||||
}
|
||||
if (thatAsSelf == null) {
|
||||
return EqualsAndInfo.FALSE;
|
||||
}
|
||||
var withInfo = equalityNode.execute(frame, self, thatAsSelf);
|
||||
var result = withInfo.isTrue();
|
||||
assert !result || assertHashCodeIsTheSame(that, thatAsSelf);
|
||||
|
@ -21,6 +21,7 @@ import org.enso.interpreter.runtime.data.EnsoObject;
|
||||
import org.enso.interpreter.runtime.data.atom.Atom;
|
||||
import org.enso.interpreter.runtime.data.atom.AtomConstructor;
|
||||
import org.enso.interpreter.runtime.data.text.Text;
|
||||
import org.enso.interpreter.runtime.library.dispatch.TypeOfNode;
|
||||
import org.enso.interpreter.runtime.number.EnsoBigInteger;
|
||||
import org.enso.interpreter.runtime.warning.WarningsLibrary;
|
||||
import org.enso.polyglot.common_utils.Core_Text_Utils;
|
||||
@ -118,7 +119,7 @@ abstract class EqualsSimpleNode extends Node {
|
||||
}
|
||||
}
|
||||
|
||||
@Specialization
|
||||
@Specialization(guards = "isNotMulti(other)")
|
||||
EqualsAndInfo equalsLongInterop(
|
||||
long self,
|
||||
Object other,
|
||||
@ -227,7 +228,8 @@ abstract class EqualsSimpleNode extends Node {
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
@Specialization(guards = {"isBigInteger(iop, self)", "!isPrimitiveValue(other)"})
|
||||
@Specialization(
|
||||
guards = {"isBigInteger(iop, self)", "!isPrimitiveValue(other)", "isNotMulti(other)"})
|
||||
EqualsAndInfo equalsBigIntInterop(
|
||||
Object self,
|
||||
Object other,
|
||||
@ -266,7 +268,11 @@ abstract class EqualsSimpleNode extends Node {
|
||||
* lexicographical order, handling Unicode normalization. See {@code Text_Utils.compare_to}.
|
||||
*/
|
||||
@Specialization(
|
||||
guards = {"selfInterop.isString(selfString)"},
|
||||
guards = {
|
||||
"selfInterop.isString(selfString)",
|
||||
"isNotMulti(selfString)",
|
||||
"isNotMulti(otherString)"
|
||||
},
|
||||
limit = "3")
|
||||
EqualsAndInfo equalsStrings(
|
||||
Object selfString,
|
||||
@ -317,6 +323,40 @@ abstract class EqualsSimpleNode extends Node {
|
||||
}
|
||||
}
|
||||
|
||||
@Specialization
|
||||
EqualsAndInfo equalsMultiValue(
|
||||
VirtualFrame frame,
|
||||
EnsoMultiValue self,
|
||||
Object other,
|
||||
@Shared("multiCast") @Cached EnsoMultiValue.CastToNode castNode,
|
||||
@Shared("multiType") @Cached TypeOfNode typesNode,
|
||||
@Shared("multiEquals") @Cached EqualsSimpleNode delegate) {
|
||||
var types = typesNode.findAllTypesOrNull(self);
|
||||
assert types != null;
|
||||
for (var t : types) {
|
||||
var value = castNode.findTypeOrNull(t, self, false, false);
|
||||
if (value == null) {
|
||||
continue;
|
||||
}
|
||||
var res = delegate.execute(frame, value, other);
|
||||
if (res.isTrue()) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
return EqualsAndInfo.FALSE;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
EqualsAndInfo equalsMultiValueReversed(
|
||||
VirtualFrame frame,
|
||||
Object self,
|
||||
EnsoMultiValue other,
|
||||
@Shared("multiCast") @Cached EnsoMultiValue.CastToNode castNode,
|
||||
@Shared("multiType") @Cached TypeOfNode typesNode,
|
||||
@Shared("multiEquals") @Cached EqualsSimpleNode delegate) {
|
||||
return equalsMultiValue(frame, other, self, castNode, typesNode, delegate);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
EqualsAndInfo equalsReverseLong(
|
||||
VirtualFrame frame,
|
||||
@ -375,7 +415,7 @@ abstract class EqualsSimpleNode extends Node {
|
||||
return true;
|
||||
}
|
||||
if (a instanceof EnsoMultiValue || b instanceof EnsoMultiValue) {
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
return !isPrimitive(a, interop) && !isPrimitive(b, interop);
|
||||
}
|
||||
@ -400,6 +440,10 @@ abstract class EqualsSimpleNode extends Node {
|
||||
return object instanceof Boolean || object instanceof Long || object instanceof Double;
|
||||
}
|
||||
|
||||
static boolean isNotMulti(Object v) {
|
||||
return !(v instanceof EnsoMultiValue);
|
||||
}
|
||||
|
||||
static boolean isBigInteger(InteropLibrary iop, Object v) {
|
||||
return switch (v) {
|
||||
case EnsoBigInteger b -> true;
|
||||
|
@ -11,7 +11,7 @@ import org.enso.interpreter.runtime.error.DataflowError;
|
||||
|
||||
/**
|
||||
* Root of hierarchy of nodes checking types. This class (and its subclasses) are an implementation
|
||||
* detail. The API to perform the is in {@link TypeCheckNode}.
|
||||
* detail. The API to perform the check or conversion is in {@link TypeCheckValueNode}.
|
||||
*/
|
||||
abstract sealed class AbstractTypeCheckNode extends Node
|
||||
permits OneOfTypesCheckNode, AllOfTypesCheckNode, SingleTypeCheckNode, MetaTypeCheckNode {
|
||||
@ -29,6 +29,16 @@ abstract sealed class AbstractTypeCheckNode extends Node
|
||||
|
||||
abstract String expectedTypeMessage();
|
||||
|
||||
final boolean isAllTypes() {
|
||||
Node p = this;
|
||||
for (; ; ) {
|
||||
if (p instanceof TypeCheckValueNode vn) {
|
||||
return vn.isAllTypes();
|
||||
}
|
||||
p = p.getParent();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The error message for this node's check. Ready for being used at "fast path".
|
||||
*
|
||||
|
@ -40,11 +40,19 @@ final class AllOfTypesCheckNode extends AbstractTypeCheckNode {
|
||||
if (result == null) {
|
||||
return null;
|
||||
}
|
||||
values[at] = result;
|
||||
valueTypes[at] = types.getType(result);
|
||||
if (result instanceof EnsoMultiValue emv) {
|
||||
result =
|
||||
EnsoMultiValue.CastToNode.getUncached()
|
||||
.findTypeOrNull(valueTypes[at], emv, false, true);
|
||||
}
|
||||
if (result == null) {
|
||||
return null;
|
||||
}
|
||||
values[at] = result;
|
||||
at++;
|
||||
}
|
||||
return EnsoMultiValue.create(valueTypes, values);
|
||||
return EnsoMultiValue.create(valueTypes, valueTypes.length, values);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -11,7 +11,7 @@ import org.enso.interpreter.runtime.util.CachingSupplier;
|
||||
|
||||
/**
|
||||
* Node for checking {@code polyglot java import} types. This class (and its subclasses)
|
||||
* are an implementation detail. The API to perform the is in {@link TypeCheckNode}.
|
||||
* are an implementation detail. The API to perform the is in {@link TypeCheckValueNode}.
|
||||
*/
|
||||
non-sealed abstract class MetaTypeCheckNode extends AbstractTypeCheckNode {
|
||||
private final CachingSupplier<? extends Object> expectedSupplier;
|
||||
|
@ -1,14 +1,7 @@
|
||||
package org.enso.interpreter.node.typecheck;
|
||||
|
||||
import com.oracle.truffle.api.CompilerAsserts;
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.dsl.Cached;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.frame.MaterializedFrame;
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import com.oracle.truffle.api.nodes.ExplodeLoop;
|
||||
import com.oracle.truffle.api.nodes.InvalidAssumptionException;
|
||||
import com.oracle.truffle.api.nodes.Node;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.enso.interpreter.EnsoLanguage;
|
||||
import org.enso.interpreter.node.EnsoRootNode;
|
||||
import org.enso.interpreter.node.ExpressionNode;
|
||||
@ -28,6 +21,16 @@ import org.enso.interpreter.runtime.error.PanicSentinel;
|
||||
import org.enso.interpreter.runtime.library.dispatch.TypeOfNode;
|
||||
import org.graalvm.collections.Pair;
|
||||
|
||||
import com.oracle.truffle.api.CompilerAsserts;
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.dsl.Cached;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.frame.MaterializedFrame;
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import com.oracle.truffle.api.nodes.ExplodeLoop;
|
||||
import com.oracle.truffle.api.nodes.InvalidAssumptionException;
|
||||
import com.oracle.truffle.api.nodes.Node;
|
||||
|
||||
non-sealed abstract class SingleTypeCheckNode extends AbstractTypeCheckNode {
|
||||
private final Type expectedType;
|
||||
@Node.Child IsValueOfTypeNode checkType;
|
||||
@ -104,7 +107,7 @@ non-sealed abstract class SingleTypeCheckNode extends AbstractTypeCheckNode {
|
||||
CompilerDirectives.transferToInterpreter();
|
||||
var enso = EnsoLanguage.get(this);
|
||||
var node = (AbstractTypeCheckNode) copy();
|
||||
lazyCheck = new LazyCheckRootNode(enso, new TypeCheckValueNode(node));
|
||||
lazyCheck = new LazyCheckRootNode(enso, new TypeCheckValueNode(node, isAllTypes()));
|
||||
}
|
||||
var lazyCheckFn = lazyCheck.wrapThunk(fn);
|
||||
return lazyCheckFn;
|
||||
@ -115,7 +118,7 @@ non-sealed abstract class SingleTypeCheckNode extends AbstractTypeCheckNode {
|
||||
CompilerDirectives.transferToInterpreter();
|
||||
castTo = insert(EnsoMultiValue.CastToNode.create());
|
||||
}
|
||||
var result = castTo.executeCast(expectedType, mv);
|
||||
var result = castTo.findTypeOrNull(expectedType, mv, true, isAllTypes());
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
@ -175,7 +178,15 @@ non-sealed abstract class SingleTypeCheckNode extends AbstractTypeCheckNode {
|
||||
if (v instanceof EnsoMultiValue multi) {
|
||||
var all = typeOfNode.findAllTypesOrNull(multi);
|
||||
assert all != null;
|
||||
return all;
|
||||
var to = 0;
|
||||
// only consider methodDispatchTypes and not "all types" of the multi value
|
||||
for (var i = 0; i < all.length; i++) {
|
||||
var typeOrNull = EnsoMultiValue.CastToNode.getUncached().findTypeOrNull(all[i], multi, false, false);
|
||||
if (typeOrNull != null) {
|
||||
all[to++] = all[i];
|
||||
}
|
||||
}
|
||||
return Arrays.copyOf(all, to);
|
||||
}
|
||||
if (v instanceof UnresolvedConstructor) {
|
||||
return null;
|
||||
|
@ -19,8 +19,8 @@ final class TypeCheckExpressionNode extends ExpressionNode {
|
||||
|
||||
@Override
|
||||
public Object executeGeneric(VirtualFrame frame) {
|
||||
java.lang.Object value = original.executeGeneric(frame);
|
||||
java.lang.Object result = check.handleCheckOrConversion(frame, value, original);
|
||||
var value = original.executeGeneric(frame);
|
||||
var result = check.handleCheckOrConversion(frame, value, original);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -18,10 +18,12 @@ import org.enso.interpreter.runtime.util.CachingSupplier;
|
||||
/** A node and a factory for nodes performing type checks (including necessary conversions). */
|
||||
public final class TypeCheckValueNode extends Node {
|
||||
private @Child AbstractTypeCheckNode check;
|
||||
private final boolean allTypes;
|
||||
|
||||
TypeCheckValueNode(AbstractTypeCheckNode check) {
|
||||
TypeCheckValueNode(AbstractTypeCheckNode check, boolean allTypes) {
|
||||
assert check != null;
|
||||
this.check = check;
|
||||
this.allTypes = allTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -83,8 +85,8 @@ public final class TypeCheckValueNode extends Node {
|
||||
var arr = toArray(flatten);
|
||||
return switch (arr.length) {
|
||||
case 0 -> null;
|
||||
case 1 -> new TypeCheckValueNode(arr[0]);
|
||||
default -> new TypeCheckValueNode(new AllOfTypesCheckNode(comment, arr));
|
||||
case 1 -> new TypeCheckValueNode(arr[0], true);
|
||||
default -> new TypeCheckValueNode(new AllOfTypesCheckNode(comment, arr), true);
|
||||
};
|
||||
}
|
||||
|
||||
@ -106,7 +108,7 @@ public final class TypeCheckValueNode extends Node {
|
||||
default -> {
|
||||
var abstractTypeCheckList = list.stream().map(n -> n.check).toList();
|
||||
var abstractTypeCheckArr = toArray(abstractTypeCheckList);
|
||||
yield new TypeCheckValueNode(new OneOfTypesCheckNode(comment, abstractTypeCheckArr));
|
||||
yield new TypeCheckValueNode(new OneOfTypesCheckNode(comment, abstractTypeCheckArr), true);
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -120,7 +122,7 @@ public final class TypeCheckValueNode extends Node {
|
||||
*/
|
||||
public static TypeCheckValueNode single(String comment, Type expectedType) {
|
||||
var typeCheckNodeImpl = SingleTypeCheckNodeGen.create(comment, expectedType);
|
||||
return new TypeCheckValueNode(typeCheckNodeImpl);
|
||||
return new TypeCheckValueNode(typeCheckNodeImpl, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -134,7 +136,25 @@ public final class TypeCheckValueNode extends Node {
|
||||
String comment, Supplier<? extends Object> metaObjectSupplier) {
|
||||
var cachingSupplier = CachingSupplier.wrap(metaObjectSupplier);
|
||||
var typeCheckNodeImpl = MetaTypeCheckNodeGen.create(comment, cachingSupplier);
|
||||
return new TypeCheckValueNode(typeCheckNodeImpl);
|
||||
return new TypeCheckValueNode(typeCheckNodeImpl, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new node with different {@code allTypes} state. Otherwise the behavior is the same as
|
||||
* {@code node}. All types state influences the behavior of type checking {@link EnsoMultiValue} -
|
||||
* should all types the value is convertible to be used or only those types that the value has
|
||||
* already been converted to?
|
||||
*
|
||||
* <pre>
|
||||
* method arg:Text = # this check has allTypes == false
|
||||
* num = arg:Number # this check has allTypes == true
|
||||
* </pre>
|
||||
*
|
||||
* @param allTypes the value of all types state for the new node
|
||||
* @param node the previous node with type checking logic
|
||||
*/
|
||||
public static TypeCheckValueNode allTypes(boolean allTypes, TypeCheckValueNode node) {
|
||||
return node == null ? null : new TypeCheckValueNode(node.check, allTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -179,4 +199,8 @@ public final class TypeCheckValueNode extends Node {
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
final boolean isAllTypes() {
|
||||
return allTypes;
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ 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.dsl.Cached;
|
||||
import com.oracle.truffle.api.dsl.Cached.Shared;
|
||||
import com.oracle.truffle.api.dsl.GenerateUncached;
|
||||
import com.oracle.truffle.api.dsl.NeverDefault;
|
||||
@ -41,19 +40,36 @@ public final class EnsoMultiValue extends EnsoObject {
|
||||
@CompilationFinal(dimensions = 1)
|
||||
private final Type[] types;
|
||||
|
||||
@CompilationFinal private final int methodDispatchTypes;
|
||||
|
||||
@CompilationFinal(dimensions = 1)
|
||||
private final Object[] values;
|
||||
|
||||
private EnsoMultiValue(Type[] types, Object[] values) {
|
||||
private EnsoMultiValue(Type[] types, int dispatchTypes, Object[] values) {
|
||||
this.types = types;
|
||||
this.methodDispatchTypes = dispatchTypes;
|
||||
assert types.length == values.length;
|
||||
this.values = values;
|
||||
assert !Stream.of(values).filter(v -> v instanceof EnsoMultiValue).findAny().isPresent()
|
||||
assert !Stream.of(values).anyMatch(v -> v instanceof EnsoMultiValue)
|
||||
: "Avoid double wrapping " + Arrays.toString(values);
|
||||
}
|
||||
|
||||
public static EnsoObject create(Type[] types, Object[] values) {
|
||||
return new EnsoMultiValue(types, values);
|
||||
/**
|
||||
* Creates new instance of EnsoMultiValue from provided information.
|
||||
*
|
||||
* @param types all the types this value can be {@link CastToNode cast to}
|
||||
* @param dispatchTypes the (subset of) types that the value is cast to currently - bigger than
|
||||
* {@code 0} and at most {@code type.length}
|
||||
* @param values value of each of the provided {@code types}
|
||||
* @return non-{@code null} multi value instance
|
||||
*/
|
||||
@NeverDefault
|
||||
public static EnsoMultiValue create(
|
||||
@NeverDefault Type[] types, @NeverDefault int dispatchTypes, @NeverDefault Object... values) {
|
||||
assert dispatchTypes > 0;
|
||||
assert dispatchTypes <= types.length;
|
||||
assert types.length == values.length;
|
||||
return new EnsoMultiValue(types, dispatchTypes, values);
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
@ -83,125 +99,134 @@ public final class EnsoMultiValue extends EnsoObject {
|
||||
return toString();
|
||||
}
|
||||
|
||||
private enum InteropType {
|
||||
NULL,
|
||||
BOOLEAN,
|
||||
DATE_TIME_ZONE,
|
||||
DURATION,
|
||||
STRING,
|
||||
NUMBER,
|
||||
POINTER,
|
||||
META_OBJECT,
|
||||
ITERATOR;
|
||||
|
||||
private record Value(InteropType type, Object value) {}
|
||||
|
||||
static Value find(Object[] values, int max, InteropLibrary iop) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
var v = values[i];
|
||||
if (iop.isNull(v)) {
|
||||
return new Value(NULL, v);
|
||||
}
|
||||
if (iop.isBoolean(v)) {
|
||||
return new Value(BOOLEAN, v);
|
||||
}
|
||||
if (iop.isDate(v) || iop.isTime(v) || iop.isTimeZone(v)) {
|
||||
return new Value(DATE_TIME_ZONE, v);
|
||||
}
|
||||
if (iop.isDuration(v)) {
|
||||
return new Value(DURATION, v);
|
||||
}
|
||||
if (iop.isString(v)) {
|
||||
return new Value(STRING, v);
|
||||
}
|
||||
if (iop.isNumber(v)) {
|
||||
return new Value(NUMBER, v);
|
||||
}
|
||||
if (iop.isPointer(v)) {
|
||||
return new Value(POINTER, v);
|
||||
}
|
||||
if (iop.isMetaObject(v)) {
|
||||
return new Value(META_OBJECT, v);
|
||||
}
|
||||
if (iop.isIterator(v)) {
|
||||
return new Value(ITERATOR, v);
|
||||
}
|
||||
}
|
||||
return new Value(null, null);
|
||||
}
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
boolean isBoolean(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) {
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
if (iop.isBoolean(values[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
var both = InteropType.find(values, methodDispatchTypes, iop);
|
||||
return both.type() == InteropType.BOOLEAN;
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
boolean asBoolean(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop)
|
||||
throws UnsupportedMessageException {
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
if (iop.isBoolean(values[i])) {
|
||||
return iop.asBoolean(values[i]);
|
||||
}
|
||||
var both = InteropType.find(values, methodDispatchTypes, iop);
|
||||
if (both.type() == InteropType.BOOLEAN) {
|
||||
return iop.asBoolean(both.value());
|
||||
}
|
||||
throw UnsupportedMessageException.create();
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
boolean isString(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) {
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
if (iop.isString(values[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
var both = InteropType.find(values, methodDispatchTypes, iop);
|
||||
return both.type() == InteropType.STRING;
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
String asString(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop)
|
||||
throws UnsupportedMessageException {
|
||||
for (Object value : values) {
|
||||
if (iop.isString(value)) {
|
||||
return iop.asString(value);
|
||||
}
|
||||
var both = InteropType.find(values, methodDispatchTypes, iop);
|
||||
if (both.type() == InteropType.STRING) {
|
||||
return iop.asString(both.value());
|
||||
}
|
||||
throw UnsupportedMessageException.create();
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
boolean isNumber(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) {
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
if (iop.isNumber(values[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
var both = InteropType.find(values, methodDispatchTypes, iop);
|
||||
return both.type() == InteropType.NUMBER;
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
boolean fitsInByte(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) {
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
if (iop.fitsInByte(values[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
var both = InteropType.find(values, methodDispatchTypes, iop);
|
||||
return both.type() == InteropType.NUMBER && iop.fitsInByte(both.value());
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
boolean fitsInShort(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) {
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
if (iop.fitsInShort(values[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
var both = InteropType.find(values, methodDispatchTypes, iop);
|
||||
return both.type() == InteropType.NUMBER && iop.fitsInShort(both.value());
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
boolean fitsInInt(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) {
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
if (iop.fitsInShort(values[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
var both = InteropType.find(values, methodDispatchTypes, iop);
|
||||
return both.type() == InteropType.NUMBER && iop.fitsInInt(both.value());
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
boolean fitsInLong(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) {
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
if (iop.fitsInLong(values[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
var both = InteropType.find(values, methodDispatchTypes, iop);
|
||||
return both.type() == InteropType.NUMBER && iop.fitsInLong(both.value());
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
boolean fitsInFloat(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) {
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
if (iop.fitsInFloat(values[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
var both = InteropType.find(values, methodDispatchTypes, iop);
|
||||
return both.type() == InteropType.NUMBER && iop.fitsInFloat(both.value());
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
boolean fitsInDouble(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) {
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
if (iop.fitsInDouble(values[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
var both = InteropType.find(values, methodDispatchTypes, iop);
|
||||
return both.type() == InteropType.NUMBER && iop.fitsInDouble(both.value());
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
byte asByte(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop)
|
||||
throws UnsupportedMessageException {
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
if (iop.fitsInByte(values[i])) {
|
||||
return iop.asByte(values[i]);
|
||||
}
|
||||
var both = InteropType.find(values, methodDispatchTypes, iop);
|
||||
if (both.type() == InteropType.NUMBER) {
|
||||
return iop.asByte(both.value());
|
||||
}
|
||||
throw UnsupportedMessageException.create();
|
||||
}
|
||||
@ -209,10 +234,9 @@ public final class EnsoMultiValue extends EnsoObject {
|
||||
@ExportMessage
|
||||
short asShort(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop)
|
||||
throws UnsupportedMessageException {
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
if (iop.fitsInShort(values[i])) {
|
||||
return iop.asShort(values[i]);
|
||||
}
|
||||
var both = InteropType.find(values, methodDispatchTypes, iop);
|
||||
if (both.type() == InteropType.NUMBER) {
|
||||
return iop.asShort(both.value());
|
||||
}
|
||||
throw UnsupportedMessageException.create();
|
||||
}
|
||||
@ -220,10 +244,9 @@ public final class EnsoMultiValue extends EnsoObject {
|
||||
@ExportMessage
|
||||
int asInt(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop)
|
||||
throws UnsupportedMessageException {
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
if (iop.fitsInInt(values[i])) {
|
||||
return iop.asInt(values[i]);
|
||||
}
|
||||
var both = InteropType.find(values, methodDispatchTypes, iop);
|
||||
if (both.type() == InteropType.NUMBER) {
|
||||
return iop.asInt(both.value());
|
||||
}
|
||||
throw UnsupportedMessageException.create();
|
||||
}
|
||||
@ -231,10 +254,9 @@ public final class EnsoMultiValue extends EnsoObject {
|
||||
@ExportMessage
|
||||
long asLong(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop)
|
||||
throws UnsupportedMessageException {
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
if (iop.fitsInLong(values[i])) {
|
||||
return iop.asLong(values[i]);
|
||||
}
|
||||
var both = InteropType.find(values, methodDispatchTypes, iop);
|
||||
if (both.type() == InteropType.NUMBER) {
|
||||
return iop.asLong(both.value());
|
||||
}
|
||||
throw UnsupportedMessageException.create();
|
||||
}
|
||||
@ -242,10 +264,9 @@ public final class EnsoMultiValue extends EnsoObject {
|
||||
@ExportMessage
|
||||
float asFloat(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop)
|
||||
throws UnsupportedMessageException {
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
if (iop.fitsInFloat(values[i])) {
|
||||
return iop.asFloat(values[i]);
|
||||
}
|
||||
var both = InteropType.find(values, methodDispatchTypes, iop);
|
||||
if (both.type() == InteropType.NUMBER) {
|
||||
return iop.asFloat(both.value());
|
||||
}
|
||||
throw UnsupportedMessageException.create();
|
||||
}
|
||||
@ -253,115 +274,89 @@ public final class EnsoMultiValue extends EnsoObject {
|
||||
@ExportMessage
|
||||
double asDouble(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop)
|
||||
throws UnsupportedMessageException {
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
if (iop.fitsInDouble(values[i])) {
|
||||
return iop.asDouble(values[i]);
|
||||
}
|
||||
var both = InteropType.find(values, methodDispatchTypes, iop);
|
||||
if (both.type() == InteropType.NUMBER) {
|
||||
return iop.asDouble(both.value());
|
||||
}
|
||||
throw UnsupportedMessageException.create();
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
boolean fitsInBigInteger(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) {
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
if (iop.fitsInBigInteger(values[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
var both = InteropType.find(values, methodDispatchTypes, iop);
|
||||
return both.type() == InteropType.NUMBER && iop.fitsInBigInteger(both.value());
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
BigInteger asBigInteger(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop)
|
||||
throws UnsupportedMessageException {
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
if (iop.fitsInBigInteger(values[i])) {
|
||||
return iop.asBigInteger(values[i]);
|
||||
}
|
||||
var both = InteropType.find(values, methodDispatchTypes, iop);
|
||||
if (both.type() == InteropType.NUMBER) {
|
||||
return iop.asBigInteger(both.value());
|
||||
}
|
||||
throw UnsupportedMessageException.create();
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
boolean isTime(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) {
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
if (iop.isTime(values[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
var both = InteropType.find(values, methodDispatchTypes, iop);
|
||||
return both.type() == InteropType.DATE_TIME_ZONE && iop.isTime(both.value());
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
LocalTime asTime(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop)
|
||||
throws UnsupportedMessageException {
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
if (iop.isTime(values[i])) {
|
||||
return iop.asTime(values[i]);
|
||||
}
|
||||
var both = InteropType.find(values, methodDispatchTypes, iop);
|
||||
if (both.type() == InteropType.DATE_TIME_ZONE) {
|
||||
return iop.asTime(both.value());
|
||||
}
|
||||
throw UnsupportedMessageException.create();
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
boolean isDate(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) {
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
if (iop.isDate(values[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
var both = InteropType.find(values, methodDispatchTypes, iop);
|
||||
return both.type() == InteropType.DATE_TIME_ZONE && iop.isDate(both.value());
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
LocalDate asDate(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop)
|
||||
throws UnsupportedMessageException {
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
if (iop.isDate(values[i])) {
|
||||
return iop.asDate(values[i]);
|
||||
}
|
||||
var both = InteropType.find(values, methodDispatchTypes, iop);
|
||||
if (both.type() == InteropType.DATE_TIME_ZONE) {
|
||||
return iop.asDate(both.value());
|
||||
}
|
||||
throw UnsupportedMessageException.create();
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
boolean isTimeZone(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) {
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
if (iop.isTimeZone(values[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
var both = InteropType.find(values, methodDispatchTypes, iop);
|
||||
return both.type() == InteropType.DATE_TIME_ZONE && iop.isTimeZone(both.value());
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
ZoneId asTimeZone(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop)
|
||||
throws UnsupportedMessageException {
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
if (iop.isTimeZone(values[i])) {
|
||||
return iop.asTimeZone(values[i]);
|
||||
}
|
||||
var both = InteropType.find(values, methodDispatchTypes, iop);
|
||||
if (both.type() == InteropType.DATE_TIME_ZONE) {
|
||||
return iop.asTimeZone(both.value());
|
||||
}
|
||||
throw UnsupportedMessageException.create();
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
boolean isDuration(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) {
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
if (iop.isDuration(values[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
var both = InteropType.find(values, methodDispatchTypes, iop);
|
||||
return both.type() == InteropType.DURATION;
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
Duration asDuration(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop)
|
||||
throws UnsupportedMessageException {
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
if (iop.isDuration(values[i])) {
|
||||
return iop.asDuration(values[i]);
|
||||
}
|
||||
var both = InteropType.find(values, methodDispatchTypes, iop);
|
||||
if (both.type() == InteropType.DURATION) {
|
||||
return iop.asDuration(both.value());
|
||||
}
|
||||
throw UnsupportedMessageException.create();
|
||||
}
|
||||
@ -376,7 +371,7 @@ public final class EnsoMultiValue extends EnsoObject {
|
||||
Object getMembers(
|
||||
boolean includeInternal, @Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) {
|
||||
var names = new TreeSet<String>();
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
for (var i = 0; i < methodDispatchTypes; i++) {
|
||||
try {
|
||||
var members = iop.getMembers(values[i]);
|
||||
var len = iop.getArraySize(members);
|
||||
@ -393,7 +388,7 @@ public final class EnsoMultiValue extends EnsoObject {
|
||||
@ExportMessage
|
||||
boolean isMemberInvocable(
|
||||
String name, @Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) {
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
for (var i = 0; i < methodDispatchTypes; i++) {
|
||||
if (iop.isMemberInvocable(values[i], name)) {
|
||||
return true;
|
||||
}
|
||||
@ -410,7 +405,7 @@ public final class EnsoMultiValue extends EnsoObject {
|
||||
ArityException,
|
||||
UnsupportedTypeException,
|
||||
UnknownIdentifierException {
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
for (var i = 0; i < methodDispatchTypes; i++) {
|
||||
if (iop.isMemberInvocable(values[i], name)) {
|
||||
return iop.invokeMember(values[i], name, args);
|
||||
}
|
||||
@ -430,11 +425,20 @@ public final class EnsoMultiValue extends EnsoObject {
|
||||
/**
|
||||
* Casts value in a multi value into specific type.
|
||||
*
|
||||
* @param type the requested t
|
||||
* @param type the requested type
|
||||
* @param mv a multi value
|
||||
* @return instance of the {@code t} or {@code null} if no suitable value was found
|
||||
* @param reorderOnly allow (modified) {@link EnsoMultiValue} to be returned otherwise extract
|
||||
* the value of {@code type} and return it directly
|
||||
* @param allTypes should we search all types or just up to {@code methodDispatchTypes}
|
||||
* @return instance of the {@code type} or {@code null} if no suitable value was found
|
||||
*/
|
||||
public abstract Object executeCast(Type type, EnsoMultiValue mv);
|
||||
public final Object findTypeOrNull(
|
||||
Type type, EnsoMultiValue mv, boolean reorderOnly, boolean allTypes) {
|
||||
return executeCast(type, mv, reorderOnly, allTypes);
|
||||
}
|
||||
|
||||
abstract Object executeCast(
|
||||
Type type, EnsoMultiValue mv, boolean reorderOnly, boolean allTypes);
|
||||
|
||||
@NeverDefault
|
||||
public static CastToNode create() {
|
||||
@ -447,13 +451,21 @@ public final class EnsoMultiValue extends EnsoObject {
|
||||
}
|
||||
|
||||
@Specialization
|
||||
Object castsToAType(
|
||||
Type type,
|
||||
EnsoMultiValue mv,
|
||||
@Cached(value = "type", allowUncached = true, neverDefault = true) Type cachedType) {
|
||||
for (var i = 0; i < mv.types.length; i++) {
|
||||
if (mv.types[i] == cachedType) {
|
||||
return mv.values[i];
|
||||
Object castsToAType(Type type, EnsoMultiValue mv, boolean reorderOnly, boolean allTypes) {
|
||||
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];
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@ -471,10 +483,11 @@ public final class EnsoMultiValue extends EnsoObject {
|
||||
MethodResolverNode node, UnresolvedSymbol symbol) {
|
||||
var ctx = EnsoContext.get(node);
|
||||
Pair<Function, Type> foundAnyMethod = null;
|
||||
for (Type t : types) {
|
||||
for (var i = 0; i < methodDispatchTypes; i++) {
|
||||
var t = types[i];
|
||||
var fnAndType = node.execute(t, symbol);
|
||||
if (fnAndType != null) {
|
||||
if (fnAndType.getRight() != ctx.getBuiltins().any()) {
|
||||
if (methodDispatchTypes == 1 || fnAndType.getRight() != ctx.getBuiltins().any()) {
|
||||
return Pair.create(fnAndType.getLeft(), t);
|
||||
}
|
||||
foundAnyMethod = fnAndType;
|
||||
|
@ -908,7 +908,11 @@ class IrToTruffle(
|
||||
arg: DefinitionArgument
|
||||
): TypeCheckValueNode = {
|
||||
val comment = "`" + arg.name.name + "`"
|
||||
arg.ascribedType.map(extractAscribedType(comment, _)).getOrElse(null)
|
||||
arg.ascribedType
|
||||
.map { t =>
|
||||
TypeCheckValueNode.allTypes(false, extractAscribedType(comment, t))
|
||||
}
|
||||
.getOrElse(null)
|
||||
}
|
||||
|
||||
/** Checks if the expression has a @Builtin_Method annotation
|
||||
|
@ -23,6 +23,7 @@ import org.graalvm.polyglot.proxy.ProxyExecutable;
|
||||
|
||||
/** A collection of classes and methods useful for testing {@link Context} related stuff. */
|
||||
public final class ContextUtils {
|
||||
|
||||
private ContextUtils() {}
|
||||
|
||||
public static Context createDefaultContext() {
|
||||
@ -60,12 +61,15 @@ public final class ContextUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the given callable in the given context. A necessity for executing artificially
|
||||
* created Truffle ASTs.
|
||||
* Executes the given callable in the given context.A necessity for executing artificially created
|
||||
* Truffle ASTs.
|
||||
*
|
||||
* @param <T> type of the return value
|
||||
* @param ctx context to execute at
|
||||
* @param callable action to invoke with given return type
|
||||
* @return Object returned from {@code callable} wrapped in {@link Value}.
|
||||
*/
|
||||
public static Value executeInContext(Context ctx, Callable<Object> callable) {
|
||||
public static <T> Value executeInContext(Context ctx, Callable<T> callable) {
|
||||
// Force initialization of the context
|
||||
ctx.eval("enso", "value = 0");
|
||||
var err = new Exception[1];
|
||||
@ -208,6 +212,7 @@ public final class ContextUtils {
|
||||
|
||||
@ExportLibrary(InteropLibrary.class)
|
||||
static final class Unwrapper implements TruffleObject {
|
||||
|
||||
Object[] args;
|
||||
|
||||
@ExportMessage
|
||||
|
@ -10,7 +10,7 @@ import project.Data.Complex_Helpers.Complex_Comparator
|
||||
type Complex
|
||||
private Value re:Float im:Float
|
||||
|
||||
new re=0:Float im=0:Float =
|
||||
new re=0:Float im=0:Float -> Complex =
|
||||
c = Complex.Value re im
|
||||
if im != 0 then c:Complex else
|
||||
c.as_complex_and_float
|
||||
|
@ -11,6 +11,7 @@ import project.Semantic.Import_Loop.Spec as Import_Loop_Spec
|
||||
import project.Semantic.Meta_Spec
|
||||
import project.Semantic.Instrumentor_Spec
|
||||
import project.Semantic.Meta_Location_Spec
|
||||
import project.Semantic.Multi_Value_Convert_Spec
|
||||
import project.Semantic.Multi_Value_Spec
|
||||
import project.Semantic.Names_Spec
|
||||
import project.Semantic.Equals_Spec
|
||||
@ -133,6 +134,7 @@ main filter=Nothing =
|
||||
Meta_Spec.add_specs suite_builder
|
||||
Instrumentor_Spec.add_specs suite_builder
|
||||
Meta_Location_Spec.add_specs suite_builder
|
||||
Multi_Value_Convert_Spec.add_specs suite_builder
|
||||
Multi_Value_Spec.add_specs suite_builder
|
||||
Names_Spec.add_specs suite_builder
|
||||
Numbers_Spec.add_specs suite_builder
|
||||
|
132
test/Base_Tests/src/Semantic/Multi_Value_Convert_Spec.enso
Normal file
132
test/Base_Tests/src/Semantic/Multi_Value_Convert_Spec.enso
Normal file
@ -0,0 +1,132 @@
|
||||
from Standard.Base import all
|
||||
from Standard.Test import all
|
||||
import Standard.Base.Errors.Common.Type_Error
|
||||
import Standard.Base.Errors.Common.No_Such_Method
|
||||
|
||||
import project.Data.Complex.Complex
|
||||
|
||||
type A
|
||||
A value
|
||||
a self = "a"
|
||||
type B
|
||||
B value
|
||||
b self = "b"
|
||||
type C
|
||||
C value
|
||||
c self = "c"
|
||||
|
||||
A.from (that:Float) = A.A that
|
||||
B.from (that:Float) = B.B that
|
||||
C.from (that:Float) = C.C that
|
||||
|
||||
type U
|
||||
U value
|
||||
u self = "u"
|
||||
type V
|
||||
V v
|
||||
|
||||
U.from (that:B) = U.U that.value
|
||||
V.from (that:C) = V.V that.value
|
||||
|
||||
add_specs suite_builder =
|
||||
suite_builder.group "Convert Multi Value" group_builder->
|
||||
|
||||
group_builder.specify "performs conversion to U&V from B and C" <|
|
||||
abc = 3.14 : A&B&C
|
||||
uv = abc : U&V
|
||||
|
||||
uv.u . should_equal "u"
|
||||
uv.v . should_equal 3.14
|
||||
|
||||
Test.expect_panic No_Such_Method <| uv.a
|
||||
Test.expect_panic No_Such_Method <| uv.b
|
||||
Test.expect_panic No_Such_Method <| uv.c
|
||||
Test.expect_panic Type_Error (uv:A)
|
||||
Test.expect_panic Type_Error (uv:B)
|
||||
Test.expect_panic Type_Error (uv:C)
|
||||
|
||||
uv:U . u . should_equal "u"
|
||||
uv:V . v . should_equal 3.14
|
||||
|
||||
Test.expect_panic No_Such_Method <| (uv:V).u
|
||||
Test.expect_panic No_Such_Method <| (uv:U).v
|
||||
|
||||
group_builder.specify "fails conversion to U&V from A and C as B is missing" <|
|
||||
ac = 3.14 : A&C
|
||||
Test.expect_panic Type_Error <| ac:U&V
|
||||
Test.expect_panic Type_Error <| ac:U
|
||||
ac:V . v . should_equal 3.14
|
||||
|
||||
group_builder.specify "case of first" <|
|
||||
abc = 3.14 : A&B&C
|
||||
c1 = case abc of
|
||||
a:A -> a.a
|
||||
b:B -> b.b
|
||||
c:C -> c.c
|
||||
_ -> "what?"
|
||||
c1 . should_equal "a"
|
||||
|
||||
group_builder.specify "case of second downcasted" <|
|
||||
abc = 3.14 : A&B&C
|
||||
c1 = case abc:B of
|
||||
b:B -> b.b
|
||||
a:A -> a.a
|
||||
c:C -> c.c
|
||||
_ -> "what?"
|
||||
c1 . should_equal "b"
|
||||
|
||||
group_builder.specify "case of third downcasted" <|
|
||||
abc = 3.14 : A&B&C
|
||||
c1 = case abc:C of
|
||||
c:C -> c.c
|
||||
b:B -> b.b
|
||||
a:A -> a.a
|
||||
_ -> "what?"
|
||||
c1 . should_equal "c"
|
||||
|
||||
suite_builder.group "Equals and hash" group_builder->
|
||||
group_builder.specify "Dictionary with value and multi value" <|
|
||||
pi = 3.14
|
||||
a = pi : A
|
||||
b = pi : B
|
||||
c = pi : C
|
||||
abc = pi : A&B&C
|
||||
downcast_a = abc : A
|
||||
downcast_ab = abc : A&B
|
||||
downcast_ba = abc : B&A
|
||||
downcast_b = abc : B
|
||||
downcast_c = abc : C
|
||||
|
||||
Ordering.compare a b . catch Any e->
|
||||
e.should_equal (Standard.Base.Errors.Common.Incomparable_Values.Error a b)
|
||||
|
||||
Ordering.compare a downcast_a . should_equal Ordering.Equal
|
||||
Ordering.compare a downcast_ab . should_equal Ordering.Equal
|
||||
Ordering.compare a abc . should_equal Ordering.Equal
|
||||
|
||||
Ordering.compare a downcast_ba . should_equal Ordering.Equal
|
||||
Ordering.compare downcast_ba b . should_equal Ordering.Equal
|
||||
# if a == downcast_ba && downcast_ba == b then # due to transitivity
|
||||
# Ordering.compare a b . should_equal Ordering.Equal
|
||||
|
||||
dict = Dictionary.empty
|
||||
. insert a "A"
|
||||
. insert b "B"
|
||||
. insert c "C"
|
||||
. insert downcast_a "A_"
|
||||
. insert downcast_b "B_"
|
||||
. insert downcast_c "C_"
|
||||
. insert abc "Multi"
|
||||
|
||||
# dict . get a . should_equal "A" # sometimes it is A and sometimes it is Multi
|
||||
# dict . get b . should_equal "B"
|
||||
# dict . get c . should_equal "C" # sometimes it is C and sometimes it is Multi
|
||||
dict . get downcast_a . should_equal "Multi"
|
||||
dict . get downcast_b . should_equal "Multi"
|
||||
dict . get downcast_c . should_equal "Multi"
|
||||
dict . get abc . should_equal "Multi"
|
||||
|
||||
main filter=Nothing =
|
||||
suite = Test.build suite_builder->
|
||||
add_specs suite_builder
|
||||
suite.run_with_filter filter
|
@ -1,9 +1,30 @@
|
||||
from Standard.Base import all
|
||||
from Standard.Test import all
|
||||
import Standard.Base.Errors.Common.Type_Error
|
||||
import Standard.Base.Errors.Common.No_Such_Method
|
||||
|
||||
import project.Data.Complex.Complex
|
||||
|
||||
type A
|
||||
a = "a"
|
||||
type B
|
||||
B v
|
||||
|
||||
b self = "b"
|
||||
|
||||
type C
|
||||
C v
|
||||
|
||||
c self = "c"
|
||||
|
||||
B.from (that:A) = B.B that
|
||||
C.from (that:B) = C.C that
|
||||
|
||||
type X
|
||||
x = "x"
|
||||
|
||||
X.from (_:A) = X
|
||||
|
||||
add_specs suite_builder =
|
||||
suite_builder.group "Complex Multi Value" group_builder->
|
||||
group_builder.specify "Cannot convert to Float if it has imaginary part" <|
|
||||
@ -14,6 +35,116 @@ add_specs suite_builder =
|
||||
(c:Complex).re . should_equal 1.5
|
||||
(c:Float) . should_equal 1.5
|
||||
|
||||
suite_builder.group "Chain Multi Value" group_builder->
|
||||
to_b_to_c obj =
|
||||
b = obj : B
|
||||
c = b : C
|
||||
c
|
||||
|
||||
group_builder.specify "Multiple conversions of A work" <|
|
||||
a = A
|
||||
converted = to_b_to_c a
|
||||
converted . to_text . should_equal "(C (B A))"
|
||||
|
||||
group_builder.specify "Multiple conversions of A & X work" <|
|
||||
ax = A:A&X
|
||||
converted = to_b_to_c ax
|
||||
converted . to_text . should_equal "(C (B A & X))"
|
||||
|
||||
group_builder.specify "Multiple conversions of A & X work" <|
|
||||
xa = A:X&A
|
||||
converted = to_b_to_c xa
|
||||
converted . to_text . should_equal "(C (B A & X))"
|
||||
|
||||
group_builder.specify "Multiple conversions of (A & X : A) work" <|
|
||||
ax = A:A&X
|
||||
a = ax : A
|
||||
converted = to_b_to_c a
|
||||
converted . to_text . should_equal "(C (B A & X))"
|
||||
|
||||
group_builder.specify "Multiple conversions of X fail" <|
|
||||
x = X
|
||||
|
||||
# has to fail as X cannot be converted to B in to_b_to_c
|
||||
Test.expect_panic Type_Error <|
|
||||
to_b_to_c x
|
||||
|
||||
group_builder.specify "Multiple conversions of (A & X : X) work" <|
|
||||
ax = A:A&X
|
||||
|
||||
ax.a . should_equal "a"
|
||||
ax.x . should_equal "x"
|
||||
|
||||
x = ax : X
|
||||
|
||||
x.x . should_equal "x"
|
||||
Test.expect_panic No_Such_Method <|
|
||||
# method of A isn't visible for direct dispatch
|
||||
x.a
|
||||
|
||||
# but the object can be converted to A and then
|
||||
# method a is available
|
||||
(x:A).a . should_equal "a"
|
||||
|
||||
call_a obj:A = obj.a
|
||||
|
||||
# A&X type has attribute a
|
||||
call_a ax . should_equal "a"
|
||||
|
||||
# according to "static typing" discussion at
|
||||
# https://github.com/enso-org/enso/pull/11600#discussion_r1867584107
|
||||
# we want the same `call_a` with `x` to fail
|
||||
Test.expect_panic Type_Error <|
|
||||
call_a x . should_equal "a"
|
||||
|
||||
# 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
|
||||
|
||||
group_builder.specify "Intersection type of unrelated types is not possible" <|
|
||||
Test.expect_panic Type_Error <|
|
||||
_ = X:X&B
|
||||
Test.expect_panic Type_Error <|
|
||||
_ = B:X&B
|
||||
Test.expect_panic Type_Error <|
|
||||
_ = X:B&X
|
||||
Test.expect_panic Type_Error <|
|
||||
_ = B:B&X
|
||||
|
||||
group_builder.specify "X:(A|X) --> B is not possible" <|
|
||||
a = X:(A|X)
|
||||
Test.expect_panic Type_Error <|
|
||||
_ = a : B
|
||||
|
||||
group_builder.specify "A:(A|X) --> B is possible" <|
|
||||
a = A:(A|X)
|
||||
b = a : B
|
||||
b.to_text . should_equal "(B A)"
|
||||
|
||||
# This test is failing
|
||||
group_builder.specify "A:(A|B) --> (A&B) is possible" <|
|
||||
a = A:(A|B)
|
||||
both = a : (A&B)
|
||||
both.a . should_equal "a"
|
||||
both.b . should_equal "b"
|
||||
|
||||
group_builder.specify "B:(A|B) --> (A&B) is not possible" <|
|
||||
b = B.B 42
|
||||
a_or_b = b : (A|B)
|
||||
Test.expect_panic Type_Error <|
|
||||
_ = a_or_b : (A&B)
|
||||
|
||||
# This test is failing
|
||||
group_builder.specify "B --> (A|C) is possible" <|
|
||||
b = B.B 42
|
||||
a_or_c = b : (A|C)
|
||||
a_or_c.c . should_equal "c"
|
||||
|
||||
group_builder.specify "no transitive conversions" <|
|
||||
a = A
|
||||
Test.expect_panic Type_Error <|
|
||||
a : C
|
||||
|
||||
main filter=Nothing =
|
||||
suite = Test.build suite_builder->
|
||||
add_specs suite_builder
|
||||
|
Loading…
Reference in New Issue
Block a user