Open noresttherein opened 4 years ago
That might be a bug but A <: AssemblerT[_]
makes no sense in this example - you can just use A <: Assembler
. In case you need a workaround.
Assembler { type T = t } forSome { type T = t }
very much isn't the same as Assembler
.
This is a very stripped down example for the described issue, but there are many constructs which compile with one of the above and not the other and vice versa. My guess (at least intention behind experimenting with many variants of the above) is that the former is a subtype of Assembler
which has a definition of T
(so A#T forSome { type A <: AssemblerT[_] } =:= a.T
for any a :A
. I may be totally wrong, because expermenting with constructs like these yield a lot of compiler errors despite looking to my untrained eye like they should be allowed.
in Dotty,
12 | def get: A#T = ???
| ^
| A is not a legal path
| since it is not a concrete type
since projections on abstract types aren't allowed anymore
As I said, I may be completely wrong because I don't know the exact algorithm the typer uses to unify abstract type terms with type parameters of the argument, particularly when a type is 'fixed' as some abstact type _$1
and not unified further as in the example.
My understanding was that as a :AssemblerT[N]
and a.type =:= N
, then as seen from Values[a.type]
, A =:= a.type
and, after parameter substitution, A#T =:= a.type#T =:= N
.
It may entirely be that once again I was foiled by my poor understanding of path dependent types and a.type
(in type Values[a.type]is a different type than
a.type(in method component)`. If so, I apologize for taking your time, but I found this example so confusing I assumed it was a not intentional side effect at best. Of course, it is not your job to educate programmers on Scala :)
I'm not sure TBH. What you're saying makes sense, but we have to check the details (I haven't yet). Just wanted to say that abstract type members are semantically equivalent to existential types and in fact dotty is removing existential types without losing much expressivity because we can still use abstract type members.
Is there anything in your original code that prevents you from just using abstract type members as a workaround?
since projections on abstract types aren't allowed anymore
Ah right then there is also the issue that this is all unsound 😄
If by refactoring to abstract type members you mean only Values[A <: Assembler]
, then I alread did it because of this issue. I tried to go with the above because I wanted to somehow eliminate casts A#T.asInstanceOf[a.T]
without mandating A <: Singleton
.
What got me somewhat worried though is the mention that projections on all abstract types aren't allowed in Dotty (ergo, at some point, in scala). If I understand correctly then, I won't be able to use A#T
even inside Values[A <: Assembler]
? It would hit me very hard in ways I can't see workarounds for right of the bat. I would gladly elaborate, but this probably isn't the best place for it.
That bane would become a boon however if I were able to express 'A <: Assembler where the member type T has a concrete definition' (which indirectly inspired the posting of this issue). If I could put bounds enforcing a1.T =:= a2.T
for all a1, a2 :A
, where A
is a type parameter, it would actually clean a lot of code in most of my projects which currently rely on 'safe' casting.
@joroKr21 noticed this looks like a duplicate of #2346
reproduction steps
problem
expectation
Typer should(?) resolve
A#T forSome { type A = a.type }
toa.type#T => a.T => N
.