1
//! Shared datatypes for azul-* crates
2
//!
3
//! `azul-core` provides the platform-independent core types used throughout
4
//! the Azul toolkit. Key modules include [`dom`] for DOM construction,
5
//! [`callbacks`] for event callback types, [`styled_dom`] for the CSSOM,
6
//! and [`window`] for OS windowing abstractions.
7
//!
8
//! This crate depends on [`azul_css`] for CSS property definitions and is
9
//! consumed by `azul-layout`, `azul-dll`, and the platform shell crates.
10
//! It supports `no_std` environments via `#![cfg_attr(not(feature = "std"), no_std)]`.
11

            
12
#![cfg_attr(not(feature = "std"), no_std)]
13
// Lint policy: deny correctness/safety issues, warn on style
14
#![deny(unused_must_use)]
15
#![warn(clippy::all)]
16
// Extreme-lint lockdown: all clippy groups plus opt-in rustc lints, enforced as
17
// -D warnings on library code by the CI clippy job. Test builds are exempt via
18
// cfg(not(test)) below since the set is high-noise and low-value on unit and
19
// generated tests; clippy::all correctness still applies to test code.
20
// (clippy::restriction wholesale + unused_results + box_pointers deliberately
21
// omitted — contradictory / overwhelmingly noisy by design.)
22
#![cfg_attr(not(test), warn(
23
    clippy::pedantic,
24
    clippy::nursery,
25
    clippy::cargo,
26
    // missing_docs,  // TODO(docs): re-enable as a dedicated final docs pass; disabled
27
    //                // for now so the cleanup focuses on code-quality lints, not doc debt.
28
    missing_debug_implementations,
29
    missing_copy_implementations,
30
    unreachable_pub,
31
    unused_qualifications,
32
    unused_lifetimes,
33
    unused_import_braces,
34
    unused_macro_rules,
35
    unused_crate_dependencies,
36
    meta_variable_misuse,
37
    trivial_casts,
38
    trivial_numeric_casts,
39
    elided_lifetimes_in_paths,
40
    single_use_lifetimes,
41
    variant_size_differences,
42
    non_ascii_idents,
43
    unsafe_op_in_unsafe_fn,
44
    let_underscore_drop,
45
))]
46
// `multiple_crate_versions` (implied by clippy::cargo) flags transitive
47
// dependency-version dups that cannot be resolved in azul's own source:
48
// `syn` 1.0.x ↔ 2.0.x (the proc-macro ecosystem is mid-migration; both are
49
// pulled in transitively). Documented allow — re-audit when the dep tree aligns.
50
#![allow(clippy::multiple_crate_versions)]
51
#![allow(
52
    clippy::non_canonical_partial_ord_impl,
53
    clippy::legacy_numeric_constants,
54
    clippy::should_implement_trait,
55
    clippy::result_unit_err,
56
    clippy::ptr_as_ptr,
57
    clippy::too_many_arguments,
58
    clippy::type_complexity,
59
    unused_imports,
60
    unused_variables,
61
    unused_mut,
62
    unused_parens,
63
    dead_code,
64
    unused_doc_comments,
65
    unused_assignments,                    // compact_cache_builder incremental updates
66
    unexpected_cfgs,
67
    unpredictable_function_pointer_comparisons, // intentional in dom callback comparison
68
    improper_ctypes_definitions,           // xml component fns use Rust fn pointers internally
69
    static_mut_refs,                       // TODO: migrate to OnceLock for Rust 2024
