diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d0a0e036..cc5dac520 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,20 @@ -1.11.4 (TBD) +1.12.0 (TBD) ============ TODO +Improvements: + +* [FEATURE #1146](https://github.com/rust-lang/regex/issues/1146): +Add `Capture::get_match` for returning the overall match without `unwrap()`. + Bug fixes: * [BUG #1116](https://github.com/rust-lang/regex/issues/1116): Fixes a memory usage regression for large regexes (introduced in `regex 1.9`). * [BUG #1165](https://github.com/rust-lang/regex/issues/1083): Fixes a panic in the lazy DFA (can only occur for especially large regexes). +* [BUG #1295](https://github.com/rust-lang/regex/pull/1295): +Fixes a panic when deserializing a corrupted dense DFA. 1.11.3 (2025-09-25) diff --git a/regex-automata/src/dfa/dense.rs b/regex-automata/src/dfa/dense.rs index 456cd386b..d47163afa 100644 --- a/regex-automata/src/dfa/dense.rs +++ b/regex-automata/src/dfa/dense.rs @@ -2344,10 +2344,17 @@ impl<'a> DFA<&'a [u32]> { // table, match states and accelerators below. If any validation fails, // then we return an error. let (dfa, nread) = unsafe { DFA::from_bytes_unchecked(slice)? }; + // Note that validation order is important here: + // + // * `MatchState::validate` can be called with an untrusted DFA. + // * `TransistionTable::validate` uses `dfa.ms` through `match_len`. + // * `StartTable::validate` needs a valid transition table. + // + // So... validate the match states first. + dfa.accels.validate()?; + dfa.ms.validate(&dfa)?; dfa.tt.validate(&dfa)?; dfa.st.validate(&dfa)?; - dfa.ms.validate(&dfa)?; - dfa.accels.validate()?; // N.B. dfa.special doesn't have a way to do unchecked deserialization, // so it has already been validated. for state in dfa.states() { @@ -5234,4 +5241,20 @@ mod tests { let got = dfa.try_search_rev(&input); assert_eq!(Err(expected), got); } + + // This panics in `TransitionTable::validate` if the match states are not + // validated first. + // + // See: https://github.com/rust-lang/regex/pull/1295 + #[test] + fn regression_validation_order() { + let mut dfa = DFA::new("abc").unwrap(); + dfa.ms = MatchStates { + slices: vec![], + pattern_ids: vec![], + pattern_len: 1, + }; + let (buf, _) = dfa.to_bytes_native_endian(); + DFA::from_bytes(&buf).unwrap_err(); + } } diff --git a/regex-automata/src/dfa/sparse.rs b/regex-automata/src/dfa/sparse.rs index 13c0b6edc..5de00aca4 100644 --- a/regex-automata/src/dfa/sparse.rs +++ b/regex-automata/src/dfa/sparse.rs @@ -1860,6 +1860,12 @@ impl StartTable> { let new_start_id = remap[dfa.to_index(old_start_id)]; sl.set_start(anchored, sty, new_start_id); } + if let Some(ref mut id) = sl.universal_start_anchored { + *id = remap[dfa.to_index(*id)]; + } + if let Some(ref mut id) = sl.universal_start_unanchored { + *id = remap[dfa.to_index(*id)]; + } Ok(sl) } } diff --git a/regex-automata/tests/dfa/api.rs b/regex-automata/tests/dfa/api.rs index 96e73af6c..8a015ad0f 100644 --- a/regex-automata/tests/dfa/api.rs +++ b/regex-automata/tests/dfa/api.rs @@ -3,7 +3,7 @@ use std::error::Error; use regex_automata::{ dfa::{dense, Automaton, OverlappingState}, nfa::thompson, - HalfMatch, Input, MatchError, + Anchored, HalfMatch, Input, MatchError, }; // Tests that quit bytes in the forward direction work correctly. @@ -67,3 +67,96 @@ fn unicode_word_implicitly_works() -> Result<(), Box> { assert_eq!(Ok(Some(expected)), dfa.try_search_fwd(&Input::new(b" a"))); Ok(()) } + +// A variant of [`Automaton::is_special_state`]'s doctest, but with universal +// start states. +// +// See: https://github.com/rust-lang/regex/pull/1195 +#[test] +fn universal_start_search() -> Result<(), Box> { + fn find( + dfa: &A, + haystack: &[u8], + ) -> Result, MatchError> { + let mut state = dfa + .universal_start_state(Anchored::No) + .expect("regex should not require lookbehind"); + let mut last_match = None; + // Walk all the bytes in the haystack. We can quit early if we see + // a dead or a quit state. The former means the automaton will + // never transition to any other state. The latter means that the + // automaton entered a condition in which its search failed. + for (i, &b) in haystack.iter().enumerate() { + state = dfa.next_state(state, b); + if dfa.is_special_state(state) { + if dfa.is_match_state(state) { + last_match = + Some(HalfMatch::new(dfa.match_pattern(state, 0), i)); + } else if dfa.is_dead_state(state) { + return Ok(last_match); + } else if dfa.is_quit_state(state) { + // It is possible to enter into a quit state after + // observing a match has occurred. In that case, we + // should return the match instead of an error. + if last_match.is_some() { + return Ok(last_match); + } + return Err(MatchError::quit(b, i)); + } + // Implementors may also want to check for start or accel + // states and handle them differently for performance + // reasons. But it is not necessary for correctness. + } + } + // Matches are always delayed by 1 byte, so we must explicitly walk + // the special "EOI" transition at the end of the search. + state = dfa.next_eoi_state(state); + if dfa.is_match_state(state) { + last_match = Some(HalfMatch::new( + dfa.match_pattern(state, 0), + haystack.len(), + )); + } + Ok(last_match) + } + + fn check_impl( + dfa: impl Automaton, + haystack: &str, + pat: usize, + offset: usize, + ) -> Result<(), Box> { + let haystack = haystack.as_bytes(); + let mat = find(&dfa, haystack)?.unwrap(); + assert_eq!(mat.pattern().as_usize(), pat); + assert_eq!(mat.offset(), offset); + Ok(()) + } + + fn check( + dfa: &dense::DFA>, + haystack: &str, + pat: usize, + offset: usize, + ) -> Result<(), Box> { + check_impl(dfa, haystack, pat, offset)?; + check_impl(dfa.to_sparse()?, haystack, pat, offset)?; + Ok(()) + } + + let dfa = dense::DFA::new(r"[a-z]+")?; + let haystack = "123 foobar 4567"; + check(&dfa, haystack, 0, 10)?; + + let dfa = dense::DFA::new(r"[0-9]{4}")?; + let haystack = "123 foobar 4567"; + check(&dfa, haystack, 0, 15)?; + + let dfa = dense::DFA::new_many(&[r"[a-z]+", r"[0-9]+"])?; + let haystack = "123 foobar 4567"; + check(&dfa, haystack, 1, 3)?; + check(&dfa, &haystack[3..], 0, 7)?; + check(&dfa, &haystack[10..], 1, 5)?; + + Ok(()) +} diff --git a/regex-capi/Cargo.toml b/regex-capi/Cargo.toml index 78867f463..5ba88782f 100644 --- a/regex-capi/Cargo.toml +++ b/regex-capi/Cargo.toml @@ -11,7 +11,8 @@ description = """ A C API for Rust's regular expression library. """ workspace = ".." -edition = "2018" +edition = "2021" +rust-version = "1.65" [lib] name = "rure" diff --git a/regex-capi/src/error.rs b/regex-capi/src/error.rs index 7b91fb9d3..c2af42084 100644 --- a/regex-capi/src/error.rs +++ b/regex-capi/src/error.rs @@ -60,7 +60,7 @@ ffi_fn! { ffi_fn! { fn rure_error_message(err: *mut Error) -> *const c_char { let err = unsafe { &mut *err }; - let cmsg = match CString::new(format!("{}", err)) { + let cmsg = match CString::new(format!("{err}")) { Ok(msg) => msg, Err(err) => { // I guess this can probably happen if the regex itself has a diff --git a/regex-capi/src/macros.rs b/regex-capi/src/macros.rs index 7807cf853..6de0b6827 100644 --- a/regex-capi/src/macros.rs +++ b/regex-capi/src/macros.rs @@ -20,8 +20,8 @@ macro_rules! ffi_fn { }; let _ = writeln!( &mut io::stderr(), - "panic unwind caught, aborting: {:?}", - msg); + "panic unwind caught, aborting: {msg:?}" + ); unsafe { abort() } } } diff --git a/regex-capi/src/rure.rs b/regex-capi/src/rure.rs index 9e17668e2..8fe697dbe 100644 --- a/regex-capi/src/rure.rs +++ b/regex-capi/src/rure.rs @@ -82,7 +82,7 @@ ffi_fn! { let re = rure_compile( pat, len, RURE_DEFAULT_FLAGS, ptr::null(), &mut err); if err.is_err() { - let _ = writeln!(&mut io::stderr(), "{}", err); + let _ = writeln!(&mut io::stderr(), "{err}"); let _ = writeln!( &mut io::stderr(), "aborting from rure_compile_must"); unsafe { abort() } @@ -579,7 +579,7 @@ ffi_fn! { let mut err = Error::new(ErrorKind::None); let esc = rure_escape(pat, len, &mut err); if err.is_err() { - let _ = writeln!(&mut io::stderr(), "{}", err); + let _ = writeln!(&mut io::stderr(), "{err}"); let _ = writeln!( &mut io::stderr(), "aborting from rure_escape_must"); unsafe { abort() } diff --git a/regex-cli/args/flags.rs b/regex-cli/args/flags.rs index 61732a28e..37b741a71 100644 --- a/regex-cli/args/flags.rs +++ b/regex-cli/args/flags.rs @@ -50,9 +50,7 @@ impl std::str::FromStr for ByteSet { for &byte in Vec::unescape_bytes(s).iter() { anyhow::ensure!( !seen[usize::from(byte)], - "saw duplicate byte 0x{:2X} in '{}'", - byte, - s, + "saw duplicate byte 0x{byte:2X} in '{s}'", ); seen[usize::from(byte)] = true; set.push(byte); @@ -96,7 +94,7 @@ impl std::str::FromStr for StartKind { "both" => regex_automata::dfa::StartKind::Both, "unanchored" => regex_automata::dfa::StartKind::Unanchored, "anchored" => regex_automata::dfa::StartKind::Anchored, - unk => anyhow::bail!("unrecognized start kind '{}'", unk), + unk => anyhow::bail!("unrecognized start kind '{unk}'"), }; Ok(StartKind { kind }) } @@ -147,7 +145,7 @@ impl std::str::FromStr for MatchKind { let kind = match s { "leftmost-first" => regex_automata::MatchKind::LeftmostFirst, "all" => regex_automata::MatchKind::All, - unk => anyhow::bail!("unrecognized match kind '{}'", unk), + unk => anyhow::bail!("unrecognized match kind '{unk}'"), }; Ok(MatchKind { kind }) } diff --git a/regex-cli/args/mod.rs b/regex-cli/args/mod.rs index ba5a032f4..93b274815 100644 --- a/regex-cli/args/mod.rs +++ b/regex-cli/args/mod.rs @@ -143,11 +143,11 @@ pub fn next_as_command(usage: &str, p: &mut Parser) -> anyhow::Result { let usage = usage.trim(); let arg = match p.next()? { Some(arg) => arg, - None => anyhow::bail!("{}", usage), + None => anyhow::bail!("{usage}"), }; let cmd = match arg { Arg::Value(cmd) => cmd.string()?, - Arg::Short('h') | Arg::Long("help") => anyhow::bail!("{}", usage), + Arg::Short('h') | Arg::Long("help") => anyhow::bail!("{usage}"), arg => return Err(arg.unexpected().into()), }; Ok(cmd) diff --git a/regex-cli/args/syntax.rs b/regex-cli/args/syntax.rs index a91ac2dca..f1cc24407 100644 --- a/regex-cli/args/syntax.rs +++ b/regex-cli/args/syntax.rs @@ -45,7 +45,7 @@ impl Config { .map(|(i, p)| { let p = p.as_ref(); self.ast(p).with_context(|| { - format!("failed to parse pattern {} to AST: '{}'", i, p,) + format!("failed to parse pattern {i} to AST: '{p}'",) }) }) .collect() @@ -80,10 +80,7 @@ impl Config { .map(|(i, (pat, ast))| { let (pat, ast) = (pat.as_ref(), ast.borrow()); self.hir(pat, ast).with_context(|| { - format!( - "failed to translate pattern {} to HIR: '{}'", - i, pat, - ) + format!("failed to translate pattern {i} to HIR: '{pat}'") }) }) .collect() diff --git a/regex-cli/cmd/compile_test.rs b/regex-cli/cmd/compile_test.rs index bd28a7dab..2926fd316 100644 --- a/regex-cli/cmd/compile_test.rs +++ b/regex-cli/cmd/compile_test.rs @@ -127,11 +127,11 @@ OPTIONS: } else { write!(wtr, "regex,")?; } - write!(wtr, "{},", revision)?; + write!(wtr, "{revision},")?; write!(wtr, "{},", tdir.test.profile.as_str())?; write!(wtr, "{:?},", m.duration)?; write!(wtr, "{:?},", m.size)?; - write!(wtr, "{:?}", relative_size)?; + write!(wtr, "{relative_size:?}")?; write!(wtr, "\n")?; } Ok(()) @@ -439,7 +439,7 @@ impl Test { let features = self .features .iter() - .map(|f| format!(r#""{}""#, f)) + .map(|f| format!(r#""{f}""#)) .collect::>() .join(", "); format!( @@ -480,7 +480,7 @@ strip = "symbols" let features = self .features .iter() - .map(|f| format!(r#""{}""#, f)) + .map(|f| format!(r#""{f}""#)) .collect::>() .join(", "); format!( @@ -521,7 +521,7 @@ strip = "symbols" let features = self .features .iter() - .map(|f| format!(r#""{}""#, f)) + .map(|f| format!(r#""{f}""#)) .collect::>() .join(", "); format!( @@ -800,8 +800,7 @@ fn baseline_size(parent_dir: &Path, profile: Profile) -> anyhow::Result { .with_context(|| format!("'cargo clean' failed for baseline"))?; anyhow::ensure!( status.success(), - "'cargo clean' got an error exit code of {:?} for baseline", - status, + "'cargo clean' got an error exit code of {status:?} for baseline", ); let status = Command::new("cargo") .arg("build") @@ -814,8 +813,7 @@ fn baseline_size(parent_dir: &Path, profile: Profile) -> anyhow::Result { .with_context(|| format!("'cargo build' failed for baseline"))?; anyhow::ensure!( status.success(), - "'cargo build' got an error exit code of {:?} for baseline", - status, + "'cargo build' got an error exit code of {status:?} for baseline", ); let bin = dir .join("target") diff --git a/regex-cli/cmd/debug/dfa.rs b/regex-cli/cmd/debug/dfa.rs index f16610fbe..a270acd53 100644 --- a/regex-cli/cmd/debug/dfa.rs +++ b/regex-cli/cmd/debug/dfa.rs @@ -29,7 +29,7 @@ COMMANDS: match &*cmd { "dfa" => run_dense_dfa(p), "regex" => run_dense_regex(p), - unk => anyhow::bail!("unrecognized command '{}'", unk), + unk => anyhow::bail!("unrecognized command '{unk}'"), } } @@ -88,7 +88,7 @@ OPTIONS: if common.table() { writeln!(stdout(), "")?; } - writeln!(stdout(), "{:?}", dfa)?; + writeln!(stdout(), "{dfa:?}")?; } Ok(()) } @@ -161,7 +161,7 @@ OPTIONS: if common.table() { writeln!(stdout(), "")?; } - writeln!(stdout(), "{:?}", re)?; + writeln!(stdout(), "{re:?}")?; } Ok(()) } @@ -188,7 +188,7 @@ COMMANDS: match &*cmd { "dfa" => run_sparse_dfa(p), "regex" => run_sparse_regex(p), - unk => anyhow::bail!("unrecognized command '{}'", unk), + unk => anyhow::bail!("unrecognized command '{unk}'"), } } @@ -245,7 +245,7 @@ OPTIONS: if common.table() { writeln!(stdout(), "")?; } - writeln!(stdout(), "{:?}", dfa)?; + writeln!(stdout(), "{dfa:?}")?; } Ok(()) } @@ -320,7 +320,7 @@ OPTIONS: if common.table() { writeln!(stdout(), "")?; } - writeln!(stdout(), "{:?}", re)?; + writeln!(stdout(), "{re:?}")?; } Ok(()) } diff --git a/regex-cli/cmd/debug/literal.rs b/regex-cli/cmd/debug/literal.rs index 9c82ec6e0..8d2d281dd 100644 --- a/regex-cli/cmd/debug/literal.rs +++ b/regex-cli/cmd/debug/literal.rs @@ -64,8 +64,7 @@ OPTIONS: ExtractKind::Suffix => seq.optimize_for_suffix_by_preference(), unk => { anyhow::bail!( - "unsupported literal extraction kind: {:?}", - unk + "unsupported literal extraction kind: {unk:?}" ) } } @@ -95,10 +94,10 @@ OPTIONS: writeln!(out, "")?; } match seq.literals() { - None => writeln!(out, "{:?}", seq)?, + None => writeln!(out, "{seq:?}")?, Some(literals) => { for lit in literals.iter() { - writeln!(stdout(), "{:?}", lit)?; + writeln!(stdout(), "{lit:?}")?; } } } @@ -127,8 +126,7 @@ impl Configurable for Literal { "prefix" => ExtractKind::Prefix, "suffix" => ExtractKind::Suffix, unk => anyhow::bail!( - "unknown value for --extract-kind: {}", - unk + "unknown value for --extract-kind: {unk}" ), }; self.kind = kind.clone(); diff --git a/regex-cli/cmd/debug/mod.rs b/regex-cli/cmd/debug/mod.rs index 525c4d138..b25dc4137 100644 --- a/regex-cli/cmd/debug/mod.rs +++ b/regex-cli/cmd/debug/mod.rs @@ -39,7 +39,7 @@ COMMANDS: "onepass" => run_onepass(p), "sparse" => dfa::run_sparse(p), "thompson" => run_thompson(p), - unk => anyhow::bail!("unrecognized command '{}'", unk), + unk => anyhow::bail!("unrecognized command '{unk}'"), } } @@ -181,7 +181,7 @@ OPTIONS: if common.table() { writeln!(stdout(), "")?; } - writeln!(stdout(), "{:?}", dfa)?; + writeln!(stdout(), "{dfa:?}")?; } Ok(()) } @@ -238,7 +238,7 @@ OPTIONS: if common.table() { writeln!(stdout(), "")?; } - writeln!(stdout(), "{:?}", nfa)?; + writeln!(stdout(), "{nfa:?}")?; } Ok(()) } diff --git a/regex-cli/cmd/find/capture/mod.rs b/regex-cli/cmd/find/capture/mod.rs index 3af46bfcf..38e27059a 100644 --- a/regex-cli/cmd/find/capture/mod.rs +++ b/regex-cli/cmd/find/capture/mod.rs @@ -46,7 +46,7 @@ ENGINES: "onepass" => dfa::run_onepass(p), "pikevm" => nfa::run_pikevm(p), "regex" => run_regex(p), - unk => anyhow::bail!("unrecognized command '{}'", unk), + unk => anyhow::bail!("unrecognized command '{unk}'"), } } @@ -388,9 +388,9 @@ fn run_counts( } let count = pattern_counts[group_index]; if let Some(name) = maybe_name { - write!(out, "{}/{}: {}", group_index, name, count)?; + write!(out, "{group_index}/{name}: {count}")?; } else { - write!(out, "{}: {}", group_index, count)?; + write!(out, "{group_index}: {count}")?; } } write!(out, " }}\n")?; @@ -440,9 +440,9 @@ fn run_search( write!(out, ", ")?; } if let Some(name) = maybe_name { - write!(out, "{}/{}: ", group_index, name)?; + write!(out, "{group_index}/{name}: ")?; } else { - write!(out, "{}: ", group_index)?; + write!(out, "{group_index}: ")?; } match caps.get_group(group_index) { None => write!(out, "NONE")?, diff --git a/regex-cli/cmd/find/half/mod.rs b/regex-cli/cmd/find/half/mod.rs index 4dd33bd3d..15c8b7362 100644 --- a/regex-cli/cmd/find/half/mod.rs +++ b/regex-cli/cmd/find/half/mod.rs @@ -45,7 +45,7 @@ ENGINES: "meta" => run_meta(p), "regex" => run_regex(p), "sparse" => dfa::run_sparse(p), - unk => anyhow::bail!("unrecognized command '{}'", unk), + unk => anyhow::bail!("unrecognized command '{unk}'"), } } diff --git a/regex-cli/cmd/find/match/mod.rs b/regex-cli/cmd/find/match/mod.rs index 0bafd8b2b..7cc8fcf42 100644 --- a/regex-cli/cmd/find/match/mod.rs +++ b/regex-cli/cmd/find/match/mod.rs @@ -44,7 +44,7 @@ ENGINES: "pikevm" => nfa::run_pikevm(p), "regex" => run_regex(p), "sparse" => dfa::run_sparse(p), - unk => anyhow::bail!("unrecognized command '{}'", unk), + unk => anyhow::bail!("unrecognized command '{unk}'"), } } diff --git a/regex-cli/cmd/find/mod.rs b/regex-cli/cmd/find/mod.rs index deb8cd1ed..2965949f7 100644 --- a/regex-cli/cmd/find/mod.rs +++ b/regex-cli/cmd/find/mod.rs @@ -31,7 +31,7 @@ COMMANDS: "half" => half::run(p), "match" => r#match::run(p), "which" => which::run(p), - unk => anyhow::bail!("unrecognized command '{}'", unk), + unk => anyhow::bail!("unrecognized command '{unk}'"), } } diff --git a/regex-cli/cmd/find/which/mod.rs b/regex-cli/cmd/find/which/mod.rs index 4416a76f8..bbb46f4fe 100644 --- a/regex-cli/cmd/find/which/mod.rs +++ b/regex-cli/cmd/find/which/mod.rs @@ -58,7 +58,7 @@ ENGINES: "pikevm" => nfa::run_pikevm(p), "regex" => run_regex(p), "sparse" => dfa::run_sparse(p), - unk => anyhow::bail!("unrecognized command '{}'", unk), + unk => anyhow::bail!("unrecognized command '{unk}'"), } } diff --git a/regex-cli/cmd/generate/fowler.rs b/regex-cli/cmd/generate/fowler.rs index 404c47721..1c2ac9f76 100644 --- a/regex-cli/cmd/generate/fowler.rs +++ b/regex-cli/cmd/generate/fowler.rs @@ -50,7 +50,7 @@ USAGE: Some(stem) => stem.to_string_lossy(), None => anyhow::bail!("{}: has no file stem", datfile.display()), }; - let tomlfile = outdir.join(format!("{}.toml", stem)); + let tomlfile = outdir.join(format!("{stem}.toml")); let mut rdr = File::open(datfile) .with_context(|| datfile.display().to_string())?; @@ -112,7 +112,7 @@ fn convert( src: &mut dyn Read, dst: &mut dyn Write, ) -> anyhow::Result<()> { - log::trace!("processing {}", group_name); + log::trace!("processing {group_name}"); let src = std::io::BufReader::new(src); writeln!( @@ -129,7 +129,7 @@ fn convert( for (i, result) in src.lines().enumerate() { // Every usize can fit into a u64... Right? let line_number = u64::try_from(i).unwrap().checked_add(1).unwrap(); - let line = result.with_context(|| format!("line {}", line_number))?; + let line = result.with_context(|| format!("line {line_number}"))?; // The last group of tests in 'repetition' take quite a lot of time // when using them to build and minimize a DFA. So we tag them with // 'expensive' so that we can skip those tests when we need to minimize @@ -146,7 +146,7 @@ fn convert( Some(dat) => dat, }; let toml = TomlTest::from_dat_test(group_name, &dat)?; - writeln!(dst, "{}", toml)?; + writeln!(dst, "{toml}")?; prev = Some(dat); } Ok(()) @@ -220,7 +220,7 @@ impl TomlTest { impl core::fmt::Display for TomlTest { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { if let Some(ref comment) = self.comment { - writeln!(f, "# {}", comment)?; + writeln!(f, "# {comment}")?; } writeln!(f, "[[test]]")?; writeln!(f, "name = \"{}{}\"", self.group_name, self.line_number)?; @@ -236,7 +236,7 @@ impl core::fmt::Display for TomlTest { } match group { None => write!(f, "[]")?, - Some((start, end)) => write!(f, "[{}, {}]", start, end)?, + Some((start, end)) => write!(f, "[{start}, {end}]")?, } } writeln!(f, "]]")?; @@ -297,7 +297,7 @@ impl DatTest { // First field contains terse one-letter flags. let mut flags: HashSet = fields[0].chars().collect(); if !flags.contains(&'E') { - log::trace!("skipping {}: does not contain 'E' flag", line_number); + log::trace!("skipping {line_number}: does not contain 'E' flag"); return Ok(None); } @@ -308,8 +308,8 @@ impl DatTest { regex = match prev { Some(test) => test.regex.clone(), None => anyhow::bail!( - "line {}: wants previous pattern but none is available", - line_number, + "line {line_number}: wants previous pattern \ + but none is available" ), }; } @@ -341,8 +341,7 @@ impl DatTest { // yet, just adding support for them here. if !fields[3].contains(',') { log::trace!( - "skipping {}: malformed capturing group", - line_number + "skipping {line_number}: malformed capturing group" ); return Ok(None); } @@ -390,7 +389,7 @@ impl DatTest { fn count_capturing_groups(pattern: &str) -> anyhow::Result { let ast = regex_syntax::ast::parse::Parser::new() .parse(pattern) - .with_context(|| format!("failed to parse '{}'", pattern))?; + .with_context(|| format!("failed to parse '{pattern}'"))?; // We add 1 to account for the capturing group for the entire // pattern. Ok(1 + count_capturing_groups_ast(&ast)) diff --git a/regex-cli/cmd/generate/mod.rs b/regex-cli/cmd/generate/mod.rs index 3a2faac40..8378a156e 100644 --- a/regex-cli/cmd/generate/mod.rs +++ b/regex-cli/cmd/generate/mod.rs @@ -23,6 +23,6 @@ pub fn run(p: &mut lexopt::Parser) -> anyhow::Result<()> { "fowler" => fowler::run(p), "serialize" => serialize::run(p), "unicode" => unicode::run(p), - unk => anyhow::bail!("unrecognized command '{}'", unk), + unk => anyhow::bail!("unrecognized command '{unk}'"), } } diff --git a/regex-cli/cmd/generate/serialize/dfa.rs b/regex-cli/cmd/generate/serialize/dfa.rs index 600d12111..583860839 100644 --- a/regex-cli/cmd/generate/serialize/dfa.rs +++ b/regex-cli/cmd/generate/serialize/dfa.rs @@ -42,7 +42,7 @@ ENGINES: match &*args::next_as_command(USAGE, p)? { "dfa" => run_dense_dfa(p), "regex" => run_dense_regex(p), - unk => anyhow::bail!("unrecognized command '{}'", unk), + unk => anyhow::bail!("unrecognized command '{unk}'"), } } @@ -154,7 +154,7 @@ ENGINES: match &*args::next_as_command(USAGE, p)? { "dfa" => run_sparse_dfa(p), "regex" => run_sparse_regex(p), - unk => anyhow::bail!("unrecognized command '{}'", unk), + unk => anyhow::bail!("unrecognized command '{unk}'"), } } @@ -389,7 +389,7 @@ impl std::str::FromStr for RustKind { "once-cell" => Ok(RustKind::OnceCell), "lazy-static" => Ok(RustKind::LazyStatic), "none" => Ok(RustKind::None), - unk => anyhow::bail!("unrecognized rust output kind: '{}'", unk), + unk => anyhow::bail!("unrecognized rust output kind: '{unk}'"), } } } diff --git a/regex-cli/cmd/generate/serialize/mod.rs b/regex-cli/cmd/generate/serialize/mod.rs index a0c2f779a..358bafc20 100644 --- a/regex-cli/cmd/generate/serialize/mod.rs +++ b/regex-cli/cmd/generate/serialize/mod.rs @@ -17,6 +17,6 @@ ENGINES: match &*args::next_as_command(USAGE, p)? { "dense" => dfa::run_dense(p), "sparse" => dfa::run_sparse(p), - unk => anyhow::bail!("unrecognized command '{}'", unk), + unk => anyhow::bail!("unrecognized command '{unk}'"), } } diff --git a/regex-cli/cmd/mod.rs b/regex-cli/cmd/mod.rs index 5e9706a57..8cf79b957 100644 --- a/regex-cli/cmd/mod.rs +++ b/regex-cli/cmd/mod.rs @@ -23,6 +23,6 @@ pub fn run(p: &mut lexopt::Parser) -> anyhow::Result<()> { "find" => find::run(p), "debug" => debug::run(p), "generate" => generate::run(p), - unk => anyhow::bail!("unrecognized command '{}'", unk), + unk => anyhow::bail!("unrecognized command '{unk}'"), } } diff --git a/regex-cli/main.rs b/regex-cli/main.rs index 3ebbac3c0..db22ea166 100644 --- a/regex-cli/main.rs +++ b/regex-cli/main.rs @@ -14,16 +14,16 @@ fn main() -> anyhow::Result<()> { "info" => log::LevelFilter::Info, "debug" => log::LevelFilter::Debug, "trace" => log::LevelFilter::Trace, - unk => anyhow::bail!("unrecognized log level '{}'", unk), + unk => anyhow::bail!("unrecognized log level '{unk}'"), }; logger::Logger::init()?; log::set_max_level(level); if let Err(err) = cmd::run(&mut lexopt::Parser::from_env()) { if std::env::var("RUST_BACKTRACE").map_or(false, |v| v == "1") { - writeln!(&mut std::io::stderr(), "{:?}", err).unwrap(); + writeln!(&mut std::io::stderr(), "{err:?}").unwrap(); } else { - writeln!(&mut std::io::stderr(), "{:#}", err).unwrap(); + writeln!(&mut std::io::stderr(), "{err:#}").unwrap(); } std::process::exit(1); } diff --git a/regex-cli/util.rs b/regex-cli/util.rs index 3ff7d60e7..fbc98d9e1 100644 --- a/regex-cli/util.rs +++ b/regex-cli/util.rs @@ -66,7 +66,7 @@ impl Table { let mut wtr = tabwriter::TabWriter::new(wtr) .alignment(tabwriter::Alignment::Right); for (label, value) in self.pairs.iter() { - writeln!(wtr, "{}:\t{:?}", label, value)?; + writeln!(wtr, "{label}:\t{value:?}")?; } wtr.flush() } diff --git a/regex-lite/src/hir/parse.rs b/regex-lite/src/hir/parse.rs index 6d4009d8d..1983a9b89 100644 --- a/regex-lite/src/hir/parse.rs +++ b/regex-lite/src/hir/parse.rs @@ -593,8 +593,7 @@ impl<'a> Parser<'a> { 'u' => 4, 'U' => 8, unk => unreachable!( - "invalid start of fixed length hexadecimal number {}", - unk + "invalid start of fixed length hexadecimal number {unk}" ), }; if !self.bump_and_bump_space() { @@ -720,7 +719,7 @@ impl<'a> Parser<'a> { '?' => (0, Some(1)), '*' => (0, None), '+' => (1, None), - unk => unreachable!("unrecognized repetition operator '{}'", unk), + unk => unreachable!("unrecognized repetition operator '{unk}'"), }; let mut greedy = true; if self.bump() && self.char() == '?' { @@ -1216,7 +1215,7 @@ impl<'a> Parser<'a> { 'd' | 'D' => posix_class("digit").unwrap(), 's' | 'S' => posix_class("space").unwrap(), 'w' | 'W' => posix_class("word").unwrap(), - unk => unreachable!("invalid Perl class \\{}", unk), + unk => unreachable!("invalid Perl class \\{unk}"), }); if ch.is_ascii_uppercase() { class.negate(); diff --git a/regex-lite/src/nfa.rs b/regex-lite/src/nfa.rs index 8f37a5451..94b000ce8 100644 --- a/regex-lite/src/nfa.rs +++ b/regex-lite/src/nfa.rs @@ -136,7 +136,7 @@ impl core::fmt::Debug for NFA { writeln!(f, "NFA(")?; writeln!(f, "pattern: {}", self.pattern)?; for (sid, state) in self.states.iter().enumerate() { - writeln!(f, "{:07?}: {:?}", sid, state)?; + writeln!(f, "{sid:07?}: {state:?}")?; } writeln!(f, ")")?; Ok(()) @@ -206,14 +206,14 @@ impl core::fmt::Debug for State { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match *self { State::Char { target, ch } => { - write!(f, "{:?} => {:?}", ch, target) + write!(f, "{ch:?} => {target:?}") } State::Ranges { target, ref ranges } => { for (i, &(start, end)) in ranges.iter().enumerate() { if i > 0 { write!(f, ", ")?; } - write!(f, "{:?}-{:?} => {:?}", start, end, target)?; + write!(f, "{start:?}-{end:?} => {target:?}")?; } Ok(()) } @@ -225,18 +225,18 @@ impl core::fmt::Debug for State { if i > 0 { write!(f, ", ")?; } - write!(f, "{:?}", sid)?; + write!(f, "{sid:?}")?; } write!(f, ")") } State::Goto { target, look: None } => { - write!(f, "goto({:?})", target) + write!(f, "goto({target:?})") } State::Goto { target, look: Some(look) } => { - write!(f, "{:?} => {:?}", look, target) + write!(f, "{look:?} => {target:?}") } State::Capture { target, slot } => { - write!(f, "capture(slot={:?}) => {:?}", slot, target,) + write!(f, "capture(slot={slot:?}) => {target:?}") } State::Fail => write!(f, "FAIL"), State::Match => { diff --git a/regex-lite/src/string.rs b/regex-lite/src/string.rs index 4dba085f2..5de5d868c 100644 --- a/regex-lite/src/string.rs +++ b/regex-lite/src/string.rs @@ -1798,7 +1798,7 @@ impl<'h> Captures<'h> { .nfa() .static_explicit_captures_len() .expect("number of capture groups can vary in a match"); - assert_eq!(N, len, "asked for {} groups, but must ask for {}", N, len); + assert_eq!(N, len, "asked for {N} groups, but must ask for {len}"); let mut matched = self.iter().flatten(); let whole_match = matched.next().expect("a match").as_str(); let group_matches = [0; N].map(|_| { @@ -1965,7 +1965,7 @@ impl<'h> core::fmt::Debug for Captures<'h> { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { write!(f, "{}", self.0)?; if let Some(name) = self.1 { - write!(f, "/{:?}", name)?; + write!(f, "/{name:?}")?; } Ok(()) } @@ -2013,7 +2013,7 @@ impl<'h> core::ops::Index for Captures<'h> { fn index(&self, i: usize) -> &str { self.get(i) .map(|m| m.as_str()) - .unwrap_or_else(|| panic!("no group at index '{}'", i)) + .unwrap_or_else(|| panic!("no group at index '{i}'")) } } @@ -2039,7 +2039,7 @@ impl<'h, 'n> core::ops::Index<&'n str> for Captures<'h> { fn index<'a>(&'a self, name: &'n str) -> &'a str { self.name(name) .map(|m| m.as_str()) - .unwrap_or_else(|| panic!("no group named '{}'", name)) + .unwrap_or_else(|| panic!("no group named '{name}'")) } } diff --git a/regex-lite/tests/string.rs b/regex-lite/tests/string.rs index 283e103a2..d47330ed3 100644 --- a/regex-lite/tests/string.rs +++ b/regex-lite/tests/string.rs @@ -23,8 +23,7 @@ fn run_test(re: &Regex, test: &RegexTest) -> TestResult { Ok(hay) => hay, Err(err) => { return TestResult::fail(&format!( - "haystack is not valid UTF-8: {}", - err + "haystack is not valid UTF-8: {err}", )); } }; @@ -45,7 +44,7 @@ fn run_test(re: &Regex, test: &RegexTest) -> TestResult { .map(|caps| testify_captures(&caps)); TestResult::captures(it) } - name => TestResult::fail(&format!("unrecognized test name: {}", name)), + name => TestResult::fail(&format!("unrecognized test name: {name}")), } } diff --git a/regex-syntax/src/ast/mod.rs b/regex-syntax/src/ast/mod.rs index ce79a89ab..7e2426dc7 100644 --- a/regex-syntax/src/ast/mod.rs +++ b/regex-syntax/src/ast/mod.rs @@ -839,7 +839,7 @@ pub enum ClassAsciiKind { Lower, /// `[ -~]` Print, - /// `[!-/:-@\[-`{-~]` + /// ``[!-/:-@\[-`{-~]`` Punct, /// `[\t\n\v\f\r ]` Space, @@ -1801,9 +1801,7 @@ mod tests { let size = core::mem::size_of::(); assert!( size <= max, - "Ast size of {} bytes is bigger than suggested max {}", - size, - max + "Ast size of {size} bytes is bigger than suggested max {max}", ); } } diff --git a/regex-syntax/src/ast/parse.rs b/regex-syntax/src/ast/parse.rs index 0c2a35265..bdaab7228 100644 --- a/regex-syntax/src/ast/parse.rs +++ b/regex-syntax/src/ast/parse.rs @@ -484,7 +484,7 @@ impl<'s, P: Borrow> ParserI<'s, P> { self.pattern()[i..] .chars() .next() - .unwrap_or_else(|| panic!("expected char at offset {}", i)) + .unwrap_or_else(|| panic!("expected char at offset {i}")) } /// Bump the parser to the next Unicode scalar value. @@ -2254,7 +2254,7 @@ impl<'s, P: Borrow> ParserI<'s, P> { 'S' => (true, ast::ClassPerlKind::Space), 'w' => (false, ast::ClassPerlKind::Word), 'W' => (true, ast::ClassPerlKind::Word), - c => panic!("expected valid Perl class but got '{}'", c), + c => panic!("expected valid Perl class but got '{c}'"), }; ast::ClassPerl { span, kind, negated } } @@ -4598,7 +4598,7 @@ bar // We also support superfluous escapes in most cases now too. for c in ['!', '@', '%', '"', '\'', '/', ' '] { - let pat = format!(r"\{}", c); + let pat = format!(r"\{c}"); assert_eq!( parser(&pat).parse_primitive(), Ok(Primitive::Literal(ast::Literal { @@ -4713,7 +4713,7 @@ bar #[test] fn parse_octal() { for i in 0..511 { - let pat = format!(r"\{:o}", i); + let pat = format!(r"\{i:o}"); assert_eq!( parser_octal(&pat).parse_escape(), Ok(Primitive::Literal(ast::Literal { @@ -4788,7 +4788,7 @@ bar #[test] fn parse_hex_two() { for i in 0..256 { - let pat = format!(r"\x{:02x}", i); + let pat = format!(r"\x{i:02x}"); assert_eq!( parser(&pat).parse_escape(), Ok(Primitive::Literal(ast::Literal { @@ -4829,7 +4829,7 @@ bar None => continue, Some(c) => c, }; - let pat = format!(r"\u{:04x}", i); + let pat = format!(r"\u{i:04x}"); assert_eq!( parser(&pat).parse_escape(), Ok(Primitive::Literal(ast::Literal { @@ -4893,7 +4893,7 @@ bar None => continue, Some(c) => c, }; - let pat = format!(r"\U{:08x}", i); + let pat = format!(r"\U{i:08x}"); assert_eq!( parser(&pat).parse_escape(), Ok(Primitive::Literal(ast::Literal { diff --git a/regex-syntax/src/ast/print.rs b/regex-syntax/src/ast/print.rs index 1ceb3c7fa..556d91f4a 100644 --- a/regex-syntax/src/ast/print.rs +++ b/regex-syntax/src/ast/print.rs @@ -199,9 +199,9 @@ impl Writer { ) -> fmt::Result { use crate::ast::RepetitionRange::*; match *ast { - Exactly(x) => write!(self.wtr, "{{{}}}", x), - AtLeast(x) => write!(self.wtr, "{{{},}}", x), - Bounded(x, y) => write!(self.wtr, "{{{},{}}}", x, y), + Exactly(x) => write!(self.wtr, "{{{x}}}"), + AtLeast(x) => write!(self.wtr, "{{{x},}}"), + Bounded(x, y) => write!(self.wtr, "{{{x},{y}}}"), } } diff --git a/regex-syntax/src/ast/visitor.rs b/regex-syntax/src/ast/visitor.rs index c1bb24d97..36cd713c0 100644 --- a/regex-syntax/src/ast/visitor.rs +++ b/regex-syntax/src/ast/visitor.rs @@ -488,7 +488,7 @@ impl<'a> core::fmt::Debug for ClassFrame<'a> { ClassFrame::BinaryLHS { .. } => "BinaryLHS", ClassFrame::BinaryRHS { .. } => "BinaryRHS", }; - write!(f, "{}", x) + write!(f, "{x}") } } @@ -517,6 +517,6 @@ impl<'a> core::fmt::Debug for ClassInduct<'a> { } }, }; - write!(f, "{}", x) + write!(f, "{x}") } } diff --git a/regex-syntax/src/debug.rs b/regex-syntax/src/debug.rs index a0b051b44..7a47d9de8 100644 --- a/regex-syntax/src/debug.rs +++ b/regex-syntax/src/debug.rs @@ -42,7 +42,7 @@ impl<'a> core::fmt::Debug for Bytes<'a> { let ch = match result { Ok(ch) => ch, Err(byte) => { - write!(f, r"\x{:02x}", byte)?; + write!(f, r"\x{byte:02x}")?; bytes = &bytes[1..]; continue; } diff --git a/regex-syntax/src/error.rs b/regex-syntax/src/error.rs index 71ea0fc6d..21e484df9 100644 --- a/regex-syntax/src/error.rs +++ b/regex-syntax/src/error.rs @@ -93,10 +93,10 @@ impl<'e, E: core::fmt::Display> core::fmt::Display for Formatter<'e, E> { let divider = repeat_char('~', 79); writeln!(f, "regex parse error:")?; - writeln!(f, "{}", divider)?; + writeln!(f, "{divider}")?; let notated = spans.notate(); - write!(f, "{}", notated)?; - writeln!(f, "{}", divider)?; + write!(f, "{notated}")?; + writeln!(f, "{divider}")?; // If we have error spans that cover multiple lines, then we just // note the line numbers. if !spans.multi_line.is_empty() { @@ -116,7 +116,7 @@ impl<'e, E: core::fmt::Display> core::fmt::Display for Formatter<'e, E> { } else { writeln!(f, "regex parse error:")?; let notated = Spans::from_formatter(self).notate(); - write!(f, "{}", notated)?; + write!(f, "{notated}")?; write!(f, "error: {}", self.err)?; } Ok(()) diff --git a/regex-syntax/src/hir/print.rs b/regex-syntax/src/hir/print.rs index dfa6d4032..89db08c25 100644 --- a/regex-syntax/src/hir/print.rs +++ b/regex-syntax/src/hir/print.rs @@ -230,7 +230,7 @@ impl Visitor for Writer { HirKind::Capture(hir::Capture { ref name, .. }) => { self.wtr.write_str("(")?; if let Some(ref name) = *name { - write!(self.wtr, "?P<{}>", name)?; + write!(self.wtr, "?P<{name}>")?; } } // Why do this? Wrapping concats and alts in non-capturing groups @@ -276,15 +276,15 @@ impl Visitor for Writer { return Ok(()); } (m, None) => { - write!(self.wtr, "{{{},}}", m)?; + write!(self.wtr, "{{{m},}}")?; } (m, Some(n)) if m == n => { - write!(self.wtr, "{{{}}}", m)?; + write!(self.wtr, "{{{m}}}")?; // a{m} and a{m}? are always exactly equivalent. return Ok(()); } (m, Some(n)) => { - write!(self.wtr, "{{{},{}}}", m, n)?; + write!(self.wtr, "{{{m},{n}}}")?; } } if !x.greedy { @@ -317,7 +317,7 @@ impl Writer { if b <= 0x7F && !b.is_ascii_control() && !b.is_ascii_whitespace() { self.write_literal_char(char::try_from(b).unwrap()) } else { - write!(self.wtr, "(?-u:\\x{:02X})", b) + write!(self.wtr, "(?-u:\\x{b:02X})") } } @@ -325,7 +325,7 @@ impl Writer { if b <= 0x7F && !b.is_ascii_control() && !b.is_ascii_whitespace() { self.write_literal_char(char::try_from(b).unwrap()) } else { - write!(self.wtr, "\\x{:02X}", b) + write!(self.wtr, "\\x{b:02X}") } } } diff --git a/regex-syntax/src/hir/translate.rs b/regex-syntax/src/hir/translate.rs index c210f1a26..48469f9e1 100644 --- a/regex-syntax/src/hir/translate.rs +++ b/regex-syntax/src/hir/translate.rs @@ -254,7 +254,7 @@ impl HirFrame { match self { HirFrame::Expr(expr) => expr, HirFrame::Literal(lit) => Hir::literal(lit), - _ => panic!("tried to unwrap expr from HirFrame, got: {:?}", self), + _ => panic!("tried to unwrap expr from HirFrame, got: {self:?}"), } } @@ -291,8 +291,7 @@ impl HirFrame { HirFrame::Repetition => {} _ => { panic!( - "tried to unwrap repetition from HirFrame, got: {:?}", - self + "tried to unwrap repetition from HirFrame, got: {self:?}" ) } } @@ -305,7 +304,7 @@ impl HirFrame { match self { HirFrame::Group { old_flags } => old_flags, _ => { - panic!("tried to unwrap group from HirFrame, got: {:?}", self) + panic!("tried to unwrap group from HirFrame, got: {self:?}") } } } @@ -316,10 +315,7 @@ impl HirFrame { match self { HirFrame::AlternationBranch => {} _ => { - panic!( - "tried to unwrap alt pipe from HirFrame, got: {:?}", - self - ) + panic!("tried to unwrap alt pipe from HirFrame, got: {self:?}") } } } diff --git a/regex-syntax/src/utf8.rs b/regex-syntax/src/utf8.rs index 69d749451..537035ed1 100644 --- a/regex-syntax/src/utf8.rs +++ b/regex-syntax/src/utf8.rs @@ -128,7 +128,7 @@ impl Utf8Sequence { Utf8Range::new(start[2], end[2]), Utf8Range::new(start[3], end[3]), ]), - n => unreachable!("invalid encoded length: {}", n), + n => unreachable!("invalid encoded length: {n}"), } } @@ -203,7 +203,7 @@ impl fmt::Debug for Utf8Sequence { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use self::Utf8Sequence::*; match *self { - One(ref r) => write!(f, "{:?}", r), + One(ref r) => write!(f, "{r:?}"), Two(ref r) => write!(f, "{:?}{:?}", r[0], r[1]), Three(ref r) => write!(f, "{:?}{:?}{:?}", r[0], r[1], r[2]), Four(ref r) => { diff --git a/regex-test/lib.rs b/regex-test/lib.rs index 6cebbfae9..e6c1037e6 100644 --- a/regex-test/lib.rs +++ b/regex-test/lib.rs @@ -151,17 +151,17 @@ impl RegexTests { /// The given group name is assigned to all loaded tests. pub fn load_slice(&mut self, group_name: &str, data: &[u8]) -> Result<()> { let data = std::str::from_utf8(&data).with_context(|| { - format!("data in {} is not valid UTF-8", group_name) + format!("data in {group_name} is not valid UTF-8") })?; let mut index = 1; let mut tests: RegexTests = toml::from_str(&data).with_context(|| { - format!("error decoding TOML for '{}'", group_name) + format!("error decoding TOML for '{group_name}'") })?; for t in &mut tests.tests { t.group = group_name.to_string(); if t.name.is_empty() { - t.name = format!("{}", index); + t.name = format!("{index}"); index += 1; } t.full_name = format!("{}/{}", t.group, t.name); @@ -1101,7 +1101,7 @@ impl RegexTestFailureKind { let mut buf = String::new(); match *self { RegexTestFailureKind::UserFailure { ref why } => { - write!(buf, "failed by implementor because: {}", why)?; + write!(buf, "failed by implementor because: {why}")?; } RegexTestFailureKind::IsMatch => { if test.is_match() { @@ -1140,13 +1140,13 @@ impl RegexTestFailureKind { write!(buf, "expected regex to NOT compile, but it did")?; } RegexTestFailureKind::CompileError { ref err } => { - write!(buf, "expected regex to compile, failed: {}", err)?; + write!(buf, "expected regex to compile, failed: {err}")?; } RegexTestFailureKind::UnexpectedPanicCompile(ref msg) => { - write!(buf, "got unexpected panic while compiling:\n{}", msg)?; + write!(buf, "got unexpected panic while compiling:\n{msg}")?; } RegexTestFailureKind::UnexpectedPanicSearch(ref msg) => { - write!(buf, "got unexpected panic while searching:\n{}", msg)?; + write!(buf, "got unexpected panic while searching:\n{msg}")?; } } Ok(buf) diff --git a/src/error.rs b/src/error.rs index 0f222c537..9e90d5674 100644 --- a/src/error.rs +++ b/src/error.rs @@ -71,7 +71,7 @@ impl core::fmt::Display for Error { Error::Syntax(ref err) => err.fmt(f), Error::CompiledTooBig(limit) => write!( f, - "Compiled regex exceeds size limit of {limit} bytes." + "Compiled regex exceeds size limit of {limit} bytes.", ), } } diff --git a/src/regex/bytes.rs b/src/regex/bytes.rs index 11b285515..cf4540877 100644 --- a/src/regex/bytes.rs +++ b/src/regex/bytes.rs @@ -1665,6 +1665,26 @@ impl<'h> Captures<'h> { .map(|sp| Match::new(self.haystack, sp.start, sp.end)) } + /// Return the overall match for the capture. + /// + /// This returns the match for index `0`. That is it is equivalent to + /// `m.get(0).unwrap()` + /// + /// # Example + /// + /// ``` + /// use regex::bytes::Regex; + /// + /// let re = Regex::new(r"[a-z]+([0-9]+)").unwrap(); + /// let caps = re.captures(b" abc123-def").unwrap(); + /// + /// assert_eq!(caps.get_match().as_bytes(), b"abc123"); + /// ``` + #[inline] + pub fn get_match(&self) -> Match { + self.get(0).unwrap() + } + /// Returns the `Match` associated with the capture group named `name`. If /// `name` isn't a valid capture group or it refers to a group that didn't /// match, then `None` is returned. @@ -1992,7 +2012,7 @@ impl<'h> core::ops::Index for Captures<'h> { fn index<'a>(&'a self, i: usize) -> &'a [u8] { self.get(i) .map(|m| m.as_bytes()) - .unwrap_or_else(|| panic!("no group at index '{}'", i)) + .unwrap_or_else(|| panic!("no group at index '{i}'")) } } @@ -2018,7 +2038,7 @@ impl<'h, 'n> core::ops::Index<&'n str> for Captures<'h> { fn index<'a>(&'a self, name: &'n str) -> &'a [u8] { self.name(name) .map(|m| m.as_bytes()) - .unwrap_or_else(|| panic!("no group named '{}'", name)) + .unwrap_or_else(|| panic!("no group named '{name}'")) } } diff --git a/src/regex/string.rs b/src/regex/string.rs index 8363cbb79..3ae75bd56 100644 --- a/src/regex/string.rs +++ b/src/regex/string.rs @@ -1675,6 +1675,27 @@ impl<'h> Captures<'h> { .map(|sp| Match::new(self.haystack, sp.start, sp.end)) } + /// Return the overall match for the capture. + /// + /// This returns the match for index `0`. That is it is equivalent to + /// `m.get(0).unwrap()` + /// + /// # Example + /// + /// ``` + /// use regex::Regex; + /// + /// let re = Regex::new(r"[a-z]+([0-9]+)").unwrap(); + /// let caps = re.captures(" abc123-def").unwrap(); + /// + /// assert_eq!(caps.get_match().as_str(), "abc123"); + /// + /// ``` + #[inline] + pub fn get_match(&self) -> Match { + self.get(0).unwrap() + } + /// Returns the `Match` associated with the capture group named `name`. If /// `name` isn't a valid capture group or it refers to a group that didn't /// match, then `None` is returned. @@ -2000,7 +2021,7 @@ impl<'h> core::ops::Index for Captures<'h> { fn index<'a>(&'a self, i: usize) -> &'a str { self.get(i) .map(|m| m.as_str()) - .unwrap_or_else(|| panic!("no group at index '{}'", i)) + .unwrap_or_else(|| panic!("no group at index '{i}'")) } } @@ -2026,7 +2047,7 @@ impl<'h, 'n> core::ops::Index<&'n str> for Captures<'h> { fn index<'a>(&'a self, name: &'n str) -> &'a str { self.name(name) .map(|m| m.as_str()) - .unwrap_or_else(|| panic!("no group named '{}'", name)) + .unwrap_or_else(|| panic!("no group named '{name}'")) } } diff --git a/tests/suite_string.rs b/tests/suite_string.rs index c8fb3d08a..2a6d7709b 100644 --- a/tests/suite_string.rs +++ b/tests/suite_string.rs @@ -23,7 +23,7 @@ fn run_test(re: &Regex, test: &RegexTest) -> TestResult { Ok(hay) => hay, Err(err) => { return TestResult::fail(&format!( - "haystack is not valid UTF-8: {err}" + "haystack is not valid UTF-8: {err}", )); } }; diff --git a/tests/suite_string_set.rs b/tests/suite_string_set.rs index 49b87ec45..122e39c75 100644 --- a/tests/suite_string_set.rs +++ b/tests/suite_string_set.rs @@ -21,7 +21,7 @@ fn run_test(re: &RegexSet, test: &RegexTest) -> TestResult { Ok(hay) => hay, Err(err) => { return TestResult::fail(&format!( - "haystack is not valid UTF-8: {err}" + "haystack is not valid UTF-8: {err}", )); } };