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][various] Better handling of arguments with commas #9539

Closed
tabatkins opened this issue Oct 30, 2023 · 69 comments
Closed

[css-values-5][various] Better handling of arguments with commas #9539

tabatkins opened this issue Oct 30, 2023 · 69 comments

Comments

@tabatkins
Copy link
Member

We've got several in-flight proposals for functions that can take "whole property value" arguments, like mix(), random-item(), etc. (And now custom functions and mixins, which @mirisuzanne and I are looking into more seriously again.) Because these arguments can potentially contain commas (say, mixing between two background-image lists), we can't use commas as the argument separator, and the proposals currently all use semicolons as the separator instead, as this is guaranteed to never show up at the top-level of a property value.

Even tho I'm the one that started this trend, I don't like it. ^_^ I'd like to find a different solution. The problems are:

  • It's obviously a bit inconsistent with existing functions. It's not terrible, but it's not good either.
  • A lot of userland tools use very simplistic parsers, and divide properties by just splitting on semicolons. This'll break them until they rewrite to be aware of nesting levels for matched parens.
  • Most uses of these functions don't need the special separator; it's only the rare uses that involve operating on an entire comma-separated list that do. Forcing people to do a weird thing for a case they'll rarely hit isn't great.
  • It's not forward-compatible in some cases. We have to design this syntax into the function right from the beginning, and can't ever expand an existing comma-separated function into it (unless we do a grammar split, which is nasty in its own way).
  • The var() function already lets you take this kind of argument, but because its syntax was designed to take only one, and as the final argument, it can continue to use comma separation. (That is, you can write var(--foo, blue, red, black) - that's two args, a -foo and a blue, red, black list.)

The obvious solution is to add an optional wrapper around arguments, which will "hide" the commas from the outer function. It would only be necessary when someone is actually passing a comma-separated argument.

I have two possible suggestions, and am happy with either:

  • Use [], like background: mix(50%, url(start), [ url(foo), url(bar) ]);
  • Use a new, generically named function, like item(): background: mix(50%, url(start), item( url(foo), url(bar) ));

This notation would only be allowed in the functions that can take these problematic values, and if present, would be stripped for the purpose of the actual value. No nesting - if you were mixing a subgridded "line names only" value for grid-template-columns, for example, you'd write it as grid-template-columns: mix(50%, [[foo bar]], [[baz qux]]), so the outer [] gets stripped and the inner [] is part of the actual value.

(I'm rejecting using naked () for the same reason we ended up switching away from them for grid lines - it would mess up Sass syntax, and possibly others, hardcore, since in Sass it indicates math. I'm rejecting {} for "naive userland tools" reasons; again, many just split the text with regexes, and stray {} can screw them up. They're already potentially broken for custom props containing these chars, of course, but that's not an official Part Of CSS like this would be.)

Thoughts?

@romainmenke
Copy link
Member

romainmenke commented Oct 30, 2023

Naked () seems like the most obvious candidate imho, even if that is already a thing in Sass.
It is also the kind of blocks used to group items in supports and media queries, so it's consistent to keep using it for grouping.

Using [] seems confusing and I personally always forget that this is a thing for grid and have to look it up each time.

A generic function would not be bad, but not as good as naked ().

@Loirooriol
Copy link
Contributor

You mention var(). So let's say somebody is using

.subgrid-2 {
  grid-column: span 2;
  grid-template-columns: subgrid var(--lines-1, [start]) var(--lines-2, [middle]) var(--lines-3, [end]);
}

Currently computes to subgrid [start] [middle] [end], you proposal would result in an invalid subgrid start middle end, so it would become invalid at computed-value time?

@tabatkins
Copy link
Member Author

Nah, I mention var() as an example, but it doesn't need to use this syntax. (But if we do item(), especially, it probably could. It just wouldn't ever be necessary, since it's a single trailing argument.)

@tabatkins
Copy link
Member Author

Naked () seems like the most obvious candidate imho, even if that is already a thing in Sass.
It is also the kind of blocks used to group items in supports and media queries, so it's consistent to keep using it for grouping.

Like I said, the WG already attempted to use naked (), and switched away from it, for exactly the reasons I gave. They're no less true now, and there's no particularly good reason to be inconsistent with our past decision here, so () is already off the table.

@SebastianZ
Copy link
Contributor

I'd be happy with either bare () or a new function. [] would also be ok, though I somewhat dislike the double meaning of them regarding the use in grid-template-*. (They might not have been the best choice for naming grid lines, though that ship has sailed.)
I think we agree that we should not use {}. Not only because of "naive userland tools" reasons but also because they indicate a rule body.

Sebastian

@fantasai
Copy link
Collaborator

fantasai commented Nov 3, 2023

Another possibility is continuing to use , as a separator, but allowing authors to upgrade to ; whenever it's needed for disambiguation. Running across a ; would invalidate the , grammar, and re-interpret the function as using ; separators. We could even do this across all of CSS for consistency.

@tabatkins
Copy link
Member Author

Yeah, I think the "allow comma or semi anywhere" works pretty reasonably, actually. We'd amend the grammar a bit so that commas in functions can also be semicolons (all at once, not on an individual level), but commas are already pretty magical in the grammar anyway.

I'm happy with either approach - "upgradeable" commas or a wrapper. We should get the WG's opinion and fix the Values 5 features accordingly.

@Loirooriol
Copy link
Contributor

Regarding the upgradeable commas.

Let's say a property has grammar <length>#{2,}. Then mix(50%, 1px, 2px, 3px, 4px) is not ambiguous (1st value must be 1px, 2px, and 2nd value 3px, 4px).

But later on we relax the grammar to allow <length>#. Then mix(50%, 1px, 2px, 3px, 4px) suddenly becomes ambiguous!

So even if not ambiguous, I would prefer to forbid values from having a top-level comma when the author uses , instead of ;. With the exception of var() to avoid breaking things.

@fantasai
Copy link
Collaborator

@Loirooriol 100% agree. If there are any commas within the values, you should have to use semicolon for the top level.

@tabatkins
Copy link
Member Author

Let's say a property has grammar <length>#{2,}

We simply wouldn't design such a grammar, so any questions about how it would behave aren't relevant, right?

@Loirooriol
Copy link
Contributor

Loirooriol commented Nov 21, 2023

Well, let me just tweak it a bit: cursor: toggle(url(icon1.ico), pointer, url(icon2.ico), move).

This is not ambiguous: 1st value must be url(icon1.ico), pointer, 2nd value must be url(icon2.ico), move. But I don't think it should be allowed with that meaning. Somebody who is not familiar with the grammar of cursor will probably assume it means toggle(url(icon1.ico); pointer; url(icon2.ico); move). And even if not likely, we will have a problem if at some point we decide to relax the grammar to allow lone urls.

@fantasai
Copy link
Collaborator

@Loirooriol I think what happens in that case is that the comma is interpreted as a top-level comma (i.e. separating toggle() arguments). Since we don't hit a semicolon, it parses cleanly and we're done.

If the author wants to include commas in their value, then they have to use semicolons to separate top-level arguments. We notice that's happening because as we're parsing the function, we hit a semicolon; that requires us to go back and reparse.

It does mean that you can't pass to a variable-length function a single argument that includes commas, but... that's probably fine?

@tabatkins
Copy link
Member Author

Right, I think that @Loirooriol is just saying that they'd prefer we explicitly state that, in cases where you could potentially include a value with commas in it, you can only do so if you use semicolons; we will never parse a production that includes commas alongside other comma-separated arguments.

We'd probably address this at the level of where we define functional notation; we'd say that, unless otherwise specified (such as in var()), commas always separate arguments (aka are top-level in the function's grammar, rather than being claimable by productions) unless a semicolon is present instead. This might require a little bit of review of existing functions, like gradients have commas in their color-list production and will need that explicitly called out as allowed.

(Or maybe we actually reify the concept of "arguments" and say that commas are solely for separating arguments, unless semicolons are used instead. But that'd require a larger review of existing spec text. Might still be a good idea, tho.)

@LeaVerou
Copy link
Member

I think a new generic function is the least weird of the proposed solutions, and the most forwards compatible (I propose value()). Special syntax like brackets removes that syntax from the design space for future properties.

Another possibility is continuing to use , as a separator, but allowing authors to upgrade to ; whenever it's needed for disambiguation. Running across a ; would invalidate the , grammar, and re-interpret the function as using ; separators. We could even do this across all of CSS for consistency.

I don’t like this at all.

  1. It includes the same cons as semicolons (breaks naive parsers)
  2. It introduces more inconsistency: if authors see a mix() with semicolons in the wild, it's not clear it also accepts commas and vice versa.
  3. Worst of all: it introduces potential breakage that is hard to track down. E.g. is mix(50%, var(--foo), var(--bar)) correct? Who knows!

@Loirooriol
Copy link
Contributor

Would you require value() always or only to wrap values with top-level commas?
Also I don't see the problem with mix(50%, var(--foo), var(--bar)), it's typical for var() that you don't know whether the result will be valid or not. But at least we know that mix() has 3 arguments, and there are already two commas. So it will be valid iff the values of --foo and --bar are valid for the property, and they do not contain top-level commas.

@LeaVerou
Copy link
Member

Would you require value() always or only to wrap values with top-level commas?

This is answered in the OP.

@tabatkins
Copy link
Member Author

Worst of all: it introduces potential breakage that is hard to track down. E.g. is mix(50%, var(--foo), var(--bar)) correct? Who knows!

This applies to a "wrap the arguments in a function" case too. What you've written also might be correct, or not, depending on the exact same thing - whether the variables contain a comma or not.

In either case, an author can defensively mark the function, as mix(50%; var(--foo); var(--bar)) or mix(50%, item(var(--foo)), item(var(--bar))), and it'll be correct regardless of what the variable is.

It introduces more inconsistency: if authors see a mix() with semicolons in the wild, it's not clear it also accepts commas and vice versa.

I'm also not sure how this doesn't apply to your preferred solution as well. If authors see a mix() with item()-wrapped arguments, isn't it exactly as unclear whether it can accept values without the item() wrapper too?

@kbabbitt
Copy link
Collaborator

I lean towards a new generic function as well. If ; were allowed as a function argument separator, I could see problems stemming from the inability to put ; in a variable value. I couldn't come up with a realistic example, but here's a contrived one:

.foo { background-image: mix(50%; var(--mix-or-toggle-me)); }

.bar { background-image: toggle(var(--mix-or-toggle-me)); }

:root {
  --bg-a: url(1.png), url(2.png);
  --bg-b: url(3.png), url(4.png);
  --mix-or-toggle-me: var(--bg-a);var(--bg-b); /* oops */
}

@Loirooriol
Copy link
Contributor

@kbabbitt That can be addressed with #8391 wile keeping ; as the separator.

@tabatkins
Copy link
Member Author

Putting this up on the F2F agenda to discuss. Summarizing the two options:

  1. Remove the ; separator idea. Instead, use some variety of wrapper for the rare cases when you need to provide a value that is, itself, comma-separated. Options:
    1. Use [], like in Grid to wrap line groups.
    2. Use {}. (Syntax now disallows {} at the top-level, but these will be inside of a function, and thus possible.)
    3. Use item() or a similar generically-named function.
  2. Make ; an optional upgrade. All functions continue to use , as the argument separator, but also allow using ; as a separator. When parsing, assume that , is the argument separator at first, but if a top-level ; is encountered in the function value, re-parse using ; as the argument separator instead.

@LeaVerou
Copy link
Member

LeaVerou commented Jan 31, 2024

Putting this up on the F2F agenda to discuss. Summarizing the two options:

  1. Remove the ; separator idea. Instead, use some variety of wrapper for the rare cases when you need to provide a value that is, itself, comma-separated. Options:

    1. Use [], like in Grid to wrap line groups.
    2. Use {}. (Syntax now disallows {} at the top-level, but these will be inside of a function, and thus possible.)
    3. Use item() or a similar generically-named function.
  2. Make ; an optional upgrade. All functions continue to use , as the argument separator, but also allow using ; as a separator. When parsing, assume that , is the argument separator at first, but if a top-level ; is encountered in the function value, re-parse using ; as the argument separator instead.

We should consider the user experience not only when writing code, but also when reading code. The former is only done once, but the latter is done many times. I’m quite concerned that 2 is prioritizing the former over the latter. A grouping construct makes it obvious what is going on, whereas using a different separator only some of the time makes parsing hard not just for parsers, but humans too.

Can we use plain parentheses, and special case the case when the first character is (?

@kizu
Copy link
Member

kizu commented Feb 27, 2024

I noticed in the agenda that this issue is mentioned alongside #6705, but neither issue contains an explicit link to another, so I feel it is worth it to mention it explicitly here, as they essentially talk about the same thing.

Regarding the potential solution, I would prefer the value() or item() (or any other function) to any syntax-only way. Most of my arguments for this were already mentioned above, but to re-iterate:

  1. I don't like the modality of the ; vs ,. It might really be confusing for authors, and it won't be trivial to search for “what is this syntax?”.
  2. I don't like the [], {} etc, for similar reason, plus I don't like the confusion with the [] from the grid syntax.
  3. Given this new language feature is required only for edge-cases, I am fine with the syntax being more verbose like with value(): if we can keep most of the use cases clear and simple, then it is ok to have the edge-case more verbose.

@tabatkins
Copy link
Member Author

And just to be clear about the actual spec text for this, we wouldn't allow {} wrappers just anywhere, so there's no need to upgrade the parsing of all functions, everywhere. Instead, it would be a change to the definition of the comma-containing productions.

Currently (with the "upgradeable commas" feature), they're defined to not allow commas, unless the function is being reparsed in semicolon mode. (aka, if the function is currently in comma mode, the production ends at a comma or the ) that ends the surrounding function)

With this change, instead they'd be defined in two branches. The production is either:

  • a single {}-block, containing anything. (including commas, or more {} blocks, etc)
  • a sequence of component values other than commas or {} blocks.

var() would get an explicit exemption here, as it needs (but, uh, currently doens't have specced) under the current approach. var(--foo, "foo.svg", "bar.svg") is valid today (resolving to "foo.svg", "bar.svg" is fallback is triggered) and almost certainly needs to be preserved. It doesn't actually run into the parsing issues that otherwise force this syntax feature, so it's just slightly unfortunate that it has to be inconsistent.

To aid in clarity, I'd probably swap them all to a new production, maybe <any-argument> (to parallel <any-value>), which has these restrictions. var() would just stay with <declaration-value>?, as it has today.

@Loirooriol
Copy link
Contributor

we wouldn't allow {} wrappers just anywhere

So if a function accepts a single parameter, then f({a}) uses {a} as the argument, but then if we add a 2nd optional parameter, then the argument will suddenly change to a? Seems bad for forwards compatibility.

@nex3
Copy link
Contributor

nex3 commented Sep 24, 2024

Would var(--foo, {"foo.svg", "bar.svg"}) resolve to "foo.svg", "bar.svg" or to {"foo.svg", "bar.svg"}? If the former, does that mean that if(cond(...): var(--foo)) resolves to "foo.svg" if cond(...) is true? If the latter, does that mean that prop: var(--foo) is invalid?

@andruud
Copy link
Member

andruud commented Sep 25, 2024

Strong +1 to what Tab is proposing here.

we wouldn't allow {} wrappers just anywhere

So if a function accepts a single parameter, then f({a}) uses {a} as the argument, but then if we add a 2nd optional parameter, then the argument will suddenly change to a? Seems bad for forwards compatibility.

Good point, but this would only be a problem for functions that accept {} in their parameters, right? So maybe there's no need to allow e.g. rgb({0}, {128}, {0}), since that's already invalid.

Would var(--foo, {"foo.svg", "bar.svg"}) resolve to "foo.svg", "bar.svg" or to {"foo.svg", "bar.svg"}?

This currently resolves to {"foo.svg", "bar.svg"}, and changing that definitely requires a use-counter. We might be able to change this to optionally allow {} if we wanted, but we shouldn't rely on it. I highly doubt we'll be able to require {}, though.

Related question: it may be too late for var()/env(), but what about attr()?

If the former, does that mean that if(cond(...): var(--foo)) resolves to "foo.svg" if cond(...) is true? If the latter, does that mean that prop: var(--foo) is invalid?

I don't think arbitrary substitution functions should be allowed to mess with the grammar of the calling function itself, just like you're not allowed to paste e.g. a fallback into another var(): --fb:, myfallback; --y: var(--x var(--fb)) /* invalid */.

So when --foo is "foo.svg", "bar.svg", then --bar: if(cond(...): var(--foo)) is "foo.svg", "bar.svg" when the condition is true, and IACVT when the condition is false.

When --foo is {"foo.svg", "bar.svg"}, then --bar: if(cond(...): var(--foo)) is {"foo.svg", "bar.svg"} when the condition is true, and IACVT when the condition is false.

@tabatkins
Copy link
Member Author

So if a function accepts a single parameter, then f({a}) uses {a} as the argument, but then if we add a 2nd optional parameter, then the argument will suddenly change to a? Seems bad for forwards compatibility.

I'm not sure what you mean by this. What's the grammar for that first argument? If it's a comma-containing production, then f({a}) would be taking a as the argument. If it's anything else, it's almost certain that {a} would just be invalid, but if we ever do define an actual grammar taking that on purpose, it woudl work as normal.

The presence or absence of a second argument, optional or not, has nothing to do with this, and I'm not sure how you're reading that into what I wrote.

@tabatkins
Copy link
Member Author

tabatkins commented Sep 25, 2024

(edited to add: per my next comment, I'm probably slightly wrong here.)

Would var(--foo, {"foo.svg", "bar.svg"}) resolve to "foo.svg", "bar.svg" or to {"foo.svg", "bar.svg"}? If the former, does that mean that if(cond(...): var(--foo)) resolves to "foo.svg" if cond(...) is true? If the latter, does that mean that prop: var(--foo) is invalid?

Per my proposal above, the var() would resolve to {"foo.svg", "bar.svg"}. Then the if() gets the var() subbed in, producing if(cond(...): {"foo.svg", "bar.svg"}).

But as Anders said, we might be able to (and hopefully can) make var() more subtle, and allow enforcing the {} rules like other locations. (But, as they say, there's no way we can make it require {} around commas; it'll still need to allow top-level commas in its fallback for back-compat.)

If that's the case, then the behavior instead becomes:

  • The var() would resolve to "foo.svg", "bar.svg".
  • It then subs into the if() to produce if(cond(...): "foo.svg", "bar.svg"), which is invalid syntax; the property becomes IACVT.
  • The solution would be to write the if() as if(cond(...): {var(--foo)}). You can do this defensively; it works correctly whether the var() substitutes as a value with commas or not. This is similar to the current specced behavior, where you might need to defensively write your function like if(cond(...): var(--foo);) to ensure that if the var() substitution contains commas, it works properly (but is also fine if it doesn't).
  • (The var() author could also defensively double-wrap their value in {}; only the outer one gets stripped by the grammar. But then it can't be safely used directly in properties. Not a good idea in general. Similarly, you shouldn't defensively write your custom property with a {} wrapper in its value, like --foo: {"foo.svg", "bar.svg"};, because it then can't be subbed directly into many locations.)

This defensive coding is slightly unfortunate but a required part of how var() (and all other arbitrary-substitution functions) work; they are intrinsically passing around raw syntax, not higher-level values. For example, a custom property could be --foo: cond1(...): val1, cond2(...): val2;, then used like if(var(--foo), cond3(...): val3).

@tabatkins
Copy link
Member Author

I don't think arbitrary substitution functions should be allowed to mess with the grammar of the calling function itself, just like you're not allowed to paste e.g. a fallback into another var(): --fb:, myfallback; --y: var(--x var(--fb)) /* invalid */.

Oh, hm, you're probably right actually. You indeed can't sub in arbitrary grammar like that, since var() is actually parsed while its surrounding context is left as unparsed tokens. I'll need to give this a little thought.

@andruud
Copy link
Member

andruud commented Sep 25, 2024

since var() is actually parsed while its surrounding context is left as unparsed tokens.

Exactly. By default, I'd expect the same from any arbitrary substitution function. I don't think we should deviate for if().

But this is a bit off-topic (we can ask roughly the same questions under upgradeable commas), so maybe we should open a new issue if more discussion is needed on that.

@Loirooriol
Copy link
Contributor

Loirooriol commented Sep 25, 2024

I'm not sure what you mean by this

Yeah sorry I misread what you said, I thought you were proposing something different.

@tabatkins
Copy link
Member Author

But this is a bit off-topic (we can ask roughly the same questions under upgradeable commas), so maybe we should open a new issue if more discussion is needed on that.

Yup, #10947

@css-meeting-bot
Copy link
Member

The CSS Working Group just discussed [css-values-5][various] Better handling of arguments with commas, and agreed to the following:

  • RESOLVED: Undo previous decision and move forward with optional curly-brace wrapping of complex arguments
The full IRC log of that discussion <emeyer> TabAtkins: We discussed how to deal with argument values that might have commas in them
<emeyer> …We did a straw poll that ended in a tie, and did a runoff
<emeyer> …That resulted in upgradeable commas, where you can replace commas as semicolons
<emeyer> …This works, but upon more thought, we would like revisit this decision and go with the other thing that got first place
<emeyer> …The option we want to go with instead is to use a curly brace wrapper around the values
<emeyer> …So if you write an if with a comma-separated condition, you write it like this (shows syntax from comment on issue)
<emeyer> …So it can either be a single curly brace block containing anything that’s valid, or it’s a sequence of component values
<emeyer> …This way as long as you’re doing anything fancy, you can just write the fuction exactly as you would elsewhere
<emeyer> …An argument with commas gets marked as being a special thing
<emeyer> …In the original version, you use quoted strings separated by commas
<emeyer> …YOu can end up with one thing being valid and one not valid, and you can only fix it with a trailing comma
<emeyer> …This is bad
<emeyer> s/YOu/You/
<emeyer> …It looks bad and it’s inconsistent with other things
<emeyer> …The curly braces make it clear where the complicated bits start and stop
<emeyer> …This also makes other issues easier
<emeyer> …There are a few parsing questions about how this would apply to var()
<emeyer> …They’re arcane
<oriol> q+
<emeyer> …So we’d like to change to curly brace wrapping and resolve on that instead
<astearns> ack oriol
<emeyer> oriol: I agree the commas were not a great decision, but I have a mild preference for the original idea to require semicolons
<emeyer> …It avoids authors thinking if they use a var(), maybe the value is something typical
<emeyer> …But the semicolons has some downsides so I’m okay with doing this
<emeyer> TabAtkins: Oriol’s pointing out authors had to memorize the special-casde syntax even if they didn’t often use it
<emeyer> …This is what turned us against it; we wanted something lower-touch
<emeyer> TabAtkins: (shows an example of the older syntax)
<emeyer> s/casde/case/
<lea> q?
<emeyer> TabAtkins: This would have required defensive coding, but that’s kind of true of any syntax we decide to use
<emeyer> …The only one that avoids it is “always use semicolons” but we rejected that for other reasons
<lea> q+ to say agree that a wrapping construct is better than upgradable commas. The natural option here is bare parens, and I don't buy the argument that we should contort our syntax *forever* to accommodate a preprocessor, especially now that its popularity is dwindling
<astearns> ack lea
<Zakim> lea, you wanted to say agree that a wrapping construct is better than upgradable commas. The natural option here is bare parens, and I don't buy the argument that we should contort
<Zakim> ... our syntax *forever* to accommodate a preprocessor, especially now that its popularity is dwindling
<emeyer> lea: I agree that upgradable commas are weird and wrapping constructs are much better
<emeyer> …For the best wrapping construct would be, we can discuss options but I agree that bare parentheses are the natural option
<emeyer> …We should not contort CSS syntax forever
<emeyer> …The choice to prioritize Sass syntax over natural syntax seems weird
<emeyer> TabAtkins: We used quare brackets in Grid because it was easier at the time
<emeyer> …Here, bare parentheses were proposed and it was pointed out we’d decided against that in the past and didn’t want to revisit that
<kizu> q+
<miriam> q+
<emeyer> …I don’t think there’s an overriding reason to go back and revisit our decision to avoid those
<astearns> ack kizu
<emeyer> kizu: +1 to Lea
<emeyer> …I think plain parentheses would be better
<emeyer> …Curly braces mean a block in CSS
<dbaron> (I agree that wrapping is better than trailing semicolons as a defensive technique, for what it's worth.)
<lea> bare parens are the quintessential grouping construct. No braces, brackets, or function compares to the natural ergonomics of bare parens for this. IMO
<emeyer> (scribe having trouble making out what’s being said)
<emeyer> …I do want to use commas with braces
<astearns> ack miriam
<emeyer> miriam: I think the same can be said the other way about parentheses in CSS< which are only used on function and calculations
<lea> q?
<emeyer> …This stands out as being different and looking different
<astearns> ack fantasai
<lea> q+ to say in every prog lang parens are used for functions and grouping, this is not new
<emeyer> fantasai: The advantage of curly braces is it leaves parentheses to be used for something more useful than “we needed to escape commas here”
<tantek> +1 dbaron, lea
<emeyer> …I don’t think we should take a syntax that could be much more useful here
<ntim> `toggle(« Arial, Helvetica, sans-serif », « Times, serif »)` :D
<emeyer> lea: I don’t see it as escaping, I see it as grouping
<emeyer> …In every popular language, parentheses are used for grouping
<emeyer> …We do that inside our own calc()
<emeyer> …If we end up deciding other constructs are better, regardless of the Sass problem, there’s no Sass problem in the first place
<astearns> q?
<astearns> ack lea
<Zakim> lea, you wanted to say in every prog lang parens are used for functions and grouping, this is not new
<emeyer> TabAtkins: Because the reasons for the original Sass allowances are valid and are even more so here, Chrome would object to using parentheses for this case regardless
<emeyer> q?
<emeyer> astearns: We have two options, each with champions, and one has an objection
<emeyer> …Is there anyone who would object to curly braces
<emeyer> lea: There are other options like square brackets
<emeyer> fantasai: Square brackets aren’t an option because you’d have to escape them
<emeyer> TabAtkins: You wouldn’t but other options lost to curly braces
<emeyer> …in the original poll
<fantasai> s/escape them/escape them in grid/
<TabAtkins> TabAtkins: Chrome doesn't object to the other options, but {} won the original poll
<romain> Curly brackets look quite nice in this example by kizu for inline conditions : https://github.com//issues/10064#issuecomment-2373483709
<lea> Option 1: Bare ()
<lea> Option 2: {}
<lea> Option 3: []
<lea> Option 4: Function e.g. value(), val(), arg(), item()
<emeyer> astearns: It’s not clear to me that the options for parentheses transfeer to square brackets
<emeyer> …It’s another grouping mechanism
<ntim> 2
<fantasai> 2
<romain> 2
<kbabbitt> 2
<lea> 3
<emeyer> s/transfeer/transfer/
<ethanjv> 2
<moonira> 2
<chrishtr> 2
<alisonmaher> 2
<kizu> 4
<emeyer> …Looks like we’re doing a straw poll; Lea put options into IRC
<miriam> 2
<TabAtkins> 2
<keithamus> 2/3
<Penny> 2
<emeyer> …Please put in a number corresponding to your preference
<astearns> 2
<futhark> 4
<lea> actually 3/4 for me
<dbaron> 2
<vmpstr> abstain
<rachelandrew> 2
<dholbert> abstain
<ydaniv> abstain
<emeyer> abstain
<castastrophe> 2
<masonf> abstain
<emeyer> astearns: Poll looks pretty clear toward curly brackets
<oriol> Preference for requiring semicolons. But 2 among the above.
<emeyer> …Is there anyone who would object to that option?
<emeyer> (silence)
<emeyer> …Given the straw poll, I suggest we resolve on using curly braces
<emeyer> …Objections?
<emeyer> astearns: Hearing none, we will undo the previous resolution
<emeyer> RESOLVED: Undo previous decision and move forward with optional curly-brace wrapping of complex arguments

@fantasai
Copy link
Collaborator

fantasai commented Oct 8, 2024

We resolved on using semicolons in if(). Are we still happy with this resolution?

@nt1m
Copy link
Member

nt1m commented Oct 8, 2024

I quite liked @kizu's solution in #10064 which I'm surprised wasn't suggested here.

.foo {
  font-family: if(
    style(--type: prose), style(--type: book) {
      'Iowan Old Style', 'Palatino Linotype', serif
    }
    style(--type: code) and not (--placement: inline) {
      'Nimbus Mono PS', 'Courier New', monospace
    }
    else {
      system-ui, sans-serif
    }
  );
}

@Loirooriol
Copy link
Contributor

@nt1m That was specifically for cond(). How would it be in genereal, wrapping the values with {}? That's basically the resolution here.

@tabatkins
Copy link
Member Author

We resolved on using semicolons in if(). Are we still happy with this resolution?

Yes. I'm a little annoyed that we went with a different syntax for if() specifically, when there was no need for it, but it is slightly special so whatever. But in general, the curly-brace resolution is still good for everything else.

tabatkins added a commit that referenced this issue Oct 15, 2024
… to allowing {} wrappers, rather than semicolon upgrades. #9539
@KennethHoff
Copy link

There's no reason we can't change it now though, is it? It hasn't shipped anywhere(?)

@tabatkins
Copy link
Member Author

In theory no, but we literally decided on both of these (the new syntax for comma-containing arguments, and the syntax for if()) at the same meeting a few weeks ago, and explicitly handled the former first so it could influence the latter's discussion.

The WG just resolved differently than what I wanted, is all. It happens.

(I'm a little confused why @fantasai is asking if we're still happy about this, for exactly this reason. The two topics were discussed on the same day, immediately following each other, with explicit references between them, so I don't understand why this resolution would be in question.)

@cdoublev
Copy link
Collaborator

If it does not start with a "{" token, then it cannot contain commas

--custom: foo, bar should remain valid so this restriction should only apply to comma-containing productions in functions. And I think <{-token> is consumed to a simple block defined with } as its ending token, so the condition should be "if it starts with a simple block whose ending token is }".

I hope you do not mind that I did not open a separate issue for this.

@tabatkins
Copy link
Member Author

--custom: foo, bar should remain valid so this restriction should only apply to comma-containing productions in functions.

Ah yes, you are completely correct. I accidentally dropped the context that this only applies within functions when I rewrote the text, thanks.

And I think <{-token> is consumed to a simple block defined with } as its ending token, so the condition should be "if it starts with a simple block whose ending token is }".

Yeah I was wondering which way I should write it. You're probably right that I should just do treat it as a block, since grammars never see the { tokens themselves.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Thursday morning
Status: Unsorted regular
Development

No branches or pull requests