diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index 9c650f8e2d63..8e8a7fb6db76 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -663,6 +663,37 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => case _ => false } + + /** Structural tree comparison (since == on trees is reference equality). + * For the moment, only Ident, Select, Literal, Apply and TypeApply are supported + */ + implicit class StructuralEqDeco(t1: Tree) { + def === (t2: Tree)(implicit ctx: Context): Boolean = (t1, t2) match { + case (t1: Ident, t2: Ident) => + t1.symbol == t2.symbol + case (t1 @ Select(q1, _), t2 @ Select(q2, _)) => + t1.symbol == t2.symbol && q1 === q2 + case (Literal(c1), Literal(c2)) => + c1 == c2 + case (Apply(f1, as1), Apply(f2, as2)) => + f1 === f2 && as1.corresponds(as2)(_ === _) + case (TypeApply(f1, ts1), TypeApply(f2, ts2)) => + f1 === f2 && ts1.tpes.corresponds(ts2.tpes)(_ =:= _) + case _ => + false + } + def hash(implicit ctx: Context): Int = + t1.getClass.hashCode * 37 + { + t1 match { + case t1: Ident => t1.symbol.hashCode + case t1 @ Select(q1, _) => t1.symbol.hashCode * 41 + q1.hash + case Literal(c1) => c1.hashCode + case Apply(f1, as1) => (f1.hash /: as1)((h, arg) => h * 41 + arg.hash) + case TypeApply(f1, ts1) => (f1.hash /: ts1)((h, arg) => h * 41 + arg.tpe.hash) + case _ => t1.hashCode + } + } + } } object TreeInfo { diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index 74f1909eaa52..016db8e2c01b 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -12,7 +12,7 @@ import Denotations._, Decorators._, DenotTransformers._ import collection.mutable import util.{Property, SourceFile, NoSource} import typer.ErrorReporting._ -import NameKinds.TempResultName +import NameKinds.{TempResultName, OuterSelectName} import scala.annotation.tailrec import scala.io.Codec @@ -171,6 +171,13 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { def SyntheticValDef(name: TermName, rhs: Tree)(implicit ctx: Context): ValDef = ValDef(ctx.newSymbol(ctx.owner, name, Synthetic, rhs.tpe.widen, coord = rhs.pos), rhs) + def DefDef(sym: TermSymbol, tparams: List[TypeSymbol], vparamss: List[List[TermSymbol]], + resultType: Type, rhs: Tree)(implicit ctx: Context): DefDef = + ta.assignType( + untpd.DefDef(sym.name, tparams map TypeDef, vparamss.nestedMap(ValDef(_)), + TypeTree(resultType), rhs), + sym) + def DefDef(sym: TermSymbol, rhs: Tree = EmptyTree)(implicit ctx: Context): DefDef = ta.assignType(DefDef(sym, Function.const(rhs) _), sym) @@ -199,14 +206,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { val (vparamss, rtp) = valueParamss(mtp) val targs = tparams map (_.typeRef) val argss = vparamss.nestedMap(vparam => Ident(vparam.termRef)) - ta.assignType( - untpd.DefDef( - sym.name, - tparams map TypeDef, - vparamss.nestedMap(ValDef(_)), - TypeTree(rtp), - rhsFn(targs)(argss)), - sym) + DefDef(sym, tparams, vparamss, rtp, rhsFn(targs)(argss)) } def TypeDef(sym: TypeSymbol)(implicit ctx: Context): TypeDef = @@ -682,6 +682,12 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { def select(name: Name)(implicit ctx: Context): Select = Select(tree, name) + /** A select node with the given selector name such that the designated + * member satisfies predicate `p`. Useful for disambiguating overloaded members. + */ + def select(name: Name, p: Symbol => Boolean)(implicit ctx: Context): Select = + select(tree.tpe.member(name).suchThat(p).symbol) + /** A select node with the given type */ def select(tp: NamedType)(implicit ctx: Context): Select = untpd.Select(tree, tp.name).withType(tp) @@ -751,9 +757,20 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { def ensureApplied(implicit ctx: Context): Tree = if (tree.tpe.widen.isParameterless) tree else tree.appliedToNone - /** `tree.isInstanceOf[tp]` */ - def isInstance(tp: Type)(implicit ctx: Context): Tree = - tree.select(defn.Any_isInstanceOf).appliedToType(tp) + /** `tree == that` */ + def equal(that: Tree)(implicit ctx: Context) = + applyOverloaded(tree, nme.EQ, that :: Nil, Nil, defn.BooleanType) + + /** `tree.isInstanceOf[tp]`, with special treatment of singleton types */ + def isInstance(tp: Type)(implicit ctx: Context): Tree = tp match { + case tp: SingletonType => + if (tp.widen.derivesFrom(defn.ObjectClass)) + tree.ensureConforms(defn.ObjectType).select(defn.Object_eq).appliedTo(singleton(tp)) + else + singleton(tp).equal(tree) + case _ => + tree.select(defn.Any_isInstanceOf).appliedToType(tp) + } /** tree.asInstanceOf[`tp`] */ def asInstance(tp: Type)(implicit ctx: Context): Tree = { @@ -771,7 +788,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { else Erasure.Boxing.adaptToType(tree, tp) /** `tree ne null` (might need a cast to be type correct) */ - def testNotNull(implicit ctx: Context): Tree = + def testNotNull(implicit ctx: Context): Tree = tree.ensureConforms(defn.ObjectType) .select(defn.Object_ne).appliedTo(Literal(Constant(null))) @@ -805,6 +822,13 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { } else Assign(tree, rhs) + /** A synthetic select with that will be turned into an outer path by ExplicitOuter. + * @param levels How many outer levels to select + * @param tp The type of the destination of the outer path. + */ + def outerSelect(levels: Int, tp: Type)(implicit ctx: Context): Tree = + untpd.Select(tree, OuterSelectName(EmptyTermName, levels)).withType(tp) + // --- Higher order traversal methods ------------------------------- /** Apply `f` to each subtree of this tree */ diff --git a/compiler/src/dotty/tools/dotc/config/Printers.scala b/compiler/src/dotty/tools/dotc/config/Printers.scala index a8be043ea084..52c10f628e44 100644 --- a/compiler/src/dotty/tools/dotc/config/Printers.scala +++ b/compiler/src/dotty/tools/dotc/config/Printers.scala @@ -32,5 +32,6 @@ object Printers { val pickling: Printer = noPrinter val inlining: Printer = noPrinter val exhaustivity: Printer = noPrinter + val patmatch: 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 b2093474f946..9861d0e0efff 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -255,7 +255,7 @@ class Definitions { // generated by pattern matcher, eliminated by erasure def AnyMethods = List(Any_==, Any_!=, Any_equals, Any_hashCode, - Any_toString, Any_##, Any_getClass, Any_isInstanceOf, Any_asInstanceOf) + Any_toString, Any_##, Any_getClass, Any_isInstanceOf, Any_asInstanceOf, Any_typeTest) lazy val ObjectClass: ClassSymbol = { val cls = ctx.requiredClass("java.lang.Object") @@ -373,6 +373,10 @@ class Definitions { def Seq_apply(implicit ctx: Context) = Seq_applyR.symbol lazy val Seq_headR = SeqClass.requiredMethodRef(nme.head) def Seq_head(implicit ctx: Context) = Seq_headR.symbol + lazy val Seq_dropR = SeqClass.requiredMethodRef(nme.drop) + def Seq_drop(implicit ctx: Context) = Seq_dropR.symbol + lazy val Seq_lengthCompareR = SeqClass.requiredMethodRef(nme.lengthCompare) + def Seq_lengthCompare(implicit ctx: Context) = Seq_lengthCompareR.symbol lazy val ArrayType: TypeRef = ctx.requiredClassRef("scala.Array") def ArrayClass(implicit ctx: Context) = ArrayType.symbol.asClass diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 9148c5554cb7..8c6474ae0e55 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -214,10 +214,7 @@ object Types { case _ => NoType } - /** Is this type guaranteed not to have `null` as a value? - * For the moment this is only true for modules, but it could - * be refined later. - */ + /** Is this type guaranteed not to have `null` as a value? */ final def isNotNull(implicit ctx: Context): Boolean = this match { case tp: ConstantType => tp.value.value != null case tp: ClassInfo => !tp.cls.isNullableClass && tp.cls != defn.NothingClass @@ -3753,7 +3750,7 @@ object Types { if (underlying1 eq underlying) tp else derivedAnnotatedType(tp, underlying1, mapOver(annot)) - case tp @ WildcardType => + case tp: WildcardType => derivedWildcardType(tp, mapOver(tp.optBounds)) case tp: JavaArrayType => diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index 3e812695668a..0e426f5e0b98 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -139,9 +139,9 @@ class Erasure extends Phase with DenotTransformer { thisTransformer => i"The type $tp - ${tp.toString} of class ${tp.getClass} of tree $tree : ${tree.tpe} / ${tree.getClass} is illegal after erasure, phase = ${ctx.phase.prev}") } -object Erasure extends TypeTestsCasts{ - +object Erasure { import tpd._ + import TypeTestsCasts._ object Boxing { diff --git a/compiler/src/dotty/tools/dotc/transform/IsInstanceOfEvaluator.scala b/compiler/src/dotty/tools/dotc/transform/IsInstanceOfEvaluator.scala index b48d219d66bb..587ba1fbe398 100644 --- a/compiler/src/dotty/tools/dotc/transform/IsInstanceOfEvaluator.scala +++ b/compiler/src/dotty/tools/dotc/transform/IsInstanceOfEvaluator.scala @@ -20,7 +20,7 @@ import TypeUtils._, TypeErasure._, Flags._ * * Steps taken: * - * 1. `evalTypeApply` will establish the matrix and choose the appropriate + * 1. `evalTypeTest` will establish the matrix and choose the appropriate * handling for the case: * - Sel/sc is a value class or scrutinee is `Any` * - `handleStaticallyKnown` @@ -45,128 +45,128 @@ class IsInstanceOfEvaluator extends MiniPhaseTransform { thisTransformer => * the correct warnings, or an error if statically known to be false in * match */ - def handleStaticallyKnown(select: Select, scrutinee: Type, selector: Type, inMatch: Boolean, pos: Position): Tree = { + def handleStaticallyKnown(qualifier: Tree, scrutinee: Type, selector: Type, inMatch: Boolean, pos: Position): Tree = { val scrutineeSubSelector = scrutinee <:< selector if (!scrutineeSubSelector && inMatch) { ctx.error( s"this case is unreachable due to `${selector.show}` not being a subclass of `${scrutinee.show}`", Position(pos.start - 5, pos.end - 5) ) - rewrite(select, to = false) + rewrite(qualifier, to = false) } else if (!scrutineeSubSelector && !inMatch) { ctx.warning( s"this will always yield false since `${scrutinee.show}` is not a subclass of `${selector.show}` (will be optimized away)", pos ) - rewrite(select, to = false) + rewrite(qualifier, to = false) } else if (scrutineeSubSelector && !inMatch) { ctx.warning( s"this will always yield true if the scrutinee is non-null, since `${scrutinee.show}` is a subclass of `${selector.show}` (will be optimized away)", pos ) - rewrite(select, to = true) - } else /* if (scrutineeSubSelector && inMatch) */ rewrite(select, to = true) + rewrite(qualifier, to = true) + } else /* if (scrutineeSubSelector && inMatch) */ rewrite(qualifier, to = true) } /** Rewrites cases with unrelated types */ - def handleFalseUnrelated(select: Select, scrutinee: Type, selector: Type, inMatch: Boolean) = + def handleFalseUnrelated(qualifier: Tree, scrutinee: Type, selector: Type, inMatch: Boolean) = if (inMatch) { ctx.error( s"will never match since `${selector.show}` is not a subclass of `${scrutinee.show}`", - Position(select.pos.start - 5, select.pos.end - 5) + Position(qualifier.pos.start - 5, qualifier.pos.end - 5) // WHY 5? ) - rewrite(select, to = false) + rewrite(qualifier, to = false) } else { ctx.warning( s"will always yield false since `${scrutinee.show}` is not a subclass of `${selector.show}`", - select.pos + tree.pos ) - rewrite(select, to = false) + rewrite(qualifier, to = false) } - /** Rewrites the select to a boolean if `to` is false or if the qualifier + /** Rewrites the qualifier of a type test to a boolean if `to` is false or if the qualifier * is a value class. * * If `to` is set to true and the qualifier is not a primitive, the * instanceOf is replaced by a null check, since: * - * `scrutinee.isInstanceOf[Selector]` if `scrutinee eq null` + * `scutinee == null` implies `!scrutinee.isInstanceOf[Selector]` */ - def rewrite(tree: Select, to: Boolean): Tree = - if (!to || !tree.qualifier.tpe.widen.derivesFrom(defn.AnyRefAlias)) { + def rewrite(qualifier: Tree, to: Boolean): Tree = + if (to && !qualifier.tpe.isNotNull) qualifier.testNotNull + else { val literal = Literal(Constant(to)) - if (!isPureExpr(tree.qualifier)) Block(List(tree.qualifier), literal) + if (!isPureExpr(qualifier)) Block(List(qualifier), literal) else literal - } else - Apply(tree.qualifier.select(defn.Object_ne), List(Literal(Constant(null)))) + } - /** Attempts to rewrite TypeApply to either `scrutinee ne null` or a - * constant + /** Attempts to rewrite type test to either `scrutinee ne null` or a + * constant. Any_typeTest nodes have been rewritten to Any_isInstanceOf at this point. + * @param tree the whole type test .asInstanceOf[T] + * @param qualifier the part + * @param inMatch tree was a type test generated by a pattern match. */ - def evalTypeApply(tree: TypeApply): Tree = - if (tree.symbol != defn.Any_isInstanceOf) tree - else tree.fun match { - case s: Select => { - val scrutinee = erasure(s.qualifier.tpe.widen) - val selector = erasure(tree.args.head.tpe.widen) - - val scTrait = scrutinee.typeSymbol is Trait - val scClass = - scrutinee.typeSymbol.isClass && - !(scrutinee.typeSymbol is Trait) && - !(scrutinee.typeSymbol is Module) - - val scClassNonFinal = scClass && !(scrutinee.typeSymbol is Final) - val scFinalClass = scClass && (scrutinee.typeSymbol is Final) - - val selTrait = selector.typeSymbol is Trait - val selClass = - selector.typeSymbol.isClass && - !(selector.typeSymbol is Trait) && - !(selector.typeSymbol is Module) - - val selClassNonFinal = selClass && !(selector.typeSymbol is Final) - val selFinalClass = selClass && (selector.typeSymbol is Final) - - // Cases --------------------------------- - val valueClassesOrAny = - ValueClasses.isDerivedValueClass(scrutinee.typeSymbol) || - ValueClasses.isDerivedValueClass(selector.typeSymbol) || - scrutinee == defn.ObjectType - - val knownStatically = scFinalClass - - val falseIfUnrelated = - (scClassNonFinal && selClassNonFinal) || - (scClassNonFinal && selFinalClass) || - (scTrait && selFinalClass) - - val happens = - (scClassNonFinal && selClassNonFinal) || - (scTrait && selClassNonFinal) || - (scTrait && selTrait) - - val inMatch = s.qualifier.symbol is Case - // FIXME: This will misclassify case objects! We need to find another way to characterize - // isInstanceOfs generated by matches. - // Probably the most robust way is to use another symbol for the isInstanceOf method. - - if (valueClassesOrAny) tree - else if (knownStatically) - handleStaticallyKnown(s, scrutinee, selector, inMatch, tree.pos) - else if (falseIfUnrelated && scrutinee <:< selector) - // scrutinee is a subtype of the selector, safe to rewrite - rewrite(s, to = true) - else if (falseIfUnrelated && !(selector <:< scrutinee)) - // selector and scrutinee are unrelated - handleFalseUnrelated(s, scrutinee, selector, inMatch) - else if (happens) tree - else tree - } - - case _ => tree - } + def evalTypeTest(tree: TypeApply, qualifier: Tree, inMatch: Boolean) = { + val scrutinee = erasure(qualifier.tpe.widen) + val selector = erasure(tree.args.head.tpe.widen) + + val scTrait = scrutinee.typeSymbol is Trait + val scClass = + scrutinee.typeSymbol.isClass && + !(scrutinee.typeSymbol is Trait) && + !(scrutinee.typeSymbol is Module) + + val scClassNonFinal = scClass && !(scrutinee.typeSymbol is Final) + val scFinalClass = scClass && (scrutinee.typeSymbol is Final) + + val selTrait = selector.typeSymbol is Trait + val selClass = + selector.typeSymbol.isClass && + !(selector.typeSymbol is Trait) && + !(selector.typeSymbol is Module) + + val selClassNonFinal = selClass && !(selector.typeSymbol is Final) + val selFinalClass = selClass && (selector.typeSymbol is Final) + + // Cases --------------------------------- + val valueClassesOrAny = + ValueClasses.isDerivedValueClass(scrutinee.typeSymbol) || + ValueClasses.isDerivedValueClass(selector.typeSymbol) || + scrutinee == defn.ObjectType + + val knownStatically = scFinalClass + + val falseIfUnrelated = + (scClassNonFinal && selClassNonFinal) || + (scClassNonFinal && selFinalClass) || + (scTrait && selFinalClass) + + val happens = + (scClassNonFinal && selClassNonFinal) || + (scTrait && selClassNonFinal) || + (scTrait && selTrait) + + if (valueClassesOrAny) tree + else if (knownStatically) + handleStaticallyKnown(qualifier, scrutinee, selector, inMatch, tree.pos) + else if (falseIfUnrelated && scrutinee <:< selector) + // scrutinee is a subtype of the selector, safe to rewrite + rewrite(qualifier, to = true) + else if (falseIfUnrelated && !(selector <:< scrutinee)) + // selector and scrutinee are unrelated + handleFalseUnrelated(qualifier, scrutinee, selector, inMatch) + else if (happens) tree + else tree + } - evalTypeApply(tree) + tree.fun match { + case fn: Select if fn.symbol == defn.Any_typeTest => + evalTypeTest( + cpy.TypeApply(tree)(fn.qualifier.select(defn.Any_isInstanceOf), tree.args), + fn.qualifier, inMatch = true) + case fn: Select if fn.symbol == defn.Any_isInstanceOf => + evalTypeTest(tree, fn.qualifier, inMatch = false) + case _ => tree + } } } diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala index 7854538e47e7..150aeb43c43c 100644 --- a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -1,57 +1,38 @@ package dotty.tools.dotc package transform -import scala.language.postfixOps - +import core._ import TreeTransforms._ -import core.Denotations._ -import core.SymDenotations._ -import core.Contexts._ -import core.Symbols._ -import core.Types._ -import core.Constants._ -import core.StdNames._ -import core.NameKinds._ -import dotty.tools.dotc.ast.{untpd, TreeTypeMap, tpd} -import dotty.tools.dotc.core -import dotty.tools.dotc.core.DenotTransformers.DenotTransformer -import dotty.tools.dotc.core.Phases.Phase -import dotty.tools.dotc.core.{TypeApplications, Flags} -import dotty.tools.dotc.typer.Applications -import dotty.tools.dotc.util.Positions -import typer.ErrorReporting._ +import collection.mutable +import SymDenotations._, Symbols._, Contexts._, Types._, Names._, StdNames._, NameOps._ import ast.Trees._ -import Applications._ -import TypeApplications._ -import SymUtils._, core.NameOps._ -import core.Mode -import patmat._ - -import dotty.tools.dotc.util.Positions.Position -import dotty.tools.dotc.core.Decorators._ -import dotty.tools.dotc.core.Flags - -/** This phase rewrites pattern matches. - * FIXME: A more detailed explanation would be good. +import util.Positions._ +import typer.Applications.{isProductMatch, isGetMatch, productSelectors} +import SymUtils._ +import Flags._, Constants._ +import Decorators._ +import patmat.Space +import NameKinds.{UniqueNameKind, PatMatStdBinderName, PatMatCaseName} +import config.Printers.patmatch + +/** The pattern matching transform. + * After this phase, the only Match nodes remaining in the code are simple switches + * where every pattern is an integer constant */ -class PatternMatcher extends MiniPhaseTransform with DenotTransformer { - import dotty.tools.dotc.ast.tpd._ - - override def transform(ref: SingleDenotation)(implicit ctx: Context): SingleDenotation = ref +class PatternMatcher extends MiniPhaseTransform { + import ast.tpd._ + import PatternMatcher._ + override def phaseName = "patternMatcher" override def runsAfter = Set(classOf[ElimRepeated]) - override def runsAfterGroupsOf = Set(classOf[TailRec]) // tailrec is not capable of reversing the patmat tranformation made for tree - override def phaseName = "patternMatcher" - - private var _id = 0 // left for debuging - override def transformMatch(tree: Match)(implicit ctx: Context, info: TransformerInfo): Tree = { - val translated = new Translator()(ctx).translator.translateMatch(tree) + val translated = new Translator(tree.tpe, this).translateMatch(tree) // check exhaustivity and unreachability - val engine = new SpaceEngine + val engine = new patmat.SpaceEngine + if (engine.checkable(tree)) { engine.checkExhaustivity(tree) engine.checkRedundancy(tree) @@ -59,1813 +40,897 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer { translated.ensureConforms(tree.tpe) } +} - class Translator(implicit ctx: Context) { - - def translator = { - new OptimizingMatchTranslator/*(localTyper)*/ - } - - class OptimizingMatchTranslator extends MatchOptimizer/*(val typer: analyzer.Typer)*/ with MatchTranslator - - trait CodegenCore { - - // assert(owner ne null); assert(owner ne NoSymbol) - def freshSym(pos: Position, tp: Type = NoType, unique: UniqueNameKind = PatMatStdBinderName, owner: Symbol = ctx.owner) = { - ctx.newSymbol(owner, unique.fresh(), Flags.Synthetic | Flags.Case, tp, coord = pos) +object PatternMatcher { + import ast.tpd._ + + final val selfCheck = false // debug option, if on we check that no case gets generated twice + + /** Was symbol generated by pattern matcher? */ + def isPatmatGenerated(sym: Symbol)(implicit ctx: Context): Boolean = + sym.is(Synthetic) && + (sym.name.is(PatMatStdBinderName) || sym.name.is(PatMatCaseName)) + + /** The pattern matching translator. + * Its general structure is a pipeline: + * + * Match tree ---matchPlan---> Plan ---optimize---> Plan ---emit---> Tree + * + * The pipeline consists of three steps: + * + * - build a plan, using methods `matchPlan`, `caseDefPlan`, `patternPlan`. + * - optimize the plan, using methods listed in `optimization`, + * - emit the translated tree, using methods `emit`, `collectSwitchCases`, + * `emitSwitchCases`, and `emitCondition`. + * + * A plan represents the underlying decision graph. It consists + * of tests, let and label bindings, calls to labels and code blocks. + * It's represented by its own data type. Plans are optimized by + * inlining, hoisting, and the elimination of redundant tests and dead code. + */ + class Translator(resultType: Type, trans: TreeTransform)(implicit ctx: Context, info: TransformerInfo) { + + // ------- Bindings for variables and labels --------------------- + + /** A map from variable symbols to their defining trees + * and from labels to their defining plans + */ + private val initializer = mutable.Map[Symbol, Tree]() + private val labelled = mutable.Map[Symbol, Plan]() + + private def newVar(rhs: Tree, flags: FlagSet): TermSymbol = + ctx.newSymbol(ctx.owner, PatMatStdBinderName.fresh(), Synthetic | Case | flags, + sanitize(rhs.tpe), coord = rhs.pos) + // TODO: Drop Case once we use everywhere else `isPatmatGenerated`. + + /** The plan `let x = rhs in body(x)` where `x` is a fresh variable */ + private def letAbstract(rhs: Tree)(body: Symbol => Plan): Plan = { + val vble = newVar(rhs, EmptyFlags) + initializer(vble) = rhs + LetPlan(vble, body(vble)) + } + + /** The plan `let l = labelled in body(l)` where `l` is a fresh label */ + private def labelAbstract(labeld: Plan)(body: (=> Plan) => Plan): Plan = { + val label = ctx.newSymbol(ctx.owner, PatMatCaseName.fresh(), Synthetic | Label | Method, + MethodType(Nil, resultType)) + labelled(label) = labeld + LabelledPlan(label, body(CallPlan(label, Nil)), Nil) + } + + /** Test whether a type refers to a pattern-generated variable */ + private val refersToInternal = new TypeAccumulator[Boolean] { + def apply(x: Boolean, tp: Type) = + x || { + tp match { + case tp: TermRef => isPatmatGenerated(tp.symbol) + case _ => false + } + } || foldOver(x, tp) } - def newSynthCaseLabel(unique: UniqueNameKind, tpe: Type, owner: Symbol = ctx.owner) = - ctx.newSymbol(owner, unique.fresh(), Flags.Label | Flags.Synthetic | Flags.Method, tpe).asTerm - //NoSymbol.newLabel(freshName(name), NoPosition) setFlag treeInfo.SYNTH_CASE_FLAGS - - // codegen relevant to the structure of the translation (how extractors are combined) - trait AbsCodegen { - def matcher(scrut: Tree, scrutSym: Symbol, restpe: Type)(cases: List[Casegen => Tree], matchFailGen: Option[Symbol => Tree]): Tree - - // local / context-free - - /* cast b to tp */ - def _asInstanceOf(b: Symbol, tp: Type): Tree - /* a check `checker` == binder */ - def _equals(checker: Tree, binder: Symbol): Tree - /* b.isIsInstanceOf[tp] */ - def _isInstanceOf(b: Symbol, tp: Type): Tree - /* tgt is expected to be a Seq, call tgt.drop(n) */ - def drop(tgt: Tree)(n: Int): Tree - /* tgt is expected to have method apply(int), call tgt.drop(i) */ - def index(tgt: Tree)(i: Int): Tree - /* make tree that accesses the i'th component of the tuple referenced by binder */ - def tupleSel(binder: Symbol)(i: Int): Tree + /** Widen type as far as necessary so that it does not refer to a pattern- + * generated variable. + */ + private def sanitize(tp: Type): Type = tp.widenExpr match { + case tp: TermRef if refersToInternal(false, tp) => sanitize(tp.underlying) + case tp => tp } - // structure - trait Casegen extends AbsCodegen { - def one(res: Tree): Tree + // ------- Plan and test types ------------------------ - def flatMap(prev: Tree, b: Symbol, next: Tree): Tree - def flatMapCond(cond: Tree, res: Tree, nextBinder: Symbol, next: Tree): Tree - def flatMapGuard(cond: Tree, next: Tree): Tree - def ifThenElseZero(c: Tree, thenp: Tree): Tree = - If(c, thenp, zero) - protected def zero: Tree - } + /** Counter to display plans nicely, for debugging */ + private var nxId = 0 - def codegen: AbsCodegen + /** The different kinds of plans */ + sealed abstract class Plan { val id = nxId; nxId += 1 } - abstract class CommonCodegen extends AbsCodegen { - def tupleSel(binder: Symbol)(i: Int): Tree = ref(binder).select(nme.productAccessorName(i)) - def index(tgt: Tree)(i: Int): Tree = { - if (i > 0) tgt.select(defn.Seq_apply).appliedTo(Literal(Constant(i))) - else tgt.select(defn.Seq_head).ensureApplied + case class TestPlan(test: Test, var scrutinee: Tree, pos: Position, + var onSuccess: Plan, var onFailure: Plan) extends Plan { + override def equals(that: Any) = that match { + case that: TestPlan => this.scrutinee === that.scrutinee && this.test == that.test + case _ => false } + override def hashCode = scrutinee.hash * 41 + test.hashCode + } - // Right now this blindly calls drop on the result of the unapplySeq - // unless it verifiably has no drop method (this is the case in particular - // with Array.) You should not actually have to write a method called drop - // for name-based matching, but this was an expedient route for the basics. - def drop(tgt: Tree)(n: Int): Tree = { - def callDirect = tgt.select(nme.drop).appliedTo(Literal(Constant(n))) - def callRuntime = ref(defn.ScalaRuntime_drop).appliedTo(tgt, Literal(Constant(n))) + case class LetPlan(sym: TermSymbol, var body: Plan) extends Plan + case class LabelledPlan(sym: TermSymbol, var body: Plan, var params: List[TermSymbol]) extends Plan + case class CodePlan(var tree: Tree) extends Plan + case class CallPlan(label: TermSymbol, + var args: List[(/*formal*/TermSymbol, /*actual*/TermSymbol)]) extends Plan - def needsRuntime = !(tgt.tpe derivesFrom defn.SeqClass) /*typeOfMemberNamedDrop(tgt.tpe) == NoType*/ + object TestPlan { + def apply(test: Test, sym: Symbol, pos: Position, ons: Plan, onf: Plan): TestPlan = + TestPlan(test, ref(sym), pos, ons, onf) + } - if (needsRuntime) callRuntime else callDirect + /** The different kinds of tests */ + sealed abstract class Test + case class TypeTest(tpt: Tree) extends Test { // scrutinee.isInstanceOf[tpt] + override def equals(that: Any) = that match { + case that: TypeTest => this.tpt.tpe =:= that.tpt.tpe + case _ => false } - - // NOTE: checker must be the target of the ==, that's the patmat semantics for ya - def _equals(checker: Tree, binder: Symbol): Tree = - tpd.applyOverloaded(checker, nme.EQ, List(ref(binder)), List.empty, defn.BooleanType) - - // the force is needed mainly to deal with the GADT typing hack (we can't detect it otherwise as tp nor pt need contain an abstract type, we're just casting wildly) - def _asInstanceOf(b: Symbol, tp: Type): Tree = ref(b).ensureConforms(tp) // andType here breaks t1048 - def _isInstanceOf(b: Symbol, tp: Type): Tree = ref(b).select(defn.Any_isInstanceOf).appliedToType(tp) + override def hashCode = tpt.tpe.hash } - } - - object Rebindings { - def apply(from: Symbol, to: Symbol) = new Rebindings(List(from), List(ref(to))) - // requires sameLength(from, to) - def apply(from: List[Symbol], to: List[Tree]) = - if (from nonEmpty) new Rebindings(from, to) else NoRebindings - } - - class Rebindings(val lhs: List[Symbol], val rhs: List[Tree]) { - def >>(other: Rebindings) = { - if (other eq NoRebindings) this - else if (this eq NoRebindings) other - else { - assert((lhs.toSet ++ other.lhs.toSet).size == lhs.length + other.lhs.length, "no double assignments") - new Rebindings(this.lhs ++ other.lhs, this.rhs ++ other.rhs) + case class EqualTest(tree: Tree) extends Test { // scrutinee == tree + override def equals(that: Any) = that match { + case that: EqualTest => this.tree === that.tree + case _ => false } + override def hashCode = tree.hash } + case class LengthTest(len: Int, exact: Boolean) extends Test // scrutinee (== | >=) len + case object NonEmptyTest extends Test // !scrutinee.isEmpty + case object NonNullTest extends Test // scrutinee ne null + case object GuardTest extends Test // scrutinee - def emitValDefs: List[ValDef] = { - (lhs, rhs).zipped.map((symbol, tree) => ValDef(symbol.asTerm, tree.ensureConforms(symbol.info))) - } - } - object NoRebindings extends Rebindings(Nil, Nil) - - trait OptimizedCodegen extends CodegenCore { - override def codegen: AbsCodegen = optimizedCodegen - - // when we know we're targetting Option, do some inlining the optimizer won't do - // for example, `o.flatMap(f)` becomes `if (o == None) None else f(o.get)`, similarly for orElse and guard - // this is a special instance of the advanced inlining optimization that takes a method call on - // an object of a type that only has two concrete subclasses, and inlines both bodies, guarded by an if to distinguish the two cases - object optimizedCodegen extends CommonCodegen { - - /** Inline runOrElse and get rid of Option allocations - * - * runOrElse(scrut: scrutTp)(matcher): resTp = matcher(scrut) getOrElse ${catchAll(`scrut`)} - * the matcher's optional result is encoded as a flag, keepGoing, where keepGoing == true encodes result.isEmpty, - * if keepGoing is false, the result Some(x) of the naive translation is encoded as matchRes == x - */ - def matcher(scrut: Tree, scrutSym: Symbol, restpe: Type)(cases: List[Casegen => Tree], matchFailGen: Option[Symbol => Tree]): Tree = { - //val matchRes = ctx.newSymbol(NoSymbol, ctx.freshName("matchRes").toTermName, Flags.Synthetic | Flags.Param | Flags.Label | Flags.Method, restpe /*withoutAnnotations*/) - //NoSymbol.newValueParameter(newTermName("x"), NoPosition, newFlags = SYNTHETIC) setInfo restpe.withoutAnnotations - - - val caseSyms: List[TermSymbol] = cases.scanLeft(ctx.owner.asTerm)((curOwner, nextTree) => - newSynthCaseLabel(PatMatCaseName, MethodType(Nil, restpe), curOwner)).tail - - // must compute catchAll after caseLabels (side-effects nextCase) - // catchAll.isEmpty iff no synthetic default case needed (the (last) user-defined case is a default) - // if the last user-defined case is a default, it will never jump to the next case; it will go immediately to matchEnd - val catchAllDef = matchFailGen.map { _(scrutSym) } - .getOrElse(Throw(New(defn.MatchErrorType, List(ref(scrutSym))))) - - val matchFail = newSynthCaseLabel(PatMatMatchFailName, MethodType(Nil, restpe)) - val catchAllDefBody = DefDef(matchFail, catchAllDef) - - val nextCases = (caseSyms.tail ::: List(matchFail)).map(ref(_).ensureApplied) - val caseDefs = (cases zip caseSyms zip nextCases).foldRight[Tree](catchAllDefBody) { - // dotty deviation - //case (((mkCase, sym), nextCase), acc) => - (x: (((Casegen => Tree), TermSymbol), Tree), acc: Tree) => x match { - case ((mkCase, sym), nextCase) => - val body = mkCase(new OptimizedCasegen(nextCase)).ensureConforms(restpe) - - DefDef(sym, _ => Block(List(acc), body)) - } - } + // ------- Generating plans from trees ------------------------ - // scrutSym == NoSymbol when generating an alternatives matcher - // val scrutDef = scrutSym.fold(List[Tree]())(ValDef(_, scrut) :: Nil) // for alternatives + /** A set of variabes that are known to be not null */ + private val nonNull = mutable.Set[Symbol]() - Block(List(caseDefs), ref(caseSyms.head).ensureApplied) + /** A conservative approximation of which patterns do not discern anything. + * They are discarded during the translation. + */ + private object WildcardPattern { + def unapply(pat: Tree): Boolean = pat match { + case Typed(_, tpt) if tpt.tpe.isRepeatedParam => true + case Bind(nme.WILDCARD, WildcardPattern()) => true // don't skip when binding an interesting symbol! + case t if isWildcardArg(t) => true + case x: BackquotedIdent => false + case x: Ident => x.name.isVariableName + case Alternative(ps) => ps.forall(unapply) + case EmptyTree => true + case _ => false } + } - class OptimizedCasegen(nextCase: Tree) extends CommonCodegen with Casegen { - def matcher(scrut: Tree, scrutSym: Symbol, restpe: Type)(cases: List[Casegen => Tree], matchFailGen: Option[Symbol => Tree]): Tree = - optimizedCodegen.matcher(scrut, scrutSym, restpe)(cases, matchFailGen) - - // only used to wrap the RHS of a body - // res: T - // returns MatchMonad[T] - def one(res: Tree): Tree = /*ref(matchEnd) appliedTo*/ res // a jump to a case label is special-cased in typedApply - protected def zero: Tree = nextCase - - // prev: MatchMonad[T] - // b: T - // next: MatchMonad[U] - // returns MatchMonad[U] - def flatMap(prev: Tree, b: Symbol, next: Tree): Tree = { - val resultArity = productArity(b.info) - if (isProductMatch(prev.tpe, resultArity)) { - val nullCheck: Tree = prev.select(defn.Object_ne).appliedTo(Literal(Constant(null))) - ifThenElseZero( - nullCheck, - Block( - List(ValDef(b.asTerm, prev)), - next //Substitution(b, ref(prevSym))(next) - ) - ) - } - else { - val getDenot = extractorMember(prev.tpe, nme.get) - val isEmptyDenot = extractorMember(prev.tpe, nme.isEmpty) - assert(getDenot.exists && isEmptyDenot.exists, i"${prev.tpe}") - - val tmpSym = freshSym(prev.pos, prev.tpe, PatMatOName) - val prevValue = ref(tmpSym).select(getDenot.symbol).ensureApplied - - Block( - List(ValDef(tmpSym, prev)), - // must be isEmpty and get as we don't control the target of the call (prev is an extractor call) - ifThenElseZero( - ref(tmpSym).select(isEmptyDenot.symbol).select(defn.Boolean_!), - Block(List(ValDef(b.asTerm, prevValue)), next) - ) - ) - } - } - - // cond: Boolean - // res: T - // nextBinder: T - // next == MatchMonad[U] - // returns MatchMonad[U] - def flatMapCond(cond: Tree, res: Tree, nextBinder: Symbol, next: Tree): Tree = { - val rest = Block(List(ValDef(nextBinder.asTerm, res)), next) - ifThenElseZero(cond, rest) - } - - // guardTree: Boolean - // next: MatchMonad[T] - // returns MatchMonad[T] - def flatMapGuard(guardTree: Tree, next: Tree): Tree = - ifThenElseZero(guardTree, next) - - def flatMapCondStored(cond: Tree, condSym: Symbol, res: Tree, nextBinder: Symbol, next: Tree): Tree = - ifThenElseZero(cond, Block( - List(Assign(ref(condSym), Literal(Constant(true))), - Assign(ref(nextBinder), res)), - next - )) + private object VarArgPattern { + def unapply(pat: Tree): Option[Tree] = swapBind(pat) match { + case Typed(pat1, tpt) if tpt.tpe.isRepeatedParam => Some(pat1) + case _ => None } } - } - final case class Suppression(exhaustive: Boolean, unreachable: Boolean) - object Suppression { - val NoSuppression = Suppression(false, false) - } - - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // the making of the trees - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - trait TreeMakers extends CodegenCore { - def optimizeCases(prevBinder: Symbol, cases: List[List[TreeMaker]], pt: Type): (List[List[TreeMaker]], List[Tree]) - def analyzeCases(prevBinder: Symbol, cases: List[List[TreeMaker]], pt: Type, suppression: Suppression): Unit = {} - - def emitSwitch(scrut: Tree, scrutSym: Symbol, cases: List[List[TreeMaker]], pt: Type, matchFailGenOverride: Option[Symbol => Tree], unchecked: Boolean): Option[Tree] = { - // TODO Deal with guards? - - def isSwitchableType(tpe: Type): Boolean = - (tpe isRef defn.IntClass) || - (tpe isRef defn.ByteClass) || - (tpe isRef defn.ShortClass) || - (tpe isRef defn.CharClass) - object IntEqualityTestTreeMaker { - def unapply(treeMaker: EqualityTestTreeMaker): Option[Int] = treeMaker match { - case EqualityTestTreeMaker(`scrutSym`, _, Literal(const), _) => - if (const.isIntRange) Some(const.intValue) - else None - case _ => - None + /** Rewrite (repeatedly) `x @ (p: T)` to `(x @ p): T` + * This brings out the type tests to where they can be analyzed. + */ + private def swapBind(tree: Tree): Tree = tree match { + case Bind(name, pat0) => + swapBind(pat0) match { + case Typed(pat, tpt) => Typed(cpy.Bind(tree)(name, pat), tpt) + case _ => tree } - } - - def isSwitchCase(treeMakers: List[TreeMaker]): Boolean = treeMakers match { - // case 5 => - case List(IntEqualityTestTreeMaker(_), _: BodyTreeMaker) => - true - - // case 5 | 6 => - case List(AlternativesTreeMaker(`scrutSym`, alts, _), _: BodyTreeMaker) => - alts.forall { - case List(IntEqualityTestTreeMaker(_)) => true - case _ => false - } + case _ => tree + } - // case _ => - case List(_: BodyTreeMaker) => - true + /** Plan for matching `scrutinee` symbol against `tree` pattern */ + private def patternPlan(scrutinee: Symbol, tree: Tree, onSuccess: Plan, onFailure: Plan): Plan = { - /* case x @ pat => - * This includes: - * case x => - * case x @ 5 => - * case x @ (5 | 6) => - */ - case (_: SubstOnlyTreeMaker) :: rest => - isSwitchCase(rest) + /** Plan for matching `selectors` against argument patterns `args` */ + def matchArgsPlan(selectors: List[Tree], args: List[Tree], onSuccess: Plan): Plan = + args match { + case arg :: args1 => + val selector :: selectors1 = selectors + letAbstract(selector)( + patternPlan(_, arg, matchArgsPlan(selectors1, args1, onSuccess), onFailure)) + case Nil => onSuccess + } - case _ => - false + /** Plan for matching the sequence in `seqSym` against sequence elements `args`. + * If `exact` is true, the sequence is not permitted to have any elements following `args`. + */ + def matchElemsPlan(seqSym: Symbol, args: List[Tree], exact: Boolean, onSuccess: Plan) = { + val selectors = args.indices.toList.map(idx => + ref(seqSym).select(nme.apply).appliedTo(Literal(Constant(idx)))) + TestPlan(LengthTest(args.length, exact), seqSym, seqSym.pos, + matchArgsPlan(selectors, args, onSuccess), onFailure) } - /* (Nil, body) means that `body` is the default case - * It's a bit hacky but it simplifies manipulations. + /** Plan for matching the sequence in `getResult` against sequence elements + * and a possible last varargs argument `args`. */ - def extractSwitchCase(treeMakers: List[TreeMaker]): (List[Int], BodyTreeMaker) = (treeMakers: @unchecked) match { - // case 5 => - case List(IntEqualityTestTreeMaker(intValue), body: BodyTreeMaker) => - (List(intValue), body) - - // case 5 | 6 => - case List(AlternativesTreeMaker(_, alts, _), body: BodyTreeMaker) => - val intValues = alts.map { alt => - (alt: @unchecked) match { - case List(IntEqualityTestTreeMaker(intValue)) => intValue + def unapplySeqPlan(getResult: Symbol, args: List[Tree]): Plan = args.lastOption match { + case Some(VarArgPattern(arg)) => + val matchRemaining = + if (args.length == 1) + patternPlan(getResult, arg, onSuccess, onFailure) + else { + val dropped = ref(getResult) + .select(defn.Seq_drop.matchingMember(getResult.info)) + .appliedTo(Literal(Constant(args.length - 1))) + letAbstract(dropped) { droppedResult => + patternPlan(droppedResult, arg, onSuccess, onFailure) + } } - } - (intValues, body) - - // case _ => - case List(body: BodyTreeMaker) => - (Nil, body) - - // case x @ pat => - case (_: SubstOnlyTreeMaker) :: rest => - /* Rebindings have been propagated, so the eventual body in `rest` - * contains all the necessary information. The substitution can be - * dropped at this point. - */ - extractSwitchCase(rest) + matchElemsPlan(getResult, args.init, exact = false, matchRemaining) + case _ => + matchElemsPlan(getResult, args, exact = true, onSuccess) } - def doOverlap(a: List[Int], b: List[Int]): Boolean = - a.exists(b.contains _) - - def makeSwitch(valuesToCases: List[(List[Int], BodyTreeMaker)]): Tree = { - def genBody(body: BodyTreeMaker): Tree = { - val valDefs = body.rebindings.emitValDefs - if (valDefs.isEmpty) body.body - else Block(valDefs, body.body) - } - - val intScrut = - if (pt isRef defn.IntClass) ref(scrutSym) - else Select(ref(scrutSym), nme.toInt) - - val (normalCases, defaultCaseAndRest) = valuesToCases.span(_._1.nonEmpty) + /** Plan for matching the result of an unapply against argument patterns `args` */ + def unapplyPlan(unapp: Tree, args: List[Tree]): Plan = { + def caseClass = unapp.symbol.owner.linkedClass + lazy val caseAccessors = caseClass.caseAccessors.filter(_.is(Method)) - val newCases = for { - (values, body) <- normalCases - } yield { - val literals = values.map(v => Literal(Constant(v))) - val pat = - if (literals.size == 1) literals.head - else Alternative(literals) - CaseDef(pat, EmptyTree, genBody(body)) - } + def isSyntheticScala2Unapply(sym: Symbol) = + sym.is(SyntheticCase) && sym.owner.is(Scala2x) - val catchAllDef = { - if (defaultCaseAndRest.isEmpty) { - matchFailGenOverride.fold[Tree]( - Throw(New(defn.MatchErrorType, List(ref(scrutSym)))))( - _(scrutSym)) - } else { - /* After the default case, assuming the IR even allows anything, - * things are unreachable anyway and can be removed. - */ - genBody(defaultCaseAndRest.head._2) + if (isSyntheticScala2Unapply(unapp.symbol) && caseAccessors.length == args.length) + matchArgsPlan(caseAccessors.map(ref(scrutinee).select(_)), args, onSuccess) + else if (unapp.tpe.isRef(defn.BooleanClass)) + TestPlan(GuardTest, unapp, unapp.pos, onSuccess, onFailure) + else { + letAbstract(unapp) { unappResult => + val isUnapplySeq = unapp.symbol.name == nme.unapplySeq + if (isProductMatch(unapp.tpe.widen, args.length) && !isUnapplySeq) { + val selectors = productSelectors(unapp.tpe).take(args.length) + .map(ref(unappResult).select(_)) + matchArgsPlan(selectors, args, onSuccess) + } + else { + assert(isGetMatch(unapp.tpe)) + val argsPlan = { + val get = ref(unappResult).select(nme.get, _.info.isParameterless) + if (isUnapplySeq) + letAbstract(get)(unapplySeqPlan(_, args)) + else + letAbstract(get) { getResult => + val selectors = + if (args.tail.isEmpty) ref(getResult) :: Nil + else productSelectors(get.tpe).map(ref(getResult).select(_)) + matchArgsPlan(selectors, args, onSuccess) + } + } + TestPlan(NonEmptyTest, unappResult, unapp.pos, argsPlan, onFailure) + } } } - val defaultCase = CaseDef(Underscore(defn.IntType), EmptyTree, catchAllDef) - - Match(intScrut, newCases :+ defaultCase) } - val dealiased = scrut.tpe.widenDealias - if (isSwitchableType(dealiased) && cases.forall(isSwitchCase)) { - val valuesToCases = cases.map(extractSwitchCase) - val values = valuesToCases.map(_._1) - if (values.tails.exists { tail => tail.nonEmpty && tail.tail.exists(doOverlap(_, tail.head)) }) { - // TODO Deal with overlapping cases (mostly useless without guards) - None - } else { - Some(makeSwitch(valuesToCases)) - } - } else { - if (dealiased hasAnnotation defn.SwitchAnnot) - ctx.warning("failed to emit switch for `@switch` annotated match", scrut.pos) - None + // begin patternPlan + swapBind(tree) match { + case Typed(pat, tpt) => + TestPlan(TypeTest(tpt), scrutinee, tree.pos, + letAbstract(ref(scrutinee).asInstance(tpt.tpe)) { casted => + nonNull += casted + patternPlan(casted, pat, onSuccess, onFailure) + }, + onFailure) + case UnApply(extractor, implicits, args) => + val mt @ MethodType(_) = extractor.tpe.widen + var unapp = extractor.appliedTo(ref(scrutinee).ensureConforms(mt.paramInfos.head)) + if (implicits.nonEmpty) unapp = unapp.appliedToArgs(implicits) + val unappPlan = unapplyPlan(unapp, args) + if (scrutinee.info.isNotNull || nonNull(scrutinee)) unappPlan + else TestPlan(NonNullTest, scrutinee, tree.pos, unappPlan, onFailure) + case Bind(name, body) => + val body1 = patternPlan(scrutinee, body, onSuccess, onFailure) + if (name == nme.WILDCARD) body1 + else { + val bound = tree.symbol.asTerm + initializer(bound) = ref(scrutinee) + LetPlan(bound, body1) + } + case Alternative(alts) => + labelAbstract(onSuccess) { ons => + (alts :\ onFailure) { (alt, onf) => + labelAbstract(onf) { onf1 => + patternPlan(scrutinee, alt, ons, onf1) + } + } + } + case WildcardPattern() => + onSuccess + case _ => + TestPlan(EqualTest(tree), scrutinee, tree.pos, onSuccess, onFailure) } } - // for catch (no need to customize match failure) - def emitTypeSwitch(bindersAndCases: List[(Symbol, List[TreeMaker])], pt: Type): Option[List[CaseDef]] = - None // todo - - abstract class TreeMaker { - def pos: Position - - private[this] var currSub: Rebindings = null + private def caseDefPlan(scrutinee: Symbol, cdef: CaseDef, onFailure: Plan): Plan = + labelAbstract(onFailure) { onf => + var onSuccess: Plan = CodePlan(cdef.body) + if (!cdef.guard.isEmpty) + onSuccess = TestPlan(GuardTest, cdef.guard, cdef.guard.pos, onSuccess, onf) + patternPlan(scrutinee, cdef.pat, onSuccess, onf) + } - /** captures the scope and the value of the bindings in patterns - * important *when* the substitution happens (can't accumulate and do at once after the full matcher has been constructed) - */ - def rebindings: Rebindings = - if (currSub eq null) introducedRebindings - else currSub + private def matchPlan(tree: Match): Plan = + letAbstract(tree.selector) { scrutinee => + val matchError: Plan = CodePlan(Throw(New(defn.MatchErrorType, ref(scrutinee) :: Nil))) + (tree.cases :\ matchError)(caseDefPlan(scrutinee, _, _)) + } - protected def introducedRebindings: Rebindings + // ----- Optimizing plans --------------- - private[TreeMakers] def incorporateOuterRebinding(outerSubst: Rebindings): Unit = { - if (currSub ne null) { - ctx.debuglog("BUG: incorporateOuterRebinding called more than once for " + ((this, currSub, outerSubst))) - if (ctx.debug) Thread.dumpStack() - } - else currSub = outerSubst >> rebindings + /** A superclass for plan transforms */ + class PlanTransform extends (Plan => Plan) { + protected val treeMap = new TreeMap { + override def transform(tree: Tree)(implicit ctx: Context) = tree + } + def apply(tree: Tree): Tree = treeMap.transform(tree) + def apply(plan: TestPlan): Plan = { + plan.scrutinee = apply(plan.scrutinee) + plan.onSuccess = apply(plan.onSuccess) + plan.onFailure = apply(plan.onFailure) + plan + } + def apply(plan: LetPlan): Plan = { + plan.body = apply(plan.body) + initializer(plan.sym) = apply(initializer(plan.sym)) + plan + } + def apply(plan: LabelledPlan): Plan = { + plan.body = apply(plan.body) + labelled(plan.sym) = apply(labelled(plan.sym)) + plan + } + def apply(plan: CallPlan): Plan = plan + def apply(plan: Plan): Plan = plan match { + case plan: TestPlan => apply(plan) + case plan: LetPlan => apply(plan) + case plan: LabelledPlan => apply(plan) + case plan: CallPlan => apply(plan) + case plan: CodePlan => plan } - - /** The substitution that specifies the trees that compute the values of the subpattern binders. - * - * Should not be used to perform actual substitution! - * Only used to reason symbolically about the values the subpattern binders are bound to. - * See TreeMakerToCond#updateSubstitution. - * - * Overridden in PreserveSubPatBinders to pretend it replaces the subpattern binders by subpattern refs - * (Even though we don't do so anymore -- see SI-5158, SI-5739 and SI-6070.) - * - * TODO: clean this up, would be nicer to have some higher-level way to compute - * the binders bound by this tree maker and the symbolic values that correspond to them - */ - def subPatternsAsRebindings: Rebindings = rebindings - - // build Tree that chains `next` after the current extractor - def chainBefore(next: Tree)(casegen: Casegen): Tree } - sealed trait NoNewBinders extends TreeMaker { - protected val introducedRebindings: Rebindings = NoRebindings + private class RefCounter extends PlanTransform { + val count = new mutable.HashMap[Symbol, Int] { + override def default(key: Symbol) = 0 + } } - case class TrivialTreeMaker(tree: Tree) extends TreeMaker with NoNewBinders { - def pos = tree.pos - - def chainBefore(next: Tree)(casegen: Casegen): Tree = tree + /** Reference counts for all labels */ + private def labelRefCount(plan: Plan): collection.Map[Symbol, Int] = { + object refCounter extends RefCounter { + override def apply(plan: LabelledPlan): Plan = { + apply(plan.body) + if (count(plan.sym) != 0) apply(labelled(plan.sym)) + plan + } + override def apply(plan: CallPlan): Plan = { + count(plan.label) += 1 + plan + } + } + refCounter(plan) + refCounter.count } - case class BodyTreeMaker(body: Tree, matchPt: Type) extends TreeMaker with NoNewBinders { - def pos = body.pos - - def chainBefore(next: Tree)(casegen: Casegen): Tree = // assert(next eq EmptyTree) - /*atPos(body.pos)*/(casegen.one(body)) // since SubstOnly treemakers are dropped, need to do it here - override def toString = "B" + ((body, matchPt)) + /** Reference counts for all variables */ + private def varRefCount(plan: Plan): collection.Map[Symbol, Int] = { + object refCounter extends RefCounter { + override val treeMap = new TreeMap { + override def transform(tree: Tree)(implicit ctx: Context) = tree match { + case tree: Ident => + if (isPatmatGenerated(tree.symbol)) count(tree.symbol) += 1 + tree + case _ => + super.transform(tree) + } + } + override def apply(plan: LetPlan): Plan = { + apply(plan.body) + if (count(plan.sym) != 0 || !isPatmatGenerated(plan.sym)) + apply(initializer(plan.sym)) + plan + } + override def apply(plan: LabelledPlan): Plan = { + apply(labelled(plan.sym)) + apply(plan.body) + plan + } + override def apply(plan: CallPlan): Plan = { + for ((formal, actual) <- plan.args) + if (count(formal) != 0) count(actual) += 1 + plan + } + } + refCounter(plan) + refCounter.count } - /** - * In scalac for such block - * x match { - * case d => - * } + /** Rewrite everywhere * - * d inside was to be substitued by x. + * if C then (let L = B in E1) else E2 + * --> + * let L = B in if C then E1 else E2 * - * In dotty, SubstOnlyTreeMakers instead generate normal ValDef, - * and does not create a new substitution. + * if C then E1 else (let L = B in E2) + * --> + * let L = B in if C then E1 else E2 * - * This was done for several reasons: - * 1) it is a lot easyer to Y-check, - * as d type could be used in . - * 2) it would simplify debugging of the generated code as - * this works also for nested patterns, and previously they used unreadable names - * 3) It showed better(~30%), performance, - * Rebuilding tree and propagating types was taking substantial time. - */ - case class SubstOnlyTreeMaker(prevBinder: Symbol, nextBinder: Symbol) extends TreeMaker { - val pos = Positions.NoPosition - - val introducedRebindings = Rebindings(prevBinder, nextBinder) - def chainBefore(next: Tree)(casegen: Casegen): Tree = next - //override def toString = "S" + localSubstitution - } - - sealed abstract class FunTreeMaker extends TreeMaker { - val nextBinder: Symbol - def pos = nextBinder.pos - } - - sealed abstract class CondTreeMaker extends FunTreeMaker { - val prevBinder: Symbol - val nextBinderTp: Type - val cond: Tree - val res: Tree - - val nextBinder: Symbol - lazy val introducedRebindings = /* - if (nextBinder ne prevBinder) Rebindings(prevBinder, nextBinder) - else */ NoRebindings - - def chainBefore(next: Tree)(casegen: Casegen): Tree = - if (prevBinder ne nextBinder) // happens when typeTest is known to succeed - /*atPos(pos)(*/casegen.flatMapCond(cond, res, nextBinder, next)//) - else casegen.flatMapGuard(cond, next) - } - - // unless we're optimizing, emit local variable bindings for all subpatterns of extractor/case class patterns - protected val debugInfoEmitVars = true //!settings.optimise.value - - /** - * Tree maker that captures sub pattern values during pattern match. - */ - sealed trait PreserveSubPatBinders extends TreeMaker { - val subPatBinders: List[Symbol] // captured values - val subPatRefs: List[Tree] // trees that will replace references to subPatBinders - val ignoredSubPatBinders: Set[Symbol] // ignored as they aren't used in body of pattern - - // unless `debugInfoEmitVars`, this set should contain the bare minimum for correctness - // mutable case class fields need to be stored regardless (SI-5158, SI-6070) -- see override in ProductExtractorTreeMaker - // sub patterns bound to wildcard (_) are never stored as they can't be referenced - // dirty debuggers will have to get dirty to see the wildcards - lazy val storedBinders: Set[Symbol] = - (if (debugInfoEmitVars) subPatBinders.toSet else Set.empty) ++ extraStoredBinders -- ignoredSubPatBinders - - // e.g., mutable fields of a case class in ProductExtractorTreeMaker - def extraStoredBinders: Set[Symbol] - - def emitVars = storedBinders.nonEmpty - - lazy val storedSubsted = (subPatBinders, subPatRefs).zipped.partition{ case (sym, _) => storedBinders(sym) } - - def stored = storedSubsted._1 - - def substed = storedSubsted._2 - - // dd: this didn't yet trigger error. But I believe it would. if this causes double denition of symbol error this can be replaced with NoRebindings - protected lazy val introducedRebindings: Rebindings = if (!emitVars) Rebindings(subPatBinders, subPatRefs) - else { - val (subPatBindersSubstituted, subPatRefsSubstituted) = substed.unzip - Rebindings(subPatBindersSubstituted.toList, subPatRefsSubstituted.toList) - } - - /** The substitution that specifies the trees that compute the values of the subpattern binders. - * - * We pretend to replace the subpattern binders by subpattern refs - * (Even though we don't do so anymore -- see SI-5158, SI-5739 and SI-6070.) - */ - override def subPatternsAsRebindings = - Rebindings(subPatBinders, subPatRefs) >> super.subPatternsAsRebindings - - def bindSubPats(in: Tree): Tree = - if (!emitVars) in - else { - // binders in `subPatBindersStored` that are referenced by tree `in` - val usedBinders = new collection.mutable.HashSet[Symbol]() - // all potentially stored subpat binders - val potentiallyStoredBinders = stored.unzip._1.toSet - // compute intersection of all symbols in the tree `in` and all potentially stored subpat binders - new DeepFolder[Unit]((x: Unit, t: Tree) => - if (potentiallyStoredBinders(t.symbol)) usedBinders += t.symbol).apply((), in) - - if (usedBinders.isEmpty) in - else { - // only store binders actually used - val (subPatBindersStored, subPatRefsStored) = stored.filter{case (b, _) => usedBinders(b)}.unzip - - Block((subPatBindersStored.toList, subPatRefsStored.toList).zipped.map((bind, ref) => { - // required in case original pattern had a more precise type - // eg case s@"foo" => would be otherwise translated to s with type String instead of String("foo") - def refTpeWiden = ref.tpe.widen - def bindInfoWiden = bind.info.widen - def loc = bind.showFullName - if (!(ref.tpe <:< bind.info.widen)) { - ctx.debuglog(s"here ${bind.showFullName} expected: ${bindInfoWiden.show} got: ${refTpeWiden.show}") - } - val refCasted = ref.ensureConforms(bind.info) - ValDef(bind.asTerm, refCasted) - }), in) - } + * let L1 = (let L2 = B2 in B1) in E + * --> + * let L2 = B2 in let L1 = B1 in E + */ + object hoistLabels extends PlanTransform { + override def apply(plan: TestPlan): Plan = + plan.onSuccess match { + case lp @ LabelledPlan(sym, body, _) => + plan.onSuccess = body + lp.body = plan + apply(lp) + case _ => + plan.onFailure match { + case lp @ LabelledPlan(sym, body, _) => + plan.onFailure = body + lp.body = plan + apply(lp) + case _ => + super.apply(plan) + } + } + override def apply(plan: LabelledPlan): Plan = + labelled(plan.sym) match { + case plan1: LabelledPlan => + labelled(plan.sym) = plan1.body + plan1.body = plan + apply(plan1) + case _ => + super.apply(plan) } } - /** - * Make a TreeMaker that will result in an extractor call specified by `extractor` - * the next TreeMaker (here, we don't know which it'll be) is chained after this one by flatMap'ing - * a function with binder `nextBinder` over our extractor's result - * the function's body is determined by the next TreeMaker - * (furthermore, the interpretation of `flatMap` depends on the codegen instance we're using). + /** Eliminate tests that are redundant (known to be true or false). + * Two parts: + * + * - If we know at some point that a test is true or false skip it and continue + * diretcly with the test's onSuccess or onFailure continuation. + * - If a label of a call points to a test that is known to be true or false + * at the point of call, let the label point instead to the test's onSuccess + * or onFailure continuation. * - * The values for the subpatterns, as computed by the extractor call in `extractor`, - * are stored in local variables that re-use the symbols in `subPatBinders`. - * This makes extractor patterns more debuggable (SI-5739). + * We use some tricks to identify a let pointing to an unapply and the + * NonEmptyTest that follows it as a single `UnappTest` test. */ - case class ExtractorTreeMaker(extractor: Tree, extraCond: Option[Tree], nextBinder: Symbol)( - val subPatBinders: List[Symbol], - val subPatRefs: List[Tree], - extractorReturnsBoolean: Boolean, - val checkedLength: Option[Int], - val prevBinder: Symbol, - val ignoredSubPatBinders: Set[Symbol] - ) extends FunTreeMaker with PreserveSubPatBinders { - - def extraStoredBinders: Set[Symbol] = Set() - - ctx.debuglog(s""" - |ExtractorTreeMaker($extractor, $extraCond, $nextBinder) { - | $subPatBinders - | $subPatRefs - | $extractorReturnsBoolean - | $checkedLength - | $prevBinder - | $ignoredSubPatBinders - |}""".stripMargin) - - def chainBefore(next: Tree)(casegen: Casegen): Tree = { - val condAndNext = extraCond match { - case Some(cond: Tree) => - casegen.ifThenElseZero(cond, bindSubPats(next)) - case _ => - bindSubPats(next) - } - - if (extractorReturnsBoolean) casegen.flatMapCond(extractor, unitLiteral, nextBinder, condAndNext) - else casegen.flatMap(extractor, nextBinder, condAndNext) // getType? - } + def elimRedundantTests(plan: Plan): Plan = { + type SeenTests = Map[TestPlan, Boolean] // Map from tests to their outcomes - override def toString = "X" + ((extractor, nextBinder.name)) - } + def isUnapply(sym: Symbol) = sym.name == nme.unapply || sym.name == nme.unapplySeq - object IrrefutableExtractorTreeMaker { - // will an extractor with unapply method of methodtype `tp` always succeed? - // note: this assumes the other side-conditions implied by the extractor are met - // (argument of the right type, length check succeeds for unapplySeq,...) - def irrefutableExtractorType(tp: Type): Boolean = tp.resultType.dealias match { - // case TypeRef(_, SomeClass, _) => true todo - // probably not useful since this type won't be inferred nor can it be written down (yet) - // case ConstantTrue => true todo - case _ => false - } + /** A locally used test value that represents combos of + * + * let x = X.unapply(...) in if !x.isEmpty then ... else ... + */ + case object UnappTest extends Test - def unapply(xtm: ExtractorTreeMaker): Option[(Tree, Symbol)] = xtm match { - case ExtractorTreeMaker(extractor, None, nextBinder) if irrefutableExtractorType(extractor.tpe) => - Some((extractor, nextBinder)) + /** If `plan` is the NonEmptyTest part of an unapply, the corresponding UnappTest + * otherwise the original plan + */ + def normalize(plan: TestPlan): TestPlan = plan.scrutinee match { + case id: Ident + if plan.test == NonEmptyTest && + isPatmatGenerated(id.symbol) && + isUnapply(initializer(id.symbol).symbol) => + TestPlan(UnappTest, initializer(id.symbol), plan.pos, plan.onSuccess, plan.onFailure) case _ => - None + plan } - } - object TypeTestTreeMaker { - // factored out so that we can consistently generate other representations of the tree that implements the test - // (e.g. propositions for exhaustivity and friends, boolean for isPureTypeTest) - trait TypeTestCondStrategy { - type Result - - def outerTest(testedBinder: Symbol, expectedTp: Type): Result - // TODO: can probably always widen - def typeTest(testedBinder: Symbol, expectedTp: Type): Result - def nonNullTest(testedBinder: Symbol): Result - def equalsTest(pat: Tree, testedBinder: Symbol): Result - def eqTest(pat: Tree, testedBinder: Symbol): Result - def and(a: Result, b: Result): Result - def tru: Result + /** Extractor for Let/NonEmptyTest combos that represent unapplies */ + object UnappTestPlan { + def unapply(plan: Plan): Option[TestPlan] = plan match { + case LetPlan(sym, body: TestPlan) => + val RHS = initializer(sym) + normalize(body) match { + case normPlan @ TestPlan(UnappTest, RHS, _, _, _) => Some(normPlan) + case _ => None + } + case _ => None + } } - object treeCondStrategy extends TypeTestCondStrategy { - type Result = Tree - - def and(a: Result, b: Result): Result = a.select(defn.Boolean_&&).appliedTo(b) - def tru = Literal(Constant(true)) - def typeTest(testedBinder: Symbol, expectedTp: Type) = codegen._isInstanceOf(testedBinder, expectedTp) - def nonNullTest(testedBinder: Symbol) = ref(testedBinder).select(defn.Object_ne).appliedTo(Literal(Constant(null))) - def equalsTest(pat: Tree, testedBinder: Symbol) = codegen._equals(pat, testedBinder) - def eqTest(pat: Tree, testedBinder: Symbol) = ref(testedBinder).select(defn.Object_eq).appliedTo(pat) - - def outerTest(testedBinder: Symbol, expectedTp: Type): Tree = { - val expectedOuter = expectedTp.normalizedPrefix match { - //case NoType => Literal(Constant(true)) // fallback for SI-6183 todo? - case pre: SingletonType => singleton(pre) - } + def intersect(tests1: SeenTests, tests2: SeenTests) = + tests1.filter { case(test, outcome) => tests2.get(test) == Some(outcome) } - // ExplicitOuter replaces `Select(q, outerSym) OBJ_EQ expectedPrefix` by `Select(q, outerAccessor(outerSym.owner)) OBJ_EQ expectedPrefix` - // if there's an outer accessor, otherwise the condition becomes `true` -- TODO: can we improve needsOuterTest so there's always an outerAccessor? - // val outer = expectedTp.typeSymbol.newMethod(vpmName.outer, newFlags = SYNTHETIC | ARTIFACT) setInfo expectedTp.prefix + /** The tests with known outcomes valid at entry to label */ + val seenAtLabel = mutable.HashMap[Symbol, SeenTests]() - val expectedClass = expectedTp.dealias.classSymbol.asClass - val test = codegen._asInstanceOf(testedBinder, expectedTp) - // TODO: Use nme.OUTER_SELECT, like the Inliner does? - val outerAccessorTested = ctx.atPhase(ctx.explicitOuterPhase.next) { implicit ctx => - ExplicitOuter.ensureOuterAccessors(expectedClass) - test.select(ExplicitOuter.outerAccessor(expectedClass)).select(defn.Object_eq).appliedTo(expectedOuter) + class ElimRedundant(seenTests: SeenTests) extends PlanTransform { + override def apply(plan: TestPlan): Plan = { + val normPlan = normalize(plan) + seenTests.get(normPlan) match { + case Some(outcome) => + apply(if (outcome) plan.onSuccess else plan.onFailure) + case None => + plan.onSuccess = new ElimRedundant(seenTests + (normPlan -> true))(plan.onSuccess) + plan.onFailure = new ElimRedundant(seenTests + (normPlan -> false))(plan.onFailure) + plan } - outerAccessorTested } - } - - /*object pureTypeTestChecker extends TypeTestCondStrategy { - type Result = Boolean - - def typeTest(testedBinder: Symbol, expectedTp: Type): Result = true - - def outerTest(testedBinder: Symbol, expectedTp: Type): Result = false - def nonNullTest(testedBinder: Symbol): Result = false - def equalsTest(pat: Tree, testedBinder: Symbol): Result = false - def eqTest(pat: Tree, testedBinder: Symbol): Result = false - def and(a: Result, b: Result): Result = false // we don't and type tests, so the conjunction must include at least one false - def tru = true - }*/ - - def nonNullImpliedByTestChecker(binder: Symbol) = new TypeTestCondStrategy { - type Result = Boolean - - def typeTest(testedBinder: Symbol, expectedTp: Type): Result = testedBinder eq binder - def outerTest(testedBinder: Symbol, expectedTp: Type): Result = false - def nonNullTest(testedBinder: Symbol): Result = testedBinder eq binder - def equalsTest(pat: Tree, testedBinder: Symbol): Result = false // could in principle analyse pat and see if it's statically known to be non-null - def eqTest(pat: Tree, testedBinder: Symbol): Result = false // could in principle analyse pat and see if it's statically known to be non-null - def and(a: Result, b: Result): Result = a || b - def tru = false - } - } - - /** implements the run-time aspects of (§8.2) (typedPattern has already done the necessary type transformations) - * - * Type patterns consist of types, type variables, and wildcards. A type pattern T is of one of the following forms: - - A reference to a class C, p.C, or T#C. - This type pattern matches any non-null instance of the given class. - Note that the prefix of the class, if it is given, is relevant for determining class instances. - For instance, the pattern p.C matches only instances of classes C which were created with the path p as prefix. - The bottom types scala.Nothing and scala.Null cannot be used as type patterns, because they would match nothing in any case. - - - A singleton type p.type. - This type pattern matches only the value denoted by the path p - (that is, a pattern match involved a comparison of the matched value with p using method eq in class AnyRef). // TODO: the actual pattern matcher uses ==, so that's what I'm using for now - // https://issues.scala-lang.org/browse/SI-4577 "pattern matcher, still disappointing us at equality time" - - - A compound type pattern T1 with ... with Tn where each Ti is a type pat- tern. - This type pattern matches all values that are matched by each of the type patterns Ti. - - - A parameterized type pattern T[a1,...,an], where the ai are type variable patterns or wildcards _. - This type pattern matches all values which match T for some arbitrary instantiation of the type variables and wildcards. - The bounds or alias type of these type variable are determined as described in (§8.3). - - - A parameterized type pattern scala.Array[T1], where T1 is a type pattern. // TODO - This type pattern matches any non-null instance of type scala.Array[U1], where U1 is a type matched by T1. - **/ - case class TypeTestTreeMaker(afterTest: Symbol, testedBinder: Symbol, expectedTp: Type, nextBinderTp: Type)(override val pos: Position, extractorArgTypeTest: Boolean = false) extends CondTreeMaker { - import TypeTestTreeMaker._ - - ctx.debuglog("TTTM" + ((prevBinder, extractorArgTypeTest, testedBinder, expectedTp, nextBinderTp))) - - val prevBinder = testedBinder - - val nextBinder = afterTest.asTerm - - def outerTestNeeded(implicit ctx: Context): Boolean = { - // See the test for SI-7214 for motivation for dealias. Later `treeCondStrategy#outerTest` - // generates an outer test based on `patType.prefix` with automatically dealises. - expectedTp.dealias match { - case tref @ TypeRef(pre: SingletonType, name) => - val s = tref - s.symbol.isClass && - ExplicitOuter.needsOuterIfReferenced(s.symbol.asClass) - case _ => - false + override def apply(plan: LabelledPlan): Plan = { + plan.body = apply(plan.body) + for (seenTests1 <- seenAtLabel.get(plan.sym)) + labelled(plan.sym) = new ElimRedundant(seenTests1)(labelled(plan.sym)) + plan } - } - - override lazy val introducedRebindings = NoRebindings - - // the logic to generate the run-time test that follows from the fact that - // a `prevBinder` is expected to have type `expectedTp` - // the actual tree-generation logic is factored out, since the analyses generate Cond(ition)s rather than Trees - // TODO: `null match { x : T }` will yield a check that (indirectly) tests whether `null ne null` - // don't bother (so that we don't end up with the warning "comparing values of types Null and Null using `ne' will always yield false") - def renderCondition(cs: TypeTestCondStrategy): cs.Result = { - import cs._ - - // propagate expected type - def expTp(t: Tree): t.type = t // setType expectedTp todo: - - def testedWide = testedBinder.info.widen - def expectedWide = expectedTp.widen - def isAnyRef = testedWide <:< defn.AnyRefType - def isAsExpected = testedWide <:< expectedTp - def isExpectedPrimitiveType = isAsExpected && expectedTp.classSymbol.isPrimitiveValueClass - def isExpectedReferenceType = isAsExpected && (expectedTp <:< defn.AnyRefType) - def mkNullTest = nonNullTest(testedBinder) - def mkOuterTest = outerTest(testedBinder, expectedTp) - def mkTypeTest = typeTest(testedBinder, expectedWide) - - def mkEqualsTest(lhs: Tree): cs.Result = equalsTest(lhs, testedBinder) - def mkEqTest(lhs: Tree): cs.Result = eqTest(lhs, testedBinder) - def addOuterTest(res: cs.Result): cs.Result = if (outerTestNeeded) and(res, mkOuterTest) else res - - // If we conform to expected primitive type: - // it cannot be null and cannot have an outer pointer. No further checking. - // If we conform to expected reference type: - // have to test outer and non-null - // If we do not conform to expected type: - // have to test type and outer (non-null is implied by successful type test) - def mkDefault = ( - if (isExpectedPrimitiveType) tru - else addOuterTest( - if (isExpectedReferenceType) mkNullTest - else mkTypeTest - ) - ) - - // true when called to type-test the argument to an extractor - // don't do any fancy equality checking, just test the type - // TODO: verify that we don't need to special-case Array - // I think it's okay: - // - the isInstanceOf test includes a test for the element type - // - Scala's arrays are invariant (so we don't drop type tests unsoundly) - if (extractorArgTypeTest) mkDefault - else expectedTp match { - case ThisType(tref) if tref.symbol.flags is Flags.Module => - and(mkEqualsTest(ref(tref.symbol.companionModule)), mkTypeTest) // must use == to support e.g. List() == Nil - case ConstantType(Constant(null)) if isAnyRef => mkEqTest(expTp(Literal(Constant(null)))) - case ConstantType(const) => mkEqualsTest(expTp(Literal(const))) - case t: SingletonType => mkEqTest(singleton(expectedTp)) // SI-4577, SI-4897 - //case ThisType(sym) => mkEqTest(expTp(This(sym))) - case _ => mkDefault + override def apply(plan: CallPlan): Plan = { + val label = plan.label + def redirect(target: Plan): Plan = { + def forward(tst: TestPlan) = seenTests.get(tst) match { + case Some(true) => redirect(tst.onSuccess) + case Some(false) => redirect(tst.onFailure) + case none => target + } + target match { + case tst: TestPlan => forward(tst) + case UnappTestPlan(tst) => forward(tst) + case _ => target + } + } + redirect(labelled(label)) match { + case target: CallPlan => + apply(target) + case _ => + seenAtLabel(label) = seenAtLabel.get(label) match { + case Some(seenTests1) => intersect(seenTests1, seenTests) + case none => seenTests + } + plan + } } } - - val cond = renderCondition(treeCondStrategy) - val res = codegen._asInstanceOf(testedBinder, nextBinderTp) - - // is this purely a type test, e.g. no outer check, no equality tests (used in switch emission) - //def isPureTypeTest = renderCondition(pureTypeTestChecker) - - def impliesBinderNonNull(binder: Symbol): Boolean = - // @odersky: scalac is able to infer in this method that nonNullImpliedByTestChecker.Result, - // dotty instead infers type projection TreeMakers.this.TypeTestTreeMaker.TypeTestCondStrategy#Result - // which in turn doesn't typecheck in this method. Can you please explain why? - // dotty deviation - renderCondition(nonNullImpliedByTestChecker(binder)).asInstanceOf[Boolean] - - override def toString = "TT" + ((expectedTp, testedBinder.name, nextBinderTp)) - } - - // need to substitute to deal with existential types -- TODO: deal with existentials better, don't substitute (see RichClass during quick.comp) - case class EqualityTestTreeMaker(prevBinder: Symbol, subpatBinder: Symbol, patTree: Tree, override val pos: Position) extends CondTreeMaker { - val nextBinderTp = patTree.tpe & prevBinder.info - val nextBinder = if (prevBinder eq subpatBinder) freshSym(pos, nextBinderTp) else subpatBinder - - // NOTE: generate `patTree == patBinder`, since the extractor must be in control of the equals method (also, patBinder may be null) - // equals need not be well-behaved, so don't intersect with pattern's (stabilized) type (unlike MaybeBoundTyped's accumType, where it's required) - val cond = codegen._equals(patTree, prevBinder) - val res = ref(prevBinder).ensureConforms(nextBinderTp) - override def toString = "ET" + ((prevBinder.name, patTree)) + new ElimRedundant(Map())(plan) } - case class AlternativesTreeMaker(prevBinder: Symbol, var altss: List[List[TreeMaker]], pos: Position) extends TreeMaker with NoNewBinders { - // don't substitute prevBinder to nextBinder, a set of alternatives does not need to introduce a new binder, simply reuse the previous one - - override private[TreeMakers] def incorporateOuterRebinding(outerSubst: Rebindings): Unit = { - super.incorporateOuterRebinding(outerSubst) - altss = altss map (alts => propagateRebindings(alts, rebindings)) - } - - def chainBefore(next: Tree)(codegenAlt: Casegen): Tree = { - /*atPos(pos)*/{ - // one alternative may still generate multiple trees (e.g., an extractor call + equality test) - // (for now,) alternatives may not bind variables (except wildcards), so we don't care about the final substitution built internally by makeTreeMakers - val combinedAlts = altss map (altTreeMakers => - ((casegen: Casegen) => combineExtractors(altTreeMakers :+ TrivialTreeMaker(casegen.one(Literal(Constant(true)))))(casegen)) - ) - - val findAltMatcher = codegenAlt.matcher(EmptyTree, NoSymbol, defn.BooleanType)(combinedAlts, Some((x: Symbol) => Literal(Constant(false)))) - codegenAlt.ifThenElseZero(findAltMatcher, next) + /** Inline labelled blocks that are referenced only once. + * Drop all labels that are not referenced anymore after this. + */ + private def inlineLabelled(plan: Plan) = { + val refCount = labelRefCount(plan) + def toDrop(sym: Symbol) = labelled.contains(sym) && refCount(sym) <= 1 + class Inliner extends PlanTransform { + override def apply(plan: LabelledPlan): Plan = + if (toDrop(plan.sym)) apply(plan.body) else super.apply(plan) + override def apply(plan: CallPlan): Plan = { + if (refCount(plan.label) == 1) apply(labelled(plan.label)) + else plan } } + (new Inliner)(plan) } - case class GuardTreeMaker(guardTree: Tree) extends TreeMaker with NoNewBinders { - val pos = guardTree.pos - - def chainBefore(next: Tree)(casegen: Casegen): Tree = casegen.flatMapGuard(guardTree, next) - override def toString = "G(" + guardTree + ")" - } - - // combineExtractors changes the current substitution's of the tree makers in `treeMakers` - // requires propagateSubstitution(treeMakers) has been called - def combineExtractors(treeMakers: List[TreeMaker])(casegen: Casegen): Tree = { - val (testsMakers, guardAndBodyMakers) = treeMakers.span(t => !(t.isInstanceOf[NoNewBinders])) - val body = guardAndBodyMakers.foldRight(EmptyTree: Tree)((a, b) => a.chainBefore(b)(casegen)) - val rebindings = guardAndBodyMakers.last.rebindings.emitValDefs - testsMakers.foldRight(Block(rebindings, body): Tree)((a, b) => a.chainBefore(b)(casegen)) - } - // a foldLeft to accumulate the localSubstitution left-to-right - // unlike in scalace it does not drop SubstOnly tree makers, - // as there could be types having them as prefix - def propagateRebindings(treeMakers: List[TreeMaker], initial: Rebindings): List[TreeMaker] = { - var accumSubst: Rebindings = initial - treeMakers foreach { maker => - maker incorporateOuterRebinding accumSubst - accumSubst = maker.rebindings + /** Merge variables that have the same right hand side. + * Propagate common variable bindings as parameters into case labels. + */ + private def mergeVars(plan: Plan): Plan = { + class RHS(val tree: Tree) { + override def equals(that: Any) = that match { + case that: RHS => this.tree === that.tree + case _ => false + } + override def hashCode: Int = tree.hash } - treeMakers - } + type SeenVars = Map[RHS, TermSymbol] - // calls propagateSubstitution on the treemakers - def combineCases(scrut: Tree, scrutSym: Symbol, casesRaw: List[List[TreeMaker]], pt: Type, owner: Symbol, matchFailGenOverride: Option[Symbol => Tree]): Tree = { - // unlike in scalac SubstOnlyTreeMakers are maintained. - val casesRebindingPropagated = casesRaw map (propagateRebindings(_, NoRebindings)) + /** The variables known at entry to label */ + val seenAtLabel = mutable.HashMap[Symbol, SeenVars]() - def matchFailGen = matchFailGenOverride orElse Some((arg: Symbol) => Throw(New(defn.MatchErrorType, List(ref(arg))))) - - ctx.debuglog("combining cases: " + (casesRebindingPropagated.map(_.mkString(" >> ")).mkString("{", "\n", "}"))) - - val (suppression, requireSwitch): (Suppression, Boolean) = - /*if (settings.XnoPatmatAnalysis)*/ (Suppression.NoSuppression, false) - /*else scrut match { - case Typed(tree, tpt) => - val suppressExhaustive = tpt.tpe hasAnnotation UncheckedClass - val supressUnreachable = tree match { - case Ident(name) if name startsWith nme.CHECK_IF_REFUTABLE_STRING => true // SI-7183 don't warn for withFilter's that turn out to be irrefutable. - case _ => false + /** Parameters of label; these are passed additional variables + * which are known at all callsites. + */ + val paramsOfLabel = mutable.HashMap[Symbol, SeenVars]() + + class Merge(seenVars: SeenVars) extends PlanTransform { + override val treeMap = new TreeMap { + override def transform(tree: Tree)(implicit ctx: Context) = tree match { + case tree: Ident => + val sym = tree.symbol + initializer.get(sym) match { + case Some(id: Ident @unchecked) + if isPatmatGenerated(sym) && isPatmatGenerated(id.symbol) => + transform(id) + case none => tree } - val suppression = Suppression(suppressExhaustive, supressUnreachable) - // matches with two or fewer cases need not apply for switchiness (if-then-else will do) - val requireSwitch = treeInfo.isSwitchAnnotation(tpt.tpe) && casesNoSubstOnly.lengthCompare(2) > 0 - (suppression, requireSwitch) case _ => - (Suppression.NoSuppression, false) - }*/ - - emitSwitch(scrut, scrutSym, casesRebindingPropagated, pt, matchFailGenOverride, suppression.exhaustive).getOrElse{ - if (requireSwitch) ctx.warning("could not emit switch for @switch annotated match", scrut.pos) - - if (casesRebindingPropagated nonEmpty) { - // before optimizing, check casesNoSubstOnly for presence of a default case, - // since DCE will eliminate trivial cases like `case _ =>`, even if they're the last one - // exhaustivity and reachability must be checked before optimization as well - // TODO: improve notion of trivial/irrefutable -- a trivial type test before the body still makes for a default case - // ("trivial" depends on whether we're emitting a straight match or an exception, or more generally, any supertype of scrutSym.tpe is a no-op) - // irrefutability checking should use the approximation framework also used for CSE, unreachability and exhaustivity checking - val synthCatchAll: Option[Symbol => Tree] = - if (casesRebindingPropagated.nonEmpty && { - val nonTrivLast = casesRebindingPropagated.last - nonTrivLast.nonEmpty && nonTrivLast.head.isInstanceOf[BodyTreeMaker] - }) None - else matchFailGen - - analyzeCases(scrutSym, casesRebindingPropagated, pt, suppression) - - val (cases, toHoist) = optimizeCases(scrutSym, casesRebindingPropagated, pt) - - val matchRes = codegen.matcher(scrut, scrutSym, pt)(cases.map(x => combineExtractors(x) _), synthCatchAll) - - if (toHoist isEmpty) matchRes else Block(toHoist, matchRes) - } else { - codegen.matcher(scrut, scrutSym, pt)(Nil, matchFailGen) + super.transform(tree) } } - } - } - - trait MatchOptimizer extends OptimizedCodegen with TreeMakers - /*with SwitchEmission // todo: toBe ported - with CommonSubconditionElimination*/ { - override def optimizeCases(prevBinder: Symbol, cases: List[List[TreeMaker]], pt: Type): (List[List[TreeMaker]], List[Tree]) = { - // TODO: do CSE on result of doDCE(prevBinder, cases, pt) - val optCases = cases// todo: doCSE(prevBinder, cases, pt) - val toHoist = Nil/*( - for (treeMakers <- optCases) - yield treeMakers.collect{case tm: ReusedCondTreeMaker => tm.treesToHoist} - ).flatten.flatten.toList*/ - (optCases, toHoist) - } - } - - trait MatchTranslator extends TreeMakers with ScalacPatternExpanders { - - def isVarPattern(pat: Tree): Boolean = pat match { - case x: BackquotedIdent => false - case x: Ident => x.name.isVariableName - case _ => false - } - - /** A conservative approximation of which patterns do not discern anything. - * They are discarded during the translation. - */ - object WildcardPattern { - def unapply(pat: Tree): Boolean = pat match { - case Typed(_, arg) if arg.tpe.isRepeatedParam => true - case Bind(nme.WILDCARD, WildcardPattern()) => true // don't skip when binding an interesting symbol! - case t if (tpd.isWildcardArg(t)) => true - case x: Ident => isVarPattern(x) - case Alternative(ps) => ps forall unapply - case EmptyTree => true - case _ => false - } - } - - object PatternBoundToUnderscore { - def unapply(pat: Tree): Boolean = pat match { - case Bind(nme.WILDCARD, _) => true // don't skip when binding an interesting symbol! - case Ident(nme.WILDCARD) => true - case Alternative(ps) => ps forall unapply - case Typed(PatternBoundToUnderscore(), _) => false // true // Dmitry: change in dotty. Type test will be performed and the field must be stored - case _ => false - } - } - - object SymbolBound { - def unapply(tree: Tree): Option[(Symbol, Tree)] = tree match { - case Bind(_, expr) if tree.symbol.exists => Some(tree.symbol -> expr) - case _ => None - } - } - - def newBoundTree(tree: Tree, pt: Type): BoundTree = tree match { - case SymbolBound(sym, Typed(subpat, tpe)) => BoundTree(freshSym(tree.pos, pt, PatMatPiName), tree) - case SymbolBound(sym, expr) => BoundTree(sym, expr) - case _ => BoundTree(freshSym(tree.pos, pt, PatMatPName), tree) - } - - final case class BoundTree(binder: Symbol, tree: Tree) { - private lazy val extractor = ExtractorCall(tree, binder) - - def pos = tree.pos - def tpe = binder.info.widenDealias - def pt = unbound match { - // case Star(tpt) => this glbWith seqType(tpt.tpe) dd todo: - case TypeBound(tpe) => tpe - case tree => tree.tpe - } - - def glbWith(other: Type) = ctx.typeComparer.glb(tpe :: other :: Nil)// .normalize - - object SymbolAndTypeBound { - def unapply(tree: Tree): Option[(Symbol, Type)] = tree match { - case SymbolBound(sym, Typed(_: UnApply, _)) => None // see comment in #189 - case SymbolBound(sym, TypeBound(tpe)) => Some(sym -> tpe) - case TypeBound(tpe) => Some(binder -> tpe) - case _ => None - } - } - - object SymbolAndValueBound { - def unapply(tree: Tree): Option[(Symbol, Tree)] = tree match { - case SymbolBound(sym, ConstantPattern(const)) => Some(sym -> const) - case _ => None - } - } - object TypeBound { - def unapply(tree: Tree): Option[Type] = tree match { - case Typed(_, arg) if !arg.tpe.isRepeatedParam => Some(tree.typeOpt) - case _ => None + override def apply(plan: LetPlan): Plan = { + initializer(plan.sym) = apply(initializer(plan.sym)) + val seenVars1 = + if (isPatmatGenerated(plan.sym)) { + val thisRhs = new RHS(initializer(plan.sym)) + seenVars.get(thisRhs) match { + case Some(seen) => + initializer(plan.sym) = ref(seen) + seenVars + case none => + seenVars.updated(thisRhs, plan.sym) + } + } + else seenVars + plan.body = new Merge(seenVars1)(plan.body) + plan } - } - object ConstantPattern { - def unapply(tree: Tree): Option[Tree] = tree match { - case Literal(Constant(_)) | Ident(_) | Select(_, _) | This(_) => Some(tree) - case _ => None + override def apply(plan: LabelledPlan): Plan = { + seenAtLabel(plan.sym) = seenVars + plan.body = apply(plan.body) + val paramsMap = paramsOfLabel.getOrElse(plan.sym, Map()) + plan.params = paramsMap.values.toList.sortBy(_.name.toString) + val seenVars1 = seenVars ++ paramsMap + labelled(plan.sym) = new Merge(seenVars1)(labelled(plan.sym)) + plan } - } - - private def rebindTo(pattern: Tree) = BoundTree(binder, pattern) - private def step(treeMakers: TreeMaker*)(subpatterns: BoundTree*): TranslationStep = TranslationStep(treeMakers.toList, subpatterns.toList) - - private def bindingStep(sub: Symbol, subpattern: Tree) = step(SubstOnlyTreeMaker(sub, binder))(rebindTo(subpattern)) - private def equalityTestStep(testedSymbol: Symbol, constantSymbol: Symbol, constant: Tree) - = step(EqualityTestTreeMaker(testedSymbol, constantSymbol, constant, pos))() - private def typeTestStep(sub: Symbol, subPt: Type) = step(TypeTestTreeMaker(sub, binder, subPt, sub.termRef)(pos))() - private def alternativesStep(alts: List[Tree]) = step(AlternativesTreeMaker(binder, translatedAlts(alts), alts.head.pos))() - private def translatedAlts(alts: List[Tree]) = alts map (alt => rebindTo(alt).translate()) - private def noStep() = step()() - - private def unsupportedPatternMsg = - i"unsupported pattern: ${tree.show} / $this (this is a scalac bug.)" - - // example check: List[Int] <:< ::[Int] - private def extractorStep(): TranslationStep = { - def paramType = extractor.aligner.wholeType - import extractor.treeMaker - // chain a type-testing extractor before the actual extractor call - // it tests the type, checks the outer pointer and casts to the expected type - // TODO: the outer check is mandated by the spec for case classes, but we do it for user-defined unapplies as well [SPEC] - // (the prefix of the argument passed to the unapply must equal the prefix of the type of the binder) - lazy val typeTest = TypeTestTreeMaker(freshSym(pos, paramType), binder, paramType, paramType)(pos, extractorArgTypeTest = true) - // check whether typetest implies binder is not null, - // even though the eventual null check will be on typeTest.nextBinder - // it'll be equal to binder casted to paramType anyway (and the type test is on binder) - def extraction: TreeMaker = treeMaker(typeTest.nextBinder, typeTest.impliesBinderNonNull(binder), pos, paramType) - - // paramType = the type expected by the unapply - // TODO: paramType may contain unbound type params (run/t2800, run/t3530) - val makers = ( - // Statically conforms to paramType - if (tpe <:< paramType) treeMaker(binder, false, pos, tpe) :: Nil - else typeTest :: extraction :: Nil - ) - step(makers: _*)(extractor.subBoundTrees: _*) - } - - // Summary of translation cases. I moved the excerpts from the specification further below so all - // the logic can be seen at once. - // - // [1] skip wildcard trees -- no point in checking them - // [2] extractor and constructor patterns - // [3] replace subpatBinder by patBinder, as if the Bind was not there. - // It must be patBinder, as subpatBinder has the wrong info: even if the bind assumes a better type, - // this is not guaranteed until we cast - // [4] typed patterns - a typed pattern never has any subtrees - // must treat Typed and Bind together -- we need to know the patBinder of the Bind pattern to get at the actual type - // [5] literal and stable id patterns - // [6] pattern alternatives - // [7] symbol-less bind patterns - this happens in certain ill-formed programs, there'll be an error later - // don't fail here though (or should we?) - def nextStep(): TranslationStep = tree match { - case _: UnApply | _: Apply | Typed(_: UnApply | _: Apply, _) => extractorStep() - case SymbolAndTypeBound(sym, tpe) => typeTestStep(sym, tpe) - case TypeBound(tpe) => typeTestStep(binder, tpe) - case SymbolBound(sym, expr) => bindingStep(sym, expr) - case WildcardPattern() => noStep() - case ConstantPattern(const) => equalityTestStep(binder, binder, const) - case Alternative(alts) => alternativesStep(alts) - case _ => ctx.error(unsupportedPatternMsg, pos) ; noStep() - } - def translate(): List[TreeMaker] = nextStep() merge (_.translate()) - - private def concreteType = tpe.bounds.hi - private def unbound = unbind(tree) - private def tpe_s = if (pt <:< concreteType) "" + pt else s"$pt (binder: $tpe)" - private def at_s = unbound match { - case WildcardPattern() => "" - case pat => s" @ $pat" - } - override def toString = s"${binder.name}: $tpe_s$at_s" - } - - // a list of TreeMakers that encode `patTree`, and a list of arguments for recursive invocations of `translatePattern` to encode its subpatterns - final case class TranslationStep(makers: List[TreeMaker], subpatterns: List[BoundTree]) { - def merge(f: BoundTree => List[TreeMaker]): List[TreeMaker] = makers ::: (subpatterns flatMap f) - override def toString = if (subpatterns.isEmpty) "" else subpatterns.mkString("(", ", ", ")") - } - - def isSyntheticDefaultCase(cdef: CaseDef) = cdef match { - case CaseDef(Bind(nme.DEFAULT_CASE, _), EmptyTree, _) => true - case _ => false - } - - /** Implement a pattern match by turning its cases (including the implicit failure case) - * into the corresponding (monadic) extractors, and combining them with the `orElse` combinator. - * - * For `scrutinee match { case1 ... caseN }`, the resulting tree has the shape - * `runOrElse(scrutinee)(x => translateCase1(x).orElse(translateCase2(x)).....orElse(zero))` - * - * NOTE: the resulting tree is not type checked, nor are nested pattern matches transformed - * thus, you must typecheck the result (and that will in turn translate nested matches) - * this could probably be optimized... (but note that the matchStrategy must be solved for each nested patternmatch) - */ - def translateMatch(match_ : Match): Tree = { - val Match(sel, cases) = match_ - - val selectorTp = sel.tpe.widen.deAnonymize/*withoutAnnotations*/ - - val selectorSym = freshSym(sel.pos, selectorTp, PatMatSelectorName) - - val (nonSyntheticCases, defaultOverride) = cases match { - case init :+ last if isSyntheticDefaultCase(last) => (init, Some(((scrut: Symbol) => last.body))) - case _ => (cases, None) - } - - - // checkMatchVariablePatterns(nonSyntheticCases) // only used for warnings - // we don't transform after uncurry - // (that would require more sophistication when generating trees, - // and the only place that emits Matches after typers is for exception handling anyway) - /*if (phase.id >= currentRun.uncurryPhase.id) - devWarning(s"running translateMatch past uncurry (at $phase) on $selector match $cases")*/ - - ctx.debuglog("translating " + cases.mkString("{", "\n", "}")) - - //val start = if (Statistics.canEnable) Statistics.startTimer(patmatNanos) else null - - // when one of the internal cps-type-state annotations is present, strip all CPS annotations - ///val origPt = removeCPSFromPt(match_.tpe) - // relevant test cases: pos/existentials-harmful.scala, pos/gadt-gilles.scala, pos/t2683.scala, pos/virtpatmat_exist4.scala - // pt is the skolemized version - val pt = match_.tpe.widen //repeatedToSeq(origPt) - - // val packedPt = repeatedToSeq(typer.packedType(match_, context.owner)) - selectorSym.setFlag(Flags.SyntheticCase) - - // pt = Any* occurs when compiling test/files/pos/annotDepMethType.scala with -Xexperimental - val combined = combineCases(sel, selectorSym, nonSyntheticCases map translateCase(selectorSym, pt), pt, ctx.owner, defaultOverride) - - // if (Statistics.canEnable) Statistics.stopTimer(patmatNanos, start) - Block(List(ValDef(selectorSym, sel)), combined) - } - - /** The translation of `pat if guard => body` has two aspects: - * 1) the substitution due to the variables bound by patterns - * 2) the combination of the extractor calls using `flatMap`. - * - * 2) is easy -- it looks like: `translatePattern_1.flatMap(translatePattern_2....flatMap(translatePattern_N.flatMap(translateGuard.flatMap((x_i) => success(Xbody(x_i)))))...)` - * this must be right-leaning tree, as can be seen intuitively by considering the scope of bound variables: - * variables bound by pat_1 must be visible from the function inside the left-most flatMap right up to Xbody all the way on the right - * 1) is tricky because translatePattern_i determines the shape of translatePattern_i + 1: - * zoom in on `translatePattern_1.flatMap(translatePattern_2)` for example -- it actually looks more like: - * `translatePattern_1(x_scrut).flatMap((x_1) => {y_i -> x_1._i}translatePattern_2)` - * - * `x_1` references the result (inside the monad) of the extractor corresponding to `pat_1`, - * this result holds the values for the constructor arguments, which translatePattern_1 has extracted - * from the object pointed to by `x_scrut`. The `y_i` are the symbols bound by `pat_1` (in order) - * in the scope of the remainder of the pattern, and they must thus be replaced by: - * - (for 1-ary unapply) x_1 - * - (for n-ary unapply, n > 1) selection of the i'th tuple component of `x_1` - * - (for unapplySeq) x_1.apply(i) - * - * in the treemakers, - * - * Thus, the result type of `translatePattern_i`'s extractor must conform to `M[(T_1,..., T_n)]`. - * - * Operationally, phase 1) is a foldLeft, since we must consider the depth-first-flattening of - * the transformed patterns from left to right. For every pattern ast node, it produces a transformed ast and - * a function that will take care of binding and substitution of the next ast (to the right). - * - */ - def translateCase(scrutSym: Symbol, pt: Type)(caseDef: CaseDef): List[TreeMaker] = { - val CaseDef(pattern, guard, body) = caseDef - translatePattern(BoundTree(scrutSym, pattern)) ++ translateGuard(guard) :+ translateBody(body, pt) - } - - def translatePattern(bound: BoundTree): List[TreeMaker] = bound.translate() - - def translateGuard(guard: Tree): List[TreeMaker] = - if (guard == EmptyTree) Nil - else List(GuardTreeMaker(guard)) - - // TODO: 1) if we want to support a generalisation of Kotlin's patmat continue, must not hard-wire lifting into the monad (which is now done by codegen.one), - // so that user can generate failure when needed -- use implicit conversion to lift into monad on-demand? - // to enable this, probably need to move away from Option to a monad specific to pattern-match, - // so that we can return Option's from a match without ambiguity whether this indicates failure in the monad, or just some result in the monad - // 2) body.tpe is the type of the body after applying the substitution that represents the solution of GADT type inference - // need the explicit cast in case our substitutions in the body change the type to something that doesn't take GADT typing into account - def translateBody(body: Tree, matchPt: Type): TreeMaker = - BodyTreeMaker(body, matchPt) - - // Some notes from the specification - - /*A constructor pattern is of the form c(p1, ..., pn) where n ≥ 0. - It consists of a stable identifier c, followed by element patterns p1, ..., pn. - The constructor c is a simple or qualified name which denotes a case class (§5.3.2). - - If the case class is monomorphic, then it must conform to the expected type of the pattern, - and the formal parameter types of x’s primary constructor (§5.3) are taken as the expected - types of the element patterns p1, ..., pn. - - If the case class is polymorphic, then its type parameters are instantiated so that the - instantiation of c conforms to the expected type of the pattern. - The instantiated formal parameter types of c’s primary constructor are then taken as the - expected types of the component patterns p1, ..., pn. - - The pattern matches all objects created from constructor invocations c(v1, ..., vn) - where each element pattern pi matches the corresponding value vi . - A special case arises when c’s formal parameter types end in a repeated parameter. - This is further discussed in (§8.1.9). - **/ - - /* A typed pattern x : T consists of a pattern variable x and a type pattern T. - The type of x is the type pattern T, where each type variable and wildcard is replaced by a fresh, unknown type. - This pattern matches any value matched by the type pattern T (§8.2); it binds the variable name to that value. - */ - - /* A pattern binder x@p consists of a pattern variable x and a pattern p. - The type of the variable x is the static type T of the pattern p. - This pattern matches any value v matched by the pattern p, - provided the run-time type of v is also an instance of T, <-- TODO! https://issues.scala-lang.org/browse/SI-1503 - and it binds the variable name to that value. - */ - - /* 8.1.4 Literal Patterns - A literal pattern L matches any value that is equal (in terms of ==) to the literal L. - The type of L must conform to the expected type of the pattern. - - 8.1.5 Stable Identifier Patterns (a stable identifier r (see §3.1)) - The pattern matches any value v such that r == v (§12.1). - The type of r must conform to the expected type of the pattern. - */ - - - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // helper methods: they analyze types and trees in isolation, but they are not (directly) concerned with the structure of the overall translation - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - object ExtractorCall { - // TODO: check unargs == args - def apply(tree: Tree, binder: Symbol): ExtractorCall = { - tree match { - case Typed(unapply, _) => apply(unapply, binder) - case UnApply(unfun, implicits, args) => - val mt @ MethodType(_) = unfun.tpe.widen - val castedBinder = ref(binder).ensureConforms(mt.paramInfos.head) - var synth = unfun.appliedTo(castedBinder) - if (implicits.nonEmpty) synth = synth.appliedToArgs(implicits) - new ExtractorCallRegular(alignPatterns(tree, synth.tpe), synth, args, synth.tpe) + override def apply(plan: CallPlan): Plan = { + paramsOfLabel(plan.label) = paramsOfLabel.get(plan.label) match { + case Some(params) => + params.filter { case (rhs, _) => seenVars.contains(rhs) } + case none => + for ((rhs, _) <- seenVars if !seenAtLabel(plan.label).contains(rhs)) + yield (rhs, newVar(rhs.tree, Param)) + } + plan.args = + for { + (rhs, actual) <- seenVars.toList + formal <- paramsOfLabel(plan.label).get(rhs) + } + yield (formal -> actual) + plan } } + (new Merge(Map()))(plan) } - abstract class ExtractorCall(val aligner: PatternAligned) { - - import aligner._ - - def args: List[Tree] - - // don't go looking for selectors if we only expect one pattern - def rawSubPatTypes = aligner.extractedTypes - - def typeArgOfBaseTypeOr(tp: Type, baseClass: Symbol)(or: => Type): Type = (tp.baseTypeWithArgs(baseClass)).argInfos match { - case x :: Nil => x - case _ => or - } - - def resultInMonad = - if (aligner.isBool) defn.UnitType - else if (isProductMatch(resultType, aligner.prodArity)) resultType - else if (isGetMatch(resultType)) extractorMemberType(resultType, nme.get) - else resultType - - def resultType: Type - - /** Create the TreeMaker that embodies this extractor call - * - * `binder` has been casted to `paramType` if necessary - * `binderKnownNonNull` indicates whether the cast implies `binder` cannot be null - * when `binderKnownNonNull` is `true`, `ProductExtractorTreeMaker` does not do a (redundant) null check on binder - */ - def treeMaker(binder: Symbol, binderKnownNonNull: Boolean, pos: Position, binderTypeTested: Type): TreeMaker - - // `subPatBinders` are the variables bound by this pattern in the following patterns - // subPatBinders are replaced by references to the relevant part of the extractor's result (tuple component, seq element, the result as-is) - // must set infos to `subPatTypes`, which are provided by extractor's result, - // as b.info may be based on a Typed type ascription, which has not been taken into account yet by the translation - // (it will later result in a type test when `tp` is not a subtype of `b.info`) - // TODO: can we simplify this, together with the Bound case? - def subPatBinders = subBoundTrees map (_.binder) - lazy val subBoundTrees = (args, subPatTypes).zipped map newBoundTree - - // never store these in local variables (for PreserveSubPatBinders) - lazy val ignoredSubPatBinders: Set[Symbol] = subPatBinders zip args collect { case (b, PatternBoundToUnderscore()) => b } toSet - - // do repeated-parameter expansion to match up with the expected number of arguments (in casu, subpatterns) - private def nonStarSubPatTypes = aligner.typedNonStarPatterns map (_.tpe) - - def subPatTypes: List[Type] = typedPatterns map (_.tpe) - - // there are `prodArity` non-seq elements in the tuple. - protected def firstIndexingBinder = prodArity - protected def expectedLength = elementArity - protected def lastIndexingBinder = totalArity - starArity - 1 - - private def productElemsToN(binder: Symbol, n: Int): List[Tree] = 1 to n map tupleSel(binder) toList - private def genTake(binder: Symbol, n: Int): List[Tree] = (0 until n).toList map (codegen index seqTree(binder)) - private def genDrop(binder: Symbol, n: Int): List[Tree] = codegen.drop(seqTree(binder))(expectedLength) :: Nil - - // codegen.drop(seqTree(binder))(nbIndexingIndices)))).toList - protected def seqTree(binder: Symbol) = tupleSel(binder)(firstIndexingBinder + 1) - protected def tupleSel(binder: Symbol)(i: Int): Tree = { - val accessors = - if (defn.isProductSubType(binder.info)) - productSelectors(binder.info) - else binder.caseAccessors - val res = - if (accessors.isDefinedAt(i - 1)) ref(binder).select(accessors(i - 1).name) - else codegen.tupleSel(binder)(i) // this won't type check for case classes, as they do not inherit ProductN - val rsym = res.symbol // just for debugging - res - } - - // the trees that select the subpatterns on the extractor's result, - // referenced by `binder` - protected def subPatRefsSeq(binder: Symbol): List[Tree] = { - def lastTrees: List[Tree] = ( - if (!aligner.isStar) Nil - else if (expectedLength == 0) seqTree(binder) :: Nil - else genDrop(binder, expectedLength) - ) - // this error-condition has already been checked by checkStarPatOK: - // if (isSeq) assert(firstIndexingBinder + nbIndexingIndices + (if (lastIsStar) 1 else 0) == totalArity, "(resultInMonad, ts, subPatTypes, subPats)= " +(resultInMonad, ts, subPatTypes, subPats)) - - // [1] there are `firstIndexingBinder` non-seq tuple elements preceding the Seq - // [2] then we have to index the binder that represents the sequence for the remaining subpatterns, except for... - // [3] the last one -- if the last subpattern is a sequence wildcard: - // drop the prefix (indexed by the refs on the preceding line), return the remainder - ( productElemsToN(binder, firstIndexingBinder) - ++ genTake(binder, expectedLength) - ++ lastTrees - ).toList - } - - // the trees that select the subpatterns on the extractor's result, referenced by `binder` - // require (nbSubPats > 0 && (!lastIsStar || isSeq)) - protected def subPatRefs(binder: Symbol): List[Tree] = { - val refs = if (totalArity > 0 && isSeq) subPatRefsSeq(binder) - else if (binder.info.member(nme._1).exists && !isSeq) productElemsToN(binder, totalArity) - else ref(binder) :: Nil - refs - } - - val mathSignymSymbol = defn.ScalaMathPackageVal.requiredMethod("signum".toTermName, List(defn.IntType)) - val mathSignum = ref(defn.ScalaMathPackageVal).select(mathSignymSymbol) - - - private def compareInts(t1: Tree, t2: Tree) = - mathSignum.appliedTo(t1.select(defn.Int_-).appliedTo(t2)) - //gen.mkMethodCall(termMember(ScalaPackage, "math"), TermName("signum"), Nil, (t1 INT_- t2) :: Nil) - - protected def lengthGuard(binder: Symbol): Option[Tree] = - // no need to check unless it's an unapplySeq and the minimal length is non-trivially satisfied - checkedLength map { expectedLength => - // `binder.lengthCompare(expectedLength)` - // ...if binder has a lengthCompare method, otherwise - // `scala.math.signum(binder.length - expectedLength)` - def checkExpectedLength: Tree = sequenceType.member(nme.lengthCompare) match { - case NoDenotation => compareInts(Select(seqTree(binder), nme.length), Literal(Constant(expectedLength))) - case x:SingleDenotation => (seqTree(binder).select(x.symbol)).appliedTo(Literal(Constant(expectedLength))) + /** Inline let-bound trees that are referenced only once. + * Drop all variables that are not referenced anymore after this. + */ + private def inlineVars(plan: Plan): Plan = { + val refCount = varRefCount(plan) + val LetPlan(topSym, _) = plan + + def toDrop(sym: Symbol) = + initializer.contains(sym) && + isPatmatGenerated(sym) && + refCount(sym) <= 1 && + sym != topSym && + isPureExpr(initializer(sym)) + + object Inliner extends PlanTransform { + override val treeMap = new TreeMap { + override def transform(tree: Tree)(implicit ctx: Context) = tree match { + case tree: Ident => + val sym = tree.symbol + if (toDrop(sym)) transform(initializer(sym)) + else tree case _ => - ctx.error("TODO: multiple lengthCompare") - EmptyTree + super.transform(tree) } - - // the comparison to perform - // when the last subpattern is a wildcard-star the expectedLength is but a lower bound - // (otherwise equality is required) - def compareOp: (Tree, Tree) => Tree = - if (aligner.isStar) _.select(defn.Int_>=).appliedTo(_) - else _.select(defn.Int_==).appliedTo(_) - - // `if (binder != null && $checkExpectedLength [== | >=] 0) then else zero` - (seqTree(binder).select(defn.Any_!=).appliedTo(Literal(Constant(null)))).select(defn.Boolean_&&).appliedTo(compareOp(checkExpectedLength, Literal(Constant(0)))) - } - - def checkedLength: Option[Int] = - // no need to check unless it's an unapplySeq and the minimal length is non-trivially satisfied - if (!isSeq || expectedLength < starArity) None - else Some(expectedLength) - } - - class ExtractorCallRegular(aligner: PatternAligned, extractorCallIncludingDummy: Tree, val args: List[Tree], val resultType: Type) extends ExtractorCall(aligner) { - - /** Create the TreeMaker that embodies this extractor call - * - * `binder` has been casted to `paramType` if necessary - * `binderKnownNonNull` is not used in this subclass - * - * TODO: implement review feedback by @retronym: - * Passing the pair of values around suggests: - * case class Binder(sym: Symbol, knownNotNull: Boolean). - * Perhaps it hasn't reached critical mass, but it would already clean things up a touch. - */ - def treeMaker(patBinderOrCasted: Symbol, binderKnownNonNull: Boolean, pos: Position, binderTypeTested: Type): TreeMaker = { - // the extractor call (applied to the binder bound by the flatMap corresponding - // to the previous (i.e., enclosing/outer) pattern) - val extractorApply = extractorCallIncludingDummy// spliceApply(patBinderOrCasted) - // can't simplify this when subPatBinders.isEmpty, since UnitTpe is definitely - // wrong when isSeq, and resultInMonad should always be correct since it comes - // directly from the extractor's result type - val binder = freshSym(pos, resultInMonad) - val spb = subPatBinders - ExtractorTreeMaker(extractorApply, lengthGuard(binder), binder)( - spb, - subPatRefs(binder, spb, resultType), - aligner.isBool, - checkedLength, - patBinderOrCasted, - ignoredSubPatBinders - ) - } - - override protected def seqTree(binder: Symbol): Tree = - if (firstIndexingBinder == 0) ref(binder) - else super.seqTree(binder) - - // the trees that select the subpatterns on the extractor's result, referenced by `binder` - // require (totalArity > 0 && (!lastIsStar || isSeq)) - protected def subPatRefs(binder: Symbol, subpatBinders: List[Symbol], binderTypeTested: Type): List[Tree] = { - if (aligner.isSingle && aligner.extractor.prodArity == 1 && defn.isTupleType(binder.info)) { - // special case for extractor - // comparing with scalac additional assertions added - val subpw = subpatBinders.head.info.widen - val binderw = binder.info.widen - val go = subpatBinders.head.info <:< binder.info - val go1 = binder.info <:< subpatBinders.head.info - //val spr = subPatRefs(binder) - assert(go && go1) - ref(binder) :: Nil } - else if ((aligner.isSingle && aligner.extractor.prodArity == 1) && - !isProductMatch(binderTypeTested, aligner.prodArity) && isGetMatch(binderTypeTested)) - List(ref(binder)) - else - subPatRefs(binder) - } - - /*protected def spliceApply(binder: Symbol): Tree = { - object splice extends TreeMap { - def binderRef(pos: Position): Tree = - ref(binder) //setPos pos - - override def transform(t: tpd.Tree)(implicit ctx: Context): tpd.Tree = t match { - // duplicated with the extractor Unapplied - case Apply(x, List(i @ Ident(nme.SELECTOR_DUMMY))) => - cpy.Apply(t, x, binderRef(i.pos) :: Nil) - // SI-7868 Account for numeric widening, e.g. .toInt - case Apply(x, List(i @ (sel @ Select(Ident(nme.SELECTOR_DUMMY), name)))) => - cpy.Apply(t, x, cpy.Select(sel, binderRef(i.pos), name) :: Nil) - case _ => - super.transform(t) + override def apply(plan: LetPlan): Plan = { + if (toDrop(plan.sym)) apply(plan.body) + else { + initializer(plan.sym) = apply(initializer(plan.sym)) + plan.body = apply(plan.body) + plan } } - splice transform extractorCallIncludingDummy - }*/ - - override def rawSubPatTypes = aligner.extractor.varargsTypes - } - } - - /** An extractor returns: F1, F2, ..., Fi, opt[Seq[E] or E*] - * A case matches: P1, P2, ..., Pj, opt[Seq[E]] - * Put together: P1/F1, P2/F2, ... Pi/Fi, Pi+1/E, Pi+2/E, ... Pj/E, opt[Seq[E]] - * - * Here Pm/Fi is the last pattern to match the fixed arity section. - * - * prodArity: the value of i, i.e. the number of non-sequence types in the extractor - * nonStarArity: the value of j, i.e. the number of non-star patterns in the case definition - * elementArity: j - i, i.e. the number of non-star patterns which must match sequence elements - * starArity: 1 or 0 based on whether there is a star (sequence-absorbing) pattern - * totalArity: nonStarArity + starArity, i.e. the number of patterns in the case definition - * - * Note that prodArity is a function only of the extractor, and - * nonStar/star/totalArity are all functions of the patterns. The key - * value for aligning and typing the patterns is elementArity, as it - * is derived from both sets of information. - */ - trait PatternExpander[Pattern, Type] { - /** You'll note we're not inside the cake. "Pattern" and "Type" are - * arbitrary types here, and NoPattern and NoType arbitrary values. - */ - def NoPattern: Pattern - def NoType: Type - - /** It's not optimal that we're carrying both sequence and repeated - * type here, but the implementation requires more unraveling before - * it can be avoided. - * - * sequenceType is Seq[T], elementType is T, repeatedType is T*. - */ - sealed case class Repeated(sequenceType: Type, elementType: Type, repeatedType: Type) { - def exists = elementType != NoType - - def elementList = if (exists) elementType :: Nil else Nil - def sequenceList = if (exists) sequenceType :: Nil else Nil - def repeatedList = if (exists) repeatedType :: Nil else Nil - - override def toString = s"${elementType}*" - } - object NoRepeated extends Repeated(NoType, NoType, NoType) { - override def toString = "" - } - - final case class Patterns(fixed: List[Pattern], star: Pattern) { - def hasStar = star != NoPattern - def starArity = if (hasStar) 1 else 0 - def nonStarArity = fixed.length - def totalArity = nonStarArity + starArity - def starPatterns = if (hasStar) star :: Nil else Nil - def all = fixed ::: starPatterns - - override def toString = all mkString ", " - } - - /** An 'extractor' can be a case class or an unapply or unapplySeq method. - * Decoding what it is that they extract takes place before we arrive here, - * so that this class can concentrate only on the relationship between - * patterns and types. - * - * In a case class, the class is the unextracted type and the fixed and - * repeated types are derived from its constructor parameters. - * - * In an unapply, this is reversed: the parameter to the unapply is the - * unextracted type, and the other types are derived based on the return - * type of the unapply method. - * - * In other words, this case class and unapply are encoded the same: - * - * case class Foo(x: Int, y: Int, zs: Char*) - * def unapplySeq(x: Foo): Option[(Int, Int, Seq[Char])] - * - * Both are Extractor(Foo, Int :: Int :: Nil, Repeated(Seq[Char], Char, Char*)) - * - * @param whole The type in its unextracted form - * @param fixed The non-sequence types which are extracted - * @param repeated The sequence type which is extracted - */ - final case class Extractor(whole: Type, fixed: List[Type], repeated: Repeated) { - require(whole != NoType, s"expandTypes($whole, $fixed, $repeated)") - - def prodArity = fixed.length - def hasSeq = repeated.exists - def elementType = repeated.elementType - def sequenceType = repeated.sequenceType - def allTypes = fixed ::: repeated.sequenceList - def varargsTypes = fixed ::: repeated.repeatedList - def isErroneous = allTypes contains NoType - - private def typeStrings = fixed.map("" + _) ::: ( if (hasSeq) List("" + repeated) else Nil ) - - def offeringString = if (isErroneous) "" else typeStrings match { - case Nil => "Boolean" - case tp :: Nil => tp - case tps => tps.mkString("(", ", ", ")") - } - override def toString = "%s => %s".format(whole, offeringString) - } - - final case class TypedPat(pat: Pattern, tpe: Type) { - override def toString = s"$pat: $tpe" - } - - /** If elementArity is... - * 0: A perfect match between extractor and the fixed patterns. - * If there is a star pattern it will match any sequence. - * > 0: There are more patterns than products. There will have to be a - * sequence which can populate at least patterns. - * < 0: There are more products than patterns: compile time error. - */ - final case class Aligned(patterns: Patterns, extractor: Extractor) { - def elementArity = patterns.nonStarArity - prodArity - def prodArity = extractor.prodArity - def starArity = patterns.starArity - def totalArity = patterns.totalArity - - def wholeType = extractor.whole - def sequenceType = extractor.sequenceType - def productTypes = extractor.fixed - def extractedTypes = extractor.allTypes - def typedNonStarPatterns = products ::: elements - def typedPatterns = typedNonStarPatterns ::: stars - - def isBool = !isSeq && prodArity == 0 - def isSingle = !isSeq && totalArity == 1 - def isStar = patterns.hasStar - def isSeq = extractor.hasSeq - - private def typedAsElement(pat: Pattern) = TypedPat(pat, extractor.elementType) - private def typedAsSequence(pat: Pattern) = TypedPat(pat, extractor.sequenceType) - private def productPats = patterns.fixed take prodArity - private def elementPats = patterns.fixed drop prodArity - private def products = (productPats, productTypes).zipped map TypedPat - private def elements = elementPats map typedAsElement - private def stars = patterns.starPatterns map typedAsSequence - - override def toString = s""" - |Aligned { - | patterns $patterns - | extractor $extractor - | arities $prodArity/$elementArity/$starArity // product/element/star - | typed ${typedPatterns mkString ", "} - |}""".stripMargin.trim - } - } - - /** This is scalac-specific logic layered on top of the scalac-agnostic - * "matching products to patterns" logic defined in PatternExpander. - */ - trait ScalacPatternExpanders { - - type PatternAligned = ScalacPatternExpander#Aligned - - implicit class AlignedOps(val aligned: PatternAligned) { - import aligned._ - def expectedTypes = typedPatterns map (_.tpe) - def unexpandedFormals = extractor.varargsTypes - } - - trait ScalacPatternExpander extends PatternExpander[Tree, Type] { - def NoPattern = EmptyTree - def NoType = core.Types.NoType - - def newPatterns(patterns: List[Tree]): Patterns = patterns match { - case init :+ last if tpd.isWildcardStarArg(last) => Patterns(init, last) - case _ => Patterns(patterns, NoPattern) - } - def typeOfMemberNamedHead(tpe: Type): Type = tpe.select(nme.head) - def typeOfMemberNamedApply(tpe: Type): Type = tpe.select(nme.apply) - - def elementTypeOf(tpe: Type) = { - val seq = tpe //repeatedToSeq(tpe) - - ( typeOfMemberNamedHead(seq) - orElse typeOfMemberNamedApply(seq) - orElse seq.elemType - ) - } - def newExtractor(whole: Type, fixed: List[Type], repeated: Repeated): Extractor = { - ctx.log(s"newExtractor($whole, $fixed, $repeated") - Extractor(whole, fixed, repeated) - } - - // Turn Seq[A] into Repeated(Seq[A], A, A*) - def repeatedFromSeq(seqType: Type): Repeated = { - val elem = elementTypeOf(seqType) - val repeated = /*scalaRepeatedType(*/elem//) - - Repeated(seqType, elem, repeated) - } - // Turn A* into Repeated(Seq[A], A, A*) - def repeatedFromVarargs(repeated: Type): Repeated = - //Repeated(repeatedToSeq(repeated), repeatedToSingle(repeated), repeated) - Repeated(repeated, repeated.elemType, repeated) - - /** In this case we are basing the pattern expansion on a case class constructor. - * The argument is the MethodType carried by the primary constructor. - */ - def applyMethodTypes(method: Type): Extractor = { - val whole = method.finalResultType - - method.paramInfoss.head match { - case init :+ last if last.isRepeatedParam => newExtractor(whole, init, repeatedFromVarargs(last)) - case tps => newExtractor(whole, tps, NoRepeated) + override def apply(plan: LabelledPlan): Plan = { + plan.params = plan.params.filter(refCount(_) != 0) + super.apply(plan) + } + override def apply(plan: CallPlan): Plan = { + plan.args = plan.args + .filter(formalActual => refCount(formalActual._1) != 0) + .sortBy(_._1.name.toString) + plan } } + Inliner(plan) + } + + // ----- Generating trees from plans --------------- + + /** The condition a test plan rewrites to */ + private def emitCondition(plan: TestPlan): Tree = { + val scrutinee = plan.scrutinee + (plan.test: @unchecked) match { + case NonEmptyTest => + scrutinee + .select(nme.isEmpty, _.info.isParameterless) + .select(nme.UNARY_!, _.info.isParameterless) + case NonNullTest => + scrutinee.testNotNull + case GuardTest => + scrutinee + case EqualTest(tree) => + tree.equal(scrutinee) + case LengthTest(len, exact) => + scrutinee + .select(defn.Seq_lengthCompare.matchingMember(scrutinee.tpe)) + .appliedTo(Literal(Constant(len))) + .select(if (exact) defn.Int_== else defn.Int_>=) + .appliedTo(Literal(Constant(0))) + case TypeTest(tpt) => + val expectedTp = tpt.tpe + + // An outer test is needed in a situation like `case x: y.Inner => ...` + def outerTestNeeded: Boolean = { + // See the test for SI-7214 for motivation for dealias. Later `treeCondStrategy#outerTest` + // generates an outer test based on `patType.prefix` with automatically dealises. + expectedTp.dealias match { + case tref @ TypeRef(pre: SingletonType, name) => + tref.symbol.isClass && + ExplicitOuter.needsOuterIfReferenced(tref.symbol.asClass) + case _ => + false + } + } - def hasSelectors(tpe: Type) = tpe.member(nme._1).exists && tpe.member(nme._2).exists // dd todo: ??? - - - /** In this case, expansion is based on an unapply or unapplySeq method. - * Unfortunately the MethodType does not carry the information of whether - * it was unapplySeq, so we have to funnel that information in separately. - */ - def unapplyMethodTypes(tree: Tree, fun: Tree, args: List[Tree], resultType: Type, isSeq: Boolean): Extractor = { - _id = _id + 1 - - val whole = tree.tpe // see scaladoc for Trees.Unapply - // fun.tpe.widen.paramTypess.headOption.flatMap(_.headOption).getOrElse(NoType)//firstParamType(method) - val resultOfGet = extractorMemberType(resultType, nme.get) - - val expanded: List[Type] = /*( - if (result =:= defn.BooleanType) Nil - else if (defn.isProductSubType(result)) productSelectorTypes(result) - else if (result.classSymbol is Flags.CaseClass) result.decls.filter(x => x.is(Flags.CaseAccessor) && x.is(Flags.Method)).map(_.info).toList - else result.select(nme.get) :: Nil - )*/ - if (isProductMatch(resultType, args.length)) productSelectorTypes(resultType) - else if (isGetMatch(resultType)) getUnapplySelectors(resultOfGet, args) - else if (resultType isRef defn.BooleanClass) Nil - else { - ctx.error(i"invalid return type in Unapply node: $resultType") - Nil + def outerTest: Tree = trans.transformFollowingDeep { + val expectedOuter = singleton(expectedTp.normalizedPrefix) + val expectedClass = expectedTp.dealias.classSymbol.asClass + ExplicitOuter.ensureOuterAccessors(expectedClass)(ctx.withPhase(ctx.explicitOuterPhase.next)) + scrutinee.ensureConforms(expectedTp) + .outerSelect(1, expectedOuter.tpe.widen) + .select(defn.Object_eq) + .appliedTo(expectedOuter) } - expanded match { - case init :+ last if isSeq => newExtractor(whole, init, repeatedFromSeq(last)) - case tps => newExtractor(whole, tps, NoRepeated) - } + expectedTp.dealias match { + case expectedTp: SingletonType => + scrutinee.isInstance(expectedTp) // will be translated to an equality test + case _ => + val typeTest = scrutinee.select(defn.Any_typeTest).appliedToType(expectedTp) + if (outerTestNeeded) typeTest.and(outerTest) else typeTest + } } } - object alignPatterns extends ScalacPatternExpander { - private def validateAligned(tree: Tree, aligned: Aligned): Aligned = { - import aligned._ - - def owner = tree.symbol.owner - def offering = extractor.offeringString - def symString = tree.symbol.showLocated - def offerString = if (extractor.isErroneous) "" else s" offering $offering" - def arityExpected = (if (extractor.hasSeq) "at least " else "") + prodArity - - def err(msg: String) = ctx.error(msg, tree.pos) - def warn(msg: String) = ctx.warning(msg, tree.pos) - def arityError(what: String) = err(s"${_id} $what patterns for $owner$offerString: expected $arityExpected, found $totalArity") + /** Collect longest list of plans that represent possible cases of + * a switch, including a last default case, by starting with this + * plan and following onSuccess plans. + */ + private def collectSwitchCases(plan: TestPlan): List[Plan] = { + def isSwitchableType(tpe: Type): Boolean = + (tpe isRef defn.IntClass) || + (tpe isRef defn.ByteClass) || + (tpe isRef defn.ShortClass) || + (tpe isRef defn.CharClass) - if (isStar && !isSeq) - err("Star pattern must correspond with varargs or unapplySeq") - else if (elementArity < 0) - arityError("not enough") - else if (elementArity > 0 && !extractor.hasSeq) - arityError("too many") + val scrutinee = plan.scrutinee - aligned + def isIntConst(tree: Tree) = tree match { + case Literal(const) => const.isIntRange + case _ => false } - object Applied { - // Duplicated with `spliceApply` - def unapply(tree: Tree): Option[Tree] = tree match { - // SI-7868 Admit Select() to account for numeric widening, e.g. .toInt - /*case Apply(fun, (Ident(nme.SELECTOR_DUMMY)| Select(Ident(nme.SELECTOR_DUMMY), _)) :: Nil) - => Some(fun)*/ - case Apply(fun, _) => unapply(fun) - case _ => None + def recur(plan: Plan): List[Plan] = plan match { + case TestPlan(EqualTest(tree), scrut, _, _, onf) + if scrut === scrutinee && isIntConst(tree) => + plan :: recur(onf) + case _ => + plan :: Nil + } + + recur(plan) + } + + /** Emit cases of a switch */ + private def emitSwitchCases(cases: List[Plan]): List[CaseDef] = (cases: @unchecked) match { + case TestPlan(EqualTest(tree), _, _, ons, _) :: cases1 => + CaseDef(tree, EmptyTree, emit(ons)) :: emitSwitchCases(cases1) + case (default: Plan) :: Nil => + CaseDef(Underscore(defn.IntType), EmptyTree, emit(default)) :: Nil + } + + /** If selfCheck is `true`, used to check whether a tree gets generated twice */ + private val emitted = mutable.Set[Int]() + + /** Translate plan to tree */ + private def emit(plan: Plan): Tree = { + if (selfCheck) { + assert(plan.isInstanceOf[CallPlan] || !emitted.contains(plan.id), plan.id) + emitted += plan.id + } + plan match { + case plan: TestPlan => + val switchCases = collectSwitchCases(plan) + if (switchCases.lengthCompare(4) >= 0) // at least 3 cases + default + Match(plan.scrutinee, emitSwitchCases(switchCases)) + else + If(emitCondition(plan).withPos(plan.pos), emit(plan.onSuccess), emit(plan.onFailure)) + case LetPlan(sym, body) => + seq(ValDef(sym, initializer(sym).ensureConforms(sym.info)) :: Nil, emit(body)) + case LabelledPlan(label, body, params) => + label.info = MethodType.fromSymbols(params, resultType) + val labelDef = DefDef(label, Nil, params :: Nil, resultType, emit(labelled(label))) + seq(labelDef :: Nil, emit(body)) + case CodePlan(tree) => + tree + case CallPlan(label, args) => + ref(label).appliedToArgs(args.map { case (_, actual) => ref(actual) }) + } + } + + /** Pretty-print plan; used for debugging */ + def show(plan: Plan): String = { + val lrefCount = labelRefCount(plan) + val vrefCount = varRefCount(plan) + val sb = new StringBuilder + val seen = mutable.Set[Int]() + def showTest(test: Test) = test match { + case EqualTest(tree) => i"EqualTest($tree)" + case TypeTest(tpt) => i"TypeTest($tpt)" + case _ => test.toString + } + def showPlan(plan: Plan): Unit = + if (!seen.contains(plan.id)) { + seen += plan.id + sb append s"\n${plan.id}: " + plan match { + case TestPlan(test, scrutinee, _, ons, onf) => + sb.append(i"$scrutinee ? ${showTest(test)}(${ons.id}, ${onf.id})") + showPlan(ons) + showPlan(onf) + case LetPlan(sym, body) => + sb.append(i"Let($sym = ${initializer(sym)}}, ${body.id})") + sb.append(s", refcount = ${vrefCount(sym)}") + showPlan(body) + case LabelledPlan(label, body, params) => + val labeld = labelled(label) + def showParam(param: Symbol) = + i"$param: ${param.info}, refCount = ${vrefCount(param)}" + sb.append(i"Labelled($label(${params.map(showParam)}%, %) = ${labeld.id}, ${body.id})") + sb.append(s", refcount = ${lrefCount(label)}") + showPlan(body) + showPlan(labeld) + case CodePlan(tree) => + sb.append(tree.show) + case CallPlan(label, params) => + sb.append(s"Call($label(${params.map(_._2)}%, %)") + } } - } + showPlan(plan) + sb.toString + } - def apply(tree: Tree, sel: Tree, args: List[Tree], resultType: Type): Aligned = { - val fn = sel match { - case Applied(fn) => fn - case _ => sel + /** If match is switch annotated, check that it translates to a switch + * with at least as many cases as the original match. + */ + private def checkSwitch(original: Match, result: Tree) = original.selector match { + case Typed(_, tpt) if tpt.tpe.hasAnnotation(defn.SwitchAnnot) => + val resultCases = result match { + case Match(_, cases) => cases + case Block(_, Match(_, cases)) => cases + case _ => Nil } - val patterns = newPatterns(args) - val isSeq = sel.symbol.name == nme.unapplySeq - val extractor = sel.symbol.name match { - case nme.unapply => unapplyMethodTypes(tree, /*fn*/sel, args, resultType, isSeq = false) - case nme.unapplySeq => unapplyMethodTypes(tree, /*fn*/sel, args, resultType, isSeq = true) - case _ => applyMethodTypes(/*fn*/sel.tpe) + def numConsts(cdefs: List[CaseDef]): Int = { + val tpes = cdefs.map(_.pat.tpe) + tpes.toSet.size: Int // without the type ascription, testPickling fails because of #2840. } - - validateAligned(fn, Aligned(patterns, extractor)) - } - - def apply(tree: Tree, resultType: Type): Aligned = tree match { - case Typed(tree, _) => apply(tree, resultType) - case Apply(fn, args) => apply(tree, fn, args, resultType) - case UnApply(fn, implicits, args) => apply(tree, fn, args, resultType) - } + if (numConsts(resultCases) < numConsts(original.cases)) + ctx.warning(i"could not emit switch for @switch annotated match", original.pos) + case _ => + } + + val optimizations: List[(String, Plan => Plan)] = List( + "hoistLabels" -> hoistLabels, + "elimRedundantTests" -> elimRedundantTests, + "inlineLabelled" -> inlineLabelled, + "mergeVars" -> mergeVars, + "inlineVars" -> inlineVars + ) + + /** Translate pattern match to sequence of tests. */ + def translateMatch(tree: Match): Tree = { + var plan = matchPlan(tree) + patmatch.println(i"Plan for $tree: ${show(plan)}") + for ((title, optimization) <- optimizations) { + plan = optimization(plan) + patmatch.println(s"After $title: ${show(plan)}") + } + val result = emit(plan) + checkSwitch(tree, result) + result } } - } } diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcherOld.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcherOld.scala new file mode 100644 index 000000000000..d2059a1f3342 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcherOld.scala @@ -0,0 +1,1871 @@ +package dotty.tools.dotc +package transform + +import scala.language.postfixOps + +import TreeTransforms._ +import core.Denotations._ +import core.SymDenotations._ +import core.Contexts._ +import core.Symbols._ +import core.Types._ +import core.Constants._ +import core.StdNames._ +import core.NameKinds._ +import dotty.tools.dotc.ast.{untpd, TreeTypeMap, tpd} +import dotty.tools.dotc.core +import dotty.tools.dotc.core.DenotTransformers.DenotTransformer +import dotty.tools.dotc.core.Phases.Phase +import dotty.tools.dotc.core.{TypeApplications, Flags} +import dotty.tools.dotc.typer.Applications +import dotty.tools.dotc.util.Positions +import typer.ErrorReporting._ +import ast.Trees._ +import Applications._ +import TypeApplications._ +import SymUtils._, core.NameOps._ +import core.Mode +import patmat._ + +import dotty.tools.dotc.util.Positions.Position +import dotty.tools.dotc.core.Decorators._ +import dotty.tools.dotc.core.Flags + +/** This phase rewrites pattern matches. + * FIXME: A more detailed explanation would be good. + */ +class PatternMatcherOld extends MiniPhaseTransform with DenotTransformer { + import dotty.tools.dotc.ast.tpd._ + + override def transform(ref: SingleDenotation)(implicit ctx: Context): SingleDenotation = ref + + override def runsAfter = Set(classOf[ElimRepeated]) + + override def runsAfterGroupsOf = Set(classOf[TailRec]) // tailrec is not capable of reversing the patmat tranformation made for tree + + override def phaseName = "patternMatcher" + + private var _id = 0 // left for debuging + + override def transformMatch(tree: Match)(implicit ctx: Context, info: TransformerInfo): Tree = { + val translated = new Translator()(ctx).translator.translateMatch(tree) + + // check exhaustivity and unreachability + val engine = new SpaceEngine + if (engine.checkable(tree)) { + engine.checkExhaustivity(tree) + engine.checkRedundancy(tree) + } + + translated.ensureConforms(tree.tpe) + } + + class Translator(implicit ctx: Context) { + + def translator = { + new OptimizingMatchTranslator/*(localTyper)*/ + } + + class OptimizingMatchTranslator extends MatchOptimizer/*(val typer: analyzer.Typer)*/ with MatchTranslator + + trait CodegenCore { + + // assert(owner ne null); assert(owner ne NoSymbol) + def freshSym(pos: Position, tp: Type = NoType, unique: UniqueNameKind = PatMatStdBinderName, owner: Symbol = ctx.owner) = { + ctx.newSymbol(owner, unique.fresh(), Flags.Synthetic | Flags.Case, tp, coord = pos) + } + + def newSynthCaseLabel(unique: UniqueNameKind, tpe: Type, owner: Symbol = ctx.owner) = + ctx.newSymbol(owner, unique.fresh(), Flags.Label | Flags.Synthetic | Flags.Method, tpe).asTerm + //NoSymbol.newLabel(freshName(name), NoPosition) setFlag treeInfo.SYNTH_CASE_FLAGS + + // codegen relevant to the structure of the translation (how extractors are combined) + trait AbsCodegen { + def matcher(scrut: Tree, scrutSym: Symbol, restpe: Type)(cases: List[Casegen => Tree], matchFailGen: Option[Symbol => Tree]): Tree + + // local / context-free + + /* cast b to tp */ + def _asInstanceOf(b: Symbol, tp: Type): Tree + /* a check `checker` == binder */ + def _equals(checker: Tree, binder: Symbol): Tree + /* b.isIsInstanceOf[tp] */ + def _isInstanceOf(b: Symbol, tp: Type): Tree + /* tgt is expected to be a Seq, call tgt.drop(n) */ + def drop(tgt: Tree)(n: Int): Tree + /* tgt is expected to have method apply(int), call tgt.apply(i) */ + def index(tgt: Tree)(i: Int): Tree + /* make tree that accesses the i'th component of the tuple referenced by binder */ + def tupleSel(binder: Symbol)(i: Int): Tree + } + + // structure + trait Casegen extends AbsCodegen { + def one(res: Tree): Tree + + def flatMap(prev: Tree, b: Symbol, next: Tree): Tree + def flatMapCond(cond: Tree, res: Tree, nextBinder: Symbol, next: Tree): Tree + def flatMapGuard(cond: Tree, next: Tree): Tree + def ifThenElseZero(c: Tree, thenp: Tree): Tree = + If(c, thenp, zero) + protected def zero: Tree + } + + def codegen: AbsCodegen + + abstract class CommonCodegen extends AbsCodegen { + def tupleSel(binder: Symbol)(i: Int): Tree = ref(binder).select(nme.productAccessorName(i)) + def index(tgt: Tree)(i: Int): Tree = { + if (i > 0) tgt.select(defn.Seq_apply).appliedTo(Literal(Constant(i))) + else tgt.select(defn.Seq_head).ensureApplied + } + + // Right now this blindly calls drop on the result of the unapplySeq + // unless it verifiably has no drop method (this is the case in particular + // with Array.) You should not actually have to write a method called drop + // for name-based matching, but this was an expedient route for the basics. + def drop(tgt: Tree)(n: Int): Tree = { + def callDirect = tgt.select(nme.drop).appliedTo(Literal(Constant(n))) + def callRuntime = ref(defn.ScalaRuntime_drop).appliedTo(tgt, Literal(Constant(n))) + + def needsRuntime = !(tgt.tpe derivesFrom defn.SeqClass) /*typeOfMemberNamedDrop(tgt.tpe) == NoType*/ + + if (needsRuntime) callRuntime else callDirect + } + + // NOTE: checker must be the target of the ==, that's the patmat semantics for ya + def _equals(checker: Tree, binder: Symbol): Tree = + tpd.applyOverloaded(checker, nme.EQ, List(ref(binder)), List.empty, defn.BooleanType) + + // the force is needed mainly to deal with the GADT typing hack (we can't detect it otherwise as tp nor pt need contain an abstract type, we're just casting wildly) + def _asInstanceOf(b: Symbol, tp: Type): Tree = ref(b).ensureConforms(tp) // andType here breaks t1048 + def _isInstanceOf(b: Symbol, tp: Type): Tree = ref(b).select(defn.Any_isInstanceOf).appliedToType(tp) + } + } + + object Rebindings { + def apply(from: Symbol, to: Symbol) = new Rebindings(List(from), List(ref(to))) + // requires sameLength(from, to) + def apply(from: List[Symbol], to: List[Tree]) = + if (from nonEmpty) new Rebindings(from, to) else NoRebindings + } + + class Rebindings(val lhs: List[Symbol], val rhs: List[Tree]) { + def >>(other: Rebindings) = { + if (other eq NoRebindings) this + else if (this eq NoRebindings) other + else { + assert((lhs.toSet ++ other.lhs.toSet).size == lhs.length + other.lhs.length, "no double assignments") + new Rebindings(this.lhs ++ other.lhs, this.rhs ++ other.rhs) + } + } + + def emitValDefs: List[ValDef] = { + (lhs, rhs).zipped.map((symbol, tree) => ValDef(symbol.asTerm, tree.ensureConforms(symbol.info))) + } + } + object NoRebindings extends Rebindings(Nil, Nil) + + trait OptimizedCodegen extends CodegenCore { + override def codegen: AbsCodegen = optimizedCodegen + + // when we know we're targetting Option, do some inlining the optimizer won't do + // for example, `o.flatMap(f)` becomes `if (o == None) None else f(o.get)`, similarly for orElse and guard + // this is a special instance of the advanced inlining optimization that takes a method call on + // an object of a type that only has two concrete subclasses, and inlines both bodies, guarded by an if to distinguish the two cases + object optimizedCodegen extends CommonCodegen { + + /** Inline runOrElse and get rid of Option allocations + * + * runOrElse(scrut: scrutTp)(matcher): resTp = matcher(scrut) getOrElse ${catchAll(`scrut`)} + * the matcher's optional result is encoded as a flag, keepGoing, where keepGoing == true encodes result.isEmpty, + * if keepGoing is false, the result Some(x) of the naive translation is encoded as matchRes == x + */ + def matcher(scrut: Tree, scrutSym: Symbol, restpe: Type)(cases: List[Casegen => Tree], matchFailGen: Option[Symbol => Tree]): Tree = { + //val matchRes = ctx.newSymbol(NoSymbol, ctx.freshName("matchRes").toTermName, Flags.Synthetic | Flags.Param | Flags.Label | Flags.Method, restpe /*withoutAnnotations*/) + //NoSymbol.newValueParameter(newTermName("x"), NoPosition, newFlags = SYNTHETIC) setInfo restpe.withoutAnnotations + + + val caseSyms: List[TermSymbol] = cases.scanLeft(ctx.owner.asTerm)((curOwner, nextTree) => + newSynthCaseLabel(PatMatCaseName, MethodType(Nil, restpe), curOwner)).tail + + // must compute catchAll after caseLabels (side-effects nextCase) + // catchAll.isEmpty iff no synthetic default case needed (the (last) user-defined case is a default) + // if the last user-defined case is a default, it will never jump to the next case; it will go immediately to matchEnd + val catchAllDef = matchFailGen.map { _(scrutSym) } + .getOrElse(Throw(New(defn.MatchErrorType, List(ref(scrutSym))))) + + val matchFail = newSynthCaseLabel(PatMatMatchFailName, MethodType(Nil, restpe)) + val catchAllDefBody = DefDef(matchFail, catchAllDef) + + val nextCases = (caseSyms.tail ::: List(matchFail)).map(ref(_).ensureApplied) + val caseDefs = (cases zip caseSyms zip nextCases).foldRight[Tree](catchAllDefBody) { + // dotty deviation + //case (((mkCase, sym), nextCase), acc) => + (x: (((Casegen => Tree), TermSymbol), Tree), acc: Tree) => x match { + case ((mkCase, sym), nextCase) => + val body = mkCase(new OptimizedCasegen(nextCase)).ensureConforms(restpe) + + DefDef(sym, _ => Block(List(acc), body)) + } + } + + // scrutSym == NoSymbol when generating an alternatives matcher + // val scrutDef = scrutSym.fold(List[Tree]())(ValDef(_, scrut) :: Nil) // for alternatives + + Block(List(caseDefs), ref(caseSyms.head).ensureApplied) + } + + class OptimizedCasegen(nextCase: Tree) extends CommonCodegen with Casegen { + def matcher(scrut: Tree, scrutSym: Symbol, restpe: Type)(cases: List[Casegen => Tree], matchFailGen: Option[Symbol => Tree]): Tree = + optimizedCodegen.matcher(scrut, scrutSym, restpe)(cases, matchFailGen) + + // only used to wrap the RHS of a body + // res: T + // returns MatchMonad[T] + def one(res: Tree): Tree = /*ref(matchEnd) appliedTo*/ res // a jump to a case label is special-cased in typedApply + protected def zero: Tree = nextCase + + // prev: MatchMonad[T] + // b: T + // next: MatchMonad[U] + // returns MatchMonad[U] + def flatMap(prev: Tree, b: Symbol, next: Tree): Tree = { + val resultArity = productArity(b.info) + if (isProductMatch(prev.tpe, resultArity)) { + val nullCheck: Tree = prev.select(defn.Object_ne).appliedTo(Literal(Constant(null))) + ifThenElseZero( + nullCheck, + Block( + List(ValDef(b.asTerm, prev)), + next //Substitution(b, ref(prevSym))(next) + ) + ) + } + else { + val getDenot = extractorMember(prev.tpe, nme.get) + val isEmptyDenot = extractorMember(prev.tpe, nme.isEmpty) + assert(getDenot.exists && isEmptyDenot.exists, i"${prev.tpe}") + + val tmpSym = freshSym(prev.pos, prev.tpe, PatMatOName) + val prevValue = ref(tmpSym).select(getDenot.symbol).ensureApplied + + Block( + List(ValDef(tmpSym, prev)), + // must be isEmpty and get as we don't control the target of the call (prev is an extractor call) + ifThenElseZero( + ref(tmpSym).select(isEmptyDenot.symbol).select(defn.Boolean_!), + Block(List(ValDef(b.asTerm, prevValue)), next) + ) + ) + } + } + + // cond: Boolean + // res: T + // nextBinder: T + // next == MatchMonad[U] + // returns MatchMonad[U] + def flatMapCond(cond: Tree, res: Tree, nextBinder: Symbol, next: Tree): Tree = { + val rest = Block(List(ValDef(nextBinder.asTerm, res)), next) + ifThenElseZero(cond, rest) + } + + // guardTree: Boolean + // next: MatchMonad[T] + // returns MatchMonad[T] + def flatMapGuard(guardTree: Tree, next: Tree): Tree = + ifThenElseZero(guardTree, next) + + def flatMapCondStored(cond: Tree, condSym: Symbol, res: Tree, nextBinder: Symbol, next: Tree): Tree = + ifThenElseZero(cond, Block( + List(Assign(ref(condSym), Literal(Constant(true))), + Assign(ref(nextBinder), res)), + next + )) + } + } + } + final case class Suppression(exhaustive: Boolean, unreachable: Boolean) + object Suppression { + val NoSuppression = Suppression(false, false) + } + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // the making of the trees + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + trait TreeMakers extends CodegenCore { + def optimizeCases(prevBinder: Symbol, cases: List[List[TreeMaker]], pt: Type): (List[List[TreeMaker]], List[Tree]) + def analyzeCases(prevBinder: Symbol, cases: List[List[TreeMaker]], pt: Type, suppression: Suppression): Unit = {} + + def emitSwitch(scrut: Tree, scrutSym: Symbol, cases: List[List[TreeMaker]], pt: Type, matchFailGenOverride: Option[Symbol => Tree], unchecked: Boolean): Option[Tree] = { + // TODO Deal with guards? + + def isSwitchableType(tpe: Type): Boolean = + (tpe isRef defn.IntClass) || + (tpe isRef defn.ByteClass) || + (tpe isRef defn.ShortClass) || + (tpe isRef defn.CharClass) + + object IntEqualityTestTreeMaker { + def unapply(treeMaker: EqualityTestTreeMaker): Option[Int] = treeMaker match { + case EqualityTestTreeMaker(`scrutSym`, _, Literal(const), _) => + if (const.isIntRange) Some(const.intValue) + else None + case _ => + None + } + } + + def isSwitchCase(treeMakers: List[TreeMaker]): Boolean = treeMakers match { + // case 5 => + case List(IntEqualityTestTreeMaker(_), _: BodyTreeMaker) => + true + + // case 5 | 6 => + case List(AlternativesTreeMaker(`scrutSym`, alts, _), _: BodyTreeMaker) => + alts.forall { + case List(IntEqualityTestTreeMaker(_)) => true + case _ => false + } + + // case _ => + case List(_: BodyTreeMaker) => + true + + /* case x @ pat => + * This includes: + * case x => + * case x @ 5 => + * case x @ (5 | 6) => + */ + case (_: SubstOnlyTreeMaker) :: rest => + isSwitchCase(rest) + + case _ => + false + } + + /* (Nil, body) means that `body` is the default case + * It's a bit hacky but it simplifies manipulations. + */ + def extractSwitchCase(treeMakers: List[TreeMaker]): (List[Int], BodyTreeMaker) = (treeMakers: @unchecked) match { + // case 5 => + case List(IntEqualityTestTreeMaker(intValue), body: BodyTreeMaker) => + (List(intValue), body) + + // case 5 | 6 => + case List(AlternativesTreeMaker(_, alts, _), body: BodyTreeMaker) => + val intValues = alts.map { alt => + (alt: @unchecked) match { + case List(IntEqualityTestTreeMaker(intValue)) => intValue + } + } + (intValues, body) + + // case _ => + case List(body: BodyTreeMaker) => + (Nil, body) + + // case x @ pat => + case (_: SubstOnlyTreeMaker) :: rest => + /* Rebindings have been propagated, so the eventual body in `rest` + * contains all the necessary information. The substitution can be + * dropped at this point. + */ + extractSwitchCase(rest) + } + + def doOverlap(a: List[Int], b: List[Int]): Boolean = + a.exists(b.contains _) + + def makeSwitch(valuesToCases: List[(List[Int], BodyTreeMaker)]): Tree = { + def genBody(body: BodyTreeMaker): Tree = { + val valDefs = body.rebindings.emitValDefs + if (valDefs.isEmpty) body.body + else Block(valDefs, body.body) + } + + val intScrut = + if (pt isRef defn.IntClass) ref(scrutSym) + else Select(ref(scrutSym), nme.toInt) + + val (normalCases, defaultCaseAndRest) = valuesToCases.span(_._1.nonEmpty) + + val newCases = for { + (values, body) <- normalCases + } yield { + val literals = values.map(v => Literal(Constant(v))) + val pat = + if (literals.size == 1) literals.head + else Alternative(literals) + CaseDef(pat, EmptyTree, genBody(body)) + } + + val catchAllDef = { + if (defaultCaseAndRest.isEmpty) { + matchFailGenOverride.fold[Tree]( + Throw(New(defn.MatchErrorType, List(ref(scrutSym)))))( + _(scrutSym)) + } else { + /* After the default case, assuming the IR even allows anything, + * things are unreachable anyway and can be removed. + */ + genBody(defaultCaseAndRest.head._2) + } + } + val defaultCase = CaseDef(Underscore(defn.IntType), EmptyTree, catchAllDef) + + Match(intScrut, newCases :+ defaultCase) + } + + val dealiased = scrut.tpe.widenDealias + if (isSwitchableType(dealiased) && cases.forall(isSwitchCase)) { + val valuesToCases = cases.map(extractSwitchCase) + val values = valuesToCases.map(_._1) + if (values.tails.exists { tail => tail.nonEmpty && tail.tail.exists(doOverlap(_, tail.head)) }) { + // TODO Deal with overlapping cases (mostly useless without guards) + None + } else { + Some(makeSwitch(valuesToCases)) + } + } else { + if (dealiased hasAnnotation defn.SwitchAnnot) + ctx.warning("failed to emit switch for `@switch` annotated match", scrut.pos) + None + } + } + + // for catch (no need to customize match failure) + def emitTypeSwitch(bindersAndCases: List[(Symbol, List[TreeMaker])], pt: Type): Option[List[CaseDef]] = + None // todo + + abstract class TreeMaker { + def pos: Position + + private[this] var currSub: Rebindings = null + + /** captures the scope and the value of the bindings in patterns + * important *when* the substitution happens (can't accumulate and do at once after the full matcher has been constructed) + */ + def rebindings: Rebindings = + if (currSub eq null) introducedRebindings + else currSub + + protected def introducedRebindings: Rebindings + + private[TreeMakers] def incorporateOuterRebinding(outerSubst: Rebindings): Unit = { + if (currSub ne null) { + ctx.debuglog("BUG: incorporateOuterRebinding called more than once for " + ((this, currSub, outerSubst))) + if (ctx.debug) Thread.dumpStack() + } + else currSub = outerSubst >> rebindings + } + + /** The substitution that specifies the trees that compute the values of the subpattern binders. + * + * Should not be used to perform actual substitution! + * Only used to reason symbolically about the values the subpattern binders are bound to. + * See TreeMakerToCond#updateSubstitution. + * + * Overridden in PreserveSubPatBinders to pretend it replaces the subpattern binders by subpattern refs + * (Even though we don't do so anymore -- see SI-5158, SI-5739 and SI-6070.) + * + * TODO: clean this up, would be nicer to have some higher-level way to compute + * the binders bound by this tree maker and the symbolic values that correspond to them + */ + def subPatternsAsRebindings: Rebindings = rebindings + + // build Tree that chains `next` after the current extractor + def chainBefore(next: Tree)(casegen: Casegen): Tree + } + + sealed trait NoNewBinders extends TreeMaker { + protected val introducedRebindings: Rebindings = NoRebindings + } + + case class TrivialTreeMaker(tree: Tree) extends TreeMaker with NoNewBinders { + def pos = tree.pos + + def chainBefore(next: Tree)(casegen: Casegen): Tree = tree + } + + case class BodyTreeMaker(body: Tree, matchPt: Type) extends TreeMaker with NoNewBinders { + def pos = body.pos + + def chainBefore(next: Tree)(casegen: Casegen): Tree = // assert(next eq EmptyTree) + /*atPos(body.pos)*/(casegen.one(body)) // since SubstOnly treemakers are dropped, need to do it here + override def toString = "B" + ((body, matchPt)) + } + + /** + * In scalac for such block + * x match { + * case d => + * } + * + * d inside was to be substitued by x. + * + * In dotty, SubstOnlyTreeMakers instead generate normal ValDef, + * and does not create a new substitution. + * + * This was done for several reasons: + * 1) it is a lot easyer to Y-check, + * as d type could be used in . + * 2) it would simplify debugging of the generated code as + * this works also for nested patterns, and previously they used unreadable names + * 3) It showed better(~30%), performance, + * Rebuilding tree and propagating types was taking substantial time. + */ + case class SubstOnlyTreeMaker(prevBinder: Symbol, nextBinder: Symbol) extends TreeMaker { + val pos = Positions.NoPosition + + val introducedRebindings = Rebindings(prevBinder, nextBinder) + def chainBefore(next: Tree)(casegen: Casegen): Tree = next + //override def toString = "S" + localSubstitution + } + + sealed abstract class FunTreeMaker extends TreeMaker { + val nextBinder: Symbol + def pos = nextBinder.pos + } + + sealed abstract class CondTreeMaker extends FunTreeMaker { + val prevBinder: Symbol + val nextBinderTp: Type + val cond: Tree + val res: Tree + + val nextBinder: Symbol + lazy val introducedRebindings = /* + if (nextBinder ne prevBinder) Rebindings(prevBinder, nextBinder) + else */ NoRebindings + + def chainBefore(next: Tree)(casegen: Casegen): Tree = + if (prevBinder ne nextBinder) // happens when typeTest is known to succeed + /*atPos(pos)(*/casegen.flatMapCond(cond, res, nextBinder, next)//) + else casegen.flatMapGuard(cond, next) + } + + // unless we're optimizing, emit local variable bindings for all subpatterns of extractor/case class patterns + protected val debugInfoEmitVars = true //!settings.optimise.value + + /** + * Tree maker that captures sub pattern values during pattern match. + */ + sealed trait PreserveSubPatBinders extends TreeMaker { + val subPatBinders: List[Symbol] // captured values + val subPatRefs: List[Tree] // trees that will replace references to subPatBinders + val ignoredSubPatBinders: Set[Symbol] // ignored as they aren't used in body of pattern + + // unless `debugInfoEmitVars`, this set should contain the bare minimum for correctness + // mutable case class fields need to be stored regardless (SI-5158, SI-6070) -- see override in ProductExtractorTreeMaker + // sub patterns bound to wildcard (_) are never stored as they can't be referenced + // dirty debuggers will have to get dirty to see the wildcards + lazy val storedBinders: Set[Symbol] = + (if (debugInfoEmitVars) subPatBinders.toSet else Set.empty) ++ extraStoredBinders -- ignoredSubPatBinders + + // e.g., mutable fields of a case class in ProductExtractorTreeMaker + def extraStoredBinders: Set[Symbol] + + def emitVars = storedBinders.nonEmpty + + lazy val storedSubsted = (subPatBinders, subPatRefs).zipped.partition{ case (sym, _) => storedBinders(sym) } + + def stored = storedSubsted._1 + + def substed = storedSubsted._2 + + // dd: this didn't yet trigger error. But I believe it would. if this causes double denition of symbol error this can be replaced with NoRebindings + protected lazy val introducedRebindings: Rebindings = if (!emitVars) Rebindings(subPatBinders, subPatRefs) + else { + val (subPatBindersSubstituted, subPatRefsSubstituted) = substed.unzip + Rebindings(subPatBindersSubstituted.toList, subPatRefsSubstituted.toList) + } + + /** The substitution that specifies the trees that compute the values of the subpattern binders. + * + * We pretend to replace the subpattern binders by subpattern refs + * (Even though we don't do so anymore -- see SI-5158, SI-5739 and SI-6070.) + */ + override def subPatternsAsRebindings = + Rebindings(subPatBinders, subPatRefs) >> super.subPatternsAsRebindings + + def bindSubPats(in: Tree): Tree = + if (!emitVars) in + else { + // binders in `subPatBindersStored` that are referenced by tree `in` + val usedBinders = new collection.mutable.HashSet[Symbol]() + // all potentially stored subpat binders + val potentiallyStoredBinders = stored.unzip._1.toSet + // compute intersection of all symbols in the tree `in` and all potentially stored subpat binders + new DeepFolder[Unit]((x: Unit, t: Tree) => + if (potentiallyStoredBinders(t.symbol)) usedBinders += t.symbol).apply((), in) + + if (usedBinders.isEmpty) in + else { + // only store binders actually used + val (subPatBindersStored, subPatRefsStored) = stored.filter{case (b, _) => usedBinders(b)}.unzip + + Block((subPatBindersStored.toList, subPatRefsStored.toList).zipped.map((bind, ref) => { + // required in case original pattern had a more precise type + // eg case s@"foo" => would be otherwise translated to s with type String instead of String("foo") + def refTpeWiden = ref.tpe.widen + def bindInfoWiden = bind.info.widen + def loc = bind.showFullName + if (!(ref.tpe <:< bind.info.widen)) { + ctx.debuglog(s"here ${bind.showFullName} expected: ${bindInfoWiden.show} got: ${refTpeWiden.show}") + } + val refCasted = ref.ensureConforms(bind.info) + ValDef(bind.asTerm, refCasted) + }), in) + } + } + } + + /** + * Make a TreeMaker that will result in an extractor call specified by `extractor` + * the next TreeMaker (here, we don't know which it'll be) is chained after this one by flatMap'ing + * a function with binder `nextBinder` over our extractor's result + * the function's body is determined by the next TreeMaker + * (furthermore, the interpretation of `flatMap` depends on the codegen instance we're using). + * + * The values for the subpatterns, as computed by the extractor call in `extractor`, + * are stored in local variables that re-use the symbols in `subPatBinders`. + * This makes extractor patterns more debuggable (SI-5739). + */ + case class ExtractorTreeMaker(extractor: Tree, extraCond: Option[Tree], nextBinder: Symbol)( + val subPatBinders: List[Symbol], + val subPatRefs: List[Tree], + extractorReturnsBoolean: Boolean, + val checkedLength: Option[Int], + val prevBinder: Symbol, + val ignoredSubPatBinders: Set[Symbol] + ) extends FunTreeMaker with PreserveSubPatBinders { + + def extraStoredBinders: Set[Symbol] = Set() + + ctx.debuglog(s""" + |ExtractorTreeMaker($extractor, $extraCond, $nextBinder) { + | $subPatBinders + | $subPatRefs + | $extractorReturnsBoolean + | $checkedLength + | $prevBinder + | $ignoredSubPatBinders + |}""".stripMargin) + + def chainBefore(next: Tree)(casegen: Casegen): Tree = { + val condAndNext = extraCond match { + case Some(cond: Tree) => + casegen.ifThenElseZero(cond, bindSubPats(next)) + case _ => + bindSubPats(next) + } + + if (extractorReturnsBoolean) casegen.flatMapCond(extractor, unitLiteral, nextBinder, condAndNext) + else casegen.flatMap(extractor, nextBinder, condAndNext) // getType? + } + + override def toString = "X" + ((extractor, nextBinder.name)) + } + + object IrrefutableExtractorTreeMaker { + // will an extractor with unapply method of methodtype `tp` always succeed? + // note: this assumes the other side-conditions implied by the extractor are met + // (argument of the right type, length check succeeds for unapplySeq,...) + def irrefutableExtractorType(tp: Type): Boolean = tp.resultType.dealias match { + // case TypeRef(_, SomeClass, _) => true todo + // probably not useful since this type won't be inferred nor can it be written down (yet) + // case ConstantTrue => true todo + case _ => false + } + + def unapply(xtm: ExtractorTreeMaker): Option[(Tree, Symbol)] = xtm match { + case ExtractorTreeMaker(extractor, None, nextBinder) if irrefutableExtractorType(extractor.tpe) => + Some((extractor, nextBinder)) + case _ => + None + } + } + + object TypeTestTreeMaker { + // factored out so that we can consistently generate other representations of the tree that implements the test + // (e.g. propositions for exhaustivity and friends, boolean for isPureTypeTest) + trait TypeTestCondStrategy { + type Result + + def outerTest(testedBinder: Symbol, expectedTp: Type): Result + // TODO: can probably always widen + def typeTest(testedBinder: Symbol, expectedTp: Type): Result + def nonNullTest(testedBinder: Symbol): Result + def equalsTest(pat: Tree, testedBinder: Symbol): Result + def eqTest(pat: Tree, testedBinder: Symbol): Result + def and(a: Result, b: Result): Result + def tru: Result + } + + object treeCondStrategy extends TypeTestCondStrategy { + type Result = Tree + + def and(a: Result, b: Result): Result = a.select(defn.Boolean_&&).appliedTo(b) + def tru = Literal(Constant(true)) + def typeTest(testedBinder: Symbol, expectedTp: Type) = codegen._isInstanceOf(testedBinder, expectedTp) + def nonNullTest(testedBinder: Symbol) = ref(testedBinder).select(defn.Object_ne).appliedTo(Literal(Constant(null))) + def equalsTest(pat: Tree, testedBinder: Symbol) = codegen._equals(pat, testedBinder) + def eqTest(pat: Tree, testedBinder: Symbol) = ref(testedBinder).select(defn.Object_eq).appliedTo(pat) + + def outerTest(testedBinder: Symbol, expectedTp: Type): Tree = { + val expectedOuter = expectedTp.normalizedPrefix match { + //case NoType => Literal(Constant(true)) // fallback for SI-6183 todo? + case pre: SingletonType => singleton(pre) + } + + // ExplicitOuter replaces `Select(q, outerSym) OBJ_EQ expectedPrefix` by `Select(q, outerAccessor(outerSym.owner)) OBJ_EQ expectedPrefix` + // if there's an outer accessor, otherwise the condition becomes `true` -- TODO: can we improve needsOuterTest so there's always an outerAccessor? + // val outer = expectedTp.typeSymbol.newMethod(vpmName.outer, newFlags = SYNTHETIC | ARTIFACT) setInfo expectedTp.prefix + + val expectedClass = expectedTp.dealias.classSymbol.asClass + val test = codegen._asInstanceOf(testedBinder, expectedTp) + // TODO: Use nme.OUTER_SELECT, like the Inliner does? + val outerAccessorTested = ctx.atPhase(ctx.explicitOuterPhase.next) { implicit ctx => + ExplicitOuter.ensureOuterAccessors(expectedClass) + test.select(ExplicitOuter.outerAccessor(expectedClass)).select(defn.Object_eq).appliedTo(expectedOuter) + } + outerAccessorTested + } + } + + /*object pureTypeTestChecker extends TypeTestCondStrategy { + type Result = Boolean + + def typeTest(testedBinder: Symbol, expectedTp: Type): Result = true + + def outerTest(testedBinder: Symbol, expectedTp: Type): Result = false + def nonNullTest(testedBinder: Symbol): Result = false + def equalsTest(pat: Tree, testedBinder: Symbol): Result = false + def eqTest(pat: Tree, testedBinder: Symbol): Result = false + def and(a: Result, b: Result): Result = false // we don't and type tests, so the conjunction must include at least one false + def tru = true + }*/ + + def nonNullImpliedByTestChecker(binder: Symbol) = new TypeTestCondStrategy { + type Result = Boolean + + def typeTest(testedBinder: Symbol, expectedTp: Type): Result = testedBinder eq binder + def outerTest(testedBinder: Symbol, expectedTp: Type): Result = false + def nonNullTest(testedBinder: Symbol): Result = testedBinder eq binder + def equalsTest(pat: Tree, testedBinder: Symbol): Result = false // could in principle analyse pat and see if it's statically known to be non-null + def eqTest(pat: Tree, testedBinder: Symbol): Result = false // could in principle analyse pat and see if it's statically known to be non-null + def and(a: Result, b: Result): Result = a || b + def tru = false + } + } + + /** implements the run-time aspects of (§8.2) (typedPattern has already done the necessary type transformations) + * + * Type patterns consist of types, type variables, and wildcards. A type pattern T is of one of the following forms: + - A reference to a class C, p.C, or T#C. + This type pattern matches any non-null instance of the given class. + Note that the prefix of the class, if it is given, is relevant for determining class instances. + For instance, the pattern p.C matches only instances of classes C which were created with the path p as prefix. + The bottom types scala.Nothing and scala.Null cannot be used as type patterns, because they would match nothing in any case. + + - A singleton type p.type. + This type pattern matches only the value denoted by the path p + (that is, a pattern match involved a comparison of the matched value with p using method eq in class AnyRef). // TODO: the actual pattern matcher uses ==, so that's what I'm using for now + // https://issues.scala-lang.org/browse/SI-4577 "pattern matcher, still disappointing us at equality time" + + - A compound type pattern T1 with ... with Tn where each Ti is a type pat- tern. + This type pattern matches all values that are matched by each of the type patterns Ti. + + - A parameterized type pattern T[a1,...,an], where the ai are type variable patterns or wildcards _. + This type pattern matches all values which match T for some arbitrary instantiation of the type variables and wildcards. + The bounds or alias type of these type variable are determined as described in (§8.3). + + - A parameterized type pattern scala.Array[T1], where T1 is a type pattern. // TODO + This type pattern matches any non-null instance of type scala.Array[U1], where U1 is a type matched by T1. + **/ + case class TypeTestTreeMaker(afterTest: Symbol, testedBinder: Symbol, expectedTp: Type, nextBinderTp: Type)(override val pos: Position, extractorArgTypeTest: Boolean = false) extends CondTreeMaker { + import TypeTestTreeMaker._ + + ctx.debuglog("TTTM" + ((prevBinder, extractorArgTypeTest, testedBinder, expectedTp, nextBinderTp))) + + val prevBinder = testedBinder + + val nextBinder = afterTest.asTerm + + def outerTestNeeded(implicit ctx: Context): Boolean = { + // See the test for SI-7214 for motivation for dealias. Later `treeCondStrategy#outerTest` + // generates an outer test based on `patType.prefix` with automatically dealises. + expectedTp.dealias match { + case tref @ TypeRef(pre: SingletonType, name) => + val s = tref + s.symbol.isClass && + ExplicitOuter.needsOuterIfReferenced(s.symbol.asClass) + case _ => + false + } + } + + override lazy val introducedRebindings = NoRebindings + + // the logic to generate the run-time test that follows from the fact that + // a `prevBinder` is expected to have type `expectedTp` + // the actual tree-generation logic is factored out, since the analyses generate Cond(ition)s rather than Trees + // TODO: `null match { x : T }` will yield a check that (indirectly) tests whether `null ne null` + // don't bother (so that we don't end up with the warning "comparing values of types Null and Null using `ne' will always yield false") + def renderCondition(cs: TypeTestCondStrategy): cs.Result = { + import cs._ + + // propagate expected type + def expTp(t: Tree): t.type = t // setType expectedTp todo: + + def testedWide = testedBinder.info.widen + def expectedWide = expectedTp.widen + def isAnyRef = testedWide <:< defn.AnyRefType + def isAsExpected = testedWide <:< expectedTp + def isExpectedPrimitiveType = isAsExpected && expectedTp.classSymbol.isPrimitiveValueClass + def isExpectedReferenceType = isAsExpected && (expectedTp <:< defn.AnyRefType) + def mkNullTest = nonNullTest(testedBinder) + def mkOuterTest = outerTest(testedBinder, expectedTp) + def mkTypeTest = typeTest(testedBinder, expectedWide) + + def mkEqualsTest(lhs: Tree): cs.Result = equalsTest(lhs, testedBinder) + def mkEqTest(lhs: Tree): cs.Result = eqTest(lhs, testedBinder) + def addOuterTest(res: cs.Result): cs.Result = if (outerTestNeeded) and(res, mkOuterTest) else res + + // If we conform to expected primitive type: + // it cannot be null and cannot have an outer pointer. No further checking. + // If we conform to expected reference type: + // have to test outer and non-null + // If we do not conform to expected type: + // have to test type and outer (non-null is implied by successful type test) + def mkDefault = ( + if (isExpectedPrimitiveType) tru + else addOuterTest( + if (isExpectedReferenceType) mkNullTest + else mkTypeTest + ) + ) + + // true when called to type-test the argument to an extractor + // don't do any fancy equality checking, just test the type + // TODO: verify that we don't need to special-case Array + // I think it's okay: + // - the isInstanceOf test includes a test for the element type + // - Scala's arrays are invariant (so we don't drop type tests unsoundly) + if (extractorArgTypeTest) mkDefault + else expectedTp match { + case ThisType(tref) if tref.symbol.flags is Flags.Module => + and(mkEqualsTest(ref(tref.symbol.companionModule)), mkTypeTest) // must use == to support e.g. List() == Nil + case ConstantType(Constant(null)) if isAnyRef => mkEqTest(expTp(Literal(Constant(null)))) + case ConstantType(const) => mkEqualsTest(expTp(Literal(const))) + case t: SingletonType => mkEqTest(singleton(expectedTp)) // SI-4577, SI-4897 + //case ThisType(sym) => mkEqTest(expTp(This(sym))) + case _ => mkDefault + } + } + + val cond = renderCondition(treeCondStrategy) + val res = codegen._asInstanceOf(testedBinder, nextBinderTp) + + // is this purely a type test, e.g. no outer check, no equality tests (used in switch emission) + //def isPureTypeTest = renderCondition(pureTypeTestChecker) + + def impliesBinderNonNull(binder: Symbol): Boolean = + // @odersky: scalac is able to infer in this method that nonNullImpliedByTestChecker.Result, + // dotty instead infers type projection TreeMakers.this.TypeTestTreeMaker.TypeTestCondStrategy#Result + // which in turn doesn't typecheck in this method. Can you please explain why? + // dotty deviation + renderCondition(nonNullImpliedByTestChecker(binder)).asInstanceOf[Boolean] + + override def toString = "TT" + ((expectedTp, testedBinder.name, nextBinderTp)) + } + + // need to substitute to deal with existential types -- TODO: deal with existentials better, don't substitute (see RichClass during quick.comp) + case class EqualityTestTreeMaker(prevBinder: Symbol, subpatBinder: Symbol, patTree: Tree, override val pos: Position) extends CondTreeMaker { + val nextBinderTp = patTree.tpe & prevBinder.info + val nextBinder = if (prevBinder eq subpatBinder) freshSym(pos, nextBinderTp) else subpatBinder + + // NOTE: generate `patTree == patBinder`, since the extractor must be in control of the equals method (also, patBinder may be null) + // equals need not be well-behaved, so don't intersect with pattern's (stabilized) type (unlike MaybeBoundTyped's accumType, where it's required) + val cond = codegen._equals(patTree, prevBinder) + val res = ref(prevBinder).ensureConforms(nextBinderTp) + override def toString = "ET" + ((prevBinder.name, patTree)) + } + + case class AlternativesTreeMaker(prevBinder: Symbol, var altss: List[List[TreeMaker]], pos: Position) extends TreeMaker with NoNewBinders { + // don't substitute prevBinder to nextBinder, a set of alternatives does not need to introduce a new binder, simply reuse the previous one + + override private[TreeMakers] def incorporateOuterRebinding(outerSubst: Rebindings): Unit = { + super.incorporateOuterRebinding(outerSubst) + altss = altss map (alts => propagateRebindings(alts, rebindings)) + } + + def chainBefore(next: Tree)(codegenAlt: Casegen): Tree = { + /*atPos(pos)*/{ + // one alternative may still generate multiple trees (e.g., an extractor call + equality test) + // (for now,) alternatives may not bind variables (except wildcards), so we don't care about the final substitution built internally by makeTreeMakers + val combinedAlts = altss map (altTreeMakers => + ((casegen: Casegen) => combineExtractors(altTreeMakers :+ TrivialTreeMaker(casegen.one(Literal(Constant(true)))))(casegen)) + ) + + val findAltMatcher = codegenAlt.matcher(EmptyTree, NoSymbol, defn.BooleanType)(combinedAlts, Some((x: Symbol) => Literal(Constant(false)))) + codegenAlt.ifThenElseZero(findAltMatcher, next) + } + } + } + + case class GuardTreeMaker(guardTree: Tree) extends TreeMaker with NoNewBinders { + val pos = guardTree.pos + + def chainBefore(next: Tree)(casegen: Casegen): Tree = casegen.flatMapGuard(guardTree, next) + override def toString = "G(" + guardTree + ")" + } + + // combineExtractors changes the current substitution's of the tree makers in `treeMakers` + // requires propagateSubstitution(treeMakers) has been called + def combineExtractors(treeMakers: List[TreeMaker])(casegen: Casegen): Tree = { + val (testsMakers, guardAndBodyMakers) = treeMakers.span(t => !(t.isInstanceOf[NoNewBinders])) + val body = guardAndBodyMakers.foldRight(EmptyTree: Tree)((a, b) => a.chainBefore(b)(casegen)) + val rebindings = guardAndBodyMakers.last.rebindings.emitValDefs + testsMakers.foldRight(Block(rebindings, body): Tree)((a, b) => a.chainBefore(b)(casegen)) + } + // a foldLeft to accumulate the localSubstitution left-to-right + // unlike in scalace it does not drop SubstOnly tree makers, + // as there could be types having them as prefix + def propagateRebindings(treeMakers: List[TreeMaker], initial: Rebindings): List[TreeMaker] = { + var accumSubst: Rebindings = initial + treeMakers foreach { maker => + maker incorporateOuterRebinding accumSubst + accumSubst = maker.rebindings + } + treeMakers + } + + // calls propagateSubstitution on the treemakers + def combineCases(scrut: Tree, scrutSym: Symbol, casesRaw: List[List[TreeMaker]], pt: Type, owner: Symbol, matchFailGenOverride: Option[Symbol => Tree]): Tree = { + // unlike in scalac SubstOnlyTreeMakers are maintained. + val casesRebindingPropagated = casesRaw map (propagateRebindings(_, NoRebindings)) + + def matchFailGen = matchFailGenOverride orElse Some((arg: Symbol) => Throw(New(defn.MatchErrorType, List(ref(arg))))) + + ctx.debuglog("combining cases: " + (casesRebindingPropagated.map(_.mkString(" >> ")).mkString("{", "\n", "}"))) + + val (suppression, requireSwitch): (Suppression, Boolean) = + /*if (settings.XnoPatmatAnalysis)*/ (Suppression.NoSuppression, false) + /*else scrut match { + case Typed(tree, tpt) => + val suppressExhaustive = tpt.tpe hasAnnotation UncheckedClass + val supressUnreachable = tree match { + case Ident(name) if name startsWith nme.CHECK_IF_REFUTABLE_STRING => true // SI-7183 don't warn for withFilter's that turn out to be irrefutable. + case _ => false + } + val suppression = Suppression(suppressExhaustive, supressUnreachable) + // matches with two or fewer cases need not apply for switchiness (if-then-else will do) + val requireSwitch = treeInfo.isSwitchAnnotation(tpt.tpe) && casesNoSubstOnly.lengthCompare(2) > 0 + (suppression, requireSwitch) + case _ => + (Suppression.NoSuppression, false) + }*/ + + emitSwitch(scrut, scrutSym, casesRebindingPropagated, pt, matchFailGenOverride, suppression.exhaustive).getOrElse{ + if (requireSwitch) ctx.warning("could not emit switch for @switch annotated match", scrut.pos) + + if (casesRebindingPropagated nonEmpty) { + // before optimizing, check casesNoSubstOnly for presence of a default case, + // since DCE will eliminate trivial cases like `case _ =>`, even if they're the last one + // exhaustivity and reachability must be checked before optimization as well + // TODO: improve notion of trivial/irrefutable -- a trivial type test before the body still makes for a default case + // ("trivial" depends on whether we're emitting a straight match or an exception, or more generally, any supertype of scrutSym.tpe is a no-op) + // irrefutability checking should use the approximation framework also used for CSE, unreachability and exhaustivity checking + val synthCatchAll: Option[Symbol => Tree] = + if (casesRebindingPropagated.nonEmpty && { + val nonTrivLast = casesRebindingPropagated.last + nonTrivLast.nonEmpty && nonTrivLast.head.isInstanceOf[BodyTreeMaker] + }) None + else matchFailGen + + analyzeCases(scrutSym, casesRebindingPropagated, pt, suppression) + + val (cases, toHoist) = optimizeCases(scrutSym, casesRebindingPropagated, pt) + + val matchRes = codegen.matcher(scrut, scrutSym, pt)(cases.map(x => combineExtractors(x) _), synthCatchAll) + + if (toHoist isEmpty) matchRes else Block(toHoist, matchRes) + } else { + codegen.matcher(scrut, scrutSym, pt)(Nil, matchFailGen) + } + } + } + } + + trait MatchOptimizer extends OptimizedCodegen with TreeMakers + /*with SwitchEmission // todo: toBe ported + with CommonSubconditionElimination*/ { + override def optimizeCases(prevBinder: Symbol, cases: List[List[TreeMaker]], pt: Type): (List[List[TreeMaker]], List[Tree]) = { + // TODO: do CSE on result of doDCE(prevBinder, cases, pt) + val optCases = cases// todo: doCSE(prevBinder, cases, pt) + val toHoist = Nil/*( + for (treeMakers <- optCases) + yield treeMakers.collect{case tm: ReusedCondTreeMaker => tm.treesToHoist} + ).flatten.flatten.toList*/ + (optCases, toHoist) + } + } + + trait MatchTranslator extends TreeMakers with ScalacPatternExpanders { + + def isVarPattern(pat: Tree): Boolean = pat match { + case x: BackquotedIdent => false + case x: Ident => x.name.isVariableName + case _ => false + } + + /** A conservative approximation of which patterns do not discern anything. + * They are discarded during the translation. + */ + object WildcardPattern { + def unapply(pat: Tree): Boolean = pat match { + case Typed(_, arg) if arg.tpe.isRepeatedParam => true + case Bind(nme.WILDCARD, WildcardPattern()) => true // don't skip when binding an interesting symbol! + case t if (tpd.isWildcardArg(t)) => true + case x: Ident => isVarPattern(x) + case Alternative(ps) => ps forall unapply + case EmptyTree => true + case _ => false + } + } + + object PatternBoundToUnderscore { + def unapply(pat: Tree): Boolean = pat match { + case Bind(nme.WILDCARD, _) => true // don't skip when binding an interesting symbol! + case Ident(nme.WILDCARD) => true + case Alternative(ps) => ps forall unapply + case Typed(PatternBoundToUnderscore(), _) => false // true // Dmitry: change in dotty. Type test will be performed and the field must be stored + case _ => false + } + } + + object SymbolBound { + def unapply(tree: Tree): Option[(Symbol, Tree)] = tree match { + case Bind(_, expr) if tree.symbol.exists => Some(tree.symbol -> expr) + case _ => None + } + } + + def newBoundTree(tree: Tree, pt: Type): BoundTree = tree match { + case SymbolBound(sym, Typed(subpat, tpe)) => BoundTree(freshSym(tree.pos, pt, PatMatPiName), tree) + case SymbolBound(sym, expr) => BoundTree(sym, expr) + case _ => BoundTree(freshSym(tree.pos, pt, PatMatPName), tree) + } + + final case class BoundTree(binder: Symbol, tree: Tree) { + private lazy val extractor = ExtractorCall(tree, binder) + + def pos = tree.pos + def tpe = binder.info.widenDealias + def pt = unbound match { + // case Star(tpt) => this glbWith seqType(tpt.tpe) dd todo: + case TypeBound(tpe) => tpe + case tree => tree.tpe + } + + def glbWith(other: Type) = ctx.typeComparer.glb(tpe :: other :: Nil)// .normalize + + object SymbolAndTypeBound { + def unapply(tree: Tree): Option[(Symbol, Type)] = tree match { + case SymbolBound(sym, Typed(_: UnApply, _)) => None // see comment in #189 + case SymbolBound(sym, TypeBound(tpe)) => Some(sym -> tpe) + case TypeBound(tpe) => Some(binder -> tpe) + case _ => None + } + } + + object SymbolAndValueBound { + def unapply(tree: Tree): Option[(Symbol, Tree)] = tree match { + case SymbolBound(sym, ConstantPattern(const)) => Some(sym -> const) + case _ => None + } + } + + object TypeBound { + def unapply(tree: Tree): Option[Type] = tree match { + case Typed(_, arg) if !arg.tpe.isRepeatedParam => Some(tree.typeOpt) + case _ => None + } + } + + object ConstantPattern { + def unapply(tree: Tree): Option[Tree] = tree match { + case Literal(Constant(_)) | Ident(_) | Select(_, _) | This(_) => Some(tree) + case _ => None + } + } + + private def rebindTo(pattern: Tree) = BoundTree(binder, pattern) + private def step(treeMakers: TreeMaker*)(subpatterns: BoundTree*): TranslationStep = TranslationStep(treeMakers.toList, subpatterns.toList) + + private def bindingStep(sub: Symbol, subpattern: Tree) = step(SubstOnlyTreeMaker(sub, binder))(rebindTo(subpattern)) + private def equalityTestStep(testedSymbol: Symbol, constantSymbol: Symbol, constant: Tree) + = step(EqualityTestTreeMaker(testedSymbol, constantSymbol, constant, pos))() + private def typeTestStep(sub: Symbol, subPt: Type) = step(TypeTestTreeMaker(sub, binder, subPt, sub.termRef)(pos))() + private def alternativesStep(alts: List[Tree]) = step(AlternativesTreeMaker(binder, translatedAlts(alts), alts.head.pos))() + private def translatedAlts(alts: List[Tree]) = alts map (alt => rebindTo(alt).translate()) + private def noStep() = step()() + + private def unsupportedPatternMsg = + i"unsupported pattern: ${tree.show} / $this (this is a scalac bug.)" + + // example check: List[Int] <:< ::[Int] + private def extractorStep(): TranslationStep = { + def paramType = extractor.aligner.wholeType + import extractor.treeMaker + // chain a type-testing extractor before the actual extractor call + // it tests the type, checks the outer pointer and casts to the expected type + // TODO: the outer check is mandated by the spec for case classes, but we do it for user-defined unapplies as well [SPEC] + // (the prefix of the argument passed to the unapply must equal the prefix of the type of the binder) + lazy val typeTest = TypeTestTreeMaker(freshSym(pos, paramType), binder, paramType, paramType)(pos, extractorArgTypeTest = true) + // check whether typetest implies binder is not null, + // even though the eventual null check will be on typeTest.nextBinder + // it'll be equal to binder casted to paramType anyway (and the type test is on binder) + def extraction: TreeMaker = treeMaker(typeTest.nextBinder, typeTest.impliesBinderNonNull(binder), pos, paramType) + + // paramType = the type expected by the unapply + // TODO: paramType may contain unbound type params (run/t2800, run/t3530) + val makers = ( + // Statically conforms to paramType + if (tpe <:< paramType) treeMaker(binder, false, pos, tpe) :: Nil + else typeTest :: extraction :: Nil + ) + step(makers: _*)(extractor.subBoundTrees: _*) + } + + // Summary of translation cases. I moved the excerpts from the specification further below so all + // the logic can be seen at once. + // + // [1] skip wildcard trees -- no point in checking them + // [2] extractor and constructor patterns + // [3] replace subpatBinder by patBinder, as if the Bind was not there. + // It must be patBinder, as subpatBinder has the wrong info: even if the bind assumes a better type, + // this is not guaranteed until we cast + // [4] typed patterns - a typed pattern never has any subtrees + // must treat Typed and Bind together -- we need to know the patBinder of the Bind pattern to get at the actual type + // [5] literal and stable id patterns + // [6] pattern alternatives + // [7] symbol-less bind patterns - this happens in certain ill-formed programs, there'll be an error later + // don't fail here though (or should we?) + def nextStep(): TranslationStep = tree match { + case _: UnApply | _: Apply | Typed(_: UnApply | _: Apply, _) => extractorStep() + case SymbolAndTypeBound(sym, tpe) => typeTestStep(sym, tpe) + case TypeBound(tpe) => typeTestStep(binder, tpe) + case SymbolBound(sym, expr) => bindingStep(sym, expr) + case WildcardPattern() => noStep() + case ConstantPattern(const) => equalityTestStep(binder, binder, const) + case Alternative(alts) => alternativesStep(alts) + case _ => ctx.error(unsupportedPatternMsg, pos) ; noStep() + } + def translate(): List[TreeMaker] = nextStep() merge (_.translate()) + + private def concreteType = tpe.bounds.hi + private def unbound = unbind(tree) + private def tpe_s = if (pt <:< concreteType) "" + pt else s"$pt (binder: $tpe)" + private def at_s = unbound match { + case WildcardPattern() => "" + case pat => s" @ $pat" + } + override def toString = s"${binder.name}: $tpe_s$at_s" + } + + // a list of TreeMakers that encode `patTree`, and a list of arguments for recursive invocations of `translatePattern` to encode its subpatterns + final case class TranslationStep(makers: List[TreeMaker], subpatterns: List[BoundTree]) { + def merge(f: BoundTree => List[TreeMaker]): List[TreeMaker] = makers ::: (subpatterns flatMap f) + override def toString = if (subpatterns.isEmpty) "" else subpatterns.mkString("(", ", ", ")") + } + + def isSyntheticDefaultCase(cdef: CaseDef) = cdef match { + case CaseDef(Bind(nme.DEFAULT_CASE, _), EmptyTree, _) => true + case _ => false + } + + /** Implement a pattern match by turning its cases (including the implicit failure case) + * into the corresponding (monadic) extractors, and combining them with the `orElse` combinator. + * + * For `scrutinee match { case1 ... caseN }`, the resulting tree has the shape + * `runOrElse(scrutinee)(x => translateCase1(x).orElse(translateCase2(x)).....orElse(zero))` + * + * NOTE: the resulting tree is not type checked, nor are nested pattern matches transformed + * thus, you must typecheck the result (and that will in turn translate nested matches) + * this could probably be optimized... (but note that the matchStrategy must be solved for each nested patternmatch) + */ + def translateMatch(match_ : Match): Tree = { + val Match(sel, cases) = match_ + + val selectorTp = sel.tpe.widen.deAnonymize/*withoutAnnotations*/ + + val selectorSym = freshSym(sel.pos, selectorTp, PatMatSelectorName) + + val (nonSyntheticCases, defaultOverride) = cases match { + case init :+ last if isSyntheticDefaultCase(last) => (init, Some(((scrut: Symbol) => last.body))) + case _ => (cases, None) + } + + + // checkMatchVariablePatterns(nonSyntheticCases) // only used for warnings + + // we don't transform after uncurry + // (that would require more sophistication when generating trees, + // and the only place that emits Matches after typers is for exception handling anyway) + /*if (phase.id >= currentRun.uncurryPhase.id) + devWarning(s"running translateMatch past uncurry (at $phase) on $selector match $cases")*/ + + ctx.debuglog("translating " + cases.mkString("{", "\n", "}")) + + //val start = if (Statistics.canEnable) Statistics.startTimer(patmatNanos) else null + + // when one of the internal cps-type-state annotations is present, strip all CPS annotations + ///val origPt = removeCPSFromPt(match_.tpe) + // relevant test cases: pos/existentials-harmful.scala, pos/gadt-gilles.scala, pos/t2683.scala, pos/virtpatmat_exist4.scala + // pt is the skolemized version + val pt = match_.tpe.widen //repeatedToSeq(origPt) + + // val packedPt = repeatedToSeq(typer.packedType(match_, context.owner)) + selectorSym.setFlag(Flags.SyntheticCase) + + // pt = Any* occurs when compiling test/files/pos/annotDepMethType.scala with -Xexperimental + val combined = combineCases(sel, selectorSym, nonSyntheticCases map translateCase(selectorSym, pt), pt, ctx.owner, defaultOverride) + + // if (Statistics.canEnable) Statistics.stopTimer(patmatNanos, start) + Block(List(ValDef(selectorSym, sel)), combined) + } + + /** The translation of `pat if guard => body` has two aspects: + * 1) the substitution due to the variables bound by patterns + * 2) the combination of the extractor calls using `flatMap`. + * + * 2) is easy -- it looks like: `translatePattern_1.flatMap(translatePattern_2....flatMap(translatePattern_N.flatMap(translateGuard.flatMap((x_i) => success(Xbody(x_i)))))...)` + * this must be right-leaning tree, as can be seen intuitively by considering the scope of bound variables: + * variables bound by pat_1 must be visible from the function inside the left-most flatMap right up to Xbody all the way on the right + * 1) is tricky because translatePattern_i determines the shape of translatePattern_i + 1: + * zoom in on `translatePattern_1.flatMap(translatePattern_2)` for example -- it actually looks more like: + * `translatePattern_1(x_scrut).flatMap((x_1) => {y_i -> x_1._i}translatePattern_2)` + * + * `x_1` references the result (inside the monad) of the extractor corresponding to `pat_1`, + * this result holds the values for the constructor arguments, which translatePattern_1 has extracted + * from the object pointed to by `x_scrut`. The `y_i` are the symbols bound by `pat_1` (in order) + * in the scope of the remainder of the pattern, and they must thus be replaced by: + * - (for 1-ary unapply) x_1 + * - (for n-ary unapply, n > 1) selection of the i'th tuple component of `x_1` + * - (for unapplySeq) x_1.apply(i) + * + * in the treemakers, + * + * Thus, the result type of `translatePattern_i`'s extractor must conform to `M[(T_1,..., T_n)]`. + * + * Operationally, phase 1) is a foldLeft, since we must consider the depth-first-flattening of + * the transformed patterns from left to right. For every pattern ast node, it produces a transformed ast and + * a function that will take care of binding and substitution of the next ast (to the right). + * + */ + def translateCase(scrutSym: Symbol, pt: Type)(caseDef: CaseDef): List[TreeMaker] = { + val CaseDef(pattern, guard, body) = caseDef + translatePattern(BoundTree(scrutSym, pattern)) ++ translateGuard(guard) :+ translateBody(body, pt) + } + + def translatePattern(bound: BoundTree): List[TreeMaker] = bound.translate() + + def translateGuard(guard: Tree): List[TreeMaker] = + if (guard == EmptyTree) Nil + else List(GuardTreeMaker(guard)) + + // TODO: 1) if we want to support a generalisation of Kotlin's patmat continue, must not hard-wire lifting into the monad (which is now done by codegen.one), + // so that user can generate failure when needed -- use implicit conversion to lift into monad on-demand? + // to enable this, probably need to move away from Option to a monad specific to pattern-match, + // so that we can return Option's from a match without ambiguity whether this indicates failure in the monad, or just some result in the monad + // 2) body.tpe is the type of the body after applying the substitution that represents the solution of GADT type inference + // need the explicit cast in case our substitutions in the body change the type to something that doesn't take GADT typing into account + def translateBody(body: Tree, matchPt: Type): TreeMaker = + BodyTreeMaker(body, matchPt) + + // Some notes from the specification + + /*A constructor pattern is of the form c(p1, ..., pn) where n ≥ 0. + It consists of a stable identifier c, followed by element patterns p1, ..., pn. + The constructor c is a simple or qualified name which denotes a case class (§5.3.2). + + If the case class is monomorphic, then it must conform to the expected type of the pattern, + and the formal parameter types of x’s primary constructor (§5.3) are taken as the expected + types of the element patterns p1, ..., pn. + + If the case class is polymorphic, then its type parameters are instantiated so that the + instantiation of c conforms to the expected type of the pattern. + The instantiated formal parameter types of c’s primary constructor are then taken as the + expected types of the component patterns p1, ..., pn. + + The pattern matches all objects created from constructor invocations c(v1, ..., vn) + where each element pattern pi matches the corresponding value vi . + A special case arises when c’s formal parameter types end in a repeated parameter. + This is further discussed in (§8.1.9). + **/ + + /* A typed pattern x : T consists of a pattern variable x and a type pattern T. + The type of x is the type pattern T, where each type variable and wildcard is replaced by a fresh, unknown type. + This pattern matches any value matched by the type pattern T (§8.2); it binds the variable name to that value. + */ + + /* A pattern binder x@p consists of a pattern variable x and a pattern p. + The type of the variable x is the static type T of the pattern p. + This pattern matches any value v matched by the pattern p, + provided the run-time type of v is also an instance of T, <-- TODO! https://issues.scala-lang.org/browse/SI-1503 + and it binds the variable name to that value. + */ + + /* 8.1.4 Literal Patterns + A literal pattern L matches any value that is equal (in terms of ==) to the literal L. + The type of L must conform to the expected type of the pattern. + + 8.1.5 Stable Identifier Patterns (a stable identifier r (see §3.1)) + The pattern matches any value v such that r == v (§12.1). + The type of r must conform to the expected type of the pattern. + */ + + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // helper methods: they analyze types and trees in isolation, but they are not (directly) concerned with the structure of the overall translation + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + object ExtractorCall { + // TODO: check unargs == args + def apply(tree: Tree, binder: Symbol): ExtractorCall = { + tree match { + case Typed(unapply, _) => apply(unapply, binder) + case UnApply(unfun, implicits, args) => + val mt @ MethodType(_) = unfun.tpe.widen + val castedBinder = ref(binder).ensureConforms(mt.paramInfos.head) + var synth = unfun.appliedTo(castedBinder) + if (implicits.nonEmpty) synth = synth.appliedToArgs(implicits) + new ExtractorCallRegular(alignPatterns(tree, synth.tpe), synth, args, synth.tpe) + } + } + } + + abstract class ExtractorCall(val aligner: PatternAligned) { + + import aligner._ + + def args: List[Tree] + + // don't go looking for selectors if we only expect one pattern + def rawSubPatTypes = aligner.extractedTypes + + def typeArgOfBaseTypeOr(tp: Type, baseClass: Symbol)(or: => Type): Type = (tp.baseTypeWithArgs(baseClass)).argInfos match { + case x :: Nil => x + case _ => or + } + + def resultInMonad = + if (aligner.isBool) defn.UnitType + else if (isProductMatch(resultType, aligner.prodArity)) resultType + else if (isGetMatch(resultType)) extractorMemberType(resultType, nme.get) + else resultType + + def resultType: Type + + /** Create the TreeMaker that embodies this extractor call + * + * `binder` has been casted to `paramType` if necessary + * `binderKnownNonNull` indicates whether the cast implies `binder` cannot be null + * when `binderKnownNonNull` is `true`, `ProductExtractorTreeMaker` does not do a (redundant) null check on binder + */ + def treeMaker(binder: Symbol, binderKnownNonNull: Boolean, pos: Position, binderTypeTested: Type): TreeMaker + + // `subPatBinders` are the variables bound by this pattern in the following patterns + // subPatBinders are replaced by references to the relevant part of the extractor's result (tuple component, seq element, the result as-is) + // must set infos to `subPatTypes`, which are provided by extractor's result, + // as b.info may be based on a Typed type ascription, which has not been taken into account yet by the translation + // (it will later result in a type test when `tp` is not a subtype of `b.info`) + // TODO: can we simplify this, together with the Bound case? + def subPatBinders = subBoundTrees map (_.binder) + lazy val subBoundTrees = (args, subPatTypes).zipped map newBoundTree + + // never store these in local variables (for PreserveSubPatBinders) + lazy val ignoredSubPatBinders: Set[Symbol] = subPatBinders zip args collect { case (b, PatternBoundToUnderscore()) => b } toSet + + // do repeated-parameter expansion to match up with the expected number of arguments (in casu, subpatterns) + private def nonStarSubPatTypes = aligner.typedNonStarPatterns map (_.tpe) + + def subPatTypes: List[Type] = typedPatterns map (_.tpe) + + // there are `prodArity` non-seq elements in the tuple. + protected def firstIndexingBinder = prodArity + protected def expectedLength = elementArity + protected def lastIndexingBinder = totalArity - starArity - 1 + + private def productElemsToN(binder: Symbol, n: Int): List[Tree] = 1 to n map tupleSel(binder) toList + private def genTake(binder: Symbol, n: Int): List[Tree] = (0 until n).toList map (codegen index seqTree(binder)) + private def genDrop(binder: Symbol, n: Int): List[Tree] = codegen.drop(seqTree(binder))(expectedLength) :: Nil + + // codegen.drop(seqTree(binder))(nbIndexingIndices)))).toList + protected def seqTree(binder: Symbol) = tupleSel(binder)(firstIndexingBinder + 1) + protected def tupleSel(binder: Symbol)(i: Int): Tree = { + val accessors = + if (defn.isProductSubType(binder.info)) + productSelectors(binder.info) + else binder.caseAccessors + val res = + if (accessors.isDefinedAt(i - 1)) ref(binder).select(accessors(i - 1).name) + else codegen.tupleSel(binder)(i) // this won't type check for case classes, as they do not inherit ProductN + val rsym = res.symbol // just for debugging + res + } + + // the trees that select the subpatterns on the extractor's result, + // referenced by `binder` + protected def subPatRefsSeq(binder: Symbol): List[Tree] = { + def lastTrees: List[Tree] = ( + if (!aligner.isStar) Nil + else if (expectedLength == 0) seqTree(binder) :: Nil + else genDrop(binder, expectedLength) + ) + // this error-condition has already been checked by checkStarPatOK: + // if (isSeq) assert(firstIndexingBinder + nbIndexingIndices + (if (lastIsStar) 1 else 0) == totalArity, "(resultInMonad, ts, subPatTypes, subPats)= " +(resultInMonad, ts, subPatTypes, subPats)) + + // [1] there are `firstIndexingBinder` non-seq tuple elements preceding the Seq + // [2] then we have to index the binder that represents the sequence for the remaining subpatterns, except for... + // [3] the last one -- if the last subpattern is a sequence wildcard: + // drop the prefix (indexed by the refs on the preceding line), return the remainder + ( productElemsToN(binder, firstIndexingBinder) + ++ genTake(binder, expectedLength) + ++ lastTrees + ).toList + } + + // the trees that select the subpatterns on the extractor's result, referenced by `binder` + // require (nbSubPats > 0 && (!lastIsStar || isSeq)) + protected def subPatRefs(binder: Symbol): List[Tree] = { + val refs = if (totalArity > 0 && isSeq) subPatRefsSeq(binder) + else if (binder.info.member(nme._1).exists && !isSeq) productElemsToN(binder, totalArity) + else ref(binder) :: Nil + refs + } + + val mathSignymSymbol = defn.ScalaMathPackageVal.requiredMethod("signum".toTermName, List(defn.IntType)) + val mathSignum = ref(defn.ScalaMathPackageVal).select(mathSignymSymbol) + + + private def compareInts(t1: Tree, t2: Tree) = + mathSignum.appliedTo(t1.select(defn.Int_-).appliedTo(t2)) + //gen.mkMethodCall(termMember(ScalaPackage, "math"), TermName("signum"), Nil, (t1 INT_- t2) :: Nil) + + protected def lengthGuard(binder: Symbol): Option[Tree] = + // no need to check unless it's an unapplySeq and the minimal length is non-trivially satisfied + checkedLength map { expectedLength => + // `binder.lengthCompare(expectedLength)` + // ...if binder has a lengthCompare method, otherwise + // `scala.math.signum(binder.length - expectedLength)` + def checkExpectedLength: Tree = sequenceType.member(nme.lengthCompare) match { + case NoDenotation => compareInts(Select(seqTree(binder), nme.length), Literal(Constant(expectedLength))) + case x:SingleDenotation => (seqTree(binder).select(x.symbol)).appliedTo(Literal(Constant(expectedLength))) + case _ => + ctx.error("TODO: multiple lengthCompare") + EmptyTree + } + + // the comparison to perform + // when the last subpattern is a wildcard-star the expectedLength is but a lower bound + // (otherwise equality is required) + def compareOp: (Tree, Tree) => Tree = + if (aligner.isStar) _.select(defn.Int_>=).appliedTo(_) + else _.select(defn.Int_==).appliedTo(_) + + // `if (binder != null && $checkExpectedLength [== | >=] 0) then else zero` + (seqTree(binder).select(defn.Any_!=).appliedTo(Literal(Constant(null)))).select(defn.Boolean_&&).appliedTo(compareOp(checkExpectedLength, Literal(Constant(0)))) + } + + def checkedLength: Option[Int] = + // no need to check unless it's an unapplySeq and the minimal length is non-trivially satisfied + if (!isSeq || expectedLength < starArity) None + else Some(expectedLength) + } + + class ExtractorCallRegular(aligner: PatternAligned, extractorCallIncludingDummy: Tree, val args: List[Tree], val resultType: Type) extends ExtractorCall(aligner) { + + /** Create the TreeMaker that embodies this extractor call + * + * `binder` has been casted to `paramType` if necessary + * `binderKnownNonNull` is not used in this subclass + * + * TODO: implement review feedback by @retronym: + * Passing the pair of values around suggests: + * case class Binder(sym: Symbol, knownNotNull: Boolean). + * Perhaps it hasn't reached critical mass, but it would already clean things up a touch. + */ + def treeMaker(patBinderOrCasted: Symbol, binderKnownNonNull: Boolean, pos: Position, binderTypeTested: Type): TreeMaker = { + // the extractor call (applied to the binder bound by the flatMap corresponding + // to the previous (i.e., enclosing/outer) pattern) + val extractorApply = extractorCallIncludingDummy// spliceApply(patBinderOrCasted) + // can't simplify this when subPatBinders.isEmpty, since UnitTpe is definitely + // wrong when isSeq, and resultInMonad should always be correct since it comes + // directly from the extractor's result type + val binder = freshSym(pos, resultInMonad) + val spb = subPatBinders + ExtractorTreeMaker(extractorApply, lengthGuard(binder), binder)( + spb, + subPatRefs(binder, spb, resultType), + aligner.isBool, + checkedLength, + patBinderOrCasted, + ignoredSubPatBinders + ) + } + + override protected def seqTree(binder: Symbol): Tree = + if (firstIndexingBinder == 0) ref(binder) + else super.seqTree(binder) + + // the trees that select the subpatterns on the extractor's result, referenced by `binder` + // require (totalArity > 0 && (!lastIsStar || isSeq)) + protected def subPatRefs(binder: Symbol, subpatBinders: List[Symbol], binderTypeTested: Type): List[Tree] = { + if (aligner.isSingle && aligner.extractor.prodArity == 1 && defn.isTupleType(binder.info)) { + // special case for extractor + // comparing with scalac additional assertions added + val subpw = subpatBinders.head.info.widen + val binderw = binder.info.widen + val go = subpatBinders.head.info <:< binder.info + val go1 = binder.info <:< subpatBinders.head.info + //val spr = subPatRefs(binder) + assert(go && go1) + ref(binder) :: Nil + } + else if ((aligner.isSingle && aligner.extractor.prodArity == 1) && + !isProductMatch(binderTypeTested, aligner.prodArity) && isGetMatch(binderTypeTested)) + List(ref(binder)) + else + subPatRefs(binder) + } + + /*protected def spliceApply(binder: Symbol): Tree = { + object splice extends TreeMap { + def binderRef(pos: Position): Tree = + ref(binder) //setPos pos + + override def transform(t: tpd.Tree)(implicit ctx: Context): tpd.Tree = t match { + // duplicated with the extractor Unapplied + case Apply(x, List(i @ Ident(nme.SELECTOR_DUMMY))) => + cpy.Apply(t, x, binderRef(i.pos) :: Nil) + // SI-7868 Account for numeric widening, e.g. .toInt + case Apply(x, List(i @ (sel @ Select(Ident(nme.SELECTOR_DUMMY), name)))) => + cpy.Apply(t, x, cpy.Select(sel, binderRef(i.pos), name) :: Nil) + case _ => + super.transform(t) + } + } + splice transform extractorCallIncludingDummy + }*/ + + override def rawSubPatTypes = aligner.extractor.varargsTypes + } + } + + /** An extractor returns: F1, F2, ..., Fi, opt[Seq[E] or E*] + * A case matches: P1, P2, ..., Pj, opt[Seq[E]] + * Put together: P1/F1, P2/F2, ... Pi/Fi, Pi+1/E, Pi+2/E, ... Pj/E, opt[Seq[E]] + * + * Here Pm/Fi is the last pattern to match the fixed arity section. + * + * prodArity: the value of i, i.e. the number of non-sequence types in the extractor + * nonStarArity: the value of j, i.e. the number of non-star patterns in the case definition + * elementArity: j - i, i.e. the number of non-star patterns which must match sequence elements + * starArity: 1 or 0 based on whether there is a star (sequence-absorbing) pattern + * totalArity: nonStarArity + starArity, i.e. the number of patterns in the case definition + * + * Note that prodArity is a function only of the extractor, and + * nonStar/star/totalArity are all functions of the patterns. The key + * value for aligning and typing the patterns is elementArity, as it + * is derived from both sets of information. + */ + trait PatternExpander[Pattern, Type] { + /** You'll note we're not inside the cake. "Pattern" and "Type" are + * arbitrary types here, and NoPattern and NoType arbitrary values. + */ + def NoPattern: Pattern + def NoType: Type + + /** It's not optimal that we're carrying both sequence and repeated + * type here, but the implementation requires more unraveling before + * it can be avoided. + * + * sequenceType is Seq[T], elementType is T, repeatedType is T*. + */ + sealed case class Repeated(sequenceType: Type, elementType: Type, repeatedType: Type) { + def exists = elementType != NoType + + def elementList = if (exists) elementType :: Nil else Nil + def sequenceList = if (exists) sequenceType :: Nil else Nil + def repeatedList = if (exists) repeatedType :: Nil else Nil + + override def toString = s"${elementType}*" + } + object NoRepeated extends Repeated(NoType, NoType, NoType) { + override def toString = "" + } + + final case class Patterns(fixed: List[Pattern], star: Pattern) { + def hasStar = star != NoPattern + def starArity = if (hasStar) 1 else 0 + def nonStarArity = fixed.length + def totalArity = nonStarArity + starArity + def starPatterns = if (hasStar) star :: Nil else Nil + def all = fixed ::: starPatterns + + override def toString = all mkString ", " + } + + /** An 'extractor' can be a case class or an unapply or unapplySeq method. + * Decoding what it is that they extract takes place before we arrive here, + * so that this class can concentrate only on the relationship between + * patterns and types. + * + * In a case class, the class is the unextracted type and the fixed and + * repeated types are derived from its constructor parameters. + * + * In an unapply, this is reversed: the parameter to the unapply is the + * unextracted type, and the other types are derived based on the return + * type of the unapply method. + * + * In other words, this case class and unapply are encoded the same: + * + * case class Foo(x: Int, y: Int, zs: Char*) + * def unapplySeq(x: Foo): Option[(Int, Int, Seq[Char])] + * + * Both are Extractor(Foo, Int :: Int :: Nil, Repeated(Seq[Char], Char, Char*)) + * + * @param whole The type in its unextracted form + * @param fixed The non-sequence types which are extracted + * @param repeated The sequence type which is extracted + */ + final case class Extractor(whole: Type, fixed: List[Type], repeated: Repeated) { + require(whole != NoType, s"expandTypes($whole, $fixed, $repeated)") + + def prodArity = fixed.length + def hasSeq = repeated.exists + def elementType = repeated.elementType + def sequenceType = repeated.sequenceType + def allTypes = fixed ::: repeated.sequenceList + def varargsTypes = fixed ::: repeated.repeatedList + def isErroneous = allTypes contains NoType + + private def typeStrings = fixed.map("" + _) ::: ( if (hasSeq) List("" + repeated) else Nil ) + + def offeringString = if (isErroneous) "" else typeStrings match { + case Nil => "Boolean" + case tp :: Nil => tp + case tps => tps.mkString("(", ", ", ")") + } + override def toString = "%s => %s".format(whole, offeringString) + } + + final case class TypedPat(pat: Pattern, tpe: Type) { + override def toString = s"$pat: $tpe" + } + + /** If elementArity is... + * 0: A perfect match between extractor and the fixed patterns. + * If there is a star pattern it will match any sequence. + * > 0: There are more patterns than products. There will have to be a + * sequence which can populate at least patterns. + * < 0: There are more products than patterns: compile time error. + */ + final case class Aligned(patterns: Patterns, extractor: Extractor) { + def elementArity = patterns.nonStarArity - prodArity + def prodArity = extractor.prodArity + def starArity = patterns.starArity + def totalArity = patterns.totalArity + + def wholeType = extractor.whole + def sequenceType = extractor.sequenceType + def productTypes = extractor.fixed + def extractedTypes = extractor.allTypes + def typedNonStarPatterns = products ::: elements + def typedPatterns = typedNonStarPatterns ::: stars + + def isBool = !isSeq && prodArity == 0 + def isSingle = !isSeq && totalArity == 1 + def isStar = patterns.hasStar + def isSeq = extractor.hasSeq + + private def typedAsElement(pat: Pattern) = TypedPat(pat, extractor.elementType) + private def typedAsSequence(pat: Pattern) = TypedPat(pat, extractor.sequenceType) + private def productPats = patterns.fixed take prodArity + private def elementPats = patterns.fixed drop prodArity + private def products = (productPats, productTypes).zipped map TypedPat + private def elements = elementPats map typedAsElement + private def stars = patterns.starPatterns map typedAsSequence + + override def toString = s""" + |Aligned { + | patterns $patterns + | extractor $extractor + | arities $prodArity/$elementArity/$starArity // product/element/star + | typed ${typedPatterns mkString ", "} + |}""".stripMargin.trim + } + } + + /** This is scalac-specific logic layered on top of the scalac-agnostic + * "matching products to patterns" logic defined in PatternExpander. + */ + trait ScalacPatternExpanders { + + type PatternAligned = ScalacPatternExpander#Aligned + + implicit class AlignedOps(val aligned: PatternAligned) { + import aligned._ + def expectedTypes = typedPatterns map (_.tpe) + def unexpandedFormals = extractor.varargsTypes + } + + trait ScalacPatternExpander extends PatternExpander[Tree, Type] { + def NoPattern = EmptyTree + def NoType = core.Types.NoType + + def newPatterns(patterns: List[Tree]): Patterns = patterns match { + case init :+ last if tpd.isWildcardStarArg(last) => Patterns(init, last) + case _ => Patterns(patterns, NoPattern) + } + def typeOfMemberNamedHead(tpe: Type): Type = tpe.select(nme.head) + def typeOfMemberNamedApply(tpe: Type): Type = tpe.select(nme.apply) + + def elementTypeOf(tpe: Type) = { + val seq = tpe //repeatedToSeq(tpe) + + ( typeOfMemberNamedHead(seq) + orElse typeOfMemberNamedApply(seq) + orElse seq.elemType + ) + } + def newExtractor(whole: Type, fixed: List[Type], repeated: Repeated): Extractor = { + ctx.log(s"newExtractor($whole, $fixed, $repeated") + Extractor(whole, fixed, repeated) + } + + // Turn Seq[A] into Repeated(Seq[A], A, A*) + def repeatedFromSeq(seqType: Type): Repeated = { + val elem = elementTypeOf(seqType) + val repeated = /*scalaRepeatedType(*/elem//) + + Repeated(seqType, elem, repeated) + } + // Turn A* into Repeated(Seq[A], A, A*) + def repeatedFromVarargs(repeated: Type): Repeated = + //Repeated(repeatedToSeq(repeated), repeatedToSingle(repeated), repeated) + Repeated(repeated, repeated.elemType, repeated) + + /** In this case we are basing the pattern expansion on a case class constructor. + * The argument is the MethodType carried by the primary constructor. + */ + def applyMethodTypes(method: Type): Extractor = { + val whole = method.finalResultType + + method.paramInfoss.head match { + case init :+ last if last.isRepeatedParam => newExtractor(whole, init, repeatedFromVarargs(last)) + case tps => newExtractor(whole, tps, NoRepeated) + } + } + + def hasSelectors(tpe: Type) = tpe.member(nme._1).exists && tpe.member(nme._2).exists // dd todo: ??? + + + /** In this case, expansion is based on an unapply or unapplySeq method. + * Unfortunately the MethodType does not carry the information of whether + * it was unapplySeq, so we have to funnel that information in separately. + */ + def unapplyMethodTypes(tree: Tree, fun: Tree, args: List[Tree], resultType: Type, isSeq: Boolean): Extractor = { + _id = _id + 1 + + val whole = tree.tpe // see scaladoc for Trees.Unapply + // fun.tpe.widen.paramTypess.headOption.flatMap(_.headOption).getOrElse(NoType)//firstParamType(method) + val resultOfGet = extractorMemberType(resultType, nme.get) + + val expanded: List[Type] = /*( + if (result =:= defn.BooleanType) Nil + else if (defn.isProductSubType(result)) productSelectorTypes(result) + else if (result.classSymbol is Flags.CaseClass) result.decls.filter(x => x.is(Flags.CaseAccessor) && x.is(Flags.Method)).map(_.info).toList + else result.select(nme.get) :: Nil + )*/ + if (isProductMatch(resultType, args.length)) productSelectorTypes(resultType) + else if (isGetMatch(resultType)) getUnapplySelectors(resultOfGet, args) + else if (resultType isRef defn.BooleanClass) Nil + else { + ctx.error(i"invalid return type in Unapply node: $resultType") + Nil + } + + expanded match { + case init :+ last if isSeq => newExtractor(whole, init, repeatedFromSeq(last)) + case tps => newExtractor(whole, tps, NoRepeated) + } + } + } + + object alignPatterns extends ScalacPatternExpander { + private def validateAligned(tree: Tree, aligned: Aligned): Aligned = { + import aligned._ + + def owner = tree.symbol.owner + def offering = extractor.offeringString + def symString = tree.symbol.showLocated + def offerString = if (extractor.isErroneous) "" else s" offering $offering" + def arityExpected = (if (extractor.hasSeq) "at least " else "") + prodArity + + def err(msg: String) = ctx.error(msg, tree.pos) + def warn(msg: String) = ctx.warning(msg, tree.pos) + def arityError(what: String) = err(s"${_id} $what patterns for $owner$offerString: expected $arityExpected, found $totalArity") + + if (isStar && !isSeq) + err("Star pattern must correspond with varargs or unapplySeq") + else if (elementArity < 0) + arityError("not enough") + else if (elementArity > 0 && !extractor.hasSeq) + arityError("too many") + + aligned + } + + object Applied { + // Duplicated with `spliceApply` + def unapply(tree: Tree): Option[Tree] = tree match { + // SI-7868 Admit Select() to account for numeric widening, e.g. .toInt + /*case Apply(fun, (Ident(nme.SELECTOR_DUMMY)| Select(Ident(nme.SELECTOR_DUMMY), _)) :: Nil) + => Some(fun)*/ + case Apply(fun, _) => unapply(fun) + case _ => None + } + } + + def apply(tree: Tree, sel: Tree, args: List[Tree], resultType: Type): Aligned = { + val fn = sel match { + case Applied(fn) => fn + case _ => sel + } + val patterns = newPatterns(args) + val isSeq = sel.symbol.name == nme.unapplySeq + val extractor = sel.symbol.name match { + case nme.unapply => unapplyMethodTypes(tree, /*fn*/sel, args, resultType, isSeq = false) + case nme.unapplySeq => unapplyMethodTypes(tree, /*fn*/sel, args, resultType, isSeq = true) + case _ => applyMethodTypes(/*fn*/sel.tpe) + } + + validateAligned(fn, Aligned(patterns, extractor)) + } + + def apply(tree: Tree, resultType: Type): Aligned = tree match { + case Typed(tree, _) => apply(tree, resultType) + case Apply(fn, args) => apply(tree, fn, args, resultType) + case UnApply(fn, implicits, args) => apply(tree, fn, args, resultType) + } + } + } + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala b/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala index 4a3f69f24028..825b2ff43e88 100644 --- a/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala +++ b/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala @@ -12,7 +12,7 @@ import SymDenotations._, Symbols._, StdNames._, Annotations._, Trees._, Scopes._ import util.Positions._ import Decorators._ import NameKinds.{ProtectedAccessorName, ProtectedSetterName, OuterSelectName, SuperAccessorName} -import Symbols._, TypeUtils._ +import Symbols._, TypeUtils._, SymUtils._ /** This class performs the following functions: * @@ -137,15 +137,11 @@ class SuperAccessors(thisTransformer: DenotTransformer) { /** Disallow some super.XX calls targeting Any methods which would * otherwise lead to either a compiler crash or runtime failure. */ - private def isDisallowed(sym: Symbol)(implicit ctx: Context) = { - val d = defn - import d._ - (sym eq Any_isInstanceOf) || - (sym eq Any_asInstanceOf) || - (sym eq Any_==) || - (sym eq Any_!=) || - (sym eq Any_##) - } + private def isDisallowed(sym: Symbol)(implicit ctx: Context) = + sym.isTypeTestOrCast || + (sym eq defn.Any_==) || + (sym eq defn.Any_!=) || + (sym eq defn.Any_##) /** Replace `sel` (or `sel[targs]` if `targs` is nonempty) with a protected accessor * call, if necessary. diff --git a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala index b6ae97450aae..319cb49d2008 100644 --- a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala +++ b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala @@ -44,8 +44,11 @@ class SymUtils(val self: Symbol) extends AnyVal { else directlyInheritedTraits } + def isTypeTest(implicit ctx: Context): Boolean = + self == defn.Any_isInstanceOf || self == defn.Any_typeTest + def isTypeTestOrCast(implicit ctx: Context): Boolean = - self == defn.Any_asInstanceOf || self == defn.Any_isInstanceOf + self == defn.Any_asInstanceOf || isTypeTest def isVolatile(implicit ctx: Context) = self.hasAnnotation(defn.VolatileAnnot) diff --git a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala index e79b56b94771..b5a1b4170421 100644 --- a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala +++ b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala @@ -7,6 +7,7 @@ import ast.Trees._ import Erasure.Boxing._ import TypeErasure._ import ValueClasses._ +import SymUtils._ import core.Flags._ import util.Positions._ @@ -22,7 +23,7 @@ import util.Positions._ * Unfortunately this phase ended up being not Y-checkable unless types are erased. A cast to an ConstantType(3) or x.type * cannot be rewritten before erasure. */ -trait TypeTestsCasts { +object TypeTestsCasts { import ast.tpd._ def interceptTypeApply(tree: TypeApply)(implicit ctx: Context): Tree = ctx.traceIndented(s"transforming ${tree.show}", show = true) { @@ -132,9 +133,7 @@ trait TypeTestsCasts { */ def transformTypeTest(expr: Tree, testType: Type, flagUnrelated: Boolean): Tree = testType.dealias match { case _: SingletonType => - val cmpOp = - if (testType derivesFrom defn.AnyValClass) defn.Any_equals else defn.Object_eq - expr.select(cmpOp).appliedTo(singleton(testType)) + expr.isInstance(testType).withPos(tree.pos) case OrType(tp1, tp2) => evalOnce(expr) { e => transformTypeTest(e, tp1, flagUnrelated = false) @@ -157,7 +156,7 @@ trait TypeTestsCasts { transformIsInstanceOf(expr, erasure(testType), flagUnrelated) } - if ((sym eq defn.Any_isInstanceOf) || (sym eq defn.Any_typeTest)) + if (sym.isTypeTest) transformTypeTest(expr, tree.args.head.tpe, flagUnrelated = true) else if (sym eq defn.Any_asInstanceOf) transformAsInstanceOf(erasure(tree.args.head.tpe)) diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/Simplify.scala b/compiler/src/dotty/tools/dotc/transform/localopt/Simplify.scala index 5d17bf8608a5..da1ad6498408 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/Simplify.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/Simplify.scala @@ -41,7 +41,7 @@ class Simplify extends MiniPhaseTransform with IdentityDenotTransformer { new InlineCaseIntrinsics(this) :: new RemoveUnnecessaryNullChecks :: new InlineOptions :: - new InlineLabelsCalledOnce :: + //new InlineLabelsCalledOnce :: // not needed: new pattern matcher does this already new Valify(this) :: new Devalify :: new Jumpjump :: diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index f4f1e57875cc..724da17863bb 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -33,13 +33,8 @@ import reporting.diagnostic.Message object Applications { import tpd._ - def extractorMember(tp: Type, name: Name)(implicit ctx: Context) = { - def isPossibleExtractorType(tp: Type) = tp match { - case _: MethodOrPoly => false - case _ => true - } - tp.member(name).suchThat(d => isPossibleExtractorType(d.info)) - } + def extractorMember(tp: Type, name: Name)(implicit ctx: Context) = + tp.member(name).suchThat(_.info.isParameterless) def extractorMemberType(tp: Type, name: Name, errorPos: Position = NoPosition)(implicit ctx: Context) = { val ref = extractorMember(tp, name) @@ -72,7 +67,8 @@ object Applications { if (defn.isProductSubType(tp)) productSelectorTypes(tp).size else -1 def productSelectors(tp: Type)(implicit ctx: Context): List[Symbol] = { - val sels = for (n <- Iterator.from(0)) yield tp.member(nme.selectorName(n)).symbol + val sels = for (n <- Iterator.from(0)) yield + tp.member(nme.selectorName(n)).suchThat(_.info.isParameterless).symbol sels.takeWhile(_.exists).toList } @@ -292,7 +288,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => def handlePositional(pnames: List[Name], args: List[Trees.Tree[T]]): List[Trees.Tree[T]] = args match { - case (arg: NamedArg) :: _ => + case (arg: NamedArg @unchecked) :: _ => val nameAssocs = for (arg @ NamedArg(name, _) <- args) yield (name, arg) handleNamed(pnames, args, nameAssocs.toMap, Set()) case arg :: args1 => arg :: handlePositional(pnames.tail, args1) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 201af58062d6..c98e231d434e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -15,7 +15,7 @@ import StdNames.nme import Contexts.Context import Names.{Name, TermName, EmptyTermName} import NameOps._ -import NameKinds.{InlineAccessorName, OuterSelectName} +import NameKinds.InlineAccessorName import SymDenotations.SymDenotation import Annotations._ import transform.ExplicitOuter @@ -412,7 +412,7 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { lazy val rhsClsSym = selfSym.info.widenDealias.classSymbol val rhs = if (lastSelf.exists) - untpd.Select(ref(lastSelf), OuterSelectName(EmptyTermName, lastLevel - level)).withType(selfSym.info) + ref(lastSelf).outerSelect(lastLevel - level, selfSym.info) else if (rhsClsSym.is(Module)) ref(rhsClsSym.sourceModule) else diff --git a/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala b/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala index ce71ef3cbc1f..4c21758c0c70 100644 --- a/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala +++ b/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala @@ -33,6 +33,7 @@ class TestBCode extends DottyBytecodeTest { | def foo(i: Int) = i match { | case 2 => println(2) | case 1 => println(1) + | case 0 => println(0) | } |}""".stripMargin @@ -54,6 +55,7 @@ class TestBCode extends DottyBytecodeTest { | def foo(i: Int) = (i: @switch) match { | case 2 => println(2) | case 1 => println(1) + | case 0 => println(0) | } |}""".stripMargin diff --git a/tests/pos/patmat.scala b/tests/pos/patmat.scala new file mode 100644 index 000000000000..87b5e2ef372b --- /dev/null +++ b/tests/pos/patmat.scala @@ -0,0 +1,50 @@ +object Test { + + val xs = List(1, 2, 3) + + xs match { + case x :: xs1 => println(x); println(xs1) + case Nil => println("none") + } + + xs.length match { + case 0 => println("0") + case 1 => println("1") + case 2 => println("2") + case 3 => println("3") + case 4 => println("4") + case _ => println("something else") + } + + (xs.length, xs) match { + case (0, Nil) => println("1") + case (_, Nil) => println("2") + case (0, _) => println("3") + case (x, y) => println("4") + } + + xs match { + case 0 :: Nil => println("1") + case _ :: Nil => println("2") + case 0 :: _ => println("3") + case x :: y => println("4") + } + + (xs.length, xs) match { + case (_, Nil) | (0, _) => println("1 or 2") + case (x, y) => println("4") + } + + enum Option[+T] { + case Some[+T](value: T) + case None + } + import Option._ + + val x: Option[String] = Some("abc") + + x match { + case Some(s) => println(s) + case None => println("nothing") + } +} diff --git a/tests/pos/patmatSeq.scala b/tests/pos/patmatSeq.scala new file mode 100644 index 000000000000..449ed0821aee --- /dev/null +++ b/tests/pos/patmatSeq.scala @@ -0,0 +1,11 @@ +object Test { + + val xs = List(1, 2, 3) + + (xs: Any) match { + case Seq(x, y) => println(s"$x, $y") + case Seq(x: _*) => println(s"other sequence") + case _ => println("None") + } + +} diff --git a/tests/run/nullInstanceEval.scala b/tests/run/nullInstanceEval.scala new file mode 100644 index 000000000000..47b7153eccf4 --- /dev/null +++ b/tests/run/nullInstanceEval.scala @@ -0,0 +1,5 @@ +object Test extends App { + val x = null + assert(!x.isInstanceOf[String]) + assert(!(x: AnyRef).isInstanceOf[String]) +} diff --git a/tests/run/reducable.scala b/tests/run/reducable.scala new file mode 100644 index 000000000000..d3c859d84979 --- /dev/null +++ b/tests/run/reducable.scala @@ -0,0 +1,25 @@ +object Test extends App { + val xs = List(1, 2, 3) + + object Cons { + var count = 0 + + def unapply[T](xs: List[T]): Option[(T, List[T])] = { + count += 1 + xs match { + case x :: xs1 => Some((x, xs1)) + case _ => None + } + } + } + + val res = xs match { + case Cons(0, Nil) => 1 + case Cons(_, Nil) => 2 + case Cons(0, _) => 3 + case Cons(1, ys) => 4 + } + + assert(res == 4, res) + assert(Cons.count ==1, Cons.count) +} diff --git a/tests/run/switches.scala b/tests/run/switches.scala new file mode 100644 index 000000000000..d9469a5d41ac --- /dev/null +++ b/tests/run/switches.scala @@ -0,0 +1,32 @@ +import annotation.switch +object Test extends App { + + val x = 3 + final val Y = 3 + + val x1 = x match { + case 0 => 0 + case 1 => 1 + case 2 => 2 + case Y => 3 + } + + val x2 = (x: @switch) match { + case 0 => 0 + case 1 | 2 => 2 + case Y => 3 + case _ => 4 + } + + val x3 = (x: @switch) match { + case '0' if x > 0 => 0 + case '1' => 1 + case '2' => 2 + case '3' => 3 + case x => 4 + } + + assert(x1 == 3) + assert(x2 == 3) + assert(x3 == 4) +}