Open keithamus opened 1 month ago
@keithamus I don't understand the second example, I think there may be a typo there? The first one won't work with the current behavior, but that’s a bit of a special case (empty values are valid in custom properties). For non-custom properties, if their whole value is if()
they would become IACVT if nothing matches. However, in the general case, an empty token stream is more composable, as you can have multiple sequential if()
that are independent and compose a value, whereas if a single if()
makes the whole declaration IACVT, that limits what you can do.
I suspect your first example can be rewritten, but without a real-world use case it's hard to suggest how.
I think they could both be rewritten to avoid if/else but I was trying to demonstrate a property of the if result is that it should be introspectable in subsequent ifs.
Agenda+ because, on review, this case isn't actually possible! Earlier, I thought you could just make one of the if()
branches resolve to the guaranteed-invalid value and suggested, as an example, using an invalid var()
. But that makes the whole property it's in invalid unconditionally (either at parse time, if it's grammatically invalid, or at substitution time, if it's cyclic or non-existent); it doesn't let you just get the invalidity in the one if()
branch.
So, there's no way for an if()
in a custom property, currently, to later be introspected for validity.
I can see two possible fixes:
if()
for empty streams, like if(empty(var(--foo)): ...)
, which is true if it's empty (importantly, after substitution). This would let you test for if an if()
failed all its conditions.guaranteed-invalid
, which is a bit past our normal spelling-complexity limits, but makes me laugh. Then, you could write --foo: if(...; else: guaranteed-invalid)
, and then var(--foo, 0)
will trigger fallback if the if()
hit its else
clause, because at evaluation time it resolved to the guaranteed-invalid value, which is what actually triggers fallback in var().Possibly we should do both; testing if a variable is empty might be useful regardless, even if the emptiness came from something other than an if()
that failed all its tests.
Deferring this until we precisely nail down the execution/substitution model for nested substitution functions. (https://github.com/w3c/csswg-drafts/issues/11144)
If #11144 goes as I expect, then this case is possible. An invalid variable in the value of a branch will just become the guaranteed-invalid value, which is allowed; only when that branch is selectede for substitution will it trigger and turn the whole thing invalid. That is, if((1 > 2): bar; else: var())
will successfully parse, then during substitution it'll fall down to the else
clause and substitute itself with the guaranteed-invalid value. This can then be caught by later var(...)
usage, or even first-valid(...)
, to replace with a different fallback value.
/If the if conditionals (https://github.com/w3c/csswg-drafts/issues/10064) have an open question as to whether a condition without a valid fallback should be empty token stream or IACTV. I believe we should consider how these can compose and how user can reason about whether or not the condition resolved to one-or-the-other where possible. Some contrives examples:
I'm unsure if IACTV vs empty token stream precludes conditions such as these but I think if they do that should be a deciding factor,