From 1cbdaf246bd4d785bab19666e02f9fd4352771a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Tue, 12 Aug 2025 14:39:53 +0200 Subject: [PATCH] EII collection queries EII collection queries --- compiler/rustc_interface/src/passes.rs | 3 + compiler/rustc_metadata/src/eii.rs | 48 ++++++ compiler/rustc_metadata/src/lib.rs | 1 + compiler/rustc_metadata/src/rmeta/decoder.rs | 8 + .../src/rmeta/decoder/cstore_impl.rs | 16 +- compiler/rustc_metadata/src/rmeta/encoder.rs | 14 ++ compiler/rustc_metadata/src/rmeta/mod.rs | 2 + .../rustc_metadata/src/rmeta/parameterized.rs | 2 + compiler/rustc_middle/src/query/erase.rs | 2 + compiler/rustc_middle/src/query/mod.rs | 13 +- compiler/rustc_passes/messages.ftl | 42 +++-- compiler/rustc_passes/src/eii.rs | 158 ++++++++++++++++++ compiler/rustc_passes/src/errors.rs | 38 +++++ compiler/rustc_passes/src/lib.rs | 2 + 14 files changed, 332 insertions(+), 17 deletions(-) create mode 100644 compiler/rustc_metadata/src/eii.rs create mode 100644 compiler/rustc_passes/src/eii.rs diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index ddfec9f886a6..fe28e5e591e2 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -1061,6 +1061,9 @@ fn run_required_analyses(tcx: TyCtxt<'_>) { parallel!( { sess.time("looking_for_entry_point", || tcx.ensure_ok().entry_fn(())); + sess.time("check_externally_implementable_items", || { + tcx.ensure_ok().check_externally_implementable_items(()) + }); sess.time("looking_for_derive_registrar", || { tcx.ensure_ok().proc_macro_decls_static(()) diff --git a/compiler/rustc_metadata/src/eii.rs b/compiler/rustc_metadata/src/eii.rs new file mode 100644 index 000000000000..2d99ba516ad4 --- /dev/null +++ b/compiler/rustc_metadata/src/eii.rs @@ -0,0 +1,48 @@ +use rustc_data_structures::fx::FxIndexMap; +use rustc_hir::attrs::{AttributeKind, EiiDecl, EiiImpl}; +use rustc_hir::def_id::DefId; +use rustc_hir::find_attr; +use rustc_middle::query::LocalCrate; +use rustc_middle::ty::TyCtxt; + +// basically the map below but flattened out +pub(crate) type EiiMapEncodedKeyValue = (DefId, (EiiDecl, Vec<(DefId, EiiImpl)>)); + +pub(crate) type EiiMap = FxIndexMap< + DefId, // the defid of the macro that declared the eii + ( + // the corresponding declaration + EiiDecl, + // all the given implementations, indexed by defid. + // We expect there to be only one, but collect them all to give errors if there are more + // (or if there are none) in the final crate we build. + FxIndexMap, + ), +>; + +pub(crate) fn collect<'tcx>(tcx: TyCtxt<'tcx>, LocalCrate: LocalCrate) -> EiiMap { + let mut eiis = EiiMap::default(); + + // iterate over all items in the current crate + // FIXME(speed up) + for id in tcx.hir_crate_items(()).definitions() { + for i in + find_attr!(tcx.get_all_attrs(id), AttributeKind::EiiImpls(e) => e).into_iter().flatten() + { + eiis.entry(i.eii_macro) + .or_insert_with(|| { + // find the decl for this one if it wasn't in yet (maybe it's from the local crate? not very useful but not illegal) + (find_attr!(tcx.get_all_attrs(i.eii_macro), AttributeKind::EiiExternTarget(d) => *d).unwrap(), Default::default()) + }).1.insert(id.into(), *i); + } + + // if we find a new declaration, add it to the list without a known implementation + if let Some(decl) = + find_attr!(tcx.get_all_attrs(id), AttributeKind::EiiExternTarget(d) => *d) + { + eiis.entry(id.into()).or_insert((decl, Default::default())); + } + } + + eiis +} diff --git a/compiler/rustc_metadata/src/lib.rs b/compiler/rustc_metadata/src/lib.rs index 114301083883..f31c4938e118 100644 --- a/compiler/rustc_metadata/src/lib.rs +++ b/compiler/rustc_metadata/src/lib.rs @@ -16,6 +16,7 @@ pub use rmeta::provide; mod dependency_format; +mod eii; mod foreign_modules; mod native_libs; mod rmeta; diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs index 76438f046dd5..1b9c7b1d8e75 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder.rs @@ -39,6 +39,7 @@ use rustc_span::{ use tracing::debug; use crate::creader::CStore; +use crate::eii::EiiMapEncodedKeyValue; use crate::rmeta::table::IsDefault; use crate::rmeta::*; @@ -1487,6 +1488,13 @@ impl<'a> CrateMetadataRef<'a> { ) } + fn get_externally_implementable_items( + self, + tcx: TyCtxt<'_>, + ) -> impl Iterator { + self.root.externally_implementable_items.decode((self, tcx)) + } + fn get_missing_lang_items<'tcx>(self, tcx: TyCtxt<'tcx>) -> &'tcx [LangItem] { tcx.arena.alloc_from_iter(self.root.lang_items_missing.decode((self, tcx))) } diff --git a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs index c48cf36930f4..f441788fdcb6 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs @@ -25,7 +25,7 @@ use super::{Decodable, DecodeIterator}; use crate::creader::{CStore, LoadedMacro}; use crate::rmeta::AttrFlags; use crate::rmeta::table::IsDefault; -use crate::{foreign_modules, native_libs}; +use crate::{eii, foreign_modules, native_libs}; trait ProcessQueryValue<'tcx, T> { fn process_decoded(self, _tcx: TyCtxt<'tcx>, _err: impl Fn() -> !) -> T; @@ -330,9 +330,22 @@ provide! { tcx, def_id, other, cdata, is_private_dep => { cdata.private_dep } is_panic_runtime => { cdata.root.panic_runtime } is_compiler_builtins => { cdata.root.compiler_builtins } + + // FIXME: to be replaced with externally_implementable_items below has_global_allocator => { cdata.root.has_global_allocator } + // FIXME: to be replaced with externally_implementable_items below has_alloc_error_handler => { cdata.root.has_alloc_error_handler } + // FIXME: to be replaced with externally_implementable_items below has_panic_handler => { cdata.root.has_panic_handler } + + externally_implementable_items => { + cdata.get_externally_implementable_items(tcx) + .map(|(decl_did, (decl, impls))| ( + decl_did, + (decl, impls.into_iter().collect()) + )).collect() + } + is_profiler_runtime => { cdata.root.profiler_runtime } required_panic_strategy => { cdata.root.required_panic_strategy } panic_in_drop_strategy => { cdata.root.panic_in_drop_strategy } @@ -430,6 +443,7 @@ pub(in crate::rmeta) fn provide(providers: &mut Providers) { }, native_libraries: native_libs::collect, foreign_modules: foreign_modules::collect, + externally_implementable_items: eii::collect, // Returns a map from a sufficiently visible external item (i.e., an // external item that is visible from at least one local module) to a diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index 2069c06c3f77..a72e8e514292 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -35,6 +35,7 @@ use rustc_span::{ }; use tracing::{debug, instrument, trace}; +use crate::eii::EiiMapEncodedKeyValue; use crate::errors::{FailCreateFileEncoder, FailWriteFile}; use crate::rmeta::*; @@ -620,6 +621,9 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { // We have already encoded some things. Get their combined size from the current position. stats.push(("preamble", self.position())); + let externally_implementable_items = stat!("externally-implementable-items", || self + .encode_externally_implementable_items()); + let (crate_deps, dylib_dependency_formats) = stat!("dep", || (self.encode_crate_deps(), self.encode_dylib_dependency_formats())); @@ -738,6 +742,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { attrs, sym::default_lib_allocator, ), + externally_implementable_items, proc_macro_data, debugger_visualizers, compiler_builtins: ast::attr::contains_name(attrs, sym::compiler_builtins), @@ -1649,6 +1654,15 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { } } + fn encode_externally_implementable_items(&mut self) -> LazyArray { + empty_proc_macro!(self); + let externally_implementable_items = self.tcx.externally_implementable_items(LOCAL_CRATE); + + self.lazy_array(externally_implementable_items.iter().map(|(decl_did, (decl, impls))| { + (*decl_did, (decl.clone(), impls.iter().map(|(impl_did, i)| (*impl_did, *i)).collect())) + })) + } + #[instrument(level = "trace", skip(self))] fn encode_info_for_adt(&mut self, local_def_id: LocalDefId) { let def_id = local_def_id.to_def_id(); diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs index c5230685bfaf..0de54cf87433 100644 --- a/compiler/rustc_metadata/src/rmeta/mod.rs +++ b/compiler/rustc_metadata/src/rmeta/mod.rs @@ -44,6 +44,7 @@ use table::TableBuilder; use {rustc_ast as ast, rustc_hir as hir}; use crate::creader::CrateMetadataRef; +use crate::eii::EiiMapEncodedKeyValue; mod decoder; mod def_path_hash_map; @@ -250,6 +251,7 @@ pub(crate) struct CrateRoot { has_alloc_error_handler: bool, has_panic_handler: bool, has_default_lib_allocator: bool, + externally_implementable_items: LazyArray, crate_deps: LazyArray, dylib_dependency_formats: LazyArray>, diff --git a/compiler/rustc_metadata/src/rmeta/parameterized.rs b/compiler/rustc_metadata/src/rmeta/parameterized.rs index d6ccad798112..8a9de07836db 100644 --- a/compiler/rustc_metadata/src/rmeta/parameterized.rs +++ b/compiler/rustc_metadata/src/rmeta/parameterized.rs @@ -87,6 +87,8 @@ trivially_parameterized_over_tcx! { rustc_hir::Safety, rustc_hir::Stability, rustc_hir::attrs::Deprecation, + rustc_hir::attrs::EiiDecl, + rustc_hir::attrs::EiiImpl, rustc_hir::attrs::StrippedCfgItem, rustc_hir::def::DefKind, rustc_hir::def::DocLinkResMap, diff --git a/compiler/rustc_middle/src/query/erase.rs b/compiler/rustc_middle/src/query/erase.rs index 404330a9a45a..774dd88997f2 100644 --- a/compiler/rustc_middle/src/query/erase.rs +++ b/compiler/rustc_middle/src/query/erase.rs @@ -296,6 +296,8 @@ trivial! { rustc_ast::expand::allocator::AllocatorKind, rustc_hir::DefaultBodyStability, rustc_hir::attrs::Deprecation, + rustc_hir::attrs::EiiDecl, + rustc_hir::attrs::EiiImpl, rustc_data_structures::svh::Svh, rustc_errors::ErrorGuaranteed, rustc_hir::Constness, diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs index 15c84a5b01fb..9b4306f656d6 100644 --- a/compiler/rustc_middle/src/query/mod.rs +++ b/compiler/rustc_middle/src/query/mod.rs @@ -76,7 +76,7 @@ use rustc_data_structures::steal::Steal; use rustc_data_structures::svh::Svh; use rustc_data_structures::unord::{UnordMap, UnordSet}; use rustc_errors::ErrorGuaranteed; -use rustc_hir::attrs::StrippedCfgItem; +use rustc_hir::attrs::{EiiDecl, EiiImpl, StrippedCfgItem}; use rustc_hir::def::{DefKind, DocLinkResMap}; use rustc_hir::def_id::{ CrateNum, DefId, DefIdMap, LocalDefId, LocalDefIdMap, LocalDefIdSet, LocalModDefId, @@ -2753,6 +2753,17 @@ rustc_queries! { desc { |tcx| "checking what set of sanitizers are enabled on `{}`", tcx.def_path_str(key) } feedable } + + query check_externally_implementable_items(_: ()) { + desc { "check externally implementable items" } + } + + /// Returns a list of all `externally implementable items` crate. + query externally_implementable_items(_: CrateNum) -> &'tcx FxIndexMap)> { + arena_cache + desc { "looking up the externally implementable items of a crate" } + separate_provide_extern + } } rustc_with_all_queries! { define_callbacks! } diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl index 43c3b3fb3619..21191085253b 100644 --- a/compiler/rustc_passes/messages.ftl +++ b/compiler/rustc_passes/messages.ftl @@ -164,6 +164,17 @@ passes_duplicate_diagnostic_item_in_crate = duplicate diagnostic item in crate `{$crate_name}`: `{$name}` .note = the diagnostic item is first defined in crate `{$orig_crate_name}` +passes_duplicate_eii_impls = + multiple implementations of `#[{$name}]` + .first = first implemented here in crate `{$first_crate}` + .second = also implemented here in crate `{$second_crate}` + .note = in addition to these two, { $num_additional_crates -> + [one] another implementation was found in crate {$additional_crate_names} + *[other] more implementations were also found in the following crates: {$additional_crate_names} + } + + .help = an "externally implementable item" can only have a single implementation in the final artifact. When multiple implementations are found, also in different crates, they conflict + passes_duplicate_feature_err = the feature `{$feature}` has already been enabled @@ -197,6 +208,22 @@ passes_duplicate_lang_item_crate_depends = .first_definition_path = first definition in `{$orig_crate_name}` loaded from {$orig_path} .second_definition_path = second definition in `{$crate_name}` loaded from {$path} +passes_eii_fn_with_track_caller = + `#[{$name}]` is not allowed to have `#[track_caller]` + .label = `#[{$name}]` is not allowed to have `#[track_caller]` + +passes_eii_impl_not_function = + `eii_macro_for` is only valid on functions + +passes_eii_impl_requires_unsafe = + `#[{$name}]` is unsafe to implement +passes_eii_impl_requires_unsafe_suggestion = wrap the attribute in `unsafe(...)` + +passes_eii_without_impl = + `#[{$name}]` required, but not found + .label = expected because `#[{$name}]` was declared here in crate `{$decl_crate_name}` + .help = expected at least one implementation in crate `{$current_crate_name}` or any of its dependencies + passes_enum_variant_same_name = it is impossible to refer to the {$dead_descr} `{$dead_name}` because it is shadowed by this enum variant with the same name @@ -590,18 +617,3 @@ passes_useless_stability = this stability annotation is useless .label = useless stability annotation .item = the stability attribute annotates this item - -passes_eii_fn_with_target_feature = - `#[{$name}]` is not allowed to have `#[target_feature]` - .label = `#[{$name}]` is not allowed to have `#[target_feature]` - -passes_eii_fn_with_track_caller = - `#[{$name}]` is not allowed to have `#[track_caller]` - .label = `#[{$name}]` is not allowed to have `#[track_caller]` - -passes_eii_impl_not_function = - `eii_macro_for` is only valid on functions - -passes_eii_impl_requires_unsafe = - `#[{$name}]` is unsafe to implement -passes_eii_impl_requires_unsafe_suggestion = wrap the attribute in `unsafe(...)` diff --git a/compiler/rustc_passes/src/eii.rs b/compiler/rustc_passes/src/eii.rs new file mode 100644 index 000000000000..691576e6a05f --- /dev/null +++ b/compiler/rustc_passes/src/eii.rs @@ -0,0 +1,158 @@ +//! Checks necessary for externally implementable items: +//! Are all items implemented etc.? + +use std::iter; + +use rustc_data_structures::fx::FxIndexMap; +use rustc_hir::attrs::{EiiDecl, EiiImpl}; +use rustc_hir::def_id::{CrateNum, DefId, LOCAL_CRATE}; +use rustc_middle::ty::TyCtxt; +use rustc_session::config::CrateType; + +use crate::errors::{DuplicateEiiImpls, EiiWithoutImpl}; + +#[derive(Clone, Copy, Debug)] +enum CheckingMode { + CheckDuplicates, + CheckExistence, +} + +fn get_checking_mode(tcx: TyCtxt<'_>) -> CheckingMode { + // if any of the crate types is not rlib or dylib, we must check for existence. + if tcx.crate_types().iter().any(|i| !matches!(i, CrateType::Rlib | CrateType::Dylib)) { + CheckingMode::CheckExistence + } else { + CheckingMode::CheckDuplicates + } +} + +/// Checks for a given crate, what EIIs need to be generated in it. +/// This is usually a small subset of all EIIs. +/// +/// EII implementations come in two varieties: explicit and default. +/// This query is called once for every crate, to check whether there aren't any duplicate explicit implementations. +/// A duplicate may be caused by an implementation in the current crate, +/// though it's also entirely possible that the source is two dependencies with an explicit implementation. +/// Those work fine on their own but the combination of the two is a conflict. +/// +/// However, if the current crate is a "root" crate, one that generates a final artifact like a binary, +/// then we check one more thing, namely that every EII actually has an implementation, either default or not. +/// If one EII has no implementation, that's an error at that point. +/// +/// These two behaviors are implemented using `CheckingMode`. +pub(crate) fn check_externally_implementable_items<'tcx>(tcx: TyCtxt<'tcx>, (): ()) { + let checking_mode = get_checking_mode(tcx); + + #[derive(Debug)] + struct FoundImpl { + imp: EiiImpl, + impl_crate: CrateNum, + } + + #[derive(Debug)] + struct FoundEii { + decl: EiiDecl, + decl_crate: CrateNum, + impls: FxIndexMap, + } + + let mut eiis = FxIndexMap::::default(); + + // collect all the EII declarations, and possibly implementations from all descendent crates + for &cnum in tcx.crates(()).iter().chain(iter::once(&LOCAL_CRATE)) { + // get the eiis for the crate we're currently looking at + let crate_eiis = tcx.externally_implementable_items(cnum); + + // update or insert the corresponding entries + for (did, (decl, impls)) in crate_eiis { + eiis.entry(*did) + .or_insert_with(|| FoundEii { + decl: *decl, + decl_crate: cnum, + impls: Default::default(), + }) + .impls + .extend( + impls + .into_iter() + .map(|(did, i)| (*did, FoundImpl { imp: *i, impl_crate: cnum })), + ); + } + } + + // now we have all eiis! For each of them, choose one we want to actually generate. + for (decl_did, FoundEii { decl, decl_crate, impls }) in eiis { + let mut default_impls = Vec::new(); + let mut explicit_impls = Vec::new(); + + for (impl_did, FoundImpl { imp, impl_crate }) in impls { + if imp.is_default { + default_impls.push((impl_did, impl_crate)); + } else { + explicit_impls.push((impl_did, impl_crate)); + } + } + + // more than one explicit implementation (across all crates) + // is instantly an error. + if explicit_impls.len() > 1 { + tcx.dcx().emit_err(DuplicateEiiImpls { + name: tcx.item_name(decl_did), + first_span: tcx.def_span(explicit_impls[0].0), + first_crate: tcx.crate_name(explicit_impls[0].1), + second_span: tcx.def_span(explicit_impls[1].0), + second_crate: tcx.crate_name(explicit_impls[1].1), + + help: (), + + additional_crates: (explicit_impls.len() > 2).then_some(()), + num_additional_crates: explicit_impls.len() - 2, + additional_crate_names: explicit_impls[2..] + .iter() + .map(|i| format!("`{}`", tcx.crate_name(i.1))) + .collect::>() + .join(", "), + }); + } + + if default_impls.len() > 1 { + panic!("multiple not supported right now"); + } + + let (local_impl, is_default) = + // note, for a single crate we never need to generate both a default and an explicit implementation. + // In that case, generating the explicit implementation is enough! + match (checking_mode, explicit_impls.first(), default_impls.first()) { + // If we find an explicit implementation, it's instantly the chosen implementation. + (_, Some((explicit, _)), _) => (explicit, false), + // if we find a default implementation, we can emit it but the alias should be weak + (_, _, Some((deflt, _))) => (deflt, true), + + // if we find no explicit implementation, + // that's fine if we're only checking for duplicates. + // The existence will be checked somewhere else in a crate downstream. + (CheckingMode::CheckDuplicates, None, _) => continue, + + // We have a target to generate, but no impl to put in it. error! + (CheckingMode::CheckExistence, None, None) => { + tcx.dcx().emit_err(EiiWithoutImpl { + current_crate_name: tcx.crate_name(LOCAL_CRATE), + decl_crate_name: tcx.crate_name(decl_crate), + name: tcx.item_name(decl_did), + span: decl.span, + help: (), + }); + + continue; + } + }; + + // if it's not local, who cares about generating it. + // That's the local crates' responsibility + let Some(chosen_impl) = local_impl.as_local() else { + continue; + }; + + tracing::debug!("generating EII {chosen_impl:?} (default={is_default})"); + } +} diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index c8db9efe4773..7abd2c703aeb 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -1344,3 +1344,41 @@ pub(crate) struct EiiWithTrackCaller { #[label] pub sig_span: Span, } + +#[derive(Diagnostic)] +#[diag(passes_eii_without_impl)] +pub(crate) struct EiiWithoutImpl { + #[primary_span] + #[label] + pub span: Span, + pub name: Symbol, + + pub current_crate_name: Symbol, + pub decl_crate_name: Symbol, + #[help] + pub help: (), +} + +#[derive(Diagnostic)] +#[diag(passes_duplicate_eii_impls)] +pub(crate) struct DuplicateEiiImpls { + pub name: Symbol, + + #[primary_span] + #[label(passes_first)] + pub first_span: Span, + pub first_crate: Symbol, + + #[label(passes_second)] + pub second_span: Span, + pub second_crate: Symbol, + + #[note] + pub additional_crates: Option<()>, + + pub num_additional_crates: usize, + pub additional_crate_names: String, + + #[help] + pub help: (), +} diff --git a/compiler/rustc_passes/src/lib.rs b/compiler/rustc_passes/src/lib.rs index 149714f943a7..865f2a9c3b8b 100644 --- a/compiler/rustc_passes/src/lib.rs +++ b/compiler/rustc_passes/src/lib.rs @@ -17,6 +17,7 @@ mod check_export; pub mod dead; mod debugger_visualizer; mod diagnostic_items; +mod eii; pub mod entry; mod errors; pub mod hir_id_validator; @@ -43,4 +44,5 @@ pub fn provide(providers: &mut Providers) { stability::provide(providers); upvars::provide(providers); check_export::provide(providers); + providers.check_externally_implementable_items = eii::check_externally_implementable_items; }