Skip to content

Commit 9ede7d0

Browse files
committed
Merge pull request #817 from smarter/fix/avoid
Fix various issues in `TypeAssigner#avoid`
2 parents 8532c98 + 82a6d6f commit 9ede7d0

File tree

2 files changed

+83
-21
lines changed

2 files changed

+83
-21
lines changed

src/dotty/tools/dotc/typer/TypeAssigner.scala

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -31,35 +31,40 @@ trait TypeAssigner {
3131
/** An upper approximation of the given type `tp` that does not refer to any symbol in `symsToAvoid`.
3232
* Approximation steps are:
3333
*
34-
* - follow aliases if the original refers to a forbidden symbol
34+
* - follow aliases and upper bounds if the original refers to a forbidden symbol
3535
* - widen termrefs that refer to a forbidden symbol
3636
* - replace ClassInfos of forbidden classes by the intersection of their parents, refined by all
3737
* non-private fields, methods, and type members.
38+
* - if the prefix of a class refers to a forbidden symbol, first try to replace the prefix,
39+
* if this is not possible, replace the ClassInfo as above.
3840
* - drop refinements referring to a forbidden symbol.
3941
*/
4042
def avoid(tp: Type, symsToAvoid: => List[Symbol])(implicit ctx: Context): Type = {
4143
val widenMap = new TypeMap {
4244
lazy val forbidden = symsToAvoid.toSet
43-
def toAvoid(tp: Type): Boolean = tp match {
44-
case tp: TermRef =>
45-
val sym = tp.symbol
46-
sym.exists && (
47-
sym.owner.isTerm && (forbidden contains sym)
48-
|| !(sym.owner is Package) && toAvoid(tp.prefix)
49-
)
50-
case tp: TypeRef =>
51-
forbidden contains tp.symbol
52-
case _ =>
53-
false
54-
}
55-
def apply(tp: Type) = tp match {
45+
def toAvoid(tp: Type): Boolean =
46+
// TODO: measure the cost of using `existsPart`, and if necessary replace it
47+
// by a `TypeAccumulator` where we have set `stopAtStatic = true`.
48+
tp existsPart {
49+
case tp: NamedType =>
50+
forbidden contains tp.symbol
51+
case _ =>
52+
false
53+
}
54+
def apply(tp: Type): Type = tp match {
5655
case tp: TermRef if toAvoid(tp) && variance > 0 =>
5756
apply(tp.info.widenExpr)
58-
case tp: TypeRef if (forbidden contains tp.symbol) || toAvoid(tp.prefix) =>
57+
case tp: TypeRef if toAvoid(tp) =>
5958
tp.info match {
6059
case TypeAlias(ref) =>
6160
apply(ref)
6261
case info: ClassInfo if variance > 0 =>
62+
if (!(forbidden contains tp.symbol)) {
63+
val prefix = apply(tp.prefix)
64+
val tp1 = tp.derivedSelect(prefix)
65+
if (tp1.typeSymbol.exists)
66+
return tp1
67+
}
6368
val parentType = info.instantiatedParents.reduceLeft(ctx.typeComparer.andType(_, _))
6469
def addRefinement(parent: Type, decl: Symbol) = {
6570
val inherited =
@@ -82,13 +87,28 @@ trait TypeAssigner {
8287
case _ =>
8388
mapOver(tp)
8489
}
85-
case tp: RefinedType =>
86-
val tp1 @ RefinedType(parent1, _) = mapOver(tp)
87-
if (tp1.refinedInfo.existsPart(toAvoid) && variance > 0) {
88-
typr.println(s"dropping refinement from $tp1")
89-
parent1
90+
case tp @ RefinedType(parent, name) if variance > 0 =>
91+
// The naive approach here would be to first approximate the parent,
92+
// but if the base type of the approximated parent is different from
93+
// the current base type, then the current refinement won't be valid
94+
// if it's a type parameter refinement.
95+
// Therefore we first approximate the base type, then use `baseArgInfos`
96+
// to get correct refinements for the approximated base type, then
97+
// recursively approximate the resulting type.
98+
val base = tp.unrefine
99+
if (toAvoid(base)) {
100+
val base1 = apply(base)
101+
apply(base1.appliedTo(tp.baseArgInfos(base1.typeSymbol)))
102+
} else {
103+
val parent1 = apply(tp.parent)
104+
val refinedInfo1 = apply(tp.refinedInfo)
105+
if (toAvoid(refinedInfo1)) {
106+
typr.println(s"dropping refinement from $tp")
107+
parent1
108+
} else {
109+
tp.derivedRefinedType(parent1, name, refinedInfo1)
110+
}
90111
}
91-
else tp1
92112
case tp: TypeVar if ctx.typerState.constraint.contains(tp) =>
93113
val lo = ctx.typerState.constraint.fullLowerBound(tp.origin)
94114
val lo1 = avoid(lo, symsToAvoid)

tests/pos/escapingRefs.scala

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
class Outer {
2+
class Inner {
3+
class Inner2
4+
}
5+
}
6+
7+
class HasA { type A }
8+
9+
class Foo[A]
10+
11+
object Test {
12+
def test = {
13+
val a: Outer#Inner = {
14+
val o = new Outer
15+
new o.Inner
16+
}
17+
18+
val b: Outer#Inner#Inner2 = {
19+
val o = new Outer
20+
val i = new o.Inner
21+
new i.Inner2
22+
}
23+
24+
val c: HasA { type A = Int } = {
25+
val h = new HasA {
26+
type A = Int
27+
}
28+
val x: HasA { type A = h.A } = h
29+
x
30+
}
31+
32+
val d: Foo[Int] = {
33+
class Bar[B] extends Foo[B]
34+
new Bar[Int]
35+
}
36+
37+
val e: Foo[_] = {
38+
class Bar[B] extends Foo[B]
39+
new Bar[Int]: Bar[_ <: Int]
40+
}
41+
}
42+
}

0 commit comments

Comments
 (0)