Merge commit '4f142aa1058f14f153f8bfd2d82f04ddb9982388' into clippyup
This commit is contained in:
@@ -136,7 +136,7 @@ pub fn get_unique_inner_attr(sess: &Session, attrs: &[ast::Attribute], name: &'s
|
||||
.emit();
|
||||
},
|
||||
ast::AttrStyle::Outer => {
|
||||
sess.span_err(attr.span, &format!("`{name}` cannot be an outer attribute"));
|
||||
sess.span_err(attr.span, format!("`{name}` cannot be an outer attribute"));
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,17 +136,49 @@ impl Constant {
|
||||
(&Self::F64(l), &Self::F64(r)) => l.partial_cmp(&r),
|
||||
(&Self::F32(l), &Self::F32(r)) => l.partial_cmp(&r),
|
||||
(&Self::Bool(ref l), &Self::Bool(ref r)) => Some(l.cmp(r)),
|
||||
(&Self::Tuple(ref l), &Self::Tuple(ref r)) | (&Self::Vec(ref l), &Self::Vec(ref r)) => iter::zip(l, r)
|
||||
.map(|(li, ri)| Self::partial_cmp(tcx, cmp_type, li, ri))
|
||||
.find(|r| r.map_or(true, |o| o != Ordering::Equal))
|
||||
.unwrap_or_else(|| Some(l.len().cmp(&r.len()))),
|
||||
(&Self::Tuple(ref l), &Self::Tuple(ref r)) if l.len() == r.len() => match *cmp_type.kind() {
|
||||
ty::Tuple(tys) if tys.len() == l.len() => l
|
||||
.iter()
|
||||
.zip(r)
|
||||
.zip(tys)
|
||||
.map(|((li, ri), cmp_type)| Self::partial_cmp(tcx, cmp_type, li, ri))
|
||||
.find(|r| r.map_or(true, |o| o != Ordering::Equal))
|
||||
.unwrap_or_else(|| Some(l.len().cmp(&r.len()))),
|
||||
_ => None,
|
||||
},
|
||||
(&Self::Vec(ref l), &Self::Vec(ref r)) => {
|
||||
let cmp_type = match *cmp_type.kind() {
|
||||
ty::Array(ty, _) | ty::Slice(ty) => ty,
|
||||
_ => return None,
|
||||
};
|
||||
iter::zip(l, r)
|
||||
.map(|(li, ri)| Self::partial_cmp(tcx, cmp_type, li, ri))
|
||||
.find(|r| r.map_or(true, |o| o != Ordering::Equal))
|
||||
.unwrap_or_else(|| Some(l.len().cmp(&r.len())))
|
||||
},
|
||||
(&Self::Repeat(ref lv, ref ls), &Self::Repeat(ref rv, ref rs)) => {
|
||||
match Self::partial_cmp(tcx, cmp_type, lv, rv) {
|
||||
match Self::partial_cmp(
|
||||
tcx,
|
||||
match *cmp_type.kind() {
|
||||
ty::Array(ty, _) => ty,
|
||||
_ => return None,
|
||||
},
|
||||
lv,
|
||||
rv,
|
||||
) {
|
||||
Some(Equal) => Some(ls.cmp(rs)),
|
||||
x => x,
|
||||
}
|
||||
},
|
||||
(&Self::Ref(ref lb), &Self::Ref(ref rb)) => Self::partial_cmp(tcx, cmp_type, lb, rb),
|
||||
(&Self::Ref(ref lb), &Self::Ref(ref rb)) => Self::partial_cmp(
|
||||
tcx,
|
||||
match *cmp_type.kind() {
|
||||
ty::Ref(_, ty, _) => ty,
|
||||
_ => return None,
|
||||
},
|
||||
lb,
|
||||
rb,
|
||||
),
|
||||
// TODO: are there any useful inter-type orderings?
|
||||
_ => None,
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ fn expr_eagerness<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> EagernessS
|
||||
.expr_ty(e)
|
||||
.has_significant_drop(self.cx.tcx, self.cx.param_env)
|
||||
{
|
||||
self.eagerness = Lazy;
|
||||
self.eagerness = ForceNoChange;
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -25,10 +25,12 @@ extern crate rustc_data_structures;
|
||||
extern crate rustc_errors;
|
||||
extern crate rustc_hir;
|
||||
extern crate rustc_hir_typeck;
|
||||
extern crate rustc_index;
|
||||
extern crate rustc_infer;
|
||||
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;
|
||||
@@ -48,6 +50,7 @@ pub mod eager_or_lazy;
|
||||
pub mod higher;
|
||||
mod hir_utils;
|
||||
pub mod macros;
|
||||
pub mod mir;
|
||||
pub mod msrvs;
|
||||
pub mod numeric_literal;
|
||||
pub mod paths;
|
||||
@@ -122,7 +125,7 @@ pub fn parse_msrv(msrv: &str, sess: Option<&Session>, span: Option<Span>) -> Opt
|
||||
return Some(version);
|
||||
} else if let Some(sess) = sess {
|
||||
if let Some(span) = span {
|
||||
sess.span_err(span, &format!("`{msrv}` is not a valid Rust version"));
|
||||
sess.span_err(span, format!("`{msrv}` is not a valid Rust version"));
|
||||
}
|
||||
}
|
||||
None
|
||||
@@ -815,13 +818,37 @@ pub fn is_default_equivalent(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
|
||||
false
|
||||
}
|
||||
},
|
||||
ExprKind::Call(repl_func, _) => is_default_equivalent_call(cx, repl_func),
|
||||
ExprKind::Call(repl_func, []) => is_default_equivalent_call(cx, repl_func),
|
||||
ExprKind::Call(from_func, [ref arg]) => is_default_equivalent_from(cx, from_func, arg),
|
||||
ExprKind::Path(qpath) => is_res_lang_ctor(cx, cx.qpath_res(qpath, e.hir_id), OptionNone),
|
||||
ExprKind::AddrOf(rustc_hir::BorrowKind::Ref, _, expr) => matches!(expr.kind, ExprKind::Array([])),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_default_equivalent_from(cx: &LateContext<'_>, from_func: &Expr<'_>, arg: &Expr<'_>) -> bool {
|
||||
if let ExprKind::Path(QPath::TypeRelative(ty, seg)) = from_func.kind &&
|
||||
seg.ident.name == sym::from
|
||||
{
|
||||
match arg.kind {
|
||||
ExprKind::Lit(hir::Lit {
|
||||
node: LitKind::Str(ref sym, _),
|
||||
..
|
||||
}) => return sym.is_empty() && is_path_diagnostic_item(cx, ty, sym::String),
|
||||
ExprKind::Array([]) => return is_path_diagnostic_item(cx, ty, sym::Vec),
|
||||
ExprKind::Repeat(_, ArrayLen::Body(len)) => {
|
||||
if let ExprKind::Lit(ref const_lit) = cx.tcx.hir().body(len.body).value.kind &&
|
||||
let LitKind::Int(v, _) = const_lit.node
|
||||
{
|
||||
return v == 0 && is_path_diagnostic_item(cx, ty, sym::Vec);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Checks if the top level expression can be moved into a closure as is.
|
||||
/// Currently checks for:
|
||||
/// * Break/Continue outside the given loop HIR ids.
|
||||
@@ -1739,6 +1766,7 @@ pub fn any_parent_is_automatically_derived(tcx: TyCtxt<'_>, node: HirId) -> bool
|
||||
/// ```rust,ignore
|
||||
/// if let Some(args) = match_function_call(cx, cmp_max_call, &paths::CMP_MAX);
|
||||
/// ```
|
||||
/// This function is deprecated. Use [`match_function_call_with_def_id`].
|
||||
pub fn match_function_call<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &'tcx Expr<'_>,
|
||||
@@ -1756,6 +1784,22 @@ pub fn match_function_call<'tcx>(
|
||||
None
|
||||
}
|
||||
|
||||
pub fn match_function_call_with_def_id<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &'tcx Expr<'_>,
|
||||
fun_def_id: DefId,
|
||||
) -> Option<&'tcx [Expr<'tcx>]> {
|
||||
if_chain! {
|
||||
if let ExprKind::Call(fun, args) = expr.kind;
|
||||
if let ExprKind::Path(ref qpath) = fun.kind;
|
||||
if cx.qpath_res(qpath, fun.hir_id).opt_def_id() == Some(fun_def_id);
|
||||
then {
|
||||
return Some(args);
|
||||
}
|
||||
};
|
||||
None
|
||||
}
|
||||
|
||||
/// Checks if the given `DefId` matches any of the paths. Returns the index of matching path, if
|
||||
/// any.
|
||||
///
|
||||
|
||||
@@ -627,7 +627,7 @@ pub enum Count<'tcx> {
|
||||
/// `FormatParamKind::Numbered`.
|
||||
Param(FormatParam<'tcx>),
|
||||
/// Not specified.
|
||||
Implied,
|
||||
Implied(Option<Span>),
|
||||
}
|
||||
|
||||
impl<'tcx> Count<'tcx> {
|
||||
@@ -638,8 +638,10 @@ impl<'tcx> Count<'tcx> {
|
||||
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_from_inner(values.format_string_span, inner?)),
|
||||
rpf::Count::CountIs(val) => Self::Is(val, span?),
|
||||
rpf::Count::CountIsName(name, _) => Self::Param(FormatParam::new(
|
||||
FormatParamKind::Named(Symbol::intern(name)),
|
||||
usage,
|
||||
@@ -661,12 +663,12 @@ impl<'tcx> Count<'tcx> {
|
||||
inner?,
|
||||
values,
|
||||
)?),
|
||||
rpf::Count::CountImplied => Self::Implied,
|
||||
rpf::Count::CountImplied => Self::Implied(span),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_implied(self) -> bool {
|
||||
matches!(self, Count::Implied)
|
||||
matches!(self, Count::Implied(_))
|
||||
}
|
||||
|
||||
pub fn param(self) -> Option<FormatParam<'tcx>> {
|
||||
@@ -675,6 +677,14 @@ impl<'tcx> Count<'tcx> {
|
||||
_ => 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
|
||||
@@ -738,8 +748,13 @@ impl<'tcx> FormatSpec<'tcx> {
|
||||
/// 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.width.is_implied()
|
||||
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.flags == 0
|
||||
@@ -757,6 +772,22 @@ pub struct FormatArg<'tcx> {
|
||||
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> {
|
||||
|
||||
52
clippy_utils/src/mir/maybe_storage_live.rs
Normal file
52
clippy_utils/src/mir/maybe_storage_live.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
use rustc_index::bit_set::BitSet;
|
||||
use rustc_middle::mir;
|
||||
use rustc_mir_dataflow::{AnalysisDomain, CallReturnPlaces, GenKill, GenKillAnalysis};
|
||||
|
||||
/// Determines liveness of each local purely based on `StorageLive`/`Dead`.
|
||||
#[derive(Copy, Clone)]
|
||||
pub(super) struct MaybeStorageLive;
|
||||
|
||||
impl<'tcx> AnalysisDomain<'tcx> for MaybeStorageLive {
|
||||
type Domain = BitSet<mir::Local>;
|
||||
const NAME: &'static str = "maybe_storage_live";
|
||||
|
||||
fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain {
|
||||
// bottom = dead
|
||||
BitSet::new_empty(body.local_decls.len())
|
||||
}
|
||||
|
||||
fn initialize_start_block(&self, body: &mir::Body<'tcx>, state: &mut Self::Domain) {
|
||||
for arg in body.args_iter() {
|
||||
state.insert(arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> GenKillAnalysis<'tcx> for MaybeStorageLive {
|
||||
type Idx = mir::Local;
|
||||
|
||||
fn statement_effect(&self, trans: &mut impl GenKill<Self::Idx>, stmt: &mir::Statement<'tcx>, _: mir::Location) {
|
||||
match stmt.kind {
|
||||
mir::StatementKind::StorageLive(l) => trans.gen(l),
|
||||
mir::StatementKind::StorageDead(l) => trans.kill(l),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn terminator_effect(
|
||||
&self,
|
||||
_trans: &mut impl GenKill<Self::Idx>,
|
||||
_terminator: &mir::Terminator<'tcx>,
|
||||
_loc: mir::Location,
|
||||
) {
|
||||
}
|
||||
|
||||
fn call_return_effect(
|
||||
&self,
|
||||
_trans: &mut impl GenKill<Self::Idx>,
|
||||
_block: mir::BasicBlock,
|
||||
_return_places: CallReturnPlaces<'_, 'tcx>,
|
||||
) {
|
||||
// Nothing to do when a call returns successfully
|
||||
}
|
||||
}
|
||||
164
clippy_utils/src/mir/mod.rs
Normal file
164
clippy_utils/src/mir/mod.rs
Normal file
@@ -0,0 +1,164 @@
|
||||
use rustc_hir::{Expr, HirId};
|
||||
use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor};
|
||||
use rustc_middle::mir::{
|
||||
traversal, Body, InlineAsmOperand, Local, Location, Place, StatementKind, TerminatorKind, START_BLOCK,
|
||||
};
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
|
||||
mod maybe_storage_live;
|
||||
|
||||
mod possible_borrower;
|
||||
pub use possible_borrower::PossibleBorrowerMap;
|
||||
|
||||
mod possible_origin;
|
||||
|
||||
mod transitive_relation;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct LocalUsage {
|
||||
/// The locations where the local is used, if any.
|
||||
pub local_use_locs: Vec<Location>,
|
||||
/// The locations where the local is consumed or mutated, if any.
|
||||
pub local_consume_or_mutate_locs: Vec<Location>,
|
||||
}
|
||||
|
||||
pub fn visit_local_usage(locals: &[Local], mir: &Body<'_>, location: Location) -> Option<Vec<LocalUsage>> {
|
||||
let init = vec![
|
||||
LocalUsage {
|
||||
local_use_locs: Vec::new(),
|
||||
local_consume_or_mutate_locs: Vec::new(),
|
||||
};
|
||||
locals.len()
|
||||
];
|
||||
|
||||
traversal::ReversePostorder::new(mir, location.block).try_fold(init, |usage, (tbb, tdata)| {
|
||||
// Give up on loops
|
||||
if tdata.terminator().successors().any(|s| s == location.block) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut v = V {
|
||||
locals,
|
||||
location,
|
||||
results: usage,
|
||||
};
|
||||
v.visit_basic_block_data(tbb, tdata);
|
||||
Some(v.results)
|
||||
})
|
||||
}
|
||||
|
||||
struct V<'a> {
|
||||
locals: &'a [Local],
|
||||
location: Location,
|
||||
results: Vec<LocalUsage>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> Visitor<'tcx> for V<'a> {
|
||||
fn visit_place(&mut self, place: &Place<'tcx>, ctx: PlaceContext, loc: Location) {
|
||||
if loc.block == self.location.block && loc.statement_index <= self.location.statement_index {
|
||||
return;
|
||||
}
|
||||
|
||||
let local = place.local;
|
||||
|
||||
for (i, self_local) in self.locals.iter().enumerate() {
|
||||
if local == *self_local {
|
||||
if !matches!(
|
||||
ctx,
|
||||
PlaceContext::MutatingUse(MutatingUseContext::Drop) | PlaceContext::NonUse(_)
|
||||
) {
|
||||
self.results[i].local_use_locs.push(loc);
|
||||
}
|
||||
if matches!(
|
||||
ctx,
|
||||
PlaceContext::NonMutatingUse(NonMutatingUseContext::Move)
|
||||
| PlaceContext::MutatingUse(MutatingUseContext::Borrow)
|
||||
) {
|
||||
self.results[i].local_consume_or_mutate_locs.push(loc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience wrapper around `visit_local_usage`.
|
||||
pub fn used_exactly_once(mir: &rustc_middle::mir::Body<'_>, local: rustc_middle::mir::Local) -> Option<bool> {
|
||||
visit_local_usage(
|
||||
&[local],
|
||||
mir,
|
||||
Location {
|
||||
block: START_BLOCK,
|
||||
statement_index: 0,
|
||||
},
|
||||
)
|
||||
.map(|mut vec| {
|
||||
let LocalUsage { local_use_locs, .. } = vec.remove(0);
|
||||
local_use_locs
|
||||
.into_iter()
|
||||
.filter(|location| !is_local_assignment(mir, local, *location))
|
||||
.count()
|
||||
== 1
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the `mir::Body` containing the node associated with `hir_id`.
|
||||
#[allow(clippy::module_name_repetitions)]
|
||||
pub fn enclosing_mir(tcx: TyCtxt<'_>, hir_id: HirId) -> &Body<'_> {
|
||||
let body_owner_local_def_id = tcx.hir().enclosing_body_owner(hir_id);
|
||||
tcx.optimized_mir(body_owner_local_def_id.to_def_id())
|
||||
}
|
||||
|
||||
/// Tries to determine the `Local` corresponding to `expr`, if any.
|
||||
/// This function is expensive and should be used sparingly.
|
||||
pub fn expr_local(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> Option<Local> {
|
||||
let mir = enclosing_mir(tcx, expr.hir_id);
|
||||
mir.local_decls.iter_enumerated().find_map(|(local, local_decl)| {
|
||||
if local_decl.source_info.span == expr.span {
|
||||
Some(local)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns a vector of `mir::Location` where `local` is assigned.
|
||||
pub fn local_assignments(mir: &Body<'_>, local: Local) -> Vec<Location> {
|
||||
let mut locations = Vec::new();
|
||||
for (block, data) in mir.basic_blocks.iter_enumerated() {
|
||||
for statement_index in 0..=data.statements.len() {
|
||||
let location = Location { block, statement_index };
|
||||
if is_local_assignment(mir, local, location) {
|
||||
locations.push(location);
|
||||
}
|
||||
}
|
||||
}
|
||||
locations
|
||||
}
|
||||
|
||||
// `is_local_assignment` is based on `is_place_assignment`:
|
||||
// https://github.com/rust-lang/rust/blob/b7413511dc85ec01ef4b91785f86614589ac6103/compiler/rustc_middle/src/mir/visit.rs#L1350
|
||||
fn is_local_assignment(mir: &Body<'_>, local: Local, location: Location) -> bool {
|
||||
let Location { block, statement_index } = location;
|
||||
let basic_block = &mir.basic_blocks[block];
|
||||
if statement_index < basic_block.statements.len() {
|
||||
let statement = &basic_block.statements[statement_index];
|
||||
if let StatementKind::Assign(box (place, _)) = statement.kind {
|
||||
place.as_local() == Some(local)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
let terminator = basic_block.terminator();
|
||||
match &terminator.kind {
|
||||
TerminatorKind::Call { destination, .. } => destination.as_local() == Some(local),
|
||||
TerminatorKind::InlineAsm { operands, .. } => operands.iter().any(|operand| {
|
||||
if let InlineAsmOperand::Out { place: Some(place), .. } = operand {
|
||||
place.as_local() == Some(local)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
241
clippy_utils/src/mir/possible_borrower.rs
Normal file
241
clippy_utils/src/mir/possible_borrower.rs
Normal file
@@ -0,0 +1,241 @@
|
||||
use super::{
|
||||
maybe_storage_live::MaybeStorageLive, possible_origin::PossibleOriginVisitor,
|
||||
transitive_relation::TransitiveRelation,
|
||||
};
|
||||
use crate::ty::is_copy;
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_index::bit_set::{BitSet, HybridBitSet};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::mir::{self, visit::Visitor as _, Mutability};
|
||||
use rustc_middle::ty::{self, visit::TypeVisitor};
|
||||
use rustc_mir_dataflow::{Analysis, ResultsCursor};
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
/// Collects the possible borrowers of each local.
|
||||
/// For example, `b = &a; c = &a;` will make `b` and (transitively) `c`
|
||||
/// possible borrowers of `a`.
|
||||
#[allow(clippy::module_name_repetitions)]
|
||||
struct PossibleBorrowerVisitor<'a, 'b, 'tcx> {
|
||||
possible_borrower: TransitiveRelation,
|
||||
body: &'b mir::Body<'tcx>,
|
||||
cx: &'a LateContext<'tcx>,
|
||||
possible_origin: FxHashMap<mir::Local, HybridBitSet<mir::Local>>,
|
||||
}
|
||||
|
||||
impl<'a, 'b, 'tcx> PossibleBorrowerVisitor<'a, 'b, 'tcx> {
|
||||
fn new(
|
||||
cx: &'a LateContext<'tcx>,
|
||||
body: &'b mir::Body<'tcx>,
|
||||
possible_origin: FxHashMap<mir::Local, HybridBitSet<mir::Local>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
possible_borrower: TransitiveRelation::default(),
|
||||
cx,
|
||||
body,
|
||||
possible_origin,
|
||||
}
|
||||
}
|
||||
|
||||
fn into_map(
|
||||
self,
|
||||
cx: &'a LateContext<'tcx>,
|
||||
maybe_live: ResultsCursor<'b, 'tcx, MaybeStorageLive>,
|
||||
) -> PossibleBorrowerMap<'b, 'tcx> {
|
||||
let mut map = FxHashMap::default();
|
||||
for row in (1..self.body.local_decls.len()).map(mir::Local::from_usize) {
|
||||
if is_copy(cx, self.body.local_decls[row].ty) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut borrowers = self.possible_borrower.reachable_from(row, self.body.local_decls.len());
|
||||
borrowers.remove(mir::Local::from_usize(0));
|
||||
if !borrowers.is_empty() {
|
||||
map.insert(row, borrowers);
|
||||
}
|
||||
}
|
||||
|
||||
let bs = BitSet::new_empty(self.body.local_decls.len());
|
||||
PossibleBorrowerMap {
|
||||
map,
|
||||
maybe_live,
|
||||
bitset: (bs.clone(), bs),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b, 'tcx> mir::visit::Visitor<'tcx> for PossibleBorrowerVisitor<'a, 'b, 'tcx> {
|
||||
fn visit_assign(&mut self, place: &mir::Place<'tcx>, rvalue: &mir::Rvalue<'_>, _location: mir::Location) {
|
||||
let lhs = place.local;
|
||||
match rvalue {
|
||||
mir::Rvalue::Ref(_, _, borrowed) => {
|
||||
self.possible_borrower.add(borrowed.local, lhs);
|
||||
},
|
||||
other => {
|
||||
if ContainsRegion
|
||||
.visit_ty(place.ty(&self.body.local_decls, self.cx.tcx).ty)
|
||||
.is_continue()
|
||||
{
|
||||
return;
|
||||
}
|
||||
rvalue_locals(other, |rhs| {
|
||||
if lhs != rhs {
|
||||
self.possible_borrower.add(rhs, lhs);
|
||||
}
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_terminator(&mut self, terminator: &mir::Terminator<'_>, _loc: mir::Location) {
|
||||
if let mir::TerminatorKind::Call {
|
||||
args,
|
||||
destination: mir::Place { local: dest, .. },
|
||||
..
|
||||
} = &terminator.kind
|
||||
{
|
||||
// TODO add doc
|
||||
// If the call returns something with lifetimes,
|
||||
// let's conservatively assume the returned value contains lifetime of all the arguments.
|
||||
// For example, given `let y: Foo<'a> = foo(x)`, `y` is considered to be a possible borrower of `x`.
|
||||
|
||||
let mut immutable_borrowers = vec![];
|
||||
let mut mutable_borrowers = vec![];
|
||||
|
||||
for op in args {
|
||||
match op {
|
||||
mir::Operand::Copy(p) | mir::Operand::Move(p) => {
|
||||
if let ty::Ref(_, _, Mutability::Mut) = self.body.local_decls[p.local].ty.kind() {
|
||||
mutable_borrowers.push(p.local);
|
||||
} else {
|
||||
immutable_borrowers.push(p.local);
|
||||
}
|
||||
},
|
||||
mir::Operand::Constant(..) => (),
|
||||
}
|
||||
}
|
||||
|
||||
let mut mutable_variables: Vec<mir::Local> = mutable_borrowers
|
||||
.iter()
|
||||
.filter_map(|r| self.possible_origin.get(r))
|
||||
.flat_map(HybridBitSet::iter)
|
||||
.collect();
|
||||
|
||||
if ContainsRegion.visit_ty(self.body.local_decls[*dest].ty).is_break() {
|
||||
mutable_variables.push(*dest);
|
||||
}
|
||||
|
||||
for y in mutable_variables {
|
||||
for x in &immutable_borrowers {
|
||||
self.possible_borrower.add(*x, y);
|
||||
}
|
||||
for x in &mutable_borrowers {
|
||||
self.possible_borrower.add(*x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ContainsRegion;
|
||||
|
||||
impl TypeVisitor<'_> for ContainsRegion {
|
||||
type BreakTy = ();
|
||||
|
||||
fn visit_region(&mut self, _: ty::Region<'_>) -> ControlFlow<Self::BreakTy> {
|
||||
ControlFlow::BREAK
|
||||
}
|
||||
}
|
||||
|
||||
fn rvalue_locals(rvalue: &mir::Rvalue<'_>, mut visit: impl FnMut(mir::Local)) {
|
||||
use rustc_middle::mir::Rvalue::{Aggregate, BinaryOp, Cast, CheckedBinaryOp, Repeat, UnaryOp, Use};
|
||||
|
||||
let mut visit_op = |op: &mir::Operand<'_>| match op {
|
||||
mir::Operand::Copy(p) | mir::Operand::Move(p) => visit(p.local),
|
||||
mir::Operand::Constant(..) => (),
|
||||
};
|
||||
|
||||
match rvalue {
|
||||
Use(op) | Repeat(op, _) | Cast(_, op, _) | UnaryOp(_, op) => visit_op(op),
|
||||
Aggregate(_, ops) => ops.iter().for_each(visit_op),
|
||||
BinaryOp(_, box (lhs, rhs)) | CheckedBinaryOp(_, box (lhs, rhs)) => {
|
||||
visit_op(lhs);
|
||||
visit_op(rhs);
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of `PossibleBorrowerVisitor`.
|
||||
#[allow(clippy::module_name_repetitions)]
|
||||
pub struct PossibleBorrowerMap<'b, 'tcx> {
|
||||
/// Mapping `Local -> its possible borrowers`
|
||||
pub map: FxHashMap<mir::Local, HybridBitSet<mir::Local>>,
|
||||
maybe_live: ResultsCursor<'b, 'tcx, MaybeStorageLive>,
|
||||
// Caches to avoid allocation of `BitSet` on every query
|
||||
pub bitset: (BitSet<mir::Local>, BitSet<mir::Local>),
|
||||
}
|
||||
|
||||
impl<'a, 'b, 'tcx> PossibleBorrowerMap<'b, 'tcx> {
|
||||
pub fn new(cx: &'a LateContext<'tcx>, mir: &'b mir::Body<'tcx>) -> Self {
|
||||
let possible_origin = {
|
||||
let mut vis = PossibleOriginVisitor::new(mir);
|
||||
vis.visit_body(mir);
|
||||
vis.into_map(cx)
|
||||
};
|
||||
let maybe_storage_live_result = MaybeStorageLive
|
||||
.into_engine(cx.tcx, mir)
|
||||
.pass_name("redundant_clone")
|
||||
.iterate_to_fixpoint()
|
||||
.into_results_cursor(mir);
|
||||
let mut vis = PossibleBorrowerVisitor::new(cx, mir, possible_origin);
|
||||
vis.visit_body(mir);
|
||||
vis.into_map(cx, maybe_storage_live_result)
|
||||
}
|
||||
|
||||
/// Returns true if the set of borrowers of `borrowed` living at `at` matches with `borrowers`.
|
||||
pub fn only_borrowers(&mut self, borrowers: &[mir::Local], borrowed: mir::Local, at: mir::Location) -> bool {
|
||||
self.bounded_borrowers(borrowers, borrowers, borrowed, at)
|
||||
}
|
||||
|
||||
/// Returns true if the set of borrowers of `borrowed` living at `at` includes at least `below`
|
||||
/// but no more than `above`.
|
||||
pub fn bounded_borrowers(
|
||||
&mut self,
|
||||
below: &[mir::Local],
|
||||
above: &[mir::Local],
|
||||
borrowed: mir::Local,
|
||||
at: mir::Location,
|
||||
) -> bool {
|
||||
self.maybe_live.seek_after_primary_effect(at);
|
||||
|
||||
self.bitset.0.clear();
|
||||
let maybe_live = &mut self.maybe_live;
|
||||
if let Some(bitset) = self.map.get(&borrowed) {
|
||||
for b in bitset.iter().filter(move |b| maybe_live.contains(*b)) {
|
||||
self.bitset.0.insert(b);
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.bitset.1.clear();
|
||||
for b in below {
|
||||
self.bitset.1.insert(*b);
|
||||
}
|
||||
|
||||
if !self.bitset.0.superset(&self.bitset.1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for b in above {
|
||||
self.bitset.0.remove(*b);
|
||||
}
|
||||
|
||||
self.bitset.0.is_empty()
|
||||
}
|
||||
|
||||
pub fn local_is_alive_at(&mut self, local: mir::Local, at: mir::Location) -> bool {
|
||||
self.maybe_live.seek_after_primary_effect(at);
|
||||
self.maybe_live.contains(local)
|
||||
}
|
||||
}
|
||||
59
clippy_utils/src/mir/possible_origin.rs
Normal file
59
clippy_utils/src/mir/possible_origin.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
use super::transitive_relation::TransitiveRelation;
|
||||
use crate::ty::is_copy;
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_index::bit_set::HybridBitSet;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::mir;
|
||||
|
||||
/// Collect possible borrowed for every `&mut` local.
|
||||
/// For example, `_1 = &mut _2` generate _1: {_2,...}
|
||||
/// Known Problems: not sure all borrowed are tracked
|
||||
#[allow(clippy::module_name_repetitions)]
|
||||
pub(super) struct PossibleOriginVisitor<'a, 'tcx> {
|
||||
possible_origin: TransitiveRelation,
|
||||
body: &'a mir::Body<'tcx>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> PossibleOriginVisitor<'a, 'tcx> {
|
||||
pub fn new(body: &'a mir::Body<'tcx>) -> Self {
|
||||
Self {
|
||||
possible_origin: TransitiveRelation::default(),
|
||||
body,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_map(self, cx: &LateContext<'tcx>) -> FxHashMap<mir::Local, HybridBitSet<mir::Local>> {
|
||||
let mut map = FxHashMap::default();
|
||||
for row in (1..self.body.local_decls.len()).map(mir::Local::from_usize) {
|
||||
if is_copy(cx, self.body.local_decls[row].ty) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut borrowers = self.possible_origin.reachable_from(row, self.body.local_decls.len());
|
||||
borrowers.remove(mir::Local::from_usize(0));
|
||||
if !borrowers.is_empty() {
|
||||
map.insert(row, borrowers);
|
||||
}
|
||||
}
|
||||
map
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> mir::visit::Visitor<'tcx> for PossibleOriginVisitor<'a, 'tcx> {
|
||||
fn visit_assign(&mut self, place: &mir::Place<'tcx>, rvalue: &mir::Rvalue<'_>, _location: mir::Location) {
|
||||
let lhs = place.local;
|
||||
match rvalue {
|
||||
// Only consider `&mut`, which can modify origin place
|
||||
mir::Rvalue::Ref(_, rustc_middle::mir::BorrowKind::Mut { .. }, borrowed) |
|
||||
// _2: &mut _;
|
||||
// _3 = move _2
|
||||
mir::Rvalue::Use(mir::Operand::Move(borrowed)) |
|
||||
// _3 = move _2 as &mut _;
|
||||
mir::Rvalue::Cast(_, mir::Operand::Move(borrowed), _)
|
||||
=> {
|
||||
self.possible_origin.add(lhs, borrowed.local);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
29
clippy_utils/src/mir/transitive_relation.rs
Normal file
29
clippy_utils/src/mir/transitive_relation.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_index::bit_set::HybridBitSet;
|
||||
use rustc_middle::mir;
|
||||
|
||||
#[derive(Default)]
|
||||
pub(super) struct TransitiveRelation {
|
||||
relations: FxHashMap<mir::Local, Vec<mir::Local>>,
|
||||
}
|
||||
|
||||
impl TransitiveRelation {
|
||||
pub fn add(&mut self, a: mir::Local, b: mir::Local) {
|
||||
self.relations.entry(a).or_default().push(b);
|
||||
}
|
||||
|
||||
pub fn reachable_from(&self, a: mir::Local, domain_size: usize) -> HybridBitSet<mir::Local> {
|
||||
let mut seen = HybridBitSet::new_empty(domain_size);
|
||||
let mut stack = vec![a];
|
||||
while let Some(u) = stack.pop() {
|
||||
if let Some(edges) = self.relations.get(&u) {
|
||||
for &v in edges {
|
||||
if seen.insert(v) {
|
||||
stack.push(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
seen
|
||||
}
|
||||
}
|
||||
@@ -69,12 +69,13 @@ impl<'a> NumericLiteral<'a> {
|
||||
|
||||
#[must_use]
|
||||
pub fn new(lit: &'a str, suffix: Option<&'a str>, float: bool) -> Self {
|
||||
let unsigned_lit = lit.trim_start_matches('-');
|
||||
// Determine delimiter for radix prefix, if present, and radix.
|
||||
let radix = if lit.starts_with("0x") {
|
||||
let radix = if unsigned_lit.starts_with("0x") {
|
||||
Radix::Hexadecimal
|
||||
} else if lit.starts_with("0b") {
|
||||
} else if unsigned_lit.starts_with("0b") {
|
||||
Radix::Binary
|
||||
} else if lit.starts_with("0o") {
|
||||
} else if unsigned_lit.starts_with("0o") {
|
||||
Radix::Octal
|
||||
} else {
|
||||
Radix::Decimal
|
||||
|
||||
@@ -16,25 +16,17 @@ pub const APPLICABILITY_VALUES: [[&str; 3]; 4] = [
|
||||
#[cfg(feature = "internal")]
|
||||
pub const DIAGNOSTIC_BUILDER: [&str; 3] = ["rustc_errors", "diagnostic_builder", "DiagnosticBuilder"];
|
||||
pub const ARC_PTR_EQ: [&str; 4] = ["alloc", "sync", "Arc", "ptr_eq"];
|
||||
pub const ASMUT_TRAIT: [&str; 3] = ["core", "convert", "AsMut"];
|
||||
pub const ASREF_TRAIT: [&str; 3] = ["core", "convert", "AsRef"];
|
||||
pub const BTREEMAP_CONTAINS_KEY: [&str; 6] = ["alloc", "collections", "btree", "map", "BTreeMap", "contains_key"];
|
||||
pub const BTREEMAP_ENTRY: [&str; 6] = ["alloc", "collections", "btree", "map", "entry", "Entry"];
|
||||
pub const BTREEMAP_INSERT: [&str; 6] = ["alloc", "collections", "btree", "map", "BTreeMap", "insert"];
|
||||
pub const BTREESET_ITER: [&str; 6] = ["alloc", "collections", "btree", "set", "BTreeSet", "iter"];
|
||||
pub const CLONE_TRAIT_METHOD: [&str; 4] = ["core", "clone", "Clone", "clone"];
|
||||
pub const COW: [&str; 3] = ["alloc", "borrow", "Cow"];
|
||||
pub const CORE_ITER_COLLECT: [&str; 6] = ["core", "iter", "traits", "iterator", "Iterator", "collect"];
|
||||
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_ITER_INTO_ITER: [&str; 6] = ["core", "iter", "traits", "collect", "IntoIterator", "into_iter"];
|
||||
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"];
|
||||
/// Preferably use the diagnostic item `sym::deref_method` where possible
|
||||
pub const DEREF_TRAIT_METHOD: [&str; 5] = ["core", "ops", "deref", "Deref", "deref"];
|
||||
pub const DISPLAY_TRAIT: [&str; 3] = ["core", "fmt", "Display"];
|
||||
#[cfg(feature = "internal")]
|
||||
pub const EARLY_CONTEXT: [&str; 2] = ["rustc_lint", "EarlyContext"];
|
||||
#[cfg(feature = "internal")]
|
||||
@@ -42,30 +34,22 @@ pub const EARLY_LINT_PASS: [&str; 3] = ["rustc_lint", "passes", "EarlyLintPass"]
|
||||
pub const EXIT: [&str; 3] = ["std", "process", "exit"];
|
||||
pub const F32_EPSILON: [&str; 4] = ["core", "f32", "<impl f32>", "EPSILON"];
|
||||
pub const F64_EPSILON: [&str; 4] = ["core", "f64", "<impl f64>", "EPSILON"];
|
||||
pub const FILE: [&str; 3] = ["std", "fs", "File"];
|
||||
pub const FILE_TYPE: [&str; 3] = ["std", "fs", "FileType"];
|
||||
pub const FROM_FROM: [&str; 4] = ["core", "convert", "From", "from"];
|
||||
pub const FROM_ITERATOR_METHOD: [&str; 6] = ["core", "iter", "traits", "collect", "FromIterator", "from_iter"];
|
||||
pub const FROM_STR_METHOD: [&str; 5] = ["core", "str", "traits", "FromStr", "from_str"];
|
||||
pub const FUTURE_FROM_GENERATOR: [&str; 3] = ["core", "future", "from_generator"];
|
||||
#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
|
||||
pub const FUTURES_IO_ASYNCREADEXT: [&str; 3] = ["futures_util", "io", "AsyncReadExt"];
|
||||
#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
|
||||
pub const FUTURES_IO_ASYNCWRITEEXT: [&str; 3] = ["futures_util", "io", "AsyncWriteExt"];
|
||||
pub const HASHMAP_CONTAINS_KEY: [&str; 6] = ["std", "collections", "hash", "map", "HashMap", "contains_key"];
|
||||
pub const HASHMAP_ENTRY: [&str; 5] = ["std", "collections", "hash", "map", "Entry"];
|
||||
pub const HASHMAP_INSERT: [&str; 6] = ["std", "collections", "hash", "map", "HashMap", "insert"];
|
||||
pub const HASHSET_ITER: [&str; 6] = ["std", "collections", "hash", "set", "HashSet", "iter"];
|
||||
#[cfg(feature = "internal")]
|
||||
pub const IDENT: [&str; 3] = ["rustc_span", "symbol", "Ident"];
|
||||
#[cfg(feature = "internal")]
|
||||
pub const IDENT_AS_STR: [&str; 4] = ["rustc_span", "symbol", "Ident", "as_str"];
|
||||
pub const INDEX: [&str; 3] = ["core", "ops", "Index"];
|
||||
pub const INDEX_MUT: [&str; 3] = ["core", "ops", "IndexMut"];
|
||||
pub const INSERT_STR: [&str; 4] = ["alloc", "string", "String", "insert_str"];
|
||||
pub const ITER_COUNT: [&str; 6] = ["core", "iter", "traits", "iterator", "Iterator", "count"];
|
||||
pub const ITER_EMPTY: [&str; 5] = ["core", "iter", "sources", "empty", "Empty"];
|
||||
pub const ITER_REPEAT: [&str; 5] = ["core", "iter", "sources", "repeat", "repeat"];
|
||||
pub const ITERTOOLS_NEXT_TUPLE: [&str; 3] = ["itertools", "Itertools", "next_tuple"];
|
||||
#[cfg(feature = "internal")]
|
||||
pub const KW_MODULE: [&str; 3] = ["rustc_span", "symbol", "kw"];
|
||||
@@ -76,13 +60,7 @@ pub const LATE_LINT_PASS: [&str; 3] = ["rustc_lint", "passes", "LateLintPass"];
|
||||
#[cfg(feature = "internal")]
|
||||
pub const LINT: [&str; 2] = ["rustc_lint_defs", "Lint"];
|
||||
pub const MEM_SWAP: [&str; 3] = ["core", "mem", "swap"];
|
||||
pub const MUTEX_GUARD: [&str; 4] = ["std", "sync", "mutex", "MutexGuard"];
|
||||
pub const OPEN_OPTIONS: [&str; 3] = ["std", "fs", "OpenOptions"];
|
||||
/// Preferably use the diagnostic item `sym::Option` where possible
|
||||
pub const OPTION: [&str; 3] = ["core", "option", "Option"];
|
||||
pub const OPTION_NONE: [&str; 4] = ["core", "option", "Option", "None"];
|
||||
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 const PARKING_LOT_MUTEX_GUARD: [&str; 3] = ["lock_api", "mutex", "MutexGuard"];
|
||||
@@ -95,8 +73,6 @@ pub const PERMISSIONS: [&str; 3] = ["std", "fs", "Permissions"];
|
||||
#[cfg_attr(not(unix), allow(clippy::invalid_paths))]
|
||||
pub const PERMISSIONS_FROM_MODE: [&str; 6] = ["std", "os", "unix", "fs", "PermissionsExt", "from_mode"];
|
||||
pub const POLL: [&str; 4] = ["core", "task", "poll", "Poll"];
|
||||
pub const POLL_PENDING: [&str; 5] = ["core", "task", "poll", "Poll", "Pending"];
|
||||
pub const POLL_READY: [&str; 5] = ["core", "task", "poll", "Poll", "Ready"];
|
||||
pub const PTR_COPY: [&str; 3] = ["core", "intrinsics", "copy"];
|
||||
pub const PTR_COPY_NONOVERLAPPING: [&str; 3] = ["core", "intrinsics", "copy_nonoverlapping"];
|
||||
pub const PTR_EQ: [&str; 3] = ["core", "ptr", "eq"];
|
||||
@@ -119,26 +95,14 @@ pub const RANGE_ARGUMENT_TRAIT: [&str; 3] = ["core", "ops", "RangeBounds"];
|
||||
pub const RC_PTR_EQ: [&str; 4] = ["alloc", "rc", "Rc", "ptr_eq"];
|
||||
pub const REFCELL_REF: [&str; 3] = ["core", "cell", "Ref"];
|
||||
pub const REFCELL_REFMUT: [&str; 3] = ["core", "cell", "RefMut"];
|
||||
#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
|
||||
pub const REGEX_BUILDER_NEW: [&str; 5] = ["regex", "re_builder", "unicode", "RegexBuilder", "new"];
|
||||
#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
|
||||
pub const REGEX_BYTES_BUILDER_NEW: [&str; 5] = ["regex", "re_builder", "bytes", "RegexBuilder", "new"];
|
||||
#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
|
||||
pub const REGEX_BYTES_NEW: [&str; 4] = ["regex", "re_bytes", "Regex", "new"];
|
||||
#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
|
||||
pub const REGEX_BYTES_SET_NEW: [&str; 5] = ["regex", "re_set", "bytes", "RegexSet", "new"];
|
||||
#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
|
||||
pub const REGEX_NEW: [&str; 4] = ["regex", "re_unicode", "Regex", "new"];
|
||||
#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
|
||||
pub const REGEX_SET_NEW: [&str; 5] = ["regex", "re_set", "unicode", "RegexSet", "new"];
|
||||
/// Preferably use the diagnostic item `sym::Result` where possible
|
||||
pub const RESULT: [&str; 3] = ["core", "result", "Result"];
|
||||
pub const RESULT_ERR: [&str; 4] = ["core", "result", "Result", "Err"];
|
||||
pub const RESULT_OK: [&str; 4] = ["core", "result", "Result", "Ok"];
|
||||
#[cfg(feature = "internal")]
|
||||
pub const RUSTC_VERSION: [&str; 2] = ["rustc_semver", "RustcVersion"];
|
||||
pub const RWLOCK_READ_GUARD: [&str; 4] = ["std", "sync", "rwlock", "RwLockReadGuard"];
|
||||
pub const RWLOCK_WRITE_GUARD: [&str; 4] = ["std", "sync", "rwlock", "RwLockWriteGuard"];
|
||||
pub const SERDE_DESERIALIZE: [&str; 3] = ["serde", "de", "Deserialize"];
|
||||
pub const SERDE_DE_VISITOR: [&str; 3] = ["serde", "de", "Visitor"];
|
||||
pub const SLICE_FROM_RAW_PARTS: [&str; 4] = ["core", "slice", "raw", "from_raw_parts"];
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
//! Contains utility functions to generate suggestions.
|
||||
#![deny(clippy::missing_docs_in_private_items)]
|
||||
|
||||
use crate::source::{snippet, snippet_opt, snippet_with_applicability, snippet_with_macro_callsite};
|
||||
use crate::source::{
|
||||
snippet, snippet_opt, snippet_with_applicability, snippet_with_context, snippet_with_macro_callsite,
|
||||
};
|
||||
use crate::ty::expr_sig;
|
||||
use crate::{get_parent_expr_for_hir, higher};
|
||||
use rustc_ast::util::parser::AssocOp;
|
||||
@@ -110,7 +112,7 @@ impl<'a> Sugg<'a> {
|
||||
if expr.span.ctxt() == ctxt {
|
||||
Self::hir_from_snippet(expr, |span| snippet(cx, span, default))
|
||||
} else {
|
||||
let snip = snippet_with_applicability(cx, expr.span, default, applicability);
|
||||
let (snip, _) = snippet_with_context(cx, expr.span, ctxt, default, applicability);
|
||||
Sugg::NonParen(snip)
|
||||
}
|
||||
}
|
||||
@@ -1052,12 +1054,7 @@ impl<'tcx> Delegate<'tcx> for DerefDelegate<'_, 'tcx> {
|
||||
|
||||
fn mutate(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {}
|
||||
|
||||
fn fake_read(
|
||||
&mut self,
|
||||
_: &rustc_hir_typeck::expr_use_visitor::PlaceWithHirId<'tcx>,
|
||||
_: FakeReadCause,
|
||||
_: HirId,
|
||||
) {}
|
||||
fn fake_read(&mut self, _: &PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
Reference in New Issue
Block a user