70
)]
71

            
72
// `extern crate` + `#[macro_use]` required for `no_std` support:
73
// makes `core` and `alloc` macros available without `use` imports.
74
#[macro_use]
75
extern crate core;
76
#[macro_use]
77
extern crate alloc;
78
#[macro_use]
79
extern crate azul_css;
80

            
81
/// Internal macros for `Vec`, `Option`, and callback boilerplate.
82
///
83
#[macro_use]
84
pub mod macros;
85
/// Debug logging system with category filtering.
86
#[macro_use]
87
pub mod debug;
88
/// SQL database POD types — `DbValue` + `DbRows` (engine-agnostic). The
89
/// `Db` handle + SQLite engine live in `azul_dll` behind `db-sqlite`.
90
pub mod db;
91
/// Unified `AZ_PROFILE` gate for memory and CPU profiling instrumentation.
92
pub mod profile;
93
/// `no_std`-friendly synchronization primitives.
94
///
95
/// In `std` builds these re-export the matching `std::sync` types. In
96
/// `no_std` builds they provide minimal spinlock-backed equivalents
97
/// implementing only the API surface azul-core relies on.
98
pub mod sync {
99
    #[cfg(feature = "std")]
100
    pub use std::sync::OnceLock;
101

            
102
    #[cfg(not(feature = "std"))]
103
    pub use self::nostd::OnceLock;
104

            
105
    #[cfg(not(feature = "std"))]
106
    mod nostd {
107
        use core::cell::UnsafeCell;
108
        use core::sync::atomic::{AtomicU8, Ordering};
109

            
110
        const UNINIT: u8 = 0;
111
        const BUSY: u8 = 1;
112
        const READY: u8 = 2;
113

            
114
        /// Minimal `no_std` `OnceLock` mirroring the slice of the std API used
115
        /// by azul-core (`new`, `get`, `get_or_init`).
116
        pub struct OnceLock<T> {
117
            state: AtomicU8,
118
            value: UnsafeCell<Option<T>>,
119
        }
120

            
121
        unsafe impl<T: Send + Sync> Sync for OnceLock<T> {}
122
        unsafe impl<T: Send> Send for OnceLock<T> {}
123

            
124
        impl<T> OnceLock<T> {
125
            pub const fn new() -> Self {
126
                OnceLock {
127
                    state: AtomicU8::new(UNINIT),
128
                    value: UnsafeCell::new(None),
129
                }
130
            }
131

            
132
            pub fn get(&self) -> Option<&T> {
133
                if self.state.load(Ordering::Acquire) == READY {
134
                    unsafe { (*self.value.get()).as_ref() }
135
                } else {
136
                    None
137
                }
138
            }
139

            
140
            pub fn get_or_init<F: FnOnce() -> T>(&self, f: F) -> &T {
141
                if let Some(v) = self.get() {
142
                    return v;
143
                }
144
                // Contend for the right to initialize.
145
                while self
146
                    .state
147
                    .compare_exchange(UNINIT, BUSY, Ordering::Acquire, Ordering::Acquire)
148
                    .is_err()
149
                {
150
                    if self.state.load(Ordering::Acquire) == READY {
151
                        return self.get().expect("OnceLock ready");
152
                    }
153
                    core::hint::spin_loop();
154
                }
155
                unsafe {
156
                    *self.value.get() = Some(f());
157
                }
158
                self.state.store(READY, Ordering::Release);
159
                self.get().expect("OnceLock initialized")
160
            }
161
        }
162

            
163
        impl<T> Default for OnceLock<T> {
164
            fn default() -> Self {
165
                Self::new()
166
            }
167
        }
168

            
169
        impl<T: core::fmt::Debug> core::fmt::Debug for OnceLock<T> {
170
            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
171
                f.debug_tuple("OnceLock").field(&self.get()).finish()
172
            }
173
        }
174

            
175
        impl<T: Clone> Clone for OnceLock<T> {
176
            fn clone(&self) -> Self {
177
                let new = OnceLock::new();
178
                if let Some(v) = self.get() {
179
                    let _ = new.get_or_init(|| v.clone());
180
                }
181
                new
182
            }
183
        }
184

            
185
        impl<T: PartialEq> PartialEq for OnceLock<T> {
186
            fn eq(&self, other: &Self) -> bool {
187
                self.get() == other.get()
188
            }
189
        }
190
    }
