From ab47bf4b9a96758caea8eee2d1d01c5a73e353f6 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Sun, 4 Oct 2026 10:16:27 -0400 Subject: [PATCH] CI: as_chunks instead of constant-size chunks_exact (clippy 1.99 chunks_exact_to_as_chunks) Stable clippy 1.99 denies chunks_exact/chunks_exact_mut with a constant size (32 sites), which failed the test job on main and every open PR. Behaviour unchanged; MSRV 1.90 has as_chunks. Co-Authored-By: Claude Opus 5.5 --- crates/annot/src/appearance.rs | 12 ++++++------ crates/annot/src/lib.rs | 7 ++++--- crates/automation/src/lib.rs | 2 +- crates/cos/src/object.rs | 2 +- crates/create/src/extract.rs | 4 ++-- crates/create/src/lib.rs | 15 +++++++++------ crates/engine/src/export.rs | 6 ++++-- crates/ocr/src/lib.rs | 2 +- crates/organize/src/labels.rs | 2 +- crates/redact/src/interp.rs | 2 +- crates/redact/src/lib.rs | 4 +++- crates/render/src/inspect.rs | 2 +- crates/render/src/raster.rs | 2 +- crates/sign/src/der.rs | 2 +- crates/ui-egui/src/fill_sign.rs | 2 +- crates/ui-egui/src/zoom_snap.rs | 2 +- crates/ui-egui/tests/viewer.rs | 2 +- crates/xfdf/src/lib.rs | 2 +- xtask/src/demo_pdf/postprocess.rs | 2 +- 19 files changed, 41 insertions(+), 33 deletions(-) diff --git a/crates/annot/src/appearance.rs b/crates/annot/src/appearance.rs index 95fc98f43..3397f4297 100644 --- a/crates/annot/src/appearance.rs +++ b/crates/annot/src/appearance.rs @@ -215,7 +215,7 @@ pub fn build(d: &Dict) -> Option { _ if markup => { let q = nums(d, b"QuadPoints").filter(|q| !q.is_empty() && q.len() % 8 == 0)?; let col = stroke?; - for quad in q.chunks_exact(8) { + for quad in q.as_chunks::<8>().0.iter() { let p = |i: usize| (quad[2 * i], quad[2 * i + 1]); let (p1, p2, p3, p4) = (p(0), p(1), p(2), p(3)); let h = (p1.0 - p3.0).hypot(p1.1 - p3.1); @@ -268,7 +268,7 @@ pub fn build(d: &Dict) -> Option { let q = nums(d, b"QuadPoints").filter(|q| !q.is_empty() && q.len() % 8 == 0)?; let col = stroke.unwrap_or([0.89, 0.13, 0.13]); c.push_str(&format!("{}1 w\n", rg_stroke(col))); - for quad in q.chunks_exact(8) { + for quad in q.as_chunks::<8>().0.iter() { c.push_str(&format!( "{} {} m {} {} l {} {} l {} {} l h S\n", n(quad[0]), @@ -326,7 +326,7 @@ pub fn build(d: &Dict) -> Option { } b"Polygon" | b"PolyLine" => { let v = nums(d, b"Vertices").filter(|v| v.len() >= 4 && v.len() % 2 == 0)?; - let pts: Vec<(f64, f64)> = v.chunks_exact(2).map(|p| (p[0], p[1])).collect(); + let pts: Vec<(f64, f64)> = v.as_chunks::<2>().0.iter().map(|p| (p[0], p[1])).collect(); let col = stroke; let closed = subtype == b"Polygon"; let fill = if closed { color(d, b"IC")? } else { None }; @@ -388,7 +388,7 @@ pub fn build(d: &Dict) -> Option { c.push_str(&format!("{}{} w 1 J 1 j\n", rg_stroke(col), n(w))); for s in list { let pts: Vec = s.as_array()?.iter().map(|o| o.as_f64()).collect::>()?; - let pts: Vec<(f64, f64)> = pts.chunks_exact(2).map(|p| (p[0], p[1])).collect(); + let pts: Vec<(f64, f64)> = pts.as_chunks::<2>().0.iter().map(|p| (p[0], p[1])).collect(); let Some(first) = pts.first() else { continue }; c.push_str(&format!("{} {} m\n", n(first.0), n(first.1))); if pts.len() == 1 { @@ -408,7 +408,7 @@ pub fn build(d: &Dict) -> Option { c.push_str(&rg(stroke.unwrap_or([0.0; 3]))); for contour in outline { let v: Vec = contour.as_array().map(|a| a.iter().filter_map(Object::as_f64).collect()).unwrap_or_default(); - for (i, p) in v.chunks_exact(2).enumerate() { + for (i, p) in v.as_chunks::<2>().0.iter().enumerate() { c.push_str(&format!("{} {} {}\n", n(x0 + p[0] * w), n(y0 + p[1] * h), if i == 0 { "m" } else { "l" })); } if v.len() >= 6 { @@ -515,7 +515,7 @@ pub fn build(d: &Dict) -> Option { return None; } let lw = bw.max(0.5); - let pts: Vec<(f64, f64)> = cl.chunks_exact(2).map(|p| (p[0], p[1])).collect(); + let pts: Vec<(f64, f64)> = cl.as_chunks::<2>().0.iter().map(|p| (p[0], p[1])).collect(); c.push_str(&format!("{}{}{} w 1 J 1 j\n", rg_stroke(text_color), rg(bg.unwrap_or([1.0; 3])), n(lw))); for (i, p) in pts.iter().enumerate() { c.push_str(&format!("{} {} {}\n", n(p.0), n(p.1), if i == 0 { "m" } else { "l" })); diff --git a/crates/annot/src/lib.rs b/crates/annot/src/lib.rs index 4132daf31..f49655f64 100644 --- a/crates/annot/src/lib.rs +++ b/crates/annot/src/lib.rs @@ -714,7 +714,8 @@ fn rect_for(shape: &Shape, style: &Style) -> Result<[f64; 4], AnnotError> { if quads.is_empty() || !quads.iter().all(|q| finite(q)) { return Err(bad("text area (no quadrilaterals)")); } - bounds(quads.iter().flat_map(|q| q.chunks_exact(2).map(|p| [p[0], p[1]]).collect::>())).ok_or_else(|| bad("text area"))? + bounds(quads.iter().flat_map(|q| q.as_chunks::<2>().0.iter().map(|p| [p[0], p[1]]).collect::>())) + .ok_or_else(|| bad("text area"))? } Shape::Rectangle { rect } | Shape::Oval { rect } @@ -1479,7 +1480,7 @@ pub fn summaries(doc: &Document) -> Vec { color = Some([f(i - 3), f(i - 2), f(i - 1)]); } } - let quads = nums(b"QuadPoints").chunks_exact(8).map(|q| <[f32; 8]>::try_from(q).unwrap_or_default()).collect(); + let quads = nums(b"QuadPoints").as_chunks::<8>().0.to_vec(); let in_reply_to = d.get(b"IRT").map(|o| doc.resolve(o)).and_then(|o| o.as_dict().cloned()).and_then(|p| text_value(doc, &p, b"NM")); out.push(Summary { page, @@ -1653,7 +1654,7 @@ pub fn erase_ink(doc: &mut Document, page: usize, index: usize, path: &[[f64; 2] .iter() .map(|s| { let v: Vec = doc.resolve(s).as_array().map(|a| a.iter().filter_map(|x| doc.resolve(x).as_f64()).collect()).unwrap_or_default(); - v.chunks_exact(2).map(|p| [p[0], p[1]]).collect() + v.as_chunks::<2>().0.iter().map(|p| [p[0], p[1]]).collect() }) .collect(); // Distance from a point to the eraser's path (segments, or a single point). diff --git a/crates/automation/src/lib.rs b/crates/automation/src/lib.rs index efc4a1503..6c36079a3 100644 --- a/crates/automation/src/lib.rs +++ b/crates/automation/src/lib.rs @@ -1506,7 +1506,7 @@ fn outline(items: &[printcraft_render::OutlineItem]) -> Value { fn encode_png(width: u32, height: u32, premultiplied: &[u8]) -> Result> { let mut rgba = premultiplied.to_vec(); - for px in rgba.chunks_exact_mut(4) { + for px in rgba.as_chunks_mut::<4>().0 { let a = u32::from(px[3]); if a != 0 && a != 255 { for c in &mut px[..3] { diff --git a/crates/cos/src/object.rs b/crates/cos/src/object.rs index 31715b4f0..cf1d0bf9b 100644 --- a/crates/cos/src/object.rs +++ b/crates/cos/src/object.rs @@ -56,7 +56,7 @@ impl PdfString { pub fn to_text(&self) -> String { let b = &self.bytes; if b.len() >= 2 && b[0] == 0xFE && b[1] == 0xFF { - let units: Vec = b[2..].chunks_exact(2).map(|c| u16::from_be_bytes([c[0], c[1]])).collect(); + let units: Vec = b[2..].as_chunks::<2>().0.iter().map(|c| u16::from_be_bytes(*c)).collect(); return String::from_utf16_lossy(&units); } if b.len() >= 3 && b[..3] == [0xEF, 0xBB, 0xBF] { diff --git a/crates/create/src/extract.rs b/crates/create/src/extract.rs index 6eb53a76c..dc4b8486d 100644 --- a/crates/create/src/extract.rs +++ b/crates/create/src/extract.rs @@ -230,7 +230,7 @@ fn unpack(data: &[u8], w: usize, h: usize, n: usize, bpc: usize) -> Option out.extend_from_slice(&r[..w * n]), - 16 => out.extend(r.chunks_exact(2).take(w * n).map(|c| c[0])), + 16 => out.extend(r.as_chunks::<2>().0.iter().take(w * n).map(|c| c[0])), _ => { let per = 8 / bpc; let m = (1u8 << bpc) - 1; @@ -275,7 +275,7 @@ fn png(w: u32, h: u32, rgb: &[u8], alpha: Option<&[u8]>) -> Result, Stri let data: Vec = match alpha { Some(a) => { enc.set_color(png::ColorType::Rgba); - rgb.chunks_exact(3).zip(a).flat_map(|(c, a)| [c[0], c[1], c[2], *a]).collect() + rgb.as_chunks::<3>().0.iter().zip(a).flat_map(|(c, a)| [c[0], c[1], c[2], *a]).collect() } None => { enc.set_color(png::ColorType::Rgb); diff --git a/crates/create/src/lib.rs b/crates/create/src/lib.rs index ed4d40773..63216beff 100644 --- a/crates/create/src/lib.rs +++ b/crates/create/src/lib.rs @@ -266,10 +266,13 @@ fn png_image(name: &str, bytes: &[u8]) -> Result { /// 8-bit RGBA pixels → an image (gray when every pixel is, with a soft mask when any pixel is /// not opaque). fn rgba_image(rgba: &[u8], (w, h): (u32, u32), dpi: (f64, f64)) -> Embedded { - let gray = rgba.chunks_exact(4).all(|p| p[0] == p[1] && p[1] == p[2]); - let opaque = rgba.chunks_exact(4).all(|p| p[3] == 255); - let colour: Vec = - if gray { rgba.chunks_exact(4).map(|p| p[0]).collect() } else { rgba.chunks_exact(4).flat_map(|p| [p[0], p[1], p[2]]).collect() }; + let gray = rgba.as_chunks::<4>().0.iter().all(|p| p[0] == p[1] && p[1] == p[2]); + let opaque = rgba.as_chunks::<4>().0.iter().all(|p| p[3] == 255); + let colour: Vec = if gray { + rgba.as_chunks::<4>().0.iter().map(|p| p[0]).collect() + } else { + rgba.as_chunks::<4>().0.iter().flat_map(|p| [p[0], p[1], p[2]]).collect() + }; let mut d = Dict::new(); d.set(b"ColorSpace".to_vec(), Object::name(if gray { "DeviceGray" } else { "DeviceRGB" })); d.set(b"BitsPerComponent".to_vec(), Object::Int(8)); @@ -281,7 +284,7 @@ fn rgba_image(rgba: &[u8], (w, h): (u32, u32), dpi: (f64, f64)) -> Embedded { m.set(b"Height".to_vec(), Object::Int(h as i64)); m.set(b"ColorSpace".to_vec(), Object::name("DeviceGray")); m.set(b"BitsPerComponent".to_vec(), Object::Int(8)); - (m, rgba.chunks_exact(4).map(|p| p[3]).collect()) + (m, rgba.as_chunks::<4>().0.iter().map(|p| p[3]).collect()) }); Embedded { dict: d, data: colour, filtered: false, smask, px: (w, h), dpi } } @@ -351,7 +354,7 @@ fn tiff_pages(name: &str, bytes: &[u8]) -> Result, CreateError> { } C::RGBA(8 | 16) => rgba_image(&data, (w, h), dpi), C::GrayA(8 | 16) => { - let rgba: Vec = data.chunks_exact(2).flat_map(|p| [p[0], p[0], p[0], p[1]]).collect(); + let rgba: Vec = data.as_chunks::<2>().0.iter().flat_map(|p| [p[0], p[0], p[0], p[1]]).collect(); rgba_image(&rgba, (w, h), dpi) } other => return Err(bad(format!("{other:?} TIFF images aren't supported yet"))), diff --git a/crates/engine/src/export.rs b/crates/engine/src/export.rs index 7ce71505f..e36f0865b 100644 --- a/crates/engine/src/export.rs +++ b/crates/engine/src/export.rs @@ -105,7 +105,7 @@ impl Exporter { /// Premultiplied RGBA → PNG (straight alpha). pub fn encode_png(width: u32, height: u32, premultiplied: &[u8]) -> Result, String> { let mut rgba = premultiplied.to_vec(); - for px in rgba.chunks_exact_mut(4) { + for px in rgba.as_chunks_mut::<4>().0 { let a = u32::from(px[3]); if a != 0 && a != 255 { for c in &mut px[..3] { @@ -155,7 +155,9 @@ impl ImageFormat { /// Premultiplied RGBA composited over white → RGB. fn over_white(premultiplied: &[u8]) -> Vec { premultiplied - .chunks_exact(4) + .as_chunks::<4>() + .0 + .iter() .flat_map(|p| { let k = 255 - p[3]; [p[0].saturating_add(k), p[1].saturating_add(k), p[2].saturating_add(k)] diff --git a/crates/ocr/src/lib.rs b/crates/ocr/src/lib.rs index 94080209c..755fc3e4f 100644 --- a/crates/ocr/src/lib.rs +++ b/crates/ocr/src/lib.rs @@ -114,7 +114,7 @@ impl Ocr { } // ocrs wants 1 or 3 channels. let rgb: std::borrow::Cow<[u8]> = if pixels.len() == (width * height * 4) as usize { - pixels.chunks_exact(4).flat_map(|p| [p[0], p[1], p[2]]).collect::>().into() + pixels.as_chunks::<4>().0.iter().flat_map(|p| [p[0], p[1], p[2]]).collect::>().into() } else { pixels.into() }; diff --git a/crates/organize/src/labels.rs b/crates/organize/src/labels.rs index 50b8beca8..50a0eed0d 100644 --- a/crates/organize/src/labels.rs +++ b/crates/organize/src/labels.rs @@ -131,7 +131,7 @@ pub fn page_label_ranges(doc: &Document) -> Vec { let node = doc.resolve(&node); let Some(d) = node.as_dict() else { continue }; if let Some(nums) = d.get(b"Nums").map(|n| doc.resolve(n)).and_then(|n| n.as_array().cloned()) { - for pair in nums.chunks_exact(2) { + for pair in nums.as_chunks::<2>().0 { let (Some(start), Some(spec)) = (pair[0].as_int(), doc.resolve(&pair[1]).as_dict().cloned()) else { continue }; if start < 0 { continue; diff --git a/crates/redact/src/interp.rs b/crates/redact/src/interp.rs index 6b26e7488..2feb7679b 100644 --- a/crates/redact/src/interp.rs +++ b/crates/redact/src/interp.rs @@ -227,7 +227,7 @@ pub(crate) fn process(doc: &mut Document, scope: &mut Scope<'_>, streams: &[Vec< let pts: Vec<(f64, f64)> = match o { b"re" => op.nums::<4>().map(|[x, y, w, h]| vec![(x, y), (x + w, y), (x, y + h), (x + w, y + h)]).unwrap_or_default(), b"h" | b"W" | b"W*" => Vec::new(), - _ => op.operands.iter().filter_map(Object::as_f64).collect::>().chunks_exact(2).map(|p| (p[0], p[1])).collect(), + _ => op.operands.iter().filter_map(Object::as_f64).collect::>().as_chunks::<2>().0.iter().map(|p| (p[0], p[1])).collect(), }; for (x, y) in pts { let (u, v) = gs.ctm.apply(x, y); diff --git a/crates/redact/src/lib.rs b/crates/redact/src/lib.rs index 3835e07da..dd310a888 100644 --- a/crates/redact/src/lib.rs +++ b/crates/redact/src/lib.rs @@ -141,7 +141,9 @@ pub fn marks(doc: &Document) -> Vec { .and_then(|q| doc.resolve(q).as_array().map(|a| a.iter().filter_map(Object::as_f64).collect())) .unwrap_or_default(); let mut rects: Vec<[f64; 4]> = quads - .chunks_exact(8) + .as_chunks::<8>() + .0 + .iter() .map(|q| { let xs = [q[0], q[2], q[4], q[6]]; let ys = [q[1], q[3], q[5], q[7]]; diff --git a/crates/render/src/inspect.rs b/crates/render/src/inspect.rs index 8248b7092..a5f69fbdf 100644 --- a/crates/render/src/inspect.rs +++ b/crates/render/src/inspect.rs @@ -586,7 +586,7 @@ impl<'a> Inspector<'a> { let quads = match d.get(b"QuadPoints").map(|o| self.resolve(o)) { Ok(Object::Array(q)) => { let v: Vec = q.iter().map(|o| o.as_float().unwrap_or(0.0)).collect(); - v.chunks_exact(8).map(|c| <[f32; 8]>::try_from(c).unwrap_or_default()).collect() + v.as_chunks::<8>().0.to_vec() } _ => Vec::new(), }; diff --git a/crates/render/src/raster.rs b/crates/render/src/raster.rs index 8fab25fdf..925c05277 100644 --- a/crates/render/src/raster.rs +++ b/crates/render/src/raster.rs @@ -742,7 +742,7 @@ trailer << /Root 1 0 R >> assert_eq!((looped.width, looped.height), (40, 40)); let plain = render(pdf("1 0 d0 0 0 1 1 re f")); assert!(plain.error.is_none(), "{:?}", plain.error); - assert!(plain.rgba.chunks_exact(4).any(|p| p[0] < 128), "a normal Type 3 glyph still paints"); + assert!(plain.rgba.as_chunks::<4>().0.iter().any(|p| p[0] < 128), "a normal Type 3 glyph still paints"); } /// From `cargo xtask fuzz`: an inline image claiming /W 4294967295 over four bytes of data diff --git a/crates/sign/src/der.rs b/crates/sign/src/der.rs index 611494b5f..4274ff600 100644 --- a/crates/sign/src/der.rs +++ b/crates/sign/src/der.rs @@ -140,7 +140,7 @@ impl<'a> Tlv<'a> { tag::UTF8_STRING | tag::PRINTABLE_STRING | tag::IA5_STRING => Some(String::from_utf8_lossy(self.value).into_owned()), tag::T61_STRING => Some(self.value.iter().map(|b| *b as char).collect()), tag::BMP_STRING => { - let units: Vec = self.value.chunks_exact(2).map(|c| u16::from_be_bytes([c[0], c[1]])).collect(); + let units: Vec = self.value.as_chunks::<2>().0.iter().map(|c| u16::from_be_bytes([c[0], c[1]])).collect(); Some(String::from_utf16_lossy(&units)) } _ => None, diff --git a/crates/ui-egui/src/fill_sign.rs b/crates/ui-egui/src/fill_sign.rs index 8ec400e8c..6495a8dd5 100644 --- a/crates/ui-egui/src/fill_sign.rs +++ b/crates/ui-egui/src/fill_sign.rs @@ -200,7 +200,7 @@ pub(crate) fn script_preview(text: &str, w: usize, h: usize) -> egui::ColorImage } } xs.sort_by(|a, b| a.total_cmp(b)); - for pair in xs.chunks_exact(2) { + for pair in xs.as_chunks::<2>().0 { let (from, to) = (pair[0].round().max(0.0) as usize, (pair[1].round() as usize).min(w)); for x in from..to { img[(x, y)] = Color32::BLACK; diff --git a/crates/ui-egui/src/zoom_snap.rs b/crates/ui-egui/src/zoom_snap.rs index 64ed35666..0a4c1e4c5 100644 --- a/crates/ui-egui/src/zoom_snap.rs +++ b/crates/ui-egui/src/zoom_snap.rs @@ -149,7 +149,7 @@ pub(crate) fn ink_bounds(w: usize, h: usize, rgba: &[u8]) -> Option<[f32; 4]> { let ink = |p: &[u8]| p[3] > 16 && (p[0] < 240 || p[1] < 240 || p[2] < 240); let (mut x0, mut y0, mut x1, mut y1) = (w, h, 0, 0); for (y, row) in rgba.chunks_exact(w * 4).take(h).enumerate() { - for (x, p) in row.chunks_exact(4).enumerate() { + for (x, p) in row.as_chunks::<4>().0.iter().enumerate() { if ink(p) { x0 = x0.min(x); x1 = x1.max(x + 1); diff --git a/crates/ui-egui/tests/viewer.rs b/crates/ui-egui/tests/viewer.rs index 90a35c26c..25cbfcc77 100644 --- a/crates/ui-egui/tests/viewer.rs +++ b/crates/ui-egui/tests/viewer.rs @@ -252,7 +252,7 @@ fn marquee_zoom_and_snapshot() { drag(&mut h, at(15.0, 175.0), at(180.0, 140.0)); let (w, hgt, px) = h.state().last_snapshot.clone().expect("a snapshot"); assert!(w > 50 && hgt > 10, "{w} × {hgt}"); - assert!(px.chunks_exact(4).any(|p| p[0] < 100), "the text is in it"); + assert!(px.as_chunks::<4>().0.iter().any(|p| p[0] < 100), "the text is in it"); // Marquee zoom on a small area zooms in, centred on it. let before = h.state().views[i].zoom; h.state_mut().set_option("quick", "marquee-zoom").unwrap(); diff --git a/crates/xfdf/src/lib.rs b/crates/xfdf/src/lib.rs index aa8399acc..09566d3be 100644 --- a/crates/xfdf/src/lib.rs +++ b/crates/xfdf/src/lib.rs @@ -256,7 +256,7 @@ fn xfdf_annots(doc: &Document, out: &mut String) { out.push_str(""); for g in ink { let v = nums_of(doc, Some(&g)); - let pts: Vec = v.chunks_exact(2).map(|p| format!("{},{}", n(p[0]), n(p[1]))).collect(); + let pts: Vec = v.as_chunks::<2>().0.iter().map(|p| format!("{},{}", n(p[0]), n(p[1]))).collect(); let _ = write!(out, "{}", pts.join(";")); } out.push_str(""); diff --git a/xtask/src/demo_pdf/postprocess.rs b/xtask/src/demo_pdf/postprocess.rs index ed1fb488e..e122fa642 100644 --- a/xtask/src/demo_pdf/postprocess.rs +++ b/xtask/src/demo_pdf/postprocess.rs @@ -364,7 +364,7 @@ fn collect_outline(doc: &Document, node: ObjectId, out: &mut Vec) { /// Decode a PDF text string (UTF-16BE with BOM, else treated as Latin-1). fn decode_text(bytes: &[u8]) -> String { if let Some(rest) = bytes.strip_prefix(&[0xfe, 0xff]) { - let units: Vec = rest.chunks_exact(2).map(|c| u16::from_be_bytes([c[0], c[1]])).collect(); + let units: Vec = rest.as_chunks::<2>().0.iter().map(|c| u16::from_be_bytes([c[0], c[1]])).collect(); String::from_utf16_lossy(&units) } else { bytes.iter().map(|&b| b as char).collect()