summaryrefslogtreecommitdiff
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
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
-rw-r--r--src/reflect/scala/reflect/internal/tpe/TypeMaps.scala81
-rw-r--r--test/files/neg/t3873.check4
-rw-r--r--test/files/neg/t3873.scala2
-rw-r--r--test/files/pos/t7753.scala36
-rw-r--r--test/files/pos/t8223.scala29
5 files changed, 119 insertions, 33 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
diff --git a/test/files/neg/t3873.check b/test/files/neg/t3873.check
index 54d6abdf63..f9f413aeaf 100644
--- a/test/files/neg/t3873.check
+++ b/test/files/neg/t3873.check
@@ -1,6 +1,6 @@
t3873.scala:11: error: type mismatch;
found : Test.a.B
- required: a.B
- wrongf(new A)(a.b) // should not compile -- TODO: improve error message? the "a" is ambiguous
+ required: a.B where val a: A
+ wrongf(new A)(a.b) // should not compile
^
one error found
diff --git a/test/files/neg/t3873.scala b/test/files/neg/t3873.scala
index e7815f0937..b27b4e9c9d 100644
--- a/test/files/neg/t3873.scala
+++ b/test/files/neg/t3873.scala
@@ -8,5 +8,5 @@ object Test {
val a = new A
wrongf(a)(a.b)
- wrongf(new A)(a.b) // should not compile -- TODO: improve error message? the "a" is ambiguous
+ wrongf(new A)(a.b) // should not compile
} \ No newline at end of file
diff --git a/test/files/pos/t7753.scala b/test/files/pos/t7753.scala
new file mode 100644
index 0000000000..93ad23f114
--- /dev/null
+++ b/test/files/pos/t7753.scala
@@ -0,0 +1,36 @@
+import scala.language.{ higherKinds, implicitConversions }
+
+trait Foo { type Out }
+
+trait SI {
+ val instance: Foo
+ type Out
+}
+
+object Test {
+ def test {
+ def indirect(si: SI)(v: si.instance.Out) = v
+
+ val foo: Foo { type Out = Int } = ???
+ def conv(i: Foo): SI { type Out = i.Out; val instance: i.type } = ???
+
+ val converted = conv(foo)
+
+ val v1: Int = indirect(converted)(23) // Okay (after refining the return type `instance` in the return type of `conv`)
+ /*
+ indirect(converted){(v: converted.instance.Out)converted.instance.Out}(
+ 23{Int(23)}
+ ){converted.instance.Out};
+ */
+
+ val v2: Int = indirect(conv(foo))(23) // Used to fail as follows:
+ /*
+ indirect(
+ conv(foo){si.SI{type Out = foo.Out; val instance: si.Test.<refinement>.type}}
+ ){(v: si.instance.Out)si.instance.Out}(
+ 23{<error>}
+ ){<error>};
+ */
+
+ }
+}
diff --git a/test/files/pos/t8223.scala b/test/files/pos/t8223.scala
new file mode 100644
index 0000000000..52d6b0098e
--- /dev/null
+++ b/test/files/pos/t8223.scala
@@ -0,0 +1,29 @@
+package p {
+ class ViewEnv[AIn] {
+ type A = AIn
+ class SubView { def has(x: A): Boolean = ??? }
+ def get: SubView = new SubView
+ }
+
+ trait HasA { type A }
+ trait Indexable[R] extends HasA
+ class ArrayTC[AIn] extends Indexable[Array[AIn]] { type A = AIn }
+}
+
+package object p {
+ implicit def arrayTypeClass[A] : ArrayTC[A] = new ArrayTC[A]
+ object intArrayTC extends ArrayTC[Int]
+
+ type EnvAlias[W <: HasA] = ViewEnv[W#A]
+ type SubAlias[W <: HasA] = ViewEnv[W#A]#SubView
+
+ def f0[R](xs: R)(implicit tc: Indexable[R]): ViewEnv[tc.A]#SubView = new ViewEnv[tc.A]() get
+ def f1[R](xs: R)(implicit tc: Indexable[R]): EnvAlias[tc.type]#SubView = new ViewEnv[tc.A]() get
+ def f2[R](xs: R)(implicit tc: Indexable[R]): SubAlias[tc.type] = new ViewEnv[tc.A]() get
+
+ def g0 = f0(Array(1)) has 2 // ok
+ def g1 = f1(Array(1)) has 2 // ok
+ def g2 = f2(Array(1)) has 2 // "found: Int(2), required: tc.A"
+ def g3 = f2(Array(1))(new ArrayTC[Int]) has 2 // "found: Int(2), required: tc.A"
+ def g4 = f2(Array(1))(intArrayTC) has 2 // ok
+}