[First-Class Mixins] Flush to spec

Closes #626
This commit is contained in:
Natalie Weizenbaum 2023-10-06 18:03:51 -07:00
parent 353942622f
commit 50c8817c36
6 changed files with 293 additions and 23 deletions

View File

@ -28,20 +28,38 @@ To execute a `@function` rule `rule`:
[vendor prefix]: ../syntax.md#vendor-prefix
* Let `parent` be the [current scope].
[current scope]: ../spec.md#scope
* Let `function` be a [function] named `name` which does the following when
executed with `args`:
[function]: ../types/functions.md
* With the current scope set to an empty [scope] with `parent` as its parent:
* Evaluate `args` with `rule`'s `ArgumentDeclaration`.
* Execute each statement in `rule`.
* Return the value from the `@return` rule if one was executed, or throw an
error if no `@return` rule was executed.
[scope]: ../spec.md#scope
* If `rule` is outside of any block of statements:
* If `name` *doesn't* begin with `-` or `_`, set [the current module][]'s
function `name` to `rule`.
function `name` to `function`.
> This overrides the previous definition, if one exists.
* Set [the current import context][]'s function `name` to `rule`.
* Set [the current import context][]'s function `name` to `function`.
> This happens regardless of whether or not it begins with `-` or `_`.
[the current module]: ../spec.md#current-module
[the current import context]: ../spec.md#current-import-context
* Otherwise, set the [current scope]'s function `name` to `rule`.
[current scope]: ../spec.md#scope
* Otherwise, set the [current scope]'s function `name` to `function`.

View File

@ -31,25 +31,41 @@ To execute a `@mixin` rule `rule`:
* Let `name` be the value of `rule`'s `Identifier`.
* Let `parent` be the [current scope].
[current scope]: ../spec.md#scope
* Let `mixin` be a [mixin] named `name` which accepts a content block if `rule`
contains a `@content` rule. To execute this mixin with `args`:
[mixin]: ../types/mixins.md
* With the current scope set to an empty [scope] with `parent` as its parent:
* Evaluate `args` with `rule`'s `ArgumentDeclaration`.
* Execute each statement in `rule`.
[scope]: ../spec.md#scope
* If `rule` is outside of any block of statements:
* If `name` *doesn't* begin with `-` or `_`, set [the current module]'s
mixin `name` to `rule`.
mixin `name` to `mixin`.
> This overrides the previous definition, if one exists.
* Set [the current import context]'s mixin `name` to `rule`.
* Set [the current import context]'s mixin `name` to `mixin`.
> This happens regardless of whether or not it begins with `-` or `_`.
* Otherwise, set the [current scope]'s mixin `name` to `rule`.
[the current module]: ../spec.md#current-module
[the current import context]: ../spec.md#current-import-context
* Otherwise, set the [current scope]'s mixin `name` to `mixin`.
> This overrides the previous definition, if one exists.
[the current module]: ../spec.md#current-module
[the current import context]: ../spec.md#current-import-context
[current scope]: ../spec.md#scope
## `@include`
### Syntax
@ -72,15 +88,12 @@ To execute an `@include` rule `rule`:
* Let `name` be `rule`'s `NamespacedIdentifier`.
* Let `mixin` be the result of [resolving a mixin][] named `name`. If this
returns null, throw an error.
* Let `mixin` be the result of [resolving a mixin] named `name`. If this returns
null, throw an error.
[resolving a mixin]: ../modules.md#resolving-a-member
* Execute `rule`'s `ArgumentInvocation` with `mixin`'s `ArgumentDeclaration` in
`mixin`'s scope.
* Execute each statement in `mixin`.
* Execute `mixin` with `rule`'s `ArgumentInvocation`.
## `@content`

38
spec/built-in-modules.md Normal file
View File

@ -0,0 +1,38 @@
# Built-In Modules
Sass provides a number of built-in [modules] that may be loaded from URLs with
the scheme `sass`. These modules have no extensions, CSS trees, dependencies, or
source files. Their canonical URLs are the same as the URLs used to load them.
[modules]: modules.md#module
## Built-In Functions and Mixins
Each function and mixin defined in a built-in modules is specified with a
signature of the form
<x><pre>
[\<ident-token>] ArgumentDeclaration
</pre></x>
[\<ident-token>]: https://drafts.csswg.org/css-syntax-3/#ident-token-diagram
followed by a procedure. It's available as a member (either function or mixin)
in the module whose name is the value of the `<ident-token>`. When it's executed
with `args`:
* With an empty scope with no parent as the [current scope]:
[current scope]: spec.md#scope
* Evaluate `args` with the signature's `ArgumentDeclaration`.
* Run the procedure, and return its result if this is a function.
Built-in mixins don't accept content blocks unless explicitly specified
otherwise.
By convention, in these procedures `$var` is used as a shorthand for "the value
of the variable `var` in the current scope".
> In other words, `$var` is the value passed to the argument `$var`.

View File

@ -5,6 +5,7 @@ This built-in module is available from the URL `sass:meta`.
## Table of Contents
* [Functions](#functions)
* [`accepts-content()`](#accepts-content)
* [`calc-name()`](#calc-name)
* [`calc-args()`](#calc-args)
* [`call()`](#call)
@ -12,19 +13,36 @@ This built-in module is available from the URL `sass:meta`.
* [`feature-exists()`](#feature-exists)
* [`function-exists()`](#function-exists)
* [`get-function()`](#get-function)
* [`get-mixin()`](#get-mixin)
* [`global-variable-exists()`](#global-variable-exists)
* [`inspect()`](#inspect)
* [`keywords()`](#keywords)
* [`mixin-exists()`](#mixin-exists)
* [`module-functions()`](#module-functions)
* [`module-mixins()`](#module-mixins)
* [`module-variables()`](#module-variables)
* [`type-of()`](#type-of)
* [`variable-exists()`](#variable-exists)
* [Mixins](#mixins)
* [`apply()`](#apply)
* [`load-css()`](#load-css)
## Functions
### `accepts-content()`
This is a new function in the `sass:meta` module.
```
accepts-content($mixin)
```
* If `$mixin` is not a [mixin], throw an error.
[mixin]: ../types/mixins.md
* Return whether `$mixin` accepts a content block as a SassScript boolean.
### `calc-name()`
```
@ -144,6 +162,31 @@ This function is also available as a global function named `get-function()`.
* Return [`use`'s module][]'s function named `$name`, or throw an error if no
such function exists.
### `get-mixin()`
```
get-mixin($name, $module: null)
```
* If `$name` is not a string, throw an error.
* If `$module` is null:
* Return the result of [resolving a mixin] named `$name`. If this returns
null, throw an error.
[resolving a mixin]: ../modules.md#resolving-a-member
* Otherwise:
* If `$module` is not a string, throw an error.
* Let `use` be the `@use` rule in [the current source file] whose namespace is
equal to `$module`. If no such rule exists, throw an error.
* Return [`use`'s module]'s mixin named `$name`, or throw an error if no such
mixin exists.
### `global-variable-exists()`
```
@ -198,16 +241,14 @@ This function is also available as a global function named `mixin-exists()`.
* If `$module` is null:
* Return whether [resolving a mixin][] named `$name` returns null.
[resolving a mixin]: ../modules.md#resolving-a-member
* Return whether [resolving a mixin] named `$name` returns null.
* Otherwise, if `$module` isn't a string, throw an error.
* Otherwise, let `use` be the `@use` rule in [the current source file][] whose
* Otherwise, let `use` be the `@use` rule in [the current source file] whose
namespace is equal to `$module`. If no such rule exists, throw an error.
* Return whether [`use`'s module][] contains a mixin named `$name`.
* Return whether [`use`'s module] contains a mixin named `$name`.
### `module-functions()`
@ -225,6 +266,22 @@ This function is also available as a global function named `module-functions()`.
* Return a map whose keys are the names of functions in [`use`'s module][] and
whose values are the corresponding functions.
### `module-mixins()`
This is a new function in the `sass:meta` module.
```
module-mixins($module)
```
* If `$module` is not a string, throw an error.
* Let `use` be the `@use` rule in [the current source file] whose namespace is
equal to `$module`. If no such rule exists, throw an error.
* Return a map whose keys are the quoted string names of mixins in
[`use`'s module] and whose values are the corresponding mixins.
### `module-variables()`
```
@ -261,6 +318,7 @@ This function is also available as a global function named `type-of()`.
| Function | `"function"` |
| List | `"list"` |
| Map | `"map"` |
| Mixin | `"mixin"` |
| Null | `"null"` |
| Number | `"number"` |
| String | `"string"` |
@ -288,6 +346,24 @@ This function is also available as a global function named `variable-exists()`.
## Mixins
### `apply()`
```
apply($mixin, $args...)
```
* If `$mixin` is not a [mixin], throw an error.
* If the current `@include` rule has a `ContentBlock` and `$mixin` doesn't
accept a block, throw an error.
* Execute `$mixin` with the `ArgumentInvocation` `(...$args)`. Treat the
`@include` rule that invoked `apply` as the `@include` rule that invoked
`$mixin`.
> This ensures that any `@content` rules in `$mixin` will use `apply()`'s
> `ContentBlock`.
### `load-css()`
```

62
spec/types/functions.md Normal file
View File

@ -0,0 +1,62 @@
# Functions
## Table of Contents
* [Types](#types)
* [Operations](#operations)
* [Equality](#equality)
* [Serialization](#serialization)
## Types
The value type known as a "function" is a procedure that takes an
`ArgumentInvocation` `args` and returns a SassScript value. Each function has a
string name.
> The specific details of executing this procedure differ depending on where and
> how the function is defined.
### Operations
A function follows the default behavior of all SassScript operations, except
that equality is defined as below.
#### Equality
Functions use reference equality: two function values are equal only if they
refer to the exact same instance of the same procedure.
> If the same file were to be imported multiple times, Sass would create a new
> function value for each `@function` rule each time the file is imported.
> Because a new function value has been created, although the name, body, and
> source span of a given function from the file would be the same between
> imports, the values would not be equal because they refer to different
> instances. Functions pre-defined by the Sass language are instatiated at most
> once during the entire evaluation of a program.
>
> As an example, if we declare two functions:
>
> ```scss
> @function foo() {
> @return red;
> }
>
> $a: meta.get-function(foo);
>
> @mixin foo {
> @return red;
> }
>
> $b: meta.get-mixin(foo);
> ```
>
> Although every aspect of the two functions is the same, `$a != $b`, because
> they refer to separate function values.
### Serialization
To serialize a function value:
* If the value is not being inspected, throw an error.
* Otherwise, emit `'get-function("'`, then the function's name, then `'")'`.

63
spec/types/mixins.md Normal file
View File

@ -0,0 +1,63 @@
# Mixins
## Table of Contents
* [Types](#types)
* [Operations](#operations)
* [Equality](#equality)
* [Serialization](#serialization)
## Types
The value type known as a "mixin" is a procedure that takes an
`ArgumentInvocation` `args` and returns nothing. Each mixin has a string name
and a boolean that indicates whether or not it accepts a content block.
> The specific details of executing this procedure differ depending on where and
> how the mixin is defined. A mixin will typically add nodes to the CSS
> stylesheet.
### Operations
A mixin follows the default behavior of all SassScript operations, except that
equality is defined as below.
#### Equality
Mixins use reference equality: two mixin values are equal only if they refer to
the exact same instance of the same procedure.
> If the same file were to be imported multiple times, Sass would create a new
> mixin value for each `@mixin` rule each time the file is imported. Because a
> new mixin value has been created, although the name, body, and source span of
> a given mixin from the file would be the same between imports, the values
> would not be equal because they refer to different instances. Mixins
> pre-defined by the Sass language are instatiated at most once during the
> entire evaluation of a program.
>
> As an example, if we declare two mixins:
>
> ```scss
> @mixin foo {
> color: red;
> }
>
> $a: meta.get-mixin(foo);
>
> @mixin foo {
> color: red;
> }
>
> $b: meta.get-mixin(foo);
> ```
>
> Although every aspect of the two mixins is the same, `$a != $b`, because they
> refer to separate mixin values.
### Serialization
To serialize a mixin value:
* If the value is not being inspected, throw an error.
* Otherwise, emit `'get-mixin("'`, then the mixin's name, then `'")'`.