]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_utils/src/check_proc_macro.rs
Auto merge of #107843 - bjorn3:sync_cg_clif-2023-02-09, r=bjorn3
[rust.git] / src / tools / clippy / clippy_utils / src / check_proc_macro.rs
1 //! This module handles checking if the span given is from a proc-macro or not.
2 //!
3 //! Proc-macros are capable of setting the span of every token they output to a few possible spans.
4 //! This includes spans we can detect easily as coming from a proc-macro (e.g. the call site
5 //! or the def site), and spans we can't easily detect as such (e.g. the span of any token
6 //! passed into the proc macro). This capability means proc-macros are capable of generating code
7 //! with a span that looks like it was written by the user, but which should not be linted by clippy
8 //! as it was generated by an external macro.
9 //!
10 //! That brings us to this module. The current approach is to determine a small bit of text which
11 //! must exist at both the start and the end of an item (e.g. an expression or a path) assuming the
12 //! code was written, and check if the span contains that text. Note this will only work correctly
13 //! if the span is not from a `macro_rules` based macro.
14
15 use rustc_ast::ast::{IntTy, LitIntType, LitKind, StrStyle, UintTy};
16 use rustc_hir::{
17     intravisit::FnKind, Block, BlockCheckMode, Body, Closure, Destination, Expr, ExprKind, FieldDef, FnHeader, HirId,
18     Impl, ImplItem, ImplItemKind, IsAuto, Item, ItemKind, LoopSource, MatchSource, Node, QPath, TraitItem,
19     TraitItemKind, UnOp, UnsafeSource, Unsafety, Variant, VariantData, YieldSource,
20 };
21 use rustc_lint::{LateContext, LintContext};
22 use rustc_middle::ty::TyCtxt;
23 use rustc_session::Session;
24 use rustc_span::{Span, Symbol};
25 use rustc_target::spec::abi::Abi;
26
27 /// The search pattern to look for. Used by `span_matches_pat`
28 #[derive(Clone, Copy)]
29 pub enum Pat {
30     /// A single string.
31     Str(&'static str),
32     /// Any of the given strings.
33     MultiStr(&'static [&'static str]),
34     /// The string representation of the symbol.
35     Sym(Symbol),
36     /// Any decimal or hexadecimal digit depending on the location.
37     Num,
38 }
39
40 /// Checks if the start and the end of the span's text matches the patterns. This will return false
41 /// if the span crosses multiple files or if source is not available.
42 fn span_matches_pat(sess: &Session, span: Span, start_pat: Pat, end_pat: Pat) -> bool {
43     let pos = sess.source_map().lookup_byte_offset(span.lo());
44     let Some(ref src) = pos.sf.src else {
45         return false;
46     };
47     let end = span.hi() - pos.sf.start_pos;
48     src.get(pos.pos.0 as usize..end.0 as usize).map_or(false, |s| {
49         // Spans can be wrapped in a mixture or parenthesis, whitespace, and trailing commas.
50         let start_str = s.trim_start_matches(|c: char| c.is_whitespace() || c == '(');
51         let end_str = s.trim_end_matches(|c: char| c.is_whitespace() || c == ')' || c == ',');
52         (match start_pat {
53             Pat::Str(text) => start_str.starts_with(text),
54             Pat::MultiStr(texts) => texts.iter().any(|s| start_str.starts_with(s)),
55             Pat::Sym(sym) => start_str.starts_with(sym.as_str()),
56             Pat::Num => start_str.as_bytes().first().map_or(false, u8::is_ascii_digit),
57         } && match end_pat {
58             Pat::Str(text) => end_str.ends_with(text),
59             Pat::MultiStr(texts) => texts.iter().any(|s| start_str.ends_with(s)),
60             Pat::Sym(sym) => end_str.ends_with(sym.as_str()),
61             Pat::Num => end_str.as_bytes().last().map_or(false, u8::is_ascii_hexdigit),
62         })
63     })
64 }
65
66 /// Get the search patterns to use for the given literal
67 fn lit_search_pat(lit: &LitKind) -> (Pat, Pat) {
68     match lit {
69         LitKind::Str(_, StrStyle::Cooked) => (Pat::Str("\""), Pat::Str("\"")),
70         LitKind::Str(_, StrStyle::Raw(0)) => (Pat::Str("r"), Pat::Str("\"")),
71         LitKind::Str(_, StrStyle::Raw(_)) => (Pat::Str("r#"), Pat::Str("#")),
72         LitKind::ByteStr(_, StrStyle::Cooked) => (Pat::Str("b\""), Pat::Str("\"")),
73         LitKind::ByteStr(_, StrStyle::Raw(0)) => (Pat::Str("br\""), Pat::Str("\"")),
74         LitKind::ByteStr(_, StrStyle::Raw(_)) => (Pat::Str("br#\""), Pat::Str("#")),
75         LitKind::Byte(_) => (Pat::Str("b'"), Pat::Str("'")),
76         LitKind::Char(_) => (Pat::Str("'"), Pat::Str("'")),
77         LitKind::Int(_, LitIntType::Signed(IntTy::Isize)) => (Pat::Num, Pat::Str("isize")),
78         LitKind::Int(_, LitIntType::Unsigned(UintTy::Usize)) => (Pat::Num, Pat::Str("usize")),
79         LitKind::Int(..) => (Pat::Num, Pat::Num),
80         LitKind::Float(..) => (Pat::Num, Pat::Str("")),
81         LitKind::Bool(true) => (Pat::Str("true"), Pat::Str("true")),
82         LitKind::Bool(false) => (Pat::Str("false"), Pat::Str("false")),
83         _ => (Pat::Str(""), Pat::Str("")),
84     }
85 }
86
87 /// Get the search patterns to use for the given path
88 fn qpath_search_pat(path: &QPath<'_>) -> (Pat, Pat) {
89     match path {
90         QPath::Resolved(ty, path) => {
91             let start = if ty.is_some() {
92                 Pat::Str("<")
93             } else {
94                 path.segments
95                     .first()
96                     .map_or(Pat::Str(""), |seg| Pat::Sym(seg.ident.name))
97             };
98             let end = path.segments.last().map_or(Pat::Str(""), |seg| {
99                 if seg.args.is_some() {
100                     Pat::Str(">")
101                 } else {
102                     Pat::Sym(seg.ident.name)
103                 }
104             });
105             (start, end)
106         },
107         QPath::TypeRelative(_, name) => (Pat::Str(""), Pat::Sym(name.ident.name)),
108         QPath::LangItem(..) => (Pat::Str(""), Pat::Str("")),
109     }
110 }
111
112 /// Get the search patterns to use for the given expression
113 fn expr_search_pat(tcx: TyCtxt<'_>, e: &Expr<'_>) -> (Pat, Pat) {
114     match e.kind {
115         ExprKind::Box(e) => (Pat::Str("box"), expr_search_pat(tcx, e).1),
116         ExprKind::ConstBlock(_) => (Pat::Str("const"), Pat::Str("}")),
117         ExprKind::Tup([]) => (Pat::Str(")"), Pat::Str("(")),
118         ExprKind::Unary(UnOp::Deref, e) => (Pat::Str("*"), expr_search_pat(tcx, e).1),
119         ExprKind::Unary(UnOp::Not, e) => (Pat::Str("!"), expr_search_pat(tcx, e).1),
120         ExprKind::Unary(UnOp::Neg, e) => (Pat::Str("-"), expr_search_pat(tcx, e).1),
121         ExprKind::Lit(ref lit) => lit_search_pat(&lit.node),
122         ExprKind::Array(_) | ExprKind::Repeat(..) => (Pat::Str("["), Pat::Str("]")),
123         ExprKind::Call(e, []) | ExprKind::MethodCall(_, e, [], _) => (expr_search_pat(tcx, e).0, Pat::Str("(")),
124         ExprKind::Call(first, [.., last])
125         | ExprKind::MethodCall(_, first, [.., last], _)
126         | ExprKind::Binary(_, first, last)
127         | ExprKind::Tup([first, .., last])
128         | ExprKind::Assign(first, last, _)
129         | ExprKind::AssignOp(_, first, last) => (expr_search_pat(tcx, first).0, expr_search_pat(tcx, last).1),
130         ExprKind::Tup([e]) | ExprKind::DropTemps(e) => expr_search_pat(tcx, e),
131         ExprKind::Cast(e, _) | ExprKind::Type(e, _) => (expr_search_pat(tcx, e).0, Pat::Str("")),
132         ExprKind::Let(let_expr) => (Pat::Str("let"), expr_search_pat(tcx, let_expr.init).1),
133         ExprKind::If(..) => (Pat::Str("if"), Pat::Str("}")),
134         ExprKind::Loop(_, Some(_), _, _) | ExprKind::Block(_, Some(_)) => (Pat::Str("'"), Pat::Str("}")),
135         ExprKind::Loop(_, None, LoopSource::Loop, _) => (Pat::Str("loop"), Pat::Str("}")),
136         ExprKind::Loop(_, None, LoopSource::While, _) => (Pat::Str("while"), Pat::Str("}")),
137         ExprKind::Loop(_, None, LoopSource::ForLoop, _) | ExprKind::Match(_, _, MatchSource::ForLoopDesugar) => {
138             (Pat::Str("for"), Pat::Str("}"))
139         },
140         ExprKind::Match(_, _, MatchSource::Normal) => (Pat::Str("match"), Pat::Str("}")),
141         ExprKind::Match(e, _, MatchSource::TryDesugar) => (expr_search_pat(tcx, e).0, Pat::Str("?")),
142         ExprKind::Match(e, _, MatchSource::AwaitDesugar) | ExprKind::Yield(e, YieldSource::Await { .. }) => {
143             (expr_search_pat(tcx, e).0, Pat::Str("await"))
144         },
145         ExprKind::Closure(&Closure { body, .. }) => (Pat::Str(""), expr_search_pat(tcx, tcx.hir().body(body).value).1),
146         ExprKind::Block(
147             Block {
148                 rules: BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided),
149                 ..
150             },
151             None,
152         ) => (Pat::Str("unsafe"), Pat::Str("}")),
153         ExprKind::Block(_, None) => (Pat::Str("{"), Pat::Str("}")),
154         ExprKind::Field(e, name) => (expr_search_pat(tcx, e).0, Pat::Sym(name.name)),
155         ExprKind::Index(e, _) => (expr_search_pat(tcx, e).0, Pat::Str("]")),
156         ExprKind::Path(ref path) => qpath_search_pat(path),
157         ExprKind::AddrOf(_, _, e) => (Pat::Str("&"), expr_search_pat(tcx, e).1),
158         ExprKind::Break(Destination { label: None, .. }, None) => (Pat::Str("break"), Pat::Str("break")),
159         ExprKind::Break(Destination { label: Some(name), .. }, None) => (Pat::Str("break"), Pat::Sym(name.ident.name)),
160         ExprKind::Break(_, Some(e)) => (Pat::Str("break"), expr_search_pat(tcx, e).1),
161         ExprKind::Continue(Destination { label: None, .. }) => (Pat::Str("continue"), Pat::Str("continue")),
162         ExprKind::Continue(Destination { label: Some(name), .. }) => (Pat::Str("continue"), Pat::Sym(name.ident.name)),
163         ExprKind::Ret(None) => (Pat::Str("return"), Pat::Str("return")),
164         ExprKind::Ret(Some(e)) => (Pat::Str("return"), expr_search_pat(tcx, e).1),
165         ExprKind::Struct(path, _, _) => (qpath_search_pat(path).0, Pat::Str("}")),
166         ExprKind::Yield(e, YieldSource::Yield) => (Pat::Str("yield"), expr_search_pat(tcx, e).1),
167         _ => (Pat::Str(""), Pat::Str("")),
168     }
169 }
170
171 fn fn_header_search_pat(header: FnHeader) -> Pat {
172     if header.is_async() {
173         Pat::Str("async")
174     } else if header.is_const() {
175         Pat::Str("const")
176     } else if header.is_unsafe() {
177         Pat::Str("unsafe")
178     } else if header.abi != Abi::Rust {
179         Pat::Str("extern")
180     } else {
181         Pat::MultiStr(&["fn", "extern"])
182     }
183 }
184
185 fn item_search_pat(item: &Item<'_>) -> (Pat, Pat) {
186     let (start_pat, end_pat) = match &item.kind {
187         ItemKind::ExternCrate(_) => (Pat::Str("extern"), Pat::Str(";")),
188         ItemKind::Static(..) => (Pat::Str("static"), Pat::Str(";")),
189         ItemKind::Const(..) => (Pat::Str("const"), Pat::Str(";")),
190         ItemKind::Fn(sig, ..) => (fn_header_search_pat(sig.header), Pat::Str("")),
191         ItemKind::ForeignMod { .. } => (Pat::Str("extern"), Pat::Str("}")),
192         ItemKind::TyAlias(..) | ItemKind::OpaqueTy(_) => (Pat::Str("type"), Pat::Str(";")),
193         ItemKind::Enum(..) => (Pat::Str("enum"), Pat::Str("}")),
194         ItemKind::Struct(VariantData::Struct(..), _) => (Pat::Str("struct"), Pat::Str("}")),
195         ItemKind::Struct(..) => (Pat::Str("struct"), Pat::Str(";")),
196         ItemKind::Union(..) => (Pat::Str("union"), Pat::Str("}")),
197         ItemKind::Trait(_, Unsafety::Unsafe, ..)
198         | ItemKind::Impl(Impl {
199             unsafety: Unsafety::Unsafe,
200             ..
201         }) => (Pat::Str("unsafe"), Pat::Str("}")),
202         ItemKind::Trait(IsAuto::Yes, ..) => (Pat::Str("auto"), Pat::Str("}")),
203         ItemKind::Trait(..) => (Pat::Str("trait"), Pat::Str("}")),
204         ItemKind::Impl(_) => (Pat::Str("impl"), Pat::Str("}")),
205         _ => return (Pat::Str(""), Pat::Str("")),
206     };
207     if item.vis_span.is_empty() {
208         (start_pat, end_pat)
209     } else {
210         (Pat::Str("pub"), end_pat)
211     }
212 }
213
214 fn trait_item_search_pat(item: &TraitItem<'_>) -> (Pat, Pat) {
215     match &item.kind {
216         TraitItemKind::Const(..) => (Pat::Str("const"), Pat::Str(";")),
217         TraitItemKind::Type(..) => (Pat::Str("type"), Pat::Str(";")),
218         TraitItemKind::Fn(sig, ..) => (fn_header_search_pat(sig.header), Pat::Str("")),
219     }
220 }
221
222 fn impl_item_search_pat(item: &ImplItem<'_>) -> (Pat, Pat) {
223     let (start_pat, end_pat) = match &item.kind {
224         ImplItemKind::Const(..) => (Pat::Str("const"), Pat::Str(";")),
225         ImplItemKind::Type(..) => (Pat::Str("type"), Pat::Str(";")),
226         ImplItemKind::Fn(sig, ..) => (fn_header_search_pat(sig.header), Pat::Str("")),
227     };
228     if item.vis_span.is_empty() {
229         (start_pat, end_pat)
230     } else {
231         (Pat::Str("pub"), end_pat)
232     }
233 }
234
235 fn field_def_search_pat(def: &FieldDef<'_>) -> (Pat, Pat) {
236     if def.vis_span.is_empty() {
237         if def.is_positional() {
238             (Pat::Str(""), Pat::Str(""))
239         } else {
240             (Pat::Sym(def.ident.name), Pat::Str(""))
241         }
242     } else {
243         (Pat::Str("pub"), Pat::Str(""))
244     }
245 }
246
247 fn variant_search_pat(v: &Variant<'_>) -> (Pat, Pat) {
248     match v.data {
249         VariantData::Struct(..) => (Pat::Sym(v.ident.name), Pat::Str("}")),
250         VariantData::Tuple(..) => (Pat::Sym(v.ident.name), Pat::Str("")),
251         VariantData::Unit(..) => (Pat::Sym(v.ident.name), Pat::Sym(v.ident.name)),
252     }
253 }
254
255 fn fn_kind_pat(tcx: TyCtxt<'_>, kind: &FnKind<'_>, body: &Body<'_>, hir_id: HirId) -> (Pat, Pat) {
256     let (start_pat, end_pat) = match kind {
257         FnKind::ItemFn(.., header) => (fn_header_search_pat(*header), Pat::Str("")),
258         FnKind::Method(.., sig) => (fn_header_search_pat(sig.header), Pat::Str("")),
259         FnKind::Closure => return (Pat::Str(""), expr_search_pat(tcx, body.value).1),
260     };
261     let start_pat = match tcx.hir().get(hir_id) {
262         Node::Item(Item { vis_span, .. }) | Node::ImplItem(ImplItem { vis_span, .. }) => {
263             if vis_span.is_empty() {
264                 start_pat
265             } else {
266                 Pat::Str("pub")
267             }
268         },
269         Node::TraitItem(_) => start_pat,
270         _ => Pat::Str(""),
271     };
272     (start_pat, end_pat)
273 }
274
275 pub trait WithSearchPat {
276     type Context: LintContext;
277     fn search_pat(&self, cx: &Self::Context) -> (Pat, Pat);
278     fn span(&self) -> Span;
279 }
280 macro_rules! impl_with_search_pat {
281     ($cx:ident: $ty:ident with $fn:ident $(($tcx:ident))?) => {
282         impl<'cx> WithSearchPat for $ty<'cx> {
283             type Context = $cx<'cx>;
284             #[allow(unused_variables)]
285             fn search_pat(&self, cx: &Self::Context) -> (Pat, Pat) {
286                 $(let $tcx = cx.tcx;)?
287                 $fn($($tcx,)? self)
288             }
289             fn span(&self) -> Span {
290                 self.span
291             }
292         }
293     };
294 }
295 impl_with_search_pat!(LateContext: Expr with expr_search_pat(tcx));
296 impl_with_search_pat!(LateContext: Item with item_search_pat);
297 impl_with_search_pat!(LateContext: TraitItem with trait_item_search_pat);
298 impl_with_search_pat!(LateContext: ImplItem with impl_item_search_pat);
299 impl_with_search_pat!(LateContext: FieldDef with field_def_search_pat);
300 impl_with_search_pat!(LateContext: Variant with variant_search_pat);
301
302 impl<'cx> WithSearchPat for (&FnKind<'cx>, &Body<'cx>, HirId, Span) {
303     type Context = LateContext<'cx>;
304
305     fn search_pat(&self, cx: &Self::Context) -> (Pat, Pat) {
306         fn_kind_pat(cx.tcx, self.0, self.1, self.2)
307     }
308
309     fn span(&self) -> Span {
310         self.3
311     }
312 }
313
314 /// Checks if the item likely came from a proc-macro.
315 ///
316 /// This should be called after `in_external_macro` and the initial pattern matching of the ast as
317 /// it is significantly slower than both of those.
318 pub fn is_from_proc_macro<T: WithSearchPat>(cx: &T::Context, item: &T) -> bool {
319     let (start_pat, end_pat) = item.search_pat(cx);
320     !span_matches_pat(cx.sess(), item.span(), start_pat, end_pat)
321 }
322
323 /// Checks if the span actually refers to a match expression
324 pub fn is_span_match(cx: &impl LintContext, span: Span) -> bool {
325     span_matches_pat(cx.sess(), span, Pat::Str("match"), Pat::Str("}"))
326 }
327
328 /// Checks if the span actually refers to an if expression
329 pub fn is_span_if(cx: &impl LintContext, span: Span) -> bool {
330     span_matches_pat(cx.sess(), span, Pat::Str("if"), Pat::Str("}"))
331 }