Auto merge of #8219 - camsteffen:macro-decoupling, r=llogiq

New macro utils

changelog: none

Sorry, this is a big one. A lot of interrelated changes and I wanted to put the new utils to use to make sure they are somewhat battle-tested. We may want to divide some of the lint-specific refactoring commits into batches for smaller reviewing tasks. I could also split into more PRs.

Introduces a bunch of new utils at `clippy_utils::macros::...`. Please read through the docs and give any feedback! I'm happy to introduce `MacroCall` and various functions to retrieve an instance. It feels like the missing puzzle piece. I'm also introducing `ExpnId` from rustc as "useful for Clippy too". `@rust-lang/clippy`

Fixes #7843 by not parsing every node of macro implementations, at least the major offenders.

I probably want to get rid of `is_expn_of` at some point.
This commit is contained in:
bors 2022-01-04 22:32:02 +00:00
commit ba03dc70fd
31 changed files with 873 additions and 870 deletions

View file

@ -1,12 +1,10 @@
use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::higher;
use clippy_utils::source::snippet_opt;
use clippy_utils::{is_direct_expn_of, is_expn_of, match_panic_call, peel_blocks};
use if_chain::if_chain;
use rustc_hir::{Expr, ExprKind, UnOp};
use clippy_utils::macros::{find_assert_args, root_macro_call_first_node, PanicExpn};
use rustc_hir::Expr;
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::sym;
declare_clippy_lint! {
/// ### What it does
@ -36,107 +34,39 @@ declare_lint_pass!(AssertionsOnConstants => [ASSERTIONS_ON_CONSTANTS]);
impl<'tcx> LateLintPass<'tcx> for AssertionsOnConstants {
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
let lint_true = |is_debug: bool| {
let Some(macro_call) = root_macro_call_first_node(cx, e) else { return };
let is_debug = match cx.tcx.get_diagnostic_name(macro_call.def_id) {
Some(sym::debug_assert_macro) => true,
Some(sym::assert_macro) => false,
_ => return,
};
let Some((condition, panic_expn)) = find_assert_args(cx, e, macro_call.expn) else { return };
let Some((Constant::Bool(val), _)) = constant(cx, cx.typeck_results(), condition) else { return };
if val {
span_lint_and_help(
cx,
ASSERTIONS_ON_CONSTANTS,
e.span,
if is_debug {
"`debug_assert!(true)` will be optimized out by the compiler"
} else {
"`assert!(true)` will be optimized out by the compiler"
},
macro_call.span,
&format!(
"`{}!(true)` will be optimized out by the compiler",
cx.tcx.item_name(macro_call.def_id)
),
None,
"remove it",
);
};
let lint_false_without_message = || {
span_lint_and_help(
cx,
ASSERTIONS_ON_CONSTANTS,
e.span,
"`assert!(false)` should probably be replaced",
None,
"use `panic!()` or `unreachable!()`",
);
};
let lint_false_with_message = |panic_message: String| {
span_lint_and_help(
cx,
ASSERTIONS_ON_CONSTANTS,
e.span,
&format!("`assert!(false, {})` should probably be replaced", panic_message),
None,
&format!("use `panic!({})` or `unreachable!({})`", panic_message, panic_message),
);
};
if let Some(debug_assert_span) = is_expn_of(e.span, "debug_assert") {
if debug_assert_span.from_expansion() {
return;
}
if_chain! {
if let ExprKind::Unary(_, lit) = e.kind;
if let Some((Constant::Bool(is_true), _)) = constant(cx, cx.typeck_results(), lit);
if is_true;
then {
lint_true(true);
}
} else if !is_debug {
let (assert_arg, panic_arg) = match panic_expn {
PanicExpn::Empty => ("", ""),
_ => (", ..", ".."),
};
} else if let Some(assert_span) = is_direct_expn_of(e.span, "assert") {
if assert_span.from_expansion() {
return;
}
if let Some(assert_match) = match_assert_with_message(cx, e) {
match assert_match {
// matched assert but not message
AssertKind::WithoutMessage(false) => lint_false_without_message(),
AssertKind::WithoutMessage(true) | AssertKind::WithMessage(_, true) => lint_true(false),
AssertKind::WithMessage(panic_message, false) => lint_false_with_message(panic_message),
};
}
span_lint_and_help(
cx,
ASSERTIONS_ON_CONSTANTS,
macro_call.span,
&format!("`assert!(false{})` should probably be replaced", assert_arg),
None,
&format!("use `panic!({})` or `unreachable!({0})`", panic_arg),
);
}
}
}
/// Result of calling `match_assert_with_message`.
enum AssertKind {
WithMessage(String, bool),
WithoutMessage(bool),
}
/// Check if the expression matches
///
/// ```rust,ignore
/// if !c {
/// {
/// ::std::rt::begin_panic(message, _)
/// }
/// }
/// ```
///
/// where `message` is any expression and `c` is a constant bool.
fn match_assert_with_message<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<AssertKind> {
if_chain! {
if let Some(higher::If { cond, then, .. }) = higher::If::hir(expr);
if let ExprKind::Unary(UnOp::Not, expr) = cond.kind;
// bind the first argument of the `assert!` macro
if let Some((Constant::Bool(is_true), _)) = constant(cx, cx.typeck_results(), expr);
let begin_panic_call = peel_blocks(then);
// function call
if let Some(arg) = match_panic_call(cx, begin_panic_call);
// bind the second argument of the `assert!` macro if it exists
if let panic_message = snippet_opt(cx, arg.span);
// second argument of begin_panic is irrelevant
// as is the second match arm
then {
// an empty message occurs when it was generated by the macro
// (and not passed by the user)
return panic_message
.filter(|msg| !msg.is_empty())
.map(|msg| AssertKind::WithMessage(msg, is_true))
.or(Some(AssertKind::WithoutMessage(is_true)));
}
}
None
}

View file

@ -1,9 +1,10 @@
//! checks for attributes
use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then};
use clippy_utils::macros::{is_panic, macro_backtrace};
use clippy_utils::msrvs;
use clippy_utils::source::{first_line_of_span, is_present_in_source, snippet_opt, without_block_comments};
use clippy_utils::{extract_msrv_attr, match_panic_def_id, meets_msrv};
use clippy_utils::{extract_msrv_attr, meets_msrv};
use if_chain::if_chain;
use rustc_ast::{AttrKind, AttrStyle, Attribute, Lit, LitKind, MetaItemKind, NestedMetaItem};
use rustc_errors::Applicability;
@ -443,20 +444,15 @@ fn is_relevant_block(cx: &LateContext<'_>, typeck_results: &ty::TypeckResults<'_
}
fn is_relevant_expr(cx: &LateContext<'_>, typeck_results: &ty::TypeckResults<'_>, expr: &Expr<'_>) -> bool {
if macro_backtrace(expr.span).last().map_or(false, |macro_call| {
is_panic(cx, macro_call.def_id) || cx.tcx.item_name(macro_call.def_id) == sym::unreachable
}) {
return false;
}
match &expr.kind {
ExprKind::Block(block, _) => is_relevant_block(cx, typeck_results, block),
ExprKind::Ret(Some(e)) => is_relevant_expr(cx, typeck_results, e),
ExprKind::Ret(None) | ExprKind::Break(_, None) => false,
ExprKind::Call(path_expr, _) => {
if let ExprKind::Path(qpath) = &path_expr.kind {
typeck_results
.qpath_res(qpath, path_expr.hir_id)
.opt_def_id()
.map_or(true, |fun_id| !match_panic_def_id(cx, fun_id))
} else {
true
}
},
_ => true,
}
}

View file

@ -1,4 +1,5 @@
use clippy_utils::{diagnostics::span_lint_and_sugg, higher, is_direct_expn_of, ty::implements_trait};
use clippy_utils::macros::{find_assert_eq_args, root_macro_call_first_node};
use clippy_utils::{diagnostics::span_lint_and_sugg, ty::implements_trait};
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, Lit};
@ -66,44 +67,40 @@ fn is_impl_not_trait_with_bool_out(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) ->
impl<'tcx> LateLintPass<'tcx> for BoolAssertComparison {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
let macros = ["assert_eq", "debug_assert_eq"];
let inverted_macros = ["assert_ne", "debug_assert_ne"];
for mac in macros.iter().chain(inverted_macros.iter()) {
if let Some(span) = is_direct_expn_of(expr.span, mac) {
if let Some(args) = higher::extract_assert_macro_args(expr) {
if let [a, b, ..] = args[..] {
let nb_bool_args = usize::from(is_bool_lit(a)) + usize::from(is_bool_lit(b));
if nb_bool_args != 1 {
// If there are two boolean arguments, we definitely don't understand
// what's going on, so better leave things as is...
//
// Or there is simply no boolean and then we can leave things as is!
return;
}
if !is_impl_not_trait_with_bool_out(cx, a) || !is_impl_not_trait_with_bool_out(cx, b) {
// At this point the expression which is not a boolean
// literal does not implement Not trait with a bool output,
// so we cannot suggest to rewrite our code
return;
}
let non_eq_mac = &mac[..mac.len() - 3];
span_lint_and_sugg(
cx,
BOOL_ASSERT_COMPARISON,
span,
&format!("used `{}!` with a literal bool", mac),
"replace it with",
format!("{}!(..)", non_eq_mac),
Applicability::MaybeIncorrect,
);
return;
}
}
}
let Some(macro_call) = root_macro_call_first_node(cx, expr) else { return };
let macro_name = cx.tcx.item_name(macro_call.def_id);
if !matches!(
macro_name.as_str(),
"assert_eq" | "debug_assert_eq" | "assert_ne" | "debug_assert_ne"
) {
return;
}
let Some ((a, b, _)) = find_assert_eq_args(cx, expr, macro_call.expn) else { return };
if !(is_bool_lit(a) ^ is_bool_lit(b)) {
// If there are two boolean arguments, we definitely don't understand
// what's going on, so better leave things as is...
//
// Or there is simply no boolean and then we can leave things as is!
return;
}
if !is_impl_not_trait_with_bool_out(cx, a) || !is_impl_not_trait_with_bool_out(cx, b) {
// At this point the expression which is not a boolean
// literal does not implement Not trait with a bool output,
// so we cannot suggest to rewrite our code
return;
}
let macro_name = macro_name.as_str();
let non_eq_mac = &macro_name[..macro_name.len() - 3];
span_lint_and_sugg(
cx,
BOOL_ASSERT_COMPARISON,
macro_call.span,
&format!("used `{}!` with a literal bool", macro_name),
"replace it with",
format!("{}!(..)", non_eq_mac),
Applicability::MaybeIncorrect,
);
}
}

View file

