diff --git a/Cargo.lock b/Cargo.lock index bf01f4ca4..0c1035e0f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -296,9 +296,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jiter" -version = "0.8.2" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8243cf2d026264056bfacf305e54f5bee8866fd46b4c1873adcaebf614a0d306" +checksum = "c024ccb0ed468a474efa325edea34d4198fb601d290c4d1bc24fe31ed11902fc" dependencies = [ "ahash", "bitvec", @@ -312,9 +312,9 @@ dependencies = [ [[package]] name = "lexical-parse-float" -version = "0.8.5" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683b3a5ebd0130b8fb52ba0bdc718cc56815b6a097e28ae5a6997d0ad17dc05f" +checksum = "de6f9cb01fb0b08060209a057c048fcbab8717b4c1ecd2eac66ebfe39a65b0f2" dependencies = [ "lexical-parse-integer", "lexical-util", @@ -323,9 +323,9 @@ dependencies = [ [[package]] name = "lexical-parse-integer" -version = "0.8.6" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d0994485ed0c312f6d965766754ea177d07f9c00c9b82a5ee62ed5b47945ee9" +checksum = "72207aae22fc0a121ba7b6d479e42cbfea549af1479c3f3a4f12c70dd66df12e" dependencies = [ "lexical-util", "static_assertions", @@ -333,9 +333,9 @@ dependencies = [ [[package]] name = "lexical-util" -version = "0.8.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5255b9ff16ff898710eb9eb63cb39248ea8a5bb036bea8085b1a767ff6c4e3fc" +checksum = "5a82e24bf537fd24c177ffbbdc6ebcc8d54732c35b50a3f28cc3f4e4c949a0b3" dependencies = [ "static_assertions", ] @@ -449,9 +449,9 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.23.5" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7778bffd85cf38175ac1f545509665d0b9b92a198ca7941f131f85f7a4f9a872" +checksum = "7f1c6c3591120564d64db2261bec5f910ae454f01def849b9c22835a84695e86" dependencies = [ "cfg-if", "indoc", @@ -468,9 +468,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.23.5" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94f6cbe86ef3bf18998d9df6e0f3fc1050a8c5efa409bf712e661a4366e010fb" +checksum = "e9b6c2b34cf71427ea37c7001aefbaeb85886a074795e35f161f5aecc7620a7a" dependencies = [ "once_cell", "python3-dll-a", @@ -479,9 +479,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.23.5" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9f1b4c431c0bb1c8fb0a338709859eed0d030ff6daa34368d3b152a63dfdd8d" +checksum = "5507651906a46432cdda02cd02dd0319f6064f1374c9147c45b978621d2c3a9c" dependencies = [ "libc", "pyo3-build-config", @@ -489,9 +489,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.23.5" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc2201328f63c4710f68abdf653c89d8dbc2858b88c5d88b0ff38a75288a9da" +checksum = "b0d394b5b4fd8d97d48336bb0dd2aebabad39f1d294edd6bcd2cccf2eefe6f42" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -501,9 +501,9 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.23.5" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fca6726ad0f3da9c9de093d6f116a93c1a38e417ed73bf138472cf4064f72028" +checksum = "fd72da09cfa943b1080f621f024d2ef7e2773df7badd51aa30a2be1f8caa7c8e" dependencies = [ "heck", "proc-macro2", @@ -690,9 +690,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "target-lexicon" -version = "0.12.14" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" +checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a" [[package]] name = "tinystr" diff --git a/Cargo.toml b/Cargo.toml index 13af5c552..16dc843c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ rust-version = "1.75" [dependencies] # TODO it would be very nice to remove the "py-clone" feature as it can panic, # but needs a bit of work to make sure it's not used in the codebase -pyo3 = { version = "0.23.5", features = ["generate-import-lib", "num-bigint", "py-clone"] } +pyo3 = { version = "0.24", features = ["generate-import-lib", "num-bigint", "py-clone"] } regex = "1.11.1" strum = { version = "0.26.3", features = ["derive"] } strum_macros = "0.26.4" @@ -45,7 +45,7 @@ idna = "1.0.3" base64 = "0.22.1" num-bigint = "0.4.6" uuid = "1.15.1" -jiter = { version = "0.8.2", features = ["python"] } +jiter = { version = "0.9.0", features = ["python"] } hex = "0.4.3" [lib] @@ -73,12 +73,12 @@ debug = true strip = false [dev-dependencies] -pyo3 = { version = "0.23.5", features = ["auto-initialize"] } +pyo3 = { version = "0.24", features = ["auto-initialize"] } [build-dependencies] version_check = "0.9.5" # used where logic has to be version/distribution specific, e.g. pypy -pyo3-build-config = { version = "0.23.5" } +pyo3-build-config = { version = "0.24" } [lints.clippy] dbg_macro = "warn" diff --git a/src/input/input_json.rs b/src/input/input_json.rs index 139c71a25..6479357cc 100644 --- a/src/input/input_json.rs +++ b/src/input/input_json.rs @@ -1,9 +1,8 @@ use std::borrow::Cow; -use jiter::{JsonArray, JsonObject, JsonValue, LazyIndexMap}; +use jiter::{JsonArray, JsonObject, JsonValue}; use pyo3::prelude::*; use pyo3::types::{PyDict, PyList, PyString}; -use smallvec::SmallVec; use speedate::MicrosecondsPrecisionOverflowBehavior; use strum::EnumMessage; @@ -62,7 +61,9 @@ impl<'py, 'data> Input<'py> for JsonValue<'data> { match self { JsonValue::Object(object) => { let dict = PyDict::new(py); - for (k, v) in LazyIndexMap::iter(object) { + for (k, v) in object.as_slice() { + // TODO: jiter doesn't deduplicate keys, so we should probably do that here to + // avoid potential wasted work creating Python objects. dict.set_item(k, v).unwrap(); } Some(dict) @@ -253,7 +254,14 @@ impl<'py, 'data> Input<'py> for JsonValue<'data> { JsonValue::Str(s) => Ok(string_to_vec(s).into()), JsonValue::Object(object) => { // return keys iterator to match python's behavior - let keys: JsonArray = JsonArray::new(object.keys().map(|k| JsonValue::Str(k.clone())).collect()); + // FIXME jiter doesn't deduplicate keys, should probably do that here before iteration. + let keys: JsonArray = JsonArray::new( + object + .as_slice() + .iter() + .map(|(k, _)| JsonValue::Str(k.clone())) + .collect(), + ); Ok(GenericIterator::from(keys).into_static()) } _ => Err(ValError::new(ErrorTypeDefaults::IterableType, self)), @@ -543,11 +551,11 @@ impl<'data> ValidatedDict<'_> for &'_ JsonObject<'data> { &'a self, consumer: impl ConsumeIterator<ValResult<(Self::Key<'a>, Self::Item<'a>)>, Output = R>, ) -> ValResult<R> { - Ok(consumer.consume_iterator(LazyIndexMap::iter(self).map(|(k, v)| Ok((k.as_ref(), v))))) + Ok(consumer.consume_iterator(self.as_slice().iter().map(|(k, v)| Ok((k.as_ref(), v))))) } fn last_key(&self) -> Option<Self::Key<'_>> { - self.keys().last().map(AsRef::as_ref) + self.last().map(|(k, _)| k.as_ref()) } } @@ -555,7 +563,7 @@ impl<'a, 'py, 'data> ValidatedList<'py> for &'a JsonArray<'data> { type Item = &'a JsonValue<'data>; fn len(&self) -> Option<usize> { - Some(SmallVec::len(self)) + Some(Vec::len(self)) } fn iterate<R>(self, consumer: impl ConsumeIterator<PyResult<Self::Item>, Output = R>) -> ValResult<R> { Ok(consumer.consume_iterator(self.iter().map(Ok))) @@ -569,7 +577,7 @@ impl<'a, 'data> ValidatedTuple<'_> for &'a JsonArray<'data> { type Item = &'a JsonValue<'data>; fn len(&self) -> Option<usize> { - Some(SmallVec::len(self)) + Some(Vec::len(self)) } fn iterate<R>(self, consumer: impl ConsumeIterator<PyResult<Self::Item>, Output = R>) -> ValResult<R> { Ok(consumer.consume_iterator(self.iter().map(Ok))) @@ -637,12 +645,12 @@ impl<'data> KeywordArgs<'_> for JsonObject<'data> { Self: 'a; fn len(&self) -> usize { - LazyIndexMap::len(self) + Vec::len(self) } fn get_item<'k>(&self, key: &'k LookupKey) -> ValResult<Option<(&'k LookupPath, Self::Item<'_>)>> { key.json_get(self) } fn iter(&self) -> impl Iterator<Item = ValResult<(Self::Key<'_>, Self::Item<'_>)>> { - LazyIndexMap::iter(self).map(|(k, v)| Ok((k.as_ref(), v))) + self.as_slice().iter().map(|(k, v)| Ok((k.as_ref(), v))) } } diff --git a/src/lookup_key.rs b/src/lookup_key.rs index 9b06db8fa..db8a4c869 100644 --- a/src/lookup_key.rs +++ b/src/lookup_key.rs @@ -262,20 +262,33 @@ impl LookupKey { &'s self, dict: &'a JsonObject<'data>, ) -> ValResult<Option<(&'s LookupPath, &'a JsonValue<'data>)>> { + // FIXME: use of find_map in here probably leads to quadratic complexity match self { - Self::Simple(path) => match dict.get(path.first_key()) { + Self::Simple(path) => match dict + .iter() + .rev() + .find_map(|(k, v)| (k == path.first_key()).then_some(v)) + { Some(value) => { debug_assert!(path.rest.is_empty()); Ok(Some((path, value))) } None => Ok(None), }, - Self::Choice { path1, path2 } => match dict.get(path1.first_key()) { + Self::Choice { path1, path2 } => match dict + .iter() + .rev() + .find_map(|(k, v)| (k == path1.first_key()).then_some(v)) + { Some(value) => { debug_assert!(path1.rest.is_empty()); Ok(Some((path1, value))) } - None => match dict.get(path2.first_key()) { + None => match dict + .iter() + .rev() + .find_map(|(k, v)| (k == path2.first_key()).then_some(v)) + { Some(value) => { debug_assert!(path2.rest.is_empty()); Ok(Some((path2, value))) @@ -287,7 +300,11 @@ impl LookupKey { for path in path_choices { // first step is different from the rest as we already know dict is JsonObject // because of above checks, we know that path should have at least one element, hence unwrap - let v: &JsonValue = match dict.get(path.first_item.key.as_str()) { + let v: &JsonValue = match dict + .iter() + .rev() + .find_map(|(k, v)| (k == path.first_key()).then_some(v)) + { Some(v) => v, None => continue, }; @@ -527,7 +544,7 @@ impl PathItem { pub fn json_obj_get<'a, 'data>(&self, json_obj: &'a JsonObject<'data>) -> Option<&'a JsonValue<'data>> { match self { - Self::S(PathItemString { key, .. }) => json_obj.get(key.as_str()), + Self::S(PathItemString { key, .. }) => json_obj.iter().rev().find_map(|(k, v)| (k == key).then_some(v)), _ => None, } } diff --git a/src/validators/dataclass.rs b/src/validators/dataclass.rs index 69c8279ea..0f2cbefc7 100644 --- a/src/validators/dataclass.rs +++ b/src/validators/dataclass.rs @@ -668,7 +668,7 @@ impl DataclassValidator { dc.call_method0(post_init) } else { let args = post_init_kwargs.downcast::<PyTuple>()?; - dc.call_method1(post_init, args) + dc.call_method1(post_init, args.clone()) // FIXME should not need clone here }; r.map_err(|e| convert_err(py, e, input))?; }