diff --git a/CHANGELOG.md b/CHANGELOG.md index 88066366fb4..8def13a6e82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1059,6 +1059,7 @@ - [Add run_google_report method][8907] - [Execute and debug individual Enso files in VSCode extension][8923] - [Check type of `self` when calling a method using the static syntax][8867] +- [Autoscoped constructors][9190] [3227]: https://github.com/enso-org/enso/pull/3227 [3248]: https://github.com/enso-org/enso/pull/3248 @@ -1217,6 +1218,7 @@ [8907]: https://github.com/enso-org/enso/pull/8907 [8923]: https://github.com/enso-org/enso/pull/8923 [8867]: https://github.com/enso-org/enso/pull/8867 +[9190]: https://github.com/enso-org/enso/pull/9190 # Enso 2.0.0-alpha.18 (2021-10-12) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Errors/Common.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Errors/Common.enso index 6fec7ad6301..e9bc69e44e0 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Errors/Common.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Errors/Common.enso @@ -79,7 +79,15 @@ type Type_Error _ -> ". Try to apply " + (missing_args.join ", ") + " arguments" _ -> tpe.to_text - "Type error: expected "+self.comment+" to be "+self.expected.to_display_text+", but got "+(if type_of_actual.is_a Text then type_of_actual else "")+"." + got = if type_of_actual.is_a Text then type_of_actual else "" + exp = self.expected.to_display_text + msg = self.comment + . replace "{exp}" exp + . replace "{got}" got + "Type error: "+msg+"." + + to_text : Text + to_text self = self.to_display_text @Builtin_Type type Compile_Error diff --git a/docs/runtime-roadmap.md b/docs/runtime-roadmap.md index a0285026e9f..97229428d01 100644 --- a/docs/runtime-roadmap.md +++ b/docs/runtime-roadmap.md @@ -14,20 +14,11 @@ priority, but the dependencies between tasks are described. ## Technology Choices -With the advent of Java 17 and its ergonomic improvements (read: -pattern-matching), it makes little sense to retain the usage of Scala throughout -the compiler. The language was originally introduced due to the capabilities of -its type system in comparison to Java's, but very little of this functionality -has been used in the end. - -We recommend moving everything to Java as part of this work, as you will end up -with better tooling support. Scala has been a problem child. - -Enso originally started working with Java 8, and was transitioned (painfully, -due to the JPMS) to Java 11. Java 8 was EOL'd by the graal team after a couple -of years. It seems likely that Java 11 will suffer a similar fate, though the -transition from 11 to 17 will be far less painful as it doesn't introduce any -breaking language-level changes. +Enso interpreter is written in a mixture of Scala and Java. Scala was originally +used due to the capabilities of its type system in comparison to Java's. Modern +Java (as provided by JDK 21 or [Frgaal compiler](http://frgaal.org)) meets most +of the needs too. The ultimate goal is to write everything in Java and also keep +up with most recent long term supported JDK/GraalVM releases. ## Static Analysis @@ -49,7 +40,8 @@ Currently, the IR is: - Very verbose and difficult to add a new node to. Adding a new node requires adding ~100 lines of code that could likely be automated away. Lots of boilerplate. -- Of unknown performance. +- Of poor performance as witnessed by + [static compiler benchmarks](https://github.com/enso-org/enso/pull/9158) - Partially mutable, making it confusing as to which things are shared. A new IR for Enso would have to: @@ -252,25 +244,6 @@ To rectify this situation, we recommend implementing a system we have termed With this done, it may still be necessary to create a Java DSL for implementing built-in methods and types, but that is unclear at this point. -### Static Methods on Types - -Currently, Enso allows calling methods on _modules_, _constructors_, and -_instances_. This does not conform to the language specification because it -allows constructors and instances to be treated the same at runtime. This leads -to odd results (see the ticket below). - -The end result should be compliant with the design described -[here](https://github.com/enso-org/enso/issues/1851), and needs to be taken into -account when defining builtins. - -### Better Safepointing - -Enso currently uses a hand-rolled safepointing system for interrupting threads -and handling resource finalisation. With 21.1, Truffle landed its own system for -doing this. Enso should be updated to use -[the new system](https://github.com/oracle/graal/blob/master/truffle/docs/Safepoints.md), -instead, as it will provide better performance and more robust operation. - ## Runtime Performance While Enso is performant when it gets JITted by GraalVM, the performance when @@ -290,29 +263,6 @@ This can be greatly improved. be improved. - Many of the above-listed static optimisations will greatly help here. -### Unboxed Atoms - -Currently every atom in Enso is stored boxed. In limited circumstances it may be -possible to unbox these and hence remove the indirection cost when accessing -their data. - -- Read the details of Truffle's - [`DynamicObject`](https://www.graalvm.org/truffle/javadoc/com/oracle/truffle/api/object/DynamicObject.html), - and the sources. -- Use this system to inform the design for a system that reduces the overhead of - dynamic field names and arities when accessing data on Atoms. - -### Unboxed Vectors - -Enso currently doesn't have support for unboxed arrays (and hence vectors). This -means that it incurs a significant performance cost when working with pure -numerical arrays. This can be improved. - -- Read the truffle documentation on - [truffle libraries](https://github.com/oracle/graal/blob/master/truffle/docs/TruffleLibraries.md). -- Based on this, define a system that seamlessly specializes and deoptimises - between boxed and unboxed arrays as necessary. - ## IDE As Enso's primary mode of use is in the IDE, there are a number of important @@ -362,7 +312,3 @@ preprocessors. - Implement caching support for the visualization expression processing. - This cache should, much like the IDE's introspection cache, track and save the values of all top-level bindings in the visualization preprocessor. - -## Parser - -Parser diff --git a/docs/syntax/functions.md b/docs/syntax/functions.md index 7554d395871..a043703d4b4 100644 --- a/docs/syntax/functions.md +++ b/docs/syntax/functions.md @@ -87,7 +87,7 @@ binds the function name. This means that: user-defined type for the function. ```ruby - sum : (a: Monoid) -> a -> a + sum : (a:Monoid) -> a -> a sum : x -> y -> x + y sum x y = x + y ``` @@ -113,11 +113,11 @@ Methods can be defined in Enso in two ways: ```ruby type Maybe a Nothing - type Just (value : a) + Just (value : a) - isJust = case this of - Nothing -> False - Just _ -> True + is_just self = case self of + Maybe.Nothing -> False + Maybe.Just _ -> True ``` 2. **As an Extension Method:** A function defined _explicitly_ on an atom counts @@ -125,7 +125,7 @@ type Maybe a to all the atoms within that typeset. ```ruby -Number.floor = case this of +Number.floor self = case self of Integer -> ... ... ``` diff --git a/docs/syntax/types.md b/docs/syntax/types.md index d0379c1e0c4..d8f5f6abb01 100644 --- a/docs/syntax/types.md +++ b/docs/syntax/types.md @@ -276,32 +276,48 @@ context-dependent manner that is discussed properly in the [type system design document](../types/README.md), but is summarised briefly below. -- **Name and Fields:** When you provide the keyword with only a name and some - field names, this creates an atom. - - ```ruby - type Just value - ``` - - **Body with Atom Definitions:** If you provide a body with atom definitions, this defines a smart constructor that defines the atoms and related functions by returning a typeset. ```ruby - type Maybe a + type Maybe Nothing - type Just (value : a) + Just (value : Integer) - isJust = case this of - Nothing -> False - Just _ -> True + is_just self = case self of + Maybe.Nothing -> False + Maybe.Just _ -> True - nothing = not isJust + nothing self = self.is_just.not ``` - Please note that the `type Foo (a : t)` is syntax only allowable inside a type - definition. It defines an atom `Foo`, but constrains the type variable of the - atom _in this usage_. + To reference atoms use type name followed by the name of the atom. E.g. + `Maybe.Nothing` or `Maybe.Just 2`. Atom constructors act like functions and + fully support currying - e.g. one can create `fn = Maybe.Just` and later apply + two to it (`fn 2`) to obtain new atom. + +- **Autoscoped Constructors:** Referencing constructors via their type name may + lead to long and boilerplate code. To simplify referencing constructors when + the _context is known_ a special `~` syntax is supported. Should there be a + method `describe`: + + ```ruby + describe (m : Maybe) -> Text = if m.is_just then m.value.to_text else "Empty" + ``` + + one may invoke it as `describe (Maybe.Just 5)` - e.g. the regular way. Or one + may use _autoscoped constructors_ and call + + ```ruby + describe (~Just 5) + ``` + + the argument `(~Just 5)` is _recorded but not executed_ until it is send to + the `describe` method. The argument of the `describe` method is known to be of + type `Maybe` and have `Just` constructor. The _scope_ is now known and the so + far deferred `~` value gets evaluated. `Maybe.Just 5` atom is constructed and + execution of `describe` method continues with such atom. - **Body Without Atom Definitions:** If you provide a body and do not define any atoms within it, this creates an interface that asserts no atoms as part of diff --git a/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/ArrayProxyBenchmarks.java b/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/ArrayProxyBenchmarks.java index c3f7341191c..c8d86cecc48 100644 --- a/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/ArrayProxyBenchmarks.java +++ b/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/ArrayProxyBenchmarks.java @@ -1,15 +1,18 @@ package org.enso.interpreter.bench.benchmarks.semantic; -import java.nio.file.Paths; import java.util.concurrent.TimeUnit; import java.util.function.Function; -import java.util.logging.Level; -import org.enso.polyglot.RuntimeOptions; -import org.graalvm.polyglot.Context; -import org.graalvm.polyglot.Engine; import org.graalvm.polyglot.Value; -import org.graalvm.polyglot.io.IOAccess; -import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; import org.openjdk.jmh.infra.BenchmarkParams; import org.openjdk.jmh.infra.Blackhole; @@ -33,16 +36,7 @@ public class ArrayProxyBenchmarks { @Setup public void initializeBenchmark(BenchmarkParams params) throws Exception { - Engine eng = - Engine.newBuilder() - .allowExperimentalOptions(true) - .option(RuntimeOptions.LOG_LEVEL, Level.WARNING.getName()) - .logHandler(System.err) - .option( - "enso.languageHomeOverride", - Paths.get("../../distribution/component").toFile().getAbsolutePath()) - .build(); - var ctx = Context.newBuilder().engine(eng).allowIO(IOAccess.ALL).allowAllAccess(true).build(); + var ctx = SrcUtil.newContextBuilder().build(); var code = """ import Standard.Base.Data.Vector.Vector diff --git a/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/AtomBenchmarks.java b/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/AtomBenchmarks.java index 33078338262..d55cb50fb18 100644 --- a/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/AtomBenchmarks.java +++ b/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/AtomBenchmarks.java @@ -1,13 +1,9 @@ package org.enso.interpreter.bench.benchmarks.semantic; -import java.nio.file.Paths; +import java.io.IOException; import java.util.concurrent.TimeUnit; -import java.util.logging.Level; -import org.enso.interpreter.bench.Utils; -import org.enso.polyglot.RuntimeOptions; import org.graalvm.polyglot.Context; import org.graalvm.polyglot.Value; -import org.graalvm.polyglot.io.IOAccess; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; @@ -59,9 +55,28 @@ public class AtomBenchmarks { import Standard.Base.Data.Numbers main = length -> - generator = acc -> i -> if i == 0 then acc else @Tail_Call generator (List.Cons i acc) (i - 1) + qualified_generator = acc -> i -> if i == 0 then acc else + @Tail_Call qualified_generator (List.Cons i acc) (i - 1) - res = generator List.Nil length + res = qualified_generator List.Nil length + res + """; + + private static final String GENERATE_LIST_AUTOSCOPING_CODE = + """ + import Standard.Base.Data.List.List + import Standard.Base.Data.Numbers + import Standard.Base.Data.Numbers.Integer + + main = length -> + autoscoped_generator x i:Integer = + acc = x:List + if i == 0 then acc else + c = ~Cons i acc + i1 = i - 1 + @Tail_Call autoscoped_generator c i1 + + res = autoscoped_generator ~Nil length res """; private static final String REVERSE_LIST_CODE = @@ -174,6 +189,7 @@ public class AtomBenchmarks { private Value millionElementsList; private Value generateList; private Value generateListQualified; + private Value generateListAutoscoping; private Value reverseList; private Value reverseListMethods; private Value sumList; @@ -184,31 +200,54 @@ public class AtomBenchmarks { private Value mapReverseListCurry; @Setup - public void initializeBenchmarks(BenchmarkParams params) { - this.context = - Context.newBuilder() - .allowExperimentalOptions(true) - .option(RuntimeOptions.LOG_LEVEL, Level.WARNING.getName()) - .logHandler(System.err) - .allowIO(IOAccess.ALL) - .allowAllAccess(true) - .option( - RuntimeOptions.LANGUAGE_HOME_OVERRIDE, - Paths.get("../../distribution/component").toFile().getAbsolutePath()) - .build(); + public void initializeBenchmarks(BenchmarkParams params) throws IOException { + this.context = SrcUtil.newContextBuilder().build(); - var millionElemListMethod = Utils.getMainMethod(context, MILLION_ELEMENT_LIST); + var millionElemListMethod = mainMethod(context, "millionElementList", MILLION_ELEMENT_LIST); this.millionElementsList = millionElemListMethod.execute(); - this.generateList = Utils.getMainMethod(context, GENERATE_LIST_CODE); - this.generateListQualified = Utils.getMainMethod(context, GENERATE_LIST_QUALIFIED_CODE); - this.reverseList = Utils.getMainMethod(context, REVERSE_LIST_CODE); - this.reverseListMethods = Utils.getMainMethod(context, REVERSE_LIST_METHODS_CODE); - this.sumList = Utils.getMainMethod(context, SUM_LIST_CODE); - this.sumListLeftFold = Utils.getMainMethod(context, SUM_LIST_LEFT_FOLD_CODE); - this.sumListFallback = Utils.getMainMethod(context, SUM_LIST_FALLBACK_CODE); - this.sumListMethods = Utils.getMainMethod(context, SUM_LIST_METHODS_CODE); - this.mapReverseList = Utils.getMainMethod(context, MAP_REVERSE_LIST_CODE); - this.mapReverseListCurry = Utils.getMainMethod(context, MAP_REVERSE_LIST_CURRY_CODE); + + var lastDot = params.getBenchmark().lastIndexOf('.'); + var name = params.getBenchmark().substring(lastDot + 1); + switch (name) { + case "benchGenerateList" -> { + this.generateList = mainMethod(context, name, GENERATE_LIST_CODE); + } + case "benchGenerateListQualified" -> { + this.generateListQualified = mainMethod(context, name, GENERATE_LIST_QUALIFIED_CODE); + } + case "benchGenerateListAutoscoping" -> { + this.generateListAutoscoping = mainMethod(context, name, GENERATE_LIST_AUTOSCOPING_CODE); + } + case "benchReverseList" -> { + this.reverseList = mainMethod(context, name, REVERSE_LIST_CODE); + } + case "benchReverseListMethods" -> { + this.reverseListMethods = mainMethod(context, name, REVERSE_LIST_METHODS_CODE); + } + case "benchSumList" -> { + this.sumList = mainMethod(context, name, SUM_LIST_CODE); + } + case "benchSumListLeftFold" -> { + this.sumListLeftFold = mainMethod(context, name, SUM_LIST_LEFT_FOLD_CODE); + } + case "benchSumListFallback" -> { + this.sumListFallback = mainMethod(context, name, SUM_LIST_FALLBACK_CODE); + } + case "benchSumListMethods" -> { + this.sumListMethods = mainMethod(context, name, SUM_LIST_METHODS_CODE); + } + case "benchMapReverseList" -> { + this.mapReverseList = mainMethod(context, name, MAP_REVERSE_LIST_CODE); + } + case "benchMapReverseListCurry" -> { + this.mapReverseListCurry = mainMethod(context, name, MAP_REVERSE_LIST_CURRY_CODE); + } + default -> throw new IllegalArgumentException(name); + } + } + + private static Value mainMethod(Context context, String name, String code) throws IOException { + return SrcUtil.getMainMethod(context, name, code); } @Benchmark @@ -223,6 +262,12 @@ public class AtomBenchmarks { bh.consume(res); } + @Benchmark + public void benchGenerateListAutoscoping(Blackhole bh) { + var res = generateListAutoscoping.execute(MILLION); + bh.consume(res); + } + @Benchmark public void benchReverseList(Blackhole bh) { var reversedList = reverseList.execute(millionElementsList); @@ -245,7 +290,7 @@ public class AtomBenchmarks { } @Benchmark - public void sumListLeftFold(Blackhole bh) { + public void benchSumListLeftFold(Blackhole bh) { var res = sumListLeftFold.execute(millionElementsList); if (!res.fitsInLong()) { throw new AssertionError("Should return a number"); @@ -278,7 +323,7 @@ public class AtomBenchmarks { } @Benchmark - public void benchMapReverseCurryList(Blackhole bh) { + public void benchMapReverseListCurry(Blackhole bh) { var res = mapReverseListCurry.execute(millionElementsList); bh.consume(res); } diff --git a/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/CallableBenchmarks.java b/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/CallableBenchmarks.java index 1822a62ec77..92a145b7460 100644 --- a/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/CallableBenchmarks.java +++ b/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/CallableBenchmarks.java @@ -1,13 +1,9 @@ package org.enso.interpreter.bench.benchmarks.semantic; -import java.nio.file.Paths; import java.util.concurrent.TimeUnit; -import java.util.logging.Level; import org.enso.interpreter.bench.Utils; -import org.enso.polyglot.RuntimeOptions; import org.graalvm.polyglot.Context; import org.graalvm.polyglot.Value; -import org.graalvm.polyglot.io.IOAccess; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; @@ -81,17 +77,7 @@ main = sumTo -> @Setup public void initializeBenchmarks(BenchmarkParams params) { - this.context = - Context.newBuilder() - .allowExperimentalOptions(true) - .option(RuntimeOptions.LOG_LEVEL, Level.WARNING.getName()) - .logHandler(System.err) - .allowIO(IOAccess.ALL) - .allowAllAccess(true) - .option( - RuntimeOptions.LANGUAGE_HOME_OVERRIDE, - Paths.get("../../distribution/component").toFile().getAbsolutePath()) - .build(); + this.context = SrcUtil.newContextBuilder().build(); this.sumTCOfromCall = Utils.getMainMethod(context, SUM_TCO_FROM_CALL_CODE); this.sumTCOmethodCall = Utils.getMainMethod(context, SUM_TCO_METHOD_CALL_CODE); diff --git a/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/EqualsBenchmarks.java b/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/EqualsBenchmarks.java index 0b739c96731..2247571dc2f 100644 --- a/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/EqualsBenchmarks.java +++ b/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/EqualsBenchmarks.java @@ -1,6 +1,5 @@ package org.enso.interpreter.bench.benchmarks.semantic; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -8,12 +7,8 @@ import java.util.List; import java.util.Random; import java.util.Set; import java.util.concurrent.TimeUnit; -import java.util.logging.Level; import org.enso.polyglot.MethodNames.Module; -import org.enso.polyglot.RuntimeOptions; -import org.graalvm.polyglot.Context; import org.graalvm.polyglot.Value; -import org.graalvm.polyglot.io.IOAccess; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; @@ -58,17 +53,7 @@ public class EqualsBenchmarks { public void initializeBenchmark(BenchmarkParams params) throws Exception { var random = new Random(42); - var ctx = - Context.newBuilder() - .allowExperimentalOptions(true) - .option(RuntimeOptions.LOG_LEVEL, Level.WARNING.getName()) - .logHandler(System.err) - .allowIO(IOAccess.ALL) - .allowAllAccess(true) - .option( - "enso.languageHomeOverride", - Paths.get("../../distribution/component").toFile().getAbsolutePath()) - .build(); + var ctx = SrcUtil.newContextBuilder().build(); var benchmarkName = SrcUtil.findName(params); var codeBuilder = diff --git a/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/IfVsCaseBenchmarks.java b/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/IfVsCaseBenchmarks.java index edf5671a37d..3a838ae688f 100644 --- a/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/IfVsCaseBenchmarks.java +++ b/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/IfVsCaseBenchmarks.java @@ -3,17 +3,13 @@ package org.enso.interpreter.bench.benchmarks.semantic; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.concurrent.TimeUnit; -import java.util.logging.Level; import org.enso.polyglot.MethodNames.Module; -import org.enso.polyglot.RuntimeOptions; import org.graalvm.polyglot.Context; import org.graalvm.polyglot.Value; -import org.graalvm.polyglot.io.IOAccess; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; @@ -47,21 +43,7 @@ public class IfVsCaseBenchmarks { @Setup public void initializeBench(BenchmarkParams params) throws IOException { OutputStream out = new ByteArrayOutputStream(); - ctx = - Context.newBuilder("enso") - .allowAllAccess(true) - .option(RuntimeOptions.LOG_LEVEL, Level.WARNING.getName()) - .logHandler(System.err) - .out(out) - .err(out) - .allowIO(IOAccess.ALL) - .allowExperimentalOptions(true) - .option( - "enso.languageHomeOverride", - Paths.get("../../distribution/component").toFile().getAbsolutePath()) - .option("engine.MultiTier", "true") - .option("engine.BackgroundCompilation", "true") - .build(); + ctx = SrcUtil.newContextBuilder().out(out).err(out).build(); var code = """ diff --git a/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/ListBenchmarks.java b/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/ListBenchmarks.java index 6740ef60420..cdadda6bcc2 100644 --- a/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/ListBenchmarks.java +++ b/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/ListBenchmarks.java @@ -1,13 +1,8 @@ package org.enso.interpreter.bench.benchmarks.semantic; -import java.nio.file.Paths; import java.util.concurrent.TimeUnit; import java.util.function.Function; -import java.util.logging.Level; -import org.enso.polyglot.RuntimeOptions; -import org.graalvm.polyglot.Context; import org.graalvm.polyglot.Value; -import org.graalvm.polyglot.io.IOAccess; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; @@ -38,17 +33,7 @@ public class ListBenchmarks { @Setup public void initializeBenchmark(BenchmarkParams params) throws Exception { - var ctx = - Context.newBuilder() - .allowExperimentalOptions(true) - .allowIO(IOAccess.ALL) - .allowAllAccess(true) - .option(RuntimeOptions.LOG_LEVEL, Level.WARNING.getName()) - .logHandler(System.err) - .option( - "enso.languageHomeOverride", - Paths.get("../../distribution/component").toFile().getAbsolutePath()) - .build(); + var ctx = SrcUtil.newContextBuilder().build(); var benchmarkName = SrcUtil.findName(params); var code = diff --git a/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/NamedDefaultedArgumentBenchmarks.java b/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/NamedDefaultedArgumentBenchmarks.java index 6eef8b7ca26..bcbba410f2a 100644 --- a/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/NamedDefaultedArgumentBenchmarks.java +++ b/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/NamedDefaultedArgumentBenchmarks.java @@ -1,13 +1,9 @@ package org.enso.interpreter.bench.benchmarks.semantic; -import java.nio.file.Paths; import java.util.concurrent.TimeUnit; -import java.util.logging.Level; import org.enso.interpreter.bench.Utils; -import org.enso.polyglot.RuntimeOptions; import org.graalvm.polyglot.Context; import org.graalvm.polyglot.Value; -import org.graalvm.polyglot.io.IOAccess; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; @@ -57,18 +53,7 @@ main = sumTo -> @Setup public void initializeBenchmarks(BenchmarkParams params) { - this.context = - Context.newBuilder() - .allowExperimentalOptions(true) - .option(RuntimeOptions.LOG_LEVEL, Level.WARNING.getName()) - .logHandler(System.err) - .allowIO(IOAccess.ALL) - .allowAllAccess(true) - .option( - RuntimeOptions.LANGUAGE_HOME_OVERRIDE, - Paths.get("../../distribution/component").toFile().getAbsolutePath()) - .build(); - + this.context = SrcUtil.newContextBuilder().build(); this.sumTCOWithNamedArguments = Utils.getMainMethod(context, SUM_TCO_WITH_NAMED_ARGUMENTS_CODE); this.sumTCOWithDefaultedArguments = Utils.getMainMethod(context, SUM_TCO_WITH_DEFAULTED_ARGUMENTS_CODE); diff --git a/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/NestedPatternCompilationBenchmarks.java b/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/NestedPatternCompilationBenchmarks.java index 94958dae919..ac6cab38501 100644 --- a/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/NestedPatternCompilationBenchmarks.java +++ b/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/NestedPatternCompilationBenchmarks.java @@ -1,14 +1,10 @@ package org.enso.interpreter.bench.benchmarks.semantic; import java.io.IOException; -import java.nio.file.Paths; import java.util.concurrent.TimeUnit; import java.util.function.Function; -import java.util.logging.Level; -import org.enso.polyglot.RuntimeOptions; import org.graalvm.polyglot.Context; import org.graalvm.polyglot.Value; -import org.graalvm.polyglot.io.IOAccess; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; @@ -36,18 +32,7 @@ public class NestedPatternCompilationBenchmarks { @Setup public void initializeBenchmark(BenchmarkParams params) throws Exception { - ctx = - Context.newBuilder() - .allowExperimentalOptions(true) - .allowIO(IOAccess.ALL) - .allowAllAccess(true) - .option(RuntimeOptions.LOG_LEVEL, Level.WARNING.getName()) - .logHandler(System.err) - .option( - "enso.languageHomeOverride", - Paths.get("../../distribution/component").toFile().getAbsolutePath()) - .build(); - + ctx = SrcUtil.newContextBuilder().build(); benchmarkName = SrcUtil.findName(params); code = """ diff --git a/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/RecursionBenchmarks.java b/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/RecursionBenchmarks.java index 77dc8a882f8..860fd27ed19 100644 --- a/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/RecursionBenchmarks.java +++ b/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/RecursionBenchmarks.java @@ -1,14 +1,19 @@ package org.enso.interpreter.bench.benchmarks.semantic; -import java.nio.file.Paths; import java.util.concurrent.TimeUnit; -import java.util.logging.Level; import org.enso.interpreter.bench.Utils; -import org.enso.polyglot.RuntimeOptions; import org.graalvm.polyglot.Context; import org.graalvm.polyglot.Value; -import org.graalvm.polyglot.io.IOAccess; -import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; import org.openjdk.jmh.infra.BenchmarkParams; import org.openjdk.jmh.infra.Blackhole; @@ -114,17 +119,7 @@ main = n -> @Setup public void initializeBenchmarks(BenchmarkParams params) { - this.context = - Context.newBuilder() - .allowExperimentalOptions(true) - .option(RuntimeOptions.LOG_LEVEL, Level.WARNING.getName()) - .logHandler(System.err) - .allowIO(IOAccess.ALL) - .allowAllAccess(true) - .option( - RuntimeOptions.LANGUAGE_HOME_OVERRIDE, - Paths.get("../../distribution/component").toFile().getAbsolutePath()) - .build(); + this.context = SrcUtil.newContextBuilder().build(); this.sumTCO = Utils.getMainMethod(context, SUM_TCO_CODE); this.sumTCOWithEval = Utils.getMainMethod(context, SUM_TCO_WITH_EVAL_CODE); diff --git a/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/SrcUtil.java b/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/SrcUtil.java index 301241f27af..5cc93871214 100644 --- a/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/SrcUtil.java +++ b/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/SrcUtil.java @@ -3,8 +3,15 @@ package org.enso.interpreter.bench.benchmarks.semantic; import java.io.File; import java.io.FileWriter; import java.io.IOException; +import java.nio.file.Paths; import java.util.Objects; +import java.util.logging.Level; +import org.enso.polyglot.MethodNames.Module; +import org.enso.polyglot.RuntimeOptions; +import org.graalvm.polyglot.Context; import org.graalvm.polyglot.Source; +import org.graalvm.polyglot.Value; +import org.graalvm.polyglot.io.IOAccess; import org.openjdk.jmh.infra.BenchmarkParams; final class SrcUtil { @@ -30,4 +37,36 @@ final class SrcUtil { Objects.requireNonNull(url, "Searching for " + resource); return Source.newBuilder("enso", url).name(resource).build(); } + + static Value getMainMethod(Context context, String benchmarkName, String code) + throws IOException { + var src = source(benchmarkName, code); + var module = context.eval(src); + var moduleType = module.invokeMember(Module.GET_ASSOCIATED_TYPE); + var main = module.invokeMember(Module.GET_METHOD, moduleType, "main"); + if (!main.canExecute()) { + throw new AssertionError("Main method should be executable"); + } + return main; + } + + /** + * Typical builder suitable for benchmarking. + * + * @return preconfigured builder to use as a base for benchmarking + */ + static Context.Builder newContextBuilder() { + return Context.newBuilder() + .allowExperimentalOptions(true) + .option(RuntimeOptions.LOG_LEVEL, Level.WARNING.getName()) + .logHandler(System.err) + .allowIO(IOAccess.ALL) + .allowAllAccess(true) + .option("engine.MultiTier", "false") + .option("engine.BackgroundCompilation", "false") + .option("engine.CompilationFailureAction", "Print") + .option( + RuntimeOptions.LANGUAGE_HOME_OVERRIDE, + Paths.get("../../distribution/component").toFile().getAbsolutePath()); + } } diff --git a/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/StringBenchmarks.java b/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/StringBenchmarks.java index a4f2cf1198a..32286fca8d7 100644 --- a/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/StringBenchmarks.java +++ b/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/StringBenchmarks.java @@ -1,13 +1,8 @@ package org.enso.interpreter.bench.benchmarks.semantic; -import java.nio.file.Paths; import java.util.concurrent.TimeUnit; import java.util.function.Function; -import java.util.logging.Level; -import org.enso.polyglot.RuntimeOptions; -import org.graalvm.polyglot.Context; import org.graalvm.polyglot.Value; -import org.graalvm.polyglot.io.IOAccess; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; @@ -34,18 +29,7 @@ public class StringBenchmarks { @Setup public void initializeBenchmark(BenchmarkParams params) throws Exception { - var ctx = - Context.newBuilder() - .allowExperimentalOptions(true) - .allowIO(IOAccess.ALL) - .allowAllAccess(true) - .option(RuntimeOptions.LOG_LEVEL, Level.WARNING.getName()) - .logHandler(System.err) - .option( - "enso.languageHomeOverride", - Paths.get("../../distribution/component").toFile().getAbsolutePath()) - .build(); - + var ctx = SrcUtil.newContextBuilder().build(); var code = """ from Standard.Base import all diff --git a/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/TypePatternBenchmarks.java b/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/TypePatternBenchmarks.java index 6349119eeb0..c1970ad3eb7 100644 --- a/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/TypePatternBenchmarks.java +++ b/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/TypePatternBenchmarks.java @@ -1,15 +1,19 @@ package org.enso.interpreter.bench.benchmarks.semantic; -import java.nio.file.Paths; import java.util.concurrent.TimeUnit; import java.util.function.Function; -import java.util.logging.Level; import org.enso.polyglot.MethodNames.Module; -import org.enso.polyglot.RuntimeOptions; -import org.graalvm.polyglot.Context; import org.graalvm.polyglot.Value; -import org.graalvm.polyglot.io.IOAccess; -import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; import org.openjdk.jmh.infra.BenchmarkParams; import org.openjdk.jmh.infra.Blackhole; @@ -26,17 +30,7 @@ public class TypePatternBenchmarks { @Setup public void initializeBenchmark(BenchmarkParams params) throws Exception { - var ctx = - Context.newBuilder() - .allowExperimentalOptions(true) - .allowIO(IOAccess.ALL) - .allowAllAccess(true) - .option(RuntimeOptions.LOG_LEVEL, Level.WARNING.getName()) - .logHandler(System.err) - .option( - "enso.languageHomeOverride", - Paths.get("../../distribution/component").toFile().getAbsolutePath()) - .build(); + var ctx = SrcUtil.newContextBuilder().build(); var code = """ from Standard.Base import Integer, Vector, Any, Float diff --git a/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/VectorBenchmarks.java b/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/VectorBenchmarks.java index 93a30596bbd..4a16cda4c84 100644 --- a/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/VectorBenchmarks.java +++ b/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/VectorBenchmarks.java @@ -1,14 +1,9 @@ package org.enso.interpreter.bench.benchmarks.semantic; -import java.nio.file.Paths; import java.util.AbstractList; import java.util.concurrent.TimeUnit; import java.util.function.Function; -import java.util.logging.Level; -import org.enso.polyglot.RuntimeOptions; -import org.graalvm.polyglot.Context; import org.graalvm.polyglot.Value; -import org.graalvm.polyglot.io.IOAccess; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; @@ -35,18 +30,7 @@ public class VectorBenchmarks { @Setup public void initializeBenchmark(BenchmarkParams params) throws Exception { - var ctx = - Context.newBuilder() - .allowExperimentalOptions(true) - .allowIO(IOAccess.ALL) - .allowAllAccess(true) - .option(RuntimeOptions.LOG_LEVEL, Level.WARNING.getName()) - .logHandler(System.err) - .option( - "enso.languageHomeOverride", - Paths.get("../../distribution/component").toFile().getAbsolutePath()) - .build(); - + var ctx = SrcUtil.newContextBuilder().build(); var benchmarkName = SrcUtil.findName(params); var code = """ diff --git a/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/WarningBenchmarks.java b/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/WarningBenchmarks.java index f95ed0f5866..820b01d4ae5 100644 --- a/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/WarningBenchmarks.java +++ b/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/WarningBenchmarks.java @@ -1,18 +1,14 @@ package org.enso.interpreter.bench.benchmarks.semantic; import java.io.IOException; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Random; import java.util.concurrent.TimeUnit; -import java.util.logging.Level; import org.enso.polyglot.MethodNames; -import org.enso.polyglot.RuntimeOptions; import org.graalvm.polyglot.Context; import org.graalvm.polyglot.Value; -import org.graalvm.polyglot.io.IOAccess; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; @@ -71,17 +67,7 @@ public class WarningBenchmarks { @Setup public void initializeBench(BenchmarkParams params) throws IOException { - this.ctx = - Context.newBuilder() - .allowExperimentalOptions(true) - .option(RuntimeOptions.LOG_LEVEL, Level.WARNING.getName()) - .logHandler(System.err) - .allowIO(IOAccess.ALL) - .allowAllAccess(true) - .option( - RuntimeOptions.LANGUAGE_HOME_OVERRIDE, - Paths.get("../../distribution/component").toFile().getAbsolutePath()) - .build(); + this.ctx = SrcUtil.newContextBuilder().build(); var random = new Random(42); benchmarkName = SrcUtil.findName(params); diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/AutoscopedConstructorTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/AutoscopedConstructorTest.java new file mode 100644 index 00000000000..3aa086075e1 --- /dev/null +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/AutoscopedConstructorTest.java @@ -0,0 +1,226 @@ +package org.enso.interpreter.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.ByteArrayOutputStream; +import org.enso.polyglot.MethodNames; +import org.graalvm.polyglot.Context; +import org.graalvm.polyglot.PolyglotException; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +public class AutoscopedConstructorTest extends TestBase { + private static final ByteArrayOutputStream out = new ByteArrayOutputStream(); + private static Context ctx; + + public AutoscopedConstructorTest() {} + + @BeforeClass + public static void prepareCtx() { + ctx = createDefaultContext(out); + } + + @Before + public void resetOut() { + out.reset(); + } + + @AfterClass + public static void disposeCtx() { + ctx.close(); + } + + @Test + public void lazyConstructorWithNoArgument() { + try { + var create = + ctx.eval( + "enso", + """ + type N + False + + materialize v:N = v.to_text + + create n = N.materialize (~False) + """) + .invokeMember(MethodNames.Module.EVAL_EXPRESSION, "create"); + assertTrue("Can evaluate", create.canExecute()); + assertEquals("False", create.execute(42).asString()); + + } catch (PolyglotException e) { + fail(e.getMessage() + " for \n" + out.toString()); + } + } + + @Test + public void lazyConstructorWithSingleArg() { + try { + var create = + ctx.eval( + "enso", + """ + type M + Construct value + + materialize v:M = v.value + + create n = M.materialize (~Construct n) + """) + .invokeMember(MethodNames.Module.EVAL_EXPRESSION, "create"); + assertTrue("Can evaluate", create.canExecute()); + assertEquals("42", create.execute(42).toString()); + + } catch (PolyglotException e) { + fail(e.getMessage() + " for \n" + out.toString()); + } + } + + @Test + public void lazyConstructorWithTwoArgs() { + try { + var create = + ctx.eval( + "enso", + """ + type M + Construct v1 v2 + + materialize v:M = [v.v1, v.v2] + + create a b = M.materialize (~Construct a b) + """) + .invokeMember(MethodNames.Module.EVAL_EXPRESSION, "create"); + assertTrue("Can evaluate", create.canExecute()); + assertEquals("[6, 7]", create.execute(6, 7).toString()); + } catch (PolyglotException e) { + fail(e.getMessage() + " for \n" + out.toString()); + } + } + + @Test + public void lazyConstructorWithTwoArgsCurried() { + try { + var create = + ctx.eval( + "enso", + """ + type M + Construct v1 v2 + + materialize v:M = [v.v1, v.v2] + + create a b = + v0 = ~Construct + v1 = v0 a + v2 = v1 b + M.materialize v2 + """) + .invokeMember(MethodNames.Module.EVAL_EXPRESSION, "create"); + assertTrue("Can evaluate", create.canExecute()); + assertEquals("[7, 6]", create.execute(7, 6).toString()); + } catch (PolyglotException e) { + fail(e.getMessage() + " for \n" + out.toString()); + } + } + + @Test + public void lazyConstructorWithTwoArgsNamed() { + try { + var create = + ctx.eval( + "enso", + """ + type M + Construct v1 v2 + + materialize v:M = [v.v1, v.v2] + + create a b = + v0 = ~Construct v2=a v1=b + M.materialize v0 + """) + .invokeMember(MethodNames.Module.EVAL_EXPRESSION, "create"); + assertTrue("Can evaluate", create.canExecute()); + assertEquals("[7, 6]", create.execute(6, 7).toString()); + } catch (PolyglotException e) { + fail(e.getMessage() + " for \n" + out.toString()); + } + } + + @Test + public void lazyConstructorWithNamedDefaultedArguments() { + try { + var module = + ctx.eval( + "enso", + """ + type M + Construct v1=1 v2=2 v3=3 v4=4 + + materialize v:M = [v.v1, v.v2, v.v3, v.v4] + + c0 _ = M.materialize (~Construct) + c1 a = M.materialize (~Construct a) + c12 a b = M.materialize (~Construct a b) + c123 a b c = M.materialize (~Construct a b c) + c1234 a b c d = M.materialize (~Construct a b c d) + c14 a d = M.materialize (~Construct a v4=d) + c13 a c = M.materialize (~Construct a v3=c) + c41 a d = M.materialize ((~Construct v4=d) a) + c31 a c = M.materialize ((~Construct v3=c) a) + """); + + var c0 = module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "c0"); + var c1 = module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "c1"); + var c12 = module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "c12"); + var c123 = module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "c123"); + var c1234 = module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "c1234"); + var c14 = module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "c14"); + var c13 = module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "c13"); + var c41 = module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "c41"); + var c31 = module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "c31"); + + assertEquals("[1, 2, 3, 4]", c0.execute("ignored").toString()); + assertEquals("[9, 2, 3, 4]", c1.execute(9).toString()); + assertEquals("[9, 7, 3, 4]", c12.execute(9, 7).toString()); + assertEquals("[9, 7, 5, 4]", c123.execute(9, 7, 5).toString()); + assertEquals("[9, 7, 5, 3]", c1234.execute(9, 7, 5, 3).toString()); + assertEquals("[8, 2, 3, 7]", c14.execute(8, 7).toString()); + assertEquals("[8, 2, 7, 4]", c13.execute(8, 7).toString()); + assertEquals("[8, 2, 3, 7]", c41.execute(8, 7).toString()); + assertEquals("[8, 2, 7, 4]", c31.execute(8, 7).toString()); + } catch (PolyglotException e) { + fail(e.getMessage() + " for \n" + out.toString()); + } + } + + @Test + public void wrongConstructorNameYieldsTypeError() { + try { + var create = + ctx.eval( + "enso", + """ + type N + False + + materialize v:N = v.to_text + + create n = N.materialize (~True) + """) + .invokeMember(MethodNames.Module.EVAL_EXPRESSION, "create"); + assertTrue("Can evaluate", create.canExecute()); + var r = create.execute(42); + fail("Expecting an exception, not " + r); + } catch (PolyglotException e) { + assertTrue( + "Expecting type error, but got: " + e.getMessage(), + e.getMessage().contains("Type_Error")); + } + } +} diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/TreeToIr.java b/engine/runtime-parser/src/main/java/org/enso/compiler/core/TreeToIr.java index 7dae188f3d7..c8ceb496adf 100644 --- a/engine/runtime-parser/src/main/java/org/enso/compiler/core/TreeToIr.java +++ b/engine/runtime-parser/src/main/java/org/enso/compiler/core/TreeToIr.java @@ -1030,6 +1030,16 @@ final class TreeToIr { } case null -> translateSyntaxError(tree, new Syntax.UnsupportedSyntax("Strange unary -")); }; + case Tree.UnaryOprApp un when "~".equals(un.getOpr().codeRepr()) -> { + var methodName = buildName(un.getRhs()); + var methodRef = new Name.MethodReference( + Option.empty(), + methodName, + methodName.location(), + meta(), diag() + ); + yield methodRef; + } case Tree.TypeSignature sig -> { var methodName = buildName(sig.getVariable()); var methodReference = new CallArgument.Specified( diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeCallableNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeCallableNode.java index bb6a92d41ba..78533384453 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeCallableNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeCallableNode.java @@ -15,6 +15,7 @@ import org.enso.interpreter.node.BaseNode; import org.enso.interpreter.node.callable.dispatch.InvokeFunctionNode; import org.enso.interpreter.node.callable.thunk.ThunkExecutorNode; import org.enso.interpreter.runtime.EnsoContext; +import org.enso.interpreter.runtime.callable.UnresolvedConstructor; import org.enso.interpreter.runtime.callable.UnresolvedConversion; import org.enso.interpreter.runtime.callable.UnresolvedSymbol; import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo; @@ -236,6 +237,12 @@ public abstract class InvokeCallableNode extends BaseNode { } } + @Specialization + public Object invokeDynamicConstructor( + UnresolvedConstructor symbol, VirtualFrame callerFrame, State state, Object[] arguments) { + return symbol.withArguments(invokeFunctionNode.getSchema(), arguments); + } + @Specialization public Object invokeDynamicSymbol( UnresolvedSymbol symbol, VirtualFrame callerFrame, State state, Object[] arguments) { diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/argument/ReadArgumentCheckNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/argument/ReadArgumentCheckNode.java index 29a6c5e86d1..f78a439b20e 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/argument/ReadArgumentCheckNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/argument/ReadArgumentCheckNode.java @@ -32,6 +32,7 @@ import org.enso.interpreter.node.expression.builtin.meta.TypeOfNode; import org.enso.interpreter.node.expression.literal.LiteralNode; import org.enso.interpreter.runtime.EnsoContext; import org.enso.interpreter.runtime.callable.Annotation; +import org.enso.interpreter.runtime.callable.UnresolvedConstructor; import org.enso.interpreter.runtime.callable.UnresolvedConversion; import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition; import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition.ExecutionMode; @@ -41,6 +42,7 @@ import org.enso.interpreter.runtime.callable.function.Function; import org.enso.interpreter.runtime.callable.function.FunctionSchema; import org.enso.interpreter.runtime.data.EnsoMultiValue; import org.enso.interpreter.runtime.data.Type; +import org.enso.interpreter.runtime.data.text.Text; import org.enso.interpreter.runtime.error.DataflowError; import org.enso.interpreter.runtime.error.PanicException; import org.enso.interpreter.runtime.error.PanicSentinel; @@ -88,7 +90,15 @@ public abstract class ReadArgumentCheckNode extends Node { expectedTypeMessage = expectedTypeMessage(); } var ctx = EnsoContext.get(this); - var msg = comment == null ? "expression" : comment; + Text msg; + if (v instanceof UnresolvedConstructor) { + msg = Text.create("Cannot find constructor {got} among {exp}"); + } else { + var where = Text.create(comment == null ? "expression" : comment); + var exp = Text.create("expected "); + var got = Text.create(" to be {exp}, but got {got}"); + msg = Text.create(exp, Text.create(where, got)); + } var err = ctx.getBuiltins().error().makeTypeErrorOfComment(expectedTypeMessage, v, msg); throw new PanicException(err, this); } @@ -254,6 +264,15 @@ public abstract class ReadArgumentCheckNode extends Node { throw panicSentinel; } + @Specialization + Object doUnresolvedConstructor( + VirtualFrame frame, + UnresolvedConstructor unresolved, + @Cached UnresolvedConstructor.ConstructNode construct) { + var state = Function.ArgumentsHelper.getState(frame.getArguments()); + return construct.execute(frame, state, expectedType, unresolved); + } + @Specialization(rewriteOn = InvalidAssumptionException.class) Object doCheckNoConversionNeeded(VirtualFrame frame, Object v) throws InvalidAssumptionException { diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/text/util/TypeToDisplayTextNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/text/util/TypeToDisplayTextNode.java index 25b854dab3a..452ff69dbd7 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/text/util/TypeToDisplayTextNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/text/util/TypeToDisplayTextNode.java @@ -54,7 +54,12 @@ public final class TypeToDisplayTextNode extends Node { throw EnsoContext.get(this).raiseAssertionPanic(this, null, e); } } else { - return "a polyglot object"; + try { + var res = iop.toDisplayString(value); + return iop.asString(res); + } catch (UnsupportedMessageException ex) { + return "a polyglot object"; + } } } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/constant/DynamicSymbolNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/constant/DynamicSymbolNode.java index 3e2058695d0..05b64ee2e15 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/constant/DynamicSymbolNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/constant/DynamicSymbolNode.java @@ -3,14 +3,16 @@ package org.enso.interpreter.node.expression.constant; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.NodeInfo; import org.enso.interpreter.node.ExpressionNode; +import org.enso.interpreter.runtime.callable.UnresolvedConstructor; import org.enso.interpreter.runtime.callable.UnresolvedSymbol; +import org.enso.interpreter.runtime.data.EnsoObject; /** Simple constant node that always results in the same {@link UnresolvedSymbol}. */ @NodeInfo(shortName = "DynamicSym") public class DynamicSymbolNode extends ExpressionNode { - private final UnresolvedSymbol unresolvedSymbol; + private final EnsoObject unresolvedSymbol; - private DynamicSymbolNode(UnresolvedSymbol unresolvedSymbol) { + private DynamicSymbolNode(EnsoObject unresolvedSymbol) { this.unresolvedSymbol = unresolvedSymbol; } @@ -24,6 +26,10 @@ public class DynamicSymbolNode extends ExpressionNode { return new DynamicSymbolNode(symbol); } + public static DynamicSymbolNode build(UnresolvedConstructor symbol) { + return new DynamicSymbolNode(symbol); + } + /** * Gets the dynamic symbol from the node. * diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Error.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Error.java index 02a6ea9fcc8..9e5a23900ad 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Error.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Error.java @@ -195,8 +195,8 @@ public final class Error { * @param comment description of the value that was being checked * @return a runtime representation of the error. */ - public Atom makeTypeErrorOfComment(Object expected, Object actual, String comment) { - return typeError.newInstance(expected, actual, Text.create(comment)); + public Atom makeTypeErrorOfComment(Object expected, Object actual, Text comment) { + return typeError.newInstance(expected, actual, comment); } /** diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/UnresolvedConstructor.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/UnresolvedConstructor.java new file mode 100644 index 00000000000..a8a3ad024c8 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/UnresolvedConstructor.java @@ -0,0 +1,161 @@ +package org.enso.interpreter.runtime.callable; + +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.interop.InteropLibrary; +import com.oracle.truffle.api.library.ExportLibrary; +import com.oracle.truffle.api.library.ExportMessage; +import com.oracle.truffle.api.nodes.Node; +import java.util.Arrays; +import java.util.Objects; +import org.enso.interpreter.node.callable.InvokeCallableNode.ArgumentsExecutionMode; +import org.enso.interpreter.node.callable.InvokeCallableNode.DefaultsExecutionMode; +import org.enso.interpreter.node.callable.dispatch.InvokeFunctionNode; +import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo; +import org.enso.interpreter.runtime.data.EnsoObject; +import org.enso.interpreter.runtime.data.Type; +import org.enso.interpreter.runtime.data.atom.AtomConstructor; +import org.enso.interpreter.runtime.state.State; + +/** + * Value representing a by-name identified constructor of a yet unknown {@link Type}. Create new + * instance by providing name to {@link #build(String)} method. Then apply arguments to it via + * {@link #withArguments(Object[])} method. + * + *

