diff --git a/changelog.d/87.dev.md b/changelog.d/87.dev.md new file mode 100644 index 0000000..ce46fae --- /dev/null +++ b/changelog.d/87.dev.md @@ -0,0 +1 @@ +Up the linter level to pedantic. diff --git a/src/codec/packstream.rs b/src/codec/packstream.rs index 547966c..1bd910a 100644 --- a/src/codec/packstream.rs +++ b/src/codec/packstream.rs @@ -65,18 +65,22 @@ impl Structure { } fn compute_index(&self, index: isize) -> PyResult { - Ok(if index < 0 { + fn index_err() -> PyErr { + PyErr::new::("field index out of range") + } + + if index < 0 { self.fields .len() - .checked_sub(-index as usize) - .ok_or_else(|| PyErr::new::("field index out of range"))? + .checked_add_signed(index) + .ok_or_else(index_err) } else { - let index = index as usize; + let index = index.try_into().map_err(|_| index_err())?; if index >= self.fields.len() { - return Err(PyErr::new::("field index out of range")); + return Err(index_err()); } - index - }) + Ok(index) + } } } @@ -131,6 +135,7 @@ impl Structure { Ok(()) } + #[expect(clippy::needless_pass_by_value, reason = "Needed for pyo3 to be happy")] fn __traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError> { for field in &self.fields { visit.call(field)?; diff --git a/src/codec/packstream/v1/pack.rs b/src/codec/packstream/v1/pack.rs index 8467cb5..8325f94 100644 --- a/src/codec/packstream/v1/pack.rs +++ b/src/codec/packstream/v1/pack.rs @@ -63,7 +63,7 @@ impl TypeMappings { let Ok(typ) = typ.cast::() else { return true; }; - is_of_known_bytes_types(typ).map(|b| !b).unwrap_or(true) + is_of_known_bytes_types(typ).map_or(true, |b| !b) }) .collect::, _>>()?; @@ -172,31 +172,33 @@ impl<'a> PackStreamEncoder<'a> { Self { dehydration_hooks, type_mappings, - buffer: Default::default(), + buffer: Vec::default(), } } fn write(&mut self, value: &Bound) -> PyResult<()> { let py = value.py(); - if self.write_exact_value(value, &self.type_mappings.none_values, &[NULL])? { + if self.write_exact_value(value, &self.type_mappings.none_values, &[NULL]) { return Ok(()); } - if self.write_exact_value(value, &self.type_mappings.true_values, &[TRUE])? { + if self.write_exact_value(value, &self.type_mappings.true_values, &[TRUE]) { return Ok(()); } - if self.write_exact_value(value, &self.type_mappings.false_values, &[FALSE])? { + if self.write_exact_value(value, &self.type_mappings.false_values, &[FALSE]) { return Ok(()); } if value.is_instance(self.type_mappings.float_types.bind(py))? { let value = value.extract::()?; - return self.write_float(value); + self.write_float(value); + return Ok(()); } if value.is_instance(self.type_mappings.int_types.bind(py))? { let value = value.extract::()?; - return self.write_int(value); + self.write_int(value); + return Ok(()); } if value.is_instance(&PyType::new::(py))? { @@ -229,14 +231,11 @@ impl<'a> PackStreamEncoder<'a> { let items = value.getattr(intern!(py, "items"))?.call0()?; return items.try_iter()?.try_for_each(|item| { let (key, value) = item?.extract::<(Bound, Bound)>()?; - let key = match key.extract::<&str>() { - Ok(key) => key, - Err(_) => { - return Err(PyErr::new::(format!( - "Map keys must be strings, not {}", - key.get_type().str()? - ))) - } + let Ok(key) = key.extract::<&str>() else { + return Err(PyErr::new::(format!( + "Map keys must be strings, not {}", + key.get_type().str()? + ))); }; self.write_string(key)?; self.write(&value) @@ -276,51 +275,54 @@ impl<'a> PackStreamEncoder<'a> { value: &Bound, values: &[Py], bytes: &[u8], - ) -> PyResult { + ) -> bool { for v in values { if value.is(v) { self.buffer.extend(bytes); - return Ok(true); + return true; } } - Ok(false) + false } - fn write_int(&mut self, i: i64) -> PyResult<()> { - if (-16..=127).contains(&i) { - self.buffer.extend(&i8::to_be_bytes(i as i8)); - } else if (-128..=127).contains(&i) { - self.buffer.extend(&[INT_8]); - self.buffer.extend(&i8::to_be_bytes(i as i8)); - } else if (-32_768..=32_767).contains(&i) { + fn write_int(&mut self, i: i64) { + if let Ok(i) = i8::try_from(i) { + if i >= -16 { + self.buffer.extend(&i8::to_be_bytes(i)); + } else { + self.buffer.extend(&[INT_8]); + self.buffer.extend(&i8::to_be_bytes(i)); + } + } else if let Ok(i) = i16::try_from(i) { self.buffer.extend(&[INT_16]); - self.buffer.extend(&i16::to_be_bytes(i as i16)); - } else if (-2_147_483_648..=2_147_483_647).contains(&i) { + self.buffer.extend(&i16::to_be_bytes(i)); + } else if let Ok(i) = i32::try_from(i) { self.buffer.extend(&[INT_32]); - self.buffer.extend(&i32::to_be_bytes(i as i32)); + self.buffer.extend(&i32::to_be_bytes(i)); } else { self.buffer.extend(&[INT_64]); self.buffer.extend(&i64::to_be_bytes(i)); } - Ok(()) } - fn write_float(&mut self, f: f64) -> PyResult<()> { + fn write_float(&mut self, f: f64) { self.buffer.extend(&[FLOAT_64]); self.buffer.extend(&f64::to_be_bytes(f)); - Ok(()) } fn write_bytes(&mut self, b: &[u8]) -> PyResult<()> { let size = Self::usize_to_u64(b.len())?; - if size <= 255 { + if size <= u8::MAX.into() { self.buffer.extend(&[BYTES_8]); + #[expect(clippy::cast_possible_truncation, reason = "checked bounds")] self.buffer.extend(&u8::to_be_bytes(size as u8)); - } else if size <= 65_535 { + } else if size <= u16::MAX.into() { self.buffer.extend(&[BYTES_16]); + #[expect(clippy::cast_possible_truncation, reason = "checked bounds")] self.buffer.extend(&u16::to_be_bytes(size as u16)); - } else if size <= 2_147_483_647 { + } else if size <= u32::MAX.into() { self.buffer.extend(&[BYTES_32]); + #[expect(clippy::cast_possible_truncation, reason = "checked bounds")] self.buffer.extend(&u32::to_be_bytes(size as u32)); } else { return Err(PyErr::new::( @@ -339,15 +341,19 @@ impl<'a> PackStreamEncoder<'a> { let bytes = s.as_bytes(); let size = Self::usize_to_u64(bytes.len())?; if size <= 15 { + #[expect(clippy::cast_possible_truncation, reason = "checked bounds")] self.buffer.extend(&[TINY_STRING + size as u8]); - } else if size <= 255 { + } else if size <= u8::MAX.into() { self.buffer.extend(&[STRING_8]); + #[expect(clippy::cast_possible_truncation, reason = "checked bounds")] self.buffer.extend(&u8::to_be_bytes(size as u8)); - } else if size <= 65_535 { + } else if size <= u16::MAX.into() { self.buffer.extend(&[STRING_16]); + #[expect(clippy::cast_possible_truncation, reason = "checked bounds")] self.buffer.extend(&u16::to_be_bytes(size as u16)); - } else if size <= 2_147_483_647 { + } else if size <= u32::MAX.into() { self.buffer.extend(&[STRING_32]); + #[expect(clippy::cast_possible_truncation, reason = "checked bounds")] self.buffer.extend(&u32::to_be_bytes(size as u32)); } else { return Err(PyErr::new::( @@ -360,15 +366,19 @@ impl<'a> PackStreamEncoder<'a> { fn write_list_header(&mut self, size: u64) -> PyResult<()> { if size <= 15 { + #[expect(clippy::cast_possible_truncation, reason = "checked bounds")] self.buffer.extend(&[TINY_LIST + size as u8]); - } else if size <= 255 { + } else if size <= u8::MAX.into() { self.buffer.extend(&[LIST_8]); + #[expect(clippy::cast_possible_truncation, reason = "checked bounds")] self.buffer.extend(&u8::to_be_bytes(size as u8)); - } else if size <= 65_535 { + } else if size <= u16::MAX.into() { self.buffer.extend(&[LIST_16]); + #[expect(clippy::cast_possible_truncation, reason = "checked bounds")] self.buffer.extend(&u16::to_be_bytes(size as u16)); - } else if size <= 2_147_483_647 { + } else if size <= u32::MAX.into() { self.buffer.extend(&[LIST_32]); + #[expect(clippy::cast_possible_truncation, reason = "checked bounds")] self.buffer.extend(&u32::to_be_bytes(size as u32)); } else { return Err(PyErr::new::( @@ -380,15 +390,19 @@ impl<'a> PackStreamEncoder<'a> { fn write_dict_header(&mut self, size: u64) -> PyResult<()> { if size <= 15 { + #[expect(clippy::cast_possible_truncation, reason = "checked bounds")] self.buffer.extend(&[TINY_MAP + size as u8]); - } else if size <= 255 { + } else if size <= u8::MAX.into() { self.buffer.extend(&[MAP_8]); + #[expect(clippy::cast_possible_truncation, reason = "checked bounds")] self.buffer.extend(&u8::to_be_bytes(size as u8)); - } else if size <= 65_535 { + } else if size <= u16::MAX.into() { self.buffer.extend(&[MAP_16]); + #[expect(clippy::cast_possible_truncation, reason = "checked bounds")] self.buffer.extend(&u16::to_be_bytes(size as u16)); - } else if size <= 2_147_483_647 { + } else if size <= u32::MAX.into() { self.buffer.extend(&[MAP_32]); + #[expect(clippy::cast_possible_truncation, reason = "checked bounds")] self.buffer.extend(&u32::to_be_bytes(size as u32)); } else { return Err(PyErr::new::( diff --git a/src/codec/packstream/v1/unpack.rs b/src/codec/packstream/v1/unpack.rs index 865cff3..8b7306d 100644 --- a/src/codec/packstream/v1/unpack.rs +++ b/src/codec/packstream/v1/unpack.rs @@ -71,6 +71,7 @@ impl<'a> PackStreamDecoder<'a> { Ok(match marker { // tiny int + #[expect(clippy::cast_possible_wrap, reason = "wrapping is what we want")] _ if marker as i8 >= -16 => (marker as i8).into_py_any(self.py)?, NULL => self.py.None(), FLOAT_64 => self.read_f64()?.into_py_any(self.py)?, @@ -82,15 +83,15 @@ impl<'a> PackStreamDecoder<'a> { INT_64 => self.read_i64()?.into_py_any(self.py)?, BYTES_8 => { let len = self.read_u8()?; - self.read_bytes(len)? + self.read_bytes(len) } BYTES_16 => { let len = self.read_u16()?; - self.read_bytes(len)? + self.read_bytes(len) } BYTES_32 => { let len = self.read_u32()?; - self.read_bytes(len)? + self.read_bytes(len) } _ if high_nibble == TINY_STRING => self.read_string((marker & 0x0F).into())?, STRING_8 => { @@ -188,9 +189,9 @@ impl<'a> PackStreamDecoder<'a> { Ok(key_value_pairs.into_py_dict(self.py)?.into()) } - fn read_bytes(&mut self, length: usize) -> PyResult> { + fn read_bytes(&mut self, length: usize) -> Py { if length == 0 { - return Ok(PyBytes::new(self.py, &[]).into_any().unbind()); + return PyBytes::new(self.py, &[]).into_any().unbind(); } let data = with_critical_section(&self.bytes, || { // Safety: @@ -205,14 +206,14 @@ impl<'a> PackStreamDecoder<'a> { } }); self.index += length; - Ok(PyBytes::new(self.py, &data).into_any().unbind()) + PyBytes::new(self.py, &data).into_any().unbind() } fn read_struct(&mut self, length: usize) -> PyResult> { let tag = self.read_byte()?; let mut fields = Vec::with_capacity(length); for _ in 0..length { - fields.push(self.read()?) + fields.push(self.read()?); } let mut bolt_struct = Structure { tag, fields } .into_pyobject(self.py)? diff --git a/src/lib.rs b/src/lib.rs index 0d13550..56d1739 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,10 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![warn(clippy::undocumented_unsafe_blocks)] +#![warn(clippy::allow_attributes_without_reason)] +#![warn(clippy::pedantic)] + mod codec; mod vector; diff --git a/src/vector.rs b/src/vector.rs index 2761fc8..060cad7 100644 --- a/src/vector.rs +++ b/src/vector.rs @@ -22,13 +22,13 @@ use crate::register_package; #[pyfunction] fn swap_endian<'py>( - type_size: Bound<'py, PyInt>, - data: Bound<'py, PyBytes>, + type_size: &Bound<'py, PyInt>, + data: &Bound<'py, PyBytes>, ) -> PyResult> { let py = type_size.py(); let type_size: usize = match type_size.extract::() { - Ok(type_size @ 2) | Ok(type_size @ 4) | Ok(type_size @ 8) => type_size, + Ok(type_size @ (2 | 4 | 8)) => type_size, _ => { return Err(PyErr::new::(format!( "Unsupported type size {type_size}", @@ -54,6 +54,7 @@ fn swap_endian<'py>( }) } +#[allow(clippy::inline_always, reason = "hot path")] #[inline(always)] fn swap_n(src: &[u8], dst: &mut [u8]) { // Doesn't technically need to be a function with a const generic, but this