diff --git a/RELEASES.md b/RELEASES.md index ae226bae..4198f744 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -4,13 +4,16 @@ Rhai Release Notes Version 0.19.9 ============== -This version removes the confusing differences between _packages_ and _modules_ +This version fixes a bug introduced in `0.19.8` which breaks property access +within closures. + +It also removes the confusing differences between _packages_ and _modules_ by unifying the terminology and API under the global umbrella of _modules_. Bug fixes --------- -* Property access in +* Bug when accessing properties in closures is fixed. Breaking changes ---------------- diff --git a/doc/src/engine/metadata/gen_fn_sig.md b/doc/src/engine/metadata/gen_fn_sig.md index 30067c60..8eab0d0c 100644 --- a/doc/src/engine/metadata/gen_fn_sig.md +++ b/doc/src/engine/metadata/gen_fn_sig.md @@ -53,9 +53,10 @@ In this case, the first parameter should be `&mut T` of the custom type and the Script-defined [function] signatures contain parameter names. Since all parameters, as well as the return value, are [`Dynamic`] the types are simply not shown. -A script-defined function always takes dynamic arguments, and the return type is also dynamic: +A script-defined function always takes dynamic arguments, and the return type is also dynamic, +so no type information is needed: -> `foo(x, y, z) -> Dynamic` +> `foo(x, y, z)` probably defined as: diff --git a/src/ast.rs b/src/ast.rs index b6c211c6..920dccb4 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -91,7 +91,7 @@ impl fmt::Display for ScriptFnDef { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, - "{}{}({}) -> Dynamic", + "{}{}({})", if self.access.is_private() { "private " } else { @@ -133,7 +133,7 @@ impl fmt::Display for ScriptFnMetadata<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, - "{}{}({}) -> Dynamic", + "{}{}({})", if self.access.is_private() { "private " } else { @@ -646,7 +646,7 @@ impl AsRef for AST { /// ## WARNING /// /// This type is volatile and may change. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] +#[derive(Clone, Eq, PartialEq, Hash)] pub struct Ident { /// Identifier name. pub name: ImmutableString, @@ -654,6 +654,12 @@ pub struct Ident { pub pos: Position, } +impl fmt::Debug for Ident { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Ident({:?} @ {:?})", self.name, self.pos) + } +} + /// _(INTERNALS)_ A type encapsulating the mode of a `return`/`throw` statement. /// Exported under the `internals` feature only. /// diff --git a/src/module/mod.rs b/src/module/mod.rs index 92c7b8fb..d1c023e5 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -101,8 +101,6 @@ impl FuncInfo { if return_type != "()" { sig.push_str(") -> "); sig.push_str(&return_type); - } else if self.func.is_script() { - sig.push_str(") -> Dynamic"); } else { sig.push_str(")"); } @@ -115,7 +113,7 @@ impl FuncInfo { } if self.func.is_script() { - sig.push_str(") -> Dynamic"); + sig.push_str(")"); } else { sig.push_str(") -> ?"); } @@ -471,6 +469,7 @@ impl Module { /// /// By taking a mutable reference, it is assumed that some sub-modules will be modified. /// Thus the module is automatically set to be non-indexed. + #[cfg(not(feature = "no_module"))] #[inline(always)] pub(crate) fn sub_modules_mut(&mut self) -> &mut HashMap> { // We must assume that the user has changed the sub-modules diff --git a/src/module/resolvers/file.rs b/src/module/resolvers/file.rs index b3be9bce..c35c347d 100644 --- a/src/module/resolvers/file.rs +++ b/src/module/resolvers/file.rs @@ -39,7 +39,7 @@ use crate::{Engine, EvalAltResult, Module, ModuleResolver, Position, Shared}; /// ``` #[derive(Debug)] pub struct FileModuleResolver { - path: PathBuf, + base_path: PathBuf, extension: String, #[cfg(not(feature = "sync"))] @@ -99,7 +99,7 @@ impl FileModuleResolver { extension: impl Into, ) -> Self { Self { - path: path.into(), + base_path: path.into(), extension: extension.into(), cache: Default::default(), } @@ -127,13 +127,13 @@ impl FileModuleResolver { /// Get the base path for script files. #[inline(always)] - pub fn path(&self) -> &Path { - self.path.as_ref() + pub fn base_path(&self) -> &Path { + self.base_path.as_ref() } /// Set the base path for script files. #[inline(always)] - pub fn set_path(&mut self, path: impl Into) -> &mut Self { - self.path = path.into(); + pub fn set_base_path(&mut self, path: impl Into) -> &mut Self { + self.base_path = path.into(); self } @@ -186,7 +186,7 @@ impl ModuleResolver for FileModuleResolver { pos: Position, ) -> Result, Box> { // Construct the script file path - let mut file_path = self.path.clone(); + let mut file_path = self.base_path.clone(); file_path.push(path); file_path.set_extension(&self.extension); // Force extension diff --git a/src/parser.rs b/src/parser.rs index 5d22c9dd..0011572c 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -280,12 +280,12 @@ fn parse_paren_expr( lib: &mut FunctionsLib, mut settings: ParseSettings, ) -> Result { - // ( ... - settings.pos = eat_token(input, Token::LeftParen); - #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; + // ( ... + settings.pos = eat_token(input, Token::LeftParen); + if match_token(input, Token::RightParen).0 { return Ok(Expr::Unit(settings.pos)); } @@ -316,11 +316,11 @@ fn parse_fn_call( mut namespace: Option, settings: ParseSettings, ) -> Result { - let (token, token_pos) = input.peek().unwrap(); - #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; + let (token, token_pos) = input.peek().unwrap(); + let mut args = StaticVec::new(); match token { @@ -642,12 +642,12 @@ fn parse_array_literal( lib: &mut FunctionsLib, mut settings: ParseSettings, ) -> Result { - // [ ... - settings.pos = eat_token(input, Token::LeftBracket); - #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; + // [ ... + settings.pos = eat_token(input, Token::LeftBracket); + let mut arr = StaticVec::new(); loop { @@ -712,12 +712,12 @@ fn parse_map_literal( lib: &mut FunctionsLib, mut settings: ParseSettings, ) -> Result { - // #{ ... - settings.pos = eat_token(input, Token::MapStart); - #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; + // #{ ... + settings.pos = eat_token(input, Token::MapStart); + let mut map: StaticVec<(Ident, Expr)> = Default::default(); loop { @@ -823,13 +823,12 @@ fn parse_switch( lib: &mut FunctionsLib, mut settings: ParseSettings, ) -> Result { - // switch ... - let token_pos = eat_token(input, Token::Switch); - settings.pos = token_pos; - #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; + // switch ... + settings.pos = eat_token(input, Token::Switch); + let item = parse_expr(input, state, lib, settings.level_up())?; match input.next().unwrap() { @@ -950,12 +949,12 @@ fn parse_primary( lib: &mut FunctionsLib, mut settings: ParseSettings, ) -> Result { - let (token, token_pos) = input.peek().unwrap(); - settings.pos = *token_pos; - #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; + let (token, token_pos) = input.peek().unwrap(); + settings.pos = *token_pos; + let mut root_expr = match token { Token::EOF => return Err(PERR::UnexpectedEOF.into_err(settings.pos)), @@ -1241,7 +1240,7 @@ fn parse_primary( state.allow_capture = false; } - let rhs = parse_unary(input, state, lib, settings.level_up())?; + let rhs = parse_primary(input, state, lib, settings.level_up())?; make_dot_expr(state, expr, rhs, tail_pos)? } // Unknown postfix operator @@ -1262,17 +1261,16 @@ fn parse_primary( }, _ => None, } - .map(|x| { - if let (_, Some((ref mut hash, ref mut namespace)), Ident { name, .. }) = x.as_mut() { + .map(|x| match x.as_mut() { + (_, Some((ref mut hash, ref mut namespace)), Ident { name, .. }) => { // Qualifiers + variable name *hash = calc_script_fn_hash(namespace.iter().map(|v| v.name.as_str()), name, 0).unwrap(); #[cfg(not(feature = "no_module"))] namespace.set_index(state.find_module(&namespace[0].name)); - } else { - unreachable!(); } + _ => unreachable!(), }); // Make sure identifiers are valid @@ -1458,12 +1456,12 @@ fn parse_op_assignment_stmt( lhs: Expr, mut settings: ParseSettings, ) -> Result { - let (token, token_pos) = input.peek().unwrap(); - settings.pos = *token_pos; - #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; + let (token, token_pos) = input.peek().unwrap(); + settings.pos = *token_pos; + let op = match token { Token::Equals => "".into(), @@ -1714,11 +1712,11 @@ fn parse_binary_op( lhs: Expr, mut settings: ParseSettings, ) -> Result { - settings.pos = lhs.position(); - #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; + settings.pos = lhs.position(); + let mut root = lhs; loop { @@ -1868,12 +1866,6 @@ fn parse_binary_op( make_in_expr(current_lhs, rhs, pos)? } - // #[cfg(not(feature = "no_object"))] - // Token::Period => { - // let rhs = args.pop().unwrap(); - // let current_lhs = args.pop().unwrap(); - // make_dot_expr(state, current_lhs, rhs, pos)? - // } Token::Custom(s) if state .engine @@ -2017,11 +2009,11 @@ fn parse_expr( lib: &mut FunctionsLib, mut settings: ParseSettings, ) -> Result { - settings.pos = input.peek().unwrap().1; - #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; + settings.pos = input.peek().unwrap().1; + // Check if it is a custom syntax. if !state.engine.custom_syntax.is_empty() { let (token, pos) = input.peek().unwrap(); @@ -2095,13 +2087,12 @@ fn parse_if( lib: &mut FunctionsLib, mut settings: ParseSettings, ) -> Result { - // if ... - let token_pos = eat_token(input, Token::If); - settings.pos = token_pos; - #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; + // if ... + settings.pos = eat_token(input, Token::If); + // if guard { if_body } ensure_not_statement_expr(input, "a boolean")?; let guard = parse_expr(input, state, lib, settings.level_up())?; @@ -2121,7 +2112,11 @@ fn parse_if( None }; - Ok(Stmt::If(guard, Box::new((if_body, else_body)), token_pos)) + Ok(Stmt::If( + guard, + Box::new((if_body, else_body)), + settings.pos, + )) } /// Parse a while loop. @@ -2149,7 +2144,7 @@ fn parse_while_loop( settings.is_breakable = true; let body = Box::new(parse_block(input, state, lib, settings.level_up())?); - Ok(Stmt::While(guard, body, token_pos)) + Ok(Stmt::While(guard, body, settings.pos)) } /// Parse a do loop. @@ -2159,13 +2154,12 @@ fn parse_do( lib: &mut FunctionsLib, mut settings: ParseSettings, ) -> Result { - // do ... - let token_pos = eat_token(input, Token::Do); - settings.pos = token_pos; - #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; + // do ... + settings.pos = eat_token(input, Token::Do); + // do { body } [while|until] guard settings.is_breakable = true; let body = Box::new(parse_block(input, state, lib, settings.level_up())?); @@ -2186,7 +2180,7 @@ fn parse_do( let guard = parse_expr(input, state, lib, settings.level_up())?; ensure_not_assignment(input)?; - Ok(Stmt::Do(body, guard, is_while, token_pos)) + Ok(Stmt::Do(body, guard, is_while, settings.pos)) } /// Parse a for loop. @@ -2196,13 +2190,12 @@ fn parse_for( lib: &mut FunctionsLib, mut settings: ParseSettings, ) -> Result { - // for ... - let token_pos = eat_token(input, Token::For); - settings.pos = token_pos; - #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; + // for ... + settings.pos = eat_token(input, Token::For); + // for name ... let name = match input.next().unwrap() { // Variable name @@ -2242,7 +2235,7 @@ fn parse_for( state.stack.truncate(prev_stack_len); - Ok(Stmt::For(expr, Box::new((name, body)), token_pos)) + Ok(Stmt::For(expr, Box::new((name, body)), settings.pos)) } /// Parse a variable definition statement. @@ -2254,13 +2247,12 @@ fn parse_let( export: bool, mut settings: ParseSettings, ) -> Result { - // let/const... (specified in `var_type`) - let token_pos = input.next().unwrap().1; - settings.pos = token_pos; - #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; + // let/const... (specified in `var_type`) + settings.pos = input.next().unwrap().1; + // let name ... let (name, pos) = match input.next().unwrap() { (Token::Identifier(s), pos) => (s, pos), @@ -2272,7 +2264,7 @@ fn parse_let( }; // let name = ... - let init_expr = if match_token(input, Token::Equals).0 { + let expr = if match_token(input, Token::Equals).0 { // let name = expr Some(parse_expr(input, state, lib, settings.level_up())?) } else { @@ -2285,14 +2277,14 @@ fn parse_let( let name = state.get_interned_string(name); state.stack.push((name.clone(), AccessMode::ReadWrite)); let var_def = Ident { name, pos }; - Ok(Stmt::Let(Box::new(var_def), init_expr, export, token_pos)) + Ok(Stmt::Let(Box::new(var_def), expr, export, settings.pos)) } // const name = { expr:constant } AccessMode::ReadOnly => { let name = state.get_interned_string(name); state.stack.push((name.clone(), AccessMode::ReadOnly)); let var_def = Ident { name, pos }; - Ok(Stmt::Const(Box::new(var_def), init_expr, export, token_pos)) + Ok(Stmt::Const(Box::new(var_def), expr, export, settings.pos)) } } } @@ -2305,23 +2297,22 @@ fn parse_import( lib: &mut FunctionsLib, mut settings: ParseSettings, ) -> Result { - // import ... - let token_pos = eat_token(input, Token::Import); - settings.pos = token_pos; - #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; + // import ... + settings.pos = eat_token(input, Token::Import); + // import expr ... let expr = parse_expr(input, state, lib, settings.level_up())?; // import expr as ... if !match_token(input, Token::As).0 { - return Ok(Stmt::Import(expr, None, token_pos)); + return Ok(Stmt::Import(expr, None, settings.pos)); } // import expr as name ... - let (name, _) = match input.next().unwrap() { + let (name, name_pos) = match input.next().unwrap() { (Token::Identifier(s), pos) => (s, pos), (Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => { return Err(PERR::Reserved(s).into_err(pos)); @@ -2337,9 +2328,9 @@ fn parse_import( expr, Some(Box::new(Ident { name, - pos: settings.pos, + pos: name_pos, })), - token_pos, + settings.pos, )) } @@ -2351,12 +2342,11 @@ fn parse_export( lib: &mut FunctionsLib, mut settings: ParseSettings, ) -> Result { - let token_pos = eat_token(input, Token::Export); - settings.pos = token_pos; - #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; + settings.pos = eat_token(input, Token::Export); + match input.peek().unwrap() { (Token::Let, pos) => { let pos = *pos; @@ -2424,7 +2414,7 @@ fn parse_export( } } - Ok(Stmt::Export(exports, token_pos)) + Ok(Stmt::Export(exports, settings.pos)) } /// Parse a statement block. @@ -2513,11 +2503,11 @@ fn parse_expr_stmt( lib: &mut FunctionsLib, mut settings: ParseSettings, ) -> Result { - settings.pos = input.peek().unwrap().1; - #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; + settings.pos = input.peek().unwrap().1; + let expr = parse_expr(input, state, lib, settings.level_up())?; let stmt = parse_op_assignment_stmt(input, state, lib, expr, settings.level_up())?; Ok(stmt) @@ -2715,13 +2705,12 @@ fn parse_try_catch( lib: &mut FunctionsLib, mut settings: ParseSettings, ) -> Result { - // try ... - let token_pos = eat_token(input, Token::Try); - settings.pos = token_pos; - #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; + // try ... + settings.pos = eat_token(input, Token::Try); + // try { body } let body = parse_block(input, state, lib, settings.level_up())?; @@ -2765,7 +2754,7 @@ fn parse_try_catch( Ok(Stmt::TryCatch( Box::new((body, var_def, catch_body)), - token_pos, + settings.pos, catch_pos, )) } diff --git a/tests/optimizer.rs b/tests/optimizer.rs index bfb9c4e0..94d77d3c 100644 --- a/tests/optimizer.rs +++ b/tests/optimizer.rs @@ -55,7 +55,7 @@ fn test_optimizer_parse() -> Result<(), Box> { let ast = engine.compile("{ const DECISION = false; if DECISION { 42 } else { 123 } }")?; - assert!(format!("{:?}", ast).starts_with(r#"AST { source: None, statements: [Block([Const(Ident { name: "DECISION", pos: 1:9 }, Some(Unit(0:0)), false, 1:3), Expr(IntegerConstant(123, 1:53))], 1:1)]"#)); + assert!(format!("{:?}", ast).starts_with(r#"AST { source: None, statements: [Block([Const(Ident("DECISION" @ 1:9), Some(Unit(0:0)), false, 1:3), Expr(IntegerConstant(123, 1:53))], 1:1)]"#)); let ast = engine.compile("if 1 == 2 { 42 }")?;