Let the object flow thru the interpreter and resolve it when the required {@link Type} is + * known. + */ +@ExportLibrary(InteropLibrary.class) +public final class UnresolvedConstructor implements EnsoObject { + private static final CallArgumentInfo[] NONE = new CallArgumentInfo[0]; + private final String name; + final CallArgumentInfo[] descs; + private final Object[] args; + + /** + * Creates a new unresolved name. + * + * @param name constructor name + * @param descs argument descriptions to apply to the constructor + * @param args argument values to apply to the constructor + */ + private UnresolvedConstructor(String name, CallArgumentInfo[] descs, Object[] args) { + this.name = name; + this.descs = descs; + this.args = args; + } + + final String getName() { + return name; + } + + @Override + @CompilerDirectives.TruffleBoundary + public String toString() { + return "~" + name; + } + + @ExportMessage + String toDisplayString(boolean allowSideEffects) { + return toString(); + } + + /** + * Creates an instance of this node. + * + * @param name the name (of the constructor) we are searching for + * @return a object representing unresolved (constructor) + */ + public static UnresolvedConstructor build(String name) { + return new UnresolvedConstructor(name, NONE, NONE); + } + + /** + * Marks this object as executable through the interop library. + * + * @param additionalDescriptions description of the applied arguments + * @param additionalArguments new arguments to add to the unresolved constructor + * @return always true @ExportMessage public boolean isExecutable() { return true; } + */ + public UnresolvedConstructor withArguments( + CallArgumentInfo[] additionalDescriptions, Object[] additionalArguments) { + if (this.args == NONE) { + return new UnresolvedConstructor(this.name, additionalDescriptions, additionalArguments); + } else { + var newDescs = join(this.descs, additionalDescriptions); + var newArgs = join(this.args, additionalArguments); + return new UnresolvedConstructor(this.name, newDescs, newArgs); + } + } + + final UnresolvedConstructor asPrototype() { + return new UnresolvedConstructor(this.name, this.descs, null); + } + + final boolean sameAsPrototyped(UnresolvedConstructor other) { + if (descs.length != other.descs.length) { + return false; + } + if (!name.equals(other.name)) { + return false; + } + for (var i = 0; i < descs.length; i++) { + if (!Objects.equals(descs[i].getName(), other.descs[i].getName())) { + return false; + } + } + return true; + } + + private static T[] join(T[] arr1, T[] arr2) { + var ret = Arrays.copyOf(arr1, arr1.length + arr2.length); + System.arraycopy(arr2, 0, ret, arr1.length, arr2.length); + return ret; + } + + public abstract static class ConstructNode extends Node { + static final DefaultsExecutionMode EXEC_MODE = DefaultsExecutionMode.EXECUTE; + static final ArgumentsExecutionMode ARGS_MODE = ArgumentsExecutionMode.EXECUTE; + + public abstract Object execute( + VirtualFrame frame, State state, Type expectedType, UnresolvedConstructor unresolved); + + @Specialization( + guards = {"cachedType == expectedType", "prototype.sameAsPrototyped(unresolved)"}, + limit = "10") + Object instantiateCached( + VirtualFrame frame, + State state, + Type expectedType, + UnresolvedConstructor unresolved, + @Cached("expectedType") Type cachedType, + @Cached("unresolved.asPrototype()") UnresolvedConstructor prototype, + @Cached("expectedType.getConstructors().get(prototype.getName())") AtomConstructor c, + @Cached("build(prototype.descs,EXEC_MODE,ARGS_MODE)") InvokeFunctionNode invoke) { + if (c == null) { + return null; + } else { + var fn = c.getConstructorFunction(); + var r = invoke.execute(fn, frame, state, unresolved.args); + return r; + } + } + + @Specialization(replaces = "instantiateCached") + @CompilerDirectives.TruffleBoundary + Object instantiateUncached( + MaterializedFrame frame, State state, Type expectedType, UnresolvedConstructor unresolved) { + var c = expectedType.getConstructors().get(unresolved.getName()); + if (c == null) { + return null; + } + var fn = c.getConstructorFunction(); + var invoke = InvokeFunctionNode.build(unresolved.descs, EXEC_MODE, ARGS_MODE); + var r = invoke.execute(fn, frame, state, unresolved.args); + return r; + } + } +} diff --git a/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala b/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala index 10652b3a689..ad27ed14420 100644 --- a/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala +++ b/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala @@ -97,6 +97,7 @@ import org.enso.interpreter.runtime.callable.function.{ Function => RuntimeFunction } import org.enso.interpreter.runtime.callable.{ + UnresolvedConstructor, UnresolvedConversion, UnresolvedSymbol, Annotation => RuntimeAnnotation @@ -1680,6 +1681,16 @@ class IrToTruffle( UnresolvedSymbol.build(nameStr, moduleScope) ) } + case Name.MethodReference( + None, + Name.Literal(nameStr, _, _, _, _, _), + _, + _, + _ + ) => + DynamicSymbolNode.build( + UnresolvedConstructor.build(nameStr) + ) case Name.Self(location, _, passData, _) => processName( Name.Literal( @@ -1715,10 +1726,6 @@ class IrToTruffle( throw new CompilerError( "Blanks should not be present at codegen time." ) - case _: Name.MethodReference => - throw new CompilerError( - "Method references should not be present at codegen time." - ) case _: Name.Qualified => throw new CompilerError( "Qualified names should not be present at codegen time." diff --git a/lib/java/benchmarks-common/src/main/java/org/enso/interpreter/bench/BenchmarksRunner.java b/lib/java/benchmarks-common/src/main/java/org/enso/interpreter/bench/BenchmarksRunner.java index 69b54ec1529..b7a69424018 100644 --- a/lib/java/benchmarks-common/src/main/java/org/enso/interpreter/bench/BenchmarksRunner.java +++ b/lib/java/benchmarks-common/src/main/java/org/enso/interpreter/bench/BenchmarksRunner.java @@ -15,6 +15,7 @@ import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.CommandLineOptionException; import org.openjdk.jmh.runner.options.CommandLineOptions; import org.openjdk.jmh.runner.options.OptionsBuilder; +import org.openjdk.jmh.runner.options.TimeValue; /** Runner class for the benchmarks. Discovers, runs and reports benchmark results. */ public class BenchmarksRunner { @@ -77,7 +78,12 @@ public class BenchmarksRunner { private static Collection runCompileOnly(List includes) throws RunnerException { System.out.println("Running benchmarks " + includes + " in compileOnly mode"); - var optsBuilder = new OptionsBuilder().measurementIterations(1).warmupIterations(0).forks(0); + var optsBuilder = + new OptionsBuilder() + .measurementTime(TimeValue.seconds(1)) + .measurementIterations(1) + .warmupIterations(0) + .forks(0); includes.forEach(optsBuilder::include); var opts = optsBuilder.build(); var runner = new Runner(opts); diff --git a/test/Base_Tests/src/Semantic/Conversion_Spec.enso b/test/Base_Tests/src/Semantic/Conversion_Spec.enso index d321387038f..ac2325df800 100644 --- a/test/Base_Tests/src/Semantic/Conversion_Spec.enso +++ b/test/Base_Tests/src/Semantic/Conversion_Spec.enso @@ -2,6 +2,9 @@ from Standard.Base import all import Standard.Base.Errors.Common.No_Such_Conversion import Standard.Base.Errors.Common.Type_Error +import Standard.Base.Runtime.State +from Standard.Base.Errors.Common import Uninitialized_State + import project.Semantic.Conversion.Methods import project.Semantic.Conversion.Types import project.Semantic.Conversion_Use.Hello @@ -30,6 +33,9 @@ type My_Error type Not_Foo Value notfoo +type Stateful + Value v:Number=(State.get Number) + Foo.from (that:Bar) = Foo.Value that.bar Foo.from (that:Baz) = Foo.Value that.baz Foo.from (that:Text) = Foo.Value that.length @@ -427,6 +433,83 @@ add_specs suite_builder = do_duration now + suite_builder.group "Autoscoped Constructors" group_builder-> + + group_builder.specify "Foo.Value as autoscoped" <| + + v = ~Value 10 + foo = v:Foo + Foo.Value 10 . should_equal foo + + group_builder.specify "Autoscope to two different values" <| + + v = ~Value 10 + foo = v:Foo + bar = v:Bar + Foo.Value 10 . should_equal foo + Bar.Value 10 . should_equal bar + + group_builder.specify "Cannot find constructor" <| + v = ~Value 10 + + b = Panic.recover Any <| + x = v:Back + x + + b . should_fail_with Type_Error + msg = b.to_display_text + msg . should_contain "Cannot find constructor ~Value among Back" + + group_builder.specify "Choose first constructor" <| + v = ~Value 10 + + m_foo (m:Foo|Bar|Back) = m + m_bar (m:Bar|Foo|Back) = m + m_back_foo (m:Back|Foo|Bar) = m + m_back_bar (m:Back|Bar|Foo) = m + + m_foo v . should_equal <| Foo.Value 10 + m_bar v . should_equal <| Bar.Value 10 + m_back_foo v . should_equal <| Foo.Value 10 + m_back_bar v . should_equal <| Bar.Value 10 + + group_builder.specify "Choose suitable constructor" <| + v = ~Times 10 + + m_foo (m:Foo|Bar) = m + m_bar (m:Bar|Foo) = m + m_back (m:Foo|Bar|Back) = m + + Panic.recover Any (m_foo v) . should_fail_with Type_Error + Panic.recover Any (m_bar v) . should_fail_with Type_Error + m_back v . should_equal <| Back.Times 10 + + group_builder.specify "Lazy constructor with State" <| + v0 = ~Value + v1 = ~Value 33 + + State.run Number 42 <| + s42 = State.get Number + s42 . should_equal 42 + + v0:Stateful . should_equal <| Stateful.Value 42 + v1:Stateful . should_equal <| Stateful.Value 33 + + v1:Stateful . should_equal <| Stateful.Value 33 + err = Panic.recover Any <| + v = v0:Stateful + v + err . should_fail_with Uninitialized_State + + group_builder.specify "Autoscope vector elements" <| + + foo_vec (v:Vector) = v.map e-> + e:Foo . foo + + vec = [~Value 3, ~Value 4, ~Value 5] + + foo_vec vec . should_equal [3, 4, 5] + suite_builder.group "Polyglot Argument" group_builder-> f1 (x : DateTimeFormatter) = x.to_text f2 (x : Text | DateTimeFormatter) = case x of diff --git a/test/Table_Tests/src/In_Memory/Table_Xml_Spec.enso b/test/Table_Tests/src/In_Memory/Table_Xml_Spec.enso index 8462abf67e5..61da14dd8b7 100644 --- a/test/Table_Tests/src/In_Memory/Table_Xml_Spec.enso +++ b/test/Table_Tests/src/In_Memory/Table_Xml_Spec.enso @@ -294,7 +294,7 @@ add_specs suite_builder = group_builder.specify "Panic if wrong types passed in element_columns" <| t = data.table r = Panic.recover Any (t.to_xml 1.23) - r.to_text.should_equal "(Error: (Type_Error.Error Vector | Text | Integer | Regex 1.23 '`element_columns`'))" + r.to_text.should_equal "(Error: Type error: expected `element_columns` to be Vector | Text | Integer | Regex, but got Float.)" group_builder.specify "Panic if wrong types passed in element_columns vector" pending='Not working' <| t = data.table r = Panic.recover Any (t.to_xml [1.23]) @@ -302,27 +302,27 @@ add_specs suite_builder = group_builder.specify "Panic if wrong types passed in attribute_columns" <| t = data.table r = Panic.recover Any (t.to_xml [] 1.23) - r.to_text.should_equal "(Error: (Type_Error.Error Vector | Text | Integer | Regex 1.23 '`attribute_columns`'))" + r.to_text.should_equal "(Error: Type error: expected `attribute_columns` to be Vector | Text | Integer | Regex, but got Float.)" group_builder.specify "Panic if wrong types passed in attribute_columns vector" pending='Not working' <| t = data.table r = Panic.recover Any (t.to_xml [] [1.23]) - r.to_text.should_equal "(Error: (Type_Error.Error Vector | Text | Integer | Regex 1.23 '`attribute_columns`'))" + r.to_text.should_equal "(Error: Type error: expected `attribute_columns` to be Vector | Text | Integer | Regex, but got Float.)" group_builder.specify "Panic if wrong types passed in value_column" <| t = data.table r = Panic.recover Any (t.to_xml [] [] 1.23) - r.to_text.should_equal "(Error: (Type_Error.Error Text | Integer | Nothing 1.23 '`value_column`'))" + r.to_text.should_equal "(Error: Type error: expected `value_column` to be Text | Integer | Nothing, but got Float.)" group_builder.specify "Panic if wrong types passed in root_name" <| t = data.table r = Panic.recover Any (t.to_xml [] [] "Year" 1.23) - r.to_text.should_equal "(Error: (Type_Error.Error Text 1.23 '`root_name`'))" + r.to_text.should_equal "(Error: Type error: expected `root_name` to be Text, but got Float.)" group_builder.specify "Panic if wrong types passed in row_name" <| t = data.table r = Panic.recover Any (t.to_xml [] [] "Year" "Table" 1.23) - r.to_text.should_equal "(Error: (Type_Error.Error Text 1.23 '`row_name`'))" + r.to_text.should_equal "(Error: Type error: expected `row_name` to be Text, but got Float.)" group_builder.specify "Panic if wrong types passed in on_problems" <| t = data.table r = Panic.recover Any (t.to_xml [] [] "Year" "Table" "row" 1.23) - r.to_text.should_equal "(Error: (Type_Error.Error Problem_Behavior 1.23 '`on_problems`'))" + r.to_text.should_equal "(Error: Type error: expected `on_problems` to be Problem_Behavior, but got Float.)" group_builder.specify "works with unicode characters" <| unicode_column = ["unicode", ['\u00A9', "👩‍🔬"]] # | unicode