Stale StyleRuleKey hash after merge_style_rules causes missed dedup and nondeterministic minified output
My knowleged in rust is pretty minimal. So I used claude and codex to help write the patch. I've reviewed everything and reproduced it locally, but flagging it so you know.
What's wrong
In CssRuleList::minify, the dedup HashMap<StyleRuleKey, usize> has a broken Hash/Eq invariant: the hash is precomputed once at insert time, but PartialEq re-reads the live rule from the Vec. When merge_style_rules mutates a rule in place, its content changes, but the hash stored in the map doesn't. Future lookups end up in the wrong bucket and miss valid duplicates.
How it surfaced for us
We hit this as build non-determinism in rspack build using LigthningCSS minimzer. Byte-identical CSS input produced different minified output across fresh process invocations at ~1–2.5%. StyleRule::hash_key() uses ahash::AHasher::default() (random per-process seed), so whether the stale hash collides with a later rule's fresh hash varies between processes.
A constant seed would hide that variance but wouldn't fix the underlying invariant violation — there's also a fully deterministic missed-dedup case, shown below.
Repro
.a { border-top-left-radius: 16px }
.a { border-top-right-radius: 16px }
.a { border-bottom-left-radius: 16px }
.a { border-bottom-right-radius: 16px }
.x { color: red }
.a { border-radius: 16px }Expected:
.x{color:red}.a{border-radius:16px}Actual:
.a{border-radius:16px}.x{color:red}.a{border-radius:16px}The four longhands merge into rules[0] and collapse to a border-radius shorthand. The trailing .a { border-radius: 16px } should dedup against it but doesn't, because rules[0] is still indexed under the original border-top-left-radius hash bucket.
Source: parcel-bundler/lightningcss