mirror of
https://github.com/rust-lang/rust.git
synced 2026-05-21 17:52:12 +03:00
Auto merge of #156559 - JonathanBrouwer:rollup-WZRJhi0, r=JonathanBrouwer
Rollup of 7 pull requests Successful merges: - rust-lang/rust#156552 (Clippy subtree update) - rust-lang/rust#156344 (Do not index past end of buffer when checking heuristic in error index syntax highlighter) - rust-lang/rust#156500 (Privacy: move macros handling to early stage) - rust-lang/rust#156260 (test: suppress deprecation warning) - rust-lang/rust#156413 (rustdoc: Correctness & perf improvements to link-to-definition) - rust-lang/rust#156539 (Add `ChildExt::kill_process_group`) - rust-lang/rust#156540 (use `deref_patterns` in `rustdoc` instead of `box_patterns`)
This commit is contained in:
+2
-2
@@ -5948,9 +5948,9 @@ checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
|
||||
|
||||
[[package]]
|
||||
name = "ui_test"
|
||||
version = "0.30.4"
|
||||
version = "0.30.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ada249620d81f010b9a1472b63a5077ac7c722dd0f4bacf6528b313d0b8c15d8"
|
||||
checksum = "980133b75aa9a95dc94feaf629d92d22c1172186f1fa1266b91f5b91414cf9a5"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.11.5",
|
||||
"anyhow",
|
||||
|
||||
@@ -106,7 +106,7 @@ pub fn highlight_rustc_lexer(&mut self, code: &str, buf: &mut Vec<u8>) -> io::Re
|
||||
// This heuristic test is to detect if the identifier is
|
||||
// a function call. If it is, then the function identifier is
|
||||
// colored differently.
|
||||
if code[*len_accum..*len_accum + len + 1].ends_with('(') {
|
||||
if code[*len_accum + len..].starts_with('(') {
|
||||
style = style.fg_color(Some(Color::Ansi(FUNCTION_COLOR)));
|
||||
}
|
||||
// The `derive` keyword is colored differently.
|
||||
|
||||
@@ -65,6 +65,10 @@ fn at_level_mut(&mut self, level: Level) -> &mut Visibility {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn public_at_level(&self) -> Option<Level> {
|
||||
Level::all_levels().into_iter().find(|&level| self.is_public_at_level(level))
|
||||
}
|
||||
|
||||
pub fn is_public_at_level(&self, level: Level) -> bool {
|
||||
self.at_level(level).is_public()
|
||||
}
|
||||
@@ -120,9 +124,7 @@ pub fn is_directly_public(&self, id: LocalDefId) -> bool {
|
||||
}
|
||||
|
||||
pub fn public_at_level(&self, id: LocalDefId) -> Option<Level> {
|
||||
self.effective_vis(id).and_then(|effective_vis| {
|
||||
Level::all_levels().into_iter().find(|&level| effective_vis.is_public_at_level(level))
|
||||
})
|
||||
self.effective_vis(id).and_then(|effective_vis| effective_vis.public_at_level())
|
||||
}
|
||||
|
||||
pub fn update_root(&mut self) {
|
||||
|
||||
@@ -179,6 +179,12 @@ pub struct ResolverGlobalCtxt {
|
||||
/// Item with a given `LocalDefId` was defined during macro expansion with ID `ExpnId`.
|
||||
pub expn_that_defined: UnordMap<LocalDefId, ExpnId>,
|
||||
pub effective_visibilities: EffectiveVisibilities,
|
||||
// FIXME: This table contains ADTs reachable from macro 2.0.
|
||||
// Currently, reachability of a definition from a macro is determined by nominal visibility
|
||||
// (see `compute_effective_visibilities`). This is incorrect and leads to the necessity
|
||||
// of traversing ADT fields in `rustc_privacy`. Remove this workaround once the
|
||||
// correct reachability logic is implemented for macros.
|
||||
pub macro_reachable_adts: FxIndexMap<LocalDefId, FxIndexSet<LocalDefId>>,
|
||||
pub extern_crate_map: UnordMap<LocalDefId, CrateNum>,
|
||||
pub maybe_unused_trait_imports: FxIndexSet<LocalDefId>,
|
||||
pub module_children: LocalDefIdMap<Vec<ModChild>>,
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
ItemIsPrivate, PrivateInterfacesOrBoundsLint, ReportEffectiveVisibility, UnnameableTypesLint,
|
||||
UnnamedItemIsPrivate,
|
||||
};
|
||||
use rustc_ast::MacroDef;
|
||||
use rustc_ast::visit::{VisitorResult, try_visit};
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_data_structures::intern::Interned;
|
||||
@@ -34,7 +33,6 @@
|
||||
};
|
||||
use rustc_middle::{bug, span_bug};
|
||||
use rustc_session::lint;
|
||||
use rustc_span::hygiene::Transparency;
|
||||
use rustc_span::{Ident, Span, Symbol, sym};
|
||||
use tracing::debug;
|
||||
|
||||
@@ -419,22 +417,8 @@ fn new_min<const SHALLOW: bool>(
|
||||
/// The embargo visitor, used to determine the exports of the AST.
|
||||
struct EmbargoVisitor<'tcx> {
|
||||
tcx: TyCtxt<'tcx>,
|
||||
|
||||
/// Effective visibilities for reachable nodes.
|
||||
effective_visibilities: EffectiveVisibilities,
|
||||
/// A set of pairs corresponding to modules, where the first module is
|
||||
/// reachable via a macro that's defined in the second module. This cannot
|
||||
/// be represented as reachable because it can't handle the following case:
|
||||
///
|
||||
/// pub mod n { // Should be `Public`
|
||||
/// pub(crate) mod p { // Should *not* be accessible
|
||||
/// pub fn f() -> i32 { 12 } // Must be `Reachable`
|
||||
/// }
|
||||
/// }
|
||||
/// pub macro m() {
|
||||
/// n::p::f()
|
||||
/// }
|
||||
macro_reachable: FxHashSet<(LocalModDefId, LocalModDefId)>,
|
||||
/// Has something changed in the level map?
|
||||
changed: bool,
|
||||
}
|
||||
@@ -509,161 +493,6 @@ fn reach_through_impl_trait(
|
||||
level: Level::ReachableThroughImplTrait,
|
||||
}
|
||||
}
|
||||
|
||||
// We have to make sure that the items that macros might reference
|
||||
// are reachable, since they might be exported transitively.
|
||||
fn update_reachability_from_macro(
|
||||
&mut self,
|
||||
local_def_id: LocalDefId,
|
||||
md: &MacroDef,
|
||||
macro_ev: EffectiveVisibility,
|
||||
) {
|
||||
// Non-opaque macros cannot make other items more accessible than they already are.
|
||||
let hir_id = self.tcx.local_def_id_to_hir_id(local_def_id);
|
||||
let attrs = self.tcx.hir_attrs(hir_id);
|
||||
|
||||
if find_attr!(attrs, RustcMacroTransparency(x) => *x)
|
||||
.unwrap_or(Transparency::fallback(md.macro_rules))
|
||||
!= Transparency::Opaque
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let macro_module_def_id = self.tcx.local_parent(local_def_id);
|
||||
if self.tcx.def_kind(macro_module_def_id) != DefKind::Mod {
|
||||
// The macro's parent doesn't correspond to a `mod`, return early (#63164, #65252).
|
||||
return;
|
||||
}
|
||||
// FIXME(typed_def_id): Introduce checked constructors that check def_kind.
|
||||
let macro_module_def_id = LocalModDefId::new_unchecked(macro_module_def_id);
|
||||
|
||||
if self.effective_visibilities.public_at_level(local_def_id).is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Since we are starting from an externally visible module,
|
||||
// all the parents in the loop below are also guaranteed to be modules.
|
||||
let mut module_def_id = macro_module_def_id;
|
||||
loop {
|
||||
let changed_reachability =
|
||||
self.update_macro_reachable(module_def_id, macro_module_def_id, macro_ev);
|
||||
if changed_reachability || module_def_id == LocalModDefId::CRATE_DEF_ID {
|
||||
break;
|
||||
}
|
||||
module_def_id = LocalModDefId::new_unchecked(self.tcx.local_parent(module_def_id));
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the item as being reachable through a macro defined in the given
|
||||
/// module. Returns `true` if the level has changed.
|
||||
fn update_macro_reachable(
|
||||
&mut self,
|
||||
module_def_id: LocalModDefId,
|
||||
defining_mod: LocalModDefId,
|
||||
macro_ev: EffectiveVisibility,
|
||||
) -> bool {
|
||||
if self.macro_reachable.insert((module_def_id, defining_mod)) {
|
||||
for child in self.tcx.module_children_local(module_def_id.to_local_def_id()) {
|
||||
if let Res::Def(def_kind, def_id) = child.res
|
||||
&& let Some(def_id) = def_id.as_local()
|
||||
&& child.vis.is_accessible_from(defining_mod, self.tcx)
|
||||
{
|
||||
let vis = self.tcx.local_visibility(def_id);
|
||||
self.update_macro_reachable_def(def_id, def_kind, vis, defining_mod, macro_ev);
|
||||
}
|
||||
}
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn update_macro_reachable_def(
|
||||
&mut self,
|
||||
def_id: LocalDefId,
|
||||
def_kind: DefKind,
|
||||
vis: ty::Visibility,
|
||||
module: LocalModDefId,
|
||||
macro_ev: EffectiveVisibility,
|
||||
) {
|
||||
self.update(def_id, macro_ev, Level::Reachable);
|
||||
match def_kind {
|
||||
// No type privacy, so can be directly marked as reachable.
|
||||
DefKind::Const { .. }
|
||||
| DefKind::Static { .. }
|
||||
| DefKind::TraitAlias
|
||||
| DefKind::TyAlias => {
|
||||
if vis.is_accessible_from(module, self.tcx) {
|
||||
self.update(def_id, macro_ev, Level::Reachable);
|
||||
}
|
||||
}
|
||||
|
||||
// Hygiene isn't really implemented for `macro_rules!` macros at the
|
||||
// moment. Accordingly, marking them as reachable is unwise. `macro` macros
|
||||
// have normal hygiene, so we can treat them like other items without type
|
||||
// privacy and mark them reachable.
|
||||
DefKind::Macro(_) => {
|
||||
let item = self.tcx.hir_expect_item(def_id);
|
||||
if let hir::ItemKind::Macro(_, MacroDef { macro_rules: false, .. }, _) = item.kind {
|
||||
if vis.is_accessible_from(module, self.tcx) {
|
||||
self.update(def_id, macro_ev, Level::Reachable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We can't use a module name as the final segment of a path, except
|
||||
// in use statements. Since re-export checking doesn't consider
|
||||
// hygiene these don't need to be marked reachable. The contents of
|
||||
// the module, however may be reachable.
|
||||
DefKind::Mod => {
|
||||
if vis.is_accessible_from(module, self.tcx) {
|
||||
self.update_macro_reachable(
|
||||
LocalModDefId::new_unchecked(def_id),
|
||||
module,
|
||||
macro_ev,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DefKind::Struct | DefKind::Union => {
|
||||
// While structs and unions have type privacy, their fields do not.
|
||||
let struct_def = self.tcx.adt_def(def_id);
|
||||
for field in &struct_def.non_enum_variant().fields {
|
||||
let def_id = field.did.expect_local();
|
||||
let field_vis = self.tcx.local_visibility(def_id);
|
||||
if field_vis.is_accessible_from(module, self.tcx) {
|
||||
self.reach(def_id, macro_ev).ty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// These have type privacy, so are not reachable unless they're
|
||||
// public, or are not namespaced at all.
|
||||
DefKind::AssocConst { .. }
|
||||
| DefKind::AssocTy
|
||||
| DefKind::ConstParam
|
||||
| DefKind::Ctor(_, _)
|
||||
| DefKind::Enum
|
||||
| DefKind::ForeignTy
|
||||
| DefKind::Fn
|
||||
| DefKind::OpaqueTy
|
||||
| DefKind::AssocFn
|
||||
| DefKind::Trait
|
||||
| DefKind::TyParam
|
||||
| DefKind::Variant
|
||||
| DefKind::LifetimeParam
|
||||
| DefKind::ExternCrate
|
||||
| DefKind::Use
|
||||
| DefKind::ForeignMod
|
||||
| DefKind::AnonConst
|
||||
| DefKind::InlineConst
|
||||
| DefKind::Field
|
||||
| DefKind::GlobalAsm
|
||||
| DefKind::Impl { .. }
|
||||
| DefKind::Closure
|
||||
| DefKind::SyntheticCoroutineBody => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> EmbargoVisitor<'tcx> {
|
||||
@@ -689,13 +518,8 @@ fn check_def_id(&mut self, owner_id: OwnerId) {
|
||||
DefKind::Use | DefKind::ExternCrate | DefKind::GlobalAsm => {}
|
||||
// The interface is empty, and all nested items are processed by `check_def_id`.
|
||||
DefKind::Mod => {}
|
||||
DefKind::Macro { .. } => {
|
||||
if let Some(item_ev) = item_ev {
|
||||
let (_, macro_def, _) =
|
||||
self.tcx.hir_expect_item(owner_id.def_id).expect_macro();
|
||||
self.update_reachability_from_macro(owner_id.def_id, macro_def, item_ev);
|
||||
}
|
||||
}
|
||||
// Effective visibilities for macros are processed earlier.
|
||||
DefKind::Macro { .. } => {}
|
||||
DefKind::ForeignTy
|
||||
| DefKind::Const { .. }
|
||||
| DefKind::Static { .. }
|
||||
@@ -1815,7 +1639,6 @@ fn effective_visibilities(tcx: TyCtxt<'_>, (): ()) -> &EffectiveVisibilities {
|
||||
let mut visitor = EmbargoVisitor {
|
||||
tcx,
|
||||
effective_visibilities: tcx.resolutions(()).effective_visibilities.clone(),
|
||||
macro_reachable: Default::default(),
|
||||
changed: false,
|
||||
};
|
||||
|
||||
@@ -1872,6 +1695,26 @@ fn effective_visibilities(tcx: TyCtxt<'_>, (): ()) -> &EffectiveVisibilities {
|
||||
visitor.changed = false;
|
||||
}
|
||||
|
||||
// FIXME: remove this once proper support for defs reachability from macros is implemented.
|
||||
// See `ResolverGlobalCtxt::macro_reachable_adts` comment.
|
||||
for (&adt_def_id, macro_mods) in &tcx.resolutions(()).macro_reachable_adts {
|
||||
let struct_def = tcx.adt_def(adt_def_id);
|
||||
let Some(struct_ev) = visitor.effective_visibilities.effective_vis(adt_def_id).copied()
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
for field in &struct_def.non_enum_variant().fields {
|
||||
let def_id = field.did.expect_local();
|
||||
let field_vis = tcx.local_visibility(def_id);
|
||||
|
||||
for ¯o_mod in macro_mods {
|
||||
if field_vis.is_accessible_from(macro_mod, tcx) {
|
||||
visitor.reach(def_id, struct_ev).ty();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let crate_items = tcx.hir_crate_items(());
|
||||
loop {
|
||||
for id in crate_items.free_items() {
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
use std::mem;
|
||||
|
||||
use rustc_ast::visit::Visitor;
|
||||
use rustc_ast::{Crate, EnumDef, ast, visit};
|
||||
use rustc_ast::{Attribute, Crate, EnumDef, ast, visit};
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::def_id::{CRATE_DEF_ID, LocalDefId};
|
||||
use rustc_middle::middle::privacy::{EffectiveVisibilities, EffectiveVisibility, Level};
|
||||
use rustc_middle::ty::Visibility;
|
||||
use rustc_span::sym;
|
||||
use tracing::info;
|
||||
|
||||
use crate::{Decl, DeclKind, Resolver};
|
||||
@@ -34,6 +36,19 @@ pub(crate) struct EffectiveVisibilitiesVisitor<'a, 'ra, 'tcx> {
|
||||
import_effective_visibilities: EffectiveVisibilities<Decl<'ra>>,
|
||||
// It's possible to recalculate this at any point, but it's relatively expensive.
|
||||
current_private_vis: Visibility,
|
||||
/// A set of pairs corresponding to modules, where the first module is
|
||||
/// reachable via a macro that's defined in the second module. This cannot
|
||||
/// be represented as reachable because it can't handle the following case:
|
||||
///
|
||||
/// pub mod n { // Should be `Public`
|
||||
/// pub(crate) mod p { // Should *not* be accessible
|
||||
/// pub fn f() -> i32 { 12 } // Must be `Reachable`
|
||||
/// }
|
||||
/// }
|
||||
/// pub macro m() {
|
||||
/// n::p::f()
|
||||
/// }
|
||||
macro_reachable: FxHashSet<(LocalDefId, LocalDefId)>,
|
||||
changed: bool,
|
||||
}
|
||||
|
||||
@@ -71,6 +86,7 @@ pub(crate) fn compute_effective_visibilities<'c>(
|
||||
def_effective_visibilities: Default::default(),
|
||||
import_effective_visibilities: Default::default(),
|
||||
current_private_vis: Visibility::Restricted(CRATE_DEF_ID),
|
||||
macro_reachable: Default::default(),
|
||||
changed: true,
|
||||
};
|
||||
|
||||
@@ -210,6 +226,123 @@ fn update_field(&mut self, def_id: LocalDefId, parent_id: LocalDefId) {
|
||||
let nominal_vis = self.r.tcx.local_visibility(def_id);
|
||||
self.update_def(def_id, nominal_vis, ParentId::Def(parent_id), self.current_private_vis);
|
||||
}
|
||||
|
||||
fn update_macro(&mut self, def_id: LocalDefId, inherited_effective_vis: EffectiveVisibility) {
|
||||
let max_vis = Some(self.r.tcx.local_visibility(def_id));
|
||||
let priv_vis = if def_id == CRATE_DEF_ID {
|
||||
Visibility::Restricted(CRATE_DEF_ID)
|
||||
} else {
|
||||
self.r.private_vis_def(def_id)
|
||||
};
|
||||
self.changed |= self.def_effective_visibilities.update(
|
||||
def_id,
|
||||
max_vis,
|
||||
priv_vis,
|
||||
inherited_effective_vis,
|
||||
Level::Reachable,
|
||||
self.r.tcx,
|
||||
);
|
||||
}
|
||||
|
||||
// We have to make sure that the items that macros might reference
|
||||
// are reachable, since they might be exported transitively.
|
||||
fn update_reachability_from_macro(
|
||||
&mut self,
|
||||
local_def_id: LocalDefId,
|
||||
md: &ast::MacroDef,
|
||||
attrs: &[Attribute],
|
||||
) {
|
||||
// Non-opaque macros cannot make other items more accessible than they already are.
|
||||
if rustc_ast::attr::find_by_name(attrs, sym::rustc_macro_transparency)
|
||||
.map_or(md.macro_rules, |attr| attr.value_str() != Some(sym::opaque))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let macro_module_def_id = self.r.tcx.local_parent(local_def_id);
|
||||
if self.r.tcx.def_kind(macro_module_def_id) != DefKind::Mod {
|
||||
// The macro's parent doesn't correspond to a `mod`, return early (#63164, #65252).
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(macro_ev) = self
|
||||
.def_effective_visibilities
|
||||
.effective_vis(local_def_id)
|
||||
.filter(|ev| ev.public_at_level().is_some())
|
||||
.copied()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Since we are starting from an externally visible module,
|
||||
// all the parents in the loop below are also guaranteed to be modules.
|
||||
let mut module_def_id = macro_module_def_id;
|
||||
loop {
|
||||
let changed_reachability =
|
||||
self.update_macro_reachable(module_def_id, macro_module_def_id, macro_ev);
|
||||
if changed_reachability || module_def_id == CRATE_DEF_ID {
|
||||
break;
|
||||
}
|
||||
module_def_id = self.r.tcx.local_parent(module_def_id);
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the item as being reachable through a macro defined in the given
|
||||
/// module. Returns `true` if the level has changed.
|
||||
fn update_macro_reachable(
|
||||
&mut self,
|
||||
module_def_id: LocalDefId,
|
||||
defining_mod: LocalDefId,
|
||||
macro_ev: EffectiveVisibility,
|
||||
) -> bool {
|
||||
if self.macro_reachable.insert((module_def_id, defining_mod)) {
|
||||
let module = self.r.expect_module(module_def_id.to_def_id());
|
||||
for (_, name_resolution) in self.r.resolutions(module).borrow().iter() {
|
||||
let Some(decl) = name_resolution.borrow().best_decl() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if let Res::Def(def_kind, def_id) = decl.res()
|
||||
&& let Some(def_id) = def_id.as_local()
|
||||
// FIXME: defs should be checked with `EffectiveVisibilities::is_reachable`.
|
||||
&& decl.vis().is_accessible_from(defining_mod, self.r.tcx)
|
||||
{
|
||||
let vis = self.r.tcx.local_visibility(def_id);
|
||||
self.update_macro_reachable_def(def_id, def_kind, vis, defining_mod, macro_ev);
|
||||
}
|
||||
}
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn update_macro_reachable_def(
|
||||
&mut self,
|
||||
def_id: LocalDefId,
|
||||
def_kind: DefKind,
|
||||
vis: Visibility,
|
||||
module: LocalDefId,
|
||||
macro_ev: EffectiveVisibility,
|
||||
) {
|
||||
self.update_macro(def_id, macro_ev);
|
||||
|
||||
match def_kind {
|
||||
DefKind::Mod => {
|
||||
if vis.is_accessible_from(module, self.r.tcx) {
|
||||
self.update_macro_reachable(def_id, module, macro_ev);
|
||||
}
|
||||
}
|
||||
DefKind::Struct | DefKind::Union => {
|
||||
self.r
|
||||
.macro_reachable_adts
|
||||
.entry(def_id)
|
||||
.or_insert_with(Default::default)
|
||||
.insert(module);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'ra, 'tcx> Visitor<'a> for EffectiveVisibilitiesVisitor<'a, 'ra, 'tcx> {
|
||||
@@ -217,7 +350,7 @@ fn visit_item(&mut self, item: &'a ast::Item) {
|
||||
let def_id = self.r.owner_def_id(item.id);
|
||||
// Update effective visibilities of nested items.
|
||||
// If it's a mod, also make the visitor walk all of its items
|
||||
match item.kind {
|
||||
match &item.kind {
|
||||
// Resolved in rustc_privacy when types are available
|
||||
ast::ItemKind::Impl(..) => return,
|
||||
|
||||
@@ -234,7 +367,7 @@ fn visit_item(&mut self, item: &'a ast::Item) {
|
||||
self.current_private_vis = prev_private_vis;
|
||||
}
|
||||
|
||||
ast::ItemKind::Enum(_, _, EnumDef { ref variants }) => {
|
||||
ast::ItemKind::Enum(_, _, EnumDef { variants }) => {
|
||||
self.set_bindings_effective_visibilities(def_id);
|
||||
for variant in variants {
|
||||
let variant_def_id = self.r.child_def_id(item.id, variant.id);
|
||||
@@ -244,7 +377,7 @@ fn visit_item(&mut self, item: &'a ast::Item) {
|
||||
}
|
||||
}
|
||||
|
||||
ast::ItemKind::Struct(_, _, ref def) | ast::ItemKind::Union(_, _, ref def) => {
|
||||
ast::ItemKind::Struct(_, _, def) | ast::ItemKind::Union(_, _, def) => {
|
||||
for field in def.fields() {
|
||||
self.update_field(self.r.child_def_id(item.id, field.id), def_id);
|
||||
}
|
||||
@@ -254,6 +387,10 @@ fn visit_item(&mut self, item: &'a ast::Item) {
|
||||
self.set_bindings_effective_visibilities(def_id);
|
||||
}
|
||||
|
||||
ast::ItemKind::MacroDef(_, macro_def) => {
|
||||
self.update_reachability_from_macro(def_id, macro_def, &item.attrs);
|
||||
}
|
||||
|
||||
ast::ItemKind::ExternCrate(..)
|
||||
| ast::ItemKind::Use(..)
|
||||
| ast::ItemKind::Static(..)
|
||||
@@ -262,7 +399,6 @@ fn visit_item(&mut self, item: &'a ast::Item) {
|
||||
| ast::ItemKind::GlobalAsm(..)
|
||||
| ast::ItemKind::TyAlias(..)
|
||||
| ast::ItemKind::TraitAlias(..)
|
||||
| ast::ItemKind::MacroDef(..)
|
||||
| ast::ItemKind::ForeignMod(..)
|
||||
| ast::ItemKind::Fn(..)
|
||||
| ast::ItemKind::Delegation(..) => return,
|
||||
|
||||
@@ -1536,6 +1536,8 @@ pub struct Resolver<'ra, 'tcx> {
|
||||
stripped_cfg_items: Vec<StrippedCfgItem<NodeId>> = Vec::new(),
|
||||
|
||||
effective_visibilities: EffectiveVisibilities,
|
||||
macro_reachable_adts: FxIndexMap<LocalDefId, FxIndexSet<LocalDefId>>,
|
||||
|
||||
doc_link_resolutions: FxIndexMap<LocalDefId, DocLinkResMap>,
|
||||
doc_link_traits_in_scope: FxIndexMap<LocalDefId, Vec<DefId>>,
|
||||
all_macro_rules: UnordSet<Symbol> = Default::default(),
|
||||
@@ -1882,6 +1884,7 @@ pub fn new(
|
||||
confused_type_with_std_module: Default::default(),
|
||||
stripped_cfg_items: Default::default(),
|
||||
effective_visibilities: Default::default(),
|
||||
macro_reachable_adts: Default::default(),
|
||||
doc_link_resolutions: Default::default(),
|
||||
doc_link_traits_in_scope: Default::default(),
|
||||
current_crate_outer_attr_insert_span,
|
||||
@@ -1992,6 +1995,7 @@ pub fn into_outputs(self) -> ResolverOutputs<'tcx> {
|
||||
expn_that_defined,
|
||||
visibilities_for_hashing: self.visibilities_for_hashing,
|
||||
effective_visibilities,
|
||||
macro_reachable_adts: self.macro_reachable_adts,
|
||||
extern_crate_map,
|
||||
module_children: self.module_children,
|
||||
ambig_module_children: self.ambig_module_children,
|
||||
|
||||
@@ -420,6 +420,68 @@ pub trait ChildExt: Sealed {
|
||||
/// }
|
||||
/// ```
|
||||
fn send_signal(&self, signal: i32) -> io::Result<()>;
|
||||
|
||||
/// Sends a signal to a child process's process group.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if the signal is invalid or if the
|
||||
/// child process does not have a process group. The integer values
|
||||
/// associated with signals are implementation-specific, so it's encouraged
|
||||
/// to use a crate that provides posix bindings.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// #![feature(unix_send_signal)]
|
||||
///
|
||||
/// use std::{io, os::unix::process::{ChildExt, CommandExt}, process::{Command, Stdio}};
|
||||
///
|
||||
/// use libc::SIGTERM;
|
||||
///
|
||||
/// fn main() -> io::Result<()> {
|
||||
/// # if cfg!(not(all(target_vendor = "apple", not(target_os = "macos")))) {
|
||||
/// let child = Command::new("cat")
|
||||
/// .stdin(Stdio::piped())
|
||||
/// .process_group(0)
|
||||
/// .spawn()?;
|
||||
/// child.send_process_group_signal(SIGTERM)?;
|
||||
/// # }
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
#[unstable(feature = "unix_send_signal", issue = "141975")]
|
||||
fn send_process_group_signal(&self, signal: i32) -> io::Result<()>;
|
||||
|
||||
/// Forces the child process's process group to exit.
|
||||
///
|
||||
/// This is analogous to [`Child::kill`] but applies to every process in
|
||||
/// the child process's process group.
|
||||
///
|
||||
/// Use [`CommandExt::process_group`] to assign a child process to an
|
||||
/// existing process group, or to make it the leader of a new process group.
|
||||
/// By default spawned processes are in the parent's process group.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// #![feature(unix_kill_process_group)]
|
||||
///
|
||||
/// use std::{os::unix::process::{ChildExt, CommandExt}, process::{Command, Stdio}};
|
||||
///
|
||||
/// fn main() -> std::io::Result<()> {
|
||||
/// let mut child = Command::new("cat")
|
||||
/// .stdin(Stdio::piped())
|
||||
/// .process_group(0)
|
||||
/// .spawn()?;
|
||||
/// child.kill_process_group()?;
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [`Child::kill`]: process::Child::kill
|
||||
#[unstable(feature = "unix_kill_process_group", issue = "156537")]
|
||||
fn kill_process_group(&mut self) -> io::Result<()>;
|
||||
}
|
||||
|
||||
#[unstable(feature = "unix_send_signal", issue = "141975")]
|
||||
@@ -427,6 +489,14 @@ impl ChildExt for process::Child {
|
||||
fn send_signal(&self, signal: i32) -> io::Result<()> {
|
||||
self.handle.send_signal(signal)
|
||||
}
|
||||
|
||||
fn send_process_group_signal(&self, signal: i32) -> io::Result<()> {
|
||||
self.handle.send_process_group_signal(signal)
|
||||
}
|
||||
|
||||
fn kill_process_group(&mut self) -> io::Result<()> {
|
||||
self.handle.send_process_group_signal(libc::SIGKILL)
|
||||
}
|
||||
}
|
||||
|
||||
#[stable(feature = "process_extensions", since = "1.2.0")]
|
||||
|
||||
@@ -95,6 +95,21 @@ pub(crate) fn send_signal(&self, signal: i32) -> io::Result<()> {
|
||||
.map(drop)
|
||||
}
|
||||
|
||||
pub(crate) fn send_process_group_signal(&self, signal: i32) -> io::Result<()> {
|
||||
// since kernel 6.9
|
||||
// https://lore.kernel.org/all/20240210-chihuahua-hinzog-3945b6abd44a@brauner/
|
||||
cvt(unsafe {
|
||||
libc::syscall(
|
||||
libc::SYS_pidfd_send_signal,
|
||||
self.0.as_raw_fd(),
|
||||
signal,
|
||||
crate::ptr::null::<()>(),
|
||||
libc::PIDFD_SIGNAL_PROCESS_GROUP,
|
||||
)
|
||||
})
|
||||
.map(drop)
|
||||
}
|
||||
|
||||
pub fn wait(&self) -> io::Result<ExitStatus> {
|
||||
let r = self.waitid(libc::WEXITED)?;
|
||||
match r {
|
||||
|
||||
@@ -158,6 +158,11 @@ pub fn send_signal(&self, _signal: i32) -> io::Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub fn send_process_group_signal(&self, _signal: i32) -> io::Result<()> {
|
||||
// Fuchsia doesn't have a direct equivalent for signals
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub fn wait(&mut self) -> io::Result<ExitStatus> {
|
||||
let mut proc_info: zx_info_process_t = Default::default();
|
||||
let mut actual: size_t = 0;
|
||||
|
||||
@@ -1002,6 +1002,19 @@ pub(crate) fn send_signal(&self, signal: i32) -> io::Result<()> {
|
||||
cvt(unsafe { libc::kill(self.pid, signal) }).map(drop)
|
||||
}
|
||||
|
||||
pub(crate) fn send_process_group_signal(&self, signal: i32) -> io::Result<()> {
|
||||
// See note in `send_signal` regarding recycled PIDs.
|
||||
if self.status.is_some() {
|
||||
return Ok(());
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
if let Some(pid_fd) = self.pidfd.as_ref() {
|
||||
// The `PIDFD_SIGNAL_PROCESS_GROUP` flag requires kernel >= 6.9
|
||||
return pid_fd.send_process_group_signal(signal);
|
||||
}
|
||||
cvt(unsafe { libc::killpg(self.pid, signal) }).map(drop)
|
||||
}
|
||||
|
||||
pub fn wait(&mut self) -> io::Result<ExitStatus> {
|
||||
use crate::sys::cvt_r;
|
||||
if let Some(status) = self.status {
|
||||
|
||||
@@ -49,6 +49,10 @@ pub fn send_signal(&self, _signal: i32) -> io::Result<()> {
|
||||
unsupported()
|
||||
}
|
||||
|
||||
pub fn send_process_group_signal(&self, _signal: i32) -> io::Result<()> {
|
||||
unsupported()
|
||||
}
|
||||
|
||||
pub fn wait(&mut self) -> io::Result<ExitStatus> {
|
||||
unsupported()
|
||||
}
|
||||
|
||||
@@ -161,6 +161,14 @@ pub fn send_signal(&self, signal: i32) -> io::Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_process_group_signal(&self, signal: i32) -> io::Result<()> {
|
||||
// See note in `send_signal` regarding recycled PIDs.
|
||||
if self.status.is_some() {
|
||||
return Ok(());
|
||||
}
|
||||
cvt(unsafe { libc::killpg(self.pid, signal) }).map(drop)
|
||||
}
|
||||
|
||||
pub fn wait(&mut self) -> io::Result<ExitStatus> {
|
||||
use crate::sys::cvt_r;
|
||||
if let Some(status) = self.status {
|
||||
|
||||
@@ -653,6 +653,15 @@ add the `--scrape-tests` flag.
|
||||
This flag enables the generation of links in the source code pages which allow the reader
|
||||
to jump to a type definition.
|
||||
|
||||
> [!WARNING]
|
||||
> In very specific scenarios, enabling this feature may lead to your program getting rejected if you
|
||||
> rely on rustdoc intentionally not running all semantic analysis passes on function bodies to aid
|
||||
> with documenting `cfg`-conditional items.
|
||||
>
|
||||
> More concretely, rustdoc may choose to type-check bodies if they contain type-dependent paths
|
||||
> including method calls. This may result in name resolution and type errors getting reported that
|
||||
> rustdoc would usually suppress.
|
||||
|
||||
### `--test-builder`: `rustc`-like program to build tests
|
||||
|
||||
* Tracking issue: [#102981](https://github.com/rust-lang/rust/issues/102981)
|
||||
|
||||
@@ -149,7 +149,7 @@ fn should_append_only_to_description(&self) -> bool {
|
||||
| CfgEntry::All(..)
|
||||
| CfgEntry::NameValue { .. }
|
||||
| CfgEntry::Version(..)
|
||||
| CfgEntry::Not(box CfgEntry::NameValue { .. }, _) => true,
|
||||
| CfgEntry::Not(CfgEntry::NameValue { .. }, _) => true,
|
||||
CfgEntry::Not(..) | CfgEntry::Bool(..) => false,
|
||||
}
|
||||
}
|
||||
@@ -386,7 +386,7 @@ fn display_sub_cfgs(
|
||||
impl fmt::Display for Display<'_> {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match &self.0 {
|
||||
CfgEntry::Not(box CfgEntry::Any(sub_cfgs, _), _) => {
|
||||
CfgEntry::Not(CfgEntry::Any(sub_cfgs, _), _) => {
|
||||
let separator = if sub_cfgs.iter().all(is_simple_cfg) { " nor " } else { ", nor " };
|
||||
fmt.write_str("neither ")?;
|
||||
|
||||
@@ -399,10 +399,10 @@ fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
})
|
||||
.joined(separator, fmt)
|
||||
}
|
||||
CfgEntry::Not(box simple @ CfgEntry::NameValue { .. }, _) => {
|
||||
CfgEntry::Not(simple @ CfgEntry::NameValue { .. }, _) => {
|
||||
write!(fmt, "non-{}", Display(simple, self.1))
|
||||
}
|
||||
CfgEntry::Not(box c, _) => write!(fmt, "not ({})", Display(c, self.1)),
|
||||
CfgEntry::Not(c, _) => write!(fmt, "not ({})", Display(c, self.1)),
|
||||
|
||||
CfgEntry::Any(sub_cfgs, _) => {
|
||||
let separator = if sub_cfgs.iter().all(is_simple_cfg) { " or " } else { ", or " };
|
||||
|
||||
@@ -1472,11 +1472,8 @@ fn param_eq_arg(param: &GenericParamDef, arg: &GenericArg) -> bool {
|
||||
generics.where_predicates.retain_mut(|pred| match *pred {
|
||||
WherePredicate::BoundPredicate {
|
||||
ty:
|
||||
QPath(box QPathData {
|
||||
ref assoc,
|
||||
ref self_type,
|
||||
trait_: Some(ref trait_),
|
||||
..
|
||||
QPath(QPathData {
|
||||
ref assoc, ref self_type, trait_: Some(ref trait_), ..
|
||||
}),
|
||||
bounds: ref mut pred_bounds,
|
||||
..
|
||||
@@ -2786,7 +2783,7 @@ fn add_without_unwanted_attributes<'hir>(
|
||||
hir::Attribute::Parsed(AttributeKind::DocComment { .. }) => {
|
||||
attrs.push((Cow::Borrowed(attr), import_parent));
|
||||
}
|
||||
hir::Attribute::Parsed(AttributeKind::Doc(box d)) => {
|
||||
hir::Attribute::Parsed(AttributeKind::Doc(d)) => {
|
||||
// Remove attributes from `normal` that should not be inherited by `use` re-export.
|
||||
let DocAttribute {
|
||||
first_span: _,
|
||||
|
||||
@@ -491,7 +491,7 @@ pub(crate) fn inner_docs(&self, tcx: TyCtxt<'_>) -> bool {
|
||||
|
||||
/// Returns true if item is an associated function with a `self` parameter.
|
||||
pub(crate) fn has_self_param(&self) -> bool {
|
||||
if let ItemKind::MethodItem(box Function { decl, .. }, _) = &self.inner.kind {
|
||||
if let ItemKind::MethodItem(Function { decl, .. }, _) = &self.inner.kind {
|
||||
decl.receiver_type().is_some()
|
||||
} else {
|
||||
false
|
||||
@@ -505,8 +505,8 @@ pub(crate) fn span(&self, tcx: TyCtxt<'_>) -> Option<Span> {
|
||||
};
|
||||
match kind {
|
||||
ItemKind::ModuleItem(Module { span, .. }) => Some(*span),
|
||||
ItemKind::ImplItem(box Impl { kind: ImplKind::Auto, .. }) => None,
|
||||
ItemKind::ImplItem(box Impl { kind: ImplKind::Blanket(_), .. }) => {
|
||||
ItemKind::ImplItem(Impl { kind: ImplKind::Auto, .. }) => None,
|
||||
ItemKind::ImplItem(Impl { kind: ImplKind::Blanket(_), .. }) => {
|
||||
if let ItemId::Blanket { impl_id, .. } = self.item_id {
|
||||
Some(rustc_span(impl_id, tcx))
|
||||
} else {
|
||||
@@ -667,16 +667,21 @@ pub(crate) fn is_variant(&self) -> bool {
|
||||
self.type_() == ItemType::Variant
|
||||
}
|
||||
pub(crate) fn is_associated_type(&self) -> bool {
|
||||
matches!(self.kind, AssocTypeItem(..) | StrippedItem(box AssocTypeItem(..)))
|
||||
matches!(self.kind, AssocTypeItem(..) | StrippedItem(AssocTypeItem(..)))
|
||||
}
|
||||
pub(crate) fn is_required_associated_type(&self) -> bool {
|
||||
matches!(self.kind, RequiredAssocTypeItem(..) | StrippedItem(box RequiredAssocTypeItem(..)))
|
||||
matches!(self.kind, RequiredAssocTypeItem(..) | StrippedItem(RequiredAssocTypeItem(..)))
|
||||
}
|
||||
pub(crate) fn is_associated_const(&self) -> bool {
|
||||
matches!(self.kind, ProvidedAssocConstItem(..) | ImplAssocConstItem(..) | StrippedItem(box (ProvidedAssocConstItem(..) | ImplAssocConstItem(..))))
|
||||
matches!(
|
||||
self.kind,
|
||||
ProvidedAssocConstItem(..)
|
||||
| ImplAssocConstItem(..)
|
||||
| StrippedItem(ProvidedAssocConstItem(..) | ImplAssocConstItem(..))
|
||||
)
|
||||
}
|
||||
pub(crate) fn is_required_associated_const(&self) -> bool {
|
||||
matches!(self.kind, RequiredAssocConstItem(..) | StrippedItem(box RequiredAssocConstItem(..)))
|
||||
matches!(self.kind, RequiredAssocConstItem(..) | StrippedItem(RequiredAssocConstItem(..)))
|
||||
}
|
||||
pub(crate) fn is_method(&self) -> bool {
|
||||
self.type_() == ItemType::Method
|
||||
@@ -1508,9 +1513,9 @@ pub(crate) fn is_doc_subtype_of(&self, other: &Self, cache: &Cache) -> bool {
|
||||
|
||||
pub(crate) fn primitive_type(&self) -> Option<PrimitiveType> {
|
||||
match *self {
|
||||
Primitive(p) | BorrowedRef { type_: box Primitive(p), .. } => Some(p),
|
||||
Slice(..) | BorrowedRef { type_: box Slice(..), .. } => Some(PrimitiveType::Slice),
|
||||
Array(..) | BorrowedRef { type_: box Array(..), .. } => Some(PrimitiveType::Array),
|
||||
Primitive(p) | BorrowedRef { type_: Primitive(p), .. } => Some(p),
|
||||
Slice(..) | BorrowedRef { type_: Slice(..), .. } => Some(PrimitiveType::Slice),
|
||||
Array(..) | BorrowedRef { type_: Array(..), .. } => Some(PrimitiveType::Array),
|
||||
Tuple(ref tys) => {
|
||||
if tys.is_empty() {
|
||||
Some(PrimitiveType::Unit)
|
||||
@@ -1590,7 +1595,7 @@ pub(crate) fn def_id(&self, cache: &Cache) -> Option<DefId> {
|
||||
Type::Path { path } => return Some(path.def_id()),
|
||||
DynTrait(bounds, _) => return bounds.first().map(|b| b.trait_.def_id()),
|
||||
Primitive(p) => return cache.primitive_locations.get(p).cloned(),
|
||||
BorrowedRef { type_: box Generic(..), .. } => PrimitiveType::Reference,
|
||||
BorrowedRef { type_: Generic(..), .. } => PrimitiveType::Reference,
|
||||
BorrowedRef { type_, .. } => return type_.def_id(cache),
|
||||
Tuple(tys) => {
|
||||
if tys.is_empty() {
|
||||
@@ -1605,7 +1610,7 @@ pub(crate) fn def_id(&self, cache: &Cache) -> Option<DefId> {
|
||||
Type::Pat(..) => PrimitiveType::Pat,
|
||||
Type::FieldOf(..) => PrimitiveType::FieldOf,
|
||||
RawPointer(..) => PrimitiveType::RawPointer,
|
||||
QPath(box QPathData { self_type, .. }) => return self_type.def_id(cache),
|
||||
QPath(QPathData { self_type, .. }) => return self_type.def_id(cache),
|
||||
Generic(_) | SelfTy | Infer | ImplTrait(_) | UnsafeBinder(_) => return None,
|
||||
};
|
||||
Primitive(t).def_id(cache)
|
||||
|
||||
@@ -105,7 +105,7 @@ fn fold_inner_recur(&mut self, kind: ItemKind) -> ItemKind {
|
||||
/// don't override!
|
||||
fn fold_item_recur(&mut self, mut item: Item) -> Item {
|
||||
item.inner.kind = match item.inner.kind {
|
||||
StrippedItem(box i) => StrippedItem(Box::new(self.fold_inner_recur(i))),
|
||||
StrippedItem(i) => StrippedItem(Box::new(self.fold_inner_recur(*i))),
|
||||
_ => self.fold_inner_recur(item.inner.kind),
|
||||
};
|
||||
item
|
||||
|
||||
@@ -248,7 +248,7 @@ fn fold_item(&mut self, item: clean::Item) -> Option<clean::Item> {
|
||||
// If this is a stripped module,
|
||||
// we don't want it or its children in the search index.
|
||||
let orig_stripped_mod = match item.kind {
|
||||
clean::StrippedItem(box clean::ModuleItem(..)) => {
|
||||
clean::StrippedItem(clean::ModuleItem(..)) => {
|
||||
mem::replace(&mut self.cache.stripped_mod, true)
|
||||
}
|
||||
_ => self.cache.stripped_mod,
|
||||
@@ -409,69 +409,69 @@ fn is_from_private_dep(tcx: TyCtxt<'_>, cache: &Cache, def_id: DefId) -> bool {
|
||||
|
||||
// Once we've recursively found all the generics, hoard off all the
|
||||
// implementations elsewhere.
|
||||
let ret = if let clean::Item {
|
||||
inner: box clean::ItemInner { kind: clean::ImplItem(ref i), .. },
|
||||
} = item
|
||||
{
|
||||
// Figure out the id of this impl. This may map to a
|
||||
// primitive rather than always to a struct/enum.
|
||||
// Note: matching twice to restrict the lifetime of the `i` borrow.
|
||||
let mut dids = FxIndexSet::default();
|
||||
match i.for_ {
|
||||
clean::Type::Path { ref path }
|
||||
| clean::BorrowedRef { type_: box clean::Type::Path { ref path }, .. } => {
|
||||
dids.insert(path.def_id());
|
||||
if let Some(generics) = path.generics()
|
||||
&& let ty::Adt(adt, _) = self
|
||||
.tcx
|
||||
.type_of(path.def_id())
|
||||
.instantiate_identity()
|
||||
.skip_norm_wip()
|
||||
.kind()
|
||||
&& adt.is_fundamental()
|
||||
{
|
||||
for ty in generics {
|
||||
dids.extend(ty.def_id(self.cache));
|
||||
let ret =
|
||||
if let clean::Item { inner: clean::ItemInner { kind: clean::ImplItem(ref i), .. } } =
|
||||
item
|
||||
{
|
||||
// Figure out the id of this impl. This may map to a
|
||||
// primitive rather than always to a struct/enum.
|
||||
// Note: matching twice to restrict the lifetime of the `i` borrow.
|
||||
let mut dids = FxIndexSet::default();
|
||||
match i.for_ {
|
||||
clean::Type::Path { ref path }
|
||||
| clean::BorrowedRef { type_: clean::Type::Path { ref path }, .. } => {
|
||||
dids.insert(path.def_id());
|
||||
if let Some(generics) = path.generics()
|
||||
&& let ty::Adt(adt, _) = self
|
||||
.tcx
|
||||
.type_of(path.def_id())
|
||||
.instantiate_identity()
|
||||
.skip_norm_wip()
|
||||
.kind()
|
||||
&& adt.is_fundamental()
|
||||
{
|
||||
for ty in generics {
|
||||
dids.extend(ty.def_id(self.cache));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
clean::DynTrait(ref bounds, _)
|
||||
| clean::BorrowedRef { type_: box clean::DynTrait(ref bounds, _), .. } => {
|
||||
dids.insert(bounds[0].trait_.def_id());
|
||||
}
|
||||
ref t => {
|
||||
let did = t
|
||||
.primitive_type()
|
||||
.and_then(|t| self.cache.primitive_locations.get(&t).cloned());
|
||||
clean::DynTrait(ref bounds, _)
|
||||
| clean::BorrowedRef { type_: clean::DynTrait(ref bounds, _), .. } => {
|
||||
dids.insert(bounds[0].trait_.def_id());
|
||||
}
|
||||
ref t => {
|
||||
let did = t
|
||||
.primitive_type()
|
||||
.and_then(|t| self.cache.primitive_locations.get(&t).cloned());
|
||||
|
||||
dids.extend(did);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(trait_) = &i.trait_
|
||||
&& let Some(generics) = trait_.generics()
|
||||
{
|
||||
for bound in generics {
|
||||
dids.extend(bound.def_id(self.cache));
|
||||
}
|
||||
}
|
||||
let impl_item = Impl { impl_item: item };
|
||||
let impl_did = impl_item.def_id();
|
||||
let trait_did = impl_item.trait_did();
|
||||
if trait_did.is_none_or(|d| self.cache.traits.contains_key(&d)) {
|
||||
for did in dids {
|
||||
if self.impl_ids.entry(did).or_default().insert(impl_did) {
|
||||
self.cache.impls.entry(did).or_default().push(impl_item.clone());
|
||||
dids.extend(did);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(trait_) = &i.trait_
|
||||
&& let Some(generics) = trait_.generics()
|
||||
{
|
||||
for bound in generics {
|
||||
dids.extend(bound.def_id(self.cache));
|
||||
}
|
||||
}
|
||||
let impl_item = Impl { impl_item: item };
|
||||
let impl_did = impl_item.def_id();
|
||||
let trait_did = impl_item.trait_did();
|
||||
if trait_did.is_none_or(|d| self.cache.traits.contains_key(&d)) {
|
||||
for did in dids {
|
||||
if self.impl_ids.entry(did).or_default().insert(impl_did) {
|
||||
self.cache.impls.entry(did).or_default().push(impl_item.clone());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let trait_did = trait_did.expect("no trait did");
|
||||
self.cache.orphan_trait_impls.push((trait_did, dids, impl_item));
|
||||
}
|
||||
None
|
||||
} else {
|
||||
let trait_did = trait_did.expect("no trait did");
|
||||
self.cache.orphan_trait_impls.push((trait_did, dids, impl_item));
|
||||
}
|
||||
None
|
||||
} else {
|
||||
Some(item)
|
||||
};
|
||||
Some(item)
|
||||
};
|
||||
|
||||
if pushed {
|
||||
self.cache.stack.pop().expect("stack already empty");
|
||||
@@ -655,7 +655,7 @@ enum ParentStackItem {
|
||||
impl ParentStackItem {
|
||||
fn new(item: &clean::Item) -> Self {
|
||||
match &item.kind {
|
||||
clean::ItemKind::ImplItem(box clean::Impl { for_, trait_, generics, kind, .. }) => {
|
||||
clean::ItemKind::ImplItem(clean::Impl { for_, trait_, generics, kind, .. }) => {
|
||||
ParentStackItem::Impl {
|
||||
for_: for_.clone(),
|
||||
trait_: trait_.clone(),
|
||||
|
||||
@@ -106,7 +106,7 @@ fn visit_u64<E: de::Error>(self, v: u64) -> Result<ItemType, E> {
|
||||
impl<'a> From<&'a clean::Item> for ItemType {
|
||||
fn from(item: &'a clean::Item) -> ItemType {
|
||||
let kind = match &item.kind {
|
||||
clean::StrippedItem(box item) => item,
|
||||
clean::StrippedItem(item) => item,
|
||||
kind => kind,
|
||||
};
|
||||
|
||||
|
||||
@@ -75,8 +75,8 @@ fn run_format_inner<'tcx, T: FormatRenderer<'tcx>>(
|
||||
prof.generic_activity_with_arg("render_mod_item", item.name.unwrap().to_string());
|
||||
|
||||
cx.mod_item_in(item)?;
|
||||
let (clean::StrippedItem(box clean::ModuleItem(ref module))
|
||||
| clean::ModuleItem(ref module)) = item.inner.kind
|
||||
let (clean::StrippedItem(clean::ModuleItem(ref module)) | clean::ModuleItem(ref module)) =
|
||||
item.inner.kind
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
@@ -961,7 +961,7 @@ fn fmt_type(
|
||||
}
|
||||
}
|
||||
},
|
||||
clean::Slice(box clean::Generic(name)) => {
|
||||
clean::Slice(clean::Generic(name)) => {
|
||||
primitive_link(f, PrimitiveType::Slice, format_args!("[{name}]"), cx)
|
||||
}
|
||||
clean::Slice(t) => Wrapped::with_square_brackets().wrap(print_type(t, cx)).fmt(f),
|
||||
@@ -974,7 +974,7 @@ fn fmt_type(
|
||||
fmt::Display::fmt(&print_type(t, cx), f)?;
|
||||
write!(f, ", {field})")
|
||||
}
|
||||
clean::Array(box clean::Generic(name), n) if !f.alternate() => primitive_link(
|
||||
clean::Array(clean::Generic(name), n) if !f.alternate() => primitive_link(
|
||||
f,
|
||||
PrimitiveType::Array,
|
||||
format_args!("[{name}; {n}]", n = Escape(n)),
|
||||
@@ -1280,7 +1280,7 @@ fn print_parameter(parameter: &clean::Parameter, cx: &Context<'_>) -> impl fmt::
|
||||
if let Some(self_ty) = parameter.to_receiver() {
|
||||
match self_ty {
|
||||
clean::SelfTy => f.write_str("self"),
|
||||
clean::BorrowedRef { lifetime, mutability, type_: box clean::SelfTy } => {
|
||||
clean::BorrowedRef { lifetime, mutability, type_: clean::SelfTy } => {
|
||||
f.write_str(if f.alternate() { "&" } else { "&" })?;
|
||||
if let Some(lt) = lifetime {
|
||||
write!(f, "{lt} ", lt = print_lifetime(lt))?;
|
||||
|
||||
@@ -832,7 +832,7 @@ fn mod_item_in(&mut self, item: &clean::Item) -> Result<(), Error> {
|
||||
|
||||
// Render sidebar-items.js used throughout this module.
|
||||
if !self.info.render_redirect_pages {
|
||||
let (clean::StrippedItem(box clean::ModuleItem(ref module))
|
||||
let (clean::StrippedItem(clean::ModuleItem(ref module))
|
||||
| clean::ModuleItem(ref module)) = item.kind
|
||||
else {
|
||||
unreachable!()
|
||||
|
||||
@@ -809,7 +809,8 @@ fn document_full_inner(
|
||||
}
|
||||
|
||||
let kind = match &item.kind {
|
||||
clean::ItemKind::StrippedItem(box kind) | kind => kind,
|
||||
clean::ItemKind::StrippedItem(kind) => kind,
|
||||
kind => kind,
|
||||
};
|
||||
|
||||
if let clean::ItemKind::FunctionItem(..) | clean::ItemKind::MethodItem(..) = kind {
|
||||
@@ -1582,7 +1583,7 @@ fn render_deref_methods(
|
||||
.items
|
||||
.iter()
|
||||
.find_map(|item| match item.kind {
|
||||
clean::AssocTypeItem(box ref t, _) => Some(match *t {
|
||||
clean::AssocTypeItem(ref t, _) => Some(match *t {
|
||||
clean::TypeAlias { item_type: Some(ref type_), .. } => (type_, &t.type_),
|
||||
_ => (&t.type_, &t.type_),
|
||||
}),
|
||||
@@ -2709,7 +2710,7 @@ fn collect_paths_for_type(first_ty: &clean::Type, cache: &Cache) -> Vec<String>
|
||||
clean::Type::BorrowedRef { type_, .. } => {
|
||||
work.push_back(type_);
|
||||
}
|
||||
clean::Type::QPath(box clean::QPathData { self_type, trait_, .. }) => {
|
||||
clean::Type::QPath(clean::QPathData { self_type, trait_, .. }) => {
|
||||
work.push_back(self_type);
|
||||
if let Some(trait_) = trait_ {
|
||||
process_path(trait_.def_id());
|
||||
|
||||
@@ -1524,9 +1524,8 @@ fn item_union(cx: &Context<'_>, it: &clean::Item, s: &clean::Union) -> impl fmt:
|
||||
fn print_tuple_struct_fields(cx: &Context<'_>, s: &[clean::Item]) -> impl Display {
|
||||
fmt::from_fn(|f| {
|
||||
if !s.is_empty()
|
||||
&& s.iter().all(|field| {
|
||||
matches!(field.kind, clean::StrippedItem(box clean::StructFieldItem(..)))
|
||||
})
|
||||
&& s.iter()
|
||||
.all(|field| matches!(field.kind, clean::StrippedItem(clean::StructFieldItem(..))))
|
||||
{
|
||||
return f.write_str("<span class=\"comment\">/* private fields */</span>");
|
||||
}
|
||||
@@ -1534,7 +1533,7 @@ fn print_tuple_struct_fields(cx: &Context<'_>, s: &[clean::Item]) -> impl Displa
|
||||
s.iter()
|
||||
.map(|ty| {
|
||||
fmt::from_fn(|f| match ty.kind {
|
||||
clean::StrippedItem(box clean::StructFieldItem(_)) => f.write_str("_"),
|
||||
clean::StrippedItem(clean::StructFieldItem(_)) => f.write_str("_"),
|
||||
clean::StructFieldItem(ref ty) => write!(f, "{}", print_type(ty, cx)),
|
||||
_ => unreachable!(),
|
||||
})
|
||||
@@ -1852,7 +1851,7 @@ fn item_variants(
|
||||
)?;
|
||||
for field in fields {
|
||||
match field.kind {
|
||||
clean::StrippedItem(box clean::StructFieldItem(_)) => {}
|
||||
clean::StrippedItem(clean::StructFieldItem(_)) => {}
|
||||
clean::StructFieldItem(ref ty) => {
|
||||
let id = cx.derive_id(format!(
|
||||
"variant.{}.field.{}",
|
||||
@@ -2355,7 +2354,7 @@ fn render_implementor(
|
||||
// full path, for example in `std::iter::ExactSizeIterator`
|
||||
let use_absolute = match implementor.inner_impl().for_ {
|
||||
clean::Type::Path { ref path, .. }
|
||||
| clean::BorrowedRef { type_: box clean::Type::Path { ref path, .. }, .. }
|
||||
| clean::BorrowedRef { type_: clean::Type::Path { ref path, .. }, .. }
|
||||
if !path.is_assoc_ty() =>
|
||||
{
|
||||
implementor_dups[&path.last()].1
|
||||
@@ -2551,7 +2550,7 @@ fn render_struct_fields(
|
||||
w.write_str("(")?;
|
||||
if !fields.is_empty()
|
||||
&& fields.iter().all(|field| {
|
||||
matches!(field.kind, clean::StrippedItem(box clean::StructFieldItem(..)))
|
||||
matches!(field.kind, clean::StrippedItem(clean::StructFieldItem(..)))
|
||||
})
|
||||
{
|
||||
write!(w, "<span class=\"comment\">/* private fields */</span>")?;
|
||||
@@ -2561,7 +2560,7 @@ fn render_struct_fields(
|
||||
w.write_str(", ")?;
|
||||
}
|
||||
match field.kind {
|
||||
clean::StrippedItem(box clean::StructFieldItem(..)) => {
|
||||
clean::StrippedItem(clean::StructFieldItem(..)) => {
|
||||
write!(w, "_")?;
|
||||
}
|
||||
clean::StructFieldItem(ref ty) => {
|
||||
|
||||
@@ -526,7 +526,7 @@ fn sidebar_deref_methods<'a>(
|
||||
debug!("found Deref: {impl_:?}");
|
||||
if let Some((target, real_target)) =
|
||||
impl_.inner_impl().items.iter().find_map(|item| match item.kind {
|
||||
clean::AssocTypeItem(box ref t, _) => Some(match *t {
|
||||
clean::AssocTypeItem(ref t, _) => Some(match *t {
|
||||
clean::TypeAlias { item_type: Some(ref type_), .. } => (type_, &t.type_),
|
||||
_ => (&t.type_, &t.type_),
|
||||
}),
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::def_id::{DefId, LOCAL_CRATE, LocalDefId};
|
||||
use rustc_hir::def_id::{DefId, LOCAL_CRATE};
|
||||
use rustc_hir::intravisit::{self, Visitor, VisitorExt};
|
||||
use rustc_hir::{ExprKind, HirId, Item, ItemKind, Mod, Node, QPath};
|
||||
use rustc_middle::hir::nested_filter;
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
use rustc_middle::ty::{self, TyCtxt};
|
||||
use rustc_span::{BytePos, ExpnKind};
|
||||
|
||||
use crate::clean::{self, PrimitiveType, rustc_span};
|
||||
@@ -82,7 +83,8 @@ pub(crate) fn collect_spans_and_sources(
|
||||
generate_link_to_definition: bool,
|
||||
) -> (FxIndexMap<PathBuf, String>, FxHashMap<Span, LinkFromSrc>) {
|
||||
if include_sources {
|
||||
let mut visitor = SpanMapVisitor { tcx, matches: FxHashMap::default() };
|
||||
let mut visitor =
|
||||
SpanMapVisitor { tcx, maybe_typeck_results: None, matches: FxHashMap::default() };
|
||||
|
||||
if generate_link_to_definition {
|
||||
tcx.hir_walk_toplevel_module(&mut visitor);
|
||||
@@ -96,49 +98,63 @@ pub(crate) fn collect_spans_and_sources(
|
||||
|
||||
struct SpanMapVisitor<'tcx> {
|
||||
pub(crate) tcx: TyCtxt<'tcx>,
|
||||
pub(crate) maybe_typeck_results: Option<LazyTypeckResults<'tcx>>,
|
||||
pub(crate) matches: FxHashMap<Span, LinkFromSrc>,
|
||||
}
|
||||
|
||||
impl SpanMapVisitor<'_> {
|
||||
impl<'tcx> SpanMapVisitor<'tcx> {
|
||||
/// Returns the typeck results of the current body if we're in one.
|
||||
///
|
||||
/// This will typeck the body if it hasn't been already. Since rustdoc intentionally doesn't run
|
||||
/// all semantic analysis passes on function bodies at the time of writing, this can lead to us
|
||||
/// "suddenly" rejecting the user's code under `--generate-link-to-definition` while accepting
|
||||
/// it if that flag isn't passed! So use this method sparingly and think about the consequences
|
||||
/// including performance!
|
||||
///
|
||||
/// This behavior is documented in the rustdoc book. Ideally, it wouldn't be that way but no
|
||||
/// good solution has been found so far. Don't think about adding some sort of flag to rustc to
|
||||
/// suppress diagnostic emission that would be unsound wrt. `ErrorGuaranteed`[^1] and generally
|
||||
/// be quite hacky!
|
||||
///
|
||||
/// [^1]: Historical context:
|
||||
/// <https://github.com/rust-lang/rust/issues/69426#issuecomment-1019412352>.
|
||||
fn maybe_typeck_results(&mut self) -> Option<&'tcx ty::TypeckResults<'tcx>> {
|
||||
let results = self.maybe_typeck_results.as_mut()?;
|
||||
let results = results.cache.get_or_insert_with(|| self.tcx.typeck_body(results.body_id));
|
||||
Some(results)
|
||||
}
|
||||
|
||||
fn link_for_def(&self, def_id: DefId) -> LinkFromSrc {
|
||||
if def_id.is_local() {
|
||||
LinkFromSrc::Local(rustc_span(def_id, self.tcx))
|
||||
} else {
|
||||
LinkFromSrc::External(def_id)
|
||||
}
|
||||
}
|
||||
|
||||
/// This function is where we handle `hir::Path` elements and add them into the "span map".
|
||||
fn handle_path(&mut self, path: &rustc_hir::Path<'_>, only_use_last_segment: bool) {
|
||||
fn handle_path(&mut self, path: &hir::Path<'_>, only_use_last_segment: bool) {
|
||||
match path.res {
|
||||
// FIXME: For now, we handle `DefKind` if it's not a `DefKind::TyParam`.
|
||||
// Would be nice to support them too alongside the other `DefKind`
|
||||
// (such as primitive types!).
|
||||
Res::Def(kind, def_id) if kind != DefKind::TyParam => {
|
||||
let link = if def_id.as_local().is_some() {
|
||||
LinkFromSrc::Local(rustc_span(def_id, self.tcx))
|
||||
} else {
|
||||
LinkFromSrc::External(def_id)
|
||||
};
|
||||
// FIXME: Properly support type parameters. Note they resolve just fine. The issue is
|
||||
// that our highlighter would then also linkify their *definition site* for some reason
|
||||
// linking them to themselves. Const parameters don't exhibit this issue.
|
||||
Res::Def(DefKind::TyParam, _) => {}
|
||||
Res::Def(_, def_id) => {
|
||||
// The segments can be empty for `use *;` in a non-crate-root scope in Rust 2015.
|
||||
let span = path.segments.last().map_or(path.span, |seg| seg.ident.span);
|
||||
// In case the path ends with generics, we remove them from the span.
|
||||
let span = if only_use_last_segment
|
||||
&& let Some(path_span) = path.segments.last().map(|segment| segment.ident.span)
|
||||
{
|
||||
path_span
|
||||
let span = if only_use_last_segment {
|
||||
span
|
||||
} else {
|
||||
path.segments
|
||||
.last()
|
||||
.map(|last| {
|
||||
// In `use` statements, the included item is not in the path segments.
|
||||
// However, it doesn't matter because you can't have generics on `use`
|
||||
// statements.
|
||||
if path.span.contains(last.ident.span) {
|
||||
path.span.with_hi(last.ident.span.hi())
|
||||
} else {
|
||||
path.span
|
||||
}
|
||||
})
|
||||
.unwrap_or(path.span)
|
||||
// In `use` statements, the included item is not in the path segments. However,
|
||||
// it doesn't matter because you can't have generics on `use` statements.
|
||||
if path.span.contains(span) { path.span.with_hi(span.hi()) } else { path.span }
|
||||
};
|
||||
self.matches.insert(span.into(), link);
|
||||
self.matches.insert(span.into(), self.link_for_def(def_id));
|
||||
}
|
||||
Res::Local(_) if let Some(span) = self.tcx.hir_res_span(path.res) => {
|
||||
let path_span = if only_use_last_segment
|
||||
&& let Some(path_span) = path.segments.last().map(|segment| segment.ident.span)
|
||||
{
|
||||
path_span
|
||||
let path_span = if only_use_last_segment {
|
||||
path.segments.last().unwrap().ident.span
|
||||
} else {
|
||||
path.span
|
||||
};
|
||||
@@ -149,7 +165,6 @@ fn handle_path(&mut self, path: &rustc_hir::Path<'_>, only_use_last_segment: boo
|
||||
self.matches
|
||||
.insert(path.span.into(), LinkFromSrc::Primitive(PrimitiveType::from(p)));
|
||||
}
|
||||
Res::Err => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -216,43 +231,6 @@ fn handle_macro(&mut self, span: rustc_span::Span) -> bool {
|
||||
self.matches.insert(new_span.into(), link_from_src);
|
||||
true
|
||||
}
|
||||
|
||||
fn infer_id(&mut self, hir_id: HirId, expr_hir_id: Option<HirId>, span: Span) {
|
||||
let tcx = self.tcx;
|
||||
let body_id = tcx.hir_enclosing_body_owner(hir_id);
|
||||
// FIXME: this is showing error messages for parts of the code that are not
|
||||
// compiled (because of cfg)!
|
||||
//
|
||||
// See discussion in https://github.com/rust-lang/rust/issues/69426#issuecomment-1019412352
|
||||
let typeck_results = tcx.typeck_body(tcx.hir_body_owned_by(body_id).id());
|
||||
// Interestingly enough, for method calls, we need the whole expression whereas for static
|
||||
// method/function calls, we need the call expression specifically.
|
||||
if let Some(def_id) = typeck_results.type_dependent_def_id(expr_hir_id.unwrap_or(hir_id)) {
|
||||
let link = if def_id.as_local().is_some() {
|
||||
LinkFromSrc::Local(rustc_span(def_id, tcx))
|
||||
} else {
|
||||
LinkFromSrc::External(def_id)
|
||||
};
|
||||
self.matches.insert(span, link);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This is a reimplementation of `hir_enclosing_body_owner` which allows to fail without
|
||||
// panicking.
|
||||
fn hir_enclosing_body_owner(tcx: TyCtxt<'_>, hir_id: HirId) -> Option<LocalDefId> {
|
||||
for (_, node) in tcx.hir_parent_iter(hir_id) {
|
||||
// FIXME: associated type impl items don't have an associated body, so we don't handle
|
||||
// them currently.
|
||||
if let Node::ImplItem(impl_item) = node
|
||||
&& matches!(impl_item.kind, rustc_hir::ImplItemKind::Type(_))
|
||||
{
|
||||
return None;
|
||||
} else if let Some((def_id, _)) = node.associated_body() {
|
||||
return Some(def_id);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> {
|
||||
@@ -262,7 +240,24 @@ fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
|
||||
self.tcx
|
||||
}
|
||||
|
||||
fn visit_path(&mut self, path: &rustc_hir::Path<'tcx>, _id: HirId) {
|
||||
fn visit_nested_body(&mut self, body_id: hir::BodyId) -> Self::Result {
|
||||
let maybe_typeck_results =
|
||||
self.maybe_typeck_results.replace(LazyTypeckResults { body_id, cache: None });
|
||||
self.visit_body(self.tcx.hir_body(body_id));
|
||||
self.maybe_typeck_results = maybe_typeck_results;
|
||||
}
|
||||
|
||||
fn visit_anon_const(&mut self, ct: &'tcx hir::AnonConst) {
|
||||
// FIXME: Typeck'ing anon consts leads to ICEs in rustc if the parent body wasn't typeck'ed
|
||||
// yet. See #156418. Figure out what the best and proper solution for this is. Until
|
||||
// then, let's prevent `typeck` from being called on anon consts by not setting
|
||||
// `maybe_typeck_results` to `Some(_)`.
|
||||
let maybe_typeck_results = self.maybe_typeck_results.take();
|
||||
self.visit_body(self.tcx.hir_body(ct.body));
|
||||
self.maybe_typeck_results = maybe_typeck_results;
|
||||
}
|
||||
|
||||
fn visit_path(&mut self, path: &hir::Path<'tcx>, _id: HirId) {
|
||||
if self.handle_macro(path.span) {
|
||||
return;
|
||||
}
|
||||
@@ -272,25 +267,32 @@ fn visit_path(&mut self, path: &rustc_hir::Path<'tcx>, _id: HirId) {
|
||||
|
||||
fn visit_qpath(&mut self, qpath: &QPath<'tcx>, id: HirId, _span: rustc_span::Span) {
|
||||
match *qpath {
|
||||
QPath::TypeRelative(qself, path) => {
|
||||
if matches!(path.res, Res::Err) {
|
||||
let tcx = self.tcx;
|
||||
if let Some(body_id) = hir_enclosing_body_owner(tcx, id) {
|
||||
let typeck_results = tcx.typeck_body(tcx.hir_body_owned_by(body_id).id());
|
||||
let path = rustc_hir::Path {
|
||||
// We change the span to not include parens.
|
||||
span: path.ident.span,
|
||||
res: typeck_results.qpath_res(qpath, id),
|
||||
segments: &[],
|
||||
};
|
||||
self.handle_path(&path, false);
|
||||
}
|
||||
} else {
|
||||
self.infer_id(path.hir_id, Some(id), path.ident.span.into());
|
||||
QPath::TypeRelative(qself, segment) => {
|
||||
// FIXME: This doesn't work for paths in *types* since HIR ty lowering currently
|
||||
// doesn't write back the resolution of type-relative paths. Updating it to
|
||||
// do so should be a simple fix.
|
||||
// FIXME: This obviously doesn't support item signatures / non-bodies. Sadly, rustc
|
||||
// currently doesn't keep around that information & thus can't provide an API
|
||||
// for it.
|
||||
// `ItemCtxt`s would need a place to write back the resolution of type-
|
||||
// dependent definitions. Ideally there was some sort of query keyed on the
|
||||
// `LocalDefId` of the owning item that returns some table with which we can
|
||||
// map the `HirId` to a `DefId`.
|
||||
// Of course, we could re-HIR-ty-lower such paths *here* if we were to extend
|
||||
// the public API of HIR analysis. However, I strongly advise against it as
|
||||
// it would be too much of a hack.
|
||||
if let Some(typeck_results) = self.maybe_typeck_results() {
|
||||
let path = hir::Path {
|
||||
// We change the span to not include parens.
|
||||
span: segment.ident.span,
|
||||
res: typeck_results.qpath_res(qpath, id),
|
||||
segments: std::slice::from_ref(segment),
|
||||
};
|
||||
self.handle_path(&path, false);
|
||||
}
|
||||
|
||||
rustc_ast::visit::try_visit!(self.visit_ty_unambig(qself));
|
||||
self.visit_path_segment(path);
|
||||
self.visit_path_segment(segment);
|
||||
}
|
||||
QPath::Resolved(maybe_qself, path) => {
|
||||
self.handle_path(path, true);
|
||||
@@ -323,23 +325,27 @@ fn visit_mod(&mut self, m: &'tcx Mod<'tcx>, span: rustc_span::Span, id: HirId) {
|
||||
intravisit::walk_mod(self, m);
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, expr: &'tcx rustc_hir::Expr<'tcx>) {
|
||||
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
|
||||
match expr.kind {
|
||||
ExprKind::MethodCall(segment, ..) => {
|
||||
self.infer_id(segment.hir_id, Some(expr.hir_id), segment.ident.span.into())
|
||||
}
|
||||
ExprKind::Call(call, ..) => self.infer_id(call.hir_id, None, call.span.into()),
|
||||
_ => {
|
||||
if self.handle_macro(expr.span) {
|
||||
// We don't want to go deeper into the macro.
|
||||
return;
|
||||
if let Some(typeck_results) = self.maybe_typeck_results()
|
||||
&& let Some(def_id) = typeck_results.type_dependent_def_id(expr.hir_id)
|
||||
{
|
||||
self.matches.insert(segment.ident.span.into(), self.link_for_def(def_id));
|
||||
}
|
||||
}
|
||||
// We don't want to go deeper into the macro.
|
||||
_ if self.handle_macro(expr.span) => return,
|
||||
_ => {}
|
||||
}
|
||||
intravisit::walk_expr(self, expr);
|
||||
}
|
||||
|
||||
fn visit_item(&mut self, item: &'tcx Item<'tcx>) {
|
||||
// We're no longer in a body since we've crossed an item boundary.
|
||||
// Temporarily take away the typeck results which are only valid in bodies.
|
||||
let maybe_typeck_results = self.maybe_typeck_results.take();
|
||||
|
||||
match item.kind {
|
||||
ItemKind::Static(..)
|
||||
| ItemKind::Const(..)
|
||||
@@ -359,6 +365,15 @@ fn visit_item(&mut self, item: &'tcx Item<'tcx>) {
|
||||
// We already have "visit_mod" above so no need to check it here.
|
||||
| ItemKind::Mod(..) => {}
|
||||
}
|
||||
|
||||
intravisit::walk_item(self, item);
|
||||
|
||||
self.maybe_typeck_results = maybe_typeck_results;
|
||||
}
|
||||
}
|
||||
|
||||
/// Lazily computed & cached [`ty::TypeckResults`].
|
||||
struct LazyTypeckResults<'tcx> {
|
||||
body_id: hir::BodyId,
|
||||
cache: Option<&'tcx ty::TypeckResults<'tcx>>,
|
||||
}
|
||||
|
||||
@@ -232,7 +232,7 @@ fn from_clean(arg: &clean::GenericArg, renderer: &JsonRenderer<'_>) -> Self {
|
||||
match arg {
|
||||
Lifetime(l) => GenericArg::Lifetime(l.into_json(renderer)),
|
||||
Type(t) => GenericArg::Type(t.into_json(renderer)),
|
||||
Const(box c) => GenericArg::Const(c.into_json(renderer)),
|
||||
Const(c) => GenericArg::Const(c.into_json(renderer)),
|
||||
Infer => GenericArg::Infer,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
)]
|
||||
#![feature(ascii_char)]
|
||||
#![feature(ascii_char_variants)]
|
||||
#![feature(box_patterns)]
|
||||
#![feature(deref_patterns)]
|
||||
#![feature(file_buffered)]
|
||||
#![feature(formatting_options)]
|
||||
#![feature(iter_intersperse)]
|
||||
|
||||
@@ -155,7 +155,7 @@ fn add_deref_target(
|
||||
|
||||
// scan through included items ahead of time to splice in Deref targets to the "valid" sets
|
||||
for it in new_items_external.iter().chain(new_items_local.iter()) {
|
||||
if let ImplItem(box Impl { ref for_, ref trait_, ref items, polarity, .. }) = it.kind
|
||||
if let ImplItem(Impl { ref for_, ref trait_, ref items, polarity, .. }) = it.kind
|
||||
&& trait_.as_ref().map(|t| t.def_id()) == tcx.lang_items().deref_trait()
|
||||
&& polarity != ty::ImplPolarity::Negative
|
||||
&& cleaner.keep_impl(for_, true)
|
||||
@@ -195,7 +195,7 @@ fn add_deref_target(
|
||||
|
||||
// Filter out external items that are not needed
|
||||
new_items_external.retain(|it| {
|
||||
if let ImplItem(box Impl { ref for_, ref trait_, ref kind, .. }) = it.kind {
|
||||
if let ImplItem(Impl { ref for_, ref trait_, ref kind, .. }) = it.kind {
|
||||
cleaner.keep_impl(
|
||||
for_,
|
||||
trait_.as_ref().map(|t| t.def_id()) == tcx.lang_items().deref_trait(),
|
||||
|
||||
@@ -69,7 +69,10 @@ fn fold_item(&mut self, mut item: Item) -> Option<Item> {
|
||||
item_stability
|
||||
};
|
||||
|
||||
let (ItemKind::StrippedItem(box kind) | kind) = &item.kind;
|
||||
let kind = match &item.kind {
|
||||
ItemKind::StrippedItem(kind) => kind,
|
||||
kind => kind,
|
||||
};
|
||||
match kind {
|
||||
ItemKind::ExternCrateItem { .. }
|
||||
| ItemKind::ImportItem(..)
|
||||
|
||||
@@ -6805,6 +6805,7 @@ Released 2018-09-13
|
||||
[`inline_asm_x86_intel_syntax`]: https://rust-lang.github.io/rust-clippy/master/index.html#inline_asm_x86_intel_syntax
|
||||
[`inline_fn_without_body`]: https://rust-lang.github.io/rust-clippy/master/index.html#inline_fn_without_body
|
||||
[`inline_modules`]: https://rust-lang.github.io/rust-clippy/master/index.html#inline_modules
|
||||
[`inline_trait_bounds`]: https://rust-lang.github.io/rust-clippy/master/index.html#inline_trait_bounds
|
||||
[`inspect_for_each`]: https://rust-lang.github.io/rust-clippy/master/index.html#inspect_for_each
|
||||
[`int_plus_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#int_plus_one
|
||||
[`integer_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#integer_arithmetic
|
||||
@@ -6885,6 +6886,7 @@ Released 2018-09-13
|
||||
[`manual_c_str_literals`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_c_str_literals
|
||||
[`manual_checked_ops`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_checked_ops
|
||||
[`manual_clamp`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_clamp
|
||||
[`manual_clear`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_clear
|
||||
[`manual_contains`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_contains
|
||||
[`manual_dangling_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_dangling_ptr
|
||||
[`manual_div_ceil`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_div_ceil
|
||||
@@ -7238,6 +7240,7 @@ Released 2018-09-13
|
||||
[`skip_while_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#skip_while_next
|
||||
[`sliced_string_as_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#sliced_string_as_bytes
|
||||
[`slow_vector_initialization`]: https://rust-lang.github.io/rust-clippy/master/index.html#slow_vector_initialization
|
||||
[`some_filter`]: https://rust-lang.github.io/rust-clippy/master/index.html#some_filter
|
||||
[`stable_sort_primitive`]: https://rust-lang.github.io/rust-clippy/master/index.html#stable_sort_primitive
|
||||
[`std_instead_of_alloc`]: https://rust-lang.github.io/rust-clippy/master/index.html#std_instead_of_alloc
|
||||
[`std_instead_of_core`]: https://rust-lang.github.io/rust-clippy/master/index.html#std_instead_of_core
|
||||
|
||||
@@ -34,7 +34,7 @@ anstream = "0.6.18"
|
||||
|
||||
[dev-dependencies]
|
||||
cargo_metadata = "0.23"
|
||||
ui_test = "0.30.2"
|
||||
ui_test = "0.30.5"
|
||||
regex = "1.5.5"
|
||||
serde = { version = "1.0.145", features = ["derive"] }
|
||||
serde_json = "1.0.122"
|
||||
|
||||
@@ -14,9 +14,9 @@ The different lint groups were defined in the [Clippy 1.0 RFC].
|
||||
|
||||
## Correctness
|
||||
|
||||
The `clippy::correctness` group is the only lint group in Clippy which lints are
|
||||
The `clippy::correctness` group is the only lint group in Clippy whose lints are
|
||||
deny-by-default and abort the compilation when triggered. This is for good
|
||||
reason: If you see a `correctness` lint, it means that your code is outright
|
||||
reason: if you see a `correctness` lint, it means that your code is outright
|
||||
wrong or useless, and you should try to fix it.
|
||||
|
||||
Lints in this category are carefully picked and should be free of false
|
||||
|
||||
@@ -297,8 +297,9 @@ fn negate(bin_op_kind: BinOpKind) -> Option<BinOpKind> {
|
||||
return Err("contains never type".to_owned());
|
||||
}
|
||||
|
||||
let ctxt = e.span.ctxt();
|
||||
for (n, expr) in self.terminals.iter().enumerate() {
|
||||
if eq_expr_value(self.cx, e, expr) {
|
||||
if eq_expr_value(self.cx, ctxt, e, expr) {
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
return Ok(Bool::Term(n as u8));
|
||||
}
|
||||
@@ -307,8 +308,8 @@ fn negate(bin_op_kind: BinOpKind) -> Option<BinOpKind> {
|
||||
&& implements_ord(self.cx, e_lhs)
|
||||
&& let ExprKind::Binary(expr_binop, expr_lhs, expr_rhs) = &expr.kind
|
||||
&& negate(e_binop.node) == Some(expr_binop.node)
|
||||
&& eq_expr_value(self.cx, e_lhs, expr_lhs)
|
||||
&& eq_expr_value(self.cx, e_rhs, expr_rhs)
|
||||
&& eq_expr_value(self.cx, ctxt, e_lhs, expr_lhs)
|
||||
&& eq_expr_value(self.cx, ctxt, e_rhs, expr_rhs)
|
||||
{
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
return Ok(Bool::Not(Box::new(Bool::Term(n as u8))));
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind, QPath, TyKind};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::Symbol;
|
||||
use rustc_span::{Symbol, SyntaxContext};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
@@ -62,7 +62,8 @@ fn check_expr(&mut self, cx: &LateContext<'_>, item: &Expr<'_>) {
|
||||
},
|
||||
_ => return,
|
||||
}
|
||||
&& !item.span.in_external_macro(cx.sess().source_map())
|
||||
&& let ctxt = item.span.ctxt()
|
||||
&& !ctxt.in_external_macro(cx.sess().source_map())
|
||||
&& !is_in_const_context(cx)
|
||||
&& let Some(cv) = match op2 {
|
||||
// todo: check for case signed -> larger unsigned == only x >= 0
|
||||
@@ -71,7 +72,7 @@ fn check_expr(&mut self, cx: &LateContext<'_>, item: &Expr<'_>) {
|
||||
let upper_lower = |lt1, gt1, lt2, gt2| {
|
||||
check_upper_bound(lt1, gt1)
|
||||
.zip(check_lower_bound(lt2, gt2))
|
||||
.and_then(|(l, r)| l.combine(r, cx))
|
||||
.and_then(|(l, r)| l.combine(r, cx, ctxt))
|
||||
};
|
||||
upper_lower(lt1, gt1, lt2, gt2).or_else(|| upper_lower(lt2, gt2, lt1, gt1))
|
||||
},
|
||||
@@ -126,8 +127,8 @@ fn read_le_ge<'tcx>(
|
||||
|
||||
impl<'a> Conversion<'a> {
|
||||
/// Combine multiple conversions if the are compatible
|
||||
pub fn combine(self, other: Self, cx: &LateContext<'_>) -> Option<Conversion<'a>> {
|
||||
if self.is_compatible(&other, cx) {
|
||||
pub fn combine(self, other: Self, cx: &LateContext<'_>, ctxt: SyntaxContext) -> Option<Conversion<'a>> {
|
||||
if self.is_compatible(&other, cx, ctxt) {
|
||||
// Prefer a Conversion that contains a type-constraint
|
||||
Some(if self.to_type.is_some() { self } else { other })
|
||||
} else {
|
||||
@@ -137,9 +138,9 @@ pub fn combine(self, other: Self, cx: &LateContext<'_>) -> Option<Conversion<'a>
|
||||
|
||||
/// Checks if two conversions are compatible
|
||||
/// same type of conversion, same 'castee' and same 'to type'
|
||||
pub fn is_compatible(&self, other: &Self, cx: &LateContext<'_>) -> bool {
|
||||
pub fn is_compatible(&self, other: &Self, cx: &LateContext<'_>, ctxt: SyntaxContext) -> bool {
|
||||
(self.cvt == other.cvt)
|
||||
&& (SpanlessEq::new(cx).eq_expr(self.expr_to_cast, other.expr_to_cast))
|
||||
&& (SpanlessEq::new(cx).eq_expr(ctxt, self.expr_to_cast, other.expr_to_cast))
|
||||
&& (self.has_compatible_to_type(other))
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::sym;
|
||||
use rustc_span::{SyntaxContext, sym};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
@@ -90,8 +90,10 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
|
||||
// Check that both sets of operands are equal
|
||||
let mut spanless_eq = SpanlessEq::new(cx);
|
||||
let same_fixed_operands = spanless_eq.eq_expr(lhs1, lhs2) && spanless_eq.eq_expr(rhs1, rhs2);
|
||||
let same_transposed_operands = spanless_eq.eq_expr(lhs1, rhs2) && spanless_eq.eq_expr(rhs1, lhs2);
|
||||
let same_fixed_operands = spanless_eq.eq_expr(SyntaxContext::root(), lhs1, lhs2)
|
||||
&& spanless_eq.eq_expr(SyntaxContext::root(), rhs1, rhs2);
|
||||
let same_transposed_operands = spanless_eq.eq_expr(SyntaxContext::root(), lhs1, rhs2)
|
||||
&& spanless_eq.eq_expr(SyntaxContext::root(), rhs1, lhs2);
|
||||
|
||||
if !same_fixed_operands && !same_transposed_operands {
|
||||
return;
|
||||
|
||||
@@ -232,6 +232,7 @@
|
||||
crate::inherent_to_string::INHERENT_TO_STRING_SHADOW_DISPLAY_INFO,
|
||||
crate::init_numbered_fields::INIT_NUMBERED_FIELDS_INFO,
|
||||
crate::inline_fn_without_body::INLINE_FN_WITHOUT_BODY_INFO,
|
||||
crate::inline_trait_bounds::INLINE_TRAIT_BOUNDS_INFO,
|
||||
crate::int_plus_one::INT_PLUS_ONE_INFO,
|
||||
crate::item_name_repetitions::ENUM_VARIANT_NAMES_INFO,
|
||||
crate::item_name_repetitions::MODULE_INCEPTION_INFO,
|
||||
@@ -414,6 +415,7 @@
|
||||
crate::methods::JOIN_ABSOLUTE_PATHS_INFO,
|
||||
crate::methods::LINES_FILTER_MAP_OK_INFO,
|
||||
crate::methods::MANUAL_C_STR_LITERALS_INFO,
|
||||
crate::methods::MANUAL_CLEAR_INFO,
|
||||
crate::methods::MANUAL_CONTAINS_INFO,
|
||||
crate::methods::MANUAL_FILTER_MAP_INFO,
|
||||
crate::methods::MANUAL_FIND_MAP_INFO,
|
||||
@@ -474,6 +476,7 @@
|
||||
crate::methods::SINGLE_CHAR_ADD_STR_INFO,
|
||||
crate::methods::SKIP_WHILE_NEXT_INFO,
|
||||
crate::methods::SLICED_STRING_AS_BYTES_INFO,
|
||||
crate::methods::SOME_FILTER_INFO,
|
||||
crate::methods::STABLE_SORT_PRIMITIVE_INFO,
|
||||
crate::methods::STR_SPLIT_AT_NEWLINE_INFO,
|
||||
crate::methods::STRING_EXTEND_CHARS_INFO,
|
||||
|
||||
@@ -498,11 +498,11 @@ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
|
||||
}
|
||||
|
||||
match try_parse_insert(self.cx, expr) {
|
||||
Some(insert_expr) if self.spanless_eq.eq_expr(self.map, insert_expr.map) => {
|
||||
Some(insert_expr) if self.spanless_eq.eq_expr(self.ctxt, self.map, insert_expr.map) => {
|
||||
self.visit_insert_expr_arguments(&insert_expr);
|
||||
// Multiple inserts, inserts with a different key, and inserts from a macro can't use the entry api.
|
||||
if self.is_map_used
|
||||
|| !self.spanless_eq.eq_expr(self.key, insert_expr.key)
|
||||
|| !self.spanless_eq.eq_expr(self.ctxt, self.key, insert_expr.key)
|
||||
|| expr.span.ctxt() != self.ctxt
|
||||
{
|
||||
self.can_use_entry = false;
|
||||
@@ -521,10 +521,10 @@ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
|
||||
self.visit_non_tail_expr(insert_expr.value);
|
||||
self.is_single_insert = is_single_insert;
|
||||
},
|
||||
_ if is_any_expr_in_map_used(self.cx, &mut self.spanless_eq, self.map, expr) => {
|
||||
_ if is_any_expr_in_map_used(self.cx, &mut self.spanless_eq, self.ctxt, self.map, expr) => {
|
||||
self.is_map_used = true;
|
||||
},
|
||||
_ if self.spanless_eq.eq_expr(self.key, expr) => {
|
||||
_ if self.spanless_eq.eq_expr(self.ctxt, self.key, expr) => {
|
||||
self.is_key_used = true;
|
||||
},
|
||||
_ => match expr.kind {
|
||||
@@ -600,11 +600,12 @@ fn visit_pat(&mut self, p: &'tcx Pat<'tcx>) {
|
||||
fn is_any_expr_in_map_used<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
spanless_eq: &mut SpanlessEq<'_, 'tcx>,
|
||||
ctxt: SyntaxContext,
|
||||
map: &'tcx Expr<'tcx>,
|
||||
expr: &'tcx Expr<'tcx>,
|
||||
) -> bool {
|
||||
for_each_expr(cx, map, |e| {
|
||||
if spanless_eq.eq_expr(e, expr) {
|
||||
if spanless_eq.eq_expr(ctxt, e, expr) {
|
||||
return ControlFlow::Break(());
|
||||
}
|
||||
ControlFlow::Continue(())
|
||||
|
||||
@@ -14,11 +14,15 @@
|
||||
/// test is positive or an expression which tests whether or not test
|
||||
/// is nonnegative.
|
||||
/// Used for check-custom-abs function below
|
||||
fn is_testing_positive(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -> bool {
|
||||
fn is_testing_positive(cx: &LateContext<'_>, ctxt: SyntaxContext, expr: &Expr<'_>, test: &Expr<'_>) -> bool {
|
||||
if let ExprKind::Binary(Spanned { node: op, .. }, left, right) = expr.kind {
|
||||
match op {
|
||||
BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, right, expr.span.ctxt()) && eq_expr_value(cx, left, test),
|
||||
BinOpKind::Lt | BinOpKind::Le => is_zero(cx, left, expr.span.ctxt()) && eq_expr_value(cx, right, test),
|
||||
BinOpKind::Gt | BinOpKind::Ge => {
|
||||
is_zero(cx, right, expr.span.ctxt()) && eq_expr_value(cx, ctxt, left, test)
|
||||
},
|
||||
BinOpKind::Lt | BinOpKind::Le => {
|
||||
is_zero(cx, left, expr.span.ctxt()) && eq_expr_value(cx, ctxt, right, test)
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
} else {
|
||||
@@ -27,11 +31,15 @@ fn is_testing_positive(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -
|
||||
}
|
||||
|
||||
/// See [`is_testing_positive`]
|
||||
fn is_testing_negative(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -> bool {
|
||||
fn is_testing_negative(cx: &LateContext<'_>, ctxt: SyntaxContext, expr: &Expr<'_>, test: &Expr<'_>) -> bool {
|
||||
if let ExprKind::Binary(Spanned { node: op, .. }, left, right) = expr.kind {
|
||||
match op {
|
||||
BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, left, expr.span.ctxt()) && eq_expr_value(cx, right, test),
|
||||
BinOpKind::Lt | BinOpKind::Le => is_zero(cx, right, expr.span.ctxt()) && eq_expr_value(cx, left, test),
|
||||
BinOpKind::Gt | BinOpKind::Ge => {
|
||||
is_zero(cx, left, expr.span.ctxt()) && eq_expr_value(cx, ctxt, right, test)
|
||||
},
|
||||
BinOpKind::Lt | BinOpKind::Le => {
|
||||
is_zero(cx, right, expr.span.ctxt()) && eq_expr_value(cx, ctxt, left, test)
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
} else {
|
||||
@@ -55,14 +63,21 @@ fn is_zero(cx: &LateContext<'_>, expr: &Expr<'_>, ctxt: SyntaxContext) -> bool {
|
||||
/// one of the two expressions
|
||||
/// If the two expressions are not negations of each other, then it
|
||||
/// returns None.
|
||||
fn are_negated<'a>(cx: &LateContext<'_>, expr1: &'a Expr<'a>, expr2: &'a Expr<'a>) -> Option<(bool, &'a Expr<'a>)> {
|
||||
fn are_negated<'a>(
|
||||
cx: &LateContext<'_>,
|
||||
ctxt: SyntaxContext,
|
||||
expr1: &'a Expr<'a>,
|
||||
expr2: &'a Expr<'a>,
|
||||
) -> Option<(bool, &'a Expr<'a>)> {
|
||||
if let ExprKind::Unary(UnOp::Neg, expr1_negated) = expr1.kind
|
||||
&& eq_expr_value(cx, expr1_negated, expr2)
|
||||
&& expr1_negated.span.ctxt() == ctxt
|
||||
&& eq_expr_value(cx, ctxt, expr1_negated, expr2)
|
||||
{
|
||||
return Some((false, expr2));
|
||||
}
|
||||
if let ExprKind::Unary(UnOp::Neg, expr2_negated) = expr2.kind
|
||||
&& eq_expr_value(cx, expr1, expr2_negated)
|
||||
&& expr2_negated.span.ctxt() == ctxt
|
||||
&& eq_expr_value(cx, ctxt, expr1, expr2_negated)
|
||||
{
|
||||
return Some((true, expr1));
|
||||
}
|
||||
@@ -77,11 +92,12 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
}) = higher::If::hir(expr)
|
||||
&& let if_body_expr = peel_blocks(then)
|
||||
&& let else_body_expr = peel_blocks(r#else)
|
||||
&& let Some((if_expr_positive, body)) = are_negated(cx, if_body_expr, else_body_expr)
|
||||
&& let ctxt = expr.span.ctxt()
|
||||
&& let Some((if_expr_positive, body)) = are_negated(cx, ctxt, if_body_expr, else_body_expr)
|
||||
{
|
||||
let sugg_positive_abs = if is_testing_positive(cx, cond, body) {
|
||||
let sugg_positive_abs = if is_testing_positive(cx, ctxt, cond, body) {
|
||||
if_expr_positive
|
||||
} else if is_testing_negative(cx, cond, body) {
|
||||
} else if is_testing_negative(cx, ctxt, cond, body) {
|
||||
!if_expr_positive
|
||||
} else {
|
||||
return;
|
||||
|
||||
@@ -19,6 +19,7 @@ pub(super) fn detect(cx: &LateContext<'_>, receiver: &Expr<'_>, app: &mut Applic
|
||||
add_rhs,
|
||||
) = receiver.kind
|
||||
{
|
||||
let ctxt = receiver.span.ctxt();
|
||||
// check if expression of the form x * x + y * y
|
||||
if let ExprKind::Binary(
|
||||
Spanned {
|
||||
@@ -34,8 +35,8 @@ pub(super) fn detect(cx: &LateContext<'_>, receiver: &Expr<'_>, app: &mut Applic
|
||||
rmul_lhs,
|
||||
rmul_rhs,
|
||||
) = add_rhs.kind
|
||||
&& eq_expr_value(cx, lmul_lhs, lmul_rhs)
|
||||
&& eq_expr_value(cx, rmul_lhs, rmul_rhs)
|
||||
&& eq_expr_value(cx, ctxt, lmul_lhs, lmul_rhs)
|
||||
&& eq_expr_value(cx, ctxt, rmul_lhs, rmul_rhs)
|
||||
{
|
||||
return Some(format!(
|
||||
"{}.hypot({})",
|
||||
|
||||
@@ -4,18 +4,18 @@
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::Spanned;
|
||||
use rustc_span::{Spanned, SyntaxContext};
|
||||
|
||||
use super::SUBOPTIMAL_FLOPS;
|
||||
|
||||
fn are_same_base_logs(cx: &LateContext<'_>, expr_a: &Expr<'_>, expr_b: &Expr<'_>) -> bool {
|
||||
fn are_same_base_logs(cx: &LateContext<'_>, ctxt: SyntaxContext, expr_a: &Expr<'_>, expr_b: &Expr<'_>) -> bool {
|
||||
if let ExprKind::MethodCall(PathSegment { ident: method_a, .. }, _, args_a, _) = expr_a.kind
|
||||
&& let ExprKind::MethodCall(PathSegment { ident: method_b, .. }, _, args_b, _) = expr_b.kind
|
||||
{
|
||||
return method_a.name == method_b.name
|
||||
&& args_a.len() == args_b.len()
|
||||
&& (matches!(method_a.name, sym::ln | sym::log2 | sym::log10)
|
||||
|| method_a.name == sym::log && args_a.len() == 1 && eq_expr_value(cx, &args_a[0], &args_b[0]));
|
||||
|| method_a.name == sym::log && args_a.len() == 1 && eq_expr_value(cx, ctxt, &args_a[0], &args_b[0]));
|
||||
}
|
||||
|
||||
false
|
||||
@@ -30,7 +30,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
lhs,
|
||||
rhs,
|
||||
) = expr.kind
|
||||
&& are_same_base_logs(cx, lhs, rhs)
|
||||
&& are_same_base_logs(cx, expr.span.ctxt(), lhs, rhs)
|
||||
&& let ExprKind::MethodCall(_, largs_self, ..) = lhs.kind
|
||||
&& let ExprKind::MethodCall(_, rargs_self, ..) = rhs.kind
|
||||
{
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::SyntaxContext;
|
||||
use rustc_span::edition::Edition::Edition2024;
|
||||
|
||||
declare_clippy_lint! {
|
||||
@@ -61,9 +62,10 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
|
||||
if_else: Some(if_else),
|
||||
..
|
||||
}) = higher::IfLet::hir(cx, expr)
|
||||
&& let Some(op_mutex) = for_each_expr_without_closures(let_expr, |e| mutex_lock_call(cx, e, None))
|
||||
&& let ctxt = expr.span.ctxt()
|
||||
&& let Some(op_mutex) = for_each_expr_without_closures(let_expr, |e| mutex_lock_call(cx, ctxt, e, None))
|
||||
&& let Some(arm_mutex) =
|
||||
for_each_expr_without_closures((if_then, if_else), |e| mutex_lock_call(cx, e, Some(op_mutex)))
|
||||
for_each_expr_without_closures((if_then, if_else), |e| mutex_lock_call(cx, ctxt, e, Some(op_mutex)))
|
||||
{
|
||||
let diag = |diag: &mut Diag<'_, ()>| {
|
||||
diag.span_label(
|
||||
@@ -89,6 +91,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
|
||||
|
||||
fn mutex_lock_call<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
ctxt: SyntaxContext,
|
||||
expr: &'tcx Expr<'_>,
|
||||
op_mutex: Option<&'tcx Expr<'_>>,
|
||||
) -> ControlFlow<&'tcx Expr<'tcx>> {
|
||||
@@ -96,7 +99,7 @@ fn mutex_lock_call<'tcx>(
|
||||
&& path.ident.name == sym::lock
|
||||
&& let ty = cx.typeck_results().expr_ty(self_arg).peel_refs()
|
||||
&& ty.is_diag_item(cx, sym::Mutex)
|
||||
&& op_mutex.is_none_or(|op| eq_expr_value(cx, self_arg, op))
|
||||
&& op_mutex.is_none_or(|op| eq_expr_value(cx, ctxt, self_arg, op))
|
||||
{
|
||||
ControlFlow::Break(self_arg)
|
||||
} else {
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::hygiene::walk_chain;
|
||||
use rustc_span::source_map::SourceMap;
|
||||
use rustc_span::{Span, Symbol};
|
||||
use rustc_span::{Span, Symbol, SyntaxContext};
|
||||
|
||||
use super::BRANCHES_SHARING_CODE;
|
||||
|
||||
@@ -196,7 +196,12 @@ fn eq_stmts(
|
||||
.all(|b| get_stmt(b).is_some_and(|s| eq_binding_names(cx, s, new_bindings)))
|
||||
} else {
|
||||
true
|
||||
}) && blocks.iter().all(|b| get_stmt(b).is_some_and(|s| eq.eq_stmt(s, stmt)))
|
||||
}) && blocks.iter().all(|b| {
|
||||
get_stmt(b).is_some_and(|s| {
|
||||
eq.set_eval_ctxt(SyntaxContext::root());
|
||||
eq.eq_stmt(s, stmt)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
#[expect(clippy::too_many_lines)]
|
||||
@@ -207,7 +212,7 @@ fn scan_block_for_eq<'tcx>(
|
||||
blocks: &[&'tcx Block<'_>],
|
||||
) -> BlockEq {
|
||||
let mut eq = SpanlessEq::new(cx);
|
||||
let mut eq = eq.inter_expr();
|
||||
let mut eq = eq.inter_expr(SyntaxContext::root());
|
||||
let mut moved_locals = Vec::new();
|
||||
|
||||
let mut cond_locals = HirIdSet::default();
|
||||
@@ -334,6 +339,7 @@ fn scan_block_for_eq<'tcx>(
|
||||
});
|
||||
if let Some(e) = block.expr {
|
||||
for block in blocks {
|
||||
eq.set_eval_ctxt(SyntaxContext::root());
|
||||
if block.expr.is_some_and(|expr| !eq.eq_expr(expr, e)) {
|
||||
moved_locals.truncate(moved_locals_at_start);
|
||||
return BlockEq {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
use clippy_utils::higher::has_let_expr;
|
||||
use rustc_hir::{Block, Expr};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::SyntaxContext;
|
||||
|
||||
use super::IF_SAME_THEN_ELSE;
|
||||
|
||||
@@ -12,7 +13,10 @@ pub(super) fn check(cx: &LateContext<'_>, conds: &[&Expr<'_>], blocks: &[&Block<
|
||||
.array_windows::<2>()
|
||||
.enumerate()
|
||||
.fold(true, |all_eq, (i, &[lhs, rhs])| {
|
||||
if eq.eq_block(lhs, rhs) && !has_let_expr(conds[i]) && conds.get(i + 1).is_none_or(|e| !has_let_expr(e)) {
|
||||
if eq.eq_block(SyntaxContext::root(), lhs, rhs)
|
||||
&& !has_let_expr(conds[i])
|
||||
&& conds.get(i + 1).is_none_or(|e| !has_let_expr(e))
|
||||
{
|
||||
span_lint_and_note(
|
||||
cx,
|
||||
IF_SAME_THEN_ELSE,
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
use clippy_utils::{SpanlessEq, eq_expr_value, find_binding_init, hash_expr, search_same};
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::SyntaxContext;
|
||||
|
||||
use super::IFS_SAME_COND;
|
||||
|
||||
@@ -34,10 +35,10 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, conds: &[&Expr<'_>], interior_
|
||||
if method_caller_is_mutable(cx, caller, interior_mut) {
|
||||
false
|
||||
} else {
|
||||
SpanlessEq::new(cx).eq_expr(lhs, rhs)
|
||||
SpanlessEq::new(cx).eq_expr(SyntaxContext::root(), lhs, rhs)
|
||||
}
|
||||
} else {
|
||||
eq_expr_value(cx, lhs, rhs)
|
||||
eq_expr_value(cx, SyntaxContext::root(), lhs, rhs)
|
||||
}
|
||||
},
|
||||
) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
use clippy_utils::{SpanlessEq, eq_expr_value, hash_expr, search_same};
|
||||
use rustc_hir::Expr;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::SyntaxContext;
|
||||
|
||||
use super::SAME_FUNCTIONS_IN_IF_CONDITION;
|
||||
|
||||
@@ -13,10 +14,10 @@ pub(super) fn check(cx: &LateContext<'_>, conds: &[&Expr<'_>]) {
|
||||
return false;
|
||||
}
|
||||
// Do not spawn warning if `IFS_SAME_COND` already produced it.
|
||||
if eq_expr_value(cx, lhs, rhs) {
|
||||
if eq_expr_value(cx, SyntaxContext::root(), lhs, rhs) {
|
||||
return false;
|
||||
}
|
||||
SpanlessEq::new(cx).eq_expr(lhs, rhs)
|
||||
SpanlessEq::new(cx).eq_expr(SyntaxContext::root(), lhs, rhs)
|
||||
};
|
||||
|
||||
for group in search_same(conds, |e| hash_expr(cx, e), eq) {
|
||||
|
||||
@@ -67,7 +67,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
|
||||
&& let ctxt = expr.span.ctxt()
|
||||
&& ex.span.ctxt() == ctxt
|
||||
&& cond.span.ctxt() == ctxt
|
||||
&& clippy_utils::SpanlessEq::new(cx).eq_expr(l, target)
|
||||
&& clippy_utils::SpanlessEq::new(cx).eq_expr(ctxt, l, target)
|
||||
&& AssignOpKind::AddAssign == op1.node
|
||||
&& let ExprKind::Lit(lit) = value.kind
|
||||
&& let LitKind::Int(Pu128(1), LitIntType::Unsuffixed) = lit.node
|
||||
|
||||
@@ -184,7 +184,7 @@ fn check_gt(
|
||||
msrv: Msrv,
|
||||
is_composited: bool,
|
||||
) {
|
||||
if is_side_effect_free(cx, big_expr) && is_side_effect_free(cx, little_expr) {
|
||||
if !big_expr.can_have_side_effects() && !little_expr.can_have_side_effects() {
|
||||
check_subtraction(
|
||||
cx,
|
||||
condition_span,
|
||||
@@ -199,10 +199,6 @@ fn check_gt(
|
||||
}
|
||||
}
|
||||
|
||||
fn is_side_effect_free(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
eq_expr_value(cx, expr, expr)
|
||||
}
|
||||
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
fn check_subtraction(
|
||||
cx: &LateContext<'_>,
|
||||
@@ -242,7 +238,8 @@ fn check_subtraction(
|
||||
&& let ExprKind::Binary(op, left, right) = if_block.kind
|
||||
&& let BinOpKind::Sub = op.node
|
||||
{
|
||||
if eq_expr_value(cx, left, big_expr) && eq_expr_value(cx, right, little_expr) {
|
||||
let ctxt = expr_span.ctxt();
|
||||
if eq_expr_value(cx, ctxt, left, big_expr) && eq_expr_value(cx, ctxt, right, little_expr) {
|
||||
// This part of the condition is voluntarily split from the one before to ensure that
|
||||
// if `snippet_opt` fails, it won't try the next conditions.
|
||||
if !is_in_const_context(cx) || msrv.meets(cx, msrvs::SATURATING_SUB_CONST) {
|
||||
@@ -277,8 +274,8 @@ fn check_subtraction(
|
||||
},
|
||||
);
|
||||
}
|
||||
} else if eq_expr_value(cx, left, little_expr)
|
||||
&& eq_expr_value(cx, right, big_expr)
|
||||
} else if eq_expr_value(cx, ctxt, left, little_expr)
|
||||
&& eq_expr_value(cx, ctxt, right, big_expr)
|
||||
&& let Some(big_expr_sugg) = Sugg::hir_opt(cx, big_expr)
|
||||
&& let Some(little_expr_sugg) = Sugg::hir_opt(cx, little_expr)
|
||||
{
|
||||
@@ -322,14 +319,15 @@ fn check_with_condition<'tcx>(
|
||||
// Extracting out the variable name
|
||||
&& let ExprKind::Path(QPath::Resolved(_, ares_path)) = target.kind
|
||||
{
|
||||
let ctxt = expr.span.ctxt();
|
||||
// Handle symmetric conditions in the if statement
|
||||
let (cond_var, cond_num_val) = if SpanlessEq::new(cx).eq_expr(cond_left, target) {
|
||||
let (cond_var, cond_num_val) = if SpanlessEq::new(cx).eq_expr(ctxt, cond_left, target) {
|
||||
if BinOpKind::Gt == cond_op || BinOpKind::Ne == cond_op {
|
||||
(cond_left, cond_right)
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else if SpanlessEq::new(cx).eq_expr(cond_right, target) {
|
||||
} else if SpanlessEq::new(cx).eq_expr(ctxt, cond_right, target) {
|
||||
if BinOpKind::Lt == cond_op || BinOpKind::Ne == cond_op {
|
||||
(cond_right, cond_left)
|
||||
} else {
|
||||
@@ -399,7 +397,7 @@ fn subtracts_one<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<&'a Exp
|
||||
ExprKind::Assign(target, value, _) => {
|
||||
if let ExprKind::Binary(ref op1, left1, right1) = value.kind
|
||||
&& BinOpKind::Sub == op1.node
|
||||
&& SpanlessEq::new(cx).eq_expr(left1, target)
|
||||
&& SpanlessEq::new(cx).eq_expr(expr.span.ctxt(), left1, target)
|
||||
&& is_integer_literal(right1, 1)
|
||||
{
|
||||
Some(target)
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::source::{HasSession, snippet};
|
||||
use rustc_ast::NodeId;
|
||||
use rustc_ast::ast::{Fn, FnRetTy, GenericParam, GenericParamKind};
|
||||
use rustc_ast::visit::{FnCtxt, FnKind};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_lint::{EarlyContext, EarlyLintPass};
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::Span;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Enforce that `where` bounds are used for all trait bounds.
|
||||
///
|
||||
/// ### Why restrict this?
|
||||
/// Enforce a single style throughout a codebase.
|
||||
/// Avoid uncertainty about whether a bound should be inline
|
||||
/// or out-of-line (i.e. a where bound).
|
||||
/// Avoid complex inline bounds, which could make a function declaration more difficult to read.
|
||||
///
|
||||
/// ### Known limitations
|
||||
/// Only lints functions and method declararions. Bounds on structs, enums,
|
||||
/// and impl blocks are not yet covered.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// fn foo<T: Clone>() {}
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// fn foo<T>() where T: Clone {}
|
||||
/// ```
|
||||
#[clippy::version = "1.97.0"]
|
||||
pub INLINE_TRAIT_BOUNDS,
|
||||
restriction,
|
||||
"enforce that `where` bounds are used for all trait bounds"
|
||||
}
|
||||
|
||||
declare_lint_pass!(InlineTraitBounds => [INLINE_TRAIT_BOUNDS]);
|
||||
|
||||
impl EarlyLintPass for InlineTraitBounds {
|
||||
fn check_fn(&mut self, cx: &EarlyContext<'_>, kind: FnKind<'_>, _: Span, _: NodeId) {
|
||||
let FnKind::Fn(ctxt, _vis, f) = kind else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Skip foreign functions (extern "C" etc.)
|
||||
if !matches!(ctxt, FnCtxt::Free | FnCtxt::Assoc(..)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if f.sig.span.in_external_macro(cx.sess().source_map()) {
|
||||
return;
|
||||
}
|
||||
|
||||
lint_fn(cx, f);
|
||||
}
|
||||
}
|
||||
|
||||
fn lint_fn(cx: &EarlyContext<'_>, f: &Fn) {
|
||||
let generics = &f.generics;
|
||||
let offenders: Vec<&GenericParam> = generics
|
||||
.params
|
||||
.iter()
|
||||
.filter(|param| {
|
||||
!param.bounds.is_empty() && matches!(param.kind, GenericParamKind::Lifetime | GenericParamKind::Type { .. })
|
||||
})
|
||||
.collect();
|
||||
if offenders.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let predicates = offenders
|
||||
.iter()
|
||||
.map(|param| build_predicate_text(cx, param))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut edits = Vec::new();
|
||||
|
||||
for param in offenders {
|
||||
if let Some(colon) = param.colon_span {
|
||||
let remove_span = colon.to(param.bounds.last().unwrap().span());
|
||||
|
||||
edits.push((remove_span, String::new()));
|
||||
}
|
||||
}
|
||||
|
||||
let predicate_text = predicates.join(", ");
|
||||
|
||||
let where_clause = &generics.where_clause;
|
||||
if where_clause.has_where_token {
|
||||
let (insert_at, suffix) = if let Some(last_pred) = where_clause.predicates.last() {
|
||||
// existing `where` with predicates: append after last predicate
|
||||
(last_pred.span.shrink_to_hi(), format!(", {predicate_text}"))
|
||||
} else {
|
||||
// `where` token present but empty predicate list
|
||||
(where_clause.span.shrink_to_hi(), format!(" {predicate_text}"))
|
||||
};
|
||||
|
||||
edits.push((insert_at, suffix));
|
||||
} else {
|
||||
let insert_at = match &f.sig.decl.output {
|
||||
FnRetTy::Default(span) => span.shrink_to_lo(),
|
||||
FnRetTy::Ty(ty) => ty.span.shrink_to_hi(),
|
||||
};
|
||||
edits.push((insert_at, format!(" where {predicate_text}")));
|
||||
}
|
||||
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
INLINE_TRAIT_BOUNDS,
|
||||
generics.span,
|
||||
"inline trait bounds used",
|
||||
|diag| {
|
||||
diag.multipart_suggestion(
|
||||
"move bounds to a `where` clause",
|
||||
edits,
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn build_predicate_text(cx: &EarlyContext<'_>, param: &GenericParam) -> String {
|
||||
// bounds is guaranteed non-empty by the filter in `lint_fn`
|
||||
let first = param.bounds.first().unwrap();
|
||||
let last = param.bounds.last().unwrap();
|
||||
|
||||
let bounds_span = first.span().to(last.span());
|
||||
|
||||
let lhs = snippet(cx, param.ident.span, "..");
|
||||
|
||||
let rhs = snippet(cx, bounds_span, "..");
|
||||
|
||||
format!("{lhs}: {rhs}")
|
||||
}
|
||||
@@ -168,6 +168,7 @@
|
||||
mod inherent_to_string;
|
||||
mod init_numbered_fields;
|
||||
mod inline_fn_without_body;
|
||||
mod inline_trait_bounds;
|
||||
mod int_plus_one;
|
||||
mod item_name_repetitions;
|
||||
mod items_after_statements;
|
||||
@@ -518,6 +519,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
|
||||
Box::new(|| Box::new(field_scoped_visibility_modifiers::FieldScopedVisibilityModifiers)),
|
||||
Box::new(|| Box::new(cfg_not_test::CfgNotTest)),
|
||||
Box::new(|| Box::new(empty_line_after::EmptyLineAfter::new())),
|
||||
Box::new(|| Box::new(inline_trait_bounds::InlineTraitBounds)),
|
||||
// add early passes here, used by `cargo dev new_lint`
|
||||
];
|
||||
store.early_passes.extend(early_lints);
|
||||
|
||||
@@ -89,13 +89,13 @@ fn check_index_usage<'tcx>(
|
||||
// `Index` directly and no deref to `str` would happen in that case).
|
||||
if cx.typeck_results().expr_ty_adjusted(recv).peel_refs().is_str()
|
||||
&& BYTE_INDEX_METHODS.contains(&segment.ident.name)
|
||||
&& eq_expr_value(cx, chars_recv, recv) =>
|
||||
&& eq_expr_value(cx, expr.span.ctxt(), chars_recv, recv) =>
|
||||
{
|
||||
"passing a character position to a method that expects a byte index"
|
||||
},
|
||||
ExprKind::Index(target, ..)
|
||||
if is_string_like(cx.typeck_results().expr_ty_adjusted(target).peel_refs())
|
||||
&& eq_expr_value(cx, chars_recv, target) =>
|
||||
&& eq_expr_value(cx, expr.span.ctxt(), chars_recv, target) =>
|
||||
{
|
||||
"indexing into a string with a character position where a byte index is expected"
|
||||
},
|
||||
|
||||
@@ -65,7 +65,7 @@ fn is_vec_pop_unwrap(cx: &LateContext<'_>, expr: &Expr<'_>, is_empty_recv: &Expr
|
||||
&& let ExprKind::MethodCall(_, pop_recv, ..) = unwrap_recv.kind
|
||||
{
|
||||
// make sure they're the same `Vec`
|
||||
SpanlessEq::new(cx).eq_expr(pop_recv, is_empty_recv)
|
||||
SpanlessEq::new(cx).eq_expr(expr.span.ctxt(), pop_recv, is_empty_recv)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
@@ -101,8 +101,9 @@ pub(super) fn check<'tcx>(
|
||||
if let ExprKind::Binary(ref op, left, right) = end.kind
|
||||
&& op.node == BinOpKind::Add
|
||||
{
|
||||
let start_equal_left = SpanlessEq::new(cx).eq_expr(start, left);
|
||||
let start_equal_right = SpanlessEq::new(cx).eq_expr(start, right);
|
||||
let ctxt = start.span.ctxt();
|
||||
let start_equal_left = SpanlessEq::new(cx).eq_expr(ctxt, start, left);
|
||||
let start_equal_right = SpanlessEq::new(cx).eq_expr(ctxt, start, right);
|
||||
|
||||
if start_equal_left {
|
||||
take_expr = right;
|
||||
|
||||
@@ -121,12 +121,12 @@ fn is_sub_expr(
|
||||
expected_b: &Expr<'_>,
|
||||
expected_ty: Ty<'_>,
|
||||
) -> bool {
|
||||
let expr = peel_blocks(expr).kind;
|
||||
let expr = peel_blocks(expr);
|
||||
|
||||
if let ty::Int(ty) = expected_ty.kind() {
|
||||
let unsigned = Ty::new_uint(cx.tcx, ty.to_unsigned());
|
||||
|
||||
return if let ExprKind::Cast(expr, cast_ty) = expr
|
||||
return if let ExprKind::Cast(expr, cast_ty) = expr.kind
|
||||
&& cx.typeck_results().node_type(cast_ty.hir_id) == unsigned
|
||||
{
|
||||
is_sub_expr(cx, expr, expected_a, expected_b, unsigned)
|
||||
@@ -135,10 +135,11 @@ fn is_sub_expr(
|
||||
};
|
||||
}
|
||||
|
||||
if let ExprKind::Binary(op, a, b) = expr
|
||||
let ctxt = expr.span.ctxt();
|
||||
if let ExprKind::Binary(op, a, b) = expr.kind
|
||||
&& let BinOpKind::Sub = op.node
|
||||
&& eq_expr_value(cx, a, expected_a)
|
||||
&& eq_expr_value(cx, b, expected_b)
|
||||
&& eq_expr_value(cx, ctxt, a, expected_a)
|
||||
&& eq_expr_value(cx, ctxt, b, expected_b)
|
||||
{
|
||||
true
|
||||
} else {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty;
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::SyntaxContext;
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
declare_clippy_lint! {
|
||||
@@ -60,7 +61,7 @@ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
&& let Some(block) = branch_block(then, r#else, branch)
|
||||
{
|
||||
let mut eq = SpanlessEq::new(cx).deny_side_effects().paths_by_resolution();
|
||||
if !eq.eq_expr(divisor, divisor) {
|
||||
if !eq.eq_expr(SyntaxContext::root(), divisor, divisor) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -70,7 +71,7 @@ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
let found_early_use = for_each_expr_without_closures(block, |e| {
|
||||
if let ExprKind::Binary(binop, lhs, rhs) = e.kind
|
||||
&& binop.node == BinOpKind::Div
|
||||
&& eq.eq_expr(rhs, divisor)
|
||||
&& eq.eq_expr(SyntaxContext::root(), rhs, divisor)
|
||||
&& is_unsigned_integer(cx, lhs)
|
||||
{
|
||||
match first_use {
|
||||
@@ -84,7 +85,7 @@ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
ControlFlow::<(), _>::Continue(Descend::No)
|
||||
} else if let ExprKind::AssignOp(op, lhs, rhs) = e.kind
|
||||
&& op.node == AssignOpKind::DivAssign
|
||||
&& eq.eq_expr(rhs, divisor)
|
||||
&& eq.eq_expr(SyntaxContext::root(), rhs, divisor)
|
||||
&& is_unsigned_integer(cx, lhs)
|
||||
{
|
||||
match first_use {
|
||||
@@ -96,7 +97,7 @@ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
division_spans.push(e.span);
|
||||
|
||||
ControlFlow::<(), _>::Continue(Descend::No)
|
||||
} else if eq.eq_expr(e, divisor) {
|
||||
} else if eq.eq_expr(SyntaxContext::root(), e, divisor) {
|
||||
if first_use.is_none() {
|
||||
first_use = Some(UseKind::Other);
|
||||
return ControlFlow::Break(());
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::Ty;
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::Span;
|
||||
use rustc_span::{Span, SyntaxContext};
|
||||
use std::cmp::Ordering;
|
||||
use std::ops::Deref;
|
||||
|
||||
@@ -261,6 +261,7 @@ fn is_if_elseif_else_pattern<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx
|
||||
{
|
||||
let params = is_clamp_meta_pattern(
|
||||
cx,
|
||||
expr.span.ctxt(),
|
||||
&BinaryOp::new(peel_blocks(cond))?,
|
||||
&BinaryOp::new(peel_blocks(else_if_cond))?,
|
||||
peel_blocks(then),
|
||||
@@ -268,7 +269,7 @@ fn is_if_elseif_else_pattern<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx
|
||||
None,
|
||||
)?;
|
||||
// Contents of the else should be the resolved input.
|
||||
if !eq_expr_value(cx, params.input, peel_blocks(else_body)) {
|
||||
if !eq_expr_value(cx, expr.span.ctxt(), params.input, peel_blocks(else_body)) {
|
||||
return None;
|
||||
}
|
||||
Some(ClampSuggestion {
|
||||
@@ -445,6 +446,7 @@ fn is_match_pattern<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Opt
|
||||
}
|
||||
if let Some(params) = is_clamp_meta_pattern(
|
||||
cx,
|
||||
expr.span.ctxt(),
|
||||
&first,
|
||||
&second,
|
||||
first_expr,
|
||||
@@ -496,11 +498,14 @@ fn is_two_if_pattern<'tcx>(cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) ->
|
||||
peel_blocks_with_stmt(first_then).kind
|
||||
&& let ExprKind::Assign(maybe_input_second_path, maybe_min_max_second, _) =
|
||||
peel_blocks_with_stmt(second_then).kind
|
||||
&& eq_expr_value(cx, maybe_input_first_path, maybe_input_second_path)
|
||||
&& let ctxt = first_expr.span.ctxt()
|
||||
&& second_expr.span.ctxt() == ctxt
|
||||
&& eq_expr_value(cx, ctxt, maybe_input_first_path, maybe_input_second_path)
|
||||
&& let Some(first_bin) = BinaryOp::new(first_cond)
|
||||
&& let Some(second_bin) = BinaryOp::new(second_cond)
|
||||
&& let Some(input_min_max) = is_clamp_meta_pattern(
|
||||
cx,
|
||||
ctxt,
|
||||
&first_bin,
|
||||
&second_bin,
|
||||
maybe_min_max_first,
|
||||
@@ -552,15 +557,17 @@ fn is_if_elseif_pattern<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) ->
|
||||
&& let ExprKind::Assign(maybe_input_second_path, maybe_min_max_second, _) =
|
||||
peel_blocks_with_stmt(else_if_then).kind
|
||||
{
|
||||
let ctxt = expr.span.ctxt();
|
||||
let params = is_clamp_meta_pattern(
|
||||
cx,
|
||||
ctxt,
|
||||
&BinaryOp::new(peel_blocks(cond))?,
|
||||
&BinaryOp::new(peel_blocks(else_if_cond))?,
|
||||
peel_blocks(maybe_min_max_first),
|
||||
peel_blocks(maybe_min_max_second),
|
||||
None,
|
||||
)?;
|
||||
if !eq_expr_value(cx, maybe_input_first_path, maybe_input_second_path) {
|
||||
if !eq_expr_value(cx, ctxt, maybe_input_first_path, maybe_input_second_path) {
|
||||
return None;
|
||||
}
|
||||
Some(ClampSuggestion {
|
||||
@@ -631,6 +638,7 @@ fn flip(&self) -> Self {
|
||||
/// result can not be the shared argument in either case.
|
||||
fn is_clamp_meta_pattern<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
ctxt: SyntaxContext,
|
||||
first_bin: &BinaryOp<'tcx>,
|
||||
second_bin: &BinaryOp<'tcx>,
|
||||
first_expr: &'tcx Expr<'tcx>,
|
||||
@@ -642,8 +650,10 @@ fn is_clamp_meta_pattern<'tcx>(
|
||||
// be the input variable, not the min or max.
|
||||
input_hir_ids: Option<(HirId, HirId)>,
|
||||
) -> Option<InputMinMax<'tcx>> {
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
fn check<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
ctxt: SyntaxContext,
|
||||
first_bin: &BinaryOp<'tcx>,
|
||||
second_bin: &BinaryOp<'tcx>,
|
||||
first_expr: &'tcx Expr<'tcx>,
|
||||
@@ -659,11 +669,11 @@ fn check<'tcx>(
|
||||
peel_blocks(first_bin.left).res_local_id() == Some(first_hir_id)
|
||||
&& peel_blocks(second_bin.left).res_local_id() == Some(second_hir_id)
|
||||
},
|
||||
None => eq_expr_value(cx, first_bin.left, second_bin.left),
|
||||
None => eq_expr_value(cx, ctxt, first_bin.left, second_bin.left),
|
||||
};
|
||||
(refers_to_input
|
||||
&& eq_expr_value(cx, first_bin.right, first_expr)
|
||||
&& eq_expr_value(cx, second_bin.right, second_expr))
|
||||
&& eq_expr_value(cx, ctxt, first_bin.right, first_expr)
|
||||
&& eq_expr_value(cx, ctxt, second_bin.right, second_expr))
|
||||
.then_some(InputMinMax {
|
||||
input: first_bin.left,
|
||||
min,
|
||||
@@ -699,9 +709,20 @@ fn check<'tcx>(
|
||||
];
|
||||
|
||||
cases.into_iter().find_map(|(first, second)| {
|
||||
check(cx, &first, &second, first_expr, second_expr, input_hir_ids, is_float).or_else(|| {
|
||||
check(
|
||||
cx,
|
||||
ctxt,
|
||||
&first,
|
||||
&second,
|
||||
first_expr,
|
||||
second_expr,
|
||||
input_hir_ids,
|
||||
is_float,
|
||||
)
|
||||
.or_else(|| {
|
||||
check(
|
||||
cx,
|
||||
ctxt,
|
||||
&second,
|
||||
&first,
|
||||
second_expr,
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty;
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::SyntaxContext;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
@@ -109,11 +110,12 @@ fn count_ones_receiver<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Optio
|
||||
/// Return `greater` if `smaller == greater - 1`
|
||||
fn is_one_less<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
ctxt: SyntaxContext,
|
||||
greater: &'tcx Expr<'tcx>,
|
||||
smaller: &Expr<'tcx>,
|
||||
) -> Option<&'tcx Expr<'tcx>> {
|
||||
if let Some((lhs, rhs)) = unexpanded_binop_operands(smaller, BinOpKind::Sub)
|
||||
&& SpanlessEq::new(cx).eq_expr(greater, lhs)
|
||||
&& SpanlessEq::new(cx).eq_expr(ctxt, greater, lhs)
|
||||
&& is_integer_literal(rhs, 1)
|
||||
&& matches!(cx.typeck_results().expr_ty_adjusted(greater).kind(), ty::Uint(_))
|
||||
{
|
||||
@@ -126,7 +128,7 @@ fn is_one_less<'tcx>(
|
||||
/// Return `v` if `expr` is `v & (v - 1)` or `(v - 1) & v`
|
||||
fn is_and_minus_one<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
|
||||
let (lhs, rhs) = unexpanded_binop_operands(expr, BinOpKind::BitAnd)?;
|
||||
is_one_less(cx, lhs, rhs).or_else(|| is_one_less(cx, rhs, lhs))
|
||||
is_one_less(cx, expr.span.ctxt(), lhs, rhs).or_else(|| is_one_less(cx, expr.span.ctxt(), rhs, lhs))
|
||||
}
|
||||
|
||||
/// Return the operands of the `expr` binary operation if the operator is `op` and none of the
|
||||
|
||||
@@ -212,7 +212,7 @@ fn check_is_some_and_pattern<'tcx>(
|
||||
&& let ExprKind::Closure(closure) = closure_arg.kind
|
||||
&& let body = cx.tcx.hir_body(closure.body)
|
||||
&& let Some((pop_collection, pop_span, suggestable)) = check_pop_unwrap(cx, then_block, pop_method)
|
||||
&& eq_expr_value(cx, collection_expr, pop_collection)
|
||||
&& eq_expr_value(cx, if_expr_span.ctxt(), collection_expr, pop_collection)
|
||||
&& let Some(param) = body.params.first()
|
||||
&& let Some(ident) = param.pat.simple_ident()
|
||||
{
|
||||
@@ -274,7 +274,7 @@ fn check_if_let_pattern<'tcx>(
|
||||
if let ExprKind::If(inner_cond, inner_then, None) = inner_if.kind
|
||||
&& is_local_used(cx, inner_cond, binding_id)
|
||||
&& let Some((pop_collection, pop_span, suggestable)) = check_pop_unwrap(cx, inner_then, pop_method)
|
||||
&& eq_expr_value(cx, collection_expr, pop_collection)
|
||||
&& eq_expr_value(cx, if_expr_span.ctxt(), collection_expr, pop_collection)
|
||||
{
|
||||
return Some(ManualPopIfPattern {
|
||||
kind,
|
||||
@@ -327,7 +327,7 @@ fn check_let_chain_pattern<'tcx>(
|
||||
&& kind.is_diag_item(cx, collection_expr)
|
||||
&& is_local_used(cx, right, binding_id)
|
||||
&& let Some((pop_collection, pop_span, suggestable)) = check_pop_unwrap(cx, then_block, pop_method)
|
||||
&& eq_expr_value(cx, collection_expr, pop_collection)
|
||||
&& eq_expr_value(cx, if_expr_span.ctxt(), collection_expr, pop_collection)
|
||||
{
|
||||
return Some(ManualPopIfPattern {
|
||||
kind,
|
||||
@@ -372,7 +372,7 @@ fn check_map_unwrap_or_pattern<'tcx>(
|
||||
&& let body = cx.tcx.hir_body(closure.body)
|
||||
&& cx.typeck_results().expr_ty(body.value).is_bool()
|
||||
&& let Some((pop_collection, pop_span, suggestable)) = check_pop_unwrap(cx, then_block, pop_method)
|
||||
&& eq_expr_value(cx, collection_expr, pop_collection)
|
||||
&& eq_expr_value(cx, if_expr_span.ctxt(), collection_expr, pop_collection)
|
||||
&& let Some(param) = body.params.first()
|
||||
&& let Some(ident) = param.pat.simple_ident()
|
||||
{
|
||||
|
||||
@@ -85,7 +85,7 @@ fn check_into_iter(
|
||||
&& let Some(into_iter_def_id) = cx.typeck_results().type_dependent_def_id(into_iter_expr.hir_id)
|
||||
&& Some(into_iter_def_id) == cx.tcx.lang_items().into_iter_fn()
|
||||
&& match_acceptable_type(cx, left_expr, msrv)
|
||||
&& SpanlessEq::new(cx).eq_expr(left_expr, struct_expr)
|
||||
&& SpanlessEq::new(cx).eq_expr(parent_expr_span.ctxt(), left_expr, struct_expr)
|
||||
&& let hir::ExprKind::MethodCall(_, _, [closure_expr], _) = target_expr.kind
|
||||
&& let hir::ExprKind::Closure(closure) = closure_expr.kind
|
||||
&& let filter_body = cx.tcx.hir_body(closure.body)
|
||||
@@ -132,7 +132,7 @@ fn check_iter(
|
||||
&& let Some(iter_expr_def_id) = cx.typeck_results().type_dependent_def_id(iter_expr.hir_id)
|
||||
&& match_acceptable_sym(cx, iter_expr_def_id)
|
||||
&& match_acceptable_type(cx, left_expr, msrv)
|
||||
&& SpanlessEq::new(cx).eq_expr(left_expr, struct_expr)
|
||||
&& SpanlessEq::new(cx).eq_expr(parent_expr_span.ctxt(), left_expr, struct_expr)
|
||||
&& let hir::ExprKind::MethodCall(_, _, [closure_expr], _) = filter_expr.kind
|
||||
&& let hir::ExprKind::Closure(closure) = closure_expr.kind
|
||||
&& let filter_body = cx.tcx.hir_body(closure.body)
|
||||
@@ -190,7 +190,7 @@ fn check_to_owned(
|
||||
&& cx.tcx.is_diagnostic_item(sym::str_chars, chars_expr_def_id)
|
||||
&& let ty = cx.typeck_results().expr_ty(str_expr).peel_refs()
|
||||
&& ty.is_lang_item(cx, hir::LangItem::String)
|
||||
&& SpanlessEq::new(cx).eq_expr(left_expr, str_expr)
|
||||
&& SpanlessEq::new(cx).eq_expr(parent_expr_span.ctxt(), left_expr, str_expr)
|
||||
&& let hir::ExprKind::MethodCall(_, _, [closure_expr], _) = filter_expr.kind
|
||||
&& let hir::ExprKind::Closure(closure) = closure_expr.kind
|
||||
&& let filter_body = cx.tcx.hir_body(closure.body)
|
||||
|
||||
@@ -75,7 +75,8 @@ fn check_expr<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
|
||||
&& let Some((l_shift_dir, l_expr, l_amount)) = parse_shift(l)
|
||||
&& let Some((r_shift_dir, r_expr, r_amount)) = parse_shift(r)
|
||||
&& l_shift_dir != r_shift_dir
|
||||
&& clippy_utils::eq_expr_value(cx, l_expr, r_expr)
|
||||
&& let ctxt = expr.span.ctxt()
|
||||
&& clippy_utils::eq_expr_value(cx, ctxt, l_expr, r_expr)
|
||||
&& let Some(bit_width) = match cx.typeck_results().expr_ty(expr).kind() {
|
||||
ty::Int(itype) => itype.bit_width(),
|
||||
ty::Uint(itype) => itype.bit_width(),
|
||||
@@ -84,7 +85,6 @@ fn check_expr<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
|
||||
{
|
||||
let const_eval = ConstEvalCtxt::new(cx);
|
||||
|
||||
let ctxt = expr.span.ctxt();
|
||||
let (shift_function, amount) = if let Some(Constant::Int(l_amount_val)) =
|
||||
const_eval.eval_local(l_amount, ctxt)
|
||||
&& let Some(Constant::Int(r_amount_val)) = const_eval.eval_local(r_amount, ctxt)
|
||||
@@ -103,7 +103,7 @@ fn check_expr<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
|
||||
};
|
||||
|
||||
if let Some(Constant::Int(minuend)) = const_eval.eval_local(minuend, ctxt)
|
||||
&& clippy_utils::eq_expr_value(cx, amount1, amount2)
|
||||
&& clippy_utils::eq_expr_value(cx, ctxt, amount1, amount2)
|
||||
// (x << s) | (x >> bit_width - s)
|
||||
&& ((binop.node == BinOpKind::Sub && u128::from(bit_width) == minuend)
|
||||
// (x << s) | (x >> (bit_width - 1) ^ s)
|
||||
|
||||
@@ -188,7 +188,7 @@ fn eq_pattern_length<'tcx>(
|
||||
{
|
||||
constant_length(cx, pattern, ctxt).is_some_and(|length| n == length)
|
||||
} else {
|
||||
len_arg(cx, expr).is_some_and(|arg| eq_expr_value(cx, pattern, arg))
|
||||
len_arg(cx, expr).is_some_and(|arg| eq_expr_value(cx, SyntaxContext::root(), pattern, arg))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use clippy_config::Conf;
|
||||
use clippy_utils::SpanlessEq;
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::msrvs::{MEM_TAKE, Msrv};
|
||||
use clippy_utils::source::snippet_with_context;
|
||||
@@ -74,10 +75,11 @@ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
&& let StmtKind::Semi(assignment) = stmt.kind
|
||||
&& let ExprKind::Assign(mut_c, possible_false, _) = assignment.kind
|
||||
&& let ExprKind::Path(_) = mut_c.kind
|
||||
&& !expr.span.in_external_macro(cx.sess().source_map())
|
||||
&& let ctxt = expr.span.ctxt()
|
||||
&& !ctxt.in_external_macro(cx.sess().source_map())
|
||||
&& let Some(std_or_core) = clippy_utils::std_or_core(cx)
|
||||
&& self.msrv.meets(cx, MEM_TAKE)
|
||||
&& clippy_utils::SpanlessEq::new(cx).eq_expr(cond, mut_c)
|
||||
&& SpanlessEq::new(cx).eq_expr(ctxt, cond, mut_c)
|
||||
&& Some(false) == as_const_bool(possible_false)
|
||||
&& let Some(then_bool) = as_const_bool(then_expr)
|
||||
&& let Some(else_bool) = as_const_bool(else_expr)
|
||||
|
||||
@@ -14,12 +14,10 @@
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::mir::FakeReadCause;
|
||||
use rustc_middle::ty;
|
||||
use rustc_span::symbol::Ident;
|
||||
use rustc_span::{BytePos, Span};
|
||||
|
||||
use crate::collapsible_if::{parens_around, peel_parens};
|
||||
use rustc_span::{BytePos, Ident, Span, SyntaxContext};
|
||||
|
||||
use super::{COLLAPSIBLE_MATCH, pat_contains_disallowed_or};
|
||||
use crate::collapsible_if::{parens_around, peel_parens};
|
||||
|
||||
pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arms: &'tcx [Arm<'_>], msrv: Msrv) {
|
||||
if let Some(els_arm) = arms.iter().rfind(|arm| arm_is_wild_like(cx, arm)) {
|
||||
@@ -28,6 +26,7 @@ pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, ar
|
||||
let only_wildcards_after = last_non_wildcard.is_none_or(|lnw| idx >= lnw);
|
||||
check_arm(
|
||||
cx,
|
||||
arm.span.ctxt(),
|
||||
true,
|
||||
arm.pat,
|
||||
expr,
|
||||
@@ -43,18 +42,20 @@ pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, ar
|
||||
|
||||
pub(super) fn check_if_let<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
ctxt: SyntaxContext,
|
||||
pat: &'tcx Pat<'_>,
|
||||
body: &'tcx Expr<'_>,
|
||||
else_expr: Option<&'tcx Expr<'_>>,
|
||||
let_expr: &'tcx Expr<'_>,
|
||||
msrv: Msrv,
|
||||
) {
|
||||
check_arm(cx, false, pat, let_expr, body, None, else_expr, msrv, false);
|
||||
check_arm(cx, ctxt, false, pat, let_expr, body, None, else_expr, msrv, false);
|
||||
}
|
||||
|
||||
#[expect(clippy::too_many_arguments, clippy::too_many_lines)]
|
||||
fn check_arm<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
ctxt: SyntaxContext,
|
||||
outer_is_match: bool,
|
||||
outer_pat: &'tcx Pat<'tcx>,
|
||||
outer_cond: &'tcx Expr<'tcx>,
|
||||
@@ -94,7 +95,7 @@ fn check_arm<'tcx>(
|
||||
&& match (outer_else_body, inner_else_body) {
|
||||
(None, None) => true,
|
||||
(None, Some(e)) | (Some(e), None) => is_unit_expr(e),
|
||||
(Some(a), Some(b)) => SpanlessEq::new(cx).eq_expr(a, b),
|
||||
(Some(a), Some(b)) => SpanlessEq::new(cx).eq_expr(ctxt, a, b),
|
||||
}
|
||||
// the binding must not be used in the if guard
|
||||
&& outer_guard.is_none_or(|e| !is_local_used(cx, e, binding_id))
|
||||
@@ -145,7 +146,7 @@ fn check_arm<'tcx>(
|
||||
&& match (outer_else_body, inner.r#else) {
|
||||
(None, None) => true,
|
||||
(None, Some(e)) | (Some(e), None) => is_unit_expr(e),
|
||||
(Some(a), Some(b)) => SpanlessEq::new(cx).eq_expr(a, b),
|
||||
(Some(a), Some(b)) => SpanlessEq::new(cx).eq_expr(ctxt, a, b),
|
||||
}
|
||||
&& !pat_bindings_moved_or_mutated(cx, outer_pat, inner.cond)
|
||||
{
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
use rustc_lint::builtin::NON_EXHAUSTIVE_OMITTED_PATTERNS;
|
||||
use rustc_lint::{LateContext, LintContext};
|
||||
use rustc_middle::ty::{self, TypeckResults};
|
||||
use rustc_span::{ByteSymbol, ErrorGuaranteed, Span, Symbol};
|
||||
use rustc_span::{ByteSymbol, ErrorGuaranteed, Span, Symbol, SyntaxContext};
|
||||
|
||||
use super::MATCH_SAME_ARMS;
|
||||
|
||||
@@ -87,7 +87,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) {
|
||||
|
||||
SpanlessEq::new(cx)
|
||||
.expr_fallback(eq_fallback)
|
||||
.eq_expr(expr_a, expr_b)
|
||||
.eq_expr(SyntaxContext::root(), expr_a, expr_b)
|
||||
// these checks could be removed to allow unused bindings
|
||||
&& bindings_eq(lhs.pat, local_map.keys().copied().collect())
|
||||
&& bindings_eq(rhs.pat, local_map.values().copied().collect())
|
||||
|
||||
@@ -1138,6 +1138,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
} else if let Some(if_let) = higher::IfLet::hir(cx, expr) {
|
||||
collapsible_match::check_if_let(
|
||||
cx,
|
||||
if_let.let_span.ctxt(),
|
||||
if_let.let_pat,
|
||||
if_let.if_then,
|
||||
if_let.if_else,
|
||||
|
||||
@@ -12,10 +12,10 @@
|
||||
Arm, BindingMode, ByRef, Expr, ExprKind, ItemKind, Node, Pat, PatExpr, PatExprKind, PatKind, Path, QPath,
|
||||
};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::sym;
|
||||
use rustc_span::{SyntaxContext, sym};
|
||||
|
||||
pub(crate) fn check_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) {
|
||||
if arms.len() > 1 && expr_ty_matches_p_ty(cx, ex, expr) && check_all_arms(cx, ex, arms) {
|
||||
if arms.len() > 1 && expr_ty_matches_p_ty(cx, ex, expr) && check_all_arms(cx, expr.span.ctxt(), ex, arms) {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
@@ -49,7 +49,10 @@ pub(crate) fn check_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>],
|
||||
/// }
|
||||
/// ```
|
||||
pub(crate) fn check_if_let<'tcx>(cx: &LateContext<'tcx>, ex: &Expr<'_>, if_let: &higher::IfLet<'tcx>) {
|
||||
if !is_else_clause(cx.tcx, ex) && expr_ty_matches_p_ty(cx, if_let.let_expr, ex) && check_if_let_inner(cx, if_let) {
|
||||
if !is_else_clause(cx.tcx, ex)
|
||||
&& expr_ty_matches_p_ty(cx, if_let.let_expr, ex)
|
||||
&& check_if_let_inner(cx, ex.span.ctxt(), if_let)
|
||||
{
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
@@ -63,7 +66,7 @@ pub(crate) fn check_if_let<'tcx>(cx: &LateContext<'tcx>, ex: &Expr<'_>, if_let:
|
||||
}
|
||||
}
|
||||
|
||||
fn check_all_arms(cx: &LateContext<'_>, match_expr: &Expr<'_>, arms: &[Arm<'_>]) -> bool {
|
||||
fn check_all_arms(cx: &LateContext<'_>, ctxt: SyntaxContext, match_expr: &Expr<'_>, arms: &[Arm<'_>]) -> bool {
|
||||
for arm in arms {
|
||||
let arm_expr = peel_blocks_with_stmt(arm.body);
|
||||
|
||||
@@ -74,7 +77,7 @@ fn check_all_arms(cx: &LateContext<'_>, match_expr: &Expr<'_>, arms: &[Arm<'_>])
|
||||
}
|
||||
|
||||
if let PatKind::Wild = arm.pat.kind {
|
||||
if !eq_expr_value(cx, match_expr, arm_expr) {
|
||||
if !eq_expr_value(cx, ctxt, match_expr, arm_expr) {
|
||||
return false;
|
||||
}
|
||||
} else if !pat_same_as_expr(arm.pat, arm_expr) {
|
||||
@@ -85,7 +88,7 @@ fn check_all_arms(cx: &LateContext<'_>, match_expr: &Expr<'_>, arms: &[Arm<'_>])
|
||||
true
|
||||
}
|
||||
|
||||
fn check_if_let_inner(cx: &LateContext<'_>, if_let: &higher::IfLet<'_>) -> bool {
|
||||
fn check_if_let_inner(cx: &LateContext<'_>, ctxt: SyntaxContext, if_let: &higher::IfLet<'_>) -> bool {
|
||||
if let Some(if_else) = if_let.if_else {
|
||||
if !pat_same_as_expr(if_let.let_pat, peel_blocks_with_stmt(if_let.if_then)) {
|
||||
return false;
|
||||
@@ -93,9 +96,9 @@ fn check_if_let_inner(cx: &LateContext<'_>, if_let: &higher::IfLet<'_>) -> bool
|
||||
|
||||
// Recursively check for each `else if let` phrase,
|
||||
if let Some(ref nested_if_let) = higher::IfLet::hir(cx, if_else)
|
||||
&& SpanlessEq::new(cx).eq_expr(nested_if_let.let_expr, if_let.let_expr)
|
||||
&& SpanlessEq::new(cx).eq_expr(ctxt, nested_if_let.let_expr, if_let.let_expr)
|
||||
{
|
||||
return check_if_let_inner(cx, nested_if_let);
|
||||
return check_if_let_inner(cx, ctxt, nested_if_let);
|
||||
}
|
||||
|
||||
if matches!(if_else.kind, ExprKind::Block(..)) {
|
||||
@@ -106,9 +109,9 @@ fn check_if_let_inner(cx: &LateContext<'_>, if_let: &higher::IfLet<'_>) -> bool
|
||||
let let_expr_ty = cx.typeck_results().expr_ty(if_let.let_expr);
|
||||
if let_expr_ty.is_diag_item(cx, sym::Option) {
|
||||
return else_expr.res(cx).ctor_parent(cx).is_lang_item(cx, OptionNone)
|
||||
|| eq_expr_value(cx, if_let.let_expr, else_expr);
|
||||
|| eq_expr_value(cx, ctxt, if_let.let_expr, else_expr);
|
||||
}
|
||||
return eq_expr_value(cx, if_let.let_expr, else_expr);
|
||||
return eq_expr_value(cx, ctxt, if_let.let_expr, else_expr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ pub(super) fn check<'tcx>(
|
||||
// of the last replace call in the current chain, don't lint as it was already linted
|
||||
if let Some(parent) = get_parent_expr(cx, expr)
|
||||
&& let Some((sym::replace, _, [current_from, current_to], _, _)) = method_call(parent)
|
||||
&& eq_expr_value(cx, to, current_to)
|
||||
&& eq_expr_value(cx, parent.span.ctxt(), to, current_to)
|
||||
&& from_kind == cx.typeck_results().expr_ty(current_from).peel_refs().kind()
|
||||
{
|
||||
return;
|
||||
@@ -46,9 +46,10 @@ fn collect_replace_calls<'tcx>(
|
||||
let mut methods = VecDeque::new();
|
||||
let mut from_args = VecDeque::new();
|
||||
|
||||
let ctxt = expr.span.ctxt();
|
||||
let _: Option<()> = for_each_expr_without_closures(expr, |e| {
|
||||
if let Some((sym::replace, _, [from, to], _, _)) = method_call(e) {
|
||||
if eq_expr_value(cx, to_arg, to) && cx.typeck_results().expr_ty(from).peel_refs().is_char() {
|
||||
if eq_expr_value(cx, ctxt, to_arg, to) && cx.typeck_results().expr_ty(from).peel_refs().is_char() {
|
||||
methods.push_front(e);
|
||||
from_args.push_front(from);
|
||||
ControlFlow::Continue(())
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::TypeckResults;
|
||||
use rustc_middle::ty::adjustment::Adjust;
|
||||
use rustc_span::Span;
|
||||
use rustc_span::symbol::{Ident, Symbol};
|
||||
use rustc_span::{Span, SyntaxContext};
|
||||
|
||||
use super::{MANUAL_FILTER_MAP, MANUAL_FIND_MAP, OPTION_FILTER_MAP, RESULT_FILTER_MAP};
|
||||
|
||||
@@ -109,6 +109,7 @@ impl<'tcx> OffendingFilterExpr<'tcx> {
|
||||
pub fn check_map_call(
|
||||
&self,
|
||||
cx: &LateContext<'tcx>,
|
||||
ctxt: SyntaxContext,
|
||||
map_body: &'tcx Body<'tcx>,
|
||||
map_param_id: HirId,
|
||||
filter_param_id: HirId,
|
||||
@@ -150,7 +151,7 @@ pub fn check_map_call(
|
||||
&& a_typeck_results.expr_ty_adjusted(a) == b_typeck_results.expr_ty_adjusted(b)
|
||||
})
|
||||
&& (simple_equal
|
||||
|| SpanlessEq::new(cx).expr_fallback(eq_fallback).eq_expr(receiver, map_arg_peeled))
|
||||
|| SpanlessEq::new(cx).expr_fallback(eq_fallback).eq_expr(ctxt, receiver, map_arg_peeled))
|
||||
{
|
||||
Some(CheckResult::Method {
|
||||
map_arg,
|
||||
@@ -323,7 +324,9 @@ pub(super) fn check(
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some((map_param_ident, check_result)) = is_find_or_filter(cx, map_recv, filter_arg, map_arg) {
|
||||
if let Some((map_param_ident, check_result)) =
|
||||
is_find_or_filter(cx, expr.span.ctxt(), map_recv, filter_arg, map_arg)
|
||||
{
|
||||
let span = filter_span.with_hi(expr.span.hi());
|
||||
let (filter_name, lint) = if is_find {
|
||||
("find", MANUAL_FIND_MAP)
|
||||
@@ -397,6 +400,7 @@ pub(super) fn check(
|
||||
|
||||
fn is_find_or_filter<'a>(
|
||||
cx: &LateContext<'a>,
|
||||
ctxt: SyntaxContext,
|
||||
map_recv: &Expr<'_>,
|
||||
filter_arg: &Expr<'_>,
|
||||
map_arg: &Expr<'_>,
|
||||
@@ -422,7 +426,7 @@ fn is_find_or_filter<'a>(
|
||||
&& let PatKind::Binding(_, map_param_id, map_param_ident, None) = map_param.pat.kind
|
||||
|
||||
&& let Some(check_result) =
|
||||
offending_expr.check_map_call(cx, map_body, map_param_id, filter_param_id, is_filter_param_ref)
|
||||
offending_expr.check_map_call(cx, ctxt, map_body, map_param_id, filter_param_id, is_filter_param_ref)
|
||||
{
|
||||
return Some((map_param_ident, check_result));
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, arg:
|
||||
&& is_integer_literal(rhs, 1)
|
||||
|
||||
// check that recv == lhs_recv `recv.get(lhs_recv.len() - 1)`
|
||||
&& SpanlessEq::new(cx).eq_expr(recv, lhs_recv)
|
||||
&& SpanlessEq::new(cx).eq_expr(expr.span.ctxt(), recv, lhs_recv)
|
||||
&& !recv.can_have_side_effects()
|
||||
{
|
||||
let method = match cx.typeck_results().expr_ty_adjusted(recv).peel_refs().kind() {
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::res::MaybeDef;
|
||||
use clippy_utils::{is_integer_literal, sym};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, LangItem};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::Span;
|
||||
|
||||
use super::MANUAL_CLEAR;
|
||||
|
||||
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, arg: &Expr<'_>, method_span: Span) {
|
||||
let ty = cx.typeck_results().expr_ty_adjusted(recv);
|
||||
let ty = ty.peel_refs();
|
||||
|
||||
let diag_name = ty.ty_adt_def().and_then(|def| cx.tcx.get_diagnostic_name(def.did()));
|
||||
|
||||
if (matches!(diag_name, Some(sym::Vec | sym::VecDeque | sym::OsString)) || ty.is_lang_item(cx, LangItem::String))
|
||||
&& is_integer_literal(arg, 0)
|
||||
{
|
||||
span_lint_and_then(cx, MANUAL_CLEAR, expr.span, "truncating to zero length", |diag| {
|
||||
// Keep the receiver as-is and only rewrite the method.
|
||||
diag.span_suggestion_verbose(
|
||||
method_span.with_hi(expr.span.hi()),
|
||||
"use `clear()` instead",
|
||||
"clear()",
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -256,7 +256,7 @@ pub(super) fn check_or<'tcx>(
|
||||
.expr_ty_adjusted(some_recv)
|
||||
.peel_refs()
|
||||
.is_diag_item(cx, sym::Option)
|
||||
&& SpanlessEq::new(cx).eq_expr(none_recv, some_recv)
|
||||
&& SpanlessEq::new(cx).eq_expr(expr.span.ctxt(), none_recv, some_recv)
|
||||
{
|
||||
(some_recv, some_arg)
|
||||
} else {
|
||||
|
||||
@@ -3,12 +3,11 @@
|
||||
use clippy_utils::peel_blocks;
|
||||
use clippy_utils::res::{MaybeDef, MaybeResPath};
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::visitors::for_each_expr_without_closures;
|
||||
use clippy_utils::usage::local_used_in;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{self as hir, Expr, ExprKind, HirId, PatKind};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::symbol::sym;
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
use super::MANUAL_OPTION_ZIP;
|
||||
|
||||
@@ -31,13 +30,7 @@ pub(super) fn check<'tcx>(
|
||||
&& method_path.ident.name == sym::map
|
||||
&& cx.typeck_results().expr_ty(map_recv).is_diag_item(cx, sym::Option)
|
||||
// `b` does not reference the outer closure parameter `a`.
|
||||
&& for_each_expr_without_closures(map_recv, |e| {
|
||||
if e.res_local_id() == Some(outer_param_id) {
|
||||
ControlFlow::Break(())
|
||||
} else {
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
}).is_none()
|
||||
&& !local_used_in(cx, outer_param_id, map_recv)
|
||||
// `|b| (a, b)`
|
||||
&& let ExprKind::Closure(&hir::Closure { body: inner_body_id, .. }) = map_arg.kind
|
||||
&& let hir::Body { params: [inner_param], value: inner_value, .. } = cx.tcx.hir_body(inner_body_id)
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
mod lib;
|
||||
mod lines_filter_map_ok;
|
||||
mod manual_c_str_literals;
|
||||
mod manual_clear;
|
||||
mod manual_contains;
|
||||
mod manual_inspect;
|
||||
mod manual_is_variant_and;
|
||||
@@ -111,6 +112,7 @@
|
||||
mod single_char_add_str;
|
||||
mod skip_while_next;
|
||||
mod sliced_string_as_bytes;
|
||||
mod some_filter;
|
||||
mod stable_sort_primitive;
|
||||
mod str_split;
|
||||
mod str_splitn;
|
||||
@@ -1769,6 +1771,31 @@
|
||||
r#"creating a `CStr` through functions when `c""` literals can be used"#
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for `.truncate(0)` calls on standard library types where it can be replaced with `.clear()`.
|
||||
///
|
||||
/// Currently this includes `Vec`, `VecDeque`, `String`, and `OsString`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// `clear()` expresses the intent better and is likely to be more efficient than `truncate(0)`.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// let mut v = vec![1, 2, 3];
|
||||
/// v.truncate(0);
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// let mut v = vec![1, 2, 3];
|
||||
/// v.clear();
|
||||
/// ```
|
||||
#[clippy::version = "1.97.0"]
|
||||
pub MANUAL_CLEAR,
|
||||
perf,
|
||||
"using `truncate(0)` instead of `clear()`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for usage of `iter().any()` on slices when it can be replaced with `contains()` and suggests doing so.
|
||||
@@ -3577,6 +3604,29 @@
|
||||
"slicing a string and immediately calling as_bytes is less efficient and can lead to panics"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for usage of `Some(x).filter(|_| predicate)`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Readability, this can be written more concisely as `predicate.then_some(x)`.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// let x = false;
|
||||
/// Some(0).filter(|_| x);
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// let x = false;
|
||||
/// x.then_some(0);
|
||||
/// ```
|
||||
#[clippy::version = "1.97.0"]
|
||||
pub SOME_FILTER,
|
||||
complexity,
|
||||
"using `Some(x).filter(|_| predicate)`, which is more succinctly expressed as `predicate.then(x)`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// When sorting primitive values (integers, bools, chars, as well
|
||||
@@ -4839,6 +4889,7 @@
|
||||
ITER_WITH_DRAIN,
|
||||
JOIN_ABSOLUTE_PATHS,
|
||||
LINES_FILTER_MAP_OK,
|
||||
MANUAL_CLEAR,
|
||||
MANUAL_CONTAINS,
|
||||
MANUAL_C_STR_LITERALS,
|
||||
MANUAL_FILTER_MAP,
|
||||
@@ -4900,6 +4951,7 @@
|
||||
SINGLE_CHAR_ADD_STR,
|
||||
SKIP_WHILE_NEXT,
|
||||
SLICED_STRING_AS_BYTES,
|
||||
SOME_FILTER,
|
||||
STABLE_SORT_PRIMITIVE,
|
||||
STRING_EXTEND_CHARS,
|
||||
STRING_LIT_CHARS_ANY,
|
||||
@@ -5307,6 +5359,7 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
// use the sourcemap to get the span of the closure
|
||||
iter_filter::check(cx, expr, arg, span);
|
||||
}
|
||||
some_filter::check(cx, expr, recv, arg, self.msrv);
|
||||
},
|
||||
(sym::find, [arg]) => {
|
||||
if let Some((sym::cloned, recv2, [], _span2, _)) = method_call(recv) {
|
||||
@@ -5808,6 +5861,9 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
(sym::to_string, []) => {
|
||||
inefficient_to_string::check(cx, expr, recv, self.msrv);
|
||||
},
|
||||
(sym::truncate, [arg]) => {
|
||||
manual_clear::check(cx, expr, recv, arg, method_span);
|
||||
},
|
||||
(sym::unwrap, []) => {
|
||||
unwrap_expect_used::check(
|
||||
cx,
|
||||
|
||||
@@ -28,7 +28,7 @@ pub(super) fn check<'tcx>(
|
||||
return;
|
||||
}
|
||||
|
||||
if SpanlessEq::new(cx).eq_expr(arg1, arg2) {
|
||||
if SpanlessEq::new(cx).eq_expr(expr.span.ctxt(), arg1, arg2) {
|
||||
span_lint(cx, NO_EFFECT_REPLACE, expr.span, "replacing text with itself");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, recv: &'
|
||||
// `.iter()` and `.len()` called on same `Path`
|
||||
&& let ExprKind::Path(QPath::Resolved(_, iter_path)) = recv.kind
|
||||
&& let ExprKind::Path(QPath::Resolved(_, len_path)) = len_recv.kind
|
||||
&& SpanlessEq::new(cx).eq_path_segments(iter_path.segments, len_path.segments)
|
||||
&& SpanlessEq::new(cx).eq_path_segments(expr.span.ctxt(), iter_path.segments, len_path.segments)
|
||||
{
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
|
||||
use clippy_utils::{as_some_expr, pat_is_wild, peel_blocks, span_contains_comment};
|
||||
use rustc_ast::util::parser::ExprPrecedence;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Body, Expr, ExprKind};
|
||||
use rustc_lint::LateContext;
|
||||
|
||||
use super::SOME_FILTER;
|
||||
|
||||
pub(super) fn check<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &'tcx Expr<'tcx>,
|
||||
recv: &'tcx Expr<'tcx>,
|
||||
arg: &'tcx Expr<'tcx>,
|
||||
msrv: Msrv,
|
||||
) {
|
||||
let (condition, value) = if let Some(value) = as_some_expr(cx, recv)
|
||||
&& let ExprKind::Closure(c) = arg.kind
|
||||
&& let Body {
|
||||
params: [p],
|
||||
value: condition,
|
||||
} = cx.tcx.hir_body(c.body)
|
||||
&& pat_is_wild(cx, &p.pat.kind, arg)
|
||||
&& msrv.meets(cx, msrvs::BOOL_THEN_SOME)
|
||||
{
|
||||
(condition, value)
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
SOME_FILTER,
|
||||
expr.span,
|
||||
"use of `Some(x).filter(|_| predicate)`",
|
||||
|diag| {
|
||||
let condition = if span_contains_comment(cx, condition.span) {
|
||||
condition
|
||||
} else {
|
||||
peel_blocks(condition)
|
||||
};
|
||||
let mut applicability = Applicability::MaybeIncorrect;
|
||||
let (condition_text, condition_is_macro) =
|
||||
snippet_with_context(cx, condition.span, arg.span.ctxt(), "_", &mut applicability);
|
||||
let parentheses = !condition_is_macro && cx.precedence(condition) < ExprPrecedence::Unambiguous;
|
||||
let value_text = snippet_with_applicability(cx, value.span, "_", &mut applicability);
|
||||
let sugg = format!(
|
||||
"{}{condition_text}{}.then_some({value_text})",
|
||||
if parentheses { "(" } else { "" },
|
||||
if parentheses { ")" } else { "" },
|
||||
);
|
||||
diag.span_suggestion_verbose(
|
||||
expr.span,
|
||||
"consider using `bool::then_some` instead",
|
||||
sugg,
|
||||
applicability,
|
||||
);
|
||||
diag.note(
|
||||
"this change will alter the order in which the condition and \
|
||||
the value are evaluated",
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -66,7 +66,7 @@ fn is_caller_or_fields_change(cx: &LateContext<'_>, body: &Expr<'_>, caller: &Ex
|
||||
for_each_expr_without_closures(block, |e| {
|
||||
match e.kind {
|
||||
ExprKind::Assign(assignee, _, _) | ExprKind::AssignOp(_, assignee, _) => {
|
||||
change |= !can_mut_borrow_both(cx, caller, assignee);
|
||||
change |= !can_mut_borrow_both(cx, body.span.ctxt(), caller, assignee);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
|
||||
@@ -238,7 +238,9 @@ fn used_underscore_binding<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
/// of what it means for an expression to be "used".
|
||||
fn is_used(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
get_parent_expr(cx, expr).is_none_or(|parent| match parent.kind {
|
||||
ExprKind::Assign(_, rhs, _) | ExprKind::AssignOp(_, _, rhs) => SpanlessEq::new(cx).eq_expr(rhs, expr),
|
||||
ExprKind::Assign(_, rhs, _) | ExprKind::AssignOp(_, _, rhs) => {
|
||||
SpanlessEq::new(cx).eq_expr(parent.span.ctxt(), rhs, expr)
|
||||
},
|
||||
_ => is_used(cx, parent),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -245,7 +245,10 @@ fn check_index<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>, map: &mut Uni
|
||||
let hash = hash_expr(cx, slice);
|
||||
|
||||
let indexes = map.entry(hash).or_default();
|
||||
let entry = indexes.iter_mut().find(|entry| eq_expr_value(cx, entry.slice(), slice));
|
||||
let ctxt = expr.span.ctxt();
|
||||
let entry = indexes
|
||||
.iter_mut()
|
||||
.find(|entry| eq_expr_value(cx, ctxt, entry.slice(), slice));
|
||||
|
||||
if let Some(entry) = entry {
|
||||
match entry {
|
||||
@@ -305,7 +308,10 @@ fn check_assert<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>, map: &mut Un
|
||||
let hash = hash_expr(cx, slice);
|
||||
let indexes = map.entry(hash).or_default();
|
||||
|
||||
let entry = indexes.iter_mut().find(|entry| eq_expr_value(cx, entry.slice(), slice));
|
||||
let ctxt = expr.span.ctxt();
|
||||
let entry = indexes
|
||||
.iter_mut()
|
||||
.find(|entry| eq_expr_value(cx, ctxt, entry.slice(), slice));
|
||||
|
||||
if let Some(entry) = entry {
|
||||
if let IndexEntry::IndexWithoutAssert {
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::SyntaxContext;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
@@ -169,7 +170,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
|
||||
}
|
||||
if let Some((lhs_a, a)) = fetch_assign(then)
|
||||
&& let Some((lhs_b, b)) = fetch_assign(else_expr)
|
||||
&& SpanlessEq::new(cx).eq_expr(lhs_a, lhs_b)
|
||||
&& SpanlessEq::new(cx).eq_expr(SyntaxContext::root(), lhs_a, lhs_b)
|
||||
{
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let cond = Sugg::hir_with_context(cx, cond, e.span.ctxt(), "..", &mut applicability);
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use super::implicit_return::IMPLICIT_RETURN;
|
||||
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
|
||||
use clippy_utils::res::{MaybeDef, MaybeQPath};
|
||||
use clippy_utils::ty::implements_trait;
|
||||
use clippy_utils::{is_from_proc_macro, last_path_segment, std_or_core};
|
||||
use clippy_utils::{is_from_proc_macro, is_lint_allowed, last_path_segment, std_or_core};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_hir::{Block, Body, Expr, ExprKind, ImplItem, ImplItemKind, Item, ItemKind, LangItem, UnOp};
|
||||
use rustc_hir::{Block, Body, Expr, ExprKind, ImplItem, ImplItemKind, Item, ItemKind, LangItem, Stmt, StmtKind, UnOp};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::ty::{TyCtxt, TypeckResults};
|
||||
use rustc_session::impl_lint_pass;
|
||||
@@ -185,8 +186,8 @@ fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
|
||||
if let Some(copy_trait) = self.copy_trait
|
||||
&& implements_trait(cx, trait_impl.self_ty(), copy_trait, &[])
|
||||
{
|
||||
for (assoc, _, block) in assoc_fns {
|
||||
check_clone_on_copy(cx, assoc, block);
|
||||
for (assoc, body, _) in assoc_fns {
|
||||
check_clone_on_copy(cx, assoc, body.value);
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -208,29 +209,34 @@ fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
|
||||
}
|
||||
}
|
||||
|
||||
fn check_clone_on_copy(cx: &LateContext<'_>, impl_item: &ImplItem<'_>, block: &Block<'_>) {
|
||||
if impl_item.ident.name == sym::clone {
|
||||
if block.stmts.is_empty()
|
||||
&& let Some(expr) = block.expr
|
||||
&& let ExprKind::Unary(UnOp::Deref, deref) = expr.kind
|
||||
&& let ExprKind::Path(qpath) = deref.kind
|
||||
&& last_path_segment(&qpath).ident.name == kw::SelfLower
|
||||
{
|
||||
// this is the canonical implementation, `fn clone(&self) -> Self { *self }`
|
||||
return;
|
||||
}
|
||||
fn is_deref_self(expr: &Expr<'_>) -> bool {
|
||||
if let ExprKind::Unary(UnOp::Deref, deref) = expr.kind
|
||||
&& let ExprKind::Path(qpath) = deref.kind
|
||||
&& last_path_segment(&qpath).ident.name == kw::SelfLower
|
||||
{
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn check_clone_on_copy(cx: &LateContext<'_>, impl_item: &ImplItem<'_>, body_expr: &Expr<'_>) {
|
||||
if impl_item.ident.name == sym::clone {
|
||||
if is_from_proc_macro(cx, impl_item) {
|
||||
return;
|
||||
}
|
||||
|
||||
let add_return = match is_canonical_clone_body(body_expr) {
|
||||
IsCanonical::WithReturn if is_lint_allowed(cx, IMPLICIT_RETURN, body_expr.hir_id) => false,
|
||||
IsCanonical::WithReturn | IsCanonical::WithoutReturn => return,
|
||||
IsCanonical::No => !is_lint_allowed(cx, IMPLICIT_RETURN, body_expr.hir_id),
|
||||
};
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
NON_CANONICAL_CLONE_IMPL,
|
||||
block.span,
|
||||
body_expr.span,
|
||||
"non-canonical implementation of `clone` on a `Copy` type",
|
||||
"change this to",
|
||||
"{ *self }".to_owned(),
|
||||
if add_return { "{ return *self; }" } else { "{ *self }" }.to_owned(),
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
@@ -248,6 +254,40 @@ fn check_clone_on_copy(cx: &LateContext<'_>, impl_item: &ImplItem<'_>, block: &B
|
||||
}
|
||||
}
|
||||
|
||||
enum IsCanonical {
|
||||
WithoutReturn,
|
||||
WithReturn,
|
||||
No,
|
||||
}
|
||||
|
||||
fn is_canonical_clone_body(body_expr: &Expr<'_>) -> IsCanonical {
|
||||
let ExprKind::Block(block, ..) = body_expr.kind else {
|
||||
return IsCanonical::No;
|
||||
};
|
||||
let single_expr = match (block.stmts, block.expr) {
|
||||
([], Some(expr)) => Some(expr),
|
||||
(
|
||||
[
|
||||
Stmt {
|
||||
kind: StmtKind::Expr(expr) | StmtKind::Semi(expr),
|
||||
..
|
||||
},
|
||||
],
|
||||
None,
|
||||
) => Some(*expr),
|
||||
_ => None,
|
||||
};
|
||||
let Some(expr) = single_expr else {
|
||||
return IsCanonical::No;
|
||||
};
|
||||
|
||||
match expr.kind {
|
||||
ExprKind::Ret(Some(ret)) if is_deref_self(ret) => IsCanonical::WithReturn,
|
||||
_ if is_deref_self(expr) => IsCanonical::WithoutReturn,
|
||||
_ => IsCanonical::No,
|
||||
}
|
||||
}
|
||||
|
||||
fn check_partial_ord_on_ord<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
impl_item: &ImplItem<'_>,
|
||||
@@ -269,7 +309,7 @@ fn check_partial_ord_on_ord<'tcx>(
|
||||
// Fix #12683, allow [`needless_return`] here
|
||||
else if block.expr.is_none()
|
||||
&& let Some(stmt) = block.stmts.first()
|
||||
&& let rustc_hir::StmtKind::Semi(Expr {
|
||||
&& let StmtKind::Semi(Expr {
|
||||
kind: ExprKind::Ret(Some(ret)),
|
||||
..
|
||||
}) = stmt.kind
|
||||
|
||||
@@ -89,9 +89,10 @@ pub(super) fn check<'tcx>(
|
||||
}
|
||||
};
|
||||
|
||||
let ctxt = expr.span.ctxt();
|
||||
let mut found = false;
|
||||
let found_multiple = for_each_expr_without_closures(e, |e| {
|
||||
if eq_expr_value(cx, assignee, e) {
|
||||
if eq_expr_value(cx, ctxt, assignee, e) {
|
||||
if found {
|
||||
return ControlFlow::Break(());
|
||||
}
|
||||
@@ -103,12 +104,12 @@ pub(super) fn check<'tcx>(
|
||||
|
||||
if found && !found_multiple {
|
||||
// a = a op b
|
||||
if eq_expr_value(cx, assignee, l) {
|
||||
if eq_expr_value(cx, ctxt, assignee, l) {
|
||||
lint(assignee, r);
|
||||
}
|
||||
// a = b commutative_op a
|
||||
// Limited to primitive type as these ops are know to be commutative
|
||||
if eq_expr_value(cx, assignee, r) && cx.typeck_results().expr_ty(assignee).is_primitive_ty() {
|
||||
if eq_expr_value(cx, ctxt, assignee, r) && cx.typeck_results().expr_ty(assignee).is_primitive_ty() {
|
||||
match op.node {
|
||||
hir::BinOpKind::Add
|
||||
| hir::BinOpKind::Mul
|
||||
|
||||
@@ -63,7 +63,7 @@ pub(super) fn check<'tcx>(
|
||||
&& left_type == right_type
|
||||
|
||||
// Check that the same expression is compared in both comparisons
|
||||
&& SpanlessEq::new(cx).eq_expr(left_expr, right_expr)
|
||||
&& SpanlessEq::new(cx).eq_expr(span.ctxt(), left_expr, right_expr)
|
||||
|
||||
&& !left_expr.can_have_side_effects()
|
||||
|
||||
|
||||
@@ -11,8 +11,9 @@
|
||||
pub(super) fn check(cx: &LateContext<'_>, op: BinOpKind, lhs: &Expr<'_>, rhs: &Expr<'_>, span: Span) {
|
||||
if let ExprKind::Binary(lop, llhs, lrhs) = lhs.kind
|
||||
&& let ExprKind::Binary(rop, rlhs, rrhs) = rhs.kind
|
||||
&& eq_expr_value(cx, llhs, rlhs)
|
||||
&& eq_expr_value(cx, lrhs, rrhs)
|
||||
&& let ctxt = span.ctxt()
|
||||
&& eq_expr_value(cx, ctxt, llhs, rlhs)
|
||||
&& eq_expr_value(cx, ctxt, lrhs, rrhs)
|
||||
{
|
||||
let op = match (op, lop.node, rop.node) {
|
||||
// x == y || x < y => x <= y
|
||||
|
||||
@@ -14,7 +14,7 @@ pub(crate) fn check_assert<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
|
||||
Some(sym::assert_eq_macro | sym::assert_ne_macro | sym::debug_assert_eq_macro | sym::debug_assert_ne_macro)
|
||||
)
|
||||
}) && let Some((lhs, rhs, _)) = find_assert_eq_args(cx, e, macro_call.expn)
|
||||
&& eq_expr_value(cx, lhs, rhs)
|
||||
&& eq_expr_value(cx, macro_call.span.ctxt(), lhs, rhs)
|
||||
&& macro_call.is_local()
|
||||
&& !is_in_test_function(cx.tcx, e.hir_id)
|
||||
{
|
||||
@@ -37,7 +37,10 @@ pub(crate) fn check<'tcx>(
|
||||
left: &'tcx Expr<'_>,
|
||||
right: &'tcx Expr<'_>,
|
||||
) {
|
||||
if is_useless_with_eq_exprs(op) && eq_expr_value(cx, left, right) && !is_in_test_function(cx.tcx, e.hir_id) {
|
||||
if is_useless_with_eq_exprs(op)
|
||||
&& eq_expr_value(cx, e.span.ctxt(), left, right)
|
||||
&& !is_in_test_function(cx.tcx, e.hir_id)
|
||||
{
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
EQ_OP,
|
||||
|
||||
@@ -21,6 +21,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, op: BinOpKind, lhs: &
|
||||
&& check_int_ty_and_feature(cx, cx.typeck_results().expr_ty(rhs))
|
||||
&& msrv.meets(cx, msrvs::DIV_CEIL)
|
||||
{
|
||||
let ctxt = expr.span.ctxt();
|
||||
match lhs.kind {
|
||||
ExprKind::Binary(inner_op, inner_lhs, inner_rhs) => {
|
||||
// (x + (y - 1)) / y
|
||||
@@ -28,7 +29,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, op: BinOpKind, lhs: &
|
||||
&& inner_op.node == BinOpKind::Add
|
||||
&& sub_op.node == BinOpKind::Sub
|
||||
&& check_literal(sub_rhs)
|
||||
&& check_eq_expr(cx, sub_lhs, rhs)
|
||||
&& SpanlessEq::new(cx).eq_expr(ctxt, sub_lhs, rhs)
|
||||
{
|
||||
build_suggestion(cx, expr, inner_lhs, rhs, &mut applicability);
|
||||
return;
|
||||
@@ -39,7 +40,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, op: BinOpKind, lhs: &
|
||||
&& inner_op.node == BinOpKind::Add
|
||||
&& sub_op.node == BinOpKind::Sub
|
||||
&& check_literal(sub_rhs)
|
||||
&& check_eq_expr(cx, sub_lhs, rhs)
|
||||
&& SpanlessEq::new(cx).eq_expr(ctxt, sub_lhs, rhs)
|
||||
{
|
||||
build_suggestion(cx, expr, inner_rhs, rhs, &mut applicability);
|
||||
return;
|
||||
@@ -50,7 +51,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, op: BinOpKind, lhs: &
|
||||
&& inner_op.node == BinOpKind::Sub
|
||||
&& add_op.node == BinOpKind::Add
|
||||
&& check_literal(inner_rhs)
|
||||
&& check_eq_expr(cx, add_rhs, rhs)
|
||||
&& SpanlessEq::new(cx).eq_expr(ctxt, add_rhs, rhs)
|
||||
{
|
||||
build_suggestion(cx, expr, add_lhs, rhs, &mut applicability);
|
||||
}
|
||||
@@ -76,7 +77,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, op: BinOpKind, lhs: &
|
||||
ExprKind::MethodCall(method, receiver, [next_multiple_of_arg], _)
|
||||
if method.ident.name == sym::next_multiple_of
|
||||
&& check_int_ty(cx.typeck_results().expr_ty(receiver))
|
||||
&& check_eq_expr(cx, next_multiple_of_arg, rhs) =>
|
||||
&& SpanlessEq::new(cx).eq_expr(ctxt, next_multiple_of_arg, rhs) =>
|
||||
{
|
||||
// x.next_multiple_of(Y) / Y
|
||||
build_suggestion(cx, expr, receiver, rhs, &mut applicability);
|
||||
@@ -88,7 +89,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, op: BinOpKind, lhs: &
|
||||
.assoc_fn_parent(cx)
|
||||
.opt_impl_ty(cx)
|
||||
&& check_int_ty(impl_ty_binder.skip_binder())
|
||||
&& check_eq_expr(cx, next_multiple_of_arg, rhs)
|
||||
&& SpanlessEq::new(cx).eq_expr(ctxt, next_multiple_of_arg, rhs)
|
||||
{
|
||||
build_suggestion(cx, expr, receiver, rhs, &mut applicability);
|
||||
}
|
||||
@@ -137,10 +138,6 @@ fn check_literal(expr: &Expr<'_>) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn check_eq_expr(cx: &LateContext<'_>, lhs: &Expr<'_>, rhs: &Expr<'_>) -> bool {
|
||||
SpanlessEq::new(cx).eq_expr(lhs, rhs)
|
||||
}
|
||||
|
||||
fn build_suggestion(
|
||||
cx: &LateContext<'_>,
|
||||
expr: &Expr<'_>,
|
||||
|
||||
@@ -19,9 +19,10 @@ pub(super) fn check<'tcx>(
|
||||
return;
|
||||
}
|
||||
// lhs op= l op r
|
||||
if eq_expr_value(cx, lhs, l) {
|
||||
let ctxt = expr.span.ctxt();
|
||||
if eq_expr_value(cx, ctxt, lhs, l) {
|
||||
lint_misrefactored_assign_op(cx, expr, op, rhs, lhs, r);
|
||||
} else if is_commutative(op) && eq_expr_value(cx, lhs, r) {
|
||||
} else if is_commutative(op) && eq_expr_value(cx, ctxt, lhs, r) {
|
||||
// lhs op= l commutative_op r
|
||||
lint_misrefactored_assign_op(cx, expr, op, rhs, lhs, l);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
use super::SELF_ASSIGNMENT;
|
||||
|
||||
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, lhs: &'tcx Expr<'_>, rhs: &'tcx Expr<'_>) {
|
||||
if eq_expr_value(cx, lhs, rhs) {
|
||||
if eq_expr_value(cx, e.span.ctxt(), lhs, rhs) {
|
||||
let lhs = snippet(cx, lhs.span, "<lhs>");
|
||||
let rhs = snippet(cx, rhs.span, "<rhs>");
|
||||
span_lint(
|
||||
|
||||
@@ -72,7 +72,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
&& ty == typeck.expr_ty(op_rhs)
|
||||
&& ty == typeck.expr_ty(other)
|
||||
&& !expr.span.in_external_macro(cx.tcx.sess.source_map())
|
||||
&& (eq_expr_value(cx, op_lhs, other) || (commutative && eq_expr_value(cx, op_rhs, other)))
|
||||
&& (eq_expr_value(cx, ctxt, op_lhs, other) || (commutative && eq_expr_value(cx, ctxt, op_rhs, other)))
|
||||
{
|
||||
span_lint(
|
||||
cx,
|
||||
|
||||
@@ -299,7 +299,7 @@ fn check_is_none_or_err_and_early_return<'tcx>(cx: &LateContext<'tcx>, expr: &Ex
|
||||
let by_ref = !cx.type_is_copy_modulo_regions(caller_ty)
|
||||
&& !matches!(caller.kind, ExprKind::Call(..) | ExprKind::MethodCall(..));
|
||||
let sugg = if let Some(else_inner) = r#else {
|
||||
if eq_expr_value(cx, caller, peel_blocks(else_inner)) {
|
||||
if eq_expr_value(cx, expr.span.ctxt(), caller, peel_blocks(else_inner)) {
|
||||
format!("Some({receiver_str}?)")
|
||||
} else {
|
||||
return;
|
||||
@@ -537,7 +537,7 @@ fn check_if_let_some_or_err_and_early_return<'tcx>(cx: &LateContext<'tcx>, expr:
|
||||
&& let is_option_early_return = is_early_return(sym::Option, cx, &if_block)
|
||||
&& (is_option_early_return || is_early_return(sym::Result, cx, &if_block))
|
||||
&& if_else
|
||||
.map(|e| eq_expr_value(cx, let_expr, peel_blocks(e)))
|
||||
.map(|e| eq_expr_value(cx, expr.span.ctxt(), let_expr, peel_blocks(e)))
|
||||
.is_none_or(|e| !e)
|
||||
{
|
||||
if !is_copy(cx, caller_ty)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
use clippy_utils::{is_from_proc_macro, is_inside_let_else};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::LangItem::ResultErr;
|
||||
use rustc_hir::{ExprKind, HirId, ItemKind, MatchSource, Node, OwnerNode, Stmt, StmtKind};
|
||||
use rustc_hir::{Expr, ExprKind, HirId, MatchSource, Node, Stmt, StmtKind};
|
||||
use rustc_lint::{LateContext, LintContext};
|
||||
use rustc_middle::ty::adjustment::Adjust;
|
||||
|
||||
@@ -23,10 +23,8 @@ pub(super) fn check_stmt<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
|
||||
&& maybe_constr.res(cx).ctor_parent(cx).is_lang_item(cx, ResultErr)
|
||||
|
||||
// Ensure this is not the final stmt, otherwise removing it would cause a compile error
|
||||
&& let OwnerNode::Item(item) = cx.tcx.hir_owner_node(cx.tcx.hir_get_parent_item(expr.hir_id))
|
||||
&& let ItemKind::Fn { body, .. } = item.kind
|
||||
&& let block = cx.tcx.hir_body(body).value
|
||||
&& let ExprKind::Block(block, _) = block.kind
|
||||
&& let block = cx.tcx.hir_body_owned_by(cx.tcx.hir_enclosing_body_owner(expr.hir_id)).value
|
||||
&& let ExprKind::Block(block, _) = peel_async_body(block).kind
|
||||
&& !is_inside_let_else(cx.tcx, expr)
|
||||
&& let [.., final_stmt] = block.stmts
|
||||
&& final_stmt.hir_id != stmt.hir_id
|
||||
@@ -45,6 +43,21 @@ pub(super) fn check_stmt<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
|
||||
}
|
||||
}
|
||||
|
||||
/// In async functions, the body is wrapped in a
|
||||
/// `Block { expr: DropTemps(inner) }`. We peel through to `inner` so
|
||||
/// we can check the actual stmts.
|
||||
/// Returns `body_value` unchanged for non-async functions.
|
||||
fn peel_async_body<'a>(body_value: &'a Expr<'a>) -> &'a Expr<'a> {
|
||||
if let ExprKind::Block(block, _) = body_value.kind
|
||||
&& let Some(expr) = block.expr
|
||||
&& let ExprKind::DropTemps(inner) = expr.kind
|
||||
{
|
||||
inner
|
||||
} else {
|
||||
body_value
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if a return statement is "needed" in the middle of a block, or if it can be removed.
|
||||
/// This is the case when the enclosing block expression is coerced to some other type,
|
||||
/// which only works because of the never-ness of `return` expressions
|
||||
|
||||
@@ -79,7 +79,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
&& let ExprKind::Path(QPath::TypeRelative(ty, fn_path)) = path_expr.kind
|
||||
&& fn_path.ident.name == sym::from_raw_parts
|
||||
&& args.len() >= 3
|
||||
&& eq_expr_value(cx, &args[1], &args[2])
|
||||
&& eq_expr_value(cx, expr.span.ctxt(), &args[1], &args[2])
|
||||
{
|
||||
let middle_ty = cx.typeck_results().node_type(ty.hir_id);
|
||||
if middle_ty.is_diag_item(cx, rustc_sym::Vec) {
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
use rustc_hir::{Expr, ExprKind, UnOp};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::Span;
|
||||
use rustc_span::symbol::Symbol;
|
||||
use rustc_span::{Span, SyntaxContext};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
@@ -119,7 +119,7 @@ fn is_set_mutated<'tcx>(cx: &LateContext<'tcx>, contains_expr: &OpExpr<'tcx>, ex
|
||||
cx.typeck_results().expr_ty(expr).peel_refs().opt_diag_name(cx),
|
||||
Some(sym::HashSet | sym::BTreeSet)
|
||||
)
|
||||
&& SpanlessEq::new(cx).eq_expr(contains_expr.receiver, expr.peel_borrows())
|
||||
&& SpanlessEq::new(cx).eq_expr(SyntaxContext::root(), contains_expr.receiver, expr.peel_borrows())
|
||||
}
|
||||
|
||||
fn find_insert_calls<'tcx>(
|
||||
@@ -129,8 +129,8 @@ fn find_insert_calls<'tcx>(
|
||||
) -> Option<OpExpr<'tcx>> {
|
||||
for_each_expr(cx, expr, |e| {
|
||||
if let Some((insert_expr, _)) = try_parse_op_call(cx, e, sym::insert)
|
||||
&& SpanlessEq::new(cx).eq_expr(contains_expr.receiver, insert_expr.receiver)
|
||||
&& SpanlessEq::new(cx).eq_expr(contains_expr.value, insert_expr.value)
|
||||
&& SpanlessEq::new(cx).eq_expr(SyntaxContext::root(), contains_expr.receiver, insert_expr.receiver)
|
||||
&& SpanlessEq::new(cx).eq_expr(SyntaxContext::root(), contains_expr.value, insert_expr.value)
|
||||
{
|
||||
return ControlFlow::Break(Some(insert_expr));
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
use rustc_hir::{BindingMode, Block, Expr, ExprKind, HirId, PatKind, Stmt, StmtKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::SyntaxContext;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
@@ -265,7 +266,7 @@ fn search_slow_resize_filling(&mut self, expr: &'tcx Expr<'tcx>) {
|
||||
{
|
||||
let is_matching_resize = if let InitializedSize::Initialized(size_expr) = self.vec_alloc.size_expr {
|
||||
// If we have a size expression, check that it is equal to what's passed to `resize`
|
||||
SpanlessEq::new(self.cx).eq_expr(len_arg, size_expr)
|
||||
SpanlessEq::new(self.cx).eq_expr(SyntaxContext::root(), len_arg, size_expr)
|
||||
|| matches!(len_arg.kind, ExprKind::MethodCall(path, ..) if path.ident.name == sym::capacity)
|
||||
} else {
|
||||
self.vec_alloc.size_expr = InitializedSize::Initialized(len_arg);
|
||||
@@ -287,7 +288,7 @@ fn is_repeat_take(&mut self, expr: &'tcx Expr<'tcx>) -> bool {
|
||||
{
|
||||
if let InitializedSize::Initialized(size_expr) = self.vec_alloc.size_expr {
|
||||
// Check that len expression is equals to `with_capacity` expression
|
||||
return SpanlessEq::new(self.cx).eq_expr(len_arg, size_expr)
|
||||
return SpanlessEq::new(self.cx).eq_expr(SyntaxContext::root(), len_arg, size_expr)
|
||||
|| matches!(len_arg.kind, ExprKind::MethodCall(path, ..) if path.ident.name == sym::capacity);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::ty;
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::Spanned;
|
||||
use rustc_span::{Spanned, SyntaxContext};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
@@ -220,7 +220,8 @@
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for StringAdd {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
|
||||
if e.span.in_external_macro(cx.sess().source_map()) {
|
||||
let ctxt = e.span.ctxt();
|
||||
if ctxt.in_external_macro(cx.sess().source_map()) {
|
||||
return;
|
||||
}
|
||||
match e.kind {
|
||||
@@ -235,8 +236,8 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
|
||||
let parent = get_parent_expr(cx, e);
|
||||
if let Some(p) = parent
|
||||
&& let ExprKind::Assign(target, _, _) = p.kind
|
||||
// avoid duplicate matches
|
||||
&& SpanlessEq::new(cx).eq_expr(target, left)
|
||||
// avoid duplicate matches
|
||||
&& SpanlessEq::new(cx).eq_expr(ctxt, target, left)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -248,7 +249,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
|
||||
"you added something to a string. Consider using `String::push_str()` instead",
|
||||
);
|
||||
},
|
||||
ExprKind::Assign(target, src, _) if is_string(cx, target) && is_add(cx, src, target) => {
|
||||
ExprKind::Assign(target, src, _) if is_string(cx, target) && is_add(cx, ctxt, src, target) => {
|
||||
span_lint(
|
||||
cx,
|
||||
STRING_ADD_ASSIGN,
|
||||
@@ -280,7 +281,7 @@ fn is_string(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
|
||||
.is_lang_item(cx, LangItem::String)
|
||||
}
|
||||
|
||||
fn is_add(cx: &LateContext<'_>, src: &Expr<'_>, target: &Expr<'_>) -> bool {
|
||||
fn is_add(cx: &LateContext<'_>, ctxt: SyntaxContext, src: &Expr<'_>, target: &Expr<'_>) -> bool {
|
||||
match peel_blocks(src).kind {
|
||||
ExprKind::Binary(
|
||||
Spanned {
|
||||
@@ -288,7 +289,7 @@ fn is_add(cx: &LateContext<'_>, src: &Expr<'_>, target: &Expr<'_>) -> bool {
|
||||
},
|
||||
left,
|
||||
_,
|
||||
) => SpanlessEq::new(cx).eq_expr(target, left),
|
||||
) => SpanlessEq::new(cx).eq_expr(ctxt, target, left),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,12 +98,12 @@ fn generate_swap_warning<'tcx>(
|
||||
let ctxt = span.ctxt();
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
|
||||
if !can_mut_borrow_both(cx, e1, e2) {
|
||||
if !can_mut_borrow_both(cx, ctxt, e1, e2) {
|
||||
if let ExprKind::Index(lhs1, idx1, _) = e1.kind
|
||||
&& let ExprKind::Index(lhs2, idx2, _) = e2.kind
|
||||
&& eq_expr_value(cx, lhs1, lhs2)
|
||||
&& e1.span.ctxt() == ctxt
|
||||
&& e2.span.ctxt() == ctxt
|
||||
&& eq_expr_value(cx, ctxt, lhs1, lhs2)
|
||||
{
|
||||
let ty = cx.typeck_results().expr_ty(lhs1).peel_refs();
|
||||
|
||||
@@ -189,14 +189,15 @@ fn check_manual_swap<'tcx>(cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) {
|
||||
&& rhs2_path.segments.len() == 1
|
||||
|
||||
&& ident.name == rhs2_path.segments[0].ident.name
|
||||
&& eq_expr_value(cx, tmp_init, lhs1)
|
||||
&& eq_expr_value(cx, rhs1, lhs2)
|
||||
|
||||
&& let ctxt = s1.span.ctxt()
|
||||
&& s2.span.ctxt() == ctxt
|
||||
&& s3.span.ctxt() == ctxt
|
||||
&& first.span.ctxt() == ctxt
|
||||
&& second.span.ctxt() == ctxt
|
||||
|
||||
&& eq_expr_value(cx, ctxt, tmp_init, lhs1)
|
||||
&& eq_expr_value(cx, ctxt, rhs1, lhs2)
|
||||
{
|
||||
let span = s1.span.to(s3.span);
|
||||
generate_swap_warning(block, cx, lhs1, lhs2, rhs1, rhs2, span, false);
|
||||
@@ -209,11 +210,12 @@ fn check_suspicious_swap(cx: &LateContext<'_>, block: &Block<'_>) {
|
||||
for [first, second] in block.stmts.array_windows() {
|
||||
if let Some((lhs0, rhs0)) = parse(first)
|
||||
&& let Some((lhs1, rhs1)) = parse(second)
|
||||
&& first.span.eq_ctxt(second.span)
|
||||
&& !first.span.in_external_macro(cx.sess().source_map())
|
||||
&& is_same(cx, lhs0, rhs1)
|
||||
&& is_same(cx, lhs1, rhs0)
|
||||
&& !is_same(cx, lhs1, rhs1) // Ignore a = b; a = a (#10421)
|
||||
&& let ctxt = first.span.ctxt()
|
||||
&& ctxt == second.span.ctxt()
|
||||
&& !ctxt.in_external_macro(cx.sess().source_map())
|
||||
&& is_same(cx, ctxt, lhs0, rhs1)
|
||||
&& is_same(cx, ctxt, lhs1, rhs0)
|
||||
&& !is_same(cx, ctxt, lhs1, rhs1) // Ignore a = b; a = a (#10421)
|
||||
&& let Some(lhs_sugg) = match &lhs0 {
|
||||
ExprOrIdent::Expr(expr) => Sugg::hir_opt(cx, expr),
|
||||
ExprOrIdent::Ident(ident) => Some(Sugg::NonParen(ident.as_str().into())),
|
||||
@@ -241,9 +243,9 @@ fn check_suspicious_swap(cx: &LateContext<'_>, block: &Block<'_>) {
|
||||
}
|
||||
}
|
||||
|
||||
fn is_same(cx: &LateContext<'_>, lhs: ExprOrIdent<'_>, rhs: &Expr<'_>) -> bool {
|
||||
fn is_same(cx: &LateContext<'_>, ctxt: SyntaxContext, lhs: ExprOrIdent<'_>, rhs: &Expr<'_>) -> bool {
|
||||
match lhs {
|
||||
ExprOrIdent::Expr(expr) => eq_expr_value(cx, expr, rhs),
|
||||
ExprOrIdent::Expr(expr) => eq_expr_value(cx, ctxt, expr, rhs),
|
||||
ExprOrIdent::Ident(ident) => {
|
||||
if let ExprKind::Path(QPath::Resolved(None, path)) = rhs.kind
|
||||
&& let [segment] = &path.segments
|
||||
@@ -284,10 +286,10 @@ fn check_xor_swap<'tcx>(cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) {
|
||||
if let Some((lhs0, rhs0)) = extract_sides_of_xor_assign(s1, ctxt)
|
||||
&& let Some((lhs1, rhs1)) = extract_sides_of_xor_assign(s2, ctxt)
|
||||
&& let Some((lhs2, rhs2)) = extract_sides_of_xor_assign(s3, ctxt)
|
||||
&& eq_expr_value(cx, lhs0, rhs1)
|
||||
&& eq_expr_value(cx, lhs2, rhs1)
|
||||
&& eq_expr_value(cx, lhs1, rhs0)
|
||||
&& eq_expr_value(cx, lhs1, rhs2)
|
||||
&& eq_expr_value(cx, ctxt, lhs0, rhs1)
|
||||
&& eq_expr_value(cx, ctxt, lhs2, rhs1)
|
||||
&& eq_expr_value(cx, ctxt, lhs1, rhs0)
|
||||
&& eq_expr_value(cx, ctxt, lhs1, rhs2)
|
||||
&& s2.span.ctxt() == ctxt
|
||||
&& s3.span.ctxt() == ctxt
|
||||
{
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::Span;
|
||||
use rustc_span::{Span, SyntaxContext};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
@@ -157,9 +157,11 @@ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'tc
|
||||
.filter_map(get_trait_info_from_bound)
|
||||
.for_each(|(trait_item_res, trait_item_segments, span)| {
|
||||
if let Some(self_segments) = self_bounds_map.get(&trait_item_res)
|
||||
&& SpanlessEq::new(cx)
|
||||
.paths_by_resolution()
|
||||
.eq_path_segments(self_segments, trait_item_segments)
|
||||
&& SpanlessEq::new(cx).paths_by_resolution().eq_path_segments(
|
||||
SyntaxContext::root(),
|
||||
self_segments,
|
||||
trait_item_segments,
|
||||
)
|
||||
{
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
@@ -252,7 +254,7 @@ struct SpanlessTy<'cx, 'tcx> {
|
||||
impl PartialEq for SpanlessTy<'_, '_> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
let mut eq = SpanlessEq::new(self.cx);
|
||||
eq.inter_expr().eq_ty(self.ty, other.ty)
|
||||
eq.inter_expr(SyntaxContext::root()).eq_ty(self.ty, other.ty)
|
||||
}
|
||||
}
|
||||
impl Hash for SpanlessTy<'_, '_> {
|
||||
@@ -382,9 +384,11 @@ struct ComparableTraitRef<'a, 'tcx> {
|
||||
impl PartialEq for ComparableTraitRef<'_, '_> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
SpanlessEq::eq_modifiers(self.modifiers, other.modifiers)
|
||||
&& SpanlessEq::new(self.cx)
|
||||
.paths_by_resolution()
|
||||
.eq_path(self.trait_ref.path, other.trait_ref.path)
|
||||
&& SpanlessEq::new(self.cx).paths_by_resolution().eq_path(
|
||||
SyntaxContext::root(),
|
||||
self.trait_ref.path,
|
||||
other.trait_ref.path,
|
||||
)
|
||||
}
|
||||
}
|
||||
impl Eq for ComparableTraitRef<'_, '_> {}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
use rustc_hir::{Expr, ExprKind, Node};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::Ty;
|
||||
use rustc_span::SyntaxContext;
|
||||
|
||||
use super::EAGER_TRANSMUTE;
|
||||
|
||||
@@ -54,9 +55,9 @@ fn binops_with_local(cx: &LateContext<'_>, local_expr: &Expr<'_>, expr: &Expr<'_
|
||||
lang_items.range_to_struct()
|
||||
].into_iter().any(|did| did == Some(receiver_adt.did())) =>
|
||||
{
|
||||
eq_expr_value(cx, local_expr, arg.peel_borrows())
|
||||
eq_expr_value(cx, SyntaxContext::root(), local_expr, arg.peel_borrows())
|
||||
},
|
||||
_ => eq_expr_value(cx, local_expr, expr),
|
||||
_ => eq_expr_value(cx, SyntaxContext::root(), local_expr, expr),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty;
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::Span;
|
||||
use rustc_span::{Span, SyntaxContext};
|
||||
|
||||
// TODO: add `ReadBuf` (RFC 2930) in "How to fix" once it is available in std
|
||||
declare_clippy_lint! {
|
||||
@@ -64,15 +64,23 @@
|
||||
// Threads: https://github.com/rust-lang/rust-clippy/pull/7682#discussion_r710998368
|
||||
impl<'tcx> LateLintPass<'tcx> for UninitVec {
|
||||
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) {
|
||||
if !block.span.in_external_macro(cx.tcx.sess.source_map()) {
|
||||
for w in block.stmts.windows(2) {
|
||||
if let StmtKind::Expr(expr) | StmtKind::Semi(expr) = w[1].kind {
|
||||
handle_uninit_vec_pair(cx, &w[0], expr);
|
||||
let ctxt = block.span.ctxt();
|
||||
if !ctxt.in_external_macro(cx.tcx.sess.source_map()) {
|
||||
for [stmt1, stmt2] in block.stmts.array_windows::<2>() {
|
||||
if let StmtKind::Expr(expr) | StmtKind::Semi(expr) = stmt2.kind
|
||||
&& stmt1.span.ctxt() == ctxt
|
||||
&& stmt2.span.ctxt() == ctxt
|
||||
&& expr.span.ctxt() == ctxt
|
||||
{
|
||||
handle_uninit_vec_pair(cx, ctxt, stmt1, expr);
|
||||
}
|
||||
}
|
||||
|
||||
if let (Some(stmt), Some(expr)) = (block.stmts.last(), block.expr) {
|
||||
handle_uninit_vec_pair(cx, stmt, expr);
|
||||
if let (Some(stmt), Some(expr)) = (block.stmts.last(), block.expr)
|
||||
&& stmt.span.ctxt() == ctxt
|
||||
&& expr.span.ctxt() == ctxt
|
||||
{
|
||||
handle_uninit_vec_pair(cx, ctxt, stmt, expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,12 +88,13 @@ fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) {
|
||||
|
||||
fn handle_uninit_vec_pair<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
ctxt: SyntaxContext,
|
||||
maybe_init_or_reserve: &'tcx Stmt<'tcx>,
|
||||
maybe_set_len: &'tcx Expr<'tcx>,
|
||||
) {
|
||||
if let Some(vec) = extract_init_or_reserve_target(cx, maybe_init_or_reserve)
|
||||
&& let Some((set_len_self, call_span)) = extract_set_len_self(cx, maybe_set_len)
|
||||
&& vec.location.eq_expr(cx, set_len_self)
|
||||
&& vec.location.eq_expr(cx, ctxt, set_len_self)
|
||||
&& let ty::Ref(_, vec_ty, _) = cx.typeck_results().expr_ty_adjusted(set_len_self).kind()
|
||||
&& let ty::Adt(_, args) = vec_ty.kind()
|
||||
// `#[allow(...)]` attribute can be set on enclosing unsafe block of `set_len()`
|
||||
@@ -138,10 +147,10 @@ enum VecLocation<'tcx> {
|
||||
}
|
||||
|
||||
impl<'tcx> VecLocation<'tcx> {
|
||||
pub fn eq_expr(self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
|
||||
pub fn eq_expr(self, cx: &LateContext<'tcx>, ctxt: SyntaxContext, expr: &'tcx Expr<'tcx>) -> bool {
|
||||
match self {
|
||||
VecLocation::Local(hir_id) => expr.res_local_id() == Some(hir_id),
|
||||
VecLocation::Expr(self_expr) => SpanlessEq::new(cx).eq_expr(self_expr, expr),
|
||||
VecLocation::Expr(self_expr) => SpanlessEq::new(cx).eq_expr(ctxt, self_expr, expr),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user