191
}
192

            
193
/// `no_std`-friendly default hasher used for change-detection hashing.
194
///
195
/// In `std` builds this re-exports `std::hash::DefaultHasher` so behaviour
196
/// is unchanged. In `no_std` builds it provides a small deterministic
197
/// `FxHasher`-style hasher implementing `core::hash::Hasher`. The values are
198
/// only required to be stable within a single process run (they back diffing /
199
/// change detection), not to match `std`'s `SipHash` output.
200
pub mod hash {
201
    #[cfg(feature = "std")]
202
    pub use std::hash::DefaultHasher;
203

            
204
    #[cfg(not(feature = "std"))]
205
    pub use self::nostd::DefaultHasher;
206

            
207
    #[cfg(not(feature = "std"))]
208
    mod nostd {
209
        use core::hash::Hasher;
210

            
211
        const SEED: u64 = 0x51_7c_c1_b7_27_22_0a_95;
212
        const ROTATE: u32 = 5;
213

            
214
        /// FxHasher-style `no_std` hasher. Not DoS-resistant; used purely for
215
        /// in-process change detection.
216
        #[derive(Default)]
217
        pub struct DefaultHasher {
218
            hash: u64,
219
        }
220

            
221
        impl DefaultHasher {
222
            pub fn new() -> Self {
223
                DefaultHasher { hash: 0 }
224
            }
225

            
226
            #[inline]
227
            fn add(&mut self, word: u64) {
228
                self.hash = (self.hash.rotate_left(ROTATE) ^ word).wrapping_mul(SEED);
229
            }
230
        }
231

            
232
        impl Hasher for DefaultHasher {
233
            #[inline]
234
            fn finish(&self) -> u64 {
235
                self.hash
236
            }
237

            
238
            #[inline]
239
            fn write(&mut self, bytes: &[u8]) {
240
                for chunk in bytes.chunks(8) {
241
                    let mut buf = [0u8; 8];
242
                    buf[..chunk.len()].copy_from_slice(chunk);
243
                    self.add(u64::from_le_bytes(buf));
244
                }
245
            }
246

            
247
            #[inline]
248
            fn write_u8(&mut self, i: u8) {
249
                self.add(i as u64);
250
            }
251
            #[inline]
252
            fn write_u64(&mut self, i: u64) {
253
                self.add(i);
254
            }
255
            #[inline]
256
            fn write_usize(&mut self, i: usize) {
257
                self.add(i as u64);
258
            }
259
        }
260
    }
261
}
262
/// Callback types: layout, event, timer, thread, and focus handling.
263
#[macro_use]
264
pub mod callbacks;
265
/// Host-language callback invoker registry.
266
///
267
/// The C-ABI surface managed-FFI bindings (Lua, Ruby, …) use to register one
268
/// per-kind invoker + a single shared releaser, so callbacks can be created via
269
/// `_createFromHostHandle` without the host having to generate trampolines for
270
/// struct-by-value signatures their FFI library can't handle.
271
#[macro_use]
272
pub mod host_invoker;
273
/// Accessibility types for screen-reader integration (AccessKit).
274
/// DOM-morph animation.
275
///
276
/// Interpolation core (springs + easing), FLIP geometry, and the keyed
277
/// store of in-flight animations. See `scripts/ANIMATION_SHADER_DESIGN.md`.
278
pub mod animation;
279
pub mod a11y;
280
/// Audio POD types — `AudioConfig` (stream format) + `AudioFrame` (interleaved
281
/// f32 samples).
282
///
283
/// The unit captured from the mic, played back, and (P8) shared
284
/// over UDP. Backend (rodio / cpal / AVAudioEngine / AAudio) lives dll-side.
285
pub mod audio;
286
/// Biometric-auth POD types — `BiometricKind` + `BiometricResult` + `BiometricPrompt`.
287
///
288
/// Stateful manager lives in `azul_layout::managers::biometric`.
289
pub mod biometric;
290
/// Camera-capture POD types — `CaptureStreamId` + `CameraConfig` +
291
/// `CameraFacing` + `StreamState` + … .
292
///
293
/// The stateful `CameraStream` /
294
/// `CameraManager` (which own the shared `ImageRef` texture) live in
295
/// `azul_layout::managers::camera`.
296
pub mod camera;
297
/// Converts `CssPropertyCache` into compact three-tier numeric cache.
298
pub mod compact;
299
/// Linear-time DOM diffing for incremental updates.
300
pub mod diff;
301
/// DOM construction: `Dom`, `NodeData`, `NodeType`, and the CSS-in-Rust API.
302
pub mod dom;
303
/// Drag context for text selection, scrollbar, node, and window drags.
304
pub mod drag;
305
/// Event filtering: mouse, keyboard, window, and synthetic events.
306
pub mod events;
307
/// Gamepad POD types — `GamepadId` + `GamepadButton` + `GamepadAxis` +
308
/// `GamepadState`.
309
///
310
/// Stateful manager lives in `azul_layout::managers::gamepad`.
311
pub mod gamepad;
312
/// Geolocation POD types — `LocationFix` + `GeolocationProbeConfig`.
313
///
314
/// Stateful manager lives in `azul_layout::managers::geolocation`.
315
pub mod geolocation;
316
/// Logical and physical coordinate types (`LogicalSize`, `PhysicalPosition`, etc.).
317
pub mod geom;
318
/// OpenGL context wrappers, shader compilation, and texture cache.
319
///
320
pub mod gl;
321
/// FXAA (Fast Approximate Anti-Aliasing) shader.
322
pub mod gl_fxaa;
323
/// OpenGL constants (GL 1.1 through GL 4.x).
324
pub mod glconst;
325
/// GPU value cache for CSS transforms and opacity.
326
pub mod gpu;
327
/// Hit-test results (which DOM nodes are under the cursor) + the type-safe
328
/// hit-test tag system for compositor integration (merged from `hit_test_tag`).
329
///
330
pub mod hit_test;
331
/// Icon provider system for loading icons from fonts, images, or zip packs.
332
pub mod icon;
333
/// Arena-based node tree storage and hierarchy management.
334
pub mod id;
335
/// JSON value types for the C API (no serde dependency).
336
pub mod json;
337
/// System-keyring POD types — `KeyringRequest` + `KeyringResult`.
338
///
339
/// Stateful manager lives in `azul_layout::managers::keyring`.
340
pub mod keyring;
341
/// Runtime log filtering: per-level and per-category atomics.
342
///
343
/// Parsed from `AZ_LOG` but changeable while the process runs. Logging is gated HERE and
344
/// never by a cargo feature — see the module docs for the 2026-08-07 incident
345
/// that made a compile-time gate delete the one diagnosis that was needed.
346
pub mod log_filter;
347
/// Menu system: context menus, dropdown menus, and menu bars.
348
pub mod menu;
349
/// Paged-media primitives: the `FragmentationContext` (continuous vs. paged) and
350
/// `PageMargins`. The pagination/slicing logic lives in `azul_layout::solver3`.
351
pub mod paged;
352
/// SVG `d=""` path data parser.
353
pub mod path_parser;
354
/// CSS property cache for efficient per-node style resolution.
355
pub mod prop_cache;
356
/// Type-erased, ref-counted smart pointer with runtime borrow checking.
357
pub mod refany;
358
/// Resource management: font/image loading, caching, and garbage collection.
359
pub mod resources;
360
/// Screen-capture POD types — `ScreenCaptureSource` + `ScreenCaptureConfig`.
361
///
362
/// Symmetric to the camera surface (a "dumb widget" in
363
/// `azul_layout::widgets::screencap`); reuses `camera`'s capture status types.
364
pub mod screencap;
365
/// Text selection and cursor positioning for inline content.
366
pub mod selection;
367
/// Motion-sensor POD types — `SensorKind` + `SensorReading`.
368
///
369
/// Stateful manager lives in `azul_layout::managers::sensors`.
370
pub mod sensors;
371
/// CSS cascade: selector matching, specificity, and property inheritance.
372
pub mod style;
373
/// `StyledDom` — the result of applying CSS to a DOM tree (the CSSOM).
374
pub mod styled_dom;
375
/// SVG rendering, path tessellation, and geometric operations.
376
pub mod svg;
377
/// Timer, thread, and async task management.
378
pub mod task;
379
/// 3D transform matrix computation for CSS transforms.
380
pub mod transform;
381
/// Built-in user-agent default stylesheet.
382
pub mod ua_css;
383
/// Default font/text constants and small geometry helpers for layout.
384
pub mod ui_solver;
385
/// URL POD type (`Url`/`UrlParseError`); parsing gated behind the `url` feature.
386
pub mod url;
387
/// Video-playback POD types — `VideoConfig` (source URL + autoplay/loop).
388
///
389
/// Same "dumb widget" architecture (`azul_layout::widgets::video`); decoded
390
/// via vk-video into the shared GL texture.
391
pub mod video;
392
/// Window configuration, input state, and platform-specific options.
393
pub mod window;
394
/// XML and XHTML parsing for declarative UI definitions.
395
pub mod xml;
396

            
397
/// Ordered map alias used throughout `azul-core`.
398
///
399
/// This is backed by `BTreeMap` (not a hash map) because the `core` crate
400
/// supports `no_std`, where `HashMap` is unavailable. The webrender crates
401
/// define their own `FastHashMap` using `HashMap` + `FxHasher`.
402
pub type OrderedMap<T, U> = alloc::collections::BTreeMap<T, U>;
403
pub type FastBTreeSet<T> = alloc::collections::BTreeSet<T>;
404

            
405
#[cfg(test)]
406
#[allow(clippy::pedantic, clippy::nursery)]
407
mod autotest_generated {
408
    use alloc::{boxed::Box, string::String, vec::Vec};
409
    use core::{
410
        cell::Cell,
411
        hash::{Hash, Hasher},
412
    };
413

            
414
    use super::{hash::DefaultHasher, sync::OnceLock, FastBTreeSet, OrderedMap};
415

            
416
    // NOTE: `sync::OnceLock` and `hash::DefaultHasher` are *aliases*: with the
417
    // (default) `std` feature they re-export `std::sync::OnceLock` /
418
    // `std::hash::DefaultHasher`; without it they resolve to the hand-written
419
    // `no_std` shims in this file. Tests below are split accordingly:
420
    //   * un-gated  -> the API contract BOTH impls must satisfy,
421
    //   * cfg-gated -> behaviour that is specific to one impl.
422
    // `DefaultHasher::add` is private to the private `hash::nostd` module, so it
423
    // is not nameable from here; `write_u64` forwards to it 1:1 and is used as
424
    // the proxy for the numeric/overflow cases.
425

            
426
    // ---------------------------------------------------------------
427
    // OnceLock — constructor / getter invariants
428
    // ---------------------------------------------------------------
429

            
430
    #[test]
431
    fn oncelock_new_is_empty() {
432
        let cell: OnceLock<u32> = OnceLock::new();
433
        assert!(cell.get().is_none());
434
        // getter must stay pure: repeated reads never initialize
435
        assert!(cell.get().is_none());
436
    }
437

            
438
    #[test]
439
    fn oncelock_new_is_usable_in_const_context() {
440
        static CELL: OnceLock<u64> = OnceLock::new();
441
        assert!(CELL.get().is_none());
442
        assert_eq!(*CELL.get_or_init(|| u64::MAX), u64::MAX);
443
        assert_eq!(CELL.get().copied(), Some(u64::MAX));
444
    }
445

            
446
    #[test]
447
    fn oncelock_default_matches_new() {
448
        let cell: OnceLock<Vec<u8>> = OnceLock::default();
449
        assert!(cell.get().is_none());
450
    }
451

            
452
    #[test]
453
    fn oncelock_get_or_init_runs_closure_exactly_once() {
454
        let calls = Cell::new(0usize);
455
        let cell: OnceLock<u32> = OnceLock::new();
456

            
457
        assert_eq!(*cell.get_or_init(|| { calls.set(calls.get() + 1); 7 }), 7);
458
        // The second/third call must return the FIRST value and never re-run `f`.
459
        assert_eq!(*cell.get_or_init(|| { calls.set(calls.get() + 1); 9 }), 7);
460
        assert_eq!(*cell.get_or_init(|| { calls.set(calls.get() + 1); 11 }), 7);
461
        assert_eq!(calls.get(), 1);
462
        assert_eq!(cell.get().copied(), Some(7));
463
    }
464

            
465
    #[test]
466
    fn oncelock_get_and_get_or_init_alias_the_same_storage() {
467
        let cell: OnceLock<u32> = OnceLock::new();
468
        let a: *const u32 = cell.get_or_init(|| 1);
469
        let b: *const u32 = cell.get().expect("initialized");
470
        let c: *const u32 = cell.get_or_init(|| 2);
471
        // The value must never be moved/duplicated by a second init attempt.
472
        assert_eq!(a, b);
473
        assert_eq!(a, c);
474
    }
475

            
476
    #[test]
477
    fn oncelock_holds_zero_sized_type() {
478
        // ZST: `Option<()>` has no payload bits, so a naive impl can confuse
479
        // "initialized" with "None".
480
        let cell: OnceLock<()> = OnceLock::new();
481
        assert!(cell.get().is_none());
482
        cell.get_or_init(|| ());
483
        assert!(cell.get().is_some());
484
    }
485

            
486
    #[test]
487
    fn oncelock_holds_large_payload() {
488
        let cell: OnceLock<Box<[u8]>> = OnceLock::new();
489
        let v = cell.get_or_init(|| alloc::vec![0xABu8; 1 << 20].into_boxed_slice());
490
        assert_eq!(v.len(), 1 << 20);
491
        assert!(v.iter().all(|b| *b == 0xAB));
492
        assert_eq!(cell.get().map(|b| b.len()), Some(1 << 20));
493
    }
494

            
495
    #[test]
496
    fn oncelock_holds_nan_without_eq_confusion() {
497
        let cell: OnceLock<f64> = OnceLock::new();
498
        // `NaN != NaN`, so initialization must be tracked by state, not by
499
        // comparing the payload against a sentinel.
500
        assert!(cell.get_or_init(|| f64::NAN).is_nan());
501
        assert!(cell.get().is_some_and(|f| f.is_nan()));
502
        // A second init must not overwrite the stored NaN with 1.0.
503
        assert!(cell.get_or_init(|| 1.0).is_nan());
504
    }
505

            
506
    #[test]
507
    fn oncelock_clone_copies_state_not_aliases_it() {
508
        let cell: OnceLock<String> = OnceLock::new();
509

            
510
        let empty = cell.clone();
511
        assert!(empty.get().is_none());
512
        // Initializing the source must not retro-fill an earlier clone.
513
        cell.get_or_init(|| String::from("azul"));
514
        assert!(empty.get().is_none());
515

            
516
        let full = cell.clone();
517
        assert_eq!(full.get().map(String::as_str), Some("azul"));
518
        // Distinct storage: the clone must own its own allocation.
519
        assert_ne!(
520
            cell.get().expect("init") as *const String,
521
            full.get().expect("init") as *const String
522
        );
523
    }
524

            
525
    #[test]
526
    fn oncelock_eq_compares_contents() {
527
        let a: OnceLock<u32> = OnceLock::new();
528
        let b: OnceLock<u32> = OnceLock::new();
529
        assert_eq!(a, b); // both empty
530

            
531
        a.get_or_init(|| 5);
532
        assert_ne!(a, b); // Some(5) vs None
533

            
534
        b.get_or_init(|| 5);
535
        assert_eq!(a, b);
536

            
537
        let c: OnceLock<u32> = OnceLock::new();
538
        c.get_or_init(|| 6);
539
        assert_ne!(a, c);
540
    }
541

            
542
    #[cfg(feature = "std")]
543
    #[test]
544
    fn oncelock_concurrent_get_or_init_initializes_exactly_once() {
545
        use std::sync::{
546
            atomic::{AtomicUsize, Ordering},
547
            Barrier,
548
        };
549

            
550
        const THREADS: usize = 8;
551

            
552
        let cell: OnceLock<usize> = OnceLock::new();
553
        let inits = AtomicUsize::new(0);
554
        let gate = Barrier::new(THREADS);
555

            
556
        std::thread::scope(|s| {
557
            for id in 0..THREADS {
558
                let (cell, inits, gate) = (&cell, &inits, &gate);
559
                let _ = s.spawn(move || {
560
                    gate.wait(); // maximize contention on the CAS
561
                    let v = *cell.get_or_init(|| {
562
                        inits.fetch_add(1, Ordering::SeqCst);
563
                        id
564
                    });
565
                    // Every racer must observe the same winner.
566
                    assert_eq!(v, *cell.get().expect("initialized after get_or_init"));
567
                    v
568
                });
569
            }
570
        });
571

            
572
        assert_eq!(inits.load(Ordering::SeqCst), 1);
573
        let winner = cell.get().copied().expect("initialized");
574
        assert!(winner < THREADS);
575
    }
576

            
577
    // The `std` OnceLock documents that a panicking `f` leaves the cell
578
    // *uninitialized* (and re-initializable) rather than poisoned.
579
    //
580
    // The `no_std` shim in this file does NOT hold this property: it leaves
581
    // `state == BUSY`, so any later `get_or_init` spins forever. This test is
582
    // therefore std-gated on purpose — running it under `no_std` would hang the
583
    // test binary instead of failing it.
584
    #[cfg(feature = "std")]
585
    #[test]
586
    fn oncelock_panicking_initializer_leaves_cell_reusable() {
587
        let cell: OnceLock<u32> = OnceLock::new();
588

            
589
        let prev = std::panic::take_hook();
590
        std::panic::set_hook(Box::new(|_| {})); // keep the expected panic quiet
591
        let caught = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
592
            cell.get_or_init(|| panic!("initializer blew up"));
593
        }));
