diff --git a/Cargo.lock b/Cargo.lock index b409832c7063a..f2bdc1fc20e39 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4267,6 +4267,7 @@ dependencies = [ "rustc_parse_format", "rustc_session", "rustc_span", + "rustc_symbol_mangling", "rustc_target", "rustc_trait_selection", "smallvec", @@ -5948,9 +5949,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", diff --git a/compiler/rustc_error_codes/src/error_codes/E0755.md b/compiler/rustc_error_codes/src/error_codes/E0755.md index bd93626a8db4d..b194e11b7f85e 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0755.md +++ b/compiler/rustc_error_codes/src/error_codes/E0755.md @@ -20,7 +20,7 @@ side effects or infinite loops: extern "C" { #[unsafe(ffi_pure)] // ok! - pub fn strlen(s: *const i8) -> isize; + pub fn strlen(s: *const std::ffi::c_char) -> usize; } # fn main() {} ``` diff --git a/compiler/rustc_error_codes/src/error_codes/E0756.md b/compiler/rustc_error_codes/src/error_codes/E0756.md index daafc2a5ac092..74233a6ad7a7a 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0756.md +++ b/compiler/rustc_error_codes/src/error_codes/E0756.md @@ -21,7 +21,7 @@ which have no side effects except for their return value: extern "C" { #[unsafe(ffi_const)] // ok! - pub fn strlen(s: *const i8) -> i32; + pub fn strlen(s: *const std::ffi::c_char) -> usize; } # fn main() {} ``` diff --git a/compiler/rustc_hir/src/lang_items.rs b/compiler/rustc_hir/src/lang_items.rs index 4a3615e5421fe..6896506a5f26b 100644 --- a/compiler/rustc_hir/src/lang_items.rs +++ b/compiler/rustc_hir/src/lang_items.rs @@ -448,6 +448,14 @@ language_item_table! { // Used to fallback `{float}` to `f32` when `f32: From<{float}>` From, sym::From, from_trait, Target::Trait, GenericRequirement::Exact(1); + + // Runtime symbols + MemCpy, sym::memcpy_fn, memcpy_fn, Target::Fn, GenericRequirement::None; + MemMove, sym::memmove_fn, memmove_fn, Target::Fn, GenericRequirement::None; + MemSet, sym::memset_fn, memset_fn, Target::Fn, GenericRequirement::None; + MemCmp, sym::memcmp_fn, memcmp_fn, Target::Fn, GenericRequirement::None; + Bcmp, sym::bcmp_fn, bcmp_fn, Target::Fn, GenericRequirement::None; + StrLen, sym::strlen_fn, strlen_fn, Target::Fn, GenericRequirement::None; } /// The requirement imposed on the generics of a lang item diff --git a/compiler/rustc_hir/src/weak_lang_items.rs b/compiler/rustc_hir/src/weak_lang_items.rs index b4e548effd46d..0652503bc28c3 100644 --- a/compiler/rustc_hir/src/weak_lang_items.rs +++ b/compiler/rustc_hir/src/weak_lang_items.rs @@ -23,8 +23,29 @@ macro_rules! weak_lang_items { } } +macro_rules! weak_only_lang_items { + ($($item:ident,)*) => { + pub static WEAK_ONLY_LANG_ITEMS: &[LangItem] = &[$(LangItem::$item,)*]; + + impl LangItem { + pub fn is_weak_only(self) -> bool { + matches!(self, $(LangItem::$item)|*) + } + } + } +} + weak_lang_items! { PanicImpl, rust_begin_unwind; EhPersonality, rust_eh_personality; EhCatchTypeinfo, rust_eh_catch_typeinfo; } + +weak_only_lang_items! { + MemCpy, + MemMove, + MemSet, + MemCmp, + Bcmp, + StrLen, +} diff --git a/compiler/rustc_lint/Cargo.toml b/compiler/rustc_lint/Cargo.toml index 758d2762a6af4..176890bab85f2 100644 --- a/compiler/rustc_lint/Cargo.toml +++ b/compiler/rustc_lint/Cargo.toml @@ -22,6 +22,7 @@ rustc_middle = { path = "../rustc_middle" } rustc_parse_format = { path = "../rustc_parse_format" } rustc_session = { path = "../rustc_session" } rustc_span = { path = "../rustc_span" } +rustc_symbol_mangling = { path = "../rustc_symbol_mangling" } rustc_target = { path = "../rustc_target" } rustc_trait_selection = { path = "../rustc_trait_selection" } smallvec = { version = "1.8.1", features = ["union", "may_dangle"] } diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index 10fd1d1501b3b..e4b4acf14036c 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -70,6 +70,7 @@ mod precedence; mod ptr_nulls; mod redundant_semicolon; mod reference_casting; +mod runtime_symbols; mod shadowed_into_iter; mod static_mut_refs; mod traits; @@ -113,6 +114,7 @@ use precedence::*; use ptr_nulls::*; use redundant_semicolon::*; use reference_casting::*; +use runtime_symbols::*; use rustc_hir::def_id::LocalModDefId; use rustc_middle::query::Providers; use rustc_middle::ty::TyCtxt; @@ -242,6 +244,7 @@ late_lint_methods!( AsyncFnInTrait: AsyncFnInTrait, NonLocalDefinitions: NonLocalDefinitions::default(), InteriorMutableConsts: InteriorMutableConsts, + RuntimeSymbols: RuntimeSymbols, ImplTraitOvercaptures: ImplTraitOvercaptures, IfLetRescope: IfLetRescope::default(), StaticMutRefs: StaticMutRefs, diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index 6d9f486f627f6..0ed0efbe6feb0 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -848,6 +848,33 @@ pub(crate) enum UseLetUnderscoreIgnoreSuggestion { }, } +// runtime_symbols.rs +#[derive(Diagnostic)] +pub(crate) enum RedefiningRuntimeSymbolsDiag<'tcx> { + #[diag( + "invalid definition of the runtime `{$symbol_name}` symbol used by the standard library" + )] + #[note( + "expected `{$expected_fn_sig}` + found `{$found_fn_sig}`" + )] + #[help( + "either fix the signature or remove any attributes like `#[unsafe(no_mangle)]`, `#[unsafe(export_name = \"{$symbol_name}\")]`, or `#[link_name = \"{$symbol_name}\"]`" + )] + FnDef { symbol_name: String, expected_fn_sig: Ty<'tcx>, found_fn_sig: Ty<'tcx> }, + #[diag( + "invalid definition of the runtime `{$symbol_name}` symbol used by the standard library" + )] + #[note( + "expected `{$expected_fn_sig}` + found `static {$symbol_name}: {$static_ty}`" + )] + #[help( + "either fix the signature or remove any attributes `#[unsafe(no_mangle)]` or `#[unsafe(export_name = \"{$symbol_name}\")]`" + )] + Static { symbol_name: String, static_ty: Ty<'tcx>, expected_fn_sig: Ty<'tcx> }, +} + // drop_forget_useless.rs #[derive(Diagnostic)] #[diag("calls to `std::mem::drop` with a reference instead of an owned value does nothing")] diff --git a/compiler/rustc_lint/src/runtime_symbols.rs b/compiler/rustc_lint/src/runtime_symbols.rs new file mode 100644 index 0000000000000..f53726ddb7242 --- /dev/null +++ b/compiler/rustc_lint/src/runtime_symbols.rs @@ -0,0 +1,205 @@ +use rustc_hir::def_id::{DefId, LocalDefId}; +use rustc_hir::{self as hir, FnSig, ForeignItemKind, LanguageItems}; +use rustc_infer::infer::DefineOpaqueTypes; +use rustc_middle::ty::{self, Instance, Ty}; +use rustc_session::{declare_lint, declare_lint_pass}; +use rustc_span::Span; +use rustc_trait_selection::infer::TyCtxtInferExt; + +use crate::lints::RedefiningRuntimeSymbolsDiag; +use crate::{LateContext, LateLintPass, LintContext}; + +declare_lint! { + /// The `invalid_runtime_symbol_definitions` lint checks the signature of items whose + /// symbol name is a runtime symbols expected by `core`. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// #[unsafe(no_mangle)] + /// pub fn strlen() {} // invalid definition of the `strlen` function + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Up-most care is required when defining runtime symbols assumed and + /// used by the standard library. They must follow the C specification, not use any + /// standard-library facility or undefined behavior may occur. + /// + /// The symbols currently checked are `memcpy`, `memmove`, `memset`, `memcmp`, + /// `bcmp` and `strlen`. + /// + /// [^1]: https://doc.rust-lang.org/core/index.html#how-to-use-the-core-library + pub INVALID_RUNTIME_SYMBOL_DEFINITIONS, + Deny, + "invalid definition of a symbol used by the standard library" +} + +declare_lint_pass!(RuntimeSymbols => [INVALID_RUNTIME_SYMBOL_DEFINITIONS]); + +static EXPECTED_SYMBOLS: &[ExpectedSymbol] = &[ + ExpectedSymbol { symbol: "memcpy", lang: LanguageItems::memcpy_fn }, + ExpectedSymbol { symbol: "memmove", lang: LanguageItems::memmove_fn }, + ExpectedSymbol { symbol: "memset", lang: LanguageItems::memset_fn }, + ExpectedSymbol { symbol: "memcmp", lang: LanguageItems::memcmp_fn }, + ExpectedSymbol { symbol: "bcmp", lang: LanguageItems::bcmp_fn }, + ExpectedSymbol { symbol: "strlen", lang: LanguageItems::strlen_fn }, +]; + +#[derive(Copy, Clone, Debug)] +struct ExpectedSymbol { + symbol: &'static str, + lang: fn(&LanguageItems) -> Option, +} + +impl<'tcx> LateLintPass<'tcx> for RuntimeSymbols { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) { + // Bail-out if the item is not a function/method or static. + match item.kind { + hir::ItemKind::Fn { sig, ident: _, generics, body: _, has_body: _ } => { + // Generic functions cannot have the same runtime symbol as we do not allow + // any symbol attributes. + if !generics.params.is_empty() { + return; + } + + // Try to get the overridden symbol name of this function (our mangling + // cannot ever conflict with runtime symbols, so no need to check for those). + let Some(symbol_name) = rustc_symbol_mangling::symbol_name_from_attrs( + cx.tcx, + rustc_middle::ty::InstanceKind::Item(item.owner_id.to_def_id()), + ) else { + return; + }; + + check_fn(cx, &symbol_name, sig, item.owner_id.def_id); + } + hir::ItemKind::Static(..) => { + // Compute the symbol name of this static (without mangling, as our mangling + // cannot ever conflict with runtime symbols). + let Some(symbol_name) = rustc_symbol_mangling::symbol_name_from_attrs( + cx.tcx, + rustc_middle::ty::InstanceKind::Item(item.owner_id.to_def_id()), + ) else { + return; + }; + + let def_id = item.owner_id.def_id; + + check_static(cx, &symbol_name, def_id, item.span); + } + hir::ItemKind::ForeignMod { abi: _, items } => { + for item in items { + let item = cx.tcx.hir_foreign_item(*item); + + let did = item.owner_id.def_id; + let instance = Instance::new_raw( + did.to_def_id(), + ty::List::identity_for_item(cx.tcx, did), + ); + let symbol_name = cx.tcx.symbol_name(instance); + + match item.kind { + ForeignItemKind::Fn(fn_sig, _idents, _generics) => { + check_fn(cx, &symbol_name.name, fn_sig, did); + } + ForeignItemKind::Static(..) => { + check_static(cx, &symbol_name.name, did, item.span); + } + ForeignItemKind::Type => return, + } + } + } + _ => return, + } + } +} + +fn check_fn(cx: &LateContext<'_>, symbol_name: &str, sig: FnSig<'_>, did: LocalDefId) { + let Some(expected_symbol) = EXPECTED_SYMBOLS.iter().find(|es| es.symbol == symbol_name) else { + // The symbol name does not correspond to a runtime symbols, bail out + return; + }; + + let Some(expected_def_id) = (expected_symbol.lang)(&cx.tcx.lang_items()) else { + // Can't find the corresponding language item, bail out + return; + }; + + // Get the two function signatures + let lang_sig = cx.tcx.normalize_erasing_regions( + cx.typing_env(), + cx.tcx.fn_sig(expected_def_id).instantiate_identity(), + ); + let user_sig = cx + .tcx + .normalize_erasing_regions(cx.typing_env(), cx.tcx.fn_sig(did).instantiate_identity()); + + // Compare the two signatures with an inference context + let infcx = cx.tcx.infer_ctxt().build(cx.typing_mode()); + let cause = rustc_middle::traits::ObligationCause::misc(sig.span, did); + let result = infcx.at(&cause, cx.param_env).eq(DefineOpaqueTypes::No, lang_sig, user_sig); + + // If they don't match, emit our own mismatch signatures + if let Err(_terr) = result { + // Create fn pointers for diagnostics purpose + let expected = Ty::new_fn_ptr(cx.tcx, lang_sig); + let actual = Ty::new_fn_ptr(cx.tcx, user_sig); + + cx.emit_span_lint( + INVALID_RUNTIME_SYMBOL_DEFINITIONS, + sig.span, + RedefiningRuntimeSymbolsDiag::FnDef { + symbol_name: symbol_name.to_string(), + found_fn_sig: actual, + expected_fn_sig: expected, + }, + ); + } +} + +fn check_static<'tcx>(cx: &LateContext<'tcx>, symbol_name: &str, did: LocalDefId, sp: Span) { + let Some(expected_symbol) = EXPECTED_SYMBOLS.iter().find(|es| es.symbol == symbol_name) else { + // The symbol name does not correspond to a runtime symbols, bail out + return; + }; + + let Some(expected_def_id) = (expected_symbol.lang)(&cx.tcx.lang_items()) else { + // Can't find the corresponding language item, bail out + return; + }; + + // Get the static type + let static_ty = cx.tcx.type_of(did).instantiate_identity().skip_norm_wip(); + + // Peel Option<...> and get the inner type (see std weak! macro with #[linkage = "extern_weak"]) + let inner_static_ty: Ty<'_> = match static_ty.kind() { + ty::Adt(def, args) if Some(def.did()) == cx.tcx.lang_items().option_type() => { + args.type_at(0) + } + _ => static_ty, + }; + + // Get the expected symbol function signature + let lang_sig = cx.tcx.normalize_erasing_regions( + cx.typing_env(), + cx.tcx.fn_sig(expected_def_id).instantiate_identity(), + ); + + let expected = Ty::new_fn_ptr(cx.tcx, lang_sig); + + // Compare the expected function signature with the static type, report an error if they don't match + if expected != inner_static_ty { + cx.emit_span_lint( + INVALID_RUNTIME_SYMBOL_DEFINITIONS, + sp, + RedefiningRuntimeSymbolsDiag::Static { + static_ty, + symbol_name: symbol_name.to_string(), + expected_fn_sig: expected, + }, + ); + } +} diff --git a/compiler/rustc_middle/src/middle/privacy.rs b/compiler/rustc_middle/src/middle/privacy.rs index 0359264d3f787..5bf4bbe79a9a0 100644 --- a/compiler/rustc_middle/src/middle/privacy.rs +++ b/compiler/rustc_middle/src/middle/privacy.rs @@ -65,6 +65,10 @@ impl EffectiveVisibility { } } + pub fn public_at_level(&self) -> Option { + 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 @@ impl EffectiveVisibilities { } pub fn public_at_level(&self, id: LocalDefId) -> Option { - 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) { diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs index 16933cb4ea8ff..f7677994960db 100644 --- a/compiler/rustc_middle/src/ty/mod.rs +++ b/compiler/rustc_middle/src/ty/mod.rs @@ -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, 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>, pub extern_crate_map: UnordMap, pub maybe_unused_trait_imports: FxIndexSet, pub module_children: LocalDefIdMap>, diff --git a/compiler/rustc_passes/src/lang_items.rs b/compiler/rustc_passes/src/lang_items.rs index 527c618c7a264..8ec1ef432e92d 100644 --- a/compiler/rustc_passes/src/lang_items.rs +++ b/compiler/rustc_passes/src/lang_items.rs @@ -29,6 +29,11 @@ pub(crate) enum Duplicate { CrateDepends, } +enum CollectWeak { + Allowed, + Ignore, +} + struct LanguageItemCollector<'ast, 'tcx> { items: LanguageItems, tcx: TyCtxt<'tcx>, @@ -60,19 +65,24 @@ impl<'ast, 'tcx> LanguageItemCollector<'ast, 'tcx> { attrs: &'ast [ast::Attribute], item_span: Span, generics: Option<&'ast ast::Generics>, + collect_weak: CollectWeak, ) { if let Some((name, attr_span)) = extract_ast(attrs) { match LangItem::from_name(name) { // Known lang item with attribute on correct target. Some(lang_item) if actual_target == lang_item.target() => { - self.collect_item_extended( - lang_item, - def_id, - item_span, - attr_span, - generics, - actual_target, - ); + // Weak lang items are handled separately + // Weak only lang items are always handled here + if !lang_item.is_weak() || matches!(collect_weak, CollectWeak::Allowed) { + self.collect_item_extended( + lang_item, + def_id, + item_span, + attr_span, + generics, + actual_target, + ); + } } // Known lang item with attribute on incorrect target. Some(lang_item) => { @@ -299,6 +309,7 @@ impl<'ast, 'tcx> visit::Visitor<'ast> for LanguageItemCollector<'ast, 'tcx> { &i.attrs, i.span, i.opt_generics(), + CollectWeak::Allowed, ); let parent_item = self.parent_item.replace(i); @@ -306,6 +317,17 @@ impl<'ast, 'tcx> visit::Visitor<'ast> for LanguageItemCollector<'ast, 'tcx> { self.parent_item = parent_item; } + fn visit_foreign_item(&mut self, i: &'ast ast::ForeignItem) { + self.check_for_lang( + Target::Fn, + self.resolver.node_id_to_def_id[&i.id], + &i.attrs, + i.span, + None, + CollectWeak::Ignore, + ); + } + fn visit_variant(&mut self, variant: &'ast ast::Variant) { self.check_for_lang( Target::Variant, @@ -313,6 +335,7 @@ impl<'ast, 'tcx> visit::Visitor<'ast> for LanguageItemCollector<'ast, 'tcx> { &variant.attrs, variant.span, None, + CollectWeak::Allowed, ); } @@ -352,6 +375,7 @@ impl<'ast, 'tcx> visit::Visitor<'ast> for LanguageItemCollector<'ast, 'tcx> { &i.attrs, i.span, generics, + CollectWeak::Allowed, ); visit::walk_assoc_item(self, i, ctxt); diff --git a/compiler/rustc_passes/src/weak_lang_items.rs b/compiler/rustc_passes/src/weak_lang_items.rs index 4200003ea1d1a..fbf486c741df5 100644 --- a/compiler/rustc_passes/src/weak_lang_items.rs +++ b/compiler/rustc_passes/src/weak_lang_items.rs @@ -48,11 +48,15 @@ struct WeakLangItemVisitor<'a, 'tcx> { impl<'ast> visit::Visitor<'ast> for WeakLangItemVisitor<'_, '_> { fn visit_foreign_item(&mut self, i: &'ast ast::ForeignItem) { if let Some((lang_item, _)) = extract_ast(&i.attrs) { - if let Some(item) = LangItem::from_name(lang_item) - && item.is_weak() - { - if self.items.get(item).is_none() { - self.items.missing.push(item); + if let Some(item) = LangItem::from_name(lang_item) { + if item.is_weak() { + if self.items.get(item).is_none() { + self.items.missing.push(item); + } + } else if item.is_weak_only() { + // weak only lang items are handled directly in lang_items.rs + } else { + self.tcx.dcx().emit_err(UnknownExternLangItem { span: i.span, lang_item }); } } else { self.tcx.dcx().emit_err(UnknownExternLangItem { span: i.span, lang_item }); diff --git a/compiler/rustc_privacy/src/lib.rs b/compiler/rustc_privacy/src/lib.rs index d92944d1b31fa..6a792e5a803b7 100644 --- a/compiler/rustc_privacy/src/lib.rs +++ b/compiler/rustc_privacy/src/lib.rs @@ -15,7 +15,6 @@ use errors::{ 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::ty::{ }; 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 @@ impl VisibilityLike for EffectiveVisibility { /// 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 @@ impl<'tcx> EmbargoVisitor<'tcx> { 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 @@ impl<'tcx> EmbargoVisitor<'tcx> { DefKind::Use | DefKind::ExternCrate | DefKind::GlobalAsm => {} // The interface is empty, and all nested items are processed by `check_def_id`. DefKind::Mod => {} - DefKind::Macro { .. } => { - if let Some(item_ev) = item_ev { - let (_, macro_def, _) = - self.tcx.hir_expect_item(owner_id.def_id).expect_macro(); - self.update_reachability_from_macro(owner_id.def_id, macro_def, item_ev); - } - } + // Effective visibilities for macros are processed earlier. + DefKind::Macro { .. } => {} DefKind::ForeignTy | DefKind::Const { .. } | DefKind::Static { .. } @@ -1815,7 +1639,6 @@ fn effective_visibilities(tcx: TyCtxt<'_>, (): ()) -> &EffectiveVisibilities { let mut visitor = EmbargoVisitor { tcx, effective_visibilities: tcx.resolutions(()).effective_visibilities.clone(), - macro_reachable: Default::default(), changed: false, }; @@ -1872,6 +1695,26 @@ fn effective_visibilities(tcx: TyCtxt<'_>, (): ()) -> &EffectiveVisibilities { visitor.changed = false; } + // FIXME: remove this once proper support for defs reachability from macros is implemented. + // See `ResolverGlobalCtxt::macro_reachable_adts` comment. + for (&adt_def_id, macro_mods) in &tcx.resolutions(()).macro_reachable_adts { + let struct_def = tcx.adt_def(adt_def_id); + let Some(struct_ev) = visitor.effective_visibilities.effective_vis(adt_def_id).copied() + else { + continue; + }; + for field in &struct_def.non_enum_variant().fields { + let def_id = field.did.expect_local(); + let field_vis = tcx.local_visibility(def_id); + + for ¯o_mod in macro_mods { + if field_vis.is_accessible_from(macro_mod, tcx) { + visitor.reach(def_id, struct_ev).ty(); + } + } + } + } + let crate_items = tcx.hir_crate_items(()); loop { for id in crate_items.free_items() { diff --git a/compiler/rustc_resolve/src/effective_visibilities.rs b/compiler/rustc_resolve/src/effective_visibilities.rs index f3b47f04c90a6..e25a57cee4062 100644 --- a/compiler/rustc_resolve/src/effective_visibilities.rs +++ b/compiler/rustc_resolve/src/effective_visibilities.rs @@ -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>, // 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 @@ impl<'a, 'ra, 'tcx> EffectiveVisibilitiesVisitor<'a, 'ra, 'tcx> { 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 @@ impl<'a, 'ra, 'tcx> EffectiveVisibilitiesVisitor<'a, 'ra, 'tcx> { 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 @@ impl<'a, 'ra, 'tcx> Visitor<'a> for EffectiveVisibilitiesVisitor<'a, 'ra, 'tcx> let def_id = self.r.local_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 @@ impl<'a, 'ra, 'tcx> Visitor<'a> for EffectiveVisibilitiesVisitor<'a, 'ra, 'tcx> 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.local_def_id(variant.id); @@ -244,7 +377,7 @@ impl<'a, 'ra, 'tcx> Visitor<'a> for EffectiveVisibilitiesVisitor<'a, 'ra, 'tcx> } } - 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.local_def_id(field.id), def_id); } @@ -254,6 +387,10 @@ impl<'a, 'ra, 'tcx> Visitor<'a> for EffectiveVisibilitiesVisitor<'a, 'ra, 'tcx> 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 @@ impl<'a, 'ra, 'tcx> Visitor<'a> for EffectiveVisibilitiesVisitor<'a, 'ra, 'tcx> | ast::ItemKind::GlobalAsm(..) | ast::ItemKind::TyAlias(..) | ast::ItemKind::TraitAlias(..) - | ast::ItemKind::MacroDef(..) | ast::ItemKind::ForeignMod(..) | ast::ItemKind::Fn(..) | ast::ItemKind::Delegation(..) => return, diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index 32bc59de711ed..1b581bdf1bd45 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -1530,6 +1530,8 @@ pub struct Resolver<'ra, 'tcx> { stripped_cfg_items: Vec> = Vec::new(), effective_visibilities: EffectiveVisibilities, + macro_reachable_adts: FxIndexMap>, + doc_link_resolutions: FxIndexMap, doc_link_traits_in_scope: FxIndexMap>, all_macro_rules: UnordSet = Default::default(), @@ -1852,6 +1854,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { 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, @@ -1962,6 +1965,7 @@ impl<'ra, 'tcx> Resolver<'ra, '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, diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 69ed7314855f9..d7aaea08db024 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -508,6 +508,7 @@ symbols! { backchain, backend_repr, bang, + bcmp_fn, begin_panic, bench, bevy_ecs, @@ -1255,7 +1256,11 @@ symbols! { mem_variant_count, mem_zeroed, member_constraints, + memcmp_fn, + memcpy_fn, + memmove_fn, memory, + memset_fn, memtag, message, meta, @@ -1992,6 +1997,7 @@ symbols! { strict_provenance_lints, string_deref_patterns, stringify, + strlen_fn, struct_field_attributes, struct_inherit, struct_variant, diff --git a/compiler/rustc_symbol_mangling/src/lib.rs b/compiler/rustc_symbol_mangling/src/lib.rs index c052037f05b39..693aa95b2524a 100644 --- a/compiler/rustc_symbol_mangling/src/lib.rs +++ b/compiler/rustc_symbol_mangling/src/lib.rs @@ -92,7 +92,7 @@ use rustc_hir::def_id::{CrateNum, LOCAL_CRATE}; use rustc_middle::middle::codegen_fn_attrs::{CodegenFnAttrFlags, CodegenFnAttrs}; use rustc_middle::mono::{InstantiationMode, MonoItem}; use rustc_middle::query::Providers; -use rustc_middle::ty::{self, Instance, TyCtxt}; +use rustc_middle::ty::{self, Instance, InstanceKind, TyCtxt}; use rustc_session::config::SymbolManglingVersion; use tracing::debug; @@ -149,29 +149,22 @@ pub fn typeid_for_trait_ref<'tcx>( v0::mangle_typeid_for_trait_ref(tcx, trait_ref) } -/// Computes the symbol name for the given instance. This function will call -/// `compute_instantiating_crate` if it needs to factor the instantiating crate -/// into the symbol name. -fn compute_symbol_name<'tcx>( +pub fn symbol_name_from_attrs<'tcx>( tcx: TyCtxt<'tcx>, - instance: Instance<'tcx>, - compute_instantiating_crate: impl FnOnce() -> CrateNum, -) -> String { - let def_id = instance.def_id(); - let args = instance.args; - - debug!("symbol_name(def_id={:?}, args={:?})", def_id, args); + instance_kind: InstanceKind<'tcx>, +) -> Option { + let def_id = instance_kind.def_id(); if let Some(def_id) = def_id.as_local() { if tcx.proc_macro_decls_static(()) == Some(def_id) { let stable_crate_id = tcx.stable_crate_id(LOCAL_CRATE); - return tcx.sess.generate_proc_macro_decls_symbol(stable_crate_id); + return Some(tcx.sess.generate_proc_macro_decls_symbol(stable_crate_id)); } } // FIXME(eddyb) Precompute a custom symbol name based on attributes. let attrs = if tcx.def_kind(def_id).has_codegen_attrs() { - &tcx.codegen_instance_attrs(instance.def) + &tcx.codegen_instance_attrs(instance_kind) } else { CodegenFnAttrs::EMPTY }; @@ -197,7 +190,7 @@ fn compute_symbol_name<'tcx>( // legacy symbol mangling scheme. let name = if let Some(name) = attrs.symbol_name { name } else { tcx.item_name(def_id) }; - return v0::mangle_internal_symbol(tcx, name.as_str()); + return Some(v0::mangle_internal_symbol(tcx, name.as_str())); } let wasm_import_module_exception_force_mangling = { @@ -225,15 +218,35 @@ fn compute_symbol_name<'tcx>( if !wasm_import_module_exception_force_mangling { if let Some(name) = attrs.symbol_name { // Use provided name - return name.to_string(); + return Some(name.to_string()); } if attrs.flags.contains(CodegenFnAttrFlags::NO_MANGLE) { // Don't mangle - return tcx.item_name(def_id).to_string(); + return Some(tcx.item_name(def_id).to_string()); } } + None +} + +/// Computes the symbol name for the given instance. This function will call +/// `compute_instantiating_crate` if it needs to factor the instantiating crate +/// into the symbol name. +fn compute_symbol_name<'tcx>( + tcx: TyCtxt<'tcx>, + instance: Instance<'tcx>, + compute_instantiating_crate: impl FnOnce() -> CrateNum, +) -> String { + let def_id = instance.def_id(); + let args = instance.args; + + debug!("symbol_name(def_id={:?}, args={:?})", def_id, args); + + if let Some(symbol) = symbol_name_from_attrs(tcx, instance.def) { + return symbol; + } + // If we're dealing with an instance of a function that's inlined from // another crate but we're marking it as globally shared to our // compilation (aka we're not making an internal copy in each of our diff --git a/library/core/src/ffi/mod.rs b/library/core/src/ffi/mod.rs index 3f1aa54050a31..df6396b84264d 100644 --- a/library/core/src/ffi/mod.rs +++ b/library/core/src/ffi/mod.rs @@ -85,3 +85,30 @@ impl fmt::Debug for c_void { )] #[link(name = "/defaultlib:libcmt", modifiers = "+verbatim", cfg(target_feature = "crt-static"))] unsafe extern "C" {} + +// Used by rustc for checking the definitions of other function with the same symbol names +// +// See the `invalid_runtime_symbols_definitions` lint. +mod runtime_symbols { + use crate::ffi::{c_char, c_int, c_void}; + + unsafe extern "C" { + #[lang = "memcpy_fn"] + fn memcpy(dest: *mut c_void, src: *const c_void, n: usize) -> *mut c_void; + + #[lang = "memmove_fn"] + fn memmove(dest: *mut c_void, src: *const c_void, n: usize) -> *mut c_void; + + #[lang = "memset_fn"] + fn memset(s: *mut c_void, c: c_int, n: usize) -> *mut c_void; + + #[lang = "memcmp_fn"] + fn memcmp(s1: *const c_void, s2: *const c_void, n: usize) -> c_int; + + #[lang = "bcmp_fn"] + fn bcmp(s1: *const c_void, s2: *const c_void, n: usize) -> c_int; + + #[lang = "strlen_fn"] + fn strlen(s: *const c_char) -> usize; + } +} diff --git a/library/std/src/os/unix/process.rs b/library/std/src/os/unix/process.rs index a739d6ad2a90d..a0defc39ac82e 100644 --- a/library/std/src/os/unix/process.rs +++ b/library/std/src/os/unix/process.rs @@ -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")] diff --git a/library/std/src/sys/pal/unix/linux/pidfd.rs b/library/std/src/sys/pal/unix/linux/pidfd.rs index 671046949aa16..ac376c1a81af6 100644 --- a/library/std/src/sys/pal/unix/linux/pidfd.rs +++ b/library/std/src/sys/pal/unix/linux/pidfd.rs @@ -95,6 +95,21 @@ impl PidFd { .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 { let r = self.waitid(libc::WEXITED)?; match r { diff --git a/library/std/src/sys/process/unix/fuchsia.rs b/library/std/src/sys/process/unix/fuchsia.rs index 3fae5ec1468b1..65ef7bf8c60c8 100644 --- a/library/std/src/sys/process/unix/fuchsia.rs +++ b/library/std/src/sys/process/unix/fuchsia.rs @@ -158,6 +158,11 @@ impl Process { 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 { let mut proc_info: zx_info_process_t = Default::default(); let mut actual: size_t = 0; diff --git a/library/std/src/sys/process/unix/unix.rs b/library/std/src/sys/process/unix/unix.rs index a68be2543bc23..d476072a6449f 100644 --- a/library/std/src/sys/process/unix/unix.rs +++ b/library/std/src/sys/process/unix/unix.rs @@ -1002,6 +1002,19 @@ impl Process { 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 { use crate::sys::cvt_r; if let Some(status) = self.status { diff --git a/library/std/src/sys/process/unix/unsupported.rs b/library/std/src/sys/process/unix/unsupported.rs index 9bda394f24659..17421d1e2e35d 100644 --- a/library/std/src/sys/process/unix/unsupported.rs +++ b/library/std/src/sys/process/unix/unsupported.rs @@ -49,6 +49,10 @@ impl Process { unsupported() } + pub fn send_process_group_signal(&self, _signal: i32) -> io::Result<()> { + unsupported() + } + pub fn wait(&mut self) -> io::Result { unsupported() } diff --git a/library/std/src/sys/process/unix/vxworks.rs b/library/std/src/sys/process/unix/vxworks.rs index 346ca6d74c9bd..c5acff2bdd3c5 100644 --- a/library/std/src/sys/process/unix/vxworks.rs +++ b/library/std/src/sys/process/unix/vxworks.rs @@ -161,6 +161,14 @@ impl Process { } } + 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 { use crate::sys::cvt_r; if let Some(status) = self.status { diff --git a/src/doc/rustdoc/src/unstable-features.md b/src/doc/rustdoc/src/unstable-features.md index 1c91ad343b8c0..45f79f53f2c53 100644 --- a/src/doc/rustdoc/src/unstable-features.md +++ b/src/doc/rustdoc/src/unstable-features.md @@ -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) diff --git a/src/librustdoc/clean/cfg.rs b/src/librustdoc/clean/cfg.rs index ec3407d361ebd..5dc9f17c15fdb 100644 --- a/src/librustdoc/clean/cfg.rs +++ b/src/librustdoc/clean/cfg.rs @@ -149,7 +149,7 @@ impl Cfg { | 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 @@ impl Display<'_> { 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 @@ impl fmt::Display for Display<'_> { }) .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 " }; diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index f020a26a23bc2..d50e4dd80557a 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -1472,11 +1472,8 @@ pub(crate) fn clean_middle_assoc_item(assoc_item: &ty::AssocItem, cx: &mut DocCo 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: _, diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 05d4888c110cc..7b7fbffbe9066 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -491,7 +491,7 @@ impl Item { /// 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 @@ impl Item { }; 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 @@ impl Item { 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 @@ impl Type { pub(crate) fn primitive_type(&self) -> Option { 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 @@ impl Type { 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 @@ impl Type { 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) diff --git a/src/librustdoc/fold.rs b/src/librustdoc/fold.rs index 8b9db4638e473..3890eccc402a7 100644 --- a/src/librustdoc/fold.rs +++ b/src/librustdoc/fold.rs @@ -105,7 +105,7 @@ pub(crate) trait DocFolder: Sized { /// 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 diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs index 18b00224dfcb0..0312f8909c036 100644 --- a/src/librustdoc/formats/cache.rs +++ b/src/librustdoc/formats/cache.rs @@ -248,7 +248,7 @@ impl DocFolder for CacheBuilder<'_, '_> { // 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 @@ impl DocFolder for CacheBuilder<'_, '_> { // 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); + 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)); + 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()); + 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(), diff --git a/src/librustdoc/formats/item_type.rs b/src/librustdoc/formats/item_type.rs index eb3492e4625be..73b85d2923619 100644 --- a/src/librustdoc/formats/item_type.rs +++ b/src/librustdoc/formats/item_type.rs @@ -106,7 +106,7 @@ item_type! { 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, }; diff --git a/src/librustdoc/formats/renderer.rs b/src/librustdoc/formats/renderer.rs index 5c458232f8f9c..bb2ccf0d4a354 100644 --- a/src/librustdoc/formats/renderer.rs +++ b/src/librustdoc/formats/renderer.rs @@ -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!() }; diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index 0694dd05cd399..35212d480cfdd 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -961,7 +961,7 @@ fn fmt_type( } } }, - clean::Slice(box clean::Generic(name)) => { + clean::Slice(clean::Generic(name)) => { primitive_link(f, PrimitiveType::Slice, format_args!("[{name}]"), cx) } clean::Slice(t) => Wrapped::with_square_brackets().wrap(print_type(t, cx)).fmt(f), @@ -974,7 +974,7 @@ fn fmt_type( fmt::Display::fmt(&print_type(t, cx), f)?; write!(f, ", {field})") } - clean::Array(box clean::Generic(name), n) if !f.alternate() => primitive_link( + clean::Array(clean::Generic(name), n) if !f.alternate() => primitive_link( f, PrimitiveType::Array, format_args!("[{name}; {n}]", n = Escape(n)), @@ -1280,7 +1280,7 @@ fn print_parameter(parameter: &clean::Parameter, cx: &Context<'_>) -> impl fmt:: if let Some(self_ty) = parameter.to_receiver() { match self_ty { clean::SelfTy => f.write_str("self"), - clean::BorrowedRef { lifetime, mutability, type_: box clean::SelfTy } => { + clean::BorrowedRef { lifetime, mutability, type_: clean::SelfTy } => { f.write_str(if f.alternate() { "&" } else { "&" })?; if let Some(lt) = lifetime { write!(f, "{lt} ", lt = print_lifetime(lt))?; diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index 6c02eecbd06ee..038eb49407f84 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -832,7 +832,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { // 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!() diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 8600fccdbe62c..20bbf96559789 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -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 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()); diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index 48108097864a4..870c60a984813 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -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("/* private fields */"); } @@ -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, "/* private fields */")?; @@ -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) => { diff --git a/src/librustdoc/html/render/sidebar.rs b/src/librustdoc/html/render/sidebar.rs index 360f8bdf642e2..817e8144bbb4a 100644 --- a/src/librustdoc/html/render/sidebar.rs +++ b/src/librustdoc/html/render/sidebar.rs @@ -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_), }), diff --git a/src/librustdoc/html/render/span_map.rs b/src/librustdoc/html/render/span_map.rs index ff214bad59f1d..517b538d1bfd1 100644 --- a/src/librustdoc/html/render/span_map.rs +++ b/src/librustdoc/html/render/span_map.rs @@ -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, FxHashMap) { 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>, pub(crate) matches: FxHashMap, } -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: + /// . + 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 @@ impl SpanMapVisitor<'_> { self.matches .insert(path.span.into(), LinkFromSrc::Primitive(PrimitiveType::from(p))); } - Res::Err => {} _ => {} } } @@ -216,43 +231,6 @@ impl SpanMapVisitor<'_> { self.matches.insert(new_span.into(), link_from_src); true } - - fn infer_id(&mut self, hir_id: HirId, expr_hir_id: Option, 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 { - 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 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { 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 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { 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 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { 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 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'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>>, +} diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index 623ad55f6e994..a0140402de428 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -232,7 +232,7 @@ impl FromClean for GenericArg { 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, } } diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index c8aa197cabe18..2fff3adfa5468 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -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)] diff --git a/src/librustdoc/passes/collect_trait_impls.rs b/src/librustdoc/passes/collect_trait_impls.rs index 762516874c0ae..9d603117ea5ac 100644 --- a/src/librustdoc/passes/collect_trait_impls.rs +++ b/src/librustdoc/passes/collect_trait_impls.rs @@ -155,7 +155,7 @@ pub(crate) fn collect_trait_impls(mut krate: Crate, cx: &mut DocContext<'_>) -> // 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 @@ pub(crate) fn collect_trait_impls(mut krate: Crate, cx: &mut DocContext<'_>) -> // 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(), diff --git a/src/librustdoc/passes/propagate_stability.rs b/src/librustdoc/passes/propagate_stability.rs index c8691fd012bf6..6700ca649d7be 100644 --- a/src/librustdoc/passes/propagate_stability.rs +++ b/src/librustdoc/passes/propagate_stability.rs @@ -69,7 +69,10 @@ impl DocFolder for StabilityPropagator<'_, '_> { 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(..) diff --git a/src/tools/clippy/CHANGELOG.md b/src/tools/clippy/CHANGELOG.md index 24b91932567a5..f53143e564b6c 100644 --- a/src/tools/clippy/CHANGELOG.md +++ b/src/tools/clippy/CHANGELOG.md @@ -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 diff --git a/src/tools/clippy/Cargo.toml b/src/tools/clippy/Cargo.toml index a87fdeaf99c5c..ca80c3fadc226 100644 --- a/src/tools/clippy/Cargo.toml +++ b/src/tools/clippy/Cargo.toml @@ -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" diff --git a/src/tools/clippy/book/src/lints.md b/src/tools/clippy/book/src/lints.md index 1dd5179766017..55b382c855651 100644 --- a/src/tools/clippy/book/src/lints.md +++ b/src/tools/clippy/book/src/lints.md @@ -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 diff --git a/src/tools/clippy/clippy_lints/src/booleans.rs b/src/tools/clippy/clippy_lints/src/booleans.rs index ed963dc3e90e7..5446148b0f9d6 100644 --- a/src/tools/clippy/clippy_lints/src/booleans.rs +++ b/src/tools/clippy/clippy_lints/src/booleans.rs @@ -297,8 +297,9 @@ impl<'v> Hir2Qmm<'_, '_, 'v> { 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 @@ impl<'v> Hir2Qmm<'_, '_, 'v> { && 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)))); diff --git a/src/tools/clippy/clippy_lints/src/checked_conversions.rs b/src/tools/clippy/clippy_lints/src/checked_conversions.rs index 5e9009c67197f..c80930f0b443f 100644 --- a/src/tools/clippy/clippy_lints/src/checked_conversions.rs +++ b/src/tools/clippy/clippy_lints/src/checked_conversions.rs @@ -7,7 +7,7 @@ use rustc_errors::Applicability; 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 @@ impl LateLintPass<'_> for CheckedConversions { }, _ => 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 @@ impl LateLintPass<'_> for CheckedConversions { 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> { - if self.is_compatible(&other, cx) { + pub fn combine(self, other: Self, cx: &LateContext<'_>, ctxt: SyntaxContext) -> Option> { + 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 @@ impl<'a> 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)) } diff --git a/src/tools/clippy/clippy_lints/src/comparison_chain.rs b/src/tools/clippy/clippy_lints/src/comparison_chain.rs index a2ddf3dad7a23..554f1ab186852 100644 --- a/src/tools/clippy/clippy_lints/src/comparison_chain.rs +++ b/src/tools/clippy/clippy_lints/src/comparison_chain.rs @@ -6,7 +6,7 @@ use rustc_errors::Applicability; 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 @@ impl<'tcx> LateLintPass<'tcx> for ComparisonChain { // 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; diff --git a/src/tools/clippy/clippy_lints/src/declared_lints.rs b/src/tools/clippy/clippy_lints/src/declared_lints.rs index 79ed199147f1d..4218d1fdc2901 100644 --- a/src/tools/clippy/clippy_lints/src/declared_lints.rs +++ b/src/tools/clippy/clippy_lints/src/declared_lints.rs @@ -232,6 +232,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ 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 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ 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 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ 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, diff --git a/src/tools/clippy/clippy_lints/src/entry.rs b/src/tools/clippy/clippy_lints/src/entry.rs index f700cb6773439..a24cc957df070 100644 --- a/src/tools/clippy/clippy_lints/src/entry.rs +++ b/src/tools/clippy/clippy_lints/src/entry.rs @@ -498,11 +498,11 @@ impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, 'tcx> { } 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 @@ impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, 'tcx> { 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 @@ impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, '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(()) diff --git a/src/tools/clippy/clippy_lints/src/floating_point_arithmetic/custom_abs.rs b/src/tools/clippy/clippy_lints/src/floating_point_arithmetic/custom_abs.rs index f476abae708d1..feff92fbf8762 100644 --- a/src/tools/clippy/clippy_lints/src/floating_point_arithmetic/custom_abs.rs +++ b/src/tools/clippy/clippy_lints/src/floating_point_arithmetic/custom_abs.rs @@ -14,11 +14,15 @@ use super::SUBOPTIMAL_FLOPS; /// 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; diff --git a/src/tools/clippy/clippy_lints/src/floating_point_arithmetic/hypot.rs b/src/tools/clippy/clippy_lints/src/floating_point_arithmetic/hypot.rs index 8d3bd8084db8f..f9b42761f3a7e 100644 --- a/src/tools/clippy/clippy_lints/src/floating_point_arithmetic/hypot.rs +++ b/src/tools/clippy/clippy_lints/src/floating_point_arithmetic/hypot.rs @@ -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({})", diff --git a/src/tools/clippy/clippy_lints/src/floating_point_arithmetic/log_division.rs b/src/tools/clippy/clippy_lints/src/floating_point_arithmetic/log_division.rs index 947935369de16..ad0038a4c2423 100644 --- a/src/tools/clippy/clippy_lints/src/floating_point_arithmetic/log_division.rs +++ b/src/tools/clippy/clippy_lints/src/floating_point_arithmetic/log_division.rs @@ -4,18 +4,18 @@ use clippy_utils::{eq_expr_value, sym}; 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 { diff --git a/src/tools/clippy/clippy_lints/src/if_let_mutex.rs b/src/tools/clippy/clippy_lints/src/if_let_mutex.rs index eed2d5d885353..38fda71fccf2f 100644 --- a/src/tools/clippy/clippy_lints/src/if_let_mutex.rs +++ b/src/tools/clippy/clippy_lints/src/if_let_mutex.rs @@ -7,6 +7,7 @@ use rustc_errors::Diag; 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 @@ impl<'tcx> LateLintPass<'tcx> for IfLetMutex { 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 @@ impl<'tcx> LateLintPass<'tcx> for IfLetMutex { 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 { diff --git a/src/tools/clippy/clippy_lints/src/ifs/branches_sharing_code.rs b/src/tools/clippy/clippy_lints/src/ifs/branches_sharing_code.rs index b6e8d047c5cd3..06ebd3ac7f5fd 100644 --- a/src/tools/clippy/clippy_lints/src/ifs/branches_sharing_code.rs +++ b/src/tools/clippy/clippy_lints/src/ifs/branches_sharing_code.rs @@ -13,7 +13,7 @@ use rustc_hir::{Block, Expr, ExprKind, HirId, HirIdSet, ItemKind, LetStmt, Node, 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 { diff --git a/src/tools/clippy/clippy_lints/src/ifs/if_same_then_else.rs b/src/tools/clippy/clippy_lints/src/ifs/if_same_then_else.rs index 69402ec890768..b923559e4b736 100644 --- a/src/tools/clippy/clippy_lints/src/ifs/if_same_then_else.rs +++ b/src/tools/clippy/clippy_lints/src/ifs/if_same_then_else.rs @@ -3,6 +3,7 @@ use clippy_utils::diagnostics::span_lint_and_note; 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, diff --git a/src/tools/clippy/clippy_lints/src/ifs/ifs_same_cond.rs b/src/tools/clippy/clippy_lints/src/ifs/ifs_same_cond.rs index 3ea941320a086..ac010a9314b3c 100644 --- a/src/tools/clippy/clippy_lints/src/ifs/ifs_same_cond.rs +++ b/src/tools/clippy/clippy_lints/src/ifs/ifs_same_cond.rs @@ -4,6 +4,7 @@ use clippy_utils::ty::InteriorMut; 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) } }, ) { diff --git a/src/tools/clippy/clippy_lints/src/ifs/same_functions_in_if_cond.rs b/src/tools/clippy/clippy_lints/src/ifs/same_functions_in_if_cond.rs index 1f6bf04e22e7d..018c2141d4fca 100644 --- a/src/tools/clippy/clippy_lints/src/ifs/same_functions_in_if_cond.rs +++ b/src/tools/clippy/clippy_lints/src/ifs/same_functions_in_if_cond.rs @@ -2,6 +2,7 @@ use clippy_utils::diagnostics::span_lint; 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) { diff --git a/src/tools/clippy/clippy_lints/src/implicit_saturating_add.rs b/src/tools/clippy/clippy_lints/src/implicit_saturating_add.rs index f0a2ad59cd288..06342113b104e 100644 --- a/src/tools/clippy/clippy_lints/src/implicit_saturating_add.rs +++ b/src/tools/clippy/clippy_lints/src/implicit_saturating_add.rs @@ -67,7 +67,7 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingAdd { && 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 diff --git a/src/tools/clippy/clippy_lints/src/implicit_saturating_sub.rs b/src/tools/clippy/clippy_lints/src/implicit_saturating_sub.rs index 45adcfbb030f5..4093ff9f4b2d8 100644 --- a/src/tools/clippy/clippy_lints/src/implicit_saturating_sub.rs +++ b/src/tools/clippy/clippy_lints/src/implicit_saturating_sub.rs @@ -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) diff --git a/src/tools/clippy/clippy_lints/src/inline_trait_bounds.rs b/src/tools/clippy/clippy_lints/src/inline_trait_bounds.rs new file mode 100644 index 0000000000000..99716253c53e7 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/inline_trait_bounds.rs @@ -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() {} + /// ``` + /// + /// Use instead: + /// ```no_run + /// fn foo() 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::>(); + + 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}") +} diff --git a/src/tools/clippy/clippy_lints/src/lib.rs b/src/tools/clippy/clippy_lints/src/lib.rs index 0875982f3bbf1..af25b1f7f1aac 100644 --- a/src/tools/clippy/clippy_lints/src/lib.rs +++ b/src/tools/clippy/clippy_lints/src/lib.rs @@ -168,6 +168,7 @@ mod inherent_impl; 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); diff --git a/src/tools/clippy/clippy_lints/src/loops/char_indices_as_byte_indices.rs b/src/tools/clippy/clippy_lints/src/loops/char_indices_as_byte_indices.rs index 4aae602ea0516..41022e6a5321b 100644 --- a/src/tools/clippy/clippy_lints/src/loops/char_indices_as_byte_indices.rs +++ b/src/tools/clippy/clippy_lints/src/loops/char_indices_as_byte_indices.rs @@ -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" }, diff --git a/src/tools/clippy/clippy_lints/src/loops/manual_while_let_some.rs b/src/tools/clippy/clippy_lints/src/loops/manual_while_let_some.rs index df5e9ffa98635..22c97bcdbd97b 100644 --- a/src/tools/clippy/clippy_lints/src/loops/manual_while_let_some.rs +++ b/src/tools/clippy/clippy_lints/src/loops/manual_while_let_some.rs @@ -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 } diff --git a/src/tools/clippy/clippy_lints/src/loops/needless_range_loop.rs b/src/tools/clippy/clippy_lints/src/loops/needless_range_loop.rs index 3979e668c2cc6..83539b977ee4d 100644 --- a/src/tools/clippy/clippy_lints/src/loops/needless_range_loop.rs +++ b/src/tools/clippy/clippy_lints/src/loops/needless_range_loop.rs @@ -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; diff --git a/src/tools/clippy/clippy_lints/src/manual_abs_diff.rs b/src/tools/clippy/clippy_lints/src/manual_abs_diff.rs index 400cfcd18f2cf..b3c2e93a077fc 100644 --- a/src/tools/clippy/clippy_lints/src/manual_abs_diff.rs +++ b/src/tools/clippy/clippy_lints/src/manual_abs_diff.rs @@ -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 { diff --git a/src/tools/clippy/clippy_lints/src/manual_checked_ops.rs b/src/tools/clippy/clippy_lints/src/manual_checked_ops.rs index 5fd1e27d5b999..0bc1c192b5716 100644 --- a/src/tools/clippy/clippy_lints/src/manual_checked_ops.rs +++ b/src/tools/clippy/clippy_lints/src/manual_checked_ops.rs @@ -5,6 +5,7 @@ use rustc_hir::{AssignOpKind, BinOpKind, Block, Expr, ExprKind}; 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 @@ impl LateLintPass<'_> for ManualCheckedOps { && 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 @@ impl LateLintPass<'_> for ManualCheckedOps { 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 @@ impl LateLintPass<'_> for ManualCheckedOps { 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 @@ impl LateLintPass<'_> for ManualCheckedOps { 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(()); diff --git a/src/tools/clippy/clippy_lints/src/manual_clamp.rs b/src/tools/clippy/clippy_lints/src/manual_clamp.rs index c8fba3e5a7bcc..6848af7748f3f 100644 --- a/src/tools/clippy/clippy_lints/src/manual_clamp.rs +++ b/src/tools/clippy/clippy_lints/src/manual_clamp.rs @@ -15,7 +15,7 @@ use rustc_hir::{Arm, BinOpKind, Block, Expr, ExprKind, HirId, PatKind, PathSegme 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 @@ impl<'tcx> BinaryOp<'tcx> { /// 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> { + #[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 is_clamp_meta_pattern<'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 is_clamp_meta_pattern<'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, diff --git a/src/tools/clippy/clippy_lints/src/manual_is_power_of_two.rs b/src/tools/clippy/clippy_lints/src/manual_is_power_of_two.rs index 4501612540fb3..26e0a6e2a51f8 100644 --- a/src/tools/clippy/clippy_lints/src/manual_is_power_of_two.rs +++ b/src/tools/clippy/clippy_lints/src/manual_is_power_of_two.rs @@ -9,6 +9,7 @@ use rustc_hir::{BinOpKind, Expr, ExprKind, QPath}; 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 diff --git a/src/tools/clippy/clippy_lints/src/manual_pop_if.rs b/src/tools/clippy/clippy_lints/src/manual_pop_if.rs index bf53a8c27a6bb..f279f330a3c0b 100644 --- a/src/tools/clippy/clippy_lints/src/manual_pop_if.rs +++ b/src/tools/clippy/clippy_lints/src/manual_pop_if.rs @@ -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() { diff --git a/src/tools/clippy/clippy_lints/src/manual_retain.rs b/src/tools/clippy/clippy_lints/src/manual_retain.rs index 564d5b53fa70b..bc2e67de99d85 100644 --- a/src/tools/clippy/clippy_lints/src/manual_retain.rs +++ b/src/tools/clippy/clippy_lints/src/manual_retain.rs @@ -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) diff --git a/src/tools/clippy/clippy_lints/src/manual_rotate.rs b/src/tools/clippy/clippy_lints/src/manual_rotate.rs index c371e5b47df82..a3a937ad713d1 100644 --- a/src/tools/clippy/clippy_lints/src/manual_rotate.rs +++ b/src/tools/clippy/clippy_lints/src/manual_rotate.rs @@ -75,7 +75,8 @@ impl LateLintPass<'_> for ManualRotate { && 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 @@ impl LateLintPass<'_> for ManualRotate { { 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 @@ impl LateLintPass<'_> for ManualRotate { }; 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) diff --git a/src/tools/clippy/clippy_lints/src/manual_strip.rs b/src/tools/clippy/clippy_lints/src/manual_strip.rs index e0a60e3747a0f..eaa6539653af3 100644 --- a/src/tools/clippy/clippy_lints/src/manual_strip.rs +++ b/src/tools/clippy/clippy_lints/src/manual_strip.rs @@ -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)) } } diff --git a/src/tools/clippy/clippy_lints/src/manual_take.rs b/src/tools/clippy/clippy_lints/src/manual_take.rs index dd8b0554a9ce4..dec2a641231a0 100644 --- a/src/tools/clippy/clippy_lints/src/manual_take.rs +++ b/src/tools/clippy/clippy_lints/src/manual_take.rs @@ -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 @@ impl LateLintPass<'_> for ManualTake { && 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) diff --git a/src/tools/clippy/clippy_lints/src/matches/collapsible_match.rs b/src/tools/clippy/clippy_lints/src/matches/collapsible_match.rs index d86b05e5c8824..2ee2cabbea077 100644 --- a/src/tools/clippy/clippy_lints/src/matches/collapsible_match.rs +++ b/src/tools/clippy/clippy_lints/src/matches/collapsible_match.rs @@ -14,12 +14,10 @@ use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, Pl 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) { diff --git a/src/tools/clippy/clippy_lints/src/matches/match_same_arms.rs b/src/tools/clippy/clippy_lints/src/matches/match_same_arms.rs index a8312a04f36f8..caef6a0b36377 100644 --- a/src/tools/clippy/clippy_lints/src/matches/match_same_arms.rs +++ b/src/tools/clippy/clippy_lints/src/matches/match_same_arms.rs @@ -13,7 +13,7 @@ use rustc_hir::{Arm, Expr, HirId, HirIdMap, HirIdMapEntry, HirIdSet, Pat, PatExp 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()) diff --git a/src/tools/clippy/clippy_lints/src/matches/mod.rs b/src/tools/clippy/clippy_lints/src/matches/mod.rs index 0e43bba60682c..ae0f3ffa0edc9 100644 --- a/src/tools/clippy/clippy_lints/src/matches/mod.rs +++ b/src/tools/clippy/clippy_lints/src/matches/mod.rs @@ -1138,6 +1138,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches { } 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, diff --git a/src/tools/clippy/clippy_lints/src/matches/needless_match.rs b/src/tools/clippy/clippy_lints/src/matches/needless_match.rs index 699bb41f2dcbe..0151aa8ef15ef 100644 --- a/src/tools/clippy/clippy_lints/src/matches/needless_match.rs +++ b/src/tools/clippy/clippy_lints/src/matches/needless_match.rs @@ -12,10 +12,10 @@ use rustc_hir::{ 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); } } diff --git a/src/tools/clippy/clippy_lints/src/methods/collapsible_str_replace.rs b/src/tools/clippy/clippy_lints/src/methods/collapsible_str_replace.rs index 6d0b944df55d6..ce296d45d605c 100644 --- a/src/tools/clippy/clippy_lints/src/methods/collapsible_str_replace.rs +++ b/src/tools/clippy/clippy_lints/src/methods/collapsible_str_replace.rs @@ -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(()) diff --git a/src/tools/clippy/clippy_lints/src/methods/filter_map.rs b/src/tools/clippy/clippy_lints/src/methods/filter_map.rs index f4b4eed26090e..9ddc3d1bf1d8e 100644 --- a/src/tools/clippy/clippy_lints/src/methods/filter_map.rs +++ b/src/tools/clippy/clippy_lints/src/methods/filter_map.rs @@ -11,8 +11,8 @@ use rustc_hir::{Closure, Expr, ExprKind, PatKind, PathSegment, QPath, UnOp}; 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 @@ impl<'tcx> OffendingFilterExpr<'tcx> { && 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)); } diff --git a/src/tools/clippy/clippy_lints/src/methods/get_last_with_len.rs b/src/tools/clippy/clippy_lints/src/methods/get_last_with_len.rs index 14e40328a4168..bdf8eb3045e07 100644 --- a/src/tools/clippy/clippy_lints/src/methods/get_last_with_len.rs +++ b/src/tools/clippy/clippy_lints/src/methods/get_last_with_len.rs @@ -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() { diff --git a/src/tools/clippy/clippy_lints/src/methods/manual_clear.rs b/src/tools/clippy/clippy_lints/src/methods/manual_clear.rs new file mode 100644 index 0000000000000..dbf1c85add55d --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/manual_clear.rs @@ -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, + ); + }); + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/manual_is_variant_and.rs b/src/tools/clippy/clippy_lints/src/methods/manual_is_variant_and.rs index 472d21977c7ab..824e5aff872e9 100644 --- a/src/tools/clippy/clippy_lints/src/methods/manual_is_variant_and.rs +++ b/src/tools/clippy/clippy_lints/src/methods/manual_is_variant_and.rs @@ -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 { diff --git a/src/tools/clippy/clippy_lints/src/methods/manual_option_zip.rs b/src/tools/clippy/clippy_lints/src/methods/manual_option_zip.rs index 203957c156586..aa23b9deff45b 100644 --- a/src/tools/clippy/clippy_lints/src/methods/manual_option_zip.rs +++ b/src/tools/clippy/clippy_lints/src/methods/manual_option_zip.rs @@ -3,12 +3,11 @@ use clippy_utils::msrvs::{self, Msrv}; 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) diff --git a/src/tools/clippy/clippy_lints/src/methods/mod.rs b/src/tools/clippy/clippy_lints/src/methods/mod.rs index 4fdde52c327e7..a8a2fc55c9019 100644 --- a/src/tools/clippy/clippy_lints/src/methods/mod.rs +++ b/src/tools/clippy/clippy_lints/src/methods/mod.rs @@ -58,6 +58,7 @@ mod join_absolute_paths; 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 should_implement_trait; 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 @@ declare_clippy_lint! { 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 @@ declare_clippy_lint! { "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 @@ impl_lint_pass!(Methods => [ 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 @@ impl_lint_pass!(Methods => [ 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 @@ impl Methods { // 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 @@ impl Methods { (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, diff --git a/src/tools/clippy/clippy_lints/src/methods/no_effect_replace.rs b/src/tools/clippy/clippy_lints/src/methods/no_effect_replace.rs index 9fa51f78c99d7..2afda2956beb6 100644 --- a/src/tools/clippy/clippy_lints/src/methods/no_effect_replace.rs +++ b/src/tools/clippy/clippy_lints/src/methods/no_effect_replace.rs @@ -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"); } } diff --git a/src/tools/clippy/clippy_lints/src/methods/range_zip_with_len.rs b/src/tools/clippy/clippy_lints/src/methods/range_zip_with_len.rs index 7ece83ba7ca39..cf6347d466f2c 100644 --- a/src/tools/clippy/clippy_lints/src/methods/range_zip_with_len.rs +++ b/src/tools/clippy/clippy_lints/src/methods/range_zip_with_len.rs @@ -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, diff --git a/src/tools/clippy/clippy_lints/src/methods/some_filter.rs b/src/tools/clippy/clippy_lints/src/methods/some_filter.rs new file mode 100644 index 0000000000000..f3db0fa165af3 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/some_filter.rs @@ -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", + ); + }, + ); +} diff --git a/src/tools/clippy/clippy_lints/src/methods/unnecessary_iter_cloned.rs b/src/tools/clippy/clippy_lints/src/methods/unnecessary_iter_cloned.rs index 4142f9f75773e..444d0a1d72422 100644 --- a/src/tools/clippy/clippy_lints/src/methods/unnecessary_iter_cloned.rs +++ b/src/tools/clippy/clippy_lints/src/methods/unnecessary_iter_cloned.rs @@ -66,7 +66,7 @@ pub fn check_for_loop_iter( 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); }, _ => {}, } diff --git a/src/tools/clippy/clippy_lints/src/misc.rs b/src/tools/clippy/clippy_lints/src/misc.rs index c77b87268910b..0bfb28d6b8bf4 100644 --- a/src/tools/clippy/clippy_lints/src/misc.rs +++ b/src/tools/clippy/clippy_lints/src/misc.rs @@ -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), }) } diff --git a/src/tools/clippy/clippy_lints/src/missing_asserts_for_indexing.rs b/src/tools/clippy/clippy_lints/src/missing_asserts_for_indexing.rs index b5becbdeb30d9..885e0c4ed527d 100644 --- a/src/tools/clippy/clippy_lints/src/missing_asserts_for_indexing.rs +++ b/src/tools/clippy/clippy_lints/src/missing_asserts_for_indexing.rs @@ -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 { diff --git a/src/tools/clippy/clippy_lints/src/needless_bool.rs b/src/tools/clippy/clippy_lints/src/needless_bool.rs index 064c1b8909fb5..7da507c6d395e 100644 --- a/src/tools/clippy/clippy_lints/src/needless_bool.rs +++ b/src/tools/clippy/clippy_lints/src/needless_bool.rs @@ -10,6 +10,7 @@ use rustc_errors::Applicability; 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 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBool { } 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); diff --git a/src/tools/clippy/clippy_lints/src/non_canonical_impls.rs b/src/tools/clippy/clippy_lints/src/non_canonical_impls.rs index 46d1be80b50a9..4729270d6a92d 100644 --- a/src/tools/clippy/clippy_lints/src/non_canonical_impls.rs +++ b/src/tools/clippy/clippy_lints/src/non_canonical_impls.rs @@ -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 @@ impl LateLintPass<'_> for NonCanonicalImpls { 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 @@ impl LateLintPass<'_> for NonCanonicalImpls { } } -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 diff --git a/src/tools/clippy/clippy_lints/src/operators/assign_op_pattern.rs b/src/tools/clippy/clippy_lints/src/operators/assign_op_pattern.rs index 2d303e40bd1c8..5695779425f4a 100644 --- a/src/tools/clippy/clippy_lints/src/operators/assign_op_pattern.rs +++ b/src/tools/clippy/clippy_lints/src/operators/assign_op_pattern.rs @@ -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 diff --git a/src/tools/clippy/clippy_lints/src/operators/const_comparisons.rs b/src/tools/clippy/clippy_lints/src/operators/const_comparisons.rs index e5a5b46b7b2e4..85128f9114e6b 100644 --- a/src/tools/clippy/clippy_lints/src/operators/const_comparisons.rs +++ b/src/tools/clippy/clippy_lints/src/operators/const_comparisons.rs @@ -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() diff --git a/src/tools/clippy/clippy_lints/src/operators/double_comparison.rs b/src/tools/clippy/clippy_lints/src/operators/double_comparison.rs index a40a724d2da5f..88cd2d11999b0 100644 --- a/src/tools/clippy/clippy_lints/src/operators/double_comparison.rs +++ b/src/tools/clippy/clippy_lints/src/operators/double_comparison.rs @@ -11,8 +11,9 @@ use super::DOUBLE_COMPARISONS; 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 diff --git a/src/tools/clippy/clippy_lints/src/operators/eq_op.rs b/src/tools/clippy/clippy_lints/src/operators/eq_op.rs index d79101a687df4..8e086cea7d91d 100644 --- a/src/tools/clippy/clippy_lints/src/operators/eq_op.rs +++ b/src/tools/clippy/clippy_lints/src/operators/eq_op.rs @@ -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, diff --git a/src/tools/clippy/clippy_lints/src/operators/manual_div_ceil.rs b/src/tools/clippy/clippy_lints/src/operators/manual_div_ceil.rs index 304c51ba2627d..57f5a046cb061 100644 --- a/src/tools/clippy/clippy_lints/src/operators/manual_div_ceil.rs +++ b/src/tools/clippy/clippy_lints/src/operators/manual_div_ceil.rs @@ -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<'_>, diff --git a/src/tools/clippy/clippy_lints/src/operators/misrefactored_assign_op.rs b/src/tools/clippy/clippy_lints/src/operators/misrefactored_assign_op.rs index 8daedd1c90140..f0b6407a141bd 100644 --- a/src/tools/clippy/clippy_lints/src/operators/misrefactored_assign_op.rs +++ b/src/tools/clippy/clippy_lints/src/operators/misrefactored_assign_op.rs @@ -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); } diff --git a/src/tools/clippy/clippy_lints/src/operators/self_assignment.rs b/src/tools/clippy/clippy_lints/src/operators/self_assignment.rs index a932378fbb529..2054cf6ac5887 100644 --- a/src/tools/clippy/clippy_lints/src/operators/self_assignment.rs +++ b/src/tools/clippy/clippy_lints/src/operators/self_assignment.rs @@ -7,7 +7,7 @@ use rustc_lint::LateContext; 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, ""); let rhs = snippet(cx, rhs.span, ""); span_lint( diff --git a/src/tools/clippy/clippy_lints/src/panicking_overflow_checks.rs b/src/tools/clippy/clippy_lints/src/panicking_overflow_checks.rs index bc1821a48a340..7e75e0affc729 100644 --- a/src/tools/clippy/clippy_lints/src/panicking_overflow_checks.rs +++ b/src/tools/clippy/clippy_lints/src/panicking_overflow_checks.rs @@ -72,7 +72,7 @@ impl<'tcx> LateLintPass<'tcx> for PanickingOverflowChecks { && 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, diff --git a/src/tools/clippy/clippy_lints/src/question_mark.rs b/src/tools/clippy/clippy_lints/src/question_mark.rs index f07cc10cbced5..cbfd1af1907b7 100644 --- a/src/tools/clippy/clippy_lints/src/question_mark.rs +++ b/src/tools/clippy/clippy_lints/src/question_mark.rs @@ -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) diff --git a/src/tools/clippy/clippy_lints/src/returns/needless_return_with_question_mark.rs b/src/tools/clippy/clippy_lints/src/returns/needless_return_with_question_mark.rs index c47a3ef21e869..7c55150db10d5 100644 --- a/src/tools/clippy/clippy_lints/src/returns/needless_return_with_question_mark.rs +++ b/src/tools/clippy/clippy_lints/src/returns/needless_return_with_question_mark.rs @@ -3,7 +3,7 @@ use clippy_utils::res::{MaybeDef, MaybeQPath}; 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 diff --git a/src/tools/clippy/clippy_lints/src/same_length_and_capacity.rs b/src/tools/clippy/clippy_lints/src/same_length_and_capacity.rs index ebf649c24307e..1d5d960665ef6 100644 --- a/src/tools/clippy/clippy_lints/src/same_length_and_capacity.rs +++ b/src/tools/clippy/clippy_lints/src/same_length_and_capacity.rs @@ -79,7 +79,7 @@ impl<'tcx> LateLintPass<'tcx> for SameLengthAndCapacity { && 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) { diff --git a/src/tools/clippy/clippy_lints/src/set_contains_or_insert.rs b/src/tools/clippy/clippy_lints/src/set_contains_or_insert.rs index 7482bac4c7b4d..4aba49071ee56 100644 --- a/src/tools/clippy/clippy_lints/src/set_contains_or_insert.rs +++ b/src/tools/clippy/clippy_lints/src/set_contains_or_insert.rs @@ -7,8 +7,8 @@ use clippy_utils::{SpanlessEq, higher, peel_hir_expr_while, sym}; 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> { 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)); } diff --git a/src/tools/clippy/clippy_lints/src/slow_vector_initialization.rs b/src/tools/clippy/clippy_lints/src/slow_vector_initialization.rs index c99f2e2fb9424..ce74a9bea57f7 100644 --- a/src/tools/clippy/clippy_lints/src/slow_vector_initialization.rs +++ b/src/tools/clippy/clippy_lints/src/slow_vector_initialization.rs @@ -8,6 +8,7 @@ use rustc_hir::intravisit::{Visitor, walk_block, walk_expr, walk_stmt}; 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 @@ impl<'tcx> VectorInitializationVisitor<'_, '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 @@ impl<'tcx> VectorInitializationVisitor<'_, 'tcx> { { 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); } diff --git a/src/tools/clippy/clippy_lints/src/strings.rs b/src/tools/clippy/clippy_lints/src/strings.rs index 61b5842ea542a..df3ca09dd76df 100644 --- a/src/tools/clippy/clippy_lints/src/strings.rs +++ b/src/tools/clippy/clippy_lints/src/strings.rs @@ -11,7 +11,7 @@ use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, LangItem, Node}; 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 @@ declare_lint_pass!(TrimSplitWhitespace => [TRIM_SPLIT_WHITESPACE]); 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 @@ impl<'tcx> LateLintPass<'tcx> for StringAdd { 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 @@ impl<'tcx> LateLintPass<'tcx> for StringAdd { "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, } } diff --git a/src/tools/clippy/clippy_lints/src/swap.rs b/src/tools/clippy/clippy_lints/src/swap.rs index 262612a2a2d3c..871c59d52659e 100644 --- a/src/tools/clippy/clippy_lints/src/swap.rs +++ b/src/tools/clippy/clippy_lints/src/swap.rs @@ -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 { diff --git a/src/tools/clippy/clippy_lints/src/trait_bounds.rs b/src/tools/clippy/clippy_lints/src/trait_bounds.rs index 13ff3914f8e0a..7b690b7eb136c 100644 --- a/src/tools/clippy/clippy_lints/src/trait_bounds.rs +++ b/src/tools/clippy/clippy_lints/src/trait_bounds.rs @@ -15,7 +15,7 @@ use rustc_hir::{ }; 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 @@ impl<'tcx> LateLintPass<'tcx> for TraitBounds { .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 @@ impl TraitBounds { 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<'_, '_> {} diff --git a/src/tools/clippy/clippy_lints/src/transmute/eager_transmute.rs b/src/tools/clippy/clippy_lints/src/transmute/eager_transmute.rs index 97e68b3df94e4..05c0af9228543 100644 --- a/src/tools/clippy/clippy_lints/src/transmute/eager_transmute.rs +++ b/src/tools/clippy/clippy_lints/src/transmute/eager_transmute.rs @@ -5,6 +5,7 @@ use rustc_errors::Applicability; 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), } } diff --git a/src/tools/clippy/clippy_lints/src/uninit_vec.rs b/src/tools/clippy/clippy_lints/src/uninit_vec.rs index df06982904b37..9449d44c009e5 100644 --- a/src/tools/clippy/clippy_lints/src/uninit_vec.rs +++ b/src/tools/clippy/clippy_lints/src/uninit_vec.rs @@ -7,7 +7,7 @@ use rustc_hir::{Block, Expr, ExprKind, HirId, PatKind, PathSegment, Stmt, StmtKi 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 @@ declare_lint_pass!(UninitVec => [UNINIT_VEC]); // 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 @@ impl<'tcx> LateLintPass<'tcx> for UninitVec { 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), } } } diff --git a/src/tools/clippy/clippy_lints_internal/src/collapsible_span_lint_calls.rs b/src/tools/clippy/clippy_lints_internal/src/collapsible_span_lint_calls.rs index bbcef0856ba4d..bac05acebf344 100644 --- a/src/tools/clippy/clippy_lints_internal/src/collapsible_span_lint_calls.rs +++ b/src/tools/clippy/clippy_lints_internal/src/collapsible_span_lint_calls.rs @@ -89,10 +89,10 @@ impl<'tcx> LateLintPass<'tcx> for CollapsibleCalls { && let ExprKind::Path(..) = recv.kind { let mut app = Applicability::MachineApplicable; - let expr_ctxt = expr.span.ctxt(); + let ctxt = expr.span.ctxt(); let and_then_snippets = get_and_then_snippets( cx, - expr_ctxt, + ctxt, call_cx.span, call_lint.span, call_sp.span, @@ -101,28 +101,24 @@ impl<'tcx> LateLintPass<'tcx> for CollapsibleCalls { ); let mut sle = SpanlessEq::new(cx).deny_side_effects(); match ps.ident.name { - sym::span_suggestion if sle.eq_expr(call_sp, &span_call_args[0]) => { - let snippets = span_suggestion_snippets(cx, expr_ctxt, span_call_args, &mut app); + sym::span_suggestion if sle.eq_expr(ctxt, call_sp, &span_call_args[0]) => { + let snippets = span_suggestion_snippets(cx, ctxt, span_call_args, &mut app); suggest_suggestion(cx, expr, &and_then_snippets, &snippets, app); }, - sym::span_help if sle.eq_expr(call_sp, &span_call_args[0]) => { - let help_snippet = - snippet_with_context(cx, span_call_args[1].span, expr_ctxt, r#""...""#, &mut app).0; + sym::span_help if sle.eq_expr(ctxt, call_sp, &span_call_args[0]) => { + let help_snippet = snippet_with_context(cx, span_call_args[1].span, ctxt, r#""...""#, &mut app).0; suggest_help(cx, expr, &and_then_snippets, help_snippet.borrow(), true, app); }, - sym::span_note if sle.eq_expr(call_sp, &span_call_args[0]) => { - let note_snippet = - snippet_with_context(cx, span_call_args[1].span, expr_ctxt, r#""...""#, &mut app).0; + sym::span_note if sle.eq_expr(ctxt, call_sp, &span_call_args[0]) => { + let note_snippet = snippet_with_context(cx, span_call_args[1].span, ctxt, r#""...""#, &mut app).0; suggest_note(cx, expr, &and_then_snippets, note_snippet.borrow(), true, app); }, sym::help => { - let help_snippet = - snippet_with_context(cx, span_call_args[0].span, expr_ctxt, r#""...""#, &mut app).0; + let help_snippet = snippet_with_context(cx, span_call_args[0].span, ctxt, r#""...""#, &mut app).0; suggest_help(cx, expr, &and_then_snippets, help_snippet.borrow(), false, app); }, sym::note => { - let note_snippet = - snippet_with_context(cx, span_call_args[0].span, expr_ctxt, r#""...""#, &mut app).0; + let note_snippet = snippet_with_context(cx, span_call_args[0].span, ctxt, r#""...""#, &mut app).0; suggest_note(cx, expr, &and_then_snippets, note_snippet.borrow(), false, app); }, _ => (), @@ -140,7 +136,7 @@ struct AndThenSnippets { fn get_and_then_snippets( cx: &LateContext<'_>, - expr_ctxt: SyntaxContext, + ctxt: SyntaxContext, cx_span: Span, lint_span: Span, span_span: Span, @@ -150,7 +146,7 @@ fn get_and_then_snippets( let cx_snippet = snippet_with_applicability(cx, cx_span, "cx", app); let lint_snippet = snippet_with_applicability(cx, lint_span, "..", app); let span_snippet = snippet_with_applicability(cx, span_span, "span", app); - let msg_snippet = snippet_with_context(cx, msg_span, expr_ctxt, r#""...""#, app).0; + let msg_snippet = snippet_with_context(cx, msg_span, ctxt, r#""...""#, app).0; AndThenSnippets { cx: cx_snippet, @@ -168,12 +164,12 @@ struct SpanSuggestionSnippets { fn span_suggestion_snippets<'hir>( cx: &LateContext<'_>, - expr_ctxt: SyntaxContext, + ctxt: SyntaxContext, span_call_args: &'hir [Expr<'hir>], app: &mut Applicability, ) -> SpanSuggestionSnippets { - let help_snippet = snippet_with_context(cx, span_call_args[1].span, expr_ctxt, r#""...""#, app).0; - let sugg_snippet = snippet_with_context(cx, span_call_args[2].span, expr_ctxt, "..", app).0; + let help_snippet = snippet_with_context(cx, span_call_args[1].span, ctxt, r#""...""#, app).0; + let sugg_snippet = snippet_with_context(cx, span_call_args[2].span, ctxt, "..", app).0; let applicability_snippet = snippet_with_applicability(cx, span_call_args[3].span, "Applicability::MachineApplicable", app); diff --git a/src/tools/clippy/clippy_lints_internal/src/repeated_is_diagnostic_item.rs b/src/tools/clippy/clippy_lints_internal/src/repeated_is_diagnostic_item.rs index 4492e3539ffb1..b300dfa27b0eb 100644 --- a/src/tools/clippy/clippy_lints_internal/src/repeated_is_diagnostic_item.rs +++ b/src/tools/clippy/clippy_lints_internal/src/repeated_is_diagnostic_item.rs @@ -152,6 +152,7 @@ impl<'tcx> LateLintPass<'tcx> for RepeatedIsDiagnosticItem { ) { let lint_span = stmt1_span.to(stmt2_span); + let ctxt = lint_span.ctxt(); // if recv1.is_diag_item(cx, sym1) && .. { // .. @@ -161,8 +162,8 @@ impl<'tcx> LateLintPass<'tcx> for RepeatedIsDiagnosticItem { // } if let Some(first @ (span1, (cx1, recv1, _))) = extract_nested_is_diag_item(cx, cond1) && let Some(second @ (span2, (cx2, recv2, _))) = extract_nested_is_diag_item(cx, cond2) - && eq_expr_value(cx, cx1, cx2) - && eq_expr_value(cx, recv1, recv2) + && eq_expr_value(cx, ctxt, cx1, cx2) + && eq_expr_value(cx, ctxt, recv1, recv2) { let recv_ty = with_forced_trimmed_paths!(format!("{}", cx.typeck_results().expr_ty_adjusted(recv1).peel_refs())); @@ -208,8 +209,8 @@ impl<'tcx> LateLintPass<'tcx> for RepeatedIsDiagnosticItem { // } if let Some(first @ (span1, (tcx1, did1, _))) = extract_nested_is_diagnostic_item(cx, cond1) && let Some(second @ (span2, (tcx2, did2, _))) = extract_nested_is_diagnostic_item(cx, cond2) - && eq_expr_value(cx, tcx1, tcx2) - && eq_expr_value(cx, did1, did2) + && eq_expr_value(cx, ctxt, tcx1, tcx2) + && eq_expr_value(cx, ctxt, did1, did2) { span_lint_and_then( cx, @@ -264,11 +265,13 @@ impl<'tcx> LateLintPass<'tcx> for RepeatedIsDiagnosticItem { } fn check_ors(cx: &LateContext<'_>, span: Span, left: &Expr<'_>, right: &Expr<'_>) { + let ctxt = span.ctxt(); + // recv1.is_diag_item(cx, sym1) || recv2.is_diag_item(cx, sym2) if let Some((cx1, recv1, sym1)) = extract_is_diag_item(cx, left) && let Some((cx2, recv2, sym2)) = extract_is_diag_item(cx, right) - && eq_expr_value(cx, cx1, cx2) - && eq_expr_value(cx, recv1, recv2) + && eq_expr_value(cx, ctxt, cx1, cx2) + && eq_expr_value(cx, ctxt, recv1, recv2) { let recv_ty = with_forced_trimmed_paths!(format!("{}", cx.typeck_results().expr_ty_adjusted(recv1).peel_refs())); @@ -300,8 +303,8 @@ fn check_ors(cx: &LateContext<'_>, span: Span, left: &Expr<'_>, right: &Expr<'_> // cx.tcx.is_diagnostic_item(sym1, did) || cx.tcx.is_diagnostic_item(sym2, did) if let Some((tcx1, did1, sym1)) = extract_is_diagnostic_item(cx, left) && let Some((tcx2, did2, sym2)) = extract_is_diagnostic_item(cx, right) - && eq_expr_value(cx, tcx1, tcx2) - && eq_expr_value(cx, did1, did2) + && eq_expr_value(cx, ctxt, tcx1, tcx2) + && eq_expr_value(cx, ctxt, did1, did2) { span_lint_and_then( cx, @@ -328,11 +331,13 @@ fn check_ors(cx: &LateContext<'_>, span: Span, left: &Expr<'_>, right: &Expr<'_> } fn check_ands(cx: &LateContext<'_>, span: Span, left: &Expr<'_>, right: &Expr<'_>) { + let ctxt = span.ctxt(); + // !recv1.is_diag_item(cx, sym1) && !recv2.is_diag_item(cx, sym2) if let Some((cx1, recv1, sym1)) = extract_is_diag_item(cx, left) && let Some((cx2, recv2, sym2)) = extract_is_diag_item(cx, right) - && eq_expr_value(cx, cx1, cx2) - && eq_expr_value(cx, recv1, recv2) + && eq_expr_value(cx, ctxt, cx1, cx2) + && eq_expr_value(cx, ctxt, recv1, recv2) { let recv_ty = with_forced_trimmed_paths!(format!("{}", cx.typeck_results().expr_ty_adjusted(recv1).peel_refs())); @@ -364,8 +369,8 @@ fn check_ands(cx: &LateContext<'_>, span: Span, left: &Expr<'_>, right: &Expr<'_ // !cx.tcx.is_diagnostic_item(sym1, did) && !cx.tcx.is_diagnostic_item(sym2, did) if let Some((tcx1, did1, sym1)) = extract_is_diagnostic_item(cx, left) && let Some((tcx2, did2, sym2)) = extract_is_diagnostic_item(cx, right) - && eq_expr_value(cx, tcx1, tcx2) - && eq_expr_value(cx, did1, did2) + && eq_expr_value(cx, ctxt, tcx1, tcx2) + && eq_expr_value(cx, ctxt, did1, did2) { span_lint_and_then( cx, @@ -400,9 +405,10 @@ fn check_if_chains<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, conds: Vec<&'t // .. // } let mut found = conds.iter().filter_map(|cond| extract_nested_is_diag_item(cx, cond)); + let ctxt = expr.span.ctxt(); if let Some(first @ (_, (cx_1, recv1, _))) = found.next() - && let other = - found.filter(|(_, (cx_, recv, _))| eq_expr_value(cx, cx_, cx_1) && eq_expr_value(cx, recv, recv1)) + && let other = found + .filter(|(_, (cx_, recv, _))| eq_expr_value(cx, ctxt, cx_, cx_1) && eq_expr_value(cx, ctxt, recv, recv1)) && let results = iter::once(first).chain(other).collect::>() && results.len() > 1 { @@ -457,7 +463,8 @@ fn check_if_chains<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, conds: Vec<&'t .into_iter() .filter_map(|cond| extract_nested_is_diagnostic_item(cx, cond)); if let Some(first @ (_, (tcx1, did1, _))) = found.next() - && let other = found.filter(|(_, (tcx, did, _))| eq_expr_value(cx, tcx, tcx1) && eq_expr_value(cx, did, did1)) + && let other = + found.filter(|(_, (tcx, did, _))| eq_expr_value(cx, ctxt, tcx, tcx1) && eq_expr_value(cx, ctxt, did, did1)) && let results = iter::once(first).chain(other).collect::>() && results.len() > 1 { diff --git a/src/tools/clippy/clippy_test_deps/Cargo.lock b/src/tools/clippy/clippy_test_deps/Cargo.lock index 63e41e8abd105..33ee75ad49450 100644 --- a/src/tools/clippy/clippy_test_deps/Cargo.lock +++ b/src/tools/clippy/clippy_test_deps/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "addr2line" -version = "0.24.2" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" dependencies = [ "gimli", ] @@ -34,9 +34,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "backtrace" -version = "0.3.75" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" dependencies = [ "addr2line", "cfg-if", @@ -44,7 +44,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets", + "windows-link", ] [[package]] @@ -177,9 +177,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.31.1" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" [[package]] name = "io-uring" @@ -203,9 +203,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.174" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "lock_api" @@ -234,9 +234,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.4" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", "wasi", @@ -245,9 +245,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.7" +version = "0.37.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" dependencies = [ "memchr", ] @@ -264,15 +264,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.11" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-link", ] [[package]] @@ -426,74 +426,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "windows-sys" -version = "0.59.0" +name = "windows-link" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets", -] +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] -name = "windows-targets" -version = "0.52.6" +name = "windows-sys" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows-link", ] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/src/tools/clippy/clippy_utils/README.md b/src/tools/clippy/clippy_utils/README.md index de7afb7cf13ac..716b13a5e8f0b 100644 --- a/src/tools/clippy/clippy_utils/README.md +++ b/src/tools/clippy/clippy_utils/README.md @@ -8,7 +8,7 @@ This crate is only guaranteed to build with this `nightly` toolchain: ``` -nightly-2026-04-30 +nightly-2026-05-13 ``` diff --git a/src/tools/clippy/clippy_utils/src/hir_utils.rs b/src/tools/clippy/clippy_utils/src/hir_utils.rs index 4d9ddb388f3cb..7590090f9be40 100644 --- a/src/tools/clippy/clippy_utils/src/hir_utils.rs +++ b/src/tools/clippy/clippy_utils/src/hir_utils.rs @@ -2,6 +2,7 @@ use crate::consts::ConstEvalCtxt; use crate::macros::macro_backtrace; use crate::source::{SpanRange, SpanRangeExt, walk_span_to_context}; use crate::{sym, tokenize_with_text}; +use core::mem; use rustc_ast::ast; use rustc_ast::ast::InlineAsmTemplatePiece; use rustc_data_structures::fx::{FxHasher, FxIndexMap}; @@ -107,46 +108,57 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> { /// Use this method to wrap comparisons that may involve inter-expression context. /// See `self.locals`. - pub fn inter_expr(&mut self) -> HirEqInterExpr<'_, 'a, 'tcx> { + pub fn inter_expr(&mut self, ctxt: SyntaxContext) -> HirEqInterExpr<'_, 'a, 'tcx> { HirEqInterExpr { inner: self, - left_ctxt: SyntaxContext::root(), - right_ctxt: SyntaxContext::root(), + eval_ctxt: ctxt, + prev_left_ctxt: ctxt, + prev_right_ctxt: ctxt, locals: HirIdMap::default(), local_items: FxIndexMap::default(), } } - pub fn eq_block(&mut self, left: &Block<'_>, right: &Block<'_>) -> bool { - self.inter_expr().eq_block(left, right) + pub fn eq_block(&mut self, ctxt: SyntaxContext, left: &Block<'_>, right: &Block<'_>) -> bool { + self.inter_expr(ctxt).eq_block(left, right) } - pub fn eq_expr(&mut self, left: &Expr<'_>, right: &Expr<'_>) -> bool { - self.inter_expr().eq_expr(left, right) + pub fn eq_expr(&mut self, ctxt: SyntaxContext, left: &Expr<'_>, right: &Expr<'_>) -> bool { + self.inter_expr(ctxt).eq_expr(left, right) } - pub fn eq_path(&mut self, left: &Path<'_>, right: &Path<'_>) -> bool { - self.inter_expr().eq_path(left, right) + pub fn eq_path(&mut self, ctxt: SyntaxContext, left: &Path<'_>, right: &Path<'_>) -> bool { + self.inter_expr(ctxt).eq_path(left, right) } - pub fn eq_path_segment(&mut self, left: &PathSegment<'_>, right: &PathSegment<'_>) -> bool { - self.inter_expr().eq_path_segment(left, right) + pub fn eq_path_segment(&mut self, ctxt: SyntaxContext, left: &PathSegment<'_>, right: &PathSegment<'_>) -> bool { + self.inter_expr(ctxt).eq_path_segment(left, right) } - pub fn eq_path_segments(&mut self, left: &[PathSegment<'_>], right: &[PathSegment<'_>]) -> bool { - self.inter_expr().eq_path_segments(left, right) + pub fn eq_path_segments( + &mut self, + ctxt: SyntaxContext, + left: &[PathSegment<'_>], + right: &[PathSegment<'_>], + ) -> bool { + self.inter_expr(ctxt).eq_path_segments(left, right) } pub fn eq_modifiers(left: TraitBoundModifiers, right: TraitBoundModifiers) -> bool { - std::mem::discriminant(&left.constness) == std::mem::discriminant(&right.constness) - && std::mem::discriminant(&left.polarity) == std::mem::discriminant(&right.polarity) + mem::discriminant(&left.constness) == mem::discriminant(&right.constness) + && mem::discriminant(&left.polarity) == mem::discriminant(&right.polarity) } } pub struct HirEqInterExpr<'a, 'b, 'tcx> { inner: &'a mut SpanlessEq<'b, 'tcx>, - left_ctxt: SyntaxContext, - right_ctxt: SyntaxContext, + + /// The root context to view each side from. + eval_ctxt: SyntaxContext, + + // Optimization to avoid rechecking the context of desugarings. + prev_left_ctxt: SyntaxContext, + prev_right_ctxt: SyntaxContext, // When binding are declared, the binding ID in the left expression is mapped to the one on the // right. For example, when comparing `{ let x = 1; x + 2 }` and `{ let y = 1; y + 2 }`, @@ -156,7 +168,17 @@ pub struct HirEqInterExpr<'a, 'b, 'tcx> { } impl HirEqInterExpr<'_, '_, '_> { + pub fn set_eval_ctxt(&mut self, ctxt: SyntaxContext) { + self.eval_ctxt = ctxt; + self.prev_left_ctxt = ctxt; + self.prev_right_ctxt = ctxt; + } + pub fn eq_stmt(&mut self, left: &Stmt<'_>, right: &Stmt<'_>) -> bool { + if self.check_ctxt(left.span.ctxt(), right.span.ctxt()) == Some(false) { + return false; + } + match (&left.kind, &right.kind) { (StmtKind::Let(l), StmtKind::Let(r)) => { // This additional check ensures that the type of the locals are equivalent even if the init @@ -372,15 +394,16 @@ impl HirEqInterExpr<'_, '_, '_> { } let lspan = left.span.data(); let rspan = right.span.data(); - if lspan.ctxt != SyntaxContext::root() && rspan.ctxt != SyntaxContext::root() { - // Don't try to check in between statements inside macros. - return over(left.stmts, right.stmts, |left, right| self.eq_stmt(left, right)) - && both(left.expr.as_ref(), right.expr.as_ref(), |left, right| { - self.eq_expr(left, right) - }); - } - if lspan.ctxt != rspan.ctxt { - return false; + match self.check_ctxt(lspan.ctxt, rspan.ctxt) { + Some(false) => return false, + None if self.eval_ctxt.is_root() => {}, + _ => { + // Don't try to check in between statements inside macros. + return over(left.stmts, right.stmts, |left, right| self.eq_stmt(left, right)) + && both(left.expr.as_ref(), right.expr.as_ref(), |left, right| { + self.eq_expr(left, right) + }); + }, } let mut lstart = lspan.lo; @@ -475,26 +498,28 @@ impl HirEqInterExpr<'_, '_, '_> { #[expect(clippy::too_many_lines)] pub fn eq_expr(&mut self, left: &Expr<'_>, right: &Expr<'_>) -> bool { - if !self.check_ctxt(left.span.ctxt(), right.span.ctxt()) { - return false; - } - - if let Some((typeck_lhs, typeck_rhs)) = self.inner.maybe_typeck_results - && typeck_lhs.expr_ty(left) == typeck_rhs.expr_ty(right) - && let (Some(l), Some(r)) = ( - ConstEvalCtxt::with_env(self.inner.cx.tcx, self.inner.cx.typing_env(), typeck_lhs) - .eval_local(left, self.left_ctxt), - ConstEvalCtxt::with_env(self.inner.cx.tcx, self.inner.cx.typing_env(), typeck_rhs) - .eval_local(right, self.right_ctxt), - ) - && l == r - { - return true; + match self.check_ctxt(left.span.ctxt(), right.span.ctxt()) { + None => { + if let Some((typeck_lhs, typeck_rhs)) = self.inner.maybe_typeck_results + && typeck_lhs.expr_ty(left) == typeck_rhs.expr_ty(right) + && let (Some(l), Some(r)) = ( + ConstEvalCtxt::with_env(self.inner.cx.tcx, self.inner.cx.typing_env(), typeck_lhs) + .eval_local(left, self.eval_ctxt), + ConstEvalCtxt::with_env(self.inner.cx.tcx, self.inner.cx.typing_env(), typeck_rhs) + .eval_local(right, self.eval_ctxt), + ) + && l == r + { + return true; + } + }, + Some(false) => return false, + Some(true) => {}, } let is_eq = match ( - reduce_exprkind(self.inner.cx, &left.kind), - reduce_exprkind(self.inner.cx, &right.kind), + reduce_exprkind(self.inner.cx, self.eval_ctxt, &left.kind), + reduce_exprkind(self.inner.cx, self.eval_ctxt, &right.kind), ) { (ExprKind::AddrOf(lb, l_mut, le), ExprKind::AddrOf(rb, r_mut, re)) => { lb == rb && l_mut == r_mut && self.eq_expr(le, re) @@ -542,7 +567,12 @@ impl HirEqInterExpr<'_, '_, '_> { && both(l.ty.as_ref(), r.ty.as_ref(), |l, r| self.eq_ty(l, r)) && self.eq_expr(l.init, r.init) }, - (ExprKind::Lit(l), ExprKind::Lit(r)) => l.node == r.node, + (ExprKind::Lit(l), ExprKind::Lit(r)) => { + if self.check_ctxt(l.span.ctxt(), r.span.ctxt()) == Some(false) { + return false; + } + l.node == r.node + }, (ExprKind::Loop(lb, ll, lls, _), ExprKind::Loop(rb, rl, rls, _)) => { lls == rls && self.eq_block(lb, rb) && both(ll.as_ref(), rl.as_ref(), |l, r| l.ident.name == r.ident.name) @@ -673,7 +703,7 @@ impl HirEqInterExpr<'_, '_, '_> { } fn eq_const_arg(&mut self, left: &ConstArg<'_>, right: &ConstArg<'_>) -> bool { - if !self.check_ctxt(left.span.ctxt(), right.span.ctxt()) { + if self.check_ctxt(left.span.ctxt(), right.span.ctxt()) == Some(false) { return false; } @@ -892,46 +922,72 @@ impl HirEqInterExpr<'_, '_, '_> { || both_some_and(left.ct(), right.ct(), |l, r| self.eq_const_arg(l, r))) } - fn check_ctxt(&mut self, left: SyntaxContext, right: SyntaxContext) -> bool { - if self.left_ctxt == left && self.right_ctxt == right { - return true; - } else if self.left_ctxt == left || self.right_ctxt == right { - // Only one context has changed. This can only happen if the two nodes are written differently. - return false; - } else if left != SyntaxContext::root() { + /// Checks whether either operand is within a macro context, and if so, whether the macro calls + /// are equal. + fn check_ctxt(&mut self, left: SyntaxContext, right: SyntaxContext) -> Option { + let prev_left = mem::replace(&mut self.prev_left_ctxt, left); + let prev_right = mem::replace(&mut self.prev_right_ctxt, right); + + if left == self.eval_ctxt && right == self.eval_ctxt { + None + } else if left == prev_left && right == prev_right { + // Same as the previous context, no need to recheck anything + Some(true) + } else if left == prev_left + || right == prev_right + || left == self.eval_ctxt + || right == self.eval_ctxt + || left.is_root() + || right.is_root() + { + // Either only one context changed, or at least one context is a parent of the + // evaluation context. + // Unfortunately we can't get a span of a metavariable so we have to treat the + // second case as unequal. + Some(false) + } else { + // Walk each context in lockstep up to the evaluation context checking that each + // expansion has the same kind. let mut left_data = left.outer_expn_data(); let mut right_data = right.outer_expn_data(); loop { use TokenKind::{BlockComment, LineComment, Whitespace}; - if left_data.macro_def_id != right_data.macro_def_id - || (matches!( - left_data.kind, - ExpnKind::Macro(MacroKind::Bang, name) - if name == sym::cfg || name == sym::option_env - ) && !eq_span_tokens(self.inner.cx, left_data.call_site, right_data.call_site, |t| { - !matches!(t, Whitespace | LineComment { .. } | BlockComment { .. }) - })) - { - // Either a different chain of macro calls, or different arguments to the `cfg` macro. - return false; + if left_data.macro_def_id != right_data.macro_def_id || left_data.kind != right_data.kind { + return Some(false); + } + let left = left_data.call_site.ctxt(); + let right = right_data.call_site.ctxt(); + if left == self.eval_ctxt && right == self.eval_ctxt { + // Finally if the outermost expansion is a macro call, check if the + // tokens are the same. + if let ExpnKind::Macro(MacroKind::Bang, _) = left_data.kind { + return Some(eq_span_tokens( + self.inner.cx, + left_data.call_site, + right_data.call_site, + |t| !matches!(t, Whitespace | LineComment { .. } | BlockComment { .. }), + )); + } + return Some(true); } - let left_ctxt = left_data.call_site.ctxt(); - let right_ctxt = right_data.call_site.ctxt(); - if left_ctxt == SyntaxContext::root() && right_ctxt == SyntaxContext::root() { - break; + if left == prev_left && right == prev_right { + return Some(true); } - if left_ctxt == SyntaxContext::root() || right_ctxt == SyntaxContext::root() { - // Different lengths for the expansion stack. This can only happen if nodes are written differently, - // or shouldn't be compared to start with. - return false; + if left == prev_left + || right == prev_right + || left == self.eval_ctxt + || right == self.eval_ctxt + || left.is_root() + || right.is_root() + { + // Either there's a different number of expansions, or at least one context is + // a parent of the evaluation context. + return Some(false); } - left_data = left_ctxt.outer_expn_data(); - right_data = right_ctxt.outer_expn_data(); + left_data = left.outer_expn_data(); + right_data = right.outer_expn_data(); } } - self.left_ctxt = left; - self.right_ctxt = right; - true } fn swap_binop<'a>( @@ -970,7 +1026,11 @@ impl HirEqInterExpr<'_, '_, '_> { } /// Some simple reductions like `{ return }` => `return` -fn reduce_exprkind<'hir>(cx: &LateContext<'_>, kind: &'hir ExprKind<'hir>) -> &'hir ExprKind<'hir> { +fn reduce_exprkind<'hir>( + cx: &LateContext<'_>, + eval_ctxt: SyntaxContext, + kind: &'hir ExprKind<'hir>, +) -> &'hir ExprKind<'hir> { if let ExprKind::Block(block, _) = kind { match (block.stmts, block.expr) { // From an `if let` expression without an `else` block. The arm for the implicit wild pattern is an empty @@ -978,17 +1038,20 @@ fn reduce_exprkind<'hir>(cx: &LateContext<'_>, kind: &'hir ExprKind<'hir>) -> &' ([], None) if block.span.is_empty() => &ExprKind::Tup(&[]), // `{}` => `()` ([], None) - if block.span.check_source_text(cx, |src| { - tokenize(src, FrontmatterAllowed::No) - .map(|t| t.kind) - .filter(|t| { - !matches!( - t, - TokenKind::LineComment { .. } | TokenKind::BlockComment { .. } | TokenKind::Whitespace - ) - }) - .eq([TokenKind::OpenBrace, TokenKind::CloseBrace].iter().copied()) - }) => + if block.span.ctxt() != eval_ctxt + || block.span.check_source_text(cx, |src| { + tokenize(src, FrontmatterAllowed::No) + .map(|t| t.kind) + .filter(|t| { + !matches!( + t, + TokenKind::LineComment { .. } + | TokenKind::BlockComment { .. } + | TokenKind::Whitespace + ) + }) + .eq([TokenKind::OpenBrace, TokenKind::CloseBrace].iter().copied()) + }) => { &ExprKind::Tup(&[]) }, @@ -1039,8 +1102,13 @@ pub fn count_eq( } /// Checks if two expressions evaluate to the same value, and don't contain any side effects. -pub fn eq_expr_value(cx: &LateContext<'_>, left: &Expr<'_>, right: &Expr<'_>) -> bool { - SpanlessEq::new(cx).deny_side_effects().eq_expr(left, right) +/// +/// The context argument is the context used to view the two expressions. e.g. when comparing the +/// two arguments in `f(m!(1), m!(2))` the context of the call expression should be used. This is +/// needed to handle the case where two macros expand to the same thing, but the arguments are +/// different. +pub fn eq_expr_value(cx: &LateContext<'_>, ctxt: SyntaxContext, left: &Expr<'_>, right: &Expr<'_>) -> bool { + SpanlessEq::new(cx).deny_side_effects().eq_expr(ctxt, left, right) } /// Returns the segments of a path that might have generic parameters. @@ -1104,7 +1172,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { self.hash_expr(e); } - std::mem::discriminant(&b.rules).hash(&mut self.s); + mem::discriminant(&b.rules).hash(&mut self.s); } #[expect(clippy::too_many_lines)] @@ -1120,11 +1188,11 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { return; } - std::mem::discriminant(&e.kind).hash(&mut self.s); + mem::discriminant(&e.kind).hash(&mut self.s); match &e.kind { ExprKind::AddrOf(kind, m, e) => { - std::mem::discriminant(kind).hash(&mut self.s); + mem::discriminant(kind).hash(&mut self.s); m.hash(&mut self.s); self.hash_expr(e); }, @@ -1141,7 +1209,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { self.hash_expr(r); }, ExprKind::AssignOp(o, l, r) => { - std::mem::discriminant(&o.node).hash(&mut self.s); + mem::discriminant(&o.node).hash(&mut self.s); self.hash_expr(l); self.hash_expr(r); }, @@ -1152,7 +1220,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { self.hash_block(b); }, ExprKind::Binary(op, l, r) => { - std::mem::discriminant(&op.node).hash(&mut self.s); + mem::discriminant(&op.node).hash(&mut self.s); self.hash_expr(l); self.hash_expr(r); }, @@ -1175,7 +1243,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { ExprKind::Closure(Closure { capture_clause, body, .. }) => { - std::mem::discriminant(capture_clause).hash(&mut self.s); + mem::discriminant(capture_clause).hash(&mut self.s); // closures inherit TypeckResults self.hash_expr(self.cx.tcx.hir_body(*body).value); }, @@ -1326,11 +1394,11 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { self.hash_expr(expr); }, ExprKind::Unary(l_op, le) => { - std::mem::discriminant(l_op).hash(&mut self.s); + mem::discriminant(l_op).hash(&mut self.s); self.hash_expr(le); }, ExprKind::UnsafeBinderCast(kind, expr, ty) => { - std::mem::discriminant(kind).hash(&mut self.s); + mem::discriminant(kind).hash(&mut self.s); self.hash_expr(expr); if let Some(ty) = ty { self.hash_ty(ty); @@ -1363,7 +1431,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { } pub fn hash_pat_expr(&mut self, lit: &PatExpr<'_>) { - std::mem::discriminant(&lit.kind).hash(&mut self.s); + mem::discriminant(&lit.kind).hash(&mut self.s); match &lit.kind { PatExprKind::Lit { lit, negated } => { lit.node.hash(&mut self.s); @@ -1374,7 +1442,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { } pub fn hash_ty_pat(&mut self, pat: &TyPat<'_>) { - std::mem::discriminant(&pat.kind).hash(&mut self.s); + mem::discriminant(&pat.kind).hash(&mut self.s); match pat.kind { TyPatKind::Range(s, e) => { self.hash_const_arg(s); @@ -1390,16 +1458,16 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { } pub fn hash_pat(&mut self, pat: &Pat<'_>) { - std::mem::discriminant(&pat.kind).hash(&mut self.s); + mem::discriminant(&pat.kind).hash(&mut self.s); match &pat.kind { PatKind::Missing => unreachable!(), PatKind::Binding(BindingMode(by_ref, mutability), _, _, pat) => { - std::mem::discriminant(by_ref).hash(&mut self.s); + mem::discriminant(by_ref).hash(&mut self.s); if let ByRef::Yes(pi, mu) = by_ref { - std::mem::discriminant(pi).hash(&mut self.s); - std::mem::discriminant(mu).hash(&mut self.s); + mem::discriminant(pi).hash(&mut self.s); + mem::discriminant(mu).hash(&mut self.s); } - std::mem::discriminant(mutability).hash(&mut self.s); + mem::discriminant(mutability).hash(&mut self.s); if let Some(pat) = pat { self.hash_pat(pat); } @@ -1418,12 +1486,12 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { if let Some(e) = e { self.hash_pat_expr(e); } - std::mem::discriminant(i).hash(&mut self.s); + mem::discriminant(i).hash(&mut self.s); }, PatKind::Ref(pat, pi, mu) => { self.hash_pat(pat); - std::mem::discriminant(pi).hash(&mut self.s); - std::mem::discriminant(mu).hash(&mut self.s); + mem::discriminant(pi).hash(&mut self.s); + mem::discriminant(mu).hash(&mut self.s); }, PatKind::Guard(pat, guard) => { self.hash_pat(pat); @@ -1492,12 +1560,12 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { pub fn hash_modifiers(&mut self, modifiers: TraitBoundModifiers) { let TraitBoundModifiers { constness, polarity } = modifiers; - std::mem::discriminant(&polarity).hash(&mut self.s); - std::mem::discriminant(&constness).hash(&mut self.s); + mem::discriminant(&polarity).hash(&mut self.s); + mem::discriminant(&constness).hash(&mut self.s); } pub fn hash_stmt(&mut self, b: &Stmt<'_>) { - std::mem::discriminant(&b.kind).hash(&mut self.s); + mem::discriminant(&b.kind).hash(&mut self.s); match &b.kind { StmtKind::Let(local) => { @@ -1518,14 +1586,14 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { pub fn hash_lifetime(&mut self, lifetime: &Lifetime) { lifetime.ident.name.hash(&mut self.s); - std::mem::discriminant(&lifetime.kind).hash(&mut self.s); + mem::discriminant(&lifetime.kind).hash(&mut self.s); if let LifetimeKind::Param(param_id) = lifetime.kind { param_id.hash(&mut self.s); } } pub fn hash_ty(&mut self, ty: &Ty<'_>) { - std::mem::discriminant(&ty.kind).hash(&mut self.s); + mem::discriminant(&ty.kind).hash(&mut self.s); self.hash_tykind(&ty.kind); } @@ -1564,7 +1632,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { for arg in fn_ptr.decl.inputs { self.hash_ty(arg); } - std::mem::discriminant(&fn_ptr.decl.output).hash(&mut self.s); + mem::discriminant(&fn_ptr.decl.output).hash(&mut self.s); match fn_ptr.decl.output { FnRetTy::DefaultReturn(_) => {}, FnRetTy::Return(ty) => { diff --git a/src/tools/clippy/clippy_utils/src/lib.rs b/src/tools/clippy/clippy_utils/src/lib.rs index 522d35bd6093e..78abf93c6b772 100644 --- a/src/tools/clippy/clippy_utils/src/lib.rs +++ b/src/tools/clippy/clippy_utils/src/lib.rs @@ -469,19 +469,23 @@ pub fn trait_ref_of_method<'tcx>(cx: &LateContext<'tcx>, owner: OwnerId) -> Opti /// this method will return a tuple, composed of a `Vec` /// containing the `Expr`s for `v[0], v[0].a, v[0].a.b, v[0].a.b[x]` /// and an `Expr` for root of them, `v` -fn projection_stack<'a, 'hir>(mut e: &'a Expr<'hir>) -> (Vec<&'a Expr<'hir>>, &'a Expr<'hir>) { +fn projection_stack<'a, 'hir>( + mut e: &'a Expr<'hir>, + ctxt: SyntaxContext, +) -> Option<(Vec<&'a Expr<'hir>>, &'a Expr<'hir>)> { let mut result = vec![]; let root = loop { match e.kind { - ExprKind::Index(ep, _, _) | ExprKind::Field(ep, _) => { + ExprKind::Index(ep, _, _) | ExprKind::Field(ep, _) if e.span.ctxt() == ctxt => { result.push(e); e = ep; }, + ExprKind::Index(..) | ExprKind::Field(..) => return None, _ => break e, } }; result.reverse(); - (result, root) + Some((result, root)) } /// Gets the mutability of the custom deref adjustment, if any. @@ -499,10 +503,14 @@ pub fn expr_custom_deref_adjustment(cx: &LateContext<'_>, e: &Expr<'_>) -> Optio /// Checks if two expressions can be mutably borrowed simultaneously /// and they aren't dependent on borrowing same thing twice -pub fn can_mut_borrow_both(cx: &LateContext<'_>, e1: &Expr<'_>, e2: &Expr<'_>) -> bool { - let (s1, r1) = projection_stack(e1); - let (s2, r2) = projection_stack(e2); - if !eq_expr_value(cx, r1, r2) { +pub fn can_mut_borrow_both(cx: &LateContext<'_>, ctxt: SyntaxContext, e1: &Expr<'_>, e2: &Expr<'_>) -> bool { + let Some((s1, r1)) = projection_stack(e1, ctxt) else { + return false; + }; + let Some((s2, r2)) = projection_stack(e2, ctxt) else { + return false; + }; + if !eq_expr_value(cx, ctxt, r1, r2) { return true; } if expr_custom_deref_adjustment(cx, r1).is_some() || expr_custom_deref_adjustment(cx, r2).is_some() { @@ -520,11 +528,6 @@ pub fn can_mut_borrow_both(cx: &LateContext<'_>, e1: &Expr<'_>, e2: &Expr<'_>) - return true; } }, - (ExprKind::Index(_, i1, _), ExprKind::Index(_, i2, _)) => { - if !eq_expr_value(cx, i1, i2) { - return false; - } - }, _ => return false, } } @@ -1744,13 +1747,7 @@ pub fn any_parent_has_attr(tcx: TyCtxt<'_>, node: HirId, symbol: Symbol) -> bool pub fn in_automatically_derived(tcx: TyCtxt<'_>, id: HirId) -> bool { tcx.hir_parent_owner_iter(id) .filter(|(_, node)| matches!(node, OwnerNode::Item(item) if matches!(item.kind, ItemKind::Impl(_)))) - .any(|(id, _)| { - find_attr!( - tcx, - id.def_id, - AutomaticallyDerived - ) - }) + .any(|(id, _)| find_attr!(tcx, id.def_id, AutomaticallyDerived)) } /// Checks if the given `DefId` matches the `libc` item. diff --git a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs index 0f5e021788c67..900534dcbf513 100644 --- a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs +++ b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs @@ -134,10 +134,11 @@ fn check_rvalue<'tcx>( ) -> McfResult { match rvalue { Rvalue::ThreadLocalRef(_) => Err((span, "cannot access thread local storage in const fn".into())), - Rvalue::Discriminant(place) | Rvalue::Ref(_, _, place) | Rvalue::Reborrow(_, _, place) | Rvalue::RawPtr(_, place) => { - check_place(cx, *place, span, body, msrv) - }, - Rvalue::CopyForDeref(place) => check_place(cx, *place, span, body, msrv), + Rvalue::Discriminant(place) + | Rvalue::Ref(_, _, place) + | Rvalue::Reborrow(_, _, place) + | Rvalue::RawPtr(_, place) + | Rvalue::CopyForDeref(place) => check_place(cx, *place, span, body, msrv), Rvalue::Repeat(operand, _) | Rvalue::Use(operand, _) | Rvalue::WrapUnsafeBinder(operand, _) diff --git a/src/tools/clippy/rust-toolchain.toml b/src/tools/clippy/rust-toolchain.toml index 9992299153e21..397173e05f8ff 100644 --- a/src/tools/clippy/rust-toolchain.toml +++ b/src/tools/clippy/rust-toolchain.toml @@ -1,6 +1,6 @@ [toolchain] # begin autogenerated nightly -channel = "nightly-2026-04-30" +channel = "nightly-2026-05-13" # end autogenerated nightly components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] profile = "minimal" diff --git a/src/tools/clippy/tests/compile-test.rs b/src/tools/clippy/tests/compile-test.rs index c5af6d2d27c4f..e5933d7c7a3f1 100644 --- a/src/tools/clippy/tests/compile-test.rs +++ b/src/tools/clippy/tests/compile-test.rs @@ -89,11 +89,33 @@ fn internal_extern_flags() -> Vec { help: Try adding to dev-dependencies in Cargo.toml\n\ help: Be sure to also add `extern crate ...;` to tests/compile-test.rs", ); - crates + + let mut args: Vec = crates .into_iter() .map(|(name, path)| format!("--extern={name}={path}")) - .chain([format!("-Ldependency={}", deps_path.display())]) - .collect() + .collect(); + + if deps_path.ends_with("deps") { + args.push(format!("-Ldependency={}", deps_path.display())); + } else { + // If the dep_path does not point to `/deps` it very likely means Cargo is using the v2 build-dir + // layout + assert!(deps_path.ends_with("out")); + + // Get a path to `target//build` + let build_dir = { + let mut d = deps_path.to_path_buf(); + d.pop(); // remove `out` + d.pop(); // remove `` + d.pop(); // remove `` + d + }; + + let out_dirs = discover_out_dirs(&build_dir); + args.extend(out_dirs.iter().map(|path| format!("-Ldependency={}", path.display()))); + } + + args } // whether to run internal tests or not @@ -214,8 +236,21 @@ impl TestContext { config.program.envs.push(("RUSTC_ICE".into(), Some("0".into()))); if let Some(host_libs) = option_env!("HOST_LIBS") { - let dep = format!("-Ldependency={}", Path::new(host_libs).join("deps").display()); - config.program.args.push(dep.into()); + let deps_dir = Path::new(host_libs).join("deps"); + + if deps_dir.exists() { + let dep = format!("-Ldependency={}", deps_dir.display()); + config.program.args.push(dep.into()); + } else { + // If `/deps` does not exist, assume Cargo v2 build-dir layout + let build_dir = Path::new(host_libs).join("build"); + let dependencies = discover_out_dirs(&build_dir); + + for dep in dependencies { + let dep = format!("-Ldependency={}", dep.display()); + config.program.args.push(dep.into()); + } + } } if let Some(sysroot) = option_env!("TEST_SYSROOT") { config.program.args.push(format!("--sysroot={sysroot}").into()); @@ -648,3 +683,20 @@ impl LintMetadata { } } } + +/// Gets all of the `out` dirs in a given Cargo `build-dir//build` dir. +fn discover_out_dirs(dir: &Path) -> Vec { + if !dir.exists() { + return Vec::new(); + } + + let read_dir = |path: &Path| path.read_dir().ok().into_iter().flatten().filter_map(Result::ok); + dir.read_dir() + .unwrap_or_else(|e| panic!("Couldn't read {}: {}", dir.display(), e)) + .map(|e| e.unwrap()) + .flat_map(|e| read_dir(&e.path())) + .flat_map(|e| read_dir(&e.path())) + .map(|e| e.path()) + .filter(|path| path.ends_with("out")) + .collect::>() +} diff --git a/src/tools/clippy/tests/ui/inline_trait_bounds.fixed b/src/tools/clippy/tests/ui/inline_trait_bounds.fixed new file mode 100644 index 0000000000000..f9cb6df4288bc --- /dev/null +++ b/src/tools/clippy/tests/ui/inline_trait_bounds.fixed @@ -0,0 +1,94 @@ +#![warn(clippy::inline_trait_bounds)] + +// Free functions + +fn inline_simple() where T: Clone {} +//~^ inline_trait_bounds + +fn inline_multiple() where T: Clone + Copy, U: core::fmt::Debug {} +//~^ inline_trait_bounds + +fn inline_lifetime<'a, 'b>(x: &'a str, y: &'b str) -> &'b str where 'a: 'b { + //~^ inline_trait_bounds + y +} + +#[allow(clippy::multiple_bound_locations)] +fn inline_with_where() +//~^ inline_trait_bounds +where + T: core::fmt::Debug, T: Clone, +{ +} + +fn inline_with_const() where T: Clone {} +//~^ inline_trait_bounds + +fn inline_with_return(val: T) -> T where T: Clone { + //~^ inline_trait_bounds + val +} + +// Trait methods + +trait MyTrait { + fn trait_method_inline(&self) where T: Clone; + //~^ inline_trait_bounds + + fn trait_method_default(&self) where T: Clone + Copy {} + //~^ inline_trait_bounds + + fn trait_method_where(&self) + where + T: Clone; +} + +// Impl methods + +struct MyStruct; + +impl MyStruct { + fn impl_method_inline(&self) where T: Clone {} + //~^ inline_trait_bounds + + fn impl_method_multiple(&self) where T: Clone, U: core::fmt::Debug {} + //~^ inline_trait_bounds +} + +impl MyTrait for MyStruct { + fn trait_method_inline(&self) where T: Clone {} + //~^ inline_trait_bounds + + fn trait_method_default(&self) where T: Clone + Copy {} + //~^ inline_trait_bounds + + fn trait_method_where(&self) + where + T: Clone, + { + } +} + +// Should NOT lint + +fn where_only() +where + T: Clone, +{ +} + +fn no_bounds() {} + +fn no_generics() {} + +struct InlineStruct(T); + +enum InlineEnum { + A(T), +} + +#[allow(invalid_type_param_default)] +//~v inline_trait_bounds +fn with_default_value(x: T) -> T where T: Clone { + x +} diff --git a/src/tools/clippy/tests/ui/inline_trait_bounds.rs b/src/tools/clippy/tests/ui/inline_trait_bounds.rs new file mode 100644 index 0000000000000..86cf740b43f46 --- /dev/null +++ b/src/tools/clippy/tests/ui/inline_trait_bounds.rs @@ -0,0 +1,94 @@ +#![warn(clippy::inline_trait_bounds)] + +// Free functions + +fn inline_simple() {} +//~^ inline_trait_bounds + +fn inline_multiple() {} +//~^ inline_trait_bounds + +fn inline_lifetime<'a: 'b, 'b>(x: &'a str, y: &'b str) -> &'b str { + //~^ inline_trait_bounds + y +} + +#[allow(clippy::multiple_bound_locations)] +fn inline_with_where() +//~^ inline_trait_bounds +where + T: core::fmt::Debug, +{ +} + +fn inline_with_const() {} +//~^ inline_trait_bounds + +fn inline_with_return(val: T) -> T { + //~^ inline_trait_bounds + val +} + +// Trait methods + +trait MyTrait { + fn trait_method_inline(&self); + //~^ inline_trait_bounds + + fn trait_method_default(&self) {} + //~^ inline_trait_bounds + + fn trait_method_where(&self) + where + T: Clone; +} + +// Impl methods + +struct MyStruct; + +impl MyStruct { + fn impl_method_inline(&self) {} + //~^ inline_trait_bounds + + fn impl_method_multiple(&self) {} + //~^ inline_trait_bounds +} + +impl MyTrait for MyStruct { + fn trait_method_inline(&self) {} + //~^ inline_trait_bounds + + fn trait_method_default(&self) {} + //~^ inline_trait_bounds + + fn trait_method_where(&self) + where + T: Clone, + { + } +} + +// Should NOT lint + +fn where_only() +where + T: Clone, +{ +} + +fn no_bounds() {} + +fn no_generics() {} + +struct InlineStruct(T); + +enum InlineEnum { + A(T), +} + +#[allow(invalid_type_param_default)] +//~v inline_trait_bounds +fn with_default_value(x: T) -> T { + x +} diff --git a/src/tools/clippy/tests/ui/inline_trait_bounds.stderr b/src/tools/clippy/tests/ui/inline_trait_bounds.stderr new file mode 100644 index 0000000000000..730e99b17754f --- /dev/null +++ b/src/tools/clippy/tests/ui/inline_trait_bounds.stderr @@ -0,0 +1,162 @@ +error: inline trait bounds used + --> tests/ui/inline_trait_bounds.rs:5:17 + | +LL | fn inline_simple() {} + | ^^^^^^^^^^ + | + = note: `-D clippy::inline-trait-bounds` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::inline_trait_bounds)]` +help: move bounds to a `where` clause + | +LL - fn inline_simple() {} +LL + fn inline_simple() where T: Clone {} + | + +error: inline trait bounds used + --> tests/ui/inline_trait_bounds.rs:8:19 + | +LL | fn inline_multiple() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: move bounds to a `where` clause + | +LL - fn inline_multiple() {} +LL + fn inline_multiple() where T: Clone + Copy, U: core::fmt::Debug {} + | + +error: inline trait bounds used + --> tests/ui/inline_trait_bounds.rs:11:19 + | +LL | fn inline_lifetime<'a: 'b, 'b>(x: &'a str, y: &'b str) -> &'b str { + | ^^^^^^^^^^^^ + | +help: move bounds to a `where` clause + | +LL - fn inline_lifetime<'a: 'b, 'b>(x: &'a str, y: &'b str) -> &'b str { +LL + fn inline_lifetime<'a, 'b>(x: &'a str, y: &'b str) -> &'b str where 'a: 'b { + | + +error: inline trait bounds used + --> tests/ui/inline_trait_bounds.rs:17:21 + | +LL | fn inline_with_where() + | ^^^^^^^^^^ + | +help: move bounds to a `where` clause + | +LL ~ fn inline_with_where() +LL | +LL | where +LL ~ T: core::fmt::Debug, T: Clone, + | + +error: inline trait bounds used + --> tests/ui/inline_trait_bounds.rs:24:21 + | +LL | fn inline_with_const() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: move bounds to a `where` clause + | +LL - fn inline_with_const() {} +LL + fn inline_with_const() where T: Clone {} + | + +error: inline trait bounds used + --> tests/ui/inline_trait_bounds.rs:27:22 + | +LL | fn inline_with_return(val: T) -> T { + | ^^^^^^^^^^ + | +help: move bounds to a `where` clause + | +LL - fn inline_with_return(val: T) -> T { +LL + fn inline_with_return(val: T) -> T where T: Clone { + | + +error: inline trait bounds used + --> tests/ui/inline_trait_bounds.rs:35:27 + | +LL | fn trait_method_inline(&self); + | ^^^^^^^^^^ + | +help: move bounds to a `where` clause + | +LL - fn trait_method_inline(&self); +LL + fn trait_method_inline(&self) where T: Clone; + | + +error: inline trait bounds used + --> tests/ui/inline_trait_bounds.rs:38:28 + | +LL | fn trait_method_default(&self) {} + | ^^^^^^^^^^^^^^^^^ + | +help: move bounds to a `where` clause + | +LL - fn trait_method_default(&self) {} +LL + fn trait_method_default(&self) where T: Clone + Copy {} + | + +error: inline trait bounds used + --> tests/ui/inline_trait_bounds.rs:51:26 + | +LL | fn impl_method_inline(&self) {} + | ^^^^^^^^^^ + | +help: move bounds to a `where` clause + | +LL - fn impl_method_inline(&self) {} +LL + fn impl_method_inline(&self) where T: Clone {} + | + +error: inline trait bounds used + --> tests/ui/inline_trait_bounds.rs:54:28 + | +LL | fn impl_method_multiple(&self) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: move bounds to a `where` clause + | +LL - fn impl_method_multiple(&self) {} +LL + fn impl_method_multiple(&self) where T: Clone, U: core::fmt::Debug {} + | + +error: inline trait bounds used + --> tests/ui/inline_trait_bounds.rs:59:27 + | +LL | fn trait_method_inline(&self) {} + | ^^^^^^^^^^ + | +help: move bounds to a `where` clause + | +LL - fn trait_method_inline(&self) {} +LL + fn trait_method_inline(&self) where T: Clone {} + | + +error: inline trait bounds used + --> tests/ui/inline_trait_bounds.rs:62:28 + | +LL | fn trait_method_default(&self) {} + | ^^^^^^^^^^^^^^^^^ + | +help: move bounds to a `where` clause + | +LL - fn trait_method_default(&self) {} +LL + fn trait_method_default(&self) where T: Clone + Copy {} + | + +error: inline trait bounds used + --> tests/ui/inline_trait_bounds.rs:92:22 + | +LL | fn with_default_value(x: T) -> T { + | ^^^^^^^^^^^^^^^^ + | +help: move bounds to a `where` clause + | +LL - fn with_default_value(x: T) -> T { +LL + fn with_default_value(x: T) -> T where T: Clone { + | + +error: aborting due to 13 previous errors + diff --git a/src/tools/clippy/tests/ui/manual_clear.fixed b/src/tools/clippy/tests/ui/manual_clear.fixed new file mode 100644 index 0000000000000..f09d62970e878 --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_clear.fixed @@ -0,0 +1,54 @@ +#![feature(os_string_truncate)] +#![warn(clippy::manual_clear)] + +use std::collections::VecDeque; +use std::ffi::OsString; + +struct CustomTruncate(String); + +impl CustomTruncate { + fn truncate(&mut self, len: usize) { + self.0.truncate(len); + } + + fn clear(&mut self) { + self.0.clear(); + } +} + +fn main() { + let mut v = vec![1, 2, 3]; + v.clear(); //~ manual_clear + + let mut d: VecDeque = VecDeque::from([1, 2, 3]); + d.clear(); //~ manual_clear + + // lint: macro receiver + macro_rules! get_vec { + ($e:expr) => { + $e + }; + } + get_vec!(v).clear(); //~ manual_clear + + // no lint: other args + v.truncate(1); + + // no lint: `0` from a different context + { + // `0` inside a block expression should not be changed into `clear()` + v.truncate({ 0 }); + } + + // lint: String + let mut s = String::from("abc"); + s.clear(); //~ manual_clear + + // lint: OsString + let mut os = OsString::from("abc"); + os.clear(); //~ manual_clear + + // no lint: custom type + let mut c = CustomTruncate(String::from("abc")); + c.truncate(0); +} diff --git a/src/tools/clippy/tests/ui/manual_clear.rs b/src/tools/clippy/tests/ui/manual_clear.rs new file mode 100644 index 0000000000000..5217b9bcf61d8 --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_clear.rs @@ -0,0 +1,54 @@ +#![feature(os_string_truncate)] +#![warn(clippy::manual_clear)] + +use std::collections::VecDeque; +use std::ffi::OsString; + +struct CustomTruncate(String); + +impl CustomTruncate { + fn truncate(&mut self, len: usize) { + self.0.truncate(len); + } + + fn clear(&mut self) { + self.0.clear(); + } +} + +fn main() { + let mut v = vec![1, 2, 3]; + v.truncate(0); //~ manual_clear + + let mut d: VecDeque = VecDeque::from([1, 2, 3]); + d.truncate(0); //~ manual_clear + + // lint: macro receiver + macro_rules! get_vec { + ($e:expr) => { + $e + }; + } + get_vec!(v).truncate(0); //~ manual_clear + + // no lint: other args + v.truncate(1); + + // no lint: `0` from a different context + { + // `0` inside a block expression should not be changed into `clear()` + v.truncate({ 0 }); + } + + // lint: String + let mut s = String::from("abc"); + s.truncate(0); //~ manual_clear + + // lint: OsString + let mut os = OsString::from("abc"); + os.truncate(0); //~ manual_clear + + // no lint: custom type + let mut c = CustomTruncate(String::from("abc")); + c.truncate(0); +} diff --git a/src/tools/clippy/tests/ui/manual_clear.stderr b/src/tools/clippy/tests/ui/manual_clear.stderr new file mode 100644 index 0000000000000..19a323a415f54 --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_clear.stderr @@ -0,0 +1,64 @@ +error: truncating to zero length + --> tests/ui/manual_clear.rs:21:5 + | +LL | v.truncate(0); + | ^^^^^^^^^^^^^ + | + = note: `-D clippy::manual-clear` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::manual_clear)]` +help: use `clear()` instead + | +LL - v.truncate(0); +LL + v.clear(); + | + +error: truncating to zero length + --> tests/ui/manual_clear.rs:24:5 + | +LL | d.truncate(0); + | ^^^^^^^^^^^^^ + | +help: use `clear()` instead + | +LL - d.truncate(0); +LL + d.clear(); + | + +error: truncating to zero length + --> tests/ui/manual_clear.rs:32:5 + | +LL | get_vec!(v).truncate(0); + | ^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `clear()` instead + | +LL - get_vec!(v).truncate(0); +LL + get_vec!(v).clear(); + | + +error: truncating to zero length + --> tests/ui/manual_clear.rs:45:5 + | +LL | s.truncate(0); + | ^^^^^^^^^^^^^ + | +help: use `clear()` instead + | +LL - s.truncate(0); +LL + s.clear(); + | + +error: truncating to zero length + --> tests/ui/manual_clear.rs:49:5 + | +LL | os.truncate(0); + | ^^^^^^^^^^^^^^ + | +help: use `clear()` instead + | +LL - os.truncate(0); +LL + os.clear(); + | + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/manual_filter.fixed b/src/tools/clippy/tests/ui/manual_filter.fixed index 3e2cebee40fe1..7d7f987ca3621 100644 --- a/src/tools/clippy/tests/ui/manual_filter.fixed +++ b/src/tools/clippy/tests/ui/manual_filter.fixed @@ -2,6 +2,7 @@ #![allow( unused_variables, clippy::question_mark, + clippy::some_filter, clippy::useless_vec, clippy::nonminimal_bool )] diff --git a/src/tools/clippy/tests/ui/manual_filter.rs b/src/tools/clippy/tests/ui/manual_filter.rs index 2b80cb450e058..b15008173e182 100644 --- a/src/tools/clippy/tests/ui/manual_filter.rs +++ b/src/tools/clippy/tests/ui/manual_filter.rs @@ -2,6 +2,7 @@ #![allow( unused_variables, clippy::question_mark, + clippy::some_filter, clippy::useless_vec, clippy::nonminimal_bool )] diff --git a/src/tools/clippy/tests/ui/manual_filter.stderr b/src/tools/clippy/tests/ui/manual_filter.stderr index c5fdf14a9b432..772401a5f6a50 100644 --- a/src/tools/clippy/tests/ui/manual_filter.stderr +++ b/src/tools/clippy/tests/ui/manual_filter.stderr @@ -1,5 +1,5 @@ error: manual implementation of `Option::filter` - --> tests/ui/manual_filter.rs:10:5 + --> tests/ui/manual_filter.rs:11:5 | LL | / match Some(0) { LL | | @@ -14,7 +14,7 @@ LL | | }; = help: to override `-D warnings` add `#[allow(clippy::manual_filter)]` error: manual implementation of `Option::filter` - --> tests/ui/manual_filter.rs:22:5 + --> tests/ui/manual_filter.rs:23:5 | LL | / match Some(1) { LL | | @@ -26,7 +26,7 @@ LL | | }; | |_____^ help: try: `Some(1).filter(|&x| x <= 0)` error: manual implementation of `Option::filter` - --> tests/ui/manual_filter.rs:34:5 + --> tests/ui/manual_filter.rs:35:5 | LL | / match Some(2) { LL | | @@ -38,7 +38,7 @@ LL | | }; | |_____^ help: try: `Some(2).filter(|&x| x <= 0)` error: manual implementation of `Option::filter` - --> tests/ui/manual_filter.rs:46:5 + --> tests/ui/manual_filter.rs:47:5 | LL | / match Some(3) { LL | | @@ -50,7 +50,7 @@ LL | | }; | |_____^ help: try: `Some(3).filter(|&x| x > 0)` error: manual implementation of `Option::filter` - --> tests/ui/manual_filter.rs:59:5 + --> tests/ui/manual_filter.rs:60:5 | LL | / match y { LL | | @@ -62,7 +62,7 @@ LL | | }; | |_____^ help: try: `y.filter(|&x| x <= 0)` error: manual implementation of `Option::filter` - --> tests/ui/manual_filter.rs:72:5 + --> tests/ui/manual_filter.rs:73:5 | LL | / match Some(5) { LL | | @@ -74,7 +74,7 @@ LL | | }; | |_____^ help: try: `Some(5).filter(|&x| x > 0)` error: manual implementation of `Option::filter` - --> tests/ui/manual_filter.rs:84:5 + --> tests/ui/manual_filter.rs:85:5 | LL | / match Some(6) { LL | | @@ -86,7 +86,7 @@ LL | | }; | |_____^ help: try: `Some(6).as_ref().filter(|&x| x > &0)` error: manual implementation of `Option::filter` - --> tests/ui/manual_filter.rs:97:5 + --> tests/ui/manual_filter.rs:98:5 | LL | / match Some(String::new()) { LL | | @@ -98,7 +98,7 @@ LL | | }; | |_____^ help: try: `Some(String::new()).filter(|x| external_cond)` error: manual implementation of `Option::filter` - --> tests/ui/manual_filter.rs:109:5 + --> tests/ui/manual_filter.rs:110:5 | LL | / if let Some(x) = Some(7) { LL | | @@ -109,7 +109,7 @@ LL | | }; | |_____^ help: try: `Some(7).filter(|&x| external_cond)` error: manual implementation of `Option::filter` - --> tests/ui/manual_filter.rs:116:5 + --> tests/ui/manual_filter.rs:117:5 | LL | / match &Some(8) { LL | | @@ -121,7 +121,7 @@ LL | | }; | |_____^ help: try: `Some(8).filter(|&x| x != 0)` error: manual implementation of `Option::filter` - --> tests/ui/manual_filter.rs:128:5 + --> tests/ui/manual_filter.rs:129:5 | LL | / match Some(9) { LL | | @@ -133,7 +133,7 @@ LL | | }; | |_____^ help: try: `Some(9).filter(|&x| x > 10 && x < 100)` error: manual implementation of `Option::filter` - --> tests/ui/manual_filter.rs:155:5 + --> tests/ui/manual_filter.rs:156:5 | LL | / match Some(11) { LL | | @@ -153,7 +153,7 @@ LL ~ }); | error: manual implementation of `Option::filter` - --> tests/ui/manual_filter.rs:200:13 + --> tests/ui/manual_filter.rs:201:13 | LL | let _ = match Some(14) { | _____________^ @@ -166,7 +166,7 @@ LL | | }; | |_____^ help: try: `Some(14).filter(|&x| unsafe { f(x) })` error: manual implementation of `Option::filter` - --> tests/ui/manual_filter.rs:211:13 + --> tests/ui/manual_filter.rs:212:13 | LL | let _ = match Some(15) { | _____________^ @@ -177,7 +177,7 @@ LL | | }; | |_____^ help: try: `Some(15).filter(|&x| unsafe { f(x) })` error: manual implementation of `Option::filter` - --> tests/ui/manual_filter.rs:220:12 + --> tests/ui/manual_filter.rs:221:12 | LL | } else if let Some(x) = Some(16) { | ____________^ @@ -190,31 +190,31 @@ LL | | }; | |_____^ help: try: `{ Some(16).filter(|&x| x % 2 == 0) }` error: manual implementation of `Option::filter` - --> tests/ui/manual_filter.rs:303:9 + --> tests/ui/manual_filter.rs:304:9 | LL | opt.and_then(|x| if x == 0 { None } else { Some(x) }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `filter(|&x| x != 0)` error: manual implementation of `Option::filter` - --> tests/ui/manual_filter.rs:307:9 + --> tests/ui/manual_filter.rs:308:9 | LL | opt.and_then(move |x| if x == y { Some(x) } else { None }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `filter(move |&x| x == y)` error: manual implementation of `Option::filter` - --> tests/ui/manual_filter.rs:311:10 + --> tests/ui/manual_filter.rs:312:10 | LL | opt1.and_then(|s| if s.len() > 2 { Some(s) } else { None }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `filter(|s| s.len() > 2)` error: manual implementation of `Option::filter` - --> tests/ui/manual_filter.rs:317:9 + --> tests/ui/manual_filter.rs:318:9 | LL | opt.and_then(|x| if unsafe { f(x as u32) } { Some(x) } else { None }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `filter(|&x| unsafe { f(x as u32) })` error: manual implementation of `Option::filter` - --> tests/ui/manual_filter.rs:319:9 + --> tests/ui/manual_filter.rs:320:9 | LL | opt.and_then(|x| unsafe { if f(x as u32) { Some(x) } else { None } }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `filter(|&x| unsafe { f(x as u32) })` diff --git a/src/tools/clippy/tests/ui/manual_option_zip.fixed b/src/tools/clippy/tests/ui/manual_option_zip.fixed index c0ab66e19169c..1f217b14ed158 100644 --- a/src/tools/clippy/tests/ui/manual_option_zip.fixed +++ b/src/tools/clippy/tests/ui/manual_option_zip.fixed @@ -116,3 +116,10 @@ impl NotOption { self.0.and_then(f) } } + +fn issue16968() { + let a = Some(1); + + let opts = [1, 2]; + let _ = a.and_then(|a| opts.into_iter().find(|b| *b == a).map(|b| (a, b))); +} diff --git a/src/tools/clippy/tests/ui/manual_option_zip.rs b/src/tools/clippy/tests/ui/manual_option_zip.rs index 578f26cea637d..f04f14cb8d2f3 100644 --- a/src/tools/clippy/tests/ui/manual_option_zip.rs +++ b/src/tools/clippy/tests/ui/manual_option_zip.rs @@ -116,3 +116,10 @@ impl NotOption { self.0.and_then(f) } } + +fn issue16968() { + let a = Some(1); + + let opts = [1, 2]; + let _ = a.and_then(|a| opts.into_iter().find(|b| *b == a).map(|b| (a, b))); +} diff --git a/src/tools/clippy/tests/ui/needless_return_with_question_mark.fixed b/src/tools/clippy/tests/ui/needless_return_with_question_mark.fixed index 2c6faf37717d3..8658192a0468e 100644 --- a/src/tools/clippy/tests/ui/needless_return_with_question_mark.fixed +++ b/src/tools/clippy/tests/ui/needless_return_with_question_mark.fixed @@ -128,3 +128,29 @@ fn general_return() { Ok(()) } } + +async fn async_fn() -> Result<(), ()> { + if true { + Err(())?; + //~^ needless_return_with_question_mark + } + Ok(()) +} + +async fn async_block() -> Result<(), ()> { + async { + if true { + Err(())?; + //~^ needless_return_with_question_mark + } + Ok(()) + } + .await +} + +async fn async_block_final_stmt() -> Result<(), ()> { + async { + return Err(())?; + } + .await +} diff --git a/src/tools/clippy/tests/ui/needless_return_with_question_mark.rs b/src/tools/clippy/tests/ui/needless_return_with_question_mark.rs index b4411fa195619..64c2d5f404d4d 100644 --- a/src/tools/clippy/tests/ui/needless_return_with_question_mark.rs +++ b/src/tools/clippy/tests/ui/needless_return_with_question_mark.rs @@ -128,3 +128,29 @@ fn general_return() { Ok(()) } } + +async fn async_fn() -> Result<(), ()> { + if true { + return Err(())?; + //~^ needless_return_with_question_mark + } + Ok(()) +} + +async fn async_block() -> Result<(), ()> { + async { + if true { + return Err(())?; + //~^ needless_return_with_question_mark + } + Ok(()) + } + .await +} + +async fn async_block_final_stmt() -> Result<(), ()> { + async { + return Err(())?; + } + .await +} diff --git a/src/tools/clippy/tests/ui/needless_return_with_question_mark.stderr b/src/tools/clippy/tests/ui/needless_return_with_question_mark.stderr index 2af0f46a1ad0f..c4ca4003c3221 100644 --- a/src/tools/clippy/tests/ui/needless_return_with_question_mark.stderr +++ b/src/tools/clippy/tests/ui/needless_return_with_question_mark.stderr @@ -13,5 +13,17 @@ error: unneeded `return` statement with `?` operator LL | return Err(())?; | ^^^^^^^ help: remove it -error: aborting due to 2 previous errors +error: unneeded `return` statement with `?` operator + --> tests/ui/needless_return_with_question_mark.rs:134:9 + | +LL | return Err(())?; + | ^^^^^^^ help: remove it + +error: unneeded `return` statement with `?` operator + --> tests/ui/needless_return_with_question_mark.rs:143:13 + | +LL | return Err(())?; + | ^^^^^^^ help: remove it + +error: aborting due to 4 previous errors diff --git a/src/tools/clippy/tests/ui/non_canonical_clone_impl.fixed b/src/tools/clippy/tests/ui/non_canonical_clone_impl.fixed index d9be98de69b17..3e2ffaf014680 100644 --- a/src/tools/clippy/tests/ui/non_canonical_clone_impl.fixed +++ b/src/tools/clippy/tests/ui/non_canonical_clone_impl.fixed @@ -6,8 +6,6 @@ extern crate proc_macros; use proc_macros::inline_macros; -// lint - struct A(u32); impl Clone for A { @@ -31,12 +29,10 @@ impl Clone for B { impl Copy for B {} // do not lint derived (clone's implementation is `*self` here anyway) - #[derive(Clone, Copy)] struct C(u32); // do not lint derived (fr this time) - struct D(u32); #[automatically_derived] @@ -54,7 +50,6 @@ impl Clone for D { impl Copy for D {} // do not lint if clone is not manually implemented - struct E(u32); #[automatically_derived] @@ -83,7 +78,6 @@ impl Clone for F { } // do not lint since copy has more restrictive bounds - #[derive(Eq, PartialEq)] struct Uwu(A); @@ -104,7 +98,7 @@ impl Copy for Uwu {} mod issue12788 { use proc_macros::{external, with_span}; - // lint -- not an external macro + // lint non-external macro inline!( #[derive(Copy)] pub struct A; @@ -114,7 +108,7 @@ mod issue12788 { } ); - // do not lint -- should skip external macros + // do not lint external macros external!( #[derive(Copy)] pub struct B; @@ -126,7 +120,7 @@ mod issue12788 { } ); - // do not lint -- should skip proc macros + // do not lint proc macros #[derive(proc_macro_derive::NonCanonicalClone)] pub struct C; @@ -142,3 +136,36 @@ mod issue12788 { } ); } + +struct N(u32); + +impl Clone for N { + fn clone(&self) -> Self { *self } +} + +impl Copy for N {} + +/// Test for corner cases with `implicit_return` enabled +mod with_implicit_return { + #![warn(clippy::implicit_return)] + #![allow(clippy::needless_return)] + + // Don't lint `return *self` under `implicit_return` + struct G(u32); + + impl Clone for G { + fn clone(&self) -> Self { + return *self; + } + } + + impl Copy for G {} + + struct H(u32); + + impl Clone for H { + fn clone(&self) -> Self { return *self; } + } + + impl Copy for H {} +} diff --git a/src/tools/clippy/tests/ui/non_canonical_clone_impl.rs b/src/tools/clippy/tests/ui/non_canonical_clone_impl.rs index 3db22bdbcf318..abe3883207522 100644 --- a/src/tools/clippy/tests/ui/non_canonical_clone_impl.rs +++ b/src/tools/clippy/tests/ui/non_canonical_clone_impl.rs @@ -6,8 +6,6 @@ extern crate proc_macros; use proc_macros::inline_macros; -// lint - struct A(u32); impl Clone for A { @@ -38,12 +36,10 @@ impl Clone for B { impl Copy for B {} // do not lint derived (clone's implementation is `*self` here anyway) - #[derive(Clone, Copy)] struct C(u32); // do not lint derived (fr this time) - struct D(u32); #[automatically_derived] @@ -61,7 +57,6 @@ impl Clone for D { impl Copy for D {} // do not lint if clone is not manually implemented - struct E(u32); #[automatically_derived] @@ -97,7 +92,6 @@ impl Clone for F { } // do not lint since copy has more restrictive bounds - #[derive(Eq, PartialEq)] struct Uwu(A); @@ -118,7 +112,7 @@ impl Copy for Uwu {} mod issue12788 { use proc_macros::{external, with_span}; - // lint -- not an external macro + // lint non-external macro inline!( #[derive(Copy)] pub struct A; @@ -131,7 +125,7 @@ mod issue12788 { } ); - // do not lint -- should skip external macros + // do not lint external macros external!( #[derive(Copy)] pub struct B; @@ -143,7 +137,7 @@ mod issue12788 { } ); - // do not lint -- should skip proc macros + // do not lint proc macros #[derive(proc_macro_derive::NonCanonicalClone)] pub struct C; @@ -159,3 +153,44 @@ mod issue12788 { } ); } + +struct N(u32); + +impl Clone for N { + fn clone(&self) -> Self { + //~^ non_canonical_clone_impl + { *self } + } +} + +impl Copy for N {} + +/// Test for corner cases with `implicit_return` enabled +mod with_implicit_return { + #![warn(clippy::implicit_return)] + #![allow(clippy::needless_return)] + + // Don't lint `return *self` under `implicit_return` + struct G(u32); + + impl Clone for G { + fn clone(&self) -> Self { + return *self; + } + } + + impl Copy for G {} + + struct H(u32); + + impl Clone for H { + fn clone(&self) -> Self { + //~^ non_canonical_clone_impl + { + return *self; + } + } + } + + impl Copy for H {} +} diff --git a/src/tools/clippy/tests/ui/non_canonical_clone_impl.stderr b/src/tools/clippy/tests/ui/non_canonical_clone_impl.stderr index cf36a8f49f812..3741a7dcce860 100644 --- a/src/tools/clippy/tests/ui/non_canonical_clone_impl.stderr +++ b/src/tools/clippy/tests/ui/non_canonical_clone_impl.stderr @@ -1,5 +1,5 @@ error: non-canonical implementation of `clone` on a `Copy` type - --> tests/ui/non_canonical_clone_impl.rs:14:29 + --> tests/ui/non_canonical_clone_impl.rs:12:29 | LL | fn clone(&self) -> Self { | _____________________________^ @@ -12,7 +12,7 @@ LL | | } = help: to override `-D warnings` add `#[allow(clippy::non_canonical_clone_impl)]` error: unnecessary implementation of `clone_from` on a `Copy` type - --> tests/ui/non_canonical_clone_impl.rs:19:5 + --> tests/ui/non_canonical_clone_impl.rs:17:5 | LL | / fn clone_from(&mut self, source: &Self) { LL | | @@ -22,7 +22,7 @@ LL | | } | |_____^ help: remove it error: non-canonical implementation of `clone` on a `Copy` type - --> tests/ui/non_canonical_clone_impl.rs:87:29 + --> tests/ui/non_canonical_clone_impl.rs:82:29 | LL | fn clone(&self) -> Self { | _____________________________^ @@ -32,7 +32,7 @@ LL | | } | |_____^ help: change this to: `{ *self }` error: unnecessary implementation of `clone_from` on a `Copy` type - --> tests/ui/non_canonical_clone_impl.rs:92:5 + --> tests/ui/non_canonical_clone_impl.rs:87:5 | LL | / fn clone_from(&mut self, source: &Self) { LL | | @@ -42,7 +42,7 @@ LL | | } | |_____^ help: remove it error: non-canonical implementation of `clone` on a `Copy` type - --> tests/ui/non_canonical_clone_impl.rs:127:37 + --> tests/ui/non_canonical_clone_impl.rs:121:37 | LL | fn clone(&self) -> Self { | _____________________________________^ @@ -53,5 +53,27 @@ LL | | } | = note: this error originates in the macro `__inline_mac_mod_issue12788` (in Nightly builds, run with -Z macro-backtrace for more info) -error: aborting due to 5 previous errors +error: non-canonical implementation of `clone` on a `Copy` type + --> tests/ui/non_canonical_clone_impl.rs:160:29 + | +LL | fn clone(&self) -> Self { + | _____________________________^ +LL | | +LL | | { *self } +LL | | } + | |_____^ help: change this to: `{ *self }` + +error: non-canonical implementation of `clone` on a `Copy` type + --> tests/ui/non_canonical_clone_impl.rs:187:33 + | +LL | fn clone(&self) -> Self { + | _________________________________^ +LL | | +LL | | { +LL | | return *self; +LL | | } +LL | | } + | |_________^ help: change this to: `{ return *self; }` + +error: aborting due to 7 previous errors diff --git a/src/tools/clippy/tests/ui/option_filter_map.fixed b/src/tools/clippy/tests/ui/option_filter_map.fixed index d390c41af53cf..b3191c6c1d46d 100644 --- a/src/tools/clippy/tests/ui/option_filter_map.fixed +++ b/src/tools/clippy/tests/ui/option_filter_map.fixed @@ -1,5 +1,5 @@ #![warn(clippy::option_filter_map)] -#![allow(clippy::map_flatten, clippy::unnecessary_map_on_constructor)] +#![allow(clippy::map_flatten, clippy::some_filter, clippy::unnecessary_map_on_constructor)] fn main() { let _ = Some(Some(1)).flatten(); diff --git a/src/tools/clippy/tests/ui/option_filter_map.rs b/src/tools/clippy/tests/ui/option_filter_map.rs index 2d3b983a76707..7d373f353688d 100644 --- a/src/tools/clippy/tests/ui/option_filter_map.rs +++ b/src/tools/clippy/tests/ui/option_filter_map.rs @@ -1,5 +1,5 @@ #![warn(clippy::option_filter_map)] -#![allow(clippy::map_flatten, clippy::unnecessary_map_on_constructor)] +#![allow(clippy::map_flatten, clippy::some_filter, clippy::unnecessary_map_on_constructor)] fn main() { let _ = Some(Some(1)).filter(Option::is_some).map(Option::unwrap); diff --git a/src/tools/clippy/tests/ui/some_filter.fixed b/src/tools/clippy/tests/ui/some_filter.fixed new file mode 100644 index 0000000000000..a214dec920d4e --- /dev/null +++ b/src/tools/clippy/tests/ui/some_filter.fixed @@ -0,0 +1,70 @@ +#![warn(clippy::some_filter)] +#![allow(clippy::const_is_empty)] + +macro_rules! unchanged { + ($result:expr) => { + $result + }; +} + +macro_rules! condition { + ($condition:expr) => { + $condition || false + }; +} + +#[clippy::msrv = "1.61"] +fn older() { + let _ = Some(0).filter(|_| false); +} + +#[clippy::msrv = "1.62"] +fn newer() { + let _ = false.then_some(0); + //~^ some_filter +} + +fn main() { + let _ = false.then_some(0); + //~^ some_filter + + // The condition contains an operator. The program should add parentheses. + let _ = (1 == 0).then_some(0); + //~^ some_filter + + let _ = match 0 { + //~^ some_filter + 0 => false, + 1 => true, + _ => true, + }.then_some(0); + + // The argument to filter requires the value in the option. The program + // can't figure out how to change it. It should leave it alone for now. + let _ = Some(0).filter(|x| *x == 0); + + // The expression is a macro argument. The program should change the macro + // argument. It should not expand the macro. + let _ = unchanged!(false.then_some(0)); + //~^ some_filter + let _ = vec![false].is_empty().then_some(0); + //~^ some_filter + + // The condition is a macro that expands to an expression containing an + // operator. The program should not add parentheses. + let _ = condition!(false).then_some(0); + //~^ some_filter + + (1 == 0).then_some(String::from( + //~^ some_filter + "looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong", + )); + { + //~^ some_filter + "looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong".is_empty() + }.then_some(5); + "looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong".is_empty().then_some(String::from( + //~^ some_filter + "looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong", + )); +} diff --git a/src/tools/clippy/tests/ui/some_filter.rs b/src/tools/clippy/tests/ui/some_filter.rs new file mode 100644 index 0000000000000..eec797536a8f7 --- /dev/null +++ b/src/tools/clippy/tests/ui/some_filter.rs @@ -0,0 +1,74 @@ +#![warn(clippy::some_filter)] +#![allow(clippy::const_is_empty)] + +macro_rules! unchanged { + ($result:expr) => { + $result + }; +} + +macro_rules! condition { + ($condition:expr) => { + $condition || false + }; +} + +#[clippy::msrv = "1.61"] +fn older() { + let _ = Some(0).filter(|_| false); +} + +#[clippy::msrv = "1.62"] +fn newer() { + let _ = Some(0).filter(|_| false); + //~^ some_filter +} + +fn main() { + let _ = Some(0).filter(|_| false); + //~^ some_filter + + // The condition contains an operator. The program should add parentheses. + let _ = Some(0).filter(|_| 1 == 0); + //~^ some_filter + + let _ = Some(0).filter(|_| match 0 { + //~^ some_filter + 0 => false, + 1 => true, + _ => true, + }); + + // The argument to filter requires the value in the option. The program + // can't figure out how to change it. It should leave it alone for now. + let _ = Some(0).filter(|x| *x == 0); + + // The expression is a macro argument. The program should change the macro + // argument. It should not expand the macro. + let _ = unchanged!(Some(0).filter(|_| false)); + //~^ some_filter + let _ = Some(0).filter(|_| vec![false].is_empty()); + //~^ some_filter + + // The condition is a macro that expands to an expression containing an + // operator. The program should not add parentheses. + let _ = Some(0).filter(|_| condition!(false)); + //~^ some_filter + + Some(String::from( + //~^ some_filter + "looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong", + )) + .filter(|_| 1 == 0); + Some(5).filter(|_| { + //~^ some_filter + "looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong".is_empty() + }); + Some(String::from( + //~^ some_filter + "looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong", + )) + .filter(|_| { + "looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong".is_empty() + }); +} diff --git a/src/tools/clippy/tests/ui/some_filter.stderr b/src/tools/clippy/tests/ui/some_filter.stderr new file mode 100644 index 0000000000000..a4f38f49df695 --- /dev/null +++ b/src/tools/clippy/tests/ui/some_filter.stderr @@ -0,0 +1,163 @@ +error: use of `Some(x).filter(|_| predicate)` + --> tests/ui/some_filter.rs:23:13 + | +LL | let _ = Some(0).filter(|_| false); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this change will alter the order in which the condition and the value are evaluated + = note: `-D clippy::some-filter` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::some_filter)]` +help: consider using `bool::then_some` instead + | +LL - let _ = Some(0).filter(|_| false); +LL + let _ = false.then_some(0); + | + +error: use of `Some(x).filter(|_| predicate)` + --> tests/ui/some_filter.rs:28:13 + | +LL | let _ = Some(0).filter(|_| false); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this change will alter the order in which the condition and the value are evaluated +help: consider using `bool::then_some` instead + | +LL - let _ = Some(0).filter(|_| false); +LL + let _ = false.then_some(0); + | + +error: use of `Some(x).filter(|_| predicate)` + --> tests/ui/some_filter.rs:32:13 + | +LL | let _ = Some(0).filter(|_| 1 == 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this change will alter the order in which the condition and the value are evaluated +help: consider using `bool::then_some` instead + | +LL - let _ = Some(0).filter(|_| 1 == 0); +LL + let _ = (1 == 0).then_some(0); + | + +error: use of `Some(x).filter(|_| predicate)` + --> tests/ui/some_filter.rs:35:13 + | +LL | let _ = Some(0).filter(|_| match 0 { + | _____________^ +LL | | +LL | | 0 => false, +LL | | 1 => true, +LL | | _ => true, +LL | | }); + | |______^ + | + = note: this change will alter the order in which the condition and the value are evaluated +help: consider using `bool::then_some` instead + | +LL ~ let _ = match 0 { +LL + +LL + 0 => false, +LL + 1 => true, +LL + _ => true, +LL ~ }.then_some(0); + | + +error: use of `Some(x).filter(|_| predicate)` + --> tests/ui/some_filter.rs:48:24 + | +LL | let _ = unchanged!(Some(0).filter(|_| false)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this change will alter the order in which the condition and the value are evaluated +help: consider using `bool::then_some` instead + | +LL - let _ = unchanged!(Some(0).filter(|_| false)); +LL + let _ = unchanged!(false.then_some(0)); + | + +error: use of `Some(x).filter(|_| predicate)` + --> tests/ui/some_filter.rs:50:13 + | +LL | let _ = Some(0).filter(|_| vec![false].is_empty()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this change will alter the order in which the condition and the value are evaluated +help: consider using `bool::then_some` instead + | +LL - let _ = Some(0).filter(|_| vec![false].is_empty()); +LL + let _ = vec![false].is_empty().then_some(0); + | + +error: use of `Some(x).filter(|_| predicate)` + --> tests/ui/some_filter.rs:55:13 + | +LL | let _ = Some(0).filter(|_| condition!(false)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this change will alter the order in which the condition and the value are evaluated +help: consider using `bool::then_some` instead + | +LL - let _ = Some(0).filter(|_| condition!(false)); +LL + let _ = condition!(false).then_some(0); + | + +error: use of `Some(x).filter(|_| predicate)` + --> tests/ui/some_filter.rs:58:5 + | +LL | / Some(String::from( +LL | | +LL | | "looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong", +LL | | )) +LL | | .filter(|_| 1 == 0); + | |_______________________^ + | + = note: this change will alter the order in which the condition and the value are evaluated +help: consider using `bool::then_some` instead + | +LL ~ (1 == 0).then_some(String::from( +LL + +LL + "looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong", +LL ~ )); + | + +error: use of `Some(x).filter(|_| predicate)` + --> tests/ui/some_filter.rs:63:5 + | +LL | / Some(5).filter(|_| { +LL | | +LL | | "looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong".is_empty() +LL | | }); + | |______^ + | + = note: this change will alter the order in which the condition and the value are evaluated +help: consider using `bool::then_some` instead + | +LL ~ { +LL + +LL + "looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong".is_empty() +LL ~ }.then_some(5); + | + +error: use of `Some(x).filter(|_| predicate)` + --> tests/ui/some_filter.rs:67:5 + | +LL | / Some(String::from( +LL | | +LL | | "looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong", +LL | | )) +LL | | .filter(|_| { +LL | | "looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong".is_empty() +LL | | }); + | |______^ + | + = note: this change will alter the order in which the condition and the value are evaluated +help: consider using `bool::then_some` instead + | +LL ~ "looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong".is_empty().then_some(String::from( +LL + +LL + "looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong", +LL ~ )); + | + +error: aborting due to 10 previous errors + diff --git a/src/tools/clippy/triagebot.toml b/src/tools/clippy/triagebot.toml index 869b8d730507a..3ed0463f8937e 100644 --- a/src/tools/clippy/triagebot.toml +++ b/src/tools/clippy/triagebot.toml @@ -73,6 +73,7 @@ This lint has been nominated for inclusion. message_on_add = [ """\ PR rust-clippy#{number} "{title}" has been nominated for lint inclusion. +Cc: {recipients} """, """\ /poll Approve lint inclusion of rust-clippy#{number}? @@ -93,7 +94,7 @@ users_on_vacation = [ "Alexendoo", "y21", "blyxyas", - "samueltardieu", + "dswij", ] [assign.owners] diff --git a/tests/codegen-llvm/no_builtins-at-crate.rs b/tests/codegen-llvm/no_builtins-at-crate.rs index ba1d31f60c390..de80a925aebc5 100644 --- a/tests/codegen-llvm/no_builtins-at-crate.rs +++ b/tests/codegen-llvm/no_builtins-at-crate.rs @@ -3,11 +3,13 @@ #![no_builtins] #![crate_type = "lib"] +use std::ffi::c_void; + // CHECK: define // CHECK-SAME: @__aeabi_memcpy // CHECK-SAME: #0 #[no_mangle] -pub unsafe extern "C" fn __aeabi_memcpy(dest: *mut u8, src: *const u8, size: usize) { +pub unsafe extern "C" fn __aeabi_memcpy(dest: *mut c_void, src: *const c_void, size: usize) { // CHECK: call // CHECK-SAME: @memcpy( memcpy(dest, src, size); @@ -17,7 +19,7 @@ pub unsafe extern "C" fn __aeabi_memcpy(dest: *mut u8, src: *const u8, size: usi // CHECK-SAME: @memcpy // CHECK-SAME: #0 extern "C" { - pub fn memcpy(dest: *mut u8, src: *const u8, n: usize) -> *mut u8; + pub fn memcpy(dest: *mut c_void, src: *const c_void, n: usize) -> *mut c_void; } // CHECK: attributes #0 diff --git a/tests/rustdoc-html/jump-to-def/assoc-items-extra.rs b/tests/rustdoc-html/jump-to-def/assoc-items-extra.rs new file mode 100644 index 0000000000000..0e15f32048f6b --- /dev/null +++ b/tests/rustdoc-html/jump-to-def/assoc-items-extra.rs @@ -0,0 +1,20 @@ +// Like test `assoc-items.rs` but now utilizing unstable features. +// FIXME: Make use of (m)GCA assoc consts once they no longer ICE! +//@ compile-flags: -Zunstable-options --generate-link-to-definition +#![feature(return_type_notation)] + +//@ has 'src/assoc_items_extra/assoc-items-extra.rs.html' + +trait Trait0 { + fn fn0() -> impl Sized; + fn fn1() -> impl Sized; +} + +fn item() +where + //@ has - '//a[@href="#9"]' 'fn0' + ::fn0(..): Copy, // Item, AssocFn, Resolved + // FIXME: Support this: + //@ !has - '//a[@href="#10"]' 'fn1' + T::fn1(..): Copy, // Item, AssocFn, TypeRelative +{} diff --git a/tests/rustdoc-html/jump-to-def/assoc-items.rs b/tests/rustdoc-html/jump-to-def/assoc-items.rs index a434fa7e6053e..f176b39625605 100644 --- a/tests/rustdoc-html/jump-to-def/assoc-items.rs +++ b/tests/rustdoc-html/jump-to-def/assoc-items.rs @@ -1,58 +1,106 @@ -// This test ensures that patterns also get a link generated. - //@ compile-flags: -Zunstable-options --generate-link-to-definition -#![crate_name = "foo"] - -//@ has 'src/foo/assoc-items.rs.html' +//@ has 'src/assoc_items/assoc-items.rs.html' -pub trait Trait { - type T; +trait Trait0 { + fn fn0(); + fn fn1(); + fn fn2(); + fn fn3(); + const CT0: usize; + const CT1: usize; + type Ty0; + type Ty1; + type Ty2: Bound0; + type Ty3: Bound0; } -pub trait Another { - type T; - const X: u32; + +trait Bound0 { + fn fn0(); + const CT0: usize; } -pub struct Foo; +fn expr() { + //@ has - '//a[@href="#6"]' 'fn0' + let _ = ::fn0; // Expr, AssocFn, Resolved + //@ has - '//a[@href="#7"]' 'fn1' + let _ = T::fn1; // Expr, AssocFn, TypeRelative -impl Foo { - pub fn new() -> Self { Foo } -} + //@ has - '//a[@href="#8"]' 'fn2' + let _ = ::fn2(); // Expr, AssocFn, Resolved + //@ has - '//a[@href="#9"]' 'fn3' + let _ = T::fn3(); // Expr, AssocFn, TypeRelative -pub struct C; + //@ has - '//a[@href="#10"]' 'CT0' + let _ = ::CT0; // Expr, AssocConst, Resolved + //@ has - '//a[@href="#11"]' 'CT1' + let _ = ::CT1; // Expr, AssocConst, TypeRelative -impl C { - pub fn wat() {} -} + //@ has - '//a[@href="#12"]' 'Ty0' + let _: ::Ty0; // Expr, AssocTy, Resolved + // FIXME: Support this: + //@ !has - '//a[@href="#13"]' 'Ty1' + let _: T::Ty1; // Expr, AssocTy, TypeRelative -// These two links must not change and in particular must contain `/derive.`! -//@ has - '//a[@href="{{channel}}/core/fmt/macros/derive.Debug.html"]' 'Debug' -//@ has - '//a[@href="{{channel}}/core/cmp/derive.PartialEq.html"]' 'PartialEq' -#[derive(Debug, PartialEq)] -pub struct Bar; -impl Trait for Bar { - type T = Foo; + //@ has - '//a[@href="#14"]' 'Ty2' + //@ has - '//a[@href="#19"]' 'fn0' + let _ = ::Ty2::fn0(); + + // FIXME: Support this: + //@ !has - '//a[@href="#14"]' 'Ty3' + //@ has - '//a[@href="#20"]' 'CT0' + let _ = T::Ty2::CT0; } -impl Another for Bar { - type T = C; - const X: u32 = 12; + +trait Trait1 { + const CT0: usize; + const CT1: usize; + const CT2: usize; + + fn scope(); } -pub fn bar() { - //@ has - '//a[@href="#20"]' 'new' - ::T::new(); - //@ has - '//a[@href="#26"]' 'wat' - ::T::wat(); - match 12u32 { - //@ has - '//a[@href="#14"]' 'X' - ::X => {} +fn pat() { + //@ has - '//a[@href="#56"]' 'CT0' + if let <() as Trait1>::CT0 = 0 {} // Pat, AssocConst, Resolved + + match 0 { + //@ has - '//a[@href="#57"]' 'CT1' + <() as Trait1>::CT1 => {} // Pat, AssocConst, Resolved _ => {} } } -pub struct Far { - //@ has - '//a[@href="#10"]' 'T' - x: ::T, +impl Trait1 for () { + const CT0: usize = 0; + const CT1: usize = 1; + const CT2: usize = 2; + + fn scope() { + //@ has - '//a[@href="#58"]' 'CT2' + if let Self::CT2 = 0 {} // Pat, AssocConst, TypeRelative + } +} + +trait Trait2 { + const CT0: usize; + type Ty0; + type Ty1; +} + +impl Trait2 for () { + const CT0: usize = 0; + type Ty0 = (); + type Ty1 = (); +} + +struct Item { + //@ has - '//a[@href="#87"]' 'CT0' + f0: [(); <() as Trait2>::CT0], // Item, AssocConst, Resolved + //@ has - '//a[@href="#88"]' 'Ty0' + f1: ::Ty0, // Item, AssocTy, Resolved + // FIXME: Support this: + //@ !has - '//a[@href="#89"]' 'Ty1' + f2: T::Ty1, // Item, AssocTy, TypeRelative } diff --git a/tests/rustdoc-html/jump-to-def/assoc-types.rs b/tests/rustdoc-html/jump-to-def/assoc-types.rs deleted file mode 100644 index f430eaf16389a..0000000000000 --- a/tests/rustdoc-html/jump-to-def/assoc-types.rs +++ /dev/null @@ -1,20 +0,0 @@ -// This test ensures that associated types don't crash rustdoc jump to def. - -//@ compile-flags: -Zunstable-options --generate-link-to-definition - - -#![crate_name = "foo"] - -//@ has 'src/foo/assoc-types.rs.html' - -pub trait Trait { - type Node; -} - -pub fn y() { - struct X(G); - - impl Trait for X { - type Node = G::Node; - } -} diff --git a/tests/rustdoc-html/jump-to-def/no-body-items.rs b/tests/rustdoc-html/jump-to-def/no-body-items.rs deleted file mode 100644 index e74640072146e..0000000000000 --- a/tests/rustdoc-html/jump-to-def/no-body-items.rs +++ /dev/null @@ -1,24 +0,0 @@ -// This test ensures that items with no body don't panic when generating -// jump to def links. - -//@ compile-flags: -Zunstable-options --generate-link-to-definition - -#![crate_name = "foo"] - -//@ has 'src/foo/no-body-items.rs.html' - -pub trait A { - type T; - type U; -} - -impl A for () { - type T = Self::U; - type U = (); -} - -pub trait C { - type X; -} - -pub struct F(pub T::X); diff --git a/tests/rustdoc-ui/generate-link-to-definition/anon-consts.rs b/tests/rustdoc-ui/generate-link-to-definition/anon-consts.rs new file mode 100644 index 0000000000000..3a774b0d9ca99 --- /dev/null +++ b/tests/rustdoc-ui/generate-link-to-definition/anon-consts.rs @@ -0,0 +1,11 @@ +// Ensure that we don't crash on anonymous constants. +// FIXME: Ideally, we would actually support linkifying type-dependent paths in +// anon consts but for now that's disabled until we figure out what the +// proper solution is implementation-wise. +// issue: +//@ check-pass + +fn scope() { + struct Hold; + let _ = X::<{ 0usize.saturating_add(1) }>; +} diff --git a/tests/rustdoc-ui/generate-link-to-definition/items-nested-in-bodies.rs b/tests/rustdoc-ui/generate-link-to-definition/items-nested-in-bodies.rs new file mode 100644 index 0000000000000..7d48e885c3f26 --- /dev/null +++ b/tests/rustdoc-ui/generate-link-to-definition/items-nested-in-bodies.rs @@ -0,0 +1,31 @@ +// When trying to resolve a type-relative path (e.g., `T::Item` where `T` is a type param) in an +// item that's nested inside of a body (e.g., of a function or constant), we once tried to look up +// the definition in the `TypeckResults` of the body owner which is wrong and led to compiler ICEs. +// +// We now make sure to invalidate the `TypeckResults` when crossing a body - item border. +// +// For additional context, `TypeckResults` as returned by queries like `typeck` store the typeck +// results of *bodies* only. In item signatures / non-bodies, there's no equivalent at the time of +// writing, so it's impossible to resolve HIR TypeRelative paths (identified by a `HirId`) to their +// definition (`DefId`) in other parts of the compiler / in tools. + +//@ compile-flags: -Zunstable-options --generate-link-to-definition +//@ check-pass +// issue: + +fn scope() { + struct X(T::Item); + + trait Trait { + type Ty; + + fn func() + where + T::Item: Copy + {} + } + + impl Trait for T { + type Ty = T::Item; + } +} diff --git a/tests/ui/error-codes/E0264.rs b/tests/ui/error-codes/E0264.rs index 855644796ed45..5974eb05e2c4b 100644 --- a/tests/ui/error-codes/E0264.rs +++ b/tests/ui/error-codes/E0264.rs @@ -1,7 +1,7 @@ #![feature(lang_items)] extern "C" { - #[lang = "copy"] + #[lang = "copy"] //~ ERROR E0718 fn copy(); //~ ERROR E0264 } diff --git a/tests/ui/error-codes/E0264.stderr b/tests/ui/error-codes/E0264.stderr index 6442f42e689d6..aa578194d1f28 100644 --- a/tests/ui/error-codes/E0264.stderr +++ b/tests/ui/error-codes/E0264.stderr @@ -1,9 +1,16 @@ +error[E0718]: `copy` lang item must be applied to a trait + --> $DIR/E0264.rs:4:5 + | +LL | #[lang = "copy"] + | ^^^^^^^^^^^^^^^^ attribute should be applied to a trait, not a function + error[E0264]: unknown external lang item: `copy` --> $DIR/E0264.rs:5:5 | LL | fn copy(); | ^^^^^^^^^^ -error: aborting due to 1 previous error +error: aborting due to 2 previous errors -For more information about this error, try `rustc --explain E0264`. +Some errors have detailed explanations: E0264, E0718. +For more information about an error, try `rustc --explain E0264`. diff --git a/tests/ui/foreign/foreign-int-types.rs b/tests/ui/foreign/foreign-int-types.rs index d20a4c96ea0e2..bac9d8c603918 100644 --- a/tests/ui/foreign/foreign-int-types.rs +++ b/tests/ui/foreign/foreign-int-types.rs @@ -4,7 +4,7 @@ mod xx { extern "C" { - pub fn strlen(str: *const u8) -> usize; + pub fn strlen2(str: *const u8) -> usize; pub fn foo(x: isize, y: usize); } } diff --git a/tests/ui/lint/runtime-symbols.rs b/tests/ui/lint/runtime-symbols.rs new file mode 100644 index 0000000000000..813ebff4ff4de --- /dev/null +++ b/tests/ui/lint/runtime-symbols.rs @@ -0,0 +1,54 @@ +// This test checks the runtime symbols lint. + +//@ edition: 2021 +//@ normalize-stderr: "\*const [iu]8" -> "*const U8" + +#![allow(clashing_extern_declarations)] // we are volontary testing differents defs + +use core::ffi::{c_char, c_int, c_void}; + +fn invalid() { + #[no_mangle] + pub extern "C" fn memcpy(dest: *mut c_void, src: *const c_void, n: i64) -> *mut c_void { + std::ptr::null_mut() + } + //~^^^ ERROR invalid definition of the runtime `memcpy` symbol + + #[no_mangle] + pub fn memmove() {} + //~^ ERROR invalid definition of the runtime `memmove` symbol + + extern "C" { + pub fn memset(); + //~^ ERROR invalid definition of the runtime `memset` symbol + + pub fn memcmp(); + //~^ ERROR invalid definition of the runtime `memcmp` symbol + } + + #[export_name = "bcmp"] + pub fn bcmp_() {} + //~^ ERROR invalid definition of the runtime `bcmp` symbol + + #[no_mangle] + pub static strlen: () = (); + //~^ ERROR invalid definition of the runtime `strlen` symbol +} + +fn valid() { + extern "C" { + fn memcpy(dest: *mut c_void, src: *const c_void, n: usize) -> *mut c_void; + + fn memmove(dest: *mut c_void, src: *const c_void, n: usize) -> *mut c_void; + + fn memset(s: *mut c_void, c: c_int, n: usize) -> *mut c_void; + + fn memcmp(s1: *const c_void, s2: *const c_void, n: usize) -> c_int; + + fn bcmp(s1: *const c_void, s2: *const c_void, n: usize) -> c_int; + + static strlen: Option usize>; + } +} + +fn main() {} diff --git a/tests/ui/lint/runtime-symbols.stderr b/tests/ui/lint/runtime-symbols.stderr new file mode 100644 index 0000000000000..bac3c352bdca6 --- /dev/null +++ b/tests/ui/lint/runtime-symbols.stderr @@ -0,0 +1,63 @@ +error: invalid definition of the runtime `memcpy` symbol used by the standard library + --> $DIR/runtime-symbols.rs:12:5 + | +LL | pub extern "C" fn memcpy(dest: *mut c_void, src: *const c_void, n: i64) -> *mut c_void { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: expected `unsafe extern "C" fn(*mut c_void, *const c_void, usize) -> *mut c_void` + found `extern "C" fn(*mut c_void, *const c_void, i64) -> *mut c_void` + = help: either fix the signature or remove any attributes like `#[unsafe(no_mangle)]`, `#[unsafe(export_name = "memcpy")]`, or `#[link_name = "memcpy"]` + = note: `#[deny(invalid_runtime_symbol_definitions)]` on by default + +error: invalid definition of the runtime `memmove` symbol used by the standard library + --> $DIR/runtime-symbols.rs:18:5 + | +LL | pub fn memmove() {} + | ^^^^^^^^^^^^^^^^ + | + = note: expected `unsafe extern "C" fn(*mut c_void, *const c_void, usize) -> *mut c_void` + found `fn()` + = help: either fix the signature or remove any attributes like `#[unsafe(no_mangle)]`, `#[unsafe(export_name = "memmove")]`, or `#[link_name = "memmove"]` + +error: invalid definition of the runtime `memset` symbol used by the standard library + --> $DIR/runtime-symbols.rs:22:9 + | +LL | pub fn memset(); + | ^^^^^^^^^^^^^^^^ + | + = note: expected `unsafe extern "C" fn(*mut c_void, i32, usize) -> *mut c_void` + found `unsafe extern "C" fn()` + = help: either fix the signature or remove any attributes like `#[unsafe(no_mangle)]`, `#[unsafe(export_name = "memset")]`, or `#[link_name = "memset"]` + +error: invalid definition of the runtime `memcmp` symbol used by the standard library + --> $DIR/runtime-symbols.rs:25:9 + | +LL | pub fn memcmp(); + | ^^^^^^^^^^^^^^^^ + | + = note: expected `unsafe extern "C" fn(*const c_void, *const c_void, usize) -> i32` + found `unsafe extern "C" fn()` + = help: either fix the signature or remove any attributes like `#[unsafe(no_mangle)]`, `#[unsafe(export_name = "memcmp")]`, or `#[link_name = "memcmp"]` + +error: invalid definition of the runtime `bcmp` symbol used by the standard library + --> $DIR/runtime-symbols.rs:30:5 + | +LL | pub fn bcmp_() {} + | ^^^^^^^^^^^^^^ + | + = note: expected `unsafe extern "C" fn(*const c_void, *const c_void, usize) -> i32` + found `fn()` + = help: either fix the signature or remove any attributes like `#[unsafe(no_mangle)]`, `#[unsafe(export_name = "bcmp")]`, or `#[link_name = "bcmp"]` + +error: invalid definition of the runtime `strlen` symbol used by the standard library + --> $DIR/runtime-symbols.rs:34:5 + | +LL | pub static strlen: () = (); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: expected `unsafe extern "C" fn(*const U8) -> usize` + found `static strlen: ()` + = help: either fix the signature or remove any attributes `#[unsafe(no_mangle)]` or `#[unsafe(export_name = "strlen")]` + +error: aborting due to 6 previous errors +