From 90a661b72531af428130b1210f08cc365860eb65 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 6 Mar 2018 21:41:30 +0100 Subject: [PATCH 01/19] Drop ephemeral Changing the interpolation scheme uncovered several cache invalidation problems with - the asSeenFrom cache in Denotations - the superType cache in AppliedType - the lastDenotation cache in NamedType The new denotation scheme performed essentially same operations as the old one, but sometimes in a different order. I am still not quite sure how the differences made the cache invalidations fail. On the other hand, it is quite plausible (obvious even, in retrospect) that the previous invalidation schemes are incomplete. So this commit replaces them with a common algorithm that does not rely on the previous global state represented by `ephemeral`. --- .../dotty/tools/dotc/core/Denotations.scala | 2 +- .../dotty/tools/dotc/core/TyperState.scala | 14 --- .../src/dotty/tools/dotc/core/Types.scala | 96 +++++++++++-------- .../dotty/tools/dotc/typer/Implicits.scala | 44 ++++----- 4 files changed, 72 insertions(+), 84 deletions(-) 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/TyperState.scala b/compiler/src/dotty/tools/dotc/core/TyperState.scala index 1821735522f5..80dcfb87ec23 100644 --- a/compiler/src/dotty/tools/dotc/core/TyperState.scala +++ b/compiler/src/dotty/tools/dotc/core/TyperState.scala @@ -42,19 +42,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 @@ -159,7 +146,6 @@ 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.gc() reporter.flush() isCommitted = true diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 3675d6e8033c..9acd0aab0392 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -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 + 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) @@ -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) 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 + } } 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 } @@ -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) diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 7bf765c21ae1..b31f80cd57ef 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() From 860d102d74392ab337f5cf5c10b80076bfce0856 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 5 Mar 2018 08:46:44 +0100 Subject: [PATCH 02/19] More flexible tracing Allow to define what gets shown as a result on a backtrace --- .../dotty/tools/dotc/reporting/trace.scala | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/trace.scala b/compiler/src/dotty/tools/dotc/reporting/trace.scala index d0184c4c9e67..a56202818764 100644 --- a/compiler/src/dotty/tools/dotc/reporting/trace.scala +++ b/compiler/src/dotty/tools/dotc/reporting/trace.scala @@ -21,12 +21,16 @@ object trace { } @inline - def apply[T](question: => String, printer: Printers.Printer, show: Boolean)(op: => T)(implicit ctx: Context): T = { + 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, show)(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 = + apply[T](question, printer, if (show) showShowable(_) else alwaysToString)(op) + @inline def apply[T](question: => String, printer: Printers.Printer)(op: => T)(implicit ctx: Context): T = apply[T](question, printer, false)(op) @@ -39,16 +43,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 = From 0ff0189f61bcdc9a353efbb66a2a9c50adaff584 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 5 Mar 2018 09:30:20 +0100 Subject: [PATCH 03/19] Avoid creation of an unused closure value in each trace op The previous scheme created a `val showOp = ` value for each trace operation. It was unused if tracing was disabled. Still might be better to avoid its creation in the first place. --- compiler/src/dotty/tools/dotc/reporting/trace.scala | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/trace.scala b/compiler/src/dotty/tools/dotc/reporting/trace.scala index a56202818764..d42008500bbf 100644 --- a/compiler/src/dotty/tools/dotc/reporting/trace.scala +++ b/compiler/src/dotty/tools/dotc/reporting/trace.scala @@ -28,8 +28,11 @@ object trace { } @inline - def apply[T](question: => String, printer: Printers.Printer, show: Boolean)(op: => T)(implicit ctx: Context): T = - apply[T](question, printer, if (show) showShowable(_) else alwaysToString)(op) + 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, if (show) showShowable(_) else alwaysToString)(op1) + } @inline def apply[T](question: => String, printer: Printers.Printer)(op: => T)(implicit ctx: Context): T = From 086f33dfcf3fc075d0f5c53403ee75fbec388bd0 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 5 Mar 2018 09:36:12 +0100 Subject: [PATCH 04/19] Allow to suppress "inlined from" parts when printing trees -Yshow-no-inline suppresses "inlined from" parts when printing trees. This is useful when one has deeply inlined structures, as is the case when looking at `trace`ed code. --- compiler/src/dotty/tools/dotc/config/ScalaSettings.scala | 3 ++- compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) 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/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 => "" From 8a190544895fac81ec29c3b21b0a1dfdfcdb5962 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 5 Mar 2018 09:46:24 +0100 Subject: [PATCH 05/19] Add SimpleIdentitySet data structure Used for small, linked sets. Normal immutable sets are about as fast for 0 - 4 elements, but are not linked for larger sizes. --- ...impleMap.scala => SimpleIdentityMap.scala} | 3 + .../tools/dotc/util/SimpleIdentitySet.scala | 97 +++++++++++++++++++ 2 files changed, 100 insertions(+) rename compiler/src/dotty/tools/dotc/util/{SimpleMap.scala => SimpleIdentityMap.scala} (98%) create mode 100644 compiler/src/dotty/tools/dotc/util/SimpleIdentitySet.scala 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..969d7bdb6f70 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/util/SimpleIdentitySet.scala @@ -0,0 +1,97 @@ +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] = + 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] = { + val xs = new Array[AnyRef](3) + xs(0) = x0 + xs(1) = x1 + xs(2) = x + new SetN[E](xs) + } + 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 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 == 3) + if (i == 0) new Set2(xs(1), xs(2)) + else if (i == 1) new Set2(xs(0), xs(2)) + else new Set2(xs(0), xs(1)) + 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 From 06fd9aa24ad7108e219da12690443a0af50bc7d0 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 5 Mar 2018 10:46:11 +0100 Subject: [PATCH 06/19] Specialize SimpleIdentitySet with 3 elements --- .../tools/dotc/util/SimpleIdentitySet.scala | 43 +++++++++++++------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/util/SimpleIdentitySet.scala b/compiler/src/dotty/tools/dotc/util/SimpleIdentitySet.scala index 969d7bdb6f70..29df005fc856 100644 --- a/compiler/src/dotty/tools/dotc/util/SimpleIdentitySet.scala +++ b/compiler/src/dotty/tools/dotc/util/SimpleIdentitySet.scala @@ -37,7 +37,7 @@ object SimpleIdentitySet { private class Set1[+Elem <: AnyRef](x0: AnyRef) extends SimpleIdentitySet[Elem] { def size = 1 def + [E >: Elem <: AnyRef](x: E): SimpleIdentitySet[E] = - new Set2[E](x0, x) + 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 @@ -46,13 +46,8 @@ object SimpleIdentitySet { private class Set2[+Elem <: AnyRef](x0: AnyRef, x1: AnyRef) extends SimpleIdentitySet[Elem] { def size = 2 - def + [E >: Elem <: AnyRef](x: E): SimpleIdentitySet[E] = { - val xs = new Array[AnyRef](3) - xs(0) = x0 - xs(1) = x1 - xs(2) = x - new SetN[E](xs) - } + 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) @@ -61,6 +56,29 @@ object SimpleIdentitySet { 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] = { @@ -73,10 +91,11 @@ object SimpleIdentitySet { var i = 0 while (i < size && (xs(i) `ne` x)) i += 1 if (i == size) this - else if (size == 3) - if (i == 0) new Set2(xs(1), xs(2)) - else if (i == 1) new Set2(xs(0), xs(2)) - else new Set2(xs(0), xs(1)) + 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) From 2b6ce9c38487e23f14f1138558da9ccc39054dc4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 6 Mar 2018 21:50:28 +0100 Subject: [PATCH 07/19] Better printing of TyperStates Identify them by number. Helps in the same way other fixed numbering schemes help understand debug output. --- compiler/src/dotty/tools/dotc/core/TyperState.scala | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/TyperState.scala b/compiler/src/dotty/tools/dotc/core/TyperState.scala index 80dcfb87ec23..5316b78f4ad8 100644 --- a/compiler/src/dotty/tools/dotc/core/TyperState.scala +++ b/compiler/src/dotty/tools/dotc/core/TyperState.scala @@ -12,9 +12,19 @@ import printing.Texts._ import config.Config import collection.mutable import java.lang.ref.WeakReference +import Decorators._ + +object TyperState { + @sharable private var nextId: Int = 0 +} class TyperState(previous: TyperState /* | Null */) extends DotClass with Showable { + val id = TyperState.nextId + TyperState.nextId += 1 + + //assert(id != 146) + private[this] var myReporter = if (previous == null) new ConsoleReporter() else previous.reporter @@ -171,7 +181,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 toText(printer: Printer): Text = s"TS[$id]" def hashesStr: String = if (previous == null) "" else hashCode.toString + " -> " + previous.hashesStr From e3d5a7f896f2e7d65f49ed9ad96766d4000d7efd Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 5 Mar 2018 10:14:31 +0100 Subject: [PATCH 08/19] Show resulting type when tracing "adapt" calls --- .../src/dotty/tools/dotc/typer/Typer.scala | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index feac27039f59..642bdb80b158 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1800,16 +1800,17 @@ 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) + 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) + } } - } def typedTrees(trees: List[untpd.Tree])(implicit ctx: Context): List[Tree] = trees mapconserve (typed(_)) @@ -1977,7 +1978,11 @@ class Typer extends Namer } 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) /*<|<*/ { + def showWithType(x: Any) = x match { + case tree: tpd.Tree @unchecked => i"$tree of type ${tree.tpe}" + case _ => String.valueOf(x) + } + /*>|>*/ trace(i"adapting $tree of type ${tree.tpe} to $pt", typr, showOp = showWithType) /*<|<*/ { if (!tree.denot.isOverloaded) { // for overloaded trees: resolve overloading before simplifying if (tree.isDef) interpolateUndetVars(tree, tree.symbol, pt) From 93a0bca7743760d3667b18196227454b17436963 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 6 Mar 2018 22:35:47 +0100 Subject: [PATCH 09/19] Two fixes to invalidation scheme - Fix isProvisional condition for TypeVars - Force recomputation via memberDenot in NamedType if previous prefix was provisional --- compiler/src/dotty/tools/dotc/core/Types.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 9acd0aab0392..0e709ebd47a6 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -111,7 +111,7 @@ object Types { x || t.mightBeProvisional && { t.mightBeProvisional = t match { case t: TypeVar => - !t.inst.exists + !t.inst.exists || apply(x, t.inst) case t: TypeRef => (t: Type).mightBeProvisional = false // break cycles t.symbol.is(Provisional) || @@ -1691,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 (lastd != null && (lastd.validFor contains now) && checkedPeriod != Nowhere) { checkedPeriod = now lastd } @@ -1731,10 +1731,10 @@ object Types { lastDenotation match { case lastd0: SingleDenotation => val lastd = lastd0.skipRemoved - if (lastd.validFor.runId == ctx.runId) finish(lastd.current) + if (lastd.validFor.runId == ctx.runId && (checkedPeriod != Nowhere)) finish(lastd.current) else lastd match { case lastd: SymDenotation => - if (ctx.stillValid(lastd)) finish(lastd.current) + if (ctx.stillValid(lastd) && (checkedPeriod != Nowhere)) finish(lastd.current) else finish(memberDenot(lastd.initial.name, allowPrivate = false)) case _ => fromDesignator From 1215dd27b4cd78d5b63de2329807c6582c684fe6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 7 Mar 2018 08:13:22 +0100 Subject: [PATCH 10/19] Workaroound for testOptimized failure. Need to follow up later on what caused it to fail. --- compiler/src/dotty/tools/dotc/core/Types.scala | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 0e709ebd47a6..abaefbaa999d 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -983,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 } @@ -1010,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 = { From c46062725a4301105c90b31293b014804121673e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 7 Mar 2018 11:58:29 +0100 Subject: [PATCH 11/19] More precise characterization of singleton bounds Avoids needlessly complicated inferred types such as C[_ >: 1.type <: Singleton] by detecting that that this is equivalent to `C[1.type]`. --- .../dotty/tools/dotc/core/TypeComparer.scala | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) 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]) From 59bb75bfbcedda18f714fb7bb86cd44d8d28892c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 7 Mar 2018 12:29:25 +0100 Subject: [PATCH 12/19] Micro-optimization for denotAt --- compiler/src/dotty/tools/dotc/core/Types.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index abaefbaa999d..d33173efc15a 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1691,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) && checkedPeriod != Nowhere) { + if (checkedPeriod != Nowhere && lastd.validFor.contains(now)) { checkedPeriod = now lastd } From a800a1089b83999802d1f36e86ada4e93826b2f0 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 7 Mar 2018 14:21:42 +0100 Subject: [PATCH 13/19] Get rid of TypeState.hashesStr Hashes ruin diffability; replace them with the serial `id` numbers. --- .../dotty/tools/dotc/core/ConstraintHandling.scala | 12 +++++++----- compiler/src/dotty/tools/dotc/core/TyperState.scala | 9 +++------ 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index 11cf1449564a..1e19c88704fb 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 } diff --git a/compiler/src/dotty/tools/dotc/core/TyperState.scala b/compiler/src/dotty/tools/dotc/core/TyperState.scala index 5316b78f4ad8..cc9f195e00c1 100644 --- a/compiler/src/dotty/tools/dotc/core/TyperState.scala +++ b/compiler/src/dotty/tools/dotc/core/TyperState.scala @@ -18,13 +18,11 @@ object TyperState { @sharable private var nextId: Int = 0 } -class TyperState(previous: TyperState /* | Null */) extends DotClass with Showable { +class TyperState(previous: TyperState /* | Null */) { val id = TyperState.nextId TyperState.nextId += 1 - //assert(id != 146) - private[this] var myReporter = if (previous == null) new ConsoleReporter() else previous.reporter @@ -181,8 +179,7 @@ class TyperState(previous: TyperState /* | Null */) extends DotClass with Showab constraint = constraint.remove(poly) } - override def toText(printer: Printer): Text = s"TS[$id]" + 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}" } From d17cab455f6ee5737c4e5b9d12cdffb8d988782b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 7 Mar 2018 15:06:02 +0100 Subject: [PATCH 14/19] Strengthen isMultiSingleton We missed some cases before. --- compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index 1e19c88704fb..1eb13bab1f6a 100644 --- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -254,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 { From 417f0c85290879f93ce60b1254718167267f0297 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 7 Mar 2018 15:22:47 +0100 Subject: [PATCH 15/19] New interpolation scheme Major changes from previous one: - We explicitly keep track in typer of which variables should and which should not be interpolated. This replaces searching trees for embedded variable definitions, which is fragile e.g. in the presence of eta expansion. - We compute variances starting with all variables found in the type, not just teh qualifying ones. The previous scheme caused some variance information to be missed, which caused some variables to be mis-classified as non-occurring. i4032.scala is a test case. Unfortunately, fixing this caused several other tricky inference failures because which were previously hidden because some variables were already instantiated prematurely. Examples were hamp.scala, hmap-covariant.scala, and i2300.scala. - We interpolate at the end of typedUnadapted instead of at the beginning of `adapt`. Managing instantiatable variables turned out easier this way. --- .../dotty/tools/dotc/core/TyperState.scala | 8 +- .../src/dotty/tools/dotc/core/Types.scala | 9 +- .../dotty/tools/dotc/transform/Erasure.scala | 8 +- .../tools/dotc/transform/TreeChecker.scala | 10 +- .../dotty/tools/dotc/typer/Applications.scala | 12 +- .../tools/dotc/typer/ErrorReporting.scala | 7 +- .../dotty/tools/dotc/typer/Implicits.scala | 7 +- .../dotty/tools/dotc/typer/Inferencing.scala | 154 +++++------- .../dotty/tools/dotc/typer/ProtoTypes.scala | 9 +- .../src/dotty/tools/dotc/typer/ReTyper.scala | 6 +- .../src/dotty/tools/dotc/typer/Typer.scala | 228 +++++++++--------- 11 files changed, 230 insertions(+), 228 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TyperState.scala b/compiler/src/dotty/tools/dotc/core/TyperState.scala index cc9f195e00c1..e133e8299db1 100644 --- a/compiler/src/dotty/tools/dotc/core/TyperState.scala +++ b/compiler/src/dotty/tools/dotc/core/TyperState.scala @@ -5,7 +5,7 @@ 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._ @@ -76,6 +76,11 @@ class TyperState(previous: TyperState /* | Null */) { /** The uninstantiated variables */ def uninstVars = constraint.uninstVars + /** The set of uninstantiated type varibles 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 @@ -154,6 +159,7 @@ class TyperState(previous: TyperState /* | Null */) { constraint foreachTypeVar { tvar => if (tvar.owningState.get eq this) tvar.owningState = new WeakReference(targetState) } + targetState.ownedVars ++= ownedVars targetState.gc() reporter.flush() isCommitted = true diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index d33173efc15a..1db82938b6b9 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._ @@ -3369,7 +3369,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 @@ -3433,6 +3436,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/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 b31f80cd57ef..31f23a3327e5 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -768,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 @@ -828,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..03bfaca67b46 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,16 @@ 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 + case tvar: TypeVar + if !tvar.isInstantiated && + targs.contains(tvar.bindingTree) && + ctx.typerState.ownedVars.contains(tvar) && + !locked.contains(tvar) => tvar } boundVars(fn, acc ::: tvars) case Select(pre, _) => boundVars(pre, acc) @@ -228,7 +233,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 +270,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 +318,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` teh 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 +378,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..46483d997068 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`. @@ -394,7 +395,9 @@ object ProtoTypes { 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 tvar = new TypeVar(tl.paramRefs(n), state, tt, ctx.owner) + 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 642bdb80b158..4f2994e4a467 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,7 +1801,19 @@ 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.isDef || !tree.tpe.widen.isInstanceOf[MethodOrPoly]) + interpolateTypeVars(tree, pt, locked) + tree.overwriteType(tree.tpe.simplified) // ??? can we move in? } + tree } protected def makeImplicitFunction(tree: untpd.Tree, pt: Type)(implicit ctx: Context): Tree = { @@ -1800,18 +1823,22 @@ class Typer extends Namer typed(ifun, pt) } - def typed(tree: untpd.Tree, pt: Type = WildcardType)(implicit ctx: Context): Tree = + /** 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), pt) + 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(_)) @@ -1924,7 +1951,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]) @@ -1941,11 +1968,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, _, _) @@ -1963,40 +1990,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") /*<|<*/ { - def showWithType(x: Any) = x match { - case tree: tpd.Tree @unchecked => i"$tree of type ${tree.tpe}" - case _ => String.valueOf(x) - } - /*>|>*/ trace(i"adapting $tree of type ${tree.tpe} to $pt", typr, showOp = showWithType) /*<|<*/ { - 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 @@ -2030,68 +2037,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 @@ -2121,7 +2142,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 @@ -2199,13 +2220,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)) } @@ -2254,20 +2275,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 @@ -2281,7 +2292,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) @@ -2295,7 +2306,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, @@ -2331,7 +2342,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 { @@ -2384,7 +2395,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), _) @@ -2405,12 +2416,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 @@ -2463,7 +2474,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) } @@ -2475,16 +2486,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) From a6e1d19ea436ec0cae723cfd97aa6c003b0dc6f9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 7 Mar 2018 15:30:33 +0100 Subject: [PATCH 16/19] Reduce # calls to simplify Call simplify and interpolateTypeVars in the same situations. --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 4f2994e4a467..438b3ef9b279 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1807,12 +1807,13 @@ class Typer extends Namer /** 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.isDef || !tree.tpe.widen.isInstanceOf[MethodOrPoly]) + 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) // ??? can we move in? - } + tree.overwriteType(tree.tpe.simplified) + } tree } From 95f8a848752bdf4ef6717cc0d924c22e3a252afa Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 7 Mar 2018 16:59:45 +0100 Subject: [PATCH 17/19] Get rid of bindingTree and ownerSym in TypeVars --- compiler/src/dotty/tools/dotc/ast/Trees.scala | 5 +++++ compiler/src/dotty/tools/dotc/core/Types.scala | 5 +---- compiler/src/dotty/tools/dotc/typer/Inferencing.scala | 3 +-- compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala | 4 ++-- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index b6e730c5fd96..80b7d86df682 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 + /** 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/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 1db82938b6b9..67e429468f82 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -3354,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 diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index 03bfaca67b46..c948d76c9958 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -166,10 +166,9 @@ object Inferencing { @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 { + val tvars = targs.filter(_.isInstanceOf[TypeVarBinder[_]]).tpes.collect { case tvar: TypeVar if !tvar.isInstantiated && - targs.contains(tvar.bindingTree) && ctx.typerState.ownedVars.contains(tvar) && !locked.contains(tvar) => tvar } diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 46483d997068..54db6540d49d 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -394,8 +394,8 @@ object ProtoTypes { def newTypeVars(tl: TypeLambda): List[TypeTree] = for (n <- (0 until tl.paramNames.length).toList) yield { - val tt = new TypeTree().withPos(owningTree.pos) - val tvar = 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) } From c810d5aa739110be5dfa61704ab3ebe94dbe84a2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 7 Mar 2018 17:26:35 +0100 Subject: [PATCH 18/19] Fix typo in extends --- compiler/src/dotty/tools/dotc/ast/Trees.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index 80b7d86df682..29096e44bc38 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -576,7 +576,7 @@ object Trees { /** 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 + class TypeVarBinder[-T >: Untyped] extends TypeTree[T] /** ref.type */ case class SingletonTypeTree[-T >: Untyped] private[ast] (ref: Tree[T]) From 855e0e906ecca91c7c54d1af9805350f5437b94d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 7 Mar 2018 21:37:33 +0100 Subject: [PATCH 19/19] Fix typos --- compiler/src/dotty/tools/dotc/core/TyperState.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Inferencing.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TyperState.scala b/compiler/src/dotty/tools/dotc/core/TyperState.scala index e133e8299db1..f54e6af62b4e 100644 --- a/compiler/src/dotty/tools/dotc/core/TyperState.scala +++ b/compiler/src/dotty/tools/dotc/core/TyperState.scala @@ -76,7 +76,7 @@ class TyperState(previous: TyperState /* | Null */) { /** The uninstantiated variables */ def uninstVars = constraint.uninstVars - /** The set of uninstantiated type varibles which have this state as their owning state */ + /** 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 diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index c948d76c9958..2e4d614f34e9 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -341,7 +341,7 @@ trait Inferencing { this: Typer => * Y <: X * * Then `Y` also occurs co-variantly in `T` because it needs to be minimized in order to constrain - * `T` teh least. See `variances` for more detail. + * `T` the least. See `variances` for more detail. */ def interpolateTypeVars(tree: Tree, pt: Type, locked: TypeVars)(implicit ctx: Context): tree.type = { val state = ctx.typerState