A refused if is answered from memory for the same condition, scope and expectation, so a refused or/and chain is checked in linear time
This commit is contained in:
parent
25e303caf4
commit
8fe2a666a1
54
lib/check.ml
54
lib/check.ml
@ -4147,9 +4147,31 @@ let tracked_call loc env name (tr : Shim.track) ret (args : Tast.expr list) =
|
|||||||
checked in and its diagnostic, for as long as the outermost call is on the
|
checked in and its diagnostic, for as long as the outermost call is on the
|
||||||
stack — see [check_truthy]. Physical identity on both, since a generic's
|
stack — see [check_truthy]. Physical identity on both, since a generic's
|
||||||
body is the same syntax checked again at another type. *)
|
body is the same syntax checked again at another type. *)
|
||||||
let truthy_failed : (Ast.expr * ctx * Loc.diag) list ref = ref []
|
let truthy_failed :
|
||||||
|
(Ast.expr * (string * binding) list * Types.t * Loc.diag) list ref = ref []
|
||||||
|
|
||||||
|
(* Whether two scopes bind the same names at the same types, which is what a
|
||||||
|
refusal under them can depend on — the slots are fresh on every pass. A
|
||||||
|
memo keyed on less would replay a refusal after a retry changed a type. *)
|
||||||
|
let same_scope (a : (string * binding) list) (b : (string * binding) list) =
|
||||||
|
a == b
|
||||||
|
|| List.equal
|
||||||
|
(fun (n, (x : binding)) (m, (y : binding)) ->
|
||||||
|
String.equal n m && Types.equal x.bty y.bty)
|
||||||
|
a b
|
||||||
let truthy_depth = ref 0
|
let truthy_depth = ref 0
|
||||||
|
|
||||||
|
(* The same for an [if], keyed on its condition and the expectation:
|
||||||
|
an [if] whose else arm is tried on its own terms first (see [check_if])
|
||||||
|
would otherwise be re-checked, refused, by the trial of every [if] above
|
||||||
|
it — the square of a refused or/and chain's length. *)
|
||||||
|
let if_failed :
|
||||||
|
(Loc.t,
|
||||||
|
Ast.expr * ((string * binding) list * Types.t) * Types.t option * Loc.diag)
|
||||||
|
Hashtbl.t =
|
||||||
|
Hashtbl.create 16
|
||||||
|
let if_depth = ref 0
|
||||||
|
|
||||||
|
|
||||||
(* A Vec or a Map parameter is a copy of the caller's header — Odin's rule —
|
(* A Vec or a Map parameter is a copy of the caller's header — Odin's rule —
|
||||||
so growing it reallocates a block only this function's copy points at, and
|
so growing it reallocates a block only this function's copy points at, and
|
||||||
@ -6000,10 +6022,13 @@ and check_recur ctx ~tail loc args =
|
|||||||
accident. *)
|
accident. *)
|
||||||
and check_truthy ctx c =
|
and check_truthy ctx c =
|
||||||
match
|
match
|
||||||
List.find_opt (fun (n, cx, _) -> n == c && cx == ctx) !truthy_failed
|
List.find_opt
|
||||||
|
(fun (n, sc, r, _) -> n == c && r == ctx.ret && same_scope sc ctx.scope)
|
||||||
|
!truthy_failed
|
||||||
with
|
with
|
||||||
| Some (_, _, d) -> raise (Loc.Error d)
|
| Some (_, _, _, d) -> raise (Loc.Error d)
|
||||||
| None ->
|
| None ->
|
||||||
|
let scope = ctx.scope in
|
||||||
incr truthy_depth;
|
incr truthy_depth;
|
||||||
Fun.protect
|
Fun.protect
|
||||||
~finally:(fun () ->
|
~finally:(fun () ->
|
||||||
@ -6012,7 +6037,7 @@ and check_truthy ctx c =
|
|||||||
(fun () ->
|
(fun () ->
|
||||||
try check_truthy_once ctx c
|
try check_truthy_once ctx c
|
||||||
with Loc.Error d as ex ->
|
with Loc.Error d as ex ->
|
||||||
truthy_failed := (c, ctx, d) :: !truthy_failed;
|
truthy_failed := (c, scope, ctx.ret, d) :: !truthy_failed;
|
||||||
raise ex)
|
raise ex)
|
||||||
|
|
||||||
and check_truthy_once ctx c =
|
and check_truthy_once ctx c =
|
||||||
@ -6064,6 +6089,27 @@ and check_truthy_once ctx c =
|
|||||||
| exception Loc.Error _ -> check ctx ~want:Types.Bool c
|
| exception Loc.Error _ -> check ctx ~want:Types.Bool c
|
||||||
|
|
||||||
and check_if ctx ?(tail = false) ?want loc c t e =
|
and check_if ctx ?(tail = false) ?want loc c t e =
|
||||||
|
match
|
||||||
|
List.find_opt
|
||||||
|
(fun (n, (sc, r), w, _) ->
|
||||||
|
n == c && r == ctx.ret && w = want && same_scope sc ctx.scope)
|
||||||
|
(Hashtbl.find_all if_failed c.Ast.loc)
|
||||||
|
with
|
||||||
|
| Some (_, _, _, d) -> raise (Loc.Error d)
|
||||||
|
| None ->
|
||||||
|
let scope = ctx.scope in
|
||||||
|
incr if_depth;
|
||||||
|
Fun.protect
|
||||||
|
~finally:(fun () ->
|
||||||
|
decr if_depth;
|
||||||
|
if !if_depth = 0 then Hashtbl.reset if_failed)
|
||||||
|
(fun () ->
|
||||||
|
try check_if_once ctx ~tail ?want loc c t e
|
||||||
|
with Loc.Error d as ex ->
|
||||||
|
Hashtbl.add if_failed c.Ast.loc (c, (scope, ctx.ret), want, d);
|
||||||
|
raise ex)
|
||||||
|
|
||||||
|
and check_if_once ctx ~tail ?want loc c t e =
|
||||||
let c = check_truthy ctx c in
|
let c = check_truthy ctx c in
|
||||||
(* Both arms are the tail, and a one-armed [if] counts: [(when c (recur ...))]
|
(* Both arms are the tail, and a one-armed [if] counts: [(when c (recur ...))]
|
||||||
is how nearly every loop is written, and the branch is still the last
|
is how nearly every loop is written, and the branch is still the last
|
||||||
|
|||||||
@ -5691,14 +5691,33 @@ let () =
|
|||||||
let rec nest k e = if k = 0 then e else nest (k - 1) ("(not " ^ e ^ ")") in
|
let rec nest k e = if k = 0 then e else nest (k - 1) ("(not " ^ e ^ ")") in
|
||||||
"(defn g [x i32] bool " ^ nest 200 "x" ^ ")"
|
"(defn g [x i32] bool " ^ nest 200 "x" ^ ")"
|
||||||
in
|
in
|
||||||
match Watchdog.within 5 (fun () -> checked deep) with
|
let t0 = Unix.gettimeofday () in
|
||||||
|
match checked deep with
|
||||||
| _ -> check "a deep not nest over an i32 is refused" false
|
| _ -> check "a deep not nest over an i32 is refused" false
|
||||||
| exception Watchdog.Timeout ->
|
|
||||||
check "a deep not nest over an i32 fails fast" false
|
|
||||||
| exception Loc.Error { Loc.dmsg; _ } ->
|
| exception Loc.Error { Loc.dmsg; _ } ->
|
||||||
|
check "a deep not nest over an i32 fails fast"
|
||||||
|
(Unix.gettimeofday () -. t0 < 3.0);
|
||||||
check "a deep not nest keeps the one-level message"
|
check "a deep not nest keeps the one-level message"
|
||||||
(dmsg = "a condition is a bool or a dyn, and this is i32 — test it, as \
|
(dmsg = "a condition is a bool or a dyn, and this is i32 — test it, as \
|
||||||
(!= x 0)"));
|
(!= x 0)"));
|
||||||
|
(* A long or chain refused at its last operand, with nothing expected of it.
|
||||||
|
Each if tries its else arm on its own terms before checking it at bool,
|
||||||
|
and a refused if is answered from memory, so a thousand operands fail
|
||||||
|
at once rather than in the square of that. *)
|
||||||
|
(let deep =
|
||||||
|
"(defn g [x i32] bool (let [b (or "
|
||||||
|
^ String.concat " " (List.init 1000 (Printf.sprintf "(= x %d)"))
|
||||||
|
^ " 5)] b))"
|
||||||
|
in
|
||||||
|
(* Timed rather than under [Watchdog.within]: a catch-all inside the
|
||||||
|
checker can swallow the alarm's exception. *)
|
||||||
|
let t0 = Unix.gettimeofday () in
|
||||||
|
match checked deep with
|
||||||
|
| _ -> check "a refused or chain is refused" false
|
||||||
|
| exception Loc.Error { Loc.dmsg; _ } ->
|
||||||
|
check "a refused or chain fails fast" (Unix.gettimeofday () -. t0 < 3.0);
|
||||||
|
check "a refused or chain keeps the one-operand message"
|
||||||
|
(dmsg = "expected bool, found the integer literal 5"));
|
||||||
(* A literal still names itself: that message knows something the rule does
|
(* A literal still names itself: that message knows something the rule does
|
||||||
not, so the re-check's answer is kept wherever it is more specific. *)
|
not, so the re-check's answer is kept wherever it is more specific. *)
|
||||||
rejects_check "a literal condition keeps its own message"
|
rejects_check "a literal condition keeps its own message"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user