From 41006455124eeece0e28c03676c34d4a6baa127d Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 27 Oct 2022 20:42:10 +0800 Subject: [PATCH] Move pure checking out of functions. --- CHANGELOG.md | 1 + Cargo.toml | 2 +- codegen/src/function.rs | 14 ++------ codegen/src/test/function.rs | 11 +++++-- codegen/src/test/module.rs | 64 ++++++++++++++---------------------- src/bin/rhai-repl.rs | 8 ++++- src/eval/target.rs | 11 ------- src/func/call.rs | 24 +++++++------- src/func/plugin.rs | 10 ++++++ src/func/register.rs | 45 ++++++++++++------------- src/types/error.rs | 32 ++++++++++++++++-- 11 files changed, 120 insertions(+), 102 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61d09c7d..49aa5966 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,6 +54,7 @@ Enhancements ------------ * `parse_json` function is added to parse a JSON string into an object map. +* `Error::ErrorNonPureMethodCallOnConstant` is added which is raised when a non-pure method is called on a constant value. Version 1.10.1 diff --git a/Cargo.toml b/Cargo.toml index d52f8b30..3999b7de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = [".", "codegen"] [package] name = "rhai" -version = "1.10.1" +version = "1.11.0" rust-version = "1.61.0" edition = "2018" resolver = "2" diff --git a/codegen/src/function.rs b/codegen/src/function.rs index d356645e..833e3d98 100644 --- a/codegen/src/function.rs +++ b/codegen/src/function.rs @@ -673,6 +673,7 @@ impl ExportedFn { let sig_name = self.name().clone(); let arg_count = self.arg_count(); let is_method_call = self.mutable_receiver(); + let is_pure = !self.mutable_receiver() || self.params().pure.is_some(); let mut unpack_statements = Vec::new(); let mut unpack_exprs = Vec::new(); @@ -713,18 +714,6 @@ impl ExportedFn { }) .unwrap(), ); - if self.params().pure.is_none() { - let arg_lit_str = - syn::LitStr::new(&pat.to_token_stream().to_string(), pat.span()); - unpack_statements.push( - syn::parse2::(quote! { - if args[0usize].is_read_only() { - return Err(EvalAltResult::ErrorAssignmentToConstant(#arg_lit_str.to_string(), Position::NONE).into()); - } - }) - .unwrap(), - ); - } #[cfg(feature = "metadata")] input_type_names.push(arg_name); input_type_exprs.push( @@ -877,6 +866,7 @@ impl ExportedFn { } #[inline(always)] fn is_method_call(&self) -> bool { #is_method_call } + #[inline(always)] fn is_pure(&self) -> bool { #is_pure } } } } diff --git a/codegen/src/test/function.rs b/codegen/src/test/function.rs index 8465cd61..05d99130 100644 --- a/codegen/src/test/function.rs +++ b/codegen/src/test/function.rs @@ -285,6 +285,7 @@ mod generate_tests { } #[inline(always)] fn is_method_call(&self) -> bool { false } + #[inline(always)] fn is_pure(&self) -> bool { true } } #[allow(unused)] #[doc(hidden)] @@ -323,6 +324,7 @@ mod generate_tests { } #[inline(always)] fn is_method_call(&self) -> bool { false } + #[inline(always)] fn is_pure(&self) -> bool { true } } #[allow(unused)] #[doc(hidden)] @@ -361,6 +363,7 @@ mod generate_tests { } #[inline(always)] fn is_method_call(&self) -> bool { false } + #[inline(always)] fn is_pure(&self) -> bool { true } } #[allow(unused)] #[doc(hidden)] @@ -401,6 +404,7 @@ mod generate_tests { } #[inline(always)] fn is_method_call(&self) -> bool { false } + #[inline(always)] fn is_pure(&self) -> bool { true } } #[allow(unused)] #[doc(hidden)] @@ -434,6 +438,7 @@ mod generate_tests { } #[inline(always)] fn is_method_call(&self) -> bool { false } + #[inline(always)] fn is_pure(&self) -> bool { true } } }; @@ -467,6 +472,7 @@ mod generate_tests { } #[inline(always)] fn is_method_call(&self) -> bool { false } + #[inline(always)] fn is_pure(&self) -> bool { true } } #[allow(unused)] #[doc(hidden)] @@ -500,15 +506,13 @@ mod generate_tests { impl PluginFunction for Token { #[inline(always)] fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { - if args[0usize].is_read_only() { - return Err(EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into()); - } let arg1 = mem::take(args[1usize]).cast::(); let arg0 = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(increment(arg0, arg1))) } #[inline(always)] fn is_method_call(&self) -> bool { true } + #[inline(always)] fn is_pure(&self) -> bool { false } } #[allow(unused)] #[doc(hidden)] @@ -548,6 +552,7 @@ mod generate_tests { } #[inline(always)] fn is_method_call(&self) -> bool { false } + #[inline(always)] fn is_pure(&self) -> bool { true } } #[allow(unused)] #[doc(hidden)] diff --git a/codegen/src/test/module.rs b/codegen/src/test/module.rs index 881746b9..0a279452 100644 --- a/codegen/src/test/module.rs +++ b/codegen/src/test/module.rs @@ -390,6 +390,7 @@ mod generate_tests { } #[inline(always)] fn is_method_call(&self) -> bool { false } + #[inline(always)] fn is_pure(&self) -> bool { true } } } }; @@ -467,6 +468,7 @@ mod generate_tests { } #[inline(always)] fn is_method_call(&self) -> bool { false } + #[inline(always)] fn is_pure(&self) -> bool { true } } } }; @@ -525,6 +527,7 @@ mod generate_tests { } #[inline(always)] fn is_method_call(&self) -> bool { false } + #[inline(always)] fn is_pure(&self) -> bool { true } } } }; @@ -582,6 +585,7 @@ mod generate_tests { } #[inline(always)] fn is_method_call(&self) -> bool { false } + #[inline(always)] fn is_pure(&self) -> bool { true } } } }; @@ -653,6 +657,7 @@ mod generate_tests { } #[inline(always)] fn is_method_call(&self) -> bool { false } + #[inline(always)] fn is_pure(&self) -> bool { true } } #[allow(non_camel_case_types)] @@ -672,6 +677,7 @@ mod generate_tests { } #[inline(always)] fn is_method_call(&self) -> bool { false } + #[inline(always)] fn is_pure(&self) -> bool { true } } } }; @@ -730,6 +736,7 @@ mod generate_tests { } #[inline(always)] fn is_method_call(&self) -> bool { false } + #[inline(always)] fn is_pure(&self) -> bool { true } } } }; @@ -795,6 +802,7 @@ mod generate_tests { } #[inline(always)] fn is_method_call(&self) -> bool { false } + #[inline(always)] fn is_pure(&self) -> bool { true } } } }; @@ -864,14 +872,12 @@ mod generate_tests { impl PluginFunction for get_mystic_number_token { #[inline(always)] fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { - if args[0usize].is_read_only() { - return Err(EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into()); - } let arg0 = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(get_mystic_number(arg0))) } #[inline(always)] fn is_method_call(&self) -> bool { true } + #[inline(always)] fn is_pure(&self) -> bool { false } } } }; @@ -1080,6 +1086,7 @@ mod generate_tests { } #[inline(always)] fn is_method_call(&self) -> bool { false } + #[inline(always)] fn is_pure(&self) -> bool { true } } } }; @@ -1170,6 +1177,7 @@ mod generate_tests { } #[inline(always)] fn is_method_call(&self) -> bool { false } + #[inline(always)] fn is_pure(&self) -> bool { true } } } }; @@ -1227,6 +1235,7 @@ mod generate_tests { } #[inline(always)] fn is_method_call(&self) -> bool { false } + #[inline(always)] fn is_pure(&self) -> bool { true } } } }; @@ -1286,6 +1295,7 @@ mod generate_tests { } #[inline(always)] fn is_method_call(&self) -> bool { true } + #[inline(always)] fn is_pure(&self) -> bool { true } } } }; @@ -1338,14 +1348,12 @@ mod generate_tests { impl PluginFunction for increment_token { #[inline(always)] fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { - if args[0usize].is_read_only() { - return Err(EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into()); - } let arg0 = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(increment(arg0))) } #[inline(always)] fn is_method_call(&self) -> bool { true } + #[inline(always)] fn is_pure(&self) -> bool { false } } } }; @@ -1401,14 +1409,12 @@ mod generate_tests { impl PluginFunction for increment_token { #[inline(always)] fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { - if args[0usize].is_read_only() { - return Err(EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into()); - } let arg0 = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(increment(arg0))) } #[inline(always)] fn is_method_call(&self) -> bool { true } + #[inline(always)] fn is_pure(&self) -> bool { false } } } #[allow(unused_imports)] @@ -1487,14 +1493,12 @@ mod generate_tests { impl PluginFunction for increment_token { #[inline(always)] fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { - if args[0usize].is_read_only() { - return Err(EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into()); - } let arg0 = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(increment(arg0))) } #[inline(always)] fn is_method_call(&self) -> bool { true } + #[inline(always)] fn is_pure(&self) -> bool { false } } } #[allow(unused_imports)] @@ -1574,14 +1578,12 @@ mod generate_tests { impl PluginFunction for int_foo_token { #[inline(always)] fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { - if args[0usize].is_read_only() { - return Err(EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into()); - } let arg0 = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(int_foo(arg0))) } #[inline(always)] fn is_method_call(&self) -> bool { true } + #[inline(always)] fn is_pure(&self) -> bool { false } } } }; @@ -1638,14 +1640,12 @@ mod generate_tests { impl PluginFunction for int_foo_token { #[inline(always)] fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { - if args[0usize].is_read_only() { - return Err(EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into()); - } let arg0 = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(int_foo(arg0))) } #[inline(always)] fn is_method_call(&self) -> bool { true } + #[inline(always)] fn is_pure(&self) -> bool { false } } } }; @@ -1699,15 +1699,13 @@ mod generate_tests { impl PluginFunction for int_foo_token { #[inline(always)] fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { - if args[0usize].is_read_only() { - return Err(EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into()); - } let arg1 = mem::take(args[1usize]).cast::(); let arg0 = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(int_foo(arg0, arg1))) } #[inline(always)] fn is_method_call(&self) -> bool { true } + #[inline(always)] fn is_pure(&self) -> bool { false } } } }; @@ -1764,15 +1762,13 @@ mod generate_tests { impl PluginFunction for int_foo_token { #[inline(always)] fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { - if args[0usize].is_read_only() { - return Err(EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into()); - } let arg1 = mem::take(args[1usize]).cast::(); let arg0 = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(int_foo(arg0, arg1))) } #[inline(always)] fn is_method_call(&self) -> bool { true } + #[inline(always)] fn is_pure(&self) -> bool { false } } } }; @@ -1826,15 +1822,13 @@ mod generate_tests { impl PluginFunction for get_by_index_token { #[inline(always)] fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { - if args[0usize].is_read_only() { - return Err(EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into()); - } let arg1 = mem::take(args[1usize]).cast::(); let arg0 = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(get_by_index(arg0, arg1))) } #[inline(always)] fn is_method_call(&self) -> bool { true } + #[inline(always)] fn is_pure(&self) -> bool { false } } } }; @@ -1896,15 +1890,13 @@ mod generate_tests { impl PluginFunction for get_by_index_token { #[inline(always)] fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { - if args[0usize].is_read_only() { - return Err(EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into()); - } let arg1 = mem::take(args[1usize]).cast::(); let arg0 = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(get_by_index(arg0, arg1))) } #[inline(always)] fn is_method_call(&self) -> bool { true } + #[inline(always)] fn is_pure(&self) -> bool { false } } } }; @@ -1961,15 +1953,13 @@ mod generate_tests { impl PluginFunction for get_by_index_token { #[inline(always)] fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { - if args[0usize].is_read_only() { - return Err(EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into()); - } let arg1 = mem::take(args[1usize]).cast::(); let arg0 = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(get_by_index(arg0, arg1))) } #[inline(always)] fn is_method_call(&self) -> bool { true } + #[inline(always)] fn is_pure(&self) -> bool { false } } } }; @@ -2023,9 +2013,6 @@ mod generate_tests { impl PluginFunction for set_by_index_token { #[inline(always)] fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { - if args[0usize].is_read_only() { - return Err(EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into()); - } let arg1 = mem::take(args[1usize]).cast::(); let arg2 = mem::take(args[2usize]).cast::(); let arg0 = &mut args[0usize].write_lock::().unwrap(); @@ -2033,6 +2020,7 @@ mod generate_tests { } #[inline(always)] fn is_method_call(&self) -> bool { true } + #[inline(always)] fn is_pure(&self) -> bool { false } } } }; @@ -2089,9 +2077,6 @@ mod generate_tests { impl PluginFunction for set_by_index_token { #[inline(always)] fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { - if args[0usize].is_read_only() { - return Err(EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into()); - } let arg1 = mem::take(args[1usize]).cast::(); let arg2 = mem::take(args[2usize]).cast::(); let arg0 = &mut args[0usize].write_lock::().unwrap(); @@ -2099,6 +2084,7 @@ mod generate_tests { } #[inline(always)] fn is_method_call(&self) -> bool { true } + #[inline(always)] fn is_pure(&self) -> bool { false } } } }; diff --git a/src/bin/rhai-repl.rs b/src/bin/rhai-repl.rs index 71801d82..ea452269 100644 --- a/src/bin/rhai-repl.rs +++ b/src/bin/rhai-repl.rs @@ -309,7 +309,13 @@ fn main() { } // Register sample functions - engine.register_global_module(exported_module!(sample_functions).into()); + engine + .register_global_module(exported_module!(sample_functions).into()) + .register_get_set( + "test", + |x: &mut INT| *x % 2 == 0, + |x: &mut INT, y: bool| if y { *x *= 2 } else { *x /= 2 }, + ); // Create scope let mut scope = Scope::new(); diff --git a/src/eval/target.rs b/src/eval/target.rs index c48aa62a..a12aa96c 100644 --- a/src/eval/target.rs +++ b/src/eval/target.rs @@ -239,17 +239,6 @@ impl<'a> Target<'a> { _ => None, } } - /// Convert a shared or reference [`Target`] into a target with an owned value. - #[inline(always)] - #[must_use] - pub fn into_owned(self) -> Self { - match self { - Self::RefMut(r) => Self::TempValue(r.clone()), - #[cfg(not(feature = "no_closure"))] - Self::SharedValue { value, .. } => Self::TempValue(value), - _ => self, - } - } /// Get the source [`Dynamic`] of the [`Target`]. #[allow(dead_code)] #[inline] diff --git a/src/func/call.rs b/src/func/call.rs index 9e0d6c31..758767fb 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -418,7 +418,12 @@ impl Engine { let context = (self, name, source, &*global, lib, pos, level).into(); let result = if func.is_plugin_fn() { - func.get_plugin_fn().unwrap().call(context, args) + let f = func.get_plugin_fn().unwrap(); + if !f.is_pure() && !args.is_empty() && args[0].is_read_only() { + Err(ERR::ErrorNonPureMethodCallOnConstant(name.to_string(), pos).into()) + } else { + f.call(context, args) + } } else { func.get_native_fn().unwrap()(context, args) }; @@ -1229,13 +1234,9 @@ impl Engine { .map(|(value, ..)| arg_values.push(value.flatten())) })?; - let (mut target, _pos) = + let (target, _pos) = self.search_namespace(scope, global, lib, this_ptr, first_expr, level)?; - if target.is_read_only() { - target = target.into_owned(); - } - self.track_operation(global, _pos)?; #[cfg(not(feature = "no_closure"))] @@ -1425,11 +1426,12 @@ impl Engine { Some(f) if f.is_plugin_fn() => { let context = (self, fn_name, module.id(), &*global, lib, pos, level).into(); - let result = f - .get_plugin_fn() - .expect("plugin function") - .clone() - .call(context, &mut args); + let f = f.get_plugin_fn().expect("plugin function"); + let result = if !f.is_pure() && !args.is_empty() && args[0].is_read_only() { + Err(ERR::ErrorNonPureMethodCallOnConstant(fn_name.to_string(), pos).into()) + } else { + f.call(context, &mut args) + }; self.check_return_value(result, pos) } diff --git a/src/func/plugin.rs b/src/func/plugin.rs index 7c4f84fe..b4a9b9d0 100644 --- a/src/func/plugin.rs +++ b/src/func/plugin.rs @@ -29,4 +29,14 @@ pub trait PluginFunction { /// Is this plugin function a method? #[must_use] fn is_method_call(&self) -> bool; + + /// Is this plugin function pure? + /// + /// This defaults to `true` such that any old implementation that has constant-checking code + /// inside the function itself will continue to work. + #[inline(always)] + #[must_use] + fn is_pure(&self) -> bool { + true + } } diff --git a/src/func/register.rs b/src/func/register.rs index 928b08b0..8899548b 100644 --- a/src/func/register.rs +++ b/src/func/register.rs @@ -96,26 +96,27 @@ pub trait RegisterNativeFunction { const EXPECT_ARGS: &str = "arguments"; macro_rules! check_constant { - ($ctx:ident, $args:ident) => { + ($abi:ident, $ctx:ident, $args:ident) => { #[cfg(any(not(feature = "no_object"), not(feature = "no_index")))] - { - let args_len = $args.len(); - - if args_len > 0 && $args[0].is_read_only() { - let deny = match $ctx.fn_name() { - #[cfg(not(feature = "no_object"))] - f if args_len == 2 && f.starts_with(crate::engine::FN_SET) => true, - #[cfg(not(feature = "no_index"))] - crate::engine::FN_IDX_SET if args_len == 3 => true, - _ => false, - }; - if deny { - return Err(crate::ERR::ErrorAssignmentToConstant( - String::new(), - crate::Position::NONE, - ) - .into()); + if stringify!($abi) == "Method" && !$args.is_empty() { + let deny = match $args.len() { + #[cfg(not(feature = "no_index"))] + 3 if $ctx.fn_name() == crate::engine::FN_IDX_SET && $args[0].is_read_only() => true, + #[cfg(not(feature = "no_object"))] + 2 if $ctx.fn_name().starts_with(crate::engine::FN_SET) + && $args[0].is_read_only() => + { + true } + _ => false, + }; + + if deny { + return Err(crate::ERR::ErrorNonPureMethodCallOnConstant( + $ctx.fn_name().to_string(), + crate::Position::NONE, + ) + .into()); } } }; @@ -144,7 +145,7 @@ macro_rules! def_register { #[inline(always)] fn into_callable_function(self) -> CallableFunction { CallableFunction::$abi(Shared::new(move |_ctx: NativeCallContext, args: &mut FnCallArgs| { // The arguments are assumed to be of the correct number and types! - check_constant!(_ctx, args); + check_constant!($abi, _ctx, args); let mut _drain = args.iter_mut(); $($let $par = ($clone)(_drain.next().expect(EXPECT_ARGS)); )* @@ -169,7 +170,7 @@ macro_rules! def_register { #[inline(always)] fn into_callable_function(self) -> CallableFunction { CallableFunction::$abi(Shared::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| { // The arguments are assumed to be of the correct number and types! - check_constant!(ctx, args); + check_constant!($abi, ctx, args); let mut _drain = args.iter_mut(); $($let $par = ($clone)(_drain.next().expect(EXPECT_ARGS)); )* @@ -195,7 +196,7 @@ macro_rules! def_register { #[inline(always)] fn into_callable_function(self) -> CallableFunction { CallableFunction::$abi(Shared::new(move |_ctx: NativeCallContext, args: &mut FnCallArgs| { // The arguments are assumed to be of the correct number and types! - check_constant!(_ctx, args); + check_constant!($abi, _ctx, args); let mut _drain = args.iter_mut(); $($let $par = ($clone)(_drain.next().expect(EXPECT_ARGS)); )* @@ -218,7 +219,7 @@ macro_rules! def_register { #[inline(always)] fn into_callable_function(self) -> CallableFunction { CallableFunction::$abi(Shared::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| { // The arguments are assumed to be of the correct number and types! - check_constant!(ctx, args); + check_constant!($abi, ctx, args); let mut _drain = args.iter_mut(); $($let $par = ($clone)(_drain.next().expect(EXPECT_ARGS)); )* diff --git a/src/types/error.rs b/src/types/error.rs index 89bbedd8..e18414de 100644 --- a/src/types/error.rs +++ b/src/types/error.rs @@ -1,5 +1,6 @@ //! Module containing error definitions for the evaluation process. +use crate::engine::{FN_GET, FN_IDX_GET, FN_IDX_SET, FN_SET}; use crate::{Dynamic, ImmutableString, ParseErrorType, Position, INT}; #[cfg(feature = "no_std")] use core_error::Error; @@ -84,6 +85,8 @@ pub enum EvalAltResult { /// Data race detected when accessing a variable. Wrapped value is the variable name. ErrorDataRace(String, Position), + /// Calling a non-pure method on a constant. Wrapped value is the function name. + ErrorNonPureMethodCallOnConstant(String, Position), /// Assignment to a constant variable. Wrapped value is the variable name. ErrorAssignmentToConstant(String, Position), /// Inappropriate property access. Wrapped value is the property name. @@ -181,7 +184,29 @@ impl fmt::Display for EvalAltResult { } Self::ErrorRuntime(d, ..) => write!(f, "Runtime error: {d}")?, - Self::ErrorAssignmentToConstant(s, ..) => write!(f, "Cannot modify constant: {s}")?, + Self::ErrorNonPureMethodCallOnConstant(s, ..) if s.starts_with(FN_GET) => { + let prop = &s[FN_GET.len()..]; + write!( + f, + "Property {prop} is not pure and cannot be accessed on a constant" + )? + } + Self::ErrorNonPureMethodCallOnConstant(s, ..) if s.starts_with(FN_SET) => { + let prop = &s[FN_SET.len()..]; + write!(f, "Cannot modify property {prop} of constant")? + } + Self::ErrorNonPureMethodCallOnConstant(s, ..) if s == FN_IDX_GET => write!( + f, + "Indexer is not pure and cannot be accessed on a constant" + )?, + Self::ErrorNonPureMethodCallOnConstant(s, ..) if s == FN_IDX_SET => { + write!(f, "Cannot assign to indexer of constant")? + } + Self::ErrorNonPureMethodCallOnConstant(s, ..) => { + write!(f, "Non-pure method {s} cannot be called on constant")? + } + + Self::ErrorAssignmentToConstant(s, ..) => write!(f, "Cannot modify constant {s}")?, Self::ErrorMismatchOutputType(e, a, ..) => match (a.as_str(), e.as_str()) { ("", e) => write!(f, "Output type incorrect, expecting {e}"), (a, "") => write!(f, "Output type incorrect: {a}"), @@ -296,6 +321,7 @@ impl EvalAltResult { | Self::ErrorIndexNotFound(..) | Self::ErrorModuleNotFound(..) | Self::ErrorDataRace(..) + | Self::ErrorNonPureMethodCallOnConstant(..) | Self::ErrorAssignmentToConstant(..) | Self::ErrorMismatchOutputType(..) | Self::ErrorDotExpr(..) @@ -364,7 +390,7 @@ impl EvalAltResult { | Self::ErrorStackOverflow(..) | Self::ErrorRuntime(..) => (), - Self::ErrorFunctionNotFound(f, ..) => { + Self::ErrorFunctionNotFound(f, ..) | Self::ErrorNonPureMethodCallOnConstant(f, ..) => { map.insert("function".into(), f.into()); } Self::ErrorInFunctionCall(f, s, ..) => { @@ -459,6 +485,7 @@ impl EvalAltResult { | Self::ErrorIndexNotFound(.., pos) | Self::ErrorModuleNotFound(.., pos) | Self::ErrorDataRace(.., pos) + | Self::ErrorNonPureMethodCallOnConstant(.., pos) | Self::ErrorAssignmentToConstant(.., pos) | Self::ErrorMismatchOutputType(.., pos) | Self::ErrorDotExpr(.., pos) @@ -518,6 +545,7 @@ impl EvalAltResult { | Self::ErrorIndexNotFound(.., pos) | Self::ErrorModuleNotFound(.., pos) | Self::ErrorDataRace(.., pos) + | Self::ErrorNonPureMethodCallOnConstant(.., pos) | Self::ErrorAssignmentToConstant(.., pos) | Self::ErrorMismatchOutputType(.., pos) | Self::ErrorDotExpr(.., pos)