Skip to content

Commit 8d1c435

Browse files
committed
Avoid export clashes
Avoid export clashes in most cases by picking the alternative which would be preferred by overloading resolution. If none is, issue a more intelligible error message than before.
1 parent 3c6425b commit 8d1c435

File tree

5 files changed

+94
-13
lines changed

5 files changed

+94
-13
lines changed

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._
@@ -1237,8 +1238,64 @@ class Namer { typer: Typer =>
12371238
addForwarders(sels1, sel.name :: seen)
12381239
case _ =>
12391240

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

tests/neg/i14966.check

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
-- [E120] Naming Error: tests/neg/i14966.scala:2:11 --------------------------------------------------------------------
2-
2 | export s.* // error
3-
| ^
4-
| Double definition:
5-
| final def ++[B >: T](suffix: IterableOnce[B]): Set[B] in class B at line 2 and
6-
| final def ++(that: IterableOnce[T]): Set[T] in class B at line 2
7-
| have the same type after erasure.
8-
|
9-
| Consider adding a @targetName annotation to one of the conflicting definitions
10-
| for disambiguation.
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 & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,11 @@
1-
class B[T](val s: Set[T]):
2-
export s.* // error
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/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)