sleuth_mutator/
lib.rs

1//! Proc-macro attribute for the `sleuth` crate.
2
3#![warn(
4    missing_docs,
5    rustdoc::all,
6    clippy::missing_docs_in_private_items,
7    clippy::all,
8    clippy::restriction,
9    clippy::pedantic,
10    clippy::nursery,
11    clippy::cargo
12)]
13#![allow(
14    clippy::blanket_clippy_restriction_lints,
15    clippy::exhaustive_structs,
16    clippy::implicit_return,
17    clippy::integer_arithmetic,
18    clippy::mod_module_files,
19    clippy::pattern_type_mismatch,
20    clippy::pub_use,
21    clippy::question_mark_used,
22    clippy::separated_literal_suffix,
23    clippy::string_add,
24    clippy::wildcard_enum_match_arm,
25    clippy::wildcard_imports
26)]
27#![deny(warnings)]
28
29/// Fake span for delimiting tokens.
30macro_rules! bs_delim_span {
31    ($d:ident) => {
32        proc_macro2::Group::new(proc_macro2::Delimiter::$d, proc_macro2::TokenStream::new())
33            .delim_span()
34    };
35}
36
37/// Tokens that have a vector of one span instead of one span for some reason, with a fake span for convenience.
38macro_rules! token {
39    ($t:ident) => {
40        syn::token::$t {
41            spans: [proc_macro2::Span::call_site()],
42        }
43    };
44}
45
46/// Tokens that have one span instead of a vector of one span for some reason, with a fake span for convenience.
47macro_rules! single_token {
48    ($t:ident) => {
49        syn::token::$t {
50            span: proc_macro2::Span::call_site(),
51        }
52    };
53}
54
55/// Tokens that delimit a group, with a fake span for convenience.
56macro_rules! dual_token {
57    ($t:ident) => {
58        syn::token::$t {
59            spans: [
60                proc_macro2::Span::call_site(),
61                proc_macro2::Span::call_site(),
62            ],
63        }
64    };
65}
66
67/// Tokens that delimit a group, with a fake span for convenience.
68macro_rules! delim_token {
69    (Paren) => {
70        syn::token::Paren {
71            span: bs_delim_span!(Parenthesis),
72        }
73    };
74    ($d:ident) => {
75        syn::token::$d {
76            span: bs_delim_span!($d),
77        }
78    };
79}
80
81mod mutate;
82mod parse;
83
84/// The name of this crate. Just in case.
85const CRATE_NAME: &str = "sleuth";
86
87/// Test that this is the shortest possible implementation to fulfill a set of properties.
88#[proc_macro_attribute]
89pub fn sleuth(
90    attr: proc_macro::TokenStream,
91    input: proc_macro::TokenStream,
92) -> proc_macro::TokenStream {
93    match mutate::implementation(attr.into(), input.into()) {
94        Ok(ts) => ts,
95        Err(e) => e.to_compile_error(),
96    }
97    .into()
98}
99
100/// Make a trivial expression holding a path with or without a leading separator (`::`).
101#[inline]
102fn expr_path(
103    leading: bool,
104    punc: syn::punctuated::Punctuated<syn::PathSegment, syn::token::PathSep>,
105) -> syn::Expr {
106    syn::Expr::Path(syn::ExprPath {
107        attrs: vec![],
108        qself: None,
109        path: path(leading, punc),
110    })
111}
112
113/// Make a trivial path with or without a leading separator (`::`).
114#[inline]
115fn path(
116    leading: bool,
117    punc: syn::punctuated::Punctuated<syn::PathSegment, syn::token::PathSep>,
118) -> syn::Path {
119    syn::Path {
120        leading_colon: leading.then(|| dual_token!(PathSep)),
121        segments: punc,
122    }
123}
124
125/// Turn a slice into a punctuated list.
126#[inline]
127fn punctuate<T, P: Default, const N: usize>(vs: [T; N]) -> syn::punctuated::Punctuated<T, P> {
128    let mut punc = syn::punctuated::Punctuated::new();
129    for v in vs {
130        punc.push(v);
131    }
132    punc
133}
134
135/// Make a trivial identifier from a string.
136#[inline]
137fn ident(s: &str) -> syn::Ident {
138    syn::Ident::new(s, proc_macro2::Span::call_site())
139}
140
141/// Make a path segment with only one argument (not really a path, but a singleton, sure).
142#[inline]
143const fn pathseg(fn_name: syn::Ident) -> syn::PathSegment {
144    syn::PathSegment {
145        ident: fn_name,
146        arguments: syn::PathArguments::None,
147    }
148}
149
150/// Where something could be generic but isn't (either a turbofish `::<>` or completely elided by the `quote` crate)
151const NO_GENERICS: syn::Generics = syn::Generics {
152    lt_token: None,
153    params: syn::punctuated::Punctuated::new(),
154    gt_token: None,
155    where_clause: None,
156};