diff --git a/compiler/src/dotty/tools/backend/jvm/LabelDefs.scala b/compiler/src/dotty/tools/backend/jvm/LabelDefs.scala index d4f09e99159a..eabe1d3b0c3b 100644 --- a/compiler/src/dotty/tools/backend/jvm/LabelDefs.scala +++ b/compiler/src/dotty/tools/backend/jvm/LabelDefs.scala @@ -75,8 +75,11 @@ import StdNames.nme * Unreachable jumps will be eliminated by local dead code analysis. * After JVM is smart enough to remove next-line jumps * - * Note that Label DefDefs can be only nested in Block, otherwise no one would - * be able to call them Other DefDefs are eliminated + * Note that his phase Ychecking this phase required softening scoping rules + * as it intentionally allowed to break scoping rules inside methods for labels. + * This is modified by setting `labelsReordered` flag in Phases. + * + * @author Dmitry Petrashko */ class LabelDefs extends MiniPhaseTransform { def phaseName: String = "labelDef" @@ -90,76 +93,24 @@ class LabelDefs extends MiniPhaseTransform { else { collectLabelDefs.clear val newRhs = collectLabelDefs.transform(tree.rhs) - val labelCalls = collectLabelDefs.labelCalls - var entryPoints = collectLabelDefs.parentLabelCalls var labelDefs = collectLabelDefs.labelDefs - var callCounts = collectLabelDefs.callCounts - - // make sure that for every label there's a single location it should return and single entry point - // if theres already a location that it returns to that's a failure - val disallowed = new mutable.HashMap[Symbol, Tree]() - queue.sizeHint(labelCalls.size + entryPoints.size) def putLabelDefsNearCallees = new TreeMap() { override def transform(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = { tree match { - case t: Apply if (entryPoints.contains(t)) => - entryPoints = entryPoints - t - labelLevel = labelLevel + 1 - val r = Block(moveLabels(t), t) - labelLevel = labelLevel - 1 - if (labelLevel == 0) beingAppended.clear() - r - case _ => if (entryPoints.nonEmpty && labelDefs.nonEmpty) super.transform(tree) else tree - } + case t: Apply if labelDefs.contains(t.symbol) => + val labelDef = labelDefs(t.symbol) + labelDefs -= t.symbol - } - } + val labelDef2 = transform(labelDef) + Block(labelDef2:: Nil, t) - def moveLabels(entryPoint: Apply): List[Tree] = { - val entrySym = entryPoint.symbol - if ((entrySym is Flags.Label) && labelDefs.contains(entrySym)) { - val visitedNow = new mutable.HashMap[Symbol, Tree]() - val treesToAppend = new ArrayBuffer[Tree]() // order matters. parents should go first - treesToAppend += labelDefs(entrySym) - queue.clear() - - var visited = 0 - queue += entryPoint - while (visited < queue.size) { - val owningLabelDefSym = queue(visited).symbol - for (call <- labelCalls(owningLabelDefSym)) { - val callSym = call.symbol - if (!beingAppended.contains(callSym)) { - if (disallowed.contains(callSym)) { - val oldCall = disallowed(callSym) - ctx.error(s"Multiple return locations for Label $oldCall and $call", callSym.pos) - } else { - if ((!visitedNow.contains(callSym)) && labelDefs.contains(callSym)) { - val defTree = labelDefs(callSym) - visitedNow.put(callSym, defTree) - val callCount = callCounts(callSym) - if (callCount > 1) { - if (!treesToAppend.contains(defTree)) { - treesToAppend += defTree - queue += call - - } - } else if (entryPoint.symbol ne callSym) entryPoints += call - } - } - } - } - - visited += 1 + case _ => if (labelDefs.nonEmpty) super.transform(tree) else tree } - beingAppended ++= treesToAppend.map(_.symbol) - treesToAppend.toList.map(putLabelDefsNearCallees.transform) - } else Nil + } } - val res = cpy.DefDef(tree)(rhs = putLabelDefsNearCallees.transform(newRhs)) res @@ -168,22 +119,11 @@ class LabelDefs extends MiniPhaseTransform { object collectLabelDefs extends TreeMap() { - // label calls from this DefDef - var parentLabelCalls: mutable.Set[Tree] = new mutable.HashSet[Tree]() - var callCounts: mutable.Map[Symbol, Int] = new mutable.HashMap[Symbol, Int]().withDefaultValue(0) - - def shouldMoveLabel = true - // labelSymbol -> Defining tree val labelDefs = new mutable.HashMap[Symbol, Tree]() - // owner -> all calls by this owner - val labelCalls = new mutable.HashMap[Symbol, mutable.Set[Tree]]() - var owner: Symbol = null def clear = { - parentLabelCalls.clear() labelDefs.clear() - labelCalls.clear() } override def transform(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = tree match { @@ -196,30 +136,14 @@ class LabelDefs extends MiniPhaseTransform { } case t: DefDef => assert(t.symbol is Flags.Label) - - val st = parentLabelCalls - parentLabelCalls = new mutable.HashSet[Tree]() - val symt = owner - owner = t.symbol - val r = super.transform(tree) - - owner = symt - labelCalls(r.symbol) = parentLabelCalls - parentLabelCalls = st - - if (shouldMoveLabel) { - labelDefs(r.symbol) = r - EmptyTree - } else r + labelDefs(r.symbol) = r + EmptyTree case t: Apply if t.symbol is Flags.Label => val sym = t.symbol - parentLabelCalls = parentLabelCalls + t - if (owner != sym) callCounts(sym) = callCounts(sym) + 1 super.transform(tree) case _ => super.transform(tree) - } } } diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 8ee016117de3..7e056431161d 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -17,6 +17,7 @@ import core.DenotTransformers.DenotTransformer import core.Denotations.SingleDenotation import dotty.tools.backend.jvm.{LabelDefs, GenBCode, CollectSuperCalls} +import dotty.tools.dotc.transform.linker.Simplify /** The central class of the dotc compiler. The job of a compiler is to create * runs, which process given `phases` in a given `rootContext`. @@ -74,6 +75,7 @@ class Compiler { new ElimByName, // Expand by-name parameter references new AugmentScala2Traits, // Expand traits defined in Scala 2.11 to simulate old-style rewritings new ResolveSuper, // Implement super accessors and add forwarders to trait methods + new Simplify, // Perform local optimizations, simplified versions of what linker does. new PrimitiveForwarders, // Add forwarders to trait methods that have a mismatch between generic and primitives new ArrayConstructors), // Intercept creation of (non-generic) arrays and intrinsify. List(new Erasure), // Rewrite types to JVM model, erasing all type parameters, abstract types and refinements. @@ -86,9 +88,10 @@ class Compiler { new NonLocalReturns, // Expand non-local returns new CapturedVars, // Represent vars captured by closures as heap objects new Constructors, // Collect initialization code in primary constructors - // Note: constructors changes decls in transformTemplate, no InfoTransformers should be added after it + // Note: constructors changes decls in transformTemplate, no InfoTransformers should be added after it new FunctionalInterfaces, // Rewrites closures to implement @specialized types of Functions. - new GetClass), // Rewrites getClass calls on primitive types. + new GetClass, // Rewrites getClass calls on primitive types. + new Simplify), // Perform local optimizations, simplified versions of what linker does. List(new LambdaLift, // Lifts out nested functions to class scope, storing free variables in environments // Note: in this mini-phase block scopes are incorrect. No phases that rely on scopes should be here new ElimStaticThis, // Replace `this` references to static objects by global identifiers diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index 5f33c29158c0..12cfc5e9e0d8 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -1190,7 +1190,9 @@ object Trees { } abstract class TreeAccumulator[X] { + // Ties the knot of the traversal: call `foldOver(x, tree))` to dive in the `tree` node. def apply(x: X, tree: Tree)(implicit ctx: Context): X + def apply(x: X, trees: Traversable[Tree])(implicit ctx: Context): X = (x /: trees)(apply) def foldOver(x: X, tree: Tree)(implicit ctx: Context): X = { def localCtx = @@ -1301,7 +1303,7 @@ object Trees { class ShallowFolder[X](f: (X, Tree) => X) extends TreeAccumulator[X] { def apply(x: X, tree: Tree)(implicit ctx: Context): X = { val x1 = f(x, tree) - if (x1.asInstanceOf[AnyRef] ne x1.asInstanceOf[AnyRef]) x1 + if (x1.asInstanceOf[AnyRef] ne x.asInstanceOf[AnyRef]) x1 else foldOver(x1, tree) } } diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index 690f18509ebd..f1337287b28a 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -454,6 +454,8 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { override val cpy: TypedTreeCopier = // Type ascription needed to pick up any new members in TreeCopier (currently there are none) new TypedTreeCopier + val cpyBetweenPhases = new TimeTravellingTreeCopier + class TypedTreeCopier extends TreeCopier { def postProcess(tree: Tree, copied: untpd.Tree): copied.ThisTree[Type] = copied.withTypeUnchecked(tree.tpe) @@ -472,16 +474,25 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { } } - override def Apply(tree: Tree)(fun: Tree, args: List[Tree])(implicit ctx: Context): Apply = - ta.assignType(untpd.cpy.Apply(tree)(fun, args), fun, args) - // Note: Reassigning the original type if `fun` and `args` have the same types as before - // does not work here: The computed type depends on the widened function type, not - // the function type itself. A treetransform may keep the function type the - // same but its widened type might change. + override def Apply(tree: Tree)(fun: Tree, args: List[Tree])(implicit ctx: Context): Apply = { + val tree1 = untpd.cpy.Apply(tree)(fun, args) + tree match { + case tree: Apply + if (fun.tpe eq tree.fun.tpe) && sameTypes(args, tree.args) => + tree1.withTypeUnchecked(tree.tpe) + case _ => ta.assignType(tree1, fun, args) + } + } - override def TypeApply(tree: Tree)(fun: Tree, args: List[Tree])(implicit ctx: Context): TypeApply = - ta.assignType(untpd.cpy.TypeApply(tree)(fun, args), fun, args) - // Same remark as for Apply + override def TypeApply(tree: Tree)(fun: Tree, args: List[Tree])(implicit ctx: Context): TypeApply = { + val tree1 = untpd.cpy.TypeApply(tree)(fun, args) + tree match { + case tree: TypeApply + if (fun.tpe eq tree.fun.tpe) && sameTypes(args, tree.args) => + tree1.withTypeUnchecked(tree.tpe) + case _ => ta.assignType(tree1, fun, args) + } + } override def Literal(tree: Tree)(const: Constant)(implicit ctx: Context): Literal = ta.assignType(untpd.cpy.Literal(tree)(const)) @@ -514,10 +525,15 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { } } - override def Closure(tree: Tree)(env: List[Tree], meth: Tree, tpt: Tree)(implicit ctx: Context): Closure = - ta.assignType(untpd.cpy.Closure(tree)(env, meth, tpt), meth, tpt) - // Same remark as for Apply + override def Closure(tree: Tree)(env: List[Tree], meth: Tree, tpt: Tree)(implicit ctx: Context): Closure = { + val tree1 = untpd.cpy.Closure(tree)(env, meth, tpt) + tree match { + case tree: Closure if sameTypes(env, tree.env) && (meth.tpe eq tree.meth.tpe) && (tpt.tpe eq tree.tpt.tpe) => + tree1.withTypeUnchecked(tree.tpe) + case _ => ta.assignType(tree1, meth, tpt) + } + } override def Match(tree: Tree)(selector: Tree, cases: List[CaseDef])(implicit ctx: Context): Match = { val tree1 = untpd.cpy.Match(tree)(selector, cases) tree match { @@ -574,6 +590,25 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { Try(tree: Tree)(expr, cases, finalizer) } + class TimeTravellingTreeCopier extends TypedTreeCopier { + override def Apply(tree: Tree)(fun: Tree, args: List[Tree])(implicit ctx: Context): Apply = + ta.assignType(untpd.cpy.Apply(tree)(fun, args), fun, args) + // Note: Reassigning the original type if `fun` and `args` have the same types as before + // does not work here: The computed type depends on the widened function type, not + // the function type itself. A treetransform may keep the function type the + // same but its widened type might change. + + override def TypeApply(tree: Tree)(fun: Tree, args: List[Tree])(implicit ctx: Context): TypeApply = + ta.assignType(untpd.cpy.TypeApply(tree)(fun, args), fun, args) + // Same remark as for Apply + + override def Closure(tree: Tree)(env: List[Tree], meth: Tree, tpt: Tree)(implicit ctx: Context): Closure = + ta.assignType(untpd.cpy.Closure(tree)(env, meth, tpt), meth, tpt) + + override def Closure(tree: Closure)(env: List[Tree] = tree.env, meth: Tree = tree.meth, tpt: Tree = tree.tpt)(implicit ctx: Context): Closure = + Closure(tree: Tree)(env, meth, tpt) + } + override def skipTransform(tree: Tree)(implicit ctx: Context) = tree.tpe.isError implicit class TreeOps[ThisTree <: tpd.Tree](val tree: ThisTree) extends AnyVal { diff --git a/compiler/src/dotty/tools/dotc/config/CompilerCommand.scala b/compiler/src/dotty/tools/dotc/config/CompilerCommand.scala index c2301a3aa252..24cfd0f7780a 100644 --- a/compiler/src/dotty/tools/dotc/config/CompilerCommand.scala +++ b/compiler/src/dotty/tools/dotc/config/CompilerCommand.scala @@ -1,4 +1,3 @@ - package dotty.tools.dotc package config diff --git a/compiler/src/dotty/tools/dotc/config/Printers.scala b/compiler/src/dotty/tools/dotc/config/Printers.scala index a77607d187e8..a8be043ea084 100644 --- a/compiler/src/dotty/tools/dotc/config/Printers.scala +++ b/compiler/src/dotty/tools/dotc/config/Printers.scala @@ -32,4 +32,5 @@ object Printers { val pickling: Printer = noPrinter val inlining: Printer = noPrinter val exhaustivity: Printer = noPrinter + val simplify: Printer = noPrinter } diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index e1d96af39e33..e32e82f1e5da 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -399,6 +399,16 @@ class Definitions { def Boolean_&& = Boolean_andR.symbol lazy val Boolean_orR = BooleanClass.requiredMethodRef(nme.ZOR) def Boolean_|| = Boolean_orR.symbol + lazy val Boolean_eqeqR = BooleanClass.info.member(nme.EQ).suchThat(_.info.firstParamTypes match { + case List(pt) => (pt isRef BooleanClass) + case _ => false + }) + def Boolean_== = Boolean_eqeqR.symbol + lazy val Boolean_neqeqR = BooleanClass.info.member(nme.NE).suchThat(_.info.firstParamTypes match { + case List(pt) => (pt isRef BooleanClass) + case _ => false + }) + def Boolean_!= = Boolean_neqeqR.symbol lazy val ByteType: TypeRef = valueTypeRef("scala.Byte", BoxedByteType, java.lang.Byte.TYPE, ByteEnc) def ByteClass(implicit ctx: Context) = ByteType.symbol.asClass @@ -430,6 +440,13 @@ class Definitions { lazy val Long_LSR_Int = LongType.member(nme.LSR).requiredSymbol( x => (x is Method) && (x.info.firstParamTypes.head isRef defn.IntClass) ) + lazy val Long_plusR = LongClass.requiredMethodRef(nme.PLUS, List(LongType)) + def Long_+ = Long_plusR.symbol + lazy val Long_mulR = LongClass.requiredMethodRef(nme.MUL, List(LongType)) + def Long_* = Long_mulR.symbol + lazy val Long_divR = LongClass.requiredMethodRef(nme.DIV, List(LongType)) + def Long_/ = Long_divR.symbol + lazy val FloatType: TypeRef = valueTypeRef("scala.Float", BoxedFloatType, java.lang.Float.TYPE, FloatEnc) def FloatClass(implicit ctx: Context) = FloatType.symbol.asClass lazy val DoubleType: TypeRef = valueTypeRef("scala.Double", BoxedDoubleType, java.lang.Double.TYPE, DoubleEnc) @@ -490,6 +507,11 @@ class Definitions { lazy val BoxedNumberClass = ctx.requiredClass("java.lang.Number") lazy val ThrowableClass = ctx.requiredClass("java.lang.Throwable") lazy val ClassCastExceptionClass = ctx.requiredClass("java.lang.ClassCastException") + lazy val ArithmeticExceptionClass = ctx.requiredClass("java.lang.ArithmeticException") + lazy val ArithmeticExceptionClass_stringConstructor = ArithmeticExceptionClass.info.member(nme.CONSTRUCTOR).suchThat(_.info.firstParamTypes match { + case List(pt) => (pt isRef StringClass) + case _ => false + }).symbol.asTerm lazy val JavaSerializableClass = ctx.requiredClass("java.io.Serializable") lazy val ComparableClass = ctx.requiredClass("java.lang.Comparable") @@ -521,6 +543,10 @@ class Definitions { def DynamicClass(implicit ctx: Context) = DynamicType.symbol.asClass lazy val OptionType: TypeRef = ctx.requiredClassRef("scala.Option") def OptionClass(implicit ctx: Context) = OptionType.symbol.asClass + lazy val SomeType: TypeRef = ctx.requiredClassRef("scala.Some") + def SomeClass(implicit ctx: Context) = SomeType.symbol.asClass + lazy val NoneModuleRef: TermRef = ctx.requiredModuleRef("scala.None") + def NoneClass(implicit ctx: Context) = NoneModuleRef.symbol.moduleClass.asClass lazy val EnumType: TypeRef = ctx.requiredClassRef("scala.Enum") def EnumClass(implicit ctx: Context) = EnumType.symbol.asClass lazy val EnumValuesType: TypeRef = ctx.requiredClassRef("scala.runtime.EnumValues") diff --git a/compiler/src/dotty/tools/dotc/core/NameKinds.scala b/compiler/src/dotty/tools/dotc/core/NameKinds.scala index d51a1d8cbfef..543b15fb1460 100644 --- a/compiler/src/dotty/tools/dotc/core/NameKinds.scala +++ b/compiler/src/dotty/tools/dotc/core/NameKinds.scala @@ -308,6 +308,9 @@ object NameKinds { val PatMatCaseName = new UniqueNameKind("case") val PatMatMatchFailName = new UniqueNameKind("matchFail") val PatMatSelectorName = new UniqueNameKind("selector") + val LocalOptFact = new UniqueNameKind("fact") + val LocalOptSelector = new UniqueNameKind("selector") + val LocalOptFallback = new UniqueNameKind("fallback") /** The kind of names of default argument getters */ val DefaultGetterName = new NumberedNameKind(DEFAULTGETTER, "DefaultGetter") { @@ -384,4 +387,4 @@ object NameKinds { def qualifiedNameKindOfTag : collection.Map[Int, QualifiedNameKind] = qualifiedNameKinds def numberedNameKindOfTag : collection.Map[Int, NumberedNameKind] = numberedNameKinds def uniqueNameKindOfSeparator: collection.Map[String, UniqueNameKind] = uniqueNameKinds -} \ No newline at end of file +} diff --git a/compiler/src/dotty/tools/dotc/core/Scopes.scala b/compiler/src/dotty/tools/dotc/core/Scopes.scala index 92ffb76d6bb1..a684a36838d1 100644 --- a/compiler/src/dotty/tools/dotc/core/Scopes.scala +++ b/compiler/src/dotty/tools/dotc/core/Scopes.scala @@ -91,8 +91,10 @@ object Scopes { /** Is the scope empty? */ def isEmpty: Boolean = lastEntry eq null - def foreach[U](p: Symbol => U)(implicit ctx: Context): Unit = toList foreach p + /** Applies a function f to all Symbols of this Scope. */ + def foreach[U](f: Symbol => U)(implicit ctx: Context): Unit = toList.foreach(f) + /** Selects all Symbols of this Scope which satisfy a predicate. */ def filter(p: Symbol => Boolean)(implicit ctx: Context): List[Symbol] = { ensureComplete() var syms: List[Symbol] = Nil @@ -105,6 +107,10 @@ object Scopes { syms } + /** Tests whether a predicate holds for at least one Symbol of this Scope. */ + def exists(p: Symbol => Boolean)(implicit ctx: Context): Boolean = filter(p).isEmpty + + /** Finds the first Symbol of this Scope satisfying a predicate, if any. */ def find(p: Symbol => Boolean)(implicit ctx: Context): Symbol = filter(p) match { case sym :: _ => sym case _ => NoSymbol diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 60c6a6ed06c0..79de21759ef8 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -434,6 +434,7 @@ object StdNames { val isArray: N = "isArray" val isDefinedAt: N = "isDefinedAt" val isDefinedAtImpl: N = "$isDefinedAt" + val isDefined: N = "isDefined" val isEmpty: N = "isEmpty" val isInstanceOf_ : N = "isInstanceOf" val java: N = "java" diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index d3865d1e0ce1..bb4ebdcf7bdd 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -467,7 +467,7 @@ object SymDenotations { name.toTermName == nme.COMPANION_CLASS_METHOD || name.toTermName == nme.COMPANION_MODULE_METHOD - /** Is this a syntetic method that represents conversions between representations of a value class + /** Is this a synthetic method that represents conversions between representations of a value class * These methods are generated in ExtensionMethods * and used in ElimErasedValueType. */ diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index d2aefc68483b..c87fb9857e8d 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -226,7 +226,8 @@ object TypeErasure { * S is last : in the linearization of the first argument type `tp1` * there are no minimal common superclasses or traits that * come after S. - * (the reason to pick last is that we prefer classes over traits that way). + * The reason to pick last is that we prefer classes over traits that way, + * which leads to more predictable bytecode and (?) faster dynamic dispatch. */ def erasedLub(tp1: Type, tp2: Type)(implicit ctx: Context): Type = tp1 match { case JavaArrayType(elem1) => @@ -245,25 +246,38 @@ object TypeErasure { case JavaArrayType(_) => defn.ObjectType case _ => val cls2 = tp2.classSymbol - @tailrec def loop(bcs: List[ClassSymbol], bestSoFar: ClassSymbol): ClassSymbol = bcs match { - case bc :: bcs1 => - if (cls2.derivesFrom(bc)) { - val newBest = if (bestSoFar.derivesFrom(bc)) bestSoFar else bc - - if (!bc.is(Trait) && bc != defn.AnyClass) - newBest - else - loop(bcs1, newBest) - } else - loop(bcs1, bestSoFar) - case nil => - bestSoFar + + /** takeWhile+1 */ + def takeUntil[T](l: List[T])(f: T => Boolean): List[T] = { + @tailrec def loop(tail: List[T], acc: List[T]): List[T] = + tail match { + case h :: t => loop(if (f(h)) t else Nil, h :: acc) + case Nil => acc.reverse + } + loop(l, Nil) + } + + // We are not interested in anything that is not a supertype of tp2 + val tp2superclasses = tp1.baseClasses.filter(cls2.derivesFrom) + + // From the spec, "Linearization also satisfies the property that a + // linearization of a class always contains the linearization of its + // direct superclass as a suffix"; it's enought to consider every + // candidate up to the first class. + val candidates = takeUntil(tp2superclasses)(!_.is(Trait)) + + // Candidates st "no other common superclass or trait derives from S" + val minimums = candidates.filter { cand => + candidates.forall(x => !x.derivesFrom(cand) || x.eq(cand)) + } + + // Pick the last minimum to prioritise classes over traits + minimums.lastOption match { + case Some(lub) if lub != defn.AnyClass && lub != defn.AnyValClass => + lub.typeRef + case _ => // Any/AnyVal only exist before erasure + defn.ObjectType } - val t = loop(tp1.baseClasses, defn.ObjectClass) - if (t eq defn.AnyValClass) - // while AnyVal is a valid common super class for primitives it does not exist after erasure - defn.ObjectType - else t.typeRef } } @@ -440,7 +454,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean // but potentially re-introduced by ResolveSuper, when we add // forwarders to mixin methods. // See doc comment for ElimByName for speculation how we could improve this. - else MethodType(Nil, Nil, eraseResult(rt)) + else MethodType(Nil, Nil, eraseResult(sym.info.finalResultType)) case tp: PolyType => eraseResult(tp.resultType) match { case rt: MethodType => rt diff --git a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala index c23ff6d2affe..f89681204054 100644 --- a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala +++ b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala @@ -174,7 +174,7 @@ class ClassfileParser( if (method) Flags.Method | methodTranslation.flags(jflags) else fieldTranslation.flags(jflags) val name = pool.getName(in.nextChar) - if (!(sflags is Flags.Private) || name == nme.CONSTRUCTOR || ctx.settings.optimise.value) { + if (!(sflags is Flags.Private) || name == nme.CONSTRUCTOR) { val member = ctx.newSymbol( getOwner(jflags), name, sflags, memberCompleter, coord = start) getScope(jflags).enter(member) diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index 8aec867921eb..3ba90409e295 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -53,7 +53,10 @@ class PlainPrinter(_ctx: Context) extends Printer { case AndType(tp1, tp2) => homogenize(tp1) & homogenize(tp2) case OrType(tp1, tp2) => - homogenize(tp1) | homogenize(tp2) + if (tp1.show > tp2.show) + homogenize(tp1) | homogenize(tp2) + else + homogenize(tp2) | homogenize(tp1) case tp: SkolemType => homogenize(tp.info) case tp: LazyRef => diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index fcda277012f3..10c6b369c136 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -125,6 +125,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { ("implicit " provided isImplicit) ~ argStr ~ " => " ~ argText(args.last) } homogenize(tp) match { + case x: ConstantType if homogenizedView => + return toText(x.widen) case AppliedType(tycon, args) => val cls = tycon.typeSymbol if (tycon.isRepeatedParam) return toTextLocal(args.head) ~ "*" diff --git a/compiler/src/dotty/tools/dotc/transform/MacroTransform.scala b/compiler/src/dotty/tools/dotc/transform/MacroTransform.scala index 9634decaa166..5ef7b4d9d28c 100644 --- a/compiler/src/dotty/tools/dotc/transform/MacroTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/MacroTransform.scala @@ -30,7 +30,7 @@ abstract class MacroTransform extends Phase { */ protected def transformPhase(implicit ctx: Context): Phase = this - class Transformer extends TreeMap { + class Transformer extends TreeMap(cpy = cpyBetweenPhases) { protected def localCtx(tree: Tree)(implicit ctx: Context) = { val sym = tree.symbol diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index 2205a104ef9c..274f114ad9a6 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -184,7 +184,10 @@ class TreeChecker extends Phase with SymTransformer { vparamss.foldRightBN(op)(withDefinedSyms(_)(_)) def assertDefined(tree: untpd.Tree)(implicit ctx: Context) = - if (tree.symbol.maybeOwner.isTerm) + if ( + tree.symbol.maybeOwner.isTerm && + !(tree.symbol.is(Label) && !tree.symbol.owner.isClass && ctx.phase.labelsReordered) // labeldefs breaks scoping + ) assert(nowDefinedSyms contains tree.symbol, i"undefined symbol ${tree.symbol}") /** assert Java classes are not used as objects */ diff --git a/compiler/src/dotty/tools/dotc/transform/TreeTransform.scala b/compiler/src/dotty/tools/dotc/transform/TreeTransform.scala index b0bd40578c17..b0eb8e425f9a 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeTransform.scala @@ -62,6 +62,8 @@ object TreeTransforms { def treeTransformPhase: Phase = phase.next + val cpy: TypedTreeCopier = cpyBetweenPhases + def prepareForIdent(tree: Ident)(implicit ctx: Context) = this def prepareForSelect(tree: Select)(implicit ctx: Context) = this def prepareForThis(tree: This)(implicit ctx: Context) = this diff --git a/compiler/src/dotty/tools/dotc/transform/linker/Analysis.scala b/compiler/src/dotty/tools/dotc/transform/linker/Analysis.scala new file mode 100644 index 000000000000..2f3ec71038dd --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/linker/Analysis.scala @@ -0,0 +1,69 @@ +package dotty.tools.dotc +package transform.linker + +import ast.Trees._ +import ast.tpd +import core.Contexts._ +import core.NameOps._ +import core.StdNames._ +import core.Symbols._ +import dotty.tools.dotc.core.Flags + +object Analysis { + import tpd._ + private val constructorWhiteList = Set( + "scala.Tuple2", + "scala.Tuple3", + "scala.Tuple4", + "scala.Tuple5", + "scala.Tuple6", + "scala.Tuple7", + "scala.Tuple8", + "scala.Tuple9", + "scala.Tuple10", + "scala.Tuple11", + "scala.Tuple12", + "scala.Tuple13", + "scala.Tuple14", + "scala.Tuple15", + "scala.Tuple16", + "scala.Tuple17", + "scala.Tuple18", + "scala.Tuple19", + "scala.Tuple20", + "scala.Tuple21", + "scala.Tuple22", + "scala.Some" + ) + + private val moduleWhiteList = constructorWhiteList.map(x => x + "$") + + private val methodsWhiteList = List( + "java.lang.Math.min", + "java.lang.Math.max", + "java.lang.Object.eq", + "java.lang.Object.ne", + "scala.Boolean.$amp$amp", + "scala.runtime.BoxesRunTime.unboxToBoolean", + "scala.runtime.BoxesRunTime.unboxToLong", + "scala.runtime.BoxesRunTime.unboxToInt", + "scala.runtime.BoxesRunTime.unboxToShort", + "scala.runtime.BoxesRunTime.unboxToDouble", + "scala.runtime.BoxesRunTime.unboxToChar", + "scala.runtime.BoxesRunTime.unboxToFloat" + ) + + def effectsDontEscape(t: Tree)(implicit ctx: Context) = { + t match { + case Apply(fun, args) if fun.symbol.isConstructor && constructorWhiteList.contains(fun.symbol.owner.fullName.toString) => + true + case Apply(fun, args) if methodsWhiteList.contains(fun.symbol.fullName.toString) => + true + case Ident(_) if t.symbol.is(Flags.Module) && (t.symbol.is(Flags.Synthetic) || moduleWhiteList.contains(t.symbol.fullName.toString)) => + true + case _ => + false + // analisys + } + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/linker/Simplify.scala b/compiler/src/dotty/tools/dotc/transform/linker/Simplify.scala new file mode 100644 index 000000000000..d1cd81fc3afd --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/linker/Simplify.scala @@ -0,0 +1,1415 @@ +package dotty.tools.dotc +package transform.linker + +import core._ +import core.Constants.Constant +import core.Contexts._ +import core.Decorators._ +import core.DenotTransformers.IdentityDenotTransformer +import core.NameOps._ +import core.StdNames._ +import core.Symbols._ +import core.Types._ +import ast.Trees._ +import ast.tpd +import scala.collection.mutable +import transform.SymUtils._ +import transform.TreeTransforms.{MiniPhaseTransform, TransformerInfo, TreeTransform} +import typer.ConstFold +import dotty.tools.dotc.config.Printers.simplify +import Flags._ + +/** This phase consists of a series of small, simple, local optimizations + * applied as a fix point transformation over Dotty Trees. + * + * The termination condition uses referential equality on Trees. Furthermore, + * termination relies of every optimization to be shrinking transformations. + */ +class Simplify extends MiniPhaseTransform with IdentityDenotTransformer { + import tpd._ + + override def phaseName: String = "simplify" + + private var SeqFactoryClass: Symbol = null + private var symmetricOperations: Set[Symbol] = null + var optimize = false + + + override val cpy = tpd.cpy + + override def prepareForUnit(tree: Tree)(implicit ctx: Context): TreeTransform = { + SeqFactoryClass = ctx.requiredClass("scala.collection.generic.SeqFactory") + symmetricOperations = Set(defn.Boolean_&&, defn.Boolean_||, defn.Int_+, defn.Int_*, defn.Long_+, defn.Long_*) + optimize = ctx.settings.optimise.value + this + } + + type Visitor = Tree => Unit + type ErasureCompatibility = Int + val BeforeErasure: ErasureCompatibility = 1 + val AfterErasure: ErasureCompatibility = 2 + val BeforeAndAfterErasure: ErasureCompatibility = BeforeErasure | AfterErasure + + val NoVisitor: Visitor = (_) => () + type Transformer = () => (Context => Tree => Tree) + + /** Every optimization is a function of the following type. + * + * - String is the optimization name (for debugging) + * + * - ErasureCompatibility is flag indicating whether this optimization can + * be run before or after Erasure (or both). + * + * - Visitor is run first to gather information on Trees (using mutation) + * + * - Transformer does the actual Tree => Tree transformation, possibly + * - using a different context from the one using in Optimization. + */ + type Optimization = (Context) => (String, ErasureCompatibility, Visitor, Transformer) + + private lazy val _optimizations: Seq[Optimization] = + inlineCaseIntrinsics :: + removeUnnecessaryNullChecks :: + inlineOptions :: + inlineLabelsCalledOnce :: + valify :: + devalify :: + jumpjump :: + dropGoodCasts :: + dropNoEffects :: + // inlineLocalObjects :: // followCases needs to be fixed, see ./tests/pos/rbtree.scala + // varify :: // varify could stop other transformations from being applied. postponed. + // bubbleUpNothing :: + constantFold :: + Nil + + // The entry point of local optimisation: DefDefs + override def transformDefDef(tree: DefDef)(implicit ctx: Context, info: TransformerInfo): Tree = { + val ctx0 = ctx + if (optimize && !tree.symbol.is(Label)) { + implicit val ctx: Context = ctx0.withOwner(tree.symbol(ctx0)) + // TODO: optimize class bodies before erasure? + var rhs0 = tree.rhs + var rhs1: Tree = null + val erasureCompatibility = if (ctx.erasedTypes) AfterErasure else BeforeErasure + while (rhs1 ne rhs0) { + rhs1 = rhs0 + val initialized = _optimizations.map(x => x(ctx.withOwner(tree.symbol))) + var (names, erasureSupport , visitors, transformers) = unzip4(initialized) + // TODO: fuse for performance + while (names.nonEmpty) { + val nextVisitor = visitors.head + val supportsErasure = erasureSupport.head + if ((supportsErasure & erasureCompatibility) > 0) { + rhs0.foreachSubTree(nextVisitor) + val nextTransformer = transformers.head() + val name = names.head + val rhst = new TreeMap() { + override def transform(tree: Tree)(implicit ctx: Context): Tree = { + val innerCtx = if (tree.isDef && tree.symbol.exists) ctx.withOwner(tree.symbol) else ctx + nextTransformer(ctx)(super.transform(tree)(innerCtx)) + } + }.transform(rhs0) + if (rhst ne rhs0) { + simplify.println(s"${tree.symbol} was simplified by ${name}: ${rhs0.show}") + simplify.println(s"became: ${rhst.show}") + } + rhs0 = rhst + } + names = names.tail + visitors = visitors.tail + erasureSupport = erasureSupport.tail + transformers = transformers.tail + } + } + if (rhs0 ne tree.rhs) tpd.cpy.DefDef(tree)(rhs = rhs0) + else tree + } else tree + } + + private def desugarIdent(i: Ident)(implicit ctx: Context): Option[Select] = { + i.tpe match { + case TermRef(prefix: TermRef, name) => + Some(ref(prefix).select(i.symbol)) + case TermRef(prefix: ThisType, name) => + Some(This(prefix.cls).select(i.symbol)) + case _ => None + } + } + + private def dropCasts(t: Tree)(implicit ctx: Context): Tree = t match { + // case TypeApply(aio@Select(rec, nm), _) if aio.symbol == defn.Any_asInstanceOf => dropCasts(rec) + case Typed(t, tpe) => t + case _ => t + } + + private def readingOnlyVals(t: Tree)(implicit ctx: Context): Boolean = dropCasts(t) match { + case Typed(exp, _) => readingOnlyVals(exp) + case TypeApply(fun @ Select(rec, _), List(tp)) => + if ((fun.symbol eq defn.Any_asInstanceOf) && rec.tpe.derivesFrom(tp.tpe.classSymbol)) + readingOnlyVals(rec) + else false + case Apply(Select(rec, _), Nil) => + def isGetterOfAImmutableField = t.symbol.isGetter && !t.symbol.is(Mutable) + def isCaseClassWithVar = t.symbol.info.decls.exists(_.is(Mutable)) + def isAccessingProductField = t.symbol.exists && + t.symbol.owner.derivesFrom(defn.ProductClass) && + t.symbol.owner.is(CaseClass) && + t.symbol.name.isSelectorName && + !isCaseClassWithVar // Conservative Covers case class A(var x: Int) + def isImmutableCaseAccessor = t.symbol.is(CaseAccessor) && !t.symbol.is(Mutable) + if (isGetterOfAImmutableField || isAccessingProductField || isImmutableCaseAccessor) + readingOnlyVals(rec) + else false + case Select(rec, _) if t.symbol.is(Method) => + if (t.symbol.isGetter && !t.symbol.is(Mutable)) readingOnlyVals(rec) // getter of a immutable field + else if (t.symbol.owner.derivesFrom(defn.ProductClass) && t.symbol.owner.is(CaseClass) && t.symbol.name.isSelectorName) { + def isImmutableField = { + val fieldId = t.symbol.name.toString.drop(1).toInt - 1 + !t.symbol.owner.caseAccessors(ctx)(fieldId).is(Mutable) + } + if (isImmutableField) readingOnlyVals(rec) // accessing a field of a product + else false + } else if (t.symbol.is(CaseAccessor) && !t.symbol.is(Mutable)) + readingOnlyVals(rec) + else false + case Select(qual, _) if !t.symbol.is(Mutable) => + readingOnlyVals(qual) + case t: Ident if !t.symbol.is(Mutable) && !t.symbol.is(Method) && !t.symbol.info.dealias.isInstanceOf[ExprType] => + desugarIdent(t) match { + case Some(t) => readingOnlyVals(t) + case None => true + } + case t: This => true + // null => false, or the following fails devalify: + // trait I { + // def foo: Any = null + // } + // object Main { + // def main = { + // val s: I = null + // s.foo + // } + // } + case Literal(Constant(null)) => false + case t: Literal => true + case _ => false + } + + /** Inline case class specific methods using desugarings assumptions. + * + * - + * - + * + * Dotty only: + * - + * + * Scala2 only: + * - + */ + val inlineCaseIntrinsics: Optimization = { implicit ctx: Context => + // Apply fun may be a side-effectful function. E.g. a block, see tests/run/t4859.scala + // we need to maintain expressions that were in this block + def evalReciever(a: Apply, res: Tree) = { + def receiver(t: Tree): Tree = t match { + case TypeApply(fun, targs) if fun.symbol eq t.symbol => receiver(fun) + case Apply(fn, args) if fn.symbol == t.symbol => receiver(fn) + case Select(qual, _) => qual + case x => x + } + val recv = receiver(a) + if (recv.isEmpty || tpd.isPureRef(recv)) + res + else + Block(recv :: Nil, res) + } + + val transformer: Transformer = () => localCtx => { + // For synthetic applies on case classes (both dotty/scalac) + // - CC.apply(args) → new CC(args) + case a: Apply if !a.tpe.isInstanceOf[MethodicType] && + a.symbol.is(Synthetic) && + a.symbol.owner.is(Module) && + (a.symbol.name == nme.apply) && + a.symbol.owner.companionClass.is(CaseClass) && + !a.tpe.derivesFrom(defn.EnumClass) && + (isPureExpr(a.fun) || a.fun.symbol.is(Synthetic)) => + + def unrollArgs(t: Tree, l: List[List[Tree]]): List[List[Tree]] = t match { + case Apply(t, args) => unrollArgs(t, args :: l) + case _ => l + } + val argss = unrollArgs(a.fun, a.args :: Nil) + def rollInArgs(l: List[List[Tree]], fun: Tree): Tree = l match { + case head :: tail => rollInArgs(tail, fun.appliedToArgs(head)) + case _ => fun + } + val constructor = a.symbol.owner.companionClass.primaryConstructor.asTerm + evalReciever(a, rollInArgs(argss.tail, New(a.tpe.widenDealias, constructor, argss.head))) + + // For synthetic dotty unapplies on case classes: + // - CC.unapply(arg): CC → arg + // - CC.unapply(arg): Boolean → true, dotty only + // - CC.unapply(arg): Option[CC] → new Some(new scala.TupleN(arg._1, ..., arg._N)) + case a: Apply if a.symbol.is(Synthetic) && + a.symbol.owner.is(Module) && + (a.symbol.name == nme.unapply) && + a.symbol.owner.companionClass.is(CaseClass) && + !a.tpe.derivesFrom(defn.EnumClass) && + (isPureExpr(a.fun) || a.fun.symbol.is(Synthetic)) => + + val args = a.args.head + val isDottyUnapply = !a.symbol.owner.is(Scala2x) + val isScalaOptionUnapply = + a.tpe.derivesFrom(defn.OptionClass) && + a.args.head.tpe.derivesFrom(a.symbol.owner.companionClass) + + if (isDottyUnapply) { // dotty only + if (a.tpe.derivesFrom(defn.BooleanClass)) + // CC.unapply(arg): Boolean → true + evalReciever(a, Literal(Constant(true))) + else + // CC.unapply(arg): CC → arg + evalReciever(a, a.args.head) + } + else if (isScalaOptionUnapply) { + // CC.unapply(arg): Option[CC] → new Some(new scala.TupleN(arg._1, ..., arg._N)) + // The output is defined as a Tree => Tree to go thought tpd.evalOnce. + def some(e: Tree) = { + val accessors = e.tpe.widenDealias.classSymbol.caseAccessors.filter(_.is(Method)) + val fields = accessors.map(x => e.select(x).ensureApplied) + val tplType = a.tpe.baseArgTypes(defn.OptionClass).head + val someTpe = a.tpe.translateParameterized(defn.OptionClass, defn.SomeClass) + + if (fields.tail.nonEmpty) + New(someTpe, New(tplType, fields) :: Nil) + else // scalac does not have Tuple1 + New(someTpe, fields.head :: Nil) + } + val none = ref(defn.NoneModuleRef) + def isNull(e: Tree) = e.select(defn.Object_eq).appliedTo(Literal(Constant(null))) + def fi(e: Tree) = If(isNull(e), none, some(e)) + evalReciever(a, evalOnce(a.args.head)(fi)) + } + else a + + // Seq.unapplySeq(arg) → new Some(arg) + // Where Seq is any companion of type <: SeqFactoryClass + case a: Apply if (a.symbol.name == nme.unapplySeq) && + a.symbol.owner.derivesFrom(SeqFactoryClass) && + a.symbol.extendedOverriddenSymbols.isEmpty && + (isPureExpr(a.fun) || a.fun.symbol.is(Synthetic)) => + + def reciever(t: Tree): Type = t match { + case t: Apply => reciever(t.fun) + case t: TypeApply => reciever(t.fun) + case t: Ident => desugarIdent(t) match { + case Some(t) => reciever(t) + case _ => NoType + } + case t: Select => t.qualifier.tpe.widenDealias + } + + val recv = reciever(a) + if (recv.typeSymbol.is(Module)) { + val someTpe = a.tpe.translateParameterized(defn.OptionClass, defn.SomeClass) + evalReciever(a, New(someTpe, a.args.head :: Nil)) + } + else a + case t => t + } + // To run this optimisation after erasure one would need to specialize it + // for constructor with outer pointer and values classes. There is probably + // no need to run this more than once. + ("inlineCaseIntrinsics", BeforeErasure, NoVisitor, transformer) + } + + /** Various constant folding. + * + * - Starts/ends with the constant folding implemented in typer (ConstFold). + * + * - (if) specific optimization that propagate booleans, negation, and factor + * out (nested) if with equivalent branches wrt to isSimilar (using &&,||). + * + * - Constant propagation over pattern matching. + */ + val constantFold: Optimization = { implicit ctx: Context => + def preEval(t: Tree) = { + if (t.isInstanceOf[Literal] || t.isInstanceOf[CaseDef] || !isPureExpr(t)) t + else { + val s = ConstFold.apply(t) + if ((s ne null) && s.tpe.isInstanceOf[ConstantType]) { + val constant = s.tpe.asInstanceOf[ConstantType].value + Literal(constant) + } else t + } + } + + def isSimilar(t1: Tree, t2: Tree): Boolean = t1 match { + case t1: Apply => + t2 match { + case t2: Apply => + (t1.symbol == t2.symbol) && + (t1.args zip t2.args).forall(x => isSimilar(x._1, x._2)) && + isSimilar(t1.fun, t2.fun) + case _ => false + } + case t1: Ident => + desugarIdent(t1) match { + case Some(t) => + val t2i = t2 match { + case t2: Ident => desugarIdent(t2).getOrElse(t2) + case _ => t2 + } + isSimilar(t, t2i) + case None => t1.symbol eq t2.symbol + } + case t1: Select => t2 match { + case t2: Select => + (t1.symbol eq t2.symbol) && + isSimilar(t1.qualifier, t2.qualifier) + case t2: Ident => desugarIdent(t2) match { + case Some(t2) => isSimilar(t1, t2) + case None => false + } + case _ => false + } + case t1: Literal => t2 match { + case t2: Literal => + t1.const.tag == t2.const.tag && + t1.const.value == t2.const.value + case _ => false + } + case _ => false + } + + def isBool(tpe: Type): Boolean = tpe.derivesFrom(defn.BooleanClass) + def isConst(tpe: Type): Boolean = tpe.isInstanceOf[ConstantType] + def asConst(tpe: Type): ConstantType = tpe.asInstanceOf[ConstantType] + + val transformer: Transformer = () => localCtx => { x => preEval(x) match { + // TODO: include handling of isInstanceOf similar to one in IsInstanceOfEvaluator + // TODO: include methods such as Int.int2double(see ./tests/pos/harmonize.scala) + case If(cond1, thenp, elsep) if isSimilar(thenp, elsep) => + Block(cond1 :: Nil, thenp) + + case If(cond1, If(cond2, thenp2, elsep2), elsep1) if isSimilar(elsep1, elsep2) => + If(cond1.select(defn.Boolean_&&).appliedTo(cond2), thenp2, elsep1) + + case If(cond1, If(cond2, thenp2, elsep2), elsep1) if isSimilar(elsep1, thenp2) => + If(cond1.select(defn.Boolean_!).ensureApplied.select(defn.Boolean_||).appliedTo(cond2), elsep1, elsep2) + + case If(cond1, thenp1, If(cond2, thenp2, elsep2)) if isSimilar(thenp1, thenp2) => + If(cond1.select(defn.Boolean_||).appliedTo(cond2), thenp1, elsep2) + + case If(cond1, thenp1, If(cond2, thenp2, elsep2)) if isSimilar(thenp1, elsep2) => + If(cond1.select(defn.Boolean_||).appliedTo(cond2.select(defn.Boolean_!).ensureApplied), thenp1, thenp2) + + case If(t: Literal, thenp, elsep) => + if (t.const.booleanValue) thenp + else elsep + + case ift @ If(cond, thenp: Literal, elsep: Literal) + if isBool(ift.tpe) && thenp.const.booleanValue && !elsep.const.booleanValue => + cond + + // the lower two are disabled, as it may make the isSimilar rule not apply for a nested structure of iffs. + // see the example below: + // (b1, b2) match { + // case (true, true) => true + // case (false, false) => true + // case _ => false + // } + // case ift @ If(cond, thenp: Literal, elsep) + // if isBool(ift.tpe) && thenp.const.booleanValue => + // if (thenp.const.booleanValue) + // cond.select(defn.Boolean_||).appliedTo(elsep) + // else // thenp is false, this tree is bigger then the original + // cond.select(defn.Boolean_!).ensureApplied.select(defn.Boolean_&&).appliedTo(elsep) + // case ift @ If(cond, thenp, elsep :Literal) if + // isBool(ift.tpe) && !elsep.const.booleanValue => + // cond.select(defn.Boolean_&&).appliedTo(elsep) + // the other case ins't handled intentionally. See previous case for explanation + + case If(t @ Select(recv, _), thenp, elsep) if t.symbol eq defn.Boolean_! => + If(recv, elsep, thenp) + + case If(t @ Apply(Select(recv, _), Nil), thenp, elsep) if t.symbol eq defn.Boolean_! => + If(recv, elsep, thenp) + + // TODO: similar trick for comparisons. + // TODO: handle comparison with min\max values + case Apply(meth1 @ Select(Apply(meth2 @ Select(rec, _), Nil), _), Nil) + if meth1.symbol == defn.Boolean_! && meth2.symbol == defn.Boolean_! => + rec + + case meth1 @ Select(meth2 @ Select(rec, _), _) + if meth1.symbol == defn.Boolean_! && meth2.symbol == defn.Boolean_! && !ctx.erasedTypes => + rec + + case t @ Apply(Select(lhs, _), List(rhs)) => + val sym = t.symbol + (lhs, rhs) match { + case (lhs, Literal(_)) if !lhs.isInstanceOf[Literal] && symmetricOperations.contains(sym) => + rhs.select(sym).appliedTo(lhs) + case (l, _) if (sym == defn.Boolean_&&) && isConst(l.tpe) => + val const = asConst(l.tpe).value.booleanValue + if (const) Block(lhs :: Nil, rhs) + else l + + case (l, x: Literal) if sym == defn.Boolean_== && isBool(l.tpe) && isBool(x.tpe) => + if (x.const.booleanValue) l + else l.select(defn.Boolean_!).ensureApplied + + case (l, x: Literal) if sym == defn.Boolean_!= && isBool(l.tpe) && isBool(x.tpe) => + if (!x.const.booleanValue) l + else l.select(defn.Boolean_!).ensureApplied + + case (x: Literal, l) if sym == defn.Boolean_== && isBool(l.tpe) && isBool(x.tpe) => + if (x.const.booleanValue) l + else l.select(defn.Boolean_!).ensureApplied + + case (x: Literal, l) if sym == defn.Boolean_!= && isBool(l.tpe) && isBool(x.tpe) => + if (!x.const.booleanValue) l + else l.select(defn.Boolean_!).ensureApplied + + case (l: Literal, _) if (sym == defn.Boolean_||) && isConst(l.tpe) => + val const = asConst(l.tpe).value.booleanValue + if (l.const.booleanValue) l + else Block(lhs :: Nil, rhs) + + // case (Literal(Constant(1)), _) if sym == defn.Int_* => rhs + // case (Literal(Constant(0)), _) if sym == defn.Int_+ => rhs + // case (Literal(Constant(1L)), _) if sym == defn.Long_* => rhs + // case (Literal(Constant(0L)), _) if sym == defn.Long_+ => rhs + // // TODO: same for float, double, short + // // TODO: empty string concat + // // TODO: disctribute & reorder constants + // // TODO: merge subsequent casts + // case (_, Literal(Constant(1))) if sym == defn.Int_/ => lhs + // case (_, Literal(Constant(1L))) if sym == defn.Long_/ => lhs + // case (_, Literal(Constant(0))) if sym == defn.Int_/ => + // Block(List(lhs), + // ref(defn.throwMethod).appliedTo(New(defn.ArithmeticExceptionClass.typeRef, defn.ArithmeticExceptionClass_stringConstructor, Literal(Constant("/ by zero")) :: Nil))) + // case (_, Literal(Constant(0L))) if sym == defn.Long_/ => + // Block(List(lhs), + // ref(defn.throwMethod).appliedTo(New(defn.ArithmeticExceptionClass.typeRef, defn.ArithmeticExceptionClass_stringConstructor, Literal(Constant("/ by zero")) :: Nil))) + + case _ => t + } + + // This case can only be triggered when running Simplify before pattern matching: + // case t: Match + // if t.selector.tpe.isInstanceOf[ConstantType] && + // t.cases.forall { x => + // x.pat.tpe.isInstanceOf[ConstantType] || (isWildcardArg(x.pat) && x.guard.isEmpty) + // } => + // val selectorValue = t.selector.tpe.asInstanceOf[ConstantType].value + // val better = t.cases.find(x => isWildcardArg(x.pat) || (x.pat.tpe.asInstanceOf[ConstantType].value eq selectorValue)) + // if (better.nonEmpty) better.get.body + // else t + + case t: Literal => t + case t: CaseDef => t + case t if !isPureExpr(t) => t + case t => + val s = ConstFold.apply(t) + if ((s ne null) && s.tpe.isInstanceOf[ConstantType]) { + val constant = s.tpe.asInstanceOf[ConstantType].value + Literal(constant) + } else t + }} + ("constantFold", BeforeAndAfterErasure, NoVisitor, transformer) + } + + /** Inline case classes as vals, this essentially (local) implements multi + * parameter value classes. The main motivation is to get ride of all the + * intermediate tuples coming from pattern matching expressions. + */ + val inlineLocalObjects: Optimization = { implicit ctx: Context => + // In the end only calls constructor. Reason for unconditional inlining + val hasPerfectRHS = mutable.HashMap[Symbol, Boolean]() + // If all values have perfect RHS than key has perfect RHS + val checkGood = mutable.HashMap[Symbol, Set[Symbol]]() + val forwarderWritesTo = mutable.HashMap[Symbol, Symbol]() + val gettersCalled = mutable.HashSet[Symbol]() + def followTailPerfect(t: Tree, symbol: Symbol): Unit = { + t match { + case Block(_, expr) => followTailPerfect(expr, symbol) + case If(_, thenp, elsep) => followTailPerfect(thenp, symbol); followTailPerfect(elsep, symbol); + case Apply(fun, _) if fun.symbol.isConstructor && t.tpe.widenDealias == symbol.info.widenDealias.finalResultType.widenDealias => + hasPerfectRHS(symbol) = true + case Apply(fun, _) if fun.symbol.is(Label) && (fun.symbol ne symbol) => + checkGood.put(symbol, checkGood.getOrElse(symbol, Set.empty) + fun.symbol) + // assert(forwarderWritesTo.getOrElse(t.symbol, symbol) == symbol) + forwarderWritesTo(t.symbol) = symbol + case t: Ident if !t.symbol.owner.isClass && (t.symbol ne symbol) => + checkGood.put(symbol, checkGood.getOrElse(symbol, Set.empty) + t.symbol) + case _ => + } + } + val visitor: Visitor = { + case vdef: ValDef if (vdef.symbol.info.classSymbol is CaseClass) && + !vdef.symbol.is(Lazy) && + !vdef.symbol.info.classSymbol.caseAccessors.exists(x => x.is(Mutable)) => + followTailPerfect(vdef.rhs, vdef.symbol) + case Assign(lhs, rhs) if !lhs.symbol.owner.isClass => + checkGood.put(lhs.symbol, checkGood.getOrElse(lhs.symbol, Set.empty) + rhs.symbol) + case t @ Select(qual, _) if (t.symbol.isGetter && !t.symbol.is(Mutable)) || + (t.symbol.maybeOwner.derivesFrom(defn.ProductClass) && t.symbol.maybeOwner.is(CaseClass) && t.symbol.name.isSelectorName) || + (t.symbol.is(CaseAccessor) && !t.symbol.is(Mutable)) => + gettersCalled(qual.symbol) = true + case t: DefDef if t.symbol.is(Label) => + followTailPerfect(t.rhs, t.symbol) + case _ => + } + + val transformer: Transformer = () => { + var hasChanged = true + while(hasChanged) { + hasChanged = false + checkGood.foreach{case (key, values) => + values.foreach { value => + if (hasPerfectRHS.getOrElse(key, false)) { + hasChanged = !hasPerfectRHS.put(value, true).getOrElse(false) + } + } + } + } + + val newMappings: Map[Symbol, Map[Symbol, Symbol]] = + hasPerfectRHS.iterator.map(x => x._1).filter(x => !x.is(Method) && !x.is(Label) && gettersCalled.contains(x.symbol) && (x.symbol.info.classSymbol is CaseClass)) + .map { refVal => + simplify.println(s"replacing ${refVal.symbol.fullName} with stack-allocated fields") + var accessors = refVal.info.classSymbol.caseAccessors.filter(_.isGetter) // TODO: drop mutable ones + if (accessors.isEmpty) accessors = refVal.info.classSymbol.caseAccessors + val productAccessors = (1 to accessors.length).map(i => refVal.info.member(nme.productAccessorName(i)).symbol) // TODO: disambiguate + val newLocals = accessors.map(x => + // TODO: it would be nice to have an additional optimization that + // TODO: is capable of turning those mutable ones into immutable in common cases + ctx.newSymbol(ctx.owner.enclosingMethod, (refVal.name + "$" + x.name).toTermName, Synthetic | Mutable, x.asSeenFrom(refVal.info).info.finalResultType.widenDealias) + ) + val fieldMapping = accessors zip newLocals + val productMappings = productAccessors zip newLocals + (refVal, (fieldMapping ++ productMappings).toMap) + }.toMap + val toSplit: mutable.Set[Symbol] = mutable.Set.empty ++ newMappings.keySet + + def splitWrites(t: Tree, target: Symbol): Tree = { + t match { + case tree@ Block(stats, expr) => tpd.cpy.Block(tree)(stats, splitWrites(expr, target)) + case tree@ If(_, thenp, elsep) => tpd.cpy.If(tree)(thenp = splitWrites(thenp, target), elsep = splitWrites(elsep, target)) + case Apply(sel , args) if sel.symbol.isConstructor && t.tpe.widenDealias == target.info.widenDealias.finalResultType.widenDealias => + val fieldsByAccessors = newMappings(target) + var accessors = target.info.classSymbol.caseAccessors.filter(_.isGetter) // TODO: when is this filter needed? + if (accessors.isEmpty) accessors = target.info.classSymbol.caseAccessors + val assigns = (accessors zip args) map (x => ref(fieldsByAccessors(x._1)).becomes(x._2)) + val recreate = sel.appliedToArgs(accessors.map(x => ref(fieldsByAccessors(x)))) + Block(assigns, recreate) + case Apply(fun, _) if fun.symbol.is(Label) => + t // Do nothing. It will do on its own. + case t: Ident if !t.symbol.owner.isClass && newMappings.contains(t.symbol) && t.symbol.info.classSymbol == target.info.classSymbol => + val fieldsByAccessorslhs = newMappings(target) + val fieldsByAccessorsrhs = newMappings(t.symbol) + val accessors = target.info.classSymbol.caseAccessors.filter(_.isGetter) + val assigns = accessors map (x => ref(fieldsByAccessorslhs(x)).becomes(ref(fieldsByAccessorsrhs(x)))) + Block(assigns, t) + // If `t` is itself split, push writes. + case _ => + evalOnce(t){ev => + if (ev.tpe.derivesFrom(defn.NothingClass)) ev + else { + val fieldsByAccessors = newMappings(target) + val accessors = target.info.classSymbol.caseAccessors.filter(_.isGetter) + val assigns = accessors map (x => ref(fieldsByAccessors(x)).becomes(ev.select(x))) + Block(assigns, ev) + } + } // Need to eval-once and update fields. + + } + } + + def followCases(t: Symbol, limit: Int = 0): Symbol = if (t.symbol.is(Label)) { + // TODO: this can create cycles, see ./tests/pos/rbtree.scala + if (limit > 100 && limit > forwarderWritesTo.size + 1) NoSymbol + // There may be cycles in labels, that never in the end write to a valdef(the value is always on stack) + // there's not much we can do here, except finding such cases and bailing out + // there may not be a cycle bigger that hashmapSize > 1 + else followCases(forwarderWritesTo.getOrElse(t.symbol, NoSymbol), limit + 1) + } else t + + hasPerfectRHS.clear() + // checkGood.clear() + gettersCalled.clear() + + val res: Context => Tree => Tree = {localCtx => (t: Tree) => t match { + case ddef: DefDef if ddef.symbol.is(Label) => + newMappings.get(followCases(ddef.symbol)) match { + case Some(mappings) => + tpd.cpy.DefDef(ddef)(rhs = splitWrites(ddef.rhs, followCases(ddef.symbol))) + case _ => ddef + } + case a: ValDef if toSplit.contains(a.symbol) => + toSplit -= a.symbol + // Break ValDef apart into fields + boxed value + val newFields = newMappings(a.symbol).values.toSet + Thicket( + newFields.map(x => ValDef(x.asTerm, defaultValue(x.symbol.info.widenDealias))).toList ::: + List(tpd.cpy.ValDef(a)(rhs = splitWrites(a.rhs, a.symbol)))) + case ass: Assign => + newMappings.get(ass.lhs.symbol) match { + case None => ass + case Some(mapping) => + val updates = mapping.filter(x => x._1.is(CaseAccessor)).map(x => ref(x._2).becomes(ref(ass.lhs.symbol).select(x._1))).toList + Thicket(ass :: updates) + } + case sel @ Select(rec, _) if (t.symbol.isGetter && !t.symbol.is(Mutable)) || + (t.symbol.maybeOwner.derivesFrom(defn.ProductClass) && t.symbol.owner.is(CaseClass) && t.symbol.name.isSelectorName) || + (t.symbol.is(CaseAccessor) && !t.symbol.is(Mutable)) => + newMappings.getOrElse(rec.symbol, Map.empty).get(sel.symbol) match { + case None => t + case Some(newSym) => ref(newSym) + } + case t => t + }} + + res + } + ("inlineLocalObjects", BeforeAndAfterErasure, visitor, transformer) + } + + private def collectTypeTests(t: Tree)(implicit ctx: Context): List[(Symbol, Type)] = { + def recur(t: Tree): List[(Symbol, Type)] = + t match { + case Apply(x, _) if (x.symbol == defn.Boolean_! || x.symbol == defn.Boolean_||) => List.empty + case Apply(fun @ Select(x, _), y) if (fun.symbol == defn.Boolean_&&) => recur(x) ++ recur(y.head) + case TypeApply(fun @ Select(x, _), List(tp)) if fun.symbol eq defn.Any_isInstanceOf => + if (x.symbol.exists && !x.symbol.owner.isClass && !x.symbol.is(Method|Mutable)) + (x.symbol, tp.tpe) :: Nil + else Nil + case _ => List.empty + } + recur(t) + } + + private def collectNullTests(t: Tree)(implicit ctx: Context): List[Symbol] = { + def recur(t: Tree): List[Symbol] = + t match { + case Apply(x, _) if (x.symbol == defn.Boolean_! || x.symbol == defn.Boolean_||) => List.empty + case Apply(fun @ Select(x, _), y) if (fun.symbol == defn.Boolean_&&) => recur(x) ++ recur(y.head) + case Apply(fun @ Select(x, _), List(tp)) if fun.symbol eq defn.Object_ne => + if (x.symbol.exists && !x.symbol.owner.isClass && !x.symbol.is(Method|Mutable)) + x.symbol :: Nil + else Nil + case _ => List.empty + } + recur(t) + } + + /** Eliminated casts and equality tests whose results can be locally + * determined at compile time: + * + * - a.asInstanceOf[T] → a when we know that a: T + * - Simplify (a == null) and (a != null) when the result is statically known + */ + val dropGoodCasts: Optimization = { implicit ctx: Context => + val transformer: Transformer = () => localCtx => { + case t @ If(cond, thenp, elsep) => + val newTypeTested = collectTypeTests(cond) + val nullTested = collectNullTests(cond).toSet + val testedMap = newTypeTested.foldRight[Map[Symbol, List[Type]]](Map.empty) { case (x, y) => + y + ((x._1, x._2 :: y.getOrElse(x._1, Nil))) + } + val dropGoodCastsInStats = new TreeMap() { + override def transform(tree: Tree)(implicit ctx: Context): Tree = { + def applyCondition(fun: Select, tree: Tree, const: Constant): Boolean = + const.tag == Constants.NullTag && + (fun.symbol == defn.Object_eq || fun.symbol == defn.Object_ne) && + (nullTested.contains(tree.symbol)) + + def applyBody(fun: Select): Tree = + if (fun.symbol == defn.Object_eq) Literal(Constant(false)) + else Literal(Constant(true)) + + super.transform(tree) match { + case t: Block => + val nstats = t.stats.filterConserve({ + case TypeApply(fun @ Select(rec, _), List(tp)) + if fun.symbol == defn.Any_asInstanceOf => + !testedMap.getOrElse(rec.symbol, Nil).exists(x => x <:< tp.tpe) + case _ => true + }) + if (nstats eq t.stats) t + else Block(nstats, t.expr) + case Apply(fun @ Select(lhs, _), List(Literal(const))) if applyCondition(fun, lhs, const) => + applyBody(fun) + case Apply(fun @ Select(Literal(const), _), List(rhs)) if applyCondition(fun, rhs, const) => + applyBody(fun) + case t => t + } + } + } + val nthenp = dropGoodCastsInStats.transform(thenp) + + tpd.cpy.If(t)(thenp = nthenp, elsep = elsep) + case t => t + } + ("dropGoodCasts", BeforeAndAfterErasure, NoVisitor, transformer) + } + + /** Eliminated null checks based on the following observations: + * + * - (this) cannot be null + * - (new C) cannot be null + * - literal is either null itself or non null + * - fallsback to `tpe.isNotNull`, which will eventually be true for non nullable types. + * - in (a.call; a == null), the first call throws a NPE if a is null; the test can be removed. + */ + val removeUnnecessaryNullChecks: Optimization = { implicit ctx: Context => + val initializedVals = mutable.HashSet[Symbol]() + val checkGood = mutable.HashMap[Symbol, Set[Symbol]]() + def isGood(t: Symbol) = { + t.exists && initializedVals.contains(t) && { + var changed = true + var set = Set(t) + while (changed) { + val oldSet = set + set = set ++ set.flatMap(x => checkGood.getOrElse(x, Nil)) + changed = set != oldSet + } + !set.exists(x => !initializedVals.contains(x)) + } + } + val visitor: Visitor = { + case vd: ValDef => + val rhs = vd.rhs + val rhsName = rhs.symbol.name + if (!vd.symbol.is(Mutable) && !rhs.isEmpty) { + def checkNonNull(t: Tree, target: Symbol): Boolean = t match { + case Block(_ , expr) => checkNonNull(expr, target) + case If(_, thenp, elsep) => checkNonNull(thenp, target) && checkNonNull(elsep, target) + case t: New => true + case t: Apply if t.symbol.isPrimaryConstructor => true + case t: Literal => t.const.value != null + case t: This => true + case t: Ident if !t.symbol.owner.isClass => + checkGood.put(target, checkGood.getOrElse(target, Set.empty) + t.symbol) + true + case t: Apply if !t.symbol.owner.isClass => + checkGood.put(target, checkGood.getOrElse(target, Set.empty) + t.symbol) + true + case t: Typed => + checkNonNull(t.expr, target) + case _ => t.tpe.isNotNull + } + if (checkNonNull(vd.rhs, vd.symbol)) + initializedVals += vd.symbol + } + case t: Tree => + } + + def isNullLiteral(tree: Tree) = tree match { + case literal: Literal => + literal.const.tag == Constants.NullTag + case _ => false + } + val transformer: Transformer = () => localCtx0 => { + implicit val ctx: Context = localCtx0 + val transformation: Tree => Tree = { + case check@Apply(Select(lhs, _), List(rhs)) => + val sym = check.symbol + if ( ((sym == defn.Object_eq) || (sym == defn.Object_ne)) && + ((isNullLiteral(lhs) && isGood(rhs.symbol)) || (isNullLiteral(rhs) && isGood(lhs.symbol)))) { + if (sym == defn.Object_eq) Block(List(lhs, rhs), Literal(Constant(false))) + else if(sym == defn.Object_ne) Block(List(lhs, rhs), Literal(Constant(true))) + else check + } else check + case t => t + } + transformation + } + ("removeUnnecessaryNullChecks", BeforeErasure, visitor, + transformer) + } + + /** Every pure statement preceding a ??? can be removed. + * + * This optimization makes it rather tricky meaningful examples since the + * compiler will often be able to reduce them to a single main with ???... + */ + val bubbleUpNothing: Optimization = { implicit ctx: Context => + object Notathing { + def unapply(t: Tree): Option[Tree] = Option(lookup(t)) + def lookup(t: Tree): Tree = t match { + case x if x.tpe.derivesFrom(defn.NothingClass) => t + case Typed(x, _) => lookup(x) + case Block(_, x) => lookup(x) + case _ => null + } + } + def notathing(t: Tree): Boolean = t match { + case Notathing(_) => true + case _ => false + } + val transformer: Transformer = () => localCtx => { + case t @ Apply(Select(Notathing(qual), _), args) => + Typed(qual, TypeTree(t.tpe)) + // This case leads to complications with multiple argument lists, + // how to do you rewrites tree.witType(???)(ctx).withType(???)(ctx) + // using Ycheckable steps? + + // Solution: only transform when having a complete application, + // steal code from tailRec + + // case t @ Apply(Select(qual, _), args) if args.exists(notathing) => + // val (keep, noth :: other) = args.span(x => !notathing(x)) + // Block(qual :: keep, Typed(noth, TypeTree(t.tpe))) + case Assign(_, rhs) if notathing(rhs) => + rhs + case t @ If(Notathing(cond), _, _) => + Typed(cond, TypeTree(t.tpe)) + case b: Block if b.stats.exists(x => !x.isDef && notathing(x)) => + val (keep, noth :: other) = b.stats.span(x => x.isDef || !notathing(x)) + val keepDefs = other.filter(x => x.isDef) + val body = keep ::: keepDefs + Typed(Block(body, noth), TypeTree(b.tpe)) + case t => t + } + ("bubbleUpNothing", BeforeErasure, NoVisitor, transformer) + } + + private def keepOnlySideEffects(t: Tree)(implicit ctx: Context): Tree = { + t match { + case l: Literal => + EmptyTree + case t: This => + EmptyTree + case Typed(exp, tpe) => + keepOnlySideEffects(exp) + case t @ If(cond, thenp, elsep) => + val nthenp = keepOnlySideEffects(thenp) + val nelsep = keepOnlySideEffects(elsep) + if (thenp.isEmpty && elsep.isEmpty) keepOnlySideEffects(cond) + else tpd.cpy.If(t)( + thenp = nthenp.orElse(if (thenp.isInstanceOf[Literal]) thenp else unitLiteral), + elsep = nelsep.orElse(if (elsep.isInstanceOf[Literal]) elsep else unitLiteral)) + case Select(rec, _) if + (t.symbol.isGetter && !t.symbol.is(Mutable | Lazy)) || + (t.symbol.owner.derivesFrom(defn.ProductClass) && t.symbol.owner.is(CaseClass) && t.symbol.name.isSelectorName) || + (t.symbol.is(CaseAccessor) && !t.symbol.is(Mutable)) => + keepOnlySideEffects(rec) // Accessing a field of a product + case s @ Select(qual, name) if + // !name.eq(nme.TYPE_) && // Keep the .TYPE added by ClassOf, would be needed for AfterErasure + !t.symbol.is(Mutable | Lazy) && !t.symbol.is(Method) => + keepOnlySideEffects(qual) + case Block(List(t: DefDef), s: Closure) => + EmptyTree + case bl @ Block(stats, expr) => + val stats1 = stats.mapConserve(keepOnlySideEffects) + val stats2 = if (stats1 ne stats) stats1.filter(x=>x ne EmptyTree) else stats1 + val expr2: Tree = expr match { + case t: Literal if t.tpe.derivesFrom(defn.UnitClass) => expr + case _ => keepOnlySideEffects(expr).orElse(unitLiteral) + } + tpd.cpy.Block(bl)(stats2, expr2) + case t: Ident if !t.symbol.is(Method | Lazy) && !t.symbol.info.isInstanceOf[ExprType] || Analysis.effectsDontEscape(t) => + desugarIdent(t) match { + case Some(t) if !(t.qualifier.symbol.is(Flags.JavaDefined) && t.qualifier.symbol.is(Flags.Package)) => t + case _ => EmptyTree + } + case app: Apply if app.fun.symbol.is(Label) && !app.tpe.finalResultType.derivesFrom(defn.UnitClass) => + // This is "the scary hack". It changes the return type to Unit, then + // invalidates the denotation cache. Because this optimization only + // operates locally, this should be fine. + val denot = app.fun.symbol.denot + if (!denot.info.finalResultType.derivesFrom(defn.UnitClass)) { + val newLabelType = app.symbol.info match { + case mt: MethodType => + mt.derivedLambdaType(mt.paramNames, mt.paramInfos, defn.UnitType) + case et: ExprType => + et.derivedExprType(defn.UnitType) + } + val newD = app.symbol.asSymDenotation.copySymDenotation(info = newLabelType) + newD.installAfter(this) + } + + ref(app.symbol).appliedToArgs(app.args) + case t @ Apply(fun, _) if Analysis.effectsDontEscape(t) => + def getArgsss(a: Tree): List[Tree] = a match { + case a: Apply => getArgsss(a.fun) ::: a.args + case _ => Nil + } + def getSel(t: Tree): Tree = {t match { + case t: Apply => getSel(t.fun) + case t: Select => t.qualifier + case t: TypeApply => getSel(t.fun) + case _ => t + }} + val args = getArgsss(t) + val rec = getSel(t) + val prefix = rec match { + case t: New => + args.map(keepOnlySideEffects) + case _ => + rec :: args.map(keepOnlySideEffects) + } + Block(prefix, unitLiteral) + case t @ TypeApply(Select(rec, _), List(testType)) if t.symbol.eq(defn.Any_asInstanceOf) && testType.tpe.widenDealias.typeSymbol.exists => + val receiverType = TypeErasure.erasure(rec.tpe) + val erazedTestedType = TypeErasure.erasure(testType.tpe) + if (receiverType.derivesFrom(erazedTestedType.typeSymbol)) + EmptyTree + else t + case _ => t + } + } + + /** Removes side effect free statements in blocks. */ + val dropNoEffects: Optimization = { implicit ctx: Context => + val transformer: Transformer = () => localCtx => { + case Block(Nil, expr) => expr + case a: Block => + val newStats0 = a.stats.mapConserve(keepOnlySideEffects) + val newStats1 = if (newStats0 eq a.stats) newStats0 else newStats0.flatMap { + case x: Block => x.stats ::: List(x.expr) + case EmptyTree => Nil + case t => t :: Nil + } + val (newStats2, newExpr) = a.expr match { + case Block(stats2, expr) => (newStats1 ++ stats2, expr) + case _ => (newStats1, a.expr) + } + if (newStats2.nonEmpty) + tpd.cpy.Block(a)(stats = newStats2, newExpr) + else newExpr + case a: DefDef => + if (a.symbol.info.finalResultType.derivesFrom(defn.UnitClass) && + !a.rhs.tpe.derivesFrom(defn.UnitClass) && + !a.rhs.tpe.derivesFrom(defn.NothingClass)) { + def insertUnit(t: Tree) = { + if (!t.tpe.derivesFrom(defn.UnitClass)) Block(t :: Nil, unitLiteral) + else t + } + tpd.cpy.DefDef(a)(rhs = insertUnit(keepOnlySideEffects(a.rhs)), tpt = TypeTree(defn.UnitType)) + } else a + case t => t + } + // BoxedUnit messes up this phase after erasure + ("dropNoEffects", BeforeErasure, NoVisitor, transformer) + } + + /** Inlines LabelDef which are used exactly once. */ + val inlineLabelsCalledOnce: Optimization = { implicit ctx: Context => + val timesUsed = mutable.HashMap[Symbol, Int]() + val defined = mutable.HashMap[Symbol, DefDef]() + + val visitor: Visitor = { + case defdef: DefDef if defdef.symbol.is(Label) => + var isRecursive = false + defdef.rhs.foreachSubTree(x => if (x.symbol == defdef.symbol) isRecursive = true) + if (!isRecursive) defined.put(defdef.symbol, defdef) + case t: Apply if t.symbol.is(Label) => + val b4 = timesUsed.getOrElseUpdate(t.symbol, 0) + timesUsed.put(t.symbol, b4 + 1) + case _ => + } + + val transformer: Transformer = () => localCtx => { + case a: Apply => + defined.get(a.symbol) match { + case None => a + case Some(defDef) if a.symbol.is(Label) && timesUsed.getOrElse(a.symbol, 0) == 1 && a.symbol.info.paramInfoss == List(Nil) => + simplify.println(s"Inlining labeldef ${defDef.name}") + defDef.rhs.changeOwner(defDef.symbol, localCtx.owner) + case Some(defDef) if defDef.rhs.isInstanceOf[Literal] => + defDef.rhs + case Some(_) => + a + } + case a: DefDef if (a.symbol.is(Label) && timesUsed.getOrElse(a.symbol, 0) == 1 && defined.contains(a.symbol)) => + simplify.println(s"Dropping labeldef (used once) ${a.name} ${timesUsed.get(a.symbol)}") + defined.put(a.symbol, a) + EmptyTree + case a: DefDef if (a.symbol.is(Label) && timesUsed.getOrElse(a.symbol, 0) == 0 && defined.contains(a.symbol)) => + simplify.println(s"Dropping labeldef (never used) ${a.name} ${timesUsed.get(a.symbol)}") + EmptyTree + case t => t + } + ("inlineLabelsCalledOnce", BeforeErasure, visitor, transformer) + } + + /** Rewrites pairs of consecutive LabelDef jumps by jumping directly to the target. */ + val jumpjump: Optimization = { implicit ctx: Context => + // Optimize label defs that call other label-defs + val defined = mutable.HashMap[Symbol, Symbol]() + + val visitor: Visitor = { + case defdef: DefDef if defdef.symbol.is(Label) => + defdef.rhs match { + case Apply(t, args) if t.symbol.is(Label) && + TypeErasure.erasure(defdef.symbol.info.finalResultType).classSymbol == + TypeErasure.erasure(t.symbol.info.finalResultType).classSymbol && + args.size == defdef.vparamss.map(_.size).sum && + args.zip(defdef.vparamss.flatten).forall(x => x._1.symbol eq x._2.symbol) && + !(defdef.symbol eq t.symbol) => + defined(defdef.symbol) = t.symbol + case _ => + } + case _ => + } + + val transformer: Transformer = () => localCtx => { + case a: Apply if defined.contains(a.fun.symbol)=> + defined.get(a.symbol) match { + case None => a + case Some(fwd) => + ref(fwd).appliedToArgs(a.args) + } + case a: DefDef if defined.contains(a.symbol) => + simplify.println(s"Dropping ${a.symbol.showFullName} as forwarder to ${defined(a.symbol).showFullName}") + EmptyTree + case t => t + } + ("jumpjump", BeforeAndAfterErasure, visitor, transformer) + } + + /** Inlines Option methods whose result is known statically. */ + val inlineOptions: Optimization = { implicit ctx: Context => + val somes = mutable.HashMap[Symbol, Tree]() + val nones = mutable.HashSet[Symbol]() + + val visitor: Visitor = { + case valdef: ValDef if !valdef.symbol.is(Mutable) && + valdef.rhs.isInstanceOf[Apply] && valdef.rhs.tpe.derivesFrom(defn.SomeClass) && + valdef.rhs.symbol.isPrimaryConstructor => + val Apply(_, value) = valdef.rhs + somes(valdef.symbol) = value.head + + case valdef: ValDef if !valdef.symbol.is(Mutable) && + valdef.rhs.isInstanceOf[Apply] && valdef.rhs.tpe.derivesFrom(defn.NoneClass) => + nones += valdef.symbol + case _ => + } + + val transformer: Transformer = () => localCtx => tree => { + def rewriteSelect(x: Tree) = x match { + case Select(rec, nm) if nm == nme.get && somes.contains(rec.symbol) => + somes(rec.symbol) + case Select(rec, nm) if nm == nme.isDefined && + (/*rec.tpe.derivesFrom(defn.SomeClass) ||*/ somes.contains(rec.symbol)) => + Literal(Constant(true)) + case Select(rec, nm) if nm == nme.isEmpty && + (/*rec.tpe.derivesFrom(defn.SomeClass) ||*/ somes.contains(rec.symbol)) => + Literal(Constant(false)) + + case Select(rec, nm) if nm == nme.get && nones.contains(rec.symbol) => + ref(defn.NoneModuleRef) + case Select(rec, nm) if nm == nme.isDefined && + (/*rec.tpe.derivesFrom(defn.NoneClass) || */ nones.contains(rec.symbol)) => + Literal(Constant(false)) + case Select(rec, nm) if nm == nme.isEmpty && + (/*rec.tpe.derivesFrom(defn.NoneClass) ||*/ nones.contains(rec.symbol)) => + Literal(Constant(true)) + case t => t + } + def dropApply(a: Tree): Tree = a match { + case Apply(fun, Nil) => fun + case _ => a + } + val old = dropApply(tree) + val nw = rewriteSelect(old) + if (nw ne old) nw + else tree + } + ("inlineOptions", BeforeErasure, visitor, transformer) + } + + /** Rewrite vars with exactly one assignment as vals. */ + val valify: Optimization = { implicit ctx: Context => + // Either a duplicate or a read through series of immutable fields. + val defined: mutable.Map[Symbol, ValDef] = mutable.Map() + val firstRead: mutable.Map[Symbol, RefTree] = mutable.Map() + val firstWrite: mutable.Map[Symbol, Assign] = mutable.Map() + val secondWrite: mutable.Map[Symbol, Assign] = mutable.Map() + val visitor: Visitor = { + case t: ValDef if t.symbol.is(Mutable, Lazy) && !t.symbol.is(Method) && !t.symbol.owner.isClass => + if (isPureExpr(t.rhs)) + defined(t.symbol) = t + + case t: RefTree if t.symbol.exists && !t.symbol.is(Method) && !t.symbol.owner.isClass => + if (!firstWrite.contains(t.symbol)) firstRead(t.symbol) = t + + case t @ Assign(l, expr) if !l.symbol.is(Method) && !l.symbol.owner.isClass => + if (!firstRead.contains(l.symbol)) { + if (firstWrite.contains(l.symbol)) { + if (!secondWrite.contains(l.symbol)) + secondWrite(l.symbol) = t + } else if (!expr.existsSubTree(x => x match { + case tree: RefTree if x.symbol == l.symbol => firstRead(l.symbol) = tree; true + case _ => false + })) { + firstWrite(l.symbol) = t + } + } + case _ => + } + + val transformer: Transformer = () => localCtx => { + val transformation: Tree => Tree = { + case t: Block => // Drop non-side-effecting stats + val valdefs = t.stats.collect { + case t: ValDef if defined.contains(t.symbol) => t + } + + val assigns = t.stats.filter { + case t @ Assign(lhs, r) => + firstWrite.contains(lhs.symbol) && !secondWrite.contains(lhs.symbol) + case _ => false + } + + val pairs = valdefs.flatMap(x => assigns.find(y => y.asInstanceOf[Assign].lhs.symbol == x.symbol) match { + case Some(y: Assign) => List((x, y)) + case _ => Nil + }) + + val valsToDrop = pairs.map(_._1).toSet + val assignsToReplace: Map[Assign, ValDef] = pairs.map(_.swap).toMap + + val newStats = t.stats.mapConserve { + case x: ValDef if valsToDrop.contains(x) => EmptyTree + case t: Assign => assignsToReplace.get(t) match { + case Some(vd) => + val newD = vd.symbol.asSymDenotation.copySymDenotation(initFlags = vd.symbol.flags.&~(Mutable)) + newD.installAfter(this) + ValDef(vd.symbol.asTerm, t.rhs) + case None => t + } + case x => x + } + + if (newStats eq t.stats) t + else tpd.cpy.Block(t)(newStats, t.expr) + case tree => tree + } + + transformation + } + ("valify", BeforeAndAfterErasure, visitor, transformer) + } + + /** Inline vals */ + val devalify: Optimization = { implicit ctx: Context => + val timesUsed = mutable.HashMap[Symbol, Int]() + val timesUsedAsType = mutable.HashMap[Symbol, Int]() + + val defined = mutable.HashSet[Symbol]() + val usedInInnerClass = mutable.HashMap[Symbol, Int]() + // Either a duplicate or a read through series of immutable fields + val copies = mutable.HashMap[Symbol, Tree]() + def visitType(tp: Type): Unit = { + tp.foreachPart(x => x match { + case TermRef(NoPrefix, _) => + val b4 = timesUsedAsType.getOrElseUpdate(x.termSymbol, 0) + timesUsedAsType.put(x.termSymbol, b4 + 1) + case _ => + }) + } + def doVisit(tree: Tree, used: mutable.HashMap[Symbol, Int]): Unit = tree match { + case valdef: ValDef if !valdef.symbol.is(Param | Mutable | Module | Lazy) && + valdef.symbol.exists && !valdef.symbol.owner.isClass => + defined += valdef.symbol + + dropCasts(valdef.rhs) match { + case t: Tree if readingOnlyVals(t) => + copies.put(valdef.symbol, valdef.rhs) + case _ => + } + visitType(valdef.symbol.info) + case t: New => + val normalized = t.tpt.tpe.normalizedPrefix + val symIfExists = normalized.termSymbol + val b4 = used.getOrElseUpdate(symIfExists, 0) + used.put(symIfExists, b4 + 1) + visitType(normalized) + + case valdef: ValDef if valdef.symbol.exists && !valdef.symbol.owner.isClass && + !valdef.symbol.is(Param | Module | Lazy) => + // TODO: handle params after constructors. Start changing public signatures by eliminating unused arguments. + defined += valdef.symbol + + case valdef: ValDef => visitType(valdef.symbol.info) + case t: DefDef => visitType(t.symbol.info) + case t: Typed => visitType(t.tpt.tpe) + case t: TypeApply => t.args.foreach(x => visitType(x.tpe)) + case t: RefTree => + val b4 = used.getOrElseUpdate(t.symbol, 0) + used.put(t.symbol, b4 + 1) + case _ => + } + + val visitor: Visitor = { tree => + def crossingClassBoundaries(t: Tree): Boolean = t match { + case _: New => true + case _: Template => true + case _ => false + } + // We shouldn't inline `This` nodes, which we approximate by not inlining + // anything across class boundaries. To do so, we visit every class a + // second time and record what's used in the usedInInnerClass Set. + if (crossingClassBoundaries(tree)) { + // Doing a foreachSubTree(tree) here would work, but would also + // be exponential for deeply nested classes. Instead we do a short + // circuit traversal that doesn't visit further nested classes. + val reVisitClass = new TreeAccumulator[Unit] { + def apply(u: Unit, t: Tree)(implicit ctx: Context): Unit = { + doVisit(t, usedInInnerClass) + if (!crossingClassBoundaries(t)) + foldOver((), t) + } + } + reVisitClass.foldOver((), tree) + } + doVisit(tree, timesUsed) + } + + val transformer: Transformer = () => localCtx => { + val valsToDrop = defined -- timesUsed.keySet -- timesUsedAsType.keySet + val copiesToReplaceAsDuplicates = copies.filter { x => + val rhs = dropCasts(x._2) + rhs.isInstanceOf[Literal] || (!rhs.symbol.owner.isClass && !rhs.symbol.is(Method | Mutable)) + } -- timesUsedAsType.keySet + // TODO: if a non-synthetic val is duplicate of a synthetic one, rename a synthetic one and drop synthetic flag? + + val copiesToReplaceAsUsedOnce = + timesUsed.filter(x => x._2 == 1) + .flatMap(x => copies.get(x._1) match { + case Some(tr) => List((x._1, tr)) + case None => Nil + }) -- timesUsedAsType.keySet + + val replacements = copiesToReplaceAsDuplicates ++ copiesToReplaceAsUsedOnce -- usedInInnerClass.keySet + + val deepReplacer = new TreeMap() { + override def transform(tree: Tree)(implicit ctx: Context): Tree = { + def loop(tree: Tree): Tree = + tree match { + case t: RefTree if replacements.contains(t.symbol) => + loop(replacements(t.symbol)) + case _ => tree + } + super.transform(loop(tree)) + } + } + + val transformation: Tree => Tree = { + case t: ValDef if valsToDrop.contains(t.symbol) => + // TODO: Could emit a warning for non synthetic code? This valdef is + // probably something users would want to remove from source... + simplify.println(s"Dropping definition of ${t.symbol.showFullName} as not used") + t.rhs.changeOwner(t.symbol, t.symbol.owner) + case t: ValDef if replacements.contains(t.symbol) => + simplify.println(s"Dropping definition of ${t.symbol.showFullName} as an alias") + EmptyTree + case t: New => + val symIfExists = t.tpt.tpe.normalizedPrefix.termSymbol + if (replacements.contains(symIfExists)) { + val newPrefix = deepReplacer.transform(replacements(symIfExists)) + val newTpt = t.tpt.tpe match { + case t: NamedType => + t.derivedSelect(newPrefix.tpe) + } + New(newTpt) + } + else t + case t: RefTree if !t.symbol.is(Method | Param | Mutable) => + if (replacements.contains(t.symbol)) + deepReplacer.transform(replacements(t.symbol)).ensureConforms(t.tpe.widen) + else t + case tree => tree + } + + transformation + } + // See tests/pos/devalify.scala for examples of why this needs to be after Erasure. + ("devalify", BeforeAndAfterErasure, visitor, transformer) + } + + /** Inline val with exactly one assignment to a var. For example: + * + * { + * val l = + * var r = l + * // code not using l + * } + * + * becomes: + * + * { + * var r = + * // code not using l + * } + */ + val varify: Optimization = { implicit ctx: Context => + val paramsTimesUsed = mutable.HashMap[Symbol, Int]() + val possibleRenames = mutable.HashMap[Symbol, Set[Symbol]]() + val visitor: Visitor = { + case t: ValDef + if t.symbol.is(Param) => + paramsTimesUsed += (t.symbol -> 0) + case valDef: ValDef + if valDef.symbol.is(Mutable) => + valDef.rhs.foreachSubTree { subtree => + if (paramsTimesUsed.contains(subtree.symbol) && + valDef.symbol.info.widenDealias <:< subtree.symbol.info.widenDealias) { + val newSet = possibleRenames.getOrElse(valDef.symbol, Set.empty) + subtree.symbol + possibleRenames.put(valDef.symbol, newSet) + } + } + case t: RefTree + if paramsTimesUsed.contains(t.symbol) => + val param = t.symbol + val current = paramsTimesUsed.get(param) + current foreach { c => paramsTimesUsed += (param -> (c + 1)) } + case _ => + } + val transformer: Transformer = () => localCtx => { + val paramCandidates = paramsTimesUsed.filter(kv => kv._2 == 1).keySet + val renames: Map[Symbol, Symbol] = possibleRenames.iterator + .map(kv => (kv._1, kv._2.intersect(paramCandidates))) + .filter(x => x._2.nonEmpty) + .map(x => (x._1, x._2.head)) + .toMap + val transformation: Tree => Tree = { + case t: RefTree + if renames.contains(t.symbol) => + ref(renames(t.symbol)) + case t: ValDef + if renames.contains(t.symbol) => + val replaced = renames(t.symbol) + if (t.rhs.symbol == replaced) EmptyTree + else ref(replaced).becomes(t.rhs) + case t: ValDef + if paramCandidates.contains(t.symbol) => + t.symbol.flags = Mutable + t + case t => t + } + transformation + } + ("varify", AfterErasure, visitor, transformer) + } + + private def unzip4[A, B, C, D](seq: Seq[(A, B, C, D)]): (Seq[A], Seq[B], Seq[C], Seq[D]) = { + val listBuilderA = new mutable.ListBuffer[A]() + val listBuilderB = new mutable.ListBuffer[B]() + val listBuilderC = new mutable.ListBuffer[C]() + val listBuilderD = new mutable.ListBuffer[D]() + seq.foreach { x => + listBuilderA += x._1 + listBuilderB += x._2 + listBuilderC += x._3 + listBuilderD += x._4 + } + (listBuilderA.toList, listBuilderB.toList, listBuilderC.toList, listBuilderD.toList) + } +} diff --git a/compiler/test/dotc/tests.scala b/compiler/test/dotc/tests.scala index ee441d44b4bd..8842840863de 100644 --- a/compiler/test/dotc/tests.scala +++ b/compiler/test/dotc/tests.scala @@ -68,8 +68,8 @@ class tests extends CompilerTest { } implicit val defaultOptions: List[String] = noCheckOptions ++ { - if (dotty.Properties.isRunByDrone) List("-Ycheck:tailrec,resolveSuper,mixin,restoreScopes,labelDef") // should be Ycheck:all, but #725 - else List("-Ycheck:tailrec,resolveSuper,mixin,restoreScopes,labelDef") + if (dotty.Properties.isRunByDrone) List("-Ycheck:tailrec,resolveSuper,mixin,restoreScopes,labelDef,simplify") // should be Ycheck:all, but #725 + else List("-Ycheck:tailrec,resolveSuper,mixin,restoreScopes,labelDef,simplify") } ++ checkOptions ++ classPath val testPickling = List("-Xprint-types", "-Ytest-pickler", "-Ystop-after:pickler", "-Yprintpos") diff --git a/compiler/test/dotty/tools/DottyTest.scala b/compiler/test/dotty/tools/DottyTest.scala index 39ff014c8f71..f3d59fcdf14f 100644 --- a/compiler/test/dotty/tools/DottyTest.scala +++ b/compiler/test/dotty/tools/DottyTest.scala @@ -18,12 +18,11 @@ trait DottyTest extends ContextEscapeDetection { dotc.parsing.Scanners // initialize keywords - implicit var ctx: Contexts.Context = { + implicit var ctx: Context = { val base = new ContextBase {} import base.settings._ val ctx = base.initialCtx.fresh - ctx.setSetting(ctx.settings.encoding, "UTF8") - ctx.setSetting(ctx.settings.classpath, Jars.dottyLib) + initializeCtx(ctx) // when classpath is changed in ctx, we need to re-initialize to get the // correct classpath from PathResolver base.initialize()(ctx) @@ -35,7 +34,12 @@ trait DottyTest extends ContextEscapeDetection { ctx = null } - private def compilerWithChecker(phase: String)(assertion:(tpd.Tree, Context) => Unit) = new Compiler { + protected def initializeCtx(fc: FreshContext): Unit = { + fc.setSetting(fc.settings.encoding, "UTF8") + fc.setSetting(fc.settings.classpath, Jars.dottyLib) + } + + private def compilerWithChecker(phase: String)(assertion: (tpd.Tree, Context) => Unit) = new Compiler { override def phases = { val allPhases = super.phases val targetPhase = allPhases.flatten.find(p => p.phaseName == phase).get @@ -58,7 +62,7 @@ trait DottyTest extends ContextEscapeDetection { run.compile(source) } - def checkCompile(checkAfterPhase: String, sources:List[String])(assertion:(tpd.Tree, Context) => Unit): Unit = { + def checkCompile(checkAfterPhase: String, sources: List[String])(assertion: (tpd.Tree, Context) => Unit): Unit = { val c = compilerWithChecker(checkAfterPhase)(assertion) c.rootContext(ctx) val run = c.newRun diff --git a/compiler/test/dotty/tools/DottyTypeStealer.scala b/compiler/test/dotty/tools/DottyTypeStealer.scala index 727cd9e7da19..331fded9b025 100644 --- a/compiler/test/dotty/tools/DottyTypeStealer.scala +++ b/compiler/test/dotty/tools/DottyTypeStealer.scala @@ -14,7 +14,7 @@ object DottyTypeStealer extends DottyTest { val gatheredSource = s" ${source}\n object A$dummyName {$vals}" var scontext : Context = null var tp: List[Type] = null - checkCompile("frontend",gatheredSource) { + checkCompile("frontend", gatheredSource) { (tree, context) => implicit val ctx = context val findValDef: (List[ValDef], tpd.Tree) => List[ValDef] = diff --git a/compiler/test/dotty/tools/dotc/SimplifyTests.scala b/compiler/test/dotty/tools/dotc/SimplifyTests.scala new file mode 100644 index 000000000000..f22e515a31d2 --- /dev/null +++ b/compiler/test/dotty/tools/dotc/SimplifyTests.scala @@ -0,0 +1,116 @@ +package dotty.tools.dotc + +import org.junit.Assert._ +import org.junit.Test +import dotty.tools.backend.jvm._ +import dotty.tools.dotc.config.CompilerCommand +import dotty.tools.dotc.core.Contexts.FreshContext +import scala.tools.asm.tree.MethodNode + +class SimplifyPosTests extends SimplifyTests(optimise = true) +class SimplifyNegTests extends SimplifyTests(optimise = false) + +abstract class SimplifyTests(val optimise: Boolean) extends DottyBytecodeTest { + override protected def initializeCtx(c: FreshContext): Unit = { + super.initializeCtx(c) + if (optimise) { + val flags = Array("-optimise") // :+ "-Xprint:simplify" + val summary = CompilerCommand.distill(flags)(c) + c.setSettings(summary.sstate) + } + } + + def check(source: String, expected: String, shared: String = ""): Unit = { + import ASMConverters._ + val src = + s""" + $shared + |class A { + | def main(): Unit = { + $source + | } + |} + |class B { + | def main(): Unit = { + $expected + | } + |} + """.stripMargin + checkBCode(src) { dir => + def instructions(clazz: String): List[Instruction] = { + val clsIn = dir.lookupName(s"$clazz.class", directory = false).input + val clsNode = loadClassNode(clsIn) + instructionsFromMethod(getMethod(clsNode, "main")) + } + val A = instructions("A") + val B = instructions("B") + val diff = diffInstructions(A, B) + if (optimise) + assert(A == B, s"Bytecode doesn't match: (lhs = source, rhs = expected) \n$diff") + else + assert(A != B, s"Same Bytecodes without -optimise: you are testing the wrong thing!") + } + } + + @Test def inlineVals = + check("println(1)", + """ + |val one = 1 + |val anotherone = one + |println(anotherone) + """) + + @Test def inlineCaseIntrinsicsDottyApply = + check( + source = "CC.apply(1, 2)", + expected = "new CC(1, 2)", + shared = "case class CC(i: Int, j: Int)") + + @Test def inlineCaseIntrinsicsScalacApply = + check("::.apply(1, Nil)", "new ::(1, Nil)") + + @Test def inlineCaseIntrinsicsScalacUnapply = + check( + """ + |val t = Tuple2(1, "s") + |print(Tuple2.unapply(t)) + """, + """ + |val t = Tuple2(1, "s") + |print({ + | Tuple2 // TODO: teach Simplify that initializing Tuple2 has no effect + | new Some(new Tuple2(t._1, t._2)) + |}) + """) + + @Test def constantFold = + check( + """ + |val t = true // val needed, or typer takes care of this + |if (t) print(1) + |else print(2) + """, + """ + |print(1) + """) + + @Test def dropNoEffects = + check( + """ + |"wow" + |print(1) + """, + """ + |print(1) + """) + + // @Test def inlineOptions = + // check( + // """ + // |val sum = Some("s") + // |println(sum.isDefined) + // """, + // """ + // |println(true) + // """) +} diff --git a/compiler/test/dotty/tools/vulpix/TestConfiguration.scala b/compiler/test/dotty/tools/vulpix/TestConfiguration.scala index 242160074cde..440a0a629534 100644 --- a/compiler/test/dotty/tools/vulpix/TestConfiguration.scala +++ b/compiler/test/dotty/tools/vulpix/TestConfiguration.scala @@ -50,12 +50,13 @@ object TestConfiguration { Array("-classpath", paths) } - private val yCheckOptions = Array("-Ycheck:tailrec,resolveSuper,mixin,restoreScopes,labelDef") + private val yCheckOptions = Array("-Ycheck:tailrec,resolveSuper,mixin,arrayConstructors,labelDef") - val defaultOptions = noCheckOptions ++ checkOptions ++ yCheckOptions ++ classPath + val defaultUnoptimised = noCheckOptions ++ checkOptions ++ yCheckOptions ++ classPath + val defaultOptions = defaultUnoptimised :+ "-optimise" val allowDeepSubtypes = defaultOptions diff Array("-Yno-deep-subtypes") val allowDoubleBindings = defaultOptions diff Array("-Yno-double-bindings") - val picklingOptions = defaultOptions ++ Array( + val picklingOptions = defaultUnoptimised ++ Array( "-Xprint-types", "-Ytest-pickler", "-Yprintpos" diff --git a/tests/pickling/A.scala b/tests/pickling/A.scala new file mode 100644 index 000000000000..7de1bc1ef418 --- /dev/null +++ b/tests/pickling/A.scala @@ -0,0 +1,6 @@ +import dotty.tools.dotc.core.Decorators._ +import dotty.tools.dotc.core.NameOps._ + +object Test { + "JFunction".toTermName.specializedFor(Nil, ???, Nil, Nil)(???) +} diff --git a/tests/pickling/B.scala b/tests/pickling/B.scala new file mode 100644 index 000000000000..1cdd19a9c2a3 --- /dev/null +++ b/tests/pickling/B.scala @@ -0,0 +1,6 @@ +import dotty.tools.dotc.core.Contexts.Context + +object Formatting { + def rainbows(implicit ctx: Context): String = + ctx.settings.color.value.toString +} diff --git a/tests/pos/bubbleUpNothing.scala b/tests/pos/bubbleUpNothing.scala new file mode 100644 index 000000000000..1c4da164866d --- /dev/null +++ b/tests/pos/bubbleUpNothing.scala @@ -0,0 +1,71 @@ +trait T { + def apply(a1: String, a2: String, a3: String): String = a3 +} + +object Test { + def test1 = + ??? // Nothing.String.CharSequence.String.CharSequence.String + .toString.subSequence(0, 1).toString.subSequence(0, 1).toString + + def test2 = + (new T {}) + .apply(???, "b", "c") + .subSequence(0, 1).toString.subSequence(0, 1).toString + + def test3 = + (new T {}) + .apply("a", ???, "c") + .subSequence(0, 1).toString.subSequence(0, 1).toString + + def test4 = + (new T {}) + .apply("a", "b", ???) + .subSequence(0, 1).toString.subSequence(0, 1).toString + + def test5 = + (new T {}) + .apply("a", "b", ???) + .subSequence(0, 1).toString.subSequence(0, 1).toString + + def test6 = + (if (???) "a" else "b") + .subSequence(0, 1).toString.subSequence(0, 1).toString + + def test7 = + { ???; "b"; "c" } + .subSequence(0, 1).toString.subSequence(0, 1).toString + + def test8 = + { "a"; ???; "c" } + .subSequence(0, 1).toString.subSequence(0, 1).toString + + def test9 = + { "a"; "b"; ??? } + .toString.subSequence(0, 1).toString.subSequence(0, 1).toString + + // run -optimise -Xprint:simplify + // def test1(): String = ???(): String + // def test2(): String = ???(): String + // def test3(): String = ???(): String + // def test4(): String = ???(): String + // def test5(): String = ???(): String + // def test6(): String = ???(): String + // def test7(): String = ???(): String + // def test8(): String = ???(): String + // def test9(): String = ???(): String + + def test10: Unit = { + def fail = throw new IllegalArgumentException("") + } + + def test11: Unit = { + trait Context + trait Type + trait Tree { + def withType(tpe: Type)(implicit ctx: Context): Tree = this + } + + def readTree()(implicit ctx: Context): Any = + (new Tree {}).withType(???)(ctx).withType(???) + } +} diff --git a/tests/pos/devalify.scala b/tests/pos/devalify.scala new file mode 100644 index 000000000000..b4e4a848cc7f --- /dev/null +++ b/tests/pos/devalify.scala @@ -0,0 +1,45 @@ +object Test { + def test0: Unit = { + trait I { + def foo: Any = null + } + val s: I = null + s.foo + } + + def test1: Unit = { + // `s1` is used once as a value, several times as a type. This tests shows + // that Ycheck is happy despite devalify inlining (and removing) `s1`. + val s1: String = "singleton" + val s2: s1.type = s1 + + val t: Option[s1.type] = None + + println(t) + println((s2: s1.type)) + } + + def test2: Unit = { + class Foo { + class Bar + } + + val foo = new Foo + val subFoo = foo + // Inlining `subFoo` changes the type of `subFooBar` from `subFoo.Bar` to `foo.Bar` + val subFooBar = new subFoo.Bar + + println(subFooBar) + } + + def test3: Unit = { + trait NumericRange { + def mapRange: NumericRange = { + val self = this + new NumericRange { + def underlyingRange: NumericRange = self + } + } + } + } +} diff --git a/tests/pos/t7126.scala b/tests/pos/t7126.scala deleted file mode 100644 index edac56d28d8c..000000000000 --- a/tests/pos/t7126.scala +++ /dev/null @@ -1,11 +0,0 @@ -import language._ - -object Test { - type T = Any - boom(???): Option[T] // SOE - def boom[CC[U]](t : CC[T]): Option[CC[T]] = None - - // okay - foo(???): Option[Any] - def foo[CC[U]](t : CC[Any]): Option[CC[Any]] = None -} diff --git a/tests/run/byname-param.check b/tests/run/byname-param.check new file mode 100644 index 000000000000..8750227c71b9 --- /dev/null +++ b/tests/run/byname-param.check @@ -0,0 +1,3 @@ +1 +1 +() diff --git a/tests/run/byname-param.scala b/tests/run/byname-param.scala new file mode 100644 index 000000000000..9240604eb95d --- /dev/null +++ b/tests/run/byname-param.scala @@ -0,0 +1,11 @@ +object Test { + // Devalify shouldn't optimize this + def theTrap(cond: Boolean, t: => Unit) = { + val a,b = t + if (cond) println(a) else println(b) + } + + def main(args: Array[String]): Unit = { + theTrap(true, println(1)) + } +} diff --git a/tests/run/t4859.scala b/tests/run/t4859.scala index 8b354ca9451a..2dbf1301bd47 100644 --- a/tests/run/t4859.scala +++ b/tests/run/t4859.scala @@ -3,6 +3,8 @@ object O { object P } +// We assume module initialisation to be pure, running this test under +// -optimise yields different results. object Outer { println("Outer") object Inner { diff --git a/tests/run/t7126.scala b/tests/run/t7126.scala new file mode 100644 index 000000000000..b785bcff69ee --- /dev/null +++ b/tests/run/t7126.scala @@ -0,0 +1,20 @@ +import language._ + +// Currently typer infers a Nothing as a CC[T] to be Nothing[T], and isn't +// able to figure out that Nothing[Any] =:= Nothing. We've had a discussion +// that it should instead infer CC[T] to be type lambda T => Nothing to be +// kind-correct. #2439 + +object T7126 { + type T = Any + boom(???): Option[T] // SOE + def boom[CC[U]](t : CC[T]): Option[CC[T]] = None + + // okay + foo(???): Option[Any] + def foo[CC[U]](t : CC[Any]): Option[CC[Any]] = None +} + +object Test { + def main(args: Array[String]): Unit = () +}