-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add support for Java records in patterns. #19577
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?
Changes from 7 commits
670664f
89cf837
24343bf
406c039
7b44500
0d277a1
143d5b3
d653059
b749672
df184af
6da240b
3aa99e7
353d9d8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -49,6 +49,10 @@ object Applications { | |
ref.info.widenExpr.annotatedToRepeated | ||
} | ||
|
||
// TODO: Error here | ||
def isJavaRecordMatch(tp: Type, numArgs: Int, errorPos: SrcPos = NoSourcePosition)(using Context): Boolean = | ||
defn.isJavaRecordClass(tp.typeSymbol) | ||
|
||
/** Does `tp` fit the "product match" conditions as an unapply result type | ||
* for a pattern with `numArgs` subpatterns? | ||
* This is the case if `tp` has members `_1` to `_N` where `N == numArgs`. | ||
|
@@ -108,6 +112,20 @@ object Applications { | |
if (isValid) elemTp else NoType | ||
} | ||
|
||
def javaRecordComponentTypes(tp: Type, errorPos: SrcPos)(using Context): List[Type] = { | ||
|
||
val params = tp.typeSymbol.javaRecordComponents.map(_.info.resultType) | ||
|
||
tp match | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is surely too standard to need to do it by hand... |
||
case tp: AppliedType => | ||
val argsIter = tp.args.iterator | ||
for (param <- params) yield param match | ||
case param if param.typeSymbol.isTypeParam => argsIter.next() | ||
case param => param | ||
case _ => params | ||
|
||
} | ||
|
||
def productSelectorTypes(tp: Type, errorPos: SrcPos)(using Context): List[Type] = { | ||
val sels = for (n <- Iterator.from(0)) yield extractorMemberType(tp, nme.selectorName(n), errorPos) | ||
sels.takeWhile(_.exists).toList | ||
|
@@ -192,6 +210,8 @@ object Applications { | |
// which is better than the message in `fail`. | ||
else if unapplyResult.derivesFrom(defn.NonEmptyTupleClass) then | ||
foldApplyTupleType(unapplyResult) | ||
else if (isJavaRecordMatch(unapplyResult, args.length, pos)) then | ||
javaRecordComponentTypes(unapplyResult, pos) | ||
else fail | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -407,6 +407,12 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): | |
def newTupleMirror(arity: Int): Tree = | ||
New(defn.RuntimeTupleMirrorTypeRef, Literal(Constant(arity)) :: Nil) | ||
|
||
def newJavaRecordReflectMirror(tpe: Type) = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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 Maybe this doesn't matter because actual practitioners would use the dotty reflect to select the field labels. |
||
ref(defn.JavaRecordReflectMirrorModule) | ||
.select(nme.apply) | ||
.appliedToType(tpe) | ||
.appliedTo(clsOf(tpe)) | ||
|
||
def makeProductMirror(pre: Type, cls: Symbol, tps: Option[List[Type]]): TreeWithErrors = | ||
val accessors = cls.caseAccessors | ||
val elemLabels = accessors.map(acc => ConstantType(Constant(acc.name.toString))) | ||
|
@@ -427,6 +433,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): | |
} | ||
val mirrorRef = | ||
if cls.useCompanionAsProductMirror then companionPath(mirroredType, span) | ||
else if defn.isJavaRecordClass(cls) then newJavaRecordReflectMirror(cls.typeRef) | ||
else if defn.isTupleClass(cls) then newTupleMirror(typeElems.size) // TODO: cls == defn.PairClass when > 22 | ||
else anonymousMirror(monoType, MirrorImpl.OfProduct(pre), span) | ||
withNoErrors(mirrorRef.cast(mirrorType).withSpan(span)) | ||
|
@@ -458,6 +465,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): | |
val reason = s"it reduces to a tuple with arity $arity, expected arity <= $maxArity" | ||
withErrors(i"${defn.PairClass} is not a generic product because $reason") | ||
case MirrorSource.ClassSymbol(pre, cls) => | ||
|
||
if cls.isGenericProduct then | ||
if ctx.runZincPhases then | ||
// The mirror should be resynthesized if the constructor of the | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package scala.runtime | ||
|
||
import java.lang.Record | ||
import java.lang.reflect.Constructor | ||
import scala.reflect.ClassTag | ||
|
||
// TODO: Rename to JavaRecordReflectMirror | ||
object JavaRecordMirror: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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 apply[T <: Record](clazz: Class[T]): JavaRecordMirror[T] = | ||
val components = clazz.getRecordComponents.nn | ||
val constructorTypes = components.map(_.nn.getType.nn) | ||
val constr = clazz.getDeclaredConstructor(constructorTypes*).nn | ||
new JavaRecordMirror(components.length, constr) | ||
|
||
def of[T <: Record : ClassTag]: JavaRecordMirror[T] = | ||
JavaRecordMirror(summon[ClassTag[T]].runtimeClass.asInstanceOf[Class[T]]) | ||
|
||
// TODO: Is a constructor serializable? | ||
final class JavaRecordMirror[T] private(arity: Int, constr: Constructor[T]) extends scala.deriving.Mirror.Product with Serializable: | ||
|
||
override type MirroredMonoType <: Record | ||
|
||
final def fromProduct(product: Product): MirroredMonoType = | ||
if product.productArity != arity then | ||
throw IllegalArgumentException(s"expected Product with $arity elements, got ${product.productArity}") | ||
else | ||
// TODO: Check this byte code, we want to unroll to give a happy medium between JIT'ing and having tons of extra classes | ||
val t = arity match | ||
case 0 => constr.newInstance() | ||
case 1 => constr.newInstance(product.productElement(0)) | ||
case 2 => constr.newInstance(product.productElement(0), product.productElement(1)) | ||
|
||
t.nn.asInstanceOf[MirroredMonoType] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import scala.deriving.Mirror | ||
|
||
object C: | ||
def useR2: Unit = | ||
summon[Mirror.Of[R2]] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
public record R2(int i, String s) {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
object C: | ||
|
||
def useR0: Unit = | ||
val r = R0() | ||
|
||
// unapply in valdef | ||
val R0() = r | ||
|
||
// unapply in patmatch | ||
r match { | ||
case R0() => | ||
} | ||
|
||
|
||
def useR1: Int = | ||
val r = R1(1, "foo") | ||
|
||
// unapply in valdef | ||
val R1(i, _) = r | ||
val a: Int = i | ||
|
||
// unapply in patmatch | ||
r match { | ||
case R1(i, _) => i | ||
} | ||
|
||
def useR2: String = | ||
val r = R2("asd") | ||
r match { | ||
case R2(s) => s | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
public record R0() {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
public record R1(int i, String s) {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
public record R2<T>(T t) {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
public record R0() {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
public record R1(int i) {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
public record R2<T>(T t, int i) {} |
Uh oh!
There was an error while loading. Please reload this page.