From 6e331205da678f18125ef9eed9abccb807b59b71 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Sun, 6 Oct 2024 21:19:22 +0200 Subject: [PATCH 01/93] Add parser-dsl and parser-processor projects --- build.sbt | 17 +++++++++++++++++ .../src/main/java/module-info.java | 4 ++++ .../src/main/java/module-info.java | 4 ++++ .../src/main/java/module-info.java | 1 + 4 files changed, 26 insertions(+) create mode 100644 engine/runtime-parser-dsl/src/main/java/module-info.java create mode 100644 engine/runtime-parser-processor/src/main/java/module-info.java diff --git a/build.sbt b/build.sbt index ca40ce98b6ff..c27d0a567ef2 100644 --- a/build.sbt +++ b/build.sbt @@ -3117,6 +3117,23 @@ lazy val `runtime-parser` = .dependsOn(`syntax-rust-definition`) .dependsOn(`persistance`) .dependsOn(`persistance-dsl` % "provided") + .dependsOn(`runtime-parser-dsl`) + +lazy val `runtime-parser-dsl` = + (project in file("engine/runtime-parser-dsl")) + .enablePlugins(JPMSPlugin) + .settings( + frgaalJavaCompilerSetting, + ) + +lazy val `runtime-parser-processor` = + (project in file("engine/runtime-parser-processor")) + .enablePlugins(JPMSPlugin) + .settings( + frgaalJavaCompilerSetting, + ) + .dependsOn(`runtime-parser`) + .dependsOn(`runtime-parser-dsl`) lazy val `runtime-compiler` = (project in file("engine/runtime-compiler")) diff --git a/engine/runtime-parser-dsl/src/main/java/module-info.java b/engine/runtime-parser-dsl/src/main/java/module-info.java new file mode 100644 index 000000000000..677307f985cd --- /dev/null +++ b/engine/runtime-parser-dsl/src/main/java/module-info.java @@ -0,0 +1,4 @@ +module org.enso.runtime.parser.dsl { + exports org.enso.runtime.parser.dsl; + requires compiler; +} diff --git a/engine/runtime-parser-processor/src/main/java/module-info.java b/engine/runtime-parser-processor/src/main/java/module-info.java new file mode 100644 index 000000000000..596e55961e60 --- /dev/null +++ b/engine/runtime-parser-processor/src/main/java/module-info.java @@ -0,0 +1,4 @@ +module org.enso.runtime.parser.processor { + requires org.enso.runtime.parser.dsl; + requires org.enso.runtime.parser; +} diff --git a/engine/runtime-parser/src/main/java/module-info.java b/engine/runtime-parser/src/main/java/module-info.java index 79de84d6dc8f..31fa29a2c6d2 100644 --- a/engine/runtime-parser/src/main/java/module-info.java +++ b/engine/runtime-parser/src/main/java/module-info.java @@ -2,6 +2,7 @@ requires org.enso.syntax; requires scala.library; requires org.enso.persistance; + requires static org.enso.runtime.parser.dsl; exports org.enso.compiler.core; exports org.enso.compiler.core.ir; From 69f9247a0b17fb4522d996c07de6d531ceef8623 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Sun, 6 Oct 2024 21:19:33 +0200 Subject: [PATCH 02/93] Add JModule --- .../java/org/enso/runtime/parser/dsl/IRChild.java | 12 ++++++++++++ .../java/org/enso/runtime/parser/dsl/IRNode.java | 13 +++++++++++++ .../java/org/enso/compiler/core/ir/JModule.java | 15 +++++++++++++++ 3 files changed, 40 insertions(+) create mode 100644 engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/IRChild.java create mode 100644 engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/IRNode.java create mode 100644 engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JModule.java diff --git a/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/IRChild.java b/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/IRChild.java new file mode 100644 index 000000000000..bfbe1fb545e2 --- /dev/null +++ b/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/IRChild.java @@ -0,0 +1,12 @@ +package org.enso.runtime.parser.dsl; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.METHOD) +public @interface IRChild { + boolean required() default true; +} diff --git a/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/IRNode.java b/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/IRNode.java new file mode 100644 index 000000000000..8d6e0c60a4e3 --- /dev/null +++ b/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/IRNode.java @@ -0,0 +1,13 @@ +package org.enso.runtime.parser.dsl; + + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.TYPE) +public @interface IRNode { + String name(); +} diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JModule.java b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JModule.java new file mode 100644 index 000000000000..879ec8134bc3 --- /dev/null +++ b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JModule.java @@ -0,0 +1,15 @@ +package org.enso.compiler.core.ir; + +import org.enso.compiler.core.ir.module.scope.Export; +import org.enso.compiler.core.ir.module.scope.Import; +import org.enso.runtime.parser.dsl.IRChild; +import org.enso.runtime.parser.dsl.IRNode; +import scala.collection.immutable.List; + +@IRNode(name = "Module") +public abstract class JModule { + @IRChild + public abstract List imports(); + @IRChild + public abstract List exports(); +} From 0e4ceb0abe0a1b4d5ca76fc2a38d91da5d3aaf29 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 7 Oct 2024 20:56:44 +0200 Subject: [PATCH 03/93] Fix module declaration - no compiler module dependency --- engine/runtime-parser-dsl/src/main/java/module-info.java | 1 - 1 file changed, 1 deletion(-) diff --git a/engine/runtime-parser-dsl/src/main/java/module-info.java b/engine/runtime-parser-dsl/src/main/java/module-info.java index 677307f985cd..c1e759c70246 100644 --- a/engine/runtime-parser-dsl/src/main/java/module-info.java +++ b/engine/runtime-parser-dsl/src/main/java/module-info.java @@ -1,4 +1,3 @@ module org.enso.runtime.parser.dsl { exports org.enso.runtime.parser.dsl; - requires compiler; } From 9891eb7787d94554999b65e316acaa88de077a19 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 7 Oct 2024 21:38:45 +0200 Subject: [PATCH 04/93] Add annotation processor testing. --- build.sbt | 19 +++++++++-- .../org/enso/runtime/parser/dsl/IRNode.java | 1 - .../src/main/java/module-info.java | 1 + .../runtime/parser/processor/IRProcessor.java | 25 ++++++++++++++ .../processor/test/TestIRProcessor.java | 34 +++++++++++++++++++ .../org/enso/compiler/core/ir/JModule.java | 1 + 6 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java create mode 100644 engine/runtime-parser-processor/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java diff --git a/build.sbt b/build.sbt index c27d0a567ef2..2671ddb09ccb 100644 --- a/build.sbt +++ b/build.sbt @@ -342,6 +342,8 @@ lazy val enso = (project in file(".")) `runtime-compiler`, `runtime-integration-tests`, `runtime-parser`, + `runtime-parser-dsl`, + `runtime-parser-processor`, `runtime-language-arrow`, `runtime-language-epb`, `runtime-instrument-common`, @@ -3111,7 +3113,8 @@ lazy val `runtime-parser` = ), Compile / internalModuleDependencies := Seq( (`syntax-rust-definition` / Compile / exportedModule).value, - (`persistance` / Compile / exportedModule).value + (`persistance` / Compile / exportedModule).value, + (`runtime-parser-dsl` / Compile / exportedModule).value ) ) .dependsOn(`syntax-rust-definition`) @@ -3123,7 +3126,7 @@ lazy val `runtime-parser-dsl` = (project in file("engine/runtime-parser-dsl")) .enablePlugins(JPMSPlugin) .settings( - frgaalJavaCompilerSetting, + frgaalJavaCompilerSetting ) lazy val `runtime-parser-processor` = @@ -3131,6 +3134,18 @@ lazy val `runtime-parser-processor` = .enablePlugins(JPMSPlugin) .settings( frgaalJavaCompilerSetting, + commands += WithDebugCommand.withDebug, + Test / fork := true, + libraryDependencies ++= Seq( + "junit" % "junit" % junitVersion % Test, + "com.github.sbt" % "junit-interface" % junitIfVersion % Test, + "org.hamcrest" % "hamcrest-all" % hamcrestVersion % Test, + "com.google.testing.compile" % "compile-testing" % "0.21.0" % Test + ), + Compile / internalModuleDependencies := Seq( + (`runtime-parser` / Compile / exportedModule).value, + (`runtime-parser-dsl` / Compile / exportedModule).value + ) ) .dependsOn(`runtime-parser`) .dependsOn(`runtime-parser-dsl`) diff --git a/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/IRNode.java b/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/IRNode.java index 8d6e0c60a4e3..c75a806ffc15 100644 --- a/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/IRNode.java +++ b/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/IRNode.java @@ -1,6 +1,5 @@ package org.enso.runtime.parser.dsl; - import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; diff --git a/engine/runtime-parser-processor/src/main/java/module-info.java b/engine/runtime-parser-processor/src/main/java/module-info.java index 596e55961e60..44d5067ce381 100644 --- a/engine/runtime-parser-processor/src/main/java/module-info.java +++ b/engine/runtime-parser-processor/src/main/java/module-info.java @@ -1,4 +1,5 @@ module org.enso.runtime.parser.processor { + requires java.compiler; requires org.enso.runtime.parser.dsl; requires org.enso.runtime.parser; } diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java new file mode 100644 index 000000000000..57ebcc670ca6 --- /dev/null +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java @@ -0,0 +1,25 @@ +package org.enso.runtime.parser.processor; + +import java.util.Set; +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.TypeElement; + +@SupportedAnnotationTypes({ + "org.enso.runtime.parser.dsl.IRNode", + "org.enso.runtime.parser.dsl.IRChild" +}) +public class IRProcessor extends AbstractProcessor { + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latest(); + } + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + throw new UnsupportedOperationException("unimplemented"); + } +} diff --git a/engine/runtime-parser-processor/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java b/engine/runtime-parser-processor/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java new file mode 100644 index 000000000000..f22a1ff8124e --- /dev/null +++ b/engine/runtime-parser-processor/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java @@ -0,0 +1,34 @@ +package org.enso.runtime.parser.processor.test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; + +import com.google.testing.compile.Compilation; +import com.google.testing.compile.Compiler; +import com.google.testing.compile.JavaFileObjects; +import org.enso.runtime.parser.processor.IRProcessor; +import org.junit.Assert; +import org.junit.Test; + +public class TestIRProcessor { + @Test + public void testIRProcessorFailsWithUnimplemented() { + var src = + JavaFileObjects.forSourceLines( + "HelloWorld", + "import org.enso.runtime.parser.dsl.IRNode;", + "@IRNode(name = \"HelloWorld\")", + "final class HelloWorld {", + "}"); + var compiler = Compiler.javac().withProcessors(new IRProcessor()); + Compilation compilation = null; + try { + compilation = compiler.compile(src); + Assert.fail("Expected compilation to fail with UnimplementedException"); + } catch (Exception e) { + // nop + assertThat(compilation, is(nullValue())); + } + } +} diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JModule.java b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JModule.java index 879ec8134bc3..ccaf3d7ff500 100644 --- a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JModule.java +++ b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JModule.java @@ -10,6 +10,7 @@ public abstract class JModule { @IRChild public abstract List imports(); + @IRChild public abstract List exports(); } From a3bf203c035831d6ca204329720f3db305815694 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Wed, 9 Oct 2024 20:37:41 +0200 Subject: [PATCH 05/93] Rewrite some IR elements to IRNode annotated interfaces --- .../compiler/core/ir/JDefinitionArgument.java | 21 +++++++ .../enso/compiler/core/ir/JExpression.java | 34 +++++++++++ .../org/enso/compiler/core/ir/JModule.java | 14 ++--- .../java/org/enso/compiler/core/ir/JName.java | 46 +++++++++++++++ .../enso/compiler/core/ir/module/JScope.java | 8 +++ .../core/ir/module/scope/JDefinition.java | 59 +++++++++++++++++++ .../core/ir/module/scope/JExport.java | 53 +++++++++++++++++ .../core/ir/module/scope/JImport.java | 51 ++++++++++++++++ 8 files changed, 279 insertions(+), 7 deletions(-) create mode 100644 engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JDefinitionArgument.java create mode 100644 engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JExpression.java create mode 100644 engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JName.java create mode 100644 engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/JScope.java create mode 100644 engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/scope/JDefinition.java create mode 100644 engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/scope/JExport.java create mode 100644 engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/scope/JImport.java diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JDefinitionArgument.java b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JDefinitionArgument.java new file mode 100644 index 000000000000..01fdb876e0d8 --- /dev/null +++ b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JDefinitionArgument.java @@ -0,0 +1,21 @@ +package org.enso.compiler.core.ir; + +import org.enso.compiler.core.IR; +import org.enso.runtime.parser.dsl.IRChild; +import org.enso.runtime.parser.dsl.IRNode; + +public interface JDefinitionArgument extends IR { + @IRChild + JName name(); + + @IRChild(required = false) + JExpression ascribedType(); + + @IRChild(required = false) + JExpression defaultValue(); + + boolean suspended(); + + @IRNode + interface JSpecified extends JDefinitionArgument {} +} diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JExpression.java b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JExpression.java new file mode 100644 index 000000000000..aa6e4234363e --- /dev/null +++ b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JExpression.java @@ -0,0 +1,34 @@ +package org.enso.compiler.core.ir; + +import java.util.List; +import org.enso.compiler.core.IR; +import org.enso.runtime.parser.dsl.IRChild; +import org.enso.runtime.parser.dsl.IRNode; + +public interface JExpression extends IR { + @IRNode + interface JBlock extends JExpression { + @IRChild + List expressions(); + + @IRChild + JExpression returnValue(); + + boolean suspended(); + } + + /** + * A binding expression of the form `name = expr` + * + *

To create a binding that binds no available name, set the name of the binding to an + * [[Name.Blank]] (e.g. _ = foo a b). + */ + @IRNode + interface JBinding extends JExpression { + @IRChild + JName name(); + + @IRChild + JExpression expression(); + } +} diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JModule.java b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JModule.java index ccaf3d7ff500..a3bac2df62e5 100644 --- a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JModule.java +++ b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JModule.java @@ -1,16 +1,16 @@ package org.enso.compiler.core.ir; -import org.enso.compiler.core.ir.module.scope.Export; -import org.enso.compiler.core.ir.module.scope.Import; +import java.util.List; +import org.enso.compiler.core.ir.module.scope.JExport; +import org.enso.compiler.core.ir.module.scope.JImport; import org.enso.runtime.parser.dsl.IRChild; import org.enso.runtime.parser.dsl.IRNode; -import scala.collection.immutable.List; -@IRNode(name = "Module") -public abstract class JModule { +@IRNode +public interface JModule { @IRChild - public abstract List imports(); + List imports(); @IRChild - public abstract List exports(); + List exports(); } diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JName.java b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JName.java new file mode 100644 index 000000000000..7fc979e7f4e8 --- /dev/null +++ b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JName.java @@ -0,0 +1,46 @@ +package org.enso.compiler.core.ir; + +import java.util.List; +import java.util.stream.Collectors; +import org.enso.compiler.core.ir.module.scope.JDefinition; +import org.enso.runtime.parser.dsl.IRChild; +import org.enso.runtime.parser.dsl.IRNode; + +public interface JName extends JExpression { + String name(); + + boolean isMethod(); + + @IRNode + interface JBlank extends JName {} + + @IRNode + interface JLiteral extends JName { + @IRChild(required = false) + JName originalName(); + } + + @IRNode + interface JQualified extends JName { + @IRChild + List parts(); + + @Override + default String name() { + return parts().stream().map(JName::name).collect(Collectors.joining(".")); + } + } + + @IRNode + interface JSelf extends JName { + boolean synthetic(); + } + + interface JAnnotation extends JName, JDefinition {} + + @IRNode + interface JGenericAnnotation extends JAnnotation { + @IRChild + JExpression expression(); + } +} diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/JScope.java b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/JScope.java new file mode 100644 index 000000000000..579de0d2eeca --- /dev/null +++ b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/JScope.java @@ -0,0 +1,8 @@ +package org.enso.compiler.core.ir.module; + +import org.enso.compiler.core.IR; +import org.enso.runtime.parser.dsl.IRNode; + +/** A representation of constructs that can only occur in the top-level module scope */ +@IRNode +public interface JScope extends IR {} diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/scope/JDefinition.java b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/scope/JDefinition.java new file mode 100644 index 000000000000..47eefdf9147b --- /dev/null +++ b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/scope/JDefinition.java @@ -0,0 +1,59 @@ +package org.enso.compiler.core.ir.module.scope; + +import java.util.List; +import org.enso.compiler.core.IR; +import org.enso.compiler.core.ir.JDefinitionArgument; +import org.enso.compiler.core.ir.JName; +import org.enso.compiler.core.ir.module.JScope; +import org.enso.runtime.parser.dsl.IRChild; +import org.enso.runtime.parser.dsl.IRNode; + +public interface JDefinition extends JScope { + @IRNode + interface JType extends JDefinition { + @IRChild + JName name(); + + @IRChild + List params(); + + @IRChild + List members(); + } + + /** The definition of an atom constructor and its associated arguments. */ + @IRNode + interface JData extends JDefinition { + /** The name of the atom */ + @IRChild + JName name(); + + /** The arguments of the atom constructor. */ + @IRChild + List arguments(); + + @IRChild + List annotations(); + + /** If the constructor is project-private. */ + boolean isPrivate(); + } + + /** + * The definition of a complex type definition that may contain multiple atom and method + * definitions. + */ + @IRNode + interface JSugaredType extends JDefinition { + + /** The name of the complex type. */ + @IRChild + JName name(); + + @IRChild + List arguments(); + + @IRChild + List body(); + } +} diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/scope/JExport.java b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/scope/JExport.java new file mode 100644 index 000000000000..3e165ecfd263 --- /dev/null +++ b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/scope/JExport.java @@ -0,0 +1,53 @@ +package org.enso.compiler.core.ir.module.scope; + +import java.util.List; +import org.enso.compiler.core.ir.JName; +import org.enso.compiler.core.ir.module.JScope; +import org.enso.runtime.parser.dsl.IRChild; +import org.enso.runtime.parser.dsl.IRNode; + +public interface JExport extends JScope { + @IRNode + interface JModule extends JExport { + @IRChild + JName.JQualified name(); + + @IRChild(required = false) + JName.JLiteral rename(); + + @IRChild(required = false) + List onlyNames(); + + boolean isSynthetic(); + + /** + * Gets the name of the module visible in the importing scope, either the original name or the + * rename. + * + * @return the name of this export visible in code + */ + default JName getSimpleName() { + if (rename() != null) { + return rename(); + } else { + return name().parts().get(name().parts().size() - 1); + } + } + + /** + * Checks whether the export statement allows use of the given exported name. + * + *

Note that it does not verify if the name is actually exported by the module, only checks + * if it is syntactically allowed. + * + * @param name the name to check + * @return whether the name could be accessed or not + */ + default boolean allowsAccess(String name) { + if (onlyNames() != null) { + return onlyNames().stream().anyMatch(n -> n.name().equalsIgnoreCase(name)); + } + return true; + } + } +} diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/scope/JImport.java b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/scope/JImport.java new file mode 100644 index 000000000000..e356ce84a3a9 --- /dev/null +++ b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/scope/JImport.java @@ -0,0 +1,51 @@ +package org.enso.compiler.core.ir.module.scope; + +import java.util.List; +import org.enso.compiler.core.ir.JName; +import org.enso.compiler.core.ir.module.JScope; +import org.enso.runtime.parser.dsl.IRChild; +import org.enso.runtime.parser.dsl.IRNode; + +/** Module-level import statements. */ +public interface JImport extends JScope { + @IRNode + interface JModule extends JImport { + @IRChild + JName.JQualified name(); + + @IRChild(required = false) + JName.JLiteral rename(); + + boolean isAll(); + + @IRChild(required = false) + List onlyNames(); + + @IRChild(required = false) + List hiddenNames(); + + boolean isSynthetic(); + + /** + * Checks whether the import statement allows use of the given exported name. + * + *

Note that it does not verify if the name is actually exported by the module, only checks + * if it is syntactically allowed. + * + * @param name the name to check + * @return whether the name could be accessed or not + */ + default boolean allowsAccess(String name) { + if (!isAll()) { + return false; + } + if (onlyNames() != null) { + return onlyNames().stream().anyMatch(n -> n.name().equals(name)); + } + if (hiddenNames() != null) { + return hiddenNames().stream().noneMatch(n -> n.name().equals(name)); + } + return true; + } + } +} From 08b0571164d0aeb42c9442373de7cba8698510b9 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Wed, 9 Oct 2024 20:38:18 +0200 Subject: [PATCH 06/93] IRProcessor does some sanity checks --- .../runtime/parser/processor/IRProcessor.java | 30 ++++++- .../processor/test/TestIRProcessor.java | 80 ++++++++++++++----- 2 files changed, 88 insertions(+), 22 deletions(-) diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java index 57ebcc670ca6..20419a3ca01e 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java @@ -5,7 +5,12 @@ import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.lang.model.SourceVersion; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; +import javax.tools.Diagnostic.Kind; +import org.enso.runtime.parser.dsl.IRNode; @SupportedAnnotationTypes({ "org.enso.runtime.parser.dsl.IRNode", @@ -20,6 +25,29 @@ public SourceVersion getSupportedSourceVersion() { @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { - throw new UnsupportedOperationException("unimplemented"); + var irNodeElems = roundEnv.getElementsAnnotatedWith(IRNode.class); + for (var irNodeElem : irNodeElems) { + processIrNode(irNodeElem); + } + return true; + } + + private void processIrNode(Element irNodeElem) { + if (irNodeElem.getKind() != ElementKind.INTERFACE) { + printError("IRNode annotation can only be applied to interfaces", irNodeElem); + } + if (!isSubtypeOfIR(irNodeElem.asType())) { + printError("Interface annotated with @IRNode must be a subtype of IR interface", irNodeElem); + } + } + + private boolean isSubtypeOfIR(TypeMirror type) { + var irType = + processingEnv.getElementUtils().getTypeElement("org.enso.compiler.core.IR").asType(); + return processingEnv.getTypeUtils().isAssignable(type, irType); + } + + private void printError(String msg, Element elem) { + processingEnv.getMessager().printMessage(Kind.ERROR, msg, elem); } } diff --git a/engine/runtime-parser-processor/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java b/engine/runtime-parser-processor/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java index f22a1ff8124e..b618c05d238f 100644 --- a/engine/runtime-parser-processor/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java +++ b/engine/runtime-parser-processor/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java @@ -1,34 +1,72 @@ package org.enso.runtime.parser.processor.test; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.nullValue; - -import com.google.testing.compile.Compilation; +import com.google.testing.compile.CompilationSubject; import com.google.testing.compile.Compiler; import com.google.testing.compile.JavaFileObjects; import org.enso.runtime.parser.processor.IRProcessor; -import org.junit.Assert; import org.junit.Test; public class TestIRProcessor { @Test - public void testIRProcessorFailsWithUnimplemented() { + public void simpleIRNodeWithoutChildren_CompilationSucceeds() { + var src = + JavaFileObjects.forSourceString( + "JName", + """ + import org.enso.runtime.parser.dsl.IRNode; + import org.enso.compiler.core.IR; + @IRNode + public interface JName extends IR {} + """); + var compiler = Compiler.javac().withProcessors(new IRProcessor()); + var compilation = compiler.compile(src); + CompilationSubject.assertThat(compilation).succeeded(); + } + + @Test + public void annotatedInterfaceMustExtendIR() { + var src = + JavaFileObjects.forSourceString( + "Hello", + """ + import org.enso.runtime.parser.dsl.IRNode; + @IRNode + public interface Hello {} + """); + var compiler = Compiler.javac().withProcessors(new IRProcessor()); + var compilation = compiler.compile(src); + CompilationSubject.assertThat(compilation).failed(); + } + + @Test + public void annotationCanOnlyBeAppliedToInterface() { + var src = + JavaFileObjects.forSourceString( + "Hello", + """ + import org.enso.runtime.parser.dsl.IRNode; + @IRNode + public class Hello {} + """); + var compiler = Compiler.javac().withProcessors(new IRProcessor()); + var compilation = compiler.compile(src); + CompilationSubject.assertThat(compilation).failed(); + } + + @Test + public void simpleIRNodeWithoutChildren_GeneratesSource() { var src = - JavaFileObjects.forSourceLines( - "HelloWorld", - "import org.enso.runtime.parser.dsl.IRNode;", - "@IRNode(name = \"HelloWorld\")", - "final class HelloWorld {", - "}"); + JavaFileObjects.forSourceString( + "JName", + """ + import org.enso.runtime.parser.dsl.IRNode; + import org.enso.compiler.core.IR; + @IRNode + public interface JName extends IR {} + """); var compiler = Compiler.javac().withProcessors(new IRProcessor()); - Compilation compilation = null; - try { - compilation = compiler.compile(src); - Assert.fail("Expected compilation to fail with UnimplementedException"); - } catch (Exception e) { - // nop - assertThat(compilation, is(nullValue())); - } + var compilation = compiler.compile(src); + CompilationSubject.assertThat(compilation).succeeded(); + CompilationSubject.assertThat(compilation).generatedSourceFile("JNameGen").isNotNull(); } } From 51796bac53d36f4d6a8f58e8c9d73d696dffa5f9 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Wed, 9 Oct 2024 20:38:27 +0200 Subject: [PATCH 07/93] IRNode does not have name parameter --- .../src/main/java/org/enso/runtime/parser/dsl/IRNode.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/IRNode.java b/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/IRNode.java index c75a806ffc15..b711f27fc391 100644 --- a/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/IRNode.java +++ b/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/IRNode.java @@ -7,6 +7,4 @@ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.TYPE) -public @interface IRNode { - String name(); -} +public @interface IRNode {} From 3ea7a905a6403703b86c1dce66c7c67862288e8d Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Wed, 9 Oct 2024 21:07:30 +0200 Subject: [PATCH 08/93] IRProcessor generates source file for record --- .../runtime/parser/processor/IRProcessor.java | 53 +++++++++++++++++++ .../processor/test/TestIRProcessor.java | 4 ++ 2 files changed, 57 insertions(+) diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java index 20419a3ca01e..82b2bc06a58a 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java @@ -1,6 +1,9 @@ package org.enso.runtime.parser.processor; +import java.io.IOException; +import java.io.PrintWriter; import java.util.Set; +import java.util.stream.Collectors; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; @@ -10,6 +13,8 @@ import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; import javax.tools.Diagnostic.Kind; +import javax.tools.JavaFileObject; +import org.enso.runtime.parser.dsl.IRChild; import org.enso.runtime.parser.dsl.IRNode; @SupportedAnnotationTypes({ @@ -39,6 +44,48 @@ private void processIrNode(Element irNodeElem) { if (!isSubtypeOfIR(irNodeElem.asType())) { printError("Interface annotated with @IRNode must be a subtype of IR interface", irNodeElem); } + assert irNodeElem instanceof TypeElement; + var irNodeTypeElem = (TypeElement) irNodeElem; + var irNodeInterfaceName = irNodeTypeElem.getSimpleName().toString(); + var pkgName = packageName(irNodeTypeElem); + var newRecordName = irNodeInterfaceName + "Gen"; + String newBinaryName; + if (!pkgName.isEmpty()) { + newBinaryName = pkgName + "." + newRecordName; + } else { + newBinaryName = newRecordName; + } + JavaFileObject srcGen = null; + try { + srcGen = processingEnv.getFiler().createSourceFile(newBinaryName, irNodeElem); + } catch (IOException e) { + printError("Failed to create source file for IRNode", irNodeElem); + } + assert srcGen != null; + try { + try (var lineWriter = new PrintWriter(srcGen.openWriter())) { + var code = + """ + public record $recordName ( + String field + ) implements $interfaceName { } + """ + .replace("$recordName", newRecordName) + .replace("$interfaceName", irNodeInterfaceName); + lineWriter.println(code); + lineWriter.println(); + } + } catch (IOException e) { + printError("Failed to write to source file for IRNode", irNodeElem); + } + var childElems = findChildElements(irNodeElem); + } + + private void processChildElem(Element childElem) {} + + private String packageName(Element elem) { + var pkg = processingEnv.getElementUtils().getPackageOf(elem); + return pkg.getQualifiedName().toString(); } private boolean isSubtypeOfIR(TypeMirror type) { @@ -47,6 +94,12 @@ private boolean isSubtypeOfIR(TypeMirror type) { return processingEnv.getTypeUtils().isAssignable(type, irType); } + private Set findChildElements(Element irNodeElem) { + return irNodeElem.getEnclosedElements().stream() + .filter(elem -> elem.getAnnotation(IRChild.class) != null) + .collect(Collectors.toUnmodifiableSet()); + } + private void printError(String msg, Element elem) { processingEnv.getMessager().printMessage(Kind.ERROR, msg, elem); } diff --git a/engine/runtime-parser-processor/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java b/engine/runtime-parser-processor/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java index b618c05d238f..0dc7f11395eb 100644 --- a/engine/runtime-parser-processor/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java +++ b/engine/runtime-parser-processor/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java @@ -1,5 +1,8 @@ package org.enso.runtime.parser.processor.test; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + import com.google.testing.compile.CompilationSubject; import com.google.testing.compile.Compiler; import com.google.testing.compile.JavaFileObjects; @@ -68,5 +71,6 @@ public interface JName extends IR {} var compilation = compiler.compile(src); CompilationSubject.assertThat(compilation).succeeded(); CompilationSubject.assertThat(compilation).generatedSourceFile("JNameGen").isNotNull(); + assertThat("Generated just one source", compilation.generatedSourceFiles().size(), is(1)); } } From f6bf733cfc0deb6950cfd1e1403eb6cbbeb58f1f Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Thu, 10 Oct 2024 20:08:10 +0200 Subject: [PATCH 09/93] Add imports and overriden IR methods to generated record --- .../parser/processor/IRNodeElement.java | 101 ++++++++++++++++++ .../runtime/parser/processor/IRProcessor.java | 11 +- 2 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeElement.java diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeElement.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeElement.java new file mode 100644 index 000000000000..c88e04c8b8f2 --- /dev/null +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeElement.java @@ -0,0 +1,101 @@ +package org.enso.runtime.parser.processor; + +import java.util.stream.Collectors; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.TypeElement; + +/** + * Representation of an interface annotated with {@link org.enso.runtime.parser.dsl.IRNode}. Takes + * care of - Methods from {@code org.enso.compiler.core.IR} with no default implementation. - + * Methods annotated with {@link org.enso.runtime.parser.dsl.IRChild} (child elements). - Other + * methods (will be fields of the record, not children). + */ +final class IRNodeElement { + private final ProcessingEnvironment processingEnv; + + static final String IMPORTS = + """ +import java.util.UUID; +import java.util.function.Function; +import org.enso.compiler.core.Identifier; +import org.enso.compiler.core.IR; +import org.enso.compiler.core.ir.DiagnosticStorage; +import org.enso.compiler.core.ir.Expression; +import org.enso.compiler.core.ir.IdentifiedLocation; +import org.enso.compiler.core.ir.MetadataStorage; +import scala.Option; +import scala.collection.immutable.List; + """; + + IRNodeElement(ProcessingEnvironment processingEnv, TypeElement irNodeInterface) { + this.processingEnv = processingEnv; + var elemsInInterface = irNodeInterface.getEnclosedElements(); + } + + /** + * Returns a String representing all the overriden methods from {@link org.enso.compiler.core.IR}. + * Meant to be inside the generated record definition. + */ + String overrideIRMethods() { + var code = + """ + + @Override + public MetadataStorage passData() { + throw new UnsupportedOperationException("unimplemented"); + } + + @Override + public Option location() { + throw new UnsupportedOperationException("unimplemented"); + } + + @Override + public IR setLocation(Option location) { + throw new UnsupportedOperationException("unimplemented"); + } + + @Override + public IR mapExpressions(Function fn) { + throw new UnsupportedOperationException("unimplemented"); + } + + @Override + public List children() { + throw new UnsupportedOperationException("unimplemented"); + } + + @Override + public @Identifier UUID getId() { + throw new UnsupportedOperationException("unimplemented"); + } + + @Override + public DiagnosticStorage diagnostics() { + throw new UnsupportedOperationException("unimplemented"); + } + + @Override + public DiagnosticStorage getDiagnostics() { + throw new UnsupportedOperationException("unimplemented"); + } + + @Override + public IR duplicate( + boolean keepLocations, + boolean keepMetadata, + boolean keepDiagnostics, + boolean keepIdentifiers + ) { + throw new UnsupportedOperationException("unimplemented"); + } + + @Override + public String showCode(int indent) { + throw new UnsupportedOperationException("unimplemented"); + } + """; + var indentedCode = code.lines().map(line -> " " + line).collect(Collectors.joining("\n")); + return indentedCode; + } +} diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java index 82b2bc06a58a..e963f1a1a64e 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java @@ -62,16 +62,23 @@ private void processIrNode(Element irNodeElem) { printError("Failed to create source file for IRNode", irNodeElem); } assert srcGen != null; + var irNodeElement = new IRNodeElement(processingEnv, irNodeTypeElem); try { try (var lineWriter = new PrintWriter(srcGen.openWriter())) { var code = """ + $imports + public record $recordName ( String field - ) implements $interfaceName { } + ) implements $interfaceName { + $overrideIRMethods + } """ + .replace("$imports", IRNodeElement.IMPORTS) .replace("$recordName", newRecordName) - .replace("$interfaceName", irNodeInterfaceName); + .replace("$interfaceName", irNodeInterfaceName) + .replace("$overrideIRMethods", irNodeElement.overrideIRMethods()); lineWriter.println(code); lineWriter.println(); } From 1870ef25639172cacc3e5f4ab385965e0f074f4d Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Thu, 10 Oct 2024 21:24:22 +0200 Subject: [PATCH 10/93] Add field processing --- .../enso/runtime/parser/processor/Field.java | 42 ++++ .../parser/processor/IRNodeElement.java | 191 +++++++++++++++++- .../runtime/parser/processor/IRProcessor.java | 24 ++- .../enso/runtime/parser/processor/Utils.java | 21 ++ .../processor/test/TestIRProcessor.java | 38 ++++ 5 files changed, 302 insertions(+), 14 deletions(-) create mode 100644 engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Field.java create mode 100644 engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Field.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Field.java new file mode 100644 index 000000000000..953316ac2c2c --- /dev/null +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Field.java @@ -0,0 +1,42 @@ +package org.enso.runtime.parser.processor; + +import javax.lang.model.element.TypeElement; + +final class Field { + private final TypeElement type; + + /** Name of the field (identifier). */ + private final String name; + + /** If the field can be {@code null}. */ + private final boolean nullable; + + private final boolean isChild; + + Field(TypeElement type, String name, boolean nullable, boolean isChild) { + this.type = type; + this.name = name; + this.nullable = nullable; + this.isChild = isChild; + } + + boolean isChild() { + return isChild; + } + + boolean isNullable() { + return nullable; + } + + String getName() { + return name; + } + + String getSimpleTypeName() { + return type.getSimpleName().toString(); + } + + String getQualifiedTypeName() { + return type.getQualifiedName().toString(); + } +} diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeElement.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeElement.java index c88e04c8b8f2..bc13bea71da5 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeElement.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeElement.java @@ -1,8 +1,15 @@ package org.enso.runtime.parser.processor; +import java.util.ArrayList; +import java.util.List; import java.util.stream.Collectors; import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; +import javax.lang.model.util.SimpleElementVisitor14; +import org.enso.runtime.parser.dsl.IRChild; +import org.enso.runtime.parser.dsl.IRNode; /** * Representation of an interface annotated with {@link org.enso.runtime.parser.dsl.IRNode}. Takes @@ -12,8 +19,10 @@ */ final class IRNodeElement { private final ProcessingEnvironment processingEnv; + private final String recordName; + private final List fields; - static final String IMPORTS = + private static final String IMPORTS = """ import java.util.UUID; import java.util.function.Function; @@ -27,9 +36,102 @@ final class IRNodeElement { import scala.collection.immutable.List; """; - IRNodeElement(ProcessingEnvironment processingEnv, TypeElement irNodeInterface) { + /** + * @param processingEnv + * @param irNodeInterface + * @param recordName Simple name (non-qualified) of the newly generated record. + */ + IRNodeElement( + ProcessingEnvironment processingEnv, TypeElement irNodeInterface, String recordName) { + assert !recordName.contains(".") : "Record name should be simple, not qualified"; this.processingEnv = processingEnv; - var elemsInInterface = irNodeInterface.getEnclosedElements(); + this.recordName = recordName; + this.fields = getAllFields(irNodeInterface); + } + + /** Returns string representation of all necessary imports. */ + String imports() { + var importsForFields = + fields.stream() + .map(field -> "import " + field.getQualifiedTypeName() + ";") + .distinct() + .collect(Collectors.joining(System.lineSeparator())); + var allImports = IMPORTS + System.lineSeparator() + importsForFields; + return allImports; + } + + /** + * Collects all abstract methods (with no parameters) from this interface and all the interfaces + * that are extended by this interface. Every abstract method corresponds to a single field in the + * newly generated record. Abstract methods annotated with {@link IRChild} are considered IR + * children. + * + * @param irNodeInterface Type element of the interface annotated with {@link IRNode}. + * @return List of fields + */ + private List getAllFields(TypeElement irNodeInterface) { + var fields = new ArrayList(); + + var elemVisitor = + new SimpleElementVisitor14() { + @Override + protected Void defaultAction(Element e, Void unused) { + for (var childElem : e.getEnclosedElements()) { + childElem.accept(this, unused); + } + return null; + } + + @Override + public Void visitExecutable(ExecutableElement e, Void unused) { + if (e.getParameters().isEmpty()) { + var retType = e.getReturnType(); + var retTypeElem = (TypeElement) processingEnv.getTypeUtils().asElement(retType); + var name = e.getSimpleName().toString(); + var childAnnot = e.getAnnotation(IRChild.class); + boolean isChild = false; + boolean isNullable = false; + if (childAnnot != null) { + ensureIsSubtypeOfIR(retTypeElem); + isChild = true; + isNullable = !childAnnot.required(); + } + fields.add(new Field(retTypeElem, name, isNullable, isChild)); + } + return super.visitExecutable(e, unused); + } + }; + irNodeInterface.accept(elemVisitor, null); + return fields; + } + + private void ensureIsSubtypeOfIR(TypeElement typeElem) { + if (!Utils.isSubtypeOfIR(typeElem.asType(), processingEnv)) { + Utils.printError( + "Method annotated with @IRChild must return a subtype of IR interface", + typeElem, + processingEnv.getMessager()); + } + } + + /** + * Returns string representation of record fields. Meant to be inside the generated record + * definition. + */ + String fields() { + var userDefinedFields = + fields.stream() + .map(field -> field.getSimpleTypeName() + " " + field.getName()) + .collect(Collectors.joining(", " + System.lineSeparator())); + var code = + """ + $userDefinedFields, + DiagnosticStorage diagnostics, + MetadataStorage passData, + IdentifiedLocation location + """ + .replace("$userDefinedFields", userDefinedFields); + return indent(code, 2); } /** @@ -95,7 +197,86 @@ public String showCode(int indent) { throw new UnsupportedOperationException("unimplemented"); } """; - var indentedCode = code.lines().map(line -> " " + line).collect(Collectors.joining("\n")); - return indentedCode; + return indent(code, 2); + } + + /** + * Returns string representation of the code for the builder - that is a nested class that allows + * to build the record. + * + * @return Code of the builder + */ + String builder() { + var fieldDeclarations = + fields.stream() + .map( + field -> + """ + $fieldType $fieldName; + """ + .replace("$fieldName", field.getName()) + .replace("$fieldType", field.getSimpleTypeName())) + .collect(Collectors.joining(System.lineSeparator())); + + var fieldSetters = + fields.stream() + .map( + field -> + """ + public Builder $fieldName($fieldType $fieldName) { + this.$fieldName = $fieldName; + return this; + } + """ + .replace("$fieldName", field.getName()) + .replace("$fieldType", field.getSimpleTypeName())) + .collect(Collectors.joining(System.lineSeparator())); + + // Validation code for all non-nullable fields + var validationCode = + fields.stream() + .filter(field -> !field.isNullable()) + .map( + field -> + """ + if (this.$fieldName == null) { + throw new IllegalArgumentException("$fieldName is required"); + } + """ + .replace("$fieldName", field.getName())) + .collect(Collectors.joining(System.lineSeparator())); + + var fieldList = fields.stream().map(Field::getName).collect(Collectors.joining(", ")); + + var code = + """ + public static final class Builder { + $fieldDeclarations + + $fieldSetters + + public $recordName build() { + validate(); + // DiagnosticStorage, MetadataStorage, IdentifiedLocation are null initially. + return new $recordName($fieldList, null, null, null); + } + + private void validate() { + $validationCode + } + } + """ + .replace("$fieldDeclarations", fieldDeclarations) + .replace("$fieldSetters", fieldSetters) + .replace("$recordName", recordName) + .replace("$fieldList", fieldList) + .replace("$validationCode", validationCode); + return indent(code, 2); + } + + private static String indent(String code, int indentation) { + return code.lines() + .map(line -> " ".repeat(indentation) + line) + .collect(Collectors.joining(System.lineSeparator())); } } diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java index e963f1a1a64e..608818ec2d1b 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java @@ -12,7 +12,6 @@ import javax.lang.model.element.ElementKind; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; -import javax.tools.Diagnostic.Kind; import javax.tools.JavaFileObject; import org.enso.runtime.parser.dsl.IRChild; import org.enso.runtime.parser.dsl.IRNode; @@ -62,7 +61,7 @@ private void processIrNode(Element irNodeElem) { printError("Failed to create source file for IRNode", irNodeElem); } assert srcGen != null; - var irNodeElement = new IRNodeElement(processingEnv, irNodeTypeElem); + var irNodeElement = new IRNodeElement(processingEnv, irNodeTypeElem, newRecordName); try { try (var lineWriter = new PrintWriter(srcGen.openWriter())) { var code = @@ -70,15 +69,24 @@ private void processIrNode(Element irNodeElem) { $imports public record $recordName ( - String field + $fields ) implements $interfaceName { + + public static Builder builder() { + return new Builder(); + } + $overrideIRMethods + + $builder } """ - .replace("$imports", IRNodeElement.IMPORTS) + .replace("$imports", irNodeElement.imports()) + .replace("$fields", irNodeElement.fields()) .replace("$recordName", newRecordName) .replace("$interfaceName", irNodeInterfaceName) - .replace("$overrideIRMethods", irNodeElement.overrideIRMethods()); + .replace("$overrideIRMethods", irNodeElement.overrideIRMethods()) + .replace("$builder", irNodeElement.builder()); lineWriter.println(code); lineWriter.println(); } @@ -96,9 +104,7 @@ private String packageName(Element elem) { } private boolean isSubtypeOfIR(TypeMirror type) { - var irType = - processingEnv.getElementUtils().getTypeElement("org.enso.compiler.core.IR").asType(); - return processingEnv.getTypeUtils().isAssignable(type, irType); + return Utils.isSubtypeOfIR(type, processingEnv); } private Set findChildElements(Element irNodeElem) { @@ -108,6 +114,6 @@ private Set findChildElements(Element irNodeElem) { } private void printError(String msg, Element elem) { - processingEnv.getMessager().printMessage(Kind.ERROR, msg, elem); + Utils.printError(msg, elem, processingEnv.getMessager()); } } diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java new file mode 100644 index 000000000000..0bad76e1614f --- /dev/null +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java @@ -0,0 +1,21 @@ +package org.enso.runtime.parser.processor; + +import javax.annotation.processing.Messager; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.Element; +import javax.lang.model.type.TypeMirror; +import javax.tools.Diagnostic.Kind; + +final class Utils { + private Utils() {} + + static boolean isSubtypeOfIR(TypeMirror type, ProcessingEnvironment processingEnv) { + var irType = + processingEnv.getElementUtils().getTypeElement("org.enso.compiler.core.IR").asType(); + return processingEnv.getTypeUtils().isAssignable(type, irType); + } + + static void printError(String msg, Element elem, Messager messager) { + messager.printMessage(Kind.ERROR, msg, elem); + } +} diff --git a/engine/runtime-parser-processor/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java b/engine/runtime-parser-processor/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java index 0dc7f11395eb..9fe0ae548e0e 100644 --- a/engine/runtime-parser-processor/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java +++ b/engine/runtime-parser-processor/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java @@ -6,6 +6,7 @@ import com.google.testing.compile.CompilationSubject; import com.google.testing.compile.Compiler; import com.google.testing.compile.JavaFileObjects; +import javax.tools.JavaFileObject; import org.enso.runtime.parser.processor.IRProcessor; import org.junit.Test; @@ -71,6 +72,43 @@ public interface JName extends IR {} var compilation = compiler.compile(src); CompilationSubject.assertThat(compilation).succeeded(); CompilationSubject.assertThat(compilation).generatedSourceFile("JNameGen").isNotNull(); + var genSrc = compilation.generatedSourceFile("JNameGen"); + assertThat(genSrc.isPresent(), is(true)); + var srcContent = readSrcFile(genSrc.get()); assertThat("Generated just one source", compilation.generatedSourceFiles().size(), is(1)); } + + @Test + public void simpleIRNodeWithChild() { + var src = + JavaFileObjects.forSourceString( + "MyIR", + """ + import org.enso.runtime.parser.dsl.IRNode; + import org.enso.runtime.parser.dsl.IRChild; + import org.enso.compiler.core.IR; + import org.enso.compiler.core.ir.JExpression; + + @IRNode + public interface MyIR extends IR { + @IRChild JExpression expression(); + } + """); + var compiler = Compiler.javac().withProcessors(new IRProcessor()); + var compilation = compiler.compile(src); + CompilationSubject.assertThat(compilation).succeeded(); + CompilationSubject.assertThat(compilation).generatedSourceFile("MyIRGen").isNotNull(); + var genSrc = compilation.generatedSourceFile("MyIRGen"); + assertThat(genSrc.isPresent(), is(true)); + var srcContent = readSrcFile(genSrc.get()); + assertThat("Generated just one source", compilation.generatedSourceFiles().size(), is(1)); + } + + private static String readSrcFile(JavaFileObject src) { + try { + return src.getCharContent(true).toString(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } } From 826245dc0f65cc40bb3864a27e69240e6ca4e581 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 14 Oct 2024 19:06:18 +0200 Subject: [PATCH 11/93] IRProcessor generates classes, not records --- .../parser/processor/IRNodeElement.java | 43 +++++++++++-------- .../runtime/parser/processor/IRProcessor.java | 17 ++++---- 2 files changed, 33 insertions(+), 27 deletions(-) diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeElement.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeElement.java index bc13bea71da5..0ff067ca48d8 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeElement.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeElement.java @@ -19,7 +19,11 @@ */ final class IRNodeElement { private final ProcessingEnvironment processingEnv; - private final String recordName; + + /** Name of the class that is being generated. */ + private final String className; + + /** User defined fields - all the abstract parameterless methods, including the inherited ones. */ private final List fields; private static final String IMPORTS = @@ -38,14 +42,14 @@ final class IRNodeElement { /** * @param processingEnv - * @param irNodeInterface - * @param recordName Simple name (non-qualified) of the newly generated record. + * @param irNodeInterface Type element of the interface annotated with {@link IRNode}. + * @param className Simple name (non-qualified) of the newly generated class. */ IRNodeElement( - ProcessingEnvironment processingEnv, TypeElement irNodeInterface, String recordName) { - assert !recordName.contains(".") : "Record name should be simple, not qualified"; + ProcessingEnvironment processingEnv, TypeElement irNodeInterface, String className) { + assert !className.contains(".") : "Class name should be simple, not qualified"; this.processingEnv = processingEnv; - this.recordName = recordName; + this.className = className; this.fields = getAllFields(irNodeInterface); } @@ -115,20 +119,22 @@ private void ensureIsSubtypeOfIR(TypeElement typeElem) { } /** - * Returns string representation of record fields. Meant to be inside the generated record - * definition. + * Returns string representation of the class fields. Meant to be at the beginning of the class + * body. */ String fields() { var userDefinedFields = fields.stream() - .map(field -> field.getSimpleTypeName() + " " + field.getName()) - .collect(Collectors.joining(", " + System.lineSeparator())); + .map(field -> "private final " + field.getSimpleTypeName() + " " + field.getName()) + .collect(Collectors.joining(";" + System.lineSeparator())); var code = """ - $userDefinedFields, - DiagnosticStorage diagnostics, - MetadataStorage passData, - IdentifiedLocation location + $userDefinedFields; + // Not final on purpose + private DiagnosticStorage diagnostics; + private MetadataStorage passData; + private IdentifiedLocation location; + private UUID id; """ .replace("$userDefinedFields", userDefinedFields); return indent(code, 2); @@ -212,7 +218,7 @@ String builder() { .map( field -> """ - $fieldType $fieldName; + private $fieldType $fieldName; """ .replace("$fieldName", field.getName()) .replace("$fieldType", field.getSimpleTypeName())) @@ -255,10 +261,9 @@ public static final class Builder { $fieldSetters - public $recordName build() { + public $className build() { validate(); - // DiagnosticStorage, MetadataStorage, IdentifiedLocation are null initially. - return new $recordName($fieldList, null, null, null); + return new $className($fieldList); } private void validate() { @@ -268,7 +273,7 @@ private void validate() { """ .replace("$fieldDeclarations", fieldDeclarations) .replace("$fieldSetters", fieldSetters) - .replace("$recordName", recordName) + .replace("$className", className) .replace("$fieldList", fieldList) .replace("$validationCode", validationCode); return indent(code, 2); diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java index 608818ec2d1b..a3097e96944f 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java @@ -47,12 +47,12 @@ private void processIrNode(Element irNodeElem) { var irNodeTypeElem = (TypeElement) irNodeElem; var irNodeInterfaceName = irNodeTypeElem.getSimpleName().toString(); var pkgName = packageName(irNodeTypeElem); - var newRecordName = irNodeInterfaceName + "Gen"; + var newClassName = irNodeInterfaceName + "Gen"; String newBinaryName; if (!pkgName.isEmpty()) { - newBinaryName = pkgName + "." + newRecordName; + newBinaryName = pkgName + "." + newClassName; } else { - newBinaryName = newRecordName; + newBinaryName = newClassName; } JavaFileObject srcGen = null; try { @@ -61,16 +61,17 @@ private void processIrNode(Element irNodeElem) { printError("Failed to create source file for IRNode", irNodeElem); } assert srcGen != null; - var irNodeElement = new IRNodeElement(processingEnv, irNodeTypeElem, newRecordName); + var irNodeElement = new IRNodeElement(processingEnv, irNodeTypeElem, newClassName); try { try (var lineWriter = new PrintWriter(srcGen.openWriter())) { var code = """ $imports - public record $recordName ( + public final class $className implements $interfaceName { $fields - ) implements $interfaceName { + + $constructor public static Builder builder() { return new Builder(); @@ -82,8 +83,9 @@ public static Builder builder() { } """ .replace("$imports", irNodeElement.imports()) + .replace("$className", newClassName) .replace("$fields", irNodeElement.fields()) - .replace("$recordName", newRecordName) + .replace("$constructor", irNodeElement.constructor()) .replace("$interfaceName", irNodeInterfaceName) .replace("$overrideIRMethods", irNodeElement.overrideIRMethods()) .replace("$builder", irNodeElement.builder()); @@ -93,7 +95,6 @@ public static Builder builder() { } catch (IOException e) { printError("Failed to write to source file for IRNode", irNodeElem); } - var childElems = findChildElements(irNodeElem); } private void processChildElem(Element childElem) {} From fc89b3dd2f30e5fd836dca81a0b65b67306d69a1 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 14 Oct 2024 19:06:54 +0200 Subject: [PATCH 12/93] IRProcessor generates overrides for user-defined parameterless methods --- .../parser/processor/IRNodeElement.java | 89 ++++++++++++++++++- .../runtime/parser/processor/IRProcessor.java | 3 + 2 files changed, 89 insertions(+), 3 deletions(-) diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeElement.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeElement.java index 0ff067ca48d8..89b203205959 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeElement.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeElement.java @@ -29,6 +29,7 @@ final class IRNodeElement { private static final String IMPORTS = """ import java.util.UUID; +import java.util.ArrayList; import java.util.function.Function; import org.enso.compiler.core.Identifier; import org.enso.compiler.core.IR; @@ -140,6 +141,61 @@ String fields() { return indent(code, 2); } + /** + * Returns string representation of the package-private constructor of the generated class. Note + * that the constructor is meant to be invoked only by the internal Builder class. + */ + String constructor() { + var sb = new StringBuilder(); + sb.append("private ").append(className).append("("); + var inParens = + fields.stream() + .map( + field -> + "$fieldType $fieldName" + .replace("$fieldType", field.getSimpleTypeName()) + .replace("$fieldName", field.getName())) + .collect(Collectors.joining(", ")); + sb.append(inParens).append(") {").append(System.lineSeparator()); + var ctorBody = + fields.stream() + .map(field -> " this.$fieldName = $fieldName;".replace("$fieldName", field.getName())) + .collect(Collectors.joining(System.lineSeparator())); + sb.append(indent(ctorBody, 2)); + sb.append(System.lineSeparator()); + sb.append("}").append(System.lineSeparator()); + return indent(sb.toString(), 2); + } + + private String childrenMethodBody() { + var sb = new StringBuilder(); + var nl = System.lineSeparator(); + sb.append("var list = new ArrayList();").append(nl); + fields.stream() + .filter(Field::isChild) + .forEach( + childField -> { + var childName = childField.getName(); + if (childField.isNullable()) { + sb.append( + """ + if ($childName != null) { + list.add($childName); + } + """ + .replace("$childName", childName)); + } else { + sb.append( + """ + list.add($childName); + """ + .replace("$childName", childName)); + } + }); + sb.append("return scala.jdk.javaapi.CollectionConverters.asScala(list).toList();").append(nl); + return indent(sb.toString(), 2); + } + /** * Returns a String representing all the overriden methods from {@link org.enso.compiler.core.IR}. * Meant to be inside the generated record definition. @@ -170,12 +226,15 @@ public IR mapExpressions(Function fn) { @Override public List children() { - throw new UnsupportedOperationException("unimplemented"); + $childrenMethodBody } @Override public @Identifier UUID getId() { - throw new UnsupportedOperationException("unimplemented"); + if (id == null) { + id = UUID.randomUUID(); + } + return id; } @Override @@ -202,7 +261,31 @@ public IR duplicate( public String showCode(int indent) { throw new UnsupportedOperationException("unimplemented"); } - """; + """ + .replace("$childrenMethodBody", childrenMethodBody()); + return indent(code, 2); + } + + /** + * Returns string representation of all parameterless abstract methods from the interface + * annotated with {@link IRNode}. + * + * @return Code of the overriden methods + */ + String overrideUserDefinedMethods() { + var code = + fields.stream() + .map( + field -> + """ + @Override + public $returnType $fieldName() { + return $fieldName; + } + """ + .replace("$returnType", field.getSimpleTypeName()) + .replace("$fieldName", field.getName())) + .collect(Collectors.joining(System.lineSeparator())); return indent(code, 2); } diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java index a3097e96944f..16b2e7d3bcbb 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java @@ -77,6 +77,8 @@ public static Builder builder() { return new Builder(); } + $overrideUserDefinedMethods + $overrideIRMethods $builder @@ -87,6 +89,7 @@ public static Builder builder() { .replace("$fields", irNodeElement.fields()) .replace("$constructor", irNodeElement.constructor()) .replace("$interfaceName", irNodeInterfaceName) + .replace("$overrideUserDefinedMethods", irNodeElement.overrideUserDefinedMethods()) .replace("$overrideIRMethods", irNodeElement.overrideIRMethods()) .replace("$builder", irNodeElement.builder()); lineWriter.println(code); From 1fc04978b8c2af25b86c9c4b1effdd41ddf51567 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Thu, 17 Oct 2024 10:19:44 +0200 Subject: [PATCH 13/93] Add Field.isExpression method This will be important for mapExpressions method implementation --- .../org/enso/runtime/parser/processor/Field.java | 13 +++++++++++++ .../runtime/parser/processor/IRNodeElement.java | 2 +- .../org/enso/runtime/parser/processor/Utils.java | 9 +++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Field.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Field.java index 953316ac2c2c..5a454d39882e 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Field.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Field.java @@ -1,5 +1,7 @@ package org.enso.runtime.parser.processor; +import java.util.function.Function; +import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.TypeElement; final class Field { @@ -39,4 +41,15 @@ String getSimpleTypeName() { String getQualifiedTypeName() { return type.getQualifiedName().toString(); } + + /** + * Returns true if this field extends {@link org.enso.compiler.core.ir.Expression} + * ({@link org.enso.compiler.core.ir.JExpression}). + * + *

This is useful, e.g., for the {@link org.enso.compiler.core.IR#mapExpressions(Function)} method. + * @return true if this field extends {@link org.enso.compiler.core.ir.Expression} + */ + boolean isExpression(ProcessingEnvironment processingEnv) { + return Utils.isSubtypeOfExpression(type.asType(), processingEnv); + } } diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeElement.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeElement.java index 89b203205959..4cc65e274239 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeElement.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeElement.java @@ -358,7 +358,7 @@ private void validate() { .replace("$fieldSetters", fieldSetters) .replace("$className", className) .replace("$fieldList", fieldList) - .replace("$validationCode", validationCode); + .replace("$validationCode", indent(validationCode, 2)); return indent(code, 2); } diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java index 0bad76e1614f..6d62a7cf6d32 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java @@ -15,6 +15,15 @@ static boolean isSubtypeOfIR(TypeMirror type, ProcessingEnvironment processingEn return processingEnv.getTypeUtils().isAssignable(type, irType); } + /** + * Returns true if the given type extends {@link org.enso.compiler.core.ir.Expression} + */ + static boolean isSubtypeOfExpression(TypeMirror type, ProcessingEnvironment processingEnv) { + var expressionType = + processingEnv.getElementUtils().getTypeElement("org.enso.compiler.core.ir.Expression").asType(); + return processingEnv.getTypeUtils().isAssignable(type, expressionType); + } + static void printError(String msg, Element elem, Messager messager) { messager.printMessage(Kind.ERROR, msg, elem); } From d2bb8c354a8d9b409c9e6ceb7fb1f788740721aa Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 18 Oct 2024 20:04:46 +0200 Subject: [PATCH 14/93] sbt picks up Scala case classes generated by Java annotation processor --- build.sbt | 5 +++++ project/Utils.scala | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 project/Utils.scala diff --git a/build.sbt b/build.sbt index 2671ddb09ccb..80008fd73010 100644 --- a/build.sbt +++ b/build.sbt @@ -3101,6 +3101,11 @@ lazy val `runtime-parser` = frgaalJavaCompilerSetting, annotationProcSetting, commands += WithDebugCommand.withDebug, + // Java annotation processor in `runtime-parser-processor` generates scala + // case classes. This setting ensures that all the generated `*.scala` files + // are picked by the sbt for compilation. + Compile / managedSources ++= + Utils.listAllGeneratedScalaFiles.value, fork := true, libraryDependencies ++= Seq( "junit" % "junit" % junitVersion % Test, diff --git a/project/Utils.scala b/project/Utils.scala new file mode 100644 index 000000000000..a18fd557cd17 --- /dev/null +++ b/project/Utils.scala @@ -0,0 +1,36 @@ +import sbt.* +import sbt.Keys.* + +import java.nio.file.attribute.BasicFileAttributes +import java.nio.file.{FileVisitResult, Files, Path, SimpleFileVisitor} +import scala.collection.mutable.ListBuffer + +object Utils { + + /** Recursively lists all `*.scala` files in the `target/src_managed` directory. + * Should be put as a dependency of `Compile / managedSources` task to ensure + * that sbt picks up any scala sources generated by the Java annotation processor. + * @return + */ + def listAllGeneratedScalaFiles(): Def.Initialize[Task[Seq[File]]] = Def.task { + val srcManagedDir = (Compile / sourceManaged).value + val scalaFiles = ListBuffer[Path]() + + Files.walkFileTree( + srcManagedDir.toPath, + new SimpleFileVisitor[Path]() { + override def visitFile( + file: Path, + attrs: BasicFileAttributes + ): FileVisitResult = { + if (file.toString.endsWith(".scala")) { + scalaFiles += file + } + FileVisitResult.CONTINUE + } + } + ) + + scalaFiles.map(_.toFile) + } +} From 68a070b98b03528cb42f5788bbb2cb6915ece2ad Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 18 Oct 2024 20:45:53 +0200 Subject: [PATCH 15/93] IRProcessor generates Scala classes. This is the only way to retain 100% backward compatibility. --- .../enso/runtime/parser/processor/Field.java | 8 +- .../parser/processor/IRNodeElement.java | 74 +++++++++---------- .../runtime/parser/processor/IRProcessor.java | 39 +++++----- .../enso/runtime/parser/processor/Utils.java | 9 ++- .../processor/test/TestIRProcessor.java | 21 +++--- 5 files changed, 74 insertions(+), 77 deletions(-) diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Field.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Field.java index 5a454d39882e..3c5ff7d4431a 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Field.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Field.java @@ -43,10 +43,12 @@ String getQualifiedTypeName() { } /** - * Returns true if this field extends {@link org.enso.compiler.core.ir.Expression} - * ({@link org.enso.compiler.core.ir.JExpression}). + * Returns true if this field extends {@link org.enso.compiler.core.ir.Expression} ({@link + * org.enso.compiler.core.ir.JExpression}). + * + *

This is useful, e.g., for the {@link org.enso.compiler.core.IR#mapExpressions(Function)} + * method. * - *

This is useful, e.g., for the {@link org.enso.compiler.core.IR#mapExpressions(Function)} method. * @return true if this field extends {@link org.enso.compiler.core.ir.Expression} */ boolean isExpression(ProcessingEnvironment processingEnv) { diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeElement.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeElement.java index 4cc65e274239..235cb8f594a3 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeElement.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeElement.java @@ -119,23 +119,23 @@ private void ensureIsSubtypeOfIR(TypeElement typeElem) { } } - /** - * Returns string representation of the class fields. Meant to be at the beginning of the class - * body. - */ + /** Returns string representation of the class fields. Meant to be in the default constructor. */ String fields() { var userDefinedFields = fields.stream() - .map(field -> "private final " + field.getSimpleTypeName() + " " + field.getName()) - .collect(Collectors.joining(";" + System.lineSeparator())); + .map(field -> "private val " + field.getName() + ": " + field.getSimpleTypeName()) + .collect(Collectors.joining("," + System.lineSeparator())) + + ","; + if (fields.isEmpty()) { + userDefinedFields = ""; + } var code = """ - $userDefinedFields; - // Not final on purpose - private DiagnosticStorage diagnostics; - private MetadataStorage passData; - private IdentifiedLocation location; - private UUID id; + $userDefinedFields + private var diagnostics: DiagnosticStorage = null, + private var passData: MetadataStorage = null, + private var location: IdentifiedLocation = null, + private var id: UUID = null """ .replace("$userDefinedFields", userDefinedFields); return indent(code, 2); @@ -170,7 +170,7 @@ String constructor() { private String childrenMethodBody() { var sb = new StringBuilder(); var nl = System.lineSeparator(); - sb.append("var list = new ArrayList();").append(nl); + sb.append("val list = new ArrayList();").append(nl); fields.stream() .filter(Field::isChild) .forEach( @@ -204,61 +204,53 @@ String overrideIRMethods() { var code = """ - @Override - public MetadataStorage passData() { + override def passData(): MetadataStorage = { throw new UnsupportedOperationException("unimplemented"); } - @Override - public Option location() { + override def location(): Option[IdentifiedLocation] { throw new UnsupportedOperationException("unimplemented"); } - @Override - public IR setLocation(Option location) { + override def setLocation(location: Option[IdentifiedLocation]): IR = { throw new UnsupportedOperationException("unimplemented"); } - @Override - public IR mapExpressions(Function fn) { + override def mapExpressions(fn: Function): IR = { throw new UnsupportedOperationException("unimplemented"); } - @Override - public List children() { + override def children(): List = { $childrenMethodBody } - @Override - public @Identifier UUID getId() { + @Identifier + override def getId(): Identifier = { if (id == null) { id = UUID.randomUUID(); } return id; } - @Override - public DiagnosticStorage diagnostics() { + override def diagnostics(): DiagnosticStorage = { throw new UnsupportedOperationException("unimplemented"); } - @Override - public DiagnosticStorage getDiagnostics() { + override def getDiagnostics(): DiagnosticStorage = { throw new UnsupportedOperationException("unimplemented"); } - @Override - public IR duplicate( + override def duplicate( boolean keepLocations, boolean keepMetadata, boolean keepDiagnostics, boolean keepIdentifiers - ) { + ): IR = { throw new UnsupportedOperationException("unimplemented"); } @Override - public String showCode(int indent) { + override def showCode(indent: Int): String = { throw new UnsupportedOperationException("unimplemented"); } """ @@ -278,9 +270,8 @@ String overrideUserDefinedMethods() { .map( field -> """ - @Override - public $returnType $fieldName() { - return $fieldName; + override def $fieldName(): $returnType = { + return this.$fieldName; } """ .replace("$returnType", field.getSimpleTypeName()) @@ -301,7 +292,7 @@ String builder() { .map( field -> """ - private $fieldType $fieldName; + private var $fieldName: $fieldType = null, """ .replace("$fieldName", field.getName()) .replace("$fieldType", field.getSimpleTypeName())) @@ -312,7 +303,7 @@ String builder() { .map( field -> """ - public Builder $fieldName($fieldType $fieldName) { + def $fieldName($fieldName: $fieldType): Builder = { this.$fieldName = $fieldName; return this; } @@ -339,17 +330,18 @@ String builder() { var code = """ - public static final class Builder { + final class Builder( $fieldDeclarations + ) { $fieldSetters - public $className build() { + def build(): $className = { validate(); return new $className($fieldList); } - private void validate() { + private def validate(): Unit = { $validationCode } } diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java index 16b2e7d3bcbb..7cdeb78902ec 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java @@ -12,10 +12,12 @@ import javax.lang.model.element.ElementKind; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; -import javax.tools.JavaFileObject; +import javax.tools.FileObject; +import javax.tools.StandardLocation; import org.enso.runtime.parser.dsl.IRChild; import org.enso.runtime.parser.dsl.IRNode; +/** Generates Scala classes for every Java interface annotated with {@link IRNode}. */ @SupportedAnnotationTypes({ "org.enso.runtime.parser.dsl.IRNode", "org.enso.runtime.parser.dsl.IRChild" @@ -47,47 +49,44 @@ private void processIrNode(Element irNodeElem) { var irNodeTypeElem = (TypeElement) irNodeElem; var irNodeInterfaceName = irNodeTypeElem.getSimpleName().toString(); var pkgName = packageName(irNodeTypeElem); - var newClassName = irNodeInterfaceName + "Gen"; - String newBinaryName; - if (!pkgName.isEmpty()) { - newBinaryName = pkgName + "." + newClassName; - } else { - newBinaryName = newClassName; - } - JavaFileObject srcGen = null; + var newCaseClassName = irNodeInterfaceName + "Gen"; + FileObject srcGen = null; try { - srcGen = processingEnv.getFiler().createSourceFile(newBinaryName, irNodeElem); + srcGen = + processingEnv + .getFiler() + .createResource(StandardLocation.SOURCE_OUTPUT, pkgName, newCaseClassName + ".scala"); } catch (IOException e) { printError("Failed to create source file for IRNode", irNodeElem); } assert srcGen != null; - var irNodeElement = new IRNodeElement(processingEnv, irNodeTypeElem, newClassName); + var irNodeElement = new IRNodeElement(processingEnv, irNodeTypeElem, newCaseClassName); try { try (var lineWriter = new PrintWriter(srcGen.openWriter())) { var code = """ $imports - public final class $className implements $interfaceName { + final class $className( $fields - - $constructor - - public static Builder builder() { - return new Builder(); - } + ) extends $interfaceName { $overrideUserDefinedMethods $overrideIRMethods + } + + final object $className { + def builder(): Builder = { + return new Builder(); + } $builder } """ .replace("$imports", irNodeElement.imports()) - .replace("$className", newClassName) + .replace("$className", newCaseClassName) .replace("$fields", irNodeElement.fields()) - .replace("$constructor", irNodeElement.constructor()) .replace("$interfaceName", irNodeInterfaceName) .replace("$overrideUserDefinedMethods", irNodeElement.overrideUserDefinedMethods()) .replace("$overrideIRMethods", irNodeElement.overrideIRMethods()) diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java index 6d62a7cf6d32..d9acc7d30f78 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java @@ -15,12 +15,13 @@ static boolean isSubtypeOfIR(TypeMirror type, ProcessingEnvironment processingEn return processingEnv.getTypeUtils().isAssignable(type, irType); } - /** - * Returns true if the given type extends {@link org.enso.compiler.core.ir.Expression} - */ + /** Returns true if the given type extends {@link org.enso.compiler.core.ir.Expression} */ static boolean isSubtypeOfExpression(TypeMirror type, ProcessingEnvironment processingEnv) { var expressionType = - processingEnv.getElementUtils().getTypeElement("org.enso.compiler.core.ir.Expression").asType(); + processingEnv + .getElementUtils() + .getTypeElement("org.enso.compiler.core.ir.Expression") + .asType(); return processingEnv.getTypeUtils().isAssignable(type, expressionType); } diff --git a/engine/runtime-parser-processor/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java b/engine/runtime-parser-processor/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java index 9fe0ae548e0e..55404a2dbb19 100644 --- a/engine/runtime-parser-processor/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java +++ b/engine/runtime-parser-processor/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java @@ -6,7 +6,8 @@ import com.google.testing.compile.CompilationSubject; import com.google.testing.compile.Compiler; import com.google.testing.compile.JavaFileObjects; -import javax.tools.JavaFileObject; +import javax.tools.FileObject; +import javax.tools.StandardLocation; import org.enso.runtime.parser.processor.IRProcessor; import org.junit.Test; @@ -71,11 +72,11 @@ public interface JName extends IR {} var compiler = Compiler.javac().withProcessors(new IRProcessor()); var compilation = compiler.compile(src); CompilationSubject.assertThat(compilation).succeeded(); - CompilationSubject.assertThat(compilation).generatedSourceFile("JNameGen").isNotNull(); - var genSrc = compilation.generatedSourceFile("JNameGen"); - assertThat(genSrc.isPresent(), is(true)); - var srcContent = readSrcFile(genSrc.get()); - assertThat("Generated just one source", compilation.generatedSourceFiles().size(), is(1)); + var generatedScalaClass = + compilation.generatedFile(StandardLocation.SOURCE_OUTPUT, "JNameGen.scala"); + assertThat(generatedScalaClass.isPresent(), is(true)); + var srcContent = readSrcFile(generatedScalaClass.get()); + assertThat("Generated just one source", compilation.generatedFiles().size(), is(1)); } @Test @@ -97,14 +98,16 @@ public interface MyIR extends IR { var compiler = Compiler.javac().withProcessors(new IRProcessor()); var compilation = compiler.compile(src); CompilationSubject.assertThat(compilation).succeeded(); - CompilationSubject.assertThat(compilation).generatedSourceFile("MyIRGen").isNotNull(); - var genSrc = compilation.generatedSourceFile("MyIRGen"); + CompilationSubject.assertThat(compilation) + .generatedFile(StandardLocation.SOURCE_OUTPUT, "MyIRGen.scala") + .isNotNull(); + var genSrc = compilation.generatedFile(StandardLocation.SOURCE_OUTPUT, "MyIRGen.scala"); assertThat(genSrc.isPresent(), is(true)); var srcContent = readSrcFile(genSrc.get()); assertThat("Generated just one source", compilation.generatedSourceFiles().size(), is(1)); } - private static String readSrcFile(JavaFileObject src) { + private static String readSrcFile(FileObject src) { try { return src.getCharContent(true).toString(); } catch (Exception e) { From cf087814e87a3e940e83d27c17fd5a2a81168094 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 21 Oct 2024 18:56:52 +0200 Subject: [PATCH 16/93] Revert "sbt picks up Scala case classes generated by Java annotation processor" This reverts commit d2bb8c354a8d9b409c9e6ceb7fb1f788740721aa. --- build.sbt | 5 ----- project/Utils.scala | 36 ------------------------------------ 2 files changed, 41 deletions(-) delete mode 100644 project/Utils.scala diff --git a/build.sbt b/build.sbt index 80008fd73010..2671ddb09ccb 100644 --- a/build.sbt +++ b/build.sbt @@ -3101,11 +3101,6 @@ lazy val `runtime-parser` = frgaalJavaCompilerSetting, annotationProcSetting, commands += WithDebugCommand.withDebug, - // Java annotation processor in `runtime-parser-processor` generates scala - // case classes. This setting ensures that all the generated `*.scala` files - // are picked by the sbt for compilation. - Compile / managedSources ++= - Utils.listAllGeneratedScalaFiles.value, fork := true, libraryDependencies ++= Seq( "junit" % "junit" % junitVersion % Test, diff --git a/project/Utils.scala b/project/Utils.scala deleted file mode 100644 index a18fd557cd17..000000000000 --- a/project/Utils.scala +++ /dev/null @@ -1,36 +0,0 @@ -import sbt.* -import sbt.Keys.* - -import java.nio.file.attribute.BasicFileAttributes -import java.nio.file.{FileVisitResult, Files, Path, SimpleFileVisitor} -import scala.collection.mutable.ListBuffer - -object Utils { - - /** Recursively lists all `*.scala` files in the `target/src_managed` directory. - * Should be put as a dependency of `Compile / managedSources` task to ensure - * that sbt picks up any scala sources generated by the Java annotation processor. - * @return - */ - def listAllGeneratedScalaFiles(): Def.Initialize[Task[Seq[File]]] = Def.task { - val srcManagedDir = (Compile / sourceManaged).value - val scalaFiles = ListBuffer[Path]() - - Files.walkFileTree( - srcManagedDir.toPath, - new SimpleFileVisitor[Path]() { - override def visitFile( - file: Path, - attrs: BasicFileAttributes - ): FileVisitResult = { - if (file.toString.endsWith(".scala")) { - scalaFiles += file - } - FileVisitResult.CONTINUE - } - } - ) - - scalaFiles.map(_.toFile) - } -} From f930e53dc8a73de70599d0d9deb89ac663ce235e Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 21 Oct 2024 18:56:54 +0200 Subject: [PATCH 17/93] Revert "IRProcessor generates Scala classes." This reverts commit 68a070b98b03528cb42f5788bbb2cb6915ece2ad. --- .../enso/runtime/parser/processor/Field.java | 8 +- .../parser/processor/IRNodeElement.java | 74 ++++++++++--------- .../runtime/parser/processor/IRProcessor.java | 39 +++++----- .../enso/runtime/parser/processor/Utils.java | 9 +-- .../processor/test/TestIRProcessor.java | 21 +++--- 5 files changed, 77 insertions(+), 74 deletions(-) diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Field.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Field.java index 3c5ff7d4431a..5a454d39882e 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Field.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Field.java @@ -43,12 +43,10 @@ String getQualifiedTypeName() { } /** - * Returns true if this field extends {@link org.enso.compiler.core.ir.Expression} ({@link - * org.enso.compiler.core.ir.JExpression}). - * - *

This is useful, e.g., for the {@link org.enso.compiler.core.IR#mapExpressions(Function)} - * method. + * Returns true if this field extends {@link org.enso.compiler.core.ir.Expression} + * ({@link org.enso.compiler.core.ir.JExpression}). * + *

This is useful, e.g., for the {@link org.enso.compiler.core.IR#mapExpressions(Function)} method. * @return true if this field extends {@link org.enso.compiler.core.ir.Expression} */ boolean isExpression(ProcessingEnvironment processingEnv) { diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeElement.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeElement.java index 235cb8f594a3..4cc65e274239 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeElement.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeElement.java @@ -119,23 +119,23 @@ private void ensureIsSubtypeOfIR(TypeElement typeElem) { } } - /** Returns string representation of the class fields. Meant to be in the default constructor. */ + /** + * Returns string representation of the class fields. Meant to be at the beginning of the class + * body. + */ String fields() { var userDefinedFields = fields.stream() - .map(field -> "private val " + field.getName() + ": " + field.getSimpleTypeName()) - .collect(Collectors.joining("," + System.lineSeparator())) - + ","; - if (fields.isEmpty()) { - userDefinedFields = ""; - } + .map(field -> "private final " + field.getSimpleTypeName() + " " + field.getName()) + .collect(Collectors.joining(";" + System.lineSeparator())); var code = """ - $userDefinedFields - private var diagnostics: DiagnosticStorage = null, - private var passData: MetadataStorage = null, - private var location: IdentifiedLocation = null, - private var id: UUID = null + $userDefinedFields; + // Not final on purpose + private DiagnosticStorage diagnostics; + private MetadataStorage passData; + private IdentifiedLocation location; + private UUID id; """ .replace("$userDefinedFields", userDefinedFields); return indent(code, 2); @@ -170,7 +170,7 @@ String constructor() { private String childrenMethodBody() { var sb = new StringBuilder(); var nl = System.lineSeparator(); - sb.append("val list = new ArrayList();").append(nl); + sb.append("var list = new ArrayList();").append(nl); fields.stream() .filter(Field::isChild) .forEach( @@ -204,53 +204,61 @@ String overrideIRMethods() { var code = """ - override def passData(): MetadataStorage = { + @Override + public MetadataStorage passData() { throw new UnsupportedOperationException("unimplemented"); } - override def location(): Option[IdentifiedLocation] { + @Override + public Option location() { throw new UnsupportedOperationException("unimplemented"); } - override def setLocation(location: Option[IdentifiedLocation]): IR = { + @Override + public IR setLocation(Option location) { throw new UnsupportedOperationException("unimplemented"); } - override def mapExpressions(fn: Function): IR = { + @Override + public IR mapExpressions(Function fn) { throw new UnsupportedOperationException("unimplemented"); } - override def children(): List = { + @Override + public List children() { $childrenMethodBody } - @Identifier - override def getId(): Identifier = { + @Override + public @Identifier UUID getId() { if (id == null) { id = UUID.randomUUID(); } return id; } - override def diagnostics(): DiagnosticStorage = { + @Override + public DiagnosticStorage diagnostics() { throw new UnsupportedOperationException("unimplemented"); } - override def getDiagnostics(): DiagnosticStorage = { + @Override + public DiagnosticStorage getDiagnostics() { throw new UnsupportedOperationException("unimplemented"); } - override def duplicate( + @Override + public IR duplicate( boolean keepLocations, boolean keepMetadata, boolean keepDiagnostics, boolean keepIdentifiers - ): IR = { + ) { throw new UnsupportedOperationException("unimplemented"); } @Override - override def showCode(indent: Int): String = { + public String showCode(int indent) { throw new UnsupportedOperationException("unimplemented"); } """ @@ -270,8 +278,9 @@ String overrideUserDefinedMethods() { .map( field -> """ - override def $fieldName(): $returnType = { - return this.$fieldName; + @Override + public $returnType $fieldName() { + return $fieldName; } """ .replace("$returnType", field.getSimpleTypeName()) @@ -292,7 +301,7 @@ String builder() { .map( field -> """ - private var $fieldName: $fieldType = null, + private $fieldType $fieldName; """ .replace("$fieldName", field.getName()) .replace("$fieldType", field.getSimpleTypeName())) @@ -303,7 +312,7 @@ String builder() { .map( field -> """ - def $fieldName($fieldName: $fieldType): Builder = { + public Builder $fieldName($fieldType $fieldName) { this.$fieldName = $fieldName; return this; } @@ -330,18 +339,17 @@ String builder() { var code = """ - final class Builder( + public static final class Builder { $fieldDeclarations - ) { $fieldSetters - def build(): $className = { + public $className build() { validate(); return new $className($fieldList); } - private def validate(): Unit = { + private void validate() { $validationCode } } diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java index 7cdeb78902ec..16b2e7d3bcbb 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java @@ -12,12 +12,10 @@ import javax.lang.model.element.ElementKind; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; -import javax.tools.FileObject; -import javax.tools.StandardLocation; +import javax.tools.JavaFileObject; import org.enso.runtime.parser.dsl.IRChild; import org.enso.runtime.parser.dsl.IRNode; -/** Generates Scala classes for every Java interface annotated with {@link IRNode}. */ @SupportedAnnotationTypes({ "org.enso.runtime.parser.dsl.IRNode", "org.enso.runtime.parser.dsl.IRChild" @@ -49,44 +47,47 @@ private void processIrNode(Element irNodeElem) { var irNodeTypeElem = (TypeElement) irNodeElem; var irNodeInterfaceName = irNodeTypeElem.getSimpleName().toString(); var pkgName = packageName(irNodeTypeElem); - var newCaseClassName = irNodeInterfaceName + "Gen"; - FileObject srcGen = null; + var newClassName = irNodeInterfaceName + "Gen"; + String newBinaryName; + if (!pkgName.isEmpty()) { + newBinaryName = pkgName + "." + newClassName; + } else { + newBinaryName = newClassName; + } + JavaFileObject srcGen = null; try { - srcGen = - processingEnv - .getFiler() - .createResource(StandardLocation.SOURCE_OUTPUT, pkgName, newCaseClassName + ".scala"); + srcGen = processingEnv.getFiler().createSourceFile(newBinaryName, irNodeElem); } catch (IOException e) { printError("Failed to create source file for IRNode", irNodeElem); } assert srcGen != null; - var irNodeElement = new IRNodeElement(processingEnv, irNodeTypeElem, newCaseClassName); + var irNodeElement = new IRNodeElement(processingEnv, irNodeTypeElem, newClassName); try { try (var lineWriter = new PrintWriter(srcGen.openWriter())) { var code = """ $imports - final class $className( + public final class $className implements $interfaceName { $fields - ) extends $interfaceName { - - $overrideUserDefinedMethods - $overrideIRMethods - } + $constructor - final object $className { - def builder(): Builder = { + public static Builder builder() { return new Builder(); } + $overrideUserDefinedMethods + + $overrideIRMethods + $builder } """ .replace("$imports", irNodeElement.imports()) - .replace("$className", newCaseClassName) + .replace("$className", newClassName) .replace("$fields", irNodeElement.fields()) + .replace("$constructor", irNodeElement.constructor()) .replace("$interfaceName", irNodeInterfaceName) .replace("$overrideUserDefinedMethods", irNodeElement.overrideUserDefinedMethods()) .replace("$overrideIRMethods", irNodeElement.overrideIRMethods()) diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java index d9acc7d30f78..6d62a7cf6d32 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java @@ -15,13 +15,12 @@ static boolean isSubtypeOfIR(TypeMirror type, ProcessingEnvironment processingEn return processingEnv.getTypeUtils().isAssignable(type, irType); } - /** Returns true if the given type extends {@link org.enso.compiler.core.ir.Expression} */ + /** + * Returns true if the given type extends {@link org.enso.compiler.core.ir.Expression} + */ static boolean isSubtypeOfExpression(TypeMirror type, ProcessingEnvironment processingEnv) { var expressionType = - processingEnv - .getElementUtils() - .getTypeElement("org.enso.compiler.core.ir.Expression") - .asType(); + processingEnv.getElementUtils().getTypeElement("org.enso.compiler.core.ir.Expression").asType(); return processingEnv.getTypeUtils().isAssignable(type, expressionType); } diff --git a/engine/runtime-parser-processor/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java b/engine/runtime-parser-processor/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java index 55404a2dbb19..9fe0ae548e0e 100644 --- a/engine/runtime-parser-processor/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java +++ b/engine/runtime-parser-processor/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java @@ -6,8 +6,7 @@ import com.google.testing.compile.CompilationSubject; import com.google.testing.compile.Compiler; import com.google.testing.compile.JavaFileObjects; -import javax.tools.FileObject; -import javax.tools.StandardLocation; +import javax.tools.JavaFileObject; import org.enso.runtime.parser.processor.IRProcessor; import org.junit.Test; @@ -72,11 +71,11 @@ public interface JName extends IR {} var compiler = Compiler.javac().withProcessors(new IRProcessor()); var compilation = compiler.compile(src); CompilationSubject.assertThat(compilation).succeeded(); - var generatedScalaClass = - compilation.generatedFile(StandardLocation.SOURCE_OUTPUT, "JNameGen.scala"); - assertThat(generatedScalaClass.isPresent(), is(true)); - var srcContent = readSrcFile(generatedScalaClass.get()); - assertThat("Generated just one source", compilation.generatedFiles().size(), is(1)); + CompilationSubject.assertThat(compilation).generatedSourceFile("JNameGen").isNotNull(); + var genSrc = compilation.generatedSourceFile("JNameGen"); + assertThat(genSrc.isPresent(), is(true)); + var srcContent = readSrcFile(genSrc.get()); + assertThat("Generated just one source", compilation.generatedSourceFiles().size(), is(1)); } @Test @@ -98,16 +97,14 @@ public interface MyIR extends IR { var compiler = Compiler.javac().withProcessors(new IRProcessor()); var compilation = compiler.compile(src); CompilationSubject.assertThat(compilation).succeeded(); - CompilationSubject.assertThat(compilation) - .generatedFile(StandardLocation.SOURCE_OUTPUT, "MyIRGen.scala") - .isNotNull(); - var genSrc = compilation.generatedFile(StandardLocation.SOURCE_OUTPUT, "MyIRGen.scala"); + CompilationSubject.assertThat(compilation).generatedSourceFile("MyIRGen").isNotNull(); + var genSrc = compilation.generatedSourceFile("MyIRGen"); assertThat(genSrc.isPresent(), is(true)); var srcContent = readSrcFile(genSrc.get()); assertThat("Generated just one source", compilation.generatedSourceFiles().size(), is(1)); } - private static String readSrcFile(FileObject src) { + private static String readSrcFile(JavaFileObject src) { try { return src.getCharContent(true).toString(); } catch (Exception e) { From 298c8836ee92a7de4e1bc632d2765b1a9f2fd5da Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 21 Oct 2024 19:43:24 +0200 Subject: [PATCH 18/93] IRProcessor handles primitive fields --- .../enso/runtime/parser/processor/Field.java | 71 +++++++++---------- .../parser/processor/IRNodeElement.java | 28 +++++--- .../parser/processor/PrimitiveField.java | 49 +++++++++++++ .../parser/processor/ReferenceField.java | 60 ++++++++++++++++ .../enso/runtime/parser/processor/Utils.java | 9 +-- .../processor/test/TestIRProcessor.java | 40 ++++++++++- 6 files changed, 200 insertions(+), 57 deletions(-) create mode 100644 engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/PrimitiveField.java create mode 100644 engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ReferenceField.java diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Field.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Field.java index 5a454d39882e..35cbb8992d82 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Field.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Field.java @@ -1,55 +1,48 @@ package org.enso.runtime.parser.processor; import java.util.function.Function; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.TypeElement; +import org.enso.runtime.parser.dsl.IRChild; -final class Field { - private final TypeElement type; +/** + * A field of an IR node. Represented by any parameterless method on an interface annotated with + * {@link org.enso.runtime.parser.dsl.IRNode}. + */ +interface Field { - /** Name of the field (identifier). */ - private final String name; + /** Name (identifier) of the field. */ + String getName(); - /** If the field can be {@code null}. */ - private final boolean nullable; + /** Does not return null. */ + String getSimpleTypeName(); - private final boolean isChild; + /** May return null if the type is primitive. */ + String getQualifiedTypeName(); - Field(TypeElement type, String name, boolean nullable, boolean isChild) { - this.type = type; - this.name = name; - this.nullable = nullable; - this.isChild = isChild; - } - - boolean isChild() { - return isChild; - } - - boolean isNullable() { - return nullable; - } - - String getName() { - return name; - } + /** + * Returns true if this field is annotated with {@link org.enso.runtime.parser.dsl.IRChild}. + * + * @return + */ + boolean isChild(); - String getSimpleTypeName() { - return type.getSimpleName().toString(); - } + /** + * Returns true if this field is child with {@link IRChild#required()} set to false. + * + * @return + */ + boolean isNullable(); - String getQualifiedTypeName() { - return type.getQualifiedName().toString(); - } + /** Returns true if the type of this field is Java primitive. */ + boolean isPrimitive(); /** - * Returns true if this field extends {@link org.enso.compiler.core.ir.Expression} - * ({@link org.enso.compiler.core.ir.JExpression}). + * Returns true if this field extends {@link org.enso.compiler.core.ir.Expression} ({@link + * org.enso.compiler.core.ir.JExpression}). + * + *

This is useful, e.g., for the {@link org.enso.compiler.core.IR#mapExpressions(Function)} + * method. * - *

This is useful, e.g., for the {@link org.enso.compiler.core.IR#mapExpressions(Function)} method. * @return true if this field extends {@link org.enso.compiler.core.ir.Expression} */ - boolean isExpression(ProcessingEnvironment processingEnv) { - return Utils.isSubtypeOfExpression(type.asType(), processingEnv); - } + boolean isExpression(); } diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeElement.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeElement.java index 4cc65e274239..ef84535f5595 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeElement.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeElement.java @@ -42,7 +42,6 @@ final class IRNodeElement { """; /** - * @param processingEnv * @param irNodeInterface Type element of the interface annotated with {@link IRNode}. * @param className Simple name (non-qualified) of the newly generated class. */ @@ -58,6 +57,7 @@ final class IRNodeElement { String imports() { var importsForFields = fields.stream() + .filter(field -> !field.isPrimitive()) .map(field -> "import " + field.getQualifiedTypeName() + ";") .distinct() .collect(Collectors.joining(System.lineSeparator())); @@ -91,17 +91,23 @@ protected Void defaultAction(Element e, Void unused) { public Void visitExecutable(ExecutableElement e, Void unused) { if (e.getParameters().isEmpty()) { var retType = e.getReturnType(); - var retTypeElem = (TypeElement) processingEnv.getTypeUtils().asElement(retType); var name = e.getSimpleName().toString(); - var childAnnot = e.getAnnotation(IRChild.class); - boolean isChild = false; - boolean isNullable = false; - if (childAnnot != null) { - ensureIsSubtypeOfIR(retTypeElem); - isChild = true; - isNullable = !childAnnot.required(); + if (retType.getKind().isPrimitive()) { + fields.add(new PrimitiveField(retType, name)); + } else { + var retTypeElem = (TypeElement) processingEnv.getTypeUtils().asElement(retType); + assert retTypeElem != null; + var childAnnot = e.getAnnotation(IRChild.class); + boolean isChild = false; + boolean isNullable = false; + if (childAnnot != null) { + ensureIsSubtypeOfIR(retTypeElem); + isChild = true; + isNullable = !childAnnot.required(); + } + fields.add( + new ReferenceField(processingEnv, retTypeElem, name, isNullable, isChild)); } - fields.add(new Field(retTypeElem, name, isNullable, isChild)); } return super.visitExecutable(e, unused); } @@ -324,7 +330,7 @@ String builder() { // Validation code for all non-nullable fields var validationCode = fields.stream() - .filter(field -> !field.isNullable()) + .filter(field -> !field.isNullable() && !field.isPrimitive()) .map( field -> """ diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/PrimitiveField.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/PrimitiveField.java new file mode 100644 index 000000000000..2841e7b302db --- /dev/null +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/PrimitiveField.java @@ -0,0 +1,49 @@ +package org.enso.runtime.parser.processor; + +import javax.lang.model.type.TypeMirror; + +final class PrimitiveField implements Field { + + private final TypeMirror type; + private final String name; + + PrimitiveField(TypeMirror type, String name) { + this.type = type; + this.name = name; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getSimpleTypeName() { + return type.toString(); + } + + @Override + public String getQualifiedTypeName() { + return null; + } + + @Override + public boolean isChild() { + return false; + } + + @Override + public boolean isNullable() { + return false; + } + + @Override + public boolean isPrimitive() { + return true; + } + + @Override + public boolean isExpression() { + return false; + } +} diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ReferenceField.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ReferenceField.java new file mode 100644 index 000000000000..76e721611559 --- /dev/null +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ReferenceField.java @@ -0,0 +1,60 @@ +package org.enso.runtime.parser.processor; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.TypeElement; + +final class ReferenceField implements Field { + private final ProcessingEnvironment procEnv; + private final TypeElement type; + private final String name; + private final boolean nullable; + private final boolean isChild; + + ReferenceField( + ProcessingEnvironment procEnv, + TypeElement type, + String name, + boolean nullable, + boolean isChild) { + this.procEnv = procEnv; + this.type = type; + this.name = name; + this.nullable = nullable; + this.isChild = isChild; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getSimpleTypeName() { + return type.toString(); + } + + @Override + public String getQualifiedTypeName() { + return type.getQualifiedName().toString(); + } + + @Override + public boolean isChild() { + return isChild; + } + + @Override + public boolean isPrimitive() { + return false; + } + + @Override + public boolean isNullable() { + return nullable; + } + + @Override + public boolean isExpression() { + return Utils.isSubtypeOfExpression(type.asType(), procEnv); + } +} diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java index 6d62a7cf6d32..d9acc7d30f78 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java @@ -15,12 +15,13 @@ static boolean isSubtypeOfIR(TypeMirror type, ProcessingEnvironment processingEn return processingEnv.getTypeUtils().isAssignable(type, irType); } - /** - * Returns true if the given type extends {@link org.enso.compiler.core.ir.Expression} - */ + /** Returns true if the given type extends {@link org.enso.compiler.core.ir.Expression} */ static boolean isSubtypeOfExpression(TypeMirror type, ProcessingEnvironment processingEnv) { var expressionType = - processingEnv.getElementUtils().getTypeElement("org.enso.compiler.core.ir.Expression").asType(); + processingEnv + .getElementUtils() + .getTypeElement("org.enso.compiler.core.ir.Expression") + .asType(); return processingEnv.getTypeUtils().isAssignable(type, expressionType); } diff --git a/engine/runtime-parser-processor/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java b/engine/runtime-parser-processor/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java index 9fe0ae548e0e..ecf90c645b20 100644 --- a/engine/runtime-parser-processor/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java +++ b/engine/runtime-parser-processor/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java @@ -72,6 +72,11 @@ public interface JName extends IR {} var compilation = compiler.compile(src); CompilationSubject.assertThat(compilation).succeeded(); CompilationSubject.assertThat(compilation).generatedSourceFile("JNameGen").isNotNull(); + var srcSubject = + CompilationSubject.assertThat(compilation) + .generatedSourceFile("JNameGen") + .contentsAsUtf8String(); + srcSubject.containsMatch(""); var genSrc = compilation.generatedSourceFile("JNameGen"); assertThat(genSrc.isPresent(), is(true)); var srcContent = readSrcFile(genSrc.get()); @@ -98,10 +103,39 @@ public interface MyIR extends IR { var compilation = compiler.compile(src); CompilationSubject.assertThat(compilation).succeeded(); CompilationSubject.assertThat(compilation).generatedSourceFile("MyIRGen").isNotNull(); - var genSrc = compilation.generatedSourceFile("MyIRGen"); - assertThat(genSrc.isPresent(), is(true)); - var srcContent = readSrcFile(genSrc.get()); assertThat("Generated just one source", compilation.generatedSourceFiles().size(), is(1)); + var srcSubject = + CompilationSubject.assertThat(compilation) + .generatedSourceFile("MyIRGen") + .contentsAsUtf8String(); + srcSubject.containsMatch("JExpression expression\\(\\)"); + } + + @Test + public void irNodeWithMultipleFields_PrimitiveField() { + var src = + JavaFileObjects.forSourceString( + "MyIR", + """ + import org.enso.runtime.parser.dsl.IRNode; + import org.enso.runtime.parser.dsl.IRChild; + import org.enso.compiler.core.IR; + import org.enso.compiler.core.ir.JExpression; + + @IRNode + public interface MyIR extends IR { + boolean suspended(); + } + """); + var compiler = Compiler.javac().withProcessors(new IRProcessor()); + var compilation = compiler.compile(src); + CompilationSubject.assertThat(compilation).succeeded(); + assertThat("Generated just one source", compilation.generatedSourceFiles().size(), is(1)); + var srcSubject = + CompilationSubject.assertThat(compilation) + .generatedSourceFile("MyIRGen") + .contentsAsUtf8String(); + srcSubject.containsMatch("boolean suspended\\(\\)"); } private static String readSrcFile(JavaFileObject src) { From bc1e9c94c4678165305ad9fa8eb0132dbddadac8 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 21 Oct 2024 19:46:48 +0200 Subject: [PATCH 19/93] Remove unused methods --- .../org/enso/runtime/parser/processor/IRProcessor.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java index 16b2e7d3bcbb..fce100567637 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java @@ -100,8 +100,6 @@ public static Builder builder() { } } - private void processChildElem(Element childElem) {} - private String packageName(Element elem) { var pkg = processingEnv.getElementUtils().getPackageOf(elem); return pkg.getQualifiedName().toString(); @@ -111,12 +109,6 @@ private boolean isSubtypeOfIR(TypeMirror type) { return Utils.isSubtypeOfIR(type, processingEnv); } - private Set findChildElements(Element irNodeElem) { - return irNodeElem.getEnclosedElements().stream() - .filter(elem -> elem.getAnnotation(IRChild.class) != null) - .collect(Collectors.toUnmodifiableSet()); - } - private void printError(String msg, Element elem) { Utils.printError(msg, elem, processingEnv.getMessager()); } From ef05c64e3bd7ac292062d7fba846c1962420c519 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 21 Oct 2024 19:49:49 +0200 Subject: [PATCH 20/93] IRProcessor fails fast --- .../enso/runtime/parser/processor/IRProcessor.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java index fce100567637..455f2fe48af7 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java @@ -3,7 +3,6 @@ import java.io.IOException; import java.io.PrintWriter; import java.util.Set; -import java.util.stream.Collectors; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; @@ -13,7 +12,6 @@ import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; import javax.tools.JavaFileObject; -import org.enso.runtime.parser.dsl.IRChild; import org.enso.runtime.parser.dsl.IRNode; @SupportedAnnotationTypes({ @@ -31,17 +29,22 @@ public SourceVersion getSupportedSourceVersion() { public boolean process(Set annotations, RoundEnvironment roundEnv) { var irNodeElems = roundEnv.getElementsAnnotatedWith(IRNode.class); for (var irNodeElem : irNodeElems) { - processIrNode(irNodeElem); + var suc = processIrNode(irNodeElem); + if (!suc) { + return false; + } } return true; } - private void processIrNode(Element irNodeElem) { + private boolean processIrNode(Element irNodeElem) { if (irNodeElem.getKind() != ElementKind.INTERFACE) { printError("IRNode annotation can only be applied to interfaces", irNodeElem); + return false; } if (!isSubtypeOfIR(irNodeElem.asType())) { printError("Interface annotated with @IRNode must be a subtype of IR interface", irNodeElem); + return false; } assert irNodeElem instanceof TypeElement; var irNodeTypeElem = (TypeElement) irNodeElem; @@ -97,7 +100,9 @@ public static Builder builder() { } } catch (IOException e) { printError("Failed to write to source file for IRNode", irNodeElem); + return false; } + return true; } private String packageName(Element elem) { From aa0a5b6ff58c14fde166e3260cd0b2cbd49d8ca1 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 21 Oct 2024 20:03:16 +0200 Subject: [PATCH 21/93] IRProcessor can collect fields from super interfaces --- .../parser/processor/IRNodeElement.java | 23 +++++++++++-- .../processor/test/TestIRProcessor.java | 32 +++++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeElement.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeElement.java index ef84535f5595..47667d650554 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeElement.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeElement.java @@ -1,12 +1,15 @@ package org.enso.runtime.parser.processor; +import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Deque; import java.util.List; import java.util.stream.Collectors; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; import javax.lang.model.util.SimpleElementVisitor14; import org.enso.runtime.parser.dsl.IRChild; import org.enso.runtime.parser.dsl.IRNode; @@ -77,7 +80,7 @@ String imports() { private List getAllFields(TypeElement irNodeInterface) { var fields = new ArrayList(); - var elemVisitor = + var fieldCollector = new SimpleElementVisitor14() { @Override protected Void defaultAction(Element e, Void unused) { @@ -112,7 +115,17 @@ public Void visitExecutable(ExecutableElement e, Void unused) { return super.visitExecutable(e, unused); } }; - irNodeInterface.accept(elemVisitor, null); + var superInterfaces = irNodeInterface.getInterfaces(); + Deque toProcess = new ArrayDeque<>(superInterfaces); + while (!toProcess.isEmpty()) { + var current = toProcess.pop(); + // Skip processing of IR. + if (processingEnv.getTypeUtils().isSameType(getIrType().asType(), current)) { + continue; + } + var currentElem = processingEnv.getTypeUtils().asElement(current); + currentElem.accept(fieldCollector, null); + } return fields; } @@ -125,6 +138,12 @@ private void ensureIsSubtypeOfIR(TypeElement typeElem) { } } + private TypeElement getIrType() { + var typeElem = processingEnv.getElementUtils().getTypeElement("org.enso.compiler.core.IR"); + assert typeElem != null; + return typeElem; + } + /** * Returns string representation of the class fields. Meant to be at the beginning of the class * body. diff --git a/engine/runtime-parser-processor/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java b/engine/runtime-parser-processor/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java index ecf90c645b20..cdc51f26dfc4 100644 --- a/engine/runtime-parser-processor/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java +++ b/engine/runtime-parser-processor/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java @@ -138,6 +138,38 @@ public interface MyIR extends IR { srcSubject.containsMatch("boolean suspended\\(\\)"); } + @Test + public void irNodeWithInheritedField() { + + var src = + JavaFileObjects.forSourceString( + "MyIR", + """ + import org.enso.runtime.parser.dsl.IRNode; + import org.enso.runtime.parser.dsl.IRChild; + import org.enso.compiler.core.IR; + import org.enso.compiler.core.ir.JExpression; + + interface MySuperIR extends IR { + boolean suspended(); + } + + @IRNode + public interface MyIR extends MySuperIR { + } + + """); + var compiler = Compiler.javac().withProcessors(new IRProcessor()); + var compilation = compiler.compile(src); + CompilationSubject.assertThat(compilation).succeeded(); + assertThat("Generated just one source", compilation.generatedSourceFiles().size(), is(1)); + var srcSubject = + CompilationSubject.assertThat(compilation) + .generatedSourceFile("MyIRGen") + .contentsAsUtf8String(); + srcSubject.containsMatch("boolean suspended\\(\\)"); + } + private static String readSrcFile(JavaFileObject src) { try { return src.getCharContent(true).toString(); From c6def78c8809de8e617bf934be20b75c4bb8aea5 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Tue, 22 Oct 2024 09:25:11 +0200 Subject: [PATCH 22/93] Fix collecting fields from super interfaces. --- .../parser/processor/IRNodeElement.java | 30 ++++++--- .../processor/test/TestIRProcessor.java | 66 +++++++++++++++++++ 2 files changed, 88 insertions(+), 8 deletions(-) diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeElement.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeElement.java index 47667d650554..ed9608922e3f 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeElement.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeElement.java @@ -1,8 +1,8 @@ package org.enso.runtime.parser.processor; import java.util.ArrayDeque; -import java.util.ArrayList; import java.util.Deque; +import java.util.LinkedHashMap; import java.util.List; import java.util.stream.Collectors; import javax.annotation.processing.ProcessingEnvironment; @@ -78,7 +78,8 @@ String imports() { * @return List of fields */ private List getAllFields(TypeElement irNodeInterface) { - var fields = new ArrayList(); + // Mapped by field name + var fields = new LinkedHashMap(); var fieldCollector = new SimpleElementVisitor14() { @@ -96,7 +97,8 @@ public Void visitExecutable(ExecutableElement e, Void unused) { var retType = e.getReturnType(); var name = e.getSimpleName().toString(); if (retType.getKind().isPrimitive()) { - fields.add(new PrimitiveField(retType, name)); + var primField = new PrimitiveField(retType, name); + fields.put(name, primField); } else { var retTypeElem = (TypeElement) processingEnv.getTypeUtils().asElement(retType); assert retTypeElem != null; @@ -108,25 +110,37 @@ public Void visitExecutable(ExecutableElement e, Void unused) { isChild = true; isNullable = !childAnnot.required(); } - fields.add( - new ReferenceField(processingEnv, retTypeElem, name, isNullable, isChild)); + var refField = + new ReferenceField(processingEnv, retTypeElem, name, isNullable, isChild); + fields.put(name, refField); } } return super.visitExecutable(e, unused); } }; var superInterfaces = irNodeInterface.getInterfaces(); - Deque toProcess = new ArrayDeque<>(superInterfaces); + Deque toProcess = new ArrayDeque<>(); + toProcess.add(irNodeInterface.asType()); + toProcess.addAll(superInterfaces); + // Process transitively all the super interface until the parent IR is reached. while (!toProcess.isEmpty()) { var current = toProcess.pop(); - // Skip processing of IR. + // Skip processing of IR root interface. if (processingEnv.getTypeUtils().isSameType(getIrType().asType(), current)) { continue; } var currentElem = processingEnv.getTypeUtils().asElement(current); currentElem.accept(fieldCollector, null); + // Add all super interfaces to the processing queue, if they are not there already. + if (currentElem instanceof TypeElement currentTypeElem) { + for (var superInterface : currentTypeElem.getInterfaces()) { + if (!toProcess.contains(superInterface)) { + toProcess.add(superInterface); + } + } + } } - return fields; + return fields.values().stream().toList(); } private void ensureIsSubtypeOfIR(TypeElement typeElem) { diff --git a/engine/runtime-parser-processor/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java b/engine/runtime-parser-processor/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java index cdc51f26dfc4..e94c6d1c8f1d 100644 --- a/engine/runtime-parser-processor/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java +++ b/engine/runtime-parser-processor/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java @@ -140,7 +140,37 @@ public interface MyIR extends IR { @Test public void irNodeWithInheritedField() { + var src = + JavaFileObjects.forSourceString( + "MyIR", + """ + import org.enso.runtime.parser.dsl.IRNode; + import org.enso.runtime.parser.dsl.IRChild; + import org.enso.compiler.core.IR; + import org.enso.compiler.core.ir.JExpression; + + interface MySuperIR extends IR { + boolean suspended(); + } + + @IRNode + public interface MyIR extends MySuperIR { + } + """); + var compiler = Compiler.javac().withProcessors(new IRProcessor()); + var compilation = compiler.compile(src); + CompilationSubject.assertThat(compilation).succeeded(); + assertThat("Generated just one source", compilation.generatedSourceFiles().size(), is(1)); + var srcSubject = + CompilationSubject.assertThat(compilation) + .generatedSourceFile("MyIRGen") + .contentsAsUtf8String(); + srcSubject.containsMatch("boolean suspended\\(\\)"); + } + + @Test + public void irNodeWithInheritedField_Override() { var src = JavaFileObjects.forSourceString( "MyIR", @@ -154,6 +184,42 @@ interface MySuperIR extends IR { boolean suspended(); } + @IRNode + public interface MyIR extends MySuperIR { + boolean suspended(); + } + + """); + var compiler = Compiler.javac().withProcessors(new IRProcessor()); + var compilation = compiler.compile(src); + CompilationSubject.assertThat(compilation).succeeded(); + assertThat("Generated just one source", compilation.generatedSourceFiles().size(), is(1)); + var srcSubject = + CompilationSubject.assertThat(compilation) + .generatedSourceFile("MyIRGen") + .contentsAsUtf8String(); + srcSubject.containsMatch("boolean suspended\\(\\)"); + } + + @Test + public void irNodeWithInheritedField_Transitive() { + + var src = + JavaFileObjects.forSourceString( + "MyIR", + """ + import org.enso.runtime.parser.dsl.IRNode; + import org.enso.runtime.parser.dsl.IRChild; + import org.enso.compiler.core.IR; + import org.enso.compiler.core.ir.JExpression; + + interface MySuperSuperIR extends IR { + boolean suspended(); + } + + interface MySuperIR extends MySuperSuperIR { + } + @IRNode public interface MyIR extends MySuperIR { } From 8cea49115152167379e190591db896c991221ebc Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Tue, 22 Oct 2024 19:10:19 +0200 Subject: [PATCH 23/93] Prepare IRNodeProcessor for processing multiple nested interfaces --- ...Element.java => IRNodeClassGenerator.java} | 147 ++++++++++++------ .../runtime/parser/processor/IRProcessor.java | 32 ++-- .../processor/test/TestIRProcessor.java | 35 +++-- 3 files changed, 135 insertions(+), 79 deletions(-) rename engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/{IRNodeElement.java => IRNodeClassGenerator.java} (77%) diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeElement.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java similarity index 77% rename from engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeElement.java rename to engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java index ed9608922e3f..d93ce2f42a07 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeElement.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java @@ -2,72 +2,119 @@ import java.util.ArrayDeque; import java.util.Deque; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.SimpleElementVisitor14; +import org.enso.compiler.core.IR; import org.enso.runtime.parser.dsl.IRChild; import org.enso.runtime.parser.dsl.IRNode; /** - * Representation of an interface annotated with {@link org.enso.runtime.parser.dsl.IRNode}. Takes - * care of - Methods from {@code org.enso.compiler.core.IR} with no default implementation. - - * Methods annotated with {@link org.enso.runtime.parser.dsl.IRChild} (child elements). - Other - * methods (will be fields of the record, not children). + * Generates code for interfaces annotated with {@link org.enso.runtime.parser.dsl.IRNode}. + * Technically, the interface does not have to be annotated with {@link + * org.enso.runtime.parser.dsl.IRNode}, it can just be enclosed by another interface with that + * annotation. + * + *

It is expected that the interface (passed as {@link javax.lang.model.element.TypeElement} in + * this class) extends {@link org.enso.compiler.core.IR}, either directly or via a hierarchy of + * other super interfaces. + * + *

Every parameterless abstract method defined by the interface (or any super interface) is + * treated as a field of the IR node. If the parameterless method is annotated with {@link + * org.enso.runtime.parser.dsl.IRChild}, it is treated as a child and will get into the + * generated code for, e.g., methods like {@link IR#children()}. */ -final class IRNodeElement { +final class IRNodeClassGenerator { private final ProcessingEnvironment processingEnv; + private final TypeElement interfaceType; - /** Name of the class that is being generated. */ + /** Name of the class that is being generated */ private final String className; /** User defined fields - all the abstract parameterless methods, including the inherited ones. */ private final List fields; - private static final String IMPORTS = - """ -import java.util.UUID; -import java.util.ArrayList; -import java.util.function.Function; -import org.enso.compiler.core.Identifier; -import org.enso.compiler.core.IR; -import org.enso.compiler.core.ir.DiagnosticStorage; -import org.enso.compiler.core.ir.Expression; -import org.enso.compiler.core.ir.IdentifiedLocation; -import org.enso.compiler.core.ir.MetadataStorage; -import scala.Option; -import scala.collection.immutable.List; - """; + private static final Set defaultImports = + Set.of( + "import java.util.UUID;", + "import java.util.ArrayList;", + "import java.util.function.Function;", + "import org.enso.compiler.core.Identifier;", + "import org.enso.compiler.core.IR;", + "import org.enso.compiler.core.ir.DiagnosticStorage;", + "import org.enso.compiler.core.ir.Expression;", + "import org.enso.compiler.core.ir.IdentifiedLocation;", + "import org.enso.compiler.core.ir.MetadataStorage;", + "import scala.Option;", + "import scala.collection.immutable.List;"); /** - * @param irNodeInterface Type element of the interface annotated with {@link IRNode}. - * @param className Simple name (non-qualified) of the newly generated class. + * @param interfaceType Type of the interface for which we are generating code. It is expected + * that the interface does not contain any nested interfaces or classes, just methods. + * @param className Name of the generated class. Non qualified. */ - IRNodeElement( - ProcessingEnvironment processingEnv, TypeElement irNodeInterface, String className) { + IRNodeClassGenerator( + ProcessingEnvironment processingEnv, TypeElement interfaceType, String className) { assert !className.contains(".") : "Class name should be simple, not qualified"; this.processingEnv = processingEnv; + this.interfaceType = interfaceType; this.className = className; - this.fields = getAllFields(irNodeInterface); + this.fields = getAllFields(interfaceType); + var nestedTypesCnt = + interfaceType.getEnclosedElements().stream() + .filter( + elem -> + elem.getKind() == ElementKind.INTERFACE || elem.getKind() == ElementKind.CLASS) + .count(); + assert nestedTypesCnt == 0 : "Nested types must be handled separately"; } - /** Returns string representation of all necessary imports. */ - String imports() { + /** Returns set of import statements that should be included in the generated class. */ + Set imports() { var importsForFields = fields.stream() .filter(field -> !field.isPrimitive()) .map(field -> "import " + field.getQualifiedTypeName() + ";") - .distinct() - .collect(Collectors.joining(System.lineSeparator())); - var allImports = IMPORTS + System.lineSeparator() + importsForFields; + .collect(Collectors.toUnmodifiableSet()); + var allImports = new HashSet(); + allImports.addAll(defaultImports); + allImports.addAll(importsForFields); return allImports; } + /** Generates the body of the class - fields, field setters, method overrides, builder, etc. */ + String classBody() { + return """ + $fields + + $constructor + + public static Builder builder() { + return new Builder(); + } + + $overrideUserDefinedMethods + + $overrideIRMethods + + $builder + """ + .replace("$fields", fieldsCode()) + .replace("$constructor", constructor()) + .replace("$overrideUserDefinedMethods", overrideUserDefinedMethods()) + .replace("$overrideIRMethods", overrideIRMethods()) + .replace("$builder", builder()); + } + /** * Collects all abstract methods (with no parameters) from this interface and all the interfaces * that are extended by this interface. Every abstract method corresponds to a single field in the @@ -143,26 +190,11 @@ public Void visitExecutable(ExecutableElement e, Void unused) { return fields.values().stream().toList(); } - private void ensureIsSubtypeOfIR(TypeElement typeElem) { - if (!Utils.isSubtypeOfIR(typeElem.asType(), processingEnv)) { - Utils.printError( - "Method annotated with @IRChild must return a subtype of IR interface", - typeElem, - processingEnv.getMessager()); - } - } - - private TypeElement getIrType() { - var typeElem = processingEnv.getElementUtils().getTypeElement("org.enso.compiler.core.IR"); - assert typeElem != null; - return typeElem; - } - /** * Returns string representation of the class fields. Meant to be at the beginning of the class * body. */ - String fields() { + private String fieldsCode() { var userDefinedFields = fields.stream() .map(field -> "private final " + field.getSimpleTypeName() + " " + field.getName()) @@ -184,7 +216,7 @@ String fields() { * Returns string representation of the package-private constructor of the generated class. Note * that the constructor is meant to be invoked only by the internal Builder class. */ - String constructor() { + private String constructor() { var sb = new StringBuilder(); sb.append("private ").append(className).append("("); var inParens = @@ -239,7 +271,7 @@ private String childrenMethodBody() { * Returns a String representing all the overriden methods from {@link org.enso.compiler.core.IR}. * Meant to be inside the generated record definition. */ - String overrideIRMethods() { + private String overrideIRMethods() { var code = """ @@ -311,7 +343,7 @@ public String showCode(int indent) { * * @return Code of the overriden methods */ - String overrideUserDefinedMethods() { + private String overrideUserDefinedMethods() { var code = fields.stream() .map( @@ -334,7 +366,7 @@ String overrideUserDefinedMethods() { * * @return Code of the builder */ - String builder() { + private String builder() { var fieldDeclarations = fields.stream() .map( @@ -406,4 +438,19 @@ private static String indent(String code, int indentation) { .map(line -> " ".repeat(indentation) + line) .collect(Collectors.joining(System.lineSeparator())); } + + private void ensureIsSubtypeOfIR(TypeElement typeElem) { + if (!Utils.isSubtypeOfIR(typeElem.asType(), processingEnv)) { + Utils.printError( + "Method annotated with @IRChild must return a subtype of IR interface", + typeElem, + processingEnv.getMessager()); + } + } + + private TypeElement getIrType() { + var typeElem = processingEnv.getElementUtils().getTypeElement("org.enso.compiler.core.IR"); + assert typeElem != null; + return typeElem; + } } diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java index 455f2fe48af7..2545129ad2ce 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java @@ -3,6 +3,7 @@ import java.io.IOException; import java.io.PrintWriter; import java.util.Set; +import java.util.stream.Collectors; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; @@ -46,6 +47,11 @@ private boolean processIrNode(Element irNodeElem) { printError("Interface annotated with @IRNode must be a subtype of IR interface", irNodeElem); return false; } + var enclosingElem = irNodeElem.getEnclosingElement(); + if (enclosingElem != null && enclosingElem.getKind() != ElementKind.PACKAGE) { + printError("Interface annotated with @IRNode must not be nested", irNodeElem); + return false; + } assert irNodeElem instanceof TypeElement; var irNodeTypeElem = (TypeElement) irNodeElem; var irNodeInterfaceName = irNodeTypeElem.getSimpleName().toString(); @@ -64,37 +70,23 @@ private boolean processIrNode(Element irNodeElem) { printError("Failed to create source file for IRNode", irNodeElem); } assert srcGen != null; - var irNodeElement = new IRNodeElement(processingEnv, irNodeTypeElem, newClassName); + var irNodeClassGen = new IRNodeClassGenerator(processingEnv, irNodeTypeElem, newClassName); try { try (var lineWriter = new PrintWriter(srcGen.openWriter())) { + var imports = + irNodeClassGen.imports().stream().collect(Collectors.joining(System.lineSeparator())); var code = """ $imports public final class $className implements $interfaceName { - $fields - - $constructor - - public static Builder builder() { - return new Builder(); - } - - $overrideUserDefinedMethods - - $overrideIRMethods - - $builder + $classBody } """ - .replace("$imports", irNodeElement.imports()) + .replace("$imports", imports) .replace("$className", newClassName) - .replace("$fields", irNodeElement.fields()) - .replace("$constructor", irNodeElement.constructor()) .replace("$interfaceName", irNodeInterfaceName) - .replace("$overrideUserDefinedMethods", irNodeElement.overrideUserDefinedMethods()) - .replace("$overrideIRMethods", irNodeElement.overrideIRMethods()) - .replace("$builder", irNodeElement.builder()); + .replace("$classBody", irNodeClassGen.classBody()); lineWriter.println(code); lineWriter.println(); } diff --git a/engine/runtime-parser-processor/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java b/engine/runtime-parser-processor/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java index e94c6d1c8f1d..db371c70bde9 100644 --- a/engine/runtime-parser-processor/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java +++ b/engine/runtime-parser-processor/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java @@ -6,7 +6,6 @@ import com.google.testing.compile.CompilationSubject; import com.google.testing.compile.Compiler; import com.google.testing.compile.JavaFileObjects; -import javax.tools.JavaFileObject; import org.enso.runtime.parser.processor.IRProcessor; import org.junit.Test; @@ -79,7 +78,6 @@ public interface JName extends IR {} srcSubject.containsMatch(""); var genSrc = compilation.generatedSourceFile("JNameGen"); assertThat(genSrc.isPresent(), is(true)); - var srcContent = readSrcFile(genSrc.get()); assertThat("Generated just one source", compilation.generatedSourceFiles().size(), is(1)); } @@ -203,7 +201,6 @@ public interface MyIR extends MySuperIR { @Test public void irNodeWithInheritedField_Transitive() { - var src = JavaFileObjects.forSourceString( "MyIR", @@ -236,11 +233,31 @@ public interface MyIR extends MySuperIR { srcSubject.containsMatch("boolean suspended\\(\\)"); } - private static String readSrcFile(JavaFileObject src) { - try { - return src.getCharContent(true).toString(); - } catch (Exception e) { - throw new RuntimeException(e); - } + @Test + public void irNodeAsNestedInterface() { + var src = + JavaFileObjects.forSourceString( + "JName", + """ + import org.enso.runtime.parser.dsl.IRNode; + import org.enso.compiler.core.IR; + + @IRNode + public interface JName extends IR { + String name(); + + interface JBlank extends JName {} + } + """); + var compiler = Compiler.javac().withProcessors(new IRProcessor()); + var compilation = compiler.compile(src); + CompilationSubject.assertThat(compilation).succeeded(); + assertThat("Generated just one source", compilation.generatedSourceFiles().size(), is(1)); + var srcSubject = + CompilationSubject.assertThat(compilation) + .generatedSourceFile("JNameGen") + .contentsAsUtf8String(); + srcSubject.contains("public class JNameGen"); + srcSubject.contains("public final class JBlankGen"); } } From f5cb3311fded5f279257d42d3e5c232b924aff8f Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Tue, 22 Oct 2024 19:58:25 +0200 Subject: [PATCH 24/93] IRNodeProcessor handles multiple nested interfaces --- .../processor/IRNodeClassGenerator.java | 20 ++- .../runtime/parser/processor/IRProcessor.java | 142 +++++++++++++++--- .../enso/runtime/parser/processor/Utils.java | 7 + .../processor/test/TestIRProcessor.java | 4 +- 4 files changed, 151 insertions(+), 22 deletions(-) diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java index d93ce2f42a07..008f2574e067 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java @@ -69,13 +69,27 @@ final class IRNodeClassGenerator { this.interfaceType = interfaceType; this.className = className; this.fields = getAllFields(interfaceType); - var nestedTypesCnt = + var nestedTypes = interfaceType.getEnclosedElements().stream() .filter( elem -> elem.getKind() == ElementKind.INTERFACE || elem.getKind() == ElementKind.CLASS) - .count(); - assert nestedTypesCnt == 0 : "Nested types must be handled separately"; + .toList(); + if (!nestedTypes.isEmpty()) { + throw new RuntimeException("Nested types must be handled separately: " + nestedTypes); + } + } + + /** Returns simple name of the generated class. */ + String getClassName() { + return className; + } + + /** + * Returns the simple name of the interface for which an implementing class is being generated. + */ + String getInterfaceName() { + return interfaceType.getSimpleName().toString(); } /** Returns set of import statements that should be included in the generated class. */ diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java index 2545129ad2ce..e22af657bf38 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java @@ -2,6 +2,8 @@ import java.io.IOException; import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; import java.util.Set; import java.util.stream.Collectors; import javax.annotation.processing.AbstractProcessor; @@ -12,6 +14,7 @@ import javax.lang.model.element.ElementKind; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.SimpleElementVisitor14; import javax.tools.JavaFileObject; import org.enso.runtime.parser.dsl.IRNode; @@ -54,6 +57,7 @@ private boolean processIrNode(Element irNodeElem) { } assert irNodeElem instanceof TypeElement; var irNodeTypeElem = (TypeElement) irNodeElem; + var nestedInterfaces = collectNestedInterfaces(irNodeTypeElem); var irNodeInterfaceName = irNodeTypeElem.getSimpleName().toString(); var pkgName = packageName(irNodeTypeElem); var newClassName = irNodeInterfaceName + "Gen"; @@ -63,6 +67,7 @@ private boolean processIrNode(Element irNodeElem) { } else { newBinaryName = newClassName; } + JavaFileObject srcGen = null; try { srcGen = processingEnv.getFiler().createSourceFile(newBinaryName, irNodeElem); @@ -70,25 +75,27 @@ private boolean processIrNode(Element irNodeElem) { printError("Failed to create source file for IRNode", irNodeElem); } assert srcGen != null; - var irNodeClassGen = new IRNodeClassGenerator(processingEnv, irNodeTypeElem, newClassName); + + String generatedCode; + if (nestedInterfaces.isEmpty()) { + var classGenerator = new IRNodeClassGenerator(processingEnv, irNodeTypeElem, newClassName); + generatedCode = generateSingleNodeClass(classGenerator); + } else { + var nestedClassGenerators = + nestedInterfaces.stream() + .map( + iface -> { + var newNestedClassName = iface.getSimpleName().toString() + "Gen"; + return new IRNodeClassGenerator(processingEnv, iface, newNestedClassName); + }) + .toList(); + generatedCode = + generateMultipleNodeClasses(nestedClassGenerators, newClassName, irNodeInterfaceName); + } + try { try (var lineWriter = new PrintWriter(srcGen.openWriter())) { - var imports = - irNodeClassGen.imports().stream().collect(Collectors.joining(System.lineSeparator())); - var code = - """ - $imports - - public final class $className implements $interfaceName { - $classBody - } - """ - .replace("$imports", imports) - .replace("$className", newClassName) - .replace("$interfaceName", irNodeInterfaceName) - .replace("$classBody", irNodeClassGen.classBody()); - lineWriter.println(code); - lineWriter.println(); + lineWriter.write(generatedCode); } } catch (IOException e) { printError("Failed to write to source file for IRNode", irNodeElem); @@ -109,4 +116,105 @@ private boolean isSubtypeOfIR(TypeMirror type) { private void printError(String msg, Element elem) { Utils.printError(msg, elem, processingEnv.getMessager()); } + + /** + * Generates code for a single class that implements a single interface annotated with {@link + * IRNode}. + * + * @return The generated code ready to be written to a {@code .java} source. + */ + private static String generateSingleNodeClass(IRNodeClassGenerator irNodeClassGen) { + var imports = + irNodeClassGen.imports().stream().collect(Collectors.joining(System.lineSeparator())); + var code = + """ + $imports + + public final class $className implements $interfaceName { + $classBody + } + """ + .replace("$imports", imports) + .replace("$className", irNodeClassGen.getClassName()) + .replace("$interfaceName", irNodeClassGen.getInterfaceName()) + .replace("$classBody", irNodeClassGen.classBody()); + return code; + } + + /** + * Generates code for many inner classes. This is the case when an outer interface annotated with + * {@link IRNode} contains many nested interfaces. + * + * @param nestedClassGenerators Class generators for all the nested interfaces. + * @param newOuterClassName Name for the newly generate public outer class. + * @param outerInterfaceName Name of the interface annotated by {@link IRNode}, that is, the outer + * interface for which we are generating multiple inner classes. + * @return The generated code ready to be written to a {@code .java} source. + */ + private static String generateMultipleNodeClasses( + List nestedClassGenerators, + String newOuterClassName, + String outerInterfaceName) { + var imports = + nestedClassGenerators.stream() + .flatMap(gen -> gen.imports().stream()) + .collect(Collectors.joining(System.lineSeparator())); + var sb = new StringBuilder(); + sb.append(imports); + sb.append(System.lineSeparator()); + sb.append(System.lineSeparator()); + sb.append("public final class ") + .append(newOuterClassName) + .append(" {") + .append(System.lineSeparator()); + sb.append(System.lineSeparator()); + sb.append(" ") + .append("private ") + .append(newOuterClassName) + .append("() {}") + .append(System.lineSeparator()); + sb.append(System.lineSeparator()); + for (var classGen : nestedClassGenerators) { + sb.append(" public static final class ") + .append(classGen.getClassName()) + .append(" implements ") + .append(outerInterfaceName) + .append(".") + .append(classGen.getInterfaceName()) + .append(" {") + .append(System.lineSeparator()); + sb.append(Utils.indent(classGen.classBody(), 2)); + sb.append(" }"); + sb.append(System.lineSeparator()); + } + sb.append("}"); + sb.append(System.lineSeparator()); + return sb.toString(); + } + + private List collectNestedInterfaces(TypeElement interfaceType) { + var nestedTypes = new ArrayList(); + var typeVisitor = + new SimpleElementVisitor14() { + @Override + protected Void defaultAction(Element e, Void unused) { + for (var childElem : e.getEnclosedElements()) { + childElem.accept(this, unused); + } + return null; + } + + @Override + public Void visitType(TypeElement e, Void unused) { + if (e.getKind() == ElementKind.INTERFACE) { + nestedTypes.add(e); + } + return super.visitType(e, unused); + } + }; + for (var enclosedElem : interfaceType.getEnclosedElements()) { + enclosedElem.accept(typeVisitor, null); + } + return nestedTypes; + } } diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java index d9acc7d30f78..13ed36d23672 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java @@ -1,5 +1,6 @@ package org.enso.runtime.parser.processor; +import java.util.stream.Collectors; import javax.annotation.processing.Messager; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; @@ -28,4 +29,10 @@ static boolean isSubtypeOfExpression(TypeMirror type, ProcessingEnvironment proc static void printError(String msg, Element elem, Messager messager) { messager.printMessage(Kind.ERROR, msg, elem); } + + static String indent(String code, int indentation) { + return code.lines() + .map(line -> " ".repeat(indentation) + line) + .collect(Collectors.joining(System.lineSeparator())); + } } diff --git a/engine/runtime-parser-processor/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java b/engine/runtime-parser-processor/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java index db371c70bde9..da4983e2142a 100644 --- a/engine/runtime-parser-processor/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java +++ b/engine/runtime-parser-processor/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java @@ -257,7 +257,7 @@ interface JBlank extends JName {} CompilationSubject.assertThat(compilation) .generatedSourceFile("JNameGen") .contentsAsUtf8String(); - srcSubject.contains("public class JNameGen"); - srcSubject.contains("public final class JBlankGen"); + srcSubject.contains("public final class JNameGen"); + srcSubject.contains("public static final class JBlankGen implements JName.JBlank"); } } From 2393e188aa0dec07dd745dbb9693957ff89b334a Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Tue, 22 Oct 2024 20:21:25 +0200 Subject: [PATCH 25/93] runtime-parser-processor does not depend on runtime-parser --- build.sbt | 2 -- .../src/main/java/module-info.java | 1 - .../processor/IRNodeClassGenerator.java | 5 ++-- .../runtime/parser/processor/IRProcessor.java | 11 +++----- .../enso/runtime/parser/processor/Utils.java | 27 ++++++++++++++++--- 5 files changed, 28 insertions(+), 18 deletions(-) diff --git a/build.sbt b/build.sbt index 2671ddb09ccb..a4ef611ff8e2 100644 --- a/build.sbt +++ b/build.sbt @@ -3143,11 +3143,9 @@ lazy val `runtime-parser-processor` = "com.google.testing.compile" % "compile-testing" % "0.21.0" % Test ), Compile / internalModuleDependencies := Seq( - (`runtime-parser` / Compile / exportedModule).value, (`runtime-parser-dsl` / Compile / exportedModule).value ) ) - .dependsOn(`runtime-parser`) .dependsOn(`runtime-parser-dsl`) lazy val `runtime-compiler` = diff --git a/engine/runtime-parser-processor/src/main/java/module-info.java b/engine/runtime-parser-processor/src/main/java/module-info.java index 44d5067ce381..0a0fea7f3c8f 100644 --- a/engine/runtime-parser-processor/src/main/java/module-info.java +++ b/engine/runtime-parser-processor/src/main/java/module-info.java @@ -1,5 +1,4 @@ module org.enso.runtime.parser.processor { requires java.compiler; requires org.enso.runtime.parser.dsl; - requires org.enso.runtime.parser; } diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java index 008f2574e067..bef182ea0588 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java @@ -14,7 +14,6 @@ import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.SimpleElementVisitor14; -import org.enso.compiler.core.IR; import org.enso.runtime.parser.dsl.IRChild; import org.enso.runtime.parser.dsl.IRNode; @@ -31,7 +30,7 @@ *

Every parameterless abstract method defined by the interface (or any super interface) is * treated as a field of the IR node. If the parameterless method is annotated with {@link * org.enso.runtime.parser.dsl.IRChild}, it is treated as a child and will get into the - * generated code for, e.g., methods like {@link IR#children()}. + * generated code for, e.g., methods like {@link org.enso.compiler.core.IR#children()}. */ final class IRNodeClassGenerator { private final ProcessingEnvironment processingEnv; @@ -454,7 +453,7 @@ private static String indent(String code, int indentation) { } private void ensureIsSubtypeOfIR(TypeElement typeElem) { - if (!Utils.isSubtypeOfIR(typeElem.asType(), processingEnv)) { + if (!Utils.isSubtypeOfIR(typeElem, processingEnv)) { Utils.printError( "Method annotated with @IRChild must return a subtype of IR interface", typeElem, diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java index e22af657bf38..7128625ce9b8 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java @@ -13,7 +13,6 @@ import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeMirror; import javax.lang.model.util.SimpleElementVisitor14; import javax.tools.JavaFileObject; import org.enso.runtime.parser.dsl.IRNode; @@ -46,7 +45,9 @@ private boolean processIrNode(Element irNodeElem) { printError("IRNode annotation can only be applied to interfaces", irNodeElem); return false; } - if (!isSubtypeOfIR(irNodeElem.asType())) { + assert irNodeElem instanceof TypeElement; + var irNodeTypeElem = (TypeElement) irNodeElem; + if (!Utils.isSubtypeOfIR(irNodeTypeElem, processingEnv)) { printError("Interface annotated with @IRNode must be a subtype of IR interface", irNodeElem); return false; } @@ -55,8 +56,6 @@ private boolean processIrNode(Element irNodeElem) { printError("Interface annotated with @IRNode must not be nested", irNodeElem); return false; } - assert irNodeElem instanceof TypeElement; - var irNodeTypeElem = (TypeElement) irNodeElem; var nestedInterfaces = collectNestedInterfaces(irNodeTypeElem); var irNodeInterfaceName = irNodeTypeElem.getSimpleName().toString(); var pkgName = packageName(irNodeTypeElem); @@ -109,10 +108,6 @@ private String packageName(Element elem) { return pkg.getQualifiedName().toString(); } - private boolean isSubtypeOfIR(TypeMirror type) { - return Utils.isSubtypeOfIR(type, processingEnv); - } - private void printError(String msg, Element elem) { Utils.printError(msg, elem, processingEnv.getMessager()); } diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java index 13ed36d23672..061ad7b96bbb 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java @@ -1,19 +1,38 @@ package org.enso.runtime.parser.processor; +import java.util.ArrayDeque; import java.util.stream.Collectors; import javax.annotation.processing.Messager; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; import javax.tools.Diagnostic.Kind; final class Utils { private Utils() {} - static boolean isSubtypeOfIR(TypeMirror type, ProcessingEnvironment processingEnv) { - var irType = - processingEnv.getElementUtils().getTypeElement("org.enso.compiler.core.IR").asType(); - return processingEnv.getTypeUtils().isAssignable(type, irType); + /** Returns true if the given {@code type} is a subtype of {@code org.enso.compiler.core.IR}. */ + static boolean isSubtypeOfIR(TypeElement type, ProcessingEnvironment processingEnv) { + var interfacesToProcess = new ArrayDeque(); + interfacesToProcess.add(type); + while (!interfacesToProcess.isEmpty()) { + var current = interfacesToProcess.pop(); + if (current.getSimpleName().toString().equals("IR")) { + // current.getQualifiedName().toString() returns only "IR" as well, so we can't use it. + // This is because runtime-parser-processor project does not depend on runtime-parser and + // so the org.enso.compiler.core.IR interface is not available in the classpath. + return true; + } + // Add all super interfaces to the queue + for (var superInterface : current.getInterfaces()) { + var superInterfaceElem = processingEnv.getTypeUtils().asElement(superInterface); + if (superInterfaceElem instanceof TypeElement superInterfaceTypeElem) { + interfacesToProcess.add(superInterfaceTypeElem); + } + } + } + return false; } /** Returns true if the given type extends {@link org.enso.compiler.core.ir.Expression} */ From cf1d079337e67ede1c1533218887552f28640392 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Tue, 22 Oct 2024 20:38:30 +0200 Subject: [PATCH 26/93] Move parser processor tests to a separate project --- build.sbt | 16 ++++++++++++---- .../parser/processor/test/TestIRProcessor.java | 0 2 files changed, 12 insertions(+), 4 deletions(-) rename engine/{runtime-parser-processor => runtime-parser-processor-tests}/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java (100%) diff --git a/build.sbt b/build.sbt index a4ef611ff8e2..c0cd6b3a6328 100644 --- a/build.sbt +++ b/build.sbt @@ -3129,9 +3129,8 @@ lazy val `runtime-parser-dsl` = frgaalJavaCompilerSetting ) -lazy val `runtime-parser-processor` = - (project in file("engine/runtime-parser-processor")) - .enablePlugins(JPMSPlugin) +lazy val `runtime-parser-processor-tests` = + (project in file("engine/runtime-parser-processor-tests")) .settings( frgaalJavaCompilerSetting, commands += WithDebugCommand.withDebug, @@ -3141,7 +3140,16 @@ lazy val `runtime-parser-processor` = "com.github.sbt" % "junit-interface" % junitIfVersion % Test, "org.hamcrest" % "hamcrest-all" % hamcrestVersion % Test, "com.google.testing.compile" % "compile-testing" % "0.21.0" % Test - ), + ) + ) + .dependsOn(`runtime-parser-processor`) + .dependsOn(`runtime-parser` % Test) + +lazy val `runtime-parser-processor` = + (project in file("engine/runtime-parser-processor")) + .enablePlugins(JPMSPlugin) + .settings( + frgaalJavaCompilerSetting, Compile / internalModuleDependencies := Seq( (`runtime-parser-dsl` / Compile / exportedModule).value ) diff --git a/engine/runtime-parser-processor/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java b/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java similarity index 100% rename from engine/runtime-parser-processor/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java rename to engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java From f63bda15b07b12ff85171f38d1e4bb2dc204ab49 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Tue, 22 Oct 2024 20:39:21 +0200 Subject: [PATCH 27/93] runtime-parser-processor does not depend on runtime-parser --- .../runtime/parser/processor/IRNodeClassGenerator.java | 8 +------- .../java/org/enso/runtime/parser/processor/Utils.java | 7 +++++++ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java index bef182ea0588..f7447d5bd549 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java @@ -186,7 +186,7 @@ public Void visitExecutable(ExecutableElement e, Void unused) { while (!toProcess.isEmpty()) { var current = toProcess.pop(); // Skip processing of IR root interface. - if (processingEnv.getTypeUtils().isSameType(getIrType().asType(), current)) { + if (Utils.isIRInterface(current, processingEnv)) { continue; } var currentElem = processingEnv.getTypeUtils().asElement(current); @@ -460,10 +460,4 @@ private void ensureIsSubtypeOfIR(TypeElement typeElem) { processingEnv.getMessager()); } } - - private TypeElement getIrType() { - var typeElem = processingEnv.getElementUtils().getTypeElement("org.enso.compiler.core.IR"); - assert typeElem != null; - return typeElem; - } } diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java index 061ad7b96bbb..6c861115ec29 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java @@ -5,6 +5,7 @@ import javax.annotation.processing.Messager; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; import javax.tools.Diagnostic.Kind; @@ -35,6 +36,12 @@ static boolean isSubtypeOfIR(TypeElement type, ProcessingEnvironment processingE return false; } + /** Returns true if the given {@code type} is an {@code org.enso.compiler.core.IR} interface. */ + static boolean isIRInterface(TypeMirror type, ProcessingEnvironment processingEnv) { + var elem = processingEnv.getTypeUtils().asElement(type); + return elem.getKind() == ElementKind.INTERFACE && elem.getSimpleName().toString().equals("IR"); + } + /** Returns true if the given type extends {@link org.enso.compiler.core.ir.Expression} */ static boolean isSubtypeOfExpression(TypeMirror type, ProcessingEnvironment processingEnv) { var expressionType = From 158a4f11087cc0307135685e0ee99239bda96471 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Tue, 22 Oct 2024 20:39:56 +0200 Subject: [PATCH 28/93] runtime-parser depends on runtime-parser-processor --- build.sbt | 4 +++- engine/runtime-parser/src/main/java/module-info.java | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index c0cd6b3a6328..7910dbf722d4 100644 --- a/build.sbt +++ b/build.sbt @@ -3114,13 +3114,15 @@ lazy val `runtime-parser` = Compile / internalModuleDependencies := Seq( (`syntax-rust-definition` / Compile / exportedModule).value, (`persistance` / Compile / exportedModule).value, - (`runtime-parser-dsl` / Compile / exportedModule).value + (`runtime-parser-dsl` / Compile / exportedModule).value, + (`runtime-parser-processor` / Compile / exportedModule).value ) ) .dependsOn(`syntax-rust-definition`) .dependsOn(`persistance`) .dependsOn(`persistance-dsl` % "provided") .dependsOn(`runtime-parser-dsl`) + .dependsOn(`runtime-parser-processor`) lazy val `runtime-parser-dsl` = (project in file("engine/runtime-parser-dsl")) diff --git a/engine/runtime-parser/src/main/java/module-info.java b/engine/runtime-parser/src/main/java/module-info.java index 31fa29a2c6d2..563b0ae6ada2 100644 --- a/engine/runtime-parser/src/main/java/module-info.java +++ b/engine/runtime-parser/src/main/java/module-info.java @@ -3,6 +3,7 @@ requires scala.library; requires org.enso.persistance; requires static org.enso.runtime.parser.dsl; + requires static org.enso.runtime.parser.processor; exports org.enso.compiler.core; exports org.enso.compiler.core.ir; From 1289374d0fea1ae9dd98709e8352ed6db5a8fbf9 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 25 Oct 2024 17:23:03 +0200 Subject: [PATCH 29/93] IRProcessor adds package --- .../runtime/parser/processor/IRProcessor.java | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java index 7128625ce9b8..bff86a39de30 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java @@ -78,7 +78,7 @@ private boolean processIrNode(Element irNodeElem) { String generatedCode; if (nestedInterfaces.isEmpty()) { var classGenerator = new IRNodeClassGenerator(processingEnv, irNodeTypeElem, newClassName); - generatedCode = generateSingleNodeClass(classGenerator); + generatedCode = generateSingleNodeClass(classGenerator, pkgName); } else { var nestedClassGenerators = nestedInterfaces.stream() @@ -89,7 +89,8 @@ private boolean processIrNode(Element irNodeElem) { }) .toList(); generatedCode = - generateMultipleNodeClasses(nestedClassGenerators, newClassName, irNodeInterfaceName); + generateMultipleNodeClasses( + nestedClassGenerators, pkgName, newClassName, irNodeInterfaceName); } try { @@ -116,19 +117,25 @@ private void printError(String msg, Element elem) { * Generates code for a single class that implements a single interface annotated with {@link * IRNode}. * + * @param pkgName Package of the current interface annotated with {@link IRNode}. * @return The generated code ready to be written to a {@code .java} source. */ - private static String generateSingleNodeClass(IRNodeClassGenerator irNodeClassGen) { + private static String generateSingleNodeClass( + IRNodeClassGenerator irNodeClassGen, String pkgName) { var imports = irNodeClassGen.imports().stream().collect(Collectors.joining(System.lineSeparator())); + var pkg = pkgName.isEmpty() ? "" : "package " + pkgName + ";"; var code = """ + $pkg + $imports public final class $className implements $interfaceName { $classBody } """ + .replace("$pkg", pkg) .replace("$imports", imports) .replace("$className", irNodeClassGen.getClassName()) .replace("$interfaceName", irNodeClassGen.getInterfaceName()) @@ -141,6 +148,7 @@ public final class $className implements $interfaceName { * {@link IRNode} contains many nested interfaces. * * @param nestedClassGenerators Class generators for all the nested interfaces. + * @param pkgName Package of the outer interface annotated with {@link IRNode}. * @param newOuterClassName Name for the newly generate public outer class. * @param outerInterfaceName Name of the interface annotated by {@link IRNode}, that is, the outer * interface for which we are generating multiple inner classes. @@ -148,6 +156,7 @@ public final class $className implements $interfaceName { */ private static String generateMultipleNodeClasses( List nestedClassGenerators, + String pkgName, String newOuterClassName, String outerInterfaceName) { var imports = @@ -155,6 +164,9 @@ private static String generateMultipleNodeClasses( .flatMap(gen -> gen.imports().stream()) .collect(Collectors.joining(System.lineSeparator())); var sb = new StringBuilder(); + if (!pkgName.isEmpty()) { + sb.append("package ").append(pkgName).append(";").append(System.lineSeparator()); + } sb.append(imports); sb.append(System.lineSeparator()); sb.append(System.lineSeparator()); From 73a9d31cc4e3ec91e815735fa68366c60b8f755d Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 25 Oct 2024 17:23:25 +0200 Subject: [PATCH 30/93] ParserDependenciesTest ensures that IRNodeProcessor is on class path --- .../enso/compiler/core/ParserDependenciesTest.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/engine/runtime-parser/src/test/java/org/enso/compiler/core/ParserDependenciesTest.java b/engine/runtime-parser/src/test/java/org/enso/compiler/core/ParserDependenciesTest.java index 7f920a52980b..b6299f79e2fb 100644 --- a/engine/runtime-parser/src/test/java/org/enso/compiler/core/ParserDependenciesTest.java +++ b/engine/runtime-parser/src/test/java/org/enso/compiler/core/ParserDependenciesTest.java @@ -1,6 +1,10 @@ package org.enso.compiler.core; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; import org.junit.Test; @@ -37,4 +41,14 @@ public void avoidPolyglotDependency() { // correct } } + + @Test + public void parserProcessorIsAvailable() { + try { + var clazz = Class.forName("org.enso.runtime.parser.processor.IRProcessor"); + assertThat(clazz, is(notNullValue())); + } catch (ClassNotFoundException e) { + fail(e.getMessage()); + } + } } From be4396c3f1ef3999f508d281f59e7c8d1b3cec0b Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 25 Oct 2024 17:23:41 +0200 Subject: [PATCH 31/93] runtime-parser-processor module provides annotation processor service --- engine/runtime-parser-processor/src/main/java/module-info.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/engine/runtime-parser-processor/src/main/java/module-info.java b/engine/runtime-parser-processor/src/main/java/module-info.java index 0a0fea7f3c8f..91a6d8e6532b 100644 --- a/engine/runtime-parser-processor/src/main/java/module-info.java +++ b/engine/runtime-parser-processor/src/main/java/module-info.java @@ -1,4 +1,7 @@ module org.enso.runtime.parser.processor { requires java.compiler; requires org.enso.runtime.parser.dsl; + + provides javax.annotation.processing.Processor with + org.enso.runtime.parser.processor.IRProcessor; } From 366523205713a89e1b305c9a8ede73d2952af65e Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 25 Oct 2024 17:23:55 +0200 Subject: [PATCH 32/93] Fix getSimpleTypeName in ReferenceField --- .../java/org/enso/runtime/parser/processor/ReferenceField.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ReferenceField.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ReferenceField.java index 76e721611559..32ee5bd77b35 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ReferenceField.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ReferenceField.java @@ -30,7 +30,7 @@ public String getName() { @Override public String getSimpleTypeName() { - return type.toString(); + return type.getSimpleName().toString(); } @Override From 260d11d766ecdf1c6c474401fc6e462ab62e9320 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 25 Oct 2024 17:24:19 +0200 Subject: [PATCH 33/93] No nested @IRNode annotation --- .../java/org/enso/compiler/core/ir/JDefinitionArgument.java | 2 +- .../main/java/org/enso/compiler/core/ir/JExpression.java | 3 +-- .../src/main/java/org/enso/compiler/core/ir/JName.java | 6 +----- .../org/enso/compiler/core/ir/module/scope/JDefinition.java | 4 +--- .../org/enso/compiler/core/ir/module/scope/JExport.java | 2 +- .../org/enso/compiler/core/ir/module/scope/JImport.java | 2 +- 6 files changed, 6 insertions(+), 13 deletions(-) diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JDefinitionArgument.java b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JDefinitionArgument.java index 01fdb876e0d8..464ae9ed278b 100644 --- a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JDefinitionArgument.java +++ b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JDefinitionArgument.java @@ -4,6 +4,7 @@ import org.enso.runtime.parser.dsl.IRChild; import org.enso.runtime.parser.dsl.IRNode; +@IRNode public interface JDefinitionArgument extends IR { @IRChild JName name(); @@ -16,6 +17,5 @@ public interface JDefinitionArgument extends IR { boolean suspended(); - @IRNode interface JSpecified extends JDefinitionArgument {} } diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JExpression.java b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JExpression.java index aa6e4234363e..459e3385f8ac 100644 --- a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JExpression.java +++ b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JExpression.java @@ -5,8 +5,8 @@ import org.enso.runtime.parser.dsl.IRChild; import org.enso.runtime.parser.dsl.IRNode; +@IRNode public interface JExpression extends IR { - @IRNode interface JBlock extends JExpression { @IRChild List expressions(); @@ -23,7 +23,6 @@ interface JBlock extends JExpression { *

To create a binding that binds no available name, set the name of the binding to an * [[Name.Blank]] (e.g. _ = foo a b). */ - @IRNode interface JBinding extends JExpression { @IRChild JName name(); diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JName.java b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JName.java index 7fc979e7f4e8..c709c99becc5 100644 --- a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JName.java +++ b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JName.java @@ -6,21 +6,19 @@ import org.enso.runtime.parser.dsl.IRChild; import org.enso.runtime.parser.dsl.IRNode; +@IRNode public interface JName extends JExpression { String name(); boolean isMethod(); - @IRNode interface JBlank extends JName {} - @IRNode interface JLiteral extends JName { @IRChild(required = false) JName originalName(); } - @IRNode interface JQualified extends JName { @IRChild List parts(); @@ -31,14 +29,12 @@ default String name() { } } - @IRNode interface JSelf extends JName { boolean synthetic(); } interface JAnnotation extends JName, JDefinition {} - @IRNode interface JGenericAnnotation extends JAnnotation { @IRChild JExpression expression(); diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/scope/JDefinition.java b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/scope/JDefinition.java index 47eefdf9147b..400f28783c5a 100644 --- a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/scope/JDefinition.java +++ b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/scope/JDefinition.java @@ -8,8 +8,8 @@ import org.enso.runtime.parser.dsl.IRChild; import org.enso.runtime.parser.dsl.IRNode; +@IRNode public interface JDefinition extends JScope { - @IRNode interface JType extends JDefinition { @IRChild JName name(); @@ -22,7 +22,6 @@ interface JType extends JDefinition { } /** The definition of an atom constructor and its associated arguments. */ - @IRNode interface JData extends JDefinition { /** The name of the atom */ @IRChild @@ -43,7 +42,6 @@ interface JData extends JDefinition { * The definition of a complex type definition that may contain multiple atom and method * definitions. */ - @IRNode interface JSugaredType extends JDefinition { /** The name of the complex type. */ diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/scope/JExport.java b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/scope/JExport.java index 3e165ecfd263..abbe2b981d7e 100644 --- a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/scope/JExport.java +++ b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/scope/JExport.java @@ -6,8 +6,8 @@ import org.enso.runtime.parser.dsl.IRChild; import org.enso.runtime.parser.dsl.IRNode; +@IRNode public interface JExport extends JScope { - @IRNode interface JModule extends JExport { @IRChild JName.JQualified name(); diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/scope/JImport.java b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/scope/JImport.java index e356ce84a3a9..faef5dfac042 100644 --- a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/scope/JImport.java +++ b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/scope/JImport.java @@ -7,8 +7,8 @@ import org.enso.runtime.parser.dsl.IRNode; /** Module-level import statements. */ +@IRNode public interface JImport extends JScope { - @IRNode interface JModule extends JImport { @IRChild JName.JQualified name(); From df95a4e4cfa6bcd51156d4306305177273fd5e31 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 25 Oct 2024 17:24:28 +0200 Subject: [PATCH 34/93] JModule extends IR --- .../src/main/java/org/enso/compiler/core/ir/JModule.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JModule.java b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JModule.java index a3bac2df62e5..21bd50278b08 100644 --- a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JModule.java +++ b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JModule.java @@ -1,13 +1,14 @@ package org.enso.compiler.core.ir; import java.util.List; +import org.enso.compiler.core.IR; import org.enso.compiler.core.ir.module.scope.JExport; import org.enso.compiler.core.ir.module.scope.JImport; import org.enso.runtime.parser.dsl.IRChild; import org.enso.runtime.parser.dsl.IRNode; @IRNode -public interface JModule { +public interface JModule extends IR { @IRChild List imports(); From 7918fd2510954d9bc0e93b327c7802ba27d1813c Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 25 Oct 2024 18:51:40 +0200 Subject: [PATCH 35/93] IRNodeProcessor handles parametrized List child --- .../enso/runtime/parser/processor/Field.java | 20 +++- .../processor/IRNodeClassGenerator.java | 97 ++++++++++++------- .../runtime/parser/processor/ListField.java | 61 ++++++++++++ .../parser/processor/PrimitiveField.java | 5 - .../parser/processor/ReferenceField.java | 5 +- .../enso/runtime/parser/processor/Utils.java | 5 + 6 files changed, 146 insertions(+), 47 deletions(-) create mode 100644 engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ListField.java diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Field.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Field.java index 35cbb8992d82..d3a1a00d426e 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Field.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Field.java @@ -1,5 +1,6 @@ package org.enso.runtime.parser.processor; +import java.util.List; import java.util.function.Function; import org.enso.runtime.parser.dsl.IRChild; @@ -12,11 +13,24 @@ interface Field { /** Name (identifier) of the field. */ String getName(); - /** Does not return null. */ + /** + * Does not return null. If the type is generic, the type parameter is included in the name. + * Returns non-qualified name. + */ String getSimpleTypeName(); - /** May return null if the type is primitive. */ - String getQualifiedTypeName(); + /** + * Returns list of (fully-qualified) types that are necessary to import in order to use simple + * type names. + */ + default List getImportedTypes() { + return List.of(); + } + + /** Returns true if this field is a scala immutable list. */ + default boolean isList() { + return false; + } /** * Returns true if this field is annotated with {@link org.enso.runtime.parser.dsl.IRChild}. diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java index f7447d5bd549..6b08f7d845c9 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java @@ -12,6 +12,7 @@ import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.SimpleElementVisitor14; import org.enso.runtime.parser.dsl.IRChild; @@ -42,19 +43,18 @@ final class IRNodeClassGenerator { /** User defined fields - all the abstract parameterless methods, including the inherited ones. */ private final List fields; - private static final Set defaultImports = + private static final Set defaultImportedTypes = Set.of( - "import java.util.UUID;", - "import java.util.ArrayList;", - "import java.util.function.Function;", - "import org.enso.compiler.core.Identifier;", - "import org.enso.compiler.core.IR;", - "import org.enso.compiler.core.ir.DiagnosticStorage;", - "import org.enso.compiler.core.ir.Expression;", - "import org.enso.compiler.core.ir.IdentifiedLocation;", - "import org.enso.compiler.core.ir.MetadataStorage;", - "import scala.Option;", - "import scala.collection.immutable.List;"); + "java.util.UUID", + "java.util.ArrayList", + "java.util.function.Function", + "org.enso.compiler.core.Identifier", + "org.enso.compiler.core.IR", + "org.enso.compiler.core.ir.DiagnosticStorage", + "org.enso.compiler.core.ir.Expression", + "org.enso.compiler.core.ir.IdentifiedLocation", + "org.enso.compiler.core.ir.MetadataStorage", + "scala.Option"); /** * @param interfaceType Type of the interface for which we are generating code. It is expected @@ -95,13 +95,14 @@ String getInterfaceName() { Set imports() { var importsForFields = fields.stream() - .filter(field -> !field.isPrimitive()) - .map(field -> "import " + field.getQualifiedTypeName() + ";") + .flatMap(field -> field.getImportedTypes().stream()) .collect(Collectors.toUnmodifiableSet()); var allImports = new HashSet(); - allImports.addAll(defaultImports); + allImports.addAll(defaultImportedTypes); allImports.addAll(importsForFields); - return allImports; + return allImports.stream() + .map(importedType -> "import " + importedType + ";") + .collect(Collectors.toUnmodifiableSet()); } /** Generates the body of the class - fields, field setters, method overrides, builder, etc. */ @@ -156,24 +157,39 @@ public Void visitExecutable(ExecutableElement e, Void unused) { if (e.getParameters().isEmpty()) { var retType = e.getReturnType(); var name = e.getSimpleName().toString(); + if (retType.getKind().isPrimitive()) { var primField = new PrimitiveField(retType, name); fields.put(name, primField); - } else { - var retTypeElem = (TypeElement) processingEnv.getTypeUtils().asElement(retType); - assert retTypeElem != null; - var childAnnot = e.getAnnotation(IRChild.class); - boolean isChild = false; - boolean isNullable = false; - if (childAnnot != null) { - ensureIsSubtypeOfIR(retTypeElem); - isChild = true; - isNullable = !childAnnot.required(); - } - var refField = - new ReferenceField(processingEnv, retTypeElem, name, isNullable, isChild); + return super.visitExecutable(e, unused); + } + + var retTypeElem = (TypeElement) processingEnv.getTypeUtils().asElement(retType); + assert retTypeElem != null; + var childAnnot = e.getAnnotation(IRChild.class); + if (childAnnot == null) { + var refField = new ReferenceField(processingEnv, retTypeElem, name, false, false); fields.put(name, refField); + return super.visitExecutable(e, unused); + } + + assert childAnnot != null; + if (Utils.isScalaList(retTypeElem, processingEnv)) { + assert retType instanceof DeclaredType; + var declaredRetType = (DeclaredType) retType; + assert declaredRetType.getTypeArguments().size() == 1; + var typeArg = declaredRetType.getTypeArguments().get(0); + var typeArgElem = (TypeElement) processingEnv.getTypeUtils().asElement(typeArg); + ensureIsSubtypeOfIR(typeArgElem); + var listField = new ListField(name, typeArgElem); + fields.put(name, listField); + return super.visitExecutable(e, unused); } + + boolean isNullable = !childAnnot.required(); + ensureIsSubtypeOfIR(retTypeElem); + var field = new ReferenceField(processingEnv, retTypeElem, name, isNullable, true); + fields.put(name, field); } return super.visitExecutable(e, unused); } @@ -259,21 +275,28 @@ private String childrenMethodBody() { .filter(Field::isChild) .forEach( childField -> { + String addToListCode; + if (!childField.isList()) { + addToListCode = "list.add(" + childField.getName() + ");"; + } else { + addToListCode = + """ + $childName.foreach(list::add); + """ + .replace("$childName", childField.getName()); + } var childName = childField.getName(); if (childField.isNullable()) { sb.append( """ if ($childName != null) { - list.add($childName); + $addToListCode } """ - .replace("$childName", childName)); + .replace("$childName", childName) + .replace("$addToListCode", addToListCode)); } else { - sb.append( - """ - list.add($childName); - """ - .replace("$childName", childName)); + sb.append(addToListCode); } }); sb.append("return scala.jdk.javaapi.CollectionConverters.asScala(list).toList();").append(nl); @@ -309,7 +332,7 @@ public IR mapExpressions(Function fn) { } @Override - public List children() { + public scala.collection.immutable.List children() { $childrenMethodBody } diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ListField.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ListField.java new file mode 100644 index 000000000000..c964057f8bcd --- /dev/null +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ListField.java @@ -0,0 +1,61 @@ +package org.enso.runtime.parser.processor; + +import java.util.List; +import javax.lang.model.element.TypeElement; + +/** Represents a {@code scala.collection.immutable.List} field in the IR node. */ +final class ListField implements Field { + private final String name; + private final TypeElement typeArgElement; + + /** + * @param name Name of the field + * @param typeArgElement TypeElement of the type argument. Must be subtype of IR. + */ + ListField(String name, TypeElement typeArgElement) { + this.name = name; + this.typeArgElement = typeArgElement; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getSimpleTypeName() { + var typeArg = typeArgElement.getSimpleName().toString(); + return "List<" + typeArg + ">"; + } + + @Override + public List getImportedTypes() { + var typePar = typeArgElement.getQualifiedName().toString(); + return List.of("scala.collection.immutable.List", typePar); + } + + @Override + public boolean isList() { + return true; + } + + @Override + public boolean isChild() { + return true; + } + + @Override + public boolean isNullable() { + return false; + } + + @Override + public boolean isPrimitive() { + return false; + } + + @Override + public boolean isExpression() { + return false; + } +} diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/PrimitiveField.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/PrimitiveField.java index 2841e7b302db..75acf0bf9ef7 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/PrimitiveField.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/PrimitiveField.java @@ -22,11 +22,6 @@ public String getSimpleTypeName() { return type.toString(); } - @Override - public String getQualifiedTypeName() { - return null; - } - @Override public boolean isChild() { return false; diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ReferenceField.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ReferenceField.java index 32ee5bd77b35..7a8af4d4bd65 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ReferenceField.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ReferenceField.java @@ -1,5 +1,6 @@ package org.enso.runtime.parser.processor; +import java.util.List; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.TypeElement; @@ -34,8 +35,8 @@ public String getSimpleTypeName() { } @Override - public String getQualifiedTypeName() { - return type.getQualifiedName().toString(); + public List getImportedTypes() { + return List.of(type.getQualifiedName().toString()); } @Override diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java index 6c861115ec29..3c086e88cb45 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java @@ -61,4 +61,9 @@ static String indent(String code, int indentation) { .map(line -> " ".repeat(indentation) + line) .collect(Collectors.joining(System.lineSeparator())); } + + static boolean isScalaList(TypeElement type, ProcessingEnvironment procEnv) { + var scalaListType = procEnv.getElementUtils().getTypeElement("scala.collection.immutable.List"); + return procEnv.getTypeUtils().isAssignable(type.asType(), scalaListType.asType()); + } } From 9ae8e3f550f4f9250814d50a605279055c1fbfe2 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 25 Oct 2024 18:51:58 +0200 Subject: [PATCH 36/93] runtime-parser-processor-tests runs with enabled assertions --- build.sbt | 1 + 1 file changed, 1 insertion(+) diff --git a/build.sbt b/build.sbt index 7910dbf722d4..d7e1a9e77008 100644 --- a/build.sbt +++ b/build.sbt @@ -3134,6 +3134,7 @@ lazy val `runtime-parser-dsl` = lazy val `runtime-parser-processor-tests` = (project in file("engine/runtime-parser-processor-tests")) .settings( + inConfig(Compile)(truffleRunOptionsSettings), frgaalJavaCompilerSetting, commands += WithDebugCommand.withDebug, Test / fork := true, From cf264cf99114dc8d4ef2ffdffe210af70918a888 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 25 Oct 2024 18:54:18 +0200 Subject: [PATCH 37/93] Test IRNode with List of children --- .../processor/test/TestIRProcessor.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java b/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java index da4983e2142a..b2a5229daa02 100644 --- a/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java +++ b/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java @@ -260,4 +260,33 @@ interface JBlank extends JName {} srcSubject.contains("public final class JNameGen"); srcSubject.contains("public static final class JBlankGen implements JName.JBlank"); } + + @Test + public void returnValueCanBeScalaList() { + var src = + JavaFileObjects.forSourceString( + "JName", + """ + import org.enso.runtime.parser.dsl.IRNode; + import org.enso.runtime.parser.dsl.IRChild; + import org.enso.compiler.core.IR; + import scala.collection.immutable.List; + + @IRNode + public interface JName extends IR { + @IRChild + List expressions(); + } + """); + var compiler = Compiler.javac().withProcessors(new IRProcessor()); + var compilation = compiler.compile(src); + CompilationSubject.assertThat(compilation).succeeded(); + assertThat("Generated just one source", compilation.generatedSourceFiles().size(), is(1)); + var srcSubject = + CompilationSubject.assertThat(compilation) + .generatedSourceFile("JNameGen") + .contentsAsUtf8String(); + srcSubject.contains("public final class JNameGen"); + srcSubject.contains("List expressions"); + } } From 0f21885b75cfdad029b5c658d35196d166017047 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 25 Oct 2024 19:04:52 +0200 Subject: [PATCH 38/93] All IR interfaces use scala list, not java.util.List --- .../main/java/org/enso/compiler/core/ir/JExpression.java | 2 +- .../src/main/java/org/enso/compiler/core/ir/JModule.java | 2 +- .../src/main/java/org/enso/compiler/core/ir/JName.java | 5 ++--- .../org/enso/compiler/core/ir/module/scope/JDefinition.java | 2 +- .../org/enso/compiler/core/ir/module/scope/JExport.java | 6 +++--- .../org/enso/compiler/core/ir/module/scope/JImport.java | 6 +++--- 6 files changed, 11 insertions(+), 12 deletions(-) diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JExpression.java b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JExpression.java index 459e3385f8ac..a222db52afbb 100644 --- a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JExpression.java +++ b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JExpression.java @@ -1,9 +1,9 @@ package org.enso.compiler.core.ir; -import java.util.List; import org.enso.compiler.core.IR; import org.enso.runtime.parser.dsl.IRChild; import org.enso.runtime.parser.dsl.IRNode; +import scala.collection.immutable.List; @IRNode public interface JExpression extends IR { diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JModule.java b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JModule.java index 21bd50278b08..cf518a3b24c0 100644 --- a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JModule.java +++ b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JModule.java @@ -1,11 +1,11 @@ package org.enso.compiler.core.ir; -import java.util.List; import org.enso.compiler.core.IR; import org.enso.compiler.core.ir.module.scope.JExport; import org.enso.compiler.core.ir.module.scope.JImport; import org.enso.runtime.parser.dsl.IRChild; import org.enso.runtime.parser.dsl.IRNode; +import scala.collection.immutable.List; @IRNode public interface JModule extends IR { diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JName.java b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JName.java index c709c99becc5..53fdacdb9c11 100644 --- a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JName.java +++ b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JName.java @@ -1,10 +1,9 @@ package org.enso.compiler.core.ir; -import java.util.List; -import java.util.stream.Collectors; import org.enso.compiler.core.ir.module.scope.JDefinition; import org.enso.runtime.parser.dsl.IRChild; import org.enso.runtime.parser.dsl.IRNode; +import scala.collection.immutable.List; @IRNode public interface JName extends JExpression { @@ -25,7 +24,7 @@ interface JQualified extends JName { @Override default String name() { - return parts().stream().map(JName::name).collect(Collectors.joining(".")); + return parts().map(JName::name).mkString("."); } } diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/scope/JDefinition.java b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/scope/JDefinition.java index 400f28783c5a..ef6be4ca3b9d 100644 --- a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/scope/JDefinition.java +++ b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/scope/JDefinition.java @@ -1,12 +1,12 @@ package org.enso.compiler.core.ir.module.scope; -import java.util.List; import org.enso.compiler.core.IR; import org.enso.compiler.core.ir.JDefinitionArgument; import org.enso.compiler.core.ir.JName; import org.enso.compiler.core.ir.module.JScope; import org.enso.runtime.parser.dsl.IRChild; import org.enso.runtime.parser.dsl.IRNode; +import scala.collection.immutable.List; @IRNode public interface JDefinition extends JScope { diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/scope/JExport.java b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/scope/JExport.java index abbe2b981d7e..a0fd51ca907b 100644 --- a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/scope/JExport.java +++ b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/scope/JExport.java @@ -1,10 +1,10 @@ package org.enso.compiler.core.ir.module.scope; -import java.util.List; import org.enso.compiler.core.ir.JName; import org.enso.compiler.core.ir.module.JScope; import org.enso.runtime.parser.dsl.IRChild; import org.enso.runtime.parser.dsl.IRNode; +import scala.collection.immutable.List; @IRNode public interface JExport extends JScope { @@ -30,7 +30,7 @@ default JName getSimpleName() { if (rename() != null) { return rename(); } else { - return name().parts().get(name().parts().size() - 1); + return name().parts().apply(name().parts().size() - 1); } } @@ -45,7 +45,7 @@ default JName getSimpleName() { */ default boolean allowsAccess(String name) { if (onlyNames() != null) { - return onlyNames().stream().anyMatch(n -> n.name().equalsIgnoreCase(name)); + return onlyNames().exists(n -> n.name().equalsIgnoreCase(name)); } return true; } diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/scope/JImport.java b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/scope/JImport.java index faef5dfac042..9928aa87a05d 100644 --- a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/scope/JImport.java +++ b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/scope/JImport.java @@ -1,10 +1,10 @@ package org.enso.compiler.core.ir.module.scope; -import java.util.List; import org.enso.compiler.core.ir.JName; import org.enso.compiler.core.ir.module.JScope; import org.enso.runtime.parser.dsl.IRChild; import org.enso.runtime.parser.dsl.IRNode; +import scala.collection.immutable.List; /** Module-level import statements. */ @IRNode @@ -40,10 +40,10 @@ default boolean allowsAccess(String name) { return false; } if (onlyNames() != null) { - return onlyNames().stream().anyMatch(n -> n.name().equals(name)); + return onlyNames().exists(n -> n.name().equals(name)); } if (hiddenNames() != null) { - return hiddenNames().stream().noneMatch(n -> n.name().equals(name)); + return hiddenNames().forall(n -> !n.name().equals(name)); } return true; } From 18b2d1032b6b52e43d18e06c1eb5b235aeedceb7 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 25 Oct 2024 19:20:21 +0200 Subject: [PATCH 39/93] Do not process already overriden methods --- .../processor/test/TestIRProcessor.java | 33 ++++++++ .../processor/IRNodeClassGenerator.java | 3 +- .../enso/runtime/parser/processor/Utils.java | 78 ++++++++++++++----- 3 files changed, 94 insertions(+), 20 deletions(-) diff --git a/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java b/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java index b2a5229daa02..337221c5f611 100644 --- a/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java +++ b/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java @@ -289,4 +289,37 @@ public interface JName extends IR { srcSubject.contains("public final class JNameGen"); srcSubject.contains("List expressions"); } + + @Test + public void processorDoesNotGenerateOverridenMethods() { + var src = + JavaFileObjects.forSourceString( + "JName", + """ + import org.enso.runtime.parser.dsl.IRNode; + import org.enso.compiler.core.IR; + + @IRNode + public interface JName extends IR { + String name(); + + interface JQualified extends JName { + @Override + default String name() { + return null; + } + } + } + """); + var compiler = Compiler.javac().withProcessors(new IRProcessor()); + var compilation = compiler.compile(src); + CompilationSubject.assertThat(compilation).succeeded(); + assertThat("Generated just one source", compilation.generatedSourceFiles().size(), is(1)); + var srcSubject = + CompilationSubject.assertThat(compilation) + .generatedSourceFile("JNameGen") + .contentsAsUtf8String(); + srcSubject.contains("public final class JNameGen"); + srcSubject.doesNotContain("String name()"); + } } diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java index 6b08f7d845c9..3bb72c0e8cf1 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java @@ -154,7 +154,8 @@ protected Void defaultAction(Element e, Void unused) { @Override public Void visitExecutable(ExecutableElement e, Void unused) { - if (e.getParameters().isEmpty()) { + if (e.getParameters().isEmpty() + && !Utils.hasDefaultImplementation(e, irNodeInterface, processingEnv)) { var retType = e.getReturnType(); var name = e.getSimpleName().toString(); diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java index 3c086e88cb45..12983eb9858c 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java @@ -1,11 +1,13 @@ package org.enso.runtime.parser.processor; import java.util.ArrayDeque; +import java.util.function.Consumer; import java.util.stream.Collectors; import javax.annotation.processing.Messager; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; import javax.tools.Diagnostic.Kind; @@ -15,25 +17,19 @@ private Utils() {} /** Returns true if the given {@code type} is a subtype of {@code org.enso.compiler.core.IR}. */ static boolean isSubtypeOfIR(TypeElement type, ProcessingEnvironment processingEnv) { - var interfacesToProcess = new ArrayDeque(); - interfacesToProcess.add(type); - while (!interfacesToProcess.isEmpty()) { - var current = interfacesToProcess.pop(); - if (current.getSimpleName().toString().equals("IR")) { - // current.getQualifiedName().toString() returns only "IR" as well, so we can't use it. - // This is because runtime-parser-processor project does not depend on runtime-parser and - // so the org.enso.compiler.core.IR interface is not available in the classpath. - return true; - } - // Add all super interfaces to the queue - for (var superInterface : current.getInterfaces()) { - var superInterfaceElem = processingEnv.getTypeUtils().asElement(superInterface); - if (superInterfaceElem instanceof TypeElement superInterfaceTypeElem) { - interfacesToProcess.add(superInterfaceTypeElem); - } - } - } - return false; + boolean irEncountered[] = {false}; + iterateSuperInterfaces( + type, + processingEnv, + superInterface -> { + // current.getQualifiedName().toString() returns only "IR" as well, so we can't use it. + // This is because runtime-parser-processor project does not depend on runtime-parser and + // so the org.enso.compiler.core.IR interface is not available in the classpath. + if (superInterface.getSimpleName().toString().equals("IR")) { + irEncountered[0] = true; + } + }); + return irEncountered[0]; } /** Returns true if the given {@code type} is an {@code org.enso.compiler.core.IR} interface. */ @@ -66,4 +62,48 @@ static boolean isScalaList(TypeElement type, ProcessingEnvironment procEnv) { var scalaListType = procEnv.getElementUtils().getTypeElement("scala.collection.immutable.List"); return procEnv.getTypeUtils().isAssignable(type.asType(), scalaListType.asType()); } + + /** + * Returns true if the method has a default implementation in some of the super interfaces. + * + * @param method the method to check + * @param interfaceType the interface that declares the method + * @param procEnv + * @return + */ + static boolean hasDefaultImplementation( + ExecutableElement method, TypeElement interfaceType, ProcessingEnvironment procEnv) { + boolean defaultMethodEncountered[] = {false}; + iterateSuperInterfaces( + interfaceType, + procEnv, + superInterface -> { + for (var enclosedElem : superInterface.getEnclosedElements()) { + if (enclosedElem instanceof ExecutableElement executableElem) { + if (executableElem.getSimpleName().equals(method.getSimpleName()) + && executableElem.isDefault()) { + defaultMethodEncountered[0] = true; + } + } + } + }); + return defaultMethodEncountered[0]; + } + + private static void iterateSuperInterfaces( + TypeElement type, ProcessingEnvironment processingEnv, Consumer consumer) { + var interfacesToProcess = new ArrayDeque(); + interfacesToProcess.add(type); + while (!interfacesToProcess.isEmpty()) { + var current = interfacesToProcess.pop(); + consumer.accept(current); + // Add all super interfaces to the queue + for (var superInterface : current.getInterfaces()) { + var superInterfaceElem = processingEnv.getTypeUtils().asElement(superInterface); + if (superInterfaceElem instanceof TypeElement superInterfaceTypeElem) { + interfacesToProcess.add(superInterfaceTypeElem); + } + } + } + } } From 26239a39cdf66fb3edb391745568883b0713f3fe Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Tue, 29 Oct 2024 12:11:15 +0100 Subject: [PATCH 40/93] Enable IRProcessor in runtime-parser --- build.sbt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build.sbt b/build.sbt index d7e1a9e77008..85d7e32d0cf6 100644 --- a/build.sbt +++ b/build.sbt @@ -3111,6 +3111,10 @@ lazy val `runtime-parser` = Compile / moduleDependencies ++= Seq( "org.netbeans.api" % "org-openide-util-lookup" % netbeansApiVersion ), + Compile / javacOptions ++= Seq( + "-processor", + "org.enso.runtime.parser.processor.IRProcessor" + ), Compile / internalModuleDependencies := Seq( (`syntax-rust-definition` / Compile / exportedModule).value, (`persistance` / Compile / exportedModule).value, From 9c3a58000605dbfedc61545dc5dcff607b9b31af Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Tue, 29 Oct 2024 18:51:04 +0100 Subject: [PATCH 41/93] Ensure that only fields from current interface and its super interfaces are collected. No methods from nested interfaces. --- .../processor/test/TestIRProcessor.java | 33 +++++ .../parser/processor/FieldCollector.java | 119 ++++++++++++++++++ .../processor/IRNodeClassGenerator.java | 89 +------------ 3 files changed, 154 insertions(+), 87 deletions(-) create mode 100644 engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/FieldCollector.java diff --git a/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java b/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java index 337221c5f611..942a82dfea42 100644 --- a/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java +++ b/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java @@ -322,4 +322,37 @@ default String name() { srcSubject.contains("public final class JNameGen"); srcSubject.doesNotContain("String name()"); } + + @Test + public void overrideCorrectMethods() { + var src = + JavaFileObjects.forSourceString( + "JExpression", + """ + import org.enso.runtime.parser.dsl.IRNode; + import org.enso.compiler.core.IR; + + @IRNode + public interface JExpression extends IR { + + interface JBlock extends JExpression { + boolean suspended(); + } + + interface JBinding extends JExpression { + String name(); + } + } + """); + var compiler = Compiler.javac().withProcessors(new IRProcessor()); + var compilation = compiler.compile(src); + CompilationSubject.assertThat(compilation).succeeded(); + assertThat("Generated just one source", compilation.generatedSourceFiles().size(), is(1)); + var srcSubject = + CompilationSubject.assertThat(compilation) + .generatedSourceFile("JExpressionGen") + .contentsAsUtf8String(); + srcSubject.contains("class JBlockGen"); + srcSubject.contains("class JBindingGen"); + } } diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/FieldCollector.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/FieldCollector.java new file mode 100644 index 000000000000..3e41bf22a0e2 --- /dev/null +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/FieldCollector.java @@ -0,0 +1,119 @@ +package org.enso.runtime.parser.processor; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeMirror; +import org.enso.runtime.parser.dsl.IRChild; + +/** + * Collects abstract parameterless methods from the given interface and all its superinterfaces - + * these will be represented as fields in the generated classes, hence the name. + */ +final class FieldCollector { + private final ProcessingEnvironment processingEnv; + private final TypeElement irNodeInterface; + // Mapped by field name + private final Map fields = new LinkedHashMap<>(); + + /** + * + * @param irNodeInterface For this interface, fields will be collected. + */ + FieldCollector(ProcessingEnvironment processingEnv, TypeElement irNodeInterface) { + assert irNodeInterface.getKind() == ElementKind.INTERFACE; + this.processingEnv = processingEnv; + this.irNodeInterface = irNodeInterface; + } + + List collectFields() { + var superInterfaces = irNodeInterface.getInterfaces(); + Deque toProcess = new ArrayDeque<>(); + toProcess.add(irNodeInterface.asType()); + toProcess.addAll(superInterfaces); + // Process transitively all the super interface until the parent IR is reached. + while (!toProcess.isEmpty()) { + var current = toProcess.pop(); + // Skip processing of IR root interface. + if (Utils.isIRInterface(current, processingEnv)) { + continue; + } + var currentElem = processingEnv.getTypeUtils().asElement(current); + if (currentElem instanceof TypeElement currentTypeElem) { + collectFromSingleInterface(currentTypeElem); + // Add all super interfaces to the processing queue, if they are not there already. + for (var superInterface : currentTypeElem.getInterfaces()) { + if (!toProcess.contains(superInterface)) { + toProcess.add(superInterface); + } + } + } + } + return fields.values().stream().toList(); + } + + /** + * Collect only parameterless methods without default implementation. + */ + private void collectFromSingleInterface(TypeElement typeElem) { + assert typeElem.getKind() == ElementKind.INTERFACE; + for (var childElem : typeElem.getEnclosedElements()) { + if (childElem instanceof ExecutableElement methodElement) { + if (methodElement.getParameters().isEmpty() + && !Utils.hasDefaultImplementation(methodElement, irNodeInterface, processingEnv)) { + var name = methodElement.getSimpleName().toString(); + if (!fields.containsKey(name)) { + var field = methodToField(methodElement); + fields.put(name, field); + } + } + } + } + } + + private Field methodToField(ExecutableElement methodElement) { + var name = methodElement.getSimpleName().toString(); + var retType = methodElement.getReturnType(); + if (retType.getKind().isPrimitive()) { + return new PrimitiveField(retType, name); + } + + var retTypeElem = (TypeElement) processingEnv.getTypeUtils().asElement(retType); + assert retTypeElem != null; + var childAnnot = methodElement.getAnnotation(IRChild.class); + if (childAnnot == null) { + return new ReferenceField(processingEnv, retTypeElem, name, false, false); + } + + assert childAnnot != null; + if (Utils.isScalaList(retTypeElem, processingEnv)) { + assert retType instanceof DeclaredType; + var declaredRetType = (DeclaredType) retType; + assert declaredRetType.getTypeArguments().size() == 1; + var typeArg = declaredRetType.getTypeArguments().get(0); + var typeArgElem = (TypeElement) processingEnv.getTypeUtils().asElement(typeArg); + ensureIsSubtypeOfIR(typeArgElem); + return new ListField(name, typeArgElem); + } + + boolean isNullable = !childAnnot.required(); + ensureIsSubtypeOfIR(retTypeElem); + return new ReferenceField(processingEnv, retTypeElem, name, isNullable, true); + } + + private void ensureIsSubtypeOfIR(TypeElement typeElem) { + if (!Utils.isSubtypeOfIR(typeElem, processingEnv)) { + Utils.printError( + "Method annotated with @IRChild must return a subtype of IR interface", + typeElem, + processingEnv.getMessager()); + } + } +} diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java index 3bb72c0e8cf1..86e29aa83fa2 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java @@ -1,20 +1,12 @@ package org.enso.runtime.parser.processor; -import java.util.ArrayDeque; -import java.util.Deque; import java.util.HashSet; -import java.util.LinkedHashMap; import java.util.List; import java.util.Set; import java.util.stream.Collectors; import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.SimpleElementVisitor14; import org.enso.runtime.parser.dsl.IRChild; import org.enso.runtime.parser.dsl.IRNode; @@ -139,85 +131,8 @@ public static Builder builder() { * @return List of fields */ private List getAllFields(TypeElement irNodeInterface) { - // Mapped by field name - var fields = new LinkedHashMap(); - - var fieldCollector = - new SimpleElementVisitor14() { - @Override - protected Void defaultAction(Element e, Void unused) { - for (var childElem : e.getEnclosedElements()) { - childElem.accept(this, unused); - } - return null; - } - - @Override - public Void visitExecutable(ExecutableElement e, Void unused) { - if (e.getParameters().isEmpty() - && !Utils.hasDefaultImplementation(e, irNodeInterface, processingEnv)) { - var retType = e.getReturnType(); - var name = e.getSimpleName().toString(); - - if (retType.getKind().isPrimitive()) { - var primField = new PrimitiveField(retType, name); - fields.put(name, primField); - return super.visitExecutable(e, unused); - } - - var retTypeElem = (TypeElement) processingEnv.getTypeUtils().asElement(retType); - assert retTypeElem != null; - var childAnnot = e.getAnnotation(IRChild.class); - if (childAnnot == null) { - var refField = new ReferenceField(processingEnv, retTypeElem, name, false, false); - fields.put(name, refField); - return super.visitExecutable(e, unused); - } - - assert childAnnot != null; - if (Utils.isScalaList(retTypeElem, processingEnv)) { - assert retType instanceof DeclaredType; - var declaredRetType = (DeclaredType) retType; - assert declaredRetType.getTypeArguments().size() == 1; - var typeArg = declaredRetType.getTypeArguments().get(0); - var typeArgElem = (TypeElement) processingEnv.getTypeUtils().asElement(typeArg); - ensureIsSubtypeOfIR(typeArgElem); - var listField = new ListField(name, typeArgElem); - fields.put(name, listField); - return super.visitExecutable(e, unused); - } - - boolean isNullable = !childAnnot.required(); - ensureIsSubtypeOfIR(retTypeElem); - var field = new ReferenceField(processingEnv, retTypeElem, name, isNullable, true); - fields.put(name, field); - } - return super.visitExecutable(e, unused); - } - }; - var superInterfaces = irNodeInterface.getInterfaces(); - Deque toProcess = new ArrayDeque<>(); - toProcess.add(irNodeInterface.asType()); - toProcess.addAll(superInterfaces); - // Process transitively all the super interface until the parent IR is reached. - while (!toProcess.isEmpty()) { - var current = toProcess.pop(); - // Skip processing of IR root interface. - if (Utils.isIRInterface(current, processingEnv)) { - continue; - } - var currentElem = processingEnv.getTypeUtils().asElement(current); - currentElem.accept(fieldCollector, null); - // Add all super interfaces to the processing queue, if they are not there already. - if (currentElem instanceof TypeElement currentTypeElem) { - for (var superInterface : currentTypeElem.getInterfaces()) { - if (!toProcess.contains(superInterface)) { - toProcess.add(superInterface); - } - } - } - } - return fields.values().stream().toList(); + var fieldCollector = new FieldCollector(processingEnv, irNodeInterface); + return fieldCollector.collectFields(); } /** From c6b198764f6fcdbf456509c766d9d09cf20260a4 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Wed, 30 Oct 2024 12:19:19 +0100 Subject: [PATCH 42/93] Implement passData, location and diagnostics methods --- .../processor/IRNodeClassGenerator.java | 19 +++++++++++++++---- .../compiler/core/ir/DiagnosticStorage.scala | 4 ++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java index 86e29aa83fa2..4e022cdb6290 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java @@ -43,6 +43,7 @@ final class IRNodeClassGenerator { "org.enso.compiler.core.Identifier", "org.enso.compiler.core.IR", "org.enso.compiler.core.ir.DiagnosticStorage", + "org.enso.compiler.core.ir.DiagnosticStorage$", "org.enso.compiler.core.ir.Expression", "org.enso.compiler.core.ir.IdentifiedLocation", "org.enso.compiler.core.ir.MetadataStorage", @@ -229,12 +230,19 @@ private String overrideIRMethods() { @Override public MetadataStorage passData() { - throw new UnsupportedOperationException("unimplemented"); + if (passData == null) { + passData = new MetadataStorage(); + } + return passData; } @Override public Option location() { - throw new UnsupportedOperationException("unimplemented"); + if (location == null) { + return scala.Option.empty(); + } else { + return scala.Option.apply(location); + } } @Override @@ -262,12 +270,15 @@ public scala.collection.immutable.List children() { @Override public DiagnosticStorage diagnostics() { - throw new UnsupportedOperationException("unimplemented"); + return diagnostics; } @Override public DiagnosticStorage getDiagnostics() { - throw new UnsupportedOperationException("unimplemented"); + if (diagnostics == null) { + diagnostics = DiagnosticStorage$.MODULE$.empty(); + } + return diagnostics; } @Override diff --git a/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/DiagnosticStorage.scala b/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/DiagnosticStorage.scala index f2d87d63c996..091de215a964 100644 --- a/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/DiagnosticStorage.scala +++ b/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/DiagnosticStorage.scala @@ -61,3 +61,7 @@ final class DiagnosticStorage(initDiagnostics: Seq[Diagnostic] = Seq()) new DiagnosticStorage(this.diagnostics) } } + +object DiagnosticStorage { + def empty(): DiagnosticStorage = new DiagnosticStorage() +} From 399ca43155970fc94c1e19f3ee671cb8c6e5421c Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Wed, 30 Oct 2024 12:22:43 +0100 Subject: [PATCH 43/93] Override duplicate method --- .../processor/test/TestIRProcessor.java | 28 ++++++++ .../processor/IRNodeClassGenerator.java | 72 ++++++++++++++++++- .../enso/runtime/parser/processor/Utils.java | 35 +++++++++ .../java/org/enso/compiler/core/ir/JName.java | 4 ++ 4 files changed, 136 insertions(+), 3 deletions(-) diff --git a/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java b/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java index 942a82dfea42..e462cbc95e92 100644 --- a/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java +++ b/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java @@ -355,4 +355,32 @@ interface JBinding extends JExpression { srcSubject.contains("class JBlockGen"); srcSubject.contains("class JBindingGen"); } + + @Test + public void canOverrideMethodsFromIR() { + var src = + JavaFileObjects.forSourceString( + "JName", + """ + import org.enso.runtime.parser.dsl.IRNode; + import org.enso.compiler.core.IR; + + @IRNode + public interface JName extends IR { + @Override + JName duplicate(boolean keepLocations, boolean keepMetadata, boolean keepDiagnostics, boolean keepIdentifiers); + + interface JSelf extends JName {} + } + """); + var compiler = Compiler.javac().withProcessors(new IRProcessor()); + var compilation = compiler.compile(src); + CompilationSubject.assertThat(compilation).succeeded(); + assertThat("Generated just one source", compilation.generatedSourceFiles().size(), is(1)); + var srcSubject = + CompilationSubject.assertThat(compilation) + .generatedSourceFile("JNameGen") + .contentsAsUtf8String(); + srcSubject.contains("JName duplicate"); + } } diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java index 4e022cdb6290..b15c9521ed01 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java @@ -6,6 +6,7 @@ import java.util.stream.Collectors; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import org.enso.runtime.parser.dsl.IRChild; import org.enso.runtime.parser.dsl.IRNode; @@ -35,6 +36,14 @@ final class IRNodeClassGenerator { /** User defined fields - all the abstract parameterless methods, including the inherited ones. */ private final List fields; + /** + * {@link org.enso.compiler.core.IR#duplicate(boolean, boolean, boolean, boolean) duplicate} method element. + * We need to know if there is any override with a different return type in the + * interface hierarchy. + * If not, this is just a reference to the method from IR. + */ + private final ExecutableElement duplicateMethod; + private static final Set defaultImportedTypes = Set.of( "java.util.UUID", @@ -61,6 +70,7 @@ final class IRNodeClassGenerator { this.interfaceType = interfaceType; this.className = className; this.fields = getAllFields(interfaceType); + this.duplicateMethod = Utils.findDuplicateMethod(interfaceType, processingEnv); var nestedTypes = interfaceType.getEnclosedElements().stream() .filter( @@ -220,11 +230,65 @@ private String childrenMethodBody() { return indent(sb.toString(), 2); } + private String duplicateMethodBody() { + var nl = System.lineSeparator(); + var nullableChildrenCode = fields.stream() + .filter(field -> field.isChild() && field.isNullable()) + .map(field -> """ + IR $childNameDup = null; + if ($childName != null) { + $childNameDup = $childName.duplicate(keepLocations, keepMetadata, keepDiagnostics, keepIdentifiers); + if (!($childNameDup instanceof $childType)) { + throw new IllegalStateException("Duplicated child is not of the expected type: " + $childNameDup); + } + } + """ + .replace("$childType", field.getSimpleTypeName()) + .replace("$childName", field.getName()) + .replace("$childNameDup", field.getName() + "Duplicated")) + .collect(Collectors.joining(nl)); + + var notNullableChildrenCode = fields.stream() + .filter(field -> field.isChild() && !field.isNullable() && !field.isList()) + .map(field -> """ + IR $childNameDup = + $childName.duplicate(keepLocations, keepMetadata, keepDiagnostics, keepIdentifiers); + if (!($childNameDup instanceof $childType)) { + throw new IllegalStateException("Duplicated child is not of the expected type: " + $childNameDup); + } + """ + .replace("$childType", field.getSimpleTypeName()) + .replace("$childName", field.getName()) + .replace("$childNameDup", field.getName() + "Duplicated")) + .collect(Collectors.joining(nl)); + + var listChildrenCode = fields.stream() + .filter(field -> field.isChild() && field.isList()) + .map(field -> """ + $childType $childNameDup = + $childName.map(child -> { + return child.duplicate(keepLocations, keepMetadata, keepDiagnostics, keepIdentifiers); + }); + """ + .replace("$childType", field.getSimpleTypeName()) + .replace("$childName", field.getName()) + .replace("$childNameDup", field.getName() + "Duplicated")) + .collect(Collectors.joining(nl)); + + var code = nullableChildrenCode + + nl + + notNullableChildrenCode + + nl + + listChildrenCode; + return indent(code, 2); + } + /** * Returns a String representing all the overriden methods from {@link org.enso.compiler.core.IR}. * Meant to be inside the generated record definition. */ private String overrideIRMethods() { + var duplicateMethodRetType = duplicateMethod.getReturnType().toString(); var code = """ @@ -282,13 +346,13 @@ public DiagnosticStorage getDiagnostics() { } @Override - public IR duplicate( + public $duplicateMethodRetType duplicate( boolean keepLocations, boolean keepMetadata, boolean keepDiagnostics, boolean keepIdentifiers ) { - throw new UnsupportedOperationException("unimplemented"); + $duplicateMethodBody } @Override @@ -296,7 +360,9 @@ public String showCode(int indent) { throw new UnsupportedOperationException("unimplemented"); } """ - .replace("$childrenMethodBody", childrenMethodBody()); + .replace("$childrenMethodBody", childrenMethodBody()) + .replace("$duplicateMethodRetType", duplicateMethodRetType) + .replace("$duplicateMethodBody", duplicateMethodBody()); return indent(code, 2); } diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java index 12983eb9858c..dfc1bb8dbd3f 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java @@ -90,6 +90,41 @@ static boolean hasDefaultImplementation( return defaultMethodEncountered[0]; } + /** + * Find any override of {@link org.enso.compiler.core.IR#duplicate(boolean, boolean, boolean, boolean) duplicate method}. + * Or the duplicate method on the interface itself. + * Note that there can be an override with a different return type in a sub interface. + * @param interfaceType Interface from where the search is started. + * All super interfaces are searched transitively. + * @return not null. + */ + static ExecutableElement findDuplicateMethod( + TypeElement interfaceType, ProcessingEnvironment procEnv) { + ExecutableElement[] duplicateMethod = {null}; + iterateSuperInterfaces( + interfaceType, + procEnv, + superInterface -> { + for (var enclosedElem : superInterface.getEnclosedElements()) { + if (enclosedElem instanceof ExecutableElement execElem) { + if (isDuplicateMethod(execElem)) { + if (duplicateMethod[0] == null) { + duplicateMethod[0] = execElem; + } + } + } + } + } + ); + assert duplicateMethod[0] != null : "Interface " + interfaceType.getQualifiedName() + " must implement IR, so it must declare duplicate method"; + return duplicateMethod[0]; + } + + private static boolean isDuplicateMethod(ExecutableElement executableElement) { + return executableElement.getSimpleName().toString().equals("duplicate") && + executableElement.getParameters().size() == 4; + } + private static void iterateSuperInterfaces( TypeElement type, ProcessingEnvironment processingEnv, Consumer consumer) { var interfacesToProcess = new ArrayDeque(); diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JName.java b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JName.java index 53fdacdb9c11..a6f4c594a0c5 100644 --- a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JName.java +++ b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JName.java @@ -11,6 +11,10 @@ public interface JName extends JExpression { boolean isMethod(); + @Override + JName duplicate(boolean keepLocations, boolean keepMetadata, boolean keepDiagnostics, + boolean keepIdentifiers); + interface JBlank extends JName {} interface JLiteral extends JName { From 7a97c61c8bd4276d801f74d80a881595590e98a5 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Wed, 30 Oct 2024 18:42:31 +0100 Subject: [PATCH 44/93] Fix duplicate method generation --- .../enso/runtime/parser/processor/Field.java | 5 ++ .../parser/processor/FieldCollector.java | 5 +- .../processor/IRNodeClassGenerator.java | 86 +++++++++++-------- .../runtime/parser/processor/ListField.java | 5 ++ .../enso/runtime/parser/processor/Utils.java | 23 ++--- .../java/org/enso/compiler/core/ir/JName.java | 5 +- 6 files changed, 79 insertions(+), 50 deletions(-) diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Field.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Field.java index d3a1a00d426e..11c4d7a0b19b 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Field.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Field.java @@ -59,4 +59,9 @@ default boolean isList() { * @return true if this field extends {@link org.enso.compiler.core.ir.Expression} */ boolean isExpression(); + + /** Returns the type parameter, if this field is a generic type. Otherwise null. */ + default String getTypeParameter() { + return null; + } } diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/FieldCollector.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/FieldCollector.java index 3e41bf22a0e2..2615b00114f6 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/FieldCollector.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/FieldCollector.java @@ -24,7 +24,6 @@ final class FieldCollector { private final Map fields = new LinkedHashMap<>(); /** - * * @param irNodeInterface For this interface, fields will be collected. */ FieldCollector(ProcessingEnvironment processingEnv, TypeElement irNodeInterface) { @@ -59,9 +58,7 @@ List collectFields() { return fields.values().stream().toList(); } - /** - * Collect only parameterless methods without default implementation. - */ + /** Collect only parameterless methods without default implementation. */ private void collectFromSingleInterface(TypeElement typeElem) { assert typeElem.getKind() == ElementKind.INTERFACE; for (var childElem : typeElem.getEnclosedElements()) { diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java index b15c9521ed01..3c977d260dbf 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java @@ -37,10 +37,9 @@ final class IRNodeClassGenerator { private final List fields; /** - * {@link org.enso.compiler.core.IR#duplicate(boolean, boolean, boolean, boolean) duplicate} method element. - * We need to know if there is any override with a different return type in the - * interface hierarchy. - * If not, this is just a reference to the method from IR. + * {@link org.enso.compiler.core.IR#duplicate(boolean, boolean, boolean, boolean) duplicate} + * method element. We need to know if there is any override with a different return type in the + * interface hierarchy. If not, this is just a reference to the method from IR. */ private final ExecutableElement duplicateMethod; @@ -232,9 +231,12 @@ private String childrenMethodBody() { private String duplicateMethodBody() { var nl = System.lineSeparator(); - var nullableChildrenCode = fields.stream() - .filter(field -> field.isChild() && field.isNullable()) - .map(field -> """ + var nullableChildrenCode = + fields.stream() + .filter(field -> field.isChild() && field.isNullable()) + .map( + field -> + """ IR $childNameDup = null; if ($childName != null) { $childNameDup = $childName.duplicate(keepLocations, keepMetadata, keepDiagnostics, keepIdentifiers); @@ -243,46 +245,60 @@ private String duplicateMethodBody() { } } """ - .replace("$childType", field.getSimpleTypeName()) - .replace("$childName", field.getName()) - .replace("$childNameDup", field.getName() + "Duplicated")) - .collect(Collectors.joining(nl)); - - var notNullableChildrenCode = fields.stream() - .filter(field -> field.isChild() && !field.isNullable() && !field.isList()) - .map(field -> """ + .replace("$childType", field.getSimpleTypeName()) + .replace("$childName", field.getName()) + .replace("$childNameDup", field.getName() + "Duplicated")) + .collect(Collectors.joining(nl)); + + var notNullableChildrenCode = + fields.stream() + .filter(field -> field.isChild() && !field.isNullable() && !field.isList()) + .map( + field -> + """ IR $childNameDup = $childName.duplicate(keepLocations, keepMetadata, keepDiagnostics, keepIdentifiers); if (!($childNameDup instanceof $childType)) { throw new IllegalStateException("Duplicated child is not of the expected type: " + $childNameDup); } """ - .replace("$childType", field.getSimpleTypeName()) - .replace("$childName", field.getName()) - .replace("$childNameDup", field.getName() + "Duplicated")) - .collect(Collectors.joining(nl)); - - var listChildrenCode = fields.stream() - .filter(field -> field.isChild() && field.isList()) - .map(field -> """ - $childType $childNameDup = + .replace("$childType", field.getSimpleTypeName()) + .replace("$childName", field.getName()) + .replace("$childNameDup", field.getName() + "Duplicated")) + .collect(Collectors.joining(nl)); + + var listChildrenCode = + fields.stream() + .filter(field -> field.isChild() && field.isList()) + .map( + field -> + """ + $childListType $childNameDup = $childName.map(child -> { - return child.duplicate(keepLocations, keepMetadata, keepDiagnostics, keepIdentifiers); + IR dupChild = child.duplicate(keepLocations, keepMetadata, keepDiagnostics, keepIdentifiers); + if (!(dupChild instanceof $childType)) { + throw new IllegalStateException("Duplicated child is not of the expected type: " + dupChild); + } + return ($childType) dupChild; }); """ - .replace("$childType", field.getSimpleTypeName()) - .replace("$childName", field.getName()) - .replace("$childNameDup", field.getName() + "Duplicated")) - .collect(Collectors.joining(nl)); - - var code = nullableChildrenCode - + nl - + notNullableChildrenCode - + nl - + listChildrenCode; + .replace("$childListType", field.getSimpleTypeName()) + .replace("$childType", field.getTypeParameter()) + .replace("$childName", field.getName()) + .replace("$childNameDup", field.getName() + "Duplicated")) + .collect(Collectors.joining(nl)); + + var code = nullableChildrenCode + nl + notNullableChildrenCode + nl + listChildrenCode; + if (stripWhitespaces(code).isEmpty()) { + code = "return new " + className + "();"; + } return indent(code, 2); } + private static String stripWhitespaces(String s) { + return s.replaceAll("\\s+", ""); + } + /** * Returns a String representing all the overriden methods from {@link org.enso.compiler.core.IR}. * Meant to be inside the generated record definition. diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ListField.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ListField.java index c964057f8bcd..a3c483441c9d 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ListField.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ListField.java @@ -28,6 +28,11 @@ public String getSimpleTypeName() { return "List<" + typeArg + ">"; } + @Override + public String getTypeParameter() { + return typeArgElement.getSimpleName().toString(); + } + @Override public List getImportedTypes() { var typePar = typeArgElement.getQualifiedName().toString(); diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java index dfc1bb8dbd3f..aa67942491d6 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java @@ -91,11 +91,12 @@ static boolean hasDefaultImplementation( } /** - * Find any override of {@link org.enso.compiler.core.IR#duplicate(boolean, boolean, boolean, boolean) duplicate method}. - * Or the duplicate method on the interface itself. - * Note that there can be an override with a different return type in a sub interface. - * @param interfaceType Interface from where the search is started. - * All super interfaces are searched transitively. + * Find any override of {@link org.enso.compiler.core.IR#duplicate(boolean, boolean, boolean, + * boolean) duplicate method}. Or the duplicate method on the interface itself. Note that there + * can be an override with a different return type in a sub interface. + * + * @param interfaceType Interface from where the search is started. All super interfaces are + * searched transitively. * @return not null. */ static ExecutableElement findDuplicateMethod( @@ -114,15 +115,17 @@ static ExecutableElement findDuplicateMethod( } } } - } - ); - assert duplicateMethod[0] != null : "Interface " + interfaceType.getQualifiedName() + " must implement IR, so it must declare duplicate method"; + }); + assert duplicateMethod[0] != null + : "Interface " + + interfaceType.getQualifiedName() + + " must implement IR, so it must declare duplicate method"; return duplicateMethod[0]; } private static boolean isDuplicateMethod(ExecutableElement executableElement) { - return executableElement.getSimpleName().toString().equals("duplicate") && - executableElement.getParameters().size() == 4; + return executableElement.getSimpleName().toString().equals("duplicate") + && executableElement.getParameters().size() == 4; } private static void iterateSuperInterfaces( diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JName.java b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JName.java index a6f4c594a0c5..c0486e3da1aa 100644 --- a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JName.java +++ b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JName.java @@ -12,7 +12,10 @@ public interface JName extends JExpression { boolean isMethod(); @Override - JName duplicate(boolean keepLocations, boolean keepMetadata, boolean keepDiagnostics, + JName duplicate( + boolean keepLocations, + boolean keepMetadata, + boolean keepDiagnostics, boolean keepIdentifiers); interface JBlank extends JName {} From 8acfa30fca1061bda14c49828b8bdbcf7db73912 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Thu, 31 Oct 2024 12:56:07 +0100 Subject: [PATCH 45/93] Move duplicate method generation to a separate class --- .../processor/DuplicateMethodGenerator.java | 190 ++++++++++++++++++ .../processor/IRNodeClassGenerator.java | 105 +--------- 2 files changed, 196 insertions(+), 99 deletions(-) create mode 100644 engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/DuplicateMethodGenerator.java diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/DuplicateMethodGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/DuplicateMethodGenerator.java new file mode 100644 index 000000000000..f72ce65145d3 --- /dev/null +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/DuplicateMethodGenerator.java @@ -0,0 +1,190 @@ +package org.enso.runtime.parser.processor; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeKind; + +/** + * Code generator for {@code org.enso.compiler.core.ir.IR#duplicate} method or any of its override. + */ +class DuplicateMethodGenerator { + private final ExecutableElement duplicateMethod; + private final List fields; + private final String className; + private static final List parameters = + List.of( + new Parameter("boolean", "keepLocations"), + new Parameter("boolean", "keepMetadata"), + new Parameter("boolean", "keepDiagnostics"), + new Parameter("boolean", "keepIdentifiers")); + + /** + * @param duplicateMethod ExecutableElement representing the duplicate method (or its override). + * @param fields List of the fields of the generated class + * @param className Name of the generated class + */ + DuplicateMethodGenerator( + ExecutableElement duplicateMethod, List fields, String className) { + ensureDuplicateMethodHasExpectedSignature(duplicateMethod); + this.duplicateMethod = Objects.requireNonNull(duplicateMethod); + this.fields = Objects.requireNonNull(fields); + this.className = Objects.requireNonNull(className); + } + + private static void ensureDuplicateMethodHasExpectedSignature(ExecutableElement duplicateMethod) { + var dupMethodParameters = duplicateMethod.getParameters(); + if (dupMethodParameters.size() != parameters.size()) { + throw new IllegalArgumentException( + "Duplicate method must have " + parameters.size() + " parameters"); + } + var allParamsAreBooleans = + dupMethodParameters.stream().allMatch(par -> par.asType().getKind() == TypeKind.BOOLEAN); + if (!allParamsAreBooleans) { + throw new IllegalArgumentException( + "All parameters of the duplicate method must be of type boolean"); + } + } + + String generateDuplicateMethodCode() { + var sb = new StringBuilder(); + sb.append("@Override").append(System.lineSeparator()); + sb.append("public ") + .append(dupMethodRetType()) + .append(" duplicate(") + .append(parameters.stream().map(Parameter::toString).collect(Collectors.joining(", "))) + .append(") {") + .append(System.lineSeparator()); + var duplicatedVars = new ArrayList(); + for (var field : fields) { + if (field.isChild()) { + if (field.isNullable()) { + sb.append(Utils.indent(nullableChildCode(field), 2)); + sb.append(System.lineSeparator()); + duplicatedVars.add( + new DuplicateVar(field.getSimpleTypeName(), dupFieldName(field), true)); + } else if (!field.isNullable() && !field.isList()) { + sb.append(Utils.indent(notNullableChildCode(field), 2)); + sb.append(System.lineSeparator()); + duplicatedVars.add( + new DuplicateVar(field.getSimpleTypeName(), dupFieldName(field), true)); + } else if (field.isList()) { + sb.append(Utils.indent(listChildCode(field), 2)); + sb.append(System.lineSeparator()); + duplicatedVars.add(new DuplicateVar(null, dupFieldName(field), false)); + } + } + // TODO: Duplicate non-child fields? + } + sb.append(Utils.indent(returnStatement(duplicatedVars), 2)); + sb.append(System.lineSeparator()); + sb.append("}"); + sb.append(System.lineSeparator()); + return sb.toString(); + } + + private static String dupFieldName(Field field) { + return field.getName() + "Duplicated"; + } + + private static String nullableChildCode(Field nullableChild) { + assert nullableChild.isNullable() && nullableChild.isChild(); + return """ + IR $dupName = null; + if ($childName != null) { + $dupName = $childName.duplicate($parameterNames); + if (!($dupName instanceof $childType)) { + throw new IllegalStateException("Duplicated child is not of the expected type: " + $dupName); + } + } + """ + .replace("$childType", nullableChild.getSimpleTypeName()) + .replace("$childName", nullableChild.getName()) + .replace("$dupName", dupFieldName(nullableChild)) + .replace("$parameterNames", String.join(", ", parameterNames())); + } + + private static String notNullableChildCode(Field child) { + assert child.isChild() && !child.isNullable() && !child.isList(); + return """ + IR $dupName = $childName.duplicate($parameterNames); + if (!($dupName instanceof $childType)) { + throw new IllegalStateException("Duplicated child is not of the expected type: " + $dupName); + } + """ + .replace("$childType", child.getSimpleTypeName()) + .replace("$childName", child.getName()) + .replace("$dupName", dupFieldName(child)) + .replace("$parameterNames", String.join(", ", parameterNames())); + } + + private static String listChildCode(Field listChild) { + assert listChild.isChild() && listChild.isList(); + return """ + $childListType $dupName = + $childName.map(child -> { + IR dupChild = child.duplicate($parameterNames); + if (!(dupChild instanceof $childType)) { + throw new IllegalStateException("Duplicated child is not of the expected type: " + dupChild); + } + return ($childType) dupChild; + }); + """ + .replace("$childListType", listChild.getSimpleTypeName()) + .replace("$childType", listChild.getTypeParameter()) + .replace("$childName", listChild.getName()) + .replace("$dupName", dupFieldName(listChild)) + .replace("$parameterNames", String.join(", ", parameterNames())); + } + + private static List parameterNames() { + return parameters.stream().map(Parameter::name).collect(Collectors.toList()); + } + + private String returnStatement(List duplicatedVars) { + var argList = + duplicatedVars.stream() + .map( + var -> { + if (var.needsCast) { + return "(" + var.type + ") " + var.name; + } else { + return var.name; + } + }) + .collect(Collectors.joining(", ")); + return "return new " + className + "(" + argList + ");"; + } + + private String dupMethodRetType() { + return duplicateMethod.getReturnType().toString(); + } + + private static String stripWhitespaces(String s) { + return s.replaceAll("\\s+", ""); + } + + /** + * @param type Nullable + * @param name + * @param needsCast If the duplicated variable needs to be casted to its type in the return + * statement. + */ + private record DuplicateVar(String type, String name, boolean needsCast) {} + + /** + * Parameter for the duplicate method + * + * @param type + * @param name + */ + private record Parameter(String type, String name) { + + @Override + public String toString() { + return type + " " + name; + } + } +} diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java index 3c977d260dbf..d8059abfae57 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java @@ -6,7 +6,6 @@ import java.util.stream.Collectors; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import org.enso.runtime.parser.dsl.IRChild; import org.enso.runtime.parser.dsl.IRNode; @@ -36,12 +35,7 @@ final class IRNodeClassGenerator { /** User defined fields - all the abstract parameterless methods, including the inherited ones. */ private final List fields; - /** - * {@link org.enso.compiler.core.IR#duplicate(boolean, boolean, boolean, boolean) duplicate} - * method element. We need to know if there is any override with a different return type in the - * interface hierarchy. If not, this is just a reference to the method from IR. - */ - private final ExecutableElement duplicateMethod; + private final DuplicateMethodGenerator duplicateMethodGenerator; private static final Set defaultImportedTypes = Set.of( @@ -69,7 +63,9 @@ final class IRNodeClassGenerator { this.interfaceType = interfaceType; this.className = className; this.fields = getAllFields(interfaceType); - this.duplicateMethod = Utils.findDuplicateMethod(interfaceType, processingEnv); + var duplicateMethod = Utils.findDuplicateMethod(interfaceType, processingEnv); + this.duplicateMethodGenerator = + new DuplicateMethodGenerator(duplicateMethod, fields, className); var nestedTypes = interfaceType.getEnclosedElements().stream() .filter( @@ -229,82 +225,11 @@ private String childrenMethodBody() { return indent(sb.toString(), 2); } - private String duplicateMethodBody() { - var nl = System.lineSeparator(); - var nullableChildrenCode = - fields.stream() - .filter(field -> field.isChild() && field.isNullable()) - .map( - field -> - """ - IR $childNameDup = null; - if ($childName != null) { - $childNameDup = $childName.duplicate(keepLocations, keepMetadata, keepDiagnostics, keepIdentifiers); - if (!($childNameDup instanceof $childType)) { - throw new IllegalStateException("Duplicated child is not of the expected type: " + $childNameDup); - } - } - """ - .replace("$childType", field.getSimpleTypeName()) - .replace("$childName", field.getName()) - .replace("$childNameDup", field.getName() + "Duplicated")) - .collect(Collectors.joining(nl)); - - var notNullableChildrenCode = - fields.stream() - .filter(field -> field.isChild() && !field.isNullable() && !field.isList()) - .map( - field -> - """ - IR $childNameDup = - $childName.duplicate(keepLocations, keepMetadata, keepDiagnostics, keepIdentifiers); - if (!($childNameDup instanceof $childType)) { - throw new IllegalStateException("Duplicated child is not of the expected type: " + $childNameDup); - } - """ - .replace("$childType", field.getSimpleTypeName()) - .replace("$childName", field.getName()) - .replace("$childNameDup", field.getName() + "Duplicated")) - .collect(Collectors.joining(nl)); - - var listChildrenCode = - fields.stream() - .filter(field -> field.isChild() && field.isList()) - .map( - field -> - """ - $childListType $childNameDup = - $childName.map(child -> { - IR dupChild = child.duplicate(keepLocations, keepMetadata, keepDiagnostics, keepIdentifiers); - if (!(dupChild instanceof $childType)) { - throw new IllegalStateException("Duplicated child is not of the expected type: " + dupChild); - } - return ($childType) dupChild; - }); - """ - .replace("$childListType", field.getSimpleTypeName()) - .replace("$childType", field.getTypeParameter()) - .replace("$childName", field.getName()) - .replace("$childNameDup", field.getName() + "Duplicated")) - .collect(Collectors.joining(nl)); - - var code = nullableChildrenCode + nl + notNullableChildrenCode + nl + listChildrenCode; - if (stripWhitespaces(code).isEmpty()) { - code = "return new " + className + "();"; - } - return indent(code, 2); - } - - private static String stripWhitespaces(String s) { - return s.replaceAll("\\s+", ""); - } - /** * Returns a String representing all the overriden methods from {@link org.enso.compiler.core.IR}. * Meant to be inside the generated record definition. */ private String overrideIRMethods() { - var duplicateMethodRetType = duplicateMethod.getReturnType().toString(); var code = """ @@ -361,15 +286,7 @@ public DiagnosticStorage getDiagnostics() { return diagnostics; } - @Override - public $duplicateMethodRetType duplicate( - boolean keepLocations, - boolean keepMetadata, - boolean keepDiagnostics, - boolean keepIdentifiers - ) { - $duplicateMethodBody - } + $duplicateMethod @Override public String showCode(int indent) { @@ -377,8 +294,7 @@ public String showCode(int indent) { } """ .replace("$childrenMethodBody", childrenMethodBody()) - .replace("$duplicateMethodRetType", duplicateMethodRetType) - .replace("$duplicateMethodBody", duplicateMethodBody()); + .replace("$duplicateMethod", duplicateMethodGenerator.generateDuplicateMethodCode()); return indent(code, 2); } @@ -483,13 +399,4 @@ private static String indent(String code, int indentation) { .map(line -> " ".repeat(indentation) + line) .collect(Collectors.joining(System.lineSeparator())); } - - private void ensureIsSubtypeOfIR(TypeElement typeElem) { - if (!Utils.isSubtypeOfIR(typeElem, processingEnv)) { - Utils.printError( - "Method annotated with @IRChild must return a subtype of IR interface", - typeElem, - processingEnv.getMessager()); - } - } } From 7988276a7fdec343ef70acbd3a3e6969ea52dd7d Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Thu, 31 Oct 2024 13:31:45 +0100 Subject: [PATCH 46/93] Duplicate method duplicates also non-child fields --- .../processor/DuplicateMethodGenerator.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/DuplicateMethodGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/DuplicateMethodGenerator.java index f72ce65145d3..1d0e3050304f 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/DuplicateMethodGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/DuplicateMethodGenerator.java @@ -75,8 +75,11 @@ String generateDuplicateMethodCode() { sb.append(System.lineSeparator()); duplicatedVars.add(new DuplicateVar(null, dupFieldName(field), false)); } + } else { + sb.append(Utils.indent(nonChildCode(field), 2)); + sb.append(System.lineSeparator()); + duplicatedVars.add(new DuplicateVar(field.getSimpleTypeName(), dupFieldName(field), false)); } - // TODO: Duplicate non-child fields? } sb.append(Utils.indent(returnStatement(duplicatedVars), 2)); sb.append(System.lineSeparator()); @@ -139,6 +142,16 @@ private static String listChildCode(Field listChild) { .replace("$parameterNames", String.join(", ", parameterNames())); } + private static String nonChildCode(Field field) { + assert !field.isChild(); + return """ + $childType $dupName = $childName; + """ + .replace("$childType", field.getSimpleTypeName()) + .replace("$childName", field.getName()) + .replace("$dupName", dupFieldName(field)); + } + private static List parameterNames() { return parameters.stream().map(Parameter::name).collect(Collectors.toList()); } From c84ebb7021f8b964eb209934aa53f950d22cc959 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Thu, 31 Oct 2024 16:57:32 +0100 Subject: [PATCH 47/93] Simplify tests --- .../processor/test/TestIRProcessor.java | 159 ++++++------------ 1 file changed, 49 insertions(+), 110 deletions(-) diff --git a/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java b/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java index e462cbc95e92..bc497573ec75 100644 --- a/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java +++ b/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java @@ -1,15 +1,39 @@ package org.enso.runtime.parser.processor.test; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; import com.google.testing.compile.CompilationSubject; import com.google.testing.compile.Compiler; import com.google.testing.compile.JavaFileObjects; +import java.io.IOException; import org.enso.runtime.parser.processor.IRProcessor; import org.junit.Test; public class TestIRProcessor { + /** + * Compiles the code given in {@code src} with {@link IRProcessor} and returns the + * contents of the generated java source file. + * @param name FQN of the Java source file + * @param src + * @return + */ + private static String generatedClass(String name, String src) { + var srcObject = JavaFileObjects.forSourceString(name, src); + var compiler = Compiler.javac().withProcessors(new IRProcessor()); + var compilation = compiler.compile(srcObject); + CompilationSubject.assertThat(compilation).succeeded(); + assertThat("Generated just one source", compilation.generatedSourceFiles().size(), is(1)); + var generatedSrc = compilation.generatedSourceFiles().get(0); + try { + return generatedSrc.getCharContent(false).toString(); + } catch (IOException e) { + throw new AssertionError(e); + } + } + @Test public void simpleIRNodeWithoutChildren_CompilationSucceeds() { var src = @@ -83,10 +107,7 @@ public interface JName extends IR {} @Test public void simpleIRNodeWithChild() { - var src = - JavaFileObjects.forSourceString( - "MyIR", - """ + var genSrc = generatedClass("MyIR", """ import org.enso.runtime.parser.dsl.IRNode; import org.enso.runtime.parser.dsl.IRChild; import org.enso.compiler.core.IR; @@ -97,22 +118,13 @@ public interface MyIR extends IR { @IRChild JExpression expression(); } """); - var compiler = Compiler.javac().withProcessors(new IRProcessor()); - var compilation = compiler.compile(src); - CompilationSubject.assertThat(compilation).succeeded(); - CompilationSubject.assertThat(compilation).generatedSourceFile("MyIRGen").isNotNull(); - assertThat("Generated just one source", compilation.generatedSourceFiles().size(), is(1)); - var srcSubject = - CompilationSubject.assertThat(compilation) - .generatedSourceFile("MyIRGen") - .contentsAsUtf8String(); - srcSubject.containsMatch("JExpression expression\\(\\)"); + assertThat(genSrc, containsString("JExpression expression()")); } @Test public void irNodeWithMultipleFields_PrimitiveField() { - var src = - JavaFileObjects.forSourceString( + var genSrc = + generatedClass( "MyIR", """ import org.enso.runtime.parser.dsl.IRNode; @@ -125,21 +137,13 @@ public interface MyIR extends IR { boolean suspended(); } """); - var compiler = Compiler.javac().withProcessors(new IRProcessor()); - var compilation = compiler.compile(src); - CompilationSubject.assertThat(compilation).succeeded(); - assertThat("Generated just one source", compilation.generatedSourceFiles().size(), is(1)); - var srcSubject = - CompilationSubject.assertThat(compilation) - .generatedSourceFile("MyIRGen") - .contentsAsUtf8String(); - srcSubject.containsMatch("boolean suspended\\(\\)"); + assertThat(genSrc, containsString("boolean suspended()")); } @Test public void irNodeWithInheritedField() { var src = - JavaFileObjects.forSourceString( + generatedClass( "MyIR", """ import org.enso.runtime.parser.dsl.IRNode; @@ -156,21 +160,13 @@ public interface MyIR extends MySuperIR { } """); - var compiler = Compiler.javac().withProcessors(new IRProcessor()); - var compilation = compiler.compile(src); - CompilationSubject.assertThat(compilation).succeeded(); - assertThat("Generated just one source", compilation.generatedSourceFiles().size(), is(1)); - var srcSubject = - CompilationSubject.assertThat(compilation) - .generatedSourceFile("MyIRGen") - .contentsAsUtf8String(); - srcSubject.containsMatch("boolean suspended\\(\\)"); + assertThat(src, containsString("boolean suspended()")); } @Test public void irNodeWithInheritedField_Override() { var src = - JavaFileObjects.forSourceString( + generatedClass( "MyIR", """ import org.enso.runtime.parser.dsl.IRNode; @@ -188,21 +184,13 @@ public interface MyIR extends MySuperIR { } """); - var compiler = Compiler.javac().withProcessors(new IRProcessor()); - var compilation = compiler.compile(src); - CompilationSubject.assertThat(compilation).succeeded(); - assertThat("Generated just one source", compilation.generatedSourceFiles().size(), is(1)); - var srcSubject = - CompilationSubject.assertThat(compilation) - .generatedSourceFile("MyIRGen") - .contentsAsUtf8String(); - srcSubject.containsMatch("boolean suspended\\(\\)"); + assertThat(src, containsString("boolean suspended()")); } @Test public void irNodeWithInheritedField_Transitive() { var src = - JavaFileObjects.forSourceString( + generatedClass( "MyIR", """ import org.enso.runtime.parser.dsl.IRNode; @@ -220,23 +208,14 @@ interface MySuperIR extends MySuperSuperIR { @IRNode public interface MyIR extends MySuperIR { } - """); - var compiler = Compiler.javac().withProcessors(new IRProcessor()); - var compilation = compiler.compile(src); - CompilationSubject.assertThat(compilation).succeeded(); - assertThat("Generated just one source", compilation.generatedSourceFiles().size(), is(1)); - var srcSubject = - CompilationSubject.assertThat(compilation) - .generatedSourceFile("MyIRGen") - .contentsAsUtf8String(); - srcSubject.containsMatch("boolean suspended\\(\\)"); + assertThat(src, containsString("boolean suspended()")); } @Test public void irNodeAsNestedInterface() { var src = - JavaFileObjects.forSourceString( + generatedClass( "JName", """ import org.enso.runtime.parser.dsl.IRNode; @@ -249,22 +228,14 @@ public interface JName extends IR { interface JBlank extends JName {} } """); - var compiler = Compiler.javac().withProcessors(new IRProcessor()); - var compilation = compiler.compile(src); - CompilationSubject.assertThat(compilation).succeeded(); - assertThat("Generated just one source", compilation.generatedSourceFiles().size(), is(1)); - var srcSubject = - CompilationSubject.assertThat(compilation) - .generatedSourceFile("JNameGen") - .contentsAsUtf8String(); - srcSubject.contains("public final class JNameGen"); - srcSubject.contains("public static final class JBlankGen implements JName.JBlank"); + assertThat(src, containsString("public final class JNameGen")); + assertThat(src, containsString("public static final class JBlankGen implements JName.JBlank")); } @Test public void returnValueCanBeScalaList() { var src = - JavaFileObjects.forSourceString( + generatedClass( "JName", """ import org.enso.runtime.parser.dsl.IRNode; @@ -278,22 +249,14 @@ public interface JName extends IR { List expressions(); } """); - var compiler = Compiler.javac().withProcessors(new IRProcessor()); - var compilation = compiler.compile(src); - CompilationSubject.assertThat(compilation).succeeded(); - assertThat("Generated just one source", compilation.generatedSourceFiles().size(), is(1)); - var srcSubject = - CompilationSubject.assertThat(compilation) - .generatedSourceFile("JNameGen") - .contentsAsUtf8String(); - srcSubject.contains("public final class JNameGen"); - srcSubject.contains("List expressions"); + assertThat(src, containsString("public final class JNameGen")); + assertThat(src, containsString("List expressions")); } @Test public void processorDoesNotGenerateOverridenMethods() { var src = - JavaFileObjects.forSourceString( + generatedClass( "JName", """ import org.enso.runtime.parser.dsl.IRNode; @@ -311,22 +274,14 @@ default String name() { } } """); - var compiler = Compiler.javac().withProcessors(new IRProcessor()); - var compilation = compiler.compile(src); - CompilationSubject.assertThat(compilation).succeeded(); - assertThat("Generated just one source", compilation.generatedSourceFiles().size(), is(1)); - var srcSubject = - CompilationSubject.assertThat(compilation) - .generatedSourceFile("JNameGen") - .contentsAsUtf8String(); - srcSubject.contains("public final class JNameGen"); - srcSubject.doesNotContain("String name()"); + assertThat(src, containsString("public final class JNameGen")); + assertThat(src, not(containsString("String name()"))); } @Test public void overrideCorrectMethods() { var src = - JavaFileObjects.forSourceString( + generatedClass( "JExpression", """ import org.enso.runtime.parser.dsl.IRNode; @@ -344,22 +299,14 @@ interface JBinding extends JExpression { } } """); - var compiler = Compiler.javac().withProcessors(new IRProcessor()); - var compilation = compiler.compile(src); - CompilationSubject.assertThat(compilation).succeeded(); - assertThat("Generated just one source", compilation.generatedSourceFiles().size(), is(1)); - var srcSubject = - CompilationSubject.assertThat(compilation) - .generatedSourceFile("JExpressionGen") - .contentsAsUtf8String(); - srcSubject.contains("class JBlockGen"); - srcSubject.contains("class JBindingGen"); + assertThat(src, containsString("class JBlockGen")); + assertThat(src, containsString("class JBindingGen")); } @Test public void canOverrideMethodsFromIR() { var src = - JavaFileObjects.forSourceString( + generatedClass( "JName", """ import org.enso.runtime.parser.dsl.IRNode; @@ -373,14 +320,6 @@ public interface JName extends IR { interface JSelf extends JName {} } """); - var compiler = Compiler.javac().withProcessors(new IRProcessor()); - var compilation = compiler.compile(src); - CompilationSubject.assertThat(compilation).succeeded(); - assertThat("Generated just one source", compilation.generatedSourceFiles().size(), is(1)); - var srcSubject = - CompilationSubject.assertThat(compilation) - .generatedSourceFile("JNameGen") - .contentsAsUtf8String(); - srcSubject.contains("JName duplicate"); + assertThat(src, containsString("JName duplicate")); } } From 8a778f047e2c13d72cb4ee5fcc52101fef2a9eb7 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Thu, 31 Oct 2024 18:11:28 +0100 Subject: [PATCH 48/93] During runtime-parser-processor-test compilation, IRNodeProcessor runs --- build.sbt | 7 ++++++- .../{TestIRProcessor.java => TestIRProcessorInline.java} | 6 +++++- 2 files changed, 11 insertions(+), 2 deletions(-) rename engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/{TestIRProcessor.java => TestIRProcessorInline.java} (97%) diff --git a/build.sbt b/build.sbt index 85d7e32d0cf6..7fab579b8f4b 100644 --- a/build.sbt +++ b/build.sbt @@ -3141,6 +3141,11 @@ lazy val `runtime-parser-processor-tests` = inConfig(Compile)(truffleRunOptionsSettings), frgaalJavaCompilerSetting, commands += WithDebugCommand.withDebug, + annotationProcSetting, + Compile / javacOptions ++= Seq( + "-processor", + "org.enso.runtime.parser.processor.IRProcessor" + ), Test / fork := true, libraryDependencies ++= Seq( "junit" % "junit" % junitVersion % Test, @@ -3150,7 +3155,7 @@ lazy val `runtime-parser-processor-tests` = ) ) .dependsOn(`runtime-parser-processor`) - .dependsOn(`runtime-parser` % Test) + .dependsOn(`runtime-parser`) lazy val `runtime-parser-processor` = (project in file("engine/runtime-parser-processor")) diff --git a/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java b/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessorInline.java similarity index 97% rename from engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java rename to engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessorInline.java index bc497573ec75..da0941fcf1c6 100644 --- a/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessor.java +++ b/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessorInline.java @@ -12,7 +12,11 @@ import org.enso.runtime.parser.processor.IRProcessor; import org.junit.Test; -public class TestIRProcessor { +/** + * Basic tests of {@link IRProcessor} that compiles snippets of annotated code, and checks the + * generated classes. The compiler (along with the processor) is invoked in the unit tests. + */ +public class TestIRProcessorInline { /** * Compiles the code given in {@code src} with {@link IRProcessor} and returns the * contents of the generated java source file. From 86d1b0eedc6505e976d09c5bf387494c224349fc Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Thu, 31 Oct 2024 18:12:10 +0100 Subject: [PATCH 49/93] docs --- .../java/org/enso/runtime/parser/dsl/IRChild.java | 8 ++++++++ .../java/org/enso/runtime/parser/dsl/IRNode.java | 13 +++++++++++++ 2 files changed, 21 insertions(+) diff --git a/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/IRChild.java b/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/IRChild.java index bfbe1fb545e2..fe31bb942289 100644 --- a/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/IRChild.java +++ b/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/IRChild.java @@ -5,8 +5,16 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * Abstract methods annotated with this annotation will return the child of the current + * IR element (the current IR element is the interface annotated with {@link IRNode} that encloses + * this method). Children of IR elements form a tree. A child will be part of the methods traversing + * the tree, like {@code mapExpression} and {@code children}. The method must have no parameters and + * return a subtype of {@code org.enso.compiler.ir.IR}. + */ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.METHOD) public @interface IRChild { + /** If true, the child will always be non-null. Otherwise, it can be null. */ boolean required() default true; } diff --git a/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/IRNode.java b/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/IRNode.java index b711f27fc391..a8aaa434331b 100644 --- a/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/IRNode.java +++ b/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/IRNode.java @@ -5,6 +5,19 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * An interface annotated with this annotation will be processed by the IR processor. The processor + * will generate a class that extends this interface. The generated class will have the same package + * as this interface, and its name will have the "Gen" suffix. The interface must be a subtype of + * {@code org.enso.compiler.ir.IR}. The interface can contain {@link IRChild} and {@link + * IRCopyMethod} annotated methods. + * + *

For every abstract parameterless method of the interface, there will be a field in the + * generated class. + * + *

The interface can contain arbitrary number of nested interfaces. In such case, the processor + * will generate nested static classes for all these nested interfaces. + */ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.TYPE) public @interface IRNode {} From 8aa64bd8aab42a27ecef29235ce90acbfeca567a Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Thu, 31 Oct 2024 18:28:02 +0100 Subject: [PATCH 50/93] Add some IR interfaces to the testing code --- .../processor/test/gen/ir/NameTestIR.java | 9 ++++ .../processor/test/gen/ir/package-info.java | 6 +++ .../processor/test/TestGeneratedIR.java | 48 +++++++++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/NameTestIR.java create mode 100644 engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/package-info.java create mode 100644 engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestGeneratedIR.java diff --git a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/NameTestIR.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/NameTestIR.java new file mode 100644 index 000000000000..1c5bfad08270 --- /dev/null +++ b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/NameTestIR.java @@ -0,0 +1,9 @@ +package org.enso.runtime.parser.processor.test.gen.ir; + +import org.enso.compiler.core.IR; +import org.enso.runtime.parser.dsl.IRNode; + +@IRNode +public interface NameTestIR extends IR { + String name(); +} diff --git a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/package-info.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/package-info.java new file mode 100644 index 000000000000..ce6762b48303 --- /dev/null +++ b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/package-info.java @@ -0,0 +1,6 @@ +/** + * Contains interfaces with parser-dsl annotations. There will be generated classes for + * these interfaces and they are tested. All these interfaces are only for testing. + */ +package org.enso.runtime.parser.processor.test.gen.ir; + diff --git a/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestGeneratedIR.java b/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestGeneratedIR.java new file mode 100644 index 000000000000..d18c0838c0ed --- /dev/null +++ b/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestGeneratedIR.java @@ -0,0 +1,48 @@ +package org.enso.runtime.parser.processor.test; + +import org.enso.runtime.parser.processor.test.gen.ir.NameTestIR; +import org.enso.runtime.parser.processor.test.gen.ir.NameTestIRGen; +import org.junit.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.fail; + + +public class TestGeneratedIR { + @Test + public void generatedCodeHasBuilder() { + NameTestIR myIr = NameTestIRGen.builder().name("name").build(); + assertThat(myIr.name(), is("name")); + } + + @Test + public void myIRHasNoChildren() { + NameTestIR myIr = NameTestIRGen.builder().name("name").build(); + assertThat(myIr.children().isEmpty(), is(true)); + } + + @Test + public void nonChildFieldIsNotNullable() { + var bldr = NameTestIRGen.builder(); + try { + bldr.build(); + fail("Expected exception - name field must be specified in the builder"); + } catch (Exception e) { + assertThat(e, is(notNullValue())); + } + } + + @Test + public void canDuplicate() { + NameTestIR myIr = NameTestIRGen.builder().name("name").build(); + var duplicated = myIr.duplicate(true, true, true, true); + assertThat("duplicate returns same type", + duplicated, instanceOf(NameTestIR.class)); + assertThat("name was correctly duplicated", + ((NameTestIR) duplicated).name(), is("name")); + } + +} From e91f09e7cd2f543fa140244168814aef640ef39d Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Thu, 31 Oct 2024 18:41:32 +0100 Subject: [PATCH 51/93] test generated list children --- .../processor/test/gen/ir/ListTestIR.java | 12 +++++ .../processor/test/gen/ir/OptNameTestIR.java | 11 +++++ .../processor/test/TestGeneratedIR.java | 46 +++++++++++++++++++ 3 files changed, 69 insertions(+) create mode 100644 engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/ListTestIR.java create mode 100644 engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/OptNameTestIR.java diff --git a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/ListTestIR.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/ListTestIR.java new file mode 100644 index 000000000000..12ff6aea443e --- /dev/null +++ b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/ListTestIR.java @@ -0,0 +1,12 @@ +package org.enso.runtime.parser.processor.test.gen.ir; + +import org.enso.compiler.core.IR; +import org.enso.runtime.parser.dsl.IRChild; +import org.enso.runtime.parser.dsl.IRNode; +import scala.collection.immutable.List; + +@IRNode +public interface ListTestIR extends IR { + @IRChild + List names(); +} diff --git a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/OptNameTestIR.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/OptNameTestIR.java new file mode 100644 index 000000000000..b6443ec5031e --- /dev/null +++ b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/OptNameTestIR.java @@ -0,0 +1,11 @@ +package org.enso.runtime.parser.processor.test.gen.ir; + +import org.enso.compiler.core.IR; +import org.enso.runtime.parser.dsl.IRChild; +import org.enso.runtime.parser.dsl.IRNode; + +@IRNode +public interface OptNameTestIR extends IR { + @IRChild(required = false) + OptNameTestIR originalName(); +} diff --git a/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestGeneratedIR.java b/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestGeneratedIR.java index d18c0838c0ed..c7631ffdee64 100644 --- a/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestGeneratedIR.java +++ b/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestGeneratedIR.java @@ -1,13 +1,19 @@ package org.enso.runtime.parser.processor.test; +import org.enso.runtime.parser.processor.test.gen.ir.ListTestIR; +import org.enso.runtime.parser.processor.test.gen.ir.ListTestIRGen; import org.enso.runtime.parser.processor.test.gen.ir.NameTestIR; import org.enso.runtime.parser.processor.test.gen.ir.NameTestIRGen; +import org.enso.runtime.parser.processor.test.gen.ir.OptNameTestIR; +import org.enso.runtime.parser.processor.test.gen.ir.OptNameTestIRGen; import org.junit.Test; +import java.util.List; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.fail; @@ -45,4 +51,44 @@ public void canDuplicate() { ((NameTestIR) duplicated).name(), is("name")); } + @Test + public void canCreateList() { + var firstName = NameTestIRGen.builder().name("first_name").build(); + var secondName = NameTestIRGen.builder().name("second_name").build(); + scala.collection.immutable.List names = asScala(List.of(firstName, secondName)); + var listIr = ListTestIRGen.builder().names(names).build(); + assertThat(listIr.names().size(), is(2)); + } + + @Test + public void canGetListAsChildren() { + var firstName = NameTestIRGen.builder().name("first_name").build(); + var secondName = NameTestIRGen.builder().name("second_name").build(); + scala.collection.immutable.List names = asScala(List.of(firstName, secondName)); + var listIr = ListTestIRGen.builder().names(names).build(); + assertThat(listIr.children().size(), is(2)); + assertThat(listIr.children().head(), instanceOf(NameTestIR.class)); + } + + @Test + public void canDuplicateListTestIR() { + var firstName = NameTestIRGen.builder().name("first_name").build(); + var secondName = NameTestIRGen.builder().name("second_name").build(); + scala.collection.immutable.List names = asScala(List.of(firstName, secondName)); + var listIr = ListTestIRGen.builder().names(names).build(); + var duplicated = listIr.duplicate(true, true, true, true); + assertThat(duplicated, instanceOf(ListTestIR.class)); + assertThat(duplicated.children().size(), is(2)); + } + + @Test + public void optChildIsNotRequired() { + var optNameTestIR = OptNameTestIRGen.builder().build(); + assertThat(optNameTestIR, is(notNullValue())); + assertThat(optNameTestIR.originalName(), is(nullValue())); + } + + private static scala.collection.immutable.List asScala(List list) { + return scala.jdk.javaapi.CollectionConverters.asScala(list).toList(); + } } From dd34e5d0926c584abbbadbf2331ad31e374582bb Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 1 Nov 2024 18:42:03 +0100 Subject: [PATCH 52/93] DuplicateMethodGenerator duplicates also meta fields --- .../processor/DuplicateMethodGenerator.java | 59 ++++++-- .../processor/GeneratedClassContext.java | 134 ++++++++++++++++++ .../processor/IRNodeClassGenerator.java | 34 ++--- .../enso/runtime/parser/processor/Utils.java | 6 + 4 files changed, 206 insertions(+), 27 deletions(-) create mode 100644 engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/GeneratedClassContext.java diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/DuplicateMethodGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/DuplicateMethodGenerator.java index 1d0e3050304f..b89ee5e8e2df 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/DuplicateMethodGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/DuplicateMethodGenerator.java @@ -12,8 +12,7 @@ */ class DuplicateMethodGenerator { private final ExecutableElement duplicateMethod; - private final List fields; - private final String className; + private final GeneratedClassContext ctx; private static final List parameters = List.of( new Parameter("boolean", "keepLocations"), @@ -23,15 +22,11 @@ class DuplicateMethodGenerator { /** * @param duplicateMethod ExecutableElement representing the duplicate method (or its override). - * @param fields List of the fields of the generated class - * @param className Name of the generated class */ - DuplicateMethodGenerator( - ExecutableElement duplicateMethod, List fields, String className) { + DuplicateMethodGenerator(ExecutableElement duplicateMethod, GeneratedClassContext ctx) { ensureDuplicateMethodHasExpectedSignature(duplicateMethod); + this.ctx = Objects.requireNonNull(ctx); this.duplicateMethod = Objects.requireNonNull(duplicateMethod); - this.fields = Objects.requireNonNull(fields); - this.className = Objects.requireNonNull(className); } private static void ensureDuplicateMethodHasExpectedSignature(ExecutableElement duplicateMethod) { @@ -58,7 +53,47 @@ String generateDuplicateMethodCode() { .append(") {") .append(System.lineSeparator()); var duplicatedVars = new ArrayList(); - for (var field : fields) { + + var duplicateMetaFieldsCode = + """ + $diagType diagnosticsDuplicated; + if (keepDiagnostics) { + diagnosticsDuplicated = this.diagnostics; + } else { + diagnosticsDuplicated = null; + } + $metaType passDataDuplicated; + if (keepMetadata) { + passDataDuplicated = this.passData; + } else { + passDataDuplicated = null; + } + $locType locationDuplicated; + if (keepLocations) { + locationDuplicated = this.location; + } else { + locationDuplicated = null; + } + $idType idDuplicated; + if (keepIdentifiers) { + idDuplicated = this.id; + } else { + idDuplicated = null; + } + """ + .replace("$locType", ctx.getLocationMetaField().type()) + .replace("$metaType", ctx.getPassDataMetaField().type()) + .replace("$diagType", ctx.getDiagnosticsMetaField().type()) + .replace("$idType", ctx.getIdMetaField().type()); + sb.append(Utils.indent(duplicateMetaFieldsCode, 2)); + sb.append(System.lineSeparator()); + for (var dupMetaVarName : + List.of( + "diagnosticsDuplicated", "passDataDuplicated", "locationDuplicated", "idDuplicated")) { + duplicatedVars.add(new DuplicateVar(null, dupMetaVarName, false)); + } + + for (var field : ctx.getUserFields()) { if (field.isChild()) { if (field.isNullable()) { sb.append(Utils.indent(nullableChildCode(field), 2)); @@ -81,6 +116,7 @@ String generateDuplicateMethodCode() { duplicatedVars.add(new DuplicateVar(field.getSimpleTypeName(), dupFieldName(field), false)); } } + sb.append(Utils.indent(returnStatement(duplicatedVars), 2)); sb.append(System.lineSeparator()); sb.append("}"); @@ -157,6 +193,9 @@ private static List parameterNames() { } private String returnStatement(List duplicatedVars) { + Utils.hardAssert( + duplicatedVars.size() == ctx.getConstructorParameters().size(), + "Number of duplicated variables must be equal to the number of constructor parameters"); var argList = duplicatedVars.stream() .map( @@ -168,7 +207,7 @@ private String returnStatement(List duplicatedVars) { } }) .collect(Collectors.joining(", ")); - return "return new " + className + "(" + argList + ");"; + return "return new " + ctx.getClassName() + "(" + argList + ");"; } private String dupMethodRetType() { diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/GeneratedClassContext.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/GeneratedClassContext.java new file mode 100644 index 000000000000..cafae8493e07 --- /dev/null +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/GeneratedClassContext.java @@ -0,0 +1,134 @@ +package org.enso.runtime.parser.processor; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.TypeElement; + +/** + * A context created for the generated class. Everything that is needed for the code generation is + * contained in this class. + */ +final class GeneratedClassContext { + private final String className; + private final List userFields; + private final List constructorParameters; + private final ProcessingEnvironment processingEnvironment; + private final TypeElement irNodeInterface; + + private static final ClassField diagnosticsMetaField = + new ClassField("private", "DiagnosticStorage", "diagnostics"); + private static final ClassField passDataMetaField = + new ClassField("private", "MetadataStorage", "passData"); + private static final ClassField locationMetaField = + new ClassField("private", "IdentifiedLocation", "location"); + private static final ClassField idMetaField = new ClassField("private", "UUID", "id"); + + /** Meta fields are always present in the generated class. */ + private static final List metaFields = + List.of(diagnosticsMetaField, passDataMetaField, locationMetaField, idMetaField); + + /** + * @param className Simple name of the generated class + * @param userFields List of user defined fields. These fields are collected from parameterless + * abstract methods in the interface. + * @param irNodeInterface Type element of the interface annotated with {@link + * org.enso.runtime.parser.dsl.IRNode} - for this interface the class is generated. + */ + GeneratedClassContext( + String className, + List userFields, + ProcessingEnvironment processingEnvironment, + TypeElement irNodeInterface) { + this.className = Objects.requireNonNull(className); + this.userFields = Objects.requireNonNull(userFields); + this.processingEnvironment = processingEnvironment; + this.irNodeInterface = irNodeInterface; + ensureSimpleName(className); + this.constructorParameters = + getAllFields().stream() + .map(classField -> new Parameter(classField.type(), classField.name())) + .toList(); + } + + private static void ensureSimpleName(String name) { + if (name.contains(".")) { + throw new IllegalArgumentException("Class name must be simple, not qualified"); + } + } + + ClassField getLocationMetaField() { + return locationMetaField; + } + + ClassField getPassDataMetaField() { + return passDataMetaField; + } + + ClassField getDiagnosticsMetaField() { + return diagnosticsMetaField; + } + + ClassField getIdMetaField() { + return idMetaField; + } + + List getConstructorParameters() { + return constructorParameters; + } + + List getUserFields() { + return userFields; + } + + String getClassName() { + return className; + } + + List getMetaFields() { + return metaFields; + } + + List getAllFields() { + var allFields = new ArrayList(metaFields); + for (var userField : userFields) { + allFields.add( + new ClassField("private final", userField.getSimpleTypeName(), userField.getName())); + } + return allFields; + } + + ProcessingEnvironment getProcessingEnvironment() { + return processingEnvironment; + } + + TypeElement getIrNodeInterface() { + return irNodeInterface; + } + + /** + * Method parameter + * + * @param type + * @param name + */ + record Parameter(String type, String name) { + @Override + public String toString() { + return type + " " + name; + } + } + + /** + * Declared field in the class + * + * @param modifiers + */ + record ClassField(String modifiers, String type, String name) { + @Override + public String toString() { + return modifiers + " " + type + " " + name; + } + } +} diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java index d8059abfae57..22fce71d69aa 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java @@ -32,9 +32,7 @@ final class IRNodeClassGenerator { /** Name of the class that is being generated */ private final String className; - /** User defined fields - all the abstract parameterless methods, including the inherited ones. */ - private final List fields; - + private final GeneratedClassContext generatedClassContext; private final DuplicateMethodGenerator duplicateMethodGenerator; private static final Set defaultImportedTypes = @@ -62,10 +60,12 @@ final class IRNodeClassGenerator { this.processingEnv = processingEnv; this.interfaceType = interfaceType; this.className = className; - this.fields = getAllFields(interfaceType); + var fields = getAllUserFields(interfaceType); var duplicateMethod = Utils.findDuplicateMethod(interfaceType, processingEnv); + this.generatedClassContext = + new GeneratedClassContext(className, fields, processingEnv, interfaceType); this.duplicateMethodGenerator = - new DuplicateMethodGenerator(duplicateMethod, fields, className); + new DuplicateMethodGenerator(duplicateMethod, generatedClassContext); var nestedTypes = interfaceType.getEnclosedElements().stream() .filter( @@ -92,7 +92,7 @@ String getInterfaceName() { /** Returns set of import statements that should be included in the generated class. */ Set imports() { var importsForFields = - fields.stream() + generatedClassContext.getUserFields().stream() .flatMap(field -> field.getImportedTypes().stream()) .collect(Collectors.toUnmodifiableSet()); var allImports = new HashSet(); @@ -136,7 +136,7 @@ public static Builder builder() { * @param irNodeInterface Type element of the interface annotated with {@link IRNode}. * @return List of fields */ - private List getAllFields(TypeElement irNodeInterface) { + private List getAllUserFields(TypeElement irNodeInterface) { var fieldCollector = new FieldCollector(processingEnv, irNodeInterface); return fieldCollector.collectFields(); } @@ -147,7 +147,7 @@ private List getAllFields(TypeElement irNodeInterface) { */ private String fieldsCode() { var userDefinedFields = - fields.stream() + generatedClassContext.getUserFields().stream() .map(field -> "private final " + field.getSimpleTypeName() + " " + field.getName()) .collect(Collectors.joining(";" + System.lineSeparator())); var code = @@ -171,17 +171,17 @@ private String constructor() { var sb = new StringBuilder(); sb.append("private ").append(className).append("("); var inParens = - fields.stream() + generatedClassContext.getConstructorParameters().stream() .map( - field -> - "$fieldType $fieldName" - .replace("$fieldType", field.getSimpleTypeName()) - .replace("$fieldName", field.getName())) + consParam -> + "$consType $consName" + .replace("$consType", consParam.type()) + .replace("$consName", consParam.name())) .collect(Collectors.joining(", ")); sb.append(inParens).append(") {").append(System.lineSeparator()); var ctorBody = - fields.stream() - .map(field -> " this.$fieldName = $fieldName;".replace("$fieldName", field.getName())) + generatedClassContext.getAllFields().stream() + .map(field -> " this.$fieldName = $fieldName;".replace("$fieldName", field.name())) .collect(Collectors.joining(System.lineSeparator())); sb.append(indent(ctorBody, 2)); sb.append(System.lineSeparator()); @@ -193,7 +193,7 @@ private String childrenMethodBody() { var sb = new StringBuilder(); var nl = System.lineSeparator(); sb.append("var list = new ArrayList();").append(nl); - fields.stream() + generatedClassContext.getUserFields().stream() .filter(Field::isChild) .forEach( childField -> { @@ -306,7 +306,7 @@ public String showCode(int indent) { */ private String overrideUserDefinedMethods() { var code = - fields.stream() + generatedClassContext.getUserFields().stream() .map( field -> """ diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java index aa67942491d6..17196d8040b1 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java @@ -123,6 +123,12 @@ static ExecutableElement findDuplicateMethod( return duplicateMethod[0]; } + static void hardAssert(boolean condition, String msg) { + if (!condition) { + throw new AssertionError(msg); + } + } + private static boolean isDuplicateMethod(ExecutableElement executableElement) { return executableElement.getSimpleName().toString().equals("duplicate") && executableElement.getParameters().size() == 4; From 2a2b6d3c7c308938c09aacc692f0138426fe434d Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 1 Nov 2024 18:42:46 +0100 Subject: [PATCH 53/93] Builder allows setting meta fields --- .../processor/BuilderMethodGenerator.java | 82 +++++++++++++++++++ .../processor/IRNodeClassGenerator.java | 77 +---------------- 2 files changed, 85 insertions(+), 74 deletions(-) create mode 100644 engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/BuilderMethodGenerator.java diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/BuilderMethodGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/BuilderMethodGenerator.java new file mode 100644 index 000000000000..d29bb640cbcf --- /dev/null +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/BuilderMethodGenerator.java @@ -0,0 +1,82 @@ +package org.enso.runtime.parser.processor; + +import java.util.stream.Collectors; +import org.enso.runtime.parser.processor.GeneratedClassContext.ClassField; + +class BuilderMethodGenerator { + private final GeneratedClassContext generatedClassContext; + + BuilderMethodGenerator(GeneratedClassContext generatedClassContext) { + this.generatedClassContext = generatedClassContext; + } + + String generateBuilder() { + var fieldDeclarations = + generatedClassContext.getAllFields().stream() + .map( + metaField -> + """ + private $type $name; + """ + .replace("$type", metaField.type()) + .replace("$name", metaField.name())) + .collect(Collectors.joining(System.lineSeparator())); + + var fieldSetters = + generatedClassContext.getAllFields().stream() + .map( + field -> + """ + public Builder $fieldName($fieldType $fieldName) { + this.$fieldName = $fieldName; + return this; + } + """ + .replace("$fieldName", field.name()) + .replace("$fieldType", field.type())) + .collect(Collectors.joining(System.lineSeparator())); + + // Validation code for all non-nullable user fields + var validationCode = + generatedClassContext.getUserFields().stream() + .filter(field -> !field.isNullable() && !field.isPrimitive()) + .map( + field -> + """ + if (this.$fieldName == null) { + throw new IllegalArgumentException("$fieldName is required"); + } + """ + .replace("$fieldName", field.getName())) + .collect(Collectors.joining(System.lineSeparator())); + + var fieldList = + generatedClassContext.getAllFields().stream() + .map(ClassField::name) + .collect(Collectors.joining(", ")); + + var code = + """ + public static final class Builder { + $fieldDeclarations + + $fieldSetters + + public $className build() { + validate(); + return new $className($fieldList); + } + + private void validate() { + $validationCode + } + } + """ + .replace("$fieldDeclarations", fieldDeclarations) + .replace("$fieldSetters", fieldSetters) + .replace("$className", generatedClassContext.getClassName()) + .replace("$fieldList", fieldList) + .replace("$validationCode", Utils.indent(validationCode, 2)); + return Utils.indent(code, 2); + } +} diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java index 22fce71d69aa..d1fd01e7d2e2 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java @@ -34,6 +34,7 @@ final class IRNodeClassGenerator { private final GeneratedClassContext generatedClassContext; private final DuplicateMethodGenerator duplicateMethodGenerator; + private final BuilderMethodGenerator builderMethodGenerator; private static final Set defaultImportedTypes = Set.of( @@ -66,6 +67,7 @@ final class IRNodeClassGenerator { new GeneratedClassContext(className, fields, processingEnv, interfaceType); this.duplicateMethodGenerator = new DuplicateMethodGenerator(duplicateMethod, generatedClassContext); + this.builderMethodGenerator = new BuilderMethodGenerator(generatedClassContext); var nestedTypes = interfaceType.getEnclosedElements().stream() .filter( @@ -124,7 +126,7 @@ public static Builder builder() { .replace("$constructor", constructor()) .replace("$overrideUserDefinedMethods", overrideUserDefinedMethods()) .replace("$overrideIRMethods", overrideIRMethods()) - .replace("$builder", builder()); + .replace("$builder", builderMethodGenerator.generateBuilder()); } /** @@ -321,79 +323,6 @@ private String overrideUserDefinedMethods() { return indent(code, 2); } - /** - * Returns string representation of the code for the builder - that is a nested class that allows - * to build the record. - * - * @return Code of the builder - */ - private String builder() { - var fieldDeclarations = - fields.stream() - .map( - field -> - """ - private $fieldType $fieldName; - """ - .replace("$fieldName", field.getName()) - .replace("$fieldType", field.getSimpleTypeName())) - .collect(Collectors.joining(System.lineSeparator())); - - var fieldSetters = - fields.stream() - .map( - field -> - """ - public Builder $fieldName($fieldType $fieldName) { - this.$fieldName = $fieldName; - return this; - } - """ - .replace("$fieldName", field.getName()) - .replace("$fieldType", field.getSimpleTypeName())) - .collect(Collectors.joining(System.lineSeparator())); - - // Validation code for all non-nullable fields - var validationCode = - fields.stream() - .filter(field -> !field.isNullable() && !field.isPrimitive()) - .map( - field -> - """ - if (this.$fieldName == null) { - throw new IllegalArgumentException("$fieldName is required"); - } - """ - .replace("$fieldName", field.getName())) - .collect(Collectors.joining(System.lineSeparator())); - - var fieldList = fields.stream().map(Field::getName).collect(Collectors.joining(", ")); - - var code = - """ - public static final class Builder { - $fieldDeclarations - - $fieldSetters - - public $className build() { - validate(); - return new $className($fieldList); - } - - private void validate() { - $validationCode - } - } - """ - .replace("$fieldDeclarations", fieldDeclarations) - .replace("$fieldSetters", fieldSetters) - .replace("$className", className) - .replace("$fieldList", fieldList) - .replace("$validationCode", indent(validationCode, 2)); - return indent(code, 2); - } - private static String indent(String code, int indentation) { return code.lines() .map(line -> " ".repeat(indentation) + line) From 7def7b14c31af9cb879b0c113fca15aecdae7f21 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 1 Nov 2024 18:50:36 +0100 Subject: [PATCH 54/93] Add tests for duplicated metadata --- .../processor/test/TestGeneratedIR.java | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestGeneratedIR.java b/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestGeneratedIR.java index c7631ffdee64..0868f6ae5857 100644 --- a/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestGeneratedIR.java +++ b/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestGeneratedIR.java @@ -1,13 +1,16 @@ package org.enso.runtime.parser.processor.test; +import org.enso.compiler.core.ir.DiagnosticStorage; +import org.enso.compiler.core.ir.IdentifiedLocation; +import org.enso.compiler.core.ir.Location; import org.enso.runtime.parser.processor.test.gen.ir.ListTestIR; import org.enso.runtime.parser.processor.test.gen.ir.ListTestIRGen; import org.enso.runtime.parser.processor.test.gen.ir.NameTestIR; import org.enso.runtime.parser.processor.test.gen.ir.NameTestIRGen; -import org.enso.runtime.parser.processor.test.gen.ir.OptNameTestIR; import org.enso.runtime.parser.processor.test.gen.ir.OptNameTestIRGen; import org.junit.Test; import java.util.List; +import scala.Option$; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.instanceOf; @@ -51,6 +54,16 @@ public void canDuplicate() { ((NameTestIR) duplicated).name(), is("name")); } + @Test + public void generatedBuilderCanSetMetadata() { + var diagnostics = DiagnosticStorage.empty(); + var nameIR = NameTestIRGen.builder() + .name("name") + .diagnostics(diagnostics) + .build(); + assertThat(nameIR.diagnostics(), is(diagnostics)); + } + @Test public void canCreateList() { var firstName = NameTestIRGen.builder().name("first_name").build(); @@ -88,6 +101,28 @@ public void optChildIsNotRequired() { assertThat(optNameTestIR.originalName(), is(nullValue())); } + @Test + public void duplicateRespectsParameters() { + var location = IdentifiedLocation.create(new Location(1, 2), Option$.MODULE$.empty()); + var diagnostics = DiagnosticStorage.empty(); + var nameIR = NameTestIRGen.builder() + .name("name") + .location(location) + .diagnostics(diagnostics) + .build(); + var duplicated = nameIR.duplicate(true, false, false, false); + assertThat("Should have copied location meta", + duplicated.location().isDefined(), is(true)); + assertThat("Should have not copied diagnostics", + duplicated.diagnostics(), is(nullValue())); + + var duplicated_2 = nameIR.duplicate(false, false, true, false); + assertThat("Should have not copied location meta", + duplicated_2.location().isDefined(), is(false)); + assertThat("Should have copied diagnostics", + duplicated_2.diagnostics(), is(notNullValue())); + } + private static scala.collection.immutable.List asScala(List list) { return scala.jdk.javaapi.CollectionConverters.asScala(list).toList(); } From 66aba890e0e4af64f08f2007cb2f2b59213404a4 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 1 Nov 2024 18:54:02 +0100 Subject: [PATCH 55/93] Add runtime-parser-processor-tests to the enso aggregate --- build.sbt | 1 + .../processor/test/gen/ir/package-info.java | 5 +- .../processor/test/TestGeneratedIR.java | 48 +++++++------------ 3 files changed, 21 insertions(+), 33 deletions(-) diff --git a/build.sbt b/build.sbt index 7fab579b8f4b..c9e7d318facf 100644 --- a/build.sbt +++ b/build.sbt @@ -344,6 +344,7 @@ lazy val enso = (project in file(".")) `runtime-parser`, `runtime-parser-dsl`, `runtime-parser-processor`, + `runtime-parser-processor-tests`, `runtime-language-arrow`, `runtime-language-epb`, `runtime-instrument-common`, diff --git a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/package-info.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/package-info.java index ce6762b48303..d571af3ee3cc 100644 --- a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/package-info.java +++ b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/package-info.java @@ -1,6 +1,5 @@ /** - * Contains interfaces with parser-dsl annotations. There will be generated classes for - * these interfaces and they are tested. All these interfaces are only for testing. + * Contains interfaces with parser-dsl annotations. There will be generated classes for these + * interfaces and they are tested. All these interfaces are only for testing. */ package org.enso.runtime.parser.processor.test.gen.ir; - diff --git a/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestGeneratedIR.java b/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestGeneratedIR.java index 0868f6ae5857..cd4de51486b3 100644 --- a/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestGeneratedIR.java +++ b/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestGeneratedIR.java @@ -1,5 +1,13 @@ package org.enso.runtime.parser.processor.test; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.fail; + +import java.util.List; import org.enso.compiler.core.ir.DiagnosticStorage; import org.enso.compiler.core.ir.IdentifiedLocation; import org.enso.compiler.core.ir.Location; @@ -9,17 +17,8 @@ import org.enso.runtime.parser.processor.test.gen.ir.NameTestIRGen; import org.enso.runtime.parser.processor.test.gen.ir.OptNameTestIRGen; import org.junit.Test; -import java.util.List; import scala.Option$; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.notNullValue; -import static org.hamcrest.Matchers.nullValue; -import static org.junit.Assert.fail; - - public class TestGeneratedIR { @Test public void generatedCodeHasBuilder() { @@ -48,19 +47,14 @@ public void nonChildFieldIsNotNullable() { public void canDuplicate() { NameTestIR myIr = NameTestIRGen.builder().name("name").build(); var duplicated = myIr.duplicate(true, true, true, true); - assertThat("duplicate returns same type", - duplicated, instanceOf(NameTestIR.class)); - assertThat("name was correctly duplicated", - ((NameTestIR) duplicated).name(), is("name")); + assertThat("duplicate returns same type", duplicated, instanceOf(NameTestIR.class)); + assertThat("name was correctly duplicated", ((NameTestIR) duplicated).name(), is("name")); } @Test public void generatedBuilderCanSetMetadata() { var diagnostics = DiagnosticStorage.empty(); - var nameIR = NameTestIRGen.builder() - .name("name") - .diagnostics(diagnostics) - .build(); + var nameIR = NameTestIRGen.builder().name("name").diagnostics(diagnostics).build(); assertThat(nameIR.diagnostics(), is(diagnostics)); } @@ -105,22 +99,16 @@ public void optChildIsNotRequired() { public void duplicateRespectsParameters() { var location = IdentifiedLocation.create(new Location(1, 2), Option$.MODULE$.empty()); var diagnostics = DiagnosticStorage.empty(); - var nameIR = NameTestIRGen.builder() - .name("name") - .location(location) - .diagnostics(diagnostics) - .build(); + var nameIR = + NameTestIRGen.builder().name("name").location(location).diagnostics(diagnostics).build(); var duplicated = nameIR.duplicate(true, false, false, false); - assertThat("Should have copied location meta", - duplicated.location().isDefined(), is(true)); - assertThat("Should have not copied diagnostics", - duplicated.diagnostics(), is(nullValue())); + assertThat("Should have copied location meta", duplicated.location().isDefined(), is(true)); + assertThat("Should have not copied diagnostics", duplicated.diagnostics(), is(nullValue())); var duplicated_2 = nameIR.duplicate(false, false, true, false); - assertThat("Should have not copied location meta", - duplicated_2.location().isDefined(), is(false)); - assertThat("Should have copied diagnostics", - duplicated_2.diagnostics(), is(notNullValue())); + assertThat( + "Should have not copied location meta", duplicated_2.location().isDefined(), is(false)); + assertThat("Should have copied diagnostics", duplicated_2.diagnostics(), is(notNullValue())); } private static scala.collection.immutable.List asScala(List list) { From f6b75ed326658a1cf05271ee35d77e522434a54a Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 1 Nov 2024 19:31:56 +0100 Subject: [PATCH 56/93] Add IRCopyMethod annotation --- .../enso/runtime/parser/dsl/IRCopyMethod.java | 27 +++++++++ .../parser/processor/CopyMethodGenerator.java | 30 ++++++++++ .../processor/IRNodeClassGenerator.java | 55 ++++++++++++++++++- .../runtime/parser/processor/IRProcessor.java | 3 +- .../processor/InterfaceHierarchyVisitor.java | 55 +++++++++++++++++++ .../enso/runtime/parser/processor/Utils.java | 32 ++++++++++- 6 files changed, 198 insertions(+), 4 deletions(-) create mode 100644 engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/IRCopyMethod.java create mode 100644 engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/CopyMethodGenerator.java create mode 100644 engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/InterfaceHierarchyVisitor.java diff --git a/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/IRCopyMethod.java b/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/IRCopyMethod.java new file mode 100644 index 000000000000..42cdbb0c6ebd --- /dev/null +++ b/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/IRCopyMethod.java @@ -0,0 +1,27 @@ +package org.enso.runtime.parser.dsl; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An abstract method annotated with this annotation will have generated implementation in the + * generated class. The types and names of the parameters must correspond to any field (abstract + * parameterless methods in the interface hierarchy), or to any of the following: + * + *

    + *
  • {@code MetadataStorage passData} + *
  • {@code IdentifiedLocation location} + *
  • {@code UUID id} + *
+ * + * The order of the parameters is not important. Number of parameters must not exceed total number + * of fields and meta fields. + * + *

The name of the annotated method can be arbitrary, but the convention is to use the {@code + * copy} name. + */ +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.METHOD) +public @interface IRCopyMethod {} diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/CopyMethodGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/CopyMethodGenerator.java new file mode 100644 index 000000000000..10ba2397fa75 --- /dev/null +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/CopyMethodGenerator.java @@ -0,0 +1,30 @@ +package org.enso.runtime.parser.processor; + +import java.util.List; +import java.util.Objects; +import javax.lang.model.element.ExecutableElement; +import org.enso.runtime.parser.dsl.IRCopyMethod; + +class CopyMethodGenerator { + private final ExecutableElement copyMethod; + private final List userDefinedFields; + + CopyMethodGenerator(ExecutableElement copyMethod, List userDefinedFields) { + ensureIsAnnotated(copyMethod); + this.copyMethod = Objects.requireNonNull(copyMethod); + this.userDefinedFields = Objects.requireNonNull(userDefinedFields); + for (var parameter : copyMethod.getParameters()) { + throw new UnsupportedOperationException("unimplemented"); + } + } + + private static void ensureIsAnnotated(ExecutableElement copyMethod) { + if (copyMethod.getAnnotation(IRCopyMethod.class) == null) { + throw new IllegalArgumentException("Copy method must be annotated with @IRCopyMethod"); + } + } + + String generateCopyMethod() { + throw new UnsupportedOperationException("unimplemented"); + } +} diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java index d1fd01e7d2e2..85acfdbf40ad 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java @@ -6,8 +6,10 @@ import java.util.stream.Collectors; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import org.enso.runtime.parser.dsl.IRChild; +import org.enso.runtime.parser.dsl.IRCopyMethod; import org.enso.runtime.parser.dsl.IRNode; /** @@ -36,6 +38,12 @@ final class IRNodeClassGenerator { private final DuplicateMethodGenerator duplicateMethodGenerator; private final BuilderMethodGenerator builderMethodGenerator; + /** + * Can be null if there is no method annotated with {@link + * org.enso.runtime.parser.dsl.IRCopyMethod}. + */ + private final CopyMethodGenerator copyMethodGenerator; + private static final Set defaultImportedTypes = Set.of( "java.util.UUID", @@ -61,13 +69,19 @@ final class IRNodeClassGenerator { this.processingEnv = processingEnv; this.interfaceType = interfaceType; this.className = className; - var fields = getAllUserFields(interfaceType); + var userFields = getAllUserFields(interfaceType); var duplicateMethod = Utils.findDuplicateMethod(interfaceType, processingEnv); + var copyMethod = findCopyMethod(); this.generatedClassContext = - new GeneratedClassContext(className, fields, processingEnv, interfaceType); + new GeneratedClassContext(className, userFields, processingEnv, interfaceType); this.duplicateMethodGenerator = new DuplicateMethodGenerator(duplicateMethod, generatedClassContext); this.builderMethodGenerator = new BuilderMethodGenerator(generatedClassContext); + if (copyMethod != null) { + this.copyMethodGenerator = new CopyMethodGenerator(copyMethod, userFields); + } else { + this.copyMethodGenerator = null; + } var nestedTypes = interfaceType.getEnclosedElements().stream() .filter( @@ -79,6 +93,27 @@ final class IRNodeClassGenerator { } } + /** + * @return null if non found. + */ + private ExecutableElement findCopyMethod() { + var ifaceVisitor = + new InterfaceHierarchyVisitor() { + @Override + public IterationResult visitInterface( + TypeElement interfaceElem, ExecutableElement ignored) { + for (var enclosedElem : interfaceElem.getEnclosedElements()) { + if (enclosedElem instanceof ExecutableElement executableElem + && Utils.hasAnnotation(executableElem, IRCopyMethod.class)) { + return new Stop<>(executableElem); + } + } + return new Continue<>(ignored); + } + }; + return Utils.iterateSuperInterfaces(interfaceType, processingEnv, ifaceVisitor); + } + /** Returns simple name of the generated class. */ String getClassName() { return className; @@ -120,12 +155,15 @@ public static Builder builder() { $overrideIRMethods + $copyMethod + $builder """ .replace("$fields", fieldsCode()) .replace("$constructor", constructor()) .replace("$overrideUserDefinedMethods", overrideUserDefinedMethods()) .replace("$overrideIRMethods", overrideIRMethods()) + .replace("$copyMethod", copyMethod()) .replace("$builder", builderMethodGenerator.generateBuilder()); } @@ -323,6 +361,19 @@ private String overrideUserDefinedMethods() { return indent(code, 2); } + /** + * Generates the code for the copy method. The method is generated only if the interface contains + * a method annotated with {@link IRCopyMethod}. In that case, returns an empty string. + * + * @return Code of the copy method or an empty string if the method is not present. + */ + private String copyMethod() { + if (copyMethodGenerator != null) { + copyMethodGenerator.generateCopyMethod(); + } + return ""; + } + private static String indent(String code, int indentation) { return code.lines() .map(line -> " ".repeat(indentation) + line) diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java index bff86a39de30..195659e2339d 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java @@ -19,7 +19,8 @@ @SupportedAnnotationTypes({ "org.enso.runtime.parser.dsl.IRNode", - "org.enso.runtime.parser.dsl.IRChild" + "org.enso.runtime.parser.dsl.IRChild", + "org.enso.runtime.parser.dsl.IRCopyMethod", }) public class IRProcessor extends AbstractProcessor { diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/InterfaceHierarchyVisitor.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/InterfaceHierarchyVisitor.java new file mode 100644 index 000000000000..05091bb4a3a0 --- /dev/null +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/InterfaceHierarchyVisitor.java @@ -0,0 +1,55 @@ +package org.enso.runtime.parser.processor; + +import javax.lang.model.element.TypeElement; + +/** + * A visitor for traversing the interface hierarchy of an interface - it iterates over all the super + * interfaces until it encounters {@code org.enso.compiler.ir.IR} interface. + */ +@FunctionalInterface +interface InterfaceHierarchyVisitor { + /** + * Visits the interface hierarchy of the given interface. + * + * @param iface the interface to visit + * @param currResult the current result of the previous visit. Can be null if this is the first + * visit. + * @return the result of the visit that may signalize whether the iteration should continue or + * stop. + */ + IterationResult visitInterface(TypeElement iface, T currResult); + + abstract class IterationResult { + final T value; + + IterationResult(T value) { + this.value = value; + } + + abstract boolean shouldStop(); + } + + final class Continue extends IterationResult { + + Continue(T value) { + super(value); + } + + @Override + public boolean shouldStop() { + return false; + } + } + + final class Stop extends IterationResult { + + Stop(T value) { + super(value); + } + + @Override + public boolean shouldStop() { + return false; + } + } +} diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java index 17196d8040b1..104e1598ec21 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java @@ -1,5 +1,6 @@ package org.enso.runtime.parser.processor; +import java.lang.annotation.Annotation; import java.util.ArrayDeque; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -129,12 +130,16 @@ static void hardAssert(boolean condition, String msg) { } } + static boolean hasAnnotation(Element element, Class annotationClass) { + return element.getAnnotation(annotationClass) != null; + } + private static boolean isDuplicateMethod(ExecutableElement executableElement) { return executableElement.getSimpleName().toString().equals("duplicate") && executableElement.getParameters().size() == 4; } - private static void iterateSuperInterfaces( + static void iterateSuperInterfaces( TypeElement type, ProcessingEnvironment processingEnv, Consumer consumer) { var interfacesToProcess = new ArrayDeque(); interfacesToProcess.add(type); @@ -150,4 +155,29 @@ private static void iterateSuperInterfaces( } } } + + static T iterateSuperInterfaces( + TypeElement type, + ProcessingEnvironment processingEnv, + InterfaceHierarchyVisitor ifaceVisitor) { + var interfacesToProcess = new ArrayDeque(); + interfacesToProcess.add(type); + T visitResult = null; + while (!interfacesToProcess.isEmpty()) { + var current = interfacesToProcess.pop(); + var iterationResult = ifaceVisitor.visitInterface(current, visitResult); + visitResult = iterationResult.value; + if (iterationResult.shouldStop()) { + break; + } + // Add all super interfaces to the queue + for (var superInterface : current.getInterfaces()) { + var superInterfaceElem = processingEnv.getTypeUtils().asElement(superInterface); + if (superInterfaceElem instanceof TypeElement superInterfaceTypeElem) { + interfacesToProcess.add(superInterfaceTypeElem); + } + } + } + return visitResult; + } } From 9bccf1c0ec80c552650abad8d6f411ae04b2a179 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 1 Nov 2024 19:50:09 +0100 Subject: [PATCH 57/93] Iteration over super interfaces is handled only via SuperInterfaceVisitor --- .../processor/IRNodeClassGenerator.java | 2 +- .../enso/runtime/parser/processor/Utils.java | 93 +++++++++---------- 2 files changed, 44 insertions(+), 51 deletions(-) diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java index 85acfdbf40ad..73729b710a24 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java @@ -111,7 +111,7 @@ public IterationResult visitInterface( return new Continue<>(ignored); } }; - return Utils.iterateSuperInterfaces(interfaceType, processingEnv, ifaceVisitor); + return Utils.iterateSuperInterfaces(interfaceType, processingEnv, ifaceVisitor, null); } /** Returns simple name of the generated class. */ diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java index 104e1598ec21..1c23256d3323 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java @@ -2,7 +2,6 @@ import java.lang.annotation.Annotation; import java.util.ArrayDeque; -import java.util.function.Consumer; import java.util.stream.Collectors; import javax.annotation.processing.Messager; import javax.annotation.processing.ProcessingEnvironment; @@ -12,25 +11,27 @@ import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; import javax.tools.Diagnostic.Kind; +import org.enso.runtime.parser.processor.InterfaceHierarchyVisitor.Continue; +import org.enso.runtime.parser.processor.InterfaceHierarchyVisitor.Stop; final class Utils { private Utils() {} /** Returns true if the given {@code type} is a subtype of {@code org.enso.compiler.core.IR}. */ static boolean isSubtypeOfIR(TypeElement type, ProcessingEnvironment processingEnv) { - boolean irEncountered[] = {false}; - iterateSuperInterfaces( + return iterateSuperInterfaces( type, processingEnv, - superInterface -> { + (TypeElement iface, Boolean currResult) -> { // current.getQualifiedName().toString() returns only "IR" as well, so we can't use it. // This is because runtime-parser-processor project does not depend on runtime-parser and // so the org.enso.compiler.core.IR interface is not available in the classpath. - if (superInterface.getSimpleName().toString().equals("IR")) { - irEncountered[0] = true; + if (iface.getSimpleName().toString().equals("IR")) { + return new Stop<>(true); } - }); - return irEncountered[0]; + return new Continue<>(currResult); + }, + false); } /** Returns true if the given {@code type} is an {@code org.enso.compiler.core.IR} interface. */ @@ -74,21 +75,21 @@ static boolean isScalaList(TypeElement type, ProcessingEnvironment procEnv) { */ static boolean hasDefaultImplementation( ExecutableElement method, TypeElement interfaceType, ProcessingEnvironment procEnv) { - boolean defaultMethodEncountered[] = {false}; - iterateSuperInterfaces( + return iterateSuperInterfaces( interfaceType, procEnv, - superInterface -> { + (TypeElement superInterface, Boolean currResult) -> { for (var enclosedElem : superInterface.getEnclosedElements()) { if (enclosedElem instanceof ExecutableElement executableElem) { if (executableElem.getSimpleName().equals(method.getSimpleName()) && executableElem.isDefault()) { - defaultMethodEncountered[0] = true; + return new Stop<>(true); } } } - }); - return defaultMethodEncountered[0]; + return new Continue<>(currResult); + }, + false); } /** @@ -102,26 +103,27 @@ static boolean hasDefaultImplementation( */ static ExecutableElement findDuplicateMethod( TypeElement interfaceType, ProcessingEnvironment procEnv) { - ExecutableElement[] duplicateMethod = {null}; - iterateSuperInterfaces( - interfaceType, - procEnv, - superInterface -> { - for (var enclosedElem : superInterface.getEnclosedElements()) { - if (enclosedElem instanceof ExecutableElement execElem) { - if (isDuplicateMethod(execElem)) { - if (duplicateMethod[0] == null) { - duplicateMethod[0] = execElem; + var duplicateMethod = + iterateSuperInterfaces( + interfaceType, + procEnv, + (TypeElement superInterface, ExecutableElement ignored) -> { + for (var enclosedElem : superInterface.getEnclosedElements()) { + if (enclosedElem instanceof ExecutableElement execElem) { + if (isDuplicateMethod(execElem)) { + return new Stop<>(execElem); + } } } - } - } - }); - assert duplicateMethod[0] != null - : "Interface " + return new Continue<>(ignored); + }, + null); + hardAssert( + duplicateMethod != null, + "Interface " + interfaceType.getQualifiedName() - + " must implement IR, so it must declare duplicate method"; - return duplicateMethod[0]; + + " must implement IR, so it must declare duplicate method"); + return duplicateMethod; } static void hardAssert(boolean condition, String msg) { @@ -139,30 +141,21 @@ private static boolean isDuplicateMethod(ExecutableElement executableElement) { && executableElement.getParameters().size() == 4; } - static void iterateSuperInterfaces( - TypeElement type, ProcessingEnvironment processingEnv, Consumer consumer) { - var interfacesToProcess = new ArrayDeque(); - interfacesToProcess.add(type); - while (!interfacesToProcess.isEmpty()) { - var current = interfacesToProcess.pop(); - consumer.accept(current); - // Add all super interfaces to the queue - for (var superInterface : current.getInterfaces()) { - var superInterfaceElem = processingEnv.getTypeUtils().asElement(superInterface); - if (superInterfaceElem instanceof TypeElement superInterfaceTypeElem) { - interfacesToProcess.add(superInterfaceTypeElem); - } - } - } - } - + /** + * @param type Type from which the iterations starts. + * @param processingEnv + * @param ifaceVisitor Visitor that is called for each interface. + * @param initialResult Initial result set for the visitor. May be null. + * @param + */ static T iterateSuperInterfaces( TypeElement type, ProcessingEnvironment processingEnv, - InterfaceHierarchyVisitor ifaceVisitor) { + InterfaceHierarchyVisitor ifaceVisitor, + T initialResult) { var interfacesToProcess = new ArrayDeque(); interfacesToProcess.add(type); - T visitResult = null; + T visitResult = initialResult; while (!interfacesToProcess.isEmpty()) { var current = interfacesToProcess.pop(); var iterationResult = ifaceVisitor.visitInterface(current, visitResult); From f027307394746da81330ed13e503e7f94618d7a3 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 1 Nov 2024 19:57:58 +0100 Subject: [PATCH 58/93] Simplify super interface iteration --- .../processor/IRNodeClassGenerator.java | 23 +++-- .../processor/InterfaceHierarchyVisitor.java | 42 +--------- .../enso/runtime/parser/processor/Utils.java | 83 +++++++++---------- 3 files changed, 53 insertions(+), 95 deletions(-) diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java index 73729b710a24..64aa7791552e 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java @@ -97,21 +97,18 @@ final class IRNodeClassGenerator { * @return null if non found. */ private ExecutableElement findCopyMethod() { - var ifaceVisitor = - new InterfaceHierarchyVisitor() { - @Override - public IterationResult visitInterface( - TypeElement interfaceElem, ExecutableElement ignored) { - for (var enclosedElem : interfaceElem.getEnclosedElements()) { - if (enclosedElem instanceof ExecutableElement executableElem - && Utils.hasAnnotation(executableElem, IRCopyMethod.class)) { - return new Stop<>(executableElem); - } + return Utils.iterateSuperInterfaces( + interfaceType, + processingEnv, + (TypeElement iface) -> { + for (var enclosedElem : iface.getEnclosedElements()) { + if (enclosedElem instanceof ExecutableElement executableElem + && Utils.hasAnnotation(executableElem, IRCopyMethod.class)) { + return executableElem; } - return new Continue<>(ignored); } - }; - return Utils.iterateSuperInterfaces(interfaceType, processingEnv, ifaceVisitor, null); + return null; + }); } /** Returns simple name of the generated class. */ diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/InterfaceHierarchyVisitor.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/InterfaceHierarchyVisitor.java index 05091bb4a3a0..d4ecc9d9d2e0 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/InterfaceHierarchyVisitor.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/InterfaceHierarchyVisitor.java @@ -12,44 +12,8 @@ interface InterfaceHierarchyVisitor { * Visits the interface hierarchy of the given interface. * * @param iface the interface to visit - * @param currResult the current result of the previous visit. Can be null if this is the first - * visit. - * @return the result of the visit that may signalize whether the iteration should continue or - * stop. + * @return If not-null, the iteration is stopped and the value is returned. If null, the iteration + * continues. */ - IterationResult visitInterface(TypeElement iface, T currResult); - - abstract class IterationResult { - final T value; - - IterationResult(T value) { - this.value = value; - } - - abstract boolean shouldStop(); - } - - final class Continue extends IterationResult { - - Continue(T value) { - super(value); - } - - @Override - public boolean shouldStop() { - return false; - } - } - - final class Stop extends IterationResult { - - Stop(T value) { - super(value); - } - - @Override - public boolean shouldStop() { - return false; - } - } + T visitInterface(TypeElement iface); } diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java index 1c23256d3323..1fbe9755207e 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java @@ -11,27 +11,28 @@ import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; import javax.tools.Diagnostic.Kind; -import org.enso.runtime.parser.processor.InterfaceHierarchyVisitor.Continue; -import org.enso.runtime.parser.processor.InterfaceHierarchyVisitor.Stop; final class Utils { private Utils() {} /** Returns true if the given {@code type} is a subtype of {@code org.enso.compiler.core.IR}. */ static boolean isSubtypeOfIR(TypeElement type, ProcessingEnvironment processingEnv) { - return iterateSuperInterfaces( - type, - processingEnv, - (TypeElement iface, Boolean currResult) -> { - // current.getQualifiedName().toString() returns only "IR" as well, so we can't use it. - // This is because runtime-parser-processor project does not depend on runtime-parser and - // so the org.enso.compiler.core.IR interface is not available in the classpath. - if (iface.getSimpleName().toString().equals("IR")) { - return new Stop<>(true); - } - return new Continue<>(currResult); - }, - false); + var irIfaceFound = + iterateSuperInterfaces( + type, + processingEnv, + (TypeElement iface) -> { + // current.getQualifiedName().toString() returns only "IR" as well, so we can't use + // it. + // This is because runtime-parser-processor project does not depend on runtime-parser + // and + // so the org.enso.compiler.core.IR interface is not available in the classpath. + if (iface.getSimpleName().toString().equals("IR")) { + return true; + } + return null; + }); + return irIfaceFound != null; } /** Returns true if the given {@code type} is an {@code org.enso.compiler.core.IR} interface. */ @@ -75,21 +76,22 @@ static boolean isScalaList(TypeElement type, ProcessingEnvironment procEnv) { */ static boolean hasDefaultImplementation( ExecutableElement method, TypeElement interfaceType, ProcessingEnvironment procEnv) { - return iterateSuperInterfaces( - interfaceType, - procEnv, - (TypeElement superInterface, Boolean currResult) -> { - for (var enclosedElem : superInterface.getEnclosedElements()) { - if (enclosedElem instanceof ExecutableElement executableElem) { - if (executableElem.getSimpleName().equals(method.getSimpleName()) - && executableElem.isDefault()) { - return new Stop<>(true); + var defImplFound = + iterateSuperInterfaces( + interfaceType, + procEnv, + (TypeElement superInterface) -> { + for (var enclosedElem : superInterface.getEnclosedElements()) { + if (enclosedElem instanceof ExecutableElement executableElem) { + if (executableElem.getSimpleName().equals(method.getSimpleName()) + && executableElem.isDefault()) { + return true; + } + } } - } - } - return new Continue<>(currResult); - }, - false); + return null; + }); + return defImplFound != null; } /** @@ -107,17 +109,16 @@ static ExecutableElement findDuplicateMethod( iterateSuperInterfaces( interfaceType, procEnv, - (TypeElement superInterface, ExecutableElement ignored) -> { + (TypeElement superInterface) -> { for (var enclosedElem : superInterface.getEnclosedElements()) { if (enclosedElem instanceof ExecutableElement execElem) { if (isDuplicateMethod(execElem)) { - return new Stop<>(execElem); + return execElem; } } } - return new Continue<>(ignored); - }, - null); + return null; + }); hardAssert( duplicateMethod != null, "Interface " @@ -145,23 +146,19 @@ private static boolean isDuplicateMethod(ExecutableElement executableElement) { * @param type Type from which the iterations starts. * @param processingEnv * @param ifaceVisitor Visitor that is called for each interface. - * @param initialResult Initial result set for the visitor. May be null. * @param */ static T iterateSuperInterfaces( TypeElement type, ProcessingEnvironment processingEnv, - InterfaceHierarchyVisitor ifaceVisitor, - T initialResult) { + InterfaceHierarchyVisitor ifaceVisitor) { var interfacesToProcess = new ArrayDeque(); interfacesToProcess.add(type); - T visitResult = initialResult; while (!interfacesToProcess.isEmpty()) { var current = interfacesToProcess.pop(); - var iterationResult = ifaceVisitor.visitInterface(current, visitResult); - visitResult = iterationResult.value; - if (iterationResult.shouldStop()) { - break; + var iterationResult = ifaceVisitor.visitInterface(current); + if (iterationResult != null) { + return iterationResult; } // Add all super interfaces to the queue for (var superInterface : current.getInterfaces()) { @@ -171,6 +168,6 @@ static T iterateSuperInterfaces( } } } - return visitResult; + return null; } } From 687d0e1fe288068da861339b6b893cfd992c5049 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 1 Nov 2024 20:39:58 +0100 Subject: [PATCH 59/93] Builder has a copy constructor --- .../processor/BuilderMethodGenerator.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/BuilderMethodGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/BuilderMethodGenerator.java index d29bb640cbcf..96399e860cfa 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/BuilderMethodGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/BuilderMethodGenerator.java @@ -60,6 +60,10 @@ String generateBuilder() { public static final class Builder { $fieldDeclarations + Builder() {} + + $copyConstructor + $fieldSetters public $className build() { @@ -73,10 +77,30 @@ private void validate() { } """ .replace("$fieldDeclarations", fieldDeclarations) + .replace("$copyConstructor", copyConstructor()) .replace("$fieldSetters", fieldSetters) .replace("$className", generatedClassContext.getClassName()) .replace("$fieldList", fieldList) .replace("$validationCode", Utils.indent(validationCode, 2)); return Utils.indent(code, 2); } + + private String copyConstructor() { + var sb = new StringBuilder(); + sb.append("/** Copy constructor */").append(System.lineSeparator()); + sb.append("Builder(") + .append(generatedClassContext.getClassName()) + .append(" from) {") + .append(System.lineSeparator()); + for (var classField : generatedClassContext.getAllFields()) { + sb.append(" this.") + .append(classField.name()) + .append(" = from.") + .append(classField.name()) + .append(";") + .append(System.lineSeparator()); + } + sb.append("}").append(System.lineSeparator()); + return sb.toString(); + } } From bd9b7e4a9177b4dfb3a4e25ba6d9e8ad6bafafd5 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 1 Nov 2024 20:40:24 +0100 Subject: [PATCH 60/93] Implement CopyMethodGenerator --- .../parser/processor/CopyMethodGenerator.java | 76 +++++++++++++++++-- .../processor/IRNodeClassGenerator.java | 4 +- .../enso/runtime/parser/processor/Utils.java | 4 + 3 files changed, 76 insertions(+), 8 deletions(-) diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/CopyMethodGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/CopyMethodGenerator.java index 10ba2397fa75..f96a3c22b414 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/CopyMethodGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/CopyMethodGenerator.java @@ -1,21 +1,48 @@ package org.enso.runtime.parser.processor; -import java.util.List; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; +import java.util.stream.Collectors; import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.VariableElement; import org.enso.runtime.parser.dsl.IRCopyMethod; +import org.enso.runtime.parser.processor.GeneratedClassContext.ClassField; class CopyMethodGenerator { private final ExecutableElement copyMethod; - private final List userDefinedFields; + private final GeneratedClassContext ctx; + private final Map parameterMapping = new HashMap<>(); - CopyMethodGenerator(ExecutableElement copyMethod, List userDefinedFields) { + CopyMethodGenerator(ExecutableElement copyMethod, GeneratedClassContext ctx) { + this.ctx = ctx; ensureIsAnnotated(copyMethod); this.copyMethod = Objects.requireNonNull(copyMethod); - this.userDefinedFields = Objects.requireNonNull(userDefinedFields); for (var parameter : copyMethod.getParameters()) { - throw new UnsupportedOperationException("unimplemented"); + var parName = parameter.getSimpleName(); + var parType = simpleTypeName(parameter); + ctx.getAllFields().stream() + .filter(field -> field.type().equals(parType)) + .filter(field -> field.name().equals(parName.toString())) + .findFirst() + .ifPresentOrElse( + field -> parameterMapping.put(parameter, field), + () -> { + var msg = + "Parameter " + + parName + + " of type " + + parType + + " in the copy method " + + "does not have a corresponding field in the interface " + + ctx.getIrNodeInterface().getQualifiedName().toString() + + ". Ensure there is a parameterless abstract method of the same return" + + " type. For more information, see @IRNode annotation docs."; + Utils.printError(msg, parameter, ctx.getProcessingEnvironment().getMessager()); + throw new IllegalArgumentException(msg); + }); } + Utils.hardAssert(parameterMapping.size() == copyMethod.getParameters().size()); } private static void ensureIsAnnotated(ExecutableElement copyMethod) { @@ -24,7 +51,44 @@ private static void ensureIsAnnotated(ExecutableElement copyMethod) { } } + private String simpleTypeName(VariableElement parameter) { + return ctx.getProcessingEnvironment() + .getTypeUtils() + .asElement(parameter.asType()) + .getSimpleName() + .toString(); + } + String generateCopyMethod() { - throw new UnsupportedOperationException("unimplemented"); + var sb = new StringBuilder(); + sb.append("@Override").append(System.lineSeparator()); + var argList = + parameterMapping.keySet().stream() + .map( + parameter -> simpleTypeName(parameter) + " " + parameter.getSimpleName().toString()) + .collect(Collectors.joining(", ")); + sb.append("public ") + .append(copyMethod.getReturnType()) + .append(" ") + .append(copyMethod.getSimpleName()) + .append("(") + .append(argList) + .append(") {") + .append(System.lineSeparator()); + sb.append(" var builder = new Builder(this);").append(System.lineSeparator()); + for (var entry : parameterMapping.entrySet()) { + var parameter = entry.getKey(); + var classField = entry.getValue(); + sb.append(" builder.") + .append(classField.name()) + .append(" = ") + .append(parameter.getSimpleName()) + .append(";") + .append(System.lineSeparator()); + } + sb.append(" var ret = builder.build();").append(System.lineSeparator()); + sb.append(" return ret;").append(System.lineSeparator()); + sb.append("}").append(System.lineSeparator()); + return sb.toString(); } } diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java index 64aa7791552e..0a54022ab88f 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java @@ -78,7 +78,7 @@ final class IRNodeClassGenerator { new DuplicateMethodGenerator(duplicateMethod, generatedClassContext); this.builderMethodGenerator = new BuilderMethodGenerator(generatedClassContext); if (copyMethod != null) { - this.copyMethodGenerator = new CopyMethodGenerator(copyMethod, userFields); + this.copyMethodGenerator = new CopyMethodGenerator(copyMethod, generatedClassContext); } else { this.copyMethodGenerator = null; } @@ -366,7 +366,7 @@ private String overrideUserDefinedMethods() { */ private String copyMethod() { if (copyMethodGenerator != null) { - copyMethodGenerator.generateCopyMethod(); + return copyMethodGenerator.generateCopyMethod(); } return ""; } diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java index 1fbe9755207e..35fd4a9baeae 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java @@ -127,6 +127,10 @@ static ExecutableElement findDuplicateMethod( return duplicateMethod; } + static void hardAssert(boolean condition) { + hardAssert(condition, "Assertion failed"); + } + static void hardAssert(boolean condition, String msg) { if (!condition) { throw new AssertionError(msg); From 98f4743f6de294a2da1d0b66c679f86c1666ac96 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 1 Nov 2024 20:40:36 +0100 Subject: [PATCH 61/93] Fix all tests --- .../processor/test/TestIRProcessorInline.java | 62 ++++++++++++++++--- 1 file changed, 55 insertions(+), 7 deletions(-) diff --git a/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessorInline.java b/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessorInline.java index da0941fcf1c6..4d18f8a485b5 100644 --- a/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessorInline.java +++ b/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessorInline.java @@ -18,8 +18,9 @@ */ public class TestIRProcessorInline { /** - * Compiles the code given in {@code src} with {@link IRProcessor} and returns the - * contents of the generated java source file. + * Compiles the code given in {@code src} with {@link IRProcessor} and returns the contents of the + * generated java source file. + * * @param name FQN of the Java source file * @param src * @return @@ -111,7 +112,10 @@ public interface JName extends IR {} @Test public void simpleIRNodeWithChild() { - var genSrc = generatedClass("MyIR", """ + var genSrc = + generatedClass( + "MyIR", + """ import org.enso.runtime.parser.dsl.IRNode; import org.enso.runtime.parser.dsl.IRChild; import org.enso.compiler.core.IR; @@ -269,7 +273,7 @@ public void processorDoesNotGenerateOverridenMethods() { @IRNode public interface JName extends IR { String name(); - + interface JQualified extends JName { @Override default String name() { @@ -293,11 +297,11 @@ public void overrideCorrectMethods() { @IRNode public interface JExpression extends IR { - + interface JBlock extends JExpression { boolean suspended(); } - + interface JBinding extends JExpression { String name(); } @@ -320,10 +324,54 @@ public void canOverrideMethodsFromIR() { public interface JName extends IR { @Override JName duplicate(boolean keepLocations, boolean keepMetadata, boolean keepDiagnostics, boolean keepIdentifiers); - + interface JSelf extends JName {} } """); assertThat(src, containsString("JName duplicate")); + assertThat(src, containsString("JSelfGen")); + } + + @Test + public void canDefineCopyMethod_WithUserDefinedField() { + var genSrc = + generatedClass( + "JName", + """ + import org.enso.runtime.parser.dsl.IRNode; + import org.enso.runtime.parser.dsl.IRCopyMethod; + import org.enso.compiler.core.IR; + + @IRNode + public interface JName extends IR { + String nameField(); + + @IRCopyMethod + JName copy(String nameField); + } + """); + assertThat(genSrc, containsString("JName copy(String nameField")); + } + + @Test + public void canDefineCopyMethod_WithMetaField() { + var genSrc = + generatedClass( + "JName", + """ + import org.enso.runtime.parser.dsl.IRNode; + import org.enso.runtime.parser.dsl.IRCopyMethod; + import org.enso.compiler.core.IR; + import org.enso.compiler.core.ir.MetadataStorage; + + @IRNode + public interface JName extends IR { + String nameField(); + + @IRCopyMethod + JName copy(MetadataStorage passData); + } + """); + assertThat(genSrc, containsString("JName copy(MetadataStorage")); } } From 92d90046a2fcc39349bb5559ffb94fda6640db4b Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 1 Nov 2024 21:02:02 +0100 Subject: [PATCH 62/93] Test copy method generation --- .../processor/test/gen/ir/CopyNameTestIR.java | 19 +++++++++++++++++++ .../processor/test/gen/ir/ListTestIR.java | 3 +++ .../processor/test/gen/ir/NameTestIR.java | 1 + .../processor/test/TestGeneratedIR.java | 12 ++++++++++++ 4 files changed, 35 insertions(+) create mode 100644 engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/CopyNameTestIR.java diff --git a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/CopyNameTestIR.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/CopyNameTestIR.java new file mode 100644 index 000000000000..14e1c4fb6d19 --- /dev/null +++ b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/CopyNameTestIR.java @@ -0,0 +1,19 @@ +package org.enso.runtime.parser.processor.test.gen.ir; + +import org.enso.compiler.core.IR; +import org.enso.runtime.parser.dsl.IRChild; +import org.enso.runtime.parser.dsl.IRCopyMethod; +import org.enso.runtime.parser.dsl.IRNode; + +@IRNode +public interface CopyNameTestIR extends IR { + @IRChild + NameTestIR name(); + + /** + * Should generate implementation that will produce the exact same copy with a different name + * field. + */ + @IRCopyMethod + CopyNameTestIR copy(NameTestIR name); +} diff --git a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/ListTestIR.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/ListTestIR.java index 12ff6aea443e..1fd580d00d33 100644 --- a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/ListTestIR.java +++ b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/ListTestIR.java @@ -9,4 +9,7 @@ public interface ListTestIR extends IR { @IRChild List names(); + + @IRChild(required = false) + NameTestIR originalName(); } diff --git a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/NameTestIR.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/NameTestIR.java index 1c5bfad08270..c03a5b069323 100644 --- a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/NameTestIR.java +++ b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/NameTestIR.java @@ -5,5 +5,6 @@ @IRNode public interface NameTestIR extends IR { + String name(); } diff --git a/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestGeneratedIR.java b/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestGeneratedIR.java index cd4de51486b3..8d4b80f28639 100644 --- a/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestGeneratedIR.java +++ b/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestGeneratedIR.java @@ -11,6 +11,7 @@ import org.enso.compiler.core.ir.DiagnosticStorage; import org.enso.compiler.core.ir.IdentifiedLocation; import org.enso.compiler.core.ir.Location; +import org.enso.runtime.parser.processor.test.gen.ir.CopyNameTestIRGen; import org.enso.runtime.parser.processor.test.gen.ir.ListTestIR; import org.enso.runtime.parser.processor.test.gen.ir.ListTestIRGen; import org.enso.runtime.parser.processor.test.gen.ir.NameTestIR; @@ -111,6 +112,17 @@ public void duplicateRespectsParameters() { assertThat("Should have copied diagnostics", duplicated_2.diagnostics(), is(notNullValue())); } + @Test + public void copyMethod() { + var diagnostics = DiagnosticStorage.empty(); + var nameIR = NameTestIRGen.builder().name("name").build(); + var copyNameIR = CopyNameTestIRGen.builder().diagnostics(diagnostics).name(nameIR).build(); + var otherNameIR = NameTestIRGen.builder().name("other_name").build(); + var copied = copyNameIR.copy(otherNameIR); + assertThat(copied.name().name(), is("other_name")); + assertThat("Diagnostics should have been copied", copied.diagnostics(), is(diagnostics)); + } + private static scala.collection.immutable.List asScala(List list) { return scala.jdk.javaapi.CollectionConverters.asScala(list).toList(); } From 69728bee3460109662017727cfca70ea7fe60c1b Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 1 Nov 2024 21:10:54 +0100 Subject: [PATCH 63/93] Do not try to override static or default methods --- .../processor/test/TestIRProcessorInline.java | 19 ++++++++++++++++++ .../parser/processor/FieldCollector.java | 2 +- .../enso/runtime/parser/processor/Utils.java | 20 +++++++++++++------ 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessorInline.java b/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessorInline.java index 4d18f8a485b5..61787446f23f 100644 --- a/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessorInline.java +++ b/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessorInline.java @@ -110,6 +110,25 @@ public interface JName extends IR {} assertThat("Generated just one source", compilation.generatedSourceFiles().size(), is(1)); } + @Test + public void doesNotOverrideStaticParameterlessMethod() { + var src = + generatedClass( + "Hello", + """ + import org.enso.runtime.parser.dsl.IRNode; + import org.enso.compiler.core.IR; + + @IRNode + public interface Hello extends IR { + static String name() { + return "Hello"; + } + } + """); + assertThat(src, not(containsString("\"Hello\""))); + } + @Test public void simpleIRNodeWithChild() { var genSrc = diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/FieldCollector.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/FieldCollector.java index 2615b00114f6..4fe0935b5e2d 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/FieldCollector.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/FieldCollector.java @@ -64,7 +64,7 @@ private void collectFromSingleInterface(TypeElement typeElem) { for (var childElem : typeElem.getEnclosedElements()) { if (childElem instanceof ExecutableElement methodElement) { if (methodElement.getParameters().isEmpty() - && !Utils.hasDefaultImplementation(methodElement, irNodeInterface, processingEnv)) { + && !Utils.hasImplementation(methodElement, irNodeInterface, processingEnv)) { var name = methodElement.getSimpleName().toString(); if (!fields.containsKey(name)) { var field = methodToField(methodElement); diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java index 35fd4a9baeae..009b8940a720 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java @@ -8,6 +8,7 @@ import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; import javax.tools.Diagnostic.Kind; @@ -67,14 +68,15 @@ static boolean isScalaList(TypeElement type, ProcessingEnvironment procEnv) { } /** - * Returns true if the method has a default implementation in some of the super interfaces. + * Returns true if the method has an implementation (is default or static) in some of the super + * interfaces. * * @param method the method to check - * @param interfaceType the interface that declares the method + * @param interfaceType the interface that declares the method to check for the implementation. * @param procEnv * @return */ - static boolean hasDefaultImplementation( + static boolean hasImplementation( ExecutableElement method, TypeElement interfaceType, ProcessingEnvironment procEnv) { var defImplFound = iterateSuperInterfaces( @@ -83,9 +85,11 @@ static boolean hasDefaultImplementation( (TypeElement superInterface) -> { for (var enclosedElem : superInterface.getEnclosedElements()) { if (enclosedElem instanceof ExecutableElement executableElem) { - if (executableElem.getSimpleName().equals(method.getSimpleName()) - && executableElem.isDefault()) { - return true; + if (executableElem.getSimpleName().equals(method.getSimpleName())) { + if (hasModifier(executableElem, Modifier.DEFAULT) + || hasModifier(executableElem, Modifier.STATIC)) { + return true; + } } } } @@ -94,6 +98,10 @@ static boolean hasDefaultImplementation( return defImplFound != null; } + static boolean hasModifier(ExecutableElement method, Modifier modifier) { + return method.getModifiers().contains(modifier); + } + /** * Find any override of {@link org.enso.compiler.core.IR#duplicate(boolean, boolean, boolean, * boolean) duplicate method}. Or the duplicate method on the interface itself. Note that there From 77095977b6786ed844c9c75f75f66d8b43190378 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 1 Nov 2024 21:21:17 +0100 Subject: [PATCH 64/93] docs --- .../runtime/parser/processor/BuilderMethodGenerator.java | 7 +++++++ .../enso/runtime/parser/processor/CopyMethodGenerator.java | 1 + .../runtime/parser/processor/DuplicateMethodGenerator.java | 1 + .../runtime/parser/processor/GeneratedClassContext.java | 5 +++-- .../parser/processor/InterfaceHierarchyVisitor.java | 4 +++- .../main/java/org/enso/runtime/parser/processor/Utils.java | 3 +++ 6 files changed, 18 insertions(+), 3 deletions(-) diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/BuilderMethodGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/BuilderMethodGenerator.java index 96399e860cfa..546a5271b7ab 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/BuilderMethodGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/BuilderMethodGenerator.java @@ -3,6 +3,13 @@ import java.util.stream.Collectors; import org.enso.runtime.parser.processor.GeneratedClassContext.ClassField; +/** + * Code generator for builder. Builder is a nested static class inside the generated class. Builder + * has a validation code that is invoked in {@code build()} method that ensures that all the + * required fields are set. Builder has a copy constructor - a constructor that takes the generated + * class object and prefills all the fields with the values from the object. This copy constructor + * is called from either the {@code duplicate} method or from copy methods. + */ class BuilderMethodGenerator { private final GeneratedClassContext generatedClassContext; diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/CopyMethodGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/CopyMethodGenerator.java index f96a3c22b414..d6cf93b27536 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/CopyMethodGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/CopyMethodGenerator.java @@ -9,6 +9,7 @@ import org.enso.runtime.parser.dsl.IRCopyMethod; import org.enso.runtime.parser.processor.GeneratedClassContext.ClassField; +/** Code generator for methods annotated with {@link IRCopyMethod}. */ class CopyMethodGenerator { private final ExecutableElement copyMethod; private final GeneratedClassContext ctx; diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/DuplicateMethodGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/DuplicateMethodGenerator.java index b89ee5e8e2df..00c96fa4cf62 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/DuplicateMethodGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/DuplicateMethodGenerator.java @@ -9,6 +9,7 @@ /** * Code generator for {@code org.enso.compiler.core.ir.IR#duplicate} method or any of its override. + * Note that in the interface hierarchy, there can be an override with a different return type. */ class DuplicateMethodGenerator { private final ExecutableElement duplicateMethod; diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/GeneratedClassContext.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/GeneratedClassContext.java index cafae8493e07..17866e14e371 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/GeneratedClassContext.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/GeneratedClassContext.java @@ -7,8 +7,8 @@ import javax.lang.model.element.TypeElement; /** - * A context created for the generated class. Everything that is needed for the code generation is - * contained in this class. + * A context created for the generated class. Everything that is needed for the code generation of a + * single class is contained in this class. */ final class GeneratedClassContext { private final String className; @@ -82,6 +82,7 @@ List getUserFields() { return userFields; } + /** Returns simple name of the class that is being generated. */ String getClassName() { return className; } diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/InterfaceHierarchyVisitor.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/InterfaceHierarchyVisitor.java index d4ecc9d9d2e0..e83d09a1eb1d 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/InterfaceHierarchyVisitor.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/InterfaceHierarchyVisitor.java @@ -4,7 +4,9 @@ /** * A visitor for traversing the interface hierarchy of an interface - it iterates over all the super - * interfaces until it encounters {@code org.enso.compiler.ir.IR} interface. + * interfaces until it encounters {@code org.enso.compiler.ir.IR} interface. The iteration can be + * stopped by returning a non-null value from the visitor. Follows a similar pattern as {@link + * com.oracle.truffle.api.frame.FrameInstanceVisitor}. */ @FunctionalInterface interface InterfaceHierarchyVisitor { diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java index 009b8940a720..149112f69aa3 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java @@ -71,6 +71,9 @@ static boolean isScalaList(TypeElement type, ProcessingEnvironment procEnv) { * Returns true if the method has an implementation (is default or static) in some of the super * interfaces. * + *

If the method is implemented in some of the super interfaces, there must not be generated an + * override for it - that would result in compilation error. + * * @param method the method to check * @param interfaceType the interface that declares the method to check for the implementation. * @param procEnv From 7d012fe74769d13d7f4badd71315f89d88029b77 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 4 Nov 2024 20:04:26 +0100 Subject: [PATCH 65/93] Allow multiple copy methods --- .../enso/runtime/parser/dsl/IRCopyMethod.java | 3 ++ .../processor/test/TestIRProcessorInline.java | 26 +++++++++++ .../processor/IRNodeClassGenerator.java | 46 ++++++++++--------- 3 files changed, 53 insertions(+), 22 deletions(-) diff --git a/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/IRCopyMethod.java b/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/IRCopyMethod.java index 42cdbb0c6ebd..9895523cbf54 100644 --- a/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/IRCopyMethod.java +++ b/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/IRCopyMethod.java @@ -19,6 +19,9 @@ * The order of the parameters is not important. Number of parameters must not exceed total number * of fields and meta fields. * + *

There can be more methods annotated with this annotation. All of them must follow the contract + * describe in this docs. For each of those methods, an implementation will be generated. + * *

The name of the annotated method can be arbitrary, but the convention is to use the {@code * copy} name. */ diff --git a/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessorInline.java b/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessorInline.java index 61787446f23f..7da28f8b1f86 100644 --- a/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessorInline.java +++ b/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessorInline.java @@ -393,4 +393,30 @@ public interface JName extends IR { """); assertThat(genSrc, containsString("JName copy(MetadataStorage")); } + + @Test + public void canDefineMultipleCopyMethods() { + var genSrc = + generatedClass( + "JName", + """ + import org.enso.runtime.parser.dsl.IRNode; + import org.enso.runtime.parser.dsl.IRCopyMethod; + import org.enso.compiler.core.IR; + import org.enso.compiler.core.ir.MetadataStorage; + + @IRNode + public interface JName extends IR { + String nameField(); + + @IRCopyMethod + JName copy(MetadataStorage passData); + + @IRCopyMethod + JName copy(String nameField); + } + """); + assertThat(genSrc, containsString("JName copy(MetadataStorage")); + assertThat(genSrc, containsString("JName copy(String")); + } } diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java index 0a54022ab88f..09013409b453 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java @@ -1,5 +1,6 @@ package org.enso.runtime.parser.processor; +import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -39,10 +40,10 @@ final class IRNodeClassGenerator { private final BuilderMethodGenerator builderMethodGenerator; /** - * Can be null if there is no method annotated with {@link - * org.enso.runtime.parser.dsl.IRCopyMethod}. + * For every method annotated with {@link IRCopyMethod}, there is a generator. Can be empty. Not + * null. */ - private final CopyMethodGenerator copyMethodGenerator; + private final List copyMethodGenerators; private static final Set defaultImportedTypes = Set.of( @@ -71,17 +72,15 @@ final class IRNodeClassGenerator { this.className = className; var userFields = getAllUserFields(interfaceType); var duplicateMethod = Utils.findDuplicateMethod(interfaceType, processingEnv); - var copyMethod = findCopyMethod(); this.generatedClassContext = new GeneratedClassContext(className, userFields, processingEnv, interfaceType); this.duplicateMethodGenerator = new DuplicateMethodGenerator(duplicateMethod, generatedClassContext); this.builderMethodGenerator = new BuilderMethodGenerator(generatedClassContext); - if (copyMethod != null) { - this.copyMethodGenerator = new CopyMethodGenerator(copyMethod, generatedClassContext); - } else { - this.copyMethodGenerator = null; - } + this.copyMethodGenerators = + findCopyMethods().stream() + .map(copyMethod -> new CopyMethodGenerator(copyMethod, generatedClassContext)) + .toList(); var nestedTypes = interfaceType.getEnclosedElements().stream() .filter( @@ -94,21 +93,25 @@ final class IRNodeClassGenerator { } /** - * @return null if non found. + * Finds all the methods annotated with {@link IRCopyMethod} in the interface hierarchy. + * + * @return empty if none. Not null. */ - private ExecutableElement findCopyMethod() { - return Utils.iterateSuperInterfaces( + private List findCopyMethods() { + var copyMethods = new ArrayList(); + Utils.iterateSuperInterfaces( interfaceType, processingEnv, (TypeElement iface) -> { for (var enclosedElem : iface.getEnclosedElements()) { if (enclosedElem instanceof ExecutableElement executableElem && Utils.hasAnnotation(executableElem, IRCopyMethod.class)) { - return executableElem; + copyMethods.add(executableElem); } } return null; }); + return copyMethods; } /** Returns simple name of the generated class. */ @@ -152,7 +155,7 @@ public static Builder builder() { $overrideIRMethods - $copyMethod + $copyMethods $builder """ @@ -160,7 +163,7 @@ public static Builder builder() { .replace("$constructor", constructor()) .replace("$overrideUserDefinedMethods", overrideUserDefinedMethods()) .replace("$overrideIRMethods", overrideIRMethods()) - .replace("$copyMethod", copyMethod()) + .replace("$copyMethods", copyMethods()) .replace("$builder", builderMethodGenerator.generateBuilder()); } @@ -359,16 +362,15 @@ private String overrideUserDefinedMethods() { } /** - * Generates the code for the copy method. The method is generated only if the interface contains - * a method annotated with {@link IRCopyMethod}. In that case, returns an empty string. + * Generates the code for all the copy methods. Returns an empty string if there are no methods + * annotated with {@link IRCopyMethod}. * * @return Code of the copy method or an empty string if the method is not present. */ - private String copyMethod() { - if (copyMethodGenerator != null) { - return copyMethodGenerator.generateCopyMethod(); - } - return ""; + private String copyMethods() { + return copyMethodGenerators.stream() + .map(CopyMethodGenerator::generateCopyMethod) + .collect(Collectors.joining(System.lineSeparator())); } private static String indent(String code, int indentation) { From cb06f03271c8f7de4e57e685473755723f53bfb0 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 4 Nov 2024 20:14:39 +0100 Subject: [PATCH 66/93] CopyMethodGenerator generates copy method with correct order of parameters --- .../processor/test/TestIRProcessorInline.java | 23 +++++++++++++++++++ .../parser/processor/CopyMethodGenerator.java | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessorInline.java b/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessorInline.java index 7da28f8b1f86..17750b5deb50 100644 --- a/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessorInline.java +++ b/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessorInline.java @@ -419,4 +419,27 @@ public interface JName extends IR { assertThat(genSrc, containsString("JName copy(MetadataStorage")); assertThat(genSrc, containsString("JName copy(String")); } + + @Test + public void copyMethod_WithArbitraryArgumentOrder() { + var genSrc = + generatedClass( + "JName", + """ + import org.enso.runtime.parser.dsl.IRNode; + import org.enso.runtime.parser.dsl.IRCopyMethod; + import org.enso.compiler.core.IR; + import org.enso.compiler.core.ir.MetadataStorage; + import org.enso.compiler.core.ir.DiagnosticStorage; + + @IRNode + public interface JName extends IR { + String nameField(); + + @IRCopyMethod + JName copy(String nameField, MetadataStorage passData, DiagnosticStorage diagnostics); + } + """); + assertThat(genSrc, containsString("JName copy(")); + } } diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/CopyMethodGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/CopyMethodGenerator.java index d6cf93b27536..1251c13200ad 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/CopyMethodGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/CopyMethodGenerator.java @@ -64,7 +64,7 @@ String generateCopyMethod() { var sb = new StringBuilder(); sb.append("@Override").append(System.lineSeparator()); var argList = - parameterMapping.keySet().stream() + copyMethod.getParameters().stream() .map( parameter -> simpleTypeName(parameter) + " " + parameter.getSimpleName().toString()) .collect(Collectors.joining(", ")); From c08c035ce94306059a44284450f0d64e6d0ab3bb Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 4 Nov 2024 20:16:03 +0100 Subject: [PATCH 67/93] Test valid parameter names for copy method --- .../processor/test/TestIRProcessorInline.java | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessorInline.java b/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessorInline.java index 17750b5deb50..91c5ee92b45d 100644 --- a/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessorInline.java +++ b/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessorInline.java @@ -39,6 +39,13 @@ private static String generatedClass(String name, String src) { } } + private static void expectCompilationFailure(String src) { + var srcObject = JavaFileObjects.forSourceString("TestHello", src); + var compiler = Compiler.javac().withProcessors(new IRProcessor()); + var compilation = compiler.compile(srcObject); + CompilationSubject.assertThat(compilation).failed(); + } + @Test public void simpleIRNodeWithoutChildren_CompilationSucceeds() { var src = @@ -442,4 +449,44 @@ public interface JName extends IR { """); assertThat(genSrc, containsString("JName copy(")); } + + @Test + public void copyMethod_MustContainValidFieldsAsParameters_1() { + expectCompilationFailure( + """ + import org.enso.runtime.parser.dsl.IRNode; + import org.enso.runtime.parser.dsl.IRCopyMethod; + import org.enso.compiler.core.IR; + import org.enso.compiler.core.ir.MetadataStorage; + import org.enso.compiler.core.ir.DiagnosticStorage; + + @IRNode + public interface JName extends IR { + String nameField(); + + @IRCopyMethod + JName copy(String NON_EXISTING_FIELD_NAME); + } + """); + } + + @Test + public void copyMethod_MustContainValidFieldsAsParameters_2() { + expectCompilationFailure( + """ + import org.enso.runtime.parser.dsl.IRNode; + import org.enso.runtime.parser.dsl.IRCopyMethod; + import org.enso.compiler.core.IR; + import org.enso.compiler.core.ir.MetadataStorage; + import org.enso.compiler.core.ir.DiagnosticStorage; + + @IRNode + public interface JName extends IR { + String nameField(); + + @IRCopyMethod + JName copy(String nameField, String ANOTHER_NON_EXISTING); + } + """); + } } From 43ed0456822ca5fdd25a70f217a53cb34ac5930f Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 4 Nov 2024 20:17:30 +0100 Subject: [PATCH 68/93] More tests --- .../processor/test/TestIRProcessorInline.java | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessorInline.java b/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessorInline.java index 91c5ee92b45d..8df60a152163 100644 --- a/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessorInline.java +++ b/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessorInline.java @@ -92,6 +92,22 @@ public class Hello {} CompilationSubject.assertThat(compilation).failed(); } + @Test + public void childAnnotation_MustBeAppliedToIRField() { + expectCompilationFailure( + """ + import org.enso.runtime.parser.dsl.IRNode; + import org.enso.runtime.parser.dsl.IRChild; + import org.enso.compiler.core.IR; + import org.enso.compiler.core.ir.JExpression; + + @IRNode + public interface MyIR extends IR { + @IRChild String expression(); + } + """); + } + @Test public void simpleIRNodeWithoutChildren_GeneratesSource() { var src = @@ -489,4 +505,28 @@ public interface JName extends IR { } """); } + + @Test + public void copyMethod_WithMoreFieldsOfSameType() { + var genSrc = + generatedClass( + "JName", + """ + import org.enso.runtime.parser.dsl.IRNode; + import org.enso.runtime.parser.dsl.IRCopyMethod; + import org.enso.compiler.core.IR; + import org.enso.compiler.core.ir.MetadataStorage; + import org.enso.compiler.core.ir.DiagnosticStorage; + + @IRNode + public interface JName extends IR { + String nameField_1(); + String nameField_2(); + + @IRCopyMethod + JName copy(String nameField_1, String nameField_2); + } + """); + assertThat(genSrc, containsString("JName copy(")); + } } From 69258cf61cbfcbcd8041c8cfdd7bd12b24edae9f Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Tue, 5 Nov 2024 21:07:35 +0100 Subject: [PATCH 69/93] Generate mapExpressions method --- .../processor/test/TestIRProcessorInline.java | 25 +++- .../enso/runtime/parser/processor/Field.java | 4 + .../parser/processor/FieldCollector.java | 2 +- .../processor/GeneratedClassContext.java | 2 +- .../processor/IRNodeClassGenerator.java | 16 ++- .../runtime/parser/processor/ListField.java | 9 +- .../MapExpressionsMethodGenerator.java | 132 ++++++++++++++++++ .../parser/processor/PrimitiveField.java | 6 + .../parser/processor/ReferenceField.java | 5 + .../enso/runtime/parser/processor/Utils.java | 36 ++++- 10 files changed, 226 insertions(+), 11 deletions(-) create mode 100644 engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/MapExpressionsMethodGenerator.java diff --git a/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessorInline.java b/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessorInline.java index 8df60a152163..602403343e33 100644 --- a/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessorInline.java +++ b/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessorInline.java @@ -94,8 +94,8 @@ public class Hello {} @Test public void childAnnotation_MustBeAppliedToIRField() { - expectCompilationFailure( - """ + expectCompilationFailure( + """ import org.enso.runtime.parser.dsl.IRNode; import org.enso.runtime.parser.dsl.IRChild; import org.enso.compiler.core.IR; @@ -529,4 +529,25 @@ public interface JName extends IR { """); assertThat(genSrc, containsString("JName copy(")); } + + @Test + public void mapExpressions_CanOverride() { + var genSrc = + generatedClass( + "JExpression", + """ + import org.enso.runtime.parser.dsl.IRNode; + import org.enso.compiler.core.IR; + import org.enso.compiler.core.ir.Expression; + import java.util.function.Function; + + @IRNode + public interface JExpression extends IR { + + @Override + JExpression mapExpressions(Function fn); + } + """); + assertThat(genSrc, containsString("JExpression mapExpressions(")); + } } diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Field.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Field.java index 11c4d7a0b19b..42c08d1fc062 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Field.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Field.java @@ -2,6 +2,7 @@ import java.util.List; import java.util.function.Function; +import javax.lang.model.element.TypeElement; import org.enso.runtime.parser.dsl.IRChild; /** @@ -13,6 +14,9 @@ interface Field { /** Name (identifier) of the field. */ String getName(); + /** Returns type of this field. Null if this is a primitive field. */ + TypeElement getType(); + /** * Does not return null. If the type is generic, the type parameter is included in the name. * Returns non-qualified name. diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/FieldCollector.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/FieldCollector.java index 4fe0935b5e2d..2612a5068670 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/FieldCollector.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/FieldCollector.java @@ -97,7 +97,7 @@ private Field methodToField(ExecutableElement methodElement) { var typeArg = declaredRetType.getTypeArguments().get(0); var typeArgElem = (TypeElement) processingEnv.getTypeUtils().asElement(typeArg); ensureIsSubtypeOfIR(typeArgElem); - return new ListField(name, typeArgElem); + return new ListField(name, retTypeElem, typeArgElem); } boolean isNullable = !childAnnot.required(); diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/GeneratedClassContext.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/GeneratedClassContext.java index 17866e14e371..a73c3d4566cb 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/GeneratedClassContext.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/GeneratedClassContext.java @@ -43,7 +43,7 @@ final class GeneratedClassContext { TypeElement irNodeInterface) { this.className = Objects.requireNonNull(className); this.userFields = Objects.requireNonNull(userFields); - this.processingEnvironment = processingEnvironment; + this.processingEnvironment = Objects.requireNonNull(processingEnvironment); this.irNodeInterface = irNodeInterface; ensureSimpleName(className); this.constructorParameters = diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java index 09013409b453..107da29f17e0 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java @@ -38,6 +38,7 @@ final class IRNodeClassGenerator { private final GeneratedClassContext generatedClassContext; private final DuplicateMethodGenerator duplicateMethodGenerator; private final BuilderMethodGenerator builderMethodGenerator; + private final MapExpressionsMethodGenerator mapExpressionsMethodGenerator; /** * For every method annotated with {@link IRCopyMethod}, there is a generator. Can be empty. Not @@ -77,6 +78,9 @@ final class IRNodeClassGenerator { this.duplicateMethodGenerator = new DuplicateMethodGenerator(duplicateMethod, generatedClassContext); this.builderMethodGenerator = new BuilderMethodGenerator(generatedClassContext); + var mapExpressionsMethod = Utils.findMapExpressionsMethod(interfaceType, processingEnv); + this.mapExpressionsMethodGenerator = + new MapExpressionsMethodGenerator(mapExpressionsMethod, generatedClassContext); this.copyMethodGenerators = findCopyMethods().stream() .map(copyMethod -> new CopyMethodGenerator(copyMethod, generatedClassContext)) @@ -155,6 +159,8 @@ public static Builder builder() { $overrideIRMethods + $mapExpressionsMethod + $copyMethods $builder @@ -163,6 +169,7 @@ public static Builder builder() { .replace("$constructor", constructor()) .replace("$overrideUserDefinedMethods", overrideUserDefinedMethods()) .replace("$overrideIRMethods", overrideIRMethods()) + .replace("$mapExpressionsMethod", mapExpressions()) .replace("$copyMethods", copyMethods()) .replace("$builder", builderMethodGenerator.generateBuilder()); } @@ -295,11 +302,6 @@ public IR setLocation(Option location) { throw new UnsupportedOperationException("unimplemented"); } - @Override - public IR mapExpressions(Function fn) { - throw new UnsupportedOperationException("unimplemented"); - } - @Override public scala.collection.immutable.List children() { $childrenMethodBody @@ -373,6 +375,10 @@ private String copyMethods() { .collect(Collectors.joining(System.lineSeparator())); } + private String mapExpressions() { + return Utils.indent(mapExpressionsMethodGenerator.generateMapExpressionsMethodCode(), 2); + } + private static String indent(String code, int indentation) { return code.lines() .map(line -> " ".repeat(indentation) + line) diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ListField.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ListField.java index a3c483441c9d..91a87298a79c 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ListField.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ListField.java @@ -7,13 +7,15 @@ final class ListField implements Field { private final String name; private final TypeElement typeArgElement; + private final TypeElement type; /** * @param name Name of the field * @param typeArgElement TypeElement of the type argument. Must be subtype of IR. */ - ListField(String name, TypeElement typeArgElement) { + ListField(String name, TypeElement type, TypeElement typeArgElement) { this.name = name; + this.type = type; this.typeArgElement = typeArgElement; } @@ -22,6 +24,11 @@ public String getName() { return name; } + @Override + public TypeElement getType() { + return type; + } + @Override public String getSimpleTypeName() { var typeArg = typeArgElement.getSimpleName().toString(); diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/MapExpressionsMethodGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/MapExpressionsMethodGenerator.java new file mode 100644 index 000000000000..7865e3077bf2 --- /dev/null +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/MapExpressionsMethodGenerator.java @@ -0,0 +1,132 @@ +package org.enso.runtime.parser.processor; + +import java.util.Objects; +import javax.lang.model.element.ExecutableElement; + +final class MapExpressionsMethodGenerator { + private final ExecutableElement mapExpressionsMethod; + private final GeneratedClassContext ctx; + private static final String METHOD_NAME = "mapExpressions"; + + /** + * @param mapExpressionsMethod Reference to {@code mapExpressions} method in the interface for + * which the class is generated. + * @param ctx + */ + MapExpressionsMethodGenerator(ExecutableElement mapExpressionsMethod, GeneratedClassContext ctx) { + ensureMapExpressionsMethodHasExpectedSignature(mapExpressionsMethod); + this.mapExpressionsMethod = mapExpressionsMethod; + this.ctx = Objects.requireNonNull(ctx); + } + + private void ensureMapExpressionsMethodHasExpectedSignature( + ExecutableElement mapExpressionsMethod) { + var parameters = mapExpressionsMethod.getParameters(); + if (parameters.size() != 1) { + Utils.printErrorAndFail( + "Map expressions method must have 1 parameter", + mapExpressionsMethod, + ctx.getProcessingEnvironment().getMessager()); + } + } + + String generateMapExpressionsMethodCode() { + var sb = new StringBuilder(); + sb.append("@Override").append(System.lineSeparator()); + sb.append("public ") + .append(mapExpressionsMethod.getReturnType()) + .append(" ") + .append(METHOD_NAME) + .append("(") + .append("Function fn") + .append(") {") + .append(System.lineSeparator()); + + var children = ctx.getUserFields().stream().filter(field -> field.isChild() && !field.isList()); + var newChildren = + children.map( + child -> { + Utils.hardAssert(!child.isList()); + var childsMapExprMethod = + Utils.findMapExpressionsMethod(child.getType(), ctx.getProcessingEnvironment()); + var typeUtils = ctx.getProcessingEnvironment().getTypeUtils(); + var childsMapExprMethodRetType = + typeUtils.asElement(childsMapExprMethod.getReturnType()); + var shouldCast = + !typeUtils.isSameType( + child.getType().asType(), childsMapExprMethodRetType.asType()); + + var newChildName = child.getName() + "Mapped"; + sb.append(" ") + .append(childsMapExprMethodRetType.getSimpleName()) + .append(" ") + .append(newChildName); + if (child.isNullable()) { + sb.append(" = null;").append(System.lineSeparator()); + sb.append(" if (") + .append(child.getName()) + .append(" != null) {") + .append(System.lineSeparator()); + // childMapped = child.mapExpressions(fn); + sb.append(" ") + .append(newChildName) + .append(".") + .append(METHOD_NAME) + .append("(fn);") + .append(System.lineSeparator()); + sb.append(" }").append(System.lineSeparator()); + } else { + if (!child.isList()) { + // ChildType childMapped = child.mapExpressions(fn); + sb.append(" = ") + .append(child.getName()) + .append(".") + .append(METHOD_NAME) + .append("(fn);") + .append(System.lineSeparator()); + } else { + Utils.hardAssert(child.isList() && !child.isNullable()); + // List childMapped = child.map(e -> e.mapExpressions(fn)); + sb.append(" = ") + .append(child.getName()) + .append(".map(e -> e.") + .append(METHOD_NAME) + .append("(fn));") + .append(System.lineSeparator()); + } + } + return new MappedChild(newChildName, child, shouldCast); + }); + sb.append(" ").append("var bldr = new Builder(this);").append(System.lineSeparator()); + newChildren.forEach( + newChild -> { + if (newChild.shouldCast) { + sb.append(" ") + .append("if (!(") + .append(newChild.newChildName) + .append(" instanceof ") + .append(newChild.child.getType().getSimpleName()) + .append(")) {") + .append(System.lineSeparator()); + sb.append(" ") + .append( + "throw new IllegalStateException(\"Duplicated child is not of the expected" + + " type: \" + ") + .append(newChild.newChildName) + .append(");") + .append(System.lineSeparator()); + sb.append(" }").append(System.lineSeparator()); + } + sb.append(" ").append("bldr.").append(newChild.child.getName()).append("("); + if (newChild.shouldCast) { + sb.append("(").append(newChild.child.getType().getSimpleName()).append(") "); + } + sb.append(newChild.newChildName).append(");").append(System.lineSeparator()); + }); + sb.append(" return bldr.build();").append(System.lineSeparator()); + sb.append("}").append(System.lineSeparator()); + return sb.toString(); + } + + private record MappedChild(String newChildName, Field child, boolean shouldCast) {} +} diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/PrimitiveField.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/PrimitiveField.java index 75acf0bf9ef7..e7f5e40d9d95 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/PrimitiveField.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/PrimitiveField.java @@ -1,5 +1,6 @@ package org.enso.runtime.parser.processor; +import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; final class PrimitiveField implements Field { @@ -17,6 +18,11 @@ public String getName() { return name; } + @Override + public TypeElement getType() { + return null; + } + @Override public String getSimpleTypeName() { return type.toString(); diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ReferenceField.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ReferenceField.java index 7a8af4d4bd65..c3cc5861ba04 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ReferenceField.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ReferenceField.java @@ -29,6 +29,11 @@ public String getName() { return name; } + @Override + public TypeElement getType() { + return type; + } + @Override public String getSimpleTypeName() { return type.getSimpleName().toString(); diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java index 149112f69aa3..edcd37f75ea3 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java @@ -14,6 +14,10 @@ import javax.tools.Diagnostic.Kind; final class Utils { + + private static final String MAP_EXPRESSIONS = "mapExpressions"; + private static final String DUPLICATE = "duplicate"; + private Utils() {} /** Returns true if the given {@code type} is a subtype of {@code org.enso.compiler.core.IR}. */ @@ -56,6 +60,11 @@ static void printError(String msg, Element elem, Messager messager) { messager.printMessage(Kind.ERROR, msg, elem); } + static void printErrorAndFail(String msg, Element elem, Messager messager) { + printError(msg, elem, messager); + throw new IllegalStateException("Unexpected failure during annotation processing: " + msg); + } + static String indent(String code, int indentation) { return code.lines() .map(line -> " ".repeat(indentation) + line) @@ -138,6 +147,31 @@ static ExecutableElement findDuplicateMethod( return duplicateMethod; } + static ExecutableElement findMapExpressionsMethod( + TypeElement interfaceType, ProcessingEnvironment processingEnv) { + var mapExprsMethod = + Utils.iterateSuperInterfaces( + interfaceType, + processingEnv, + iface -> { + // Filter only ExecutableElement from iface.getEnclosedElements and convert the stream + // to Stream + var declaredMethods = + iface.getEnclosedElements().stream() + .filter(elem -> elem instanceof ExecutableElement) + .map(elem -> (ExecutableElement) elem); + var mapExprMethod = + declaredMethods + .filter(elem -> elem.getSimpleName().toString().equals(MAP_EXPRESSIONS)) + .findFirst(); + return mapExprMethod.orElse(null); + }); + hardAssert( + mapExprsMethod != null, + "mapExpressions method must be found it must be defined at least on IR super interface"); + return mapExprsMethod; + } + static void hardAssert(boolean condition) { hardAssert(condition, "Assertion failed"); } @@ -153,7 +187,7 @@ static boolean hasAnnotation(Element element, Class annota } private static boolean isDuplicateMethod(ExecutableElement executableElement) { - return executableElement.getSimpleName().toString().equals("duplicate") + return executableElement.getSimpleName().toString().equals(DUPLICATE) && executableElement.getParameters().size() == 4; } From f888cee47b052107fa2705bca730bd8753bba92f Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Tue, 5 Nov 2024 21:07:47 +0100 Subject: [PATCH 70/93] Add mock definition of JCallArgument --- .../org/enso/compiler/core/ir/JCallArgument.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JCallArgument.java diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JCallArgument.java b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JCallArgument.java new file mode 100644 index 000000000000..f4c7804210be --- /dev/null +++ b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JCallArgument.java @@ -0,0 +1,16 @@ +package org.enso.compiler.core.ir; + +import org.enso.compiler.core.IR; +import org.enso.runtime.parser.dsl.IRChild; +import org.enso.runtime.parser.dsl.IRNode; + +@IRNode +public interface JCallArgument extends IR { + @IRChild(required = false) + Name name(); + + @IRChild + Expression value(); + + interface JSpecified extends JCallArgument {} +} From 57c5bff7622d231703e83dfe9c93d043475e8201 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Wed, 6 Nov 2024 16:17:51 +0100 Subject: [PATCH 71/93] Use qualified type names in mapExpressions method --- .../processor/MapExpressionsMethodGenerator.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/MapExpressionsMethodGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/MapExpressionsMethodGenerator.java index 7865e3077bf2..568449277691 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/MapExpressionsMethodGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/MapExpressionsMethodGenerator.java @@ -1,7 +1,9 @@ package org.enso.runtime.parser.processor; import java.util.Objects; +import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; final class MapExpressionsMethodGenerator { private final ExecutableElement mapExpressionsMethod; @@ -58,7 +60,7 @@ String generateMapExpressionsMethodCode() { var newChildName = child.getName() + "Mapped"; sb.append(" ") - .append(childsMapExprMethodRetType.getSimpleName()) + .append(typeName(childsMapExprMethodRetType)) .append(" ") .append(newChildName); if (child.isNullable()) { @@ -128,5 +130,12 @@ String generateMapExpressionsMethodCode() { return sb.toString(); } + private String typeName(Element element) { + if (element instanceof TypeElement typeElement) { + return typeElement.getQualifiedName().toString(); + } + return element.getSimpleName().toString(); + } + private record MappedChild(String newChildName, Field child, boolean shouldCast) {} } From be2d3742e9825d3ade9974ce75eb7f7c1e0248e0 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Wed, 6 Nov 2024 16:18:10 +0100 Subject: [PATCH 72/93] Implement JBlank.create factory method --- .../src/main/java/org/enso/compiler/core/ir/JName.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JName.java b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JName.java index c0486e3da1aa..218bcdad7030 100644 --- a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JName.java +++ b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JName.java @@ -1,5 +1,6 @@ package org.enso.compiler.core.ir; +import org.enso.compiler.core.ir.JNameGen.JBlankGen; import org.enso.compiler.core.ir.module.scope.JDefinition; import org.enso.runtime.parser.dsl.IRChild; import org.enso.runtime.parser.dsl.IRNode; @@ -18,7 +19,11 @@ JName duplicate( boolean keepDiagnostics, boolean keepIdentifiers); - interface JBlank extends JName {} + interface JBlank extends JName { + static JBlank create() { + return JBlankGen.builder().build(); + } + } interface JLiteral extends JName { @IRChild(required = false) From b8286036f70a29e3445e38d8ffb07446d91ca886 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Wed, 6 Nov 2024 16:18:36 +0100 Subject: [PATCH 73/93] JExpression overrides duplicate and mapExpressions --- .../org/enso/compiler/core/ir/JExpression.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JExpression.java b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JExpression.java index a222db52afbb..9ef0e17c9803 100644 --- a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JExpression.java +++ b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JExpression.java @@ -1,12 +1,27 @@ package org.enso.compiler.core.ir; +import java.util.function.Function; import org.enso.compiler.core.IR; +import org.enso.compiler.core.ir.JExpressionGen.IfThenElseGen; import org.enso.runtime.parser.dsl.IRChild; +import org.enso.runtime.parser.dsl.IRCopyMethod; import org.enso.runtime.parser.dsl.IRNode; +import scala.Option; import scala.collection.immutable.List; @IRNode public interface JExpression extends IR { + + @Override + JExpression mapExpressions(Function fn); + + @Override + JExpression duplicate( + boolean keepLocations, + boolean keepMetadata, + boolean keepDiagnostics, + boolean keepIdentifiers); + interface JBlock extends JExpression { @IRChild List expressions(); From 419dca07dded8172eded0b9931932bfb1aaa4d04 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 2 Dec 2024 13:35:02 +0100 Subject: [PATCH 74/93] Remove unused import --- .../src/main/java/org/enso/compiler/core/ir/JExpression.java | 1 - 1 file changed, 1 deletion(-) diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JExpression.java b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JExpression.java index 9ef0e17c9803..cd9d0d8fbfb1 100644 --- a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JExpression.java +++ b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JExpression.java @@ -2,7 +2,6 @@ import java.util.function.Function; import org.enso.compiler.core.IR; -import org.enso.compiler.core.ir.JExpressionGen.IfThenElseGen; import org.enso.runtime.parser.dsl.IRChild; import org.enso.runtime.parser.dsl.IRCopyMethod; import org.enso.runtime.parser.dsl.IRNode; From c8cbc98dc339eb509a0db102794b20570a3070ba Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 2 Dec 2024 13:42:56 +0100 Subject: [PATCH 75/93] Move all the sample `JXyz` interfaces to tests --- .../runtime/parser/processor/test/gen}/ir/JCallArgument.java | 2 +- .../parser/processor/test/gen}/ir/JDefinitionArgument.java | 2 +- .../enso/runtime/parser/processor/test/gen}/ir/JExpression.java | 2 +- .../org/enso/runtime/parser/processor/test/gen}/ir/JModule.java | 2 +- .../org/enso/runtime/parser/processor/test/gen}/ir/JName.java | 2 +- .../runtime/parser/processor/test/gen}/ir/module/JScope.java | 2 +- .../parser/processor/test/gen}/ir/module/scope/JDefinition.java | 2 +- .../parser/processor/test/gen}/ir/module/scope/JExport.java | 2 +- .../parser/processor/test/gen}/ir/module/scope/JImport.java | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) rename engine/{runtime-parser/src/main/java/org/enso/compiler/core => runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen}/ir/JCallArgument.java (84%) rename engine/{runtime-parser/src/main/java/org/enso/compiler/core => runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen}/ir/JDefinitionArgument.java (88%) rename engine/{runtime-parser/src/main/java/org/enso/compiler/core => runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen}/ir/JExpression.java (95%) rename engine/{runtime-parser/src/main/java/org/enso/compiler/core => runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen}/ir/JModule.java (87%) rename engine/{runtime-parser/src/main/java/org/enso/compiler/core => runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen}/ir/JName.java (95%) rename engine/{runtime-parser/src/main/java/org/enso/compiler/core => runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen}/ir/module/JScope.java (77%) rename engine/{runtime-parser/src/main/java/org/enso/compiler/core => runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen}/ir/module/scope/JDefinition.java (95%) rename engine/{runtime-parser/src/main/java/org/enso/compiler/core => runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen}/ir/module/scope/JExport.java (95%) rename engine/{runtime-parser/src/main/java/org/enso/compiler/core => runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen}/ir/module/scope/JImport.java (95%) diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JCallArgument.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/JCallArgument.java similarity index 84% rename from engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JCallArgument.java rename to engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/JCallArgument.java index f4c7804210be..55207096722d 100644 --- a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JCallArgument.java +++ b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/JCallArgument.java @@ -1,4 +1,4 @@ -package org.enso.compiler.core.ir; +package org.enso.runtime.parser.processor.test.gen.ir; import org.enso.compiler.core.IR; import org.enso.runtime.parser.dsl.IRChild; diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JDefinitionArgument.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/JDefinitionArgument.java similarity index 88% rename from engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JDefinitionArgument.java rename to engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/JDefinitionArgument.java index 464ae9ed278b..16fd751079b1 100644 --- a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JDefinitionArgument.java +++ b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/JDefinitionArgument.java @@ -1,4 +1,4 @@ -package org.enso.compiler.core.ir; +package org.enso.runtime.parser.processor.test.gen.ir; import org.enso.compiler.core.IR; import org.enso.runtime.parser.dsl.IRChild; diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JExpression.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/JExpression.java similarity index 95% rename from engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JExpression.java rename to engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/JExpression.java index cd9d0d8fbfb1..36d455b70bcd 100644 --- a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JExpression.java +++ b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/JExpression.java @@ -1,4 +1,4 @@ -package org.enso.compiler.core.ir; +package org.enso.runtime.parser.processor.test.gen.ir; import java.util.function.Function; import org.enso.compiler.core.IR; diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JModule.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/JModule.java similarity index 87% rename from engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JModule.java rename to engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/JModule.java index cf518a3b24c0..eacfd4221821 100644 --- a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JModule.java +++ b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/JModule.java @@ -1,4 +1,4 @@ -package org.enso.compiler.core.ir; +package org.enso.runtime.parser.processor.test.gen.ir; import org.enso.compiler.core.IR; import org.enso.compiler.core.ir.module.scope.JExport; diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JName.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/JName.java similarity index 95% rename from engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JName.java rename to engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/JName.java index 218bcdad7030..dd88a4fbae99 100644 --- a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JName.java +++ b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/JName.java @@ -1,4 +1,4 @@ -package org.enso.compiler.core.ir; +package org.enso.runtime.parser.processor.test.gen.ir; import org.enso.compiler.core.ir.JNameGen.JBlankGen; import org.enso.compiler.core.ir.module.scope.JDefinition; diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/JScope.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/module/JScope.java similarity index 77% rename from engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/JScope.java rename to engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/module/JScope.java index 579de0d2eeca..6742b1839e99 100644 --- a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/JScope.java +++ b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/module/JScope.java @@ -1,4 +1,4 @@ -package org.enso.compiler.core.ir.module; +package org.enso.runtime.parser.processor.test.gen.ir.module; import org.enso.compiler.core.IR; import org.enso.runtime.parser.dsl.IRNode; diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/scope/JDefinition.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/module/scope/JDefinition.java similarity index 95% rename from engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/scope/JDefinition.java rename to engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/module/scope/JDefinition.java index ef6be4ca3b9d..fa238dc4cc09 100644 --- a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/scope/JDefinition.java +++ b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/module/scope/JDefinition.java @@ -1,4 +1,4 @@ -package org.enso.compiler.core.ir.module.scope; +package org.enso.runtime.parser.processor.test.gen.ir.module.scope; import org.enso.compiler.core.IR; import org.enso.compiler.core.ir.JDefinitionArgument; diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/scope/JExport.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/module/scope/JExport.java similarity index 95% rename from engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/scope/JExport.java rename to engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/module/scope/JExport.java index a0fd51ca907b..2de709b54682 100644 --- a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/scope/JExport.java +++ b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/module/scope/JExport.java @@ -1,4 +1,4 @@ -package org.enso.compiler.core.ir.module.scope; +package org.enso.runtime.parser.processor.test.gen.ir.module.scope; import org.enso.compiler.core.ir.JName; import org.enso.compiler.core.ir.module.JScope; diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/scope/JImport.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/module/scope/JImport.java similarity index 95% rename from engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/scope/JImport.java rename to engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/module/scope/JImport.java index 9928aa87a05d..31557fd3d50d 100644 --- a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/module/scope/JImport.java +++ b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/module/scope/JImport.java @@ -1,4 +1,4 @@ -package org.enso.compiler.core.ir.module.scope; +package org.enso.runtime.parser.processor.test.gen.ir.module.scope; import org.enso.compiler.core.ir.JName; import org.enso.compiler.core.ir.module.JScope; From fd6ad5b93e5f81d0ab2ec9f57eaac0df9c4e82cd Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 2 Dec 2024 14:12:28 +0100 Subject: [PATCH 76/93] Generate identifiedLocation method override --- .../enso/runtime/parser/processor/IRNodeClassGenerator.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java index 107da29f17e0..dd3ba5a95318 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java @@ -301,6 +301,11 @@ public Option location() { public IR setLocation(Option location) { throw new UnsupportedOperationException("unimplemented"); } + + @Override + public IdentifiedLocation identifiedLocation() { + return this.location; + } @Override public scala.collection.immutable.List children() { From e066374f6165f355dbed633159260ab4a7e4e7d8 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 2 Dec 2024 14:12:40 +0100 Subject: [PATCH 77/93] Fix compilation after the move --- .../runtime/parser/processor/test/gen/ir/JCallArgument.java | 2 ++ .../runtime/parser/processor/test/gen/ir/JExpression.java | 1 + .../enso/runtime/parser/processor/test/gen/ir/JModule.java | 4 ++-- .../enso/runtime/parser/processor/test/gen/ir/JName.java | 5 ++--- .../processor/test/gen/ir/module/scope/JDefinition.java | 6 +++--- .../parser/processor/test/gen/ir/module/scope/JExport.java | 4 ++-- .../parser/processor/test/gen/ir/module/scope/JImport.java | 4 ++-- 7 files changed, 14 insertions(+), 12 deletions(-) diff --git a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/JCallArgument.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/JCallArgument.java index 55207096722d..14f611680599 100644 --- a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/JCallArgument.java +++ b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/JCallArgument.java @@ -1,6 +1,8 @@ package org.enso.runtime.parser.processor.test.gen.ir; import org.enso.compiler.core.IR; +import org.enso.compiler.core.ir.Expression; +import org.enso.compiler.core.ir.Name; import org.enso.runtime.parser.dsl.IRChild; import org.enso.runtime.parser.dsl.IRNode; diff --git a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/JExpression.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/JExpression.java index 36d455b70bcd..bcb84f467479 100644 --- a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/JExpression.java +++ b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/JExpression.java @@ -2,6 +2,7 @@ import java.util.function.Function; import org.enso.compiler.core.IR; +import org.enso.compiler.core.ir.Expression; import org.enso.runtime.parser.dsl.IRChild; import org.enso.runtime.parser.dsl.IRCopyMethod; import org.enso.runtime.parser.dsl.IRNode; diff --git a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/JModule.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/JModule.java index eacfd4221821..a0983a317ddc 100644 --- a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/JModule.java +++ b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/JModule.java @@ -1,10 +1,10 @@ package org.enso.runtime.parser.processor.test.gen.ir; import org.enso.compiler.core.IR; -import org.enso.compiler.core.ir.module.scope.JExport; -import org.enso.compiler.core.ir.module.scope.JImport; import org.enso.runtime.parser.dsl.IRChild; import org.enso.runtime.parser.dsl.IRNode; +import org.enso.runtime.parser.processor.test.gen.ir.module.scope.JExport; +import org.enso.runtime.parser.processor.test.gen.ir.module.scope.JImport; import scala.collection.immutable.List; @IRNode diff --git a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/JName.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/JName.java index dd88a4fbae99..c9f9af4ba93c 100644 --- a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/JName.java +++ b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/JName.java @@ -1,9 +1,8 @@ package org.enso.runtime.parser.processor.test.gen.ir; -import org.enso.compiler.core.ir.JNameGen.JBlankGen; -import org.enso.compiler.core.ir.module.scope.JDefinition; import org.enso.runtime.parser.dsl.IRChild; import org.enso.runtime.parser.dsl.IRNode; +import org.enso.runtime.parser.processor.test.gen.ir.module.scope.JDefinition; import scala.collection.immutable.List; @IRNode @@ -21,7 +20,7 @@ JName duplicate( interface JBlank extends JName { static JBlank create() { - return JBlankGen.builder().build(); + return JNameGen.JBlankGen.builder().build(); } } diff --git a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/module/scope/JDefinition.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/module/scope/JDefinition.java index fa238dc4cc09..50f6ac72169c 100644 --- a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/module/scope/JDefinition.java +++ b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/module/scope/JDefinition.java @@ -1,11 +1,11 @@ package org.enso.runtime.parser.processor.test.gen.ir.module.scope; import org.enso.compiler.core.IR; -import org.enso.compiler.core.ir.JDefinitionArgument; -import org.enso.compiler.core.ir.JName; -import org.enso.compiler.core.ir.module.JScope; import org.enso.runtime.parser.dsl.IRChild; import org.enso.runtime.parser.dsl.IRNode; +import org.enso.runtime.parser.processor.test.gen.ir.JDefinitionArgument; +import org.enso.runtime.parser.processor.test.gen.ir.JName; +import org.enso.runtime.parser.processor.test.gen.ir.module.JScope; import scala.collection.immutable.List; @IRNode diff --git a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/module/scope/JExport.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/module/scope/JExport.java index 2de709b54682..6dd8dbb60818 100644 --- a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/module/scope/JExport.java +++ b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/module/scope/JExport.java @@ -1,9 +1,9 @@ package org.enso.runtime.parser.processor.test.gen.ir.module.scope; -import org.enso.compiler.core.ir.JName; -import org.enso.compiler.core.ir.module.JScope; import org.enso.runtime.parser.dsl.IRChild; import org.enso.runtime.parser.dsl.IRNode; +import org.enso.runtime.parser.processor.test.gen.ir.JName; +import org.enso.runtime.parser.processor.test.gen.ir.module.JScope; import scala.collection.immutable.List; @IRNode diff --git a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/module/scope/JImport.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/module/scope/JImport.java index 31557fd3d50d..e0d2f146f688 100644 --- a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/module/scope/JImport.java +++ b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/module/scope/JImport.java @@ -1,9 +1,9 @@ package org.enso.runtime.parser.processor.test.gen.ir.module.scope; -import org.enso.compiler.core.ir.JName; -import org.enso.compiler.core.ir.module.JScope; import org.enso.runtime.parser.dsl.IRChild; import org.enso.runtime.parser.dsl.IRNode; +import org.enso.runtime.parser.processor.test.gen.ir.JName; +import org.enso.runtime.parser.processor.test.gen.ir.module.JScope; import scala.collection.immutable.List; /** Module-level import statements. */ From 68389f4065c406db5bddda5b0a1017bdf2864889 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 2 Dec 2024 14:25:46 +0100 Subject: [PATCH 78/93] Move sample `JXyz` interfaces to a dedicated test package --- .../processor/test/gen/ir/{ => core}/JCallArgument.java | 2 +- .../test/gen/ir/{ => core}/JDefinitionArgument.java | 2 +- .../processor/test/gen/ir/{ => core}/JExpression.java | 4 +--- .../parser/processor/test/gen/ir/{ => core}/JModule.java | 2 +- .../parser/processor/test/gen/ir/{ => core}/JName.java | 2 +- .../parser/processor/test/gen/ir/core/package-info.java | 9 +++++++++ .../processor/test/gen/ir/module/scope/JDefinition.java | 4 ++-- .../processor/test/gen/ir/module/scope/JExport.java | 2 +- .../processor/test/gen/ir/module/scope/JImport.java | 2 +- 9 files changed, 18 insertions(+), 11 deletions(-) rename engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/{ => core}/JCallArgument.java (86%) rename engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/{ => core}/JDefinitionArgument.java (87%) rename engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/{ => core}/JExpression.java (89%) rename engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/{ => core}/JModule.java (87%) rename engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/{ => core}/JName.java (94%) create mode 100644 engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/package-info.java diff --git a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/JCallArgument.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JCallArgument.java similarity index 86% rename from engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/JCallArgument.java rename to engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JCallArgument.java index 14f611680599..150949228daf 100644 --- a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/JCallArgument.java +++ b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JCallArgument.java @@ -1,4 +1,4 @@ -package org.enso.runtime.parser.processor.test.gen.ir; +package org.enso.runtime.parser.processor.test.gen.ir.core; import org.enso.compiler.core.IR; import org.enso.compiler.core.ir.Expression; diff --git a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/JDefinitionArgument.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JDefinitionArgument.java similarity index 87% rename from engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/JDefinitionArgument.java rename to engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JDefinitionArgument.java index 16fd751079b1..37cae56ec8d5 100644 --- a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/JDefinitionArgument.java +++ b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JDefinitionArgument.java @@ -1,4 +1,4 @@ -package org.enso.runtime.parser.processor.test.gen.ir; +package org.enso.runtime.parser.processor.test.gen.ir.core; import org.enso.compiler.core.IR; import org.enso.runtime.parser.dsl.IRChild; diff --git a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/JExpression.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JExpression.java similarity index 89% rename from engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/JExpression.java rename to engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JExpression.java index bcb84f467479..bf6fe1f79fb1 100644 --- a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/JExpression.java +++ b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JExpression.java @@ -1,12 +1,10 @@ -package org.enso.runtime.parser.processor.test.gen.ir; +package org.enso.runtime.parser.processor.test.gen.ir.core; import java.util.function.Function; import org.enso.compiler.core.IR; import org.enso.compiler.core.ir.Expression; import org.enso.runtime.parser.dsl.IRChild; -import org.enso.runtime.parser.dsl.IRCopyMethod; import org.enso.runtime.parser.dsl.IRNode; -import scala.Option; import scala.collection.immutable.List; @IRNode diff --git a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/JModule.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JModule.java similarity index 87% rename from engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/JModule.java rename to engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JModule.java index a0983a317ddc..4162b197882b 100644 --- a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/JModule.java +++ b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JModule.java @@ -1,4 +1,4 @@ -package org.enso.runtime.parser.processor.test.gen.ir; +package org.enso.runtime.parser.processor.test.gen.ir.core; import org.enso.compiler.core.IR; import org.enso.runtime.parser.dsl.IRChild; diff --git a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/JName.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JName.java similarity index 94% rename from engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/JName.java rename to engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JName.java index c9f9af4ba93c..58c07d32e934 100644 --- a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/JName.java +++ b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JName.java @@ -1,4 +1,4 @@ -package org.enso.runtime.parser.processor.test.gen.ir; +package org.enso.runtime.parser.processor.test.gen.ir.core; import org.enso.runtime.parser.dsl.IRChild; import org.enso.runtime.parser.dsl.IRNode; diff --git a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/package-info.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/package-info.java new file mode 100644 index 000000000000..2b6acc4e3861 --- /dev/null +++ b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/package-info.java @@ -0,0 +1,9 @@ +/** + * Contains hierarchy of interfaces that should correspond to the previous + * {@link org.enso.compiler.core.IR} element hierarchy. All the classes inside + * this package have {@code J} prefix. So for example {@code JCallArgument} correspond to + * {@code CallArgument}. + * + *

The motivation to put these classes here is to test the generation of {@link org.enso.runtime.parser.processor.IRProcessor}. + */ +package org.enso.runtime.parser.processor.test.gen.ir.core; diff --git a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/module/scope/JDefinition.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/module/scope/JDefinition.java index 50f6ac72169c..42c0be0292b7 100644 --- a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/module/scope/JDefinition.java +++ b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/module/scope/JDefinition.java @@ -3,8 +3,8 @@ import org.enso.compiler.core.IR; import org.enso.runtime.parser.dsl.IRChild; import org.enso.runtime.parser.dsl.IRNode; -import org.enso.runtime.parser.processor.test.gen.ir.JDefinitionArgument; -import org.enso.runtime.parser.processor.test.gen.ir.JName; +import org.enso.runtime.parser.processor.test.gen.ir.core.JDefinitionArgument; +import org.enso.runtime.parser.processor.test.gen.ir.core.JName; import org.enso.runtime.parser.processor.test.gen.ir.module.JScope; import scala.collection.immutable.List; diff --git a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/module/scope/JExport.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/module/scope/JExport.java index 6dd8dbb60818..53b26778d147 100644 --- a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/module/scope/JExport.java +++ b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/module/scope/JExport.java @@ -2,7 +2,7 @@ import org.enso.runtime.parser.dsl.IRChild; import org.enso.runtime.parser.dsl.IRNode; -import org.enso.runtime.parser.processor.test.gen.ir.JName; +import org.enso.runtime.parser.processor.test.gen.ir.core.JName; import org.enso.runtime.parser.processor.test.gen.ir.module.JScope; import scala.collection.immutable.List; diff --git a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/module/scope/JImport.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/module/scope/JImport.java index e0d2f146f688..ff74f509324c 100644 --- a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/module/scope/JImport.java +++ b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/module/scope/JImport.java @@ -2,7 +2,7 @@ import org.enso.runtime.parser.dsl.IRChild; import org.enso.runtime.parser.dsl.IRNode; -import org.enso.runtime.parser.processor.test.gen.ir.JName; +import org.enso.runtime.parser.processor.test.gen.ir.core.JName; import org.enso.runtime.parser.processor.test.gen.ir.module.JScope; import scala.collection.immutable.List; From 1b7a120ffbdd50a520aab64558fe04b0213ac049 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 2 Dec 2024 14:29:33 +0100 Subject: [PATCH 79/93] Add sample definition of JEmpty --- .../processor/test/gen/ir/core/JEmpty.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JEmpty.java diff --git a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JEmpty.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JEmpty.java new file mode 100644 index 000000000000..e466b3ae588c --- /dev/null +++ b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JEmpty.java @@ -0,0 +1,34 @@ +package org.enso.runtime.parser.processor.test.gen.ir.core; + +import java.util.UUID; +import org.enso.compiler.core.IR; +import org.enso.compiler.core.ir.DiagnosticStorage; +import org.enso.compiler.core.ir.IdentifiedLocation; +import org.enso.compiler.core.ir.MetadataStorage; +import org.enso.runtime.parser.dsl.IRCopyMethod; +import org.enso.runtime.parser.dsl.IRNode; + +@IRNode +public interface JEmpty extends IR { + static JEmptyGen.Builder builder() { + return JEmptyGen.builder(); + } + + static JEmpty createEmpty() { + return JEmptyGen.builder().build(); + } + + static JEmpty createFromLocation(IdentifiedLocation location) { + return JEmptyGen.builder().location(location).build(); + } + + static JEmpty createFromLocationAndPassData(IdentifiedLocation location, MetadataStorage passData) { + return JEmptyGen.builder() + .location(location) + .passData(passData) + .build(); + } + + @IRCopyMethod + JEmpty copy(IdentifiedLocation location, MetadataStorage passData, DiagnosticStorage diagnostics, UUID id); +} From aa345ed456cbc05db2baed83bc4b034e973ceeb6 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 2 Dec 2024 14:35:16 +0100 Subject: [PATCH 80/93] Replace Scala Empty with Java Empty annotated with @IRNode --- .../java/org/enso/compiler/core/ir/Empty.java | 27 ++++++ .../org/enso/compiler/core/ir/Empty.scala | 89 ------------------- 2 files changed, 27 insertions(+), 89 deletions(-) create mode 100644 engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/Empty.java delete mode 100644 engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/Empty.scala diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/Empty.java b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/Empty.java new file mode 100644 index 000000000000..96ec7dc8ce06 --- /dev/null +++ b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/Empty.java @@ -0,0 +1,27 @@ +package org.enso.compiler.core.ir; + +import java.util.UUID; +import org.enso.compiler.core.IR; +import org.enso.runtime.parser.dsl.IRCopyMethod; +import org.enso.runtime.parser.dsl.IRNode; + +@IRNode +public interface Empty extends IR { + static Empty createEmpty() { + return EmptyGen.builder().build(); + } + + static Empty createFromLocation(IdentifiedLocation location) { + return EmptyGen.builder().location(location).build(); + } + + static Empty createFromLocationAndPassData(IdentifiedLocation location, MetadataStorage passData) { + return EmptyGen.builder() + .location(location) + .passData(passData) + .build(); + } + + @IRCopyMethod + Empty copy(IdentifiedLocation location, MetadataStorage passData, DiagnosticStorage diagnostics, UUID id); +} diff --git a/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/Empty.scala b/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/Empty.scala deleted file mode 100644 index 849d58cd9401..000000000000 --- a/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/Empty.scala +++ /dev/null @@ -1,89 +0,0 @@ -package org.enso.compiler.core.ir - -import org.enso.compiler.core.Implicits.{ShowPassData, ToStringHelper} -import org.enso.compiler.core.{IR, Identifier} - -import java.util.UUID - -/** A node representing an empty IR construct that can be used in any place. - * - * @param identifiedLocation the source location that the node corresponds to - * @param passData the pass metadata associated with this node - */ -sealed case class Empty( - override val identifiedLocation: IdentifiedLocation, - override val passData: MetadataStorage = new MetadataStorage() -) extends IR - with Expression - with IRKind.Primitive - with LazyDiagnosticStorage - with LazyId { - - /** Creates a copy of `this` - * - * @param location the source location that the node corresponds to - * @param passData the pass metadata associated with this node - * @param diagnostics compiler diagnostics for this node - * @param id the identifier for the new node - * @return a copy of `this` with the specified fields updated - */ - def copy( - location: Option[IdentifiedLocation] = location, - passData: MetadataStorage = passData, - diagnostics: DiagnosticStorage = diagnostics, - id: UUID @Identifier = id - ): Empty = { - if ( - location != this.location - || (passData ne this.passData) - || diagnostics != this.diagnostics - || id != this.id - ) { - val res = Empty(location.orNull, passData) - res.diagnostics = diagnostics - res.id = id - res - } else this - } - - /** @inheritdoc */ - override def duplicate( - keepLocations: Boolean = true, - keepMetadata: Boolean = true, - keepDiagnostics: Boolean = true, - keepIdentifiers: Boolean = false - ): Empty = - copy( - location = if (keepLocations) location else None, - passData = - if (keepMetadata) passData.duplicate else new MetadataStorage(), - diagnostics = if (keepDiagnostics) diagnosticsCopy else null, - id = if (keepIdentifiers) id else null - ) - - /** @inheritdoc */ - override def setLocation(location: Option[IdentifiedLocation]): Empty = - copy(location = location) - - /** @inheritdoc */ - override def mapExpressions( - fn: java.util.function.Function[Expression, Expression] - ): Empty = this - - /** String representation. */ - override def toString: String = - s""" - |Empty( - |location = $location, - |passData = ${this.showPassData}, - |diagnostics = $diagnostics, - |id = $id - |) - |""".toSingleLine - - /** @inheritdoc */ - override def children: List[IR] = List() - - /** @inheritdoc */ - override def showCode(indent: Int): String = "IR.Empty" -} From fa2614426d08198235036a361a3c773f30a7974f Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 2 Dec 2024 14:48:53 +0100 Subject: [PATCH 81/93] Refactor Utils.findMethod --- .../enso/runtime/parser/processor/Utils.java | 59 ++++++++++--------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java index edcd37f75ea3..b252eddf8f10 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java @@ -2,6 +2,7 @@ import java.lang.annotation.Annotation; import java.util.ArrayDeque; +import java.util.function.Predicate; import java.util.stream.Collectors; import javax.annotation.processing.Messager; import javax.annotation.processing.ProcessingEnvironment; @@ -115,30 +116,45 @@ static boolean hasModifier(ExecutableElement method, Modifier modifier) { } /** - * Find any override of {@link org.enso.compiler.core.IR#duplicate(boolean, boolean, boolean, - * boolean) duplicate method}. Or the duplicate method on the interface itself. Note that there - * can be an override with a different return type in a sub interface. - * - * @param interfaceType Interface from where the search is started. All super interfaces are - * searched transitively. - * @return not null. + * Finds a method in the interface hierarchy. The interface hierarchy processing starts from {@code interfaceType} and + * iterates until {@code org.enso.compiler.core.IR} interface type is encountered. + * Every method in the hierarchy is checked by {@code methodPredicate}. + * @param interfaceType Type of the interface. Must extend {@code org.enso.compiler.core.IR}. + * @param procEnv + * @param methodPredicate Predicate that is called for each method in the hierarchy. + * @return Method that satisfies the predicate or null if no such method is found. */ - static ExecutableElement findDuplicateMethod( - TypeElement interfaceType, ProcessingEnvironment procEnv) { - var duplicateMethod = + static ExecutableElement findMethod( + TypeElement interfaceType, ProcessingEnvironment procEnv, Predicate methodPredicate) { + var foundMethod = iterateSuperInterfaces( interfaceType, procEnv, (TypeElement superInterface) -> { for (var enclosedElem : superInterface.getEnclosedElements()) { if (enclosedElem instanceof ExecutableElement execElem) { - if (isDuplicateMethod(execElem)) { + if (methodPredicate.test(execElem)) { return execElem; } } } return null; }); + return foundMethod; + } + + /** + * Find any override of {@link org.enso.compiler.core.IR#duplicate(boolean, boolean, boolean, + * boolean) duplicate method}. Or the duplicate method on the interface itself. Note that there + * can be an override with a different return type in a sub interface. + * + * @param interfaceType Interface from where the search is started. All super interfaces are + * searched transitively. + * @return not null. + */ + static ExecutableElement findDuplicateMethod( + TypeElement interfaceType, ProcessingEnvironment procEnv) { + var duplicateMethod = findMethod(interfaceType, procEnv, Utils::isDuplicateMethod); hardAssert( duplicateMethod != null, "Interface " @@ -149,23 +165,10 @@ static ExecutableElement findDuplicateMethod( static ExecutableElement findMapExpressionsMethod( TypeElement interfaceType, ProcessingEnvironment processingEnv) { - var mapExprsMethod = - Utils.iterateSuperInterfaces( - interfaceType, - processingEnv, - iface -> { - // Filter only ExecutableElement from iface.getEnclosedElements and convert the stream - // to Stream - var declaredMethods = - iface.getEnclosedElements().stream() - .filter(elem -> elem instanceof ExecutableElement) - .map(elem -> (ExecutableElement) elem); - var mapExprMethod = - declaredMethods - .filter(elem -> elem.getSimpleName().toString().equals(MAP_EXPRESSIONS)) - .findFirst(); - return mapExprMethod.orElse(null); - }); + var mapExprsMethod = findMethod( + interfaceType, + processingEnv, + method -> method.getSimpleName().toString().equals(MAP_EXPRESSIONS)); hardAssert( mapExprsMethod != null, "mapExpressions method must be found it must be defined at least on IR super interface"); From c4a669e290343c90c68467d512f615e6113c37da Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 2 Dec 2024 15:09:41 +0100 Subject: [PATCH 82/93] Fix test after merge of develop --- .../org/enso/runtime/parser/processor/test/TestGeneratedIR.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestGeneratedIR.java b/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestGeneratedIR.java index 8d4b80f28639..ac010597ce51 100644 --- a/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestGeneratedIR.java +++ b/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestGeneratedIR.java @@ -98,7 +98,7 @@ public void optChildIsNotRequired() { @Test public void duplicateRespectsParameters() { - var location = IdentifiedLocation.create(new Location(1, 2), Option$.MODULE$.empty()); + var location = new IdentifiedLocation(new Location(1, 2)); var diagnostics = DiagnosticStorage.empty(); var nameIR = NameTestIRGen.builder().name("name").location(location).diagnostics(diagnostics).build(); From e4222f088dfb44482c7b03ea4c837a6cc76b5cd9 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 2 Dec 2024 15:13:46 +0100 Subject: [PATCH 83/93] Fix TestIRProcessorInline after `J*` classes were moved to test dir --- .../parser/processor/test/TestIRProcessorInline.java | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessorInline.java b/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessorInline.java index 602403343e33..2a0deb436100 100644 --- a/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessorInline.java +++ b/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessorInline.java @@ -99,7 +99,6 @@ public void childAnnotation_MustBeAppliedToIRField() { import org.enso.runtime.parser.dsl.IRNode; import org.enso.runtime.parser.dsl.IRChild; import org.enso.compiler.core.IR; - import org.enso.compiler.core.ir.JExpression; @IRNode public interface MyIR extends IR { @@ -161,14 +160,14 @@ public void simpleIRNodeWithChild() { import org.enso.runtime.parser.dsl.IRNode; import org.enso.runtime.parser.dsl.IRChild; import org.enso.compiler.core.IR; - import org.enso.compiler.core.ir.JExpression; + import org.enso.compiler.core.ir.Expression; @IRNode public interface MyIR extends IR { - @IRChild JExpression expression(); + @IRChild Expression expression(); } """); - assertThat(genSrc, containsString("JExpression expression()")); + assertThat(genSrc, containsString("Expression expression()")); } @Test @@ -180,7 +179,6 @@ public void irNodeWithMultipleFields_PrimitiveField() { import org.enso.runtime.parser.dsl.IRNode; import org.enso.runtime.parser.dsl.IRChild; import org.enso.compiler.core.IR; - import org.enso.compiler.core.ir.JExpression; @IRNode public interface MyIR extends IR { @@ -199,7 +197,6 @@ public void irNodeWithInheritedField() { import org.enso.runtime.parser.dsl.IRNode; import org.enso.runtime.parser.dsl.IRChild; import org.enso.compiler.core.IR; - import org.enso.compiler.core.ir.JExpression; interface MySuperIR extends IR { boolean suspended(); @@ -222,7 +219,6 @@ public void irNodeWithInheritedField_Override() { import org.enso.runtime.parser.dsl.IRNode; import org.enso.runtime.parser.dsl.IRChild; import org.enso.compiler.core.IR; - import org.enso.compiler.core.ir.JExpression; interface MySuperIR extends IR { boolean suspended(); @@ -246,7 +242,6 @@ public void irNodeWithInheritedField_Transitive() { import org.enso.runtime.parser.dsl.IRNode; import org.enso.runtime.parser.dsl.IRChild; import org.enso.compiler.core.IR; - import org.enso.compiler.core.ir.JExpression; interface MySuperSuperIR extends IR { boolean suspended(); From 4d5b214441da95fc23fe0c2163b0431a68b4cff0 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 2 Dec 2024 15:14:17 +0100 Subject: [PATCH 84/93] Implement SetLocationMethodGenerator --- .../processor/IRNodeClassGenerator.java | 16 ++++-- .../processor/SetLocationMethodGenerator.java | 52 +++++++++++++++++++ 2 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/SetLocationMethodGenerator.java diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java index dd3ba5a95318..d4b70c2e1699 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java @@ -37,6 +37,7 @@ final class IRNodeClassGenerator { private final GeneratedClassContext generatedClassContext; private final DuplicateMethodGenerator duplicateMethodGenerator; + private final SetLocationMethodGenerator setLocationMethodGenerator; private final BuilderMethodGenerator builderMethodGenerator; private final MapExpressionsMethodGenerator mapExpressionsMethodGenerator; @@ -81,6 +82,13 @@ final class IRNodeClassGenerator { var mapExpressionsMethod = Utils.findMapExpressionsMethod(interfaceType, processingEnv); this.mapExpressionsMethodGenerator = new MapExpressionsMethodGenerator(mapExpressionsMethod, generatedClassContext); + var setLocationMethod = + Utils.findMethod( + interfaceType, + processingEnv, + method -> method.getSimpleName().toString().equals("setLocation")); + this.setLocationMethodGenerator = + new SetLocationMethodGenerator(setLocationMethod, processingEnv); this.copyMethodGenerators = findCopyMethods().stream() .map(copyMethod -> new CopyMethodGenerator(copyMethod, generatedClassContext)) @@ -297,11 +305,8 @@ public Option location() { } } - @Override - public IR setLocation(Option location) { - throw new UnsupportedOperationException("unimplemented"); - } - + $setLocationMethod + @Override public IdentifiedLocation identifiedLocation() { return this.location; @@ -341,6 +346,7 @@ public String showCode(int indent) { } """ .replace("$childrenMethodBody", childrenMethodBody()) + .replace("$setLocationMethod", setLocationMethodGenerator.generateMethodCode()) .replace("$duplicateMethod", duplicateMethodGenerator.generateDuplicateMethodCode()); return indent(code, 2); } diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/SetLocationMethodGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/SetLocationMethodGenerator.java new file mode 100644 index 000000000000..2aa8d05705a8 --- /dev/null +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/SetLocationMethodGenerator.java @@ -0,0 +1,52 @@ +package org.enso.runtime.parser.processor; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.ExecutableElement; + +class SetLocationMethodGenerator { + private final ExecutableElement setLocationMethod; + private final ProcessingEnvironment processingEnv; + + SetLocationMethodGenerator( + ExecutableElement setLocationMethod, ProcessingEnvironment processingEnv) { + ensureCorrectSignature(setLocationMethod); + this.processingEnv = processingEnv; + this.setLocationMethod = setLocationMethod; + } + + private static void ensureCorrectSignature(ExecutableElement setLocationMethod) { + if (!setLocationMethod.getSimpleName().toString().equals("setLocation")) { + throw new IllegalArgumentException( + "setLocation method must be named setLocation, but was: " + setLocationMethod); + } + if (setLocationMethod.getParameters().size() != 1) { + throw new IllegalArgumentException( + "setLocation method must have exactly one parameter, but had: " + + setLocationMethod.getParameters()); + } + } + + String generateMethodCode() { + var code = + """ + @Override + public $retType setLocation(Option location) { + IdentifiedLocation loc = null; + if (location.isDefined()) { + loc = location.get(); + } + return builder().location(loc).build(); + } + """ + .replace("$retType", retType()); + return Utils.indent(code, 2); + } + + private String retType() { + return processingEnv + .getTypeUtils() + .asElement(setLocationMethod.getReturnType()) + .getSimpleName() + .toString(); + } +} From 5ef1bdee8c90ed70b06873c5466ab1b7570a3d39 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 2 Dec 2024 15:14:52 +0100 Subject: [PATCH 85/93] fmt --- .../processor/test/gen/ir/core/JEmpty.java | 14 +++++++------ .../test/gen/ir/core/package-info.java | 10 +++++----- .../processor/test/TestGeneratedIR.java | 1 - .../enso/runtime/parser/processor/Utils.java | 20 +++++++++++-------- 4 files changed, 25 insertions(+), 20 deletions(-) diff --git a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JEmpty.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JEmpty.java index e466b3ae588c..59d51aa08614 100644 --- a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JEmpty.java +++ b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JEmpty.java @@ -22,13 +22,15 @@ static JEmpty createFromLocation(IdentifiedLocation location) { return JEmptyGen.builder().location(location).build(); } - static JEmpty createFromLocationAndPassData(IdentifiedLocation location, MetadataStorage passData) { - return JEmptyGen.builder() - .location(location) - .passData(passData) - .build(); + static JEmpty createFromLocationAndPassData( + IdentifiedLocation location, MetadataStorage passData) { + return JEmptyGen.builder().location(location).passData(passData).build(); } @IRCopyMethod - JEmpty copy(IdentifiedLocation location, MetadataStorage passData, DiagnosticStorage diagnostics, UUID id); + JEmpty copy( + IdentifiedLocation location, + MetadataStorage passData, + DiagnosticStorage diagnostics, + UUID id); } diff --git a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/package-info.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/package-info.java index 2b6acc4e3861..654c976fd82e 100644 --- a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/package-info.java +++ b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/package-info.java @@ -1,9 +1,9 @@ /** - * Contains hierarchy of interfaces that should correspond to the previous - * {@link org.enso.compiler.core.IR} element hierarchy. All the classes inside - * this package have {@code J} prefix. So for example {@code JCallArgument} correspond to - * {@code CallArgument}. + * Contains hierarchy of interfaces that should correspond to the previous {@link + * org.enso.compiler.core.IR} element hierarchy. All the classes inside this package have {@code J} + * prefix. So for example {@code JCallArgument} correspond to {@code CallArgument}. * - *

The motivation to put these classes here is to test the generation of {@link org.enso.runtime.parser.processor.IRProcessor}. + *

The motivation to put these classes here is to test the generation of {@link + * org.enso.runtime.parser.processor.IRProcessor}. */ package org.enso.runtime.parser.processor.test.gen.ir.core; diff --git a/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestGeneratedIR.java b/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestGeneratedIR.java index ac010597ce51..728caa56bae8 100644 --- a/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestGeneratedIR.java +++ b/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestGeneratedIR.java @@ -18,7 +18,6 @@ import org.enso.runtime.parser.processor.test.gen.ir.NameTestIRGen; import org.enso.runtime.parser.processor.test.gen.ir.OptNameTestIRGen; import org.junit.Test; -import scala.Option$; public class TestGeneratedIR { @Test diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java index b252eddf8f10..d31657923f96 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java @@ -116,16 +116,19 @@ static boolean hasModifier(ExecutableElement method, Modifier modifier) { } /** - * Finds a method in the interface hierarchy. The interface hierarchy processing starts from {@code interfaceType} and - * iterates until {@code org.enso.compiler.core.IR} interface type is encountered. - * Every method in the hierarchy is checked by {@code methodPredicate}. + * Finds a method in the interface hierarchy. The interface hierarchy processing starts from + * {@code interfaceType} and iterates until {@code org.enso.compiler.core.IR} interface type is + * encountered. Every method in the hierarchy is checked by {@code methodPredicate}. + * * @param interfaceType Type of the interface. Must extend {@code org.enso.compiler.core.IR}. * @param procEnv * @param methodPredicate Predicate that is called for each method in the hierarchy. * @return Method that satisfies the predicate or null if no such method is found. */ static ExecutableElement findMethod( - TypeElement interfaceType, ProcessingEnvironment procEnv, Predicate methodPredicate) { + TypeElement interfaceType, + ProcessingEnvironment procEnv, + Predicate methodPredicate) { var foundMethod = iterateSuperInterfaces( interfaceType, @@ -165,10 +168,11 @@ static ExecutableElement findDuplicateMethod( static ExecutableElement findMapExpressionsMethod( TypeElement interfaceType, ProcessingEnvironment processingEnv) { - var mapExprsMethod = findMethod( - interfaceType, - processingEnv, - method -> method.getSimpleName().toString().equals(MAP_EXPRESSIONS)); + var mapExprsMethod = + findMethod( + interfaceType, + processingEnv, + method -> method.getSimpleName().toString().equals(MAP_EXPRESSIONS)); hardAssert( mapExprsMethod != null, "mapExpressions method must be found it must be defined at least on IR super interface"); From 9b48ed48c9de14e7e895b551d573a9cbc2ea7d32 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 2 Dec 2024 15:31:03 +0100 Subject: [PATCH 86/93] Ensure only a single IR interface is extended --- .../processor/test/TestIRProcessorInline.java | 21 ++++++++++++ .../runtime/parser/processor/IRProcessor.java | 32 +++++++++++++++++-- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessorInline.java b/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessorInline.java index 2a0deb436100..28e464dcf17e 100644 --- a/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessorInline.java +++ b/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessorInline.java @@ -77,6 +77,27 @@ public interface Hello {} CompilationSubject.assertThat(compilation).failed(); } + @Test + public void annotatedInterfaceMustNotExtendTwoIRInterfaces() { + var src = + JavaFileObjects.forSourceString( + "Hello", + """ + import org.enso.runtime.parser.dsl.IRNode; + import org.enso.compiler.core.IR; + import org.enso.compiler.core.ir.Expression; + + @IRNode + public interface Hello extends IR, Expression {} + """); + var compiler = Compiler.javac().withProcessors(new IRProcessor()); + var compilation = compiler.compile(src); + CompilationSubject.assertThat(compilation).failed(); + CompilationSubject.assertThat(compilation).hadErrorCount(1); + CompilationSubject.assertThat(compilation) + .hadErrorContaining("must extend only a single IR interface"); + } + @Test public void annotationCanOnlyBeAppliedToInterface() { var src = diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java index 195659e2339d..313d4ca840c7 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java @@ -48,8 +48,7 @@ private boolean processIrNode(Element irNodeElem) { } assert irNodeElem instanceof TypeElement; var irNodeTypeElem = (TypeElement) irNodeElem; - if (!Utils.isSubtypeOfIR(irNodeTypeElem, processingEnv)) { - printError("Interface annotated with @IRNode must be a subtype of IR interface", irNodeElem); + if (!ensureExtendsSingleIRInterface(irNodeTypeElem)) { return false; } var enclosingElem = irNodeElem.getEnclosingElement(); @@ -105,6 +104,35 @@ private boolean processIrNode(Element irNodeElem) { return true; } + /** + * The interface that is being processed must extend just a single interface that is a subtype of + * {@code org.enso.compiler.core.IR}. Otherwise, the code generation would not work correctly as + * it would find ambiguous methods to override. + * + * @param interfaceType The current interface annotated with {@link IRNode} being processed. + * @return {@code true} if the interface extends a single IR interface, {@code false} otherwise. + */ + private boolean ensureExtendsSingleIRInterface(TypeElement interfaceType) { + var superIfacesExtendingIr = + interfaceType.getInterfaces().stream() + .filter( + superInterface -> { + var superInterfaceType = + (TypeElement) processingEnv.getTypeUtils().asElement(superInterface); + return Utils.isSubtypeOfIR(superInterfaceType, processingEnv); + }) + .toList(); + if (superIfacesExtendingIr.size() != 1) { + printError( + "Interface annotated with @IRNode must be a subtype of IR interface, " + + "and must extend only a single IR interface. All the IR interfaces found: " + + superIfacesExtendingIr, + interfaceType); + return false; + } + return true; + } + private String packageName(Element elem) { var pkg = processingEnv.getElementUtils().getPackageOf(elem); return pkg.getQualifiedName().toString(); From 5ac716a01517cb12dd919a96271d4932ade714f9 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 2 Dec 2024 15:37:20 +0100 Subject: [PATCH 87/93] Fix compilation after Refactoring Empty to an interface with @IRNode --- .../compiler/pass/optimise/LambdaConsolidate.scala | 4 +++- .../compiler/pass/resolve/SuspendedArguments.scala | 2 +- .../test/core/ir/DiagnosticStorageTest.scala | 2 +- .../test/pass/desugar/OperatorToFunctionTest.scala | 12 ++++++------ .../main/java/org/enso/compiler/core/TreeToIr.java | 6 +++--- 5 files changed, 14 insertions(+), 12 deletions(-) diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/optimise/LambdaConsolidate.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/optimise/LambdaConsolidate.scala index 6f2ab1e20910..75953dff7807 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/optimise/LambdaConsolidate.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/optimise/LambdaConsolidate.scala @@ -227,7 +227,9 @@ case object LambdaConsolidate extends IRPass { } val shadower: IR = - mShadower.getOrElse(Empty(spec.identifiedLocation)) + mShadower.getOrElse( + Empty.createFromLocation(spec.identifiedLocation) + ) spec.getDiagnostics.add( warnings.Shadowed diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/SuspendedArguments.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/SuspendedArguments.scala index 26629b1c9478..0383e9c09bd6 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/SuspendedArguments.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/SuspendedArguments.scala @@ -306,7 +306,7 @@ case object SuspendedArguments extends IRPass { } else if (args.length > signatureSegments.length) { val additionalSegments = signatureSegments ::: List.fill( args.length - signatureSegments.length - )(Empty(identifiedLocation = null)) + )(Empty.createEmpty()) args.zip(additionalSegments) } else { diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/core/ir/DiagnosticStorageTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/core/ir/DiagnosticStorageTest.scala index a119cfe64321..5ae324828226 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/core/ir/DiagnosticStorageTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/core/ir/DiagnosticStorageTest.scala @@ -16,7 +16,7 @@ class DiagnosticStorageTest extends CompilerTest { def mkDiagnostic(name: String): Diagnostic = { warnings.Shadowed.FunctionParam( name, - Empty(identifiedLocation = null), + Empty.createEmpty(), identifiedLocation = null ) } diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/desugar/OperatorToFunctionTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/desugar/OperatorToFunctionTest.scala index e387e7908f44..45f5afa77dad 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/desugar/OperatorToFunctionTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/desugar/OperatorToFunctionTest.scala @@ -84,9 +84,9 @@ class OperatorToFunctionTest extends MiniPassTest { // === The Tests ============================================================ val opName = Name.Literal("=:=", isMethod = true, null) - val left = Empty(null) - val right = Empty(null) - val rightArg = CallArgument.Specified(None, Empty(null), false, null) + val left = Empty.createEmpty() + val right = Empty.createEmpty() + val rightArg = CallArgument.Specified(None, Empty.createEmpty(), false, null) val (operator, operatorFn) = genOprAndFn(opName, left, right) @@ -96,11 +96,11 @@ class OperatorToFunctionTest extends MiniPassTest { "Operators" should { val opName = Name.Literal("=:=", isMethod = true, identifiedLocation = null) - val left = Empty(identifiedLocation = null) - val right = Empty(identifiedLocation = null) + val left = Empty.createEmpty() + val right = Empty.createEmpty() val rightArg = CallArgument.Specified( None, - Empty(identifiedLocation = null), + Empty.createEmpty(), false, identifiedLocation = null ) 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 14e2fd759503..6bfd3706dcad 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 @@ -492,7 +492,7 @@ private List translateMethodBinding(Tree.Function fn, List Date: Mon, 2 Dec 2024 15:38:13 +0100 Subject: [PATCH 88/93] Empty extends Expression --- .../java/org/enso/compiler/core/ir/Empty.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/Empty.java b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/Empty.java index 96ec7dc8ce06..18dc87f286ac 100644 --- a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/Empty.java +++ b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/Empty.java @@ -1,12 +1,11 @@ package org.enso.compiler.core.ir; import java.util.UUID; -import org.enso.compiler.core.IR; import org.enso.runtime.parser.dsl.IRCopyMethod; import org.enso.runtime.parser.dsl.IRNode; @IRNode -public interface Empty extends IR { +public interface Empty extends Expression { static Empty createEmpty() { return EmptyGen.builder().build(); } @@ -15,13 +14,15 @@ static Empty createFromLocation(IdentifiedLocation location) { return EmptyGen.builder().location(location).build(); } - static Empty createFromLocationAndPassData(IdentifiedLocation location, MetadataStorage passData) { - return EmptyGen.builder() - .location(location) - .passData(passData) - .build(); + static Empty createFromLocationAndPassData( + IdentifiedLocation location, MetadataStorage passData) { + return EmptyGen.builder().location(location).passData(passData).build(); } @IRCopyMethod - Empty copy(IdentifiedLocation location, MetadataStorage passData, DiagnosticStorage diagnostics, UUID id); + Empty copy( + IdentifiedLocation location, + MetadataStorage passData, + DiagnosticStorage diagnostics, + UUID id); } From 9b5f1b289be70b3d9baf9f17934c5fd07b132f95 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 2 Dec 2024 18:15:11 +0100 Subject: [PATCH 89/93] Override equals and hashCode --- .../processor/EqualsMethodGenerator.java | 35 +++++++++++++++++++ .../processor/HashCodeMethodGenerator.java | 26 ++++++++++++++ .../processor/IRNodeClassGenerator.java | 11 ++++++ 3 files changed, 72 insertions(+) create mode 100644 engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/EqualsMethodGenerator.java create mode 100644 engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/HashCodeMethodGenerator.java diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/EqualsMethodGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/EqualsMethodGenerator.java new file mode 100644 index 000000000000..d426d8ab6911 --- /dev/null +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/EqualsMethodGenerator.java @@ -0,0 +1,35 @@ +package org.enso.runtime.parser.processor; + +final class EqualsMethodGenerator { + private final GeneratedClassContext ctx; + + EqualsMethodGenerator(GeneratedClassContext ctx) { + this.ctx = ctx; + } + + String generateMethodCode() { + var sb = new StringBuilder(); + sb.append("@Override").append(System.lineSeparator()); + sb.append("public boolean equals(Object o) {").append(System.lineSeparator()); + sb.append(" if (this == o) {").append(System.lineSeparator()); + sb.append(" return true;").append(System.lineSeparator()); + sb.append(" }").append(System.lineSeparator()); + sb.append(" if (o instanceof ") + .append(ctx.getClassName()) + .append(" other) {") + .append(System.lineSeparator()); + for (var field : ctx.getAllFields()) { + sb.append( + " if (!(Objects.equals(this.$name, other.$name))) {" + .replace("$name", field.name())) + .append(System.lineSeparator()); + sb.append(" return false;").append(System.lineSeparator()); + sb.append(" }").append(System.lineSeparator()); + } + sb.append(" return true;").append(System.lineSeparator()); + sb.append(" }").append(System.lineSeparator()); + sb.append(" return false;").append(System.lineSeparator()); + sb.append("}").append(System.lineSeparator()); + return Utils.indent(sb.toString(), 2); + } +} diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/HashCodeMethodGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/HashCodeMethodGenerator.java new file mode 100644 index 000000000000..b46412a1ff94 --- /dev/null +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/HashCodeMethodGenerator.java @@ -0,0 +1,26 @@ +package org.enso.runtime.parser.processor; + +import java.util.stream.Collectors; +import org.enso.runtime.parser.processor.GeneratedClassContext.ClassField; + +final class HashCodeMethodGenerator { + private final GeneratedClassContext ctx; + + HashCodeMethodGenerator(GeneratedClassContext ctx) { + this.ctx = ctx; + } + + String generateMethodCode() { + var fieldList = + ctx.getAllFields().stream().map(ClassField::name).collect(Collectors.joining(", ")); + var code = + """ + @Override + public int hashCode() { + return Objects.hash($fieldList); + } + """ + .replace("$fieldList", fieldList); + return Utils.indent(code, 2); + } +} diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java index d4b70c2e1699..cfb0bbd288ce 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java @@ -40,6 +40,8 @@ final class IRNodeClassGenerator { private final SetLocationMethodGenerator setLocationMethodGenerator; private final BuilderMethodGenerator builderMethodGenerator; private final MapExpressionsMethodGenerator mapExpressionsMethodGenerator; + private final EqualsMethodGenerator equalsMethodGenerator; + private final HashCodeMethodGenerator hashCodeMethodGenerator; /** * For every method annotated with {@link IRCopyMethod}, there is a generator. Can be empty. Not @@ -52,6 +54,7 @@ final class IRNodeClassGenerator { "java.util.UUID", "java.util.ArrayList", "java.util.function.Function", + "java.util.Objects", "org.enso.compiler.core.Identifier", "org.enso.compiler.core.IR", "org.enso.compiler.core.ir.DiagnosticStorage", @@ -93,6 +96,8 @@ final class IRNodeClassGenerator { findCopyMethods().stream() .map(copyMethod -> new CopyMethodGenerator(copyMethod, generatedClassContext)) .toList(); + this.equalsMethodGenerator = new EqualsMethodGenerator(generatedClassContext); + this.hashCodeMethodGenerator = new HashCodeMethodGenerator(generatedClassContext); var nestedTypes = interfaceType.getEnclosedElements().stream() .filter( @@ -171,6 +176,10 @@ public static Builder builder() { $copyMethods + $equalsMethod + + $hashCodeMethod + $builder """ .replace("$fields", fieldsCode()) @@ -179,6 +188,8 @@ public static Builder builder() { .replace("$overrideIRMethods", overrideIRMethods()) .replace("$mapExpressionsMethod", mapExpressions()) .replace("$copyMethods", copyMethods()) + .replace("$equalsMethod", equalsMethodGenerator.generateMethodCode()) + .replace("$hashCodeMethod", hashCodeMethodGenerator.generateMethodCode()) .replace("$builder", builderMethodGenerator.generateBuilder()); } From e294877046c2cb7301f9922d27c4f5d7bbbf4711 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 2 Dec 2024 20:10:35 +0100 Subject: [PATCH 90/93] Explicitly list all the processor classes in javacOptions --- build.sbt | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/build.sbt b/build.sbt index 45fe3633d86a..1fa62d92211d 100644 --- a/build.sbt +++ b/build.sbt @@ -3216,10 +3216,20 @@ lazy val `runtime-parser` = Compile / moduleDependencies ++= Seq( "org.netbeans.api" % "org-openide-util-lookup" % netbeansApiVersion ), - Compile / javacOptions ++= Seq( - "-processor", - "org.enso.runtime.parser.processor.IRProcessor" - ), + // Java compiler is not able to correctly find all the annotation processor, because + // one of the is on module-path. To overcome this, we explicitly list all of them here. + Compile / javacOptions ++= { + val processorClasses = Seq( + "org.enso.runtime.parser.processor.IRProcessor", + "org.enso.persist.impl.PersistableProcessor", + "org.netbeans.modules.openide.util.ServiceProviderProcessor", + "org.netbeans.modules.openide.util.NamedServiceProcessor" + ).mkString(",") + Seq( + "-processor", + processorClasses + ) + }, Compile / internalModuleDependencies := Seq( (`syntax-rust-definition` / Compile / exportedModule).value, (`persistance` / Compile / exportedModule).value, From 00ad5f67a50086fd0bf38278965aa3d93fa4a680 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Tue, 3 Dec 2024 11:46:28 +0100 Subject: [PATCH 91/93] Refactor field class to field package --- .../processor/DuplicateMethodGenerator.java | 1 + .../parser/processor/GeneratedClassContext.java | 1 + .../parser/processor/IRNodeClassGenerator.java | 2 ++ .../MapExpressionsMethodGenerator.java | 1 + .../enso/runtime/parser/processor/Utils.java | 17 +++++++++-------- .../parser/processor/{ => field}/Field.java | 4 ++-- .../processor/{ => field}/FieldCollector.java | 9 +++++---- .../parser/processor/{ => field}/ListField.java | 2 +- .../processor/{ => field}/PrimitiveField.java | 2 +- .../processor/{ => field}/ReferenceField.java | 3 ++- 10 files changed, 25 insertions(+), 17 deletions(-) rename engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/{ => field}/Field.java (96%) rename engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/{ => field}/FieldCollector.java (94%) rename engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/{ => field}/ListField.java (96%) rename engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/{ => field}/PrimitiveField.java (94%) rename engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/{ => field}/ReferenceField.java (93%) diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/DuplicateMethodGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/DuplicateMethodGenerator.java index 00c96fa4cf62..0e3e96db3073 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/DuplicateMethodGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/DuplicateMethodGenerator.java @@ -6,6 +6,7 @@ import java.util.stream.Collectors; import javax.lang.model.element.ExecutableElement; import javax.lang.model.type.TypeKind; +import org.enso.runtime.parser.processor.field.Field; /** * Code generator for {@code org.enso.compiler.core.ir.IR#duplicate} method or any of its override. diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/GeneratedClassContext.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/GeneratedClassContext.java index a73c3d4566cb..841ccf3eff7b 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/GeneratedClassContext.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/GeneratedClassContext.java @@ -5,6 +5,7 @@ import java.util.Objects; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.TypeElement; +import org.enso.runtime.parser.processor.field.Field; /** * A context created for the generated class. Everything that is needed for the code generation of a diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java index cfb0bbd288ce..9472d698656a 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java @@ -12,6 +12,8 @@ import org.enso.runtime.parser.dsl.IRChild; import org.enso.runtime.parser.dsl.IRCopyMethod; import org.enso.runtime.parser.dsl.IRNode; +import org.enso.runtime.parser.processor.field.Field; +import org.enso.runtime.parser.processor.field.FieldCollector; /** * Generates code for interfaces annotated with {@link org.enso.runtime.parser.dsl.IRNode}. diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/MapExpressionsMethodGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/MapExpressionsMethodGenerator.java index 568449277691..f56b86803305 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/MapExpressionsMethodGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/MapExpressionsMethodGenerator.java @@ -4,6 +4,7 @@ import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; +import org.enso.runtime.parser.processor.field.Field; final class MapExpressionsMethodGenerator { private final ExecutableElement mapExpressionsMethod; diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java index d31657923f96..e282485c104b 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java @@ -14,7 +14,7 @@ import javax.lang.model.type.TypeMirror; import javax.tools.Diagnostic.Kind; -final class Utils { +public final class Utils { private static final String MAP_EXPRESSIONS = "mapExpressions"; private static final String DUPLICATE = "duplicate"; @@ -22,7 +22,7 @@ final class Utils { private Utils() {} /** Returns true if the given {@code type} is a subtype of {@code org.enso.compiler.core.IR}. */ - static boolean isSubtypeOfIR(TypeElement type, ProcessingEnvironment processingEnv) { + public static boolean isSubtypeOfIR(TypeElement type, ProcessingEnvironment processingEnv) { var irIfaceFound = iterateSuperInterfaces( type, @@ -42,13 +42,14 @@ static boolean isSubtypeOfIR(TypeElement type, ProcessingEnvironment processingE } /** Returns true if the given {@code type} is an {@code org.enso.compiler.core.IR} interface. */ - static boolean isIRInterface(TypeMirror type, ProcessingEnvironment processingEnv) { + public static boolean isIRInterface(TypeMirror type, ProcessingEnvironment processingEnv) { var elem = processingEnv.getTypeUtils().asElement(type); return elem.getKind() == ElementKind.INTERFACE && elem.getSimpleName().toString().equals("IR"); } /** Returns true if the given type extends {@link org.enso.compiler.core.ir.Expression} */ - static boolean isSubtypeOfExpression(TypeMirror type, ProcessingEnvironment processingEnv) { + public static boolean isSubtypeOfExpression( + TypeMirror type, ProcessingEnvironment processingEnv) { var expressionType = processingEnv .getElementUtils() @@ -57,11 +58,11 @@ static boolean isSubtypeOfExpression(TypeMirror type, ProcessingEnvironment proc return processingEnv.getTypeUtils().isAssignable(type, expressionType); } - static void printError(String msg, Element elem, Messager messager) { + public static void printError(String msg, Element elem, Messager messager) { messager.printMessage(Kind.ERROR, msg, elem); } - static void printErrorAndFail(String msg, Element elem, Messager messager) { + public static void printErrorAndFail(String msg, Element elem, Messager messager) { printError(msg, elem, messager); throw new IllegalStateException("Unexpected failure during annotation processing: " + msg); } @@ -72,7 +73,7 @@ static String indent(String code, int indentation) { .collect(Collectors.joining(System.lineSeparator())); } - static boolean isScalaList(TypeElement type, ProcessingEnvironment procEnv) { + public static boolean isScalaList(TypeElement type, ProcessingEnvironment procEnv) { var scalaListType = procEnv.getElementUtils().getTypeElement("scala.collection.immutable.List"); return procEnv.getTypeUtils().isAssignable(type.asType(), scalaListType.asType()); } @@ -89,7 +90,7 @@ static boolean isScalaList(TypeElement type, ProcessingEnvironment procEnv) { * @param procEnv * @return */ - static boolean hasImplementation( + public static boolean hasImplementation( ExecutableElement method, TypeElement interfaceType, ProcessingEnvironment procEnv) { var defImplFound = iterateSuperInterfaces( diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Field.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/Field.java similarity index 96% rename from engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Field.java rename to engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/Field.java index 42c08d1fc062..b5d1277ebf3c 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Field.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/Field.java @@ -1,4 +1,4 @@ -package org.enso.runtime.parser.processor; +package org.enso.runtime.parser.processor.field; import java.util.List; import java.util.function.Function; @@ -9,7 +9,7 @@ * A field of an IR node. Represented by any parameterless method on an interface annotated with * {@link org.enso.runtime.parser.dsl.IRNode}. */ -interface Field { +public interface Field { /** Name (identifier) of the field. */ String getName(); diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/FieldCollector.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/FieldCollector.java similarity index 94% rename from engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/FieldCollector.java rename to engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/FieldCollector.java index 2612a5068670..e6c905b08bb0 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/FieldCollector.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/FieldCollector.java @@ -1,4 +1,4 @@ -package org.enso.runtime.parser.processor; +package org.enso.runtime.parser.processor.field; import java.util.ArrayDeque; import java.util.Deque; @@ -12,12 +12,13 @@ import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; import org.enso.runtime.parser.dsl.IRChild; +import org.enso.runtime.parser.processor.Utils; /** * Collects abstract parameterless methods from the given interface and all its superinterfaces - * these will be represented as fields in the generated classes, hence the name. */ -final class FieldCollector { +public final class FieldCollector { private final ProcessingEnvironment processingEnv; private final TypeElement irNodeInterface; // Mapped by field name @@ -26,13 +27,13 @@ final class FieldCollector { /** * @param irNodeInterface For this interface, fields will be collected. */ - FieldCollector(ProcessingEnvironment processingEnv, TypeElement irNodeInterface) { + public FieldCollector(ProcessingEnvironment processingEnv, TypeElement irNodeInterface) { assert irNodeInterface.getKind() == ElementKind.INTERFACE; this.processingEnv = processingEnv; this.irNodeInterface = irNodeInterface; } - List collectFields() { + public List collectFields() { var superInterfaces = irNodeInterface.getInterfaces(); Deque toProcess = new ArrayDeque<>(); toProcess.add(irNodeInterface.asType()); diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ListField.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/ListField.java similarity index 96% rename from engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ListField.java rename to engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/ListField.java index 91a87298a79c..6942d19df2a5 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ListField.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/ListField.java @@ -1,4 +1,4 @@ -package org.enso.runtime.parser.processor; +package org.enso.runtime.parser.processor.field; import java.util.List; import javax.lang.model.element.TypeElement; diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/PrimitiveField.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/PrimitiveField.java similarity index 94% rename from engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/PrimitiveField.java rename to engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/PrimitiveField.java index e7f5e40d9d95..5bd90afb7c74 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/PrimitiveField.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/PrimitiveField.java @@ -1,4 +1,4 @@ -package org.enso.runtime.parser.processor; +package org.enso.runtime.parser.processor.field; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ReferenceField.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/ReferenceField.java similarity index 93% rename from engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ReferenceField.java rename to engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/ReferenceField.java index c3cc5861ba04..d8505b6393df 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ReferenceField.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/ReferenceField.java @@ -1,8 +1,9 @@ -package org.enso.runtime.parser.processor; +package org.enso.runtime.parser.processor.field; import java.util.List; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.TypeElement; +import org.enso.runtime.parser.processor.Utils; final class ReferenceField implements Field { private final ProcessingEnvironment procEnv; From e6c7609bef85f88ac3d97d644d925225c3db4e05 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Tue, 3 Dec 2024 11:50:35 +0100 Subject: [PATCH 92/93] Refactor method generation classes to methodgen package --- .../processor/GeneratedClassContext.java | 24 +++++++++---------- .../processor/IRNodeClassGenerator.java | 7 ++++++ .../enso/runtime/parser/processor/Utils.java | 8 +++---- .../BuilderMethodGenerator.java | 10 ++++---- .../{ => methodgen}/CopyMethodGenerator.java | 10 ++++---- .../DuplicateMethodGenerator.java | 10 ++++---- .../EqualsMethodGenerator.java | 11 +++++---- .../HashCodeMethodGenerator.java | 10 ++++---- .../MapExpressionsMethodGenerator.java | 11 +++++---- .../SetLocationMethodGenerator.java | 9 +++---- 10 files changed, 66 insertions(+), 44 deletions(-) rename engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/{ => methodgen}/BuilderMethodGenerator.java (92%) rename engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/{ => methodgen}/CopyMethodGenerator.java (91%) rename engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/{ => methodgen}/DuplicateMethodGenerator.java (96%) rename engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/{ => methodgen}/EqualsMethodGenerator.java (79%) rename engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/{ => methodgen}/HashCodeMethodGenerator.java (62%) rename engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/{ => methodgen}/MapExpressionsMethodGenerator.java (93%) rename engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/{ => methodgen}/SetLocationMethodGenerator.java (87%) diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/GeneratedClassContext.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/GeneratedClassContext.java index 841ccf3eff7b..bf10c52b72f8 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/GeneratedClassContext.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/GeneratedClassContext.java @@ -11,7 +11,7 @@ * A context created for the generated class. Everything that is needed for the code generation of a * single class is contained in this class. */ -final class GeneratedClassContext { +public final class GeneratedClassContext { private final String className; private final List userFields; private final List constructorParameters; @@ -59,32 +59,32 @@ private static void ensureSimpleName(String name) { } } - ClassField getLocationMetaField() { + public ClassField getLocationMetaField() { return locationMetaField; } - ClassField getPassDataMetaField() { + public ClassField getPassDataMetaField() { return passDataMetaField; } - ClassField getDiagnosticsMetaField() { + public ClassField getDiagnosticsMetaField() { return diagnosticsMetaField; } - ClassField getIdMetaField() { + public ClassField getIdMetaField() { return idMetaField; } - List getConstructorParameters() { + public List getConstructorParameters() { return constructorParameters; } - List getUserFields() { + public List getUserFields() { return userFields; } /** Returns simple name of the class that is being generated. */ - String getClassName() { + public String getClassName() { return className; } @@ -92,7 +92,7 @@ List getMetaFields() { return metaFields; } - List getAllFields() { + public List getAllFields() { var allFields = new ArrayList(metaFields); for (var userField : userFields) { allFields.add( @@ -101,11 +101,11 @@ List getAllFields() { return allFields; } - ProcessingEnvironment getProcessingEnvironment() { + public ProcessingEnvironment getProcessingEnvironment() { return processingEnvironment; } - TypeElement getIrNodeInterface() { + public TypeElement getIrNodeInterface() { return irNodeInterface; } @@ -127,7 +127,7 @@ public String toString() { * * @param modifiers */ - record ClassField(String modifiers, String type, String name) { + public record ClassField(String modifiers, String type, String name) { @Override public String toString() { return modifiers + " " + type + " " + name; diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java index 9472d698656a..252b198c7e80 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java @@ -14,6 +14,13 @@ import org.enso.runtime.parser.dsl.IRNode; import org.enso.runtime.parser.processor.field.Field; import org.enso.runtime.parser.processor.field.FieldCollector; +import org.enso.runtime.parser.processor.methodgen.BuilderMethodGenerator; +import org.enso.runtime.parser.processor.methodgen.CopyMethodGenerator; +import org.enso.runtime.parser.processor.methodgen.DuplicateMethodGenerator; +import org.enso.runtime.parser.processor.methodgen.EqualsMethodGenerator; +import org.enso.runtime.parser.processor.methodgen.HashCodeMethodGenerator; +import org.enso.runtime.parser.processor.methodgen.MapExpressionsMethodGenerator; +import org.enso.runtime.parser.processor.methodgen.SetLocationMethodGenerator; /** * Generates code for interfaces annotated with {@link org.enso.runtime.parser.dsl.IRNode}. diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java index e282485c104b..e2300f64df3c 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java @@ -67,7 +67,7 @@ public static void printErrorAndFail(String msg, Element elem, Messager messager throw new IllegalStateException("Unexpected failure during annotation processing: " + msg); } - static String indent(String code, int indentation) { + public static String indent(String code, int indentation) { return code.lines() .map(line -> " ".repeat(indentation) + line) .collect(Collectors.joining(System.lineSeparator())); @@ -167,7 +167,7 @@ static ExecutableElement findDuplicateMethod( return duplicateMethod; } - static ExecutableElement findMapExpressionsMethod( + public static ExecutableElement findMapExpressionsMethod( TypeElement interfaceType, ProcessingEnvironment processingEnv) { var mapExprsMethod = findMethod( @@ -180,11 +180,11 @@ static ExecutableElement findMapExpressionsMethod( return mapExprsMethod; } - static void hardAssert(boolean condition) { + public static void hardAssert(boolean condition) { hardAssert(condition, "Assertion failed"); } - static void hardAssert(boolean condition, String msg) { + public static void hardAssert(boolean condition, String msg) { if (!condition) { throw new AssertionError(msg); } diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/BuilderMethodGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/BuilderMethodGenerator.java similarity index 92% rename from engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/BuilderMethodGenerator.java rename to engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/BuilderMethodGenerator.java index 546a5271b7ab..209cd75496b6 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/BuilderMethodGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/BuilderMethodGenerator.java @@ -1,7 +1,9 @@ -package org.enso.runtime.parser.processor; +package org.enso.runtime.parser.processor.methodgen; import java.util.stream.Collectors; +import org.enso.runtime.parser.processor.GeneratedClassContext; import org.enso.runtime.parser.processor.GeneratedClassContext.ClassField; +import org.enso.runtime.parser.processor.Utils; /** * Code generator for builder. Builder is a nested static class inside the generated class. Builder @@ -10,14 +12,14 @@ * class object and prefills all the fields with the values from the object. This copy constructor * is called from either the {@code duplicate} method or from copy methods. */ -class BuilderMethodGenerator { +public class BuilderMethodGenerator { private final GeneratedClassContext generatedClassContext; - BuilderMethodGenerator(GeneratedClassContext generatedClassContext) { + public BuilderMethodGenerator(GeneratedClassContext generatedClassContext) { this.generatedClassContext = generatedClassContext; } - String generateBuilder() { + public String generateBuilder() { var fieldDeclarations = generatedClassContext.getAllFields().stream() .map( diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/CopyMethodGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/CopyMethodGenerator.java similarity index 91% rename from engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/CopyMethodGenerator.java rename to engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/CopyMethodGenerator.java index 1251c13200ad..f31da27cd629 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/CopyMethodGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/CopyMethodGenerator.java @@ -1,4 +1,4 @@ -package org.enso.runtime.parser.processor; +package org.enso.runtime.parser.processor.methodgen; import java.util.HashMap; import java.util.Map; @@ -7,15 +7,17 @@ import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.VariableElement; import org.enso.runtime.parser.dsl.IRCopyMethod; +import org.enso.runtime.parser.processor.GeneratedClassContext; import org.enso.runtime.parser.processor.GeneratedClassContext.ClassField; +import org.enso.runtime.parser.processor.Utils; /** Code generator for methods annotated with {@link IRCopyMethod}. */ -class CopyMethodGenerator { +public class CopyMethodGenerator { private final ExecutableElement copyMethod; private final GeneratedClassContext ctx; private final Map parameterMapping = new HashMap<>(); - CopyMethodGenerator(ExecutableElement copyMethod, GeneratedClassContext ctx) { + public CopyMethodGenerator(ExecutableElement copyMethod, GeneratedClassContext ctx) { this.ctx = ctx; ensureIsAnnotated(copyMethod); this.copyMethod = Objects.requireNonNull(copyMethod); @@ -60,7 +62,7 @@ private String simpleTypeName(VariableElement parameter) { .toString(); } - String generateCopyMethod() { + public String generateCopyMethod() { var sb = new StringBuilder(); sb.append("@Override").append(System.lineSeparator()); var argList = diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/DuplicateMethodGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/DuplicateMethodGenerator.java similarity index 96% rename from engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/DuplicateMethodGenerator.java rename to engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/DuplicateMethodGenerator.java index 0e3e96db3073..c86b4cc056c9 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/DuplicateMethodGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/DuplicateMethodGenerator.java @@ -1,4 +1,4 @@ -package org.enso.runtime.parser.processor; +package org.enso.runtime.parser.processor.methodgen; import java.util.ArrayList; import java.util.List; @@ -6,13 +6,15 @@ import java.util.stream.Collectors; import javax.lang.model.element.ExecutableElement; import javax.lang.model.type.TypeKind; +import org.enso.runtime.parser.processor.GeneratedClassContext; +import org.enso.runtime.parser.processor.Utils; import org.enso.runtime.parser.processor.field.Field; /** * Code generator for {@code org.enso.compiler.core.ir.IR#duplicate} method or any of its override. * Note that in the interface hierarchy, there can be an override with a different return type. */ -class DuplicateMethodGenerator { +public class DuplicateMethodGenerator { private final ExecutableElement duplicateMethod; private final GeneratedClassContext ctx; private static final List parameters = @@ -25,7 +27,7 @@ class DuplicateMethodGenerator { /** * @param duplicateMethod ExecutableElement representing the duplicate method (or its override). */ - DuplicateMethodGenerator(ExecutableElement duplicateMethod, GeneratedClassContext ctx) { + public DuplicateMethodGenerator(ExecutableElement duplicateMethod, GeneratedClassContext ctx) { ensureDuplicateMethodHasExpectedSignature(duplicateMethod); this.ctx = Objects.requireNonNull(ctx); this.duplicateMethod = Objects.requireNonNull(duplicateMethod); @@ -45,7 +47,7 @@ private static void ensureDuplicateMethodHasExpectedSignature(ExecutableElement } } - String generateDuplicateMethodCode() { + public String generateDuplicateMethodCode() { var sb = new StringBuilder(); sb.append("@Override").append(System.lineSeparator()); sb.append("public ") diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/EqualsMethodGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/EqualsMethodGenerator.java similarity index 79% rename from engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/EqualsMethodGenerator.java rename to engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/EqualsMethodGenerator.java index d426d8ab6911..27958cb0ce3f 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/EqualsMethodGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/EqualsMethodGenerator.java @@ -1,13 +1,16 @@ -package org.enso.runtime.parser.processor; +package org.enso.runtime.parser.processor.methodgen; -final class EqualsMethodGenerator { +import org.enso.runtime.parser.processor.GeneratedClassContext; +import org.enso.runtime.parser.processor.Utils; + +public final class EqualsMethodGenerator { private final GeneratedClassContext ctx; - EqualsMethodGenerator(GeneratedClassContext ctx) { + public EqualsMethodGenerator(GeneratedClassContext ctx) { this.ctx = ctx; } - String generateMethodCode() { + public String generateMethodCode() { var sb = new StringBuilder(); sb.append("@Override").append(System.lineSeparator()); sb.append("public boolean equals(Object o) {").append(System.lineSeparator()); diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/HashCodeMethodGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/HashCodeMethodGenerator.java similarity index 62% rename from engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/HashCodeMethodGenerator.java rename to engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/HashCodeMethodGenerator.java index b46412a1ff94..0ded21008205 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/HashCodeMethodGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/HashCodeMethodGenerator.java @@ -1,16 +1,18 @@ -package org.enso.runtime.parser.processor; +package org.enso.runtime.parser.processor.methodgen; import java.util.stream.Collectors; +import org.enso.runtime.parser.processor.GeneratedClassContext; import org.enso.runtime.parser.processor.GeneratedClassContext.ClassField; +import org.enso.runtime.parser.processor.Utils; -final class HashCodeMethodGenerator { +public final class HashCodeMethodGenerator { private final GeneratedClassContext ctx; - HashCodeMethodGenerator(GeneratedClassContext ctx) { + public HashCodeMethodGenerator(GeneratedClassContext ctx) { this.ctx = ctx; } - String generateMethodCode() { + public String generateMethodCode() { var fieldList = ctx.getAllFields().stream().map(ClassField::name).collect(Collectors.joining(", ")); var code = diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/MapExpressionsMethodGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/MapExpressionsMethodGenerator.java similarity index 93% rename from engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/MapExpressionsMethodGenerator.java rename to engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/MapExpressionsMethodGenerator.java index f56b86803305..aa3c9201f798 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/MapExpressionsMethodGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/MapExpressionsMethodGenerator.java @@ -1,12 +1,14 @@ -package org.enso.runtime.parser.processor; +package org.enso.runtime.parser.processor.methodgen; import java.util.Objects; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; +import org.enso.runtime.parser.processor.GeneratedClassContext; +import org.enso.runtime.parser.processor.Utils; import org.enso.runtime.parser.processor.field.Field; -final class MapExpressionsMethodGenerator { +public final class MapExpressionsMethodGenerator { private final ExecutableElement mapExpressionsMethod; private final GeneratedClassContext ctx; private static final String METHOD_NAME = "mapExpressions"; @@ -16,7 +18,8 @@ final class MapExpressionsMethodGenerator { * which the class is generated. * @param ctx */ - MapExpressionsMethodGenerator(ExecutableElement mapExpressionsMethod, GeneratedClassContext ctx) { + public MapExpressionsMethodGenerator( + ExecutableElement mapExpressionsMethod, GeneratedClassContext ctx) { ensureMapExpressionsMethodHasExpectedSignature(mapExpressionsMethod); this.mapExpressionsMethod = mapExpressionsMethod; this.ctx = Objects.requireNonNull(ctx); @@ -33,7 +36,7 @@ private void ensureMapExpressionsMethodHasExpectedSignature( } } - String generateMapExpressionsMethodCode() { + public String generateMapExpressionsMethodCode() { var sb = new StringBuilder(); sb.append("@Override").append(System.lineSeparator()); sb.append("public ") diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/SetLocationMethodGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/SetLocationMethodGenerator.java similarity index 87% rename from engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/SetLocationMethodGenerator.java rename to engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/SetLocationMethodGenerator.java index 2aa8d05705a8..9ce9ceaed8b0 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/SetLocationMethodGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/SetLocationMethodGenerator.java @@ -1,13 +1,14 @@ -package org.enso.runtime.parser.processor; +package org.enso.runtime.parser.processor.methodgen; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.ExecutableElement; +import org.enso.runtime.parser.processor.Utils; -class SetLocationMethodGenerator { +public class SetLocationMethodGenerator { private final ExecutableElement setLocationMethod; private final ProcessingEnvironment processingEnv; - SetLocationMethodGenerator( + public SetLocationMethodGenerator( ExecutableElement setLocationMethod, ProcessingEnvironment processingEnv) { ensureCorrectSignature(setLocationMethod); this.processingEnv = processingEnv; @@ -26,7 +27,7 @@ private static void ensureCorrectSignature(ExecutableElement setLocationMethod) } } - String generateMethodCode() { + public String generateMethodCode() { var code = """ @Override From c8b7e349d29bc26f74b7e98cf73b2d889bdf8d2a Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Tue, 3 Dec 2024 11:52:59 +0100 Subject: [PATCH 93/93] Introduce utils package --- .../parser/processor/IRNodeClassGenerator.java | 1 + .../enso/runtime/parser/processor/IRProcessor.java | 1 + .../parser/processor/field/FieldCollector.java | 2 +- .../parser/processor/field/ReferenceField.java | 2 +- .../processor/methodgen/BuilderMethodGenerator.java | 2 +- .../processor/methodgen/CopyMethodGenerator.java | 2 +- .../processor/methodgen/DuplicateMethodGenerator.java | 2 +- .../processor/methodgen/EqualsMethodGenerator.java | 2 +- .../processor/methodgen/HashCodeMethodGenerator.java | 2 +- .../methodgen/MapExpressionsMethodGenerator.java | 2 +- .../methodgen/SetLocationMethodGenerator.java | 2 +- .../{ => utils}/InterfaceHierarchyVisitor.java | 4 ++-- .../runtime/parser/processor/{ => utils}/Utils.java | 11 ++++++----- 13 files changed, 19 insertions(+), 16 deletions(-) rename engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/{ => utils}/InterfaceHierarchyVisitor.java (87%) rename engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/{ => utils}/Utils.java (96%) diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java index 252b198c7e80..89101485442e 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRNodeClassGenerator.java @@ -21,6 +21,7 @@ import org.enso.runtime.parser.processor.methodgen.HashCodeMethodGenerator; import org.enso.runtime.parser.processor.methodgen.MapExpressionsMethodGenerator; import org.enso.runtime.parser.processor.methodgen.SetLocationMethodGenerator; +import org.enso.runtime.parser.processor.utils.Utils; /** * Generates code for interfaces annotated with {@link org.enso.runtime.parser.dsl.IRNode}. diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java index 313d4ca840c7..8080988ba7a6 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessor.java @@ -16,6 +16,7 @@ import javax.lang.model.util.SimpleElementVisitor14; import javax.tools.JavaFileObject; import org.enso.runtime.parser.dsl.IRNode; +import org.enso.runtime.parser.processor.utils.Utils; @SupportedAnnotationTypes({ "org.enso.runtime.parser.dsl.IRNode", diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/FieldCollector.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/FieldCollector.java index e6c905b08bb0..5d3c798e2b8a 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/FieldCollector.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/FieldCollector.java @@ -12,7 +12,7 @@ import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; import org.enso.runtime.parser.dsl.IRChild; -import org.enso.runtime.parser.processor.Utils; +import org.enso.runtime.parser.processor.utils.Utils; /** * Collects abstract parameterless methods from the given interface and all its superinterfaces - diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/ReferenceField.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/ReferenceField.java index d8505b6393df..ec40774422c3 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/ReferenceField.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/ReferenceField.java @@ -3,7 +3,7 @@ import java.util.List; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.TypeElement; -import org.enso.runtime.parser.processor.Utils; +import org.enso.runtime.parser.processor.utils.Utils; final class ReferenceField implements Field { private final ProcessingEnvironment procEnv; diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/BuilderMethodGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/BuilderMethodGenerator.java index 209cd75496b6..87117ebd4828 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/BuilderMethodGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/BuilderMethodGenerator.java @@ -3,7 +3,7 @@ import java.util.stream.Collectors; import org.enso.runtime.parser.processor.GeneratedClassContext; import org.enso.runtime.parser.processor.GeneratedClassContext.ClassField; -import org.enso.runtime.parser.processor.Utils; +import org.enso.runtime.parser.processor.utils.Utils; /** * Code generator for builder. Builder is a nested static class inside the generated class. Builder diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/CopyMethodGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/CopyMethodGenerator.java index f31da27cd629..098c49867662 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/CopyMethodGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/CopyMethodGenerator.java @@ -9,7 +9,7 @@ import org.enso.runtime.parser.dsl.IRCopyMethod; import org.enso.runtime.parser.processor.GeneratedClassContext; import org.enso.runtime.parser.processor.GeneratedClassContext.ClassField; -import org.enso.runtime.parser.processor.Utils; +import org.enso.runtime.parser.processor.utils.Utils; /** Code generator for methods annotated with {@link IRCopyMethod}. */ public class CopyMethodGenerator { diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/DuplicateMethodGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/DuplicateMethodGenerator.java index c86b4cc056c9..4acd6073f25d 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/DuplicateMethodGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/DuplicateMethodGenerator.java @@ -7,8 +7,8 @@ import javax.lang.model.element.ExecutableElement; import javax.lang.model.type.TypeKind; import org.enso.runtime.parser.processor.GeneratedClassContext; -import org.enso.runtime.parser.processor.Utils; import org.enso.runtime.parser.processor.field.Field; +import org.enso.runtime.parser.processor.utils.Utils; /** * Code generator for {@code org.enso.compiler.core.ir.IR#duplicate} method or any of its override. diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/EqualsMethodGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/EqualsMethodGenerator.java index 27958cb0ce3f..c8319ec0216c 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/EqualsMethodGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/EqualsMethodGenerator.java @@ -1,7 +1,7 @@ package org.enso.runtime.parser.processor.methodgen; import org.enso.runtime.parser.processor.GeneratedClassContext; -import org.enso.runtime.parser.processor.Utils; +import org.enso.runtime.parser.processor.utils.Utils; public final class EqualsMethodGenerator { private final GeneratedClassContext ctx; diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/HashCodeMethodGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/HashCodeMethodGenerator.java index 0ded21008205..a9463d2ed8ce 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/HashCodeMethodGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/HashCodeMethodGenerator.java @@ -3,7 +3,7 @@ import java.util.stream.Collectors; import org.enso.runtime.parser.processor.GeneratedClassContext; import org.enso.runtime.parser.processor.GeneratedClassContext.ClassField; -import org.enso.runtime.parser.processor.Utils; +import org.enso.runtime.parser.processor.utils.Utils; public final class HashCodeMethodGenerator { private final GeneratedClassContext ctx; diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/MapExpressionsMethodGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/MapExpressionsMethodGenerator.java index aa3c9201f798..64c972afbd09 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/MapExpressionsMethodGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/MapExpressionsMethodGenerator.java @@ -5,8 +5,8 @@ import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import org.enso.runtime.parser.processor.GeneratedClassContext; -import org.enso.runtime.parser.processor.Utils; import org.enso.runtime.parser.processor.field.Field; +import org.enso.runtime.parser.processor.utils.Utils; public final class MapExpressionsMethodGenerator { private final ExecutableElement mapExpressionsMethod; diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/SetLocationMethodGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/SetLocationMethodGenerator.java index 9ce9ceaed8b0..3efd3d0ad955 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/SetLocationMethodGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/SetLocationMethodGenerator.java @@ -2,7 +2,7 @@ import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.ExecutableElement; -import org.enso.runtime.parser.processor.Utils; +import org.enso.runtime.parser.processor.utils.Utils; public class SetLocationMethodGenerator { private final ExecutableElement setLocationMethod; diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/InterfaceHierarchyVisitor.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/InterfaceHierarchyVisitor.java similarity index 87% rename from engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/InterfaceHierarchyVisitor.java rename to engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/InterfaceHierarchyVisitor.java index e83d09a1eb1d..89ebed243817 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/InterfaceHierarchyVisitor.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/InterfaceHierarchyVisitor.java @@ -1,4 +1,4 @@ -package org.enso.runtime.parser.processor; +package org.enso.runtime.parser.processor.utils; import javax.lang.model.element.TypeElement; @@ -9,7 +9,7 @@ * com.oracle.truffle.api.frame.FrameInstanceVisitor}. */ @FunctionalInterface -interface InterfaceHierarchyVisitor { +public interface InterfaceHierarchyVisitor { /** * Visits the interface hierarchy of the given interface. * diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java similarity index 96% rename from engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java rename to engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java index e2300f64df3c..c6a887592a1c 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/Utils.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java @@ -1,4 +1,4 @@ -package org.enso.runtime.parser.processor; +package org.enso.runtime.parser.processor.utils; import java.lang.annotation.Annotation; import java.util.ArrayDeque; @@ -126,7 +126,7 @@ static boolean hasModifier(ExecutableElement method, Modifier modifier) { * @param methodPredicate Predicate that is called for each method in the hierarchy. * @return Method that satisfies the predicate or null if no such method is found. */ - static ExecutableElement findMethod( + public static ExecutableElement findMethod( TypeElement interfaceType, ProcessingEnvironment procEnv, Predicate methodPredicate) { @@ -156,7 +156,7 @@ static ExecutableElement findMethod( * searched transitively. * @return not null. */ - static ExecutableElement findDuplicateMethod( + public static ExecutableElement findDuplicateMethod( TypeElement interfaceType, ProcessingEnvironment procEnv) { var duplicateMethod = findMethod(interfaceType, procEnv, Utils::isDuplicateMethod); hardAssert( @@ -190,7 +190,8 @@ public static void hardAssert(boolean condition, String msg) { } } - static boolean hasAnnotation(Element element, Class annotationClass) { + public static boolean hasAnnotation( + Element element, Class annotationClass) { return element.getAnnotation(annotationClass) != null; } @@ -205,7 +206,7 @@ private static boolean isDuplicateMethod(ExecutableElement executableElement) { * @param ifaceVisitor Visitor that is called for each interface. * @param */ - static T iterateSuperInterfaces( + public static T iterateSuperInterfaces( TypeElement type, ProcessingEnvironment processingEnv, InterfaceHierarchyVisitor ifaceVisitor) {