594
        std::panic::set_hook(prev);
595

            
596
        assert!(caught.is_err(), "the panic must propagate to the caller");
597
        assert!(cell.get().is_none(), "cell must remain uninitialized");
598
        assert_eq!(*cell.get_or_init(|| 42), 42, "cell must still be usable");
599
    }
600

            
601
    // ---------------------------------------------------------------
602
    // DefaultHasher — construction / determinism
603
    // ---------------------------------------------------------------
604

            
605
    fn hash_bytes(bytes: &[u8]) -> u64 {
606
        let mut h = DefaultHasher::new();
607
        h.write(bytes);
608
        h.finish()
609
    }
610

            
611
    fn hash_u64(word: u64) -> u64 {
612
        let mut h = DefaultHasher::new();
613
        h.write_u64(word);
614
        h.finish()
615
    }
616

            
617
    #[test]
618
    fn hasher_new_and_default_agree_and_are_deterministic() {
619
        assert_eq!(DefaultHasher::new().finish(), DefaultHasher::new().finish());
620
        assert_eq!(
621
            DefaultHasher::new().finish(),
622
            DefaultHasher::default().finish()
623
        );
624
    }
625

            
626
    #[test]
627
    fn hasher_is_deterministic_within_a_run() {
628
        assert_eq!(hash_bytes(b"azul"), hash_bytes(b"azul"));
629
        assert_eq!(hash_u64(0xDEAD_BEEF_CAFE_F00D), hash_u64(0xDEAD_BEEF_CAFE_F00D));
630
    }
