From 912fce1104bc18c93bcb580477a7dada28c1d68a Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Thu, 28 Aug 2025 10:53:37 -0700 Subject: [PATCH 1/2] Mention named givens in double def explainer Print info at typer in example code. Could be automatic. --- .../dotty/tools/dotc/reporting/messages.scala | 27 +++++++---- tests/neg/i23350.check | 3 +- tests/neg/i23402.check | 3 +- tests/neg/i23832a.check | 46 +++++++++++++++++++ tests/neg/i23832a.scala | 9 ++++ tests/neg/i23832b.check | 46 +++++++++++++++++++ tests/neg/i23832b.scala | 9 ++++ 7 files changed, 130 insertions(+), 13 deletions(-) create mode 100644 tests/neg/i23832a.check create mode 100644 tests/neg/i23832a.scala create mode 100644 tests/neg/i23832b.check create mode 100644 tests/neg/i23832b.scala diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 880c8add64cf..90a3cba77a9c 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -2364,7 +2364,7 @@ class SymbolIsNotAValue(symbol: Symbol)(using Context) extends TypeMsg(SymbolIsN } class DoubleDefinition(decl: Symbol, previousDecl: Symbol, base: Symbol)(using Context) -extends NamingMsg(DoubleDefinitionID) { +extends NamingMsg(DoubleDefinitionID): import Signature.MatchDegree.* private def erasedType: Type = @@ -2426,6 +2426,16 @@ extends NamingMsg(DoubleDefinitionID) { } + details } def explain(using Context) = + def givenAddendum = + def isGivenName(sym: Symbol) = sym.name.startsWith("given_") // Desugar.inventGivenName + if decl.is(Given) && previousDecl.is(Given) && isGivenName(decl) && isGivenName(previousDecl) then + i""" + |3. Provide an explicit, unique name to given definitions, since the names + | assigned to anonymous givens may clash. For example: + | + | given myGiven: ${atPhase(typerPhase)(decl.info)} + |""" + else "" decl.signature.matchDegree(previousDecl.signature) match case FullMatch => i""" @@ -2439,8 +2449,8 @@ extends NamingMsg(DoubleDefinitionID) { | |In your code the two declarations | - | ${previousDecl.showDcl} - | ${decl.showDcl} + | ${atPhase(typerPhase)(previousDecl.showDcl)} + | ${atPhase(typerPhase)(decl.showDcl)} | |erase to the identical signature | @@ -2452,17 +2462,16 @@ extends NamingMsg(DoubleDefinitionID) { | |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: + | bytecode-level name via `@targetName`; for example: | | @targetName("${decl.name.show}_2") - | ${decl.showDcl} - | + | ${atPhase(typerPhase)(decl.showDcl)} + |$givenAddendum |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 _ => "" - -} +end DoubleDefinition class ImportedTwice(sel: Name)(using Context) extends SyntaxMsg(ImportedTwiceID) { def msg(using Context) = s"${sel.show} is imported twice on the same import line." diff --git a/tests/neg/i23350.check b/tests/neg/i23350.check index d9ae6a99cdca..801b13aeec77 100644 --- a/tests/neg/i23350.check +++ b/tests/neg/i23350.check @@ -35,12 +35,11 @@ | | 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: + | 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. - | --------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg/i23402.check b/tests/neg/i23402.check index 4a98af863348..a23221e660ed 100644 --- a/tests/neg/i23402.check +++ b/tests/neg/i23402.check @@ -35,12 +35,11 @@ | | 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: + | 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. - | --------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg/i23832a.check b/tests/neg/i23832a.check new file mode 100644 index 000000000000..aa432a836fe3 --- /dev/null +++ b/tests/neg/i23832a.check @@ -0,0 +1,46 @@ +-- [E120] Naming Error: tests/neg/i23832a.scala:9:8 -------------------------------------------------------------------- +9 | given Special[Option[Int]] = ??? // error + | ^ + | Conflicting definitions: + | final lazy given val given_Special_Option: Special[Option[Long]] in object syntax at line 8 and + | final lazy given val given_Special_Option: Special[Option[Int]] in object syntax at line 9 + |--------------------------------------------------------------------------------------------------------------------- + | 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 + | + | final lazy given val given_Special_Option: Special[Option[Long]] + | final lazy given val given_Special_Option: Special[Option[Int]] + | + | erase to the identical signature + | + | Special + | + | 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("given_Special_Option_2") + | final lazy given val given_Special_Option: Special[Option[Int]] + | + | 3. Provide an explicit, unique name to given definitions, since the names + | assigned to anonymous givens may clash. For example: + | + | given myGiven: Special[Option[Int]] + | + | 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. + --------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg/i23832a.scala b/tests/neg/i23832a.scala new file mode 100644 index 000000000000..5020c998ee96 --- /dev/null +++ b/tests/neg/i23832a.scala @@ -0,0 +1,9 @@ +//> using options -explain + +// follow-up to neg/i23402*.scala + +trait Special[A] + +object syntax: + given Special[Option[Long]] = ??? + given Special[Option[Int]] = ??? // error diff --git a/tests/neg/i23832b.check b/tests/neg/i23832b.check new file mode 100644 index 000000000000..32772cbb9b2f --- /dev/null +++ b/tests/neg/i23832b.check @@ -0,0 +1,46 @@ +-- [E120] Naming Error: tests/neg/i23832b.scala:9:8 -------------------------------------------------------------------- +9 | given [A] => Special[Option[A]] = ??? // error + | ^ + | Conflicting definitions: + | final lazy given val given_Special_Option: Special[Option[Long]] in object syntax at line 8 and + | final given def given_Special_Option[A]: Special[Option[A]] in object syntax at line 9 + |--------------------------------------------------------------------------------------------------------------------- + | 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 + | + | final lazy given val given_Special_Option: Special[Option[Long]] + | final given def given_Special_Option[A]: Special[Option[A]] + | + | erase to the identical signature + | + | (): Special + | + | 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("given_Special_Option_2") + | final given def given_Special_Option[A]: Special[Option[A]] + | + | 3. Provide an explicit, unique name to given definitions, since the names + | assigned to anonymous givens may clash. For example: + | + | given myGiven: [A]: Special[Option[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. + --------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg/i23832b.scala b/tests/neg/i23832b.scala new file mode 100644 index 000000000000..6e43ed008047 --- /dev/null +++ b/tests/neg/i23832b.scala @@ -0,0 +1,9 @@ +//> using options -explain + +// follow-up to neg/i23402*.scala + +trait Special[A] + +object syntax: + given Special[Option[Long]] = ??? + given [A] => Special[Option[A]] = ??? // error From 6fe3abcd417e32bc22e7651360ce6d39c7c44e7a Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Wed, 3 Sep 2025 08:32:34 -0700 Subject: [PATCH 2/2] Naming anon givens is a fix by renaming --- .../dotty/tools/dotc/reporting/messages.scala | 27 ++++++++++++------- tests/neg/i23350.check | 4 +-- tests/neg/i23402.check | 4 +-- tests/neg/i23832a.check | 13 +++++---- tests/neg/i23832b.check | 13 +++++---- 5 files changed, 34 insertions(+), 27 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 90a3cba77a9c..60aeb43a9b32 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -2428,13 +2428,22 @@ extends NamingMsg(DoubleDefinitionID): def explain(using Context) = def givenAddendum = def isGivenName(sym: Symbol) = sym.name.startsWith("given_") // Desugar.inventGivenName + def print(tpe: Type): String = + def addParams(tpe: Type): List[String] = tpe match + case tpe: MethodType => + val s = if tpe.isContextualMethod then i"(${tpe.paramInfos}%, %) =>" else "" + s :: addParams(tpe.resType) + case tpe: PolyType => + i"[${tpe.paramNames}%, %] =>" :: addParams(tpe.resType) + case tpe => + i"$tpe" :: Nil + addParams(tpe).mkString(" ") if decl.is(Given) && previousDecl.is(Given) && isGivenName(decl) && isGivenName(previousDecl) then - i""" - |3. Provide an explicit, unique name to given definitions, since the names - | assigned to anonymous givens may clash. For example: - | - | given myGiven: ${atPhase(typerPhase)(decl.info)} - |""" + i"""| Provide an explicit, unique name to given definitions, + | since the names assigned to anonymous givens may clash. For example: + | + | given myGiven: ${print(atPhase(typerPhase)(decl.info))} + |""" else "" decl.signature.matchDegree(previousDecl.signature) match case FullMatch => @@ -2458,15 +2467,15 @@ extends NamingMsg(DoubleDefinitionID): | |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: + |To fix this error, you must disambiguate the two definitions by doing one of the following: | - |1. Rename one of the definitions, or + |1. Rename one of the definitions.$givenAddendum |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") | ${atPhase(typerPhase)(decl.showDcl)} - |$givenAddendum + | |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. |""" diff --git a/tests/neg/i23350.check b/tests/neg/i23350.check index 801b13aeec77..ac64b3d22c1e 100644 --- a/tests/neg/i23350.check +++ b/tests/neg/i23350.check @@ -31,9 +31,9 @@ | | 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: + | To fix this error, you must disambiguate the two definitions by doing one of the following: | - | 1. Rename one of the definitions, or + | 1. Rename one of the definitions. | 2. Keep the same names in source but give one definition a distinct | bytecode-level name via `@targetName`; for example: | diff --git a/tests/neg/i23402.check b/tests/neg/i23402.check index a23221e660ed..b258ab79e75c 100644 --- a/tests/neg/i23402.check +++ b/tests/neg/i23402.check @@ -31,9 +31,9 @@ | | 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: + | To fix this error, you must disambiguate the two definitions by doing one of the following: | - | 1. Rename one of the definitions, or + | 1. Rename one of the definitions. | 2. Keep the same names in source but give one definition a distinct | bytecode-level name via `@targetName`; for example: | diff --git a/tests/neg/i23832a.check b/tests/neg/i23832a.check index aa432a836fe3..6886327484c3 100644 --- a/tests/neg/i23832a.check +++ b/tests/neg/i23832a.check @@ -27,20 +27,19 @@ | | 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: + | To fix this error, you must disambiguate the two definitions by doing one of the following: + | + | 1. Rename one of the definitions. Provide an explicit, unique name to given definitions, + | since the names assigned to anonymous givens may clash. For example: + | + | given myGiven: Special[Option[Int]] | - | 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("given_Special_Option_2") | final lazy given val given_Special_Option: Special[Option[Int]] | - | 3. Provide an explicit, unique name to given definitions, since the names - | assigned to anonymous givens may clash. For example: - | - | given myGiven: Special[Option[Int]] - | | 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. --------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg/i23832b.check b/tests/neg/i23832b.check index 32772cbb9b2f..82cb54044449 100644 --- a/tests/neg/i23832b.check +++ b/tests/neg/i23832b.check @@ -27,20 +27,19 @@ | | 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: + | To fix this error, you must disambiguate the two definitions by doing one of the following: + | + | 1. Rename one of the definitions. Provide an explicit, unique name to given definitions, + | since the names assigned to anonymous givens may clash. For example: + | + | given myGiven: [A] => Special[Option[A]] | - | 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("given_Special_Option_2") | final given def given_Special_Option[A]: Special[Option[A]] | - | 3. Provide an explicit, unique name to given definitions, since the names - | assigned to anonymous givens may clash. For example: - | - | given myGiven: [A]: Special[Option[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. ---------------------------------------------------------------------------------------------------------------------