Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[css-values-5] we should ensure an if with no fallback can be reasoned about later #10956

Open
keithamus opened this issue Sep 26, 2024 · 4 comments

Comments

@keithamus
Copy link
Member

keithamus commented Sep 26, 2024

/If the if conditionals (#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:

--my-hue: if(style(max-width: 400px): 300);
background: oklch(70% 0.1 var(--my-hue, 0)) /* This should fallback to zero */
--my-lch: if(style(max-width: 400px): 300);
--my-keyword: if(style(max-width: 500px): red);

background: if(
  var(--my-keyword): var(--my-keyword); /* when 500px this should resolve to `red` */
  else: oklch(70% 0.1 var(--my-hue, 0)); /* otherwise we might have an okclh hue */
); // This should be `red` or `oklch`

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,

@LeaVerou
Copy link
Member

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

@keithamus
Copy link
Member Author

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.

@tabatkins
Copy link
Member

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:

  1. We could add a test to 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.
  2. We could finally add a reserved global keyword that is guaranteed-invalid. If we do this, I propose we spell it 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.

@tabatkins tabatkins removed the Agenda+ label Nov 5, 2024
@tabatkins
Copy link
Member

tabatkins commented Nov 5, 2024

Deferring this until we precisely nail down the execution/substitution model for nested substitution functions. (#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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants
@keithamus @LeaVerou @tabatkins and others