From 118f419d8fb5a11a4d639ae09a8e323be29b4597 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 2 Nov 2020 12:55:19 +0100 Subject: [PATCH 01/20] Rename @alpha to @targetName --- .../tools/dotc/config/ScalaSettings.scala | 2 +- .../dotty/tools/dotc/core/Definitions.scala | 2 +- .../tools/dotc/core/SymDenotations.scala | 25 ++++++++----------- .../src/dotty/tools/dotc/typer/Checking.scala | 24 ++++++++---------- .../dotty/tools/dotc/typer/RefChecks.scala | 4 +-- .../src/dotty/tools/dotc/typer/Typer.scala | 4 +-- .../dotty/tools/dotc/CompilationTests.scala | 2 +- library/src/scala/annotation/alpha.scala | 1 + library/src/scala/annotation/targetName.scala | 8 ++++++ tests/neg-custom-args/missing-alpha.scala | 4 +-- tests/neg/7722.check | 8 +++--- tests/neg/7722.scala | 2 +- tests/neg/alpha-early.scala | 8 +++--- tests/neg/alpha-late.scala | 4 +-- tests/neg/alpha-override/C_2.scala | 4 +-- tests/neg/alpha-override1/D_2.scala | 6 ++--- tests/neg/alpha.scala | 10 ++++---- tests/neg/doubleDefinition-late.scala | 4 +-- tests/pos/alpha-override/A_1.scala | 4 +-- tests/pos/alpha.scala | 4 +-- tests/pos/i8391.scala | 4 +-- tests/run/alpha-interop/alpha_1.scala | 12 ++++----- tests/run/alpha-modules-1/7721_1.scala | 2 +- tests/run/alpha-modules-2/7723_1.scala | 2 +- tests/run/alpha.scala | 4 +-- tests/run/i9155.scala | 2 +- tests/run/infix.scala | 4 +-- 27 files changed, 81 insertions(+), 79 deletions(-) create mode 100644 library/src/scala/annotation/targetName.scala diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index a57b2ee30ac9..9b8193648a62 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -173,7 +173,7 @@ class ScalaSettings extends Settings.SettingGroup { val YexplicitNulls: Setting[Boolean] = BooleanSetting("-Yexplicit-nulls", "Make reference types non-nullable. Nullable types can be expressed with unions: e.g. String|Null.") val YerasedTerms: Setting[Boolean] = BooleanSetting("-Yerased-terms", "Allows the use of erased terms.") val YcheckInit: Setting[Boolean] = BooleanSetting("-Ycheck-init", "Check initialization of objects") - val YrequireAlpha: Setting[Boolean] = BooleanSetting("-Yrequire-alpha", "Warn if an operator is defined without an @alpha annotation") + val YrequireTargetName: Setting[Boolean] = BooleanSetting("-Yrequire-targetName", "Warn if an operator is defined without a @targetName annotation") /** Area-specific debug output */ val YexplainLowlevel: Setting[Boolean] = BooleanSetting("-Yexplain-lowlevel", "When explaining type errors, show types at a lower level.") diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 9c8f9fdab6af..23ae40aa4aea 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -937,7 +937,7 @@ class Definitions { @tu lazy val ShowAsInfixAnnot: ClassSymbol = requiredClass("scala.annotation.showAsInfix") @tu lazy val FunctionalInterfaceAnnot: ClassSymbol = requiredClass("java.lang.FunctionalInterface") @tu lazy val InfixAnnot: ClassSymbol = requiredClass("scala.annotation.infix") - @tu lazy val AlphaAnnot: ClassSymbol = requiredClass("scala.annotation.alpha") + @tu lazy val TargetNameAnnot: ClassSymbol = requiredClass("scala.annotation.targetName") @tu lazy val VarargsAnnot: ClassSymbol = requiredClass("scala.annotation.varargs") // A list of annotations that are commonly used to indicate that a field/method argument or return diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 6f10d7aa2d87..66ec6bfdbb31 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -495,26 +495,21 @@ object SymDenotations { /** `fullName` where `.' is the separator character */ def fullName(using Context): Name = fullNameSeparated(QualifiedName) - /** The name given in an `@alpha` annotation if one is present, `name` otherwise */ + /** The name given in a `@targetName` annotation if one is present, `name` otherwise */ final def erasedName(using Context): Name = - val alphaAnnot = - if isAllOf(ModuleClass | Synthetic) then companionClass.getAnnotation(defn.AlphaAnnot) - else getAnnotation(defn.AlphaAnnot) - alphaAnnot match { + val targetNameAnnot = + if isAllOf(ModuleClass | Synthetic) then companionClass.getAnnotation(defn.TargetNameAnnot) + else getAnnotation(defn.TargetNameAnnot) + targetNameAnnot match case Some(ann) => - ann.arguments match { + ann.arguments match case Literal(Constant(str: String)) :: Nil => - if (isType) - if (is(ModuleClass)) - str.toTypeName.moduleClassName - else - str.toTypeName - else - str.toTermName + if isType then + if is(ModuleClass) then str.toTypeName.moduleClassName + else str.toTypeName + else str.toTermName case _ => name - } case _ => name - } // ----- Tests ------------------------------------------------- diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 7f1ebc490e53..f6fdee2c81d2 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -308,19 +308,20 @@ object Checking { } } - /** If `sym` has an operator name, check that it has an @alpha annotation in 3.1 and later + /** Under -Yrequire-targetName, if `sym` has an operator name, check that it has a + * @targetName annotation. */ def checkValidOperator(sym: Symbol)(using Context): Unit = - if ctx.settings.YrequireAlpha.value then + if ctx.settings.YrequireTargetName.value then sym.name.toTermName match case name: SimpleName if name.isOperatorName && !name.isSetterName && !name.isConstructorName - && !sym.getAnnotation(defn.AlphaAnnot).isDefined + && !sym.getAnnotation(defn.TargetNameAnnot).isDefined && !sym.is(Synthetic) => report.warning( - i"$sym has an operator name; it should come with an @alpha annotation", sym.srcPos) + i"$sym has an operator name; it should come with an @targetName annotation", sym.srcPos) case _ => /** Check that `info` of symbol `sym` is not cyclic. @@ -1203,19 +1204,16 @@ trait Checking { } /** Check that symbol's external name does not clash with symbols defined in the same scope */ - def checkNoAlphaConflict(stats: List[Tree])(using Context): Unit = { + def checkNoTargetNameConflict(stats: List[Tree])(using Context): Unit = var seen = Set[Name]() - for (stat <- stats) { + for stat <- stats do val sym = stat.symbol val ename = sym.erasedName - if (ename != sym.name) { + if ename != sym.name then val preExisting = ctx.effectiveScope.lookup(ename) - if (preExisting.exists || seen.contains(ename)) - report.error(em"@alpha annotation ${'"'}$ename${'"'} clashes with other definition is same scope", stat.srcPos) + if preExisting.exists || seen.contains(ename) then + report.error(em"@targetName annotation ${'"'}$ename${'"'} clashes with other definition in same scope", stat.srcPos) if stat.isDef then seen += ename - } - } - } } trait ReChecking extends Checking { @@ -1238,7 +1236,7 @@ trait NoChecking extends ReChecking { override def checkImplicitConversionUseOK(tree: Tree)(using Context): Unit = () override def checkFeasibleParent(tp: Type, pos: SrcPos, where: => String = "")(using Context): Type = tp override def checkInlineConformant(tpt: Tree, tree: Tree, sym: Symbol)(using Context): Unit = () - override def checkNoAlphaConflict(stats: List[Tree])(using Context): Unit = () + override def checkNoTargetNameConflict(stats: List[Tree])(using Context): Unit = () override def checkParentCall(call: Tree, caller: ClassSymbol)(using Context): Unit = () override def checkSimpleKinded(tpt: Tree)(using Context): Tree = tpt override def checkDerivedValueClass(clazz: Symbol, stats: List[Tree])(using Context): Unit = () diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 89b2ddff0c49..779aedb113a8 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -433,9 +433,9 @@ object RefChecks { overrideError("has incompatible type" + err.whyNoMatchStr(memberTp(self), otherTp(self))) else if (member.erasedName != other.erasedName) if (other.erasedName != other.name) - overrideError(i"needs to be declared with @alpha(${"\""}${other.erasedName}${"\""}) so that external names match") + overrideError(i"needs to be declared with @targetName(${"\""}${other.erasedName}${"\""}) so that external names match") else - overrideError("cannot have an @alpha annotation since external names would be different") + overrideError("cannot have a @targetName annotation since external names would be different") else checkOverrideDeprecated() } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index e61a7f438102..02c442d14dff 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1982,7 +1982,7 @@ class Typer extends Namer if (sym.isConstructor && !sym.isPrimaryConstructor) { val ename = sym.erasedName if (ename != sym.name) - report.error(em"@alpha annotation ${'"'}$ename${'"'} may not be used on a constructor", ddef.srcPos) + report.error(em"@targetName annotation may not be used on a constructor", ddef.srcPos) for (param <- tparams1 ::: vparamss1.flatten) checkRefsLegal(param, sym.owner, (name, sym) => sym.is(TypeParam), "secondary constructor") @@ -2665,7 +2665,7 @@ class Typer extends Namer } val (stats0, finalCtx) = traverse(stats)(using localCtx) val stats1 = stats0.mapConserve(finalize) - if (ctx.owner == exprOwner) checkNoAlphaConflict(stats1) + if ctx.owner == exprOwner then checkNoTargetNameConflict(stats1) (stats1, finalCtx) } diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index c7813a86e243..f580ed32f334 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -153,7 +153,7 @@ class CompilationTests { defaultOptions), compileFile("tests/neg-custom-args/i6300.scala", allowDeepSubtypes), compileFile("tests/neg-custom-args/infix.scala", defaultOptions.and("-source", "3.1", "-deprecation", "-Xfatal-warnings")), - compileFile("tests/neg-custom-args/missing-alpha.scala", defaultOptions.and("-Yrequire-alpha", "-Xfatal-warnings")), + compileFile("tests/neg-custom-args/missing-alpha.scala", defaultOptions.and("-Yrequire-targetName", "-Xfatal-warnings")), compileFile("tests/neg-custom-args/wildcards.scala", defaultOptions.and("-source", "3.1", "-deprecation", "-Xfatal-warnings")), compileFile("tests/neg-custom-args/indentRight.scala", defaultOptions.and("-noindent", "-Xfatal-warnings")), compileFile("tests/neg-custom-args/extmethods-tparams.scala", defaultOptions.and("-deprecation", "-Xfatal-warnings")), diff --git a/library/src/scala/annotation/alpha.scala b/library/src/scala/annotation/alpha.scala index 4d3e9f357c4d..ac2a11c5b650 100644 --- a/library/src/scala/annotation/alpha.scala +++ b/library/src/scala/annotation/alpha.scala @@ -6,4 +6,5 @@ package scala.annotation * the regular name. An `alpha` annotation is mandatory for definitions * with symbolic names. */ +@deprecated("use @targetName instead") final class alpha(externalName: String) extends StaticAnnotation diff --git a/library/src/scala/annotation/targetName.scala b/library/src/scala/annotation/targetName.scala new file mode 100644 index 000000000000..56e1361c73d7 --- /dev/null +++ b/library/src/scala/annotation/targetName.scala @@ -0,0 +1,8 @@ +package scala.annotation + +/** An annotation that defines an external name for a definition. + * If an `targetName(extname)` annotation is given for a method or some other + * definition, its implementation will use the name `extname` instead of + * the regular name. + */ +final class targetName(name: String) extends StaticAnnotation diff --git a/tests/neg-custom-args/missing-alpha.scala b/tests/neg-custom-args/missing-alpha.scala index d40a61b5bdf8..c32ef461aee0 100644 --- a/tests/neg-custom-args/missing-alpha.scala +++ b/tests/neg-custom-args/missing-alpha.scala @@ -1,8 +1,8 @@ // Compile with -strict -Xfatal-warnings -deprecation -import scala.annotation.alpha +import scala.annotation.targetName class & { // error - @alpha("op") def *(x: Int): Int = ??? // OK + @targetName("op") def *(x: Int): Int = ??? // OK def / (x: Int): Int // error val frozen_& : Int = ??? // error object some_??? // error diff --git a/tests/neg/7722.check b/tests/neg/7722.check index ba61e9fb5ef5..f1254c1c6f57 100644 --- a/tests/neg/7722.check +++ b/tests/neg/7722.check @@ -1,4 +1,4 @@ --- Error: tests/neg/7722.scala:2:35 ------------------------------------------------------------------------------------ -2 | @scala.annotation.alpha("E") def this() = this(3) // error - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | @alpha annotation "E" may not be used on a constructor +-- Error: tests/neg/7722.scala:2:40 ------------------------------------------------------------------------------------ +2 | @scala.annotation.targetName("E") def this() = this(3) // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | @targetName annotation may not be used on a constructor diff --git a/tests/neg/7722.scala b/tests/neg/7722.scala index bc309f1926cd..23602ad3f558 100644 --- a/tests/neg/7722.scala +++ b/tests/neg/7722.scala @@ -1,5 +1,5 @@ class A(i:Int){ - @scala.annotation.alpha("E") def this() = this(3) // error + @scala.annotation.targetName("E") def this() = this(3) // error } object O{ def main(args: Array[String]) = { diff --git a/tests/neg/alpha-early.scala b/tests/neg/alpha-early.scala index 89ec197f6b90..09f44d0589dc 100644 --- a/tests/neg/alpha-early.scala +++ b/tests/neg/alpha-early.scala @@ -1,14 +1,14 @@ -import annotation.alpha +import annotation.targetName class Gamma { def foo2() = { def bar = 1 - @alpha("bar") def baz = 2 // error: @alpha annotation clashes + @targetName("bar") def baz = 2 // error: @targetName annotation clashes - @alpha("bam") def x1 = 0 - @alpha("bam") def y1 = 0 // error: @alpha annotation clashes + @targetName("bam") def x1 = 0 + @targetName("bam") def y1 = 0 // error: @targetName annotation clashes } } diff --git a/tests/neg/alpha-late.scala b/tests/neg/alpha-late.scala index 254260838fd3..3dee2a734e75 100644 --- a/tests/neg/alpha-late.scala +++ b/tests/neg/alpha-late.scala @@ -1,5 +1,5 @@ -import annotation.alpha +import annotation.targetName class Gamma { @@ -8,5 +8,5 @@ class Gamma { } class Delta extends Gamma { // error: name clash - @alpha("foo") def bar: Int = 1 + @targetName("foo") def bar: Int = 1 } \ No newline at end of file diff --git a/tests/neg/alpha-override/C_2.scala b/tests/neg/alpha-override/C_2.scala index a6e50148ffe0..18f741613ef4 100644 --- a/tests/neg/alpha-override/C_2.scala +++ b/tests/neg/alpha-override/C_2.scala @@ -1,4 +1,4 @@ -import annotation.alpha +import annotation.targetName class C extends B_1 { // error: Name clash between defined and inherited member - @alpha("bar") def foo(): Int = 3 + @targetName("bar") def foo(): Int = 3 } diff --git a/tests/neg/alpha-override1/D_2.scala b/tests/neg/alpha-override1/D_2.scala index f0a0dee774a4..85b52cdcc542 100644 --- a/tests/neg/alpha-override1/D_2.scala +++ b/tests/neg/alpha-override1/D_2.scala @@ -1,7 +1,7 @@ -import annotation.alpha +import annotation.targetName class D extends B_1 { - @alpha("bar") def foo(): Int = 3 + @targetName("bar") def foo(): Int = 3 } class E extends B_1 { - @alpha("baz") override def bar(): Int = 3 // error: cannot have an @alpha annotation since external names would be different + @targetName("baz") override def bar(): Int = 3 // error: cannot have an @targetName annotation since external names would be different } \ No newline at end of file diff --git a/tests/neg/alpha.scala b/tests/neg/alpha.scala index 49bdbc095eee..4d4ec0481170 100644 --- a/tests/neg/alpha.scala +++ b/tests/neg/alpha.scala @@ -1,21 +1,21 @@ -import annotation.alpha +import annotation.targetName abstract class Alpha[T] { def foo() = 1 - @alpha("bar") def foo(x: T): T + @targetName("bar") def foo(x: T): T - @alpha("append") def ++ (xs: Alpha[T]): Alpha[T] = this + @targetName("append") def ++ (xs: Alpha[T]): Alpha[T] = this } class Beta extends Alpha[String] { - @alpha("foo1") override def foo() = 1 // error + @targetName("foo1") override def foo() = 1 // error - @alpha("baz") def foo(x: String): String = x ++ x // error + @targetName("baz") def foo(x: String): String = x ++ x // error override def ++ (xs: Alpha[String]): Alpha[String] = this // error diff --git a/tests/neg/doubleDefinition-late.scala b/tests/neg/doubleDefinition-late.scala index c0ea1438d75c..aa0fb981d152 100644 --- a/tests/neg/doubleDefinition-late.scala +++ b/tests/neg/doubleDefinition-late.scala @@ -1,10 +1,10 @@ -import annotation.alpha +import annotation.targetName class Gamma { val v = 1 - @alpha("v") val w = 2 // error: double definition + @targetName("v") val w = 2 // error: double definition } // Error when overloading polymorphic and non-polymorphic methods diff --git a/tests/pos/alpha-override/A_1.scala b/tests/pos/alpha-override/A_1.scala index edfb54d656f7..212d05e8b245 100644 --- a/tests/pos/alpha-override/A_1.scala +++ b/tests/pos/alpha-override/A_1.scala @@ -1,5 +1,5 @@ // Scala -import annotation.alpha +import annotation.targetName class A_1 { - @alpha("bar") def foo(): Int = 1 + @targetName("bar") def foo(): Int = 1 } diff --git a/tests/pos/alpha.scala b/tests/pos/alpha.scala index 15e0355543e9..36eba2c3bfec 100644 --- a/tests/pos/alpha.scala +++ b/tests/pos/alpha.scala @@ -1,8 +1,8 @@ -import annotation.alpha +import annotation.targetName object Test { def foo() = 1 - @alpha("bar") def foo(x: Int) = 2 + @targetName("bar") def foo(x: Int) = 2 } diff --git a/tests/pos/i8391.scala b/tests/pos/i8391.scala index d0e40ecda39f..98b41beccd1c 100644 --- a/tests/pos/i8391.scala +++ b/tests/pos/i8391.scala @@ -1,7 +1,7 @@ -import scala.annotation.alpha +import scala.annotation.targetName trait Foo { - @alpha("intersection") + @targetName("intersection") def *(other: Foo): Foo } diff --git a/tests/run/alpha-interop/alpha_1.scala b/tests/run/alpha-interop/alpha_1.scala index 8885ad6413e0..41fb26c3f019 100644 --- a/tests/run/alpha-interop/alpha_1.scala +++ b/tests/run/alpha-interop/alpha_1.scala @@ -1,20 +1,20 @@ package alpha -import annotation.alpha +import annotation.targetName abstract class Alpha[T] { def foo() = 1 - @alpha("bar") def foo(x: T): T + @targetName("bar") def foo(x: T): T - @alpha("append") def ++ (xs: Alpha[T]): Alpha[T] = this + @targetName("append") def ++ (xs: Alpha[T]): Alpha[T] = this } -@alpha("Bar") class | extends Alpha[String] { +@targetName("Bar") class | extends Alpha[String] { - @alpha("bar") override def foo(x: String) = x ++ x + @targetName("bar") override def foo(x: String) = x ++ x - @alpha("append") override def ++ (xs: Alpha[String]) = this + @targetName("append") override def ++ (xs: Alpha[String]) = this } \ No newline at end of file diff --git a/tests/run/alpha-modules-1/7721_1.scala b/tests/run/alpha-modules-1/7721_1.scala index 8275e86ce748..5e59433c6beb 100644 --- a/tests/run/alpha-modules-1/7721_1.scala +++ b/tests/run/alpha-modules-1/7721_1.scala @@ -1,5 +1,5 @@ package alpha -@scala.annotation.alpha("A") object B { +@scala.annotation.targetName("A") object B { def foo = 23 } diff --git a/tests/run/alpha-modules-2/7723_1.scala b/tests/run/alpha-modules-2/7723_1.scala index d9186316d94a..128dac508a14 100644 --- a/tests/run/alpha-modules-2/7723_1.scala +++ b/tests/run/alpha-modules-2/7723_1.scala @@ -1,3 +1,3 @@ package alpha -@scala.annotation.alpha("A") class B(val i: Int = 1) +@scala.annotation.targetName("A") class B(val i: Int = 1) diff --git a/tests/run/alpha.scala b/tests/run/alpha.scala index bf58321baaf5..c023298c678f 100644 --- a/tests/run/alpha.scala +++ b/tests/run/alpha.scala @@ -1,8 +1,8 @@ -import annotation.alpha +import annotation.targetName object Test extends App { def foo(x: Any): Int = 1 - @alpha("bar") def foo[A <: AnyRef](x: A) = 2 + @targetName("bar") def foo[A <: AnyRef](x: A) = 2 assert(foo("a") == 2) } diff --git a/tests/run/i9155.scala b/tests/run/i9155.scala index 0ef470258855..2f5bf086c8eb 100644 --- a/tests/run/i9155.scala +++ b/tests/run/i9155.scala @@ -1,5 +1,5 @@ object Foo: - @scala.annotation.alpha("w") def \/\/ = "W" + @scala.annotation.targetName("w") def \/\/ = "W" object Bar: export Foo._ diff --git a/tests/run/infix.scala b/tests/run/infix.scala index d8d47ac51609..68dd960b337a 100644 --- a/tests/run/infix.scala +++ b/tests/run/infix.scala @@ -1,10 +1,10 @@ -import annotation.{infix, alpha} +import annotation.{infix, targetName} object Test extends App { case class Rational(n: Int, d: Int) { @infix def + (that: Rational) = Rational(this.n * that.d + that.n * this.d, this.d * that.d) - @infix @alpha("multiply") def * (that: Rational) = + @infix @targetName("multiply") def * (that: Rational) = Rational(this.n * that.n, this.d * that.d) } From 6eaa10e2e9596a1562c28fb193bd5aa01f7feb27 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 2 Nov 2020 19:49:35 +0100 Subject: [PATCH 02/20] Define NoSymbol.erasedName to be EmptyTermName This will streamline some of the later matching code. --- compiler/src/dotty/tools/dotc/core/SymDenotations.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 66ec6bfdbb31..751a7594b3b2 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -496,7 +496,7 @@ object SymDenotations { def fullName(using Context): Name = fullNameSeparated(QualifiedName) /** The name given in a `@targetName` annotation if one is present, `name` otherwise */ - final def erasedName(using Context): Name = + def erasedName(using Context): Name = val targetNameAnnot = if isAllOf(ModuleClass | Synthetic) then companionClass.getAnnotation(defn.TargetNameAnnot) else getAnnotation(defn.TargetNameAnnot) @@ -2318,6 +2318,7 @@ object SymDenotations { override def mapInfo(f: Type => Type)(using Context): SingleDenotation = this override def matches(other: SingleDenotation)(using Context): Boolean = false + override def erasedName(using Context): Name = EmptyTermName override def mapInherited(ownDenots: PreDenotation, prevDenots: PreDenotation, pre: Type)(using Context): SingleDenotation = this override def filterWithPredicate(p: SingleDenotation => Boolean): SingleDenotation = this override def filterDisjoint(denots: PreDenotation)(using Context): SingleDenotation = this From 8f16a339670c4112467a85ad2ad9bad6e10c0d86 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 3 Nov 2020 22:46:28 +0100 Subject: [PATCH 03/20] Allow to disambiguate overloads with @targetName Alternatives that have the same signature but different targetNames are considered to be separate. --- compiler/src/dotty/tools/dotc/ast/tpd.scala | 6 +- .../dotty/tools/dotc/core/Denotations.scala | 87 ++++++++++--------- .../src/dotty/tools/dotc/core/NameKinds.scala | 14 +-- .../src/dotty/tools/dotc/core/NameTags.scala | 1 + .../tools/dotc/core/SymDenotations.scala | 27 ++++-- .../src/dotty/tools/dotc/core/Types.scala | 15 ++-- .../tools/dotc/core/tasty/NameBuffer.scala | 23 +++-- .../dotc/core/tasty/TastyUnpickler.scala | 19 ++-- .../tools/dotc/core/tasty/TreePickler.scala | 15 ++-- .../tools/dotc/core/tasty/TreeUnpickler.scala | 28 +++--- .../dotty/tools/dotc/reporting/messages.scala | 7 +- .../src/dotty/tools/dotc/typer/Namer.scala | 2 +- .../src/dotty/tools/dotc/typer/Typer.scala | 3 + tasty/src/dotty/tools/tasty/TastyFormat.scala | 9 +- tests/neg/alpha.scala | 22 ----- tests/neg/targetName-override-2.scala | 15 ++++ tests/neg/targetName-override.check | 16 ++++ tests/neg/targetName-override.scala | 22 +++++ tests/pos/targetName-infer-result.scala | 23 +++++ tests/pos/targetName-refine.scala | 8 ++ tests/run/targetName.scala | 23 +++++ 21 files changed, 266 insertions(+), 119 deletions(-) delete mode 100644 tests/neg/alpha.scala create mode 100644 tests/neg/targetName-override-2.scala create mode 100644 tests/neg/targetName-override.check create mode 100644 tests/neg/targetName-override.scala create mode 100644 tests/pos/targetName-infer-result.scala create mode 100644 tests/pos/targetName-refine.scala create mode 100644 tests/run/targetName.scala diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index 2f46106e1230..d259390da680 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -882,8 +882,8 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { } /** A select node with the given selector name and signature and a computed type */ - def selectWithSig(name: Name, sig: Signature)(using Context): Tree = - untpd.SelectWithSig(tree, name, sig).withType(tree.tpe.select(name.asTermName, sig)) + def selectWithSig(name: Name, sig: Signature, target: Name)(using Context): Tree = + untpd.SelectWithSig(tree, name, sig).withType(tree.tpe.select(name.asTermName, sig, target)) /** A select node with selector name and signature taken from `sym`. * Note: Use this method instead of select(sym) if the referenced symbol @@ -891,7 +891,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { * on select(sym: Symbol). */ def selectWithSig(sym: Symbol)(using Context): Tree = - selectWithSig(sym.name, sym.signature) + selectWithSig(sym.name, sym.signature, sym.erasedName) /** A unary apply node with given argument: `tree(arg)` */ def appliedTo(arg: Tree)(using Context): Apply = diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index 752ab3aaa955..0ea364880590 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -222,7 +222,7 @@ object Denotations { * when seen from prefix `site`. * @param relaxed When true, consider only parameter signatures for a match. */ - def atSignature(sig: Signature, site: Type = NoPrefix, relaxed: Boolean = false)(using Context): Denotation + def atSignature(sig: Signature, targetName: Name, site: Type = NoPrefix, relaxed: Boolean = false)(using Context): Denotation /** The variant of this denotation that's current in the given context. * If no such denotation exists, returns the denotation with each alternative @@ -347,13 +347,15 @@ object Denotations { } /** The alternative of this denotation that has a type matching `targetType` when seen - * as a member of type `site`, `NoDenotation` if none exists. + * as a member of type `site` and that has an erased name matching `targetName`, or + * `NoDenotation` if none exists. */ - def matchingDenotation(site: Type, targetType: Type)(using Context): SingleDenotation = { - def qualifies(sym: Symbol) = site.memberInfo(sym).matchesLoosely(targetType) + def matchingDenotation(site: Type, targetType: Type, targetName: Name)(using Context): SingleDenotation = { + def qualifies(sym: Symbol) = + site.memberInfo(sym).matchesLoosely(targetType) && targetNamesMatch(sym.erasedName, targetName) if (isOverloaded) - atSignature(targetType.signature, site, relaxed = true) match { - case sd: SingleDenotation => sd.matchingDenotation(site, targetType) + atSignature(targetType.signature, targetName, site, relaxed = true) match { + case sd: SingleDenotation => sd.matchingDenotation(site, targetType, targetName) case md => md.suchThat(qualifies(_)) } else if (exists && !qualifies(symbol)) NoDenotation @@ -610,9 +612,9 @@ object Denotations { def accessibleFrom(pre: Type, superAccess: Boolean)(using Context): Denotation = if (!symbol.exists || symbol.isAccessibleFrom(pre, superAccess)) this else NoDenotation - def atSignature(sig: Signature, site: Type, relaxed: Boolean)(using Context): SingleDenotation = + def atSignature(sig: Signature, targetName: Name, site: Type, relaxed: Boolean)(using Context): SingleDenotation = val situated = if site == NoPrefix then this else asSeenFrom(site) - val matches = sig.matchDegree(situated.signature) match + val sigMatches = sig.matchDegree(situated.signature) match case FullMatch => true case MethodNotAMethodMatch => @@ -622,7 +624,8 @@ object Denotations { relaxed case noMatch => false - if matches then this else NoDenotation + if sigMatches && targetNamesMatch(symbol.erasedName, targetName) then this + else NoDenotation def matchesImportBound(bound: Type)(using Context): Boolean = if bound.isRef(defn.NothingClass) then false @@ -983,33 +986,35 @@ object Denotations { final def last: SingleDenotation = this def matches(other: SingleDenotation)(using Context): Boolean = - val d = signature.matchDegree(other.signature) - - d match - case FullMatch => - true - case MethodNotAMethodMatch => - !ctx.erasedTypes && { - val isJava = symbol.is(JavaDefined) - val otherIsJava = other.symbol.is(JavaDefined) - // A Scala zero-parameter method and a Scala non-method always match. - if !isJava && !otherIsJava then - true - // Java allows defining both a field and a zero-parameter method with the same name, - // so they must not match. - else if isJava && otherIsJava then - false - // A Java field never matches a Scala method. - else if isJava then - symbol.is(Method) - else // otherIsJava - other.symbol.is(Method) - } - case ParamMatch => - // The signatures do not tell us enough to be sure about matching - !ctx.erasedTypes && info.matches(other.info) - case noMatch => - false + targetNamesMatch(symbol.erasedName, other.symbol.erasedName) + && { + val d = signature.matchDegree(other.signature) + d match + case FullMatch => + true + case MethodNotAMethodMatch => + !ctx.erasedTypes && { + val isJava = symbol.is(JavaDefined) + val otherIsJava = other.symbol.is(JavaDefined) + // A Scala zero-parameter method and a Scala non-method always match. + if !isJava && !otherIsJava then + true + // Java allows defining both a field and a zero-parameter method with the same name, + // so they must not match. + else if isJava && otherIsJava then + false + // A Java field never matches a Scala method. + else if isJava then + symbol.is(Method) + else // otherIsJava + other.symbol.is(Method) + } + case ParamMatch => + // The signatures do not tell us enough to be sure about matching + !ctx.erasedTypes && info.matches(other.info) + case noMatch => + false + } end matches def mapInherited(ownDenots: PreDenotation, prevDenots: PreDenotation, pre: Type)(using Context): SingleDenotation = @@ -1164,9 +1169,11 @@ object Denotations { final def hasUniqueSym: Boolean = false final def name(using Context): Name = denot1.name final def signature(using Context): Signature = Signature.OverloadedSignature - def atSignature(sig: Signature, site: Type, relaxed: Boolean)(using Context): Denotation = + def atSignature(sig: Signature, targetName: Name, site: Type, relaxed: Boolean)(using Context): Denotation = if (sig eq Signature.OverloadedSignature) this - else derivedUnionDenotation(denot1.atSignature(sig, site, relaxed), denot2.atSignature(sig, site, relaxed)) + else derivedUnionDenotation( + denot1.atSignature(sig, targetName, site, relaxed), + denot2.atSignature(sig, targetName, site, relaxed)) def current(using Context): Denotation = derivedUnionDenotation(denot1.current, denot2.current) def altsWith(p: Symbol => Boolean): List[SingleDenotation] = @@ -1263,7 +1270,6 @@ object Denotations { else ctx.run.staticRefs.getOrElseUpdate(path, recur(path)) } - /** If we are looking for a non-existing term name in a package, * assume it is a package for which we do not have a directory and * enter it. @@ -1279,4 +1285,7 @@ object Denotations { util.Stats.record("stale symbol") override def getMessage(): String = msg } + + def targetNamesMatch(name1: Name, name2: Name): Boolean = + name1 == name2 || name1.isEmpty || name2.isEmpty } diff --git a/compiler/src/dotty/tools/dotc/core/NameKinds.scala b/compiler/src/dotty/tools/dotc/core/NameKinds.scala index a45973b2ccab..aa11660710b5 100644 --- a/compiler/src/dotty/tools/dotc/core/NameKinds.scala +++ b/compiler/src/dotty/tools/dotc/core/NameKinds.scala @@ -371,17 +371,19 @@ object NameKinds { /** A name together with a signature. Used in Tasty trees. */ object SignedName extends NameKind(SIGNED) { - case class SignedInfo(sig: Signature) extends Info { + case class SignedInfo(sig: Signature, target: TermName) extends Info { assert(sig ne Signature.NotAMethod) - override def toString: String = s"$infoString $sig" + override def toString: String = + val targetStr = if target.isEmpty then "" else s" @$target" + s"$infoString $sig$targetStr" override def hashCode = scala.runtime.ScalaRunTime._hashCode(this) * 31 + kind.hashCode } type ThisInfo = SignedInfo - def apply(qual: TermName, sig: Signature): TermName = - qual.derived(new SignedInfo(sig)) - def unapply(name: DerivedName): Option[(TermName, Signature)] = name match { - case DerivedName(underlying, info: SignedInfo) => Some((underlying, info.sig)) + def apply(qual: TermName, sig: Signature, target: TermName): TermName = + qual.derived(new SignedInfo(sig, target)) + def unapply(name: DerivedName): Option[(TermName, Signature, TermName)] = name match { + case DerivedName(underlying, info: SignedInfo) => Some((underlying, info.sig, info.target)) case _ => None } diff --git a/compiler/src/dotty/tools/dotc/core/NameTags.scala b/compiler/src/dotty/tools/dotc/core/NameTags.scala index 299cba7b6948..c570f300a4bf 100644 --- a/compiler/src/dotty/tools/dotc/core/NameTags.scala +++ b/compiler/src/dotty/tools/dotc/core/NameTags.scala @@ -52,5 +52,6 @@ object NameTags extends TastyFormat.NameTags { case OBJECTCLASS => "OBJECTCLASS" case SIGNED => "SIGNED" + case TARGETSIGNED => "TARGETSIGNED" } } diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 751a7594b3b2..d66358f94038 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -495,11 +495,9 @@ object SymDenotations { /** `fullName` where `.' is the separator character */ def fullName(using Context): Name = fullNameSeparated(QualifiedName) - /** The name given in a `@targetName` annotation if one is present, `name` otherwise */ - def erasedName(using Context): Name = - val targetNameAnnot = - if isAllOf(ModuleClass | Synthetic) then companionClass.getAnnotation(defn.TargetNameAnnot) - else getAnnotation(defn.TargetNameAnnot) + private var myTargetName: Name = null + + private def computeTargetName(targetNameAnnot: Option[Annotation])(using Context): Name = targetNameAnnot match case Some(ann) => ann.arguments match @@ -511,6 +509,21 @@ object SymDenotations { case _ => name case _ => name + def setTargetName(name: Name): Unit = + myTargetName = name + + /** The name given in a `@targetName` annotation if one is present, `name` otherwise */ + def erasedName(using Context): Name = + if myTargetName == null then + val carrier: SymDenotation = + if isAllOf(ModuleClass | Synthetic) then companionClass else this + val targetNameAnnot = + if carrier.isCompleting // annotations have been set already in this case + then carrier.unforcedAnnotation(defn.TargetNameAnnot) + else carrier.getAnnotation(defn.TargetNameAnnot) + myTargetName = computeTargetName(targetNameAnnot) + myTargetName + // ----- Tests ------------------------------------------------- /** Is this denotation a type? */ @@ -1238,7 +1251,7 @@ object SymDenotations { final def matchingDecl(inClass: Symbol, site: Type)(using Context): Symbol = { var denot = inClass.info.nonPrivateDecl(name) if (denot.isTerm) // types of the same name always match - denot = denot.matchingDenotation(site, site.memberInfo(symbol)) + denot = denot.matchingDenotation(site, site.memberInfo(symbol), symbol.erasedName) denot.symbol } @@ -1247,7 +1260,7 @@ object SymDenotations { final def matchingMember(site: Type)(using Context): Symbol = { var denot = site.nonPrivateMember(name) if (denot.isTerm) // types of the same name always match - denot = denot.matchingDenotation(site, site.memberInfo(symbol)) + denot = denot.matchingDenotation(site, site.memberInfo(symbol), symbol.erasedName) denot.symbol } diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 283bee2e853e..3eaebebf27b4 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1457,8 +1457,8 @@ object Types { def select(name: TermName)(using Context): TermRef = TermRef(this, name, member(name)) - def select(name: TermName, sig: Signature)(using Context): TermRef = - TermRef(this, name, member(name).atSignature(sig, relaxed = !ctx.erasedTypes)) + def select(name: TermName, sig: Signature, target: Name)(using Context): TermRef = + TermRef(this, name, member(name).atSignature(sig, target, relaxed = !ctx.erasedTypes)) // ----- Access to parts -------------------------------------------- @@ -2084,14 +2084,14 @@ object Types { } private def disambiguate(d: Denotation)(using Context): Denotation = - disambiguate(d, currentSignature) + disambiguate(d, currentSignature, currentSymbol.erasedName) - private def disambiguate(d: Denotation, sig: Signature)(using Context): Denotation = + private def disambiguate(d: Denotation, sig: Signature, target: Name)(using Context): Denotation = if (sig != null) - d.atSignature(sig, relaxed = !ctx.erasedTypes) match { + d.atSignature(sig, target, relaxed = !ctx.erasedTypes) match { case d1: SingleDenotation => d1 case d1 => - d1.atSignature(sig, relaxed = false) match { + d1.atSignature(sig, target, relaxed = false) match { case d2: SingleDenotation => d2 case d2 => d2.suchThat(currentSymbol.eq).orElse(d2) } @@ -2395,7 +2395,8 @@ object Types { if (d.isOverloaded && lastSymbol.exists) d = disambiguate(d, if (lastSymbol.signature == Signature.NotAMethod) Signature.NotAMethod - else lastSymbol.asSeenFrom(prefix).signature) + else lastSymbol.asSeenFrom(prefix).signature, + lastSymbol.erasedName) NamedType(prefix, name, d) } if (prefix eq this.prefix) this diff --git a/compiler/src/dotty/tools/dotc/core/tasty/NameBuffer.scala b/compiler/src/dotty/tools/dotc/core/tasty/NameBuffer.scala index 1294b6fe0a72..a98a48ac4bff 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/NameBuffer.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/NameBuffer.scala @@ -11,6 +11,8 @@ import Names.{Name, chrs, SimpleName, DerivedName, TypeName} import NameKinds._ import Decorators._ import scala.io.Codec +import Denotations.targetNamesMatch +import NameTags.{SIGNED, TARGETSIGNED} class NameBuffer extends TastyBuffer(10000) { import NameBuffer._ @@ -24,8 +26,9 @@ class NameBuffer extends TastyBuffer(10000) { ref case None => name1 match { - case SignedName(original, Signature(params, result)) => + case SignedName(original, Signature(params, result), target) => nameIndex(original) + if !targetNamesMatch(original, target) then nameIndex(target) nameIndex(result) params.foreach { case param: TypeName => @@ -70,29 +73,39 @@ class NameBuffer extends TastyBuffer(10000) { def pickleNameContents(name: Name): Unit = { val tag = name.toTermName.info.kind.tag - writeByte(tag) name.toTermName match { case name: SimpleName => + writeByte(tag) val bytes = if (name.length == 0) new Array[Byte](0) else Codec.toUTF8(chrs, name.start, name.length) writeNat(bytes.length) writeBytes(bytes, bytes.length) case AnyQualifiedName(prefix, name) => + writeByte(tag) withLength { writeNameRef(prefix); writeNameRef(name) } case AnyUniqueName(original, separator, num) => + writeByte(tag) withLength { writeNameRef(separator) writeNat(num) if (!original.isEmpty) writeNameRef(original) } case AnyNumberedName(original, num) => + writeByte(tag) withLength { writeNameRef(original); writeNat(num) } - case SignedName(original, Signature(paramsSig, result)) => + case SignedName(original, Signature(paramsSig, result), target) => + val needsTarget = !targetNamesMatch(original, target) + writeByte(if needsTarget then TARGETSIGNED else SIGNED) withLength( - { writeNameRef(original); writeNameRef(result); paramsSig.foreach(writeParamSig) }, - if ((paramsSig.length + 2) * maxIndexWidth <= maxNumInByte) 1 else 2) + { writeNameRef(original) + if needsTarget then writeNameRef(target) + writeNameRef(result) + paramsSig.foreach(writeParamSig) + }, + if ((paramsSig.length + 3) * maxIndexWidth <= maxNumInByte) 1 else 2) case DerivedName(original, _) => + writeByte(tag) withLength { writeNameRef(original) } } } diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyUnpickler.scala index fa00db183e0b..7bb71aa6deee 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyUnpickler.scala @@ -50,6 +50,14 @@ class TastyUnpickler(reader: TastyReader) { val length = readNat() val start = currentAddr val end = start + length + def readSignedRest(original: TermName, target: TermName): TermName = + val result = readName().toTypeName + // DOTTY: we shouldn't have to give an explicit type to paramsSig, + // see https://github.com/lampepfl/dotty/issues/4867 + val paramsSig: List[Signature.ParamSig] = until(end)(readParamSig()) + val sig = Signature(paramsSig, result) + SignedName(original, sig, target) + val result = tag match { case UTF8 => goto(end) @@ -66,12 +74,11 @@ class TastyUnpickler(reader: TastyReader) { numberedNameKindOfTag(tag)(readName(), readNat()) case SIGNED => val original = readName() - val result = readName().toTypeName - // DOTTY: we shouldn't have to give an explicit type to paramsSig, - // see https://github.com/lampepfl/dotty/issues/4867 - val paramsSig: List[Signature.ParamSig] = until(end)(readParamSig()) - val sig = Signature(paramsSig, result) - SignedName(original, sig) + readSignedRest(original, original) + case TARGETSIGNED => + val original = readName() + val target = readName() + readSignedRest(original, target) case _ => simpleNameKindOfTag(tag)(readName()) } diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 534a01ab8edb..bef41343bb2d 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -92,10 +92,10 @@ class TreePickler(pickler: TastyPickler) { def pickleName(name: Name): Unit = writeNat(nameIndex(name).index) - private def pickleNameAndSig(name: Name, sig: Signature): Unit = + private def pickleNameAndSig(name: Name, sig: Signature, target: Name): Unit = pickleName( if (sig eq Signature.NotAMethod) name - else SignedName(name.toTermName, sig)) + else SignedName(name.toTermName, sig, target.asTermName)) private def pickleSymRef(sym: Symbol)(using Context) = symRefs.get(sym) match { case Some(label) => @@ -197,14 +197,14 @@ class TreePickler(pickler: TastyPickler) { if (sym.is(Flags.Private) || isShadowedRef) { writeByte(if (tpe.isType) TYPEREFin else TERMREFin) withLength { - pickleNameAndSig(sym.name, tpe.symbol.signature) + pickleNameAndSig(sym.name, sym.signature, sym.erasedName) pickleType(tpe.prefix) pickleType(sym.owner.typeRef) } } else { writeByte(if (tpe.isType) TYPEREF else TERMREF) - pickleNameAndSig(sym.name, tpe.signature) + pickleNameAndSig(sym.name, tpe.signature, sym.erasedName) pickleType(tpe.prefix) } } @@ -405,21 +405,22 @@ class TreePickler(pickler: TastyPickler) { } case _ => val sig = tree.tpe.signature + var ename = tree.symbol.erasedName val isAmbiguous = sig != Signature.NotAMethod && qual.tpe.nonPrivateMember(name).match - case d: MultiDenotation => d.atSignature(sig).isInstanceOf[MultiDenotation] + case d: MultiDenotation => d.atSignature(sig, ename).isInstanceOf[MultiDenotation] case _ => false if isAmbiguous then writeByte(SELECTin) withLength { - pickleNameAndSig(name, tree.symbol.signature) + pickleNameAndSig(name, tree.symbol.signature, ename) pickleTree(qual) pickleType(tree.symbol.owner.typeRef) } else writeByte(if (name.isTypeName) SELECTtpt else SELECT) - pickleNameAndSig(name, sig) + pickleNameAndSig(name, sig, ename) pickleTree(qual) } case Apply(fun, args) => diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 647d38b157ac..b71bf97b4f44 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -335,8 +335,8 @@ class TreeUnpickler(reader: TastyReader, val prefix = readType() val owner = readType() sname match { - case SignedName(name, sig) => - TermRef(prefix, name, owner.decl(name).atSignature(sig).asSeenFrom(prefix)) + case SignedName(name, sig, target) => + TermRef(prefix, name, owner.decl(name).atSignature(sig, target).asSeenFrom(prefix)) case name => TermRef(prefix, name, owner.decl(name).asSeenFrom(prefix)) } @@ -410,8 +410,8 @@ class TreeUnpickler(reader: TastyReader, val sname = readName() val prefix = readType() sname match { - case SignedName(name, sig) => - TermRef(prefix, name, prefix.member(name).atSignature(sig)) + case SignedName(name, sig, target) => + TermRef(prefix, name, prefix.member(name).atSignature(sig, target)) case name => TermRef(prefix, name) } @@ -1057,20 +1057,20 @@ class TreeUnpickler(reader: TastyReader, } ConstFold.Select(untpd.Select(qual, name).withType(tpe)) - def completeSelect(name: Name, sig: Signature): Select = + def completeSelect(name: Name, sig: Signature, target: Name): Select = val qual = readTerm() - val denot = accessibleDenot(qual.tpe.widenIfUnstable, name, sig) + val denot = accessibleDenot(qual.tpe.widenIfUnstable, name, sig, target) makeSelect(qual, name, denot) def readQualId(): (untpd.Ident, TypeRef) = val qual = readTerm().asInstanceOf[untpd.Ident] (untpd.Ident(qual.name).withSpan(qual.span), qual.tpe.asInstanceOf[TypeRef]) - def accessibleDenot(qualType: Type, name: Name, sig: Signature) = { + def accessibleDenot(qualType: Type, name: Name, sig: Signature, target: Name) = { val pre = ctx.typeAssigner.maybeSkolemizePrefix(qualType, name) - val d = qualType.findMember(name, pre).atSignature(sig) + val d = qualType.findMember(name, pre).atSignature(sig, target) if (!d.symbol.exists || d.symbol.isAccessibleFrom(pre)) d - else qualType.findMember(name, pre, excluded = Private).atSignature(sig) + else qualType.findMember(name, pre, excluded = Private).atSignature(sig, target) } def readSimpleTerm(): Tree = tag match { @@ -1082,12 +1082,12 @@ class TreeUnpickler(reader: TastyReader, untpd.Ident(readName().toTypeName).withType(readType()) case SELECT => readName() match { - case SignedName(name, sig) => completeSelect(name, sig) - case name => completeSelect(name, Signature.NotAMethod) + case SignedName(name, sig, target) => completeSelect(name, sig, target) + case name => completeSelect(name, Signature.NotAMethod, EmptyTermName) } case SELECTtpt => val name = readName().toTypeName - completeSelect(name, Signature.NotAMethod) + completeSelect(name, Signature.NotAMethod, EmptyTermName) case QUALTHIS => val (qual, tref) = readQualId() untpd.This(qual).withType(ThisType.raw(tref)) @@ -1181,8 +1181,8 @@ class TreeUnpickler(reader: TastyReader, val prefix = ctx.typeAssigner.maybeSkolemizePrefix(qual.tpe.widenIfUnstable, name) makeSelect(qual, name, denot.asSeenFrom(prefix)) sname match - case SignedName(name, sig) => - select(name, owner.decl(name).atSignature(sig)) + case SignedName(name, sig, target) => + select(name, owner.decl(name).atSignature(sig, target)) case name => select(name, owner.decl(name)) case REPEATED => diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index e6943bca6ce3..7e6800cb1eb3 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -1084,7 +1084,12 @@ import transform.SymUtils._ class OverridesNothingButNameExists(member: Symbol, existing: List[Denotations.SingleDenotation])(using Context) extends DeclarationMsg(OverridesNothingButNameExistsID) { - def msg = em"""${member} has a different signature than the overridden declaration""" + def msg = + val what = + if !existing.exists(sd => Denotations.targetNamesMatch(member.erasedName, sd.symbol.erasedName)) + then "target name" + else "signature" + em"""${member} has a different $what than the overridden declaration""" def explain = val existingDecl: String = existing.map(_.showDcl).mkString(" \n") em"""|There must be a non-final field or method with the name ${member.name} and the diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 2ee42903c14e..a828d76c311f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1298,7 +1298,7 @@ class Namer { typer: Typer => else NoType } val iRawInfo = - cls.info.nonPrivateDecl(sym.name).matchingDenotation(site, schema).info + cls.info.nonPrivateDecl(sym.name).matchingDenotation(site, schema, sym.erasedName).info val iResType = instantiatedResType(iRawInfo, typeParams, paramss).asSeenFrom(site, cls) if (iResType.exists) typr.println(i"using inherited type for ${mdef.name}; raw: $iRawInfo, inherited: $iResType") diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 02c442d14dff..2635e518ec51 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1664,6 +1664,9 @@ class Typer extends Namer typr.println(s"adding refinement $refinement") checkRefinementNonCyclic(refinement, refineCls, seen) val rsym = refinement.symbol + rsym.setTargetName(EmptyTermName) + // refinements can refine members with arbitrary target names, so we make their tragetnames + // polymorphic here in order to avoid to trigger the `member.isOverloaded` test below. val polymorphicRefinementAllowed = tpt1.tpe.typeSymbol == defn.PolyFunctionClass && rsym.name == nme.apply if (!polymorphicRefinementAllowed && rsym.info.isInstanceOf[PolyType] && rsym.allOverriddenSymbols.isEmpty) diff --git a/tasty/src/dotty/tools/tasty/TastyFormat.scala b/tasty/src/dotty/tools/tasty/TastyFormat.scala index 6b84cddf31e9..5258438c830d 100644 --- a/tasty/src/dotty/tools/tasty/TastyFormat.scala +++ b/tasty/src/dotty/tools/tasty/TastyFormat.scala @@ -40,6 +40,7 @@ Macro-format: OBJECTCLASS Length underlying_NameRef -- A$ (name of the module class for module A) SIGNED Length original_NameRef resultSig_NameRef ParamSig* -- name + signature + TARGETSIGNED Length original_NameRef target_NameRef resultSig_NameRef ParamSig* ParamSig = Int // If negative, the absolute value represents the length of a type parameter section // If positive, this is a NameRef for the fully qualified name of a term parameter. @@ -253,7 +254,7 @@ object TastyFormat { final val header: Array[Int] = Array(0x5C, 0xA1, 0xAB, 0x1F) val MajorVersion: Int = 24 - val MinorVersion: Int = 0 + val MinorVersion: Int = 1 /** Tags used to serialize names, should update [[nameTagToString]] if a new constant is added */ class NameTags { @@ -284,6 +285,11 @@ object TastyFormat { final val SIGNED = 63 // A pair of a name and a signature, used to identify // possibly overloaded methods. + + final val TARGETSIGNED = 62 // A triple of a name, a targetname and a signature, used to identify + // possibly overloaded methods that carry a @targetName annotation. + + // TODO swap SIGNED and TARGETSIGNED codes on next major version bump } object NameTags extends NameTags @@ -306,6 +312,7 @@ object TastyFormat { case BODYRETAINER => "BODYRETAINER" case OBJECTCLASS => "OBJECTCLASS" case SIGNED => "SIGNED" + case TARGETSIGNED => "TARGETSIGNED" case id => s"NotANameTag($id)" } } diff --git a/tests/neg/alpha.scala b/tests/neg/alpha.scala deleted file mode 100644 index 4d4ec0481170..000000000000 --- a/tests/neg/alpha.scala +++ /dev/null @@ -1,22 +0,0 @@ -import annotation.targetName - - -abstract class Alpha[T] { - - def foo() = 1 - - @targetName("bar") def foo(x: T): T - - @targetName("append") def ++ (xs: Alpha[T]): Alpha[T] = this - -} - -class Beta extends Alpha[String] { - - @targetName("foo1") override def foo() = 1 // error - - @targetName("baz") def foo(x: String): String = x ++ x // error - - override def ++ (xs: Alpha[String]): Alpha[String] = this // error - -} diff --git a/tests/neg/targetName-override-2.scala b/tests/neg/targetName-override-2.scala new file mode 100644 index 000000000000..531efbfd931f --- /dev/null +++ b/tests/neg/targetName-override-2.scala @@ -0,0 +1,15 @@ +import annotation.targetName + +class Alpha[T]: + + def foo() = 1 + + @targetName("bar") def foo(x: T): T = x + + @targetName("baz") def foo(x: String): String = x + +class Beta extends Alpha[String]: // error: Name clash between defined and inherited member: foo/baz + + def bar(x: String): String = x // OK, since no bridge is generated + + def baz(x: String): String = x \ No newline at end of file diff --git a/tests/neg/targetName-override.check b/tests/neg/targetName-override.check new file mode 100644 index 000000000000..469b94e2b7e1 --- /dev/null +++ b/tests/neg/targetName-override.check @@ -0,0 +1,16 @@ +-- Error: tests/neg/targetName-override.scala:14:6 --------------------------------------------------------------------- +14 |class Beta extends Alpha[String] { // error: needs to be abstract + | ^ + |class Beta needs to be abstract, since there is a deferred declaration of method foo in class Alpha of type (x: String): String which is not implemented in a subclass +-- [E038] Declaration Error: tests/neg/targetName-override.scala:16:35 ------------------------------------------------- +16 | @targetName("foo1") override def foo() = 1 // error: different signature than overridden + | ^ + | method foo has a different target name than the overridden declaration + +longer explanation available when compiling with `-explain` +-- [E038] Declaration Error: tests/neg/targetName-override.scala:20:15 ------------------------------------------------- +20 | override def ++ (xs: Alpha[String]): Alpha[String] = this // error: different signature than overidden + | ^ + | method ++ has a different target name than the overridden declaration + +longer explanation available when compiling with `-explain` diff --git a/tests/neg/targetName-override.scala b/tests/neg/targetName-override.scala new file mode 100644 index 000000000000..c9b6e36b40f5 --- /dev/null +++ b/tests/neg/targetName-override.scala @@ -0,0 +1,22 @@ +import annotation.targetName + + +abstract class Alpha[T] { + + def foo() = 1 + + @targetName("bar") def foo(x: T): T + + @targetName("append") def ++ (xs: Alpha[T]): Alpha[T] = this + +} + +class Beta extends Alpha[String] { // error: needs to be abstract + + @targetName("foo1") override def foo() = 1 // error: different signature than overridden + + @targetName("baz") def foo(x: String): String = x ++ x // OK, + + override def ++ (xs: Alpha[String]): Alpha[String] = this // error: different signature than overidden + +} diff --git a/tests/pos/targetName-infer-result.scala b/tests/pos/targetName-infer-result.scala new file mode 100644 index 000000000000..7e732395a483 --- /dev/null +++ b/tests/pos/targetName-infer-result.scala @@ -0,0 +1,23 @@ +import annotation.targetName +enum Tree: + case Bind(sym: Symbol, body: Tree) + +class Symbol + +object Test1: + abstract class TreeAccumulator[X]: + def app(x: X, tree: Tree): X + def app(x: X, trees: List[Tree]): X = ??? + + val acc = new TreeAccumulator[List[Symbol]]: + def app(syms: List[Symbol], tree: Tree) = tree match + case Tree.Bind(sym, body) => app(sym :: syms, body) + +object Test2: + abstract class TreeAccumulator[X]: + @targetName("apply") def app(x: X, tree: Tree): X + def app(x: X, trees: List[Tree]): X = ??? + + val acc = new TreeAccumulator[List[Symbol]]: + @targetName("apply") def app(syms: List[Symbol], tree: Tree) = tree match + case Tree.Bind(sym, body) => app(sym :: syms, body) diff --git a/tests/pos/targetName-refine.scala b/tests/pos/targetName-refine.scala new file mode 100644 index 000000000000..eaa02b8b7976 --- /dev/null +++ b/tests/pos/targetName-refine.scala @@ -0,0 +1,8 @@ +import annotation.targetName +trait T: + @targetName("f2") def f: Any +class C extends T: + @targetName("f2") def f: Int = 1 + +val x: T { def f: Int } = C() + diff --git a/tests/run/targetName.scala b/tests/run/targetName.scala new file mode 100644 index 000000000000..00b04ec633c2 --- /dev/null +++ b/tests/run/targetName.scala @@ -0,0 +1,23 @@ +import annotation.targetName + +object A: + def f(x: => String): Int = x.length + @targetName("f2") def f(x: => Int): Int = x + +import A._ +@main def Test = + assert(f("abc") == f(3)) + +trait T: + def f(x: => String): Int + @targetName("f2") def f(x: => Int): Int + +class C: + def f(x: => String): Int = x.length + @targetName("f2") def f(x: => Int): Int = x + +object B1 extends C, T: + @targetName("f2") override def f(x: => Int): Int = x + 1 + +object B2 extends C, T: + override def f(x: => String): Int = x.length + 1 From b3d0c479965cf32f6f8565c90c355d9f1d47b1f6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 4 Nov 2020 10:43:42 +0100 Subject: [PATCH 04/20] Handle superAccessors in the presence of @targetName --- .../tools/dotc/core/SymDenotations.scala | 3 + .../dotty/tools/dotc/reporting/messages.scala | 5 +- .../tools/dotc/transform/ResolveSuper.scala | 24 ++++---- .../tools/dotc/transform/SuperAccessors.scala | 18 ++++-- tests/run/supercalls-traits-targetname.check | 5 ++ tests/run/supercalls-traits-targetname.scala | 57 +++++++++++++++++++ 6 files changed, 96 insertions(+), 16 deletions(-) create mode 100644 tests/run/supercalls-traits-targetname.check create mode 100644 tests/run/supercalls-traits-targetname.scala diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index d66358f94038..7d36f274350f 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -522,6 +522,9 @@ object SymDenotations { then carrier.unforcedAnnotation(defn.TargetNameAnnot) else carrier.getAnnotation(defn.TargetNameAnnot) myTargetName = computeTargetName(targetNameAnnot) + if name.is(SuperAccessorName) then + myTargetName = myTargetName.unmangle(List(ExpandedName, SuperAccessorName, ExpandPrefixName)) + myTargetName // ----- Tests ------------------------------------------------- diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 7e6800cb1eb3..26de90ec7ef6 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -2204,7 +2204,7 @@ import transform.SymUtils._ def explain = "" } - class IllegalSuperAccessor(base: Symbol, memberName: Name, + class IllegalSuperAccessor(base: Symbol, memberName: Name, targetName: Name, acc: Symbol, accTp: Type, other: Symbol, otherTp: Type)(using Context) extends DeclarationMsg(IllegalSuperAccessorID) { def msg = { @@ -2220,7 +2220,8 @@ import transform.SymUtils._ // does in classes, i.e. followed the linearization of the trait itself. val staticSuperCall = { val staticSuper = accMixin.asClass.info.parents.reverse - .find(_.nonPrivateMember(memberName).matchingDenotation(accMixin.thisType, acc.info).exists) + .find(_.nonPrivateMember(memberName) + .matchingDenotation(accMixin.thisType, acc.info, targetName).exists) val staticSuperName = staticSuper match { case Some(parent) => parent.classSymbol.name.show diff --git a/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala b/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala index ffde47d3bebf..0fd3b1e2be9e 100644 --- a/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala +++ b/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala @@ -82,20 +82,24 @@ object ResolveSuper { var bcs = base.info.baseClasses.dropWhile(acc.owner != _).tail var sym: Symbol = NoSymbol - var mix: Name = nme.EMPTY - val memberName = acc.name.unexpandedName match - case SuperAccessorName(ExpandPrefixName(name, mixName)) => - mix = mixName.toTypeName - name - case SuperAccessorName(name) => - name + def decomposeSuperName(superName: Name): (Name, TypeName) = + superName.unexpandedName match + case SuperAccessorName(ExpandPrefixName(name, mixName)) => + (name, mixName.toTypeName) + case SuperAccessorName(name) => + (name, EmptyTypeName) + + val (memberName, mix) = decomposeSuperName(acc.name.unexpandedName) + val targetName = + if acc.name == acc.erasedName then memberName + else decomposeSuperName(acc.erasedName)._1 report.debuglog(i"starting rebindsuper from $base of ${acc.showLocated}: ${acc.info} in $bcs, name = $memberName") while (bcs.nonEmpty && sym == NoSymbol) { val other = bcs.head.info.nonPrivateDecl(memberName) .filterWithPredicate(denot => mix.isEmpty || denot.symbol.owner.name == mix) - .matchingDenotation(base.thisType, base.thisType.memberInfo(acc)) + .matchingDenotation(base.thisType, base.thisType.memberInfo(acc), targetName) report.debuglog(i"rebindsuper ${bcs.head} $other deferred = ${other.symbol.is(Deferred)}") if other.exists && !other.symbol.is(Deferred) then sym = other.symbol @@ -104,11 +108,11 @@ object ResolveSuper { val otherTp = other.asSeenFrom(base.typeRef).info val accTp = acc.asSeenFrom(base.typeRef).info if (!(otherTp.overrides(accTp, matchLoosely = true))) - report.error(IllegalSuperAccessor(base, memberName, acc, accTp, other.symbol, otherTp), base.srcPos) + report.error(IllegalSuperAccessor(base, memberName, targetName, acc, accTp, other.symbol, otherTp), base.srcPos) bcs = bcs.tail } - assert(sym.exists) + assert(sym.exists, i"cannot rebind $acc, ${acc.erasedName} $memberName") sym } } diff --git a/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala b/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala index 5e84def109bb..a2ba3e1023d0 100644 --- a/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala +++ b/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala @@ -8,6 +8,8 @@ import ValueClasses.isMethodWithExtension import core._ import Contexts._, Flags._, Symbols._, Names._, StdNames._, NameOps._, Trees._ import TypeUtils._, SymUtils._ +import Constants.Constant +import Annotations.Annotation import DenotTransformers.DenotTransformer import Symbols._ import util.Spans._ @@ -67,11 +69,15 @@ class SuperAccessors(thisPhase: DenotTransformer) { val Select(qual, name) = sel val sym = sel.symbol val clazz = qual.symbol.asClass - val preName = if (mixName.isEmpty) name.toTermName else ExpandPrefixName(name.toTermName, mixName.toTermName) - var superName = SuperAccessorName(preName) - if (clazz.is(Trait)) superName = superName.expandedName(clazz) - val superInfo = sel.tpe.widenSingleton.ensureMethodic + def superAccessorName(original: Name) = + val unexpanded = SuperAccessorName( + if mixName.isEmpty then original.toTermName + else ExpandPrefixName(original.toTermName, mixName.toTermName)) + if clazz.is(Trait) then unexpanded.expandedName(clazz) else unexpanded + + val superName = superAccessorName(name) + val superInfo = sel.tpe.widenSingleton.ensureMethodic val accRange = sel.span.focus val superAcc = clazz.info.decl(superName) .suchThat(_.signature == superInfo.signature).symbol @@ -81,6 +87,10 @@ class SuperAccessors(thisPhase: DenotTransformer) { val acc = newSymbol( clazz, superName, Artifact | Method | maybeDeferred, superInfo, coord = accRange).enteredAfter(thisPhase) + if !Denotations.targetNamesMatch(sym.name, sym.erasedName) then + acc.addAnnotation( + Annotation(defn.TargetNameAnnot, + Literal(Constant(superAccessorName(sym.erasedName).toString)).withSpan(sym.span))) // Diagnostic for SI-7091 if (!accDefs.contains(clazz)) report.error( diff --git a/tests/run/supercalls-traits-targetname.check b/tests/run/supercalls-traits-targetname.check new file mode 100644 index 000000000000..f559999a8f5d --- /dev/null +++ b/tests/run/supercalls-traits-targetname.check @@ -0,0 +1,5 @@ +C1A1A2B1B2C2 +C1B3B4C3 +IT +AT +ER diff --git a/tests/run/supercalls-traits-targetname.scala b/tests/run/supercalls-traits-targetname.scala new file mode 100644 index 000000000000..330c7a128eb8 --- /dev/null +++ b/tests/run/supercalls-traits-targetname.scala @@ -0,0 +1,57 @@ +import annotation.targetName + +trait A { + @targetName("foo2") def foo = 1 +} + +trait B { + @targetName("foo2") def foo = 2 +} + +class C extends A with B { + @targetName("foo2") override def foo = super[A].foo + super[B].foo +} + +class Base[A](exp: => Option[A]) + +object Empty extends Base[Nothing](None) + + +trait B1 extends C1 { @targetName("f2") override def f() = { super.f(); print("B1") }} +trait B2 extends B1 { @targetName("f2") override def f() = { super.f(); print("B2") }} +trait A1 extends C1 { @targetName("f2") override def f() = { super.f(); print("A1") }} +trait A2 extends A1 { @targetName("f2") override def f() = { super.f(); print("A2") }} +class C1 { @targetName("f2") def f() = print("C1") } +class C2 extends A2 with B2 { @targetName("f2") override def f() = { super.f(); print("C2") }} + + +trait B3 extends C1 { @targetName("f2") override def f() = { super.f(); print("B3") }} +trait B4 extends C1 { this: B3 => @targetName("f2") override def f() = { super.f(); print("B4") }} +class C3 extends C1 with B3 with B4 { @targetName("f2") override def f() = { super.f(); print("C3") }} + +trait DT { + @targetName("f2") def f(): Unit +} +trait IT extends DT { + @targetName("f2") def f() = { println("IT") } +} +abstract class MPT { +} +trait AT extends MPT with DT { + @targetName("f2") abstract override def f() = { super.f(); println("AT") } +} +class ER extends MPT with IT with AT { + @targetName("f2") override def f() = { super.f(); println("ER") } +} + +object Test { + def main(args: Array[String]): Unit = { + assert(new C().foo == 3) + new C2().f() + println() + new C3().f() + println() + new ER().f() + Empty + } +} From 419cd87f040bf3d26dc46399c4e6c9384af8c792 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 4 Nov 2020 10:41:06 +0100 Subject: [PATCH 05/20] Print arguments of annotations So far, annotations of symbols printed as just the class name; also include their arguments. But avoid this for Body annotations, since they just repeat the possibly large body of an inline function. --- compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index c2cc0f52214c..21948ada6b98 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -881,6 +881,11 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { protected def annotText(tree: untpd.Tree): Text = "@" ~ constrText(tree) // DD + override def annotsText(sym: Symbol): Text = + Text(sym.annotations.map(ann => + if ann.symbol == defn.BodyAnnot then Str(simpleNameString(ann.symbol)) + else annotText(ann.tree))) + protected def modText(mods: untpd.Modifiers, sym: Symbol, kw: String, isType: Boolean): Text = { // DD val suppressKw = if (enclDefIsClass) mods.isAllOf(LocalParam) else mods.is(Param) var flagMask = From b7b1e1b23f76c5405ffa5094aad65e4e16fec0be Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 4 Nov 2020 13:07:20 +0100 Subject: [PATCH 06/20] Handle retained inline bodies for methods with @targetName annotations --- compiler/src/dotty/tools/dotc/transform/Erasure.scala | 10 +++++++++- .../dotty/tools/dotc/transform/SuperAccessors.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Inliner.scala | 2 ++ tests/run/inline-override.check | 2 ++ tests/run/inline-override.scala | 6 ++++++ 5 files changed, 20 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index e66606fd2882..087f2a5ff729 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -936,9 +936,17 @@ object Erasure { case stat: DefDef @unchecked if stat.symbol.name.is(BodyRetainerName) => val retainer = stat.symbol val origName = retainer.name.asTermName.exclude(BodyRetainerName) + val targetName = + if retainer.hasAnnotation(defn.TargetNameAnnot) then + try retainer.erasedName + finally + // reset target name so that definition will have a BodyRetainerName + // after erasure and be thereby eliminated in typedDefDef + retainer.setTargetName(retainer.name) + else origName val inlineMeth = atPhase(typerPhase) { retainer.owner.info.decl(origName) - .matchingDenotation(retainer.owner.thisType, stat.symbol.info) + .matchingDenotation(retainer.owner.thisType, stat.symbol.info, targetName) .symbol } (inlineMeth, stat) diff --git a/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala b/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala index a2ba3e1023d0..86d915134151 100644 --- a/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala +++ b/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala @@ -87,7 +87,7 @@ class SuperAccessors(thisPhase: DenotTransformer) { val acc = newSymbol( clazz, superName, Artifact | Method | maybeDeferred, superInfo, coord = accRange).enteredAfter(thisPhase) - if !Denotations.targetNamesMatch(sym.name, sym.erasedName) then + if sym.hasAnnotation(defn.TargetNameAnnot) then acc.addAnnotation( Annotation(defn.TargetNameAnnot, Literal(Constant(superAccessorName(sym.erasedName).toString)).withSpan(sym.span))) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 7efd0f8fc58c..f18ed0e1f040 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -198,6 +198,8 @@ object Inliner { name = BodyRetainerName(meth.name), flags = meth.flags &~ (Inline | Macro | Override) | Private, coord = mdef.rhs.span.startPos).asTerm + for targetAnnot <- meth.getAnnotation(defn.TargetNameAnnot) do + retainer.addAnnotation(targetAnnot) polyDefDef(retainer, targs => prefss => inlineCall( ref(meth).appliedToTypes(targs).appliedToArgss(prefss) diff --git a/tests/run/inline-override.check b/tests/run/inline-override.check index 60d76e340238..60bcf2e154d3 100644 --- a/tests/run/inline-override.check +++ b/tests/run/inline-override.check @@ -3,3 +3,5 @@ inline 22 inline 22 inline 22 inline 22 +inline 22 +inline 22 diff --git a/tests/run/inline-override.scala b/tests/run/inline-override.scala index e62405f3f687..a128002fc4ba 100644 --- a/tests/run/inline-override.scala +++ b/tests/run/inline-override.scala @@ -1,22 +1,28 @@ +import annotation.targetName + abstract class A: def f(x: Int) = s"dynamic $x" def h(x: Int): String + @targetName("h2") def h1(x: Int): String inline def i(x: Int): String class B extends A: inline override def f(x: Int) = g(x) inline def g(x: Int) = s"inline $x" inline def h(x: Int) = g(x) + @targetName("h2") inline def h1(x: Int) = g(x) inline def i(x: Int) = g(x) @main def Test = val b = B() println(b.f(22)) println(b.h(22)) + println(b.h1(22)) println(b.i(22)) val a: A = b println(a.f(22)) println(a.h(22)) + println(a.h1(22)) // println(a.i(22)) From 87bce7b7ee24bdd2bab55d264ca67e973833fc3e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 4 Nov 2020 16:12:24 +0100 Subject: [PATCH 07/20] Update reference pages --- .../reference/changed-features/operators.md | 68 +++++----------- .../other-new-features/targetName.md | 79 +++++++++++++++++++ docs/docs/reference/overview.md | 5 +- docs/sidebar.yml | 4 +- tests/neg/override-erasure-clash.check | 7 ++ tests/neg/override-erasure-clash.scala | 6 ++ 6 files changed, 117 insertions(+), 52 deletions(-) create mode 100644 docs/docs/reference/other-new-features/targetName.md create mode 100644 tests/neg/override-erasure-clash.check create mode 100644 tests/neg/override-erasure-clash.scala diff --git a/docs/docs/reference/changed-features/operators.md b/docs/docs/reference/changed-features/operators.md index 2aa20a9baa0c..70155e77330e 100644 --- a/docs/docs/reference/changed-features/operators.md +++ b/docs/docs/reference/changed-features/operators.md @@ -3,60 +3,17 @@ layout: doc-page title: Rules for Operators --- -The rules for infix operators have changed. There are two annotations that regulate operators: `infix` and `alpha`. -Furthermore, a syntax change allows infix operators to be written on the left in a multi-line expression. +The rules for infix operators have changed in some parts: -## The @alpha Annotation - -An `@alpha` annotation on a method definition defines an alternate name for the implementation of that method: Example: -```scala -import scala.annotation.alpha - -object VecOps { - @alpha("append") def (xs: Vec[T]) ++= [T] (ys: Vec[T]): Vec[T] = ... -} -``` -Here, the `++=` operation is implemented (in Byte code or native code) under the name `append`. The implementation name affects the code that is generated, and is the name under which code from other languages can call the method. For instance, `++=` could be invoked from Java like this: -``` -VecOps.append(vec1, vec2) -``` -The `@alpha` annotation has no bearing on Scala usages. Any application of that method in Scala has to use `++=`, not `append`. - -### Motivation - -The `@alpha` annotation serves a dual purpose: - - - It helps interoperability between Scala and other languages. - - It serves as a documentation tool by providing an alternative regular name - as an alias of a symbolic operator. - -### Details - - 1. `@alpha` is defined in package `scala.annotation`. It takes a single argument - of type `String`. That string is called the _external name_ of the definition - that's annotated. - - 2. An `@alpha` annotation can be given for all kinds of definitions. - - 3. The name given in an `@alpha` annotation must be a legal name - for the defined entities on the host platform. - - 4. Definitions with symbolic names should have an `@alpha` annotation. Lack of such - an annotation will raise a deprecation warning. - - 5. Definitions with names in backticks that are not legal host platform names - should have an `@alpha` annotation. Lack of such an annotation will raise a deprecation warning. - - 6. `@alpha` annotations must agree: If two definitions are members of an object or class with the same name and matching types, then either none of them has an `@alpha` annotation, or both have `@alpha` annotations with the same name. - - 7. There must be a one-to-one relationship between external and internal names: - If two definitions are members of an object or class with matching types and both have `@alpha` annotations with the same external name, then their internal method names must also be the same. +First, an alphanumeric method can be used as an infix operator only if its definition carries an `@infix` annotation. Second, it is recommended (but not enforced) to +augment definitions of symbolic operators with `@targetName` annotations. Finally, +a syntax change allows infix operators to be written on the left in a multi-line expression. ## The @infix Annotation An `@infix` annotation on a method definition allows using the method as an infix operation. Example: ```scala -import scala.annotation.alpha +import scala.annotation.{infix, targetName} trait MultiSet[T] { @@ -65,7 +22,7 @@ trait MultiSet[T] { def difference(other: MultiSet[T]): MultiSet[T] - @alpha("intersection") + @targetName("intersection") def *(other: MultiSet[T]): MultiSet[T] } @@ -133,6 +90,19 @@ The purpose of the `@infix` annotation is to achieve consistency across a code b 5. To smooth migration to Scala 3.0, alphanumeric operators will only be deprecated from Scala 3.1 onwards, or if the `-source 3.1` option is given in Dotty/Scala 3. +## The @targetName Annotation + +It is recommended that definitions of symbolic operators carry a [@targetName annotation](../other-new-features/targetName.html) that provides an encoding of the operator with an alphanumeric name. This has several benefits: + + - It helps interoperability between Scala and other languages. One can call + a Scala-defined symbolic operator from another language using its target name, + which avoids having to remember the low-level encoding of the symbolic name. + - It helps legibility of stacktraces and other runtime diagnostics, where the + user-defined alphanumeric name will be shown instead of the low-level encoding. + - It serves as a documentation tool by providing an alternative regular name + as an alias of a symbolic operator. This makes the definition also easier + to find in a search. + ## Syntax Change Infix operators can now appear at the start of lines in a multi-line expression. Examples: diff --git a/docs/docs/reference/other-new-features/targetName.md b/docs/docs/reference/other-new-features/targetName.md new file mode 100644 index 000000000000..2c6a2b95cacf --- /dev/null +++ b/docs/docs/reference/other-new-features/targetName.md @@ -0,0 +1,79 @@ +--- +layout: doc-page +title: The @targetName annotation +--- + +A `@targetName` annotation on a definition defines an alternate name for the implementation of that definition: Example: +```scala +import scala.annotation.targetName + +object VecOps { + @targetName("append") def (xs: Vec[T]) ++= [T] (ys: Vec[T]): Vec[T] = ... +} +``` +Here, the `++=` operation is implemented (in Byte code or native code) under the name `append`. The implementation name affects the code that is generated, and is the name under which code from other languages can call the method. For instance, `++=` could be invoked from Java like this: +``` +VecOps.append(vec1, vec2) +``` +The `@targetName` annotation has no bearing on Scala usages. Any application of that method in Scala has to use `++=`, not `append`. + +### Details + + 1. `@targetName` is defined in package `scala.annotation`. It takes a single argument + of type `String`. That string is called the _external name_ of the definition + that's annotated. + + 2. A `@targetName` annotation can be given for all kinds of definitions. + + 3. The name given in a `@targetName` annotation must be a legal name + for the defined entities on the host platform. + + 4. It is recommended that definitions with symbolic names have a `@targetName` annotation. This will establish an alternate name that is easier to search for and + will avoid cryptic encodings in runtime diagnostics. + + 5. Definitions with names in backticks that are not legal host platform names + should also have a `@targetName` annotation. + +### Relationship with Overriding + +`@targetName` annotations are significant for matching two method definitions to decide whether they conflict or override each other. Two method definitions match if they have the same name, signature, and erased name. Here, + + - The _signature_ of a definition consists of the names of the erased types of all (value-) parameters and the method's result type. + - The _erased name_ of a method definition is its target name if a `@targetName` + annotation is given and its defined name otherwise. + +This means that `@targetName` annotations can be used to disambiguate two method definitions that would otherwise clash. For instance. +```scala +def f(x: => String): Int = x.length +def f(x: => Int): Int = x + 1 // error: double definition +``` +The two definitions above clash since their erased parameter types are both `Function0`, which is the type of the translation of a by-name-parameter. Hence +they have the same names and signatures. But we can avoid the clash by adding a `@targetName` annotation to either method or to both of them. E.g. +```scala +@targetName("f_string") +def f(x: => String): Int = x.length +def f(x: => Int): Int = x + 1 // OK +``` +This will produce methods `f_string` and `f` in the generated code. + +As usual, any overriding relationship in the generated code must also +be present in the original code. So the following example would be in error: +```scala +import annotation.targetName +class A: + def f(): Int = 1 +class B extends A: + targetName("f") def g(): Int = 2 +``` +Here, the original methods `g` and `f` do not override each other since they have +different names. But once we switch to target names, there is a clash that is reported by the compiler: +``` +-- [E120] Naming Error: test.scala:4:6 ----------------------------------------- +4 |class B extends A: + | ^ + | Name clash between defined and inherited member: + | def f(): Int in class A at line 3 and + | def g(): Int in class B at line 5 + | have the same name and type after erasure. +1 error found +``` diff --git a/docs/docs/reference/overview.md b/docs/docs/reference/overview.md index 7ab8705dd179..84f5714fd192 100644 --- a/docs/docs/reference/overview.md +++ b/docs/docs/reference/overview.md @@ -58,8 +58,8 @@ These constructs are restricted to make the language safer. - [Given Imports](contextual/given-imports.md): implicits now require a special form of import, to make the import clearly visible. - [Type Projection](dropped-features/type-projection.md): only classes can be used as prefix `C` of a type projection `C#A`. Type projection on abstract types is no longer supported since it is unsound. - [Multiversal Equality](contextual/multiversal-equality.md) implements an "opt-in" scheme to rule out nonsensical comparisons with `==` and `!=`. - - [@infix and @alpha](changed-features/operators.md) - make method application syntax uniform across code bases and require alphanumeric aliases for all symbolic names (proposed, not implemented). + - [@infix annotations](changed-features/operators.md) + make method application syntax uniform across code bases. Unrestricted implicit conversions continue to be available in Scala 3.0, but will be deprecated and removed later. Unrestricted versions of the other constructs in the list above are available only under `-source 3.0-migration`. @@ -109,6 +109,7 @@ These are additions to the language that make it more powerful or pleasant to us - [Dependent Function Types](new-types/dependent-function-types.md) generalize dependent methods to dependent function values and types. - [Polymorphic Function Types](https://github.com/lampepfl/dotty/pull/4672) generalize polymorphic methods to dependent function values and types. _Current status_: There is a proposal, and a prototype implementation, but the implementation has not been finalized or merged yet. - [Kind Polymorphism](other-new-features/kind-polymorphism.md) allows the definition of operators working equally on types and type constructors. + - [@targetName Annotations](other-new-features/targetName.md) make it easier to interoperate with code written in other languages and give more flexibility for avoiding name clashes. ## Metaprogramming diff --git a/docs/sidebar.yml b/docs/sidebar.yml index 93683709a745..fd7b76b41fca 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -103,8 +103,10 @@ sidebar: url: docs/reference/other-new-features/kind-polymorphism.html - title: Tupled Function url: docs/reference/other-new-features/tupled-function.html - - title: threadUnsafe Annotation + - title: @threadUnsafe Annotation url: docs/reference/other-new-features/threadUnsafe-annotation.html + - title: @targetName Annotation + url: docs/reference/other-new-features/targetName.html - title: New Control Syntax url: docs/reference/other-new-features/control-syntax.html - title: Optional Braces diff --git a/tests/neg/override-erasure-clash.check b/tests/neg/override-erasure-clash.check new file mode 100644 index 000000000000..e936a8de2397 --- /dev/null +++ b/tests/neg/override-erasure-clash.check @@ -0,0 +1,7 @@ +-- [E120] Naming Error: tests/neg/override-erasure-clash.scala:4:6 ----------------------------------------------------- +4 |class B extends A: // error + | ^ + | Name clash between defined and inherited member: + | def f(): Int in class A at line 3 and + | def g(): Int in class B at line 5 + | have the same name and type after erasure. diff --git a/tests/neg/override-erasure-clash.scala b/tests/neg/override-erasure-clash.scala new file mode 100644 index 000000000000..688eb816afcc --- /dev/null +++ b/tests/neg/override-erasure-clash.scala @@ -0,0 +1,6 @@ +import annotation.targetName +class A: + def f(): Int = 1 +class B extends A: // error + @targetName("f") def g(): Int = 2 + From 270f6618336d1c0f7e3991ebf41dde5d90d9e097 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 4 Nov 2020 16:35:04 +0100 Subject: [PATCH 08/20] Update test to show that #7942 is fixed --- tests/run/targetName.check | 3 +++ tests/run/targetName.scala | 17 +++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 tests/run/targetName.check diff --git a/tests/run/targetName.check b/tests/run/targetName.check new file mode 100644 index 000000000000..9315ab62c86c --- /dev/null +++ b/tests/run/targetName.check @@ -0,0 +1,3 @@ +strings: ArraySeq(hello) +ints: ArraySeq(1, 2) +objs: ArraySeq(1, hi) diff --git a/tests/run/targetName.scala b/tests/run/targetName.scala index 00b04ec633c2..7774922c7b12 100644 --- a/tests/run/targetName.scala +++ b/tests/run/targetName.scala @@ -5,8 +5,6 @@ object A: @targetName("f2") def f(x: => Int): Int = x import A._ -@main def Test = - assert(f("abc") == f(3)) trait T: def f(x: => String): Int @@ -21,3 +19,18 @@ object B1 extends C, T: object B2 extends C, T: override def f(x: => String): Int = x.length + 1 + +@targetName("fooString") def foo(ps: String*) : Unit = println(s"strings: $ps") +@targetName("fooInt") def foo(ps: Int*) : Unit = println(s"ints: $ps") +@targetName("fooAny") def foo(ps: Any*) : Unit = println(s"objs: $ps") + +@main def Test = + assert(f("abc") == f(3)) + assert(B2.f("abc") == 4) + val t: T = B1 + assert(t.f(2) == 3) + foo("hello") + foo(1, 2) + foo(1, "hi") + + From 3b0f98c8294e41b0b725630ca4455d7b1ef0a563 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 5 Nov 2020 12:05:05 +0100 Subject: [PATCH 09/20] Fix handling of RetainedInline when compiling from tasty --- compiler/src/dotty/tools/dotc/transform/Erasure.scala | 6 +----- compiler/src/dotty/tools/dotc/typer/Inliner.scala | 7 +++++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index 087f2a5ff729..b96b8739380f 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -938,11 +938,7 @@ object Erasure { val origName = retainer.name.asTermName.exclude(BodyRetainerName) val targetName = if retainer.hasAnnotation(defn.TargetNameAnnot) then - try retainer.erasedName - finally - // reset target name so that definition will have a BodyRetainerName - // after erasure and be thereby eliminated in typedDefDef - retainer.setTargetName(retainer.name) + retainer.erasedName.unmangle(BodyRetainerName).exclude(BodyRetainerName) else origName val inlineMeth = atPhase(typerPhase) { retainer.owner.info.decl(origName) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index f18ed0e1f040..28626a4ed1b0 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -17,6 +17,7 @@ import Contexts._ import Names.{Name, TermName} import NameKinds.{InlineAccessorName, InlineBinderName, InlineScrutineeName, BodyRetainerName} import ProtoTypes.selectionProto +import Annotations.Annotation import SymDenotations.SymDenotation import Inferencing.isFullyDefined import config.Printers.inlining @@ -198,8 +199,10 @@ object Inliner { name = BodyRetainerName(meth.name), flags = meth.flags &~ (Inline | Macro | Override) | Private, coord = mdef.rhs.span.startPos).asTerm - for targetAnnot <- meth.getAnnotation(defn.TargetNameAnnot) do - retainer.addAnnotation(targetAnnot) + if meth.hasAnnotation(defn.TargetNameAnnot) then + retainer.addAnnotation( + Annotation(defn.TargetNameAnnot, + Literal(Constant(BodyRetainerName(meth.erasedName.asTermName).toString)).withSpan(meth.span))) polyDefDef(retainer, targs => prefss => inlineCall( ref(meth).appliedToTypes(targs).appliedToArgss(prefss) From 1195bd14749648442167ba2bb7b714748f8626f7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 5 Nov 2020 12:21:51 +0100 Subject: [PATCH 10/20] Update features classification doc page --- docs/docs/reference/features-classification.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/reference/features-classification.md b/docs/docs/reference/features-classification.md index 459370613f62..f831aca713dc 100644 --- a/docs/docs/reference/features-classification.md +++ b/docs/docs/reference/features-classification.md @@ -73,8 +73,8 @@ These constructs are restricted to make the language safer. - [Given Imports](contextual/import-delegate.md): implicits now require a special form of import, to make the import clearly visible. - [Type Projection](dropped-features/type-projection.md): only classes can be used as prefix `C` of a type projection `C#A`. Type projection on abstract types is no longer supported since it is unsound. - [Multiversal Equality](contextual/multiversal-equality.md) implements an "opt-in" scheme to rule out nonsensical comparisons with `==` and `!=`. - - [@infix and @alpha](https://github.com/lampepfl/dotty/pull/5975) - make method application syntax uniform across code bases and require alphanumeric aliases for all symbolic names (proposed, not implemented). + - [@infix](https://github.com/lampepfl/dotty/pull/5975) + makes method application syntax uniform across code bases. Unrestricted implicit conversions continue to be available in Scala 3.0, but will be deprecated and removed later. Unrestricted versions of the other constructs in the list above are available only under `-source 3.0-migration`. From fca3345f96891e153e566736b667e41afdf92fa3 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 5 Nov 2020 12:23:09 +0100 Subject: [PATCH 11/20] Factor out common behavior for target name annotations --- .../tools/dotc/transform/SuperAccessors.scala | 5 +- .../dotty/tools/dotc/transform/SymUtils.scala | 483 +++++++++--------- .../src/dotty/tools/dotc/typer/Inliner.scala | 5 +- 3 files changed, 249 insertions(+), 244 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala b/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala index 86d915134151..6247444d97b4 100644 --- a/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala +++ b/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala @@ -87,10 +87,7 @@ class SuperAccessors(thisPhase: DenotTransformer) { val acc = newSymbol( clazz, superName, Artifact | Method | maybeDeferred, superInfo, coord = accRange).enteredAfter(thisPhase) - if sym.hasAnnotation(defn.TargetNameAnnot) then - acc.addAnnotation( - Annotation(defn.TargetNameAnnot, - Literal(Constant(superAccessorName(sym.erasedName).toString)).withSpan(sym.span))) + acc.deriveTargetNameAnnotation(sym, superAccessorName) // Diagnostic for SI-7091 if (!accDefs.contains(clazz)) report.error( diff --git a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala index 9adc11585638..725e422f747d 100644 --- a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala +++ b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala @@ -14,247 +14,258 @@ import Flags._ import Annotations._ import ValueClasses.isDerivedValueClass import Decorators._ +import Constants.Constant +import Annotations.Annotation +import ast.tpd.Literal import language.implicitConversions import scala.annotation.tailrec -object SymUtils { - - extension (self: Symbol) { - - /** All traits implemented by a class or trait except for those inherited - * through the superclass. Traits are given in the order they appear in the - * parents clause (which is the reverse of their order in baseClasses) - */ - def directlyInheritedTraits(using Context): List[ClassSymbol] = { - val superCls = self.asClass.superClass - val baseClasses = self.asClass.baseClasses - if (baseClasses.isEmpty) Nil - else - def recur(bcs: List[ClassSymbol], acc: List[ClassSymbol]): List[ClassSymbol] = bcs match - case bc :: bcs1 => if bc eq superCls then acc else recur(bcs1, bc :: acc) - case nil => acc - recur(baseClasses.tail, Nil) - } - - /** All traits implemented by a class, except for those inherited through the superclass. - * The empty list if `self` is a trait. - */ - def mixins(using Context): List[ClassSymbol] = - if (self.is(Trait)) Nil - else directlyInheritedTraits - - def isTypeTest(using Context): Boolean = - self == defn.Any_isInstanceOf || self == defn.Any_typeTest - - def isTypeCast(using Context): Boolean = - self == defn.Any_asInstanceOf || self == defn.Any_typeCast - - def isTypeTestOrCast(using Context): Boolean = - isTypeCast || isTypeTest - - def isThreadUnsafe(using Context): Boolean = self.hasAnnotation(defn.ThreadUnsafeAnnot) - - def isVolatile(using Context): Boolean = self.hasAnnotation(defn.VolatileAnnot) - - def isAnyOverride(using Context): Boolean = self.is(Override) || self.is(AbsOverride) - // careful: AbsOverride is a term only flag. combining with Override would catch only terms. - - def isSuperAccessor(using Context): Boolean = self.name.is(SuperAccessorName) - - /** Is this a type or term parameter or a term parameter accessor? */ - def isParamOrAccessor(using Context): Boolean = - self.is(Param) || self.is(ParamAccessor) - - def derivesFromJavaEnum(using Context) = - self.is(Enum, butNot = Case) && - self.info.parents.exists(p => p.typeSymbol == defn.JavaEnumClass) - - /** Is this a case class for which a product mirror is generated? - * Excluded are value classes, abstract classes and case classes with more than one - * parameter section. - */ - def whyNotGenericProduct(using Context): String = - if (!self.is(CaseClass)) "it is not a case class" - else if (self.is(Abstract)) "it is an abstract class" - else if (self.primaryConstructor.info.paramInfoss.length != 1) "it takes more than one parameter list" - else if (isDerivedValueClass(self)) "it is a value class" - else "" - - def isGenericProduct(using Context): Boolean = whyNotGenericProduct.isEmpty - - /** Is this a sealed class or trait for which a sum mirror is generated? - * It must satisfy the following conditions: - * - it has at least one child class or object - * - none of its children are anonymous classes - * - all of its children are addressable through a path from its companion object - * - all of its children are generic products or singletons - */ - def whyNotGenericSum(using Context): String = - if (!self.is(Sealed)) - s"it is not a sealed ${self.kindString}" - else { - val children = self.children - val companion = self.linkedClass - def problem(child: Symbol) = { - - def isAccessible(sym: Symbol): Boolean = - companion.isContainedIn(sym) || sym.is(Module) && isAccessible(sym.owner) - - if (child == self) "it has anonymous or inaccessible subclasses" - else if (!isAccessible(child.owner)) i"its child $child is not accessible" - else if (!child.isClass) "" - else { - val s = child.whyNotGenericProduct - if (s.isEmpty) s - else i"its child $child is not a generic product because $s" +object SymUtils: + + extension (self: Symbol) + + /** All traits implemented by a class or trait except for those inherited + * through the superclass. Traits are given in the order they appear in the + * parents clause (which is the reverse of their order in baseClasses) + */ + def directlyInheritedTraits(using Context): List[ClassSymbol] = { + val superCls = self.asClass.superClass + val baseClasses = self.asClass.baseClasses + if (baseClasses.isEmpty) Nil + else + def recur(bcs: List[ClassSymbol], acc: List[ClassSymbol]): List[ClassSymbol] = bcs match + case bc :: bcs1 => if bc eq superCls then acc else recur(bcs1, bc :: acc) + case nil => acc + recur(baseClasses.tail, Nil) + } + + /** All traits implemented by a class, except for those inherited through the superclass. + * The empty list if `self` is a trait. + */ + def mixins(using Context): List[ClassSymbol] = + if (self.is(Trait)) Nil + else directlyInheritedTraits + + def isTypeTest(using Context): Boolean = + self == defn.Any_isInstanceOf || self == defn.Any_typeTest + + def isTypeCast(using Context): Boolean = + self == defn.Any_asInstanceOf || self == defn.Any_typeCast + + def isTypeTestOrCast(using Context): Boolean = + isTypeCast || isTypeTest + + def isThreadUnsafe(using Context): Boolean = self.hasAnnotation(defn.ThreadUnsafeAnnot) + + def isVolatile(using Context): Boolean = self.hasAnnotation(defn.VolatileAnnot) + + def isAnyOverride(using Context): Boolean = self.is(Override) || self.is(AbsOverride) + // careful: AbsOverride is a term only flag. combining with Override would catch only terms. + + def isSuperAccessor(using Context): Boolean = self.name.is(SuperAccessorName) + + /** Is this a type or term parameter or a term parameter accessor? */ + def isParamOrAccessor(using Context): Boolean = + self.is(Param) || self.is(ParamAccessor) + + def derivesFromJavaEnum(using Context) = + self.is(Enum, butNot = Case) && + self.info.parents.exists(p => p.typeSymbol == defn.JavaEnumClass) + + /** Is this a case class for which a product mirror is generated? + * Excluded are value classes, abstract classes and case classes with more than one + * parameter section. + */ + def whyNotGenericProduct(using Context): String = + if (!self.is(CaseClass)) "it is not a case class" + else if (self.is(Abstract)) "it is an abstract class" + else if (self.primaryConstructor.info.paramInfoss.length != 1) "it takes more than one parameter list" + else if (isDerivedValueClass(self)) "it is a value class" + else "" + + def isGenericProduct(using Context): Boolean = whyNotGenericProduct.isEmpty + + /** Is this a sealed class or trait for which a sum mirror is generated? + * It must satisfy the following conditions: + * - it has at least one child class or object + * - none of its children are anonymous classes + * - all of its children are addressable through a path from its companion object + * - all of its children are generic products or singletons + */ + def whyNotGenericSum(using Context): String = + if (!self.is(Sealed)) + s"it is not a sealed ${self.kindString}" + else { + val children = self.children + val companion = self.linkedClass + def problem(child: Symbol) = { + + def isAccessible(sym: Symbol): Boolean = + companion.isContainedIn(sym) || sym.is(Module) && isAccessible(sym.owner) + + if (child == self) "it has anonymous or inaccessible subclasses" + else if (!isAccessible(child.owner)) i"its child $child is not accessible" + else if (!child.isClass) "" + else { + val s = child.whyNotGenericProduct + if (s.isEmpty) s + else i"its child $child is not a generic product because $s" + } } + if (children.isEmpty) "it does not have subclasses" + else children.map(problem).find(!_.isEmpty).getOrElse("") } - if (children.isEmpty) "it does not have subclasses" - else children.map(problem).find(!_.isEmpty).getOrElse("") + + def isGenericSum(using Context): Boolean = whyNotGenericSum.isEmpty + + /** If this is a constructor, its owner: otherwise this. */ + final def skipConstructor(using Context): Symbol = + if (self.isConstructor) self.owner else self + + /** The closest properly enclosing method or class of this symbol. */ + final def enclosure(using Context): Symbol = + self.owner.enclosingMethodOrClass + + /** The closest enclosing method or class of this symbol */ + @tailrec final def enclosingMethodOrClass(using Context): Symbol = + if (self.is(Method) || self.isClass) self + else if (self.exists) self.owner.enclosingMethodOrClass + else NoSymbol + + /** Apply symbol/symbol substitution to this symbol */ + def subst(from: List[Symbol], to: List[Symbol]): Symbol = { + @tailrec def loop(from: List[Symbol], to: List[Symbol]): Symbol = + if (from.isEmpty) self + else if (self eq from.head) to.head + else loop(from.tail, to.tail) + loop(from, to) } - def isGenericSum(using Context): Boolean = whyNotGenericSum.isEmpty - - /** If this is a constructor, its owner: otherwise this. */ - final def skipConstructor(using Context): Symbol = - if (self.isConstructor) self.owner else self - - /** The closest properly enclosing method or class of this symbol. */ - final def enclosure(using Context): Symbol = - self.owner.enclosingMethodOrClass - - /** The closest enclosing method or class of this symbol */ - @tailrec final def enclosingMethodOrClass(using Context): Symbol = - if (self.is(Method) || self.isClass) self - else if (self.exists) self.owner.enclosingMethodOrClass - else NoSymbol - - /** Apply symbol/symbol substitution to this symbol */ - def subst(from: List[Symbol], to: List[Symbol]): Symbol = { - @tailrec def loop(from: List[Symbol], to: List[Symbol]): Symbol = - if (from.isEmpty) self - else if (self eq from.head) to.head - else loop(from.tail, to.tail) - loop(from, to) - } - - def accessorNamed(name: TermName)(using Context): Symbol = - self.owner.info.decl(name).suchThat(_.is(Accessor)).symbol - - def caseAccessors(using Context): List[Symbol] = - self.info.decls.filter(_.is(CaseAccessor)) - - def getter(using Context): Symbol = - if (self.isGetter) self else accessorNamed(self.asTerm.name.getterName) - - def setter(using Context): Symbol = - if (self.isSetter) self - else accessorNamed(self.asTerm.name.setterName) - - def traitSetter(using Context): Symbol = - if (self.name.is(TraitSetterName)) self - else accessorNamed(Mixin.traitSetterName(self.asTerm)) - - def field(using Context): Symbol = { - val thisName = self.name.asTermName - val fieldName = - if (self.hasAnnotation(defn.ScalaStaticAnnot)) thisName.getterName - else thisName.fieldName - self.owner.info.decl(fieldName).suchThat(!_.is(Method)).symbol - } - - def isField(using Context): Boolean = - self.isTerm && !self.is(Method) - - def isEnumCase(using Context): Boolean = - self.isAllOf(EnumCase, butNot = JavaDefined) - - def annotationsCarrying(meta: ClassSymbol)(using Context): List[Annotation] = - self.annotations.filter(_.symbol.hasAnnotation(meta)) - - def withAnnotationsCarrying(from: Symbol, meta: ClassSymbol)(using Context): self.type = { - self.addAnnotations(from.annotationsCarrying(meta)) - self - } - - def isEnum(using Context): Boolean = self.is(Enum, butNot = JavaDefined) - def isEnumClass(using Context): Boolean = isEnum && !self.is(Case) - - /** Does this symbol refer to anonymous classes synthesized by enum desugaring? */ - def isEnumAnonymClass(using Context): Boolean = - self.isAnonymousClass && (self.owner.name.eq(nme.DOLLAR_NEW) || self.owner.is(CaseVal)) - - /** Is this symbol defined locally (i.e. at some level owned by a term) so that - * it cannot be seen from parent class `cls`? - */ - def isInaccessibleChildOf(cls: Symbol)(using Context): Boolean = - def isAccessible(sym: Symbol, cls: Symbol): Boolean = - if cls.isType && !cls.is(Package) then - isAccessible(sym, cls.owner) - else - sym == cls - || sym.is(Package) - || sym.isType && isAccessible(sym.owner, cls) - !isAccessible(self.owner, cls) - - def hasAnonymousChild(using Context): Boolean = - self.children.exists(_ `eq` self) - - /** Is this symbol directly owner by a term symbol, i.e., is it local to a block? */ - def isLocalToBlock(using Context): Boolean = - self.owner.isTerm - - /** Is symbol directly or indirectly owned by a term symbol? */ - @tailrec final def isLocal(using Context): Boolean = { - val owner = self.maybeOwner - if (!owner.exists) false - else if (isLocalToBlock) true - else if (owner.is(Package)) false - else owner.isLocal - } - - /** The typeRef with wildcard arguments for each type parameter */ - def rawTypeRef(using Context) = - self.typeRef.appliedTo(self.typeParams.map(_ => TypeBounds.emptyPolyKind)) - - /** Is symbol a quote operation? */ - def isQuote(using Context): Boolean = - self == defn.InternalQuoted_exprQuote || self == defn.QuotedTypeModule_apply - - /** Is symbol a term splice operation? */ - def isExprSplice(using Context): Boolean = - self == defn.InternalQuoted_exprSplice || self == defn.InternalQuoted_exprNestedSplice - - /** Is symbol a type splice operation? */ - def isTypeSplice(using Context): Boolean = - self == defn.QuotedType_splice - - def isScalaStatic(using Context): Boolean = - self.hasAnnotation(defn.ScalaStaticAnnot) - - def isDeprecated(using Context): Boolean = - self.hasAnnotation(defn.DeprecatedAnnot) - - /** Is symbol assumed or declared as an infix symbol? */ - def isDeclaredInfix(using Context): Boolean = - self.hasAnnotation(defn.InfixAnnot) - || defn.isInfix(self) - || self.name.isUnapplyName - && self.owner.is(Module) - && self.owner.linkedClass.is(Case) - && self.owner.linkedClass.isDeclaredInfix - - /** The declared self type of this class, as seen from `site`, stripping - * all refinements for opaque types. - */ - def declaredSelfTypeAsSeenFrom(site: Type)(using Context) = - extension (tp: Type) def stripOpaques: Type = tp match - case RefinedType(parent, name, _) if self.info.decl(name).symbol.isOpaqueAlias => - parent.stripOpaques - case _ => - tp - self.asClass.givenSelfType.stripOpaques.asSeenFrom(site, self) - - } -} + def accessorNamed(name: TermName)(using Context): Symbol = + self.owner.info.decl(name).suchThat(_.is(Accessor)).symbol + + def caseAccessors(using Context): List[Symbol] = + self.info.decls.filter(_.is(CaseAccessor)) + + def getter(using Context): Symbol = + if (self.isGetter) self else accessorNamed(self.asTerm.name.getterName) + + def setter(using Context): Symbol = + if (self.isSetter) self + else accessorNamed(self.asTerm.name.setterName) + + def traitSetter(using Context): Symbol = + if (self.name.is(TraitSetterName)) self + else accessorNamed(Mixin.traitSetterName(self.asTerm)) + + def field(using Context): Symbol = { + val thisName = self.name.asTermName + val fieldName = + if (self.hasAnnotation(defn.ScalaStaticAnnot)) thisName.getterName + else thisName.fieldName + self.owner.info.decl(fieldName).suchThat(!_.is(Method)).symbol + } + + def isField(using Context): Boolean = + self.isTerm && !self.is(Method) + + def isEnumCase(using Context): Boolean = + self.isAllOf(EnumCase, butNot = JavaDefined) + + def annotationsCarrying(meta: ClassSymbol)(using Context): List[Annotation] = + self.annotations.filter(_.symbol.hasAnnotation(meta)) + + def withAnnotationsCarrying(from: Symbol, meta: ClassSymbol)(using Context): self.type = { + self.addAnnotations(from.annotationsCarrying(meta)) + self + } + + def isEnum(using Context): Boolean = self.is(Enum, butNot = JavaDefined) + def isEnumClass(using Context): Boolean = isEnum && !self.is(Case) + + /** Does this symbol refer to anonymous classes synthesized by enum desugaring? */ + def isEnumAnonymClass(using Context): Boolean = + self.isAnonymousClass && (self.owner.name.eq(nme.DOLLAR_NEW) || self.owner.is(CaseVal)) + + /** Is this symbol defined locally (i.e. at some level owned by a term) so that + * it cannot be seen from parent class `cls`? + */ + def isInaccessibleChildOf(cls: Symbol)(using Context): Boolean = + def isAccessible(sym: Symbol, cls: Symbol): Boolean = + if cls.isType && !cls.is(Package) then + isAccessible(sym, cls.owner) + else + sym == cls + || sym.is(Package) + || sym.isType && isAccessible(sym.owner, cls) + !isAccessible(self.owner, cls) + + def hasAnonymousChild(using Context): Boolean = + self.children.exists(_ `eq` self) + + /** Is this symbol directly owner by a term symbol, i.e., is it local to a block? */ + def isLocalToBlock(using Context): Boolean = + self.owner.isTerm + + /** Is symbol directly or indirectly owned by a term symbol? */ + @tailrec final def isLocal(using Context): Boolean = { + val owner = self.maybeOwner + if (!owner.exists) false + else if (isLocalToBlock) true + else if (owner.is(Package)) false + else owner.isLocal + } + + /** The typeRef with wildcard arguments for each type parameter */ + def rawTypeRef(using Context) = + self.typeRef.appliedTo(self.typeParams.map(_ => TypeBounds.emptyPolyKind)) + + /** Is symbol a quote operation? */ + def isQuote(using Context): Boolean = + self == defn.InternalQuoted_exprQuote || self == defn.QuotedTypeModule_apply + + /** Is symbol a term splice operation? */ + def isExprSplice(using Context): Boolean = + self == defn.InternalQuoted_exprSplice || self == defn.InternalQuoted_exprNestedSplice + + /** Is symbol a type splice operation? */ + def isTypeSplice(using Context): Boolean = + self == defn.QuotedType_splice + + def isScalaStatic(using Context): Boolean = + self.hasAnnotation(defn.ScalaStaticAnnot) + + def isDeprecated(using Context): Boolean = + self.hasAnnotation(defn.DeprecatedAnnot) + + /** Is symbol assumed or declared as an infix symbol? */ + def isDeclaredInfix(using Context): Boolean = + self.hasAnnotation(defn.InfixAnnot) + || defn.isInfix(self) + || self.name.isUnapplyName + && self.owner.is(Module) + && self.owner.linkedClass.is(Case) + && self.owner.linkedClass.isDeclaredInfix + + /** The declared self type of this class, as seen from `site`, stripping + * all refinements for opaque types. + */ + def declaredSelfTypeAsSeenFrom(site: Type)(using Context): Type = + extension (tp: Type) def stripOpaques: Type = tp match + case RefinedType(parent, name, _) if self.info.decl(name).symbol.isOpaqueAlias => + parent.stripOpaques + case _ => + tp + self.asClass.givenSelfType.stripOpaques.asSeenFrom(site, self) + + /** If `original` has a target name annotation, add one to this symbol as well + * such that the new target name is `original`'s target name transformed by `nameFn`. + */ + def deriveTargetNameAnnotation(original: Symbol, nameFn: Name => Name)(using Context): Unit = + if original.hasAnnotation(defn.TargetNameAnnot) then + self.addAnnotation( + Annotation(defn.TargetNameAnnot, + Literal(Constant(nameFn(original.erasedName).toString)).withSpan(original.span))) + end extension +end SymUtils \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 28626a4ed1b0..19efce2b4ac4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -199,10 +199,7 @@ object Inliner { name = BodyRetainerName(meth.name), flags = meth.flags &~ (Inline | Macro | Override) | Private, coord = mdef.rhs.span.startPos).asTerm - if meth.hasAnnotation(defn.TargetNameAnnot) then - retainer.addAnnotation( - Annotation(defn.TargetNameAnnot, - Literal(Constant(BodyRetainerName(meth.erasedName.asTermName).toString)).withSpan(meth.span))) + retainer.deriveTargetNameAnnotation(meth, name => BodyRetainerName(name.asTermName)) polyDefDef(retainer, targs => prefss => inlineCall( ref(meth).appliedToTypes(targs).appliedToArgss(prefss) From 54337a89d436698f1caed7fe6068f6201695e95c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 5 Nov 2020 12:27:57 +0100 Subject: [PATCH 12/20] Rename erasedName -> targetName --- compiler/src/dotty/tools/dotc/ast/tpd.scala | 2 +- compiler/src/dotty/tools/dotc/core/Denotations.scala | 8 ++++---- .../src/dotty/tools/dotc/core/SymDenotations.scala | 8 ++++---- compiler/src/dotty/tools/dotc/core/Types.scala | 4 ++-- .../dotty/tools/dotc/core/tasty/TreePickler.scala | 6 +++--- .../src/dotty/tools/dotc/reporting/messages.scala | 2 +- .../src/dotty/tools/dotc/transform/Erasure.scala | 4 ++-- .../dotty/tools/dotc/transform/ResolveSuper.scala | 6 +++--- .../src/dotty/tools/dotc/transform/SymUtils.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Checking.scala | 12 ++++++------ compiler/src/dotty/tools/dotc/typer/Namer.scala | 2 +- compiler/src/dotty/tools/dotc/typer/RefChecks.scala | 6 +++--- compiler/src/dotty/tools/dotc/typer/Typer.scala | 3 +-- 13 files changed, 32 insertions(+), 33 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index d259390da680..4528ac8d7bb6 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -891,7 +891,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { * on select(sym: Symbol). */ def selectWithSig(sym: Symbol)(using Context): Tree = - selectWithSig(sym.name, sym.signature, sym.erasedName) + selectWithSig(sym.name, sym.signature, sym.targetName) /** A unary apply node with given argument: `tree(arg)` */ def appliedTo(arg: Tree)(using Context): Apply = diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index 0ea364880590..d129f74ccbf3 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -347,12 +347,12 @@ object Denotations { } /** The alternative of this denotation that has a type matching `targetType` when seen - * as a member of type `site` and that has an erased name matching `targetName`, or + * as a member of type `site` and that has a target name matching `targetName`, or * `NoDenotation` if none exists. */ def matchingDenotation(site: Type, targetType: Type, targetName: Name)(using Context): SingleDenotation = { def qualifies(sym: Symbol) = - site.memberInfo(sym).matchesLoosely(targetType) && targetNamesMatch(sym.erasedName, targetName) + site.memberInfo(sym).matchesLoosely(targetType) && targetNamesMatch(sym.targetName, targetName) if (isOverloaded) atSignature(targetType.signature, targetName, site, relaxed = true) match { case sd: SingleDenotation => sd.matchingDenotation(site, targetType, targetName) @@ -624,7 +624,7 @@ object Denotations { relaxed case noMatch => false - if sigMatches && targetNamesMatch(symbol.erasedName, targetName) then this + if sigMatches && targetNamesMatch(symbol.targetName, targetName) then this else NoDenotation def matchesImportBound(bound: Type)(using Context): Boolean = @@ -986,7 +986,7 @@ object Denotations { final def last: SingleDenotation = this def matches(other: SingleDenotation)(using Context): Boolean = - targetNamesMatch(symbol.erasedName, other.symbol.erasedName) + targetNamesMatch(symbol.targetName, other.symbol.targetName) && { val d = signature.matchDegree(other.signature) d match diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 7d36f274350f..d432642e3f63 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -513,7 +513,7 @@ object SymDenotations { myTargetName = name /** The name given in a `@targetName` annotation if one is present, `name` otherwise */ - def erasedName(using Context): Name = + def targetName(using Context): Name = if myTargetName == null then val carrier: SymDenotation = if isAllOf(ModuleClass | Synthetic) then companionClass else this @@ -1254,7 +1254,7 @@ object SymDenotations { final def matchingDecl(inClass: Symbol, site: Type)(using Context): Symbol = { var denot = inClass.info.nonPrivateDecl(name) if (denot.isTerm) // types of the same name always match - denot = denot.matchingDenotation(site, site.memberInfo(symbol), symbol.erasedName) + denot = denot.matchingDenotation(site, site.memberInfo(symbol), symbol.targetName) denot.symbol } @@ -1263,7 +1263,7 @@ object SymDenotations { final def matchingMember(site: Type)(using Context): Symbol = { var denot = site.nonPrivateMember(name) if (denot.isTerm) // types of the same name always match - denot = denot.matchingDenotation(site, site.memberInfo(symbol), symbol.erasedName) + denot = denot.matchingDenotation(site, site.memberInfo(symbol), symbol.targetName) denot.symbol } @@ -2334,7 +2334,7 @@ object SymDenotations { override def mapInfo(f: Type => Type)(using Context): SingleDenotation = this override def matches(other: SingleDenotation)(using Context): Boolean = false - override def erasedName(using Context): Name = EmptyTermName + override def targetName(using Context): Name = EmptyTermName override def mapInherited(ownDenots: PreDenotation, prevDenots: PreDenotation, pre: Type)(using Context): SingleDenotation = this override def filterWithPredicate(p: SingleDenotation => Boolean): SingleDenotation = this override def filterDisjoint(denots: PreDenotation)(using Context): SingleDenotation = this diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 3eaebebf27b4..14547d36e423 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -2084,7 +2084,7 @@ object Types { } private def disambiguate(d: Denotation)(using Context): Denotation = - disambiguate(d, currentSignature, currentSymbol.erasedName) + disambiguate(d, currentSignature, currentSymbol.targetName) private def disambiguate(d: Denotation, sig: Signature, target: Name)(using Context): Denotation = if (sig != null) @@ -2396,7 +2396,7 @@ object Types { d = disambiguate(d, if (lastSymbol.signature == Signature.NotAMethod) Signature.NotAMethod else lastSymbol.asSeenFrom(prefix).signature, - lastSymbol.erasedName) + lastSymbol.targetName) NamedType(prefix, name, d) } if (prefix eq this.prefix) this diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index bef41343bb2d..25867ca596ee 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -197,14 +197,14 @@ class TreePickler(pickler: TastyPickler) { if (sym.is(Flags.Private) || isShadowedRef) { writeByte(if (tpe.isType) TYPEREFin else TERMREFin) withLength { - pickleNameAndSig(sym.name, sym.signature, sym.erasedName) + pickleNameAndSig(sym.name, sym.signature, sym.targetName) pickleType(tpe.prefix) pickleType(sym.owner.typeRef) } } else { writeByte(if (tpe.isType) TYPEREF else TERMREF) - pickleNameAndSig(sym.name, tpe.signature, sym.erasedName) + pickleNameAndSig(sym.name, tpe.signature, sym.targetName) pickleType(tpe.prefix) } } @@ -405,7 +405,7 @@ class TreePickler(pickler: TastyPickler) { } case _ => val sig = tree.tpe.signature - var ename = tree.symbol.erasedName + var ename = tree.symbol.targetName val isAmbiguous = sig != Signature.NotAMethod && qual.tpe.nonPrivateMember(name).match diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 26de90ec7ef6..a5d91796c706 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -1086,7 +1086,7 @@ import transform.SymUtils._ extends DeclarationMsg(OverridesNothingButNameExistsID) { def msg = val what = - if !existing.exists(sd => Denotations.targetNamesMatch(member.erasedName, sd.symbol.erasedName)) + if !existing.exists(sd => Denotations.targetNamesMatch(member.targetName, sd.symbol.targetName)) then "target name" else "signature" em"""${member} has a different $what than the overridden declaration""" diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index b96b8739380f..c864301f9743 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -77,7 +77,7 @@ class Erasure extends Phase with DenotTransformer { val oldOwner = ref.owner val newOwner = if (oldOwner eq defn.AnyClass) defn.ObjectClass else oldOwner val oldName = ref.name - val newName = ref.erasedName + val newName = ref.targetName val oldInfo = ref.info val newInfo = transformInfo(oldSymbol, oldInfo) val oldFlags = ref.flags @@ -938,7 +938,7 @@ object Erasure { val origName = retainer.name.asTermName.exclude(BodyRetainerName) val targetName = if retainer.hasAnnotation(defn.TargetNameAnnot) then - retainer.erasedName.unmangle(BodyRetainerName).exclude(BodyRetainerName) + retainer.targetName.unmangle(BodyRetainerName).exclude(BodyRetainerName) else origName val inlineMeth = atPhase(typerPhase) { retainer.owner.info.decl(origName) diff --git a/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala b/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala index 0fd3b1e2be9e..9e3d3c2c3b85 100644 --- a/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala +++ b/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala @@ -91,8 +91,8 @@ object ResolveSuper { val (memberName, mix) = decomposeSuperName(acc.name.unexpandedName) val targetName = - if acc.name == acc.erasedName then memberName - else decomposeSuperName(acc.erasedName)._1 + if acc.name == acc.targetName then memberName + else decomposeSuperName(acc.targetName)._1 report.debuglog(i"starting rebindsuper from $base of ${acc.showLocated}: ${acc.info} in $bcs, name = $memberName") @@ -112,7 +112,7 @@ object ResolveSuper { bcs = bcs.tail } - assert(sym.exists, i"cannot rebind $acc, ${acc.erasedName} $memberName") + assert(sym.exists, i"cannot rebind $acc, ${acc.targetName} $memberName") sym } } diff --git a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala index 725e422f747d..cded40911039 100644 --- a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala +++ b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala @@ -266,6 +266,6 @@ object SymUtils: if original.hasAnnotation(defn.TargetNameAnnot) then self.addAnnotation( Annotation(defn.TargetNameAnnot, - Literal(Constant(nameFn(original.erasedName).toString)).withSpan(original.span))) + Literal(Constant(nameFn(original.targetName).toString)).withSpan(original.span))) end extension end SymUtils \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index f6fdee2c81d2..900002abfe5e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -1208,12 +1208,12 @@ trait Checking { var seen = Set[Name]() for stat <- stats do val sym = stat.symbol - val ename = sym.erasedName - if ename != sym.name then - val preExisting = ctx.effectiveScope.lookup(ename) - if preExisting.exists || seen.contains(ename) then - report.error(em"@targetName annotation ${'"'}$ename${'"'} clashes with other definition in same scope", stat.srcPos) - if stat.isDef then seen += ename + val tname = sym.targetName + if tname != sym.name then + val preExisting = ctx.effectiveScope.lookup(tname) + if preExisting.exists || seen.contains(tname) then + report.error(em"@targetName annotation ${'"'}$tname${'"'} clashes with other definition in same scope", stat.srcPos) + if stat.isDef then seen += tname } trait ReChecking extends Checking { diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index a828d76c311f..01671224b73d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1298,7 +1298,7 @@ class Namer { typer: Typer => else NoType } val iRawInfo = - cls.info.nonPrivateDecl(sym.name).matchingDenotation(site, schema, sym.erasedName).info + cls.info.nonPrivateDecl(sym.name).matchingDenotation(site, schema, sym.targetName).info val iResType = instantiatedResType(iRawInfo, typeParams, paramss).asSeenFrom(site, cls) if (iResType.exists) typr.println(i"using inherited type for ${mdef.name}; raw: $iRawInfo, inherited: $iResType") diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 779aedb113a8..d3bfc0b7acf0 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -431,9 +431,9 @@ object RefChecks { else if (!compatibleTypes(memberTp(self), otherTp(self)) && !compatibleTypes(memberTp(upwardsSelf), otherTp(upwardsSelf))) overrideError("has incompatible type" + err.whyNoMatchStr(memberTp(self), otherTp(self))) - else if (member.erasedName != other.erasedName) - if (other.erasedName != other.name) - overrideError(i"needs to be declared with @targetName(${"\""}${other.erasedName}${"\""}) so that external names match") + else if (member.targetName != other.targetName) + if (other.targetName != other.name) + overrideError(i"needs to be declared with @targetName(${"\""}${other.targetName}${"\""}) so that external names match") else overrideError("cannot have a @targetName annotation since external names would be different") else diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 2635e518ec51..b0c4c2003c43 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1983,8 +1983,7 @@ class Typer extends Namer PrepareInlineable.registerInlineInfo(sym, rhsToInline) if (sym.isConstructor && !sym.isPrimaryConstructor) { - val ename = sym.erasedName - if (ename != sym.name) + if (sym.targetName != sym.name) report.error(em"@targetName annotation may not be used on a constructor", ddef.srcPos) for (param <- tparams1 ::: vparamss1.flatten) From b27633bd0fbde8f2f57837a72aaeec2b43e1e59e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 5 Nov 2020 13:13:48 +0100 Subject: [PATCH 13/20] Streamline targetName matching tests --- compiler/src/dotty/tools/dotc/core/Denotations.scala | 10 +++------- compiler/src/dotty/tools/dotc/core/NameOps.scala | 4 ++++ .../src/dotty/tools/dotc/core/SymDenotations.scala | 3 +++ .../src/dotty/tools/dotc/core/tasty/NameBuffer.scala | 6 +++--- compiler/src/dotty/tools/dotc/reporting/messages.scala | 2 +- 5 files changed, 14 insertions(+), 11 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index d129f74ccbf3..951e00d5f48f 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -352,7 +352,7 @@ object Denotations { */ def matchingDenotation(site: Type, targetType: Type, targetName: Name)(using Context): SingleDenotation = { def qualifies(sym: Symbol) = - site.memberInfo(sym).matchesLoosely(targetType) && targetNamesMatch(sym.targetName, targetName) + site.memberInfo(sym).matchesLoosely(targetType) && sym.hasTargetName(targetName) if (isOverloaded) atSignature(targetType.signature, targetName, site, relaxed = true) match { case sd: SingleDenotation => sd.matchingDenotation(site, targetType, targetName) @@ -624,8 +624,7 @@ object Denotations { relaxed case noMatch => false - if sigMatches && targetNamesMatch(symbol.targetName, targetName) then this - else NoDenotation + if sigMatches && symbol.hasTargetName(targetName) then this else NoDenotation def matchesImportBound(bound: Type)(using Context): Boolean = if bound.isRef(defn.NothingClass) then false @@ -986,7 +985,7 @@ object Denotations { final def last: SingleDenotation = this def matches(other: SingleDenotation)(using Context): Boolean = - targetNamesMatch(symbol.targetName, other.symbol.targetName) + symbol.hasTargetName(other.symbol.targetName) && { val d = signature.matchDegree(other.signature) d match @@ -1285,7 +1284,4 @@ object Denotations { util.Stats.record("stale symbol") override def getMessage(): String = msg } - - def targetNamesMatch(name1: Name, name2: Name): Boolean = - name1 == name2 || name1.isEmpty || name2.isEmpty } diff --git a/compiler/src/dotty/tools/dotc/core/NameOps.scala b/compiler/src/dotty/tools/dotc/core/NameOps.scala index a84123361541..26ccc5c87932 100644 --- a/compiler/src/dotty/tools/dotc/core/NameOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NameOps.scala @@ -170,6 +170,10 @@ object NameOps { } } + /** Do two target names match? An empty target name matchws any other name. */ + def matchesTargetName(other: Name) = + name == other || name.isEmpty || other.isEmpty + private def functionSuffixStart: Int = val first = name.firstPart var idx = first.length - 1 diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index d432642e3f63..9643617c3631 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -512,6 +512,9 @@ object SymDenotations { def setTargetName(name: Name): Unit = myTargetName = name + def hasTargetName(name: Name)(using Context): Boolean = + targetName.matchesTargetName(name) + /** The name given in a `@targetName` annotation if one is present, `name` otherwise */ def targetName(using Context): Name = if myTargetName == null then diff --git a/compiler/src/dotty/tools/dotc/core/tasty/NameBuffer.scala b/compiler/src/dotty/tools/dotc/core/tasty/NameBuffer.scala index a98a48ac4bff..e09b2b74fc9e 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/NameBuffer.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/NameBuffer.scala @@ -9,9 +9,9 @@ import TastyBuffer._ import collection.mutable import Names.{Name, chrs, SimpleName, DerivedName, TypeName} import NameKinds._ +import NameOps._ import Decorators._ import scala.io.Codec -import Denotations.targetNamesMatch import NameTags.{SIGNED, TARGETSIGNED} class NameBuffer extends TastyBuffer(10000) { @@ -28,7 +28,7 @@ class NameBuffer extends TastyBuffer(10000) { name1 match { case SignedName(original, Signature(params, result), target) => nameIndex(original) - if !targetNamesMatch(original, target) then nameIndex(target) + if !original.matchesTargetName(target) then nameIndex(target) nameIndex(result) params.foreach { case param: TypeName => @@ -95,7 +95,7 @@ class NameBuffer extends TastyBuffer(10000) { writeByte(tag) withLength { writeNameRef(original); writeNat(num) } case SignedName(original, Signature(paramsSig, result), target) => - val needsTarget = !targetNamesMatch(original, target) + val needsTarget = !original.matchesTargetName(target) writeByte(if needsTarget then TARGETSIGNED else SIGNED) withLength( { writeNameRef(original) diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index a5d91796c706..3671a37b5fe5 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -1086,7 +1086,7 @@ import transform.SymUtils._ extends DeclarationMsg(OverridesNothingButNameExistsID) { def msg = val what = - if !existing.exists(sd => Denotations.targetNamesMatch(member.targetName, sd.symbol.targetName)) + if !existing.exists(_.symbol.hasTargetName(member.targetName)) then "target name" else "signature" em"""${member} has a different $what than the overridden declaration""" From ce53d89d774faeefd67dd3a8155fe767861c3e65 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 5 Nov 2020 13:24:38 +0100 Subject: [PATCH 14/20] Add hint to error message In case of a double definition error that's due to an erasure clash, add a hint that one can resolve it with a @targetName annotation --- compiler/src/dotty/tools/dotc/reporting/messages.scala | 5 ++++- tests/neg/doubleDefinition.check | 6 ++++++ tests/neg/exports.check | 9 +++++++++ tests/neg/i7359-f.check | 3 +++ tests/neg/mixin-forwarder-clash1.check | 3 +++ tests/neg/mixin-forwarder-clash2.check | 3 +++ tests/neg/override-erasure-clash.check | 3 +++ 7 files changed, 31 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 3671a37b5fe5..af41722d290d 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -2079,7 +2079,10 @@ import transform.SymUtils._ case MethodNotAMethodMatch => "neither has parameters." case FullMatch => - i"have the same$nameAnd type after erasure." + i"""have the same$nameAnd type after erasure. + | + |Consider adding a @targetName annotation to one of the conflicting definitions + |for disambiguation.""" } } else "" diff --git a/tests/neg/doubleDefinition.check b/tests/neg/doubleDefinition.check index 7731951f123e..7edfcdefcc64 100644 --- a/tests/neg/doubleDefinition.check +++ b/tests/neg/doubleDefinition.check @@ -5,6 +5,9 @@ | def foo(x: List[A]): A => A in class Test2 at line 13 and | def foo(x: List[B]): B => B in class Test2 at line 14 | have the same type after erasure. + | + | Consider adding a @targetName annotation to one of the conflicting definitions + | for disambiguation. -- [E120] Naming Error: tests/neg/doubleDefinition.scala:21:4 ---------------------------------------------------------- 21 | def foo(x: List[A]): Function2[B, B, B] = ??? // error | ^ @@ -43,6 +46,9 @@ | def foo(x: List[A]): A => A in trait Test6 at line 54 and | def foo(x: List[B]): B => B in trait Test6 at line 55 | have the same type after erasure. + | + | Consider adding a @targetName annotation to one of the conflicting definitions + | for disambiguation. -- [E120] Naming Error: tests/neg/doubleDefinition.scala:62:4 ---------------------------------------------------------- 62 | def foo(x: List[A]): Function2[B, B, B] = ??? // error | ^ diff --git a/tests/neg/exports.check b/tests/neg/exports.check index 13299a35d258..4d871161087f 100644 --- a/tests/neg/exports.check +++ b/tests/neg/exports.check @@ -19,6 +19,9 @@ | def status: => List[String] in class Copier at line 28 and | final def status: => List[String] in class Copier at line 23 | have the same type after erasure. + | + | Consider adding a @targetName annotation to one of the conflicting definitions + | for disambiguation. -- [E120] Naming Error: tests/neg/exports.scala:24:20 ------------------------------------------------------------------ 24 | export scanUnit._ // error: double definition | ^ @@ -26,6 +29,9 @@ | final def status: => List[String] in class Copier at line 23 and | final def status: => List[String] in class Copier at line 24 | have the same type after erasure. + | + | Consider adding a @targetName annotation to one of the conflicting definitions + | for disambiguation. -- [E120] Naming Error: tests/neg/exports.scala:26:21 ------------------------------------------------------------------ 26 | export printUnit.status // error: double definition | ^ @@ -33,6 +39,9 @@ | final def status: => List[String] in class Copier at line 24 and | final def status: => List[String] in class Copier at line 26 | have the same type after erasure. + | + | Consider adding a @targetName annotation to one of the conflicting definitions + | for disambiguation. -- Error: tests/neg/exports.scala:35:24 -------------------------------------------------------------------------------- 35 | export this.{concat => ++} // error: no eligible member | ^^^^^^^^^^^^ diff --git a/tests/neg/i7359-f.check b/tests/neg/i7359-f.check index 5cc88336bbf3..d85a5fd0ad2e 100644 --- a/tests/neg/i7359-f.check +++ b/tests/neg/i7359-f.check @@ -5,3 +5,6 @@ | def equals: [T >: Boolean <: Boolean](obj: Any): T in trait SAMTrait at line 3 and | def equals(x$0: Any): Boolean in class Any | have the same type after erasure. + | + | Consider adding a @targetName annotation to one of the conflicting definitions + | for disambiguation. diff --git a/tests/neg/mixin-forwarder-clash1.check b/tests/neg/mixin-forwarder-clash1.check index f5a10c40202f..e581e79d0975 100644 --- a/tests/neg/mixin-forwarder-clash1.check +++ b/tests/neg/mixin-forwarder-clash1.check @@ -5,3 +5,6 @@ | def concat(suffix: Int): X in trait One at line 4 and | def concat: [Dummy](suffix: Int): Y in trait Two at line 8 | have the same type after erasure. + | + | Consider adding a @targetName annotation to one of the conflicting definitions + | for disambiguation. diff --git a/tests/neg/mixin-forwarder-clash2.check b/tests/neg/mixin-forwarder-clash2.check index ee0318142d43..02d839551046 100644 --- a/tests/neg/mixin-forwarder-clash2.check +++ b/tests/neg/mixin-forwarder-clash2.check @@ -6,3 +6,6 @@ | def concat(suffix: Int): X in trait One at line 4 and | def concat: [Dummy](suffix: Int): Y in trait Two at line 8 | have the same type after erasure. + | + | Consider adding a @targetName annotation to one of the conflicting definitions + | for disambiguation. diff --git a/tests/neg/override-erasure-clash.check b/tests/neg/override-erasure-clash.check index e936a8de2397..2936d744f950 100644 --- a/tests/neg/override-erasure-clash.check +++ b/tests/neg/override-erasure-clash.check @@ -5,3 +5,6 @@ | def f(): Int in class A at line 3 and | def g(): Int in class B at line 5 | have the same name and type after erasure. + | + | Consider adding a @targetName annotation to one of the conflicting definitions + | for disambiguation. From 9deb8085d3f90868537b63e313378fdebfb13fa1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 5 Nov 2020 13:41:27 +0100 Subject: [PATCH 15/20] Refine @targetName hinting Don't issue the hint of one of the conflicting definitions already carries a @targetName. --- .../src/dotty/tools/dotc/reporting/messages.scala | 14 ++++++++++---- tests/neg/override-erasure-clash.check | 3 --- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index af41722d290d..512197e98b3f 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -2079,10 +2079,16 @@ import transform.SymUtils._ case MethodNotAMethodMatch => "neither has parameters." case FullMatch => - i"""have the same$nameAnd type after erasure. - | - |Consider adding a @targetName annotation to one of the conflicting definitions - |for disambiguation.""" + val hint = + if !decl.hasAnnotation(defn.TargetNameAnnot) + && !previousDecl.hasAnnotation(defn.TargetNameAnnot) + then + i""" + | + |Consider adding a @targetName annotation to one of the conflicting definitions + |for disambiguation.""" + else "" + i"have the same$nameAnd type after erasure.$hint" } } else "" diff --git a/tests/neg/override-erasure-clash.check b/tests/neg/override-erasure-clash.check index 2936d744f950..e936a8de2397 100644 --- a/tests/neg/override-erasure-clash.check +++ b/tests/neg/override-erasure-clash.check @@ -5,6 +5,3 @@ | def f(): Int in class A at line 3 and | def g(): Int in class B at line 5 | have the same name and type after erasure. - | - | Consider adding a @targetName annotation to one of the conflicting definitions - | for disambiguation. From aedf313c5d26ac4caf95d34b2b0526a2fcb69243 Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 8 Nov 2020 12:04:20 +0100 Subject: [PATCH 16/20] Apply suggestions from code review Co-authored-by: Fengyun Liu --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 2 +- docs/docs/reference/other-new-features/targetName.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index b0c4c2003c43..36bd4184c9ea 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1665,7 +1665,7 @@ class Typer extends Namer checkRefinementNonCyclic(refinement, refineCls, seen) val rsym = refinement.symbol rsym.setTargetName(EmptyTermName) - // refinements can refine members with arbitrary target names, so we make their tragetnames + // refinements can refine members with arbitrary target names, so we make their target names // polymorphic here in order to avoid to trigger the `member.isOverloaded` test below. val polymorphicRefinementAllowed = tpt1.tpe.typeSymbol == defn.PolyFunctionClass && rsym.name == nme.apply diff --git a/docs/docs/reference/other-new-features/targetName.md b/docs/docs/reference/other-new-features/targetName.md index 2c6a2b95cacf..57b5cdf09278 100644 --- a/docs/docs/reference/other-new-features/targetName.md +++ b/docs/docs/reference/other-new-features/targetName.md @@ -3,7 +3,7 @@ layout: doc-page title: The @targetName annotation --- -A `@targetName` annotation on a definition defines an alternate name for the implementation of that definition: Example: +A `@targetName` annotation on a definition defines an alternate name for the implementation of that definition. Example: ```scala import scala.annotation.targetName From 039f42f7d1df3b483dd99c584396248e0af36a8a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 8 Nov 2020 16:10:40 +0100 Subject: [PATCH 17/20] Avoid `@` in sidebar titles --- docs/sidebar.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sidebar.yml b/docs/sidebar.yml index fd7b76b41fca..1b901d208ab7 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -103,9 +103,9 @@ sidebar: url: docs/reference/other-new-features/kind-polymorphism.html - title: Tupled Function url: docs/reference/other-new-features/tupled-function.html - - title: @threadUnsafe Annotation + - title: threadUnsafe Annotation url: docs/reference/other-new-features/threadUnsafe-annotation.html - - title: @targetName Annotation + - title: targetName Annotation url: docs/reference/other-new-features/targetName.html - title: New Control Syntax url: docs/reference/other-new-features/control-syntax.html From 6310ec2013b4bfd9601851b14f314bef884e15fb Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 10 Nov 2020 11:58:54 +0100 Subject: [PATCH 18/20] Enforce matching targetNames when overriding --- .../dotty/tools/dotc/config/Printers.scala | 1 + .../dotty/tools/dotc/core/Denotations.scala | 59 ++++++------ .../dotty/tools/dotc/typer/RefChecks.scala | 90 +++++++++++++------ tests/neg/targetName-override.check | 27 +++--- tests/neg/targetName-override.scala | 8 +- tests/pos/targetName-override.scala | 11 +++ 6 files changed, 123 insertions(+), 73 deletions(-) create mode 100644 tests/pos/targetName-override.scala diff --git a/compiler/src/dotty/tools/dotc/config/Printers.scala b/compiler/src/dotty/tools/dotc/config/Printers.scala index 1b5d71d9e702..8f9e480e0330 100644 --- a/compiler/src/dotty/tools/dotc/config/Printers.scala +++ b/compiler/src/dotty/tools/dotc/config/Printers.scala @@ -37,6 +37,7 @@ object Printers { val pickling = noPrinter val quotePickling = noPrinter val plugins = noPrinter + val refcheck = noPrinter val simplify = noPrinter val staging = noPrinter val subtyping = noPrinter diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index 951e00d5f48f..e219aefa5f47 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -986,35 +986,36 @@ object Denotations { def matches(other: SingleDenotation)(using Context): Boolean = symbol.hasTargetName(other.symbol.targetName) - && { - val d = signature.matchDegree(other.signature) - d match - case FullMatch => - true - case MethodNotAMethodMatch => - !ctx.erasedTypes && { - val isJava = symbol.is(JavaDefined) - val otherIsJava = other.symbol.is(JavaDefined) - // A Scala zero-parameter method and a Scala non-method always match. - if !isJava && !otherIsJava then - true - // Java allows defining both a field and a zero-parameter method with the same name, - // so they must not match. - else if isJava && otherIsJava then - false - // A Java field never matches a Scala method. - else if isJava then - symbol.is(Method) - else // otherIsJava - other.symbol.is(Method) - } - case ParamMatch => - // The signatures do not tell us enough to be sure about matching - !ctx.erasedTypes && info.matches(other.info) - case noMatch => - false - } - end matches + && matchesLoosely(other) + + /** matches without a target name check */ + def matchesLoosely(other: SingleDenotation)(using Context): Boolean = + val d = signature.matchDegree(other.signature) + d match + case FullMatch => + true + case MethodNotAMethodMatch => + !ctx.erasedTypes && { + val isJava = symbol.is(JavaDefined) + val otherIsJava = other.symbol.is(JavaDefined) + // A Scala zero-parameter method and a Scala non-method always match. + if !isJava && !otherIsJava then + true + // Java allows defining both a field and a zero-parameter method with the same name, + // so they must not match. + else if isJava && otherIsJava then + false + // A Java field never matches a Scala method. + else if isJava then + symbol.is(Method) + else // otherIsJava + other.symbol.is(Method) + } + case ParamMatch => + // The signatures do not tell us enough to be sure about matching + !ctx.erasedTypes && info.matches(other.info) + case noMatch => + false def mapInherited(ownDenots: PreDenotation, prevDenots: PreDenotation, pre: Type)(using Context): SingleDenotation = if hasUniqueSym && prevDenots.containsSym(symbol) then NoDenotation diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index d3bfc0b7acf0..5590554dcf50 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -18,6 +18,7 @@ import config.{ScalaVersion, NoScalaVersion} import Decorators._ import typer.ErrorReporting._ import config.Feature.{warnOnMigration, migrateTo3} +import config.Printers.refcheck import reporting._ import scala.util.matching.Regex._ import Constants.Constant @@ -236,6 +237,26 @@ object RefChecks { i"${if (showLocation) sym1.showLocated else sym1}$infoStr" } + def compatibleTypes(member: Symbol, memberTp: Type, other: Symbol, otherTp: Type, fallBack: => Boolean = false): Boolean = + try + if (member.isType) // intersection of bounds to refined types must be nonempty + memberTp.bounds.hi.hasSameKindAs(otherTp.bounds.hi) && + ((memberTp frozen_<:< otherTp) || + !member.owner.derivesFrom(other.owner) && { + // if member and other come from independent classes or traits, their + // bounds must have non-empty-intersection + val jointBounds = (memberTp.bounds & otherTp.bounds).bounds + jointBounds.lo frozen_<:< jointBounds.hi + }) + else + member.name.is(DefaultGetterName) || // default getters are not checked for compatibility + memberTp.overrides(otherTp, + member.matchNullaryLoosely || other.matchNullaryLoosely || fallBack) + catch case ex: MissingType => + // can happen when called with upwardsSelf as qualifier of memberTp and otherTp, + // because in that case we might access types that are not members of the qualifier. + false + /* Check that all conditions for overriding `other` by `member` * of class `clazz` are met. */ @@ -245,7 +266,7 @@ object RefChecks { else self.memberInfo(member) def otherTp(self: Type) = self.memberInfo(other) - report.debuglog("Checking validity of %s overriding %s".format(member.showLocated, other.showLocated)) + refcheck.println(i"check override ${infoString(member)} overriding ${infoString(other)}") def noErrorType = !memberTp(self).isErroneous && !otherTp(self).isErroneous @@ -265,6 +286,12 @@ object RefChecks { infoStringWithLocation(other), infoString(member), msg, addendum) } + def compatTypes(memberTp: Type, otherTp: Type): Boolean = + compatibleTypes(member, memberTp, other, otherTp, + fallBack = warnOnMigration( + overrideErrorMsg("no longer has compatible type"), + (if (member.owner == clazz) member else clazz).srcPos)) + def emitOverrideError(fullmsg: String) = if (!(hasErrors && member.is(Synthetic) && member.is(Module))) { // suppress errors relating toi synthetic companion objects if other override @@ -291,29 +318,14 @@ object RefChecks { (if (otherAccess == "") "public" else "at least " + otherAccess)) } - def compatibleTypes(memberTp: Type, otherTp: Type): Boolean = - try - if (member.isType) // intersection of bounds to refined types must be nonempty - memberTp.bounds.hi.hasSameKindAs(otherTp.bounds.hi) && - ((memberTp frozen_<:< otherTp) || - !member.owner.derivesFrom(other.owner) && { - // if member and other come from independent classes or traits, their - // bounds must have non-empty-intersection - val jointBounds = (memberTp.bounds & otherTp.bounds).bounds - jointBounds.lo frozen_<:< jointBounds.hi - }) - else - member.name.is(DefaultGetterName) || // default getters are not checked for compatibility - memberTp.overrides(otherTp, - member.matchNullaryLoosely || other.matchNullaryLoosely || - warnOnMigration(overrideErrorMsg("no longer has compatible type"), - (if (member.owner == clazz) member else clazz).srcPos)) - catch { - case ex: MissingType => - // can happen when called with upwardsSelf as qualifier of memberTp and otherTp, - // because in that case we might access types that are not members of the qualifier. - false - } + def overrideTargetNameError() = + val otherTargetName = i"@targetName(${other.targetName})" + if member.hasTargetName(member.name) then + overrideError(i"misses a target name annotation $otherTargetName") + else if other.hasTargetName(other.name) then + overrideError(i"should not have a @targetName annotation since the overridden member hasn't one either") + else + overrideError(i"has a different target name annotation; it should be $otherTargetName") //Console.println(infoString(member) + " overrides " + infoString(other) + " in " + clazz);//DEBUG @@ -359,7 +371,9 @@ object RefChecks { && (ob.isContainedIn(mb) || other.isAllOf(JavaProtected)) // m relaxes o's access boundary, // or o is Java defined and protected (see #3946) - if (!isOverrideAccessOK) + if !member.hasTargetName(other.targetName) then + overrideTargetNameError() + else if (!isOverrideAccessOK) overrideAccessError() else if (other.isClass) // direct overrides were already checked on completion (see Checking.chckWellFormed) @@ -428,8 +442,8 @@ object RefChecks { overrideError("is not inline, cannot implement an inline method") else if (other.isScala2Macro && !member.isScala2Macro) // (1.11) overrideError("cannot be used here - only Scala-2 macros can override Scala-2 macros") - else if (!compatibleTypes(memberTp(self), otherTp(self)) && - !compatibleTypes(memberTp(upwardsSelf), otherTp(upwardsSelf))) + else if (!compatTypes(memberTp(self), otherTp(self)) && + !compatTypes(memberTp(upwardsSelf), otherTp(upwardsSelf))) overrideError("has incompatible type" + err.whyNoMatchStr(memberTp(self), otherTp(self))) else if (member.targetName != other.targetName) if (other.targetName != other.name) @@ -451,7 +465,27 @@ object RefChecks { }*/ } - val opc = new OverridingPairs.Cursor(clazz) + val opc = new OverridingPairs.Cursor(clazz): + + /** We declare a match if either we have a full match including matching names + * or we have a loose match with different target name but the types are the same. + * This leaves two possible sorts of discrepancies to be reported as errors + * in `checkOveride`: + * + * - matching names, target names, and signatures but different types + * - matching names and types, but different target names + */ + override def matches(sym1: Symbol, sym2: Symbol): Boolean = + sym1.isType + || { + val sd1 = sym1.asSeenFrom(clazz.thisType) + val sd2 = sym2.asSeenFrom(clazz.thisType) + sd1.matchesLoosely(sd2) + && (sym1.hasTargetName(sym2.targetName) + || compatibleTypes(sym1, sd1.info, sym2, sd2.info)) + } + end opc + while opc.hasNext do checkOverride(opc.overriding, opc.overridden) opc.next() diff --git a/tests/neg/targetName-override.check b/tests/neg/targetName-override.check index 469b94e2b7e1..912f7debc247 100644 --- a/tests/neg/targetName-override.check +++ b/tests/neg/targetName-override.check @@ -1,16 +1,19 @@ +-- Error: tests/neg/targetName-override.scala:16:35 -------------------------------------------------------------------- +16 | @targetName("foo1") override def foo() = 1 // error: should not have a target name + | ^ + |error overriding method foo in class Alpha of type (): Int; + | method foo of type (): Int should not have a @targetName annotation since the overridden member hasn't one either +-- Error: tests/neg/targetName-override.scala:18:25 -------------------------------------------------------------------- +18 | @targetName("baz") def foo(x: String): String = x ++ x // error: has a different target name annotation + | ^ + | error overriding method foo in class Alpha of type (x: String): String; + | method foo of type (x: String): String has a different target name annotation; it should be @targetName(foo1) +-- Error: tests/neg/targetName-override.scala:20:15 -------------------------------------------------------------------- +20 | override def ++ (xs: Alpha[String]): Alpha[String] = this // error: misses a targetname annotation + | ^ + | error overriding method ++ in class Alpha of type (xs: Alpha[String]): Alpha[String]; + | method ++ of type (xs: Alpha[String]): Alpha[String] misses a target name annotation @targetName(append) -- Error: tests/neg/targetName-override.scala:14:6 --------------------------------------------------------------------- 14 |class Beta extends Alpha[String] { // error: needs to be abstract | ^ |class Beta needs to be abstract, since there is a deferred declaration of method foo in class Alpha of type (x: String): String which is not implemented in a subclass --- [E038] Declaration Error: tests/neg/targetName-override.scala:16:35 ------------------------------------------------- -16 | @targetName("foo1") override def foo() = 1 // error: different signature than overridden - | ^ - | method foo has a different target name than the overridden declaration - -longer explanation available when compiling with `-explain` --- [E038] Declaration Error: tests/neg/targetName-override.scala:20:15 ------------------------------------------------- -20 | override def ++ (xs: Alpha[String]): Alpha[String] = this // error: different signature than overidden - | ^ - | method ++ has a different target name than the overridden declaration - -longer explanation available when compiling with `-explain` diff --git a/tests/neg/targetName-override.scala b/tests/neg/targetName-override.scala index c9b6e36b40f5..3766ece64ecf 100644 --- a/tests/neg/targetName-override.scala +++ b/tests/neg/targetName-override.scala @@ -5,7 +5,7 @@ abstract class Alpha[T] { def foo() = 1 - @targetName("bar") def foo(x: T): T + @targetName("foo1") def foo(x: T): T @targetName("append") def ++ (xs: Alpha[T]): Alpha[T] = this @@ -13,10 +13,10 @@ abstract class Alpha[T] { class Beta extends Alpha[String] { // error: needs to be abstract - @targetName("foo1") override def foo() = 1 // error: different signature than overridden + @targetName("foo1") override def foo() = 1 // error: should not have a target name - @targetName("baz") def foo(x: String): String = x ++ x // OK, + @targetName("baz") def foo(x: String): String = x ++ x // error: has a different target name annotation - override def ++ (xs: Alpha[String]): Alpha[String] = this // error: different signature than overidden + override def ++ (xs: Alpha[String]): Alpha[String] = this // error: misses a targetname annotation } diff --git a/tests/pos/targetName-override.scala b/tests/pos/targetName-override.scala new file mode 100644 index 000000000000..a15ce5594a76 --- /dev/null +++ b/tests/pos/targetName-override.scala @@ -0,0 +1,11 @@ +import scala.annotation.targetName + +class A: + @targetName("ff") def f(x: => Int): Int = x + def f(x: => String): Int = x.length + + +class B extends A: + @targetName("ff") override def f(x: => Int): Int = x // OK + override def f(x: => String): Int = x.length // OK + From d4fca547b090862068cc86604cdc88b2c62da988 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 10 Nov 2020 12:27:00 +0100 Subject: [PATCH 19/20] Add clause to docs --- .../other-new-features/targetName.md | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/docs/docs/reference/other-new-features/targetName.md b/docs/docs/reference/other-new-features/targetName.md index 57b5cdf09278..c4b62c96116c 100644 --- a/docs/docs/reference/other-new-features/targetName.md +++ b/docs/docs/reference/other-new-features/targetName.md @@ -56,8 +56,31 @@ def f(x: => Int): Int = x + 1 // OK ``` This will produce methods `f_string` and `f` in the generated code. +However, `@targetName` annotations are not allowed to break overriding relationships +between two definitions that have otherwise the same names and types. So the following would be in error: +```scala +import annotation.targetName +class A: + def f(): Int = 1 +class B extends A: + targetName("g") def f(): Int = 2 +``` +The compiler reports here: +``` +-- Error: test.scala:6:23 ------------------------------------------------------ +6 | @targetName("g") def f(): Int = 2 + | ^ + |error overriding method f in class A of type (): Int; + | method f of type (): Int should not have a @targetName annotation since the overridden member hasn't one either +``` +The relevant overriding rules can be summarized as follows: + + - Two members can override each other if their names and signatures are the same, + and they either have the same erased names or the same types. + - If two members override, then both their erased names and their types must be the same. + As usual, any overriding relationship in the generated code must also -be present in the original code. So the following example would be in error: +be present in the original code. So the following example would also be in error: ```scala import annotation.targetName class A: From 773023b58d054aacd0f561a18130cefb2084c4bd Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 10 Nov 2020 13:41:08 +0100 Subject: [PATCH 20/20] Require string literals as target name arguments --- compiler/src/dotty/tools/dotc/typer/Checking.scala | 14 ++++++++++++++ compiler/src/dotty/tools/dotc/typer/Typer.scala | 2 +- tests/neg/targetName-illegal.scala | 4 ++++ 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 tests/neg/targetName-illegal.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 900002abfe5e..c02544aeb11f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -1073,6 +1073,19 @@ trait Checking { if !Inliner.inInlineMethod && !ctx.isInlineContext then report.error(em"$what can only be used in an inline method", pos) + /** Check arguments of compiler-defined annotations */ + def checkAnnotArgs(tree: Tree)(using Context): tree.type = + val cls = Annotations.annotClass(tree) + def needsStringLit(arg: Tree) = + report.error(em"@${cls.name} needs a string literal as argument", arg.srcPos) + tree match + case Apply(tycon, arg :: Nil) if cls == defn.TargetNameAnnot => + arg match + case Literal(_) => // ok + case _ => needsStringLit(arg) + case _ => + tree + /** 1. Check that all case classes that extend `scala.reflect.Enum` are `enum` cases * 2. Check that parameterised `enum` cases do not extend java.lang.Enum. * 3. Check that only a static `enum` base class can extend java.lang.Enum. @@ -1235,6 +1248,7 @@ trait NoChecking extends ReChecking { override def checkImplicitConversionDefOK(sym: Symbol)(using Context): Unit = () override def checkImplicitConversionUseOK(tree: Tree)(using Context): Unit = () override def checkFeasibleParent(tp: Type, pos: SrcPos, where: => String = "")(using Context): Type = tp + override def checkAnnotArgs(tree: Tree)(using Context): tree.type = tree override def checkInlineConformant(tpt: Tree, tree: Tree, sym: Symbol)(using Context): Unit = () override def checkNoTargetNameConflict(stats: List[Tree])(using Context): Unit = () override def checkParentCall(call: Tree, caller: ClassSymbol)(using Context): Unit = () diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 36bd4184c9ea..3f8759028542 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1920,7 +1920,7 @@ class Typer extends Namer } def typedAnnotation(annot: untpd.Tree)(using Context): Tree = - typed(annot, defn.AnnotationClass.typeRef) + checkAnnotArgs(typed(annot, defn.AnnotationClass.typeRef)) def typedValDef(vdef: untpd.ValDef, sym: Symbol)(using Context): Tree = { val ValDef(name, tpt, _) = vdef diff --git a/tests/neg/targetName-illegal.scala b/tests/neg/targetName-illegal.scala new file mode 100644 index 000000000000..194a956e9e1d --- /dev/null +++ b/tests/neg/targetName-illegal.scala @@ -0,0 +1,4 @@ +import annotation.targetName + +val s = "x" +@targetName(s) def y = 1 // error