diff --git a/CHANGELOG.md b/CHANGELOG.md index 58bd82da..060f305b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,7 +33,9 @@ Version 1.1.1 Bug fixes --------- +* Assignment to indexing expression with dot expressions inside no longer cause a compilation error. * The `no_module` and `internals` features now work together without a compilation error. +* String literal operations (such as `"hello" + ", world"`) now optimizes correctly. Version 1.1.0 diff --git a/src/fn_builtin.rs b/src/fn_builtin.rs index 1315d366..d2328b0c 100644 --- a/src/fn_builtin.rs +++ b/src/fn_builtin.rs @@ -266,6 +266,24 @@ pub fn get_builtin_binary_op_fn( _ => None, }; } + // () op string + if types_pair == (TypeId::of::<()>(), TypeId::of::()) { + return match op { + "+" => Some(|_, args| Ok(args[1].clone())), + "==" | ">" | ">=" | "<" | "<=" => Some(|_, _| Ok(Dynamic::FALSE)), + "!=" => Some(|_, _| Ok(Dynamic::TRUE)), + _ => None, + }; + } + // string op () + if types_pair == (TypeId::of::(), TypeId::of::<()>()) { + return match op { + "+" => Some(|_, args| Ok(args[0].clone())), + "==" | ">" | ">=" | "<" | "<=" => Some(|_, _| Ok(Dynamic::FALSE)), + "!=" => Some(|_, _| Ok(Dynamic::TRUE)), + _ => None, + }; + } // map op string #[cfg(not(feature = "no_object"))] diff --git a/src/fn_call.rs b/src/fn_call.rs index 6a1b688f..14998eb8 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -732,7 +732,7 @@ impl Engine { return Ok((Dynamic::UNIT, false)); } - let scope: &mut Scope = &mut Default::default(); + let scope = &mut Scope::new(); // Move captured variables into scope #[cfg(not(feature = "no_closure"))] diff --git a/src/optimize.rs b/src/optimize.rs index 7d529293..08c1f5bf 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -482,9 +482,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b // switch const { ... } Stmt::Switch(match_expr, x, pos) if match_expr.is_constant() => { - let value = match_expr - .get_literal_value() - .expect("`match_expr` is constant"); + let value = match_expr.get_literal_value().expect("constant"); let hasher = &mut get_hasher(); value.hash(hasher); let hash = hasher.finish(); @@ -878,7 +876,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) { #[cfg(not(feature = "no_index"))] Expr::Array(_, _) if expr.is_constant() => { state.set_dirty(); - *expr = Expr::DynamicConstant(expr.get_literal_value().expect("`expr` is constant").into(), expr.position()); + *expr = Expr::DynamicConstant(expr.get_literal_value().expect("constant").into(), expr.position()); } // [ items .. ] #[cfg(not(feature = "no_index"))] @@ -887,7 +885,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) { #[cfg(not(feature = "no_object"))] Expr::Map(_, _) if expr.is_constant() => { state.set_dirty(); - *expr = Expr::DynamicConstant(expr.get_literal_value().expect("`expr` is constant").into(), expr.position()); + *expr = Expr::DynamicConstant(expr.get_literal_value().expect("constant").into(), expr.position()); } // #{ key:value, .. } #[cfg(not(feature = "no_object"))] @@ -981,31 +979,37 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) { => { let arg_values = &mut x.args.iter().map(|e| match e { Expr::Stack(slot, _) => x.constants[*slot].clone(), - _ => e.get_literal_value().expect("`e` is constant") + _ => e.get_literal_value().expect("constant") }).collect::>(); let arg_types: StaticVec<_> = arg_values.iter().map(Dynamic::type_id).collect(); - let result = match x.name.as_str() { - KEYWORD_TYPE_OF if arg_values.len() == 1 => Some(state.engine.map_type_name(arg_values[0].type_name()).into()), + match x.name.as_str() { + KEYWORD_TYPE_OF if arg_values.len() == 1 => { + state.set_dirty(); + *expr = Expr::from_dynamic(state.engine.map_type_name(arg_values[0].type_name()).into(), *pos); + return; + } #[cfg(not(feature = "no_closure"))] - KEYWORD_IS_SHARED if arg_values.len() == 1 => Some(Dynamic::FALSE), + KEYWORD_IS_SHARED if arg_values.len() == 1 => { + state.set_dirty(); + *expr = Expr::from_dynamic(Dynamic::FALSE, *pos); + return; + } // Overloaded operators can override built-in. _ if x.args.len() == 2 && !state.has_native_fn(x.hashes.native, arg_types.as_ref()) => { - get_builtin_binary_op_fn(x.name.as_ref(), &arg_values[0], &arg_values[1]) + if let Some(result) = get_builtin_binary_op_fn(x.name.as_ref(), &arg_values[0], &arg_values[1]) .and_then(|f| { let ctx = (state.engine, x.name.as_ref(), state.lib).into(); let (first, second) = arg_values.split_first_mut().expect("`arg_values` is not empty"); (f)(ctx, &mut [ first, &mut second[0] ]).ok() - }) + }) { + state.set_dirty(); + *expr = Expr::from_dynamic(result, *pos); + return; + } } - _ => None - }; - - if let Some(result) = result { - state.set_dirty(); - *expr = Expr::from_dynamic(result, *pos); - return; + _ => () } x.args.iter_mut().for_each(|a| optimize_expr(a, state, false)); @@ -1035,7 +1039,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) { if !has_script_fn { let arg_values = &mut x.args.iter().map(|e| match e { Expr::Stack(slot, _) => x.constants[*slot].clone(), - _ => e.get_literal_value().expect("`e` is constant") + _ => e.get_literal_value().expect("constant") }).collect::>(); let result = match x.name.as_str() { diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 02955b09..5cf314bc 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -45,24 +45,19 @@ mod string_functions { s } - #[rhai_fn(name = "+", name = "append")] + #[rhai_fn(name = "append")] pub fn add_append_str(string1: ImmutableString, string2: ImmutableString) -> ImmutableString { string1 + string2 } - #[rhai_fn(name = "+", name = "append")] + #[rhai_fn(name = "append")] pub fn add_append_char(string: ImmutableString, character: char) -> ImmutableString { string + character } - - #[rhai_fn(name = "+", name = "append")] + #[rhai_fn(name = "append")] pub fn add_append_unit(string: ImmutableString, item: ()) -> ImmutableString { let _item = item; string } - #[rhai_fn(name = "+")] - pub fn add_prepend_unit(_item: (), string: ImmutableString) -> ImmutableString { - string - } #[rhai_fn(name = "len", get = "len")] pub fn len(string: &str) -> INT { diff --git a/src/parse.rs b/src/parse.rs index b953c09d..e490b18c 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -1573,13 +1573,18 @@ fn make_assignment_stmt( #[must_use] fn check_lvalue(expr: &Expr, parent_is_dot: bool) -> Option { match expr { - Expr::Index(x, _, _) | Expr::Dot(x, _, _) if parent_is_dot => match x.lhs { - Expr::Property(_) => check_lvalue(&x.rhs, matches!(expr, Expr::Dot(_, _, _))), + Expr::Index(x, term, _) | Expr::Dot(x, term, _) if parent_is_dot => match x.lhs { + Expr::Property(_) if !term => { + check_lvalue(&x.rhs, matches!(expr, Expr::Dot(_, _, _))) + } + Expr::Property(_) => None, + // Anything other than a property after dotting (e.g. a method call) is not an l-value ref e => Some(e.position()), }, - Expr::Index(x, _, _) | Expr::Dot(x, _, _) => match x.lhs { + Expr::Index(x, term, _) | Expr::Dot(x, term, _) => match x.lhs { Expr::Property(_) => unreachable!("unexpected Expr::Property in indexing"), - _ => check_lvalue(&x.rhs, matches!(expr, Expr::Dot(_, _, _))), + _ if !term => check_lvalue(&x.rhs, matches!(expr, Expr::Dot(_, _, _))), + _ => None, }, Expr::Property(_) if parent_is_dot => None, Expr::Property(_) => unreachable!("unexpected Expr::Property in indexing"), @@ -1619,19 +1624,30 @@ fn make_assignment_stmt( } } // xxx[???]... = rhs, xxx.prop... = rhs - Expr::Index(ref x, _, _) | Expr::Dot(ref x, _, _) => { - match check_lvalue(&x.rhs, matches!(lhs, Expr::Dot(_, _, _))) { - None => match x.lhs { - // var[???] = rhs, var.??? = rhs - Expr::Variable(_, _, _) => { - Ok(Stmt::Assignment((lhs, op_info, rhs).into(), op_pos)) + Expr::Index(ref x, term, _) | Expr::Dot(ref x, term, _) => { + let valid_lvalue = if term { + None + } else { + check_lvalue(&x.rhs, matches!(lhs, Expr::Dot(_, _, _))) + }; + + match valid_lvalue { + None => { + match x.lhs { + // var[???] = rhs, var.??? = rhs + Expr::Variable(_, _, _) => { + Ok(Stmt::Assignment((lhs, op_info, rhs).into(), op_pos)) + } + // expr[???] = rhs, expr.??? = rhs + ref expr => { + Err(PERR::AssignmentToInvalidLHS("".to_string()) + .into_err(expr.position())) + } } - // expr[???] = rhs, expr.??? = rhs - ref expr => { - Err(PERR::AssignmentToInvalidLHS("".to_string()).into_err(expr.position())) - } - }, - Some(pos) => Err(PERR::AssignmentToInvalidLHS("".to_string()).into_err(pos)), + } + Some(err_pos) => { + Err(PERR::AssignmentToInvalidLHS("".to_string()).into_err(err_pos)) + } } } // ??? && ??? = rhs, ??? || ??? = rhs diff --git a/src/scope.rs b/src/scope.rs index 6f217481..4064fe6d 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -307,10 +307,11 @@ impl<'a> Scope<'a> { pub(crate) fn get_index(&self, name: &str) -> Option<(usize, AccessMode)> { self.names .iter() - .enumerate() .rev() // Always search a Scope in reverse order + .enumerate() .find_map(|(index, (key, _))| { if name == key.as_ref() { + let index = self.len() - 1 - index; Some((index, self.values[index].access_mode())) } else { None @@ -334,10 +335,14 @@ impl<'a> Scope<'a> { pub fn get_value(&self, name: &str) -> Option { self.names .iter() - .enumerate() .rev() + .enumerate() .find(|(_, (key, _))| name == key.as_ref()) - .and_then(|(index, _)| self.values[index].flatten_clone().try_cast()) + .and_then(|(index, _)| { + self.values[self.len() - 1 - index] + .flatten_clone() + .try_cast() + }) } /// Check if the named entry in the [`Scope`] is constant. /// @@ -516,12 +521,14 @@ impl<'a> Scope<'a> { self.names .iter() - .enumerate() .rev() - .for_each(|(i, (name, alias))| { + .enumerate() + .for_each(|(index, (name, alias))| { if !entries.names.iter().any(|(key, _)| key == name) { entries.names.push((name.clone(), alias.clone())); - entries.values.push(self.values[i].clone()); + entries + .values + .push(self.values[self.len() - 1 - index].clone()); } });