From 8ed77f4980d9396c693e97c8059dc7cc64dfb77a Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Wed, 5 Aug 2015 22:07:49 +0200 Subject: [PATCH 1/3] Typer#escapingRefs: don't let the types of lower bounds escape In 0efa171e8ccca0d49fc6d800fd21e29f7b7336fd I changed the definition of NamedPartsAccumulator to exclude lower bounds as this is required for the implicit search, but NamedPartsAccumulator is also used by Typer#escapingRefs so in the following code: class Foo[T] val z = { class C ??? : Foo[_ >: C] } the type of z was inferred to be Foo[_ >: C] instead of Foo. To avoid this, NamedPartsAccumulator will only exclude lower bounds if the parameter excludeLowerBounds is explicitely set to true. No test because there is no way to detect that a type has escaped, this might be something that could be added to TreeChecker. --- src/dotty/tools/dotc/core/Types.scala | 15 +++++++++++---- src/dotty/tools/dotc/typer/Implicits.scala | 2 +- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 4d6a7e22b719..6527b4c05950 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -245,9 +245,14 @@ object Types { /** The parts of this type which are type or term refs and which * satisfy predicate `p`. + * + * @param p The predicate to satisfy + * @param excludeLowerBounds If set to true, the lower bounds of abstract + * types will be ignored. */ - def namedPartsWith(p: NamedType => Boolean)(implicit ctx: Context): collection.Set[NamedType] = - new NamedPartsAccumulator(p).apply(mutable.LinkedHashSet(), this) + def namedPartsWith(p: NamedType => Boolean, excludeLowerBounds: Boolean = false) + (implicit ctx: Context): collection.Set[NamedType] = + new NamedPartsAccumulator(p, excludeLowerBounds).apply(mutable.LinkedHashSet(), this) /** Map function `f` over elements of an AndType, rebuilding with function `g` */ def mapReduceAnd[T](f: Type => T)(g: (T, T) => T)(implicit ctx: Context): T = stripTypeVar match { @@ -3105,7 +3110,8 @@ object Types { def apply(x: Unit, tp: Type): Unit = foldOver(p(tp), tp) } - class NamedPartsAccumulator(p: NamedType => Boolean)(implicit ctx: Context) extends TypeAccumulator[mutable.Set[NamedType]] { + class NamedPartsAccumulator(p: NamedType => Boolean, excludeLowerBounds: Boolean = false) + (implicit ctx: Context) extends TypeAccumulator[mutable.Set[NamedType]] { override def stopAtStatic = false def maybeAdd(x: mutable.Set[NamedType], tp: NamedType) = if (p(tp)) x += tp else x val seen: mutable.Set[Type] = mutable.Set() @@ -3118,7 +3124,8 @@ object Types { apply(foldOver(maybeAdd(x, tp), tp), tp.underlying) case tp: TypeRef => foldOver(maybeAdd(x, tp), tp) - case TypeBounds(_, hi) => + case TypeBounds(lo, hi) => + if (!excludeLowerBounds) apply(x, lo) apply(x, hi) case tp: ThisType => apply(x, tp.underlying) diff --git a/src/dotty/tools/dotc/typer/Implicits.scala b/src/dotty/tools/dotc/typer/Implicits.scala index 5444dddb0ae0..9793b9e188ff 100644 --- a/src/dotty/tools/dotc/typer/Implicits.scala +++ b/src/dotty/tools/dotc/typer/Implicits.scala @@ -325,7 +325,7 @@ trait ImplicitRunInfo { self: RunInfo => } tp.classSymbols(liftingCtx) foreach addClassScope case _ => - for (part <- tp.namedPartsWith(_.isType)) + for (part <- tp.namedPartsWith(_.isType, excludeLowerBounds = true)) comps ++= iscopeRefs(part) } comps From 3c92ecd248f634fecbfa8c3774dcf7da655b0d29 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Wed, 5 Aug 2015 22:45:52 +0200 Subject: [PATCH 2/3] Typer#escapingRefs: remove dead code --- src/dotty/tools/dotc/typer/Typer.scala | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 33ec156a1d72..4ab07246e94c 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -450,16 +450,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } def escapingRefs(block: Tree, localSyms: => List[Symbol])(implicit ctx: Context): collection.Set[NamedType] = { - var hoisted: Set[Symbol] = Set() lazy val locals = localSyms.toSet - def leakingTypes(tp: Type): collection.Set[NamedType] = - tp namedPartsWith (tp => locals.contains(tp.symbol)) - def typeLeaks(tp: Type): Boolean = leakingTypes(tp).nonEmpty - def classLeaks(sym: ClassSymbol): Boolean = - (ctx.owner is Method) || // can't hoist classes out of method bodies - (sym.info.parents exists typeLeaks) || - (sym.info.decls.toList exists (t => typeLeaks(t.info))) - leakingTypes(block.tpe) + block.tpe namedPartsWith (tp => locals.contains(tp.symbol)) } /** Check that expression's type can be expressed without references to locally defined From c9127938a3ba3406fee26cfe3673e51b72c51f1e Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Thu, 6 Aug 2015 00:43:46 +0200 Subject: [PATCH 3/3] NamedPartsAccumulator: also include bounds of uninstantiated type variables In the following code: class Foo[T](x: T) val z = { class X new Foo(new X) } When we call `Typer#ensureNoLocalRefs` on the block, its type is: Foo[T] where T <: X Before this commit, `Typer#escapingRefs` returned the empty set for this type since T is not instantiated yet, so the type was kept as is, and X escaped: val z: Foo[X] = { class X new Foo(new X) } We avoid this by inspecting the bounds of uninstantiated type variables in `NamedPartsAccumulator` which is used by `Typer#escapingRefs`. --- src/dotty/tools/dotc/core/Types.scala | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 6527b4c05950..7346205e39d4 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -3135,6 +3135,18 @@ object Types { apply(x, tp.underlying) case tp: PolyParam => apply(x, tp.underlying) + case tp: TypeVar => + val inst = tp.instanceOpt + if (inst.exists) apply(x, inst) + else { + ctx.typerState.ephemeral = true + val bounds = + if (ctx.typerState.constraint.contains(tp)) + ctx.typerState.constraint.fullBounds(tp.origin) + else + tp.origin + apply(x, bounds) + } case _ => foldOver(x, tp) }