Skip to content

Commit 9676cc9

Browse files
authored
Merge pull request #945 from sass/fix-bugs
Fix a number of defects related to imported environments
2 parents 7457d2e + 73ddae9 commit 9676cc9

File tree

7 files changed

+91
-35
lines changed

7 files changed

+91
-35
lines changed

CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
## 1.25.1-test.1
1+
## 1.25.2
22

3-
* No user-visible changes.
3+
* Fix a bug where, under extremely rare circumstances, a valid variable could
4+
become unassigned.
45

56
## 1.25.0
67

lib/src/async_environment.dart

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -209,11 +209,11 @@ class AsyncEnvironment {
209209
_mixins.toList(),
210210
_content);
211211

212-
/// Returns a new global environment.
212+
/// Returns a new environment to use for an imported file.
213213
///
214-
/// The returned environment shares this environment's global variables,
215-
/// functions, and mixins, but not its modules.
216-
AsyncEnvironment global() => AsyncEnvironment._(
214+
/// The returned environment shares this environment's variables, functions,
215+
/// and mixins, but not its modules.
216+
AsyncEnvironment forImport() => AsyncEnvironment._(
217217
{},
218218
{},
219219
null,
@@ -419,7 +419,12 @@ class AsyncEnvironment {
419419
}
420420

421421
index = _variableIndex(name);
422-
if (index == null) return _getVariableFromGlobalModule(name);
422+
if (index == null) {
423+
// There isn't a real variable defined as this index, but it will cause
424+
// [getVariable] to short-circuit and get to this function faster next
425+
// time the variable is accessed.
426+
return _getVariableFromGlobalModule(name);
427+
}
423428

424429
_lastVariableName = name;
425430
_lastVariableIndex = index;
@@ -476,12 +481,6 @@ class AsyncEnvironment {
476481
/// required, since some nodes need to do real work to manufacture a source
477482
/// span.
478483
AstNode _getVariableNodeFromGlobalModule(String name) {
479-
// There isn't a real variable defined as this index, but it will cause
480-
// [getVariable] to short-circuit and get to this function faster next time
481-
// the variable is accessed.
482-
_lastVariableName = name;
483-
_lastVariableIndex = 0;
484-
485484
if (_globalModules == null) return null;
486485

487486
// We don't need to worry about multiple modules defining the same variable,
@@ -811,6 +810,20 @@ class AsyncEnvironment {
811810
forwarded: _forwardedModules);
812811
}
813812

813+
/// Returns a module with the same members and upstream modules as [this], but
814+
/// an empty stylesheet and extender.
815+
///
816+
/// This is used when resolving imports, since they need to inject forwarded
817+
/// members into the current scope. It's the only situation in which a nested
818+
/// environment can become a module.
819+
Module toDummyModule() {
820+
return _EnvironmentModule(
821+
this,
822+
CssStylesheet(const [], SourceFile.decoded(const []).span(0)),
823+
Extender.empty,
824+
forwarded: _forwardedModules);
825+
}
826+
814827
/// Returns the module with the given [namespace], or throws a
815828
/// [SassScriptException] if none exists.
816829
Module _getModule(String namespace) {

lib/src/environment.dart

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

@@ -215,11 +215,11 @@ class Environment {
215215
_mixins.toList(),
216216
_content);
217217

218-
/// Returns a new global environment.
218+
/// Returns a new environment to use for an imported file.
219219
///
220-
/// The returned environment shares this environment's global variables,
221-
/// functions, and mixins, but not its modules.
222-
Environment global() => Environment._(
220+
/// The returned environment shares this environment's variables, functions,
221+
/// and mixins, but not its modules.
222+
Environment forImport() => Environment._(
223223
{},
224224
{},
225225
null,
@@ -426,7 +426,12 @@ class Environment {
426426
}
427427

428428
index = _variableIndex(name);
429-
if (index == null) return _getVariableFromGlobalModule(name);
429+
if (index == null) {
430+
// There isn't a real variable defined as this index, but it will cause
431+
// [getVariable] to short-circuit and get to this function faster next
432+
// time the variable is accessed.
433+
return _getVariableFromGlobalModule(name);
434+
}
430435

431436
_lastVariableName = name;
432437
_lastVariableIndex = index;
@@ -483,12 +488,6 @@ class Environment {
483488
/// required, since some nodes need to do real work to manufacture a source
484489
/// span.
485490
AstNode _getVariableNodeFromGlobalModule(String name) {
486-
// There isn't a real variable defined as this index, but it will cause
487-
// [getVariable] to short-circuit and get to this function faster next time
488-
// the variable is accessed.
489-
_lastVariableName = name;
490-
_lastVariableIndex = 0;
491-
492491
if (_globalModules == null) return null;
493492

494493
// We don't need to worry about multiple modules defining the same variable,
@@ -816,6 +815,20 @@ class Environment {
816815
forwarded: _forwardedModules);
817816
}
818817

818+
/// Returns a module with the same members and upstream modules as [this], but
819+
/// an empty stylesheet and extender.
820+
///
821+
/// This is used when resolving imports, since they need to inject forwarded
822+
/// members into the current scope. It's the only situation in which a nested
823+
/// environment can become a module.
824+
Module<Callable> toDummyModule() {
825+
return _EnvironmentModule(
826+
this,
827+
CssStylesheet(const [], SourceFile.decoded(const []).span(0)),
828+
Extender.empty,
829+
forwarded: _forwardedModules);
830+
}
831+
819832
/// Returns the module with the given [namespace], or throws a
820833
/// [SassScriptException] if none exists.
821834
Module<Callable> _getModule(String namespace) {

lib/src/visitor/async_evaluate.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1383,7 +1383,7 @@ class _EvaluateVisitor
13831383
}
13841384

13851385
List<ModifiableCssNode> children;
1386-
var environment = _environment.global();
1386+
var environment = _environment.forImport();
13871387
await _withEnvironment(environment, () async {
13881388
var oldImporter = _importer;
13891389
var oldStylesheet = _stylesheet;
@@ -1420,8 +1420,7 @@ class _EvaluateVisitor
14201420
// Create a dummy module with empty CSS and no extensions to make forwarded
14211421
// members available in the current import context and to combine all the
14221422
// CSS from modules used by [stylesheet].
1423-
var module = environment.toModule(
1424-
CssStylesheet(const [], stylesheet.span), Extender.empty);
1423+
var module = environment.toDummyModule();
14251424
_environment.importForwards(module);
14261425

14271426
if (module.transitivelyContainsCss) {
@@ -2803,7 +2802,8 @@ class _EvaluateVisitor
28032802
if (!_sourceMap) return null;
28042803
if (expression is VariableExpression) {
28052804
return _environment.getVariableNode(expression.name,
2806-
namespace: expression.namespace);
2805+
namespace: expression.namespace) ??
2806+
expression;
28072807
} else {
28082808
return expression;
28092809
}

lib/src/visitor/evaluate.dart

Lines changed: 5 additions & 5 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: eb095e782e2983223945d189caadc649b081a676
8+
// Checksum: 0ad7cedc5ee1d05297c4a8d321d492854742e8ab
99
//
1010
// ignore_for_file: unused_import
1111

@@ -1380,7 +1380,7 @@ class _EvaluateVisitor
13801380
}
13811381

13821382
List<ModifiableCssNode> children;
1383-
var environment = _environment.global();
1383+
var environment = _environment.forImport();
13841384
_withEnvironment(environment, () {
13851385
var oldImporter = _importer;
13861386
var oldStylesheet = _stylesheet;
@@ -1417,8 +1417,7 @@ class _EvaluateVisitor
14171417
// Create a dummy module with empty CSS and no extensions to make forwarded
14181418
// members available in the current import context and to combine all the
14191419
// CSS from modules used by [stylesheet].
1420-
var module = environment.toModule(
1421-
CssStylesheet(const [], stylesheet.span), Extender.empty);
1420+
var module = environment.toDummyModule();
14221421
_environment.importForwards(module);
14231422

14241423
if (module.transitivelyContainsCss) {
@@ -2778,7 +2777,8 @@ class _EvaluateVisitor
27782777
if (!_sourceMap) return null;
27792778
if (expression is VariableExpression) {
27802779
return _environment.getVariableNode(expression.name,
2781-
namespace: expression.namespace);
2780+
namespace: expression.namespace) ??
2781+
expression;
27822782
} else {
27832783
return expression;
27842784
}

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: sass
2-
version: 1.25.1-test.1
2+
version: 1.25.2-dev
33
description: A Sass implementation in Dart.
44
author: Sass Team
55
homepage: https://github.com/sass/dart-sass

test/cli/shared.dart

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,4 +538,33 @@ void sharedTests(
538538
});
539539
});
540540
});
541+
542+
test("doesn't unassign variables", () async {
543+
// This is a regression test for one of the strangest errors I've ever
544+
// encountered. Every bit of what's going on was necessary to reproduce it,
545+
// *including* running with source maps enabled, which is why it's here and
546+
// not in sass-spec.
547+
await d.file("input.scss", "a {@import 'downstream'}").create();
548+
await d.file("_downstream.scss", r"""
549+
@import 'midstream';
550+
551+
$b: $c;
552+
@mixin d($_) {}
553+
@include d($b);
554+
e {f: $b}
555+
""").create();
556+
await d.file("_midstream.scss", "@forward 'upstream'").create();
557+
await d.file("_upstream.scss", r"$c: g").create();
558+
559+
var sass = await runSass(["input.scss", "output.css"]);
560+
await sass.shouldExit(0);
561+
562+
await d.file("output.css", equalsIgnoringWhitespace("""
563+
a e {
564+
f: g;
565+
}
566+
567+
/*# sourceMappingURL=output.css.map */
568+
""")).validate();
569+
});
541570
}

0 commit comments

Comments
 (0)