Add support for Java records in patterns. - #19577
Conversation
|
This should be epic, does it support compiled records, eg the records from a jar? |
|
Perhaps related - I wonder if we can support mirrors for records (and sealed classes) |
|
@He-Pin It should do - I'm not entirely clear about the way Scala works with raw jars, but you can certainly call the synthesized @joroKr21 That is indeed the intention; there are a few other issues that I want to fix to help the record support along; like #19386 for example. |
|
What is the point in inlining |
|
It is because I am unsure whether it is OK to let the final compiled code refer to the functions in the synthesized companion module. I do not understand where/how that code would be bundled, especially for a jar not compiled with scalac. |
|
I strongly suggest you use |
For each Java record, we create a synthetic `unapply` method returning a `Tuple` on the companion object.
aad2667 to
7b44500
Compare
This adds Java records as a first-class citizen within Scala's pattern matching. Broadly, we synthesize an `inline unapply` on the companion module which we later use to correlate with the bound variables within the pattern matcher.
|
|
||
| val params = tp.typeSymbol.javaRecordComponents.map(_.info.resultType) | ||
|
|
||
| tp match |
There was a problem hiding this comment.
This is surely too standard to need to do it by hand...
24e861e to
143d5b3
Compare
| import scala.reflect.ClassTag | ||
|
|
||
| // TODO: Rename to JavaRecordReflectMirror | ||
| object JavaRecordMirror: |
There was a problem hiding this comment.
Is this going to be a big deal? It means that compiling the scala library would need JDK 16+ but could target older versions, but having this on the classpath and not loaded should not be an issue? Maybe I could exclude the source, if records are not supported but would that cause issues with the current publishing process?
| def newTupleMirror(arity: Int): Tree = | ||
| New(defn.RuntimeTupleMirrorTypeRef, Literal(Constant(arity)) :: Nil) | ||
|
|
||
| def newJavaRecordReflectMirror(tpe: Type) = |
There was a problem hiding this comment.
This mirror is of limited use and requires the user to check the type at the derivation site for the mirrored mono type. This isn't the end of the world - we could add an extension method which uses the RecordComponent to invoke the accessor, but can we add a method to the mirror like productElement(a: A)(i: Int) which allows users which derive with the mirror to hide the implementation details?
Maybe this doesn't matter because actual practitioners would use the dotty reflect to select the field labels.
|
|
||
| flatTree(trees2) | ||
| else super.transform(tree) | ||
| case Apply(fun, args) if fun.symbol.name == nme.unapply && fun.symbol.is(ConstructorProxy) => |
There was a problem hiding this comment.
Should this transformation happen in the Typer just after we've created the unapply object instead - this would mean the pickler wouldn't see the proxy; would that be better?
| // TODO: Add this check back in at some point. | ||
| def checkNoConstructorProxy(tree: Tree)(using Context): Unit = | ||
| if tree.symbol.is(ConstructorProxy) then | ||
| if tree.symbol.is(ConstructorProxy) && tree.symbol.name != nme.unapply then |
There was a problem hiding this comment.
We can transform the unapply into an identity function.
|
@yilinwei What is the status of this PR? Do you mean to work on it in the near future, or should we close it? |
|
I've tried the same approach in #24140 (synthetic |
|
Tracked under #20561 (and the linked above last attempt, #24140) |
For each Java record, we create a synthetic
unapplymethod returning aTupleon the companion object.I briefly toyed with the idea of adding inline methods for
Productto reduce the allocation, but I don't think that would be safe. In particular, I expect the following block to be unsound if that was the case:I would like to test this further; hopefully after #19578 is fixed.
Many thanks to @som-snytt, @SethTisue and @sjrd for the help.
There was also a suggestion from the Discord chat of a different desugaring involving extension methods and by-name pattern matching with an opaque type alias to reduce the allocations.
However we currently only check for the by name extension methods on the actual extractor objects (see Applications#extractorMember) and looking for extension methods in this case looks as though it might open a whole can of worms.
Using an
AnyValworks - but I would like to verify that the byte code generated is correct.EDIT:
The byte code looks fine in that there is no allocation of an intermediate object.
However the
isEmptyisn't omitted even if the type is narrowed tofalseand the accessors,_1and_2aren't inlined either.I'm also worried about records compiled via javac for this encoding since the method descriptors stick around in the compiled code so presumably they would need to be bundled somewhere otherwise it would fail at runtime? It's unclear to me how methods which aren't inlined on the companion module of the record would work due to my rather limited understanding of the nuances of the mixed compilation processes.
Roughly speaking, here is the encoding which I experimented with:
EDIT:
As per the discussions in #19631, we now deal with the problems in the
PatternMatcherand make Java records a first class citizen of the pattern matcher.I have a few points here and there I'd like to check, but after that I will start writing the SIP.
EDIT:
Pre-SIP is here.