631

            
632
    #[test]
633
    fn hasher_distinguishes_different_inputs() {
634
        assert_ne!(hash_bytes(b"a"), hash_bytes(b"b"));
635
        assert_ne!(hash_u64(0), hash_u64(1));
636
    }
637

            
638
    #[test]
639
    fn hasher_is_order_sensitive() {
640
        let mut a = DefaultHasher::new();
641
        a.write_u64(1);
642
        a.write_u64(2);
643

            
644
        let mut b = DefaultHasher::new();
645
        b.write_u64(2);
646
        b.write_u64(1);
647

            
648
        assert_ne!(a.finish(), b.finish());
649
    }
650

            
651
    #[test]
652
    fn hasher_finish_does_not_consume_state() {
653
        let mut h = DefaultHasher::new();
654
        h.write_u64(7);
655
        let first = h.finish();
656
        // `finish` must be a pure read: calling it twice returns the same value.
657
        assert_eq!(first, h.finish());
658
        // ...and further writes must keep mutating the same running state.
659
        h.write_u64(7);
660
        assert_ne!(first, h.finish());
661
    }
662

            
663
    // ---------------------------------------------------------------
664
    // DefaultHasher — numeric limits / overflow (exercises the private `add`
665
    // via its 1:1 forwarders `write_u64` / `write_usize` / `write_u8`)
