summaryrefslogtreecommitdiffstats
path: root/src/tools/clippy/clippy_utils
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:20:39 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:20:39 +0000
commit1376c5a617be5c25655d0d7cb63e3beaa5a6e026 (patch)
tree3bb8d61aee02bc7a15eab3f36e3b921afc2075d0 /src/tools/clippy/clippy_utils
parentReleasing progress-linux version 1.69.0+dfsg1-1~progress7.99u1. (diff)
downloadrustc-1376c5a617be5c25655d0d7cb63e3beaa5a6e026.tar.xz
rustc-1376c5a617be5c25655d0d7cb63e3beaa5a6e026.zip
Merging upstream version 1.70.0+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/tools/clippy/clippy_utils')
-rw-r--r--src/tools/clippy/clippy_utils/Cargo.toml2
-rw-r--r--src/tools/clippy/clippy_utils/src/ast_utils.rs43
-rw-r--r--src/tools/clippy/clippy_utils/src/attrs.rs4
-rw-r--r--src/tools/clippy/clippy_utils/src/check_proc_macro.rs1
-rw-r--r--src/tools/clippy/clippy_utils/src/eager_or_lazy.rs8
-rw-r--r--src/tools/clippy/clippy_utils/src/hir_utils.rs10
-rw-r--r--src/tools/clippy/clippy_utils/src/lib.rs89
-rw-r--r--src/tools/clippy/clippy_utils/src/macros.rs832
-rw-r--r--src/tools/clippy/clippy_utils/src/msrvs.rs1
-rw-r--r--src/tools/clippy/clippy_utils/src/paths.rs3
-rw-r--r--src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs17
-rw-r--r--src/tools/clippy/clippy_utils/src/source.rs42
-rw-r--r--src/tools/clippy/clippy_utils/src/sugg.rs12
-rw-r--r--src/tools/clippy/clippy_utils/src/ty.rs69
-rw-r--r--src/tools/clippy/clippy_utils/src/visitors.rs5
15 files changed, 358 insertions, 780 deletions
diff --git a/src/tools/clippy/clippy_utils/Cargo.toml b/src/tools/clippy/clippy_utils/Cargo.toml
index 173469f6c..124ebd164 100644
--- a/src/tools/clippy/clippy_utils/Cargo.toml
+++ b/src/tools/clippy/clippy_utils/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "clippy_utils"
-version = "0.1.69"
+version = "0.1.70"
edition = "2021"
publish = false
diff --git a/src/tools/clippy/clippy_utils/src/ast_utils.rs b/src/tools/clippy/clippy_utils/src/ast_utils.rs
index d82098523..1f15598db 100644
--- a/src/tools/clippy/clippy_utils/src/ast_utils.rs
+++ b/src/tools/clippy/clippy_utils/src/ast_utils.rs
@@ -143,7 +143,7 @@ pub fn eq_expr(l: &Expr, r: &Expr) -> bool {
(Paren(l), _) => eq_expr(l, r),
(_, Paren(r)) => eq_expr(l, r),
(Err, Err) => true,
- (Box(l), Box(r)) | (Try(l), Try(r)) | (Await(l), Await(r)) => eq_expr(l, r),
+ (Try(l), Try(r)) | (Await(l), Await(r)) => eq_expr(l, r),
(Array(l), Array(r)) => over(l, r, |l, r| eq_expr(l, r)),
(Tup(l), Tup(r)) => over(l, r, |l, r| eq_expr(l, r)),
(Repeat(le, ls), Repeat(re, rs)) => eq_expr(le, re) && eq_expr(&ls.value, &rs.value),
@@ -209,7 +209,7 @@ pub fn eq_expr(l: &Expr, r: &Expr) -> bool {
&& eq_fn_decl(lf, rf)
&& eq_expr(le, re)
},
- (Async(lc, _, lb), Async(rc, _, rb)) => lc == rc && eq_block(lb, rb),
+ (Async(lc, lb), Async(rc, rb)) => lc == rc && eq_block(lb, rb),
(Range(lf, lt, ll), Range(rf, rt, rl)) => ll == rl && eq_expr_opt(lf, rf) && eq_expr_opt(lt, rt),
(AddrOf(lbk, lm, le), AddrOf(rbk, rm, re)) => lbk == rbk && lm == rm && eq_expr(le, re),
(Path(lq, lp), Path(rq, rp)) => both(lq, rq, eq_qself) && eq_path(lp, rp),
@@ -286,8 +286,30 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool {
match (l, r) {
(ExternCrate(l), ExternCrate(r)) => l == r,
(Use(l), Use(r)) => eq_use_tree(l, r),
- (Static(lt, lm, le), Static(rt, rm, re)) => lm == rm && eq_ty(lt, rt) && eq_expr_opt(le, re),
- (Const(ld, lt, le), Const(rd, rt, re)) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re),
+ (
+ Static(box ast::StaticItem {
+ ty: lt,
+ mutability: lm,
+ expr: le,
+ }),
+ Static(box ast::StaticItem {
+ ty: rt,
+ mutability: rm,
+ expr: re,
+ }),
+ ) => lm == rm && eq_ty(lt, rt) && eq_expr_opt(le, re),
+ (
+ Const(box ast::ConstItem {
+ defaultness: ld,
+ ty: lt,
+ expr: le,
+ }),
+ Const(box ast::ConstItem {
+ defaultness: rd,
+ ty: rt,
+ expr: re,
+ }),
+ ) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re),
(
Fn(box ast::Fn {
defaultness: ld,
@@ -451,7 +473,18 @@ pub fn eq_foreign_item_kind(l: &ForeignItemKind, r: &ForeignItemKind) -> bool {
pub fn eq_assoc_item_kind(l: &AssocItemKind, r: &AssocItemKind) -> bool {
use AssocItemKind::*;
match (l, r) {
- (Const(ld, lt, le), Const(rd, rt, re)) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re),
+ (
+ Const(box ast::ConstItem {
+ defaultness: ld,
+ ty: lt,
+ expr: le,
+ }),
+ Const(box ast::ConstItem {
+ defaultness: rd,
+ ty: rt,
+ expr: re,
+ }),
+ ) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re),
(
Fn(box ast::Fn {
defaultness: ld,
diff --git a/src/tools/clippy/clippy_utils/src/attrs.rs b/src/tools/clippy/clippy_utils/src/attrs.rs
index 7987a233b..b4ad42a50 100644
--- a/src/tools/clippy/clippy_utils/src/attrs.rs
+++ b/src/tools/clippy/clippy_utils/src/attrs.rs
@@ -145,8 +145,8 @@ pub fn get_unique_attr<'a>(
/// Return true if the attributes contain any of `proc_macro`,
/// `proc_macro_derive` or `proc_macro_attribute`, false otherwise
-pub fn is_proc_macro(sess: &Session, attrs: &[ast::Attribute]) -> bool {
- attrs.iter().any(|attr| sess.is_proc_macro_attr(attr))
+pub fn is_proc_macro(attrs: &[ast::Attribute]) -> bool {
+ attrs.iter().any(rustc_ast::Attribute::is_proc_macro_attr)
}
/// Return true if the attributes contain `#[doc(hidden)]`
diff --git a/src/tools/clippy/clippy_utils/src/check_proc_macro.rs b/src/tools/clippy/clippy_utils/src/check_proc_macro.rs
index 43f0df145..d3a6929f6 100644
--- a/src/tools/clippy/clippy_utils/src/check_proc_macro.rs
+++ b/src/tools/clippy/clippy_utils/src/check_proc_macro.rs
@@ -112,7 +112,6 @@ fn qpath_search_pat(path: &QPath<'_>) -> (Pat, Pat) {
/// 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),
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 ee2f816f1..28c857170 100644
--- a/src/tools/clippy/clippy_utils/src/eager_or_lazy.rs
+++ b/src/tools/clippy/clippy_utils/src/eager_or_lazy.rs
@@ -199,11 +199,9 @@ fn expr_eagerness<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> EagernessS
},
// Memory allocation, custom operator, loop, or call to an unknown function
- ExprKind::Box(_)
- | ExprKind::Unary(..)
- | ExprKind::Binary(..)
- | ExprKind::Loop(..)
- | ExprKind::Call(..) => self.eagerness = Lazy,
+ ExprKind::Unary(..) | ExprKind::Binary(..) | ExprKind::Loop(..) | ExprKind::Call(..) => {
+ self.eagerness = Lazy;
+ },
ExprKind::ConstBlock(_)
| ExprKind::Array(_)
diff --git a/src/tools/clippy/clippy_utils/src/hir_utils.rs b/src/tools/clippy/clippy_utils/src/hir_utils.rs
index 0603755f8..3ee714782 100644
--- a/src/tools/clippy/clippy_utils/src/hir_utils.rs
+++ b/src/tools/clippy/clippy_utils/src/hir_utils.rs
@@ -249,7 +249,6 @@ impl HirEqInterExpr<'_, '_, '_> {
both(&li.label, &ri.label, |l, r| l.ident.name == r.ident.name)
&& both(le, re, |l, r| self.eq_expr(l, r))
},
- (&ExprKind::Box(l), &ExprKind::Box(r)) => self.eq_expr(l, r),
(&ExprKind::Call(l_fun, l_args), &ExprKind::Call(r_fun, r_args)) => {
self.inner.allow_side_effects && self.eq_expr(l_fun, r_fun) && self.eq_exprs(l_args, r_args)
},
@@ -402,14 +401,9 @@ impl HirEqInterExpr<'_, '_, '_> {
}
fn eq_path_parameters(&mut self, left: &GenericArgs<'_>, right: &GenericArgs<'_>) -> bool {
- if !(left.parenthesized || right.parenthesized) {
+ if left.parenthesized == right.parenthesized {
over(left.args, right.args, |l, r| self.eq_generic_arg(l, r)) // FIXME(flip1995): may not work
&& over(left.bindings, right.bindings, |l, r| self.eq_type_binding(l, r))
- } else if left.parenthesized && right.parenthesized {
- over(left.inputs(), right.inputs(), |l, r| self.eq_ty(l, r))
- && both(&Some(&left.bindings[0].ty()), &Some(&right.bindings[0].ty()), |l, r| {
- self.eq_ty(l, r)
- })
} else {
false
}
@@ -628,7 +622,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
self.hash_expr(j);
}
},
- ExprKind::Box(e) | ExprKind::DropTemps(e) | ExprKind::Yield(e, _) => {
+ ExprKind::DropTemps(e) | ExprKind::Yield(e, _) => {
self.hash_expr(e);
},
ExprKind::Call(fun, args) => {
diff --git a/src/tools/clippy/clippy_utils/src/lib.rs b/src/tools/clippy/clippy_utils/src/lib.rs
index f02f8ecb4..6b677df46 100644
--- a/src/tools/clippy/clippy_utils/src/lib.rs
+++ b/src/tools/clippy/clippy_utils/src/lib.rs
@@ -3,7 +3,6 @@
#![feature(let_chains)]
#![feature(lint_reasons)]
#![feature(never_type)]
-#![feature(once_cell)]
#![feature(rustc_private)]
#![recursion_limit = "512"]
#![cfg_attr(feature = "deny-warnings", deny(warnings))]
@@ -33,7 +32,6 @@ extern crate rustc_lexer;
extern crate rustc_lint;
extern crate rustc_middle;
extern crate rustc_mir_dataflow;
-extern crate rustc_parse_format;
extern crate rustc_session;
extern crate rustc_span;
extern crate rustc_target;
@@ -78,7 +76,7 @@ use std::sync::OnceLock;
use std::sync::{Mutex, MutexGuard};
use if_chain::if_chain;
-use rustc_ast::ast::{self, LitKind};
+use rustc_ast::ast::{self, LitKind, RangeLimits};
use rustc_ast::Attribute;
use rustc_data_structures::fx::FxHashMap;
use rustc_data_structures::unhash::UnhashMap;
@@ -96,6 +94,7 @@ use rustc_hir::{
use rustc_lexer::{tokenize, TokenKind};
use rustc_lint::{LateContext, Level, Lint, LintContext};
use rustc_middle::hir::place::PlaceBase;
+use rustc_middle::mir::ConstantKind;
use rustc_middle::ty as rustc_ty;
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow};
use rustc_middle::ty::binding::BindingMode;
@@ -104,7 +103,7 @@ use rustc_middle::ty::fast_reject::SimplifiedType::{
PtrSimplifiedType, SliceSimplifiedType, StrSimplifiedType, UintSimplifiedType,
};
use rustc_middle::ty::{
- layout::IntegerExt, BorrowKind, ClosureKind, DefIdTree, Ty, TyCtxt, TypeAndMut, TypeVisitableExt, UpvarCapture,
+ layout::IntegerExt, BorrowKind, ClosureKind, Ty, TyCtxt, TypeAndMut, TypeVisitableExt, UpvarCapture,
};
use rustc_middle::ty::{FloatTy, IntTy, UintTy};
use rustc_span::hygiene::{ExpnKind, MacroKind};
@@ -114,7 +113,8 @@ use rustc_span::symbol::{kw, Ident, Symbol};
use rustc_span::Span;
use rustc_target::abi::Integer;
-use crate::consts::{constant, Constant};
+use crate::consts::{constant, miri_to_const, Constant};
+use crate::higher::Range;
use crate::ty::{can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type, ty_is_fn_once_param};
use crate::visitors::for_each_expr;
@@ -617,7 +617,7 @@ fn item_children_by_name(tcx: TyCtxt<'_>, def_id: DefId, name: Symbol) -> Vec<Re
/// Can return multiple resolutions when there are multiple versions of the same crate, e.g.
/// `memchr::memchr` could return the functions from both memchr 1.0 and memchr 2.0.
///
-/// Also returns multiple results when there are mulitple paths under the same name e.g. `std::vec`
+/// Also returns multiple results when there are multiple paths under the same name e.g. `std::vec`
/// would have both a [`DefKind::Mod`] and [`DefKind::Macro`].
///
/// This function is expensive and should be used sparingly.
@@ -1491,6 +1491,68 @@ pub fn is_else_clause(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
}
}
+/// Checks whether the given `Expr` is a range equivalent to a `RangeFull`.
+/// For the lower bound, this means that:
+/// - either there is none
+/// - or it is the smallest value that can be represented by the range's integer type
+/// For the upper bound, this means that:
+/// - either there is none
+/// - or it is the largest value that can be represented by the range's integer type and is
+/// inclusive
+/// - or it is a call to some container's `len` method and is exclusive, and the range is passed to
+/// a method call on that same container (e.g. `v.drain(..v.len())`)
+/// If the given `Expr` is not some kind of range, the function returns `false`.
+pub fn is_range_full(cx: &LateContext<'_>, expr: &Expr<'_>, container_path: Option<&Path<'_>>) -> bool {
+ let ty = cx.typeck_results().expr_ty(expr);
+ if let Some(Range { start, end, limits }) = Range::hir(expr) {
+ let start_is_none_or_min = start.map_or(true, |start| {
+ if let rustc_ty::Adt(_, subst) = ty.kind()
+ && let bnd_ty = subst.type_at(0)
+ && let Some(min_val) = bnd_ty.numeric_min_val(cx.tcx)
+ && let const_val = cx.tcx.valtree_to_const_val((bnd_ty, min_val.to_valtree()))
+ && let min_const_kind = ConstantKind::from_value(const_val, bnd_ty)
+ && let Some(min_const) = miri_to_const(cx.tcx, min_const_kind)
+ && let Some((start_const, _)) = constant(cx, cx.typeck_results(), start)
+ {
+ start_const == min_const
+ } else {
+ false
+ }
+ });
+ let end_is_none_or_max = end.map_or(true, |end| {
+ match limits {
+ RangeLimits::Closed => {
+ if let rustc_ty::Adt(_, subst) = ty.kind()
+ && let bnd_ty = subst.type_at(0)
+ && let Some(max_val) = bnd_ty.numeric_max_val(cx.tcx)
+ && let const_val = cx.tcx.valtree_to_const_val((bnd_ty, max_val.to_valtree()))
+ && let max_const_kind = ConstantKind::from_value(const_val, bnd_ty)
+ && let Some(max_const) = miri_to_const(cx.tcx, max_const_kind)
+ && let Some((end_const, _)) = constant(cx, cx.typeck_results(), end)
+ {
+ end_const == max_const
+ } else {
+ false
+ }
+ },
+ RangeLimits::HalfOpen => {
+ if let Some(container_path) = container_path
+ && let ExprKind::MethodCall(name, self_arg, [], _) = end.kind
+ && name.ident.name == sym::len
+ && let ExprKind::Path(QPath::Resolved(None, path)) = self_arg.kind
+ {
+ container_path.res == path.res
+ } else {
+ false
+ }
+ },
+ }
+ });
+ return start_is_none_or_min && end_is_none_or_max;
+ }
+ false
+}
+
/// Checks whether the given expression is a constant integer of the given value.
/// unlike `is_integer_literal`, this version does const folding
pub fn is_integer_const(cx: &LateContext<'_>, e: &Expr<'_>, value: u128) -> bool {
@@ -1904,16 +1966,7 @@ pub fn is_async_fn(kind: FnKind<'_>) -> bool {
/// Peels away all the compiler generated code surrounding the body of an async function,
pub fn get_async_fn_body<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'_>) -> Option<&'tcx Expr<'tcx>> {
- if let ExprKind::Call(
- _,
- &[
- Expr {
- kind: ExprKind::Closure(&Closure { body, .. }),
- ..
- },
- ],
- ) = body.value.kind
- {
+ if let ExprKind::Closure(&Closure { body, .. }) = body.value.kind {
if let ExprKind::Block(
Block {
stmts: [],
@@ -2114,9 +2167,7 @@ pub fn fn_has_unsatisfiable_preds(cx: &LateContext<'_>, did: DefId) -> bool {
.filter_map(|(p, _)| if p.is_global() { Some(*p) } else { None });
traits::impossible_predicates(
cx.tcx,
- traits::elaborate_predicates(cx.tcx, predicates)
- .map(|o| o.predicate)
- .collect::<Vec<_>>(),
+ traits::elaborate(cx.tcx, predicates).collect::<Vec<_>>(),
)
}
diff --git a/src/tools/clippy/clippy_utils/src/macros.rs b/src/tools/clippy/clippy_utils/src/macros.rs
index be6133d32..62d388a5e 100644
--- a/src/tools/clippy/clippy_utils/src/macros.rs
+++ b/src/tools/clippy/clippy_utils/src/macros.rs
@@ -1,22 +1,18 @@
#![allow(clippy::similar_names)] // `expr` and `expn`
-use crate::source::snippet_opt;
use crate::visitors::{for_each_expr, Descend};
use arrayvec::ArrayVec;
-use itertools::{izip, Either, Itertools};
-use rustc_ast::ast::LitKind;
-use rustc_hir::intravisit::{walk_expr, Visitor};
-use rustc_hir::{self as hir, Expr, ExprField, ExprKind, HirId, LangItem, Node, QPath, TyKind};
-use rustc_lexer::unescape::unescape_literal;
-use rustc_lexer::{tokenize, unescape, LiteralKind, TokenKind};
+use rustc_ast::{FormatArgs, FormatArgument, FormatPlaceholder};
+use rustc_data_structures::fx::FxHashMap;
+use rustc_hir::{self as hir, Expr, ExprKind, HirId, Node, QPath};
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, BytePos, ExpnData, ExpnId, ExpnKind, Pos, Span, SpanData, Symbol};
-use std::iter::{once, zip};
+use rustc_span::{sym, BytePos, ExpnData, ExpnId, ExpnKind, Span, Symbol};
+use std::cell::RefCell;
use std::ops::ControlFlow;
+use std::sync::atomic::{AtomicBool, Ordering};
const FORMAT_MACRO_DIAG_ITEMS: &[Symbol] = &[
sym::assert_eq_macro,
@@ -213,6 +209,7 @@ pub fn is_assert_macro(cx: &LateContext<'_>, def_id: DefId) -> bool {
matches!(name, sym::assert_macro | sym::debug_assert_macro)
}
+#[derive(Debug)]
pub enum PanicExpn<'a> {
/// No arguments - `panic!()`
Empty,
@@ -221,15 +218,12 @@ pub enum PanicExpn<'a> {
/// A single argument that implements `Display` - `panic!("{}", object)`
Display(&'a Expr<'a>),
/// Anything else - `panic!("error {}: {}", a, b)`
- Format(FormatArgsExpn<'a>),
+ Format(&'a Expr<'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 };
+ pub fn parse(expr: &'a Expr<'a>) -> Option<Self> {
+ let ExprKind::Call(callee, [arg, rest @ ..]) = &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,
@@ -238,7 +232,22 @@ impl<'a> PanicExpn<'a> {
let ExprKind::AddrOf(_, _, e) = &arg.kind else { return None };
Self::Display(e)
},
- "panic_fmt" => Self::Format(FormatArgsExpn::parse(cx, arg)?),
+ "panic_fmt" => Self::Format(arg),
+ // Since Rust 1.52, `assert_{eq,ne}` macros expand to use:
+ // `core::panicking::assert_failed(.., left_val, right_val, None | Some(format_args!(..)));`
+ "assert_failed" => {
+ // It should have 4 arguments in total (we already matched with the first argument,
+ // so we're just checking for 3)
+ if rest.len() != 3 {
+ return None;
+ }
+ // `msg_arg` is either `None` (no custom message) or `Some(format_args!(..))` (custom message)
+ let msg_arg = &rest[2];
+ match msg_arg.kind {
+ ExprKind::Call(_, [fmt_arg]) => Self::Format(fmt_arg),
+ _ => Self::Empty,
+ }
+ },
_ => return None,
};
Some(result)
@@ -251,7 +260,17 @@ pub fn find_assert_args<'a>(
expr: &'a Expr<'a>,
expn: ExpnId,
) -> Option<(&'a Expr<'a>, PanicExpn<'a>)> {
- find_assert_args_inner(cx, expr, expn).map(|([e], p)| (e, p))
+ find_assert_args_inner(cx, expr, expn).map(|([e], mut p)| {
+ // `assert!(..)` expands to `core::panicking::panic("assertion failed: ...")` (which we map to
+ // `PanicExpn::Str(..)`) and `assert!(.., "..")` expands to
+ // `core::panicking::panic_fmt(format_args!(".."))` (which we map to `PanicExpn::Format(..)`).
+ // So even we got `PanicExpn::Str(..)` that means there is no custom message provided
+ if let PanicExpn::Str(_) = p {
+ p = PanicExpn::Empty;
+ }
+
+ (e, p)
+ })
}
/// Finds the arguments of an `assert_eq!` or `debug_assert_eq!` macro call within the macro
@@ -275,13 +294,12 @@ fn find_assert_args_inner<'a, const N: usize>(
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;
- let _: Option<!> = for_each_expr(expr, |e| {
+ let panic_expn = for_each_expr(expr, |e| {
if args.is_full() {
- if panic_expn.is_none() && e.span.ctxt() != expr.span.ctxt() {
- panic_expn = PanicExpn::parse(cx, e);
+ match PanicExpn::parse(e) {
+ Some(expn) => ControlFlow::Break(expn),
+ None => ControlFlow::Continue(Descend::Yes),
}
- ControlFlow::Continue(Descend::from(panic_expn.is_none()))
} else if is_assert_arg(cx, e, expn) {
args.push(e);
ControlFlow::Continue(Descend::No)
@@ -339,241 +357,127 @@ fn is_assert_arg(cx: &LateContext<'_>, expr: &Expr<'_>, assert_expn: ExpnId) ->
}
}
-/// The format string doesn't exist in the HIR, so we reassemble it from source code
-#[derive(Debug)]
-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>,
+thread_local! {
+ /// We preserve the [`FormatArgs`] structs from the early pass for use in the late pass to be
+ /// able to access the many features of a [`LateContext`].
+ ///
+ /// A thread local is used because [`FormatArgs`] is `!Send` and `!Sync`, we are making an
+ /// assumption that the early pass the populates the map and the later late passes will all be
+ /// running on the same thread.
+ static AST_FORMAT_ARGS: RefCell<FxHashMap<Span, FormatArgs>> = {
+ static CALLED: AtomicBool = AtomicBool::new(false);
+ debug_assert!(
+ !CALLED.swap(true, Ordering::SeqCst),
+ "incorrect assumption: `AST_FORMAT_ARGS` should only be accessed by a single thread",
+ );
+
+ RefCell::default()
+ };
}
-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,
- };
+/// Record [`rustc_ast::FormatArgs`] for use in late lint passes, this should only be called by
+/// `FormatArgsCollector`
+pub fn collect_ast_format_args(span: Span, format_args: &FormatArgs) {
+ AST_FORMAT_ARGS.with(|ast_format_args| {
+ ast_format_args.borrow_mut().insert(span, format_args.clone());
+ });
+}
- let mode = if style.is_some() {
- unescape::Mode::RawStr
+/// Calls `callback` with an AST [`FormatArgs`] node if a `format_args` expansion is found as a
+/// descendant of `expn_id`
+pub fn find_format_args(cx: &LateContext<'_>, start: &Expr<'_>, expn_id: ExpnId, callback: impl FnOnce(&FormatArgs)) {
+ let format_args_expr = for_each_expr(start, |expr| {
+ let ctxt = expr.span.ctxt();
+ if ctxt.outer_expn().is_descendant_of(expn_id) {
+ if macro_backtrace(expr.span)
+ .map(|macro_call| cx.tcx.item_name(macro_call.def_id))
+ .any(|name| matches!(name, sym::const_format_args | sym::format_args | sym::format_args_nl))
+ {
+ ControlFlow::Break(expr)
+ } else {
+ ControlFlow::Continue(Descend::Yes)
+ }
} else {
- unescape::Mode::Str
- };
-
- let mut unescaped = String::with_capacity(inner.len());
- // Sometimes the original string comes from a macro which accepts a malformed string, such as in a
- // #[display(""somestring)] attribute (accepted by the `displaythis` crate). Reconstructing the
- // string from the span will not be possible, so we will just return None here.
- let mut unparsable = false;
- unescape_literal(inner, mode, &mut |_, ch| match ch {
- Ok(ch) => unescaped.push(ch),
- Err(e) if !e.is_fatal() => (),
- Err(_) => unparsable = true,
- });
- if unparsable {
- return None;
+ ControlFlow::Continue(Descend::No)
}
+ });
- let mut parts = Vec::new();
- let _: Option<!> = for_each_expr(pieces, |expr| {
- if let ExprKind::Lit(lit) = &expr.kind
- && let LitKind::Str(symbol, _) = lit.node
- {
- parts.push(symbol);
- }
- ControlFlow::Continue(())
+ if let Some(expr) = format_args_expr {
+ AST_FORMAT_ARGS.with(|ast_format_args| {
+ ast_format_args.borrow().get(&expr.span).map(callback);
});
-
- Some(Self {
- span,
- snippet,
- style,
- unescaped,
- parts,
- })
}
}
-struct FormatArgsValues<'tcx> {
- /// Values passed after the format string and implicit captures. `[1, z + 2, x]` for
- /// `format!("{x} {} {}", 1, z + 2)`.
- 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();
- let _: Option<!> = for_each_expr(args, |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 TyKind::Path(QPath::LangItem(LangItem::FormatArgument, _, _)) = ty.kind
- {
- 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);
- }
- ControlFlow::Continue(Descend::Yes)
- } else {
- // assume that any expr with a differing span is a value
- value_args.push(expr);
- ControlFlow::Continue(Descend::No)
- }
- });
-
- Self {
- value_args,
- pos_to_value_index,
- format_string_span,
+/// Attempt to find the [`rustc_hir::Expr`] that corresponds to the [`FormatArgument`]'s value, if
+/// it cannot be found it will return the [`rustc_ast::Expr`].
+pub fn find_format_arg_expr<'hir, 'ast>(
+ start: &'hir Expr<'hir>,
+ target: &'ast FormatArgument,
+) -> Result<&'hir rustc_hir::Expr<'hir>, &'ast rustc_ast::Expr> {
+ for_each_expr(start, |expr| {
+ if expr.span == target.expr.span {
+ ControlFlow::Break(expr)
+ } else {
+ ControlFlow::Continue(())
}
- }
+ })
+ .ok_or(&target.expr)
}
-/// The positions of a format argument's value, precision and width
+/// Span of the `:` and format specifiers
///
-/// 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>,
+/// ```ignore
+/// format!("{:.}"), format!("{foo:.}")
+/// ^^ ^^
+/// ```
+pub fn format_placeholder_format_span(placeholder: &FormatPlaceholder) -> Option<Span> {
+ let base = placeholder.span?.data();
+
+ // `base.hi` is `{...}|`, subtract 1 byte (the length of '}') so that it points before the closing
+ // brace `{...|}`
+ Some(Span::new(
+ placeholder.argument.span?.hi(),
+ base.hi - BytePos(1),
+ base.ctxt,
+ base.parent,
+ ))
}
-impl<'tcx> Visitor<'tcx> for ParamPosition {
- fn visit_expr_field(&mut self, field: &'tcx ExprField<'tcx>) {
- match field.ident.name {
- sym::position => {
- if let ExprKind::Lit(lit) = &field.expr.kind
- && let LitKind::Int(pos, _) = lit.node
- {
- self.value = pos as usize;
- }
- },
- sym::precision => {
- self.precision = parse_count(field.expr);
- },
- sym::width => {
- self.width = parse_count(field.expr);
- },
- _ => walk_expr(self, field.expr),
- }
+/// Span covering the format string and values
+///
+/// ```ignore
+/// format("{}.{}", 10, 11)
+/// // ^^^^^^^^^^^^^^^
+/// ```
+pub fn format_args_inputs_span(format_args: &FormatArgs) -> Span {
+ match format_args.arguments.explicit_args() {
+ [] => format_args.span,
+ [.., last] => format_args
+ .span
+ .to(hygiene::walk_chain(last.expr.span, format_args.span.ctxt())),
}
}
-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::TypeRelative(_, path)) = ctor.kind
- && path.ident.name == sym::Param
- && let ExprKind::Lit(lit) = &val.kind
- && let LitKind::Int(pos, _) = lit.node
- {
- Some(pos as usize)
- } else {
- None
- }
-}
+/// Returns the [`Span`] of the value at `index` extended to the previous comma, e.g. for the value
+/// `10`
+///
+/// ```ignore
+/// format("{}.{}", 10, 11)
+/// // ^^^^
+/// ```
+pub fn format_arg_removal_span(format_args: &FormatArgs, index: usize) -> Option<Span> {
+ let ctxt = format_args.span.ctxt();
-/// 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> {
- if let ExprKind::AddrOf(.., array) = fmt_arg.kind
- && let ExprKind::Array(specs) = array.kind
- {
- Some(specs.iter().map(|spec| {
- if let ExprKind::Call(f, args) = spec.kind
- && let ExprKind::Path(QPath::TypeRelative(ty, f)) = f.kind
- && let TyKind::Path(QPath::LangItem(LangItem::FormatPlaceholder, _, _)) = ty.kind
- && f.ident.name == sym::new
- && let [position, _fill, _align, _flags, precision, width] = args
- && let ExprKind::Lit(position) = &position.kind
- && let LitKind::Int(position, _) = position.node {
- ParamPosition {
- value: position as usize,
- width: parse_count(width),
- precision: parse_count(precision),
- }
- } else {
- ParamPosition::default()
- }
- }))
- } else {
- None
- }
-}
+ let current = hygiene::walk_chain(format_args.arguments.by_index(index)?.expr.span, ctxt);
-/// `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,
- )
-}
+ let prev = if index == 0 {
+ format_args.span
+ } else {
+ hygiene::walk_chain(format_args.arguments.by_index(index - 1)?.expr.span, ctxt)
+ };
-/// How a format parameter is used in the format string
-#[derive(Debug, Copy, Clone, PartialEq, Eq)]
-pub enum FormatParamKind {
- /// An implicit parameter , such as `{}` or `{:?}`.
- Implicit,
- /// A parameter with an explicit number, e.g. `{1}`, `{0:?}`, or `{:.0$}`
- Numbered,
- /// A parameter with an asterisk precision. e.g. `{:.*}`.
- Starred,
- /// 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),
+ Some(current.with_lo(prev.hi()))
}
/// Where a format parameter is being used in the format string
@@ -587,462 +491,6 @@ pub enum FormatParamUsage {
Precision,
}
-/// 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,
- /// Where this format param is being used - argument/width/precision
- pub usage: FormatParamUsage,
- /// 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,
- usage: FormatParamUsage,
- 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,
- usage,
- 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(Option<Span>),
-}
-
-impl<'tcx> Count<'tcx> {
- fn new(
- usage: FormatParamUsage,
- count: rpf::Count<'_>,
- position: Option<usize>,
- inner: Option<rpf::InnerSpan>,
- values: &FormatArgsValues<'tcx>,
- ) -> Option<Self> {
- let span = inner.map(|inner| span_from_inner(values.format_string_span, inner));
-
- Some(match count {
- rpf::Count::CountIs(val) => Self::Is(val, span?),
- rpf::Count::CountIsName(name, _) => Self::Param(FormatParam::new(
- FormatParamKind::Named(Symbol::intern(name)),
- usage,
- position?,
- inner?,
- values,
- )?),
- rpf::Count::CountIsParam(_) => Self::Param(FormatParam::new(
- FormatParamKind::Numbered,
- usage,
- position?,
- inner?,
- values,
- )?),
- rpf::Count::CountIsStar(_) => Self::Param(FormatParam::new(
- FormatParamKind::Starred,
- usage,
- position?,
- inner?,
- values,
- )?),
- rpf::Count::CountImplied => Self::Implied(span),
- })
- }
-
- 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,
- }
- }
-
- pub fn span(self) -> Option<Span> {
- match self {
- Count::Is(_, span) => Some(span),
- Count::Param(param) => Some(param.span),
- Count::Implied(span) => span,
- }
- }
-}
-
-/// 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,
- /// Whether all flag options are set to default (no flags specified).
- pub no_flags: bool,
- /// 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,
- no_flags: spec.sign.is_none() && !spec.alternate && !spec.zero_pad && spec.debug_hex.is_none(),
- precision: Count::new(
- FormatParamUsage::Precision,
- spec.precision,
- positions.precision,
- spec.precision_span,
- values,
- )?,
- width: Count::new(
- FormatParamUsage::Width,
- 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)),
- })
- }
-
- /// Returns true if this format spec is unchanged from the default. e.g. returns true for `{}`,
- /// `{foo}` and `{2}`, but false for `{:?}`, `{foo:5}` and `{3:.5}`
- pub fn is_default(&self) -> bool {
- self.r#trait == sym::Display && self.is_default_for_trait()
- }
-
- /// Has no other formatting specifiers than setting the format trait. returns true for `{}`,
- /// `{foo}`, `{:?}`, but false for `{foo:5}`, `{3:.5?}`
- pub fn is_default_for_trait(&self) -> bool {
- self.width.is_implied() && self.precision.is_implied() && self.align == Alignment::AlignUnknown && self.no_flags
- }
-}
-
-/// 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,
-}
-
-impl<'tcx> FormatArg<'tcx> {
- /// Span of the `:` and format specifiers
- ///
- /// ```ignore
- /// format!("{:.}"), format!("{foo:.}")
- /// ^^ ^^
- /// ```
- pub fn format_span(&self) -> Span {
- let base = self.span.data();
-
- // `base.hi` is `{...}|`, subtract 1 byte (the length of '}') so that it points before the closing
- // brace `{...|}`
- Span::new(self.param.span.hi(), base.hi - BytePos(1), base.ctxt, base.parent)
- }
-}
-
-/// 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,
- /// Spans of the commas between the format string and explicit values, excluding any trailing
- /// comma
- ///
- /// ```ignore
- /// format!("..", 1, 2, 3,)
- /// // ^ ^ ^
- /// ```
- comma_spans: Vec<Span>,
- /// Explicit values passed after the format string, ignoring implicit captures. `[1, z + 2]` for
- /// `format!("{x} {} {y}", 1, z + 2)`.
- explicit_values: Vec<&'tcx Expr<'tcx>>,
-}
-
-impl<'tcx> FormatArgsExpn<'tcx> {
- /// Gets the spans of the commas inbetween the format string and explicit args, not including
- /// any trailing comma
- ///
- /// ```ignore
- /// format!("{} {}", a, b)
- /// // ^ ^
- /// ```
- ///
- /// Ensures that the format string and values aren't coming from a proc macro that sets the
- /// output span to that of its input
- fn comma_spans(cx: &LateContext<'_>, explicit_values: &[&Expr<'_>], fmt_span: Span) -> Option<Vec<Span>> {
- // `format!("{} {} {c}", "one", "two", c = "three")`
- // ^^^^^ ^^^^^ ^^^^^^^
- let value_spans = explicit_values
- .iter()
- .map(|val| hygiene::walk_chain(val.span, fmt_span.ctxt()));
-
- // `format!("{} {} {c}", "one", "two", c = "three")`
- // ^^ ^^ ^^^^^^
- let between_spans = once(fmt_span)
- .chain(value_spans)
- .tuple_windows()
- .map(|(start, end)| start.between(end));
-
- let mut comma_spans = Vec::new();
- for between_span in between_spans {
- let mut offset = 0;
- let mut seen_comma = false;
-
- for token in tokenize(&snippet_opt(cx, between_span)?) {
- match token.kind {
- TokenKind::LineComment { .. } | TokenKind::BlockComment { .. } | TokenKind::Whitespace => {},
- TokenKind::Comma if !seen_comma => {
- seen_comma = true;
-
- let base = between_span.data();
- comma_spans.push(Span::new(
- base.lo + BytePos(offset),
- base.lo + BytePos(offset + 1),
- base.ctxt,
- base.parent,
- ));
- },
- // named arguments, `start_val, name = end_val`
- // ^^^^^^^^^ between_span
- TokenKind::Ident | TokenKind::Eq if seen_comma => {},
- // An unexpected token usually indicates the format string or a value came from a proc macro output
- // that sets the span of its output to an input, e.g. `println!(some_proc_macro!("input"), ..)` that
- // emits a string literal with the span set to that of `"input"`
- _ => return None,
- }
- offset += token.len;
- }
-
- if !seen_comma {
- return None;
- }
- }
-
- Some(comma_spans)
- }
-
- 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
- && let TyKind::Path(QPath::LangItem(LangItem::FormatArguments, _, _)) = ty.kind
- && 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)),
- },
- FormatParamUsage::Argument,
- 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<_>>>()?;
-
- let mut explicit_values = values.value_args;
- // remove values generated for implicitly captured vars
- let len = explicit_values
- .iter()
- .take_while(|val| !format_string.span.contains(val.span))
- .count();
- explicit_values.truncate(len);
-
- let comma_spans = Self::comma_spans(cx, &explicit_values, format_string.span)?;
-
- Some(Self {
- format_string,
- args,
- newline,
- comma_spans,
- explicit_values,
- })
- } else {
- None
- }
- }
-
- pub fn find_nested(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, expn_id: ExpnId) -> Option<Self> {
- for_each_expr(expr, |e| {
- let e_ctxt = e.span.ctxt();
- if e_ctxt == expr.span.ctxt() {
- ControlFlow::Continue(Descend::Yes)
- } else if e_ctxt.outer_expn().is_descendant_of(expn_id) {
- if let Some(args) = FormatArgsExpn::parse(cx, e) {
- ControlFlow::Break(args)
- } else {
- ControlFlow::Continue(Descend::No)
- }
- } else {
- ControlFlow::Continue(Descend::No)
- }
- })
- }
-
- /// Source callsite span of all inputs
- pub fn inputs_span(&self) -> Span {
- match *self.explicit_values {
- [] => self.format_string.span,
- [.., last] => self
- .format_string
- .span
- .to(hygiene::walk_chain(last.span, self.format_string.span.ctxt())),
- }
- }
-
- /// Get the span of a value expanded to the previous comma, e.g. for the value `10`
- ///
- /// ```ignore
- /// format("{}.{}", 10, 11)
- /// // ^^^^
- /// ```
- pub fn value_with_prev_comma_span(&self, value_id: HirId) -> Option<Span> {
- for (comma_span, value) in zip(&self.comma_spans, &self.explicit_values) {
- if value.hir_id == value_id {
- return Some(comma_span.to(hygiene::walk_chain(value.span, comma_span.ctxt())));
- }
- }
-
- None
- }
-
- /// 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()
- }
-}
-
/// A node with a `HirId` and a `Span`
pub trait HirNode {
fn hir_id(&self) -> HirId;
diff --git a/src/tools/clippy/clippy_utils/src/msrvs.rs b/src/tools/clippy/clippy_utils/src/msrvs.rs
index dbf9f3b62..e05de2dc9 100644
--- a/src/tools/clippy/clippy_utils/src/msrvs.rs
+++ b/src/tools/clippy/clippy_utils/src/msrvs.rs
@@ -19,6 +19,7 @@ macro_rules! msrv_aliases {
// names may refer to stabilized feature flags or library items
msrv_aliases! {
+ 1,68,0 { PATH_MAIN_SEPARATOR_STR }
1,65,0 { LET_ELSE }
1,62,0 { BOOL_THEN_SOME, DEFAULT_ENUM_ATTRIBUTE }
1,58,0 { FORMAT_ARGS_CAPTURE, PATTERN_TRAIT_CHAR_ARRAY }
diff --git a/src/tools/clippy/clippy_utils/src/paths.rs b/src/tools/clippy/clippy_utils/src/paths.rs
index 4aae0f728..9be2d0eae 100644
--- a/src/tools/clippy/clippy_utils/src/paths.rs
+++ b/src/tools/clippy/clippy_utils/src/paths.rs
@@ -23,6 +23,7 @@ pub const CLONE_TRAIT_METHOD: [&str; 4] = ["core", "clone", "Clone", "clone"];
pub const CORE_ITER_CLONED: [&str; 6] = ["core", "iter", "traits", "iterator", "Iterator", "cloned"];
pub const CORE_ITER_COPIED: [&str; 6] = ["core", "iter", "traits", "iterator", "Iterator", "copied"];
pub const CORE_ITER_FILTER: [&str; 6] = ["core", "iter", "traits", "iterator", "Iterator", "filter"];
+pub const CORE_RESULT_OK_METHOD: [&str; 4] = ["core", "result", "Result", "ok"];
pub const CSTRING_AS_C_STR: [&str; 5] = ["alloc", "ffi", "c_str", "CString", "as_c_str"];
pub const DEFAULT_TRAIT_METHOD: [&str; 4] = ["core", "default", "Default", "default"];
pub const DEREF_MUT_TRAIT_METHOD: [&str; 5] = ["core", "ops", "deref", "DerefMut", "deref_mut"];
@@ -67,6 +68,7 @@ pub const PARKING_LOT_MUTEX_GUARD: [&str; 3] = ["lock_api", "mutex", "MutexGuard
pub const PARKING_LOT_RWLOCK_READ_GUARD: [&str; 3] = ["lock_api", "rwlock", "RwLockReadGuard"];
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_MAIN_SEPARATOR: [&str; 3] = ["std", "path", "MAIN_SEPARATOR"];
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"];
@@ -112,6 +114,7 @@ pub const STDERR: [&str; 4] = ["std", "io", "stdio", "stderr"];
pub const STDOUT: [&str; 4] = ["std", "io", "stdio", "stdout"];
pub const CONVERT_IDENTITY: [&str; 3] = ["core", "convert", "identity"];
pub const STD_FS_CREATE_DIR: [&str; 3] = ["std", "fs", "create_dir"];
+pub const STD_IO_LINES: [&str; 3] = ["std", "io", "Lines"];
pub const STD_IO_SEEK: [&str; 3] = ["std", "io", "Seek"];
pub const STD_IO_SEEK_FROM_CURRENT: [&str; 4] = ["std", "io", "SeekFrom", "Current"];
pub const STD_IO_SEEKFROM_START: [&str; 4] = ["std", "io", "SeekFrom", "Start"];
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 1a35fe050..354b6d71a 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
@@ -37,7 +37,7 @@ pub fn is_min_const_fn<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, msrv: &Msrv)
| ty::PredicateKind::ConstEvaluatable(..)
| ty::PredicateKind::ConstEquate(..)
| ty::PredicateKind::TypeWellFormedFromEnv(..) => continue,
- ty::PredicateKind::AliasEq(..) => panic!("alias eq predicate on function: {predicate:#?}"),
+ ty::PredicateKind::AliasRelate(..) => panic!("alias relate predicate on function: {predicate:#?}"),
ty::PredicateKind::ObjectSafe(_) => panic!("object safe predicate on function: {predicate:#?}"),
ty::PredicateKind::ClosureKind(..) => panic!("closure kind predicate on function: {predicate:#?}"),
ty::PredicateKind::Subtype(_) => panic!("subtype predicate on function: {predicate:#?}"),
@@ -176,6 +176,10 @@ fn check_rvalue<'tcx>(
// FIXME(dyn-star)
unimplemented!()
},
+ Rvalue::Cast(CastKind::Transmute, _, _) => Err((
+ span,
+ "transmute can attempt to turn pointers into integers, so is unstable in const fn".into(),
+ )),
// binops are fine on integers
Rvalue::BinaryOp(_, box (lhs, rhs)) | Rvalue::CheckedBinaryOp(_, box (lhs, rhs)) => {
check_operand(tcx, lhs, span, body)?;
@@ -241,6 +245,7 @@ fn check_statement<'tcx>(
| StatementKind::StorageDead(_)
| StatementKind::Retag { .. }
| StatementKind::AscribeUserType(..)
+ | StatementKind::PlaceMention(..)
| StatementKind::Coverage(..)
| StatementKind::ConstEvalCounter
| StatementKind::Nop => Ok(()),
@@ -296,17 +301,13 @@ fn check_terminator<'tcx>(
| TerminatorKind::Goto { .. }
| TerminatorKind::Return
| TerminatorKind::Resume
+ | TerminatorKind::Terminate
| TerminatorKind::Unreachable => Ok(()),
TerminatorKind::Drop { place, .. } => check_place(tcx, *place, span, body),
- TerminatorKind::DropAndReplace { place, value, .. } => {
- check_place(tcx, *place, span, body)?;
- check_operand(tcx, value, span, body)
- },
TerminatorKind::SwitchInt { discr, targets: _ } => check_operand(tcx, discr, span, body),
- TerminatorKind::Abort => Err((span, "abort is not stable in const fn".into())),
TerminatorKind::GeneratorDrop | TerminatorKind::Yield { .. } => {
Err((span, "const fn generators are unstable".into()))
},
@@ -317,7 +318,7 @@ fn check_terminator<'tcx>(
from_hir_call: _,
destination: _,
target: _,
- cleanup: _,
+ unwind: _,
fn_span: _,
} => {
let fn_ty = func.ty(body, tcx);
@@ -360,7 +361,7 @@ fn check_terminator<'tcx>(
expected: _,
msg: _,
target: _,
- cleanup: _,
+ unwind: _,
} => check_operand(tcx, cond, span, body),
TerminatorKind::InlineAsm { .. } => Err((span, "cannot use inline assembly in const fn".into())),
diff --git a/src/tools/clippy/clippy_utils/src/source.rs b/src/tools/clippy/clippy_utils/src/source.rs
index cd5dcfdac..62fa37660 100644
--- a/src/tools/clippy/clippy_utils/src/source.rs
+++ b/src/tools/clippy/clippy_utils/src/source.rs
@@ -12,24 +12,21 @@ use rustc_span::{BytePos, Pos, Span, SpanData, SyntaxContext, DUMMY_SP};
use std::borrow::Cow;
/// 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>(
+pub fn expr_block<T: LintContext>(
cx: &T,
expr: &Expr<'_>,
- option: Option<String>,
- default: &'a str,
+ outer: SyntaxContext,
+ default: &str,
indent_relative_to: Option<Span>,
-) -> Cow<'a, str> {
- let code = snippet_block(cx, expr.span, default, indent_relative_to);
- let string = option.unwrap_or_default();
- if expr.span.from_expansion() {
- Cow::Owned(format!("{{ {} }}", snippet_with_macro_callsite(cx, expr.span, default)))
+ app: &mut Applicability,
+) -> String {
+ let (code, from_macro) = snippet_block_with_context(cx, expr.span, outer, default, indent_relative_to, app);
+ if from_macro {
+ format!("{{ {code} }}")
} else if let ExprKind::Block(_, _) = expr.kind {
- Cow::Owned(format!("{code}{string}"))
- } else if string.is_empty() {
- Cow::Owned(format!("{{ {code} }}"))
+ format!("{code}")
} else {
- Cow::Owned(format!("{{\n{code};\n{string}\n}}"))
+ format!("{{ {code} }}")
}
}
@@ -229,12 +226,6 @@ fn snippet_with_applicability_sess<'a>(
)
}
-/// Same as `snippet`, but should only be used when it's clear that the input span is
-/// not a macro argument.
-pub fn snippet_with_macro_callsite<'a, T: LintContext>(cx: &T, span: Span, default: &'a str) -> Cow<'a, str> {
- snippet(cx, span.source_callsite(), default)
-}
-
/// Converts a span to a code snippet. Returns `None` if not available.
pub fn snippet_opt(cx: &impl LintContext, span: Span) -> Option<String> {
snippet_opt_sess(cx.sess(), span)
@@ -303,6 +294,19 @@ pub fn snippet_block_with_applicability<'a>(
reindent_multiline(snip, true, indent)
}
+pub fn snippet_block_with_context<'a>(
+ cx: &impl LintContext,
+ span: Span,
+ outer: SyntaxContext,
+ default: &'a str,
+ indent_relative_to: Option<Span>,
+ app: &mut Applicability,
+) -> (Cow<'a, str>, bool) {
+ let (snip, from_macro) = snippet_with_context(cx, span, outer, default, app);
+ let indent = indent_relative_to.and_then(|s| indent_of(cx, s));
+ (reindent_multiline(snip, true, indent), from_macro)
+}
+
/// Same as `snippet_with_applicability`, but first walks the span up to the given context. This
/// will result in the macro call, rather then the expansion, if the span is from a child context.
/// If the span is not from a child context, it will be used directly instead.
diff --git a/src/tools/clippy/clippy_utils/src/sugg.rs b/src/tools/clippy/clippy_utils/src/sugg.rs
index 07feadca2..a5a4a921d 100644
--- a/src/tools/clippy/clippy_utils/src/sugg.rs
+++ b/src/tools/clippy/clippy_utils/src/sugg.rs
@@ -1,9 +1,7 @@
//! Contains utility functions to generate suggestions.
#![deny(clippy::missing_docs_in_private_items)]
-use crate::source::{
- snippet, snippet_opt, snippet_with_applicability, snippet_with_context, snippet_with_macro_callsite,
-};
+use crate::source::{snippet, snippet_opt, snippet_with_applicability, snippet_with_context};
use crate::ty::expr_sig;
use crate::{get_parent_expr_for_hir, higher};
use rustc_ast::util::parser::AssocOp;
@@ -89,12 +87,6 @@ impl<'a> Sugg<'a> {
})
}
- /// Same as `hir`, but will use the pre expansion span if the `expr` was in a macro.
- pub fn hir_with_macro_callsite(cx: &LateContext<'_>, expr: &hir::Expr<'_>, default: &'a str) -> Self {
- let get_snippet = |span| snippet_with_macro_callsite(cx, span, default);
- Self::hir_from_snippet(expr, get_snippet)
- }
-
/// Same as `hir`, but first walks the span up to the given context. This will result in the
/// macro call, rather then the expansion, if the span is from a child context. If the span is
/// not from a child context, it will be used directly instead.
@@ -133,7 +125,6 @@ impl<'a> Sugg<'a> {
match expr.kind {
hir::ExprKind::AddrOf(..)
- | hir::ExprKind::Box(..)
| hir::ExprKind::If(..)
| hir::ExprKind::Let(..)
| hir::ExprKind::Closure { .. }
@@ -188,7 +179,6 @@ impl<'a> Sugg<'a> {
match expr.kind {
_ if expr.span.ctxt() != ctxt => Sugg::NonParen(snippet_with_context(cx, expr.span, ctxt, default, app).0),
ast::ExprKind::AddrOf(..)
- | ast::ExprKind::Box(..)
| ast::ExprKind::Closure { .. }
| ast::ExprKind::If(..)
| ast::ExprKind::Let(..)
diff --git a/src/tools/clippy/clippy_utils/src/ty.rs b/src/tools/clippy/clippy_utils/src/ty.rs
index 25654e695..9449f0b55 100644
--- a/src/tools/clippy/clippy_utils/src/ty.rs
+++ b/src/tools/clippy/clippy_utils/src/ty.rs
@@ -16,9 +16,9 @@ use rustc_infer::infer::{
use rustc_lint::LateContext;
use rustc_middle::mir::interpret::{ConstValue, Scalar};
use rustc_middle::ty::{
- self, AdtDef, AliasTy, AssocKind, Binder, BoundRegion, DefIdTree, FnSig, IntTy, List, ParamEnv, Predicate,
- PredicateKind, Region, RegionKind, SubstsRef, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt,
- TypeVisitor, UintTy, VariantDef, VariantDiscr,
+ self, layout::ValidityRequirement, AdtDef, AliasTy, AssocKind, Binder, BoundRegion, FnSig, IntTy, List, ParamEnv,
+ Predicate, PredicateKind, Region, RegionKind, SubstsRef, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable,
+ TypeVisitableExt, TypeVisitor, UintTy, VariantDef, VariantDiscr,
};
use rustc_middle::ty::{GenericArg, GenericArgKind};
use rustc_span::symbol::Ident;
@@ -538,11 +538,26 @@ pub fn same_type_and_consts<'tcx>(a: Ty<'tcx>, b: Ty<'tcx>) -> bool {
}
/// Checks if a given type looks safe to be uninitialized.
-pub fn is_uninit_value_valid_for_ty(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
+pub fn is_uninit_value_valid_for_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
+ cx.tcx
+ .check_validity_requirement((ValidityRequirement::Uninit, cx.param_env.and(ty)))
+ .unwrap_or_else(|_| is_uninit_value_valid_for_ty_fallback(cx, ty))
+}
+
+/// A fallback for polymorphic types, which are not supported by `check_validity_requirement`.
+fn is_uninit_value_valid_for_ty_fallback<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
match *ty.kind() {
+ // The array length may be polymorphic, let's try the inner type.
ty::Array(component, _) => is_uninit_value_valid_for_ty(cx, component),
+ // Peek through tuples and try their fallbacks.
ty::Tuple(types) => types.iter().all(|ty| is_uninit_value_valid_for_ty(cx, ty)),
- ty::Adt(adt, _) => cx.tcx.lang_items().maybe_uninit() == Some(adt.did()),
+ // Unions are always fine right now.
+ // This includes MaybeUninit, the main way people use uninitialized memory.
+ // For ADTs, we could look at all fields just like for tuples, but that's potentially
+ // exponential, so let's avoid doing that for now. Code doing that is sketchy enough to
+ // just use an `#[allow()]`.
+ ty::Adt(adt, _) => adt.is_union(),
+ // For the rest, conservatively assume that they cannot be uninit.
_ => false,
}
}
@@ -1121,3 +1136,47 @@ pub fn make_normalized_projection<'tcx>(
}
helper(tcx, param_env, make_projection(tcx, container_id, assoc_ty, substs)?)
}
+
+/// Check if given type has inner mutability such as [`std::cell::Cell`] or [`std::cell::RefCell`]
+/// etc.
+pub fn is_interior_mut_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
+ match *ty.kind() {
+ ty::Ref(_, inner_ty, mutbl) => mutbl == Mutability::Mut || is_interior_mut_ty(cx, inner_ty),
+ ty::Slice(inner_ty) => is_interior_mut_ty(cx, inner_ty),
+ ty::Array(inner_ty, size) => {
+ size.try_eval_target_usize(cx.tcx, cx.param_env)
+ .map_or(true, |u| u != 0)
+ && is_interior_mut_ty(cx, inner_ty)
+ },
+ ty::Tuple(fields) => fields.iter().any(|ty| is_interior_mut_ty(cx, ty)),
+ ty::Adt(def, substs) => {
+ // Special case for collections in `std` who's impl of `Hash` or `Ord` delegates to
+ // that of their type parameters. Note: we don't include `HashSet` and `HashMap`
+ // because they have no impl for `Hash` or `Ord`.
+ let def_id = def.did();
+ let is_std_collection = [
+ sym::Option,
+ sym::Result,
+ sym::LinkedList,
+ sym::Vec,
+ sym::VecDeque,
+ sym::BTreeMap,
+ sym::BTreeSet,
+ sym::Rc,
+ sym::Arc,
+ ]
+ .iter()
+ .any(|diag_item| cx.tcx.is_diagnostic_item(*diag_item, def_id));
+ let is_box = Some(def_id) == cx.tcx.lang_items().owned_box();
+ if is_std_collection || is_box {
+ // The type is mutable if any of its type parameters are
+ substs.types().any(|ty| is_interior_mut_ty(cx, ty))
+ } else {
+ !ty.has_escaping_bound_vars()
+ && cx.tcx.layout_of(cx.param_env.and(ty)).is_ok()
+ && !ty.is_freeze(cx.tcx, cx.param_env)
+ }
+ },
+ _ => false,
+ }
+}
diff --git a/src/tools/clippy/clippy_utils/src/visitors.rs b/src/tools/clippy/clippy_utils/src/visitors.rs
index d27a20bd4..1dc19bac9 100644
--- a/src/tools/clippy/clippy_utils/src/visitors.rs
+++ b/src/tools/clippy/clippy_utils/src/visitors.rs
@@ -599,10 +599,7 @@ pub fn for_each_unconsumed_temporary<'tcx, B>(
| ExprKind::Let(&Let { init: e, .. }) => {
helper(typeck, false, e, f)?;
},
- ExprKind::Block(&Block { expr: Some(e), .. }, _)
- | ExprKind::Box(e)
- | ExprKind::Cast(e, _)
- | ExprKind::Unary(_, e) => {
+ ExprKind::Block(&Block { expr: Some(e), .. }, _) | ExprKind::Cast(e, _) | ExprKind::Unary(_, e) => {
helper(typeck, true, e, f)?;
},
ExprKind::Call(callee, args) => {