Strict mode in functions check for static modules.

This commit is contained in:
Stephen Chung 2022-06-09 17:59:28 +08:00
parent 285bf23dfa
commit dcaac20eb9
5 changed files with 50 additions and 52 deletions

View File

@ -13,6 +13,8 @@ Bug fixes
* Merging or combining a self-contained `AST` into another `AST` now works properly. * Merging or combining a self-contained `AST` into another `AST` now works properly.
* Plugin modules/functions no longer generate errors under `#![deny(missing_docs)]`. * 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. * 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 Deprecated API's
---------------- ----------------

View File

@ -32,15 +32,19 @@ impl Engine {
if let Some(index) = index { if let Some(index) = index {
let offset = global.num_imports() - index.get(); let offset = global.num_imports() - index.get();
Some(global.get_shared_import(offset).unwrap())
} else { if let m @ Some(_) = global.get_shared_import(offset) {
global return m;
.find_import(root)
.map(|n| global.get_shared_import(n).unwrap())
.or_else(|| self.global_sub_modules.get(root).cloned())
} }
} }
// 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, /// Search for a variable within the scope or within imports,
/// depending on whether the variable name is namespace-qualified. /// depending on whether the variable name is namespace-qualified.
pub(crate) fn search_namespace<'s>( pub(crate) fn search_namespace<'s>(

View File

@ -57,7 +57,7 @@ pub struct ParseState<'e> {
pub block_stack_len: usize, pub block_stack_len: usize,
/// Tracks a list of external variables (variables that are not explicitly declared in the scope). /// Tracks a list of external variables (variables that are not explicitly declared in the scope).
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
pub external_vars: crate::FnArgsVec<crate::ast::Ident>, pub external_vars: Vec<crate::ast::Ident>,
/// An indicator that disables variable capturing into externals one single time /// An indicator that disables variable capturing into externals one single time
/// up until the nearest consumed Identifier token. /// 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. /// 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 { Self {
tokenizer_control, tokenizer_control,
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
external_vars: crate::FnArgsVec::new_const(), external_vars: Vec::new(),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
allow_capture: true, allow_capture: true,
interned_strings: StringsInterner::new(), interned_strings: StringsInterner::new(),
@ -494,20 +494,17 @@ impl Engine {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
let hash = if !namespace.is_empty() { 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"))] if settings.options.contains(LangOptions::STRICT_VAR) && index.is_none() {
let relax = settings.is_function_scope; if root != crate::engine::KEYWORD_GLOBAL
#[cfg(feature = "no_function")] && !self.global_sub_modules.contains_key(root)
let relax = false;
if !relax
&& settings.options.contains(LangOptions::STRICT_VAR)
&& index.is_none()
{ {
return Err(PERR::ModuleUndefined(namespace.root().to_string()) return Err(PERR::ModuleUndefined(root.to_string())
.into_err(namespace.position())); .into_err(namespace.position()));
} }
}
namespace.set_index(index); namespace.set_index(index);
@ -557,20 +554,17 @@ impl Engine {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
let hash = if !namespace.is_empty() { 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"))] if settings.options.contains(LangOptions::STRICT_VAR) && index.is_none() {
let relax = settings.is_function_scope; if root != crate::engine::KEYWORD_GLOBAL
#[cfg(feature = "no_function")] && !self.global_sub_modules.contains_key(root)
let relax = false;
if !relax
&& settings.options.contains(LangOptions::STRICT_VAR)
&& index.is_none()
{ {
return Err(PERR::ModuleUndefined(namespace.root().to_string()) return Err(PERR::ModuleUndefined(root.to_string())
.into_err(namespace.position())); .into_err(namespace.position()));
} }
}
namespace.set_index(index); namespace.set_index(index);
@ -1268,6 +1262,7 @@ impl Engine {
Token::Pipe | Token::Or if settings.options.contains(LangOptions::ANON_FN) => { Token::Pipe | Token::Or if settings.options.contains(LangOptions::ANON_FN) => {
let mut new_state = let mut new_state =
ParseState::new(self, state.scope, state.tokenizer_control.clone()); ParseState::new(self, state.scope, state.tokenizer_control.clone());
new_state.imports = state.imports.clone();
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
{ {
@ -1678,20 +1673,17 @@ impl Engine {
#[cfg(not(feature = "no_module"))] #[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"))] if settings.options.contains(LangOptions::STRICT_VAR) && index.is_none() {
let relax = settings.is_function_scope; if root != crate::engine::KEYWORD_GLOBAL
#[cfg(feature = "no_function")] && !self.global_sub_modules.contains_key(root)
let relax = false;
if !relax
&& settings.options.contains(LangOptions::STRICT_VAR)
&& index.is_none()
{ {
return Err(PERR::ModuleUndefined(namespace.root().to_string()) return Err(PERR::ModuleUndefined(root.to_string())
.into_err(namespace.position())); .into_err(namespace.position()));
} }
}
namespace.set_index(index); namespace.set_index(index);
} }
@ -3080,6 +3072,7 @@ impl Engine {
(Token::Fn, pos) => { (Token::Fn, pos) => {
let mut new_state = let mut new_state =
ParseState::new(self, state.scope, state.tokenizer_control.clone()); ParseState::new(self, state.scope, state.tokenizer_control.clone());
new_state.imports = state.imports.clone();
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
{ {

View File

@ -52,7 +52,7 @@ pub trait Variant: Any + private::Sealed {
#[must_use] #[must_use]
fn as_any_mut(&mut self) -> &mut dyn Any; fn as_any_mut(&mut self) -> &mut dyn Any;
/// Convert this [`Variant`] trait object to [`Box<dyn Any>`]. /// Convert this [`Variant`] trait object to [`Box<dyn Any>`][Any].
#[must_use] #[must_use]
fn as_boxed_any(self: Box<Self>) -> Box<dyn Any>; fn as_boxed_any(self: Box<Self>) -> Box<dyn Any>;
@ -79,7 +79,7 @@ pub trait Variant: Any + Send + Sync + private::Sealed {
#[must_use] #[must_use]
fn as_any_mut(&mut self) -> &mut dyn Any; fn as_any_mut(&mut self) -> &mut dyn Any;
/// Convert this [`Variant`] trait object to [`Box<dyn Any>`]. /// Convert this [`Variant`] trait object to [`Box<dyn Any>`][Any].
#[must_use] #[must_use]
fn as_boxed_any(self: Box<Self>) -> Box<dyn Any>; fn as_boxed_any(self: Box<Self>) -> Box<dyn Any>;

View File

@ -91,18 +91,17 @@ fn test_options_strict_var() -> Result<(), Box<EvalAltResult>> {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
assert!(engine.compile("fn foo(x) { x + y }").is_err()); assert!(engine.compile("fn foo(x) { x + y }").is_err());
#[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
{ {
assert!(engine.compile("print(h::y::z);").is_err()); 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()); 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"))] #[cfg(not(feature = "no_function"))]
{ {
assert!(engine.compile("let f = |y| x * y;").is_err()); assert!(engine.compile("let f = |y| x * y;").is_err());