diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index b6e730c5fd96..29096e44bc38 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -573,6 +573,11 @@ object Trees { s"TypeTree${if (hasType) s"[$typeOpt]" else ""}" } + /** A type tree that defines a new type variable. Its type is always a TypeVar. + * Every TypeVar is created as the type of one TypeVarBinder. + */ + class TypeVarBinder[-T >: Untyped] extends TypeTree[T] + /** ref.type */ case class SingletonTypeTree[-T >: Untyped] private[ast] (ref: Tree[T]) extends DenotingTree[T] with TypTree[T] { diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 8437db43fb3b..41d5dfc6d282 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -87,6 +87,7 @@ class ScalaSettings extends Settings.SettingGroup { val YdisableFlatCpCaching = BooleanSetting("-YdisableFlatCpCaching", "Do not cache flat classpath representation of classpath elements from jars across compiler instances.") val YnoImports = BooleanSetting("-Yno-imports", "Compile without importing scala.*, java.lang.*, or Predef.") + val YnoInline = BooleanSetting("-Yno-inline", "Suppress inlining.") val YnoGenericSig = BooleanSetting("-Yno-generic-signatures", "Suppress generation of generic signatures for Java.") val YnoPredef = BooleanSetting("-Yno-predef", "Compile without importing Predef.") val Yskip = PhasesSetting("-Yskip", "Skip") @@ -126,7 +127,7 @@ class ScalaSettings extends Settings.SettingGroup { val YexplainLowlevel = BooleanSetting("-Yexplain-lowlevel", "When explaining type errors, show types at a lower level.") val YnoDoubleBindings = BooleanSetting("-Yno-double-bindings", "Assert no namedtype is bound twice (should be enabled only if program is error-free).") val YshowVarBounds = BooleanSetting("-Yshow-var-bounds", "Print type variables with their bounds") - val YnoInline = BooleanSetting("-Yno-inline", "Suppress inlining.") + val YshowNoInline = BooleanSetting("-Yshow-no-inline", "Show inlined code without the 'inlined from' info") /** Linker specific flags */ val optimise = BooleanSetting("-optimise", "Generates faster bytecode by applying local optimisations to the .program") withAbbreviation "-optimize" diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index 11cf1449564a..1eb13bab1f6a 100644 --- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -86,6 +86,8 @@ trait ConstraintHandling { finally homogenizeArgs = saved } + private def location(implicit ctx: Context) = "" // i"in ${ctx.typerState.stateChainStr}" // use for debugging + protected def addUpperBound(param: TypeParamRef, bound: Type): Boolean = { def description = i"constraint $param <: $bound to\n$constraint" if (bound.isRef(defn.NothingClass) && ctx.typerState.isGlobalCommittable) { @@ -93,12 +95,12 @@ trait ConstraintHandling { if (Config.failOnInstantiationToNothing) assert(false, msg) else ctx.log(msg) } - constr.println(i"adding $description in ${ctx.typerState.hashesStr}") + constr.println(i"adding $description$location") val lower = constraint.lower(param) val res = addOneBound(param, bound, isUpper = true) && lower.forall(addOneBound(_, bound, isUpper = true)) - constr.println(i"added $description = $res in ${ctx.typerState.hashesStr}") + constr.println(i"added $description = $res$location") res } @@ -109,7 +111,7 @@ trait ConstraintHandling { val res = addOneBound(param, bound, isUpper = false) && upper.forall(addOneBound(_, bound, isUpper = false)) - constr.println(i"added $description = $res in ${ctx.typerState.hashesStr}") + constr.println(i"added $description = $res$location") res } @@ -122,12 +124,12 @@ trait ConstraintHandling { val up2 = p2 :: constraint.exclusiveUpper(p2, p1) val lo1 = constraint.nonParamBounds(p1).lo val hi2 = constraint.nonParamBounds(p2).hi - constr.println(i"adding $description down1 = $down1, up2 = $up2 ${ctx.typerState.hashesStr}") + constr.println(i"adding $description down1 = $down1, up2 = $up2$location") constraint = constraint.addLess(p1, p2) down1.forall(addOneBound(_, hi2, isUpper = true)) && up2.forall(addOneBound(_, lo1, isUpper = false)) } - constr.println(i"added $description = $res ${ctx.typerState.hashesStr}") + constr.println(i"added $description = $res$location") res } @@ -252,6 +254,9 @@ trait ConstraintHandling { case tp: SingletonType => true case AndType(tp1, tp2) => isMultiSingleton(tp1) | isMultiSingleton(tp2) case OrType(tp1, tp2) => isMultiSingleton(tp1) & isMultiSingleton(tp2) + case tp: TypeRef => isMultiSingleton(tp.info.hiBound) + case tp: TypeVar => isMultiSingleton(tp.underlying) + case tp: TypeParamRef => isMultiSingleton(bounds(tp).hi) case _ => false } def isFullyDefined(tp: Type): Boolean = tp match { diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index e9a91bec0cb4..c82b1c1e85ca 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -130,7 +130,7 @@ object Denotations { if ((cachedPrefix ne pre) || ctx.period != validAsSeenFrom) { cachedAsSeenFrom = computeAsSeenFrom(pre) cachedPrefix = pre - validAsSeenFrom = ctx.period + validAsSeenFrom = if (pre.isProvisional) Nowhere else ctx.period } cachedAsSeenFrom } else computeAsSeenFrom(pre) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 989a6ae7e60c..2611d96df182 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -1191,6 +1191,26 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { isSubRef(tp1, tp2) && isSubRef(tp2, tp1) } + /** If the range `tp1..tp2` consist of a single type, that type, otherwise NoType`. + * This is the case if `tp1 =:= tp2`, but also if `tp1 <:< tp2`, `tp1` is a singleton type, + * and `tp2` derives from `scala.Singleton` (or vice-versa). Examples of the latter case: + * + * "name".type .. Singleton + * "name".type .. String & Singleton + * Singleton .. "name".type + * String & Singleton .. "name".type + * + * All consist of the single type `"name".type`. + */ + def singletonInterval(tp1: Type, tp2: Type): Type = { + def isSingletonBounds(lo: Type, hi: Type) = + lo.isSingleton && hi.derivesFrom(defn.SingletonClass) && isSubTypeWhenFrozen(lo, hi) + if (isSameTypeWhenFrozen(tp1, tp2)) tp1 + else if (isSingletonBounds(tp1, tp2)) tp1 + else if (isSingletonBounds(tp2, tp1)) tp2 + else NoType + } + /** The greatest lower bound of two types */ def glb(tp1: Type, tp2: Type): Type = /*>|>*/ trace(s"glb(${tp1.show}, ${tp2.show})", subtyping, show = true) /*<|<*/ { if (tp1 eq tp2) tp1 @@ -1279,9 +1299,10 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { case tparam :: tparamsRest => val arg1 :: args1Rest = args1 val arg2 :: args2Rest = args2 + val common = singletonInterval(arg1, arg2) val v = tparam.paramVariance val lubArg = - if (isSameTypeWhenFrozen(arg1, arg2)) arg1 + if (common.exists) common else if (v > 0) lub(arg1.hiBound, arg2.hiBound, canConstrain) else if (v < 0) glb(arg1.loBound, arg2.loBound) else TypeBounds(glb(arg1.loBound, arg2.loBound), @@ -1310,9 +1331,10 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { case tparam :: tparamsRest => val arg1 :: args1Rest = args1 val arg2 :: args2Rest = args2 + val common = singletonInterval(arg1, arg2) val v = tparam.paramVariance val glbArg = - if (isSameTypeWhenFrozen(arg1, arg2)) arg1 + if (common.exists) common else if (v > 0) glb(arg1.hiBound, arg2.hiBound) else if (v < 0) lub(arg1.loBound, arg2.loBound) else if (arg1.isInstanceOf[TypeBounds] || arg2.isInstanceOf[TypeBounds]) diff --git a/compiler/src/dotty/tools/dotc/core/TyperState.scala b/compiler/src/dotty/tools/dotc/core/TyperState.scala index 1821735522f5..f54e6af62b4e 100644 --- a/compiler/src/dotty/tools/dotc/core/TyperState.scala +++ b/compiler/src/dotty/tools/dotc/core/TyperState.scala @@ -5,15 +5,23 @@ package core import Types._ import Flags._ import Contexts._ -import util.{SimpleIdentityMap, DotClass} +import util.{SimpleIdentityMap, SimpleIdentitySet, DotClass} import reporting._ import printing.{Showable, Printer} import printing.Texts._ import config.Config import collection.mutable import java.lang.ref.WeakReference +import Decorators._ -class TyperState(previous: TyperState /* | Null */) extends DotClass with Showable { +object TyperState { + @sharable private var nextId: Int = 0 +} + +class TyperState(previous: TyperState /* | Null */) { + + val id = TyperState.nextId + TyperState.nextId += 1 private[this] var myReporter = if (previous == null) new ConsoleReporter() else previous.reporter @@ -42,19 +50,6 @@ class TyperState(previous: TyperState /* | Null */) extends DotClass with Showab private val previousConstraint = if (previous == null) constraint else previous.constraint - private[this] var myEphemeral: Boolean = - if (previous == null) false else previous.ephemeral - - /** The ephemeral flag is set as a side effect if an operation accesses - * the underlying type of a type variable. The reason we need this flag is - * that any such operation is not referentially transparent; it might logically change - * its value at the moment the type variable is instantiated. Caching code needs to - * check the ephemeral flag; If the flag is set during an operation, the result - * of that operation should not be cached. - */ - def ephemeral = myEphemeral - def ephemeral_=(x: Boolean): Unit = { myEphemeral = x } - private[this] var myIsCommittable = true def isCommittable = myIsCommittable @@ -81,6 +76,11 @@ class TyperState(previous: TyperState /* | Null */) extends DotClass with Showab /** The uninstantiated variables */ def uninstVars = constraint.uninstVars + /** The set of uninstantiated type variables which have this state as their owning state */ + private[this] var myOwnedVars: TypeVars = SimpleIdentitySet.empty + def ownedVars = myOwnedVars + def ownedVars_=(vs: TypeVars): Unit = myOwnedVars = vs + /** Gives for each instantiated type var that does not yet have its `inst` field * set, the instance value stored in the constraint. Storing instances in constraints * is done only in a temporary way for contexts that may be retracted @@ -159,7 +159,7 @@ class TyperState(previous: TyperState /* | Null */) extends DotClass with Showab constraint foreachTypeVar { tvar => if (tvar.owningState.get eq this) tvar.owningState = new WeakReference(targetState) } - targetState.ephemeral |= ephemeral + targetState.ownedVars ++= ownedVars targetState.gc() reporter.flush() isCommitted = true @@ -185,8 +185,7 @@ class TyperState(previous: TyperState /* | Null */) extends DotClass with Showab constraint = constraint.remove(poly) } - override def toText(printer: Printer): Text = constraint.toText(printer) + override def toString: String = s"TS[$id]" - def hashesStr: String = - if (previous == null) "" else hashCode.toString + " -> " + previous.hashesStr + def stateChainStr: String = s"$this${if (previous == null) "" else previous.stateChainStr}" } diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 3675d6e8033c..67e429468f82 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -18,7 +18,7 @@ import Denotations._ import Periods._ import util.Positions.{Position, NoPosition} import util.Stats._ -import util.DotClass +import util.{DotClass, SimpleIdentitySet} import reporting.diagnostic.Message import reporting.diagnostic.messages.CyclicReferenceInvolving import ast.tpd._ @@ -96,6 +96,41 @@ object Types { nextId } + /** A cache indicating whether the type was still provisional, last time we checked */ + @sharable private var mightBeProvisional = true + + /** Is this type still provisional? This is the case if the type contains, or depends on, + * uninstantiated type variables or type symbols that have the Provisional flag set. + * This is an antimonotonic property - once a type is not provisional, it stays so forever. + */ + def isProvisional(implicit ctx: Context) = mightBeProvisional && testProvisional + + private def testProvisional(implicit ctx: Context) = { + val accu = new TypeAccumulator[Boolean] { + override def apply(x: Boolean, t: Type) = + x || t.mightBeProvisional && { + t.mightBeProvisional = t match { + case t: TypeVar => + !t.inst.exists || apply(x, t.inst) + case t: TypeRef => + (t: Type).mightBeProvisional = false // break cycles + t.symbol.is(Provisional) || + apply(x, t.prefix) || { + t.info match { + case TypeAlias(alias) => apply(x, alias) + case TypeBounds(lo, hi) => apply(apply(x, lo), hi) + case _ => false + } + } + case _ => + foldOver(x, t) + } + t.mightBeProvisional + } + } + accu.apply(false, this) + } + /** Is this type different from NoType? */ def exists: Boolean = true @@ -590,7 +625,6 @@ object Types { val next = tp.underlying ctx.typerState.constraint.entry(tp) match { case bounds: TypeBounds if bounds ne next => - ctx.typerState.ephemeral = true go(bounds.hi) case _ => go(next) @@ -949,25 +983,25 @@ object Types { this } - private def dealias(keepAnnots: Boolean)(implicit ctx: Context): Type = this match { + private def dealias1(keepAnnots: Boolean)(implicit ctx: Context): Type = this match { case tp: TypeRef => if (tp.symbol.isClass) tp else tp.info match { - case TypeAlias(tp) => tp.dealias(keepAnnots): @tailrec + case TypeAlias(tp) => tp.dealias1(keepAnnots): @tailrec case _ => tp } case app @ AppliedType(tycon, args) => - val tycon1 = tycon.dealias(keepAnnots) - if (tycon1 ne tycon) app.superType.dealias(keepAnnots): @tailrec + val tycon1 = tycon.dealias1(keepAnnots) + if (tycon1 ne tycon) app.superType.dealias1(keepAnnots): @tailrec else this case tp: TypeVar => val tp1 = tp.instanceOpt - if (tp1.exists) tp1.dealias(keepAnnots): @tailrec else tp + if (tp1.exists) tp1.dealias1(keepAnnots): @tailrec else tp case tp: AnnotatedType => - val tp1 = tp.tpe.dealias(keepAnnots) + val tp1 = tp.tpe.dealias1(keepAnnots) if (keepAnnots) tp.derivedAnnotatedType(tp1, tp.annot) else tp1 case tp: LazyRef => - tp.ref.dealias(keepAnnots): @tailrec + tp.ref.dealias1(keepAnnots): @tailrec case _ => this } @@ -976,14 +1010,14 @@ object Types { * Goes through annotated types and rewraps annotations on the result. */ final def dealiasKeepAnnots(implicit ctx: Context): Type = - dealias(keepAnnots = true) + dealias1(keepAnnots = true) /** Follow aliases and dereferences LazyRefs, annotated types and instantiated * TypeVars until type is no longer alias type, annotated type, LazyRef, * or instantiated type variable. */ final def dealias(implicit ctx: Context): Type = - dealias(keepAnnots = false) + dealias1(keepAnnots = false) /** Perform successive widenings and dealiasings until none can be applied anymore */ @tailrec final def widenDealias(implicit ctx: Context): Type = { @@ -1657,7 +1691,7 @@ object Types { * attempt in `denot` does not yield a denotation. */ private def denotAt(lastd: Denotation, now: Period)(implicit ctx: Context): Denotation = { - if (lastd != null && (lastd.validFor contains now)) { + if (checkedPeriod != Nowhere && lastd.validFor.contains(now)) { checkedPeriod = now lastd } @@ -1667,9 +1701,7 @@ object Types { private def computeDenot(implicit ctx: Context): Denotation = { def finish(d: Denotation) = { - if (ctx.typerState.ephemeral) - record("ephemeral cache miss: memberDenot") - else if (d.exists) + if (d.exists) // Avoid storing NoDenotations in the cache - we will not be able to recover from // them. The situation might arise that a type has NoDenotation in some later // phase but a defined denotation earlier (e.g. a TypeRef to an abstract type @@ -1696,23 +1728,19 @@ object Types { finish(symd.current) } - val savedEphemeral = ctx.typerState.ephemeral - ctx.typerState.ephemeral = false - try - lastDenotation match { - case lastd0: SingleDenotation => - val lastd = lastd0.skipRemoved - if (lastd.validFor.runId == ctx.runId) finish(lastd.current) - else lastd match { - case lastd: SymDenotation => - if (ctx.stillValid(lastd)) finish(lastd.current) - else finish(memberDenot(lastd.initial.name, allowPrivate = false)) - case _ => - fromDesignator - } - case _ => fromDesignator - } - finally ctx.typerState.ephemeral |= savedEphemeral + lastDenotation match { + case lastd0: SingleDenotation => + val lastd = lastd0.skipRemoved + if (lastd.validFor.runId == ctx.runId && (checkedPeriod != Nowhere)) finish(lastd.current) + else lastd match { + case lastd: SymDenotation => + if (ctx.stillValid(lastd) && (checkedPeriod != Nowhere)) finish(lastd.current) + else finish(memberDenot(lastd.initial.name, allowPrivate = false)) + case _ => + fromDesignator + } + case _ => fromDesignator + } } private def disambiguate(d: Denotation)(implicit ctx: Context): Denotation = @@ -1797,7 +1825,7 @@ object Types { lastDenotation = denot lastSymbol = denot.symbol - checkedPeriod = ctx.period + checkedPeriod = if (prefix.isProvisional) Nowhere else ctx.period designator match { case sym: Symbol if designator ne lastSymbol => designator = lastSymbol.asInstanceOf[Designator{ type ThisName = self.ThisName }] @@ -3138,20 +3166,13 @@ object Types { override def superType(implicit ctx: Context): Type = { if (ctx.period != validSuper) { - validSuper = ctx.period cachedSuper = tycon match { case tycon: HKTypeLambda => defn.AnyType - case tycon: TypeVar if !tycon.inst.exists => - // supertype not stable, since underlying might change - validSuper = Nowhere - tycon.underlying.applyIfParameterized(args) - case tycon: TypeRef if tycon.symbol.isClass => - tycon - case tycon: TypeProxy => - if (tycon.typeSymbol.is(Provisional)) validSuper = Nowhere - tycon.superType.applyIfParameterized(args) + case tycon: TypeRef if tycon.symbol.isClass => tycon + case tycon: TypeProxy => tycon.superType.applyIfParameterized(args) case _ => defn.AnyType } + validSuper = if (tycon.isProvisional) Nowhere else ctx.period } cachedSuper } @@ -3333,14 +3354,11 @@ object Types { * * @param origin The parameter that's tracked by the type variable. * @param creatorState The typer state in which the variable was created. - * @param bindingTree The TypeTree which introduces the type variable, or EmptyTree - * if the type variable does not correspond to a source term. - * @paran owner The current owner if the context where the variable was created. * * `owningTree` and `owner` are used to determine whether a type-variable can be instantiated * at some given point. See `Inferencing#interpolateUndetVars`. */ - final class TypeVar(val origin: TypeParamRef, creatorState: TyperState, var bindingTree: untpd.Tree, val owner: Symbol) extends CachedProxyType with ValueType { + final class TypeVar(val origin: TypeParamRef, creatorState: TyperState) extends CachedProxyType with ValueType { /** The permanent instance type of the variable, or NoType is none is given yet */ private[this] var myInst: Type = NoType @@ -3348,7 +3366,10 @@ object Types { private[core] def inst = myInst private[core] def inst_=(tp: Type) = { myInst = tp - if (tp.exists) owningState = null // no longer needed; null out to avoid a memory leak + if (tp.exists) { + owningState.get.ownedVars -= this + owningState = null // no longer needed; null out to avoid a memory leak + } } /** The state owning the variable. This is at first `creatorState`, but it can @@ -3360,10 +3381,7 @@ object Types { * uninstantiated */ def instanceOpt(implicit ctx: Context): Type = - if (inst.exists) inst else { - ctx.typerState.ephemeral = true - ctx.typerState.instType(this) - } + if (inst.exists) inst else ctx.typerState.instType(this) /** Is the variable already instantiated? */ def isInstantiated(implicit ctx: Context) = instanceOpt.exists @@ -3403,11 +3421,7 @@ object Types { /** If the variable is instantiated, its instance, otherwise its origin */ override def underlying(implicit ctx: Context): Type = { val inst = instanceOpt - if (inst.exists) inst - else { - ctx.typerState.ephemeral = true - origin - } + if (inst.exists) inst else origin } override def computeHash(bs: Binders): Int = identityHash(bs) @@ -3419,6 +3433,8 @@ object Types { } } + type TypeVars = SimpleIdentitySet[TypeVar] + // ------ ClassInfo, Type Bounds ------------------------------------------------------------ type TypeOrSymbol = AnyRef /* should be: Type | Symbol */ diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 719b9498671f..48bc992f8a47 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -359,7 +359,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { case SeqLiteral(elems, elemtpt) => "[" ~ toTextGlobal(elems, ",") ~ " : " ~ toText(elemtpt) ~ "]" case tree @ Inlined(call, bindings, body) => - (("/* inlined from " ~ toText(call) ~ "*/ ") provided !homogenizedView) ~ + (("/* inlined from " ~ toText(call) ~ "*/ ") provided !homogenizedView && !ctx.settings.YshowNoInline.value) ~ blockText(bindings :+ body) case tpt: untpd.DerivedTypeTree => "" diff --git a/compiler/src/dotty/tools/dotc/reporting/trace.scala b/compiler/src/dotty/tools/dotc/reporting/trace.scala index d0184c4c9e67..d42008500bbf 100644 --- a/compiler/src/dotty/tools/dotc/reporting/trace.scala +++ b/compiler/src/dotty/tools/dotc/reporting/trace.scala @@ -20,11 +20,18 @@ object trace { else op1 } + @inline + def apply[T](question: => String, printer: Printers.Printer, showOp: Any => String)(op: => T)(implicit ctx: Context): T = { + def op1 = op + if (!Config.tracingEnabled || printer.eq(config.Printers.noPrinter)) op1 + else doTrace[T](question, printer, showOp)(op1) + } + @inline def apply[T](question: => String, printer: Printers.Printer, show: Boolean)(op: => T)(implicit ctx: Context): T = { def op1 = op if (!Config.tracingEnabled || printer.eq(config.Printers.noPrinter)) op1 - else doTrace[T](question, printer, show)(op1) + else doTrace[T](question, printer, if (show) showShowable(_) else alwaysToString)(op1) } @inline @@ -39,16 +46,21 @@ object trace { def apply[T](question: => String)(op: => T)(implicit ctx: Context): T = apply[T](question, Printers.default, false)(op) - private def doTrace[T](question: => String, printer: Printers.Printer = Printers.default, show: Boolean = false) - (op: => T)(implicit ctx: Context): T = { - def resStr(res: Any): String = res match { - case res: printing.Showable if show => res.show - case _ => String.valueOf(res) - } + private def showShowable(x: Any)(implicit ctx: Context) = x match { + case x: printing.Showable => x.show + case _ => String.valueOf(x) + } + + private val alwaysToString = (x: Any) => String.valueOf(x) + + private def doTrace[T](question: => String, + printer: Printers.Printer = Printers.default, + showOp: Any => String = alwaysToString) + (op: => T)(implicit ctx: Context): T = { // Avoid evaluating question multiple time, since each evaluation // may cause some extra logging output. lazy val q: String = question - apply[T](s"==> $q?", (res: Any) => s"<== $q = ${resStr(res)}")(op) + apply[T](s"==> $q?", (res: Any) => s"<== $q = ${showOp(res)}")(op) } def apply[T](leading: => String, trailing: Any => String)(op: => T)(implicit ctx: Context): T = diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index 76e5dc4a3fc5..0f8c76b6cbe7 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -675,14 +675,16 @@ object Erasure { super.typedStats(stats1, exprOwner).filter(!_.isEmpty) } - override def adapt(tree: Tree, pt: Type)(implicit ctx: Context): Tree = + override def adapt(tree: Tree, pt: Type, locked: TypeVars)(implicit ctx: Context): Tree = trace(i"adapting ${tree.showSummary}: ${tree.tpe} to $pt", show = true) { - assert(ctx.phase == ctx.erasurePhase.next, ctx.phase) + assert(ctx.phase == ctx.erasurePhase || ctx.phase == ctx.erasurePhase.next, ctx.phase) if (tree.isEmpty) tree else if (ctx.mode is Mode.Pattern) tree // TODO: replace with assertion once pattern matcher is active else adaptToType(tree, pt) } - } + + override def simplify(tree: Tree, pt: Type, locked: TypeVars)(implicit ctx: Context): tree.type = tree +} def takesBridges(sym: Symbol)(implicit ctx: Context) = sym.isClass && !sym.is(Flags.Trait | Flags.Package) diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index 9e0639a944f6..6be342739f75 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -259,14 +259,14 @@ class TreeChecker extends Phase with SymTransformer { tpdTree } - override def typedUnadapted(tree: untpd.Tree, pt: Type)(implicit ctx: Context): tpd.Tree = { + override def typedUnadapted(tree: untpd.Tree, pt: Type, locked: TypeVars)(implicit ctx: Context): tpd.Tree = { val res = tree match { case _: untpd.TypedSplice | _: untpd.Thicket | _: EmptyValDef[_] => - super.typedUnadapted(tree) + super.typedUnadapted(tree, pt, locked) case _ if tree.isType => promote(tree) case _ => - val tree1 = super.typedUnadapted(tree, pt) + val tree1 = super.typedUnadapted(tree, pt, locked) def isSubType(tp1: Type, tp2: Type) = (tp1 eq tp2) || // accept NoType / NoType (tp1 <:< tp2) @@ -435,7 +435,7 @@ class TreeChecker extends Phase with SymTransformer { override def ensureNoLocalRefs(tree: Tree, pt: Type, localSyms: => List[Symbol])(implicit ctx: Context): Tree = tree - override def adapt(tree: Tree, pt: Type)(implicit ctx: Context) = { + override def adapt(tree: Tree, pt: Type, locked: TypeVars)(implicit ctx: Context) = { def isPrimaryConstructorReturn = ctx.owner.isPrimaryConstructor && pt.isRef(ctx.owner.owner) && tree.tpe.isRef(defn.UnitClass) if (ctx.mode.isExpr && @@ -449,6 +449,8 @@ class TreeChecker extends Phase with SymTransformer { }) tree } + + override def simplify(tree: Tree, pt: Type, locked: TypeVars)(implicit ctx: Context): tree.type = tree } /** diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 12808301f93b..fc2d4eae6422 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -546,7 +546,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => init() def addArg(arg: Tree, formal: Type): Unit = - typedArgBuf += adaptInterpolated(arg, formal.widenExpr) + typedArgBuf += adapt(arg, formal.widenExpr) def makeVarArg(n: Int, elemFormal: Type): Unit = { val args = typedArgBuf.takeRight(n).toList @@ -711,7 +711,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => * part. Return an optional value to indicate success. */ def tryWithImplicitOnQualifier(fun1: Tree, proto: FunProto)(implicit ctx: Context): Option[Tree] = - tryInsertImplicitOnQualifier(fun1, proto) flatMap { fun2 => + tryInsertImplicitOnQualifier(fun1, proto, ctx.typerState.ownedVars) flatMap { fun2 => tryEither { implicit ctx => Some(simpleApply(fun2, proto)): Option[Tree] } { @@ -1519,11 +1519,11 @@ trait Applications extends Compatibility { self: Typer with Dynamic => * If the resulting trees all have the same type, return them instead of the original ones. */ def harmonize(trees: List[Tree])(implicit ctx: Context): List[Tree] = { - def adapt(tree: Tree, pt: Type): Tree = tree match { - case cdef: CaseDef => tpd.cpy.CaseDef(cdef)(body = adapt(cdef.body, pt)) - case _ => adaptInterpolated(tree, pt) + def adaptDeep(tree: Tree, pt: Type): Tree = tree match { + case cdef: CaseDef => tpd.cpy.CaseDef(cdef)(body = adaptDeep(cdef.body, pt)) + case _ => adapt(tree, pt) } - if (ctx.isAfterTyper) trees else harmonizeWith(trees)(_.tpe, adapt) + if (ctx.isAfterTyper) trees else harmonizeWith(trees)(_.tpe, adaptDeep) } /** Apply a transformation `harmonize` on the results of operation `op`. diff --git a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala index adce2ad9a537..3b8f18001c15 100644 --- a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -120,9 +120,12 @@ object ErrorReporting { val found1 = dropJavaMethod(found) val expected1 = dropJavaMethod(expected) if ((found1 eq found) != (expected eq expected1) && (found1 <:< expected1)) - "\n(Note that Scala's and Java's representation of this type differs)" + i""" + |(Note that Scala's and Java's representation of this type differs)""" else if (ctx.settings.explainTypes.value) - "\n" + ctx.typerState.show + "\n" + TypeComparer.explained((found <:< expected)(_)) + i""" + |${ctx.typerState.constraint} + |${TypeComparer.explained((found <:< expected)(_))}""" else "" } diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 7bf765c21ae1..31f23a3327e5 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -224,14 +224,9 @@ object Implicits { } else if (ctx eq NoContext) Nil else { - val savedEphemeral = ctx.typerState.ephemeral - ctx.typerState.ephemeral = false - try { - val result = computeEligible(tp) - if (ctx.typerState.ephemeral) record("ephemeral cache miss: eligible") - else eligibleCache.put(tp, result) - result - } finally ctx.typerState.ephemeral |= savedEphemeral + val result = computeEligible(tp) + eligibleCache.put(tp, result) + result } } } @@ -470,27 +465,20 @@ trait ImplicitRunInfo { self: Run => * @param isLifted Type `tp` is the result of a `liftToClasses` application */ def iscope(tp: Type, isLifted: Boolean = false): OfTypeImplicits = { - val canCache = Config.cacheImplicitScopes && tp.hash != NotCached + val canCache = Config.cacheImplicitScopes && tp.hash != NotCached && !tp.isProvisional def computeIScope() = { - val savedEphemeral = ctx.typerState.ephemeral - ctx.typerState.ephemeral = false - try { - val liftedTp = if (isLifted) tp else liftToClasses(tp) - val refs = - if (liftedTp ne tp) - iscope(liftedTp, isLifted = true).companionRefs - else - collectCompanions(tp) - val result = new OfTypeImplicits(tp, refs)(ctx) - if (ctx.typerState.ephemeral) - record("ephemeral cache miss: implicitScope") - else if (canCache && - ((tp eq rootTp) || // first type traversed is always cached - !incomplete.contains(tp))) // other types are cached if they are not incomplete - implicitScopeCache(tp) = result - result - } - finally ctx.typerState.ephemeral |= savedEphemeral + val liftedTp = if (isLifted) tp else liftToClasses(tp) + val refs = + if (liftedTp ne tp) + iscope(liftedTp, isLifted = true).companionRefs + else + collectCompanions(tp) + val result = new OfTypeImplicits(tp, refs)(ctx) + if (canCache && + ((tp eq rootTp) || // first type traversed is always cached + !incomplete.contains(tp))) // other types are cached if they are not incomplete + implicitScopeCache(tp) = result + result } if (canCache) implicitScopeCache.getOrElse(tp, computeIScope()) else computeIScope() @@ -780,7 +768,7 @@ trait Implicits { self: Typer => case result: SearchSuccess => result.tstate.commit() implicits.println(i"success: $result") - implicits.println(i"committing ${result.tstate.constraint} yielding ${ctx.typerState.constraint} ${ctx.typerState.hashesStr}") + implicits.println(i"committing ${result.tstate.constraint} yielding ${ctx.typerState.constraint} in ${ctx.typerState}") result case result: SearchFailure if result.isAmbiguous => val deepPt = pt.deepenProto @@ -840,11 +828,12 @@ trait Implicits { self: Typer => def typedImplicit(cand: Candidate, contextual: Boolean)(implicit ctx: Context): SearchResult = track("typedImplicit") { trace(i"typed implicit ${cand.ref}, pt = $pt, implicitsEnabled == ${ctx.mode is ImplicitsEnabled}", implicits, show = true) { val ref = cand.ref var generated: Tree = tpd.ref(ref).withPos(pos.startPos) + val locked = ctx.typerState.ownedVars if (!argument.isEmpty) generated = typedUnadapted( untpd.Apply(untpd.TypedSplice(generated), untpd.TypedSplice(argument) :: Nil), - pt) - val generated1 = adapt(generated, pt) + pt, locked) + val generated1 = adapt(generated, pt, locked) lazy val shadowing = typed(untpd.Ident(cand.implicitRef.implicitName) withPos pos.toSynthetic, funProto)( nestedContext().addMode(Mode.ImplicitShadowing).setExploreTyperState()) diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index 76e810c0321c..2e4d614f34e9 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -20,6 +20,7 @@ import config.Printers.{typr, constr} import annotation.tailrec import reporting._ import collection.mutable +import config.Config object Inferencing { @@ -161,12 +162,15 @@ object Inferencing { * - The prefix `p` of a selection `p.f`. * - The result expression `e` of a block `{s1; .. sn; e}`. */ - def tvarsInParams(tree: Tree)(implicit ctx: Context): List[TypeVar] = { + def tvarsInParams(tree: Tree, locked: TypeVars)(implicit ctx: Context): List[TypeVar] = { @tailrec def boundVars(tree: Tree, acc: List[TypeVar]): List[TypeVar] = tree match { case Apply(fn, _) => boundVars(fn, acc) case TypeApply(fn, targs) => - val tvars = targs.tpes.collect { - case tvar: TypeVar if !tvar.isInstantiated && targs.contains(tvar.bindingTree) => tvar + val tvars = targs.filter(_.isInstanceOf[TypeVarBinder[_]]).tpes.collect { + case tvar: TypeVar + if !tvar.isInstantiated && + ctx.typerState.ownedVars.contains(tvar) && + !locked.contains(tvar) => tvar } boundVars(fn, acc ::: tvars) case Select(pre, _) => boundVars(pre, acc) @@ -228,7 +232,7 @@ object Inferencing { * to instantiate undetermined type variables that occur non-variantly */ def maximizeType(tp: Type, pos: Position, fromScala2x: Boolean)(implicit ctx: Context): List[Symbol] = Stats.track("maximizeType") { - val vs = variances(tp, alwaysTrue) + val vs = variances(tp) val patternBound = new mutable.ListBuffer[Symbol] vs foreachBinding { (tvar, v) => if (v == 1) tvar.instantiate(fromBelow = false) @@ -265,14 +269,14 @@ object Inferencing { * * we want to instantiate U to x.type right away. No need to wait further. */ - private def variances(tp: Type, include: TypeVar => Boolean)(implicit ctx: Context): VarianceMap = Stats.track("variances") { + private def variances(tp: Type)(implicit ctx: Context): VarianceMap = Stats.track("variances") { val constraint = ctx.typerState.constraint object accu extends TypeAccumulator[VarianceMap] { def setVariance(v: Int) = variance = v def apply(vmap: VarianceMap, t: Type): VarianceMap = t match { case t: TypeVar - if !t.isInstantiated && (ctx.typerState.constraint contains t) && include(t) => + if !t.isInstantiated && ctx.typerState.constraint.contains(t) => val v = vmap(t) if (v == null) vmap.updated(t, variance) else if (v == variance || v == 0) vmap @@ -313,71 +317,47 @@ object Inferencing { propagate(accu(SimpleIdentityMap.Empty, tp)) } - - private def varianceInContext(tvar: TypeVar)(implicit ctx: Context): FlagSet = { - object accu extends TypeAccumulator[FlagSet] { - def apply(fs: FlagSet, t: Type): FlagSet = - if (fs == EmptyFlags) fs - else if (t eq tvar) - if (variance > 0) fs &~ Contravariant - else if (variance < 0) fs &~ Covariant - else EmptyFlags - else foldOver(fs, t) - } - val constraint = ctx.typerState.constraint - val tparam = tvar.origin - (VarianceFlags /: constraint.uninstVars) { (fs, tv) => - if ((tv `eq` tvar) || (fs == EmptyFlags)) fs - else { - val otherParam = tv.origin - val fs1 = if (constraint.isLess(tparam, otherParam)) fs &~ Covariant else fs - val fs2 = if (constraint.isLess(otherParam, tparam)) fs1 &~ Contravariant else fs1 - accu(fs2, constraint.entry(otherParam)) - } - } - } } trait Inferencing { this: Typer => import Inferencing._ import tpd._ - /** Interpolate those undetermined type variables in the widened type of this tree - * which are introduced by type application contained in the tree. - * If such a variable appears covariantly in type `tp` or does not appear at all, - * approximate it by its lower bound. Otherwise, if it appears contravariantly - * in type `tp` approximate it by its upper bound. - * @param ownedBy if it is different from NoSymbol, all type variables owned by - * `ownedBy` qualify, independent of position. - * Without that second condition, it can be that certain variables escape - * interpolation, for instance when their tree was eta-lifted, so - * the typechecked tree is no longer the tree in which the variable - * was declared. A concrete example of this phenomenon can be - * observed when compiling core.TypeOps#asSeenFrom. + /** Interpolate undetermined type variables in the widened type of this tree. + * @param tree the tree whose type is interpolated + * @param pt the expected result type + * @param locked the set of type variables of the current typer state that cannot be interpolated + * at the present time + * Eligible for interpolation are all type variables owned by the current typerstate + * that are not in locked. Type variables occurring co- (respectively, contra-) variantly in the type + * are minimized (respectvely, maximized). Non occurring type variables are minimized if they + * have a lower bound different from Nothing, maximized otherwise. Type variables appearing + * non-variantly in the type are left untouched. + * + * Note that even type variables that do not appear directly in a type, can occur with + * some variance in the type, because of the constraints. E.g if `X` occurs co-variantly in `T` + * and we have a constraint + * + * Y <: X + * + * Then `Y` also occurs co-variantly in `T` because it needs to be minimized in order to constrain + * `T` the least. See `variances` for more detail. */ - def interpolateUndetVars(tree: Tree, ownedBy: Symbol, pt: Type)(implicit ctx: Context): Unit = { - val constraint = ctx.typerState.constraint - val qualifies = (tvar: TypeVar) => - (tree contains tvar.bindingTree) || ownedBy.exists && tvar.owner == ownedBy - def interpolate() = Stats.track("interpolateUndetVars") { - val tp = tree.tpe.widen - constr.println(s"interpolate undet vars in ${tp.show}, pos = ${tree.pos}, mode = ${ctx.mode}, undets = ${constraint.uninstVars map (tvar => s"${tvar.show}@${tvar.bindingTree.pos}")}") - constr.println(s"qualifying undet vars: ${constraint.uninstVars filter qualifies map (tvar => s"$tvar / ${tvar.show}")}, constraint: ${constraint.show}") - - val vs = variances(tp, qualifies) - val hasUnreportedErrors = ctx.typerState.reporter match { - case r: StoreReporter if r.hasErrors => true - case _ => false - } - - var isConstrained = tree.isInstanceOf[Apply] || tree.tpe.isInstanceOf[MethodOrPoly] - - def ensureConstrained() = if (!isConstrained) { - isConstrained = true + def interpolateTypeVars(tree: Tree, pt: Type, locked: TypeVars)(implicit ctx: Context): tree.type = { + val state = ctx.typerState + if (state.ownedVars.size > locked.size) { + val qualifying = state.ownedVars -- locked + typr.println(i"interpolate $tree: ${tree.tpe.widen} in $state, owned vars = ${state.ownedVars.toList}%, %, previous = ${locked.toList}%, % / ${state.constraint}") + val resultAlreadyConstrained = + tree.isInstanceOf[Apply] || tree.tpe.isInstanceOf[MethodOrPoly] + if (!resultAlreadyConstrained) constrainResult(tree.tpe, pt) - } + // This is needed because it could establish singleton type upper bounds. See i2998.scala. - // Avoid interpolating variables if typerstate has unreported errors. + val tp = tree.tpe.widen + val vs = variances(tp) + + // Avoid interpolating variables occurring in tree's type if typerstate has unreported errors. // Reason: The errors might reflect unsatisfiable constraints. In that // case interpolating without taking account the constraints risks producing // nonsensical types that then in turn produce incomprehensible errors. @@ -397,40 +377,29 @@ trait Inferencing { this: Typer => // found : Int(1) // required: String // val y: List[List[String]] = List(List(1)) - if (!hasUnreportedErrors) - vs foreachBinding { (tvar, v) => - if (v != 0 && ctx.typerState.constraint.contains(tvar)) { - // previous interpolations could have already instantiated `tvar` - // through unification, that's why we have to check again whether `tvar` - // is contained in the current constraint. - typr.println(s"interpolate ${if (v == 1) "co" else "contra"}variant ${tvar.show} in ${tp.show}") - ensureConstrained() - tvar.instantiate(fromBelow = v == 1) + val hasUnreportedErrors = state.reporter match { + case r: StoreReporter if r.hasErrors => true + case _ => false + } + def constraint = state.constraint + for (tvar <- qualifying) + if (!tvar.isInstantiated && state.constraint.contains(tvar)) { + // Needs to be checked again, since previous interpolations could already have + // instantiated `tvar` through unification. + val v = vs(tvar) + if (v == null) { + typr.println(i"interpolate non-occurring $tvar in $state in $tree: $tp, fromBelow = ${tvar.hasLowerBound}, $constraint") + tvar.instantiate(fromBelow = tvar.hasLowerBound) } + else if (!hasUnreportedErrors) + if (v.intValue != 0) { + typr.println(i"interpolate $tvar in $state in $tree: $tp, fromBelow = ${v.intValue == 1}, $constraint") + tvar.instantiate(fromBelow = v.intValue == 1) + } + else typr.println(i"no interpolation for nonvariant $tvar in $state") } - for (tvar <- constraint.uninstVars) - if (!(vs contains tvar) && qualifies(tvar)) { - typr.println(s"instantiating non-occurring ${tvar.show} in ${tp.show} / $tp") - ensureConstrained() - tvar.instantiate( - fromBelow = tvar.hasLowerBound || !varianceInContext(tvar).is(Covariant)) - } - } - if (constraint.uninstVars exists qualifies) interpolate() - } - - /** The uninstantiated type variables introduced somehwere in `tree` */ - def uninstBoundVars(tree: Tree)(implicit ctx: Context): List[TypeVar] = { - val buf = new mutable.ListBuffer[TypeVar] - tree.foreachSubTree { - case TypeApply(_, args) => - args.tpes.foreach { - case tv: TypeVar if !tv.isInstantiated && tree.contains(tv.bindingTree) => buf += tv - case _ => - } - case _ => } - buf.toList + tree } } diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 4c1b2f12b778..54db6540d49d 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -241,8 +241,9 @@ object ProtoTypes { * used to avoid repeated typings of trees when backtracking. */ def typedArg(arg: untpd.Tree, formal: Type)(implicit ctx: Context): Tree = { - val targ = cacheTypedArg(arg, typer.typedUnadapted(_, formal)) - typer.adapt(targ, formal) + val locked = ctx.typerState.ownedVars + val targ = cacheTypedArg(arg, typer.typedUnadapted(_, formal, locked)) + typer.adapt(targ, formal, locked) } /** The type of the argument `arg`. @@ -393,8 +394,10 @@ object ProtoTypes { def newTypeVars(tl: TypeLambda): List[TypeTree] = for (n <- (0 until tl.paramNames.length).toList) yield { - val tt = new TypeTree().withPos(owningTree.pos) - tt.withType(new TypeVar(tl.paramRefs(n), state, tt, ctx.owner)) + val tt = new TypeVarBinder().withPos(owningTree.pos) + val tvar = new TypeVar(tl.paramRefs(n), state) + state.ownedVars += tvar + tt.withType(tvar) } /** Ensure that `tl` is not already in constraint, make a copy of necessary */ diff --git a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala index 8de89f25aab6..689464d6cc0c 100644 --- a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala +++ b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala @@ -93,7 +93,7 @@ class ReTyper extends Typer with ReChecking { override def index(trees: List[untpd.Tree])(implicit ctx: Context) = ctx override def annotate(trees: List[untpd.Tree])(implicit ctx: Context) = () - override def tryInsertApplyOrImplicit(tree: Tree, pt: ProtoType)(fallBack: => Tree)(implicit ctx: Context): Tree = + override def tryInsertApplyOrImplicit(tree: Tree, pt: ProtoType, locked: TypeVars)(fallBack: => Tree)(implicit ctx: Context): Tree = fallBack override def completeAnnotations(mdef: untpd.MemberDef, sym: Symbol)(implicit ctx: Context): Unit = () @@ -109,8 +109,8 @@ class ReTyper extends Typer with ReChecking { super.handleUnexpectedFunType(tree, fun) } - override def typedUnadapted(tree: untpd.Tree, pt: Type)(implicit ctx: Context) = - try super.typedUnadapted(tree, pt) + override def typedUnadapted(tree: untpd.Tree, pt: Type, locked: TypeVars)(implicit ctx: Context) = + try super.typedUnadapted(tree, pt, locked) catch { case NonFatal(ex) => if (ctx.isAfterTyper) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index feac27039f59..438b3ef9b279 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -39,6 +39,7 @@ import rewrite.Rewrites.patch import NavigateAST._ import transform.SymUtils._ import reporting.trace +import config.Config import language.implicitConversions import printing.SyntaxHighlighting._ @@ -614,8 +615,9 @@ class Typer extends Namer untpd.Apply(wrappedUpdate, (args map (untpd.TypedSplice(_))) :+ tree.rhs) typed(appliedUpdate, pt) case lhs => - val lhsCore = typedUnadapted(lhs, AssignProto) - def lhs1 = typed(untpd.TypedSplice(lhsCore)) + val locked = ctx.typerState.ownedVars + val lhsCore = typedUnadapted(lhs, AssignProto, locked) + def lhs1 = typed(untpd.TypedSplice(lhsCore), WildcardType, locked) def reassignmentToVal = errorTree(cpy.Assign(tree)(lhsCore, typed(tree.rhs, lhs1.tpe.widen)), @@ -646,7 +648,7 @@ class Typer extends Namer val setterTypeRaw = pre.select(setterName, setter) val setterType = ensureAccessible(setterTypeRaw, isSuperSelection(lhsCore), tree.pos) val lhs2 = untpd.rename(lhsCore, setterName).withType(setterType) - typedUnadapted(untpd.Apply(untpd.TypedSplice(lhs2), tree.rhs :: Nil)) + typedUnadapted(untpd.Apply(untpd.TypedSplice(lhs2), tree.rhs :: Nil), WildcardType, locked) case _ => reassignmentToVal } @@ -1707,7 +1709,16 @@ class Typer extends Namer protected def localTyper(sym: Symbol): Typer = nestedTyper.remove(sym).get - def typedUnadapted(initTree: untpd.Tree, pt: Type = WildcardType)(implicit ctx: Context): Tree = { + def typedUnadapted(initTree: untpd.Tree, pt: Type = WildcardType)(implicit ctx: Context): Tree = + typedUnadapted(initTree, pt, ctx.typerState.ownedVars) + + /** Typecheck tree without adapting it, returning a typed tree. + * @param initTree the untyped tree + * @param pt the expected result type + * @param locked the set of type variables of the current typer state that cannot be interpolated + * at the present time + */ + def typedUnadapted(initTree: untpd.Tree, pt: Type, locked: TypeVars)(implicit ctx: Context): Tree = { record("typedUnadapted") val xtree = expanded(initTree) xtree.removeAttachment(TypedAhead) match { @@ -1731,7 +1742,7 @@ class Typer extends Namer typedClassDef(tree, sym.asClass)(ctx.localContext(tree, sym).setMode(ctx.mode &~ Mode.InSuperCall)) else typedTypeDef(tree, sym)(ctx.localContext(tree, sym).setNewScope) - case _ => typedUnadapted(desugar(tree), pt) + case _ => typedUnadapted(desugar(tree), pt, locked) } } @@ -1776,11 +1787,11 @@ class Typer extends Namer case tree: untpd.InfixOp if ctx.mode.isExpr => typedInfixOp(tree, pt) case tree @ untpd.PostfixOp(qual, Ident(nme.WILDCARD)) => typedAsFunction(tree, pt) case untpd.EmptyTree => tpd.EmptyTree - case _ => typedUnadapted(desugar(tree), pt) + case _ => typedUnadapted(desugar(tree), pt, locked) } val ifpt = defn.asImplicitFunctionType(pt) - if (ifpt.exists && + val result = if (ifpt.exists && xtree.isTerm && !untpd.isImplicitClosure(xtree) && !ctx.mode.is(Mode.ImplicitShadowing) && @@ -1790,9 +1801,22 @@ class Typer extends Namer case xtree: untpd.NameTree => typedNamed(xtree, pt) case xtree => typedUnnamed(xtree) } + simplify(result, pt, locked) } } + /** Interpolate and simplify the type of the given tree. */ + protected def simplify(tree: Tree, pt: Type, locked: TypeVars)(implicit ctx: Context): tree.type = { + if (!tree.denot.isOverloaded) // for overloaded trees: resolve overloading before simplifying + if (!tree.tpe.widen.isInstanceOf[MethodOrPoly] // wait with simplifying until method is fully applied + || tree.isDef) // ... unless tree is a definition + { + interpolateTypeVars(tree, pt, locked) + tree.overwriteType(tree.tpe.simplified) + } + tree + } + protected def makeImplicitFunction(tree: untpd.Tree, pt: Type)(implicit ctx: Context): Tree = { val defn.FunctionOf(formals, _, true, _) = pt.dropDependentRefinement val ifun = desugar.makeImplicitFunction(formals, tree) @@ -1800,16 +1824,21 @@ class Typer extends Namer typed(ifun, pt) } - def typed(tree: untpd.Tree, pt: Type = WildcardType)(implicit ctx: Context): Tree = /*>|>*/ trace(i"typing $tree", typr, show = true) /*<|<*/ { - record(s"typed $getClass") - record("typed total") - assertPositioned(tree) - try adapt(typedUnadapted(tree, pt), pt) - catch { - case ex: CyclicReference => errorTree(tree, cyclicErrorMsg(ex)) - case ex: TypeError => errorTree(tree, ex.getMessage) + /** Typecheck and adapt tree, returning a typed tree. Parameters as for `typedUnadapted` */ + def typed(tree: untpd.Tree, pt: Type, locked: TypeVars)(implicit ctx: Context): Tree = + trace(i"typing $tree", typr, show = true) { + record(s"typed $getClass") + record("typed total") + assertPositioned(tree) + try adapt(typedUnadapted(tree, pt, locked), pt, locked) + catch { + case ex: CyclicReference => errorTree(tree, cyclicErrorMsg(ex)) + case ex: TypeError => errorTree(tree, ex.getMessage) + } } - } + + def typed(tree: untpd.Tree, pt: Type = WildcardType)(implicit ctx: Context): Tree = + typed(tree, pt, ctx.typerState.ownedVars) def typedTrees(trees: List[untpd.Tree])(implicit ctx: Context): List[Tree] = trees mapconserve (typed(_)) @@ -1923,7 +1952,7 @@ class Typer extends Namer * around the qualifier part `qual` so that the result conforms to the expected type * with wildcard result type. */ - def tryInsertApplyOrImplicit(tree: Tree, pt: ProtoType)(fallBack: => Tree)(implicit ctx: Context): Tree = { + def tryInsertApplyOrImplicit(tree: Tree, pt: ProtoType, locked: TypeVars)(fallBack: => Tree)(implicit ctx: Context): Tree = { def isMethod(tree: Tree) = tree.tpe match { case ref: TermRef => ref.denot.alternatives.forall(_.info.widen.isInstanceOf[MethodicType]) @@ -1940,11 +1969,11 @@ class Typer extends Namer val sel = typedSelect(untpd.Select(untpd.TypedSplice(tree), nme.apply), pt) sel.pushAttachment(InsertedApply, ()) if (sel.tpe.isError) sel - else try adapt(sel, pt) finally sel.removeAttachment(InsertedApply) + else try adapt(simplify(sel, pt, locked), pt, locked) finally sel.removeAttachment(InsertedApply) } def tryImplicit = - tryInsertImplicitOnQualifier(tree, pt).getOrElse(fallBack) + tryInsertImplicitOnQualifier(tree, pt, locked).getOrElse(fallBack) pt match { case pt @ FunProto(Nil, _, _) @@ -1962,36 +1991,20 @@ class Typer extends Namer /** If this tree is a select node `qual.name`, try to insert an implicit conversion * `c` around `qual` so that `c(qual).name` conforms to `pt`. */ - def tryInsertImplicitOnQualifier(tree: Tree, pt: Type)(implicit ctx: Context): Option[Tree] = trace(i"try insert impl on qualifier $tree $pt") { + def tryInsertImplicitOnQualifier(tree: Tree, pt: Type, locked: TypeVars)(implicit ctx: Context): Option[Tree] = trace(i"try insert impl on qualifier $tree $pt") { tree match { case Select(qual, name) if name != nme.CONSTRUCTOR => val qualProto = SelectionProto(name, pt, NoViewsAllowed, privateOK = false) tryEither { implicit ctx => - val qual1 = adaptInterpolated(qual, qualProto) + val qual1 = adapt(qual, qualProto, locked) if ((qual eq qual1) || ctx.reporter.hasErrors) None - else Some(typed(cpy.Select(tree)(untpd.TypedSplice(qual1), name), pt)) + else Some(typed(cpy.Select(tree)(untpd.TypedSplice(qual1), name), pt, locked)) } { (_, _) => None } case _ => None } } - def adapt(tree: Tree, pt: Type)(implicit ctx: Context): Tree = /*>|>*/ track("adapt") /*<|<*/ { - /*>|>*/ trace(i"adapting $tree of type ${tree.tpe} to $pt", typr, show = true) /*<|<*/ { - if (!tree.denot.isOverloaded) { - // for overloaded trees: resolve overloading before simplifying - if (tree.isDef) interpolateUndetVars(tree, tree.symbol, pt) - else if (!tree.tpe.widen.isInstanceOf[MethodOrPoly]) interpolateUndetVars(tree, NoSymbol, pt) - tree.overwriteType(tree.tpe.simplified) - } - adaptInterpolated(tree, pt) - } - } - - /** (-1) For expressions with annotated types, let AnnotationCheckers decide what to do - * (0) Convert expressions with constant types to literals (unless in interactive/scaladoc mode) - */ - /** Perform the following adaptations of expression, pattern or type `tree` wrt to * given prototype `pt`: * (1) Resolve overloading @@ -2025,68 +2038,82 @@ class Typer extends Namer * (13) When in mode EXPRmode, apply AnnotationChecker conversion if expected type is annotated. * (14) When in mode EXPRmode, apply a view * If all this fails, error + * Parameters as for `typedUnadapted`. */ - def adaptInterpolated(tree: Tree, pt: Type)(implicit ctx: Context): Tree = { + def adapt(tree: Tree, pt: Type, locked: TypeVars)(implicit ctx: Context): Tree = /*>|>*/ track("adapt") /*<|<*/ { + def showWithType(x: Any) = x match { + case tree: tpd.Tree @unchecked => i"$tree of type ${tree.tpe}" + case _ => String.valueOf(x) + } + adapt1(tree, pt, locked) + } - assert(pt.exists) + def adapt(tree: Tree, pt: Type)(implicit ctx: Context): Tree = { + adapt(tree, pt, ctx.typerState.ownedVars) + } + private def adapt1(tree: Tree, pt: Type, locked: TypeVars)(implicit ctx: Context): Tree = { + assert(pt.exists) def methodStr = err.refStr(methPart(tree).tpe) + def readapt(tree: Tree)(implicit ctx: Context) = adapt(tree, pt, locked) + def readaptSimplified(tree: Tree)(implicit ctx: Context) = readapt(simplify(tree, pt, locked)) + def missingArgs(mt: MethodType) = { ctx.error(MissingEmptyArgumentList(methPart(tree).symbol), tree.pos) tree.withType(mt.resultType) } - def adaptOverloaded(ref: TermRef) = { - val altDenots = ref.denot.alternatives - typr.println(i"adapt overloaded $ref with alternatives ${altDenots map (_.info)}%, %") - val alts = altDenots.map(TermRef(ref.prefix, ref.name, _)) - resolveOverloaded(alts, pt) match { - case alt :: Nil => - adapt(tree.withType(alt), pt) - case Nil => - def noMatches = - errorTree(tree, - em"""none of the ${err.overloadedAltsStr(altDenots)} - |match ${err.expectedTypeStr(pt)}""") - def hasEmptyParams(denot: SingleDenotation) = denot.info.paramInfoss == ListOfNil - pt match { - case pt: FunProto => - tryInsertApplyOrImplicit(tree, pt)(noMatches) - case _ => - if (altDenots exists (_.info.paramInfoss == ListOfNil)) - typed(untpd.Apply(untpd.TypedSplice(tree), Nil), pt) - else - noMatches + def adaptOverloaded(ref: TermRef) = { + val altDenots = ref.denot.alternatives + typr.println(i"adapt overloaded $ref with alternatives ${altDenots map (_.info)}%, %") + val alts = altDenots.map(TermRef(ref.prefix, ref.name, _)) + resolveOverloaded(alts, pt) match { + case alt :: Nil => + readaptSimplified(tree.withType(alt)) + case Nil => + def noMatches = + errorTree(tree, + em"""none of the ${err.overloadedAltsStr(altDenots)} + |match ${err.expectedTypeStr(pt)}""") + def hasEmptyParams(denot: SingleDenotation) = denot.info.paramInfoss == ListOfNil + pt match { + case pt: FunProto => + tryInsertApplyOrImplicit(tree, pt, locked)(noMatches) + case _ => + if (altDenots exists (_.info.paramInfoss == ListOfNil)) + typed(untpd.Apply(untpd.TypedSplice(tree), Nil), pt, locked) + else + noMatches + } + case alts => + val remainingDenots = alts map (_.denot.asInstanceOf[SingleDenotation]) + errorTree(tree, AmbiguousOverload(tree, remainingDenots, pt)(err)) + } + } + + def isUnary(tp: Type): Boolean = tp match { + case tp: MethodicType => + tp.firstParamTypes match { + case ptype :: Nil => !ptype.isRepeatedParam + case _ => false } - case alts => - val remainingDenots = alts map (_.denot.asInstanceOf[SingleDenotation]) - errorTree(tree, AmbiguousOverload(tree, remainingDenots, pt)(err)) + case tp: TermRef => + tp.denot.alternatives.forall(alt => isUnary(alt.info)) + case _ => + false } - } - def isUnary(tp: Type): Boolean = tp match { - case tp: MethodicType => - tp.firstParamTypes match { - case ptype :: Nil => !ptype.isRepeatedParam - case _ => false + def adaptToArgs(wtp: Type, pt: FunProto): Tree = wtp match { + case _: MethodOrPoly => + if (pt.args.lengthCompare(1) > 0 && isUnary(wtp) && ctx.canAutoTuple) + adapt(tree, pt.tupled, locked) + else + tree + case _ => tryInsertApplyOrImplicit(tree, pt, locked) { + errorTree(tree, MethodDoesNotTakeParameters(tree, methPart(tree).tpe)(err)) } - case tp: TermRef => - tp.denot.alternatives.forall(alt => isUnary(alt.info)) - case _ => - false - } - - def adaptToArgs(wtp: Type, pt: FunProto): Tree = wtp match { - case _: MethodOrPoly => - if (pt.args.lengthCompare(1) > 0 && isUnary(wtp) && ctx.canAutoTuple) - adaptInterpolated(tree, pt.tupled) - else - tree - case _ => tryInsertApplyOrImplicit(tree, pt) { - errorTree(tree, MethodDoesNotTakeParameters(tree, methPart(tree).tpe)(err)) } - } /** If `tp` is a TypeVar which is fully constrained (i.e. its upper bound `hi` conforms * to its lower bound `lo`), replace `tp` by `hi`. This is necessary to @@ -2116,7 +2143,7 @@ class Typer extends Namer def adaptNoArgsImplicitMethod(wtp: MethodType): Tree = { assert(wtp.isImplicitMethod) - val tvarsToInstantiate = tvarsInParams(tree) + val tvarsToInstantiate = tvarsInParams(tree, locked) wtp.paramInfos.foreach(instantiateSelected(_, tvarsToInstantiate)) val constr = ctx.typerState.constraint @@ -2194,13 +2221,13 @@ class Typer extends Namer if (arg.tpe.isError) Nil else untpd.NamedArg(pname, untpd.TypedSplice(arg)) :: Nil } tryEither { implicit ctx => - typed(untpd.Apply(untpd.TypedSplice(tree), namedArgs), pt) + typed(untpd.Apply(untpd.TypedSplice(tree), namedArgs), pt, locked) } { (_, _) => issueErrors() } } else issueErrors() } - else adapt(tpd.Apply(tree, args), pt) + else readaptSimplified(tpd.Apply(tree, args)) } addImplicitArgs(argCtx(tree)) } @@ -2249,20 +2276,10 @@ class Typer extends Namer if (arity >= 0 && !tree.symbol.isConstructor && !ctx.mode.is(Mode.Pattern) && - !(isSyntheticApply(tree) && !isExpandableApply)) { - // Eta expansion interacts in tricky ways with type variable instantiation - // because it can extend the region where type variables are bound (and therefore may not - // be interpolated). To avoid premature interpolations, we need to extend the - // bindingTree of variables as we go along. Test case in pos/i3945.scala. - val boundtvs = uninstBoundVars(tree) - val uexpanded = etaExpand(tree, wtp, arity) - boundtvs.foreach(_.bindingTree = uexpanded) // make boundtvs point to uexpanded so that they are _not_ interpolated - val texpanded = typedUnadapted(uexpanded, pt) - boundtvs.foreach(_.bindingTree = texpanded) // make boundtvs point to texpanded so that they _can_ be interpolated - adapt(texpanded, pt) - } + !(isSyntheticApply(tree) && !isExpandableApply)) + simplify(typed(etaExpand(tree, wtp, arity), pt), pt, locked) else if (wtp.paramInfos.isEmpty && isAutoApplied(tree.symbol)) - adaptInterpolated(tpd.Apply(tree, Nil), pt) + readaptSimplified(tpd.Apply(tree, Nil)) else if (wtp.isImplicitMethod) err.typeMismatch(tree, pt) else @@ -2276,7 +2293,7 @@ class Typer extends Namer !isApplyProto(pt) && !ctx.isAfterTyper) { typr.println(i"insert apply on implicit $tree") - typed(untpd.Select(untpd.TypedSplice(tree), nme.apply), pt) + typed(untpd.Select(untpd.TypedSplice(tree), nme.apply), pt, locked) } else if (ctx.mode is Mode.Pattern) { checkEqualityEvidence(tree, pt) @@ -2290,7 +2307,7 @@ class Typer extends Namer !ctx.settings.YnoInline.value && !ctx.isAfterTyper && !ctx.reporter.hasErrors) - adapt(Inliner.inlineCall(tree, pt), pt) + readaptSimplified(Inliner.inlineCall(tree, pt)) else if (ctx.typeComparer.GADTused && pt.isValueType) // Insert an explicit cast, so that -Ycheck in later phases succeeds. // I suspect, but am not 100% sure that this might affect inferred types, @@ -2326,7 +2343,7 @@ class Typer extends Namer lazy val resultMatch = constrainResult(wtp, followAlias(pt)) wtp match { case wtp: ExprType => - adaptInterpolated(tree.withType(wtp.resultType), pt) + readaptSimplified(tree.withType(wtp.resultType)) case wtp: MethodType if wtp.isImplicitMethod && (resultMatch || !functionExpected) => if (resultMatch || ctx.mode.is(Mode.ImplicitsEnabled)) adaptNoArgsImplicitMethod(wtp) else { @@ -2379,7 +2396,7 @@ class Typer extends Namer if (pt isRef defn.UnitClass) // local adaptation makes sure every adapted tree conforms to its pt // so will take the code path that decides on inlining - return tpd.Block(adapt(tree, WildcardType) :: Nil, Literal(Constant(()))) + return tpd.Block(adapt(tree, WildcardType, locked) :: Nil, Literal(Constant(()))) // convert function literal to SAM closure tree match { case closure(Nil, id @ Ident(nme.ANON_FUN), _) @@ -2400,12 +2417,12 @@ class Typer extends Namer val prevConstraint = ctx.typerState.constraint def recover(failure: SearchFailureType) = if (isFullyDefined(wtp, force = ForceDegree.all) && - ctx.typerState.constraint.ne(prevConstraint)) adapt(tree, pt) + ctx.typerState.constraint.ne(prevConstraint)) readapt(tree) else err.typeMismatch(tree, pt, failure) if (ctx.mode.is(Mode.ImplicitsEnabled)) inferView(tree, pt) match { case SearchSuccess(inferred, _, _) => - adapt(inferred, pt)(ctx.retractMode(Mode.ImplicitsEnabled)) + readapt(inferred)(ctx.retractMode(Mode.ImplicitsEnabled)) case failure: SearchFailure => if (pt.isInstanceOf[ProtoType] && !failure.isAmbiguous) // don't report the failure but return the tree unchanged. This @@ -2458,7 +2475,7 @@ class Typer extends Namer pt match { case pt: FunProto if pt.args.lengthCompare(1) > 0 && isUnary(ref) && ctx.canAutoTuple => - adaptInterpolated(tree, pt.tupled) + adapt(tree, pt.tupled, locked) case _ => adaptOverloaded(ref) } @@ -2470,16 +2487,15 @@ class Typer extends Namer case _ => Nil } if (typeArgs.isEmpty) typeArgs = constrained(poly, tree)._2 - convertNewGenericArray( - adaptInterpolated(tree.appliedToTypeTrees(typeArgs), pt)) + convertNewGenericArray(readapt(tree.appliedToTypeTrees(typeArgs))) } case wtp => - if (isStructuralTermSelect(tree)) adapt(handleStructural(tree), pt) + if (isStructuralTermSelect(tree)) readaptSimplified(handleStructural(tree)) else pt match { case pt: FunProto => adaptToArgs(wtp, pt) case pt: PolyProto => - tryInsertApplyOrImplicit(tree, pt)(tree) // error will be reported in typedTypeApply + tryInsertApplyOrImplicit(tree, pt, locked)(tree) // error will be reported in typedTypeApply case _ => if (ctx.mode is Mode.Type) adaptType(tree.tpe) else adaptNoArgs(wtp) diff --git a/compiler/src/dotty/tools/dotc/util/SimpleMap.scala b/compiler/src/dotty/tools/dotc/util/SimpleIdentityMap.scala similarity index 98% rename from compiler/src/dotty/tools/dotc/util/SimpleMap.scala rename to compiler/src/dotty/tools/dotc/util/SimpleIdentityMap.scala index dbd5b1d1abbb..bdbba5605141 100644 --- a/compiler/src/dotty/tools/dotc/util/SimpleMap.scala +++ b/compiler/src/dotty/tools/dotc/util/SimpleIdentityMap.scala @@ -2,6 +2,9 @@ package dotty.tools.dotc.util import collection.mutable.ListBuffer +/** A simple linked map with `eq` as the key comparison, optimized for small maps. + * It has linear complexity for `apply`, `updated`, and `remove`. + */ abstract class SimpleIdentityMap[K <: AnyRef, +V >: Null <: AnyRef] extends (K => V) { def size: Int def apply(k: K): V diff --git a/compiler/src/dotty/tools/dotc/util/SimpleIdentitySet.scala b/compiler/src/dotty/tools/dotc/util/SimpleIdentitySet.scala new file mode 100644 index 000000000000..29df005fc856 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/util/SimpleIdentitySet.scala @@ -0,0 +1,116 @@ +package dotty.tools.dotc.util + +import collection.mutable.ListBuffer + +/** A simple linked set with `eq` as the comparison, optimized for small sets. + * It has linear complexity for `contains`, `+`, and `-`. + */ +abstract class SimpleIdentitySet[+Elem <: AnyRef] { + def size: Int + def + [E >: Elem <: AnyRef](x: E): SimpleIdentitySet[E] + def - [E >: Elem <: AnyRef](x: E): SimpleIdentitySet[Elem] + def contains[E >: Elem <: AnyRef](x: E): Boolean + def foreach(f: Elem => Unit): Unit + def toList: List[Elem] = { + val buf = new ListBuffer[Elem] + foreach(buf += _) + buf.toList + } + def ++ [E >: Elem <: AnyRef](that: SimpleIdentitySet[E]) = + ((this: SimpleIdentitySet[E]) /: that.toList)(_ + _) + def -- [E >: Elem <: AnyRef](that: SimpleIdentitySet[E]) = + (this /: that.toList)(_ - _) + override def toString = toList.mkString("(", ", ", ")") +} + +object SimpleIdentitySet { + object empty extends SimpleIdentitySet[Nothing] { + def size = 0 + def + [E <: AnyRef](x: E): SimpleIdentitySet[E] = + new Set1[E](x) + def - [E <: AnyRef](x: E): SimpleIdentitySet[Nothing] = + this + def contains[E <: AnyRef](x: E): Boolean = false + def foreach(f: Nothing => Unit): Unit = () + } + + private class Set1[+Elem <: AnyRef](x0: AnyRef) extends SimpleIdentitySet[Elem] { + def size = 1 + def + [E >: Elem <: AnyRef](x: E): SimpleIdentitySet[E] = + if (contains(x)) this else new Set2[E](x0, x) + def - [E >: Elem <: AnyRef](x: E): SimpleIdentitySet[Elem] = + if (x `eq` x0) empty else this + def contains[E >: Elem <: AnyRef](x: E): Boolean = x `eq` x0 + def foreach(f: Elem => Unit): Unit = f(x0.asInstanceOf[Elem]) + } + + private class Set2[+Elem <: AnyRef](x0: AnyRef, x1: AnyRef) extends SimpleIdentitySet[Elem] { + def size = 2 + def + [E >: Elem <: AnyRef](x: E): SimpleIdentitySet[E] = + if (contains(x)) this else new Set3(x0, x1, x) + def - [E >: Elem <: AnyRef](x: E): SimpleIdentitySet[Elem] = + if (x `eq` x0) new Set1(x1) + else if (x `eq` x1) new Set1(x0) + else this + def contains[E >: Elem <: AnyRef](x: E): Boolean = (x `eq` x0) || (x `eq` x1) + def foreach(f: Elem => Unit): Unit = { f(x0.asInstanceOf[Elem]); f(x1.asInstanceOf[Elem]) } + } + + private class Set3[+Elem <: AnyRef](x0: AnyRef, x1: AnyRef, x2: AnyRef) extends SimpleIdentitySet[Elem] { + def size = 3 + def + [E >: Elem <: AnyRef](x: E): SimpleIdentitySet[E] = + if (contains(this)) this + else { + val xs = new Array[AnyRef](4) + xs(0) = x0 + xs(1) = x1 + xs(2) = x2 + xs(3) = x + new SetN[E](xs) + } + def - [E >: Elem <: AnyRef](x: E): SimpleIdentitySet[Elem] = + if (x `eq` x0) new Set2(x1, x2) + else if (x `eq` x1) new Set2(x0, x2) + else if (x `eq` x2) new Set2(x0, x1) + else this + def contains[E >: Elem <: AnyRef](x: E): Boolean = (x `eq` x0) || (x `eq` x1) || (x `eq` x2) + def foreach(f: Elem => Unit): Unit = { + f(x0.asInstanceOf[Elem]); f(x1.asInstanceOf[Elem]); f(x2.asInstanceOf[Elem]) + } + } + + private class SetN[+Elem <: AnyRef](xs: Array[AnyRef]) extends SimpleIdentitySet[Elem] { + def size = xs.length + def + [E >: Elem <: AnyRef](x: E): SimpleIdentitySet[E] = { + val xs1 = new Array[AnyRef](size + 1) + Array.copy(xs, 0, xs1, 0, size) + xs1(size) = x + new SetN[E](xs1) + } + def - [E >: Elem <: AnyRef](x: E): SimpleIdentitySet[Elem] = { + var i = 0 + while (i < size && (xs(i) `ne` x)) i += 1 + if (i == size) this + else if (size == 4) + if (i == 0) new Set3(xs(1), xs(2), xs(3)) + else if (i == 1) new Set3(xs(0), xs(2), xs(3)) + else if (i == 2) new Set3(xs(0), xs(1), xs(3)) + else new Set3(xs(0), xs(1), xs(2)) + else { + val xs1 = new Array[AnyRef](size - 1) + Array.copy(xs, 0, xs1, 0, i) + Array.copy(xs, i + 1, xs1, i, size - (i + 1)) + new SetN(xs1) + } + } + def contains[E >: Elem <: AnyRef](x: E): Boolean = { + var i = 0 + while (i < size && (xs(i) `ne` x)) i += 1 + i < size + } + def foreach(f: Elem => Unit): Unit = { + var i = 0 + while (i < size) { f(xs(i).asInstanceOf[Elem]); i += 1 } + } + } +} \ No newline at end of file