@ -1,8 +1,9 @@
use clippy_utils::attrs::is_doc_hidden;
use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_note, span_lint_and_then};
use clippy_utils::macros::{is_panic, root_macro_call_first_node};
use clippy_utils::source::{first_line_of_span, snippet_with_applicability};
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
use clippy_utils::{is_entrypoint_fn, is_expn_of, match_panic_def_id, method_chain_args, return_ty};
use clippy_utils::{is_entrypoint_fn, method_chain_args, return_ty};
use if_chain::if_chain;
use itertools::Itertools;
use rustc_ast::ast::{Async, AttrKind, Attribute, Fn, FnRetTy, ItemKind};
@ -13,7 +14,7 @@ use rustc_errors::emitter::EmitterWriter;
use rustc_errors::{Applicability, Handler, SuggestionStyle};
use rustc_hir as hir;
use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
use rustc_hir::{AnonConst, Expr, ExprKind, QPath};
use rustc_hir::{AnonConst, Expr};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::map::Map;
use rustc_middle::lint::in_external_macro;
@ -805,24 +806,17 @@ impl<'a, 'tcx> Visitor<'tcx> for FindPanicUnwrap<'a, 'tcx> {
return;
}
// check for `begin_panic`
if_chain! {
if let ExprKind::Call(func_expr, _) = expr.kind;
if let ExprKind::Path(QPath::Resolved(_, path)) = func_expr.kind;
if let Some(path_def_id) = path.res.opt_def_id();
if match_panic_def_id(self.cx, path_def_id);
if is_expn_of(expr.span, "unreachable").is_none();
if !is_expn_of_debug_assertions(expr.span);
then {
self.panic_span = Some(expr.span);
if let Some(macro_call) = root_macro_call_first_node(self.cx, expr) {
if is_panic(self.cx, macro_call.def_id)
|| matches!(
self.cx.tcx.item_name(macro_call.def_id).as_str(),
"assert" | "assert_eq" | "assert_ne" | "todo"
)
{
self.panic_span = Some(macro_call.span);
}
}
// check for `assert_eq` or `assert_ne`
if is_expn_of(expr.span, "assert_eq").is_some() || is_expn_of(expr.span, "assert_ne").is_some() {
self.panic_span = Some(expr.span);
}
// check for `unwrap`
if let Some(arglists) = method_chain_args(expr, &["unwrap"]) {
let receiver_ty = self.typeck_results.expr_ty(&arglists[0][0]).peel_refs();
@ -844,8 +838,3 @@ impl<'a, 'tcx> Visitor<'tcx> for FindPanicUnwrap<'a, 'tcx> {
NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
}
}
fn is_expn_of_debug_assertions(span: Span) -> bool {
const MACRO_NAMES: &[&str] = &["debug_assert", "debug_assert_eq", "debug_assert_ne"];
MACRO_NAMES.iter().any(|name| is_expn_of(span, name).is_some())
}

View file

@ -1,10 +1,11 @@
use clippy_utils::diagnostics::{multispan_sugg, span_lint, span_lint_and_then};
use clippy_utils::macros::{find_assert_eq_args, first_node_macro_backtrace};
use clippy_utils::source::snippet;
use clippy_utils::ty::{implements_trait, is_copy};
use clippy_utils::{ast_utils::is_useless_with_eq_exprs, eq_expr_value, higher, is_expn_of, is_in_test_function};
use clippy_utils::{ast_utils::is_useless_with_eq_exprs, eq_expr_value, is_in_test_function};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, StmtKind};
use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
@ -68,32 +69,26 @@ declare_clippy_lint! {
declare_lint_pass!(EqOp => [EQ_OP, OP_REF]);
const ASSERT_MACRO_NAMES: [&str; 4] = ["assert_eq", "assert_ne", "debug_assert_eq", "debug_assert_ne"];
impl<'tcx> LateLintPass<'tcx> for EqOp {
#[allow(clippy::similar_names, clippy::too_many_lines)]
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
if let ExprKind::Block(block, _) = e.kind {
for stmt in block.stmts {
for amn in &ASSERT_MACRO_NAMES {
if_chain! {
if is_expn_of(stmt.span, amn).is_some();
if let StmtKind::Semi(matchexpr) = stmt.kind;
if let Some(macro_args) = higher::extract_assert_macro_args(matchexpr);
if macro_args.len() == 2;
let (lhs, rhs) = (macro_args[0], macro_args[1]);
if eq_expr_value(cx, lhs, rhs);
if !is_in_test_function(cx.tcx, e.hir_id);
then {
span_lint(
cx,
EQ_OP,
lhs.span.to(rhs.span),
&format!("identical args used in this `{}!` macro call", amn),
);
}
}
}
if_chain! {
if let Some((macro_call, macro_name)) = first_node_macro_backtrace(cx, e).find_map(|macro_call| {
let name = cx.tcx.item_name(macro_call.def_id);
matches!(name.as_str(), "assert_eq" | "assert_ne" | "debug_assert_eq" | "debug_assert_ne")
.then(|| (macro_call, name))
});
if let Some((lhs, rhs, _)) = find_assert_eq_args(cx, e, macro_call.expn);
if eq_expr_value(cx, lhs, rhs);
if macro_call.is_local();
if !is_in_test_function(cx.tcx, e.hir_id);
then {
span_lint(
cx,
EQ_OP,
lhs.span.to(rhs.span),
&format!("identical args used in this `{}!` macro call", macro_name),
);
}
}
if let ExprKind::Binary(op, left, right) = e.kind {

View file

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
use clippy_utils::higher::FormatArgsExpn;
use clippy_utils::macros::FormatArgsExpn;
use clippy_utils::{is_expn_of, match_function_call, paths};
use if_chain::if_chain;
use rustc_errors::Applicability;
@ -48,7 +48,7 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitWrite {
} else {
None
};
if let Some(format_args) = FormatArgsExpn::parse(write_arg);
if let Some(format_args) = FormatArgsExpn::parse(cx, write_arg);
then {
let calling_macro =
// ordering is important here, since `writeln!` uses `write!` internally
@ -80,7 +80,7 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitWrite {
)
};
let msg = format!("use of `{}.unwrap()`", used);
if let [write_output] = *format_args.format_string_symbols {
if let [write_output] = *format_args.format_string_parts {
let mut write_output = write_output.to_string();
if write_output.ends_with('\n') {
write_output.pop();

View file

@ -1,6 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::macros::{is_panic, root_macro_call_first_node};
use clippy_utils::method_chain_args;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{is_expn_of, match_panic_def_id, method_chain_args};
use if_chain::if_chain;
use rustc_hir as hir;
use rustc_lint::{LateContext, LateLintPass};
@ -68,7 +69,7 @@ impl<'tcx> LateLintPass<'tcx> for FallibleImplFrom {
fn lint_impl_body<'tcx>(cx: &LateContext<'tcx>, impl_span: Span, impl_items: &[hir::ImplItemRef]) {
use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
use rustc_hir::{Expr, ExprKind, ImplItemKind, QPath};
use rustc_hir::{Expr, ImplItemKind};
struct FindPanicUnwrap<'a, 'tcx> {
lcx: &'a LateContext<'tcx>,
@ -80,14 +81,8 @@ fn lint_impl_body<'tcx>(cx: &LateContext<'tcx>, impl_span: Span, impl_items: &[h
type Map = Map<'tcx>;
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
// check for `begin_panic`
if_chain! {
if let ExprKind::Call(func_expr, _) = expr.kind;
if let ExprKind::Path(QPath::Resolved(_, path)) = func_expr.kind;
if let Some(path_def_id) = path.res.opt_def_id();
if match_panic_def_id(self.lcx, path_def_id);
if is_expn_of(expr.span, "unreachable").is_none();
then {
if let Some(macro_call) = root_macro_call_first_node(self.lcx, expr) {
if is_panic(self.lcx, macro_call.def_id) {
self.result.push(expr.span);
}
}

View file

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::higher::FormatExpn;
use clippy_utils::macros::{root_macro_call_first_node, FormatArgsExpn};
use clippy_utils::source::{snippet_opt, snippet_with_applicability};
use clippy_utils::sugg::Sugg;
use if_chain::if_chain;
@ -43,38 +43,41 @@ declare_lint_pass!(UselessFormat => [USELESS_FORMAT]);
impl<'tcx> LateLintPass<'tcx> for UselessFormat {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
let FormatExpn { call_site, format_args } = match FormatExpn::parse(expr) {
Some(e) if !e.call_site.from_expansion() => e,
_ => return,
let (format_args, call_site) = if_chain! {
if let Some(macro_call) = root_macro_call_first_node(cx, expr);
if cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id);
if let Some(format_args) = FormatArgsExpn::find_nested(cx, expr, macro_call.expn);
then {
(format_args, macro_call.span)
} else {
return
}
};
let mut applicability = Applicability::MachineApplicable;
if format_args.value_args.is_empty() {
if format_args.format_string_parts.is_empty() {
span_useless_format_empty(cx, call_site, "String::new()".to_owned(), applicability);
} else {
if_chain! {
if let [e] = &*format_args.format_string_parts;
if let ExprKind::Lit(lit) = &e.kind;
if let Some(s_src) = snippet_opt(cx, lit.span);
then {
match *format_args.format_string_parts {
[] => span_useless_format_empty(cx, call_site, "String::new()".to_owned(), applicability),
[_] => {
if let Some(s_src) = snippet_opt(cx, format_args.format_string_span) {
// Simulate macro expansion, converting {{ and }} to { and }.
let s_expand = s_src.replace("{{", "{").replace("}}", "}");
let sugg = format!("{}.to_string()", s_expand);
span_useless_format(cx, call_site, sugg, applicability);
}
}
},
[..] => {},
}
} else if let [value] = *format_args.value_args {
if_chain! {
if format_args.format_string_symbols == [kw::Empty];
if format_args.format_string_parts == [kw::Empty];
if match cx.typeck_results().expr_ty(value).peel_refs().kind() {
ty::Adt(adt, _) => cx.tcx.is_diagnostic_item(sym::String, adt.did),
ty::Str => true,
_ => false,
};
if let Some(args) = format_args.args();
if args.iter().all(|arg| arg.is_display() && !arg.has_string_formatting());
if args.iter().all(|arg| arg.format_trait == sym::Display && !arg.has_string_formatting());
then {
let is_new_string = match value.kind {
ExprKind::Binary(..) => true,

View file

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::higher::{FormatArgsArg, FormatArgsExpn, FormatExpn};
use clippy_utils::macros::{FormatArgsArg, FormatArgsExpn};
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::implements_trait;
use clippy_utils::{is_diag_trait_item, match_def_path, paths};
@ -83,7 +83,7 @@ const FORMAT_MACRO_DIAG_ITEMS: &[Symbol] = &[sym::format_macro, sym::std_panic_m
impl<'tcx> LateLintPass<'tcx> for FormatArgs {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if_chain! {
if let Some(format_args) = FormatArgsExpn::parse(expr);
if let Some(format_args) = FormatArgsExpn::parse(cx, expr);
let expr_expn_data = expr.span.ctxt().outer_expn_data();
let outermost_expn_data = outermost_expn_data(expr_expn_data);
if let Some(macro_def_id) = outermost_expn_data.macro_def_id;
@ -97,7 +97,7 @@ impl<'tcx> LateLintPass<'tcx> for FormatArgs {
if let Some(args) = format_args.args();
then {
for (i, arg) in args.iter().enumerate() {
if !arg.is_display() {
if arg.format_trait != sym::Display {
continue;
}
if arg.has_string_formatting() {
@ -106,8 +106,8 @@ impl<'tcx> LateLintPass<'tcx> for FormatArgs {
if is_aliased(&args, i) {
continue;
}
check_format_in_format_args(cx, outermost_expn_data.call_site, name, arg);
check_to_string_in_format_args(cx, name, arg);
check_format_in_format_args(cx, outermost_expn_data.call_site, name, arg.value);
check_to_string_in_format_args(cx, name, arg.value);
}
}
}
@ -122,30 +122,31 @@ fn outermost_expn_data(expn_data: ExpnData) -> ExpnData {
}
}
fn check_format_in_format_args(cx: &LateContext<'_>, call_site: Span, name: Symbol, arg: &FormatArgsArg<'_>) {
if_chain! {
if FormatExpn::parse(arg.value).is_some();
if !arg.value.span.ctxt().outer_expn_data().call_site.from_expansion();
then {
span_lint_and_then(
cx,
FORMAT_IN_FORMAT_ARGS,
call_site,
&format!("`format!` in `{}!` args", name),
|diag| {
diag.help(&format!(
"combine the `format!(..)` arguments with the outer `{}!(..)` call",
name
));
diag.help("or consider changing `format!` to `format_args!`");
},
);
}
fn check_format_in_format_args(cx: &LateContext<'_>, call_site: Span, name: Symbol, arg: &Expr<'_>) {
let expn_data = arg.span.ctxt().outer_expn_data();
if expn_data.call_site.from_expansion() {
return;
}
let Some(mac_id) = expn_data.macro_def_id else { return };
if !cx.tcx.is_diagnostic_item(sym::format_macro, mac_id) {
return;
}
span_lint_and_then(
cx,
FORMAT_IN_FORMAT_ARGS,
call_site,
&format!("`format!` in `{}!` args", name),
|diag| {
diag.help(&format!(
"combine the `format!(..)` arguments with the outer `{}!(..)` call",
name
));
diag.help("or consider changing `format!` to `format_args!`");
},
);
}
fn check_to_string_in_format_args<'tcx>(cx: &LateContext<'tcx>, name: Symbol, arg: &FormatArgsArg<'tcx>) {
let value = arg.value;
fn check_to_string_in_format_args(cx: &LateContext<'_>, name: Symbol, value: &Expr<'_>) {
if_chain! {
if !value.span.from_expansion();
if let ExprKind::MethodCall(_, _, [receiver], _) = value.kind;

View file

@ -1,11 +1,12 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::higher::PanicExpn;
use clippy_utils::macros::{root_macro_call, FormatArgsExpn};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{is_expn_of, sugg};
use clippy_utils::{peel_blocks_with_stmt, sugg};
use rustc_errors::Applicability;
use rustc_hir::{Block, Expr, ExprKind, StmtKind, UnOp};
use rustc_hir::{Expr, ExprKind, UnOp};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::sym;
declare_clippy_lint! {
/// ### What it does
@ -35,64 +36,33 @@ declare_clippy_lint! {
declare_lint_pass!(ManualAssert => [MANUAL_ASSERT]);
impl LateLintPass<'_> for ManualAssert {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
if_chain! {
if let Expr {
kind: ExprKind:: If(cond, Expr {
kind: ExprKind::Block(
Block {
stmts: [stmt],
..
},
_),
..
}, None),
..
} = &expr;
if is_expn_of(stmt.span, "panic").is_some();
if let ExprKind::If(cond, then, None) = expr.kind;
if !matches!(cond.kind, ExprKind::Let(_));
if let StmtKind::Semi(semi) = stmt.kind;
if !expr.span.from_expansion();
let then = peel_blocks_with_stmt(then);
if let Some(macro_call) = root_macro_call(then.span);
if cx.tcx.item_name(macro_call.def_id) == sym::panic;
if !cx.tcx.sess.source_map().is_multiline(cond.span);
if let Some(format_args) = FormatArgsExpn::find_nested(cx, then, macro_call.expn);
then {
let call = if_chain! {
if let ExprKind::Block(block, _) = semi.kind;
if let Some(init) = block.expr;
then {
init
} else {
semi
}
};
let span = if let Some(panic_expn) = PanicExpn::parse(call) {
match *panic_expn.format_args.value_args {
[] => panic_expn.format_args.format_string_span,
[.., last] => panic_expn.format_args.format_string_span.to(last.span),
}
} else if let ExprKind::Call(_, [format_args]) = call.kind {
format_args.span
} else {
return
};
let mut applicability = Applicability::MachineApplicable;
let sugg = snippet_with_applicability(cx, span, "..", &mut applicability);
let cond_sugg = if let ExprKind::DropTemps(e, ..) = cond.kind {
if let Expr{kind: ExprKind::Unary(UnOp::Not, not_expr), ..} = e {
sugg::Sugg::hir_with_applicability(cx, not_expr, "..", &mut applicability).maybe_par().to_string()
} else {
format!("!{}", sugg::Sugg::hir_with_applicability(cx, e, "..", &mut applicability).maybe_par())
}
} else {
format!("!{}", sugg::Sugg::hir_with_applicability(cx, cond, "..", &mut applicability).maybe_par())
let format_args_snip = snippet_with_applicability(cx, format_args.inputs_span(), "..", &mut applicability);
let cond = cond.peel_drop_temps();
let (cond, not) = match cond.kind {
ExprKind::Unary(UnOp::Not, e) => (e, ""),
_ => (cond, "!"),
};
let cond_sugg = sugg::Sugg::hir_with_applicability(cx, cond, "..", &mut applicability).maybe_par();
let sugg = format!("assert!({not}{cond_sugg}, {format_args_snip});");
span_lint_and_sugg(
cx,
MANUAL_ASSERT,
expr.span,
"only a `panic!` in `if`-then statement",
"try",
format!("assert!({}, {});", cond_sugg, sugg),
sugg,
Applicability::MachineApplicable,
);
}

View file

@ -2,16 +2,17 @@ use clippy_utils::consts::{constant, constant_full_int, miri_to_const, FullInt};
use clippy_utils::diagnostics::{
multispan_sugg, span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then,
};
use clippy_utils::higher;
use clippy_utils::macros::{is_panic, root_macro_call};
use clippy_utils::source::{expr_block, indent_of, snippet, snippet_block, snippet_opt, snippet_with_applicability};
use clippy_utils::sugg::Sugg;
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item, match_type, peel_mid_ty_refs};
use clippy_utils::visitors::is_local_used;
use clippy_utils::{
get_parent_expr, is_expn_of, is_lang_ctor, is_lint_allowed, is_refutable, is_unit_expr, is_wild, meets_msrv, msrvs,
get_parent_expr, is_lang_ctor, is_lint_allowed, is_refutable, is_unit_expr, is_wild, meets_msrv, msrvs,
path_to_local, path_to_local_id, peel_blocks, peel_hir_pat_refs, peel_n_hir_expr_refs, recurse_or_patterns,
strip_pat_refs,
};
use clippy_utils::{higher, peel_blocks_with_stmt};
use clippy_utils::{paths, search_same, SpanlessEq, SpanlessHash};
use core::iter::{once, ExactSizeIterator};
use if_chain::if_chain;
@ -974,7 +975,8 @@ fn check_wild_err_arm<'tcx>(cx: &LateContext<'tcx>, ex: &Expr<'tcx>, arms: &[Arm
}
if_chain! {
if matching_wild;
if is_panic_call(arm.body);
if let Some(macro_call) = root_macro_call(peel_blocks_with_stmt(arm.body).span);
if is_panic(cx, macro_call.def_id);
then {
// `Err(_)` or `Err(_e)` arm with `panic!` found
span_lint_and_note(cx,
@ -1179,22 +1181,6 @@ fn check_wild_enum_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>])
};
}
// If the block contains only a `panic!` macro (as expression or statement)
fn is_panic_call(expr: &Expr<'_>) -> bool {
// Unwrap any wrapping blocks
let span = if let ExprKind::Block(block, _) = expr.kind {
match (&block.expr, block.stmts.len(), block.stmts.first()) {
(&Some(exp), 0, _) => exp.span,
(&None, 1, Some(stmt)) => stmt.span,
_ => return false,
}
} else {
expr.span
};
is_expn_of(span, "panic").is_some() && is_expn_of(span, "unreachable").is_none()
}
fn check_match_ref_pats<'a, 'b, I>(cx: &LateContext<'_>, ex: &Expr<'_>, pats: I, expr: &Expr<'_>)
where
'b: 'a,

View file

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::higher::FormatExpn;
use clippy_utils::macros::{root_macro_call_first_node, FormatArgsExpn};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::is_type_diagnostic_item;
use rustc_errors::Applicability;
@ -14,7 +14,13 @@ use super::EXPECT_FUN_CALL;
/// Checks for the `EXPECT_FUN_CALL` lint.
#[allow(clippy::too_many_lines)]
pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, method_span: Span, name: &str, args: &[hir::Expr<'_>]) {
pub(super) fn check(
cx: &LateContext<'tcx>,
expr: &hir::Expr<'_>,
method_span: Span,
name: &str,
args: &'tcx [hir::Expr<'tcx>],
) {
// Strip `&`, `as_ref()` and `as_str()` off `arg` until we're left with either a `String` or
// `&str`
fn get_arg_root<'a>(cx: &LateContext<'_>, arg: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> {
@ -128,11 +134,12 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, method_span: Spa
let mut applicability = Applicability::MachineApplicable;
//Special handling for `format!` as arg_root
if let Some(format_expn) = FormatExpn::parse(arg_root) {
let span = match *format_expn.format_args.value_args {
[] => format_expn.format_args.format_string_span,
[.., last] => format_expn.format_args.format_string_span.to(last.span),
};
if let Some(macro_call) = root_macro_call_first_node(cx, arg_root) {
if !cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id) {
return;
}
let Some(format_args) = FormatArgsExpn::find_nested(cx, arg_root, macro_call.expn) else { return };
let span = format_args.inputs_span();
let sugg = snippet_with_applicability(cx, span, "..", &mut applicability);
span_lint_and_sugg(
cx,

View file

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::{higher, is_direct_expn_of};
use clippy_utils::macros::{find_assert_eq_args, root_macro_call_first_node};
use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
use rustc_hir::{BorrowKind, Expr, ExprKind, MatchSource, Mutability};
use rustc_lint::{LateContext, LateLintPass};
@ -34,26 +34,30 @@ declare_clippy_lint! {
declare_lint_pass!(DebugAssertWithMutCall => [DEBUG_ASSERT_WITH_MUT_CALL]);
const DEBUG_MACRO_NAMES: [&str; 3] = ["debug_assert", "debug_assert_eq", "debug_assert_ne"];
impl<'tcx> LateLintPass<'tcx> for DebugAssertWithMutCall {
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
for dmn in &DEBUG_MACRO_NAMES {
if is_direct_expn_of(e.span, dmn).is_some() {
if let Some(macro_args) = higher::extract_assert_macro_args(e) {
for arg in macro_args {
let mut visitor = MutArgVisitor::new(cx);
visitor.visit_expr(arg);
if let Some(span) = visitor.expr_span() {
span_lint(
cx,
DEBUG_ASSERT_WITH_MUT_CALL,
span,
&format!("do not call a function with mutable arguments inside of `{}!`", dmn),
);
}
}
}
let Some(macro_call) = root_macro_call_first_node(cx, e) else { return };
let macro_name = cx.tcx.item_name(macro_call.def_id);
if !matches!(
macro_name.as_str(),
"debug_assert" | "debug_assert_eq" | "debug_assert_ne"
) {
return;
}
let Some((lhs, rhs, _)) = find_assert_eq_args(cx, e, macro_call.expn) else { return };
for arg in [lhs, rhs] {
let mut visitor = MutArgVisitor::new(cx);
visitor.visit_expr(arg);
if let Some(span) = visitor.expr_span() {
span_lint(
cx,
DEBUG_ASSERT_WITH_MUT_CALL,
span,
&format!(
"do not call a function with mutable arguments inside of `{}!`",
macro_name
),
);
}
}
}

View file

@ -1,8 +1,10 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::macros::root_macro_call_first_node;
use clippy_utils::return_ty;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{find_macro_calls, is_expn_of, return_ty};
use clippy_utils::visitors::expr_visitor_no_bodies;
use rustc_hir as hir;
use rustc_hir::intravisit::FnKind;
use rustc_hir::intravisit::{FnKind, Visitor};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::{sym, Span};
@ -55,19 +57,19 @@ impl<'tcx> LateLintPass<'tcx> for PanicInResultFn {
}
fn lint_impl_body<'tcx>(cx: &LateContext<'tcx>, impl_span: Span, body: &'tcx hir::Body<'tcx>) {
let mut panics = find_macro_calls(
&[
"unimplemented",
"unreachable",
"panic",
"todo",
"assert",
"assert_eq",
"assert_ne",
],
body,
);
panics.retain(|span| is_expn_of(*span, "debug_assert").is_none());
let mut panics = Vec::new();
expr_visitor_no_bodies(|expr| {
let Some(macro_call) = root_macro_call_first_node(cx, expr) else { return true };
if matches!(
&*cx.tcx.item_name(macro_call.def_id).as_str(),
"unimplemented" | "unreachable" | "panic" | "todo" | "assert" | "assert_eq" | "assert_ne"
) {
panics.push(macro_call.span);
return false;
}
true
})
.visit_expr(&body.value);
if !panics.is_empty() {
span_lint_and_then(
cx,

View file

@ -1,10 +1,8 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::{is_expn_of, match_panic_call};
use if_chain::if_chain;
use clippy_utils::macros::{is_panic, root_macro_call_first_node};
use rustc_hir::Expr;
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::Span;
declare_clippy_lint! {
/// ### What it does
@ -78,37 +76,37 @@ declare_lint_pass!(PanicUnimplemented => [UNIMPLEMENTED, UNREACHABLE, TODO, PANI
impl<'tcx> LateLintPass<'tcx> for PanicUnimplemented {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if match_panic_call(cx, expr).is_some()
&& (is_expn_of(expr.span, "debug_assert").is_none() && is_expn_of(expr.span, "assert").is_none())
{
let span = get_outer_span(expr);
if is_expn_of(expr.span, "unimplemented").is_some() {
let Some(macro_call) = root_macro_call_first_node(cx, expr) else { return };
if is_panic(cx, macro_call.def_id) {
span_lint(
cx,
PANIC,
macro_call.span,
"`panic` should not be present in production code",
);
return;
}
match cx.tcx.item_name(macro_call.def_id).as_str() {
"todo" => {
span_lint(
cx,
TODO,
macro_call.span,
"`todo` should not be present in production code",
);
},
"unimplemented" => {
span_lint(
cx,
UNIMPLEMENTED,
span,
macro_call.span,
"`unimplemented` should not be present in production code",
);
} else if is_expn_of(expr.span, "todo").is_some() {
span_lint(cx, TODO, span, "`todo` should not be present in production code");
} else if is_expn_of(expr.span, "unreachable").is_some() {
span_lint(cx, UNREACHABLE, span, "usage of the `unreachable!` macro");
} else if is_expn_of(expr.span, "panic").is_some() {
span_lint(cx, PANIC, span, "`panic` should not be present in production code");
}
}
}
}
fn get_outer_span(expr: &Expr<'_>) -> Span {
if_chain! {
if expr.span.from_expansion();
let first = expr.span.ctxt().outer_expn_data().call_site;
if first.from_expansion();
then {
first.ctxt().outer_expn_data().call_site
} else {
expr.span
},
"unreachable" => {
span_lint(cx, UNREACHABLE, macro_call.span, "usage of the `unreachable!` macro");
},
_ => {},
}
}
}

View file

@ -1,35 +1,29 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::macros::{find_assert_eq_args, root_macro_call_first_node};
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::LateContext;
use rustc_span::hygiene::{ExpnKind, MacroKind};
use super::UNIT_CMP;
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
if expr.span.from_expansion() {
if let Some(callee) = expr.span.source_callee() {
if let ExpnKind::Macro(MacroKind::Bang, symbol) = callee.kind {
if let ExprKind::Binary(ref cmp, left, _) = expr.kind {
let op = cmp.node;
if op.is_comparison() && cx.typeck_results().expr_ty(left).is_unit() {
let result = match symbol.as_str() {
"assert_eq" | "debug_assert_eq" => "succeed",
"assert_ne" | "debug_assert_ne" => "fail",
_ => return,
};
span_lint(
cx,
UNIT_CMP,
expr.span,
&format!(
"`{}` of unit values detected. This will always {}",
symbol.as_str(),
result
),
);
}
}
if let Some(macro_call) = root_macro_call_first_node(cx, expr) {
let macro_name = cx.tcx.item_name(macro_call.def_id);
let result = match macro_name.as_str() {
"assert_eq" | "debug_assert_eq" => "succeed",
"assert_ne" | "debug_assert_ne" => "fail",
_ => return,
};
let Some ((left, _, _)) = find_assert_eq_args(cx, expr, macro_call.expn) else { return };
if !cx.typeck_results().expr_ty(left).is_unit() {
return;
}
span_lint(
cx,
UNIT_CMP,
macro_call.span,
&format!("`{}` of unit values detected. This will always {}", macro_name, result),
);
}
return;
}

View file

@ -1,5 +1,6 @@
use clippy_utils::consts::{constant_simple, Constant};
use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then};
use clippy_utils::macros::root_macro_call_first_node;
use clippy_utils::source::snippet;
use clippy_utils::ty::match_type;
use clippy_utils::{
@ -410,9 +411,13 @@ impl<'tcx> LateLintPass<'tcx> for LintWithoutLintPass {
}
self.declared_lints.insert(item.ident.name, item.span);
}
} else if is_expn_of(item.span, "impl_lint_pass").is_some()
|| is_expn_of(item.span, "declare_lint_pass").is_some()
{
} else if let Some(macro_call) = root_macro_call_first_node(cx, item) {
if !matches!(
&*cx.tcx.item_name(macro_call.def_id).as_str(),
"impl_lint_pass" | "declare_lint_pass"
) {
return;
}
if let hir::ItemKind::Impl(hir::Impl {
of_trait: None,
items: impl_item_refs,

View file

@ -5,6 +5,7 @@ edition = "2021"
publish = false
[dependencies]
arrayvec = { version = "0.7", default-features = false }
if_chain = "1.0"
rustc-semver = "1.1"

View file

@ -5,7 +5,6 @@
#![allow(clippy::similar_names, clippy::wildcard_imports, clippy::enum_glob_use)]
use crate::{both, over};
use if_chain::if_chain;
use rustc_ast::ptr::P;
use rustc_ast::{self as ast, *};
use rustc_span::symbol::Ident;
@ -679,34 +678,3 @@ pub fn eq_mac_args(l: &MacArgs, r: &MacArgs) -> bool {
_ => false,
}
}
/// Extract args from an assert-like macro.
///
/// Currently working with:
/// - `assert_eq!` and `assert_ne!`
/// - `debug_assert_eq!` and `debug_assert_ne!`
///
/// For example:
///
/// `debug_assert_eq!(a, b)` will return Some([a, b])
pub fn extract_assert_macro_args(mut expr: &Expr) -> Option<[&Expr; 2]> {
if_chain! {
if let ExprKind::If(_, ref block, _) = expr.kind;
if let StmtKind::Semi(ref e) = block.stmts.get(0)?.kind;
then {
expr = e;
}
}
if_chain! {
if let ExprKind::Block(ref block, _) = expr.kind;
if let StmtKind::Expr(ref expr) = block.stmts.get(0)?.kind;
if let ExprKind::Match(ref match_expr, _) = expr.kind;
if let ExprKind::Tup(ref tup) = match_expr.kind;
if let [a, b, ..] = tup.as_slice();
if let (&ExprKind::AddrOf(_, _, ref a), &ExprKind::AddrOf(_, _, ref b)) = (&a.kind, &b.kind);
then {
return Some([&*a, &*b]);
}
}
None
}

View file

@ -3,15 +3,13 @@
#![deny(clippy::missing_docs_in_private_items)]
use crate::ty::is_type_diagnostic_item;
use crate::{is_expn_of, last_path_segment, match_def_path, paths};
use crate::{is_expn_of, match_def_path, paths};
use if_chain::if_chain;
use rustc_ast::ast::{self, LitKind};
use rustc_hir as hir;
use rustc_hir::{
Arm, Block, BorrowKind, Expr, ExprKind, HirId, LoopSource, MatchSource, Node, Pat, QPath, StmtKind, UnOp,
};
use rustc_hir::{Arm, Block, Expr, ExprKind, HirId, LoopSource, MatchSource, Node, Pat, QPath};
use rustc_lint::LateContext;
use rustc_span::{sym, symbol, ExpnKind, Span, Symbol};
use rustc_span::{sym, symbol, Span};
/// The essential nodes of a desugared for loop as well as the entire span:
/// `for pat in arg { body }` becomes `(pat, arg, body)`. Return `(pat, arg, body, span)`.
@ -428,293 +426,6 @@ pub fn binop(op: hir::BinOpKind) -> ast::BinOpKind {
}
}
/// Extract args from an assert-like macro.
/// Currently working with:
/// - `assert!`, `assert_eq!` and `assert_ne!`
/// - `debug_assert!`, `debug_assert_eq!` and `debug_assert_ne!`
/// For example:
/// `assert!(expr)` will return `Some([expr])`
/// `debug_assert_eq!(a, b)` will return `Some([a, b])`
pub fn extract_assert_macro_args<'tcx>(e: &'tcx Expr<'tcx>) -> Option<Vec<&'tcx Expr<'tcx>>> {
/// Try to match the AST for a pattern that contains a match, for example when two args are
/// compared
fn ast_matchblock(matchblock_expr: &'tcx Expr<'tcx>) -> Option<Vec<&Expr<'_>>> {
if_chain! {
if let ExprKind::Match(headerexpr, _, _) = &matchblock_expr.kind;
if let ExprKind::Tup([lhs, rhs]) = &headerexpr.kind;
if let ExprKind::AddrOf(BorrowKind::Ref, _, lhs) = lhs.kind;
if let ExprKind::AddrOf(BorrowKind::Ref, _, rhs) = rhs.kind;
then {
return Some(vec![lhs, rhs]);
}
}
None
}
if let ExprKind::Block(block, _) = e.kind {
if block.stmts.len() == 1 {
if let StmtKind::Semi(matchexpr) = block.stmts.get(0)?.kind {
// macros with unique arg: `{debug_}assert!` (e.g., `debug_assert!(some_condition)`)
if_chain! {
if let Some(If { cond, .. }) = If::hir(matchexpr);
if let ExprKind::Unary(UnOp::Not, condition) = cond.kind;
then {
return Some(vec![condition]);
}
}
// debug macros with two args: `debug_assert_{ne, eq}` (e.g., `assert_ne!(a, b)`)
if_chain! {
if let ExprKind::Block(matchblock,_) = matchexpr.kind;
if let Some(matchblock_expr) = matchblock.expr;
then {
return ast_matchblock(matchblock_expr);
}
}
}
} else if let Some(matchblock_expr) = block.expr {
// macros with two args: `assert_{ne, eq}` (e.g., `assert_ne!(a, b)`)
return ast_matchblock(matchblock_expr);
}
}
None
}
/// A parsed `format!` expansion
pub struct FormatExpn<'tcx> {
/// Span of `format!(..)`
pub call_site: Span,
/// Inner `format_args!` expansion
pub format_args: FormatArgsExpn<'tcx>,
}
impl FormatExpn<'tcx> {
/// Parses an expanded `format!` invocation
pub fn parse(expr: &'tcx Expr<'tcx>) -> Option<Self> {
if_chain! {
if let ExprKind::Block(block, _) = expr.kind;
if let [stmt] = block.stmts;
if let StmtKind::Local(local) = stmt.kind;
if let Some(init) = local.init;
if let ExprKind::Call(_, [format_args]) = init.kind;
let expn_data = expr.span.ctxt().outer_expn_data();
if let ExpnKind::Macro(_, sym::format) = expn_data.kind;
if let Some(format_args) = FormatArgsExpn::parse(format_args);
then {
Some(FormatExpn {
call_site: expn_data.call_site,
format_args,
})
} else {
None
}
}
}
}
/// A parsed `format_args!` expansion
pub struct FormatArgsExpn<'tcx> {
/// Span of the first argument, the format string
pub format_string_span: Span,
/// Values passed after the format string
pub value_args: Vec<&'tcx Expr<'tcx>>,
/// String literal expressions which represent the format string split by "{}"
pub format_string_parts: &'tcx [Expr<'tcx>],
/// Symbols corresponding to [`Self::format_string_parts`]
pub format_string_symbols: Vec<Symbol>,
/// Expressions like `ArgumentV1::new(arg0, Debug::fmt)`
pub args: &'tcx [Expr<'tcx>],
/// The final argument passed to `Arguments::new_v1_formatted`, if applicable
pub fmt_expr: Option<&'tcx Expr<'tcx>>,
}
impl FormatArgsExpn<'tcx> {
/// Parses an expanded `format_args!` or `format_args_nl!` invocation
pub fn parse(expr: &'tcx Expr<'tcx>) -> Option<Self> {
if_chain! {
if let ExpnKind::Macro(_, name) = expr.span.ctxt().outer_expn_data().kind;
let name = name.as_str();
if name.ends_with("format_args") || name.ends_with("format_args_nl");
if let ExprKind::Call(_, args) = expr.kind;
if let Some((strs_ref, args, fmt_expr)) = match args {
// Arguments::new_v1
[strs_ref, args] => Some((strs_ref, args, None)),
// Arguments::new_v1_formatted
[strs_ref, args, fmt_expr, _unsafe_arg] => Some((strs_ref, args, Some(fmt_expr))),
_ => None,
};
if let ExprKind::AddrOf(BorrowKind::Ref, _, strs_arr) = strs_ref.kind;
if let ExprKind::Array(format_string_parts) = strs_arr.kind;
if let Some(format_string_symbols) = format_string_parts
.iter()
.map(|e| {
if let ExprKind::Lit(lit) = &e.kind {
if let LitKind::Str(symbol, _style) = lit.node {
return Some(symbol);
}
}
None
})
.collect();
if let ExprKind::AddrOf(BorrowKind::Ref, _, args) = args.kind;
if let ExprKind::Match(args, [arm], _) = args.kind;
if let ExprKind::Tup(value_args) = args.kind;
if let Some(value_args) = value_args
.iter()
.map(|e| match e.kind {
ExprKind::AddrOf(_, _, e) => Some(e),
_ => None,
})
.collect();
if let ExprKind::Array(args) = arm.body.kind;
then {
Some(FormatArgsExpn {
format_string_span: strs_ref.span,
value_args,
format_string_parts,
format_string_symbols,
args,
fmt_expr,
})
} else {
None
}
}
}
/// Returns a vector of `FormatArgsArg`.
pub fn args(&self) -> Option<Vec<FormatArgsArg<'tcx>>> {
if let Some(expr) = self.fmt_expr {
if_chain! {
if let ExprKind::AddrOf(BorrowKind::Ref, _, expr) = expr.kind;
if let ExprKind::Array(exprs) = expr.kind;
then {
exprs.iter().map(|fmt| {
if_chain! {
// struct `core::fmt::rt::v1::Argument`
if let ExprKind::Struct(_, fields, _) = fmt.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);
let arg = &self.args[i];
if let ExprKind::Call(_, [arg_name, _]) = arg.kind;
if let ExprKind::Field(_, j) = arg_name.kind;
if let Ok(j) = j.name.as_str().parse::<usize>();
then {
Some(FormatArgsArg { value: self.value_args[j], arg, fmt: Some(fmt) })
} else {
None
}
}
}).collect()
} else {
None
}
}
} else {
Some(
self.value_args
.iter()
.zip(self.args.iter())
.map(|(value, arg)| FormatArgsArg { value, arg, fmt: None })
.collect(),
)
}
}
}
/// 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 arg: &'tcx Expr<'tcx>,
/// An element of `fmt_expn`
pub fmt: 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.fmt.map_or(false, |fmt| {
// `!` because these conditions check that `self` is unformatted.
!if_chain! {
// struct `core::fmt::rt::v1::Argument`
if let ExprKind::Struct(_, fields, _) = fmt.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;
let mut precision_found = false;
let mut width_found = false;
if subfields.iter().all(|field| {
match field.ident.name {
sym::precision => {
precision_found = true;
if let ExprKind::Path(ref precision_path) = field.expr.kind {
last_path_segment(precision_path).ident.name == sym::Implied
} else {
false
}
}
sym::width => {
width_found = true;
if let ExprKind::Path(ref width_qpath) = field.expr.kind {
last_path_segment(width_qpath).ident.name == sym::Implied
} else {
false
}
}
_ => true,
}
});
if precision_found && width_found;
then { true } else { false }
}
})
}
/// Returns true if the argument is formatted using `Display::fmt`.
pub fn is_display(&self) -> bool {
if_chain! {
if let ExprKind::Call(_, [_, format_field]) = self.arg.kind;
if let ExprKind::Path(QPath::Resolved(_, path)) = format_field.kind;
if let [.., t, _] = path.segments;
if t.ident.name == sym::Display;
then { true } else { false }
}
}
}
/// A parsed `panic!` expansion
pub struct PanicExpn<'tcx> {
/// Span of `panic!(..)`
pub call_site: Span,
/// Inner `format_args!` expansion
pub format_args: FormatArgsExpn<'tcx>,
}
impl PanicExpn<'tcx> {
/// Parses an expanded `panic!` invocation
pub fn parse(expr: &'tcx Expr<'tcx>) -> Option<Self> {
if_chain! {
if let ExprKind::Call(_, [format_args]) = expr.kind;
let expn_data = expr.span.ctxt().outer_expn_data();
if let Some(format_args) = FormatArgsExpn::parse(format_args);
then {
Some(PanicExpn {
call_site: expn_data.call_site,
format_args,
})
} else {
None
}
}
}
}
/// A parsed `Vec` initialization expression
#[derive(Clone, Copy)]
pub enum VecInitKind {

View file

@ -44,6 +44,7 @@ pub mod diagnostics;
pub mod eager_or_lazy;
pub mod higher;
mod hir_utils;
pub mod macros;
pub mod msrvs;
pub mod numeric_literal;
pub mod paths;
@ -1159,19 +1160,6 @@ pub fn contains_return(expr: &hir::Expr<'_>) -> bool {
found
}
/// Finds calls of the specified macros in a function body.
pub fn find_macro_calls(names: &[&str], body: &Body<'_>) -> Vec<Span> {
let mut result = Vec::new();
expr_visitor_no_bodies(|expr| {
if names.iter().any(|fun| is_expn_of(expr.span, fun).is_some()) {
result.push(expr.span);
}
true
})
.visit_expr(&body.value);
result
}
/// Extends the span to the beginning of the spans line, incl. whitespaces.
///
/// ```rust
@ -1700,32 +1688,6 @@ pub fn match_libc_symbol(cx: &LateContext<'_>, did: DefId, name: &str) -> bool {
path.first().map_or(false, |s| s.as_str() == "libc") && path.last().map_or(false, |s| s.as_str() == name)
}
pub fn match_panic_call(cx: &LateContext<'_>, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
if let ExprKind::Call(func, [arg]) = expr.kind {
expr_path_res(cx, func)
.opt_def_id()
.map_or(false, |id| match_panic_def_id(cx, id))
.then(|| arg)
} else {
None
}
}
pub fn match_panic_def_id(cx: &LateContext<'_>, did: DefId) -> bool {
match_any_def_paths(
cx,
did,
&[
&paths::BEGIN_PANIC,
&paths::PANIC_ANY,
&paths::PANICKING_PANIC,
&paths::PANICKING_PANIC_FMT,
&paths::PANICKING_PANIC_STR,
],
)
.is_some()
}
/// Returns the list of condition expressions and the list of blocks in a
/// sequence of `if/else`.
/// E.g., this returns `([a, b], [c, d, e])` for the expression

539
clippy_utils/src/macros.rs Normal file
View file

@ -0,0 +1,539 @@
#![allow(clippy::similar_names)] // `expr` and `expn`
use crate::visitors::expr_visitor_no_bodies;
use arrayvec::ArrayVec;
use if_chain::if_chain;
use rustc_ast::ast::LitKind;
use rustc_hir::intravisit::Visitor;
use rustc_hir::{self as hir, Expr, ExprKind, HirId, Node, QPath};
use rustc_lint::LateContext;
use rustc_span::def_id::DefId;
use rustc_span::hygiene::{MacroKind, SyntaxContext};
use rustc_span::{sym, ExpnData, ExpnId, ExpnKind, Span, Symbol};
use std::ops::ControlFlow;
/// A macro call, like `vec![1, 2, 3]`.
///
/// Use `tcx.item_name(macro_call.def_id)` to get the macro name.
/// Even better is to check if it is a diagnostic item.
///
/// This structure is similar to `ExpnData` but it precludes desugaring expansions.
#[derive(Debug)]
pub struct MacroCall {
/// Macro `DefId`
pub def_id: DefId,
/// Kind of macro
pub kind: MacroKind,
/// The expansion produced by the macro call
pub expn: ExpnId,
/// Span of the macro call site
pub span: Span,
}
impl MacroCall {
pub fn is_local(&self) -> bool {
span_is_local(self.span)
}
}
/// Returns an iterator of expansions that created the given span
pub fn expn_backtrace(mut span: Span) -> impl Iterator<Item = (ExpnId, ExpnData)> {
std::iter::from_fn(move || {
let ctxt = span.ctxt();
if ctxt == SyntaxContext::root() {
return None;
}
let expn = ctxt.outer_expn();
let data = expn.expn_data();
span = data.call_site;
Some((expn, data))
})
}
/// Checks whether the span is from the root expansion or a locally defined macro
pub fn span_is_local(span: Span) -> bool {
!span.from_expansion() || expn_is_local(span.ctxt().outer_expn())
}
/// Checks whether the expansion is the root expansion or a locally defined macro
pub fn expn_is_local(expn: ExpnId) -> bool {
if expn == ExpnId::root() {
return true;
}
let data = expn.expn_data();
let backtrace = expn_backtrace(data.call_site);
std::iter::once((expn, data))
.chain(backtrace)
.find_map(|(_, data)| data.macro_def_id)
.map_or(true, DefId::is_local)
}
/// Returns an iterator of macro expansions that created the given span.
/// Note that desugaring expansions are skipped.
pub fn macro_backtrace(span: Span) -> impl Iterator<Item = MacroCall> {
expn_backtrace(span).filter_map(|(expn, data)| match data {
ExpnData {
kind: ExpnKind::Macro(kind, _),
macro_def_id: Some(def_id),
call_site: span,
..
} => Some(MacroCall {
def_id,
kind,
expn,
span,
}),
_ => None,
})
}
/// If the macro backtrace of `span` has a macro call at the root expansion
/// (i.e. not a nested macro call), returns `Some` with the `MacroCall`
pub fn root_macro_call(span: Span) -> Option<MacroCall> {
macro_backtrace(span).last()
}
/// Like [`root_macro_call`], but only returns `Some` if `node` is the "first node"
/// produced by the macro call, as in [`first_node_in_macro`].
pub fn root_macro_call_first_node(cx: &LateContext<'_>, node: &impl HirNode) -> Option<MacroCall> {
if first_node_in_macro(cx, node) != Some(ExpnId::root()) {
return None;
}
root_macro_call(node.span())
}
/// Like [`macro_backtrace`], but only returns macro calls where `node` is the "first node" of the
/// macro call, as in [`first_node_in_macro`].
pub fn first_node_macro_backtrace(cx: &LateContext<'_>, node: &impl HirNode) -> impl Iterator<Item = MacroCall> {
let span = node.span();
first_node_in_macro(cx, node)
.into_iter()
.flat_map(move |expn| macro_backtrace(span).take_while(move |macro_call| macro_call.expn != expn))
}
/// If `node` is the "first node" in a macro expansion, returns `Some` with the `ExpnId` of the
/// macro call site (i.e. the parent of the macro expansion). This generally means that `node`
/// is the outermost node of an entire macro expansion, but there are some caveats noted below.
/// This is useful for finding macro calls while visiting the HIR without processing the macro call
/// at every node within its expansion.
///
/// If you already have immediate access to the parent node, it is simpler to
/// just check the context of that span directly (e.g. `parent.span.from_expansion()`).
///
/// If a macro call is in statement position, it expands to one or more statements.
/// In that case, each statement *and* their immediate descendants will all yield `Some`
/// with the `ExpnId` of the containing block.
///
/// A node may be the "first node" of multiple macro calls in a macro backtrace.
/// The expansion of the outermost macro call site is returned in such cases.
pub fn first_node_in_macro(cx: &LateContext<'_>, node: &impl HirNode) -> Option<ExpnId> {
// get the macro expansion or return `None` if not found
// `macro_backtrace` importantly ignores desugaring expansions
let expn = macro_backtrace(node.span()).next()?.expn;
// get the parent node, possibly skipping over a statement
// if the parent is not found, it is sensible to return `Some(root)`
let hir = cx.tcx.hir();
let mut parent_iter = hir.parent_iter(node.hir_id());
let (parent_id, _) = match parent_iter.next() {
None => return Some(ExpnId::root()),
Some((_, Node::Stmt(_))) => match parent_iter.next() {
None => return Some(ExpnId::root()),
Some(next) => next,
},
Some(next) => next,
};
// get the macro expansion of the parent node
let parent_span = hir.span(parent_id);
let Some(parent_macro_call) = macro_backtrace(parent_span).next() else {
// the parent node is not in a macro
return Some(ExpnId::root());
};
if parent_macro_call.expn.is_descendant_of(expn) {
// `node` is input to a macro call
return None;
}
Some(parent_macro_call.expn)
}
/* Specific Macro Utils */
/// Is `def_id` of `std::panic`, `core::panic` or any inner implementation macros
pub fn is_panic(cx: &LateContext<'_>, def_id: DefId) -> bool {
let Some(name) = cx.tcx.get_diagnostic_name(def_id) else { return false };
matches!(
name.as_str(),
"core_panic_macro"
| "std_panic_macro"
| "core_panic_2015_macro"
| "std_panic_2015_macro"
| "core_panic_2021_macro"
)
}
pub enum PanicExpn<'a> {
/// No arguments - `panic!()`
Empty,
/// A string literal or any `&str` - `panic!("message")` or `panic!(message)`
Str(&'a Expr<'a>),
/// A single argument that implements `Display` - `panic!("{}", object)`
Display(&'a Expr<'a>),
/// Anything else - `panic!("error {}: {}", a, b)`
Format(FormatArgsExpn<'a>),
}
impl<'a> PanicExpn<'a> {
pub fn parse(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<Self> {
if !macro_backtrace(expr.span).any(|macro_call| is_panic(cx, macro_call.def_id)) {
return None;
}
let ExprKind::Call(callee, [arg]) = expr.kind else { return None };
let ExprKind::Path(QPath::Resolved(_, path)) = callee.kind else { return None };
let result = match path.segments.last().unwrap().ident.as_str() {
"panic" if arg.span.ctxt() == expr.span.ctxt() => Self::Empty,
"panic" | "panic_str" => Self::Str(arg),
"panic_display" => {
let ExprKind::AddrOf(_, _, e) = arg.kind else { return None };
Self::Display(e)
},
"panic_fmt" => Self::Format(FormatArgsExpn::parse(cx, arg)?),
_ => return None,
};
Some(result)
}
}
/// Finds the arguments of an `assert!` or `debug_assert!` macro call within the macro expansion
pub fn find_assert_args<'a>(
cx: &LateContext<'_>,
expr: &'a Expr<'a>,
expn: ExpnId,
) -> Option<(&'a Expr<'a>, PanicExpn<'a>)> {
find_assert_args_inner(cx, expr, expn).map(|([e], p)| (e, p))
}
/// Finds the arguments of an `assert_eq!` or `debug_assert_eq!` macro call within the macro
/// expansion
pub fn find_assert_eq_args<'a>(
cx: &LateContext<'_>,
expr: &'a Expr<'a>,
expn: ExpnId,
) -> Option<(&'a Expr<'a>, &'a Expr<'a>, PanicExpn<'a>)> {
find_assert_args_inner(cx, expr, expn).map(|([a, b], p)| (a, b, p))
}
fn find_assert_args_inner<'a, const N: usize>(
cx: &LateContext<'_>,
expr: &'a Expr<'a>,
expn: ExpnId,
) -> Option<([&'a Expr<'a>; N], PanicExpn<'a>)> {
let macro_id = expn.expn_data().macro_def_id?;
let (expr, expn) = match cx.tcx.item_name(macro_id).as_str().strip_prefix("debug_") {
None => (expr, expn),
Some(inner_name) => find_assert_within_debug_assert(cx, expr, expn, Symbol::intern(inner_name))?,
};
let mut args = ArrayVec::new();
let mut panic_expn = None;
expr_visitor_no_bodies(|e| {
if args.is_full() {
if panic_expn.is_none() && e.span.ctxt() != expr.span.ctxt() {
panic_expn = PanicExpn::parse(cx, e);
}
panic_expn.is_none()
} else if is_assert_arg(cx, e, expn) {
args.push(e);
false
} else {
true
}
})
.visit_expr(expr);
let args = args.into_inner().ok()?;
// if no `panic!(..)` is found, use `PanicExpn::Empty`
// to indicate that the default assertion message is used
let panic_expn = panic_expn.unwrap_or(PanicExpn::Empty);
Some((args, panic_expn))
}
fn find_assert_within_debug_assert<'a>(
cx: &LateContext<'_>,
expr: &'a Expr<'a>,
expn: ExpnId,
assert_name: Symbol,
) -> Option<(&'a Expr<'a>, ExpnId)> {
let mut found = None;
expr_visitor_no_bodies(|e| {
if found.is_some() || !e.span.from_expansion() {
return false;
}
let e_expn = e.span.ctxt().outer_expn();
if e_expn == expn {
return true;
}
if e_expn.expn_data().macro_def_id.map(|id| cx.tcx.item_name(id)) == Some(assert_name) {
found = Some((e, e_expn));
}
false
})
.visit_expr(expr);
found
}
fn is_assert_arg(cx: &LateContext<'_>, expr: &'a Expr<'a>, assert_expn: ExpnId) -> bool {
if !expr.span.from_expansion() {
return true;
}
let result = macro_backtrace(expr.span).try_for_each(|macro_call| {
if macro_call.expn == assert_expn {
ControlFlow::Break(false)
} else {
match cx.tcx.item_name(macro_call.def_id) {
// `cfg!(debug_assertions)` in `debug_assert!`
sym::cfg => ControlFlow::CONTINUE,
// assert!(other_macro!(..))
_ => ControlFlow::Break(true),
}
}
});
match result {
ControlFlow::Break(is_assert_arg) => is_assert_arg,
ControlFlow::Continue(()) => true,
}
}
/// A parsed `format_args!` expansion
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>>,
}
impl 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() {
// ArgumnetV1::new(<value>, <format_trait>::fmt)
if_chain! {
if let ExprKind::Call(callee, [val, fmt_path]) = e.kind;
if let ExprKind::Path(QPath::TypeRelative(ty, seg)) = callee.kind;
if seg.ident.name == sym::new;
if let hir::TyKind::Path(QPath::Resolved(_, path)) = ty.kind;
if path.segments.last().unwrap().ident.name == sym::ArgumentV1;
if let ExprKind::Path(QPath::Resolved(_, path)) = fmt_path.kind;
if let [.., fmt_trait, _fmt] = path.segments;
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()
}
};
formatters.push((val_idx, fmt_trait.ident.name));
}
}
if let ExprKind::Struct(QPath::Resolved(_, path), ..) = e.kind {
if path.segments.last().unwrap().ident.name == sym::Argument {
specs.push(e);
}
}
// 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);
} else {
// assume that any further exprs with a differing context are value args
value_args.push(e);
}
// 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,
value_args,
formatters,
specs,
})
}
/// Finds a nested call to `format_args!` within a `format!`-like macro call
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| {
if format_args.is_some() {
return false;
}
let e_ctxt = e.span.ctxt();
if e_ctxt == expr.span.ctxt() {
return true;
}
if e_ctxt.outer_expn().is_descendant_of(expn_id) {
format_args = FormatArgsExpn::parse(cx, e);
}
false
})
.visit_expr(expr);
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()
}
/// Span of all inputs
pub fn inputs_span(&self) -> Span {
match *self.value_args {
[] => self.format_string_span,
[.., last] => self.format_string_span.to(last.span),
}
}
}
/// 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 }
}
})
}
}
/// A node with a `HirId` and a `Span`
pub trait HirNode {
fn hir_id(&self) -> HirId;
fn span(&self) -> Span;
}
macro_rules! impl_hir_node {
($($t:ident),*) => {
$(impl HirNode for hir::$t<'_> {
fn hir_id(&self) -> HirId {
self.hir_id
}
fn span(&self) -> Span {
self.span
}
})*
};
}
impl_hir_node!(Expr, Pat);
impl HirNode for hir::Item<'_> {
fn hir_id(&self) -> HirId {
self.hir_id()
}
fn span(&self) -> Span {
self.span
}
}

View file

@ -25,7 +25,6 @@ pub const ASSERT_MACRO: [&str; 4] = ["core", "macros", "builtin", "assert"];
pub const ASSERT_NE_MACRO: [&str; 3] = ["core", "macros", "assert_ne"];
pub const ASMUT_TRAIT: [&str; 3] = ["core", "convert", "AsMut"];
pub const ASREF_TRAIT: [&str; 3] = ["core", "convert", "AsRef"];
pub(super) const BEGIN_PANIC: [&str; 3] = ["std", "panicking", "begin_panic"];
/// Preferably use the diagnostic item `sym::Borrow` where possible
pub const BORROW_TRAIT: [&str; 3] = ["core", "borrow", "Borrow"];
pub const BORROW_MUT_TRAIT: [&str; 3] = ["core", "borrow", "BorrowMut"];
@ -110,10 +109,6 @@ pub const OPTION_SOME: [&str; 4] = ["core", "option", "Option", "Some"];
pub const ORD: [&str; 3] = ["core", "cmp", "Ord"];
pub const OS_STRING_AS_OS_STR: [&str; 5] = ["std", "ffi", "os_str", "OsString", "as_os_str"];
pub const OS_STR_TO_OS_STRING: [&str; 5] = ["std", "ffi", "os_str", "OsStr", "to_os_string"];
pub(super) const PANICKING_PANIC: [&str; 3] = ["core", "panicking", "panic"];
pub(super) const PANICKING_PANIC_FMT: [&str; 3] = ["core", "panicking", "panic_fmt"];
pub(super) const PANICKING_PANIC_STR: [&str; 3] = ["core", "panicking", "panic_str"];
pub(super) const PANIC_ANY: [&str; 3] = ["std", "panic", "panic_any"];
pub const PARKING_LOT_RAWMUTEX: [&str; 3] = ["parking_lot", "raw_mutex", "RawMutex"];
pub const PARKING_LOT_RAWRWLOCK: [&str; 3] = ["parking_lot", "raw_rwlock", "RawRwLock"];
pub const PARKING_LOT_MUTEX_GUARD: [&str; 2] = ["parking_lot", "MutexGuard"];

View file

@ -1,4 +1,3 @@
//FIXME: suggestions are wrongly expanded, this should be fixed along with #7843
#![allow(non_fmt_panics)]
macro_rules! assert_const {

View file

@ -1,75 +1,75 @@
error: `assert!(true)` will be optimized out by the compiler
--> $DIR/assertions_on_constants.rs:11:5
--> $DIR/assertions_on_constants.rs:10:5
|
LL | assert!(true);
| ^^^^^^^^^^^^^
|
= note: `-D clippy::assertions-on-constants` implied by `-D warnings`
= help: remove it
= note: this error originates in the macro `assert` (in Nightly builds, run with -Z macro-backtrace for more info)
error: `assert!(false)` should probably be replaced
--> $DIR/assertions_on_constants.rs:12:5
--> $DIR/assertions_on_constants.rs:11:5
|
LL | assert!(false);
| ^^^^^^^^^^^^^^
|
= help: use `panic!()` or `unreachable!()`
= note: this error originates in the macro `assert` (in Nightly builds, run with -Z macro-backtrace for more info)
error: `assert!(true)` will be optimized out by the compiler
--> $DIR/assertions_on_constants.rs:13:5
--> $DIR/assertions_on_constants.rs:12:5
|
LL | assert!(true, "true message");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: remove it
= note: this error originates in the macro `assert` (in Nightly builds, run with -Z macro-backtrace for more info)
error: `assert!(false, $crate::const_format_args!($($t)+))` should probably be replaced
--> $DIR/assertions_on_constants.rs:14:5
error: `assert!(false, ..)` should probably be replaced
--> $DIR/assertions_on_constants.rs:13:5
|
LL | assert!(false, "false message");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: use `panic!($crate::const_format_args!($($t)+))` or `unreachable!($crate::const_format_args!($($t)+))`
= note: this error originates in the macro `assert` (in Nightly builds, run with -Z macro-backtrace for more info)
= help: use `panic!(..)` or `unreachable!(..)`
error: `assert!(false, ..)` should probably be replaced
--> $DIR/assertions_on_constants.rs:16:5
|
LL | assert!(false, "{}", msg.to_uppercase());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: use `panic!(..)` or `unreachable!(..)`
error: `assert!(true)` will be optimized out by the compiler
--> $DIR/assertions_on_constants.rs:20:5
--> $DIR/assertions_on_constants.rs:19:5
|
LL | assert!(B);
| ^^^^^^^^^^
|
= help: remove it
= note: this error originates in the macro `assert` (in Nightly builds, run with -Z macro-backtrace for more info)
error: `assert!(false)` should probably be replaced
--> $DIR/assertions_on_constants.rs:23:5
--> $DIR/assertions_on_constants.rs:22:5
|
LL | assert!(C);
| ^^^^^^^^^^
|
= help: use `panic!()` or `unreachable!()`
= note: this error originates in the macro `assert` (in Nightly builds, run with -Z macro-backtrace for more info)
error: `assert!(false, $crate::const_format_args!($($t)+))` should probably be replaced
--> $DIR/assertions_on_constants.rs:24:5
error: `assert!(false, ..)` should probably be replaced
--> $DIR/assertions_on_constants.rs:23:5
|
LL | assert!(C, "C message");
| ^^^^^^^^^^^^^^^^^^^^^^^
|
= help: use `panic!($crate::const_format_args!($($t)+))` or `unreachable!($crate::const_format_args!($($t)+))`
= note: this error originates in the macro `assert` (in Nightly builds, run with -Z macro-backtrace for more info)
= help: use `panic!(..)` or `unreachable!(..)`
error: `debug_assert!(true)` will be optimized out by the compiler
--> $DIR/assertions_on_constants.rs:26:5
--> $DIR/assertions_on_constants.rs:25:5
|
LL | debug_assert!(true);
| ^^^^^^^^^^^^^^^^^^^
|
= help: remove it
= note: this error originates in the macro `$crate::assert` (in Nightly builds, run with -Z macro-backtrace for more info)
error: aborting due to 8 previous errors
error: aborting due to 9 previous errors

View file

@ -21,6 +21,28 @@ LL | assert_in_macro_def!();
|
= note: this error originates in the macro `assert_in_macro_def` (in Nightly builds, run with -Z macro-backtrace for more info)
error: identical args used in this `debug_assert_eq!` macro call
--> $DIR/eq_op_macros.rs:9:26
|
LL | debug_assert_eq!(a, a);
| ^^^^
...
LL | assert_in_macro_def!();
| ---------------------- in this macro invocation
|
= note: this error originates in the macro `assert_in_macro_def` (in Nightly builds, run with -Z macro-backtrace for more info)
error: identical args used in this `debug_assert_ne!` macro call
--> $DIR/eq_op_macros.rs:10:26
|
LL | debug_assert_ne!(a, a);
| ^^^^
...
LL | assert_in_macro_def!();
| ---------------------- in this macro invocation
|
= note: this error originates in the macro `assert_in_macro_def` (in Nightly builds, run with -Z macro-backtrace for more info)
error: identical args used in this `assert_eq!` macro call
--> $DIR/eq_op_macros.rs:22:16
|
@ -45,28 +67,6 @@ error: identical args used in this `assert_ne!` macro call
LL | assert_ne!(a + 1, a + 1);
| ^^^^^^^^^^^^
error: identical args used in this `debug_assert_eq!` macro call
--> $DIR/eq_op_macros.rs:9:26
|
LL | debug_assert_eq!(a, a);
| ^^^^
...
LL | assert_in_macro_def!();
| ---------------------- in this macro invocation
|
= note: this error originates in the macro `assert_in_macro_def` (in Nightly builds, run with -Z macro-backtrace for more info)
error: identical args used in this `debug_assert_ne!` macro call
--> $DIR/eq_op_macros.rs:10:26
|
LL | debug_assert_ne!(a, a);
| ^^^^
...
LL | assert_in_macro_def!();
| ---------------------- in this macro invocation
|
= note: this error originates in the macro `assert_in_macro_def` (in Nightly builds, run with -Z macro-backtrace for more info)
error: identical args used in this `debug_assert_eq!` macro call
--> $DIR/eq_op_macros.rs:38:22
|

View file

@ -27,7 +27,6 @@ note: first possible panic found here
|
LL | panic!("This function panics")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: this error originates in the macro `$crate::panic::panic_2021` (in Nightly builds, run with -Z macro-backtrace for more info)
error: docs for function which may panic missing `# Panics` section
--> $DIR/missing_panics_doc.rs:17:1
@ -42,7 +41,6 @@ note: first possible panic found here
|
LL | todo!()
| ^^^^^^^
= note: this error originates in the macro `todo` (in Nightly builds, run with -Z macro-backtrace for more info)
error: docs for function which may panic missing `# Panics` section
--> $DIR/missing_panics_doc.rs:22:1
@ -61,7 +59,6 @@ note: first possible panic found here
|
LL | panic!()
| ^^^^^^^^
= note: this error originates in the macro `$crate::panic::panic_2021` (in Nightly builds, run with -Z macro-backtrace for more info)
error: docs for function which may panic missing `# Panics` section
--> $DIR/missing_panics_doc.rs:31:1
@ -76,7 +73,6 @@ note: first possible panic found here
|
LL | if true { unreachable!() } else { panic!() }
| ^^^^^^^^
= note: this error originates in the macro `$crate::panic::panic_2021` (in Nightly builds, run with -Z macro-backtrace for more info)
error: docs for function which may panic missing `# Panics` section
--> $DIR/missing_panics_doc.rs:36:1
@ -92,7 +88,6 @@ note: first possible panic found here
|
LL | assert_eq!(x, 0);
| ^^^^^^^^^^^^^^^^
= note: this error originates in the macro `assert_eq` (in Nightly builds, run with -Z macro-backtrace for more info)
error: docs for function which may panic missing `# Panics` section
--> $DIR/missing_panics_doc.rs:42:1
@ -108,7 +103,6 @@ note: first possible panic found here
|
LL | assert_ne!(x, 0);
| ^^^^^^^^^^^^^^^^
= note: this error originates in the macro `assert_ne` (in Nightly builds, run with -Z macro-backtrace for more info)
error: aborting due to 7 previous errors

View file

@ -14,7 +14,6 @@ note: return Err() instead of panicking
|
LL | panic!("error");
| ^^^^^^^^^^^^^^^
= note: this error originates in the macro `$crate::panic::panic_2021` (in Nightly builds, run with -Z macro-backtrace for more info)
error: used `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertion in a function that returns `Result`
--> $DIR/panic_in_result_fn.rs:11:5
@ -31,7 +30,6 @@ note: return Err() instead of panicking
|
LL | unimplemented!();
| ^^^^^^^^^^^^^^^^
= note: this error originates in the macro `unimplemented` (in Nightly builds, run with -Z macro-backtrace for more info)
error: used `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertion in a function that returns `Result`
--> $DIR/panic_in_result_fn.rs:16:5
@ -48,7 +46,6 @@ note: return Err() instead of panicking
|
LL | unreachable!();
| ^^^^^^^^^^^^^^
= note: this error originates in the macro `unreachable` (in Nightly builds, run with -Z macro-backtrace for more info)
error: used `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertion in a function that returns `Result`
--> $DIR/panic_in_result_fn.rs:21:5
@ -65,7 +62,6 @@ note: return Err() instead of panicking
|
LL | todo!("Finish this");
| ^^^^^^^^^^^^^^^^^^^^
= note: this error originates in the macro `todo` (in Nightly builds, run with -Z macro-backtrace for more info)
error: used `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertion in a function that returns `Result`
--> $DIR/panic_in_result_fn.rs:52:1
@ -82,7 +78,6 @@ note: return Err() instead of panicking
|
LL | panic!("error");
| ^^^^^^^^^^^^^^^
= note: this error originates in the macro `$crate::panic::panic_2021` (in Nightly builds, run with -Z macro-backtrace for more info)
error: used `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertion in a function that returns `Result`
--> $DIR/panic_in_result_fn.rs:67:1
@ -99,7 +94,6 @@ note: return Err() instead of panicking
|
LL | todo!("finish main method");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: this error originates in the macro `todo` (in Nightly builds, run with -Z macro-backtrace for more info)
error: aborting due to 6 previous errors

View file

@ -15,7 +15,6 @@ note: return Err() instead of panicking
|
LL | assert!(x == 5, "wrong argument");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: this error originates in the macro `assert` (in Nightly builds, run with -Z macro-backtrace for more info)
error: used `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertion in a function that returns `Result`
--> $DIR/panic_in_result_fn_assertions.rs:13:5
@ -33,7 +32,6 @@ note: return Err() instead of panicking
|
LL | assert_eq!(x, 5);
| ^^^^^^^^^^^^^^^^
= note: this error originates in the macro `assert_eq` (in Nightly builds, run with -Z macro-backtrace for more info)
error: used `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertion in a function that returns `Result`
--> $DIR/panic_in_result_fn_assertions.rs:19:5
@ -51,7 +49,6 @@ note: return Err() instead of panicking
|
LL | assert_ne!(x, 1);
| ^^^^^^^^^^^^^^^^
= note: this error originates in the macro `assert_ne` (in Nightly builds, run with -Z macro-backtrace for more info)
error: aborting due to 3 previous errors

View file

@ -25,23 +25,18 @@ LL | todo!();
| ^^^^^^^
|
= note: `-D clippy::todo` implied by `-D warnings`
= note: this error originates in the macro `todo` (in Nightly builds, run with -Z macro-backtrace for more info)
error: `todo` should not be present in production code
--> $DIR/panicking_macros.rs:17:5
|
LL | todo!("message");
| ^^^^^^^^^^^^^^^^
|
= note: this error originates in the macro `todo` (in Nightly builds, run with -Z macro-backtrace for more info)
error: `todo` should not be present in production code
--> $DIR/panicking_macros.rs:18:5
|
LL | todo!("{} {}", "panic with", "multiple arguments");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this error originates in the macro `todo` (in Nightly builds, run with -Z macro-backtrace for more info)
error: `unimplemented` should not be present in production code
--> $DIR/panicking_macros.rs:24:5
@ -50,23 +45,18 @@ LL | unimplemented!();
| ^^^^^^^^^^^^^^^^
|
= note: `-D clippy::unimplemented` implied by `-D warnings`
= note: this error originates in the macro `unimplemented` (in Nightly builds, run with -Z macro-backtrace for more info)
error: `unimplemented` should not be present in production code
--> $DIR/panicking_macros.rs:25:5
|
LL | unimplemented!("message");
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this error originates in the macro `unimplemented` (in Nightly builds, run with -Z macro-backtrace for more info)
error: `unimplemented` should not be present in production code
--> $DIR/panicking_macros.rs:26:5
|
LL | unimplemented!("{} {}", "panic with", "multiple arguments");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this error originates in the macro `unimplemented` (in Nightly builds, run with -Z macro-backtrace for more info)
error: usage of the `unreachable!` macro
--> $DIR/panicking_macros.rs:32:5
@ -75,23 +65,18 @@ LL | unreachable!();
| ^^^^^^^^^^^^^^
|
= note: `-D clippy::unreachable` implied by `-D warnings`
= note: this error originates in the macro `unreachable` (in Nightly builds, run with -Z macro-backtrace for more info)
error: usage of the `unreachable!` macro
--> $DIR/panicking_macros.rs:33:5
|
LL | unreachable!("message");
| ^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this error originates in the macro `$crate::unreachable` (in Nightly builds, run with -Z macro-backtrace for more info)
error: usage of the `unreachable!` macro
--> $DIR/panicking_macros.rs:34:5
|
LL | unreachable!("{} {}", "panic with", "multiple arguments");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this error originates in the macro `unreachable` (in Nightly builds, run with -Z macro-backtrace for more info)
error: `panic` should not be present in production code
--> $DIR/panicking_macros.rs:40:5
@ -104,24 +89,18 @@ error: `todo` should not be present in production code
|
LL | todo!();
| ^^^^^^^
|
= note: this error originates in the macro `todo` (in Nightly builds, run with -Z macro-backtrace for more info)
error: `unimplemented` should not be present in production code
--> $DIR/panicking_macros.rs:42:5
|
LL | unimplemented!();
| ^^^^^^^^^^^^^^^^
|
= note: this error originates in the macro `unimplemented` (in Nightly builds, run with -Z macro-backtrace for more info)
error: usage of the `unreachable!` macro
--> $DIR/panicking_macros.rs:43:5
|
LL | unreachable!();
| ^^^^^^^^^^^^^^
|
= note: this error originates in the macro `unreachable` (in Nightly builds, run with -Z macro-backtrace for more info)
error: aborting due to 16 previous errors

View file

@ -33,8 +33,6 @@ LL | | },
LL | | }
LL | | );
| |_____^
|
= note: this error originates in the macro `assert_eq` (in Nightly builds, run with -Z macro-backtrace for more info)
error: `debug_assert_eq` of unit values detected. This will always succeed
--> $DIR/unit_cmp.rs:32:5
@ -47,8 +45,6 @@ LL | | },
LL | | }
LL | | );
| |_____^
|
= note: this error originates in the macro `$crate::assert_eq` (in Nightly builds, run with -Z macro-backtrace for more info)
error: `assert_ne` of unit values detected. This will always fail
--> $DIR/unit_cmp.rs:41:5
@ -61,8 +57,6 @@ LL | | },
LL | | }
LL | | );
| |_____^
|
= note: this error originates in the macro `assert_ne` (in Nightly builds, run with -Z macro-backtrace for more info)
error: `debug_assert_ne` of unit values detected. This will always fail
--> $DIR/unit_cmp.rs:49:5
@ -75,8 +69,6 @@ LL | | },
LL | | }
LL | | );
| |_____^
|
= note: this error originates in the macro `$crate::assert_ne` (in Nightly builds, run with -Z macro-backtrace for more info)
error: aborting due to 6 previous errors