EII collection queries

EII collection queries
This commit is contained in:
Jana Dönszelmann 2025-08-12 14:39:53 +02:00
parent 33df6ccb21
commit 1cbdaf246b
No known key found for this signature in database
14 changed files with 332 additions and 17 deletions

View file

@ -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(())

View file

@ -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<DefId, EiiImpl>,
),
>;
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
}

View file

@ -16,6 +16,7 @@
pub use rmeta::provide;
mod dependency_format;
mod eii;
mod foreign_modules;
mod native_libs;
mod rmeta;

View file

@ -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<Item = EiiMapEncodedKeyValue> {
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)))
}

View file

@ -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

View file

@ -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<EiiMapEncodedKeyValue> {
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();

View file

@ -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<EiiMapEncodedKeyValue>,
crate_deps: LazyArray<CrateDep>,
dylib_dependency_formats: LazyArray<Option<LinkagePreference>>,

View file

@ -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_id::DefIndex>,
rustc_hir::def::DefKind,
rustc_hir::def::DocLinkResMap,

View file

@ -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,

View file

@ -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<DefId, (EiiDecl, FxIndexMap<DefId, EiiImpl>)> {
arena_cache
desc { "looking up the externally implementable items of a crate" }
separate_provide_extern
}
}
rustc_with_all_queries! { define_callbacks! }

View file

@ -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(...)`

View file

@ -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<DefId, FoundImpl>,
}
let mut eiis = FxIndexMap::<DefId, FoundEii>::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::<Vec<_>>()
.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})");
}
}

View file

@ -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: (),
}

View file

@ -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;
}