-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
No warning on uncheckable isInstanceOf call #11834
Comments
@abgruszecki Nice counter-example. I'm wondering whether the compiler should check that |
I think one justification for this check is the locality of reasoning. If the code only involves |
I think we're perfectly capable of local reasoning - we just cannot assume that if a value is of types type structA = { type A_X }
// A[X] translated to structA & { type A_X <: X }
type structB = { type B_X; type A_X <: this.B_X }
// B[X] translates analogously to A[X]
// note that forall X, (translated B[X]) <: (translated A[X]) |
From the perspective of soundness, there can be many designs that make sense. However, IMHO, the design with the check is more intuitive and usable than others in practice. |
Here it depends on how we read the contract below: trait A[+X]
class B[+X](val x: X) extends A[X] If we think from programmer's perspective, we may think the encoding captures only part of the contract. A possible enhancement of the contract is the following:
|
If you're suggesting to forbid definitions like |
It's easy to misread this when the types are named the same. The issue becomes more apparent if you use different names: trait A[+X]
class B[+Y](val x: Y) extends A[Y] Now we can see there is no reason that A possible solution I've always found attractive would be to bring back trait A[type +X]
class B[type +X](val x: X) extends A[X] Above, |
Would that give all type members the ability to declare their variance, or only
I assume that today the user could (or should) be able to enforce that X doesn't get multiple instantiations by making B final or, harder, make A sealed (and reason about the known children). Also, are those two enhancements different? I couldn't think of an example that satisfies one and not the other... |
IIRC, variance of
You mean Fengyun's and Lionel's suggestion? Lionel is arguing for something similar to what Fengyun is suggesting, but with the ability to opt into the behaviour. There's also the matter that Lionel is proposing to make type parameters with |
No. In my proposal, just like in current Scala, a variance annotation affects the variance of the type constructor, that's it. Type members have no business being variant as far as I can tell. If you disagree, would you be able to describe a reasonable semantics for variant members, as well as interesting use cases for them? |
No (and it's more of an aside) I meant between Fengyun's two "forall" examples - I thought they must intended different things but I couldn't think of a differing example.
I don't know. 😄 There was a desire to unify type parameters with type members, with type parameters desugaring to type members, but that leaves nowhere for variance to be defined, right? And then that project in Dotty was aborted and I haven't found out why. With DOT only having type member, I guess we never model variance in any of the soundness proofs? Do any of the DOT extensions add variance? |
Yeah you are right that at some point, IIRC, Dotty had a concept of type parameters desugaring into type members which had variance associated to them. But I think that was a conceptual mistake, or at the very least a conflation of unrelated concepts.
I think declaration-site variance of type constructors is not essential, but more of a derived concept. It can be even defined without reference to an "intrinsic" concept of type parameters. In DOT, we'd consider a definition |
Huh, thanks! I guess that also does away with having to model all the variance checks that class type-checking does, making sure variance is honoured in type parameter usage in its methods. |
Sure, pushing this approach to its logical conclusion, we end up basically treating variance as a thin layer of syntax sugar over Java-style use-site variance (where there are no restrictions on type parameter use). It's helpful to boil things down this way to understand the relation of variance to the underlying theory. But proper declaration-site variance is still useful, from a user point of view 🙂 |
I wonder whether we can instead flag this line as an error. class C[+X](x: Any) extends B[Any](x) with A[X] The principle would be that we don't allow variance reversal where we have parents A[X], B[Y], with Not sure about the precise rule, but it seems that it's a lot more reasonable to continue to support reasoning about the pattern matches than to continue supporting such inheritance patterns. |
That's mentioned in:
Which I think refers back to your comment in scala/bug#7093 (comment), which I think might refer to <2.13 collections? So perhaps we can do this now, which would make this simpler to think about. |
Note that we already emit an error if B is a case class: trait A[+X]
case class B[X](x: X) extends A[X]
class C[X](x: Any) extends B[Any](x) with A[X]
// ^
//illegal inheritance: class C inherits conflicting instances of non-variant base trait A.
//
// Direct basetype: A[X]
// Basetype via case class B: A[Any] This is implemented in https://github.com/lampepfl/dotty/blob/30ce9a5256494d60ae7249a004a18e03e433faf1/compiler/src/dotty/tools/dotc/typer/RefChecks.scala#L776-L785 These were both implemented in #4013 |
This is something that came up when we were thinking on how to encode class type parameters as type members for GADT purposes. It feels like there were cases of code that actually depended on being able to define classes like class C[+X](x: Any) extends B[Any](x) with A[X] But I cannot recall any specific examples. From the perspective of type members, the way I understand type A[+X] <: { type X_A <: X }
type B[X] <: A[X] & { type X_B = X }
type C[X] <: B[Any] & A[X] Take a careful look at how type AltB[X] <: A[X] & { type X_B = X ; type X_A = X_B }
type AltC[X] <: A[X] & AltB[Any] This alternative version of So I don't know if this is an argument for doing things one way or another, but at least in DOT, we have a way of expressing both versions of |
I've found an example where the current implementation of trait A[+X]
trait B extends A[B]
class C extends B with A[C] In the original this is Iterable, Seq and List, with List refining that it not only rebuilds from Seqs but from Lists specifically. I'm struggling with the implementation to find a way to make it allow Seq -> List, but disallow X -> Any & X. Perhaps it's just not doable with |
I think we are all still scratching our heads way too much to be able to give advice on specifics like that. 🤯 |
Why do you want to do that? FWIW, the type |
Thanks. I think I found what I'm looking for: for a covariant type parameter I want to allow new instantiations that are subtypes, so from <: Seq to <: List, but not from <: T to <: String. I mean I found the thing to look for, now I just need to figure out how to find it, in the API 🙂 trait A[+X]
class B[+Y](val value: Y) extends A[Y]
class C[+Z](str: String) extends B[String](str) with A[Z]
// A[T] = A { type X <: T }
// B[T] = B { type X <: Y; type Y <: T }
// C[T] = C { type X <: (Y & Z); type Y <: String; type Z <: T }
//
// C[T] ~> B[String] ~> A[String] ~> String // via middle=B
// C[T] ~> A[T] ~> T // via base=A
// T <: String = false
trait Iter[+Coll]
trait Seq extends Iter[Seq]
class List extends Seq with Iter[List]
// Iter[T] = Iter { type Coll <: T }
// Seq = Seq { type Coll <: Seq }
// List = List { type Coll <: (Seq & List) }
//
// List ~> Seq ~> Iter[Seq] ~> Seq // via middle=Seq
// List ~> Iter[List] ~> List // via base=Iter
// List <: Seq = true Edit: Nevermind... it's not |
@abgruszecki discovered that Kotlin also requires "invariant refinements", in the following gist covariant type parameter |
Yes you are right! It's something we've actually just noticed and mentioned in the introductory material of our latest paper (in submission – let me know if you want me to send you a preprint). |
Gladly, thank you. |
Minimized code
Output
Compiles without warning, explodes at runtime.
Expectation
A warning about the
.isInstanceOf
test should be emitted.Adapted from https://github.com/lampepfl/dotty/blob/master/tests/neg/i3989c.scala#L1.
/cc @liufengyun it seems that additional tweaking of how GADTs are used for checking
.isInstanceOf
is necessary.The text was updated successfully, but these errors were encountered: