package coq
Formal proof management system
Install
Dune Dependency
Authors
Maintainers
Sources
coq-8.14.0.tar.gz
sha256=b1501d686c21836302191ae30f610cca57fb309214c126518ca009363ad2cd3c
doc/src/coq-core.proofs/proof.ml.html
Source file proof.ml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622
(************************************************************************) (* * The Coq Proof Assistant / The Coq Development Team *) (* v * Copyright INRIA, CNRS and contributors *) (* <O___,, * (see version control and CREDITS file for authors & dates) *) (* \VV/ **************************************************************) (* // * This file is distributed under the terms of the *) (* * GNU Lesser General Public License Version 2.1 *) (* * (see LICENSE file for the text of the license) *) (************************************************************************) (* Module defining the last essential tiles of interactive proofs. A proof deals with the focusing commands (including the braces and bullets), the shelf (see the [shelve] tactic) and given up goal (see the [give_up] tactic). A proof is made of the following: - Proofview: a proof is primarily the data of the current view. That which is shown to the user (as a remainder, a proofview is mainly the logical state of the proof, together with the currently focused goals). - Focus: a proof has a focus stack: the top of the stack contains the context in which to unfocus the current view to a view focused with the rest of the stack. In addition, this contains, for each of the focus context, a "focus kind" and a "focus condition" (in practice, and for modularity, the focus kind is actually stored inside the condition). To unfocus, one needs to know the focus kind, and the condition (for instance "no condition" or the proof under focused must be complete) must be met. - Given up goals: as long as there is a given up goal, the proof is not completed. Given up goals cannot be retrieved, the user must go back where the tactic [give_up] was run and solve the goal there. *) open Util type _focus_kind = int type 'a focus_kind = _focus_kind type focus_info = Obj.t type reason = NotThisWay | AlreadyNoFocus type unfocusable = | Cannot of reason | Loose | Strict type _focus_condition = | CondNo of bool * _focus_kind | CondDone of bool * _focus_kind | CondEndStack of _focus_kind (* loose_end is false here *) type 'a focus_condition = _focus_condition let next_kind = ref 0 let new_focus_kind () = let r = !next_kind in incr next_kind; r (* To be authorized to unfocus one must meet the condition prescribed by the action which focused.*) (* spiwack: we could consider having a list of authorized focus_kind instead of just one, if anyone needs it *) exception CannotUnfocusThisWay (* Cannot focus on non-existing subgoals *) exception NoSuchGoals of int * int exception NoSuchGoal of Names.Id.t option exception FullyUnfocused let _ = CErrors.register_handler begin function | CannotUnfocusThisWay -> Some (Pp.str "This proof is focused, but cannot be unfocused this way") | NoSuchGoals (i,j) when Int.equal i j -> Some Pp.(str "[Focus] No such goal (" ++ int i ++ str").") | NoSuchGoals (i,j) -> Some Pp.(str "[Focus] Not every goal in range ["++ int i ++ str","++int j++str"] exist.") | NoSuchGoal (Some id) -> Some Pp.(str "[Focus] No such goal: " ++ str (Names.Id.to_string id) ++ str ".") | NoSuchGoal None -> Some Pp.(str "[Focus] No such goal.") | FullyUnfocused -> Some (Pp.str "The proof is not focused") | _ -> None end let check_cond_kind c k = let kind_of_cond = function | CondNo (_,k) | CondDone(_,k) | CondEndStack k -> k in Int.equal (kind_of_cond c) k let test_cond c k1 pw = match c with | CondNo(_, k) when Int.equal k k1 -> Strict | CondNo(true, _) -> Loose | CondNo(false, _) -> Cannot NotThisWay | CondDone(_, k) when Int.equal k k1 && Proofview.finished pw -> Strict | CondDone(true, _) -> Loose | CondDone(false, _) -> Cannot NotThisWay | CondEndStack k when Int.equal k k1 -> Strict | CondEndStack _ -> Cannot AlreadyNoFocus let no_cond ?(loose_end=false) k = CondNo (loose_end, k) let done_cond ?(loose_end=false) k = CondDone (loose_end,k) (* Subpart of the type of proofs. It contains the parts of the proof which are under control of the undo mechanism *) type t = { proofview: Proofview.proofview (** Current focused proofview *) ; entry : Proofview.entry (** Entry for the proofview *) ; focus_stack: (_focus_condition*focus_info*Proofview.focus_context) list (** History of the focusings, provides information on how to unfocus the proof and the extra information stored while focusing. The list is empty when the proof is fully unfocused. *) ; name : Names.Id.t (** the name of the theorem whose proof is being constructed *) ; poly : bool (** polymorphism *) ; typing_flags : Declarations.typing_flags option } (*** General proof functions ***) let proof p = let (goals,sigma) = Proofview.proofview p.proofview in (* spiwack: beware, the bottom of the stack is used by [Proof] internally, and should not be exposed. *) let rec map_minus_one f = function | [] -> assert false | [_] -> [] | a::l -> f a :: (map_minus_one f l) in let stack = map_minus_one (fun (_,_,c) -> Proofview.focus_context c) p.focus_stack in (goals,stack,sigma) let rec unroll_focus pv = function | (_,_,ctx)::stk -> unroll_focus (Proofview.unfocus ctx pv) stk | [] -> pv (* spiwack: a proof is considered completed even if its still focused, if the focus doesn't hide any goal. Unfocusing is handled in {!return}. *) let is_done p = Proofview.finished p.proofview && Proofview.finished (unroll_focus p.proofview p.focus_stack) (* spiwack: for compatibility with <= 8.2 proof engine *) let has_unresolved_evar p = Proofview.V82.has_unresolved_evar p.proofview let has_shelved_goals p = let (_goals,sigma) = Proofview.proofview p.proofview in Evd.has_shelved sigma let has_given_up_goals p = let (_goals,sigma) = Proofview.proofview p.proofview in Evd.has_given_up sigma let is_complete p = is_done p && not (has_unresolved_evar p) && not (has_shelved_goals p) && not (has_given_up_goals p) (* Returns the list of partial proofs to initial goals *) let partial_proof p = Proofview.partial_proof p.entry p.proofview (*** The following functions implement the basic internal mechanisms of proofs, they are not meant to be exported in the .mli ***) (* An auxiliary function to push a {!focus_context} on the focus stack. *) let push_focus cond inf context pr = { pr with focus_stack = (cond,inf,context)::pr.focus_stack } (* An auxiliary function to read the kind of the next focusing step *) let cond_of_focus pr = match pr.focus_stack with | (cond,_,_)::_ -> cond | _ -> raise FullyUnfocused (* An auxiliary function to pop and read the last {!Proofview.focus_context} on the focus stack. *) let pop_focus pr = match pr.focus_stack with | focus::other_focuses -> { pr with focus_stack = other_focuses }, focus | _ -> raise FullyUnfocused (* This function focuses the proof [pr] between indices [i] and [j] *) let _focus cond inf i j pr = let focused, context = Proofview.focus i j pr.proofview in let pr = push_focus cond inf context pr in { pr with proofview = focused } (* This function unfocuses the proof [pr], it raises [FullyUnfocused], if the proof is already fully unfocused. This function does not care about the condition of the current focus. *) let _unfocus pr = let pr, (_,_,fc) = pop_focus pr in { pr with proofview = Proofview.unfocus fc pr.proofview } (* Focus command (focuses on the [i]th subgoal) *) (* spiwack: there could also, easily be a focus-on-a-range tactic, is there a need for it? *) let focus cond inf i pr = try _focus cond (Obj.repr inf) i i pr with CList.IndexOutOfRange -> raise (NoSuchGoals (i,i)) (* Focus on the goal named id *) let focus_id cond inf id pr = let (focused_goals, evar_map) = Proofview.proofview pr.proofview in begin match try Some (Evd.evar_key id evar_map) with Not_found -> None with | Some ev -> begin match CList.safe_index Evar.equal ev focused_goals with | Some i -> (* goal is already under focus *) _focus cond (Obj.repr inf) i i pr | None -> if CList.mem_f Evar.equal ev (Evd.shelf evar_map) then (* goal is on the shelf, put it in focus *) let proofview = Proofview.unshelve [ev] pr.proofview in let pr = { pr with proofview } in let (focused_goals, _) = Proofview.proofview pr.proofview in let i = (* Now we know that this will succeed *) try CList.index Evar.equal ev focused_goals with Not_found -> assert false in _focus cond (Obj.repr inf) i i pr else raise CannotUnfocusThisWay end | None -> raise (NoSuchGoal (Some id)) end let rec unfocus kind pr () = let cond = cond_of_focus pr in match test_cond cond kind pr.proofview with | Cannot NotThisWay -> raise CannotUnfocusThisWay | Cannot AlreadyNoFocus -> raise FullyUnfocused | Strict -> let pr = _unfocus pr in pr | Loose -> begin try let pr = _unfocus pr in unfocus kind pr () with FullyUnfocused -> raise CannotUnfocusThisWay end exception NoSuchFocus (* no handler: should not be allowed to reach toplevel. *) let rec get_in_focus_stack kind stack = match stack with | (cond,inf,_)::stack -> if check_cond_kind cond kind then inf else get_in_focus_stack kind stack | [] -> raise NoSuchFocus let get_at_focus kind pr = Obj.magic (get_in_focus_stack kind pr.focus_stack) let is_last_focus kind pr = let (cond,_,_) = List.hd pr.focus_stack in check_cond_kind cond kind let no_focused_goal p = Proofview.finished p.proofview let rec maximal_unfocus k p = if no_focused_goal p then try maximal_unfocus k (unfocus k p ()) with FullyUnfocused | CannotUnfocusThisWay -> p else p (*** Proof Creation/Termination ***) (* [end_of_stack] is unfocused by return to close every loose focus. *) let end_of_stack_kind = new_focus_kind () let end_of_stack = CondEndStack end_of_stack_kind let unfocused = is_last_focus end_of_stack_kind let start ~name ~poly ?typing_flags sigma goals = let entry, proofview = Proofview.init sigma goals in let pr = { proofview ; entry ; focus_stack = [] ; name ; poly ; typing_flags } in _focus end_of_stack (Obj.repr ()) 1 (List.length goals) pr let dependent_start ~name ~poly ?typing_flags goals = let entry, proofview = Proofview.dependent_init goals in let pr = { proofview ; entry ; focus_stack = [] ; name ; poly ; typing_flags } in let number_of_goals = List.length (Proofview.initial_goals pr.entry) in _focus end_of_stack (Obj.repr ()) 1 number_of_goals pr type open_error_reason = | UnfinishedProof | HasGivenUpGoals let print_open_error_reason er = let open Pp in match er with | UnfinishedProof -> str "Attempt to save an incomplete proof" | HasGivenUpGoals -> strbrk "Attempt to save a proof with given up goals. If this is really what you want to do, use Admitted in place of Qed." exception OpenProof of Names.Id.t option * open_error_reason let _ = CErrors.register_handler begin function | OpenProof (pid, reason) -> let open Pp in Some (Option.cata (fun pid -> str " (in proof " ++ Names.Id.print pid ++ str "): ") (mt()) pid ++ print_open_error_reason reason) | _ -> None end let warn_remaining_shelved_goals = CWarnings.create ~name:"remaining-shelved-goals" ~category:"tactics" (fun () -> Pp.str"The proof has remaining shelved goals") let warn_remaining_unresolved_evars = CWarnings.create ~name:"remaining-unresolved-evars" ~category:"tactics" (fun () -> Pp.str"The proof has unresolved variables") let return ?pid (p : t) = if not (is_done p) then raise (OpenProof(pid, UnfinishedProof)) else if has_given_up_goals p then raise (OpenProof(pid, HasGivenUpGoals)) else begin if has_shelved_goals p then warn_remaining_shelved_goals () else if has_unresolved_evar p then warn_remaining_unresolved_evars (); let p = unfocus end_of_stack_kind p () in Proofview.return p.proofview end let compact p = let entry, proofview = Proofview.compact p.entry p.proofview in { p with proofview; entry } let update_sigma_univs ugraph p = let proofview = Proofview.Unsafe.update_sigma_univs ugraph p.proofview in { p with proofview } (*** Function manipulation proof extra informations ***) (*** Tactics ***) let run_tactic env tac pr = let open Proofview.Notations in let undef sigma l = List.filter (fun g -> Evd.is_undefined sigma g) l in let tac = Proofview.tclEVARMAP >>= fun sigma -> Proofview.Unsafe.tclEVARS (Evd.push_shelf sigma) >>= fun () -> tac >>= fun result -> Proofview.tclEVARMAP >>= fun sigma -> (* Already solved goals are not to be counted as shelved. Nor are they to be marked as unresolvable. *) let retrieved, sigma = Evd.pop_future_goals sigma in let retrieved = Evd.FutureGoals.filter (Evd.is_undefined sigma) retrieved in let retrieved = List.rev retrieved.Evd.FutureGoals.comb in let sigma = Proofview.Unsafe.mark_as_goals sigma retrieved in let to_shelve, sigma = Evd.pop_shelf sigma in Proofview.Unsafe.tclEVARS sigma >>= fun () -> Proofview.Unsafe.tclNEWSHELVED (retrieved@to_shelve) <*> Proofview.tclUNIT (result,retrieved,to_shelve) in let { name; poly; proofview } = pr in let proofview = Proofview.Unsafe.push_future_goals proofview in let ((result,retrieved,to_shelve),proofview,status,info_trace) = Proofview.apply ~name ~poly env tac proofview in let sigma = Proofview.return proofview in let to_shelve = undef sigma to_shelve in let proofview = Proofview.Unsafe.mark_as_unresolvables proofview to_shelve in let proofview = Proofview.filter_shelf (Evd.is_undefined sigma) proofview in { pr with proofview },(status,info_trace),result (*** Commands ***) (* Remove all the goals from the shelf and adds them at the end of the focused goals. *) let unshelve p = let sigma = Proofview.return p.proofview in let shelf = Evd.shelf sigma in let proofview = Proofview.unshelve shelf p.proofview in { p with proofview } (*** Compatibility layer with <=v8.2 ***) module V82 = struct let background_subgoals p = let it, sigma = Proofview.proofview (unroll_focus p.proofview p.focus_stack) in Evd.{ it; sigma } let top_goal p = let { Evd.it=gls ; sigma=sigma; } = Proofview.V82.top_goals p.entry p.proofview in { Evd.it=List.hd gls ; sigma=sigma; } let top_evars p = Proofview.V82.top_evars p.entry p.proofview let warn_deprecated_grab_existentials = CWarnings.create ~name:"deprecated-grab-existentials" ~category:"deprecated" Pp.(fun () -> str "The Grab Existential Variables command is " ++ str"deprecated. Please use the Unshelve command or the unshelve tactical " ++ str"instead.") let grab_evars p = warn_deprecated_grab_existentials (); if not (is_done p) then raise (OpenProof(None, UnfinishedProof)) else { p with proofview = Proofview.V82.grab p.proofview } let warn_deprecated_existential = CWarnings.create ~name:"deprecated-existential" ~category:"deprecated" Pp.(fun () -> str "The Existential command is " ++ str"deprecated. Please use the Unshelve command or the unshelve " ++ str"tactical, and the instantiate tactic instead.") (* Main component of vernac command Existential *) let instantiate_evar env n intern pr = warn_deprecated_existential (); let tac = Proofview.tclBIND Proofview.tclEVARMAP begin fun sigma -> let (evk, evi) = let evl = Evarutil.non_instantiated sigma in let evl = Evar.Map.bindings evl in if (n <= 0) then CErrors.user_err Pp.(str "incorrect existential variable index") else if CList.length evl < n then CErrors.user_err Pp.(str "not so many uninstantiated existential variables") else CList.nth evl (n-1) in let env = Evd.evar_filtered_env env evi in let rawc = intern env sigma in let ltac_vars = Glob_ops.empty_lvar in let sigma = Evar_refiner.w_refine (evk, evi) (ltac_vars, rawc) env sigma in Proofview.Unsafe.tclEVARS sigma end in let { name; poly } = pr in let ((), proofview, _, _) = Proofview.apply ~name ~poly env tac pr.proofview in let proofview = Proofview.filter_shelf begin fun g -> Evd.is_undefined (Proofview.return proofview) g end proofview in { pr with proofview } end let all_goals p = let add gs set = List.fold_left (fun s g -> Goal.Set.add g s) set gs in let (goals,stack,sigma) = proof p in let set = add goals Goal.Set.empty in let set = List.fold_left (fun s gs -> let (g1, g2) = gs in add g1 (add g2 set)) set stack in let set = add (Evd.shelf sigma) set in let set = Goal.Set.union (Evd.given_up sigma) set in let { Evd.it = bgoals ; sigma = bsigma } = V82.background_subgoals p in add bgoals set type data = { sigma : Evd.evar_map (** A representation of the evar_map [EJGA wouldn't it better to just return the proofview?] *) ; goals : Evar.t list (** Focused goals *) ; entry : Proofview.entry (** Entry for the proofview *) ; stack : (Evar.t list * Evar.t list) list (** A representation of the focus stack *) ; name : Names.Id.t (** The name of the theorem whose proof is being constructed *) ; poly : bool (** Locality, polymorphism, and "kind" [Coercion, Definition, etc...] *) } let data { proofview; focus_stack; entry; name; poly } = let goals, sigma = Proofview.proofview proofview in (* spiwack: beware, the bottom of the stack is used by [Proof] internally, and should not be exposed. *) let rec map_minus_one f = function | [] -> assert false | [_] -> [] | a::l -> f a :: (map_minus_one f l) in let stack = map_minus_one (fun (_,_,c) -> Proofview.focus_context c) focus_stack in { sigma; goals; entry; stack; name; poly } let pr_proof p = let { goals=fg_goals; stack=bg_goals; sigma } = data p in Pp.( let pr_goal_list = prlist_with_sep spc Goal.pr_goal in let rec aux acc = function | [] -> acc | (before,after)::stack -> aux (pr_goal_list before ++ spc () ++ str "{" ++ acc ++ str "}" ++ spc () ++ pr_goal_list after) stack in str "[" ++ str "focus structure: " ++ aux (pr_goal_list fg_goals) bg_goals ++ str ";" ++ spc () ++ str "shelved: " ++ pr_goal_list (Evd.shelf sigma) ++ str ";" ++ spc () ++ str "given up: " ++ pr_goal_list (Evar.Set.elements @@ Evd.given_up sigma) ++ str "]" ) let use_unification_heuristics = Goptions.declare_bool_option_and_ref ~depr:false ~key:["Solve";"Unification";"Constraints"] ~value:true exception SuggestNoSuchGoals of int * t let solve ?with_end_tac gi info_lvl tac pr = let tac = match with_end_tac with | None -> tac | Some etac -> Proofview.tclTHEN tac etac in let tac = match info_lvl with | None -> tac | Some _ -> Proofview.Trace.record_info_trace tac in let nosuchgoal = let info = Exninfo.reify () in Proofview.tclZERO ~info (SuggestNoSuchGoals (1,pr)) in let tac = Goal_select.tclSELECT ~nosuchgoal gi tac in let tac = if use_unification_heuristics () then Proofview.tclTHEN tac Refine.solve_constraints else tac in let env = Global.env () in let env = Environ.update_typing_flags ?typing_flags:pr.typing_flags env in let (p,(status,info),()) = run_tactic env tac pr in let env = Global.env () in let sigma = Evd.from_env env in let () = match info_lvl with | None -> () | Some i -> Feedback.msg_info (Pp.hov 0 (Proofview.Trace.pr_info env sigma ~lvl:i info)) in (p,status) (**********************************************************************) (* Shortcut to build a term using tactics *) let refine_by_tactic ~name ~poly env sigma ty tac = (* Save the initial side-effects to restore them afterwards. We set the current set of side-effects to be empty so that we can retrieve the ones created during the tactic invocation easily. *) let eff = Evd.eval_side_effects sigma in let sigma = Evd.drop_side_effects sigma in (* Save the existing goals *) let sigma = Evd.push_future_goals sigma in (* Start a proof *) let prf = start ~name ~poly sigma [env, ty] in let (prf, _, ()) = try run_tactic env tac prf with Logic_monad.TacticFailure e as src -> (* Catch the inner error of the monad tactic *) let (_, info) = Exninfo.capture src in Exninfo.iraise (e, info) in (* Plug back the retrieved sigma *) let { goals; stack; sigma; entry } = data prf in assert (stack = []); let ans = match Proofview.initial_goals entry with | [c, _] -> c | _ -> assert false in let ans = EConstr.to_constr ~abort_on_undefined_evars:false sigma ans in (* [neff] contains the freshly generated side-effects *) let neff = Evd.eval_side_effects sigma in (* Reset the old side-effects *) let sigma = Evd.drop_side_effects sigma in let sigma = Evd.emit_side_effects eff sigma in (* Restore former goals *) let _goals, sigma = Evd.pop_future_goals sigma in (* Push remaining goals as future_goals which is the only way we have to inform the caller that there are goals to collect while not being encapsulated in the monad *) let sigma = List.fold_right Evd.declare_future_goal goals sigma in (* Get rid of the fresh side-effects by internalizing them in the term itself. Note that this is unsound, because the tactic may have solved other goals that were already present during its invocation, so that those goals rely on effects that are not present anymore. Hopefully, this hack will work in most cases. *) let neff = neff.Evd.seff_private in let (ans, _) = Safe_typing.inline_private_constants env ((ans, Univ.ContextSet.empty), neff) in ans, sigma let get_nth_V82_goal p i = let { sigma; goals } = data p in try { Evd.it = List.nth goals (i-1) ; sigma } with Failure _ -> raise (NoSuchGoal None) let get_goal_context_gen pf i = let { Evd.it=goal ; sigma=sigma; } = get_nth_V82_goal pf i in (sigma, Global.env_of_context (Goal.V82.hyps sigma goal)) let get_proof_context p = try get_goal_context_gen p 1 with | NoSuchGoal _ -> (* No more focused goals *) let { sigma } = data p in sigma, Global.env ()
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>