Skip to content

Commit 811dc19

Browse files
authored
Merge pull request #8341 from dotty-staging/fix-#8333
Fix #8333: Check for duplicate symbols in exports
2 parents 93f65c6 + 4ae6522 commit 811dc19

File tree

2 files changed

+66
-51
lines changed

2 files changed

+66
-51
lines changed

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

Lines changed: 53 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,49 @@ class Namer { typer: Typer =>
265265
sym
266266
}
267267

268+
/** Check that a new definition with given name and privacy status
269+
* in current context would not conflict with existing currently
270+
* compiled definitions.
271+
* The logic here is very subtle and fragile due to the fact that
272+
* we are not allowed to force anything.
273+
*/
274+
def checkNoConflict(name: Name, isPrivate: Boolean, span: Span)(using ctx: Context): Name =
275+
val owner = ctx.owner
276+
var conflictsDetected = false
277+
278+
def conflict(conflicting: Symbol) =
279+
val where: String =
280+
if conflicting.owner == owner then ""
281+
else if conflicting.owner.isPackageObject then i" in ${conflicting.associatedFile}"
282+
else i" in ${conflicting.owner}"
283+
ctx.error(i"$name is already defined as $conflicting$where", ctx.source.atSpan(span))
284+
conflictsDetected = true
285+
286+
def checkNoConflictIn(owner: Symbol) =
287+
val preExisting = owner.unforcedDecls.lookup(name)
288+
if (preExisting.isDefinedInCurrentRun || preExisting.lastKnownDenotation.is(Package))
289+
&& (!preExisting.lastKnownDenotation.is(Private) || preExisting.owner.is(Package))
290+
then conflict(preExisting)
291+
292+
def pkgObjs(pkg: Symbol) =
293+
pkg.denot.asInstanceOf[PackageClassDenotation].packageObjs.map(_.symbol)
294+
295+
if owner.is(PackageClass) then
296+
checkNoConflictIn(owner)
297+
for pkgObj <- pkgObjs(owner) do
298+
checkNoConflictIn(pkgObj)
299+
else
300+
def preExisting = ctx.effectiveScope.lookup(name)
301+
if (!owner.isClass || name.isTypeName) && preExisting.exists then
302+
conflict(preExisting)
303+
else if owner.isPackageObject && !isPrivate && name != nme.CONSTRUCTOR then
304+
checkNoConflictIn(owner.owner)
305+
for pkgObj <- pkgObjs(owner.owner) if pkgObj != owner do
306+
checkNoConflictIn(pkgObj)
307+
308+
if conflictsDetected then name.freshened else name
309+
end checkNoConflict
310+
268311
/** If this tree is a member def or an import, create a symbol of it
269312
* and store in symOfTree map.
270313
*/
@@ -302,49 +345,6 @@ class Namer { typer: Typer =>
302345

303346
typr.println(i"creating symbol for $tree in ${ctx.mode}")
304347

305-
/** Check that a new definition with given name and privacy status
306-
* in current context would not conflict with existing currently
307-
* compiled definitions.
308-
* The logic here is very subtle and fragile due to the fact that
309-
* we are not allowed to force anything.
310-
*/
311-
def checkNoConflict(name: Name, isPrivate: Boolean): Name =
312-
val owner = ctx.owner
313-
var conflictsDetected = false
314-
315-
def conflict(conflicting: Symbol) =
316-
val where: String =
317-
if conflicting.owner == owner then ""
318-
else if conflicting.owner.isPackageObject then i" in ${conflicting.associatedFile}"
319-
else i" in ${conflicting.owner}"
320-
ctx.error(i"$name is already defined as $conflicting$where", tree.sourcePos)
321-
conflictsDetected = true
322-
323-
def checkNoConflictIn(owner: Symbol) =
324-
val preExisting = owner.unforcedDecls.lookup(name)
325-
if (preExisting.isDefinedInCurrentRun || preExisting.lastKnownDenotation.is(Package))
326-
&& (!preExisting.lastKnownDenotation.is(Private) || preExisting.owner.is(Package))
327-
then conflict(preExisting)
328-
329-
def pkgObjs(pkg: Symbol) =
330-
pkg.denot.asInstanceOf[PackageClassDenotation].packageObjs.map(_.symbol)
331-
332-
if owner.is(PackageClass) then
333-
checkNoConflictIn(owner)
334-
for pkgObj <- pkgObjs(owner) do
335-
checkNoConflictIn(pkgObj)
336-
else
337-
def preExisting = ctx.effectiveScope.lookup(name)
338-
if (!owner.isClass || name.isTypeName) && preExisting.exists then
339-
conflict(preExisting)
340-
else if owner.isPackageObject && !isPrivate && name != nme.CONSTRUCTOR then
341-
checkNoConflictIn(owner.owner)
342-
for pkgObj <- pkgObjs(owner.owner) if pkgObj != owner do
343-
checkNoConflictIn(pkgObj)
344-
345-
if conflictsDetected then name.freshened else name
346-
end checkNoConflict
347-
348348
/** Create new symbol or redefine existing symbol under lateCompile. */
349349
def createOrRefine[S <: Symbol](
350350
tree: MemberDef, name: Name, flags: FlagSet, owner: Symbol, infoFn: S => Type,
@@ -376,7 +376,7 @@ class Namer { typer: Typer =>
376376
tree match {
377377
case tree: TypeDef if tree.isClassDef =>
378378
val flags = checkFlags(tree.mods.flags &~ GivenOrImplicit)
379-
val name = checkNoConflict(tree.name, flags.is(Private)).asTypeName
379+
val name = checkNoConflict(tree.name, flags.is(Private), tree.span).asTypeName
380380
val cls =
381381
createOrRefine[ClassSymbol](tree, name, flags, ctx.owner,
382382
cls => adjustIfModule(new ClassCompleter(cls, tree)(ctx), tree),
@@ -385,7 +385,7 @@ class Namer { typer: Typer =>
385385
cls
386386
case tree: MemberDef =>
387387
var flags = checkFlags(tree.mods.flags)
388-
val name = checkNoConflict(tree.name, flags.is(Private))
388+
val name = checkNoConflict(tree.name, flags.is(Private), tree.span)
389389
tree match
390390
case tree: ValOrDefDef =>
391391
if tree.unforcedRhs == EmptyTree
@@ -1079,9 +1079,10 @@ class Namer { typer: Typer =>
10791079
if (whyNoForwarder(mbr) == "") {
10801080
val sym = mbr.symbol
10811081
val forwarder =
1082-
if (mbr.isType)
1082+
if mbr.isType then
1083+
val forwarderName = checkNoConflict(alias.toTypeName, isPrivate = false, span)
10831084
ctx.newSymbol(
1084-
cls, alias.toTypeName,
1085+
cls, forwarderName,
10851086
Exported | Final,
10861087
TypeAlias(path.tpe.select(sym)),
10871088
coord = span)
@@ -1096,7 +1097,8 @@ class Namer { typer: Typer =>
10961097
else
10971098
(EmptyFlags, mbr.info.ensureMethodic)
10981099
val mbrFlags = Exported | Method | Final | maybeStable | sym.flags & RetainedExportFlags
1099-
ctx.newSymbol(cls, alias, mbrFlags, mbrInfo, coord = span)
1100+
val forwarderName = checkNoConflict(alias, isPrivate = false, span)
1101+
ctx.newSymbol(cls, forwarderName, mbrFlags, mbrInfo, coord = span)
11001102
}
11011103
forwarder.info = avoidPrivateLeaks(forwarder)
11021104
val forwarderDef =
@@ -1146,9 +1148,9 @@ class Namer { typer: Typer =>
11461148
forwarders
11471149
}
11481150

1149-
val forwarderss =
1150-
for (exp @ Export(_, _) <- rest) yield exportForwarders(exp)
1151-
forwarderss.foreach(_.foreach(fwdr => fwdr.symbol.entered))
1151+
for case exp @ Export(_, _) <- rest do
1152+
for forwarder <- exportForwarders(exp) do
1153+
forwarder.symbol.entered
11521154
}
11531155

11541156
/** Ensure constructor is completed so that any parameter accessors

tests/neg/i8333.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
class A:
2+
type T = Int // can also be class T
3+
class B(x: A, y: A):
4+
export x._
5+
export y._ // error: duplicate
6+
class C(x: A):
7+
type T = String
8+
export x._ // error: duplicate
9+
class D(x: A):
10+
export x._ // error: duplicate
11+
type T = String
12+
13+

0 commit comments

Comments
 (0)