Skip to content

css: return RGBA from parse_hash_color instead of a tuple - #39173

Merged
alii merged 3 commits into
mainfrom
farm/2ad86dce/css-hash-color-struct
Aug 15, 2026
Merged

alii merged 3 commits into
mainfrom
farm/2ad86dce/css-hash-color-struct

Conversation

@robobun

@robobun robobun commented Aug 15, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

  • mordant's tuple_wants_struct flags color::parse_hash_color in src/css/css_parser.rs:5730: it returns (u8, u8, u8, f32), and both callers (CssColor::parse in src/css/values/color.rs:408, TokenList hash tokens in src/css/properties/custom.rs:653) unpack it as (r, g, b, a). The channel names live only at the call sites, and since the first three fields are all u8, a swapped pair still compiles.
  • Both callers then immediately build an RGBA out of the tuple, so the function already had a named type for its result; custom.rs also converted each byte to a float and RGBA::from_floats converted it straight back.

Fix

  • parse_hash_color / parse_hash_color_impl build the RGBA (red/green/blue/alpha, all u8) themselves and return it; the callers wrap it in CssColor::Rgba. OPAQUE becomes the u8 alpha 255 it was being converted to.
  • No behavior change. The hex digits were already parsed as bytes; the only thing removed is the byte -> f32 -> byte round trip through RGBA::new / RGBA::from_floats, which is the identity for all 256 values (checked with f32 arithmetic), and OPAQUE (1.0) mapped to 255 the same way.
  • The baseline entry tuple_wants_struct:src/css/css_parser.rs is removed from mordant-baseline.toml. The [bun_css] section was regenerated with MORDANT_BASELINE_WRITE=1 cargo dylint --all -p bun_css --no-deps (the per-crate form of bun run rust:mordant:baseline); that line is the only difference, the values/color.rs = 5 entry is unchanged.
  • No test is added. The change has no observable effect (the RGBA produced for every hash is byte for byte what it was), so there is no test that fails before it and passes after it; the check that does flip is the mordant run, which reports the css_parser.rs finding over the trimmed baseline without this change and is clean with it. The existing coverage below is what guards the refactor.
  • Verified with bun bd:
    • bun bd test test/js/bun/css/css.test.ts: 1142 pass. This covers both callers with every hash length: 8-digit hashes inside custom properties (--bar: ... #01010116 round-trips through custom.rs), #0006 / #ffffff80 / 3- and 6-digit hashes in regular properties through color.rs.
    • bun bd test test/js/bun/css/color.test.ts (Bun.color): 993 pass.
    • bun bd test test/bundler/css/: 169 pass.
    • cargo check -p bun_css, cargo clippy -p bun_css --no-deps and cargo dylint --all -p bun_css --no-deps (mordant) are clean; the latter writes no target/mordant/over-baseline.txt.

Background

  • css_parser::color is the port of the cssparser crate's color helpers; parse_hash_color parses the text after # (3, 4, 6 or 8 hex digits) into channels. RGBA (src/css/values/color.rs) is the crate's byte-per-channel color and is what CssColor::Rgba stores, which is why both callers were converting to it.
  • mordant-baseline.toml is the ratchet for the mordant lint pack: it records the per-(lint, file) finding counts that predate the job so CI only fails on new findings. Fixing a site means removing (or decrementing) its entry; mordant's write mode rewrites one section per compiled crate, so regenerating with only bun_css compiled touches only that section.

parse_hash_color returned (u8, u8, u8, f32) and both callers unpacked it
as (r, g, b, a) only to build an RGBA from it, converting the byte
channels to floats and back on the way. Build the RGBA in the parser
and hand it out directly. The alpha byte was already exact, so the
resulting RGBA values are unchanged.

Drops the matching tuple_wants_struct entry for css_parser.rs from the
mordant baseline.
@coderabbitai

coderabbitai Bot commented Aug 15, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 56 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 00d687ee-d3a1-483b-95ce-817a911ff731

📥 Commits

Reviewing files that changed from the base of the PR and between dc7800c and e982468.

📒 Files selected for processing (4)
  • mordant-baseline.toml
  • src/css/css_parser.rs
  • src/css/properties/custom.rs
  • src/css/values/color.rs

Comment @coderabbitai help to get the list of available commands.

@robobun

robobun commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author

Status: ready for review. Refactor only, no behavior change intended.

  • Verified on the debug build: test/js/bun/css/css.test.ts (1142 pass; covers 3/4/6/8-digit hashes through both callers), test/js/bun/css/color.test.ts (993 pass), test/bundler/css/ (169 pass).
  • mordant no longer reports tuple_wants_struct for src/css/css_parser.rs; the regenerated [bun_css] baseline section differs from main by that one line only.
  • css: pass SelectorFlags to SelectorBuilder::build instead of three bools #39122 removes a neighbouring line in the same baseline section, so whichever of the two lands second will need a trivial rebase of mordant-baseline.toml.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — mechanical refactor returning RGBA directly instead of a tuple, with no behavior change.

What was reviewed:

  • Confirmed parse_hash_color/parse_hash_color_impl have exactly the two callers updated here; OPAQUE is module-private and only used inside parse_hash_color_impl.
  • Verified the removed byte→f32→byte round-trip through clamp_unit_f32 ((x/255.0 * 255.0).round()) is identity for all u8, and OPAQUE = 1.0 mapped to 255.
  • Confirmed the dropped RGBA import in custom.rs has no remaining uses; mordant-baseline.toml change removes only the one resolved entry.
Extended reasoning...

Overview

This PR changes css_parser::color::parse_hash_color{,_impl} to return the crate's RGBA struct (four u8 fields) instead of a (u8, u8, u8, f32) tuple, resolving a mordant tuple_wants_struct lint. Both callers — CssColor::parse in values/color.rs and the hash-token arm of TokenList::parse_into_impl in properties/custom.rs — previously destructured the tuple and immediately built an RGBA from it; now they take the RGBA directly. The module-private OPAQUE constant changes from f32 = 1.0 to u8 = 255. The corresponding baseline entry is removed from mordant-baseline.toml.

Security risks

None. This is pure internal refactoring of hex-color parsing with no change to input handling, bounds, or external surface.

Level of scrutiny

Low. The change is small, mechanical, and self-contained. I verified via grep that (1) parse_hash_color and parse_hash_color_impl are called only from the two sites updated in this PR, (2) OPAQUE is used only inside parse_hash_color_impl, so its type change has no other consumers, and (3) the dropped RGBA import in custom.rs has no remaining references. The old alpha path was byte / 255.0 fed through clamp_unit_f32 ((v * 255.0).round().clamp(0,255) as u8), which is the identity for every u8; the custom.rs caller additionally round-tripped R/G/B through from_floats the same way. So constructing RGBA { red, green, blue, alpha } directly is byte-for-byte equivalent.

Other factors

The PR description reports the full CSS test suites pass (css.test.ts 1142, color.test.ts 993, bundler CSS 169) covering all four hash lengths through both callers, plus clean cargo check/clippy/dylint. The baseline-file edit is a single-line deletion matching the resolved lint. No outstanding reviewer comments.

@robobun

robobun commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 12:14 PM PT - Aug 15th, 2026

@robobun, your commit d4cf979 is building: #98595

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — mechanical refactor that returns RGBA directly instead of a tuple both callers immediately rebuilt into one.

What was reviewed:

  • Verified parse_hash_color/parse_hash_color_impl and OPAQUE have no other callers in src/; both call sites now just wrap the result in CssColor::Rgba.
  • Checked the removed byte→f32→byte round-trip through clamp_unit_f32 ((x/255.0 * 255.0).round()) is the identity for all u8, and OPAQUE: 1.0 mapped to 255 — no observable behavior change.
  • The now-unused RGBA import in custom.rs is dropped; the mordant baseline entry is decremented per the regen procedure.
Extended reasoning...

Overview

This PR changes css_parser::color::parse_hash_color / parse_hash_color_impl to return the crate's RGBA struct directly instead of (u8, u8, u8, f32), updates the two callers in src/css/values/color.rs and src/css/properties/custom.rs to consume it, retypes the module-private OPAQUE constant from f32 1.0 to u8 255, and removes the corresponding tuple_wants_struct entry from mordant-baseline.toml.

Security risks

None. This is pure value-type plumbing inside the CSS hex-color parser; no untrusted-length arithmetic changes, no allocation, no FFI, no user-facing surface.

Level of scrutiny

Low. The change is a type-level refactor with a straightforward equivalence argument: previously the tuple's u8 channels went through RGBA::new/RGBA::from_floats, which apply clamp_unit_f32(x) = (x * 255.0).round().clamp(0,255) as u8. For every u8 byte b, (b as f32 / 255.0 * 255.0).round() recovers b exactly (f32 error is far below 0.5), and clamp_unit_f32(1.0) == 255, so the new direct-byte construction is byte-for-byte identical to the old path for all 3/4/6/8-digit inputs. Grep confirms no other consumers of parse_hash_color* or OPAQUE exist.

Other factors

The refactor is guarded by existing coverage the author ran on the debug build (css.test.ts 1142 pass covering all hash lengths through both callers, color.test.ts 993 pass, test/bundler/css/ 169 pass), plus cargo check/clippy/mordant clean. REVIEW.md's "every behavioral change ships a test" doesn't apply here — there is no behavioral change to observe, and the mordant baseline is the check that flips. The dead RGBA import is removed in the same PR per the dead-code rule. The author already flagged the trivial baseline-file rebase against #39122.

@alii
alii merged commit 2f941ed into main Aug 15, 2026
9 of 10 checks passed
@alii
alii deleted the farm/2ad86dce/css-hash-color-struct branch August 15, 2026 19:33

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — mechanical refactor replacing the (u8, u8, u8, f32) return with RGBA, no behavior change.

What was reviewed:

  • Confirmed parse_hash_color/parse_hash_color_impl have exactly the two updated callers and OPAQUE is used nowhere else in the crate.
  • Verified the removed byte→f32→byte round trip via clamp_unit_f32 ((n/255.0*255.0).round()) is the identity for all u8 inputs, and OPAQUE 1.0 mapped to 255.
  • Checked the RGBA import drop in custom.rs leaves no dangling references; the mordant baseline diff removes only the one resolved entry.
Extended reasoning...

Overview

This PR changes css_parser::color::parse_hash_color / parse_hash_color_impl to return the crate's RGBA struct directly instead of a (u8, u8, u8, f32) tuple, and updates the two callers (CssColor::parse in values/color.rs and the hash-token arm of TokenList::parse_into_impl in properties/custom.rs) to wrap the result in CssColor::Rgba without re-packing. OPAQUE becomes u8 255 (its only uses are the two 3/6-digit arms in the same function). One line is removed from mordant-baseline.toml for the resolved tuple_wants_struct finding.

Security risks

None. This is a pure internal type-shape refactor of a hex-color parser with no I/O, allocation, or user-facing surface change.

Level of scrutiny

Low. The change is small, mechanical, and has a straightforward equivalence argument: the old paths went u8 → f32/255 → clamp_unit_f32 → u8 via RGBA::new / RGBA::from_floats; clamp_unit_f32(x) is (x*255.0).round().clamp(0,255) as u8, which is the identity for every n as f32 / 255.0 with n in 0..=255, and 1.0 maps to 255. So each of the four match arms produces the byte-identical RGBA it did before. Grep confirms there are no other callers of parse_hash_color, parse_hash_color_impl, or OPAQUE, and RGBA is no longer referenced in custom.rs after the import is dropped.

Other factors

The PR description documents that the existing suites (css.test.ts covering 3/4/6/8-digit hashes through both callers, color.test.ts, and test/bundler/css/) all pass on the debug build, and mordant is clean over the trimmed baseline. No new test is expected for a behavior-preserving refactor whose only observable flip is the lint ratchet. There are no prior reviewer comments to address.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants