From ef237691d228427d61bac8437c0a0c7003260b70 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Fri, 25 Aug 2023 20:55:07 +0000 Subject: [PATCH 01/27] initial impl of first class mixins --- lib/src/callable/built_in.dart | 9 ++- lib/src/functions/meta.dart | 13 ++++ lib/src/value.dart | 9 +++ lib/src/value/mixin.dart | 38 ++++++++++++ lib/src/visitor/async_evaluate.dart | 92 +++++++++++++++++++++++++++- lib/src/visitor/interface/value.dart | 1 + lib/src/visitor/serialize.dart | 10 +++ 7 files changed, 168 insertions(+), 4 deletions(-) create mode 100644 lib/src/value/mixin.dart diff --git a/lib/src/callable/built_in.dart b/lib/src/callable/built_in.dart index 905d11e56..104e0a2d0 100644 --- a/lib/src/callable/built_in.dart +++ b/lib/src/callable/built_in.dart @@ -21,6 +21,8 @@ final class BuiltInCallable implements Callable, AsyncBuiltInCallable { /// The overloads declared for this callable. final List<(ArgumentDeclaration, Callback)> _overloads; + bool hasContent = false; + /// Creates a function with a single [arguments] declaration and a single /// [callback]. /// @@ -48,18 +50,19 @@ final class BuiltInCallable implements Callable, AsyncBuiltInCallable { /// defined. BuiltInCallable.mixin( String name, String arguments, void callback(List arguments), - {Object? url}) + {Object? url, bool hasContent = false}) : this.parsed(name, ArgumentDeclaration.parse('@mixin $name($arguments) {', url: url), (arguments) { callback(arguments); return sassNull; - }); + }, hasContent: hasContent); /// Creates a callable with a single [arguments] declaration and a single /// [callback]. BuiltInCallable.parsed(this.name, ArgumentDeclaration arguments, - Value callback(List arguments)) + Value callback(List arguments), + {this.hasContent = false}) : _overloads = [(arguments, callback)]; /// Creates a function with multiple implementations. diff --git a/lib/src/functions/meta.dart b/lib/src/functions/meta.dart index 41537d55d..cb3e1b541 100644 --- a/lib/src/functions/meta.dart +++ b/lib/src/functions/meta.dart @@ -6,6 +6,7 @@ import 'dart:collection'; import 'package:collection/collection.dart'; +import '../ast/sass/statement/mixin_rule.dart'; import '../callable.dart'; import '../util/map.dart'; import '../value.dart'; @@ -45,6 +46,7 @@ final global = UnmodifiableListView([ sassNull => "null", SassNumber() => "number", SassFunction() => "function", + SassMixin() => "mixin", SassCalculation() => "calculation", SassString() => "string", _ => throw "[BUG] Unknown value type ${arguments[0]}" @@ -77,6 +79,17 @@ final local = UnmodifiableListView([ ? argument : SassString(argument.toString(), quotes: false)), ListSeparator.comma); + }), + _function("accepts-content", r"$mixin", (arguments) { + var mixin = arguments[0].assertMixin("mixin"); + bool acceptsContent = switch (mixin.callable) { + BuiltInCallable(hasContent: var hasContent) => hasContent, + UserDefinedCallable(declaration: MixinRule(hasContent: var hasContent)) => + hasContent, + AsyncCallable() => + throw UnsupportedError("Unknown callable type $mixin."), + }; + return SassBoolean(acceptsContent); }) ]); diff --git a/lib/src/value.dart b/lib/src/value.dart index 4b21e2434..51f3eb83e 100644 --- a/lib/src/value.dart +++ b/lib/src/value.dart @@ -15,6 +15,7 @@ import 'value/color.dart'; import 'value/function.dart'; import 'value/list.dart'; import 'value/map.dart'; +import 'value/mixin.dart'; import 'value/number.dart'; import 'value/string.dart'; import 'visitor/interface/value.dart'; @@ -27,6 +28,7 @@ export 'value/color.dart'; export 'value/function.dart'; export 'value/list.dart'; export 'value/map.dart'; +export 'value/mixin.dart'; export 'value/null.dart'; export 'value/number.dart' hide conversionFactor; export 'value/string.dart'; @@ -177,6 +179,13 @@ abstract class Value { SassFunction assertFunction([String? name]) => throw SassScriptException("$this is not a function reference.", name); + /// Throws a [SassScriptException] if [this] isn't a mixin reference. + /// + /// If this came from a mixin argument, [name] is the argument name + /// (without the `$`). It's used for error reporting. + SassMixin assertMixin([String? name]) => + throw SassScriptException("$this is not a mixin reference.", name); + /// Throws a [SassScriptException] if [this] isn't a map. /// /// If this came from a function argument, [name] is the argument name diff --git a/lib/src/value/mixin.dart b/lib/src/value/mixin.dart new file mode 100644 index 000000000..c9795d76d --- /dev/null +++ b/lib/src/value/mixin.dart @@ -0,0 +1,38 @@ +// Copyright 2016 Google Inc. Use of this source code is governed by an +// MIT-style license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'package:meta/meta.dart'; + +import '../callable.dart'; +import '../visitor/interface/value.dart'; +import '../value.dart'; + +/// A SassScript mixin reference. +/// +/// A mixin reference captures a mixin from the local environment so that +/// it may be passed between modules. +/// +/// {@category Value} +@sealed +class SassMixin extends Value { + /// The callable that this mixin invokes. + /// + /// Note that this is typed as an [AsyncCallable] so that it will work with + /// both synchronous and asynchronous evaluate visitors, but in practice the + /// synchronous evaluate visitor will crash if this isn't a [Callable]. + final AsyncCallable callable; + + SassMixin(this.callable); + + /// @nodoc + @internal + T accept(ValueVisitor visitor) => visitor.visitMixin(this); + + SassMixin assertMixin([String? name]) => this; + + bool operator ==(Object other) => + other is SassMixin && callable == other.callable; + + int get hashCode => callable.hashCode; +} diff --git a/lib/src/visitor/async_evaluate.dart b/lib/src/visitor/async_evaluate.dart index 3e8dabcd3..952d5c0d8 100644 --- a/lib/src/visitor/async_evaluate.dart +++ b/lib/src/visitor/async_evaluate.dart @@ -417,6 +417,19 @@ final class _EvaluateVisitor }); }, url: "sass:meta"), + BuiltInCallable.function("module-mixins", r"$module", (arguments) { + var namespace = arguments[0].assertString("module"); + var module = _environment.modules[namespace.text]; + if (module == null) { + throw 'There is no module with namespace "${namespace.text}".'; + } + + return SassMap({ + for (var (name, value) in module.mixins.pairs) + SassString(name): SassMixin(value) + }); + }, url: "sass:meta"), + BuiltInCallable.function( "get-function", r"$name, $css: false, $module: null", (arguments) { var name = arguments[0].assertString("name"); @@ -439,6 +452,20 @@ final class _EvaluateVisitor return SassFunction(callable); }, url: "sass:meta"), + BuiltInCallable.function("get-mixin", r"$name, $module: null", + (arguments) { + var name = arguments[0].assertString("name"); + var module = arguments[1].realNull?.assertString("module"); + + var callable = _addExceptionSpan( + _callableNode!, + () => _environment.getMixin(name.text.replaceAll("_", "-"), + namespace: module?.text)); + if (callable == null) throw "Mixin not found: $name"; + + return SassMixin(callable); + }, url: "sass:meta"), + AsyncBuiltInCallable.function("call", r"$function, $args...", (arguments) async { var function = arguments[0]; @@ -512,7 +539,70 @@ final class _EvaluateVisitor configuration: configuration, namesInErrors: true); _assertConfigurationIsEmpty(configuration, nameInError: true); - }, url: "sass:meta") + }, url: "sass:meta"), + BuiltInCallable.mixin("apply", r"$mixin, $args...", (arguments) async { + var mixin = arguments[0]; + var args = arguments[1] as SassArgumentList; + + var callableNode = _callableNode!; + var invocation = ArgumentInvocation([], {}, callableNode.span, + rest: ValueExpression(args, callableNode.span), + keywordRest: args.keywords.isEmpty + ? null + : ValueExpression( + SassMap({ + for (var (name, value) in args.keywords.pairs) + SassString(name, quotes: false): value + }), + callableNode.span)); + + var callable = mixin.assertMixin("mixin").callable; + // ignore: unnecessary_type_check + if (callable is AsyncCallable) { + switch (callable) { + case BuiltInCallable() when _environment.content != null: + throw _exception("Mixin doesn't accept a content block."); + + case BuiltInCallable(): + await _runBuiltInCallable(invocation, callable, callableNode); + + case UserDefinedCallable( + declaration: MixinRule(hasContent: false) + ) + when _environment.content != null: + throw MultiSpanSassRuntimeException( + "Mixin doesn't accept a content block.", + callableNode.span, + "invocation", + {callable.declaration.arguments.spanWithName: "declaration"}, + _stackTrace(callableNode.span)); + + case UserDefinedCallable(): + var contentCallable = _environment.content.andThen((content) => + UserDefinedCallable( + content as CallableDeclaration, _environment.closure(), + inDependency: _inDependency)); + _runUserDefinedCallable(invocation, callable, callableNode, + () async { + _environment.withContent(contentCallable, () async { + _environment.asMixin(() async { + for (var statement in callable.declaration.children) { + await _addErrorSpan( + callableNode, () => statement.accept(this)); + } + }); + }); + }); + + case _: + throw UnsupportedError("Unknown callable type $mixin."); + } + } else { + throw SassScriptException( + "The mixin ${callable.name} is asynchronous.\n" + "This is probably caused by a bug in a Sass plugin."); + } + }, url: "sass:meta", hasContent: true), ]; var metaModule = BuiltInModule("meta", diff --git a/lib/src/visitor/interface/value.dart b/lib/src/visitor/interface/value.dart index db25c86d5..e25f5ba11 100644 --- a/lib/src/visitor/interface/value.dart +++ b/lib/src/visitor/interface/value.dart @@ -12,6 +12,7 @@ abstract interface class ValueVisitor { T visitCalculation(SassCalculation value); T visitColor(SassColor value); T visitFunction(SassFunction value); + T visitMixin(SassMixin value); T visitList(SassList value); T visitMap(SassMap value); T visitNull(); diff --git a/lib/src/visitor/serialize.dart b/lib/src/visitor/serialize.dart index c0c071155..1df44fabc 100644 --- a/lib/src/visitor/serialize.dart +++ b/lib/src/visitor/serialize.dart @@ -676,6 +676,16 @@ final class _SerializeVisitor _buffer.writeCharCode($rparen); } + void visitMixin(SassMixin mixin) { + if (!_inspect) { + throw SassScriptException("$mixin isn't a valid CSS value."); + } + + _buffer.write("get-mixin("); + _visitQuotedString(mixin.callable.name); + _buffer.writeCharCode($rparen); + } + void visitList(SassList value) { if (value.hasBrackets) { _buffer.writeCharCode($lbracket); From 0b9f3a8d584977de503cbca60b0d42d0b179d54f Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Sat, 26 Aug 2023 03:41:41 +0000 Subject: [PATCH 02/27] add mixin type to js api --- lib/src/js.dart | 1 + lib/src/js/exports.dart | 1 + lib/src/js/value.dart | 2 ++ lib/src/js/value/mixin.dart | 22 ++++++++++++++++++++++ 4 files changed, 26 insertions(+) create mode 100644 lib/src/js/value/mixin.dart diff --git a/lib/src/js.dart b/lib/src/js.dart index cd1480719..9a2c51c06 100644 --- a/lib/src/js.dart +++ b/lib/src/js.dart @@ -32,6 +32,7 @@ void main() { exports.CalculationInterpolation = calculationInterpolationClass; exports.SassColor = colorClass; exports.SassFunction = functionClass; + exports.SassMixin = mixinClass; exports.SassList = listClass; exports.SassMap = mapClass; exports.SassNumber = numberClass; diff --git a/lib/src/js/exports.dart b/lib/src/js/exports.dart index 0dff13698..ee5a74471 100644 --- a/lib/src/js/exports.dart +++ b/lib/src/js/exports.dart @@ -32,6 +32,7 @@ class Exports { external set SassBoolean(JSClass function); external set SassColor(JSClass function); external set SassFunction(JSClass function); + external set SassMixin(JSClass mixin); external set SassList(JSClass function); external set SassMap(JSClass function); external set SassNumber(JSClass function); diff --git a/lib/src/js/value.dart b/lib/src/js/value.dart index f8697efea..c57621c45 100644 --- a/lib/src/js/value.dart +++ b/lib/src/js/value.dart @@ -15,6 +15,7 @@ export 'value/color.dart'; export 'value/function.dart'; export 'value/list.dart'; export 'value/map.dart'; +export 'value/mixin.dart'; export 'value/number.dart'; export 'value/string.dart'; @@ -42,6 +43,7 @@ final JSClass valueClass = () { 'assertColor': (Value self, [String? name]) => self.assertColor(name), 'assertFunction': (Value self, [String? name]) => self.assertFunction(name), 'assertMap': (Value self, [String? name]) => self.assertMap(name), + 'assertMixin': (Value self, [String? name]) => self.assertMixin(name), 'assertNumber': (Value self, [String? name]) => self.assertNumber(name), 'assertString': (Value self, [String? name]) => self.assertString(name), 'tryMap': (Value self) => self.tryMap(), diff --git a/lib/src/js/value/mixin.dart b/lib/src/js/value/mixin.dart new file mode 100644 index 000000000..c3bbf59b7 --- /dev/null +++ b/lib/src/js/value/mixin.dart @@ -0,0 +1,22 @@ +// Copyright 2021 Google Inc. Use of this source code is governed by an +// MIT-style license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'package:node_interop/js.dart'; + +import '../../callable.dart'; +import '../../value.dart'; +import '../reflection.dart'; +import '../utils.dart'; + +/// The JavaScript `SassMixin` class. +final JSClass mixinClass = () { + var jsClass = createJSClass('sass.SassMixin', () { + jsThrow(JsError( + 'It is not possible to construct a SassMixin through the JavaScript API')); + }); + + getJSClass(SassMixin(Callable('f', '', (_) => sassNull))) + .injectSuperclass(jsClass); + return jsClass; +}(); From 63c1f5e819736813b6a40b38056109717da4775e Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Sat, 26 Aug 2023 04:06:55 +0000 Subject: [PATCH 03/27] add first class mixins to embedded protocol --- lib/src/embedded/dispatcher.dart | 4 +++- lib/src/embedded/host_callable.dart | 8 ++++--- lib/src/embedded/mixin_registry.dart | 33 ++++++++++++++++++++++++++++ lib/src/embedded/protofier.dart | 19 ++++++++++++++-- 4 files changed, 58 insertions(+), 6 deletions(-) create mode 100644 lib/src/embedded/mixin_registry.dart diff --git a/lib/src/embedded/dispatcher.dart b/lib/src/embedded/dispatcher.dart index fae22b458..1f0a12481 100644 --- a/lib/src/embedded/dispatcher.dart +++ b/lib/src/embedded/dispatcher.dart @@ -18,6 +18,7 @@ import 'host_callable.dart'; import 'importer/file.dart'; import 'importer/host.dart'; import 'logger.dart'; +import 'mixin_registry.dart'; import 'util/proto_extensions.dart'; import 'utils.dart'; @@ -126,6 +127,7 @@ final class Dispatcher { Future _compile( InboundMessage_CompileRequest request) async { var functions = FunctionRegistry(); + var mixins = MixinRegistry(); var style = request.style == OutputStyle.COMPRESSED ? sass.OutputStyle.compressed @@ -139,7 +141,7 @@ final class Dispatcher { (throw mandatoryError("Importer.importer"))); var globalFunctions = request.globalFunctions - .map((signature) => hostCallable(this, functions, signature)); + .map((signature) => hostCallable(this, functions, mixins, signature)); late sass.CompileResult result; switch (request.whichInput()) { diff --git a/lib/src/embedded/host_callable.dart b/lib/src/embedded/host_callable.dart index bb1770ea4..23c1cf4e5 100644 --- a/lib/src/embedded/host_callable.dart +++ b/lib/src/embedded/host_callable.dart @@ -5,6 +5,8 @@ // ignore: deprecated_member_use import 'dart:cli'; +import 'package:sass/src/embedded/mixin_registry.dart'; + import '../callable.dart'; import '../exception.dart'; import 'dispatcher.dart'; @@ -21,12 +23,12 @@ import 'utils.dart'; /// the name defined in the [signature]. /// /// Throws a [SassException] if [signature] is invalid. -Callable hostCallable( - Dispatcher dispatcher, FunctionRegistry functions, String signature, +Callable hostCallable(Dispatcher dispatcher, FunctionRegistry functions, + MixinRegistry mixins, String signature, {int? id}) { late Callable callable; callable = Callable.fromSignature(signature, (arguments) { - var protofier = Protofier(dispatcher, functions); + var protofier = Protofier(dispatcher, functions, mixins); var request = OutboundMessage_FunctionCallRequest() ..arguments.addAll( [for (var argument in arguments) protofier.protofy(argument)]); diff --git a/lib/src/embedded/mixin_registry.dart b/lib/src/embedded/mixin_registry.dart new file mode 100644 index 000000000..54cdb2c86 --- /dev/null +++ b/lib/src/embedded/mixin_registry.dart @@ -0,0 +1,33 @@ +// Copyright 2019 Google Inc. Use of this source code is governed by an +// MIT-style license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import '../value/mixin.dart'; +import 'embedded_sass.pb.dart'; + +/// A registry of [SassMixin]s indexed by ID so that the host can invoke +/// them. +final class MixinRegistry { + /// First-class mixins that have been sent to the host. + /// + /// The mixins are located at indexes in the list matching their IDs. + final _mixinsById = []; + + /// A reverse map from mixins to their indexes in [_mixinsById]. + final _idsByMixin = {}; + + /// Converts [mixin] to a protocol buffer to send to the host. + Value_CompilerMixin protofy(SassMixin mixin) { + var id = _idsByMixin.putIfAbsent(mixin, () { + _mixinsById.add(mixin); + return _mixinsById.length - 1; + }); + + return Value_CompilerMixin()..id = id; + } + + /// Returns the compiler-side mixin associated with [id]. + /// + /// If no such mixin exists, returns `null`. + SassMixin? operator [](int id) => _mixinsById[id]; +} diff --git a/lib/src/embedded/protofier.dart b/lib/src/embedded/protofier.dart index 3a1a792b0..80afaaf42 100644 --- a/lib/src/embedded/protofier.dart +++ b/lib/src/embedded/protofier.dart @@ -9,6 +9,7 @@ import 'dispatcher.dart'; import 'embedded_sass.pb.dart' as proto; import 'embedded_sass.pb.dart' hide Value, ListSeparator, CalculationOperator; import 'function_registry.dart'; +import 'mixin_registry.dart'; import 'host_callable.dart'; import 'utils.dart'; @@ -23,6 +24,9 @@ final class Protofier { /// The IDs of first-class functions. final FunctionRegistry _functions; + /// The IDs of first-class mixins. + final MixinRegistry _mixins; + /// Any argument lists transitively contained in [value]. /// /// The IDs of the [Value_ArgumentList] protobufs are always one greater than @@ -35,7 +39,10 @@ final class Protofier { /// /// The [functions] tracks the IDs of first-class functions so that the host /// can pass them back to the compiler. - Protofier(this._dispatcher, this._functions); + /// + /// Similarly, the [mixins] tracks the IDs of first-class mixins so that the + /// host can pass them back to the compiler. + Protofier(this._dispatcher, this._functions, this._mixins); /// Converts [value] to its protocol buffer representation. proto.Value protofy(Value value) { @@ -85,6 +92,8 @@ final class Protofier { result.calculation = _protofyCalculation(value); case SassFunction(): result.compilerFunction = _functions.protofy(value); + case SassMixin(): + result.compilerMixin = _mixins.protofy(value); case sassTrue: result.singleton = SingletonValue.TRUE; case sassFalse: @@ -240,9 +249,15 @@ final class Protofier { case Value_Value.hostFunction: return SassFunction(hostCallable( - _dispatcher, _functions, value.hostFunction.signature, + _dispatcher, _functions, _mixins, value.hostFunction.signature, id: value.hostFunction.id)); + case Value_Value.compilerMixin: + var id = value.compilerMixin.id; + if (_mixins[id] case var mixin?) return mixin; + throw paramsError( + "CompilerMixin.id $id doesn't match any known mixins"); + case Value_Value.calculation: return _deprotofyCalculation(value.calculation); From f9badfabc314cbd5d70e50563253edd938cbb29a Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Sat, 26 Aug 2023 04:19:14 +0000 Subject: [PATCH 04/27] support AsyncBuiltInCallable in accepts-content --- lib/src/callable/async_built_in.dart | 9 ++++++--- lib/src/functions/meta.dart | 3 ++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/src/callable/async_built_in.dart b/lib/src/callable/async_built_in.dart index 0132b787f..20c29846f 100644 --- a/lib/src/callable/async_built_in.dart +++ b/lib/src/callable/async_built_in.dart @@ -26,6 +26,8 @@ class AsyncBuiltInCallable implements AsyncCallable { /// The callback to run when executing this callable. final Callback _callback; + bool hasContent = false; + /// Creates a function with a single [arguments] declaration and a single /// [callback]. /// @@ -52,7 +54,7 @@ class AsyncBuiltInCallable implements AsyncCallable { /// defined. AsyncBuiltInCallable.mixin(String name, String arguments, FutureOr callback(List arguments), - {Object? url}) + {Object? url, bool hasContent = false}) : this.parsed(name, ArgumentDeclaration.parse('@mixin $name($arguments) {', url: url), (arguments) async { @@ -62,11 +64,12 @@ class AsyncBuiltInCallable implements AsyncCallable { // quickly so it's easier to just return Sass's `null` for mixins and // simply ignore it at the call site. return sassNull; - }); + }, hasContent: hasContent); /// Creates a callable with a single [arguments] declaration and a single /// [callback]. - AsyncBuiltInCallable.parsed(this.name, this._arguments, this._callback); + AsyncBuiltInCallable.parsed(this.name, this._arguments, this._callback, + {this.hasContent = false}); /// Returns the argument declaration and Dart callback for the given /// positional and named arguments. diff --git a/lib/src/functions/meta.dart b/lib/src/functions/meta.dart index cb3e1b541..76090dd94 100644 --- a/lib/src/functions/meta.dart +++ b/lib/src/functions/meta.dart @@ -83,7 +83,8 @@ final local = UnmodifiableListView([ _function("accepts-content", r"$mixin", (arguments) { var mixin = arguments[0].assertMixin("mixin"); bool acceptsContent = switch (mixin.callable) { - BuiltInCallable(hasContent: var hasContent) => hasContent, + AsyncBuiltInCallable(hasContent: var hasContent) || + BuiltInCallable(hasContent: var hasContent) || UserDefinedCallable(declaration: MixinRule(hasContent: var hasContent)) => hasContent, AsyncCallable() => From 010ad35a5f42b8385f850ee8e9dcb43100f8dd93 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Sat, 26 Aug 2023 04:20:30 +0000 Subject: [PATCH 05/27] generate evaluate file --- lib/src/visitor/evaluate.dart | 92 ++++++++++++++++++++++++++++++++++- 1 file changed, 90 insertions(+), 2 deletions(-) diff --git a/lib/src/visitor/evaluate.dart b/lib/src/visitor/evaluate.dart index a8639f4e6..742894ce6 100644 --- a/lib/src/visitor/evaluate.dart +++ b/lib/src/visitor/evaluate.dart @@ -5,7 +5,7 @@ // DO NOT EDIT. This file was generated from async_evaluate.dart. // See tool/grind/synchronize.dart for details. // -// Checksum: 6eb7f76735562eba91e9460af796b269b3b0aaf7 +// Checksum: d7004c5c19ae2dc5523c96cecb3c24168deddcf4 // // ignore_for_file: unused_import @@ -425,6 +425,19 @@ final class _EvaluateVisitor }); }, url: "sass:meta"), + BuiltInCallable.function("module-mixins", r"$module", (arguments) { + var namespace = arguments[0].assertString("module"); + var module = _environment.modules[namespace.text]; + if (module == null) { + throw 'There is no module with namespace "${namespace.text}".'; + } + + return SassMap({ + for (var (name, value) in module.mixins.pairs) + SassString(name): SassMixin(value) + }); + }, url: "sass:meta"), + BuiltInCallable.function( "get-function", r"$name, $css: false, $module: null", (arguments) { var name = arguments[0].assertString("name"); @@ -447,6 +460,20 @@ final class _EvaluateVisitor return SassFunction(callable); }, url: "sass:meta"), + BuiltInCallable.function("get-mixin", r"$name, $module: null", + (arguments) { + var name = arguments[0].assertString("name"); + var module = arguments[1].realNull?.assertString("module"); + + var callable = _addExceptionSpan( + _callableNode!, + () => _environment.getMixin(name.text.replaceAll("_", "-"), + namespace: module?.text)); + if (callable == null) throw "Mixin not found: $name"; + + return SassMixin(callable); + }, url: "sass:meta"), + BuiltInCallable.function("call", r"$function, $args...", (arguments) { var function = arguments[0]; var args = arguments[1] as SassArgumentList; @@ -517,7 +544,68 @@ final class _EvaluateVisitor configuration: configuration, namesInErrors: true); _assertConfigurationIsEmpty(configuration, nameInError: true); - }, url: "sass:meta") + }, url: "sass:meta"), + BuiltInCallable.mixin("apply", r"$mixin, $args...", (arguments) { + var mixin = arguments[0]; + var args = arguments[1] as SassArgumentList; + + var callableNode = _callableNode!; + var invocation = ArgumentInvocation([], {}, callableNode.span, + rest: ValueExpression(args, callableNode.span), + keywordRest: args.keywords.isEmpty + ? null + : ValueExpression( + SassMap({ + for (var (name, value) in args.keywords.pairs) + SassString(name, quotes: false): value + }), + callableNode.span)); + + var callable = mixin.assertMixin("mixin").callable; + // ignore: unnecessary_type_check + if (callable is Callable) { + switch (callable) { + case BuiltInCallable() when _environment.content != null: + throw _exception("Mixin doesn't accept a content block."); + + case BuiltInCallable(): + _runBuiltInCallable(invocation, callable, callableNode); + + case UserDefinedCallable( + declaration: MixinRule(hasContent: false) + ) + when _environment.content != null: + throw MultiSpanSassRuntimeException( + "Mixin doesn't accept a content block.", + callableNode.span, + "invocation", + {callable.declaration.arguments.spanWithName: "declaration"}, + _stackTrace(callableNode.span)); + + case UserDefinedCallable(): + var contentCallable = _environment.content.andThen((content) => + UserDefinedCallable( + content as CallableDeclaration, _environment.closure(), + inDependency: _inDependency)); + _runUserDefinedCallable(invocation, callable, callableNode, () { + _environment.withContent(contentCallable, () { + _environment.asMixin(() { + for (var statement in callable.declaration.children) { + _addErrorSpan(callableNode, () => statement.accept(this)); + } + }); + }); + }); + + case _: + throw UnsupportedError("Unknown callable type $mixin."); + } + } else { + throw SassScriptException( + "The mixin ${callable.name} is asynchronous.\n" + "This is probably caused by a bug in a Sass plugin."); + } + }, url: "sass:meta", hasContent: true), ]; var metaModule = BuiltInModule("meta", From b908e2746397023e55ada6c41d6f6b1d18495a8f Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Tue, 29 Aug 2023 19:31:57 +0000 Subject: [PATCH 06/27] bump ci From f715c226d362768334f38183f9540c73a3e816bb Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Sat, 2 Sep 2023 00:37:24 +0000 Subject: [PATCH 07/27] revert embedded protocol changes --- lib/src/embedded/dispatcher.dart | 4 +--- lib/src/embedded/host_callable.dart | 7 +++--- lib/src/embedded/mixin_registry.dart | 33 ---------------------------- lib/src/embedded/protofier.dart | 19 ++-------------- 4 files changed, 6 insertions(+), 57 deletions(-) delete mode 100644 lib/src/embedded/mixin_registry.dart diff --git a/lib/src/embedded/dispatcher.dart b/lib/src/embedded/dispatcher.dart index 3174ebc1c..22df57fca 100644 --- a/lib/src/embedded/dispatcher.dart +++ b/lib/src/embedded/dispatcher.dart @@ -18,7 +18,6 @@ import 'host_callable.dart'; import 'importer/file.dart'; import 'importer/host.dart'; import 'logger.dart'; -import 'mixin_registry.dart'; import 'util/proto_extensions.dart'; import 'utils.dart'; @@ -111,7 +110,6 @@ final class Dispatcher { OutboundMessage_CompileResponse _compile( InboundMessage_CompileRequest request) { var functions = FunctionRegistry(); - var mixins = MixinRegistry(); var style = request.style == OutputStyle.COMPRESSED ? sass.OutputStyle.compressed @@ -125,7 +123,7 @@ final class Dispatcher { (throw mandatoryError("Importer.importer"))); var globalFunctions = request.globalFunctions - .map((signature) => hostCallable(this, functions, mixins, signature)); + .map((signature) => hostCallable(this, functions, signature)); late sass.CompileResult result; switch (request.whichInput()) { diff --git a/lib/src/embedded/host_callable.dart b/lib/src/embedded/host_callable.dart index 5c3719a47..448cce217 100644 --- a/lib/src/embedded/host_callable.dart +++ b/lib/src/embedded/host_callable.dart @@ -2,7 +2,6 @@ // MIT-style license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -import '../../src/embedded/mixin_registry.dart'; import '../callable.dart'; import '../exception.dart'; import 'dispatcher.dart'; @@ -19,12 +18,12 @@ import 'utils.dart'; /// the name defined in the [signature]. /// /// Throws a [SassException] if [signature] is invalid. -Callable hostCallable(Dispatcher dispatcher, FunctionRegistry functions, - MixinRegistry mixins, String signature, +Callable hostCallable( + Dispatcher dispatcher, FunctionRegistry functions, String signature, {int? id}) { late Callable callable; callable = Callable.fromSignature(signature, (arguments) { - var protofier = Protofier(dispatcher, functions, mixins); + var protofier = Protofier(dispatcher, functions); var request = OutboundMessage_FunctionCallRequest() ..arguments.addAll( [for (var argument in arguments) protofier.protofy(argument)]); diff --git a/lib/src/embedded/mixin_registry.dart b/lib/src/embedded/mixin_registry.dart deleted file mode 100644 index 54cdb2c86..000000000 --- a/lib/src/embedded/mixin_registry.dart +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2019 Google Inc. Use of this source code is governed by an -// MIT-style license that can be found in the LICENSE file or at -// https://opensource.org/licenses/MIT. - -import '../value/mixin.dart'; -import 'embedded_sass.pb.dart'; - -/// A registry of [SassMixin]s indexed by ID so that the host can invoke -/// them. -final class MixinRegistry { - /// First-class mixins that have been sent to the host. - /// - /// The mixins are located at indexes in the list matching their IDs. - final _mixinsById = []; - - /// A reverse map from mixins to their indexes in [_mixinsById]. - final _idsByMixin = {}; - - /// Converts [mixin] to a protocol buffer to send to the host. - Value_CompilerMixin protofy(SassMixin mixin) { - var id = _idsByMixin.putIfAbsent(mixin, () { - _mixinsById.add(mixin); - return _mixinsById.length - 1; - }); - - return Value_CompilerMixin()..id = id; - } - - /// Returns the compiler-side mixin associated with [id]. - /// - /// If no such mixin exists, returns `null`. - SassMixin? operator [](int id) => _mixinsById[id]; -} diff --git a/lib/src/embedded/protofier.dart b/lib/src/embedded/protofier.dart index 80afaaf42..3a1a792b0 100644 --- a/lib/src/embedded/protofier.dart +++ b/lib/src/embedded/protofier.dart @@ -9,7 +9,6 @@ import 'dispatcher.dart'; import 'embedded_sass.pb.dart' as proto; import 'embedded_sass.pb.dart' hide Value, ListSeparator, CalculationOperator; import 'function_registry.dart'; -import 'mixin_registry.dart'; import 'host_callable.dart'; import 'utils.dart'; @@ -24,9 +23,6 @@ final class Protofier { /// The IDs of first-class functions. final FunctionRegistry _functions; - /// The IDs of first-class mixins. - final MixinRegistry _mixins; - /// Any argument lists transitively contained in [value]. /// /// The IDs of the [Value_ArgumentList] protobufs are always one greater than @@ -39,10 +35,7 @@ final class Protofier { /// /// The [functions] tracks the IDs of first-class functions so that the host /// can pass them back to the compiler. - /// - /// Similarly, the [mixins] tracks the IDs of first-class mixins so that the - /// host can pass them back to the compiler. - Protofier(this._dispatcher, this._functions, this._mixins); + Protofier(this._dispatcher, this._functions); /// Converts [value] to its protocol buffer representation. proto.Value protofy(Value value) { @@ -92,8 +85,6 @@ final class Protofier { result.calculation = _protofyCalculation(value); case SassFunction(): result.compilerFunction = _functions.protofy(value); - case SassMixin(): - result.compilerMixin = _mixins.protofy(value); case sassTrue: result.singleton = SingletonValue.TRUE; case sassFalse: @@ -249,15 +240,9 @@ final class Protofier { case Value_Value.hostFunction: return SassFunction(hostCallable( - _dispatcher, _functions, _mixins, value.hostFunction.signature, + _dispatcher, _functions, value.hostFunction.signature, id: value.hostFunction.id)); - case Value_Value.compilerMixin: - var id = value.compilerMixin.id; - if (_mixins[id] case var mixin?) return mixin; - throw paramsError( - "CompilerMixin.id $id doesn't match any known mixins"); - case Value_Value.calculation: return _deprotofyCalculation(value.calculation); From c0eced3585c0eaab0b63e25f3b50b73bb1610be7 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Tue, 5 Sep 2023 17:36:11 -0700 Subject: [PATCH 08/27] Style nits --- lib/src/functions/meta.dart | 8 +++----- lib/src/value/mixin.dart | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/src/functions/meta.dart b/lib/src/functions/meta.dart index 76090dd94..be296c295 100644 --- a/lib/src/functions/meta.dart +++ b/lib/src/functions/meta.dart @@ -82,15 +82,13 @@ final local = UnmodifiableListView([ }), _function("accepts-content", r"$mixin", (arguments) { var mixin = arguments[0].assertMixin("mixin"); - bool acceptsContent = switch (mixin.callable) { + return SassBoolean(switch (mixin.callable) { AsyncBuiltInCallable(hasContent: var hasContent) || BuiltInCallable(hasContent: var hasContent) || UserDefinedCallable(declaration: MixinRule(hasContent: var hasContent)) => hasContent, - AsyncCallable() => - throw UnsupportedError("Unknown callable type $mixin."), - }; - return SassBoolean(acceptsContent); + _ => throw UnsupportedError("Unknown callable type $mixin.") + }); }) ]); diff --git a/lib/src/value/mixin.dart b/lib/src/value/mixin.dart index c9795d76d..f3b77a870 100644 --- a/lib/src/value/mixin.dart +++ b/lib/src/value/mixin.dart @@ -1,4 +1,4 @@ -// Copyright 2016 Google Inc. Use of this source code is governed by an +// Copyright 2023 Google Inc. Use of this source code is governed by an // MIT-style license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. From 91ac2793365bdf0ceb6b524d0011223accd80502 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Thu, 7 Sep 2023 14:56:29 +0000 Subject: [PATCH 09/27] some pr review --- lib/src/callable/async_built_in.dart | 9 +-- lib/src/functions/meta.dart | 3 +- lib/src/value.dart | 2 +- lib/src/visitor/async_evaluate.dart | 112 ++++++++++++--------------- 4 files changed, 55 insertions(+), 71 deletions(-) diff --git a/lib/src/callable/async_built_in.dart b/lib/src/callable/async_built_in.dart index 20c29846f..0132b787f 100644 --- a/lib/src/callable/async_built_in.dart +++ b/lib/src/callable/async_built_in.dart @@ -26,8 +26,6 @@ class AsyncBuiltInCallable implements AsyncCallable { /// The callback to run when executing this callable. final Callback _callback; - bool hasContent = false; - /// Creates a function with a single [arguments] declaration and a single /// [callback]. /// @@ -54,7 +52,7 @@ class AsyncBuiltInCallable implements AsyncCallable { /// defined. AsyncBuiltInCallable.mixin(String name, String arguments, FutureOr callback(List arguments), - {Object? url, bool hasContent = false}) + {Object? url}) : this.parsed(name, ArgumentDeclaration.parse('@mixin $name($arguments) {', url: url), (arguments) async { @@ -64,12 +62,11 @@ class AsyncBuiltInCallable implements AsyncCallable { // quickly so it's easier to just return Sass's `null` for mixins and // simply ignore it at the call site. return sassNull; - }, hasContent: hasContent); + }); /// Creates a callable with a single [arguments] declaration and a single /// [callback]. - AsyncBuiltInCallable.parsed(this.name, this._arguments, this._callback, - {this.hasContent = false}); + AsyncBuiltInCallable.parsed(this.name, this._arguments, this._callback); /// Returns the argument declaration and Dart callback for the given /// positional and named arguments. diff --git a/lib/src/functions/meta.dart b/lib/src/functions/meta.dart index be296c295..49ed8da3f 100644 --- a/lib/src/functions/meta.dart +++ b/lib/src/functions/meta.dart @@ -83,8 +83,7 @@ final local = UnmodifiableListView([ _function("accepts-content", r"$mixin", (arguments) { var mixin = arguments[0].assertMixin("mixin"); return SassBoolean(switch (mixin.callable) { - AsyncBuiltInCallable(hasContent: var hasContent) || - BuiltInCallable(hasContent: var hasContent) || + AsyncBuiltInCallable() || BuiltInCallable() => false, UserDefinedCallable(declaration: MixinRule(hasContent: var hasContent)) => hasContent, _ => throw UnsupportedError("Unknown callable type $mixin.") diff --git a/lib/src/value.dart b/lib/src/value.dart index 51f3eb83e..3149435a0 100644 --- a/lib/src/value.dart +++ b/lib/src/value.dart @@ -181,7 +181,7 @@ abstract class Value { /// Throws a [SassScriptException] if [this] isn't a mixin reference. /// - /// If this came from a mixin argument, [name] is the argument name + /// If this came from a function argument, [name] is the argument name /// (without the `$`). It's used for error reporting. SassMixin assertMixin([String? name]) => throw SassScriptException("$this is not a mixin reference.", name); diff --git a/lib/src/visitor/async_evaluate.dart b/lib/src/visitor/async_evaluate.dart index 952d5c0d8..864684cb2 100644 --- a/lib/src/visitor/async_evaluate.dart +++ b/lib/src/visitor/async_evaluate.dart @@ -472,16 +472,12 @@ final class _EvaluateVisitor var args = arguments[1] as SassArgumentList; var callableNode = _callableNode!; - var invocation = ArgumentInvocation([], {}, callableNode.span, - rest: ValueExpression(args, callableNode.span), - keywordRest: args.keywords.isEmpty - ? null - : ValueExpression( - SassMap({ - for (var (name, value) in args.keywords.pairs) - SassString(name, quotes: false): value - }), - callableNode.span)); + var invocation = ArgumentInvocation( + [], + {}, + callableNode.span, + rest: ValueExpression(args, callableNode.span), + ); if (function is SassString) { warnForDeprecation( @@ -545,64 +541,56 @@ final class _EvaluateVisitor var args = arguments[1] as SassArgumentList; var callableNode = _callableNode!; - var invocation = ArgumentInvocation([], {}, callableNode.span, - rest: ValueExpression(args, callableNode.span), - keywordRest: args.keywords.isEmpty - ? null - : ValueExpression( - SassMap({ - for (var (name, value) in args.keywords.pairs) - SassString(name, quotes: false): value - }), - callableNode.span)); + var invocation = ArgumentInvocation( + [], + {}, + callableNode.span, + rest: ValueExpression(args, callableNode.span), + ); var callable = mixin.assertMixin("mixin").callable; - // ignore: unnecessary_type_check - if (callable is AsyncCallable) { - switch (callable) { - case BuiltInCallable() when _environment.content != null: - throw _exception("Mixin doesn't accept a content block."); - - case BuiltInCallable(): - await _runBuiltInCallable(invocation, callable, callableNode); - - case UserDefinedCallable( - declaration: MixinRule(hasContent: false) - ) - when _environment.content != null: - throw MultiSpanSassRuntimeException( - "Mixin doesn't accept a content block.", - callableNode.span, - "invocation", - {callable.declaration.arguments.spanWithName: "declaration"}, - _stackTrace(callableNode.span)); - - case UserDefinedCallable(): - var contentCallable = _environment.content.andThen((content) => - UserDefinedCallable( - content as CallableDeclaration, _environment.closure(), - inDependency: _inDependency)); - _runUserDefinedCallable(invocation, callable, callableNode, - () async { - _environment.withContent(contentCallable, () async { - _environment.asMixin(() async { - for (var statement in callable.declaration.children) { - await _addErrorSpan( - callableNode, () => statement.accept(this)); - } - }); + switch (callable) { + case AsyncBuiltInCallable() when _environment.content != null: + throw _exception( + "Mixin doesn't accept a content block.", callableNode.span); + + case AsyncBuiltInCallable(): + await _runBuiltInCallable(invocation, callable, callableNode); + + case UserDefinedCallable( + declaration: MixinRule(hasContent: false) + ) + when _environment.content != null: + throw MultiSpanSassRuntimeException( + "Mixin doesn't accept a content block.", + callableNode.span, + "invocation", + {callable.declaration.arguments.spanWithName: "declaration"}, + _stackTrace(callableNode.span)); + + case UserDefinedCallable(): + var contentCallable = _environment.content.andThen((content) => + UserDefinedCallable( + content as CallableDeclaration, _environment.closure(), + inDependency: _inDependency)); + _runUserDefinedCallable(invocation, callable, callableNode, + () async { + _environment.withContent(contentCallable, () async { + _environment.asMixin(() async { + for (var statement in callable.declaration.children) { + await _addErrorSpan( + callableNode, () => statement.accept(this)); + } }); }); + }); - case _: - throw UnsupportedError("Unknown callable type $mixin."); - } - } else { - throw SassScriptException( - "The mixin ${callable.name} is asynchronous.\n" - "This is probably caused by a bug in a Sass plugin."); + case _: + throw SassScriptException( + "The mixin ${callable.name} is asynchronous.\n" + "This is probably caused by a bug in a Sass plugin."); } - }, url: "sass:meta", hasContent: true), + }, url: "sass:meta"), ]; var metaModule = BuiltInModule("meta", From 194e171401b58f9873491cf789bbe6649f5bf343 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Thu, 7 Sep 2023 14:57:17 +0000 Subject: [PATCH 10/27] Revert "revert embedded protocol changes" This reverts commit f715c226d362768334f38183f9540c73a3e816bb. --- lib/src/embedded/dispatcher.dart | 4 +++- lib/src/embedded/host_callable.dart | 7 +++--- lib/src/embedded/mixin_registry.dart | 33 ++++++++++++++++++++++++++++ lib/src/embedded/protofier.dart | 19 ++++++++++++++-- 4 files changed, 57 insertions(+), 6 deletions(-) create mode 100644 lib/src/embedded/mixin_registry.dart diff --git a/lib/src/embedded/dispatcher.dart b/lib/src/embedded/dispatcher.dart index 22df57fca..3174ebc1c 100644 --- a/lib/src/embedded/dispatcher.dart +++ b/lib/src/embedded/dispatcher.dart @@ -18,6 +18,7 @@ import 'host_callable.dart'; import 'importer/file.dart'; import 'importer/host.dart'; import 'logger.dart'; +import 'mixin_registry.dart'; import 'util/proto_extensions.dart'; import 'utils.dart'; @@ -110,6 +111,7 @@ final class Dispatcher { OutboundMessage_CompileResponse _compile( InboundMessage_CompileRequest request) { var functions = FunctionRegistry(); + var mixins = MixinRegistry(); var style = request.style == OutputStyle.COMPRESSED ? sass.OutputStyle.compressed @@ -123,7 +125,7 @@ final class Dispatcher { (throw mandatoryError("Importer.importer"))); var globalFunctions = request.globalFunctions - .map((signature) => hostCallable(this, functions, signature)); + .map((signature) => hostCallable(this, functions, mixins, signature)); late sass.CompileResult result; switch (request.whichInput()) { diff --git a/lib/src/embedded/host_callable.dart b/lib/src/embedded/host_callable.dart index 448cce217..5c3719a47 100644 --- a/lib/src/embedded/host_callable.dart +++ b/lib/src/embedded/host_callable.dart @@ -2,6 +2,7 @@ // MIT-style license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +import '../../src/embedded/mixin_registry.dart'; import '../callable.dart'; import '../exception.dart'; import 'dispatcher.dart'; @@ -18,12 +19,12 @@ import 'utils.dart'; /// the name defined in the [signature]. /// /// Throws a [SassException] if [signature] is invalid. -Callable hostCallable( - Dispatcher dispatcher, FunctionRegistry functions, String signature, +Callable hostCallable(Dispatcher dispatcher, FunctionRegistry functions, + MixinRegistry mixins, String signature, {int? id}) { late Callable callable; callable = Callable.fromSignature(signature, (arguments) { - var protofier = Protofier(dispatcher, functions); + var protofier = Protofier(dispatcher, functions, mixins); var request = OutboundMessage_FunctionCallRequest() ..arguments.addAll( [for (var argument in arguments) protofier.protofy(argument)]); diff --git a/lib/src/embedded/mixin_registry.dart b/lib/src/embedded/mixin_registry.dart new file mode 100644 index 000000000..54cdb2c86 --- /dev/null +++ b/lib/src/embedded/mixin_registry.dart @@ -0,0 +1,33 @@ +// Copyright 2019 Google Inc. Use of this source code is governed by an +// MIT-style license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import '../value/mixin.dart'; +import 'embedded_sass.pb.dart'; + +/// A registry of [SassMixin]s indexed by ID so that the host can invoke +/// them. +final class MixinRegistry { + /// First-class mixins that have been sent to the host. + /// + /// The mixins are located at indexes in the list matching their IDs. + final _mixinsById = []; + + /// A reverse map from mixins to their indexes in [_mixinsById]. + final _idsByMixin = {}; + + /// Converts [mixin] to a protocol buffer to send to the host. + Value_CompilerMixin protofy(SassMixin mixin) { + var id = _idsByMixin.putIfAbsent(mixin, () { + _mixinsById.add(mixin); + return _mixinsById.length - 1; + }); + + return Value_CompilerMixin()..id = id; + } + + /// Returns the compiler-side mixin associated with [id]. + /// + /// If no such mixin exists, returns `null`. + SassMixin? operator [](int id) => _mixinsById[id]; +} diff --git a/lib/src/embedded/protofier.dart b/lib/src/embedded/protofier.dart index 3a1a792b0..80afaaf42 100644 --- a/lib/src/embedded/protofier.dart +++ b/lib/src/embedded/protofier.dart @@ -9,6 +9,7 @@ import 'dispatcher.dart'; import 'embedded_sass.pb.dart' as proto; import 'embedded_sass.pb.dart' hide Value, ListSeparator, CalculationOperator; import 'function_registry.dart'; +import 'mixin_registry.dart'; import 'host_callable.dart'; import 'utils.dart'; @@ -23,6 +24,9 @@ final class Protofier { /// The IDs of first-class functions. final FunctionRegistry _functions; + /// The IDs of first-class mixins. + final MixinRegistry _mixins; + /// Any argument lists transitively contained in [value]. /// /// The IDs of the [Value_ArgumentList] protobufs are always one greater than @@ -35,7 +39,10 @@ final class Protofier { /// /// The [functions] tracks the IDs of first-class functions so that the host /// can pass them back to the compiler. - Protofier(this._dispatcher, this._functions); + /// + /// Similarly, the [mixins] tracks the IDs of first-class mixins so that the + /// host can pass them back to the compiler. + Protofier(this._dispatcher, this._functions, this._mixins); /// Converts [value] to its protocol buffer representation. proto.Value protofy(Value value) { @@ -85,6 +92,8 @@ final class Protofier { result.calculation = _protofyCalculation(value); case SassFunction(): result.compilerFunction = _functions.protofy(value); + case SassMixin(): + result.compilerMixin = _mixins.protofy(value); case sassTrue: result.singleton = SingletonValue.TRUE; case sassFalse: @@ -240,9 +249,15 @@ final class Protofier { case Value_Value.hostFunction: return SassFunction(hostCallable( - _dispatcher, _functions, value.hostFunction.signature, + _dispatcher, _functions, _mixins, value.hostFunction.signature, id: value.hostFunction.id)); + case Value_Value.compilerMixin: + var id = value.compilerMixin.id; + if (_mixins[id] case var mixin?) return mixin; + throw paramsError( + "CompilerMixin.id $id doesn't match any known mixins"); + case Value_Value.calculation: return _deprotofyCalculation(value.calculation); From 154e12ea9af7b44b68ff10fa36cd27f2ea23aff8 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Fri, 8 Sep 2023 06:38:36 +0000 Subject: [PATCH 11/27] remove redundant content cast --- lib/src/visitor/async_evaluate.dart | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/lib/src/visitor/async_evaluate.dart b/lib/src/visitor/async_evaluate.dart index 864684cb2..d75be5c22 100644 --- a/lib/src/visitor/async_evaluate.dart +++ b/lib/src/visitor/async_evaluate.dart @@ -569,19 +569,13 @@ final class _EvaluateVisitor _stackTrace(callableNode.span)); case UserDefinedCallable(): - var contentCallable = _environment.content.andThen((content) => - UserDefinedCallable( - content as CallableDeclaration, _environment.closure(), - inDependency: _inDependency)); _runUserDefinedCallable(invocation, callable, callableNode, () async { - _environment.withContent(contentCallable, () async { - _environment.asMixin(() async { - for (var statement in callable.declaration.children) { - await _addErrorSpan( - callableNode, () => statement.accept(this)); - } - }); + _environment.asMixin(() async { + for (var statement in callable.declaration.children) { + await _addErrorSpan( + callableNode, () => statement.accept(this)); + } }); }); From ca57cb0fec7bcc49695838d2bab4904a7fa56457 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Fri, 8 Sep 2023 08:41:21 +0000 Subject: [PATCH 12/27] correctly pass content through meta.apply --- lib/src/callable/async_built_in.dart | 8 ++- lib/src/callable/built_in.dart | 16 +++-- lib/src/visitor/async_evaluate.dart | 56 +++++++++------ lib/src/visitor/evaluate.dart | 103 +++++++++++++-------------- 4 files changed, 101 insertions(+), 82 deletions(-) diff --git a/lib/src/callable/async_built_in.dart b/lib/src/callable/async_built_in.dart index 0132b787f..c758dd560 100644 --- a/lib/src/callable/async_built_in.dart +++ b/lib/src/callable/async_built_in.dart @@ -26,6 +26,9 @@ class AsyncBuiltInCallable implements AsyncCallable { /// The callback to run when executing this callable. final Callback _callback; + /// Whether this callback could potentially accept an `@content` block. + final bool acceptsContent; + /// Creates a function with a single [arguments] declaration and a single /// [callback]. /// @@ -52,7 +55,7 @@ class AsyncBuiltInCallable implements AsyncCallable { /// defined. AsyncBuiltInCallable.mixin(String name, String arguments, FutureOr callback(List arguments), - {Object? url}) + {Object? url, bool acceptsContent = false}) : this.parsed(name, ArgumentDeclaration.parse('@mixin $name($arguments) {', url: url), (arguments) async { @@ -66,7 +69,8 @@ class AsyncBuiltInCallable implements AsyncCallable { /// Creates a callable with a single [arguments] declaration and a single /// [callback]. - AsyncBuiltInCallable.parsed(this.name, this._arguments, this._callback); + AsyncBuiltInCallable.parsed(this.name, this._arguments, this._callback, + {this.acceptsContent = false}); /// Returns the argument declaration and Dart callback for the given /// positional and named arguments. diff --git a/lib/src/callable/built_in.dart b/lib/src/callable/built_in.dart index 104e0a2d0..f59b7cf10 100644 --- a/lib/src/callable/built_in.dart +++ b/lib/src/callable/built_in.dart @@ -21,7 +21,7 @@ final class BuiltInCallable implements Callable, AsyncBuiltInCallable { /// The overloads declared for this callable. final List<(ArgumentDeclaration, Callback)> _overloads; - bool hasContent = false; + final bool acceptsContent; /// Creates a function with a single [arguments] declaration and a single /// [callback]. @@ -50,19 +50,19 @@ final class BuiltInCallable implements Callable, AsyncBuiltInCallable { /// defined. BuiltInCallable.mixin( String name, String arguments, void callback(List arguments), - {Object? url, bool hasContent = false}) + {Object? url, bool acceptsContent = false}) : this.parsed(name, ArgumentDeclaration.parse('@mixin $name($arguments) {', url: url), (arguments) { callback(arguments); return sassNull; - }, hasContent: hasContent); + }); /// Creates a callable with a single [arguments] declaration and a single /// [callback]. BuiltInCallable.parsed(this.name, ArgumentDeclaration arguments, Value callback(List arguments), - {this.hasContent = false}) + {this.acceptsContent = false}) : _overloads = [(arguments, callback)]; /// Creates a function with multiple implementations. @@ -82,9 +82,10 @@ final class BuiltInCallable implements Callable, AsyncBuiltInCallable { ArgumentDeclaration.parse('@function $name($args) {', url: url), callback ) - ]; + ], + acceptsContent = false; - BuiltInCallable._(this.name, this._overloads); + BuiltInCallable._(this.name, this._overloads, this.acceptsContent); /// Returns the argument declaration and Dart callback for the given /// positional and named arguments. @@ -120,5 +121,6 @@ final class BuiltInCallable implements Callable, AsyncBuiltInCallable { } /// Returns a copy of this callable with the given [name]. - BuiltInCallable withName(String name) => BuiltInCallable._(name, _overloads); + BuiltInCallable withName(String name) => + BuiltInCallable._(name, _overloads, acceptsContent); } diff --git a/lib/src/visitor/async_evaluate.dart b/lib/src/visitor/async_evaluate.dart index d75be5c22..806f2aa6f 100644 --- a/lib/src/visitor/async_evaluate.dart +++ b/lib/src/visitor/async_evaluate.dart @@ -472,12 +472,16 @@ final class _EvaluateVisitor var args = arguments[1] as SassArgumentList; var callableNode = _callableNode!; - var invocation = ArgumentInvocation( - [], - {}, - callableNode.span, - rest: ValueExpression(args, callableNode.span), - ); + var invocation = ArgumentInvocation([], {}, callableNode.span, + rest: ValueExpression(args, callableNode.span), + keywordRest: args.keywords.isEmpty + ? null + : ValueExpression( + SassMap({ + for (var (name, value) in args.keywords.pairs) + SassString(name, quotes: false): value + }), + callableNode.span)); if (function is SassString) { warnForDeprecation( @@ -549,18 +553,22 @@ final class _EvaluateVisitor ); var callable = mixin.assertMixin("mixin").callable; + var content = _environment.content; + switch (callable) { - case AsyncBuiltInCallable() when _environment.content != null: + case AsyncBuiltInCallable(acceptsContent: false) when content != null: throw _exception( "Mixin doesn't accept a content block.", callableNode.span); case AsyncBuiltInCallable(): - await _runBuiltInCallable(invocation, callable, callableNode); + await _environment.withContent(content, () async { + await _runBuiltInCallable(invocation, callable, callableNode); + }); case UserDefinedCallable( declaration: MixinRule(hasContent: false) ) - when _environment.content != null: + when content != null: throw MultiSpanSassRuntimeException( "Mixin doesn't accept a content block.", callableNode.span, @@ -571,11 +579,13 @@ final class _EvaluateVisitor case UserDefinedCallable(): _runUserDefinedCallable(invocation, callable, callableNode, () async { - _environment.asMixin(() async { - for (var statement in callable.declaration.children) { - await _addErrorSpan( - callableNode, () => statement.accept(this)); - } + await _environment.withContent(content, () async { + _environment.asMixin(() async { + for (var statement in callable.declaration.children) { + await _addErrorSpan( + callableNode, () => statement.accept(this)); + } + }); }); }); @@ -584,7 +594,7 @@ final class _EvaluateVisitor "The mixin ${callable.name} is asynchronous.\n" "This is probably caused by a bug in a Sass plugin."); } - }, url: "sass:meta"), + }, url: "sass:meta", acceptsContent: true), ]; var metaModule = BuiltInModule("meta", @@ -1804,15 +1814,24 @@ final class _EvaluateVisitor var nodeWithSpan = AstNode.fake(() => node.spanWithoutContent); var mixin = _addExceptionSpan(node, () => _environment.getMixin(node.name, namespace: node.namespace)); + var contentCallable = node.content.andThen((content) => UserDefinedCallable( + content, _environment.closure(), + inDependency: _inDependency)); + switch (mixin) { case null: throw _exception("Undefined mixin.", node.span); - case AsyncBuiltInCallable() when node.content != null: + case AsyncBuiltInCallable(acceptsContent: false) + when node.content != null: throw _exception("Mixin doesn't accept a content block.", node.span); case AsyncBuiltInCallable(): - await _runBuiltInCallable(node.arguments, mixin, nodeWithSpan); + await _environment.withContent(contentCallable, () async { + await _environment.asMixin(() async { + await _runBuiltInCallable(node.arguments, mixin, nodeWithSpan); + }); + }); case UserDefinedCallable( declaration: MixinRule(hasContent: false) @@ -1826,9 +1845,6 @@ final class _EvaluateVisitor _stackTrace(node.spanWithoutContent)); case UserDefinedCallable(): - var contentCallable = node.content.andThen((content) => - UserDefinedCallable(content, _environment.closure(), - inDependency: _inDependency)); await _runUserDefinedCallable(node.arguments, mixin, nodeWithSpan, () async { await _environment.withContent(contentCallable, () async { diff --git a/lib/src/visitor/evaluate.dart b/lib/src/visitor/evaluate.dart index 742894ce6..138312799 100644 --- a/lib/src/visitor/evaluate.dart +++ b/lib/src/visitor/evaluate.dart @@ -5,7 +5,7 @@ // DO NOT EDIT. This file was generated from async_evaluate.dart. // See tool/grind/synchronize.dart for details. // -// Checksum: d7004c5c19ae2dc5523c96cecb3c24168deddcf4 +// Checksum: 247a61eb8ef7396fdf0f6082e113cf49e2706e3c // // ignore_for_file: unused_import @@ -550,62 +550,54 @@ final class _EvaluateVisitor var args = arguments[1] as SassArgumentList; var callableNode = _callableNode!; - var invocation = ArgumentInvocation([], {}, callableNode.span, - rest: ValueExpression(args, callableNode.span), - keywordRest: args.keywords.isEmpty - ? null - : ValueExpression( - SassMap({ - for (var (name, value) in args.keywords.pairs) - SassString(name, quotes: false): value - }), - callableNode.span)); + var invocation = ArgumentInvocation( + [], + {}, + callableNode.span, + rest: ValueExpression(args, callableNode.span), + ); var callable = mixin.assertMixin("mixin").callable; - // ignore: unnecessary_type_check - if (callable is Callable) { - switch (callable) { - case BuiltInCallable() when _environment.content != null: - throw _exception("Mixin doesn't accept a content block."); + var content = _environment.content; - case BuiltInCallable(): + switch (callable) { + case BuiltInCallable(acceptsContent: false) when content != null: + throw _exception( + "Mixin doesn't accept a content block.", callableNode.span); + + case BuiltInCallable(): + _environment.withContent(content, () { _runBuiltInCallable(invocation, callable, callableNode); + }); - case UserDefinedCallable( - declaration: MixinRule(hasContent: false) - ) - when _environment.content != null: - throw MultiSpanSassRuntimeException( - "Mixin doesn't accept a content block.", - callableNode.span, - "invocation", - {callable.declaration.arguments.spanWithName: "declaration"}, - _stackTrace(callableNode.span)); - - case UserDefinedCallable(): - var contentCallable = _environment.content.andThen((content) => - UserDefinedCallable( - content as CallableDeclaration, _environment.closure(), - inDependency: _inDependency)); - _runUserDefinedCallable(invocation, callable, callableNode, () { - _environment.withContent(contentCallable, () { - _environment.asMixin(() { - for (var statement in callable.declaration.children) { - _addErrorSpan(callableNode, () => statement.accept(this)); - } - }); + case UserDefinedCallable( + declaration: MixinRule(hasContent: false) + ) + when content != null: + throw MultiSpanSassRuntimeException( + "Mixin doesn't accept a content block.", + callableNode.span, + "invocation", + {callable.declaration.arguments.spanWithName: "declaration"}, + _stackTrace(callableNode.span)); + + case UserDefinedCallable(): + _runUserDefinedCallable(invocation, callable, callableNode, () { + _environment.withContent(content, () { + _environment.asMixin(() { + for (var statement in callable.declaration.children) { + _addErrorSpan(callableNode, () => statement.accept(this)); + } }); }); + }); - case _: - throw UnsupportedError("Unknown callable type $mixin."); - } - } else { - throw SassScriptException( - "The mixin ${callable.name} is asynchronous.\n" - "This is probably caused by a bug in a Sass plugin."); + case _: + throw SassScriptException( + "The mixin ${callable.name} is asynchronous.\n" + "This is probably caused by a bug in a Sass plugin."); } - }, url: "sass:meta", hasContent: true), + }, url: "sass:meta", acceptsContent: true), ]; var metaModule = BuiltInModule("meta", @@ -1817,15 +1809,23 @@ final class _EvaluateVisitor var nodeWithSpan = AstNode.fake(() => node.spanWithoutContent); var mixin = _addExceptionSpan(node, () => _environment.getMixin(node.name, namespace: node.namespace)); + var contentCallable = node.content.andThen((content) => UserDefinedCallable( + content, _environment.closure(), + inDependency: _inDependency)); + switch (mixin) { case null: throw _exception("Undefined mixin.", node.span); - case BuiltInCallable() when node.content != null: + case BuiltInCallable(acceptsContent: false) when node.content != null: throw _exception("Mixin doesn't accept a content block.", node.span); case BuiltInCallable(): - _runBuiltInCallable(node.arguments, mixin, nodeWithSpan); + _environment.withContent(contentCallable, () { + _environment.asMixin(() { + _runBuiltInCallable(node.arguments, mixin, nodeWithSpan); + }); + }); case UserDefinedCallable( declaration: MixinRule(hasContent: false) @@ -1839,9 +1839,6 @@ final class _EvaluateVisitor _stackTrace(node.spanWithoutContent)); case UserDefinedCallable(): - var contentCallable = node.content.andThen((content) => - UserDefinedCallable(content, _environment.closure(), - inDependency: _inDependency)); _runUserDefinedCallable(node.arguments, mixin, nodeWithSpan, () { _environment.withContent(contentCallable, () { _environment.asMixin(() { From 4093914ed5fa1f0e8c8f4123ecd4bdf95110d371 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Fri, 8 Sep 2023 13:13:17 +0000 Subject: [PATCH 13/27] correctly pass through acceptsContent value --- lib/src/callable/built_in.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/callable/built_in.dart b/lib/src/callable/built_in.dart index f59b7cf10..a52662fa0 100644 --- a/lib/src/callable/built_in.dart +++ b/lib/src/callable/built_in.dart @@ -56,7 +56,7 @@ final class BuiltInCallable implements Callable, AsyncBuiltInCallable { (arguments) { callback(arguments); return sassNull; - }); + }, acceptsContent: acceptsContent); /// Creates a callable with a single [arguments] declaration and a single /// [callback]. From eb33c561d7fc9a5f07b9e34718075d60d3c4db30 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Thu, 14 Sep 2023 17:48:08 +0000 Subject: [PATCH 14/27] pr review --- lib/src/callable/async_built_in.dart | 4 +- lib/src/functions/meta.dart | 4 +- lib/src/value/mixin.dart | 3 + lib/src/visitor/async_evaluate.dart | 104 ++++++++++++--------------- lib/src/visitor/evaluate.dart | 103 ++++++++++++-------------- 5 files changed, 102 insertions(+), 116 deletions(-) diff --git a/lib/src/callable/async_built_in.dart b/lib/src/callable/async_built_in.dart index c758dd560..7dba7e1cd 100644 --- a/lib/src/callable/async_built_in.dart +++ b/lib/src/callable/async_built_in.dart @@ -26,7 +26,9 @@ class AsyncBuiltInCallable implements AsyncCallable { /// The callback to run when executing this callable. final Callback _callback; - /// Whether this callback could potentially accept an `@content` block. + /// Whether this callable could potentially accept an `@content` block. + /// + /// This can only be true for mixins. final bool acceptsContent; /// Creates a function with a single [arguments] declaration and a single diff --git a/lib/src/functions/meta.dart b/lib/src/functions/meta.dart index 49ed8da3f..a3b683b9a 100644 --- a/lib/src/functions/meta.dart +++ b/lib/src/functions/meta.dart @@ -83,7 +83,9 @@ final local = UnmodifiableListView([ _function("accepts-content", r"$mixin", (arguments) { var mixin = arguments[0].assertMixin("mixin"); return SassBoolean(switch (mixin.callable) { - AsyncBuiltInCallable() || BuiltInCallable() => false, + AsyncBuiltInCallable(acceptsContent: var acceptsContent) || + BuiltInCallable(acceptsContent: var acceptsContent) => + acceptsContent, UserDefinedCallable(declaration: MixinRule(hasContent: var hasContent)) => hasContent, _ => throw UnsupportedError("Unknown callable type $mixin.") diff --git a/lib/src/value/mixin.dart b/lib/src/value/mixin.dart index f3b77a870..95716220c 100644 --- a/lib/src/value/mixin.dart +++ b/lib/src/value/mixin.dart @@ -21,6 +21,9 @@ class SassMixin extends Value { /// Note that this is typed as an [AsyncCallable] so that it will work with /// both synchronous and asynchronous evaluate visitors, but in practice the /// synchronous evaluate visitor will crash if this isn't a [Callable]. + /// + /// @nodoc + @internal final AsyncCallable callable; SassMixin(this.callable); diff --git a/lib/src/visitor/async_evaluate.dart b/lib/src/visitor/async_evaluate.dart index 806f2aa6f..685719b52 100644 --- a/lib/src/visitor/async_evaluate.dart +++ b/lib/src/visitor/async_evaluate.dart @@ -546,8 +546,8 @@ final class _EvaluateVisitor var callableNode = _callableNode!; var invocation = ArgumentInvocation( - [], - {}, + const [], + const {}, callableNode.span, rest: ValueExpression(args, callableNode.span), ); @@ -555,44 +555,14 @@ final class _EvaluateVisitor var callable = mixin.assertMixin("mixin").callable; var content = _environment.content; - switch (callable) { - case AsyncBuiltInCallable(acceptsContent: false) when content != null: - throw _exception( - "Mixin doesn't accept a content block.", callableNode.span); - - case AsyncBuiltInCallable(): - await _environment.withContent(content, () async { - await _runBuiltInCallable(invocation, callable, callableNode); - }); - - case UserDefinedCallable( - declaration: MixinRule(hasContent: false) - ) - when content != null: - throw MultiSpanSassRuntimeException( - "Mixin doesn't accept a content block.", - callableNode.span, - "invocation", - {callable.declaration.arguments.spanWithName: "declaration"}, - _stackTrace(callableNode.span)); - - case UserDefinedCallable(): - _runUserDefinedCallable(invocation, callable, callableNode, - () async { - await _environment.withContent(content, () async { - _environment.asMixin(() async { - for (var statement in callable.declaration.children) { - await _addErrorSpan( - callableNode, () => statement.accept(this)); - } - }); - }); - }); - - case _: - throw SassScriptException( - "The mixin ${callable.name} is asynchronous.\n" - "This is probably caused by a bug in a Sass plugin."); + // ignore: unnecessary_type_check + if (callable is AsyncCallable) { + _applyMixin(callable, content, invocation, callableNode.span, + callableNode.span); + } else { + throw SassScriptException( + "The mixin ${callable.name} is asynchronous.\n" + "This is probably caused by a bug in a Sass plugin."); } }, url: "sass:meta", acceptsContent: true), ]; @@ -1810,43 +1780,50 @@ final class _EvaluateVisitor } } - Future visitIncludeRule(IncludeRule node) async { - var nodeWithSpan = AstNode.fake(() => node.spanWithoutContent); - var mixin = _addExceptionSpan(node, - () => _environment.getMixin(node.name, namespace: node.namespace)); - var contentCallable = node.content.andThen((content) => UserDefinedCallable( - content, _environment.closure(), - inDependency: _inDependency)); - + Future _applyMixin( + AsyncCallable? mixin, + UserDefinedCallable? contentCallable, + ArgumentInvocation arguments, + FileSpan span, + FileSpan spanWithoutContent) async { + var nodeWithSpan = AstNode.fake(() => spanWithoutContent); switch (mixin) { case null: - throw _exception("Undefined mixin.", node.span); + throw _exception("Undefined mixin.", span); case AsyncBuiltInCallable(acceptsContent: false) - when node.content != null: - throw _exception("Mixin doesn't accept a content block.", node.span); - + when contentCallable != null: + { + var evaluated = await _evaluateArguments(arguments); + var (overload, _) = mixin.callbackFor( + evaluated.positional.length, MapKeySet(evaluated.named)); + throw MultiSpanSassRuntimeException( + "Mixin doesn't accept a content block.", + spanWithoutContent, + "invocation", + {overload.spanWithName: "declaration"}, + _stackTrace(spanWithoutContent)); + } case AsyncBuiltInCallable(): await _environment.withContent(contentCallable, () async { await _environment.asMixin(() async { - await _runBuiltInCallable(node.arguments, mixin, nodeWithSpan); + await _runBuiltInCallable(arguments, mixin, nodeWithSpan); }); }); case UserDefinedCallable( declaration: MixinRule(hasContent: false) ) - when node.content != null: + when contentCallable != null: throw MultiSpanSassRuntimeException( "Mixin doesn't accept a content block.", - node.spanWithoutContent, + spanWithoutContent, "invocation", {mixin.declaration.arguments.spanWithName: "declaration"}, - _stackTrace(node.spanWithoutContent)); + _stackTrace(spanWithoutContent)); case UserDefinedCallable(): - await _runUserDefinedCallable(node.arguments, mixin, nodeWithSpan, - () async { + await _runUserDefinedCallable(arguments, mixin, nodeWithSpan, () async { await _environment.withContent(contentCallable, () async { await _environment.asMixin(() async { for (var statement in mixin.declaration.children) { @@ -1859,6 +1836,17 @@ final class _EvaluateVisitor case _: throw UnsupportedError("Unknown callable type $mixin."); } + } + + Future visitIncludeRule(IncludeRule node) async { + var mixin = _addExceptionSpan(node, + () => _environment.getMixin(node.name, namespace: node.namespace)); + var contentCallable = node.content.andThen((content) => UserDefinedCallable( + content, _environment.closure(), + inDependency: _inDependency)); + + _applyMixin(mixin, contentCallable, node.arguments, node.span, + node.spanWithoutContent); return null; } diff --git a/lib/src/visitor/evaluate.dart b/lib/src/visitor/evaluate.dart index 138312799..1cdb8f5b0 100644 --- a/lib/src/visitor/evaluate.dart +++ b/lib/src/visitor/evaluate.dart @@ -5,7 +5,7 @@ // DO NOT EDIT. This file was generated from async_evaluate.dart. // See tool/grind/synchronize.dart for details. // -// Checksum: 247a61eb8ef7396fdf0f6082e113cf49e2706e3c +// Checksum: a195d5b1947bfcb7a76679a8d77c3a6fb040adf8 // // ignore_for_file: unused_import @@ -551,8 +551,8 @@ final class _EvaluateVisitor var callableNode = _callableNode!; var invocation = ArgumentInvocation( - [], - {}, + const [], + const {}, callableNode.span, rest: ValueExpression(args, callableNode.span), ); @@ -560,42 +560,14 @@ final class _EvaluateVisitor var callable = mixin.assertMixin("mixin").callable; var content = _environment.content; - switch (callable) { - case BuiltInCallable(acceptsContent: false) when content != null: - throw _exception( - "Mixin doesn't accept a content block.", callableNode.span); - - case BuiltInCallable(): - _environment.withContent(content, () { - _runBuiltInCallable(invocation, callable, callableNode); - }); - - case UserDefinedCallable( - declaration: MixinRule(hasContent: false) - ) - when content != null: - throw MultiSpanSassRuntimeException( - "Mixin doesn't accept a content block.", - callableNode.span, - "invocation", - {callable.declaration.arguments.spanWithName: "declaration"}, - _stackTrace(callableNode.span)); - - case UserDefinedCallable(): - _runUserDefinedCallable(invocation, callable, callableNode, () { - _environment.withContent(content, () { - _environment.asMixin(() { - for (var statement in callable.declaration.children) { - _addErrorSpan(callableNode, () => statement.accept(this)); - } - }); - }); - }); - - case _: - throw SassScriptException( - "The mixin ${callable.name} is asynchronous.\n" - "This is probably caused by a bug in a Sass plugin."); + // ignore: unnecessary_type_check + if (callable is Callable) { + _applyMixin(callable, content, invocation, callableNode.span, + callableNode.span); + } else { + throw SassScriptException( + "The mixin ${callable.name} is asynchronous.\n" + "This is probably caused by a bug in a Sass plugin."); } }, url: "sass:meta", acceptsContent: true), ]; @@ -1805,41 +1777,49 @@ final class _EvaluateVisitor } } - Value? visitIncludeRule(IncludeRule node) { - var nodeWithSpan = AstNode.fake(() => node.spanWithoutContent); - var mixin = _addExceptionSpan(node, - () => _environment.getMixin(node.name, namespace: node.namespace)); - var contentCallable = node.content.andThen((content) => UserDefinedCallable( - content, _environment.closure(), - inDependency: _inDependency)); - + void _applyMixin( + Callable? mixin, + UserDefinedCallable? contentCallable, + ArgumentInvocation arguments, + FileSpan span, + FileSpan spanWithoutContent) { + var nodeWithSpan = AstNode.fake(() => spanWithoutContent); switch (mixin) { case null: - throw _exception("Undefined mixin.", node.span); - - case BuiltInCallable(acceptsContent: false) when node.content != null: - throw _exception("Mixin doesn't accept a content block.", node.span); + throw _exception("Undefined mixin.", span); + case BuiltInCallable(acceptsContent: false) when contentCallable != null: + { + var evaluated = _evaluateArguments(arguments); + var (overload, _) = mixin.callbackFor( + evaluated.positional.length, MapKeySet(evaluated.named)); + throw MultiSpanSassRuntimeException( + "Mixin doesn't accept a content block.", + spanWithoutContent, + "invocation", + {overload.spanWithName: "declaration"}, + _stackTrace(spanWithoutContent)); + } case BuiltInCallable(): _environment.withContent(contentCallable, () { _environment.asMixin(() { - _runBuiltInCallable(node.arguments, mixin, nodeWithSpan); + _runBuiltInCallable(arguments, mixin, nodeWithSpan); }); }); case UserDefinedCallable( declaration: MixinRule(hasContent: false) ) - when node.content != null: + when contentCallable != null: throw MultiSpanSassRuntimeException( "Mixin doesn't accept a content block.", - node.spanWithoutContent, + spanWithoutContent, "invocation", {mixin.declaration.arguments.spanWithName: "declaration"}, - _stackTrace(node.spanWithoutContent)); + _stackTrace(spanWithoutContent)); case UserDefinedCallable(): - _runUserDefinedCallable(node.arguments, mixin, nodeWithSpan, () { + _runUserDefinedCallable(arguments, mixin, nodeWithSpan, () { _environment.withContent(contentCallable, () { _environment.asMixin(() { for (var statement in mixin.declaration.children) { @@ -1852,6 +1832,17 @@ final class _EvaluateVisitor case _: throw UnsupportedError("Unknown callable type $mixin."); } + } + + Value? visitIncludeRule(IncludeRule node) { + var mixin = _addExceptionSpan(node, + () => _environment.getMixin(node.name, namespace: node.namespace)); + var contentCallable = node.content.andThen((content) => UserDefinedCallable( + content, _environment.closure(), + inDependency: _inDependency)); + + _applyMixin(mixin, contentCallable, node.arguments, node.span, + node.spanWithoutContent); return null; } From eeb14ee5fa2dc3bd5b533ab4cbeb17e5ad7543e9 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Thu, 14 Sep 2023 17:59:12 +0000 Subject: [PATCH 15/27] refactor embedded registry --- lib/src/embedded/dispatcher.dart | 9 ++++--- lib/src/embedded/function_registry.dart | 33 ------------------------- lib/src/embedded/host_callable.dart | 12 ++++++--- lib/src/embedded/mixin_registry.dart | 33 ------------------------- lib/src/embedded/opaque_registry.dart | 30 ++++++++++++++++++++++ lib/src/embedded/protofier.dart | 12 ++++----- 6 files changed, 49 insertions(+), 80 deletions(-) delete mode 100644 lib/src/embedded/function_registry.dart delete mode 100644 lib/src/embedded/mixin_registry.dart create mode 100644 lib/src/embedded/opaque_registry.dart diff --git a/lib/src/embedded/dispatcher.dart b/lib/src/embedded/dispatcher.dart index 3174ebc1c..39d46e2ea 100644 --- a/lib/src/embedded/dispatcher.dart +++ b/lib/src/embedded/dispatcher.dart @@ -12,13 +12,14 @@ import 'package:path/path.dart' as p; import 'package:protobuf/protobuf.dart'; import 'package:sass/sass.dart' as sass; +import '../value/function.dart'; +import '../value/mixin.dart'; import 'embedded_sass.pb.dart'; -import 'function_registry.dart'; +import 'opaque_registry.dart'; import 'host_callable.dart'; import 'importer/file.dart'; import 'importer/host.dart'; import 'logger.dart'; -import 'mixin_registry.dart'; import 'util/proto_extensions.dart'; import 'utils.dart'; @@ -110,8 +111,8 @@ final class Dispatcher { OutboundMessage_CompileResponse _compile( InboundMessage_CompileRequest request) { - var functions = FunctionRegistry(); - var mixins = MixinRegistry(); + var functions = OpaqueRegistry(); + var mixins = OpaqueRegistry(); var style = request.style == OutputStyle.COMPRESSED ? sass.OutputStyle.compressed diff --git a/lib/src/embedded/function_registry.dart b/lib/src/embedded/function_registry.dart deleted file mode 100644 index b288fbd8d..000000000 --- a/lib/src/embedded/function_registry.dart +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2019 Google Inc. Use of this source code is governed by an -// MIT-style license that can be found in the LICENSE file or at -// https://opensource.org/licenses/MIT. - -import '../value/function.dart'; -import 'embedded_sass.pb.dart'; - -/// A registry of [SassFunction]s indexed by ID so that the host can invoke -/// them. -final class FunctionRegistry { - /// First-class functions that have been sent to the host. - /// - /// The functions are located at indexes in the list matching their IDs. - final _functionsById = []; - - /// A reverse map from functions to their indexes in [_functionsById]. - final _idsByFunction = {}; - - /// Converts [function] to a protocol buffer to send to the host. - Value_CompilerFunction protofy(SassFunction function) { - var id = _idsByFunction.putIfAbsent(function, () { - _functionsById.add(function); - return _functionsById.length - 1; - }); - - return Value_CompilerFunction()..id = id; - } - - /// Returns the compiler-side function associated with [id]. - /// - /// If no such function exists, returns `null`. - SassFunction? operator [](int id) => _functionsById[id]; -} diff --git a/lib/src/embedded/host_callable.dart b/lib/src/embedded/host_callable.dart index 5c3719a47..3518b57e2 100644 --- a/lib/src/embedded/host_callable.dart +++ b/lib/src/embedded/host_callable.dart @@ -2,12 +2,13 @@ // MIT-style license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -import '../../src/embedded/mixin_registry.dart'; import '../callable.dart'; import '../exception.dart'; +import '../value/function.dart'; +import '../value/mixin.dart'; import 'dispatcher.dart'; import 'embedded_sass.pb.dart'; -import 'function_registry.dart'; +import 'opaque_registry.dart'; import 'protofier.dart'; import 'utils.dart'; @@ -19,8 +20,11 @@ import 'utils.dart'; /// the name defined in the [signature]. /// /// Throws a [SassException] if [signature] is invalid. -Callable hostCallable(Dispatcher dispatcher, FunctionRegistry functions, - MixinRegistry mixins, String signature, +Callable hostCallable( + Dispatcher dispatcher, + OpaqueRegistry functions, + OpaqueRegistry mixins, + String signature, {int? id}) { late Callable callable; callable = Callable.fromSignature(signature, (arguments) { diff --git a/lib/src/embedded/mixin_registry.dart b/lib/src/embedded/mixin_registry.dart deleted file mode 100644 index 54cdb2c86..000000000 --- a/lib/src/embedded/mixin_registry.dart +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2019 Google Inc. Use of this source code is governed by an -// MIT-style license that can be found in the LICENSE file or at -// https://opensource.org/licenses/MIT. - -import '../value/mixin.dart'; -import 'embedded_sass.pb.dart'; - -/// A registry of [SassMixin]s indexed by ID so that the host can invoke -/// them. -final class MixinRegistry { - /// First-class mixins that have been sent to the host. - /// - /// The mixins are located at indexes in the list matching their IDs. - final _mixinsById = []; - - /// A reverse map from mixins to their indexes in [_mixinsById]. - final _idsByMixin = {}; - - /// Converts [mixin] to a protocol buffer to send to the host. - Value_CompilerMixin protofy(SassMixin mixin) { - var id = _idsByMixin.putIfAbsent(mixin, () { - _mixinsById.add(mixin); - return _mixinsById.length - 1; - }); - - return Value_CompilerMixin()..id = id; - } - - /// Returns the compiler-side mixin associated with [id]. - /// - /// If no such mixin exists, returns `null`. - SassMixin? operator [](int id) => _mixinsById[id]; -} diff --git a/lib/src/embedded/opaque_registry.dart b/lib/src/embedded/opaque_registry.dart new file mode 100644 index 000000000..70c0fbe24 --- /dev/null +++ b/lib/src/embedded/opaque_registry.dart @@ -0,0 +1,30 @@ +// Copyright 2019 Google Inc. Use of this source code is governed by an +// MIT-style license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +/// A registry of some `T` indexed by ID so that the host can invoke +/// them. +final class OpaqueRegistry { + /// Instantiations of `T` that have been sent to the host. + /// + /// The values are located at indexes in the list matching their IDs. + final _elementsById = []; + + /// A reverse map from elements to their indexes in [_elementsById]. + final _idsByElement = {}; + + /// Converts an element of type `T` to a protocol buffer to send to the host. + int protofy(T element) { + var id = _idsByElement.putIfAbsent(element, () { + _elementsById.add(element); + return _elementsById.length - 1; + }); + + return id; + } + + /// Returns the compiler-side element associated with [id]. + /// + /// If no such element exists, returns `null`. + T? operator [](int id) => _elementsById[id]; +} diff --git a/lib/src/embedded/protofier.dart b/lib/src/embedded/protofier.dart index 80afaaf42..9c13828c4 100644 --- a/lib/src/embedded/protofier.dart +++ b/lib/src/embedded/protofier.dart @@ -8,9 +8,8 @@ import '../value.dart'; import 'dispatcher.dart'; import 'embedded_sass.pb.dart' as proto; import 'embedded_sass.pb.dart' hide Value, ListSeparator, CalculationOperator; -import 'function_registry.dart'; -import 'mixin_registry.dart'; import 'host_callable.dart'; +import 'opaque_registry.dart'; import 'utils.dart'; /// A class that converts Sass [Value] objects into [Value] protobufs. @@ -22,10 +21,10 @@ final class Protofier { final Dispatcher _dispatcher; /// The IDs of first-class functions. - final FunctionRegistry _functions; + final OpaqueRegistry _functions; /// The IDs of first-class mixins. - final MixinRegistry _mixins; + final OpaqueRegistry _mixins; /// Any argument lists transitively contained in [value]. /// @@ -91,9 +90,10 @@ final class Protofier { case SassCalculation(): result.calculation = _protofyCalculation(value); case SassFunction(): - result.compilerFunction = _functions.protofy(value); + result.compilerFunction = + Value_CompilerFunction(id: _functions.protofy(value)); case SassMixin(): - result.compilerMixin = _mixins.protofy(value); + result.compilerMixin = Value_CompilerMixin(id: _mixins.protofy(value)); case sassTrue: result.singleton = SingletonValue.TRUE; case sassFalse: From faba24a4d3cd156f729bb555f669ba91ce2d0616 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Tue, 19 Sep 2023 20:22:05 +0000 Subject: [PATCH 16/27] await _applyMixin --- lib/src/visitor/async_evaluate.dart | 4 ++-- lib/src/visitor/evaluate.dart | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/src/visitor/async_evaluate.dart b/lib/src/visitor/async_evaluate.dart index 164674d37..873b92fa7 100644 --- a/lib/src/visitor/async_evaluate.dart +++ b/lib/src/visitor/async_evaluate.dart @@ -562,7 +562,7 @@ final class _EvaluateVisitor // ignore: unnecessary_type_check if (callable is AsyncCallable) { - _applyMixin(callable, content, invocation, callableNode.span, + await _applyMixin(callable, content, invocation, callableNode.span, callableNode.span); } else { throw SassScriptException( @@ -1850,7 +1850,7 @@ final class _EvaluateVisitor content, _environment.closure(), inDependency: _inDependency)); - _applyMixin(mixin, contentCallable, node.arguments, node.span, + await _applyMixin(mixin, contentCallable, node.arguments, node.span, node.spanWithoutContent); return null; diff --git a/lib/src/visitor/evaluate.dart b/lib/src/visitor/evaluate.dart index 03b7be140..b13130746 100644 --- a/lib/src/visitor/evaluate.dart +++ b/lib/src/visitor/evaluate.dart @@ -5,7 +5,7 @@ // DO NOT EDIT. This file was generated from async_evaluate.dart. // See tool/grind/synchronize.dart for details. // -// Checksum: fe0ef48c51d8d4beca77070cd1003ebbdaa3b4ee +// Checksum: 61f8aeb4df0e1a3e236d824dbdd934f325978090 // // ignore_for_file: unused_import From 89ef731277e5ceb229906d587d82ffb7e2d2a2e8 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Thu, 21 Sep 2023 22:39:04 +0000 Subject: [PATCH 17/27] add doc comment to _applyMixin --- lib/src/visitor/async_evaluate.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/src/visitor/async_evaluate.dart b/lib/src/visitor/async_evaluate.dart index 873b92fa7..e21a3a2e4 100644 --- a/lib/src/visitor/async_evaluate.dart +++ b/lib/src/visitor/async_evaluate.dart @@ -1785,6 +1785,7 @@ final class _EvaluateVisitor } } + /// Evaluate a given [mixin] with [arguments] and [contentCallable] Future _applyMixin( AsyncCallable? mixin, UserDefinedCallable? contentCallable, From 93b812abc52b2610b7cbaa14e92742793221a53b Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Fri, 22 Sep 2023 04:21:35 +0000 Subject: [PATCH 18/27] run synchronize --- lib/src/visitor/evaluate.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/src/visitor/evaluate.dart b/lib/src/visitor/evaluate.dart index b13130746..83563cb5a 100644 --- a/lib/src/visitor/evaluate.dart +++ b/lib/src/visitor/evaluate.dart @@ -5,7 +5,7 @@ // DO NOT EDIT. This file was generated from async_evaluate.dart. // See tool/grind/synchronize.dart for details. // -// Checksum: 61f8aeb4df0e1a3e236d824dbdd934f325978090 +// Checksum: 0985a8b7386d8784bf7cb08a0337b9b81deb5bbe // // ignore_for_file: unused_import @@ -1782,6 +1782,7 @@ final class _EvaluateVisitor } } + /// Evaluate a given [mixin] with [arguments] and [contentCallable] void _applyMixin( Callable? mixin, UserDefinedCallable? contentCallable, From 6e85d50bc5b5e335b22312c6d92e637d3113c951 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Mon, 25 Sep 2023 16:23:45 +0000 Subject: [PATCH 19/27] bump ci last CI run failed due to being out of sync with spec tests. now that spec tests are fixed, re-run ci without changing any code From 80fd8bb52070f384ed1b0080d5e153432468bd38 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Thu, 28 Sep 2023 13:48:50 -0700 Subject: [PATCH 20/27] Update pubspec and changelog --- CHANGELOG.md | 13 ++++++++++++- pubspec.yaml | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 84aba9723..21c739e63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,15 @@ -## 1.68.1 +## 1.69.0 + +* Add a `meta.get-mixin()` function that returns a mixin as a first-class Sass + value. + +* Add a `meta.apply()` mixin that includes a mixin value. + +* Add a `meta.module-mixins()` function which returns a map from mixin names in + a module to the first-class mixins that belong to those names. + +* Add a `meta.accepts-content()` function which returns whether or not a mixin + value can take a content block. ### Dart API diff --git a/pubspec.yaml b/pubspec.yaml index 420ca9d5f..b518e7566 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: sass -version: 1.68.1-dev +version: 1.69.0-dev description: A Sass implementation in Dart. homepage: /~https://github.com/sass/dart-sass From 3144e02af73c89f68561bc32cf889b1af1a178d6 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Sat, 30 Sep 2023 16:12:08 +0000 Subject: [PATCH 21/27] nits --- lib/src/embedded/opaque_registry.dart | 4 ++-- lib/src/embedded/protofier.dart | 4 ++-- lib/src/value/mixin.dart | 3 +-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/src/embedded/opaque_registry.dart b/lib/src/embedded/opaque_registry.dart index 70c0fbe24..bf9adaab2 100644 --- a/lib/src/embedded/opaque_registry.dart +++ b/lib/src/embedded/opaque_registry.dart @@ -13,8 +13,8 @@ final class OpaqueRegistry { /// A reverse map from elements to their indexes in [_elementsById]. final _idsByElement = {}; - /// Converts an element of type `T` to a protocol buffer to send to the host. - int protofy(T element) { + /// Returns the compiler-side id associated with [element]. + int getId(T element) { var id = _idsByElement.putIfAbsent(element, () { _elementsById.add(element); return _elementsById.length - 1; diff --git a/lib/src/embedded/protofier.dart b/lib/src/embedded/protofier.dart index f4c09c5df..eec7e3d05 100644 --- a/lib/src/embedded/protofier.dart +++ b/lib/src/embedded/protofier.dart @@ -91,9 +91,9 @@ final class Protofier { result.calculation = _protofyCalculation(value); case SassFunction(): result.compilerFunction = - Value_CompilerFunction(id: _functions.protofy(value)); + Value_CompilerFunction(id: _functions.getId(value)); case SassMixin(): - result.compilerMixin = Value_CompilerMixin(id: _mixins.protofy(value)); + result.compilerMixin = Value_CompilerMixin(id: _mixins.getId(value)); case sassTrue: result.singleton = SingletonValue.TRUE; case sassFalse: diff --git a/lib/src/value/mixin.dart b/lib/src/value/mixin.dart index 95716220c..79091579d 100644 --- a/lib/src/value/mixin.dart +++ b/lib/src/value/mixin.dart @@ -14,8 +14,7 @@ import '../value.dart'; /// it may be passed between modules. /// /// {@category Value} -@sealed -class SassMixin extends Value { +final class SassMixin extends Value { /// The callable that this mixin invokes. /// /// Note that this is typed as an [AsyncCallable] so that it will work with From f1c1d5af1cc1408b80fc240c3ba42077029d2e88 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Tue, 3 Oct 2023 05:03:37 +0000 Subject: [PATCH 22/27] lazily evaluate file spans --- lib/src/visitor/async_evaluate.dart | 35 ++++++++++++++++------------ lib/src/visitor/evaluate.dart | 36 ++++++++++++++++------------- 2 files changed, 40 insertions(+), 31 deletions(-) diff --git a/lib/src/visitor/async_evaluate.dart b/lib/src/visitor/async_evaluate.dart index 65324f722..389c6991a 100644 --- a/lib/src/visitor/async_evaluate.dart +++ b/lib/src/visitor/async_evaluate.dart @@ -562,8 +562,8 @@ final class _EvaluateVisitor // ignore: unnecessary_type_check if (callable is AsyncCallable) { - await _applyMixin(callable, content, invocation, callableNode.span, - callableNode.span); + await _applyMixin( + callable, content, invocation, callableNode, callableNode); } else { throw SassScriptException( "The mixin ${callable.name} is asynchronous.\n" @@ -1790,12 +1790,11 @@ final class _EvaluateVisitor AsyncCallable? mixin, UserDefinedCallable? contentCallable, ArgumentInvocation arguments, - FileSpan span, - FileSpan spanWithoutContent) async { - var nodeWithSpan = AstNode.fake(() => spanWithoutContent); + AstNode nodeWithSpan, + AstNode nodeWithSpanWithoutContent) async { switch (mixin) { case null: - throw _exception("Undefined mixin.", span); + throw _exception("Undefined mixin.", nodeWithSpan.span); case AsyncBuiltInCallable(acceptsContent: false) when contentCallable != null: @@ -1805,15 +1804,16 @@ final class _EvaluateVisitor evaluated.positional.length, MapKeySet(evaluated.named)); throw MultiSpanSassRuntimeException( "Mixin doesn't accept a content block.", - spanWithoutContent, + nodeWithSpanWithoutContent.span, "invocation", {overload.spanWithName: "declaration"}, - _stackTrace(spanWithoutContent)); + _stackTrace(nodeWithSpanWithoutContent.span)); } case AsyncBuiltInCallable(): await _environment.withContent(contentCallable, () async { await _environment.asMixin(() async { - await _runBuiltInCallable(arguments, mixin, nodeWithSpan); + await _runBuiltInCallable( + arguments, mixin, nodeWithSpanWithoutContent); }); }); @@ -1823,17 +1823,19 @@ final class _EvaluateVisitor when contentCallable != null: throw MultiSpanSassRuntimeException( "Mixin doesn't accept a content block.", - spanWithoutContent, + nodeWithSpanWithoutContent.span, "invocation", {mixin.declaration.arguments.spanWithName: "declaration"}, - _stackTrace(spanWithoutContent)); + _stackTrace(nodeWithSpanWithoutContent.span)); case UserDefinedCallable(): - await _runUserDefinedCallable(arguments, mixin, nodeWithSpan, () async { + await _runUserDefinedCallable( + arguments, mixin, nodeWithSpanWithoutContent, () async { await _environment.withContent(contentCallable, () async { await _environment.asMixin(() async { for (var statement in mixin.declaration.children) { - await _addErrorSpan(nodeWithSpan, () => statement.accept(this)); + await _addErrorSpan( + nodeWithSpanWithoutContent, () => statement.accept(this)); } }); }); @@ -1851,8 +1853,11 @@ final class _EvaluateVisitor content, _environment.closure(), inDependency: _inDependency)); - await _applyMixin(mixin, contentCallable, node.arguments, node.span, - node.spanWithoutContent); + var nodeWithSpanWithoutContent = + AstNode.fake(() => node.spanWithoutContent); + + await _applyMixin(mixin, contentCallable, node.arguments, node, + nodeWithSpanWithoutContent); return null; } diff --git a/lib/src/visitor/evaluate.dart b/lib/src/visitor/evaluate.dart index af2a7a352..0aaecec85 100644 --- a/lib/src/visitor/evaluate.dart +++ b/lib/src/visitor/evaluate.dart @@ -5,7 +5,7 @@ // DO NOT EDIT. This file was generated from async_evaluate.dart. // See tool/grind/synchronize.dart for details. // -// Checksum: 1fb0e5a2af9c147e6a74073444b842cdfca87865 +// Checksum: 358960b72c6e4f48d3e2e9d52be3abbe9e8b5a9f // // ignore_for_file: unused_import @@ -567,8 +567,8 @@ final class _EvaluateVisitor // ignore: unnecessary_type_check if (callable is Callable) { - _applyMixin(callable, content, invocation, callableNode.span, - callableNode.span); + _applyMixin( + callable, content, invocation, callableNode, callableNode); } else { throw SassScriptException( "The mixin ${callable.name} is asynchronous.\n" @@ -1787,12 +1787,11 @@ final class _EvaluateVisitor Callable? mixin, UserDefinedCallable? contentCallable, ArgumentInvocation arguments, - FileSpan span, - FileSpan spanWithoutContent) { - var nodeWithSpan = AstNode.fake(() => spanWithoutContent); + AstNode nodeWithSpan, + AstNode nodeWithSpanWithoutContent) { switch (mixin) { case null: - throw _exception("Undefined mixin.", span); + throw _exception("Undefined mixin.", nodeWithSpan.span); case BuiltInCallable(acceptsContent: false) when contentCallable != null: { @@ -1801,15 +1800,15 @@ final class _EvaluateVisitor evaluated.positional.length, MapKeySet(evaluated.named)); throw MultiSpanSassRuntimeException( "Mixin doesn't accept a content block.", - spanWithoutContent, + nodeWithSpanWithoutContent.span, "invocation", {overload.spanWithName: "declaration"}, - _stackTrace(spanWithoutContent)); + _stackTrace(nodeWithSpanWithoutContent.span)); } case BuiltInCallable(): _environment.withContent(contentCallable, () { _environment.asMixin(() { - _runBuiltInCallable(arguments, mixin, nodeWithSpan); + _runBuiltInCallable(arguments, mixin, nodeWithSpanWithoutContent); }); }); @@ -1819,17 +1818,19 @@ final class _EvaluateVisitor when contentCallable != null: throw MultiSpanSassRuntimeException( "Mixin doesn't accept a content block.", - spanWithoutContent, + nodeWithSpanWithoutContent.span, "invocation", {mixin.declaration.arguments.spanWithName: "declaration"}, - _stackTrace(spanWithoutContent)); + _stackTrace(nodeWithSpanWithoutContent.span)); case UserDefinedCallable(): - _runUserDefinedCallable(arguments, mixin, nodeWithSpan, () { + _runUserDefinedCallable(arguments, mixin, nodeWithSpanWithoutContent, + () { _environment.withContent(contentCallable, () { _environment.asMixin(() { for (var statement in mixin.declaration.children) { - _addErrorSpan(nodeWithSpan, () => statement.accept(this)); + _addErrorSpan( + nodeWithSpanWithoutContent, () => statement.accept(this)); } }); }); @@ -1847,8 +1848,11 @@ final class _EvaluateVisitor content, _environment.closure(), inDependency: _inDependency)); - _applyMixin(mixin, contentCallable, node.arguments, node.span, - node.spanWithoutContent); + var nodeWithSpanWithoutContent = + AstNode.fake(() => node.spanWithoutContent); + + _applyMixin(mixin, contentCallable, node.arguments, node, + nodeWithSpanWithoutContent); return null; } From 96bbca25c1054b0c33483dfd34295d81b69d9ec8 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Tue, 3 Oct 2023 23:30:29 +0000 Subject: [PATCH 23/27] bump ci From 679e1bdc93cbc407edb315bdbd30ceee65c7930e Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Wed, 4 Oct 2023 00:16:18 +0000 Subject: [PATCH 24/27] bump ci From 75ccdfb4ec4224e18a4eebcb6b3178ce37a45147 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Wed, 4 Oct 2023 00:46:25 +0000 Subject: [PATCH 25/27] take Object self in JS constructor --- lib/src/js/value/mixin.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/js/value/mixin.dart b/lib/src/js/value/mixin.dart index c3bbf59b7..a41b394d2 100644 --- a/lib/src/js/value/mixin.dart +++ b/lib/src/js/value/mixin.dart @@ -11,7 +11,7 @@ import '../utils.dart'; /// The JavaScript `SassMixin` class. final JSClass mixinClass = () { - var jsClass = createJSClass('sass.SassMixin', () { + var jsClass = createJSClass('sass.SassMixin', (Object self) { jsThrow(JsError( 'It is not possible to construct a SassMixin through the JavaScript API')); }); From 6277d5df98a24c5f57f56516d7268f63ccf76e24 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Wed, 4 Oct 2023 20:44:51 +0000 Subject: [PATCH 26/27] bump ci From f1b4ff3f70027b8c00fadccb9e7a2feedad84339 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Thu, 5 Oct 2023 00:18:45 +0000 Subject: [PATCH 27/27] add SassMixin to ESM export --- tool/grind.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/tool/grind.dart b/tool/grind.dart index ce65138df..425730c03 100644 --- a/tool/grind.dart +++ b/tool/grind.dart @@ -61,6 +61,7 @@ void main(List args) { 'SassFunction', 'SassList', 'SassMap', + 'SassMixin', 'SassNumber', 'SassString', 'Value',