A lambda's last form is checked at the return its position wants, a call's arguments and a kept cond with no else keep a when as an Option, a when used as an arithmetic operand is blamed itself, elif let reads and prints as a nested if-let, and dyn get over text or a vec runs at's own runtime body.

This commit is contained in:
Joseph Ferano 2026-09-26 13:09:21 +07:00
parent a3899f3ecb
commit 79430aca77
12 changed files with 258 additions and 27 deletions

View File

@ -22,7 +22,8 @@ CLOSED: [2026-09-26]
is a statement, not an Option. Rules out a plain name or =_= as the pattern (use =let=). is a statement, not an Option. Rules out a plain name or =_= as the pattern (use =let=).
** DONE when as a value, and get as a checked lookup ** DONE when as a value, and get as a checked lookup
CLOSED: [2026-09-26] CLOSED: [2026-09-26]
Every one-armed =if= is a =when=; kept, it is =Option(T)=, nested over an Option body Every one-armed =if= (and a =cond= with no =:else=) is a =when=; kept — a =let= value, a
call's argument, a lambda's return — it is =Option(T)=, nested over an Option body
(Rust's =bool::then=), and body-or-nil where a dyn is wanted. A =_=-inferred return's (Rust's =bool::then=), and body-or-nil where a dyn is wanted. A =_=-inferred return's
last form is not kept. =get= over dyn text or vec is nil when out of range; =.field= still traps. last form is not kept. =get= over dyn text or vec is nil when out of range; =.field= still traps.

View File

@ -951,6 +951,10 @@ type ctx = {
Option; not, it is a statement. [want] alone cannot say, since a Option; not, it is a statement. [want] alone cannot say, since a
statement and an unannotated [let] value both arrive with none. *) statement and an unannotated [let] value both arrive with none. *)
mutable used : bool; mutable used : bool;
(* The arguments of the call being checked: each is kept, whatever the
callee wants of it, so a [when] written as an operand answers its Option
there rather than a Unit the other operands are then blamed against. *)
mutable kept : Ast.expr list;
(* True inside a [defer]'s forms. A defer is the cleanup a transfer runs on (* True inside a [defer]'s forms. A defer is the cleanup a transfer runs on
its way out (§5), so a transfer *starting* there has no answer: this its way out (§5), so a transfer *starting* there has no answer: this
function's defers are already half run and the first transfer's target is function's defers are already half run and the first transfer's target is
@ -5112,7 +5116,7 @@ let with_recovery env ~on f =
let invented_ctx env ret = let invented_ctx env ret =
{ env; ret; lits = None; slots = 0; slot_tys = []; slot_names = []; scope = []; { env; ret; lits = None; slots = 0; slot_tys = []; slot_names = []; scope = [];
defers = []; defer_slot = None; outer = []; outer_what = None; caught = []; place_ok = false; envslot = None; parent = None; in_frames = None; loops = []; tail = false; used = false; defers = []; defer_slot = None; outer = []; outer_what = None; caught = []; place_ok = false; envslot = None; parent = None; in_frames = None; loops = []; tail = false; used = false; kept = [];
in_defer = false; defer_ok = false; defer_block = "a nested form"; in_defer = false; defer_ok = false; defer_block = "a nested form";
owner = "<none>" } owner = "<none>" }
@ -5990,7 +5994,7 @@ and check_value ctx ?want (e : Ast.expr) : Tast.expr =
it unless the arm below hands it on deliberately. *) it unless the arm below hands it on deliberately. *)
let tail = ctx.tail in let tail = ctx.tail in
ctx.tail <- false; ctx.tail <- false;
let used = ctx.used in let used = ctx.used || List.memq e ctx.kept in
ctx.used <- false; ctx.used <- false;
match e.Ast.e with match e.Ast.e with
(* A negative literal in a generic body, at an instantiation that made it (* A negative literal in a generic body, at an instantiation that made it
@ -6404,7 +6408,15 @@ and check_value ctx ?want (e : Ast.expr) : Tast.expr =
when (match want with Some (Types.Var _) -> true | _ -> false) when (match want with Some (Types.Var _) -> true | _ -> false)
&& literal_arith e <> None -> && literal_arith e <> None ->
int_literal loc ~want ~preds:ctx.env.tvpreds (Option.get (literal_arith e)) int_literal loc ~want ~preds:ctx.env.tvpreds (Option.get (literal_arith e))
| Ast.Call (head, args) -> check_call ctx ~want loc head args | Ast.Call (head, args) ->
let outer = ctx.kept in
(* A with-allocator's arguments after the first are a body, run in
order, and not values. *)
(match head.Ast.e with
| Ast.Var ("with-allocator" | "builtin/with-allocator") -> ctx.kept <- []
| _ -> ctx.kept <- args);
Fun.protect ~finally:(fun () -> ctx.kept <- outer)
(fun () -> check_call ctx ~want loc head args)
| Ast.Unwrap (Ast.Usome, v) -> | Ast.Unwrap (Ast.Usome, v) ->
(* Unwrap Some, else early-return None from the enclosing function, so the (* Unwrap Some, else early-return None from the enclosing function, so the
enclosing function must itself return an Option (plan.org). *) enclosing function must itself return an Option (plan.org). *)
@ -6966,7 +6978,20 @@ and check_fn ctx ~want ?gen loc (params : string list) body =
in in
List.iter2 List.iter2
(fun n t -> ignore (bind fctx n t ~assignable:false)) params pts; (fun n t -> ignore (bind fctx n t ~assignable:false)) params pts;
let fbody = map_lr (fun e -> check fctx e) body in (* The last form is checked at the return type the position wants, as a
defn's is at its declared one, so a value that takes its type from what
is asked of it — a kept [when], [None], a bare struct — gets it here. *)
let last_want =
match ret0 with
| Some r when not (Types.equal r Types.Unit) -> Some r
| _ -> None
in
let n = List.length body in
let fbody =
map_lr
(fun (i, e) -> if i = n - 1 then check fctx ?want:last_want e else check fctx e)
(List.mapi (fun i e -> (i, e)) body)
in
(* The same rule an ordinary defn's body follows: the last form is the (* The same rule an ordinary defn's body follows: the last form is the
answer, and it has to be the declared return type — or, when nothing answer, and it has to be the declared return type — or, when nothing
declared one ([ret0] is [None]), the last form's own type *is* the declared one ([ret0] is [None]), the last form's own type *is* the
@ -8336,6 +8361,42 @@ and check_if_once ctx ~tail ~used ?want loc c t e =
match e with match e with
| None -> check_when ctx ~used ?want loc c (fun ?want () -> | None -> check_when ctx ~used ?want loc c (fun ?want () ->
branch ctx (fun () -> in_tail (fun () -> check ctx ?want t))) branch ctx (fun () -> in_tail (fun () -> check ctx ?want t)))
(* A kept chain whose last else is missing — a [cond] with no [:else],
whose fallthrough is [(do)], or an [if] whose else is a [when] — is one
[when] spread over several tests: an Option, [None] when no test holds,
and [Some] of the arm that ran. *)
| Some e when kept_open ~used want e ->
(match e.Ast.e with
| Ast.Do [] ->
check_when ctx ~used:true ?want loc c (fun ?want () ->
branch ctx (fun () -> in_tail (fun () -> check ctx ?want t)))
| _ ->
let tw =
match want with
| Some (Types.Option i) -> Some i
| Some Types.Dyn -> Some Types.Dyn
| _ -> None
in
let t = branch ctx (fun () -> in_tail (fun () -> check ctx ?want:tw t)) in
let rest ~used ?want () =
branch ctx (fun () ->
ctx.tail <- tail; ctx.used <- used; check ctx ?want e)
in
match t.Tast.ty with
| Types.Unit ->
expect ctx loc ~want
(mk loc Types.Unit (Tast.If (c, t, rest ~used:false ())))
| Types.Never ->
let e = rest ~used:true ?want () in
mk loc e.Tast.ty (Tast.If (c, t, e))
| Types.Dyn ->
let e = rest ~used:true ~want:Types.Dyn () in
expect ctx loc ~want (mk loc Types.Dyn (Tast.If (c, t, e)))
| ty ->
let oty = Types.Option ty in
let e = rest ~used:true ~want:oty () in
expect ctx loc ~want
(mk loc oty (Tast.If (c, mk loc oty (Tast.Some_ t), e))))
(* Two literal arms meet at the wider of their own types, as two literal (* Two literal arms meet at the wider of their own types, as two literal
elements of an array do: [(if c 1 2.5)] is an f64. *) elements of an array do: [(if c 1 2.5)] is an f64. *)
| Some e | Some e
@ -8572,6 +8633,23 @@ and check_when ctx ~used ?want loc c
expect ctx loc ~want expect ctx loc ~want
(mk loc oty (Tast.If (c, mk loc oty (Tast.Some_ t), mk loc oty Tast.None_))) (mk loc oty (Tast.If (c, mk loc oty (Tast.Some_ t), mk loc oty Tast.None_)))
(* Whether a two-armed [if] is kept and its else chain ends without one —
[(do)], or a one-armed [if] — so the whole chain answers an Option. *)
and kept_open ~used want (e : Ast.expr) =
let kept =
match want with
| Some (Types.Unit | Types.Never) -> false
| Some _ -> true
| None -> used
in
let rec open_ (e : Ast.expr) =
match e.Ast.e with
| Ast.Do [] | Ast.If (_, _, None) -> true
| Ast.If (_, _, Some e') -> open_ e'
| _ -> false
in
kept && open_ e
(* The type two literals meet at, each at its own type — a wide integer at (* The type two literals meet at, each at its own type — a wide integer at
u64, which is the only type that holds one. *) u64, which is the only type that holds one. *)
and literal_join ctx (a : Ast.expr) (b : Ast.expr) = and literal_join ctx (a : Ast.expr) (b : Ast.expr) =
@ -10984,6 +11062,7 @@ and cast_operand ctx loc name ~needs ?also ~what ~is v =
"%s converts %s. %s — %s" name what known fix "%s converts %s. %s — %s" name what known fix
and fold_left_prim ctx ~want loc name p ~needs ok what args = and fold_left_prim ctx ~want loc name p ~needs ok what args =
refuse_kept_when ctx name args;
let x, y, rest = let x, y, rest =
match args with x :: y :: rest -> x, y, rest | _ -> assert false match args with x :: y :: rest -> x, y, rest | _ -> assert false
in in
@ -11016,6 +11095,29 @@ and fold_left_prim ctx ~want loc name p ~needs ok what args =
expect ctx loc ~want acc expect ctx loc ~want acc
end end
(* An operand is kept, so a [when] written as one answers an Option, which
no arithmetic takes. Said at the [when], before the operands are checked
against each other — where the literal beside it would be blamed instead.
A [when] over a dyn answers a dyn, and that is left to the fold. *)
and refuse_kept_when ctx name (args : Ast.expr list) =
List.iter
(fun (a : Ast.expr) ->
match a.Ast.e with
| Ast.If (_, _, None) ->
(match probe ctx a.Ast.loc (fun () -> (check ctx a).Tast.ty) with
| Some (Types.Option _ as t) ->
let fln = fln_source a.Ast.loc in
Loc.failk "check/kept-when" a.Ast.loc
"this %s is an operand of %s, so its value is kept, and there it \
gives %s: Some of its value when the test holds, None when it \
does not. %s takes numbers. Give it an else, or unwrap what it \
gives with match"
(if fln then "if without an else" else "when") name
(tyname a.Ast.loc t) name
| _ -> ())
| _ -> ())
args
(* The dyn lowering of a fold: one call per operator application, left to (* The dyn lowering of a fold: one call per operator application, left to
right, each taking and answering a dyn word. The typed side of a mixed pair right, each taking and answering a dyn word. The typed side of a mixed pair
is boxed on the way in — [box] is the identity on something already dyn, so is boxed on the way in — [box] is the identity on something already dyn, so
@ -11716,8 +11818,10 @@ and checked_get ctx ~want loc (target : Tast.expr) (idx : Ast.expr list) =
and dyn_get ctx ~want loc (target : Tast.expr) (keys : Ast.expr list) = and dyn_get ctx ~want loc (target : Tast.expr) (keys : Ast.expr list) =
let nil () = rt loc Types.Dyn "flan_dyn_nil" [] in let nil () = rt loc Types.Dyn "flan_dyn_nil" [] in
let one v k = rt loc Types.Dyn "flan_dyn_get_at" [ v; k; here loc ] in let one v k = rt loc Types.Dyn "flan_dyn_get_at" [ v; k; here loc ] in
(* A slot per key only when there are several: one key is passed as it (* A slot per key only when there are several: one key goes straight to
stands, so a single-key get compiles as it always did. *) [flan_dyn_get_at]. Over a text or a vec that runs [at]'s own body in the
runtime, so [get] and [at] count a text the same way whatever [at]
comes to count. *)
let slotted = List.length keys > 1 in let slotted = List.length keys > 1 in
let keys = let keys =
map_lr map_lr
@ -11909,6 +12013,7 @@ and named_call ?(qualified = false) ctx ~want loc name args =
nobody writes on purpose. *) nobody writes on purpose. *)
| "%" -> | "%" ->
arity ctx loc name 2 args; arity ctx loc name 2 args;
refuse_kept_when ctx name args;
let a, b = binary ctx ~dyn_ok:true name loc ~want:(numeric_want want) args in let a, b = binary ctx ~dyn_ok:true name loc ~want:(numeric_want want) args in
if a.Tast.ty = Types.Dyn || b.Tast.ty = Types.Dyn then if a.Tast.ty = Types.Dyn || b.Tast.ty = Types.Dyn then
dyn_fold ctx ~want loc name [ a; b ] [] dyn_fold ctx ~want loc name [ a; b ] []
@ -15319,7 +15424,7 @@ and trial ctx f =
let[@warning "+9"] { env = _; ret = _; lits = _; slots; slot_tys; slot_names; scope; let[@warning "+9"] { env = _; ret = _; lits = _; slots; slot_tys; slot_names; scope;
defers; defer_slot; defer_ok; defer_block; outer = _; defers; defer_slot; defer_ok; defer_block; outer = _;
outer_what; caught; place_ok; envslot; parent = _; outer_what; caught; place_ok; envslot; parent = _;
in_frames; loops; tail; used; in_defer; in_frames; loops; tail; used; kept; in_defer;
owner = _ } = ctx in owner = _ } = ctx in
let undo, keep = snapshot_env ctx.env in let undo, keep = snapshot_env ctx.env in
match speculate ctx.env f with match speculate ctx.env f with
@ -15332,7 +15437,7 @@ and trial ctx f =
ctx.defer_ok <- defer_ok; ctx.defer_block <- defer_block; ctx.defer_ok <- defer_ok; ctx.defer_block <- defer_block;
ctx.outer_what <- outer_what; ctx.in_frames <- in_frames; ctx.outer_what <- outer_what; ctx.in_frames <- in_frames;
ctx.caught <- caught; ctx.place_ok <- place_ok; ctx.envslot <- envslot; ctx.caught <- caught; ctx.place_ok <- place_ok; ctx.envslot <- envslot;
ctx.loops <- loops; ctx.tail <- tail; ctx.used <- used; ctx.loops <- loops; ctx.tail <- tail; ctx.used <- used; ctx.kept <- kept;
ctx.in_defer <- in_defer; ctx.in_defer <- in_defer;
Error d Error d
| exception e -> keep (); raise e | exception e -> keep (); raise e

View File

@ -1056,6 +1056,33 @@ and value_lines n prefix (v : Form.t) =
and slot n (f : Form.t) = block n (stmts_of f) and slot n (f : Form.t) = block n (stmts_of f)
(* Whether an else is a chain the reader makes of an [elif let]: an if-let,
or an [if] whose own else is one. *)
and has_let_else (x : Form.t) =
match x.v with
| Form.List ({ v = Form.Sym "if-let"; _ } :: { v = Form.Vec [ _; _ ]; _ } :: _ :: ([] | [ _ ])) -> true
| Form.List [ { v = Form.Sym "if"; _ }; _; _; r ] -> has_let_else r
| _ -> false
(* Such a chain as the elif and else clauses at column [n]. A [when] at its
end is an [elif] with no else after it, which is how the reader reads one
back. *)
and let_chain n (x : Form.t) =
let i = ind n in
let tag (x : Form.t) l = Source_text.tag x.loc.Loc.line l in
match x.v with
| Form.List ({ v = Form.Sym "if-let"; _ } :: ({ v = Form.Vec [ _; _ ]; _ } as hd) :: a
:: ([] | [ _ ] as r)) ->
(tag x (i ^ "el" ^ if_let_head hd) :: slot (n + 2) a)
@ (match r with [ r ] -> let_chain n r | _ -> [])
(* Past an [elif let] every clause nests, so an [if] here is an [elif]
whether or not another let follows. *)
| Form.List [ { v = Form.Sym "if"; _ }; c; a; r ] ->
(tag c (i ^ "elif " ^ at 1 c) :: slot (n + 2) a) @ let_chain n r
| Form.List ({ v = Form.Sym "when"; _ } :: c :: (_ :: _ as body)) ->
tag c (i ^ "elif " ^ at 1 c) :: block (n + 2) body
| _ -> tag x (i ^ "else") :: slot (n + 2) x
and label_of = function and label_of = function
| ({ Form.v = Form.Kw k; _ }) :: rest when kw_ok k -> (":" ^ k ^ " ", rest) | ({ Form.v = Form.Kw k; _ }) :: rest when kw_ok k -> (":" ^ k ^ " ", rest)
| rest -> ("", rest) | rest -> ("", rest)
@ -1091,6 +1118,9 @@ and sugar n (f : Form.t) : string list option =
| _ -> simple x | _ -> simple x
in in
let line = i ^ fst (expr f) in let line = i ^ fst (expr f) in
if has_let_else b then
Some (((i ^ "if " ^ at 1 c) :: slot (n + 2) a) @ let_chain n b)
else
if simple a && chain b && String.length line <= width && not (!inside f) if simple a && chain b && String.length line <= width && not (!inside f)
then Some [ line ] then Some [ line ]
else else
@ -1118,6 +1148,7 @@ and sugar n (f : Form.t) : string list option =
let head = (i ^ if_let_head hd) :: slot (n + 2) a in let head = (i ^ if_let_head hd) :: slot (n + 2) a in
(match b with (match b with
| [] -> Some head | [] -> Some head
| [ e ] when has_let_else e -> Some (head @ let_chain n e)
| [ ({ v = Form.List ({ v = Form.Sym "cond"; _ } :: args); _ } as e) ] -> | [ ({ v = Form.List ({ v = Form.Sym "cond"; _ } :: args); _ } as e) ] ->
(match pairs args with (match pairs args with
| Some (_ :: _ as prs) -> | Some (_ :: _ as prs) ->

View File

@ -2134,11 +2134,17 @@ and header (s : st) w : Form.t =
(match (peek p).tok with (match (peek p).tok with
| NAME ("else" | "elif") when w = "when" && not (assigns p) -> when_else p | NAME ("else" | "elif") when w = "when" && not (assigns p) -> when_else p
| _ -> ()); | _ -> ());
(* The [elif let P = v] heads, as the [[P v]] each stands as. *)
let elif_lets = ref [] in
let rec elifs acc = let rec elifs acc =
match (peek p).tok with match (peek p).tok with
| NAME "elif" when not (assigns p) -> | NAME "elif" when not (assigns p) ->
ignore (advance p); ignore (advance p);
let c, _ = binary p 1 in let c =
match if_let_head p with
| Some m -> elif_lets := m :: !elif_lets; m
| None -> fst (binary p 1)
in
(match (peek p).tok with (match (peek p).tok with
| NAME "then" when oneline -> | NAME "then" when oneline ->
ignore (advance p); ignore (advance p);
@ -2174,6 +2180,25 @@ and header (s : st) w : Form.t =
| _ -> None | _ -> None
in in
match els_, else_ with match els_, else_ with
| _ when !elif_lets <> [] ->
(* An [elif let] makes the rest of the chain the else of an if-let:
each clause nests in the one before it, [if] or [if-let] as its
head is, and a chain with no else ends in a [when]. *)
let is_let c = List.memq c !elif_lets || Some c == letp in
let rec build = function
| [] -> Option.map (fun (el, e) -> blk s el e) else_
| ((c : Form.t), b) :: rest ->
let at = c.Form.loc in
let f items = Form.make (Form.List items) at in
let r = build rest in
Some
(if is_let c then f (sym at "if-let" :: c :: blk s at b :: Option.to_list r)
else
match r with
| None -> f (sym at "when" :: c :: b)
| Some r -> f [ sym at "if"; c; blk s at b; r ])
in
Option.get (build ((c, body) :: els_))
| [], None -> named "when" (c :: body) | [], None -> named "when" (c :: body)
| [], Some (el, e) -> named "if" [ c; blk s l0 body; blk s el e ] | [], Some (el, e) -> named "if" [ c; blk s l0 body; blk s el e ]
| _ -> | _ ->

View File

@ -4610,8 +4610,11 @@ static int64_t need_index(const uint8_t *loc, int64_t loclen, const char *op,
/* A text answers a byte, as an int. That is what [(at s i)] on a /* A text answers a byte, as an int. That is what [(at s i)] on a
* [(Slice u8)] does in the typed language, and a text is a run of bytes in * [(Slice u8)] does in the typed language, and a text is a run of bytes in
* both. Codepoints are utf8's job and stay there. */ * both. Codepoints are utf8's job and stay there. */
flan_dyn flan_dyn_at(flan_dyn v, flan_dyn i, const uint8_t *loc, /* [at]'s, and [get]'s over a text or a vec: one body, so the two always
int64_t loclen) { * count the same way. [soft] is [get]'s — an index out of range is nil
* rather than a trap. */
static flan_dyn at_core(flan_dyn v, flan_dyn i, const uint8_t *loc,
int64_t loclen, int soft) {
int64_t k; int64_t k;
flan_obj *o; flan_obj *o;
/* m[:k] on a map is (get m :k), whatever the key: get's rule, nil when /* m[:k] on a map is (get m :k), whatever the key: get's rule, nil when
@ -4624,7 +4627,10 @@ flan_dyn flan_dyn_at(flan_dyn v, flan_dyn i, const uint8_t *loc,
o = dyn_obj(v); o = dyn_obj(v);
if (o->kind == OBJ_VIEW) { if (o->kind == OBJ_VIEW) {
int64_t len = view_len(loc, loclen, "at", o); int64_t len = view_len(loc, loclen, "at", o);
if (k < 0 || k >= len) trap_range(loc, loclen, "at", v, k, len); if (k < 0 || k >= len) {
if (soft) return flan_dyn_nil();
trap_range(loc, loclen, "at", v, k, len);
}
{ {
uint8_t *p = view_elem_at(o, k); uint8_t *p = view_elem_at(o, k);
switch ((o->gen >> 9) & 3) { switch ((o->gen >> 9) & 3) {
@ -4635,11 +4641,19 @@ flan_dyn flan_dyn_at(flan_dyn v, flan_dyn i, const uint8_t *loc,
} }
} }
} }
if (k < 0 || k >= o->len) trap_range(loc, loclen, "at", v, k, o->len); if (k < 0 || k >= o->len) {
if (soft) return flan_dyn_nil();
trap_range(loc, loclen, "at", v, k, o->len);
}
if (o->kind == OBJ_TEXT) return flan_dyn_from_i64(obj_text_bytes(o)[k]); if (o->kind == OBJ_TEXT) return flan_dyn_from_i64(obj_text_bytes(o)[k]);
return o->u.v.items[k]; return o->u.v.items[k];
} }
flan_dyn flan_dyn_at(flan_dyn v, flan_dyn i, const uint8_t *loc,
int64_t loclen) {
return at_core(v, i, loc, loclen, 0);
}
/* (slice s lo) and (slice s lo hi) over a text; nil for [hi] is the length. /* (slice s lo) and (slice s lo hi) over a text; nil for [hi] is the length.
* The typed slice of a string is a view, and this is a copy: a text is * The typed slice of a string is a view, and this is a copy: a text is
* immutable, so no program can tell the two apart. A vec's slice would have * immutable, so no program can tell the two apart. A vec's slice would have
@ -4824,14 +4838,10 @@ flan_dyn flan_dyn_get(flan_dyn m, flan_dyn k, const uint8_t *loc,
* still be an int — a wrong kind of key is a mistake, not an absence. */ * still be an int — a wrong kind of key is a mistake, not an absence. */
flan_dyn flan_dyn_get_at(flan_dyn v, flan_dyn i, const uint8_t *loc, flan_dyn flan_dyn_get_at(flan_dyn v, flan_dyn i, const uint8_t *loc,
int64_t loclen) { int64_t loclen) {
int64_t k, len; /* Through [at]'s own body, so however [at] counts a text — bytes now —
flan_obj *o; * [get] counts the same. */
if (!is_text(v) && !is_vec(v)) return flan_dyn_get(v, i, loc, loclen); if (!is_text(v) && !is_vec(v)) return flan_dyn_get(v, i, loc, loclen);
k = need_index(loc, loclen, "get", v, i); return at_core(v, i, loc, loclen, 1);
o = dyn_obj(v);
len = o->kind == OBJ_VIEW ? view_len(loc, loclen, "get", o) : o->len;
if (k < 0 || k >= len) return flan_dyn_nil();
return flan_dyn_at(v, i, loc, loclen);
} }
static flan_dyn contains_walk(flan_dyn m, flan_dyn k) { static flan_dyn contains_walk(flan_dyn m, flan_dyn k) {

View File

@ -235,9 +235,11 @@ Each item: the proposal, then the reason in one line.
what an `if` without `else` reads as too. No `else` or `elif` follows it. what an `if` without `else` reads as too. No `else` or `elif` follows it.
A `when` whose value is kept (a `let`'s value, an argument, a return) gives A `when` whose value is kept (a `let`'s value, an argument, a return) gives
`Some(a)` when `c` holds and `None` when it does not; where a `dyn` is `Some(a)` when `c` holds and `None` when it does not; where a `dyn` is
wanted, `a` or `nil`. As a statement it gives nothing. **Built.** wanted, `a` or `nil`. As a statement it gives nothing. An `if`/`elif` chain
with no `else` is the same when kept: `None` when no test holds. **Built.**
- **`if let P = v`** plus a block reads as `(if-let [P v] then)`; `elif` and - **`if let P = v`** plus a block reads as `(if-let [P v] then)`; `elif` and
`else` follow as for `if`, the rest of the chain being the `if-let`'s else. `else` follow as for `if`, the rest of the chain being the `if-let`'s else.
`elif let P = v` is a further `if-let` nested in that else.
`P` is any `match` pattern, and its names are bound in the block only. One `P` is any `match` pattern, and its names are bound in the block only. One
line: `if let Some(g) = o then g else 0`. A pattern that cannot fail, a line: `if let Some(g) = o then g else 0`. A pattern that cannot fail, a
plain name or `_`, is refused toward `let`. **Built.** plain name or `_`, is refused toward `let`. **Built.**

View File

@ -10,6 +10,7 @@
(defn dvec [] dyn [10 [20 21] 30]) (defn dvec [] dyn [10 [20 21] 30])
(defn dmap [] dyn {:a 1 :b [5 6]}) (defn dmap [] dyn {:a 1 :b [5 6]})
(defn dtext [] dyn "hey")
(defn main [] () (defn main [] ()
(let [a [1 2 3] (let [a [1 2 3]
@ -60,4 +61,7 @@
(println (get m :z)) (println (get m :z))
(println (get m :b 1)) (println (get m :b 1))
(println (get m :z 1)) (println (get m :z 1))
(println (.a m)))) (println (.a m))
;; Dyn text: get counts it as at does.
(println (get (dtext) 1))
(println (get (dtext) 3))))

View File

@ -19,6 +19,17 @@ fn describe(o: Option(i32), k: i32) -> i32
fn first(xs: [3 i32]) -> i32 fn first(xs: [3 i32]) -> i32
if let Some(x) = get(xs, 0) then x else -1 if let Some(x) = get(xs, 0) then x else -1
;; elif let: the rest of the chain is the else of an if let.
fn pick(a: Option(i32), b: Option(i32), k: i32) -> i32
if let Some(x) = a
x
elif let Some(y) = b
y * 10
elif k > 5
100
else
0
;; Nested: a pattern tested inside the block of another. ;; Nested: a pattern tested inside the block of another.
fn area(s: Option(Shape)) -> i32 fn area(s: Option(Shape)) -> i32
if let Some(x) = s if let Some(x) = s
@ -35,6 +46,10 @@ fn main()
println(describe(None, 9)) println(describe(None, 9))
println(describe(None, 1)) println(describe(None, 1))
println(first([9, 8, 7])) println(first([9, 8, 7]))
println(pick(Some(1), None, 0))
println(pick(None, Some(2), 0))
println(pick(None, None, 9))
println(pick(None, None, 1))
;; Shadowing: g is the payload inside the block and the outer g after it. ;; Shadowing: g is the payload inside the block and the outer g after it.
if let Some(g) = Some(1) if let Some(g) = Some(1)
println(g) println(g)

View File

@ -21,6 +21,13 @@
;; Dyn: the body or nil. ;; Dyn: the body or nil.
(defn dyn-when [x] dyn (when x 5)) (defn dyn-when [x] dyn (when x 5))
;; A lambda's last form is kept at the return type its position wants.
(defn call-it [f (Fn [] (Option i32))] () (show (f)))
;; A kept cond with no :else is a when over several tests.
(defn pick [n i32] (Option i32) (cond (= n 1) 10 (= n 2) 20))
(defn dpick [n] dyn (cond (= n 1) "a" (= n 2) "b"))
(defn main [] () (defn main [] ()
(show (half 10)) (show (half 10))
(show (half 7)) (show (half 7))
@ -36,6 +43,14 @@
(level (wrap false (Some 1))) (level (wrap false (Some 1)))
(println (dyn-when true)) (println (dyn-when true))
(println (dyn-when nil)) (println (dyn-when nil))
(call-it (fn [] (when true 6)))
(call-it (fn [] (when false 6)))
(show (pick 2))
(show (pick 3))
(let [k (cond (> 3 5) 1 (> 3 2) 2)] (show k))
(println (dpick 1))
(println (dpick 9))
(cond (> 3 5) (println "no") (> 3 2) (println "cond stmt"))
;; Statements, unchanged. ;; Statements, unchanged.
(when true (println "ran")) (when true (println "ran"))
(when false (println "not run")) (when false (println "not run"))

View File

@ -2147,7 +2147,8 @@ let () =
dyn_if_truthy_out; dyn_if_truthy_out;
(* A kept when is an Option; get is a checked lookup; if let. *) (* A kept when is an Option; get is a checked lookup; if let. *)
let when_value_out = let when_value_out =
"5\nnone\n42\nnone\n9\n1\nsome none\nnone\n5\nnil\nran\nend\n" "5\nnone\n42\nnone\n9\n1\nsome none\nnone\n5\nnil\n6\nnone\n20\nnone\n2\n\
a\nnil\ncond stmt\nran\nend\n"
in in
outputs "when as a value" "programs/when-value.flan" when_value_out; outputs "when as a value" "programs/when-value.flan" when_value_out;
outputs ~opt:"-O0" "when as a value, -O0" "programs/when-value.flan" when_value_out; outputs ~opt:"-O0" "when as a value, -O0" "programs/when-value.flan" when_value_out;
@ -2155,7 +2156,7 @@ let () =
let get_checked_out = let get_checked_out =
"none\n1\n3\nnone\n4\nnone\nnone\n6\nnone\n3\nnone\n\ "none\n1\n3\nnone\n4\nnone\nnone\n6\nnone\n3\nnone\n\
none\n10\n30\nnone\n20\n30\nnone\nnone\n101\nnone\n\ none\n10\n30\nnone\n20\n30\nnone\nnone\n101\nnone\n\
nil\n10\n30\nnil\n21\nnil\nnil\n1\nnil\n6\nnil\n1\n" nil\n10\n30\nnil\n21\nnil\nnil\n1\nnil\n6\nnil\n1\n101\nnil\n"
in in
outputs "get as a checked lookup" "programs/get-checked.flan" get_checked_out; outputs "get as a checked lookup" "programs/get-checked.flan" get_checked_out;
outputs ~opt:"-O0" "get as a checked lookup, -O0" "programs/get-checked.flan" outputs ~opt:"-O0" "get as a checked lookup, -O0" "programs/get-checked.flan"
@ -2163,7 +2164,7 @@ let () =
outputs ~x86:true "get as a checked lookup, --x86" "programs/get-checked.flan" outputs ~x86:true "get as a checked lookup, --x86" "programs/get-checked.flan"
get_checked_out; get_checked_out;
let if_let_out = let if_let_out =
"5\n100\n0\n9\n1\n7\nabsent\nnorth\n6\n-1\n-2\n14\nwhen block\n" "5\n100\n0\n9\n1\n20\n100\n0\n1\n7\nabsent\nnorth\n6\n-1\n-2\n14\nwhen block\n"
in in
outputs "if let" "programs/if-let.fln" if_let_out; outputs "if let" "programs/if-let.fln" if_let_out;
outputs ~opt:"-O0" "if let, -O0" "programs/if-let.fln" if_let_out; outputs ~opt:"-O0" "if let, -O0" "programs/if-let.fln" if_let_out;

View File

@ -3917,7 +3917,7 @@ let () =
rejects_check "an inline generator's body has to answer the element type" rejects_check "an inline generator's body has to answer the element type"
"(defonce grid [2 [3 u8]] (array-gen [2 3] (fn [i j] 1.5)))\n\ "(defonce grid [2 [3 u8]] (array-gen [2 3] (fn [i j] 1.5)))\n\
(defn f [] i32 0)" (defn f [] i32 0)"
~needle:"expected u8, found f64"; ~needle:"expected u8, found the float literal 1.5";
rejects_check "an inline generator takes one argument per dimension too" rejects_check "an inline generator takes one argument per dimension too"
"(defn f [] i32 (let [a (array-gen [2] (fn [i j] i))] 0))" "(defn f [] i32 (let [a (array-gen [2] (fn [i j] i))] 0))"
~needle:"this array-gen has 1 dimension, so its generator is called with \ ~needle:"this array-gen has 1 dimension, so its generator is called with \
@ -8280,6 +8280,17 @@ let () =
"(defn main [] () (let [x (the (Option i32) (when true 1))] (println x)))"; "(defn main [] () (let [x (the (Option i32) (when true 1))] (println x)))";
rejects_check "a when is not its branch's type" ~needle:"(Option i32)" rejects_check "a when is not its branch's type" ~needle:"(Option i32)"
"(defn f [] i32 (when true 1))\n(defn main [] ())"; "(defn f [] i32 (when true 1))\n(defn main [] ())";
rejects_check "a when as an operand is blamed, not the literal beside it"
~needle:"this when is an operand of +, so its value is kept"
"(defn main [] () (println (+ 1 (when true 5))))";
rejects_check "on either side"
~needle:"this when is an operand of +, so its value is kept"
"(defn main [] () (println (+ (when true 5) 1)))";
accepts "a lambda's when takes the Option its position wants"
"(defn call-it [f (Fn [] (Option i32))] () (println (f)))\n\
(defn main [] () (call-it (fn [] (when true 6))))";
accepts "a kept cond with no :else is an Option"
"(defn f [n i32] (Option i32) (cond (= n 1) 10 (= n 2) 20))\n(defn main [] ())";
rejects_check "a map's get takes one key" ~needle:"a map's get takes one key" rejects_check "a map's get takes one key" ~needle:"a map's get takes one key"
"(defn main [] () (let [m (map-new i32 i32)] (println (get m 1 2))))"; "(defn main [] () (let [m (map-new i32 i32)] (println (get m 1 2))))";

View File

@ -1009,6 +1009,17 @@ let () =
" elif k > 5\n 100\n else\n 0"; " elif k > 5\n 100\n else\n 0";
round "if let with no else" "(defn f [o (Option i32)] () (if-let [(Some g) o] (do (println g) (println g))))" round "if let with no else" "(defn f [o (Option i32)] () (if-let [(Some g) o] (do (println g) (println g))))"
" if let Some(g) = o\n println(g)"; " if let Some(g) = o\n println(g)";
round "elif let"
"(defn f [a (Option i32) b (Option i32) k i32] i32 \
(if-let [(Some x) a] x (if-let [(Some y) b] (* y 10) (if (> k 5) 100 0))))"
" elif let Some(y) = b\n y * 10\n elif k > 5\n 100\n else\n 0";
round "elif let after a plain if"
"(defn f [a bool b (Option i32)] () \
(if a (println 1) (if-let [(Some y) b] (println y) (when (> 1 0) (println 2)))))"
" elif let Some(y) = b\n println(y)\n elif 1 > 0\n println(2)";
reads "elif let reads as a nested if-let"
"if let Some(x) = a\n f(x)\nelif let None = b\n g()\nelse\n h()"
"(if-let [(Some x) a] (f x) (if-let [None b] (g) (h)))";
round "a kept when" "(defn f [] () (let [w (when (> a 1) 2)] (g w)))" round "a kept when" "(defn f [] () (let [w (when (> a 1) 2)] (g w)))"
" let w = when a > 1 then 2"; " let w = when a > 1 then 2";
reads "when with a block" "when a\n b()\n c()" "(when a (b) (c))"; reads "when with a block" "when a\n b()\n c()" "(when a (b) (c))";