Open atennapel opened 3 years ago
GADT reasoning does not yet look at type members AFAIK. Here is an ugly workaround that wrangles the proofs manually:
sealed trait Ty {
type T
}
class TUnit() extends Ty {
type T = Unit
}
case object TUnit extends TUnit()
final case class TFun(dom: Ty, cod: Ty) extends Ty {
type T = dom.T => cod.T
}
def default(ty: Ty): ty.T = ty match {
case ty: (ty.type & TUnit) => (): ty.T
case ty: (ty.type & TFun) =>
val f = { (x: ty.dom.T) => default(ty.cod) }
f: ty.T
}
The reason I had to make TUnit
a class is that Dotty has limitations when it comes to intersections of singleton types.
However, this strangely raises a warning:
match may not be exhaustive.
It would fail on pattern case: TUnit(), TFun(_, _)
Maybe @liufengyun can tell us why.
Going back to the type-parameter version: In principle, you should be able to write:
def default[T](ty: Ty[T]): T = ty match {
case TUnit => ()
case TFun(dom, cod): TFun[a, b] => { (x: a) => default(cod) }
}
but it does not work:
Found: a => b
Required: T
Maybe @abgruszecki has an idea why.
However, this strangely raises a warning:
match may not be exhaustive. It would fail on pattern case: TUnit(), TFun(_, _)
Maybe @liufengyun can tell us why.
The reason is that the exhaustivity check always widens the singleton type of selector first -- after widening the subtyping does not hold any more.
@liufengyun I seem to remember that this used to be different, wasn't it? It would be nice if there was at least a special-case to allow refining what is matched to subtype the singleton type of the scrutinee, otherwise we lose too much information in type-heavy programming patterns.
@liufengyun I seem to remember that this used to be different, wasn't it? It would be nice if there was at least a special-case to allow refining what is matched to subtype the singleton type of the scrutinee, otherwise we lose too much information in type-heavy programming patterns.
I think it's possible to do better here. I'll give it a try.
@LPTK I proposed a tentative solution in #12549 -- however, the scrutinee has to be explicitly annotated:
sealed trait Ty {
type T
}
class TUnit() extends Ty {
type T = Unit
}
case object TUnit extends TUnit()
final case class TFun(dom: Ty, cod: Ty) extends Ty {
type T = dom.T => cod.T
}
def default(ty: Ty): ty.T = (ty: ty.type & Ty) match {
case a: (ty.type & TUnit) => (): a.T
case a: (ty.type & TFun) =>
val f = { (x: a.dom.T) => default(a.cod) }
f: a.T
}
@liufengyun Cool, thanks for taking the time to look into this!
Compiler version
3.0
Minimized code
Output
Expectation
I expected the result type inside of the match to be specialized to
Unit
forTUnit
anddom.T => cod.T
forTFun
, but this is not the case. It may be the case that I expect too much though.With GADTs the result types are specialized, but I cannot type the
x
in the case ofTFun
, because I have no access to the type parameters ofTFun
: