Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dependent types will make you say "wha....." #8177

Closed
scabug opened this issue Jan 24, 2014 · 63 comments
Closed

Dependent types will make you say "wha....." #8177

scabug opened this issue Jan 24, 2014 · 63 comments
Assignees
Labels
dealias compiler isn't dealiasing when it should, or vice versa has PR typer
Milestone

Comments

@scabug
Copy link

scabug commented Jan 24, 2014

This is essentially #7753, which is probably #6161, but it seems like something to fix. It's really amazing how hard it is to use the language which is advertised in the brochure.

One thing I noticed while debugging this is that the logic for comparing prefixes is completely borked. Watch isSameType assessing the equivalence of two types and rejecting them though they resolve to the same thing. Up close you can see it following logic with code like this:

trait Trait { type Foo }
object Stable extends Trait { type Foo = Int }

scala, thinking: Is Stable.Foo the same type as scala.Int ? Hmm, Foo has prefix "Stable.type" but Int has prefix "scala.type". Verdict: REJECT

This reduced manifestation:

trait Thing { type A }
object IntThing extends Thing { type A = Int }

// compiles
package p1 {
  class View(val in: Thing { type A = Int }) {          def f(p: in.A): in.A = p }
  class SubView extends View(IntThing)       { override def f(p: in.A): in.A = p }
}
// does not compile
package p2 {
  class View[AIn](val in: Thing { type A = AIn }) {          def f(p: in.A): in.A = p }
  class SubView extends View[Int](IntThing)       { override def f(p: in.A): in.A = p }
}
// c.scala:12: error: method f overrides nothing.
// Note: the super classes of class SubView contain the following, non final members named f:
// def f(p: AIn): AIn
//   class SubView extends View[Int](IntThing)       { override def f(p: in.A): in.A = p }
//                                                                  ^
// one error found
@scabug
Copy link
Author

scabug commented Jan 24, 2014

Imported From: https://issues.scala-lang.org/browse/SI-8177?orig=1
Reporter: @paulp
See #6596

@scabug
Copy link
Author

scabug commented Jan 24, 2014

@paulp said:
Here is a revealing look at how various forms of the same type are viewed in a subclass. What do you think "T" means in B.

trait Thing { type A; var p: A = _ }
class A[T](final val x: Thing { type A = T }) {
  type Q = T

  def x1: T   = x.p
  def x2: Q   = x.p
  def x3: x.A = x.p
}
class B extends A[Int](null) {
  def y1 = x1
  def y2 = x2
  def y3 = x3

  // After typer:
  // def y1: Int = B.this.x1;
  // def y2: B.this.Q = B.this.x2;
  // def y3: B.this.x.A = B.this.x3;
  //
  // After uncurry:
  // def y1(): Int = B.this.x1();
  // def y2(): Int = B.this.x2();
  // def y3(): T = B.this.x3();
}

@scabug
Copy link
Author

scabug commented Jan 25, 2014

@paulp said:
I was able to fix this, and the code in my comment in #7753, without affecting either the original #7753 or #6161.

@scabug
Copy link
Author

scabug commented Jan 25, 2014

@retronym said:
Interested to see where you managed to finesse this!

Reducing the example from your comment and adding an explicit type:

trait Thing { type A; var p: A = _ }
class AA[T](final val x: HasA { type A = T }) {
  def foo: x.A = ???
}

class B extends AA[Int](null) {
  override def foo: B.this.x.A = super.foo
}

Leads to:

sandbox/test.scala:7: error: type mismatch;
 found   : B#7645.this.x#14044.A#14051
    (which expands to)  T#7883
 required: B#7645.this.x#14044.scala.A#15337
    (which expands to)  scala#21.this.Int#1760
  override def foo: B.this.x.A = super.foo

The scala. part looks like a bug in type printing, something must be using the prefix of the normalized type there.

The second symbol for A comes from the as-seen-from of the selection B#7645.this.x in the type of the overriding method.

     // Base class
    <method> <triedcooking> def foo#14047: AA#7882.this.x#14044.A#14051 = scala#21.this.Predef#1992.$qmark$qmark$qmark#7269

    // Sub class
    <method> override def foo#15326: B#7645.this.x#14044.A#15337 = B#7645.super.<error: method foo#14047>#25431

I guess you've ended up in:

  private def equalSymsAndPrefixes(sym1: Symbol, pre1: Type, sym2: Symbol, pre2: Type): Boolean = (
    if (sym1 == sym2)
      sym1.hasPackageFlag || sym1.owner.hasPackageFlag || phase.erasedTypes || pre1 =:= pre2
    else
      (sym1.name == sym2.name) && isUnifiable(pre1, pre2)
  )

and isEligibleForPrefixUnification. I seem to remember pushing those around in a previous excursion out of the trenches.

Okay, git tells me that was in the aborted attempt at #8071

scala/scala#3267

@scabug
Copy link
Author

scabug commented Jan 25, 2014

@retronym said (edited on Jan 25, 2014 11:07:18 AM UTC):
Here's my dabble: https://github.com/retronym/scala/compare/ticket;8177?expand=1

Note that one of the tests (from the comments of #7753) is stuck in pending-ville.

@scabug
Copy link
Author

scabug commented Jan 25, 2014

@retronym said:
Just pushed another commit that shows a way to make AsSeenFrom::classParameterAsSeen figure out the correct nextBase in light of refinement types.

        def nextBase = {
          val base = if (clazz.isRefinementClass) {
            pre.baseTypeSeq.toList.find(_ <:< clazz.info).getOrElse(NoType)
          } else pre baseType clazz
          base.deconst
        }

@scabug
Copy link
Author

scabug commented Jan 25, 2014

@retronym said:
Moving Paul's test case from #7753 over here, as I closed that ticket as not-a-bug.

trait CC[A]
trait HasElem[Repr] { type A }
object ccInt extends HasElem[CC[Int]] { type A = Int }
 
object Test {
  final class View[Repr, AIn](repr: Repr, val tc: HasElem[Repr] { type A = AIn }) {
    // For a time it's completely convinced AIn and tc.A are the same type.
    def equivalence1[B](implicit ev: B =:= AIn) = true
    def equivalence2[B](implicit ev: B =:= tc.A) = true
    def evidences = List(equivalence1[tc.A], equivalence1[AIn], equivalence2[tc.A], equivalence2[AIn]) // 4 out of 4
 
    // Foreshadow.
    def f1(p: AIn): AIn = p
    def f2(p: tc.A): tc.A = p
  }
 
  def xs: CC[Int] = ???
  val view = new View[CC[Int], Int](xs, ccInt)
 
  // No longer equivalent, transitivity lost.
  def g0 = List(view.equivalence1[Int], view.equivalence2[Int])
  // b.scala:33: error: Cannot prove that Int =:= Test.view.tc.A.
  //   def g0 = List(view.equivalence1[Int], view.equivalence2[Int])
  //                                                          ^
 
  def g1 = view f1 5  // compiles
  def g2 = view f2 5  // fails
  // b.scala:31: error: type mismatch;
  //  found   : Int(5)
  //  required: Test.view.tc.A
  //     (which expands to)  AIn
  //   def g2 = view f2 5  // fails
  //                    ^
}

This is the one that needs the tweak to nextBase described in the previous comment.

@scabug
Copy link
Author

scabug commented Jan 25, 2014

@paulp said:
Here's what I did. I'm not saying it's any better; really I don't believe there is a correct way to handle it given the compiler architecture.

https://github.com/paulp/scala/compare/issue;8177

@scabug
Copy link
Author

scabug commented Jan 26, 2014

@retronym said:
Noting that my patch can lead to SOE.

scala> trait T[A] { val foo: { type B = A } = ???; def bar(b: foo.B) = () }
defined trait T

scala> object O extends T[Int] { /*bar(0)*/ }
defined object O

scala> val bar = typeOf[T[_]].member(newTermName("bar"))
bar: $r.intp.global.Symbol = method bar

scala> typeOf[O.type].memberInfo(bar)
res0: $r.intp.global.Type = (b: O.foo.B)Unit

scala> val seenFromParamType = typeOf[O.type].memberInfo(bar).params.head.info
seenFromParamType: $r.intp.global.Type = O.foo.B

scala> val paramType = bar.info.params.head.info
paramType: $r.intp.global.Type = T.this.foo.B

scala> val seenFromParamTypePreInfo = seenFromParamType.asInstanceOf[TypeRef].pre.typeSymbol.info
seenFromParamTypePreInfo: $r.intp.global.Type = AnyRef{type B = Int}

scala> val paramTypePreInfo = paramType.asInstanceOf[TypeRef].pre.typeSymbol.info
paramTypePreInfo: $r.intp.global.Type = AnyRef{type B = A}

scala> seenFromParamTypePreInfo <:< paramTypePreInfo
KABOOM

The <:< added to nextBase can recursively call asSeenFrom by way of specializedSym.

@scabug
Copy link
Author

scabug commented Jan 26, 2014

@retronym said:
I can get around this one with my latest push.

But there is an additional bug with refinements defined in constructors. They are owned by the enclosing package class, rather than by the class. This means they are considered uninteresting for AsSeenFrom, by way of isBaseClassOfEnclosingClass. Geez Louise...

@scabug
Copy link
Author

scabug commented Jan 26, 2014

@paulp said:
If you want this to ever be a little robust, I suggest:

  1. write a method which returns the list of every type a type can ever reach (chasing all possible avenues of dealiasing, widening, bounds, type parameters, type arguments, prefixes, etc.) including all of the preceding as seen from the prefix under consideration.
  2. formulate in code whatever invariant asSeenFrom is ostensibly enforcing (that is: after a call to x.asSeenFrom(y, z), exactly what sorts of types should never appear in the result?)
  3. Check for non-empty intersections between 1) and 2) on the result of calls to asSeenFrom - not all the time, but under -Xdev or something. The pending tests probably offer a bunch of different triggers.

