Merge branch 'v1.1-fixes'
This commit is contained in:
commit
ff9ac41da2
@ -33,7 +33,9 @@ Version 1.1.1
|
|||||||
Bug fixes
|
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.
|
* 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
|
Version 1.1.0
|
||||||
|
@ -266,6 +266,24 @@ pub fn get_builtin_binary_op_fn(
|
|||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
// () op string
|
||||||
|
if types_pair == (TypeId::of::<()>(), TypeId::of::<ImmutableString>()) {
|
||||||
|
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::<ImmutableString>(), TypeId::of::<()>()) {
|
||||||
|
return match op {
|
||||||
|
"+" => Some(|_, args| Ok(args[0].clone())),
|
||||||
|
"==" | ">" | ">=" | "<" | "<=" => Some(|_, _| Ok(Dynamic::FALSE)),
|
||||||
|
"!=" => Some(|_, _| Ok(Dynamic::TRUE)),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// map op string
|
// map op string
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
@ -732,7 +732,7 @@ impl Engine {
|
|||||||
return Ok((Dynamic::UNIT, false));
|
return Ok((Dynamic::UNIT, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
let scope: &mut Scope = &mut Default::default();
|
let scope = &mut Scope::new();
|
||||||
|
|
||||||
// Move captured variables into scope
|
// Move captured variables into scope
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
|
@ -482,9 +482,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
|
|
||||||
// switch const { ... }
|
// switch const { ... }
|
||||||
Stmt::Switch(match_expr, x, pos) if match_expr.is_constant() => {
|
Stmt::Switch(match_expr, x, pos) if match_expr.is_constant() => {
|
||||||
let value = match_expr
|
let value = match_expr.get_literal_value().expect("constant");
|
||||||
.get_literal_value()
|
|
||||||
.expect("`match_expr` is constant");
|
|
||||||
let hasher = &mut get_hasher();
|
let hasher = &mut get_hasher();
|
||||||
value.hash(hasher);
|
value.hash(hasher);
|
||||||
let hash = hasher.finish();
|
let hash = hasher.finish();
|
||||||
@ -878,7 +876,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
|
|||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Array(_, _) if expr.is_constant() => {
|
Expr::Array(_, _) if expr.is_constant() => {
|
||||||
state.set_dirty();
|
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 .. ]
|
// [ items .. ]
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[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"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Expr::Map(_, _) if expr.is_constant() => {
|
Expr::Map(_, _) if expr.is_constant() => {
|
||||||
state.set_dirty();
|
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, .. }
|
// #{ key:value, .. }
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
@ -981,32 +979,38 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
|
|||||||
=> {
|
=> {
|
||||||
let arg_values = &mut x.args.iter().map(|e| match e {
|
let arg_values = &mut x.args.iter().map(|e| match e {
|
||||||
Expr::Stack(slot, _) => x.constants[*slot].clone(),
|
Expr::Stack(slot, _) => x.constants[*slot].clone(),
|
||||||
_ => e.get_literal_value().expect("`e` is constant")
|
_ => e.get_literal_value().expect("constant")
|
||||||
}).collect::<StaticVec<_>>();
|
}).collect::<StaticVec<_>>();
|
||||||
|
|
||||||
let arg_types: StaticVec<_> = arg_values.iter().map(Dynamic::type_id).collect();
|
let arg_types: StaticVec<_> = arg_values.iter().map(Dynamic::type_id).collect();
|
||||||
|
|
||||||
let result = match x.name.as_str() {
|
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()),
|
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"))]
|
#[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.
|
// Overloaded operators can override built-in.
|
||||||
_ if x.args.len() == 2 && !state.has_native_fn(x.hashes.native, arg_types.as_ref()) => {
|
_ 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| {
|
.and_then(|f| {
|
||||||
let ctx = (state.engine, x.name.as_ref(), state.lib).into();
|
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");
|
let (first, second) = arg_values.split_first_mut().expect("`arg_values` is not empty");
|
||||||
(f)(ctx, &mut [ first, &mut second[0] ]).ok()
|
(f)(ctx, &mut [ first, &mut second[0] ]).ok()
|
||||||
})
|
}) {
|
||||||
}
|
|
||||||
_ => None
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(result) = result {
|
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
*expr = Expr::from_dynamic(result, *pos);
|
*expr = Expr::from_dynamic(result, *pos);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
|
|
||||||
x.args.iter_mut().for_each(|a| optimize_expr(a, state, false));
|
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 {
|
if !has_script_fn {
|
||||||
let arg_values = &mut x.args.iter().map(|e| match e {
|
let arg_values = &mut x.args.iter().map(|e| match e {
|
||||||
Expr::Stack(slot, _) => x.constants[*slot].clone(),
|
Expr::Stack(slot, _) => x.constants[*slot].clone(),
|
||||||
_ => e.get_literal_value().expect("`e` is constant")
|
_ => e.get_literal_value().expect("constant")
|
||||||
}).collect::<StaticVec<_>>();
|
}).collect::<StaticVec<_>>();
|
||||||
|
|
||||||
let result = match x.name.as_str() {
|
let result = match x.name.as_str() {
|
||||||
|
@ -45,24 +45,19 @@ mod string_functions {
|
|||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rhai_fn(name = "+", name = "append")]
|
#[rhai_fn(name = "append")]
|
||||||
pub fn add_append_str(string1: ImmutableString, string2: ImmutableString) -> ImmutableString {
|
pub fn add_append_str(string1: ImmutableString, string2: ImmutableString) -> ImmutableString {
|
||||||
string1 + string2
|
string1 + string2
|
||||||
}
|
}
|
||||||
#[rhai_fn(name = "+", name = "append")]
|
#[rhai_fn(name = "append")]
|
||||||
pub fn add_append_char(string: ImmutableString, character: char) -> ImmutableString {
|
pub fn add_append_char(string: ImmutableString, character: char) -> ImmutableString {
|
||||||
string + character
|
string + character
|
||||||
}
|
}
|
||||||
|
#[rhai_fn(name = "append")]
|
||||||
#[rhai_fn(name = "+", name = "append")]
|
|
||||||
pub fn add_append_unit(string: ImmutableString, item: ()) -> ImmutableString {
|
pub fn add_append_unit(string: ImmutableString, item: ()) -> ImmutableString {
|
||||||
let _item = item;
|
let _item = item;
|
||||||
string
|
string
|
||||||
}
|
}
|
||||||
#[rhai_fn(name = "+")]
|
|
||||||
pub fn add_prepend_unit(_item: (), string: ImmutableString) -> ImmutableString {
|
|
||||||
string
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rhai_fn(name = "len", get = "len")]
|
#[rhai_fn(name = "len", get = "len")]
|
||||||
pub fn len(string: &str) -> INT {
|
pub fn len(string: &str) -> INT {
|
||||||
|
36
src/parse.rs
36
src/parse.rs
@ -1573,13 +1573,18 @@ fn make_assignment_stmt(
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
fn check_lvalue(expr: &Expr, parent_is_dot: bool) -> Option<Position> {
|
fn check_lvalue(expr: &Expr, parent_is_dot: bool) -> Option<Position> {
|
||||||
match expr {
|
match expr {
|
||||||
Expr::Index(x, _, _) | Expr::Dot(x, _, _) if parent_is_dot => match x.lhs {
|
Expr::Index(x, term, _) | Expr::Dot(x, term, _) if parent_is_dot => match x.lhs {
|
||||||
Expr::Property(_) => check_lvalue(&x.rhs, matches!(expr, Expr::Dot(_, _, _))),
|
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()),
|
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"),
|
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(_) if parent_is_dot => None,
|
||||||
Expr::Property(_) => unreachable!("unexpected Expr::Property in indexing"),
|
Expr::Property(_) => unreachable!("unexpected Expr::Property in indexing"),
|
||||||
@ -1619,19 +1624,30 @@ fn make_assignment_stmt(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// xxx[???]... = rhs, xxx.prop... = rhs
|
// xxx[???]... = rhs, xxx.prop... = rhs
|
||||||
Expr::Index(ref x, _, _) | Expr::Dot(ref x, _, _) => {
|
Expr::Index(ref x, term, _) | Expr::Dot(ref x, term, _) => {
|
||||||
match check_lvalue(&x.rhs, matches!(lhs, Expr::Dot(_, _, _))) {
|
let valid_lvalue = if term {
|
||||||
None => match x.lhs {
|
None
|
||||||
|
} else {
|
||||||
|
check_lvalue(&x.rhs, matches!(lhs, Expr::Dot(_, _, _)))
|
||||||
|
};
|
||||||
|
|
||||||
|
match valid_lvalue {
|
||||||
|
None => {
|
||||||
|
match x.lhs {
|
||||||
// var[???] = rhs, var.??? = rhs
|
// var[???] = rhs, var.??? = rhs
|
||||||
Expr::Variable(_, _, _) => {
|
Expr::Variable(_, _, _) => {
|
||||||
Ok(Stmt::Assignment((lhs, op_info, rhs).into(), op_pos))
|
Ok(Stmt::Assignment((lhs, op_info, rhs).into(), op_pos))
|
||||||
}
|
}
|
||||||
// expr[???] = rhs, expr.??? = rhs
|
// expr[???] = rhs, expr.??? = rhs
|
||||||
ref expr => {
|
ref expr => {
|
||||||
Err(PERR::AssignmentToInvalidLHS("".to_string()).into_err(expr.position()))
|
Err(PERR::AssignmentToInvalidLHS("".to_string())
|
||||||
|
.into_err(expr.position()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(err_pos) => {
|
||||||
|
Err(PERR::AssignmentToInvalidLHS("".to_string()).into_err(err_pos))
|
||||||
}
|
}
|
||||||
},
|
|
||||||
Some(pos) => Err(PERR::AssignmentToInvalidLHS("".to_string()).into_err(pos)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ??? && ??? = rhs, ??? || ??? = rhs
|
// ??? && ??? = rhs, ??? || ??? = rhs
|
||||||
|
19
src/scope.rs
19
src/scope.rs
@ -307,10 +307,11 @@ impl<'a> Scope<'a> {
|
|||||||
pub(crate) fn get_index(&self, name: &str) -> Option<(usize, AccessMode)> {
|
pub(crate) fn get_index(&self, name: &str) -> Option<(usize, AccessMode)> {
|
||||||
self.names
|
self.names
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
|
||||||
.rev() // Always search a Scope in reverse order
|
.rev() // Always search a Scope in reverse order
|
||||||
|
.enumerate()
|
||||||
.find_map(|(index, (key, _))| {
|
.find_map(|(index, (key, _))| {
|
||||||
if name == key.as_ref() {
|
if name == key.as_ref() {
|
||||||
|
let index = self.len() - 1 - index;
|
||||||
Some((index, self.values[index].access_mode()))
|
Some((index, self.values[index].access_mode()))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@ -334,10 +335,14 @@ impl<'a> Scope<'a> {
|
|||||||
pub fn get_value<T: Variant + Clone>(&self, name: &str) -> Option<T> {
|
pub fn get_value<T: Variant + Clone>(&self, name: &str) -> Option<T> {
|
||||||
self.names
|
self.names
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
|
||||||
.rev()
|
.rev()
|
||||||
|
.enumerate()
|
||||||
.find(|(_, (key, _))| name == key.as_ref())
|
.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.
|
/// Check if the named entry in the [`Scope`] is constant.
|
||||||
///
|
///
|
||||||
@ -516,12 +521,14 @@ impl<'a> Scope<'a> {
|
|||||||
|
|
||||||
self.names
|
self.names
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
|
||||||
.rev()
|
.rev()
|
||||||
.for_each(|(i, (name, alias))| {
|
.enumerate()
|
||||||
|
.for_each(|(index, (name, alias))| {
|
||||||
if !entries.names.iter().any(|(key, _)| key == name) {
|
if !entries.names.iter().any(|(key, _)| key == name) {
|
||||||
entries.names.push((name.clone(), alias.clone()));
|
entries.names.push((name.clone(), alias.clone()));
|
||||||
entries.values.push(self.values[i].clone());
|
entries
|
||||||
|
.values
|
||||||
|
.push(self.values[self.len() - 1 - index].clone());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user