diff options
author | Jason Zaugg <jzaugg@gmail.com> | 2014-09-19 00:11:39 +1000 |
---|---|---|
committer | Jason Zaugg <jzaugg@gmail.com> | 2014-10-02 21:00:29 +1000 |
commit | ee2b7d615be8e7d893b78d770cf68a95c8fa0621 (patch) | |
tree | 87dbd3814e5790714b8eb0047814836219135ff0 /src | |
parent | 79f59c5330122ee1ff1bac833bc77feca81d49af (diff) | |
download | scala-ee2b7d615be8e7d893b78d770cf68a95c8fa0621.tar.gz scala-ee2b7d615be8e7d893b78d770cf68a95c8fa0621.tar.bz2 scala-ee2b7d615be8e7d893b78d770cf68a95c8fa0621.zip |
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.
Diffstat (limited to 'src')
-rw-r--r-- | src/compiler/scala/tools/nsc/typechecker/Typers.scala | 27 |
1 files changed, 22 insertions, 5 deletions
diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 422b940cd3..b498d9e667 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -1100,7 +1100,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper adaptConstant(value) case OverloadedType(pre, alts) if !mode.inFunMode => // (1) inferExprAlternative(tree, pt) - adapt(tree, mode, pt, original) + adaptAfterOverloadResolution(tree, mode, pt, original) case NullaryMethodType(restpe) => // (2) adapt(tree setType restpe, mode, pt, original) case TypeRef(_, ByNameParamClass, arg :: Nil) if mode.inExprMode => // (2) @@ -1133,6 +1133,12 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper } } + // This just exists to help keep track of the spots where we have to adapt a tree after + // overload resolution. These proved hard to find during the fix for SI-8267. + def adaptAfterOverloadResolution(tree: Tree, mode: Mode, pt: Type = WildcardType, original: Tree = EmptyTree): Tree = { + adapt(tree, mode, pt, original) + } + def instantiate(tree: Tree, mode: Mode, pt: Type): Tree = { inferExprInstance(tree, context.extractUndetparams(), pt) adapt(tree, mode, pt) @@ -3171,7 +3177,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper if (sym1 != NoSymbol) sym = sym1 } if (sym == NoSymbol) fun - else adapt(fun setSymbol sym setType pre.memberType(sym), mode.forFunMode, WildcardType) + else adaptAfterOverloadResolution(fun setSymbol sym setType pre.memberType(sym), mode.forFunMode) } else fun } @@ -3216,7 +3222,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper setError(tree) else { inferMethodAlternative(fun, undetparams, argTpes, pt) - doTypedApply(tree, adapt(fun, mode.forFunMode, WildcardType), args1, mode, pt) + doTypedApply(tree, adaptAfterOverloadResolution(fun, mode.forFunMode, WildcardType), args1, mode, pt) } } handleOverloaded @@ -3799,7 +3805,18 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper protected def typedTypeApply(tree: Tree, mode: Mode, fun: Tree, args: List[Tree]): Tree = fun.tpe match { case OverloadedType(pre, alts) => inferPolyAlternatives(fun, mapList(args)(treeTpe)) - val tparams = fun.symbol.typeParams //@M TODO: fun.symbol.info.typeParams ? (as in typedAppliedTypeTree) + + // SI-8267 `memberType` can introduce existentials *around* a PolyType/MethodType, see AsSeenFromMap#captureThis. + // If we had selected a non-overloaded symbol, `memberType` would have been called in `makeAccessible` + // and the resulting existential type would have been skolemized in `adapt` *before* we typechecked + // the enclosing type-/ value- application. + // + // However, if the selection is overloaded, we defer calling `memberType` until we can select a single + // alternative here. It is therefore necessary to skolemize the existential here. + // + val fun1 = adaptAfterOverloadResolution(fun, mode.forFunMode | TAPPmode) + + val tparams = fun1.symbol.typeParams //@M TODO: fun.symbol.info.typeParams ? (as in typedAppliedTypeTree) val args1 = if (sameLength(args, tparams)) { //@M: in case TypeApply we can't check the kind-arities of the type arguments, // 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 // ...actually this was looping anyway, see bug #278. return TypedApplyWrongNumberOfTpeParametersError(fun, fun) - typedTypeApply(tree, mode, fun, args1) + typedTypeApply(tree, mode, fun1, args1) case SingleType(_, _) => typedTypeApply(tree, mode, fun setType fun.tpe.widen, args) case PolyType(tparams, restpe) if tparams.nonEmpty => |