A match arm can be a number, char or string literal

This commit is contained in:
Joseph Ferano 2026-09-25 21:01:40 +07:00
commit fcd32c21c5
11 changed files with 378 additions and 19 deletions

View File

@ -300,9 +300,10 @@ on its own.
Held 2026-09-25 as a future direction, like the JS backend: nested destructuring, 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. guards, or-patterns, literals at any depth, exhaustiveness over the nesting.
** NEXT match over numbers and strings ** DONE match over numbers and strings
Decided 2026-09-25: a match arm's pattern can be an integer, a float, a char or a CLOSED: [2026-09-25]
string literal, compared as =(= t lit)=; a match over such a type needs a =_= arm. 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 ** DONE match over enums
CLOSED: [2026-09-25] CLOSED: [2026-09-25]

View File

@ -222,6 +222,10 @@ and arm = { pat : pattern; body : expr list; aloc : Loc.t }
and pattern = and pattern =
| Pctor of string * string list (* (Some e) (Rect w h) None *) | Pctor of string * string list (* (Some e) (Rect w h) None *)
| Pkw of string (* :north — an enum member *) | 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 *) | Pwild (* _ :else *)
(* ── Declarations ──────────────────────────────────────────────────── *) (* ── Declarations ──────────────────────────────────────────────────── *)

View File

@ -3801,6 +3801,11 @@ let box_option ctx loc (t : Types.t) (got : Tast.expr) : Tast.expr =
(Tast.Let ([ (s, got) ], (Tast.Let ([ (s, got) ],
[ mk loc Types.Dyn (Tast.If (is_some, some_dyn, none_dyn)) ])) [ 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 unbox_option ctx loc (t : Types.t) (got : Tast.expr) : Tast.expr =
let oty = Types.Option t in let oty = Types.Option t in
match t with 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, \ "%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 \ so there is nothing to match on. Read the member you mean with \
(.member u), or use a defdata" n (.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 -> | 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) (Types.to_string other)
in 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 (* 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 whole of what differs between the two subjects; everything below it is
shared. *) 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 \ "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 arm names a member as a keyword: %s" n c
(String.concat " " (List.map (fun (m, _) -> ":" ^ m) members)) (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 -> | `Option _, Ast.Pkw k ->
fail a.Ast.aloc fail a.Ast.aloc
":%s is an enum member, and this match is over an Option, whose arms \ ":%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 -> | Some c ->
if Hashtbl.mem seen c then if Hashtbl.mem seen c then
fail a.Ast.aloc "this match has two %s arms" 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 ()); Hashtbl.add seen c ());
(a, ctor, binds)) (a, ctor, binds))
arms arms
@ -8286,7 +8444,19 @@ and check_match ctx ?(tail = false) ?want loc scrutinee arms =
List.filter_map List.filter_map
(fun (m, _) -> if Hashtbl.mem seen m then None else Some (":" ^ m)) (fun (m, _) -> if Hashtbl.mem seen m then None else Some (":" ^ m))
members members
| `Lit _ -> []
in 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 if not !saw_wild && missing <> [] then
(* The data type's declaration, because that is where the case list this match (* 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 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 ~notes:(match subject with
| `Data u -> declared_note ctx.env u.Tast.dname | `Data u -> declared_note ctx.env u.Tast.dname
| `Enum (n, _) -> declared_note ctx.env n | `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 \ "this match is not exhaustive — %s %s no arm. Add %s, or a _ arm for \
the rest" the rest"
(String.concat ", " missing) (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 let ty = match !want with Some t -> t | None -> Types.Never in
match subject with match subject with
| `Option _ | `Data _ -> mk loc ty (Tast.Match (s, arms)) | `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 (* 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 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 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 | ({ Tast.acase = None; _ } as a) :: _ -> body a
| [ a ] -> body a | [ a ] -> body a
| ({ Tast.acase = Some m; _ } as a) :: rest -> | ({ Tast.acase = Some m; _ } as a) :: rest ->
let v = mk loc s.Tast.ty (Tast.Int (List.assoc m members, Types.I32)) in let test =
mk loc ty match subject with
(Tast.If (mk loc Types.Bool (Tast.Prim (Tast.Eq, [ local; v ])), | `Enum (_, members) ->
body a, chain rest)) 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 in
mk loc ty (Tast.Let ([ (slot, s) ], [ chain arms ])) 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. *) and the negation is an [i1] flip the backend folds away. *)
let link u v = let link u v =
let cmp = 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 in
if String.equal name "!=" then if String.equal name "!=" then
mk loc Types.Bool (Tast.Prim (Tast.Not, [ cmp ])) mk loc Types.Bool (Tast.Prim (Tast.Not, [ cmp ]))

View File

@ -274,7 +274,7 @@ let rec rename_expr owned alias bound (e : Ast.expr) : Ast.expr =
let bound = let bound =
match a.Ast.pat with match a.Ast.pat with
| Ast.Pctor (_, ns) -> ns @ bound | Ast.Pctor (_, ns) -> ns @ bound
| Ast.Pkw _ | Ast.Pwild -> bound | Ast.Pkw _ | Ast.Plit _ | Ast.Pwild -> bound
in in
{ a with Ast.body = List.map (rename_expr owned alias bound) { a with Ast.body = List.map (rename_expr owned alias bound)
a.Ast.body }) arms) a.Ast.body }) arms)

