-
Notifications
You must be signed in to change notification settings - Fork 32
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
Scala3 migration2 #122
Scala3 migration2 #122
Conversation
Fixed type annotations on toString methods.
- Changed Binop from private to protected[analysis] and all 'binop' methods - Added explicit type annotations on specializations of Value.getType that return a subtype of Type Compiles/tests cleanly with scala 3.1 (thanks to the migrate tool).
[warn] -- [E121] Pattern Match Warning: C:\opt\local\github.fprime\fpp\compiler\lib\src\main\scala\analysis\Semantics\TypeVisitor.scala:45:11 [warn] 45 | case _ => default(in, t) [warn] | ^ [warn] |Unreachable case except for null (if this is intentional, consider writing case null => instead). [warn] -- [E121] Pattern Match Warning: C:\opt\local\github.fprime\fpp\compiler\lib\src\main\scala\analysis\Semantics\ValueVisitor.scala:45:11 [warn] 45 | case _ => default(in, v) [warn] | ^ [warn] |Unreachable case except for null (if this is intentional, consider writing case null => instead). All tests pass.
Thanks for doing this!
If the compiler warns that those cases are unreachable, then let's delete them. Looking over the changes, I see that most of them add return type annotations. Some of them remove the
|
Also, in cases where you added |
Overall the code is more verbose, but also more explicit about the return type. |
I found this in the Scala 3 Migration Guide:
https://docs.scala-lang.org/scala3/guides/migration/incompat-type-inference.html |
Here are some comments about the changes that I had to do above and beyond what the scala3 migration tool did.
To avoid getting lots of E147 warning noise, I deleted all these redundant keywords.
There was a subtle Scala 2.13 vs. Scala 3 difference w.r.t
With Scala3, the compiler detected an error in ValueXmlWriter:
Overall, the migration tool is really impressive! It did most of the changes for me, the only things were the corner cases described above. |
Added -Xfatal-warnings. All tests pass
val (_, node1, _) = node | ||
val data = node1.data | ||
visitList(s, data.members, matchModuleMember) | ||
} | ||
|
||
override def defPortAnnotatedNode(s: State, node: Ast.Annotated[AstNode[Ast.DefPort]]) = { | ||
override def defPortAnnotatedNode(s: State, node: Ast.Annotated[AstNode[Ast.DefPort]]): Either[CodeGenError.DuplicateXmlFile,XmlWriterState] = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wondering about this change and others like it. It looks like the migration tool has inferred a specific type and added it as the return type. But this coding style seems like a pain to write by hand. The style I was using before is
- Make a type alias for the more general type, e.g,. type
Result = Either[Error,State]
- Avoid writing the return type at all where possible
- Otherwise use the type alias
In the new style, it seems to use more specific and more complex instantiations of parameterized types. Is this just an artifact of the migration, or will Scala 3 require this more specific type annotation going forward? And will we have to write these types by hand? Again, this seems like it could be a pain.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Completely agree with you... Let me see if I can use the type alias instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, sounds good. If Scala 3 is just being more fussy about writing an explicit return type, maybe we can copy over the return type from the method being overridden? Except in rare cases like the one you identified with the structType, I think this is what is intended and should work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Regarding type aliases, why are there two different aliases for the same type?
trait AstStateVisitor extends AstVisitor {
type State
type In = State
type Out = Result.Result[State]
type Result = Result.Result[State]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI: I think it would be preferable to avoid synonym aliases because it makes the code harder to read.
e.g:
AstVisitor has:
def transUnit(in: In, tu: Ast.TransUnit): Out
and ComputeXmlFiles, a subtype of AstVisitor has:
override def transUnit(s: State, tu: Ast.TransUnit): Result
Since Out
and Result
are synonyms for the same type, the compiler is OK; however, it creates unnecessary confusion.
Regarding eliminating the return types, unfortunately, the presence of type aliases is potentially confusing.
Is Either[CodeGenError.DuplicateXmlFile, XmlWriterState]
the appropriate return type or should it be Out
or Result
?
All 3 are acceptable to the compiler; for a human, I think the intended return type would help with code legibility, particularly when there is only 1 sensible type alias -- e.g. Out
or Result
but not both!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI:
The IDE find 24 usages of AstStateVisitor.Out
and 113 usages of AstStateVisitor.Result
.
I propose refactoring all 24 usages of AstStateVisitor.Out
as AstStateVisitor.Result
.
@bocchino Is this OK with you?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The rationale for having both is that an AstVisitor is a generic operation that take an In to an Out, whereas an AstStateVisitor manages state. Also, a Result is the result of an operation that can return an error.
So I'd prefer to keep In/Out for AstVisitor and State/Result for AstStateVisitor.
Refactoring uses of AstStateVisitor.Out to AstStateVisitor.Result seems good. We still need to keep the type alias AstStateVisitor.Out to satisfy the inheritance from AstVisitor. Overall I think having synonyms for the same type reflecting its use in different contexts is OK.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In English, the logic is something like this: "AstVisitor takes an In to an Out, so to inherit from it you have to define an In type and an Out type. AstStateVisitor takes a State to a Result, so it defines those types. It also inherits from AstVisitor, and its State type is the In type of AstVisitor, and its Result type is the Out type of AstVisitor. So we create those type aliases to satisfy the inheritance contract with AstVisitor."
@bocchino Are your concerns properly addressed? |
Getting there. See the comment I just left. I want to understand why these more complicated and specific type annotations have been added, and whether we need to keep writing them going forward. |
Overall, I am concerned about having to write explicit |
I believe the migration tool should have not added explicit return types for overrides methods as long as the calculated return type matches the super method's return type. The return type is required when the calculated return type of the overridden method is a subtype of the super method. I am working on removing these cases and I will file a bug report on the migration tool. |
I think the tool may be enforcing the recommendation in the migration notes, to put in return types explicitly for public methods. I think that's OK. I think the case to watch out for is
In this case the migration tool conservatively assumes that we want to preserve the type inferred by Scala 2.13, so it inserts T2 as the return type. I think we should manually change the type to T1. (Or we could delete the type and let Scala 3 infer T1, but this seems contrary to the recommendation in the migration notes.) |
All tests pass
7293985
to
49fc431
Compare
@bocchino Can you please review the changes? I agree with your analysis. I carefully reviewed most of the cases where the migration tool added a return type to an overriding method and removed a lot of such type annotations if possible, leaving those that are strictly required. There were a few methods that lacked the I generally left the type annotations that the migration tool added on non-overriding methods as this helps improve code legibility. |
@@ -42,9 +42,9 @@ trait TypeExpressionAnalyzer | |||
} yield a | |||
} | |||
|
|||
def typeNameNode(a: Analysis, node: AstNode[Ast.TypeName]) = matchTypeNameNode(a, node) | |||
def typeNameNode(a: Analysis, node: AstNode[Ast.TypeName]): Out = matchTypeNameNode(a, node) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be Result instead of Out?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I replaced it.
Note that the confusion comes from here:
trait AstStateVisitor extends AstVisitor {
type State
type In = State
type Out = Result.Result[State]
type Result = Result.Result[State]
...
Generally, I prefer avoiding type synonyms like Out
and Result
because one can easily get confused with inconsistent usage as was the case here.
@@ -34,7 +35,7 @@ sealed trait Value { | |||
} | |||
|
|||
/** Generic binary operation */ | |||
def binop(op: Value.Binop)(v: Value): Option[Value] = None | |||
protected[analysis] def binop(op: Value.Binop)(v: Value): Option[Value] = None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the rationale for replacing private
with protected[analysis]
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I replaced it with private[analysis]
.
Without scoping to the analysis
package, this code would not compile:
case class EnumConstant(value: (Name.Unqualified, BigInt), t: Type.Enum) extends Value {
override private[analysis] def binop(op: Binop)(v: Value) = convertToRepType.binop(op)(v)
The reason is that convertToRepType
is a PrimitiveInt
, not an EnumConstant' so
binop` would not be accessible.
@@ -14,7 +14,7 @@ trait AstStateVisitor extends AstVisitor { | |||
type Result = Result.Result[State] | |||
|
|||
/** Default state transformation */ | |||
def default(s: State) = Right(s) | |||
def default(s: State): Out = Right(s) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be Result?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an override method so I removed the type annotation.
@@ -15,13 +15,13 @@ object ComputeGeneratedFiles { | |||
} | |||
yield xmlFiles ++ cppFiles | |||
|
|||
def getCppFiles(tul: List[Ast.TransUnit]) = | |||
def getCppFiles(tul: List[Ast.TransUnit]): Either[Error,List[String]] = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use Result.Result instead of Either[...] here? Similarly for the other uses of Either.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great! The code is much more readable now.
I ran the tests and they all passed. Just a few more comments.
Can you also please apply the following patch to your branch:
I noticed that if the 2.13 and 3.1.2 binaries were both in there, the installer was not able to select the correct ones and failed. |
I cleaned up the Regarding Graal VM, I think we should ask the scala native folks if they have developed a github action workflow to build this as part of CI. |
Looks good. Unfortunately updating GraalVM did not help. Also running the tracing agent as discussed here https://docs.oracle.com/en/graalvm/enterprise/19/guide/reference/native-image/tracing-agent.html did not help. I tried that because of comments here suggesting it could remedy the following runtime error when compiling with Scala 3 and then
|
Can you put in a README somewhere the procedure you've followed to build the graal native image? |
Yes, I'm working on it. I'll push that change to main. |
OK, I added a section on building native binaries to the README in the compiler directory: https://github.com/fprime-community/fpp/tree/main/compiler#building-native-binaries |
I pushed a change that simplifies the procedure. |
I think we need representative runs of the JVM-based application running with
Using the
After running the JVM-based
Moving these files to the resource folder ( This time, it worked like a charm!
This is the executable:
Also, I see that the native packager provides some support for building native binaries: The question here is can you suggest a representative set of JVM-based runs we can do w/ the agent to generate the appropriate config for the native runs to work without problems? |
This is great, thanks!
It seems the problems occur in the interaction with scopt, which occurs at the beginning (parsing command-line arguments). So it seems like just running one of the applications, even with no input, should be enough. |
I think when I tried this I created the JSON files but didn't copy them into the right place. |
The above commit adds a new folder: Using just |
This is great progress! However the JVM / GraalVM interaction is still having issues.
Item (4) is a bit of a stumper. Either there is something wrong with my procedure, or the trace analysis is not working in this case, since the same input that produced the trace is causing a failed run. |
…t-dir=<path to lib/resources/META-INF/native-image>; release; ./native-fpp-Linux-x86_64/fpp-syntax ./tools/fpp-syntax/test/syntax.fpp
I followed your procedure, which resulted in updated JSON config files (see commit above).
No errors. |
Hum, can you check this update?
#122 (comment)
* Nicolas.
From: Rob Bocchino ***@***.***>
Sent: Wednesday, May 18, 2022 8:24 AM
To: fprime-community/fpp ***@***.***>
Cc: Rouquette, Nicolas F (US 319K) ***@***.***>; Author ***@***.***>
Subject: [EXTERNAL] Re: [fprime-community/fpp] Scala3 migration2 (PR #122)
This is great progress! However the JVM / GraalVM interaction is still having issues.
1. I confirmed that the native binaries work with -h
2. I ran the unit tests by copying the native binaries into bin and running ./test. Many failed with Java exceptions. The ones I examined were complaining about missing fields during initialization. They seem to have to do with the Parser library.
3. I re-ran the trace analysis with some input file. As expected, the JSON files have more entries in them. This result makes sense, because when we run with -h we are not exercising the parser.
4. However, I still could not get the native binaries to work as expected. For example, I saw this:
a. Build and install JAR files with ./install.
b. Run fpp-syntax.jar on this file<https://urldefense.us/v3/__https:/github.com/fprime-community/fpp/blob/main/compiler/tools/fpp-syntax/test/syntax.fpp__;!!PvBDto6Hs4WbVuu7!YyijdlzF4nlljhawbYuaJWnZnadcsa0zm80-OtsFaRG_-zCQtsaoBxeaWhge-pOSu7GtHBoeO0zKoA$> with trace analysis and put the results in the META-INF/native-image directory. As expected, there were new entries in the JSON files.
c. Generate the native binary for fpp-syntax with ./release, picking up the updated JSON files.
d. Run the native binary for fpp-syntax on the FPP file referred to in (b). It still failed with a runtime exception.
Item (4) is a bit of a stumper. Either there is something wrong with my procedure, or the trace analysis is not working in this case, since the same input that produced the trace is causing a failed run.
—
Reply to this email directly, view it on GitHub<https://urldefense.us/v3/__https:/github.com/fprime-community/fpp/pull/122*issuecomment-1130160298__;Iw!!PvBDto6Hs4WbVuu7!YyijdlzF4nlljhawbYuaJWnZnadcsa0zm80-OtsFaRG_-zCQtsaoBxeaWhge-pOSu7GtHBrbciUJOg$>, or unsubscribe<https://urldefense.us/v3/__https:/github.com/notifications/unsubscribe-auth/AAR767VQZK3ECQBPZQQVNJTVKUDSHANCNFSM5V7YQK7Q__;!!PvBDto6Hs4WbVuu7!YyijdlzF4nlljhawbYuaJWnZnadcsa0zm80-OtsFaRG_-zCQtsaoBxeaWhge-pOSu7GtHBpY8XQh1A$>.
You are receiving this because you authored the thread.Message ID: ***@***.******@***.***>>
|
Great, I confirmed that all the fpp-syntax tests pass now. Can you do the following:
I think we will have to use the |
Note: We are done with GraalVM issues when we can do this:
and all the unit tests pass, except for one known failure in |
Revert changes to parameter types
Since GraalVM provides a tracing agent option for collecting configuration data across multiple runs, let's use this mechanism to collect tracing configuration data for the entire unit test suite! Since the unit test suite provides great code coverage, the tracing configuration should be reasonably complete.
For step 1, I modified the I got 4 error from the first step:
Is this due to a relative path for
These are due to problems in this script: I fixed all 4 errors in the scripts and made sure that all tests execute properly. After steps 2 and 3, all unit tests pass OK! The performance difference between running these tests in the JVM vs. native applications is quite impressive! JVM:
Native:
These tests were done on an AMD Ryzen Threadripper PRO 3955WX 16-Cores, 3892 MHz in WSL2 from Windows 10. |
Super, this is great news!! I confirmed that all the tests pass with the GraalVM images on my MacBook Pro. Can you make one small change: Please comment this line out (leaving it in as a comment): Please then add the same line, but without calling the tracing agent. That way we don't require the tracing agent to run the tools. I'll separately add a script with an option to run the tracing agent. |
I made this change myself, to resolve the conflict. |
Thanks |
Thanks to the Scala3 migration tool, the migration was very easy and a fun exercise!
There are 2 compiler warnings that should be fixed in the source code:
src/main/scala/analysis/Semantics/TypeVisitor.scala
src/main/scala/analysis/Semantics/ValueVisitor.scala
Please advise w.r.t how to proceed to eliminate the warning.