Introducing generic Any.to type conversion method (#7704)

This commit is contained in:
Jaroslav Tulach 2023-09-01 08:05:48 +02:00 committed by GitHub
parent 54689510a4
commit 1437a671e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 129 additions and 52 deletions

View File

@ -560,6 +560,7 @@
- [Expose `Text.normalize`.][7425]
- [Implemented new value types (various sizes of `Integer` type, fixed-length
and length-limited `Char` type) for the in-memory `Table` backend.][7557]
- [Introducing generic `Any.to` conversion method][7704]
- [Added `take` and `drop` to database tables.][7615]
- [Added ability to specify expected value type in `Column.from_vector`,
`Column.map` and `Column.zip`.][7637]
@ -797,6 +798,7 @@
[7297]: https://github.com/enso-org/enso/pull/7297
[7425]: https://github.com/enso-org/enso/pull/7425
[7557]: https://github.com/enso-org/enso/pull/7557
[7704]: https://github.com/enso-org/enso/pull/7704
[7615]: https://github.com/enso-org/enso/pull/7615
[7637]: https://github.com/enso-org/enso/pull/7637

View File

@ -20,6 +20,51 @@ from project.Function import const
be used in that position.
@Builtin_Type
type Any
## GROUP Conversions
Generic conversion of an arbitrary Enso value to requested type.
Delegates to appropriate `.from` conversion method, if it exists.
If such method doesn't exist, `No_Such_Conversion` panic is raised.
Arguments:
- typ: the requested type.
> Example
Following code defines conversion of a `Complex` type to a `Number`
by computing absolute distance from `0`. The code yields `5.0`:
type Complex
Value re:Number im:Number
Number.from (that:Complex) = that.re*that.re+that.im*that.im . sqrt
Complex.Value 3 4 . to Number
> Example
`.from` conversion methods may have additional arguments
with default values. Thus the conversion from `Complex` to
`Number` may take additional argument:
type Complex
Value re:Number im:Number
Number.from (that:Complex) = that.re*that.re+that.im*that.im . sqrt
Complex.Value 3 4 . to Number
type Complex
Value re:Number im:Number
Number.from (that:Complex) (ignore_im:Boolean=False) = case ignore_im of
False -> that.re*that.re+that.im*that.im . sqrt
True -> that.re
yields_3 = Complex.Value 3 4 . to Number ignore_im=True
yields_5 = Complex.Value 3 4 . to Number ignore_im=False
default5 = Complex.Value 3 4 . to Number
to : Any -> Any ! No_Such_Conversion
to self typ = typ.from self ...
## GROUP Conversions
Generic conversion of an arbitrary Enso value to a corresponding textual
representation.

View File

@ -62,10 +62,7 @@ public abstract class IndirectInvokeConversionNode extends Node {
IndirectInvokeFunctionNode indirectInvokeFunctionNode) {
Function function =
conversionResolverNode.expectNonNull(
that,
InvokeConversionNode.extractConstructor(this, self),
typesLib.getType(that),
conversion);
that, InvokeConversionNode.extractType(this, self), typesLib.getType(that), conversion);
return indirectInvokeFunctionNode.execute(
function,
frame,
@ -95,7 +92,7 @@ public abstract class IndirectInvokeConversionNode extends Node {
@Shared("conversionResolverNode") @Cached ConversionResolverNode conversionResolverNode) {
Function function =
conversionResolverNode.execute(
InvokeConversionNode.extractConstructor(this, self),
InvokeConversionNode.extractType(this, self),
EnsoContext.get(this).getBuiltins().dataflowError(),
conversion);
if (function != null) {
@ -184,7 +181,7 @@ public abstract class IndirectInvokeConversionNode extends Node {
Function function =
conversionResolverNode.expectNonNull(
txt,
InvokeConversionNode.extractConstructor(this, self),
InvokeConversionNode.extractType(this, self),
EnsoContext.get(this).getBuiltins().text(),
conversion);
arguments[0] = txt;

View File

@ -1,16 +1,8 @@
package org.enso.interpreter.node.callable;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.dsl.*;
import com.oracle.truffle.api.dsl.Cached.Shared;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.library.CachedLibrary;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.source.SourceSection;
import java.util.UUID;
import java.util.concurrent.locks.Lock;
import org.enso.interpreter.node.BaseNode;
import org.enso.interpreter.node.callable.dispatch.InvokeFunctionNode;
import org.enso.interpreter.node.callable.resolver.ConversionResolverNode;
@ -22,10 +14,25 @@ import org.enso.interpreter.runtime.control.TailCallException;
import org.enso.interpreter.runtime.data.ArrayRope;
import org.enso.interpreter.runtime.data.Type;
import org.enso.interpreter.runtime.data.text.Text;
import org.enso.interpreter.runtime.error.*;
import org.enso.interpreter.runtime.error.DataflowError;
import org.enso.interpreter.runtime.error.PanicException;
import org.enso.interpreter.runtime.error.PanicSentinel;
import org.enso.interpreter.runtime.error.Warning;
import org.enso.interpreter.runtime.error.WithWarnings;
import org.enso.interpreter.runtime.library.dispatch.TypesLibrary;
import org.enso.interpreter.runtime.state.State;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Cached.Shared;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.library.CachedLibrary;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.source.SourceSection;
public abstract class InvokeConversionNode extends BaseNode {
private @Child InvokeFunctionNode invokeFunctionNode;
private @Child InvokeConversionNode childDispatch;
@ -79,18 +86,18 @@ public abstract class InvokeConversionNode extends BaseNode {
Object that,
Object[] arguments);
static Type extractConstructor(Node thisNode, Object self) {
if (self instanceof Type) {
return (Type) self;
static Type extractType(Node thisNode, Object self) {
if (self instanceof Type type) {
return type;
} else {
throw new PanicException(
EnsoContext.get(thisNode).getBuiltins().error().makeInvalidConversionTarget(self),
thisNode);
var ctx = EnsoContext.get(thisNode);
var err = ctx.getBuiltins().error().makeInvalidConversionTarget(self);
throw new PanicException(err, thisNode);
}
}
Type extractConstructor(Object self) {
return extractConstructor(this, self);
private Type extractType(Object self) {
return extractType(this, self);
}
@Specialization(guards = {"dispatch.hasType(that)", "!dispatch.hasSpecialDispatch(that)"})
@ -102,12 +109,16 @@ public abstract class InvokeConversionNode extends BaseNode {
Object that,
Object[] arguments,
@Shared("typesLib") @CachedLibrary(limit = "10") TypesLibrary dispatch,
@Shared("conversionResolverNode") @Cached ConversionResolverNode conversionResolverNode) {
Function function =
conversionResolverNode.expectNonNull(
that, extractConstructor(self), dispatch.getType(that), conversion);
@Shared("conversionResolverNode") @Cached ConversionResolverNode resolveNode) {
var thatType = dispatch.getType(that);
if (thatType == self) {
return that;
} else {
var selfType = extractType(self);
var function = resolveNode.expectNonNull(that, selfType, thatType, conversion);
return invokeFunctionNode.execute(function, frame, state, arguments);
}
}
@Specialization
Object doDataflowError(
@ -120,8 +131,7 @@ public abstract class InvokeConversionNode extends BaseNode {
@Shared("typesLib") @CachedLibrary(limit = "10") TypesLibrary dispatch,
@Shared("conversionResolverNode") @Cached ConversionResolverNode conversionResolverNode) {
Function function =
conversionResolverNode.execute(
extractConstructor(self),
conversionResolverNode.execute(extractType(self),
EnsoContext.get(this).getBuiltins().dataflowError(),
conversion);
if (function != null) {
@ -197,9 +207,8 @@ public abstract class InvokeConversionNode extends BaseNode {
String str = interop.asString(that);
Text txt = Text.create(str);
Function function =
conversionResolverNode.expectNonNull(
txt,
extractConstructor(self),
conversionResolverNode.expectNonNull(txt,
extractType(self),
EnsoContext.get(this).getBuiltins().text(),
conversion);
arguments[0] = txt;
@ -227,8 +236,7 @@ public abstract class InvokeConversionNode extends BaseNode {
@Shared("typesLib") @CachedLibrary(limit = "10") TypesLibrary typesLib,
@Shared("conversionResolverNode") @Cached ConversionResolverNode conversionResolverNode) {
Function function =
conversionResolverNode.expectNonNull(
that, extractConstructor(self), EnsoContext.get(this).getBuiltins().date(), conversion);
conversionResolverNode.expectNonNull(that, extractType(self), EnsoContext.get(this).getBuiltins().date(), conversion);
return invokeFunctionNode.execute(function, frame, state, arguments);
}
@ -250,9 +258,8 @@ public abstract class InvokeConversionNode extends BaseNode {
@Shared("typesLib") @CachedLibrary(limit = "10") TypesLibrary typesLib,
@Shared("conversionResolverNode") @Cached ConversionResolverNode conversionResolverNode) {
Function function =
conversionResolverNode.expectNonNull(
that,
extractConstructor(self),
conversionResolverNode.expectNonNull(that,
extractType(self),
EnsoContext.get(this).getBuiltins().timeOfDay(),
conversion);
return invokeFunctionNode.execute(function, frame, state, arguments);
@ -276,9 +283,8 @@ public abstract class InvokeConversionNode extends BaseNode {
@Shared("typesLib") @CachedLibrary(limit = "10") TypesLibrary typesLib,
@Shared("conversionResolverNode") @Cached ConversionResolverNode conversionResolverNode) {
Function function =
conversionResolverNode.expectNonNull(
that,
extractConstructor(self),
conversionResolverNode.expectNonNull(that,
extractType(self),
EnsoContext.get(this).getBuiltins().dateTime(),
conversion);
return invokeFunctionNode.execute(function, frame, state, arguments);
@ -301,9 +307,8 @@ public abstract class InvokeConversionNode extends BaseNode {
@Shared("typesLib") @CachedLibrary(limit = "10") TypesLibrary typesLib,
@Shared("conversionResolverNode") @Cached ConversionResolverNode conversionResolverNode) {
Function function =
conversionResolverNode.expectNonNull(
that,
extractConstructor(self),
conversionResolverNode.expectNonNull(that,
extractType(self),
EnsoContext.get(this).getBuiltins().duration(),
conversion);
return invokeFunctionNode.execute(function, frame, state, arguments);
@ -326,9 +331,8 @@ public abstract class InvokeConversionNode extends BaseNode {
@Shared("typesLib") @CachedLibrary(limit = "10") TypesLibrary typesLib,
@Shared("conversionResolverNode") @Cached ConversionResolverNode conversionResolverNode) {
Function function =
conversionResolverNode.expectNonNull(
thatMap,
extractConstructor(self),
conversionResolverNode.expectNonNull(thatMap,
extractType(self),
EnsoContext.get(this).getBuiltins().map(),
conversion);
return invokeFunctionNode.execute(function, frame, state, arguments);
@ -352,8 +356,7 @@ public abstract class InvokeConversionNode extends BaseNode {
@Shared("conversionResolverNode") @Cached ConversionResolverNode conversionResolverNode) {
var ctx = EnsoContext.get(this);
var function =
conversionResolverNode.execute(
extractConstructor(self), ctx.getBuiltins().any(), conversion);
conversionResolverNode.execute(extractType(self), ctx.getBuiltins().any(), conversion);
if (function == null) {
throw new PanicException(
ctx.getBuiltins().error().makeNoSuchConversion(self, that, conversion), this);

View File

@ -1,4 +1,5 @@
from Standard.Base import all
import Standard.Base.Errors.Common.No_Such_Conversion
import project.Semantic.Conversion.Methods
import project.Semantic.Conversion.Types
@ -120,7 +121,30 @@ spec =
Hello.formulate [ Hello.Say "Proper", Hello.Say "Type" ] . should_equal "ProperType"
Hello.formulate [ Foo.Value "Perform", Bar.Value "Conversion" ] . should_equal "PERFORM conversion!"
Hello.from (that:Foo) = Hello.Say <| (that.foo.to_case Case.Upper) + " "
Hello.from (that:Bar) = Hello.Say <| (that.bar.to_case Case.Lower) + "!"
Test.specify "Convert Foo.to Hello" <|
hello = Foo.Value "Perform" . to Hello
hello . msg . should_equal "PERFORM "
Test.specify "Convert Bar.to Hello" <|
hello = Bar.Value "Conversion" . to Hello
hello . msg . should_equal "conversion!"
Test.specify "Convert Bar.to Hello with other suffix" <|
hello = Bar.Value "Conversion" . to Hello suffix="?"
hello . msg . should_equal "conversion?"
Test.specify "Idempotent convert Hello.to Hello" <|
Hello.Say "Hi there!" . to Hello . msg . should_equal "Hi there!"
Test.specify "Unknown convertion Text.to Hello" <|
h = Panic.recover No_Such_Conversion <| "Hi there!" . to Hello
h . should_fail_with No_Such_Conversion
Test.specify "Use Any.to in Conversion_Use module" <|
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!"
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
main = Test_Suite.run_main spec

View File

@ -7,3 +7,9 @@ type Hello
formulate arr =
process (t:Text) (h:Hello) = t + h.msg
arr.fold "" process
formulate_with_to : Vector Hello -> Text
formulate_with_to arr =
arr.fold "" t-> h->
m = h.to Hello . msg
t + m