Skip to content

Commit f7c25f7

Browse files
committed
Detect double toplevel definitions in separate files
Flag as error if two separate files contain toplevel definitions with the same name.
1 parent e5b3023 commit f7c25f7

File tree

10 files changed

+68
-23
lines changed

10 files changed

+68
-23
lines changed

Diff for: compiler/src/dotty/tools/dotc/core/SymDenotations.scala

+3-3
Original file line numberDiff line numberDiff line change
@@ -2178,17 +2178,17 @@ object SymDenotations {
21782178
recur(packageObjs, super.memberNames(keepOnly))
21792179
}
21802180

2181-
/** If another symbol with the same name is entered, unlink it,
2182-
* and, if symbol is a package object, invalidate the packageObj cache.
2181+
/** If another symbol with the same name is entered, unlink it.
2182+
* If symbol is a package object, invalidate the packageObj cache.
21832183
* @return `sym` is not already entered
21842184
*/
21852185
override def proceedWithEnter(sym: Symbol, mscope: MutableScope)(implicit ctx: Context): Boolean = {
21862186
val entry = mscope.lookupEntry(sym.name)
21872187
if (entry != null) {
21882188
if (entry.sym == sym) return false
21892189
mscope.unlink(entry)
2190-
if (sym.name.isPackageObjectName) packageObjsRunId = NoRunId
21912190
}
2191+
if (sym.name.isPackageObjectName) packageObjsRunId = NoRunId
21922192
true
21932193
}
21942194

Diff for: compiler/src/dotty/tools/dotc/typer/Namer.scala

