Skip to content

Commit ee2b7d6

Browse files
committed
SI-8267 Avoid existentials after polymorphic overload resolution
... which can be introduced by `memberType` for methods with parameter types dependent on class type parameters. Here's an example of such a type: ``` scala> class Bippy { trait Foo[A] } defined class Bippy scala> final class RichBippy[C <: Bippy with Singleton](val c1: C) { | def g[A](x: A)(ev: c1.Foo[A]): Int = 2 | } defined class RichBippy scala> :power ** Power User mode enabled - BEEP WHIR GYVE ** ** :phase has been set to 'typer'. ** ** scala.tools.nsc._ has been imported ** ** global._, definitions._ also imported ** ** Try :help, :vals, power.<tab> ** scala> val g = typeOf[RichBippy[_]].member(TermName("g")) g: $r.intp.global.Symbol = method g scala> val c = new Bippy c: Bippy = Bippy@92e2c93 scala> val memberType = typeOf[RichBippy[c.type]].memberType(g) memberType: $r.intp.global.Type = ([A](x: A)(ev: _7.c1.Foo[A])Int) forSome { val _7: RichBippy[c.type] } ``` In this example, if we were to typecheck the selection `new RichBippy[c.type].g` that existential type would be short lived. Consider this approximation of `Typer#typedInternal`: ```scala val tree1: Tree = typed1(tree, mode, ptWild) val result = adapt(tree1, mode, ptPlugins, tree) ``` Given that `tree1.tpe` is not an overloaded, adapt will find its way to: ``` case tp if mode.typingExprNotLhs && isExistentialType(tp) => adapt(tree setType tp.dealias.skolemizeExistential(context.owner, tree), mode, pt, original) ``` Which would open the existential as per: ``` scala> memberType.skolemizeExistential res2: $r.intp.global.Type = [A](x: A)(ev: _7.c1.Foo[A])Int ``` However, if do have overloaded alternatives, as in the test case, we have to remember to call `adapt` again *after* we have picked the winning alternative. We actually don't have a centralised place where overload resolution occurs, as the process differs depending on the context of the selection. (Are there explicit type arguments? Inferred type arguments? Do we need to use the expected type to pick a winner?) This commit finds the existing places that call adapt after overloade resolution and routes those calls through a marker method. It then adds one more call to this in `inferPolyAlternatives`, which fixes the bug.
1 parent 79f59c5 commit ee2b7d6

File tree

2 files changed

+55
-5
lines changed

2 files changed

+55
-5
lines changed

src/compiler/scala/tools/nsc/typechecker/Typers.scala

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1100,7 +1100,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
11001100
adaptConstant(value)
11011101
case OverloadedType(pre, alts) if !mode.inFunMode => // (1)
11021102
inferExprAlternative(tree, pt)
1103-
adapt(tree, mode, pt, original)
1103+
adaptAfterOverloadResolution(tree, mode, pt, original)
11041104
case NullaryMethodType(restpe) => // (2)
11051105
adapt(tree setType restpe, mode, pt, original)
11061106
case TypeRef(_, ByNameParamClass, arg :: Nil) if mode.inExprMode => // (2)
@@ -1133,6 +1133,12 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
11331133
}
11341134
}
11351135

1136+
// This just exists to help keep track of the spots where we have to adapt a tree after
1137+
// overload resolution. These proved hard to find during the fix for SI-8267.
1138+
def adaptAfterOverloadResolution(tree: Tree, mode: Mode, pt: Type = WildcardType, original: Tree = EmptyTree): Tree = {
1139+
adapt(tree, mode, pt, original)
1140+
}
1141+
11361142
def instantiate(tree: Tree, mode: Mode, pt: Type): Tree = {
11371143
inferExprInstance(tree, context.extractUndetparams(), pt)
11381144
adapt(tree, mode, pt)
@@ -3171,7 +3177,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
31713177
if (sym1 != NoSymbol) sym = sym1
31723178
}
31733179
if (sym == NoSymbol) fun
3174-
else adapt(fun setSymbol sym setType pre.memberType(sym), mode.forFunMode, WildcardType)
3180+
else adaptAfterOverloadResolution(fun setSymbol sym setType pre.memberType(sym), mode.forFunMode)
31753181
} else fun
31763182
}
31773183

@@ -3216,7 +3222,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
32163222
setError(tree)
32173223
else {
32183224
inferMethodAlternative(fun, undetparams, argTpes, pt)
3219-
doTypedApply(tree, adapt(fun, mode.forFunMode, WildcardType), args1, mode, pt)
3225+
doTypedApply(tree, adaptAfterOverloadResolution(fun, mode.forFunMode, WildcardType), args1, mode, pt)
32203226
}
32213227
}
32223228
handleOverloaded
@@ -3799,7 +3805,18 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
37993805
protected def typedTypeApply(tree: Tree, mode: Mode, fun: Tree, args: List[Tree]): Tree = fun.tpe match {
38003806
case OverloadedType(pre, alts) =>
38013807
inferPolyAlternatives(fun, mapList(args)(treeTpe))
3802-
val tparams = fun.symbol.typeParams //@M TODO: fun.symbol.info.typeParams ? (as in typedAppliedTypeTree)
3808+
3809+
// SI-8267 `memberType` can introduce existentials *around* a PolyType/MethodType, see AsSeenFromMap#captureThis.
3810+
// If we had selected a non-overloaded symbol, `memberType` would have been called in `makeAccessible`
3811+
// and the resulting existential type would have been skolemized in `adapt` *before* we typechecked
3812+
// the enclosing type-/ value- application.
3813+
//
3814+
// However, if the selection is overloaded, we defer calling `memberType` until we can select a single
3815+
// alternative here. It is therefore necessary to skolemize the existential here.
3816+
//
3817+
val fun1 = adaptAfterOverloadResolution(fun, mode.forFunMode | TAPPmode)
3818+
3819+
val tparams = fun1.symbol.typeParams //@M TODO: fun.symbol.info.typeParams ? (as in typedAppliedTypeTree)
38033820
val args1 = if (sameLength(args, tparams)) {
38043821
//@M: in case TypeApply we can't check the kind-arities of the type arguments,
38053822
// as we don't know which alternative to choose... here we do
@@ -3813,7 +3830,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
38133830
// ...actually this was looping anyway, see bug #278.
38143831
return TypedApplyWrongNumberOfTpeParametersError(fun, fun)
38153832

3816-
typedTypeApply(tree, mode, fun, args1)
3833+
typedTypeApply(tree, mode, fun1, args1)
38173834
case SingleType(_, _) =>
38183835
typedTypeApply(tree, mode, fun setType fun.tpe.widen, args)
38193836
case PolyType(tparams, restpe) if tparams.nonEmpty =>

test/files/pos/t8267.scala

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
class Bippy { trait Foo[A] }
2+
3+
final class RichBippy[C <: Bippy with Singleton](val c1: C) {
4+
def f: Int = 1
5+
def f[A](x: A)(ev: c1.Foo[A]): Int = 2
6+
7+
def g[A <: Nothing](x: A): Int = 1
8+
def g[A](x: A)(ev: c1.Foo[A]): Int = 2
9+
10+
def h[A](x: A)(ev: c1.Foo[A]): Int = 1
11+
12+
def i(x: Nothing): Int = 1
13+
def i(x: AnyRef)(ev: c1.Foo[x.type]): Int = 2
14+
}
15+
16+
object p {
17+
18+
val c = new Bippy
19+
val d0 = new RichBippy[c.type](c)
20+
def d1 = new RichBippy[c.type](c)
21+
22+
d0.f[Int](5)(null: c.Foo[Int]) // ok
23+
d1.f[Int](5)(null: c.Foo[Int]) // fails
24+
25+
d0.g[Int](5)(null: c.Foo[Int]) // ok
26+
d1.g[Int](5)(null: c.Foo[Int]) // fails
27+
28+
d0.h[Int](5)(null: c.Foo[Int]) // ok
29+
d1.h[Int](5)(null: c.Foo[Int]) // ok
30+
31+
d0.i("")(null) // ok
32+
d1.i("")(null) // ok
33+
}

0 commit comments

Comments
 (0)