From a0504365c018da0b3b2c8265151ee37b5f2272aa Mon Sep 17 00:00:00 2001 From: Hubert Plociniczak Date: Thu, 12 May 2022 12:25:19 +0200 Subject: [PATCH 01/11] Generate BuiltinType from @Builtin Expanded processing to allow for @Builtin to be added to classes. More boilerplate gone. --- .../expression/builtin/mutable/Array.java | 7 --- .../enso/interpreter/runtime/data/Array.java | 1 + .../org/enso/interpreter/dsl/Builtin.java | 2 +- .../interpreter/dsl/BuiltinsProcessor.java | 45 ++++++++++++++++++- 4 files changed, 45 insertions(+), 10 deletions(-) delete mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/Array.java diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/Array.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/Array.java deleted file mode 100644 index 1133dfa4caea..000000000000 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/Array.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.enso.interpreter.node.expression.builtin.mutable; - -import org.enso.interpreter.dsl.BuiltinType; -import org.enso.interpreter.node.expression.builtin.Builtin; - -@BuiltinType(name = "Standard.Base.Data.Array.Array") -public class Array extends Builtin {} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Array.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Array.java index d64938fe91c3..8c0418d73647 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Array.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Array.java @@ -22,6 +22,7 @@ /** A primitve boxed array type for use in the runtime. */ @ExportLibrary(InteropLibrary.class) @ExportLibrary(MethodDispatchLibrary.class) +@Builtin(pkg = "mutable", name = "Standard.Base.Data.Array.Array") public class Array implements TruffleObject { private final Object[] items; diff --git a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/Builtin.java b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/Builtin.java index d29e2b7ec126..9134fe43b6a7 100644 --- a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/Builtin.java +++ b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/Builtin.java @@ -6,7 +6,7 @@ import java.lang.annotation.Target; /** An annotation denoting a method that will auto-generate a BuiltinMethod node. */ -@Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) +@Target({ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.TYPE}) @Retention(RetentionPolicy.SOURCE) public @interface Builtin { /** @return the name of the subpackage for the generated method node. */ diff --git a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/BuiltinsProcessor.java b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/BuiltinsProcessor.java index 0693e389d071..0893ec356d98 100644 --- a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/BuiltinsProcessor.java +++ b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/BuiltinsProcessor.java @@ -12,6 +12,7 @@ import java.io.PrintWriter; import java.util.Arrays; import java.util.List; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -40,6 +41,12 @@ public final boolean process(Set annotations, RoundEnviro } catch (Exception ioe) { processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, ioe.getMessage()); } + } else if (elt.getKind() == ElementKind.CLASS) { + try { + handleClassElement(elt, roundEnv); + } catch (IOException ioe) { + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, ioe.getMessage()); + } } else { processingEnv .getMessager() @@ -52,6 +59,37 @@ public final boolean process(Set annotations, RoundEnviro return true; } + public void handleClassElement(Element element, RoundEnvironment roundEnv) throws IOException { + TypeElement elt = (TypeElement) element; + Builtin annotation = element.getAnnotation(Builtin.class); + String clazzName = element.getSimpleName().toString(); + String builtinPkg = + annotation.pkg().isEmpty() ? BuiltinsPkg : BuiltinsPkg + "." + annotation.pkg(); + ClassName builtinType = new ClassName(builtinPkg, clazzName); + JavaFileObject gen = + processingEnv.getFiler().createSourceFile(builtinType.fullyQualifiedName()); + Optional stdLibName = annotation.name().isEmpty() ? Optional.empty() : Optional.of(annotation.name()); + generateBuiltinType(gen, builtinType, stdLibName); + } + + private void generateBuiltinType( + JavaFileObject gen, + ClassName builtinType, + Optional stdLibName) throws IOException { + try (PrintWriter out = new PrintWriter(gen.openWriter())) { + out.println("package " + builtinType.pkg() + ";"); + out.println(); + for (String importPkg : typeNecessaryImports) { + out.println("import " + importPkg + ";"); + } + out.println(); + String stdLib = stdLibName.map(n -> "(name = \"" + n + "\")").orElse(""); + out.println("@BuiltinType" + stdLib); + out.println("public class " + builtinType.name() + " extends Builtin {}"); + out.println(); + } + } + public void handleMethodElement(Element element, RoundEnvironment roundEnv) throws IOException { ExecutableElement method = (ExecutableElement) element; Element owner = element.getEnclosingElement(); @@ -107,7 +145,7 @@ private void generateBuiltinMethodNode( try (PrintWriter out = new PrintWriter(gen.openWriter())) { out.println("package " + builtinNode.pkg() + ";"); out.println(); - for (String importPkg : necessaryImports) { + for (String importPkg : methodNecessaryImports) { out.println("import " + importPkg + ";"); } out.println("import " + ownerClazz.fullyQualifiedName() + ";"); @@ -132,7 +170,10 @@ private void generateBuiltinMethodNode( } } - private final List necessaryImports = + private final List typeNecessaryImports = + Arrays.asList("org.enso.interpreter.dsl.BuiltinType", + "org.enso.interpreter.node.expression.builtin.Builtin"); + private final List methodNecessaryImports = Arrays.asList("com.oracle.truffle.api.nodes.Node", "org.enso.interpreter.dsl.BuiltinMethod"); private MethodParameter fromVariableElementToMethodParameter(VariableElement v) { From 22334d8440f65f30d05d595533c3095a4ceb597f Mon Sep 17 00:00:00 2001 From: Hubert Plociniczak Date: Thu, 12 May 2022 16:44:04 +0200 Subject: [PATCH 02/11] Generate method nodes for varargs When a method or constructor has vararg parameter, we may want to generate multiple BuiltinMethod representation, up to some number of parameters. The approach has been extended in a generic way to take it into consideration when `expandVargars` parameter is bigger than zero. This allowed us to remove 4 more hardcoded classes. --- .../expression/builtin/mutable/New1Node.java | 15 -- .../expression/builtin/mutable/New2Node.java | 16 --- .../expression/builtin/mutable/New3Node.java | 16 --- .../expression/builtin/mutable/New4Node.java | 16 --- .../enso/interpreter/runtime/data/Array.java | 2 +- .../org/enso/interpreter/dsl/Builtin.java | 7 + .../interpreter/dsl/BuiltinsProcessor.java | 133 ++++++++++++++---- 7 files changed, 113 insertions(+), 92 deletions(-) delete mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/New1Node.java delete mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/New2Node.java delete mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/New3Node.java delete mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/New4Node.java diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/New1Node.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/New1Node.java deleted file mode 100644 index 58c69d97b3b0..000000000000 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/New1Node.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.enso.interpreter.node.expression.builtin.mutable; - -import com.oracle.truffle.api.nodes.Node; -import org.enso.interpreter.dsl.BuiltinMethod; -import org.enso.interpreter.runtime.data.Array; - -@BuiltinMethod( - type = "Array", - name = "new_1", - description = "Creates an array with one given element.") -public class New1Node extends Node { - Object execute(Object _this, Object item_1) { - return new Array(item_1); - } -} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/New2Node.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/New2Node.java deleted file mode 100644 index e999c45c459d..000000000000 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/New2Node.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.enso.interpreter.node.expression.builtin.mutable; - -import com.oracle.truffle.api.nodes.Node; -import org.enso.interpreter.dsl.BuiltinMethod; -import org.enso.interpreter.runtime.data.Array; - -@BuiltinMethod( - type = "Array", - name = "new_2", - description = "Creates an array with two given elements.") -public class New2Node extends Node { - - Object execute(Object _this, Object item_1, Object item_2) { - return new Array(item_1, item_2); - } -} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/New3Node.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/New3Node.java deleted file mode 100644 index e2d5192e51c2..000000000000 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/New3Node.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.enso.interpreter.node.expression.builtin.mutable; - -import com.oracle.truffle.api.nodes.Node; -import org.enso.interpreter.dsl.BuiltinMethod; -import org.enso.interpreter.runtime.data.Array; - -@BuiltinMethod( - type = "Array", - name = "new_3", - description = "Creates an array with three given elements.") -public class New3Node extends Node { - - Object execute(Object _this, Object item_1, Object item_2, Object item_3) { - return new Array(item_1, item_2, item_3); - } -} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/New4Node.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/New4Node.java deleted file mode 100644 index 6034414ba346..000000000000 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/New4Node.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.enso.interpreter.node.expression.builtin.mutable; - -import com.oracle.truffle.api.nodes.Node; -import org.enso.interpreter.dsl.BuiltinMethod; -import org.enso.interpreter.runtime.data.Array; - -@BuiltinMethod( - type = "Array", - name = "new_4", - description = "Creates an array with four given elements.") -public class New4Node extends Node { - - Object execute(Object _this, Object item_1, Object item_2, Object item_3, Object item_4) { - return new Array(item_1, item_2, item_3, item_4); - } -} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Array.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Array.java index 8c0418d73647..acb03b143286 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Array.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Array.java @@ -9,7 +9,6 @@ import com.oracle.truffle.api.library.ExportLibrary; import com.oracle.truffle.api.library.ExportMessage; import org.enso.interpreter.dsl.Builtin; -import org.enso.interpreter.dsl.BuiltinMethod; import org.enso.interpreter.runtime.Context; import org.enso.interpreter.runtime.callable.UnresolvedConversion; import org.enso.interpreter.runtime.callable.UnresolvedSymbol; @@ -31,6 +30,7 @@ public class Array implements TruffleObject { * * @param items the element values */ + @Builtin(pkg = "mutable", expandVarargs = 4, description = "Creates an array with given elements.") public Array(Object... items) { this.items = items; } diff --git a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/Builtin.java b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/Builtin.java index 9134fe43b6a7..11cededc241b 100644 --- a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/Builtin.java +++ b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/Builtin.java @@ -17,4 +17,11 @@ /** @return a short description of this method. */ String description() default ""; + + /** + * @return when applied to a method/constructor with varargs, + * will generate methods with parameters repeated up to the value. + * Must be zero when there are no varaargs in the parameters list. + */ + int expandVarargs() default 0; } diff --git a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/BuiltinsProcessor.java b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/BuiltinsProcessor.java index 0893ec356d98..b1c330ec4b49 100644 --- a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/BuiltinsProcessor.java +++ b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/BuiltinsProcessor.java @@ -15,6 +15,8 @@ import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; /** * The processor used to generate code from the methods of the runtime representations annotated @@ -98,23 +100,51 @@ public void handleMethodElement(Element element, RoundEnvironment roundEnv) thro PackageElement pkgElement = (PackageElement) tpeElement.getEnclosingElement(); Builtin annotation = element.getAnnotation(Builtin.class); boolean isConstructor = method.getKind() == ElementKind.CONSTRUCTOR; - String methodName = - !annotation.name().isEmpty() ? annotation.name() : - isConstructor ? "new" : method.getSimpleName().toString(); String builtinPkg = annotation.pkg().isEmpty() ? BuiltinsPkg : BuiltinsPkg + "." + annotation.pkg(); - String optConstrSuffix = isConstructor ? tpeElement.getSimpleName().toString() : ""; - ClassName builtinMethodNode = - new ClassName( - builtinPkg, - methodName.substring(0, 1).toUpperCase() + methodName.substring(1) + optConstrSuffix + "Node"); - ClassName ownerClass = - new ClassName(pkgElement.getQualifiedName(), tpeElement.getSimpleName()); - JavaFileObject gen = - processingEnv.getFiler().createSourceFile(builtinMethodNode.fullyQualifiedName()); - MethodGenerator methodGen = new MethodGenerator(method); - generateBuiltinMethodNode( - gen, methodGen, methodName, method.getSimpleName().toString(), annotation.description(), builtinMethodNode, ownerClass); + + if (annotation.expandVarargs() != 0) { + if (annotation.expandVarargs() < 0) throw new RuntimeException("Invalid varargs value in @Builtin annotation. Must be positive"); + if (!annotation.name().isEmpty()) throw new RuntimeException("Name cannot be non-empty when varargs are used"); + + IntStream.rangeClosed(1, annotation.expandVarargs()).forEach(i -> { + String methodName = (isConstructor ? "new" : method.getSimpleName().toString()) + "_" + i; + String noUnderscoresMethodName = methodName.replaceAll("_", ""); + String optConstrSuffix = isConstructor ? tpeElement.getSimpleName().toString() : ""; + ClassName builtinMethodNode = + new ClassName( + builtinPkg, + noUnderscoresMethodName.substring(0, 1).toUpperCase() + noUnderscoresMethodName.substring(1) + optConstrSuffix + "Node"); + ClassName ownerClass = + new ClassName(pkgElement.getQualifiedName(), tpeElement.getSimpleName()); + try { + JavaFileObject gen = + processingEnv.getFiler().createSourceFile(builtinMethodNode.fullyQualifiedName()); + MethodGenerator methodGen = new MethodGenerator(method, i); + generateBuiltinMethodNode( + gen, methodGen, methodName, method.getSimpleName().toString(), annotation.description(), builtinMethodNode, ownerClass); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + }); + } else { + String methodName = + !annotation.name().isEmpty() ? annotation.name() : + isConstructor ? "new" : method.getSimpleName().toString(); + + String optConstrSuffix = isConstructor ? tpeElement.getSimpleName().toString() : ""; + ClassName builtinMethodNode = + new ClassName( + builtinPkg, + methodName.substring(0, 1).toUpperCase() + methodName.substring(1) + optConstrSuffix + "Node"); + ClassName ownerClass = + new ClassName(pkgElement.getQualifiedName(), tpeElement.getSimpleName()); + JavaFileObject gen = + processingEnv.getFiler().createSourceFile(builtinMethodNode.fullyQualifiedName()); + MethodGenerator methodGen = new MethodGenerator(method, 0); + generateBuiltinMethodNode( + gen, methodGen, methodName, method.getSimpleName().toString(), annotation.description(), builtinMethodNode, ownerClass); + } } else { throw new RuntimeException("@Builtin method must be owned by the class"); } @@ -176,8 +206,8 @@ private void generateBuiltinMethodNode( private final List methodNecessaryImports = Arrays.asList("com.oracle.truffle.api.nodes.Node", "org.enso.interpreter.dsl.BuiltinMethod"); - private MethodParameter fromVariableElementToMethodParameter(VariableElement v) { - return new MethodParameter(v.getSimpleName().toString(), v.asType().toString()); + private MethodParameter fromVariableElementToMethodParameter(int i, VariableElement v) { + return new MethodParameter(i, v.getSimpleName().toString(), v.asType().toString()); } /** Method generator encapsulates the generation of the `execute` method. */ @@ -186,22 +216,41 @@ private class MethodGenerator { private final List params; private final boolean isStatic; private final boolean isConstructor; + private final int varargExpansion; + private final boolean needsVarargExpansion; - public MethodGenerator(ExecutableElement method) { + public MethodGenerator(ExecutableElement method, int expandedVarargs) { + this(method, method.getParameters(), expandedVarargs); + } + + private MethodGenerator(ExecutableElement method, List params, int expandedVarargs) { this( method.getReturnType().toString(), - method.getParameters().stream() - .map(v -> fromVariableElementToMethodParameter(v)) - .collect(Collectors.toList()), + IntStream.range(0, method.getParameters().size()).mapToObj(i -> + fromVariableElementToMethodParameter(i, params.get(i))).collect(Collectors.toList()), method.getModifiers().contains(Modifier.STATIC), - method.getKind() == ElementKind.CONSTRUCTOR); + method.getKind() == ElementKind.CONSTRUCTOR, + expandedVarargs, + method.isVarArgs()); } - private MethodGenerator(String returnTpe, List params, boolean isStatic, boolean isConstructor) { + private MethodGenerator( + String returnTpe, + List params, + boolean isStatic, + boolean isConstructor, + int expandedVarargs, + boolean isVarargs) { this.returnTpe = returnTpe; this.params = params; this.isStatic = isStatic; this.isConstructor = isConstructor; + this.varargExpansion = expandedVarargs; + this.needsVarargExpansion = isVarargs && (varargExpansion > 0); + } + + private Optional expandVararg(int paramIndex) { + return needsVarargExpansion && params.size() >= (paramIndex + 1) ? Optional.of(varargExpansion) : Optional.empty(); } public String[] generateMethod(String name, String owner) { @@ -213,9 +262,9 @@ public String[] generateMethod(String name, String owner) { } else { paramsDef = ", " - + StringUtils.join(params.stream().map(x -> x.declaredParameter()).toArray(), ", "); + + StringUtils.join(params.stream().flatMap(x -> x.declaredParameters(expandVararg(x.index))).toArray(), ", "); paramsApplied = - StringUtils.join(params.stream().map(x -> x.name()).toArray(), ", "); + StringUtils.join(params.stream().flatMap(x -> x.names(expandVararg(x.index))).toArray(), ", "); } String thisParamTpe = isStatic || isConstructor ? "Object" : owner; String targetReturnTpe = isConstructor ? "Object" : returnTpe; @@ -245,9 +294,37 @@ public String fullyQualifiedName() { } } - private record MethodParameter(String name, String tpe) { - public String declaredParameter() { - return tpe + " " + name; + /** + * MethodParameter encapsulates the generation of string representation of the parameter. + * Additionally, it can optionally expand vararg parameters. + */ + private record MethodParameter(int index, String name, String tpe) { + /** + * Returns a parameter's declaration. + * If the parameter represents a vararg, the declaration is repeated. Otherwise return a single + * element Stream of declarations. + * + * @param expand For a non-empty value n, the parameter must be repeated n-times. + * @return A string representation of the parameter, potentially repeated for varargs + */ + public Stream declaredParameters(Optional expand) { + // If the parameter is the expanded vararg we must get rid of the `[]` suffix + String parameterTpe = expand.isEmpty() ? tpe : tpe.substring(0, tpe.length() - 2); + return names(expand).map(n -> parameterTpe + " " + n); + } + + /** + * Returns a parameter's name.. + * If the parameter represents a vararg, the name is repeated. Otherwise return a single + * element Stream of declarations. + * + * @param expand For a non-empty value n, the parameter must be repeated n-times. + * @return A string representation of the parameter, potentially repeated for varargs + */ + public Stream names(Optional expand) { + return expand.map(e-> + IntStream.range(0, e).mapToObj(i-> name + "_" + (i+1)) + ).orElse(Stream.of(name)); } } From 8f8f71d7c465381a3391e56e747b4a1bfd1463bb Mon Sep 17 00:00:00 2001 From: Hubert Plociniczak Date: Fri, 13 May 2022 18:20:22 +0200 Subject: [PATCH 03/11] Generate code that wraps exceptions to panic Added `wrapException` annotation element that allows us to catch and wrap runtime exceptions into panic. `Array.at` and `Array.set_at` nodes are now generated from simple methods. Additionally, we also have to propagate user-defined annotations on parameters. --- .../expression/builtin/mutable/GetAtNode.java | 24 --- .../expression/builtin/mutable/SetAtNode.java | 26 --- .../enso/interpreter/runtime/data/Array.java | 26 ++- .../org/enso/interpreter/dsl/Builtin.java | 13 +- .../interpreter/dsl/BuiltinsProcessor.java | 152 +++++++++++++++--- 5 files changed, 162 insertions(+), 79 deletions(-) delete mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/GetAtNode.java delete mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/SetAtNode.java diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/GetAtNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/GetAtNode.java deleted file mode 100644 index 96dd228596f0..000000000000 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/GetAtNode.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.enso.interpreter.node.expression.builtin.mutable; - -import com.oracle.truffle.api.nodes.Node; -import org.enso.interpreter.dsl.BuiltinMethod; -import org.enso.interpreter.runtime.Context; -import org.enso.interpreter.runtime.builtin.Builtins; -import org.enso.interpreter.runtime.data.Array; -import org.enso.interpreter.runtime.error.PanicException; - -@BuiltinMethod( - type = "Array", - name = "at", - description = "Gets an array element at the given index.") -public class GetAtNode extends Node { - - Object execute(Array _this, long index) { - try { - return _this.getItems()[(int) index]; - } catch (IndexOutOfBoundsException exception) { - Builtins builtins = Context.get(this).getBuiltins(); - throw new PanicException(builtins.error().makeInvalidArrayIndexError(_this, index), this); - } - } -} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/SetAtNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/SetAtNode.java deleted file mode 100644 index 841355272ac5..000000000000 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/SetAtNode.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.enso.interpreter.node.expression.builtin.mutable; - -import com.oracle.truffle.api.nodes.Node; -import org.enso.interpreter.dsl.AcceptsError; -import org.enso.interpreter.dsl.BuiltinMethod; -import org.enso.interpreter.runtime.Context; -import org.enso.interpreter.runtime.builtin.Builtins; -import org.enso.interpreter.runtime.data.Array; -import org.enso.interpreter.runtime.error.PanicException; - -@BuiltinMethod( - type = "Array", - name = "set_at", - description = "Puts the given element in the given position in the array.") -public class SetAtNode extends Node { - - Object execute(Array _this, long index, @AcceptsError Object value) { - try { - _this.getItems()[(int) index] = value; - return _this; - } catch (IndexOutOfBoundsException exception) { - Builtins builtins = Context.get(this).getBuiltins(); - throw new PanicException(builtins.error().makeInvalidArrayIndexError(_this, index), this); - } - } -} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Array.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Array.java index acb03b143286..de1b2a829314 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Array.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Array.java @@ -8,7 +8,9 @@ import com.oracle.truffle.api.interop.TruffleObject; import com.oracle.truffle.api.library.ExportLibrary; import com.oracle.truffle.api.library.ExportMessage; +import org.enso.interpreter.dsl.AcceptsError; import org.enso.interpreter.dsl.Builtin; +import org.enso.interpreter.node.expression.builtin.error.InvalidArrayIndexError; import org.enso.interpreter.runtime.Context; import org.enso.interpreter.runtime.callable.UnresolvedConversion; import org.enso.interpreter.runtime.callable.UnresolvedSymbol; @@ -30,7 +32,10 @@ public class Array implements TruffleObject { * * @param items the element values */ - @Builtin(pkg = "mutable", expandVarargs = 4, description = "Creates an array with given elements.") + @Builtin( + pkg = "mutable", + expandVarargs = 4, + description = "Creates an array with given elements.") public Array(Object... items) { this.items = items; } @@ -105,6 +110,25 @@ long getArraySize() { return items.length; } + @Builtin( + pkg = "mutable", + name = "at", + description = "Gets an array element at the given index.", + wrapException = {IndexOutOfBoundsException.class, InvalidArrayIndexError.class}) + public Object get(long index) { + return getItems()[(int) index]; + } + + @Builtin( + pkg = "mutable", + name = "setAt", + description = "Gets an array element at the given index.", + wrapException = {IndexOutOfBoundsException.class, InvalidArrayIndexError.class}) + public Object set(long index, @AcceptsError Object value) { + getItems()[(int) index] = value; + return this; + } + /** * Exposes an index validity check through the polyglot API. * diff --git a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/Builtin.java b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/Builtin.java index 11cededc241b..f92e886fb5ab 100644 --- a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/Builtin.java +++ b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/Builtin.java @@ -19,9 +19,16 @@ String description() default ""; /** - * @return when applied to a method/constructor with varargs, - * will generate methods with parameters repeated up to the value. - * Must be zero when there are no varaargs in the parameters list. + * @return when applied to a method/constructor with varargs, will generate methods with + * parameters repeated up to the value. Must be zero when there are no varaargs in the + * parameters list. */ int expandVarargs() default 0; + + /** + * @return even-length array representing pairs of classes. The first element of the tuple always represents + * a possible runtime exception that can be thrown during the execution of the method, while the second element + * represents Enso builtin type wrapper for it that will be reported to the user. + */ + Class[] wrapException() default {}; } diff --git a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/BuiltinsProcessor.java b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/BuiltinsProcessor.java index b1c330ec4b49..ddc3aa4c23fe 100644 --- a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/BuiltinsProcessor.java +++ b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/BuiltinsProcessor.java @@ -6,14 +6,14 @@ import javax.annotation.processing.*; import javax.lang.model.SourceVersion; import javax.lang.model.element.*; +import com.sun.tools.javac.code.Attribute; + +import javax.lang.model.type.TypeMirror; import javax.tools.Diagnostic; import javax.tools.JavaFileObject; import java.io.IOException; import java.io.PrintWriter; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; @@ -31,6 +31,7 @@ public class BuiltinsProcessor extends AbstractProcessor { private static final String BuiltinsPkg = "org.enso.interpreter.node.expression.builtin"; + private static final String WrapExceptionElementName = "wrapException"; @Override public final boolean process(Set annotations, RoundEnvironment roundEnv) { @@ -95,6 +96,7 @@ private void generateBuiltinType( public void handleMethodElement(Element element, RoundEnvironment roundEnv) throws IOException { ExecutableElement method = (ExecutableElement) element; Element owner = element.getEnclosingElement(); + if (owner.getKind() == ElementKind.CLASS) { TypeElement tpeElement = (TypeElement) owner; PackageElement pkgElement = (PackageElement) tpeElement.getEnclosingElement(); @@ -103,6 +105,9 @@ public void handleMethodElement(Element element, RoundEnvironment roundEnv) thro String builtinPkg = annotation.pkg().isEmpty() ? BuiltinsPkg : BuiltinsPkg + "." + annotation.pkg(); + Attribute.Class[] exceptionWrappers = getClassElementFromAnnotation(element, Builtin.class, WrapExceptionElementName); + Map parameterCounts = builtinTypesParametersCount(roundEnv); + if (annotation.expandVarargs() != 0) { if (annotation.expandVarargs() < 0) throw new RuntimeException("Invalid varargs value in @Builtin annotation. Must be positive"); if (!annotation.name().isEmpty()) throw new RuntimeException("Name cannot be non-empty when varargs are used"); @@ -120,9 +125,9 @@ public void handleMethodElement(Element element, RoundEnvironment roundEnv) thro try { JavaFileObject gen = processingEnv.getFiler().createSourceFile(builtinMethodNode.fullyQualifiedName()); - MethodGenerator methodGen = new MethodGenerator(method, i); + MethodGenerator methodGen = new MethodGenerator(method, i, exceptionWrappers); generateBuiltinMethodNode( - gen, methodGen, methodName, method.getSimpleName().toString(), annotation.description(), builtinMethodNode, ownerClass); + gen, methodGen, methodName, method.getSimpleName().toString(), annotation.description(), builtinMethodNode, ownerClass, parameterCounts); } catch (IOException ioe) { throw new RuntimeException(ioe); } @@ -141,15 +146,59 @@ public void handleMethodElement(Element element, RoundEnvironment roundEnv) thro new ClassName(pkgElement.getQualifiedName(), tpeElement.getSimpleName()); JavaFileObject gen = processingEnv.getFiler().createSourceFile(builtinMethodNode.fullyQualifiedName()); - MethodGenerator methodGen = new MethodGenerator(method, 0); + MethodGenerator methodGen = new MethodGenerator(method, 0, exceptionWrappers); generateBuiltinMethodNode( - gen, methodGen, methodName, method.getSimpleName().toString(), annotation.description(), builtinMethodNode, ownerClass); + gen, methodGen, methodName, method.getSimpleName().toString(), annotation.description(), builtinMethodNode, ownerClass, parameterCounts); } } else { throw new RuntimeException("@Builtin method must be owned by the class"); } } + private Map builtinTypesParametersCount(RoundEnvironment roundEnv) { + return roundEnv + .getElementsAnnotatedWith(BuiltinType.class) + .stream() + .collect(Collectors.toMap(e -> e.getSimpleName().toString(), e -> e.getAnnotation(BuiltinType.class).params().length)); + } + + /** + * Return the annotation type's element value, when its type is declared to involve Class (or Class[]). + * Sadly this cannot be simply retrieved by invoking the () method and one has to go through mirrors. + * Refer to blog + * for details. + * + * @param element element annotated with the given annotation + * @param annotationClass the class representation of the annotation + * @param annotationMethodName the name of the element in the annotation type having Class type + * @return even-length array of class annotation values, if present + */ + private Attribute.Class[] getClassElementFromAnnotation(Element element, Class annotationClass, String annotationMethodName) { + Element builtinElement = processingEnv.getElementUtils().getTypeElement(annotationClass.getName()); + TypeMirror builtinType = builtinElement.asType(); + + AnnotationValue value = null; + for (AnnotationMirror am: element.getAnnotationMirrors()) { + if (am.getAnnotationType().equals(builtinType)) { + for (Map.Entry entry : am.getElementValues().entrySet() ) { + if (annotationMethodName.equals(entry.getKey().getSimpleName().toString())) { + value = entry.getValue(); + break; + } + } + } + } + + if (value != null) { + List elems = ((List)value.getValue()); + if ((elems.size() % 2) != 0) { + throw new RuntimeException("Length of `" + annotationMethodName + "`value has to be even"); + } + return elems.toArray(new Attribute.Class[0]); + } + return new Attribute.Class[0]; + } + /** * Generates a Java class for @BuiltinMethod Node. * @@ -160,6 +209,7 @@ public void handleMethodElement(Element element, RoundEnvironment roundEnv) thro * @param description short description for the node * @param builtinNode class name of the target node * @param ownerClazz class name of the owner of the annotated method + * @param builtinTypesParamCount a map from builtin types to their parameters count * @throws IOException throws an exception when we cannot write the new class */ private void generateBuiltinMethodNode( @@ -169,7 +219,8 @@ private void generateBuiltinMethodNode( String ownerMethodName, String description, ClassName builtinNode, - ClassName ownerClazz) + ClassName ownerClazz, + Map builtinTypesParamCount) throws IOException { String ensoMethodName = methodName.replaceAll("([^_A-Z])([A-Z])", "$1_$2").toLowerCase(); try (PrintWriter out = new PrintWriter(gen.openWriter())) { @@ -191,7 +242,7 @@ private void generateBuiltinMethodNode( out.println("public class " + builtinNode.name() + " extends Node {"); out.println(); - for (String line : mgen.generateMethod(ownerMethodName, ownerClazz.name())) { + for (String line : mgen.generateMethod(ownerMethodName, ownerClazz.name(), builtinTypesParamCount)) { out.println(" " + line); } @@ -204,10 +255,16 @@ private void generateBuiltinMethodNode( Arrays.asList("org.enso.interpreter.dsl.BuiltinType", "org.enso.interpreter.node.expression.builtin.Builtin"); private final List methodNecessaryImports = - Arrays.asList("com.oracle.truffle.api.nodes.Node", "org.enso.interpreter.dsl.BuiltinMethod"); + Arrays.asList( + "com.oracle.truffle.api.nodes.Node", + "org.enso.interpreter.dsl.BuiltinMethod", + "org.enso.interpreter.runtime.builtin.Builtins", + "org.enso.interpreter.runtime.Context", + "org.enso.interpreter.runtime.error.PanicException"); private MethodParameter fromVariableElementToMethodParameter(int i, VariableElement v) { - return new MethodParameter(i, v.getSimpleName().toString(), v.asType().toString()); + return new MethodParameter(i, v.getSimpleName().toString(), v.asType().toString(), + v.getAnnotationMirrors().stream().map(am -> am.toString()).collect(Collectors.toList())); } /** Method generator encapsulates the generation of the `execute` method. */ @@ -218,12 +275,13 @@ private class MethodGenerator { private final boolean isConstructor; private final int varargExpansion; private final boolean needsVarargExpansion; + private final Attribute.Class[] exceptionWrappers; - public MethodGenerator(ExecutableElement method, int expandedVarargs) { - this(method, method.getParameters(), expandedVarargs); + public MethodGenerator(ExecutableElement method, int expandedVarargs, Attribute.Class[] exceptionWrappers) { + this(method, method.getParameters(), expandedVarargs, exceptionWrappers); } - private MethodGenerator(ExecutableElement method, List params, int expandedVarargs) { + private MethodGenerator(ExecutableElement method, List params, int expandedVarargs, Attribute.Class[] exceptionWrappers) { this( method.getReturnType().toString(), IntStream.range(0, method.getParameters().size()).mapToObj(i -> @@ -231,7 +289,8 @@ private MethodGenerator(ExecutableElement method, List 0); + this.exceptionWrappers = exceptionWrappers; } private Optional expandVararg(int paramIndex) { return needsVarargExpansion && params.size() >= (paramIndex + 1) ? Optional.of(varargExpansion) : Optional.empty(); } - public String[] generateMethod(String name, String owner) { + private String fromAnnotationValueToClassName(Attribute.Class clazz) { + String[] clazzElements = clazz.classType.baseType().toString().split("\\."); + if (clazzElements.length == 0) { + return clazz.classType.baseType().toString(); + } else { + return clazzElements[clazzElements.length - 1]; + } + } + + public List generateMethod(String name, String owner, Map builtinTypesParameterCounts) { String paramsDef; String paramsApplied; if (params.isEmpty()) { @@ -276,14 +346,45 @@ public String[] generateMethod(String name, String owner) { ? " return " + owner + "." + name + "(" + paramsApplied + ");" : " return _this." + name + "(" + paramsApplied + ");"; } - return new String[] { - targetReturnTpe + " execute(" + thisParamTpe + " _this" + paramsDef + ") {", - body, - "}" - }; + boolean wrapsExceptions = exceptionWrappers.length != 0; + if (wrapsExceptions) { + List wrappedBody = new ArrayList<>(); + wrappedBody.add(targetReturnTpe + " execute(" + thisParamTpe + " _this" + paramsDef + ") {"); + wrappedBody.add(" try {"); + wrappedBody.add(" " + body); + + for (int i=0; i < exceptionWrappers.length; i+=2) { + String from = fromAnnotationValueToClassName(exceptionWrappers[i]); + String to = fromAnnotationValueToClassName(exceptionWrappers[i+1]); + int toParamCount = errorParametersCount(exceptionWrappers[i+1], builtinTypesParameterCounts); + String errorParamsApplied = StringUtils.join(params.stream().limit(toParamCount - 1).flatMap(x -> x.names(Optional.empty())).toArray(), ", "); + wrappedBody.addAll(List.of( + " } catch (" + from + " exception) {", + " Builtins builtins = Context.get(this).getBuiltins();", + " throw new PanicException(builtins.error().make" + to + "(_this, " + errorParamsApplied + "), this);", + " }" + )); + } + wrappedBody.add("}"); + return wrappedBody; + } else { + return List.of( + targetReturnTpe + " execute(" + thisParamTpe + " _this" + paramsDef + ") {", + body, + "}" + ); + } } + + private int errorParametersCount(Attribute.Class clazz, Map builtinTypesParameterCounts) { + String clazzSimple = fromAnnotationValueToClassName(clazz); + // `this` counts as 1 + return builtinTypesParameterCounts.getOrDefault(clazzSimple, 1); + } + } + private record ClassName(String pkg, String name) { private ClassName(Name pkgName, Name nameSeq) { this(pkgName.toString(), nameSeq.toString()); @@ -298,7 +399,7 @@ public String fullyQualifiedName() { * MethodParameter encapsulates the generation of string representation of the parameter. * Additionally, it can optionally expand vararg parameters. */ - private record MethodParameter(int index, String name, String tpe) { + private record MethodParameter(int index, String name, String tpe, List annotations) { /** * Returns a parameter's declaration. * If the parameter represents a vararg, the declaration is repeated. Otherwise return a single @@ -310,7 +411,8 @@ private record MethodParameter(int index, String name, String tpe) { public Stream declaredParameters(Optional expand) { // If the parameter is the expanded vararg we must get rid of the `[]` suffix String parameterTpe = expand.isEmpty() ? tpe : tpe.substring(0, tpe.length() - 2); - return names(expand).map(n -> parameterTpe + " " + n); + String copiedAnnotations = annotations.isEmpty() ? "" : (StringUtils.joinWith(" ", annotations.toArray()) + " "); + return names(expand).map(n -> copiedAnnotations + parameterTpe + " " + n); } /** From 9d8c8571931a07407175bea4a454b55b94a91162 Mon Sep 17 00:00:00 2001 From: Hubert Plociniczak Date: Mon, 16 May 2022 15:00:43 +0200 Subject: [PATCH 04/11] Formatting --- .../src/main/java/org/enso/interpreter/dsl/Builtin.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/Builtin.java b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/Builtin.java index f92e886fb5ab..dce75439b601 100644 --- a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/Builtin.java +++ b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/Builtin.java @@ -26,9 +26,10 @@ int expandVarargs() default 0; /** - * @return even-length array representing pairs of classes. The first element of the tuple always represents - * a possible runtime exception that can be thrown during the execution of the method, while the second element - * represents Enso builtin type wrapper for it that will be reported to the user. + * @return even-length array representing pairs of classes. The first element of the tuple always + * represents a possible runtime exception that can be thrown during the execution of the + * method, while the second element represents Enso builtin type wrapper for it that will be + * reported to the user. */ Class[] wrapException() default {}; } From 0ac9a2c28882d03e44689c036f9d49f2ccbf0f79 Mon Sep 17 00:00:00 2001 From: Hubert Plociniczak Date: Mon, 16 May 2022 15:09:46 +0200 Subject: [PATCH 05/11] Add CHANGELOG --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 861e1c3386d5..a41ce4aac37c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -201,6 +201,8 @@ - [Move Builtin Types and Methods definitions to stdlib][3363] - [Reduce boilerplate by generating BuiltinMethod nodes from simple method signatures][3444] +- [Generate boilerplate classes related to error handling and varargs in + builtins from method signatures][3454] [3227]: https://github.com/enso-org/enso/pull/3227 [3248]: https://github.com/enso-org/enso/pull/3248 @@ -220,6 +222,7 @@ [3363]: https://github.com/enso-org/enso/pull/3363 [3444]: https://github.com/enso-org/enso/pull/3444 [3453]: https://github.com/enso-org/enso/pull/3453 +[3454]: https://github.com/enso-org/enso/pull/3454 # Enso 2.0.0-alpha.18 (2021-10-12) From 2e6ab25dc9ecc11772e307f0946f8f0916022a0d Mon Sep 17 00:00:00 2001 From: Hubert Plociniczak Date: Mon, 16 May 2022 17:03:55 +0200 Subject: [PATCH 06/11] Fix typo --- .../src/main/java/org/enso/interpreter/dsl/TypeProcessor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/TypeProcessor.java b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/TypeProcessor.java index 442c5990ebfc..ac93ff2985fd 100644 --- a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/TypeProcessor.java +++ b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/TypeProcessor.java @@ -130,7 +130,7 @@ protected void storeMetadata(Writer writer, Map pastEntries) thr String[] elements = entry.split(":"); if (elements.length == 4 && !elements[3].isEmpty()) { out.println( - " public static void final String " + " public static final String " + elements[0].toUpperCase() + " = \"" + elements[3] From 8a84a79cdac3d1a32e5513ed0fdc211597978d2a Mon Sep 17 00:00:00 2001 From: Hubert Plociniczak Date: Wed, 18 May 2022 10:13:45 +0200 Subject: [PATCH 07/11] Rewrite DSL, allow for repeated exception wrapping The existing Builtin DSL was becoming a bit too crowded with multiple elements and default values. That wouldn't scale so had to refactor it. The functionality stays almost the same except that now we really can have repeatable @WrapException annotation indicating multiple catch-clauses for possible exceptions that need to be wrapped. Added a ton of documentation as the implenetation of the processor is getting a bit more complicated. --- build.sbt | 9 +- .../enso/interpreter/runtime/data/Array.java | 31 +-- .../org/enso/interpreter/dsl/Builtin.java | 56 ++-- .../interpreter/dsl/BuiltinsProcessor.java | 254 +++++++++++++----- 4 files changed, 234 insertions(+), 116 deletions(-) diff --git a/build.sbt b/build.sbt index 002a7766b3e0..003a30e4c420 100644 --- a/build.sbt +++ b/build.sbt @@ -157,10 +157,11 @@ Global / onChangedBuildSource := ReloadOnSourceChanges // ============================================================================ ThisBuild / javacOptions ++= Seq( - "-encoding", // Provide explicit encoding (the next line) - "UTF-8", // Specify character encoding used by Java source files. - "-deprecation", // Shows a description of each use or override of a deprecated member or class. - "-g" // Include debugging information + "-encoding", // Provide explicit encoding (the next line) + "UTF-8", // Specify character encoding used by Java source files + "-deprecation", // Shows a description of each use or override of a deprecated member or class + "-g", // Include debugging information + "-Xlint:unchecked", // Enable additional warnings ) ThisBuild / scalacOptions ++= Seq( diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Array.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Array.java index de1b2a829314..8955156476fe 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Array.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Array.java @@ -23,7 +23,7 @@ /** A primitve boxed array type for use in the runtime. */ @ExportLibrary(InteropLibrary.class) @ExportLibrary(MethodDispatchLibrary.class) -@Builtin(pkg = "mutable", name = "Standard.Base.Data.Array.Array") +@Builtin(pkg = "mutable", stdlibName = "Standard.Base.Data.Array.Array") public class Array implements TruffleObject { private final Object[] items; @@ -32,10 +32,7 @@ public class Array implements TruffleObject { * * @param items the element values */ - @Builtin( - pkg = "mutable", - expandVarargs = 4, - description = "Creates an array with given elements.") + @Builtin.Method(expandVarargs = 4, description = "Creates an array with given elements.") public Array(Object... items) { this.items = items; } @@ -45,7 +42,7 @@ public Array(Object... items) { * * @param size the size of the created array. */ - @Builtin(pkg = "mutable", description = "Creates an uninitialized array of a given size.") + @Builtin.Method(description = "Creates an uninitialized array of a given size.") public Array(long size) { this.items = new Object[(int) size]; } @@ -81,21 +78,19 @@ public Object readArrayElement(long index) throws InvalidArrayIndexException { } /** @return the size of this array */ - @Builtin(pkg = "mutable", description = "Returns the size of this array.") + @Builtin.Method(description = "Returns the size of this array.") public long length() { return this.getItems().length; } /** @return an empty array */ - @Builtin(pkg = "mutable", description = "Creates an empty Array") + @Builtin.Method(description = "Creates an empty Array") public static Object empty() { return new Array(); } /** @return an identity array */ - @Builtin( - pkg = "mutable", - description = "Identity on arrays, implemented for protocol completeness.") + @Builtin.Method(description = "Identity on arrays, implemented for protocol completeness.") public Object toArray() { return this; } @@ -110,20 +105,14 @@ long getArraySize() { return items.length; } - @Builtin( - pkg = "mutable", - name = "at", - description = "Gets an array element at the given index.", - wrapException = {IndexOutOfBoundsException.class, InvalidArrayIndexError.class}) + @Builtin.Method(name = "at", description = "Gets an array element at the given index.") + @Builtin.WrapException(from = IndexOutOfBoundsException.class, to = InvalidArrayIndexError.class) public Object get(long index) { return getItems()[(int) index]; } - @Builtin( - pkg = "mutable", - name = "setAt", - description = "Gets an array element at the given index.", - wrapException = {IndexOutOfBoundsException.class, InvalidArrayIndexError.class}) + @Builtin.Method(name = "setAt", description = "Gets an array element at the given index.") + @Builtin.WrapException(from = IndexOutOfBoundsException.class, to = InvalidArrayIndexError.class) public Object set(long index, @AcceptsError Object value) { getItems()[(int) index] = value; return this; diff --git a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/Builtin.java b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/Builtin.java index dce75439b601..1cdf85022a62 100644 --- a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/Builtin.java +++ b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/Builtin.java @@ -1,35 +1,51 @@ package org.enso.interpreter.dsl; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; +import java.lang.annotation.*; /** An annotation denoting a method that will auto-generate a BuiltinMethod node. */ -@Target({ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.TYPE}) +@Target(ElementType.TYPE) @Retention(RetentionPolicy.SOURCE) public @interface Builtin { /** @return the name of the subpackage for the generated method node. */ String pkg() default ""; - /** @return a custom name, by default it uses the name of the annotated element. */ - String name() default ""; + /** @return A fully qualified name of the corresponding Enso type in standard library. */ + String stdlibName() default ""; - /** @return a short description of this method. */ - String description() default ""; + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) + @interface Method { + /** + * @return a custom name for the generated builting method. By default, it uses the name of the + * annotated method. + */ + String name() default ""; - /** - * @return when applied to a method/constructor with varargs, will generate methods with - * parameters repeated up to the value. Must be zero when there are no varaargs in the - * parameters list. - */ - int expandVarargs() default 0; + /** @return a short description for the generated builtin method. */ + String description() default ""; + + /** + * @return When applied to a method/constructor with varargs, indicates how many times vararg + * parameter should be expanded and implicitly how many builtin nodes should be generated. + * Must be zero when there are no varaargs in the parameters list. + */ + int expandVarargs() default 0; + } /** - * @return even-length array representing pairs of classes. The first element of the tuple always - * represents a possible runtime exception that can be thrown during the execution of the - * method, while the second element represents Enso builtin type wrapper for it that will be - * reported to the user. + * Annotation indicating that a potential exception during the execution of the method should be + * wrapped in Enso's error type that will be reported to the user. The annotation can be repeated + * leading to multiple catch clauses. */ - Class[] wrapException() default {}; + @Repeatable(WrapExceptions.class) + @interface WrapException { + /** @return Class of the potential exception to be caught during the execution of the method */ + Class from(); + /** @return Class of Enso's builtin (error) type * */ + Class to(); + } + + /** Container for {@link WrapException} annotations */ + @interface WrapExceptions { + WrapException[] value() default {}; + } } diff --git a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/BuiltinsProcessor.java b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/BuiltinsProcessor.java index ddc3aa4c23fe..e370e533a5b8 100644 --- a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/BuiltinsProcessor.java +++ b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/BuiltinsProcessor.java @@ -1,18 +1,23 @@ package org.enso.interpreter.dsl; +import com.sun.tools.javac.code.Attribute; +import com.sun.tools.javac.code.Symbol; +import com.sun.tools.javac.util.Pair; + import org.apache.commons.lang3.StringUtils; import org.openide.util.lookup.ServiceProvider; import javax.annotation.processing.*; import javax.lang.model.SourceVersion; import javax.lang.model.element.*; -import com.sun.tools.javac.code.Attribute; - import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Types; import javax.tools.Diagnostic; import javax.tools.JavaFileObject; + import java.io.IOException; import java.io.PrintWriter; +import java.lang.annotation.Annotation; import java.util.*; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -26,12 +31,13 @@ * generate a corresponding @BuiltinMethod class which, in turn, will get processed by another * processor and deliver a RootNode for the method. */ -@SupportedAnnotationTypes("org.enso.interpreter.dsl.Builtin") +@SupportedAnnotationTypes({"org.enso.interpreter.dsl.Builtin", "org.enso.interpreter.dsl.Builtin.Method"}) @ServiceProvider(service = Processor.class) public class BuiltinsProcessor extends AbstractProcessor { private static final String BuiltinsPkg = "org.enso.interpreter.node.expression.builtin"; - private static final String WrapExceptionElementName = "wrapException"; + private final WrapExceptionExtractor wrapExceptionsExtractor = + new WrapExceptionExtractor(Builtin.WrapException.class, Builtin.WrapExceptions.class); @Override public final boolean process(Set annotations, RoundEnvironment roundEnv) { @@ -41,8 +47,9 @@ public final boolean process(Set annotations, RoundEnviro if (elt.getKind() == ElementKind.METHOD || elt.getKind() == ElementKind.CONSTRUCTOR) { try { handleMethodElement(elt, roundEnv); - } catch (Exception ioe) { - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, ioe.getMessage()); + } catch (Exception e) { + e.printStackTrace(); + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, e.getMessage()); } } else if (elt.getKind() == ElementKind.CLASS) { try { @@ -71,7 +78,7 @@ public void handleClassElement(Element element, RoundEnvironment roundEnv) throw ClassName builtinType = new ClassName(builtinPkg, clazzName); JavaFileObject gen = processingEnv.getFiler().createSourceFile(builtinType.fullyQualifiedName()); - Optional stdLibName = annotation.name().isEmpty() ? Optional.empty() : Optional.of(annotation.name()); + Optional stdLibName = annotation.stdlibName().isEmpty() ? Optional.empty() : Optional.of(annotation.stdlibName()); generateBuiltinType(gen, builtinType, stdLibName); } @@ -98,14 +105,16 @@ public void handleMethodElement(Element element, RoundEnvironment roundEnv) thro Element owner = element.getEnclosingElement(); if (owner.getKind() == ElementKind.CLASS) { + Builtin ownerAnnotation = owner.getAnnotation(Builtin.class); TypeElement tpeElement = (TypeElement) owner; PackageElement pkgElement = (PackageElement) tpeElement.getEnclosingElement(); - Builtin annotation = element.getAnnotation(Builtin.class); + Builtin.Method annotation = element.getAnnotation(Builtin.Method.class); boolean isConstructor = method.getKind() == ElementKind.CONSTRUCTOR; String builtinPkg = - annotation.pkg().isEmpty() ? BuiltinsPkg : BuiltinsPkg + "." + annotation.pkg(); + ownerAnnotation.pkg().isEmpty() ? BuiltinsPkg : BuiltinsPkg + "." + ownerAnnotation.pkg(); - Attribute.Class[] exceptionWrappers = getClassElementFromAnnotation(element, Builtin.class, WrapExceptionElementName); + SafeWrapException[] wrapExceptions = + wrapExceptionsExtractor.extract(element); Map parameterCounts = builtinTypesParametersCount(roundEnv); if (annotation.expandVarargs() != 0) { @@ -125,7 +134,7 @@ public void handleMethodElement(Element element, RoundEnvironment roundEnv) thro try { JavaFileObject gen = processingEnv.getFiler().createSourceFile(builtinMethodNode.fullyQualifiedName()); - MethodGenerator methodGen = new MethodGenerator(method, i, exceptionWrappers); + MethodGenerator methodGen = new MethodGenerator(method, i, wrapExceptions); generateBuiltinMethodNode( gen, methodGen, methodName, method.getSimpleName().toString(), annotation.description(), builtinMethodNode, ownerClass, parameterCounts); } catch (IOException ioe) { @@ -146,7 +155,7 @@ public void handleMethodElement(Element element, RoundEnvironment roundEnv) thro new ClassName(pkgElement.getQualifiedName(), tpeElement.getSimpleName()); JavaFileObject gen = processingEnv.getFiler().createSourceFile(builtinMethodNode.fullyQualifiedName()); - MethodGenerator methodGen = new MethodGenerator(method, 0, exceptionWrappers); + MethodGenerator methodGen = new MethodGenerator(method, 0, wrapExceptions); generateBuiltinMethodNode( gen, methodGen, methodName, method.getSimpleName().toString(), annotation.description(), builtinMethodNode, ownerClass, parameterCounts); } @@ -162,43 +171,6 @@ private Map builtinTypesParametersCount(RoundEnvironment roundE .collect(Collectors.toMap(e -> e.getSimpleName().toString(), e -> e.getAnnotation(BuiltinType.class).params().length)); } - /** - * Return the annotation type's element value, when its type is declared to involve Class (or Class[]). - * Sadly this cannot be simply retrieved by invoking the () method and one has to go through mirrors. - * Refer to blog - * for details. - * - * @param element element annotated with the given annotation - * @param annotationClass the class representation of the annotation - * @param annotationMethodName the name of the element in the annotation type having Class type - * @return even-length array of class annotation values, if present - */ - private Attribute.Class[] getClassElementFromAnnotation(Element element, Class annotationClass, String annotationMethodName) { - Element builtinElement = processingEnv.getElementUtils().getTypeElement(annotationClass.getName()); - TypeMirror builtinType = builtinElement.asType(); - - AnnotationValue value = null; - for (AnnotationMirror am: element.getAnnotationMirrors()) { - if (am.getAnnotationType().equals(builtinType)) { - for (Map.Entry entry : am.getElementValues().entrySet() ) { - if (annotationMethodName.equals(entry.getKey().getSimpleName().toString())) { - value = entry.getValue(); - break; - } - } - } - } - - if (value != null) { - List elems = ((List)value.getValue()); - if ((elems.size() % 2) != 0) { - throw new RuntimeException("Length of `" + annotationMethodName + "`value has to be even"); - } - return elems.toArray(new Attribute.Class[0]); - } - return new Attribute.Class[0]; - } - /** * Generates a Java class for @BuiltinMethod Node. * @@ -262,6 +234,12 @@ private void generateBuiltinMethodNode( "org.enso.interpreter.runtime.Context", "org.enso.interpreter.runtime.error.PanicException"); + /** + * Convert annotated method's variable to MethodParameter + * @param i position of the variable representing the parameter + * @param v variable element representing the parameter + * @return MethodParameter encapsulating the method's parameter info + */ private MethodParameter fromVariableElementToMethodParameter(int i, VariableElement v) { return new MethodParameter(i, v.getSimpleName().toString(), v.asType().toString(), v.getAnnotationMirrors().stream().map(am -> am.toString()).collect(Collectors.toList())); @@ -275,13 +253,13 @@ private class MethodGenerator { private final boolean isConstructor; private final int varargExpansion; private final boolean needsVarargExpansion; - private final Attribute.Class[] exceptionWrappers; + private final SafeWrapException[] exceptionWrappers; - public MethodGenerator(ExecutableElement method, int expandedVarargs, Attribute.Class[] exceptionWrappers) { + public MethodGenerator(ExecutableElement method, int expandedVarargs, SafeWrapException[] exceptionWrappers) { this(method, method.getParameters(), expandedVarargs, exceptionWrappers); } - private MethodGenerator(ExecutableElement method, List params, int expandedVarargs, Attribute.Class[] exceptionWrappers) { + private MethodGenerator(ExecutableElement method, List params, int expandedVarargs, SafeWrapException[] exceptionWrappers) { this( method.getReturnType().toString(), IntStream.range(0, method.getParameters().size()).mapToObj(i -> @@ -300,7 +278,7 @@ private MethodGenerator( boolean isConstructor, int expandedVarargs, boolean isVarargs, - Attribute.Class[] exceptionWrappers) { + SafeWrapException[] exceptionWrappers) { this.returnTpe = returnTpe; this.params = params; this.isStatic = isStatic; @@ -352,19 +330,10 @@ public List generateMethod(String name, String owner, Map x.names(Optional.empty())).toArray(), ", "); - wrappedBody.addAll(List.of( - " } catch (" + from + " exception) {", - " Builtins builtins = Context.get(this).getBuiltins();", - " throw new PanicException(builtins.error().make" + to + "(_this, " + errorParamsApplied + "), this);", - " }" - )); + for (int i=0; i < exceptionWrappers.length; i++) { + wrappedBody.addAll(exceptionWrappers[i].toCatchClause(params, builtinTypesParameterCounts)); } + wrappedBody.add(" }"); wrappedBody.add("}"); return wrappedBody; } else { @@ -376,12 +345,6 @@ public List generateMethod(String name, String owner, Map builtinTypesParameterCounts) { - String clazzSimple = fromAnnotationValueToClassName(clazz); - // `this` counts as 1 - return builtinTypesParameterCounts.getOrDefault(clazzSimple, 1); - } - } @@ -430,6 +393,155 @@ public Stream names(Optional expand) { } } + /** + * Wrapper around {@link Builtin.WrapException} annotation with all elements of Class type resolved. + * extracted + */ + private record SafeWrapException(Attribute.Class from, Attribute.Class to) { + + /** + * Generate a catch-clause that catches `from`, wraps it into `to` Enso type and rethrows the latter + * @param methodParameters list of all method's parameters, potentially to be applied to `to` constructor + * @param builtinTypesParameterCounts a map from builtin errors to the number of parameters in their constructors + * @return Lines representing the (unclosed) catch-clause catching the runtime `from` exception + */ + List toCatchClause(List methodParameters, Map builtinTypesParameterCounts) { + String from = fromAttributeToClassName(from()); + String to = fromAttributeToClassName(to()); + int toParamCount = errorParametersCount(to(), builtinTypesParameterCounts); + String errorParamsApplied = StringUtils.join(methodParameters.stream().limit(toParamCount - 1).flatMap(x -> x.names(Optional.empty())).toArray(), ", "); + return List.of( + " } catch (" + from + " exception) {", + " Builtins builtins = Context.get(this).getBuiltins();", + " throw new PanicException(builtins.error().make" + to + "(_this, " + errorParamsApplied + "), this);" + ); + } + + private String fromAttributeToClassName(Attribute.Class clazz) { + String[] clazzElements = clazz.classType.baseType().toString().split("\\."); + if (clazzElements.length == 0) { + return clazz.classType.baseType().toString(); + } else { + return clazzElements[clazzElements.length - 1]; + } + } + + private int errorParametersCount(Attribute.Class clazz, Map builtinTypesParameterCounts) { + String clazzSimple = fromAttributeToClassName(clazz); + // `this` counts as 1 + return builtinTypesParameterCounts.getOrDefault(clazzSimple, 1); + } + } + + /** + * Helper class that encapsulates retrieving the values of elements which type involves Class. + * Such elements' values cannot be retrieved by invoking the () method. Instead one has to go through mirrors. + * The logic has to deal with the following scenarios: + * - method with an individual annotation + * - method with multiple annotations of the same type, thus implicitly being annotation with + * container annotation + * - method with an explicit container annotation + * + * Refer to blog + * for details. + **/ + private class WrapExceptionExtractor { + + private static final String FromElementName = "from"; + private static final String ToElementName = "to"; + private static final String ValueElementName = "value"; + + private Class wrapExceptionAnnotationClass; + private Class wrapExceptionsAnnotationClass; + + public WrapExceptionExtractor( + Class wrapExceptionAnnotationClass, + Class wrapExceptionsAnnotationClass) { + this.wrapExceptionAnnotationClass = wrapExceptionAnnotationClass; + this.wrapExceptionsAnnotationClass = wrapExceptionsAnnotationClass; + } + + /** + * Extract {@link org.enso.interpreter.dsl.Builtin.WrapException} from the annotated element + * in a mirror-safe manner. + * + * @param element a method annotated with either {@link org.enso.interpreter.dsl.Builtin.WrapException} or + * {@link org.enso.interpreter.dsl.Builtin.WrapExceptions} + * @return An array of safely retrieved (potentially repeated) values of + * {@link org.enso.interpreter.dsl.Builtin.WrapException} annotation(s) + */ + public SafeWrapException[] extract(Element element) { + if (element.getAnnotation(wrapExceptionsAnnotationClass) != null) { + return extractClassElementFromAnnotationContainer(element, wrapExceptionsAnnotationClass); + } else if (element.getAnnotation(wrapExceptionAnnotationClass) != null) { + return extractClassElementFromAnnotation(element, wrapExceptionAnnotationClass); + } else { + return new SafeWrapException[0]; + } + } + + private SafeWrapException[] extractClassElementFromAnnotation(Element element, Class annotationClass) { + Element builtinElement = processingEnv.getElementUtils().getTypeElement(annotationClass.getCanonicalName()); + TypeMirror builtinType = builtinElement.asType(); + + List exceptionWrappers = new ArrayList<>(); + for (AnnotationMirror am: element.getAnnotationMirrors()) { + if (am.getAnnotationType().equals(builtinType)) { + Attribute.Class valueFrom = null; + Attribute.Class valueTo = null; + for (Map.Entry entry : am.getElementValues().entrySet() ) { + if (FromElementName.equals(entry.getKey().getSimpleName().toString())) { + valueFrom = (Attribute.Class)(entry.getValue()); + } else if (ToElementName.equals(entry.getKey().getSimpleName().toString())) { + valueTo = (Attribute.Class)(entry.getValue()); + } + } + if (valueFrom != null && valueTo != null) { + exceptionWrappers.add(new SafeWrapException(valueFrom, valueTo)); + } + } + } + return exceptionWrappers.toArray(new SafeWrapException[0]); + } + + + private SafeWrapException[] extractClassElementFromAnnotationContainer(Element element, Class annotationClass) { + + Element builtinElement = processingEnv.getElementUtils().getTypeElement(annotationClass.getCanonicalName()); + Types tpeUtils = processingEnv.getTypeUtils(); + TypeMirror builtinType = builtinElement.asType(); + + List wrappedExceptions = new ArrayList<>(); + for (AnnotationMirror am: element.getAnnotationMirrors()) { + if (tpeUtils.isSameType(am.getAnnotationType(), builtinType)) { + for (Map.Entry entry : am.getElementValues().entrySet()) { + if (ValueElementName.equals(entry.getKey().getSimpleName().toString())) { + Attribute.Array wrapExceptions = (Attribute.Array)entry.getValue(); + for (int i = 0; i p: attr.values) { + if (p.fst.getSimpleName().contentEquals(FromElementName)) { + valueFrom = (Attribute.Class)p.snd; + } else if (p.fst.getSimpleName().contentEquals(ToElementName)) { + valueTo = (Attribute.Class)p.snd; + } + } + if (valueFrom != null && valueTo != null) { + wrappedExceptions.add(new SafeWrapException(valueFrom, valueTo)); + } + } + break; + } + } + } + } + return wrappedExceptions.toArray(new SafeWrapException[0]); + } + + } + @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latest(); From b143559e2fd8efabec5a1260190e9a3e2fd92813 Mon Sep 17 00:00:00 2001 From: Hubert Plociniczak Date: Wed, 18 May 2022 16:02:49 +0200 Subject: [PATCH 08/11] Apply DSL to Ref methods --- .../expression/builtin/mutable/GetRefNode.java | 13 ------------- .../expression/builtin/mutable/NewRefNode.java | 14 -------------- .../expression/builtin/mutable/PutRefNode.java | 15 --------------- .../node/expression/builtin/mutable/Ref.java | 7 ------- .../org/enso/interpreter/runtime/data/Ref.java | 9 ++++++++- 5 files changed, 8 insertions(+), 50 deletions(-) delete mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/GetRefNode.java delete mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/NewRefNode.java delete mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/PutRefNode.java delete mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/Ref.java diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/GetRefNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/GetRefNode.java deleted file mode 100644 index 835a5ec254e9..000000000000 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/GetRefNode.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.enso.interpreter.node.expression.builtin.mutable; - -import com.oracle.truffle.api.nodes.Node; -import org.enso.interpreter.dsl.BuiltinMethod; -import org.enso.interpreter.runtime.data.Ref; - -@BuiltinMethod(type = "Ref", name = "get", description = "Gets the value stored in the reference.") -public class GetRefNode extends Node { - - Object execute(Ref _this) { - return _this.getValue(); - } -} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/NewRefNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/NewRefNode.java deleted file mode 100644 index df3da34c3229..000000000000 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/NewRefNode.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.enso.interpreter.node.expression.builtin.mutable; - -import com.oracle.truffle.api.nodes.Node; -import org.enso.interpreter.dsl.BuiltinMethod; -import org.enso.interpreter.runtime.data.Array; -import org.enso.interpreter.runtime.data.Ref; - -@BuiltinMethod(type = "Ref", name = "new", description = "Creates a new ref.") -public class NewRefNode extends Node { - - Object execute(Object _this, Object value) { - return new Ref(value); - } -} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/PutRefNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/PutRefNode.java deleted file mode 100644 index 86ed968c19e3..000000000000 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/PutRefNode.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.enso.interpreter.node.expression.builtin.mutable; - -import com.oracle.truffle.api.nodes.Node; -import org.enso.interpreter.dsl.BuiltinMethod; -import org.enso.interpreter.runtime.data.Ref; - -@BuiltinMethod(type = "Ref", name = "put", description = "Stores a new value in the reference.") -public class PutRefNode extends Node { - - Object execute(Ref _this, Object new_value) { - Object old = _this.getValue(); - _this.setValue(new_value); - return old; - } -} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/Ref.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/Ref.java deleted file mode 100644 index 8336cd33ad27..000000000000 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/Ref.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.enso.interpreter.node.expression.builtin.mutable; - -import org.enso.interpreter.dsl.BuiltinType; -import org.enso.interpreter.node.expression.builtin.Builtin; - -@BuiltinType(name = "Standard.Base.Data.Ref.Ref") -public class Ref extends Builtin {} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Ref.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Ref.java index 4044186087f3..4557f9dc0205 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Ref.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Ref.java @@ -6,6 +6,7 @@ import com.oracle.truffle.api.interop.TruffleObject; import com.oracle.truffle.api.library.ExportLibrary; import com.oracle.truffle.api.library.ExportMessage; +import org.enso.interpreter.dsl.Builtin; import org.enso.interpreter.runtime.Context; import org.enso.interpreter.runtime.callable.UnresolvedSymbol; import org.enso.interpreter.runtime.callable.function.Function; @@ -13,6 +14,7 @@ /** A mutable reference type. */ @ExportLibrary(MethodDispatchLibrary.class) +@Builtin(pkg = "mutable", stdlibName = "Standard.Base.Data.Ref.Ref") public class Ref implements TruffleObject { private volatile Object value; @@ -21,11 +23,13 @@ public class Ref implements TruffleObject { * * @param value the initial value to store in the reference. */ + @Builtin.Method(description = "Creates a new Ref") public Ref(Object value) { this.value = value; } /** @return the current value of the reference. */ + @Builtin.Method(name = "get", description = "Gets the value stored in the reference") public Object getValue() { return value; } @@ -35,8 +39,11 @@ public Object getValue() { * * @param value the value to store. */ - public void setValue(Object value) { + @Builtin.Method(name="put", description = "Stores a new value in the reference") + public Object setValue(Object value) { + Object old = this.value; this.value = value; + return old; } @ExportMessage From cbef1ec5897bf1714055a705b73559c5547e436f Mon Sep 17 00:00:00 2001 From: Hubert Plociniczak Date: Wed, 18 May 2022 16:11:44 +0200 Subject: [PATCH 09/11] Make formatter happy --- .../src/main/java/org/enso/interpreter/runtime/data/Ref.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Ref.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Ref.java index 4557f9dc0205..935a50616f61 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Ref.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Ref.java @@ -39,7 +39,7 @@ public Object getValue() { * * @param value the value to store. */ - @Builtin.Method(name="put", description = "Stores a new value in the reference") + @Builtin.Method(name = "put", description = "Stores a new value in the reference") public Object setValue(Object value) { Object old = this.value; this.value = value; From d6a3614e8f01148157c11750da891810cc2bfb18 Mon Sep 17 00:00:00 2001 From: Hubert Plociniczak Date: Wed, 18 May 2022 18:18:43 +0200 Subject: [PATCH 10/11] Fix a problem in separate compilation Discovered a problem with separate compilation when generating exception wrappers. The latter requires information about all builtin types annotations but that is not present in separate compilation. Instead we do a similar trick as in other processors - we read metadata and diff the results. The change is bigger than a single method because I didn't want to write a yet another parser of metadata entries. Instead, every annotation processor has to provide that implementation and it can be re-used. --- .../dsl/BuiltinsMetadataProcessor.java | 33 +++++++++--- .../interpreter/dsl/BuiltinsProcessor.java | 47 +++++++++++++++-- .../enso/interpreter/dsl/MethodProcessor.java | 25 ++++++++- .../enso/interpreter/dsl/TypeProcessor.java | 51 ++++++++++++++----- 4 files changed, 129 insertions(+), 27 deletions(-) diff --git a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/BuiltinsMetadataProcessor.java b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/BuiltinsMetadataProcessor.java index 6ca014bf19e3..8d361bd99d7a 100644 --- a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/BuiltinsMetadataProcessor.java +++ b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/BuiltinsMetadataProcessor.java @@ -2,7 +2,6 @@ import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; -import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.tools.Diagnostic; import javax.tools.FileObject; @@ -19,7 +18,8 @@ * generating code, {@code BuiltinsMetadataProcessor} detects when the processing of the last * annotation in the round is being processed and allows for dumping any collected metadata once. */ -public abstract class BuiltinsMetadataProcessor extends AbstractProcessor { +public abstract class BuiltinsMetadataProcessor + extends AbstractProcessor { /** * Processes annotated elements, generating code for each of them, if necessary. @@ -44,7 +44,7 @@ public final boolean process(Set annotations, RoundEnviro // we read the exisitng metadata. // Deletes/renaming are still not going to work nicely but that would be the same case // if we were writing metadata information per source file anyway. - Map pastEntries; + Map pastEntries; try { FileObject existingFile = processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, "", metadataPath()); @@ -52,7 +52,8 @@ public final boolean process(Set annotations, RoundEnviro pastEntries = new BufferedReader(new InputStreamReader(resource, StandardCharsets.UTF_8)) .lines() - .collect(Collectors.toMap(l -> l.split(":")[0], Function.identity())); + .map(l -> toMetadataEntry(l)) + .collect(Collectors.toMap(e -> e.key(), Function.identity())); } } catch (NoSuchFileException notFoundException) { // This is the first time we are generating the metadata file, ignore the exception. @@ -70,8 +71,8 @@ public final boolean process(Set annotations, RoundEnviro try { storeMetadata(writer, pastEntries); // Dump past entries, to workaround separate compilation + annotation processing issues - for (String value : pastEntries.values()) { - writer.append(value + "\n"); + for (MetadataEntry value : pastEntries.values()) { + writer.append(value.toString() + "\n"); } } finally { writer.close(); @@ -104,7 +105,7 @@ public final boolean process(Set annotations, RoundEnviro * should not be appended to {@code writer} should be removed * @throws IOException */ - protected abstract void storeMetadata(Writer writer, Map pastEntries) + protected abstract void storeMetadata(Writer writer, Map pastEntries) throws IOException; /** @@ -123,4 +124,22 @@ protected abstract boolean handleProcess( * processing is done. */ protected abstract void cleanup(); + + /** + * A common interface that all metadata entries have to provide. + * In the future can avoid plain text representation. + */ + public interface MetadataEntry { + String toString(); + + String key(); + } + + /** + * A conversion method for a single raw metadata entry. For now represented as a string. + * + * @param line raw representation of the metadata entry + * @return parsed metedata entry, specific to the given processor + */ + protected abstract T toMetadataEntry(String line); } diff --git a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/BuiltinsProcessor.java b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/BuiltinsProcessor.java index e370e533a5b8..ed5e13e58d12 100644 --- a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/BuiltinsProcessor.java +++ b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/BuiltinsProcessor.java @@ -13,11 +13,13 @@ import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Types; import javax.tools.Diagnostic; +import javax.tools.FileObject; import javax.tools.JavaFileObject; +import javax.tools.StandardLocation; -import java.io.IOException; -import java.io.PrintWriter; +import java.io.*; import java.lang.annotation.Annotation; +import java.nio.charset.StandardCharsets; import java.util.*; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -164,11 +166,40 @@ public void handleMethodElement(Element element, RoundEnvironment roundEnv) thro } } + /** + * Returns a map of builtin types and the number of their paramneters. + * Takes into account the possibility of separate compilation by reading entries from metadate, if any. + * @param roundEnv current round environment + * @return a map from a builtin type name to the number of its parameters + */ private Map builtinTypesParametersCount(RoundEnvironment roundEnv) { - return roundEnv + // For separate compilation we need to read that information from BuiltinTypes metadata file + Map pastEntries; + try { + FileObject existingFile = + processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, "", TypeProcessor.META_PATH); + + try (InputStream resource = existingFile.openInputStream()) { + pastEntries = + new BufferedReader(new InputStreamReader(resource, StandardCharsets.UTF_8)) + .lines() + .map(l -> TypeProcessor.fromStringToMetadataEntry(l)) + .collect(Collectors.toMap(e -> e.key().replaceAll("_", ""), e -> e.paramNames().length)); + } + } catch (IOException e) { + // Ignore, this is a clean run + pastEntries = new HashMap<>(); + } + + Map currentRoundEntries = + roundEnv .getElementsAnnotatedWith(BuiltinType.class) .stream() .collect(Collectors.toMap(e -> e.getSimpleName().toString(), e -> e.getAnnotation(BuiltinType.class).params().length)); + + pastEntries.forEach((k, v) -> currentRoundEntries.merge(k, v, (v1, v2) -> v1)); + + return currentRoundEntries; } /** @@ -409,11 +440,17 @@ List toCatchClause(List methodParameters, Map x.names(Optional.empty())).toArray(), ", "); + List errorParameters = + methodParameters + .stream() + .limit(toParamCount - 1) + .flatMap(x -> x.names(Optional.empty())) + .collect(Collectors.toList()); + String errorParameterCode = errorParameters.isEmpty() ? "" : ", " + StringUtils.join(errorParameters, ", "); return List.of( " } catch (" + from + " exception) {", " Builtins builtins = Context.get(this).getBuiltins();", - " throw new PanicException(builtins.error().make" + to + "(_this, " + errorParamsApplied + "), this);" + " throw new PanicException(builtins.error().make" + to + "(_this" + errorParameterCode + "), this);" ); } diff --git a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/MethodProcessor.java b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/MethodProcessor.java index 08cd9f3dd19f..1524899f1e68 100644 --- a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/MethodProcessor.java +++ b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/MethodProcessor.java @@ -1,5 +1,6 @@ package org.enso.interpreter.dsl; +import org.apache.commons.lang3.StringUtils; import org.enso.interpreter.dsl.model.MethodDefinition; import javax.annotation.processing.*; @@ -20,7 +21,7 @@ */ @SupportedAnnotationTypes("org.enso.interpreter.dsl.BuiltinMethod") @ServiceProvider(service = Processor.class) -public class MethodProcessor extends BuiltinsMetadataProcessor { +public class MethodProcessor extends BuiltinsMetadataProcessor { private final Map> builtinMethods = new HashMap<>(); @@ -436,7 +437,7 @@ private boolean generateWarningsCheck( * should not be appended to {@code writer} should be removed * @throws IOException */ - protected void storeMetadata(Writer writer, Map pastEntries) throws IOException { + protected void storeMetadata(Writer writer, Map pastEntries) throws IOException { for (Filer f : builtinMethods.keySet()) { for (Map.Entry entry : builtinMethods.get(f).entrySet()) { writer.append(entry.getKey() + ":" + entry.getValue() + "\n"); @@ -485,4 +486,24 @@ private String capitalize(String name) { public SourceVersion getSupportedSourceVersion() { return SourceVersion.latest(); } + + public record MethodMetadataEntry(String fullEnsoName, String clazzName) implements MetadataEntry { + + @Override + public String toString() { + return fullEnsoName + ":" + clazzName; + } + + @Override + public String key() { + return fullEnsoName; + } + } + + @Override + protected MethodMetadataEntry toMetadataEntry(String line) { + String[] elements = line.split(":"); + if (elements.length != 2) throw new RuntimeException("invalid builtin metadata entry: " + line); + return new MethodMetadataEntry(elements[0], elements[1]); + } } diff --git a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/TypeProcessor.java b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/TypeProcessor.java index ac93ff2985fd..2aaef110cda9 100644 --- a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/TypeProcessor.java +++ b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/TypeProcessor.java @@ -15,7 +15,7 @@ @SupportedAnnotationTypes("org.enso.interpreter.dsl.BuiltinType") @ServiceProvider(service = Processor.class) -public class TypeProcessor extends BuiltinsMetadataProcessor { +public class TypeProcessor extends BuiltinsMetadataProcessor { private final Map> builtinTypes = new HashMap<>(); @@ -84,7 +84,7 @@ protected boolean handleProcess( * @throws IOException */ @Override - protected void storeMetadata(Writer writer, Map pastEntries) throws IOException { + protected void storeMetadata(Writer writer, Map pastEntries) throws IOException { JavaFileObject gen = processingEnv.getFiler().createSourceFile(ConstantsGenFullClassname); for (Filer f : builtinTypes.keySet()) { System.out.println("foo" + f.toString()); @@ -126,17 +126,16 @@ protected void storeMetadata(Writer writer, Map pastEntries) thr pastEntries .values() .forEach( - entry -> { - String[] elements = entry.split(":"); - if (elements.length == 4 && !elements[3].isEmpty()) { - out.println( - " public static final String " - + elements[0].toUpperCase() - + " = \"" - + elements[3] - + "\";"); - } - }); + entry -> + entry.stdlibName().ifPresent(n -> + out.println( + " public static final String " + + entry.ensoName().toUpperCase() + + " = \"" + + n + + "\";") + ) + ); out.println(); out.println("}"); @@ -171,4 +170,30 @@ protected void cleanup() { public SourceVersion getSupportedSourceVersion() { return SourceVersion.latest(); } + + public record TypeMetadataEntry(String ensoName, String clazzName, String[] paramNames, Optional stdlibName) implements MetadataEntry { + + @Override + public String toString() { + return ensoName + ":" + clazzName + ":" + StringUtils.join(paramNames, ",") + ":" + stdlibName.orElse(""); + } + + @Override + public String key() { + return ensoName; + } + } + + @Override + protected TypeMetadataEntry toMetadataEntry(String line) { + return fromStringToMetadataEntry(line); + } + + public static TypeMetadataEntry fromStringToMetadataEntry(String line) { + String[] elements = line.split(":"); + if (elements.length < 2) throw new RuntimeException("invalid builtin metadata entry: " + line); + String[] params = elements.length >= 3 ? elements[2].split(",") : new String[0]; + Optional stdLibName = elements.length == 4 ? Optional.of(elements[3]) : Optional.empty(); + return new TypeMetadataEntry(elements[0], elements[1], params, stdLibName); + } } From c9fd0542abd1e6dbbdcbb807601bc542d44147b3 Mon Sep 17 00:00:00 2001 From: Hubert Plociniczak Date: Thu, 19 May 2022 10:12:14 +0200 Subject: [PATCH 11/11] Fix formatting --- .../org/enso/interpreter/dsl/BuiltinsMetadataProcessor.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/BuiltinsMetadataProcessor.java b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/BuiltinsMetadataProcessor.java index 8d361bd99d7a..d28daecd9825 100644 --- a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/BuiltinsMetadataProcessor.java +++ b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/BuiltinsMetadataProcessor.java @@ -126,8 +126,8 @@ protected abstract boolean handleProcess( protected abstract void cleanup(); /** - * A common interface that all metadata entries have to provide. - * In the future can avoid plain text representation. + * A common interface that all metadata entries have to provide. In the future can avoid plain + * text representation. */ public interface MetadataEntry { String toString();