Skip to content

Commit 943132e

Browse files
andruudtabatkins
andauthored
[css-mixins-1] Handle dynamic cycle detection, argument grammar (#12165)
* [css-mixins-1] Handle dynamic cycle detection, argument grammar I couldn't make cycle detection work for custom functions by invoking "substitute arbitrary substitution functions", so I made that more explicit using "guarded" substitution contexts. Resolves #11500. * Update css-mixins-1/Overview.bs --------- Co-authored-by: Tab Atkins Jr. <[email protected]>
1 parent 9acaf6c commit 943132e

File tree

2 files changed

+131
-138
lines changed

2 files changed

+131
-138
lines changed

css-mixins-1/Overview.bs

Lines changed: 116 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ as well as other [=custom functions=] via <<dashed-function>>s.
285285

286286
The '@function/result' descriptor itself does not have a type,
287287
but its [=resolve function styles|resolved=] value is type-checked
288-
during the [=substitute a dashed function|substitution=] of a <<dashed-function>>.
288+
during the [=replace a dashed function|substitution=] of a <<dashed-function>>.
289289

290290
Arguments & Local Variables {#args}
291291
-----------------------------------
@@ -385,7 +385,7 @@ with a <<dashed-function>>.
385385

386386
A <dfn><<dashed-function>></dfn> is a [=functional notation=]
387387
whose function name starts with two dashes (U+002D HYPHEN-MINUS).
388-
Its syntax is:
388+
Its [=argument grammar=] is:
389389

390390
<pre class="prod informative" nohighlight>
391391
&lt;dashed-function> = --*( <<declaration-value>>#? )
@@ -396,7 +396,7 @@ A <<dashed-function>> can only be used where ''var()'' is allowed.
396396
If a property contains one or more <<dashed-function>>s,
397397
the entire property’s grammar must be assumed to be valid at parse time.
398398
At computed-value time,
399-
every <<dashed-function>> must be [=substitute a dashed function|substituted=]
399+
every <<dashed-function>> must be [=replace a dashed function|replaced=]
400400
before finally being checked against the property's grammar.
401401

402402
Note: Within the body of a [=custom function=],
@@ -420,18 +420,18 @@ a [=calling context's=] <dfn for="calling context">root element</dfn>
420420
is the real element at the root of the [=calling context=] stack.
421421

422422
<div algorithm>
423-
To <dfn>substitute a dashed function</dfn> in a value,
424-
with |dashed function| being a <<dashed-function>>:
423+
To <dfn>replace a dashed function</dfn> |dashed function|,
424+
with a list of |arguments|:
425425

426426
1. Let |function| be the result of dereferencing
427427
the |dashed function|'s name as a [=tree-scoped reference=].
428-
If no such name exists, return failure.
429-
2. [=substitute arbitrary substitution functions|Substitute=]
430-
any [=arbitrary substitution functions=]
431-
within |dashed function|'s arguments,
432-
then parse it as ''<<declaration-value>>#''
433-
and let |arguments| be the result
434-
(a comma-separated list of CSS values).
428+
If no such name exists, return the [=guaranteed-invalid value=].
429+
2. For each |arg| in |arguments|,
430+
[=substitute arbitrary subsitution functions=] in |arg|,
431+
and replace |arg| with the result.
432+
433+
Note: This may leave some (or all) arguments as the [=guaranteed-invalid value=],
434+
triggering [=default values=] (if any).
435435
3. If |dashed function| is being substituted into a property on an element,
436436
let |calling context| be a [=calling context=]
437437
with that element and that property
@@ -442,17 +442,12 @@ is the real element at the root of the [=calling context=] stack.
442442
Let |calling context| be a [=calling context=]
443443
with that "hypothetical element" and that descriptor.
444444

445-
5. [=Evaluate a custom function=],
445+
4. [=Evaluate a custom function=],
446446
using |function|, |arguments|, and |calling context|,
447-
and replace the <<dashed-function>> with the [=equivalent token sequence=]
447+
and return the [=equivalent token sequence=]
448448
of the value resulting from the evaluation.
449449
</div>
450450

451-
If [=substitute a dashed function=] fails,
452-
and the substitution is taking place on a property's value,
453-
then the declaration containing the <<dashed-function>> becomes
454-
[=invalid at computed-value time=].
455-
456451
<div class='example'>
457452
A [=comma-containing productions|comma-containing value=]
458453
may be passed as a single argument
@@ -468,6 +463,66 @@ then the declaration containing the <<dashed-function>> becomes
468463
</pre>
469464
</div>
470465

466+
<div class='example'>
467+
In the following,
468+
<code>--foo()</code> is in a cycle with itself:
469+
470+
<pre class='lang-css'>
471+
@function --foo(--x) {
472+
result: --foo(10);
473+
}
474+
</pre>
475+
476+
Similarly,
477+
<code>--bar()</code> is in a cycle with itself,
478+
even though the local variable <code>--x</code> is never referenced
479+
by '@function/result':
480+
481+
<pre class='lang-css'>
482+
@function --bar() {
483+
--x: --bar();
484+
result: 1;
485+
}
486+
</pre>
487+
488+
However, <code>--baz()</code> is not in a cycle with itself here,
489+
since we never evaluate the <code>result</code> declaration within
490+
the <code>@media</code> rule:
491+
492+
<pre class='lang-css'>
493+
@function --baz(--x) {
494+
@media (unknown-feature) {
495+
result: --baz(42);
496+
}
497+
result: 1;
498+
}
499+
500+
</pre>
501+
</div>
502+
503+
<div class='example'>
504+
The function <code>--baz()</code> is not in a cycle in the example below:
505+
even though <code>var(--x)</code> and <code>var(--y)</code> appear in the function body,
506+
they refer to a [=function parameter=] and [=local variable=], respectively.
507+
The [=custom properties=] <code>--x</code> and <code>--y</code>
508+
both reference <code>--baz()</code>, but that's fine:
509+
those [=custom properties=] are not referenced within <code>--baz()</code>.
510+
511+
<pre class='lang-css'>
512+
@function --baz(--x) {
513+
--y: 10px;
514+
result: calc(var(--x) + var(--y));
515+
}
516+
517+
div {
518+
--x: --baz(1px);
519+
--y: --baz(2px);
520+
width: var(--x); /* 11px */
521+
height: var(--y); /* 12px */
522+
}
523+
</pre>
524+
</div>
525+
471526
Evaluating Custom Functions {#evaluating-custom-functions}
472527
----------------------------------------------------------
473528

@@ -486,52 +541,71 @@ with its [=function parameters=] overriding "inherited" custom properties of the
486541
and a list of CSS values |arguments|,
487542
returning a CSS value:
488543

489-
1. If the number of items in |arguments|
544+
1. Let |substitution context| be a [=substitution context=]
545+
containing &bs<<;"function", |custom function|&bs>>;.
546+
547+
Note: Due to [=tree-scoped names|tree-scoping=],
548+
the same function name may appear multiple times on the stack
549+
while referring to different [=custom functions=].
550+
For this reason, the [=custom function=] itself is included
551+
in the [=substitution context=], not just its name.
552+
2. [=guarded|Guard=] |substitution context| for the remainder of this algorithm.
553+
If |substitution context| is marked as [=cyclic substitution context|cyclic=],
554+
return the [=guaranteed-invalid value=].
555+
3. If the number of items in |arguments|
490556
is greater than the number of [=function parameters=] in |custom function|,
491557
return the [=guaranteed-invalid value=].
492-
2. Let |registrations| be an initially empty set of [=custom property registrations=].
493-
3. For each [=function parameter=] of |custom function|,
558+
4. Let |registrations| be an initially empty set of [=custom property registrations=].
559+
5. For each [=function parameter=] of |custom function|,
494560
create a [=custom property registration=]
495561
with the parameter's name,
496562
a syntax of the [=parameter type=],
497563
an inherit flag of "true",
498564
and no initial value.
499565
Add the registration to |registrations|.
500-
4. If |custom function| has a [=custom function/return type=],
566+
6. If |custom function| has a [=custom function/return type=],
501567
create a [=custom property registration=]
502568
with the name "return"
503569
(violating the usual rules for what a registration's name can be),
504570
a syntax of the [=custom function/return type=],
505571
an inherit flag of "false",
506572
and no initial value.
507573
Add the registration to |registrations|.
508-
5. Let |argument rule| be an initially empty [=style rule=].
509-
6. For each [=function parameter=] of |custom function|:
574+
7. Let |argument rule| be an initially empty [=style rule=].
575+
8. For each [=function parameter=] of |custom function|:
510576
1. Let |arg value| be the value of the corresponding argument in |arguments|,
511577
or the [=guaranteed-invalid value=] if there is no corresponding argument.
512578
2. Let |default value| be the parameter's [=default value=].
513579
3. Add a [=custom property=] to |argument rule|
514580
with a name of the parameter's name,
515581
and a value of ''first-valid(|arg value|, |default value|)''.
516-
7. [=Resolve function styles=] using |argument styles|, |registrations|, and |calling context|.
582+
9. [=Resolve function styles=] using |custom function|, |argument styles|, |registrations|, and |calling context|.
517583
Let |argument styles| be the result.
518-
8. Let |body rule| be the [=function body=] of |custom function|,
584+
10. Let |body rule| be the [=function body=] of |custom function|,
519585
as a [=style rule=].
520-
9. For each [=custom property registration=] of |registrations|,
586+
11. For each [=custom property registration=] of |registrations|,
521587
set its initial value
522588
to the corresponding value in |argument styles|,
523589
set its syntax
524590
to the [=universal syntax definition=],
525591
and prepend a [=custom property=] to |body rule|
526592
with the property name and value in |argument styles|.
527-
10. [=Resolve function styles=] using |body rule|, |registrations|, and |calling context|.
593+
12. [=Resolve function styles=] using |custom function|, |body rule|, |registrations|, and |calling context|.
528594
Let |body styles| be the result.
529-
11. Return the value of the '@function/result' property in |body styles|.
595+
13. If |substitution context| is marked as a [=cyclic substitution context=],
596+
return the [=guaranteed-invalid value=].
597+
598+
Note: Nested [=arbitrary substitution functions=]
599+
may have marked |substitution context| as [=cyclic substitution context|cyclic=]
600+
at some point after step 2,
601+
for example when resolving '@function/result'.
602+
14. Return the value of the '@function/result' property in |body styles|.
530603
</div>
531604

532605
<div algorithm>
533606
To <dfn>resolve function styles</dfn>,
534-
given a style rule |rule|,
607+
given a [=custom function=] |custom function|,
608+
a style rule |rule|,
535609
a set of [=custom property registrations=] |registrations|,
536610
and a [=calling context=] |calling context|,
537611
returning a set of [=computed value|computed=] styles:
@@ -565,6 +639,17 @@ with its [=function parameters=] overriding "inherited" custom properties of the
565639
Note: ''result: inherit'', for example,
566640
will cause the <<dashed-function>> to <em>evaluate to</em> the ''inherit'' keyword,
567641
similar to ''var(--unknown, inherit)''.
642+
* For a given [=custom property=] |prop|,
643+
during [=property replacement=] for that property,
644+
the [=substitution context=] also includes |custom function|.
645+
In other words, the [=substitution context=] is
646+
&bs<<;"property", |prop|'s name, |custom function|&bs>>;
647+
648+
Note: Due to dynamic scoping,
649+
the same property name may appear multiple times on the stack
650+
while referring to different [=custom properties=].
651+
For this reason, the [=custom function=] itself is included
652+
in the [=substitution context=], not just its name.
568653

569654
3. Determine the [=computed value=] of all [=custom properties=]
570655
and the '@function/result' "property" on |el|,
@@ -588,88 +673,6 @@ with its [=function parameters=] overriding "inherited" custom properties of the
588673
will be used from these styles.
589674
</div>
590675

591-
592-
593-
594-
Cycles {#cycles}
595-
----------------
596-
597-
The ''@function/result'' descriptor and [=local variables=]
598-
within a [=custom function=]
599-
may reference other [=custom functions=] or [=custom properties=],
600-
and may therefore create [[css-variables-1#cycles|cycles]].
601-
602-
For each element, add a node for every specified [=custom function=]
603-
to the graph described in [[css-variables-1#cycles]];
604-
add a node for each [=local variable=]
605-
defined within each of those functions;
606-
then, for each [=custom function=] <var>func</var>, add edges as follows:
607-
608-
* From <var>func</var> to any [=custom function=]
609-
referenced by a <<dashed-function>> within <var>func</var>'s body.
610-
* From <var>func</var> to any [=custom property=] or [=local variable=]
611-
referenced by a ''var()'' within <var>func</var>'s body.
612-
* To <var>func</var> from any [=custom property=] or [=local variable=]
613-
that references <var>func</var>
614-
using a <<dashed-function>>.
615-
616-
A <<dashed-function>> referencing a [=custom function=]
617-
which is part of a cycle
618-
makes the containing [=declaration=] [=invalid at computed-value time=].
619-
620-
Note: Cycles are disallowed even through branches that are not taken
621-
during execution.
622-
623-
<div class='example'>
624-
In the following,
625-
<code>--foo()</code> is in a cycle with itself,
626-
even though the media query never evaluates to "true":
627-
628-
<pre class='lang-css'>
629-
@function --foo(--x) {
630-
@media (unknown-feature) {
631-
result: --foo(42);
632-
}
633-
result: 1;
634-
}
635-
</pre>
636-
637-
Similarly,
638-
<code>--bar()</code> is in a cycle with itself,
639-
even though the local variable <code>--x</code> is never referenced:
640-
641-
<pre class='lang-css'>
642-
@function --bar() {
643-
--x: --bar();
644-
result: 1;
645-
}
646-
</pre>
647-
</div>
648-
649-
<div class='example'>
650-
The function <code>--baz()</code> is not in a cycle in the example below:
651-
even though <code>var(--x)</code> and <code>var(--y)</code> appear in the function body,
652-
they refer to a [=function parameter=] and [=local variable=], respectively.
653-
The [=custom properties=] <code>--x</code> and <code>--y</code>
654-
both reference <code>--baz()</code>, but that's fine:
655-
those [=custom properties=] are not referenced within <code>--baz()</code>.
656-
657-
<pre class='lang-css'>
658-
@function --baz(--x) {
659-
--y: 10px;
660-
result: calc(var(--x) + var(--y));
661-
}
662-
663-
div {
664-
--x: --baz(1px);
665-
--y: --baz(2px);
666-
width: var(--x); /* 11px */
667-
height: var(--y); /* 12px */
668-
}
669-
</pre>
670-
</div>
671-
672-
673676
<!-- Big Text: execution
674677

675678
█████▌ █ █ █████▌ ███▌ █▌ █▌ █████▌ ████ ███▌ █ █▌

css-values-5/Overview.bs

Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1406,8 +1406,8 @@ Conditional Value Selection: the ''if()'' notation</h3>
14061406
2. Evaluate |condition|.
14071407

14081408
If a <<style-query>> in |condition| tests the value of a property,
1409-
and a &bs<<;"property", referenced-property-name&bs>>; [=substitution context=]
1410-
would be [=detect cyclic substitutions|detected=] as a [=cyclic substitution context=],
1409+
and [=guarded|guarding=] a [=substitution context=] &bs<<;"property", referenced-property-name&bs>>;
1410+
would mark it as a [=cyclic substitution context=],
14111411
that query evaluates to false.
14121412

14131413
<div class=example>
@@ -3331,8 +3331,7 @@ Substitution</h3>
33313331
in a sequence of [=component values=] |values|,
33323332
given an optional [=substitution context=] |context|:
33333333

3334-
1. If |context| was provided,
3335-
[=detect cyclic substitutions=] using |context|.
3334+
1. [=Guard=] |context| for the remainder of this algorithm.
33363335
If |context| is marked as a [=cyclic substitution context=],
33373336
return the [=guaranteed-invalid value=].
33383337

@@ -3397,32 +3396,23 @@ Substitution</h3>
33973396
</div>
33983397

33993398
The types of [=substitution contexts=] are currently:
3400-
* "property", followed by a property name
3401-
* "attribute", followed by an attribute name
3399+
* "property", followed by a property name,
3400+
and optionally a [=custom function=].
3401+
* "attribute", followed by an attribute name.
3402+
* "function", followed by a [=custom function=].
34023403
</div>
34033404

34043405
<div>
3406+
34053407
As [=substitution=] is recursively invoked
34063408
by nested [=arbitrary substitution functions=] being [=replaced=],
3407-
the [=substitution contexts=] passed to each invocation "stack up".
3408-
3409-
A [=substitution context=] may be marked
3410-
as a <dfn export>cyclic substitution context</dfn>
3411-
if it's involved in a cycle.
3412-
3413-
<div algorithm>
3414-
To <dfn export>detect cyclic substitutions</dfn>,
3415-
given a [=substitution context=] |context|:
3416-
3417-
1. If |context| matches a [=substitution context=] |outer context|
3418-
established by a [=substitution=] invocation "higher in the stack",
3419-
mark |context|,
3420-
|outer context|,
3421-
and any [=substitution context=] in between
3422-
as [=cyclic substitution contexts=].
3423-
2. Otherwise,
3424-
do nothing.
3425-
</div>
3409+
[=guarded|guards=] "stack up" the [=substitution contexts=] passed to each invocation.
3410+
3411+
When a [=substitution context=] is <dfn export>guarded</dfn>,
3412+
it means that, for the duration of the guard,
3413+
an attempt to guard a matching [=substitution context=] again
3414+
will mark all [=substitution contexts=] involved in the cycle as
3415+
<dfn export>cyclic substitution contexts</dfn>.
34263416

34273417
<div class=example>
34283418
For example, given the following style:

0 commit comments

Comments
 (0)