From c4604fa9e5180bf76d675e5aa03d432b7eb5382d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 3 Jun 2019 20:15:55 +0200 Subject: [PATCH 1/6] Implement @alpha annotation --- .../dotty/tools/dotc/core/Denotations.scala | 2 ++ .../tools/dotc/core/SymDenotations.scala | 20 +++++++++++++++++ .../dotty/tools/dotc/core/TypeErasure.scala | 8 +++++-- .../dotc/reporting/diagnostic/messages.scala | 3 ++- .../dotc/transform/ElimErasedValueType.scala | 8 ++++++- .../dotty/tools/dotc/transform/Erasure.scala | 7 ++++-- .../src/dotty/tools/dotc/typer/Checking.scala | 19 ++++++++++++++-- .../dotty/tools/dotc/typer/RefChecks.scala | 5 +++++ .../src/dotty/tools/dotc/typer/Typer.scala | 4 +++- tests/neg/alpha-early.scala | 14 ++++++++++++ tests/neg/alpha-late.scala | 12 ++++++++++ tests/neg/alpha.scala | 22 +++++++++++++++++++ tests/neg/doubleDefinition-late.scala | 20 +++++++++++++++++ tests/neg/doubleDefinition.check | 14 ------------ tests/neg/doubleDefinition.scala | 13 +---------- tests/pos/alpha.scala | 9 ++++++++ tests/run/alpha/Test_2.java | 12 ++++++++++ tests/run/alpha/Test_3.scala | 5 +++++ tests/run/alpha/alpha_1.scala | 20 +++++++++++++++++ 19 files changed, 182 insertions(+), 35 deletions(-) create mode 100644 tests/neg/alpha-early.scala create mode 100644 tests/neg/alpha-late.scala create mode 100644 tests/neg/alpha.scala create mode 100644 tests/neg/doubleDefinition-late.scala create mode 100644 tests/pos/alpha.scala create mode 100644 tests/run/alpha/Test_2.java create mode 100644 tests/run/alpha/Test_3.scala create mode 100644 tests/run/alpha/alpha_1.scala diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index e992a53a4d1b..7d25b44aee7a 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -734,6 +734,8 @@ object Denotations { case _ => Signature.NotAMethod } + def erasedName(implicit ctx: Context): Name = symbol.erasedName + def derivedSingleDenotation(symbol: Symbol, info: Type)(implicit ctx: Context): SingleDenotation = if ((symbol eq this.symbol) && (info eq this.info)) this else newLikeThis(symbol, info) diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 98f7ae383f61..67e0be46129b 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -5,11 +5,13 @@ package core import Periods._, Contexts._, Symbols._, Denotations._, Names._, NameOps._, Annotations._ import Types._, Flags._, Decorators._, DenotTransformers._, StdNames._, Scopes._ import NameOps._, NameKinds._, Phases._ +import Constants.Constant import TypeApplications.TypeParamInfo import Scopes.Scope import dotty.tools.io.AbstractFile import Decorators.SymbolIteratorDecorator import ast._ +import Trees.Literal import annotation.tailrec import util.SimpleIdentityMap import util.Stats @@ -442,6 +444,24 @@ object SymDenotations { /** `fullName` where `.' is the separator character */ def fullName(implicit ctx: Context): Name = fullNameSeparated(QualifiedName) + private var myErasedName: Name = null + + final override def erasedName(implicit ctx: Context): Name = { + if (myErasedName == null) { + myErasedName = name + if (isTerm) + getAnnotation(defn.AlphaAnnot) match { + case Some(ann) => + ann.arguments match { + case Literal(Constant(str: String)) :: Nil => myErasedName = str.toTermName + case _ => + } + case _ => + } + } + myErasedName + } + // ----- Tests ------------------------------------------------- /** Is this denotation a type? */ diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index 4dad0c56fec9..f5ceb84d7e85 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -537,7 +537,11 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean private def eraseDerivedValueClassRef(tref: TypeRef)(implicit ctx: Context): Type = { val cls = tref.symbol.asClass val underlying = underlyingOfValueClass(cls) - if (underlying.exists && !isCyclic(cls)) ErasedValueType(tref, valueErasure(underlying)) + if (underlying.exists && !isCyclic(cls)) { + val erasedValue = valueErasure(underlying) + assert(erasedValue.exists, i"no erasure for $underlying") + ErasedValueType(tref, erasedValue) + } else NoType } @@ -605,7 +609,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean case tp: TypeVar => val inst = tp.instanceOpt if (inst.exists) sigName(inst) else tpnme.Uninstantiated - case tp @ RefinedType(parent, nme.apply, _) if parent.typeSymbol eq defn.PolyFunctionClass => + case tp @ RefinedType(parent, nme.apply, _) if parent.typeSymbol eq defn.PolyFunctionClass => // we need this case rather than falling through to the default // because RefinedTypes <: TypeProxy and it would be caught by // the case immediately below diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index f81434bd65de..43c2410d5bad 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -2145,6 +2145,7 @@ object messages { case class DoubleDefinition(decl: Symbol, previousDecl: Symbol, base: Symbol)(implicit ctx: Context) extends Message(DoubleDefinitionID) { val kind: String = "Duplicate Symbol" val msg: String = { + def nameAnd = if (decl.name != previousDecl.name) " name and" else "" val details = if (decl.isRealMethod && previousDecl.isRealMethod) { // compare the signatures when both symbols represent methods decl.signature.matchDegree(previousDecl.signature) match { @@ -2153,7 +2154,7 @@ object messages { case Signature.ParamMatch => "have matching parameter types." case Signature.FullMatch => - "have the same type after erasure." + i"have the same$nameAnd type after erasure." } } else "" def symLocation(sym: Symbol) = { diff --git a/compiler/src/dotty/tools/dotc/transform/ElimErasedValueType.scala b/compiler/src/dotty/tools/dotc/transform/ElimErasedValueType.scala index 18a731545eed..200fad6634a0 100644 --- a/compiler/src/dotty/tools/dotc/transform/ElimErasedValueType.scala +++ b/compiler/src/dotty/tools/dotc/transform/ElimErasedValueType.scala @@ -8,6 +8,7 @@ import Types._, Contexts._, Flags._, DenotTransformers._ import Symbols._, StdNames._, Trees._ import TypeErasure.ErasedValueType, ValueClasses._ import reporting.diagnostic.messages.DoubleDefinition +import NameKinds.SuperAccessorName object ElimErasedValueType { val name: String = "elimErasedValueType" @@ -95,7 +96,12 @@ class ElimErasedValueType extends MiniPhase with InfoTransformer { thisPhase => (sym1.owner.derivesFrom(defn.PolyFunctionClass) || sym2.owner.derivesFrom(defn.PolyFunctionClass)) - if (!info1.matchesLoosely(info2) && !bothPolyApply) + if (sym1.name != sym2.name && + !(sym1.name.is(SuperAccessorName) && sym2.name.is(SuperAccessorName)) + // super-accessors start as private, and their expanded name can clash after + // erasure. TODO: Verify that this is OK. + || + !info1.matchesLoosely(info2) && !bothPolyApply) ctx.error(DoubleDefinition(sym1, sym2, root), root.sourcePos) } val earlyCtx = ctx.withPhase(ctx.elimRepeatedPhase.next) diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index 800d812a14e9..8cd1854c4a37 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -69,6 +69,8 @@ class Erasure extends Phase with DenotTransformer { else oldSymbol val oldOwner = ref.owner val newOwner = if (oldOwner eq defn.AnyClass) defn.ObjectClass else oldOwner + val oldName = ref.name + val newName = ref.erasedName val oldInfo = ref.info val newInfo = transformInfo(oldSymbol, oldInfo) val oldFlags = ref.flags @@ -77,10 +79,11 @@ class Erasure extends Phase with DenotTransformer { else oldFlags &~ Flags.HasDefaultParams // HasDefaultParams needs to be dropped because overriding might become overloading // TODO: define derivedSymDenotation? - if ((oldSymbol eq newSymbol) && (oldOwner eq newOwner) && (oldInfo eq newInfo) && (oldFlags == newFlags)) ref + if ((oldSymbol eq newSymbol) && (oldOwner eq newOwner) && (oldName eq newName) && (oldInfo eq newInfo) && (oldFlags == newFlags)) + ref else { assert(!ref.is(Flags.PackageClass), s"trans $ref @ ${ctx.phase} oldOwner = $oldOwner, newOwner = $newOwner, oldInfo = $oldInfo, newInfo = $newInfo ${oldOwner eq newOwner} ${oldInfo eq newInfo}") - ref.copySymDenotation(symbol = newSymbol, owner = newOwner, initFlags = newFlags, info = newInfo) + ref.copySymDenotation(symbol = newSymbol, owner = newOwner, name = newName, initFlags = newFlags, info = newInfo) } } case ref: JointRefDenotation => diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 7e94b41b3452..79a6204084d0 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -864,7 +864,7 @@ trait Checking { def javaFieldMethodPair = decl.is(JavaDefined) && other.is(JavaDefined) && decl.is(Method) != other.is(Method) - if ((decl.signature.clashes(other.signature) || decl.matches(other)) && !javaFieldMethodPair) { + if (decl.matches(other) && !javaFieldMethodPair) { def doubleDefError(decl: Symbol, other: Symbol): Unit = if (!decl.info.isErroneous && !other.info.isErroneous) ctx.error(DoubleDefinition(decl, other, cls), decl.sourcePos) @@ -1124,6 +1124,21 @@ trait Checking { case _ => } } + + /** Check that symbol's external name does not clash with symbols defined in the same scope */ + def checkNoAlphaConflict(stats: List[Tree])(implicit ctx: Context): Unit = { + var seen = Set[Name]() + for (stat <- stats) { + val sym = stat.symbol + val ename = sym.erasedName + if (ename != sym.name) { + val preExisting = ctx.effectiveScope.lookup(ename) + if (preExisting.exists || seen.contains(ename)) + ctx.error(em"@alpha annotation ${'"'}$ename${'"'} clashes with other definition is same scope", stat.sourcePos) + seen += ename + } + } + } } trait ReChecking extends Checking { @@ -1144,7 +1159,7 @@ trait NoChecking extends ReChecking { override def checkImplicitConversionUseOK(sym: Symbol, posd: Positioned)(implicit ctx: Context): Unit = () override def checkFeasibleParent(tp: Type, pos: SourcePosition, where: => String = "")(implicit ctx: Context): Type = tp override def checkInlineConformant(tree: Tree, isFinal: Boolean, what: => String)(implicit ctx: Context): Unit = () - override def checkNoDoubleDeclaration(cls: Symbol)(implicit ctx: Context): Unit = () + override def checkNoAlphaConflict(stats: List[Tree])(implicit ctx: Context): Unit = () override def checkParentCall(call: Tree, caller: ClassSymbol)(implicit ctx: Context): Unit = () override def checkSimpleKinded(tpt: Tree)(implicit ctx: Context): Tree = tpt override def checkDerivedValueClass(clazz: Symbol, stats: List[Tree])(implicit ctx: Context): Unit = () diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 40451db18130..fa53f1070ba5 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -408,6 +408,11 @@ 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 @alpha(${"\""}${other.erasedName}${"\""}) so that external names match") + else + overrideError("cannot have an @alpha 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 bc17cf960a72..52b315025376 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2312,7 +2312,9 @@ class Typer extends Namer case _ => stat } - traverse(stats)(localCtx).mapConserve(finalize) + val stats1 = traverse(stats)(localCtx).mapConserve(finalize) + if (ctx.owner == exprOwner) checkNoAlphaConflict(stats1) + stats1 } /** Given an inline method `mdef`, the method rewritten so that its body diff --git a/tests/neg/alpha-early.scala b/tests/neg/alpha-early.scala new file mode 100644 index 000000000000..89ec197f6b90 --- /dev/null +++ b/tests/neg/alpha-early.scala @@ -0,0 +1,14 @@ + +import annotation.alpha + +class Gamma { + + def foo2() = { + def bar = 1 + @alpha("bar") def baz = 2 // error: @alpha annotation clashes + + @alpha("bam") def x1 = 0 + @alpha("bam") def y1 = 0 // error: @alpha annotation clashes + + } +} diff --git a/tests/neg/alpha-late.scala b/tests/neg/alpha-late.scala new file mode 100644 index 000000000000..254260838fd3 --- /dev/null +++ b/tests/neg/alpha-late.scala @@ -0,0 +1,12 @@ + +import annotation.alpha + +class Gamma { + + def foo: Int = 1 + +} + +class Delta extends Gamma { // error: name clash + @alpha("foo") def bar: Int = 1 +} \ No newline at end of file diff --git a/tests/neg/alpha.scala b/tests/neg/alpha.scala new file mode 100644 index 000000000000..49bdbc095eee --- /dev/null +++ b/tests/neg/alpha.scala @@ -0,0 +1,22 @@ +import annotation.alpha + + +abstract class Alpha[T] { + + def foo() = 1 + + @alpha("bar") def foo(x: T): T + + @alpha("append") def ++ (xs: Alpha[T]): Alpha[T] = this + +} + +class Beta extends Alpha[String] { + + @alpha("foo1") override def foo() = 1 // error + + @alpha("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 new file mode 100644 index 000000000000..c0ea1438d75c --- /dev/null +++ b/tests/neg/doubleDefinition-late.scala @@ -0,0 +1,20 @@ + +import annotation.alpha + +class Gamma { + + val v = 1 + @alpha("v") val w = 2 // error: double definition + +} +// Error when overloading polymorphic and non-polymorphic methods +class Test19 { + def foo[T <: Int](x: T): T = x + def foo(x: Int): Int = x // error +} + +// Error when overloading polymorphic methods +class Test20 { + def foo[T <: Int](x: T): T = x + def foo[S <: Int, T <: Int](x: S): T = ??? // error +} diff --git a/tests/neg/doubleDefinition.check b/tests/neg/doubleDefinition.check index d9688b1370a4..e3df6788d3b0 100644 --- a/tests/neg/doubleDefinition.check +++ b/tests/neg/doubleDefinition.check @@ -122,17 +122,3 @@ | Double definition: | var foo: Int in class Test16 at line 115 and | def foo: => Int in class Test16 at line 116 --- [E120] Duplicate Symbol Error: tests/neg/doubleDefinition.scala:138:6 ----------------------------------------------- -138 | def foo(x: Int): Int = x // error - | ^ - | Double definition: - | def foo: [T <: Int](x: T): T in class Test19 at line 137 and - | def foo(x: Int): Int in class Test19 at line 138 - | have the same type after erasure. --- [E120] Duplicate Symbol Error: tests/neg/doubleDefinition.scala:144:6 ----------------------------------------------- -144 | def foo[S <: Int, T <: Int](x: S): T = ??? // error - | ^ - | Double definition: - | def foo: [T <: Int](x: T): T in class Test20 at line 143 and - | def foo: [S <: Int, T <: Int](x: S): T in class Test20 at line 144 - | have the same type after erasure. diff --git a/tests/neg/doubleDefinition.scala b/tests/neg/doubleDefinition.scala index 812908503db7..f77e4b8eb5d9 100644 --- a/tests/neg/doubleDefinition.scala +++ b/tests/neg/doubleDefinition.scala @@ -82,7 +82,7 @@ class Test8d { def foo = 2 // error } -// test method and contructor argument clashing +// test method and constructor argument clashing class Test9(val foo: Int) { def foo: String // error @@ -132,14 +132,3 @@ class Test18 { def foo(b: B) = 1 } -// Error when overloading polymorphic and non-polymorphic methods -class Test19 { - def foo[T <: Int](x: T): T = x - def foo(x: Int): Int = x // error -} - -// Error when overloading polymorphic methods -class Test20 { - def foo[T <: Int](x: T): T = x - def foo[S <: Int, T <: Int](x: S): T = ??? // error -} diff --git a/tests/pos/alpha.scala b/tests/pos/alpha.scala new file mode 100644 index 000000000000..8458397b0f2c --- /dev/null +++ b/tests/pos/alpha.scala @@ -0,0 +1,9 @@ +import annotation.alpha + +object Test { + + def foo() = 1 + + @alpha("bar") def foo(x: Int) = 2 + +} \ No newline at end of file diff --git a/tests/run/alpha/Test_2.java b/tests/run/alpha/Test_2.java new file mode 100644 index 000000000000..6fc7d0ab714e --- /dev/null +++ b/tests/run/alpha/Test_2.java @@ -0,0 +1,12 @@ + +package alpha; + +public class Test_2 { + + public static void main(String[] args) { + Alpha a = new Beta(); + assert a.foo() == 1; + assert a.bar("a").equals("aa"); + Alpha aa = a.append(a); + } +} diff --git a/tests/run/alpha/Test_3.scala b/tests/run/alpha/Test_3.scala new file mode 100644 index 000000000000..afafaa1da8e0 --- /dev/null +++ b/tests/run/alpha/Test_3.scala @@ -0,0 +1,5 @@ + +object Test { + def main(args: Array[String]): Unit = + alpha.Test_2.main(args) +} diff --git a/tests/run/alpha/alpha_1.scala b/tests/run/alpha/alpha_1.scala new file mode 100644 index 000000000000..28ddb74e3cd2 --- /dev/null +++ b/tests/run/alpha/alpha_1.scala @@ -0,0 +1,20 @@ +package alpha +import annotation.alpha + +abstract class Alpha[T] { + + def foo() = 1 + + @alpha("bar") def foo(x: T): T + + @alpha("append") def ++ (xs: Alpha[T]): Alpha[T] = this + +} + +class Beta extends Alpha[String] { + + @alpha("bar") override def foo(x: String) = x ++ x + + @alpha("append") override def ++ (xs: Alpha[String]) = this + +} \ No newline at end of file From 1e17425fdaa396f9a91fd33e89497905a493b9ec Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 3 Jun 2019 20:23:44 +0200 Subject: [PATCH 2/6] Don't cache erasedName It does not seem to be worthwhile to spend a field in every symbol for it. --- .../dotty/tools/dotc/core/Denotations.scala | 2 -- .../tools/dotc/core/SymDenotations.scala | 29 ++++++++----------- 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index 7d25b44aee7a..e992a53a4d1b 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -734,8 +734,6 @@ object Denotations { case _ => Signature.NotAMethod } - def erasedName(implicit ctx: Context): Name = symbol.erasedName - def derivedSingleDenotation(symbol: Symbol, info: Type)(implicit ctx: Context): SingleDenotation = if ((symbol eq this.symbol) && (info eq this.info)) this else newLikeThis(symbol, info) diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 67e0be46129b..647f641a78c4 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -444,23 +444,18 @@ object SymDenotations { /** `fullName` where `.' is the separator character */ def fullName(implicit ctx: Context): Name = fullNameSeparated(QualifiedName) - private var myErasedName: Name = null - - final override def erasedName(implicit ctx: Context): Name = { - if (myErasedName == null) { - myErasedName = name - if (isTerm) - getAnnotation(defn.AlphaAnnot) match { - case Some(ann) => - ann.arguments match { - case Literal(Constant(str: String)) :: Nil => myErasedName = str.toTermName - case _ => - } - case _ => - } - } - myErasedName - } + /** The name given in an `@alpha` annotation if one is present, `name` otherwise */ + final def erasedName(implicit ctx: Context): Name = + if (isTerm) + getAnnotation(defn.AlphaAnnot) match { + case Some(ann) => + ann.arguments match { + case Literal(Constant(str: String)) :: Nil => str.toTermName + case _ => name + } + case _ => name + } + else name // ----- Tests ------------------------------------------------- From ebb119521308e3005d5ab29910788ddc515bfd48 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 3 Jun 2019 21:26:46 +0200 Subject: [PATCH 3/6] Allow @alpha on types --- .../tools/dotc/core/SymDenotations.scala | 19 +++++++++---------- tests/run/alpha/Test_2.java | 2 +- tests/run/alpha/alpha_1.scala | 2 +- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 647f641a78c4..23a4d993aaee 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -446,16 +446,15 @@ object SymDenotations { /** The name given in an `@alpha` annotation if one is present, `name` otherwise */ final def erasedName(implicit ctx: Context): Name = - if (isTerm) - getAnnotation(defn.AlphaAnnot) match { - case Some(ann) => - ann.arguments match { - case Literal(Constant(str: String)) :: Nil => str.toTermName - case _ => name - } - case _ => name - } - else name + getAnnotation(defn.AlphaAnnot) match { + case Some(ann) => + ann.arguments match { + case Literal(Constant(str: String)) :: Nil => + if (isType) str.toTypeName else str.toTermName + case _ => name + } + case _ => name + } // ----- Tests ------------------------------------------------- diff --git a/tests/run/alpha/Test_2.java b/tests/run/alpha/Test_2.java index 6fc7d0ab714e..6e93f3cd3533 100644 --- a/tests/run/alpha/Test_2.java +++ b/tests/run/alpha/Test_2.java @@ -4,7 +4,7 @@ public class Test_2 { public static void main(String[] args) { - Alpha a = new Beta(); + Alpha a = new Bar(); assert a.foo() == 1; assert a.bar("a").equals("aa"); Alpha aa = a.append(a); diff --git a/tests/run/alpha/alpha_1.scala b/tests/run/alpha/alpha_1.scala index 28ddb74e3cd2..8885ad6413e0 100644 --- a/tests/run/alpha/alpha_1.scala +++ b/tests/run/alpha/alpha_1.scala @@ -11,7 +11,7 @@ abstract class Alpha[T] { } -class Beta extends Alpha[String] { +@alpha("Bar") class | extends Alpha[String] { @alpha("bar") override def foo(x: String) = x ++ x From d5f5ae584ef8e8f70c0e1531bc5c1c257cdb0839 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 3 Jun 2019 22:25:30 +0200 Subject: [PATCH 4/6] Deprecation warnings for operators without alpha Operators without an alpha annotation give a deprecation warning -strict mode. --- .../src/dotty/tools/dotc/transform/PostTyper.scala | 1 + compiler/src/dotty/tools/dotc/typer/Checking.scala | 14 ++++++++++++++ .../test/dotty/tools/dotc/CompilationTests.scala | 3 ++- tests/neg-custom-args/alpha.scala | 9 +++++++++ 4 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 tests/neg-custom-args/alpha.scala diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 866e7e29f060..d4fb2a1ca6db 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -111,6 +111,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase private def processMemberDef(tree: Tree)(implicit ctx: Context): tree.type = { val sym = tree.symbol + Checking.checkValidOperator(sym) sym.transformAnnotations(transformAnnot) sym.defTree = tree tree diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 79a6204084d0..448bc98aa942 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -274,6 +274,20 @@ object Checking { } } + /** If `sym` has an operator name, check that it has an @alpha annotation under -strict */ + def checkValidOperator(sym: Symbol)(implicit ctx: Context): Unit = + sym.name.toTermName match { + case name: SimpleName + if name.exists(isOperatorPart) && + !sym.getAnnotation(defn.AlphaAnnot).isDefined && + !sym.is(Synthetic) && + !name.isConstructorName && + ctx.settings.strict.value => + ctx.deprecationWarning( + i"$sym has an operator name; it should come with an @alpha annotation", sym.sourcePos) + case _ => + } + /** Check that `info` of symbol `sym` is not cyclic. * @pre sym is not yet initialized (i.e. its type is a Completer). * @return `info` where every legal F-bounded reference is proctected diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 9e32cc6a08b0..608b3d21c11f 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -180,7 +180,8 @@ class CompilationTests extends ParallelTesting { "tests/neg-custom-args/toplevel-samesource/nested/S.scala"), defaultOptions), compileFile("tests/neg-custom-args/i6300.scala", allowDeepSubtypes), - compileFile("tests/neg-custom-args/infix.scala", defaultOptions.and("-strict", "-deprecation", "-Xfatal-warnings")) + compileFile("tests/neg-custom-args/infix.scala", defaultOptions.and("-strict", "-deprecation", "-Xfatal-warnings")), + compileFile("tests/neg-custom-args/alpha.scala", defaultOptions.and("-strict", "-deprecation", "-Xfatal-warnings")) ).checkExpectedErrors() } diff --git a/tests/neg-custom-args/alpha.scala b/tests/neg-custom-args/alpha.scala new file mode 100644 index 000000000000..d40a61b5bdf8 --- /dev/null +++ b/tests/neg-custom-args/alpha.scala @@ -0,0 +1,9 @@ +// Compile with -strict -Xfatal-warnings -deprecation +import scala.annotation.alpha +class & { // error + + @alpha("op") def *(x: Int): Int = ??? // OK + def / (x: Int): Int // error + val frozen_& : Int = ??? // error + object some_??? // error +} From dd221ae33eb44e652e5f9bdcb951bf5b97320824 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 5 Jun 2019 09:30:33 +0200 Subject: [PATCH 5/6] Address review comments --- .../dotty/tools/dotc/transform/ElimErasedValueType.scala | 9 ++++----- compiler/test/dotty/tools/dotc/CompilationTests.scala | 2 +- .../neg-custom-args/{alpha.scala => missing-alpha.scala} | 0 tests/neg/alpha-override/B_1.java | 6 ++++++ tests/neg/alpha-override/C_2.scala | 4 ++++ tests/neg/alpha-override/D_3.scala | 7 +++++++ tests/pos/alpha-override/A_1.scala | 5 +++++ tests/pos/alpha-override/B_2.java | 7 +++++++ tests/pos/alpha.scala | 3 +-- tests/run/{alpha => alpha-interop}/Test_2.java | 0 tests/run/{alpha => alpha-interop}/Test_3.scala | 0 tests/run/{alpha => alpha-interop}/alpha_1.scala | 0 tests/run/alpha.scala | 8 ++++++++ 13 files changed, 43 insertions(+), 8 deletions(-) rename tests/neg-custom-args/{alpha.scala => missing-alpha.scala} (100%) create mode 100644 tests/neg/alpha-override/B_1.java create mode 100644 tests/neg/alpha-override/C_2.scala create mode 100644 tests/neg/alpha-override/D_3.scala create mode 100644 tests/pos/alpha-override/A_1.scala create mode 100644 tests/pos/alpha-override/B_2.java rename tests/run/{alpha => alpha-interop}/Test_2.java (100%) rename tests/run/{alpha => alpha-interop}/Test_3.scala (100%) rename tests/run/{alpha => alpha-interop}/alpha_1.scala (100%) create mode 100644 tests/run/alpha.scala diff --git a/compiler/src/dotty/tools/dotc/transform/ElimErasedValueType.scala b/compiler/src/dotty/tools/dotc/transform/ElimErasedValueType.scala index 200fad6634a0..b4bae8a4a18b 100644 --- a/compiler/src/dotty/tools/dotc/transform/ElimErasedValueType.scala +++ b/compiler/src/dotty/tools/dotc/transform/ElimErasedValueType.scala @@ -96,11 +96,10 @@ class ElimErasedValueType extends MiniPhase with InfoTransformer { thisPhase => (sym1.owner.derivesFrom(defn.PolyFunctionClass) || sym2.owner.derivesFrom(defn.PolyFunctionClass)) - if (sym1.name != sym2.name && - !(sym1.name.is(SuperAccessorName) && sym2.name.is(SuperAccessorName)) - // super-accessors start as private, and their expanded name can clash after - // erasure. TODO: Verify that this is OK. - || + // super-accessors start as private, and their expanded name can clash after + // erasure. TODO: Verify that this is OK. + def bothSuperAccessors = sym1.name.is(SuperAccessorName) && sym2.name.is(SuperAccessorName) + if (sym1.name != sym2.name && !bothSuperAccessors || !info1.matchesLoosely(info2) && !bothPolyApply) ctx.error(DoubleDefinition(sym1, sym2, root), root.sourcePos) } diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 608b3d21c11f..e8e750b7f22a 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -181,7 +181,7 @@ class CompilationTests extends ParallelTesting { defaultOptions), compileFile("tests/neg-custom-args/i6300.scala", allowDeepSubtypes), compileFile("tests/neg-custom-args/infix.scala", defaultOptions.and("-strict", "-deprecation", "-Xfatal-warnings")), - compileFile("tests/neg-custom-args/alpha.scala", defaultOptions.and("-strict", "-deprecation", "-Xfatal-warnings")) + compileFile("tests/neg-custom-args/missing-alpha.scala", defaultOptions.and("-strict", "-deprecation", "-Xfatal-warnings")) ).checkExpectedErrors() } diff --git a/tests/neg-custom-args/alpha.scala b/tests/neg-custom-args/missing-alpha.scala similarity index 100% rename from tests/neg-custom-args/alpha.scala rename to tests/neg-custom-args/missing-alpha.scala diff --git a/tests/neg/alpha-override/B_1.java b/tests/neg/alpha-override/B_1.java new file mode 100644 index 000000000000..f5df05929a33 --- /dev/null +++ b/tests/neg/alpha-override/B_1.java @@ -0,0 +1,6 @@ +// Java +public class B_1 extends A_1 { + public int bar() { + return 2; + } +} diff --git a/tests/neg/alpha-override/C_2.scala b/tests/neg/alpha-override/C_2.scala new file mode 100644 index 000000000000..4f1c30b89ddc --- /dev/null +++ b/tests/neg/alpha-override/C_2.scala @@ -0,0 +1,4 @@ +import annotation.alpha +class C extends B_1 { // error: Name clash between defined and inherited member + @alpha("bar") override def foo(): Int = 3 +} diff --git a/tests/neg/alpha-override/D_3.scala b/tests/neg/alpha-override/D_3.scala new file mode 100644 index 000000000000..1a821fb25ec7 --- /dev/null +++ b/tests/neg/alpha-override/D_3.scala @@ -0,0 +1,7 @@ +import annotation.alpha +class D extends B_1 { + @alpha("bar") def foo(): Int = 3 // error: needs override +} +class E extends B_1 { + @alpha("baz") override def bar(): Int = 3 // error: cannot have an @alpha annotation since external names would be different +} \ No newline at end of file diff --git a/tests/pos/alpha-override/A_1.scala b/tests/pos/alpha-override/A_1.scala new file mode 100644 index 000000000000..edfb54d656f7 --- /dev/null +++ b/tests/pos/alpha-override/A_1.scala @@ -0,0 +1,5 @@ +// Scala +import annotation.alpha +class A_1 { + @alpha("bar") def foo(): Int = 1 +} diff --git a/tests/pos/alpha-override/B_2.java b/tests/pos/alpha-override/B_2.java new file mode 100644 index 000000000000..ba42655223d3 --- /dev/null +++ b/tests/pos/alpha-override/B_2.java @@ -0,0 +1,7 @@ +// Java +public class B_2 extends A_1 { + @Override + public int bar() { + return 2; + } +} diff --git a/tests/pos/alpha.scala b/tests/pos/alpha.scala index 8458397b0f2c..15e0355543e9 100644 --- a/tests/pos/alpha.scala +++ b/tests/pos/alpha.scala @@ -5,5 +5,4 @@ object Test { def foo() = 1 @alpha("bar") def foo(x: Int) = 2 - -} \ No newline at end of file +} diff --git a/tests/run/alpha/Test_2.java b/tests/run/alpha-interop/Test_2.java similarity index 100% rename from tests/run/alpha/Test_2.java rename to tests/run/alpha-interop/Test_2.java diff --git a/tests/run/alpha/Test_3.scala b/tests/run/alpha-interop/Test_3.scala similarity index 100% rename from tests/run/alpha/Test_3.scala rename to tests/run/alpha-interop/Test_3.scala diff --git a/tests/run/alpha/alpha_1.scala b/tests/run/alpha-interop/alpha_1.scala similarity index 100% rename from tests/run/alpha/alpha_1.scala rename to tests/run/alpha-interop/alpha_1.scala diff --git a/tests/run/alpha.scala b/tests/run/alpha.scala new file mode 100644 index 000000000000..bf58321baaf5 --- /dev/null +++ b/tests/run/alpha.scala @@ -0,0 +1,8 @@ +import annotation.alpha + +object Test extends App { + def foo(x: Any): Int = 1 + @alpha("bar") def foo[A <: AnyRef](x: A) = 2 + + assert(foo("a") == 2) +} From 97a8a37e208e42857e61d33451fc0bfee42b15e9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 5 Jun 2019 13:53:54 +0200 Subject: [PATCH 6/6] fix tests --- tests/neg/alpha-override/B_1.java | 2 +- tests/neg/alpha-override/C_2.scala | 2 +- tests/neg/alpha-override1/B_1.java | 6 ++++++ .../{alpha-override/D_3.scala => alpha-override1/D_2.scala} | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 tests/neg/alpha-override1/B_1.java rename tests/neg/{alpha-override/D_3.scala => alpha-override1/D_2.scala} (76%) diff --git a/tests/neg/alpha-override/B_1.java b/tests/neg/alpha-override/B_1.java index f5df05929a33..b3fcaac04aa2 100644 --- a/tests/neg/alpha-override/B_1.java +++ b/tests/neg/alpha-override/B_1.java @@ -1,5 +1,5 @@ // Java -public class B_1 extends A_1 { +public class B_1 { public int bar() { return 2; } diff --git a/tests/neg/alpha-override/C_2.scala b/tests/neg/alpha-override/C_2.scala index 4f1c30b89ddc..a6e50148ffe0 100644 --- a/tests/neg/alpha-override/C_2.scala +++ b/tests/neg/alpha-override/C_2.scala @@ -1,4 +1,4 @@ import annotation.alpha class C extends B_1 { // error: Name clash between defined and inherited member - @alpha("bar") override def foo(): Int = 3 + @alpha("bar") def foo(): Int = 3 } diff --git a/tests/neg/alpha-override1/B_1.java b/tests/neg/alpha-override1/B_1.java new file mode 100644 index 000000000000..b3fcaac04aa2 --- /dev/null +++ b/tests/neg/alpha-override1/B_1.java @@ -0,0 +1,6 @@ +// Java +public class B_1 { + public int bar() { + return 2; + } +} diff --git a/tests/neg/alpha-override/D_3.scala b/tests/neg/alpha-override1/D_2.scala similarity index 76% rename from tests/neg/alpha-override/D_3.scala rename to tests/neg/alpha-override1/D_2.scala index 1a821fb25ec7..f0a0dee774a4 100644 --- a/tests/neg/alpha-override/D_3.scala +++ b/tests/neg/alpha-override1/D_2.scala @@ -1,6 +1,6 @@ import annotation.alpha class D extends B_1 { - @alpha("bar") def foo(): Int = 3 // error: needs override + @alpha("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