That's for finding where asSeenFrom isn't doing its job. Then there are a bunch more where the inputs to asSeenFrom are asking nonsense (involving type parameters which aren't in scope, This.Foo.x where there's no Foo to be seen anywhere, etc.) which will lead back to earlier bugs.

@scabug
Copy link
Author

scabug commented Jan 26, 2014

@retronym said:
Sounds like a good plan.

Seems like the known-to-be-a-hack definitions of refinements with a class-symbol is the real problem here. Most of the code that's not handling it in ASF really shouldn't have to be changed. This is compounded by the problems of incoherent owner-structures vs scoping in constructor param tpts.

@scabug
Copy link
Author

scabug commented Jan 26, 2014

@retronym said (edited on Jan 26, 2014 10:21:12 PM UTC):
The dodgy owner of the refinement class in the param accessor type is a verbatim copy of that in the constructor parameter's tpt. We really ought to run a TypeMap over that to spit out a type with the class as owner of refinements therein. Or, take another swing at:

      private def derivedTpt = {
        // For existentials, don't specify a type for the getter, even one derived
        // from the symbol! This leads to incompatible existentials for the field and
        // the getter. Let the typer do all the work. You might think "why only for
        // existentials, why not always," and you would be right, except: a single test
        // fails, but it looked like some work to deal with it. Test neg/t0606.scala
        // starts compiling (instead of failing like it's supposed to) because the typer
        // expects to be able to identify escaping locals in typedDefDef, and fails to
        // spot that brand of them. In other words it's an artifact of the implementation.
        val tpt = derivedSym.tpe_*.finalResultType.widen match {
          // Range position errors ensue if we don't duplicate this in some
          // circumstances (at least: concrete vals with existential types.)
          case ExistentialType(_, _)  => TypeTree() setOriginal (tree.tpt.duplicate setPos tree.tpt.pos.focus)
          case _ if mods.isDeferred   => TypeTree() setOriginal tree.tpt // keep type tree of original abstract field
          case tp                     => TypeTree(tp)
        }

If we do that, I'd want to make sure that the typer infers singleton types correctly in cases like:

val s = ""
class C(val ss: s.stype)

@scabug
Copy link
Author

scabug commented Jan 27, 2014

@paulp said (edited on Jan 27, 2014 1:43:21 AM UTC):
As your branch currently stands it works for
{code}val x: HasA { type A = T }{code}
but not for
{code}type HasAT[T] = HasA { type A = T } ; val x: HasAT[T]{code}
My branch, taking more of a brute force approach, does compile it.

@scabug
Copy link
Author

scabug commented Jan 27, 2014

@paulp said:
That can be fixed by throwing a dealias into isEligibleForPrefixUnification, but I don't know if that has other implications.

@scabug
Copy link
Author

scabug commented Jan 27, 2014

@retronym said:
Could you show the entire code for that still-failing example?

I tried:

trait HasA { type A; var p: A = _ }
class AA[T] {
  type HasAT[T] = HasA { type A = T }
  val x: HasAT[T] = ???
  def foo: x.A = ???
}
 
class B extends AA[Int] {
  override def foo: B.this.x.A = super.foo
}

There's a chance that it is only working because of my in-flight changes.

@scabug
Copy link
Author

scabug commented Jan 27, 2014

@retronym said:
I'm going to have to defer this one to 2.12. I think I can see most of the problem areas now, but more work is needed to find a palatable solution.

We can't fix this in 2.11.1, unfortunately, as will change erased signatures.

I'd advise to use named type aliases rather than in-line refinements in the meantime. Doubly so for refinements defined in primary constructors.

@scabug
Copy link
Author

scabug commented Jan 27, 2014

@retronym said:
BTW, we also need to extend our tests to things like:

class A[T](final val x: Thing { type A <: T }) {

I haven't tried, but I suspect that your approach might fail to unwrap these.

@scabug
Copy link
Author

scabug commented Jan 27, 2014

@retronym said:
Just to add another angle: #6596 demonstrates that refinement owners aren't consistent in joint and separate compilation.

Martin's states over there: "I believe owners for refinement types are a compiler-internal artefact only.".

That might be true, but the compiler does need to make sense of the owners for asSeenFrom to work, as this ticket shows.

@scabug
Copy link
Author

scabug commented Jan 27, 2014

@paulp said:
Here's a test case which compiles for me but not at the time I tried your 8177 branch.

trait HasA { type A; var p: A = _ }

package p1 {
  class AA[T](final val x: HasA { type A = T }) { def foo: x.A = ??? }
  class B extends AA[Int](null) { def bar: B.this.x.A = foo }
}

package p2 {
  object Types { type HasAT[T] = HasA { type A = T } }
  class AA[T](final val x: Types.HasAT[T]) { def foo: x.A = ??? }
  class B extends AA[Int](null) { def bar: B.this.x.A = foo }
}

In your branch I changed the last line of isEligibleForPrefixUnification to

case _                     => (tp ne tp.dealias) && isEligibleForPrefixUnification(tp.dealias)

Which allowed that to compile and all tests still passed, so if it introduces something bad it's on the untested side of the ledger.

@scabug
Copy link
Author

scabug commented Jan 27, 2014

@paulp said:

I'd advise to use named type aliases rather than in-line refinements in the meantime. Doubly so for refinements defined in primary constructors.

This will definitely not serve as a workaround for the problems I'm having, which still aren't fully illuminated. I'm still struggling with the minimization, as it seems to evaporate when I try to build it up from the ground.

@scabug
Copy link
Author

scabug commented Jan 27, 2014

@paulp said:
Well, maybe type aliases plus named classes will do it after all. It's a pretty heavy burden though.

package p {
  trait Indexed[A]
  trait Indexable[Repr] { type A }
  object Indexable {
    implicit def indexedIsIndexable[A0] : Indexable[Indexed[A0]] = new Indexable[Indexed[A0]] { type A = A0 }

    /** It works if we instead write that conversion as: */
    // class IndexedIsIndexable[A0] extends Indexable[Indexed[A0]] { type A = A0 }
    // implicit def indexedIsIndexable[A] : IndexedIsIndexable[A] = new IndexedIsIndexable[A]
  }

  object IndexedView {
    def apply[Coll](repr: Coll)(implicit tc: Indexable[Coll]): IndexedView[Coll, tc.A] = new IndexedView[Coll, tc.A](repr)(tc)
  }
  final class IndexedView[Coll, A](val repr: Coll)(implicit val tc: IndexableType[Coll, A]) {
    final def map[B](f: A => B): IndexedView[Coll, B] = ???
    final def force: Indexed[A] = ???
  }

  class Foo {
    def bufferAt(i: Int): String  = ???
    def indices: Indexed[Int]     = ???
    def contents: Indexed[String] = indices map bufferAt force
    // ./a.scala:23: error: type mismatch;
    //  found   : Int => String
    //  required: tc.A => ?
    //     def contents: Indexed[String] = indices map bufferAt force
    //                                                 ^
    // one error found
  }
}

package object p {
  implicit def liftIndexedView[Coll](repr: Coll)(implicit tc: Indexable[Coll]): IndexedView[Coll, tc.A] = IndexedView(repr)

  type IndexableType[Repr, A0] = Indexable[Repr] { type A = A0 }
}

@scabug
Copy link
Author

scabug commented Feb 3, 2014

@adriaanm said:
You're stripping the refinement from the implicit def indexedIsIndexable. Either leave off the return type, or repeat it in full.

It compiles for me with this change:

    implicit def indexedIsIndexable[A0] = new Indexable[Indexed[A0]] { type A = A0 }

@scabug
Copy link
Author

scabug commented Feb 3, 2014

@paulp said:
That is true, thanks, but I fear that's just a mistake I made trying to present a minimal example and not indicative of my real issues, which are tied to type aliases and prefixes.

Also, I assume you know why I can't leave off the return type. I don't write all these ungodly types 2-4 times for the joy of it.

1 time: instantiating the class
2 times: implicit visibility rules, have to have a return type or I will be punished later
3 times: bugs/pessimizations with implicit/value classes which drive me into separate the implicit from the class
4 times: hello there A, Repr, CC, let's put you and all your bounds on both the implicit method and the class and then also parameterize the type class. Okay paulp, don't change anything unless you love busywork. Get it right the first time.

You'd think this stuff would be enough to cure us all of using inheritance, because the only thing worse than the above is threading the type parameters and repeated repeated repeated bounds information through a string of superclasses.

@scabug
Copy link
Author

scabug commented Feb 3, 2014

@retronym said:
I'd probably go for a term alias to go with IndexableType. Sure, it's not such a big change, but you sound like a camel with a lot of straws on your back at the moment, so maybe removing one little one might help.

package p {
  trait Indexed[A]
  trait Indexable[Repr] { type A }
  object Indexable {
    implicit def indexedIsIndexable[A0] : IndexableType[Indexed[A0], A0] = IndexableType[Indexed[A0], A0]
  }
 
  object IndexedView {
    def apply[Coll](repr: Coll)(implicit tc: Indexable[Coll]): IndexedView[Coll, tc.A] = new IndexedView[Coll, tc.A](repr)(tc)
  }
  final class IndexedView[Coll, A](val repr: Coll)(implicit val tc: IndexableType[Coll, A]) {
    final def map[B](f: A => B): IndexedView[Coll, B] = ???
    final def force: Indexed[A] = ???
  }
 
  class Foo {
    def bufferAt(i: Int): String  = ???
    def indices: Indexed[Int]     = ???
    def contents: Indexed[String] = indices map bufferAt force
  }
}
 
package object p {
  implicit def liftIndexedView[Coll](repr: Coll)(implicit tc: Indexable[Coll]): IndexedView[Coll, tc.A] = IndexedView(repr)
 
  type IndexableType[Repr, A0] = Indexable[Repr] { type A = A0 }
  def IndexableType[Repr, A0]: IndexableType[Repr, A0] = new Indexable[Repr] { type A = A0 }
}

@scabug
Copy link
Author

scabug commented Feb 6, 2014

@adriaanm said:
Sorry to rewind all the way to the top, but regarding

scala, thinking: Is Stable.Foo the same type as scala.Int ?
Hmm, Foo has prefix "Stable.type" but Int has prefix "scala.type". Verdict: REJECT

Shouldn't this fallback to normalizePlus?

scala> trait Trait { type Foo }
defined trait Trait

scala> object Stable extends Trait { type Foo = Int }
defined object Stable

scala> typeOf[Stable.Foo] =:= typeOf[scala.Int]
res0: Boolean = true

@scabug
Copy link
Author

scabug commented Feb 6, 2014

@adriaanm said:
As a workaround for your rebellious thing, I suggest:

trait Thing { type A }
object IntThing extends Thing { type A = Int }
 
class View[T <: Thing](val in: T) {          def f(p: in.A): in.A = p }
class SubView extends View(IntThing)       { override def f(p: in.A): in.A = p }

@scabug
Copy link
Author

scabug commented Feb 6, 2014

@adriaanm said:
This confirms the hypothesis the brokenness is with refinement types (possibly in combination with type aliases).

@scabug
Copy link
Author

scabug commented Feb 6, 2014

@adriaanm said (edited on Feb 6, 2014 2:42:04 AM UTC):
Trying to get a closer look as follows:

scala> trait Thing { type A }
     | trait View[AIn] {
     |   val in: Thing { type A = AIn }
     |   type Bla = in.A // 
     | }

scala> typeOf[View[Int]].memberType(typeOf[View[Int]].member(newTypeName("Bla")))
res1: $r.intp.global.Type = AIn // AHA! should be Int

@scabug
Copy link
Author

scabug commented Feb 6, 2014

@paulp said (edited on Feb 6, 2014 4:17:34 AM UTC):
Right, which is why running things through asSeenFrom again works:

scala> typeOf[View[Int]].memberType(typeOf[View[Int]].member(newTypeName("Bla")))
res2: $r.intp.global.Type = AIn

scala> res2.asSeenFrom(typeOf[View[Int]], typeOf[View[Int]].typeSymbol)
res3: $r.intp.global.Type = Int

It would help if I better understood what is the resistance to more aggressive dealiasing. I don't mean stuff like "there will be cycles" or other implementation artifacts, nor "the undealiased types are desirable in error messages" which is valid but cosmetic, but whatever is the deeper objection. Because as far as I can see it, if there is a T from Foo[T] reachable in any way shape or form in the result of a whatever.asSeenFrom(Foo[Arg]) then when that T comes out it's going to be a bug. So I don't see the advantage of allowing it, ever. Which requires peeking inside type aliases, always.

@scabug
Copy link
Author

scabug commented Feb 6, 2014

@adriaanm said:
:-) /away bedtime

i'm very suspicious of the transform and the mis-aligned owners; we shouldn't be doing an tp.ASF(pre, clazz) only makes sense when tp.typeSymbol.owner isSubClass clazz

@scabug
Copy link
Author

scabug commented Feb 6, 2014

@adriaanm said:
dumping my brain/debugging session:

trait Thing { type A }
trait View[AIn] {
  val in: Thing { type A = AIn }
  type Bla = in.A // 
}

val view = typeOf[View[Int]]
val bla = view.member(newTypeName("Bla"))

view.memberType(bla)

Types$AliasNoArgsTypeRef(Types$Type).asSeenFrom(Types$Type, Symbols$Symbol) line: 661	
  this	Types$AliasNoArgsTypeRef  (id=6373)	View.this.Bla
  pre	Types$ClassArgsTypeRef  (id=6357)	 View[Int]
  clazz	Symbols$ClassSymbol  (id=6374)	trait View
  tp	Types$AliasNoArgsTypeRef  (id=6478)	 _4.Bla // the existential that captures View.this  <: View[Int] with Singleton
  
Types$class.existentialAbstraction(SymbolTable, List, Types$Type) line: 3662	
  tpe0	Types$AliasNoArgsTypeRef  (id=6478)	 _4.Bla
  
TypeMaps$normalizeAliases$.apply(Types$Type) line: 22	
  tp	Types$AliasNoArgsTypeRef  (id=6478)	_4.Bla
  
Types$AliasNoArgsTypeRef(Types$TypeRef).normalize() line: 2277	
  this: _4.Bla


Types$AliasTypeRef$class.betaReduce(Types$AliasTypeRef) line: 2183	
  this: _4.in.A
  
Types$AliasNoArgsTypeRef(Types$NoArgsTypeRef).transform(Types$Type) line: 2081	
  tp: AIn
...
Types$AbstractNoArgsTypeRef(Types$Type).asSeenFrom(Types$Type, Symbols$Symbol) line: 660	
  this: AIn
  pre: View.<refinement>.type
  clazz: <refinement of Thing> // UHOH, does not own AIn, nor do any of its superclasses

@scabug
Copy link
Author

scabug commented Feb 6, 2014

@paulp said:
Here's a small accompaniment.

trait Boo {
  type A1 >: Int <: Int
  type A2 = Int
}
class Foo(val in: Boo) {
  type A1 = in.A1
  type A2 = in.A2

  type B1 = A1
  type B2 = A2
}
object Baz extends Foo(new Boo { })

/**
scala> typeOf[Baz.type].members filter (_.isType) map (typeOf[Baz.type] memberType _ normalize)  >
Int
Baz.in.A1
Int
Baz.in.A1
**/

@scabug
Copy link
Author

scabug commented Feb 6, 2014

@adriaanm said (edited on Feb 6, 2014 7:39:45 PM UTC):
Normalize isn't supposed to change abstract types (even those equivalent to aliases), so this looks correct to me:

scala> typeOf[Baz.type].members filter (_.isType) map (x => (x, typeOf[Baz.type] memberType x normalize) )
warning: there were 1 feature warning(s); re-run with -feature for details
res0: Iterable[($r.intp.global.Symbol, $r.intp.global.Type)] = List(
(type B2,Int), (type B1,Baz.in.A1), (type A2,Int), (type A1,Baz.in.A1))

scala> typeOf[Baz.type].members filter (_.isType) map (x => (x, (typeOf[Baz.type] memberType x normalize) bounds) )
warning: there were 2 feature warning(s); re-run with -feature for details
res1: Iterable[($r.intp.global.Symbol, $r.intp.global.TypeBounds)] = List(
(type B2, >: Int <: Int), (type B1, >: Int <: Int), (type A2, >: Int <: Int), (type A1, >: Int <: Int))

@scabug
Copy link
Author

scabug commented Feb 6, 2014

@paulp said (edited on Feb 6, 2014 8:10:05 PM UTC):
I believe it's correct relative to "normalize isn't supposed to change abstract types", but if something doesn't change those abstract types, then it will never be correct by traditional views of correctness.

What is supposed to deal with all the types embedded in abstract types, like type parameters of enclosing classes and other abstract types which aren't visible outside the class?

@scabug
Copy link
Author

scabug commented Feb 6, 2014

@paulp said:
In that example you can fail to normalize the abstract type and still recover the concrete type later. But it's easy to create ones where that's not true, just include an enclosing type parameter like in most of the other examples under discussion.

@scabug
Copy link
Author

scabug commented Feb 7, 2014

@adriaanm said:
co-evolution == creation?

finally answered one of those cringe-inducing questions-to-self

    override def coevolveSym(pre1: Type): Symbol =
      if (pre eq pre1) sym else (pre, pre1) match {
        // don't look at parents -- it would be an error to override alias types anyway
        case (RefinedType(_, _), RefinedType(_, decls1)) => decls1 lookup sym.name
        // TODO: is there another way a typeref's symbol can refer to a symbol defined in its pre?
        // yup: they could both be `SingleType`s with an underlying RefinedType
        case (preOrig, preNew) if preOrig.isStable && preNew.isStable => preNew.decls lookup sym.name 

@scabug
Copy link
Author

scabug commented Feb 7, 2014

@adriaanm said (edited on Feb 7, 2014 1:45:15 AM UTC):
can probably just change the else branch in coevolveSym to pre1.decls lookup sym.name

@scabug
Copy link
Author

scabug commented Feb 7, 2014

@adriaanm said:
scala/scala#3482

@scabug
Copy link
Author

scabug commented Feb 7, 2014

@paulp said:
Now we're getting somewhere! Into test failure land no doubt, but still that's somewhere.

@scabug
Copy link
Author

scabug commented Feb 7, 2014

@paulp said:
Ooh that's a tastily small set of failures.

@scabug
Copy link
Author

scabug commented Feb 7, 2014

@paulp said:
#764 opened by mark harrah in april 2008. Nice.

@scabug
Copy link
Author

scabug commented Feb 7, 2014

@retronym said:

opened by mark harrah in april 2008.

A fact that's also apparent from the tabs in the test case :)

@scabug
Copy link
Author

scabug commented Feb 7, 2014

@retronym said:
@adriaan If you're evolving coevolution, perhaps you'll wind up fixing, or enabling a fix of, #6493 / scala/scala#2986

@scabug
Copy link
Author

scabug commented Feb 7, 2014

@adriaanm said (edited on Feb 7, 2014 10:11:54 PM UTC):
thanks for the pointer -- looks like it's [EDIT: not :-(] fixed! I'll push some additional test cases

@scabug
Copy link
Author

scabug commented Feb 8, 2014

@adriaanm said:
I spent all day on this, but I don't know how to generalize this fix. Clearly, the current approach is a hack.
Co-evolution should be pushed to AsSeenFromMap and SubstMap and only rebind symbols when going from TypeRef(pre, sym, args)
to TypeRef(pre', sym', args'), where pre is now "discarded" and thus it makes no sense to refer to the symbols it defines.
We don't have this information in the current approach, so we can't fix the first half of #6493, which requires a broader approach to rebinding these symbols. neg/t4137.scala is a good example of where we don't want to rebind: A#EPC2 and B#EPC2 are both valid typerefs, so when you're doing an ASF that rewrite a this type from A to B, you still don't want to change EPC2 (the one defined in A) to the other EPC2 (the one defined in B). Maybe a check like pre1.typeSymbolDirect.isNonBottomSubclass(pre2.typeSymbolDirect) could work... it's still feels like it pushes the hack into no-go territory

@scabug
Copy link
Author

scabug commented Feb 8, 2014

@adriaanm said:
Concretely, with a more liberal coevolveSym, we don't get both expected errors:

[ t8177 *$%<> ] scala/ $ qsc /Users/adriaan/git/scala/test/files/neg/t4137.scala  -uniqid -Xprint:typer
[[syntax trees at end of                     typer]] // t4137.scala
package <empty>#4 {
  abstract trait C#7686[T#7687] extends scala#20.AnyRef#2675;
  abstract trait A#7688[T#7689] extends scala#20.AnyRef#2675 {
    type EPC#7782[X1#7783] = C#7686[X1#7783];
    type EPC2#7784[X1#7785] = C#7686[X1#7785]
  };
  abstract trait B#7690[T#7691] extends AnyRef#2675 with A#7688[T#7691] {
    override type EPC#7787 = C#7686[T#7691];
    override type EPC2#7788[X1#7789 <: String#7202] = C#7686[X1#7789]
  }
}