+39-15
Original file line numberDiff line numberDiff line change
@@ -302,21 +302,45 @@ class Namer { typer: Typer =>
302302

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

305-
def checkNoConflict(name: Name): Name = {
306-
def errorName(msg: => String) = {
307-
ctx.error(msg, tree.sourcePos)
308-
name.freshened
309-
}
305+
def checkNoConflict(name: Name, isPrivate: Boolean): Name =
306+
val owner = ctx.owner
307+
var conflictsDetected = false
308+
309+
def conflict(conflicting: Symbol) =
310+
val where: String =
311+
if conflicting.owner == owner then ""
312+
else if conflicting.owner.isPackageObject then i" in ${conflicting.associatedFile}"
313+
else i" in ${conflicting.owner}"
314+
ctx.error(i"$name is already defined as $conflicting$where", tree.sourcePos)
315+
conflictsDetected = true
316+
317+
def checkNoConflictWith(preExisting: Symbol) =
318+
if (preExisting.isDefinedInCurrentRun || preExisting.is(Package))
319+
&& (!preExisting.is(Private) || preExisting.owner.is(Package))
320+
then conflict(preExisting)
321+
322+
def checkNoConflictIn(owner: Symbol) =
323+
checkNoConflictWith( owner.unforcedDecls.lookup(name))
324+
325+
def pkgObjs(pkg: Symbol) =
326+
pkg.denot.asInstanceOf[PackageClassDenotation].packageObjs.map(_.symbol)
327+
310328
def preExisting = ctx.effectiveScope.lookup(name)
311-
if (ctx.owner.is(PackageClass))
312-
if (preExisting.isDefinedInCurrentRun)
313-
errorName(s"${preExisting.showLocated} has already been compiled once during this run")
314-
else name
329+
if owner.is(PackageClass) then
330+
checkNoConflictWith(preExisting)
331+
return name
332+
for pkgObj <- pkgObjs(owner) do
333+
checkNoConflictIn(pkgObj)
315334
else
316-
if ((!ctx.owner.isClass || name.isTypeName) && preExisting.exists)
317-
errorName(i"$name is already defined as $preExisting")
318-
else name
319-
}
335+
if (!owner.isClass || name.isTypeName) && preExisting.exists then
336+
conflict(preExisting)
337+
else if owner.isPackageObject && !isPrivate && name != nme.CONSTRUCTOR then
338+
checkNoConflictIn(owner.owner)
339+
for pkgObj <- pkgObjs(owner.owner) if pkgObj != owner do
340+
checkNoConflictIn(pkgObj)
341+
342+
if conflictsDetected then name.freshened else name
343+
end checkNoConflict
320344

321345
/** Create new symbol or redefine existing symbol under lateCompile. */
322346
def createOrRefine[S <: Symbol](
@@ -348,17 +372,17 @@ class Namer { typer: Typer =>
348372

349373
tree match {
350374
case tree: TypeDef if tree.isClassDef =>
351-
val name = checkNoConflict(tree.name).asTypeName
352375
val flags = checkFlags(tree.mods.flags &~ GivenOrImplicit)
376+
val name = checkNoConflict(tree.name, flags.is(Private)).asTypeName
353377
val cls =
354378
createOrRefine[ClassSymbol](tree, name, flags, ctx.owner,
355379
cls => adjustIfModule(new ClassCompleter(cls, tree)(ctx), tree),
356380
ctx.newClassSymbol(ctx.owner, name, _, _, _, tree.nameSpan, ctx.source.file))
357381
cls.completer.asInstanceOf[ClassCompleter].init()
358382
cls
359383
case tree: MemberDef =>
360-
val name = checkNoConflict(tree.name)
361384
var flags = checkFlags(tree.mods.flags)
385+
val name = checkNoConflict(tree.name, flags.is(Private))
362386
tree match
363387
case tree: ValOrDefDef =>
364388
if tree.unforcedRhs == EmptyTree

Diff for: compiler/src/dotty/tools/dotc/typer/Typer.scala

+3-4
Original file line numberDiff line numberDiff line change
@@ -1875,10 +1875,10 @@ class Typer extends Namer
18751875
assignType(cpy.Import(imp)(expr1, selectors1), sym)
18761876
}
18771877

1878-
def typedPackageDef(tree: untpd.PackageDef)(implicit ctx: Context): Tree = {
1878+
def typedPackageDef(tree: untpd.PackageDef)(implicit ctx: Context): Tree =
18791879
val pid1 = typedExpr(tree.pid, AnySelectionProto)(ctx.addMode(Mode.InPackageClauseName))
18801880
val pkg = pid1.symbol
1881-
pid1 match {
1881+
pid1 match
18821882
case pid1: RefTree if pkg.is(Package) =>
18831883
val packageCtx = ctx.packageContext(tree, pkg)
18841884
var stats1 = typedStats(tree.stats, pkg.moduleClass)(packageCtx)._1
@@ -1890,8 +1890,7 @@ class Typer extends Namer
18901890
errorTree(tree,
18911891
if pkg.exists then PackageNameAlreadyDefined(pkg)
18921892
else i"package ${tree.pid.name} does not exist")
1893-
}
1894-
}
1893+
end typedPackageDef
18951894

18961895
def typedAnnotated(tree: untpd.Annotated, pt: Type)(implicit ctx: Context): Tree = {
18971896
val annot1 = typedExpr(tree.annot, defn.AnnotationClass.typeRef)

Diff for: tests/neg/toplevel-doubledef/defs_1.scala

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
def hello(x: String): Unit =
2+
println(s"hello: $x")

Diff for: tests/neg/toplevel-doubledef/moredefs_1.scala

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
def hello(x: String): Unit = // error: has already been compiled
2+
println(s"hi, $x")

Diff for: tests/neg/toplevel-overload/defs_1.scala

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
trait A
2+
3+
def f(x: A) = s"A"
4+
5+
def g(): Unit = ()

Diff for: tests/neg/toplevel-overload/moredefs_1.scala

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
trait B
2+
3+
def f(x: B) = s"B" // error: has already been compiled
4+
5+
private def g(): Unit = () // OK, since it is private

Diff for: tests/neg/toplevel-package/defs_1.scala

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package foo
2+
package bar
3+
class C
4+
def hello(x: String): Unit =
5+
println(s"hello: $x")
6+

Diff for: tests/neg/toplevel-package/moredefs_2.scala

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
package foo
2+
def bar = 2 // error: bar is a package

Diff for: tests/run/toplevel-mixed/B_1.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@ object X2 {
99
def bar = "hi"
1010
}
1111

12-
class X3
12+
class X4
1313

0 commit comments

Comments
 (0)