Skip to content

Commit 4d6f215

Browse files
committed
Detect clashes involving renamed exports
Fixes #14818
1 parent 4a96ce7 commit 4d6f215

File tree

3 files changed

+50
-1
lines changed

3 files changed

+50
-1
lines changed

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1064,11 +1064,23 @@ class Namer { typer: Typer =>
10641064
/** The forwarders defined by export `exp` */
10651065
private def exportForwarders(exp: Export)(using Context): List[tpd.MemberDef] =
10661066
val buf = new mutable.ListBuffer[tpd.MemberDef]
1067-
val Export(expr, selectors) = exp
1067+
val Export(expr, selectors0) = exp
10681068
if expr.isEmpty then
10691069
report.error(em"Export selector must have prefix and `.`", exp.srcPos)
10701070
return Nil
10711071

1072+
val renamed = mutable.Set[Name]()
1073+
val selectors = selectors0 map {
1074+
case sel @ ImportSelector(imported, id @ Ident(alias), bound) if alias != nme.WILDCARD =>
1075+
if renamed.contains(alias) then
1076+
report.error(i"duplicate rename target", id.srcPos)
1077+
cpy.ImportSelector(sel)(imported, EmptyTree, bound).asInstanceOf[ImportSelector]
1078+
else
1079+
renamed += alias
1080+
sel
1081+
case sel => sel
1082+
}
1083+
10721084
val path = typedAheadExpr(expr, AnySelectionProto)
10731085
checkLegalExportPath(path, selectors)
10741086
lazy val wildcardBound = importBound(selectors, isGiven = false)
@@ -1083,6 +1095,8 @@ class Namer { typer: Typer =>
10831095
Skip
10841096
else if cls.derivesFrom(sym.owner) && (sym.owner == cls || !sym.is(Deferred)) then
10851097
No(i"is already a member of $cls")
1098+
else if renamed.contains(sym.name.toTermName) then
1099+
No(i"clashes with a renamed export")
10861100
else if sym.is(Override) then
10871101
sym.allOverriddenSymbols.find(
10881102
other => cls.derivesFrom(other.owner) && !other.is(Deferred)

tests/neg/i14818.check

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
-- Error: tests/neg/i14818.scala:9:12 ----------------------------------------------------------------------------------
2+
9 | export M.{A, B as A} // error
3+
| ^
4+
| no eligible member A at M
5+
| M.A cannot be exported because it clashes with a renamed export
6+
-- [E050] Type Error: tests/neg/i14818.scala:16:10 ---------------------------------------------------------------------
7+
16 | val x = b(1) // error
8+
| ^
9+
| method b in object T3 does not take parameters
10+
|
11+
| longer explanation available when compiling with `-explain`
12+
-- Error: tests/neg/i14818.scala:19:25 ---------------------------------------------------------------------------------
13+
19 | export M.{A as C, B as C} // error
14+
| ^
15+
| duplicate rename target

tests/neg/i14818.scala

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
object M {
2+
type A
3+
type B
4+
def a = 1
5+
def b(x: Int) = x
6+
}
7+
8+
object T1:
9+
export M.{A, B as A} // error
10+
11+
object T2:
12+
export M.{A as B, *}
13+
14+
object T3:
15+
export M.{a as b, *}
16+
val x = b(1) // error
17+
18+
object T4:
19+
export M.{A as C, B as C} // error
20+

0 commit comments

Comments
 (0)