Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/87.dev.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Up the linter level to pedantic<ISSUES_LIST>.
19 changes: 12 additions & 7 deletions src/codec/packstream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,18 +65,22 @@ impl Structure {
}

fn compute_index(&self, index: isize) -> PyResult<usize> {
Ok(if index < 0 {
fn index_err() -> PyErr {
PyErr::new::<PyIndexError, _>("field index out of range")
}

if index < 0 {
self.fields
.len()
.checked_sub(-index as usize)
.ok_or_else(|| PyErr::new::<PyIndexError, _>("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::<PyIndexError, _>("field index out of range"));
return Err(index_err());
}
index
})
Ok(index)
}
}
}

Expand Down Expand Up @@ -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)?;
Expand Down
100 changes: 57 additions & 43 deletions src/codec/packstream/v1/pack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ impl TypeMappings {
let Ok(typ) = typ.cast::<PyType>() 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::<Result<Vec<_>, _>>()?;

Expand Down Expand Up @@ -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<PyAny>) -> 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::<f64>()?;
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::<i64>()?;
return self.write_int(value);
self.write_int(value);
return Ok(());
}

if value.is_instance(&PyType::new::<PyString>(py))? {
Expand Down Expand Up @@ -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<PyAny>, Bound<PyAny>)>()?;
let key = match key.extract::<&str>() {
Ok(key) => key,
Err(_) => {
return Err(PyErr::new::<PyTypeError, _>(format!(
"Map keys must be strings, not {}",
key.get_type().str()?
)))
}
let Ok(key) = key.extract::<&str>() else {
return Err(PyErr::new::<PyTypeError, _>(format!(
"Map keys must be strings, not {}",
key.get_type().str()?
)));
};
self.write_string(key)?;
self.write(&value)
Expand Down Expand Up @@ -276,51 +275,54 @@ impl<'a> PackStreamEncoder<'a> {
value: &Bound<PyAny>,
values: &[Py<PyAny>],
bytes: &[u8],
) -> PyResult<bool> {
) -> 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::<PyOverflowError, _>(
Expand All @@ -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::<PyOverflowError, _>(
Expand All @@ -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::<PyOverflowError, _>(
Expand All @@ -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::<PyOverflowError, _>(
Expand Down
15 changes: 8 additions & 7 deletions src/codec/packstream/v1/unpack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)?,
Expand All @@ -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 => {
Expand Down Expand Up @@ -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<Py<PyAny>> {
fn read_bytes(&mut self, length: usize) -> Py<PyAny> {
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:
Expand All @@ -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<Py<PyAny>> {
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)?
Expand Down
4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
7 changes: 4 additions & 3 deletions src/vector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Bound<'py, PyBytes>> {
let py = type_size.py();

let type_size: usize = match type_size.extract::<usize>() {
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::<PyValueError, _>(format!(
"Unsupported type size {type_size}",
Expand All @@ -54,6 +54,7 @@ fn swap_endian<'py>(
})
}

#[allow(clippy::inline_always, reason = "hot path")]
#[inline(always)]
fn swap_n<const N: usize>(src: &[u8], dst: &mut [u8]) {
// Doesn't technically need to be a function with a const generic, but this
Expand Down