```ucm:hide scratch/main> builtins.merge ``` # Basics ## non-exhaustive patterns ```unison:error unique type T = A | B | C test : T -> () test = cases A -> () ``` ```unison:error unique type T = A | B test : (T, Optional T) -> () test = cases (A, Some _) -> () (A, None) -> () (B, Some A) -> () (B, None) -> () ``` ## redundant patterns ```unison:error unique type T = A | B | C test : T -> () test = cases A -> () B -> () C -> () _ -> () ``` ```unison:error unique type T = A | B test : (T, Optional T) -> () test = cases (A, Some _) -> () (A, None) -> () (B, Some _) -> () (B, None) -> () (A, Some A) -> () ``` # Uninhabited patterns match is complete without covering uninhabited patterns ```unison unique type V = test : Optional (Optional V) -> () test = cases None -> () Some None -> () ``` uninhabited patterns are reported as redundant ```unison:error unique type V = test0 : V -> () test0 = cases _ -> () ``` ```unison:error unique type V = test : Optional (Optional V) -> () test = cases None -> () Some None -> () Some _ -> () ``` # Guards ## Incomplete patterns due to guards should be reported ```unison:error test : () -> () test = cases () | false -> () ``` ```unison:error test : Optional Nat -> Nat test = cases None -> 0 Some x | isEven x -> x ``` ## Complete patterns with guards should be accepted ```unison:error test : Optional Nat -> Nat test = cases None -> 0 Some x | isEven x -> x | otherwise -> 0 ``` # Pattern instantiation depth Uncovered patterns are only instantiated as deeply as necessary to distinguish them from existing patterns. ```unison:error unique type T = A | B | C test : Optional (Optional T) -> () test = cases None -> () Some None -> () ``` ```unison:error unique type T = A | B | C test : Optional (Optional T) -> () test = cases None -> () Some None -> () Some (Some A) -> () ``` # Literals ## Non-exhaustive Nat ```unison:error test : Nat -> () test = cases 0 -> () ``` Boolean ```unison:error test : Boolean -> () test = cases true -> () ``` ## Exhaustive Nat ```unison test : Nat -> () test = cases 0 -> () _ -> () ``` Boolean ```unison test : Boolean -> () test = cases true -> () false -> () ``` # Redundant Nat ```unison:error test : Nat -> () test = cases 0 -> () 0 -> () _ -> () ``` Boolean ```unison:error test : Boolean -> () test = cases true -> () false -> () _ -> () ``` # Sequences ## Exhaustive ```unison test : [()] -> () test = cases [] -> () x +: xs -> () ``` ## Non-exhaustive ```unison:error test : [()] -> () test = cases [] -> () ``` ```unison:error test : [()] -> () test = cases x +: xs -> () ``` ```unison:error test : [()] -> () test = cases xs :+ x -> () ``` ```unison:error test : [()] -> () test = cases x0 +: (x1 +: xs) -> () [] -> () ``` ```unison:error test : [()] -> () test = cases [] -> () x0 +: [] -> () ``` ## Uninhabited `Cons` is not expected since `V` is uninhabited ```unison unique type V = test : [V] -> () test = cases [] -> () ``` ## Length restrictions can equate cons and nil patterns Here the first pattern matches lists of length two or greater, the second pattern matches lists of length 0. The third case matches when the final element is `false`, while the fourth pattern matches when the first element is `true`. However, the only possible list length at the third or fourth clause is 1, so the first and final element must be equal. Thus, the pattern match is exhaustive. ```unison test : [Boolean] -> () test = cases [a, b] ++ xs -> () [] -> () xs :+ false -> () true +: xs -> () ``` This is the same idea as above but shows that fourth match is redundant. ```unison:error test : [Boolean] -> () test = cases [a, b] ++ xs -> () [] -> () xs :+ true -> () true +: xs -> () _ -> () ``` This is another similar example. The first pattern matches lists of length 5 or greater. The second matches lists of length 4 or greater where the first and third element are true. The third matches lists of length 4 or greater where the final 4 elements are `true, false, true, false`. The list must be exactly of length 4 to arrive at the second or third clause, so the third pattern is redundant. ```unison:error test : [Boolean] -> () test = cases [a, b, c, d, f] ++ xs -> () [true, _, true, _] ++ _ -> () _ ++ [true, false, true, false] -> () _ -> () ``` # bugfix: Sufficient data decl map ```unison unique type T = A unit2t : Unit -> T unit2t = cases () -> A ``` ```ucm scratch/main> add ``` Pattern coverage checking needs the data decl map to contain all transitive type dependencies of the scrutinee type. We do this before typechecking begins in a roundabout way: fetching all transitive type dependencies of references that appear in the expression. This test ensures that we have fetched the `T` type although there is no data decl reference to `T` in `witht`. ```unison witht : Unit witht = match unit2t () with x -> () ``` ```unison unique type V = evil : Unit -> V evil = bug "" ``` ```ucm scratch/main> add ``` ```unison:error withV : Unit withV = match evil () with x -> () ``` ```unison unique type SomeType = A ``` ```ucm scratch/main> add ``` ```unison unique type R = R SomeType get x = match x with R y -> y ``` ```unison unique type R = { someType : SomeType } ``` # Ability handlers ## Exhaustive ability handlers are accepted ```unison structural ability Abort where abort : {Abort} a result : '{e, Abort} a -> {e} a result f = handle !f with cases { x } -> x { abort -> _ } -> bug "aborted" ``` ```unison structural ability Abort where abort : {Abort} a unique type T = A | B result : '{e, Abort} T -> {e} () result f = handle !f with cases { T.A } -> () { B } -> () { abort -> _ } -> bug "aborted" ``` ```unison structural ability Abort where abort : {Abort} a result : '{e, Abort} V -> {e} V result f = impl : Request {Abort} V -> V impl = cases { abort -> _ } -> bug "aborted" handle !f with impl ``` ```unison structural ability Abort where abort : {Abort} a structural ability Stream a where emit : a -> {Stream a} Unit handleMulti : '{Stream a, Abort} r -> (Optional r, [a]) handleMulti c = impl xs = cases { r } -> (Some r, xs) { emit x -> resume } -> handle !resume with impl (xs :+ x) { abort -> _ } -> (None, xs) handle !c with impl [] ``` ## Non-exhaustive ability handlers are rejected ```unison:error structural ability Abort where abort : {Abort} a abortWithMessage : Text -> {Abort} a result : '{e, Abort} a -> {e} a result f = handle !f with cases { abort -> _ } -> bug "aborted" ``` ```unison:error structural ability Abort where abort : {Abort} a unique type T = A | B result : '{e, Abort} T -> {e} () result f = handle !f with cases { T.A } -> () { abort -> _ } -> bug "aborted" ``` ```unison:error unique ability Give a where give : a -> {Give a} Unit unique type T = A | B result : '{e, Give T} r -> {e} r result f = handle !f with cases { x } -> x { give T.A -> resume } -> result resume ``` ```unison:error structural ability Abort where abort : {Abort} a structural ability Stream a where emit : a -> {Stream a} Unit handleMulti : '{Stream a, Abort} r -> (Optional r, [a]) handleMulti c = impl : [a] -> Request {Stream a, Abort} r -> (Optional r, [a]) impl xs = cases { r } -> (Some r, xs) { emit x -> resume } -> handle !resume with impl (xs :+ x) handle !c with impl [] ``` ## Redundant handler cases are rejected ```unison:error unique ability Give a where give : a -> {Give a} Unit unique type T = A | B result : '{e, Give T} r -> {e} r result f = handle !f with cases { x } -> x { give _ -> resume } -> result resume { give T.A -> resume } -> result resume ``` ## Exhaustive ability reinterpretations are accepted ```unison structural ability Abort where abort : {Abort} a abortWithMessage : Text -> {Abort} a result : '{e, Abort} a -> {e, Abort} a result f = handle !f with cases { x } -> x { abort -> _ } -> abort { abortWithMessage msg -> _ } -> abortWithMessage ("aborting: " ++ msg) ``` ```unison structural ability Abort a where abort : {Abort a} r abortWithMessage : a -> {Abort a} r result : '{e, Abort V} a -> {e, Abort V} a result f = impl : Request {Abort V} r -> {Abort V} r impl = cases { x } -> x { abort -> _ } -> abort handle !f with impl ``` ## Non-exhaustive ability reinterpretations are rejected ```unison:error structural ability Abort where abort : {Abort} a abortWithMessage : Text -> {Abort} a result : '{e, Abort} a -> {e, Abort} a result f = handle !f with cases { x } -> x { abortWithMessage msg -> _ } -> abortWithMessage ("aborting: " ++ msg) ``` ## Hacky workaround for uninhabited abilities Although all of the constructors of an ability might be uninhabited, the typechecker requires at least one be specified so that it can determine that the ability should be discharged. So, the default pattern match coverage checking behavior of prohibiting covering any of the cases is problematic. Instead, the pattern match coverage checker will require that at least one constructor be given, even if they are all uninhabited. The messages here aren't the best, but I don't think uninhabited abilities will come up and get handlers written for them often. ```unison:error unique ability Give a where give : a -> {Give a} Unit give2 : a -> {Give a} Unit result : '{e, Give V} r -> {e} r result f = impl : Request {Give V} r -> {} r impl = cases { x } -> x handle !f with impl ``` ```unison unique ability Give a where give : a -> {Give a} Unit give2 : a -> {Give a} Unit result : '{e, Give V} r -> {e} r result f = impl : Request {Give V} r -> {} r impl = cases { x } -> x { give _ -> resume } -> bug "impossible" handle !f with impl ``` ```unison unique ability Give a where give : a -> {Give a} Unit give2 : a -> {Give a} Unit result : '{e, Give V} r -> {e} r result f = impl : Request {Give V} r -> {} r impl = cases { x } -> x { give2 _ -> resume } -> bug "impossible" handle !f with impl ``` ```unison:error unique ability Give a where give : a -> {Give a} Unit give2 : a -> {Give a} Unit result : '{e, Give V} r -> {e} r result f = impl : Request {Give V} r -> {} r impl = cases { x } -> x { give _ -> resume } -> bug "impossible" { give2 _ -> resume } -> bug "impossible" handle !f with impl ``` ```unison:error unique ability GiveA a where giveA : a -> {GiveA a} Unit giveA2 : a -> {GiveA a} Unit unique ability GiveB a where giveB : a -> {GiveB a} Unit giveB2 : a -> {GiveB a} Unit result : '{e, GiveA V, GiveB V} r -> {e} r result f = impl : Request {GiveA V, GiveB V} r -> {} r impl = cases { x } -> x { giveA _ -> _ } -> bug "impossible" { giveA2 _ -> _ } -> bug "impossible" { giveB _ -> _ } -> bug "impossible" { giveB2 _ -> _ } -> bug "impossible" handle !f with impl ``` ```unison unique ability GiveA a where giveA : a -> {GiveA a} Unit giveA2 : a -> {GiveA a} Unit unique ability GiveB a where giveB : a -> {GiveB a} Unit giveB2 : a -> {GiveB a} Unit result : '{e, GiveA V, GiveB V} r -> {e} r result f = impl : Request {GiveA V, GiveB V} r -> {} r impl = cases { x } -> x { giveA2 _ -> _ } -> bug "impossible" { giveB _ -> _ } -> bug "impossible" handle !f with impl ```