Skip to content

Commit

Permalink
[First-Class Mixins] Flush to spec
Browse files Browse the repository at this point in the history
Closes sass#626
  • Loading branch information
nex3 authored and jgerigmeyer committed Nov 16, 2023
1 parent 6e3dbd9 commit dc1e561
Show file tree
Hide file tree
Showing 6 changed files with 293 additions and 23 deletions.
28 changes: 23 additions & 5 deletions spec/at-rules/function.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
39 changes: 26 additions & 13 deletions spec/at-rules/mixin.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,24 +31,40 @@ 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

> This overrides the previous definition, if one exists.
* Otherwise, set the [current scope]'s mixin `name` to `mixin`.

[the current module]: ../spec.md#current-module
[the current import context]: ../spec.md#current-import-context
[current scope]: ../spec.md#scope
> This overrides the previous definition, if one exists.
## `@include`

Expand All @@ -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`

Expand Down
38 changes: 38 additions & 0 deletions spec/built-in-modules.md
Original file line number Diff line number Diff line change
@@ -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`.
86 changes: 81 additions & 5 deletions spec/built-in-modules/meta.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,44 @@ 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)
* [`content-exists()`](#content-exists)
* [`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()`

```
Expand Down Expand Up @@ -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()`

```
Expand Down Expand Up @@ -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()`

Expand All @@ -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()`

```
Expand Down Expand Up @@ -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"` |
Expand Down Expand Up @@ -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()`

```
Expand Down
62 changes: 62 additions & 0 deletions spec/types/functions.md
Original file line number Diff line number Diff line change
@@ -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 `'")'`.
Loading

0 comments on commit dc1e561

Please sign in to comment.