summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Zaugg <jzaugg@gmail.com>2016-07-18 11:29:44 +1000
committerAdriaan Moors <adriaan.moors@typesafe.com>2016-08-23 15:51:51 -0700
commit893acc1829b3be96c75d00189e9e1b94ff4bd848 (patch)
tree4db52cd4b749e6b19a26ddffb248723276ef8b7a
parent56f23af90732b3770770139b33f92852384c9f04 (diff)
downloadscala-893acc1829b3be96c75d00189e9e1b94ff4bd848.tar.gz
scala-893acc1829b3be96c75d00189e9e1b94ff4bd848.tar.bz2
scala-893acc1829b3be96c75d00189e9e1b94ff4bd848.zip
SI-5294 Use bounds of abstract prefix in asSeenFrom
ASF was failing to recognize the correspondence between a prefix if it has an abstract type symbol, even if it is bounded by the currently considered class. Distilling the test cases, this led to incorrect typechecking of the RHS of `G` in: ``` trait T { type A trait HasH { type H[U] <: U } type F[N <: HasH] = N#H[T] type G[N <: HasH] = F[N]#A // RHS was incorrectly reduced to T.this.A } ``` In the fuller examples (included as test cases), this meant that type level functions written as members of `HList` could not be implemented in terms of each other, e.g. defining `Apply[N]` as `Drop[N]#Head` had the wrong semantics. This commit checks checks if the prefix has the candidate class as a base type, rather than checking if its type symbol has this as a base class. The latter formulation discarded information about the instantation of the abstract type. Using the example above: ``` scala> val F = typeOf[T].member(TypeName("F")).info F: $r.intp.global.Type = [N <: T.this.HasH]N#H[T] scala> F.resultType.typeSymbol.baseClasses // old approach res14: List[$r.intp.global.Symbol] = List(class Any) scala> F.resultType.baseClasses // new approach res13: List[$r.intp.global.Symbol] = List(trait T, class Object, class Any) ``` It is worth noting that dotty rejects some of these programs, as it introduces the rule that: > // A type T is a legal prefix in a type selection T#A if > // T is stable or T contains no abstract types except possibly A. > final def isLegalPrefixFor(selector: Name)(implicit ctx: Context) However, typechecking the program above in this comment in dotty yields: <trait> trait T() extends Object { type A <trait> trait HasH() extends Object { type H <: [HK$0] => <: HK$0 } type F = [HK$0] => HK$0#H{HK$0 = T}#Apply type G = [HK$0] => HK$0#H{HK$0 = T}#Apply#A } As the equivalent code [1] in dotc's `asSeenFrom` already looks for a base type of the prefix, rather than looking for a superclass of the prefix's type symbol. [1] https://github.com/lampepfl/dotty/blob/d2c96d02fccef3a82b88ee1ff31253b6ef17f900/src/dotty/tools/dotc/core/TypeOps.scala#L62
-rw-r--r--src/reflect/scala/reflect/internal/tpe/TypeMaps.scala25
-rw-r--r--test/files/pos/t5294b.scala36
-rw-r--r--test/files/pos/t5294c.scala30
-rw-r--r--test/files/pos/t6161b.scala (renamed from test/pending/pos/t6161.scala)0
-rw-r--r--test/files/run/t5294.scala22
5 files changed, 108 insertions, 5 deletions
diff --git a/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala b/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala
index 24f83c0b99..df61d2e418 100644
--- a/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala
+++ b/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala
@@ -610,11 +610,26 @@ private[internal] trait TypeMaps {
}
// Does the candidate symbol match the given prefix and class?
- // Since pre may be something like ThisType(A) where trait A { self: B => },
- // we have to test the typeSymbol of the widened type, not pre.typeSymbol, or
- // B will not be considered.
- private def matchesPrefixAndClass(pre: Type, clazz: Symbol)(candidate: Symbol) =
- (clazz == candidate) && (pre.widen.typeSymbol isSubClass clazz)
+ private def matchesPrefixAndClass(pre: Type, clazz: Symbol)(candidate: Symbol) = (clazz == candidate) && {
+ val pre1 = pre match {
+ case tv: TypeVar =>
+ // Needed with existentials in prefixes, e.g. test/files/pos/typevar-in-prefix.scala
+ // Perhaps the base type sequence of a type var should include it bounds?
+ tv.origin
+ case _ => pre
+ }
+ // widen needed (at least) because of https://github.com/scala/scala-dev/issues/166
+ (clazz == candidate) && (
+ if (clazz.isRefinementClass)
+ // base type seqs of aliases over refinement types have copied refinement types based on beta reduction
+ // for reliable lookup we need to consult the base type of the type symbol. (example: pos/t8177b.scala)
+ pre1.widen.typeSymbol isSubClass clazz
+ else
+ // In the general case, we look at the base type sequence of the prefix itself,
+ // which can have more concrete base classes than `.typeSymbol.baseClasses` (example: t5294, t6161)
+ pre1.widen.baseTypeIndex(clazz) != -1
+ )
+ }
// Whether the annotation tree currently being mapped over has had a This(_) node rewritten.
private[this] var wroteAnnotation = false
diff --git a/test/files/pos/t5294b.scala b/test/files/pos/t5294b.scala
new file mode 100644
index 0000000000..038d2ff806
--- /dev/null
+++ b/test/files/pos/t5294b.scala
@@ -0,0 +1,36 @@
+class Test {
+ def test = {
+
+ val l1 = null: Int #: String #: Boolean #: String #: HNil.type
+
+ type _2 = Succ[Succ[Zero.type]]
+
+ val t1: Boolean = null.asInstanceOf[ l1.type#Drop[_2]#Head ]
+
+ val t2: Boolean = null.asInstanceOf[ l1.type#Apply[_2] ]
+ }
+}
+
+
+sealed trait Nat {
+ type Fold[U, F[_ <: U] <: U, Z <: U] <: U
+}
+
+final object Zero extends Nat {
+ type Fold[U, F[_ <: U] <: U, Z <: U] = Z
+}
+
+final class Succ[N <: Nat] extends Nat {
+ type Fold[U, F[_ <: U] <: U, Z <: U] = F[N#Fold[U, F, Z]]
+}
+
+trait HList {
+ type Head
+ type Tail <: HList
+ type Drop[N <: Nat] = N#Fold[HList, ({ type L[X <: HList] = X#Tail })#L, this.type]
+ type Apply[N <: Nat] = Drop[N]#Head
+}
+
+class #: [H, T <: HList] extends HList { type Head = H; type Tail = T }
+
+object HNil extends HList { type Head = Nothing; type Tail = Nothing }
diff --git a/test/files/pos/t5294c.scala b/test/files/pos/t5294c.scala
new file mode 100644
index 0000000000..2709098988
--- /dev/null
+++ b/test/files/pos/t5294c.scala
@@ -0,0 +1,30 @@
+sealed trait Nat {
+ type IsZero[U, A <: U, B <: U] <: U
+}
+
+final object Zero extends Nat {
+ type IsZero[U, T <: U, F <: U] = T
+}
+
+final class Succ[N <: Nat] extends Nat {
+ type IsZero[U, T <: U, F <: U] = F
+}
+
+trait HList {
+ type Head
+ type Tail <: HList
+ type Drop[N <: Nat] = N#IsZero[HList, this.type, Tail]
+ type Apply[N <: Nat] = Drop[N]#Head // typechecks as HList.this.Head
+}
+
+object Test {
+ type ::[H, T <: HList] = HList { type Head = H; type Tail = T}
+ type HNil <: HList
+ type T = Int :: String :: HNil
+
+ type U = T#Drop[Succ[Zero.type]]#Head
+ type V = T#Apply[Succ[Zero.type]]
+ var u: U = ???
+ var v: V = ???
+ u = v
+}
diff --git a/test/pending/pos/t6161.scala b/test/files/pos/t6161b.scala
index 5783cc85f2..5783cc85f2 100644
--- a/test/pending/pos/t6161.scala
+++ b/test/files/pos/t6161b.scala
diff --git a/test/files/run/t5294.scala b/test/files/run/t5294.scala
new file mode 100644
index 0000000000..2551ae89a6
--- /dev/null
+++ b/test/files/run/t5294.scala
@@ -0,0 +1,22 @@
+import scala.language.higherKinds
+
+package p {
+ trait T[+A, +CC] {
+ def t: CC
+ }
+ class C {
+ def test[CC[X] <: T[X,String] with T[X,Int]](from: CC[_]): Unit = ()
+ }
+}
+
+object Test {
+ def main(args: Array[String]): Unit = {
+ val symtab = reflect.runtime.universe.asInstanceOf[reflect.internal.SymbolTable]
+ val CTpe = reflect.runtime.universe.typeOf[p.C].asInstanceOf[symtab.Type]
+ val TClass = reflect.runtime.universe.symbolOf[p.T[_, _]].asInstanceOf[symtab.Symbol]
+ import symtab._
+ val from = CTpe.member(TermName("test")).paramss.head.head
+ assert(from.baseClasses contains TClass)
+ assert(from.info.baseTypeIndex(TClass) != -1) // was failing!
+ }
+}