Skip to content

Commit d5a1ea6

Browse files
committed
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
1 parent 9c4a6e3 commit d5a1ea6

File tree

5 files changed

+119
-33
lines changed

5 files changed

+119
-33
lines changed

src/reflect/scala/reflect/internal/tpe/TypeMaps.scala

Lines changed: 51 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -863,31 +863,24 @@ private[internal] trait TypeMaps {
863863
private val existentials = new Array[Symbol](actuals.size)
864864
def existentialsNeeded: List[Symbol] = existentials.iterator.filter(_ ne null).toList
865865

866-
private object StableArg {
867-
def unapply(param: Symbol) = Arg unapply param map actuals filter (tp =>
868-
tp.isStable && (tp.typeSymbol != NothingClass)
869-
)
870-
}
871-
private object Arg {
872-
def unapply(param: Symbol) = Some(params indexOf param) filter (_ >= 0)
873-
}
874-
875-
def apply(tp: Type): Type = mapOver(tp) match {
876-
// unsound to replace args by unstable actual #3873
877-
case SingleType(NoPrefix, StableArg(arg)) => arg
878-
// (soundly) expand type alias selections on implicit arguments,
879-
// see depmet_implicit_oopsla* test cases -- typically, `param.isImplicit`
880-
case tp1 @ TypeRef(SingleType(NoPrefix, Arg(pid)), sym, targs) =>
881-
val arg = actuals(pid)
882-
val res = typeRef(arg, sym, targs)
883-
if (res.typeSymbolDirect.isAliasType) res.dealias else tp1
884-
// don't return the original `tp`, which may be different from `tp1`,
885-
// due to dropping annotations
886-
case tp1 => tp1
866+
private object StableArgTp {
867+
// type of actual arg corresponding to param -- if the type is stable
868+
def unapply(param: Symbol): Option[Type] = (params indexOf param) match {
869+
case -1 => None
870+
case pid =>
871+
val tp = actuals(pid)
872+
if (tp.isStable && (tp.typeSymbol != NothingClass)) Some(tp)
873+
else None
874+
}
887875
}
888876

889-
/* Return the type symbol for referencing a parameter inside the existential quantifier.
890-
* (Only needed if the actual is unstable.)
877+
/** Return the type symbol for referencing a parameter that's instantiated to an unstable actual argument.
878+
*
879+
* To soundly abstract over an unstable value (x: T) while retaining the most type information,
880+
* use `x.type forSome { type x.type <: T with Singleton}`
881+
* `typeOf[T].narrowExistentially(symbolOf[x])`.
882+
*
883+
* See also: captureThis in AsSeenFromMap.
891884
*/
892885
private def existentialFor(pid: Int) = {
893886
if (existentials(pid) eq null) {
@@ -900,6 +893,38 @@ private[internal] trait TypeMaps {
900893
existentials(pid)
901894
}
902895

896+
private object UnstableArgTp {
897+
// existential quantifier and type of corresponding actual arg with unstable type
898+
def unapply(param: Symbol): Option[(Symbol, Type)] = (params indexOf param) match {
899+
case -1 => None
900+
case pid =>
901+
val sym = existentialFor(pid)
902+
Some((sym, sym.tpe_*)) // refers to an actual value, must be kind-*
903+
}
904+
}
905+
906+
private object StabilizedArgTp {
907+
def unapply(param: Symbol): Option[Type] =
908+
param match {
909+
case StableArgTp(tp) => Some(tp) // (1)
910+
case UnstableArgTp(_, tp) => Some(tp) // (2)
911+
case _ => None
912+
}
913+
}
914+
915+
/** instantiate `param.type` to the (sound approximation of the) type `T`
916+
* of the actual argument `arg` that was passed in for `param`
917+
*
918+
* (1) If `T` is stable, we can just use that.
919+
*
920+
* (2) SI-3873: it'd be unsound to instantiate `param.type` to an unstable `T`,
921+
* so we approximate to `X forSome {type X <: T with Singleton}` -- we can't soundly say more.
922+
*/
923+
def apply(tp: Type): Type = tp match {
924+
case SingleType(NoPrefix, StabilizedArgTp(tp)) => tp
925+
case _ => mapOver(tp)
926+
}
927+
903928
//AM propagate more info to annotations -- this seems a bit ad-hoc... (based on code by spoon)
904929
override def mapOver(arg: Tree, giveup: ()=>Nothing): Tree = {
905930
// TODO: this should be simplified; in the stable case, one can
@@ -922,13 +947,9 @@ private[internal] trait TypeMaps {
922947
// Both examples are from run/constrained-types.scala.
923948
object treeTrans extends Transformer {
924949
override def transform(tree: Tree): Tree = tree.symbol match {
925-
case StableArg(actual) =>
926-
gen.mkAttributedQualifier(actual, tree.symbol)
927-
case Arg(pid) =>
928-
val sym = existentialFor(pid)
929-
Ident(sym) copyAttrs tree setType typeRef(NoPrefix, sym, Nil)
930-
case _ =>
931-
super.transform(tree)
950+
case StableArgTp(tp) => gen.mkAttributedQualifier(tp, tree.symbol)
951+
case UnstableArgTp(quant, tp) => Ident(quant) copyAttrs tree setType tp
952+
case _ => super.transform(tree)
932953
}
933954
}
934955
treeTrans transform arg

test/files/neg/t3873.check

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
t3873.scala:11: error: type mismatch;
22
found : Test.a.B
3-
required: a.B
4-
wrongf(new A)(a.b) // should not compile -- TODO: improve error message? the "a" is ambiguous
3+
required: a.B where val a: A
4+
wrongf(new A)(a.b) // should not compile
55
^
66
one error found

test/files/neg/t3873.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ object Test {
88

99
val a = new A
1010
wrongf(a)(a.b)
11-
wrongf(new A)(a.b) // should not compile -- TODO: improve error message? the "a" is ambiguous
11+
wrongf(new A)(a.b) // should not compile
1212
}

test/files/pos/t7753.scala

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import scala.language.{ higherKinds, implicitConversions }
2+
3+
trait Foo { type Out }
4+
5+
trait SI {
6+
val instance: Foo
7+
type Out
8+
}
9+
10+
object Test {
11+
def test {
12+
def indirect(si: SI)(v: si.instance.Out) = v
13+
14+
val foo: Foo { type Out = Int } = ???
15+
def conv(i: Foo): SI { type Out = i.Out; val instance: i.type } = ???
16+
17+
val converted = conv(foo)
18+
19+
val v1: Int = indirect(converted)(23) // Okay (after refining the return type `instance` in the return type of `conv`)
20+
/*
21+
indirect(converted){(v: converted.instance.Out)converted.instance.Out}(
22+
23{Int(23)}
23+
){converted.instance.Out};
24+
*/
25+
26+
val v2: Int = indirect(conv(foo))(23) // Used to fail as follows:
27+
/*
28+
indirect(
29+
conv(foo){si.SI{type Out = foo.Out; val instance: si.Test.<refinement>.type}}
30+
){(v: si.instance.Out)si.instance.Out}(
31+
23{<error>}
32+
){<error>};
33+
*/
34+
35+
}
36+
}

test/files/pos/t8223.scala

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package p {
2+
class ViewEnv[AIn] {
3+
type A = AIn
4+
class SubView { def has(x: A): Boolean = ??? }
5+
def get: SubView = new SubView
6+
}
7+
8+
trait HasA { type A }
9+
trait Indexable[R] extends HasA
10+
class ArrayTC[AIn] extends Indexable[Array[AIn]] { type A = AIn }
11+
}
12+
13+
package object p {
14+
implicit def arrayTypeClass[A] : ArrayTC[A] = new ArrayTC[A]
15+
object intArrayTC extends ArrayTC[Int]
16+
17+
type EnvAlias[W <: HasA] = ViewEnv[W#A]
18+
type SubAlias[W <: HasA] = ViewEnv[W#A]#SubView
19+
20+
def f0[R](xs: R)(implicit tc: Indexable[R]): ViewEnv[tc.A]#SubView = new ViewEnv[tc.A]() get
21+
def f1[R](xs: R)(implicit tc: Indexable[R]): EnvAlias[tc.type]#SubView = new ViewEnv[tc.A]() get
22+
def f2[R](xs: R)(implicit tc: Indexable[R]): SubAlias[tc.type] = new ViewEnv[tc.A]() get
23+
24+
def g0 = f0(Array(1)) has 2 // ok
25+
def g1 = f1(Array(1)) has 2 // ok
26+
def g2 = f2(Array(1)) has 2 // "found: Int(2), required: tc.A"
27+
def g3 = f2(Array(1))(new ArrayTC[Int]) has 2 // "found: Int(2), required: tc.A"
28+
def g4 = f2(Array(1))(intArrayTC) has 2 // ok
29+
}

0 commit comments

Comments
 (0)