Skip to content

Commit

Permalink
Generate IR definitions by annotation processor - 1st step (#11770)
Browse files Browse the repository at this point in the history
The overall description is the same as in #11267, but this approach tries to generate super classes as suggested in https://github.com/enso-org/enso/pull/11267/files#r1869342527

# Important Notes
Apart from the annotation processor implementation and tests, [CallArgument.Specified](https://github.com/enso-org/enso/blob/80509a1f2420e97b3879fc8e925a9a863373826d/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/CallArgument.java) was migrated to the new approach from Scala case class. I uploaded its generated class code in [this gist](https://gist.github.com/Akirathan/0e43de77fe91da399406c1ee7ac2cecd) so you can see it without compilation.
  • Loading branch information
Akirathan authored Jan 9, 2025
1 parent a617492 commit 4ace1b2
Show file tree
Hide file tree
Showing 48 changed files with 3,995 additions and 269 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,15 @@
- [Native libraries of projects can be added to `polyglot/lib` directory][11874]
- [Redo stack is no longer lost when interacting with text literals][11908].
- 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
[11874]: https://github.com/enso-org/enso/pull/11874
[11908]: https://github.com/enso-org/enso/pull/11908
[11897]: https://github.com/enso-org/enso/pull/11897
[11770]: https://github.com/enso-org/enso/pull/11770

# Enso 2024.5

Expand Down
104 changes: 94 additions & 10 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,10 @@ lazy val enso = (project in file("."))
`runtime-and-langs`,
`runtime-benchmarks`,
`runtime-compiler`,
`runtime-parser`,
`runtime-parser-dsl`,
`runtime-parser-processor`,
`runtime-parser-processor-tests`,
`runtime-language-arrow`,
`runtime-language-epb`,
`runtime-instrument-common`,
Expand Down Expand Up @@ -914,10 +918,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(
Expand Down Expand Up @@ -2024,13 +2027,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),
Expand All @@ -2050,9 +2052,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,
Expand Down Expand Up @@ -2572,6 +2573,25 @@ 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"
)

/** 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
Expand Down Expand Up @@ -3197,9 +3217,9 @@ lazy val `runtime-parser` =
.settings(
scalaModuleDependencySetting,
mixedJavaScalaProjectSetting,
version := mavenUploadVersion,
javaMethodParametersSetting,
publishLocalSetting,
javadocSettings,
publish / skip := false,
crossPaths := false,
frgaalJavaCompilerSetting,
annotationProcSetting,
Expand All @@ -3214,14 +3234,78 @@ 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 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",
"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
(`persistance` / 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"))
.enablePlugins(JPMSPlugin)
.settings(
frgaalJavaCompilerSetting,
javaMethodParametersSetting,
publishLocalSetting
)

lazy val `runtime-parser-processor-tests` =
(project in file("engine/runtime-parser-processor-tests"))
.settings(
inConfig(Compile)(truffleRunOptionsSettings),
frgaalJavaCompilerSetting,
javaMethodParametersSetting,
commands += WithDebugCommand.withDebug,
annotationProcSetting,
Compile / javacOptions ++= Seq(
"-processor",
"org.enso.runtime.parser.processor.IRProcessor"
),
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,
"org.scalatest" %% "scalatest" % scalatestVersion % Test
)
)
.dependsOn(`runtime-parser-processor`)
.dependsOn(`runtime-parser`)

lazy val `runtime-parser-processor` =
(project in file("engine/runtime-parser-processor"))
.enablePlugins(JPMSPlugin)
.settings(
frgaalJavaCompilerSetting,
javaMethodParametersSetting,
publishLocalSetting,
Compile / internalModuleDependencies := Seq(
(`runtime-parser-dsl` / Compile / exportedModule).value
)
)
.dependsOn(`runtime-parser-dsl`)

lazy val `runtime-compiler` =
(project in file("engine/runtime-compiler"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = new CallArgument.Specified(None, Empty(null), false, null)
val left = new Empty(null)
val right = new Empty(null)
val rightArg = new CallArgument.Specified(None, new Empty(null), false, null)

val (operator, operatorFn) = genOprAndFn(opName, left, right)

Expand All @@ -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 = new CallArgument.Specified(
None,
Empty(identifiedLocation = null),
new Empty(null),
false,
identifiedLocation = null
)
Expand Down
3 changes: 3 additions & 0 deletions engine/runtime-parser-dsl/src/main/java/module-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module org.enso.runtime.parser.dsl {
exports org.enso.runtime.parser.dsl;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
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 <b>fields</b>will 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}.
*
* <h2>Fields</h2>
*
* The generated class will contain 4 <b>meta</b> fields that are required to be present inside
* every IR element:
*
* <ul>
* <li>{@code private DiagnosticStorage diagnostics}
* <li>{@code private MetadataStorage passData}
* <li>{@code private IdentifiedLocation location}
* <li>{@code private UUID id}
* </ul>
*
* Apart from these <b>meta</b> fields, the generated class will also contain <b>user-defined</b>
* 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:
*
* <ul>
* <li>Any reference, or primitive type annotated with {@link IRField}
* <li>A subtype of {@code org.enso.compiler.ir.IR} annotated with {@link IRChild}
* <li>One of the <emph>meta</emph> types mentioned above
* </ul>
*
* <p>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}.
*
* <p>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.
*
* <p>For a constructor parameter of a meta type, there will be no user-defined field generated, as
* the meta fields are always generated.
*
* <p>Other types of constructor parameters are forbidden.
*/
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.CONSTRUCTOR)
public @interface GenerateFields {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
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 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.
*
* <p>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.
*
* <p>The class can be enclosed (nested inside) an interface.
*
* <p>The class must contain a single constructor annotated with {@link GenerateFields}.
*/
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface GenerateIR {

/**
* 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
* implement {@code org.enso.compiler.core.IR} by default.
*/
Class[] interfaces() default {};
}
Original file line number Diff line number Diff line change
@@ -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;

/**
* Constructor parameter annotated with this annotation will be represented as a child field in the
* 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}.
*/
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.PARAMETER)
public @interface IRChild {
/** If true, the child will always be non-null. Otherwise, it can be null. */
boolean required() default true;
}
Original file line number Diff line number Diff line change
@@ -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).
*
* <p>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;
}
Loading

0 comments on commit 4ace1b2

Please sign in to comment.