A match arm can be a number, char or string literal
This commit is contained in:
commit
fcd32c21c5
7
TODO.org
7
TODO.org
@ -300,9 +300,10 @@ on its own.
|
||||
Held 2026-09-25 as a future direction, like the JS backend: nested destructuring,
|
||||
guards, or-patterns, literals at any depth, exhaustiveness over the nesting.
|
||||
|
||||
** NEXT match over numbers and strings
|
||||
Decided 2026-09-25: a match arm's pattern can be an integer, a float, a char or a
|
||||
string literal, compared as =(= t lit)=; a match over such a type needs a =_= arm.
|
||||
** DONE match over numbers and strings
|
||||
CLOSED: [2026-09-25]
|
||||
Rules out a literal the scrutinee's type cannot hold (refused, not widened as =(=)=
|
||||
would), keyword arms over a dyn, and a bare-name catch-all: a bare name is a nullary case.
|
||||
|
||||
** DONE match over enums
|
||||
CLOSED: [2026-09-25]
|
||||
|
||||
@ -222,6 +222,10 @@ and arm = { pat : pattern; body : expr list; aloc : Loc.t }
|
||||
and pattern =
|
||||
| Pctor of string * string list (* (Some e) (Rect w h) None *)
|
||||
| Pkw of string (* :north — an enum member *)
|
||||
(* 5 -2.5 \a "go" — an Int, UInt, Float, Byte or Str expr, compared as
|
||||
(= t lit). An expr and not a literal type of its own, so the checker
|
||||
types it against the scrutinee as any literal is typed against its site. *)
|
||||
| Plit of expr
|
||||
| Pwild (* _ :else *)
|
||||
|
||||
(* ── Declarations ──────────────────────────────────────────────────── *)
|
||||
|
||||
197
lib/check.ml
197
lib/check.ml
@ -3801,6 +3801,11 @@ let box_option ctx loc (t : Types.t) (got : Tast.expr) : Tast.expr =
|
||||
(Tast.Let ([ (s, got) ],
|
||||
[ mk loc Types.Dyn (Tast.If (is_some, some_dyn, none_dyn)) ]))
|
||||
|
||||
(* [=] over a dyn pair, answering a bool. Shared by the [=] builtin and a
|
||||
literal [match] over a dyn, which is (= t lit) by definition. *)
|
||||
let dyn_eq loc u v =
|
||||
unbox loc Types.Bool (rt loc Types.Dyn "flan_dyn_eq" [ box loc u; box loc v ])
|
||||
|
||||
let unbox_option ctx loc (t : Types.t) (got : Tast.expr) : Tast.expr =
|
||||
let oty = Types.Option t in
|
||||
match t with
|
||||
@ -8089,10 +8094,52 @@ and check_match ctx ?(tail = false) ?want loc scrutinee arms =
|
||||
"%s is a union, and nothing in one records which member was written, \
|
||||
so there is nothing to match on. Read the member you mean with \
|
||||
(.member u), or use a defdata" n
|
||||
(* A number, a string or a dyn: the arms are literals, and each is the
|
||||
test (= t lit) over one temporary — the enum's chain, with [=]'s own
|
||||
two lowerings for the test, so a match over a dyn means what [=] over
|
||||
it means. [is_equatable]'s set minus the enums, which are above. *)
|
||||
| (Types.Int _ | Types.Float _ | Types.String | Types.Dyn) as t -> `Lit t
|
||||
| other ->
|
||||
fail loc "match works on an Option, a data type or an enum, not on %s"
|
||||
fail loc
|
||||
"match works on an Option, a data type, an enum, a number, a string \
|
||||
or a dyn, not on %s"
|
||||
(Types.to_string other)
|
||||
in
|
||||
(* A literal arm, spelled as it was written, for the refusals that name one. *)
|
||||
let spell (e : Ast.expr) =
|
||||
match e.Ast.e with
|
||||
| Ast.Int n -> Int64.to_string n
|
||||
| Ast.UInt (_, t) -> t
|
||||
| Ast.Float x when Float.is_integer x && Float.abs x < 1e15 ->
|
||||
Printf.sprintf "%.1f" x
|
||||
| Ast.Float x ->
|
||||
(* The shortest spelling that reads back as the same float. *)
|
||||
let rec go p =
|
||||
let t = Printf.sprintf "%.*g" p x in
|
||||
if p >= 17 || float_of_string t = x then t else go (p + 1)
|
||||
in
|
||||
go 1
|
||||
| Ast.Byte b when b > 32 && b < 127 -> Printf.sprintf "\\%c" (Char.chr b)
|
||||
| Ast.Byte b -> string_of_int b
|
||||
| Ast.Str t -> Printf.sprintf "%S" t
|
||||
| _ -> "this literal"
|
||||
in
|
||||
let what_ty t = match t with Types.Dyn -> "a dyn" | t -> Types.to_string t in
|
||||
(* A literal match that compiles, over the scrutinee's own name where it
|
||||
has one, for the refusals that need to show the shape. *)
|
||||
let lit_arms_fix t =
|
||||
let name =
|
||||
match scrutinee.Ast.e with Ast.Var n -> n | _ -> "t"
|
||||
in
|
||||
Printf.sprintf "(match %s %s)" name
|
||||
(match t with
|
||||
| Types.String -> "\"yes\" 1 _ 0"
|
||||
| Types.Float _ -> "0.5 1 _ 0"
|
||||
| _ -> "5 1 _ 0")
|
||||
in
|
||||
(* The checked literal of each literal arm, by the key [resolve_pat] gave it. *)
|
||||
let lits : (string, Tast.expr) Hashtbl.t = Hashtbl.create 8 in
|
||||
let lit_values = ref [] in
|
||||
(* Which case each arm names, and the type of each name it binds. This is the
|
||||
whole of what differs between the two subjects; everything below it is
|
||||
shared. *)
|
||||
@ -8124,6 +8171,114 @@ and check_match ctx ?(tail = false) ?want loc scrutinee arms =
|
||||
"this match is over the enum %s, and %s is not one of its members. An \
|
||||
arm names a member as a keyword: %s" n c
|
||||
(String.concat " " (List.map (fun (m, _) -> ":" ^ m) members))
|
||||
| `Lit t, Ast.Plit e ->
|
||||
let v =
|
||||
(* [=]'s dyn pair checks its literal at dyn, which boxes it. *)
|
||||
match trial ctx (fun () -> check ctx ~want:t e) with
|
||||
| Ok v -> v
|
||||
| Error _ ->
|
||||
(* A literal that does not fit is refused, where [=] would widen
|
||||
the pair and let the arm quietly never match. The literal's
|
||||
own refusal is not repeated: its fixes are casts, and a cast
|
||||
is not a pattern. *)
|
||||
(match t with
|
||||
| Types.Dyn ->
|
||||
fail a.Ast.aloc
|
||||
"this match is over a dyn, which holds a number as an i64 or \
|
||||
an f64, and %s fits in neither. Change the arm to a value an \
|
||||
i64 holds, or remove it" (spell e)
|
||||
| _ -> ());
|
||||
let tn = Types.to_string t in
|
||||
let an =
|
||||
match tn.[0] with
|
||||
| 'a' | 'e' | 'f' | 'i' | 'o' -> "an " ^ tn
|
||||
| _ -> "a " ^ tn
|
||||
in
|
||||
let why =
|
||||
match e.Ast.e, t with
|
||||
| Ast.Str _, _ -> "is a string"
|
||||
| _, Types.String -> "is a number"
|
||||
| Ast.Float x, Types.Int _ when not (Float.is_integer x) ->
|
||||
"is not a whole number"
|
||||
| Ast.Float _, Types.Int _ -> "is a float"
|
||||
| _ -> "does not fit in one"
|
||||
in
|
||||
fail a.Ast.aloc
|
||||
"this match is over %s, so each arm has to be %s, and %s %s. \
|
||||
Change the arm to a value %s holds, or remove it"
|
||||
tn an (spell e) why an
|
||||
in
|
||||
(* The arm's value at the scrutinee's type, and a second arm [=] could
|
||||
not tell from an earlier one is refused, since it can never be
|
||||
reached: 97 and \a are one u8, 0.1 and 0.10000000001 are one f32,
|
||||
and over a dyn 1 and 1.0 are equal. Compared pairwise rather than
|
||||
hashed, because dyn = between an integer and a float goes through
|
||||
the float and is not transitive past 2^53. *)
|
||||
let value =
|
||||
let f32 x = Int32.float_of_bits (Int32.bits_of_float x) in
|
||||
let num x =
|
||||
match t with Types.Float Types.F32 -> `F (f32 x) | _ -> `F x
|
||||
in
|
||||
match e.Ast.e, t with
|
||||
| Ast.Str s, _ -> `S s
|
||||
| (Ast.Int n | Ast.UInt (n, _)), Types.Float _ -> num (Int64.to_float n)
|
||||
| Ast.Byte b, Types.Float _ -> num (float_of_int b)
|
||||
| (Ast.Int n | Ast.UInt (n, _)), _ -> `I n
|
||||
| Ast.Byte b, _ -> `I (Int64.of_int b)
|
||||
| Ast.Float x, _ -> num x
|
||||
| _ -> assert false
|
||||
in
|
||||
let same x y =
|
||||
match x, y with
|
||||
| `I a, `I b -> Int64.equal a b
|
||||
| `F a, `F b -> a = b
|
||||
| `I a, `F b | `F b, `I a -> Int64.to_float a = b
|
||||
| `S a, `S b -> String.equal a b
|
||||
| _ -> false
|
||||
in
|
||||
(match List.find_opt (fun (w, _) -> same value w) !lit_values with
|
||||
| Some (_, earlier) when earlier = spell e ->
|
||||
fail a.Ast.aloc "this match has two %s arms" earlier
|
||||
| Some (_, earlier) ->
|
||||
fail a.Ast.aloc
|
||||
"this match has two %s arms — %s equals it as %s, so this arm is \
|
||||
never reached. Remove it"
|
||||
earlier (spell e)
|
||||
(match t with
|
||||
| Types.Dyn -> "a dyn"
|
||||
| t ->
|
||||
let tn = Types.to_string t in
|
||||
(match tn.[0] with
|
||||
| 'a' | 'e' | 'f' | 'i' | 'o' -> "an " ^ tn
|
||||
| _ -> "a " ^ tn))
|
||||
| None -> ());
|
||||
lit_values := (value, spell e) :: !lit_values;
|
||||
let key = string_of_int (Hashtbl.length lits) in
|
||||
Hashtbl.replace lits key v;
|
||||
Some key, []
|
||||
| `Lit t, Ast.Pkw k ->
|
||||
fail a.Ast.aloc
|
||||
":%s is an enum member, and this match is over %s, whose arms are \
|
||||
literals, as in %s" k (what_ty t) (lit_arms_fix t)
|
||||
| `Lit t, Ast.Pctor (c, _) ->
|
||||
fail a.Ast.aloc
|
||||
"%s names a case, and this match is over %s, whose arms are \
|
||||
literals, as in %s" c (what_ty t) (lit_arms_fix t)
|
||||
| `Option _, Ast.Plit e ->
|
||||
fail a.Ast.aloc
|
||||
"%s is a literal, and this match is over an Option, whose arms are \
|
||||
(Some x) and None" (spell e)
|
||||
| `Enum (n, members), Ast.Plit e ->
|
||||
fail a.Ast.aloc
|
||||
"%s is a literal, and this match is over the enum %s, whose arms \
|
||||
name its members as keywords: %s" (spell e) n
|
||||
(String.concat " " (List.map (fun (m, _) -> ":" ^ m) members))
|
||||
| `Data u, Ast.Plit e ->
|
||||
fail a.Ast.aloc
|
||||
"%s is a literal, and this match is over the data type %s, whose \
|
||||
arms name its cases: %s" (spell e) u.Tast.dname
|
||||
(String.concat ", "
|
||||
(List.map (fun (v : Tast.variant) -> v.Tast.vname) u.Tast.cases))
|
||||
| `Option _, Ast.Pkw k ->
|
||||
fail a.Ast.aloc
|
||||
":%s is an enum member, and this match is over an Option, whose arms \
|
||||
@ -8184,7 +8339,10 @@ and check_match ctx ?(tail = false) ?want loc scrutinee arms =
|
||||
| Some c ->
|
||||
if Hashtbl.mem seen c then
|
||||
fail a.Ast.aloc "this match has two %s arms"
|
||||
(match subject with `Enum _ -> ":" ^ c | _ -> c);
|
||||
(match subject, a.Ast.pat with
|
||||
| `Enum _, _ -> ":" ^ c
|
||||
| _, Ast.Plit e -> spell e
|
||||
| _ -> c);
|
||||
Hashtbl.add seen c ());
|
||||
(a, ctor, binds))
|
||||
arms
|
||||
@ -8286,7 +8444,19 @@ and check_match ctx ?(tail = false) ?want loc scrutinee arms =
|
||||
List.filter_map
|
||||
(fun (m, _) -> if Hashtbl.mem seen m then None else Some (":" ^ m))
|
||||
members
|
||||
| `Lit _ -> []
|
||||
in
|
||||
(* No list of literals covers a number, a string or a dyn, so a literal
|
||||
match always needs its [_]. Refused here, before the chain below, which
|
||||
would otherwise run a lone last arm untested as the enum's does. *)
|
||||
(match subject with
|
||||
| `Lit t when not !saw_wild ->
|
||||
Loc.failk "check/non-exhaustive-match" loc
|
||||
"this match is not exhaustive — its arms are literals, and no list of \
|
||||
them covers every %s. Add a _ arm for the rest, as in %s"
|
||||
(match t with Types.Dyn -> "dyn value" | t -> Types.to_string t)
|
||||
(lit_arms_fix t)
|
||||
| _ -> ());
|
||||
if not !saw_wild && missing <> [] then
|
||||
(* The data type's declaration, because that is where the case list this match
|
||||
failed to cover actually lives, and because adding a case there is what
|
||||
@ -8295,7 +8465,7 @@ and check_match ctx ?(tail = false) ?want loc scrutinee arms =
|
||||
~notes:(match subject with
|
||||
| `Data u -> declared_note ctx.env u.Tast.dname
|
||||
| `Enum (n, _) -> declared_note ctx.env n
|
||||
| `Option _ -> [])
|
||||
| `Option _ | `Lit _ -> [])
|
||||
"this match is not exhaustive — %s %s no arm. Add %s, or a _ arm for \
|
||||
the rest"
|
||||
(String.concat ", " missing)
|
||||
@ -8304,7 +8474,7 @@ and check_match ctx ?(tail = false) ?want loc scrutinee arms =
|
||||
let ty = match !want with Some t -> t | None -> Types.Never in
|
||||
match subject with
|
||||
| `Option _ | `Data _ -> mk loc ty (Tast.Match (s, arms))
|
||||
| `Enum (_, members) ->
|
||||
| `Enum _ | `Lit _ ->
|
||||
(* The scrutinee once, into a temporary, and then an [if] per arm in the
|
||||
order written. A [_] arm ends the chain, and so does the last arm of a
|
||||
match with none: it is exhaustive by the check above, so the last
|
||||
@ -8320,10 +8490,18 @@ and check_match ctx ?(tail = false) ?want loc scrutinee arms =
|
||||
| ({ Tast.acase = None; _ } as a) :: _ -> body a
|
||||
| [ a ] -> body a
|
||||
| ({ Tast.acase = Some m; _ } as a) :: rest ->
|
||||
let v = mk loc s.Tast.ty (Tast.Int (List.assoc m members, Types.I32)) in
|
||||
mk loc ty
|
||||
(Tast.If (mk loc Types.Bool (Tast.Prim (Tast.Eq, [ local; v ])),
|
||||
body a, chain rest))
|
||||
let test =
|
||||
match subject with
|
||||
| `Enum (_, members) ->
|
||||
let v =
|
||||
mk loc s.Tast.ty (Tast.Int (List.assoc m members, Types.I32))
|
||||
in
|
||||
mk loc Types.Bool (Tast.Prim (Tast.Eq, [ local; v ]))
|
||||
| `Lit Types.Dyn -> dyn_eq loc local (Hashtbl.find lits m)
|
||||
| _ ->
|
||||
mk loc Types.Bool (Tast.Prim (Tast.Eq, [ local; Hashtbl.find lits m ]))
|
||||
in
|
||||
mk loc ty (Tast.If (test, body a, chain rest))
|
||||
in
|
||||
mk loc ty (Tast.Let ([ (slot, s) ], [ chain arms ]))
|
||||
|
||||
@ -9698,7 +9876,8 @@ and named_call ?(qualified = false) ctx ~want loc name args =
|
||||
and the negation is an [i1] flip the backend folds away. *)
|
||||
let link u v =
|
||||
let cmp =
|
||||
unbox loc Types.Bool (rt loc Types.Dyn sym ([ u; v ] @ site))
|
||||
if String.equal sym "flan_dyn_eq" then dyn_eq loc u v
|
||||
else unbox loc Types.Bool (rt loc Types.Dyn sym ([ u; v ] @ site))
|
||||
in
|
||||
if String.equal name "!=" then
|
||||
mk loc Types.Bool (Tast.Prim (Tast.Not, [ cmp ]))
|
||||
|
||||
@ -274,7 +274,7 @@ let rec rename_expr owned alias bound (e : Ast.expr) : Ast.expr =
|
||||
let bound =
|
||||
match a.Ast.pat with
|
||||
| Ast.Pctor (_, ns) -> ns @ bound
|
||||
| Ast.Pkw _ | Ast.Pwild -> bound
|
||||
| Ast.Pkw _ | Ast.Plit _ | Ast.Pwild -> bound
|
||||
in
|
||||
{ a with Ast.body = List.map (rename_expr owned alias bound)
|
||||
a.Ast.body }) arms)
|
||||
|
||||
@ -1402,6 +1402,7 @@ and pattern (f : Form.t) : Ast.pattern =
|
||||
(* An enum member. Which enum is the scrutinee's type, so [Check] resolves
|
||||
it, as it resolves a keyword anywhere an enum is expected. *)
|
||||
| Kw member -> Ast.Pkw member
|
||||
| Int _ | UInt _ | Float _ | Byte _ | Str _ -> Ast.Plit (expr f)
|
||||
| List ({ v = Sym ctor; _ } :: binds) ->
|
||||
List.iter no_pattern binds;
|
||||
Ast.Pctor (ctor, List.map dname binds)
|
||||
|
||||
@ -202,9 +202,17 @@ Each item: the proposal, then the reason in one line.
|
||||
Rect(w, h) -> w * h
|
||||
:north -> 0
|
||||
_ -> 0
|
||||
|
||||
match code
|
||||
404 -> "missing"
|
||||
-1 -> "none"
|
||||
"ok" -> "fine"
|
||||
\a -> "a"
|
||||
_ -> "other"
|
||||
```
|
||||
An arm's body can be an indented block, which reads as `(do …)`. **Built** (a
|
||||
one-line block reads as that line).
|
||||
one-line block reads as that line). A number, char or string pattern is the
|
||||
literal as written, compared as `(= t lit)`.
|
||||
- **Conditions**, clauses at the header's column:
|
||||
|
||||
```
|
||||
|
||||
71
test/programs/match-literal.flan
Normal file
71
test/programs/match-literal.flan
Normal file
@ -0,0 +1,71 @@
|
||||
;;;; match over numbers, chars, strings and dyn values: each arm is (= t lit)
|
||||
;;;; over one temporary, and a _ arm is the rest.
|
||||
|
||||
(defn small [n i16] string
|
||||
(match n
|
||||
5 "five"
|
||||
-3 "minus three"
|
||||
_ "other"))
|
||||
|
||||
(defn half [x f32] i32
|
||||
(match x
|
||||
0.5 1
|
||||
2 2
|
||||
_ 0))
|
||||
|
||||
(defn letter [c u8] i32
|
||||
(match c
|
||||
\a 1
|
||||
98 2
|
||||
_ 0))
|
||||
|
||||
(defn command [s string] i32
|
||||
(match s
|
||||
"go" 1
|
||||
"stop" 2
|
||||
"" 3
|
||||
_ 0))
|
||||
|
||||
(defn big [n u64] i32
|
||||
(match n
|
||||
18446744073709551615 1
|
||||
_ 0))
|
||||
|
||||
;; Over a dyn the test is dyn =, so 1 matches 1.0 and "go" matches only a
|
||||
;; string.
|
||||
(defn kind [d dyn] string
|
||||
(match d
|
||||
1 "one"
|
||||
2.5 "two and a half"
|
||||
"go" "go"
|
||||
_ "other"))
|
||||
|
||||
(defn calls [] i32
|
||||
(print "(called) ")
|
||||
7)
|
||||
|
||||
;; recur from inside an arm: the arm is the loop's tail.
|
||||
(defn count-down [from i32] i32
|
||||
(loop [n from steps 0]
|
||||
(match n
|
||||
0 steps
|
||||
_ (recur (- n 1) (+ steps 1)))))
|
||||
|
||||
(defn main [] i32
|
||||
(println (small 5))
|
||||
(println (small -3))
|
||||
(println (small 4))
|
||||
(print (half 0.5)) (print (half 2.0)) (print (half 3.0)) (println "")
|
||||
(print (letter 97)) (print (letter 98)) (print (letter 99)) (println "")
|
||||
(print (command "go")) (print (command "stop")) (print (command ""))
|
||||
(print (command "gone")) (println "")
|
||||
(print (big 18446744073709551615)) (print (big 1)) (println "")
|
||||
(println (kind 1))
|
||||
(println (kind 1.0))
|
||||
(println (kind 2.5))
|
||||
(println (kind "go"))
|
||||
(println (kind "1"))
|
||||
;; The scrutinee is evaluated once, however many arms test it.
|
||||
(println (match (calls) 1 "a" 2 "b" 7 "seven" _ "c"))
|
||||
(print (count-down 4)) (println "")
|
||||
0)
|
||||
@ -432,6 +432,19 @@ let () =
|
||||
match_enum_out;
|
||||
outputs ~dev:true "match over an enum, dev" "programs/match-enum.flan"
|
||||
match_enum_out;
|
||||
(* A literal match is the same chain with (= t lit) as each test, so the
|
||||
dyn rows (1 and 1.0 both "one") are dyn ='s answer. *)
|
||||
let match_lit_out =
|
||||
"five\nminus three\nother\n120\n120\n1230\n10\none\none\n\
|
||||
two and a half\ngo\nother\n(called) seven\n4\n"
|
||||
in
|
||||
outputs "match over literals" "programs/match-literal.flan" match_lit_out;
|
||||
outputs ~opt:"-O0" "match over literals, -O0" "programs/match-literal.flan"
|
||||
match_lit_out;
|
||||
outputs ~x86:true "match over literals, --x86" "programs/match-literal.flan"
|
||||
match_lit_out;
|
||||
outputs ~dev:true "match over literals, dev" "programs/match-literal.flan"
|
||||
match_lit_out;
|
||||
(* update, ++ and -- evaluate their place's subexpressions once: the
|
||||
counts are the number of calls an index or a key function got. *)
|
||||
let update_out = "3\n11 20 90\n1 1 3\n16\n2\n7 1\n32\n2 50\n" in
|
||||
|
||||
@ -2690,7 +2690,7 @@ let () =
|
||||
accepts "a wildcard arm is exhaustive"
|
||||
"(defn g [] (Option i32) None) (defn f [] i32 (match (g) (Some v) v _ 0))";
|
||||
rejects_check "match on a non-Option"
|
||||
"(defn f [x i32] i32 (match x _ 0))" ~needle:"match works on an Option";
|
||||
"(defn f [x bool] i32 (match x _ 0))" ~needle:"match works on an Option";
|
||||
|
||||
(* ── Names, order-independence, entry point ────────────────────── *)
|
||||
accepts "mutually recursive, no forward declaration"
|
||||
@ -4582,8 +4582,85 @@ let () =
|
||||
(k ^ "(defn f [k K] i32 (match k :lo 1 _ \"x\"))")
|
||||
~needle:"expected i32, found string";
|
||||
rejects_check "match over something that is none of them"
|
||||
"(defn f [n i32] i32 (match n _ 2))"
|
||||
~needle:"match works on an Option, a data type or an enum, not on i32";
|
||||
"(defn f [n bool] i32 (match n _ 2))"
|
||||
~needle:"match works on an Option, a data type, an enum, a number, a \
|
||||
string or a dyn, not on bool";
|
||||
|
||||
(* ── match over literals ───────────────────────────────────────── *)
|
||||
|
||||
(* Each arm is (= t lit) with the literal built at the scrutinee's type, so
|
||||
a literal that type cannot hold is refused rather than widened into an
|
||||
arm that never matches. *)
|
||||
accepts "match over an i16, a literal arm built at i16"
|
||||
"(defn f [n i16] i32 (match n 5 1 -3 2 _ 0))";
|
||||
accepts "match over a string" "(defn f [s string] i32 (match s \"go\" 1 _ 0))";
|
||||
accepts "match over a dyn, arms of several kinds"
|
||||
"(defn f [d dyn] i32 (match d 1 1 2.5 2 \"go\" 3 \\a 4 _ 0))";
|
||||
accepts "match over a number, :else for the rest"
|
||||
"(defn f [n i32] i32 (match n 5 1 :else 0))";
|
||||
rejects_check "a literal arm the scrutinee cannot hold"
|
||||
"(defn f [n i8] i32 (match n 300 1 _ 0))"
|
||||
~needle:"this match is over i8, so each arm has to be an i8, and 300 does \
|
||||
not fit in one. Change the arm to a value an i8 holds, or remove it";
|
||||
rejects_check "a float arm over an integer"
|
||||
"(defn f [n i32] i32 (match n 1.5 1 _ 0))"
|
||||
~needle:"and 1.5 is not a whole number";
|
||||
rejects_check "a string arm over a number"
|
||||
"(defn f [n i32] i32 (match n \"a\" 1 _ 0))"
|
||||
~needle:"and \"a\" is a string";
|
||||
rejects_check "a number arm over a string"
|
||||
"(defn f [s string] i32 (match s 5 1 _ 0))"
|
||||
~needle:"so each arm has to be a string, and 5 is a number";
|
||||
rejects_check "a literal match with no _ arm"
|
||||
"(defn f [n i32] i32 (match n 5 1 6 2))"
|
||||
~needle:"this match is not exhaustive — its arms are literals, and no list \
|
||||
of them covers every i32. Add a _ arm for the rest, as in (match \
|
||||
n 5 1 _ 0)";
|
||||
rejects_check "a literal match over a dyn with no _ arm"
|
||||
"(defn f [d dyn] i32 (match d 5 1))"
|
||||
~needle:"covers every dyn value";
|
||||
rejects_check "a literal named twice"
|
||||
"(defn f [n i32] i32 (match n 5 1 5 2 _ 0))"
|
||||
~needle:"this match has two 5 arms";
|
||||
rejects_check "a char and a number that are one u8"
|
||||
"(defn f [c u8] i32 (match c \\a 1 97 2 _ 0))"
|
||||
~needle:"this match has two \\a arms — 97 equals it as a u8";
|
||||
rejects_check "1 and 1.0 are one arm over a dyn, as dyn = says"
|
||||
"(defn f [d dyn] i32 (match d 1 1 1.0 2 _ 0))"
|
||||
~needle:"this match has two 1 arms — 1.0 equals it as a dyn";
|
||||
rejects_check "two literals that round to one f32"
|
||||
"(defn f [x f32] i32 (match x 0.1 1 0.10000000001 2 _ 0))"
|
||||
~needle:"this match has two 0.1 arms — 0.10000000001 equals it as an f32";
|
||||
rejects_check "two integers that round to one f32"
|
||||
"(defn f [x f32] i32 (match x 16777216 1 16777217 2 _ 0))"
|
||||
~needle:"two 16777216 arms — 16777217 equals it as an f32";
|
||||
rejects_check "an integer and a float that are one f64"
|
||||
"(defn f [x f64] i32 \
|
||||
(match x 4611686018427387904 1 4611686018427387904.0 2 _ 0))"
|
||||
~needle:"equals it as an f64";
|
||||
accepts "two f64 literals that differ"
|
||||
"(defn f [x f64] i32 (match x 0.1 1 0.10000000001 2 _ 0))";
|
||||
rejects_check "a literal no dyn holds"
|
||||
"(defn f [x dyn] i32 (match x 18446744073709551615 1 _ 0))"
|
||||
~needle:"this match is over a dyn, which holds a number as an i64 or an \
|
||||
f64, and 18446744073709551615 fits in neither. Change the arm to \
|
||||
a value an i64 holds, or remove it";
|
||||
rejects_check "a keyword arm among literal arms"
|
||||
"(defn f [n i32] i32 (match n 5 1 :lo 2 _ 0))"
|
||||
~needle:":lo is an enum member, and this match is over i32, whose arms are \
|
||||
literals, as in (match n 5 1 _ 0)";
|
||||
rejects_check "a case arm among literal arms"
|
||||
"(defn f [n i32] i32 (match n 5 1 (Some x) 2 _ 0))"
|
||||
~needle:"Some names a case, and this match is over i32";
|
||||
rejects_check "a literal arm among keyword arms"
|
||||
(k ^ "(defn f [k K] i32 (match k :lo 1 5 2 _ 0))")
|
||||
~needle:"5 is a literal, and this match is over the enum K";
|
||||
rejects_check "a literal arm over an Option"
|
||||
"(defn f [o (Option i32)] i32 (match o 5 1 _ 0))"
|
||||
~needle:"5 is a literal, and this match is over an Option";
|
||||
rejects_check "a literal match over a byte slice, which = does not compare"
|
||||
"(defn f [b [u8]] i32 (match b \"a\" 1 _ 0))"
|
||||
~needle:"not on [u8]";
|
||||
(* A destructuring pattern in an arm's binds is a name position like any
|
||||
other. *)
|
||||
rejects_check "a pattern inside a match arm's binds"
|
||||
|
||||
@ -362,6 +362,8 @@ let () =
|
||||
"(restart-case (f) (continue [] (do)))";
|
||||
reads "match" "match s\n Circle(r) -> r\n _ ->\n a()\n b()"
|
||||
"(match s (Circle r) r _ (do (a) (b)))";
|
||||
reads "match over literals" "match n\n 5 -> a\n -2.5 -> b\n \"go\" -> c\n \\a -> d\n _ -> e"
|
||||
"(match n 5 a -2.5 b \"go\" c \\a d _ e)";
|
||||
reads "handler-bind moves the clauses" "handler-bind\n f()\non E(c)\n g(c)"
|
||||
"(handler-bind [(E [c] (g c))] (f))";
|
||||
reads "quote block"
|
||||
|
||||
@ -1096,8 +1096,11 @@ as <code>first-even</code> does above.</p>
|
||||
<h3>Option, <code>match</code> and <code>some</code></h3>
|
||||
|
||||
<p><code>(Option T)</code> is how absence is spelled: a lookup miss, an empty
|
||||
collection, the end of a stream. <code>match</code> works on an <code>Option</code> and
|
||||
on a <code>defdata</code>, and on nothing else. <code>some</code> unwraps
|
||||
collection, the end of a stream. <code>match</code> works on an <code>Option</code>, a
|
||||
<code>defdata</code> and an enum, whose arms name cases; and on a number, a string or a
|
||||
<code>dyn</code>, whose arms are literals — <code>(match n 0 "zero" -1 "none" _ "some")</code>
|
||||
— each compared with <code>=</code>, with a <code>_</code> arm required for the rest.
|
||||
<code>some</code> unwraps
|
||||
<code>Some</code> and early-returns <code>None</code> from the enclosing function.</p>
|
||||
|
||||
<pre><code>(defconst nums [4 i32] [4 8 15 16])
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user