/Users/adriaan/git/scala/test/files/neg/t4137.scala:9: error: overriding type EPC#7782 in trait A#7688, which equals [X1#7783]C#7686[X1#7783];
 type EPC#7787 has incompatible type
  override type EPC = C[T]
                ^
B#7690.this.type has type EPC2#7788 =(false)= type EPC2#7784
co-evolve: A#7688.this.type -> B#7690.this.type, type EPC2#7784 : [X1#7785]C#7686[X1#7785] -> type EPC2#7788 : [X1#7789 <: String#7202]C#7686[X1#7789]
one error found // should be two

@scabug
Copy link
Author

scabug commented Feb 8, 2014

@adriaanm said:
Thinking about it, this could work, but the memberType/memberInfo distinction may hit elsewhere:

https://github.com/adriaanm/scala/compare/t6493?expand=1

see also: https://github.com/adriaanm/scala/compare/t8177-wip?expand=1

@scabug
Copy link
Author

scabug commented Feb 8, 2014

@paulp said (edited on Feb 8, 2014 3:52:44 AM UTC):
I think you're hitting the natural ceiling on the "copy and mutate" approach to coherence. These constructs are too complex and too poorly managed to tolerate an ad hoc copy-on-demand approach to maintaining different views of the same type. Imagine if you will:

@immutable abstract class Type
@immutable abstract class Prefix extends Type
@immutable final case class TypeView(seenFrom: Prefix, tpe: Type) extends Type

Until such a day arrives, everyone should be thankful for whatever scraps of correctness they can scavenge. I'll certainly be glad to get these particular scraps. I thank you for the effort, while simultaneously regretting the percentage of it which goes to the chase-the-mutable monkey gods for whom no sacrifice will ever be enough.

@scabug
Copy link
Author

scabug commented Feb 10, 2014

@adriaanm said:
Agreed. I was thinking some more correctness scraps could be scavenged in ASF. It should never select a symbol on a prefix that isn't a member of this prefix -- we currently assume TypeRefs are always well-formed that way. A gross approximation could be sym.owner.isRefinementClass. Those symbols should not be trusted, and instead be looked up again.

@scabug
Copy link
Author

scabug commented Feb 11, 2014

@adriaanm said:
scala/scala#3482

@scabug
Copy link
Author

scabug commented Feb 13, 2014

@adriaanm said:
scala/scala#3516

@scabug scabug closed this as completed Feb 13, 2014
@scabug scabug added this to the 2.11.0-RC1 milestone Apr 7, 2017
@SethTisue SethTisue added the dealias compiler isn't dealiasing when it should, or vice versa label Nov 11, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
dealias compiler isn't dealiasing when it should, or vice versa has PR typer
Projects
None yet
Development

No branches or pull requests

3 participants