666
    // ---------------------------------------------------------------
667

            
668
    #[test]
669
    fn hasher_handles_integer_limits_without_panicking() {
670
        // `add` does a `wrapping_mul`; a debug build must not overflow-panic.
671
        for word in [
672
            0u64,
673
            1,
674
            u64::MAX,
675
            u64::MAX - 1,
676
            i64::MIN as u64, // 0x8000_0000_0000_0000 — "negative" bit pattern
677
            i64::MAX as u64,
678
            -1i64 as u64,
679
            1 << 63,
680
            usize::MAX as u64,
681
        ] {
682
            let h = hash_u64(word);
683
            // deterministic + no panic; value itself is impl-defined
684
            assert_eq!(h, hash_u64(word));
685
        }
686

            
687
        let mut h = DefaultHasher::new();
688
        h.write_usize(usize::MAX);
689
        h.write_usize(0);
690
        h.write_u8(u8::MAX);
691
        h.write_u8(0);
692
        let _ = h.finish();
693
    }
694

            
695
    #[test]
696
    fn hasher_repeated_max_words_do_not_overflow_panic() {
697
        // Hammer the wrapping rotate/xor/multiply chain: every iteration
698
        // overflows u64. Must wrap, never panic (even in a debug profile).
699
        let mut h = DefaultHasher::new();
700
        for _ in 0..10_000 {
701
            h.write_u64(u64::MAX);
702
        }
703
        let a = h.finish();
704

            
705
        let mut h2 = DefaultHasher::new();
706
        for _ in 0..10_000 {
707
            h2.write_u64(u64::MAX);
708
        }
709
        assert_eq!(a, h2.finish(), "overflowing chain must stay deterministic");
710
    }
711

            
712
    #[test]
713
    fn hasher_zero_words_are_deterministic() {
714
        let mut h = DefaultHasher::new();
715
        for _ in 0..1_000 {
716
            h.write_u64(0);
717
        }
718
        let a = h.finish();
719

            
720
        let mut h2 = DefaultHasher::new();
721
        for _ in 0..1_000 {
722
            h2.write_u64(0);
723
        }
724
        assert_eq!(a, h2.finish());
725
    }
726

            
727
    // ---------------------------------------------------------------
728
    // DefaultHasher — `write` chunking / boundaries / unicode
729
    // ---------------------------------------------------------------
730

            
731
    #[test]
732
    fn hasher_write_empty_slice_does_not_panic() {
733
        let mut h = DefaultHasher::new();
734
        h.write(&[]);
735
        h.write(&[]);
736
        let a = h.finish();
737

            
738
        let mut h2 = DefaultHasher::new();
739
        h2.write(&[]);
740
        h2.write(&[]);
741
        assert_eq!(a, h2.finish());
742
    }
743

            
744
    #[test]
745
    fn hasher_write_covers_every_chunk_boundary() {
746
        // The `no_std` impl walks `chunks(8)` and zero-pads the tail; lengths
747
        // 0..=24 cover empty, short, exact-multiple and ragged-tail cases.
748
        let data: Vec<u8> = (0u8..=24).collect();
749
        for len in 0..=24usize {
750
            let slice = &data[..len];
751
            assert_eq!(hash_bytes(slice), hash_bytes(slice), "len {len}");
752
        }
753
        // A short slice must not collide with the same slice explicitly padded
754
        // out past the next 8-byte chunk boundary.
755
        assert_ne!(hash_bytes(&[1u8]), hash_bytes(&[1u8, 0, 0, 0, 0, 0, 0, 0, 0]));
756
    }
757

            
758
    // `write` must not swallow a trailing zero byte: `[1]` and `[1, 0]` are
759
    // different inputs and must hash differently.
760
    //
761
    // The `no_std` shim FAILS this: it zero-pads the final `chunks(8)` chunk
762
    // and mixes in no length, so `[1]` and `[1, 0]` both become the word
763
    // `0x0000_0000_0000_0001` — a guaranteed collision for every pair of byte
764
    // strings differing only in trailing zeros. Kept as a live assertion for
765
    // the (default) `std` build and `ignore`d rather than weakened under
766
    // `no_std`; see the autotest report.
767
    #[cfg_attr(
768
        not(feature = "std"),
769
        ignore = "no_std DefaultHasher zero-pads without length mixing: hash([1]) == hash([1, 0])"
770
    )]
771
    #[test]
772
    fn hasher_write_does_not_swallow_trailing_zero_bytes() {
773
        assert_ne!(hash_bytes(&[1u8]), hash_bytes(&[1u8, 0]));
774
        assert_ne!(hash_bytes(b"az"), hash_bytes(b"az\0"));
775
        assert_ne!(hash_bytes(&[]), hash_bytes(&[0u8]));
776
    }
777

            
778
    #[test]
779
    fn hasher_handles_huge_input() {
780
        let big: Vec<u8> = (0..(1 << 16)).map(|i| (i % 251) as u8).collect();
781
        let a = hash_bytes(&big);
782
        assert_eq!(a, hash_bytes(&big));
783

            
784
        // A single flipped byte in the middle must change the digest.
785
        let mut flipped = big.clone();
786
        flipped[1 << 15] ^= 0xFF;
787
        assert_ne!(a, hash_bytes(&flipped));
788
    }
789

            
790
    #[test]
791
    fn hasher_handles_unicode_and_nul_bytes() {
792
        for s in [
793
            "",
794
            "\u{0}",
795
            "ascii",
796
            "héllo wörld",
797
            "日本語テキスト",
798
            "🦀🔥👨‍👩‍👧‍👦",
799
            "a\u{0}b",
800
            "\u{FEFF}bom",
801
            "\u{10FFFF}",
802
        ] {
803
            let mut h = DefaultHasher::new();
804
            s.hash(&mut h);
805
            let a = h.finish();
806

            
807
            let mut h2 = DefaultHasher::new();
808
            s.hash(&mut h2);
809
            assert_eq!(a, h2.finish(), "unstable hash for {s:?}");
810
        }
811

            
812
        // Interior NUL must not truncate the input (C-string style bug).
813
        let mut a = DefaultHasher::new();
814
        "a\u{0}b".hash(&mut a);
815
        let mut b = DefaultHasher::new();
816
        "a".hash(&mut b);
817
        assert_ne!(a.finish(), b.finish());
818
    }
819

            
820
    #[test]
821
    fn hasher_respects_eq_hash_contract_for_std_types() {
822
        fn digest<T: Hash>(t: &T) -> u64 {
823
            let mut h = DefaultHasher::new();
824
            t.hash(&mut h);
825
            h.finish()
826
        }
827

            
828
        // Equal values must hash equal.
829
        assert_eq!(digest(&String::from("x")), digest(&String::from("x")));
830
        assert_eq!(digest(&alloc::vec![1u64, 2, 3]), digest(&alloc::vec![1u64, 2, 3]));
831
        assert_eq!(digest(&(1u8, "a")), digest(&(1u8, "a")));
832

            
833
        // Length must be part of the digest: [1,2] vs [1,2,0] must differ...
834
        assert_ne!(digest(&alloc::vec![1u8, 2]), digest(&alloc::vec![1u8, 2, 0]));
835
        // ...and prefix-concatenation must not collide ("ab" vs "a"+"b" fields).
836
        assert_ne!(digest(&("ab", "")), digest(&("a", "b")));
837
    }
838

            
839
    // ---------------------------------------------------------------
840
    // `no_std` shim internals: exact FxHasher-style formula of the private
841
    // `add`, reached through its 1:1 forwarder `write_u64`.
842
    // ---------------------------------------------------------------
843

            
844
    #[cfg(not(feature = "std"))]
845
    #[test]
846
    fn nostd_hasher_add_matches_documented_formula() {
847
        const SEED: u64 = 0x51_7c_c1_b7_27_22_0a_95;
848
        const ROTATE: u32 = 5;
849

            
850
        fn expect(words: &[u64]) -> u64 {
851
            words
852
                .iter()
853
                .fold(0u64, |h, w| (h.rotate_left(ROTATE) ^ w).wrapping_mul(SEED))
854
        }
855

            
856
        assert_eq!(DefaultHasher::new().finish(), 0, "fresh state must be 0");
857

            
858
        for words in [
859
            &[0u64][..],
860
            &[1][..],
861
            &[u64::MAX][..],
862
            &[i64::MIN as u64][..],
863
            &[u64::MAX, u64::MAX, u64::MAX][..],
864
            &[0, u64::MAX, 0, 1 << 63][..],
865
        ] {
866
            let mut h = DefaultHasher::new();
867
            for w in words {
868
                h.write_u64(*w);
869
            }
870
            assert_eq!(h.finish(), expect(words), "formula drift for {words:?}");
871
        }
872
    }
873

            
874
    #[cfg(not(feature = "std"))]
875
    #[test]
876
    fn nostd_hasher_zero_is_an_absorbing_state() {
877
        // Documented FxHasher weakness, asserted so it stays *intentional*:
878
        // from a zero state, hashing zero words keeps the state at zero
879
        // ((0.rotate_left(5) ^ 0) * SEED == 0).
880
        let mut h = DefaultHasher::new();
881
        for _ in 0..64 {
882
            h.write_u64(0);
883
        }
884
        assert_eq!(h.finish(), 0);
885

            
886
        // Leading zero words are therefore invisible: hash([0, x]) == hash([x]).
887
        let mut a = DefaultHasher::new();
888
        a.write_u64(0);
889
        a.write_u64(0xABCD);
890
        let mut b = DefaultHasher::new();
891
        b.write_u64(0xABCD);
892
        assert_eq!(a.finish(), b.finish());
893
    }
894

            
895
    #[cfg(not(feature = "std"))]
896
    #[test]
897
    fn nostd_hasher_write_empty_slice_is_a_noop() {
898
        // `chunks(8)` over an empty slice yields nothing, so the state is untouched.
899
        let mut h = DefaultHasher::new();
900
        h.write(b"seed");
901
        let before = h.finish();
902
        h.write(&[]);
903
        assert_eq!(h.finish(), before);
904
    }
905

            
906
    // ---------------------------------------------------------------
907
    // Public type aliases — ordering / dedup invariants
908
    // ---------------------------------------------------------------
909

            
910
    #[test]
911
    fn ordered_map_iterates_in_key_order() {
912
        let mut m: OrderedMap<i64, &str> = OrderedMap::new();
913
        for (k, v) in [(i64::MAX, "max"), (0, "zero"), (i64::MIN, "min"), (-1, "neg")] {
914
            let _ = m.insert(k, v);
915
        }
916
        let keys: Vec<i64> = m.keys().copied().collect();
917
        assert_eq!(keys, alloc::vec![i64::MIN, -1, 0, i64::MAX]);
918

            
919
        // Re-insert must overwrite, not duplicate.
920
        assert_eq!(m.insert(0, "zero2"), Some("zero"));
921
        assert_eq!(m.len(), 4);
922
        assert_eq!(m.get(&0).copied(), Some("zero2"));
923
    }
924

            
925
    #[test]
926
    fn fast_btree_set_dedups_and_orders() {
927
        let mut s: FastBTreeSet<u32> = FastBTreeSet::new();
928
        assert!(s.insert(u32::MAX));
929
        assert!(s.insert(0));
930
        assert!(!s.insert(0), "duplicate insert must report false");
931
        assert!(s.insert(1));
932

            
933
        assert_eq!(s.len(), 3);
934
        assert_eq!(s.iter().copied().collect::<Vec<u32>>(), alloc::vec![0, 1, u32::MAX]);
935
        assert!(s.contains(&u32::MAX));
936
        assert!(!s.contains(&2));
937
    }
938
}