Skip to content

Commit

Permalink
Introducing generic Any.to type conversion method (#7704)
Browse files Browse the repository at this point in the history
  • Loading branch information
JaroslavTulach authored Sep 1, 2023
1 parent 5468951 commit 1437a67
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 52 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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

Expand Down
45 changes: 45 additions & 0 deletions distribution/lib/Standard/Base/0.0.0-dev/src/Any.enso
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,7 @@ Object doConvertFrom(
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,
Expand Down Expand Up @@ -95,7 +92,7 @@ Object doDataflowError(
@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) {
Expand Down Expand Up @@ -184,7 +181,7 @@ Object doConvertText(
Function function =
conversionResolverNode.expectNonNull(
txt,
InvokeConversionNode.extractConstructor(this, self),
InvokeConversionNode.extractType(this, self),
EnsoContext.get(this).getBuiltins().text(),
conversion);
arguments[0] = txt;
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -22,10 +14,25 @@
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;
Expand Down Expand Up @@ -79,18 +86,18 @@ public abstract Object execute(
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)"})
Expand All @@ -102,11 +109,15 @@ Object doConvertFrom(
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);
return invokeFunctionNode.execute(function, frame, state, arguments);
@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
Expand All @@ -120,8 +131,7 @@ Object doDataflowError(
@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) {
Expand Down Expand Up @@ -197,9 +207,8 @@ Object doConvertText(
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;
Expand Down Expand Up @@ -227,8 +236,7 @@ Object doConvertDate(
@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);
}

Expand All @@ -250,9 +258,8 @@ Object doConvertTime(
@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);
Expand All @@ -276,9 +283,8 @@ Object doConvertDateTime(
@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);
Expand All @@ -301,9 +307,8 @@ Object doConvertDuration(
@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);
Expand All @@ -326,9 +331,8 @@ Object doConvertMap(
@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);
Expand All @@ -352,8 +356,7 @@ Object doFallback(
@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);
Expand Down
28 changes: 26 additions & 2 deletions test/Tests/src/Semantic/Conversion_Spec.enso
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
6 changes: 6 additions & 0 deletions test/Tests/src/Semantic/Conversion_Use.enso
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit 1437a67

Please sign in to comment.