Skip to content

Commit d9c04cc

Browse files
authored
Merge pull request #6597 from dotty-staging/add-alpha
Implement @Alpha annotation
2 parents 9116ecb + 97a8a37 commit d9c04cc

28 files changed

+241
-36
lines changed

compiler/src/dotty/tools/dotc/core/SymDenotations.scala

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ package core
55
import Periods._, Contexts._, Symbols._, Denotations._, Names._, NameOps._, Annotations._
66
import Types._, Flags._, Decorators._, DenotTransformers._, StdNames._, Scopes._
77
import NameOps._, NameKinds._, Phases._
8+
import Constants.Constant
89
import TypeApplications.TypeParamInfo
910
import Scopes.Scope
1011
import dotty.tools.io.AbstractFile
1112
import Decorators.SymbolIteratorDecorator
1213
import ast._
14+
import Trees.Literal
1315
import annotation.tailrec
1416
import util.SimpleIdentityMap
1517
import util.Stats
@@ -442,6 +444,18 @@ object SymDenotations {
442444
/** `fullName` where `.' is the separator character */
443445
def fullName(implicit ctx: Context): Name = fullNameSeparated(QualifiedName)
444446

447+
/** The name given in an `@alpha` annotation if one is present, `name` otherwise */
448+
final def erasedName(implicit ctx: Context): Name =
449+
getAnnotation(defn.AlphaAnnot) match {
450+
case Some(ann) =>
451+
ann.arguments match {
452+
case Literal(Constant(str: String)) :: Nil =>
453+
if (isType) str.toTypeName else str.toTermName
454+
case _ => name
455+
}
456+
case _ => name
457+
}
458+
445459
// ----- Tests -------------------------------------------------
446460

447461
/** Is this denotation a type? */

compiler/src/dotty/tools/dotc/core/TypeErasure.scala

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -537,7 +537,11 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
537537
private def eraseDerivedValueClassRef(tref: TypeRef)(implicit ctx: Context): Type = {
538538
val cls = tref.symbol.asClass
539539
val underlying = underlyingOfValueClass(cls)
540-
if (underlying.exists && !isCyclic(cls)) ErasedValueType(tref, valueErasure(underlying))
540+
if (underlying.exists && !isCyclic(cls)) {
541+
val erasedValue = valueErasure(underlying)
542+
assert(erasedValue.exists, i"no erasure for $underlying")
543+
ErasedValueType(tref, erasedValue)
544+
}
541545
else NoType
542546
}
543547

@@ -605,7 +609,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
605609
case tp: TypeVar =>
606610
val inst = tp.instanceOpt
607611
if (inst.exists) sigName(inst) else tpnme.Uninstantiated
608-
case tp @ RefinedType(parent, nme.apply, _) if parent.typeSymbol eq defn.PolyFunctionClass =>
612+
case tp @ RefinedType(parent, nme.apply, _) if parent.typeSymbol eq defn.PolyFunctionClass =>
609613
// we need this case rather than falling through to the default
610614
// because RefinedTypes <: TypeProxy and it would be caught by
611615
// the case immediately below

compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2145,6 +2145,7 @@ object messages {
21452145
case class DoubleDefinition(decl: Symbol, previousDecl: Symbol, base: Symbol)(implicit ctx: Context) extends Message(DoubleDefinitionID) {
21462146
val kind: String = "Duplicate Symbol"
21472147
val msg: String = {
2148+
def nameAnd = if (decl.name != previousDecl.name) " name and" else ""
21482149
val details = if (decl.isRealMethod && previousDecl.isRealMethod) {
21492150
// compare the signatures when both symbols represent methods
21502151
decl.signature.matchDegree(previousDecl.signature) match {
@@ -2153,7 +2154,7 @@ object messages {
21532154
case Signature.ParamMatch =>
21542155
"have matching parameter types."
21552156
case Signature.FullMatch =>
2156-
"have the same type after erasure."
2157+
i"have the same$nameAnd type after erasure."
21572158
}
21582159
} else ""
21592160
def symLocation(sym: Symbol) = {

compiler/src/dotty/tools/dotc/transform/ElimErasedValueType.scala

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import Types._, Contexts._, Flags._, DenotTransformers._
88
import Symbols._, StdNames._, Trees._
99
import TypeErasure.ErasedValueType, ValueClasses._
1010
import reporting.diagnostic.messages.DoubleDefinition
11+
import NameKinds.SuperAccessorName
1112

1213
object ElimErasedValueType {
1314
val name: String = "elimErasedValueType"
@@ -95,7 +96,11 @@ class ElimErasedValueType extends MiniPhase with InfoTransformer { thisPhase =>
9596
(sym1.owner.derivesFrom(defn.PolyFunctionClass) ||
9697
sym2.owner.derivesFrom(defn.PolyFunctionClass))
9798

98-
if (!info1.matchesLoosely(info2) && !bothPolyApply)
99+
// super-accessors start as private, and their expanded name can clash after
100+
// erasure. TODO: Verify that this is OK.
101+
def bothSuperAccessors = sym1.name.is(SuperAccessorName) && sym2.name.is(SuperAccessorName)
102+
if (sym1.name != sym2.name && !bothSuperAccessors ||
103+
!info1.matchesLoosely(info2) && !bothPolyApply)
99104
ctx.error(DoubleDefinition(sym1, sym2, root), root.sourcePos)
100105
}
101106
val earlyCtx = ctx.withPhase(ctx.elimRepeatedPhase.next)

compiler/src/dotty/tools/dotc/transform/Erasure.scala

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ class Erasure extends Phase with DenotTransformer {
6969
else oldSymbol
7070
val oldOwner = ref.owner
7171
val newOwner = if (oldOwner eq defn.AnyClass) defn.ObjectClass else oldOwner
72+
val oldName = ref.name
73+
val newName = ref.erasedName
7274
val oldInfo = ref.info
7375
val newInfo = transformInfo(oldSymbol, oldInfo)
7476
val oldFlags = ref.flags
@@ -77,10 +79,11 @@ class Erasure extends Phase with DenotTransformer {
7779
else oldFlags &~ Flags.HasDefaultParams // HasDefaultParams needs to be dropped because overriding might become overloading
7880

7981
// TODO: define derivedSymDenotation?
80-
if ((oldSymbol eq newSymbol) && (oldOwner eq newOwner) && (oldInfo eq newInfo) && (oldFlags == newFlags)) ref
82+
if ((oldSymbol eq newSymbol) && (oldOwner eq newOwner) && (oldName eq newName) && (oldInfo eq newInfo) && (oldFlags == newFlags))
83+
ref
8184
else {
8285
assert(!ref.is(Flags.PackageClass), s"trans $ref @ ${ctx.phase} oldOwner = $oldOwner, newOwner = $newOwner, oldInfo = $oldInfo, newInfo = $newInfo ${oldOwner eq newOwner} ${oldInfo eq newInfo}")
83-
ref.copySymDenotation(symbol = newSymbol, owner = newOwner, initFlags = newFlags, info = newInfo)
86+
ref.copySymDenotation(symbol = newSymbol, owner = newOwner, name = newName, initFlags = newFlags, info = newInfo)
8487
}
8588
}
8689
case ref: JointRefDenotation =>

compiler/src/dotty/tools/dotc/transform/PostTyper.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
111111

112112
private def processMemberDef(tree: Tree)(implicit ctx: Context): tree.type = {
113113
val sym = tree.symbol
114+
Checking.checkValidOperator(sym)
114115
sym.transformAnnotations(transformAnnot)
115116
sym.defTree = tree
116117
tree

compiler/src/dotty/tools/dotc/typer/Checking.scala

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,20 @@ object Checking {
274274
}
275275
}
276276

277+
/** If `sym` has an operator name, check that it has an @alpha annotation under -strict */
278+
def checkValidOperator(sym: Symbol)(implicit ctx: Context): Unit =
279+
sym.name.toTermName match {
280+
case name: SimpleName
281+
if name.exists(isOperatorPart) &&
282+
!sym.getAnnotation(defn.AlphaAnnot).isDefined &&
283+
!sym.is(Synthetic) &&
284+
!name.isConstructorName &&
285+
ctx.settings.strict.value =>
286+
ctx.deprecationWarning(
287+
i"$sym has an operator name; it should come with an @alpha annotation", sym.sourcePos)
288+
case _ =>
289+
}
290+
277291
/** Check that `info` of symbol `sym` is not cyclic.
278292
* @pre sym is not yet initialized (i.e. its type is a Completer).
279293
* @return `info` where every legal F-bounded reference is proctected
@@ -864,7 +878,7 @@ trait Checking {
864878
def javaFieldMethodPair =
865879
decl.is(JavaDefined) && other.is(JavaDefined) &&
866880
decl.is(Method) != other.is(Method)
867-
if ((decl.signature.clashes(other.signature) || decl.matches(other)) && !javaFieldMethodPair) {
881+
if (decl.matches(other) && !javaFieldMethodPair) {
868882
def doubleDefError(decl: Symbol, other: Symbol): Unit =
869883
if (!decl.info.isErroneous && !other.info.isErroneous)
870884
ctx.error(DoubleDefinition(decl, other, cls), decl.sourcePos)
@@ -1130,6 +1144,21 @@ trait Checking {
11301144
case _ =>
11311145
}
11321146
}
1147+
1148+
/** Check that symbol's external name does not clash with symbols defined in the same scope */
1149+
def checkNoAlphaConflict(stats: List[Tree])(implicit ctx: Context): Unit = {
1150+
var seen = Set[Name]()
1151+
for (stat <- stats) {
1152+
val sym = stat.symbol
1153+
val ename = sym.erasedName
1154+
if (ename != sym.name) {
1155+
val preExisting = ctx.effectiveScope.lookup(ename)
1156+
if (preExisting.exists || seen.contains(ename))
1157+
ctx.error(em"@alpha annotation ${'"'}$ename${'"'} clashes with other definition is same scope", stat.sourcePos)
1158+
seen += ename
1159+
}
1160+
}
1161+
}
11331162
}
11341163

11351164
trait ReChecking extends Checking {
@@ -1150,7 +1179,7 @@ trait NoChecking extends ReChecking {
11501179
override def checkImplicitConversionUseOK(sym: Symbol, posd: Positioned)(implicit ctx: Context): Unit = ()
11511180
override def checkFeasibleParent(tp: Type, pos: SourcePosition, where: => String = "")(implicit ctx: Context): Type = tp
11521181
override def checkInlineConformant(tree: Tree, isFinal: Boolean, what: => String)(implicit ctx: Context): Unit = ()
1153-
override def checkNoDoubleDeclaration(cls: Symbol)(implicit ctx: Context): Unit = ()
1182+
override def checkNoAlphaConflict(stats: List[Tree])(implicit ctx: Context): Unit = ()
11541183
override def checkParentCall(call: Tree, caller: ClassSymbol)(implicit ctx: Context): Unit = ()
11551184
override def checkSimpleKinded(tpt: Tree)(implicit ctx: Context): Tree = tpt
11561185
override def checkDerivedValueClass(clazz: Symbol, stats: List[Tree])(implicit ctx: Context): Unit = ()

compiler/src/dotty/tools/dotc/typer/RefChecks.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,11 @@ object RefChecks {
408408
} else if (!compatibleTypes(memberTp(self), otherTp(self)) &&
409409
!compatibleTypes(memberTp(upwardsSelf), otherTp(upwardsSelf))) {
410410
overrideError("has incompatible type" + err.whyNoMatchStr(memberTp(self), otherTp(self)))
411+
} else if (member.erasedName != other.erasedName) {
412+
if (other.erasedName != other.name)
413+
overrideError(i"needs to be declared with @alpha(${"\""}${other.erasedName}${"\""}) so that external names match")
414+
else
415+
overrideError("cannot have an @alpha annotation since external names would be different")
411416
} else {
412417
checkOverrideDeprecated()
413418
}

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2312,7 +2312,9 @@ class Typer extends Namer
23122312
case _ =>
23132313
stat
23142314
}
2315-
traverse(stats)(localCtx).mapConserve(finalize)
2315+
val stats1 = traverse(stats)(localCtx).mapConserve(finalize)
2316+
if (ctx.owner == exprOwner) checkNoAlphaConflict(stats1)
2317+
stats1
23162318
}
23172319

23182320
/** Given an inline method `mdef`, the method rewritten so that its body

compiler/test/dotty/tools/dotc/CompilationTests.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,8 @@ class CompilationTests extends ParallelTesting {
180180
"tests/neg-custom-args/toplevel-samesource/nested/S.scala"),
181181
defaultOptions),
182182
compileFile("tests/neg-custom-args/i6300.scala", allowDeepSubtypes),
183-
compileFile("tests/neg-custom-args/infix.scala", defaultOptions.and("-strict", "-deprecation", "-Xfatal-warnings"))
183+
compileFile("tests/neg-custom-args/infix.scala", defaultOptions.and("-strict", "-deprecation", "-Xfatal-warnings")),
184+
compileFile("tests/neg-custom-args/missing-alpha.scala", defaultOptions.and("-strict", "-deprecation", "-Xfatal-warnings"))
184185
).checkExpectedErrors()
185186
}
186187

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Compile with -strict -Xfatal-warnings -deprecation
2+
import scala.annotation.alpha
3+
class & { // error
4+
5+
@alpha("op") def *(x: Int): Int = ??? // OK
6+
def / (x: Int): Int // error
7+
val frozen_& : Int = ??? // error
8+
object some_??? // error
9+
}

tests/neg/alpha-early.scala

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2+
import annotation.alpha
3+
4+
class Gamma {
5+
6+
def foo2() = {
7+
def bar = 1
8+
@alpha("bar") def baz = 2 // error: @alpha annotation clashes
9+
10+
@alpha("bam") def x1 = 0
11+
@alpha("bam") def y1 = 0 // error: @alpha annotation clashes
12+
13+
}
14+
}

tests/neg/alpha-late.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
2+
import annotation.alpha
3+
4+
class Gamma {
5+
6+
def foo: Int = 1
7+
8+
}
9+
10+
class Delta extends Gamma { // error: name clash
11+
@alpha("foo") def bar: Int = 1
12+
}

tests/neg/alpha-override/B_1.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Java
2+
public class B_1 {
3+
public int bar() {
4+
return 2;
5+
}
6+
}

tests/neg/alpha-override/C_2.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import annotation.alpha
2+
class C extends B_1 { // error: Name clash between defined and inherited member
3+
@alpha("bar") def foo(): Int = 3
4+
}

tests/neg/alpha-override1/B_1.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Java
2+
public class B_1 {
3+
public int bar() {
4+
return 2;
5+
}
6+
}

tests/neg/alpha-override1/D_2.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import annotation.alpha
2+
class D extends B_1 {
3+
@alpha("bar") def foo(): Int = 3
4+
}
5+
class E extends B_1 {
6+
@alpha("baz") override def bar(): Int = 3 // error: cannot have an @alpha annotation since external names would be different
7+
}

tests/neg/alpha.scala

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import annotation.alpha
2+
3+
4+
abstract class Alpha[T] {
5+
6+
def foo() = 1
7+
8+
@alpha("bar") def foo(x: T): T
9+
10+
@alpha("append") def ++ (xs: Alpha[T]): Alpha[T] = this
11+
12+
}
13+
14+
class Beta extends Alpha[String] {
15+
16+
@alpha("foo1") override def foo() = 1 // error
17+
18+
@alpha("baz") def foo(x: String): String = x ++ x // error
19+
20+
override def ++ (xs: Alpha[String]): Alpha[String] = this // error
21+
22+
}

tests/neg/doubleDefinition-late.scala

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
2+
import annotation.alpha
3+
4+
class Gamma {
5+
6+
val v = 1
7+
@alpha("v") val w = 2 // error: double definition
8+
9+
}
10+
// Error when overloading polymorphic and non-polymorphic methods
11+
class Test19 {
12+
def foo[T <: Int](x: T): T = x
13+
def foo(x: Int): Int = x // error
14+
}
15+
16+
// Error when overloading polymorphic methods
17+
class Test20 {
18+
def foo[T <: Int](x: T): T = x
19+
def foo[S <: Int, T <: Int](x: S): T = ??? // error
20+
}

tests/neg/doubleDefinition.check

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -122,17 +122,3 @@
122122
| Double definition:
123123
| var foo: Int in class Test16 at line 115 and
124124
| def foo: => Int in class Test16 at line 116
125-
-- [E120] Duplicate Symbol Error: tests/neg/doubleDefinition.scala:138:6 -----------------------------------------------
126-
138 | def foo(x: Int): Int = x // error
127-
| ^
128-
| Double definition:
129-
| def foo: [T <: Int](x: T): T in class Test19 at line 137 and
130-
| def foo(x: Int): Int in class Test19 at line 138
131-
| have the same type after erasure.
132-
-- [E120] Duplicate Symbol Error: tests/neg/doubleDefinition.scala:144:6 -----------------------------------------------
133-
144 | def foo[S <: Int, T <: Int](x: S): T = ??? // error
134-
| ^
135-
| Double definition:
136-
| def foo: [T <: Int](x: T): T in class Test20 at line 143 and
137-
| def foo: [S <: Int, T <: Int](x: S): T in class Test20 at line 144
138-
| have the same type after erasure.

tests/neg/doubleDefinition.scala

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ class Test8d {
8282
def foo = 2 // error
8383
}
8484

85-
// test method and contructor argument clashing
85+
// test method and constructor argument clashing
8686

8787
class Test9(val foo: Int) {
8888
def foo: String // error
@@ -132,14 +132,3 @@ class Test18 {
132132
def foo(b: B) = 1
133133
}
134134

135-
// Error when overloading polymorphic and non-polymorphic methods
136-
class Test19 {
137-
def foo[T <: Int](x: T): T = x
138-
def foo(x: Int): Int = x // error
139-
}
140-
141-
// Error when overloading polymorphic methods
142-
class Test20 {
143-
def foo[T <: Int](x: T): T = x
144-
def foo[S <: Int, T <: Int](x: S): T = ??? // error
145-
}

tests/pos/alpha-override/A_1.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Scala
2+
import annotation.alpha
3+
class A_1 {
4+
@alpha("bar") def foo(): Int = 1
5+
}

tests/pos/alpha-override/B_2.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Java
2+
public class B_2 extends A_1 {
3+
@Override
4+
public int bar() {
5+
return 2;
6+
}
7+
}

tests/pos/alpha.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import annotation.alpha
2+
3+
object Test {
4+
5+
def foo() = 1
6+
7+
@alpha("bar") def foo(x: Int) = 2
8+
}

0 commit comments

Comments
 (0)