diff --git a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala index 20becf1cce35..c7999b6e209d 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala @@ -168,7 +168,8 @@ enum ErrorMessageID extends java.lang.Enum[ErrorMessageID] { CannotExtendJavaEnumID, InvalidReferenceInImplicitNotFoundAnnotationID, TraitMayNotDefineNativeMethodID, - JavaEnumParentArgsID + JavaEnumParentArgsID, + AlreadyDefinedID def errorNumber = ordinal - 2 } diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index eca28faaacc8..bd8bfcfa6465 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -1847,6 +1847,19 @@ import transform.SymUtils._ def explain = "" } + class AlreadyDefined(name: Name, owner: Symbol, conflicting: Symbol)(using Context) extends NamingMsg(AlreadyDefinedID): + private def where: String = + if conflicting.effectiveOwner.is(Package) && conflicting.associatedFile != null then + i" in ${conflicting.associatedFile}" + else if conflicting.owner == owner then "" + else i" in ${conflicting.owner}" + def msg = + if conflicting.isTerm != name.isTermName then + em"$name clashes with $conflicting$where; the two must be defined together" + else + em"$name is already defined as $conflicting$where" + def explain = "" + class PackageNameAlreadyDefined(pkg: Symbol)(using Context) extends NamingMsg(PackageNameAlreadyDefinedID) { lazy val (where, or) = if pkg.associatedFile == null then ("", "") diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 53a2c40f22f3..7bd92e991354 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -746,6 +746,17 @@ trait Checking { em"Implementation restriction: ${path.tpe.classSymbol} is not a valid prefix " + "for a wildcard export, as it is a package.", path.srcPos) + /** Check that module `sym` does not clash with a class of the same name + * that is concurrently compiled in another source file. + */ + def checkNoModuleClash(sym: Symbol)(using Context): Unit = + if sym.effectiveOwner.is(Package) + && sym.owner.info.member(sym.name.moduleClassName).symbol.isAbsent() + then + val conflicting = sym.owner.info.member(sym.name.toTypeName).symbol + if conflicting.exists then + report.error(AlreadyDefined(sym.name, sym.owner, conflicting), sym.srcPos) + /** Check that `tp` is a class type. * Also, if `traitReq` is true, check that `tp` is a trait. * Also, if `stablePrefixReq` is true and phase is not after RefChecks, @@ -1266,6 +1277,7 @@ trait ReChecking extends Checking { override def checkEnumCaseRefsLegal(cdef: TypeDef, enumCtx: Context)(using Context): Unit = () override def checkAnnotApplicable(annot: Tree, sym: Symbol)(using Context): Boolean = true override def checkMatchable(tp: Type, pos: SrcPos, pattern: Boolean)(using Context): Unit = () + override def checkNoModuleClash(sym: Symbol)(using Context) = () } trait NoChecking extends ReChecking { diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index c40800862a31..e2eb42d1c3f2 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -136,11 +136,10 @@ class Namer { typer: Typer => var conflictsDetected = false def conflict(conflicting: Symbol) = - val where: String = - if conflicting.owner == owner then "" - else if conflicting.owner.isPackageObject then i" in ${conflicting.associatedFile}" - else i" in ${conflicting.owner}" - report.error(i"$name is already defined as $conflicting$where", ctx.source.atSpan(span)) + val other = + if conflicting.is(ConstructorProxy) then conflicting.companionClass + else conflicting + report.error(AlreadyDefined(name, owner, other), ctx.source.atSpan(span)) conflictsDetected = true def checkNoConflictIn(owner: Symbol) = diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index adb76201cbf2..ecfdcf64fef4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1979,6 +1979,7 @@ class Typer extends Namer val ValDef(name, tpt, _) = vdef completeAnnotations(vdef, sym) if (sym.isOneOf(GivenOrImplicit)) checkImplicitConversionDefOK(sym) + if sym.is(Module) then checkNoModuleClash(sym) val tpt1 = checkSimpleKinded(typedType(tpt)) val rhs1 = vdef.rhs match { case rhs @ Ident(nme.WILDCARD) => rhs withType tpt1.tpe diff --git a/tests/neg/i9472a.check b/tests/neg/i9472a.check new file mode 100644 index 000000000000..b90545094132 --- /dev/null +++ b/tests/neg/i9472a.check @@ -0,0 +1,4 @@ +-- [E161] Naming Error: tests/neg/i9472a/B.scala:3:0 ------------------------------------------------------------------- +3 |object Reproduction // error + |^ + |Reproduction clashes with class Reproduction in tests/neg/i9472a/A.scala; the two must be defined together diff --git a/tests/neg/i9472a/A.scala b/tests/neg/i9472a/A.scala new file mode 100644 index 000000000000..161294826607 --- /dev/null +++ b/tests/neg/i9472a/A.scala @@ -0,0 +1,4 @@ +package example.reproduction + +class Reproduction + diff --git a/tests/neg/i9472a/B.scala b/tests/neg/i9472a/B.scala new file mode 100644 index 000000000000..64df483c2a92 --- /dev/null +++ b/tests/neg/i9472a/B.scala @@ -0,0 +1,5 @@ +package example.reproduction + +object Reproduction // error + + diff --git a/tests/neg/i9472b.check b/tests/neg/i9472b.check new file mode 100644 index 000000000000..0c31a3972508 --- /dev/null +++ b/tests/neg/i9472b.check @@ -0,0 +1,4 @@ +-- [E161] Naming Error: tests/neg/i9472b/A.scala:3:0 ------------------------------------------------------------------- +3 |object Reproduction // error + |^ + |Reproduction clashes with class Reproduction in tests/neg/i9472b/B.scala; the two must be defined together diff --git a/tests/neg/i9472b/A.scala b/tests/neg/i9472b/A.scala new file mode 100644 index 000000000000..841ce25e5595 --- /dev/null +++ b/tests/neg/i9472b/A.scala @@ -0,0 +1,4 @@ +package example.reproduction + +object Reproduction // error + diff --git a/tests/neg/i9472b/B.scala b/tests/neg/i9472b/B.scala new file mode 100644 index 000000000000..161294826607 --- /dev/null +++ b/tests/neg/i9472b/B.scala @@ -0,0 +1,4 @@ +package example.reproduction + +class Reproduction + diff --git a/tests/neg/trailingCommas.scala b/tests/neg/trailingCommas.scala index ad5a0f3ea942..5cc243a9c8be 100644 --- a/tests/neg/trailingCommas.scala +++ b/tests/neg/trailingCommas.scala @@ -7,9 +7,9 @@ trait ArgumentExprs2 { validMethod(23, "bar")(Ev0, Ev1, ) } // error // error trait ArgumentExprs3 { new ValidClass(23, "bar", )(Ev0, Ev1) } // error // error trait ArgumentExprs4 { new ValidClass(23, "bar")(Ev0, Ev1, ) } // error // error -trait Params1 { def f(foo: Int, bar: String, )(implicit ev0: Ev0, ev1: Ev1, ) = 1 } // error // error // error +trait Params1 { def f(foo: Int, bar: String, )(implicit ev0: Ev0, ev1: Ev1, ) = 1 } // error // error -trait Params2 { def f(foo: Int, bar: String, )(implicit ev0: Ev0, ev1: Ev1, ) = 1 } // error // error // error +trait Params2 { def f(foo: Int, bar: String, )(implicit ev0: Ev0, ev1: Ev1, ) = 1 } // error // error trait ClassParams1 { final class C(foo: Int, bar: String, )(implicit ev0: Ev0, ev1: Ev1) } // error trait ClassParams2 { final class C(foo: Int, bar: String)(implicit ev0: Ev0, ev1: Ev1, ) } // error