-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Lack of type refinement for match case #15958
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
@abgruszecki could you confirm this should actually work? |
I don't see a good way of making this example work, unfortunately. Pattern matching can't really reconstruct the bound Perhaps we could make examples like this one work in the future, if we could somehow express that |
Alternatively, we will soon be merging a PR where we will reconstruct the bound |
@abgruszecki Thank you for the kind explanation. I'm not sure I fully follow it, unfortunately. In my example, the case pattern is actually For my own code, it would not be a problem to change the signature of def apply[M <: NatT](m: M)(using proof: IsLessThan[m.type, Succ[N]]): T once the PR you mentioned is merged. P.S.: Being able to force precise types would be nice. |
Even if the pattern is cons.apply[NatT](Succ(Zero())) If we did conclude that def foo[X](x: X): (X => Unit) =
x match
case _: String =>
// returning `String => Unit` is OK, since we believe that `X = String`
(str: String) => println(str.toLowerCase())
val fun : Any => Unit = foo[Any]("")
fun(0) // type error at runtime, 0 doesn't have .toLowerCase |
The PR I mentioned is #14754 . |
@abgruszecki Thank you! Interesting. Can unsoundness happen even if the return type is not contravariant in |
The return type is just one way to use the reconstructed bound. In general it simply doesn't need to be the case that |
@abgruszecki I still feel like there might be a condition related to contravariance which is required for unsoundness to happen. But fair enough. Should this issue be closed, then, since this is behaving as expected? |
@Bersier think about it this way: I'd keep this issue around. We should at least verify that the PR I mentioned does infer the bound on |
Currently in #14754 we only record equalities between singleton types. So we will not record a constraint like def apply[M <: NatT](m: M)(using proof: IsLessThan[m.type, Succ[N]]): T = m match
case Zero() => head
case m1: Succ[predM] =>
// ... we will be able to record the equality case class Cons[N <: NatT, T](head: T, tail: UniformTuple[N, T]) extends UniformTuple[Succ[N], T]:
def apply[M <: NatT](m: M)(using proof: IsLessThan[m.type, Succ[N]]): T = m match
case Zero() => head
case m1: Succ[predM] =>
// ???
val predM: predM = m1.n
val proof1: IsLessThan[m1.type, Succ[N]] = proof
val proof2: IsLessThan[Succ[predM], Succ[N]] = proof1
tail(predM)(using IsLessThan.reduction(using proof2)) The last line still cannot typecheck because what we want here is a |
So to make the approach that changes the definition to |
I checked this example again and found that we could add a type member to sealed trait NatT { type This <: NatT }
case class Zero() extends NatT {
type This = Zero
}
case class Succ[N <: NatT](n: N) extends NatT {
type This = Succ[n.This]
}
trait IsLessThan[+M <: NatT, N <: NatT]
object IsLessThan:
given base[M <: NatT]: IsLessThan[M, Succ[M]]()
given weakening[N <: NatT, M <: NatT] (using IsLessThan[N, M]): IsLessThan[N, Succ[M]]()
given reduction[N <: NatT, M <: NatT] (using IsLessThan[Succ[N], Succ[M]]): IsLessThan[N, M]()
sealed trait UniformTuple[Length <: NatT, T]:
def apply[M <: NatT](m: M)(using IsLessThan[m.This, Length]): T
case class Empty[T]() extends UniformTuple[Zero, T]:
def apply[M <: NatT](m: M)(using IsLessThan[m.This, Zero]): T = throw new AssertionError("Uncallable")
case class Cons[N <: NatT, T](head: T, tail: UniformTuple[N, T]) extends UniformTuple[Succ[N], T]:
def apply[M <: NatT](m: M)(using proof: IsLessThan[m.This, Succ[N]]): T = m match
case Zero() => head
case m1: Succ[predM] =>
val proof1: IsLessThan[m1.This, Succ[N]] = proof
val proof2: IsLessThan[Succ[m1.n.This], Succ[N]] = proof1
tail(m1.n)(using IsLessThan.reduction(using proof2)) Note that we use |
@Linyxus This is great. Now that I know about it and once the PR is in, I can make use of this pattern. I do wonder how many people would find such a solution when in a similar situation. I know that I would never have come up with this by myself. I guess documentation can go a long way. |
Compiler version
3.1.3
Minimized code
Output
Expectation
The code is expected to compile, as within that case branch,
M
is identified withSucc[predM]
.I opened a discussion thread on contributors.scala-lang.org about this, but didn't get any answer. So I'm tentatively filing this as a bug.
The text was updated successfully, but these errors were encountered: