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,3 +1,20 @@
//! Definitions shared by macros / syntax extensions and e.g. `rustc_middle`.
use rustc_span::{def_id::DefId, symbol::Ident};
use crate::MetaItem;
pub mod allocator;
#[derive(Debug, Clone, Encodable, Decodable, HashStable_Generic)]
pub struct StrippedCfgItem<ModId = DefId> {
pub parent_module: ModId,
pub name: Ident,
pub cfg: MetaItem,
}
impl<ModId> StrippedCfgItem<ModId> {
pub fn map_mod_id<New>(self, f: impl FnOnce(ModId) -> New) -> StrippedCfgItem<New> {
StrippedCfgItem { parent_module: f(self.parent_module), name: self.name, cfg: self.cfg }
}
}

View File

@ -947,6 +947,8 @@ pub trait ResolverExpand {
/// HIR proc macros items back to their harness items.
fn declare_proc_macro(&mut self, id: NodeId);
fn append_stripped_cfg_item(&mut self, parent_node: NodeId, name: Ident, cfg: ast::MetaItem);
/// Tools registered with `#![register_tool]` and used by tool attributes and lints.
fn registered_tools(&self) -> &RegisteredTools;
}
@ -965,7 +967,7 @@ pub trait LintStoreExpand {
type LintStoreExpandDyn<'a> = Option<&'a (dyn LintStoreExpand + 'a)>;
#[derive(Clone, Default)]
#[derive(Debug, Clone, Default)]
pub struct ModuleData {
/// Path to the module starting from the crate name, like `my_crate::foo::bar`.
pub mod_path: Vec<Ident>,

View File

@ -416,20 +416,28 @@ impl<'a> StripUnconfigured<'a> {
/// Determines if a node with the given attributes should be included in this configuration.
fn in_cfg(&self, attrs: &[Attribute]) -> bool {
attrs.iter().all(|attr| !is_cfg(attr) || self.cfg_true(attr))
attrs.iter().all(|attr| !is_cfg(attr) || self.cfg_true(attr).0)
}
pub(crate) fn cfg_true(&self, attr: &Attribute) -> bool {
pub(crate) fn cfg_true(&self, attr: &Attribute) -> (bool, Option<MetaItem>) {
let meta_item = match validate_attr::parse_meta(&self.sess.parse_sess, attr) {
Ok(meta_item) => meta_item,
Err(mut err) => {
err.emit();
return true;
return (true, None);
}
};
parse_cfg(&meta_item, &self.sess).map_or(true, |meta_item| {
attr::cfg_matches(&meta_item, &self.sess.parse_sess, self.lint_node_id, self.features)
})
(
parse_cfg(&meta_item, &self.sess).map_or(true, |meta_item| {
attr::cfg_matches(
&meta_item,
&self.sess.parse_sess,
self.lint_node_id,
self.features,
)
}),
Some(meta_item),
)
}
/// If attributes are not allowed on expressions, emit an error for `attr`

View File

@ -1042,6 +1042,12 @@ trait InvocationCollectorNode: HasAttrs + HasNodeId + Sized {
fn expand_cfg_false(&mut self, collector: &mut InvocationCollector<'_, '_>, span: Span) {
collector.cx.emit_err(RemoveNodeNotSupported { span, descr: Self::descr() });
}
/// All of the names (items) declared by this node.
/// This is an approximation and should only be used for diagnostics.
fn declared_names(&self) -> Vec<Ident> {
vec![]
}
}
impl InvocationCollectorNode for P<ast::Item> {
@ -1148,6 +1154,27 @@ impl InvocationCollectorNode for P<ast::Item> {
collector.cx.current_expansion.module = orig_module;
res
}
fn declared_names(&self) -> Vec<Ident> {
if let ItemKind::Use(ut) = &self.kind {
fn collect_use_tree_leaves(ut: &ast::UseTree, idents: &mut Vec<Ident>) {
match &ut.kind {
ast::UseTreeKind::Glob => {}
ast::UseTreeKind::Simple(_) => idents.push(ut.ident()),
ast::UseTreeKind::Nested(nested) => {
for (ut, _) in nested {
collect_use_tree_leaves(&ut, idents);
}
}
}
}
let mut idents = Vec::new();
collect_use_tree_leaves(&ut, &mut idents);
return idents;
}
vec![self.ident]
}
}
struct TraitItemTag;
@ -1685,8 +1712,8 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
node: &mut impl HasAttrs,
attr: ast::Attribute,
pos: usize,
) -> bool {
let res = self.cfg().cfg_true(&attr);
) -> (bool, Option<ast::MetaItem>) {
let (res, meta_item) = self.cfg().cfg_true(&attr);
if res {
// FIXME: `cfg(TRUE)` attributes do not currently remove themselves during expansion,
// and some tools like rustdoc and clippy rely on that. Find a way to remove them
@ -1694,7 +1721,8 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
self.cx.expanded_inert_attrs.mark(&attr);
node.visit_attrs(|attrs| attrs.insert(pos, attr));
}
res
(res, meta_item)
}
fn expand_cfg_attr(&self, node: &mut impl HasAttrs, attr: &ast::Attribute, pos: usize) {
@ -1715,9 +1743,20 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
return match self.take_first_attr(&mut node) {
Some((attr, pos, derives)) => match attr.name_or_empty() {
sym::cfg => {
if self.expand_cfg_true(&mut node, attr, pos) {
let (res, meta_item) = self.expand_cfg_true(&mut node, attr, pos);
if res {
continue;
}
if let Some(meta_item) = meta_item {
for name in node.declared_names() {
self.cx.resolver.append_stripped_cfg_item(
self.cx.current_expansion.lint_node_id,
name,
meta_item.clone(),
)
}
}
Default::default()
}
sym::cfg_attr => {
@ -1761,7 +1800,7 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
Some((attr, pos, derives)) => match attr.name_or_empty() {
sym::cfg => {
let span = attr.span;
if self.expand_cfg_true(node, attr, pos) {
if self.expand_cfg_true(node, attr, pos).0 {
continue;
}

View File

@ -995,6 +995,15 @@ impl<'a, 'tcx> CrateMetadataRef<'a> {
)
}
fn get_stripped_cfg_items(self, cnum: CrateNum, tcx: TyCtxt<'tcx>) -> &'tcx [StrippedCfgItem] {
let item_names = self
.root
.stripped_cfg_items
.decode((self, tcx))
.map(|item| item.map_mod_id(|index| DefId { krate: cnum, index }));
tcx.arena.alloc_from_iter(item_names)
}
/// Iterates over the diagnostic items in the given crate.
fn get_diagnostic_items(self) -> DiagnosticItems {
let mut id_to_name = FxHashMap::default();

View File

@ -345,6 +345,7 @@ provide! { tcx, def_id, other, cdata,
stability_implications => {
cdata.get_stability_implications(tcx).iter().copied().collect()
}
stripped_cfg_items => { cdata.get_stripped_cfg_items(cdata.cnum, tcx) }
is_intrinsic => { cdata.get_is_intrinsic(def_id.index) }
defined_lang_items => { cdata.get_lang_items(tcx) }
diagnostic_items => { cdata.get_diagnostic_items() }

View File

@ -3,6 +3,7 @@ use crate::rmeta::def_path_hash_map::DefPathHashMapRef;
use crate::rmeta::table::TableBuilder;
use crate::rmeta::*;
use rustc_ast::expand::StrippedCfgItem;
use rustc_ast::Attribute;
use rustc_data_structures::fingerprint::Fingerprint;
use rustc_data_structures::fx::{FxHashMap, FxIndexSet};
@ -584,6 +585,8 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
(self.encode_lang_items(), self.encode_lang_items_missing())
});
let stripped_cfg_items = stat!("stripped-cfg-items", || self.encode_stripped_cfg_items());
let diagnostic_items = stat!("diagnostic-items", || self.encode_diagnostic_items());
let native_libraries = stat!("native-libs", || self.encode_native_libraries());
@ -694,6 +697,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
lang_items,
diagnostic_items,
lang_items_missing,
stripped_cfg_items,
native_libraries,
foreign_modules,
source_map,
@ -1940,6 +1944,15 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
self.lazy_array(&tcx.lang_items().missing)
}
fn encode_stripped_cfg_items(&mut self) -> LazyArray<StrippedCfgItem<DefIndex>> {
self.lazy_array(
self.tcx
.stripped_cfg_items(LOCAL_CRATE)
.into_iter()
.map(|item| item.clone().map_mod_id(|def_id| def_id.index)),
)
}
fn encode_traits(&mut self) -> LazyArray<DefIndex> {
empty_proc_macro!(self);
self.lazy_array(self.tcx.traits(LOCAL_CRATE).iter().map(|def_id| def_id.index))

View File

@ -6,6 +6,7 @@ use rustc_middle::middle::debugger_visualizer::DebuggerVisualizerFile;
use table::TableBuilder;
use rustc_ast as ast;
use rustc_ast::expand::StrippedCfgItem;
use rustc_attr as attr;
use rustc_data_structures::svh::Svh;
use rustc_hir as hir;
@ -256,6 +257,7 @@ pub(crate) struct CrateRoot {
stability_implications: LazyArray<(Symbol, Symbol)>,
lang_items: LazyArray<(DefIndex, LangItem)>,
lang_items_missing: LazyArray<LangItem>,
stripped_cfg_items: LazyArray<StrippedCfgItem<DefIndex>>,
diagnostic_items: LazyArray<(Symbol, DefIndex)>,
native_libraries: LazyArray<NativeLib>,
foreign_modules: LazyArray<ForeignModule>,

View File

@ -124,6 +124,7 @@ macro_rules! arena_types {
[] predefined_opaques_in_body: rustc_middle::traits::solve::PredefinedOpaquesData<'tcx>,
[decode] doc_link_resolutions: rustc_hir::def::DocLinkResMap,
[] closure_kind_origin: (rustc_span::Span, rustc_middle::hir::place::Place<'tcx>),
[] stripped_cfg_items: rustc_ast::expand::StrippedCfgItem,
[] mod_child: rustc_middle::metadata::ModChild,
]);
)

View File

@ -54,7 +54,7 @@ use crate::ty::{
};
use rustc_arena::TypedArena;
use rustc_ast as ast;
use rustc_ast::expand::allocator::AllocatorKind;
use rustc_ast::expand::{allocator::AllocatorKind, StrippedCfgItem};
use rustc_attr as attr;
use rustc_data_structures::fingerprint::Fingerprint;
use rustc_data_structures::fx::{FxHashMap, FxIndexMap, FxIndexSet};
@ -2173,6 +2173,15 @@ rustc_queries! {
query check_tys_might_be_eq(arg: Canonical<'tcx, (ty::ParamEnv<'tcx>, Ty<'tcx>, Ty<'tcx>)>) -> Result<(), NoSolution> {
desc { "check whether two const param are definitely not equal to eachother"}
}
/// Get all item paths that were stripped by a `#[cfg]` in a particular crate.
/// Should not be called for the local crate before the resolver outputs are created, as it
/// is only fed there.
query stripped_cfg_items(cnum: CrateNum) -> &'tcx [StrippedCfgItem] {
feedable
desc { "getting cfg-ed out item names" }
separate_provide_extern
}
}
rustc_query_append! { define_callbacks! }

View File

@ -73,6 +73,7 @@ trivially_parameterized_over_tcx! {
ty::fast_reject::SimplifiedType,
rustc_ast::Attribute,
rustc_ast::DelimArgs,
rustc_ast::expand::StrippedCfgItem<rustc_hir::def_id::DefIndex>,
rustc_attr::ConstStability,
rustc_attr::DefaultBodyStability,
rustc_attr::Deprecation,

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:

View File

@ -330,6 +330,7 @@ pub(crate) struct ParamInTyOfConstParam {
pub(crate) param_kind: Option<ParamKindInTyOfConstParam>,
}
#[derive(Debug)]
#[derive(Subdiagnostic)]
pub(crate) enum ParamKindInTyOfConstParam {
#[note(resolve_type_param_in_ty_of_const_param)]
@ -365,6 +366,7 @@ pub(crate) struct ParamInNonTrivialAnonConst {
#[help(resolve_param_in_non_trivial_anon_const_help)]
pub(crate) struct ParamInNonTrivialAnonConstHelp;
#[derive(Debug)]
#[derive(Subdiagnostic)]
pub(crate) enum ParamKindInNonTrivialAnonConst {
#[note(resolve_type_param_in_non_trivial_anon_const)]
@ -562,6 +564,7 @@ pub(crate) struct CfgAccessibleUnsure {
pub(crate) span: Span,
}
#[derive(Debug)]
#[derive(Diagnostic)]
#[diag(resolve_param_in_enum_discriminant)]
pub(crate) struct ParamInEnumDiscriminant {
@ -573,6 +576,7 @@ pub(crate) struct ParamInEnumDiscriminant {
pub(crate) param_kind: ParamKindInEnumDiscriminant,
}
#[derive(Debug)]
#[derive(Subdiagnostic)]
pub(crate) enum ParamKindInEnumDiscriminant {
#[note(resolve_type_param_in_enum_discriminant)]

View File

@ -1365,20 +1365,12 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
ribs: Option<&PerNS<Vec<Rib<'a>>>>,
ignore_binding: Option<&'a NameBinding<'a>>,
) -> PathResult<'a> {
debug!(
"resolve_path(path={:?}, opt_ns={:?}, finalize={:?}) path_len: {}",
path,
opt_ns,
finalize,
path.len()
);
let mut module = None;
let mut allow_super = true;
let mut second_binding = None;
for (i, &Segment { ident, id, .. }) in path.iter().enumerate() {
debug!("resolve_path ident {} {:?} {:?}", i, ident, id);
for (segment_idx, &Segment { ident, id, .. }) in path.iter().enumerate() {
debug!("resolve_path ident {} {:?} {:?}", segment_idx, ident, id);
let record_segment_res = |this: &mut Self, res| {
if finalize.is_some() {
if let Some(id) = id {
@ -1390,7 +1382,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
}
};
let is_last = i + 1 == path.len();
let is_last = segment_idx + 1 == path.len();
let ns = if is_last { opt_ns.unwrap_or(TypeNS) } else { TypeNS };
let name = ident.name;
@ -1399,7 +1391,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
if ns == TypeNS {
if allow_super && name == kw::Super {
let mut ctxt = ident.span.ctxt().normalize_to_macros_2_0();
let self_module = match i {
let self_module = match segment_idx {
0 => Some(self.resolve_self(&mut ctxt, parent_scope.module)),
_ => match module {
Some(ModuleOrUniformRoot::Module(module)) => Some(module),
@ -1414,11 +1406,15 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
continue;
}
}
return PathResult::failed(ident.span, false, finalize.is_some(), || {
("there are too many leading `super` keywords".to_string(), None)
});
return PathResult::failed(
ident.span,
false,
finalize.is_some(),
module,
|| ("there are too many leading `super` keywords".to_string(), None),
);
}
if i == 0 {
if segment_idx == 0 {
if name == kw::SelfLower {
let mut ctxt = ident.span.ctxt().normalize_to_macros_2_0();
module = Some(ModuleOrUniformRoot::Module(
@ -1447,14 +1443,14 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
}
// Report special messages for path segment keywords in wrong positions.
if ident.is_path_segment_keyword() && i != 0 {
return PathResult::failed(ident.span, false, finalize.is_some(), || {
if ident.is_path_segment_keyword() && segment_idx != 0 {
return PathResult::failed(ident.span, false, finalize.is_some(), module, || {
let name_str = if name == kw::PathRoot {
"crate root".to_string()
} else {
format!("`{}`", name)
};
let label = if i == 1 && path[0].ident.name == kw::PathRoot {
let label = if segment_idx == 1 && path[0].ident.name == kw::PathRoot {
format!("global paths cannot start with {}", name_str)
} else {
format!("{} in paths can only be used in start position", name_str)
@ -1519,7 +1515,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
};
match binding {
Ok(binding) => {
if i == 1 {
if segment_idx == 1 {
second_binding = Some(binding);
}
let res = binding.res();
@ -1543,17 +1539,23 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
record_segment_res(self, res);
return PathResult::NonModule(PartialRes::with_unresolved_segments(
res,
path.len() - i - 1,
path.len() - segment_idx - 1,
));
} else {
return PathResult::failed(ident.span, is_last, finalize.is_some(), || {
let label = format!(
"`{ident}` is {} {}, not a module",
res.article(),
res.descr()
);
(label, None)
});
return PathResult::failed(
ident.span,
is_last,
finalize.is_some(),
module,
|| {
let label = format!(
"`{ident}` is {} {}, not a module",
res.article(),
res.descr()
);
(label, None)
},
);
}
}
Err(Undetermined) => return PathResult::Indeterminate,
@ -1562,23 +1564,29 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
if opt_ns.is_some() && !module.is_normal() {
return PathResult::NonModule(PartialRes::with_unresolved_segments(
module.res().unwrap(),
path.len() - i,
path.len() - segment_idx,
));
}
}
return PathResult::failed(ident.span, is_last, finalize.is_some(), || {
self.report_path_resolution_error(
path,
opt_ns,
parent_scope,
ribs,
ignore_binding,
module,
i,
ident,
)
});
return PathResult::failed(
ident.span,
is_last,
finalize.is_some(),
module,
|| {
self.report_path_resolution_error(
path,
opt_ns,
parent_scope,
ribs,
ignore_binding,
module,
segment_idx,
ident,
)
},
);
}
}
}

View File

@ -803,14 +803,34 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
module
}
PathResult::Failed { is_error_from_last_segment: false, span, label, suggestion } => {
PathResult::Failed {
is_error_from_last_segment: false,
span,
label,
suggestion,
module,
} => {
if no_ambiguity {
assert!(import.imported_module.get().is_none());
self.report_error(span, ResolutionError::FailedToResolve { label, suggestion });
self.report_error(
span,
ResolutionError::FailedToResolve {
last_segment: None,
label,
suggestion,
module,
},
);
}
return None;
}
PathResult::Failed { is_error_from_last_segment: true, span, label, suggestion } => {
PathResult::Failed {
is_error_from_last_segment: true,
span,
label,
suggestion,
..
} => {
if no_ambiguity {
assert!(import.imported_module.get().is_none());
let err = match self.make_path_suggestion(

View File

@ -3524,7 +3524,7 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> {
None
};
this.r.use_injections.push(UseError {
let ue = UseError {
err,
candidates,
def_id,
@ -3532,7 +3532,9 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> {
suggestion,
path: path.into(),
is_call: source.is_call(),
});
};
this.r.use_injections.push(ue);
}
PartialRes::new(Res::Err)
@ -3866,8 +3868,22 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> {
PathResult::Module(ModuleOrUniformRoot::Module(module)) => {
PartialRes::new(module.res().unwrap())
}
PathResult::Failed { is_error_from_last_segment: false, span, label, suggestion } => {
return Err(respan(span, ResolutionError::FailedToResolve { label, suggestion }));
PathResult::Failed {
is_error_from_last_segment: false,
span,
label,
suggestion,
module,
} => {
return Err(respan(
span,
ResolutionError::FailedToResolve {
last_segment: None,
label,
suggestion,
module,
},
));
}
PathResult::Module(..) | PathResult::Failed { .. } => return Ok(None),
PathResult::Indeterminate => bug!("indeterminate path result in resolve_qpath"),

View File

@ -149,6 +149,7 @@ struct BaseError {
span_label: Option<(Span, &'static str)>,
could_be_expr: bool,
suggestion: Option<(Span, &'static str, String)>,
module: Option<DefId>,
}
#[derive(Debug)]
@ -210,10 +211,11 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> {
_ => false,
},
suggestion: None,
module: None,
}
} else {
let item_span = path.last().unwrap().ident.span;
let (mod_prefix, mod_str, suggestion) = if path.len() == 1 {
let (mod_prefix, mod_str, module, suggestion) = if path.len() == 1 {
debug!(?self.diagnostic_metadata.current_impl_items);
debug!(?self.diagnostic_metadata.current_function);
let suggestion = if self.current_trait_ref.is_none()
@ -247,26 +249,37 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> {
} else {
None
};
(String::new(), "this scope".to_string(), suggestion)
(String::new(), "this scope".to_string(), None, suggestion)
} else if path.len() == 2 && path[0].ident.name == kw::PathRoot {
if self.r.tcx.sess.edition() > Edition::Edition2015 {
// In edition 2018 onwards, the `::foo` syntax may only pull from the extern prelude
// which overrides all other expectations of item type
expected = "crate";
(String::new(), "the list of imported crates".to_string(), None)
(String::new(), "the list of imported crates".to_string(), None, None)
} else {
(String::new(), "the crate root".to_string(), None)
(
String::new(),
"the crate root".to_string(),
Some(CRATE_DEF_ID.to_def_id()),
None,
)
}
} else if path.len() == 2 && path[0].ident.name == kw::Crate {
(String::new(), "the crate root".to_string(), None)
(String::new(), "the crate root".to_string(), Some(CRATE_DEF_ID.to_def_id()), None)
} else {
let mod_path = &path[..path.len() - 1];
let mod_prefix = match self.resolve_path(mod_path, Some(TypeNS), None) {
let mod_res = self.resolve_path(mod_path, Some(TypeNS), None);
let mod_prefix = match mod_res {
PathResult::Module(ModuleOrUniformRoot::Module(module)) => module.res(),
_ => None,
}
.map_or_else(String::new, |res| format!("{} ", res.descr()));
(mod_prefix, format!("`{}`", Segment::names_to_string(mod_path)), None)
};
let module_did = mod_prefix.as_ref().and_then(Res::mod_def_id);
let mod_prefix =
mod_prefix.map_or_else(String::new, |res| (format!("{} ", res.descr())));
(mod_prefix, format!("`{}`", Segment::names_to_string(mod_path)), module_did, None)
};
let (fallback_label, suggestion) = if path_str == "async"
@ -300,6 +313,7 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> {
span_label: None,
could_be_expr: false,
suggestion,
module,
}
}
}
@ -315,6 +329,7 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> {
) -> (DiagnosticBuilder<'tcx, ErrorGuaranteed>, Vec<ImportSuggestion>) {
debug!(?res, ?source);
let base_error = self.make_base_error(path, span, source, res);
let code = source.error_code(res.is_some());
let mut err = self.r.tcx.sess.struct_span_err_with_code(
base_error.span,
@ -366,6 +381,10 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> {
}
self.err_code_special_cases(&mut err, source, path, span);
if let Some(module) = base_error.module {
self.r.find_cfg_stripped(&mut err, &path.last().unwrap().ident.name, module);
}
(err, candidates)
}

View File

@ -25,6 +25,7 @@ use errors::{
ParamKindInEnumDiscriminant, ParamKindInNonTrivialAnonConst, ParamKindInTyOfConstParam,
};
use rustc_arena::{DroplessArena, TypedArena};
use rustc_ast::expand::StrippedCfgItem;
use rustc_ast::node_id::NodeMap;
use rustc_ast::{self as ast, attr, NodeId, CRATE_NODE_ID};
use rustc_ast::{AngleBracketedArg, Crate, Expr, ExprKind, GenericArg, GenericArgs, LitKind, Path};
@ -171,6 +172,7 @@ enum ImplTraitContext {
Universal(LocalDefId),
}
#[derive(Debug)]
struct BindingError {
name: Symbol,
origin: BTreeSet<Span>,
@ -178,6 +180,7 @@ struct BindingError {
could_be_path: bool,
}
#[derive(Debug)]
enum ResolutionError<'a> {
/// Error E0401: can't use type or const parameters from outer function.
GenericParamsFromOuterFunction(Res, HasGenericParams),
@ -207,7 +210,12 @@ enum ResolutionError<'a> {
/// Error E0431: `self` import can only appear in an import list with a non-empty prefix.
SelfImportOnlyInImportListWithNonEmptyPrefix,
/// Error E0433: failed to resolve.
FailedToResolve { label: String, suggestion: Option<Suggestion> },
FailedToResolve {
last_segment: Option<Symbol>,
label: String,
suggestion: Option<Suggestion>,
module: Option<ModuleOrUniformRoot<'a>>,
},
/// Error E0434: can't capture dynamic environment in a fn item.
CannotCaptureDynamicEnvironmentInFnItem,
/// Error E0435: attempt to use a non-constant value in a constant.
@ -402,6 +410,7 @@ enum PathResult<'a> {
label: String,
suggestion: Option<Suggestion>,
is_error_from_last_segment: bool,
module: Option<ModuleOrUniformRoot<'a>>,
},
}
@ -410,11 +419,12 @@ impl<'a> PathResult<'a> {
span: Span,
is_error_from_last_segment: bool,
finalize: bool,
module: Option<ModuleOrUniformRoot<'a>>,
label_and_suggestion: impl FnOnce() -> (String, Option<Suggestion>),
) -> PathResult<'a> {
let (label, suggestion) =
if finalize { label_and_suggestion() } else { (String::new(), None) };
PathResult::Failed { span, label, suggestion, is_error_from_last_segment }
PathResult::Failed { span, label, suggestion, is_error_from_last_segment, module }
}
}
@ -685,6 +695,7 @@ struct PrivacyError<'a> {
dedup_span: Span,
}
#[derive(Debug)]
struct UseError<'a> {
err: DiagnosticBuilder<'a, ErrorGuaranteed>,
/// Candidates which user could `use` to access the missing type.
@ -1059,6 +1070,9 @@ pub struct Resolver<'a, 'tcx> {
/// Whether lifetime elision was successful.
lifetime_elision_allowed: FxHashSet<NodeId>,
/// Names of items that were stripped out via cfg with their corresponding cfg meta item.
stripped_cfg_items: Vec<StrippedCfgItem<NodeId>>,
effective_visibilities: EffectiveVisibilities,
doc_link_resolutions: FxHashMap<LocalDefId, DocLinkResMap>,
doc_link_traits_in_scope: FxHashMap<LocalDefId, Vec<DefId>>,
@ -1353,6 +1367,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
proc_macros: Default::default(),
confused_type_with_std_module: Default::default(),
lifetime_elision_allowed: Default::default(),
stripped_cfg_items: Default::default(),
effective_visibilities: Default::default(),
doc_link_resolutions: Default::default(),
doc_link_traits_in_scope: Default::default(),
@ -1410,6 +1425,14 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
let main_def = self.main_def;
let confused_type_with_std_module = self.confused_type_with_std_module;
let effective_visibilities = self.effective_visibilities;
self.tcx.feed_local_crate().stripped_cfg_items(self.tcx.arena.alloc_from_iter(
self.stripped_cfg_items.into_iter().filter_map(|item| {
let parent_module = self.node_id_to_def_id.get(&item.parent_module)?.to_def_id();
Some(StrippedCfgItem { parent_module, name: item.name, cfg: item.cfg })
}),
));
let global_ctxt = ResolverGlobalCtxt {
expn_that_defined,
visibilities,

View File

@ -6,6 +6,7 @@ use crate::Namespace::*;
use crate::{BuiltinMacroState, Determinacy};
use crate::{DeriveData, Finalize, ParentScope, ResolutionError, Resolver, ScopeSet};
use crate::{ModuleKind, ModuleOrUniformRoot, NameBinding, PathResult, Segment};
use rustc_ast::expand::StrippedCfgItem;
use rustc_ast::{self as ast, attr, Inline, ItemKind, ModKind, NodeId};
use rustc_ast_pretty::pprust;
use rustc_attr::StabilityLevel;
@ -465,6 +466,10 @@ impl<'a, 'tcx> ResolverExpand for Resolver<'a, 'tcx> {
self.proc_macros.push(id)
}
fn append_stripped_cfg_item(&mut self, parent_node: NodeId, name: Ident, cfg: ast::MetaItem) {
self.stripped_cfg_items.push(StrippedCfgItem { parent_module: parent_node, name, cfg });
}
fn registered_tools(&self) -> &RegisteredTools {
&self.registered_tools
}
@ -721,7 +726,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
}
path_res @ (PathResult::NonModule(..) | PathResult::Failed { .. }) => {
let mut suggestion = None;
let (span, label) = if let PathResult::Failed { span, label, .. } = path_res {
let (span, label, module) = if let PathResult::Failed { span, label, module, .. } = path_res {
// try to suggest if it's not a macro, maybe a function
if let PathResult::NonModule(partial_res) = self.maybe_resolve_path(&path, Some(ValueNS), &parent_scope)
&& partial_res.unresolved_segments() == 0 {
@ -733,7 +738,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
Applicability::MaybeIncorrect
));
}
(span, label)
(span, label, module)
} else {
(
path_span,
@ -742,11 +747,12 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
kind.article(),
kind.descr()
),
None,
)
};
self.report_error(
span,
ResolutionError::FailedToResolve { label, suggestion },
ResolutionError::FailedToResolve { last_segment: path.last().map(|segment| segment.ident.name), label, suggestion, module },
);
}
PathResult::Module(..) | PathResult::Indeterminate => unreachable!(),

View File

@ -0,0 +1,22 @@
pub mod inner {
#[cfg(FALSE)]
pub fn uwu() {}
#[cfg(FALSE)]
pub mod doesnt_exist {
pub fn hello() {}
}
pub mod wrong {
#[cfg(feature = "suggesting me fails the test!!")]
pub fn meow() {}
}
pub mod right {
#[cfg(feature = "what-a-cool-feature")]
pub fn meow() {}
}
}
#[cfg(i_dont_exist_and_you_can_do_nothing_about_it)]
pub fn vanished() {}

View File

@ -0,0 +1,31 @@
// aux-build:cfged_out.rs
extern crate cfged_out;
fn main() {
// There is no uwu at this path - no diagnostic.
cfged_out::uwu(); //~ ERROR cannot find function
//~^ NOTE not found in `cfged_out`
// It does exist here - diagnostic.
cfged_out::inner::uwu(); //~ ERROR cannot find function
//~^ NOTE found an item that was configured out
//~| NOTE not found in `cfged_out::inner`
// The module isn't found - we would like to get a diagnostic, but currently don't due to
// the awkward way the resolver diagnostics are currently implemented.
// FIXME(Nilstrieb): Also add a note to the cfg diagnostic here
cfged_out::inner::doesnt_exist::hello(); //~ ERROR failed to resolve
//~^ NOTE could not find `doesnt_exist` in `inner`
// It should find the one in the right module, not the wrong one.
cfged_out::inner::right::meow(); //~ ERROR cannot find function
//~^ NOTE found an item that was configured out
//~| NOTE not found in `cfged_out::inner::right
//~| NOTE the item is gated behind the `what-a-cool-feature` feature
// Exists in the crate root - diagnostic.
cfged_out::vanished(); //~ ERROR cannot find function
//~^ NOTE found an item that was configured out
//~| NOTE not found in `cfged_out`
}

View File

@ -0,0 +1,53 @@
error[E0433]: failed to resolve: could not find `doesnt_exist` in `inner`
--> $DIR/diagnostics-cross-crate.rs:18:23
|
LL | cfged_out::inner::doesnt_exist::hello();
| ^^^^^^^^^^^^ could not find `doesnt_exist` in `inner`
error[E0425]: cannot find function `uwu` in crate `cfged_out`
--> $DIR/diagnostics-cross-crate.rs:7:16
|
LL | cfged_out::uwu();
| ^^^ not found in `cfged_out`
error[E0425]: cannot find function `uwu` in module `cfged_out::inner`
--> $DIR/diagnostics-cross-crate.rs:11:23
|
LL | cfged_out::inner::uwu();
| ^^^ not found in `cfged_out::inner`
|
note: found an item that was configured out
--> $DIR/auxiliary/cfged_out.rs:3:12
|
LL | pub fn uwu() {}
| ^^^
error[E0425]: cannot find function `meow` in module `cfged_out::inner::right`
--> $DIR/diagnostics-cross-crate.rs:22:30
|
LL | cfged_out::inner::right::meow();
| ^^^^ not found in `cfged_out::inner::right`
|
note: found an item that was configured out
--> $DIR/auxiliary/cfged_out.rs:17:16
|
LL | pub fn meow() {}
| ^^^^
= note: the item is gated behind the `what-a-cool-feature` feature
error[E0425]: cannot find function `vanished` in crate `cfged_out`
--> $DIR/diagnostics-cross-crate.rs:28:16
|
LL | cfged_out::vanished();
| ^^^^^^^^ not found in `cfged_out`
|
note: found an item that was configured out
--> $DIR/auxiliary/cfged_out.rs:22:8
|
LL | pub fn vanished() {}
| ^^^^^^^^
error: aborting due to 5 previous errors
Some errors have detailed explanations: E0425, E0433.
For more information about an error, try `rustc --explain E0425`.

View File

@ -0,0 +1,12 @@
pub mod inner {
pub fn i_am_here() {
#[cfg(feature = "another one that doesn't exist")]
loop {}
}
}
fn main() {
inner::i_am_here();
// ensure that nothing bad happens when we are checking for cfgs
inner::i_am_not(); //~ ERROR cannot find function
}

View File

@ -0,0 +1,9 @@
error[E0425]: cannot find function `i_am_not` in module `inner`
--> $DIR/diagnostics-not-a-def.rs:11:12
|
LL | inner::i_am_not();
| ^^^^^^^^ not found in `inner`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0425`.

View File

@ -0,0 +1,16 @@
pub mod inner {
#[cfg(FALSE)]
mod gone {
pub fn uwu() {}
}
#[cfg(FALSE)]
pub use super::uwu;
//~^ NOTE found an item that was configured out
}
fn main() {
// There is no uwu at this path - no diagnostic.
inner::uwu(); //~ ERROR cannot find function
//~^ NOTE not found in `inner`
}

View File

@ -0,0 +1,15 @@
error[E0425]: cannot find function `uwu` in module `inner`
--> $DIR/diagnostics-reexport.rs:14:12
|
LL | inner::uwu();
| ^^^ not found in `inner`
|
note: found an item that was configured out
--> $DIR/diagnostics-reexport.rs:8:20
|
LL | pub use super::uwu;
| ^^^
error: aborting due to previous error
For more information about this error, try `rustc --explain E0425`.

View File

@ -0,0 +1,51 @@
pub mod inner {
#[cfg(FALSE)]
pub fn uwu() {}
//~^ NOTE found an item that was configured out
#[cfg(FALSE)]
pub mod doesnt_exist {
pub fn hello() {}
}
pub mod wrong {
#[cfg(feature = "suggesting me fails the test!!")]
pub fn meow() {}
}
pub mod right {
#[cfg(feature = "what-a-cool-feature")]
pub fn meow() {}
//~^ NOTE found an item that was configured out
}
}
#[cfg(i_dont_exist_and_you_can_do_nothing_about_it)]
pub fn vanished() {}
fn main() {
// There is no uwu at this path - no diagnostic.
uwu(); //~ ERROR cannot find function
//~^ NOTE not found in this scope
// It does exist here - diagnostic.
inner::uwu(); //~ ERROR cannot find function
//~| NOTE not found in `inner`
// The module isn't found - we would like to get a diagnostic, but currently don't due to
// the awkward way the resolver diagnostics are currently implemented.
// FIXME(Nilstrieb): Also add a note to the cfg diagnostic here
inner::doesnt_exist::hello(); //~ ERROR failed to resolve
//~| NOTE could not find `doesnt_exist` in `inner`
// It should find the one in the right module, not the wrong one.
inner::right::meow(); //~ ERROR cannot find function
//~| NOTE not found in `inner::right
//~| NOTE the item is gated behind the `what-a-cool-feature` feature
// Exists in the crate root - we would generally want a diagnostic,
// but currently don't have one.
// Not that it matters much though, this is highly unlikely to confuse anyone.
vanished(); //~ ERROR cannot find function
//~^ NOTE not found in this scope
}

View File

@ -0,0 +1,47 @@
error[E0433]: failed to resolve: could not find `doesnt_exist` in `inner`
--> $DIR/diagnostics-same-crate.rs:38:12
|
LL | inner::doesnt_exist::hello();
| ^^^^^^^^^^^^ could not find `doesnt_exist` in `inner`
error[E0425]: cannot find function `uwu` in module `inner`
--> $DIR/diagnostics-same-crate.rs:32:12
|
LL | inner::uwu();
| ^^^ not found in `inner`
|
note: found an item that was configured out
--> $DIR/diagnostics-same-crate.rs:3:12
|
LL | pub fn uwu() {}
| ^^^
error[E0425]: cannot find function `meow` in module `inner::right`
--> $DIR/diagnostics-same-crate.rs:42:19
|
LL | inner::right::meow();
| ^^^^ not found in `inner::right`
|
note: found an item that was configured out
--> $DIR/diagnostics-same-crate.rs:18:16
|
LL | pub fn meow() {}
| ^^^^
= note: the item is gated behind the `what-a-cool-feature` feature
error[E0425]: cannot find function `uwu` in this scope
--> $DIR/diagnostics-same-crate.rs:28:5
|
LL | uwu();
| ^^^ not found in this scope
error[E0425]: cannot find function `vanished` in this scope
--> $DIR/diagnostics-same-crate.rs:49:5
|
LL | vanished();
| ^^^^^^^^ not found in this scope
error: aborting due to 5 previous errors
Some errors have detailed explanations: E0425, E0433.
For more information about an error, try `rustc --explain E0425`.

View File

@ -93,6 +93,9 @@ error[E0433]: failed to resolve: could not find `test` in `std`
|
LL | #[std::test]
| ^^^^ could not find `test` in `std`
|
note: found an item that was configured out
--> $SRC_DIR/std/src/lib.rs:LL:COL
error: aborting due to 16 previous errors

View File

@ -4,6 +4,11 @@ error[E0425]: cannot find function `bar` in module `a`
LL | a::bar();
| ^^^ not found in `a`
|
note: found an item that was configured out
--> $DIR/macro-outer-attributes.rs:9:14
|
LL | pub fn bar() { });
| ^^^
help: consider importing this function
|
LL + use b::bar;