-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Fix #10994: align typed pattern syntax with Scala 2 #11023
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
Conversation
In Scala 2, a typed pattern `p: T` restricts that `p` can only be a pattern variable. In Dotty, scala#6919 allows `p` to be any pattern, in order to support pattern matching on generic number literals. This PR aligns the syntax with Scala 2 by stipulating that in a typed pattern `p: T`, either - `p` is a pattern variable, or - `p` is a number literal
The test case `tests/explicit-nulls/neg/strip.scala` specify that null unions inside intersection types should work. After changing the scrutinee type of the extractor `OrNull` it is no longer the case. Changing the scrutinee type is still justified because it agrees with the name as well as the usage in `Types.scala`. In contrast, in `Typer.scala`, the logic is more clear without using `OrNull`.
TIL. But this seems useful:
or
OK I glanced at the ticket but I don't know what a fork pattern is. 🍴 |
I assume it meant using a custom extractor, like the following: scala> object Fork { def unapply[T](x: T): (x.type, x.type) = (x,x) }
// defined object Fork
scala> List(23, 47) match { case Fork(List(a, _), List(_, b)) => println(s"it works! $a, $b") }
it works! 23, 47 or for your example: scala> Array(1,2,3.0) match { case Fork(Array(1,2,3), _: Array[Int]) => } // MatchError |
This seems to be a marginal use case because
The use cases found in the compiler seem to be an abuse of the feature. There is concern that the feature might lead to more difficult to understand code. Meanwhile, the feature gives rise to problems like lampepfl/dotty-feature-requests#159. Therefore, I think it's better to align with Scala 2 syntax. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code change looks good to me.
the change to enable nested patterns within a typetest came from the generic number literals feature, which has been suspended until after 3.0.
The ability to use destructuring patterns inside a typetest pattern could be desirable in general but is probably a significant enough change on its own to require a full language proposal, what do you think?
Merging this will still support generic number literal patterns.
Co-authored-by: Jamie Thompson <bishbashboshjt@gmail.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why should we change this? It makes the language more complicated and less powerful?
The reason is to make sure pattern match code is always simple and easily understandable. So far we have seen no valid usage of this feature, and the use cases found in the compiler show that it only complicates the code and hide refactoring opportunities in the code. |
This syntax can be useful for assistance with extracting phantom types, but that is probably a symptom of unapply patterns not having the syntax to extract the relevant type variable: enum Ev[F <: AnyKind]:
case HK() extends Ev[List]
def f[F <: AnyKind](ev: Ev[F]): Unit = ev match {
case Ev.HK(): Ev[f] =>
val m: f[Int] = List(1,2,3)
()
} |
In this example, it seems we already know Suppose there are cases where we do need to use typed pattern to extract a phantom type. I'd like to argue that such use cases can be misleading. The reason is that usually in patmat, the inferred type comes from the scrutinee. In the case above, the concrete type of |
The reason i used this example is because extracting |
It seems related to kind checking. The following code compiles: enum Ev[F[_]]:
case HK() extends Ev[List]
def f[F[_]](ev: Ev[F]): Unit = ev match {
case Ev.HK(): Ev[F] =>
val m: F[Int] = List(1,2,3)
()
} |
@liufengyun there are many situations in more advanced use cases, relating to dependent typing, where specifying the names of types that are being matched is very useful. It's not just for phantom types either. The fact you have not found such patterns in the compiler does not imply these are not useful. It's more likely that:
|
@LPTK Thanks for the feedback. We discussed it today, we need more careful evaluation and feedback about
As one example of type checking, look at the code below: enum Ev[F<:AnyKind]:
case EvInt() extends Ev[Int]
case EvStr() extends Ev[String]
// works
def f[F](ev: (F, Ev[F])): Int = ev match {
case (x: F, _: Ev[Int]) => x
case _ => 0
}
// works
def g[F](ev: (F, Ev[F])): Int = ev match {
case (x: F, Ev.EvInt()) => x
case _ => 0
}
// Does not work
def h[F](ev: (F, Ev[F])): Int = ev match {
case (x: F, Ev.EvInt(): Ev[Int]) => x // error
case _ => 0
} Both the pattern -- [E007] Type Mismatch Error: examples/A.scala:19:39 --------------------------
19 | case (x: F, Ev.EvInt(): Ev[Int]) => x // error
| ^
| Found: (x : F)
| Required: Int
|
| where: F is a type in method h with bounds >: (?1 : F) which is counter-intuitive. It suggests type checking needs to be enhanced to avoid surprising behavior. |
@liufengyun this just sounds like a missing case in the GADT reasoning code. Maybe the assumption of the person who wrote the code was that there was no more type info to get on the LHS of a type pattern. Maybe @abgruszecki can tell us more. At any rate, there's no reason that it should behave differently than the following, which works: case (x: F, _: (Ev.EvInt & Ev[Int])) => x |
I agree that in principle nested typecase patterns behave the same as intersection types. It's clear what GADT logic should do with patterns like that, it just was never tested with them. The idea that "the assumption of the person who wrote the code was that there was no more type info to get on the LHS of a type pattern" is a good one, that'd be my hunch as well. |
Fix #10994: align typed pattern syntax with Scala 2
In Scala 2, a typed pattern
p: T
restricts thatp
can only be apattern variable.
In Dotty, #6919 allows
p
to be any pattern, in order to supportpattern matching on generic number literals.
This PR aligns the syntax with Scala 2 by stipulating that in a typed
pattern
p: T
, eitherp
is a pattern variable, orp
is a number literal