-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add support for java records in pattern matching #24140
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
base: main
Are you sure you want to change the base?
Conversation
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.
Really great work!
I have some small nitpicks/suggestions, mostly connected to the tests.
I have also left one more general comment about the JavaRecordFields
addition that isn't really actionable, but I still wanted to point this out in case you or anyone else from the compiler team have any better suggestions there.
def javaRecordFields(tp: Type)(using Context): List[Name] = | ||
tp.typeSymbol.getAnnotation(defn.JavaRecordFieldsAnnot) match | ||
case Some(JavaRecordFieldsAnnotation(fields)) => fields.map(termName) | ||
case _ => assert(false) |
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.
Doesn't matter, but a little cleaner to just return Nil
here in my opinion
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.
Ideally we would not need this, since we don't need that information pickled and it's only relevant for the current compilation run. We could add a property key for a tree, but that would only work for the situations where we use the JavaOutlineParser, when we read the classfiles we would not have access to a tree on which a property could be used. I see we can really only get this information out when parsing the tree, and we don't save it anywhere else, which I guess justifies the annotation here.
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 99.9% sure we can do without and I think it's best if we don't introduce this.
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 would prefer to do this without adding new annotation, but I couldn't find another solution. If you have any suggestions, I'm happy to explore them.
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.
@hamzaremmal Do you have any ideas how to implement this without adding annotation?
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 just realized. We are only holding Strings inside of the annotation (we don't need any of the actual trees), so perhaps it would be ok to have a Map[Symbol, Seq[String]] held in the ContextBase (basically, a global value)? We could even change it to Map[Int, Seq[String]] with symbolIDs if comparing Symbols will prove to be problematic
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.
We do the Scala 2 schema instead. For record Foo(int x, String y)
, we would generate def unapply(x$0: Foo): Option[(Int, String)] = ...
instead of def unapply(x$0: Foo): Foo = x$0
.
@sjrd What do you think about this?
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.
Returning an Option[(Int, String)]
won't help. At the end of the day, you need to emit bytecode that reads those fields. That bytecode needs to know the field names.
I would go with a compiler-internal Map
.
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.
But we know the field names when we build the body of unapply
. If we know the field names to attach an annotation to the record, we surely know them to synthesis a method.
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.
You can't synthesize an actual method. It's a Java-compiled class. It's all fake. It must be handled at call site.
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 this PR, it is marked as inline
, I'm suggesting this change based on that assumption. If we remove inline
, this all breaks down.
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 good!
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 PR doesn't handle yet the following case:
record Foo(Object... x) {}
where we should have an unapplySeq
generated.
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 99.9% sure we can do without and I think it's best if we don't introduce this.
Can we have Scala's case class be a record too? seems we can't. |
Nope, we can't. |
).checkRuns() | ||
) | ||
|
||
if scala.util.Properties.isJavaAtLeast("16") then |
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.
No need, we run everything with Java 17 now.
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.
We do the Scala 2 schema instead. For record Foo(int x, String y)
, we would generate def unapply(x$0: Foo): Option[(Int, String)] = ...
instead of def unapply(x$0: Foo): Foo = x$0
.
@sjrd What do you think about this?
.withAddedAnnotation( | ||
New( | ||
ref(defn.JavaRecordFieldsAnnot), | ||
header.map(field => Literal(Constant(field.name.toString))) :: Nil, |
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.
See, we have the names here, so we will have them in line 923. Same for the ClassfileParser
.
val completer = RecordUnapplyCompleter() | ||
val member = newSymbol( | ||
moduleRoot.symbol, | ||
nme.unapply, |
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 fact I'm not a big fan of this fake synthetic method. It could be used in regular calls that are not pattern matching. That means TASTy effectively embeds knowledge of these synthetic, fake methods.
Could we directly adapt pattern matching 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.
That means TASTy effectively embeds knowledge of these synthetic, fake methods.
Yes please. I've been arguing this behind the doors but this is very much a SIP change because TASTy is affected.
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 not sure TASTy being affected is SIP material by default, per se.
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.
Yes, because TASTy will contain a synthetic method that will have to be in the spec. It will be saying Java records generate an unapply of this form...
Closes #20561