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

Consider it an error when a conversion does not apply #109

Merged
merged 2 commits into from
Mar 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ serde_json = "1.0.79"
[features]
__auto_concretize = ["test-fuzz/auto_concretize"]
__bar_fuzz = []
__inapplicable_conversion = []
37 changes: 37 additions & 0 deletions examples/tests/conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Y> for Z {
fn from(_: Y) -> Self {
Z
}
}

impl test_fuzz::Into<Y> 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();
}
}
70 changes: 50 additions & 20 deletions macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -30,7 +36,7 @@ use ord_type::OrdType;

mod type_utils;

type Conversions = BTreeMap<OrdType, Type>;
type Conversions = BTreeMap<OrdType, (Type, bool)>;

lazy_static! {
pub(crate) static ref CARGO_CRATE_NAME: String =
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -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<TokenStream2> = arg_tys.iter().map(|ty| quote! { pub #ty }).collect();
Expand Down Expand Up @@ -724,7 +748,8 @@ fn map_method_or_fn(
}

fn map_args<'a, I>(
conversions: &Conversions,
conversions: &mut Conversions,
candidates: &mut BTreeSet<OrdType>,
self_ty: &Option<Type>,
trait_path: &Option<Path>,
inputs: I,
Expand All @@ -736,20 +761,20 @@ 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);

(receiver, ty, fmt, ser, de)
}

fn map_arg(
conversions: &Conversions,
fn map_arg<'a>(
conversions: &'a mut Conversions,
candidates: &'a mut BTreeSet<OrdType>,
self_ty: &Option<Type>,
trait_path: &Option<Path>,
) -> 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)| {
Expand Down Expand Up @@ -786,27 +811,30 @@ 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<OrdType>,
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()) },
parse_quote! { <_ as test_fuzz::Into::<_>>::into(args.#i) },
);
}
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() },
Expand All @@ -816,7 +844,8 @@ fn map_typed_arg(
}

fn map_path_arg(
_conversions: &Conversions,
_conversions: &mut Conversions,
_candidates: &mut BTreeSet<OrdType>,
i: &Literal,
expr: &Expr,
path: &TypePath,
Expand All @@ -829,7 +858,8 @@ fn map_path_arg(
}

fn map_ref_arg(
conversions: &Conversions,
conversions: &mut Conversions,
candidates: &mut BTreeSet<OrdType>,
i: &Literal,
expr: &Expr,
ty: &TypeReference,
Expand All @@ -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 })
}
}
Expand All @@ -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 })
}
}
Expand Down
27 changes: 15 additions & 12 deletions macro/src/ord_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,34 @@ 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 {
<String as Ord>::cmp(
&self.0.to_token_stream().to_string(),
&other.0.to_token_stream().to_string(),
)
<String as Ord>::cmp(&self.to_string(), &other.to_string())
}
}

impl PartialOrd for OrdType {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
<String as PartialOrd>::partial_cmp(
&self.0.to_token_stream().to_string(),
&other.0.to_token_stream().to_string(),
)
<String as PartialOrd>::partial_cmp(&self.to_string(), &other.to_string())
}
}

impl Eq for OrdType {}

impl PartialEq for OrdType {
fn eq(&self, other: &Self) -> bool {
<String as PartialEq>::eq(
&self.0.to_token_stream().to_string(),
&other.0.to_token_stream().to_string(),
)
<String as PartialEq>::eq(&self.to_string(), &other.to_string())
}
}
30 changes: 30 additions & 0 deletions test-fuzz/tests/conversion.rs
Original file line number Diff line number Diff line change
@@ -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
}
40 changes: 18 additions & 22 deletions test-fuzz/tests/rename.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}