Skip to content

Commit d43a22f

Browse files
committed
Add Tuple and *: types
These types are injected as parents of Tuple1,...,Tuple22, but are eliminated again at erasure. To support them properly before full dotty bootstrap we needed a @`rewrite` annotation that erases the annotated method (unlike `@forceInline`, which generates code).
1 parent 0480620 commit d43a22f

File tree

8 files changed

+87
-22
lines changed

8 files changed

+87
-22
lines changed

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

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -703,6 +703,12 @@ class Definitions {
703703

704704
lazy val XMLTopScopeModuleRef = ctx.requiredModuleRef("scala.xml.TopScope")
705705

706+
lazy val TupleTypeRef = ctx.requiredClassRef("scala.Tuple")
707+
def TupleClass(implicit ctx: Context) = TupleTypeRef.symbol.asClass
708+
709+
lazy val PairType = ctx.requiredClassRef("scala.*:")
710+
def PairClass(implicit ctx: Context) = PairType.symbol.asClass
711+
706712
// Annotation base classes
707713
lazy val AnnotationType = ctx.requiredClassRef("scala.annotation.Annotation")
708714
def AnnotationClass(implicit ctx: Context) = AnnotationType.symbol.asClass
@@ -734,6 +740,8 @@ class Definitions {
734740
def ImplicitNotFoundAnnot(implicit ctx: Context) = ImplicitNotFoundAnnotType.symbol.asClass
735741
lazy val ForceInlineAnnotType = ctx.requiredClassRef("scala.forceInline")
736742
def ForceInlineAnnot(implicit ctx: Context) = ForceInlineAnnotType.symbol.asClass
743+
lazy val RewriteAnnotType = ctx.requiredClassRef("scala.rewrite")
744+
def RewriteAnnot(implicit ctx: Context) = RewriteAnnotType.symbol.asClass
737745
lazy val TransparentParamAnnotType = ctx.requiredClassRef("scala.annotation.internal.TransparentParam")
738746
def TransparentParamAnnot(implicit ctx: Context) = TransparentParamAnnotType.symbol.asClass
739747
lazy val InvariantBetweenAnnotType = ctx.requiredClassRef("scala.annotation.internal.InvariantBetween")
@@ -877,7 +885,7 @@ class Definitions {
877885
private lazy val ImplementedFunctionType = mkArityArray("scala.Function", MaxImplementedFunctionArity, 0)
878886
def FunctionClassPerRun = new PerRun[Array[Symbol]](implicit ctx => ImplementedFunctionType.map(_.symbol.asClass))
879887

880-
lazy val TupleType = mkArityArray("scala.Tuple", MaxTupleArity, 2)
888+
lazy val TupleType = mkArityArray("scala.Tuple", MaxTupleArity, 1)
881889

882890
def FunctionClass(n: Int, isImplicit: Boolean = false, isErased: Boolean = false)(implicit ctx: Context) =
883891
if (isImplicit && isErased)
@@ -898,8 +906,6 @@ class Definitions {
898906
if (n <= MaxImplementedFunctionArity && (!isImplicit || ctx.erasedTypes) && !isErased) ImplementedFunctionType(n)
899907
else FunctionClass(n, isImplicit, isErased).typeRef
900908

901-
private lazy val TupleTypes: Set[TypeRef] = TupleType.toSet
902-
903909
/** If `cls` is a class in the scala package, its name, otherwise EmptyTypeName */
904910
def scalaClassName(cls: Symbol)(implicit ctx: Context): TypeName =
905911
if (cls.isClass && cls.owner == ScalaPackageClass) cls.asClass.name else EmptyTypeName
@@ -1196,6 +1202,8 @@ class Definitions {
11961202
def isValueSubClass(sym1: Symbol, sym2: Symbol) =
11971203
valueTypeEnc(sym2.asClass.name) % valueTypeEnc(sym1.asClass.name) == 0
11981204

1205+
lazy val erasedToObject = Set[Symbol](AnyClass, AnyValClass, TupleClass, PairClass, SingletonClass)
1206+
11991207
// ----- Initialization ---------------------------------------------------
12001208

12011209
/** Lists core classes that don't have underlying bytecode, but are synthesized on-the-fly in every reflection universe */
@@ -1223,6 +1231,24 @@ class Definitions {
12231231

12241232
private[this] var isInitialized = false
12251233

1234+
def fixTupleCompleter(cls: ClassSymbol): Unit = cls.infoOrCompleter match {
1235+
case completer: LazyType =>
1236+
cls.info = new LazyType {
1237+
def syntheticParent(tparams: List[TypeSymbol]): Type =
1238+
if (tparams.isEmpty) TupleTypeRef
1239+
else (tparams :\ (UnitType: Type)) ((tparam, tail) => PairType.appliedTo(tparam.typeRef, tail))
1240+
override def complete(denot: SymDenotation)(implicit ctx: Context) = {
1241+
completer.complete(denot)
1242+
denot.info match {
1243+
case info: ClassInfo =>
1244+
denot.info = info.derivedClassInfo(
1245+
classParents = info.classParents :+ syntheticParent(cls.typeParams))
1246+
}
1247+
}
1248+
}
1249+
case _ =>
1250+
}
1251+
12261252
def init()(implicit ctx: Context) = {
12271253
this.ctx = ctx
12281254
if (!isInitialized) {
@@ -1240,6 +1266,10 @@ class Definitions {
12401266
// force initialization of every symbol that is synthesized or hijacked by the compiler
12411267
val forced = syntheticCoreClasses ++ syntheticCoreMethods ++ ScalaValueClasses()
12421268

1269+
fixTupleCompleter(UnitClass)
1270+
for (i <- 1 to MaxTupleArity)
1271+
fixTupleCompleter(TupleType(i).symbol.asClass)
1272+
12431273
isInitialized = true
12441274
}
12451275
}

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

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ object TypeErasure {
4444
case tp: TypeRef =>
4545
val sym = tp.symbol
4646
sym.isClass &&
47-
sym != defn.AnyClass && sym != defn.ArrayClass &&
47+
sym != defn.ArrayClass &&
48+
!defn.erasedToObject.contains(sym) &&
4849
!defn.isSyntheticFunctionClass(sym)
4950
case _: TermRef =>
5051
true
@@ -279,10 +280,8 @@ object TypeErasure {
279280

280281
// Pick the last minimum to prioritise classes over traits
281282
minimums.lastOption match {
282-
case Some(lub) if lub != defn.AnyClass && lub != defn.AnyValClass =>
283-
lub.typeRef
284-
case _ => // Any/AnyVal only exist before erasure
285-
defn.ObjectType
283+
case Some(lub) => valueErasure(lub.typeRef)
284+
case _ => defn.ObjectType
286285
}
287286
}
288287
}
@@ -353,7 +352,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
353352
* - otherwise, if T is a type parameter coming from Java, []Object
354353
* - otherwise, Object
355354
* - For a term ref p.x, the type <noprefix> # x.
356-
* - For a typeref scala.Any, scala.AnyVal or scala.Singleton: |java.lang.Object|
355+
* - For a typeref scala.Any, scala.AnyVal, scala.Singleton, scala.Tuple, or scala.*: : |java.lang.Object|
357356
* - For a typeref scala.Unit, |scala.runtime.BoxedUnit|.
358357
* - For a typeref scala.FunctionN, where N > MaxImplementedFunctionArity, scala.FunctionXXL
359358
* - For a typeref scala.ImplicitFunctionN, | scala.FunctionN |
@@ -499,7 +498,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
499498

500499
private def normalizeClass(cls: ClassSymbol)(implicit ctx: Context): ClassSymbol = {
501500
if (cls.owner == defn.ScalaPackageClass) {
502-
if (cls == defn.AnyClass || cls == defn.AnyValClass || cls == defn.SingletonClass)
501+
if (defn.erasedToObject.contains(cls))
503502
return defn.ObjectClass
504503
if (cls == defn.UnitClass)
505504
return defn.BoxedUnitClass

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

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -135,12 +135,15 @@ class Erasure extends Phase with DenotTransformer {
135135
}
136136
}
137137

138-
def assertErased(tp: Type, tree: tpd.Tree = tpd.EmptyTree)(implicit ctx: Context): Unit =
139-
if (tp.typeSymbol == defn.ArrayClass &&
140-
ctx.compilationUnit.source.file.name == "Array.scala") {} // ok
141-
else
142-
assert(isErasedType(tp),
138+
def assertErased(tp: Type, tree: tpd.Tree = tpd.EmptyTree)(implicit ctx: Context): Unit = {
139+
def isAllowed(cls: Symbol, sourceName: String) =
140+
tp.typeSymbol == cls && ctx.compilationUnit.source.file.name == sourceName
141+
assert(isErasedType(tp) ||
142+
isAllowed(defn.ArrayClass, "Array.scala") ||
143+
isAllowed(defn.TupleClass, "Tuple.scala") ||
144+
isAllowed(defn.PairClass, "Tuple.scala"),
143145
i"The type $tp - ${tp.toString} of class ${tp.getClass} of tree $tree : ${tree.tpe} / ${tree.getClass} is illegal after erasure, phase = ${ctx.phase.prev}")
146+
}
144147
}
145148

146149
object Erasure {
@@ -376,7 +379,7 @@ object Erasure {
376379
* on selections `e.m`, where `OT` is the type of the owner of `m` and `ET`
377380
* is the erased type of the selection's original qualifier expression.
378381
*
379-
* e.m1 -> e.m2 if `m1` is a member of Any or AnyVal and `m2` is
382+
* e.m1 -> e.m2 if `m1` is a member of a class that erases to object and `m2` is
380383
* the same-named member in Object.
381384
* e.m -> box(e).m if `e` is primitive and `m` is a member or a reference class
382385
* or `e` has an erased value class type.
@@ -396,7 +399,7 @@ object Erasure {
396399

397400
def mapOwner(sym: Symbol): Symbol = {
398401
def recur(owner: Symbol): Symbol =
399-
if ((owner eq defn.AnyClass) || (owner eq defn.AnyValClass)) {
402+
if (defn.erasedToObject.contains(owner)) {
400403
assert(sym.isConstructor, s"${sym.showLocated}")
401404
defn.ObjectClass
402405
} else if (defn.isSyntheticFunctionClass(owner))

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -753,7 +753,8 @@ class Namer { typer: Typer =>
753753
val cls = typedAheadAnnotationClass(annotTree)(annotCtx)
754754
val ann = Annotation.deferred(cls, implicit ctx => typedAnnotation(annotTree))
755755
sym.addAnnotation(ann)
756-
if (cls == defn.ForceInlineAnnot && sym.is(Method, butNot = Accessor))
756+
if ((cls == defn.ForceInlineAnnot || cls == defn.RewriteAnnot) &&
757+
sym.is(Method, butNot = Accessor))
757758
sym.setFlag(Rewrite)
758759
}
759760
case _ =>

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1518,9 +1518,10 @@ class Typer extends Namer
15181518
def typedParent(tree: untpd.Tree): Tree = {
15191519
var result = if (tree.isType) typedType(tree)(superCtx) else typedExpr(tree)(superCtx)
15201520
val psym = result.tpe.dealias.typeSymbol
1521-
if (seenParents.contains(psym) && !cls.isRefinementClass)
1522-
ctx.error(i"$psym is extended twice", tree.pos)
1523-
seenParents += psym
1521+
if (seenParents.contains(psym) && !cls.isRefinementClass) {
1522+
if (!ctx.isAfterTyper) ctx.error(i"$psym is extended twice", tree.pos)
1523+
}
1524+
else seenParents += psym
15241525
if (tree.isType) {
15251526
if (psym.is(Trait) && !cls.is(Trait) && !cls.superClass.isSubClass(psym))
15261527
result = maybeCall(result, psym, psym.primaryConstructor.info)

library/src/scala/Tuple.scala

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package scala
2+
import annotation.showAsInfix
3+
4+
sealed trait Tuple extends Any
5+
6+
@showAsInfix
7+
sealed class *:[+H, +T <: Tuple] extends Tuple {
8+
@`rewrite` def head: H = ???
9+
@`rewrite` def tail: T = ???
10+
}
11+
12+
object *: {
13+
@`rewrite` def unapply[H, T <: Tuple](x: H *: T) = Some((x.head, x.tail))
14+
}

library/src/scala/forceInline.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package scala
22

3-
/** An annotation on methods that is equivalent to Dotty `inline` modifier.
3+
/** An annotation on methods that is equivalent to Dotty `rewrite` modifier,
4+
* except that it does not imply `erased`.
45
*
56
* The annotation should be used instead of the `inline` modifier in code
67
* that needs to cross compile between Scala 2 and Dotty.

library/src/scala/rewrite.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package scala
2+
3+
/** An annotation on methods that is equivalent to Dotty `rewrite` modifier.
4+
*
5+
* The annotation should be used instead of the `rewrite` modifier in code
6+
* that needs to cross compile between Scala 2 and Dotty.
7+
*
8+
* Note that Scala 2 ignores the `@rewrite` annotation, and one must use
9+
* both the `@inline` and `@rewrite` annotation to inline across the
10+
* two compilers. E.g.
11+
*
12+
* ```scala
13+
* @inline @`rewrite` def foo = ...
14+
* ```
15+
*/
16+
class `rewrite` extends scala.annotation.StaticAnnotation

0 commit comments

Comments
 (0)