Remember names of cfg-ed out items to mention them in diagnostics

`#[cfg]`s are frequently used to gate crate content behind cargo
features. This can lead to very confusing errors when features are
missing. For example, `serde` doesn't have the `derive` feature by
default. Therefore, `serde::Serialize` fails to resolve with a generic
error, even though the macro is present in the docs.

This commit adds a list of all stripped item names to metadata. This is
filled during macro expansion and then, through a fed query, persisted
in metadata. The downstream resolver can then access the metadata to
look at possible candidates for mentioning in the errors.

This slightly increases metadata (800k->809k for the feature-heavy
windows crate), but not enough to really matter.
This commit is contained in:
Nilstrieb 2023-03-10 22:39:14 +01:00
parent 642c92e630
commit a647ba250a
30 changed files with 599 additions and 84 deletions

View file

@ -1,8 +1,10 @@
use std::ptr;
use rustc_ast::expand::StrippedCfgItem;
use rustc_ast::ptr::P;
use rustc_ast::visit::{self, Visitor};
use rustc_ast::{self as ast, Crate, ItemKind, ModKind, NodeId, Path, CRATE_NODE_ID};
use rustc_ast::{MetaItemKind, NestedMetaItem};
use rustc_ast_pretty::pprust;
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::{
@ -776,7 +778,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
.tcx
.sess
.create_err(errs::SelfImportOnlyInImportListWithNonEmptyPrefix { span }),
ResolutionError::FailedToResolve { label, suggestion } => {
ResolutionError::FailedToResolve { last_segment, label, suggestion, module } => {
let mut err =
struct_span_err!(self.tcx.sess, span, E0433, "failed to resolve: {}", &label);
err.span_label(span, label);
@ -789,6 +791,13 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
err.multipart_suggestion(msg, suggestions, applicability);
}
if let Some(ModuleOrUniformRoot::Module(module)) = module
&& let Some(module) = module.opt_def_id()
&& let Some(last_segment) = last_segment
{
self.find_cfg_stripped(&mut err, &last_segment, module);
}
err
}
ResolutionError::CannotCaptureDynamicEnvironmentInFnItem => {
@ -971,9 +980,15 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
VisResolutionError::AncestorOnly(span) => {
self.tcx.sess.create_err(errs::AncestorOnly(span))
}
VisResolutionError::FailedToResolve(span, label, suggestion) => {
self.into_struct_error(span, ResolutionError::FailedToResolve { label, suggestion })
}
VisResolutionError::FailedToResolve(span, label, suggestion) => self.into_struct_error(
span,
ResolutionError::FailedToResolve {
last_segment: None,
label,
suggestion,
module: None,
},
),
VisResolutionError::ExpectedFound(span, path_str, res) => {
self.tcx.sess.create_err(errs::ExpectedFound { span, res, path_str })
}
@ -1721,10 +1736,10 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
ribs: Option<&PerNS<Vec<Rib<'a>>>>,
ignore_binding: Option<&'a NameBinding<'a>>,
module: Option<ModuleOrUniformRoot<'a>>,
i: usize,
failed_segment_idx: usize,
ident: Ident,
) -> (String, Option<Suggestion>) {
let is_last = i == path.len() - 1;
let is_last = failed_segment_idx == path.len() - 1;
let ns = if is_last { opt_ns.unwrap_or(TypeNS) } else { TypeNS };
let module_res = match module {
Some(ModuleOrUniformRoot::Module(module)) => module.res(),
@ -1758,8 +1773,8 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
} else {
(format!("could not find `{ident}` in the crate root"), None)
}
} else if i > 0 {
let parent = path[i - 1].ident.name;
} else if failed_segment_idx > 0 {
let parent = path[failed_segment_idx - 1].ident.name;
let parent = match parent {
// ::foo is mounted at the crate root for 2015, and is the extern
// prelude for 2018+
@ -2207,6 +2222,44 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
None
}
}
/// Finds a cfg-ed out item inside `module` with the matching name.
pub(crate) fn find_cfg_stripped(
&mut self,
err: &mut Diagnostic,
last_segment: &Symbol,
module: DefId,
) {
let local_items;
let symbols = if module.is_local() {
local_items = self
.stripped_cfg_items
.iter()
.filter_map(|item| {
let parent_module = self.opt_local_def_id(item.parent_module)?.to_def_id();
Some(StrippedCfgItem { parent_module, name: item.name, cfg: item.cfg.clone() })
})
.collect::<Vec<_>>();
local_items.as_slice()
} else {
self.tcx.stripped_cfg_items(module.krate)
};
for &StrippedCfgItem { parent_module, name, ref cfg } in symbols {
if parent_module != module || name.name != *last_segment {
continue;
}
err.span_note(name.span, "found an item that was configured out");
if let MetaItemKind::List(nested) = &cfg.kind
&& let NestedMetaItem::MetaItem(meta_item) = &nested[0]
&& let MetaItemKind::NameValue(feature_name) = &meta_item.kind
{
err.note(format!("the item is gated behind the `{}` feature", feature_name.symbol));
}
}
}
}
/// Given a `binding_span` of a binding within a use statement: