From b1657ddaba51627cc6e6c93afb829015a23e12ac Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Tue, 19 Mar 2024 10:37:52 +0000 Subject: [PATCH] remove with_new_extra --- src/recursion_guard.rs | 16 +- src/validators/dataclass.rs | 386 ++++++++++++++--------------- src/validators/mod.rs | 2 +- src/validators/model_fields.rs | 105 +++----- src/validators/typed_dict.rs | 36 +-- src/validators/validation_state.rs | 28 +-- 6 files changed, 250 insertions(+), 323 deletions(-) diff --git a/src/recursion_guard.rs b/src/recursion_guard.rs index d3302c0e9..b1f07ed2c 100644 --- a/src/recursion_guard.rs +++ b/src/recursion_guard.rs @@ -71,8 +71,11 @@ pub struct RecursionState { depth: u8, } +// with debug_assertions enabled, function stacks are a bit bigger so need to be a bit more restricted +const GUARD_OFFSET: u8 = if cfg!(debug_assertions) { 20 } else { 0 }; + // A hard limit to avoid stack overflows when rampant recursion occurs -pub const RECURSION_GUARD_LIMIT: u8 = if cfg!(any(target_family = "wasm", all(windows, PyPy))) { +pub const RECURSION_GUARD_LIMIT: u8 = (if cfg!(any(target_family = "wasm", all(windows, PyPy))) { // wasm and windows PyPy have very limited stack sizes 49 } else if cfg!(any(PyPy, windows)) { @@ -80,7 +83,7 @@ pub const RECURSION_GUARD_LIMIT: u8 = if cfg!(any(target_family = "wasm", all(wi 99 } else { 255 -}; +}) - GUARD_OFFSET; impl RecursionState { // insert a new value @@ -92,17 +95,20 @@ impl RecursionState { // see #143 this is used as a backup in case the identity check recursion guard fails #[must_use] - #[cfg(any(target_family = "wasm", windows, PyPy))] + #[cfg(any(target_family = "wasm", windows, PyPy, debug_assertions))] pub fn incr_depth(&mut self) -> bool { + #[allow(clippy::assertions_on_constants)] + { + debug_assert!(RECURSION_GUARD_LIMIT < 255); + } // use saturating_add as it's faster (since there's no error path) // and the RECURSION_GUARD_LIMIT check will be hit before it overflows - debug_assert!(RECURSION_GUARD_LIMIT < 255); self.depth = self.depth.saturating_add(1); self.depth > RECURSION_GUARD_LIMIT } #[must_use] - #[cfg(not(any(target_family = "wasm", windows, PyPy)))] + #[cfg(not(any(target_family = "wasm", windows, PyPy, debug_assertions)))] pub fn incr_depth(&mut self) -> bool { debug_assert_eq!(RECURSION_GUARD_LIMIT, 255); // use checked_add to check if we've hit the limit diff --git a/src/validators/dataclass.rs b/src/validators/dataclass.rs index e2b479fbd..766691895 100644 --- a/src/validators/dataclass.rs +++ b/src/validators/dataclass.rs @@ -17,9 +17,7 @@ use crate::validators::function::convert_err; use super::arguments::{json_get, json_slice, py_get, py_slice}; use super::model::{create_class, force_setattr, Revalidate}; use super::validation_state::Exactness; -use super::{ - build_validator, BuildValidator, CombinedValidator, DefinitionsBuilder, Extra, ValidationState, Validator, -}; +use super::{build_validator, BuildValidator, CombinedValidator, DefinitionsBuilder, ValidationState, Validator}; #[derive(Debug)] struct Field { @@ -155,222 +153,213 @@ impl Validator for DataclassArgsValidator { let mut errors: Vec = Vec::new(); let mut used_keys: AHashSet<&str> = AHashSet::with_capacity(self.fields.len()); - state.with_new_extra( - Extra { - data: Some(output_dict.clone()), - ..*state.extra() - }, - |state| { - macro_rules! set_item { - ($field:ident, $value:expr) => {{ - let py_name = $field.py_name.bind(py); - if $field.init_only { - if let Some(ref mut init_only_args) = init_only_args { - init_only_args.push($value); - } - } else { - output_dict.set_item(py_name, $value)?; - } - }}; + let state = &mut state.rebind_extra(|extra| extra.data = Some(output_dict.clone())); + + macro_rules! set_item { + ($field:ident, $value:expr) => {{ + let py_name = $field.py_name.bind(py); + if $field.init_only { + if let Some(ref mut init_only_args) = init_only_args { + init_only_args.push($value); + } + } else { + output_dict.set_item(py_name, $value)?; } + }}; + } - macro_rules! process { - ($args:ident, $get_method:ident, $get_macro:ident, $slice_macro:ident) => {{ - // go through fields getting the value from args or kwargs and validating it - for (index, field) in self.fields.iter().enumerate() { - if (!field.init) { - match field.validator.default_value(py, Some(field.name.as_str()), state) { - Ok(Some(value)) => { - // Default value exists, and passed validation if required - set_item!(field, value); - }, - Ok(None) | Err(ValError::Omit) => continue, - // Note: this will always use the field name even if there is an alias - // However, we don't mind so much because this error can only happen if the - // default value fails validation, which is arguably a developer error. - // We could try to "fix" this in the future if desired. - Err(ValError::LineErrors(line_errors)) => errors.extend(line_errors), - Err(err) => return Err(err), - }; - continue; - }; - - let mut pos_value = None; - if let Some(args) = &$args.args { - if !field.kw_only { - pos_value = $get_macro!(args, index); - } + macro_rules! process { + ($args:ident, $get_method:ident, $get_macro:ident, $slice_macro:ident) => {{ + // go through fields getting the value from args or kwargs and validating it + for (index, field) in self.fields.iter().enumerate() { + if (!field.init) { + match field + .validator + .default_value(py, Some(field.name.as_str()), state) + { + Ok(Some(value)) => { + // Default value exists, and passed validation if required + set_item!(field, value); } + Ok(None) | Err(ValError::Omit) => continue, + // Note: this will always use the field name even if there is an alias + // However, we don't mind so much because this error can only happen if the + // default value fails validation, which is arguably a developer error. + // We could try to "fix" this in the future if desired. + Err(ValError::LineErrors(line_errors)) => errors.extend(line_errors), + Err(err) => return Err(err), + }; + continue; + }; - let mut kw_value = None; - if let Some(kwargs) = &$args.kwargs { - if let Some((lookup_path, value)) = field.lookup_key.$get_method(&kwargs)? { - used_keys.insert(lookup_path.first_key()); - kw_value = Some((lookup_path, value)); + let mut pos_value = None; + if let Some(args) = &$args.args { + if !field.kw_only { + pos_value = $get_macro!(args, index); + } + } + + let mut kw_value = None; + if let Some(kwargs) = &$args.kwargs { + if let Some((lookup_path, value)) = field.lookup_key.$get_method(&kwargs)? { + used_keys.insert(lookup_path.first_key()); + kw_value = Some((lookup_path, value)); + } + } + let kw_value = kw_value + .as_ref() + .map(|(path, value)| (path, value.borrow_input())); + + match (pos_value, kw_value) { + // found both positional and keyword arguments, error + (Some(_), Some((_, kw_value))) => { + errors.push(ValLineError::new_with_loc( + ErrorTypeDefaults::MultipleArgumentValues, + kw_value, + field.name.clone(), + )); + } + // found a positional argument, validate it + (Some(pos_value), None) => { + match field.validator.validate(py, pos_value.borrow_input(), state) { + Ok(value) => set_item!(field, value), + Err(ValError::LineErrors(line_errors)) => { + errors.extend(line_errors.into_iter().map(|err| err.with_outer_location(index))); } + Err(err) => return Err(err), } - let kw_value = kw_value - .as_ref() - .map(|(path, value)| (path, value.borrow_input())); - - match (pos_value, kw_value) { - // found both positional and keyword arguments, error - (Some(_), Some((_, kw_value))) => { - errors.push( - ValLineError::new_with_loc( - ErrorTypeDefaults::MultipleArgumentValues, - kw_value, - field.name.clone(), - ), - ); + } + // found a keyword argument, validate it + (None, Some((lookup_path, kw_value))) => match field.validator.validate(py, kw_value, state) { + Ok(value) => set_item!(field, value), + Err(ValError::LineErrors(line_errors)) => { + errors.extend( + line_errors + .into_iter() + .map(|err| lookup_path.apply_error_loc(err, self.loc_by_alias, &field.name)), + ); + } + Err(err) => return Err(err), + }, + // found neither, check if there is a default value, otherwise error + (None, None) => { + match field + .validator + .default_value(py, Some(field.name.as_str()), state) + { + Ok(Some(value)) => { + // Default value exists, and passed validation if required + set_item!(field, value); } - // found a positional argument, validate it - (Some(pos_value), None) => match field.validator.validate(py, pos_value.borrow_input(), state) { - Ok(value) => set_item!(field, value), - Err(ValError::LineErrors(line_errors)) => { - errors.extend( - line_errors - .into_iter() - .map(|err| err.with_outer_location(index)), - ); - } - Err(err) => return Err(err), - }, - // found a keyword argument, validate it - (None, Some((lookup_path, kw_value))) => { - match field.validator.validate(py, kw_value, state) { - Ok(value) => set_item!(field, value), - Err(ValError::LineErrors(line_errors)) => { - errors.extend(line_errors.into_iter().map(|err| { - lookup_path - .apply_error_loc(err, self.loc_by_alias, &field.name) - })); - } - Err(err) => return Err(err), - } + Ok(None) => { + // This means there was no default value + errors.push(field.lookup_key.error( + ErrorTypeDefaults::Missing, + input, + self.loc_by_alias, + &field.name, + )); } - // found neither, check if there is a default value, otherwise error - (None, None) => { - match field.validator.default_value(py, Some(field.name.as_str()), state) { - Ok(Some(value)) => { - // Default value exists, and passed validation if required - set_item!(field, value); - }, - Ok(None) => { - // This means there was no default value - errors.push(field.lookup_key.error( - ErrorTypeDefaults::Missing, - input, - self.loc_by_alias, - &field.name - )); - }, - Err(ValError::Omit) => continue, - Err(ValError::LineErrors(line_errors)) => { - for err in line_errors { - // Note: this will always use the field name even if there is an alias - // However, we don't mind so much because this error can only happen if the - // default value fails validation, which is arguably a developer error. - // We could try to "fix" this in the future if desired. - errors.push(err); - } - } - Err(err) => return Err(err), + Err(ValError::Omit) => continue, + Err(ValError::LineErrors(line_errors)) => { + for err in line_errors { + // Note: this will always use the field name even if there is an alias + // However, we don't mind so much because this error can only happen if the + // default value fails validation, which is arguably a developer error. + // We could try to "fix" this in the future if desired. + errors.push(err); } } + Err(err) => return Err(err), } } - // if there are more args than positional_count, add an error for each one - if let Some(args) = $args.args { - let len = args.len(); - if len > self.positional_count { - for (index, item) in $slice_macro!(args, self.positional_count, len) - .iter() - .enumerate() - { - errors.push(ValLineError::new_with_loc( - ErrorTypeDefaults::UnexpectedPositionalArgument, - item.borrow_input(), - index + self.positional_count, - )); - } - } + } + } + // if there are more args than positional_count, add an error for each one + if let Some(args) = $args.args { + let len = args.len(); + if len > self.positional_count { + for (index, item) in $slice_macro!(args, self.positional_count, len).iter().enumerate() { + errors.push(ValLineError::new_with_loc( + ErrorTypeDefaults::UnexpectedPositionalArgument, + item.borrow_input(), + index + self.positional_count, + )); } - // if there are kwargs check any that haven't been processed yet - if let Some(kwargs) = $args.kwargs { - if kwargs.len() != used_keys.len() { - for (raw_key, value) in kwargs.iter() { - match raw_key.validate_str(true, false).map(ValidationMatch::into_inner) { - Ok(either_str) => { - if !used_keys.contains(either_str.as_cow()?.as_ref()) { - // Unknown / extra field - match self.extra_behavior { - ExtraBehavior::Forbid => { - errors.push( - ValLineError::new_with_loc( - ErrorTypeDefaults::UnexpectedKeywordArgument, - value.borrow_input(), - raw_key.clone(), - ), - ); - } - ExtraBehavior::Ignore => {} - ExtraBehavior::Allow => { - if let Some(ref validator) = self.extras_validator { - match validator.validate(py, value.borrow_input(), state) { - Ok(value) => output_dict - .set_item(either_str.as_py_string(py), value)?, - Err(ValError::LineErrors(line_errors)) => { - for err in line_errors { - errors.push(err.with_outer_location(raw_key.clone())); - } - } - Err(err) => return Err(err), - } - } else { + } + } + // if there are kwargs check any that haven't been processed yet + if let Some(kwargs) = $args.kwargs { + if kwargs.len() != used_keys.len() { + for (raw_key, value) in kwargs.iter() { + match raw_key.validate_str(true, false).map(ValidationMatch::into_inner) { + Ok(either_str) => { + if !used_keys.contains(either_str.as_cow()?.as_ref()) { + // Unknown / extra field + match self.extra_behavior { + ExtraBehavior::Forbid => { + errors.push(ValLineError::new_with_loc( + ErrorTypeDefaults::UnexpectedKeywordArgument, + value.borrow_input(), + raw_key.clone(), + )); + } + ExtraBehavior::Ignore => {} + ExtraBehavior::Allow => { + if let Some(ref validator) = self.extras_validator { + match validator.validate(py, value.borrow_input(), state) { + Ok(value) => { output_dict.set_item(either_str.as_py_string(py), value)? } + Err(ValError::LineErrors(line_errors)) => { + for err in line_errors { + errors.push(err.with_outer_location(raw_key.clone())); + } + } + Err(err) => return Err(err), } + } else { + output_dict.set_item(either_str.as_py_string(py), value)? } } } - Err(ValError::LineErrors(line_errors)) => { - for err in line_errors { - errors.push( - err.with_outer_location(raw_key.clone()) - .with_type(ErrorTypeDefaults::InvalidKey), - ); - } - } - Err(err) => return Err(err), } } + Err(ValError::LineErrors(line_errors)) => { + for err in line_errors { + errors.push( + err.with_outer_location(raw_key.clone()) + .with_type(ErrorTypeDefaults::InvalidKey), + ); + } + } + Err(err) => return Err(err), } } - }}; + } } + }}; + } - match args { - GenericArguments::Py(a) => process!(a, py_get_dict_item, py_get, py_slice), - GenericArguments::Json(a) => process!(a, json_get, json_get, json_slice), - GenericArguments::StringMapping(a) => { - // StringMapping cannot pass positional args, so wrap the PyDict - // in a type with guaranteed empty args array for sake of the process - // macro - struct StringMappingArgs<'py> { - args: Option>, - kwargs: Option>, - } - let a = StringMappingArgs { - args: None, - kwargs: Some(a.clone()), - }; - process!(a, py_get_string_mapping_item, py_get, py_slice); - } + match args { + GenericArguments::Py(a) => process!(a, py_get_dict_item, py_get, py_slice), + GenericArguments::Json(a) => process!(a, json_get, json_get, json_slice), + GenericArguments::StringMapping(a) => { + // StringMapping cannot pass positional args, so wrap the PyDict + // in a type with guaranteed empty args array for sake of the process + // macro + struct StringMappingArgs<'py> { + args: Option>, + kwargs: Option>, } - Ok(()) - }, - )?; + let a = StringMappingArgs { + args: None, + kwargs: Some(a.clone()), + }; + process!(a, py_get_string_mapping_item, py_get, py_slice); + } + } + if errors.is_empty() { if let Some(init_only_args) = init_only_args { Ok((output_dict, PyTuple::new_bound(py, init_only_args)).to_object(py)) @@ -417,12 +406,11 @@ impl Validator for DataclassArgsValidator { return Err(err.into()); } } - match state.with_new_extra( - Extra { - data: Some(data_dict.clone()), - ..*state.extra() - }, - |state| field.validator.validate(py, field_value, state), + + match field.validator.validate( + py, + field_value, + &mut state.rebind_extra(|extra| extra.data = Some(data_dict.clone())), ) { Ok(output) => ok(output), Err(ValError::LineErrors(line_errors)) => { diff --git a/src/validators/mod.rs b/src/validators/mod.rs index 8fbfb95eb..4201ce422 100644 --- a/src/validators/mod.rs +++ b/src/validators/mod.rs @@ -560,7 +560,7 @@ pub fn build_validator( /// More (mostly immutable) data to pass between validators, should probably be class `Context`, /// but that would confuse it with context as per pydantic/pydantic#1549 -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Extra<'a, 'py> { /// Validation mode pub input_type: InputType, diff --git a/src/validators/model_fields.rs b/src/validators/model_fields.rs index 39042158f..7ae4973fb 100644 --- a/src/validators/model_fields.rs +++ b/src/validators/model_fields.rs @@ -15,11 +15,7 @@ use crate::input::{ use crate::lookup_key::LookupKey; use crate::tools::SchemaDict; -use super::{ - build_validator, BuildValidator, CombinedValidator, DefinitionsBuilder, Extra, ValidationState, Validator, -}; - -use std::ops::ControlFlow; +use super::{build_validator, BuildValidator, CombinedValidator, DefinitionsBuilder, ValidationState, Validator}; #[derive(Debug)] struct Field { @@ -164,21 +160,11 @@ impl Validator for ModelFieldsValidator { _ => None, }; - macro_rules! control_flow { - ($e: expr) => { - match $e { - Ok(v) => ControlFlow::Continue(v), - Err(err) => ControlFlow::Break(ValError::from(err)), - } - }; - } - macro_rules! process { ($dict:expr, $get_method:ident, $iter:ty $(,$kwargs:expr)?) => {{ - match state.with_new_extra(Extra { - data: Some(model_dict.clone()), - ..*state.extra() - }, |state| { + { + let state = &mut state.rebind_extra(|extra| extra.data = Some(model_dict.clone())); + for field in &self.fields { let op_key_value = match field.lookup_key.$get_method($dict $(, $kwargs )? ) { Ok(v) => v, @@ -188,7 +174,7 @@ impl Validator for ModelFieldsValidator { } continue; } - Err(err) => return ControlFlow::Break(err), + Err(err) => return Err(err), }; if let Some((lookup_path, value)) = op_key_value { if let Some(ref mut used_keys) = used_keys { @@ -198,7 +184,7 @@ impl Validator for ModelFieldsValidator { } match field.validator.validate(py, value.borrow_input(), state) { Ok(value) => { - control_flow!(model_dict.set_item(&field.name_py, value))?; + model_dict.set_item(&field.name_py, value)?; fields_set_vec.push(field.name_py.clone_ref(py)); } Err(ValError::Omit) => continue, @@ -210,7 +196,7 @@ impl Validator for ModelFieldsValidator { ); } } - Err(err) => return ControlFlow::Break(err), + Err(err) => return Err(err), } continue; } @@ -218,7 +204,7 @@ impl Validator for ModelFieldsValidator { match field.validator.default_value(py, Some(field.name.as_str()), state) { Ok(Some(value)) => { // Default value exists, and passed validation if required - control_flow!(model_dict.set_item(&field.name_py, value))?; + model_dict.set_item(&field.name_py, value)?; }, Ok(None) => { // This means there was no default value @@ -239,13 +225,9 @@ impl Validator for ModelFieldsValidator { errors.push(err); } } - Err(err) => return ControlFlow::Break(err), + Err(err) => return Err(err), } } - ControlFlow::Continue(()) - }) { - ControlFlow::Continue(()) => {} - ControlFlow::Break(err) => return Err(err), } if let Some(ref mut used_keys) = used_keys { @@ -376,48 +358,43 @@ impl Validator for ModelFieldsValidator { } } - let new_extra = Extra { - data: Some(data_dict), - ..*state.extra() - }; + let new_data = { + let state = &mut state.rebind_extra(move |extra| extra.data = Some(data_dict)); - let new_data = if let Some(field) = self.fields.iter().find(|f| f.name == field_name) { - if field.frozen { - Err(ValError::new_with_loc( - ErrorTypeDefaults::FrozenField, - field_value, - field.name.to_string(), - )) - } else { - prepare_result( - state.with_new_extra(new_extra, |state| field.validator.validate(py, field_value, state)), - ) - } - } else { - // Handle extra (unknown) field - // We partially use the extra_behavior for initialization / validation - // to determine how to handle assignment - // For models / typed dicts we forbid assigning extra attributes - // unless the user explicitly set extra_behavior to 'allow' - match self.extra_behavior { - ExtraBehavior::Allow => match self.extras_validator { - Some(ref validator) => prepare_result( - state.with_new_extra(new_extra, |state| validator.validate(py, field_value, state)), - ), - None => get_updated_dict(field_value.to_object(py)), - }, - ExtraBehavior::Forbid | ExtraBehavior::Ignore => { + if let Some(field) = self.fields.iter().find(|f| f.name == field_name) { + if field.frozen { return Err(ValError::new_with_loc( - ErrorType::NoSuchAttribute { - attribute: field_name.to_string(), - context: None, - }, + ErrorTypeDefaults::FrozenField, field_value, - field_name.to_string(), - )) + field.name.to_string(), + )); + } + + prepare_result(field.validator.validate(py, field_value, state))? + } else { + // Handle extra (unknown) field + // We partially use the extra_behavior for initialization / validation + // to determine how to handle assignment + // For models / typed dicts we forbid assigning extra attributes + // unless the user explicitly set extra_behavior to 'allow' + match self.extra_behavior { + ExtraBehavior::Allow => match self.extras_validator { + Some(ref validator) => prepare_result(validator.validate(py, field_value, state))?, + None => get_updated_dict(field_value.to_object(py))?, + }, + ExtraBehavior::Forbid | ExtraBehavior::Ignore => { + return Err(ValError::new_with_loc( + ErrorType::NoSuchAttribute { + attribute: field_name.to_string(), + context: None, + }, + field_value, + field_name.to_string(), + )) + } } } - }?; + }; let new_extra = match &self.extra_behavior { ExtraBehavior::Allow => { diff --git a/src/validators/typed_dict.rs b/src/validators/typed_dict.rs index 998942f7f..afde8868c 100644 --- a/src/validators/typed_dict.rs +++ b/src/validators/typed_dict.rs @@ -1,5 +1,3 @@ -use std::ops::ControlFlow; - use pyo3::intern; use pyo3::prelude::*; use pyo3::types::{PyDict, PyString}; @@ -16,9 +14,7 @@ use crate::input::{ use crate::lookup_key::LookupKey; use crate::tools::SchemaDict; -use super::{ - build_validator, BuildValidator, CombinedValidator, DefinitionsBuilder, Extra, ValidationState, Validator, -}; +use super::{build_validator, BuildValidator, CombinedValidator, DefinitionsBuilder, ValidationState, Validator}; #[derive(Debug)] struct TypedDictField { @@ -165,21 +161,11 @@ impl Validator for TypedDictValidator { _ => None, }; - macro_rules! control_flow { - ($e: expr) => { - match $e { - Ok(v) => ControlFlow::Continue(v), - Err(err) => ControlFlow::Break(ValError::from(err)), - } - }; - } - macro_rules! process { ($dict:expr, $get_method:ident, $iter:ty $(,$kwargs:expr)?) => {{ - match state.with_new_extra(Extra { - data: Some(output_dict.clone()), - ..*state.extra() - }, |state| { + { + let state = &mut state.rebind_extra(|extra| extra.data = Some(output_dict.clone())); + for field in &self.fields { let op_key_value = match field.lookup_key.$get_method($dict $(, $kwargs )? ) { Ok(v) => v, @@ -189,7 +175,7 @@ impl Validator for TypedDictValidator { } continue; } - Err(err) => return ControlFlow::Break(err), + Err(err) => return Err(err), }; if let Some((lookup_path, value)) = op_key_value { if let Some(ref mut used_keys) = used_keys { @@ -199,7 +185,7 @@ impl Validator for TypedDictValidator { } match field.validator.validate(py, value.borrow_input(), state) { Ok(value) => { - control_flow!(output_dict.set_item(&field.name_py, value))?; + output_dict.set_item(&field.name_py, value)?; } Err(ValError::Omit) => continue, Err(ValError::LineErrors(line_errors)) => { @@ -210,7 +196,7 @@ impl Validator for TypedDictValidator { ); } } - Err(err) => return ControlFlow::Break(err), + Err(err) => return Err(err), } continue; } @@ -218,7 +204,7 @@ impl Validator for TypedDictValidator { match field.validator.default_value(py, Some(field.name.as_str()), state) { Ok(Some(value)) => { // Default value exists, and passed validation if required - control_flow!(output_dict.set_item(&field.name_py, value))?; + output_dict.set_item(&field.name_py, value)?; }, Ok(None) => { // This means there was no default value @@ -241,13 +227,9 @@ impl Validator for TypedDictValidator { errors.push(err); } } - Err(err) => return ControlFlow::Break(err), + Err(err) => return Err(err), } } - ControlFlow::Continue(()) - }) { - ControlFlow::Continue(()) => {} - ControlFlow::Break(err) => return Err(err), } if let Some(ref mut used_keys) = used_keys { diff --git a/src/validators/validation_state.rs b/src/validators/validation_state.rs index d7a831aee..1de2d75bd 100644 --- a/src/validators/validation_state.rs +++ b/src/validators/validation_state.rs @@ -25,28 +25,6 @@ impl<'a, 'py> ValidationState<'a, 'py> { } } - pub fn with_new_extra<'r, R: 'r>( - &mut self, - extra: Extra<'_, 'py>, - f: impl for<'s> FnOnce(&'s mut ValidationState<'_, 'py>) -> R, - ) -> R { - // TODO: It would be nice to implement this function with a drop guard instead of a closure, - // but lifetimes get in a tangle. Maybe someone brave wants to have a go at unpicking lifetimes. - let mut new_state = ValidationState { - recursion_guard: self.recursion_guard, - exactness: self.exactness, - extra, - }; - let result = f(&mut new_state); - let exactness = new_state.exactness; - drop(new_state); - match exactness { - Some(exactness) => self.floor_exactness(exactness), - None => self.exactness = None, - } - result - } - /// Temporarily rebinds the extra field by calling `f` to modify extra. /// /// When `ValidationStateWithReboundExtra` drops, the extra field is restored to its original value. @@ -54,11 +32,7 @@ impl<'a, 'py> ValidationState<'a, 'py> { &'state mut self, f: impl FnOnce(&mut Extra<'a, 'py>), ) -> ValidationStateWithReboundExtra<'state, 'a, 'py> { - #[allow(clippy::unnecessary_struct_initialization)] - let old_extra = Extra { - data: self.extra.data.clone(), - ..self.extra - }; + let old_extra = self.extra.clone(); f(&mut self.extra); ValidationStateWithReboundExtra { state: self, old_extra } }