diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 7f08118a..25402f1a 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -20,3 +20,4 @@ serde_json = "1.0.79" [features] __auto_concretize = ["test-fuzz/auto_concretize"] __bar_fuzz = [] +__inapplicable_conversion = [] diff --git a/examples/tests/conversion.rs b/examples/tests/conversion.rs index 06b7b7d8..88214397 100644 --- a/examples/tests/conversion.rs +++ b/examples/tests/conversion.rs @@ -60,3 +60,40 @@ mod receiver { X.target(); } } + +#[cfg(feature = "__inapplicable_conversion")] +mod inapplicable_conversion { + use serde::{Deserialize, Serialize}; + + #[derive(Clone)] + struct X; + + #[derive(Clone)] + struct Y; + + #[derive(Clone, Deserialize, Serialize)] + struct Z; + + impl From for Z { + fn from(_: Y) -> Self { + Z + } + } + + impl test_fuzz::Into for Z { + fn into(self) -> Y { + Y + } + } + + #[test_fuzz::test_fuzz_impl] + impl X { + #[test_fuzz::test_fuzz(convert = "Y, Z")] + fn target(self) {} + } + + #[test] + fn test() { + X.target(); + } +} diff --git a/macro/src/lib.rs b/macro/src/lib.rs index 8f75d767..2fbffa0f 100644 --- a/macro/src/lib.rs +++ b/macro/src/lib.rs @@ -8,7 +8,13 @@ use lazy_static::lazy_static; use proc_macro::TokenStream; use proc_macro2::{Literal, Span, TokenStream as TokenStream2}; use quote::{quote, ToTokens}; -use std::{collections::BTreeMap, convert::TryFrom, env::var, io::Write, str::FromStr}; +use std::{ + collections::{BTreeMap, BTreeSet}, + convert::TryFrom, + env::var, + io::Write, + str::FromStr, +}; use subprocess::{Exec, Redirection}; use syn::{ parse::Parser, parse_macro_input, parse_quote, parse_str, punctuated::Punctuated, token, @@ -30,7 +36,7 @@ use ord_type::OrdType; mod type_utils; -type Conversions = BTreeMap; +type Conversions = BTreeMap; lazy_static! { pub(crate) static ref CARGO_CRATE_NAME: String = @@ -233,7 +239,7 @@ fn map_method_or_fn( let mut iter = args.into_iter(); let key = iter.next().expect("Should have two `convert` arguments"); let value = iter.next().expect("Should have two `convert` arguments"); - conversions.insert(OrdType(key), value); + conversions.insert(OrdType(key), (value, false)); }); #[allow(unused_mut, unused_variables)] @@ -362,8 +368,26 @@ fn map_method_or_fn( let self_ty_base = self_ty.as_ref().map(type_utils::type_base); - let (receiver, mut arg_tys, fmt_args, mut ser_args, de_args) = - map_args(&conversions, self_ty, trait_path, sig.inputs.iter()); + let (receiver, mut arg_tys, fmt_args, mut ser_args, de_args) = { + let mut candidates = BTreeSet::new(); + let result = map_args( + &mut conversions, + &mut candidates, + self_ty, + trait_path, + sig.inputs.iter(), + ); + for (from, (to, used)) in conversions { + assert!( + used, + r#"Conversion "{}" -> "{}" does not apply to the following cadidates: {:#?}"#, + from, + OrdType(to), + candidates + ); + } + result + }; arg_tys.extend_from_slice(&phantom_tys); ser_args.extend_from_slice(&phantoms); let pub_arg_tys: Vec = arg_tys.iter().map(|ty| quote! { pub #ty }).collect(); @@ -724,7 +748,8 @@ fn map_method_or_fn( } fn map_args<'a, I>( - conversions: &Conversions, + conversions: &mut Conversions, + candidates: &mut BTreeSet, self_ty: &Option, trait_path: &Option, inputs: I, @@ -736,7 +761,7 @@ where let (receiver, ty, fmt, ser, de): (Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>) = inputs .enumerate() - .map(map_arg(conversions, self_ty, trait_path)) + .map(map_arg(conversions, candidates, self_ty, trait_path)) .unzip_n(); let receiver = receiver.first().map_or(false, |&x| x); @@ -744,12 +769,12 @@ where (receiver, ty, fmt, ser, de) } -fn map_arg( - conversions: &Conversions, +fn map_arg<'a>( + conversions: &'a mut Conversions, + candidates: &'a mut BTreeSet, self_ty: &Option, trait_path: &Option, -) -> impl Fn((usize, &FnArg)) -> (bool, Type, Stmt, Expr, Expr) { - let conversions = conversions.clone(); +) -> impl FnMut((usize, &FnArg)) -> (bool, Type, Stmt, Expr, Expr) + 'a { let self_ty = self_ty.clone(); let trait_path = trait_path.clone(); move |(i, arg)| { @@ -786,18 +811,21 @@ fn map_arg( (false, expr, ty, fmt) } }; - let (ty, ser, de) = map_typed_arg(&conversions, &i, &expr, &ty); + let (ty, ser, de) = map_typed_arg(conversions, candidates, &i, &expr, &ty); (receiver, ty, fmt, ser, de) } } fn map_typed_arg( - conversions: &Conversions, + conversions: &mut Conversions, + candidates: &mut BTreeSet, i: &Literal, expr: &Expr, ty: &Type, ) -> (Type, Expr, Expr) { - if let Some(arg_ty) = conversions.get(&OrdType(ty.clone())) { + candidates.insert(OrdType(ty.clone())); + if let Some((arg_ty, used)) = conversions.get_mut(&OrdType(ty.clone())) { + *used = true; return ( parse_quote! { #arg_ty }, parse_quote! { <#arg_ty as From::<#ty>>::from(#expr.clone()) }, @@ -805,8 +833,8 @@ fn map_typed_arg( ); } match &ty { - Type::Path(path) => map_path_arg(conversions, i, expr, path), - Type::Reference(ty) => map_ref_arg(conversions, i, expr, ty), + Type::Path(path) => map_path_arg(conversions, candidates, i, expr, path), + Type::Reference(ty) => map_ref_arg(conversions, candidates, i, expr, ty), _ => ( parse_quote! { #ty }, parse_quote! { #expr.clone() }, @@ -816,7 +844,8 @@ fn map_typed_arg( } fn map_path_arg( - _conversions: &Conversions, + _conversions: &mut Conversions, + _candidates: &mut BTreeSet, i: &Literal, expr: &Expr, path: &TypePath, @@ -829,7 +858,8 @@ fn map_path_arg( } fn map_ref_arg( - conversions: &Conversions, + conversions: &mut Conversions, + candidates: &mut BTreeSet, i: &Literal, expr: &Expr, ty: &TypeReference, @@ -850,7 +880,7 @@ fn map_ref_arg( ) } else { let expr = parse_quote! { (*#expr) }; - let (ty, ser, de) = map_path_arg(conversions, i, &expr, path); + let (ty, ser, de) = map_path_arg(conversions, candidates, i, &expr, path); (ty, ser, parse_quote! { & #mutability #de }) } } @@ -861,7 +891,7 @@ fn map_ref_arg( ), _ => { let expr = parse_quote! { (*#expr) }; - let (ty, ser, de) = map_typed_arg(conversions, i, &expr, ty); + let (ty, ser, de) = map_typed_arg(conversions, candidates, i, &expr, ty); (ty, ser, parse_quote! { & #mutability #de }) } } diff --git a/macro/src/ord_type.rs b/macro/src/ord_type.rs index 34bbb47f..e569f279 100644 --- a/macro/src/ord_type.rs +++ b/macro/src/ord_type.rs @@ -5,21 +5,27 @@ use syn::Type; #[derive(Clone)] pub struct OrdType(pub Type); +impl std::fmt::Display for OrdType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.to_token_stream().fmt(f) + } +} + +impl std::fmt::Debug for OrdType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.to_string().fmt(f) + } +} + impl Ord for OrdType { fn cmp(&self, other: &Self) -> Ordering { - ::cmp( - &self.0.to_token_stream().to_string(), - &other.0.to_token_stream().to_string(), - ) + ::cmp(&self.to_string(), &other.to_string()) } } impl PartialOrd for OrdType { fn partial_cmp(&self, other: &Self) -> Option { - ::partial_cmp( - &self.0.to_token_stream().to_string(), - &other.0.to_token_stream().to_string(), - ) + ::partial_cmp(&self.to_string(), &other.to_string()) } } @@ -27,9 +33,6 @@ impl Eq for OrdType {} impl PartialEq for OrdType { fn eq(&self, other: &Self) -> bool { - ::eq( - &self.0.to_token_stream().to_string(), - &other.0.to_token_stream().to_string(), - ) + ::eq(&self.to_string(), &other.to_string()) } } diff --git a/test-fuzz/tests/conversion.rs b/test-fuzz/tests/conversion.rs new file mode 100644 index 00000000..d58b4f0b --- /dev/null +++ b/test-fuzz/tests/conversion.rs @@ -0,0 +1,30 @@ +use assert_cmd::prelude::*; +use predicates::prelude::*; +use std::process::Command; +use testing::examples::MANIFEST_PATH; + +#[test] +fn conversion() { + let mut command = test(); + + command.assert().success(); + + command + .args(["--features", "__inapplicable_conversion"]) + .assert() + .failure() + .stderr(predicate::str::is_match(r#"(?m)\bConversion "Y" -> "Z" does not apply to the following cadidates: \{\s*"X",\s*}$"#).unwrap()); +} + +fn test() -> Command { + let mut command = Command::new("cargo"); + command.args(&[ + "test", + "--manifest-path", + &MANIFEST_PATH, + "--no-run", + "--features", + &("test-fuzz/".to_owned() + test_fuzz::serde_format().as_feature()), + ]); + command +} diff --git a/test-fuzz/tests/rename.rs b/test-fuzz/tests/rename.rs index 01e39434..a35f6b32 100644 --- a/test-fuzz/tests/rename.rs +++ b/test-fuzz/tests/rename.rs @@ -5,32 +5,28 @@ use testing::examples::MANIFEST_PATH; #[test] fn rename() { - Command::new("cargo") - .args(&[ - "test", - "--manifest-path", - &MANIFEST_PATH, - "--no-run", - "--features", - &("test-fuzz/".to_owned() + test_fuzz::serde_format().as_feature()), - ]) - .assert() - .success(); + let mut command = test(); + + command.assert().success(); - Command::new("cargo") - .args(&[ - "test", - "--manifest-path", - &MANIFEST_PATH, - "--no-run", - "--features", - &("test-fuzz/".to_owned() + test_fuzz::serde_format().as_feature()), - "--features", - "__bar_fuzz", - ]) + command + .args(["--features", "__bar_fuzz"]) .assert() .failure() .stderr(predicate::str::contains( "the name `bar_fuzz` is defined multiple times", )); } + +fn test() -> Command { + let mut command = Command::new("cargo"); + command.args(&[ + "test", + "--manifest-path", + &MANIFEST_PATH, + "--no-run", + "--features", + &("test-fuzz/".to_owned() + test_fuzz::serde_format().as_feature()), + ]); + command +}