Skip to content

Add an explainer to the DoubleDefinition error #23470

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 43 additions & 5 deletions compiler/src/dotty/tools/dotc/reporting/messages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2339,14 +2339,16 @@ class SymbolIsNotAValue(symbol: Symbol)(using Context) extends TypeMsg(SymbolIsN

class DoubleDefinition(decl: Symbol, previousDecl: Symbol, base: Symbol)(using Context)
extends NamingMsg(DoubleDefinitionID) {
import Signature.MatchDegree.*

private def erasedType: Type =
if ctx.erasedTypes then decl.info
else TypeErasure.transformInfo(decl, decl.info)

def msg(using Context) = {
def nameAnd = if (decl.name != previousDecl.name) " name and" else ""
def erasedType: Type =
if ctx.erasedTypes then decl.info
else TypeErasure.transformInfo(decl, decl.info)
def details(using Context): String =
if (decl.isRealMethod && previousDecl.isRealMethod) {
import Signature.MatchDegree.*

// compare the signatures when both symbols represent methods
decl.signature.matchDegree(previousDecl.signature) match {
Expand Down Expand Up @@ -2397,7 +2399,43 @@ extends NamingMsg(DoubleDefinitionID) {
|"""
} + details
}
def explain(using Context) = ""
def explain(using Context) =
decl.signature.matchDegree(previousDecl.signature) match
case FullMatch =>
i"""
|As part of the Scala compilation pipeline every type is reduced to its erased
|(runtime) form. In this phase, among other transformations, generic parameters
|disappear and separate parameter-list boundaries are flattened.
|
|For example, both `f[T](x: T)(y: String): Unit` and `f(x: Any, z: String): Unit`
|erase to the same runtime signature `f(x: Object, y: String): Unit`. Note that
|parameter names are irrelevant.
|
|In your code the two declarations
|
| ${previousDecl.showDcl}
| ${decl.showDcl}
|
|erase to the identical signature
|
| ${erasedType}
|
|so the compiler cannot keep both: the generated bytecode symbols would collide.
|
|To fix this error, you need to disambiguate the two definitions. You can either:
|
|1. Rename one of the definitions, or
|2. Keep the same names in source but give one definition a distinct
| bytecode-level name via `@targetName` for example:
|
| @targetName("${decl.name.show}_2")
| ${decl.showDcl}
|
|Choose the `@targetName` argument carefully: it is the name that will be used
|when calling the method externally, so it should be unique and descriptive.
"""
case _ => ""

}

class ImportedTwice(sel: Name)(using Context) extends SyntaxMsg(ImportedTwiceID) {
Expand Down
36 changes: 36 additions & 0 deletions tests/neg/doubleDefinition.check
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
|
| Consider adding a @targetName annotation to one of the conflicting definitions
| for disambiguation.
|
| longer explanation available when compiling with `-explain`
-- [E120] Naming Error: tests/neg/doubleDefinition.scala:21:5 ----------------------------------------------------------
21 | def foo(x: List[A]): Function2[B, B, B] = ??? // error
| ^
Expand All @@ -21,24 +23,32 @@
| Conflicting definitions:
| val foo: Int in class Test4 at line 25 and
| def foo: Int in class Test4 at line 26
|
| longer explanation available when compiling with `-explain`
-- [E120] Naming Error: tests/neg/doubleDefinition.scala:31:5 ----------------------------------------------------------
31 | val foo = 1 // error
| ^
| Conflicting definitions:
| def foo: Int in class Test4b at line 30 and
| val foo: Int in class Test4b at line 31
|
| longer explanation available when compiling with `-explain`
-- [E120] Naming Error: tests/neg/doubleDefinition.scala:36:5 ----------------------------------------------------------
36 | var foo = 1 // error
| ^
| Conflicting definitions:
| def foo: Int in class Test4c at line 35 and
| var foo: Int in class Test4c at line 36
|
| longer explanation available when compiling with `-explain`
-- [E120] Naming Error: tests/neg/doubleDefinition.scala:41:5 ----------------------------------------------------------
41 | def foo = 2 // error
| ^
| Conflicting definitions:
| var foo: Int in class Test4d at line 40 and
| def foo: Int in class Test4d at line 41
|
| longer explanation available when compiling with `-explain`
-- [E120] Naming Error: tests/neg/doubleDefinition.scala:55:5 ----------------------------------------------------------
55 | def foo(x: List[B]): Function1[B, B] = ??? // error: same jvm signature
| ^
Expand All @@ -49,6 +59,8 @@
|
| Consider adding a @targetName annotation to one of the conflicting definitions
| for disambiguation.
|
| longer explanation available when compiling with `-explain`
-- [E120] Naming Error: tests/neg/doubleDefinition.scala:62:5 ----------------------------------------------------------
62 | def foo(x: List[A]): Function2[B, B, B] = ??? // error
| ^
Expand All @@ -62,69 +74,93 @@
| Conflicting definitions:
| val foo: Int in class Test8 at line 66 and
| def foo: Int in class Test8 at line 67
|
| longer explanation available when compiling with `-explain`
-- [E120] Naming Error: tests/neg/doubleDefinition.scala:72:5 ----------------------------------------------------------
72 | val foo = 1 // error
| ^
| Conflicting definitions:
| def foo: Int in class Test8b at line 71 and
| val foo: Int in class Test8b at line 72
|
| longer explanation available when compiling with `-explain`
-- [E120] Naming Error: tests/neg/doubleDefinition.scala:77:5 ----------------------------------------------------------
77 | var foo = 1 // error
| ^
| Conflicting definitions:
| def foo: Int in class Test8c at line 76 and
| var foo: Int in class Test8c at line 77
|
| longer explanation available when compiling with `-explain`
-- [E120] Naming Error: tests/neg/doubleDefinition.scala:82:5 ----------------------------------------------------------
82 | def foo = 2 // error
| ^
| Conflicting definitions:
| var foo: Int in class Test8d at line 81 and
| def foo: Int in class Test8d at line 82
|
| longer explanation available when compiling with `-explain`
-- [E120] Naming Error: tests/neg/doubleDefinition.scala:88:5 ----------------------------------------------------------
88 | def foo: String // error
| ^
| Conflicting definitions:
| val foo: Int in class Test9 at line 87 and
| def foo: String in class Test9 at line 88
|
| longer explanation available when compiling with `-explain`
-- [E120] Naming Error: tests/neg/doubleDefinition.scala:92:5 ----------------------------------------------------------
92 | def foo: Int // error
| ^
| Conflicting definitions:
| val foo: Int in class Test10 at line 91 and
| def foo: Int in class Test10 at line 92
|
| longer explanation available when compiling with `-explain`
-- [E120] Naming Error: tests/neg/doubleDefinition.scala:96:5 ----------------------------------------------------------
96 | def foo: String // error
| ^
| Conflicting definitions:
| val foo: Int in class Test11 at line 95 and
| def foo: String in class Test11 at line 96
|
| longer explanation available when compiling with `-explain`
-- [E120] Naming Error: tests/neg/doubleDefinition.scala:100:5 ---------------------------------------------------------
100 | def foo: Int // error
| ^
| Conflicting definitions:
| val foo: Int in class Test12 at line 99 and
| def foo: Int in class Test12 at line 100
|
| longer explanation available when compiling with `-explain`
-- [E120] Naming Error: tests/neg/doubleDefinition.scala:104:5 ---------------------------------------------------------
104 | def foo: String // error
| ^
| Conflicting definitions:
| var foo: Int in class Test13 at line 103 and
| def foo: String in class Test13 at line 104
|
| longer explanation available when compiling with `-explain`
-- [E120] Naming Error: tests/neg/doubleDefinition.scala:108:5 ---------------------------------------------------------
108 | def foo: Int // error
| ^
| Conflicting definitions:
| var foo: Int in class Test14 at line 107 and
| def foo: Int in class Test14 at line 108
|
| longer explanation available when compiling with `-explain`
-- [E120] Naming Error: tests/neg/doubleDefinition.scala:112:5 ---------------------------------------------------------
112 | def foo: String // error
| ^
| Conflicting definitions:
| var foo: Int in class Test15 at line 111 and
| def foo: String in class Test15 at line 112
|
| longer explanation available when compiling with `-explain`
-- [E120] Naming Error: tests/neg/doubleDefinition.scala:116:5 ---------------------------------------------------------
116 | def foo: Int // error
| ^
| Conflicting definitions:
| var foo: Int in class Test16 at line 115 and
| def foo: Int in class Test16 at line 116
|
| longer explanation available when compiling with `-explain`
8 changes: 8 additions & 0 deletions tests/neg/exports.check
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
|
| Consider adding a @targetName annotation to one of the conflicting definitions
| for disambiguation.
|
| longer explanation available when compiling with `-explain`
-- [E120] Naming Error: tests/neg/exports.scala:24:20 ------------------------------------------------------------------
24 | export scanUnit._ // error: double definition
| ^
Expand All @@ -32,6 +34,8 @@
|
| Consider adding a @targetName annotation to one of the conflicting definitions
| for disambiguation.
|
| longer explanation available when compiling with `-explain`
-- [E120] Naming Error: tests/neg/exports.scala:26:21 ------------------------------------------------------------------
26 | export printUnit.status // error: double definition
| ^
Expand All @@ -42,6 +46,8 @@
|
| Consider adding a @targetName annotation to one of the conflicting definitions
| for disambiguation.
|
| longer explanation available when compiling with `-explain`
-- Error: tests/neg/exports.scala:35:24 --------------------------------------------------------------------------------
35 | export this.{concat => ++} // error: no eligible member
| ^^^^^^^^^^^^
Expand All @@ -58,6 +64,8 @@
| Conflicting definitions:
| val bar: Bar in class Baz at line 45 and
| final def bar: (Baz.this.bar.bar : => (Baz.this.bar.baz.bar : Bar)) in class Baz at line 46
|
| longer explanation available when compiling with `-explain`
-- [E083] Type Error: tests/neg/exports.scala:57:11 --------------------------------------------------------------------
57 | export printer.* // error: not stable
| ^^^^^^^
Expand Down
2 changes: 2 additions & 0 deletions tests/neg/i14966a.check
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@
|
| Consider adding a @targetName annotation to one of the conflicting definitions
| for disambiguation.
|
| longer explanation available when compiling with `-explain`
2 changes: 2 additions & 0 deletions tests/neg/i19809.check
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@
|
| Consider adding a @targetName annotation to one of the conflicting definitions
| for disambiguation.
|
| longer explanation available when compiling with `-explain`
46 changes: 46 additions & 0 deletions tests/neg/i23350.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
-- [E120] Naming Error: tests/neg/i23350.scala:8:7 ---------------------------------------------------------------------
8 |object D extends A: // error
| ^
| Name clash between defined and inherited member:
| def apply(p: A.this.Props): Unit in class A at line 5 and
| def apply(a: UndefOr2[String]): Unit in object D at line 10
| have the same type (a: Object): Unit after erasure.
|
| Consider adding a @targetName annotation to one of the conflicting definitions
| for disambiguation.
|---------------------------------------------------------------------------------------------------------------------
| Explanation (enabled by `-explain`)
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
| As part of the Scala compilation pipeline every type is reduced to its erased
| (runtime) form. In this phase, among other transformations, generic parameters
| disappear and separate parameter-list boundaries are flattened.
|
| For example, both `f[T](x: T)(y: String): Unit` and `f(x: Any, z: String): Unit`
| erase to the same runtime signature `f(x: Object, y: String): Unit`. Note that
| parameter names are irrelevant.
|
| In your code the two declarations
|
| def apply(p: A.this.Props): Unit
| def apply(a: UndefOr2[String]): Unit
|
| erase to the identical signature
|
| (a: Object): Unit
|
| so the compiler cannot keep both: the generated bytecode symbols would collide.
|
| To fix this error, you need to disambiguate the two definitions. You can either:
|
| 1. Rename one of the definitions, or
| 2. Keep the same names in source but give one definition a distinct
| bytecode-level name via `@targetName` for example:
|
| @targetName("apply_2")
| def apply(a: UndefOr2[String]): Unit
|
| Choose the `@targetName` argument carefully: it is the name that will be used
| when calling the method externally, so it should be unique and descriptive.
|
---------------------------------------------------------------------------------------------------------------------
10 changes: 10 additions & 0 deletions tests/neg/i23350.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//> using options -explain

abstract class A:
type Props
def apply(p: Props) = ()

type UndefOr2[A] = A | Unit
object D extends A: // error
case class Props()
def apply(a: UndefOr2[String]) = ()
44 changes: 40 additions & 4 deletions tests/neg/i23402.check
Original file line number Diff line number Diff line change
@@ -1,10 +1,46 @@
-- [E120] Naming Error: tests/neg/i23402.scala:4:5 ---------------------------------------------------------------------
4 | def apply(p1: String)(p2: Int): A = A(p1, p2) // error
-- [E120] Naming Error: tests/neg/i23402.scala:6:5 ---------------------------------------------------------------------
6 | def apply(p1: String)(p2: Int): A = A(p1, p2) // error
| ^
| Conflicting definitions:
| def apply(p1: String, p2: Int): A in object A at line 3 and
| def apply(p1: String)(p2: Int): A in object A at line 4
| def apply(p1: String, p2: Int): A in object A at line 5 and
| def apply(p1: String)(p2: Int): A in object A at line 6
| have the same type (p1: String, p2: Int): A after erasure.
|
| Consider adding a @targetName annotation to one of the conflicting definitions
| for disambiguation.
|---------------------------------------------------------------------------------------------------------------------
| Explanation (enabled by `-explain`)
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
| As part of the Scala compilation pipeline every type is reduced to its erased
| (runtime) form. In this phase, among other transformations, generic parameters
| disappear and separate parameter-list boundaries are flattened.
|
| For example, both `f[T](x: T)(y: String): Unit` and `f(x: Any, z: String): Unit`
| erase to the same runtime signature `f(x: Object, y: String): Unit`. Note that
| parameter names are irrelevant.
|
| In your code the two declarations
|
| def apply(p1: String, p2: Int): A
| def apply(p1: String)(p2: Int): A
|
| erase to the identical signature
|
| (p1: String, p2: Int): A
|
| so the compiler cannot keep both: the generated bytecode symbols would collide.
|
| To fix this error, you need to disambiguate the two definitions. You can either:
|
| 1. Rename one of the definitions, or
| 2. Keep the same names in source but give one definition a distinct
| bytecode-level name via `@targetName` for example:
|
| @targetName("apply_2")
| def apply(p1: String)(p2: Int): A
|
| Choose the `@targetName` argument carefully: it is the name that will be used
| when calling the method externally, so it should be unique and descriptive.
|
---------------------------------------------------------------------------------------------------------------------
2 changes: 2 additions & 0 deletions tests/neg/i23402.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//> using options -explain

class A(p1: String, p2: Int)
object A {
def apply(p1: String, p2: Int): A = A(p1, p2)
Expand Down
2 changes: 2 additions & 0 deletions tests/neg/i23402b.check
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@
|
| Consider adding a @targetName annotation to one of the conflicting definitions
| for disambiguation.
|
| longer explanation available when compiling with `-explain`
2 changes: 2 additions & 0 deletions tests/neg/i23402c.check
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@
|
| Consider adding a @targetName annotation to one of the conflicting definitions
| for disambiguation.
|
| longer explanation available when compiling with `-explain`
12 changes: 12 additions & 0 deletions tests/neg/i23402d.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
-- [E120] Naming Error: tests/neg/i23402d.scala:5:4 --------------------------------------------------------------------
5 |def f(x: Any): Unit = ??? // error
| ^
| Conflicting definitions:
| def f[T](x: T): Unit in the top-level definitions in package <empty> at line 4 and
| def f(x: Any): Unit in the top-level definitions in package <empty> at line 5
| have the same type (x: Object): Unit after erasure.
|
| Consider adding a @targetName annotation to one of the conflicting definitions
| for disambiguation.
|
| longer explanation available when compiling with `-explain`
Loading
Loading