Skip to content

css: print a number written with an exponent as written when its value is kept as tokens - #47222

Open
robobun wants to merge 3 commits into
mainfrom
robobun/0d4ead60/css-exponent-tokens-as-written
Open

robobun wants to merge 3 commits into
mainfrom
robobun/0d4ead60/css-exponent-tokens-as-written

Conversation

@robobun

@robobun robobun commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

  • bun build changes a number that has an exponent in a value kept as tokens (custom properties, unparsed values). --shf: 4.9406564584124654e-324 prints --shf:0. --tag:8e64 prints 3.40282e38.
  • 13 .css files in 6 GitHub repositories contain that first number, and 5 divide by it. Main prints 0 for all 14 uses.
  • The cause: Parser::raw_number_text (src/css/css_parser.rs:3603) keeps the text of a long number, but counts only the digits before the exponent.

Fix

  • raw_number_text also keeps the text of a token that has an exponent: its f32 can be another number (8e64, 1e-50, 1.6777217e7). No other function changes.
  • Exception: a unit with an escape (1e3\70) prints as before, because its text can stop inside the escape.
  • Verified: test/js/bun/css/css.test.ts, 19 rows fail on main. Also ran test/js/bun/css/, test/bundler/css/.
  • Self-reviewed: 13 concerns raised, 12 addressed. Not added: an optional debug_assert!.

Background

Downsides

  • z-index:1e3 and --t:1e3ms print as written (main: 1000, 1s). A browser drops that z-index, as it drops the source. 12 GitHub .css files have one.
  • Two right outputs turn wrong: rgb(0 0 0)5e-1 prints #0005e-1 (main: #000.5), and a:unknown(1e3/**/.5) prints a:unknown(1e30.5) (main: 1000.00.5, still 1000 and .5). Long fractions and leading zeros still change.
  • In raw_number_text a token with a fraction costs 145 instructions (main: 82), one without 57 (main: 84). Release .text grows 256 bytes.
Notes

What prints (--minify, main against this branch; the same without --minify)

Input main this PR
--tag:8e64 3.40282e38 as written
--commit:12e45ab 3.40282e38ab as written
--small:1e-50 0 as written
--w:1.23456789e10 12345679000 as written
--uuid:12345678-12e3-8d9b-a456-426614174000 12345678-12000-8d9b-a456-426614174000 as written
--g:1e3e3 1000e3, and a second pass prints 1000000 as written
--p:1e3% 1000.0% as written
.x{--tag:8e64}.y{--tag:9e99} .x,.y{--tag:3.40282e38} as written
.x{--n:1e3}.y{--n:1E3} .x,.y{--n:1000} as written
.a{z-index:7;z-index:1e3} .a{z-index:7;z-index:1000} as written
width:calc(var(--a) + 1e3px) calc(var(--a) + 1000px) as written
width:calc(7.3px*4.9406564584124654e-324/4.9406564584124654e-324) calc(7.3px*0/0) as written
@unknown 1e3{a{b:1e3}} @unknown 1000{a{b:1000}} as written
--t:1e3ms, --u:5e-1 1s, .5 as written
--x:8e64\70 x (a unit with an escape) 3.40282e38px same as main
width:1e39px, flex-grow:1e3 (typed) 3.40282e38px, 1000 same as main
--x:241.8301435407, --x:0000001 241.83, 1 same as main

Main already prints +1e3, 1e3.5 and 1234567e2 as written, because they have a +, touch another number or have 7 digits. 1e3, 1E3 and 1000 are three texts now, so two rules that differ only in that spelling no longer merge.

