Skip to content

fix(css): resolve <number> operands in calc() - #44

Merged
LastLeaf merged 3 commits into
wechat-miniprogram:masterfrom
gaojunran:fix/calc-number-operands
Oct 26, 2026
Merged

LastLeaf merged 3 commits into
wechat-miniprogram:masterfrom
gaojunran:fix/calc-number-operands

Conversation

@gaojunran

@gaojunran gaojunran commented Oct 22, 2026 •

Copy link
Copy Markdown
Member

Problem

calc() cannot resolve a <number> operand, so several spec-valid expressions silently fall back to the property default:

expression expected before
calc(0.5 * (1rem - 16px) + 17px) 17px @ 16px base unresolvable
calc(2 * min(10px, 5vw)) 20px unresolvable
calc((100% - 10px) / 2) 3px (16px reference) unresolvable
calc((1rem - 16px) * (1 + 0.5)) 12px @ 24px base parse error

The first one is the half-scale formula used by the official miniprogram-elder-transform toolchain for font scaling, and float-pigment-css/tests/calc.rs already carries a FIXME for exactly that expression.

Root cause

Two independent gaps, both around <number>:

  1. Resolve time. CalcExpr::resolve_to_f32 returns None for CalcExpr::Number, i.e. both operands of * and / had to resolve as lengths. Per css-values-4 §10.9 a <number> is the empty type map, so length * number and length / number are still lengths. Worse, the parser folds every literal case and rejects length * length, so any Mul/Div node that survives parsing necessarily has a <number> operand — the old code resolved none of them.
  2. Parse time. * and / fold their both-number operands into a literal, but + and - keep a Plus(Number, Number) tree, which an enclosing * then rejects because neither operand is a CalcExpr::Number.

Fix

  • Add CalcExpr::resolve_number(), which resolves the scalar side of a * or /; Mul and Div use it for the right operand.
  • Fold a numeric sum in parse_calc_sum_expr, mirroring the product rule, so a numeric sub-expression always ends up as a literal no matter where it appears — parenthesized, inside min() / max() / clamp(), or at the top level.

A bare number (calc(2 * 3)) and a number added to a length (calc(2 + 3px)) are still rejected, as required by the type rules.

`calc(0.5 * (1rem - 16px))` — the half-scale formula used by the official
elder-transform toolchain — is parsed as `Mul(Sub(1rem, 16px), Number(0.5))`
(the parser normalizes the number into the right operand), because the
operands cannot be folded at parse time. `resolve_to_f32` returned `None`
for `CalcExpr::Number`, so the whole expression was unresolvable and the
property silently fell back to its default value.

Resolve `<number>` nodes when they are the factor of a `*` or the divisor of
a `/`, as `<number>` is the empty type map in css-values-4 §10.9: a bare
number (`calc(2 * 3)`) and a number added to a length (`calc(2 + 3px)`) are
still rejected.
`calc((1rem - 16px) * (1 + 0.5))` failed to parse: `*` and `/` fold their
numeric operands at parse time, but `+` and `-` kept a `Plus(Number, Number)`
tree, which the enclosing `*` rejected because neither operand was a number.

Fold a numeric sum the same way, so a numeric sub-expression always ends up
as a literal no matter where it appears.
@gaojunran
gaojunran force-pushed the fix/calc-number-operands branch 2 times, most recently from 2400c0e to 6076d26 Compare October 22, 2026 05:27
`font-size: calc(17px + 0.5 * (1rem - 16px))` used to resolve to the
inherited 16px because the `<number>` operand was unresolvable; it now
resolves to 17px at the 16px root font size.

The tree still keeps `Mul(Sub(1rem, 16px), Number(0.5))` instead of
distributing it into `Plus(Px(9), Rem(0.5))`. That is the algebraic
simplification described in css-values-4 §10.10.1, which is a separate
piece of work and does not change the resolved value.
@gaojunran
gaojunran force-pushed the fix/calc-number-operands branch from 6076d26 to 85b7807 Compare October 22, 2026 05:33
@gaojunran
gaojunran requested review from LastLeaf and TtTRz October 22, 2026 06:00

@TtTRz TtTRz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

@LastLeaf
LastLeaf merged commit 6a984bf into wechat-miniprogram:master Oct 26, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants