You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[css-values] specification for calc() should be clearer about when the result has a percentage #10017
There are a number of cases where layout algorithms in CSS (and perhaps other things) care about whether a value has a percentage. In particular, values that have a percentage are treated differently when there's nothing for the percentage to resolve against: inside of something with auto height a height: calc(30px) is a fixed value but a height: calc(30px + 0%) is treated as height: auto.
With calc() as it was specified in css-values-3, this could essentially be implemented as part of the type computation; a calc() expression could effectively be either a <length> or a <length-percentage> as its toplevel type, and those that were <length-percentage> act as though they have a percent.
Newer levels of the specification introduce features that make this approach insufficient:
(I think this is clearly defined for calc-size() because it's very important there, but I don't think it's clearly defined for the other cases.)
It should be clearer whether these other things "erase" the percentage-ness of their arguments when they erase the types of those arguments, or whether they still produce toplevel values that are treated as having a percentage.
Unit division is actually well-defined, by reference to Typed OM's concept of "matching". Something matches <length>/etc only if it has {length -> 1}and its percent hint is null. I suppose technically it's only by implication that a non-null percent hint means it contains a percentage, tho, so we could probably put in a definition for that.
In-flight edit: actually it looks like I am invoking the types wrong for cases like min(); there I just say the args must have "the same type" and define that the function resolves to that type. This doesn't handle percent hints properly. I'll need to tweak it.
sign() does indeed currently erase the percentage information, tho, as does anything that returns a value of a different type than its input calculations. We should think about whether it should transfer that information thru - doing so via the calculation type (copying the percent hint of the input calculation) would mean it doesn't match <number> anymore.
The progress() family just need to define their type as the sum of their input calculation's types, same as min()/etc. (Subject to the changes I just discovered I need to make.)
calc-size() makes an end-run around the issue, since it defines that it simply acts like its basis argument in all ways.
If they contain a percentage, I think they should be treated as containing a percentage, no matter how much nested into math functions it appears.
The exception should be e.g. height: mix(25%; 0px; 100px) where the 25% is not treated as per height rules, it's basically equivalent to height: calc(0.75 * 0px + 0.25 * 100px) so no percentage.
Oops, I had misunderstood the statement in determine the type of a calculation that "In all cases, the associated percent hint is null" as applying to the whole section rather than just the terminal values, so I thought that the calc() spec wasn't using the percent hint mechanism. But I realize (despite the perhaps unclear indentation) that I think that's applying only to the terminal values bullet, and it's the adding types rule that creates the percent hints.
Okay, fixed up the first two issues. After giving it some thought, I'm fairly certain that the type-changing functions should also retain the percent hint of their arguments. I think in practice it's fine; Typed OM has to worry about percentages that aren't yet resolved (thus the percent hint, which indicates what we've assumed the percent will resolve to), while in general use the calculation will know where it's used, so percents will all have a consistent type.
Still, tho, I'm gonna go ahead and define that they preserve the type hint.
I think I also agree that the type-changing functions should preserve the percent hint, with the probable exception of calc-size(). I think #10017 (comment) also agrees with that.
Is there someplace that defines that it's the percent hint that affects the layout behaviors I mentioned? I don't think defining whether the value is accepted for a <length-percentage> is alone sufficient to define that, and I didn't see it defined elsewhere.
(Also see #10018, which I filed as a separate issue since it's at least somewhat distinct.)
I mean that calc-size() should erase the percent hint that comes from the calculation part of its value (so that it doesn't have the layout effects). (I don't think this is problematic since I think calc-size() should effectively mandate that both arguments have (after applying a "length" percent hint) a type of «[ "length" → 1 ]», and the only question about its resulting type is whether there's a percent hint to propagate from the basis argument.
I also note that log() and exp() only accept calculation(s) resolving to , but their return type is not defined to be consistent or made consistent.
Whoops, that's an oversight on my part. In the source, there's a huge details block separating the log/exp definitions from the preceding pow/sqrt/hypot ones, so I just missed that they were there.
Re: the percent hint making things not match, I'm thinking now if I ever actually needed that restriction. What I really want to capture is the idea that the context the calculation finds itself in supplies the ability to resolve percentages (or not). I ran into this as an issue when dealing with progress()/*-mix(), and kinda manually hacked it in, but you're right that this is a larger issue.
Okay, yeah, I've gone ahead and loosened the text in Typed OM; it now just requires that the context in which the value is used allow percentages; if that's true, it can then match a <length> or whatever even with a non-null percent hint.
I've also tweaked <progress> in Values 5 to be consistent with this - a literal <percentage-token> used as a <progress> will map to the obvious <number>, but any other uses of percentages (aka, in a function) will resolve percentages as normal for the context.
The argument calculations in mod() and rem() should probably have a consistent type (like round()):
The modulus functions <dfn lt="mod()">mod(A, B)</dfn>
and <dfn lt=rem()>rem(A, B)</dfn>
[...]
The argument [=calculations=] can resolve to any <<number>>, <<dimension>>, or <<percentage>>,
- but must have the <em>same</em> [=determine the type of a calculation|type=],+ but must have a [=consistent type=]
or else the function is invalid;
- the result will have the same [=CSSNumericValue/type=] as the arguments.+ the result's type will be the [=consistent type=].
The input calculations in log() should probably have a consistent type (it has two arguments, like pow()):
The <dfn lt="log()">log(A, B?)</dfn> function
contains one or two [=calculations=]
[...]
which must resolve to <<number>>s,
and returns the logarithm base B of the value A,
as a <<number>>
- with the return type [=made consistent=]- with the input [=calculation’s=] type.+ The input [=calculations=]+ must have a [=consistent type=]+ or else the function is invalid;+ the result's type will be the [=consistent type=].
The return value type of random() should probably be consistent.
All of the [=calculation=] arguments can resolve to any <<number>>, <<dimension>>, or <<percentage>>,
- but must have the <em>same</em> [=determine the type of a calculation|type=], or else the function is invalid;- the result will have the same type as the arguments.+ but must have a [=consistent type=], or else the function is invalid;+ the result's type will be the [=consistent type=].
And <percentage> should probably be <percentage-token> in the production rule of <progress> (which should probably only allow omitting easing).
My first thought was that percentHint should not be checked at all (when matching a standalone dimension type or <number>), but I was not sure of the consequences.
I did not considered that the context should supply the ability to resolve percentages as a valid reason, because I thought hsl(calc(var(--progress-as-percentage, 0%) / 100%) 100 50) would be a valid use case, and it seemed inconsistent to allow hsl(calc(1px / 1px) 100 50) but not hsl(calc(1% / 1%) 100 50).
edit: hmm, no, there is no percent hint involved in my example, which is valid, but I do not see when a percent hint can appear when the context does not allow it.
An expression having a percent hint means that the expression only makes sense if percentages in that expression are the type of the percent hint. For example, calc(10px + 20%) has a percent hint of length and a type of «[ "length" → 1 ]» (the type for <length>), while calc((10px + 20%) / 15px) still has a percent hint of length but it has a type of «[ ]» (the type for <number>).
So if we don't check percent hints at all for values of type <number> then calc((10px + 20%) / 15px) would be accepted for any <number>, whereas it only really makes sense in a context that both (a) accepts <number>and (b) where percentages resolve to lengths.
Another relevant example showing something that we do want to accept is width: calc(log((10px + 20%) / 15px) * 3em). The log() function accepts only <number> and in this case it is being used in a context where percentages are lengths. So log() allows the number it accepts to have a percent hint, and the percent hint needs to be present on the result type of log() as well, so that whatever contains the log() will eventually check that the context is one where percentages are lengths.
I think the presence of a percent hint should indicate that the context allows resolving percentages as that type.
So in border-width: calc(log((10px + 20%) / 15px) * 3em) it doesn't make sense for 10px + 20% to have type «[ "length" → 1 ]» with a length percent hint. "add two types" should just return failure because border-width doesn't resolve percentages as lengths.
(Thanks for the examples. I missed that a percentage can get a percent hint when it is added to a dimension type.)
Returning failure when adding a percentage type without a percent hint to anything but a percentage without a percent hint, also makes sense to me.
edit: no because "when you're doing operations on plain CSSNumericValue objects, you don't yet know the context they'll be used in, so you can't tell whether a % will become a length/etc" (#10727 (comment)).
Activity
tabatkins commentedon Feb 29, 2024
Unit division is actually well-defined, by reference to Typed OM's concept of "matching". Something matches
<length>/etc only if it has{length -> 1}and its percent hint is null. I suppose technically it's only by implication that a non-null percent hint means it contains a percentage, tho, so we could probably put in a definition for that.In-flight edit: actually it looks like I am invoking the types wrong for cases like min(); there I just say the args must have "the same type" and define that the function resolves to that type. This doesn't handle percent hints properly. I'll need to tweak it.
sign() does indeed currently erase the percentage information, tho, as does anything that returns a value of a different type than its input calculations. We should think about whether it should transfer that information thru - doing so via the calculation type (copying the percent hint of the input calculation) would mean it doesn't match
<number>anymore.The progress() family just need to define their type as the sum of their input calculation's types, same as min()/etc. (Subject to the changes I just discovered I need to make.)
calc-size() makes an end-run around the issue, since it defines that it simply acts like its basis argument in all ways.
Loirooriol commentedon Feb 29, 2024
If they contain a percentage, I think they should be treated as containing a percentage, no matter how much nested into math functions it appears.
The exception should be e.g.
height: mix(25%; 0px; 100px)where the25%is not treated as perheightrules, it's basically equivalent toheight: calc(0.75 * 0px + 0.25 * 100px)so no percentage.@tabatkins You are mentioning types, but percentages in
heightjust have a «[ "length" → 1 ]» type, with null percent hint. See https://drafts.csswg.org/css-values-4/#determine-the-type-of-a-calculationtabatkins commentedon Feb 29, 2024
Yup, that's also part of what I noticed was wrong. Percents resolved against another type should gain that as a percent hint.
dbaron commentedon Feb 29, 2024
Oops, I had misunderstood the statement in determine the type of a calculation that "In all cases, the associated percent hint is null" as applying to the whole section rather than just the terminal values, so I thought that the
calc()spec wasn't using the percent hint mechanism. But I realize (despite the perhaps unclear indentation) that I think that's applying only to the terminal values bullet, and it's the adding types rule that creates the percent hints.tabatkins commentedon Feb 29, 2024
Yeah, I'm gonna do a quick rewrite of that bit, I also read it wrong before realizing the scope of that line. :(
[css-values-4] Define that a percent resolved against another type ha…
[css-values-4] Better define how math functions combine their argumen…
tabatkins commentedon Feb 29, 2024
Okay, fixed up the first two issues. After giving it some thought, I'm fairly certain that the type-changing functions should also retain the percent hint of their arguments. I think in practice it's fine; Typed OM has to worry about percentages that aren't yet resolved (thus the percent hint, which indicates what we've assumed the percent will resolve to), while in general use the calculation will know where it's used, so percents will all have a consistent type.
Still, tho, I'm gonna go ahead and define that they preserve the type hint.
dbaron commentedon Feb 29, 2024
I think I also agree that the type-changing functions should preserve the percent hint, with the probable exception of
calc-size(). I think #10017 (comment) also agrees with that.Is there someplace that defines that it's the percent hint that affects the layout behaviors I mentioned? I don't think defining whether the value is accepted for a
<length-percentage>is alone sufficient to define that, and I didn't see it defined elsewhere.(Also see #10018, which I filed as a separate issue since it's at least somewhat distinct.)
[css-values-4] Make the type-changing math functions also carry the p…
tabatkins commentedon Feb 29, 2024
Yup, that's now in the spec.
What do you mean by this?
No, not yet. Values is probably the right place to define it, one sec.
dbaron commentedon Feb 29, 2024
I mean that
calc-size()should erase the percent hint that comes from the calculation part of its value (so that it doesn't have the layout effects). (I don't think this is problematic since I thinkcalc-size()should effectively mandate that both arguments have (after applying a "length" percent hint) a type of «[ "length" → 1 ]», and the only question about its resulting type is whether there's a percent hint to propagate from the basis argument.[css-values-4][css-sizing-3] Define 'contains a percentage', ref it i…
8 remaining items
Loirooriol commentedon Mar 4, 2024
Mm, yeah, I guess
sqrt()& friends should ignore the percent hint when validating the input?[css-values-4] Make log/exp use the consistency terminology. #10017
tabatkins commentedon Mar 4, 2024
Whoops, that's an oversight on my part. In the source, there's a huge details block separating the log/exp definitions from the preceding pow/sqrt/hypot ones, so I just missed that they were there.
tabatkins commentedon Mar 4, 2024
Re: the percent hint making things not match, I'm thinking now if I ever actually needed that restriction. What I really want to capture is the idea that the context the calculation finds itself in supplies the ability to resolve percentages (or not). I ran into this as an issue when dealing with progress()/*-mix(), and kinda manually hacked it in, but you're right that this is a larger issue.
Let me fiddle with the wording a bit.
[css-values-5] Tweak the wording of <progress> to only allow literal …
tabatkins commentedon Mar 4, 2024
Okay, yeah, I've gone ahead and loosened the text in Typed OM; it now just requires that the context in which the value is used allow percentages; if that's true, it can then match a
<length>or whatever even with a non-null percent hint.I've also tweaked
<progress>in Values 5 to be consistent with this - a literal<percentage-token>used as a<progress>will map to the obvious<number>, but any other uses of percentages (aka, in a function) will resolve percentages as normal for the context.cdoublev commentedon Mar 5, 2024
The argument calculations in
mod()andrem()should probably have a consistent type (likeround()):The input calculations in
log()should probably have a consistent type (it has two arguments, likepow()):The return value type of
random()should probably be consistent.And
<percentage>should probably be<percentage-token>in the production rule of<progress>(which should probably only allow omitting easing).cdoublev commentedon Mar 5, 2024
My first thought was that
percentHintshould not be checked at all (when matching a standalone dimension type or<number>), but I was not sure of the consequences.I did not considered that the context should supply the ability to resolve percentages as a valid reason, because I thought
hsl(calc(var(--progress-as-percentage, 0%) / 100%) 100 50)would be a valid use case, and it seemed inconsistent to allowhsl(calc(1px / 1px) 100 50)but nothsl(calc(1% / 1%) 100 50).edit: hmm, no, there is no percent hint involved in my example, which is valid, but I do not see when a percent hint can appear when the context does not allow it.
dbaron commentedon Mar 5, 2024
An expression having a percent hint means that the expression only makes sense if percentages in that expression are the type of the percent hint. For example,
calc(10px + 20%)has a percent hint oflengthand a type of«[ "length" → 1 ]»(the type for<length>), whilecalc((10px + 20%) / 15px)still has a percent hint oflengthbut it has a type of«[ ]»(the type for<number>).So if we don't check percent hints at all for values of type
<number>thencalc((10px + 20%) / 15px)would be accepted for any<number>, whereas it only really makes sense in a context that both (a) accepts<number>and (b) where percentages resolve to lengths.dbaron commentedon Mar 5, 2024
Another relevant example showing something that we do want to accept is
width: calc(log((10px + 20%) / 15px) * 3em). Thelog()function accepts only<number>and in this case it is being used in a context where percentages are lengths. Solog()allows the number it accepts to have a percent hint, and the percent hint needs to be present on the result type oflog()as well, so that whatever contains thelog()will eventually check that the context is one where percentages are lengths.Loirooriol commentedon Mar 5, 2024
I think the presence of a percent hint should indicate that the context allows resolving percentages as that type.
So in
border-width: calc(log((10px + 20%) / 15px) * 3em)it doesn't make sense for10px + 20%to have type «[ "length" → 1 ]» with a length percent hint. "add two types" should just return failure becauseborder-widthdoesn't resolve percentages as lengths.cdoublev commentedon Mar 6, 2024
(Thanks for the examples. I missed that a percentage can get a percent hint when it is added to a dimension type.)
Returning failure when adding a percentage type without a percent hint to anything but a percentage without a percent hint, also makes sense to me.
edit: no because "when you're doing operations on plain
CSSNumericValueobjects, you don't yet know the context they'll be used in, so you can't tell whether a % will become a length/etc" (#10727 (comment)).Disallow percentage in calc() for <length>
Disallow percentage in calc() for <length>
Bug 2077616 [wpt PR 63209] - Disallow percentage in calc() for <lengt…