Skip to content

Commit 4f15021

Browse files
authored
Merge pull request #2078 from dotty-staging/fix-#1569-v2
Fix #360: Improve avoidance algorithm
2 parents a886727 + e8c27da commit 4f15021

File tree

5 files changed

+41
-26
lines changed

5 files changed

+41
-26
lines changed

compiler/src/dotty/tools/dotc/core/Types.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3546,6 +3546,13 @@ object Types {
35463546
def apply(tp: Type) = tp
35473547
}
35483548

3549+
/** A type map that approximates NoTypes by upper or lower known bounds depending on
3550+
* variance.
3551+
*
3552+
* if variance > 0 : approximate by upper bound
3553+
* variance < 0 : approximate by lower bound
3554+
* variance = 0 : propagate NoType to next outer level
3555+
*/
35493556
abstract class ApproximatingTypeMap(implicit ctx: Context) extends TypeMap { thisMap =>
35503557
def approx(lo: Type = defn.NothingType, hi: Type = defn.AnyType) =
35513558
if (variance == 0) NoType

compiler/src/dotty/tools/dotc/transform/TreeChecker.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,7 @@ class TreeChecker extends Phase with SymTransformer {
446446
super.typedStats(trees, exprOwner)
447447
}
448448

449-
override def ensureNoLocalRefs(tree: Tree, pt: Type, localSyms: => List[Symbol], forcedDefined: Boolean = false)(implicit ctx: Context): Tree =
449+
override def ensureNoLocalRefs(tree: Tree, pt: Type, localSyms: => List[Symbol])(implicit ctx: Context): Tree =
450450
tree
451451

452452
override def adapt(tree: Tree, pt: Type, original: untpd.Tree = untpd.EmptyTree)(implicit ctx: Context) = {

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -625,31 +625,36 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
625625
block.tpe namedPartsWith (tp => locals.contains(tp.symbol))
626626
}
627627

628-
/** Check that expression's type can be expressed without references to locally defined
629-
* symbols. The following two remedies are tried before giving up:
630-
* 1. If the expected type of the expression is fully defined, pick it as the
631-
* type of the result expressed by adding a type ascription.
632-
* 2. If (1) fails, force all type variables so that the block's type is
633-
* fully defined and try again.
628+
/** Ensure that an expression's type can be expressed without references to locally defined
629+
* symbols. This is done by adding a type ascription of a widened type that does
630+
* not refer to the locally defined symbols. The widened type is computed using
631+
* `TyperAssigner#avoid`. However, if the expected type is fully defined and not
632+
* a supertype of the widened type, we ascribe with the expected type instead.
633+
*
634+
* There's a special case having to do with anonymous classes. Sometimes the
635+
* expected type of a block is the anonymous class defined inside it. In that
636+
* case there's technically a leak which is not removed by the ascription.
634637
*/
635-
protected def ensureNoLocalRefs(tree: Tree, pt: Type, localSyms: => List[Symbol], forcedDefined: Boolean = false)(implicit ctx: Context): Tree = {
638+
protected def ensureNoLocalRefs(tree: Tree, pt: Type, localSyms: => List[Symbol])(implicit ctx: Context): Tree = {
636639
def ascribeType(tree: Tree, pt: Type): Tree = tree match {
637640
case block @ Block(stats, expr) =>
638641
val expr1 = ascribeType(expr, pt)
639642
cpy.Block(block)(stats, expr1) withType expr1.tpe // no assignType here because avoid is redundant
640643
case _ =>
641644
Typed(tree, TypeTree(pt.simplified))
642645
}
643-
val leaks = escapingRefs(tree, localSyms)
644-
if (leaks.isEmpty) tree
645-
else if (isFullyDefined(pt, ForceDegree.none)) ascribeType(tree, pt)
646-
else if (!forcedDefined) {
646+
def noLeaks(t: Tree): Boolean = escapingRefs(t, localSyms).isEmpty
647+
if (noLeaks(tree)) tree
648+
else {
647649
fullyDefinedType(tree.tpe, "block", tree.pos)
648-
val tree1 = ascribeType(tree, avoid(tree.tpe, localSyms))
649-
ensureNoLocalRefs(tree1, pt, localSyms, forcedDefined = true)
650-
} else
651-
errorTree(tree,
652-
em"local definition of ${leaks.head.name} escapes as part of expression's type ${tree.tpe}"/*; full type: ${result.tpe.toString}"*/)
650+
var avoidingType = avoid(tree.tpe, localSyms)
651+
val ptDefined = isFullyDefined(pt, ForceDegree.none)
652+
if (ptDefined && !(avoidingType <:< pt)) avoidingType = pt
653+
val tree1 = ascribeType(tree, avoidingType)
654+
assert(ptDefined || noLeaks(tree1), // `ptDefined` needed because of special case of anonymous classes
655+
i"leak: ${escapingRefs(tree1, localSyms).toList}%, % in $tree1")
656+
tree1
657+
}
653658
}
654659

655660
def typedIf(tree: untpd.If, pt: Type)(implicit ctx: Context): Tree = track("typedIf") {

tests/neg/t1569-failedAvoid.scala

Lines changed: 0 additions & 9 deletions
This file was deleted.

tests/pos/t1569.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// See pos/t1569a.scala for related examples that work.
2+
object Bug {
3+
class C { type T }
4+
def foo(x: Int)(y: C)(z: y.T): Unit = {}
5+
foo(3)(new C { type T = String })("hello")
6+
}
7+
object Bug2 {
8+
class C { type T }
9+
class D extends C { type T = String }
10+
def foo(x: Int)(y: C)(z: y.T): Unit = {}
11+
foo(3)(new D {})("hello")
12+
}

0 commit comments

Comments
 (0)