diff options
Diffstat (limited to 'src/dotty')
43 files changed, 832 insertions, 342 deletions
diff --git a/src/dotty/runtime/LazyVals.scala b/src/dotty/runtime/LazyVals.scala index 4130d4d60..2aa45e6fd 100644 --- a/src/dotty/runtime/LazyVals.scala +++ b/src/dotty/runtime/LazyVals.scala @@ -11,8 +11,8 @@ object LazyVals { final val BITS_PER_LAZY_VAL = 2 final val LAZY_VAL_MASK = 3 - @inline def STATE(cur: Long, ord: Long) = (cur >> (ord * BITS_PER_LAZY_VAL)) & LAZY_VAL_MASK - @inline def CAS(t: Object, offset: Long, e: Long, v: Long, ord: Int) = { + @inline def STATE(cur: Long, ord: Int) = (cur >> (ord * BITS_PER_LAZY_VAL)) & LAZY_VAL_MASK + @inline def CAS(t: Object, offset: Long, e: Long, v: Int, ord: Int) = { val mask = ~(LAZY_VAL_MASK << ord * BITS_PER_LAZY_VAL) val n = (e & mask) | (v << (ord * BITS_PER_LAZY_VAL)) compareAndSet(t, offset, e, n) @@ -65,7 +65,7 @@ object LazyVals { monitors(id) } - @inline def getOffset(obj: Object, name: String) = unsafe.objectFieldOffset(obj.getClass.getDeclaredField(name)) + @inline def getOffset(clz: Class[_], name: String) = unsafe.objectFieldOffset(clz.getDeclaredField(name)) object Names { final val state = "STATE" diff --git a/src/dotty/tools/backend/jvm/DottyBackendInterface.scala b/src/dotty/tools/backend/jvm/DottyBackendInterface.scala index 009b7fb2b..2e904cc23 100644 --- a/src/dotty/tools/backend/jvm/DottyBackendInterface.scala +++ b/src/dotty/tools/backend/jvm/DottyBackendInterface.scala @@ -589,7 +589,7 @@ class DottyBackendInterface()(implicit ctx: Context) extends BackendInterface{ def isDeferred: Boolean = sym is Flags.Deferred def isPrivate: Boolean = sym is Flags.Private def getsJavaFinalFlag: Boolean = - isFinal && !toDenot(sym).isClassConstructor && !(sym is Flags.Mutable) && !(sym.enclosingClass is Flags.JavaInterface) + isFinal && !toDenot(sym).isClassConstructor && !(sym is Flags.Mutable) && !(sym.enclosingClass is Flags.Trait) def getsJavaPrivateFlag: Boolean = isPrivate //|| (sym.isPrimaryConstructor && sym.owner.isTopLevelModuleClass) diff --git a/src/dotty/tools/dotc/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala index 6787e3a2d..44e7ac450 100644 --- a/src/dotty/tools/dotc/Compiler.scala +++ b/src/dotty/tools/dotc/Compiler.scala @@ -49,20 +49,22 @@ class Compiler { List(new PatternMatcher, new ExplicitOuter, new Splitter), - List(new LazyVals, - new SeqLiterals, + List(new SeqLiterals, new InterceptedMethods, new Literalize, new Getters, new ElimByName, new ResolveSuper), List(new Erasure), - List(new Mixin, + List(new ElimErasedValueType, + new VCInline, + new Mixin, + new LazyVals, new Memoize, - new CapturedVars, + new CapturedVars, // capturedVars has a transformUnit: no phases should introduce local mutable vars here new Constructors, new FunctionalInterfaces), - List(new LambdaLift, + List(new LambdaLift, // in this mini-phase block scopes are incorrect. No phases that rely on scopes should be here new Flatten, new RestoreScopes), List(/*new PrivateToStatic,*/ new CollectEntryPoints, new LabelDefs, new ElimWildcardIdents, new TraitConstructors), diff --git a/src/dotty/tools/dotc/Run.scala b/src/dotty/tools/dotc/Run.scala index a008214c9..c99f5efb9 100644 --- a/src/dotty/tools/dotc/Run.scala +++ b/src/dotty/tools/dotc/Run.scala @@ -10,6 +10,7 @@ import reporting.Reporter import transform.TreeChecker import java.io.{BufferedWriter, OutputStreamWriter} import scala.reflect.io.VirtualFile +import scala.util.control.NonFatal class Run(comp: Compiler)(implicit ctx: Context) { @@ -27,9 +28,13 @@ class Run(comp: Compiler)(implicit ctx: Context) { } } - def compile(fileNames: List[String]): Unit = { + def compile(fileNames: List[String]): Unit = try { val sources = fileNames map getSource compileSources(sources) + } catch { + case NonFatal(ex) => + println(s"exception occurred while compiling $units%, %") + throw ex } /** TODO: There's a fundamental design problem here: We assmble phases using `squash` diff --git a/src/dotty/tools/dotc/ast/Desugar.scala b/src/dotty/tools/dotc/ast/Desugar.scala index 0c13d1ecc..f7904abc0 100644 --- a/src/dotty/tools/dotc/ast/Desugar.scala +++ b/src/dotty/tools/dotc/ast/Desugar.scala @@ -289,8 +289,13 @@ object desugar { val caseParams = constrVparamss.head.toArray val productElemMeths = for (i <- 0 until arity) yield syntheticProperty(nme.selectorName(i), Select(This(EmptyTypeName), caseParams(i).name)) + def isRepeated(tree: Tree): Boolean = tree match { + case PostfixOp(_, nme.raw.STAR) => true + case ByNameTypeTree(tree1) => isRepeated(tree1) + case _ => false + } val hasRepeatedParam = constrVparamss.exists(_.exists { - case ValDef(_, PostfixOp(_, nme.raw.STAR), _) => true + case ValDef(_, tpt, _) => isRepeated(tpt) case _ => false }) val copyMeths = diff --git a/src/dotty/tools/dotc/ast/TreeInfo.scala b/src/dotty/tools/dotc/ast/TreeInfo.scala index c6d7e10f7..6d1c04978 100644 --- a/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -160,11 +160,12 @@ trait TreeInfo[T >: Untyped <: Type] { self: Trees.Instance[T] => case _ => Nil } - /** Is tpt a vararg type of the form T* ? */ - def isRepeatedParamType(tpt: Tree)(implicit ctx: Context) = tpt match { + /** Is tpt a vararg type of the form T* or => T*? */ + def isRepeatedParamType(tpt: Tree)(implicit ctx: Context): Boolean = tpt match { + case ByNameTypeTree(tpt1) => isRepeatedParamType(tpt1) case tpt: TypeTree => tpt.typeOpt.isRepeatedParam - case AppliedTypeTree(Select(_, tpnme.REPEATED_PARAM_CLASS), _) => true - case _ => false + case AppliedTypeTree(Select(_, tpnme.REPEATED_PARAM_CLASS), _) => true + case _ => false } /** Is name a left-associative operator? */ diff --git a/src/dotty/tools/dotc/ast/tpd.scala b/src/dotty/tools/dotc/ast/tpd.scala index 0a1611b61..de0ef3344 100644 --- a/src/dotty/tools/dotc/ast/tpd.scala +++ b/src/dotty/tools/dotc/ast/tpd.scala @@ -2,6 +2,7 @@ package dotty.tools package dotc package ast +import dotty.tools.dotc.transform.ExplicitOuter import dotty.tools.dotc.typer.ProtoTypes.FunProtoTyped import transform.SymUtils._ import core._ @@ -243,6 +244,17 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { ta.assignType(untpd.TypeDef(cls.name, impl), cls) } + // { <label> def while$(): Unit = if (cond) { body; while$() } ; while$() } + def WhileDo(owner: Symbol, cond: Tree, body: List[Tree])(implicit ctx: Context): Tree = { + val sym = ctx.newSymbol(owner, nme.WHILE_PREFIX, Flags.Label | Flags.Synthetic, + MethodType(Nil, defn.UnitType), coord = cond.pos) + + val call = Apply(ref(sym), Nil) + val rhs = If(cond, Block(body, call), unitLiteral) + Block(List(DefDef(sym, rhs)), call) + } + + def Import(expr: Tree, selectors: List[untpd.Tree])(implicit ctx: Context): Import = ta.assignType(untpd.Import(expr, selectors), ctx.newImportSymbol(ctx.owner, expr)) @@ -288,7 +300,16 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { if (tp.isType) TypeTree(tp) else if (prefixIsElidable(tp)) Ident(tp) else tp.prefix match { - case pre: SingletonType => singleton(pre).select(tp) + case pre: SingletonType => + val prefix = + singleton(pre) match { + case t: This if ctx.erasedTypes && !(t.symbol == ctx.owner.enclosingClass || t.symbol.isStaticOwner) => + // after erasure outer paths should be respected + new ExplicitOuter.OuterOps(ctx).path(t.tpe.widen.classSymbol) + case t => + t + } + prefix.select(tp) case pre => SelectFromTypeTree(TypeTree(pre), tp) } // no checks necessary @@ -563,7 +584,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { loop(from.owner, from :: froms, to :: tos) else { //println(i"change owner ${from :: froms}%, % ==> $tos of $tree") - new TreeTypeMap(oldOwners = from :: froms, newOwners = tos).apply(tree) + new TreeTypeMap(oldOwners = from :: froms, newOwners = tos)(ctx.withMode(Mode.FutureDefsOK)).apply(tree) } } loop(from, Nil, to :: Nil) @@ -578,8 +599,11 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { def traverse(tree: Tree)(implicit ctx: Context) = tree match { case tree: DefTree => val sym = tree.symbol - if (sym.denot(ctx.withPhase(trans)).owner == from) - sym.copySymDenotation(owner = to).installAfter(trans) + if (sym.denot(ctx.withPhase(trans)).owner == from) { + val d = sym.copySymDenotation(owner = to) + d.installAfter(trans) + d.transformAfter(trans, d => if (d.owner eq from) d.copySymDenotation(owner = to) else d) + } if (sym.isWeakOwner) traverseChildren(tree) case _ => traverseChildren(tree) diff --git a/src/dotty/tools/dotc/core/Denotations.scala b/src/dotty/tools/dotc/core/Denotations.scala index a30cff714..6502c4a40 100644 --- a/src/dotty/tools/dotc/core/Denotations.scala +++ b/src/dotty/tools/dotc/core/Denotations.scala @@ -620,14 +620,9 @@ object Denotations { // println(s"installing $this after $phase/${phase.id}, valid = ${current.validFor}") // printPeriods(current) this.validFor = Period(ctx.runId, targetId, current.validFor.lastPhaseId) - if (current.validFor.firstPhaseId == targetId) { - // replace current with this denotation - var prev = current - while (prev.nextInRun ne current) prev = prev.nextInRun - prev.nextInRun = this - this.nextInRun = current.nextInRun - current.validFor = Nowhere - } else { + if (current.validFor.firstPhaseId == targetId) + replaceDenotation(current) + else { // insert this denotation after current current.validFor = Period(ctx.runId, current.validFor.firstPhaseId, targetId - 1) this.nextInRun = current.nextInRun @@ -637,6 +632,33 @@ object Denotations { } } + /** Apply a transformation `f` to all denotations in this group that start at or after + * given phase. Denotations are replaced while keeping the same validity periods. + */ + protected def transformAfter(phase: DenotTransformer, f: SymDenotation => SymDenotation)(implicit ctx: Context): Unit = { + var current = symbol.current + while (current.validFor.firstPhaseId < phase.id && (current.nextInRun.validFor.code > current.validFor.code)) + current = current.nextInRun + var hasNext = true + while ((current.validFor.firstPhaseId >= phase.id) && hasNext) { + val current1: SingleDenotation = f(current.asSymDenotation) + if (current1 ne current) { + current1.validFor = current.validFor + current1.replaceDenotation(current) + } + hasNext = current1.nextInRun.validFor.code > current1.validFor.code + current = current1.nextInRun + } + } + + private def replaceDenotation(current: SingleDenotation): Unit = { + var prev = current + while (prev.nextInRun ne current) prev = prev.nextInRun + prev.nextInRun = this + this.nextInRun = current.nextInRun + current.validFor = Nowhere + } + def staleSymbolError(implicit ctx: Context) = { def ownerMsg = this match { case denot: SymDenotation => s"in ${denot.owner}" diff --git a/src/dotty/tools/dotc/core/Phases.scala b/src/dotty/tools/dotc/core/Phases.scala index 96066db5e..406a3457a 100644 --- a/src/dotty/tools/dotc/core/Phases.scala +++ b/src/dotty/tools/dotc/core/Phases.scala @@ -232,6 +232,7 @@ object Phases { private val typerCache = new PhaseCache(classOf[FrontEnd]) private val refChecksCache = new PhaseCache(classOf[RefChecks]) + private val extensionMethodsCache = new PhaseCache(classOf[ExtensionMethods]) private val erasureCache = new PhaseCache(classOf[Erasure]) private val patmatCache = new PhaseCache(classOf[PatternMatcher]) private val flattenCache = new PhaseCache(classOf[Flatten]) @@ -241,6 +242,7 @@ object Phases { def typerPhase = typerCache.phase def refchecksPhase = refChecksCache.phase + def extensionMethodsPhase = extensionMethodsCache.phase def erasurePhase = erasureCache.phase def patmatPhase = patmatCache.phase def flattenPhase = flattenCache.phase diff --git a/src/dotty/tools/dotc/core/StdNames.scala b/src/dotty/tools/dotc/core/StdNames.scala index 829ff8b8f..74a121b47 100644 --- a/src/dotty/tools/dotc/core/StdNames.scala +++ b/src/dotty/tools/dotc/core/StdNames.scala @@ -226,6 +226,7 @@ object StdNames { val ANYname: N = "<anyname>" val CONSTRUCTOR: N = Names.CONSTRUCTOR.toString val DEFAULT_CASE: N = "defaultCase$" + val EVT2U: N = "evt2u$" val EQEQ_LOCAL_VAR: N = "eqEqTemp$" val FAKE_LOCAL_THIS: N = "this$" val IMPLCLASS_CONSTRUCTOR: N = "$init$" @@ -257,6 +258,7 @@ object StdNames { val SKOLEM: N = "<skolem>" val SPECIALIZED_INSTANCE: N = "specInstance$" val THIS: N = "_$this" + val U2EVT: N = "u2evt$" final val Nil: N = "Nil" final val Predef: N = "Predef" diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index 83499ca7b..14be606a1 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -1043,6 +1043,12 @@ object SymDenotations { /** Install this denotation as the result of the given denotation transformer. */ override def installAfter(phase: DenotTransformer)(implicit ctx: Context): Unit = super.installAfter(phase) + + /** Apply a transformation `f` to all denotations in this group that start at or after + * given phase. Denotations are replaced while keeping the same validity periods. + */ + override def transformAfter(phase: DenotTransformer, f: SymDenotation => SymDenotation)(implicit ctx: Context): Unit = + super.transformAfter(phase, f) } /** The contents of a class definition during a period @@ -1441,10 +1447,16 @@ object SymDenotations { def inCache(tp: Type) = baseTypeRefCache.containsKey(tp) - /** Can't cache types containing type variables which are uninstantiated - * or whose instances can change, depending on typerstate. + /** We cannot cache: + * - type variables which are uninstantiated or whose instances can + * change, depending on typerstate. + * - types where the underlying type is an ErasedValueType, because + * this underlying type will change after ElimErasedValueType, + * and this changes subtyping relations. As a shortcut, we do not + * cache ErasedValueType at all. */ def isCachable(tp: Type): Boolean = tp match { + case _: TypeErasure.ErasedValueType => false case tp: TypeVar => tp.inst.exists && inCache(tp.inst) case tp: TypeProxy => inCache(tp.underlying) case tp: AndOrType => inCache(tp.tp1) && inCache(tp.tp2) diff --git a/src/dotty/tools/dotc/core/Symbols.scala b/src/dotty/tools/dotc/core/Symbols.scala index 9f18e723c..2b91efbcd 100644 --- a/src/dotty/tools/dotc/core/Symbols.scala +++ b/src/dotty/tools/dotc/core/Symbols.scala @@ -434,14 +434,6 @@ object Symbols { /** If this symbol satisfies predicate `p` this symbol, otherwise `NoSymbol` */ def filter(p: Symbol => Boolean): Symbol = if (p(this)) this else NoSymbol - /** Is this symbol a user-defined value class? */ - final def isDerivedValueClass(implicit ctx: Context): Boolean = { - this.derivesFrom(defn.AnyValClass)(ctx.withPhase(denot.validFor.firstPhaseId)) - // Simulate ValueClasses.isDerivedValueClass - false // will migrate to ValueClasses.isDerivedValueClass; - // unsupported value class code will continue to use this stub while it exists - } - /** The current name of this symbol */ final def name(implicit ctx: Context): ThisName = denot.name.asInstanceOf[ThisName] diff --git a/src/dotty/tools/dotc/core/TypeApplications.scala b/src/dotty/tools/dotc/core/TypeApplications.scala index 5325189e1..a5e24c5ff 100644 --- a/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/src/dotty/tools/dotc/core/TypeApplications.scala @@ -282,12 +282,17 @@ class TypeApplications(val self: Type) extends AnyVal { /** Translate a type of the form From[T] to To[T], keep other types as they are. * `from` and `to` must be static classes, both with one type parameter, and the same variance. + * Do the same for by name types => From[T] and => To[T] */ - def translateParameterized(from: ClassSymbol, to: ClassSymbol)(implicit ctx: Context): Type = - if (self.derivesFrom(from)) - if (ctx.erasedTypes) to.typeRef - else RefinedType(to.typeRef, to.typeParams.head.name, self.member(from.typeParams.head.name).info) - else self + def translateParameterized(from: ClassSymbol, to: ClassSymbol)(implicit ctx: Context): Type = self match { + case self @ ExprType(tp) => + self.derivedExprType(tp.translateParameterized(from, to)) + case _ => + if (self.derivesFrom(from)) + if (ctx.erasedTypes) to.typeRef + else RefinedType(to.typeRef, to.typeParams.head.name, self.member(from.typeParams.head.name).info) + else self + } /** If this is repeated parameter type, its underlying Seq type, * or, if isJava is true, Array type, else the type itself. diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index a59a64a91..18f9f08bb 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -229,6 +229,14 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi compareSuper case AndType(tp21, tp22) => isSubType(tp1, tp21) && isSubType(tp1, tp22) + case TypeErasure.ErasedValueType(cls2, underlying2) => + def compareErasedValueType = tp1 match { + case TypeErasure.ErasedValueType(cls1, underlying1) => + (cls1 eq cls2) && isSameType(underlying1, underlying2) + case _ => + secondTry(tp1, tp2) + } + compareErasedValueType case ErrorType => true case _ => diff --git a/src/dotty/tools/dotc/core/TypeErasure.scala b/src/dotty/tools/dotc/core/TypeErasure.scala index 20cf816c2..e695e6721 100644 --- a/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/src/dotty/tools/dotc/core/TypeErasure.scala @@ -3,12 +3,15 @@ package dotc package core import Symbols._, Types._, Contexts._, Flags._, Names._, StdNames._, Decorators._, Flags.JavaDefined +import Uniques.unique import dotc.transform.ExplicitOuter._ +import dotc.transform.ValueClasses._ import typer.Mode import util.DotClass /** Erased types are: * + * ErasedValueType * TypeRef(prefix is ignored, denot is ClassDenotation) * TermRef(prefix is ignored, denot is SymDenotation) * JavaArrayType @@ -29,8 +32,12 @@ object TypeErasure { /** A predicate that tests whether a type is a legal erased type. Only asInstanceOf and * isInstanceOf may have types that do not satisfy the predicate. + * ErasedValueType is considered an erased type because it is valid after Erasure (it is + * eliminated by ElimErasedValueType). */ def isErasedType(tp: Type)(implicit ctx: Context): Boolean = tp match { + case _: ErasedValueType => + true case tp: TypeRef => tp.symbol.isClass && tp.symbol != defn.AnyClass case _: TermRef => @@ -51,13 +58,35 @@ object TypeErasure { false } - case class ErasedValueType(cls: ClassSymbol, underlying: Type) extends CachedGroundType { - override def computeHash = doHash(cls, underlying) + /** A type representing the semi-erasure of a derived value class, see SIP-15 + * where it's called "C$unboxed" for a class C. + * Derived value classes are erased to this type during Erasure (when + * semiEraseVCs = true) and subsequently erased to their underlying type + * during ElimErasedValueType. This type is outside the normal Scala class + * hierarchy: it is a subtype of no other type and is a supertype only of + * Nothing. This is because this type is only useful for type adaptation (see + * [[Erasure.Boxing#adaptToType]]). + * + * @param cls The value class symbol + * @param erasedUnderlying The erased type of the single field of the value class + */ + abstract case class ErasedValueType(cls: ClassSymbol, erasedUnderlying: Type) + extends CachedGroundType with ValueType { + override def computeHash = doHash(cls, erasedUnderlying) } - private def erasureIdx(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wildcardOK: Boolean) = + final class CachedErasedValueType(cls: ClassSymbol, erasedUnderlying: Type) + extends ErasedValueType(cls, erasedUnderlying) + + object ErasedValueType { + def apply(cls: ClassSymbol, erasedUnderlying: Type)(implicit ctx: Context) = { + unique(new CachedErasedValueType(cls, erasedUnderlying)) + } + } + + private def erasureIdx(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean, wildcardOK: Boolean) = (if (isJava) 1 else 0) + - (if (isSemi) 2 else 0) + + (if (semiEraseVCs) 2 else 0) + (if (isConstructor) 4 else 0) + (if (wildcardOK) 8 else 0) @@ -65,41 +94,32 @@ object TypeErasure { for { isJava <- List(false, true) - isSemi <- List(false, true) + semiEraseVCs <- List(false, true) isConstructor <- List(false, true) wildcardOK <- List(false, true) - } erasures(erasureIdx(isJava, isSemi, isConstructor, wildcardOK)) = - new TypeErasure(isJava, isSemi, isConstructor, wildcardOK) + } erasures(erasureIdx(isJava, semiEraseVCs, isConstructor, wildcardOK)) = + new TypeErasure(isJava, semiEraseVCs, isConstructor, wildcardOK) - /** Produces an erasure function. - * @param isJava Arguments should be treated the way Java does it - * @param isSemi Value classes are mapped in an intermediate step to - * ErasedValueClass types, instead of going directly to - * the erasure of the underlying type. - * @param isConstructor Argument forms part of the type of a constructor - * @param wildcardOK Wildcards are acceptable (true when using the erasure - * for computing a signature name). + /** Produces an erasure function. See the documentation of the class [[TypeErasure]] + * for a description of each parameter. */ - private def erasureFn(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wildcardOK: Boolean): TypeErasure = - erasures(erasureIdx(isJava, isSemi, isConstructor, wildcardOK)) - - private val scalaErasureFn = erasureFn(isJava = false, isSemi = false, isConstructor = false, wildcardOK = false) - private val scalaSigFn = erasureFn(isJava = false, isSemi = false, isConstructor = false, wildcardOK = true) - private val javaSigFn = erasureFn(isJava = true, isSemi = false, isConstructor = false, wildcardOK = true) - private val semiErasureFn = erasureFn(isJava = false, isSemi = true, isConstructor = false, wildcardOK = false) + private def erasureFn(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean, wildcardOK: Boolean): TypeErasure = + erasures(erasureIdx(isJava, semiEraseVCs, isConstructor, wildcardOK)) /** The current context with a phase no later than erasure */ private def erasureCtx(implicit ctx: Context) = if (ctx.erasedTypes) ctx.withPhase(ctx.erasurePhase).addMode(Mode.FutureDefsOK) else ctx - def erasure(tp: Type)(implicit ctx: Context): Type = scalaErasureFn(tp)(erasureCtx) - def semiErasure(tp: Type)(implicit ctx: Context): Type = semiErasureFn(tp)(erasureCtx) + def erasure(tp: Type, semiEraseVCs: Boolean = true)(implicit ctx: Context): Type = + erasureFn(isJava = false, semiEraseVCs, isConstructor = false, wildcardOK = false)(tp)(erasureCtx) + def sigName(tp: Type, isJava: Boolean)(implicit ctx: Context): TypeName = { val seqClass = if (isJava) defn.ArrayClass else defn.SeqClass val normTp = if (tp.isRepeatedParam) tp.translateParameterized(defn.RepeatedParamClass, seqClass) else tp - (if (isJava) javaSigFn else scalaSigFn).sigName(normTp)(erasureCtx) + val erase = erasureFn(isJava, semiEraseVCs = false, isConstructor = false, wildcardOK = true) + erase.sigName(normTp)(erasureCtx) } /** The erasure of a top-level reference. Differs from normal erasure in that @@ -117,29 +137,20 @@ object TypeErasure { erasure(tp) } - /** The erasure of a symbol's info. This is different of `erasure` in the way `ExprType`s are - * treated. `eraseInfo` maps them them to nullary method types, whereas `erasure` maps them - * to `Function0`. - */ - def eraseInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = - scalaErasureFn.eraseInfo(tp, sym)(erasureCtx) - - /** The erasure of a function result type. Differs from normal erasure in that - * Unit is kept instead of being mapped to BoxedUnit. - */ - def eraseResult(tp: Type)(implicit ctx: Context): Type = - scalaErasureFn.eraseResult(tp)(erasureCtx) - /** The symbol's erased info. This is the type's erasure, except for the following symbols: * * - For $asInstanceOf : [T]T * - For $isInstanceOf : [T]Boolean * - For all abstract types : = ? + * - For COMPANION_CLASS_METHOD : the erasure of their type with semiEraseVCs = false, + * this is needed to keep [[SymDenotation#companionClass]] + * working after erasure for value classes. * - For all other symbols : the semi-erasure of their types, with * isJava, isConstructor set according to symbol. */ def transformInfo(sym: Symbol, tp: Type)(implicit ctx: Context): Type = { - val erase = erasureFn(sym is JavaDefined, isSemi = true, sym.isConstructor, wildcardOK = false) + val semiEraseVCs = sym.name ne nme.COMPANION_CLASS_METHOD + val erase = erasureFn(sym is JavaDefined, semiEraseVCs, sym.isConstructor, wildcardOK = false) def eraseParamBounds(tp: PolyType): Type = tp.derivedPolyType( @@ -148,7 +159,7 @@ object TypeErasure { if (defn.isPolymorphicAfterErasure(sym)) eraseParamBounds(sym.info.asInstanceOf[PolyType]) else if (sym.isAbstractType) TypeAlias(WildcardType) else if (sym.isConstructor) outer.addParam(sym.owner.asClass, erase(tp)(erasureCtx)) - else eraseInfo(tp, sym)(erasureCtx) match { + else erase.eraseInfo(tp, sym)(erasureCtx) match { case einfo: MethodType if sym.isGetter && einfo.resultType.isRef(defn.UnitClass) => defn.BoxedUnitClass.typeRef case einfo => @@ -241,12 +252,15 @@ object TypeErasure { import TypeErasure._ /** - * This is used as the Scala erasure during the erasure phase itself - * It differs from normal erasure in that value classes are erased to ErasedValueTypes which - * are then later converted to the underlying parameter type in phase posterasure. - * + * @param isJava Arguments should be treated the way Java does it + * @param semiEraseVCs If true, value classes are semi-erased to ErasedValueType + * (they will be fully erased in [[ElimErasedValueType]]). + * If false, they are erased like normal classes. + * @param isConstructor Argument forms part of the type of a constructor + * @param wildcardOK Wildcards are acceptable (true when using the erasure + * for computing a signature name). */ -class TypeErasure(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wildcardOK: Boolean) extends DotClass { +class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean, wildcardOK: Boolean) extends DotClass { /** The erasure |T| of a type T. This is: * @@ -279,10 +293,12 @@ class TypeErasure(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wild * - For any other type, exception. */ private def apply(tp: Type)(implicit ctx: Context): Type = tp match { + case _: ErasedValueType => + tp case tp: TypeRef => val sym = tp.symbol if (!sym.isClass) this(tp.info) - else if (sym.isDerivedValueClass) eraseDerivedValueClassRef(tp) + else if (semiEraseVCs && isDerivedValueClass(sym)) eraseDerivedValueClassRef(tp) else eraseNormalClassRef(tp) case tp: RefinedType => val parent = tp.parent @@ -291,7 +307,9 @@ class TypeErasure(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wild case tp: TermRef => this(tp.widen) case tp: ThisType => - this(tp.cls.typeRef) + def thisTypeErasure(tpToErase: Type) = + erasureFn(isJava, semiEraseVCs = false, isConstructor, wildcardOK)(tpToErase) + thisTypeErasure(tp.cls.typeRef) case SuperType(thistpe, supertpe) => SuperType(this(thistpe), this(supertpe)) case ExprType(rt) => @@ -303,7 +321,8 @@ class TypeErasure(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wild case OrType(tp1, tp2) => ctx.typeComparer.orType(this(tp1), this(tp2), erased = true) case tp: MethodType => - val paramErasure = erasureFn(tp.isJava, isSemi, isConstructor, wildcardOK)(_) + def paramErasure(tpToErase: Type) = + erasureFn(tp.isJava, semiEraseVCs, isConstructor, wildcardOK)(tpToErase) val formals = tp.paramTypes.mapConserve(paramErasure) eraseResult(tp.resultType) match { case rt: MethodType => @@ -341,11 +360,17 @@ class TypeErasure(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wild private def eraseArray(tp: RefinedType)(implicit ctx: Context) = { val defn.ArrayType(elemtp) = tp + def arrayErasure(tpToErase: Type) = + erasureFn(isJava, semiEraseVCs = false, isConstructor, wildcardOK)(tpToErase) if (elemtp derivesFrom defn.NullClass) JavaArrayType(defn.ObjectType) else if (isUnboundedGeneric(elemtp)) defn.ObjectType - else JavaArrayType(this(elemtp)) + else JavaArrayType(arrayErasure(elemtp)) } + /** The erasure of a symbol's info. This is different from `apply` in the way `ExprType`s are + * treated. `eraseInfo` maps them them to nullary method types, whereas `apply` maps them + * to `Function0`. + */ def eraseInfo(tp: Type, sym: Symbol)(implicit ctx: Context) = tp match { case ExprType(rt) => if (sym is Param) apply(tp) @@ -354,22 +379,30 @@ class TypeErasure(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wild // forwarders to mixin methods. // See doc comment for ElimByName for speculation how we could improve this. else MethodType(Nil, Nil, eraseResult(rt)) - case tp => erasure(tp) + case tp => this(tp) + } + + private def eraseDerivedValueClassRef(tref: TypeRef)(implicit ctx: Context): Type = { + val cls = tref.symbol.asClass + val underlying = underlyingOfValueClass(cls) + ErasedValueType(cls, erasure(underlying)) } - private def eraseDerivedValueClassRef(tref: TypeRef)(implicit ctx: Context): Type = - unsupported("eraseDerivedValueClass") private def eraseNormalClassRef(tref: TypeRef)(implicit ctx: Context): Type = { val cls = tref.symbol.asClass (if (cls.owner is Package) normalizeClass(cls) else cls).typeRef } + /** The erasure of a function result type. */ private def eraseResult(tp: Type)(implicit ctx: Context): Type = tp match { case tp: TypeRef => val sym = tp.typeSymbol if (sym eq defn.UnitClass) sym.typeRef - else if (sym.isDerivedValueClass) eraseNormalClassRef(tp) + // For a value class V, "new V(x)" should have type V for type adaptation to work + // correctly (see SIP-15 and [[Erasure.Boxing.adaptToType]]), so the return type of a + // constructor method should not be semi-erased. + else if (isConstructor && isDerivedValueClass(sym)) eraseNormalClassRef(tp) else this(tp) case RefinedType(parent, _) if !(parent isRef defn.ArrayClass) => eraseResult(parent) @@ -391,10 +424,12 @@ class TypeErasure(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wild * Need to ensure correspondence with erasure! */ private def sigName(tp: Type)(implicit ctx: Context): TypeName = tp match { + case ErasedValueType(_, underlying) => + sigName(underlying) case tp: TypeRef => val sym = tp.symbol if (!sym.isClass) sigName(tp.info) - else if (sym.isDerivedValueClass) sigName(eraseDerivedValueClassRef(tp)) + else if (isDerivedValueClass(sym)) sigName(eraseDerivedValueClassRef(tp)) else normalizeClass(sym.asClass).fullName.asTypeName case defn.ArrayType(elem) => sigName(this(tp)) diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index e290e8868..595732b37 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -2066,14 +2066,16 @@ object Types { def apply(paramTypes: List[Type], resultType: Type)(implicit ctx: Context): MethodType = apply(nme.syntheticParamNames(paramTypes.length), paramTypes, resultType) def fromSymbols(params: List[Symbol], resultType: Type)(implicit ctx: Context) = { - def paramInfo(param: Symbol): Type = param.info match { + def translateRepeated(tp: Type): Type = tp match { + case tp @ ExprType(tp1) => tp.derivedExprType(translateRepeated(tp1)) case AnnotatedType(annot, tp) if annot matches defn.RepeatedAnnot => - val typeSym = param.info.typeSymbol.asClass + val typeSym = tp.typeSymbol.asClass assert(typeSym == defn.SeqClass || typeSym == defn.ArrayClass) tp.translateParameterized(typeSym, defn.RepeatedParamClass) case tp => tp } + def paramInfo(param: Symbol): Type = translateRepeated(param.info) def transformResult(mt: MethodType) = resultType.subst(params, (0 until params.length).toList map (MethodParam(mt, _))) apply(params map (_.name.asTermName), params map paramInfo)(transformResult _) diff --git a/src/dotty/tools/dotc/core/pickling/NameBuffer.scala b/src/dotty/tools/dotc/core/pickling/NameBuffer.scala index 2a6239c5a..7ea94089f 100644 --- a/src/dotty/tools/dotc/core/pickling/NameBuffer.scala +++ b/src/dotty/tools/dotc/core/pickling/NameBuffer.scala @@ -11,7 +11,7 @@ import scala.io.Codec import TastyName._ import PickleFormat._ -class NameBuffer extends TastyBuffer(100000) { +class NameBuffer extends TastyBuffer(10000) { private val nameRefs = new mutable.LinkedHashMap[TastyName, NameRef] diff --git a/src/dotty/tools/dotc/core/pickling/PositionPickler.scala b/src/dotty/tools/dotc/core/pickling/PositionPickler.scala index e8a0b3d01..1e36105cb 100644 --- a/src/dotty/tools/dotc/core/pickling/PositionPickler.scala +++ b/src/dotty/tools/dotc/core/pickling/PositionPickler.scala @@ -38,7 +38,7 @@ object PositionPickler { import PositionPickler._ class PositionPickler(pickler: TastyPickler, addrOfTree: Tree => Option[Addr]) { - val buf = new TastyBuffer(100000) + val buf = new TastyBuffer(5000) pickler.newSection("Positions", buf) import buf._ diff --git a/src/dotty/tools/dotc/core/pickling/TastyBuffer.scala b/src/dotty/tools/dotc/core/pickling/TastyBuffer.scala index a67722227..f57c15a3d 100644 --- a/src/dotty/tools/dotc/core/pickling/TastyBuffer.scala +++ b/src/dotty/tools/dotc/core/pickling/TastyBuffer.scala @@ -44,7 +44,8 @@ class TastyBuffer(initialSize: Int) { /** Write a byte of data. */ def writeByte(b: Int): Unit = { - if (length == bytes.length) bytes = dble(bytes) + if (length >= bytes.length) + bytes = dble(bytes) bytes(length) = b.toByte length += 1 } @@ -116,6 +117,8 @@ class TastyBuffer(initialSize: Int) { def putNat(at: Addr, x: Int, width: Int): Unit = { var y = x var w = width + if(at.index + w >= bytes.length) + bytes = dble(bytes) var digit = y & 0x7f | 0x80 while (w > 0) { w -= 1 diff --git a/src/dotty/tools/dotc/core/pickling/TreeBuffer.scala b/src/dotty/tools/dotc/core/pickling/TreeBuffer.scala index c224fc30b..393ffd278 100644 --- a/src/dotty/tools/dotc/core/pickling/TreeBuffer.scala +++ b/src/dotty/tools/dotc/core/pickling/TreeBuffer.scala @@ -8,10 +8,9 @@ import TastyBuffer.{Addr, AddrWidth} import config.Printers.pickling import ast.tpd.Tree -class TreeBuffer extends TastyBuffer(1000000) { +class TreeBuffer extends TastyBuffer(50000) { private final val ItemsOverOffsets = 2 - private val initialOffsetSize = bytes.length / (AddrWidth * ItemsOverOffsets) private var offsets = new Array[Int](initialOffsetSize) private var isRelative = new Array[Boolean](initialOffsetSize) diff --git a/src/dotty/tools/dotc/parsing/Parsers.scala b/src/dotty/tools/dotc/parsing/Parsers.scala index cbefb81fe..dd2c9bcaa 100644 --- a/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/src/dotty/tools/dotc/parsing/Parsers.scala @@ -754,17 +754,21 @@ object Parsers { if (in.token == ARROW) atPos(in.skipToken()) { ByNameTypeTree(argType()) } else argType() - /** ParamType ::= FunArgType | ArgType `*' + /** ParamType ::= [`=>'] ParamValueType */ def paramType(): Tree = - if (in.token == ARROW) funArgType() - else { - val t = argType() - if (isIdent(nme.raw.STAR)) { - in.nextToken() - atPos(t.pos.start) { PostfixOp(t, nme.raw.STAR) } - } else t - } + if (in.token == ARROW) atPos(in.skipToken()) { ByNameTypeTree(paramValueType()) } + else paramValueType() + + /** ParamValueType ::= Type [`*'] + */ + def paramValueType(): Tree = { + val t = typ() + if (isIdent(nme.raw.STAR)) { + in.nextToken() + atPos(t.pos.start) { PostfixOp(t, nme.raw.STAR) } + } else t + } /** TypeArgs ::= `[' ArgType {`,' ArgType} `]' */ diff --git a/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 67bd65bc7..11d451255 100644 --- a/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -3,6 +3,7 @@ package printing import core._ import Texts._, Types._, Flags._, Names._, Symbols._, NameOps._, Constants._ +import TypeErasure.ErasedValueType import Contexts.Context, Scopes.Scope, Denotations._, SymDenotations._, Annotations.Annotation import StdNames.nme import ast.{Trees, untpd, tpd} @@ -63,7 +64,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { } override def toTextPrefix(tp: Type): Text = controlled { - def isOmittable(sym: Symbol) = + def isOmittable(sym: Symbol) = if (ctx.settings.verbose.value) false else if (homogenizedView) isEmptyPrefix(sym) // drop <root> and anonymous classes, but not scala, Predef. else isOmittablePrefix(sym) @@ -132,6 +133,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { return toText(tp.info) case ExprType(result) => return "=> " ~ toText(result) + case ErasedValueType(clazz, underlying) => + return "ErasedValueType(" ~ toText(clazz.typeRef) ~ ", " ~ toText(underlying) ~ ")" case tp: ClassInfo => return toTextParents(tp.instantiatedParents) ~ "{...}" case JavaArrayType(elemtp) => @@ -351,6 +354,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { toTextLocal(tpt) ~ " " ~ blockText(refines) case AppliedTypeTree(tpt, args) => toTextLocal(tpt) ~ "[" ~ Text(args map argText, ", ") ~ "]" + case ByNameTypeTree(tpt) => + "=> " ~ toTextLocal(tpt) case TypeBoundsTree(lo, hi) => optText(lo)(" >: " ~ _) ~ optText(hi)(" <: " ~ _) case Bind(name, body) => diff --git a/src/dotty/tools/dotc/transform/Constructors.scala b/src/dotty/tools/dotc/transform/Constructors.scala index cd64497e9..ddd64d500 100644 --- a/src/dotty/tools/dotc/transform/Constructors.scala +++ b/src/dotty/tools/dotc/transform/Constructors.scala @@ -80,11 +80,10 @@ class Constructors extends MiniPhaseTransform with SymTransformer { thisTransfor // (2) If the parameter accessor reference was to an alias getter, // drop the () when replacing by the parameter. object intoConstr extends TreeMap { - private var excluded: FlagSet = _ override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match { case Ident(_) | Select(This(_), _) => var sym = tree.symbol - if (sym is (ParamAccessor, butNot = excluded)) sym = sym.subst(accessors, paramSyms) + if (sym is (ParamAccessor, butNot = Mutable)) sym = sym.subst(accessors, paramSyms) if (sym.owner.isConstructor) ref(sym).withPos(tree.pos) else tree case Apply(fn, Nil) => val fn1 = transform(fn) @@ -95,9 +94,8 @@ class Constructors extends MiniPhaseTransform with SymTransformer { thisTransfor if (noDirectRefsFrom(tree)) tree else super.transform(tree) } - def apply(tree: Tree, inSuperCall: Boolean = false)(implicit ctx: Context): Tree = { - this.excluded = if (inSuperCall) EmptyFlags else Mutable - transform(tree) + def apply(tree: Tree, prevOwner: Symbol)(implicit ctx: Context): Tree = { + transform(tree).changeOwnerAfter(prevOwner, constr.symbol, thisTransform) } } @@ -153,19 +151,19 @@ class Constructors extends MiniPhaseTransform with SymTransformer { thisTransfor val sym = stat.symbol if (isRetained(sym)) { if (!stat.rhs.isEmpty && !isWildcardArg(stat.rhs)) - constrStats += Assign(ref(sym), intoConstr(stat.rhs)).withPos(stat.pos) + constrStats += Assign(ref(sym), intoConstr(stat.rhs, sym)).withPos(stat.pos) clsStats += cpy.ValDef(stat)(rhs = EmptyTree) } else if (!stat.rhs.isEmpty) { sym.copySymDenotation( initFlags = sym.flags &~ Private, owner = constr.symbol).installAfter(thisTransform) - constrStats += intoConstr(stat) + constrStats += intoConstr(stat, sym) } case _: DefTree => clsStats += stat case _ => - constrStats += intoConstr(stat) + constrStats += intoConstr(stat, tree.symbol) } splitStats(stats1) case Nil => diff --git a/src/dotty/tools/dotc/transform/ElimErasedValueType.scala b/src/dotty/tools/dotc/transform/ElimErasedValueType.scala new file mode 100644 index 000000000..8a18c9c17 --- /dev/null +++ b/src/dotty/tools/dotc/transform/ElimErasedValueType.scala @@ -0,0 +1,82 @@ +package dotty.tools.dotc +package transform + +import ast.{Trees, tpd} +import core._, core.Decorators._ +import TreeTransforms._, Phases.Phase +import Types._, Contexts._, Constants._, Names._, NameOps._, Flags._, DenotTransformers._ +import SymDenotations._, Symbols._, StdNames._, Annotations._, Trees._, Scopes._, Denotations._ +import TypeErasure.ErasedValueType, ValueClasses._ + +/** This phase erases ErasedValueType to their underlying type. + * It also removes the synthetic cast methods u2evt$ and evt2u$ which are + * no longer needed afterwards. + */ +class ElimErasedValueType extends MiniPhaseTransform with InfoTransformer { + + import tpd._ + + override def phaseName: String = "elimErasedValueType" + + override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[Erasure]) + + def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = sym match { + case sym: ClassSymbol if sym is ModuleClass => + sym.companionClass match { + case origClass: ClassSymbol if isDerivedValueClass(origClass) => + val cinfo = tp.asInstanceOf[ClassInfo] + val decls1 = cinfo.decls.cloneScope + ctx.atPhase(this.next) { implicit ctx => + // Remove synthetic cast methods introduced by ExtensionMethods, + // they are no longer needed after this phase. + decls1.unlink(cinfo.decl(nme.U2EVT).symbol) + decls1.unlink(cinfo.decl(nme.EVT2U).symbol) + } + cinfo.derivedClassInfo(decls = decls1) + case _ => + tp + } + case _ => + elimEVT(tp) + } + + def elimEVT(tp: Type)(implicit ctx: Context): Type = tp match { + case ErasedValueType(_, underlying) => + elimEVT(underlying) + case tp: MethodType => + val paramTypes = tp.paramTypes.mapConserve(elimEVT) + val retType = elimEVT(tp.resultType) + tp.derivedMethodType(tp.paramNames, paramTypes, retType) + case _ => + tp + } + + def transformTypeOfTree(tree: Tree)(implicit ctx: Context): Tree = + tree.withType(elimEVT(tree.tpe)) + + override def transformApply(tree: Apply)(implicit ctx: Context, info: TransformerInfo): Tree = { + val Apply(fun, args) = tree + val name = fun.symbol.name + + // The casts to and from ErasedValueType are no longer needed once ErasedValueType + // has been eliminated. + val t = + if ((name eq nme.U2EVT) || (name eq nme.EVT2U)) + args.head + else + tree + transformTypeOfTree(t) + } + + // FIXME: transformIf and transformBlock won't be required anymore once #444 is fixed. + override def transformIdent(tree: Ident)(implicit ctx: Context, info: TransformerInfo): Tree = + transformTypeOfTree(tree) + override def transformSelect(tree: Select)(implicit ctx: Context, info: TransformerInfo): Tree = + transformTypeOfTree(tree) + override def transformBlock(tree: Block)(implicit ctx: Context, info: TransformerInfo): Tree = + transformTypeOfTree(tree) + override def transformIf(tree: If)(implicit ctx: Context, info: TransformerInfo): Tree = + transformTypeOfTree(tree) + override def transformTypeTree(tree: TypeTree)(implicit ctx: Context, info: TransformerInfo): Tree = + transformTypeOfTree(tree) +} diff --git a/src/dotty/tools/dotc/transform/Erasure.scala b/src/dotty/tools/dotc/transform/Erasure.scala index 79db568b8..996c480ce 100644 --- a/src/dotty/tools/dotc/transform/Erasure.scala +++ b/src/dotty/tools/dotc/transform/Erasure.scala @@ -62,7 +62,7 @@ class Erasure extends Phase with DenotTransformer { thisTransformer => } } case ref => - ref.derivedSingleDenotation(ref.symbol, eraseInfo(ref.info, ref.symbol)) + ref.derivedSingleDenotation(ref.symbol, transformInfo(ref.symbol, ref.info)) } val eraser = new Erasure.Typer @@ -170,15 +170,29 @@ object Erasure extends TypeTestsCasts{ def unbox(tree: Tree, pt: Type)(implicit ctx: Context): Tree = ctx.traceIndented(i"unboxing ${tree.showSummary}: ${tree.tpe} as a $pt") { pt match { case ErasedValueType(clazz, underlying) => + def unboxedTree(t: Tree) = + adaptToType(t, clazz.typeRef) + .select(valueClassUnbox(clazz)) + .appliedToNone + + // Null unboxing needs to be treated separately since we cannot call a method on null. + // "Unboxing" null to underlying is equivalent to doing null.asInstanceOf[underlying] + // See tests/pos/valueclasses/nullAsInstanceOfVC.scala for cases where this might happen. val tree1 = - if ((tree.tpe isRef defn.NullClass) && underlying.isPrimitiveValueType) - // convert `null` directly to underlying type, as going - // via the unboxed type would yield a NPE (see SI-5866) - unbox(tree, underlying) - else - adaptToType(tree, clazz.typeRef) - .select(valueClassUnbox(clazz)) - .appliedToNone + if (tree.tpe isRef defn.NullClass) + adaptToType(tree, underlying) + else if (!(tree.tpe <:< clazz.typeRef)) { + assert(!(tree.tpe.typeSymbol.isPrimitiveValueClass)) + val nullTree = Literal(Constant(null)) + val unboxedNull = adaptToType(nullTree, underlying) + + evalOnce(tree) { t => + If(t.select(defn.Object_eq).appliedTo(nullTree), + unboxedNull, + unboxedTree(t)) + } + } else unboxedTree(tree) + cast(tree1, pt) case _ => val cls = pt.widen.classSymbol @@ -192,6 +206,8 @@ object Erasure extends TypeTestsCasts{ /** Generate a synthetic cast operation from tree.tpe to pt. * Does not do any boxing/unboxing (this is handled upstream). + * Casts from and to ErasedValueType are special, see the explanation + * in ExtensionMethods#transform. */ def cast(tree: Tree, pt: Type)(implicit ctx: Context): Tree = { // TODO: The commented out assertion fails for tailcall/t6574.scala @@ -203,9 +219,18 @@ object Erasure extends TypeTestsCasts{ if treeElem.widen.isPrimitiveValueType && !ptElem.isPrimitiveValueType => // See SI-2386 for one example of when this might be necessary. cast(ref(defn.runtimeMethod(nme.toObjectArray)).appliedTo(tree), pt) + case (_, ErasedValueType(cls, _)) => + ref(u2evt(cls)).appliedTo(tree) case _ => - if (pt.isPrimitiveValueType) primitiveConversion(tree, pt.classSymbol) - else tree.asInstance(pt) + tree.tpe.widen match { + case ErasedValueType(cls, _) => + ref(evt2u(cls)).appliedTo(tree) + case _ => + if (pt.isPrimitiveValueType) + primitiveConversion(tree, pt.classSymbol) + else + tree.asInstance(pt) + } } } @@ -243,18 +268,39 @@ object Erasure extends TypeTestsCasts{ class Typer extends typer.ReTyper with NoChecking { import Boxing._ - def erasedType(tree: untpd.Tree)(implicit ctx: Context): Type = tree.typeOpt match { - case tp: TermRef if tree.isTerm => erasedRef(tp) - case tp => erasure(tp) - } + def erasedType(tree: untpd.Tree, semiEraseVCs: Boolean = true)(implicit ctx: Context): Type = + tree.typeOpt match { + case tp: TermRef if tree.isTerm => erasedRef(tp) + case tp => erasure(tp, semiEraseVCs) + } - override def promote(tree: untpd.Tree)(implicit ctx: Context): tree.ThisTree[Type] = { + def promote(tree: untpd.Tree, semiEraseVCs: Boolean)(implicit ctx: Context): tree.ThisTree[Type] = { assert(tree.hasType) - val erased = erasedType(tree) + val erased = erasedType(tree, semiEraseVCs) ctx.log(s"promoting ${tree.show}: ${erased.showWithUnderlying()}") tree.withType(erased) } + override def promote(tree: untpd.Tree)(implicit ctx: Context): tree.ThisTree[Type] = { + promote(tree, true) + } + + /** When erasing most TypeTrees we should not semi-erase value types. + * This is not the case for [[DefDef#tpt]], [[ValDef#tpt]] and [[Typed#tpt]], they + * are handled separately by [[typedDefDef]], [[typedValDef]] and [[typedTyped]]. + */ + override def typedTypeTree(tree: untpd.TypeTree, pt: Type)(implicit ctx: Context): TypeTree = { + promote(tree, semiEraseVCs = false) + } + + /** This override is only needed to semi-erase type ascriptions */ + override def typedTyped(tree: untpd.Typed, pt: Type)(implicit ctx: Context): Tree = { + val Typed(expr, tpt) = tree + val tpt1 = promote(tpt) + val expr1 = typed(expr, tpt1.tpe) + assignType(untpd.cpy.Typed(tree)(expr1, tpt1), tpt1) + } + override def typedLiteral(tree: untpd.Literal)(implicit ctc: Context): Literal = if (tree.typeOpt.isRef(defn.UnitClass)) tree.withType(tree.typeOpt) else super.typedLiteral(tree) @@ -319,7 +365,7 @@ object Erasure extends TypeTestsCasts{ assert(sym.isConstructor, s"${sym.showLocated}") select(qual, defn.ObjectClass.info.decl(sym.name).symbol) } - else if (qualIsPrimitive && !symIsPrimitive || qual.tpe.isErasedValueType) + else if (qualIsPrimitive && !symIsPrimitive || qual.tpe.widenDealias.isErasedValueType) recur(box(qual)) else if (!qualIsPrimitive && symIsPrimitive) recur(unbox(qual, sym.owner.typeRef)) @@ -338,7 +384,7 @@ object Erasure extends TypeTestsCasts{ } override def typedSelectFromTypeTree(tree: untpd.SelectFromTypeTree, pt: Type)(implicit ctx: Context) = - untpd.Ident(tree.name).withPos(tree.pos).withType(erasedType(tree)) + untpd.Ident(tree.name).withPos(tree.pos).withType(erasedType(tree, semiEraseVCs = false)) override def typedThis(tree: untpd.This)(implicit ctx: Context): Tree = if (tree.symbol == ctx.owner.enclosingClass || tree.symbol.isStaticOwner) promote(tree) @@ -435,6 +481,58 @@ object Erasure extends TypeTestsCasts{ super.typedDefDef(ddef1, sym) } + /** After erasure, we may have to replace the closure method by a bridge. + * LambdaMetaFactory handles this automatically for most types, but we have + * to deal with boxing and unboxing of value classes ourselves. + */ + override def typedClosure(tree: untpd.Closure, pt: Type)(implicit ctx: Context) = { + val implClosure @ Closure(_, meth, _) = super.typedClosure(tree, pt) + implClosure.tpe match { + case SAMType(sam) => + val implType = meth.tpe.widen + + val List(implParamTypes) = implType.paramTypess + val List(samParamTypes) = sam.info.paramTypess + val implResultType = implType.resultType + val samResultType = sam.info.resultType + + // Given a value class V with an underlying type U, the following code: + // val f: Function1[V, V] = x => ... + // results in the creation of a closure and a method: + // def $anonfun(v1: V): V = ... + // val f: Function1[V, V] = closure($anonfun) + // After [[Erasure]] this method will look like: + // def $anonfun(v1: ErasedValueType(V, U)): ErasedValueType(V, U) = ... + // And after [[ElimErasedValueType]] it will look like: + // def $anonfun(v1: U): U = ... + // This method does not implement the SAM of Function1[V, V] anymore and + // needs to be replaced by a bridge: + // def $anonfun$2(v1: V): V = new V($anonfun(v1.underlying)) + // val f: Function1 = closure($anonfun$2) + // In general, a bridge is needed when the signature of the closure method after + // Erasure contains an ErasedValueType but the corresponding type in the functional + // interface is not an ErasedValueType. + val bridgeNeeded = + (implResultType :: implParamTypes, samResultType :: samParamTypes).zipped.forall( + (implType, samType) => implType.isErasedValueType && !samType.isErasedValueType + ) + + if (bridgeNeeded) { + val bridge = ctx.newSymbol(ctx.owner, nme.ANON_FUN, Flags.Synthetic | Flags.Method, sam.info) + val bridgeCtx = ctx.withOwner(bridge) + Closure(bridge, bridgeParamss => { + implicit val ctx: Context = bridgeCtx + + val List(bridgeParams) = bridgeParamss + val rhs = Apply(meth, (bridgeParams, implParamTypes).zipped.map(adapt(_, _))) + adapt(rhs, sam.info.resultType) + }) + } else implClosure + case _ => + implClosure + } + } + override def typedTypeDef(tdef: untpd.TypeDef, sym: Symbol)(implicit ctx: Context) = EmptyTree @@ -502,6 +600,8 @@ object Erasure extends TypeTestsCasts{ traverse(newStats, oldStats) } + + private final val NoBridgeFlags = Flags.Accessor | Flags.Deferred | Flags.Lazy /** Create a bridge DefDef which overrides a parent method. * @@ -520,7 +620,7 @@ object Erasure extends TypeTestsCasts{ ??? } val bridge = ctx.newSymbol(currentClass, - parentSym.name, parentSym.flags | Flags.Bridge, parentSym.info, coord = newDefSym.owner.coord).asTerm + parentSym.name, parentSym.flags &~ NoBridgeFlags | Flags.Bridge, parentSym.info, coord = newDefSym.owner.coord).asTerm bridge.enteredAfter(ctx.phase.prev.asInstanceOf[DenotTransformer]) // this should be safe, as we're executing in context of next phase ctx.debuglog(s"generating bridge from ${newDefSym} to $bridge") diff --git a/src/dotty/tools/dotc/transform/ExplicitOuter.scala b/src/dotty/tools/dotc/transform/ExplicitOuter.scala index 9fc164d3b..20e367e1f 100644 --- a/src/dotty/tools/dotc/transform/ExplicitOuter.scala +++ b/src/dotty/tools/dotc/transform/ExplicitOuter.scala @@ -28,8 +28,8 @@ import collection.mutable * * - add outer parameters to constructors * - pass outer arguments in constructor calls - * - replace outer this by outer paths. * + * replacement of outer this by outer paths is done in Erasure. * needs to run after pattern matcher as it can add outer checks and force creation of $outer */ class ExplicitOuter extends MiniPhaseTransform with InfoTransformer { thisTransformer => diff --git a/src/dotty/tools/dotc/transform/ExtensionMethods.scala b/src/dotty/tools/dotc/transform/ExtensionMethods.scala index ae22adc39..b2f402bc5 100644 --- a/src/dotty/tools/dotc/transform/ExtensionMethods.scala +++ b/src/dotty/tools/dotc/transform/ExtensionMethods.scala @@ -14,17 +14,29 @@ import core._ import Phases.Phase import Types._, Contexts._, Constants._, Names._, NameOps._, Flags._, DenotTransformers._ import SymDenotations._, Symbols._, StdNames._, Annotations._, Trees._, Scopes._, Denotations._ +import TypeErasure.{ erasure, ErasedValueType } import TypeUtils._ import util.Positions._ import Decorators._ +import SymUtils._ /** * Perform Step 1 in the inline classes SIP: Creates extension methods for all * methods in a value class, except parameter or super accessors, or constructors. + * + * Additionally, for a value class V, let U be the underlying type after erasure. We add + * to the companion module of V two cast methods: + * def u2evt$(x0: U): ErasedValueType(V, U) + * def evt2u$(x0: ErasedValueType(V, U)): U + * The casts are used in [[Erasure]] to make it typecheck, they are then removed + * in [[ElimErasedValueType]]. + * This is different from the implementation of value classes in Scala 2 + * (see SIP-15) which uses `asInstanceOf` which does not typecheck. */ class ExtensionMethods extends MiniPhaseTransform with DenotTransformer with FullParameterization { thisTransformer => import tpd._ + import ExtensionMethods._ /** the following two members override abstract members in Transform */ override def phaseName: String = "extmethods" @@ -37,13 +49,27 @@ class ExtensionMethods extends MiniPhaseTransform with DenotTransformer with Ful case ref: ClassDenotation if ref is ModuleClass => ref.linkedClass match { case origClass: ClassSymbol if isDerivedValueClass(origClass) => - val cinfo = ref.classInfo // ./tests/pos/t2667.scala dies here for module class AnyVal$ + val cinfo = ref.classInfo val decls1 = cinfo.decls.cloneScope ctx.atPhase(thisTransformer.next) { implicit ctx => - for (decl <- origClass.classInfo.decls) { - if (isMethodWithExtension(decl)) - decls1.enter(createExtensionMethod(decl, ref.symbol)) + // In Scala 2, extension methods are added before pickling so we should + // not generate them again. + if (!(origClass is Scala2x)) { + for (decl <- origClass.classInfo.decls) { + if (isMethodWithExtension(decl)) + decls1.enter(createExtensionMethod(decl, ref.symbol)) + } } + + val sym = ref.symbol + val underlying = erasure(underlyingOfValueClass(origClass)) + val evt = ErasedValueType(origClass, underlying) + val u2evtSym = ctx.newSymbol(sym, nme.U2EVT, Synthetic | Method, + MethodType(List(nme.x_0), List(underlying), evt)) + val evt2uSym = ctx.newSymbol(sym, nme.EVT2U, Synthetic | Method, + MethodType(List(nme.x_0), List(evt), underlying)) + decls1.enter(u2evtSym) + decls1.enter(evt2uSym) } if (decls1.isEmpty) ref else ref.copySymDenotation(info = cinfo.derivedClassInfo(decls = decls1)) @@ -64,65 +90,6 @@ class ExtensionMethods extends MiniPhaseTransform with DenotTransformer with Ful target.owner.linkedClass == derived.owner) extensionMethod(target) else NoSymbol - /** Generate stream of possible names for the extension version of given instance method `imeth`. - * If the method is not overloaded, this stream consists of just "imeth$extension". - * If the method is overloaded, the stream has as first element "imeth$extenionX", where X is the - * index of imeth in the sequence of overloaded alternatives with the same name. This choice will - * always be picked as the name of the generated extension method. - * After this first choice, all other possible indices in the range of 0 until the number - * of overloaded alternatives are returned. The secondary choices are used to find a matching method - * in `extensionMethod` if the first name has the wrong type. We thereby gain a level of insensitivity - * of how overloaded types are ordered between phases and picklings. - */ - private def extensionNames(imeth: Symbol)(implicit ctx: Context): Stream[Name] = { - val decl = imeth.owner.info.decl(imeth.name) - - /** No longer needed for Dotty, as we are more disciplined with scopes now. - // Bridge generation is done at phase `erasure`, but new scopes are only generated - // for the phase after that. So bridges are visible in earlier phases. - // - // `info.member(imeth.name)` filters these out, but we need to use `decl` - // to restrict ourselves to members defined in the current class, so we - // must do the filtering here. - val declTypeNoBridge = decl.filter(sym => !sym.isBridge).tpe - */ - decl match { - case decl: MultiDenotation => - val alts = decl.alternatives - val index = alts indexOf imeth.denot - assert(index >= 0, alts + " does not contain " + imeth) - def altName(index: Int) = (imeth.name + "$extension" + index).toTermName - altName(index) #:: ((0 until alts.length).toStream filter (index != _) map altName) - case decl => - assert(decl.exists, imeth.name + " not found in " + imeth.owner + "'s decls: " + imeth.owner.info.decls) - Stream((imeth.name + "$extension").toTermName) - } - } - - /** Return the extension method that corresponds to given instance method `meth`. */ - def extensionMethod(imeth: Symbol)(implicit ctx: Context): TermSymbol = - ctx.atPhase(thisTransformer.next) { implicit ctx => - // FIXME use toStatic instead? - val companionInfo = imeth.owner.companionModule.info - val candidates = extensionNames(imeth) map (companionInfo.decl(_).symbol) filter (_.exists) - val matching = candidates filter (c => memberSignature(c.info) == imeth.signature) - assert(matching.nonEmpty, - sm"""|no extension method found for: - | - | $imeth:${imeth.info.show} with signature ${imeth.signature} - | - | Candidates: - | - | ${candidates.map(c => c.name + ":" + c.info.show).mkString("\n")} - | - | Candidates (signatures normalized): - | - | ${candidates.map(c => c.name + ":" + c.info.signature + ":" + memberSignature(c.info)).mkString("\n")} - | - | Eligible Names: ${extensionNames(imeth).mkString(",")}""") - matching.head.asTerm - } - private def createExtensionMethod(imeth: Symbol, staticClass: Symbol)(implicit ctx: Context): TermSymbol = { assert(ctx.phase == thisTransformer.next) val extensionName = extensionNames(imeth).head.toTermName @@ -184,3 +151,64 @@ class ExtensionMethods extends MiniPhaseTransform with DenotTransformer with Ful } else tree } } + +object ExtensionMethods { + /** Generate stream of possible names for the extension version of given instance method `imeth`. + * If the method is not overloaded, this stream consists of just "imeth$extension". + * If the method is overloaded, the stream has as first element "imeth$extenionX", where X is the + * index of imeth in the sequence of overloaded alternatives with the same name. This choice will + * always be picked as the name of the generated extension method. + * After this first choice, all other possible indices in the range of 0 until the number + * of overloaded alternatives are returned. The secondary choices are used to find a matching method + * in `extensionMethod` if the first name has the wrong type. We thereby gain a level of insensitivity + * of how overloaded types are ordered between phases and picklings. + */ + private def extensionNames(imeth: Symbol)(implicit ctx: Context): Stream[Name] = { + val decl = imeth.owner.info.decl(imeth.name) + + /** No longer needed for Dotty, as we are more disciplined with scopes now. + // Bridge generation is done at phase `erasure`, but new scopes are only generated + // for the phase after that. So bridges are visible in earlier phases. + // + // `info.member(imeth.name)` filters these out, but we need to use `decl` + // to restrict ourselves to members defined in the current class, so we + // must do the filtering here. + val declTypeNoBridge = decl.filter(sym => !sym.isBridge).tpe + */ + decl match { + case decl: MultiDenotation => + val alts = decl.alternatives + val index = alts indexOf imeth.denot + assert(index >= 0, alts + " does not contain " + imeth) + def altName(index: Int) = (imeth.name + "$extension" + index).toTermName + altName(index) #:: ((0 until alts.length).toStream filter (index != _) map altName) + case decl => + assert(decl.exists, imeth.name + " not found in " + imeth.owner + "'s decls: " + imeth.owner.info.decls) + Stream((imeth.name + "$extension").toTermName) + } + } + + /** Return the extension method that corresponds to given instance method `meth`. */ + def extensionMethod(imeth: Symbol)(implicit ctx: Context): TermSymbol = + ctx.atPhase(ctx.extensionMethodsPhase.next) { implicit ctx => + // FIXME use toStatic instead? + val companionInfo = imeth.owner.companionModule.info + val candidates = extensionNames(imeth) map (companionInfo.decl(_).symbol) filter (_.exists) + val matching = candidates filter (c => FullParameterization.memberSignature(c.info) == imeth.signature) + assert(matching.nonEmpty, + sm"""|no extension method found for: + | + | $imeth:${imeth.info.show} with signature ${imeth.signature} + | + | Candidates: + | + | ${candidates.map(c => c.name + ":" + c.info.show).mkString("\n")} + | + | Candidates (signatures normalized): + | + | ${candidates.map(c => c.name + ":" + c.info.signature + ":" + FullParameterization.memberSignature(c.info)).mkString("\n")} + | + | Eligible Names: ${extensionNames(imeth).mkString(",")}""") + matching.head.asTerm + } +} diff --git a/src/dotty/tools/dotc/transform/FullParameterization.scala b/src/dotty/tools/dotc/transform/FullParameterization.scala index f46942fb3..d402c2e7f 100644 --- a/src/dotty/tools/dotc/transform/FullParameterization.scala +++ b/src/dotty/tools/dotc/transform/FullParameterization.scala @@ -52,6 +52,7 @@ import ast.Trees._ trait FullParameterization { import tpd._ + import FullParameterization._ /** If references to original symbol `referenced` from within fully parameterized method * `derived` should be rewired to some fully parameterized method, the rewiring target symbol, @@ -124,15 +125,6 @@ trait FullParameterization { } } - /** Assuming `info` is a result of a `fullyParameterizedType` call, the signature of the - * original method type `X` such that `info = fullyParameterizedType(X, ...)`. - */ - def memberSignature(info: Type)(implicit ctx: Context): Signature = info match { - case info: PolyType => memberSignature(info.resultType) - case info @ MethodType(nme.SELF :: Nil, _) => info.resultType.ensureMethodic.signature - case _ => Signature.NotAMethod - } - /** The type parameters (skolems) of the method definition `originalDef`, * followed by the class parameters of its enclosing class. */ @@ -230,3 +222,14 @@ trait FullParameterization { .appliedToArgss(originalDef.vparamss.nestedMap(vparam => ref(vparam.symbol))) .withPos(originalDef.rhs.pos) } + +object FullParameterization { + /** Assuming `info` is a result of a `fullyParameterizedType` call, the signature of the + * original method type `X` such that `info = fullyParameterizedType(X, ...)`. + */ + def memberSignature(info: Type)(implicit ctx: Context): Signature = info match { + case info: PolyType => memberSignature(info.resultType) + case info @ MethodType(nme.SELF :: Nil, _) => info.resultType.ensureMethodic.signature + case _ => Signature.NotAMethod + } +} diff --git a/src/dotty/tools/dotc/transform/Getters.scala b/src/dotty/tools/dotc/transform/Getters.scala index 918a92a04..e1c35feba 100644 --- a/src/dotty/tools/dotc/transform/Getters.scala +++ b/src/dotty/tools/dotc/transform/Getters.scala @@ -16,15 +16,21 @@ import Decorators._ /** Performs the following rewritings for fields of a class: * * <mods> val x: T = e - * --> <mods> <stable> def x: T = e + * --> <mods> <stable> <accessor> def x: T = e * <mods> var x: T = e - * --> <mods> def x: T = e + * --> <mods> <accessor> def x: T = e * * <mods> val x: T - * --> <mods> <stable> def x: T + * --> <mods> <stable> <accessor> def x: T + * + * <mods> lazy val x: T = e + * --> <mods> <accessor> lazy def x: T =e * * <mods> var x: T - * --> <mods> def x: T + * --> <mods> <accessor> def x: T + * + * <mods> non-static <module> val x$ = e + * --> <mods> <module> <accessor> def x$ = e * * Omitted from the rewritings are * @@ -47,10 +53,10 @@ class Getters extends MiniPhaseTransform with SymTransformer { thisTransform => override def transformSym(d: SymDenotation)(implicit ctx: Context): SymDenotation = { def noGetterNeeded = d.is(NoGetterNeeded) || - d.initial.asInstanceOf[SymDenotation].is(PrivateLocal) && !d.owner.is(Trait) || + d.initial.asInstanceOf[SymDenotation].is(PrivateLocal) && !d.owner.is(Trait) && !d.is(Flags.Lazy) || d.is(Module) && d.isStatic || d.isSelfSym - if (d.isTerm && d.owner.isClass && d.info.isValueType && !noGetterNeeded) { + if (d.isTerm && (d.is(Lazy) || d.owner.isClass) && d.info.isValueType && !noGetterNeeded) { val maybeStable = if (d.isStable) Stable else EmptyFlags d.copySymDenotation( initFlags = d.flags | maybeStable | AccessorCreationFlags, @@ -58,7 +64,7 @@ class Getters extends MiniPhaseTransform with SymTransformer { thisTransform => } else d } - private val NoGetterNeeded = Method | Param | JavaDefined | JavaStatic | Lazy + private val NoGetterNeeded = Method | Param | JavaDefined | JavaStatic override def transformValDef(tree: ValDef)(implicit ctx: Context, info: TransformerInfo): Tree = if (tree.symbol is Method) DefDef(tree.symbol.asTerm, tree.rhs) else tree diff --git a/src/dotty/tools/dotc/transform/InterceptedMethods.scala b/src/dotty/tools/dotc/transform/InterceptedMethods.scala index 725910949..ff354a54c 100644 --- a/src/dotty/tools/dotc/transform/InterceptedMethods.scala +++ b/src/dotty/tools/dotc/transform/InterceptedMethods.scala @@ -27,6 +27,7 @@ import scala.collection.mutable.ListBuffer import dotty.tools.dotc.core.Denotations.SingleDenotation import dotty.tools.dotc.core.SymDenotations.SymDenotation import StdNames._ +import Phases.Phase /** Replace member references as follows: * diff --git a/src/dotty/tools/dotc/transform/LazyVals.scala b/src/dotty/tools/dotc/transform/LazyVals.scala index a28102d7b..62dc2f085 100644 --- a/src/dotty/tools/dotc/transform/LazyVals.scala +++ b/src/dotty/tools/dotc/transform/LazyVals.scala @@ -1,6 +1,8 @@ package dotty.tools.dotc package transform +import dotty.tools.dotc.typer.Mode + import scala.collection.mutable import core._ import Contexts._ @@ -14,54 +16,46 @@ import dotty.tools.dotc.ast.{untpd, tpd} import dotty.tools.dotc.core.Constants.Constant import dotty.tools.dotc.core.Types.{ExprType, NoType, MethodType} import dotty.tools.dotc.core.Names.Name -import dotty.runtime.{LazyVals => RLazyVals} // dotty deviation import SymUtils._ import scala.collection.mutable.ListBuffer import dotty.tools.dotc.core.Denotations.SingleDenotation import dotty.tools.dotc.core.SymDenotations.SymDenotation import dotty.tools.dotc.core.DenotTransformers.{SymTransformer, IdentityDenotTransformer, DenotTransformer} +import Erasure.Boxing.adaptToType -class LazyVals extends MiniPhaseTransform with SymTransformer { +class LazyVals extends MiniPhaseTransform with IdentityDenotTransformer { + import LazyVals._ import tpd._ - - def transformSym(d: SymDenotation)(implicit ctx: Context): SymDenotation = { - if (d is(Flags.Lazy, butNot = Flags.ModuleVal | Flags.Method)) { - // Method flag is set on lazy vals coming from Unpickler. They are already methods and shouldn't be transformed twice - d.copySymDenotation( - initFlags = d.flags | Flags.Method, - info = ExprType(d.info)) - } - else d - } - def transformer = new LazyVals val containerFlags = Flags.Synthetic | Flags.Mutable | Flags.Lazy - val initFlags = Flags.Synthetic | Flags.Method + val initFlags = Flags.Synthetic | Flags.Method + + val containerFlagsMask = Flags.Method | Flags.Lazy | Flags.Accessor | Flags.Module /** this map contains mutable state of transformation: OffsetDefs to be appended to companion object definitions, * and number of bits currently used */ class OffsetInfo(var defs: List[Tree], var ord:Int) - val appendOffsetDefs = mutable.Map.empty[Name, OffsetInfo] + val appendOffsetDefs = mutable.Map.empty[Symbol, OffsetInfo] override def phaseName: String = "LazyVals" - /** List of names of phases that should have finished processing of tree - * before this phase starts processing same tree */ - // override def ensureAfter: Set[String] = Set("mixin") + /** List of names of phases that should have finished processing of tree + * before this phase starts processing same tree */ + override def runsAfter = Set(classOf[Mixin]) - override def transformValDef(tree: ValDef)(implicit ctx: Context, info: TransformerInfo): Tree = { - if (!(tree.mods is Flags.Lazy) || (tree.mods is Flags.ModuleVal)) tree + override def transformDefDef(tree: DefDef)(implicit ctx: Context, info: TransformerInfo): Tree = { + if (!(tree.symbol is Flags.Lazy) || tree.symbol.owner.is(Flags.Trait)) tree else { - val isField = tree.symbol.owner.isClass + val isField = tree.symbol.owner.isClass - if (isField) { - if (tree.symbol.isVolatile) transformFieldValDefVolatile(tree) - else transformFieldValDefNonVolatile(tree) - } - else transformLocalValDef(tree) + if (isField) { + if (tree.symbol.isVolatile || tree.symbol.is(Flags.Module)) transformMemberDefVolatile(tree) + else transformMemberDefNonVolatile(tree) + } + else transformLocalDef(tree) } } @@ -70,11 +64,12 @@ class LazyVals extends MiniPhaseTransform with SymTransformer { override def transformTypeDef(tree: TypeDef)(implicit ctx: Context, info: TransformerInfo): Tree = { if (!tree.symbol.isClass) tree else { - appendOffsetDefs.get(tree.symbol.name) match { + appendOffsetDefs.get(tree.symbol) match { case None => tree case Some(data) => val template = tree.rhs.asInstanceOf[Template] - ClassDef(tree.symbol.asClass, template.constr, data.defs.mapConserve(transformFollowingDeep) ::: template.body) + val newTemplate = cpy.Template(template)(body = data.defs ::: template.body) + cpy.TypeDef(tree)(rhs = newTemplate) //(ctx.withMode(Mode.FutureDefsOK)) } } } @@ -82,12 +77,11 @@ class LazyVals extends MiniPhaseTransform with SymTransformer { * with a LazyHolder from * dotty.runtime(eg dotty.runtime.LazyInt) */ - def transformLocalValDef(x: ValDef)(implicit ctx: Context) = x match { - case ValDef(name, tpt, _) => + def transformLocalDef(x: DefDef)(implicit ctx: Context) = { val valueInitter = x.rhs - val holderName = ctx.freshName(name.toString + StdNames.nme.LAZY_LOCAL).toTermName - val initName = ctx.freshName(name.toString + StdNames.nme.LAZY_LOCAL_INIT).toTermName - val tpe = x.tpe.widen + val holderName = ctx.freshName(x.name ++ StdNames.nme.LAZY_LOCAL).toTermName + val initName = ctx.freshName(x.name ++ StdNames.nme.LAZY_LOCAL_INIT).toTermName + val tpe = x.tpe.widen.resultType.widen val holderType = if (tpe isRef defn.IntClass) "LazyInt" @@ -105,17 +99,19 @@ class LazyVals extends MiniPhaseTransform with SymTransformer { val holderSymbol = ctx.newSymbol(x.symbol.owner, holderName, containerFlags, holderImpl.typeRef, coord = x.pos) val initSymbol = ctx.newSymbol(x.symbol.owner, initName, initFlags, MethodType(Nil, tpe), coord = x.pos) - val result = ref(holderSymbol).select("value".toTermName) - val flag = ref(holderSymbol).select("initialized".toTermName) + val result = ref(holderSymbol).select(lazyNme.value) + val flag = ref(holderSymbol).select(lazyNme.initialized) val initer = valueInitter.changeOwner(x.symbol, initSymbol) val initBody = - ref(holderSymbol).select(defn.Object_synchronized).appliedToType(tpe).appliedTo( - mkNonThreadSafeDef(result, flag, initer).ensureConforms(tpe)) + adaptToType( + ref(holderSymbol).select(defn.Object_synchronized).appliedTo( + adaptToType(mkNonThreadSafeDef(result, flag, initer), defn.ObjectType)), + tpe) val initTree = DefDef(initSymbol, initBody) val holderTree = ValDef(holderSymbol, New(holderImpl.typeRef, List())) val methodBody = { tpd.If(flag, EmptyTree, ref(initSymbol)) - result.ensureConforms(tpe) + result.ensureApplied.ensureConforms(tpe) } val methodTree = DefDef(x.symbol.asTerm, methodBody) ctx.debuglog(s"found a lazy val ${x.show},\n rewrote with ${holderTree.show}") @@ -124,7 +120,16 @@ class LazyVals extends MiniPhaseTransform with SymTransformer { override def transformStats(trees: List[tpd.Tree])(implicit ctx: Context, info: TransformerInfo): List[tpd.Tree] = { - val (holders, stats) = trees.partition { _.symbol.flags == containerFlags} + // backend requires field usage to be after field definition + // need to bring containers to start of method + val (holders, stats) = + atGroupEnd { implicit ctx: Context => + trees.partition { + _.symbol.flags.&~(Flags.Touched) == containerFlags + // Filtering out Flags.Touched is not required currently, as there are no LazyTypes involved here + // but just to be more safe + } + } holders:::stats } @@ -140,10 +145,10 @@ class LazyVals extends MiniPhaseTransform with SymTransformer { */ def mkNonThreadSafeDef(target: Tree, flag: Tree, rhs: Tree)(implicit ctx: Context) = { - val setFlag = Assign(flag, Literal(Constants.Constant(true))) - val setTarget = Assign(target, rhs) - val init = Block(List(setFlag, setTarget), target) - If(flag, target, init) + val setFlag = flag.becomes(Literal(Constants.Constant(true))) + val setTarget = target.becomes(rhs) + val init = Block(List(setFlag, setTarget), target.ensureApplied) + If(flag.ensureApplied, target.ensureApplied, init) } /** Create non-threadsafe lazy accessor for not-nullable types equivalent to such code @@ -157,34 +162,36 @@ class LazyVals extends MiniPhaseTransform with SymTransformer { def mkDefNonThreadSafeNonNullable(target: Symbol, rhs: Tree)(implicit ctx: Context) = { val cond = ref(target).select(nme.eq).appliedTo(Literal(Constant(null))) val exp = ref(target) - val setTarget = Assign(exp, rhs) + val setTarget = exp.becomes(rhs) val init = Block(List(setTarget), exp) If(cond, init, exp) } - def transformFieldValDefNonVolatile(x: ValDef)(implicit ctx: Context) = x match { - case ValDef(name, tpt, _) if (x.mods is Flags.Lazy) => + def transformMemberDefNonVolatile(x: DefDef)(implicit ctx: Context) = { val claz = x.symbol.owner.asClass - val tpe = x.tpe.widen + val tpe = x.tpe.widen.resultType.widen assert(!(x.mods is Flags.Mutable)) - val containerName = ctx.freshName(name.toString + StdNames.nme.LAZY_LOCAL).toTermName - val containerSymbol = ctx.newSymbol(claz, containerName, (x.mods &~ Flags.Lazy | containerFlags).flags, tpe, coord = x.symbol.coord).enteredAfter(this) + val containerName = ctx.freshName(x.name ++ StdNames.nme.LAZY_LOCAL).toTermName + val containerSymbol = ctx.newSymbol(claz, containerName, + x.symbol.flags &~ containerFlagsMask | containerFlags | Flags.Private, + tpe, coord = x.symbol.coord + ).entered val containerTree = ValDef(containerSymbol, initValue(tpe)) - if (x.tpe.isNotNull && tpe <:< defn.AnyRefType) { // can use 'null' value instead of flag + if (x.tpe.isNotNull && tpe <:< defn.ObjectType) { // can use 'null' value instead of flag val slowPath = DefDef(x.symbol.asTerm, mkDefNonThreadSafeNonNullable(containerSymbol, x.rhs)) Thicket(List(containerTree, slowPath)) } else { - val flagName = ctx.freshName(name.toString + StdNames.nme.BITMAP_PREFIX).toTermName - val flagSymbol = ctx.newSymbol(x.symbol.owner, flagName, containerFlags, defn.BooleanType) + val flagName = ctx.freshName(x.name ++ StdNames.nme.BITMAP_PREFIX).toTermName + val flagSymbol = ctx.newSymbol(x.symbol.owner, flagName, containerFlags | Flags.Private, defn.BooleanType).entered val flag = ValDef(flagSymbol, Literal(Constants.Constant(false))) val slowPath = DefDef(x.symbol.asTerm, mkNonThreadSafeDef(ref(containerSymbol), ref(flagSymbol), x.rhs)) Thicket(List(containerTree, flag, slowPath)) } } - /** Create non-threadsafe lazy accessor equivalent to such code + /** Create a threadsafe lazy accessor equivalent to such code * * def methodSymbol(): Int = { * val result: Int = 0 @@ -215,39 +222,38 @@ class LazyVals extends MiniPhaseTransform with SymTransformer { * } * result * } - * FIXME: Don't use strings with toTermName, use predefined names instead. */ def mkThreadSafeDef(methodSymbol: TermSymbol, claz: ClassSymbol, ord: Int, target: Symbol, rhs: Tree, tp: Types.Type, offset: Tree, getFlag: Tree, stateMask: Tree, casFlag: Tree, setFlagState: Tree, waitOnLock: Tree)(implicit ctx: Context) = { val initState = Literal(Constants.Constant(0)) val computeState = Literal(Constants.Constant(1)) val notifyState = Literal(Constants.Constant(2)) val computedState = Literal(Constants.Constant(3)) - val flagSymbol = ctx.newSymbol(methodSymbol, "flag".toTermName, containerFlags, defn.LongType) + val flagSymbol = ctx.newSymbol(methodSymbol, lazyNme.flag, containerFlags, defn.LongType) val flagDef = ValDef(flagSymbol, Literal(Constant(0L))) val thiz = This(claz)(ctx.fresh.setOwner(claz)) - val resultSymbol = ctx.newSymbol(methodSymbol, "result".toTermName, containerFlags, tp) + val resultSymbol = ctx.newSymbol(methodSymbol, lazyNme.result, containerFlags, tp) val resultDef = ValDef(resultSymbol, initValue(tp)) - val retrySymbol = ctx.newSymbol(methodSymbol, "retry".toTermName, containerFlags, defn.BooleanType) + val retrySymbol = ctx.newSymbol(methodSymbol, lazyNme.retry, containerFlags, defn.BooleanType) val retryDef = ValDef(retrySymbol, Literal(Constants.Constant(true))) val whileCond = ref(retrySymbol) val compute = { - val handlerSymbol = ctx.newSymbol(methodSymbol, "$anonfun".toTermName, Flags.Synthetic, - MethodType(List("x$1".toTermName), List(defn.ThrowableType), defn.IntType)) + val handlerSymbol = ctx.newSymbol(methodSymbol, nme.ANON_FUN, Flags.Synthetic, + MethodType(List(nme.x_1), List(defn.ThrowableType), defn.IntType)) val caseSymbol = ctx.newSymbol(methodSymbol, nme.DEFAULT_EXCEPTION_NAME, Flags.Synthetic, defn.ThrowableType) val complete = setFlagState.appliedTo(thiz, offset, initState, Literal(Constant(ord))) val handler = CaseDef(Bind(caseSymbol, ref(caseSymbol)), EmptyTree, Block(List(complete), Throw(ref(caseSymbol)) )) - val compute = Assign(ref(resultSymbol), rhs) + val compute = ref(resultSymbol).becomes(rhs) val tr = Try(compute, List(handler), EmptyTree) - val assign = Assign(ref(target), ref(resultSymbol)) - val noRetry = Assign(ref(retrySymbol), Literal(Constants.Constant(false))) + val assign = ref(target).becomes(ref(resultSymbol)) + val noRetry = ref(retrySymbol).becomes(Literal(Constants.Constant(false))) val body = If(casFlag.appliedTo(thiz, offset, ref(flagSymbol), computeState, Literal(Constant(ord))), Block(tr :: assign :: complete :: noRetry :: Nil, Literal(Constant(()))), Literal(Constant(()))) @@ -266,38 +272,39 @@ class LazyVals extends MiniPhaseTransform with SymTransformer { } val computed = { - val noRetry = Assign(ref(retrySymbol), Literal(Constants.Constant(false))) - val result = Assign(ref(resultSymbol), ref(target)) + val noRetry = ref(retrySymbol).becomes(Literal(Constants.Constant(false))) + val result = ref(resultSymbol).becomes(ref(target)) val body = Block(noRetry :: result :: Nil, Literal(Constant(()))) CaseDef(computedState, EmptyTree, body) } + val default = CaseDef(untpd.Ident(nme.WILDCARD).withType(defn.LongType), EmptyTree, Literal(Constant(()))) + val cases = Match(stateMask.appliedTo(ref(flagSymbol), Literal(Constant(ord))), - List(compute, waitFirst, waitSecond, computed)) //todo: annotate with @switch + List(compute, waitFirst, waitSecond, computed, default)) //todo: annotate with @switch - val whileBody = Block(List(Assign(ref(flagSymbol), getFlag.appliedTo(thiz, offset))), cases) - val cycle = untpd.WhileDo(whileCond, whileBody).withTypeUnchecked(defn.UnitType) + val whileBody = List(ref(flagSymbol).becomes(getFlag.appliedTo(thiz, offset)), cases) + val cycle = WhileDo(methodSymbol, whileCond, whileBody) DefDef(methodSymbol, Block(resultDef :: retryDef :: flagDef :: cycle :: Nil, ref(resultSymbol))) } - def transformFieldValDefVolatile(x: ValDef)(implicit ctx: Context) = x match { - case ValDef(name, tpt, _) if (x.mods is Flags.Lazy) => + def transformMemberDefVolatile(x: DefDef)(implicit ctx: Context) = { assert(!(x.mods is Flags.Mutable)) - val tpe = x.tpe.widen + val tpe = x.tpe.widen.resultType.widen val claz = x.symbol.owner.asClass - val thiz = This(claz)(ctx.fresh.setOwner(claz)) + val thizClass = Literal(Constant(claz.info)) val companion = claz.companionModule val helperModule = ctx.requiredModule("dotty.runtime.LazyVals") - val getOffset = Select(ref(helperModule), RLazyVals.Names.getOffset.toTermName) + val getOffset = Select(ref(helperModule), lazyNme.RLazyVals.getOffset) var offsetSymbol: TermSymbol = null var flag: Tree = EmptyTree var ord = 0 // compute or create appropriate offsetSymol, bitmap and bits used by current ValDef - appendOffsetDefs.get(companion.name.moduleClassName) match { + appendOffsetDefs.get(companion.moduleClass) match { case Some(info) => - val flagsPerLong = 64 / RLazyVals.BITS_PER_LAZY_VAL + val flagsPerLong = 64 / dotty.runtime.LazyVals.BITS_PER_LAZY_VAL info.ord += 1 ord = info.ord % flagsPerLong val id = info.ord / flagsPerLong @@ -310,7 +317,7 @@ class LazyVals extends MiniPhaseTransform with SymTransformer { val flagName = (StdNames.nme.BITMAP_PREFIX + id.toString).toTermName val flagSymbol = ctx.newSymbol(claz, flagName, containerFlags, defn.LongType).enteredAfter(this) flag = ValDef(flagSymbol, Literal(Constants.Constant(0L))) - val offsetTree = ValDef(offsetSymbol, getOffset.appliedTo(thiz, Literal(Constant(flagName.toString)))) + val offsetTree = ValDef(offsetSymbol, getOffset.appliedTo(thizClass, Literal(Constant(flagName.toString)))) info.defs = offsetTree :: info.defs } @@ -319,20 +326,20 @@ class LazyVals extends MiniPhaseTransform with SymTransformer { val flagName = (StdNames.nme.BITMAP_PREFIX + "0").toTermName val flagSymbol = ctx.newSymbol(claz, flagName, containerFlags, defn.LongType).enteredAfter(this) flag = ValDef(flagSymbol, Literal(Constants.Constant(0L))) - val offsetTree = ValDef(offsetSymbol, getOffset.appliedTo(thiz, Literal(Constant(flagName.toString)))) - appendOffsetDefs += (companion.name.moduleClassName -> new OffsetInfo(List(offsetTree), ord)) + val offsetTree = ValDef(offsetSymbol, getOffset.appliedTo(thizClass, Literal(Constant(flagName.toString)))) + appendOffsetDefs += (companion.moduleClass -> new OffsetInfo(List(offsetTree), ord)) } - val containerName = ctx.freshName(name.toString + StdNames.nme.LAZY_LOCAL).toTermName - val containerSymbol = ctx.newSymbol(claz, containerName, (x.mods &~ Flags.Lazy | containerFlags).flags, tpe, coord = x.symbol.coord).enteredAfter(this) + val containerName = ctx.freshName(x.name ++ StdNames.nme.LAZY_LOCAL).toTermName + val containerSymbol = ctx.newSymbol(claz, containerName, (x.mods &~ containerFlagsMask | containerFlags).flags, tpe, coord = x.symbol.coord).entered val containerTree = ValDef(containerSymbol, initValue(tpe)) - val offset = Select(ref(companion), offsetSymbol.name) - val getFlag = Select(ref(helperModule), RLazyVals.Names.get.toTermName) - val setFlag = Select(ref(helperModule), RLazyVals.Names.setFlag.toTermName) - val wait = Select(ref(helperModule), RLazyVals.Names.wait4Notification.toTermName) - val state = Select(ref(helperModule), RLazyVals.Names.state.toTermName) - val cas = Select(ref(helperModule), RLazyVals.Names.cas.toTermName) + val offset = ref(companion).ensureApplied.select(offsetSymbol) + val getFlag = Select(ref(helperModule), lazyNme.RLazyVals.get) + val setFlag = Select(ref(helperModule), lazyNme.RLazyVals.setFlag) + val wait = Select(ref(helperModule), lazyNme.RLazyVals.wait4Notification) + val state = Select(ref(helperModule), lazyNme.RLazyVals.state) + val cas = Select(ref(helperModule), lazyNme.RLazyVals.cas) val accessor = mkThreadSafeDef(x.symbol.asTerm, claz, ord, containerSymbol, x.rhs, tpe, offset, getFlag, state, cas, setFlag, wait) if (flag eq EmptyTree) @@ -341,5 +348,24 @@ class LazyVals extends MiniPhaseTransform with SymTransformer { } } +object LazyVals { + object lazyNme { + object RLazyVals { + import dotty.runtime.LazyVals._ + val get = Names.get.toTermName + val setFlag = Names.setFlag.toTermName + val wait4Notification = Names.wait4Notification.toTermName + val state = Names.state.toTermName + val cas = Names.cas.toTermName + val getOffset = Names.getOffset.toTermName + } + val flag = "flag".toTermName + val result = "result".toTermName + val value = "value".toTermName + val initialized = "initialized".toTermName + val retry = "retry".toTermName + } +} + diff --git a/src/dotty/tools/dotc/transform/Memoize.scala b/src/dotty/tools/dotc/transform/Memoize.scala index 75a195032..d96a52868 100644 --- a/src/dotty/tools/dotc/transform/Memoize.scala +++ b/src/dotty/tools/dotc/transform/Memoize.scala @@ -48,38 +48,26 @@ import Decorators._ case _ => } - override def prepareForDefDef(tree: DefDef)(implicit ctx: Context) = { - val sym = tree.symbol - if (sym.isGetter && !sym.is(NoFieldNeeded)) { - // allocate field early so that initializer has the right owner for subsequeny phases in - // the group. - val maybeMutable = if (sym is Stable) EmptyFlags else Mutable - val field = ctx.newSymbol( - owner = ctx.owner, - name = sym.name.asTermName.fieldName, - flags = Private | maybeMutable, - info = sym.info.resultType, - coord = tree.pos).enteredAfter(thisTransform) - tree.rhs.changeOwnerAfter(sym, field, thisTransform) - } - this - } - override def transformDefDef(tree: DefDef)(implicit ctx: Context, info: TransformerInfo): Tree = { val sym = tree.symbol - def field = { - val field = sym.field.asTerm - assert(field.exists, i"no field for ${sym.showLocated} in ${sym.owner.info.decls.toList.map{_.showDcl}}%; %") - field - } + + def newField = ctx.newSymbol( + owner = ctx.owner, + name = sym.name.asTermName.fieldName, + flags = Private | (if (sym is Stable) EmptyFlags else Mutable), + info = sym.info.resultType, + coord = tree.pos).enteredAfter(thisTransform) + + lazy val field = sym.field.orElse(newField).asTerm if (sym.is(Accessor, butNot = NoFieldNeeded)) if (sym.isGetter) { + tree.rhs.changeOwnerAfter(sym, field, thisTransform) val fieldDef = transformFollowing(ValDef(field, tree.rhs)) val getterDef = cpy.DefDef(tree)(rhs = transformFollowingDeep(ref(field))) Thicket(fieldDef, getterDef) } else if (sym.isSetter) { - if (!sym.is(ParamAccessor)) { val Literal(Constant(())) = tree.rhs } + if (!sym.is(ParamAccessor)) { val Literal(Constant(())) = tree.rhs } // this is intended as an assertion val initializer = Assign(ref(field), ref(tree.vparamss.head.head.symbol)) cpy.DefDef(tree)(rhs = transformFollowingDeep(initializer)) } diff --git a/src/dotty/tools/dotc/transform/Mixin.scala b/src/dotty/tools/dotc/transform/Mixin.scala index 7e307c736..e20468899 100644 --- a/src/dotty/tools/dotc/transform/Mixin.scala +++ b/src/dotty/tools/dotc/transform/Mixin.scala @@ -97,7 +97,9 @@ class Mixin extends MiniPhaseTransform with SymTransformer { thisTransform => def traitDefs(stats: List[Tree]): List[Tree] = { val initBuf = new mutable.ListBuffer[Tree] stats flatMap { - case stat: DefDef if stat.symbol.isGetter && !stat.rhs.isEmpty => + case stat: DefDef if stat.symbol.isGetter && !stat.rhs.isEmpty && !stat.symbol.is(Flags.Lazy) => + // make initializer that has all effects of previous getter, + // replace getter rhs with empty tree. val vsym = stat.symbol val isym = initializer(vsym) val rhs = Block( @@ -150,7 +152,8 @@ class Mixin extends MiniPhaseTransform with SymTransformer { thisTransform => def traitInits(mixin: ClassSymbol): List[Tree] = for (getter <- mixin.info.decls.filter(getr => getr.isGetter && !wasDeferred(getr)).toList) yield { - DefDef(implementation(getter.asTerm), superRef(initializer(getter)).appliedToNone) + // transformFollowing call is needed to make memoize & lazy vals run + transformFollowing(DefDef(implementation(getter.asTerm), superRef(initializer(getter)).appliedToNone)) } def setters(mixin: ClassSymbol): List[Tree] = @@ -163,7 +166,7 @@ class Mixin extends MiniPhaseTransform with SymTransformer { thisTransform => if (cls is Trait) traitDefs(impl.body) else { val mixInits = mixins.flatMap { mixin => - traitInits(mixin) ::: superCallOpt(mixin) ::: setters(mixin) + flatten(traitInits(mixin)) ::: superCallOpt(mixin) ::: setters(mixin) } superCallOpt(superCls) ::: mixInits ::: impl.body }) diff --git a/src/dotty/tools/dotc/transform/MixinOps.scala b/src/dotty/tools/dotc/transform/MixinOps.scala index e6074323a..1dce85eaa 100644 --- a/src/dotty/tools/dotc/transform/MixinOps.scala +++ b/src/dotty/tools/dotc/transform/MixinOps.scala @@ -17,7 +17,7 @@ class MixinOps(cls: ClassSymbol, thisTransform: DenotTransformer)(implicit ctx: member.copy( owner = cls, name = member.name.stripScala2LocalSuffix, - flags = member.flags &~ Deferred &~ Module, + flags = member.flags &~ Deferred, info = cls.thisType.memberInfo(member)).enteredAfter(thisTransform).asTerm def superRef(target: Symbol, pos: Position = cls.pos): Tree = { diff --git a/src/dotty/tools/dotc/transform/ParamForwarding.scala b/src/dotty/tools/dotc/transform/ParamForwarding.scala index 87ecaba07..d017e75a2 100644 --- a/src/dotty/tools/dotc/transform/ParamForwarding.scala +++ b/src/dotty/tools/dotc/transform/ParamForwarding.scala @@ -11,7 +11,7 @@ import Contexts._, Types._, Symbols._, Flags._, TypeUtils._, DenotTransformers._ * * if * (1) x is forwarded in the supercall to a parameter that's also named `x` - * (2) the superclass parameter accessor for `x` is accessible from the current class to + * (2) the superclass parameter accessor for `x` is accessible from the current class * change the accessor to * * def x: T = super.x.asInstanceOf[T] diff --git a/src/dotty/tools/dotc/transform/SuperAccessors.scala b/src/dotty/tools/dotc/transform/SuperAccessors.scala index b111fdb92..a37b8df1f 100644 --- a/src/dotty/tools/dotc/transform/SuperAccessors.scala +++ b/src/dotty/tools/dotc/transform/SuperAccessors.scala @@ -48,7 +48,7 @@ class SuperAccessors(thisTransformer: DenotTransformer) { * of adding accessors. For instance, super calls from these regions * always have to go through an accessor. * - * The `invalidOwner` field, if different from NoSymbol, + * The `invalidEnclClass` field, if different from NoSymbol, * contains the symbol that is not a valid owner. */ private var invalidEnclClass: Symbol = NoSymbol diff --git a/src/dotty/tools/dotc/transform/TreeChecker.scala b/src/dotty/tools/dotc/transform/TreeChecker.scala index 1661f7576..7c8ba8432 100644 --- a/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -50,8 +50,7 @@ class TreeChecker extends Phase with SymTransformer { def testDuplicate(sym: Symbol, registry: mutable.Map[String, Symbol], typ: String)(implicit ctx: Context) = { val name = sym.fullName.toString - if (registry.contains(name)) - if (this.flatClasses || !(sym.isAnonymousFunction || sym.isAnonymousClass || sym.isAnonymousModuleVal)) + if (this.flatClasses && registry.contains(name)) printError(s"$typ defined twice $sym ${sym.id} ${registry(name).id}") registry(name) = sym } diff --git a/src/dotty/tools/dotc/transform/TreeExtractors.scala b/src/dotty/tools/dotc/transform/TreeExtractors.scala new file mode 100644 index 000000000..7a5c5df9d --- /dev/null +++ b/src/dotty/tools/dotc/transform/TreeExtractors.scala @@ -0,0 +1,48 @@ +package dotty.tools.dotc +package transform + +import ast.{Trees, tpd} +import core._, core.Decorators._ +import Contexts._, Flags._, Trees._, Types._, StdNames._, Symbols._ +import ValueClasses._ + +object TreeExtractors { + import tpd._ + + /** Match arg1.op(arg2) and extract (arg1, op.symbol, arg2) */ + object BinaryOp { + def unapply(t: Tree)(implicit ctx: Context): Option[(Tree, Symbol, Tree)] = t match { + case Apply(sel @ Select(arg1, _), List(arg2)) => + Some((arg1, sel.symbol, arg2)) + case _ => + None + } + } + + /** Match new C(args) and extract (C, args) */ + object NewWithArgs { + def unapply(t: Tree)(implicit ctx: Context): Option[(Type, List[Tree])] = t match { + case Apply(Select(New(_), nme.CONSTRUCTOR), args) => + Some((t.tpe, args)) + case _ => + None + } + } + + /** For an instance v of a value class like: + * class V(val underlying: X) extends AnyVal + * Match v.underlying() and extract v + */ + object ValueClassUnbox { + def unapply(t: Tree)(implicit ctx: Context): Option[Tree] = t match { + case Apply(sel @ Select(ref, _), Nil) => + val d = ref.tpe.widenDealias.typeSymbol.denot + if (isDerivedValueClass(d) && (sel.symbol eq valueClassUnbox(d.asClass))) { + Some(ref) + } else + None + case _ => + None + } + } +} diff --git a/src/dotty/tools/dotc/transform/TypeTestsCasts.scala b/src/dotty/tools/dotc/transform/TypeTestsCasts.scala index 9d827d3e0..d7fa9feaf 100644 --- a/src/dotty/tools/dotc/transform/TypeTestsCasts.scala +++ b/src/dotty/tools/dotc/transform/TypeTestsCasts.scala @@ -14,6 +14,7 @@ import typer.ErrorReporting._ import ast.Trees._ import Erasure.Boxing._ import core.TypeErasure._ +import ValueClasses._ /** This transform normalizes type tests and type casts, * also replacing type tests with singleton argument type with reference equality check @@ -90,10 +91,12 @@ trait TypeTestsCasts { } else if (argCls.isPrimitiveValueClass) unbox(qual.ensureConforms(defn.ObjectType), argType) - else + else if (isDerivedValueClass(argCls)) { + qual // adaptToType in Erasure will do the necessary type adaptation + } else derivedTree(qual, defn.Any_asInstanceOf, argType) } - def erasedArg = erasure(tree.args.head.tpe) + def erasedArg = erasure(tree.args.head.tpe, semiEraseVCs = false) if (sym eq defn.Any_isInstanceOf) transformIsInstanceOf(qual, erasedArg) else if (sym eq defn.Any_asInstanceOf) diff --git a/src/dotty/tools/dotc/transform/VCInline.scala b/src/dotty/tools/dotc/transform/VCInline.scala new file mode 100644 index 000000000..e7b16f59e --- /dev/null +++ b/src/dotty/tools/dotc/transform/VCInline.scala @@ -0,0 +1,59 @@ +package dotty.tools.dotc +package transform + +import ast.{Trees, tpd} +import core._, core.Decorators._ +import Contexts._, Trees._, StdNames._, Symbols._ +import DenotTransformers._, TreeTransforms._, Phases.Phase +import ExtensionMethods._, TreeExtractors._, ValueClasses._ + +/** This phase inlines calls to methods and fields of value classes. + * + * For a value class V defined as: + * case class V(val underlying: U) extends AnyVal + * We replace method calls by calls to the corresponding extension method: + * v.foo(args) => V.foo$extension(v.underlying(), args) + * And we avoid unnecessary allocations: + * new V(u1) == new V(u2) => u1 == u2 + * (new V(u)).underlying() => u + */ +class VCInline extends MiniPhaseTransform with IdentityDenotTransformer { + import tpd._ + + override def phaseName: String = "vcInline" + + override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[ElimErasedValueType]) + + override def transformApply(tree: Apply)(implicit ctx: Context, info: TransformerInfo): Tree = + tree match { + // new V(u1) == new V(u2) => u1 == u2 + // (We don't handle != because it has been eliminated by InterceptedMethods) + case BinaryOp(NewWithArgs(tp1, List(u1)), op, NewWithArgs(tp2, List(u2))) + if (tp1 eq tp2) && (op eq defn.Any_==) && isDerivedValueClass(tp1.typeSymbol) => + // == is overloaded in primitive classes + applyOverloaded(u1, nme.EQ, List(u2), Nil, defn.BooleanType) + + // (new V(u)).underlying() => u + case ValueClassUnbox(NewWithArgs(_, List(u))) => + u + + // (new V(u)).foo(args) => V.foo$extension(u, args) + // v.foo(args) => V.foo$extension(v.underlying(), args) + case Apply(sel @ Select(receiver, _), args) => + val classMeth = sel.symbol + if (isMethodWithExtension(classMeth)) { + val classSym = receiver.tpe.widenDealias.typeSymbol.asClass + val unboxedReceiver = receiver match { + case NewWithArgs(_, List(u)) => + u + case _ => + receiver.select(valueClassUnbox(classSym)).appliedToNone + } + val extensionMeth = extensionMethod(classMeth) + ref(extensionMeth).appliedToArgs(unboxedReceiver :: args) + } else tree + + case _ => + tree + } +} diff --git a/src/dotty/tools/dotc/transform/ValueClasses.scala b/src/dotty/tools/dotc/transform/ValueClasses.scala index a7a0db97c..8969b9321 100644 --- a/src/dotty/tools/dotc/transform/ValueClasses.scala +++ b/src/dotty/tools/dotc/transform/ValueClasses.scala @@ -13,6 +13,7 @@ import StdNames._ object ValueClasses { def isDerivedValueClass(d: SymDenotation)(implicit ctx: Context) = { + !d.isRefinementClass && d.isValueClass && (d.initial.symbol ne defn.AnyValClass) && // Compare the initial symbol because AnyVal does not exist after erasure !d.isPrimitiveValueClass @@ -34,6 +35,20 @@ object ValueClasses { .map(_.symbol) .getOrElse(NoSymbol) + /** For a value class `d`, this returns the synthetic cast from the underlying type to + * ErasedValueType defined in the companion module. This method is added to the module + * and further described in [[ExtensionMethods]]. + */ + def u2evt(d: ClassDenotation)(implicit ctx: Context): Symbol = + d.linkedClass.info.decl(nme.U2EVT).symbol + + /** For a value class `d`, this returns the synthetic cast from ErasedValueType to the + * underlying type defined in the companion module. This method is added to the module + * and further described in [[ExtensionMethods]]. + */ + def evt2u(d: ClassDenotation)(implicit ctx: Context): Symbol = + d.linkedClass.info.decl(nme.EVT2U).symbol + /** The unboxed type that underlies a derived value class */ def underlyingOfValueClass(d: ClassDenotation)(implicit ctx: Context): Type = valueClassUnbox(d).info.resultType diff --git a/src/dotty/tools/dotc/typer/Applications.scala b/src/dotty/tools/dotc/typer/Applications.scala index 2ba27d0f4..7a826fee8 100644 --- a/src/dotty/tools/dotc/typer/Applications.scala +++ b/src/dotty/tools/dotc/typer/Applications.scala @@ -330,7 +330,7 @@ trait Applications extends Compatibility { self: Typer => case arg :: Nil if isVarArg(arg) => addTyped(arg, formal) case _ => - val elemFormal = formal.argTypesLo.head + val elemFormal = formal.widenExpr.argTypesLo.head args foreach (addTyped(_, elemFormal)) makeVarArg(args.length, elemFormal) } @@ -842,7 +842,10 @@ trait Applications extends Compatibility { self: Typer => val tparams = ctx.newTypeParams(alt1.symbol, tp1.paramNames, EmptyFlags, tp1.instantiateBounds) isAsSpecific(alt1, tp1.instantiate(tparams map (_.typeRef)), alt2, tp2) case tp1: MethodType => - def repeatedToSingle(tp: Type) = if (tp.isRepeatedParam) tp.argTypesHi.head else tp + def repeatedToSingle(tp: Type): Type = tp match { + case tp @ ExprType(tp1) => tp.derivedExprType(repeatedToSingle(tp1)) + case _ => if (tp.isRepeatedParam) tp.argTypesHi.head else tp + } isApplicable(alt2, tp1.paramTypes map repeatedToSingle, WildcardType) || tp1.paramTypes.isEmpty && tp2.isInstanceOf[MethodOrPoly] case _ => diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index a2b280c6e..b58f48728 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -595,7 +595,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } } - def typedClosure(tree: untpd.Closure, pt: Type)(implicit ctx: Context) = track("typedClosure") { + def typedClosure(tree: untpd.Closure, pt: Type)(implicit ctx: Context): Tree = track("typedClosure") { val env1 = tree.env mapconserve (typed(_)) val meth1 = typedUnadapted(tree.meth) val target = |