Skip to content

Conversation

Florian3k
Copy link
Contributor

Closes #20561

@Florian3k Florian3k requested a review from a team as a code owner October 5, 2025 22:58
@jchyb jchyb self-assigned this Oct 6, 2025
@jchyb jchyb self-requested a review October 6, 2025 09:38
Copy link
Contributor

@jchyb jchyb left a 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)
Copy link
Contributor

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

Copy link
Contributor

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.

Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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?

Copy link
Contributor

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

Copy link
Member

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?

Copy link
Member

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.

Copy link
Member

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.

Copy link
Member

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.

Copy link
Member

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.

@jchyb jchyb assigned Florian3k and unassigned jchyb Oct 6, 2025
Copy link
Contributor

@jchyb jchyb left a comment

Choose a reason for hiding this comment

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

Looks good!

Copy link
Member

@hamzaremmal hamzaremmal left a 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.

Copy link
Member

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.

@He-Pin
Copy link
Contributor

He-Pin commented Oct 10, 2025

Can we have Scala's case class be a record too? seems we can't.

@hamzaremmal
Copy link
Member

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

@hamzaremmal hamzaremmal Oct 15, 2025

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.

Copy link
Member

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,
Copy link
Member

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,
Copy link
Member

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?

Copy link
Member

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.

Copy link
Contributor

@Gedochao Gedochao Oct 15, 2025

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.

Copy link
Member

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...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Scala Pattern Matching on Java Records (JEP-440)

6 participants