A T value flows into a T? where one is wanted, and a kept else-less form whose arm is already T? gives T?, not T??.

This commit is contained in:
Joseph Ferano 2026-09-26 18:16:34 +07:00
commit cfdc6e29fd
9 changed files with 503 additions and 36 deletions

View File

@ -45,6 +45,16 @@ Decided (133): =x?= is a bool; =if x?=, =elif x?=, =while x?= and the rest of an
make a local Option its payload in the block, in place (not a copy). Assigning an Option make a local Option its payload in the block, in place (not a copy). Assigning an Option
to it there is refused rather than ending the narrowing; =e? as g= names what a test to it there is refused rather than ending the narrowing; =e? as g= names what a test
found. Rules out =if let g = x= over a plain name, which is refused toward these. found. Rules out =if let g = x= over a plain name, which is refused toward these.
** DONE A T is wrapped where a T? is wanted
CLOSED: [2026-09-26]
Decided (138), like Swift: one level per boundary, the literal built at T first; not
inside a container. Rules out implicit unwrapping: a T? where a T is wanted stays refused.
** DONE A kept when over an Option body flattens one level
CLOSED: [2026-09-26]
Decided (140), reversing 125a: a kept =when=, else-less =if=/=elif= or =if let= chain whose
arm is already a =T?= is a =T?=, and =T= beside =T?= arms is =T?=; a =T??= arm stays =T??=.
Where =T??= is wanted the arm is Some of it. Rules out telling "no branch matched" apart
from "a branch gave None" without asking for =T??=.
** TODO The stepper does not step inside an optional chain ** TODO The stepper does not step inside an optional chain
=Ast.step_expr= treats a =Chain= as a leaf (its catch-all), so nothing in a chain's =Ast.step_expr= treats a =Chain= as a leaf (its catch-all), so nothing in a chain's
body gets a step point of its own. body gets a step point of its own.
@ -64,8 +74,8 @@ Option or a dyn holds (130); over any other type, and =_=, it is refused toward
** 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= (and a =cond= with no =:else=) is a =when=; kept — a =let= value, a 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 call's argument, a lambda's return — it is =Option(T)=, and body-or-nil where a dyn is
(Rust's =bool::then=), and body-or-nil where a dyn is wanted. A =_=-inferred return's wanted. Over an Option body it was nested (Rust's =bool::then=, 125a); 140 reversed that. 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.
** NEXT str and String ** NEXT str and String

View File

@ -2891,6 +2891,13 @@ let rec bind_ty ?(widen = false) ?(ro = true) subst (pat : Types.t)
bind_ty ~ro:(m = Types.Const) subst p a bind_ty ~ro:(m = Types.Const) subst p a
| Types.Vec p, Types.Vec a | Types.Vec p, Types.Vec a
| Types.Option p, Types.Option a -> inner p a | Types.Option p, Types.Option a -> inner p a
(* A plain value at a [$t?] parameter binds [$t] to its own type, and
[expect] wraps it (decision 138). At the top of an argument only, as the
widening below: an element of a Vec is never wrapped, so [(Vec $t?)]
meets a [(Vec i32)] as a mismatch. *)
| Types.Option p, a
when widen && (match a with Types.Dyn | Types.Never -> false | _ -> true) ->
bind_ty ~widen subst p a
| Types.Array (n, p), Types.Array (m, a) -> Int64.equal n m && inner p a | Types.Array (n, p), Types.Array (m, a) -> Int64.equal n m && inner p a
| Types.Map (k, v), Types.Map (k', v') -> inner k k' && inner v v' | Types.Map (k, v), Types.Map (k', v') -> inner k k' && inner v v'
(* Each function type against its own. *) (* Each function type against its own. *)
@ -3227,6 +3234,14 @@ let rec literal_arith (e : Ast.expr) : int64 option =
over literals alone. *) over literals alone. *)
let lone_literal (e : Ast.expr) = is_literal e || literal_arith e <> None let lone_literal (e : Ast.expr) = is_literal e || literal_arith e <> None
(* Set for one [check_value] entry: the literal is checked at the Option
itself, not wrapped, so a refusal names the Option (decision 138). *)
let skip_wrap = ref false
(* [None] as written, which has no type until an Option is asked of it. *)
let is_none_lit (e : Ast.expr) =
match e.Ast.e with Ast.Var "None" -> true | _ -> false
(* A value whose type comes only from defaults — a literal, [nil], [(Some 3)], (* A value whose type comes only from defaults — a literal, [nil], [(Some 3)],
arithmetic over literals, a [do] ending in one — so it takes the type of whatever arithmetic over literals, a [do] ending in one — so it takes the type of whatever
meets it. An arm of this kind is checked after the others, at their type, meets it. An arm of this kind is checked after the others, at their type,
@ -3811,6 +3826,12 @@ let lit_admits kind (t : Types.t) =
| `Box, t -> not (Types.equal t Types.Dyn) | `Box, t -> not (Types.equal t Types.Dyn)
| _ -> false | _ -> false
(* What a use at [t] says about a literal local: an (Option T) wanted of it
says T, since the local is built at T and then wrapped (decision 138), so
[let w: i64? = x] makes [x] the i64 [let w: i64 = x] does. *)
let rec lit_payload (t : Types.t) =
match t with Types.Option p -> lit_payload p | t -> t
(* The rounds a session may take before its last guesses are checked as (* The rounds a session may take before its last guesses are checked as
they stand. Merging makes two the usual count; the bound only stops a they stand. Merging makes two the usual count; the bound only stops a
pathological program from looping. *) pathological program from looping. *)
@ -5036,7 +5057,7 @@ let no_bare_nil (ops : Tast.expr list) =
| Some t -> nil_has_no_none nil.Tast.loc t | Some t -> nil_has_no_none nil.Tast.loc t
| None -> () | None -> ()
let expect ctx loc ~want (got : Tast.expr) = let rec expect ctx loc ~want (got : Tast.expr) =
match want with match want with
| None -> got | None -> got
| Some w -> | Some w ->
@ -5105,6 +5126,21 @@ let expect ctx loc ~want (got : Tast.expr) =
| (Types.Slice (Types.Const, _) | Types.Ptr (Types.Const, _)), _ | (Types.Slice (Types.Const, _) | Types.Ptr (Types.Const, _)), _
when Types.const_widens ~from:got.Tast.ty ~into:w -> when Types.const_widens ~from:got.Tast.ty ~into:w ->
{ got with Tast.ty = w } { got with Tast.ty = w }
(* Decision 138, Swift's rule: a T where a (Option T) is wanted is
[Some] of it. One level each time — a T? into a T?? is [Some] of the
Option, never the Option itself — and the payload goes through this
same boundary first, so a T into a T?? is [Some (Some t)] and an i32
into an (Option i64) is widened, then wrapped. Never the other way:
a T? where a T is wanted is still refused, and a dyn is left to the
nil <-> None arms above. When the payload is refused too, the
refusal below names the Option, as it did before. *)
| Types.Option t, g
when (match g with Types.Dyn | Types.Never -> false | _ -> true)
&& not (Types.fits ~expected:w ~actual:g) ->
(match expect ctx loc ~want:(Some t) got with
| v when Types.fits ~expected:t ~actual:v.Tast.ty -> mk loc w (Tast.Some_ v)
| _ -> got
| exception Loc.Error _ -> got)
| _ -> got | _ -> got
in in
if Types.fits ~expected:w ~actual:got.Tast.ty then got if Types.fits ~expected:w ~actual:got.Tast.ty then got
@ -5205,8 +5241,16 @@ let arm_join (a : Types.t) (b : Types.t) =
match Types.const_join a b with match Types.const_join a b with
| Some j -> Some j | Some j -> Some j
| None -> | None ->
(* A T beside a T? meets at the T?, the T wrapped in [Some]
(decision 138). Only the plain side moves, and by one level. *)
let wraps p u =
(match u with Types.Option _ | Types.Unit -> false | _ -> true)
&& (Types.equal p u || Types.widens_to ~from:u ~into:p)
in
(match a, b with (match a, b with
| Types.Dyn, _ | _, Types.Dyn -> Some Types.Dyn | Types.Dyn, _ | _, Types.Dyn -> Some Types.Dyn
| Types.Option p, u when wraps p u -> Some a
| u, Types.Option p when wraps p u -> Some b
| _ -> None) | _ -> None)
(* The type two untyped literals meet at: the wider of their own types, and (* The type two untyped literals meet at: the wider of their own types, and
an integer beside a float at the float — [(if c 1 2.5)] is an f32, though an integer beside a float at the float — [(if c 1 2.5)] is an f32, though
@ -6166,7 +6210,26 @@ and check_value ctx ?want (e : Ast.expr) : Tast.expr =
ctx.tail <- false; ctx.tail <- false;
let used = ctx.used || List.memq e ctx.kept in let used = ctx.used || List.memq e ctx.kept in
ctx.used <- false; ctx.used <- false;
let skipping = !skip_wrap in
skip_wrap := false;
match e.Ast.e with match e.Ast.e with
(* A literal where an (Option T) is wanted is built at T and then wrapped
(decision 138): [s = -1] over an [i64?] is [Some] of an i64 -1. It has
no type until one is asked of it, so it is asked the payload's, rather
than being built at a default and wrapped at the wrong width. *)
| Ast.Int _ | Ast.UInt _ | Ast.Float _ | Ast.Byte _ | Ast.Call _ | Ast.Arr (_ :: _)
when (match want with Some (Types.Option _) -> not skipping | _ -> false)
&& (lone_literal e || (match e.Ast.e with Ast.Arr _ -> true | _ -> false)) ->
let w = Option.get want in
let t = match w with Types.Option t -> t | _ -> assert false in
(match trial ctx (fun () -> check ctx ~want:t e) with
| Ok v when Types.fits ~expected:t ~actual:v.Tast.ty -> mk loc w (Tast.Some_ v)
| Ok v -> expect ctx loc ~want v
(* Refused at T: checked again at the Option as it was before 138, so
the refusal names what was wanted, [str?], and not only its payload. *)
| Error _ ->
skip_wrap := true;
check ctx ?want e)
(* 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
unsigned. The cast the ordinary refusal names would be wrong at every unsigned. The cast the ordinary refusal names would be wrong at every
other type the function is called at, so the fix is one that needs no other type the function is called at, so the fix is one that needs no
@ -7019,18 +7082,19 @@ and var ctx ?(qualified = false) loc ~want name =
| Some ({ blit = Some key; _ } as b) | Some ({ blit = Some key; _ } as b)
when (match ctx.lits, want with when (match ctx.lits, want with
| Some s, Some t -> | Some s, Some t ->
let t = lit_payload t in
s.recording && not !lit_quiet s.recording && not !lit_quiet
&& (Types.equal t Types.Dyn && (Types.equal t Types.Dyn
|| lit_admits (Option.value (lit_kind key) ~default:`Int) t) || lit_admits (Option.value (lit_kind key) ~default:`Int) t)
| _ -> false) -> | _ -> false) ->
let s = Option.get ctx.lits and t = Option.get want in let s = Option.get ctx.lits and t = lit_payload (Option.get want) in
let operand = List.memq loc !lit_operand_locs in let operand = List.memq loc !lit_operand_locs in
let c = if operand || Types.equal t Types.Dyn then Hint else Up in let c = if operand || Types.equal t Types.Dyn then Hint else Up in
lit_add s key (c, t, loc); lit_add s key (c, t, loc);
(try expect ctx loc ~want (mk loc b.bty (Tast.Local b.slot)) (try expect ctx loc ~want (mk loc b.bty (Tast.Local b.slot))
with Loc.Error _ when lit_kind key <> Some `Box && not operand -> with Loc.Error _ when lit_kind key <> Some `Box && not operand ->
s.dirty <- true; s.dirty <- true;
mk loc t (Tast.Local b.slot)) expect ctx loc ~want (mk loc t (Tast.Local b.slot)))
| Some b -> | Some b ->
expect ctx loc ~want (local_of loc b) expect ctx loc ~want (local_of loc b)
(* A local of the enclosing function, in a body that was lifted out of it: (* A local of the enclosing function, in a body that was lifted out of it:
@ -8628,12 +8692,31 @@ and check_if_once ctx ~tail ~used ?want loc c t e =
| Some Types.Dyn -> Some Types.Dyn | Some Types.Dyn -> Some Types.Dyn
| _ -> None | _ -> None
in in
let t = branch ctx (fun () -> in_tail (fun () -> check ctx ?want:tw t)) in let arm w = branch ctx (fun () -> in_tail (fun () -> check ctx ?want:w t)) in
(* At an Option want the arm is asked the payload first, and the chain
wraps it; refused there, it is asked the Option itself, which it
then is (decision 140). So a T?? wanted of a T? arm is Some of it,
and the chain's own None stays the outer one. *)
let t, at_payload =
match want with
| Some (Types.Option _) ->
(match trial ctx (fun () -> arm tw) with
| Ok t -> t, true
| Error d ->
(match trial ctx (fun () -> arm want) with
| Ok t -> t, false
| Error _ -> raise (Loc.Error d)))
| _ -> arm tw, false
in
let rest ~used ?want () = let rest ~used ?want () =
branch ctx (fun () -> branch ctx (fun () ->
ctx.tail <- tail; ctx.used <- used; check ctx ?want e) ctx.tail <- tail; ctx.used <- used; check ctx ?want e)
in in
match t.Tast.ty with match t.Tast.ty with
| ty when at_payload && not (Types.equal ty Types.Never) ->
let oty = Option.get want in
let e = rest ~used:true ~want:oty () in
mk loc oty (Tast.If (c, mk loc oty (Tast.Some_ t), e))
| Types.Unit -> | Types.Unit ->
expect ctx loc ~want expect ctx loc ~want
(mk loc Types.Unit (Tast.If (c, t, rest ~used:false ()))) (mk loc Types.Unit (Tast.If (c, t, rest ~used:false ())))
@ -8643,6 +8726,12 @@ and check_if_once ctx ~tail ~used ?want loc c t e =
| Types.Dyn -> | Types.Dyn ->
let e = rest ~used:true ~want:Types.Dyn () in let e = rest ~used:true ~want:Types.Dyn () in
expect ctx loc ~want (mk loc Types.Dyn (Tast.If (c, t, e))) expect ctx loc ~want (mk loc Types.Dyn (Tast.If (c, t, e)))
(* An arm that is already an Option is the chain's value as it is,
and [None] when no test holds — one level flattened (decision
140): an arm's None and no arm running are one answer. *)
| Types.Option _ as o ->
let e = rest ~used:true ~want:o () in
expect ctx loc ~want (mk loc o (Tast.If (c, t, e)))
| ty -> | ty ->
let oty = Types.Option ty in let oty = Types.Option ty in
let e = rest ~used:true ~want:oty () in let e = rest ~used:true ~want:oty () in
@ -8659,14 +8748,33 @@ and check_if_once ctx ~tail ~used ?want loc c t e =
let t = branch ctx (fun () -> in_tail (fun () -> check ctx ?want t)) in let t = branch ctx (fun () -> in_tail (fun () -> check ctx ?want t)) in
let e = branch ctx (fun () -> in_tail (fun () -> check ctx ?want e)) in let e = branch ctx (fun () -> in_tail (fun () -> check ctx ?want e)) in
mk loc t.Tast.ty (Tast.If (c, t, e)) mk loc t.Tast.ty (Tast.If (c, t, e))
| Some e when want = None && adapts t && not (adapts e) | Some e when want = None
&& ((adapts t && not (adapts e || is_none_lit e))
(* [if c then None else 5]: the else arm decides T, and
None meets it at T? below, as the other order does. *)
|| (is_none_lit t && not (is_none_lit e)))
&& not (and_sentinel e) -> && not (and_sentinel e) ->
(* A literal has no type of its own until something asks, so with no (* A literal has no type of its own until something asks, so with no
expectation the other arm decides: [(if c 4000000 n)] over an i64 [n] expectation the other arm decides: [(if c 4000000 n)] over an i64 [n]
is an i64, as [(+ 4000000 n)] is. *) is an i64, as [(+ 4000000 n)] is. *)
let e = branch ctx (fun () -> in_tail (fun () -> check ctx e)) in let e = branch ctx (fun () -> in_tail (fun () -> check ctx e)) in
let twant = if e.Tast.ty = Types.Never then None else Some e.Tast.ty in let twant = if e.Tast.ty = Types.Never then None else Some e.Tast.ty in
let t = branch ctx (fun () -> in_tail (fun () -> check ctx ?want:twant t)) in let then_at w = branch ctx (fun () -> in_tail (fun () -> check ctx ?want:w t)) in
(* [None] or [Some(1)] beside a plain T: the two meet at T?, the other
arm wrapped (decision 138). Tried only once the arm is refused at T,
so an arm that fits T is never an Option. *)
let t, e =
match e.Tast.ty with
| Types.Option _ | Types.Dyn | Types.Unit | Types.Never -> then_at twant, e
| ety ->
(match trial ctx (fun () -> then_at twant) with
| Ok t -> t, e
| Error _ ->
let oty = Types.Option ety in
(match trial ctx (fun () -> then_at (Some oty)) with
| Ok t -> t, expect ctx e.Tast.loc ~want:(Some oty) e
| Error _ -> then_at twant, e))
in
let ty = if e.Tast.ty = Types.Never then t.Tast.ty else e.Tast.ty in let ty = if e.Tast.ty = Types.Never then t.Tast.ty else e.Tast.ty in
mk loc ty (Tast.If (c, t, e)) mk loc ty (Tast.If (c, t, e))
| Some e -> | Some e ->
@ -8730,7 +8838,24 @@ and check_if_once ctx ~tail ~used ?want loc c t e =
| Some j -> Some (j, expect ctx v.Tast.loc ~want:(Some j) v) | Some j -> Some (j, expect ctx v.Tast.loc ~want:(Some j) v)
| None -> None | None -> None
in in
match at_then () with (* The else arm refused at T and fine at T? — [None], [Some(1)] —
and the two meet at T?, the then arm wrapped (decision 138). *)
let at_option () =
match t.Tast.ty with
| Types.Option _ | Types.Dyn | Types.Unit -> None
| ty ->
let oty = Types.Option ty in
(match
trial ctx (fun () ->
branch ctx (fun () -> in_tail (fun () -> check ctx ~want:oty e)))
with
| Ok v when Types.equal v.Tast.ty oty -> Some (oty, v)
| _ -> None)
in
(* Only where the arms met nowhere else, so nothing that met before
meets differently: a dyn else arm still meets at dyn. *)
match
(match at_then () with
| Ok v -> | Ok v ->
(match opened_dyn ~box:(to_dyn ctx) v with (match opened_dyn ~box:(to_dyn ctx) v with
| Some box -> Some (Types.Dyn, box) | Some box -> Some (Types.Dyn, box)
@ -8757,7 +8882,10 @@ and check_if_once ctx ~tail ~used ?want loc c t e =
| Some r -> Some r | Some r -> Some r
| None -> | None ->
Some (t.Tast.ty, expect ctx v.Tast.loc ~want:(Some t.Tast.ty) v)) Some (t.Tast.ty, expect ctx v.Tast.loc ~want:(Some t.Tast.ty) v))
| Error _ -> None) | Error _ -> None))
with
| None -> at_option ()
| j -> j
in in
match joined with match joined with
| Some (j, v) -> | Some (j, v) ->
@ -8846,9 +8974,9 @@ and check_if_once ctx ~tail ~used ?want loc c t e =
branch evaluates to. Kept — a [let]'s value, an argument, a return, or branch evaluates to. Kept — a [let]'s value, an argument, a return, or
anything else with a type wanted of it — it answers (Option T): [Some] of anything else with a type wanted of it — it answers (Option T): [Some] of
the branch when the test held and [None] when it did not. A branch that the branch when the test held and [None] when it did not. A branch that
is already an Option is not flattened: the answer is (Option (Option T)), is already an Option is that Option, flattened one level (decision 140,
Rust's [bool::then], so [None] from the branch and a failed test stay two Kotlin's [?.] rather than Rust's [bool::then]): [None] from the branch and
answers. a failed test are one answer. A (Option (Option T)) branch stays one.
Dyn has no Option. Where a dyn is wanted, or the branch is a dyn, a false Dyn has no Option. Where a dyn is wanted, or the branch is a dyn, a false
test answers nil and a true one the branch's value — one absence, as a test answers nil and a true one the branch's value — one absence, as a
@ -8868,17 +8996,26 @@ and check_when ctx ~used ?want loc c
| Some Types.Dyn -> | Some Types.Dyn ->
let t = branch_at ~want:Types.Dyn () in let t = branch_at ~want:Types.Dyn () in
mk loc Types.Dyn (Tast.If (c, t, nil ())) mk loc Types.Dyn (Tast.If (c, t, nil ()))
| Some (Types.Option inner) -> | Some (Types.Option inner as oty) ->
let t = branch_at ~want:inner () in (* The payload first, wrapped; refused there, the Option itself, which
let oty = Types.Option inner in the branch then is (decision 140). *)
let some = if t.Tast.ty = Types.Never then t else mk loc oty (Tast.Some_ t) in let t =
mk loc oty (Tast.If (c, some, mk loc oty Tast.None_)) match trial ctx (fun () -> branch_at ~want:inner ()) with
| Ok t -> if t.Tast.ty = Types.Never then t else mk loc oty (Tast.Some_ t)
| Error d ->
(match trial ctx (fun () -> branch_at ~want:oty ()) with
| Ok t -> if t.Tast.ty = Types.Never then t else expect ctx loc ~want:(Some oty) t
| Error _ -> raise (Loc.Error d))
in
mk loc oty (Tast.If (c, t, mk loc oty Tast.None_))
| None when not used -> stmt (branch_at ()) | None when not used -> stmt (branch_at ())
| _ -> | _ ->
let t = branch_at () in let t = branch_at () in
if valueless t then stmt t if valueless t then stmt t
else if Types.equal t.Tast.ty Types.Dyn then else if Types.equal t.Tast.ty Types.Dyn then
expect ctx loc ~want (mk loc Types.Dyn (Tast.If (c, t, nil ()))) expect ctx loc ~want (mk loc Types.Dyn (Tast.If (c, t, nil ())))
else if (match t.Tast.ty with Types.Option _ -> true | _ -> false) then
expect ctx loc ~want (mk loc t.Tast.ty (Tast.If (c, t, mk loc t.Tast.ty Tast.None_)))
else else
let oty = Types.Option t.Tast.ty in let oty = Types.Option t.Tast.ty in
expect ctx loc ~want expect ctx loc ~want
@ -9800,11 +9937,38 @@ and check_the ctx ~want loc (t : Ast.texpr) (v : Ast.expr) =
| _ -> ty | _ -> ty
in in
let is_nil = match v.Ast.e with Ast.Var "nil" -> true | _ -> false in let is_nil = match v.Ast.e with Ast.Var "nil" -> true | _ -> false in
(* Quietly: what [v] is on its own terms is not a use of a literal local
inside it — [[x]] read with no want would pin x at its guess, and the
annotation's want below is the use that says what x is. *)
let own_ty () =
let was = !lit_quiet in
lit_quiet := true;
Fun.protect ~finally:(fun () -> lit_quiet := was) (fun () ->
probe ctx loc (fun () -> (check ctx v).Tast.ty))
in
if ty <> Types.Dyn && not is_nil if ty <> Types.Dyn && not is_nil
&& probe ctx loc (fun () -> (check ctx v).Tast.ty) = Some Types.Dyn && own_ty () = Some Types.Dyn
(* A keyword naming one of an enum's members is that member at the (* A keyword naming one of an enum's members is that member at the
enum's type, not a dyn: [let d: Dir = :north]. *) enum's type, not a dyn: [let d: Dir = :north]. *)
&& not (match ty, v.Ast.e with Types.Enum _, Ast.Kw _ -> true | _ -> false) && not (match ty, v.Ast.e with Types.Enum _, Ast.Kw _ -> true | _ -> false)
(* An array literal that is a dyn vector only because its elements do
not agree among themselves — [[1, None]] — is built at the annotation
when every element fits it, as [x: [2 i32?] = [1, None]] (decision
138). Nothing is converted: the literal is built at T. *)
&& not (let rec holds_option = function
| Types.Option _ -> true
| Types.Array (_, t) | Types.Slice (_, t) -> holds_option t
| _ -> false
in
let rec elems_option = function
| Types.Array (_, t) | Types.Slice (_, t) -> holds_option t
| Types.Option t -> elems_option t
| _ -> false
in
match v.Ast.e with
| Ast.Arr _ when elems_option ty ->
probe ctx loc (fun () -> ignore (check ctx ~want:ty v)) <> None
| _ -> false)
then begin then begin
let tn = tyname loc ty in let tn = tyname loc ty in
let numeric = match ty with Types.Int _ | Types.Float _ -> true | _ -> false in let numeric = match ty with Types.Int _ | Types.Float _ -> true | _ -> false in
@ -9960,7 +10124,7 @@ and check_array_gen ctx ~want loc dims f =
~element:(fun idxs -> mk loc elem (Tast.CallPtr (fv, idxs)))) ~element:(fun idxs -> mk loc elem (Tast.CallPtr (fv, idxs))))
and check_match ctx ?(tail = false) ?(used = false) ?(stmt = false) ?(opt = false) and check_match ctx ?(tail = false) ?(used = false) ?(stmt = false) ?(opt = false)
?opt_rest ?want loc scrutinee arms = ?(flat = false) ?opt_rest ?want loc scrutinee arms =
(* [stmt] is an [if let] with no else: a statement, Unit whatever its arm (* [stmt] is an [if let] with no else: a statement, Unit whatever its arm
answers, as a one-armed [if] is when nothing keeps it. [opt] is one that answers, as a one-armed [if] is when nothing keeps it. [opt] is one that
is kept: its arm answers [Some], and the arm with no body [None]. *) is kept: its arm answers [Some], and the arm with no body [None]. *)
@ -9969,7 +10133,11 @@ and check_match ctx ?(tail = false) ?(used = false) ?(stmt = false) ?(opt = fals
let want0 = want in let want0 = want in
let want = let want =
if stmt then None if stmt then None
else if opt then (match want with Some (Types.Option i) -> Some i | _ -> None) else if opt then
(match want with
| Some (Types.Option _) when flat -> want
| Some (Types.Option i) -> Some i
| _ -> None)
else want else want
in in
let s = check ctx scrutinee in let s = check ctx scrutinee in
@ -10337,8 +10505,24 @@ and check_match ctx ?(tail = false) ?(used = false) ?(stmt = false) ?(opt = fals
let idx = List.mapi (fun i r -> (i, r)) resolved in let idx = List.mapi (fun i r -> (i, r)) resolved in
if !want <> None then idx if !want <> None then idx
else else
List.filter (fun (_, r) -> not (literal_arm r)) idx let typed = List.filter (fun (_, r) -> not (literal_arm r)) idx in
@ List.filter (fun (_, r) -> literal_arm r) idx (* A bare [None] that would be checked first has nothing to take its
type from and was always refused; it goes after the others, so it
meets their T at T? (decision 138). Only the leading ones move, so
no order that checked before changes. *)
let none_arm (_, ((a : Ast.arm), _, _)) =
match List.rev a.Ast.body with last :: _ -> is_none_lit last | [] -> false
in
let rec lead acc = function
| x :: rest when none_arm x -> lead (x :: acc) rest
| rest -> (List.rev acc, rest)
in
let nones, typed =
match lead [] typed with
| _ :: _ as nones, rest when List.length nones < List.length idx -> nones, rest
| _ -> [], typed
in
typed @ List.filter (fun (_, r) -> literal_arm r) idx @ nones
in in
let checked = let checked =
map_lr map_lr
@ -10403,6 +10587,19 @@ and check_match ctx ?(tail = false) ?(used = false) ?(stmt = false) ?(opt = fals
(head, (ctx.scope, ctx.ret), w, d); (head, (ctx.scope, ctx.ret), w, d);
Error d) Error d)
in in
(* Where it would be refused: an arm fine at T? — [None],
[Some(1)] — meets a T join at T? (decision 138), and
[arm_join] wraps the arms before it. *)
let refused () =
let fallback () = at !want () in
match w with
| Types.Option _ | Types.Dyn | Types.Unit -> fallback ()
| _ ->
let oty = Types.Option w in
(match trial ctx (at (Some oty)) with
| Ok b when Types.equal b.Tast.ty oty -> b
| _ -> fallback ())
in
match at_join () with match at_join () with
| Ok b -> | Ok b ->
(match opened_dyn ~box:(to_dyn ctx) b with (match opened_dyn ~box:(to_dyn ctx) b with
@ -10418,10 +10615,10 @@ and check_match ctx ?(tail = false) ?(used = false) ?(stmt = false) ?(opt = fals
with with
| Ok b -> b | Ok b -> b
| Error own when String.equal own.Loc.kind not_kept -> | Error own when String.equal own.Loc.kind not_kept ->
at !want () refused ()
| Error own when is_mismatch d && not (is_mismatch own) -> | Error own when is_mismatch d && not (is_mismatch own) ->
at None () at None ()
| Error _ -> at !want ()) | Error _ -> refused ())
else block ctx ?want:!want a.Ast.aloc a.Ast.body else block ctx ?want:!want a.Ast.aloc a.Ast.body
in in
let body = let body =
@ -10539,6 +10736,14 @@ and check_match ctx ?(tail = false) ?(used = false) ?(stmt = false) ?(opt = fals
match r with match r with
| Some e -> e | Some e -> e
| None -> rt a.Ast.aloc Types.Dyn "flan_dyn_nil" []) | None -> rt a.Ast.aloc Types.Dyn "flan_dyn_nil" [])
(* Already an Option: the whole, one level flattened (decision 140). *)
| Some (Types.Option _ as o) when flat || want0 = None ->
opt_result := Some o;
let r = rest ~used:true ~want:o () in
fill Fun.id (fun a ->
match r with
| Some e -> e
| None -> mk a.Ast.aloc o Tast.None_)
| Some t -> | Some t ->
let oty = Types.Option t in let oty = Types.Option t in
opt_result := Some oty; opt_result := Some oty;
@ -10701,9 +10906,22 @@ and check_if_let ctx ~tail ~used ?want loc scrutinee (arm : Ast.arm) els =
ctx.tail <- tail; ctx.used <- used; check ctx ?want e)) ctx.tail <- tail; ctx.used <- used; check ctx ?want e))
els els
in in
(* The arm at the payload first, as [check_when] asks it; refused
there, at the Option itself (decision 140). *)
let go ~flat () =
check_match ctx ~tail ~used:true ~opt:true ~flat ?opt_rest ?want loc scrutinee
[ arm; wild [] ]
in
expect ctx loc ~want expect ctx loc ~want
(check_match ctx ~tail ~used:true ~opt:true ?opt_rest ?want loc scrutinee (match want with
[ arm; wild [] ])) | Some (Types.Option _) ->
(match trial ctx (go ~flat:false) with
| Ok r -> r
| Error d ->
(match trial ctx (go ~flat:true) with
| Ok r -> r
| Error _ -> raise (Loc.Error d)))
| _ -> go ~flat:false ()))
| Some e -> | Some e ->
check_match ctx ~tail ~used ?want loc scrutinee [ arm; wild [ e ] ] check_match ctx ~tail ~used ?want loc scrutinee [ arm; wild [ e ] ]
| None -> | None ->
@ -16732,6 +16950,12 @@ and generic_call ctx ~want loc name vars pats pret args =
call with no type variables in it. *) call with no type variables in it. *)
| _ -> | _ ->
(match subst_ty !subst pat, a.Tast.ty with (match subst_ty !subst pat, a.Tast.ty with
(* A plain value at a [$t?] parameter, which [bind_ty] bound
through the Option: wrapped now that $t is known (decision
138). *)
| Types.Option _ as o, at
when (match at with Types.Option _ | Types.Dyn | Types.Never -> false | _ -> true) ->
expect ctx a.Tast.loc ~want:(Some o) a
| Types.Fn (ps, r), Types.CFn (ps', r') -> | Types.Fn (ps, r), Types.CFn (ps', r') ->
if Types.equal (Types.Fn (ps, r)) (Types.Fn (ps', r')) then if Types.equal (Types.Fn (ps, r)) (Types.Fn (ps', r')) then
mk a.Tast.loc (Types.Fn (ps, r)) mk a.Tast.loc (Types.Fn (ps, r))
@ -17696,7 +17920,9 @@ let builtins : (string * string * string) list =
(* Option *) (* Option *)
("Some", "Some [T] (Option T)", ("Some", "Some [T] (Option T)",
"Wraps a value as a present Option. None is the other half, and is \ "Wraps a value as a present Option. None is the other half, and is \
written as a name rather than as a call."); written as a name rather than as a call. Where an (Option T) is \
expected a T is wrapped with no Some written, one level at a time; an \
Option is never unwrapped that way.");
(* the host primitives *) (* the host primitives *)
("bytes", "bytes [str Allocator?] [u8]", ("bytes", "bytes [str Allocator?] [u8]",

View File

@ -244,6 +244,16 @@ Each item: the proposal, then the reason in one line.
holds — `a?.f(x)`, `a?[i]`, `a?.b.c`. A result that is already an Option holds — `a?.f(x)`, `a?[i]`, `a?.b.c`. A result that is already an Option
is not wrapped again, so `a?.b?.c` is one Option. A rest with no value is not wrapped again, so `a?.b?.c` is one Option. A rest with no value
makes the whole a statement. `~o1` is a fresh name no reader produces. makes the whole a statement. `~o1` is a fresh name no reader produces.
- A `T` where a `T?` is wanted is `Some` of it (decision 138): an
assignment, a `let` with a type, an argument, a return, a struct field, an
array or `Vec` element, and an `if` or `match` arm beside an Option arm.
A literal is built at `T` first, so `s = -1` over an `i64?` is `Some(-1)`
at i64. One level at a time: a `T` into a `T??` is `Some(Some(t))`, a `T?`
into a `T??` is `Some` of it. A `$t` meeting `$u?` binds `$u` to `T`.
Never inside a container (`Vec(i32)` is not a `Vec(i32?)`), and never the
other way: a `T?` where a `T` is wanted still needs `!`, `??`, `x?` or
`as`. A kept chain whose arms are a `T` and a `T?` is a `T?` (decision
140, under `when c`). **Built.**
- **Casts and type-taking builtins are calls:** `i32(x)`, `vec-new(u8)`, - **Casts and type-taking builtins are calls:** `i32(x)`, `vec-new(u8)`,
`max-value(u8)`, `the([3 f32], [1 2 3.5])`. A pointer cast is the type `max-value(u8)`, `the([3 f32], [1 2 3.5])`. A pointer cast is the type
called: `Ptr(Color)(p)` reads `((Ptr Color) p)`. **Built.** called: `Ptr(Color)(p)` reads `((Ptr Color) p)`. **Built.**
@ -307,7 +317,13 @@ Each item: the proposal, then the reason in one line.
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. An `if`/`elif` chain 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.** with no `else` is the same when kept: `None` when no test holds. When `a`
is already an Option it is not wrapped again (decision 140): `when c then
o` over an `i32?` is an `i32?`, `None` when `c` fails or `o` is `None`, and
a chain mixing `T` and `T?` arms is a `T?`. One level only: an arm that is
a `T??` gives a `T??`. Where an Option of the arm's type is wanted, as a
`T??` over a `T?` arm, the arm is `Some` of its value and a failed test is
the outer `None`. `if let` with no `else` follows the same rule. **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. `elif let P = v` is a further `if-let` nested in that else.

148
test/programs/autowrap.fln Normal file
View File

@ -0,0 +1,148 @@
;; A T where a T? is wanted is Some of it (decision 138): each position,
;; a literal built at the payload's type, nested Options, generics, and the
;; arms of an if, a match and a kept chain.
struct P
a: i64?
b: i32?
fn show(o: i32?) -> i32 = o ?? -9
fn back(b: bool, x: i32) -> i32?
if b
return x
None
fn pick(b: bool, x: i32) -> i32?
if b then x else None
fn pick2(b: bool, x: i32) -> i32?
if b then None else x
fn two(o: Option(i32?)) -> str
match o
Some(i) -> if i? then "some some" else "some none"
None -> "none"
fn first(o: $u?) -> $u = o!
fn wrap(x: $t) -> $t? = x
;; A generic body's own $t handed to a $u? parameter.
fn through(x: $t) -> $t = first(x)
;; A None arm first takes its type from the arms after it.
fn arms(k: i32, x: i32) -> i32?
match k
5 -> None
4 -> x
_ -> 0
fn big(x: i64?) -> i64 = x ?? 0
;; A T?? is wanted, so each arm is Some of its value and the chain's None is
;; the outer one.
fn chain(a: bool, b: bool, opt: i32?) -> Option(i32?)
if a
1
elif b
opt
;; Nothing wanted: a T arm beside a T? arm makes the chain a T?, flattened
;; one level (decision 140).
fn flat(a: bool, b: bool, opt: i32?) -> i32?
let r =
if a
1
elif b
opt
r
fn main()
;; assignment, and a literal at the payload's width
let s: i64? = None
s = -1
println(s ?? 0)
;; a let with an annotation, from a literal, a name and arithmetic
let x: i32 = 4
let a: i32? = x + 1
let w: i64? = x
let f: f64? = 2
println(a ?? 0, w ?? 0, f ?? 0.0)
;; an argument and a return value
println(show(7), back(true, 8) ?? -1, back(false, 8) ?? -1)
;; a struct field
let p = P{.a 3 .b x}
println(p.a ?? 0, p.b ?? 0)
p.b = 7
println(p.b ?? 0)
;; an array and a Vec element
let xs: [3 i32?] = [1, None, x]
println(xs[0] ?? 0, xs[1] ?? 0, xs[2] ?? 0)
xs[1] = 5
println(xs[1] ?? 0)
let v: Vec(i32?) = vec-new(i32?)
push(v, 6)
push(v, None)
println(length(v), v[0] ?? 0, v[1] ?? 0)
;; the arms of an if and a match
println(pick(true, 2) ?? -1, pick(false, 2) ?? -1, pick2(true, 2) ?? -1, pick2(false, 2) ?? -1)
let m = match x
4 -> x
_ -> None
println(m ?? 0)
println(arms(5, 3) ?? -1, arms(4, 3) ?? -1, arms(1, 3) ?? -1)
;; nested: a T into a T?? is Some(Some(t)), a T? is Some of it
let nn: Option(i32?) = 5
let none: i32? = None
let nn2: Option(i32?) = none
println(two(nn), two(nn2))
;; kept chains over T and T? arms
println(two(chain(true, false, none)), two(chain(false, true, none)), two(chain(false, false, none)))
println(flat(true, false, none) ?? -1, flat(false, true, Some(6)) ?? -1, flat(false, true, none) ?? -1, flat(false, false, none) ?? -1)
let kk =
if x > 9
none
elif x > 1
1
println(kk ?? -1)
;; generics
println(first(9), first(Some(8)), wrap(3) ?? 0, through(5))
;; a literal local takes the payload's type from an Option use, as it
;; takes T from a T use: each pair prints the same
let la = 4
let wa: i64? = la
let lb = 4
let wb: i64 = lb
println(la * 1000000000, lb * 1000000000, wa ?? 0, wb)
let lc = 4
println(big(lc), lc * 1000000000)
let lf = 7
let wf: f64? = lf
let lg = 7
let wg: f64 = lg
println(lf / 2, lg / 2, wf ?? 0.0, wg)
let lu = 200
let wu: u8? = lu
let lv = 200
let wv: u8 = lv
println(wu ?? 0, wv)
;; and from a typed array's element, with or without an Option
let la2 = 3
let xa: [1 i64] = [la2]
let lb2 = 3
let xb: [2 i64?] = [lb2, None]
println(la2 * 1000000000, lb2 * 1000000000, xa[0], xb[0] ?? 0)
;; None first in an if, as in a match
let e1 = if x > 1 then None else 5
let e2 = if x > 1 then 5 else None
println(e1 ?? -1, e2 ?? -1)
;; an Option around an array of Options
let ao: [2 i32?]? = [1, None]
let ai = ao!
println(ai[0] ?? 0, ai[1] ?? 0)
;; a narrowed name still takes a payload value
let o: i32? = Some(1)
if o?
o = 10
println(o)

View File

@ -21,6 +21,28 @@ fn early(a: Option(i32)) -> Option(i32)
elif true elif true
3 3
;; An arm that is already an Option is the whole, flattened (decision 140).
fn flat(a: Option(i32), o: Option(i32)) -> Option(i32)
if let Some(x) = a then o
;; An Option of it wanted: the arm is Some of its value, and no match is the
;; outer None.
fn nest(a: Option(i32), o: Option(i32)) -> Option(Option(i32))
if let Some(x) = a then o
fn level(oo: Option(Option(i32)))
match oo
Some(o) -> if o? then println(o) else println("some none")
None -> println("none")
fn flat_chain(a: Option(i32), k: i32)
let r =
if let Some(x) = a
x
elif k > 0
None
show(r)
fn dyn_only(a: Option(i32)) -> dyn fn dyn_only(a: Option(i32)) -> dyn
if let Some(x) = a then x if let Some(x) = a then x
@ -40,6 +62,15 @@ fn main()
show(lead(1, None)) show(lead(1, None))
show(early(None)) show(early(None))
show(early(Some(1))) show(early(Some(1)))
show(flat(Some(1), Some(4)))
show(flat(Some(1), None))
show(flat(None, Some(4)))
level(nest(Some(1), Some(4)))
level(nest(Some(1), None))
level(nest(None, Some(4)))
flat_chain(Some(3), 0)
flat_chain(None, 1)
flat_chain(None, 0)
println(dyn_only(Some(5))) println(dyn_only(Some(5)))
println(dyn_only(None)) println(dyn_only(None))
;; As a statement it is unchanged. ;; As a statement it is unchanged.

View File

@ -1,7 +1,8 @@
;;;; A when whose value is kept answers an Option: Some of its body when the ;;;; A when whose value is kept answers an Option: Some of its body when the
;;;; test holds, None when it does not. As a statement it answers nothing. ;;;; test holds, None when it does not. As a statement it answers nothing.
;;;; Where a dyn is wanted it answers the body or nil, since dyn has no ;;;; Where a dyn is wanted it answers the body or nil, since dyn has no
;;;; Option. A body that is already an Option is not flattened. ;;;; Option. A body that is already an Option is that Option, flattened one
;;;; level (decision 140), unless an Option of it is what is wanted.
(defn show [o (Option i32)] () (defn show [o (Option i32)] ()
(match o (Some v) (println v) None (println "none"))) (match o (Some v) (println v) None (println "none")))
@ -9,10 +10,13 @@
;; Returned: the return type is the want. ;; Returned: the return type is the want.
(defn half [n i32] (Option i32) (when (= 0 (% n 2)) (/ n 2))) (defn half [n i32] (Option i32) (when (= 0 (% n 2)) (/ n 2)))
;; Nested, as Rust's bool::then: None from the body stays apart from a ;; An (Option (Option i32)) is wanted, so the body is Some of it and the
;; failed test. ;; failed test is the outer None.
(defn wrap [c bool o (Option i32)] (Option (Option i32)) (when c o)) (defn wrap [c bool o (Option i32)] (Option (Option i32)) (when c o))
;; Flattened: None from the body and a failed test are one answer.
(defn flat [c bool o (Option i32)] (Option i32) (when c o))
(defn level [oo (Option (Option i32))] () (defn level [oo (Option (Option i32))] ()
(match oo (match oo
(Some o) (match o (Some v) (println v) None (println "some none")) (Some o) (match o (Some v) (println v) None (println "some none"))
@ -45,6 +49,12 @@
(level (wrap true (Some 1))) (level (wrap true (Some 1)))
(level (wrap true None)) (level (wrap true None))
(level (wrap false (Some 1))) (level (wrap false (Some 1)))
(show (flat true (Some 4)))
(show (flat true None))
(show (flat false (Some 4)))
(let [o (the (Option i32) (Some 8))
f (when true o)]
(show f))
(println (dyn-when true)) (println (dyn-when true))
(println (dyn-when nil)) (println (dyn-when nil))
(show (early None)) (show (early None))

View File

@ -2226,8 +2226,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\n3\nnone\n6\nnone\n20\nnone\n2\n\ "5\nnone\n42\nnone\n9\n1\nsome none\nnone\n4\nnone\nnone\n8\n5\nnil\n3\nnone\n6\nnone\n\
a\nnil\ncond stmt\nran\nend\n" 20\nnone\n2\na\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;
@ -2246,7 +2246,8 @@ let () =
"5\n100\n0\n9\n1\n20\n100\n0\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
let if_let_kept_out = let if_let_kept_out =
"1\n4\nnone\n6\nnone\n9\n2\nnone\n3\nnone\n5\nnil\n7\n" "1\n4\nnone\n6\nnone\n9\n2\nnone\n3\nnone\n4\nnone\nnone\n4\nsome none\nnone\n\
3\nnone\nnone\n5\nnil\n7\n"
in in
outputs "a kept if let chain" "programs/if-let-kept.fln" if_let_kept_out; outputs "a kept if let chain" "programs/if-let-kept.fln" if_let_kept_out;
outputs ~opt:"-O0" "a kept if let chain, -O0" "programs/if-let-kept.fln" if_let_kept_out; outputs ~opt:"-O0" "a kept if let chain, -O0" "programs/if-let-kept.fln" if_let_kept_out;
@ -2274,6 +2275,11 @@ let () =
outputs ~opt:"-O0" (path ^ ", -O0") ("programs/" ^ path) want; outputs ~opt:"-O0" (path ^ ", -O0") ("programs/" ^ path) want;
outputs ~x86:true (path ^ ", --x86") ("programs/" ^ path) want) outputs ~x86:true (path ^ ", --x86") ("programs/" ^ path) want)
[ ("optionals.fln", optionals_out); ("optionals-dyn.fln", optionals_dyn_out); [ ("optionals.fln", optionals_out); ("optionals-dyn.fln", optionals_dyn_out);
(* A T where a T? is wanted is Some of it (decision 138). *)
("autowrap.fln",
"-1\n5 4 2\n7 8 -1\n3 4\n7\n1 0 4\n5\n2 6 0\n2 -1 -1 2\n4\n-1 3 0\n\
some some some none\nsome some some none none\n1 6 -1 -1\n1\n9 8 3 5\n\
4000000000 4000000000 4 4\n4 4000000000\n3.5 3.5 7 7\n200 200\n3000000000 3000000000 3 3\n-1 5\n1 0\n10\n");
(* x? tests and narrows, e? as g names what it found (decision 133). *) (* x? tests and narrows, e? as g names what it found (decision 133). *)
("presence.fln", "true false true\n6\n-1\n3\n101 209 0\n11\n42\n2\nabsent\n6\nfalse true\n3\n6\n15\n"); ("presence-dyn.fln", "true false\n103 209 0\nno pet\nann\n3 2\n") ]; ("presence.fln", "true false true\n6\n-1\n3\n101 209 0\n11\n42\n2\nabsent\n6\nfalse true\n3\n6\n15\n"); ("presence-dyn.fln", "true false\n103 209 0\nno pet\nann\n3 2\n") ];
(* The pipe (decision 137): chains, multi-line, qualified, dyn, and the (* The pipe (decision 137): chains, multi-line, qualified, dyn, and the

View File

@ -8482,6 +8482,26 @@ let () =
parse_rejects "_ in a defgeneric's return slot" parse_rejects "_ in a defgeneric's return slot"
~needle:"defgeneric's methods each have their own" ~needle:"defgeneric's methods each have their own"
"(defgeneric area [s] _)"; "(defgeneric area [s] _)";
(* Decision 138: a T is wrapped where a T? is wanted, and never the other
way. The program half is programs/autowrap.fln. *)
accepts "a T is Some of it at a T?" "(defn f [] (Option i64) -1)\n(defn main [] ())";
rejects_check "a T? is not unwrapped at a T" ~needle:"expected i32, found (Option i32)"
"(defn f [o (Option i32)] i32 o)\n(defn main [] ())";
rejects_check "a T? argument is not unwrapped" ~needle:"expected i32, found (Option i32)"
"(defn g [x i32] i32 x)\n(defn f [o (Option i32)] i32 (g o))\n(defn main [] ())";
rejects_check "a payload that does not fit is refused at the Option"
~needle:"expected (Option i32), found str"
"(defn f [] (Option i32) \"no\")\n(defn main [] ())";
rejects_check "a literal that does not fit is refused at the Option"
~needle:"expected (Option str), found the integer literal 5"
"(defn f [] (Option str) 5)\n(defn main [] ())";
rejects_check "a narrowing is not wrapped" ~needle:"expected (Option i32), found i64"
"(defn f [x i64] (Option i32) x)\n(defn main [] ())";
rejects_check "no wrap inside a container" ~needle:"expected (Vec (Option i32)), found (Vec i32)"
"(defn f [v (Vec i32)] (Vec (Option i32)) v)\n(defn main [] ())";
rejects_check "a narrowed name still refuses an Option"
~needle:"it cannot be given an Option here"
"(defn main [] () (let [o (the (Option i32) (Some 1))] (when (? o) (set o (Some 2)))))";
(* A plain name binds what an Option or a dyn holds; over anything else (* A plain name binds what an Option or a dyn holds; over anything else
it cannot fail, and is refused toward let. The program half is it cannot fail, and is refused toward let. The program half is
programs/if-let.flan and programs/optionals.fln. *) programs/if-let.flan and programs/optionals.fln. *)

View File

@ -1528,7 +1528,7 @@ let () =
refused "addr-taken.fln" refused "addr-taken.fln"
"fn clear(p: Ptr(i32?))\n deref(p) = None\n\nfn main()\n let x: i32? = Some(1)\n\ "fn clear(p: Ptr(i32?))\n deref(p) = None\n\nfn main()\n let x: i32? = Some(1)\n\
\ let p = addr(x)\n if x?\n clear(p)\n println(x + 1)\n" \ let p = addr(x)\n if x?\n clear(p)\n println(x + 1)\n"
[ "expected Option(i32)" ]; [ "+ takes numbers, found Option(i32)" ];
refused "capital-local.fln" "fn main()\n let X: i32? = Some(1)\n println(X?)\n" refused "capital-local.fln" "fn main()\n let X: i32? = Some(1)\n println(X?)\n"
[ "To test the local X, give it a lowercase name, as in x?" ]; [ "To test the local X, give it a lowercase name, as in x?" ];
checks "addr-taken-test.fln" checks "addr-taken-test.fln"