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:
bors
2026-05-14 01:30:22 +00:00
141 changed files with 2741 additions and 1026 deletions
+2 -2
View File
@@ -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.
+5 -3
View File
@@ -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) {
+6
View File
@@ -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>>,
+22 -179
View File
@@ -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 &macro_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,
+4
View File
@@ -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,
+70
View File
@@ -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;
+13
View File
@@ -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 {
+9
View File
@@ -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)
+4 -4
View File
@@ -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 " };
+3 -6
View File
@@ -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: _,
+17 -12
View File
@@ -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)
+1 -1
View File
@@ -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
+59 -59
View File
@@ -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(),
+1 -1
View File
@@ -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,
};
+2 -2
View File
@@ -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!()
};
+3 -3
View File
@@ -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 { "&amp;" })?;
if let Some(lt) = lifetime {
write!(f, "{lt} ", lt = print_lifetime(lt))?;
+1 -1
View File
@@ -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!()
+4 -3
View File
@@ -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());
+7 -8
View File
@@ -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) => {
+1 -1
View File
@@ -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_),
}),
+114 -99
View File
@@ -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>>,
}
+1 -1
View File
@@ -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,
}
}
+1 -1
View File
@@ -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)]
+2 -2
View File
@@ -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(),
+4 -1
View File
@@ -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(..)
+3
View File
@@ -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
+1 -1
View File
@@ -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"
+2 -2
View File
@@ -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,
+6 -5
View File
@@ -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}")
}
+2
View File
@@ -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);
},
_ => {},
}
+3 -1
View File
@@ -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);
}
+8 -7
View File
@@ -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,
}
}
+17 -15
View File
@@ -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),
}
}
+19 -10
View File
@@ -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