-
Notifications
You must be signed in to change notification settings - Fork 21
Dependent types will make you say "wha....." #8177
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
Comments
Imported From: https://issues.scala-lang.org/browse/SI-8177?orig=1 |
@paulp said: 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();
} |
@retronym said: 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:
The The second symbol for // 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 Okay, git tells me that was in the aborted attempt at #8071 |
@retronym said (edited on Jan 25, 2014 11:07:18 AM UTC): Note that one of the tests (from the comments of #7753) is stuck in pending-ville. |
@retronym said: def nextBase = {
val base = if (clazz.isRefinementClass) {
pre.baseTypeSeq.toList.find(_ <:< clazz.info).getOrElse(NoType)
} else pre baseType clazz
base.deconst
} |
@retronym said: 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 |
@paulp said: |
@retronym said: 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 |
@retronym said: 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 |
@paulp said:
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. |
@retronym said: 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. |
@retronym said (edited on Jan 26, 2014 10:21:12 PM UTC): 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) |
@paulp said (edited on Jan 27, 2014 1:43:21 AM UTC): |
@paulp said: |
@retronym said: 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. |
@retronym said: 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. |
@retronym said: 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. |
@retronym said: 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. |
@paulp said: 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. |
@paulp said:
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. |
@paulp said: 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 }
} |
@adriaanm said: It compiles for me with this change: implicit def indexedIsIndexable[A0] = new Indexable[Indexed[A0]] { type A = A0 } |
@paulp said: 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 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. |
@retronym said: 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 }
} |
@adriaanm said:
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 |
@adriaanm said: 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 } |
@adriaanm said: |
@adriaanm said (edited on Feb 6, 2014 2:42:04 AM UTC): 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
|
@paulp said (edited on Feb 6, 2014 4:17:34 AM UTC): 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. |
@adriaanm said: 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 |
@adriaanm said: 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 |
@paulp said: 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
**/ |
@adriaanm said (edited on Feb 6, 2014 7:39:45 PM UTC): 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)) |
@paulp said (edited on Feb 6, 2014 8:10:05 PM UTC): 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? |
@paulp said: |
@adriaanm said: 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 |
@adriaanm said (edited on Feb 7, 2014 1:45:15 AM UTC): |
@adriaanm said: |
@paulp said: |
@paulp said: |
@retronym said:
A fact that's also apparent from the tabs in the test case :) |
@retronym said: |
@adriaanm said (edited on Feb 7, 2014 10:11:54 PM UTC): |
@adriaanm said: |
@adriaanm said: [ 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 |
@adriaanm said: https://github.com/adriaanm/scala/compare/t6493?expand=1 see also: https://github.com/adriaanm/scala/compare/t8177-wip?expand=1 |
@paulp said (edited on Feb 8, 2014 3:52:44 AM UTC): @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. |
@adriaanm said: |
@adriaanm said: |
@adriaanm said: |
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:
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:
The text was updated successfully, but these errors were encountered: