diff options
author | Adriaan Moors <adriaan.moors@typesafe.com> | 2014-02-10 16:16:37 -0800 |
---|---|---|
committer | Adriaan Moors <adriaan.moors@typesafe.com> | 2014-02-12 17:49:48 -0800 |
commit | d5a1ea61871ad695c67a2165d0e569f304e63662 (patch) | |
tree | 4e3af4441ef920bd697b4faa4511486018e9de4d /src/reflect | |
parent | 9c4a6e3ed7624892f46948c1c0fb57d7d5b3346e (diff) | |
download | scala-d5a1ea61871ad695c67a2165d0e569f304e63662.tar.gz scala-d5a1ea61871ad695c67a2165d0e569f304e63662.tar.bz2 scala-d5a1ea61871ad695c67a2165d0e569f304e63662.zip |
SI-7753 InstantiateDependentMap narrows type of unstable args
[Most of this comment and the initial fix were implemented by Jason Zaugg.
I just cleaned it up a bit.]
After a soundness fix in SI-3873, instantiation of dependent
method type results behaved differently depending on whether the argument
from which we were propagating information had a stable type
or not. This is particular to substitution into singleton types
over the parameter in question.
If the argument was stable, it was substituted into singleton
types, such as the one below in the prefix in `a.type#B`
(which is the longhand version of `a.B`)
scala> class A { type B >: Null <: AnyRef }
defined class A
scala> object AA extends A { type B = String }
defined object AA
scala> def foo(a: A): a.B = null
foo: (a: A)a.B
scala> foo(AA)
res0: AA.B = null
But what if it isn't stable?
scala> foo({def a = AA; a: A { type B <: String}})
res1: a.B = null
This commit changes that to:
scala> foo({def a = AA; a: A { type B <: String}})
res1: A{type B <: String}#B = null
Diffstat (limited to 'src/reflect')
-rw-r--r-- | src/reflect/scala/reflect/internal/tpe/TypeMaps.scala | 81 |
1 files changed, 51 insertions, 30 deletions
diff --git a/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala b/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala index f427813c01..07c9242bf3 100644 --- a/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala +++ b/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala @@ -863,31 +863,24 @@ private[internal] trait TypeMaps { private val existentials = new Array[Symbol](actuals.size) def existentialsNeeded: List[Symbol] = existentials.iterator.filter(_ ne null).toList - private object StableArg { - def unapply(param: Symbol) = Arg unapply param map actuals filter (tp => - tp.isStable && (tp.typeSymbol != NothingClass) - ) - } - private object Arg { - def unapply(param: Symbol) = Some(params indexOf param) filter (_ >= 0) - } - - def apply(tp: Type): Type = mapOver(tp) match { - // unsound to replace args by unstable actual #3873 - case SingleType(NoPrefix, StableArg(arg)) => arg - // (soundly) expand type alias selections on implicit arguments, - // see depmet_implicit_oopsla* test cases -- typically, `param.isImplicit` - case tp1 @ TypeRef(SingleType(NoPrefix, Arg(pid)), sym, targs) => - val arg = actuals(pid) - val res = typeRef(arg, sym, targs) - if (res.typeSymbolDirect.isAliasType) res.dealias else tp1 - // don't return the original `tp`, which may be different from `tp1`, - // due to dropping annotations - case tp1 => tp1 + private object StableArgTp { + // type of actual arg corresponding to param -- if the type is stable + def unapply(param: Symbol): Option[Type] = (params indexOf param) match { + case -1 => None + case pid => + val tp = actuals(pid) + if (tp.isStable && (tp.typeSymbol != NothingClass)) Some(tp) + else None + } } - /* Return the type symbol for referencing a parameter inside the existential quantifier. - * (Only needed if the actual is unstable.) + /** Return the type symbol for referencing a parameter that's instantiated to an unstable actual argument. + * + * To soundly abstract over an unstable value (x: T) while retaining the most type information, + * use `x.type forSome { type x.type <: T with Singleton}` + * `typeOf[T].narrowExistentially(symbolOf[x])`. + * + * See also: captureThis in AsSeenFromMap. */ private def existentialFor(pid: Int) = { if (existentials(pid) eq null) { @@ -900,6 +893,38 @@ private[internal] trait TypeMaps { existentials(pid) } + private object UnstableArgTp { + // existential quantifier and type of corresponding actual arg with unstable type + def unapply(param: Symbol): Option[(Symbol, Type)] = (params indexOf param) match { + case -1 => None + case pid => + val sym = existentialFor(pid) + Some((sym, sym.tpe_*)) // refers to an actual value, must be kind-* + } + } + + private object StabilizedArgTp { + def unapply(param: Symbol): Option[Type] = + param match { + case StableArgTp(tp) => Some(tp) // (1) + case UnstableArgTp(_, tp) => Some(tp) // (2) + case _ => None + } + } + + /** instantiate `param.type` to the (sound approximation of the) type `T` + * of the actual argument `arg` that was passed in for `param` + * + * (1) If `T` is stable, we can just use that. + * + * (2) SI-3873: it'd be unsound to instantiate `param.type` to an unstable `T`, + * so we approximate to `X forSome {type X <: T with Singleton}` -- we can't soundly say more. + */ + def apply(tp: Type): Type = tp match { + case SingleType(NoPrefix, StabilizedArgTp(tp)) => tp + case _ => mapOver(tp) + } + //AM propagate more info to annotations -- this seems a bit ad-hoc... (based on code by spoon) override def mapOver(arg: Tree, giveup: ()=>Nothing): Tree = { // TODO: this should be simplified; in the stable case, one can @@ -922,13 +947,9 @@ private[internal] trait TypeMaps { // Both examples are from run/constrained-types.scala. object treeTrans extends Transformer { override def transform(tree: Tree): Tree = tree.symbol match { - case StableArg(actual) => - gen.mkAttributedQualifier(actual, tree.symbol) - case Arg(pid) => - val sym = existentialFor(pid) - Ident(sym) copyAttrs tree setType typeRef(NoPrefix, sym, Nil) - case _ => - super.transform(tree) + case StableArgTp(tp) => gen.mkAttributedQualifier(tp, tree.symbol) + case UnstableArgTp(quant, tp) => Ident(quant) copyAttrs tree setType tp + case _ => super.transform(tree) } } treeTrans transform arg |