summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAdriaan Moors <adriaan.moors@typesafe.com>2014-02-10 16:16:37 -0800
committerAdriaan Moors <adriaan.moors@typesafe.com>2014-02-12 17:49:48 -0800
commitd5a1ea61871ad695c67a2165d0e569f304e63662 (patch)
tree4e3af4441ef920bd697b4faa4511486018e9de4d /src
parent9c4a6e3ed7624892f46948c1c0fb57d7d5b3346e (diff)
downloadscala-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')
-rw-r--r--src/reflect/scala/reflect/internal/tpe/TypeMaps.scala81
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