Skip to content

Commit e7b7621

Browse files
committed
Don't export members that will be synthesized in case classes
Fixes scala#13228
1 parent 49d7399 commit e7b7621

File tree

5 files changed

+33
-5
lines changed

5 files changed

+33
-5
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1210,6 +1210,9 @@ class Definitions {
12101210
val AbstractFunctionClassPerRun: PerRun[Array[Symbol]] = new PerRun(AbstractFunctionType.map(_.symbol.asClass))
12111211
def AbstractFunctionClass(n: Int)(using Context): Symbol = AbstractFunctionClassPerRun()(using ctx)(n)
12121212

1213+
@tu lazy val caseClassSynthesized: Set[Symbol] = Set(Any_toString, Product_canEqual,
1214+
Product_productArity, Product_productPrefix, Product_productElement, Product_productElementName)
1215+
12131216
val LazyHolder: PerRun[Map[Symbol, Symbol]] = new PerRun({
12141217
def holderImpl(holderType: String) = requiredClass("scala.runtime." + holderType)
12151218
Map[Symbol, Symbol](

compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,7 @@ class SyntheticMembers(thisPhase: DenotTransformer) {
6363
private def initSymbols(using Context) =
6464
if (myValueSymbols.isEmpty) {
6565
myValueSymbols = List(defn.Any_hashCode, defn.Any_equals)
66-
myCaseSymbols = myValueSymbols ++ List(defn.Any_toString, defn.Product_canEqual,
67-
defn.Product_productArity, defn.Product_productPrefix, defn.Product_productElement,
68-
defn.Product_productElementName)
66+
myCaseSymbols = myValueSymbols ++ defn.caseClassSynthesized
6967
myCaseModuleSymbols = myCaseSymbols.filter(_ ne defn.Any_equals)
7068
myEnumValueSymbols = List(defn.Product_productPrefix)
7169
myNonJavaEnumValueSymbols = myEnumValueSymbols :+ defn.Any_toString

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,7 @@ class Namer { typer: Typer =>
425425
* is still missing its parents. Parents are set to Nil when completion starts and are
426426
* set to the actual parents later. If a superclass completes a subclass in one
427427
* of its parents, the parents of the superclass or some intervening class might
428-
* not yet be set. This situation can be detected by asking for the baseType of Any -
428+
* not yet be set. This situation can be detected by asking for the baseType of Any -
429429
* if that type does not exist, one of the base classes of this class misses its parents.
430430
* If this situation arises, the computation of the superclass might be imprecise.
431431
* For instance, in i12722.scala, the superclass of `IPersonalCoinOps` is computed
@@ -1151,10 +1151,19 @@ class Namer { typer: Typer =>
11511151
.flatMap(path.tpe.memberBasedOnFlags(_, excluded = Private|Given|ConstructorProxy).alternatives)
11521152
.foreach(addForwarder(name, _, span)) // ignore if any are not added
11531153

1154+
val fromCaseClass = path.tpe.widen.classSymbols.exists(_.is(Case))
1155+
1156+
/** Is symbol from a base trait of a case class so that it will be synthesized
1157+
* in the case class itself. Such members are treated like synthetic members,
1158+
* i.e. they don't get export forwarders.
1159+
*/
1160+
def isCaseClassSynthesized(mbr: Symbol) =
1161+
fromCaseClass && defn.caseClassSynthesized.contains(mbr)
1162+
11541163
def addWildcardForwarders(seen: List[TermName], span: Span): Unit =
11551164
val nonContextual = mutable.HashSet(seen: _*)
11561165
for mbr <- path.tpe.membersBasedOnFlags(required = EmptyFlags, excluded = PrivateOrSynthetic) do
1157-
if !mbr.symbol.isSuperAccessor then
1166+
if !mbr.symbol.isSuperAccessor && !isCaseClassSynthesized(mbr.symbol) then
11581167
// Scala 2 superaccessors have neither Synthetic nor Artfact set, so we
11591168
// need to filter them out here (by contrast, Scala 3 superaccessors are Artifacts)
11601169
val alias = mbr.name.toTermName

tests/run/i13228.check

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
RegisteredUser(Id,User(Name))
2+
false
3+
false
4+
false
5+
false

tests/run/i13228.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
case class User(name: String)
2+
3+
case class RegisteredUser(id: String, data: User) {
4+
export data.*
5+
}
6+
7+
@main def Test() =
8+
println(RegisteredUser("Id", User("Name"))) // RegisteredUser(Name)
9+
println(RegisteredUser("Id", User("Name")).canEqual(User("Name"))) // True
10+
// The rest works as expected
11+
println(RegisteredUser("Id", User("Name")) == User("Name")) // False
12+
println(RegisteredUser("Id", User("Name")).hashCode == User("Name").hashCode) // False
13+
println(RegisteredUser("Id", User("Name")).productArity == User("Name").productArity) // False

0 commit comments

Comments
 (0)