From fac74a618ae4666490cd8c7fd3f9604d877562d9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 11 Mar 2017 20:56:02 +0100 Subject: Fix #1569: Improve avoidance algorithm The essential change is that we do not throw away more precise info of the avoided type if the expected type is fully defined. --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) (limited to 'compiler/src') diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index ba14b7498..c43a8adcd 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -642,12 +642,18 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } val leaks = escapingRefs(tree, localSyms) if (leaks.isEmpty) tree - else if (isFullyDefined(pt, ForceDegree.none)) ascribeType(tree, pt) else if (!forcedDefined) { fullyDefinedType(tree.tpe, "block", tree.pos) - val tree1 = ascribeType(tree, avoid(tree.tpe, localSyms)) - ensureNoLocalRefs(tree1, pt, localSyms, forcedDefined = true) - } else + val avoidingType = avoid(tree.tpe, localSyms) + if (isFullyDefined(pt, ForceDegree.none) && !(avoidingType <:< pt)) + ascribeType(tree, pt) + else { + val tree1 = ascribeType(tree, avoidingType) + ensureNoLocalRefs(tree1, pt, localSyms, forcedDefined = true) + } + } else if (isFullyDefined(pt, ForceDegree.none)) + ascribeType(tree, pt) + else errorTree(tree, em"local definition of ${leaks.head.name} escapes as part of expression's type ${tree.tpe}"/*; full type: ${result.tpe.toString}"*/) } -- cgit v1.2.3 From 00034a2a8c91f3e83b3a2518cc7c944c66ffab3f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 12 Mar 2017 15:47:43 +0100 Subject: Improve definition and doc comment for ensureNoLeaks No more try-again business necessary. --- .../dotty/tools/dotc/transform/TreeChecker.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Typer.scala | 43 +++++++++++----------- 2 files changed, 22 insertions(+), 23 deletions(-) (limited to 'compiler/src') diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index 7a4af647f..51e2469b2 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -446,7 +446,7 @@ class TreeChecker extends Phase with SymTransformer { super.typedStats(trees, exprOwner) } - override def ensureNoLocalRefs(tree: Tree, pt: Type, localSyms: => List[Symbol], forcedDefined: Boolean = false)(implicit ctx: Context): Tree = + override def ensureNoLocalRefs(tree: Tree, pt: Type, localSyms: => List[Symbol])(implicit ctx: Context): Tree = tree override def adapt(tree: Tree, pt: Type, original: untpd.Tree = untpd.EmptyTree)(implicit ctx: Context) = { diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index c43a8adcd..9a561ec73 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -625,14 +625,17 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit block.tpe namedPartsWith (tp => locals.contains(tp.symbol)) } - /** Check that expression's type can be expressed without references to locally defined - * symbols. The following two remedies are tried before giving up: - * 1. If the expected type of the expression is fully defined, pick it as the - * type of the result expressed by adding a type ascription. - * 2. If (1) fails, force all type variables so that the block's type is - * fully defined and try again. + /** Ensure that an expression's type can be expressed without references to locally defined + * symbols. This is done by adding a type ascription of a widened type that does + * not refer to the locally defined symbols. The widened type is computed using + * `TyperAssigner#avoid`. However, if the expected type is fully defined and not + * a supertype of the widened type, we ascribe with the expected type instead. + * + * There's a special case having to do with anonymous classes. Sometimes the + * expected type of a block is the anonymous class defined inside it. In that + * case there's technically a leak which is not removed by the ascription. */ - protected def ensureNoLocalRefs(tree: Tree, pt: Type, localSyms: => List[Symbol], forcedDefined: Boolean = false)(implicit ctx: Context): Tree = { + protected def ensureNoLocalRefs(tree: Tree, pt: Type, localSyms: => List[Symbol])(implicit ctx: Context): Tree = { def ascribeType(tree: Tree, pt: Type): Tree = tree match { case block @ Block(stats, expr) => val expr1 = ascribeType(expr, pt) @@ -640,22 +643,18 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit case _ => Typed(tree, TypeTree(pt.simplified)) } - val leaks = escapingRefs(tree, localSyms) - if (leaks.isEmpty) tree - else if (!forcedDefined) { + def noLeaks(t: Tree): Boolean = escapingRefs(t, localSyms).isEmpty + if (noLeaks(tree)) tree + else { fullyDefinedType(tree.tpe, "block", tree.pos) - val avoidingType = avoid(tree.tpe, localSyms) - if (isFullyDefined(pt, ForceDegree.none) && !(avoidingType <:< pt)) - ascribeType(tree, pt) - else { - val tree1 = ascribeType(tree, avoidingType) - ensureNoLocalRefs(tree1, pt, localSyms, forcedDefined = true) - } - } else if (isFullyDefined(pt, ForceDegree.none)) - ascribeType(tree, pt) - else - errorTree(tree, - em"local definition of ${leaks.head.name} escapes as part of expression's type ${tree.tpe}"/*; full type: ${result.tpe.toString}"*/) + var avoidingType = avoid(tree.tpe, localSyms) + val ptDefined = isFullyDefined(pt, ForceDegree.none) + if (ptDefined && !(avoidingType <:< pt)) avoidingType = pt + val tree1 = ascribeType(tree, avoidingType) + assert(ptDefined || noLeaks(tree1), // `ptDefined` needed because of special case of anonymous classes + i"leak: ${escapingRefs(tree1, localSyms).toList}%, % in $tree1") + tree1 + } } def typedIf(tree: untpd.If, pt: Type)(implicit ctx: Context): Tree = track("typedIf") { -- cgit v1.2.3 From e8c27da5855f59574ba00cb1a95be8fb36b1fb48 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 12 Mar 2017 16:34:59 +0100 Subject: Comment ApproximatingTypeMap --- compiler/src/dotty/tools/dotc/core/Types.scala | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'compiler/src') diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index b8f81f1bb..639f5d142 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -3546,6 +3546,13 @@ object Types { def apply(tp: Type) = tp } + /** A type map that approximates NoTypes by upper or lower known bounds depending on + * variance. + * + * if variance > 0 : approximate by upper bound + * variance < 0 : approximate by lower bound + * variance = 0 : propagate NoType to next outer level + */ abstract class ApproximatingTypeMap(implicit ctx: Context) extends TypeMap { thisMap => def approx(lo: Type = defn.NothingType, hi: Type = defn.AnyType) = if (variance == 0) NoType -- cgit v1.2.3