Skip to content

Fix #360: Improve avoidance algorithm #2078

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 12, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/transform/TreeChecker.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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) = {
Expand Down
43 changes: 21 additions & 22 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -625,37 +625,36 @@ 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: TyperAssigner -> TypeAssigner

* 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)
cpy.Block(block)(stats, expr1) withType expr1.tpe // no assignType here because avoid is redundant
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") {
Expand Down