Csswg-drafts: [css-values] Clarify that calc(0) is never a length

Created on 3 Dec 2019  Â·  10Comments  Â·  Source: w3c/csswg-drafts

Inspired by this twitter thread, where some people I'd consider very informed CSS devs were suprised to discover that calc expressions evaluating to zero are invalid as lengths.

I couldn't find a good spec reference to point to as a authoritative explanation. There are two relevant sections of CSS Values, but they don't currently cross-link in a useful way.

"Real Numbers: the <number> type" states:

The value <zero> represents a literal number with the value 0. Expressions that merely evaluate to a <number> with the value 0 (for example, calc(0)) do not match <zero>; only literal <number-token>s do.

However, the <zero> production is not referenced anywhere other than that paragraph.

"Distance Units: the <length> type" states

For zero lengths the unit identifier is optional (i.e. can be syntactically represented as the <number> 0).

(…and then goes on to talk about how interpretation as a number takes precedence if the property accepts either number and length.) But nothing about "Expressions that merely evaluate" to zero.

The official specification of the "calc(0) is not a length" rule would come from the calculation type-checking rules, but those are rather long and convoluted and not something most authors would read.

A more prominent note in the number/length sections would be helpful, either making use of the <zero> type or getting rid of it.

Closed Accepted as Editorial css-values-3 css-values-4

Most helpful comment

Honestly, I'm not sure that @Loirooriol's quote is the perfect match here. The twitter thread referenced above seems to be about the properties that accept only <length-percentage>, so I don't see how the rule for _properties_ legitimately accepting _both_ <number> and <length> applies here.

As I understand the problem, we have two 'kinds' of the numeric zero value – the literal one and the calculated one – and the question is why the latter is different from the former when it comes to casting to <length>. The quote, on the contrary, considers two potential interpretation of the same _literal_ zero number.

IMO, the notion of the <zero> sub-type seems to be the better option to clear the confusion. Replacing

(i.e. can be syntactically represented as the <number> 0).

with

(i.e. can be syntactically represented as <zero>).

seems to completely solve the issue to me.

All 10 comments

I think the important spec quote is in https://drafts.csswg.org/css-values/#length-value:

However, if a 0 could be parsed as either a <number> or a <length> in a property (such as line-height), it must parse as a <number>.

Then, when https://drafts.csswg.org/css-values/#calc-type-checking could parse 0 in calc() as either a <number> or <length>...

  • <number>
  • <integer>
    the type is «[ ]» (empty map)
  • <length>
    the type is «[ "length" → 1 ]»

...it must do it as a <number>. So it follows that calc(0) is not a length.

Good point, Oriol.

We could follow the paragraph your quoted (on ambiguous types) with a note: “This means that, inside a mathematical function, a 0 is always parsed as a number and not as a length. See [Type Checking].” That should handle the clarification for authors.

My point about the <zero> definition still stands, though: either use it or lose it!

Regarding <zero>, it seems it's used in https://drafts.csswg.org/css-transforms-2/#three-d-transform-functions

Honestly, I'm not sure that @Loirooriol's quote is the perfect match here. The twitter thread referenced above seems to be about the properties that accept only <length-percentage>, so I don't see how the rule for _properties_ legitimately accepting _both_ <number> and <length> applies here.

As I understand the problem, we have two 'kinds' of the numeric zero value – the literal one and the calculated one – and the question is why the latter is different from the former when it comes to casting to <length>. The quote, on the contrary, considers two potential interpretation of the same _literal_ zero number.

IMO, the notion of the <zero> sub-type seems to be the better option to clear the confusion. Replacing

(i.e. can be syntactically represented as the <number> 0).

with

(i.e. can be syntactically represented as <zero>).

seems to completely solve the issue to me.

@SelenIT I think your reading of the quote is too strict. It says "could be parsed [...] in a property", so if you have margin: calc(0), the 0 can still be parsed as either a <number> or a <length> when finding the type of the calculation in the margin property, even if margin doesn't accept <number>.

If we can't have that then I think your proposal doesn't completely solve the problem either, because when finding the type of calc(0), the 0 does match <zero> and thus could be a <length>, so calc(0) could have type «[ "length" → 1 ]», and therefore margin: calc(0) could be valid even if calc(0) is not a dimension nor <zero>.

@Loirooriol now I feel confused a bit about the meaning of “parsed as … in the property”. Initially, I read it as “would produce a valid value for that property if parsed that way”, since otherwise it didn’t make sense for me (if we don’t care about the result, then everything can be “parsed” as anything anywhere). On reflection, it seems I see your reasoning that it’s all about the _parse-time_, and the calculation “doesn’t know” in which context its result will be used in yet. Do I understand it correctly that the essential difference between literal values and calculations is that the former can be checked for correctness in the filtering phase of the cascade, while the latter become declared values anyway in all cases except the parsing error?

@SelenIT The thing with calc() is that you don't know whether it's e.g. a valid length until you determine the type of the calculation and get «[ "length" → 1 ]». So more than “would produce a valid value for that property if parsed that way” I think it's “could produce a valid value for that property if parsed that way”.

But I agree “parsed as … in the property” is a bit ambiguous. A possible way to clarify this could be:

  • Allow <zero> in lengths, as you suggested
> (i.e. can be syntactically represented as `<zero>`).
  • In calc() type checking make it explicit that only a <length> different than <zero> can get the «[ "length" → 1 ]» type.

    • <length> different than <zero>
      the type is «[ "length" → 1 ]»
  • Keep the note about <number> taking precedence over <length>, to still cover things like line-height: 0.

The spec for calc() (once you follow the definitions...) defines that the type of calc(0) is <number>, not <length>.

But yes, I agree with @SelenIT's comment that explicitly referencing <zero> in the definition of zero-lengths would make things a little clearer.

<zero> isn't defined in L3, only in L4. So either we only take the fix (which is a clarification afaict anyway) in L4, or we backport the definition of <zero> to L3. Not sure which option to take here...

I would prefer fixing it in L4 only if L3 doesn't contain that section, since L4 is marked as "current work" and we should probably finish L3 (by publishing a REC) instead of backporting changes.

Was this page helpful?
0 / 5 - 0 ratings