Skip to main content

bun_css/
generics.rs

1//! Generic trait-based dispatch for CSS value operations.
2//!
3//! The Zig original (`generics.zig`) uses `@typeInfo`/`@hasDecl`/`@field` comptime
4//! reflection to derive `eql`, `hash`, `deepClone`, `toCss`, `parse`, etc. across
5//! every CSS value type. Per PORTING.md §Comptime reflection, that has no Rust
6//! equivalent — the port defines a trait per protocol, provides blanket impls for
7//! the structural cases (Option/Box/slice/Vec/Vec/SmallList/primitives), and
8//! per-struct/-enum impls come from the `bun_css_derive` proc-macros
9//! (`#[derive(ToCss, DeepClone, CssEql, CssHash)]` etc.).
10//!
11//! Free functions with the original names are kept as thin trait-method wrappers
12//! so call sites in sibling files port 1:1.
13
14use core::cmp::Ordering;
15
16use bun_alloc::Arena; // bumpalo::Bump re-export
17use bun_collections::VecExt;
18// Zig `std.hash.Wyhash` (iterative) → `bun_wyhash::Wyhash` (the final4 variant
19// matching upstream `std.hash.Wyhash`; NOT `Wyhash11`, which is a legacy v0.11
20// variant kept only for on-disk lockfile compat — different digest).
21// Re-exported `pub` so `#[derive(CssHash)]` (in `bun_css_derive`) can name the
22// hasher type as `::bun_css::generics::Wyhash` without depending on `bun_wyhash`
23// directly.
24pub use bun_wyhash::Wyhash;
25
26use crate::SmallList;
27use crate::css_parser as css;
28use crate::css_parser::CssResult;
29use crate::css_parser::{Parser, ParserOptions};
30use crate::printer::Printer;
31use crate::values::angle::Angle;
32use crate::values::ident::{
33    CustomIdent, CustomIdentFns, DashedIdent, DashedIdentFns, Ident, IdentFns,
34};
35use crate::values::number::{CSSInteger, CSSIntegerFns, CSSNumber, CSSNumberFns};
36use crate::values::rect::Rect;
37use crate::values::size::Size2D;
38use crate::{PrintErr, VendorPrefix};
39#[cfg(bao_nightly)]
40type ParseListVec<T> = Vec<T>;
41#[cfg(not(bao_nightly))]
42type ParseListVec<T> = ::allocator_api2::vec::Vec<T, ::bun_alloc::core_alloc::Global>;
43
44// `ArrayList(T)` in the Zig is `std.ArrayListUnmanaged(T)` fed the parser arena.
45// In this AST crate that maps to `bun_alloc::ArenaVec<'bump, T>`.
46pub type ArrayList<'bump, T> = bun_alloc::ArenaVec<'bump, T>;
47
48// ───────────────────────────────────────────────────────────────────────────────
49// DeepClone
50// ───────────────────────────────────────────────────────────────────────────────
51
52/// Arena-aware deep clone. Equivalent of Zig's `deepClone(T, *const T, Allocator) T`.
53///
54/// Per-struct/-enum impls come from `#[derive(DeepClone)]`;
55/// the Zig `implementDeepClone` body is the spec for that derive (field-wise /
56/// variant-wise recursion).
57pub trait DeepClone<'bump>: Sized {
58    fn deep_clone(&self, bump: &'bump Arena) -> Self;
59}
60
61/// `#[derive(DeepClone)]` — field-wise / variant-wise port of Zig's
62/// `css.implementDeepClone`. See `src/css_derive/lib.rs` for the expansion
63/// rules. Re-exported here so `use crate::generics::DeepClone;` brings both
64/// the trait and the derive into scope (same-name trait+derive is the std
65/// idiom, cf. `Clone`).
66pub use bun_css_derive::DeepClone;
67
68#[inline]
69pub fn implement_deep_clone<'bump, T: DeepClone<'bump>>(this: &T, bump: &'bump Arena) -> T {
70    // TODO(port): Zig `implementDeepClone` is comptime field/variant reflection.
71    // In Rust this is the body of `#[derive(DeepClone)]`; the free fn just
72    // forwards to the trait so existing callers keep working.
73    this.deep_clone(bump)
74}
75
76// Alias: in Zig `deepClone` (structural type-dispatch entry) and
77// `implementDeepClone` (field-reflection body) are distinct, but in Rust both
78// collapse to `T::deep_clone` because the structural dispatch lives in the
79// blanket impls below and the field-reflection lives in `#[derive(DeepClone)]`.
80// Kept as a re-export so generated code (`properties_generated.rs`) and
81// hand-written callers can use either name.
82pub use implement_deep_clone as deep_clone;
83
84// Blanket impls covering the structural cases the Zig switch handled inline.
85
86impl<'bump, T: DeepClone<'bump>> DeepClone<'bump> for Option<T> {
87    #[inline]
88    fn deep_clone(&self, bump: &'bump Arena) -> Self {
89        self.as_ref().map(|v| v.deep_clone(bump))
90    }
91}
92
93impl<'bump, T: DeepClone<'bump>> DeepClone<'bump> for &'bump T {
94    #[inline]
95    fn deep_clone(&self, bump: &'bump Arena) -> Self {
96        // Zig: `bun.create(arena, TT, deepClone(TT, this.*, arena))`
97        bump.alloc((**self).deep_clone(bump))
98    }
99}
100
101impl<'bump, T: DeepClone<'bump>> DeepClone<'bump> for &'bump [T] {
102    fn deep_clone(&self, bump: &'bump Arena) -> Self {
103        // Zig: alloc slice, then memcpy if simple-copy else element-wise deepClone.
104        // PERF(port): Zig fast-paths `isSimpleCopyType` with @memcpy — profile if hot
105        // (specialization would let `T: Copy` use `alloc_slice_copy`).
106        bump.alloc_slice_fill_iter(self.iter().map(|e| e.deep_clone(bump)))
107    }
108}
109
110impl<'bump, T: DeepClone<'bump>> DeepClone<'bump> for ArrayList<'bump, T> {
111    #[inline]
112    fn deep_clone(&self, bump: &'bump Arena) -> Self {
113        // Zig: `css.deepClone(T, arena, this)` → alloc capacity, element-wise deepClone.
114        // PERF(port): Zig fast-paths simple-copy types with @memcpy — profile if hot.
115        let mut out = ArrayList::with_capacity_in(self.len(), bump);
116        for item in self.iter() {
117            out.push(item.deep_clone(bump));
118        }
119        out
120    }
121}
122
123#[cfg(not(bao_nightly))]
124impl<'bump, T: DeepClone<'bump>> DeepClone<'bump> for ::allocator_api2::vec::Vec<T, ::bun_alloc::core_alloc::Global> {
125    #[inline]
126    fn deep_clone(&self, bump: &'bump Arena) -> Self {
127        self.deep_clone_with(|e| e.deep_clone(bump))
128    }
129}
130
131#[cfg(bao_nightly)]
132impl<'bump, T: DeepClone<'bump>> DeepClone<'bump> for Vec<T> {
133    #[inline]
134    fn deep_clone(&self, bump: &'bump Arena) -> Self {
135        // `Vec::deep_clone_with` takes a per-element closure so the arena
136        // lifetime carried by *this* trait's `deep_clone` threads through.
137        self.deep_clone_with(|e| e.deep_clone(bump))
138    }
139}
140
141impl<'bump, T: DeepClone<'bump>, const N: usize> DeepClone<'bump> for SmallList<T, N> {
142    #[inline]
143    fn deep_clone(&self, bump: &'bump Arena) -> Self {
144        let mut ret = SmallList::<T, N>::init_capacity(self.len());
145        for item in self.slice() {
146            ret.append(item.deep_clone(bump));
147        }
148        ret
149    }
150}
151
152// "Simple copy types" + arena-borrowed strings: clone is identity.
153macro_rules! deep_clone_copy {
154    ($($t:ty),* $(,)?) => {$(
155        impl<'bump> DeepClone<'bump> for $t {
156            #[inline]
157            fn deep_clone(&self, _bump: &'bump Arena) -> Self { *self }
158        }
159    )*};
160}
161// `u8` is intentionally omitted: a `DeepClone for u8` impl would make the
162// generic `&'bump [T]` impl below overlap the explicit `&'bump [u8]` impl
163// (Rust has no stable specialization). Bytes only appear as `[u8]` slices in
164// the CSS AST, never as standalone values.
165deep_clone_copy!(f32, f64, i32, u32, i64, u64, usize, isize, u16, bool);
166
167impl<'bump> DeepClone<'bump> for &'bump [u8] {
168    #[inline]
169    fn deep_clone(&self, _bump: &'bump Arena) -> Self {
170        // Strings in the CSS parser are always arena allocated
171        // So it is safe to skip const strings as they will never be mutated
172        *self
173    }
174}
175
176impl<'bump> DeepClone<'bump> for &'bump str {
177    #[inline]
178    fn deep_clone(&self, _bump: &'bump Arena) -> Self {
179        // Same arena-borrowed-string rule as `&[u8]` above.
180        *self
181    }
182}
183
184impl<'bump, T: DeepClone<'bump>> DeepClone<'bump> for Box<T> {
185    #[inline]
186    fn deep_clone(&self, bump: &'bump Arena) -> Self {
187        Box::new((**self).deep_clone(bump))
188    }
189}
190
191impl<'bump> DeepClone<'bump> for bun_ast::Loc {
192    #[inline]
193    fn deep_clone(&self, _bump: &'bump Arena) -> Self {
194        *self
195    }
196}
197
198// ───────────────────────────────────────────────────────────────────────────────
199// Eql
200// ───────────────────────────────────────────────────────────────────────────────
201
202/// `lhs.eql(&rhs)` for CSS types. This is the equivalent of doing
203/// `#[derive(PartialEq)]` in Rust — and most impls could be exactly that.
204/// Kept as a separate trait because some CSS types want structural
205/// equality that differs from `PartialEq` (e.g. `VendorPrefix`, idents).
206pub trait CssEql {
207    fn eql(&self, other: &Self) -> bool;
208}
209
210/// `#[derive(CssEql)]` — field-wise / variant-wise port of Zig's
211/// `css.implementEql`. See `src/css_derive/lib.rs` for the expansion rules.
212/// Re-exported here so `use crate::generics::CssEql;` brings both trait and
213/// derive into scope (same-name idiom, cf. `Clone`).
214pub use bun_css_derive::CssEql;
215
216#[inline]
217pub fn implement_eql<T: CssEql>(this: &T, other: &T) -> bool {
218    // TODO(port): Zig `implementEql` is comptime field/variant reflection ==
219    // the body of `#[derive(CssEql)]`. Free fn forwards to trait.
220    this.eql(other)
221}
222
223#[inline]
224pub(crate) fn eql<T: CssEql>(lhs: &T, rhs: &T) -> bool {
225    lhs.eql(rhs)
226}
227
228pub(crate) fn eql_list<T: CssEql>(lhs: &ArrayList<'_, T>, rhs: &ArrayList<'_, T>) -> bool {
229    if lhs.len() != rhs.len() {
230        return false;
231    }
232    debug_assert_eq!(lhs.len(), rhs.len());
233    for (left, right) in lhs.iter().zip(rhs.iter()) {
234        if !left.eql(right) {
235            return false;
236        }
237    }
238    true
239}
240
241// Blanket / base impls.
242
243impl<T: CssEql> CssEql for Option<T> {
244    #[inline]
245    fn eql(&self, other: &Self) -> bool {
246        match (self, other) {
247            (None, None) => true,
248            (Some(a), Some(b)) => a.eql(b),
249            _ => false,
250        }
251    }
252}
253
254impl<T: CssEql + ?Sized> CssEql for &T {
255    #[inline]
256    fn eql(&self, other: &Self) -> bool {
257        (**self).eql(&**other)
258    }
259}
260
261impl<T: CssEql> CssEql for [T] {
262    fn eql(&self, other: &Self) -> bool {
263        if self.len() != other.len() {
264            return false;
265        }
266        for (a, b) in self.iter().zip(other.iter()) {
267            if !a.eql(b) {
268                return false;
269            }
270        }
271        true
272    }
273}
274
275impl<'bump, T: CssEql> CssEql for ArrayList<'bump, T> {
276    #[inline]
277    fn eql(&self, other: &Self) -> bool {
278        eql_list(self, other)
279    }
280}
281
282#[cfg(not(bao_nightly))]
283impl<T: CssEql> CssEql for ::allocator_api2::vec::Vec<T, ::bun_alloc::core_alloc::Global> {
284    #[inline]
285    fn eql(&self, other: &Self) -> bool {
286        self.slice_const().eql(other.slice_const())
287    }
288}
289
290#[cfg(bao_nightly)]
291impl<T: CssEql> CssEql for Vec<T> {
292    #[inline]
293    fn eql(&self, other: &Self) -> bool {
294        self.slice_const().eql(other.slice_const())
295    }
296}
297
298impl<T: CssEql, const N: usize> CssEql for SmallList<T, N> {
299    #[inline]
300    fn eql(&self, other: &Self) -> bool {
301        self.slice().eql(other.slice())
302    }
303}
304
305macro_rules! eql_simple {
306    ($($t:ty),* $(,)?) => {$(
307        impl CssEql for $t {
308            #[inline]
309            fn eql(&self, other: &Self) -> bool { *self == *other }
310        }
311    )*};
312}
313// `u8` omitted to avoid `[T]`/`[u8]` overlap — see deep_clone_copy! note.
314eql_simple!(f32, f64, i32, u32, i64, u64, usize, isize, u16, bool);
315
316/// Stamp `impl CssEql for $T` forwarding to `PartialEq::eq`.
317///
318/// Exported sibling of `eql_simple!` for crate-defined types whose
319/// inherent `pub fn eql(&self, other) { self == other }` was a pure `PartialEq`
320/// forwarder (Zig `css.implementEql(@This())` leakage).
321///
322/// Unlike `#[derive(CssEql)]` (field-wise `.eql()` walk), this does **not**
323/// require every field type to itself impl `CssEql`; it bridges
324/// `PartialEq` → `CssEql` wholesale. Prefer `#[derive(CssEql)]` only when a
325/// field's `CssEql` is intentionally *different* from its `PartialEq` (e.g.
326/// `Ident`, `Url`).
327///
328/// REJECTED alternative: `impl<T: PartialEq> CssEql for T {}` blanket — would
329/// overlap the existing `Option<T>`/`Vec<T>`/`[T]`/`Box<T>` blanket impls
330/// above (coherence).
331#[macro_export]
332macro_rules! css_eql_partialeq {
333    ($($t:ty),+ $(,)?) => {$(
334        impl $crate::generics::CssEql for $t {
335            #[inline]
336            fn eql(&self, other: &Self) -> bool { self == other }
337        }
338    )+};
339}
340// upstream 475b885d6e: `pub use css_eql_partialeq;` had no reader (every use
341// is textual `crate::css_eql_partialeq!`) — deleted.
342
343impl CssEql for [u8] {
344    #[inline]
345    fn eql(&self, other: &Self) -> bool {
346        bun_core::eql(self, other)
347    }
348}
349
350impl CssEql for str {
351    #[inline]
352    fn eql(&self, other: &Self) -> bool {
353        bun_core::eql(self.as_bytes(), other.as_bytes())
354    }
355}
356
357impl<T: CssEql, const N: usize> CssEql for [T; N] {
358    #[inline]
359    fn eql(&self, other: &Self) -> bool {
360        // Zig: element-wise eql (length is `N` on both sides by type).
361        for (a, b) in self.iter().zip(other.iter()) {
362            if !a.eql(b) {
363                return false;
364            }
365        }
366        true
367    }
368}
369
370impl<T: CssEql + ?Sized> CssEql for Box<T> {
371    #[inline]
372    fn eql(&self, other: &Self) -> bool {
373        (**self).eql(&**other)
374    }
375}
376
377impl CssEql for VendorPrefix {
378    #[inline]
379    fn eql(&self, other: &Self) -> bool {
380        // Zig: `VendorPrefix.eql` is bitwise compare of the packed struct.
381        *self == *other
382    }
383}
384
385impl CssEql for bun_ast::Loc {
386    #[inline]
387    fn eql(&self, other: &Self) -> bool {
388        self.start == other.start
389    }
390}
391
392impl CssEql for () {
393    #[inline]
394    fn eql(&self, _other: &Self) -> bool {
395        true
396    }
397}
398
399// CustomIdent/DashedIdent/Ident wrapper structs are hoisted as data-only stubs
400// in `crate::values::ident` (lib.rs); the full impls live in gated
401// `values/ident.rs` and supersede these on un-gate.
402mod ident_eql {
403    use super::CssEql;
404    use crate::values::ident::{CustomIdent, DashedIdent, Ident};
405
406    macro_rules! ident_eql_impl {
407        ($($t:ty),* $(,)?) => {$(
408            impl CssEql for $t {
409                #[inline]
410                fn eql(&self, other: &Self) -> bool {
411                    // `.v()` borrows the parser arena (see `crate::arena_str`).
412                    bun_core::eql(self.v(), other.v())
413                }
414            }
415        )*};
416    }
417    ident_eql_impl!(CustomIdent, DashedIdent, Ident);
418}
419
420// ───────────────────────────────────────────────────────────────────────────────
421// Bridge inherent eql/hash/deep_clone → trait impls
422//
423// Many CSS value types carry hand-rolled inherent `eql`/`hash`/`deep_clone`
424// (ported verbatim from the Zig `implementEql`/`implementHash`/
425// `implementDeepClone` bodies — usually because a field is a raw `*const [u8]`
426// arena slice that the derive can't see through). The `#[derive(CssEql/…)]`
427// expansion on *containing* types dispatches via UFCS trait paths, so those
428// inherent methods alone don't satisfy the bound. These thin forwarding impls
429// close the gap without duplicating logic.
430mod inherent_bridge {
431    use super::{Arena, CssEql, CssHash, DeepClone, Wyhash};
432
433    macro_rules! bridge_eql {
434        ($($t:ty),* $(,)?) => {$(
435            impl CssEql for $t {
436                #[inline]
437                fn eql(&self, other: &Self) -> bool { <$t>::eql(self, other) }
438            }
439        )*};
440    }
441    macro_rules! bridge_hash {
442        ($($t:ty),* $(,)?) => {$(
443            impl CssHash for $t {
444                #[inline]
445                fn hash(&self, hasher: &mut Wyhash) { <$t>::hash(self, hasher) }
446            }
447        )*};
448    }
449    macro_rules! bridge_deep_clone {
450        ($($t:ty),* $(,)?) => {$(
451            impl<'bump> DeepClone<'bump> for $t {
452                #[inline]
453                fn deep_clone(&self, bump: &'bump Arena) -> Self { <$t>::deep_clone(self, bump) }
454            }
455        )*};
456    }
457    macro_rules! bridge_deep_clone_copy {
458        ($($t:ty),* $(,)?) => {$(
459            impl<'bump> DeepClone<'bump> for $t {
460                #[inline]
461                fn deep_clone(&self, _bump: &'bump Arena) -> Self { Clone::clone(self) }
462            }
463        )*};
464    }
465
466    use crate::values::ident::{CustomIdent, DashedIdent, DashedIdentReference, Ident};
467    bridge_hash!(CustomIdent, DashedIdent, Ident, DashedIdentReference);
468    bridge_eql!(DashedIdentReference);
469    bridge_deep_clone!(CustomIdent, DashedIdent, Ident, DashedIdentReference);
470
471    use crate::values::color::CssColor;
472    bridge_hash!(CssColor);
473    bridge_deep_clone!(CssColor);
474
475    use crate::values::url::Url;
476    bridge_eql!(Url);
477    bridge_hash!(Url);
478    bridge_deep_clone!(Url);
479
480    use crate::properties::animation::AnimationName;
481    bridge_eql!(AnimationName);
482    bridge_hash!(AnimationName);
483    bridge_deep_clone!(AnimationName);
484
485    use crate::properties::custom::UAEnvironmentVariable;
486    impl CssEql for UAEnvironmentVariable {
487        #[inline]
488        fn eql(&self, other: &Self) -> bool {
489            UAEnvironmentVariable::eql(*self, *other)
490        }
491    }
492    // CssHash — via #[derive(CssHash)] on the enum (properties/custom.rs).
493    bridge_deep_clone_copy!(UAEnvironmentVariable);
494
495    // `Direction` is re-exported from `properties::text` — bridged below as `TextDirection`.
496    use crate::selectors::parser::{ViewTransitionPartName, WebKitScrollbarPseudoElement};
497    impl CssEql for WebKitScrollbarPseudoElement {
498        #[inline]
499        fn eql(&self, other: &Self) -> bool {
500            WebKitScrollbarPseudoElement::eql(*self, *other)
501        }
502    }
503    bridge_eql!(ViewTransitionPartName);
504    // CssHash for WebKitScrollbarPseudoElement — via #[derive(CssHash)] on the enum.
505    bridge_hash!(ViewTransitionPartName);
506    bridge_deep_clone_copy!(WebKitScrollbarPseudoElement, ViewTransitionPartName);
507
508    // ───────────────────────────────────────────────────────────────────────
509    // Property value-type bridges — `Property::deep_clone`/`eql` dispatch via
510    // `css::generic::deep_clone`/`eql` (trait bounds), but most leaf types
511    // only carry inherent methods or `derive(Clone, PartialEq)`. Bridge them.
512    // ───────────────────────────────────────────────────────────────────────
513
514    /// Forward `CssEql` to `PartialEq`.
515    macro_rules! bridge_eql_partialeq {
516        ($($t:ty),* $(,)?) => {$(
517            impl CssEql for $t {
518                #[inline]
519                fn eql(&self, other: &Self) -> bool { PartialEq::eq(self, other) }
520            }
521        )*};
522    }
523    /// Forward `IsCompatible` to inherent `is_compatible`.
524    macro_rules! bridge_is_compatible {
525        ($($t:ty),* $(,)?) => {$(
526            impl super::IsCompatible for $t {
527                #[inline]
528                fn is_compatible(&self, browsers: &crate::targets::Browsers) -> bool {
529                    <$t>::is_compatible(self, browsers)
530                }
531            }
532        )*};
533    }
534    /// Combo: `Clone` → `DeepClone`, `PartialEq` → `CssEql`.
535    macro_rules! bridge_clone_partialeq {
536        ($($t:ty),* $(,)?) => {
537            bridge_deep_clone_copy!($($t),*);
538            bridge_eql_partialeq!($($t),*);
539        };
540    }
541
542    // ── values/ ──
543    use crate::values::length::{Length, LengthOrNumber, LengthPercentageOrAuto};
544    bridge_clone_partialeq!(LengthPercentageOrAuto, LengthOrNumber, Length);
545    bridge_is_compatible!(LengthPercentageOrAuto, LengthOrNumber, Length);
546
547    use crate::values::easing::EasingFunction;
548    bridge_clone_partialeq!(EasingFunction);
549
550    use crate::values::alpha::AlphaValue;
551    bridge_clone_partialeq!(AlphaValue);
552
553    use crate::values::image::Image;
554    bridge_eql!(Image);
555    bridge_deep_clone!(Image);
556    bridge_is_compatible!(Image);
557    // Gradient payload structs only carry inherent `deep_clone`; bridge them so
558    // `#[derive(css::DeepClone)]` on `Gradient` / `WebKitGradient` (UFCS trait
559    // dispatch) resolves.
560    use crate::values::gradient::{
561        ConicGradient, LinearGradient, RadialGradient, WebKitGradientLinear, WebKitGradientRadial,
562    };
563    bridge_deep_clone!(
564        LinearGradient,
565        RadialGradient,
566        ConicGradient,
567        WebKitGradientLinear,
568        WebKitGradientRadial
569    );
570
571    use crate::values::position::{
572        HorizontalPositionKeyword, Position, PositionComponent, VerticalPositionKeyword,
573    };
574    bridge_clone_partialeq!(Position, HorizontalPositionKeyword, VerticalPositionKeyword);
575    // `PositionComponent<S>` is generic, so `bridge_clone_partialeq!` (which
576    // takes concrete `ty`s) can't cover it — spell the trivial impls out.
577    impl<S: Clone + PartialEq> CssEql for PositionComponent<S> {
578        #[inline]
579        fn eql(&self, other: &Self) -> bool {
580            PartialEq::eq(self, other)
581        }
582    }
583    impl<'bump, S: Clone + PartialEq> DeepClone<'bump> for PositionComponent<S> {
584        #[inline]
585        fn deep_clone(&self, _bump: &'bump Arena) -> Self {
586            Clone::clone(self)
587        }
588    }
589
590    use crate::values::rect::Rect;
591    impl<T: PartialEq> CssEql for Rect<T> {
592        #[inline]
593        fn eql(&self, other: &Self) -> bool {
594            Rect::<T>::eql(self, other)
595        }
596    }
597    impl<'bump, T: Clone> DeepClone<'bump> for Rect<T> {
598        #[inline]
599        fn deep_clone(&self, bump: &'bump Arena) -> Self {
600            Rect::<T>::deep_clone(self, bump)
601        }
602    }
603
604    use crate::values::size::Size2D;
605    impl<T: Clone + PartialEq> CssEql for Size2D<T> {
606        #[inline]
607        fn eql(&self, other: &Self) -> bool {
608            Size2D::<T>::eql(self, other)
609        }
610    }
611    impl<'bump, T: Clone + PartialEq> DeepClone<'bump> for Size2D<T> {
612        #[inline]
613        fn deep_clone(&self, bump: &'bump Arena) -> Self {
614            Size2D::<T>::deep_clone(self, bump)
615        }
616    }
617
618    bridge_is_compatible!(CssColor);
619
620    // ── properties/border ──
621    use crate::properties::border::{
622        BorderBlockColor, BorderBlockStyle, BorderBlockWidth, BorderColor, BorderInlineColor,
623        BorderInlineStyle, BorderInlineWidth, BorderSideWidth, BorderStyle, BorderWidth,
624        GenericBorder, LineStyle,
625    };
626    bridge_eql_partialeq!(LineStyle);
627    bridge_deep_clone!(BorderSideWidth);
628    bridge_deep_clone_copy!(LineStyle);
629    impl super::IsCompatible for BorderSideWidth {
630        #[inline]
631        fn is_compatible(&self, browsers: &crate::targets::Browsers) -> bool {
632            BorderSideWidth::is_compatible(self, browsers)
633        }
634    }
635    impl super::IsCompatible for LineStyle {
636        #[inline]
637        fn is_compatible(&self, browsers: &crate::targets::Browsers) -> bool {
638            LineStyle::is_compatible(*self, browsers)
639        }
640    }
641    bridge_clone_partialeq!(
642        BorderColor,
643        BorderStyle,
644        BorderWidth,
645        BorderBlockColor,
646        BorderBlockStyle,
647        BorderBlockWidth,
648        BorderInlineColor,
649        BorderInlineStyle,
650        BorderInlineWidth,
651    );
652    impl<S: CssEql, const P: u8> CssEql for GenericBorder<S, P> {
653        #[inline]
654        fn eql(&self, other: &Self) -> bool {
655            self.width.eql(&other.width)
656                && self.style.eql(&other.style)
657                && self.color.eql(&other.color)
658        }
659    }
660    impl<'bump, S: DeepClone<'bump>, const P: u8> DeepClone<'bump> for GenericBorder<S, P> {
661        #[inline]
662        fn deep_clone(&self, bump: &'bump Arena) -> Self {
663            GenericBorder {
664                width: self.width.deep_clone(bump),
665                style: self.style.deep_clone(bump),
666                color: self.color.deep_clone(bump),
667            }
668        }
669    }
670
671    use crate::properties::outline::OutlineStyle;
672    bridge_clone_partialeq!(OutlineStyle);
673
674    use crate::properties::border_image::{
675        BorderImage, BorderImageRepeat, BorderImageSideWidth, BorderImageSlice,
676    };
677    // PORT NOTE: BorderImageRepeat/SideWidth/Slice carry inherent
678    // `deep_clone(&self, &Arena)` / `eql(&self, &Self)` (no `Clone`/`PartialEq`
679    // derives — see border_image.rs), so route through bridge_deep_clone/eql.
680    bridge_deep_clone!(BorderImageRepeat, BorderImageSideWidth, BorderImageSlice);
681    bridge_eql!(BorderImageRepeat, BorderImageSlice);
682    bridge_deep_clone!(BorderImage);
683    bridge_eql!(BorderImage);
684
685    use crate::properties::border_radius::BorderRadius;
686    // PORT NOTE: BorderRadius has inherent deep_clone/eql (Size2D<T> lacks
687    // Clone/PartialEq derives — see border_radius.rs PORT NOTE).
688    bridge_deep_clone!(BorderRadius);
689    bridge_eql!(BorderRadius);
690
691    // ── properties/background ──
692    use crate::properties::background::{
693        Background, BackgroundAttachment, BackgroundClip, BackgroundOrigin, BackgroundPosition,
694        BackgroundRepeat, BackgroundSize,
695    };
696    bridge_eql!(Background);
697    bridge_deep_clone!(Background, BackgroundSize, BackgroundPosition);
698    bridge_deep_clone_copy!(BackgroundRepeat);
699    bridge_clone_partialeq!(BackgroundAttachment, BackgroundClip, BackgroundOrigin);
700
701    // ── properties/align ──
702    use crate::properties::align::{
703        AlignContent, AlignItems, AlignSelf, Gap, GapValue, JustifyContent, JustifyItems,
704        JustifySelf, PlaceContent, PlaceItems, PlaceSelf,
705    };
706    bridge_clone_partialeq!(
707        AlignContent,
708        AlignItems,
709        AlignSelf,
710        JustifyContent,
711        JustifyItems,
712        JustifySelf,
713        PlaceContent,
714        PlaceItems,
715        PlaceSelf,
716        Gap,
717        GapValue,
718    );
719
720    // ── properties/flex ──
721    use crate::properties::flex::{
722        BoxAlign, BoxDirection, BoxLines, BoxOrient, BoxPack, Flex, FlexDirection, FlexFlow,
723        FlexItemAlign, FlexLinePack, FlexPack, FlexWrap,
724    };
725    bridge_clone_partialeq!(
726        FlexDirection,
727        FlexWrap,
728        FlexFlow,
729        Flex,
730        BoxOrient,
731        BoxDirection,
732        BoxAlign,
733        BoxPack,
734        BoxLines,
735        FlexPack,
736        FlexItemAlign,
737        FlexLinePack,
738    );
739
740    // ── properties/font ──
741    use crate::properties::font::{
742        FontFamily, FontSize, FontStretch, FontStyle, FontVariantCaps, FontWeight, LineHeight,
743    };
744    bridge_clone_partialeq!(
745        FontWeight,
746        FontSize,
747        FontStretch,
748        FontStyle,
749        FontVariantCaps,
750        LineHeight,
751    );
752    impl super::IsCompatible for FontWeight {
753        #[inline]
754        fn is_compatible(&self, browsers: &crate::targets::Browsers) -> bool {
755            FontWeight::is_compatible(self, browsers)
756        }
757    }
758    impl super::IsCompatible for FontSize {
759        #[inline]
760        fn is_compatible(&self, browsers: &crate::targets::Browsers) -> bool {
761            FontSize::is_compatible(self, browsers)
762        }
763    }
764    impl super::IsCompatible for FontStretch {
765        #[inline]
766        fn is_compatible(&self, browsers: &crate::targets::Browsers) -> bool {
767            FontStretch::is_compatible(*self, browsers)
768        }
769    }
770    impl super::IsCompatible for FontStyle {
771        #[inline]
772        fn is_compatible(&self, browsers: &crate::targets::Browsers) -> bool {
773            FontStyle::is_compatible(*self, browsers)
774        }
775    }
776    impl super::IsCompatible for FontVariantCaps {
777        #[inline]
778        fn is_compatible(&self, browsers: &crate::targets::Browsers) -> bool {
779            FontVariantCaps::is_compatible(*self, browsers)
780        }
781    }
782    impl super::IsCompatible for LineHeight {
783        #[inline]
784        fn is_compatible(&self, browsers: &crate::targets::Browsers) -> bool {
785            LineHeight::is_compatible(self, browsers)
786        }
787    }
788    impl super::IsCompatible for FontFamily {
789        #[inline]
790        fn is_compatible(&self, browsers: &crate::targets::Browsers) -> bool {
791            FontFamily::is_compatible(self, browsers)
792        }
793    }
794    bridge_clone_partialeq!(FontFamily);
795    // `Font` DeepClone/CssEql now via `#[derive]` on the struct (properties/font.rs).
796
797    // ── properties/size ──
798    use crate::properties::size::{AspectRatio, BoxSizing, MaxSize, Size};
799    bridge_eql!(Size, MaxSize, AspectRatio);
800    bridge_deep_clone!(Size, MaxSize, AspectRatio);
801    bridge_clone_partialeq!(BoxSizing);
802
803    // ── properties/display ──
804    use crate::properties::display::{Display, Visibility};
805    bridge_deep_clone_copy!(Display);
806    bridge_eql_partialeq!(Display);
807    bridge_clone_partialeq!(Visibility);
808
809    // ── properties/overflow ──
810    use crate::properties::overflow::{Overflow, OverflowKeyword, TextOverflow};
811    bridge_clone_partialeq!(Overflow, OverflowKeyword, TextOverflow);
812
813    // ── properties/position ──
814    use crate::properties::position::Position as PositionProp;
815    bridge_clone_partialeq!(PositionProp);
816
817    // ── properties/text ──
818    use crate::properties::text::{Direction as TextDirection, TextShadow};
819    bridge_clone_partialeq!(TextDirection);
820    // CssHash for TextDirection — via #[derive(CssHash)] on the enum (properties/text.rs).
821    bridge_deep_clone!(TextShadow);
822    bridge_eql_partialeq!(TextShadow);
823
824    // ── properties/transform ──
825    use crate::properties::transform::{
826        BackfaceVisibility, Perspective, Rotate, Scale, TransformBox, TransformList,
827        TransformStyle, Translate,
828    };
829    bridge_deep_clone!(TransformList, Translate, Rotate, Scale, Perspective);
830    // Unit enums via DefineEnumProperty — no inherent eql/deep_clone, route through Copy/PartialEq.
831    bridge_clone_partialeq!(TransformStyle, TransformBox, BackfaceVisibility);
832
833    // ── properties/transition ──
834    use crate::properties::transition::Transition;
835    bridge_deep_clone!(Transition);
836    bridge_eql!(Transition);
837
838    // ── properties/masking ──
839    use crate::properties::masking::{
840        GeometryBox, MaskBorderMode, MaskClip, MaskComposite, MaskMode, MaskType,
841        WebKitMaskComposite, WebKitMaskSourceType,
842    };
843    bridge_clone_partialeq!(
844        GeometryBox,
845        MaskMode,
846        MaskClip,
847        MaskComposite,
848        MaskType,
849        MaskBorderMode,
850        WebKitMaskComposite,
851        WebKitMaskSourceType,
852    );
853    // `Mask`/`MaskBorder` DeepClone/CssEql now via `#[derive]` on the structs
854    // (properties/masking.rs).
855
856    // ── properties/ui ──
857    use crate::properties::ui::ColorScheme;
858    bridge_deep_clone_copy!(ColorScheme);
859    bridge_eql_partialeq!(ColorScheme);
860
861    // ── properties/css_modules ──
862    use crate::properties::css_modules::Composes;
863    bridge_deep_clone!(Composes);
864    bridge_eql!(Composes);
865
866    // ── properties/margin_padding ──
867    use crate::properties::margin_padding::{
868        Inset, InsetBlock, InsetInline, Margin, MarginBlock, MarginInline, Padding, PaddingBlock,
869        PaddingInline, ScrollMargin, ScrollMarginBlock, ScrollMarginInline, ScrollPadding,
870        ScrollPaddingBlock, ScrollPaddingInline,
871    };
872    bridge_clone_partialeq!(
873        Margin,
874        MarginBlock,
875        MarginInline,
876        Padding,
877        PaddingBlock,
878        PaddingInline,
879        ScrollMargin,
880        ScrollMarginBlock,
881        ScrollMarginInline,
882        ScrollPadding,
883        ScrollPaddingBlock,
884        ScrollPaddingInline,
885        Inset,
886        InsetBlock,
887        InsetInline,
888    );
889
890    // ── properties/properties_generated ──
891    use crate::properties::PropertyId;
892    bridge_deep_clone_copy!(PropertyId);
893    bridge_eql_partialeq!(PropertyId);
894}
895
896// TODO(port): Zig also special-cases `@typeInfo(T).struct.layout == .packed` →
897// bitwise `==`. In Rust those are `bitflags!` types implementing `PartialEq`;
898// add `impl<T: BitFlags> CssEql for T` or per-type impls.
899
900// ───────────────────────────────────────────────────────────────────────────────
901// Hash
902// ───────────────────────────────────────────────────────────────────────────────
903
904pub const HASH_SEED: u64 = 0;
905
906/// Wyhash-based structural hash for CSS values.
907pub trait CssHash {
908    fn hash(&self, hasher: &mut Wyhash);
909}
910
911/// `#[derive(CssHash)]` — field-wise / variant-wise port of Zig's
912/// `css.implementHash`. See `src/css_derive/lib.rs` for the expansion rules.
913/// Re-exported here so `use crate::generics::CssHash;` brings both trait and
914/// derive into scope.
915pub use bun_css_derive::CssHash;
916
917#[inline]
918pub fn implement_hash<T: CssHash>(this: &T, hasher: &mut Wyhash) {
919    // TODO(port): Zig `implementHash` is comptime field/variant reflection ==
920    // the body of `#[derive(CssHash)]`. Free fn forwards to trait.
921    this.hash(hasher)
922}
923
924#[inline]
925pub fn hash<T: CssHash>(this: &T, hasher: &mut Wyhash) {
926    this.hash(hasher)
927}
928
929pub(crate) fn hash_array_list<V: CssHash>(this: &ArrayList<'_, V>, hasher: &mut Wyhash) {
930    for item in this.iter() {
931        item.hash(hasher);
932    }
933}
934
935#[cfg(bao_nightly)]
936pub(crate) fn hash_baby_list<V: CssHash>(this: &[V], hasher: &mut Wyhash) {
937    for item in this {
938        item.hash(hasher);
939    }
940}
941
942impl CssHash for () {
943    #[inline]
944    fn hash(&self, _hasher: &mut Wyhash) {}
945}
946
947impl<T: CssHash> CssHash for Option<T> {
948    #[inline]
949    fn hash(&self, hasher: &mut Wyhash) {
950        // Zig `hash()` dispatcher: Some → hash inner, None → no-op (no prefix).
951        // The "null"/"some" prefixes are from implementHash's dead .optional arm
952        // (guarded by @compileError) — do NOT emit them here.
953        if let Some(v) = self {
954            v.hash(hasher);
955        }
956    }
957}
958
959impl<T: CssHash + ?Sized> CssHash for &T {
960    #[inline]
961    fn hash(&self, hasher: &mut Wyhash) {
962        (**self).hash(hasher)
963    }
964}
965
966impl<T: CssHash> CssHash for [T] {
967    fn hash(&self, hasher: &mut Wyhash) {
968        // Zig `hash()` for `.slice` pointers iterates items only — no len prefix.
969        for item in self {
970            item.hash(hasher);
971        }
972    }
973}
974
975impl<T: CssHash, const N: usize> CssHash for [T; N] {
976    fn hash(&self, hasher: &mut Wyhash) {
977        // Zig: `bun.writeAnyToHasher(hasher, list.len)` — feeds the raw bytes
978        // of `usize` into the hasher. `bun_core::write_any_to_hasher` exists
979        // but is `H: Hasher`-generic and routes through `Hasher::write`, which
980        // for `Wyhash11` calls `update` — so inlining the `usize` byte-feed
981        // here is byte-identical and avoids the trait hop.
982        hasher.update(&self.len().to_ne_bytes());
983        for item in self {
984            item.hash(hasher);
985        }
986    }
987}
988
989impl<'bump, T: CssHash> CssHash for ArrayList<'bump, T> {
990    #[inline]
991    fn hash(&self, hasher: &mut Wyhash) {
992        hash_array_list(self, hasher)
993    }
994}
995
996#[cfg(not(bao_nightly))]
997impl<T: CssHash> CssHash for ::allocator_api2::vec::Vec<T, ::bun_alloc::core_alloc::Global> {
998    #[inline]
999    fn hash(&self, hasher: &mut Wyhash) {
1000        for item in self.slice_const() {
1001            item.hash(hasher);
1002        }
1003    }
1004}
1005
1006#[cfg(bao_nightly)]
1007impl<T: CssHash> CssHash for Vec<T> {
1008    #[inline]
1009    fn hash(&self, hasher: &mut Wyhash) {
1010        hash_baby_list(self, hasher)
1011    }
1012}
1013
1014impl<T: CssHash, const N: usize> CssHash for SmallList<T, N> {
1015    #[inline]
1016    fn hash(&self, hasher: &mut Wyhash) {
1017        for item in self.slice() {
1018            item.hash(hasher);
1019        }
1020    }
1021}
1022
1023macro_rules! hash_simple {
1024    ($($t:ty),* $(,)?) => {$(
1025        impl CssHash for $t {
1026            #[inline]
1027            fn hash(&self, hasher: &mut Wyhash) {
1028                // Zig: `hasher.update(std.mem.asBytes(&this))`
1029                hasher.update(&self.to_ne_bytes());
1030            }
1031        }
1032    )*};
1033}
1034// `u8` omitted to avoid `[T]`/`[u8]` overlap — see deep_clone_copy! note.
1035hash_simple!(f32, f64, i32, u32, i64, u64, usize, isize, u16);
1036
1037impl CssHash for bool {
1038    #[inline]
1039    fn hash(&self, hasher: &mut Wyhash) {
1040        hasher.update(&[*self as u8]);
1041    }
1042}
1043
1044impl CssHash for [u8] {
1045    #[inline]
1046    fn hash(&self, hasher: &mut Wyhash) {
1047        hasher.update(self);
1048    }
1049}
1050
1051impl CssHash for str {
1052    #[inline]
1053    fn hash(&self, hasher: &mut Wyhash) {
1054        hasher.update(self.as_bytes());
1055    }
1056}
1057
1058impl<T: CssHash + ?Sized> CssHash for Box<T> {
1059    #[inline]
1060    fn hash(&self, hasher: &mut Wyhash) {
1061        (**self).hash(hasher)
1062    }
1063}
1064
1065impl CssHash for VendorPrefix {
1066    #[inline]
1067    fn hash(&self, hasher: &mut Wyhash) {
1068        // Zig: `hasher.update(std.mem.asBytes(&this))` on the packed-struct repr.
1069        hasher.update(&[self.as_bits()]);
1070    }
1071}
1072
1073impl CssHash for bun_ast::Loc {
1074    #[inline]
1075    fn hash(&self, hasher: &mut Wyhash) {
1076        // Zig `implementHash` doesn't reach `Loc` (callers skip it), but
1077        // providing a structural hash here lets `#[derive(CssHash)]` types
1078        // include a `loc` field without `#[css(skip)]` if they want.
1079        hasher.update(&self.start.to_ne_bytes());
1080    }
1081}
1082
1083// ───────────────────────────────────────────────────────────────────────────────
1084// slice / isCompatible
1085// ───────────────────────────────────────────────────────────────────────────────
1086
1087/// Uniform `.as_slice()` over the three list container shapes the CSS parser uses.
1088pub trait ListContainer {
1089    type Item;
1090    fn slice(&self) -> &[Self::Item];
1091}
1092
1093impl<'bump, T> ListContainer for ArrayList<'bump, T> {
1094    type Item = T;
1095    #[inline]
1096    fn slice(&self) -> &[T] {
1097        self.as_slice()
1098    }
1099}
1100impl<T> ListContainer for Vec<T> {
1101    type Item = T;
1102    #[inline]
1103    fn slice(&self) -> &[T] {
1104        self.as_slice()
1105    }
1106}
1107#[cfg(not(bao_nightly))]
1108impl<T> ListContainer for ::allocator_api2::vec::Vec<T, ::bun_alloc::core_alloc::Global> {
1109    type Item = T;
1110    #[inline]
1111    fn slice(&self) -> &[T] {
1112        self.as_slice()
1113    }
1114}
1115impl<T, const N: usize> ListContainer for SmallList<T, N> {
1116    type Item = T;
1117    #[inline]
1118    fn slice(&self) -> &[T] {
1119        SmallList::slice(self)
1120    }
1121}
1122
1123#[inline]
1124pub fn slice<L: ListContainer>(val: &L) -> &[L::Item] {
1125    val.slice()
1126}
1127
1128pub trait IsCompatible {
1129    fn is_compatible(&self, browsers: &crate::targets::Browsers) -> bool;
1130}
1131
1132/// `#[derive(IsCompatible)]` — field-wise / variant-wise port of the
1133/// hand-written `isCompatible` pattern (struct → AND of fields, enum → unit
1134/// variants `true` / payload variants delegate). See `src/css_derive/lib.rs`.
1135/// Re-exported here so `use crate::generics::IsCompatible;` brings both trait
1136/// and derive into scope.
1137pub use bun_css_derive::IsCompatible;
1138
1139#[inline]
1140pub(crate) fn is_compatible<T: IsCompatible>(val: &T, browsers: &crate::targets::Browsers) -> bool {
1141    val.is_compatible(browsers)
1142}
1143
1144impl<T: IsCompatible + ?Sized> IsCompatible for &T {
1145    #[inline]
1146    fn is_compatible(&self, browsers: &crate::targets::Browsers) -> bool {
1147        (**self).is_compatible(browsers)
1148    }
1149}
1150
1151impl<T: IsCompatible + ?Sized> IsCompatible for Box<T> {
1152    #[inline]
1153    fn is_compatible(&self, browsers: &crate::targets::Browsers) -> bool {
1154        (**self).is_compatible(browsers)
1155    }
1156}
1157
1158impl<T: IsCompatible> IsCompatible for Option<T> {
1159    #[inline]
1160    fn is_compatible(&self, browsers: &crate::targets::Browsers) -> bool {
1161        // Zig's `isCompatible` doesn't special-case Optional, but every
1162        // hand-written caller treats absent as compatible (no value → no
1163        // feature gate to check).
1164        match self {
1165            Some(v) => v.is_compatible(browsers),
1166            None => true,
1167        }
1168    }
1169}
1170
1171impl<T: IsCompatible> IsCompatible for [T] {
1172    #[inline]
1173    fn is_compatible(&self, browsers: &crate::targets::Browsers) -> bool {
1174        for item in self {
1175            if !item.is_compatible(browsers) {
1176                return false;
1177            }
1178        }
1179        true
1180    }
1181}
1182
1183impl<T: IsCompatible, const N: usize> IsCompatible for [T; N] {
1184    #[inline]
1185    fn is_compatible(&self, browsers: &crate::targets::Browsers) -> bool {
1186        self.as_slice().is_compatible(browsers)
1187    }
1188}
1189
1190#[cfg(not(bao_nightly))]
1191impl<T: IsCompatible> IsCompatible for ::allocator_api2::vec::Vec<T, ::bun_alloc::core_alloc::Global> {
1192    #[inline]
1193    fn is_compatible(&self, browsers: &crate::targets::Browsers) -> bool {
1194        self.as_slice().is_compatible(browsers)
1195    }
1196}
1197
1198#[cfg(bao_nightly)]
1199impl<T: IsCompatible> IsCompatible for Vec<T> {
1200    #[inline]
1201    fn is_compatible(&self, browsers: &crate::targets::Browsers) -> bool {
1202        self.as_slice().is_compatible(browsers)
1203    }
1204}
1205
1206// The Zig original blanket-impls over "any list container". A Rust blanket
1207// `impl<L: ListContainer>` conflicts with the `&T` impl above (coherence can't
1208// prove `&T` never impls `ListContainer`), so spell out the three concrete
1209// container types instead.
1210macro_rules! is_compatible_container {
1211    ($(($($gen:tt)*) $ty:ty),* $(,)?) => {$(
1212        impl<$($gen)*> IsCompatible for $ty
1213        where
1214            <$ty as ListContainer>::Item: IsCompatible,
1215        {
1216            fn is_compatible(&self, browsers: &crate::targets::Browsers) -> bool {
1217                for item in ListContainer::slice(self) {
1218                    if !item.is_compatible(browsers) {
1219                        return false;
1220                    }
1221                }
1222                true
1223            }
1224        }
1225    )*};
1226}
1227is_compatible_container!(
1228    ('bump, T) ArrayList<'bump, T>,
1229    (T, const N: usize) SmallList<T, N>,
1230);
1231
1232// ───────────────────────────────────────────────────────────────────────────────
1233// Parse / ParseWithOptions
1234// ───────────────────────────────────────────────────────────────────────────────
1235// Zig's `generic.parse(T, input)` / `generic.parseWithOptions(T, input, opts)`
1236// dispatch via `@hasDecl(T, "parse"[WithOptions])`. In Rust each leaf value
1237// type either hand-writes an inherent `parse(&mut Parser) -> CssResult<Self>`
1238// or derives one via `#[derive(Parse)]` / `#[derive(DefineEnumProperty)]`; the
1239// trait below is the uniform bound that `Property::parse` and the container
1240// blanket impls (`SmallList`/`Vec`/`Option`/`Size2D`/`Rect`) need.
1241//
1242// `Parse` is intentionally lifetime-free: every value-type parser takes
1243// `&mut Parser<'_>` (the borrowed source slice) and returns an owned value.
1244// TODO(refactor): `'bump` arena threading is a follow-up; until then the parser
1245// holds the arena and arena-backed lists go through `from_list(Vec)`.
1246
1247/// `T::parse(&mut Parser) -> CssResult<T>`.
1248pub trait Parse: Sized {
1249    fn parse(input: &mut Parser) -> CssResult<Self>;
1250}
1251
1252/// `T::parse_with_options(&mut Parser, &ParserOptions) -> CssResult<T>`.
1253///
1254/// Zig falls through to `parse` when a type has no `parseWithOptions` decl.
1255/// PORT NOTE: Rust can't express that as a `where Self: Parse` default method
1256/// — the bound becomes part of the *method signature*, so the free
1257/// `parse_with_options::<T>` below would require `T: Parse` even for impls
1258/// that override the body. Instead the fallthrough lives in
1259/// `impl_pwo_via_parse!`/`impl_parse_tocss_via_inherent!` and the container
1260/// impls; every `ParseWithOptions` impl provides the method explicitly.
1261pub trait ParseWithOptions: Sized {
1262    fn parse_with_options(input: &mut Parser, options: &ParserOptions) -> CssResult<Self>;
1263}
1264
1265#[inline]
1266pub fn parse_with_options<T: ParseWithOptions>(
1267    input: &mut Parser,
1268    options: &ParserOptions,
1269) -> CssResult<T> {
1270    T::parse_with_options(input, options)
1271}
1272
1273#[inline]
1274pub fn parse<T: Parse>(input: &mut Parser) -> CssResult<T> {
1275    T::parse(input)
1276}
1277
1278#[inline]
1279pub fn parse_for<T: Parse>() -> fn(&mut Parser) -> CssResult<T> {
1280    |input| T::parse(input)
1281}
1282
1283// ── container / primitive Parse impls ────────────────────────────────────────
1284
1285impl<T: Parse> Parse for Option<T> {
1286    #[inline]
1287    fn parse(input: &mut Parser) -> CssResult<Self> {
1288        Ok(input.try_parse(T::parse).ok())
1289    }
1290}
1291impl<T: Parse> ParseWithOptions for Option<T> {
1292    #[inline]
1293    fn parse_with_options(input: &mut Parser, _options: &ParserOptions) -> CssResult<Self> {
1294        <Self as Parse>::parse(input)
1295    }
1296}
1297
1298#[cfg(bao_nightly)]
1299impl<T: Parse> Parse for Vec<T> {
1300    #[inline]
1301    fn parse(input: &mut Parser) -> CssResult<Self> {
1302        input.parse_comma_separated(T::parse)
1303    }
1304}
1305#[cfg(not(bao_nightly))]
1306impl<T: Parse> Parse for ::allocator_api2::vec::Vec<T, ::bun_alloc::core_alloc::Global> {
1307    #[inline]
1308    fn parse(input: &mut Parser) -> CssResult<Self> {
1309        input.parse_comma_separated(T::parse)
1310    }
1311}
1312
1313#[cfg(bao_nightly)]
1314impl<T: Parse> ParseWithOptions for Vec<T> {
1315    #[inline]
1316    fn parse_with_options(input: &mut Parser, _options: &ParserOptions) -> CssResult<Self> {
1317        <Self as Parse>::parse(input)
1318    }
1319}
1320#[cfg(not(bao_nightly))]
1321impl<T: Parse> ParseWithOptions for ::allocator_api2::vec::Vec<T, ::bun_alloc::core_alloc::Global> {
1322    #[inline]
1323    fn parse_with_options(input: &mut Parser, _options: &ParserOptions) -> CssResult<Self> {
1324        <Self as Parse>::parse(input)
1325    }
1326}
1327
1328// Zig `.pointer` arm (`generics.zig:273-279`): parse the pointee then heap-allocate.
1329impl<T: Parse> Parse for Box<T> {
1330    #[inline]
1331    fn parse(input: &mut Parser) -> CssResult<Self> {
1332        T::parse(input).map(Box::new)
1333    }
1334}
1335impl<T: Parse> ParseWithOptions for Box<T> {
1336    #[inline]
1337    fn parse_with_options(input: &mut Parser, _options: &ParserOptions) -> CssResult<Self> {
1338        <Self as Parse>::parse(input)
1339    }
1340}
1341
1342impl<T: Parse, const N: usize> Parse for SmallList<T, N> {
1343    #[inline]
1344    fn parse(input: &mut Parser) -> CssResult<Self> {
1345        input
1346            .parse_comma_separated(T::parse)
1347            .map(|v: ParseListVec<T>| SmallList::from_list(v.into_iter().collect()))
1348    }
1349}
1350impl<T: Parse, const N: usize> ParseWithOptions for SmallList<T, N> {
1351    #[inline]
1352    fn parse_with_options(input: &mut Parser, _options: &ParserOptions) -> CssResult<Self> {
1353        <Self as Parse>::parse(input)
1354    }
1355}
1356
1357impl<T: Parse + Clone + PartialEq> Parse for Size2D<T> {
1358    #[inline]
1359    fn parse(input: &mut Parser) -> CssResult<Self> {
1360        Size2D::<T>::parse(input)
1361    }
1362}
1363impl<T: Parse + Clone + PartialEq> ParseWithOptions for Size2D<T> {
1364    #[inline]
1365    fn parse_with_options(input: &mut Parser, _options: &ParserOptions) -> CssResult<Self> {
1366        Size2D::<T>::parse(input)
1367    }
1368}
1369
1370impl<T: Parse + Clone> Parse for Rect<T> {
1371    #[inline]
1372    fn parse(input: &mut Parser) -> CssResult<Self> {
1373        Rect::<T>::parse(input)
1374    }
1375}
1376impl<T: Parse + Clone> ParseWithOptions for Rect<T> {
1377    #[inline]
1378    fn parse_with_options(input: &mut Parser, _options: &ParserOptions) -> CssResult<Self> {
1379        Rect::<T>::parse(input)
1380    }
1381}
1382
1383impl Parse for f32 {
1384    #[inline]
1385    fn parse(input: &mut Parser) -> CssResult<Self> {
1386        CSSNumberFns::parse(input)
1387    }
1388}
1389impl Parse for CSSInteger {
1390    #[inline]
1391    fn parse(input: &mut Parser) -> CssResult<Self> {
1392        CSSIntegerFns::parse(input)
1393    }
1394}
1395impl Parse for CustomIdent {
1396    #[inline]
1397    fn parse(input: &mut Parser) -> CssResult<Self> {
1398        CustomIdentFns::parse(input)
1399    }
1400}
1401impl Parse for DashedIdent {
1402    #[inline]
1403    fn parse(input: &mut Parser) -> CssResult<Self> {
1404        DashedIdentFns::parse(input)
1405    }
1406}
1407impl Parse for Ident {
1408    #[inline]
1409    fn parse(input: &mut Parser) -> CssResult<Self> {
1410        IdentFns::parse(input)
1411    }
1412}
1413
1414// ───────────────────────────────────────────────────────────────────────────────
1415// ToCss
1416// ───────────────────────────────────────────────────────────────────────────────
1417
1418pub trait ToCss {
1419    fn to_css(&self, dest: &mut Printer) -> core::result::Result<(), PrintErr>;
1420}
1421
1422#[inline]
1423pub(crate) fn to_css<T: ToCss>(this: &T, dest: &mut Printer) -> core::result::Result<(), PrintErr> {
1424    this.to_css(dest)
1425}
1426
1427impl<T: ToCss + ?Sized> ToCss for &T {
1428    #[inline]
1429    fn to_css(&self, dest: &mut Printer) -> core::result::Result<(), PrintErr> {
1430        (**self).to_css(dest)
1431    }
1432}
1433
1434// Zig `.pointer` arm (`generics.zig:338-341`): recurse into `*T` pointee.
1435impl<T: ToCss + ?Sized> ToCss for Box<T> {
1436    #[inline]
1437    fn to_css(&self, dest: &mut Printer) -> core::result::Result<(), PrintErr> {
1438        (**self).to_css(dest)
1439    }
1440}
1441
1442impl<T: ToCss> ToCss for Option<T> {
1443    #[inline]
1444    fn to_css(&self, dest: &mut Printer) -> core::result::Result<(), PrintErr> {
1445        if let Some(val) = self {
1446            return val.to_css(dest);
1447        }
1448        Ok(())
1449    }
1450}
1451
1452impl<'bump, T: ToCss> ToCss for ArrayList<'bump, T> {
1453    #[inline]
1454    fn to_css(&self, dest: &mut Printer) -> core::result::Result<(), PrintErr> {
1455        css::to_css::from_list(self.as_slice(), dest)
1456    }
1457}
1458
1459#[cfg(not(bao_nightly))]
1460impl<T: ToCss> ToCss for ::allocator_api2::vec::Vec<T, ::bun_alloc::core_alloc::Global> {
1461    #[inline]
1462    fn to_css(&self, dest: &mut Printer) -> core::result::Result<(), PrintErr> {
1463        css::to_css::from_list(self.as_slice(), dest)
1464    }
1465}
1466
1467#[cfg(bao_nightly)]
1468impl<T: ToCss> ToCss for Vec<T> {
1469    #[inline]
1470    fn to_css(&self, dest: &mut Printer) -> core::result::Result<(), PrintErr> {
1471        css::to_css::from_list(self.as_slice(), dest)
1472    }
1473}
1474
1475impl<T: ToCss, const N: usize> ToCss for SmallList<T, N> {
1476    #[inline]
1477    fn to_css(&self, dest: &mut Printer) -> core::result::Result<(), PrintErr> {
1478        css::to_css::from_list(self.slice(), dest)
1479    }
1480}
1481
1482impl<T: ToCss + Clone + PartialEq> ToCss for Size2D<T> {
1483    #[inline]
1484    fn to_css(&self, dest: &mut Printer) -> core::result::Result<(), PrintErr> {
1485        Size2D::<T>::to_css(self, dest)
1486    }
1487}
1488
1489impl<T: ToCss + PartialEq> ToCss for Rect<T> {
1490    #[inline]
1491    fn to_css(&self, dest: &mut Printer) -> core::result::Result<(), PrintErr> {
1492        Rect::<T>::to_css(self, dest)
1493    }
1494}
1495
1496impl ToCss for f32 {
1497    #[inline]
1498    fn to_css(&self, dest: &mut Printer) -> core::result::Result<(), PrintErr> {
1499        CSSNumberFns::to_css(*self, dest)
1500    }
1501}
1502impl ToCss for CSSInteger {
1503    #[inline]
1504    fn to_css(&self, dest: &mut Printer) -> core::result::Result<(), PrintErr> {
1505        CSSIntegerFns::to_css(*self, dest)
1506    }
1507}
1508impl ToCss for CustomIdent {
1509    #[inline]
1510    fn to_css(&self, dest: &mut Printer) -> core::result::Result<(), PrintErr> {
1511        CustomIdentFns::to_css(self, dest)
1512    }
1513}
1514impl ToCss for DashedIdent {
1515    #[inline]
1516    fn to_css(&self, dest: &mut Printer) -> core::result::Result<(), PrintErr> {
1517        DashedIdentFns::to_css(self, dest)
1518    }
1519}
1520impl ToCss for Ident {
1521    #[inline]
1522    fn to_css(&self, dest: &mut Printer) -> core::result::Result<(), PrintErr> {
1523        IdentFns::to_css(self, dest)
1524    }
1525}
1526
1527// ── leaf-type forwarding macros ──────────────────────────────────────────────
1528// Every CSS leaf value type carries inherent `parse` / `to_css` (hand-written
1529// or derived). `Property::{parse,value_to_css}` dispatch through the
1530// `generic::{Parse,ToCss,ParseWithOptions}` *traits*, so each leaf must impl
1531// them.
1532//
1533// Two sources of the trait impl:
1534//   1. `#[derive(ToCss/Parse/DefineEnumProperty)]`
1535//      (bun_css_derive) — emits the trait impl directly.
1536//   2. Hand-written leaves — list them under `impl_parse_tocss_via_inherent!`
1537//      to forward the trait to the inherent.
1538//
1539// A type must use exactly one of the two; listing a derive-carrying type in
1540// the macro is an E0119 coherence conflict.
1541#[macro_export]
1542macro_rules! impl_parse_tocss_via_inherent {
1543    ($($ty:ty),+ $(,)?) => {$(
1544        impl $crate::generics::Parse for $ty {
1545            #[inline]
1546            fn parse(input: &mut $crate::css_parser::Parser) -> $crate::css_parser::CssResult<Self> {
1547                <$ty>::parse(input)
1548            }
1549        }
1550        impl $crate::generics::ParseWithOptions for $ty {
1551            #[inline]
1552            fn parse_with_options(
1553                input: &mut $crate::css_parser::Parser,
1554                _options: &$crate::css_parser::ParserOptions,
1555            ) -> $crate::css_parser::CssResult<Self> {
1556                <$ty>::parse(input)
1557            }
1558        }
1559        impl $crate::generics::ToCss for $ty {
1560            #[inline]
1561            fn to_css(
1562                &self,
1563                dest: &mut $crate::printer::Printer,
1564            ) -> ::core::result::Result<(), $crate::PrintErr> {
1565                (*self).to_css(dest)
1566            }
1567        }
1568    )+};
1569}
1570
1571/// `ParseWithOptions` for primitives — same fallthrough as the macro, but
1572/// listed once rather than duplicated per call site.
1573macro_rules! impl_pwo_via_parse {
1574    ($($ty:ty),+ $(,)?) => {$(
1575        impl ParseWithOptions for $ty {
1576            #[inline]
1577            fn parse_with_options(input: &mut Parser, _options: &ParserOptions) -> CssResult<Self> {
1578                <$ty as Parse>::parse(input)
1579            }
1580        }
1581    )+};
1582}
1583impl_pwo_via_parse!(f32, CSSInteger, CustomIdent, DashedIdent, Ident);
1584
1585// ───────────────────────────────────────────────────────────────────────────────
1586// Numeric helpers (tryFromAngle / trySign / tryMap / tryOp / tryOpTo / partialCmp)
1587// ───────────────────────────────────────────────────────────────────────────────
1588
1589pub trait TryFromAngle: Sized {
1590    fn try_from_angle(angle: Angle) -> Option<Self>;
1591}
1592
1593impl TryFromAngle for CSSNumber {
1594    #[inline]
1595    fn try_from_angle(angle: Angle) -> Option<Self> {
1596        CSSNumberFns::try_from_angle(angle)
1597    }
1598}
1599
1600pub trait TrySign {
1601    fn try_sign(&self) -> Option<f32>;
1602}
1603
1604impl TrySign for CSSNumber {
1605    #[inline]
1606    fn try_sign(&self) -> Option<f32> {
1607        Some(CSSNumberFns::sign(*self))
1608    }
1609}
1610// TODO(port): Zig fallback `if @hasDecl(T, "sign") T.sign else T.trySign` —
1611// model as a trait with default method delegating to `Sign` where available.
1612
1613pub trait TryMap: Sized {
1614    // Zig: `comptime map_fn: *const fn(f32) f32` — generic param preserves monomorphization.
1615    fn try_map(&self, map_fn: impl Fn(f32) -> f32) -> Option<Self>;
1616}
1617
1618impl TryMap for CSSNumber {
1619    #[inline]
1620    fn try_map(&self, map_fn: impl Fn(f32) -> f32) -> Option<Self> {
1621        Some(map_fn(*self))
1622    }
1623}
1624
1625pub trait TryOpTo: Sized {
1626    // Zig: `comptime op_fn: *const fn(...)` — generic param preserves monomorphization.
1627    // `R` is method-generic (not trait-generic) so `TryOpTo` can appear as a
1628    // supertrait bound without committing to a result type.
1629    fn try_op_to<R, C>(&self, rhs: &Self, ctx: C, op_fn: impl Fn(C, f32, f32) -> R) -> Option<R>;
1630}
1631
1632impl TryOpTo for CSSNumber {
1633    #[inline]
1634    fn try_op_to<R, C>(&self, rhs: &Self, ctx: C, op_fn: impl Fn(C, f32, f32) -> R) -> Option<R> {
1635        Some(op_fn(ctx, *self, *rhs))
1636    }
1637}
1638
1639impl IsCompatible for CSSNumber {
1640    #[inline]
1641    fn is_compatible(&self, _: &crate::targets::Browsers) -> bool {
1642        true
1643    }
1644}
1645
1646pub trait TryOp: Sized {
1647    // Zig: `comptime op_fn: *const fn(...)` — generic param preserves monomorphization.
1648    fn try_op<C>(&self, rhs: &Self, ctx: C, op_fn: impl Fn(C, f32, f32) -> f32) -> Option<Self>;
1649}
1650
1651impl TryOp for CSSNumber {
1652    #[inline]
1653    fn try_op<C>(&self, rhs: &Self, ctx: C, op_fn: impl Fn(C, f32, f32) -> f32) -> Option<Self> {
1654        Some(op_fn(ctx, *self, *rhs))
1655    }
1656}
1657
1658pub trait PartialCmp {
1659    fn partial_cmp(&self, rhs: &Self) -> Option<Ordering>;
1660}
1661
1662#[inline]
1663pub(crate) fn partial_cmp_f32(lhs: f32, rhs: f32) -> Option<Ordering> {
1664    let lte = lhs <= rhs;
1665    let rte = lhs >= rhs;
1666    if !lte && !rte {
1667        return None;
1668    }
1669    if !lte && rte {
1670        return Some(Ordering::Greater);
1671    }
1672    if lte && !rte {
1673        return Some(Ordering::Less);
1674    }
1675    Some(Ordering::Equal)
1676}
1677
1678impl PartialCmp for f32 {
1679    #[inline]
1680    fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
1681        partial_cmp_f32(*self, *rhs)
1682    }
1683}
1684impl PartialCmp for CSSInteger {
1685    #[inline]
1686    fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
1687        Some(Ord::cmp(self, rhs))
1688    }
1689}
1690
1691// ───────────────────────────────────────────────────────────────────────────────
1692// Zero / MulF32 / TryAdd — numeric protocol traits used by `DimensionPercentage<D>`
1693// and the `CalcValue` supertrait set. Formerly duplicated in `values::protocol`;
1694// that module now re-exports from here.
1695// ───────────────────────────────────────────────────────────────────────────────
1696
1697/// `D::zero()` / `d.is_zero()` — additive identity.
1698pub trait Zero: Sized {
1699    fn zero() -> Self;
1700    fn is_zero(&self) -> bool;
1701}
1702/// `d.mul_f32(rhs)` — scalar multiplication.
1703pub trait MulF32: Sized {
1704    fn mul_f32(self, rhs: f32) -> Self;
1705}
1706/// `d.try_add(&rhs)` — same-unit addition, `None` if incompatible.
1707pub trait TryAdd: Sized {
1708    fn try_add(&self, rhs: &Self) -> Option<Self>;
1709}
1710
1711impl MulF32 for CSSNumber {
1712    #[inline]
1713    fn mul_f32(self, rhs: f32) -> Self {
1714        self * rhs
1715    }
1716}
1717
1718// ported from: src/css/generics.zig