From 582dd373d6ca8bff6de1ecd6c2a70f40eb569b2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rasmus=20L=C3=B6vegren?= Date: Tue, 16 Jan 2024 14:35:35 +0200 Subject: [PATCH] Allow disabling type safety checks for mutable HashMapContexts --- src/context/mod.rs | 26 ++++++++++++++++++++++++-- tests/integration.rs | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/src/context/mod.rs b/src/context/mod.rs index 3306e015..231f5209 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -37,6 +37,14 @@ pub trait ContextWithMutableVariables: Context { fn set_value(&mut self, _identifier: String, _value: Value) -> EvalexprResult<()> { Err(EvalexprError::ContextNotMutable) } + + /// Checks if type-safety assignment checks have been disabled. + fn are_type_safety_checks_disabled(&self) -> bool; + + /// Disables type-safety assignment checks if `disabled` is `true`, and enables them otherwise. + fn set_type_safety_checks_disabled(&mut self, _disabled: bool) -> EvalexprResult<()> { + Err(EvalexprError::ContextNotMutable) + } } /// A context that allows to assign to function identifiers. @@ -168,7 +176,7 @@ impl IterateVariablesContext for EmptyContextWithBuiltinFunctions { /// /// *Value and function mappings are stored independently, meaning that there can be a function and a value with the same identifier.* /// -/// This context is type-safe, meaning that an identifier that is assigned a value of some type once cannot be assigned a value of another type. +/// By default, this context is type-safe, meaning that an identifier that is assigned a value of some type once cannot be assigned a value of another type. #[derive(Clone, Debug, Default)] #[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))] pub struct HashMapContext { @@ -178,6 +186,9 @@ pub struct HashMapContext { /// True if builtin functions are disabled. without_builtin_functions: bool, + + /// True if type-unsafe assignments are allowed. + without_type_safety_check: bool, } impl HashMapContext { @@ -258,7 +269,9 @@ impl Context for HashMapContext { impl ContextWithMutableVariables for HashMapContext { fn set_value(&mut self, identifier: String, value: Value) -> EvalexprResult<()> { if let Some(existing_value) = self.variables.get_mut(&identifier) { - if ValueType::from(&existing_value) == ValueType::from(&value) { + if self.without_type_safety_check + || ValueType::from(&existing_value) == ValueType::from(&value) + { *existing_value = value; return Ok(()); } else { @@ -270,6 +283,15 @@ impl ContextWithMutableVariables for HashMapContext { self.variables.insert(identifier, value); Ok(()) } + + fn are_type_safety_checks_disabled(&self) -> bool { + self.without_type_safety_check + } + + fn set_type_safety_checks_disabled(&mut self, disabled: bool) -> EvalexprResult<()> { + self.without_type_safety_check = disabled; + Ok(()) + } } impl ContextWithMutableFunctions for HashMapContext { diff --git a/tests/integration.rs b/tests/integration.rs index 695056bc..d75cf168 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -1660,6 +1660,43 @@ fn test_hashmap_context_type_safety() { ); } +#[test] +fn test_disabled_hashmap_context_type_safety() { + let mut context = context_map! {"a" => 5, "b" => 5.0}.unwrap(); + assert!(!context.are_type_safety_checks_disabled()); + context.set_type_safety_checks_disabled(true).unwrap(); + assert!(context.are_type_safety_checks_disabled()); + + // Assignment to same type still works + assert_eq!( + eval_with_context_mut("a = 4", &mut context), + Ok(Value::Empty) + ); + assert_eq!(context.get_value("a").unwrap().as_int(), Ok(4)); + + // Assignment to another type works + assert_eq!( + eval_with_context_mut(r#"b = "foo""#, &mut context), + Ok(Value::Empty) + ); + assert_eq!( + context.get_value("b").unwrap().as_string(), + Ok("foo".into()) + ); + + // Assignment to empty value then another type works + assert_eq!( + eval_with_context_mut("c = ()", &mut context), + Ok(Value::Empty) + ); + assert_eq!(context.get_value("c").unwrap().as_empty(), Ok(())); + assert_eq!( + eval_with_context_mut("c = 5", &mut context), + Ok(Value::Empty) + ); + assert_eq!(context.get_value("c").unwrap().as_int(), Ok(5)); +} + #[test] fn test_hashmap_context_clone_debug() { let mut context = HashMapContext::new();