|
| 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 | + Block, BlockCheckMode, Closure, Destination, Expr, ExprKind, LoopSource, MatchSource, QPath, UnOp, UnsafeSource, |
| 18 | + YieldSource, |
| 19 | +}; |
| 20 | +use rustc_lint::{LateContext, LintContext}; |
| 21 | +use rustc_middle::ty::TyCtxt; |
| 22 | +use rustc_session::Session; |
| 23 | +use rustc_span::{Span, Symbol}; |
| 24 | + |
| 25 | +#[derive(Clone, Copy)] |
| 26 | +enum Pat { |
| 27 | + Str(&'static str), |
| 28 | + Sym(Symbol), |
| 29 | + Num, |
| 30 | +} |
| 31 | + |
| 32 | +/// Checks if the start and the end of the span's text matches the patterns. This will return false |
| 33 | +/// if the span crosses multiple files or if source is not available. |
| 34 | +fn span_matches_pat(sess: &Session, span: Span, start_pat: Pat, end_pat: Pat) -> bool { |
| 35 | + let pos = sess.source_map().lookup_byte_offset(span.lo()); |
| 36 | + let Some(ref src) = pos.sf.src else { |
| 37 | + return false; |
| 38 | + }; |
| 39 | + let end = span.hi() - pos.sf.start_pos; |
| 40 | + src.get(pos.pos.0 as usize..end.0 as usize).map_or(false, |s| { |
| 41 | + // Spans can be wrapped in a mixture or parenthesis, whitespace, and trailing commas. |
| 42 | + let start_str = s.trim_start_matches(|c: char| c.is_whitespace() || c == '('); |
| 43 | + let end_str = s.trim_end_matches(|c: char| c.is_whitespace() || c == ')' || c == ','); |
| 44 | + (match start_pat { |
| 45 | + Pat::Str(text) => start_str.starts_with(text), |
| 46 | + Pat::Sym(sym) => start_str.starts_with(sym.as_str()), |
| 47 | + Pat::Num => start_str.as_bytes().first().map_or(false, u8::is_ascii_digit), |
| 48 | + } && match end_pat { |
| 49 | + Pat::Str(text) => end_str.ends_with(text), |
| 50 | + Pat::Sym(sym) => end_str.ends_with(sym.as_str()), |
| 51 | + Pat::Num => end_str.as_bytes().last().map_or(false, u8::is_ascii_hexdigit), |
| 52 | + }) |
| 53 | + }) |
| 54 | +} |
| 55 | + |
| 56 | +/// Get the search patterns to use for the given literal |
| 57 | +fn lit_search_pat(lit: &LitKind) -> (Pat, Pat) { |
| 58 | + match lit { |
| 59 | + LitKind::Str(_, StrStyle::Cooked) => (Pat::Str("\""), Pat::Str("\"")), |
| 60 | + LitKind::Str(_, StrStyle::Raw(0)) => (Pat::Str("r"), Pat::Str("\"")), |
| 61 | + LitKind::Str(_, StrStyle::Raw(_)) => (Pat::Str("r#"), Pat::Str("#")), |
| 62 | + LitKind::ByteStr(_) => (Pat::Str("b\""), Pat::Str("\"")), |
| 63 | + LitKind::Byte(_) => (Pat::Str("b'"), Pat::Str("'")), |
| 64 | + LitKind::Char(_) => (Pat::Str("'"), Pat::Str("'")), |
| 65 | + LitKind::Int(_, LitIntType::Signed(IntTy::Isize)) => (Pat::Num, Pat::Str("isize")), |
| 66 | + LitKind::Int(_, LitIntType::Unsigned(UintTy::Usize)) => (Pat::Num, Pat::Str("usize")), |
| 67 | + LitKind::Int(..) => (Pat::Num, Pat::Num), |
| 68 | + LitKind::Float(..) => (Pat::Num, Pat::Str("")), |
| 69 | + LitKind::Bool(true) => (Pat::Str("true"), Pat::Str("true")), |
| 70 | + LitKind::Bool(false) => (Pat::Str("false"), Pat::Str("false")), |
| 71 | + _ => (Pat::Str(""), Pat::Str("")), |
| 72 | + } |
| 73 | +} |
| 74 | + |
| 75 | +/// Get the search patterns to use for the given path |
| 76 | +fn qpath_search_pat(path: &QPath<'_>) -> (Pat, Pat) { |
| 77 | + match path { |
| 78 | + QPath::Resolved(ty, path) => { |
| 79 | + let start = if ty.is_some() { |
| 80 | + Pat::Str("<") |
| 81 | + } else { |
| 82 | + path.segments |
| 83 | + .first() |
| 84 | + .map_or(Pat::Str(""), |seg| Pat::Sym(seg.ident.name)) |
| 85 | + }; |
| 86 | + let end = path.segments.last().map_or(Pat::Str(""), |seg| { |
| 87 | + if seg.args.is_some() { |
| 88 | + Pat::Str(">") |
| 89 | + } else { |
| 90 | + Pat::Sym(seg.ident.name) |
| 91 | + } |
| 92 | + }); |
| 93 | + (start, end) |
| 94 | + }, |
| 95 | + QPath::TypeRelative(_, name) => (Pat::Str(""), Pat::Sym(name.ident.name)), |
| 96 | + QPath::LangItem(..) => (Pat::Str(""), Pat::Str("")), |
| 97 | + } |
| 98 | +} |
| 99 | + |
| 100 | +/// Get the search patterns to use for the given expression |
| 101 | +fn expr_search_pat(tcx: TyCtxt<'_>, e: &Expr<'_>) -> (Pat, Pat) { |
| 102 | + match e.kind { |
| 103 | + ExprKind::Box(e) => (Pat::Str("box"), expr_search_pat(tcx, e).1), |
| 104 | + ExprKind::ConstBlock(_) => (Pat::Str("const"), Pat::Str("}")), |
| 105 | + ExprKind::Tup([]) => (Pat::Str(")"), Pat::Str("(")), |
| 106 | + ExprKind::Unary(UnOp::Deref, _) => (Pat::Str("*"), expr_search_pat(tcx, e).1), |
| 107 | + ExprKind::Unary(UnOp::Not, _) => (Pat::Str("!"), expr_search_pat(tcx, e).1), |
| 108 | + ExprKind::Unary(UnOp::Neg, _) => (Pat::Str("-"), expr_search_pat(tcx, e).1), |
| 109 | + ExprKind::Lit(ref lit) => lit_search_pat(&lit.node), |
| 110 | + ExprKind::Array(_) | ExprKind::Repeat(..) => (Pat::Str("["), Pat::Str("]")), |
| 111 | + ExprKind::Call(e, []) | ExprKind::MethodCall(_, [e], _) => (expr_search_pat(tcx, e).0, Pat::Str("(")), |
| 112 | + ExprKind::Call(first, [.., last]) |
| 113 | + | ExprKind::MethodCall(_, [first, .., last], _) |
| 114 | + | ExprKind::Binary(_, first, last) |
| 115 | + | ExprKind::Tup([first, .., last]) |
| 116 | + | ExprKind::Assign(first, last, _) |
| 117 | + | ExprKind::AssignOp(_, first, last) => (expr_search_pat(tcx, first).0, expr_search_pat(tcx, last).1), |
| 118 | + ExprKind::Tup([e]) | ExprKind::DropTemps(e) => expr_search_pat(tcx, e), |
| 119 | + ExprKind::Cast(e, _) | ExprKind::Type(e, _) => (expr_search_pat(tcx, e).0, Pat::Str("")), |
| 120 | + ExprKind::Let(let_expr) => (Pat::Str("let"), expr_search_pat(tcx, let_expr.init).1), |
| 121 | + ExprKind::If(..) => (Pat::Str("if"), Pat::Str("}")), |
| 122 | + ExprKind::Loop(_, Some(_), _, _) | ExprKind::Block(_, Some(_)) => (Pat::Str("'"), Pat::Str("}")), |
| 123 | + ExprKind::Loop(_, None, LoopSource::Loop, _) => (Pat::Str("loop"), Pat::Str("}")), |
| 124 | + ExprKind::Loop(_, None, LoopSource::While, _) => (Pat::Str("while"), Pat::Str("}")), |
| 125 | + ExprKind::Loop(_, None, LoopSource::ForLoop, _) | ExprKind::Match(_, _, MatchSource::ForLoopDesugar) => { |
| 126 | + (Pat::Str("for"), Pat::Str("}")) |
| 127 | + }, |
| 128 | + ExprKind::Match(_, _, MatchSource::Normal) => (Pat::Str("match"), Pat::Str("}")), |
| 129 | + ExprKind::Match(e, _, MatchSource::TryDesugar) => (expr_search_pat(tcx, e).0, Pat::Str("?")), |
| 130 | + ExprKind::Match(e, _, MatchSource::AwaitDesugar) | ExprKind::Yield(e, YieldSource::Await { .. }) => { |
| 131 | + (expr_search_pat(tcx, e).0, Pat::Str("await")) |
| 132 | + }, |
| 133 | + ExprKind::Closure(&Closure { body, .. }) => (Pat::Str(""), expr_search_pat(tcx, &tcx.hir().body(body).value).1), |
| 134 | + ExprKind::Block( |
| 135 | + Block { |
| 136 | + rules: BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided), |
| 137 | + .. |
| 138 | + }, |
| 139 | + None, |
| 140 | + ) => (Pat::Str("unsafe"), Pat::Str("}")), |
| 141 | + ExprKind::Block(_, None) => (Pat::Str("{"), Pat::Str("}")), |
| 142 | + ExprKind::Field(e, name) => (expr_search_pat(tcx, e).0, Pat::Sym(name.name)), |
| 143 | + ExprKind::Index(e, _) => (expr_search_pat(tcx, e).0, Pat::Str("]")), |
| 144 | + ExprKind::Path(ref path) => qpath_search_pat(path), |
| 145 | + ExprKind::AddrOf(_, _, e) => (Pat::Str("&"), expr_search_pat(tcx, e).1), |
| 146 | + ExprKind::Break(Destination { label: None, .. }, None) => (Pat::Str("break"), Pat::Str("break")), |
| 147 | + ExprKind::Break(Destination { label: Some(name), .. }, None) => (Pat::Str("break"), Pat::Sym(name.ident.name)), |
| 148 | + ExprKind::Break(_, Some(e)) => (Pat::Str("break"), expr_search_pat(tcx, e).1), |
| 149 | + ExprKind::Continue(Destination { label: None, .. }) => (Pat::Str("continue"), Pat::Str("continue")), |
| 150 | + ExprKind::Continue(Destination { label: Some(name), .. }) => (Pat::Str("continue"), Pat::Sym(name.ident.name)), |
| 151 | + ExprKind::Ret(None) => (Pat::Str("return"), Pat::Str("return")), |
| 152 | + ExprKind::Struct(path, _, _) => (qpath_search_pat(path).0, Pat::Str("}")), |
| 153 | + ExprKind::Yield(e, YieldSource::Yield) => (Pat::Str("yield"), expr_search_pat(tcx, e).1), |
| 154 | + _ => (Pat::Str(""), Pat::Str("")), |
| 155 | + } |
| 156 | +} |
| 157 | + |
| 158 | +/// Checks if the expression likely came from a proc-macro |
| 159 | +pub fn is_expr_from_proc_macro(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { |
| 160 | + let (start_pat, end_pat) = expr_search_pat(cx.tcx, e); |
| 161 | + !span_matches_pat(cx.sess(), e.span, start_pat, end_pat) |
| 162 | +} |
| 163 | + |
| 164 | +/// Checks if the span actually refers to a match expression |
| 165 | +pub fn is_span_match(cx: &LateContext<'_>, span: Span) -> bool { |
| 166 | + span_matches_pat(cx.sess(), span, Pat::Str("match"), Pat::Str("}")) |
| 167 | +} |
0 commit comments