Real CSS

  • GitHub code search for "4.9406564584124654e-324" extension:css returns 13 files in 6 repositories. The number is the smallest double. calc(X * N / N) rounds X to an integer with it.
  • How the 13 files write it: --shf: in 9 (KurtMage.github.io 7, kwy404/Chess, douglasspeck), --rounding: in 1 (chocmake.github.io), a var() fallback in 2 (86rk.ru) and inline in a width:calc() in 1 (MnCSD/chess.com).
  • I built the 13 files with both builds. Main prints 0 for all 14 uses, and this branch keeps all 14. 5 files divide by the number in the same file (kwy404, chocmake, 86rk twice, MnCSD). The other 8 declare --shf and do not read it in that file.
  • Chrome 153 with the max-width rule of chocmake's css/main.css:333 and --max-width: 1223.4px: 1224px from the source, 0px with --rounding:0. width:calc(7.3px * var(--r) / var(--r)) computes 7px and 0px.
  • No npm stylesheet loses a value. Of 9,382 stylesheets in the latest versions of 450 npm packages, the 9 outputs that change are re-spelled only. open-props 1.7.23 writes --radius-round: 1e5px, which main prints as 100000px.
  • No user reported this for Bun. A user reported --tag: 8e64 for lightningcss (Minifier does not preserve values of CSS custom property parcel-bundler/lightningcss#807). esbuild 0.28.2 prints the exponent rows of the table as written, except that its --minify cuts 1.23456789e10 to 1.23456789e1. lightningcss 1.33.0 prints these tokens from an f32, as main does (--tag:3.40282e38, --small:0).
  • css-variables-1 uses --uuid: 12345678-12e3-8d9b-a456-426614174000 as its example of a custom property that must not be re-serialized from its value. Fractions and leading zeros still are.

z-index:1e3 (Chrome 153, computed style of the source against the output of main)

  • CSS.supports("z-index", "1e3") and CSS.supports("order", "1e1") are false.
  • z-index:7;z-index:1e3 computes 7 and 1000. order:3;order:1e1 computes 3 and 10. --n:1e3;z-index:var(--n) computes auto and 1000.
  • css: treat out-of-range and unit-less numeric values as invalid instead of merging or repairing them #42104 reported order:1e1 printing as 10. z-index:1.0 still prints 1 on both builds.
  • GitHub code search for "z-index: 1e3", "z-index: 1e6", "z-index: 1e9" and "z-index: 1e10" with extension:css returns 12 files in 10 repositories. No warning is added for them.

A comment between two numbers in :unknown() (debug builds of the merge base 11c3671 and of this branch, the same with and without --minify)

  • Bun keeps the arguments of a pseudo-class that it does not know as tokens. TokenList::parse_raw drops a comment between two tokens, and the two tokens print with nothing between them.
  • Main prints both numbers from the f32: 1e3 as 1000.0 and .5 as 0.5. 1000.00.5 still cuts into 1000 and .5. This branch prints 1e3 as written, and 1e30.5 cuts into 1e30 and .5.
  • The second number can have the exponent: a:unknown(1.5/**/1e-3) prints 1.51e-3 (main: 1.50.001). ::unknown() and :state() take the same path.
  • Census of a:unknown(X/**/Y) and a:unknown(X/*c*/Y) with 32 tokens X and 36 tokens Y (2,304 cells). A cell is right when the printed arguments cut into the written tokens. 248 cells are right on main and wrong here, and 82 are wrong on main and right here. Main is right in 828 cells and this branch in 662.
  • In each of the 248, X or Y has an exponent, and main prints X with a dot and Y with a leading 0..
  • With a blank between the two tokens, alone or beside a comment (4,608 cells), no cell turns wrong and 144 turn right.
  • A custom property does not have this case, because css: consolidate the open CSS parser fixes #47025 made a comment there a blank: --x:1e3/**/.5 prints 1e3 .5.

Measurements (release builds of 11c3671 without and with this change, x64 Linux)

  • Instructions are counted with qemu-x86_64 -one-insn-per-tb -d exec,nochain -dfilter. raw_number_text runs 757,191 instructions in 11,380 calls for the 15 files of test/js/bun/css/files with --minify (main: 926,482). The fixtures have no exponent. 12 fixtures go down and 3 go up: animate.css +15,331, pico.css +6,773, hint.css +959.
  • Per token, on 1,000 tokens of each kind: no fraction 57 instructions (main: 62 to 95), with a fraction 128 to 164 (main: 79 to 88).
  • The other functions: llvm-objdump shows 0 differing instructions in consume_numeric, parse_into_impl, parse_raw, TokenList::to_css, to_css_raw, dtoa_short, serialize_dimension and write_numeric (4,263 instructions). consume_numeric runs 5,250,426 instructions in 42,751 calls on both builds.
  • Size: raw_number_text is 739 bytes (main: 385). .text is 138,011,154 bytes (main: 138,010,898). data and bss do not change. No type changes and nothing allocates.
  • Output: 9,320 of the 9,382 npm stylesheets build on both builds and 62 fail on both. 9 outputs differ (open-props 7, terminal.css 2) in 47 tokens, and each new token is in the source. The total goes from 337,737,442 to 337,737,307 bytes. The 15 fixtures print the same bytes with and without --minify.
  • Fuzz: 1,000,000 numeric tokens from a fixed seed, each in a custom property. 564,924 have an exponent by a port of consume_numeric. 387,841 print as written. The other 177,083 have an escape and print what main prints. No token without an exponent prints differently from main.

The escape exception. The kept text of 1e3\70 stops inside an escape. A blank that prints after it becomes part of the escape, and the unit reads on into the next token: a{--x:1e3\70/**/a} must stay 1000p a. The check is "the text has a backslash", so a unit whose escape is complete (1e3\70 x) also prints as on main.

Tests that moved. Two expectations in angle-serialization-hang.test.ts: ro: 1.3157e308rad and filter: hue-rotate(1e50rad) are token lists and print as written. The new row ro: 1e50ra\64 still sends a huge angle through a token list into Angle::to_css.

Not in this PR (the same on main and on this branch, unless a line says otherwise; I opened no tracking issues)

  • @media (foo:8e64) prints (foo:3.40282e38), and two such rules with different values merge. An unknown media feature does not pass raw_number_text (MediaFeatureValue, src/css/media_query.rs).
  • A typed calc() folds in f32: width:calc(7.3px*1e-39/1e-39) prints 3.40282e38px.
  • A number after a colour is glued to it: rgb(0 0 0)5 prints #0005. The colour arm of TokenList::to_css writes no separator. With this change rgb(0 0 0)5e-1 joins that case.
  • --v:1.0.0-beta.0 prints 1.0.0-beta0 and --x:1-0 prints 10. --x:1e3-.0 prints 10000 on main and 1e30 here.
  • a:unknown(1/**/5) prints a:unknown(15): the raw selector path drops a comment between two tokens. a:unknown(1e3/**/5) prints 1000.05 on main and 1e35 here. With this change a:unknown(1e3/**/.5) joins that case. The fix for all of them is a separator where the comment was, in TokenList::parse_raw.
  • The two existing cases keep text that stops inside an escape: a{--x:1234567\70/**/a} prints 1234567\70 a.
  • a{--x:1000\65 3} prints 1000e3: the printer escapes the unit e and e-, and not e3.
  • A decimal with no exponent that an f32 reads as 0 (0. and 49 zeros, then 1) prints 0.

Two questions for a maintainer

  • The exponent test of consume_numeric (css_parser.rs:4528) is now stated a second time, as a slice pattern in raw_number_text. One helper for both adds an edit to the function that every numeric token passes through. I can add it if you prefer one statement.
  • This is the third case in raw_number_text. If a fourth comes, the other design is to keep the text of every token-list number and to shorten it as text.

This is the exponent half of the first version of #46715.


[human-review] gate passed · iteration 0 · 3 files touched

fails on main (without fix)
ASAN without fix: 20 failed, 47 skipped
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/pr_gate.xml" test/js/bun/css/angle-serialization-hang.test.ts test/js/bun/css/css.test.ts
bun test v1.4.4 (367d939d9)

test/js/bun/css/css.test.ts:
Output .flexrow {
  flex-direction: row;
}

.flexcol {
  flex-direction: column;
}

.hello {
  flex-wrap: wrap;
}

.world {
  flex-wrap: nowrap;
}

(pass) css tests > .flexrow {
	flex-direction: row;
}

.flexcol {
	flex-direction: column;
}

.hello {
	flex-wrap: wrap;
}

.world {
	flex-wrap: nowrap;
} [3.68ms]
Output .foo {
  content: "+";
}

(pass) css tests > .foo {
  content: "\2b";
} [1.43ms]
Output div {
  --foo: 1 1 1 #0101011a, 2 2 2 #02020233;
  --bar: 1 1 1 #01010116, 2 2 2 #02020233;
}

(pass) css tests > custom property cases > div {
        --foo: 1 1 1 rgba(1, 1, 1, 0.1), 2 2 2 rgba(2, 2, 2, 0.2);
        --bar: 1 1 1 #01010116, 2 2 2 #02020233;
      } [1.74ms]
Output :root {
  --my-color: red;
  --my-bg: white;
  --my-font-size: 16px;
}

.element {
  color: var(--my-color);
  background-color: var(--my-bg);
  font-size: var(--my-font-size);
}

(pass) css tests > custom property cases > :ro
... (truncated)

release without fix: 20 failed, 47 skipped
bun test v1.4.4-canary.1 (51fd76213)

test/js/bun/css/css.test.ts:
Output .flexrow {
  flex-direction: row;
}

.flexcol {
  flex-direction: column;
}

.hello {
  flex-wrap: wrap;
}

.world {
  flex-wrap: nowrap;
}

(pass) css tests > .flexrow {
	flex-direction: row;
}

.flexcol {
	flex-direction: column;
}

.hello {
	flex-wrap: wrap;
}

.world {
	flex-wrap: nowrap;
} [0.90ms]
Output .foo {
  content: "+";
}

(pass) css tests > .foo {
  content: "\2b";
} [0.24ms]
Output div {
  --foo: 1 1 1 #0101011a, 2 2 2 #02020233;
  --bar: 1 1 1 #01010116, 2 2 2 #02020233;
}

(pass) css tests > custom property cases > div {
        --foo: 1 1 1 rgba(1, 1, 1, 0.1), 2 2 2 rgba(2, 2, 2, 0.2);
        --bar: 1 1 1 #01010116, 2 2 2 #02020233;
      } [0.08ms]
Output :root {
  --my-color: red;
  --my-bg: white;
  --my-font-size: 16px;
}

.element {
  color: var(--my-color);
  background-color: var(--my-bg);
  font-size: var(--my-font-size);
}

(pass) css tests > custom property cases > :root {
        --my-color: red;
        --my-bg: white;
        --my-font-size: 16px;
      }
      .element {
        color: var(--my-color);
        background-color: var(--my-bg);
        font-size: 
... (truncated)
passes on PR (with fix)
ASAN with fix: 47 skipped
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/pr_gate.xml" test/js/bun/css/angle-serialization-hang.test.ts test/js/bun/css/css.test.ts
bun test v1.4.4 (367d939d9)

test/js/bun/css/css.test.ts:
Output .flexrow {
  flex-direction: row;
}

.flexcol {
  flex-direction: column;
}

.hello {
  flex-wrap: wrap;
}

.world {
  flex-wrap: nowrap;
}

(pass) css tests > .flexrow {
	flex-direction: row;
}

.flexcol {
	flex-direction: column;
}

.hello {
	flex-wrap: wrap;
}

.world {
	flex-wrap: nowrap;
} [8.19ms]
Output .foo {
  content: "+";
}

(pass) css tests > .foo {
  content: "\2b";
} [1.92ms]
Output div {
  --foo: 1 1 1 #0101011a, 2 2 2 #02020233;
  --bar: 1 1 1 #01010116, 2 2 2 #02020233;
}

(pass) css tests > custom property cases > div {
        --foo: 1 1 1 rgba(1, 1, 1, 0.1), 2 2 2 rgba(2, 2, 2, 0.2);
        --bar: 1 1 1 #01010116, 2 2 2 #02020233;
      } [2.19ms]
Output :root {
  --my-color: red;
  --my-bg: white;
  --my-font-size: 16px;
}

.element {
  color: var(--my-color);
  background-color: var(--my-bg);
  font-size: var(--my-font-size);
}

(pass) css tests > custom property cases > :ro
... (truncated)

release with fix: 47 skipped
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped) in 18374ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[1/412] gen generated_host_exports.rs
generated_host_exports.rs: 111 exports (lazy=4, generic=107, rust=0); 264 extern-C blocks audited
[2/411] rustc bun_brotli 
[3/411] rustc bun_safety 
[4/411] rustc bun_zstd 
[5/411] rustc bun_picohttp 
[6/411] rustc bun_boringssl_sys 
[7/411] rustc bun_base64 
[8/411] rustc bun_cares_sys 
[9/411] rustc bun_ptr 
[10/411] rustc bun_zlib_sys 
[11/411] rustc bun_errno 
[12/411] rustc bun_output 
[13/411] rustc bun_text_diff 
[14/411] rustc bun_clap 
[15/411] rustc bun_tcc_sys 
[16/411] rustc bun_platform 
[17/411] rustc bun_valkey 
[18/411] rustc bun_lsquic_sys 
[19/411] rustc bun_paths 
[20/411] rustc bun_zlib 
[21/411] rustc bun_core 
[22/411] rustc bun_collections 
[23/411] rustc bun_shell_parser 
warning: `feature(generic_const_exprs)` is not supported with the next-generation trait solver
 --> src/shell_parser/lib.rs:1:30
  |
1 | #![feature(adt_const_params, generic_const_exprs, allocator_api)]
  |                              ^^^^^^^^^^^^^^^^^^^
  |
  = note: `-Zne
... (truncated)
diff hotspot
src/css/css_parser.rs                            | 31 +++++++++++++------
 test/js/bun/css/angle-serialization-hang.test.ts | 11 ++++---
 test/js/bun/css/css.test.ts                      | 39 ++++++++++++++++++++++++
 3 files changed, 68 insertions(+), 13 deletions(-)

gate history · 1 passed · 0 rejected · iteration 0

evidence per changed file
file                                              reads  edits  tests
src/css/css_parser.rs                                 7      7     17
test/js/bun/css/angle-serialization-hang.test.ts      2      2     10
test/js/bun/css/css.test.ts                           2      3     13

…e is kept as tokens

A value that the parser keeps as tokens (a custom property, an unparsed
property, a var() fallback, an unknown at-rule) printed a number such as
`8e64` from its f32. The f32 clamps `8e64` to `3.40282e38`, reads
`4.9406564584124654e-324` as 0 and rounds `1.6777217e7` to `16777218`.

`Parser::raw_number_text` now also returns the source text of a token
that has an exponent, unless its unit is written with an escape. A token
with no fraction and no exponent no longer walks its text.
@robobun

robobun commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 11:23 AM PT - Oct 29th, 2026

❌ @robobun, your commit 54a10f3 has some failures in Build #135219 (All Failures)


🧪   To try this PR locally:

bunx bun-pr 47222

That installs a local version of the PR into your bun-47222 executable, so you can run:

bun-47222 --bun

@robobun

robobun commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator Author

Status

Reproduced on main (11c3671) with bun build d.css --minify --outdir o, where d.css is:

:root{--commit:12e45ab;--tag:8e64;--shf:4.9406564584124654e-324;--uuid:12345678-12e3-8d9b-a456-426614174000}
  • Output on main, exit code 0: :root{--commit:3.40282e38ab;--tag:3.40282e38;--shf:0;--uuid:12345678-12000-8d9b-a456-426614174000}
  • Output with this PR: the source, byte for byte.

PR: #47222

CI (build 135219, head 54a10f3): the diff is green. 185 of 188 jobs passed and no test failed. The build is red because 3 darwin any x64 - test-bun jobs expired after 4.5 hours without an agent.

  • The fourth darwin any x64 - test-bun job passed. The 4 darwin any aarch64 - test-bun jobs and both macOS build jobs passed.
  • Every darwin any x64 - test-bun job passed for the earlier head 51fd762 (build 134583). The code of this PR is the same there, apart from one line of a doc comment.
  • All GitHub Actions checks pass on this head.
  • A retry of the 3 expired jobs is the only thing that CI still needs.

@coderabbitai

coderabbitai Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 6c3d1bf and 51fd762.

Files selected for processing: 3
  • src/css/css_parser.rs
  • test/js/bun/css/angle-serialization-hang.test.ts
  • test/js/bun/css/css.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 0 remain after this review.

Walkthrough

The CSS parser now preserves selected integer and exponent-form numeric token spellings. Regression tests cover token-list serialization, typed-value conversion, and angle serialization for large radian values.

Changes

CSS Numeric Token Preservation

Layer / File(s) Summary
Preserve numeric token spellings
src/css/css_parser.rs, test/js/bun/css/css.test.ts, test/js/bun/css/angle-serialization-hang.test.ts
raw_number_text checks integer magnitude, adjacency, number-text length, and unescaped exponent notation when deciding whether to preserve token text. Tests cover exponent forms, escaped units, typed-value conversion, and large radian values that remain token-preserved or reach Angle::to_css.

Possibly related PRs

  • oven-sh/bun#46715: Preserves long integer source text in token-list serialization; this PR extends that behavior to exponent-form numbers.
  • oven-sh/bun#47025: Changes the CSS parser and custom token serialization to preserve raw numeric token text, which is the same serialization path changed here.

Suggested reviewers: jarred-sumner

Priority: Normal

Merge Risk: Minimal · up to 51fd7

Exponent spellings are intended to remain intact in token-preserved values. No material merge-blocking risk was identified.

Pre-merge checks: Passed 4
Check name Status Explanation
Title check Passed The title clearly identifies the main change: preserving exponent-form CSS numbers when values remain tokenized. It is specific and related to the changeset, although slightly verbose.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Description check Passed The description follows the template's two required sections, "What does this PR do?" (as "Problem", "Fix", "Background", "Downsides") and "How did you verify your code works?" (as "Fix" verification …
Full details: Description check

Explanation

The description follows the template's two required sections, "What does this PR do?" (as "Problem", "Fix", "Background", "Downsides") and "How did you verify your code works?" (as "Fix" verification bullet plus detailed measurements). It explains the bug, the fix, the exception for escaped units, and the test expectations that changed. Verification names the test files run, the 19 failing rows on main, and the instruction and binary-size measurements. The description is very long and includes a large amount of supporting detail inside a collapsed notes block, but the required information is present and specific.

Finishing Touches: 💡 1
🛠️ Fix failing CI checks: 💡
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@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.

Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.

Beyond the inline findings, I also checked the integer fast path in raw_number_text against the old digit-count rule — the tokenizer's saturating as i32 cast makes |int| >= 1_000_000 match the old "more than 6 digits after stripping leading zeros" rule for every integer token, including 01000000, -1000000, and overflowing digit runs — and checked that has_exponent only matches the e/E runs consume_numeric consumes as an exponent, so 1e-x, 1em, and 1.e3 still print as before.

Extended reasoning...

The change extends one helper in src/css/css_parser.rs that decides whether a numeric token kept as raw tokens prints its source text, adding an integer short-circuit, exponent detection, and an escape guard; it touches no security-sensitive surface. Two inline findings were posted (a browser-visible behavior change for z-index:1e3, and a pre-existing escape-merging gap the new guard only partially covers), which already signal that a human should weigh the behavioral tradeoff, so this body only records what else was examined and ruled out.

Comment thread test/js/bun/css/css.test.ts Resolved
Comment thread src/css/css_parser.rs Resolved
@robobun

robobun commented Oct 28, 2026

Copy link
Copy Markdown
Collaborator Author

Reply to the review of 51fd762: no code change. The reasons and the evidence are in the two threads.

  • z-index:1e3 prints as written. This is the first Downsides bullet. Chrome 153 computes 7 for .a{z-index:7;z-index:1e3} and for the output of this branch, and 1000 for the output of main. The narrower rule (custom properties only) keeps width:calc(7.3px*0/0) for a calc() that divides by 4.9406564584124654e-324.
  • a{--x:1234567\70/**/a} prints 1234567\70 a on main and on this branch. The two arms that css: consolidate the open CSS parser fixes #47025 added keep that text. The fix for all three arms is a blank after such text in RawNumber::to_css. I kept it out of this PR, and I can add it if a maintainer wants it here.

@robobun

robobun commented Oct 29, 2026

Copy link
Copy Markdown
Collaborator Author

The description now states a second output that turns from right to wrong. No code change.

  • a:unknown(1e3/**/.5) prints a:unknown(1e30.5) on this branch. Main prints a:unknown(1000.00.5), which still cuts into 1000 and .5. It is in the second Downsides bullet.
  • The cause is older than this PR. TokenList::parse_raw drops a comment between two tokens in the arguments of a pseudo-class that Bun does not know: a:unknown(1/**/5) prints a:unknown(15) on main. Main keeps some pairs apart by accident, because it prints 1e3 as 1000.0 and .5 as 0.5.
  • The notes have a census (section "A comment between two numbers in :unknown()"). Of 2,304 cells with only a comment between two tokens, 248 turn wrong and 82 turn right. Of 4,608 cells with a blank between the tokens, none turns wrong.
  • A separator where the comment was fixes all of these cells. It is not in this PR. I can add it here if a maintainer wants it in the same change.

Comment thread src/css/css_parser.rs Resolved
@robobun

robobun commented Oct 29, 2026

Copy link
Copy Markdown
Collaborator Author

Merged main (da867c6) into this branch. The PR merges clean again.

@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.

Code review found no issues

No high-confidence issues detected in this change.

@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.

Code review completed

Nothing new to post: everything this review found is already covered by existing comments on this pull request or didn't merit a separate one.

This branch has not been deployed

No deployments
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.

1 participant