Skip to content

Commit c4604fa

Browse files
committed
Implement @Alpha annotation
1 parent 8144f98 commit c4604fa

19 files changed

+182
-35
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,8 @@ object Denotations {
734734
case _ => Signature.NotAMethod
735735
}
736736

737+
def erasedName(implicit ctx: Context): Name = symbol.erasedName
738+
737739
def derivedSingleDenotation(symbol: Symbol, info: Type)(implicit ctx: Context): SingleDenotation =
738740
if ((symbol eq this.symbol) && (info eq this.info)) this
739741
else newLikeThis(symbol, info)

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

Lines changed: 20 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,24 @@ object SymDenotations {
442444
/** `fullName` where `.' is the separator character */
443445
def fullName(implicit ctx: Context): Name = fullNameSeparated(QualifiedName)
444446

447+
private var myErasedName: Name = null
448+
449+
final override def erasedName(implicit ctx: Context): Name = {
450+
if (myErasedName == null) {
451+
myErasedName = name
452+
if (isTerm)
453+
getAnnotation(defn.AlphaAnnot) match {
454+
case Some(ann) =>
455+
ann.arguments match {
456+
case Literal(Constant(str: String)) :: Nil => myErasedName = str.toTermName
457+
case _ =>
458+
}
459+
case _ =>
460+
}
461+
}
462+
myErasedName
463+
}
464+
445465
// ----- Tests -------------------------------------------------
446466

447467
/** 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: 7 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,12 @@ 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+
if (sym1.name != sym2.name &&
100+
!(sym1.name.is(SuperAccessorName) && sym2.name.is(SuperAccessorName))
101+
// super-accessors start as private, and their expanded name can clash after
102+
// erasure. TODO: Verify that this is OK.
103+
||
104+
!info1.matchesLoosely(info2) && !bothPolyApply)
99105
ctx.error(DoubleDefinition(sym1, sym2, root), root.sourcePos)
100106
}
101107
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/typer/Checking.scala

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -864,7 +864,7 @@ trait Checking {
864864
def javaFieldMethodPair =
865865
decl.is(JavaDefined) && other.is(JavaDefined) &&
866866
decl.is(Method) != other.is(Method)
867-
if ((decl.signature.clashes(other.signature) || decl.matches(other)) && !javaFieldMethodPair) {
867+
if (decl.matches(other) && !javaFieldMethodPair) {
868868
def doubleDefError(decl: Symbol, other: Symbol): Unit =
869869
if (!decl.info.isErroneous && !other.info.isErroneous)
870870
ctx.error(DoubleDefinition(decl, other, cls), decl.sourcePos)
@@ -1124,6 +1124,21 @@ trait Checking {
11241124
case _ =>
11251125
}
11261126
}
1127+
1128+
/** Check that symbol's external name does not clash with symbols defined in the same scope */
1129+
def checkNoAlphaConflict(stats: List[Tree])(implicit ctx: Context): Unit = {
1130+
var seen = Set[Name]()
1131+
for (stat <- stats) {
1132+
val sym = stat.symbol
1133+
val ename = sym.erasedName
1134+
if (ename != sym.name) {
1135+
val preExisting = ctx.effectiveScope.lookup(ename)
1136+
if (preExisting.exists || seen.contains(ename))
1137+
ctx.error(em"@alpha annotation ${'"'}$ename${'"'} clashes with other definition is same scope", stat.sourcePos)
1138+
seen += ename
1139+
}
1140+
}
1141+
}
11271142
}
11281143

11291144
trait ReChecking extends Checking {
@@ -1144,7 +1159,7 @@ trait NoChecking extends ReChecking {
11441159
override def checkImplicitConversionUseOK(sym: Symbol, posd: Positioned)(implicit ctx: Context): Unit = ()
11451160
override def checkFeasibleParent(tp: Type, pos: SourcePosition, where: => String = "")(implicit ctx: Context): Type = tp
11461161
override def checkInlineConformant(tree: Tree, isFinal: Boolean, what: => String)(implicit ctx: Context): Unit = ()
1147-
override def checkNoDoubleDeclaration(cls: Symbol)(implicit ctx: Context): Unit = ()
1162+
override def checkNoAlphaConflict(stats: List[Tree])(implicit ctx: Context): Unit = ()
11481163
override def checkParentCall(call: Tree, caller: ClassSymbol)(implicit ctx: Context): Unit = ()
11491164
override def checkSimpleKinded(tpt: Tree)(implicit ctx: Context): Tree = tpt
11501165
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

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.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.scala

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

tests/run/alpha/Test_2.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
2+
package alpha;
3+
4+
public class Test_2 {
5+
6+
public static void main(String[] args) {
7+
Alpha<String> a = new Beta();
8+
assert a.foo() == 1;
9+
assert a.bar("a").equals("aa");
10+
Alpha<String> aa = a.append(a);
11+
}
12+
}

tests/run/alpha/Test_3.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2+
object Test {
3+
def main(args: Array[String]): Unit =
4+
alpha.Test_2.main(args)
5+
}

tests/run/alpha/alpha_1.scala

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package alpha
2+
import annotation.alpha
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("bar") override def foo(x: String) = x ++ x
17+
18+
@alpha("append") override def ++ (xs: Alpha[String]) = this
19+
20+
}

0 commit comments

Comments
 (0)