Skip to content

Commit c7c3c14

Browse files
authored
Merge pull request #14967 from dotty-staging/fix-14966
2 parents 803f048 + 806386e commit c7c3c14

File tree

8 files changed

+116
-6
lines changed

8 files changed

+116
-6
lines changed

compiler/src/dotty/tools/dotc/reporting/messages.scala

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2121,6 +2121,7 @@ import transform.SymUtils._
21212121
class DoubleDefinition(decl: Symbol, previousDecl: Symbol, base: Symbol)(using Context) extends NamingMsg(DoubleDefinitionID) {
21222122
def msg = {
21232123
def nameAnd = if (decl.name != previousDecl.name) " name and" else ""
2124+
def erasedType = if ctx.erasedTypes then i" ${decl.info}" else ""
21242125
def details(using Context): String =
21252126
if (decl.isRealMethod && previousDecl.isRealMethod) {
21262127
import Signature.MatchDegree._
@@ -2148,7 +2149,7 @@ import transform.SymUtils._
21482149
|Consider adding a @targetName annotation to one of the conflicting definitions
21492150
|for disambiguation."""
21502151
else ""
2151-
i"have the same$nameAnd type after erasure.$hint"
2152+
i"have the same$nameAnd type$erasedType after erasure.$hint"
21522153
}
21532154
}
21542155
else ""
@@ -2167,10 +2168,12 @@ import transform.SymUtils._
21672168
else
21682169
"Name clash between inherited members"
21692170

2170-
em"""$clashDescription:
2171-
|${previousDecl.showDcl} ${symLocation(previousDecl)} and
2172-
|${decl.showDcl} ${symLocation(decl)}
2173-
|""" + details
2171+
atPhase(typerPhase) {
2172+
em"""$clashDescription:
2173+
|${previousDecl.showDcl} ${symLocation(previousDecl)} and
2174+
|${decl.showDcl} ${symLocation(decl)}
2175+
|"""
2176+
} + details
21742177
}
21752178
def explain = ""
21762179
}

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

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import Inferencing._
2424
import transform.ValueClasses._
2525
import transform.TypeUtils._
2626
import transform.SymUtils._
27+
import TypeErasure.erasure
2728
import reporting._
2829
import config.Feature.sourceVersion
2930
import config.SourceVersion._
@@ -1238,8 +1239,64 @@ class Namer { typer: Typer =>
12381239
addForwarders(sels1, sel.name :: seen)
12391240
case _ =>
12401241

1242+
/** Avoid a clash of export forwarder `forwarder` with other forwarders in `forwarders`.
1243+
* @return If `forwarder` clashes, a new leading forwarder and trailing forwarders list
1244+
* that avoids the clash according to the scheme described in `avoidClashes`.
1245+
* If there's no clash, the inputs as they are in a pair.
1246+
*/
1247+
def avoidClashWith(forwarder: tpd.DefDef, forwarders: List[tpd.MemberDef]): (tpd.DefDef, List[tpd.MemberDef]) =
1248+
def clashes(fwd1: Symbol, fwd2: Symbol) =
1249+
fwd1.targetName == fwd2.targetName
1250+
&& erasure(fwd1.info).signature == erasure(fwd2.info).signature
1251+
1252+
forwarders match
1253+
case forwarders @ ((forwarder1: tpd.DefDef) :: forwarders1)
1254+
if forwarder.name == forwarder1.name =>
1255+
if clashes(forwarder.symbol, forwarder1.symbol) then
1256+
val alt1 = tpd.methPart(forwarder.rhs).tpe
1257+
val alt2 = tpd.methPart(forwarder1.rhs).tpe
1258+
val cmp = alt1 match
1259+
case alt1: TermRef => alt2 match
1260+
case alt2: TermRef => compare(alt1, alt2)
1261+
case _ => 0
1262+
case _ => 0
1263+
if cmp == 0 then
1264+
report.error(
1265+
ex"""Clashing exports: The exported
1266+
| ${forwarder.rhs.symbol}: ${alt1.widen}
1267+
|and ${forwarder1.rhs.symbol}: ${alt2.widen}
1268+
|have the same signature after erasure and overloading resolution could not disambiguate.""",
1269+
exp.srcPos)
1270+
avoidClashWith(if cmp < 0 then forwarder1 else forwarder, forwarders1)
1271+
else
1272+
val (forwarder2, forwarders2) = avoidClashWith(forwarder, forwarders1)
1273+
(forwarder2, forwarders.derivedCons(forwarder1, forwarders2))
1274+
case _ =>
1275+
(forwarder, forwarders)
1276+
end avoidClashWith
1277+
1278+
/** Avoid clashes of any two export forwarders in `forwarders`.
1279+
* A clash is if two forwarders f1 and f2 have the same name and signatures after erasure.
1280+
* We try to avoid a clash by dropping one of f1 and f2, keeping the one whose right hand
1281+
* side reference would be preferred by overloading resolution.
1282+
* If neither of f1 or f2 is preferred over the other, report an error.
1283+
*
1284+
* The idea is that this simulates the hypothetical case where export forwarders
1285+
* are not generated and we treat an export instead more like an import where we
1286+
* expand the use site reference. Test cases in {neg,pos}/i14699.scala.
1287+
*
1288+
* @pre Forwarders with the same name are consecutive in `forwarders`.
1289+
*/
1290+
def avoidClashes(forwarders: List[tpd.MemberDef]): List[tpd.MemberDef] = forwarders match
1291+
case forwarders @ (forwarder :: forwarders1) =>
1292+
val (forwarder2, forwarders2) = forwarder match
1293+
case forwarder: tpd.DefDef => avoidClashWith(forwarder, forwarders1)
1294+
case _ => (forwarder, forwarders1)
1295+
forwarders.derivedCons(forwarder2, avoidClashes(forwarders2))
1296+
case Nil => forwarders
1297+
12411298
addForwarders(selectors, Nil)
1242-
val forwarders = buf.toList
1299+
val forwarders = avoidClashes(buf.toList)
12431300
exp.pushAttachment(ExportForwarders, forwarders)
12441301
forwarders
12451302
end exportForwarders

tests/neg/i14966.check

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
-- Error: tests/neg/i14966.scala:10:9 ----------------------------------------------------------------------------------
2+
10 | export s.* // error
3+
| ^^^^^^^^^^
4+
| Clashing exports: The exported
5+
| method f: (x: List[Int]): Int
6+
| and method f²: (x: List[String]): Int
7+
| have the same signature after erasure and overloading resolution could not disambiguate.
8+
|
9+
| where: f is a method in trait S
10+
| f² is a method in trait I

tests/neg/i14966.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
trait I[A]:
2+
def f(x: List[String]): A
3+
4+
trait S:
5+
def f(x: List[Int]): Int
6+
7+
trait T[A] extends I[A], S
8+
9+
class Test(s: T[Int]):
10+
export s.* // error
11+

tests/neg/i14966a.check

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
-- [E120] Naming Error: tests/neg/i14966a.scala:3:6 --------------------------------------------------------------------
2+
3 | def f(x: List[Int]): String = ??? // error
3+
| ^
4+
| Double definition:
5+
| def f[X <: String](x: List[X]): String in class Test at line 2 and
6+
| def f(x: List[Int]): String in class Test at line 3
7+
| have the same type (x: scala.collection.immutable.List): String after erasure.
8+
|
9+
| Consider adding a @targetName annotation to one of the conflicting definitions
10+
| for disambiguation.

tests/neg/i14966a.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
class Test:
2+
def f[X <: String](x: List[X]): String = ???
3+
def f(x: List[Int]): String = ??? // error
4+

tests/pos/i14966.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
trait I[+A] extends IOps[A, I[A]]
2+
3+
trait S[A] extends I[A], SOps[A, S[A]]
4+
5+
trait IOps[+A, +C <: I[A]]:
6+
def concat[B >: A](other: IterableOnce[B]): C
7+
8+
trait SOps[A, +C <: S[A]] extends IOps[A, C]:
9+
def concat(other: IterableOnce[A]): C
10+
11+
class Test(s: S[Int]):
12+
export s.*
13+

tests/pos/i14966a.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
class B[T](val s: Set[T]):
2+
export s.*

0 commit comments

Comments
 (0)