diff --git a/Cargo.lock b/Cargo.lock index ea25d278..6d1daa76 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -155,7 +155,7 @@ dependencies = [ [[package]] name = "cargo-mutants" -version = "27.0.0-pre" +version = "27.0.0" dependencies = [ "anyhow", "assert_cmd", diff --git a/Cargo.toml b/Cargo.toml index 2376adc0..5100ed63 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cargo-mutants" -version = "27.0.0-pre" +version = "27.0.0" edition = "2024" authors = ["Martin Pool"] license = "MIT" diff --git a/NEWS.md b/NEWS.md index 33d453d1..863e3ce8 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,8 @@ # cargo-mutants changelog -## Unreleased +## 27.0.0 + +Released 2026-03-07. - Changed: Command line values for `--file`, `--exclude`, `--examine-re`, and `--exclude-re` are now combined with, rather than replacing, values given in the configuration file, consistently with every other option that takes a list. (Use `--config=OTHER` or `--no-config` to avoid using values in the configuration.) Thanks to @sandersaares for pointing this out. diff --git a/src/fnvalue.rs b/src/fnvalue.rs index eddb85e5..a9d0dda0 100644 --- a/src/fnvalue.rs +++ b/src/fnvalue.rs @@ -56,6 +56,33 @@ fn type_replacements(type_: &Type, error_exprs: &[Expr]) -> impl Iterator generic form (stabilized in Rust 1.79) + if let Type::Path(syn::TypePath { + path: inner_path, .. + }) = inner_type + { + if path_is_unsigned(inner_path) { + vec![quote! { 1.try_into().unwrap() }] + } else if path_is_signed(inner_path) { + vec![ + quote! { 1.try_into().unwrap() }, + quote! { (-1).try_into().unwrap() }, + ] + } else { + // Unknown T, assume it could be signed + vec![ + quote! { 1.try_into().unwrap() }, + quote! { (-1).try_into().unwrap() }, + ] + } + } else { + // T is not a simple path, assume it could be signed + vec![ + quote! { 1.try_into().unwrap() }, + quote! { (-1).try_into().unwrap() }, + ] + } } else if path_is_float(path) { vec![quote! { 0.0 }, quote! { 1.0 }, quote! { -1.0 }] } else if path_ends_with(path, "Result") { @@ -393,7 +420,6 @@ fn path_is_nonzero_signed(path: &Path) -> bool { } fn path_is_nonzero_unsigned(path: &Path) -> bool { - // TODO: Also NonZero etc. if let Some(l) = path.segments.last().map(|p| p.ident.to_string()) { matches!( l.as_str(), @@ -494,6 +520,58 @@ mod test { ); } + #[test] + fn nonzero_generic_unsigned_replacements() { + check_replacements( + &parse_quote! { -> NonZero }, + &[], + &["1.try_into().unwrap()"], + ); + + check_replacements( + &parse_quote! { -> NonZero }, + &[], + &["1.try_into().unwrap()"], + ); + + check_replacements( + &parse_quote! { -> std::num::NonZero }, + &[], + &["1.try_into().unwrap()"], + ); + } + + #[test] + fn nonzero_generic_signed_replacements() { + check_replacements( + &parse_quote! { -> NonZero }, + &[], + &["1.try_into().unwrap()", "(-1).try_into().unwrap()"], + ); + + check_replacements( + &parse_quote! { -> NonZero }, + &[], + &["1.try_into().unwrap()", "(-1).try_into().unwrap()"], + ); + + check_replacements( + &parse_quote! { -> std::num::NonZero }, + &[], + &["1.try_into().unwrap()", "(-1).try_into().unwrap()"], + ); + } + + #[test] + fn nonzero_generic_unknown_type_replacements() { + // When T is not a recognized integer type, assume it could be signed. + check_replacements( + &parse_quote! { -> NonZero }, + &[], + &["1.try_into().unwrap()", "(-1).try_into().unwrap()"], + ); + } + #[test] fn unit_replacement() { check_replacements(&parse_quote! { -> () }, &[], &["()"]);