Say why match stops at Option, since the milestone was never the reason
Two ways to write a match over an enum and two different refusals, neither of them true. (match k :lo ...) died in the parser with "expected a pattern, found :hi" — which arm it named depended on cons evaluation order, and it never mentioned enums. (match k lo ...) died in the checker blaming milestone 2, which is not what stands in the way. What stands in the way is worth writing down, because the feature is close. An enum is an i32 at run time and its members are all known, so the arms are a chain of (= k :member) and the exhaustiveness check falls out of env.enums — a desugaring, no new IR node, the same shape as everything else this lane landed. What is missing is a case in Ast.pattern for a keyword, and load.ml matches that type exhaustively with no wildcard, so the variant cannot be added from a session that does not own the file. One line, for whoever does. That is also why destructuring went through a call to an unspellable name instead: a name in call position is an open namespace check.ml already owns, whereas tagging Pctor with ":lo" would put a second meaning into a field another file destructures as a constructor. The struct-tail case in the acceptance program is unrelated housekeeping: the corpus slices arrays of i32, u8 and f32 and nothing wider, so nothing else proves the desugared (slice xs n (len xs)) gets a struct's stride right.
This commit is contained in:
parent
0b8564828b
commit
4428c864cf
12
lib/check.ml
12
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"
|
||||
|
||||
13
lib/parse.ml
13
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)
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user