From bd93ed55ba2b92159cff636a3dc99032161dcf6c Mon Sep 17 00:00:00 2001 From: Urgau Date: Sun, 19 Apr 2026 17:22:58 +0200 Subject: [PATCH 01/16] Add function to extract the symbol name from the attributes --- compiler/rustc_symbol_mangling/src/lib.rs | 47 +++++++++++++++-------- 1 file changed, 30 insertions(+), 17 deletions(-) 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 From 32ab7005ad92a17eafd44ab9a693dce979e752a7 Mon Sep 17 00:00:00 2001 From: Urgau Date: Sun, 19 Apr 2026 17:25:55 +0200 Subject: [PATCH 02/16] Add weak-only lang items for core runtime symbols --- compiler/rustc_hir/src/lang_items.rs | 8 ++++ compiler/rustc_hir/src/weak_lang_items.rs | 21 ++++++++++ compiler/rustc_passes/src/lang_items.rs | 40 ++++++++++++++++---- compiler/rustc_passes/src/weak_lang_items.rs | 14 ++++--- compiler/rustc_span/src/symbol.rs | 6 +++ library/core/src/ffi/mod.rs | 27 +++++++++++++ tests/ui/error-codes/E0264.rs | 2 +- tests/ui/error-codes/E0264.stderr | 11 +++++- 8 files changed, 113 insertions(+), 16 deletions(-) diff --git a/compiler/rustc_hir/src/lang_items.rs b/compiler/rustc_hir/src/lang_items.rs index ea014e8a275b5..f4b0b050190f4 100644 --- a/compiler/rustc_hir/src/lang_items.rs +++ b/compiler/rustc_hir/src/lang_items.rs @@ -447,6 +447,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_passes/src/lang_items.rs b/compiler/rustc_passes/src/lang_items.rs index b6fb9937dfa45..a867960c97add 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_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index ddfe05e5c607d..038638fc88ed4 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, @@ -1251,7 +1252,11 @@ symbols! { mem_variant_count, mem_zeroed, member_constraints, + memcmp_fn, + memcpy_fn, + memmove_fn, memory, + memset_fn, memtag, message, meta, @@ -1986,6 +1991,7 @@ symbols! { strict_provenance_lints, string_deref_patterns, stringify, + strlen_fn, struct_field_attributes, struct_inherit, struct_variant, 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/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`. From c15eded26d1ff0f62447eddef8c44f4669e4d474 Mon Sep 17 00:00:00 2001 From: Urgau Date: Sun, 12 Apr 2026 20:46:06 +0200 Subject: [PATCH 03/16] Add lint against invalid runtime symbol definitions --- Cargo.lock | 1 + compiler/rustc_lint/Cargo.toml | 1 + compiler/rustc_lint/src/lib.rs | 3 + compiler/rustc_lint/src/lints.rs | 27 +++ compiler/rustc_lint/src/runtime_symbols.rs | 195 +++++++++++++++++++++ tests/ui/lint/runtime-symbols.rs | 54 ++++++ tests/ui/lint/runtime-symbols.stderr | 63 +++++++ 7 files changed, 344 insertions(+) create mode 100644 compiler/rustc_lint/src/runtime_symbols.rs create mode 100644 tests/ui/lint/runtime-symbols.rs create mode 100644 tests/ui/lint/runtime-symbols.stderr diff --git a/Cargo.lock b/Cargo.lock index 4648a12da487b..c6ca8807acfe3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4174,6 +4174,7 @@ dependencies = [ "rustc_parse_format", "rustc_session", "rustc_span", + "rustc_symbol_mangling", "rustc_target", "rustc_trait_selection", "smallvec", 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 ea0e657f7edef..dbcb20374c3bd 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..05675a14d2f91 --- /dev/null +++ b/compiler/rustc_lint/src/runtime_symbols.rs @@ -0,0 +1,195 @@ +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; + let static_ty = cx.tcx.type_of(def_id).instantiate_identity(); + + check_static(cx, &symbol_name, static_ty, 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(..) => { + let def_id = item.owner_id.def_id; + let static_ty = cx.tcx.type_of(def_id).instantiate_identity(); + check_static(cx, &symbol_name.name, static_ty, 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, static_ty: Ty<'tcx>, 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; + }; + + // Unconditionally report a mismatch, a static cannot ever be a function definition + + 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); + + 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/tests/ui/lint/runtime-symbols.rs b/tests/ui/lint/runtime-symbols.rs new file mode 100644 index 0000000000000..6c8a79f7718cb --- /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; + + fn strlen(s: *const c_char) -> 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 + From 6f899dfc82c4a9a5d73eff14919e18fe2ebe0e20 Mon Sep 17 00:00:00 2001 From: Urgau Date: Sun, 19 Apr 2026 20:43:18 +0200 Subject: [PATCH 04/16] Handle static whose type is a function pointer --- compiler/rustc_lint/src/runtime_symbols.rs | 42 +++++++++++++--------- tests/ui/lint/runtime-symbols.rs | 2 +- 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/compiler/rustc_lint/src/runtime_symbols.rs b/compiler/rustc_lint/src/runtime_symbols.rs index 05675a14d2f91..f53726ddb7242 100644 --- a/compiler/rustc_lint/src/runtime_symbols.rs +++ b/compiler/rustc_lint/src/runtime_symbols.rs @@ -87,9 +87,8 @@ impl<'tcx> LateLintPass<'tcx> for RuntimeSymbols { }; let def_id = item.owner_id.def_id; - let static_ty = cx.tcx.type_of(def_id).instantiate_identity(); - check_static(cx, &symbol_name, static_ty, item.span); + check_static(cx, &symbol_name, def_id, item.span); } hir::ItemKind::ForeignMod { abi: _, items } => { for item in items { @@ -107,9 +106,7 @@ impl<'tcx> LateLintPass<'tcx> for RuntimeSymbols { check_fn(cx, &symbol_name.name, fn_sig, did); } ForeignItemKind::Static(..) => { - let def_id = item.owner_id.def_id; - let static_ty = cx.tcx.type_of(def_id).instantiate_identity(); - check_static(cx, &symbol_name.name, static_ty, item.span); + check_static(cx, &symbol_name.name, did, item.span); } ForeignItemKind::Type => return, } @@ -163,7 +160,7 @@ fn check_fn(cx: &LateContext<'_>, symbol_name: &str, sig: FnSig<'_>, did: LocalD } } -fn check_static<'tcx>(cx: &LateContext<'tcx>, symbol_name: &str, static_ty: Ty<'tcx>, sp: Span) { +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; @@ -174,8 +171,18 @@ fn check_static<'tcx>(cx: &LateContext<'tcx>, symbol_name: &str, static_ty: Ty<' return; }; - // Unconditionally report a mismatch, a static cannot ever be a function definition + // 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(), @@ -183,13 +190,16 @@ fn check_static<'tcx>(cx: &LateContext<'tcx>, symbol_name: &str, static_ty: Ty<' let expected = Ty::new_fn_ptr(cx.tcx, lang_sig); - cx.emit_span_lint( - INVALID_RUNTIME_SYMBOL_DEFINITIONS, - sp, - RedefiningRuntimeSymbolsDiag::Static { - static_ty, - symbol_name: symbol_name.to_string(), - expected_fn_sig: expected, - }, - ); + // 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/tests/ui/lint/runtime-symbols.rs b/tests/ui/lint/runtime-symbols.rs index 6c8a79f7718cb..813ebff4ff4de 100644 --- a/tests/ui/lint/runtime-symbols.rs +++ b/tests/ui/lint/runtime-symbols.rs @@ -47,7 +47,7 @@ fn valid() { fn bcmp(s1: *const c_void, s2: *const c_void, n: usize) -> c_int; - fn strlen(s: *const c_char) -> usize; + static strlen: Option usize>; } } From 05165c18c4cbdf20da377f4a182433f0b17cb04b Mon Sep 17 00:00:00 2001 From: Urgau Date: Sun, 19 Apr 2026 23:02:53 +0200 Subject: [PATCH 05/16] Fix invalid `memcpy` definition in codegen test and UI tests --- tests/codegen-llvm/no_builtins-at-crate.rs | 6 ++++-- tests/ui/foreign/foreign-int-types.rs | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) 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/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); } } From 87d009021e6e5aa59fd17fe91751de91e084db27 Mon Sep 17 00:00:00 2001 From: Urgau Date: Mon, 20 Apr 2026 00:10:38 +0200 Subject: [PATCH 06/16] Fix invalid definition of `strlen` in error codes documentation --- compiler/rustc_error_codes/src/error_codes/E0755.md | 2 +- compiler/rustc_error_codes/src/error_codes/E0756.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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() {} ``` From 2493ef150399adb8ad85234a5ae60de9b8ac2d5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Sun, 10 May 2026 15:59:37 +0200 Subject: [PATCH 07/16] rustdoc: `SpanMapVisitor`: Cache `TypeckResults` --- src/doc/rustdoc/src/unstable-features.md | 9 ++ src/librustdoc/html/render/span_map.rs | 122 +++++++++++------ .../jump-to-def/assoc-items-extra.rs | 20 +++ tests/rustdoc-html/jump-to-def/assoc-items.rs | 126 ++++++++++++------ tests/rustdoc-html/jump-to-def/assoc-types.rs | 20 --- .../rustdoc-html/jump-to-def/no-body-items.rs | 24 ---- .../items-nested-in-bodies.rs | 31 +++++ 7 files changed, 228 insertions(+), 124 deletions(-) create mode 100644 tests/rustdoc-html/jump-to-def/assoc-items-extra.rs delete mode 100644 tests/rustdoc-html/jump-to-def/assoc-types.rs delete mode 100644 tests/rustdoc-html/jump-to-def/no-body-items.rs create mode 100644 tests/rustdoc-ui/generate-link-to-definition/items-nested-in-bodies.rs 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/html/render/span_map.rs b/src/librustdoc/html/render/span_map.rs index ff214bad59f1d..d314f2b55865e 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,12 +98,34 @@ 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) + } + /// 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` @@ -218,18 +242,13 @@ impl SpanMapVisitor<'_> { } 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()); + let Some(typeck_results) = self.maybe_typeck_results() else { return }; + // 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)) + LinkFromSrc::Local(rustc_span(def_id, self.tcx)) } else { LinkFromSrc::External(def_id) }; @@ -238,23 +257,6 @@ impl SpanMapVisitor<'_> { } } -// 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> { type NestedFilter = nested_filter::All; @@ -262,7 +264,14 @@ 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_path(&mut self, path: &hir::Path<'tcx>, _id: HirId) { if self.handle_macro(path.span) { return; } @@ -272,25 +281,37 @@ 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 { + QPath::TypeRelative(qself, segment) => { + if let Res::Err = segment.res { + // 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: path.ident.span, + span: segment.ident.span, res: typeck_results.qpath_res(qpath, id), + // FIXME(fmease): Don't create a path with zero segments! segments: &[], }; self.handle_path(&path, false); } } else { - self.infer_id(path.hir_id, Some(id), path.ident.span.into()); + self.infer_id(segment.hir_id, Some(id), segment.ident.span.into()); } 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,11 +344,17 @@ 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()) } + // FIXME(fmease): We needlessly request `TypeckResults` even if the callee isn't + // type-relative. In the majority of cases, it's just gonna be a + // `Resolved` path meaning we can end up unnecessarily + // `typeck`'ing the body which is super costly! + // Moreover, if it actually is a type-relative path, we end up + // "resolving" it twice (with slightly different spans). ExprKind::Call(call, ..) => self.infer_id(call.hir_id, None, call.span.into()), _ => { if self.handle_macro(expr.span) { @@ -340,6 +367,10 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { } 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 +390,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/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/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; + } +} From b0eb4ffb00008891ed797eccda532a4246fd9b55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Sun, 10 May 2026 16:26:46 +0200 Subject: [PATCH 08/16] Don't check if the resolution of `TypeRelative` paths is `Err` because it always is `rustc_resolve` doesn't resolve type-relative paths since that's the job of HIR ty lowering and HIR typeck. `segment.res` comes from `rustc_resolve` and is thus always `Res::Err`. So just try to obtain the `TypeckResults` immediately since they contain the actual resolution as deduced by HIR typeck. --- src/librustdoc/html/render/span_map.rs | 48 ++++++++++++-------------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/src/librustdoc/html/render/span_map.rs b/src/librustdoc/html/render/span_map.rs index d314f2b55865e..324c79ff2a43c 100644 --- a/src/librustdoc/html/render/span_map.rs +++ b/src/librustdoc/html/render/span_map.rs @@ -282,32 +282,28 @@ 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, segment) => { - if let Res::Err = segment.res { - // 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), - // FIXME(fmease): Don't create a path with zero segments! - segments: &[], - }; - self.handle_path(&path, false); - } - } else { - self.infer_id(segment.hir_id, Some(id), segment.ident.span.into()); + // 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), + // FIXME(fmease): Don't create a path with zero segments! + segments: &[], + }; + self.handle_path(&path, false); } rustc_ast::visit::try_visit!(self.visit_ty_unambig(qself)); From f5ad60c7daa7280f55cf9bc9eaee83de402bc946 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Sun, 10 May 2026 16:49:39 +0200 Subject: [PATCH 09/16] Inline non-self-descriptive fn `infer_id` --- src/librustdoc/html/render/span_map.rs | 53 +++++++++++--------------- 1 file changed, 22 insertions(+), 31 deletions(-) diff --git a/src/librustdoc/html/render/span_map.rs b/src/librustdoc/html/render/span_map.rs index 324c79ff2a43c..6c847effe0b39 100644 --- a/src/librustdoc/html/render/span_map.rs +++ b/src/librustdoc/html/render/span_map.rs @@ -124,6 +124,14 @@ impl<'tcx> SpanMapVisitor<'tcx> { 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: &hir::Path<'_>, only_use_last_segment: bool) { match path.res { @@ -131,11 +139,6 @@ impl<'tcx> SpanMapVisitor<'tcx> { // 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) - }; // 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) @@ -156,7 +159,7 @@ impl<'tcx> SpanMapVisitor<'tcx> { }) .unwrap_or(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 @@ -240,21 +243,6 @@ impl<'tcx> SpanMapVisitor<'tcx> { 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 Some(typeck_results) = self.maybe_typeck_results() else { return }; - - // 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, self.tcx)) - } else { - LinkFromSrc::External(def_id) - }; - self.matches.insert(span, link); - } - } } impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { @@ -341,23 +329,26 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'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()) + let mut handle_type_dependent_def = |hir_id: HirId, span: rustc_span::Span| { + // Exprs *have* to exist in a body, so typeck results should always be available. + let typeck_results = self.maybe_typeck_results().unwrap(); + if let Some(def_id) = typeck_results.type_dependent_def_id(hir_id) { + self.matches.insert(span.into(), self.link_for_def(def_id)); } + }; + + match expr.kind { + ExprKind::MethodCall(seg, ..) => handle_type_dependent_def(expr.hir_id, seg.ident.span), // FIXME(fmease): We needlessly request `TypeckResults` even if the callee isn't // type-relative. In the majority of cases, it's just gonna be a // `Resolved` path meaning we can end up unnecessarily // `typeck`'ing the body which is super costly! // Moreover, if it actually is a type-relative path, we end up // "resolving" it twice (with slightly different spans). - 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; - } - } + ExprKind::Call(callee, ..) => handle_type_dependent_def(callee.hir_id, callee.span), + // We don't want to go deeper into the macro. + _ if self.handle_macro(expr.span) => return, + _ => {} } intravisit::walk_expr(self, expr); } From bfcaf513b4819cfd3dd2b3e930dc702db05e7f87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Sun, 10 May 2026 18:23:50 +0200 Subject: [PATCH 10/16] Don't create a path with zero segments --- src/librustdoc/html/render/span_map.rs | 43 +++++++++----------------- 1 file changed, 15 insertions(+), 28 deletions(-) diff --git a/src/librustdoc/html/render/span_map.rs b/src/librustdoc/html/render/span_map.rs index 6c847effe0b39..824eb758937f6 100644 --- a/src/librustdoc/html/render/span_map.rs +++ b/src/librustdoc/html/render/span_map.rs @@ -135,37 +135,26 @@ impl<'tcx> SpanMapVisitor<'tcx> { /// This function is where we handle `hir::Path` elements and add them into the "span map". 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 => { + // 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(), 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 }; @@ -176,7 +165,6 @@ impl<'tcx> SpanMapVisitor<'tcx> { self.matches .insert(path.span.into(), LinkFromSrc::Primitive(PrimitiveType::from(p))); } - Res::Err => {} _ => {} } } @@ -288,8 +276,7 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { // We change the span to not include parens. span: segment.ident.span, res: typeck_results.qpath_res(qpath, id), - // FIXME(fmease): Don't create a path with zero segments! - segments: &[], + segments: std::slice::from_ref(segment), }; self.handle_path(&path, false); } From efaf441d7b37a28462fd006d8ca64271c8cbb0a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Sun, 10 May 2026 19:55:04 +0200 Subject: [PATCH 11/16] Don't special-case `ExprKind::Call` Don't unnecessarily try to obtain the type-dependent definition of callees in `visit_expr`, just let `visit_qpath` handle callees. This means that for callees that are * `Resolved` paths (the majority of callees) we don't try to `typeck` the enclosing body which should improve perf if the body doesn't contain any type-dependent definitions. * actually `TypeRelative` paths we don't resolve them twice (with slightly different spans) --- src/librustdoc/html/render/span_map.rs | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/src/librustdoc/html/render/span_map.rs b/src/librustdoc/html/render/span_map.rs index 824eb758937f6..863ce3a79f4e6 100644 --- a/src/librustdoc/html/render/span_map.rs +++ b/src/librustdoc/html/render/span_map.rs @@ -316,23 +316,14 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { } fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) { - let mut handle_type_dependent_def = |hir_id: HirId, span: rustc_span::Span| { - // Exprs *have* to exist in a body, so typeck results should always be available. - let typeck_results = self.maybe_typeck_results().unwrap(); - if let Some(def_id) = typeck_results.type_dependent_def_id(hir_id) { - self.matches.insert(span.into(), self.link_for_def(def_id)); - } - }; - match expr.kind { - ExprKind::MethodCall(seg, ..) => handle_type_dependent_def(expr.hir_id, seg.ident.span), - // FIXME(fmease): We needlessly request `TypeckResults` even if the callee isn't - // type-relative. In the majority of cases, it's just gonna be a - // `Resolved` path meaning we can end up unnecessarily - // `typeck`'ing the body which is super costly! - // Moreover, if it actually is a type-relative path, we end up - // "resolving" it twice (with slightly different spans). - ExprKind::Call(callee, ..) => handle_type_dependent_def(callee.hir_id, callee.span), + ExprKind::MethodCall(segment, ..) => { + // Exprs *have* to exist in a body, so typeck results should always be available. + let typeck_results = self.maybe_typeck_results().unwrap(); + if 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, _ => {} From e205784d77f5e84e5505b2b77536ad2d89961ff6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Wed, 13 May 2026 03:50:04 +0200 Subject: [PATCH 12/16] Don't try to resolve type-dependent paths in anon consts --- src/librustdoc/html/render/span_map.rs | 16 +++++++++++++--- .../generate-link-to-definition/anon-consts.rs | 11 +++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 tests/rustdoc-ui/generate-link-to-definition/anon-consts.rs diff --git a/src/librustdoc/html/render/span_map.rs b/src/librustdoc/html/render/span_map.rs index 863ce3a79f4e6..517b538d1bfd1 100644 --- a/src/librustdoc/html/render/span_map.rs +++ b/src/librustdoc/html/render/span_map.rs @@ -247,6 +247,16 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { 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; @@ -318,9 +328,9 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) { match expr.kind { ExprKind::MethodCall(segment, ..) => { - // Exprs *have* to exist in a body, so typeck results should always be available. - let typeck_results = self.maybe_typeck_results().unwrap(); - if let Some(def_id) = typeck_results.type_dependent_def_id(expr.hir_id) { + 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)); } } 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) }>; +} From 2c30279ff69c568248a341bdbd77a0ee24afcc15 Mon Sep 17 00:00:00 2001 From: John Millikin Date: Wed, 13 May 2026 19:24:01 +0900 Subject: [PATCH 13/16] Add `send_process_group_signal` to existing `unix_send_signal` feature This function wraps POSIX `killpg()`, and on Linux additionally may be implemented by `pidfd_send_signal`. --- library/std/src/os/unix/process.rs | 36 +++++++++++++++++++ library/std/src/sys/pal/unix/linux/pidfd.rs | 15 ++++++++ library/std/src/sys/process/unix/fuchsia.rs | 5 +++ library/std/src/sys/process/unix/unix.rs | 13 +++++++ .../std/src/sys/process/unix/unsupported.rs | 4 +++ library/std/src/sys/process/unix/vxworks.rs | 8 +++++ 6 files changed, 81 insertions(+) diff --git a/library/std/src/os/unix/process.rs b/library/std/src/os/unix/process.rs index a739d6ad2a90d..e0bc415f9e3e0 100644 --- a/library/std/src/os/unix/process.rs +++ b/library/std/src/os/unix/process.rs @@ -420,6 +420,38 @@ 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<()>; } #[unstable(feature = "unix_send_signal", issue = "141975")] @@ -427,6 +459,10 @@ 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) + } } #[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 { From 6e99c6851628fd40b08cdc4a6128bcd4a6aeb7cf Mon Sep 17 00:00:00 2001 From: cyrgani Date: Wed, 13 May 2026 10:55:37 +0000 Subject: [PATCH 14/16] use `deref_patterns` in `rustdoc` --- src/librustdoc/clean/cfg.rs | 8 +- src/librustdoc/clean/mod.rs | 9 +- src/librustdoc/clean/types.rs | 29 +++-- src/librustdoc/fold.rs | 2 +- src/librustdoc/formats/cache.rs | 112 +++++++++---------- src/librustdoc/formats/item_type.rs | 2 +- src/librustdoc/formats/renderer.rs | 4 +- src/librustdoc/html/format.rs | 6 +- src/librustdoc/html/render/context.rs | 2 +- src/librustdoc/html/render/mod.rs | 7 +- src/librustdoc/html/render/print_item.rs | 15 ++- src/librustdoc/html/render/sidebar.rs | 2 +- src/librustdoc/json/conversions.rs | 2 +- src/librustdoc/lib.rs | 2 +- src/librustdoc/passes/collect_trait_impls.rs | 4 +- src/librustdoc/passes/propagate_stability.rs | 5 +- 16 files changed, 108 insertions(+), 103 deletions(-) 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/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(..) From 7bf5fe7bf84f5b94e5970de18543abac0579209c Mon Sep 17 00:00:00 2001 From: John Millikin Date: Wed, 13 May 2026 19:52:56 +0900 Subject: [PATCH 15/16] Add `ChildExt::kill_process_group` This function wraps POSIX `killpg(pid, SIGKILL)`, and on Linux additionally may be implemented by `pidfd_send_signal`. --- library/std/src/os/unix/process.rs | 34 ++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/library/std/src/os/unix/process.rs b/library/std/src/os/unix/process.rs index e0bc415f9e3e0..a0defc39ac82e 100644 --- a/library/std/src/os/unix/process.rs +++ b/library/std/src/os/unix/process.rs @@ -452,6 +452,36 @@ pub trait ChildExt: Sealed { /// ``` #[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")] @@ -463,6 +493,10 @@ impl ChildExt for process::Child { 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")] From 3d0ee528cb125e58710f1d56fbb4b1516f877ee3 Mon Sep 17 00:00:00 2001 From: Bryanskiy Date: Fri, 8 May 2026 14:48:57 +0300 Subject: [PATCH 16/16] Privacy: move macros handling to early stage --- compiler/rustc_middle/src/middle/privacy.rs | 8 +- compiler/rustc_middle/src/ty/mod.rs | 6 + compiler/rustc_privacy/src/lib.rs | 201 ++---------------- .../src/effective_visibilities.rs | 146 ++++++++++++- compiler/rustc_resolve/src/lib.rs | 4 + 5 files changed, 178 insertions(+), 187 deletions(-) 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 93233efae6650..7044dc54b5a3a 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_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 44a72fec56b95..ccb7ffbaba53c 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -1531,6 +1531,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(), @@ -1853,6 +1855,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, @@ -1963,6 +1966,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,