Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More generic impl of Replacer for closures #1048

Closed
wants to merge 13 commits into from
Closed
85 changes: 80 additions & 5 deletions src/regex/string.rs
Expand Up @@ -2371,6 +2371,28 @@ impl<'c, 'h> ExactSizeIterator for SubCaptureMatches<'c, 'h> {}

impl<'c, 'h> core::iter::FusedIterator for SubCaptureMatches<'c, 'h> {}

/// Contains helper trait for blanket implementation for [`Replacer`].
mod replacer_closure {
use super::*;
/// If a closure implements this for all `&'a Captures<'b>`, then it also
/// implements [`Replacer`].
pub trait ReplacerClosure<Arg>
where
Self: FnMut(Arg) -> <Self as ReplacerClosure<Arg>>::Output,
{
/// Return type of the closure (may depend on lifetime `'a` or `'b`).
type Output: AsRef<str>;
}
impl<'a, 'b, F, O> ReplacerClosure<&'a Captures<'b>> for F
where
F: ?Sized + FnMut(&'a Captures<'b>) -> O,
O: AsRef<str>,
{
type Output = O;
}
}
use replacer_closure::*;

/// A trait for types that can be used to replace matches in a haystack.
///
/// In general, users of this crate shouldn't need to implement this trait,
Expand Down Expand Up @@ -2403,6 +2425,49 @@ impl<'c, 'h> core::iter::FusedIterator for SubCaptureMatches<'c, 'h> {}
/// let result = re.replace("Springsteen, Bruce", NameSwapper);
/// assert_eq!(result, "Bruce Springsteen");
/// ```
///
/// # Implementation by closures
///
/// Closures that take an argument of type `&'a Captures<'b>` (for all `'a`
/// and `'b`) and which return a type `T: AsRef<str>` (that may depend on `'a`
/// or `'b`) implement the `Replacer` trait through a [blanket implementation].
///
/// [blanket implementation]: Self#impl-Replacer-for-F
///
/// A simple example looks like this:
///
/// ```
/// use regex::{Captures, Regex};
///
/// let re = Regex::new(r"[0-9]+").unwrap();
/// let result = re.replace_all("1234,12345", |caps: &Captures<'_>| {
/// format!("[number with {} digits]", caps[0].len())
/// });
/// assert_eq!(result, "[number with 4 digits],[number with 5 digits]");
/// ```
///
/// The return type of the closure may depend on the lifetime of the reference
/// that is passed as an argument to the closure. Using a function, this can be
/// expressed:
///
/// ```
/// use regex::{Captures, Regex};
/// use std::borrow::Cow;
///
/// let re = Regex::new(r"[0-9]+").unwrap();
/// fn func<'a, 'b>(caps: &'a Captures<'b>) -> Cow<'a, str> {
/// if caps[0].len() % 2 == 1 {
/// Cow::Owned(format!("0{}", &caps[0]))
/// } else {
/// Cow::Borrowed(&caps[0])
/// }
/// }
/// let result = re.replace_all("1234,12345", func);
/// assert_eq!(result, "1234,012345");
/// ```
///
/// *Note:* Using a closure instead of a function in the last example can be
/// more tricky and requires a coercing helper function as of yet.
pub trait Replacer {
/// Appends possibly empty data to `dst` to replace the current match.
///
Expand Down Expand Up @@ -2501,11 +2566,21 @@ impl<'a> Replacer for &'a Cow<'a, str> {
}
}

impl<F, T> Replacer for F
where
F: FnMut(&Captures<'_>) -> T,
T: AsRef<str>,
{
/// Blanket implementation of `Replacer` for closures.
///
/// This implementation is basically the following, except that the return type
/// `T` may optionally depend on the lifetimes `'a` and `'b`.
///
/// ```ignore
/// impl<F, T> Replacer for F
/// where
/// F: for<'a, 'b> FnMut(&'a Captures<'b>) -> T,
/// T: AsRef<str>, // `T` may depend on `'a` or `'b`, which can't be expressed easily
/// {
/// /* … */
/// }
/// ```
impl<F: for<'a, 'b> ReplacerClosure<&'a Captures<'b>>> Replacer for F {
fn replace_append(&mut self, caps: &Captures<'_>, dst: &mut String) {
dst.push_str((*self)(caps).as_ref());
}
Expand Down
30 changes: 30 additions & 0 deletions tests/misc.rs
Expand Up @@ -141,3 +141,33 @@ fn dfa_handles_pathological_case() {
};
assert!(re.is_match(&text));
}

// Test if implementation of `Replacer` for closures covers any reasonable
// lifetime combination in regard to the argument and return type.
mod replacer_closure_lifetimes {
use regex::{Captures, Regex};
use std::borrow::Cow;
#[test]
fn reference_lifetime() {
fn coerce<F: for<'a> FnMut(&'a Captures<'_>) -> Cow<'a, str>>(
f: F,
) -> F {
f
}
let s = Regex::new("x")
.unwrap()
.replace_all("x", coerce(|caps| Cow::Borrowed(&caps[0])));
assert_eq!(s, "x");
}
#[test]
fn parameter_lifetime() {
fn coerce<F: for<'b> FnMut(&Captures<'b>) -> Cow<'b, str>>(f: F) -> F {
f
}
let s = Regex::new("x").unwrap().replace_all(
"x",
coerce(|caps| Cow::Borrowed(caps.get(0).unwrap().as_str())),
);
assert_eq!(s, "x");
}
}