From 1f672c4c4942ec712e45032b99f19ff2b69d2015 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Sat, 31 Jul 2021 11:42:20 -0400 Subject: [PATCH] mixins and functions can use their module's scope --- src/builtin/functions/meta.rs | 2 +- src/parse/function.rs | 48 ++++++++++++++++++++++++++-------- src/parse/mixin.rs | 23 ++++++++++++---- src/parse/value/eval.rs | 22 +++++++++------- src/parse/value/parse.rs | 21 ++++++++------- src/scope.rs | 2 +- src/value/sass_function.rs | 20 ++++++++++---- tests/use.rs | 49 +++++++++++++++++++++++++++++++++++ 8 files changed, 146 insertions(+), 41 deletions(-) diff --git a/src/builtin/functions/meta.rs b/src/builtin/functions/meta.rs index ed0aa6b..d115c67 100644 --- a/src/builtin/functions/meta.rs +++ b/src/builtin/functions/meta.rs @@ -283,7 +283,7 @@ pub(crate) fn call(mut args: CallArgs, parser: &mut Parser) -> SassResult .into()) } }; - func.call(args.decrement(), parser) + func.call(args.decrement(), None, parser) } #[allow(clippy::needless_pass_by_value)] diff --git a/src/parse/function.rs b/src/parse/function.rs index 042cf7d..edf0ee6 100644 --- a/src/parse/function.rs +++ b/src/parse/function.rs @@ -14,8 +14,16 @@ use crate::{ use super::{common::ContextFlags, Parser, Stmt}; /// Names that functions are not allowed to have -const RESERVED_IDENTIFIERS: [&str; 7] = - ["calc", "element", "expression", "url", "and", "or", "not"]; +const RESERVED_IDENTIFIERS: [&str; 8] = [ + "calc", + "element", + "expression", + "url", + "and", + "or", + "not", + "clamp", +]; impl<'a, 'b> Parser<'a, 'b> { pub(super) fn parse_function(&mut self) -> SassResult<()> { @@ -56,16 +64,15 @@ impl<'a, 'b> Parser<'a, 'b> { let name_as_ident = Identifier::from(name); + let sass_function = SassFunction::UserDefined { + function: Box::new(function), + name: name_as_ident, + }; + if self.at_root { - self.global_scope.insert_fn( - name_as_ident, - SassFunction::UserDefined(Box::new(function), name_as_ident), - ); + self.global_scope.insert_fn(name_as_ident, sass_function); } else { - self.scopes.insert_fn( - name_as_ident, - SassFunction::UserDefined(Box::new(function), name_as_ident), - ); + self.scopes.insert_fn(name_as_ident, sass_function); } Ok(()) } @@ -78,7 +85,12 @@ impl<'a, 'b> Parser<'a, 'b> { Ok(Box::new(v.node)) } - pub fn eval_function(&mut self, function: Function, args: CallArgs) -> SassResult { + pub fn eval_function( + &mut self, + function: Function, + args: CallArgs, + module: Option>, + ) -> SassResult { let Function { body, args: fn_args, @@ -97,6 +109,16 @@ impl<'a, 'b> Parser<'a, 'b> { self.scopes.enter_scope(scope); }; + if let Some(module) = module { + let module = self.modules.get(module.node, module.span)?; + + if declared_at_root { + new_scope.enter_scope(module.scope.clone()); + } else { + self.scopes.enter_scope(module.scope.clone()); + } + } + let mut return_value = Parser { toks: &mut Lexer::new(body), map: self.map, @@ -125,6 +147,10 @@ impl<'a, 'b> Parser<'a, 'b> { self.scopes.exit_scope(); } + if module.is_some() { + self.scopes.exit_scope(); + } + debug_assert!( return_value.len() <= 1, "we expect there to be only one return value" diff --git a/src/parse/mixin.rs b/src/parse/mixin.rs index a315068..79dccae 100644 --- a/src/parse/mixin.rs +++ b/src/parse/mixin.rs @@ -73,15 +73,18 @@ impl<'a, 'b> Parser<'a, 'b> { self.whitespace_or_comment(); let name = self.parse_identifier()?.map_node(Into::into); - let mixin = if self.consume_char_if_exists('.') { + let (mixin, module) = if self.consume_char_if_exists('.') { let module = name; let name = self.parse_identifier()?.map_node(Into::into); - self.modules - .get(module.node, module.span)? - .get_mixin(name)? + ( + self.modules + .get(module.node, module.span)? + .get_mixin(name)?, + Some(module), + ) } else { - self.scopes.get_mixin(name, self.global_scope)? + (self.scopes.get_mixin(name, self.global_scope)?, None) }; self.whitespace_or_comment(); @@ -152,6 +155,11 @@ impl<'a, 'b> Parser<'a, 'b> { self.scopes.enter_scope(scope); + if let Some(module) = module { + let module = self.modules.get(module.node, module.span)?; + self.scopes.enter_scope(module.scope.clone()); + } + self.content.push(Content { content, content_args, @@ -180,6 +188,11 @@ impl<'a, 'b> Parser<'a, 'b> { .parse_stmt()?; self.content.pop(); + + if module.is_some() { + self.scopes.exit_scope(); + } + self.scopes.exit_scope(); if declared_at_root { diff --git a/src/parse/value/eval.rs b/src/parse/value/eval.rs index 3e7796f..758d166 100644 --- a/src/parse/value/eval.rs +++ b/src/parse/value/eval.rs @@ -7,7 +7,7 @@ use num_traits::Zero; use crate::{ args::CallArgs, - common::{Op, QuoteKind}, + common::{Identifier, Op, QuoteKind}, error::SassResult, unit::Unit, value::{SassFunction, Value}, @@ -19,7 +19,7 @@ use super::super::Parser; pub(crate) enum HigherIntermediateValue { Literal(Value), /// A function that hasn't yet been evaluated - Function(SassFunction, CallArgs), + Function(SassFunction, CallArgs, Option>), BinaryOp(Box, Op, Box), UnaryOp(Op, Box), } @@ -31,8 +31,13 @@ impl HigherIntermediateValue { } impl<'a, 'b> Parser<'a, 'b> { - fn call_function(&mut self, function: SassFunction, args: CallArgs) -> SassResult { - function.call(args, self) + fn call_function( + &mut self, + function: SassFunction, + args: CallArgs, + module: Option>, + ) -> SassResult { + function.call(args, module, self) } } @@ -54,8 +59,8 @@ impl<'a, 'b: 'a, 'c> ValueVisitor<'a, 'b, 'c> { HigherIntermediateValue::Literal(v) => Ok(v), HigherIntermediateValue::BinaryOp(v1, op, v2) => self.bin_op(*v1, op, *v2, in_parens), HigherIntermediateValue::UnaryOp(op, val) => self.unary_op(op, *val, in_parens), - HigherIntermediateValue::Function(function, args) => { - self.parser.call_function(function, args) + HigherIntermediateValue::Function(function, args, module) => { + self.parser.call_function(function, args, module) } } } @@ -216,8 +221,8 @@ impl<'a, 'b: 'a, 'c> ValueVisitor<'a, 'b, 'c> { HigherIntermediateValue::UnaryOp(op, val) => { HigherIntermediateValue::Literal(self.unary_op(op, *val, in_parens)?) } - HigherIntermediateValue::Function(function, args) => { - HigherIntermediateValue::Literal(self.parser.call_function(function, args)?) + HigherIntermediateValue::Function(function, args, module) => { + HigherIntermediateValue::Literal(self.parser.call_function(function, args, module)?) } val => val, }) @@ -301,7 +306,6 @@ impl<'a, 'b: 'a, 'c> ValueVisitor<'a, 'b, 'c> { .into()); } if unit == unit2 { - // dbg!(&num, &num2, num.clone() + num2.clone(), unit.clone(), unit2.clone()); Value::Dimension(Some(num + num2), unit, true) } else if unit == Unit::None { Value::Dimension(Some(num + num2), unit2, true) diff --git a/src/parse/value/parse.rs b/src/parse/value/parse.rs index b27f5cb..bec9b22 100644 --- a/src/parse/value/parse.rs +++ b/src/parse/value/parse.rs @@ -198,8 +198,7 @@ impl<'a, 'b> Parser<'a, 'b> { #[allow(clippy::eval_order_dependence)] fn parse_module_item( &mut self, - module: &str, - mut module_span: Span, + mut module: Spanned, ) -> SassResult> { Ok( IntermediateValue::Value(if self.consume_char_if_exists('$') { @@ -207,9 +206,9 @@ impl<'a, 'b> Parser<'a, 'b> { .parse_identifier_no_interpolation(false)? .map_node(|i| i.into()); - module_span = module_span.merge(var.span); + module.span = module.span.merge(var.span); - let value = self.modules.get(module.into(), module_span)?.get_var(var)?; + let value = self.modules.get(module.node, module.span)?.get_var(var)?; HigherIntermediateValue::Literal(value.clone()) } else { let fn_name = self @@ -218,7 +217,7 @@ impl<'a, 'b> Parser<'a, 'b> { let function = self .modules - .get(module.into(), module_span)? + .get(module.node, module.span)? .get_fn(fn_name)? .ok_or(("Undefined function.", fn_name.span))?; @@ -226,9 +225,9 @@ impl<'a, 'b> Parser<'a, 'b> { let call_args = self.parse_call_args()?; - HigherIntermediateValue::Function(function, call_args) + HigherIntermediateValue::Function(function, call_args, Some(module)) }) - .span(module_span), + .span(module.span), ) } @@ -260,6 +259,7 @@ impl<'a, 'b> Parser<'a, 'b> { return Ok(IntermediateValue::Value(HigherIntermediateValue::Function( SassFunction::Builtin(f.clone(), as_ident), self.parse_call_args()?, + None, )) .span(self.span_before)); } @@ -297,7 +297,7 @@ impl<'a, 'b> Parser<'a, 'b> { let call_args = self.parse_call_args()?; Ok( - IntermediateValue::Value(HigherIntermediateValue::Function(func, call_args)) + IntermediateValue::Value(HigherIntermediateValue::Function(func, call_args, None)) .span(self.span_before), ) } @@ -336,7 +336,10 @@ impl<'a, 'b> Parser<'a, 'b> { Some(Token { kind: '.', .. }) => { if !predicate(self) { self.toks.next(); - return self.parse_module_item(&s, span); + return self.parse_module_item(Spanned { + node: s.into(), + span, + }); } } _ => {} diff --git a/src/scope.rs b/src/scope.rs index a31f8a4..bcfe8bc 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -13,7 +13,7 @@ use crate::{ /// A singular scope /// /// Contains variables, functions, and mixins -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub(crate) struct Scope { pub vars: BTreeMap, pub mixins: BTreeMap, diff --git a/src/value/sass_function.rs b/src/value/sass_function.rs index ba65030..a193ca3 100644 --- a/src/value/sass_function.rs +++ b/src/value/sass_function.rs @@ -11,6 +11,8 @@ use std::fmt; +use codemap::Spanned; + use crate::{ args::CallArgs, atrule::Function, builtin::Builtin, common::Identifier, error::SassResult, parse::Parser, value::Value, @@ -25,7 +27,10 @@ use crate::{ #[derive(Clone, Eq, PartialEq)] pub(crate) enum SassFunction { Builtin(Builtin, Identifier), - UserDefined(Box, Identifier), + UserDefined { + function: Box, + name: Identifier, + }, } impl SassFunction { @@ -34,7 +39,7 @@ impl SassFunction { /// Used mainly in debugging and `inspect()` pub fn name(&self) -> &Identifier { match self { - Self::Builtin(_, name) | Self::UserDefined(_, name) => name, + Self::Builtin(_, name) | Self::UserDefined { name, .. } => name, } } @@ -44,14 +49,19 @@ impl SassFunction { fn kind(&self) -> &'static str { match &self { Self::Builtin(..) => "Builtin", - Self::UserDefined(..) => "UserDefined", + Self::UserDefined { .. } => "UserDefined", } } - pub fn call(self, args: CallArgs, parser: &mut Parser) -> SassResult { + pub fn call( + self, + args: CallArgs, + module: Option>, + parser: &mut Parser, + ) -> SassResult { match self { Self::Builtin(f, ..) => f.0(args, parser), - Self::UserDefined(f, ..) => parser.eval_function(*f, args), + Self::UserDefined { function, .. } => parser.eval_function(*function, args, module), } } } diff --git a/tests/use.rs b/tests/use.rs index c9a86fd..da7be35 100644 --- a/tests/use.rs +++ b/tests/use.rs @@ -369,6 +369,55 @@ fn use_modules_imported_by_other_modules_does_not_cause_conflict() { ); } +#[test] +fn use_mixin_can_use_scope_from_own_module() { + let input = r#" + @use "use_mixin_can_use_scope_from_own_module__a" as a; + @include a.foo(); + "#; + + tempfile!( + "use_mixin_can_use_scope_from_own_module__a.scss", + "$a: red; + + @mixin foo() { + a { + color: $a; + } + }" + ); + + assert_eq!( + "a {\n color: red;\n}\n", + &grass::from_string(input.to_string(), &grass::Options::default()).expect(input) + ); +} + +#[test] +fn use_function_can_use_scope_from_own_module() { + let input = r#" + @use "use_function_can_use_scope_from_own_module__a" as a; + + a { + color: a.foo(); + } + "#; + + tempfile!( + "use_function_can_use_scope_from_own_module__a.scss", + "$a: red; + + @function foo() { + @return $a; + }" + ); + + assert_eq!( + "a {\n color: red;\n}\n", + &grass::from_string(input.to_string(), &grass::Options::default()).expect(input) + ); +} + #[test] fn use_variable_redeclaration_builtin() { let input = "@use \"sass:math\";\nmath.$e: red;";