Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Poc] Generate IR definitions - superclass approach #11770

Draft
wants to merge 138 commits into
base: develop
Choose a base branch
from

Conversation

Akirathan
Copy link
Member

@Akirathan Akirathan commented Dec 4, 2024

Alternative approach to #11267

Pull Request Description

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

An example of generated code for JExpression is:

Checklist

Please ensure that the following checklist has been satisfied before submitting the PR:

  • The documentation has been updated, if necessary.
  • Screenshots/screencasts have been attached, if there are any visual changes. For interactive or animated visual changes, a screencast is preferred.
  • All code follows the
    Scala,
    Java,
    TypeScript,
    and
    Rust
    style guides. In case you are using a language not listed above, follow the Rust style guide.
  • Unit tests have been written where possible.
  • If meaningful changes were made to logic or tests affecting Enso Cloud integration in the libraries,
    or the Snowflake database integration, a run of the Extra Tests has been scheduled.
    • If applicable, it is suggested to paste a link to a successful run of the Extra Tests.

This will be important for mapExpressions method implementation
This is the only way to retain 100% backward compatibility.
@JaroslavTulach
Copy link
Member

I think the generated code looks reasonably nice, but:

  • when serializing, we do not want to intialize passData() neither diagnostics() if they were not initialized before
  • probably the @Persistance annotation shall be in the same package and have package private access to these special fields
  • JBindingGen should be abstract
  • builder should return JBinding and not the generated class
  • I'd like to remind you of the WhiningBuilder pattern - better than validate() as it does compile type checks
  • the build() method shall throw NameExc, ExpressionExc and the builder type should be Builder<NameExc extends Exception, ExpressionExc extends Exception>
  • empty builder is initialized with as Builder<MissingNameException, MissingExpressionException>
  • Signature of name method is: Builder<RuntimeException, ExpressionExc> name(String name) - to signal that name has already been provided
  • name.duplicate(...) should be overridden in Name to return Name
  • IR setLocation(Option<IdentifiedLocation> location) should return JBinding
  • JExpression duplicate( should return JBinding
  • showCode may be implemented manually in JBinding - another reason for making JBindingGen abstract
  • missing new line in list.add(returnValue);return scala.jdk.javaapi.CollectionConverters.asScala(list).toList();

As a concept it is nice, but the real challenge is going to be the implementation of real IR element(s).

@JaroslavTulach
Copy link
Member

I see one failure related to Empty and EmptyGen. Are there any other important failures?

@Akirathan
Copy link
Member Author

Unapply & apply

In order to make necessary modifications in runtime-parser as minimal as possible, we could implement unapply methods on Java classes to make it work with Scala deconstruct pattern matching.

0a1f5a8 demonstrates how a Java class JSpecified can implement public static unapply method so it works in Scala deconstruct match. The Scala compiler is happy and the tests succeeds. But the IDE is not happy:
image

I assume VSCode with Metals extension will also not be able to deduce that JSpecified java class can be treated as a case class in this particular case.

The question is: is this really worth it? Wouldn't it be better to just incrementally rewrite pattern matching in Scala that uses deconstruct to simpler variants that just matches on types? For example:

    val callArg = new JSpecified(true, None, lit)
    callArg match {
      case JSpecified(isSynthetic, _, _) =>
        isSynthetic shouldEqual true
    }

Could be rewritten to

    callArg match {
      case specified: JSpecified =>
        specified.isSynthetic shouldEqual true
    }

without any behavioral changes. Moreover, the latter is OK in IDE.

I would like to first create a PR that simplifies pattern matching, for CallArgument, and then continue with the migration. Any strong opinions against @JaroslavTulach @hubertp ?

TL;DR;

Don't try to bend Java classes to look like Scala case classes. Just refactor deconstructing pattern matches in Scala to simpler variants.

@JaroslavTulach
Copy link
Member

I don't mind how this particular IDE behaves. All that matters is whether the code compiles and behaves as it should.

@Akirathan
Copy link
Member Author

I don't mind how this particular IDE behaves. All that matters is whether the code compiles and behaves as it should.

One of our long-term visions is to replace all Scala code in runtime-parser with Java. Refactoring IR elements such that Scala compiler does not use its magical apply and unapply methods (as demonstrated in #11943) only helps this goal. Such refactoring is trivial. Not only it moves us closer to Java, but it also makes the IJ IDE (and presumably also VSCode with Metals) experience much better. Imagine a situation where half of our IR elements are migrated to generated Java classes - that would make almost half of the code base in runtime-parser unreadable in the IDE and the experience would be almost the same as using plain text editor. If the "fix" is so straightforward, as #11943, I am strongly pushing for it. I would not like to decrease the DevX anymore, as it already is quite low.

@JaroslavTulach
Copy link
Member

presumably also VSCode with Metals

I doubt. Metals is using completely different technology than your IDE.

# Conflicts:
#	engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/desugar/OperatorToFunctionTest.scala
Copy link
Member

@JaroslavTulach JaroslavTulach left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Few notes.

/**
* 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is possible to extract the name of the super class from class X extends MyParent definition. E.g. then the name of the super class could be arbitrary. It would have to be in the same package, but there would be no restriction on the Gen suffix.

*
* @return
*/
String interfaces() default "org.enso.compiler.core.IR";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this is not used yet. I see plural being used here. In such case the value should be [] array. I would also suggest to use Class[] that way one prevents typos as the referenced classes must exist. Btw. looking at Block I see it implements IRKind.Primitive - that interface isn't subtype of 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* 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

* 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}.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* a subtype of {@code org.enso.compiler.ir.IR}.
* a subtype of {@link org.enso.compiler.ir.IR}.

import org.enso.runtime.parser.dsl.GenerateFields;
import org.enso.runtime.parser.dsl.GenerateIR;

@GenerateIR(interfaces = "org.enso.compiler.core.ir.Expression")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using Class[] might be better. Then this line would become:

Suggested change
@GenerateIR(interfaces = "org.enso.compiler.core.ir.Expression")
@GenerateIR(interfaces =Expression.class)

@@ -61,3 +61,7 @@ final class DiagnosticStorage(initDiagnostics: Seq[Diagnostic] = Seq())
new DiagnosticStorage(this.diagnostics)
}
}

object DiagnosticStorage {
def empty(): DiagnosticStorage = new DiagnosticStorage()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

create() or createEmpty() might be better to indicate that a new instance of this mutable class is always created.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CI: Clean build required CI runners will be cleaned before and after this PR is built.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants