zsh/ported/loop.rs
1//! Loop execution for zshrs
2//!
3//! Port from zsh/Src/loop.c (802 lines)
4//!
5//! # of nested loops we are in // c:33
6//! # of continue levels // c:38
7//! # of break levels // c:43
8//!
9//! In C, loop.c contains execfor, execwhile, execif, execcase, execselect,
10//! execrepeat, and exectry as separate functions operating on bytecode.
11//! In Rust, all of these are implemented as match arms in
12//! ShellExecutor::execute_compound() in vm_helper, operating on the typed AST
13//! (CompoundCommand::For, While, If, Case, Select, Repeat, Try).
14//!
15//! This module provides the loop state management and helper functions
16//! that support the executor's loop implementation.
17
18use crate::ported::utils::{adjustcolumns, adjustlines};
19use std::io::Write;
20use std::sync::atomic::AtomicI32;
21// ===========================================================
22// C `Src/loop.c` — wordcode VM helpers for control flow.
23//
24// In C zsh, these seven functions run as part of the wordcode VM in
25// `Src/exec.c`: each consumes an `Estate` / wordcode cursor (not a
26// separate AST interpreter in the parser).
27//
28// zshrs lowers shell constructs to fusevm bytecode
29// (see `tree_walker_absent.rs` / `no_tree_walker_dispatch.rs`
30// invariant tests), so these entries exist to satisfy ABI/name
31// parity. The actual control-flow lowering happens in the
32// fusevm compiler (`crate::fusevm::compile`) where every
33// `for`/`while`/`if`/`case`/`select`/`repeat`/`try` AST node
34// becomes a fusevm `Op`.
35// ===========================================================
36
37// The seven entries below are zsh's `Src/loop.c` wordcode VM hooks.
38// zshrs lowers shell constructs to fusevm bytecode — every `for`/`while`/`if`/`case`/`select`/
39// `repeat`/`try` AST node lowers to a fusevm Op in
40// `src/extensions/compile_zsh.rs`. These entries exist purely for
41// C-name parity (drift gate enforces every Rust fn maps to a C fn).
42//
43// The 96-test architectural invariant in `tree_walker_absent.rs` +
44// `no_tree_walker_dispatch.rs` proves these are never reached in
45// production. Each body is `unreachable!()` so ANY caller fails
46// loudly rather than silently returning 0 — if a port regresses
47// the bytecode lowering, we want the test suite to crash, not pass.
48//
49// Faithful per-fn port of the C bodies is intentionally NOT done:
50// they read `Wordcode` / `Estate` cursors that zshrs doesn't model.
51// The semantic equivalent lives in:
52// execfor → compile_zsh.rs::compile_for
53// execselect → compile_zsh.rs::compile_select
54// execwhile → compile_zsh.rs::compile_while
55// execrepeat → compile_zsh.rs::compile_repeat
56// execif → compile_zsh.rs::compile_if
57// execcase → compile_zsh.rs::compile_case
58// exectry → compile_zsh.rs::compile_try
59
60// execfor moved to src/ported/exec.rs as a faithful port of
61// `Src/loop.c:50`. See exec.rs for the C-line-cited body.
62
63// Note: dead `ForIterator` / `CForState` / `TryState` aggregates
64// removed per PORT_PLAN Phase 2. None had production callers (only
65// internal test references). The actual control flow is lowered in
66// the fusevm compiler — every `for`/`while`/`select`/`repeat`/`try`
67// AST node becomes a fusevm Op (see `src/extensions/compile_zsh.rs`).
68//
69// C source's relevant try-block file-globals (loop.c:719-727):
70//
71// zlong try_errflag = -1; // line 719 (TRY_BLOCK_ERROR)
72// zlong try_interrupt = -1; // line 727 (TRY_BLOCK_INTERRUPT)
73// zlong try_tryflag = 0; // line 731 (TRY_BLOCK_DEPTH)
74//
75// Exported via `IPDEF6` paramdef in `Src/params.c:364`, so they're
76// cross-compilation-unit globals → PORT_PLAN Phase 3 bucket-2
77// (Arc<RwLock>) work, not the Phase 2 bucket-1 (thread_local!) wave.
78
79/// Port of `mod_export zlong try_tryflag` from `Src/loop.c:731`.
80/// Depth-counter for active `always {}` blocks; bumped at try entry
81/// (`exectry`), decremented at exit. `dotrapargs` (`signals.c:1215`)
82/// reads it to decide whether a trap's `errflag` propagates.
83pub static try_tryflag: std::sync::atomic::AtomicI64 = std::sync::atomic::AtomicI64::new(0); // c:731
84
85/// Port of `mod_export zlong try_errflag` from `Src/loop.c:719`.
86/// Initialized to -1 (sentinel: "no try block has fired yet in this
87/// scope"); the `exectry` always-arm sets it to `errflag & ERRFLAG_ERROR`
88/// before running the always body, then restores. `TRY_BLOCK_ERROR`
89/// param (IPDEF6 at `Src/params.c:364`) reads this global directly.
90pub static try_errflag: std::sync::atomic::AtomicI64 = std::sync::atomic::AtomicI64::new(-1); // c:719
91
92/// Port of `mod_export zlong try_interrupt` from `Src/loop.c:727`.
93/// Initialized to -1; mirrors `try_errflag` for the `ERRFLAG_INT` bit.
94/// `TRY_BLOCK_INTERRUPT` param reads this.
95pub static try_interrupt: std::sync::atomic::AtomicI64 = std::sync::atomic::AtomicI64::new(-1); // c:727
96
97// execselect moved to src/ported/exec.rs as a faithful port of
98// `Src/loop.c:217`. See exec.rs for the C-line-cited body.
99
100// Note: dead `LoopState` aggregate (and impl/tests) deleted per
101// PORT_PLAN Phase 2. It was a Rust-only invention that double-tracked
102// the same data already living in the file-statics LOOP_DEPTH /
103// CONT_FLAG / BREAK_LEVEL above (and on `ShellExecutor.breaking` /
104// `ShellExecutor.continuing` in src/vm_helper:572-573). Zero callers
105// outside its own test module.
106//
107// C source's actual loop-control file-globals at `Src/loop.c`:
108//
109// int loops; // line 36
110// mod_export int contflag; // line 41
111// mod_export volatile int breaks; // line 46
112//
113// All `mod_export` (cross-compilation-unit), so they're PORT_PLAN
114// Phase 3 bucket-2 (Arc<RwLock>) work. The canonical C-named ports
115// (LOOPS / CONTFLAG / BREAKS) live in `src/ported/builtin.rs:3657-3659`
116// where the bin_break dispatcher consults them; the local
117// LOOP_DEPTH / CONT_FLAG / BREAK_LEVEL above are this file's
118// internal mirrors.
119
120// And this is used to print select lists. // c:347
121/// Select-menu display.
122/// Port of `selectlist(LinkList l, size_t start)` from Src/loop.c:347 — formats the
123/// numbered menu the C source uses for `select var in words`. Picks
124/// columns automatically when `columns == 0`, mirroring the C
125/// source's terminal-width auto-detection.
126/// WARNING: param names don't match C — Rust=(items, start) vs C=(l, start)
127pub fn selectlist(items: &[&str], start: usize) -> usize {
128 // c:347
129 let mut stderr = std::io::stderr().lock();
130
131 // c:351 — zleentry(ZLE_CMD_TRASH); — flush ZLE redraw state.
132 // zshrs's ZLE entry-point dispatch is wired through the
133 // executor; the trash hook runs there, not in this body.
134
135 let ct = items.len(); // c:362 ap - arr
136 if ct == 0 {
137 // guard against empty list
138 return 0;
139 }
140 let mut longest: usize = 1; // c:350
141 for ap in items {
142 // c:354 for (ap = arr; *ap; ap++)
143 // C uses MB_METASTRWIDTH for visible width (combining-char
144 // aware). Rust port uses chars().count() — adequate for
145 // standard ASCII / non-combining content.
146 let aplen = ap.chars().count(); // c:359 unmetafy width
147 if aplen > longest {
148 // c:362
149 longest = aplen; // c:363
150 }
151 }
152 longest += 1; // c:365 +1 for ") "
153 let mut t0 = ct; // c:366
154 while t0 != 0 {
155 // c:367
156 t0 /= 10; // c:368
157 longest += 1; // c:368 (+1 per digit)
158 }
159
160 let zterm_columns = adjustcolumns(); // c:zterm_columns
161 let zterm_lines = adjustlines();
162 let mut fct: usize = (zterm_columns.saturating_sub(1)) / (longest + 3); // c:371
163 let fw: usize;
164 if fct == 0 {
165 // c:372
166 fct = 1; // c:373
167 fw = 0; // (unused when fct==1)
168 } else {
169 fw = (zterm_columns - 1) / fct; // c:375
170 }
171 let colsz = (ct + fct - 1) / fct; // c:377
172
173 // c:379 — for (t1 = start; t1 != colsz && t1 - start < zterm_lines - 2; t1++)
174 //
175 // C uses `*ap` (NUL-terminated array walk) to bound the inner loop;
176 // the Rust port lost that sentinel, so an out-of-range `start` (or
177 // a row-offset that reaches past `ct`) indexes `items[idx]` and
178 // panics. Mirror the C terminator with explicit bounds checks:
179 // - outer: stop when `t1 >= colsz` OR `start + t1*fct >= ct`
180 // - inner: skip the row entirely when the column-base is OOB.
181 let mut t1 = start;
182 let max_lines = zterm_lines.saturating_sub(2);
183 while t1 < colsz && (t1.saturating_sub(start)) < max_lines {
184 let mut idx = t1; // c:381 ap = arr + t1
185 if idx >= ct {
186 // Past the last item — C's `*ap` would already be NULL here.
187 // Emit the blank line C would produce and advance.
188 let _ = stderr.write_all(b"\n");
189 t1 += 1;
190 continue;
191 }
192 loop {
193 // c:383 do {
194 let entry = items[idx];
195 // c:385 — t2 = MB_METASTRWIDTH(*ap) + 2;
196 let mut t2 = entry.chars().count() + 2;
197 // c:391 — fprintf(stderr, "%d) %s", t3 = ap - arr + 1, *ap);
198 let t3 = idx + 1;
199 let _ = write!(stderr, "{}) {}", t3, entry);
200 // c:393 — while (t3) t2++, t3 /= 10;
201 let mut digits = t3;
202 while digits != 0 {
203 // c:393
204 t2 += 1;
205 digits /= 10;
206 }
207 // c:396 — for (; t2 < fw; t2++) fputc(' ', stderr);
208 while t2 < fw {
209 // c:396
210 let _ = stderr.write_all(b" ");
211 t2 += 1;
212 }
213 // c:398 — for (t0 = colsz; t0 && *ap; t0--, ap++);
214 let mut t0 = colsz;
215 while t0 != 0 && idx + 1 < ct {
216 t0 -= 1;
217 idx += 1;
218 if t0 == 0 {
219 break;
220 }
221 }
222 if idx + 1 >= ct {
223 break;
224 } // c:401 while (*ap);
225 }
226 let _ = stderr.write_all(b"\n"); // c:401 fputc('\n', stderr);
227 t1 += 1;
228 }
229
230 let _ = stderr.flush(); // c:413 fflush(stderr);
231
232 if t1 < colsz {
233 t1
234 } else {
235 0
236 } // c:415 return
237}
238
239// execwhile / execrepeat / execif / execcase / exectry moved to
240// src/ported/exec.rs as faithful ports of Src/loop.c:413 / :499 /
241// :553 / :600 / :735. See exec.rs for the C-line-cited bodies.
242
243/// Number of nested loops.
244/// Port of the global `loops` counter from Src/loop.c — every
245/// `execfor`/`execwhile`/`execrepeat`/`execselect` entry bumps it
246/// and decrements on exit.
247static LOOP_DEPTH: AtomicI32 = AtomicI32::new(0);
248
249/// Continue flag / level.
250/// Port of the global `contflag` from Src/loop.c — set by the
251/// `continue` builtin (Src/builtin.c:bin_break) and consumed by
252/// the loop body's exit check.
253static CONT_FLAG: AtomicI32 = AtomicI32::new(0);
254
255/// Break level.
256/// Port of the global `breaks` counter from Src/loop.c — set by
257/// the `break` builtin (Src/builtin.c:bin_break) and tested by
258/// each enclosing loop on exit.
259static BREAK_LEVEL: AtomicI32 = AtomicI32::new(0);
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264
265 #[test]
266 fn test_selectlist_returns_zero_when_full_fits() {
267 let _g = crate::test_util::global_state_lock();
268 let items = ["one", "two", "three"];
269 let r = selectlist(&items, 0);
270 assert!(r < items.len() || r == 0);
271 }
272
273 // The 7 *_panics_with_tree_walker_disabled tests were deleted
274 // when the underlying `unreachable!()` stubs were replaced with
275 // faithful ports in src/ported/exec.rs. The architectural pin
276 // remains in tests/tree_walker_absent.rs (which asserts the
277 // AST-side `execute_simple`/`execute_pipeline`/etc. tree walker
278 // is gone — a different concept from the wordcode-VM dispatch
279 // chain ported in exec.rs).
280
281 /// Empty list to selectlist: nothing to draw, returns 0.
282 #[test]
283 fn selectlist_empty_returns_zero() {
284 let _g = crate::test_util::global_state_lock();
285 assert_eq!(selectlist(&[], 0), 0);
286 }
287
288 // ═══════════════════════════════════════════════════════════════════
289 // Additional C-parity tests for Src/loop.c selectlist.
290 // ═══════════════════════════════════════════════════════════════════
291
292 /// c:347 — `selectlist` with start=0 doesn't panic and returns
293 /// a valid count.
294 #[test]
295 fn selectlist_start_zero_returns_valid_count() {
296 let _g = crate::test_util::global_state_lock();
297 let items = ["a", "b", "c"];
298 let r = selectlist(&items, 0);
299 // Return value is "next start" for paginated display.
300 // Must be reasonable: 0 (full draw fit) or >0 (paginated).
301 assert!(r <= items.len());
302 }
303
304 /// c:347 — single-item list always fits in one column.
305 #[test]
306 fn selectlist_single_item_no_panic() {
307 let _g = crate::test_util::global_state_lock();
308 let r = selectlist(&["only"], 0);
309 let _ = r;
310 }
311
312 /// c:347 — list with empty string entries doesn't panic.
313 #[test]
314 fn selectlist_empty_string_entries_no_panic() {
315 let _g = crate::test_util::global_state_lock();
316 let items = ["", "", ""];
317 let _ = selectlist(&items, 0);
318 }
319
320 /// c:347 — long items (longer than terminal width) handled safely.
321 #[test]
322 fn selectlist_long_items_no_panic() {
323 let _g = crate::test_util::global_state_lock();
324 let long = "x".repeat(500);
325 let items = [long.as_str(), "short"];
326 let _ = selectlist(&items, 0);
327 }
328
329 /// c:347 — multibyte content (CJK) doesn't panic.
330 #[test]
331 fn selectlist_multibyte_entries_no_panic() {
332 let _g = crate::test_util::global_state_lock();
333 let items = ["日本", "中文", "한국어"];
334 let _ = selectlist(&items, 0);
335 }
336
337 /// c:347 — many small items doesn't panic (stress: 100 entries).
338 #[test]
339 fn selectlist_many_items_no_panic() {
340 let _g = crate::test_util::global_state_lock();
341 let items: Vec<String> = (0..100).map(|i| format!("item{}", i)).collect();
342 let refs: Vec<&str> = items.iter().map(|s| s.as_str()).collect();
343 let _ = selectlist(&refs, 0);
344 }
345
346 /// c:347 — start > 0 with valid index PANICS in zshrs port
347 /// ("index out of bounds: the len is 5 but the index is 5").
348 /// C handles paginated start correctly via the c:379 column loop
349 /// bound `t1 - start < zterm_lines - 2`. Likely off-by-one in
350 /// the Rust port's index walk past last column.
351 #[test]
352 fn selectlist_start_in_range_no_panic() {
353 let _g = crate::test_util::global_state_lock();
354 let items = ["a", "b", "c", "d", "e"];
355 let _ = selectlist(&items, 2);
356 }
357
358 /// c:347 — `selectlist` is deterministic for a given (items, start).
359 #[test]
360 fn selectlist_is_deterministic() {
361 let _g = crate::test_util::global_state_lock();
362 let items = ["a", "b", "c"];
363 let first = selectlist(&items, 0);
364 for _ in 0..5 {
365 assert_eq!(selectlist(&items, 0), first);
366 }
367 }
368
369 // ═══════════════════════════════════════════════════════════════════
370 // Additional C-parity tests for Src/loop.c
371 // c:347 selectlist + statics (LOOP_DEPTH/CONT_FLAG/BREAK_LEVEL/try_tryflag)
372 // ═══════════════════════════════════════════════════════════════════
373
374 /// c:347 — `selectlist` return value ≤ items.len() (valid next-start).
375 #[test]
376 fn selectlist_return_bounded_by_items_len() {
377 let _g = crate::test_util::global_state_lock();
378 for sz in [1usize, 3, 5, 20] {
379 let items: Vec<String> = (0..sz).map(|i| format!("i{}", i)).collect();
380 let refs: Vec<&str> = items.iter().map(|s| s.as_str()).collect();
381 let r = selectlist(&refs, 0);
382 assert!(r <= sz, "selectlist({} items, 0) = {} > {}", sz, r, sz);
383 }
384 }
385
386 /// c:347 — `selectlist` returns usize (compile-time type pin).
387 #[test]
388 fn selectlist_returns_usize_type() {
389 let _g = crate::test_util::global_state_lock();
390 let _: usize = selectlist(&["x"], 0);
391 }
392
393 /// c:347 — `selectlist` two-item list doesn't panic.
394 #[test]
395 fn selectlist_two_items_no_panic() {
396 let _g = crate::test_util::global_state_lock();
397 let _ = selectlist(&["a", "b"], 0);
398 }
399
400 /// c:347 — `selectlist` with entries containing tabs doesn't panic.
401 #[test]
402 fn selectlist_entries_with_tabs_no_panic() {
403 let _g = crate::test_util::global_state_lock();
404 let _ = selectlist(&["a\tb", "c\td"], 0);
405 }
406
407 /// c:731 — `try_tryflag` is AtomicI64 (compile-time type pin).
408 #[test]
409 fn try_tryflag_is_atomic_i64() {
410 use std::sync::atomic::Ordering;
411 let v: i64 = try_tryflag.load(Ordering::SeqCst);
412 // Just verify it loads — any i64 is a valid initial state.
413 let _ = v;
414 }
415
416 /// c:347 — `selectlist` with entries containing ANSI escape doesn't panic.
417 #[test]
418 fn selectlist_ansi_escape_entries_no_panic() {
419 let _g = crate::test_util::global_state_lock();
420 let _ = selectlist(&["\x1b[31mred\x1b[0m", "plain"], 0);
421 }
422
423 /// c:347 — `selectlist` with single space entry doesn't panic.
424 #[test]
425 fn selectlist_single_space_entry_no_panic() {
426 let _g = crate::test_util::global_state_lock();
427 let _ = selectlist(&[" "], 0);
428 }
429
430 // ═══════════════════════════════════════════════════════════════════
431 // Additional C-parity tests for Src/loop.c
432 // c:36 loops / c:41 contflag / c:46 breaks / c:347 selectlist /
433 // c:731 try_tryflag — initial-state + idempotency pins.
434 // ═══════════════════════════════════════════════════════════════════
435
436 /// c:36 — `loops` initial state is zero (`int loops;` BSS zero init).
437 #[test]
438 fn loop_depth_initial_state_is_zero() {
439 use std::sync::atomic::Ordering;
440 let v = LOOP_DEPTH.load(Ordering::SeqCst);
441 assert!(v >= 0, "loop depth must never be negative; got {}", v);
442 }
443
444 /// c:41 — `contflag` initial state must be non-negative
445 /// (`mod_export int contflag;` BSS-zero).
446 #[test]
447 fn cont_flag_initial_state_non_negative() {
448 use std::sync::atomic::Ordering;
449 let v = CONT_FLAG.load(Ordering::SeqCst);
450 assert!(v >= 0, "cont flag must be non-negative; got {}", v);
451 }
452
453 /// c:46 — `breaks` initial state non-negative
454 /// (`volatile int breaks;` BSS-zero).
455 #[test]
456 fn break_level_initial_state_non_negative() {
457 use std::sync::atomic::Ordering;
458 let v = BREAK_LEVEL.load(Ordering::SeqCst);
459 assert!(v >= 0, "break level must be non-negative; got {}", v);
460 }
461
462 /// c:347 — `selectlist` is idempotent across many calls (no mutation).
463 #[test]
464 fn selectlist_no_mutation_across_calls() {
465 let _g = crate::test_util::global_state_lock();
466 let items = ["alpha", "beta", "gamma"];
467 let first = selectlist(&items, 0);
468 for _ in 0..20 {
469 assert_eq!(
470 selectlist(&items, 0),
471 first,
472 "selectlist must be pure across repeated calls"
473 );
474 }
475 }
476
477 /// c:347 — `selectlist` four items doesn't panic.
478 #[test]
479 fn selectlist_four_items_no_panic() {
480 let _g = crate::test_util::global_state_lock();
481 let _ = selectlist(&["a", "b", "c", "d"], 0);
482 }
483
484 /// c:347 — `selectlist` with newline-bearing entry doesn't panic.
485 #[test]
486 fn selectlist_newline_entry_no_panic() {
487 let _g = crate::test_util::global_state_lock();
488 let _ = selectlist(&["a\nb"], 0);
489 }
490
491 /// c:347 — `selectlist` mixed-length entries doesn't panic.
492 #[test]
493 fn selectlist_mixed_length_entries_no_panic() {
494 let _g = crate::test_util::global_state_lock();
495 let _ = selectlist(&["a", "ab", "abc", "abcd", "abcde"], 0);
496 }
497
498 /// c:347 — `selectlist` Unicode emoji entry doesn't panic.
499 #[test]
500 fn selectlist_emoji_entries_no_panic() {
501 let _g = crate::test_util::global_state_lock();
502 let _ = selectlist(&["foo", "bar"], 0);
503 }
504
505 /// c:731 — `try_tryflag` load is idempotent (pure read, no mutation).
506 #[test]
507 fn try_tryflag_load_idempotent() {
508 use std::sync::atomic::Ordering;
509 let first = try_tryflag.load(Ordering::SeqCst);
510 for _ in 0..50 {
511 assert_eq!(
512 try_tryflag.load(Ordering::SeqCst),
513 first,
514 "try_tryflag pure load must be deterministic"
515 );
516 }
517 }
518
519 /// c:731 — `try_tryflag` static address is stable.
520 #[test]
521 fn try_tryflag_address_is_stable() {
522 let p1 = &try_tryflag as *const _;
523 let p2 = &try_tryflag as *const _;
524 assert_eq!(p1, p2, "static must have a single, stable address");
525 }
526
527 /// c:347 — `selectlist` with whitespace-only entries doesn't panic.
528 #[test]
529 fn selectlist_whitespace_only_entries_no_panic() {
530 let _g = crate::test_util::global_state_lock();
531 let _ = selectlist(&[" ", "\t\t", " \t "], 0);
532 }
533
534 /// c:36 / c:41 / c:46 — `LOOP_DEPTH`, `CONT_FLAG`, `BREAK_LEVEL`
535 /// addresses are stable (no per-call alloc).
536 #[test]
537 fn loop_static_addresses_stable() {
538 let a1 = &LOOP_DEPTH as *const _;
539 let a2 = &LOOP_DEPTH as *const _;
540 assert_eq!(a1, a2, "LOOP_DEPTH must be a true static");
541 let b1 = &CONT_FLAG as *const _;
542 let b2 = &CONT_FLAG as *const _;
543 assert_eq!(b1, b2, "CONT_FLAG must be a true static");
544 let c1 = &BREAK_LEVEL as *const _;
545 let c2 = &BREAK_LEVEL as *const _;
546 assert_eq!(c1, c2, "BREAK_LEVEL must be a true static");
547 }
548
549 // ═══════════════════════════════════════════════════════════════════
550 // Additional C-parity pins for Src/loop.c
551 // c:347 selectlist — empty / single / huge entries / start-offset edges
552 // ═══════════════════════════════════════════════════════════════════
553
554 /// c:362 — `selectlist([])` empty input returns 0 (no menu rendered, alt).
555 #[test]
556 fn selectlist_empty_returns_zero_alt() {
557 let _g = crate::test_util::global_state_lock();
558 assert_eq!(
559 selectlist(&[], 0),
560 0,
561 "empty items must produce zero-line output"
562 );
563 }
564
565 /// c:362 — `selectlist([single])` doesn't panic.
566 #[test]
567 fn selectlist_single_entry_no_panic() {
568 let _g = crate::test_util::global_state_lock();
569 let _ = selectlist(&["one"], 0);
570 }
571
572 /// c:347 — `selectlist` return type usize (compile-time pin, alt).
573 #[test]
574 fn selectlist_returns_usize_type_alt() {
575 let _g = crate::test_util::global_state_lock();
576 let _: usize = selectlist(&["a"], 0);
577 }
578
579 /// c:347 — `selectlist` with start ≥ items.len() doesn't panic
580 /// (start past end is degenerate but safe).
581 #[test]
582 fn selectlist_start_past_end_no_panic() {
583 let _g = crate::test_util::global_state_lock();
584 let _ = selectlist(&["a", "b"], 100);
585 let _ = selectlist(&["a", "b"], usize::MAX / 2);
586 }
587
588 /// c:347 — `selectlist` with very large entry list doesn't panic
589 /// (pin against accidental quadratic / OOB on big counts).
590 #[test]
591 fn selectlist_large_list_no_panic() {
592 let _g = crate::test_util::global_state_lock();
593 let items: Vec<String> = (0..200).map(|i| format!("item{}", i)).collect();
594 let refs: Vec<&str> = items.iter().map(|s| s.as_str()).collect();
595 let _ = selectlist(&refs, 0);
596 }
597
598 /// c:347 — `selectlist` with start at exactly items.len()-1 doesn't panic.
599 #[test]
600 fn selectlist_start_at_last_index_no_panic() {
601 let _g = crate::test_util::global_state_lock();
602 let _ = selectlist(&["a", "b", "c"], 2);
603 }
604
605 /// c:36/41/46 — LOOP_DEPTH/CONT_FLAG/BREAK_LEVEL atomic i32 type pins.
606 #[test]
607 fn loop_statics_are_atomic_i32() {
608 let _: &AtomicI32 = &LOOP_DEPTH;
609 let _: &AtomicI32 = &CONT_FLAG;
610 let _: &AtomicI32 = &BREAK_LEVEL;
611 }
612
613 /// c:36 — LOOP_DEPTH atomic store/load round-trip.
614 #[test]
615 fn loop_depth_store_load_round_trip() {
616 use std::sync::atomic::Ordering;
617 let _g = crate::test_util::global_state_lock();
618 let saved = LOOP_DEPTH.load(Ordering::SeqCst);
619 for v in [0, 1, 5, 100, -1, i32::MAX, i32::MIN] {
620 LOOP_DEPTH.store(v, Ordering::SeqCst);
621 assert_eq!(
622 LOOP_DEPTH.load(Ordering::SeqCst),
623 v,
624 "LOOP_DEPTH must round-trip {}",
625 v
626 );
627 }
628 LOOP_DEPTH.store(saved, Ordering::SeqCst);
629 }
630
631 /// c:41 — CONT_FLAG atomic store/load round-trip.
632 #[test]
633 fn cont_flag_store_load_round_trip() {
634 use std::sync::atomic::Ordering;
635 let _g = crate::test_util::global_state_lock();
636 let saved = CONT_FLAG.load(Ordering::SeqCst);
637 for v in [0, 1, 5, 100, -1, i32::MAX, i32::MIN] {
638 CONT_FLAG.store(v, Ordering::SeqCst);
639 assert_eq!(CONT_FLAG.load(Ordering::SeqCst), v);
640 }
641 CONT_FLAG.store(saved, Ordering::SeqCst);
642 }
643
644 /// c:46 — BREAK_LEVEL atomic store/load round-trip.
645 #[test]
646 fn break_level_store_load_round_trip() {
647 use std::sync::atomic::Ordering;
648 let _g = crate::test_util::global_state_lock();
649 let saved = BREAK_LEVEL.load(Ordering::SeqCst);
650 for v in [0, 1, 5, 100, -1, i32::MAX, i32::MIN] {
651 BREAK_LEVEL.store(v, Ordering::SeqCst);
652 assert_eq!(BREAK_LEVEL.load(Ordering::SeqCst), v);
653 }
654 BREAK_LEVEL.store(saved, Ordering::SeqCst);
655 }
656
657 /// c:347 — `selectlist` is deterministic across identical calls
658 /// (the return value depends only on the inputs).
659 #[test]
660 fn selectlist_deterministic_on_identical_input() {
661 let _g = crate::test_util::global_state_lock();
662 let first = selectlist(&["one", "two", "three"], 0);
663 for _ in 0..5 {
664 assert_eq!(
665 selectlist(&["one", "two", "three"], 0),
666 first,
667 "selectlist must be deterministic"
668 );
669 }
670 }
671}