Skip to content

Commit da4996a

Browse files
Remove synthetic record constructor if the user has written one manually
Co-authored-by: Guillaume Martres <[email protected]>
1 parent d1df3ee commit da4996a

File tree

5 files changed

+32
-17
lines changed

5 files changed

+32
-17
lines changed

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -688,6 +688,7 @@ class Definitions {
688688
@tu lazy val JavaCalendarClass: ClassSymbol = requiredClass("java.util.Calendar")
689689
@tu lazy val JavaDateClass: ClassSymbol = requiredClass("java.util.Date")
690690
@tu lazy val JavaFormattableClass: ClassSymbol = requiredClass("java.util.Formattable")
691+
@tu lazy val JavaRecordClass: Symbol = getClassIfDefined("java.lang.Record")
691692

692693
@tu lazy val JavaEnumClass: ClassSymbol = {
693694
val cls = requiredClass("java.lang.Enum")

compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@ import reporting._
2121
import dotty.tools.dotc.util.SourceFile
2222
import util.Spans._
2323

24-
import scala.collection.mutable.ListBuffer
25-
import scala.collection.immutable.ListMap
24+
import scala.collection.mutable.{ListBuffer, LinkedHashMap}
2625

2726
object JavaParsers {
2827

@@ -839,7 +838,7 @@ object JavaParsers {
839838

840839
// We need to generate accessors for every param, if no method with the same name is already defined
841840

842-
var fieldsByName = header.map(v => (v.name, (v.tpt, v.mods.annotations))).to(ListMap)
841+
var fieldsByName = header.map(v => (v.name, (v.tpt, v.mods.annotations))).to(LinkedHashMap)
843842

844843
for case DefDef(name, paramss, _, _) <- body
845844
if paramss.isEmpty && fieldsByName.contains(name)
@@ -855,7 +854,8 @@ object JavaParsers {
855854

856855
// generate the canonical constructor
857856
val canonicalConstructor =
858-
DefDef(nme.CONSTRUCTOR, joinParams(tparams, List(header)), TypeTree(), EmptyTree).withMods(Modifiers(Flags.JavaDefined, mods.privateWithin))
857+
DefDef(nme.CONSTRUCTOR, joinParams(tparams, List(header)), TypeTree(), EmptyTree)
858+
.withMods(Modifiers(Flags.JavaDefined | Flags.Synthetic, mods.privateWithin))
859859

860860
// return the trees
861861
val recordTypeDef = atSpan(start, nameOffset) {
@@ -866,7 +866,7 @@ object JavaParsers {
866866
tparams = tparams,
867867
true
868868
)
869-
)
869+
).withMods(mods)
870870
}
871871
addCompanionObject(statics, recordTypeDef)
872872
end recordDecl

compiler/src/dotty/tools/dotc/typer/Namer.scala

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -862,7 +862,6 @@ class Namer { typer: Typer =>
862862
* with a user-defined method in the same scope with a matching type.
863863
*/
864864
private def invalidateIfClashingSynthetic(denot: SymDenotation): Unit =
865-
866865
def isCaseClassOrCompanion(owner: Symbol) =
867866
owner.isClass && {
868867
if (owner.is(Module)) owner.linkedClass.is(CaseClass)
@@ -879,10 +878,19 @@ class Namer { typer: Typer =>
879878
!sd.symbol.is(Deferred) && sd.matches(denot)))
880879

881880
val isClashingSynthetic =
882-
denot.is(Synthetic, butNot = ConstructorProxy)
883-
&& desugar.isRetractableCaseClassMethodName(denot.name)
884-
&& isCaseClassOrCompanion(denot.owner)
885-
&& (definesMember || inheritsConcreteMember)
881+
denot.is(Synthetic, butNot = ConstructorProxy) &&
882+
(
883+
(desugar.isRetractableCaseClassMethodName(denot.name)
884+
&& isCaseClassOrCompanion(denot.owner)
885+
&& (definesMember || inheritsConcreteMember)
886+
)
887+
||
888+
// remove synthetic constructor of a java Record if it clashes with a non-synthetic constructor
889+
(denot.isConstructor
890+
&& denot.owner.is(JavaDefined) && denot.owner.derivesFrom(defn.JavaRecordClass)
891+
&& denot.owner.unforcedDecls.lookupAll(denot.name).exists(c => c != denot.symbol && c.info.matches(denot.info))
892+
)
893+
)
886894

887895
if isClashingSynthetic then
888896
typr.println(i"invalidating clashing $denot in ${denot.owner}")

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2433,11 +2433,17 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
24332433
}
24342434

24352435
def typedDefDef(ddef: untpd.DefDef, sym: Symbol)(using Context): Tree = {
2436-
if (!sym.info.exists) { // it's a discarded synthetic case class method, drop it
2437-
assert(sym.is(Synthetic) && desugar.isRetractableCaseClassMethodName(sym.name))
2436+
def canBeInvalidated(sym: Symbol): Boolean =
2437+
sym.is(Synthetic)
2438+
&& (desugar.isRetractableCaseClassMethodName(sym.name) ||
2439+
(sym.isConstructor && sym.owner.derivesFrom(defn.JavaRecordClass)))
2440+
2441+
if !sym.info.exists then
2442+
// it's a discarded method (synthetic case class method or synthetic java record constructor), drop it
2443+
assert(canBeInvalidated(sym))
24382444
sym.owner.info.decls.openForMutations.unlink(sym)
24392445
return EmptyTree
2440-
}
2446+
24412447
// TODO: - Remove this when `scala.language.experimental.erasedDefinitions` is no longer experimental.
24422448
// - Modify signature to `erased def erasedValue[T]: T`
24432449
if sym.eq(defn.Compiletime_erasedValue) then

tests/pos-java16+/java-records/R2.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ public int getInt() {
55
}
66

77
// Canonical constructor
8-
// public R(int i, java.lang.String s) {
9-
// this.i = i;
10-
// this.s = s.intern();
11-
// }
8+
public R(int i, java.lang.String s) {
9+
this.i = i;
10+
this.s = s.intern();
11+
}
1212
}
1313
}

0 commit comments

Comments
 (0)