Skip to content

fundamentals of base type sequences [ci:last-only]#5041

Closed
paulp wants to merge 7 commits intoscala:2.12.xfrom
paulp:pr/6161
Closed

fundamentals of base type sequences [ci:last-only]#5041
paulp wants to merge 7 commits intoscala:2.12.xfrom
paulp:pr/6161

Conversation

@paulp
Copy link
Contributor

@paulp paulp commented Mar 15, 2016

Builds on retronym's branch ticket/5294 (rebased onto 2.12.x) to fix SI-6161 and other tickets with which it is entangled.

retronym and others added 7 commits March 14, 2016 21:57
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
This test case is my minimization from the ticket comments.

I've manually verified that the original version also works now,
but haven't used that as a test case as it depends too intimately
on the structure of `Global`.

```
% cat sandbox/test.scala
trait Namers {
  val global: scala.tools.nsc.Global
  import global.{ Name, Symbol }

  // Relevant definitions in Names
  //
  // def encode: ThisNameType
  // type ThisNameType >: Null <: Name
  //
  def f(x: Symbol): Name = x.name.encode
}
% qscalac sandbox/test.scala
%
```
At this point it builds, with six test failures.

!! 1 - pos/t8177.scala     [compilation failed]
!! 2 - pos/t8177d.scala    [compilation failed]
!! 3 - pos/t8177a.scala    [compilation failed]
!! 4 - pos/t7688.scala     [compilation failed]
!! 1 - run/t3425.scala     [compilation failed]
!! 2 - run/t8177f.scala    [non-zero exit code]
baseTypeIndex uses binary search, which depends on certain
attributes of the sequence being searched. In particular, it
depends on there being no equal elements among the sequence
being searched. This invariant was not maintained, leading to
the search failing to find matching types.

The intention is that every type in a BaseTypeSeq has a distinct
typeSymbol. I made this true by intercepting when two types with
the same typeSymbol were being placed into a base type sequence,
and rather than letting them both in, intersecting them.

At this point there's one failure, pos/t7688.scala.

t7688.scala:6: error: no type parameters for constructor A: (position: C#Position)A[C] exist so that it can be applied to arguments (c.universe.Position)
 --- because ---
argument expression's type is not compatible with formal parameter type;
 found   : c.universe.Position
 required: ?C#Position
    (which expands to)  Aliases.this.universe.Position
  def apply(c: Context)(in: c.Tree): A[c.type] = new A(in.pos)
                                                 ^
t7688.scala:6: error: type mismatch;
 found   : c.universe.Position
 required: C#Position
    (which expands to)  C#universe.Position
  def apply(c: Context)(in: c.Tree): A[c.type] = new A(in.pos)
                                                          ^
two errors found
Prefix was not being solved correctly because the constraint
acquiring mechanism depends on subtype tests taking place, so
if the process is short circuited it can underconstrain the
type to be inferred.
@scala-jenkins scala-jenkins added this to the 2.12.0-M5 milestone Mar 15, 2016
pre match {
// We may be solving for the prefix, in which case we need the
// subclass test to add the constraint. See pos/t7688 for an example.
case _: TypeVar => pre.typeSymbol isSubClass clazz
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we need to generalize this to !pre.isGround.

@paulp
Copy link
Contributor Author

paulp commented Mar 15, 2016

As you can see in the comments it's expected that the commits other than the last one don't pass the tests. I'm not sure what else to do with it given that I started from retronym's branch which didn't even compile, but I'm not about to smoosh his code into mine. I understand some people don't mind discarding ten years of repository history and reimporting everything under their own name, but I believe people should receive credit for their work.

@retronym retronym changed the title fundamentals of base type sequences fundamentals of base type sequences [ci:last-only] Mar 15, 2016
@retronym
Copy link
Member

Thanks for picking this one up, it has been hanging over my head.

This presentation of commits is good starting point for review. I've tweaked the metadata of this PR to only build the last commit in the future, but marked as on-hold so we don't merge until we agree on how to write the history books.

Just to set expectations, we're currently trying to tie off the changes to the trait encoding and cut 2.12.0-M4. We'll target this one for M5.

@retronym
Copy link
Member

/synch

@paulp
Copy link
Contributor Author

paulp commented Mar 15, 2016

I can't even find the console output from those details links, so I'll just note for the record that the tests pass for me.

@SethTisue
Copy link
Member

https://scala-ci.typesafe.com/job/scala-2.12.x-validate-test/1273/consoleFull:

[quick.compiler] /home/jenkins/workspace/scala-2.12.x-validate-test/src/compiler/scala/tools/nsc/ast/parser/MarkupParsers.scala:271: error: the type intersection scala.collection.mutable.AbstractSeq[_ >: Char with MarkupParsers.this.global.Tree] with scala.collection.mutable.IndexedSeq[_ >: Char with MarkupParsers.this.global.Tree] with scala.collection.IndexedSeqOptimized[Any,Cloneable with Mutable with Serializable with scala.collection.generic.Clearable with Equals] with scala.collection.mutable.Builder[Char with MarkupParsers.this.global.Tree,java.io.Serializable] with Serializable is malformed
[quick.compiler]  --- because ---
[quick.compiler] no common type instance of base types scala.collection.IndexedSeqLike[Any,Cloneable with Mutable with Serializable with scala.collection.generic.Clearable with Equals], and scala.collection.IndexedSeqLike[_249,scala.collection.mutable.IndexedSeq[_249]] with scala.collection.IndexedSeqLike[_249,IndexedSeq[_249]] forSome { type _249 >: Char with MarkupParsers.this.global.Tree } exists.
[quick.compiler]             t.attachments.get[handle.TextAttache] match {
[quick.compiler]                                                   ^
[quick.compiler] one error found

@paulp
Copy link
Contributor Author

paulp commented Mar 15, 2016

That's the very last commit which I added afterward (reverting retronym's type annotation) which shows why I'm smart not to trust my dulled scala build reflexes. But it's the previous commit where the tests pass for me.

@paulp
Copy link
Contributor Author

paulp commented Mar 15, 2016

Also, it makes me very suspicious of the sbt build that "sbt test" passes from clean checkout on that same commit. Which indicates it is not building the compiler with itself like the ant build does. Don't know if that's expected.

@paulp
Copy link
Contributor Author

paulp commented Mar 15, 2016

I reverted the reversion so now we're back at ec41931, the one which should pass.

@SethTisue
Copy link
Member

it is not building the compiler with itself like the ant build does. Don't know if that's expected

it is expected. building-with-itself is handled by scripts/jobs/integrate/bootstrap. see

scala/build.sbt

Lines 41 to 53 in 99a82be

* Note on bootstrapping:
*
* Let's start with reminder what bootstrapping means in our context. It's an answer
* to this question: which version of Scala are using to compile Scala? The fact that
* the question sounds circular suggests trickiness. Indeed, bootstrapping Scala
* compiler is a tricky process.
*
* Ant build used to have involved system of bootstrapping Scala. It would consist of
* three layers: starr, locker and quick. The sbt build for Scala ditches layering
* and strives to be as standard sbt project as possible. This means that we are simply
* building Scala with latest stable release of Scala.
* See this discussion for more details behind this decision:
* https://groups.google.com/d/topic/scala-internals/gp5JsM1E0Fo/discussion

@adriaanm adriaanm added the welcome hello new contributor! label Mar 15, 2016
@paulp
Copy link
Contributor Author

paulp commented Mar 17, 2016

Talk about rolling out the blue carpet.

@adriaanm adriaanm self-assigned this Apr 4, 2016
@milessabin
Copy link
Contributor

What needs to happen to move this along?

@retronym
Copy link
Member

It's in my radar... I'm coming back online this week after 👶 -leave

My goal is with this PR is to make the original problem with base type seqs more understandable/distilled so that we evaluate the right way to fix it.

@retronym
Copy link
Member

retronym commented Apr 29, 2016

Okay, I'm going to post some examples that show what's wrong with the status quo to discussion about how things ought to look.

Here's the code to pretty print the base type seq

import scala.language.higherKinds
import scala.language.existentials

object p {
  type Problem = /* Type Goes Here*/
}

object Test {
  def main(args: Array[String]): Unit = {
    val settings = new scala.tools.nsc.Settings()
    settings.usejavacp.value = true
    // settings.debug.value = true

    val global = new scala.tools.nsc.Global(settings)
    import global._, definitions._

    def formatBTS(t: Type): String = {
      val bts = t.baseTypeSeq
      import scala.reflect.internal.util.TableDef, TableDef.Column

      val cols = List[Column[Int]](
        Column[Int]("i",                   i => i, false),
        Column[Int]("baseClass",           i => t.typeSymbolDirect.baseClasses(i), false),
        Column[Int]("baseTypeIndex($bc)",  i => t.baseTypeIndex(t.typeSymbolDirect.baseClasses(i)), false),
        Column[Int]("typeSymbol",          i => bts.typeSymbol(i), false),
        // Column[Int]("apply",            i => bts(i), false),
        Column[Int]("rawElem.getClass",    i => bts.rawElem(i).getClass.getSimpleName, false)
      )
      TableDef(cols: _*).table(bts.toList.indices).toString
    }
    new Run

    println(formatBTS(typeOf[p.Problem]))
  }
}

Let's start with a simplified version of one of my test cases in this PR. This shows how an existential type wrapping a refinement type is enough to lead to an inconsistent base type seq. This was what motivated my original attempt to fix things by adding calls to underlying in BaseTypeSeq#{typeSymbol,apply}.

object p {
  trait T[A, B]
  type Problem = (T[X,String] with T[X,Int]) forSome { type X }
}
i                                     baseClass baseTypeIndex($bc)                                    typeSymbol   rawElem.getClass
- --------------------------------------------- ------------------ --------------------------------------------- ------------------
0 <refinement of p.T[X,String] with p.T[X,Int]>                 -1 <refinement of p.T[X,String] with p.T[X,Int]>    ExistentialType
1                                       trait T                 -1 <refinement of p.T[X,Int] with p.T[X,String]>    ExistentialType
2                                  class Object                  2                                  class Object ClassNoArgsTypeRef
3                                     class Any                  3                                     class Any ClassNoArgsTypeRef

Simplifying further:

trait T[A, B]
type Problem = (T[X,String]{}) forSome { type X }
i                     baseClass baseTypeIndex($bc)                    typeSymbol   rawElem.getClass
- ----------------------------- ------------------ ----------------------------- ------------------
0 <refinement of p.T[X,String]>                 -1 <refinement of p.T[X,String]>    ExistentialType
1                       trait T                 -1 <refinement of p.T[X,String]>    ExistentialType
2                  class Object                  2                  class Object ClassNoArgsTypeRef
3                     class Any                  3                     class Any ClassNoArgsTypeRef

Removing the existential to contrast with a coherent BTS:

trait T[A, B]
type Problem = (T[Int,String]{})
i                       baseClass baseTypeIndex($bc)                      typeSymbol           apply   rawElem.getClass
- ------------ ------------------ ------------------------------- --------------- ------------------
0 <refinement of p.T[Int,String]>                  0 <refinement of p.T[Int,String]> p.T[Int,String]  RefinementTypeRef
1                         trait T                  1                         trait T p.T[Int,String]   ClassArgsTypeRef
2                    class Object                  2                    class Object          Object ClassNoArgsTypeRef
3                       class Any                  3                       class Any             Any ClassNoArgsTypeRef

Wrapping a refinement in another:

trait T[A, B]
type Problem = (T[Int,String] with (T[String, String] {}))
i                                               baseClass baseTypeIndex($bc)                                              typeSymbol                                   apply   rawElem.getClass
- ---------------------------------- ------------------ ------------------------------------------------------- --------------------------------------- ------------------
0 <refinement of p.T[Int,String] with p.T[String,String]>                  0 <refinement of p.T[Int,String] with p.T[String,String]> p.T[Int,String] with p.T[String,String]  RefinementTypeRef
1                      <refinement of p.T[String,String]>                  1                      <refinement of p.T[String,String]>                      p.T[String,String]  RefinementTypeRef
2                                                 trait T                  2                                                 trait T       p.T[_ >: String with Int, String]    ExistentialType
3                                            class Object                  3                                            class Object                                  Object ClassNoArgsTypeRef
4                                               class Any                  4                                               class Any                                     Any ClassNoArgsTypeRef

And now an existental that wraps a regular typeref:

trait T[A, B]
type Problem = (T[X,String]) forSome { type X }
i    baseClass baseTypeIndex($bc)   typeSymbol                                         apply   rawElem.getClass
- ------------ ------------------ ------------ --------------------------------------------- ------------------
0      trait T                  0      trait T <empty>.this.p.T[X,String] forSome { type X }    ExistentialType
1 class Object                  1 class Object                              lang.this.Object ClassNoArgsTypeRef
2    class Any                  2    class Any                                scala.this.Any ClassNoArgsTypeRef

@retronym
Copy link
Member

This was what motivated my original attempt to fix things by adding calls to underlying in BaseTypeSeq#{typeSymbol,apply}.

I attemted to make this change in:

elems(i) match {
case rtp @ RefinedType(variants, decls) =>
// can't assert decls.isEmpty; see t0764
//if (!decls.isEmpty) abort("computing closure of "+this+":"+this.isInstanceOf[RefinedType]+"/"+closureCache(j))
//Console.println("compute closure of "+this+" => glb("+variants+")")
pending += i
try {
mergePrefixAndArgs(variants, Variance.Contravariant, lubDepth(variants)) match {
case NoType => typeError("no common type instance of base types "+(variants mkString ", and ")+" exists.")
case tp0 =>
pending(i) = false
elems(i) = tp0
tp0
}
}
catch {
case CyclicInheritance =>
typeError(
"computing the common type instance of base types "+(variants mkString ", and ")+" leads to a cycle.")
}
case tp =>
tp
and
elems(i) match {
case RefinedType(v :: vs, _) => v.typeSymbol
case tp => tp.typeSymbol
}

I have since found similar looking special cases might also need to be adapted.

i = 0
while (i < nparents) {
if (nextTypeSymbol(i) == minSym) {
nextRawElem(i) match {
case RefinedType(variants, decls) =>
for (tp <- variants)
if (!alreadyInMinTypes(tp)) minTypes ::= tp
case tp =>
if (!alreadyInMinTypes(tp)) minTypes ::= tp
}
index(i) = index(i) + 1
}
i += 1
}
buf += intersectionType(minTypes)
btsSize += 1
}

if (tpe.typeSymbol.isRefinementClass)

@retronym
Copy link
Member

retronym commented Apr 29, 2016

@adriaanm What is your view on how the errant base type seqs ought to be arranged? My gut feel is that we should have a single entry for the ExistentialType(... RefinedType(TypeRef :: ..., ...).

@retronym
Copy link
Member

retronym commented Apr 29, 2016

Running that test with the code in this PR:

type Problem = (T[X,String]{}) forSome { type X }
i                     baseClass baseTypeIndex($bc)   typeSymbol   rawElem.getClass
- ----------------------------- ------------------ ------------ ------------------
0 <refinement of p.T[X,String]>                 -1      trait T    ExistentialType
1                       trait T                  1      trait T    ExistentialType
2                  class Object                  2 class Object ClassNoArgsTypeRef
3                     class Any                  3    class Any ClassNoArgsTypeRef

@adriaanm adriaanm removed the on-hold label May 3, 2016
@adriaanm
Copy link
Contributor

adriaanm commented May 3, 2016

Now that I finally have my big PRs (indysammy and fields refactoring) in decent enough shape, I'll find some time to work on understanding this one. Unfortunately, I have travel and meetings coming up next for Scala Days etc.

@adriaanm adriaanm modified the milestones: 2.12.0-RC1, 2.12.0-M5 Jun 3, 2016
@retronym
Copy link
Member

For slightly easier debuggability, you can express my test above in JUnit with:

diff --git a/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala b/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala
index 38285fb..4e8d561 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala
@@ -113,4 +113,33 @@ class DirectCompileTest extends BytecodeTesting {
     compiler.compileToBytes("", a)
     compiler.compileToBytes(b)
   }
+
+  @Test
+  def baseTypeSeq(): Unit = {
+    val compiler = newCompiler(extraArgs = "-language:_")
+    import compiler.global._
+    compiler.compileToBytes("package pack; object p {\n  trait T[A, B]\n  type Problem = (T[X,String] with T[X,Int]) forSome { type X }\n}")
+
+
+    def formatBTS(t: Type): String = {
+      val bts = t.baseTypeSeq
+      import scala.reflect.internal.util.TableDef, TableDef.Column
+
+      val cols = List[Column[Int]](
+        Column[Int]("i",                   i => i, false),
+        Column[Int]("baseClass",           i => t.typeSymbolDirect.baseClasses(i), false),
+        Column[Int]("baseTypeIndex($bc)",  i => t.baseTypeIndex(t.typeSymbolDirect.baseClasses(i)), false),
+        Column[Int]("typeSymbol",          i => bts.typeSymbol(i), false),
+        // Column[Int]("apply",            i => bts(i), false),
+        Column[Int]("rawElem.getClass",    i => bts.rawElem(i).getClass.getSimpleName, false)
+      )
+      TableDef(cols: _*).table(bts.toList.indices).toString
+    }
+    new Run
+
+    val root = rootMirror.findMemberFromRoot(TypeName("pack.p.Problem"))
+    println(root)
+    println(formatBTS(root.info))
+
+  }
 }

@retronym
Copy link
Member

retronym commented Jun 30, 2016

I've honed in on:

compoundBaseTypeSeqImpl(pack.p.T[X,String] with pack.p.T[X,Int])
buf += pack.p.T[X,String] with pack.p.T[X,Int]
while (i < nparents) {
  nextTypeSymbol(0) = trait T, minSym = trait T
  0 nextRawElem(i) = pack.p.T[X,String]
  0 minTypes = List(pack.p.T[X,String])
  nextTypeSymbol(1) = trait T, minSym = trait T
  1 nextRawElem(i) = pack.p.T[X,Int]
  1 minTypes = List(pack.p.T[X,Int], pack.p.T[X,String])
buf += intersectionType(List(pack.p.T[X,Int], pack.p.T[X,String])) (pack.p.T[X,Int] with pack.p.T[X,String]
...
elems = List(
  pack.p.T[X,String] with pack.p.T[X,Int],
  pack.p.T[X,Int] with pack.p.T[X,String], Object, Any)

@retronym
Copy link
Member

retronym commented Jun 30, 2016

Maybe I honed in too far there. We have an entry for a typeref for the refinement class itself (RefinementTypeRef), followed by the first entry derived from the parents (RefinedType).

On my travels, I noticed some suspicious code in RefinementTypeRef#normalize

    // I think this is okay, but see #1241 (r12414), #2208, and typedTypeConstructor in Typers
    override protected def normalizeImpl: Type = sym.info.normalize

That discards the prefix.

scala> class C[A] { type T = { def foo: A } }
defined class C

scala> val ref = TypeRef(typeOf[C[Int]], typeOf[C[_]].member(TypeName("T")).info.typeSymbolDirect, Nil)
ref: $r.intp.global.Type = AnyRef{def foo: A}

scala> ref <:< typeOf[{ def foo: Int }]
res41: Boolean = true

scala> ref <:< typeOf[{ def foo: String }]
res42: Boolean = false

scala> ref.normalize <:< typeOf[{ def foo: Int }] // oops. discarded the prefix
res45: Boolean = false

Maybe something like this would be better:

    // I think this is okay, but see #1241 (r12414), #2208, and typedTypeConstructor in Typers
    override protected def normalizeImpl: Type = {
      if (sym.info.normalize eq sym.info)
        this
      else TypeRef(pre0, sym.cloneSymbol.modifyInfo(_.normalize), Nil)
    }

@paulp
Copy link
Contributor Author

paulp commented Jun 30, 2016

Are there any published invariants about how symbols and types are supposed to relate? It seems unlikely progress can be made without that. Anyway, here is some test code I just wrote to illuminate some of the mysteries. Under what conditions should a type be allowed like AnyRef{def foo: A} which has a prefix of C[Int] thus fixing that A to Int? What possible good could come of a partially substituted type parameter?

import scala.language.dynamics

object Typ {
  abstract class WType extends Dynamic {
    def pre: Type
    def sym(name: String): Symbol

    def sym1(name: String): Symbol = pre member TypeName(name)
    def sym2(name: String): Symbol = sym1(name).info.typeSymbolDirect
    def sym3(name: String): Symbol = pre memberInfo sym1(name) typeSymbolDirect

    def selectDynamic(name: String): Type              = TypeRef(pre, sym(name), Nil)
    def applyDynamic(name: String)(targs: Type*): Type = TypeRef(pre, sym(name), targs.toList)
  }
  case class WType1(val pre: Type) extends WType { def sym(name: String) = sym1(name) }
  case class WType2(val pre: Type) extends WType { def sym(name: String) = sym2(name) }
  case class WType3(val pre: Type) extends WType { def sym(name: String) = sym3(name) }
}
import Typ._

def show(tp: Type): Unit = println(s"$tp has prefix ${tp.prefix}")
class C[A] { type T = { def foo: A } }

{
  show(typeOf[C[Int]#T])
  show(WType1(typeOf[C[Int]]).T)
  show(WType2(typeOf[C[Int]]).T)
  show(WType3(typeOf[C[Int]]).T)
}
// Object{def foo: Int} has prefix <notype>
// C[Int]#T has prefix <notype>
// AnyRef{def foo: A} has prefix C[Int]
// AnyRef{def foo: Int} has prefix C[Int]

@retronym
Copy link
Member

retronym commented Jul 1, 2016

My test was just showing that the invariant tp.normalize =:= tp doesn't hold for RefinedTypeRef. I didn't dig further to find a case when that leads to an error in typechecking.

Conceptually, a refined type ref is the same as a type ref to a regular class. In those cases, we don't substitute the prefix into the members of a nested class, you have to use the prefix again to intepret the types of those members.

scala> trait M[A]; class C[A] { trait T extends M[A] { def a: A } }
defined trait M
defined class C

scala> val `C[Int]#T` = typeOf[C[Int]].memberType(TClass)
C[Int]#T: $r.intp.global.Type = C[Int]#T

scala> `C[Int]#T`.parents
res54: List[$r.intp.global.Type] = List(AnyRef, M[Int])

scala> val `C[Int]#T` = typeOf[C[Int]].memberType(TClass)
C[Int]#T: $r.intp.global.Type = C[Int]#T

scala> `C[Int]#T`.member(TermName("a")).info
res53: $r.intp.global.Type = => A

scala> `C[Int]#T`.memberType(`C[Int]#T`.member(TermName("a")))
res55: $r.intp.global.Type = => Int

@paulp
Copy link
Contributor Author

paulp commented Jul 1, 2016

you have to use the prefix again to intepret the types of those members.

I guess I'm wondering exactly how often that happens when it's necessary. It's hard to believe these sorts of tweaks will ever converge. But my advocacy has even less point than usual here, so I'll return to the observation deck.

@retronym
Copy link
Member

retronym commented Jul 1, 2016

This still needs some sorting out, but I've made some progress here: 2.12.x...retronym:review/5041

A base type sequence of an existential type is computed by mapping maybeRewrap element-wise over the BTS of the underlying type. maybeRewrap re-uses the same existential type if the new type to wrap is =:= to the old underlying type. For the first element of the sequence, we have newtp: RefinementTypeRef, which is =:= to the old underlying (a RefinedType). However, there is a subtle difference between RefinementTypeRef and RefinedType in the context of BaseTypeSeq; the latter are used for lazy computation of LUBs and their type symbols transparently forward to that of the parents. So we really want to rewrap the RefinementTypeRef verbatim.

The status quo:

scala> def foreachBaseType[A](tp: Type)(f: (Int, BaseTypeSeq) => A): List[A] = { val bts = tp.baseTypeSeq; bts.toList.indices.toList.map(i => f(i, bts)) }
foreachBaseType: [A](tp: $r.intp.global.Type)(f: (Int, $r.intp.global.BaseTypeSeq) => A)List[A]

scala> trait M[A]; type T = (M[Int] with M[X]) forSome { type X }
warning: there was one feature warning; re-run with -feature for details
defined trait M
defined type alias T

scala> val existential = typeOf[T].dealias
existential: $r.intp.global.Type = M[Int] with M[X] forSome { type X }

scala> val underlying = existential.underlying
underlying: $r.intp.global.Type = M[Int] with M[X]

scala> underlying.baseTypeSeq
res0: $r.intp.global.BaseTypeSeq = BTS(M[Int] with M[X],M[X] with M[Int],Object,Any)

scala> foreachBaseType(underlying)((i, bts) => (bts.rawElem(i), bts.rawElem(i).getClass.getSimpleName)).mkString("\n")
res3: String =
(M[Int] with M[X],RefinementTypeRef)
(M[X] with M[Int],RefinedType0)
(Object,ClassNoArgsTypeRef)
(Any,ClassNoArgsTypeRef)

scala> foreachBaseType(existential)((i, bts) => (bts.rawElem(i), bts.rawElem(i).underlying.getClass.getSimpleName)).mkString("\n")
res10: String =
(M[Int] with M[X] forSome { type X },RefinedType0)
(M[X] with M[Int] forSome { type X },RefinedType0)
(Object,ClassNoArgsTypeRef)
(Any,ClassNoArgsTypeRef)

scala> existential.asInstanceOf[{def maybeRewrap(tp: Type): Type}].maybeRewrap(underlying.baseTypeSeq.rawElem(0)).underlying.getClass
warning: there was one feature warning; re-run with -feature for details
res9: Class[_ <: $r.intp.global.Type] = class scala.reflect.internal.Types$RefinedType0

On my latest branch:

scala> foreachBaseType(underlying)((i, bts) => (bts.rawElem(i), bts.rawElem(i).getClass.getSimpleName)).mkString("\n")
res0: String =
(M[Int] with M[X],RefinementTypeRef)
(M[Int] with M[X],RefinedType0)
(Object,ClassNoArgsTypeRef)
(Any,ClassNoArgsTypeRef)

scala> foreachBaseType(existential)((i, bts) => (bts.rawElem(i), bts.rawElem(i).underlying.getClass.getSimpleName)).mkString("\n")
res3: String =
(M[Int] with M[X] forSome { type X },RefinementTypeRef)
(M[Int] with M[X] forSome { type X },RefinedType0)
(Object,ClassNoArgsTypeRef)
(Any,ClassNoArgsTypeRef)

@retronym
Copy link
Member

retronym commented Jul 1, 2016

An alternative approach to my fix above (which ensures that we preserve RefinementTypeRef elements of the BTS of an ExistentialType) could be to better encapsulate the way that lazily computed base type elements are encoded. This would amount to changing:

buf += intersectionType(minTypes.reverse)

... to something that distinguished this special RefinedType (e.g. another subclass of RefinedType, or an instance with a Symbol marked in some similar way), and then changing the logic in BaseTypeSeq that currently considers all RefinedTypes specially to be more selective.

@retronym
Copy link
Member

retronym commented Jul 6, 2016

Moving over to #5263.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

welcome hello new contributor!

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants