Auto merge of #100419 - flip1995:clippyup, r=Manishearth
Update Clippy r? `@Manishearth`
This commit is contained in:
commit
9ac237dce5
182 changed files with 2240 additions and 827 deletions
329
clippy_utils/src/check_proc_macro.rs
Normal file
329
clippy_utils/src/check_proc_macro.rs
Normal file
|
|
@ -0,0 +1,329 @@
|
|||
//! This module handles checking if the span given is from a proc-macro or not.
|
||||
//!
|
||||
//! Proc-macros are capable of setting the span of every token they output to a few possible spans.
|
||||
//! This includes spans we can detect easily as coming from a proc-macro (e.g. the call site
|
||||
//! or the def site), and spans we can't easily detect as such (e.g. the span of any token
|
||||
//! passed into the proc macro). This capability means proc-macros are capable of generating code
|
||||
//! with a span that looks like it was written by the user, but which should not be linted by clippy
|
||||
//! as it was generated by an external macro.
|
||||
//!
|
||||
//! That brings us to this module. The current approach is to determine a small bit of text which
|
||||
//! must exist at both the start and the end of an item (e.g. an expression or a path) assuming the
|
||||
//! code was written, and check if the span contains that text. Note this will only work correctly
|
||||
//! if the span is not from a `macro_rules` based macro.
|
||||
|
||||
use rustc_ast::ast::{IntTy, LitIntType, LitKind, StrStyle, UintTy};
|
||||
use rustc_hir::{
|
||||
intravisit::FnKind, Block, BlockCheckMode, Body, Closure, Destination, Expr, ExprKind, FieldDef, FnHeader, HirId,
|
||||
Impl, ImplItem, ImplItemKind, IsAuto, Item, ItemKind, LoopSource, MatchSource, Node, QPath, TraitItem,
|
||||
TraitItemKind, UnOp, UnsafeSource, Unsafety, Variant, VariantData, YieldSource,
|
||||
};
|
||||
use rustc_lint::{LateContext, LintContext};
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
use rustc_session::Session;
|
||||
use rustc_span::{Span, Symbol};
|
||||
use rustc_target::spec::abi::Abi;
|
||||
|
||||
/// The search pattern to look for. Used by `span_matches_pat`
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Pat {
|
||||
/// A single string.
|
||||
Str(&'static str),
|
||||
/// Any of the given strings.
|
||||
MultiStr(&'static [&'static str]),
|
||||
/// The string representation of the symbol.
|
||||
Sym(Symbol),
|
||||
/// Any decimal or hexadecimal digit depending on the location.
|
||||
Num,
|
||||
}
|
||||
|
||||
/// Checks if the start and the end of the span's text matches the patterns. This will return false
|
||||
/// if the span crosses multiple files or if source is not available.
|
||||
fn span_matches_pat(sess: &Session, span: Span, start_pat: Pat, end_pat: Pat) -> bool {
|
||||
let pos = sess.source_map().lookup_byte_offset(span.lo());
|
||||
let Some(ref src) = pos.sf.src else {
|
||||
return false;
|
||||
};
|
||||
let end = span.hi() - pos.sf.start_pos;
|
||||
src.get(pos.pos.0 as usize..end.0 as usize).map_or(false, |s| {
|
||||
// Spans can be wrapped in a mixture or parenthesis, whitespace, and trailing commas.
|
||||
let start_str = s.trim_start_matches(|c: char| c.is_whitespace() || c == '(');
|
||||
let end_str = s.trim_end_matches(|c: char| c.is_whitespace() || c == ')' || c == ',');
|
||||
(match start_pat {
|
||||
Pat::Str(text) => start_str.starts_with(text),
|
||||
Pat::MultiStr(texts) => texts.iter().any(|s| start_str.starts_with(s)),
|
||||
Pat::Sym(sym) => start_str.starts_with(sym.as_str()),
|
||||
Pat::Num => start_str.as_bytes().first().map_or(false, u8::is_ascii_digit),
|
||||
} && match end_pat {
|
||||
Pat::Str(text) => end_str.ends_with(text),
|
||||
Pat::MultiStr(texts) => texts.iter().any(|s| start_str.ends_with(s)),
|
||||
Pat::Sym(sym) => end_str.ends_with(sym.as_str()),
|
||||
Pat::Num => end_str.as_bytes().last().map_or(false, u8::is_ascii_hexdigit),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the search patterns to use for the given literal
|
||||
fn lit_search_pat(lit: &LitKind) -> (Pat, Pat) {
|
||||
match lit {
|
||||
LitKind::Str(_, StrStyle::Cooked) => (Pat::Str("\""), Pat::Str("\"")),
|
||||
LitKind::Str(_, StrStyle::Raw(0)) => (Pat::Str("r"), Pat::Str("\"")),
|
||||
LitKind::Str(_, StrStyle::Raw(_)) => (Pat::Str("r#"), Pat::Str("#")),
|
||||
LitKind::ByteStr(_) => (Pat::Str("b\""), Pat::Str("\"")),
|
||||
LitKind::Byte(_) => (Pat::Str("b'"), Pat::Str("'")),
|
||||
LitKind::Char(_) => (Pat::Str("'"), Pat::Str("'")),
|
||||
LitKind::Int(_, LitIntType::Signed(IntTy::Isize)) => (Pat::Num, Pat::Str("isize")),
|
||||
LitKind::Int(_, LitIntType::Unsigned(UintTy::Usize)) => (Pat::Num, Pat::Str("usize")),
|
||||
LitKind::Int(..) => (Pat::Num, Pat::Num),
|
||||
LitKind::Float(..) => (Pat::Num, Pat::Str("")),
|
||||
LitKind::Bool(true) => (Pat::Str("true"), Pat::Str("true")),
|
||||
LitKind::Bool(false) => (Pat::Str("false"), Pat::Str("false")),
|
||||
_ => (Pat::Str(""), Pat::Str("")),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the search patterns to use for the given path
|
||||
fn qpath_search_pat(path: &QPath<'_>) -> (Pat, Pat) {
|
||||
match path {
|
||||
QPath::Resolved(ty, path) => {
|
||||
let start = if ty.is_some() {
|
||||
Pat::Str("<")
|
||||
} else {
|
||||
path.segments
|
||||
.first()
|
||||
.map_or(Pat::Str(""), |seg| Pat::Sym(seg.ident.name))
|
||||
};
|
||||
let end = path.segments.last().map_or(Pat::Str(""), |seg| {
|
||||
if seg.args.is_some() {
|
||||
Pat::Str(">")
|
||||
} else {
|
||||
Pat::Sym(seg.ident.name)
|
||||
}
|
||||
});
|
||||
(start, end)
|
||||
},
|
||||
QPath::TypeRelative(_, name) => (Pat::Str(""), Pat::Sym(name.ident.name)),
|
||||
QPath::LangItem(..) => (Pat::Str(""), Pat::Str("")),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the search patterns to use for the given expression
|
||||
fn expr_search_pat(tcx: TyCtxt<'_>, e: &Expr<'_>) -> (Pat, Pat) {
|
||||
match e.kind {
|
||||
ExprKind::Box(e) => (Pat::Str("box"), expr_search_pat(tcx, e).1),
|
||||
ExprKind::ConstBlock(_) => (Pat::Str("const"), Pat::Str("}")),
|
||||
ExprKind::Tup([]) => (Pat::Str(")"), Pat::Str("(")),
|
||||
ExprKind::Unary(UnOp::Deref, e) => (Pat::Str("*"), expr_search_pat(tcx, e).1),
|
||||
ExprKind::Unary(UnOp::Not, e) => (Pat::Str("!"), expr_search_pat(tcx, e).1),
|
||||
ExprKind::Unary(UnOp::Neg, e) => (Pat::Str("-"), expr_search_pat(tcx, e).1),
|
||||
ExprKind::Lit(ref lit) => lit_search_pat(&lit.node),
|
||||
ExprKind::Array(_) | ExprKind::Repeat(..) => (Pat::Str("["), Pat::Str("]")),
|
||||
ExprKind::Call(e, []) | ExprKind::MethodCall(_, [e], _) => (expr_search_pat(tcx, e).0, Pat::Str("(")),
|
||||
ExprKind::Call(first, [.., last])
|
||||
| ExprKind::MethodCall(_, [first, .., last], _)
|
||||
| ExprKind::Binary(_, first, last)
|
||||
| ExprKind::Tup([first, .., last])
|
||||
| ExprKind::Assign(first, last, _)
|
||||
| ExprKind::AssignOp(_, first, last) => (expr_search_pat(tcx, first).0, expr_search_pat(tcx, last).1),
|
||||
ExprKind::Tup([e]) | ExprKind::DropTemps(e) => expr_search_pat(tcx, e),
|
||||
ExprKind::Cast(e, _) | ExprKind::Type(e, _) => (expr_search_pat(tcx, e).0, Pat::Str("")),
|
||||
ExprKind::Let(let_expr) => (Pat::Str("let"), expr_search_pat(tcx, let_expr.init).1),
|
||||
ExprKind::If(..) => (Pat::Str("if"), Pat::Str("}")),
|
||||
ExprKind::Loop(_, Some(_), _, _) | ExprKind::Block(_, Some(_)) => (Pat::Str("'"), Pat::Str("}")),
|
||||
ExprKind::Loop(_, None, LoopSource::Loop, _) => (Pat::Str("loop"), Pat::Str("}")),
|
||||
ExprKind::Loop(_, None, LoopSource::While, _) => (Pat::Str("while"), Pat::Str("}")),
|
||||
ExprKind::Loop(_, None, LoopSource::ForLoop, _) | ExprKind::Match(_, _, MatchSource::ForLoopDesugar) => {
|
||||
(Pat::Str("for"), Pat::Str("}"))
|
||||
},
|
||||
ExprKind::Match(_, _, MatchSource::Normal) => (Pat::Str("match"), Pat::Str("}")),
|
||||
ExprKind::Match(e, _, MatchSource::TryDesugar) => (expr_search_pat(tcx, e).0, Pat::Str("?")),
|
||||
ExprKind::Match(e, _, MatchSource::AwaitDesugar) | ExprKind::Yield(e, YieldSource::Await { .. }) => {
|
||||
(expr_search_pat(tcx, e).0, Pat::Str("await"))
|
||||
},
|
||||
ExprKind::Closure(&Closure { body, .. }) => (Pat::Str(""), expr_search_pat(tcx, &tcx.hir().body(body).value).1),
|
||||
ExprKind::Block(
|
||||
Block {
|
||||
rules: BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided),
|
||||
..
|
||||
},
|
||||
None,
|
||||
) => (Pat::Str("unsafe"), Pat::Str("}")),
|
||||
ExprKind::Block(_, None) => (Pat::Str("{"), Pat::Str("}")),
|
||||
ExprKind::Field(e, name) => (expr_search_pat(tcx, e).0, Pat::Sym(name.name)),
|
||||
ExprKind::Index(e, _) => (expr_search_pat(tcx, e).0, Pat::Str("]")),
|
||||
ExprKind::Path(ref path) => qpath_search_pat(path),
|
||||
ExprKind::AddrOf(_, _, e) => (Pat::Str("&"), expr_search_pat(tcx, e).1),
|
||||
ExprKind::Break(Destination { label: None, .. }, None) => (Pat::Str("break"), Pat::Str("break")),
|
||||
ExprKind::Break(Destination { label: Some(name), .. }, None) => (Pat::Str("break"), Pat::Sym(name.ident.name)),
|
||||
ExprKind::Break(_, Some(e)) => (Pat::Str("break"), expr_search_pat(tcx, e).1),
|
||||
ExprKind::Continue(Destination { label: None, .. }) => (Pat::Str("continue"), Pat::Str("continue")),
|
||||
ExprKind::Continue(Destination { label: Some(name), .. }) => (Pat::Str("continue"), Pat::Sym(name.ident.name)),
|
||||
ExprKind::Ret(None) => (Pat::Str("return"), Pat::Str("return")),
|
||||
ExprKind::Ret(Some(e)) => (Pat::Str("return"), expr_search_pat(tcx, e).1),
|
||||
ExprKind::Struct(path, _, _) => (qpath_search_pat(path).0, Pat::Str("}")),
|
||||
ExprKind::Yield(e, YieldSource::Yield) => (Pat::Str("yield"), expr_search_pat(tcx, e).1),
|
||||
_ => (Pat::Str(""), Pat::Str("")),
|
||||
}
|
||||
}
|
||||
|
||||
fn fn_header_search_pat(header: FnHeader) -> Pat {
|
||||
if header.is_async() {
|
||||
Pat::Str("async")
|
||||
} else if header.is_const() {
|
||||
Pat::Str("const")
|
||||
} else if header.is_unsafe() {
|
||||
Pat::Str("unsafe")
|
||||
} else if header.abi != Abi::Rust {
|
||||
Pat::Str("extern")
|
||||
} else {
|
||||
Pat::MultiStr(&["fn", "extern"])
|
||||
}
|
||||
}
|
||||
|
||||
fn item_search_pat(item: &Item<'_>) -> (Pat, Pat) {
|
||||
let (start_pat, end_pat) = match &item.kind {
|
||||
ItemKind::ExternCrate(_) => (Pat::Str("extern"), Pat::Str(";")),
|
||||
ItemKind::Static(..) => (Pat::Str("static"), Pat::Str(";")),
|
||||
ItemKind::Const(..) => (Pat::Str("const"), Pat::Str(";")),
|
||||
ItemKind::Fn(sig, ..) => (fn_header_search_pat(sig.header), Pat::Str("")),
|
||||
ItemKind::ForeignMod { .. } => (Pat::Str("extern"), Pat::Str("}")),
|
||||
ItemKind::TyAlias(..) | ItemKind::OpaqueTy(_) => (Pat::Str("type"), Pat::Str(";")),
|
||||
ItemKind::Enum(..) => (Pat::Str("enum"), Pat::Str("}")),
|
||||
ItemKind::Struct(VariantData::Struct(..), _) => (Pat::Str("struct"), Pat::Str("}")),
|
||||
ItemKind::Struct(..) => (Pat::Str("struct"), Pat::Str(";")),
|
||||
ItemKind::Union(..) => (Pat::Str("union"), Pat::Str("}")),
|
||||
ItemKind::Trait(_, Unsafety::Unsafe, ..)
|
||||
| ItemKind::Impl(Impl {
|
||||
unsafety: Unsafety::Unsafe,
|
||||
..
|
||||
}) => (Pat::Str("unsafe"), Pat::Str("}")),
|
||||
ItemKind::Trait(IsAuto::Yes, ..) => (Pat::Str("auto"), Pat::Str("}")),
|
||||
ItemKind::Trait(..) => (Pat::Str("trait"), Pat::Str("}")),
|
||||
ItemKind::Impl(_) => (Pat::Str("impl"), Pat::Str("}")),
|
||||
_ => return (Pat::Str(""), Pat::Str("")),
|
||||
};
|
||||
if item.vis_span.is_empty() {
|
||||
(start_pat, end_pat)
|
||||
} else {
|
||||
(Pat::Str("pub"), end_pat)
|
||||
}
|
||||
}
|
||||
|
||||
fn trait_item_search_pat(item: &TraitItem<'_>) -> (Pat, Pat) {
|
||||
match &item.kind {
|
||||
TraitItemKind::Const(..) => (Pat::Str("const"), Pat::Str(";")),
|
||||
TraitItemKind::Type(..) => (Pat::Str("type"), Pat::Str(";")),
|
||||
TraitItemKind::Fn(sig, ..) => (fn_header_search_pat(sig.header), Pat::Str("")),
|
||||
}
|
||||
}
|
||||
|
||||
fn impl_item_search_pat(item: &ImplItem<'_>) -> (Pat, Pat) {
|
||||
let (start_pat, end_pat) = match &item.kind {
|
||||
ImplItemKind::Const(..) => (Pat::Str("const"), Pat::Str(";")),
|
||||
ImplItemKind::TyAlias(..) => (Pat::Str("type"), Pat::Str(";")),
|
||||
ImplItemKind::Fn(sig, ..) => (fn_header_search_pat(sig.header), Pat::Str("")),
|
||||
};
|
||||
if item.vis_span.is_empty() {
|
||||
(start_pat, end_pat)
|
||||
} else {
|
||||
(Pat::Str("pub"), end_pat)
|
||||
}
|
||||
}
|
||||
|
||||
fn field_def_search_pat(def: &FieldDef<'_>) -> (Pat, Pat) {
|
||||
if def.vis_span.is_empty() {
|
||||
if def.is_positional() {
|
||||
(Pat::Str(""), Pat::Str(""))
|
||||
} else {
|
||||
(Pat::Sym(def.ident.name), Pat::Str(""))
|
||||
}
|
||||
} else {
|
||||
(Pat::Str("pub"), Pat::Str(""))
|
||||
}
|
||||
}
|
||||
|
||||
fn variant_search_pat(v: &Variant<'_>) -> (Pat, Pat) {
|
||||
match v.data {
|
||||
VariantData::Struct(..) => (Pat::Sym(v.ident.name), Pat::Str("}")),
|
||||
VariantData::Tuple(..) => (Pat::Sym(v.ident.name), Pat::Str("")),
|
||||
VariantData::Unit(..) => (Pat::Sym(v.ident.name), Pat::Sym(v.ident.name)),
|
||||
}
|
||||
}
|
||||
|
||||
fn fn_kind_pat(tcx: TyCtxt<'_>, kind: &FnKind<'_>, body: &Body<'_>, hir_id: HirId) -> (Pat, Pat) {
|
||||
let (start_pat, end_pat) = match kind {
|
||||
FnKind::ItemFn(.., header) => (fn_header_search_pat(*header), Pat::Str("")),
|
||||
FnKind::Method(.., sig) => (fn_header_search_pat(sig.header), Pat::Str("")),
|
||||
FnKind::Closure => return (Pat::Str(""), expr_search_pat(tcx, &body.value).1),
|
||||
};
|
||||
let start_pat = match tcx.hir().get(hir_id) {
|
||||
Node::Item(Item { vis_span, .. }) | Node::ImplItem(ImplItem { vis_span, .. }) => {
|
||||
if vis_span.is_empty() {
|
||||
start_pat
|
||||
} else {
|
||||
Pat::Str("pub")
|
||||
}
|
||||
},
|
||||
Node::TraitItem(_) => start_pat,
|
||||
_ => Pat::Str(""),
|
||||
};
|
||||
(start_pat, end_pat)
|
||||
}
|
||||
|
||||
pub trait WithSearchPat {
|
||||
type Context: LintContext;
|
||||
fn search_pat(&self, cx: &Self::Context) -> (Pat, Pat);
|
||||
fn span(&self) -> Span;
|
||||
}
|
||||
macro_rules! impl_with_search_pat {
|
||||
($cx:ident: $ty:ident with $fn:ident $(($tcx:ident))?) => {
|
||||
impl<'cx> WithSearchPat for $ty<'cx> {
|
||||
type Context = $cx<'cx>;
|
||||
#[allow(unused_variables)]
|
||||
fn search_pat(&self, cx: &Self::Context) -> (Pat, Pat) {
|
||||
$(let $tcx = cx.tcx;)?
|
||||
$fn($($tcx,)? self)
|
||||
}
|
||||
fn span(&self) -> Span {
|
||||
self.span
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
impl_with_search_pat!(LateContext: Expr with expr_search_pat(tcx));
|
||||
impl_with_search_pat!(LateContext: Item with item_search_pat);
|
||||
impl_with_search_pat!(LateContext: TraitItem with trait_item_search_pat);
|
||||
impl_with_search_pat!(LateContext: ImplItem with impl_item_search_pat);
|
||||
impl_with_search_pat!(LateContext: FieldDef with field_def_search_pat);
|
||||
impl_with_search_pat!(LateContext: Variant with variant_search_pat);
|
||||
|
||||
impl<'cx> WithSearchPat for (&FnKind<'cx>, &Body<'cx>, HirId, Span) {
|
||||
type Context = LateContext<'cx>;
|
||||
|
||||
fn search_pat(&self, cx: &Self::Context) -> (Pat, Pat) {
|
||||
fn_kind_pat(cx.tcx, self.0, self.1, self.2)
|
||||
}
|
||||
|
||||
fn span(&self) -> Span {
|
||||
self.3
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the item likely came from a proc-macro.
|
||||
///
|
||||
/// This should be called after `in_external_macro` and the initial pattern matching of the ast as
|
||||
/// it is significantly slower than both of those.
|
||||
pub fn is_from_proc_macro<T: WithSearchPat>(cx: &T::Context, item: &T) -> bool {
|
||||
let (start_pat, end_pat) = item.search_pat(cx);
|
||||
!span_matches_pat(cx.sess(), item.span(), start_pat, end_pat)
|
||||
}
|
||||
|
||||
/// Checks if the span actually refers to a match expression
|
||||
pub fn is_span_match(cx: &impl LintContext, span: Span) -> bool {
|
||||
span_matches_pat(cx.sess(), span, Pat::Str("match"), Pat::Str("}"))
|
||||
}
|
||||
|
||||
/// Checks if the span actually refers to an if expression
|
||||
pub fn is_span_if(cx: &impl LintContext, span: Span) -> bool {
|
||||
span_matches_pat(cx.sess(), span, Pat::Str("if"), Pat::Str("}"))
|
||||
}
|
||||
|
|
@ -39,6 +39,7 @@ pub mod sym_helper;
|
|||
|
||||
pub mod ast_utils;
|
||||
pub mod attrs;
|
||||
mod check_proc_macro;
|
||||
pub mod comparisons;
|
||||
pub mod consts;
|
||||
pub mod diagnostics;
|
||||
|
|
@ -59,6 +60,7 @@ pub mod usage;
|
|||
pub mod visitors;
|
||||
|
||||
pub use self::attrs::*;
|
||||
pub use self::check_proc_macro::{is_from_proc_macro, is_span_if, is_span_match};
|
||||
pub use self::hir_utils::{
|
||||
both, count_eq, eq_expr_value, hash_expr, hash_stmt, over, HirEqInterExpr, SpanlessEq, SpanlessHash,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -223,10 +223,12 @@ impl<'a> NumericLiteral<'a> {
|
|||
|
||||
fn split_suffix<'a>(src: &'a str, lit_kind: &LitKind) -> (&'a str, Option<&'a str>) {
|
||||
debug_assert!(lit_kind.is_numeric());
|
||||
lit_suffix_length(lit_kind).map_or((src, None), |suffix_length| {
|
||||
let (unsuffixed, suffix) = src.split_at(src.len() - suffix_length);
|
||||
(unsuffixed, Some(suffix))
|
||||
})
|
||||
lit_suffix_length(lit_kind)
|
||||
.and_then(|suffix_length| src.len().checked_sub(suffix_length))
|
||||
.map_or((src, None), |split_pos| {
|
||||
let (unsuffixed, suffix) = src.split_at(split_pos);
|
||||
(unsuffixed, Some(suffix))
|
||||
})
|
||||
}
|
||||
|
||||
fn lit_suffix_length(lit_kind: &LitKind) -> Option<usize> {
|
||||
|
|
|
|||
|
|
@ -194,3 +194,5 @@ pub const VEC_RESIZE: [&str; 4] = ["alloc", "vec", "Vec", "resize"];
|
|||
pub const WEAK_ARC: [&str; 3] = ["alloc", "sync", "Weak"];
|
||||
pub const WEAK_RC: [&str; 3] = ["alloc", "rc", "Weak"];
|
||||
pub const PTR_NON_NULL: [&str; 4] = ["core", "ptr", "non_null", "NonNull"];
|
||||
pub const INSTANT_NOW: [&str; 4] = ["std", "time", "Instant", "now"];
|
||||
pub const INSTANT: [&str; 3] = ["std", "time", "Instant"];
|
||||
|
|
|
|||
|
|
@ -11,24 +11,6 @@ use rustc_span::source_map::SourceMap;
|
|||
use rustc_span::{BytePos, Pos, Span, SpanData, SyntaxContext};
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// Checks if the span starts with the given text. This will return false if the span crosses
|
||||
/// multiple files or if source is not available.
|
||||
///
|
||||
/// This is used to check for proc macros giving unhelpful spans to things.
|
||||
pub fn span_starts_with<T: LintContext>(cx: &T, span: Span, text: &str) -> bool {
|
||||
fn helper(sm: &SourceMap, span: Span, text: &str) -> bool {
|
||||
let pos = sm.lookup_byte_offset(span.lo());
|
||||
let Some(ref src) = pos.sf.src else {
|
||||
return false;
|
||||
};
|
||||
let end = span.hi() - pos.sf.start_pos;
|
||||
src.get(pos.pos.0 as usize..end.0 as usize)
|
||||
// Expression spans can include wrapping parenthesis. Remove them first.
|
||||
.map_or(false, |s| s.trim_start_matches('(').starts_with(text))
|
||||
}
|
||||
helper(cx.sess().source_map(), span, text)
|
||||
}
|
||||
|
||||
/// Like `snippet_block`, but add braces if the expr is not an `ExprKind::Block`.
|
||||
/// Also takes an `Option<String>` which can be put inside the braces.
|
||||
pub fn expr_block<'a, T: LintContext>(
|
||||
|
|
|
|||
|
|
@ -315,6 +315,12 @@ impl<'a> Sugg<'a> {
|
|||
Sugg::NonParen(Cow::Owned(format!("{{ {} }}", self)))
|
||||
}
|
||||
|
||||
/// Convenience method to prefix the expression with the `async` keyword.
|
||||
/// Can be used after `blockify` to create an async block.
|
||||
pub fn asyncify(self) -> Sugg<'static> {
|
||||
Sugg::NonParen(Cow::Owned(format!("async {}", self)))
|
||||
}
|
||||
|
||||
/// Convenience method to create the `<lhs>..<rhs>` or `<lhs>...<rhs>`
|
||||
/// suggestion.
|
||||
pub fn range(self, end: &Self, limit: ast::RangeLimits) -> Sugg<'static> {
|
||||
|
|
|
|||
|
|
@ -503,7 +503,7 @@ pub fn all_predicates_of(tcx: TyCtxt<'_>, id: DefId) -> impl Iterator<Item = &(P
|
|||
pub enum ExprFnSig<'tcx> {
|
||||
Sig(Binder<'tcx, FnSig<'tcx>>, Option<DefId>),
|
||||
Closure(Option<&'tcx FnDecl<'tcx>>, Binder<'tcx, FnSig<'tcx>>),
|
||||
Trait(Binder<'tcx, Ty<'tcx>>, Option<Binder<'tcx, Ty<'tcx>>>),
|
||||
Trait(Binder<'tcx, Ty<'tcx>>, Option<Binder<'tcx, Ty<'tcx>>>, Option<DefId>),
|
||||
}
|
||||
impl<'tcx> ExprFnSig<'tcx> {
|
||||
/// Gets the argument type at the given offset. This will return `None` when the index is out of
|
||||
|
|
@ -518,7 +518,7 @@ impl<'tcx> ExprFnSig<'tcx> {
|
|||
}
|
||||
},
|
||||
Self::Closure(_, sig) => Some(sig.input(0).map_bound(|ty| ty.tuple_fields()[i])),
|
||||
Self::Trait(inputs, _) => Some(inputs.map_bound(|ty| ty.tuple_fields()[i])),
|
||||
Self::Trait(inputs, _, _) => Some(inputs.map_bound(|ty| ty.tuple_fields()[i])),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -541,7 +541,7 @@ impl<'tcx> ExprFnSig<'tcx> {
|
|||
decl.and_then(|decl| decl.inputs.get(i)),
|
||||
sig.input(0).map_bound(|ty| ty.tuple_fields()[i]),
|
||||
)),
|
||||
Self::Trait(inputs, _) => Some((None, inputs.map_bound(|ty| ty.tuple_fields()[i]))),
|
||||
Self::Trait(inputs, _, _) => Some((None, inputs.map_bound(|ty| ty.tuple_fields()[i]))),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -550,12 +550,16 @@ impl<'tcx> ExprFnSig<'tcx> {
|
|||
pub fn output(self) -> Option<Binder<'tcx, Ty<'tcx>>> {
|
||||
match self {
|
||||
Self::Sig(sig, _) | Self::Closure(_, sig) => Some(sig.output()),
|
||||
Self::Trait(_, output) => output,
|
||||
Self::Trait(_, output, _) => output,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn predicates_id(&self) -> Option<DefId> {
|
||||
if let ExprFnSig::Sig(_, id) = *self { id } else { None }
|
||||
if let ExprFnSig::Sig(_, id) | ExprFnSig::Trait(_, _, id) = *self {
|
||||
id
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -568,7 +572,8 @@ pub fn expr_sig<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option<ExprFnS
|
|||
}
|
||||
}
|
||||
|
||||
fn ty_sig<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<ExprFnSig<'tcx>> {
|
||||
/// If the type is function like, get the signature for it.
|
||||
pub fn ty_sig<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<ExprFnSig<'tcx>> {
|
||||
if ty.is_box() {
|
||||
return ty_sig(cx, ty.boxed_ty());
|
||||
}
|
||||
|
|
@ -580,7 +585,7 @@ fn ty_sig<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<ExprFnSig<'tcx>>
|
|||
Some(ExprFnSig::Closure(decl, subs.as_closure().sig()))
|
||||
},
|
||||
ty::FnDef(id, subs) => Some(ExprFnSig::Sig(cx.tcx.bound_fn_sig(id).subst(cx.tcx, subs), Some(id))),
|
||||
ty::Opaque(id, _) => ty_sig(cx, cx.tcx.type_of(id)),
|
||||
ty::Opaque(id, _) => sig_from_bounds(cx, ty, cx.tcx.item_bounds(id), cx.tcx.opt_parent(id)),
|
||||
ty::FnPtr(sig) => Some(ExprFnSig::Sig(sig, None)),
|
||||
ty::Dynamic(bounds, _) => {
|
||||
let lang_items = cx.tcx.lang_items();
|
||||
|
|
@ -594,26 +599,31 @@ fn ty_sig<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<ExprFnSig<'tcx>>
|
|||
.projection_bounds()
|
||||
.find(|p| lang_items.fn_once_output().map_or(false, |id| id == p.item_def_id()))
|
||||
.map(|p| p.map_bound(|p| p.term.ty().unwrap()));
|
||||
Some(ExprFnSig::Trait(bound.map_bound(|b| b.substs.type_at(0)), output))
|
||||
Some(ExprFnSig::Trait(bound.map_bound(|b| b.substs.type_at(0)), output, None))
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
},
|
||||
ty::Projection(proj) => match cx.tcx.try_normalize_erasing_regions(cx.param_env, ty) {
|
||||
Ok(normalized_ty) if normalized_ty != ty => ty_sig(cx, normalized_ty),
|
||||
_ => sig_for_projection(cx, proj).or_else(|| sig_from_bounds(cx, ty)),
|
||||
_ => sig_for_projection(cx, proj).or_else(|| sig_from_bounds(cx, ty, cx.param_env.caller_bounds(), None)),
|
||||
},
|
||||
ty::Param(_) => sig_from_bounds(cx, ty),
|
||||
ty::Param(_) => sig_from_bounds(cx, ty, cx.param_env.caller_bounds(), None),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn sig_from_bounds<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<ExprFnSig<'tcx>> {
|
||||
fn sig_from_bounds<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
ty: Ty<'tcx>,
|
||||
predicates: &'tcx [Predicate<'tcx>],
|
||||
predicates_id: Option<DefId>,
|
||||
) -> Option<ExprFnSig<'tcx>> {
|
||||
let mut inputs = None;
|
||||
let mut output = None;
|
||||
let lang_items = cx.tcx.lang_items();
|
||||
|
||||
for (pred, _) in all_predicates_of(cx.tcx, cx.typeck_results().hir_owner.to_def_id()) {
|
||||
for pred in predicates {
|
||||
match pred.kind().skip_binder() {
|
||||
PredicateKind::Trait(p)
|
||||
if (lang_items.fn_trait() == Some(p.def_id())
|
||||
|
|
@ -621,11 +631,12 @@ fn sig_from_bounds<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<ExprFnS
|
|||
|| lang_items.fn_once_trait() == Some(p.def_id()))
|
||||
&& p.self_ty() == ty =>
|
||||
{
|
||||
if inputs.is_some() {
|
||||
let i = pred.kind().rebind(p.trait_ref.substs.type_at(1));
|
||||
if inputs.map_or(false, |inputs| i != inputs) {
|
||||
// Multiple different fn trait impls. Is this even allowed?
|
||||
return None;
|
||||
}
|
||||
inputs = Some(pred.kind().rebind(p.trait_ref.substs.type_at(1)));
|
||||
inputs = Some(i);
|
||||
},
|
||||
PredicateKind::Projection(p)
|
||||
if Some(p.projection_ty.item_def_id) == lang_items.fn_once_output()
|
||||
|
|
@ -641,7 +652,7 @@ fn sig_from_bounds<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<ExprFnS
|
|||
}
|
||||
}
|
||||
|
||||
inputs.map(|ty| ExprFnSig::Trait(ty, output))
|
||||
inputs.map(|ty| ExprFnSig::Trait(ty, output, predicates_id))
|
||||
}
|
||||
|
||||
fn sig_for_projection<'tcx>(cx: &LateContext<'tcx>, ty: ProjectionTy<'tcx>) -> Option<ExprFnSig<'tcx>> {
|
||||
|
|
@ -661,14 +672,15 @@ fn sig_for_projection<'tcx>(cx: &LateContext<'tcx>, ty: ProjectionTy<'tcx>) -> O
|
|||
|| lang_items.fn_mut_trait() == Some(p.def_id())
|
||||
|| lang_items.fn_once_trait() == Some(p.def_id())) =>
|
||||
{
|
||||
if inputs.is_some() {
|
||||
let i = pred
|
||||
.map_bound(|pred| pred.kind().rebind(p.trait_ref.substs.type_at(1)))
|
||||
.subst(cx.tcx, ty.substs);
|
||||
|
||||
if inputs.map_or(false, |inputs| inputs != i) {
|
||||
// Multiple different fn trait impls. Is this even allowed?
|
||||
return None;
|
||||
}
|
||||
inputs = Some(
|
||||
pred.map_bound(|pred| pred.kind().rebind(p.trait_ref.substs.type_at(1)))
|
||||
.subst(cx.tcx, ty.substs),
|
||||
);
|
||||
inputs = Some(i);
|
||||
},
|
||||
PredicateKind::Projection(p) if Some(p.projection_ty.item_def_id) == lang_items.fn_once_output() => {
|
||||
if output.is_some() {
|
||||
|
|
@ -684,7 +696,7 @@ fn sig_for_projection<'tcx>(cx: &LateContext<'tcx>, ty: ProjectionTy<'tcx>) -> O
|
|||
}
|
||||
}
|
||||
|
||||
inputs.map(|ty| ExprFnSig::Trait(ty, output))
|
||||
inputs.map(|ty| ExprFnSig::Trait(ty, output, None))
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue