diff --git a/lib/check.ml b/lib/check.ml index bad8782..59ba57f 100644 --- a/lib/check.ml +++ b/lib/check.ml @@ -810,6 +810,18 @@ and check_match ctx ?want loc scrutinee arms = let elem = match s.Tast.ty with | Types.Option t -> t + (* An enum is the one scrutinee that is not a milestone away: it is an i32 + at run time and its members are all known, so the arms would be a chain + of [=] with an exhaustiveness check over [env.enums] — a desugaring, not + a new IR node. What blocks it is upstream of here: a keyword has no case + in [Ast.pattern], and [lib/load.ml] matches that type exhaustively, so + the variant cannot be added. Said as itself rather than folded into the + milestone answer below, because the milestone is not the reason. *) + | Types.Enum n -> + fail loc + "match over the enum %s is not implemented — the lowering is a chain \ + of (= k :member), but a keyword has no case in the pattern type yet. \ + Use cond" n | other -> (* Union matching arrives with unions themselves, at milestone 6. *) fail loc "match works on an Option at milestone 2, not on %s" diff --git a/lib/parse.ml b/lib/parse.ml index ae792f0..a7c4102 100644 --- a/lib/parse.ml +++ b/lib/parse.ml @@ -614,7 +614,20 @@ and pattern (f : Form.t) : Ast.pattern = | Sym "_" -> Ast.Pwild | Kw "else" -> Ast.Pwild | Sym ctor -> Ast.Pctor (ctor, []) + (* An enum member, which is the one other thing [match] could plausibly be + over: an enum is an i32 at run time, so the arms would be a chain of [=] + and the members are all known, which is exhaustiveness [cond] cannot give. + What stops it is not the lowering, it is that a keyword pattern needs a + case in [Ast.pattern] — and [lib/load.ml] matches that type exhaustively, + so the variant cannot be added from here. Refused by name rather than + spelled as a constructor it is not. *) + | Kw member -> + fail f + ":%s is not implemented as a pattern — match is over an Option here, \ + and an enum member cannot be one until Ast.pattern can hold a keyword. \ + Use cond with (= k :%s)" member member | List ({ v = Sym ctor; _ } :: binds) -> + List.iter no_pattern binds; Ast.Pctor (ctor, List.map sym binds) | _ -> fail f "expected a pattern, found %s" (Form.to_string f) diff --git a/test/programs/destructure.flan b/test/programs/destructure.flan index b4ba30b..212e232 100644 --- a/test/programs/destructure.flan +++ b/test/programs/destructure.flan @@ -85,6 +85,18 @@ [{:keys [x]} {y :y}] ps] (show2 "nested-in-array" x y)) + ;; A tail of something wider than a machine word. The corpus slices arrays of + ;; i32, u8 and f32 and nothing else, so this is the one place the desugared + ;; (slice xs n (len xs)) has to get a struct's stride right rather than a + ;; scalar's. + (let [ps [(Point {:x 1 :y 2}) (Point {:x 3 :y 4}) (Point {:x 5 :y 6})] + [first & others] ps] + (print-str "struct-tail ") + (print-i64 (i64 (.x first))) (print-str " ") + (print-i64 (i64 (len others))) (print-str " ") + (print-i64 (i64 (.y (at others 0)))) (print-str " ") + (print-i64 (i64 (.x (at others 1)))) (newline)) + ;; Evaluate-once. Two patterns, two calls, four names — one call per pattern. ;; Without the temporary each of the four names would call it again: 4, not 2. (let [{:keys [x y]} (make-point) diff --git a/test/test_acceptance.ml b/test/test_acceptance.ml index 56b9e0a..fdc7f6b 100644 --- a/test/test_acceptance.ml +++ b/test/test_acceptance.ml @@ -873,7 +873,7 @@ ERR@7 unexpected token: not the kind the caller was reading let destructure_out = "keys 1 2\npairs 10 20\nnested 7 8\nshadow 5 6\nsequential 100 200\n\ array 11 22 33\nrest 1 4 2 5\nempty-tail 17 0\nnested-in-array 1 4\n\ - calls 2 14\n" + struct-tail 1 2 4 5\ncalls 2 14\n" in outputs "destructuring" "programs/destructure.flan" destructure_out; outputs ~opt:"-O0" "destructuring, -O0" "programs/destructure.flan" diff --git a/test/test_flan.ml b/test/test_flan.ml index beb6dcb..9e91d00 100644 --- a/test/test_flan.ml +++ b/test/test_flan.ml @@ -930,6 +930,30 @@ let () = "a dotimes counter", "(defn f [] (dotimes [[a b] 3] 0))"; "a declare parameter", pt ^ "(declare g [{:keys [x]} Point] \"G\")" ]; + (* ── match over an enum ────────────────────────────────────────── *) + + (* Not shipped, and refused twice over because there are two ways to write it + and they fail in different files. Both now say the same thing, which is the + point: the lowering is not what is missing — a keyword has no case in + [Ast.pattern], and [lib/load.ml] matches that type exhaustively. *) + rejects_check "match over an enum, members written as keywords" + "(defenum K [lo 0 hi 1])\n(defn f [k K] i32 (match k :lo 1 :hi 2))" + ~needle:"is not implemented as a pattern"; + rejects_check "match over an enum, members written as names" + "(defenum K [lo 0 hi 1])\n(defn f [k K] i32 (match k lo 1 hi 2))" + ~needle:"match over the enum K is not implemented"; + (* The old message blamed milestone 2, which was never the reason. An Option + still gets that answer, and still should. *) + rejects_check "match over something that is neither" + "(defn f [n i32] i32 (match n _ 2))" + ~needle:"match works on an Option at milestone 2, not on i32"; + (* 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" + "(defstruct P [x i32])\n\ + (defn f [o (Option P)] i32 (match o (Some {:keys [x]}) x None 0))" + ~needle:"a pattern binds only in let"; + (* The desugaring's own machinery is unspellable: the reader makes [~] a delimiter, so the name never reaches the parser as one symbol. *) rejects_check "the desugaring's internal name cannot be written by hand"