From 6e331205da678f18125ef9eed9abccb807b59b71 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Sun, 6 Oct 2024 21:19:22 +0200 Subject: [PATCH 001/195] 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 002/195] 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 003/195] 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 004/195] 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 005/195] 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 006/195] 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 007/195] 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 008/195] 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 009/195] 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 010/195] 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 011/195] 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 012/195] 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 013/195] 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 014/195] 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 015/195] 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 016/195] 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 017/195] 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 018/195] 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 019/195] 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 020/195] 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 021/195] 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 022/195] 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 023/195] 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 024/195] 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 025/195] 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 026/195] 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 027/195] 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 028/195] 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 029/195] 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 030/195] 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 031/195] 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 032/195] 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 033/195] 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 034/195] 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 035/195] 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 036/195] 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 037/195] 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 038/195] 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 039/195] 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 040/195] 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 041/195] 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 042/195] 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 043/195] 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 044/195] 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 045/195] 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 046/195] 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 047/195] 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 048/195] 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 049/195] 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 050/195] 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 051/195] 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 052/195] 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 053/195] 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 054/195] 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 055/195] 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 056/195] 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 057/195] 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 058/195] 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 059/195] 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 060/195] 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 061/195] 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 062/195] 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 063/195] 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 064/195] 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 065/195] 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 066/195] 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 067/195] 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 068/195] 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 069/195] 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 070/195] 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 071/195] 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 072/195] 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 073/195] 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 074/195] 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 075/195] 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 076/195] 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 077/195] 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 078/195] 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 079/195] 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 080/195] 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 081/195] 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 082/195] 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 083/195] 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 084/195] 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 085/195] 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 086/195] 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 087/195] 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 088/195] 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 089/195] 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 090/195] 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 091/195] 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 092/195] 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 093/195] 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) { From fadf97369743c088257d71c8a55c8823328308df Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Wed, 4 Dec 2024 21:31:35 +0100 Subject: [PATCH 094/195] Add GenerateIR annotation --- .../enso/runtime/parser/dsl/GenerateIR.java | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/GenerateIR.java diff --git a/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/GenerateIR.java b/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/GenerateIR.java new file mode 100644 index 000000000000..93560c97625a --- /dev/null +++ b/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/GenerateIR.java @@ -0,0 +1,62 @@ +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; + +/** + * A class annotated with this annotation will be processed by the IR processor. The processor will + * generate a super class that is meant to be extended by this class. The generated class will have + * the same package as this class, and its name will have the "Gen" suffix. Majority of the methods + * in the generated class will be either private or package-private, so that they are not accessible + * from the outside. + * + *

The class can be enclosed (nested inside) an interface. + * + *

Fields

+ * + * The generated class will contain 4 meta fields that are required to be present inside + * every IR element: + * + *
    + *
  • {@code private DiagnosticStorage diagnostics} + *
  • {@code private MetadataStorage passData} + *
  • {@code private IdentifiedLocation location} + *
  • {@code private UUID id} + *
+ * + * Apart from these meta fields, the generated class will also contain user-defined + * fields. User-defined fields are inferred from all the parameters of the constructor. The + * parameter of the constructor can be one of the following: + * + *
    + *
  • Any reference, or primitive type without annotation + *
  • A subtype of {@code org.enso.compiler.ir.IR} annotated with {@link IRChild} + *
  • One of the meta types mentioned above + *
+ * + * A user-defined field generated out of constructor parameter annotated with {@link IRChild} is a + * child element of this IR element. That means that it will be included in generated implementation + * of IR methods that iterate over the IR tree. For example {@code mapExpressions} or {@code + * children}. + * + *

A user-defined field generated out of constructor parameter that is not annotated with {@link + * IRChild} will be just a field for which there will be generated a getter. + * + *

For a constructor parameter of a meta type, there will be no user-defined field generated, as + * the meta fields are always generated. + */ +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.TYPE) +public @interface GenerateIR { + + /** + * Name of the interfaces that the generated superclass must implement. All the interfaces must be + * subtypes of the {@code org.enso.compiler.ir.IR} interface. All the abstract parameterless + * methods from all the interfaces will be implemented by the generated class. Must not be empty. + * + * @return + */ + String[] interfaces() default {}; +} From b549bf77ca73355356ee17c972db5b85312c098b Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Wed, 4 Dec 2024 21:31:46 +0100 Subject: [PATCH 095/195] Remove tests using the IRNode annotation --- .../processor/test/gen/ir/CopyNameTestIR.java | 19 ------- .../processor/test/gen/ir/ListTestIR.java | 15 ------ .../processor/test/gen/ir/NameTestIR.java | 10 ---- .../processor/test/gen/ir/OptNameTestIR.java | 11 ---- .../test/gen/ir/core/JCallArgument.java | 18 ------- .../test/gen/ir/core/JDefinitionArgument.java | 21 -------- .../processor/test/gen/ir/core/JEmpty.java | 36 ------------- .../test/gen/ir/core/JExpression.java | 46 ---------------- .../processor/test/gen/ir/core/JModule.java | 17 ------ .../processor/test/gen/ir/core/JName.java | 52 ------------------- 10 files changed, 245 deletions(-) delete mode 100644 engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/CopyNameTestIR.java delete mode 100644 engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/ListTestIR.java delete mode 100644 engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/NameTestIR.java delete mode 100644 engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/OptNameTestIR.java delete mode 100644 engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JCallArgument.java delete mode 100644 engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JDefinitionArgument.java delete mode 100644 engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JEmpty.java delete mode 100644 engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JExpression.java delete mode 100644 engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JModule.java delete mode 100644 engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JName.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 deleted file mode 100644 index 14e1c4fb6d19..000000000000 --- a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/CopyNameTestIR.java +++ /dev/null @@ -1,19 +0,0 @@ -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 deleted file mode 100644 index 1fd580d00d33..000000000000 --- a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/ListTestIR.java +++ /dev/null @@ -1,15 +0,0 @@ -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(); - - @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 deleted file mode 100644 index c03a5b069323..000000000000 --- a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/NameTestIR.java +++ /dev/null @@ -1,10 +0,0 @@ -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/OptNameTestIR.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/OptNameTestIR.java deleted file mode 100644 index b6443ec5031e..000000000000 --- a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/OptNameTestIR.java +++ /dev/null @@ -1,11 +0,0 @@ -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/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JCallArgument.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JCallArgument.java deleted file mode 100644 index 150949228daf..000000000000 --- a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JCallArgument.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.enso.runtime.parser.processor.test.gen.ir.core; - -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; - -@IRNode -public interface JCallArgument extends IR { - @IRChild(required = false) - Name name(); - - @IRChild - Expression value(); - - interface JSpecified extends JCallArgument {} -} diff --git a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JDefinitionArgument.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JDefinitionArgument.java deleted file mode 100644 index 37cae56ec8d5..000000000000 --- a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JDefinitionArgument.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.enso.runtime.parser.processor.test.gen.ir.core; - -import org.enso.compiler.core.IR; -import org.enso.runtime.parser.dsl.IRChild; -import org.enso.runtime.parser.dsl.IRNode; - -@IRNode -public interface JDefinitionArgument extends IR { - @IRChild - JName name(); - - @IRChild(required = false) - JExpression ascribedType(); - - @IRChild(required = false) - JExpression defaultValue(); - - boolean suspended(); - - interface JSpecified extends JDefinitionArgument {} -} 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 deleted file mode 100644 index 59d51aa08614..000000000000 --- a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JEmpty.java +++ /dev/null @@ -1,36 +0,0 @@ -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); -} diff --git a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JExpression.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JExpression.java deleted file mode 100644 index bf6fe1f79fb1..000000000000 --- a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JExpression.java +++ /dev/null @@ -1,46 +0,0 @@ -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.IRNode; -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(); - - @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). - */ - interface JBinding extends JExpression { - @IRChild - JName name(); - - @IRChild - JExpression expression(); - } -} diff --git a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JModule.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JModule.java deleted file mode 100644 index 4162b197882b..000000000000 --- a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JModule.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.enso.runtime.parser.processor.test.gen.ir.core; - -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.module.scope.JExport; -import org.enso.runtime.parser.processor.test.gen.ir.module.scope.JImport; -import scala.collection.immutable.List; - -@IRNode -public interface JModule extends IR { - @IRChild - List imports(); - - @IRChild - List exports(); -} diff --git a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JName.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JName.java deleted file mode 100644 index 58c07d32e934..000000000000 --- a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JName.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.enso.runtime.parser.processor.test.gen.ir.core; - -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 -public interface JName extends JExpression { - String name(); - - boolean isMethod(); - - @Override - JName duplicate( - boolean keepLocations, - boolean keepMetadata, - boolean keepDiagnostics, - boolean keepIdentifiers); - - interface JBlank extends JName { - static JBlank create() { - return JNameGen.JBlankGen.builder().build(); - } - } - - interface JLiteral extends JName { - @IRChild(required = false) - JName originalName(); - } - - interface JQualified extends JName { - @IRChild - List parts(); - - @Override - default String name() { - return parts().map(JName::name).mkString("."); - } - } - - interface JSelf extends JName { - boolean synthetic(); - } - - interface JAnnotation extends JName, JDefinition {} - - interface JGenericAnnotation extends JAnnotation { - @IRChild - JExpression expression(); - } -} From f6f0e4dacfef45554b210012e3350e14bc003a94 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Wed, 4 Dec 2024 21:32:58 +0100 Subject: [PATCH 096/195] Revert "Empty extends Expression" This reverts commit 4a0ea14a3752dcc149c704f856059a2368cf7ad2. --- .../java/org/enso/compiler/core/ir/Empty.java | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 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 18dc87f286ac..96ec7dc8ce06 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,11 +1,12 @@ 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 Expression { +public interface Empty extends IR { static Empty createEmpty() { return EmptyGen.builder().build(); } @@ -14,15 +15,13 @@ 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 70deacaac93ac34781cb5d1394d4c159d3983609 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Wed, 4 Dec 2024 21:33:04 +0100 Subject: [PATCH 097/195] Revert "Fix compilation after Refactoring Empty to an interface with @IRNode" This reverts commit 5ac716a01517cb12dd919a96271d4932ade714f9. --- .../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, 12 insertions(+), 14 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 75953dff7807..6f2ab1e20910 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,9 +227,7 @@ case object LambdaConsolidate extends IRPass { } val shadower: IR = - mShadower.getOrElse( - Empty.createFromLocation(spec.identifiedLocation) - ) + mShadower.getOrElse(Empty(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 0383e9c09bd6..26629b1c9478 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.createEmpty()) + )(Empty(identifiedLocation = null)) 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 5ae324828226..a119cfe64321 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.createEmpty(), + Empty(identifiedLocation = null), 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 45f5afa77dad..e387e7908f44 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.createEmpty() - val right = Empty.createEmpty() - val rightArg = CallArgument.Specified(None, Empty.createEmpty(), false, null) + val left = Empty(null) + val right = Empty(null) + val rightArg = CallArgument.Specified(None, Empty(null), 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.createEmpty() - val right = Empty.createEmpty() + val left = Empty(identifiedLocation = null) + val right = Empty(identifiedLocation = null) val rightArg = CallArgument.Specified( None, - Empty.createEmpty(), + Empty(identifiedLocation = null), 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 6bfd3706dcad..14e2fd759503 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: Wed, 4 Dec 2024 21:33:24 +0100 Subject: [PATCH 098/195] Revert "Replace Scala Empty with Java Empty annotated with @IRNode" This reverts commit aa345ed456cbc05db2baed83bc4b034e973ceeb6. --- .../java/org/enso/compiler/core/ir/Empty.java | 27 ------ .../org/enso/compiler/core/ir/Empty.scala | 89 +++++++++++++++++++ 2 files changed, 89 insertions(+), 27 deletions(-) delete mode 100644 engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/Empty.java create 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 deleted file mode 100644 index 96ec7dc8ce06..000000000000 --- a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/Empty.java +++ /dev/null @@ -1,27 +0,0 @@ -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 new file mode 100644 index 000000000000..849d58cd9401 --- /dev/null +++ b/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/Empty.scala @@ -0,0 +1,89 @@ +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 088b61416b93bf1d2b63dc49da81361f058db682 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Wed, 4 Dec 2024 21:34:43 +0100 Subject: [PATCH 099/195] Remove all test interfaces using IRNode annotation --- .../processor/test/gen/ir/module/JScope.java | 8 --- .../test/gen/ir/module/scope/JDefinition.java | 57 ------------------- .../test/gen/ir/module/scope/JExport.java | 53 ----------------- .../test/gen/ir/module/scope/JImport.java | 51 ----------------- 4 files changed, 169 deletions(-) delete mode 100644 engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/module/JScope.java delete mode 100644 engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/module/scope/JDefinition.java delete mode 100644 engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/module/scope/JExport.java delete mode 100644 engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/module/scope/JImport.java diff --git a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/module/JScope.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/module/JScope.java deleted file mode 100644 index 6742b1839e99..000000000000 --- a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/module/JScope.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.enso.runtime.parser.processor.test.gen.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-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 deleted file mode 100644 index 42c0be0292b7..000000000000 --- a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/module/scope/JDefinition.java +++ /dev/null @@ -1,57 +0,0 @@ -package org.enso.runtime.parser.processor.test.gen.ir.module.scope; - -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.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; - -@IRNode -public interface JDefinition extends JScope { - interface JType extends JDefinition { - @IRChild - JName name(); - - @IRChild - List params(); - - @IRChild - List members(); - } - - /** The definition of an atom constructor and its associated arguments. */ - 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. - */ - 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-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 deleted file mode 100644 index 53b26778d147..000000000000 --- a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/module/scope/JExport.java +++ /dev/null @@ -1,53 +0,0 @@ -package org.enso.runtime.parser.processor.test.gen.ir.module.scope; - -import org.enso.runtime.parser.dsl.IRChild; -import org.enso.runtime.parser.dsl.IRNode; -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; - -@IRNode -public interface JExport extends JScope { - 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().apply(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().exists(n -> n.name().equalsIgnoreCase(name)); - } - return true; - } - } -} 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 deleted file mode 100644 index ff74f509324c..000000000000 --- a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/module/scope/JImport.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.enso.runtime.parser.processor.test.gen.ir.module.scope; - -import org.enso.runtime.parser.dsl.IRChild; -import org.enso.runtime.parser.dsl.IRNode; -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; - -/** Module-level import statements. */ -@IRNode -public interface JImport extends JScope { - 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().exists(n -> n.name().equals(name)); - } - if (hiddenNames() != null) { - return hiddenNames().forall(n -> !n.name().equals(name)); - } - return true; - } - } -} From 3a3cc143d5dd1f84b99ff91289fdc4f3bb3318ed Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Wed, 4 Dec 2024 21:37:31 +0100 Subject: [PATCH 100/195] Remove TestGeneratedIR --- .../processor/test/TestGeneratedIR.java | 128 ------------------ 1 file changed, 128 deletions(-) delete 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/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 deleted file mode 100644 index 728caa56bae8..000000000000 --- a/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestGeneratedIR.java +++ /dev/null @@ -1,128 +0,0 @@ -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; -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; -import org.enso.runtime.parser.processor.test.gen.ir.NameTestIRGen; -import org.enso.runtime.parser.processor.test.gen.ir.OptNameTestIRGen; -import org.junit.Test; - -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")); - } - - @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(); - 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())); - } - - @Test - public void duplicateRespectsParameters() { - var location = new IdentifiedLocation(new Location(1, 2)); - 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())); - } - - @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 11d2a257b0e59501cd69da90d34191fd4bcaa9c6 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Wed, 4 Dec 2024 21:59:21 +0100 Subject: [PATCH 101/195] Update TestIRPRocessorInline. Remove IRNode annotation --- .../processor/test/TestIRProcessorInline.java | 452 +++--------------- 1 file changed, 71 insertions(+), 381 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 28e464dcf17e..9951b28c76d7 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 @@ -3,7 +3,6 @@ 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; @@ -47,15 +46,14 @@ private static void expectCompilationFailure(String src) { } @Test - public void simpleIRNodeWithoutChildren_CompilationSucceeds() { + public void simpleIRNodeWithoutFields_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 {} + import org.enso.runtime.parser.dsl.GenerateIR; + @GenerateIR + public final class JName {} """); var compiler = Compiler.javac().withProcessors(new IRProcessor()); var compilation = compiler.compile(src); @@ -63,113 +61,36 @@ public interface JName extends IR {} } @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 annotatedInterfaceMustNotExtendTwoIRInterfaces() { + public void onlyFinalClassCanBeAnnotated() { var src = JavaFileObjects.forSourceString( - "Hello", + "JName", """ - 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 {} + import org.enso.runtime.parser.dsl.GenerateIR; + @GenerateIR + public class JName {} """); 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"); + CompilationSubject.assertThat(compilation).hadErrorContaining("final"); } @Test - public void annotationCanOnlyBeAppliedToInterface() { + public void simpleIRNodeWithUserDefinedFiled_CompilationSucceeds() { 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 childAnnotation_MustBeAppliedToIRField() { - expectCompilationFailure( """ - import org.enso.runtime.parser.dsl.IRNode; - import org.enso.runtime.parser.dsl.IRChild; - import org.enso.compiler.core.IR; - - @IRNode - public interface MyIR extends IR { - @IRChild String expression(); - } - """); - } - - @Test - public void simpleIRNodeWithoutChildren_GeneratesSource() { - 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(); - 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)); - 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; + import org.enso.runtime.parser.dsl.GenerateIR; - @IRNode - public interface Hello extends IR { - static String name() { - return "Hello"; - } + @GenerateIR + public final class JName { + public JName(String name) {} } - """); - assertThat(src, not(containsString("\"Hello\""))); + """; + var genClass = generatedClass("JName", src); + assertThat(genClass, containsString("class JNameGen")); + assertThat("Getter for 'name' generated", genClass, containsString("String name()")); } @Test @@ -178,14 +99,13 @@ public void simpleIRNodeWithChild() { generatedClass( "MyIR", """ - import org.enso.runtime.parser.dsl.IRNode; + import org.enso.runtime.parser.dsl.GenerateIR; import org.enso.runtime.parser.dsl.IRChild; - import org.enso.compiler.core.IR; import org.enso.compiler.core.ir.Expression; - @IRNode - public interface MyIR extends IR { - @IRChild Expression expression(); + @GenerateIR + public final class MyIR { + public MyIR(@IRChild Expression expression) {} } """); assertThat(genSrc, containsString("Expression expression()")); @@ -197,35 +117,53 @@ public void irNodeWithMultipleFields_PrimitiveField() { generatedClass( "MyIR", """ - import org.enso.runtime.parser.dsl.IRNode; + import org.enso.runtime.parser.dsl.GenerateIR; import org.enso.runtime.parser.dsl.IRChild; - import org.enso.compiler.core.IR; - @IRNode - public interface MyIR extends IR { - boolean suspended(); + @GenerateIR + public final class MyIR { + public MyIR(boolean suspended) {} } """); assertThat(genSrc, containsString("boolean suspended()")); } + @Test + public void interfacesMustBeSubtypeOfIR() { + var src = + JavaFileObjects.forSourceString( + "MyIR", + """ + import org.enso.runtime.parser.dsl.GenerateIR; + + interface MySuperIR { + boolean suspended(); + } + + @GenerateIR(interfaces = {"MySuperIR"}) + public final class MyIR { } + """); + var compiler = Compiler.javac().withProcessors(new IRProcessor()); + var compilation = compiler.compile(src); + CompilationSubject.assertThat(compilation).failed(); + CompilationSubject.assertThat(compilation).hadErrorContaining("subtype"); + } + @Test public void irNodeWithInheritedField() { var src = generatedClass( "MyIR", """ - import org.enso.runtime.parser.dsl.IRNode; - import org.enso.runtime.parser.dsl.IRChild; + import org.enso.runtime.parser.dsl.GenerateIR; import org.enso.compiler.core.IR; interface MySuperIR extends IR { boolean suspended(); } - @IRNode - public interface MyIR extends MySuperIR { - } + @GenerateIR(interfaces = {"MySuperIR"}) + public final class MyIR { } """); assertThat(src, containsString("boolean suspended()")); @@ -237,17 +175,16 @@ public void irNodeWithInheritedField_Override() { generatedClass( "MyIR", """ - import org.enso.runtime.parser.dsl.IRNode; - import org.enso.runtime.parser.dsl.IRChild; + import org.enso.runtime.parser.dsl.GenerateIR; import org.enso.compiler.core.IR; interface MySuperIR extends IR { boolean suspended(); } - @IRNode - public interface MyIR extends MySuperIR { - boolean suspended(); + @GenerateIR + public final class MyIR { + public MyIR(boolean suspended) {} } """); @@ -260,8 +197,7 @@ public void irNodeWithInheritedField_Transitive() { generatedClass( "MyIR", """ - import org.enso.runtime.parser.dsl.IRNode; - import org.enso.runtime.parser.dsl.IRChild; + import org.enso.runtime.parser.dsl.GenerateIR; import org.enso.compiler.core.IR; interface MySuperSuperIR extends IR { @@ -271,299 +207,53 @@ interface MySuperSuperIR extends IR { interface MySuperIR extends MySuperSuperIR { } - @IRNode - public interface MyIR extends MySuperIR { + @GenerateIR + public final class MyIR { } """); assertThat(src, containsString("boolean suspended()")); } @Test - public void irNodeAsNestedInterface() { + public void irNodeAsNestedClass() { var src = generatedClass( "JName", """ - import org.enso.runtime.parser.dsl.IRNode; + import org.enso.runtime.parser.dsl.GenerateIR; import org.enso.compiler.core.IR; - @IRNode public interface JName extends IR { String name(); - interface JBlank extends JName {} + @GenerateIR(interfaces = {"JName"}) + final class JBlank {} } """); - assertThat(src, containsString("public final class JNameGen")); - assertThat(src, containsString("public static final class JBlankGen implements JName.JBlank")); + assertThat(src, containsString("class JBlankGen implements JName")); } @Test - public void returnValueCanBeScalaList() { + public void fieldCanBeScalaList() { var src = generatedClass( "JName", """ - import org.enso.runtime.parser.dsl.IRNode; + import org.enso.runtime.parser.dsl.GenerateIR; 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(); + @GenerateIR + public final class JName { + public JName(@IRChild List expressions) {} } """); - assertThat(src, containsString("public final class JNameGen")); + assertThat(src, containsString("class JNameGen")); assertThat(src, containsString("List expressions")); } - @Test - public void processorDoesNotGenerateOverridenMethods() { - var src = - generatedClass( - "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; - } - } - } - """); - assertThat(src, containsString("public final class JNameGen")); - assertThat(src, not(containsString("String name()"))); - } - - @Test - public void overrideCorrectMethods() { - var src = - generatedClass( - "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(); - } - } - """); - assertThat(src, containsString("class JBlockGen")); - assertThat(src, containsString("class JBindingGen")); - } + // TODO: Can contain multiple GenerateIR annotations in single source - @Test - public void canOverrideMethodsFromIR() { - var src = - generatedClass( - "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 {} - } - """); - 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")); - } - - @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")); - } - - @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(")); - } - - @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); - } - """); - } - - @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(")); - } - - @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(")); - } + // TODO: Multiple interfaces in the annotation } From e6205f1d1214f23e3695584f7cd806de47790a2b Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Wed, 4 Dec 2024 21:59:33 +0100 Subject: [PATCH 102/195] GenerateIR uses IR as the default interface --- .../src/main/java/org/enso/runtime/parser/dsl/GenerateIR.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/GenerateIR.java b/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/GenerateIR.java index 93560c97625a..be531fab82b1 100644 --- a/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/GenerateIR.java +++ b/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/GenerateIR.java @@ -58,5 +58,5 @@ * * @return */ - String[] interfaces() default {}; + String[] interfaces() default {"org.enso.compiler.core.IR"}; } From ea17b2f0470b23cc675ff82229bc0e79bcb9673e Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Thu, 5 Dec 2024 17:38:29 +0100 Subject: [PATCH 103/195] Skeleton of implementation --- .../runtime/parser/dsl/GenerateFields.java | 53 ++++ .../enso/runtime/parser/dsl/GenerateIR.java | 35 +-- .../org/enso/runtime/parser/dsl/IRChild.java | 2 +- .../org/enso/runtime/parser/dsl/IRField.java | 19 ++ .../processor/test/TestIRProcessorInline.java | 65 +++- .../processor/GeneratedClassContext.java | 13 +- .../processor/IRNodeClassGenerator.java | 151 ++-------- .../runtime/parser/processor/IRProcessor.java | 279 ++++++++---------- .../parser/processor/ProcessedClass.java | 39 +++ .../processor/field/FieldCollector.java | 130 ++++---- .../methodgen/CopyMethodGenerator.java | 97 ------ .../runtime/parser/processor/utils/Utils.java | 43 +-- 12 files changed, 396 insertions(+), 530 deletions(-) create mode 100644 engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/GenerateFields.java create mode 100644 engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/IRField.java create mode 100644 engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ProcessedClass.java delete mode 100644 engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/CopyMethodGenerator.java diff --git a/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/GenerateFields.java b/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/GenerateFields.java new file mode 100644 index 000000000000..25bd4c8b3222 --- /dev/null +++ b/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/GenerateFields.java @@ -0,0 +1,53 @@ +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; + +/** + * Parameters of the constructor annotated with this annotation will be scanned by the IR processor + * and fieldswill be generated for them. There can be only a single constructor with this + * annotation in a class. The enclosing class must be annotated with {@link GenerateIR}. + * + *

Fields

+ * + * The generated class will contain 4 meta fields that are required to be present inside + * every IR element: + * + *
    + *
  • {@code private DiagnosticStorage diagnostics} + *
  • {@code private MetadataStorage passData} + *
  • {@code private IdentifiedLocation location} + *
  • {@code private UUID id} + *
+ * + * Apart from these meta fields, the generated class will also contain user-defined + * fields. User-defined fields are inferred from all the parameters of the constructor annotated + * with {@link GenerateFields}. The parameter of the constructor can be one of the following: + * + *
    + *
  • Any reference, or primitive type without annotation + *
  • Any reference, or primitive type annotated with {@link IRField} + *
  • A subtype of {@code org.enso.compiler.ir.IR} annotated with {@link IRChild} + *
  • One of the meta types mentioned above + *
+ * + * Constructor parameters without any annotation will not be processed at all. There will be no code + * generated for them. + * + *

A user-defined field generated out of constructor parameter annotated with {@link IRChild} is + * a child element of this IR element. That means that it will be included in generated + * implementation of IR methods that iterate over the IR tree. For example {@code mapExpressions} or + * {@code children}. + * + *

A user-defined field generated out of constructor parameter annotated with {@link IRField} + * will be a field with generated getters. Such field however, will not be part of the IR tree + * traversal methods. + * + *

For a constructor parameter of a meta type, there will be no user-defined field generated, as + * the meta fields are always generated. + */ +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.CONSTRUCTOR) +public @interface GenerateFields {} diff --git a/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/GenerateIR.java b/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/GenerateIR.java index be531fab82b1..d6e8a01b3424 100644 --- a/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/GenerateIR.java +++ b/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/GenerateIR.java @@ -14,38 +14,7 @@ * *

The class can be enclosed (nested inside) an interface. * - *

Fields

- * - * The generated class will contain 4 meta fields that are required to be present inside - * every IR element: - * - *
    - *
  • {@code private DiagnosticStorage diagnostics} - *
  • {@code private MetadataStorage passData} - *
  • {@code private IdentifiedLocation location} - *
  • {@code private UUID id} - *
- * - * Apart from these meta fields, the generated class will also contain user-defined - * fields. User-defined fields are inferred from all the parameters of the constructor. The - * parameter of the constructor can be one of the following: - * - *
    - *
  • Any reference, or primitive type without annotation - *
  • A subtype of {@code org.enso.compiler.ir.IR} annotated with {@link IRChild} - *
  • One of the meta types mentioned above - *
- * - * A user-defined field generated out of constructor parameter annotated with {@link IRChild} is a - * child element of this IR element. That means that it will be included in generated implementation - * of IR methods that iterate over the IR tree. For example {@code mapExpressions} or {@code - * children}. - * - *

A user-defined field generated out of constructor parameter that is not annotated with {@link - * IRChild} will be just a field for which there will be generated a getter. - * - *

For a constructor parameter of a meta type, there will be no user-defined field generated, as - * the meta fields are always generated. + *

The class must contain a single constructor annotated with {@link GenerateFields}. */ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.TYPE) @@ -58,5 +27,5 @@ * * @return */ - String[] interfaces() default {"org.enso.compiler.core.IR"}; + String interfaces() default "org.enso.compiler.core.IR"; } 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 fe31bb942289..8be0bebb7573 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 @@ -13,7 +13,7 @@ * return a subtype of {@code org.enso.compiler.ir.IR}. */ @Retention(RetentionPolicy.SOURCE) -@Target(ElementType.METHOD) +@Target(ElementType.PARAMETER) 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/IRField.java b/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/IRField.java new file mode 100644 index 000000000000..c850b84d1746 --- /dev/null +++ b/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/IRField.java @@ -0,0 +1,19 @@ +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; + +/** + * A constructor parameter annotated with this annotation will have a corresponding user-defined + * field generated in the super class (See {@link GenerateFields} for docs about fields). + * + *

There is no restriction on the type of the parameter. + */ +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.PARAMETER) +public @interface IRField { + /** If true, the field will always be non-null. Otherwise, it can be null. */ + boolean required() default true; +} 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 9951b28c76d7..352b9be0cde2 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 @@ -52,8 +52,13 @@ public void simpleIRNodeWithoutFields_CompilationSucceeds() { "JName", """ import org.enso.runtime.parser.dsl.GenerateIR; + import org.enso.runtime.parser.dsl.GenerateFields; + @GenerateIR - public final class JName {} + public final class JName { + @GenerateFields + public JName() {} + } """); var compiler = Compiler.javac().withProcessors(new IRProcessor()); var compilation = compiler.compile(src); @@ -77,6 +82,23 @@ public class JName {} CompilationSubject.assertThat(compilation).hadErrorContaining("final"); } + @Test + public void annotatedClassMustHaveAnnotatedConstructor() { + var src = + JavaFileObjects.forSourceString( + "JName", + """ + import org.enso.runtime.parser.dsl.GenerateIR; + @GenerateIR + public final class JName {} + """); + var compiler = Compiler.javac().withProcessors(new IRProcessor()); + var compilation = compiler.compile(src); + CompilationSubject.assertThat(compilation).failed(); + CompilationSubject.assertThat(compilation) + .hadErrorContaining("must have exactly one constructor annotated with @GenerateFields"); + } + @Test public void simpleIRNodeWithUserDefinedFiled_CompilationSucceeds() { var src = @@ -100,11 +122,13 @@ public void simpleIRNodeWithChild() { "MyIR", """ import org.enso.runtime.parser.dsl.GenerateIR; + import org.enso.runtime.parser.dsl.GenerateFields; import org.enso.runtime.parser.dsl.IRChild; import org.enso.compiler.core.ir.Expression; @GenerateIR public final class MyIR { + @GenerateFields public MyIR(@IRChild Expression expression) {} } """); @@ -118,10 +142,12 @@ public void irNodeWithMultipleFields_PrimitiveField() { "MyIR", """ import org.enso.runtime.parser.dsl.GenerateIR; + import org.enso.runtime.parser.dsl.GenerateFields; import org.enso.runtime.parser.dsl.IRChild; @GenerateIR public final class MyIR { + @GenerateFields public MyIR(boolean suspended) {} } """); @@ -135,13 +161,17 @@ public void interfacesMustBeSubtypeOfIR() { "MyIR", """ import org.enso.runtime.parser.dsl.GenerateIR; + import org.enso.runtime.parser.dsl.GenerateFields; interface MySuperIR { boolean suspended(); } - @GenerateIR(interfaces = {"MySuperIR"}) - public final class MyIR { } + @GenerateIR(interfaces = "MySuperIR") + public final class MyIR { + @GenerateFields + public MyIR() {} + } """); var compiler = Compiler.javac().withProcessors(new IRProcessor()); var compilation = compiler.compile(src); @@ -156,15 +186,18 @@ public void irNodeWithInheritedField() { "MyIR", """ import org.enso.runtime.parser.dsl.GenerateIR; + import org.enso.runtime.parser.dsl.GenerateFields; import org.enso.compiler.core.IR; interface MySuperIR extends IR { boolean suspended(); } - @GenerateIR(interfaces = {"MySuperIR"}) - public final class MyIR { } - + @GenerateIR(interfaces = "MySuperIR") + public final class MyIR { + @GenerateFields + public MyIR() {} + } """); assertThat(src, containsString("boolean suspended()")); } @@ -176,6 +209,8 @@ public void irNodeWithInheritedField_Override() { "MyIR", """ import org.enso.runtime.parser.dsl.GenerateIR; + import org.enso.runtime.parser.dsl.GenerateFields; + import org.enso.runtime.parser.dsl.IRField; import org.enso.compiler.core.IR; interface MySuperIR extends IR { @@ -184,7 +219,8 @@ interface MySuperIR extends IR { @GenerateIR public final class MyIR { - public MyIR(boolean suspended) {} + @GenerateFields + public MyIR(@IRField boolean suspended) {} } """); @@ -198,6 +234,8 @@ public void irNodeWithInheritedField_Transitive() { "MyIR", """ import org.enso.runtime.parser.dsl.GenerateIR; + import org.enso.runtime.parser.dsl.GenerateFields; + import org.enso.runtime.parser.dsl.IRField; import org.enso.compiler.core.IR; interface MySuperSuperIR extends IR { @@ -207,8 +245,10 @@ interface MySuperSuperIR extends IR { interface MySuperIR extends MySuperSuperIR { } - @GenerateIR + @GenerateIR(interfaces = "MySuperIR") public final class MyIR { + @GenerateFields + public MyIR(@IRField boolean suspended) {} } """); assertThat(src, containsString("boolean suspended()")); @@ -221,16 +261,21 @@ public void irNodeAsNestedClass() { "JName", """ import org.enso.runtime.parser.dsl.GenerateIR; + import org.enso.runtime.parser.dsl.GenerateFields; import org.enso.compiler.core.IR; public interface JName extends IR { String name(); - @GenerateIR(interfaces = {"JName"}) - final class JBlank {} + @GenerateIR(interfaces = "JName") + public final class JBlank { + @GenerateFields + public JBlank(@IRField String name) {} + } } """); assertThat(src, containsString("class JBlankGen implements JName")); + assertThat(src, containsString("String name()")); } @Test 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 bf10c52b72f8..f945275a02a1 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 @@ -4,7 +4,6 @@ import java.util.List; import java.util.Objects; import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.TypeElement; import org.enso.runtime.parser.processor.field.Field; /** @@ -16,7 +15,7 @@ public final class GeneratedClassContext { private final List userFields; private final List constructorParameters; private final ProcessingEnvironment processingEnvironment; - private final TypeElement irNodeInterface; + private final ProcessedClass processedClass; private static final ClassField diagnosticsMetaField = new ClassField("private", "DiagnosticStorage", "diagnostics"); @@ -34,18 +33,16 @@ public final class GeneratedClassContext { * @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) { + ProcessedClass processedClass) { this.className = Objects.requireNonNull(className); this.userFields = Objects.requireNonNull(userFields); this.processingEnvironment = Objects.requireNonNull(processingEnvironment); - this.irNodeInterface = irNodeInterface; + this.processedClass = processedClass; ensureSimpleName(className); this.constructorParameters = getAllFields().stream() @@ -105,10 +102,6 @@ public ProcessingEnvironment getProcessingEnvironment() { return processingEnvironment; } - public TypeElement getIrNodeInterface() { - return irNodeInterface; - } - /** * Method parameter * 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 89101485442e..dd889e37157d 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,21 +1,13 @@ package org.enso.runtime.parser.processor; -import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; 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; 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; @@ -24,23 +16,12 @@ import org.enso.runtime.parser.processor.utils.Utils; /** - * 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 org.enso.compiler.core.IR#children()}. + * Generates code for a super class for a class annotated with {@link + * org.enso.runtime.parser.dsl.GenerateIR}. */ final class IRNodeClassGenerator { private final ProcessingEnvironment processingEnv; - private final TypeElement interfaceType; + private final ProcessedClass processedClass; /** Name of the class that is being generated */ private final String className; @@ -53,12 +34,6 @@ final class IRNodeClassGenerator { private final EqualsMethodGenerator equalsMethodGenerator; private final HashCodeMethodGenerator hashCodeMethodGenerator; - /** - * For every method annotated with {@link IRCopyMethod}, there is a generator. Can be empty. Not - * null. - */ - private final List copyMethodGenerators; - private static final Set defaultImportedTypes = Set.of( "java.util.UUID", @@ -75,70 +50,35 @@ final class IRNodeClassGenerator { "scala.Option"); /** - * @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. */ IRNodeClassGenerator( - ProcessingEnvironment processingEnv, TypeElement interfaceType, String className) { + ProcessingEnvironment processingEnv, ProcessedClass processedClass, String className) { assert !className.contains(".") : "Class name should be simple, not qualified"; this.processingEnv = processingEnv; - this.interfaceType = interfaceType; + this.processedClass = processedClass; this.className = className; - var userFields = getAllUserFields(interfaceType); - var duplicateMethod = Utils.findDuplicateMethod(interfaceType, processingEnv); + var userFields = getAllUserFields(processedClass); + var duplicateMethod = + Utils.findDuplicateMethod(processedClass.getInterfaceElem(), processingEnv); this.generatedClassContext = - new GeneratedClassContext(className, userFields, processingEnv, interfaceType); + new GeneratedClassContext(className, userFields, processingEnv, processedClass); this.duplicateMethodGenerator = new DuplicateMethodGenerator(duplicateMethod, generatedClassContext); this.builderMethodGenerator = new BuilderMethodGenerator(generatedClassContext); - var mapExpressionsMethod = Utils.findMapExpressionsMethod(interfaceType, processingEnv); + var mapExpressionsMethod = + Utils.findMapExpressionsMethod(processedClass.getInterfaceElem(), processingEnv); this.mapExpressionsMethodGenerator = new MapExpressionsMethodGenerator(mapExpressionsMethod, generatedClassContext); var setLocationMethod = Utils.findMethod( - interfaceType, + processedClass.getInterfaceElem(), processingEnv, method -> method.getSimpleName().toString().equals("setLocation")); this.setLocationMethodGenerator = new SetLocationMethodGenerator(setLocationMethod, processingEnv); - this.copyMethodGenerators = - 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( - elem -> - elem.getKind() == ElementKind.INTERFACE || elem.getKind() == ElementKind.CLASS) - .toList(); - if (!nestedTypes.isEmpty()) { - throw new RuntimeException("Nested types must be handled separately: " + nestedTypes); - } - } - - /** - * Finds all the methods annotated with {@link IRCopyMethod} in the interface hierarchy. - * - * @return empty if none. Not null. - */ - 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)) { - copyMethods.add(executableElem); - } - } - return null; - }); - return copyMethods; } /** Returns simple name of the generated class. */ @@ -146,13 +86,6 @@ 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. */ Set imports() { var importsForFields = @@ -169,7 +102,8 @@ Set imports() { /** Generates the body of the class - fields, field setters, method overrides, builder, etc. */ String classBody() { - return """ + var code = + """ $fields $constructor @@ -178,42 +112,31 @@ public static Builder builder() { return new Builder(); } - $overrideUserDefinedMethods + $userDefinedGetters $overrideIRMethods $mapExpressionsMethod - $copyMethods - $equalsMethod $hashCodeMethod $builder """ - .replace("$fields", fieldsCode()) - .replace("$constructor", constructor()) - .replace("$overrideUserDefinedMethods", overrideUserDefinedMethods()) - .replace("$overrideIRMethods", overrideIRMethods()) - .replace("$mapExpressionsMethod", mapExpressions()) - .replace("$copyMethods", copyMethods()) - .replace("$equalsMethod", equalsMethodGenerator.generateMethodCode()) - .replace("$hashCodeMethod", hashCodeMethodGenerator.generateMethodCode()) - .replace("$builder", builderMethodGenerator.generateBuilder()); + .replace("$fields", fieldsCode()) + .replace("$constructor", constructor()) + .replace("$userDefinedGetters", userDefinedGetters()) + .replace("$overrideIRMethods", overrideIRMethods()) + .replace("$mapExpressionsMethod", mapExpressions()) + .replace("$equalsMethod", equalsMethodGenerator.generateMethodCode()) + .replace("$hashCodeMethod", hashCodeMethodGenerator.generateMethodCode()) + .replace("$builder", builderMethodGenerator.generateBuilder()); + return Utils.indent(code, 2); } - /** - * 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 getAllUserFields(TypeElement irNodeInterface) { - var fieldCollector = new FieldCollector(processingEnv, irNodeInterface); + private List getAllUserFields(ProcessedClass processedClass) { + var fieldCollector = new FieldCollector(processingEnv, processedClass); return fieldCollector.collectFields(); } @@ -372,19 +295,13 @@ public String showCode(int indent) { 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 - */ - private String overrideUserDefinedMethods() { + /** Returns string representation of all getters for the user-defined fields. */ + private String userDefinedGetters() { var code = generatedClassContext.getUserFields().stream() .map( field -> """ - @Override public $returnType $fieldName() { return $fieldName; } @@ -395,18 +312,6 @@ private String overrideUserDefinedMethods() { return indent(code, 2); } - /** - * 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 copyMethods() { - return copyMethodGenerators.stream() - .map(CopyMethodGenerator::generateCopyMethod) - .collect(Collectors.joining(System.lineSeparator())); - } - private String mapExpressions() { return Utils.indent(mapExpressionsMethodGenerator.generateMapExpressionsMethodCode(), 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 8080988ba7a6..390af479b939 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,8 +2,6 @@ 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,14 +10,17 @@ import javax.lang.model.SourceVersion; 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.util.SimpleElementVisitor14; import javax.tools.JavaFileObject; +import org.enso.runtime.parser.dsl.GenerateFields; +import org.enso.runtime.parser.dsl.GenerateIR; import org.enso.runtime.parser.dsl.IRNode; import org.enso.runtime.parser.processor.utils.Utils; @SupportedAnnotationTypes({ - "org.enso.runtime.parser.dsl.IRNode", + "org.enso.runtime.parser.dsl.GenerateIR", "org.enso.runtime.parser.dsl.IRChild", "org.enso.runtime.parser.dsl.IRCopyMethod", }) @@ -32,9 +33,12 @@ public SourceVersion getSupportedSourceVersion() { @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { - var irNodeElems = roundEnv.getElementsAnnotatedWith(IRNode.class); - for (var irNodeElem : irNodeElems) { - var suc = processIrNode(irNodeElem); + var generateIRElems = roundEnv.getElementsAnnotatedWith(GenerateIR.class); + for (var generateIRElem : generateIRElems) { + if (!ensureIsClass(generateIRElem)) { + return false; + } + var suc = processGenerateIRElem((TypeElement) generateIRElem); if (!suc) { return false; } @@ -42,25 +46,55 @@ public boolean process(Set annotations, RoundEnvironment return true; } - private boolean processIrNode(Element irNodeElem) { - if (irNodeElem.getKind() != ElementKind.INTERFACE) { - printError("IRNode annotation can only be applied to interfaces", irNodeElem); + private TypeElement findInterface(TypeElement processedClassElem, String interfaceName) { + if (isBinaryName(interfaceName)) { + var iface = processingEnv.getElementUtils().getTypeElement(interfaceName); + return iface; + } else { + var enclosingElem = processedClassElem.getEnclosingElement(); + if (enclosingElem.getKind() == ElementKind.INTERFACE) { + if (enclosingElem.getSimpleName().toString().equals(interfaceName)) { + return (TypeElement) enclosingElem; + } + } else if (enclosingElem.getKind() == ElementKind.PACKAGE) { + return (TypeElement) + enclosingElem.getEnclosedElements().stream() + .filter(pkgElem -> pkgElem.getKind() == ElementKind.INTERFACE) + .filter(ifaceElem -> ifaceElem.getSimpleName().toString().equals(interfaceName)) + .findFirst() + .orElse(null); + } + } + return null; + } + + private static boolean isBinaryName(String name) { + return name.contains("."); + } + + /** + * @param processedClassElem Class annotated with {@link GenerateIR}. + * @return true if processing was successful, false otherwise. + */ + private boolean processGenerateIRElem(TypeElement processedClassElem) { + if (!ensureIsPublicFinal(processedClassElem)) { + return false; + } + if (!ensureEnclosedInInterfaceOrPackage(processedClassElem)) { return false; } - assert irNodeElem instanceof TypeElement; - var irNodeTypeElem = (TypeElement) irNodeElem; - if (!ensureExtendsSingleIRInterface(irNodeTypeElem)) { + if (!ensureHasSingleAnnotatedConstructor(processedClassElem)) { return false; } - var enclosingElem = irNodeElem.getEnclosingElement(); - if (enclosingElem != null && enclosingElem.getKind() != ElementKind.PACKAGE) { - printError("Interface annotated with @IRNode must not be nested", irNodeElem); + var processedClass = constructProcessedClass(processedClassElem); + if (processedClass == null) { + printError("Failed to construct ProcessedClass", processedClassElem); return false; } - var nestedInterfaces = collectNestedInterfaces(irNodeTypeElem); - var irNodeInterfaceName = irNodeTypeElem.getSimpleName().toString(); - var pkgName = packageName(irNodeTypeElem); - var newClassName = irNodeInterfaceName + "Gen"; + + var processedClassName = processedClassElem.getSimpleName().toString(); + var pkgName = packageName(processedClassElem); + var newClassName = processedClassName + "Gen"; String newBinaryName; if (!pkgName.isEmpty()) { newBinaryName = pkgName + "." + newClassName; @@ -70,70 +104,97 @@ private boolean processIrNode(Element irNodeElem) { JavaFileObject srcGen = null; try { - srcGen = processingEnv.getFiler().createSourceFile(newBinaryName, irNodeElem); + srcGen = processingEnv.getFiler().createSourceFile(newBinaryName, processedClassElem); } catch (IOException e) { - printError("Failed to create source file for IRNode", irNodeElem); + printError("Failed to create source file for IRNode", processedClassElem); } assert srcGen != null; String generatedCode; - if (nestedInterfaces.isEmpty()) { - var classGenerator = new IRNodeClassGenerator(processingEnv, irNodeTypeElem, newClassName); - generatedCode = generateSingleNodeClass(classGenerator, pkgName); - } else { - var nestedClassGenerators = - nestedInterfaces.stream() - .map( - iface -> { - var newNestedClassName = iface.getSimpleName().toString() + "Gen"; - return new IRNodeClassGenerator(processingEnv, iface, newNestedClassName); - }) - .toList(); - generatedCode = - generateMultipleNodeClasses( - nestedClassGenerators, pkgName, newClassName, irNodeInterfaceName); - } + var classGenerator = new IRNodeClassGenerator(processingEnv, processedClass, newClassName); + generatedCode = generateSingleNodeClass(classGenerator, processedClass, pkgName); try { try (var lineWriter = new PrintWriter(srcGen.openWriter())) { lineWriter.write(generatedCode); } } catch (IOException e) { - printError("Failed to write to source file for IRNode", irNodeElem); + printError("Failed to write to source file for IRNode", processedClassElem); return false; } 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) { + private ProcessedClass constructProcessedClass(TypeElement processedClassElem) { + var generateIrAnnot = processedClassElem.getAnnotation(GenerateIR.class); + var ifaceToImplement = findInterface(processedClassElem, generateIrAnnot.interfaces()); + if (ifaceToImplement == null) { + printError( + "Could not find interface '" + generateIrAnnot.interfaces() + "'", processedClassElem); + return null; + } + var annotatedCtor = getAnnotatedCtor(processedClassElem); + var processedClass = new ProcessedClass(processedClassElem, annotatedCtor, ifaceToImplement); + return processedClass; + } + + private boolean ensureIsClass(Element elem) { + if (elem.getKind() != ElementKind.CLASS) { + printError("GenerateIR annotation can only be applied to classes", elem); + return false; + } + return true; + } + + private boolean ensureIsPublicFinal(TypeElement clazz) { + if (!clazz.getModifiers().contains(Modifier.FINAL) + || !clazz.getModifiers().contains(Modifier.PUBLIC)) { + printError("Class annotated with @GenerateIR must be public final", clazz); + return false; + } + return true; + } + + private boolean ensureEnclosedInInterfaceOrPackage(TypeElement clazz) { + var enclosingElem = clazz.getEnclosingElement(); + if (enclosingElem != null) { + if (!(enclosingElem.getKind() == ElementKind.PACKAGE + || enclosingElem.getKind() == ElementKind.INTERFACE)) { + printError( + "Class annotated with @GenerateIR must be enclosed in a package or an interface", + clazz); + return false; + } + } + return true; + } + + private boolean ensureHasSingleAnnotatedConstructor(TypeElement clazz) { + var annotatedCtorsCnt = + clazz.getEnclosedElements().stream() + .filter(elem -> elem.getKind() == ElementKind.CONSTRUCTOR) + .filter(ctor -> ctor.getAnnotation(GenerateFields.class) != null) + .count(); + if (annotatedCtorsCnt != 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); + "Class annotated with @GenerateIR must have exactly one constructor annotated with" + + " @GenerateFields", + clazz); return false; } return true; } + private static ExecutableElement getAnnotatedCtor(TypeElement clazz) { + // It should already be ensured that there is only a single annotated constructor in the class, + // hence the AssertionError + return clazz.getEnclosedElements().stream() + .filter(elem -> elem.getAnnotation(GenerateFields.class) != null) + .map(elem -> (ExecutableElement) elem) + .findFirst() + .orElseThrow(() -> new AssertionError("No constructor annotated with GenerateFields")); + } + private String packageName(Element elem) { var pkg = processingEnv.getElementUtils().getPackageOf(elem); return pkg.getQualifiedName().toString(); @@ -144,114 +205,32 @@ private void printError(String msg, Element elem) { } /** - * Generates code for a single class that implements a single interface annotated with {@link - * IRNode}. + * Generates code for a super class. * - * @param pkgName Package of the current interface annotated with {@link IRNode}. + * @param pkgName Package of the current processed class. * @return The generated code ready to be written to a {@code .java} source. */ private static String generateSingleNodeClass( - IRNodeClassGenerator irNodeClassGen, String pkgName) { + IRNodeClassGenerator irNodeClassGen, ProcessedClass processedClass, String pkgName) { var imports = irNodeClassGen.imports().stream().collect(Collectors.joining(System.lineSeparator())); var pkg = pkgName.isEmpty() ? "" : "package " + pkgName + ";"; + var interfaces = processedClass.getInterfaceElem().getQualifiedName().toString(); var code = """ $pkg $imports - public final class $className implements $interfaceName { + class $className implements $interfaces { $classBody } """ .replace("$pkg", pkg) .replace("$imports", imports) .replace("$className", irNodeClassGen.getClassName()) - .replace("$interfaceName", irNodeClassGen.getInterfaceName()) + .replace("$interfaces", interfaces) .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 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. - * @return The generated code ready to be written to a {@code .java} source. - */ - private static String generateMultipleNodeClasses( - List nestedClassGenerators, - String pkgName, - String newOuterClassName, - String outerInterfaceName) { - var imports = - nestedClassGenerators.stream() - .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()); - 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/ProcessedClass.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ProcessedClass.java new file mode 100644 index 000000000000..407e3e3296d2 --- /dev/null +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ProcessedClass.java @@ -0,0 +1,39 @@ +package org.enso.runtime.parser.processor; + +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import org.enso.runtime.parser.dsl.GenerateIR; + +/** + * Represents a class annotated with {@link org.enso.runtime.parser.dsl.GenerateIR} that is + * currently being processed by the {@link IRProcessor}. + */ +public final class ProcessedClass { + private final TypeElement clazz; + private final ExecutableElement ctor; + private final TypeElement interfaceElem; + + /** + * @param clazz Class being processed by the processor, annotated with {@link GenerateIR} + * @param ctor Constructor annotated with {@link org.enso.runtime.parser.dsl.GenerateFields}. + * @param interfaceElem Interface that the generated superclass must implement. See {@link + * GenerateIR#interfaces()}. + */ + ProcessedClass(TypeElement clazz, ExecutableElement ctor, TypeElement interfaceElem) { + this.clazz = clazz; + this.ctor = ctor; + this.interfaceElem = interfaceElem; + } + + public TypeElement getClazz() { + return clazz; + } + + public ExecutableElement getCtor() { + return ctor; + } + + public TypeElement getInterfaceElem() { + return interfaceElem; + } +} 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 5d3c798e2b8a..e74291c14430 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 @@ -1,17 +1,15 @@ package org.enso.runtime.parser.processor.field; -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.element.VariableElement; import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeMirror; import org.enso.runtime.parser.dsl.IRChild; +import org.enso.runtime.parser.dsl.IRField; +import org.enso.runtime.parser.processor.ProcessedClass; import org.enso.runtime.parser.processor.utils.Utils; /** @@ -20,90 +18,88 @@ */ public final class FieldCollector { private final ProcessingEnvironment processingEnv; - private final TypeElement irNodeInterface; + private final ProcessedClass processedClass; // Mapped by field name - private final Map fields = new LinkedHashMap<>(); + private Map fields; - /** - * @param irNodeInterface For this interface, fields will be collected. - */ - public FieldCollector(ProcessingEnvironment processingEnv, TypeElement irNodeInterface) { - assert irNodeInterface.getKind() == ElementKind.INTERFACE; + public FieldCollector(ProcessingEnvironment processingEnv, ProcessedClass processedClass) { this.processingEnv = processingEnv; - this.irNodeInterface = irNodeInterface; + this.processedClass = processedClass; } public 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); - } - } - } + if (fields == null) { + fields = new LinkedHashMap<>(); + collectFromCtor(); } 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.hasImplementation(methodElement, irNodeInterface, processingEnv)) { - var name = methodElement.getSimpleName().toString(); - if (!fields.containsKey(name)) { - var field = methodToField(methodElement); - fields.put(name, field); - } - } + private void collectFromCtor() { + var ctor = processedClass.getCtor(); + for (var param : ctor.getParameters()) { + var paramName = param.getSimpleName().toString(); + var irFieldAnnot = param.getAnnotation(IRField.class); + var irChildAnnot = param.getAnnotation(IRChild.class); + Field field; + if (Utils.hasNoAnnotations(param)) { + field = null; + } else if (irFieldAnnot != null) { + field = processIrField(param, irFieldAnnot); + } else if (irChildAnnot != null) { + field = processIrChild(param, irChildAnnot); + } else { + throw new IllegalStateException("Unexpected annotation on constructor parameter " + param); } - } - } - private Field methodToField(ExecutableElement methodElement) { - var name = methodElement.getSimpleName().toString(); - var retType = methodElement.getReturnType(); - if (retType.getKind().isPrimitive()) { - return new PrimitiveField(retType, name); + if (field != null) { + fields.put(paramName, field); + } } + } - 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); + private Field processIrField(VariableElement param, IRField irFieldAnnot) { + var isNullable = !irFieldAnnot.required(); + var name = param.getSimpleName().toString(); + var type = getParamType(param); + if (isPrimitiveType(param)) { + return new PrimitiveField(param.asType(), name); + } else { + // TODO: Assert that type is simple reference type - does not extend IR, is not generic + Utils.hardAssert(type != null); + return new ReferenceField(processingEnv, type, name, isNullable, false); } + } - assert childAnnot != null; - if (Utils.isScalaList(retTypeElem, processingEnv)) { - assert retType instanceof DeclaredType; - var declaredRetType = (DeclaredType) retType; + private Field processIrChild(VariableElement param, IRChild irChildAnnot) { + var name = param.getSimpleName().toString(); + var type = getParamType(param); + var isNullable = !irChildAnnot.required(); + if (Utils.isScalaList(type, processingEnv)) { + assert type instanceof DeclaredType; + var declaredRetType = (DeclaredType) type; assert declaredRetType.getTypeArguments().size() == 1; var typeArg = declaredRetType.getTypeArguments().get(0); var typeArgElem = (TypeElement) processingEnv.getTypeUtils().asElement(typeArg); ensureIsSubtypeOfIR(typeArgElem); - return new ListField(name, retTypeElem, typeArgElem); + return new ListField(name, type, typeArgElem); + } else { + if (!Utils.isSubtypeOfIR(type, processingEnv)) { + Utils.printError( + "Constructor parameter annotated with @IRChild must be a subtype of IR interface", + param, + processingEnv.getMessager()); + } + return new ReferenceField(processingEnv, type, name, isNullable, true); } + } + + private static boolean isPrimitiveType(VariableElement ctorParam) { + return ctorParam.asType().getKind().isPrimitive(); + } - boolean isNullable = !childAnnot.required(); - ensureIsSubtypeOfIR(retTypeElem); - return new ReferenceField(processingEnv, retTypeElem, name, isNullable, true); + private TypeElement getParamType(VariableElement param) { + return (TypeElement) processingEnv.getTypeUtils().asElement(param.asType()); } private void ensureIsSubtypeOfIR(TypeElement typeElem) { 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 deleted file mode 100644 index 098c49867662..000000000000 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/CopyMethodGenerator.java +++ /dev/null @@ -1,97 +0,0 @@ -package org.enso.runtime.parser.processor.methodgen; - -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; -import org.enso.runtime.parser.processor.GeneratedClassContext.ClassField; -import org.enso.runtime.parser.processor.utils.Utils; - -/** Code generator for methods annotated with {@link IRCopyMethod}. */ -public class CopyMethodGenerator { - private final ExecutableElement copyMethod; - private final GeneratedClassContext ctx; - private final Map parameterMapping = new HashMap<>(); - - public CopyMethodGenerator(ExecutableElement copyMethod, GeneratedClassContext ctx) { - this.ctx = ctx; - ensureIsAnnotated(copyMethod); - this.copyMethod = Objects.requireNonNull(copyMethod); - for (var parameter : copyMethod.getParameters()) { - 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) { - if (copyMethod.getAnnotation(IRCopyMethod.class) == null) { - throw new IllegalArgumentException("Copy method must be annotated with @IRCopyMethod"); - } - } - - private String simpleTypeName(VariableElement parameter) { - return ctx.getProcessingEnvironment() - .getTypeUtils() - .asElement(parameter.asType()) - .getSimpleName() - .toString(); - } - - public String generateCopyMethod() { - var sb = new StringBuilder(); - sb.append("@Override").append(System.lineSeparator()); - var argList = - copyMethod.getParameters().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/utils/Utils.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java index c6a887592a1c..aae999d05f17 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java @@ -9,7 +9,6 @@ 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; @@ -78,44 +77,6 @@ public static boolean isScalaList(TypeElement type, ProcessingEnvironment procEn return procEnv.getTypeUtils().isAssignable(type.asType(), scalaListType.asType()); } - /** - * 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 - * @return - */ - public static boolean hasImplementation( - ExecutableElement method, TypeElement interfaceType, ProcessingEnvironment procEnv) { - var defImplFound = - iterateSuperInterfaces( - interfaceType, - procEnv, - (TypeElement superInterface) -> { - for (var enclosedElem : superInterface.getEnclosedElements()) { - if (enclosedElem instanceof ExecutableElement executableElem) { - if (executableElem.getSimpleName().equals(method.getSimpleName())) { - if (hasModifier(executableElem, Modifier.DEFAULT) - || hasModifier(executableElem, Modifier.STATIC)) { - return true; - } - } - } - } - return null; - }); - return defImplFound != null; - } - - static boolean hasModifier(ExecutableElement method, Modifier modifier) { - return method.getModifiers().contains(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 @@ -190,6 +151,10 @@ public static void hardAssert(boolean condition, String msg) { } } + public static boolean hasNoAnnotations(Element element) { + return element.getAnnotationMirrors().isEmpty(); + } + public static boolean hasAnnotation( Element element, Class annotationClass) { return element.getAnnotation(annotationClass) != null; From d978592e5d68986277298ec2a5c7a122d2fc8b20 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Thu, 5 Dec 2024 17:40:49 +0100 Subject: [PATCH 104/195] Remove IRNode --- .../org/enso/runtime/parser/dsl/IRChild.java | 9 ++++---- .../org/enso/runtime/parser/dsl/IRNode.java | 23 ------------------- .../runtime/parser/processor/IRProcessor.java | 1 - .../runtime/parser/processor/field/Field.java | 5 +--- 4 files changed, 5 insertions(+), 33 deletions(-) delete mode 100644 engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/IRNode.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 index 8be0bebb7573..6fe615536fb6 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 @@ -6,11 +6,10 @@ 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}. + * Constructor parameter annotated with this annotation will be represented as a child field in the + * generated super class. Children of IR elements for a tree. A child will be part of the methods + * traversing the tree, like {@code mapExpression} and {@code children}. The parameter type must be + * a subtype of {@code org.enso.compiler.ir.IR}. */ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.PARAMETER) 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 deleted file mode 100644 index a8aaa434331b..000000000000 --- a/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/IRNode.java +++ /dev/null @@ -1,23 +0,0 @@ -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 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 {} 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 390af479b939..66a15079b25e 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,7 +16,6 @@ import javax.tools.JavaFileObject; import org.enso.runtime.parser.dsl.GenerateFields; import org.enso.runtime.parser.dsl.GenerateIR; -import org.enso.runtime.parser.dsl.IRNode; import org.enso.runtime.parser.processor.utils.Utils; @SupportedAnnotationTypes({ diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/Field.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/Field.java index b5d1277ebf3c..f32c55d0e15d 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/Field.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/Field.java @@ -5,10 +5,7 @@ import javax.lang.model.element.TypeElement; import org.enso.runtime.parser.dsl.IRChild; -/** - * A field of an IR node. Represented by any parameterless method on an interface annotated with - * {@link org.enso.runtime.parser.dsl.IRNode}. - */ +/** Represents a field in the generated super class. */ public interface Field { /** Name (identifier) of the field. */ From 54d9c256f54842156a56e2a027e91261d5592303 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Thu, 5 Dec 2024 17:46:07 +0100 Subject: [PATCH 105/195] Better error msg in FieldCollector --- .../runtime/parser/processor/field/FieldCollector.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) 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 e74291c14430..7081416c868f 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 @@ -49,7 +49,14 @@ private void collectFromCtor() { } else if (irChildAnnot != null) { field = processIrChild(param, irChildAnnot); } else { - throw new IllegalStateException("Unexpected annotation on constructor parameter " + param); + var errMsg = + "Unexpected annotation on constructor parameter " + + param + + ". " + + "All annotations: " + + param.getAnnotationMirrors(); + Utils.printError(errMsg, param, processingEnv.getMessager()); + throw new IllegalStateException(errMsg); } if (field != null) { From 75070652f2062fe9c245fc9a908cdc1be69ab501 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Thu, 5 Dec 2024 17:46:17 +0100 Subject: [PATCH 106/195] Add import in test --- .../runtime/parser/processor/test/TestIRProcessorInline.java | 1 + 1 file changed, 1 insertion(+) 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 352b9be0cde2..20cfe6453950 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 @@ -262,6 +262,7 @@ public void irNodeAsNestedClass() { """ import org.enso.runtime.parser.dsl.GenerateIR; import org.enso.runtime.parser.dsl.GenerateFields; + import org.enso.runtime.parser.dsl.IRField; import org.enso.compiler.core.IR; public interface JName extends IR { From e026c7760a985ae51bfd11aa03a7a41a51f4bb0f Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Thu, 5 Dec 2024 17:51:00 +0100 Subject: [PATCH 107/195] Ensure GenerateIR.interfaces is subtype of IR --- .../runtime/parser/processor/IRProcessor.java | 56 ++++++++++--------- 1 file changed, 30 insertions(+), 26 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 66a15079b25e..585c8d29b689 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 @@ -45,32 +45,6 @@ public boolean process(Set annotations, RoundEnvironment return true; } - private TypeElement findInterface(TypeElement processedClassElem, String interfaceName) { - if (isBinaryName(interfaceName)) { - var iface = processingEnv.getElementUtils().getTypeElement(interfaceName); - return iface; - } else { - var enclosingElem = processedClassElem.getEnclosingElement(); - if (enclosingElem.getKind() == ElementKind.INTERFACE) { - if (enclosingElem.getSimpleName().toString().equals(interfaceName)) { - return (TypeElement) enclosingElem; - } - } else if (enclosingElem.getKind() == ElementKind.PACKAGE) { - return (TypeElement) - enclosingElem.getEnclosedElements().stream() - .filter(pkgElem -> pkgElem.getKind() == ElementKind.INTERFACE) - .filter(ifaceElem -> ifaceElem.getSimpleName().toString().equals(interfaceName)) - .findFirst() - .orElse(null); - } - } - return null; - } - - private static boolean isBinaryName(String name) { - return name.contains("."); - } - /** * @param processedClassElem Class annotated with {@link GenerateIR}. * @return true if processing was successful, false otherwise. @@ -124,6 +98,32 @@ private boolean processGenerateIRElem(TypeElement processedClassElem) { return true; } + private TypeElement findInterface(TypeElement processedClassElem, String interfaceName) { + if (isBinaryName(interfaceName)) { + var iface = processingEnv.getElementUtils().getTypeElement(interfaceName); + return iface; + } else { + var enclosingElem = processedClassElem.getEnclosingElement(); + if (enclosingElem.getKind() == ElementKind.INTERFACE) { + if (enclosingElem.getSimpleName().toString().equals(interfaceName)) { + return (TypeElement) enclosingElem; + } + } else if (enclosingElem.getKind() == ElementKind.PACKAGE) { + return (TypeElement) + enclosingElem.getEnclosedElements().stream() + .filter(pkgElem -> pkgElem.getKind() == ElementKind.INTERFACE) + .filter(ifaceElem -> ifaceElem.getSimpleName().toString().equals(interfaceName)) + .findFirst() + .orElse(null); + } + } + return null; + } + + private static boolean isBinaryName(String name) { + return name.contains("."); + } + private ProcessedClass constructProcessedClass(TypeElement processedClassElem) { var generateIrAnnot = processedClassElem.getAnnotation(GenerateIR.class); var ifaceToImplement = findInterface(processedClassElem, generateIrAnnot.interfaces()); @@ -132,6 +132,10 @@ private ProcessedClass constructProcessedClass(TypeElement processedClassElem) { "Could not find interface '" + generateIrAnnot.interfaces() + "'", processedClassElem); return null; } + if (!Utils.isSubtypeOfIR(ifaceToImplement, processingEnv)) { + printError("Interface to implement must be a subtype of IR interface", ifaceToImplement); + return null; + } var annotatedCtor = getAnnotatedCtor(processedClassElem); var processedClass = new ProcessedClass(processedClassElem, annotatedCtor, ifaceToImplement); return processedClass; From f66ded211dc0b7d0754ee3191bdf7765596ae4a0 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Thu, 5 Dec 2024 17:51:05 +0100 Subject: [PATCH 108/195] Add import in test --- .../runtime/parser/processor/test/TestIRProcessorInline.java | 3 ++- 1 file changed, 2 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 20cfe6453950..384d4cfc09ed 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 @@ -144,11 +144,12 @@ public void irNodeWithMultipleFields_PrimitiveField() { import org.enso.runtime.parser.dsl.GenerateIR; import org.enso.runtime.parser.dsl.GenerateFields; import org.enso.runtime.parser.dsl.IRChild; + import org.enso.runtime.parser.dsl.IRField; @GenerateIR public final class MyIR { @GenerateFields - public MyIR(boolean suspended) {} + public MyIR(@IRField boolean suspended) {} } """); assertThat(genSrc, containsString("boolean suspended()")); From 363a8380185951fa8dda3c1f26910eb25618f3f7 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Thu, 5 Dec 2024 18:04:17 +0100 Subject: [PATCH 109/195] Fix generic type introspection from VariableElement --- .../runtime/parser/processor/field/FieldCollector.java | 8 ++++---- .../org/enso/runtime/parser/processor/utils/Utils.java | 5 +++++ 2 files changed, 9 insertions(+), 4 deletions(-) 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 7081416c868f..68758d0607fd 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 @@ -83,10 +83,10 @@ private Field processIrChild(VariableElement param, IRChild irChildAnnot) { var type = getParamType(param); var isNullable = !irChildAnnot.required(); if (Utils.isScalaList(type, processingEnv)) { - assert type instanceof DeclaredType; - var declaredRetType = (DeclaredType) type; - assert declaredRetType.getTypeArguments().size() == 1; - var typeArg = declaredRetType.getTypeArguments().get(0); + assert param.asType() instanceof DeclaredType; + var declaredType = (DeclaredType) param.asType(); + assert declaredType.getTypeArguments().size() == 1; + var typeArg = declaredType.getTypeArguments().get(0); var typeArgElem = (TypeElement) processingEnv.getTypeUtils().asElement(typeArg); ensureIsSubtypeOfIR(typeArgElem); return new ListField(name, type, typeArgElem); diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java index aae999d05f17..ae009cb4d5a6 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java @@ -77,6 +77,11 @@ public static boolean isScalaList(TypeElement type, ProcessingEnvironment procEn return procEnv.getTypeUtils().isAssignable(type.asType(), scalaListType.asType()); } + public static boolean isScalaList(TypeMirror type, ProcessingEnvironment procEnv) { + var scalaListType = procEnv.getElementUtils().getTypeElement("scala.collection.immutable.List"); + return procEnv.getTypeUtils().isSameType(type, scalaListType.asType()); + } + /** * 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 From b8f6b8f21735f3c1302118e94a005aede07e7556 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Thu, 5 Dec 2024 18:04:47 +0100 Subject: [PATCH 110/195] Fix imports in rest of the tests. TestIRProcessorInline are now green. --- .../parser/processor/test/TestIRProcessorInline.java | 10 ++++++++-- 1 file changed, 8 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 384d4cfc09ed..d02ac126ea73 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 @@ -104,10 +104,13 @@ public void simpleIRNodeWithUserDefinedFiled_CompilationSucceeds() { var src = """ import org.enso.runtime.parser.dsl.GenerateIR; + import org.enso.runtime.parser.dsl.GenerateFields; + import org.enso.runtime.parser.dsl.IRField; @GenerateIR public final class JName { - public JName(String name) {} + @GenerateFields + public JName(@IRField String name) {} } """; var genClass = generatedClass("JName", src); @@ -188,6 +191,7 @@ public void irNodeWithInheritedField() { """ import org.enso.runtime.parser.dsl.GenerateIR; import org.enso.runtime.parser.dsl.GenerateFields; + import org.enso.runtime.parser.dsl.IRField; import org.enso.compiler.core.IR; interface MySuperIR extends IR { @@ -197,7 +201,7 @@ interface MySuperIR extends IR { @GenerateIR(interfaces = "MySuperIR") public final class MyIR { @GenerateFields - public MyIR() {} + public MyIR(@IRField boolean suspended) {} } """); assertThat(src, containsString("boolean suspended()")); @@ -287,12 +291,14 @@ public void fieldCanBeScalaList() { "JName", """ import org.enso.runtime.parser.dsl.GenerateIR; + import org.enso.runtime.parser.dsl.GenerateFields; import org.enso.runtime.parser.dsl.IRChild; import org.enso.compiler.core.IR; import scala.collection.immutable.List; @GenerateIR public final class JName { + @GenerateFields public JName(@IRChild List expressions) {} } """); From a42d236aa56ad33704e4c4480847cddc0dde4088 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Thu, 5 Dec 2024 18:15:13 +0100 Subject: [PATCH 111/195] Generated constructor is protected --- .../processor/test/TestIRProcessorInline.java | 20 +++++++++++++++++++ .../processor/IRNodeClassGenerator.java | 5 ++--- 2 files changed, 22 insertions(+), 3 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 d02ac126ea73..9365628f9d77 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 @@ -118,6 +118,26 @@ public JName(@IRField String name) {} assertThat("Getter for 'name' generated", genClass, containsString("String name()")); } + @Test + public void generatedClassHasProtectedConstructor() { + var src = + """ + import org.enso.runtime.parser.dsl.GenerateIR; + import org.enso.runtime.parser.dsl.GenerateFields; + + @GenerateIR + public final class JName { + @GenerateFields + public JName() {} + } + """; + var genClass = generatedClass("JName", src); + assertThat(genClass, containsString("class JNameGen")); + assertThat("Generate class has protected constructor", + genClass, + containsString("protected JNameGen")); + } + @Test public void simpleIRNodeWithChild() { var genSrc = 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 dd889e37157d..8f9572770fbd 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 @@ -163,12 +163,11 @@ private String fieldsCode() { } /** - * 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. + * Returns string representation of the protected constructor of the generated class. */ private String constructor() { var sb = new StringBuilder(); - sb.append("private ").append(className).append("("); + sb.append("protected ").append(className).append("("); var inParens = generatedClassContext.getConstructorParameters().stream() .map( From 6587692548f906928448635c00d2869370948c84 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Thu, 5 Dec 2024 18:39:27 +0100 Subject: [PATCH 112/195] Generate second constructor - with user fields --- .../processor/test/TestIRProcessorInline.java | 52 +++++++++++++- .../processor/GeneratedClassContext.java | 5 ++ .../processor/IRNodeClassGenerator.java | 68 ++++++++++++++++--- 3 files changed, 113 insertions(+), 12 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 9365628f9d77..8b6a4c456ed5 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 @@ -133,9 +133,57 @@ public JName() {} """; var genClass = generatedClass("JName", src); assertThat(genClass, containsString("class JNameGen")); - assertThat("Generate class has protected constructor", + assertThat( + "Generate class has protected constructor", genClass, containsString("protected JNameGen")); + } + + /** The default generated protected constructor has meta parameters */ + @Test + public void generatedClassHasConstructor_WithMetaFields() { + var src = + """ + import org.enso.runtime.parser.dsl.GenerateIR; + import org.enso.runtime.parser.dsl.GenerateFields; + + @GenerateIR + public final class JName { + @GenerateFields + public JName() {} + } + """; + var genClass = generatedClass("JName", src); + assertThat(genClass, containsString("class JNameGen")); + assertThat( + "Generate class has protected constructor with meta parameters", + genClass, + containsString( + "protected JNameGen(DiagnosticStorage diagnostics, MetadataStorage passData," + + " IdentifiedLocation location, UUID id)")); + } + + /** + * The second generated protected constructor has user fields as parameters. In this case, it will + * be an empty constructor. + */ + @Test + public void generatedClassHasConstructor_WithUserFields() { + var src = + """ + import org.enso.runtime.parser.dsl.GenerateIR; + import org.enso.runtime.parser.dsl.GenerateFields; + + @GenerateIR + public final class JName { + @GenerateFields + public JName() {} + } + """; + var genClass = generatedClass("JName", src); + assertThat(genClass, containsString("class JNameGen")); + assertThat( + "Generate class has protected constructor with user fields as parameters", genClass, - containsString("protected JNameGen")); + containsString("protected JNameGen()")); } @Test 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 f945275a02a1..e400de51eb10 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 @@ -72,6 +72,11 @@ public ClassField getIdMetaField() { return idMetaField; } + /** + * Returns all constructor parameters for the default constructor. Including meta parameters. + * + * @return + */ public List getConstructorParameters() { return 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 8f9572770fbd..ac0ed859050d 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 @@ -5,6 +5,8 @@ import java.util.Set; import java.util.stream.Collectors; import javax.annotation.processing.ProcessingEnvironment; +import org.enso.runtime.parser.processor.GeneratedClassContext.ClassField; +import org.enso.runtime.parser.processor.GeneratedClassContext.Parameter; import org.enso.runtime.parser.processor.field.Field; import org.enso.runtime.parser.processor.field.FieldCollector; import org.enso.runtime.parser.processor.methodgen.BuilderMethodGenerator; @@ -106,7 +108,9 @@ String classBody() { """ $fields - $constructor + $defaultCtor + + $ctorWithUserFields public static Builder builder() { return new Builder(); @@ -125,7 +129,8 @@ public static Builder builder() { $builder """ .replace("$fields", fieldsCode()) - .replace("$constructor", constructor()) + .replace("$defaultCtor", defaultConstructor()) + .replace("$ctorWithUserFields", constructorForUserFields()) .replace("$userDefinedGetters", userDefinedGetters()) .replace("$overrideIRMethods", overrideIRMethods()) .replace("$mapExpressionsMethod", mapExpressions()) @@ -163,13 +168,43 @@ private String fieldsCode() { } /** - * Returns string representation of the protected constructor of the generated class. + * Returns string representation of the protected constructor of the generated class. The default + * constructor has parameters for both meta fields and user-defined fields. */ - private String constructor() { + private String defaultConstructor() { + return constructorForFields(generatedClassContext.getConstructorParameters(), List.of()); + } + + /** + * Returns string representation of the second protected constructor of the generated class. This + * constructor accepts only user defined fields as parameters. Meta fields are initialized to + * null. + */ + private String constructorForUserFields() { + var userFieldsAsParameters = + generatedClassContext.getUserFields().stream() + .map(field -> new Parameter(field.getSimpleTypeName(), field.getName())) + .toList(); + return constructorForFields(userFieldsAsParameters, generatedClassContext.getMetaFields()); + } + + /** + * The caller must ensure that parameters and {@code initializeToNull} are disjoint sets and that + * the union of them is equal to the set of all fields in the generated class. + * + * @param parameters Fields that will be parameters of the constructor. Can be empty list. + * @param initializeToNull Rest of the fields that will be initialized to null in the constructor. + * Can be empty list. + */ + private String constructorForFields( + List parameters, List initializeToNull) { + Utils.hardAssert( + !(parameters.isEmpty() && initializeToNull.isEmpty()), + "At least one of the list must be non empty"); var sb = new StringBuilder(); sb.append("protected ").append(className).append("("); var inParens = - generatedClassContext.getConstructorParameters().stream() + parameters.stream() .map( consParam -> "$consType $consName" @@ -177,11 +212,24 @@ private String constructor() { .replace("$consName", consParam.name())) .collect(Collectors.joining(", ")); sb.append(inParens).append(") {").append(System.lineSeparator()); - var ctorBody = - generatedClassContext.getAllFields().stream() - .map(field -> " this.$fieldName = $fieldName;".replace("$fieldName", field.name())) - .collect(Collectors.joining(System.lineSeparator())); - sb.append(indent(ctorBody, 2)); + + if (!parameters.isEmpty()) { + var ctorBody = + parameters.stream() + .map(field -> " this.$fieldName = $fieldName;".replace("$fieldName", field.name())) + .collect(Collectors.joining(System.lineSeparator())); + sb.append(indent(ctorBody, 2)); + } + + // The rest of the constructor body initializes the rest of the fields to null. + if (!initializeToNull.isEmpty()) { + var initToNullBody = + initializeToNull.stream() + .map(field -> " this.$fieldName = null;".replace("$fieldName", field.name())) + .collect(Collectors.joining(System.lineSeparator())); + sb.append(indent(initToNullBody, 2)); + } + sb.append(System.lineSeparator()); sb.append("}").append(System.lineSeparator()); return indent(sb.toString(), 2); From 3ea1bb3765fed4fe6421ec01c3c24b8c288d14b2 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Thu, 5 Dec 2024 18:43:27 +0100 Subject: [PATCH 113/195] style - add new line --- .../org/enso/runtime/parser/processor/IRNodeClassGenerator.java | 1 + 1 file changed, 1 insertion(+) 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 ac0ed859050d..9399b7db8589 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 @@ -220,6 +220,7 @@ private String constructorForFields( .collect(Collectors.joining(System.lineSeparator())); sb.append(indent(ctorBody, 2)); } + sb.append(System.lineSeparator()); // The rest of the constructor body initializes the rest of the fields to null. if (!initializeToNull.isEmpty()) { From d61eca707d0229b0670bf7c5829af0d08c03d957 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Thu, 5 Dec 2024 18:47:00 +0100 Subject: [PATCH 114/195] Add docs to the generated ctors. --- .../processor/IRNodeClassGenerator.java | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 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 9399b7db8589..cf317f8b9d59 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 @@ -172,7 +172,14 @@ private String fieldsCode() { * constructor has parameters for both meta fields and user-defined fields. */ private String defaultConstructor() { - return constructorForFields(generatedClassContext.getConstructorParameters(), List.of()); + var docs = """ + /** + * Default constructor for all the fields inside the generated class - both meta and + * user defined. + */ + """; + var ctorCode = constructorForFields(generatedClassContext.getConstructorParameters(), List.of()); + return Utils.indent(docs + ctorCode, 2); } /** @@ -181,11 +188,18 @@ private String defaultConstructor() { * null. */ private String constructorForUserFields() { + var docs = """ + /** + * Second generated constructor only for user-defined fields. All the meta fields will + * be initialized to the default value ({@code null}). + */ + """; var userFieldsAsParameters = generatedClassContext.getUserFields().stream() .map(field -> new Parameter(field.getSimpleTypeName(), field.getName())) .toList(); - return constructorForFields(userFieldsAsParameters, generatedClassContext.getMetaFields()); + var ctorCode = constructorForFields(userFieldsAsParameters, generatedClassContext.getMetaFields()); + return Utils.indent(docs + ctorCode, 2); } /** @@ -233,7 +247,7 @@ private String constructorForFields( sb.append(System.lineSeparator()); sb.append("}").append(System.lineSeparator()); - return indent(sb.toString(), 2); + return sb.toString(); } private String childrenMethodBody() { From 0ea630d66ada5d80c5a8fdc942efef3a51706f8f Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Thu, 5 Dec 2024 19:00:23 +0100 Subject: [PATCH 115/195] Add sample JExpression to tests --- .../test/gen/ir/core/JExpression.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JExpression.java diff --git a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JExpression.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JExpression.java new file mode 100644 index 000000000000..2975ebe2aad6 --- /dev/null +++ b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JExpression.java @@ -0,0 +1,42 @@ +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.compiler.core.ir.Name; +import org.enso.runtime.parser.dsl.GenerateFields; +import org.enso.runtime.parser.dsl.GenerateIR; +import org.enso.runtime.parser.dsl.IRChild; +import org.enso.runtime.parser.dsl.IRField; +import scala.collection.immutable.List; + +public interface JExpression extends IR { + @Override + JExpression mapExpressions(Function fn); + + @Override + JExpression duplicate( + boolean keepLocations, + boolean keepMetadata, + boolean keepDiagnostics, + boolean keepIdentifiers); + + @GenerateIR(interfaces = "JExpression") + final class JBlock extends JBlockGen { + @GenerateFields + public JBlock( + @IRChild List expressions, + @IRChild JExpression returnValue, + @IRField boolean suspended) { + super(expressions, returnValue, suspended); + } + } + + @GenerateIR(interfaces = "JExpression") + final class JBinding extends JBindingGen { + @GenerateFields + public JBinding(@IRChild Name name, @IRChild JExpression expression) { + super(name, expression); + } + } +} From 006406f0df59a6ee7816d45f4c5389d9fbca563d Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Thu, 5 Dec 2024 19:00:30 +0100 Subject: [PATCH 116/195] fmt --- .../parser/processor/IRNodeClassGenerator.java | 12 ++++++++---- 1 file changed, 8 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 cf317f8b9d59..b2378f688e7a 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 @@ -172,13 +172,15 @@ private String fieldsCode() { * constructor has parameters for both meta fields and user-defined fields. */ private String defaultConstructor() { - var docs = """ + var docs = + """ /** * Default constructor for all the fields inside the generated class - both meta and * user defined. */ """; - var ctorCode = constructorForFields(generatedClassContext.getConstructorParameters(), List.of()); + var ctorCode = + constructorForFields(generatedClassContext.getConstructorParameters(), List.of()); return Utils.indent(docs + ctorCode, 2); } @@ -188,7 +190,8 @@ private String defaultConstructor() { * null. */ private String constructorForUserFields() { - var docs = """ + var docs = + """ /** * Second generated constructor only for user-defined fields. All the meta fields will * be initialized to the default value ({@code null}). @@ -198,7 +201,8 @@ private String constructorForUserFields() { generatedClassContext.getUserFields().stream() .map(field -> new Parameter(field.getSimpleTypeName(), field.getName())) .toList(); - var ctorCode = constructorForFields(userFieldsAsParameters, generatedClassContext.getMetaFields()); + var ctorCode = + constructorForFields(userFieldsAsParameters, generatedClassContext.getMetaFields()); return Utils.indent(docs + ctorCode, 2); } From 4973bb89c9f2ffd2f3032a7a78da7d34e883e364 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Thu, 5 Dec 2024 19:19:31 +0100 Subject: [PATCH 117/195] Add java docs to the generated class --- .../runtime/parser/processor/IRProcessor.java | 18 ++++++++++++++++++ 1 file changed, 18 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 585c8d29b689..360dfcf5a58f 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 @@ -225,15 +225,33 @@ private static String generateSingleNodeClass( $imports + $docs class $className implements $interfaces { $classBody } """ .replace("$pkg", pkg) .replace("$imports", imports) + .replace("$docs", jdoc(processedClass)) .replace("$className", irNodeClassGen.getClassName()) .replace("$interfaces", interfaces) .replace("$classBody", irNodeClassGen.classBody()); return code; } + + private static String jdoc(ProcessedClass processedClass) { + var thisClassName = IRProcessor.class.getName(); + var processedClassName = processedClass.getClazz().getQualifiedName().toString(); + var docs = + """ + /** + * Generated by {@code $thisClassName} IR annotation processor. + * Generated from {@link $processedClassName}. + * The {@link $processedClassName} is meant to extend this generated class. + */ + """ + .replace("$thisClassName", thisClassName) + .replace("$processedClassName", processedClassName); + return docs; + } } From 00cae9087381e00b5eddd59b0657385f7e328267 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Tue, 10 Dec 2024 17:44:39 +0100 Subject: [PATCH 118/195] Remove unused IRCopyMethod --- .../enso/runtime/parser/dsl/IRCopyMethod.java | 30 ------------------- 1 file changed, 30 deletions(-) delete mode 100644 engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/IRCopyMethod.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 deleted file mode 100644 index 9895523cbf54..000000000000 --- a/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/IRCopyMethod.java +++ /dev/null @@ -1,30 +0,0 @@ -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. - * - *

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. - */ -@Retention(RetentionPolicy.SOURCE) -@Target(ElementType.METHOD) -public @interface IRCopyMethod {} From 3cb8424a360a18b2959b4bc22362c2e011c03251 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Tue, 10 Dec 2024 18:02:32 +0100 Subject: [PATCH 119/195] Replace scala Empty with generated IR --- .../pass/optimise/LambdaConsolidate.scala | 2 +- .../pass/resolve/SuspendedArguments.scala | 2 +- .../test/core/ir/DiagnosticStorageTest.scala | 2 +- .../pass/desugar/OperatorToFunctionTest.scala | 12 +-- .../java/org/enso/compiler/core/ir/Empty.java | 16 ++++ .../org/enso/compiler/core/ir/Empty.scala | 89 ------------------- 6 files changed, 25 insertions(+), 98 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-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..6b32d472bc16 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,7 @@ case object LambdaConsolidate extends IRPass { } val shadower: IR = - mShadower.getOrElse(Empty(spec.identifiedLocation)) + mShadower.getOrElse(new Empty(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..e5f50d37b0f4 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)) + )(new Empty(null)) 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..f2454dcf43ef 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), + new Empty(null), 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..4d6ad396c0ba 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 = new Empty(null) + val right = new Empty(null) + val rightArg = CallArgument.Specified(None, new Empty(null), 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 = new Empty(null) + val right = new Empty(null) val rightArg = CallArgument.Specified( None, - Empty(identifiedLocation = null), + new Empty(null), false, identifiedLocation = null ) 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..5bbeaf51ff96 --- /dev/null +++ b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/Empty.java @@ -0,0 +1,16 @@ +package org.enso.compiler.core.ir; + +import org.enso.runtime.parser.dsl.GenerateFields; +import org.enso.runtime.parser.dsl.GenerateIR; + +@GenerateIR(interfaces = "org.enso.compiler.core.ir.Expression") +public final class Empty extends EmptyGen { + @GenerateFields + public Empty(IdentifiedLocation identifiedLocation, MetadataStorage passData) { + super(null, passData, identifiedLocation, null); + } + + public Empty(IdentifiedLocation identifiedLocation) { + this(identifiedLocation, null); + } +} 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 371071b76319988c2ee056d4dd4278322f96d969 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Tue, 10 Dec 2024 20:11:45 +0100 Subject: [PATCH 120/195] Support scala.Option field --- .../processor/test/TestIRProcessorInline.java | 22 ++++++ .../processor/IRNodeClassGenerator.java | 15 +++- .../runtime/parser/processor/field/Field.java | 7 +- .../processor/field/FieldCollector.java | 29 ++++++-- .../parser/processor/field/ListField.java | 4 +- .../parser/processor/field/OptionField.java | 69 +++++++++++++++++++ .../methodgen/DuplicateMethodGenerator.java | 51 ++++++++++---- .../MapExpressionsMethodGenerator.java | 34 ++++++--- .../runtime/parser/processor/utils/Utils.java | 5 ++ 9 files changed, 200 insertions(+), 36 deletions(-) create mode 100644 engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/OptionField.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 8b6a4c456ed5..7172c40fb419 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 @@ -374,6 +374,28 @@ public JName(@IRChild List expressions) {} assertThat(src, containsString("List expressions")); } + @Test + public void fieldCanBeScalaOption() { + var src = + generatedClass( + "JName", + """ + import org.enso.runtime.parser.dsl.GenerateIR; + import org.enso.runtime.parser.dsl.GenerateFields; + import org.enso.runtime.parser.dsl.IRChild; + import org.enso.compiler.core.IR; + import scala.Option; + + @GenerateIR + public final class JName { + @GenerateFields + public JName(@IRChild Option expression) {} + } + """); + assertThat(src, containsString("class JNameGen")); + assertThat("has getter method for expression", src, containsString("Option expression()")); + } + // TODO: Can contain multiple GenerateIR annotations in single source // TODO: Multiple interfaces in the annotation 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 b2378f688e7a..7224ca5a692a 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 @@ -263,15 +263,24 @@ private String childrenMethodBody() { .forEach( childField -> { String addToListCode; - if (!childField.isList()) { - addToListCode = "list.add(" + childField.getName() + ");"; - } else { + if (childField.isList()) { addToListCode = """ $childName.foreach(list::add); """ .replace("$childName", childField.getName()); + } else if (childField.isOption()) { + addToListCode = + """ + if ($childName.isDefined()) { + list.add($childName.get()); + } + """ + .replace("$childName", childField.getName()); + } else { + addToListCode = "list.add(" + childField.getName() + ");"; } + var childName = childField.getName(); if (childField.isNullable()) { sb.append( diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/Field.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/Field.java index f32c55d0e15d..01ffacf99c72 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/Field.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/Field.java @@ -33,6 +33,11 @@ default boolean isList() { return false; } + /** Returns true if this field is {@code scala.Option}. */ + default boolean isOption() { + return false; + } + /** * Returns true if this field is annotated with {@link org.enso.runtime.parser.dsl.IRChild}. * @@ -62,7 +67,7 @@ default boolean isList() { boolean isExpression(); /** Returns the type parameter, if this field is a generic type. Otherwise null. */ - default String getTypeParameter() { + default TypeElement getTypeParameter() { return null; } } 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 68758d0607fd..ef0ce914ec2c 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 @@ -83,16 +83,14 @@ private Field processIrChild(VariableElement param, IRChild irChildAnnot) { var type = getParamType(param); var isNullable = !irChildAnnot.required(); if (Utils.isScalaList(type, processingEnv)) { - assert param.asType() instanceof DeclaredType; - var declaredType = (DeclaredType) param.asType(); - assert declaredType.getTypeArguments().size() == 1; - var typeArg = declaredType.getTypeArguments().get(0); - var typeArgElem = (TypeElement) processingEnv.getTypeUtils().asElement(typeArg); - ensureIsSubtypeOfIR(typeArgElem); + var typeArgElem = getGenericType(param); return new ListField(name, type, typeArgElem); + } else if (Utils.isScalaOption(type, processingEnv)) { + var typeArgElem = getGenericType(param); + return new OptionField(name, type, typeArgElem); } else { if (!Utils.isSubtypeOfIR(type, processingEnv)) { - Utils.printError( + Utils.printErrorAndFail( "Constructor parameter annotated with @IRChild must be a subtype of IR interface", param, processingEnv.getMessager()); @@ -101,6 +99,23 @@ private Field processIrChild(VariableElement param, IRChild irChildAnnot) { } } + /** + * Returns the generic type parameter. Assumes that the given {@code param} is declared with some + * generic type, and has exactly one type parameter. + * + * @param param the parameter to get the generic type from + * @return the generic type parameter + */ + private TypeElement getGenericType(VariableElement param) { + assert param.asType() instanceof DeclaredType; + var declaredType = (DeclaredType) param.asType(); + assert declaredType.getTypeArguments().size() == 1; + var typeArg = declaredType.getTypeArguments().get(0); + var typeArgElem = (TypeElement) processingEnv.getTypeUtils().asElement(typeArg); + ensureIsSubtypeOfIR(typeArgElem); + return typeArgElem; + } + private static boolean isPrimitiveType(VariableElement ctorParam) { return ctorParam.asType().getKind().isPrimitive(); } diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/ListField.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/ListField.java index 6942d19df2a5..3ff0865e453d 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/ListField.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/ListField.java @@ -36,8 +36,8 @@ public String getSimpleTypeName() { } @Override - public String getTypeParameter() { - return typeArgElement.getSimpleName().toString(); + public TypeElement getTypeParameter() { + return typeArgElement; } @Override diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/OptionField.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/OptionField.java new file mode 100644 index 000000000000..07160bfa43c6 --- /dev/null +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/OptionField.java @@ -0,0 +1,69 @@ +package org.enso.runtime.parser.processor.field; + +import java.util.List; +import javax.lang.model.element.TypeElement; + +/** Field representing {@code scala.Option} */ +public class OptionField implements Field { + private final String name; + private final TypeElement typeArgElement; + private final TypeElement type; + + public OptionField(String name, TypeElement type, TypeElement typeArgElement) { + this.name = name; + this.type = type; + this.typeArgElement = typeArgElement; + } + + @Override + public String getName() { + return name; + } + + @Override + public TypeElement getType() { + return type; + } + + @Override + public String getSimpleTypeName() { + var typeArg = typeArgElement.getSimpleName().toString(); + return "Option<" + typeArg + ">"; + } + + @Override + public TypeElement getTypeParameter() { + return typeArgElement; + } + + @Override + public List getImportedTypes() { + var typePar = typeArgElement.getQualifiedName().toString(); + return List.of("scala.Option", typePar); + } + + @Override + public boolean isOption() { + 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/methodgen/DuplicateMethodGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/DuplicateMethodGenerator.java index 4acd6073f25d..92e0fcdeeb79 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 @@ -104,15 +104,21 @@ public String generateDuplicateMethodCode() { 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)); + } else { + if (field.isList()) { + sb.append(Utils.indent(listChildCode(field), 2)); + sb.append(System.lineSeparator()); + duplicatedVars.add(new DuplicateVar(null, dupFieldName(field), false)); + } else if (field.isOption()) { + sb.append(Utils.indent(optionChildCode(field), 2)); + sb.append(System.lineSeparator()); + duplicatedVars.add(new DuplicateVar(null, dupFieldName(field), false)); + } else { + sb.append(Utils.indent(notNullableChildCode(field), 2)); + sb.append(System.lineSeparator()); + duplicatedVars.add( + new DuplicateVar(field.getSimpleTypeName(), dupFieldName(field), true)); + } } } else { sb.append(Utils.indent(nonChildCode(field), 2)); @@ -150,7 +156,7 @@ private static String nullableChildCode(Field nullableChild) { } private static String notNullableChildCode(Field child) { - assert child.isChild() && !child.isNullable() && !child.isList(); + assert child.isChild() && !child.isNullable() && !child.isList() && !child.isOption(); return """ IR $dupName = $childName.duplicate($parameterNames); if (!($dupName instanceof $childType)) { @@ -164,7 +170,7 @@ private static String notNullableChildCode(Field child) { } private static String listChildCode(Field listChild) { - assert listChild.isChild() && listChild.isList(); + Utils.hardAssert(listChild.isChild() && listChild.isList()); return """ $childListType $dupName = $childName.map(child -> { @@ -176,14 +182,33 @@ private static String listChildCode(Field listChild) { }); """ .replace("$childListType", listChild.getSimpleTypeName()) - .replace("$childType", listChild.getTypeParameter()) + .replace("$childType", listChild.getTypeParameter().getSimpleName()) .replace("$childName", listChild.getName()) .replace("$dupName", dupFieldName(listChild)) .replace("$parameterNames", String.join(", ", parameterNames())); } + private static String optionChildCode(Field optionChild) { + Utils.hardAssert(optionChild.isOption() && optionChild.isChild()); + return """ + $childOptType $dupName = $childName; + if ($childName.isDefined()) { + var duplicated = $childName.get().duplicate($parameterNames); + if (!(duplicated instanceof $childType)) { + throw new IllegalStateException("Duplicated child is not of the expected type: " + $dupName); + } + $dupName = Option.apply(duplicated); + } + """ + .replace("$childOptType", optionChild.getSimpleTypeName()) + .replace("$childType", optionChild.getTypeParameter().getSimpleName()) + .replace("$childName", optionChild.getName()) + .replace("$dupName", dupFieldName(optionChild)) + .replace("$parameterNames", String.join(", ", parameterNames())); + } + private static String nonChildCode(Field field) { - assert !field.isChild(); + Utils.hardAssert(!field.isChild()); return """ $childType $dupName = $childName; """ 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 64c972afbd09..0e74fdde515f 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 @@ -48,25 +48,39 @@ public String generateMapExpressionsMethodCode() { .append(") {") .append(System.lineSeparator()); - var children = ctx.getUserFields().stream().filter(field -> field.isChild() && !field.isList()); + var children = ctx.getUserFields().stream().filter(Field::isChild); var newChildren = children.map( child -> { - Utils.hardAssert(!child.isList()); - var childsMapExprMethod = - Utils.findMapExpressionsMethod(child.getType(), ctx.getProcessingEnvironment()); + ExecutableElement childsMapExprMethod; + if (child.isList() || child.isOption()) { + childsMapExprMethod = + Utils.findMapExpressionsMethod( + child.getTypeParameter(), ctx.getProcessingEnvironment()); + } else { + 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()); + if (child.isList() || child.isOption()) { + shouldCast = false; + } + + String newChildType = typeName(childsMapExprMethodRetType); + if (child.isList()) { + newChildType = "List<" + newChildType + ">"; + } else if (child.isOption()) { + newChildType = "Option<" + newChildType + ">"; + } var newChildName = child.getName() + "Mapped"; - sb.append(" ") - .append(typeName(childsMapExprMethodRetType)) - .append(" ") - .append(newChildName); + sb.append(" ").append(newChildType).append(" ").append(newChildName); if (child.isNullable()) { sb.append(" = null;").append(System.lineSeparator()); sb.append(" if (") @@ -82,7 +96,7 @@ public String generateMapExpressionsMethodCode() { .append(System.lineSeparator()); sb.append(" }").append(System.lineSeparator()); } else { - if (!child.isList()) { + if (!child.isList() && !child.isOption()) { // ChildType childMapped = child.mapExpressions(fn); sb.append(" = ") .append(child.getName()) @@ -91,7 +105,7 @@ public String generateMapExpressionsMethodCode() { .append("(fn);") .append(System.lineSeparator()); } else { - Utils.hardAssert(child.isList() && !child.isNullable()); + Utils.hardAssert(child.isList() || child.isOption()); // List childMapped = child.map(e -> e.mapExpressions(fn)); sb.append(" = ") .append(child.getName()) diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java index ae009cb4d5a6..834a3f04c9b0 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java @@ -77,6 +77,11 @@ public static boolean isScalaList(TypeElement type, ProcessingEnvironment procEn return procEnv.getTypeUtils().isAssignable(type.asType(), scalaListType.asType()); } + public static boolean isScalaOption(TypeElement type, ProcessingEnvironment procEnv) { + var scalaListType = procEnv.getElementUtils().getTypeElement("scala.Option"); + return procEnv.getTypeUtils().isAssignable(type.asType(), scalaListType.asType()); + } + public static boolean isScalaList(TypeMirror type, ProcessingEnvironment procEnv) { var scalaListType = procEnv.getElementUtils().getTypeElement("scala.collection.immutable.List"); return procEnv.getTypeUtils().isSameType(type, scalaListType.asType()); From a94a954f828e9202aa1de8bf9b27b924c908bffd Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Tue, 10 Dec 2024 20:21:35 +0100 Subject: [PATCH 121/195] Add nl --- .../org/enso/runtime/parser/processor/IRNodeClassGenerator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 7224ca5a692a..cf9037149a5e 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 @@ -292,7 +292,7 @@ private String childrenMethodBody() { .replace("$childName", childName) .replace("$addToListCode", addToListCode)); } else { - sb.append(addToListCode); + sb.append(addToListCode).append(nl); } }); sb.append("return scala.jdk.javaapi.CollectionConverters.asScala(list).toList();").append(nl); From 4368bd12e6f8e9283ed97d9aa9f34e28b2929c7d Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Wed, 11 Dec 2024 12:18:40 +0100 Subject: [PATCH 122/195] Add JCallArgument to tests --- .../test/gen/ir/core/JCallArgument.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JCallArgument.java diff --git a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JCallArgument.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JCallArgument.java new file mode 100644 index 000000000000..206b312f30db --- /dev/null +++ b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JCallArgument.java @@ -0,0 +1,42 @@ +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.compiler.core.ir.Name; +import org.enso.runtime.parser.dsl.GenerateFields; +import org.enso.runtime.parser.dsl.GenerateIR; +import org.enso.runtime.parser.dsl.IRChild; +import org.enso.runtime.parser.dsl.IRField; +import scala.Option; + +/** Call-site arguments in Enso. */ +public interface JCallArgument extends IR { + /** The name of the argument, if present. */ + Option name(); + + /** The expression of the argument, if present. */ + Expression value(); + + /** Flag indicating that the argument was generated by compiler. */ + boolean isSynthetic(); + + @Override + JCallArgument mapExpressions(Function fn); + + @Override + JCallArgument duplicate( + boolean keepLocations, + boolean keepMetadata, + boolean keepDiagnostics, + boolean keepIdentifiers); + + @GenerateIR(interfaces = "JCallArgument") + final class JSpecified extends JSpecifiedGen { + @GenerateFields + public JSpecified( + @IRField boolean isSynthetic, @IRChild Expression value, @IRChild Option name) { + super(isSynthetic, value, name); + } + } +} From 8f699f947731da6b3e3bc838a2dcd4e682ff4d39 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 23 Dec 2024 19:52:47 +0100 Subject: [PATCH 123/195] Test that annotated constructor can have meta parameter --- .../processor/test/TestIRProcessorInline.java | 26 +++++++++++++++++++ 1 file changed, 26 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 7172c40fb419..404fde32ce07 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 @@ -4,6 +4,7 @@ import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; +import com.google.testing.compile.Compilation; import com.google.testing.compile.CompilationSubject; import com.google.testing.compile.Compiler; import com.google.testing.compile.JavaFileObjects; @@ -45,6 +46,13 @@ private static void expectCompilationFailure(String src) { CompilationSubject.assertThat(compilation).failed(); } + private static Compilation compile(String src, String name) { + var srcObject = JavaFileObjects.forSourceString(name, src); + var compiler = Compiler.javac().withProcessors(new IRProcessor()); + var compilation = compiler.compile(srcObject); + return compilation; + } + @Test public void simpleIRNodeWithoutFields_CompilationSucceeds() { var src = @@ -186,6 +194,24 @@ public JName() {} containsString("protected JNameGen()")); } + @Test + public void annotatedConstructor_CanHaveMetaParameters() { + var src = + """ + import org.enso.runtime.parser.dsl.GenerateIR; + import org.enso.runtime.parser.dsl.GenerateFields; + import org.enso.compiler.core.ir.MetadataStorage; + + @GenerateIR + public final class JName { + @GenerateFields + public JName(MetadataStorage passData) {} + } + """; + var compilation = compile("JName", src); + CompilationSubject.assertThat(compilation).succeeded(); + } + @Test public void simpleIRNodeWithChild() { var genSrc = From 51420df1eab1529503b3c8550c3debadd891410d Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 23 Dec 2024 20:08:22 +0100 Subject: [PATCH 124/195] Constructor parameters must be annotated --- .../runtime/parser/dsl/GenerateFields.java | 6 ++--- .../processor/test/TestIRProcessorInline.java | 24 ++++++++++++++++++- .../processor/field/FieldCollector.java | 15 ++++-------- 3 files changed, 30 insertions(+), 15 deletions(-) diff --git a/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/GenerateFields.java b/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/GenerateFields.java index 25bd4c8b3222..05ff1f020be9 100644 --- a/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/GenerateFields.java +++ b/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/GenerateFields.java @@ -27,15 +27,11 @@ * with {@link GenerateFields}. The parameter of the constructor can be one of the following: * *

    - *
  • Any reference, or primitive type without annotation *
  • Any reference, or primitive type annotated with {@link IRField} *
  • A subtype of {@code org.enso.compiler.ir.IR} annotated with {@link IRChild} *
  • One of the meta types mentioned above *
* - * Constructor parameters without any annotation will not be processed at all. There will be no code - * generated for them. - * *

A user-defined field generated out of constructor parameter annotated with {@link IRChild} is * a child element of this IR element. That means that it will be included in generated * implementation of IR methods that iterate over the IR tree. For example {@code mapExpressions} or @@ -47,6 +43,8 @@ * *

For a constructor parameter of a meta type, there will be no user-defined field generated, as * the meta fields are always generated. + * + *

Other types of constructor parameters are forbidden. */ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.CONSTRUCTOR) 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 404fde32ce07..7342ef2de465 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 @@ -3,6 +3,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; +import static org.junit.Assert.fail; import com.google.testing.compile.Compilation; import com.google.testing.compile.CompilationSubject; @@ -46,7 +47,7 @@ private static void expectCompilationFailure(String src) { CompilationSubject.assertThat(compilation).failed(); } - private static Compilation compile(String src, String name) { + private static Compilation compile(String name, String src) { var srcObject = JavaFileObjects.forSourceString(name, src); var compiler = Compiler.javac().withProcessors(new IRProcessor()); var compilation = compiler.compile(srcObject); @@ -194,6 +195,27 @@ public JName() {} containsString("protected JNameGen()")); } + @Test + public void annotatedConstructor_MustNotHaveUnannotatedParameters() { + var src = + """ + import org.enso.runtime.parser.dsl.GenerateIR; + import org.enso.runtime.parser.dsl.GenerateFields; + + @GenerateIR + public final class JName { + @GenerateFields + public JName(int param) {} + } + """; + try { + compile("JName", src); + fail("Exception in compilation expected"); + } catch (Exception e) { + assertThat(e.getMessage(), containsString("must be annotated")); + } + } + @Test public void annotatedConstructor_CanHaveMetaParameters() { var src = 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 ef0ce914ec2c..0d1b97797930 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 @@ -42,26 +42,21 @@ private void collectFromCtor() { var irFieldAnnot = param.getAnnotation(IRField.class); var irChildAnnot = param.getAnnotation(IRChild.class); Field field; - if (Utils.hasNoAnnotations(param)) { - field = null; - } else if (irFieldAnnot != null) { + if (irFieldAnnot != null) { field = processIrField(param, irFieldAnnot); } else if (irChildAnnot != null) { field = processIrChild(param, irChildAnnot); } else { var errMsg = - "Unexpected annotation on constructor parameter " + "Constructor parameter " + param - + ". " - + "All annotations: " - + param.getAnnotationMirrors(); + + " must be annotated with either @IRField or @IRChild"; Utils.printError(errMsg, param, processingEnv.getMessager()); throw new IllegalStateException(errMsg); } - if (field != null) { - fields.put(paramName, field); - } + assert field != null; + fields.put(paramName, field); } } From db713a7d08c7edf027492bb28e63e529d5fb63da Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 23 Dec 2024 20:18:49 +0100 Subject: [PATCH 125/195] Constructor parameters can be meta type --- .../processor/field/FieldCollector.java | 36 +++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) 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 0d1b97797930..9feb4d015bf2 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 @@ -3,6 +3,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; @@ -19,12 +20,32 @@ public final class FieldCollector { private final ProcessingEnvironment processingEnv; private final ProcessedClass processedClass; + private final TypeElement metadataStorageType; + private final TypeElement diagnosticStorageType; + private final TypeElement identifiedLocationType; + private final TypeElement uuidType; + // Mapped by field name private Map fields; public FieldCollector(ProcessingEnvironment processingEnv, ProcessedClass processedClass) { this.processingEnv = processingEnv; this.processedClass = processedClass; + this.metadataStorageType = + processingEnv.getElementUtils().getTypeElement("org.enso.compiler.core.ir.MetadataStorage"); + this.diagnosticStorageType = + processingEnv + .getElementUtils() + .getTypeElement("org.enso.compiler.core.ir.DiagnosticStorage"); + this.identifiedLocationType = + processingEnv + .getElementUtils() + .getTypeElement("org.enso.compiler.core.ir.IdentifiedLocation"); + this.uuidType = processingEnv.getElementUtils().getTypeElement("java.util.UUID"); + Objects.requireNonNull(metadataStorageType); + Objects.requireNonNull(diagnosticStorageType); + Objects.requireNonNull(identifiedLocationType); + Objects.requireNonNull(uuidType); } public List collectFields() { @@ -46,6 +67,8 @@ private void collectFromCtor() { field = processIrField(param, irFieldAnnot); } else if (irChildAnnot != null) { field = processIrChild(param, irChildAnnot); + } else if (Utils.hasNoAnnotations(param) && isMeta(param)) { + field = null; } else { var errMsg = "Constructor parameter " @@ -55,11 +78,20 @@ private void collectFromCtor() { throw new IllegalStateException(errMsg); } - assert field != null; - fields.put(paramName, field); + if (field != null) { + fields.put(paramName, field); + } } } + private boolean isMeta(VariableElement param) { + var typeUtils = processingEnv.getTypeUtils(); + return typeUtils.isSameType(param.asType(), metadataStorageType.asType()) + || typeUtils.isSameType(param.asType(), diagnosticStorageType.asType()) + || typeUtils.isSameType(param.asType(), identifiedLocationType.asType()) + || typeUtils.isSameType(param.asType(), uuidType.asType()); + } + private Field processIrField(VariableElement param, IRField irFieldAnnot) { var isNullable = !irFieldAnnot.required(); var name = param.getSimpleName().toString(); From f33af6e172cd9bfd9055aeacbb484ef4f732dffc Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Tue, 24 Dec 2024 18:00:37 +0100 Subject: [PATCH 126/195] Generated super class is abstract --- .../processor/test/TestIRProcessorInline.java | 17 +++ .../processor/GeneratedClassContext.java | 114 ++++++++++++++-- .../processor/IRNodeClassGenerator.java | 14 +- .../runtime/parser/processor/IRProcessor.java | 2 +- .../methodgen/BuilderMethodGenerator.java | 47 +++++-- .../methodgen/DuplicateMethodGenerator.java | 122 ++++++++++++++---- .../runtime/parser/processor/utils/Utils.java | 5 + 7 files changed, 262 insertions(+), 59 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 7342ef2de465..5d60c24b425b 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 @@ -195,6 +195,23 @@ public JName() {} containsString("protected JNameGen()")); } + @Test + public void generatedClass_IsAbstract() { + var src = + """ + import org.enso.runtime.parser.dsl.GenerateIR; + import org.enso.runtime.parser.dsl.GenerateFields; + + @GenerateIR + public final class JName { + @GenerateFields + public JName() {} + } + """; + var genClass = generatedClass("JName", src); + assertThat(genClass, containsString("abstract class JNameGen")); + } + @Test public void annotatedConstructor_MustNotHaveUnannotatedParameters() { var src = 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 e400de51eb10..c3be850ad4ec 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 @@ -3,8 +3,14 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.function.Supplier; import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.PrimitiveType; +import javax.lang.model.util.SimpleTypeVisitor14; import org.enso.runtime.parser.processor.field.Field; +import org.enso.runtime.parser.processor.utils.Utils; /** * A context created for the generated class. Everything that is needed for the code generation of a @@ -13,17 +19,18 @@ public final class GeneratedClassContext { private final String className; private final List userFields; + private final List allFields; private final List constructorParameters; private final ProcessingEnvironment processingEnvironment; private final ProcessedClass processedClass; private static final ClassField diagnosticsMetaField = - new ClassField("private", "DiagnosticStorage", "diagnostics"); + new ClassField("protected", "DiagnosticStorage", "diagnostics"); private static final ClassField passDataMetaField = - new ClassField("private", "MetadataStorage", "passData"); + new ClassField("protected", "MetadataStorage", "passData"); private static final ClassField locationMetaField = - new ClassField("private", "IdentifiedLocation", "location"); - private static final ClassField idMetaField = new ClassField("private", "UUID", "id"); + new ClassField("protected", "IdentifiedLocation", "location"); + private static final ClassField idMetaField = new ClassField("protected", "UUID", "id"); /** Meta fields are always present in the generated class. */ private static final List metaFields = @@ -44,8 +51,13 @@ public final class GeneratedClassContext { this.processingEnvironment = Objects.requireNonNull(processingEnvironment); this.processedClass = processedClass; ensureSimpleName(className); + this.allFields = new ArrayList<>(metaFields); + for (var userField : userFields) { + allFields.add( + new ClassField("private final", userField.getSimpleTypeName(), userField.getName())); + } this.constructorParameters = - getAllFields().stream() + allFields.stream() .map(classField -> new Parameter(classField.type(), classField.name())) .toList(); } @@ -73,11 +85,12 @@ public ClassField getIdMetaField() { } /** - * Returns all constructor parameters for the default constructor. Including meta parameters. + * Returns all constructor parameters for the default constructor of the generated class. + * Including meta parameters. * * @return */ - public List getConstructorParameters() { + public List getSuperclassConstructorParameters() { return constructorParameters; } @@ -90,16 +103,16 @@ public String getClassName() { return className; } + public ProcessedClass getProcessedClass() { + return processedClass; + } + List getMetaFields() { return metaFields; } + /** Returns list of all fields in the generated class - meta field and user-defined fields. */ public List getAllFields() { - var allFields = new ArrayList(metaFields); - for (var userField : userFields) { - allFields.add( - new ClassField("private final", userField.getSimpleTypeName(), userField.getName())); - } return allFields; } @@ -107,6 +120,74 @@ public ProcessingEnvironment getProcessingEnvironment() { return processingEnvironment; } + /** + * Returns list of parameters for the constructor of the subclass annotated with {@link + * org.enso.runtime.parser.dsl.GenerateFields}. The list is gathered from all the fields present + * in the generated super class. + * + * @see #getAllFields() + * @return List of parameters for the constructor of the subclass. A subset of all the fields in + * the generated super class. + */ + public List getSubclassConstructorParameters() { + var ctor = processedClass.getCtor(); + var ctorParams = new ArrayList(); + for (var param : ctor.getParameters()) { + var paramSimpleType = simpleTypeName(param); + var paramName = param.getSimpleName().toString(); + Supplier printErrAndFail = + () -> { + var errMsg = + String.format( + "No matching field found for parameter %s of type %s. All fields: %s", + paramName, paramSimpleType, allFields); + Utils.printErrorAndFail(errMsg, param, processingEnvironment.getMessager()); + return null; + }; + var fieldsWithSameType = + allFields.stream() + .filter(field -> paramSimpleType.equals(field.simpleTypeName())) + .toList(); + if (fieldsWithSameType.isEmpty()) { + printErrAndFail.get(); + } else if (fieldsWithSameType.size() == 1) { + ctorParams.add(fieldsWithSameType.get(0)); + } else { + // There are multiple fields with the same type - try to match on the name + var fieldsWithSameName = + fieldsWithSameType.stream().filter(field -> paramName.equals(field.name)).toList(); + Utils.hardAssert( + fieldsWithSameName.size() < 2, + "Cannot have more than one field with the same name and type"); + if (fieldsWithSameName.isEmpty()) { + printErrAndFail.get(); + } + Utils.hardAssert(fieldsWithSameName.size() == 1); + ctorParams.add(fieldsWithSameName.get(0)); + } + } + return ctorParams; + } + + private String simpleTypeName(VariableElement param) { + var paramType = param.asType(); + var typeVisitor = + new SimpleTypeVisitor14() { + @Override + public String visitDeclared(DeclaredType t, Void unused) { + return t.asElement().getSimpleName().toString(); + } + + @Override + public String visitPrimitive(PrimitiveType t, Void unused) { + return t.toString(); + } + }; + var typeName = paramType.accept(typeVisitor, null); + ; + return typeName; + } + /** * Method parameter * @@ -123,12 +204,19 @@ public String toString() { /** * Declared field in the class * - * @param modifiers + * @param modifiers e.g. "private final" + * @param type Type name. Includes generics. Can be, e.g., {@code Option}. */ public record ClassField(String modifiers, String type, String name) { @Override public String toString() { return modifiers + " " + type + " " + name; } + + /** Returns simple non-qualified type name. Generic types are returned as raw types. */ + public String simpleTypeName() { + var typeParts = type.split("<"); + return typeParts[0]; + } } } 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 cf9037149a5e..301083bd629b 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 @@ -157,11 +157,13 @@ private String fieldsCode() { var code = """ $userDefinedFields; - // Not final on purpose - private DiagnosticStorage diagnostics; - private MetadataStorage passData; - private IdentifiedLocation location; - private UUID id; + // The following meta fields cannot be private, as we are explicitly + // setting them in the `duplicate` method. Inheritor should not access + // these fields directly + protected DiagnosticStorage diagnostics; + protected MetadataStorage passData; + protected IdentifiedLocation location; + protected UUID id; """ .replace("$userDefinedFields", userDefinedFields); return indent(code, 2); @@ -180,7 +182,7 @@ private String defaultConstructor() { */ """; var ctorCode = - constructorForFields(generatedClassContext.getConstructorParameters(), List.of()); + constructorForFields(generatedClassContext.getSuperclassConstructorParameters(), List.of()); return Utils.indent(docs + ctorCode, 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 360dfcf5a58f..6971fb1f247f 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 @@ -226,7 +226,7 @@ private static String generateSingleNodeClass( $imports $docs - class $className implements $interfaces { + abstract class $className implements $interfaces { $classBody } """ 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 87117ebd4828..62819515f4fc 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 @@ -59,11 +59,6 @@ public String generateBuilder() { .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 { @@ -75,10 +70,7 @@ public static final class Builder { $fieldSetters - public $className build() { - validate(); - return new $className($fieldList); - } + $buildMethod private void validate() { $validationCode @@ -88,8 +80,7 @@ private void validate() { .replace("$fieldDeclarations", fieldDeclarations) .replace("$copyConstructor", copyConstructor()) .replace("$fieldSetters", fieldSetters) - .replace("$className", generatedClassContext.getClassName()) - .replace("$fieldList", fieldList) + .replace("$buildMethod", buildMethod()) .replace("$validationCode", Utils.indent(validationCode, 2)); return Utils.indent(code, 2); } @@ -112,4 +103,38 @@ private String copyConstructor() { sb.append("}").append(System.lineSeparator()); return sb.toString(); } + + private String buildMethod() { + var sb = new StringBuilder(); + var processedClassName = + generatedClassContext.getProcessedClass().getClazz().getSimpleName().toString(); + var ctorParams = generatedClassContext.getSubclassConstructorParameters(); + var ctorParamsStr = ctorParams.stream().map(ClassField::name).collect(Collectors.joining(", ")); + var fieldsNotInCtor = Utils.minus(generatedClassContext.getAllFields(), ctorParams); + sb.append(" public ") + .append(processedClassName) + .append(" build() {") + .append(System.lineSeparator()); + sb.append(" ").append("validate();").append(System.lineSeparator()); + sb.append(" ") + .append(processedClassName) + .append(" result = new ") + .append(processedClassName) + .append("(") + .append(ctorParamsStr) + .append(");") + .append(System.lineSeparator()); + for (var fieldNotInCtor : fieldsNotInCtor) { + sb.append(" ") + .append("result.") + .append(fieldNotInCtor.name()) + .append(" = ") + .append(fieldNotInCtor.name()) + .append(";") + .append(System.lineSeparator()); + } + sb.append(" ").append("return result;").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/methodgen/DuplicateMethodGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/DuplicateMethodGenerator.java index 92e0fcdeeb79..1ff3c42ca555 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 @@ -91,10 +91,14 @@ public String generateDuplicateMethodCode() { .replace("$idType", ctx.getIdMetaField().type()); sb.append(Utils.indent(duplicateMetaFieldsCode, 2)); sb.append(System.lineSeparator()); - for (var dupMetaVarName : + for (var metaVar : List.of( - "diagnosticsDuplicated", "passDataDuplicated", "locationDuplicated", "idDuplicated")) { - duplicatedVars.add(new DuplicateVar(null, dupMetaVarName, false)); + new MetaField("DiagnosticStorage", "diagnostics"), + new MetaField("MetadataStorage", "passData"), + new MetaField("IdentifiedLocation", "location"), + new MetaField("UUID", "id"))) { + var dupName = metaVar.name + "Duplicated"; + duplicatedVars.add(new DuplicateVar(metaVar.type, dupName, metaVar.name, false)); } for (var field : ctx.getUserFields()) { @@ -103,32 +107,52 @@ public String generateDuplicateMethodCode() { sb.append(Utils.indent(nullableChildCode(field), 2)); sb.append(System.lineSeparator()); duplicatedVars.add( - new DuplicateVar(field.getSimpleTypeName(), dupFieldName(field), true)); + new DuplicateVar( + field.getSimpleTypeName(), dupFieldName(field), field.getName(), true)); } else { if (field.isList()) { sb.append(Utils.indent(listChildCode(field), 2)); sb.append(System.lineSeparator()); - duplicatedVars.add(new DuplicateVar(null, dupFieldName(field), false)); + duplicatedVars.add( + new DuplicateVar("List", dupFieldName(field), field.getName(), false)); } else if (field.isOption()) { sb.append(Utils.indent(optionChildCode(field), 2)); sb.append(System.lineSeparator()); - duplicatedVars.add(new DuplicateVar(null, dupFieldName(field), false)); + duplicatedVars.add( + new DuplicateVar("Option", dupFieldName(field), field.getName(), false)); } else { sb.append(Utils.indent(notNullableChildCode(field), 2)); sb.append(System.lineSeparator()); duplicatedVars.add( - new DuplicateVar(field.getSimpleTypeName(), dupFieldName(field), true)); + new DuplicateVar( + field.getSimpleTypeName(), dupFieldName(field), field.getName(), true)); } } } else { sb.append(Utils.indent(nonChildCode(field), 2)); sb.append(System.lineSeparator()); - duplicatedVars.add(new DuplicateVar(field.getSimpleTypeName(), dupFieldName(field), false)); + duplicatedVars.add( + new DuplicateVar( + field.getSimpleTypeName(), dupFieldName(field), field.getName(), false)); } } - sb.append(Utils.indent(returnStatement(duplicatedVars), 2)); - sb.append(System.lineSeparator()); + var ctorParams = matchCtorParams(duplicatedVars); + var newSubclass = newSubclass(ctorParams); + sb.append(newSubclass); + + // Rest of the fields that need to be set + var restOfDuplicatedVars = Utils.minus(duplicatedVars, ctorParams); + for (var duplVar : restOfDuplicatedVars) { + sb.append(" ").append("duplicated.").append(duplVar.originalName).append(" = "); + if (duplVar.needsCast) { + sb.append("(").append(duplVar.type).append(") "); + } + sb.append(duplVar.duplicatedName).append(";").append(System.lineSeparator()); + } + + sb.append(" ").append("return duplicated;").append(System.lineSeparator()); + sb.append("}"); sb.append(System.lineSeparator()); return sb.toString(); @@ -221,39 +245,79 @@ private static List parameterNames() { return parameters.stream().map(Parameter::name).collect(Collectors.toList()); } - 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() + /** Generate code for call of a constructor of the subclass. */ + private String newSubclass(List ctorParams) { + var subClassType = ctx.getProcessedClass().getClazz().getSimpleName().toString(); + var ctor = ctx.getProcessedClass().getCtor(); + Utils.hardAssert(ctor.getParameters().size() == ctorParams.size()); + var sb = new StringBuilder(); + sb.append(" ") + .append(subClassType) + .append(" duplicated") + .append(" = ") + .append("new ") + .append(subClassType) + .append("("); + var ctorParamsStr = + ctorParams.stream() .map( - var -> { - if (var.needsCast) { - return "(" + var.type + ") " + var.name; + ctorParam -> { + if (ctorParam.needsCast) { + return "(" + ctorParam.type + ") " + ctorParam.duplicatedName; } else { - return var.name; + return ctorParam.duplicatedName; } }) .collect(Collectors.joining(", ")); - return "return new " + ctx.getClassName() + "(" + argList + ");"; + sb.append(ctorParamsStr).append(");").append(System.lineSeparator()); + return sb.toString(); } - private String dupMethodRetType() { - return duplicateMethod.getReturnType().toString(); + /** + * Returns sublist of the given list that matches the parameters of the constructor of the + * subclass. + * + * @param duplicatedVars All duplicated variables. + * @return sublist, potentially reordered. + */ + private List matchCtorParams(List duplicatedVars) { + var ctorParams = new ArrayList(); + for (var subclassCtorParam : ctx.getSubclassConstructorParameters()) { + var paramType = subclassCtorParam.simpleTypeName(); + var paramName = subclassCtorParam.name(); + duplicatedVars.stream() + .filter(var -> var.type.equals(paramType) && var.originalName.equals(paramName)) + .findFirst() + .ifPresentOrElse( + ctorParams::add, + () -> { + var errMsg = + String.format( + "No matching field found for parameter %s of type %s. All duplicated vars:" + + " %s", + paramName, paramType, duplicatedVars); + Utils.printErrorAndFail( + errMsg, + ctx.getProcessedClass().getCtor(), + ctx.getProcessingEnvironment().getMessager()); + }); + } + return ctorParams; } - private static String stripWhitespaces(String s) { - return s.replaceAll("\\s+", ""); + private String dupMethodRetType() { + return duplicateMethod.getReturnType().toString(); } /** - * @param type Nullable - * @param name + * @param type Simple type name. Must not be null. + * @param duplicatedName Name of the duplicated variable + * @param originalName Name of the original variable (field) * @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) {} + private record DuplicateVar( + String type, String duplicatedName, String originalName, boolean needsCast) {} /** * Parameter for the duplicate method @@ -268,4 +332,6 @@ public String toString() { return type + " " + name; } } + + private record MetaField(String type, String name) {} } diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java index 834a3f04c9b0..ef2cf7bb3cd8 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java @@ -2,6 +2,7 @@ import java.lang.annotation.Annotation; import java.util.ArrayDeque; +import java.util.List; import java.util.function.Predicate; import java.util.stream.Collectors; import javax.annotation.processing.Messager; @@ -203,4 +204,8 @@ public static T iterateSuperInterfaces( } return null; } + + public static List minus(List superset, List subset) { + return superset.stream().filter(e -> !subset.contains(e)).collect(Collectors.toList()); + } } From 30f14d9678889803ce4ecb58ff3782fc360ed66c Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Tue, 24 Dec 2024 18:26:10 +0100 Subject: [PATCH 127/195] Annotated class must extend the generated super class --- .../processor/test/TestIRProcessorInline.java | 26 ++++++++++++++-- .../runtime/parser/processor/IRProcessor.java | 31 +++++++++++++++++-- 2 files changed, 52 insertions(+), 5 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 5d60c24b425b..3fe8a952c266 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,7 +92,7 @@ public class JName {} } @Test - public void annotatedClassMustHaveAnnotatedConstructor() { + public void annotatedClass_MustHaveAnnotatedConstructor() { var src = JavaFileObjects.forSourceString( "JName", @@ -108,6 +108,24 @@ public final class JName {} .hadErrorContaining("must have exactly one constructor annotated with @GenerateFields"); } + @Test + public void annotatedClass_MustExtendGeneratedSuperclass() { + var src = + """ + import org.enso.runtime.parser.dsl.GenerateIR; + import org.enso.runtime.parser.dsl.GenerateFields; + + @GenerateIR + public final class JName { + @GenerateFields + public JName() {} + } + """; + var compilation = compile("JName", src); + CompilationSubject.assertThat(compilation).failed(); + CompilationSubject.assertThat(compilation).hadErrorContaining("must extend"); + } + @Test public void simpleIRNodeWithUserDefinedFiled_CompilationSucceeds() { var src = @@ -407,9 +425,11 @@ public interface JName extends IR { String name(); @GenerateIR(interfaces = "JName") - public final class JBlank { + public final class JBlank extends JBlankGen { @GenerateFields - public JBlank(@IRField String name) {} + public JBlank(@IRField String name) { + super(name); + } } } """); 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 6971fb1f247f..ceeb27538093 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,6 +13,7 @@ import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeKind; import javax.tools.JavaFileObject; import org.enso.runtime.parser.dsl.GenerateFields; import org.enso.runtime.parser.dsl.GenerateIR; @@ -59,15 +60,17 @@ private boolean processGenerateIRElem(TypeElement processedClassElem) { if (!ensureHasSingleAnnotatedConstructor(processedClassElem)) { return false; } + if (!ensureExtendsGeneratedSuperclass(processedClassElem)) { + return false; + } var processedClass = constructProcessedClass(processedClassElem); if (processedClass == null) { printError("Failed to construct ProcessedClass", processedClassElem); return false; } - var processedClassName = processedClassElem.getSimpleName().toString(); var pkgName = packageName(processedClassElem); - var newClassName = processedClassName + "Gen"; + var newClassName = generatedClassName(processedClassElem); String newBinaryName; if (!pkgName.isEmpty()) { newBinaryName = pkgName + "." + newClassName; @@ -120,6 +123,10 @@ private TypeElement findInterface(TypeElement processedClassElem, String interfa return null; } + private static String generatedClassName(TypeElement processedClassElem) { + return processedClassElem.getSimpleName().toString() + "Gen"; + } + private static boolean isBinaryName(String name) { return name.contains("."); } @@ -188,6 +195,26 @@ private boolean ensureHasSingleAnnotatedConstructor(TypeElement clazz) { return true; } + private boolean ensureExtendsGeneratedSuperclass(TypeElement clazz) { + var superClass = clazz.getSuperclass(); + if (superClass.getKind() == TypeKind.NONE) { + return false; + } + var superClassName = superClass.toString(); + var genClassName = generatedClassName(clazz); + var extendsGeneratedSuperclass = superClassName.equals(genClassName); + if (!extendsGeneratedSuperclass) { + printError( + "Class annotated with @GenerateIR must extend generated superclass '" + + genClassName + + "'", + clazz); + return false; + } else { + return true; + } + } + private static ExecutableElement getAnnotatedCtor(TypeElement clazz) { // It should already be ensured that there is only a single annotated constructor in the class, // hence the AssertionError From ba602c4eafeb617729d7bd30567a1d2b61ba7952 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Tue, 24 Dec 2024 18:32:24 +0100 Subject: [PATCH 128/195] Fix tests - annotated class must extend the generated super class --- .../processor/test/TestIRProcessorInline.java | 68 ++++++++++++------- 1 file changed, 43 insertions(+), 25 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 3fe8a952c266..8d56d95ab142 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 @@ -64,7 +64,7 @@ public void simpleIRNodeWithoutFields_CompilationSucceeds() { import org.enso.runtime.parser.dsl.GenerateFields; @GenerateIR - public final class JName { + public final class JName extends JNameGen { @GenerateFields public JName() {} } @@ -135,9 +135,11 @@ public void simpleIRNodeWithUserDefinedFiled_CompilationSucceeds() { import org.enso.runtime.parser.dsl.IRField; @GenerateIR - public final class JName { + public final class JName extends JNameGen { @GenerateFields - public JName(@IRField String name) {} + public JName(@IRField String name) { + super(name); + } } """; var genClass = generatedClass("JName", src); @@ -153,7 +155,7 @@ public void generatedClassHasProtectedConstructor() { import org.enso.runtime.parser.dsl.GenerateFields; @GenerateIR - public final class JName { + public final class JName extends JNameGen { @GenerateFields public JName() {} } @@ -173,7 +175,7 @@ public void generatedClassHasConstructor_WithMetaFields() { import org.enso.runtime.parser.dsl.GenerateFields; @GenerateIR - public final class JName { + public final class JName extends JNameGen { @GenerateFields public JName() {} } @@ -200,7 +202,7 @@ public void generatedClassHasConstructor_WithUserFields() { import org.enso.runtime.parser.dsl.GenerateFields; @GenerateIR - public final class JName { + public final class JName extends JNameGen { @GenerateFields public JName() {} } @@ -221,7 +223,7 @@ public void generatedClass_IsAbstract() { import org.enso.runtime.parser.dsl.GenerateFields; @GenerateIR - public final class JName { + public final class JName extends JNameGen { @GenerateFields public JName() {} } @@ -238,7 +240,7 @@ public void annotatedConstructor_MustNotHaveUnannotatedParameters() { import org.enso.runtime.parser.dsl.GenerateFields; @GenerateIR - public final class JName { + public final class JName extends JNameGen { @GenerateFields public JName(int param) {} } @@ -260,9 +262,11 @@ public void annotatedConstructor_CanHaveMetaParameters() { import org.enso.compiler.core.ir.MetadataStorage; @GenerateIR - public final class JName { + public final class JName extends JNameGen { @GenerateFields - public JName(MetadataStorage passData) {} + public JName(MetadataStorage passData) { + super(); + } } """; var compilation = compile("JName", src); @@ -281,9 +285,11 @@ public void simpleIRNodeWithChild() { import org.enso.compiler.core.ir.Expression; @GenerateIR - public final class MyIR { + public final class MyIR extends MyIRGen { @GenerateFields - public MyIR(@IRChild Expression expression) {} + public MyIR(@IRChild Expression expression) { + super(expression); + } } """); assertThat(genSrc, containsString("Expression expression()")); @@ -301,9 +307,11 @@ public void irNodeWithMultipleFields_PrimitiveField() { import org.enso.runtime.parser.dsl.IRField; @GenerateIR - public final class MyIR { + public final class MyIR extends MyIRGen { @GenerateFields - public MyIR(@IRField boolean suspended) {} + public MyIR(@IRField boolean suspended) { + super(suspended); + } } """); assertThat(genSrc, containsString("boolean suspended()")); @@ -323,7 +331,7 @@ interface MySuperIR { } @GenerateIR(interfaces = "MySuperIR") - public final class MyIR { + public final class MyIR extends MyIRGen { @GenerateFields public MyIR() {} } @@ -350,9 +358,11 @@ interface MySuperIR extends IR { } @GenerateIR(interfaces = "MySuperIR") - public final class MyIR { + public final class MyIR extends MyIRGen { @GenerateFields - public MyIR(@IRField boolean suspended) {} + public MyIR(@IRField boolean suspended) { + super(suspended); + } } """); assertThat(src, containsString("boolean suspended()")); @@ -374,9 +384,11 @@ interface MySuperIR extends IR { } @GenerateIR - public final class MyIR { + public final class MyIR extends MyIRGen { @GenerateFields - public MyIR(@IRField boolean suspended) {} + public MyIR(@IRField boolean suspended) { + super(suspended); + } } """); @@ -402,9 +414,11 @@ interface MySuperIR extends MySuperSuperIR { } @GenerateIR(interfaces = "MySuperIR") - public final class MyIR { + public final class MyIR extends MyIRGen { @GenerateFields - public MyIR(@IRField boolean suspended) {} + public MyIR(@IRField boolean suspended) { + super(suspended); + } } """); assertThat(src, containsString("boolean suspended()")); @@ -450,9 +464,11 @@ public void fieldCanBeScalaList() { import scala.collection.immutable.List; @GenerateIR - public final class JName { + public final class JName extends JNameGen { @GenerateFields - public JName(@IRChild List expressions) {} + public JName(@IRChild List expressions) { + super(expressions); + } } """); assertThat(src, containsString("class JNameGen")); @@ -472,9 +488,11 @@ public void fieldCanBeScalaOption() { import scala.Option; @GenerateIR - public final class JName { + public final class JName extends JNameGen { @GenerateFields - public JName(@IRChild Option expression) {} + public JName(@IRChild Option expression) { + super(expression); + } } """); assertThat(src, containsString("class JNameGen")); From 9bae261d56678d453050791dd889d998552a2ed6 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Tue, 24 Dec 2024 19:04:16 +0100 Subject: [PATCH 129/195] Use specific IRProcessingException. - Do not use asserts or any other exceptions. - Error is printed ony in the main processing loop. --- .../processor/test/TestIRProcessorInline.java | 10 +- .../processor/GeneratedClassContext.java | 5 +- .../processor/IRNodeClassGenerator.java | 2 +- .../processor/IRProcessingException.java | 25 ++++ .../runtime/parser/processor/IRProcessor.java | 115 ++++++++---------- .../processor/field/FieldCollector.java | 19 ++- .../methodgen/DuplicateMethodGenerator.java | 16 ++- .../MapExpressionsMethodGenerator.java | 7 +- .../methodgen/SetLocationMethodGenerator.java | 11 +- .../runtime/parser/processor/utils/Utils.java | 14 +-- 10 files changed, 107 insertions(+), 117 deletions(-) create mode 100644 engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessingException.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 8d56d95ab142..1a2216c2fb0a 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 @@ -3,7 +3,6 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; -import static org.junit.Assert.fail; import com.google.testing.compile.Compilation; import com.google.testing.compile.CompilationSubject; @@ -245,12 +244,9 @@ public final class JName extends JNameGen { public JName(int param) {} } """; - try { - compile("JName", src); - fail("Exception in compilation expected"); - } catch (Exception e) { - assertThat(e.getMessage(), containsString("must be annotated")); - } + var compilation = compile("JName", src); + CompilationSubject.assertThat(compilation).failed(); + CompilationSubject.assertThat(compilation).hadErrorContaining("must be annotated"); } @Test 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 c3be850ad4ec..690359ec9ff0 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 @@ -64,7 +64,7 @@ public final class GeneratedClassContext { private static void ensureSimpleName(String name) { if (name.contains(".")) { - throw new IllegalArgumentException("Class name must be simple, not qualified"); + throw new IRProcessingException("Class name must be simple, not qualified", null); } } @@ -141,8 +141,7 @@ public List getSubclassConstructorParameters() { String.format( "No matching field found for parameter %s of type %s. All fields: %s", paramName, paramSimpleType, allFields); - Utils.printErrorAndFail(errMsg, param, processingEnvironment.getMessager()); - return null; + throw new IRProcessingException(errMsg, param); }; var fieldsWithSameType = allFields.stream() 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 301083bd629b..14160038de7d 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 @@ -56,7 +56,7 @@ final class IRNodeClassGenerator { */ IRNodeClassGenerator( ProcessingEnvironment processingEnv, ProcessedClass processedClass, String className) { - assert !className.contains(".") : "Class name should be simple, not qualified"; + Utils.hardAssert(!className.contains("."), "Class name should be simple, not qualified"); this.processingEnv = processingEnv; this.processedClass = processedClass; this.className = className; diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessingException.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessingException.java new file mode 100644 index 000000000000..3956ae44d482 --- /dev/null +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/IRProcessingException.java @@ -0,0 +1,25 @@ +package org.enso.runtime.parser.processor; + +import javax.lang.model.element.Element; + +/** + * This exception should be contained only in IR element processing. It is caught in the main + * processing loop in {@link IRProcessor}. + */ +public final class IRProcessingException extends RuntimeException { + private final Element element; + + public IRProcessingException(String message, Element element, Throwable cause) { + super(message, cause); + this.element = element; + } + + public IRProcessingException(String message, Element element) { + super(message); + this.element = element; + } + + public Element getElement() { + return element; + } +} 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 ceeb27538093..e365710894ea 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.function.Supplier; import java.util.stream.Collectors; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; @@ -14,6 +15,7 @@ import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeKind; +import javax.tools.Diagnostic.Kind; import javax.tools.JavaFileObject; import org.enso.runtime.parser.dsl.GenerateFields; import org.enso.runtime.parser.dsl.GenerateIR; @@ -35,11 +37,17 @@ public SourceVersion getSupportedSourceVersion() { public boolean process(Set annotations, RoundEnvironment roundEnv) { var generateIRElems = roundEnv.getElementsAnnotatedWith(GenerateIR.class); for (var generateIRElem : generateIRElems) { - if (!ensureIsClass(generateIRElem)) { - return false; - } - var suc = processGenerateIRElem((TypeElement) generateIRElem); - if (!suc) { + try { + ensureIsClass(generateIRElem); + processGenerateIRElem((TypeElement) generateIRElem); + } catch (IRProcessingException e) { + Element element; + if (e.getElement() != null) { + element = e.getElement(); + } else { + element = generateIRElem; + } + processingEnv.getMessager().printMessage(Kind.ERROR, e.getMessage(), element); return false; } } @@ -48,27 +56,14 @@ public boolean process(Set annotations, RoundEnvironment /** * @param processedClassElem Class annotated with {@link GenerateIR}. - * @return true if processing was successful, false otherwise. */ - private boolean processGenerateIRElem(TypeElement processedClassElem) { - if (!ensureIsPublicFinal(processedClassElem)) { - return false; - } - if (!ensureEnclosedInInterfaceOrPackage(processedClassElem)) { - return false; - } - if (!ensureHasSingleAnnotatedConstructor(processedClassElem)) { - return false; - } - if (!ensureExtendsGeneratedSuperclass(processedClassElem)) { - return false; - } - var processedClass = constructProcessedClass(processedClassElem); - if (processedClass == null) { - printError("Failed to construct ProcessedClass", processedClassElem); - return false; - } + private void processGenerateIRElem(TypeElement processedClassElem) { + ensureIsPublicFinal(processedClassElem); + ensureEnclosedInInterfaceOrPackage(processedClassElem); + ensureHasSingleAnnotatedConstructor(processedClassElem); + ensureExtendsGeneratedSuperclass(processedClassElem); + var processedClass = constructProcessedClass(processedClassElem); var pkgName = packageName(processedClassElem); var newClassName = generatedClassName(processedClassElem); String newBinaryName; @@ -78,13 +73,13 @@ private boolean processGenerateIRElem(TypeElement processedClassElem) { newBinaryName = newClassName; } - JavaFileObject srcGen = null; + JavaFileObject srcGen; try { srcGen = processingEnv.getFiler().createSourceFile(newBinaryName, processedClassElem); } catch (IOException e) { - printError("Failed to create source file for IRNode", processedClassElem); + throw new IRProcessingException( + "Failed to create source file for IRNode", processedClassElem, e); } - assert srcGen != null; String generatedCode; var classGenerator = new IRNodeClassGenerator(processingEnv, processedClass, newClassName); @@ -95,10 +90,9 @@ private boolean processGenerateIRElem(TypeElement processedClassElem) { lineWriter.write(generatedCode); } } catch (IOException e) { - printError("Failed to write to source file for IRNode", processedClassElem); - return false; + throw new IRProcessingException( + "Failed to write to source file for IRNode", processedClassElem, e); } - return true; } private TypeElement findInterface(TypeElement processedClassElem, String interfaceName) { @@ -135,83 +129,75 @@ private ProcessedClass constructProcessedClass(TypeElement processedClassElem) { var generateIrAnnot = processedClassElem.getAnnotation(GenerateIR.class); var ifaceToImplement = findInterface(processedClassElem, generateIrAnnot.interfaces()); if (ifaceToImplement == null) { - printError( + throw new IRProcessingException( "Could not find interface '" + generateIrAnnot.interfaces() + "'", processedClassElem); - return null; } if (!Utils.isSubtypeOfIR(ifaceToImplement, processingEnv)) { - printError("Interface to implement must be a subtype of IR interface", ifaceToImplement); - return null; + throw new IRProcessingException( + "Interface to implement must be a subtype of IR interface", ifaceToImplement); } var annotatedCtor = getAnnotatedCtor(processedClassElem); var processedClass = new ProcessedClass(processedClassElem, annotatedCtor, ifaceToImplement); return processedClass; } - private boolean ensureIsClass(Element elem) { + private void ensureIsClass(Element elem) { if (elem.getKind() != ElementKind.CLASS) { - printError("GenerateIR annotation can only be applied to classes", elem); - return false; + throw new IRProcessingException("GenerateIR annotation can only be applied to classes", elem); } - return true; } - private boolean ensureIsPublicFinal(TypeElement clazz) { + private void ensureIsPublicFinal(TypeElement clazz) { if (!clazz.getModifiers().contains(Modifier.FINAL) || !clazz.getModifiers().contains(Modifier.PUBLIC)) { - printError("Class annotated with @GenerateIR must be public final", clazz); - return false; + throw new IRProcessingException( + "Class annotated with @GenerateIR must be public final", clazz); } - return true; } - private boolean ensureEnclosedInInterfaceOrPackage(TypeElement clazz) { + private void ensureEnclosedInInterfaceOrPackage(TypeElement clazz) { var enclosingElem = clazz.getEnclosingElement(); if (enclosingElem != null) { if (!(enclosingElem.getKind() == ElementKind.PACKAGE || enclosingElem.getKind() == ElementKind.INTERFACE)) { - printError( + throw new IRProcessingException( "Class annotated with @GenerateIR must be enclosed in a package or an interface", clazz); - return false; } } - return true; } - private boolean ensureHasSingleAnnotatedConstructor(TypeElement clazz) { + private void ensureHasSingleAnnotatedConstructor(TypeElement clazz) { var annotatedCtorsCnt = clazz.getEnclosedElements().stream() .filter(elem -> elem.getKind() == ElementKind.CONSTRUCTOR) .filter(ctor -> ctor.getAnnotation(GenerateFields.class) != null) .count(); if (annotatedCtorsCnt != 1) { - printError( + throw new IRProcessingException( "Class annotated with @GenerateIR must have exactly one constructor annotated with" + " @GenerateFields", clazz); - return false; } - return true; } - private boolean ensureExtendsGeneratedSuperclass(TypeElement clazz) { + private void ensureExtendsGeneratedSuperclass(TypeElement clazz) { var superClass = clazz.getSuperclass(); + var genClassName = generatedClassName(clazz); + Supplier errSupplier = + () -> + new IRProcessingException( + "Class annotated with @GenerateIR must extend generated superclass '" + + genClassName + + "'", + clazz); if (superClass.getKind() == TypeKind.NONE) { - return false; + throw errSupplier.get(); } var superClassName = superClass.toString(); - var genClassName = generatedClassName(clazz); var extendsGeneratedSuperclass = superClassName.equals(genClassName); if (!extendsGeneratedSuperclass) { - printError( - "Class annotated with @GenerateIR must extend generated superclass '" - + genClassName - + "'", - clazz); - return false; - } else { - return true; + throw errSupplier.get(); } } @@ -222,7 +208,8 @@ private static ExecutableElement getAnnotatedCtor(TypeElement clazz) { .filter(elem -> elem.getAnnotation(GenerateFields.class) != null) .map(elem -> (ExecutableElement) elem) .findFirst() - .orElseThrow(() -> new AssertionError("No constructor annotated with GenerateFields")); + .orElseThrow( + () -> new IRProcessingException("No constructor annotated with GenerateFields", clazz)); } private String packageName(Element elem) { @@ -230,10 +217,6 @@ private String packageName(Element elem) { return pkg.getQualifiedName().toString(); } - private void printError(String msg, Element elem) { - Utils.printError(msg, elem, processingEnv.getMessager()); - } - /** * Generates code for a super class. * 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 9feb4d015bf2..2f163fa6fc14 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 @@ -10,6 +10,7 @@ import javax.lang.model.type.DeclaredType; import org.enso.runtime.parser.dsl.IRChild; import org.enso.runtime.parser.dsl.IRField; +import org.enso.runtime.parser.processor.IRProcessingException; import org.enso.runtime.parser.processor.ProcessedClass; import org.enso.runtime.parser.processor.utils.Utils; @@ -74,8 +75,7 @@ private void collectFromCtor() { "Constructor parameter " + param + " must be annotated with either @IRField or @IRChild"; - Utils.printError(errMsg, param, processingEnv.getMessager()); - throw new IllegalStateException(errMsg); + throw new IRProcessingException(errMsg, param); } if (field != null) { @@ -117,10 +117,9 @@ private Field processIrChild(VariableElement param, IRChild irChildAnnot) { return new OptionField(name, type, typeArgElem); } else { if (!Utils.isSubtypeOfIR(type, processingEnv)) { - Utils.printErrorAndFail( + throw new IRProcessingException( "Constructor parameter annotated with @IRChild must be a subtype of IR interface", - param, - processingEnv.getMessager()); + param); } return new ReferenceField(processingEnv, type, name, isNullable, true); } @@ -134,9 +133,9 @@ private Field processIrChild(VariableElement param, IRChild irChildAnnot) { * @return the generic type parameter */ private TypeElement getGenericType(VariableElement param) { - assert param.asType() instanceof DeclaredType; + Utils.hardAssert(param.asType() instanceof DeclaredType); var declaredType = (DeclaredType) param.asType(); - assert declaredType.getTypeArguments().size() == 1; + Utils.hardAssert(declaredType.getTypeArguments().size() == 1); var typeArg = declaredType.getTypeArguments().get(0); var typeArgElem = (TypeElement) processingEnv.getTypeUtils().asElement(typeArg); ensureIsSubtypeOfIR(typeArgElem); @@ -153,10 +152,8 @@ private TypeElement getParamType(VariableElement param) { 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()); + throw new IRProcessingException( + "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/methodgen/DuplicateMethodGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/DuplicateMethodGenerator.java index 1ff3c42ca555..9d7b18b887a6 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,6 +7,7 @@ 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.IRProcessingException; import org.enso.runtime.parser.processor.field.Field; import org.enso.runtime.parser.processor.utils.Utils; @@ -36,14 +37,14 @@ public DuplicateMethodGenerator(ExecutableElement duplicateMethod, GeneratedClas 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"); + throw new IRProcessingException( + "Duplicate method must have " + parameters.size() + " parameters", duplicateMethod); } 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"); + throw new IRProcessingException( + "All parameters of the duplicate method must be of type boolean", duplicateMethod); } } @@ -163,7 +164,7 @@ private static String dupFieldName(Field field) { } private static String nullableChildCode(Field nullableChild) { - assert nullableChild.isNullable() && nullableChild.isChild(); + Utils.hardAssert(nullableChild.isNullable() && nullableChild.isChild()); return """ IR $dupName = null; if ($childName != null) { @@ -296,10 +297,7 @@ private List matchCtorParams(List duplicatedVars) { "No matching field found for parameter %s of type %s. All duplicated vars:" + " %s", paramName, paramType, duplicatedVars); - Utils.printErrorAndFail( - errMsg, - ctx.getProcessedClass().getCtor(), - ctx.getProcessingEnvironment().getMessager()); + throw new IRProcessingException(errMsg, ctx.getProcessedClass().getCtor()); }); } return ctorParams; 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 0e74fdde515f..00e709e52e9c 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,6 +5,7 @@ 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.IRProcessingException; import org.enso.runtime.parser.processor.field.Field; import org.enso.runtime.parser.processor.utils.Utils; @@ -29,10 +30,8 @@ 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()); + throw new IRProcessingException( + "Map expressions method must have 1 parameter", 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 3efd3d0ad955..4630786574fe 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,6 +2,7 @@ import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.ExecutableElement; +import org.enso.runtime.parser.processor.IRProcessingException; import org.enso.runtime.parser.processor.utils.Utils; public class SetLocationMethodGenerator { @@ -17,13 +18,15 @@ public SetLocationMethodGenerator( private static void ensureCorrectSignature(ExecutableElement setLocationMethod) { if (!setLocationMethod.getSimpleName().toString().equals("setLocation")) { - throw new IllegalArgumentException( - "setLocation method must be named setLocation, but was: " + setLocationMethod); + throw new IRProcessingException( + "setLocation method must be named setLocation, but was: " + setLocationMethod, + setLocationMethod); } if (setLocationMethod.getParameters().size() != 1) { - throw new IllegalArgumentException( + throw new IRProcessingException( "setLocation method must have exactly one parameter, but had: " - + setLocationMethod.getParameters()); + + setLocationMethod.getParameters(), + setLocationMethod); } } diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java index ef2cf7bb3cd8..d2f3623fa03c 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java @@ -5,14 +5,13 @@ import java.util.List; import java.util.function.Predicate; 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; +import org.enso.runtime.parser.processor.IRProcessingException; public final class Utils { @@ -58,15 +57,6 @@ public static boolean isSubtypeOfExpression( return processingEnv.getTypeUtils().isAssignable(type, expressionType); } - public static void printError(String msg, Element elem, Messager messager) { - messager.printMessage(Kind.ERROR, msg, elem); - } - - public static void printErrorAndFail(String msg, Element elem, Messager messager) { - printError(msg, elem, messager); - throw new IllegalStateException("Unexpected failure during annotation processing: " + msg); - } - public static String indent(String code, int indentation) { return code.lines() .map(line -> " ".repeat(indentation) + line) @@ -158,7 +148,7 @@ public static void hardAssert(boolean condition) { public static void hardAssert(boolean condition, String msg) { if (!condition) { - throw new AssertionError(msg); + throw new IRProcessingException(msg, null); } } From 1b069ed8ebd1669ba8c2204ced6cd0f1d4a4d23d Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Tue, 24 Dec 2024 19:17:16 +0100 Subject: [PATCH 130/195] setLocation returns subclass type --- .../processor/test/TestIRProcessorInline.java | 17 +++++++++++++++++ .../parser/processor/IRNodeClassGenerator.java | 2 +- .../methodgen/SetLocationMethodGenerator.java | 14 +++++--------- 3 files changed, 23 insertions(+), 10 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 1a2216c2fb0a..a296461635c5 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 @@ -231,6 +231,23 @@ public JName() {} assertThat(genClass, containsString("abstract class JNameGen")); } + @Test + public void generatedMethod_setLocation_returnsSubClassType() { + var src = + """ + import org.enso.runtime.parser.dsl.GenerateIR; + import org.enso.runtime.parser.dsl.GenerateFields; + + @GenerateIR + public final class JName extends JNameGen { + @GenerateFields + public JName() {} + } + """; + var genClass = generatedClass("JName", src); + assertThat(genClass, containsString("JName setLocation(")); + } + @Test public void annotatedConstructor_MustNotHaveUnannotatedParameters() { var src = 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 14160038de7d..9db637024342 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 { processingEnv, method -> method.getSimpleName().toString().equals("setLocation")); this.setLocationMethodGenerator = - new SetLocationMethodGenerator(setLocationMethod, processingEnv); + new SetLocationMethodGenerator(setLocationMethod, generatedClassContext); this.equalsMethodGenerator = new EqualsMethodGenerator(generatedClassContext); this.hashCodeMethodGenerator = new HashCodeMethodGenerator(generatedClassContext); } 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 4630786574fe..61d7d74aa18e 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 @@ -1,18 +1,18 @@ package org.enso.runtime.parser.processor.methodgen; -import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.ExecutableElement; +import org.enso.runtime.parser.processor.GeneratedClassContext; import org.enso.runtime.parser.processor.IRProcessingException; import org.enso.runtime.parser.processor.utils.Utils; public class SetLocationMethodGenerator { private final ExecutableElement setLocationMethod; - private final ProcessingEnvironment processingEnv; + private final GeneratedClassContext ctx; public SetLocationMethodGenerator( - ExecutableElement setLocationMethod, ProcessingEnvironment processingEnv) { + ExecutableElement setLocationMethod, GeneratedClassContext ctx) { ensureCorrectSignature(setLocationMethod); - this.processingEnv = processingEnv; + this.ctx = ctx; this.setLocationMethod = setLocationMethod; } @@ -47,10 +47,6 @@ public String generateMethodCode() { } private String retType() { - return processingEnv - .getTypeUtils() - .asElement(setLocationMethod.getReturnType()) - .getSimpleName() - .toString(); + return ctx.getProcessedClass().getClazz().getSimpleName().toString(); } } From b3e262b3813af347b164f1ef7beb32e26abd51f6 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Tue, 24 Dec 2024 19:17:27 +0100 Subject: [PATCH 131/195] Reorder generated fields in JCallArgument --- .../parser/processor/test/gen/ir/core/JCallArgument.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JCallArgument.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JCallArgument.java index 206b312f30db..700b969ce057 100644 --- a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JCallArgument.java +++ b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JCallArgument.java @@ -35,8 +35,8 @@ JCallArgument duplicate( final class JSpecified extends JSpecifiedGen { @GenerateFields public JSpecified( - @IRField boolean isSynthetic, @IRChild Expression value, @IRChild Option name) { - super(isSynthetic, value, name); + @IRField boolean isSynthetic, @IRChild Option name, @IRChild Expression value) { + super(isSynthetic, name, value); } } } From 1a878991a15ec422fae0f63966438cfb597d2964 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Tue, 24 Dec 2024 19:21:13 +0100 Subject: [PATCH 132/195] mapExpressions returns subclass type --- .../processor/test/TestIRProcessorInline.java | 17 +++++++++++++++++ .../MapExpressionsMethodGenerator.java | 3 ++- 2 files changed, 19 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 a296461635c5..1a38ac38cd7c 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 @@ -248,6 +248,23 @@ public JName() {} assertThat(genClass, containsString("JName setLocation(")); } + @Test + public void generatedMethod_mapExpressions_returnsSubClassType() { + var src = + """ + import org.enso.runtime.parser.dsl.GenerateIR; + import org.enso.runtime.parser.dsl.GenerateFields; + + @GenerateIR + public final class JName extends JNameGen { + @GenerateFields + public JName() {} + } + """; + var genClass = generatedClass("JName", src); + assertThat(genClass, containsString("JName mapExpressions(")); + } + @Test public void annotatedConstructor_MustNotHaveUnannotatedParameters() { var src = 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 00e709e52e9c..27f5f3fc6cd1 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 @@ -37,9 +37,10 @@ private void ensureMapExpressionsMethodHasExpectedSignature( public String generateMapExpressionsMethodCode() { var sb = new StringBuilder(); + var subclassType = ctx.getProcessedClass().getClazz().getSimpleName().toString(); sb.append("@Override").append(System.lineSeparator()); sb.append("public ") - .append(mapExpressionsMethod.getReturnType()) + .append(subclassType) .append(" ") .append(METHOD_NAME) .append("(") From e7dc7254ddbc7d776db836e8d3e389dd851c7476 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Tue, 24 Dec 2024 19:45:36 +0100 Subject: [PATCH 133/195] Add scala tests to runtime-parser-processor-tests --- build.sbt | 9 +++++---- .../processor/test/GeneratedIRTest.scala | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+), 4 deletions(-) create mode 100644 engine/runtime-parser-processor-tests/src/test/scala/org/enso/runtime/parser/processor/test/GeneratedIRTest.scala diff --git a/build.sbt b/build.sbt index 4d1d67842c30..2e6468e1870f 100644 --- a/build.sbt +++ b/build.sbt @@ -3268,10 +3268,11 @@ lazy val `runtime-parser-processor-tests` = ), 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 + "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, + "org.scalatest" %% "scalatest" % scalatestVersion % Test ) ) .dependsOn(`runtime-parser-processor`) diff --git a/engine/runtime-parser-processor-tests/src/test/scala/org/enso/runtime/parser/processor/test/GeneratedIRTest.scala b/engine/runtime-parser-processor-tests/src/test/scala/org/enso/runtime/parser/processor/test/GeneratedIRTest.scala new file mode 100644 index 000000000000..ba34dc39f4ac --- /dev/null +++ b/engine/runtime-parser-processor-tests/src/test/scala/org/enso/runtime/parser/processor/test/GeneratedIRTest.scala @@ -0,0 +1,19 @@ +package org.enso.runtime.parser.processor.test + +import org.enso.compiler.core.ir.{Literal, MetadataStorage} +import org.enso.runtime.parser.processor.test.gen.ir.core.JCallArgument.JSpecified +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +/** Tests IR elements generated from package [[org.enso.runtime.parser.processor.test.gen.ir]]. + */ +class GeneratedIRTest extends AnyFlatSpec with Matchers { + "JSpecifiedGen" should "be duplicated correctly" in { + val lit = Literal.Text("foo", null, new MetadataStorage()) + val callArg = new JSpecified(true, None, lit) + callArg should not be null + + val dupl = callArg.duplicate(false, false, false, false) + dupl.value() shouldEqual lit + } +} From 0a1f5a88695c8f1c359e45eb72bdce94215e23aa Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Tue, 24 Dec 2024 20:08:58 +0100 Subject: [PATCH 134/195] [WIP] Demonstrate implementation of unapply --- .../parser/processor/test/gen/ir/core/JCallArgument.java | 5 +++++ .../runtime/parser/processor/test/GeneratedIRTest.scala | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JCallArgument.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JCallArgument.java index 700b969ce057..14cdc9eac4e5 100644 --- a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JCallArgument.java +++ b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JCallArgument.java @@ -9,6 +9,7 @@ import org.enso.runtime.parser.dsl.IRChild; import org.enso.runtime.parser.dsl.IRField; import scala.Option; +import scala.Tuple3; /** Call-site arguments in Enso. */ public interface JCallArgument extends IR { @@ -38,5 +39,9 @@ public JSpecified( @IRField boolean isSynthetic, @IRChild Option name, @IRChild Expression value) { super(isSynthetic, name, value); } + + public static Option, Expression>> unapply(JSpecified self) { + return Option.apply(new Tuple3<>(self.isSynthetic(), self.name(), self.value())); + } } } diff --git a/engine/runtime-parser-processor-tests/src/test/scala/org/enso/runtime/parser/processor/test/GeneratedIRTest.scala b/engine/runtime-parser-processor-tests/src/test/scala/org/enso/runtime/parser/processor/test/GeneratedIRTest.scala index ba34dc39f4ac..d24357c808ef 100644 --- a/engine/runtime-parser-processor-tests/src/test/scala/org/enso/runtime/parser/processor/test/GeneratedIRTest.scala +++ b/engine/runtime-parser-processor-tests/src/test/scala/org/enso/runtime/parser/processor/test/GeneratedIRTest.scala @@ -16,4 +16,13 @@ class GeneratedIRTest extends AnyFlatSpec with Matchers { val dupl = callArg.duplicate(false, false, false, false) dupl.value() shouldEqual lit } + + "JSpecified" should "can implement unapply" in { + val lit = Literal.Text("foo", null, new MetadataStorage()) + val callArg = new JSpecified(true, None, lit) + callArg match { + case JSpecified(isSynthetic, _, _) => + isSynthetic shouldEqual true + } + } } From 83fe02318e2440c2b5c65df69dc1a007d8c14889 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 27 Dec 2024 17:20:09 +0100 Subject: [PATCH 135/195] Revert "[WIP] Demonstrate implementation of unapply" This reverts commit 0a1f5a88695c8f1c359e45eb72bdce94215e23aa. --- .../parser/processor/test/gen/ir/core/JCallArgument.java | 5 ----- .../runtime/parser/processor/test/GeneratedIRTest.scala | 9 --------- 2 files changed, 14 deletions(-) diff --git a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JCallArgument.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JCallArgument.java index 14cdc9eac4e5..700b969ce057 100644 --- a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JCallArgument.java +++ b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JCallArgument.java @@ -9,7 +9,6 @@ import org.enso.runtime.parser.dsl.IRChild; import org.enso.runtime.parser.dsl.IRField; import scala.Option; -import scala.Tuple3; /** Call-site arguments in Enso. */ public interface JCallArgument extends IR { @@ -39,9 +38,5 @@ public JSpecified( @IRField boolean isSynthetic, @IRChild Option name, @IRChild Expression value) { super(isSynthetic, name, value); } - - public static Option, Expression>> unapply(JSpecified self) { - return Option.apply(new Tuple3<>(self.isSynthetic(), self.name(), self.value())); - } } } diff --git a/engine/runtime-parser-processor-tests/src/test/scala/org/enso/runtime/parser/processor/test/GeneratedIRTest.scala b/engine/runtime-parser-processor-tests/src/test/scala/org/enso/runtime/parser/processor/test/GeneratedIRTest.scala index d24357c808ef..ba34dc39f4ac 100644 --- a/engine/runtime-parser-processor-tests/src/test/scala/org/enso/runtime/parser/processor/test/GeneratedIRTest.scala +++ b/engine/runtime-parser-processor-tests/src/test/scala/org/enso/runtime/parser/processor/test/GeneratedIRTest.scala @@ -16,13 +16,4 @@ class GeneratedIRTest extends AnyFlatSpec with Matchers { val dupl = callArg.duplicate(false, false, false, false) dupl.value() shouldEqual lit } - - "JSpecified" should "can implement unapply" in { - val lit = Literal.Text("foo", null, new MetadataStorage()) - val callArg = new JSpecified(true, None, lit) - callArg match { - case JSpecified(isSynthetic, _, _) => - isSynthetic shouldEqual true - } - } } From 2ae6a5963af5ec84a088aa873ed58e5cd618d00f Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 30 Dec 2024 12:19:39 +0100 Subject: [PATCH 136/195] Improve error message in IRProcessor --- .../runtime/parser/processor/IRProcessor.java | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 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 e365710894ea..82e83791af32 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.function.Supplier; import java.util.stream.Collectors; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; @@ -184,20 +183,24 @@ private void ensureHasSingleAnnotatedConstructor(TypeElement clazz) { private void ensureExtendsGeneratedSuperclass(TypeElement clazz) { var superClass = clazz.getSuperclass(); var genClassName = generatedClassName(clazz); - Supplier errSupplier = - () -> - new IRProcessingException( - "Class annotated with @GenerateIR must extend generated superclass '" - + genClassName - + "'", - clazz); if (superClass.getKind() == TypeKind.NONE) { - throw errSupplier.get(); + throw new IRProcessingException( + "Class annotated with @GenerateIR must extend generated superclass'" + + genClassName + + "' " + + "But instead extends no class.", + clazz); } var superClassName = superClass.toString(); var extendsGeneratedSuperclass = superClassName.equals(genClassName); if (!extendsGeneratedSuperclass) { - throw errSupplier.get(); + throw new IRProcessingException( + "Class annotated with @GenerateIR must extend generated superclass'" + + genClassName + + "'. " + + "The actual extended class is " + + superClassName, + clazz); } } From d8160b581824e01da92a118abd553a7dac64da0a Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 30 Dec 2024 12:19:58 +0100 Subject: [PATCH 137/195] Fix check of super class --- .../java/org/enso/runtime/parser/processor/IRProcessor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 82e83791af32..f29bc3166979 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 @@ -192,7 +192,7 @@ private void ensureExtendsGeneratedSuperclass(TypeElement clazz) { clazz); } var superClassName = superClass.toString(); - var extendsGeneratedSuperclass = superClassName.equals(genClassName); + var extendsGeneratedSuperclass = superClassName.contains(genClassName); if (!extendsGeneratedSuperclass) { throw new IRProcessingException( "Class annotated with @GenerateIR must extend generated superclass'" From 0edd8324c51306cb20b12cae5f7223a97e678ce4 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 30 Dec 2024 12:21:13 +0100 Subject: [PATCH 138/195] Generated classes are compiled with -parameters --- build.sbt | 13 ++++++++++++- .../parser/processor/test/GeneratedIRTest.scala | 6 ++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 2e6468e1870f..1bbe83446901 100644 --- a/build.sbt +++ b/build.sbt @@ -2577,6 +2577,13 @@ lazy val mixedJavaScalaProjectSetting: SettingsDefinition = Seq( excludeFilter := excludeFilter.value || "module-info.java" ) +/** Ensure that javac compiler generates parameter names for methods, so that these + * Java methods can be called with named parameters from Scala. + */ +lazy val javaMethodParametersSetting: SettingsDefinition = Seq( + javacOptions += "-parameters" +) + def customFrgaalJavaCompilerSettings(targetJdk: String) = { // There might be slightly different Frgaal compiler configuration for // both Compile and Test configurations @@ -3204,6 +3211,7 @@ lazy val `runtime-parser` = .settings( scalaModuleDependencySetting, mixedJavaScalaProjectSetting, + javaMethodParametersSetting, version := mavenUploadVersion, javadocSettings, publish / skip := false, @@ -3252,7 +3260,8 @@ lazy val `runtime-parser-dsl` = (project in file("engine/runtime-parser-dsl")) .enablePlugins(JPMSPlugin) .settings( - frgaalJavaCompilerSetting + frgaalJavaCompilerSetting, + javaMethodParametersSetting ) lazy val `runtime-parser-processor-tests` = @@ -3260,6 +3269,7 @@ lazy val `runtime-parser-processor-tests` = .settings( inConfig(Compile)(truffleRunOptionsSettings), frgaalJavaCompilerSetting, + javaMethodParametersSetting, commands += WithDebugCommand.withDebug, annotationProcSetting, Compile / javacOptions ++= Seq( @@ -3283,6 +3293,7 @@ lazy val `runtime-parser-processor` = .enablePlugins(JPMSPlugin) .settings( frgaalJavaCompilerSetting, + javaMethodParametersSetting, Compile / internalModuleDependencies := Seq( (`runtime-parser-dsl` / Compile / exportedModule).value ) diff --git a/engine/runtime-parser-processor-tests/src/test/scala/org/enso/runtime/parser/processor/test/GeneratedIRTest.scala b/engine/runtime-parser-processor-tests/src/test/scala/org/enso/runtime/parser/processor/test/GeneratedIRTest.scala index ba34dc39f4ac..d2d23f73c12d 100644 --- a/engine/runtime-parser-processor-tests/src/test/scala/org/enso/runtime/parser/processor/test/GeneratedIRTest.scala +++ b/engine/runtime-parser-processor-tests/src/test/scala/org/enso/runtime/parser/processor/test/GeneratedIRTest.scala @@ -16,4 +16,10 @@ class GeneratedIRTest extends AnyFlatSpec with Matchers { val dupl = callArg.duplicate(false, false, false, false) dupl.value() shouldEqual lit } + + "JSpecifiedGen" should "have generated parameter names with javac compiler" in { + val lit = Literal.Text("foo", null, new MetadataStorage()) + val callArg = new JSpecified(isSynthetic = true, value = lit, name = None) + callArg should not be null + } } From e54fdb933026929d618c3855689657ef57059d98 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 30 Dec 2024 13:20:33 +0100 Subject: [PATCH 139/195] GenerateIR.interfaces is of type Class[] --- .../enso/runtime/parser/dsl/GenerateIR.java | 10 ++- .../test/gen/ir/core/JCallArgument.java | 2 +- .../test/gen/ir/core/JExpression.java | 4 +- .../processor/test/TestIRProcessorInline.java | 8 +-- .../runtime/parser/processor/IRProcessor.java | 72 +++++++++++-------- 5 files changed, 52 insertions(+), 44 deletions(-) diff --git a/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/GenerateIR.java b/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/GenerateIR.java index d6e8a01b3424..cbe01912174c 100644 --- a/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/GenerateIR.java +++ b/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/GenerateIR.java @@ -21,11 +21,9 @@ public @interface GenerateIR { /** - * Name of the interfaces that the generated superclass must implement. All the interfaces must be - * subtypes of the {@code org.enso.compiler.ir.IR} interface. All the abstract parameterless - * methods from all the interfaces will be implemented by the generated class. Must not be empty. - * - * @return + * Interfaces that the generated superclass must implement. All the interfaces must be subtypes of + * the {@code org.enso.compiler.ir.IR} interface. All the abstract parameterless methods from all + * the interfaces will be implemented by the generated class. */ - String interfaces() default "org.enso.compiler.core.IR"; + Class[] interfaces() default {}; } diff --git a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JCallArgument.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JCallArgument.java index 700b969ce057..12a8596bfc15 100644 --- a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JCallArgument.java +++ b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JCallArgument.java @@ -31,7 +31,7 @@ JCallArgument duplicate( boolean keepDiagnostics, boolean keepIdentifiers); - @GenerateIR(interfaces = "JCallArgument") + @GenerateIR(interfaces = {JCallArgument.class}) final class JSpecified extends JSpecifiedGen { @GenerateFields public JSpecified( diff --git a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JExpression.java b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JExpression.java index 2975ebe2aad6..92116fbfd0d3 100644 --- a/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JExpression.java +++ b/engine/runtime-parser-processor-tests/src/main/java/org/enso/runtime/parser/processor/test/gen/ir/core/JExpression.java @@ -21,7 +21,7 @@ JExpression duplicate( boolean keepDiagnostics, boolean keepIdentifiers); - @GenerateIR(interfaces = "JExpression") + @GenerateIR(interfaces = {JExpression.class}) final class JBlock extends JBlockGen { @GenerateFields public JBlock( @@ -32,7 +32,7 @@ public JBlock( } } - @GenerateIR(interfaces = "JExpression") + @GenerateIR(interfaces = {JExpression.class}) final class JBinding extends JBindingGen { @GenerateFields public JBinding(@IRChild Name name, @IRChild JExpression expression) { 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 1a38ac38cd7c..1de521d61f87 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 @@ -360,7 +360,7 @@ interface MySuperIR { boolean suspended(); } - @GenerateIR(interfaces = "MySuperIR") + @GenerateIR(interfaces = {MySuperIR.class}) public final class MyIR extends MyIRGen { @GenerateFields public MyIR() {} @@ -387,7 +387,7 @@ interface MySuperIR extends IR { boolean suspended(); } - @GenerateIR(interfaces = "MySuperIR") + @GenerateIR(interfaces = {MySuperIR.class}) public final class MyIR extends MyIRGen { @GenerateFields public MyIR(@IRField boolean suspended) { @@ -443,7 +443,7 @@ interface MySuperSuperIR extends IR { interface MySuperIR extends MySuperSuperIR { } - @GenerateIR(interfaces = "MySuperIR") + @GenerateIR(interfaces = {MySuperIR.class}) public final class MyIR extends MyIRGen { @GenerateFields public MyIR(@IRField boolean suspended) { @@ -468,7 +468,7 @@ public void irNodeAsNestedClass() { public interface JName extends IR { String name(); - @GenerateIR(interfaces = "JName") + @GenerateIR(interfaces = {JName.class}) public final class JBlank extends JBlankGen { @GenerateFields public JBlank(@IRField String name) { 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 f29bc3166979..913150aa4154 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,18 +2,22 @@ import java.io.IOException; import java.io.PrintWriter; +import java.util.List; import java.util.Set; import java.util.stream.Collectors; 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.AnnotationValue; 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.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.SimpleAnnotationValueVisitor14; import javax.tools.Diagnostic.Kind; import javax.tools.JavaFileObject; import org.enso.runtime.parser.dsl.GenerateFields; @@ -94,43 +98,49 @@ private void processGenerateIRElem(TypeElement processedClassElem) { } } - private TypeElement findInterface(TypeElement processedClassElem, String interfaceName) { - if (isBinaryName(interfaceName)) { - var iface = processingEnv.getElementUtils().getTypeElement(interfaceName); - return iface; - } else { - var enclosingElem = processedClassElem.getEnclosingElement(); - if (enclosingElem.getKind() == ElementKind.INTERFACE) { - if (enclosingElem.getSimpleName().toString().equals(interfaceName)) { - return (TypeElement) enclosingElem; - } - } else if (enclosingElem.getKind() == ElementKind.PACKAGE) { - return (TypeElement) - enclosingElem.getEnclosedElements().stream() - .filter(pkgElem -> pkgElem.getKind() == ElementKind.INTERFACE) - .filter(ifaceElem -> ifaceElem.getSimpleName().toString().equals(interfaceName)) - .findFirst() - .orElse(null); - } - } - return null; - } - private static String generatedClassName(TypeElement processedClassElem) { return processedClassElem.getSimpleName().toString() + "Gen"; } - private static boolean isBinaryName(String name) { - return name.contains("."); - } - private ProcessedClass constructProcessedClass(TypeElement processedClassElem) { - var generateIrAnnot = processedClassElem.getAnnotation(GenerateIR.class); - var ifaceToImplement = findInterface(processedClassElem, generateIrAnnot.interfaces()); - if (ifaceToImplement == null) { - throw new IRProcessingException( - "Could not find interface '" + generateIrAnnot.interfaces() + "'", processedClassElem); + // GenerateIR.interfaces cannot be accessed directly, we have to access the + // classes via type mirrors. + TypeElement ifaceToImplement = + processingEnv.getElementUtils().getTypeElement("org.enso.compiler.core.IR"); + var annotValueVisitor = + new SimpleAnnotationValueVisitor14, Void>() { + @Override + public List visitArray(List vals, Void unused) { + return vals.stream() + .map(val -> val.accept(this, null)) + .flatMap(List::stream) + .collect(Collectors.toList()); + } + + @Override + public List visitType(TypeMirror t, Void unused) { + var element = processingEnv.getTypeUtils().asElement(t); + Utils.hardAssert(element instanceof TypeElement); + return List.of((TypeElement) element); + } + }; + for (var annotMirror : processedClassElem.getAnnotationMirrors()) { + if (annotMirror.getAnnotationType().toString().equals(GenerateIR.class.getName())) { + for (var entry : annotMirror.getElementValues().entrySet()) { + if (entry.getKey().getSimpleName().toString().equals("interfaces")) { + var typeMirrors = entry.getValue().accept(annotValueVisitor, null); + if (typeMirrors.size() == 1) { + ifaceToImplement = typeMirrors.get(0); + } else { + throw new IRProcessingException( + "Only one interface is currently supported in `GenerateIR.interfaces`", + processedClassElem); + } + } + } + } } + Utils.hardAssert(ifaceToImplement != null); if (!Utils.isSubtypeOfIR(ifaceToImplement, processingEnv)) { throw new IRProcessingException( "Interface to implement must be a subtype of IR interface", ifaceToImplement); From 9da9394508cb519b6035d559d4230e773ef9bab1 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 30 Dec 2024 14:16:36 +0100 Subject: [PATCH 140/195] Can specify multiple GenerateIR.interfaces to implement --- .../enso/runtime/parser/dsl/GenerateIR.java | 6 +- .../processor/test/TestIRProcessorInline.java | 68 ++++++++++++------- .../GenerateIRAnnotationVisitor.java | 68 +++++++++++++++++++ .../processor/IRNodeClassGenerator.java | 6 +- .../runtime/parser/processor/IRProcessor.java | 55 ++++++--------- .../parser/processor/ProcessedClass.java | 34 ++++++++-- .../runtime/parser/processor/utils/Utils.java | 6 ++ 7 files changed, 169 insertions(+), 74 deletions(-) create mode 100644 engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/GenerateIRAnnotationVisitor.java diff --git a/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/GenerateIR.java b/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/GenerateIR.java index cbe01912174c..0252a654d452 100644 --- a/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/GenerateIR.java +++ b/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/GenerateIR.java @@ -21,9 +21,9 @@ public @interface GenerateIR { /** - * Interfaces that the generated superclass must implement. All the interfaces must be subtypes of - * the {@code org.enso.compiler.ir.IR} interface. All the abstract parameterless methods from all - * the interfaces will be implemented by the generated class. + * Interfaces that the generated superclass will implement. The list of the interfaces will simply + * be put inside the {@code implements} clause of the generated class. All the generated classes + * implements {@code org.enso.compiler.core.IR} by default. */ Class[] interfaces() default {}; } 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 1de521d61f87..86528efe0b62 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 @@ -125,6 +125,47 @@ public JName() {} CompilationSubject.assertThat(compilation).hadErrorContaining("must extend"); } + @Test + public void annotatedClass_InterfacesToImplement_CanHaveMore() { + var src = + """ + import org.enso.runtime.parser.dsl.GenerateIR; + import org.enso.runtime.parser.dsl.GenerateFields; + import org.enso.compiler.core.IR; + + interface MySuperIR { } + + @GenerateIR(interfaces = {MySuperIR.class, IR.class}) + public final class MyIR extends MyIRGen { + @GenerateFields + public MyIR() {} + } + """; + var generatedClass = generatedClass("MyIR", src); + assertThat(generatedClass, containsString("class MyIRGen implements IR, MySuperIR")); + } + + @Test + public void annotatedClass_InterfacesToImplement_DoNotHaveToExtendIR() { + var src = + """ + import org.enso.runtime.parser.dsl.GenerateIR; + import org.enso.runtime.parser.dsl.GenerateFields; + + interface MySuperIR_1 { } + interface MySuperIR_2 { } + + @GenerateIR(interfaces = {MySuperIR_1.class, MySuperIR_2.class}) + public final class MyIR extends MyIRGen { + @GenerateFields + public MyIR() {} + } +"""; + var generatedClass = generatedClass("MyIR", src); + assertThat( + generatedClass, containsString("class MyIRGen implements IR, MySuperIR_1, MySuperIR_2")); + } + @Test public void simpleIRNodeWithUserDefinedFiled_CompilationSucceeds() { var src = @@ -347,31 +388,6 @@ public MyIR(@IRField boolean suspended) { assertThat(genSrc, containsString("boolean suspended()")); } - @Test - public void interfacesMustBeSubtypeOfIR() { - var src = - JavaFileObjects.forSourceString( - "MyIR", - """ - import org.enso.runtime.parser.dsl.GenerateIR; - import org.enso.runtime.parser.dsl.GenerateFields; - - interface MySuperIR { - boolean suspended(); - } - - @GenerateIR(interfaces = {MySuperIR.class}) - public final class MyIR extends MyIRGen { - @GenerateFields - public MyIR() {} - } - """); - var compiler = Compiler.javac().withProcessors(new IRProcessor()); - var compilation = compiler.compile(src); - CompilationSubject.assertThat(compilation).failed(); - CompilationSubject.assertThat(compilation).hadErrorContaining("subtype"); - } - @Test public void irNodeWithInheritedField() { var src = @@ -477,7 +493,7 @@ public JBlank(@IRField String name) { } } """); - assertThat(src, containsString("class JBlankGen implements JName")); + assertThat(src, containsString("class JBlankGen implements IR, JName")); assertThat(src, containsString("String name()")); } diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/GenerateIRAnnotationVisitor.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/GenerateIRAnnotationVisitor.java new file mode 100644 index 000000000000..96b72901b905 --- /dev/null +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/GenerateIRAnnotationVisitor.java @@ -0,0 +1,68 @@ +package org.enso.runtime.parser.processor; + +import java.util.LinkedHashSet; +import java.util.List; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.SimpleAnnotationValueVisitor14; +import org.enso.runtime.parser.dsl.GenerateIR; +import org.enso.runtime.parser.processor.utils.Utils; + +final class GenerateIRAnnotationVisitor extends SimpleAnnotationValueVisitor14 { + private final ProcessingEnvironment procEnv; + private final ExecutableElement annotationField; + private final LinkedHashSet allInterfaces = new LinkedHashSet<>(); + private TypeElement irInterface; + + GenerateIRAnnotationVisitor(ProcessingEnvironment procEnv, ExecutableElement annotationField) { + this.procEnv = procEnv; + this.annotationField = annotationField; + allInterfaces.add(Utils.irTypeElement(procEnv)); + } + + @Override + public Void visitArray(List vals, Void unused) { + for (var val : vals) { + val.accept(this, null); + } + return null; + } + + @Override + public Void visitType(TypeMirror t, Void unused) { + var typeElem = (TypeElement) procEnv.getTypeUtils().asElement(t); + if (Utils.isSubtypeOfIR(typeElem, procEnv)) { + if (irInterface != null) { + throw new IRProcessingException( + "Only one interface can be specified as the IR interface, but found multiple: " + + irInterface + + " and " + + typeElem, + annotationField); + } + irInterface = typeElem; + } + allInterfaces.add(typeElem); + return null; + } + + /** + * Returns list of all the interfaces specified in {@link GenerateIR#interfaces()}. May be empty. + */ + public List getAllInterfaces() { + return allInterfaces.stream().toList(); + } + + /** + * Returns a type from {@link GenerateIR#interfaces()} that is a subtype of {@code + * org.enso.compiler.core.IR}. There must be only one such subtype specified. + * + * @return May be null. + */ + public TypeElement getIrInterface() { + return irInterface; + } +} 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 9db637024342..dcede7e6ac34 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 @@ -62,19 +62,19 @@ final class IRNodeClassGenerator { this.className = className; var userFields = getAllUserFields(processedClass); var duplicateMethod = - Utils.findDuplicateMethod(processedClass.getInterfaceElem(), processingEnv); + Utils.findDuplicateMethod(processedClass.getIrInterfaceElem(), processingEnv); this.generatedClassContext = new GeneratedClassContext(className, userFields, processingEnv, processedClass); this.duplicateMethodGenerator = new DuplicateMethodGenerator(duplicateMethod, generatedClassContext); this.builderMethodGenerator = new BuilderMethodGenerator(generatedClassContext); var mapExpressionsMethod = - Utils.findMapExpressionsMethod(processedClass.getInterfaceElem(), processingEnv); + Utils.findMapExpressionsMethod(processedClass.getIrInterfaceElem(), processingEnv); this.mapExpressionsMethodGenerator = new MapExpressionsMethodGenerator(mapExpressionsMethod, generatedClassContext); var setLocationMethod = Utils.findMethod( - processedClass.getInterfaceElem(), + processedClass.getIrInterfaceElem(), processingEnv, method -> method.getSimpleName().toString().equals("setLocation")); this.setLocationMethodGenerator = 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 913150aa4154..22c0049c61b7 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 @@ -9,15 +9,12 @@ import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.lang.model.SourceVersion; -import javax.lang.model.element.AnnotationValue; 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.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.SimpleAnnotationValueVisitor14; import javax.tools.Diagnostic.Kind; import javax.tools.JavaFileObject; import org.enso.runtime.parser.dsl.GenerateFields; @@ -105,48 +102,33 @@ private static String generatedClassName(TypeElement processedClassElem) { private ProcessedClass constructProcessedClass(TypeElement processedClassElem) { // GenerateIR.interfaces cannot be accessed directly, we have to access the // classes via type mirrors. - TypeElement ifaceToImplement = - processingEnv.getElementUtils().getTypeElement("org.enso.compiler.core.IR"); - var annotValueVisitor = - new SimpleAnnotationValueVisitor14, Void>() { - @Override - public List visitArray(List vals, Void unused) { - return vals.stream() - .map(val -> val.accept(this, null)) - .flatMap(List::stream) - .collect(Collectors.toList()); - } - - @Override - public List visitType(TypeMirror t, Void unused) { - var element = processingEnv.getTypeUtils().asElement(t); - Utils.hardAssert(element instanceof TypeElement); - return List.of((TypeElement) element); - } - }; + TypeElement irIfaceToImplement = Utils.irTypeElement(processingEnv); + List allInterfacesToImplement = List.of(); for (var annotMirror : processedClassElem.getAnnotationMirrors()) { if (annotMirror.getAnnotationType().toString().equals(GenerateIR.class.getName())) { - for (var entry : annotMirror.getElementValues().entrySet()) { + var annotMirrorElemValues = + processingEnv.getElementUtils().getElementValuesWithDefaults(annotMirror); + for (var entry : annotMirrorElemValues.entrySet()) { if (entry.getKey().getSimpleName().toString().equals("interfaces")) { - var typeMirrors = entry.getValue().accept(annotValueVisitor, null); - if (typeMirrors.size() == 1) { - ifaceToImplement = typeMirrors.get(0); - } else { - throw new IRProcessingException( - "Only one interface is currently supported in `GenerateIR.interfaces`", - processedClassElem); + var annotValueVisitor = new GenerateIRAnnotationVisitor(processingEnv, entry.getKey()); + entry.getValue().accept(annotValueVisitor, null); + if (annotValueVisitor.getIrInterface() != null) { + irIfaceToImplement = annotValueVisitor.getIrInterface(); } + allInterfacesToImplement = annotValueVisitor.getAllInterfaces(); } } } } - Utils.hardAssert(ifaceToImplement != null); - if (!Utils.isSubtypeOfIR(ifaceToImplement, processingEnv)) { + Utils.hardAssert(irIfaceToImplement != null); + if (!Utils.isSubtypeOfIR(irIfaceToImplement, processingEnv)) { throw new IRProcessingException( - "Interface to implement must be a subtype of IR interface", ifaceToImplement); + "Interface to implement must be a subtype of IR interface", irIfaceToImplement); } var annotatedCtor = getAnnotatedCtor(processedClassElem); - var processedClass = new ProcessedClass(processedClassElem, annotatedCtor, ifaceToImplement); + var processedClass = + new ProcessedClass( + processedClassElem, annotatedCtor, irIfaceToImplement, allInterfacesToImplement); return processedClass; } @@ -241,7 +223,10 @@ private static String generateSingleNodeClass( var imports = irNodeClassGen.imports().stream().collect(Collectors.joining(System.lineSeparator())); var pkg = pkgName.isEmpty() ? "" : "package " + pkgName + ";"; - var interfaces = processedClass.getInterfaceElem().getQualifiedName().toString(); + var interfaces = + processedClass.getInterfaces().stream() + .map(TypeElement::getSimpleName) + .collect(Collectors.joining(", ")); var code = """ $pkg diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ProcessedClass.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ProcessedClass.java index 407e3e3296d2..7ced69816fa0 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ProcessedClass.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ProcessedClass.java @@ -1,5 +1,6 @@ package org.enso.runtime.parser.processor; +import java.util.List; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import org.enso.runtime.parser.dsl.GenerateIR; @@ -11,18 +12,25 @@ public final class ProcessedClass { private final TypeElement clazz; private final ExecutableElement ctor; - private final TypeElement interfaceElem; + private final TypeElement irInterfaceElem; + private final List interfaces; /** * @param clazz Class being processed by the processor, annotated with {@link GenerateIR} * @param ctor Constructor annotated with {@link org.enso.runtime.parser.dsl.GenerateFields}. - * @param interfaceElem Interface that the generated superclass must implement. See {@link - * GenerateIR#interfaces()}. + * @param irInterfaceElem Interface that the generated superclass must implement. Must be subtype + * of {@code org.enso.compiler.core.IR}. + * @param interfaces All interfaces to implement. See {@link GenerateIR#interfaces()}. */ - ProcessedClass(TypeElement clazz, ExecutableElement ctor, TypeElement interfaceElem) { + ProcessedClass( + TypeElement clazz, + ExecutableElement ctor, + TypeElement irInterfaceElem, + List interfaces) { this.clazz = clazz; this.ctor = ctor; - this.interfaceElem = interfaceElem; + this.irInterfaceElem = irInterfaceElem; + this.interfaces = interfaces; } public TypeElement getClazz() { @@ -33,7 +41,19 @@ public ExecutableElement getCtor() { return ctor; } - public TypeElement getInterfaceElem() { - return interfaceElem; + /** + * Returns the interface that the generated superclass must implement. Is a subtype of {@code + * org.enso.compiler.core.IR}. + */ + public TypeElement getIrInterfaceElem() { + return irInterfaceElem; + } + + /** + * Returns all interfaces that the generated superclass must implement. See {@link + * GenerateIR#interfaces()}. + */ + public List getInterfaces() { + return interfaces; } } diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java index d2f3623fa03c..64d8143ba858 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java @@ -46,6 +46,12 @@ public static boolean isIRInterface(TypeMirror type, ProcessingEnvironment proce return elem.getKind() == ElementKind.INTERFACE && elem.getSimpleName().toString().equals("IR"); } + public static TypeElement irTypeElement(ProcessingEnvironment procEnv) { + var ret = procEnv.getElementUtils().getTypeElement("org.enso.compiler.core.IR"); + hardAssert(ret != null); + return ret; + } + /** Returns true if the given type extends {@link org.enso.compiler.core.ir.Expression} */ public static boolean isSubtypeOfExpression( TypeMirror type, ProcessingEnvironment processingEnv) { From bdd73c351ab1b778a92ce279e7be0596f8bda6f3 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 30 Dec 2024 14:40:44 +0100 Subject: [PATCH 141/195] Generated super class can have arbitrary name --- .../enso/runtime/parser/dsl/GenerateIR.java | 7 ++++-- .../processor/test/TestIRProcessorInline.java | 17 +++++++++++++ .../runtime/parser/processor/IRProcessor.java | 25 ++++++------------- 3 files changed, 30 insertions(+), 19 deletions(-) diff --git a/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/GenerateIR.java b/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/GenerateIR.java index 0252a654d452..82185bf570c7 100644 --- a/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/GenerateIR.java +++ b/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/GenerateIR.java @@ -7,8 +7,11 @@ /** * A class annotated with this annotation will be processed by the IR processor. The processor will - * generate a super class that is meant to be extended by this class. The generated class will have - * the same package as this class, and its name will have the "Gen" suffix. Majority of the methods + * generate a super class from the {@code extends} clause of the annotated class. If the annotated + * class does not have {@code extends} clause, an error is generated. Moreover, if the class in the + * {@code extends} clause already exists, an error is generated. + * + *

The generated class will have the same package as the annotated class. Majority of the methods * in the generated class will be either private or package-private, so that they are not accessible * from the outside. * 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 86528efe0b62..5cacb2892e3a 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 @@ -272,6 +272,23 @@ public JName() {} assertThat(genClass, containsString("abstract class JNameGen")); } + @Test + public void generatedClass_CanHaveArbitraryName() { + var src = + """ + import org.enso.runtime.parser.dsl.GenerateIR; + import org.enso.runtime.parser.dsl.GenerateFields; + + @GenerateIR + public final class JName extends MySuperGeneratedClass { + @GenerateFields + public JName() {} + } + """; + var genClass = generatedClass("JName", src); + assertThat(genClass, containsString("abstract class MySuperGeneratedClass")); + } + @Test public void generatedMethod_setLocation_returnsSubClassType() { 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 22c0049c61b7..2eeb6c449fad 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 @@ -95,8 +95,9 @@ private void processGenerateIRElem(TypeElement processedClassElem) { } } - private static String generatedClassName(TypeElement processedClassElem) { - return processedClassElem.getSimpleName().toString() + "Gen"; + private String generatedClassName(TypeElement processedClassElem) { + var superClass = processedClassElem.getSuperclass(); + return superClass.toString(); } private ProcessedClass constructProcessedClass(TypeElement processedClassElem) { @@ -174,25 +175,15 @@ private void ensureHasSingleAnnotatedConstructor(TypeElement clazz) { private void ensureExtendsGeneratedSuperclass(TypeElement clazz) { var superClass = clazz.getSuperclass(); - var genClassName = generatedClassName(clazz); if (superClass.getKind() == TypeKind.NONE) { throw new IRProcessingException( - "Class annotated with @GenerateIR must extend generated superclass'" - + genClassName - + "' " - + "But instead extends no class.", - clazz); + "Class annotated with @GenerateIR must have 'extends' clause", clazz); } - var superClassName = superClass.toString(); - var extendsGeneratedSuperclass = superClassName.contains(genClassName); - if (!extendsGeneratedSuperclass) { + if (superClass.getKind() != TypeKind.ERROR) { throw new IRProcessingException( - "Class annotated with @GenerateIR must extend generated superclass'" - + genClassName - + "'. " - + "The actual extended class is " - + superClassName, - clazz); + "Class annotated with @GenerateIR must extend generated superclass. " + + "The generated super class can have arbitrary name, but must not exist.", + processingEnv.getTypeUtils().asElement(superClass)); } } From ac0d78bc84af28bab072cba5fb58d8e61caebe40 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 30 Dec 2024 14:43:18 +0100 Subject: [PATCH 142/195] Fix typo in docs --- .../src/main/java/org/enso/runtime/parser/dsl/IRChild.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 6fe615536fb6..46195a57c1ab 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 @@ -7,7 +7,7 @@ /** * Constructor parameter annotated with this annotation will be represented as a child field in the - * generated super class. Children of IR elements for a tree. A child will be part of the methods + * generated super class. 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 parameter type must be * a subtype of {@code org.enso.compiler.ir.IR}. */ From b3ce70420d775f494a45f93d8588df784da5aaa1 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 30 Dec 2024 14:46:07 +0100 Subject: [PATCH 143/195] Rename DiagnosticStorage.empty to createEmpty --- .../org/enso/runtime/parser/processor/IRNodeClassGenerator.java | 2 +- .../scala/org/enso/compiler/core/ir/DiagnosticStorage.scala | 2 +- 2 files changed, 2 insertions(+), 2 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 dcede7e6ac34..626b2b4489f2 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 @@ -354,7 +354,7 @@ public DiagnosticStorage diagnostics() { @Override public DiagnosticStorage getDiagnostics() { if (diagnostics == null) { - diagnostics = DiagnosticStorage$.MODULE$.empty(); + diagnostics = DiagnosticStorage$.MODULE$.createEmpty(); } return diagnostics; } 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 091de215a964..b4d02cf58017 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 @@ -63,5 +63,5 @@ final class DiagnosticStorage(initDiagnostics: Seq[Diagnostic] = Seq()) } object DiagnosticStorage { - def empty(): DiagnosticStorage = new DiagnosticStorage() + def createEmpty(): DiagnosticStorage = new DiagnosticStorage() } From b82d7cf1024f8b58fd6dd69258f15c48490c139c Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 30 Dec 2024 14:56:16 +0100 Subject: [PATCH 144/195] Fix build --- .../src/main/java/org/enso/compiler/core/ir/Empty.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 5bbeaf51ff96..566f49dc32aa 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 @@ -3,7 +3,7 @@ import org.enso.runtime.parser.dsl.GenerateFields; import org.enso.runtime.parser.dsl.GenerateIR; -@GenerateIR(interfaces = "org.enso.compiler.core.ir.Expression") +@GenerateIR(interfaces = {Expression.class}) public final class Empty extends EmptyGen { @GenerateFields public Empty(IdentifiedLocation identifiedLocation, MetadataStorage passData) { From 94c40d091e8e43ab2130285a275c8996734ba343 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 30 Dec 2024 15:41:41 +0100 Subject: [PATCH 145/195] Duplicate method has specific return type --- .../processor/test/TestIRProcessorInline.java | 23 +++++++++++++++++++ .../methodgen/DuplicateMethodGenerator.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 5cacb2892e3a..1082bbb3d73d 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 @@ -289,6 +289,29 @@ public JName() {} assertThat(genClass, containsString("abstract class MySuperGeneratedClass")); } + /** + * Generated {@code duplicate} method returns the annotated class type, not any of its + * super types. + */ + @Test + public void generatedClass_DuplicateMethodHasSpecificReturnType() { + var src = + """ + import org.enso.runtime.parser.dsl.GenerateIR; + import org.enso.runtime.parser.dsl.GenerateFields; + + @GenerateIR + public final class JName extends JNameGen { + @GenerateFields + public JName() {} + } + """; + var genClass = generatedClass("JName", src); + assertThat(genClass, containsString("JName duplicate(")); + } + + /** + @Test public void generatedMethod_setLocation_returnsSubClassType() { var src = 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 9d7b18b887a6..4ab1b402025f 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 @@ -304,7 +304,7 @@ private List matchCtorParams(List duplicatedVars) { } private String dupMethodRetType() { - return duplicateMethod.getReturnType().toString(); + return ctx.getProcessedClass().getClazz().getSimpleName().toString(); } /** From aaad0c7118b6f81478f279df360d7b5de8f1988d Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 30 Dec 2024 15:47:07 +0100 Subject: [PATCH 146/195] Generate also parameterless duplicate method --- .../processor/test/TestIRProcessorInline.java | 26 ++++++++++++++----- .../processor/IRNodeClassGenerator.java | 4 +-- .../methodgen/DuplicateMethodGenerator.java | 20 ++++++++++++-- 3 files changed, 39 insertions(+), 11 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 1082bbb3d73d..3fef6cf94557 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 @@ -290,8 +290,8 @@ public JName() {} } /** - * Generated {@code duplicate} method returns the annotated class type, not any of its - * super types. + * Generated {@code duplicate} method returns the annotated class type, not any of its super + * types. */ @Test public void generatedClass_DuplicateMethodHasSpecificReturnType() { @@ -310,7 +310,23 @@ public JName() {} assertThat(genClass, containsString("JName duplicate(")); } - /** + /** Parameterless {@code duplicate} method just delegates to the other duplicate method. */ + @Test + public void generatedClass_HasParameterlessDuplicateMethod() { + var src = + """ + import org.enso.runtime.parser.dsl.GenerateIR; + import org.enso.runtime.parser.dsl.GenerateFields; + + @GenerateIR + public final class JName extends JNameGen { + @GenerateFields + public JName() {} + } + """; + var genClass = generatedClass("JName", src); + assertThat(genClass, containsString("JName duplicate()")); + } @Test public void generatedMethod_setLocation_returnsSubClassType() { @@ -584,8 +600,4 @@ public JName(@IRChild Option expression) { assertThat(src, containsString("class JNameGen")); assertThat("has getter method for expression", src, containsString("Option expression()")); } - - // TODO: Can contain multiple GenerateIR annotations in single source - - // TODO: Multiple interfaces in the annotation } 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 626b2b4489f2..de833b0ababa 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 @@ -359,7 +359,7 @@ public DiagnosticStorage getDiagnostics() { return diagnostics; } - $duplicateMethod + $duplicateMethods @Override public String showCode(int indent) { @@ -368,7 +368,7 @@ public String showCode(int indent) { """ .replace("$childrenMethodBody", childrenMethodBody()) .replace("$setLocationMethod", setLocationMethodGenerator.generateMethodCode()) - .replace("$duplicateMethod", duplicateMethodGenerator.generateDuplicateMethodCode()); + .replace("$duplicateMethods", duplicateMethodGenerator.generateDuplicateMethodsCode()); return indent(code, 2); } 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 4ab1b402025f..f3110f92fb10 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 @@ -48,7 +48,11 @@ private static void ensureDuplicateMethodHasExpectedSignature(ExecutableElement } } - public String generateDuplicateMethodCode() { + /** + * Generate code for two duplicate methods - one overridden with all four parameters, and another + * parameterless that just delegates to the first one. + */ + public String generateDuplicateMethodsCode() { var sb = new StringBuilder(); sb.append("@Override").append(System.lineSeparator()); sb.append("public ") @@ -156,7 +160,19 @@ public String generateDuplicateMethodCode() { sb.append("}"); sb.append(System.lineSeparator()); - return sb.toString(); + var defaultDuplicateMethod = sb.toString(); + return defaultDuplicateMethod + System.lineSeparator() + parameterlessDuplicateMethod(); + } + + private String parameterlessDuplicateMethod() { + var code = + """ + public $retType duplicate() { + return duplicate(true, true, true, false); + } + """ + .replace("$retType", dupMethodRetType()); + return code; } private static String dupFieldName(Field field) { From c062b90426519b06b9632dff5866af9a0a006e49 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 30 Dec 2024 16:04:03 +0100 Subject: [PATCH 147/195] Generated class can already exist - will be rewritten --- .../runtime/parser/processor/IRProcessor.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 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 2eeb6c449fad..70d94b9680f2 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 @@ -97,7 +97,17 @@ private void processGenerateIRElem(TypeElement processedClassElem) { private String generatedClassName(TypeElement processedClassElem) { var superClass = processedClassElem.getSuperclass(); - return superClass.toString(); + if (superClass.getKind() == TypeKind.ERROR) { + // The super class does not yet exist + return superClass.toString(); + } else if (superClass.getKind() == TypeKind.DECLARED) { + var superClassElem = (TypeElement) processingEnv.getTypeUtils().asElement(superClass); + return superClassElem.getSimpleName().toString(); + } else { + throw new IRProcessingException( + "Super class must be a declared type", + processingEnv.getTypeUtils().asElement(superClass)); + } } private ProcessedClass constructProcessedClass(TypeElement processedClassElem) { @@ -179,12 +189,6 @@ private void ensureExtendsGeneratedSuperclass(TypeElement clazz) { throw new IRProcessingException( "Class annotated with @GenerateIR must have 'extends' clause", clazz); } - if (superClass.getKind() != TypeKind.ERROR) { - throw new IRProcessingException( - "Class annotated with @GenerateIR must extend generated superclass. " - + "The generated super class can have arbitrary name, but must not exist.", - processingEnv.getTypeUtils().asElement(superClass)); - } } private static ExecutableElement getAnnotatedCtor(TypeElement clazz) { From 140d7b56561891283008c5e92aceddb45e2eaa9a Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 30 Dec 2024 16:18:47 +0100 Subject: [PATCH 148/195] Generate diagnosticsCopy. From LazyDiagnostics trait. --- .../runtime/parser/processor/IRNodeClassGenerator.java | 8 ++++++++ 1 file changed, 8 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 de833b0ababa..caf60e5726a7 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 @@ -358,6 +358,14 @@ public DiagnosticStorage getDiagnostics() { } return diagnostics; } + + public DiagnosticStorage diagnosticsCopy() { + if (diagnostics == null) { + return null; + } else { + return diagnostics.copy(); + } + } $duplicateMethods From a5420c1f0bbaafd47aaf5132c7a6d3764974179b Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 30 Dec 2024 16:19:09 +0100 Subject: [PATCH 149/195] Replace CallArgument Scala code with the generated class. --- .../enso/compiler/core/ir/CallArgument.java | 75 ++++++++ .../enso/compiler/core/ir/CallArgument.scala | 161 ------------------ 2 files changed, 75 insertions(+), 161 deletions(-) create mode 100644 engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/CallArgument.java delete mode 100644 engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/CallArgument.scala diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/CallArgument.java b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/CallArgument.java new file mode 100644 index 000000000000..2728f8f4d822 --- /dev/null +++ b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/CallArgument.java @@ -0,0 +1,75 @@ +package org.enso.compiler.core.ir; + +import java.util.function.Function; +import org.enso.compiler.core.IR; +import org.enso.runtime.parser.dsl.GenerateFields; +import org.enso.runtime.parser.dsl.GenerateIR; +import org.enso.runtime.parser.dsl.IRChild; +import org.enso.runtime.parser.dsl.IRField; +import scala.Option; + +/** Call-site arguments in Enso. */ +public interface CallArgument extends IR { + /** The name of the argument, if present. */ + Option name(); + + /** The expression of the argument, if present. */ + Expression value(); + + /** Flag indicating that the argument was generated by compiler. */ + boolean isSynthetic(); + + @Override + CallArgument mapExpressions(Function fn); + + @Override + CallArgument duplicate( + boolean keepLocations, + boolean keepMetadata, + boolean keepDiagnostics, + boolean keepIdentifiers); + + /** + * A representation of an argument at a function call site. + * + *

A {@link CallArgument} where the {@link CallArgument#value()} is an {@link Name.Blank} is a + * representation of a lambda shorthand argument. + */ + @GenerateIR(interfaces = {CallArgument.class}) + final class Specified extends SpecifiedGen { + + /** + * @param name the name of the argument being called, if present + * @param value the expression being passed as the argument's value + * @param isSynthetic the flag indicating that the argument was generated by compiler + */ + @GenerateFields + public Specified( + @IRChild Option name, + @IRChild Expression value, + @IRField boolean isSynthetic, + IdentifiedLocation identifiedLocation, + MetadataStorage passData) { + super( + DiagnosticStorage.createEmpty(), + passData, + identifiedLocation, + null, + name, + value, + isSynthetic); + } + + public Specified( + Option name, + Expression value, + boolean isSynthetic, + IdentifiedLocation identifiedLocation) { + this(name, value, isSynthetic, identifiedLocation, null); + } + + public Specified copy(Expression value) { + return new Builder(this).value(value).build(); + } + } +} diff --git a/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/CallArgument.scala b/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/CallArgument.scala deleted file mode 100644 index 87feed0d1717..000000000000 --- a/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/CallArgument.scala +++ /dev/null @@ -1,161 +0,0 @@ -package org.enso.compiler.core.ir - -import org.enso.compiler.core.{IR, Identifier} -import org.enso.compiler.core.Implicits.{ShowPassData, ToStringHelper} - -import java.util.UUID - -/** Call-site arguments in Enso. */ -sealed trait CallArgument extends IR { - - /** The name of the argument, if present. */ - def name: Option[Name] - - /** The expression of the argument, if present. */ - def value: Expression - - /** Flag indicating that the argument was generated by compiler. */ - def isSynthetic: Boolean - - /** @inheritdoc */ - override def mapExpressions( - fn: java.util.function.Function[Expression, Expression] - ): CallArgument - - /** @inheritdoc */ - override def duplicate( - keepLocations: Boolean = true, - keepMetadata: Boolean = true, - keepDiagnostics: Boolean = true, - keepIdentifiers: Boolean = false - ): CallArgument -} - -object CallArgument { - - /** A representation of an argument at a function call site. - * - * A [[CallArgument]] where the `value` is an [[Name.Blank]] is a - * representation of a lambda shorthand argument. - * - * @param name the name of the argument being called, if present - * @param value the expression being passed as the argument's value - * @param isSynthetic the flag indicating that the argument was generated by compiler - * @param identifiedLocation the source location that the node corresponds to - * @param passData the pass metadata associated with this node - */ - sealed case class Specified( - override val name: Option[Name], - override val value: Expression, - override val isSynthetic: Boolean, - identifiedLocation: IdentifiedLocation, - passData: MetadataStorage = new MetadataStorage() - ) extends CallArgument - with IRKind.Primitive - with LazyDiagnosticStorage - with LazyId { - - /** Creates a copy of `this`. - * - * @param name the name of the argument being called, if present - * @param value the expression being passed as the argument's value - * @param isSynthetic the flag indicating that the argument was generated by compiler - * @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`, updated with the specified values - */ - def copy( - name: Option[Name] = name, - value: Expression = value, - isSynthetic: Boolean = isSynthetic, - location: Option[IdentifiedLocation] = location, - passData: MetadataStorage = passData, - diagnostics: DiagnosticStorage = diagnostics, - id: UUID @Identifier = id - ): Specified = { - if ( - name != this.name - || value != this.value - || location != this.location - || (passData ne this.passData) - || diagnostics != this.diagnostics - || id != this.id - ) { - val res = - new Specified(name, value, isSynthetic, 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 - ): Specified = - copy( - name = name.map( - _.duplicate( - keepLocations, - keepMetadata, - keepDiagnostics, - keepIdentifiers - ) - ), - value = value.duplicate( - keepLocations, - keepMetadata, - keepDiagnostics, - keepIdentifiers - ), - 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] - ): Specified = copy(location = location) - - /** @inheritdoc */ - override def mapExpressions( - fn: java.util.function.Function[Expression, Expression] - ): Specified = { - copy(name = name.map(n => n.mapExpressions(fn)), value = fn(value)) - } - - /** String representation. */ - override def toString: String = - s""" - |CallArgument.Specified( - |name = $name, - |value = $value, - |location = $location, - |passData = ${this.showPassData}, - |diagnostics = $diagnostics, - |id = $id - |) - |""".toSingleLine - - /** @inheritdoc */ - override def children: List[IR] = - name.map(List(_, value)).getOrElse(List(value)) - - /** @inheritdoc */ - override def showCode(indent: Int): String = { - if (name.isDefined) { - s"(${name.get.showCode(indent)} = ${value.showCode(indent)})" - } else { - s"${value.showCode(indent)}" - } - } - } -} From 0708535a1fa72de7f69e657a8cccb7cc60d3eec0 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 30 Dec 2024 16:38:59 +0100 Subject: [PATCH 150/195] Fix duplicate method - passData and diagnostics are copied. --- .../parser/processor/methodgen/DuplicateMethodGenerator.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 f3110f92fb10..41bd5dc56fee 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 @@ -67,13 +67,13 @@ public String generateDuplicateMethodsCode() { """ $diagType diagnosticsDuplicated; if (keepDiagnostics) { - diagnosticsDuplicated = this.diagnostics; + diagnosticsDuplicated = this.diagnostics.copy(); } else { diagnosticsDuplicated = null; } $metaType passDataDuplicated; if (keepMetadata) { - passDataDuplicated = this.passData; + passDataDuplicated = this.passData.duplicate(); } else { passDataDuplicated = null; } From e2c3c3bce8070be99ce5fe4f85dd6b4c068fdc00 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 30 Dec 2024 18:45:13 +0100 Subject: [PATCH 151/195] Generate toString method --- .../processor/test/GeneratedIRTest.scala | 12 +++++ .../processor/IRNodeClassGenerator.java | 10 ++++- .../methodgen/ToStringMethodGenerator.java | 45 +++++++++++++++++++ 3 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/ToStringMethodGenerator.java diff --git a/engine/runtime-parser-processor-tests/src/test/scala/org/enso/runtime/parser/processor/test/GeneratedIRTest.scala b/engine/runtime-parser-processor-tests/src/test/scala/org/enso/runtime/parser/processor/test/GeneratedIRTest.scala index d2d23f73c12d..3d0bb884df71 100644 --- a/engine/runtime-parser-processor-tests/src/test/scala/org/enso/runtime/parser/processor/test/GeneratedIRTest.scala +++ b/engine/runtime-parser-processor-tests/src/test/scala/org/enso/runtime/parser/processor/test/GeneratedIRTest.scala @@ -22,4 +22,16 @@ class GeneratedIRTest extends AnyFlatSpec with Matchers { val callArg = new JSpecified(isSynthetic = true, value = lit, name = None) callArg should not be null } + + "JSpecifiedGen" should "have overridden toString method" in { + val lit = Literal.Text("foo", null, new MetadataStorage()) + val callArg = new JSpecified(true, None, lit) + val str = callArg.toString + withClue(s"String representation: " + str) { + str.contains("JCallArgument.JSpecified") shouldBe true + str.contains("name = None") shouldBe true + str.contains("value = Literal.Text") shouldBe true + str.contains("passData = null") shouldBe true + } + } } 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 caf60e5726a7..28684ca9f725 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 @@ -15,6 +15,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.methodgen.ToStringMethodGenerator; import org.enso.runtime.parser.processor.utils.Utils; /** @@ -35,6 +36,7 @@ final class IRNodeClassGenerator { private final MapExpressionsMethodGenerator mapExpressionsMethodGenerator; private final EqualsMethodGenerator equalsMethodGenerator; private final HashCodeMethodGenerator hashCodeMethodGenerator; + private final ToStringMethodGenerator toStringMethodGenerator; private static final Set defaultImportedTypes = Set.of( @@ -81,6 +83,7 @@ final class IRNodeClassGenerator { new SetLocationMethodGenerator(setLocationMethod, generatedClassContext); this.equalsMethodGenerator = new EqualsMethodGenerator(generatedClassContext); this.hashCodeMethodGenerator = new HashCodeMethodGenerator(generatedClassContext); + this.toStringMethodGenerator = new ToStringMethodGenerator(generatedClassContext); } /** Returns simple name of the generated class. */ @@ -126,6 +129,8 @@ public static Builder builder() { $hashCodeMethod + $toStringMethod + $builder """ .replace("$fields", fieldsCode()) @@ -136,6 +141,7 @@ public static Builder builder() { .replace("$mapExpressionsMethod", mapExpressions()) .replace("$equalsMethod", equalsMethodGenerator.generateMethodCode()) .replace("$hashCodeMethod", hashCodeMethodGenerator.generateMethodCode()) + .replace("$toStringMethod", toStringMethodGenerator.generateMethodCode()) .replace("$builder", builderMethodGenerator.generateBuilder()); return Utils.indent(code, 2); } @@ -302,7 +308,7 @@ private String childrenMethodBody() { } /** - * Returns a String representing all the overriden methods from {@link org.enso.compiler.core.IR}. + * Returns a String representing all the overriden methods from {@code org.enso.compiler.core.IR}. * Meant to be inside the generated record definition. */ private String overrideIRMethods() { @@ -358,7 +364,7 @@ public DiagnosticStorage getDiagnostics() { } return diagnostics; } - + public DiagnosticStorage diagnosticsCopy() { if (diagnostics == null) { return null; diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/ToStringMethodGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/ToStringMethodGenerator.java new file mode 100644 index 000000000000..f5e0b116f33f --- /dev/null +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/ToStringMethodGenerator.java @@ -0,0 +1,45 @@ +package org.enso.runtime.parser.processor.methodgen; + +import java.util.stream.Collectors; +import javax.lang.model.element.ElementKind; +import org.enso.runtime.parser.processor.GeneratedClassContext; + +public class ToStringMethodGenerator { + private final GeneratedClassContext ctx; + + public ToStringMethodGenerator(GeneratedClassContext ctx) { + this.ctx = ctx; + } + + public String generateMethodCode() { + var sb = new StringBuilder(); + sb.append("@Override").append(System.lineSeparator()); + sb.append("public String toString() {").append(System.lineSeparator()); + sb.append(" return ").append(System.lineSeparator()); + sb.append(" ").append(quoted(className())).append(System.lineSeparator()); + sb.append(" + ").append(quoted("(")).append(System.lineSeparator()); + var fieldsStrRepr = + ctx.getAllFields().stream() + .map(field -> " \"$fieldName = \" + $fieldName".replace("$fieldName", field.name())) + .collect(Collectors.joining(" + \", \" + " + System.lineSeparator())); + sb.append(" + ").append(fieldsStrRepr).append(System.lineSeparator()); + sb.append(" + ").append(quoted(")")).append(";").append(System.lineSeparator()); + sb.append("}").append(System.lineSeparator()); + return sb.toString(); + } + + private String className() { + var clazz = ctx.getProcessedClass().getClazz(); + var enclosingElem = clazz.getEnclosingElement(); + if (enclosingElem.getKind() == ElementKind.INTERFACE + || enclosingElem.getKind() == ElementKind.CLASS) { + return enclosingElem.getSimpleName().toString() + "." + clazz.getSimpleName().toString(); + } else { + return clazz.getSimpleName().toString(); + } + } + + private static String quoted(String str) { + return '"' + str + '"'; + } +} From 3086f52b106558da0af7bd47f2ee3eeb77c99737 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 30 Dec 2024 18:45:23 +0100 Subject: [PATCH 152/195] Fix expected error message --- .../runtime/parser/processor/test/TestIRProcessorInline.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/TestIRProcessorInline.java b/engine/runtime-parser-processor-tests/src/test/java/org/enso/runtime/parser/processor/test/TestIRProcessorInline.java index 3fef6cf94557..573d46e4835b 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 @@ -122,7 +122,7 @@ public JName() {} """; var compilation = compile("JName", src); CompilationSubject.assertThat(compilation).failed(); - CompilationSubject.assertThat(compilation).hadErrorContaining("must extend"); + CompilationSubject.assertThat(compilation).hadErrorContaining("must have 'extends' clause"); } @Test From 24b28ad91056320dc9abeb44dda269af7bec01de Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 30 Dec 2024 18:45:34 +0100 Subject: [PATCH 153/195] FIx ensureExtendsGeneratedSuperclass --- .../java/org/enso/runtime/parser/processor/IRProcessor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 70d94b9680f2..4904e5495a12 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 @@ -185,7 +185,7 @@ private void ensureHasSingleAnnotatedConstructor(TypeElement clazz) { private void ensureExtendsGeneratedSuperclass(TypeElement clazz) { var superClass = clazz.getSuperclass(); - if (superClass.getKind() == TypeKind.NONE) { + if (superClass.getKind() == TypeKind.NONE || superClass.toString().equals("java.lang.Object")) { throw new IRProcessingException( "Class annotated with @GenerateIR must have 'extends' clause", clazz); } From 74799251ccd22d55ecfaf4f441dd18f5b29cc4f4 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 30 Dec 2024 19:29:47 +0100 Subject: [PATCH 154/195] Fix NPE in duplicate --- .../methodgen/DuplicateMethodGenerator.java | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) 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 41bd5dc56fee..10250cb2f610 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 @@ -65,29 +65,21 @@ public String generateDuplicateMethodsCode() { var duplicateMetaFieldsCode = """ - $diagType diagnosticsDuplicated; - if (keepDiagnostics) { + $diagType diagnosticsDuplicated = null; + if (keepDiagnostics && this.diagnostics != null) { diagnosticsDuplicated = this.diagnostics.copy(); - } else { - diagnosticsDuplicated = null; } - $metaType passDataDuplicated; - if (keepMetadata) { + $metaType passDataDuplicated = null; + if (keepMetadata && this.passData != null) { passDataDuplicated = this.passData.duplicate(); - } else { - passDataDuplicated = null; } - $locType locationDuplicated; - if (keepLocations) { + $locType locationDuplicated = null; + if (keepLocations && this.location != null) { locationDuplicated = this.location; - } else { - locationDuplicated = null; } - $idType idDuplicated; - if (keepIdentifiers) { + $idType idDuplicated = null; + if (keepIdentifiers && this.id != null) { idDuplicated = this.id; - } else { - idDuplicated = null; } """ .replace("$locType", ctx.getLocationMetaField().type()) From f8c9e12d2757b3c535001868d94c5fc39b53303d Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 30 Dec 2024 20:17:11 +0100 Subject: [PATCH 155/195] mapExpressions duplicates this iff one of the mapped children changed --- .../MapExpressionsMethodGenerator.java | 236 +++++++++++------- 1 file changed, 139 insertions(+), 97 deletions(-) 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 27f5f3fc6cd1..a8e4411c0d83 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 @@ -1,10 +1,12 @@ package org.enso.runtime.parser.processor.methodgen; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; -import javax.lang.model.element.Element; +import java.util.stream.Collectors; 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.GeneratedClassContext.ClassField; import org.enso.runtime.parser.processor.IRProcessingException; import org.enso.runtime.parser.processor.field.Field; import org.enso.runtime.parser.processor.utils.Utils; @@ -50,109 +52,149 @@ public String generateMapExpressionsMethodCode() { var children = ctx.getUserFields().stream().filter(Field::isChild); var newChildren = - children.map( - child -> { - ExecutableElement childsMapExprMethod; - if (child.isList() || child.isOption()) { - childsMapExprMethod = - Utils.findMapExpressionsMethod( - child.getTypeParameter(), ctx.getProcessingEnvironment()); - } else { - childsMapExprMethod = - Utils.findMapExpressionsMethod(child.getType(), ctx.getProcessingEnvironment()); - } + children + .map( + child -> { + ExecutableElement childsMapExprMethod; + if (child.isList() || child.isOption()) { + childsMapExprMethod = + Utils.findMapExpressionsMethod( + child.getTypeParameter(), ctx.getProcessingEnvironment()); + } else { + 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()); - if (child.isList() || child.isOption()) { - shouldCast = false; - } + var typeUtils = ctx.getProcessingEnvironment().getTypeUtils(); + var childsMapExprMethodRetType = + typeUtils.asElement(childsMapExprMethod.getReturnType()); + var shouldCast = + !typeUtils.isSameType( + child.getType().asType(), childsMapExprMethodRetType.asType()); + if (child.isList() || child.isOption()) { + shouldCast = false; + } - String newChildType = typeName(childsMapExprMethodRetType); - if (child.isList()) { - newChildType = "List<" + newChildType + ">"; - } else if (child.isOption()) { - newChildType = "Option<" + newChildType + ">"; - } + String newChildType = childsMapExprMethodRetType.getSimpleName().toString(); + if (child.isList()) { + newChildType = "List<" + newChildType + ">"; + } else if (child.isOption()) { + newChildType = "Option<" + newChildType + ">"; + } - var newChildName = child.getName() + "Mapped"; - sb.append(" ").append(newChildType).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() && !child.isOption()) { - // 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.isOption()); - // 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()); + var newChildName = child.getName() + "Mapped"; + sb.append(" ").append(newChildType).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() && !child.isOption()) { + // 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.isOption()); + // 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); + }) + .toList(); + if (newChildren.isEmpty()) { + sb.append(" return ") + .append("(") + .append(ctx.getProcessedClass().getClazz().getSimpleName().toString()) + .append(") this;") + .append(System.lineSeparator()); + sb.append("}").append(System.lineSeparator()); + return sb.toString(); + } + sb.append(" // Only copy if some of the children actually changed") + .append(System.lineSeparator()); + var changedCond = + newChildren.stream() + .map(newChild -> newChild.newChildName + " != " + newChild.child.getName()) + .collect(Collectors.joining(" || ")); + sb.append(" ").append("if (").append(changedCond).append(") {").append(System.lineSeparator()); + sb.append(" ").append("var bldr = new Builder();").append(System.lineSeparator()); + for (MappedChild newChild : newChildren) { + 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()); + } + for (var field : restOfTheFields(newChildren)) { + sb.append(" ") + .append("bldr.") + .append(field.name()) + .append("(") + .append(field.name()) + .append(");") + .append(System.lineSeparator()); + } + sb.append(" return bldr.build();").append(System.lineSeparator()); + sb.append(" } else { ").append(System.lineSeparator()); + sb.append(" // None of the mapped children changed - just return this") + .append(System.lineSeparator()); + sb.append(" return ") + .append("(") + .append(ctx.getProcessedClass().getClazz().getSimpleName().toString()) + .append(") this;") + .append(System.lineSeparator()); + sb.append(" }").append(System.lineSeparator()); sb.append("}").append(System.lineSeparator()); return sb.toString(); } - private String typeName(Element element) { - if (element instanceof TypeElement typeElement) { - return typeElement.getQualifiedName().toString(); + private List restOfTheFields(List newChildren) { + var restOfFields = new ArrayList(); + for (var field : ctx.getAllFields()) { + if (newChildren.stream() + .noneMatch(newChild -> newChild.child.getName().equals(field.name()))) { + restOfFields.add(field); + } } - return element.getSimpleName().toString(); + return restOfFields; } private record MappedChild(String newChildName, Field child, boolean shouldCast) {} From 198ce88b7efddec6991288999dd8660f72320a04 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 30 Dec 2024 20:40:03 +0100 Subject: [PATCH 156/195] mapExpressions do not recurse on Expression children --- .../MapExpressionsMethodGenerator.java | 64 +++++++++++++------ .../runtime/parser/processor/utils/Utils.java | 22 +++++-- 2 files changed, 61 insertions(+), 25 deletions(-) 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 a8e4411c0d83..ed96d8d6d9f0 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 @@ -51,6 +51,9 @@ public String generateMapExpressionsMethodCode() { .append(System.lineSeparator()); var children = ctx.getUserFields().stream().filter(Field::isChild); + // A list of new children that are created by calling mapExpressions on the existing children + // Or the function directly if the child is of Expression type (this prevents + // recursion). var newChildren = children .map( @@ -82,6 +85,9 @@ public String generateMapExpressionsMethodCode() { } else if (child.isOption()) { newChildType = "Option<" + newChildType + ">"; } + var childIsExpression = + Utils.isExpression( + childsMapExprMethodRetType, ctx.getProcessingEnvironment()); var newChildName = child.getName() + "Mapped"; sb.append(" ").append(newChildType).append(" ").append(newChildName); @@ -91,32 +97,54 @@ public String generateMapExpressionsMethodCode() { .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() && !child.isOption()) { - // ChildType childMapped = child.mapExpressions(fn); - sb.append(" = ") + if (childIsExpression) { + // childMapped = fn.apply(child); + sb.append(" ") + .append(newChildName) + .append(" = fn.apply(") .append(child.getName()) + .append(");") + .append(System.lineSeparator()); + } else { + // 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() && !child.isOption()) { + if (childIsExpression) { + // ChildType childMapped = fn.apply(child); + sb.append(" = ") + .append("fn.apply(") + .append(child.getName()) + .append(");") + .append(System.lineSeparator()); + } else { + // 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.isOption()); // 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()); + sb.append(" = ").append(child.getName()).append(".map(e -> "); + if (childIsExpression) { + // List childMapped = child.map(e -> fn.apply(e)); + sb.append("fn.apply(e)"); + } else { + // List childMapped = child.map(e -> e.mapExpressions(fn)); + sb.append("e.").append(METHOD_NAME).append("(fn)"); + } + sb.append(");").append(System.lineSeparator()); } } return new MappedChild(newChildName, child, shouldCast); diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java index 64d8143ba858..cbdc8f7e4167 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java @@ -52,15 +52,23 @@ public static TypeElement irTypeElement(ProcessingEnvironment procEnv) { return ret; } - /** Returns true if the given type extends {@link org.enso.compiler.core.ir.Expression} */ + public static boolean isExpression(Element elem, ProcessingEnvironment processingEnvironment) { + if (elem instanceof TypeElement typeElem) { + var exprType = expressionType(processingEnvironment); + return processingEnvironment.getTypeUtils().isSameType(typeElem.asType(), exprType.asType()); + } + return false; + } + + /** Returns true if the given type extends {@code org.enso.compiler.core.ir.Expression} */ public 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); + var exprType = expressionType(processingEnv).asType(); + return processingEnv.getTypeUtils().isAssignable(type, exprType); + } + + public static TypeElement expressionType(ProcessingEnvironment procEnv) { + return procEnv.getElementUtils().getTypeElement("org.enso.compiler.core.ir.Expression"); } public static String indent(String code, int indentation) { From 70b1be3707629df938e0f97563f04c4e6bf3c018 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 30 Dec 2024 20:45:42 +0100 Subject: [PATCH 157/195] Specified.copy only copies if parameter is different --- .../java/org/enso/compiler/core/ir/CallArgument.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/CallArgument.java b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/CallArgument.java index 2728f8f4d822..9999bf772eb3 100644 --- a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/CallArgument.java +++ b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/CallArgument.java @@ -69,7 +69,15 @@ public Specified( } public Specified copy(Expression value) { - return new Builder(this).value(value).build(); + if (value != this.value()) { + var duplicated = + new Specified(name(), value, isSynthetic(), identifiedLocation(), passData()); + duplicated.id = id; + duplicated.diagnostics = diagnostics; + return duplicated; + } else { + return this; + } } } } From a25ad6fd80dd8c1984cae01ac5edff065fe1b619 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 30 Dec 2024 20:46:04 +0100 Subject: [PATCH 158/195] Pass null to meta field in super ctor --- .../src/main/java/org/enso/compiler/core/ir/CallArgument.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/CallArgument.java b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/CallArgument.java index 9999bf772eb3..9b29f6fb3b14 100644 --- a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/CallArgument.java +++ b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/CallArgument.java @@ -51,7 +51,7 @@ public Specified( IdentifiedLocation identifiedLocation, MetadataStorage passData) { super( - DiagnosticStorage.createEmpty(), + null, passData, identifiedLocation, null, From 80509a1f2420e97b3879fc8e925a9a863373826d Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 30 Dec 2024 21:04:13 +0100 Subject: [PATCH 159/195] Fix whitespaces in formatting of generated classes --- .../processor/IRNodeClassGenerator.java | 16 ++++---- .../methodgen/BuilderMethodGenerator.java | 38 +++++++++---------- .../methodgen/EqualsMethodGenerator.java | 3 +- .../methodgen/HashCodeMethodGenerator.java | 3 +- .../methodgen/SetLocationMethodGenerator.java | 3 +- .../enso/compiler/core/ir/CallArgument.java | 9 +---- 6 files changed, 30 insertions(+), 42 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 28684ca9f725..ce227d1ed15b 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 @@ -172,7 +172,7 @@ private String fieldsCode() { protected UUID id; """ .replace("$userDefinedFields", userDefinedFields); - return indent(code, 2); + return code; } /** @@ -189,7 +189,7 @@ private String defaultConstructor() { """; var ctorCode = constructorForFields(generatedClassContext.getSuperclassConstructorParameters(), List.of()); - return Utils.indent(docs + ctorCode, 2); + return docs + ctorCode; } /** @@ -211,7 +211,7 @@ private String constructorForUserFields() { .toList(); var ctorCode = constructorForFields(userFieldsAsParameters, generatedClassContext.getMetaFields()); - return Utils.indent(docs + ctorCode, 2); + return docs + ctorCode; } /** @@ -244,7 +244,7 @@ private String constructorForFields( parameters.stream() .map(field -> " this.$fieldName = $fieldName;".replace("$fieldName", field.name())) .collect(Collectors.joining(System.lineSeparator())); - sb.append(indent(ctorBody, 2)); + sb.append(ctorBody); } sb.append(System.lineSeparator()); @@ -254,7 +254,7 @@ private String constructorForFields( initializeToNull.stream() .map(field -> " this.$fieldName = null;".replace("$fieldName", field.name())) .collect(Collectors.joining(System.lineSeparator())); - sb.append(indent(initToNullBody, 2)); + sb.append(initToNullBody); } sb.append(System.lineSeparator()); @@ -383,7 +383,7 @@ public String showCode(int indent) { .replace("$childrenMethodBody", childrenMethodBody()) .replace("$setLocationMethod", setLocationMethodGenerator.generateMethodCode()) .replace("$duplicateMethods", duplicateMethodGenerator.generateDuplicateMethodsCode()); - return indent(code, 2); + return code; } /** Returns string representation of all getters for the user-defined fields. */ @@ -400,11 +400,11 @@ private String userDefinedGetters() { .replace("$returnType", field.getSimpleTypeName()) .replace("$fieldName", field.getName())) .collect(Collectors.joining(System.lineSeparator())); - return indent(code, 2); + return code; } private String mapExpressions() { - return Utils.indent(mapExpressionsMethodGenerator.generateMapExpressionsMethodCode(), 2); + return mapExpressionsMethodGenerator.generateMapExpressionsMethodCode(); } private static String indent(String code, int indentation) { 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 62819515f4fc..4fa9200732b4 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 @@ -24,9 +24,7 @@ public String generateBuilder() { generatedClassContext.getAllFields().stream() .map( metaField -> - """ - private $type $name; - """ + "private $type $name;" .replace("$type", metaField.type()) .replace("$name", metaField.name())) .collect(Collectors.joining(System.lineSeparator())); @@ -62,27 +60,27 @@ public String generateBuilder() { var code = """ public static final class Builder { - $fieldDeclarations + $fieldDeclarations Builder() {} - $copyConstructor + $copyConstructor - $fieldSetters + $fieldSetters - $buildMethod + $buildMethod private void validate() { - $validationCode + $validationCode } } """ - .replace("$fieldDeclarations", fieldDeclarations) - .replace("$copyConstructor", copyConstructor()) - .replace("$fieldSetters", fieldSetters) - .replace("$buildMethod", buildMethod()) - .replace("$validationCode", Utils.indent(validationCode, 2)); - return Utils.indent(code, 2); + .replace("$fieldDeclarations", Utils.indent(fieldDeclarations, 2)) + .replace("$copyConstructor", Utils.indent(copyConstructor(), 2)) + .replace("$fieldSetters", Utils.indent(fieldSetters, 2)) + .replace("$buildMethod", Utils.indent(buildMethod(), 2)) + .replace("$validationCode", Utils.indent(validationCode, 4)); + return code; } private String copyConstructor() { @@ -111,12 +109,12 @@ private String buildMethod() { var ctorParams = generatedClassContext.getSubclassConstructorParameters(); var ctorParamsStr = ctorParams.stream().map(ClassField::name).collect(Collectors.joining(", ")); var fieldsNotInCtor = Utils.minus(generatedClassContext.getAllFields(), ctorParams); - sb.append(" public ") + sb.append("public ") .append(processedClassName) .append(" build() {") .append(System.lineSeparator()); - sb.append(" ").append("validate();").append(System.lineSeparator()); - sb.append(" ") + sb.append(" ").append("validate();").append(System.lineSeparator()); + sb.append(" ") .append(processedClassName) .append(" result = new ") .append(processedClassName) @@ -125,7 +123,7 @@ private String buildMethod() { .append(");") .append(System.lineSeparator()); for (var fieldNotInCtor : fieldsNotInCtor) { - sb.append(" ") + sb.append(" ") .append("result.") .append(fieldNotInCtor.name()) .append(" = ") @@ -133,8 +131,8 @@ private String buildMethod() { .append(";") .append(System.lineSeparator()); } - sb.append(" ").append("return result;").append(System.lineSeparator()); - sb.append(" }").append(System.lineSeparator()); + sb.append(" ").append("return result;").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/methodgen/EqualsMethodGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/EqualsMethodGenerator.java index c8319ec0216c..84960e2923de 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,6 @@ package org.enso.runtime.parser.processor.methodgen; import org.enso.runtime.parser.processor.GeneratedClassContext; -import org.enso.runtime.parser.processor.utils.Utils; public final class EqualsMethodGenerator { private final GeneratedClassContext ctx; @@ -33,6 +32,6 @@ public String generateMethodCode() { sb.append(" }").append(System.lineSeparator()); sb.append(" return false;").append(System.lineSeparator()); sb.append("}").append(System.lineSeparator()); - return Utils.indent(sb.toString(), 2); + return sb.toString(); } } 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 a9463d2ed8ce..82b8101ed542 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,6 @@ 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.Utils; public final class HashCodeMethodGenerator { private final GeneratedClassContext ctx; @@ -23,6 +22,6 @@ public int hashCode() { } """ .replace("$fieldList", fieldList); - return Utils.indent(code, 2); + return code; } } 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 61d7d74aa18e..39900dd180fd 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 @@ -3,7 +3,6 @@ import javax.lang.model.element.ExecutableElement; import org.enso.runtime.parser.processor.GeneratedClassContext; import org.enso.runtime.parser.processor.IRProcessingException; -import org.enso.runtime.parser.processor.utils.Utils; public class SetLocationMethodGenerator { private final ExecutableElement setLocationMethod; @@ -43,7 +42,7 @@ public String generateMethodCode() { } """ .replace("$retType", retType()); - return Utils.indent(code, 2); + return code; } private String retType() { diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/CallArgument.java b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/CallArgument.java index 9b29f6fb3b14..55c1f08ffb66 100644 --- a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/CallArgument.java +++ b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/CallArgument.java @@ -50,14 +50,7 @@ public Specified( @IRField boolean isSynthetic, IdentifiedLocation identifiedLocation, MetadataStorage passData) { - super( - null, - passData, - identifiedLocation, - null, - name, - value, - isSynthetic); + super(null, passData, identifiedLocation, null, name, value, isSynthetic); } public Specified( From df0a0010621b30c13a12ae70aaea42801c0a79bf Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 30 Dec 2024 21:05:26 +0100 Subject: [PATCH 160/195] No "copy constructor" for Builder --- .../methodgen/BuilderMethodGenerator.java | 22 ------------------- 1 file changed, 22 deletions(-) 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 4fa9200732b4..32bae349ec61 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 @@ -64,8 +64,6 @@ public static final class Builder { Builder() {} - $copyConstructor - $fieldSetters $buildMethod @@ -76,32 +74,12 @@ private void validate() { } """ .replace("$fieldDeclarations", Utils.indent(fieldDeclarations, 2)) - .replace("$copyConstructor", Utils.indent(copyConstructor(), 2)) .replace("$fieldSetters", Utils.indent(fieldSetters, 2)) .replace("$buildMethod", Utils.indent(buildMethod(), 2)) .replace("$validationCode", Utils.indent(validationCode, 4)); return code; } - 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(); - } - private String buildMethod() { var sb = new StringBuilder(); var processedClassName = From bd4b0b2f99d2490508e7f2fae9e2e07452b0ea77 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 30 Dec 2024 21:07:42 +0100 Subject: [PATCH 161/195] imports are sorted --- .../java/org/enso/runtime/parser/processor/IRProcessor.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 4904e5495a12..1138b2874190 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 @@ -216,7 +216,9 @@ private String packageName(Element elem) { private static String generateSingleNodeClass( IRNodeClassGenerator irNodeClassGen, ProcessedClass processedClass, String pkgName) { var imports = - irNodeClassGen.imports().stream().collect(Collectors.joining(System.lineSeparator())); + irNodeClassGen.imports().stream() + .sorted() + .collect(Collectors.joining(System.lineSeparator())); var pkg = pkgName.isEmpty() ? "" : "package " + pkgName + ";"; var interfaces = processedClass.getInterfaces().stream() From 412d46626bcead71d5e66ffd749862f57c8889bc Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Wed, 1 Jan 2025 13:27:37 +0100 Subject: [PATCH 162/195] Replace Supplier with private method --- .../processor/GeneratedClassContext.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) 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 690359ec9ff0..435cdbe7173f 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 @@ -3,7 +3,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; -import java.util.function.Supplier; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.VariableElement; import javax.lang.model.type.DeclaredType; @@ -135,20 +134,12 @@ public List getSubclassConstructorParameters() { for (var param : ctor.getParameters()) { var paramSimpleType = simpleTypeName(param); var paramName = param.getSimpleName().toString(); - Supplier printErrAndFail = - () -> { - var errMsg = - String.format( - "No matching field found for parameter %s of type %s. All fields: %s", - paramName, paramSimpleType, allFields); - throw new IRProcessingException(errMsg, param); - }; var fieldsWithSameType = allFields.stream() .filter(field -> paramSimpleType.equals(field.simpleTypeName())) .toList(); if (fieldsWithSameType.isEmpty()) { - printErrAndFail.get(); + throw noMatchingFieldError(param); } else if (fieldsWithSameType.size() == 1) { ctorParams.add(fieldsWithSameType.get(0)); } else { @@ -159,7 +150,7 @@ public List getSubclassConstructorParameters() { fieldsWithSameName.size() < 2, "Cannot have more than one field with the same name and type"); if (fieldsWithSameName.isEmpty()) { - printErrAndFail.get(); + throw noMatchingFieldError(param); } Utils.hardAssert(fieldsWithSameName.size() == 1); ctorParams.add(fieldsWithSameName.get(0)); @@ -183,10 +174,19 @@ public String visitPrimitive(PrimitiveType t, Void unused) { } }; var typeName = paramType.accept(typeVisitor, null); - ; return typeName; } + private IRProcessingException noMatchingFieldError(VariableElement param) { + var paramSimpleType = simpleTypeName(param); + var paramName = param.getSimpleName().toString(); + var errMsg = + String.format( + "No matching field found for parameter %s of type %s. All fields: %s", + paramName, paramSimpleType, allFields); + return new IRProcessingException(errMsg, param); + } + /** * Method parameter * From 3d865b9b8e483f4c1f4b387e24cfce8d38a76142 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Wed, 1 Jan 2025 13:30:33 +0100 Subject: [PATCH 163/195] Add docs --- .../parser/processor/GenerateIRAnnotationVisitor.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/GenerateIRAnnotationVisitor.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/GenerateIRAnnotationVisitor.java index 96b72901b905..7010f9d590c9 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/GenerateIRAnnotationVisitor.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/GenerateIRAnnotationVisitor.java @@ -60,7 +60,10 @@ public List getAllInterfaces() { * Returns a type from {@link GenerateIR#interfaces()} that is a subtype of {@code * org.enso.compiler.core.IR}. There must be only one such subtype specified. * - * @return May be null. + * @return If there is no interface that is a subtype of {@code org.enso.compiler.core.IR} in the + * {@link GenerateIR#interfaces()}, returns {@code null}. Otherwise, returns the interface. + * Note that if null is returned, {@code org.enso.compiler.core.IR} should be used. See {@link + * GenerateIR#interfaces()}. */ public TypeElement getIrInterface() { return irInterface; From fab106fbea6075fc515d5d01f40f76041e5b5c01 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Wed, 1 Jan 2025 13:32:53 +0100 Subject: [PATCH 164/195] fix typos --- build.sbt | 4 ++-- .../src/main/java/org/enso/runtime/parser/dsl/GenerateIR.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.sbt b/build.sbt index 1bbe83446901..274c9b2cf458 100644 --- a/build.sbt +++ b/build.sbt @@ -3229,8 +3229,8 @@ lazy val `runtime-parser` = Compile / moduleDependencies ++= Seq( "org.netbeans.api" % "org-openide-util-lookup" % netbeansApiVersion ), - // 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. + // Java compiler is not able to correctly find all the annotation processors, because + // one of them 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", diff --git a/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/GenerateIR.java b/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/GenerateIR.java index 82185bf570c7..64f68945066c 100644 --- a/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/GenerateIR.java +++ b/engine/runtime-parser-dsl/src/main/java/org/enso/runtime/parser/dsl/GenerateIR.java @@ -26,7 +26,7 @@ /** * Interfaces that the generated superclass will implement. The list of the interfaces will simply * be put inside the {@code implements} clause of the generated class. All the generated classes - * implements {@code org.enso.compiler.core.IR} by default. + * implement {@code org.enso.compiler.core.IR} by default. */ Class[] interfaces() default {}; } From fe0a378cb2505d61a89e95d3ac54fbbcb37cec9d Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Wed, 1 Jan 2025 13:37:20 +0100 Subject: [PATCH 165/195] Rename minus to diff --- .../parser/processor/methodgen/BuilderMethodGenerator.java | 2 +- .../parser/processor/methodgen/DuplicateMethodGenerator.java | 2 +- .../java/org/enso/runtime/parser/processor/utils/Utils.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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 32bae349ec61..fbc2896a939e 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 @@ -86,7 +86,7 @@ private String buildMethod() { generatedClassContext.getProcessedClass().getClazz().getSimpleName().toString(); var ctorParams = generatedClassContext.getSubclassConstructorParameters(); var ctorParamsStr = ctorParams.stream().map(ClassField::name).collect(Collectors.joining(", ")); - var fieldsNotInCtor = Utils.minus(generatedClassContext.getAllFields(), ctorParams); + var fieldsNotInCtor = Utils.diff(generatedClassContext.getAllFields(), ctorParams); sb.append("public ") .append(processedClassName) .append(" build() {") 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 10250cb2f610..619ac0c0ce36 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 @@ -139,7 +139,7 @@ public String generateDuplicateMethodsCode() { sb.append(newSubclass); // Rest of the fields that need to be set - var restOfDuplicatedVars = Utils.minus(duplicatedVars, ctorParams); + var restOfDuplicatedVars = Utils.diff(duplicatedVars, ctorParams); for (var duplVar : restOfDuplicatedVars) { sb.append(" ").append("duplicated.").append(duplVar.originalName).append(" = "); if (duplVar.needsCast) { diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java index cbdc8f7e4167..0ec3afe6df92 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java @@ -209,7 +209,7 @@ public static T iterateSuperInterfaces( return null; } - public static List minus(List superset, List subset) { + public static List diff(List superset, List subset) { return superset.stream().filter(e -> !subset.contains(e)).collect(Collectors.toList()); } } From 42a988f2aa15c128d6cc591e1fb044c50d7f796c Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Wed, 1 Jan 2025 13:40:43 +0100 Subject: [PATCH 166/195] Introduce constants --- .../runtime/parser/processor/utils/Utils.java | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java index 0ec3afe6df92..a920b116b3ea 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java @@ -17,6 +17,11 @@ public final class Utils { private static final String MAP_EXPRESSIONS = "mapExpressions"; private static final String DUPLICATE = "duplicate"; + private static final String IR_INTERFACE_SIMPLE_NAME = "IR"; + private static final String IR_INTERFACE_FQN = "org.enso.compiler.core.IR"; + private static final String EXPRESSION_FQN = "org.enso.compiler.core.ir.Expression"; + private static final String SCALA_LIST = "scala.collection.immutable.List"; + private static final String SCALA_OPTION = "scala.Option"; private Utils() {} @@ -32,7 +37,7 @@ public static boolean isSubtypeOfIR(TypeElement type, ProcessingEnvironment proc // 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")) { + if (iface.getSimpleName().toString().equals(IR_INTERFACE_SIMPLE_NAME)) { return true; } return null; @@ -43,11 +48,12 @@ public static boolean isSubtypeOfIR(TypeElement type, ProcessingEnvironment proc /** Returns true if the given {@code type} is an {@code org.enso.compiler.core.IR} interface. */ public static boolean isIRInterface(TypeMirror type, ProcessingEnvironment processingEnv) { var elem = processingEnv.getTypeUtils().asElement(type); - return elem.getKind() == ElementKind.INTERFACE && elem.getSimpleName().toString().equals("IR"); + return elem.getKind() == ElementKind.INTERFACE + && elem.getSimpleName().toString().equals(IR_INTERFACE_SIMPLE_NAME); } public static TypeElement irTypeElement(ProcessingEnvironment procEnv) { - var ret = procEnv.getElementUtils().getTypeElement("org.enso.compiler.core.IR"); + var ret = procEnv.getElementUtils().getTypeElement(IR_INTERFACE_FQN); hardAssert(ret != null); return ret; } @@ -68,7 +74,7 @@ public static boolean isSubtypeOfExpression( } public static TypeElement expressionType(ProcessingEnvironment procEnv) { - return procEnv.getElementUtils().getTypeElement("org.enso.compiler.core.ir.Expression"); + return procEnv.getElementUtils().getTypeElement(EXPRESSION_FQN); } public static String indent(String code, int indentation) { @@ -78,17 +84,17 @@ public static String indent(String code, int indentation) { } public static boolean isScalaList(TypeElement type, ProcessingEnvironment procEnv) { - var scalaListType = procEnv.getElementUtils().getTypeElement("scala.collection.immutable.List"); + var scalaListType = procEnv.getElementUtils().getTypeElement(SCALA_LIST); return procEnv.getTypeUtils().isAssignable(type.asType(), scalaListType.asType()); } public static boolean isScalaOption(TypeElement type, ProcessingEnvironment procEnv) { - var scalaListType = procEnv.getElementUtils().getTypeElement("scala.Option"); + var scalaListType = procEnv.getElementUtils().getTypeElement(SCALA_OPTION); return procEnv.getTypeUtils().isAssignable(type.asType(), scalaListType.asType()); } public static boolean isScalaList(TypeMirror type, ProcessingEnvironment procEnv) { - var scalaListType = procEnv.getElementUtils().getTypeElement("scala.collection.immutable.List"); + var scalaListType = procEnv.getElementUtils().getTypeElement(SCALA_LIST); return procEnv.getTypeUtils().isSameType(type, scalaListType.asType()); } From cbaa6f7a50cf932d6a4e51e3df605ee463698bad Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Wed, 1 Jan 2025 17:30:17 +0100 Subject: [PATCH 167/195] passData is never null --- .../processor/GeneratedClassContext.java | 56 ++++++++++++++++--- .../processor/IRNodeClassGenerator.java | 12 ++-- .../methodgen/BuilderMethodGenerator.java | 11 ++-- .../enso/compiler/core/ir/CallArgument.java | 2 +- 4 files changed, 64 insertions(+), 17 deletions(-) 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 435cdbe7173f..1f73a1f542d5 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 @@ -26,7 +26,7 @@ public final class GeneratedClassContext { private static final ClassField diagnosticsMetaField = new ClassField("protected", "DiagnosticStorage", "diagnostics"); private static final ClassField passDataMetaField = - new ClassField("protected", "MetadataStorage", "passData"); + new ClassField("protected", "MetadataStorage", "passData", "new MetadataStorage()"); private static final ClassField locationMetaField = new ClassField("protected", "IdentifiedLocation", "location"); private static final ClassField idMetaField = new ClassField("protected", "UUID", "id"); @@ -200,13 +200,53 @@ public String toString() { } } - /** - * Declared field in the class - * - * @param modifiers e.g. "private final" - * @param type Type name. Includes generics. Can be, e.g., {@code Option}. - */ - public record ClassField(String modifiers, String type, String name) { + /** Declared field in the class */ + public static final class ClassField { + private final String modifiers; + private final String type; + private final String name; + private final String initializer; + + /** + * @param modifiers e.g. "private final" + * @param type Type name. Includes generics. Can be, e.g., {@code Option}. + */ + public ClassField(String modifiers, String type, String name) { + this(modifiers, type, name, null); + } + + /** + * @param modifiers e.g. "private final" + * @param type Type name. Includes generics. Can be, e.g., {@code Option}. + * @param initializer Initial value of the field. Can be, e.g., {@code "null"}. + */ + public ClassField(String modifiers, String type, String name, String initializer) { + this.modifiers = modifiers; + this.type = type; + this.name = name; + this.initializer = initializer; + } + + public String name() { + return name; + } + + public String modifiers() { + return modifiers; + } + + public String type() { + return type; + } + + /** + * @return May be null. In that case, initializer is unknown. Note that the class field can be + * primitive. + */ + public String initializer() { + return initializer; + } + @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 ce227d1ed15b..21c3e97fc3f6 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 @@ -252,7 +252,13 @@ private String constructorForFields( if (!initializeToNull.isEmpty()) { var initToNullBody = initializeToNull.stream() - .map(field -> " this.$fieldName = null;".replace("$fieldName", field.name())) + .map( + field -> { + var initializer = field.initializer() != null ? field.initializer() : "null"; + return " this.$fieldName = $init;" + .replace("$fieldName", field.name()) + .replace("$init", initializer); + }) .collect(Collectors.joining(System.lineSeparator())); sb.append(initToNullBody); } @@ -317,9 +323,7 @@ private String overrideIRMethods() { @Override public MetadataStorage passData() { - if (passData == null) { - passData = new MetadataStorage(); - } + assert passData != null : "passData must always be initialized"; return passData; } 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 fbc2896a939e..82f132483adb 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 @@ -23,10 +23,13 @@ public String generateBuilder() { var fieldDeclarations = generatedClassContext.getAllFields().stream() .map( - metaField -> - "private $type $name;" - .replace("$type", metaField.type()) - .replace("$name", metaField.name())) + field -> { + var initializer = field.initializer() != null ? " = " + field.initializer() : ""; + return "private $type $name $initializer;" + .replace("$type", field.type()) + .replace("$name", field.name()) + .replace("$initializer", initializer); + }) .collect(Collectors.joining(System.lineSeparator())); var fieldSetters = diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/CallArgument.java b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/CallArgument.java index 55c1f08ffb66..fa63ac407368 100644 --- a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/CallArgument.java +++ b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/CallArgument.java @@ -58,7 +58,7 @@ public Specified( Expression value, boolean isSynthetic, IdentifiedLocation identifiedLocation) { - this(name, value, isSynthetic, identifiedLocation, null); + this(name, value, isSynthetic, identifiedLocation, new MetadataStorage()); } public Specified copy(Expression value) { From 6ad9e232555ed7e998aaabac2415949a714ec86b Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 3 Jan 2025 14:14:26 +0100 Subject: [PATCH 168/195] Field uses TypeMirror instead of TypeElement --- .../runtime/parser/processor/field/Field.java | 5 +- .../processor/field/FieldCollector.java | 10 ++-- .../parser/processor/field/ListField.java | 7 +-- .../parser/processor/field/OptionField.java | 7 +-- .../processor/field/PrimitiveField.java | 5 +- .../processor/field/ReferenceField.java | 19 +++++--- .../methodgen/BuilderMethodGenerator.java | 6 +-- .../methodgen/DuplicateMethodGenerator.java | 8 ++-- .../MapExpressionsMethodGenerator.java | 13 +++--- .../runtime/parser/processor/utils/Utils.java | 46 +++++++++++++++++++ 10 files changed, 89 insertions(+), 37 deletions(-) diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/Field.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/Field.java index 01ffacf99c72..d2911438eaba 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/Field.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/Field.java @@ -3,6 +3,7 @@ import java.util.List; import java.util.function.Function; import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; import org.enso.runtime.parser.dsl.IRChild; /** Represents a field in the generated super class. */ @@ -11,8 +12,8 @@ public interface Field { /** Name (identifier) of the field. */ String getName(); - /** Returns type of this field. Null if this is a primitive field. */ - TypeElement getType(); + /** Returns type of this field. Must not be null. */ + TypeMirror getType(); /** * Does not return null. If the type is generic, the type parameter is included in the name. 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 2f163fa6fc14..7b9e953dc9e5 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 @@ -95,13 +95,11 @@ private boolean isMeta(VariableElement param) { private Field processIrField(VariableElement param, IRField irFieldAnnot) { var isNullable = !irFieldAnnot.required(); var name = param.getSimpleName().toString(); - var type = getParamType(param); if (isPrimitiveType(param)) { return new PrimitiveField(param.asType(), name); } else { // TODO: Assert that type is simple reference type - does not extend IR, is not generic - Utils.hardAssert(type != null); - return new ReferenceField(processingEnv, type, name, isNullable, false); + return new ReferenceField(processingEnv, param.asType(), name, isNullable, false); } } @@ -111,17 +109,17 @@ private Field processIrChild(VariableElement param, IRChild irChildAnnot) { var isNullable = !irChildAnnot.required(); if (Utils.isScalaList(type, processingEnv)) { var typeArgElem = getGenericType(param); - return new ListField(name, type, typeArgElem); + return new ListField(name, param.asType(), typeArgElem); } else if (Utils.isScalaOption(type, processingEnv)) { var typeArgElem = getGenericType(param); - return new OptionField(name, type, typeArgElem); + return new OptionField(name, param.asType(), typeArgElem); } else { if (!Utils.isSubtypeOfIR(type, processingEnv)) { throw new IRProcessingException( "Constructor parameter annotated with @IRChild must be a subtype of IR interface", param); } - return new ReferenceField(processingEnv, type, name, isNullable, true); + return new ReferenceField(processingEnv, param.asType(), name, isNullable, true); } } diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/ListField.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/ListField.java index 3ff0865e453d..463c3de13a8f 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/ListField.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/ListField.java @@ -2,18 +2,19 @@ import java.util.List; import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; /** 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; - private final TypeElement type; + private final TypeMirror type; /** * @param name Name of the field * @param typeArgElement TypeElement of the type argument. Must be subtype of IR. */ - ListField(String name, TypeElement type, TypeElement typeArgElement) { + ListField(String name, TypeMirror type, TypeElement typeArgElement) { this.name = name; this.type = type; this.typeArgElement = typeArgElement; @@ -25,7 +26,7 @@ public String getName() { } @Override - public TypeElement getType() { + public TypeMirror getType() { return type; } diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/OptionField.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/OptionField.java index 07160bfa43c6..e6ed5cee43b3 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/OptionField.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/OptionField.java @@ -2,14 +2,15 @@ import java.util.List; import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; /** Field representing {@code scala.Option} */ public class OptionField implements Field { private final String name; private final TypeElement typeArgElement; - private final TypeElement type; + private final TypeMirror type; - public OptionField(String name, TypeElement type, TypeElement typeArgElement) { + public OptionField(String name, TypeMirror type, TypeElement typeArgElement) { this.name = name; this.type = type; this.typeArgElement = typeArgElement; @@ -21,7 +22,7 @@ public String getName() { } @Override - public TypeElement getType() { + public TypeMirror getType() { return type; } diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/PrimitiveField.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/PrimitiveField.java index 5bd90afb7c74..f41d93dd6e57 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/PrimitiveField.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/PrimitiveField.java @@ -1,6 +1,5 @@ package org.enso.runtime.parser.processor.field; -import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; final class PrimitiveField implements Field { @@ -19,8 +18,8 @@ public String getName() { } @Override - public TypeElement getType() { - return null; + public TypeMirror getType() { + return type; } @Override 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 ec40774422c3..9c1f8fe69cb2 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 @@ -2,19 +2,19 @@ import java.util.List; import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; import org.enso.runtime.parser.processor.utils.Utils; final class ReferenceField implements Field { private final ProcessingEnvironment procEnv; - private final TypeElement type; + private final TypeMirror type; private final String name; private final boolean nullable; private final boolean isChild; ReferenceField( ProcessingEnvironment procEnv, - TypeElement type, + TypeMirror type, String name, boolean nullable, boolean isChild) { @@ -31,18 +31,23 @@ public String getName() { } @Override - public TypeElement getType() { + public TypeMirror getType() { return type; } @Override public String getSimpleTypeName() { - return type.getSimpleName().toString(); + return type.toString(); } @Override public List getImportedTypes() { - return List.of(type.getQualifiedName().toString()); + var typeElem = Utils.typeMirrorToElement(type); + if (typeElem != null) { + return List.of(typeElem.getQualifiedName().toString()); + } else { + return List.of(); + } } @Override @@ -62,6 +67,6 @@ public boolean isNullable() { @Override public boolean isExpression() { - return Utils.isSubtypeOfExpression(type.asType(), procEnv); + return Utils.isSubtypeOfExpression(type, 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 82f132483adb..4ecdb560406a 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 @@ -1,8 +1,8 @@ package org.enso.runtime.parser.processor.methodgen; import java.util.stream.Collectors; +import org.enso.runtime.parser.processor.ClassField; import org.enso.runtime.parser.processor.GeneratedClassContext; -import org.enso.runtime.parser.processor.GeneratedClassContext.ClassField; import org.enso.runtime.parser.processor.utils.Utils; /** @@ -26,7 +26,7 @@ public String generateBuilder() { field -> { var initializer = field.initializer() != null ? " = " + field.initializer() : ""; return "private $type $name $initializer;" - .replace("$type", field.type()) + .replace("$type", field.getTypeName()) .replace("$name", field.name()) .replace("$initializer", initializer); }) @@ -43,7 +43,7 @@ public String generateBuilder() { } """ .replace("$fieldName", field.name()) - .replace("$fieldType", field.type())) + .replace("$fieldType", field.getTypeName())) .collect(Collectors.joining(System.lineSeparator())); // Validation code for all non-nullable user fields 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 619ac0c0ce36..01c51b651faa 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 @@ -82,10 +82,10 @@ public String generateDuplicateMethodsCode() { idDuplicated = this.id; } """ - .replace("$locType", ctx.getLocationMetaField().type()) - .replace("$metaType", ctx.getPassDataMetaField().type()) - .replace("$diagType", ctx.getDiagnosticsMetaField().type()) - .replace("$idType", ctx.getIdMetaField().type()); + .replace("$locType", ctx.getLocationMetaField().getTypeName()) + .replace("$metaType", ctx.getPassDataMetaField().getTypeName()) + .replace("$diagType", ctx.getDiagnosticsMetaField().getTypeName()) + .replace("$idType", ctx.getIdMetaField().getTypeName()); sb.append(Utils.indent(duplicateMetaFieldsCode, 2)); sb.append(System.lineSeparator()); for (var metaVar : 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 ed96d8d6d9f0..a44ce415140f 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 java.util.Objects; import java.util.stream.Collectors; import javax.lang.model.element.ExecutableElement; +import org.enso.runtime.parser.processor.ClassField; import org.enso.runtime.parser.processor.GeneratedClassContext; -import org.enso.runtime.parser.processor.GeneratedClassContext.ClassField; import org.enso.runtime.parser.processor.IRProcessingException; import org.enso.runtime.parser.processor.field.Field; import org.enso.runtime.parser.processor.utils.Utils; @@ -64,17 +64,18 @@ public String generateMapExpressionsMethodCode() { Utils.findMapExpressionsMethod( child.getTypeParameter(), ctx.getProcessingEnvironment()); } else { + var childTypeElem = + Utils.typeMirrorToElement(child.getType()); childsMapExprMethod = Utils.findMapExpressionsMethod( - child.getType(), ctx.getProcessingEnvironment()); + childTypeElem, ctx.getProcessingEnvironment()); } var typeUtils = ctx.getProcessingEnvironment().getTypeUtils(); var childsMapExprMethodRetType = typeUtils.asElement(childsMapExprMethod.getReturnType()); var shouldCast = - !typeUtils.isSameType( - child.getType().asType(), childsMapExprMethodRetType.asType()); + !typeUtils.isSameType(child.getType(), childsMapExprMethodRetType.asType()); if (child.isList() || child.isOption()) { shouldCast = false; } @@ -173,7 +174,7 @@ public String generateMapExpressionsMethodCode() { .append("if (!(") .append(newChild.newChildName) .append(" instanceof ") - .append(newChild.child.getType().getSimpleName()) + .append(newChild.child.getSimpleTypeName()) .append(")) {") .append(System.lineSeparator()); sb.append(" ") @@ -187,7 +188,7 @@ public String generateMapExpressionsMethodCode() { } sb.append(" ").append("bldr.").append(newChild.child.getName()).append("("); if (newChild.shouldCast) { - sb.append("(").append(newChild.child.getType().getSimpleName()).append(") "); + sb.append("(").append(newChild.child.getSimpleTypeName()).append(") "); } sb.append(newChild.newChildName).append(");").append(System.lineSeparator()); } diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java index a920b116b3ea..538ca0129161 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java @@ -10,6 +10,8 @@ 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.TypeKind; import javax.lang.model.type.TypeMirror; import org.enso.runtime.parser.processor.IRProcessingException; @@ -22,6 +24,12 @@ public final class Utils { private static final String EXPRESSION_FQN = "org.enso.compiler.core.ir.Expression"; private static final String SCALA_LIST = "scala.collection.immutable.List"; private static final String SCALA_OPTION = "scala.Option"; + private static final String DIAGNOSTIC_STORAGE_FQN = + "org.enso.compiler.core.ir.DiagnosticStorage"; + private static final String IDENTIFIED_LOCATION_FQN = + "org.enso.compiler.core.ir.IdentifiedLocation"; + private static final String METADATA_STORAGE_FQN = "org.enso.compiler.core.ir.MetadataStorage"; + private static final String UUID_FQN = "java.util.UUID"; private Utils() {} @@ -58,6 +66,30 @@ public static TypeElement irTypeElement(ProcessingEnvironment procEnv) { return ret; } + public static TypeElement diagnosticStorageTypeElement(ProcessingEnvironment procEnv) { + var ret = procEnv.getElementUtils().getTypeElement(DIAGNOSTIC_STORAGE_FQN); + hardAssert(ret != null); + return ret; + } + + public static TypeElement identifiedLocationTypeElement(ProcessingEnvironment procEnv) { + var ret = procEnv.getElementUtils().getTypeElement(IDENTIFIED_LOCATION_FQN); + hardAssert(ret != null); + return ret; + } + + public static TypeElement metadataStorageTypeElement(ProcessingEnvironment procEnv) { + var ret = procEnv.getElementUtils().getTypeElement(METADATA_STORAGE_FQN); + hardAssert(ret != null); + return ret; + } + + public static TypeElement uuidTypeElement(ProcessingEnvironment procEnv) { + var ret = procEnv.getElementUtils().getTypeElement(UUID_FQN); + hardAssert(ret != null); + return ret; + } + public static boolean isExpression(Element elem, ProcessingEnvironment processingEnvironment) { if (elem instanceof TypeElement typeElem) { var exprType = expressionType(processingEnvironment); @@ -83,6 +115,20 @@ public static String indent(String code, int indentation) { .collect(Collectors.joining(System.lineSeparator())); } + /** + * Returns null if the given {@code typeMirror} is not a declared type and thus has no associated + * {@link TypeElement}. + */ + public static TypeElement typeMirrorToElement(TypeMirror typeMirror) { + if (typeMirror.getKind() == TypeKind.DECLARED) { + var elem = ((DeclaredType) typeMirror).asElement(); + if (elem instanceof TypeElement typeElem) { + return typeElem; + } + } + return null; + } + public static boolean isScalaList(TypeElement type, ProcessingEnvironment procEnv) { var scalaListType = procEnv.getElementUtils().getTypeElement(SCALA_LIST); return procEnv.getTypeUtils().isAssignable(type.asType(), scalaListType.asType()); From 79364fc4534d45f69461de691a080bafbcd172c9 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 3 Jan 2025 14:15:05 +0100 Subject: [PATCH 169/195] More constant in Utils --- .../processor/field/FieldCollector.java | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) 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 7b9e953dc9e5..6c558f7e6d76 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 @@ -3,7 +3,6 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; @@ -32,21 +31,10 @@ public final class FieldCollector { public FieldCollector(ProcessingEnvironment processingEnv, ProcessedClass processedClass) { this.processingEnv = processingEnv; this.processedClass = processedClass; - this.metadataStorageType = - processingEnv.getElementUtils().getTypeElement("org.enso.compiler.core.ir.MetadataStorage"); - this.diagnosticStorageType = - processingEnv - .getElementUtils() - .getTypeElement("org.enso.compiler.core.ir.DiagnosticStorage"); - this.identifiedLocationType = - processingEnv - .getElementUtils() - .getTypeElement("org.enso.compiler.core.ir.IdentifiedLocation"); - this.uuidType = processingEnv.getElementUtils().getTypeElement("java.util.UUID"); - Objects.requireNonNull(metadataStorageType); - Objects.requireNonNull(diagnosticStorageType); - Objects.requireNonNull(identifiedLocationType); - Objects.requireNonNull(uuidType); + this.metadataStorageType = Utils.metadataStorageTypeElement(processingEnv); + this.diagnosticStorageType = Utils.diagnosticStorageTypeElement(processingEnv); + this.identifiedLocationType = Utils.identifiedLocationTypeElement(processingEnv); + this.uuidType = Utils.uuidTypeElement(processingEnv); } public List collectFields() { From d22bf6d9978dffe9f36ec57736fd5934f68a9b3c Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 3 Jan 2025 14:15:56 +0100 Subject: [PATCH 170/195] Move ClassField out of GeneratedClassContext. Also introduce more fields to ClassField --- .../runtime/parser/processor/ClassField.java | 128 ++++++++++++++++++ .../processor/GeneratedClassContext.java | 119 +++++++--------- .../processor/IRNodeClassGenerator.java | 1 - .../methodgen/HashCodeMethodGenerator.java | 2 +- 4 files changed, 177 insertions(+), 73 deletions(-) create mode 100644 engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ClassField.java diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ClassField.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ClassField.java new file mode 100644 index 000000000000..dcaf260d93c3 --- /dev/null +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ClassField.java @@ -0,0 +1,128 @@ +package org.enso.runtime.parser.processor; + +import java.util.Objects; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; + +/** Declared field in the generated class. */ +public final class ClassField { + + private final String modifiers; + private final TypeMirror type; + private final String name; + private final String initializer; + private final boolean canBeNull; + private final ProcessingEnvironment procEnv; + + public static Builder builder() { + return new Builder(); + } + + /** + * @param modifiers e.g. "private final" + * @param initializer Initial value of the field. Can be, e.g., {@code "null"}. + */ + private ClassField( + String modifiers, + TypeMirror type, + String name, + String initializer, + boolean canBeNull, + ProcessingEnvironment procEnv) { + this.modifiers = modifiers; + this.type = type; + this.name = name; + this.initializer = initializer; + this.canBeNull = canBeNull; + this.procEnv = procEnv; + } + + public String name() { + return name; + } + + public String modifiers() { + return modifiers; + } + + public String getTypeName() { + var elem = procEnv.getTypeUtils().asElement(type); + if (elem instanceof TypeElement typeElem) { + return typeElem.getQualifiedName().toString(); + } else { + // Is primitive + return type.toString(); + } + } + + /** + * @return May be null. In that case, initializer is unknown. Note that the class field can be + * primitive. + */ + public String initializer() { + return initializer; + } + + public boolean canBeNull() { + return canBeNull; + } + + @Override + public String toString() { + return modifiers + " " + type + " " + name; + } + + /** Returns simple non-qualified type name. Generic types are returned as raw types. */ + public String simpleTypeName() { + var typeParts = getTypeName().split("<"); + return typeParts[0]; + } + + public static final class Builder { + private TypeMirror type; + private String name; + private String modifiers = null; + private String initializer = null; + private boolean canBeNull = true; + private ProcessingEnvironment procEnv; + + public Builder modifiers(String modifiers) { + this.modifiers = modifiers; + return this; + } + + public Builder type(TypeMirror type) { + this.type = type; + return this; + } + + public Builder name(String name) { + this.name = name; + return this; + } + + public Builder canBeNull(boolean canBeNull) { + this.canBeNull = canBeNull; + return this; + } + + public Builder initializer(String initializer) { + this.initializer = initializer; + return this; + } + + public Builder procEnv(ProcessingEnvironment procEnv) { + this.procEnv = procEnv; + return this; + } + + public ClassField build() { + Objects.requireNonNull(type); + Objects.requireNonNull(name); + Objects.requireNonNull(procEnv); + var modifiers = this.modifiers != null ? this.modifiers : ""; + return new ClassField(modifiers, type, name, initializer, canBeNull, procEnv); + } + } +} 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 1f73a1f542d5..f805db6a4491 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 @@ -23,17 +23,13 @@ public final class GeneratedClassContext { private final ProcessingEnvironment processingEnvironment; private final ProcessedClass processedClass; - private static final ClassField diagnosticsMetaField = - new ClassField("protected", "DiagnosticStorage", "diagnostics"); - private static final ClassField passDataMetaField = - new ClassField("protected", "MetadataStorage", "passData", "new MetadataStorage()"); - private static final ClassField locationMetaField = - new ClassField("protected", "IdentifiedLocation", "location"); - private static final ClassField idMetaField = new ClassField("protected", "UUID", "id"); + private final ClassField diagnosticsMetaField; + private final ClassField passDataMetaField; + private final ClassField locationMetaField; + private final ClassField idMetaField; /** Meta fields are always present in the generated class. */ - private static final List metaFields = - List.of(diagnosticsMetaField, passDataMetaField, locationMetaField, idMetaField); + private final List metaFields; /** * @param className Simple name of the generated class @@ -50,14 +46,54 @@ public final class GeneratedClassContext { this.processingEnvironment = Objects.requireNonNull(processingEnvironment); this.processedClass = processedClass; ensureSimpleName(className); + + this.diagnosticsMetaField = + ClassField.builder() + .modifiers("protected") + .type(Utils.diagnosticStorageTypeElement(processingEnvironment).asType()) + .name("diagnostics") + .procEnv(processingEnvironment) + .build(); + this.passDataMetaField = + ClassField.builder() + .modifiers("protected") + .type(Utils.metadataStorageTypeElement(processingEnvironment).asType()) + .name("passData") + .initializer("new MetadataStorage()") + .procEnv(processingEnvironment) + .canBeNull(false) + .build(); + this.locationMetaField = + ClassField.builder() + .modifiers("protected") + .type(Utils.identifiedLocationTypeElement(processingEnvironment).asType()) + .name("location") + .procEnv(processingEnvironment) + .build(); + this.idMetaField = + ClassField.builder() + .modifiers("protected") + .type(Utils.uuidTypeElement(processingEnvironment).asType()) + .name("id") + .procEnv(processingEnvironment) + .build(); + this.metaFields = + List.of(diagnosticsMetaField, passDataMetaField, locationMetaField, idMetaField); + this.allFields = new ArrayList<>(metaFields); for (var userField : userFields) { allFields.add( - new ClassField("private final", userField.getSimpleTypeName(), userField.getName())); + ClassField.builder() + .modifiers("private final") + .type(userField.getType()) + .name(userField.getName()) + .canBeNull(userField.isNullable() && !userField.isPrimitive()) + .procEnv(processingEnvironment) + .build()); } this.constructorParameters = allFields.stream() - .map(classField -> new Parameter(classField.type(), classField.name())) + .map(classField -> new Parameter(classField.getTypeName(), classField.name())) .toList(); } @@ -145,7 +181,7 @@ public List getSubclassConstructorParameters() { } else { // There are multiple fields with the same type - try to match on the name var fieldsWithSameName = - fieldsWithSameType.stream().filter(field -> paramName.equals(field.name)).toList(); + fieldsWithSameType.stream().filter(field -> paramName.equals(field.name())).toList(); Utils.hardAssert( fieldsWithSameName.size() < 2, "Cannot have more than one field with the same name and type"); @@ -199,63 +235,4 @@ public String toString() { return type + " " + name; } } - - /** Declared field in the class */ - public static final class ClassField { - private final String modifiers; - private final String type; - private final String name; - private final String initializer; - - /** - * @param modifiers e.g. "private final" - * @param type Type name. Includes generics. Can be, e.g., {@code Option}. - */ - public ClassField(String modifiers, String type, String name) { - this(modifiers, type, name, null); - } - - /** - * @param modifiers e.g. "private final" - * @param type Type name. Includes generics. Can be, e.g., {@code Option}. - * @param initializer Initial value of the field. Can be, e.g., {@code "null"}. - */ - public ClassField(String modifiers, String type, String name, String initializer) { - this.modifiers = modifiers; - this.type = type; - this.name = name; - this.initializer = initializer; - } - - public String name() { - return name; - } - - public String modifiers() { - return modifiers; - } - - public String type() { - return type; - } - - /** - * @return May be null. In that case, initializer is unknown. Note that the class field can be - * primitive. - */ - public String initializer() { - return initializer; - } - - @Override - public String toString() { - return modifiers + " " + type + " " + name; - } - - /** Returns simple non-qualified type name. Generic types are returned as raw types. */ - public String simpleTypeName() { - var typeParts = type.split("<"); - return typeParts[0]; - } - } } 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 21c3e97fc3f6..a738ee0dc40b 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 @@ -5,7 +5,6 @@ import java.util.Set; import java.util.stream.Collectors; import javax.annotation.processing.ProcessingEnvironment; -import org.enso.runtime.parser.processor.GeneratedClassContext.ClassField; import org.enso.runtime.parser.processor.GeneratedClassContext.Parameter; import org.enso.runtime.parser.processor.field.Field; import org.enso.runtime.parser.processor.field.FieldCollector; 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 82b8101ed542..bdc5da20cce0 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 @@ -1,8 +1,8 @@ package org.enso.runtime.parser.processor.methodgen; import java.util.stream.Collectors; +import org.enso.runtime.parser.processor.ClassField; import org.enso.runtime.parser.processor.GeneratedClassContext; -import org.enso.runtime.parser.processor.GeneratedClassContext.ClassField; public final class HashCodeMethodGenerator { private final GeneratedClassContext ctx; From f36220a0aaa79e67886b81cfbb7b83f57141089a Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 3 Jan 2025 15:06:13 +0100 Subject: [PATCH 171/195] duplicate method compares fqn type names --- .../runtime/parser/processor/ClassField.java | 13 ++- .../processor/GeneratedClassContext.java | 6 +- .../methodgen/DuplicateMethodGenerator.java | 81 ++++++++++--------- .../MapExpressionsMethodGenerator.java | 3 +- 4 files changed, 53 insertions(+), 50 deletions(-) diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ClassField.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ClassField.java index dcaf260d93c3..5b1f4b9c6406 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ClassField.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ClassField.java @@ -2,7 +2,6 @@ import java.util.Objects; import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; /** Declared field in the generated class. */ @@ -46,14 +45,12 @@ public String modifiers() { return modifiers; } + public TypeMirror getType() { + return type; + } + public String getTypeName() { - var elem = procEnv.getTypeUtils().asElement(type); - if (elem instanceof TypeElement typeElem) { - return typeElem.getQualifiedName().toString(); - } else { - // Is primitive - return type.toString(); - } + return type.toString(); } /** 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 f805db6a4491..f356ad7ae51c 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 @@ -168,12 +168,10 @@ public List getSubclassConstructorParameters() { var ctor = processedClass.getCtor(); var ctorParams = new ArrayList(); for (var param : ctor.getParameters()) { - var paramSimpleType = simpleTypeName(param); + var paramType = param.asType().toString(); var paramName = param.getSimpleName().toString(); var fieldsWithSameType = - allFields.stream() - .filter(field -> paramSimpleType.equals(field.simpleTypeName())) - .toList(); + allFields.stream().filter(field -> paramType.equals(field.getTypeName())).toList(); if (fieldsWithSameType.isEmpty()) { throw noMatchingFieldError(param); } else if (fieldsWithSameType.size() == 1) { 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 01c51b651faa..65aff2d53d71 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 @@ -6,6 +6,7 @@ import java.util.stream.Collectors; import javax.lang.model.element.ExecutableElement; import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; import org.enso.runtime.parser.processor.GeneratedClassContext; import org.enso.runtime.parser.processor.IRProcessingException; import org.enso.runtime.parser.processor.field.Field; @@ -16,25 +17,26 @@ * Note that in the interface hierarchy, there can be an override with a different return type. */ public class DuplicateMethodGenerator { - private final ExecutableElement duplicateMethod; + private final GeneratedClassContext ctx; - private static final List parameters = - List.of( - new Parameter("boolean", "keepLocations"), - new Parameter("boolean", "keepMetadata"), - new Parameter("boolean", "keepDiagnostics"), - new Parameter("boolean", "keepIdentifiers")); + private final List parameters; /** * @param duplicateMethod ExecutableElement representing the duplicate method (or its override). */ public DuplicateMethodGenerator(ExecutableElement duplicateMethod, GeneratedClassContext ctx) { - ensureDuplicateMethodHasExpectedSignature(duplicateMethod); this.ctx = Objects.requireNonNull(ctx); - this.duplicateMethod = Objects.requireNonNull(duplicateMethod); + var boolType = ctx.getProcessingEnvironment().getTypeUtils().getPrimitiveType(TypeKind.BOOLEAN); + this.parameters = + List.of( + new Parameter(boolType, "keepLocations"), + new Parameter(boolType, "keepMetadata"), + new Parameter(boolType, "keepDiagnostics"), + new Parameter(boolType, "keepIdentifiers")); + ensureDuplicateMethodHasExpectedSignature(duplicateMethod); } - private static void ensureDuplicateMethodHasExpectedSignature(ExecutableElement duplicateMethod) { + private void ensureDuplicateMethodHasExpectedSignature(ExecutableElement duplicateMethod) { var dupMethodParameters = duplicateMethod.getParameters(); if (dupMethodParameters.size() != parameters.size()) { throw new IRProcessingException( @@ -88,12 +90,7 @@ public String generateDuplicateMethodsCode() { .replace("$idType", ctx.getIdMetaField().getTypeName()); sb.append(Utils.indent(duplicateMetaFieldsCode, 2)); sb.append(System.lineSeparator()); - for (var metaVar : - List.of( - new MetaField("DiagnosticStorage", "diagnostics"), - new MetaField("MetadataStorage", "passData"), - new MetaField("IdentifiedLocation", "location"), - new MetaField("UUID", "id"))) { + for (var metaVar : metaFields()) { var dupName = metaVar.name + "Duplicated"; duplicatedVars.add(new DuplicateVar(metaVar.type, dupName, metaVar.name, false)); } @@ -104,33 +101,30 @@ public String generateDuplicateMethodsCode() { sb.append(Utils.indent(nullableChildCode(field), 2)); sb.append(System.lineSeparator()); duplicatedVars.add( - new DuplicateVar( - field.getSimpleTypeName(), dupFieldName(field), field.getName(), true)); + new DuplicateVar(field.getType(), dupFieldName(field), field.getName(), true)); } else { if (field.isList()) { sb.append(Utils.indent(listChildCode(field), 2)); sb.append(System.lineSeparator()); duplicatedVars.add( - new DuplicateVar("List", dupFieldName(field), field.getName(), false)); + new DuplicateVar(field.getType(), dupFieldName(field), field.getName(), false)); } else if (field.isOption()) { sb.append(Utils.indent(optionChildCode(field), 2)); sb.append(System.lineSeparator()); duplicatedVars.add( - new DuplicateVar("Option", dupFieldName(field), field.getName(), false)); + new DuplicateVar(field.getType(), dupFieldName(field), field.getName(), false)); } else { sb.append(Utils.indent(notNullableChildCode(field), 2)); sb.append(System.lineSeparator()); duplicatedVars.add( - new DuplicateVar( - field.getSimpleTypeName(), dupFieldName(field), field.getName(), true)); + new DuplicateVar(field.getType(), dupFieldName(field), field.getName(), true)); } } } else { sb.append(Utils.indent(nonChildCode(field), 2)); sb.append(System.lineSeparator()); duplicatedVars.add( - new DuplicateVar( - field.getSimpleTypeName(), dupFieldName(field), field.getName(), false)); + new DuplicateVar(field.getType(), dupFieldName(field), field.getName(), false)); } } @@ -156,6 +150,19 @@ public String generateDuplicateMethodsCode() { return defaultDuplicateMethod + System.lineSeparator() + parameterlessDuplicateMethod(); } + private List metaFields() { + var procEnv = ctx.getProcessingEnvironment(); + var diagTypeElem = Utils.diagnosticStorageTypeElement(procEnv); + var metaTypeElem = Utils.metadataStorageTypeElement(procEnv); + var locationTypeElem = Utils.identifiedLocationTypeElement(procEnv); + var uuidTypeElem = Utils.uuidTypeElement(procEnv); + return List.of( + new MetaField(diagTypeElem.asType(), "diagnostics"), + new MetaField(metaTypeElem.asType(), "passData"), + new MetaField(locationTypeElem.asType(), "location"), + new MetaField(uuidTypeElem.asType(), "id")); + } + private String parameterlessDuplicateMethod() { var code = """ @@ -171,7 +178,7 @@ private static String dupFieldName(Field field) { return field.getName() + "Duplicated"; } - private static String nullableChildCode(Field nullableChild) { + private String nullableChildCode(Field nullableChild) { Utils.hardAssert(nullableChild.isNullable() && nullableChild.isChild()); return """ IR $dupName = null; @@ -188,7 +195,7 @@ private static String nullableChildCode(Field nullableChild) { .replace("$parameterNames", String.join(", ", parameterNames())); } - private static String notNullableChildCode(Field child) { + private String notNullableChildCode(Field child) { assert child.isChild() && !child.isNullable() && !child.isList() && !child.isOption(); return """ IR $dupName = $childName.duplicate($parameterNames); @@ -202,7 +209,7 @@ private static String notNullableChildCode(Field child) { .replace("$parameterNames", String.join(", ", parameterNames())); } - private static String listChildCode(Field listChild) { + private String listChildCode(Field listChild) { Utils.hardAssert(listChild.isChild() && listChild.isList()); return """ $childListType $dupName = @@ -221,7 +228,7 @@ private static String listChildCode(Field listChild) { .replace("$parameterNames", String.join(", ", parameterNames())); } - private static String optionChildCode(Field optionChild) { + private String optionChildCode(Field optionChild) { Utils.hardAssert(optionChild.isOption() && optionChild.isChild()); return """ $childOptType $dupName = $childName; @@ -250,7 +257,7 @@ private static String nonChildCode(Field field) { .replace("$dupName", dupFieldName(field)); } - private static List parameterNames() { + private List parameterNames() { return parameters.stream().map(Parameter::name).collect(Collectors.toList()); } @@ -292,10 +299,13 @@ private String newSubclass(List ctorParams) { private List matchCtorParams(List duplicatedVars) { var ctorParams = new ArrayList(); for (var subclassCtorParam : ctx.getSubclassConstructorParameters()) { - var paramType = subclassCtorParam.simpleTypeName(); + var paramType = subclassCtorParam.getTypeName(); var paramName = subclassCtorParam.name(); duplicatedVars.stream() - .filter(var -> var.type.equals(paramType) && var.originalName.equals(paramName)) + .filter( + var -> + var.type.equals(subclassCtorParam.getType()) + && var.originalName.equals(paramName)) .findFirst() .ifPresentOrElse( ctorParams::add, @@ -316,14 +326,13 @@ private String dupMethodRetType() { } /** - * @param type Simple type name. Must not be null. * @param duplicatedName Name of the duplicated variable * @param originalName Name of the original variable (field) - * @param needsCast If the duplicated variable needs to be casted to its type in the return + * @param needsCast If the duplicated variable needs to be cast to its type in the return * statement. */ private record DuplicateVar( - String type, String duplicatedName, String originalName, boolean needsCast) {} + TypeMirror type, String duplicatedName, String originalName, boolean needsCast) {} /** * Parameter for the duplicate method @@ -331,7 +340,7 @@ private record DuplicateVar( * @param type * @param name */ - private record Parameter(String type, String name) { + private record Parameter(TypeMirror type, String name) { @Override public String toString() { @@ -339,5 +348,5 @@ public String toString() { } } - private record MetaField(String type, String name) {} + private record MetaField(TypeMirror type, String name) {} } 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 a44ce415140f..c7e25e91d69d 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 @@ -64,8 +64,7 @@ public String generateMapExpressionsMethodCode() { Utils.findMapExpressionsMethod( child.getTypeParameter(), ctx.getProcessingEnvironment()); } else { - var childTypeElem = - Utils.typeMirrorToElement(child.getType()); + var childTypeElem = Utils.typeMirrorToElement(child.getType()); childsMapExprMethod = Utils.findMapExpressionsMethod( childTypeElem, ctx.getProcessingEnvironment()); From c872739c877c8bc253a0b53e2e7c6f19d5cf4342 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 3 Jan 2025 16:41:46 +0100 Subject: [PATCH 172/195] ClassField.canBeNull must be specified in builder --- .../enso/runtime/parser/processor/ClassField.java | 13 ++++++------- .../parser/processor/GeneratedClassContext.java | 3 +++ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ClassField.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ClassField.java index 5b1f4b9c6406..755af4d2e7eb 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ClassField.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ClassField.java @@ -53,6 +53,10 @@ public String getTypeName() { return type.toString(); } + public boolean isPrimitive() { + return type.getKind().isPrimitive(); + } + /** * @return May be null. In that case, initializer is unknown. Note that the class field can be * primitive. @@ -70,18 +74,12 @@ public String toString() { return modifiers + " " + type + " " + name; } - /** Returns simple non-qualified type name. Generic types are returned as raw types. */ - public String simpleTypeName() { - var typeParts = getTypeName().split("<"); - return typeParts[0]; - } - public static final class Builder { private TypeMirror type; private String name; private String modifiers = null; private String initializer = null; - private boolean canBeNull = true; + private Boolean canBeNull = null; private ProcessingEnvironment procEnv; public Builder modifiers(String modifiers) { @@ -118,6 +116,7 @@ public ClassField build() { Objects.requireNonNull(type); Objects.requireNonNull(name); Objects.requireNonNull(procEnv); + Objects.requireNonNull(canBeNull); var modifiers = this.modifiers != null ? this.modifiers : ""; return new ClassField(modifiers, type, name, initializer, canBeNull, procEnv); } 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 f356ad7ae51c..8a4988cbc7b7 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 @@ -53,6 +53,7 @@ public final class GeneratedClassContext { .type(Utils.diagnosticStorageTypeElement(processingEnvironment).asType()) .name("diagnostics") .procEnv(processingEnvironment) + .canBeNull(true) .build(); this.passDataMetaField = ClassField.builder() @@ -69,12 +70,14 @@ public final class GeneratedClassContext { .type(Utils.identifiedLocationTypeElement(processingEnvironment).asType()) .name("location") .procEnv(processingEnvironment) + .canBeNull(true) .build(); this.idMetaField = ClassField.builder() .modifiers("protected") .type(Utils.uuidTypeElement(processingEnvironment).asType()) .name("id") + .canBeNull(true) .procEnv(processingEnvironment) .build(); this.metaFields = From 0b8715ef229bec83f75a9295dab8178f64df65e0 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 3 Jan 2025 16:42:00 +0100 Subject: [PATCH 173/195] Generate validateConstructor method --- .../processor/IRNodeClassGenerator.java | 36 +++++++++++++++++++ 1 file changed, 36 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 a738ee0dc40b..8d1b4b4918aa 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 @@ -114,6 +114,8 @@ String classBody() { $ctorWithUserFields + $validateConstructor + public static Builder builder() { return new Builder(); } @@ -135,6 +137,7 @@ public static Builder builder() { .replace("$fields", fieldsCode()) .replace("$defaultCtor", defaultConstructor()) .replace("$ctorWithUserFields", constructorForUserFields()) + .replace("$validateConstructor", validateConstructor()) .replace("$userDefinedGetters", userDefinedGetters()) .replace("$overrideIRMethods", overrideIRMethods()) .replace("$mapExpressionsMethod", mapExpressions()) @@ -260,8 +263,41 @@ private String constructorForFields( }) .collect(Collectors.joining(System.lineSeparator())); sb.append(initToNullBody); + sb.append(System.lineSeparator()); } + sb.append(" validateConstructor();").append(System.lineSeparator()); + sb.append(System.lineSeparator()); + sb.append("}").append(System.lineSeparator()); + return sb.toString(); + } + /** + * Generates code for validation at the end of the constructor. Validates if all the required + * fields were set in the constructor (passed as params). + */ + private String validateConstructor() { + var sb = new StringBuilder(); + sb.append( + """ + /** + * Validates if all the required fields were set in the constructor. + */ + """); + sb.append("private void validateConstructor() {").append(System.lineSeparator()); + var checkCode = + generatedClassContext.getAllFields().stream() + .filter(field -> !field.canBeNull()) + .filter(field -> !field.isPrimitive()) + .map( + notNullField -> + """ + if ($fieldName == null) { + throw new IllegalArgumentException("$fieldName is required"); + } + """ + .replace("$fieldName", notNullField.name())) + .collect(Collectors.joining(System.lineSeparator())); + sb.append(Utils.indent(checkCode, 2)); sb.append(System.lineSeparator()); sb.append("}").append(System.lineSeparator()); return sb.toString(); From 832f47db066a8c3a58eeee88ecd7b5e91fb54c18 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 3 Jan 2025 16:48:55 +0100 Subject: [PATCH 174/195] Prefer simple type names in generated source --- .../processor/GeneratedClassContext.java | 18 +++++++++--------- .../parser/processor/IRNodeClassGenerator.java | 4 ++-- .../runtime/parser/processor/utils/Utils.java | 9 +++++++++ 3 files changed, 20 insertions(+), 11 deletions(-) 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 8a4988cbc7b7..02003112d9aa 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,6 +7,7 @@ import javax.lang.model.element.VariableElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.PrimitiveType; +import javax.lang.model.type.TypeMirror; import javax.lang.model.util.SimpleTypeVisitor14; import org.enso.runtime.parser.processor.field.Field; import org.enso.runtime.parser.processor.utils.Utils; @@ -96,7 +97,7 @@ public final class GeneratedClassContext { } this.constructorParameters = allFields.stream() - .map(classField -> new Parameter(classField.getTypeName(), classField.name())) + .map(classField -> new Parameter(classField.getType(), classField.name())) .toList(); } @@ -224,16 +225,15 @@ private IRProcessingException noMatchingFieldError(VariableElement param) { return new IRProcessingException(errMsg, param); } - /** - * Method parameter - * - * @param type - * @param name - */ - record Parameter(String type, String name) { + /** Method parameter */ + record Parameter(TypeMirror type, String name) { @Override public String toString() { - return type + " " + name; + return simpleTypeName() + " " + name; + } + + String simpleTypeName() { + return Utils.simpleTypeName(type); } } } 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 8d1b4b4918aa..7c96323accff 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 @@ -209,7 +209,7 @@ private String constructorForUserFields() { """; var userFieldsAsParameters = generatedClassContext.getUserFields().stream() - .map(field -> new Parameter(field.getSimpleTypeName(), field.getName())) + .map(field -> new Parameter(field.getType(), field.getName())) .toList(); var ctorCode = constructorForFields(userFieldsAsParameters, generatedClassContext.getMetaFields()); @@ -236,7 +236,7 @@ private String constructorForFields( .map( consParam -> "$consType $consName" - .replace("$consType", consParam.type()) + .replace("$consType", consParam.simpleTypeName()) .replace("$consName", consParam.name())) .collect(Collectors.joining(", ")); sb.append(inParens).append(") {").append(System.lineSeparator()); diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java index 538ca0129161..d6cf19da5f43 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java @@ -109,6 +109,15 @@ public static TypeElement expressionType(ProcessingEnvironment procEnv) { return procEnv.getElementUtils().getTypeElement(EXPRESSION_FQN); } + public static String simpleTypeName(TypeMirror typeMirror) { + var typeElem = typeMirrorToElement(typeMirror); + if (typeElem != null) { + return typeElem.getSimpleName().toString(); + } else { + return typeMirror.toString(); + } + } + public static String indent(String code, int indentation) { return code.lines() .map(line -> " ".repeat(indentation) + line) From bdf0258fbf3936afb9c3eb52a3f9dbdb7e084e1f Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 3 Jan 2025 16:49:06 +0100 Subject: [PATCH 175/195] Fix test: passData is never null --- .../enso/runtime/parser/processor/test/GeneratedIRTest.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/runtime-parser-processor-tests/src/test/scala/org/enso/runtime/parser/processor/test/GeneratedIRTest.scala b/engine/runtime-parser-processor-tests/src/test/scala/org/enso/runtime/parser/processor/test/GeneratedIRTest.scala index 3d0bb884df71..d545c9428412 100644 --- a/engine/runtime-parser-processor-tests/src/test/scala/org/enso/runtime/parser/processor/test/GeneratedIRTest.scala +++ b/engine/runtime-parser-processor-tests/src/test/scala/org/enso/runtime/parser/processor/test/GeneratedIRTest.scala @@ -31,7 +31,7 @@ class GeneratedIRTest extends AnyFlatSpec with Matchers { str.contains("JCallArgument.JSpecified") shouldBe true str.contains("name = None") shouldBe true str.contains("value = Literal.Text") shouldBe true - str.contains("passData = null") shouldBe true + str.contains("location = null") shouldBe true } } } From 597edacde4e1cb5a028302301b5bb5790a038b3b Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 3 Jan 2025 17:02:21 +0100 Subject: [PATCH 176/195] Fix Utils.simpleTypeName --- .../runtime/parser/processor/utils/Utils.java | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java index d6cf19da5f43..25b890dae2cf 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java @@ -109,13 +109,21 @@ public static TypeElement expressionType(ProcessingEnvironment procEnv) { return procEnv.getElementUtils().getTypeElement(EXPRESSION_FQN); } + /** Converts all the FQN parts of the type name to simple names. Includes type arguments. */ public static String simpleTypeName(TypeMirror typeMirror) { - var typeElem = typeMirrorToElement(typeMirror); - if (typeElem != null) { - return typeElem.getSimpleName().toString(); - } else { - return typeMirror.toString(); + if (typeMirror.getKind() == TypeKind.DECLARED) { + var declared = (DeclaredType) typeMirror; + var typeArgs = declared.getTypeArguments(); + var typeElem = (TypeElement) declared.asElement(); + if (!typeArgs.isEmpty()) { + var typeArgsStr = + typeArgs.stream().map(Utils::simpleTypeName).collect(Collectors.joining(", ")); + return typeElem.getSimpleName().toString() + "<" + typeArgsStr + ">"; + } else { + return typeElem.getSimpleName().toString(); + } } + return typeMirror.toString(); } public static String indent(String code, int indentation) { From 2cd394618cf6b746f0a24a046f7346d0d89220de Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 3 Jan 2025 17:52:40 +0100 Subject: [PATCH 177/195] Field is abstract class. Simplify Field hierarchy --- .../runtime/parser/processor/field/Field.java | 92 +++++++++++++------ .../processor/field/FieldCollector.java | 32 +++---- .../parser/processor/field/ListField.java | 56 +---------- .../parser/processor/field/OptionField.java | 51 +--------- .../processor/field/PrimitiveField.java | 31 +------ .../processor/field/ReferenceField.java | 46 +--------- .../runtime/parser/processor/utils/Utils.java | 44 +++++++-- 7 files changed, 125 insertions(+), 227 deletions(-) diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/Field.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/Field.java index d2911438eaba..6b55027ad205 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/Field.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/Field.java @@ -2,41 +2,43 @@ import java.util.List; import java.util.function.Function; +import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import org.enso.runtime.parser.dsl.IRChild; +import org.enso.runtime.parser.processor.IRProcessingException; +import org.enso.runtime.parser.processor.utils.Utils; /** Represents a field in the generated super class. */ -public interface Field { +public abstract class Field { + protected final TypeMirror type; + protected final String name; + private final ProcessingEnvironment procEnv; + + protected Field(TypeMirror type, String name, ProcessingEnvironment procEnv) { + this.type = type; + this.name = name; + this.procEnv = procEnv; + } /** Name (identifier) of the field. */ - String getName(); + public String getName() { + return name; + } /** Returns type of this field. Must not be null. */ - TypeMirror getType(); + public TypeMirror getType() { + return type; + } /** * Does not return null. If the type is generic, the type parameter is included in the name. * Returns non-qualified name. */ - String getSimpleTypeName(); - - /** - * 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 {@code scala.Option}. */ - default boolean isOption() { - return false; + public String getSimpleTypeName() { + return Utils.simpleTypeName(type); } /** @@ -44,31 +46,65 @@ default boolean isOption() { * * @return */ - boolean isChild(); + public abstract boolean isChild(); /** * Returns true if this field is child with {@link IRChild#required()} set to false. * * @return */ - boolean isNullable(); + public abstract boolean isNullable(); + + /** + * Returns list of (fully-qualified) types that are necessary to import in order to use simple + * type names. + */ + public List getImportedTypes() { + return Utils.getImportedTypes(type); + } + + /** Returns true if this field is a scala immutable list. */ + public boolean isList() { + return Utils.isScalaList(type, procEnv); + } + + /** Returns true if this field is {@code scala.Option}. */ + public boolean isOption() { + return Utils.isScalaOption(type, procEnv); + } /** Returns true if the type of this field is Java primitive. */ - boolean isPrimitive(); + public boolean isPrimitive() { + return type.getKind().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}. * *

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(); + public boolean isExpression() { + return Utils.isSubtypeOfExpression(type, procEnv); + } /** Returns the type parameter, if this field is a generic type. Otherwise null. */ - default TypeElement getTypeParameter() { + public TypeElement getTypeParameter() { + if (type.getKind() == TypeKind.DECLARED) { + var declared = (DeclaredType) type; + var typeArgs = declared.getTypeArguments(); + if (typeArgs.isEmpty()) { + return null; + } else if (typeArgs.size() == 1) { + var typeArg = typeArgs.get(0); + return (TypeElement) procEnv.getTypeUtils().asElement(typeArg); + } else { + throw new IRProcessingException( + "Unexpected number of type arguments: " + typeArgs.size(), null); + } + } return null; } } 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 6c558f7e6d76..2aa537e1c43a 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 @@ -7,6 +7,7 @@ import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeMirror; import org.enso.runtime.parser.dsl.IRChild; import org.enso.runtime.parser.dsl.IRField; import org.enso.runtime.parser.processor.IRProcessingException; @@ -84,7 +85,7 @@ private Field processIrField(VariableElement param, IRField irFieldAnnot) { var isNullable = !irFieldAnnot.required(); var name = param.getSimpleName().toString(); if (isPrimitiveType(param)) { - return new PrimitiveField(param.asType(), name); + return new PrimitiveField(param.asType(), name, processingEnv); } else { // TODO: Assert that type is simple reference type - does not extend IR, is not generic return new ReferenceField(processingEnv, param.asType(), name, isNullable, false); @@ -95,37 +96,30 @@ private Field processIrChild(VariableElement param, IRChild irChildAnnot) { var name = param.getSimpleName().toString(); var type = getParamType(param); var isNullable = !irChildAnnot.required(); - if (Utils.isScalaList(type, processingEnv)) { - var typeArgElem = getGenericType(param); - return new ListField(name, param.asType(), typeArgElem); - } else if (Utils.isScalaOption(type, processingEnv)) { - var typeArgElem = getGenericType(param); - return new OptionField(name, param.asType(), typeArgElem); + if (Utils.isScalaList(param.asType(), processingEnv)) { + ensureTypeArgIsSubtypeOfIR(param.asType()); + return new ListField(name, param.asType(), processingEnv); + } else if (Utils.isScalaOption(param.asType(), processingEnv)) { + ensureTypeArgIsSubtypeOfIR(param.asType()); + return new OptionField(name, param.asType(), processingEnv); } else { if (!Utils.isSubtypeOfIR(type, processingEnv)) { throw new IRProcessingException( - "Constructor parameter annotated with @IRChild must be a subtype of IR interface", + "Constructor parameter annotated with @IRChild must be a subtype of IR interface. " + + "Actual type is: " + + type, param); } return new ReferenceField(processingEnv, param.asType(), name, isNullable, true); } } - /** - * Returns the generic type parameter. Assumes that the given {@code param} is declared with some - * generic type, and has exactly one type parameter. - * - * @param param the parameter to get the generic type from - * @return the generic type parameter - */ - private TypeElement getGenericType(VariableElement param) { - Utils.hardAssert(param.asType() instanceof DeclaredType); - var declaredType = (DeclaredType) param.asType(); + private void ensureTypeArgIsSubtypeOfIR(TypeMirror typeMirror) { + var declaredType = (DeclaredType) typeMirror; Utils.hardAssert(declaredType.getTypeArguments().size() == 1); var typeArg = declaredType.getTypeArguments().get(0); var typeArgElem = (TypeElement) processingEnv.getTypeUtils().asElement(typeArg); ensureIsSubtypeOfIR(typeArgElem); - return typeArgElem; } private static boolean isPrimitiveType(VariableElement ctorParam) { diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/ListField.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/ListField.java index 463c3de13a8f..1017e09683ce 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/ListField.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/ListField.java @@ -1,50 +1,12 @@ package org.enso.runtime.parser.processor.field; -import java.util.List; -import javax.lang.model.element.TypeElement; +import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.type.TypeMirror; /** 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; - private final TypeMirror type; - - /** - * @param name Name of the field - * @param typeArgElement TypeElement of the type argument. Must be subtype of IR. - */ - ListField(String name, TypeMirror type, TypeElement typeArgElement) { - this.name = name; - this.type = type; - this.typeArgElement = typeArgElement; - } - - @Override - public String getName() { - return name; - } - - @Override - public TypeMirror getType() { - return type; - } - - @Override - public String getSimpleTypeName() { - var typeArg = typeArgElement.getSimpleName().toString(); - return "List<" + typeArg + ">"; - } - - @Override - public TypeElement getTypeParameter() { - return typeArgElement; - } - - @Override - public List getImportedTypes() { - var typePar = typeArgElement.getQualifiedName().toString(); - return List.of("scala.collection.immutable.List", typePar); +final class ListField extends Field { + ListField(String name, TypeMirror type, ProcessingEnvironment procEnv) { + super(type, name, procEnv); } @Override @@ -61,14 +23,4 @@ public boolean isChild() { 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/field/OptionField.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/OptionField.java index e6ed5cee43b3..e3c846eaa0ee 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/OptionField.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/OptionField.java @@ -1,46 +1,13 @@ package org.enso.runtime.parser.processor.field; -import java.util.List; -import javax.lang.model.element.TypeElement; +import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.type.TypeMirror; /** Field representing {@code scala.Option} */ -public class OptionField implements Field { - private final String name; - private final TypeElement typeArgElement; - private final TypeMirror type; +public final class OptionField extends Field { - public OptionField(String name, TypeMirror type, TypeElement typeArgElement) { - this.name = name; - this.type = type; - this.typeArgElement = typeArgElement; - } - - @Override - public String getName() { - return name; - } - - @Override - public TypeMirror getType() { - return type; - } - - @Override - public String getSimpleTypeName() { - var typeArg = typeArgElement.getSimpleName().toString(); - return "Option<" + typeArg + ">"; - } - - @Override - public TypeElement getTypeParameter() { - return typeArgElement; - } - - @Override - public List getImportedTypes() { - var typePar = typeArgElement.getQualifiedName().toString(); - return List.of("scala.Option", typePar); + public OptionField(String name, TypeMirror type, ProcessingEnvironment procEnv) { + super(type, name, procEnv); } @Override @@ -57,14 +24,4 @@ public boolean isChild() { 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/field/PrimitiveField.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/PrimitiveField.java index f41d93dd6e57..9f7b7254dbc5 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/PrimitiveField.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/PrimitiveField.java @@ -1,30 +1,12 @@ package org.enso.runtime.parser.processor.field; +import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.type.TypeMirror; -final class PrimitiveField implements Field { +final class PrimitiveField extends 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 TypeMirror getType() { - return type; - } - - @Override - public String getSimpleTypeName() { - return type.toString(); + PrimitiveField(TypeMirror type, String name, ProcessingEnvironment procEnv) { + super(type, name, procEnv); } @Override @@ -41,9 +23,4 @@ public boolean isNullable() { 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/field/ReferenceField.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/field/ReferenceField.java index 9c1f8fe69cb2..7153f13bdf09 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 @@ -1,14 +1,9 @@ package org.enso.runtime.parser.processor.field; -import java.util.List; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.type.TypeMirror; -import org.enso.runtime.parser.processor.utils.Utils; -final class ReferenceField implements Field { - private final ProcessingEnvironment procEnv; - private final TypeMirror type; - private final String name; +final class ReferenceField extends Field { private final boolean nullable; private final boolean isChild; @@ -18,55 +13,18 @@ final class ReferenceField implements Field { String name, boolean nullable, boolean isChild) { - this.procEnv = procEnv; - this.type = type; - this.name = name; + super(type, name, procEnv); this.nullable = nullable; this.isChild = isChild; } - @Override - public String getName() { - return name; - } - - @Override - public TypeMirror getType() { - return type; - } - - @Override - public String getSimpleTypeName() { - return type.toString(); - } - - @Override - public List getImportedTypes() { - var typeElem = Utils.typeMirrorToElement(type); - if (typeElem != null) { - return List.of(typeElem.getQualifiedName().toString()); - } else { - return List.of(); - } - } - @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, procEnv); - } } diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java index 25b890dae2cf..7dca953cde38 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/utils/Utils.java @@ -2,6 +2,7 @@ import java.lang.annotation.Annotation; import java.util.ArrayDeque; +import java.util.ArrayList; import java.util.List; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -126,6 +127,26 @@ public static String simpleTypeName(TypeMirror typeMirror) { return typeMirror.toString(); } + /** + * Returns (a possibly empty) list of FQN that should be imported in order to use the given {@code + * typeMirror}. + * + * @return List of FQN, intended to be used in import statements. + */ + public static List getImportedTypes(TypeMirror typeMirror) { + var importedTypes = new ArrayList(); + if (typeMirror.getKind() == TypeKind.DECLARED) { + var declared = (DeclaredType) typeMirror; + var typeElem = (TypeElement) declared.asElement(); + var typeArgs = declared.getTypeArguments(); + importedTypes.add(typeElem.getQualifiedName().toString()); + for (var typeArg : typeArgs) { + importedTypes.addAll(getImportedTypes(typeArg)); + } + } + return importedTypes; + } + public static String indent(String code, int indentation) { return code.lines() .map(line -> " ".repeat(indentation) + line) @@ -146,19 +167,22 @@ public static TypeElement typeMirrorToElement(TypeMirror typeMirror) { return null; } - public static boolean isScalaList(TypeElement type, ProcessingEnvironment procEnv) { - var scalaListType = procEnv.getElementUtils().getTypeElement(SCALA_LIST); - return procEnv.getTypeUtils().isAssignable(type.asType(), scalaListType.asType()); - } - - public static boolean isScalaOption(TypeElement type, ProcessingEnvironment procEnv) { - var scalaListType = procEnv.getElementUtils().getTypeElement(SCALA_OPTION); - return procEnv.getTypeUtils().isAssignable(type.asType(), scalaListType.asType()); + public static boolean isScalaOption(TypeMirror type, ProcessingEnvironment procEnv) { + var elem = procEnv.getTypeUtils().asElement(type); + if (elem instanceof TypeElement typeElem) { + var optionType = procEnv.getElementUtils().getTypeElement(SCALA_OPTION); + return procEnv.getTypeUtils().isSameType(optionType.asType(), typeElem.asType()); + } + return false; } public static boolean isScalaList(TypeMirror type, ProcessingEnvironment procEnv) { - var scalaListType = procEnv.getElementUtils().getTypeElement(SCALA_LIST); - return procEnv.getTypeUtils().isSameType(type, scalaListType.asType()); + var elem = procEnv.getTypeUtils().asElement(type); + if (elem instanceof TypeElement typeElem) { + var listType = procEnv.getElementUtils().getTypeElement(SCALA_LIST); + return procEnv.getTypeUtils().isSameType(listType.asType(), typeElem.asType()); + } + return false; } /** From e443619f4ae64bd6092fea35b4bf02872fee2551 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 3 Jan 2025 17:57:34 +0100 Subject: [PATCH 178/195] Prefer simple type names --- .../org/enso/runtime/parser/processor/ClassField.java | 5 +++++ .../processor/methodgen/BuilderMethodGenerator.java | 4 ++-- .../processor/methodgen/DuplicateMethodGenerator.java | 8 ++++---- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ClassField.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ClassField.java index 755af4d2e7eb..cb2d18723ed6 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ClassField.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/ClassField.java @@ -3,6 +3,7 @@ import java.util.Objects; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.type.TypeMirror; +import org.enso.runtime.parser.processor.utils.Utils; /** Declared field in the generated class. */ public final class ClassField { @@ -53,6 +54,10 @@ public String getTypeName() { return type.toString(); } + public String getSimpleTypeName() { + return Utils.simpleTypeName(type); + } + public boolean isPrimitive() { return type.getKind().isPrimitive(); } 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 4ecdb560406a..53148cbb6efa 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 @@ -26,7 +26,7 @@ public String generateBuilder() { field -> { var initializer = field.initializer() != null ? " = " + field.initializer() : ""; return "private $type $name $initializer;" - .replace("$type", field.getTypeName()) + .replace("$type", field.getSimpleTypeName()) .replace("$name", field.name()) .replace("$initializer", initializer); }) @@ -43,7 +43,7 @@ public String generateBuilder() { } """ .replace("$fieldName", field.name()) - .replace("$fieldType", field.getTypeName())) + .replace("$fieldType", field.getSimpleTypeName())) .collect(Collectors.joining(System.lineSeparator())); // Validation code for all non-nullable user fields 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 65aff2d53d71..c9b01dd168e0 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 @@ -84,10 +84,10 @@ public String generateDuplicateMethodsCode() { idDuplicated = this.id; } """ - .replace("$locType", ctx.getLocationMetaField().getTypeName()) - .replace("$metaType", ctx.getPassDataMetaField().getTypeName()) - .replace("$diagType", ctx.getDiagnosticsMetaField().getTypeName()) - .replace("$idType", ctx.getIdMetaField().getTypeName()); + .replace("$locType", ctx.getLocationMetaField().getSimpleTypeName()) + .replace("$metaType", ctx.getPassDataMetaField().getSimpleTypeName()) + .replace("$diagType", ctx.getDiagnosticsMetaField().getSimpleTypeName()) + .replace("$idType", ctx.getIdMetaField().getSimpleTypeName()); sb.append(Utils.indent(duplicateMetaFieldsCode, 2)); sb.append(System.lineSeparator()); for (var metaVar : metaFields()) { From 1ceabfeb292f90e87838d3476685834960bc58e3 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 3 Jan 2025 18:25:23 +0100 Subject: [PATCH 179/195] Generate copy method --- .../processor/IRNodeClassGenerator.java | 6 ++ .../methodgen/CopyMethodGenerator.java | 84 +++++++++++++++++++ .../enso/compiler/core/ir/CallArgument.java | 17 ++-- 3 files changed, 98 insertions(+), 9 deletions(-) create mode 100644 engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/CopyMethodGenerator.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 7c96323accff..d4b670f3666d 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 @@ -9,6 +9,7 @@ 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; @@ -30,6 +31,7 @@ final class IRNodeClassGenerator { private final GeneratedClassContext generatedClassContext; private final DuplicateMethodGenerator duplicateMethodGenerator; + private final CopyMethodGenerator copyMethodGenerator; private final SetLocationMethodGenerator setLocationMethodGenerator; private final BuilderMethodGenerator builderMethodGenerator; private final MapExpressionsMethodGenerator mapExpressionsMethodGenerator; @@ -68,6 +70,7 @@ final class IRNodeClassGenerator { new GeneratedClassContext(className, userFields, processingEnv, processedClass); this.duplicateMethodGenerator = new DuplicateMethodGenerator(duplicateMethod, generatedClassContext); + this.copyMethodGenerator = new CopyMethodGenerator(generatedClassContext); this.builderMethodGenerator = new BuilderMethodGenerator(generatedClassContext); var mapExpressionsMethod = Utils.findMapExpressionsMethod(processedClass.getIrInterfaceElem(), processingEnv); @@ -120,6 +123,8 @@ public static Builder builder() { return new Builder(); } + $copyMethod + $userDefinedGetters $overrideIRMethods @@ -138,6 +143,7 @@ public static Builder builder() { .replace("$defaultCtor", defaultConstructor()) .replace("$ctorWithUserFields", constructorForUserFields()) .replace("$validateConstructor", validateConstructor()) + .replace("$copyMethod", copyMethodGenerator.generateMethodCode()) .replace("$userDefinedGetters", userDefinedGetters()) .replace("$overrideIRMethods", overrideIRMethods()) .replace("$mapExpressionsMethod", mapExpressions()) 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 new file mode 100644 index 000000000000..dbb15e08f321 --- /dev/null +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/CopyMethodGenerator.java @@ -0,0 +1,84 @@ +package org.enso.runtime.parser.processor.methodgen; + +import java.util.List; +import java.util.stream.Collectors; +import org.enso.runtime.parser.processor.GeneratedClassContext; + +public final class CopyMethodGenerator { + private final GeneratedClassContext ctx; + + public CopyMethodGenerator(GeneratedClassContext ctx) { + this.ctx = ctx; + } + + /** Generates the default {@code copy} method, with all the fields as parameters. */ + public String generateMethodCode() { + var docs = + """ + /** + * Creates a shallow copy of this IR element. If all of the given parameters are the + * same objects as fields, no copy is created and {@code this} is returned. + * + *

As opposed to the {@link #duplicate(boolean, boolean, boolean, boolean)} method, + * does not copy this IR element recursively. + */ + """; + var sb = new StringBuilder(); + sb.append(docs); + var paramList = String.join(", ", parameters()); + sb.append("public ") + .append(copyMethodRetType()) + .append(" copy(") + .append(paramList) + .append(") {") + .append(System.lineSeparator()); + sb.append(" ") + .append("boolean cond = ") + .append(cond()) + .append(";") + .append(System.lineSeparator()); + sb.append(" ").append("if (cond) {").append(System.lineSeparator()); + sb.append(" ") + .append("// One of the parameters is a different object than the field.") + .append(System.lineSeparator()); + sb.append(" ").append("var bldr = new Builder();").append(System.lineSeparator()); + for (var field : ctx.getAllFields()) { + sb.append(" ") + .append("bldr.") + .append(field.name()) + .append("(") + .append(field.name()) + .append(");") + .append(System.lineSeparator()); + } + sb.append(" ").append("return bldr.build();").append(System.lineSeparator()); + sb.append(" ").append("} else {").append(System.lineSeparator()); + sb.append(" ") + .append("return (") + .append(copyMethodRetType()) + .append(") this;") + .append(System.lineSeparator()); + sb.append(" ").append("}").append(System.lineSeparator()); + sb.append("}").append(System.lineSeparator()); + return sb.toString(); + } + + private String copyMethodRetType() { + return ctx.getProcessedClass().getClazz().getSimpleName().toString(); + } + + private List parameters() { + return ctx.getAllFields().stream() + .map(field -> field.getSimpleTypeName() + " " + field.name()) + .toList(); + } + + /** Condition expression if one of the parameters is a different object than the field. */ + private String cond() { + var inner = + ctx.getAllFields().stream() + .map(field -> "(" + field.name() + " != this." + field.name() + ")") + .collect(Collectors.joining(" || ")); + return "(" + inner + ")"; + } +} diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/CallArgument.java b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/CallArgument.java index fa63ac407368..8887fd6ed36d 100644 --- a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/CallArgument.java +++ b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/CallArgument.java @@ -62,15 +62,14 @@ public Specified( } public Specified copy(Expression value) { - if (value != this.value()) { - var duplicated = - new Specified(name(), value, isSynthetic(), identifiedLocation(), passData()); - duplicated.id = id; - duplicated.diagnostics = diagnostics; - return duplicated; - } else { - return this; - } + return copy( + this.diagnostics, + this.passData, + this.location, + this.id, + this.name(), + value, + this.isSynthetic()); } } } From 0e15befd717070c938a620be2aa99f38414164a4 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 3 Jan 2025 18:39:39 +0100 Subject: [PATCH 180/195] Generate just a single constructor matching subtype --- .../processor/test/TestIRProcessorInline.java | 85 +++++++++++++------ .../processor/GeneratedClassContext.java | 10 --- .../processor/IRNodeClassGenerator.java | 41 ++------- .../enso/compiler/core/ir/CallArgument.java | 2 +- .../java/org/enso/compiler/core/ir/Empty.java | 2 +- 5 files changed, 72 insertions(+), 68 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 573d46e4835b..e0866a4538e2 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 @@ -206,9 +206,12 @@ public JName() {} "Generate class has protected constructor", genClass, containsString("protected JNameGen")); } - /** The default generated protected constructor has meta parameters */ + /** + * The default generated protected constructor has the same signature as the constructor in + * subtype annotated with {@link org.enso.runtime.parser.dsl.GenerateFields} + */ @Test - public void generatedClassHasConstructor_WithMetaFields() { + public void generatedClassHasConstructorMatchingSubtype_Empty() { var src = """ import org.enso.runtime.parser.dsl.GenerateIR; @@ -217,42 +220,76 @@ public void generatedClassHasConstructor_WithMetaFields() { @GenerateIR public final class JName extends JNameGen { @GenerateFields - public JName() {} + public JName() { + super(); + } } """; - var genClass = generatedClass("JName", src); - assertThat(genClass, containsString("class JNameGen")); - assertThat( - "Generate class has protected constructor with meta parameters", - genClass, - containsString( - "protected JNameGen(DiagnosticStorage diagnostics, MetadataStorage passData," - + " IdentifiedLocation location, UUID id)")); + var compilation = compile("JName", src); + CompilationSubject.assertThat(compilation).succeeded(); } - /** - * The second generated protected constructor has user fields as parameters. In this case, it will - * be an empty constructor. - */ @Test - public void generatedClassHasConstructor_WithUserFields() { + public void generatedClassHasConstructorMatchingSubtype_UserFields() { var src = """ import org.enso.runtime.parser.dsl.GenerateIR; import org.enso.runtime.parser.dsl.GenerateFields; + import org.enso.runtime.parser.dsl.IRField; @GenerateIR public final class JName extends JNameGen { @GenerateFields - public JName() {} + public JName(@IRField boolean suspended, @IRField String name) { + super(suspended, name); + } } """; - var genClass = generatedClass("JName", src); - assertThat(genClass, containsString("class JNameGen")); - assertThat( - "Generate class has protected constructor with user fields as parameters", - genClass, - containsString("protected JNameGen()")); + var compilation = compile("JName", src); + CompilationSubject.assertThat(compilation).succeeded(); + } + + @Test + public void generatedClassHasConstructorMatchingSubtype_MetaFields() { + var src = + """ + import org.enso.runtime.parser.dsl.GenerateIR; + import org.enso.runtime.parser.dsl.GenerateFields; + import org.enso.compiler.core.ir.DiagnosticStorage; + import org.enso.compiler.core.ir.IdentifiedLocation; + + @GenerateIR + public final class JName extends JNameGen { + @GenerateFields + public JName(DiagnosticStorage diag, IdentifiedLocation loc) { + super(diag, loc); + } + } + """; + var compilation = compile("JName", src); + CompilationSubject.assertThat(compilation).succeeded(); + } + + @Test + public void generatedClassHasConstructorMatchingSubtype_UserFieldsAndMetaFields() { + var src = + """ + import org.enso.runtime.parser.dsl.GenerateIR; + import org.enso.runtime.parser.dsl.GenerateFields; + import org.enso.runtime.parser.dsl.IRField; + import org.enso.compiler.core.ir.DiagnosticStorage; + import org.enso.compiler.core.ir.IdentifiedLocation; + + @GenerateIR + public final class JName extends JNameGen { + @GenerateFields + public JName(DiagnosticStorage diag, IdentifiedLocation loc, @IRField boolean suspended) { + super(diag, loc, suspended); + } + } + """; + var compilation = compile("JName", src); + CompilationSubject.assertThat(compilation).succeeded(); } @Test @@ -392,7 +429,7 @@ public void annotatedConstructor_CanHaveMetaParameters() { public final class JName extends JNameGen { @GenerateFields public JName(MetadataStorage passData) { - super(); + super(passData); } } """; 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 02003112d9aa..71a02b5ab702 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 @@ -123,16 +123,6 @@ public ClassField getIdMetaField() { return idMetaField; } - /** - * Returns all constructor parameters for the default constructor of the generated class. - * Including meta parameters. - * - * @return - */ - public List getSuperclassConstructorParameters() { - return constructorParameters; - } - public List getUserFields() { return userFields; } 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 d4b670f3666d..67959070fe55 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 @@ -5,7 +5,6 @@ import java.util.Set; import java.util.stream.Collectors; import javax.annotation.processing.ProcessingEnvironment; -import org.enso.runtime.parser.processor.GeneratedClassContext.Parameter; import org.enso.runtime.parser.processor.field.Field; import org.enso.runtime.parser.processor.field.FieldCollector; import org.enso.runtime.parser.processor.methodgen.BuilderMethodGenerator; @@ -115,8 +114,6 @@ String classBody() { $defaultCtor - $ctorWithUserFields - $validateConstructor public static Builder builder() { @@ -141,7 +138,6 @@ public static Builder builder() { """ .replace("$fields", fieldsCode()) .replace("$defaultCtor", defaultConstructor()) - .replace("$ctorWithUserFields", constructorForUserFields()) .replace("$validateConstructor", validateConstructor()) .replace("$copyMethod", copyMethodGenerator.generateMethodCode()) .replace("$userDefinedGetters", userDefinedGetters()) @@ -191,34 +187,15 @@ private String defaultConstructor() { var docs = """ /** - * Default constructor for all the fields inside the generated class - both meta and - * user defined. - */ - """; - var ctorCode = - constructorForFields(generatedClassContext.getSuperclassConstructorParameters(), List.of()); - return docs + ctorCode; - } - - /** - * Returns string representation of the second protected constructor of the generated class. This - * constructor accepts only user defined fields as parameters. Meta fields are initialized to - * null. - */ - private String constructorForUserFields() { - var docs = - """ - /** - * Second generated constructor only for user-defined fields. All the meta fields will - * be initialized to the default value ({@code null}). + * Default constructor matching the signature of subtype's constructor. + * The rest of the fields not specified as parameters to this constructor are initialized to + * their default value. */ """; - var userFieldsAsParameters = - generatedClassContext.getUserFields().stream() - .map(field -> new Parameter(field.getType(), field.getName())) - .toList(); - var ctorCode = - constructorForFields(userFieldsAsParameters, generatedClassContext.getMetaFields()); + var subclassCtorParams = generatedClassContext.getSubclassConstructorParameters(); + var allFields = generatedClassContext.getAllFields(); + var diff = Utils.diff(allFields, subclassCtorParams); + var ctorCode = constructorForFields(subclassCtorParams, diff); return docs + ctorCode; } @@ -231,7 +208,7 @@ private String constructorForUserFields() { * Can be empty list. */ private String constructorForFields( - List parameters, List initializeToNull) { + List parameters, List initializeToNull) { Utils.hardAssert( !(parameters.isEmpty() && initializeToNull.isEmpty()), "At least one of the list must be non empty"); @@ -242,7 +219,7 @@ private String constructorForFields( .map( consParam -> "$consType $consName" - .replace("$consType", consParam.simpleTypeName()) + .replace("$consType", consParam.getSimpleTypeName()) .replace("$consName", consParam.name())) .collect(Collectors.joining(", ")); sb.append(inParens).append(") {").append(System.lineSeparator()); diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/CallArgument.java b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/CallArgument.java index 8887fd6ed36d..fa4d8bd054c1 100644 --- a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/CallArgument.java +++ b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/CallArgument.java @@ -50,7 +50,7 @@ public Specified( @IRField boolean isSynthetic, IdentifiedLocation identifiedLocation, MetadataStorage passData) { - super(null, passData, identifiedLocation, null, name, value, isSynthetic); + super(name, value, isSynthetic, identifiedLocation, passData); } public Specified( 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 566f49dc32aa..ce34726e2a4e 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 @@ -7,7 +7,7 @@ public final class Empty extends EmptyGen { @GenerateFields public Empty(IdentifiedLocation identifiedLocation, MetadataStorage passData) { - super(null, passData, identifiedLocation, null); + super(identifiedLocation, passData); } public Empty(IdentifiedLocation identifiedLocation) { From 6451745b05f64ead49926378136c5ae652a3eae3 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 3 Jan 2025 19:17:43 +0100 Subject: [PATCH 181/195] Implement Specified.showCode --- .../java/org/enso/compiler/core/ir/CallArgument.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/CallArgument.java b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/CallArgument.java index fa4d8bd054c1..8d65af13cbc6 100644 --- a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/CallArgument.java +++ b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/CallArgument.java @@ -71,5 +71,14 @@ public Specified copy(Expression value) { value, this.isSynthetic()); } + + @Override + public String showCode(int indent) { + if (name().isDefined()) { + return "(" + name().get().showCode(indent) + " = " + value().showCode(indent) + ")"; + } else { + return value().showCode(indent); + } + } } } From 6fa233a19a89a72239b7ff725a32e09aad783b68 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 3 Jan 2025 19:26:37 +0100 Subject: [PATCH 182/195] passData is never null --- .../src/main/java/org/enso/compiler/core/ir/Empty.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ce34726e2a4e..46a833939618 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 @@ -11,6 +11,6 @@ public Empty(IdentifiedLocation identifiedLocation, MetadataStorage passData) { } public Empty(IdentifiedLocation identifiedLocation) { - this(identifiedLocation, null); + this(identifiedLocation, new MetadataStorage()); } } From 7076c8a70ba00c7e3947d32cf1a0e6ee8ccf43ec Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 6 Jan 2025 10:58:37 +0100 Subject: [PATCH 183/195] Generated toString method returns one-line string --- .../processor/methodgen/ToStringMethodGenerator.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/ToStringMethodGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/ToStringMethodGenerator.java index f5e0b116f33f..b0bae908d3a3 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/ToStringMethodGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/ToStringMethodGenerator.java @@ -12,10 +12,17 @@ public ToStringMethodGenerator(GeneratedClassContext ctx) { } public String generateMethodCode() { + var docs = + """ + /** + * Returns a one-line string representation of this IR object. + */ + """; var sb = new StringBuilder(); + sb.append(docs); sb.append("@Override").append(System.lineSeparator()); sb.append("public String toString() {").append(System.lineSeparator()); - sb.append(" return ").append(System.lineSeparator()); + sb.append(" String ret = ").append(System.lineSeparator()); sb.append(" ").append(quoted(className())).append(System.lineSeparator()); sb.append(" + ").append(quoted("(")).append(System.lineSeparator()); var fieldsStrRepr = @@ -24,6 +31,8 @@ public String generateMethodCode() { .collect(Collectors.joining(" + \", \" + " + System.lineSeparator())); sb.append(" + ").append(fieldsStrRepr).append(System.lineSeparator()); sb.append(" + ").append(quoted(")")).append(";").append(System.lineSeparator()); + sb.append(" return ret.replaceAll(System.lineSeparator(), \" \");") + .append(System.lineSeparator()); sb.append("}").append(System.lineSeparator()); return sb.toString(); } From 2586bbef6b2d219a13ddbb7e294a317394194214 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 6 Jan 2025 11:00:21 +0100 Subject: [PATCH 184/195] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b5e023d18db..19617904c38f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,11 +18,13 @@ previously allowed to use spaces in the argument definition without parentheses. [This is now a syntax error.][11856] - Symetric, transitive and reflexive [equality for intersection types][11897] +- [IR definitions are generated by an annotation processor][11770] [11777]: https://github.com/enso-org/enso/pull/11777 [11600]: https://github.com/enso-org/enso/pull/11600 [11856]: https://github.com/enso-org/enso/pull/11856 [11897]: https://github.com/enso-org/enso/pull/11897 +[11770]: https://github.com/enso-org/enso/pull/11770 # Next Release From 4226c2e5dd1eec57bfa10dcf3850e58d789a6119 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 6 Jan 2025 12:23:19 +0100 Subject: [PATCH 185/195] Add generated sources to runtime-parser/Compile/doc/sources. This fixes publishM2 task --- build.sbt | 21 +++++++++++++++++++++ project/PackageListPlugin.scala | 8 +++++--- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/build.sbt b/build.sbt index 274c9b2cf458..10fe915ffba9 100644 --- a/build.sbt +++ b/build.sbt @@ -13,6 +13,8 @@ import src.main.scala.licenses.{ SBTDistributionComponent } +import scala.collection.mutable.ArrayBuffer + // This import is unnecessary, but bit adds a proper code completion features // to IntelliJ. import JPMSPlugin.autoImport._ @@ -3208,6 +3210,7 @@ lazy val `runtime-benchmarks` = lazy val `runtime-parser` = (project in file("engine/runtime-parser")) .enablePlugins(JPMSPlugin) + .enablePlugins(PackageListPlugin) .settings( scalaModuleDependencySetting, mixedJavaScalaProjectSetting, @@ -3218,6 +3221,24 @@ lazy val `runtime-parser` = crossPaths := false, frgaalJavaCompilerSetting, annotationProcSetting, + // Explicitly list generated sources so that `scaladoc` knows where to find them. + // This is necessary because some Java classes extend from the generated classes. + // By default, `scaladoc` is unaware of the generated Java sources and would fail. + Compile / doc / sources ++= { + val managedSrcDir = (Compile / sourceManaged).value + val pkgs = (Compile / packages).value + val generatedJavaSrcs = new ArrayBuffer[File]() + pkgs.foreach { pkg => + val pathPart = pkg.replace(".", File.separator) + val dir = managedSrcDir.toPath.resolve(pathPart) + IO.listFiles(dir.toFile).foreach { file => + if (file.getName.endsWith(".java")) { + generatedJavaSrcs += file + } + } + } + generatedJavaSrcs.toList + }, commands += WithDebugCommand.withDebug, fork := true, libraryDependencies ++= Seq( diff --git a/project/PackageListPlugin.scala b/project/PackageListPlugin.scala index 308e16d17547..298bc6644f36 100644 --- a/project/PackageListPlugin.scala +++ b/project/PackageListPlugin.scala @@ -61,9 +61,11 @@ object PackageListPlugin extends AutoPlugin { if (file.isFile) { val fPath = file.toPath val relativePath = rootDir.relativize(fPath) - val pkgPart = relativePath.subpath(0, relativePath.getNameCount - 1) - val pkg = pkgPart.toString.replace(File.separator, ".") - pkgs.add(pkg) + if (relativePath.getNameCount > 1) { + val pkgPart = relativePath.subpath(0, relativePath.getNameCount - 1) + val pkg = pkgPart.toString.replace(File.separator, ".") + pkgs.add(pkg) + } } else if (file.isDirectory) { listOfPackages(rootDir, file, pkgs) } From 98476fa6ef3d45fed8947308681ada9eb29014c9 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 6 Jan 2025 12:23:39 +0100 Subject: [PATCH 186/195] Do not link duplicate method in javadoc. scaladoc fails on that. --- .../runtime/parser/processor/methodgen/CopyMethodGenerator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 dbb15e08f321..79ff2971eedf 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 @@ -19,7 +19,7 @@ public String generateMethodCode() { * Creates a shallow copy of this IR element. If all of the given parameters are the * same objects as fields, no copy is created and {@code this} is returned. * - *

As opposed to the {@link #duplicate(boolean, boolean, boolean, boolean)} method, + *

As opposed to the {@code duplicate} method, * does not copy this IR element recursively. */ """; From 291e593f1802194154de7576ff1b71a199f332be Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 6 Jan 2025 13:56:43 +0100 Subject: [PATCH 187/195] Run sbt clean before publishing M2 --- .github/workflows/enso4igv.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/enso4igv.yml b/.github/workflows/enso4igv.yml index 0437224a1360..aae9fea3f566 100644 --- a/.github/workflows/enso4igv.yml +++ b/.github/workflows/enso4igv.yml @@ -128,6 +128,9 @@ jobs: java-version: "21" distribution: "graalvm-community" + - name: Clean sbt + run: sbt clean + - name: Publish Enso Libraries to Local Maven Repository run: sbt publishM2 From dd5efcd2d928673e08116e8ec7c39bc96a0ec660 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 6 Jan 2025 15:36:54 +0100 Subject: [PATCH 188/195] [WIP] Print sbt config --- .github/workflows/enso4igv.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/enso4igv.yml b/.github/workflows/enso4igv.yml index aae9fea3f566..1b21a524a476 100644 --- a/.github/workflows/enso4igv.yml +++ b/.github/workflows/enso4igv.yml @@ -131,6 +131,9 @@ jobs: - name: Clean sbt run: sbt clean + - name: WIP Print sbt config + run: sbt 'print runtime-parser/Compile/doc/sources' + - name: Publish Enso Libraries to Local Maven Repository run: sbt publishM2 From cdcdb48b687f700567b853b9ba8cc83050095811 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Wed, 8 Jan 2025 13:58:24 +0100 Subject: [PATCH 189/195] Revert "[WIP] Print sbt config" This reverts commit dd5efcd2d928673e08116e8ec7c39bc96a0ec660. --- .github/workflows/enso4igv.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/enso4igv.yml b/.github/workflows/enso4igv.yml index 1b21a524a476..aae9fea3f566 100644 --- a/.github/workflows/enso4igv.yml +++ b/.github/workflows/enso4igv.yml @@ -131,9 +131,6 @@ jobs: - name: Clean sbt run: sbt clean - - name: WIP Print sbt config - run: sbt 'print runtime-parser/Compile/doc/sources' - - name: Publish Enso Libraries to Local Maven Repository run: sbt publishM2 From e0f05370c0832a52682d95c5c67393af6cea5beb Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Wed, 8 Jan 2025 13:58:28 +0100 Subject: [PATCH 190/195] Revert "Run sbt clean before publishing M2" This reverts commit 291e593f1802194154de7576ff1b71a199f332be. --- .github/workflows/enso4igv.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/enso4igv.yml b/.github/workflows/enso4igv.yml index aae9fea3f566..0437224a1360 100644 --- a/.github/workflows/enso4igv.yml +++ b/.github/workflows/enso4igv.yml @@ -128,9 +128,6 @@ jobs: java-version: "21" distribution: "graalvm-community" - - name: Clean sbt - run: sbt clean - - name: Publish Enso Libraries to Local Maven Repository run: sbt publishM2 From 65701c15c0f4109fc16a26141f7e2f03a8e8738b Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Wed, 8 Jan 2025 13:59:12 +0100 Subject: [PATCH 191/195] Revert "Add generated sources to runtime-parser/Compile/doc/sources." This reverts commit 4226c2e5dd1eec57bfa10dcf3850e58d789a6119. --- build.sbt | 21 --------------------- project/PackageListPlugin.scala | 8 +++----- 2 files changed, 3 insertions(+), 26 deletions(-) diff --git a/build.sbt b/build.sbt index 10fe915ffba9..274c9b2cf458 100644 --- a/build.sbt +++ b/build.sbt @@ -13,8 +13,6 @@ import src.main.scala.licenses.{ SBTDistributionComponent } -import scala.collection.mutable.ArrayBuffer - // This import is unnecessary, but bit adds a proper code completion features // to IntelliJ. import JPMSPlugin.autoImport._ @@ -3210,7 +3208,6 @@ lazy val `runtime-benchmarks` = lazy val `runtime-parser` = (project in file("engine/runtime-parser")) .enablePlugins(JPMSPlugin) - .enablePlugins(PackageListPlugin) .settings( scalaModuleDependencySetting, mixedJavaScalaProjectSetting, @@ -3221,24 +3218,6 @@ lazy val `runtime-parser` = crossPaths := false, frgaalJavaCompilerSetting, annotationProcSetting, - // Explicitly list generated sources so that `scaladoc` knows where to find them. - // This is necessary because some Java classes extend from the generated classes. - // By default, `scaladoc` is unaware of the generated Java sources and would fail. - Compile / doc / sources ++= { - val managedSrcDir = (Compile / sourceManaged).value - val pkgs = (Compile / packages).value - val generatedJavaSrcs = new ArrayBuffer[File]() - pkgs.foreach { pkg => - val pathPart = pkg.replace(".", File.separator) - val dir = managedSrcDir.toPath.resolve(pathPart) - IO.listFiles(dir.toFile).foreach { file => - if (file.getName.endsWith(".java")) { - generatedJavaSrcs += file - } - } - } - generatedJavaSrcs.toList - }, commands += WithDebugCommand.withDebug, fork := true, libraryDependencies ++= Seq( diff --git a/project/PackageListPlugin.scala b/project/PackageListPlugin.scala index 298bc6644f36..308e16d17547 100644 --- a/project/PackageListPlugin.scala +++ b/project/PackageListPlugin.scala @@ -61,11 +61,9 @@ object PackageListPlugin extends AutoPlugin { if (file.isFile) { val fPath = file.toPath val relativePath = rootDir.relativize(fPath) - if (relativePath.getNameCount > 1) { - val pkgPart = relativePath.subpath(0, relativePath.getNameCount - 1) - val pkg = pkgPart.toString.replace(File.separator, ".") - pkgs.add(pkg) - } + val pkgPart = relativePath.subpath(0, relativePath.getNameCount - 1) + val pkg = pkgPart.toString.replace(File.separator, ".") + pkgs.add(pkg) } else if (file.isDirectory) { listOfPackages(rootDir, file, pkgs) } From 5de98f379a3ec90450f367011530e48d328dd2b5 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Wed, 8 Jan 2025 14:00:15 +0100 Subject: [PATCH 192/195] Don't publish doc and src jars to M2 --- build.sbt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build.sbt b/build.sbt index 274c9b2cf458..4d1a5f00cbd9 100644 --- a/build.sbt +++ b/build.sbt @@ -3215,6 +3215,8 @@ lazy val `runtime-parser` = version := mavenUploadVersion, javadocSettings, publish / skip := false, + packageDoc / publishArtifact := false, + packageSrc / publishArtifact := false, crossPaths := false, frgaalJavaCompilerSetting, annotationProcSetting, From 176292931faab77507736a19100d13536f006104 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Wed, 8 Jan 2025 14:40:15 +0100 Subject: [PATCH 193/195] Add common settings to the projects published to local Maven repo --- build.sbt | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/build.sbt b/build.sbt index 4d1a5f00cbd9..36a8285e0356 100644 --- a/build.sbt +++ b/build.sbt @@ -913,10 +913,9 @@ lazy val `syntax-rust-definition` = project .enablePlugins(JPMSPlugin) .configs(Test) .settings( - version := mavenUploadVersion, - Compile / exportJars := true, javadocSettings, - publish / skip := false, + publishLocalSetting, + Compile / exportJars := true, autoScalaLibrary := false, crossPaths := false, libraryDependencies ++= Seq( @@ -2029,13 +2028,12 @@ lazy val `ydoc-server` = project lazy val `persistance` = (project in file("lib/java/persistance")) .enablePlugins(JPMSPlugin) .settings( - version := mavenUploadVersion, Test / fork := true, commands += WithDebugCommand.withDebug, frgaalJavaCompilerSetting, annotationProcSetting, javadocSettings, - publish / skip := false, + publishLocalSetting, autoScalaLibrary := false, crossPaths := false, Compile / javacOptions := ((Compile / javacOptions).value), @@ -2055,9 +2053,8 @@ lazy val `persistance` = (project in file("lib/java/persistance")) lazy val `persistance-dsl` = (project in file("lib/java/persistance-dsl")) .settings( - version := mavenUploadVersion, frgaalJavaCompilerSetting, - publish / skip := false, + publishLocalSetting, autoScalaLibrary := false, crossPaths := false, javadocSettings, @@ -2584,6 +2581,18 @@ lazy val javaMethodParametersSetting: SettingsDefinition = Seq( javacOptions += "-parameters" ) +/** Projects that are published to the local Maven repository via `publishM2` task + * should incorporate these settings. We need to publish some projects to the local + * Maven repo, because they are dependencies of some external projects like `enso4igv`. + * By default, all projects are set `publish / skip := true`. + */ +lazy val publishLocalSetting: SettingsDefinition = Seq( + version := mavenUploadVersion, + publish / skip := false, + packageDoc / publishArtifact := false, + packageSrc / publishArtifact := false +) + def customFrgaalJavaCompilerSettings(targetJdk: String) = { // There might be slightly different Frgaal compiler configuration for // both Compile and Test configurations @@ -3212,11 +3221,8 @@ lazy val `runtime-parser` = scalaModuleDependencySetting, mixedJavaScalaProjectSetting, javaMethodParametersSetting, - version := mavenUploadVersion, + publishLocalSetting, javadocSettings, - publish / skip := false, - packageDoc / publishArtifact := false, - packageSrc / publishArtifact := false, crossPaths := false, frgaalJavaCompilerSetting, annotationProcSetting, @@ -3263,7 +3269,8 @@ lazy val `runtime-parser-dsl` = .enablePlugins(JPMSPlugin) .settings( frgaalJavaCompilerSetting, - javaMethodParametersSetting + javaMethodParametersSetting, + publishLocalSetting ) lazy val `runtime-parser-processor-tests` = @@ -3296,6 +3303,7 @@ lazy val `runtime-parser-processor` = .settings( frgaalJavaCompilerSetting, javaMethodParametersSetting, + publishLocalSetting, Compile / internalModuleDependencies := Seq( (`runtime-parser-dsl` / Compile / exportedModule).value ) From 4437f783ff134038bbb80f65acb139c1ef23a3ff Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Wed, 8 Jan 2025 17:40:41 +0100 Subject: [PATCH 194/195] toStringMethod converts return value to single line --- .../methodgen/ToStringMethodGenerator.java | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/ToStringMethodGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/ToStringMethodGenerator.java index b0bae908d3a3..5a4c8f6afbb5 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/ToStringMethodGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/ToStringMethodGenerator.java @@ -31,12 +31,27 @@ public String generateMethodCode() { .collect(Collectors.joining(" + \", \" + " + System.lineSeparator())); sb.append(" + ").append(fieldsStrRepr).append(System.lineSeparator()); sb.append(" + ").append(quoted(")")).append(";").append(System.lineSeparator()); - sb.append(" return ret.replaceAll(System.lineSeparator(), \" \");") - .append(System.lineSeparator()); + sb.append(" return toSingleLine(ret);").append(System.lineSeparator()); sb.append("}").append(System.lineSeparator()); + + sb.append(toSingleLineMethod()).append(System.lineSeparator()); return sb.toString(); } + private String toSingleLineMethod() { + return """ + private static String toSingleLine(String str) { + String[] lines = str.trim().split(System.lineSeparator()); + var body = new StringBuilder(); + for (int i = 1; i < lines.length - 1; i++) { + body.append(lines[i].trim()); + body.append(" "); + } + return lines[0] + body.toString().trim() + lines[lines.length - 1]; + } + """; + } + private String className() { var clazz = ctx.getProcessedClass().getClazz(); var enclosingElem = clazz.getEnclosingElement(); From 3a9b098bf5f430f37aa7d08009eaa3b74f06a47f Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Thu, 9 Jan 2025 11:38:35 +0100 Subject: [PATCH 195/195] fix: toStringMethod converts return value to single line --- .../parser/processor/IRNodeClassGenerator.java | 1 + .../processor/methodgen/ToStringMethodGenerator.java | 12 ++++-------- 2 files changed, 5 insertions(+), 8 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 67959070fe55..8df6dc6d5f0d 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 @@ -44,6 +44,7 @@ final class IRNodeClassGenerator { "java.util.ArrayList", "java.util.function.Function", "java.util.Objects", + "java.util.stream.Collectors", "org.enso.compiler.core.Identifier", "org.enso.compiler.core.IR", "org.enso.compiler.core.ir.DiagnosticStorage", diff --git a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/ToStringMethodGenerator.java b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/ToStringMethodGenerator.java index 5a4c8f6afbb5..9a99acbc6dba 100644 --- a/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/ToStringMethodGenerator.java +++ b/engine/runtime-parser-processor/src/main/java/org/enso/runtime/parser/processor/methodgen/ToStringMethodGenerator.java @@ -40,15 +40,11 @@ public String generateMethodCode() { private String toSingleLineMethod() { return """ - private static String toSingleLine(String str) { - String[] lines = str.trim().split(System.lineSeparator()); - var body = new StringBuilder(); - for (int i = 1; i < lines.length - 1; i++) { - body.append(lines[i].trim()); - body.append(" "); + private static String toSingleLine(String str) { + return str.trim().lines() + .map(s -> s.trim()) + .collect(Collectors.joining(" ")); } - return lines[0] + body.toString().trim() + lines[lines.length - 1]; - } """; }