View File

@ -1402,6 +1402,7 @@ and pattern (f : Form.t) : Ast.pattern =
(* An enum member. Which enum is the scrutinee's type, so [Check] resolves (* An enum member. Which enum is the scrutinee's type, so [Check] resolves
it, as it resolves a keyword anywhere an enum is expected. *) it, as it resolves a keyword anywhere an enum is expected. *)
| Kw member -> Ast.Pkw member | Kw member -> Ast.Pkw member
| Int _ | UInt _ | Float _ | Byte _ | Str _ -> Ast.Plit (expr f)
| List ({ v = Sym ctor; _ } :: binds) -> | List ({ v = Sym ctor; _ } :: binds) ->
List.iter no_pattern binds; List.iter no_pattern binds;
Ast.Pctor (ctor, List.map dname binds) Ast.Pctor (ctor, List.map dname binds)

View File

@ -202,9 +202,17 @@ Each item: the proposal, then the reason in one line.
Rect(w, h) -> w * h Rect(w, h) -> w * h
:north -> 0 :north -> 0
_ -> 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 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: - **Conditions**, clauses at the header's column:
``` ```

View 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)

View File

@ -432,6 +432,19 @@ let () =
match_enum_out; match_enum_out;
outputs ~dev:true "match over an enum, dev" "programs/match-enum.flan" outputs ~dev:true "match over an enum, dev" "programs/match-enum.flan"
match_enum_out; 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 (* update, ++ and -- evaluate their place's subexpressions once: the
counts are the number of calls an index or a key function got. *) 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 let update_out = "3\n11 20 90\n1 1 3\n16\n2\n7 1\n32\n2 50\n" in

View File

@ -2690,7 +2690,7 @@ let () =
accepts "a wildcard arm is exhaustive" accepts "a wildcard arm is exhaustive"
"(defn g [] (Option i32) None) (defn f [] i32 (match (g) (Some v) v _ 0))"; "(defn g [] (Option i32) None) (defn f [] i32 (match (g) (Some v) v _ 0))";
rejects_check "match on a non-Option" 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 ────────────────────── *) (* ── Names, order-independence, entry point ────────────────────── *)
accepts "mutually recursive, no forward declaration" accepts "mutually recursive, no forward declaration"
@ -4582,8 +4582,85 @@ let () =
(k ^ "(defn f [k K] i32 (match k :lo 1 _ \"x\"))") (k ^ "(defn f [k K] i32 (match k :lo 1 _ \"x\"))")
~needle:"expected i32, found string"; ~needle:"expected i32, found string";
rejects_check "match over something that is none of them" rejects_check "match over something that is none of them"
"(defn f [n i32] i32 (match n _ 2))" "(defn f [n bool] i32 (match n _ 2))"
~needle:"match works on an Option, a data type or an enum, not on i32"; ~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 (* A destructuring pattern in an arm's binds is a name position like any
other. *) other. *)
rejects_check "a pattern inside a match arm's binds" rejects_check "a pattern inside a match arm's binds"

View File

@ -362,6 +362,8 @@ let () =
"(restart-case (f) (continue [] (do)))"; "(restart-case (f) (continue [] (do)))";
reads "match" "match s\n Circle(r) -> r\n _ ->\n a()\n b()" reads "match" "match s\n Circle(r) -> r\n _ ->\n a()\n b()"
"(match s (Circle r) r _ (do (a) (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)" reads "handler-bind moves the clauses" "handler-bind\n f()\non E(c)\n g(c)"
"(handler-bind [(E [c] (g c))] (f))"; "(handler-bind [(E [c] (g c))] (f))";
reads "quote block" reads "quote block"

View File

@ -1096,8 +1096,11 @@ as <code>first-even</code> does above.</p>
<h3>Option, <code>match</code> and <code>some</code></h3> <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 <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 collection, the end of a stream. <code>match</code> works on an <code>Option</code>, a
on a <code>defdata</code>, and on nothing else. <code>some</code> unwraps <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 &mdash; <code>(match n 0 "zero" -1 "none" _ "some")</code>
&mdash; 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> <code>Some</code> and early-returns <code>None</code> from the enclosing function.</p>
<pre><code>(defconst nums [4 i32] [4 8 15 16]) <pre><code>(defconst nums [4 i32] [4 8 15 16])