From a96383c033bdafff335a0501e2288c4cf798445c Mon Sep 17 00:00:00 2001 From: Christian Jaeger Date: Fri, 4 Sep 2026 23:23:46 +0200 Subject: [PATCH 1/9] patchparser: remove the remaining `ReborrowIn` as well as `CloneIn` implementations --- patchparser/src/line.rs | 19 ---------- patchparser/src/patch/diff.rs | 55 ---------------------------- patchparser/src/patch/parsed_hunk.rs | 20 ---------- 3 files changed, 94 deletions(-) diff --git a/patchparser/src/line.rs b/patchparser/src/line.rs index 37bde39..94046e1 100644 --- a/patchparser/src/line.rs +++ b/patchparser/src/line.rs @@ -12,8 +12,6 @@ use bumpalo::{ Bump, }; -use crate::{bumpalo_cow::CloneIn, reborrow_in::ReborrowIn}; - /// A reference to a line string without the line ending and the line /// number for location reporting #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -31,23 +29,6 @@ impl<'a> Deref for Line<'a> { } } -impl<'a> CloneIn<'a> for Line<'a> { - fn clone_in(&self, _bump: &'a bumpalo::Bump) -> Self { - *self - } -} - -impl<'a, 'b> ReborrowIn<'b> for Line<'a> -where - 'a: 'b, -{ - type Reborrowed = Line<'b>; - - fn reborrow_in(&self, _bump: &'b Bump) -> Self::Reborrowed { - *self - } -} - impl<'a> Display for Line<'a> { /// Display line number rather than the line contents fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { diff --git a/patchparser/src/patch/diff.rs b/patchparser/src/patch/diff.rs index b8fda3a..1814cb5 100644 --- a/patchparser/src/patch/diff.rs +++ b/patchparser/src/patch/diff.rs @@ -14,7 +14,6 @@ use crate::{ hunk::{Hunk, ParseMode}, parsed_hunk::HandleCheckError, }, - reborrow_in::ReborrowIn, write_to::WriteTo, }; @@ -28,28 +27,6 @@ pub struct DiffDifferences<'a> { pub hunks: &'a [Hunk<'a>], } -impl<'a, 'b> ReborrowIn<'b> for DiffDifferences<'a> -where - 'a: 'b, -{ - type Reborrowed = DiffDifferences<'b>; - - fn reborrow_in(&self, _bump: &'b Bump) -> DiffDifferences<'b> { - let Self { - index_line, - minus_line, - plus_line, - hunks, - } = self; - DiffDifferences { - index_line: *index_line, - minus_line: *minus_line, - plus_line: *plus_line, - hunks, - } - } -} - /// A bare diff for a single file. (A Patch file represents any number /// of Diff instances.) #[derive(Clone, PartialEq, Eq)] @@ -104,38 +81,6 @@ fn t_strip_leading_path_segment() { ); } -impl<'a, 'b> ReborrowIn<'b> for Diff<'a> -where - 'a: 'b, -{ - type Reborrowed = Diff<'b>; - - fn reborrow_in(&self, bump: &'b Bump) -> Diff<'b> { - let Self { - diff_line, - diff_path_a_full, - diff_path_b_full, - newfile_line, - deleted_line, - similarity_line, - rename_from_line, - rename_to_line, - differences, - } = self; - Diff { - diff_line: *diff_line, - diff_path_a_full: *diff_path_a_full, - diff_path_b_full: *diff_path_b_full, - newfile_line: *newfile_line, - deleted_line: *deleted_line, - similarity_line: *similarity_line, - rename_from_line: *rename_from_line, - rename_to_line: *rename_to_line, - differences: differences.as_ref().map(|v| v.reborrow_in(bump)), - } - } -} - impl<'a> Diff<'a> { /// Replace the hunks with the given ones, while keeping file /// context information (except for deleting if diff --git a/patchparser/src/patch/parsed_hunk.rs b/patchparser/src/patch/parsed_hunk.rs index 9540f86..953d6d0 100644 --- a/patchparser/src/patch/parsed_hunk.rs +++ b/patchparser/src/patch/parsed_hunk.rs @@ -15,7 +15,6 @@ use crate::{ change_line::{ChangeLineKind, ChangeLineReport}, }, re, - reborrow_in::ReborrowIn, regex_utils::GetStr, write_to::WriteTo, }; @@ -166,25 +165,6 @@ impl<'a> WriteTo for ParsedHunk<'a> { } } -// XX do we still want that? -impl<'a, 'b> ReborrowIn<'b> for ParsedHunk<'a> -where - 'a: 'b, -{ - type Reborrowed = ParsedHunk<'b>; - - fn reborrow_in(&self, _bump: &'b Bump) -> Self::Reborrowed - where - 'a: 'b, - { - let Self { head, changes } = self; - ParsedHunk { - head: head.clone(), - changes, - } - } -} - // -------------------------------------------- #[derive(Debug)] From 7e4b4b62eb426ca510084ee04c72372c31fa5fb6 Mon Sep 17 00:00:00 2001 From: Christian Jaeger Date: Fri, 4 Sep 2026 23:24:59 +0200 Subject: [PATCH 2/9] patchparser: remove `bumpalo_cow.rs` and `reborrow_in.rs` With the new non-owning approach, the parser/AST does not need them anymore. --- patchparser/src/bumpalo_cow.rs | 320 --------------------------------- patchparser/src/lib.rs | 2 - patchparser/src/reborrow_in.rs | 9 - 3 files changed, 331 deletions(-) delete mode 100644 patchparser/src/bumpalo_cow.rs delete mode 100644 patchparser/src/reborrow_in.rs diff --git a/patchparser/src/bumpalo_cow.rs b/patchparser/src/bumpalo_cow.rs deleted file mode 100644 index c9a35f2..0000000 --- a/patchparser/src/bumpalo_cow.rs +++ /dev/null @@ -1,320 +0,0 @@ -//! A copy of `std::borrow::Cow` adapted for allocation from bumpalo - -use core::fmt; -// #[cfg(not(no_global_oom_handling))] -use std::{ - borrow::Borrow, - cmp::Ordering, - hash::{Hash, Hasher}, - ops::Deref, -}; - -use bumpalo::{collections as bc, Bump}; - -pub trait ToOwnedIn<'b> { - type Owned: Borrow; - - fn to_owned_in(&self, bump: &'b Bump) -> Self::Owned; - - fn clone_into_in(&self, target: &mut Self::Owned, bump: &'b Bump) { - *target = self.to_owned_in(bump); - } -} - -// Also see `ReborrowIn` -pub trait CloneIn<'b>: Sized { - #[must_use] - fn clone_in(&self, bump: &'b Bump) -> Self; - - fn clone_from_in(&mut self, source: &Self, bump: &'b Bump) - // where - // Self: ~const Destruct, - { - *self = source.clone_in(bump); - } -} - -impl<'a, T> CloneIn<'a> for &'a T { - fn clone_in(&self, _bump: &'a Bump) -> Self { - self - } -} - -pub enum BumpaloCow<'b, 'a, B: ?Sized + 'a> -where - B: ToOwnedIn<'b>, -{ - Borrowed(&'a B), - Owned(>::Owned), -} - -impl<'b, 'a, B: ?Sized + 'a> From<&'a B> for BumpaloCow<'b, 'a, B> -where - B: ToOwnedIn<'b>, -{ - fn from(value: &'a B) -> Self { - BumpaloCow::Borrowed(value) - } -} - -// Conflicting implementations with From for T in core. -// impl<'b, 'a, B: ?Sized + 'a> From<>::Owned> for BumpaloCow<'b, 'a, B> -// where -// B: ToOwnedIn<'b>, -// { -// fn from(value: >::Owned) -> Self { -// BumpaloCow::Owned(value) -// } -// } - -impl<'b, B: ?Sized + ToOwnedIn<'b>> CloneIn<'b> for BumpaloCow<'b, '_, B> { - fn clone_in(&self, bump: &'b Bump) -> Self { - use BumpaloCow::*; - match *self { - Borrowed(b) => Borrowed(b), - Owned(ref o) => { - let b: &B = o.borrow(); - Owned(b.to_owned_in(bump)) - } - } - } - - fn clone_from_in(&mut self, source: &Self, bump: &'b Bump) { - use BumpaloCow::*; - match (self, source) { - (&mut Owned(ref mut dest), Owned(o)) => o.borrow().clone_into_in(dest, bump), - (t, s) => *t = s.clone_in(bump), - } - } -} - -impl<'b, B: ?Sized + ToOwnedIn<'b>> Clone for BumpaloCow<'b, '_, B> -where - >::Owned: Clone, -{ - fn clone(&self) -> Self { - use BumpaloCow::*; - match *self { - Borrowed(b) => Borrowed(b), - Owned(ref o) => Owned(o.clone()), - } - } - - fn clone_from(&mut self, source: &Self) { - use BumpaloCow::*; - match (self, source) { - (&mut Owned(ref mut dest), Owned(o)) => o.clone_into(dest), - (t, s) => *t = s.clone(), - } - } -} - -impl<'b, B: ?Sized + ToOwnedIn<'b>> BumpaloCow<'b, '_, B> { - pub const fn is_borrowed(&self) -> bool { - use BumpaloCow::*; - match *self { - Borrowed(_) => true, - Owned(_) => false, - } - } - - pub const fn is_owned(&self) -> bool { - !self.is_borrowed() - } - - pub fn to_mut(&mut self, bump: &'b Bump) -> &mut >::Owned { - use BumpaloCow::*; - match *self { - Borrowed(borrowed) => { - *self = Owned(borrowed.to_owned_in(bump)); - match *self { - Borrowed(..) => unreachable!(), - Owned(ref mut owned) => owned, - } - } - Owned(ref mut owned) => owned, - } - } - - pub fn into_owned_in(self, bump: &'b Bump) -> >::Owned { - use BumpaloCow::*; - match self { - Borrowed(borrowed) => borrowed.to_owned_in(bump), - Owned(owned) => owned, - } - } -} - -impl<'b, B: ?Sized + ToOwnedIn<'b>> Deref for BumpaloCow<'b, '_, B> -where - B::Owned: Borrow, -{ - type Target = B; - - fn deref(&self) -> &B { - use BumpaloCow::*; - match *self { - Borrowed(borrowed) => borrowed, - Owned(ref owned) => owned.borrow(), - } - } -} - -impl<'b, B: ?Sized> Eq for BumpaloCow<'b, '_, B> where B: Eq + ToOwnedIn<'b> {} - -impl<'b, B: ?Sized> Ord for BumpaloCow<'b, '_, B> -where - B: Ord + ToOwnedIn<'b>, -{ - #[inline] - fn cmp(&self, other: &Self) -> Ordering { - Ord::cmp(&**self, &**other) - } -} - -impl<'bump, 'a, 'b, B: ?Sized, C: ?Sized> PartialEq> - for BumpaloCow<'bump, 'a, B> -where - B: PartialEq + ToOwnedIn<'bump>, - C: ToOwnedIn<'bump>, -{ - #[inline] - fn eq(&self, other: &BumpaloCow<'bump, 'b, C>) -> bool { - PartialEq::eq(&**self, &**other) - } -} - -impl<'bump, 'a, B: ?Sized> PartialOrd for BumpaloCow<'bump, 'a, B> -where - B: PartialOrd + ToOwnedIn<'bump>, -{ - // XXX this is wrong, no requirement for the same 'bump, please - #[inline] - fn partial_cmp(&self, other: &BumpaloCow<'bump, 'a, B>) -> Option { - PartialOrd::partial_cmp(&**self, &**other) - } -} - -impl<'bump, B: ?Sized> fmt::Debug for BumpaloCow<'bump, '_, B> -where - B: fmt::Debug + ToOwnedIn<'bump, Owned: fmt::Debug>, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use BumpaloCow::*; - match *self { - Borrowed(ref b) => fmt::Debug::fmt(b, f), - Owned(ref o) => fmt::Debug::fmt(o, f), - } - } -} - -impl<'bump, B: ?Sized> fmt::Display for BumpaloCow<'bump, '_, B> -where - B: fmt::Display + ToOwnedIn<'bump, Owned: fmt::Display>, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use BumpaloCow::*; - match *self { - Borrowed(ref b) => fmt::Display::fmt(b, f), - Owned(ref o) => fmt::Display::fmt(o, f), - } - } -} - -impl<'bump, B: ?Sized> Default for BumpaloCow<'bump, '_, B> -where - B: ToOwnedIn<'bump, Owned: Default>, -{ - /// Creates an owned Cow<'a, B> with the default value for the contained owned value. - fn default() -> Self { - use BumpaloCow::*; - Owned(::Owned::default()) - } -} - -impl<'bump, B: ?Sized> Hash for BumpaloCow<'bump, '_, B> -where - B: Hash + ToOwnedIn<'bump>, -{ - #[inline] - fn hash(&self, state: &mut H) { - Hash::hash(&**self, state) - } -} - -impl<'bump, T: ?Sized + ToOwnedIn<'bump>> AsRef for BumpaloCow<'bump, '_, T> { - fn as_ref(&self) -> &T { - self - } -} - -impl<'bump> ToOwnedIn<'bump> for str { - type Owned = bc::String<'bump>; - - fn to_owned_in(&self, bump: &'bump Bump) -> Self::Owned { - bc::String::from_str_in(self, bump) - } -} - -// Can't implement these as there is no access to bump; or should we -// store that inside BorrowCow? - -// impl<'bump, 'a> Add<&'a str> for BumpaloCow<'bump, 'a, str> { -// type Output = BumpaloCow<'bump, 'a, str>; - -// #[inline] -// fn add(mut self, rhs: &'a str) -> Self::Output { -// self += rhs; -// self -// } -// } - -// impl<'bump, 'a> Add> for BumpaloCow<'bump, 'a, str> { -// type Output = BumpaloCow<'bump, 'a, str>; - -// #[inline] -// fn add(mut self, rhs: BumpaloCow<'bump, 'a, str>) -> Self::Output { -// self += rhs; -// self -// } -// } - -// // #[cfg(not(no_global_oom_handling))] -// impl<'bump, 'a> AddAssign<&'a str> for BumpaloCow<'bump, 'a, str> { -// fn add_assign(&mut self, rhs: &'a str) { -// if self.is_empty() { -// *self = BumpaloCow::Borrowed(rhs) -// } else if !rhs.is_empty() { -// if let BumpaloCow::Borrowed(lhs) = *self { -// let mut s = bc::String::with_capacity_in(lhs.len() + rhs.len(), bump); -// s.push_str(lhs); -// *self = BumpaloCow::Owned(s); -// } -// self.to_mut().push_str(rhs); -// } -// } -// } - -// // #[cfg(not(no_global_oom_handling))] -// impl<'bump, 'a> AddAssign> for BumpaloCow<'bump, 'a, str> { -// fn add_assign(&mut self, rhs: BumpaloCow<'bump, 'a, str>) { -// if self.is_empty() { -// *self = rhs -// } else if !rhs.is_empty() { -// if let BumpaloCow::Borrowed(lhs) = *self { -// let mut s = String::with_capacity(lhs.len() + rhs.len()); -// s.push_str(lhs); -// *self = BumpaloCow::Owned(s); -// } -// self.to_mut().push_str(&rhs); -// } -// } -// } - -impl<'a, T: 'a + CloneIn<'a>> ToOwnedIn<'a> for [T] { - type Owned = bc::Vec<'a, T>; - - fn to_owned_in(&self, bump: &'a Bump) -> Self::Owned { - bc::Vec::from_iter_in(self.iter().map(|v| v.clone_in(bump)), bump) - } -} diff --git a/patchparser/src/lib.rs b/patchparser/src/lib.rs index 96a648e..f43b391 100644 --- a/patchparser/src/lib.rs +++ b/patchparser/src/lib.rs @@ -1,12 +1,10 @@ pub mod anyhow_once; pub mod bumpalo_bstring; -pub mod bumpalo_cow; pub mod bumpalo_utils; pub mod format_binary; pub mod from_lines; pub mod line; pub mod patch; -pub mod reborrow_in; pub mod regex_utils; pub mod utils; pub mod write_to; diff --git a/patchparser/src/reborrow_in.rs b/patchparser/src/reborrow_in.rs deleted file mode 100644 index bfca02a..0000000 --- a/patchparser/src/reborrow_in.rs +++ /dev/null @@ -1,9 +0,0 @@ -use bumpalo::Bump; - -/// Unlike `CloneIn`, this allows to change the type; implementors are -/// meant to only change lifetime parameters of the type. -pub trait ReborrowIn<'b>: Sized { - type Reborrowed: 'b; - - fn reborrow_in(&self, bump: &'b Bump) -> Self::Reborrowed; -} From 54f7f29f22d417c720fafdc0b9a0923b314887c6 Mon Sep 17 00:00:00 2001 From: Christian Jaeger Date: Fri, 4 Sep 2026 23:32:29 +0200 Subject: [PATCH 3/9] pathparser: Patch::update_header: copy `header_lines` only when actually mutated --- patchparser/src/patch/patch.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/patchparser/src/patch/patch.rs b/patchparser/src/patch/patch.rs index ff4819c..f8719ef 100644 --- a/patchparser/src/patch/patch.rs +++ b/patchparser/src/patch/patch.rs @@ -162,16 +162,18 @@ impl<'a> PatchHeadHeader<'a> { bump: &'a Bump, ) -> (&'a Self, Option<&'a BStr>) { let header_name = header_name.as_ref(); - let header_lines = bump.alloc_slice_copy(self.header_lines); - for line in header_lines.iter_mut() { - if let Some(header_line) = HeaderLine::from_line(*line) { + + for (header_line_i, header_line) in self.header_lines.iter().enumerate() { + if let Some(header_line) = HeaderLine::from_line(*header_line) { if string_equal_ci(header_line.mixed_case_header_name, header_name) { if let Some(replacement) = f(header_line.value.as_ref()) { let mut contents = b::BString::new_in(bump); contents.extend_from_slice(header_line.mixed_case_header_name); contents.extend_from_slice(header_line.separator); contents.extend_from_slice(&replacement); - line.set_contents(contents.into_bump_slice()); + let header_lines = bump.alloc_slice_copy(self.header_lines); + header_lines[header_line_i].set_contents(contents.into_bump_slice()); + let this = bump.alloc(Self { from_line: self.from_line, header_lines, From 4cbee7771bcc4063fd5e5c73df619917fb86f406 Mon Sep 17 00:00:00 2001 From: Christian Jaeger Date: Fri, 4 Sep 2026 23:55:56 +0200 Subject: [PATCH 4/9] patchparser: handle `Diff` universally by reference, too It is quite large, hence Clone is wasteful. Also, should universally never need Clone: either the type is Copy, then include it directly, or otherwise handle it by reference. - The `.map(|reference| &*reference)` is quite ugly. It is to turn the mutable reference into a shared one. (This is necessary because while an individual mutable reference is automatically cast to a shared one, after packing them up in the Vec no such conversion would happen anymore.) Not sure what to do about it. --- patchparser/examples/generate_patch.rs | 2 +- patchparser/src/patch/diff.rs | 6 +++--- patchparser/src/patch/patch.rs | 3 ++- split-patch/src/core.rs | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/patchparser/examples/generate_patch.rs b/patchparser/examples/generate_patch.rs index 14faabc..c29868b 100644 --- a/patchparser/examples/generate_patch.rs +++ b/patchparser/examples/generate_patch.rs @@ -77,7 +77,7 @@ fn main() -> Result<()> { }), remaining_lines: &["Hey there!".into(), "".into()], }, - diffs: bump.alloc([diff]), + diffs: bump.alloc([&diff]), footer: &[], }; diff --git a/patchparser/src/patch/diff.rs b/patchparser/src/patch/diff.rs index 1814cb5..2c64ce5 100644 --- a/patchparser/src/patch/diff.rs +++ b/patchparser/src/patch/diff.rs @@ -220,7 +220,7 @@ impl<'a> Diff<'a> { bump: &'a Bump, parse_mode: ParseMode, handle_check_error: impl HandleCheckError, - ) -> Result> { + ) -> Result<&'a mut Diff<'a>> { let mut lines = lines_slice.iter(); let diff_line = *lines @@ -328,7 +328,7 @@ impl<'a> Diff<'a> { None }; - Ok(Diff { + Ok(bump.alloc(Diff { diff_line, diff_path_a_full, diff_path_b_full, @@ -338,6 +338,6 @@ impl<'a> Diff<'a> { rename_from_line, rename_to_line, differences, - }) + })) } } diff --git a/patchparser/src/patch/patch.rs b/patchparser/src/patch/patch.rs index f8719ef..d1c0216 100644 --- a/patchparser/src/patch/patch.rs +++ b/patchparser/src/patch/patch.rs @@ -303,7 +303,7 @@ pub struct Patch<'a> { /// The head represents the lines found before the first "diff " /// line. pub head: &'a PatchHead<'a>, - pub diffs: &'a [Diff<'a>], + pub diffs: &'a [&'a Diff<'a>], /// The lines from "-- " onwards in "git format-patch" style /// files, including the "-- " line. pub footer: &'a [Line<'a>], @@ -370,6 +370,7 @@ impl<'a> Patch<'a> { diff_lines_groups.len() ) }) + .map(|reference| &*reference) }) .collect_in::>>(bump)?; diff --git a/split-patch/src/core.rs b/split-patch/src/core.rs index dc1a643..c9d8528 100644 --- a/split-patch/src/core.rs +++ b/split-patch/src/core.rs @@ -163,7 +163,7 @@ fn write_patch_file<'a>( if split_options.dry_run { Ok(output_path.into()) } else { - let diffs = [(*diff).clone()]; + let diffs = [diff]; let patch = Patch { head, diffs: &diffs, From 5ac1d09ba3a6cbbcc9015247446254bea5ea843b Mon Sep 17 00:00:00 2001 From: Christian Jaeger Date: Sat, 5 Sep 2026 00:17:05 +0200 Subject: [PATCH 5/9] examples: locally owned storage works fine here, too --- patchparser/examples/generate_patch.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patchparser/examples/generate_patch.rs b/patchparser/examples/generate_patch.rs index c29868b..00eb551 100644 --- a/patchparser/examples/generate_patch.rs +++ b/patchparser/examples/generate_patch.rs @@ -77,7 +77,7 @@ fn main() -> Result<()> { }), remaining_lines: &["Hey there!".into(), "".into()], }, - diffs: bump.alloc([&diff]), + diffs: &[&diff], footer: &[], }; From 574e14e777053c603da027c479ecd32bf1aec9b6 Mon Sep 17 00:00:00 2001 From: Yusuf Raji Date: Sat, 5 Sep 2026 12:37:57 +0200 Subject: [PATCH 6/9] patchparser: handle `DiffDifferences` universally by reference, too --- patchparser/examples/generate_patch.rs | 2 +- patchparser/src/patch/diff.rs | 19 ++++++++++++++----- split-patch/src/core.rs | 2 +- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/patchparser/examples/generate_patch.rs b/patchparser/examples/generate_patch.rs index 00eb551..a59d09f 100644 --- a/patchparser/examples/generate_patch.rs +++ b/patchparser/examples/generate_patch.rs @@ -60,7 +60,7 @@ fn main() -> Result<()> { similarity_line: None, rename_from_line: None, rename_to_line: None, - differences: Some(DiffDifferences { + differences: Some(&DiffDifferences { index_line: Some("index line".into()), minus_line: "minus line".into(), plus_line: "plus line".into(), diff --git a/patchparser/src/patch/diff.rs b/patchparser/src/patch/diff.rs index 2c64ce5..d5f8b28 100644 --- a/patchparser/src/patch/diff.rs +++ b/patchparser/src/patch/diff.rs @@ -43,7 +43,7 @@ pub struct Diff<'a> { pub similarity_line: Option>, pub rename_from_line: Option>, pub rename_to_line: Option>, - pub differences: Option>, + pub differences: Option<&'a DiffDifferences<'a>>, } fn strip_leading_path_segment(s: &BStr) -> Result<&BStr> { @@ -87,15 +87,24 @@ impl<'a> Diff<'a> { /// `delete_index_line` is true). /// /// Panics if self does not contain a `DiffDifferences`. - pub fn set_hunks(&mut self, hunks: &'a [Hunk<'a>], delete_index_line: bool) -> &mut Self { + pub fn set_hunks( + &mut self, + hunks: &'a [Hunk<'a>], + delete_index_line: bool, + bump: &'a Bump, + ) -> &mut Self { let differences = self .differences .as_mut() .expect("`differences` required for setting hunks on Diff"); - differences.hunks = hunks; + let differences = bump.alloc(DiffDifferences { + hunks, + ..**differences + }); if delete_index_line { differences.index_line = None; } + self.differences = Some(differences); self } @@ -318,12 +327,12 @@ impl<'a> Diff<'a> { let hunks = gather_hunks(lines.as_slice(), bump, parse_mode, handle_check_error)? .into_bump_slice(); - Some(DiffDifferences { + Some(&*bump.alloc(DiffDifferences { index_line, minus_line, plus_line, hunks, - }) + })) } else { None }; diff --git a/split-patch/src/core.rs b/split-patch/src/core.rs index c9d8528..15a3193 100644 --- a/split-patch/src/core.rs +++ b/split-patch/src/core.rs @@ -67,7 +67,7 @@ fn split_diff_in<'a>( macro_rules! diff_with_hunk { { $hunk:expr } => { diff.clone() - .set_hunks(bump.alloc([$hunk]), delete_index_line) + .set_hunks(bump.alloc([$hunk]), delete_index_line, bump) } } From eba17f0d82567425a0e49d0cbf588d615d42064e Mon Sep 17 00:00:00 2001 From: Yusuf Raji Date: Sat, 5 Sep 2026 13:35:18 +0200 Subject: [PATCH 7/9] patchparser: change `Diff::set_hunks` to do functional updates --- patchparser/src/patch/diff.rs | 10 ++++++---- split-patch/src/core.rs | 3 +-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/patchparser/src/patch/diff.rs b/patchparser/src/patch/diff.rs index d5f8b28..f5afe0f 100644 --- a/patchparser/src/patch/diff.rs +++ b/patchparser/src/patch/diff.rs @@ -88,14 +88,14 @@ impl<'a> Diff<'a> { /// /// Panics if self does not contain a `DiffDifferences`. pub fn set_hunks( - &mut self, + &self, hunks: &'a [Hunk<'a>], delete_index_line: bool, bump: &'a Bump, ) -> &mut Self { let differences = self .differences - .as_mut() + .as_ref() .expect("`differences` required for setting hunks on Diff"); let differences = bump.alloc(DiffDifferences { hunks, @@ -104,8 +104,10 @@ impl<'a> Diff<'a> { if delete_index_line { differences.index_line = None; } - self.differences = Some(differences); - self + bump.alloc(Self { + differences: Some(differences), + ..*self + }) } /// Not the head of the patch (i.e. mail headers / commit diff --git a/split-patch/src/core.rs b/split-patch/src/core.rs index 15a3193..8942494 100644 --- a/split-patch/src/core.rs +++ b/split-patch/src/core.rs @@ -66,8 +66,7 @@ fn split_diff_in<'a>( macro_rules! diff_with_hunk { { $hunk:expr } => { - diff.clone() - .set_hunks(bump.alloc([$hunk]), delete_index_line, bump) + diff.set_hunks(bump.alloc([$hunk]), delete_index_line, bump) } } From 5f3a35b05a2888aa482b72b23897ffa9d003b792 Mon Sep 17 00:00:00 2001 From: Yusuf Raji and Christian Jaeger Date: Sat, 5 Sep 2026 16:46:27 +0200 Subject: [PATCH 8/9] Makefile: eliminate `cargo_check` in favour of a `cargo_test` with color --- Makefile | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index 7bd2628..0c10a81 100644 --- a/Makefile +++ b/Makefile @@ -10,14 +10,9 @@ check_formatting: fmt cargo_test: @echo "++ Run cargo $(OUR_CARGO_FLAGS) test on both crates" - ( cd patchparser && cargo $(OUR_CARGO_FLAGS) test ) - ( cd patchparser && cargo $(OUR_CARGO_FLAGS) test --examples ) - ( cd split-patch && cargo $(OUR_CARGO_FLAGS) test ) - -cargo_check: - ( cd patchparser && cargo $(OUR_CARGO_FLAGS) test --color=always ) - ( cd patchparser && cargo $(OUR_CARGO_FLAGS) build --examples --color=always ) - ( cd split-patch && cargo $(OUR_CARGO_FLAGS) test --color=always ) + ( cd patchparser && cargo $(OUR_CARGO_FLAGS) test --color=always) + ( cd patchparser && cargo $(OUR_CARGO_FLAGS) test --examples --color=always ) + ( cd split-patch && cargo $(OUR_CARGO_FLAGS) test --color=always) # This target is to abstract running clippy on everything (and can be run manually) clippy: From 424303d2779674d40c94effc4968f912ddbeaa1c Mon Sep 17 00:00:00 2001 From: Yusuf Raji and Christian Jaeger Date: Sat, 5 Sep 2026 17:11:01 +0200 Subject: [PATCH 9/9] patchparser: add `coerce_ref.rs` Will this prove useful? --- patchparser/src/coerce_ref.rs | 28 ++++++++++++++++++++++++++++ patchparser/src/lib.rs | 1 + patchparser/src/patch/diff.rs | 16 ++++++++++------ patchparser/src/patch/parsed_hunk.rs | 19 +++++++++++-------- patchparser/src/patch/patch.rs | 5 +++-- 5 files changed, 53 insertions(+), 16 deletions(-) create mode 100644 patchparser/src/coerce_ref.rs diff --git a/patchparser/src/coerce_ref.rs b/patchparser/src/coerce_ref.rs new file mode 100644 index 0000000..1f9d303 --- /dev/null +++ b/patchparser/src/coerce_ref.rs @@ -0,0 +1,28 @@ +pub trait CoerceRef { + type U; + fn coerce_ref(self) -> Self::U; +} + +impl<'t, T, E> CoerceRef for Result<&'t mut T, E> { + type U = Result<&'t T, E>; + + fn coerce_ref(self) -> Self::U { + self.map(|v| -> &T { v }) + } +} + +impl<'t, T, const N: usize> CoerceRef for [&'t mut T; N] { + type U = [&'t T; N]; + + fn coerce_ref(self) -> Self::U { + self.map(|v| -> &T { v }) + } +} + +impl<'t, T> CoerceRef for &'t mut T { + type U = &'t T; + + fn coerce_ref(self) -> Self::U { + self + } +} diff --git a/patchparser/src/lib.rs b/patchparser/src/lib.rs index f43b391..2a6d3c0 100644 --- a/patchparser/src/lib.rs +++ b/patchparser/src/lib.rs @@ -1,6 +1,7 @@ pub mod anyhow_once; pub mod bumpalo_bstring; pub mod bumpalo_utils; +pub mod coerce_ref; pub mod format_binary; pub mod from_lines; pub mod line; diff --git a/patchparser/src/patch/diff.rs b/patchparser/src/patch/diff.rs index f5afe0f..7c7552f 100644 --- a/patchparser/src/patch/diff.rs +++ b/patchparser/src/patch/diff.rs @@ -9,6 +9,7 @@ use bumpalo::{ use crate::{ bumpalo_utils::try_split_before_in, + coerce_ref::CoerceRef, line::{write_lines_to, Line}, patch::{ hunk::{Hunk, ParseMode}, @@ -329,12 +330,15 @@ impl<'a> Diff<'a> { let hunks = gather_hunks(lines.as_slice(), bump, parse_mode, handle_check_error)? .into_bump_slice(); - Some(&*bump.alloc(DiffDifferences { - index_line, - minus_line, - plus_line, - hunks, - })) + Some( + bump.alloc(DiffDifferences { + index_line, + minus_line, + plus_line, + hunks, + }) + .coerce_ref(), + ) } else { None }; diff --git a/patchparser/src/patch/parsed_hunk.rs b/patchparser/src/patch/parsed_hunk.rs index 953d6d0..1201cd4 100644 --- a/patchparser/src/patch/parsed_hunk.rs +++ b/patchparser/src/patch/parsed_hunk.rs @@ -9,6 +9,7 @@ use bumpalo::{ use crate::{ bumpalo_bstring::BString, + coerce_ref::CoerceRef, line::{write_lines_to, Line}, patch::{ change::{Change, Position}, @@ -364,14 +365,16 @@ fn split_hunk_into_changes<'a>( false }; - let change = &*bump.alloc(Change { - post_from_previous_change, - pre: pre.into_bump_slice(), - minus: minus.into_bump_slice(), - plus: plus.into_bump_slice(), - post: post.into_bump_slice(), - backslash, - }); + let change = bump + .alloc(Change { + post_from_previous_change, + pre: pre.into_bump_slice(), + minus: minus.into_bump_slice(), + plus: plus.into_bump_slice(), + post: post.into_bump_slice(), + backslash, + }) + .coerce_ref(); post_from_previous_change = change.post; changes.push(change); diff --git a/patchparser/src/patch/patch.rs b/patchparser/src/patch/patch.rs index d1c0216..7a52ff5 100644 --- a/patchparser/src/patch/patch.rs +++ b/patchparser/src/patch/patch.rs @@ -9,6 +9,7 @@ use bumpalo::{ use crate::{ bumpalo_bstring as b, + coerce_ref::CoerceRef, from_lines::FromLines, line::{write_lines_to, Line}, patch::{diff::Diff, hunk::ParseMode, parsed_hunk::HandleCheckError}, @@ -361,7 +362,7 @@ impl<'a> Patch<'a> { let diffs = diff_lines_groups .iter() .enumerate() - .map(|(diff_i, diff_lines)| -> Result<_> { + .map(|(diff_i, diff_lines)| -> Result<&Diff> { Diff::from_lines(diff_lines, bump, parse_mode, &mut handle_check_error) .with_context(|| { format!( @@ -370,7 +371,7 @@ impl<'a> Patch<'a> { diff_lines_groups.len() ) }) - .map(|reference| &*reference) + .coerce_ref() }) .collect_in::>>(bump)?;