diff --git a/CHANGELOG.md b/CHANGELOG.md index 546da8b6..381ca6ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ Bug fixes * Merging or combining a self-contained `AST` into another `AST` now works properly. * Plugin modules/functions no longer generate errors under `#![deny(missing_docs)]`. * Calling a property on a function call that returns a shared value no longer causes an error. +* _Strict Variables Mode_ now checks for module namespaces within functions as well. +* Module defined via `Engine::register_static_module` are now checked in _Strict Variables Mode_. Deprecated API's ---------------- diff --git a/src/eval/expr.rs b/src/eval/expr.rs index d0dce935..7077637c 100644 --- a/src/eval/expr.rs +++ b/src/eval/expr.rs @@ -32,13 +32,17 @@ impl Engine { if let Some(index) = index { let offset = global.num_imports() - index.get(); - Some(global.get_shared_import(offset).unwrap()) - } else { - global - .find_import(root) - .map(|n| global.get_shared_import(n).unwrap()) - .or_else(|| self.global_sub_modules.get(root).cloned()) + + if let m @ Some(_) = global.get_shared_import(offset) { + return m; + } } + + // Do a text-match search if the index doesn't work + global.find_import(root).map_or_else( + || self.global_sub_modules.get(root).cloned(), + |offset| global.get_shared_import(offset), + ) } /// Search for a variable within the scope or within imports, diff --git a/src/parser.rs b/src/parser.rs index 0feeaf19..f3350933 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -57,7 +57,7 @@ pub struct ParseState<'e> { pub block_stack_len: usize, /// Tracks a list of external variables (variables that are not explicitly declared in the scope). #[cfg(not(feature = "no_closure"))] - pub external_vars: crate::FnArgsVec, + pub external_vars: Vec, /// An indicator that disables variable capturing into externals one single time /// up until the nearest consumed Identifier token. /// If set to false the next call to [`access_var`][ParseState::access_var] will not capture the variable. @@ -80,7 +80,7 @@ impl<'e> ParseState<'e> { Self { tokenizer_control, #[cfg(not(feature = "no_closure"))] - external_vars: crate::FnArgsVec::new_const(), + external_vars: Vec::new(), #[cfg(not(feature = "no_closure"))] allow_capture: true, interned_strings: StringsInterner::new(), @@ -494,19 +494,16 @@ impl Engine { #[cfg(not(feature = "no_module"))] let hash = if !namespace.is_empty() { - let index = state.find_module(namespace.root()); + let root = namespace.root(); + let index = state.find_module(root); - #[cfg(not(feature = "no_function"))] - let relax = settings.is_function_scope; - #[cfg(feature = "no_function")] - let relax = false; - - if !relax - && settings.options.contains(LangOptions::STRICT_VAR) - && index.is_none() - { - return Err(PERR::ModuleUndefined(namespace.root().to_string()) - .into_err(namespace.position())); + if settings.options.contains(LangOptions::STRICT_VAR) && index.is_none() { + if root != crate::engine::KEYWORD_GLOBAL + && !self.global_sub_modules.contains_key(root) + { + return Err(PERR::ModuleUndefined(root.to_string()) + .into_err(namespace.position())); + } } namespace.set_index(index); @@ -557,19 +554,16 @@ impl Engine { #[cfg(not(feature = "no_module"))] let hash = if !namespace.is_empty() { - let index = state.find_module(namespace.root()); + let root = namespace.root(); + let index = state.find_module(root); - #[cfg(not(feature = "no_function"))] - let relax = settings.is_function_scope; - #[cfg(feature = "no_function")] - let relax = false; - - if !relax - && settings.options.contains(LangOptions::STRICT_VAR) - && index.is_none() - { - return Err(PERR::ModuleUndefined(namespace.root().to_string()) - .into_err(namespace.position())); + if settings.options.contains(LangOptions::STRICT_VAR) && index.is_none() { + if root != crate::engine::KEYWORD_GLOBAL + && !self.global_sub_modules.contains_key(root) + { + return Err(PERR::ModuleUndefined(root.to_string()) + .into_err(namespace.position())); + } } namespace.set_index(index); @@ -1268,6 +1262,7 @@ impl Engine { Token::Pipe | Token::Or if settings.options.contains(LangOptions::ANON_FN) => { let mut new_state = ParseState::new(self, state.scope, state.tokenizer_control.clone()); + new_state.imports = state.imports.clone(); #[cfg(not(feature = "unchecked"))] { @@ -1678,19 +1673,16 @@ impl Engine { #[cfg(not(feature = "no_module"))] { - let index = state.find_module(namespace.root()); + let root = namespace.root(); + let index = state.find_module(root); - #[cfg(not(feature = "no_function"))] - let relax = settings.is_function_scope; - #[cfg(feature = "no_function")] - let relax = false; - - if !relax - && settings.options.contains(LangOptions::STRICT_VAR) - && index.is_none() - { - return Err(PERR::ModuleUndefined(namespace.root().to_string()) - .into_err(namespace.position())); + if settings.options.contains(LangOptions::STRICT_VAR) && index.is_none() { + if root != crate::engine::KEYWORD_GLOBAL + && !self.global_sub_modules.contains_key(root) + { + return Err(PERR::ModuleUndefined(root.to_string()) + .into_err(namespace.position())); + } } namespace.set_index(index); @@ -3080,6 +3072,7 @@ impl Engine { (Token::Fn, pos) => { let mut new_state = ParseState::new(self, state.scope, state.tokenizer_control.clone()); + new_state.imports = state.imports.clone(); #[cfg(not(feature = "unchecked"))] { diff --git a/src/types/dynamic.rs b/src/types/dynamic.rs index 0b5e891c..8c71de89 100644 --- a/src/types/dynamic.rs +++ b/src/types/dynamic.rs @@ -52,7 +52,7 @@ pub trait Variant: Any + private::Sealed { #[must_use] fn as_any_mut(&mut self) -> &mut dyn Any; - /// Convert this [`Variant`] trait object to [`Box`]. + /// Convert this [`Variant`] trait object to [`Box`][Any]. #[must_use] fn as_boxed_any(self: Box) -> Box; @@ -79,7 +79,7 @@ pub trait Variant: Any + Send + Sync + private::Sealed { #[must_use] fn as_any_mut(&mut self) -> &mut dyn Any; - /// Convert this [`Variant`] trait object to [`Box`]. + /// Convert this [`Variant`] trait object to [`Box`][Any]. #[must_use] fn as_boxed_any(self: Box) -> Box; diff --git a/tests/options.rs b/tests/options.rs index 5725a76f..cbe0f70a 100644 --- a/tests/options.rs +++ b/tests/options.rs @@ -91,18 +91,17 @@ fn test_options_strict_var() -> Result<(), Box> { #[cfg(not(feature = "no_function"))] assert!(engine.compile("fn foo(x) { x + y }").is_err()); + #[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_module"))] { assert!(engine.compile("print(h::y::z);").is_err()); - engine.compile(r#"import "hello" as h; print(h::y::z);"#)?; + assert!(engine.compile("fn foo() { h::y::z }").is_err()); + assert!(engine.compile("fn foo() { h::y::foo() }").is_err()); + engine.compile(r#"import "hello" as h; fn foo() { h::a::b::c } print(h::y::z);"#)?; assert!(engine.compile("let x = h::y::foo();").is_err()); - engine.compile(r#"import "hello" as h; let x = h::y::foo();"#)?; + engine.compile(r#"import "hello" as h; fn foo() { h::a::b::c() } let x = h::y::foo();"#)?; } - #[cfg(not(feature = "no_function"))] - #[cfg(not(feature = "no_module"))] - engine.compile("fn foo() { h::y::foo() }")?; - #[cfg(not(feature = "no_function"))] { assert!(engine.compile("let f = |y| x * y;").is_err());