diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index bc302046b2d..ced879b6b2c 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -2,6 +2,7 @@ #![feature(box_patterns)] #![feature(drain_filter)] +#![feature(format_args_capture)] #![feature(in_band_lifetimes)] #![feature(iter_zip)] #![feature(once_cell)] diff --git a/clippy_lints/src/utils/author.rs b/clippy_lints/src/utils/author.rs index 79186fbe035..d20bf341318 100644 --- a/clippy_lints/src/utils/author.rs +++ b/clippy_lints/src/utils/author.rs @@ -3,14 +3,14 @@ use clippy_utils::{get_attr, higher}; use rustc_ast::ast::{LitFloatType, LitKind}; -use rustc_ast::{walk_list, Label, LitIntType}; +use rustc_ast::LitIntType; use rustc_data_structures::fx::FxHashMap; use rustc_hir as hir; -use rustc_hir::intravisit::{NestedVisitorMap, Visitor}; -use rustc_hir::{Arm, Block, Expr, ExprKind, FnRetTy, Lit, MatchSource, Pat, PatKind, QPath, Stmt, StmtKind, TyKind}; +use rustc_hir::{ExprKind, FnRetTy, HirId, Lit, PatKind, QPath, StmtKind, TyKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; -use rustc_middle::hir::map::Map; use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::symbol::{Ident, Symbol}; +use std::fmt::{Display, Formatter, Write as _}; declare_clippy_lint! { /// ### What it does @@ -53,6 +53,42 @@ declare_clippy_lint! { declare_lint_pass!(Author => [LINT_AUTHOR]); +/// Writes a line of output with indentation added +macro_rules! out { + ($($t:tt)*) => { + println!(" {}", format_args!($($t)*)) + }; +} + +/// The variables passed in are replaced with `&Binding`s where the `value` field is set +/// to the original value of the variable. The `name` field is set to the name of the variable +/// (using `stringify!`) and is adjusted to avoid duplicate names. +/// Note that the `Binding` may be printed directly to output the `name`. +macro_rules! bind { + ($self:ident $(, $name:ident)+) => { + $(let $name = & $self.bind(stringify!($name), $name);)+ + }; +} + +/// Transforms the given `Option` varibles into `OptionPat>`. +/// This displays as `Some($name)` or `None` when printed. The name of the inner binding +/// is set to the name of the variable passed to the macro. +macro_rules! opt_bind { + ($self:ident $(, $name:ident)+) => { + $(let $name = OptionPat::new($name.map(|o| $self.bind(stringify!($name), o)));)+ + }; +} + +/// Creates a `Binding` that accesses the field of an existing `Binding` +macro_rules! field { + ($binding:ident.$field:ident) => { + &Binding { + name: $binding.name.to_string() + stringify!(.$field), + value: $binding.value.$field, + } + }; +} + fn prelude() { println!("if_chain! {{"); } @@ -66,1005 +102,594 @@ fn done() { impl<'tcx> LateLintPass<'tcx> for Author { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) { - if !has_attr(cx, item.hir_id()) { - return; - } - prelude(); - PrintVisitor::new("item", cx).visit_item(item); - done(); + check_item(cx, item.hir_id()); } fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) { - if !has_attr(cx, item.hir_id()) { - return; - } - prelude(); - PrintVisitor::new("item", cx).visit_impl_item(item); - done(); + check_item(cx, item.hir_id()); } fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) { - if !has_attr(cx, item.hir_id()) { - return; - } - prelude(); - PrintVisitor::new("item", cx).visit_trait_item(item); - done(); - } - - fn check_variant(&mut self, cx: &LateContext<'tcx>, var: &'tcx hir::Variant<'_>) { - if !has_attr(cx, var.id) { - return; - } - prelude(); - let parent_hir_id = cx.tcx.hir().get_parent_node(var.id); - PrintVisitor::new("var", cx).visit_variant(var, &hir::Generics::empty(), parent_hir_id); - done(); - } - - fn check_field_def(&mut self, cx: &LateContext<'tcx>, field: &'tcx hir::FieldDef<'_>) { - if !has_attr(cx, field.hir_id) { - return; - } - prelude(); - PrintVisitor::new("field", cx).visit_field_def(field); - done(); - } - - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { - if !has_attr(cx, expr.hir_id) { - return; - } - prelude(); - PrintVisitor::new("expr", cx).visit_expr(expr); - done(); + check_item(cx, item.hir_id()); } fn check_arm(&mut self, cx: &LateContext<'tcx>, arm: &'tcx hir::Arm<'_>) { - if !has_attr(cx, arm.hir_id) { - return; - } - prelude(); - PrintVisitor::new("arm", cx).visit_arm(arm); - done(); + check_node(cx, arm.hir_id, |v| { + v.arm(&v.bind("arm", arm)); + }); + } + + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { + check_node(cx, expr.hir_id, |v| { + v.expr(&v.bind("expr", expr)); + }); } fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx hir::Stmt<'_>) { - if !has_attr(cx, stmt.hir_id) { - return; - } match stmt.kind { StmtKind::Expr(e) | StmtKind::Semi(e) if has_attr(cx, e.hir_id) => return, _ => {}, } - prelude(); - PrintVisitor::new("stmt", cx).visit_stmt(stmt); - done(); + check_node(cx, stmt.hir_id, |v| { + v.stmt(&v.bind("stmt", stmt)); + }); } +} - fn check_foreign_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ForeignItem<'_>) { - if !has_attr(cx, item.hir_id()) { - return; - } +fn check_item(cx: &LateContext<'_>, hir_id: HirId) { + let hir = cx.tcx.hir(); + if let Some(body_id) = hir.maybe_body_owned_by(hir_id) { + check_node(cx, hir_id, |v| { + v.expr(&v.bind("expr", &hir.body(body_id).value)); + }); + } +} + +fn check_node(cx: &LateContext<'_>, hir_id: HirId, f: impl Fn(&PrintVisitor<'_, '_>)) { + if has_attr(cx, hir_id) { prelude(); - PrintVisitor::new("item", cx).visit_foreign_item(item); + f(&PrintVisitor::new(cx)); done(); } } +struct Binding { + name: String, + value: T, +} + +impl Display for Binding { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.name) + } +} + +struct OptionPat { + pub opt: Option, +} + +impl OptionPat { + fn new(opt: Option) -> Self { + Self { opt } + } + + fn if_some(&self, f: impl Fn(&T)) { + if let Some(t) = &self.opt { + f(t); + } + } +} + +impl Display for OptionPat { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match &self.opt { + None => f.write_str("None"), + Some(node) => write!(f, "Some({node})"), + } + } +} + +struct PrintVisitor<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + /// Fields are the current index that needs to be appended to pattern + /// binding names + ids: std::cell::Cell>, +} + +#[allow(clippy::unused_self)] impl<'a, 'tcx> PrintVisitor<'a, 'tcx> { - #[must_use] - fn new(s: &'static str, cx: &'a LateContext<'tcx>) -> Self { + fn new(cx: &'a LateContext<'tcx>) -> Self { Self { - ids: FxHashMap::default(), - current: s.to_owned(), cx, + ids: std::cell::Cell::default(), } } - fn next(&mut self, s: &'static str) -> String { - use std::collections::hash_map::Entry::{Occupied, Vacant}; - match self.ids.entry(s) { - // already there: start numbering from `1` - Occupied(mut occ) => { - let val = occ.get_mut(); - *val += 1; - format!("{}{}", s, *val) - }, - // not there: insert and return name as given - Vacant(vac) => { - vac.insert(0); - s.to_owned() + fn next(&self, s: &'static str) -> String { + let mut ids = self.ids.take(); + let out = match *ids.entry(s).and_modify(|n| *n += 1).or_default() { + // first usage of the name, use it as is + 0 => s.to_string(), + // append a number starting with 1 + n => format!("{s}{n}"), + }; + self.ids.set(ids); + out + } + + fn bind(&self, name: &'static str, value: T) -> Binding { + let name = self.next(name); + Binding { name, value } + } + + fn option(&self, option: &Binding>, name: &'static str, f: impl Fn(&Binding)) { + match option.value { + None => out!("if {option}.is_none();"), + Some(value) => { + let value = &self.bind(name, value); + out!("if let Some({value}) = {option};"); + f(value); }, } } - fn print_qpath(&mut self, path: &QPath<'_>) { - if let QPath::LangItem(lang_item, _) = *path { - println!( - " if matches!({}, QPath::LangItem(LangItem::{:?}, _));", - self.current, lang_item, - ); + fn slice(&self, slice: &Binding<&[T]>, f: impl Fn(&Binding<&T>)) { + if slice.value.is_empty() { + out!("if {slice}.is_empty();"); } else { - print!(" if match_qpath({}, &[", self.current); - print_path(path, &mut true); - println!("]);"); + out!("if {slice}.len() == {};", slice.value.len()); + for (i, value) in slice.value.iter().enumerate() { + let name = format!("{slice}[{i}]"); + f(&Binding { name, value }); + } } } - fn print_label(&mut self, label: Option