-
Notifications
You must be signed in to change notification settings - Fork 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
Implement @covers annotation for partial irrefutable specification #11186
Conversation
9794dcd
to
53db02e
Compare
Oh, nice @liufengyun! But why did those exhaustivity checkfiles change? |
Thanks Dale. The changes are due to the fact that we re-ordered some code: decompose a sealed type a little earlier in the operation. The result is still valid. |
I don't see how we could use this feature in combination with the implementation of |
@nicolasstucki For def unapply(x: S): Option[x.type & T] @covers[S] |
One floating idea is to introduce another annotation @branch[Num]
opaque type Nat <: Int = Int
@branch[Num]
opaque type Neg <: Int = Int
type Num = Int
object Nat:
def unapply(x: Num): Option[Nat] @covers[Nat] = The annotation object Opt {
@branch[OptAlias]
type NonEmptyOpt[+T] <: Opt[T]
@branch[OptAlias]
val Empty: Opt[Nothing] = ???
type OptAlias[+T] = Opt[T]
def unapply[T](x: OptAlias[T]): Opt[T] @covers[NonEmptyOpt[T]] = x
} |
I belive it should be def unapply(x: S): Option[x.type & T] @covers[T] Still, it should be tested with some concrete examples. |
It isn't clear to me from the above how |
What kind of sequences you have in mind? The The |
literally any |
Thanks @SethTisue . I just had a look at object :+ {
/** Splits a sequence into init :+ last.
* @return Some((init, last)) if sequence is non-empty. None otherwise.
*/
def unapply[A, CC[_] <: Seq[_], C <: SeqOps[A, CC, C]](t: C with SeqOps[A, CC, C]): Option[(C, A)] =
if(t.isEmpty) None
else Some(t.init -> t.last)
} To make it work, we need to use match types in Scala 3: object :+ {
def unapply[A, CC[_] <: Seq[_], C <: SeqOps[A, CC, C]](t: C with SeqOps[A, CC, C]): Option[(C, A)] @covers[NonEmpty[C]] =
if(t.isEmpty) None
else Some(t.init -> t.last)
type NonEmpty[C] = C match {
case List[T] => ::[T]
case Stream[T] => Stream.Cons[T]
....
}
} |
But won't that only work for |
For those types other than |
Just realised that the way that this is done in Haskell is the data Choice a = Choice Bool a
pattern LeftChoice :: a -> Choice a
pattern LeftChoice a = Choice False a
pattern RightChoice :: a -> Choice a
pattern RightChoice a = Choice True a
{-# COMPLETE LeftChoice, RightChoice #-}
foo :: Choice Int -> Int
foo (LeftChoice n) = n * 2
foo (RightChoice n) = n - 2 https://downloads.haskell.org/~ghc/9.0.1/docs/html/users_guide/exts/pragmas.html#complete-pragma |
This is now deprecated, we have a better SIP. |
We propose two orthogonal annotations to enhance exhaustivity check for custom extractors and virtual ADT data types.
@covers[T]
: specifying that anunapply
is partially Irrefutable (implemented in this PR)@branch[T]
: defining a branch for a virtual ADT data typeT
(to be implemented)1. Partially Irrefutable Extractor
Currently, Scala only supports specifying that an
unapply
is irrefutable if its result type is of the formSome[T]
, or it is a subtype ofProductN[T1, .., TN]
, or it has a memberisEmpty
whose result typetrue
. In all such cases, the extractor is either irrefutable or refutable.The annotation
@covers[T]
provides a way to support specifying partial irrefutability. If the result type ofunapply
has the formU @covers[T]
, then the extractor is irrefutable when the scrutinee is a subtype ofT
.Example 1
Example 2
2. Virtual ADT
Sometimes, we might want to define a virtual ADT:
Int
The annotation
@branch
enables a library author to define a virtual ADT.Example 3
The following is an example that makes
Int
a virtual ADT.Example 4
This example is a tentative solution to the problem discussed in the contributor forum:
Links