summaryrefslogtreecommitdiffstats
path: root/src/tools/clippy/clippy_utils
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/clippy/clippy_utils')
-rw-r--r--src/tools/clippy/clippy_utils/Cargo.toml3
-rw-r--r--src/tools/clippy/clippy_utils/src/ast_utils.rs2
-rw-r--r--src/tools/clippy/clippy_utils/src/attrs.rs4
-rw-r--r--src/tools/clippy/clippy_utils/src/check_proc_macro.rs329
-rw-r--r--src/tools/clippy/clippy_utils/src/consts.rs8
-rw-r--r--src/tools/clippy/clippy_utils/src/diagnostics.rs8
-rw-r--r--src/tools/clippy/clippy_utils/src/eager_or_lazy.rs18
-rw-r--r--src/tools/clippy/clippy_utils/src/hir_utils.rs39
-rw-r--r--src/tools/clippy/clippy_utils/src/lib.rs84
-rw-r--r--src/tools/clippy/clippy_utils/src/macros.rs676
-rw-r--r--src/tools/clippy/clippy_utils/src/msrvs.rs4
-rw-r--r--src/tools/clippy/clippy_utils/src/numeric_literal.rs10
-rw-r--r--src/tools/clippy/clippy_utils/src/paths.rs6
-rw-r--r--src/tools/clippy/clippy_utils/src/ptr.rs2
-rw-r--r--src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs18
-rw-r--r--src/tools/clippy/clippy_utils/src/source.rs18
-rw-r--r--src/tools/clippy/clippy_utils/src/sugg.rs30
-rw-r--r--src/tools/clippy/clippy_utils/src/ty.rs118
-rw-r--r--src/tools/clippy/clippy_utils/src/visitors.rs11
19 files changed, 1058 insertions, 330 deletions
diff --git a/src/tools/clippy/clippy_utils/Cargo.toml b/src/tools/clippy/clippy_utils/Cargo.toml
index bb443bdc1..c36bca065 100644
--- a/src/tools/clippy/clippy_utils/Cargo.toml
+++ b/src/tools/clippy/clippy_utils/Cargo.toml
@@ -1,12 +1,13 @@
[package]
name = "clippy_utils"
-version = "0.1.64"
+version = "0.1.65"
edition = "2021"
publish = false
[dependencies]
arrayvec = { version = "0.7", default-features = false }
if_chain = "1.0"
+itertools = "0.10.1"
rustc-semver = "1.1"
[features]
diff --git a/src/tools/clippy/clippy_utils/src/ast_utils.rs b/src/tools/clippy/clippy_utils/src/ast_utils.rs
index b22602632..e84adee9d 100644
--- a/src/tools/clippy/clippy_utils/src/ast_utils.rs
+++ b/src/tools/clippy/clippy_utils/src/ast_utils.rs
@@ -693,7 +693,7 @@ pub fn eq_attr(l: &Attribute, r: &Attribute) -> bool {
l.style == r.style
&& match (&l.kind, &r.kind) {
(DocComment(l1, l2), DocComment(r1, r2)) => l1 == r1 && l2 == r2,
- (Normal(l, _), Normal(r, _)) => eq_path(&l.path, &r.path) && eq_mac_args(&l.args, &r.args),
+ (Normal(l), Normal(r)) => eq_path(&l.item.path, &r.item.path) && eq_mac_args(&l.item.args, &r.item.args),
_ => false,
}
}
diff --git a/src/tools/clippy/clippy_utils/src/attrs.rs b/src/tools/clippy/clippy_utils/src/attrs.rs
index 186bba09d..8ab77c881 100644
--- a/src/tools/clippy/clippy_utils/src/attrs.rs
+++ b/src/tools/clippy/clippy_utils/src/attrs.rs
@@ -59,8 +59,8 @@ pub fn get_attr<'a>(
name: &'static str,
) -> impl Iterator<Item = &'a ast::Attribute> {
attrs.iter().filter(move |attr| {
- let attr = if let ast::AttrKind::Normal(ref attr, _) = attr.kind {
- attr
+ let attr = if let ast::AttrKind::Normal(ref normal) = attr.kind {
+ &normal.item
} else {
return false;
};
diff --git a/src/tools/clippy/clippy_utils/src/check_proc_macro.rs b/src/tools/clippy/clippy_utils/src/check_proc_macro.rs
new file mode 100644
index 000000000..7a8d4e806
--- /dev/null
+++ b/src/tools/clippy/clippy_utils/src/check_proc_macro.rs
@@ -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("}"))
+}
diff --git a/src/tools/clippy/clippy_utils/src/consts.rs b/src/tools/clippy/clippy_utils/src/consts.rs
index 351a3f4ae..e053708ed 100644
--- a/src/tools/clippy/clippy_utils/src/consts.rs
+++ b/src/tools/clippy/clippy_utils/src/consts.rs
@@ -45,7 +45,7 @@ pub enum Constant {
/// A reference
Ref(Box<Constant>),
/// A literal with syntax error.
- Err(Symbol),
+ Err,
}
impl PartialEq for Constant {
@@ -118,9 +118,7 @@ impl Hash for Constant {
Self::Ref(ref r) => {
r.hash(state);
},
- Self::Err(ref s) => {
- s.hash(state);
- },
+ Self::Err => {},
}
}
}
@@ -194,7 +192,7 @@ pub fn lit_to_mir_constant(lit: &LitKind, ty: Option<Ty<'_>>) -> Constant {
_ => bug!(),
},
LitKind::Bool(b) => Constant::Bool(b),
- LitKind::Err(s) => Constant::Err(s),
+ LitKind::Err => Constant::Err,
}
}
diff --git a/src/tools/clippy/clippy_utils/src/diagnostics.rs b/src/tools/clippy/clippy_utils/src/diagnostics.rs
index 7f55db3b3..ad95369b9 100644
--- a/src/tools/clippy/clippy_utils/src/diagnostics.rs
+++ b/src/tools/clippy/clippy_utils/src/diagnostics.rs
@@ -155,13 +155,7 @@ where
});
}
-pub fn span_lint_hir(
- cx: &LateContext<'_>,
- lint: &'static Lint,
- hir_id: HirId,
- sp: Span,
- msg: &str,
-) {
+pub fn span_lint_hir(cx: &LateContext<'_>, lint: &'static Lint, hir_id: HirId, sp: Span, msg: &str) {
cx.tcx.struct_span_lint_hir(lint, hir_id, sp, |diag| {
let mut diag = diag.build(msg);
docs_link(&mut diag, lint);
diff --git a/src/tools/clippy/clippy_utils/src/eager_or_lazy.rs b/src/tools/clippy/clippy_utils/src/eager_or_lazy.rs
index 730724b95..91c9c382c 100644
--- a/src/tools/clippy/clippy_utils/src/eager_or_lazy.rs
+++ b/src/tools/clippy/clippy_utils/src/eager_or_lazy.rs
@@ -45,12 +45,7 @@ impl ops::BitOrAssign for EagernessSuggestion {
}
/// Determine the eagerness of the given function call.
-fn fn_eagerness<'tcx>(
- cx: &LateContext<'tcx>,
- fn_id: DefId,
- name: Symbol,
- args: &'tcx [Expr<'_>],
-) -> EagernessSuggestion {
+fn fn_eagerness(cx: &LateContext<'_>, fn_id: DefId, name: Symbol, have_one_arg: bool) -> EagernessSuggestion {
use EagernessSuggestion::{Eager, Lazy, NoChange};
let name = name.as_str();
@@ -59,7 +54,7 @@ fn fn_eagerness<'tcx>(
None => return Lazy,
};
- if (name.starts_with("as_") || name == "len" || name == "is_empty") && args.len() == 1 {
+ if (name.starts_with("as_") || name == "len" || name == "is_empty") && have_one_arg {
if matches!(
cx.tcx.crate_name(fn_id.krate),
sym::std | sym::core | sym::alloc | sym::proc_macro
@@ -127,10 +122,11 @@ fn expr_eagerness<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> EagernessS
},
Res::Def(_, id) => match path {
QPath::Resolved(_, p) => {
- self.eagerness |= fn_eagerness(self.cx, id, p.segments.last().unwrap().ident.name, args);
+ self.eagerness |=
+ fn_eagerness(self.cx, id, p.segments.last().unwrap().ident.name, !args.is_empty());
},
QPath::TypeRelative(_, name) => {
- self.eagerness |= fn_eagerness(self.cx, id, name.ident.name, args);
+ self.eagerness |= fn_eagerness(self.cx, id, name.ident.name, !args.is_empty());
},
QPath::LangItem(..) => self.eagerness = Lazy,
},
@@ -141,12 +137,12 @@ fn expr_eagerness<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> EagernessS
self.eagerness |= NoChange;
return;
},
- ExprKind::MethodCall(name, args, _) => {
+ ExprKind::MethodCall(name, ..) => {
self.eagerness |= self
.cx
.typeck_results()
.type_dependent_def_id(e.hir_id)
- .map_or(Lazy, |id| fn_eagerness(self.cx, id, name.ident.name, args));
+ .map_or(Lazy, |id| fn_eagerness(self.cx, id, name.ident.name, true));
},
ExprKind::Index(_, e) => {
let ty = self.cx.typeck_results().expr_ty_adjusted(e);
diff --git a/src/tools/clippy/clippy_utils/src/hir_utils.rs b/src/tools/clippy/clippy_utils/src/hir_utils.rs
index 1834e2a2d..7212d9cd7 100644
--- a/src/tools/clippy/clippy_utils/src/hir_utils.rs
+++ b/src/tools/clippy/clippy_utils/src/hir_utils.rs
@@ -6,9 +6,9 @@ use rustc_data_structures::fx::FxHasher;
use rustc_hir::def::Res;
use rustc_hir::HirIdMap;
use rustc_hir::{
- ArrayLen, BinOpKind, Block, BodyId, Closure, Expr, ExprField, ExprKind, FnRetTy, GenericArg, GenericArgs, Guard,
- HirId, InlineAsmOperand, Let, Lifetime, LifetimeName, ParamName, Pat, PatField, PatKind, Path, PathSegment, QPath,
- Stmt, StmtKind, Ty, TyKind, TypeBinding,
+ ArrayLen, BinOpKind, BindingAnnotation, Block, BodyId, Closure, Expr, ExprField, ExprKind, FnRetTy, GenericArg,
+ GenericArgs, Guard, HirId, InlineAsmOperand, Let, Lifetime, LifetimeName, ParamName, Pat, PatField, PatKind, Path,
+ PathSegment, QPath, Stmt, StmtKind, Ty, TyKind, TypeBinding,
};
use rustc_lexer::{tokenize, TokenKind};
use rustc_lint::LateContext;
@@ -201,8 +201,8 @@ impl HirEqInterExpr<'_, '_, '_> {
self.inner.cx.tcx.typeck_body(right),
));
let res = self.eq_expr(
- &self.inner.cx.tcx.hir().body(left).value,
- &self.inner.cx.tcx.hir().body(right).value,
+ self.inner.cx.tcx.hir().body(left).value,
+ self.inner.cx.tcx.hir().body(right).value,
);
self.inner.maybe_typeck_results = old_maybe_typeck_results;
res
@@ -282,8 +282,14 @@ impl HirEqInterExpr<'_, '_, '_> {
&& self.eq_expr(l.body, r.body)
})
},
- (&ExprKind::MethodCall(l_path, l_args, _), &ExprKind::MethodCall(r_path, r_args, _)) => {
- self.inner.allow_side_effects && self.eq_path_segment(l_path, r_path) && self.eq_exprs(l_args, r_args)
+ (
+ &ExprKind::MethodCall(l_path, l_receiver, l_args, _),
+ &ExprKind::MethodCall(r_path, r_receiver, r_args, _),
+ ) => {
+ self.inner.allow_side_effects
+ && self.eq_path_segment(l_path, r_path)
+ && self.eq_expr(l_receiver, r_receiver)
+ && self.eq_exprs(l_args, r_args)
},
(&ExprKind::Repeat(le, ll), &ExprKind::Repeat(re, rl)) => {
self.eq_expr(le, re) && self.eq_array_length(ll, rl)
@@ -643,7 +649,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
}) => {
std::mem::discriminant(&capture_clause).hash(&mut self.s);
// closures inherit TypeckResults
- self.hash_expr(&self.cx.tcx.hir().body(body).value);
+ self.hash_expr(self.cx.tcx.hir().body(body).value);
},
ExprKind::Field(e, ref f) => {
self.hash_expr(e);
@@ -743,8 +749,9 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
s.hash(&mut self.s);
},
- ExprKind::MethodCall(path, args, ref _fn_span) => {
+ ExprKind::MethodCall(path, receiver, args, ref _fn_span) => {
self.hash_name(path.ident.name);
+ self.hash_expr(receiver);
self.hash_exprs(args);
},
ExprKind::ConstBlock(ref l_id) => {
@@ -815,8 +822,9 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
pub fn hash_pat(&mut self, pat: &Pat<'_>) {
std::mem::discriminant(&pat.kind).hash(&mut self.s);
match pat.kind {
- PatKind::Binding(ann, _, _, pat) => {
- std::mem::discriminant(&ann).hash(&mut self.s);
+ PatKind::Binding(BindingAnnotation(by_ref, mutability), _, _, pat) => {
+ std::mem::discriminant(&by_ref).hash(&mut self.s);
+ std::mem::discriminant(&mutability).hash(&mut self.s);
if let Some(pat) = pat {
self.hash_pat(pat);
}
@@ -921,7 +929,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
}
}
- pub fn hash_lifetime(&mut self, lifetime: Lifetime) {
+ pub fn hash_lifetime(&mut self, lifetime: &Lifetime) {
std::mem::discriminant(&lifetime.name).hash(&mut self.s);
if let LifetimeName::Param(param_id, ref name) = lifetime.name {
std::mem::discriminant(name).hash(&mut self.s);
@@ -979,8 +987,9 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
}
},
TyKind::Path(ref qpath) => self.hash_qpath(qpath),
- TyKind::OpaqueDef(_, arg_list) => {
+ TyKind::OpaqueDef(_, arg_list, in_trait) => {
self.hash_generic_args(arg_list);
+ in_trait.hash(&mut self.s);
},
TyKind::TraitObject(_, lifetime, _) => {
self.hash_lifetime(*lifetime);
@@ -1002,7 +1011,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
pub fn hash_body(&mut self, body_id: BodyId) {
// swap out TypeckResults when hashing a body
let old_maybe_typeck_results = self.maybe_typeck_results.replace(self.cx.tcx.typeck_body(body_id));
- self.hash_expr(&self.cx.tcx.hir().body(body_id).value);
+ self.hash_expr(self.cx.tcx.hir().body(body_id).value);
self.maybe_typeck_results = old_maybe_typeck_results;
}
@@ -1010,7 +1019,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
for arg in arg_list {
match *arg {
GenericArg::Lifetime(l) => self.hash_lifetime(l),
- GenericArg::Type(ref ty) => self.hash_ty(ty),
+ GenericArg::Type(ty) => self.hash_ty(ty),
GenericArg::Const(ref ca) => self.hash_body(ca.value.body),
GenericArg::Infer(ref inf) => self.hash_ty(&inf.to_ty()),
}
diff --git a/src/tools/clippy/clippy_utils/src/lib.rs b/src/tools/clippy/clippy_utils/src/lib.rs
index 8322df862..62da850a1 100644
--- a/src/tools/clippy/clippy_utils/src/lib.rs
+++ b/src/tools/clippy/clippy_utils/src/lib.rs
@@ -1,9 +1,9 @@
#![feature(array_chunks)]
#![feature(box_patterns)]
#![feature(control_flow_enum)]
-#![feature(let_else)]
#![feature(let_chains)]
#![feature(lint_reasons)]
+#![cfg_attr(bootstrap, feature(let_else))]
#![feature(once_cell)]
#![feature(rustc_private)]
#![recursion_limit = "512"]
@@ -28,6 +28,7 @@ extern crate rustc_infer;
extern crate rustc_lexer;
extern crate rustc_lint;
extern crate rustc_middle;
+extern crate rustc_parse_format;
extern crate rustc_session;
extern crate rustc_span;
extern crate rustc_target;
@@ -39,6 +40,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 +61,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,
};
@@ -85,6 +88,7 @@ use rustc_hir::{
Mutability, Node, Param, Pat, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, TraitItem, TraitItemKind,
TraitRef, TyKind, UnOp,
};
+use rustc_lexer::{tokenize, TokenKind};
use rustc_lint::{LateContext, Level, Lint, LintContext};
use rustc_middle::hir::place::PlaceBase;
use rustc_middle::ty as rustc_ty;
@@ -102,6 +106,7 @@ use rustc_semver::RustcVersion;
use rustc_session::Session;
use rustc_span::hygiene::{ExpnKind, MacroKind};
use rustc_span::source_map::original_sp;
+use rustc_span::source_map::SourceMap;
use rustc_span::sym;
use rustc_span::symbol::{kw, Symbol};
use rustc_span::{Span, DUMMY_SP};
@@ -187,7 +192,7 @@ pub fn find_binding_init<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<
let hir = cx.tcx.hir();
if_chain! {
if let Some(Node::Pat(pat)) = hir.find(hir_id);
- if matches!(pat.kind, PatKind::Binding(BindingAnnotation::Unannotated, ..));
+ if matches!(pat.kind, PatKind::Binding(BindingAnnotation::NONE, ..));
let parent = hir.get_parent_node(hir_id);
if let Some(Node::Local(local)) = hir.find(parent);
then {
@@ -332,7 +337,7 @@ pub fn qpath_generic_tys<'tcx>(qpath: &QPath<'tcx>) -> impl Iterator<Item = &'tc
.map_or(&[][..], |a| a.args)
.iter()
.filter_map(|a| match a {
- hir::GenericArg::Type(ty) => Some(ty),
+ hir::GenericArg::Type(ty) => Some(*ty),
_ => None,
})
}
@@ -370,15 +375,19 @@ pub fn match_qpath(path: &QPath<'_>, segments: &[&str]) -> bool {
/// If the expression is a path, resolves it to a `DefId` and checks if it matches the given path.
///
-/// Please use `is_expr_diagnostic_item` if the target is a diagnostic item.
+/// Please use `is_path_diagnostic_item` if the target is a diagnostic item.
pub fn is_expr_path_def_path(cx: &LateContext<'_>, expr: &Expr<'_>, segments: &[&str]) -> bool {
path_def_id(cx, expr).map_or(false, |id| match_def_path(cx, id, segments))
}
-/// If the expression is a path, resolves it to a `DefId` and checks if it matches the given
-/// diagnostic item.
-pub fn is_expr_diagnostic_item(cx: &LateContext<'_>, expr: &Expr<'_>, diag_item: Symbol) -> bool {
- path_def_id(cx, expr).map_or(false, |id| cx.tcx.is_diagnostic_item(diag_item, id))
+/// If `maybe_path` is a path node which resolves to an item, resolves it to a `DefId` and checks if
+/// it matches the given diagnostic item.
+pub fn is_path_diagnostic_item<'tcx>(
+ cx: &LateContext<'_>,
+ maybe_path: &impl MaybePath<'tcx>,
+ diag_item: Symbol,
+) -> bool {
+ path_def_id(cx, maybe_path).map_or(false, |id| cx.tcx.is_diagnostic_item(diag_item, id))
}
/// THIS METHOD IS DEPRECATED and will eventually be removed since it does not match against the
@@ -1022,26 +1031,26 @@ pub fn can_move_expr_to_closure<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'
v.allow_closure.then_some(v.captures)
}
+/// Arguments of a method: the receiver and all the additional arguments.
+pub type MethodArguments<'tcx> = Vec<(&'tcx Expr<'tcx>, &'tcx [Expr<'tcx>])>;
+
/// Returns the method names and argument list of nested method call expressions that make up
/// `expr`. method/span lists are sorted with the most recent call first.
-pub fn method_calls<'tcx>(
- expr: &'tcx Expr<'tcx>,
- max_depth: usize,
-) -> (Vec<Symbol>, Vec<&'tcx [Expr<'tcx>]>, Vec<Span>) {
+pub fn method_calls<'tcx>(expr: &'tcx Expr<'tcx>, max_depth: usize) -> (Vec<Symbol>, MethodArguments<'tcx>, Vec<Span>) {
let mut method_names = Vec::with_capacity(max_depth);
let mut arg_lists = Vec::with_capacity(max_depth);
let mut spans = Vec::with_capacity(max_depth);
let mut current = expr;
for _ in 0..max_depth {
- if let ExprKind::MethodCall(path, args, _) = &current.kind {
- if args.iter().any(|e| e.span.from_expansion()) {
+ if let ExprKind::MethodCall(path, receiver, args, _) = &current.kind {
+ if receiver.span.from_expansion() || args.iter().any(|e| e.span.from_expansion()) {
break;
}
method_names.push(path.ident.name);
- arg_lists.push(&**args);
+ arg_lists.push((*receiver, &**args));
spans.push(path.ident.span);
- current = &args[0];
+ current = receiver;
} else {
break;
}
@@ -1056,18 +1065,18 @@ pub fn method_calls<'tcx>(
/// `method_chain_args(expr, &["bar", "baz"])` will return a `Vec`
/// containing the `Expr`s for
/// `.bar()` and `.baz()`
-pub fn method_chain_args<'a>(expr: &'a Expr<'_>, methods: &[&str]) -> Option<Vec<&'a [Expr<'a>]>> {
+pub fn method_chain_args<'a>(expr: &'a Expr<'_>, methods: &[&str]) -> Option<Vec<(&'a Expr<'a>, &'a [Expr<'a>])>> {
let mut current = expr;
let mut matched = Vec::with_capacity(methods.len());
for method_name in methods.iter().rev() {
// method chains are stored last -> first
- if let ExprKind::MethodCall(path, args, _) = current.kind {
+ if let ExprKind::MethodCall(path, receiver, args, _) = current.kind {
if path.ident.name.as_str() == *method_name {
- if args.iter().any(|e| e.span.from_expansion()) {
+ if receiver.span.from_expansion() || args.iter().any(|e| e.span.from_expansion()) {
return None;
}
- matched.push(args); // build up `matched` backwards
- current = &args[0]; // go to parent expression
+ matched.push((receiver, args)); // build up `matched` backwards
+ current = receiver; // go to parent expression
} else {
return None;
}
@@ -1112,7 +1121,7 @@ pub struct ContainsName {
}
impl<'tcx> Visitor<'tcx> for ContainsName {
- fn visit_name(&mut self, _: Span, name: Symbol) {
+ fn visit_name(&mut self, name: Symbol) {
if self.name == name {
self.result = true;
}
@@ -1230,8 +1239,10 @@ pub fn get_enclosing_loop_or_multi_call_closure<'tcx>(
ty_is_fn_once_param(cx.tcx, ty.skip_binder(), predicates).then_some(())
})
},
- ExprKind::MethodCall(_, args, _) => {
- let i = args.iter().position(|arg| arg.hir_id == id)?;
+ ExprKind::MethodCall(_, receiver, args, _) => {
+ let i = std::iter::once(receiver)
+ .chain(args.iter())
+ .position(|arg| arg.hir_id == id)?;
let id = cx.typeck_results().type_dependent_def_id(e.hir_id)?;
let ty = cx.tcx.fn_sig(id).skip_binder().inputs()[i];
ty_is_fn_once_param(cx.tcx, ty, cx.tcx.param_env(id).caller_bounds()).then_some(())
@@ -1541,7 +1552,8 @@ pub fn iter_input_pats<'tcx>(decl: &FnDecl<'_>, body: &'tcx Body<'_>) -> impl It
pub fn is_try<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
fn is_ok(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool {
if_chain! {
- if let PatKind::TupleStruct(ref path, pat, None) = arm.pat.kind;
+ if let PatKind::TupleStruct(ref path, pat, ddpos) = arm.pat.kind;
+ if ddpos.as_opt_usize().is_none();
if is_lang_ctor(cx, path, ResultOk);
if let PatKind::Binding(_, hir_id, _, None) = pat[0].kind;
if path_to_local_id(arm.body, hir_id);
@@ -1803,7 +1815,7 @@ pub fn is_expr_identity_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool
}
};
- let mut expr = &func.value;
+ let mut expr = func.value;
loop {
match expr.kind {
#[rustfmt::skip]
@@ -1892,8 +1904,8 @@ pub fn std_or_core(cx: &LateContext<'_>) -> Option<&'static str> {
pub fn is_no_std_crate(cx: &LateContext<'_>) -> bool {
cx.tcx.hir().attrs(hir::CRATE_HIR_ID).iter().any(|attr| {
- if let ast::AttrKind::Normal(ref attr, _) = attr.kind {
- attr.path == sym::no_std
+ if let ast::AttrKind::Normal(ref normal) = attr.kind {
+ normal.item.path == sym::no_std
} else {
false
}
@@ -1902,8 +1914,8 @@ pub fn is_no_std_crate(cx: &LateContext<'_>) -> bool {
pub fn is_no_core_crate(cx: &LateContext<'_>) -> bool {
cx.tcx.hir().attrs(hir::CRATE_HIR_ID).iter().any(|attr| {
- if let ast::AttrKind::Normal(ref attr, _) = attr.kind {
- attr.path == sym::no_core
+ if let ast::AttrKind::Normal(ref normal) = attr.kind {
+ normal.item.path == sym::no_core
} else {
false
}
@@ -2272,6 +2284,18 @@ pub fn walk_to_expr_usage<'tcx, T>(
None
}
+/// Checks whether a given span has any comment token
+/// This checks for all types of comment: line "//", block "/**", doc "///" "//!"
+pub fn span_contains_comment(sm: &SourceMap, span: Span) -> bool {
+ let Ok(snippet) = sm.span_to_snippet(span) else { return false };
+ return tokenize(&snippet).any(|token| {
+ matches!(
+ token.kind,
+ TokenKind::BlockComment { .. } | TokenKind::LineComment { .. }
+ )
+ });
+}
+
macro_rules! op_utils {
($($name:ident $assign:ident)*) => {
/// Binary operation traits like `LangItem::Add`
diff --git a/src/tools/clippy/clippy_utils/src/macros.rs b/src/tools/clippy/clippy_utils/src/macros.rs
index a268e339b..bd89ff977 100644
--- a/src/tools/clippy/clippy_utils/src/macros.rs
+++ b/src/tools/clippy/clippy_utils/src/macros.rs
@@ -1,16 +1,21 @@
#![allow(clippy::similar_names)] // `expr` and `expn`
+use crate::is_path_diagnostic_item;
+use crate::source::snippet_opt;
use crate::visitors::expr_visitor_no_bodies;
use arrayvec::ArrayVec;
-use if_chain::if_chain;
+use itertools::{izip, Either, Itertools};
use rustc_ast::ast::LitKind;
use rustc_hir::intravisit::Visitor;
use rustc_hir::{self as hir, Expr, ExprKind, HirId, Node, QPath};
+use rustc_lexer::unescape::unescape_literal;
+use rustc_lexer::{tokenize, unescape, LiteralKind, TokenKind};
use rustc_lint::LateContext;
+use rustc_parse_format::{self as rpf, Alignment};
use rustc_span::def_id::DefId;
use rustc_span::hygiene::{self, MacroKind, SyntaxContext};
-use rustc_span::{sym, ExpnData, ExpnId, ExpnKind, Span, Symbol};
+use rustc_span::{sym, BytePos, ExpnData, ExpnId, ExpnKind, Pos, Span, SpanData, Symbol};
use std::ops::ControlFlow;
const FORMAT_MACRO_DIAG_ITEMS: &[Symbol] = &[
@@ -332,121 +337,497 @@ fn is_assert_arg(cx: &LateContext<'_>, expr: &Expr<'_>, assert_expn: ExpnId) ->
}
}
-/// A parsed `format_args!` expansion
+/// The format string doesn't exist in the HIR, so we reassemble it from source code
#[derive(Debug)]
-pub struct FormatArgsExpn<'tcx> {
- /// Span of the first argument, the format string
- pub format_string_span: Span,
- /// The format string split by formatted args like `{..}`
- pub format_string_parts: Vec<Symbol>,
- /// Values passed after the format string
- pub value_args: Vec<&'tcx Expr<'tcx>>,
- /// Each element is a `value_args` index and a formatting trait (e.g. `sym::Debug`)
- pub formatters: Vec<(usize, Symbol)>,
- /// List of `fmt::v1::Argument { .. }` expressions. If this is empty,
- /// then `formatters` represents the format args (`{..}`).
- /// If this is non-empty, it represents the format args, and the `position`
- /// parameters within the struct expressions are indexes of `formatters`.
- pub specs: Vec<&'tcx Expr<'tcx>>,
+pub struct FormatString {
+ /// Span of the whole format string literal, including `[r#]"`.
+ pub span: Span,
+ /// Snippet of the whole format string literal, including `[r#]"`.
+ pub snippet: String,
+ /// If the string is raw `r"..."`/`r#""#`, how many `#`s does it have on each side.
+ pub style: Option<usize>,
+ /// The unescaped value of the format string, e.g. `"val – {}"` for the literal
+ /// `"val \u{2013} {}"`.
+ pub unescaped: String,
+ /// The format string split by format args like `{..}`.
+ pub parts: Vec<Symbol>,
}
-impl<'tcx> FormatArgsExpn<'tcx> {
- /// Parses an expanded `format_args!` or `format_args_nl!` invocation
- pub fn parse(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<Self> {
- macro_backtrace(expr.span).find(|macro_call| {
- matches!(
- cx.tcx.item_name(macro_call.def_id),
- sym::const_format_args | sym::format_args | sym::format_args_nl
- )
- })?;
- let mut format_string_span: Option<Span> = None;
- let mut format_string_parts: Vec<Symbol> = Vec::new();
- let mut value_args: Vec<&Expr<'_>> = Vec::new();
- let mut formatters: Vec<(usize, Symbol)> = Vec::new();
- let mut specs: Vec<&Expr<'_>> = Vec::new();
- expr_visitor_no_bodies(|e| {
- // if we're still inside of the macro definition...
- if e.span.ctxt() == expr.span.ctxt() {
- // ArgumentV1::new_<format_trait>(<value>)
- if_chain! {
- if let ExprKind::Call(callee, [val]) = e.kind;
- if let ExprKind::Path(QPath::TypeRelative(ty, seg)) = callee.kind;
- if let hir::TyKind::Path(QPath::Resolved(_, path)) = ty.kind;
- if path.segments.last().unwrap().ident.name == sym::ArgumentV1;
- if seg.ident.name.as_str().starts_with("new_");
- then {
- let val_idx = if_chain! {
- if val.span.ctxt() == expr.span.ctxt();
- if let ExprKind::Field(_, field) = val.kind;
- if let Ok(idx) = field.name.as_str().parse();
- then {
- // tuple index
- idx
- } else {
- // assume the value expression is passed directly
- formatters.len()
- }
- };
- let fmt_trait = match seg.ident.name.as_str() {
- "new_display" => "Display",
- "new_debug" => "Debug",
- "new_lower_exp" => "LowerExp",
- "new_upper_exp" => "UpperExp",
- "new_octal" => "Octal",
- "new_pointer" => "Pointer",
- "new_binary" => "Binary",
- "new_lower_hex" => "LowerHex",
- "new_upper_hex" => "UpperHex",
- _ => unreachable!(),
- };
- formatters.push((val_idx, Symbol::intern(fmt_trait)));
- }
- }
- if let ExprKind::Struct(QPath::Resolved(_, path), ..) = e.kind {
- if path.segments.last().unwrap().ident.name == sym::Argument {
- specs.push(e);
- }
+impl FormatString {
+ fn new(cx: &LateContext<'_>, pieces: &Expr<'_>) -> Option<Self> {
+ // format_args!(r"a {} b \", 1);
+ //
+ // expands to
+ //
+ // ::core::fmt::Arguments::new_v1(&["a ", " b \\"],
+ // &[::core::fmt::ArgumentV1::new_display(&1)]);
+ //
+ // where `pieces` is the expression `&["a ", " b \\"]`. It has the span of `r"a {} b \"`
+ let span = pieces.span;
+ let snippet = snippet_opt(cx, span)?;
+
+ let (inner, style) = match tokenize(&snippet).next()?.kind {
+ TokenKind::Literal { kind, .. } => {
+ let style = match kind {
+ LiteralKind::Str { .. } => None,
+ LiteralKind::RawStr { n_hashes: Some(n), .. } => Some(n.into()),
+ _ => return None,
+ };
+
+ let start = style.map_or(1, |n| 2 + n);
+ let end = snippet.len() - style.map_or(1, |n| 1 + n);
+
+ (&snippet[start..end], style)
+ },
+ _ => return None,
+ };
+
+ let mode = if style.is_some() {
+ unescape::Mode::RawStr
+ } else {
+ unescape::Mode::Str
+ };
+
+ let mut unescaped = String::with_capacity(inner.len());
+ unescape_literal(inner, mode, &mut |_, ch| match ch {
+ Ok(ch) => unescaped.push(ch),
+ Err(e) if !e.is_fatal() => (),
+ Err(e) => panic!("{:?}", e),
+ });
+
+ let mut parts = Vec::new();
+ expr_visitor_no_bodies(|expr| {
+ if let ExprKind::Lit(lit) = &expr.kind {
+ if let LitKind::Str(symbol, _) = lit.node {
+ parts.push(symbol);
}
- // walk through the macro expansion
- return true;
}
- // assume that the first expr with a differing context represents
- // (and has the span of) the format string
- if format_string_span.is_none() {
- format_string_span = Some(e.span);
- let span = e.span;
- // walk the expr and collect string literals which are format string parts
- expr_visitor_no_bodies(|e| {
- if e.span.ctxt() != span.ctxt() {
- // defensive check, probably doesn't happen
- return false;
- }
- if let ExprKind::Lit(lit) = &e.kind {
- if let LitKind::Str(symbol, _s) = lit.node {
- format_string_parts.push(symbol);
- }
- }
- true
- })
- .visit_expr(e);
+
+ true
+ })
+ .visit_expr(pieces);
+
+ Some(Self {
+ span,
+ snippet,
+ style,
+ unescaped,
+ parts,
+ })
+ }
+}
+
+struct FormatArgsValues<'tcx> {
+ /// See `FormatArgsExpn::value_args`
+ value_args: Vec<&'tcx Expr<'tcx>>,
+ /// Maps an `rt::v1::Argument::position` or an `rt::v1::Count::Param` to its index in
+ /// `value_args`
+ pos_to_value_index: Vec<usize>,
+ /// Used to check if a value is declared inline & to resolve `InnerSpan`s.
+ format_string_span: SpanData,
+}
+
+impl<'tcx> FormatArgsValues<'tcx> {
+ fn new(args: &'tcx Expr<'tcx>, format_string_span: SpanData) -> Self {
+ let mut pos_to_value_index = Vec::new();
+ let mut value_args = Vec::new();
+ expr_visitor_no_bodies(|expr| {
+ if expr.span.ctxt() == args.span.ctxt() {
+ // ArgumentV1::new_<format_trait>(<val>)
+ // ArgumentV1::from_usize(<val>)
+ if let ExprKind::Call(callee, [val]) = expr.kind
+ && let ExprKind::Path(QPath::TypeRelative(ty, _)) = callee.kind
+ && let hir::TyKind::Path(QPath::Resolved(_, path)) = ty.kind
+ && path.segments.last().unwrap().ident.name == sym::ArgumentV1
+ {
+ let val_idx = if val.span.ctxt() == expr.span.ctxt()
+ && let ExprKind::Field(_, field) = val.kind
+ && let Ok(idx) = field.name.as_str().parse()
+ {
+ // tuple index
+ idx
+ } else {
+ // assume the value expression is passed directly
+ pos_to_value_index.len()
+ };
+
+ pos_to_value_index.push(val_idx);
+ }
+
+ true
} else {
- // assume that any further exprs with a differing context are value args
- value_args.push(e);
+ // assume that any expr with a differing span is a value
+ value_args.push(expr);
+
+ false
}
- // don't walk anything not from the macro expansion (e.a. inputs)
- false
})
- .visit_expr(expr);
- Some(FormatArgsExpn {
- format_string_span: format_string_span?,
- format_string_parts,
+ .visit_expr(args);
+
+ Self {
value_args,
- formatters,
- specs,
+ pos_to_value_index,
+ format_string_span,
+ }
+ }
+}
+
+/// The positions of a format argument's value, precision and width
+///
+/// A position is an index into the second argument of `Arguments::new_v1[_formatted]`
+#[derive(Debug, Default, Copy, Clone)]
+struct ParamPosition {
+ /// The position stored in `rt::v1::Argument::position`.
+ value: usize,
+ /// The position stored in `rt::v1::FormatSpec::width` if it is a `Count::Param`.
+ width: Option<usize>,
+ /// The position stored in `rt::v1::FormatSpec::precision` if it is a `Count::Param`.
+ precision: Option<usize>,
+}
+
+/// Parses the `fmt` arg of `Arguments::new_v1_formatted(pieces, args, fmt, _)`
+fn parse_rt_fmt<'tcx>(fmt_arg: &'tcx Expr<'tcx>) -> Option<impl Iterator<Item = ParamPosition> + 'tcx> {
+ fn parse_count(expr: &Expr<'_>) -> Option<usize> {
+ // ::core::fmt::rt::v1::Count::Param(1usize),
+ if let ExprKind::Call(ctor, [val]) = expr.kind
+ && let ExprKind::Path(QPath::Resolved(_, path)) = ctor.kind
+ && path.segments.last()?.ident.name == sym::Param
+ && let ExprKind::Lit(lit) = &val.kind
+ && let LitKind::Int(pos, _) = lit.node
+ {
+ Some(pos as usize)
+ } else {
+ None
+ }
+ }
+
+ if let ExprKind::AddrOf(.., array) = fmt_arg.kind
+ && let ExprKind::Array(specs) = array.kind
+ {
+ Some(specs.iter().map(|spec| {
+ let mut position = ParamPosition::default();
+
+ // ::core::fmt::rt::v1::Argument {
+ // position: 0usize,
+ // format: ::core::fmt::rt::v1::FormatSpec {
+ // ..
+ // precision: ::core::fmt::rt::v1::Count::Implied,
+ // width: ::core::fmt::rt::v1::Count::Implied,
+ // },
+ // }
+
+ // TODO: this can be made much nicer next sync with `Visitor::visit_expr_field`
+ if let ExprKind::Struct(_, fields, _) = spec.kind {
+ for field in fields {
+ match (field.ident.name, &field.expr.kind) {
+ (sym::position, ExprKind::Lit(lit)) => {
+ if let LitKind::Int(pos, _) = lit.node {
+ position.value = pos as usize;
+ }
+ },
+ (sym::format, &ExprKind::Struct(_, spec_fields, _)) => {
+ for spec_field in spec_fields {
+ match spec_field.ident.name {
+ sym::precision => {
+ position.precision = parse_count(spec_field.expr);
+ },
+ sym::width => {
+ position.width = parse_count(spec_field.expr);
+ },
+ _ => {},
+ }
+ }
+ },
+ _ => {},
+ }
+ }
+ }
+
+ position
+ }))
+ } else {
+ None
+ }
+}
+
+/// `Span::from_inner`, but for `rustc_parse_format`'s `InnerSpan`
+fn span_from_inner(base: SpanData, inner: rpf::InnerSpan) -> Span {
+ Span::new(
+ base.lo + BytePos::from_usize(inner.start),
+ base.lo + BytePos::from_usize(inner.end),
+ base.ctxt,
+ base.parent,
+ )
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub enum FormatParamKind {
+ /// An implicit parameter , such as `{}` or `{:?}`.
+ Implicit,
+ /// A parameter with an explicit number, or an asterisk precision. e.g. `{1}`, `{0:?}`,
+ /// `{:.0$}` or `{:.*}`.
+ Numbered,
+ /// A named parameter with a named `value_arg`, such as the `x` in `format!("{x}", x = 1)`.
+ Named(Symbol),
+ /// An implicit named parameter, such as the `y` in `format!("{y}")`.
+ NamedInline(Symbol),
+}
+
+/// A `FormatParam` is any place in a `FormatArgument` that refers to a supplied value, e.g.
+///
+/// ```
+/// let precision = 2;
+/// format!("{:.precision$}", 0.1234);
+/// ```
+///
+/// has two `FormatParam`s, a [`FormatParamKind::Implicit`] `.kind` with a `.value` of `0.1234`
+/// and a [`FormatParamKind::NamedInline("precision")`] `.kind` with a `.value` of `2`
+#[derive(Debug, Copy, Clone)]
+pub struct FormatParam<'tcx> {
+ /// The expression this parameter refers to.
+ pub value: &'tcx Expr<'tcx>,
+ /// How this parameter refers to its `value`.
+ pub kind: FormatParamKind,
+ /// Span of the parameter, may be zero width. Includes the whitespace of implicit parameters.
+ ///
+ /// ```text
+ /// format!("{}, { }, {0}, {name}", ...);
+ /// ^ ~~ ~ ~~~~
+ /// ```
+ pub span: Span,
+}
+
+impl<'tcx> FormatParam<'tcx> {
+ fn new(
+ mut kind: FormatParamKind,
+ position: usize,
+ inner: rpf::InnerSpan,
+ values: &FormatArgsValues<'tcx>,
+ ) -> Option<Self> {
+ let value_index = *values.pos_to_value_index.get(position)?;
+ let value = *values.value_args.get(value_index)?;
+ let span = span_from_inner(values.format_string_span, inner);
+
+ // if a param is declared inline, e.g. `format!("{x}")`, the generated expr's span points
+ // into the format string
+ if let FormatParamKind::Named(name) = kind && values.format_string_span.contains(value.span.data()) {
+ kind = FormatParamKind::NamedInline(name);
+ }
+
+ Some(Self { value, kind, span })
+ }
+}
+
+/// Used by [width](https://doc.rust-lang.org/std/fmt/#width) and
+/// [precision](https://doc.rust-lang.org/std/fmt/#precision) specifiers.
+#[derive(Debug, Copy, Clone)]
+pub enum Count<'tcx> {
+ /// Specified with a literal number, stores the value.
+ Is(usize, Span),
+ /// Specified using `$` and `*` syntaxes. The `*` format is still considered to be
+ /// `FormatParamKind::Numbered`.
+ Param(FormatParam<'tcx>),
+ /// Not specified.
+ Implied,
+}
+
+impl<'tcx> Count<'tcx> {
+ fn new(
+ count: rpf::Count<'_>,
+ position: Option<usize>,
+ inner: Option<rpf::InnerSpan>,
+ values: &FormatArgsValues<'tcx>,
+ ) -> Option<Self> {
+ Some(match count {
+ rpf::Count::CountIs(val) => Self::Is(val, span_from_inner(values.format_string_span, inner?)),
+ rpf::Count::CountIsName(name, span) => Self::Param(FormatParam::new(
+ FormatParamKind::Named(Symbol::intern(name)),
+ position?,
+ span,
+ values,
+ )?),
+ rpf::Count::CountIsParam(_) | rpf::Count::CountIsStar(_) => {
+ Self::Param(FormatParam::new(FormatParamKind::Numbered, position?, inner?, values)?)
+ },
+ rpf::Count::CountImplied => Self::Implied,
+ })
+ }
+
+ pub fn is_implied(self) -> bool {
+ matches!(self, Count::Implied)
+ }
+
+ pub fn param(self) -> Option<FormatParam<'tcx>> {
+ match self {
+ Count::Param(param) => Some(param),
+ _ => None,
+ }
+ }
+}
+
+/// Specification for the formatting of an argument in the format string. See
+/// <https://doc.rust-lang.org/std/fmt/index.html#formatting-parameters> for the precise meanings.
+#[derive(Debug)]
+pub struct FormatSpec<'tcx> {
+ /// Optionally specified character to fill alignment with.
+ pub fill: Option<char>,
+ /// Optionally specified alignment.
+ pub align: Alignment,
+ /// Packed version of various flags provided, see [`rustc_parse_format::Flag`].
+ pub flags: u32,
+ /// Represents either the maximum width or the integer precision.
+ pub precision: Count<'tcx>,
+ /// The minimum width, will be padded according to `width`/`align`
+ pub width: Count<'tcx>,
+ /// The formatting trait used by the argument, e.g. `sym::Display` for `{}`, `sym::Debug` for
+ /// `{:?}`.
+ pub r#trait: Symbol,
+ pub trait_span: Option<Span>,
+}
+
+impl<'tcx> FormatSpec<'tcx> {
+ fn new(spec: rpf::FormatSpec<'_>, positions: ParamPosition, values: &FormatArgsValues<'tcx>) -> Option<Self> {
+ Some(Self {
+ fill: spec.fill,
+ align: spec.align,
+ flags: spec.flags,
+ precision: Count::new(spec.precision, positions.precision, spec.precision_span, values)?,
+ width: Count::new(spec.width, positions.width, spec.width_span, values)?,
+ r#trait: match spec.ty {
+ "" => sym::Display,
+ "?" => sym::Debug,
+ "o" => sym!(Octal),
+ "x" => sym!(LowerHex),
+ "X" => sym!(UpperHex),
+ "p" => sym::Pointer,
+ "b" => sym!(Binary),
+ "e" => sym!(LowerExp),
+ "E" => sym!(UpperExp),
+ _ => return None,
+ },
+ trait_span: spec
+ .ty_span
+ .map(|span| span_from_inner(values.format_string_span, span)),
})
}
- /// Finds a nested call to `format_args!` within a `format!`-like macro call
+ /// Returns true if this format spec would change the contents of a string when formatted
+ pub fn has_string_formatting(&self) -> bool {
+ self.r#trait != sym::Display || !self.width.is_implied() || !self.precision.is_implied()
+ }
+}
+
+/// A format argument, such as `{}`, `{foo:?}`.
+#[derive(Debug)]
+pub struct FormatArg<'tcx> {
+ /// The parameter the argument refers to.
+ pub param: FormatParam<'tcx>,
+ /// How to format `param`.
+ pub format: FormatSpec<'tcx>,
+ /// span of the whole argument, `{..}`.
+ pub span: Span,
+}
+
+/// A parsed `format_args!` expansion.
+#[derive(Debug)]
+pub struct FormatArgsExpn<'tcx> {
+ /// The format string literal.
+ pub format_string: FormatString,
+ // The format arguments, such as `{:?}`.
+ pub args: Vec<FormatArg<'tcx>>,
+ /// Has an added newline due to `println!()`/`writeln!()`/etc. The last format string part will
+ /// include this added newline.
+ pub newline: bool,
+ /// Values passed after the format string and implicit captures. `[1, z + 2, x]` for
+ /// `format!("{x} {} {y}", 1, z + 2)`.
+ value_args: Vec<&'tcx Expr<'tcx>>,
+}
+
+impl<'tcx> FormatArgsExpn<'tcx> {
+ pub fn parse(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<Self> {
+ let macro_name = macro_backtrace(expr.span)
+ .map(|macro_call| cx.tcx.item_name(macro_call.def_id))
+ .find(|&name| matches!(name, sym::const_format_args | sym::format_args | sym::format_args_nl))?;
+ let newline = macro_name == sym::format_args_nl;
+
+ // ::core::fmt::Arguments::new_v1(pieces, args)
+ // ::core::fmt::Arguments::new_v1_formatted(pieces, args, fmt, _unsafe_arg)
+ if let ExprKind::Call(callee, [pieces, args, rest @ ..]) = expr.kind
+ && let ExprKind::Path(QPath::TypeRelative(ty, seg)) = callee.kind
+ && is_path_diagnostic_item(cx, ty, sym::Arguments)
+ && matches!(seg.ident.as_str(), "new_v1" | "new_v1_formatted")
+ {
+ let format_string = FormatString::new(cx, pieces)?;
+
+ let mut parser = rpf::Parser::new(
+ &format_string.unescaped,
+ format_string.style,
+ Some(format_string.snippet.clone()),
+ // `format_string.unescaped` does not contain the appended newline
+ false,
+ rpf::ParseMode::Format,
+ );
+
+ let parsed_args = parser
+ .by_ref()
+ .filter_map(|piece| match piece {
+ rpf::Piece::NextArgument(a) => Some(a),
+ rpf::Piece::String(_) => None,
+ })
+ .collect_vec();
+ if !parser.errors.is_empty() {
+ return None;
+ }
+
+ let positions = if let Some(fmt_arg) = rest.first() {
+ // If the argument contains format specs, `new_v1_formatted(_, _, fmt, _)`, parse
+ // them.
+
+ Either::Left(parse_rt_fmt(fmt_arg)?)
+ } else {
+ // If no format specs are given, the positions are in the given order and there are
+ // no `precision`/`width`s to consider.
+
+ Either::Right((0..).map(|n| ParamPosition {
+ value: n,
+ width: None,
+ precision: None,
+ }))
+ };
+
+ let values = FormatArgsValues::new(args, format_string.span.data());
+
+ let args = izip!(positions, parsed_args, parser.arg_places)
+ .map(|(position, parsed_arg, arg_span)| {
+ Some(FormatArg {
+ param: FormatParam::new(
+ match parsed_arg.position {
+ rpf::Position::ArgumentImplicitlyIs(_) => FormatParamKind::Implicit,
+ rpf::Position::ArgumentIs(_) => FormatParamKind::Numbered,
+ // NamedInline is handled by `FormatParam::new()`
+ rpf::Position::ArgumentNamed(name) => FormatParamKind::Named(Symbol::intern(name)),
+ },
+ position.value,
+ parsed_arg.position_span,
+ &values,
+ )?,
+ format: FormatSpec::new(parsed_arg.format, position, &values)?,
+ span: span_from_inner(values.format_string_span, arg_span),
+ })
+ })
+ .collect::<Option<Vec<_>>>()?;
+
+ Some(Self {
+ format_string,
+ args,
+ value_args: values.value_args,
+ newline,
+ })
+ } else {
+ None
+ }
+ }
+
pub fn find_nested(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, expn_id: ExpnId) -> Option<Self> {
let mut format_args = None;
expr_visitor_no_bodies(|e| {
@@ -466,88 +847,23 @@ impl<'tcx> FormatArgsExpn<'tcx> {
format_args
}
- /// Returns a vector of `FormatArgsArg`.
- pub fn args(&self) -> Option<Vec<FormatArgsArg<'tcx>>> {
- if self.specs.is_empty() {
- let args = std::iter::zip(&self.value_args, &self.formatters)
- .map(|(value, &(_, format_trait))| FormatArgsArg {
- value,
- format_trait,
- spec: None,
- })
- .collect();
- return Some(args);
- }
- self.specs
- .iter()
- .map(|spec| {
- if_chain! {
- // struct `core::fmt::rt::v1::Argument`
- if let ExprKind::Struct(_, fields, _) = spec.kind;
- if let Some(position_field) = fields.iter().find(|f| f.ident.name == sym::position);
- if let ExprKind::Lit(lit) = &position_field.expr.kind;
- if let LitKind::Int(position, _) = lit.node;
- if let Ok(i) = usize::try_from(position);
- if let Some(&(j, format_trait)) = self.formatters.get(i);
- then {
- Some(FormatArgsArg {
- value: self.value_args[j],
- format_trait,
- spec: Some(spec),
- })
- } else {
- None
- }
- }
- })
- .collect()
- }
-
/// Source callsite span of all inputs
pub fn inputs_span(&self) -> Span {
match *self.value_args {
- [] => self.format_string_span,
+ [] => self.format_string.span,
[.., last] => self
- .format_string_span
- .to(hygiene::walk_chain(last.span, self.format_string_span.ctxt())),
+ .format_string
+ .span
+ .to(hygiene::walk_chain(last.span, self.format_string.span.ctxt())),
}
}
-}
-/// Type representing a `FormatArgsExpn`'s format arguments
-pub struct FormatArgsArg<'tcx> {
- /// An element of `value_args` according to `position`
- pub value: &'tcx Expr<'tcx>,
- /// An element of `args` according to `position`
- pub format_trait: Symbol,
- /// An element of `specs`
- pub spec: Option<&'tcx Expr<'tcx>>,
-}
-
-impl<'tcx> FormatArgsArg<'tcx> {
- /// Returns true if any formatting parameters are used that would have an effect on strings,
- /// like `{:+2}` instead of just `{}`.
- pub fn has_string_formatting(&self) -> bool {
- self.spec.map_or(false, |spec| {
- // `!` because these conditions check that `self` is unformatted.
- !if_chain! {
- // struct `core::fmt::rt::v1::Argument`
- if let ExprKind::Struct(_, fields, _) = spec.kind;
- if let Some(format_field) = fields.iter().find(|f| f.ident.name == sym::format);
- // struct `core::fmt::rt::v1::FormatSpec`
- if let ExprKind::Struct(_, subfields, _) = format_field.expr.kind;
- if subfields.iter().all(|field| match field.ident.name {
- sym::precision | sym::width => match field.expr.kind {
- ExprKind::Path(QPath::Resolved(_, path)) => {
- path.segments.last().unwrap().ident.name == sym::Implied
- }
- _ => false,
- }
- _ => true,
- });
- then { true } else { false }
- }
- })
+ /// Iterator of all format params, both values and those referenced by `width`/`precision`s.
+ pub fn params(&'tcx self) -> impl Iterator<Item = FormatParam<'tcx>> {
+ self.args
+ .iter()
+ .flat_map(|arg| [Some(arg.param), arg.format.precision.param(), arg.format.width.param()])
+ .flatten()
}
}
diff --git a/src/tools/clippy/clippy_utils/src/msrvs.rs b/src/tools/clippy/clippy_utils/src/msrvs.rs
index 9e238c6f1..62020e21c 100644
--- a/src/tools/clippy/clippy_utils/src/msrvs.rs
+++ b/src/tools/clippy/clippy_utils/src/msrvs.rs
@@ -13,7 +13,7 @@ macro_rules! msrv_aliases {
// names may refer to stabilized feature flags or library items
msrv_aliases! {
1,62,0 { BOOL_THEN_SOME }
- 1,53,0 { OR_PATTERNS, MANUAL_BITS, BTREE_MAP_RETAIN, BTREE_SET_RETAIN }
+ 1,53,0 { OR_PATTERNS, MANUAL_BITS, BTREE_MAP_RETAIN, BTREE_SET_RETAIN, ARRAY_INTO_ITERATOR }
1,52,0 { STR_SPLIT_ONCE, REM_EUCLID_CONST }
1,51,0 { BORROW_AS_PTR, UNSIGNED_ABS }
1,50,0 { BOOL_THEN }
@@ -32,8 +32,8 @@ msrv_aliases! {
1,30,0 { ITERATOR_FIND_MAP, TOOL_ATTRIBUTES }
1,28,0 { FROM_BOOL }
1,26,0 { RANGE_INCLUSIVE, STRING_RETAIN }
+ 1,24,0 { IS_ASCII_DIGIT }
1,18,0 { HASH_MAP_RETAIN, HASH_SET_RETAIN }
1,17,0 { FIELD_INIT_SHORTHAND, STATIC_IN_CONST, EXPECT_ERR }
1,16,0 { STR_REPEAT }
- 1,24,0 { IS_ASCII_DIGIT }
}
diff --git a/src/tools/clippy/clippy_utils/src/numeric_literal.rs b/src/tools/clippy/clippy_utils/src/numeric_literal.rs
index 3fb5415ce..80098d976 100644
--- a/src/tools/clippy/clippy_utils/src/numeric_literal.rs
+++ b/src/tools/clippy/clippy_utils/src/numeric_literal.rs
@@ -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> {
diff --git a/src/tools/clippy/clippy_utils/src/paths.rs b/src/tools/clippy/clippy_utils/src/paths.rs
index 05429d05d..07170e2df 100644
--- a/src/tools/clippy/clippy_utils/src/paths.rs
+++ b/src/tools/clippy/clippy_utils/src/paths.rs
@@ -66,12 +66,9 @@ pub const INDEX_MUT: [&str; 3] = ["core", "ops", "IndexMut"];
pub const INSERT_STR: [&str; 4] = ["alloc", "string", "String", "insert_str"];
pub const IO_READ: [&str; 3] = ["std", "io", "Read"];
pub const IO_WRITE: [&str; 3] = ["std", "io", "Write"];
-pub const IPADDR_V4: [&str; 5] = ["std", "net", "ip", "IpAddr", "V4"];
-pub const IPADDR_V6: [&str; 5] = ["std", "net", "ip", "IpAddr", "V6"];
pub const ITER_COUNT: [&str; 6] = ["core", "iter", "traits", "iterator", "Iterator", "count"];
pub const ITER_EMPTY: [&str; 5] = ["core", "iter", "sources", "empty", "Empty"];
pub const ITER_REPEAT: [&str; 5] = ["core", "iter", "sources", "repeat", "repeat"];
-#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
pub const ITERTOOLS_NEXT_TUPLE: [&str; 3] = ["itertools", "Itertools", "next_tuple"];
#[cfg(feature = "internal")]
pub const KW_MODULE: [&str; 3] = ["rustc_span", "symbol", "kw"];
@@ -96,6 +93,7 @@ pub const PARKING_LOT_RWLOCK_READ_GUARD: [&str; 3] = ["lock_api", "rwlock", "RwL
pub const PARKING_LOT_RWLOCK_WRITE_GUARD: [&str; 3] = ["lock_api", "rwlock", "RwLockWriteGuard"];
pub const PATH_BUF_AS_PATH: [&str; 4] = ["std", "path", "PathBuf", "as_path"];
pub const PATH_TO_PATH_BUF: [&str; 4] = ["std", "path", "Path", "to_path_buf"];
+pub const PEEKABLE: [&str; 5] = ["core", "iter", "adapters", "peekable", "Peekable"];
pub const PERMISSIONS: [&str; 3] = ["std", "fs", "Permissions"];
#[cfg_attr(not(unix), allow(clippy::invalid_paths))]
pub const PERMISSIONS_FROM_MODE: [&str; 6] = ["std", "os", "unix", "fs", "PermissionsExt", "from_mode"];
@@ -194,3 +192,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"];
diff --git a/src/tools/clippy/clippy_utils/src/ptr.rs b/src/tools/clippy/clippy_utils/src/ptr.rs
index 649b7b994..0226f7490 100644
--- a/src/tools/clippy/clippy_utils/src/ptr.rs
+++ b/src/tools/clippy/clippy_utils/src/ptr.rs
@@ -36,7 +36,7 @@ fn extract_clone_suggestions<'tcx>(
if abort {
return false;
}
- if let ExprKind::MethodCall(seg, [recv], _) = expr.kind {
+ if let ExprKind::MethodCall(seg, recv, [], _) = expr.kind {
if path_to_local_id(recv, id) {
if seg.ident.name.as_str() == "capacity" {
abort = true;
diff --git a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs
index 3bf75bcbe..8835b9329 100644
--- a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs
+++ b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs
@@ -6,8 +6,8 @@
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
use rustc_middle::mir::{
- Body, CastKind, NullOp, Operand, Place, ProjectionElem, Rvalue, Statement, StatementKind, Terminator,
- TerminatorKind,
+ Body, CastKind, NonDivergingIntrinsic, NullOp, Operand, Place, ProjectionElem, Rvalue, Statement, StatementKind,
+ Terminator, TerminatorKind,
};
use rustc_middle::ty::subst::GenericArgKind;
use rustc_middle::ty::{self, adjustment::PointerCast, Ty, TyCtxt};
@@ -55,7 +55,7 @@ pub fn is_min_const_fn<'a, 'tcx>(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, msrv:
body.local_decls.iter().next().unwrap().source_info.span,
)?;
- for bb in body.basic_blocks() {
+ for bb in body.basic_blocks.iter() {
check_terminator(tcx, body, bb.terminator(), msrv)?;
for stmt in &bb.statements {
check_statement(tcx, body, def_id, stmt)?;
@@ -82,7 +82,7 @@ fn check_ty<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, span: Span) -> McfResult {
ty::FnPtr(..) => {
return Err((span, "function pointers in const fn are unstable".into()));
},
- ty::Dynamic(preds, _) => {
+ ty::Dynamic(preds, _, _) => {
for pred in preds.iter() {
match pred.skip_binder() {
ty::ExistentialPredicate::AutoTrait(_) | ty::ExistentialPredicate::Projection(_) => {
@@ -161,6 +161,10 @@ fn check_rvalue<'tcx>(
Rvalue::Cast(CastKind::PointerExposeAddress, _, _) => {
Err((span, "casting pointers to ints is unstable in const fn".into()))
},
+ Rvalue::Cast(CastKind::DynStar, _, _) => {
+ // FIXME(dyn-star)
+ unimplemented!()
+ },
// binops are fine on integers
Rvalue::BinaryOp(_, box (lhs, rhs)) | Rvalue::CheckedBinaryOp(_, box (lhs, rhs)) => {
check_operand(tcx, lhs, span, body)?;
@@ -212,7 +216,11 @@ fn check_statement<'tcx>(
check_place(tcx, **place, span, body)
},
- StatementKind::CopyNonOverlapping(box rustc_middle::mir::CopyNonOverlapping { dst, src, count }) => {
+ StatementKind::Intrinsic(box NonDivergingIntrinsic::Assume(op)) => check_operand(tcx, op, span, body),
+
+ StatementKind::Intrinsic(box NonDivergingIntrinsic::CopyNonOverlapping(
+ rustc_middle::mir::CopyNonOverlapping { dst, src, count },
+ )) => {
check_operand(tcx, dst, span, body)?;
check_operand(tcx, src, span, body)?;
check_operand(tcx, count, span, body)
diff --git a/src/tools/clippy/clippy_utils/src/source.rs b/src/tools/clippy/clippy_utils/src/source.rs
index 1197fe914..d85f591fb 100644
--- a/src/tools/clippy/clippy_utils/src/source.rs
+++ b/src/tools/clippy/clippy_utils/src/source.rs
@@ -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>(
diff --git a/src/tools/clippy/clippy_utils/src/sugg.rs b/src/tools/clippy/clippy_utils/src/sugg.rs
index bad291dfc..cca71bbf7 100644
--- a/src/tools/clippy/clippy_utils/src/sugg.rs
+++ b/src/tools/clippy/clippy_utils/src/sugg.rs
@@ -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> {
@@ -367,12 +373,14 @@ fn binop_to_string(op: AssocOp, lhs: &str, rhs: &str) -> String {
| AssocOp::LessEqual
| AssocOp::NotEqual
| AssocOp::Greater
- | AssocOp::GreaterEqual => format!(
- "{} {} {}",
- lhs,
- op.to_ast_binop().expect("Those are AST ops").to_string(),
- rhs
- ),
+ | AssocOp::GreaterEqual => {
+ format!(
+ "{} {} {}",
+ lhs,
+ op.to_ast_binop().expect("Those are AST ops").to_string(),
+ rhs
+ )
+ },
AssocOp::Assign => format!("{} = {}", lhs, rhs),
AssocOp::AssignOp(op) => {
format!("{} {}= {}", lhs, token_kind_to_string(&token::BinOp(op)), rhs)
@@ -862,15 +870,15 @@ impl<'tcx> DerefDelegate<'_, 'tcx> {
/// indicates whether the function from `parent_expr` takes its args by double reference
fn func_takes_arg_by_double_ref(&self, parent_expr: &'tcx hir::Expr<'_>, cmt_hir_id: HirId) -> bool {
let ty = match parent_expr.kind {
- ExprKind::MethodCall(_, call_args, _) => {
+ ExprKind::MethodCall(_, receiver, call_args, _) => {
if let Some(sig) = self
.cx
.typeck_results()
.type_dependent_def_id(parent_expr.hir_id)
.map(|did| self.cx.tcx.fn_sig(did).skip_binder())
{
- call_args
- .iter()
+ std::iter::once(receiver)
+ .chain(call_args.iter())
.position(|arg| arg.hir_id == cmt_hir_id)
.map(|i| sig.inputs()[i])
} else {
@@ -927,14 +935,14 @@ impl<'tcx> Delegate<'tcx> for DerefDelegate<'_, 'tcx> {
match &parent_expr.kind {
// given expression is the self argument and will be handled completely by the compiler
// i.e.: `|x| x.is_something()`
- ExprKind::MethodCall(_, [self_expr, ..], _) if self_expr.hir_id == cmt.hir_id => {
+ ExprKind::MethodCall(_, self_expr, ..) if self_expr.hir_id == cmt.hir_id => {
let _ = write!(self.suggestion_start, "{}{}", start_snip, ident_str_with_proj);
self.next_pos = span.hi();
return;
},
// item is used in a call
// i.e.: `Call`: `|x| please(x)` or `MethodCall`: `|x| [1, 2, 3].contains(x)`
- ExprKind::Call(_, [call_args @ ..]) | ExprKind::MethodCall(_, [_, call_args @ ..], _) => {
+ ExprKind::Call(_, [call_args @ ..]) | ExprKind::MethodCall(_, _, [call_args @ ..], _) => {
let expr = self.cx.tcx.hir().expect_expr(cmt.hir_id);
let arg_ty_kind = self.cx.typeck_results().expr_ty(expr).kind();
diff --git a/src/tools/clippy/clippy_utils/src/ty.rs b/src/tools/clippy/clippy_utils/src/ty.rs
index a05d633d9..a8ad6cf4f 100644
--- a/src/tools/clippy/clippy_utils/src/ty.rs
+++ b/src/tools/clippy/clippy_utils/src/ty.rs
@@ -43,14 +43,6 @@ pub fn can_partially_move_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool
}
}
-/// Walks into `ty` and returns `true` if any inner type is the same as `other_ty`
-pub fn contains_ty<'tcx>(ty: Ty<'tcx>, other_ty: Ty<'tcx>) -> bool {
- ty.walk().any(|inner| match inner.unpack() {
- GenericArgKind::Type(inner_ty) => other_ty == inner_ty,
- GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => false,
- })
-}
-
/// Walks into `ty` and returns `true` if any inner type is an instance of the given adt
/// constructor.
pub fn contains_adt_constructor<'tcx>(ty: Ty<'tcx>, adt: AdtDef<'tcx>) -> bool {
@@ -209,7 +201,7 @@ pub fn is_must_use_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
}
false
},
- ty::Dynamic(binder, _) => {
+ ty::Dynamic(binder, _, _) => {
for predicate in binder.iter() {
if let ty::ExistentialPredicate::Trait(ref trait_ref) = predicate.skip_binder() {
if cx.tcx.has_attr(trait_ref.def_id, sym::must_use) {
@@ -410,7 +402,7 @@ pub fn peel_mid_ty_refs(ty: Ty<'_>) -> (Ty<'_>, usize) {
peel(ty, 0)
}
-/// Peels off all references on the type.Returns the underlying type, the number of references
+/// Peels off all references on the type. Returns the underlying type, the number of references
/// removed, and whether the pointer is ultimately mutable or not.
pub fn peel_mid_ty_refs_is_mutable(ty: Ty<'_>) -> (Ty<'_>, usize, Mutability) {
fn f(ty: Ty<'_>, count: usize, mutability: Mutability) -> (Ty<'_>, usize, Mutability) {
@@ -503,7 +495,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 +510,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 +533,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 +542,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 +564,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,9 +577,9 @@ 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, _) => {
+ ty::Dynamic(bounds, _, _) => {
let lang_items = cx.tcx.lang_items();
match bounds.principal() {
Some(bound)
@@ -594,26 +591,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 +623,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 +644,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 +664,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 +688,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)]
@@ -827,3 +831,53 @@ pub fn ty_is_fn_once_param<'tcx>(tcx: TyCtxt<'_>, ty: Ty<'tcx>, predicates: &'tc
})
.unwrap_or(false)
}
+
+/// Comes up with an "at least" guesstimate for the type's size, not taking into
+/// account the layout of type parameters.
+pub fn approx_ty_size<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> u64 {
+ use rustc_middle::ty::layout::LayoutOf;
+ if !is_normalizable(cx, cx.param_env, ty) {
+ return 0;
+ }
+ match (cx.layout_of(ty).map(|layout| layout.size.bytes()), ty.kind()) {
+ (Ok(size), _) => size,
+ (Err(_), ty::Tuple(list)) => list.as_substs().types().map(|t| approx_ty_size(cx, t)).sum(),
+ (Err(_), ty::Array(t, n)) => {
+ n.try_eval_usize(cx.tcx, cx.param_env).unwrap_or_default() * approx_ty_size(cx, *t)
+ },
+ (Err(_), ty::Adt(def, subst)) if def.is_struct() => def
+ .variants()
+ .iter()
+ .map(|v| {
+ v.fields
+ .iter()
+ .map(|field| approx_ty_size(cx, field.ty(cx.tcx, subst)))
+ .sum::<u64>()
+ })
+ .sum(),
+ (Err(_), ty::Adt(def, subst)) if def.is_enum() => def
+ .variants()
+ .iter()
+ .map(|v| {
+ v.fields
+ .iter()
+ .map(|field| approx_ty_size(cx, field.ty(cx.tcx, subst)))
+ .sum::<u64>()
+ })
+ .max()
+ .unwrap_or_default(),
+ (Err(_), ty::Adt(def, subst)) if def.is_union() => def
+ .variants()
+ .iter()
+ .map(|v| {
+ v.fields
+ .iter()
+ .map(|field| approx_ty_size(cx, field.ty(cx.tcx, subst)))
+ .max()
+ .unwrap_or_default()
+ })
+ .max()
+ .unwrap_or_default(),
+ (Err(_), _) => 0,
+ }
+}
diff --git a/src/tools/clippy/clippy_utils/src/visitors.rs b/src/tools/clippy/clippy_utils/src/visitors.rs
index bae8ad9f5..232d57190 100644
--- a/src/tools/clippy/clippy_utils/src/visitors.rs
+++ b/src/tools/clippy/clippy_utils/src/visitors.rs
@@ -274,7 +274,7 @@ pub fn is_res_used(cx: &LateContext<'_>, res: Res, body: BodyId) -> bool {
}
!found
})
- .visit_expr(&cx.tcx.hir().body(body).value);
+ .visit_expr(cx.tcx.hir().body(body).value);
found
}
@@ -568,6 +568,7 @@ pub fn for_each_local_use_after_expr<'tcx, B>(
// Calls the given function for every unconsumed temporary created by the expression. Note the
// function is only guaranteed to be called for types which need to be dropped, but it may be called
// for other types.
+#[allow(clippy::too_many_lines)]
pub fn for_each_unconsumed_temporary<'tcx, B>(
cx: &LateContext<'tcx>,
e: &'tcx Expr<'tcx>,
@@ -620,7 +621,13 @@ pub fn for_each_unconsumed_temporary<'tcx, B>(
helper(typeck, true, arg, f)?;
}
},
- ExprKind::MethodCall(_, args, _) | ExprKind::Tup(args) | ExprKind::Array(args) => {
+ ExprKind::MethodCall(_, receiver, args, _) => {
+ helper(typeck, true, receiver, f)?;
+ for arg in args {
+ helper(typeck, true, arg, f)?;
+ }
+ },
+ ExprKind::Tup(args) | ExprKind::Array(args) => {
for arg in args {
helper(typeck, true, arg, f)?;
}