Skip to content

Commit 2430ac6

Browse files
committed
Avoid creating zone variables inside _EvaluateVisitor
Instead, we create a single zone-scoped object that's visible for the entire lifespan of the visitor, and which exposes evaluation internals which can be updated as direct field modifications.
1 parent d13a38e commit 2430ac6

File tree

10 files changed

+137
-105
lines changed

10 files changed

+137
-105
lines changed

lib/sass.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export 'src/logger.dart';
2828
export 'src/syntax.dart';
2929
export 'src/value.dart' hide SassApiColor;
3030
export 'src/visitor/serialize.dart' show OutputStyle;
31-
export 'src/warn.dart' show warn;
31+
export 'src/evaluation_context.dart' show warn;
3232

3333
/// Loads the Sass file at [path], compiles it to CSS, and returns a
3434
/// [CompileResult] containing the CSS and additional metadata about the

lib/src/evaluation_context.dart

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright 2021 Google Inc. Use of this source code is governed by an
2+
// MIT-style license that can be found in the LICENSE file or at
3+
// https://opensource.org/licenses/MIT.
4+
5+
import 'dart:async';
6+
7+
import 'package:source_span/source_span.dart';
8+
9+
/// An interface that exposes information about the current Sass evaluation.
10+
///
11+
/// This allows us to expose zone-scoped information without having to create a
12+
/// new zone variable for each piece of information.
13+
abstract class EvaluationContext {
14+
/// The current evaluation context.
15+
///
16+
/// Throws [StateError] if there isn't a Sass stylesheet currently being
17+
/// evaluated.
18+
static EvaluationContext get current {
19+
var context = Zone.current[#_evaluationContext];
20+
if (context is EvaluationContext) return context;
21+
throw StateError("No Sass stylesheet is currently being evaluated.");
22+
}
23+
24+
/// Returns the span for the currently executing callable.
25+
///
26+
/// For normal exception reporting, this should be avoided in favor of
27+
/// throwing [SassScriptException]s. It should only be used when calling APIs
28+
/// that require spans.
29+
///
30+
/// Throws a [StateError] if there isn't a callable being invoked.
31+
FileSpan get currentCallableSpan;
32+
33+
/// Prints a warning message associated with the current `@import` or function
34+
/// call.
35+
///
36+
/// If [deprecation] is `true`, the warning is emitted as a deprecation
37+
/// warning.
38+
void warn(String message, {bool deprecation = false});
39+
}
40+
41+
/// Prints a warning message associated with the current `@import` or function
42+
/// call.
43+
///
44+
/// If [deprecation] is `true`, the warning is emitted as a deprecation warning.
45+
///
46+
/// This may only be called within a custom function or importer callback.
47+
///
48+
/// {@category Compile}
49+
void warn(String message, {bool deprecation = false}) =>
50+
EvaluationContext.current.warn(message, deprecation: deprecation);
51+
52+
/// Runs [callback] with [context] as [EvaluationContext.current].
53+
///
54+
/// This is zone-based, so if [callback] is asynchronous [warn] is set for the
55+
/// duration of that callback.
56+
T withEvaluationContext<T>(EvaluationContext context, T callback()) =>
57+
runZoned(callback, zoneValues: {#_evaluationContext: context});

lib/src/functions.dart

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,9 @@
33
// https://opensource.org/licenses/MIT.
44

55
import 'dart:collection';
6-
import 'dart:async';
76

87
import 'package:collection/collection.dart';
9-
import 'package:source_span/source_span.dart';
108

11-
import 'ast/node.dart';
129
import 'callable.dart';
1310
import 'functions/color.dart' as color;
1411
import 'functions/list.dart' as list;
@@ -49,21 +46,3 @@ final coreModules = UnmodifiableListView([
4946
selector.module,
5047
string.module
5148
]);
52-
53-
/// Returns the span for the currently executing callable.
54-
///
55-
/// For normal exception reporting, this should be avoided in favor of throwing
56-
/// [SassScriptException]s. It should only be used when calling APIs that
57-
/// require spans.
58-
FileSpan get currentCallableSpan {
59-
var node = Zone.current[#_currentCallableNode];
60-
if (node is AstNode) return node.span;
61-
62-
throw StateError("currentCallableSpan may only be called within an "
63-
"active Sass callable.");
64-
}
65-
66-
/// Runs [callback] in a zone with [callableNode]'s span available from
67-
/// [currentCallableSpan].
68-
T withCurrentCallableNode<T>(AstNode callableNode, T callback()) =>
69-
runZoned(callback, zoneValues: {#_currentCallableNode: callableNode});

lib/src/functions/color.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ import 'dart:collection';
77
import 'package:collection/collection.dart';
88

99
import '../callable.dart';
10+
import '../evaluation_context.dart';
1011
import '../exception.dart';
1112
import '../module/built_in.dart';
1213
import '../util/number.dart';
1314
import '../util/nullable.dart';
1415
import '../utils.dart';
1516
import '../value.dart';
16-
import '../warn.dart';
1717

1818
/// A regular expression matching the beginning of a proprietary Microsoft
1919
/// filter declaration.

lib/src/functions/math.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ import 'dart:math' as math;
88
import 'package:collection/collection.dart';
99

1010
import '../callable.dart';
11+
import '../evaluation_context.dart';
1112
import '../exception.dart';
1213
import '../module/built_in.dart';
1314
import '../util/number.dart';
1415
import '../value.dart';
15-
import '../warn.dart';
1616

1717
/// The global definitions of Sass math functions.
1818
final global = UnmodifiableListView([

lib/src/functions/selector.dart

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ import 'package:collection/collection.dart';
88

99
import '../ast/selector.dart';
1010
import '../callable.dart';
11+
import '../evaluation_context.dart';
1112
import '../exception.dart';
1213
import '../extend/extension_store.dart';
13-
import '../functions.dart';
1414
import '../module/built_in.dart';
1515
import '../value.dart';
1616

@@ -88,7 +88,8 @@ final _extend =
8888
var target = arguments[1].assertSelector(name: "extendee");
8989
var source = arguments[2].assertSelector(name: "extender");
9090

91-
return ExtensionStore.extend(selector, source, target, currentCallableSpan)
91+
return ExtensionStore.extend(selector, source, target,
92+
EvaluationContext.current.currentCallableSpan)
9293
.asSassList;
9394
});
9495

@@ -98,7 +99,8 @@ final _replace =
9899
var target = arguments[1].assertSelector(name: "original");
99100
var source = arguments[2].assertSelector(name: "replacement");
100101

101-
return ExtensionStore.replace(selector, source, target, currentCallableSpan)
102+
return ExtensionStore.replace(selector, source, target,
103+
EvaluationContext.current.currentCallableSpan)
102104
.asSassList;
103105
});
104106

lib/src/visitor/async_evaluate.dart

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import '../callable.dart';
2323
import '../color_names.dart';
2424
import '../configuration.dart';
2525
import '../configured_value.dart';
26+
import '../evaluation_context.dart';
2627
import '../exception.dart';
2728
import '../extend/extension_store.dart';
2829
import '../extend/extension.dart';
@@ -39,7 +40,6 @@ import '../syntax.dart';
3940
import '../utils.dart';
4041
import '../util/nullable.dart';
4142
import '../value.dart';
42-
import '../warn.dart';
4343
import 'interface/css.dart';
4444
import 'interface/expression.dart';
4545
import 'interface/modifiable_css.dart';
@@ -498,7 +498,7 @@ class _EvaluateVisitor
498498
}
499499

500500
Future<EvaluateResult> run(AsyncImporter? importer, Stylesheet node) async {
501-
return _withWarnCallback(node, () async {
501+
return withEvaluationContext(_EvaluationContext(this, node), () async {
502502
var url = node.span.sourceUrl;
503503
if (url != null) {
504504
_activeModules[url] = null;
@@ -512,29 +512,17 @@ class _EvaluateVisitor
512512
}
513513

514514
Future<Value> runExpression(AsyncImporter? importer, Expression expression) =>
515-
_withWarnCallback(
516-
expression,
515+
withEvaluationContext(
516+
_EvaluationContext(this, expression),
517517
() => _withFakeStylesheet(
518518
importer, expression, () => expression.accept(this)));
519519

520520
Future<void> runStatement(AsyncImporter? importer, Statement statement) =>
521-
_withWarnCallback(
522-
statement,
521+
withEvaluationContext(
522+
_EvaluationContext(this, statement),
523523
() => _withFakeStylesheet(
524524
importer, statement, () => statement.accept(this)));
525525

526-
/// Runs [callback] with a definition for the top-level `warn` function.
527-
///
528-
/// If no other span can be found to report a warning, falls back on
529-
/// [nodeWithSpan]'s.
530-
T _withWarnCallback<T>(AstNode nodeWithSpan, T callback()) {
531-
return withWarnCallback(
532-
(message, deprecation) => _warn(
533-
message, _importSpan ?? _callableNode?.span ?? nodeWithSpan.span,
534-
deprecation: deprecation),
535-
callback);
536-
}
537-
538526
/// Asserts that [value] is not `null` and returns it.
539527
///
540528
/// This is used for fields that are set whenever the evaluator is evaluating
@@ -2590,8 +2578,7 @@ class _EvaluateVisitor
25902578

25912579
Value result;
25922580
try {
2593-
result = await withCurrentCallableNode(
2594-
nodeWithSpan, () => callback(evaluated.positional));
2581+
result = await callback(evaluated.positional);
25952582
} on SassRuntimeException {
25962583
rethrow;
25972584
} on MultiSpanSassScriptException catch (error) {
@@ -3462,6 +3449,34 @@ class EvaluateResult {
34623449
EvaluateResult(this.stylesheet, this.loadedUrls);
34633450
}
34643451

3452+
/// An implementation of [EvaluationContext] using the information available in
3453+
/// [_EvaluateVisitor].
3454+
class _EvaluationContext implements EvaluationContext {
3455+
/// The visitor backing this context.
3456+
final _EvaluateVisitor _visitor;
3457+
3458+
/// The AST node whose span should be used for [warn] if no other span is
3459+
/// avaiable.
3460+
final AstNode _defaultWarnNodeWithSpan;
3461+
3462+
_EvaluationContext(this._visitor, this._defaultWarnNodeWithSpan);
3463+
3464+
FileSpan get currentCallableSpan {
3465+
var callableNode = _visitor._callableNode;
3466+
if (callableNode != null) return callableNode.span;
3467+
throw StateError("No Sass callable is currently being evaluated.");
3468+
}
3469+
3470+
void warn(String message, {bool deprecation = false}) {
3471+
_visitor._warn(
3472+
message,
3473+
_visitor._importSpan ??
3474+
_visitor._callableNode?.span ??
3475+
_defaultWarnNodeWithSpan.span,
3476+
deprecation: deprecation);
3477+
}
3478+
}
3479+
34653480
/// The result of evaluating arguments to a function or mixin.
34663481
class _ArgumentResults {
34673482
/// Arguments passed by position.

lib/src/visitor/evaluate.dart

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// DO NOT EDIT. This file was generated from async_evaluate.dart.
66
// See tool/grind/synchronize.dart for details.
77
//
8-
// Checksum: 7a3ae06379ca09dbf3e92d01c1fd974c5b3b9154
8+
// Checksum: 5cdb3467b517bf381d525a1a4bc4f9b6a0eeefad
99
//
1010
// ignore_for_file: unused_import
1111

@@ -32,6 +32,7 @@ import '../callable.dart';
3232
import '../color_names.dart';
3333
import '../configuration.dart';
3434
import '../configured_value.dart';
35+
import '../evaluation_context.dart';
3536
import '../exception.dart';
3637
import '../extend/extension_store.dart';
3738
import '../extend/extension.dart';
@@ -48,7 +49,6 @@ import '../syntax.dart';
4849
import '../utils.dart';
4950
import '../util/nullable.dart';
5051
import '../value.dart';
51-
import '../warn.dart';
5252
import 'interface/css.dart';
5353
import 'interface/expression.dart';
5454
import 'interface/modifiable_css.dart';
@@ -503,7 +503,7 @@ class _EvaluateVisitor
503503
}
504504

505505
EvaluateResult run(Importer? importer, Stylesheet node) {
506-
return _withWarnCallback(node, () {
506+
return withEvaluationContext(_EvaluationContext(this, node), () {
507507
var url = node.span.sourceUrl;
508508
if (url != null) {
509509
_activeModules[url] = null;
@@ -517,29 +517,17 @@ class _EvaluateVisitor
517517
}
518518

519519
Value runExpression(Importer? importer, Expression expression) =>
520-
_withWarnCallback(
521-
expression,
520+
withEvaluationContext(
521+
_EvaluationContext(this, expression),
522522
() => _withFakeStylesheet(
523523
importer, expression, () => expression.accept(this)));
524524

525525
void runStatement(Importer? importer, Statement statement) =>
526-
_withWarnCallback(
527-
statement,
526+
withEvaluationContext(
527+
_EvaluationContext(this, statement),
528528
() => _withFakeStylesheet(
529529
importer, statement, () => statement.accept(this)));
530530

531-
/// Runs [callback] with a definition for the top-level `warn` function.
532-
///
533-
/// If no other span can be found to report a warning, falls back on
534-
/// [nodeWithSpan]'s.
535-
T _withWarnCallback<T>(AstNode nodeWithSpan, T callback()) {
536-
return withWarnCallback(
537-
(message, deprecation) => _warn(
538-
message, _importSpan ?? _callableNode?.span ?? nodeWithSpan.span,
539-
deprecation: deprecation),
540-
callback);
541-
}
542-
543531
/// Asserts that [value] is not `null` and returns it.
544532
///
545533
/// This is used for fields that are set whenever the evaluator is evaluating
@@ -2573,8 +2561,7 @@ class _EvaluateVisitor
25732561

25742562
Value result;
25752563
try {
2576-
result = withCurrentCallableNode(
2577-
nodeWithSpan, () => callback(evaluated.positional));
2564+
result = callback(evaluated.positional);
25782565
} on SassRuntimeException {
25792566
rethrow;
25802567
} on MultiSpanSassScriptException catch (error) {
@@ -3403,6 +3390,34 @@ class _ImportedCssVisitor implements ModifiableCssVisitor<void> {
34033390
_visitor._addChild(node, through: (node) => node is CssStyleRule);
34043391
}
34053392

3393+
/// An implementation of [EvaluationContext] using the information available in
3394+
/// [_EvaluateVisitor].
3395+
class _EvaluationContext implements EvaluationContext {
3396+
/// The visitor backing this context.
3397+
final _EvaluateVisitor _visitor;
3398+
3399+
/// The AST node whose span should be used for [warn] if no other span is
3400+
/// avaiable.
3401+
final AstNode _defaultWarnNodeWithSpan;
3402+
3403+
_EvaluationContext(this._visitor, this._defaultWarnNodeWithSpan);
3404+
3405+
FileSpan get currentCallableSpan {
3406+
var callableNode = _visitor._callableNode;
3407+
if (callableNode != null) return callableNode.span;
3408+
throw StateError("No Sass callable is currently being evaluated.");
3409+
}
3410+
3411+
void warn(String message, {bool deprecation = false}) {
3412+
_visitor._warn(
3413+
message,
3414+
_visitor._importSpan ??
3415+
_visitor._callableNode?.span ??
3416+
_defaultWarnNodeWithSpan.span,
3417+
deprecation: deprecation);
3418+
}
3419+
}
3420+
34063421
/// The result of evaluating arguments to a function or mixin.
34073422
class _ArgumentResults {
34083423
/// Arguments passed by position.

0 commit comments

Comments
 (0)