about summary refs log tree commit diff
diff options
context:
space:
mode:
authorNilstrieb <48135649+Nilstrieb@users.noreply.github.com>2023-03-10 22:39:14 +0100
committerNilstrieb <48135649+Nilstrieb@users.noreply.github.com>2023-06-01 19:17:19 +0200
commita647ba250a65b44574830cb71aab5c0403adf31b (patch)
tree5df6e29286345eba5a8cb0ad29c1eb6974f0bd8c
parent642c92e63008ffb49f6ad8344e07bfa7d5b0d9bb (diff)
downloadrust-a647ba250a65b44574830cb71aab5c0403adf31b.tar.gz
rust-a647ba250a65b44574830cb71aab5c0403adf31b.zip
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.
-rw-r--r--compiler/rustc_ast/src/expand/mod.rs17
-rw-r--r--compiler/rustc_expand/src/base.rs4
-rw-r--r--compiler/rustc_expand/src/config.rs20
-rw-r--r--compiler/rustc_expand/src/expand.rs49
-rw-r--r--compiler/rustc_metadata/src/rmeta/decoder.rs9
-rw-r--r--compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs1
-rw-r--r--compiler/rustc_metadata/src/rmeta/encoder.rs13
-rw-r--r--compiler/rustc_metadata/src/rmeta/mod.rs2
-rw-r--r--compiler/rustc_middle/src/arena.rs1
-rw-r--r--compiler/rustc_middle/src/query/mod.rs11
-rw-r--r--compiler/rustc_middle/src/ty/parameterized.rs1
-rw-r--r--compiler/rustc_resolve/src/diagnostics.rs69
-rw-r--r--compiler/rustc_resolve/src/errors.rs4
-rw-r--r--compiler/rustc_resolve/src/ident.rs92
-rw-r--r--compiler/rustc_resolve/src/imports.rs26
-rw-r--r--compiler/rustc_resolve/src/late.rs24
-rw-r--r--compiler/rustc_resolve/src/late/diagnostics.rs37
-rw-r--r--compiler/rustc_resolve/src/lib.rs27
-rw-r--r--compiler/rustc_resolve/src/macros.rs12
-rw-r--r--tests/ui/cfg/auxiliary/cfged_out.rs22
-rw-r--r--tests/ui/cfg/diagnostics-cross-crate.rs31
-rw-r--r--tests/ui/cfg/diagnostics-cross-crate.stderr53
-rw-r--r--tests/ui/cfg/diagnostics-not-a-def.rs12
-rw-r--r--tests/ui/cfg/diagnostics-not-a-def.stderr9
-rw-r--r--tests/ui/cfg/diagnostics-reexport.rs16
-rw-r--r--tests/ui/cfg/diagnostics-reexport.stderr15
-rw-r--r--tests/ui/cfg/diagnostics-same-crate.rs51
-rw-r--r--tests/ui/cfg/diagnostics-same-crate.stderr47
-rw-r--r--tests/ui/macros/builtin-std-paths-fail.stderr3
-rw-r--r--tests/ui/macros/macro-outer-attributes.stderr5
30 files changed, 599 insertions, 84 deletions
diff --git a/compiler/rustc_ast/src/expand/mod.rs b/compiler/rustc_ast/src/expand/mod.rs
index 2ee1bfe0ae7..942347383ce 100644
--- a/compiler/rustc_ast/src/expand/mod.rs
+++ b/compiler/rustc_ast/src/expand/mod.rs
@@ -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 }
+    }
+}
diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs
index 0d43b30474b..b7c30841983 100644
--- a/compiler/rustc_expand/src/base.rs
+++ b/compiler/rustc_expand/src/base.rs
@@ -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>,
diff --git a/compiler/rustc_expand/src/config.rs b/compiler/rustc_expand/src/config.rs
index 4ff8e409d88..690f80f6876 100644
--- a/compiler/rustc_expand/src/config.rs
+++ b/compiler/rustc_expand/src/config.rs
@@ -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`
diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs
index ce0093c7d4c..dd8863df195 100644
--- a/compiler/rustc_expand/src/expand.rs
+++ b/compiler/rustc_expand/src/expand.rs
@@ -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;
                         }
 
diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs
index 8f883bdcf12..21cbab54293 100644
--- a/compiler/rustc_metadata/src/rmeta/decoder.rs
+++ b/compiler/rustc_metadata/src/rmeta/decoder.rs
@@ -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();
diff --git a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs
index a15307e4345..2a609303197 100644
--- a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs
+++ b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs
@@ -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() }
diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs
index 6ceb61e793e..8f63a0c9df6 100644
--- a/compiler/rustc_metadata/src/rmeta/encoder.rs
+++ b/compiler/rustc_metadata/src/rmeta/encoder.rs
@@ -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))
diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs
index 2da888f4468..f6bbc4bc60b 100644
--- a/compiler/rustc_metadata/src/rmeta/mod.rs
+++ b/compiler/rustc_metadata/src/rmeta/mod.rs
@@ -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>,
diff --git a/compiler/rustc_middle/src/arena.rs b/compiler/rustc_middle/src/arena.rs
index a149a61ec13..6c404fbb7c6 100644
--- a/compiler/rustc_middle/src/arena.rs
+++ b/compiler/rustc_middle/src/arena.rs
@@ -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,
         ]);
     )
diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs
index 0b31c9bbf81..bcffbed8d94 100644
--- a/compiler/rustc_middle/src/query/mod.rs
+++ b/compiler/rustc_middle/src/query/mod.rs
@@ -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! }
diff --git a/compiler/rustc_middle/src/ty/parameterized.rs b/compiler/rustc_middle/src/ty/parameterized.rs
index a2e77d9cdfe..13be15269f4 100644
--- a/compiler/rustc_middle/src/ty/parameterized.rs
+++ b/compiler/rustc_middle/src/ty/parameterized.rs
@@ -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,
diff --git a/compiler/rustc_resolve/src/diagnostics.rs b/compiler/rustc_resolve/src/diagnostics.rs
index 377652ce71b..15c8a690530 100644
--- a/compiler/rustc_resolve/src/diagnostics.rs
+++ b/compiler/rustc_resolve/src/diagnostics.rs
@@ -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:
diff --git a/compiler/rustc_resolve/src/errors.rs b/compiler/rustc_resolve/src/errors.rs
index 2ab55f12637..e88cbb955b5 100644
--- a/compiler/rustc_resolve/src/errors.rs
+++ b/compiler/rustc_resolve/src/errors.rs
@@ -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)]
diff --git a/compiler/rustc_resolve/src/ident.rs b/compiler/rustc_resolve/src/ident.rs
index 945c7ce3a9b..ec0a8535e71 100644
--- a/compiler/rustc_resolve/src/ident.rs
+++ b/compiler/rustc_resolve/src/ident.rs
@@ -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,
+                            )
+                        },
+                    );
                 }
             }
         }
diff --git a/compiler/rustc_resolve/src/imports.rs b/compiler/rustc_resolve/src/imports.rs
index c1bb262c0d4..7f944fb5745 100644
--- a/compiler/rustc_resolve/src/imports.rs
+++ b/compiler/rustc_resolve/src/imports.rs
@@ -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(
diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs
index e0611907613..ddd75ea3b33 100644
--- a/compiler/rustc_resolve/src/late.rs
+++ b/compiler/rustc_resolve/src/late.rs
@@ -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"),
diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs
index f79f8d0c6ca..2f9759a668b 100644
--- a/compiler/rustc_resolve/src/late/diagnostics.rs
+++ b/compiler/rustc_resolve/src/late/diagnostics.rs
@@ -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)
     }
 
diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs
index fd977e8e254..dd8d01e35e5 100644
--- a/compiler/rustc_resolve/src/lib.rs
+++ b/compiler/rustc_resolve/src/lib.rs
@@ -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,
diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs
index d8a7bcbfff9..805c804e575 100644
--- a/compiler/rustc_resolve/src/macros.rs
+++ b/compiler/rustc_resolve/src/macros.rs
@@ -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!(),
diff --git a/tests/ui/cfg/auxiliary/cfged_out.rs b/tests/ui/cfg/auxiliary/cfged_out.rs
new file mode 100644
index 00000000000..f6a9089cf29
--- /dev/null
+++ b/tests/ui/cfg/auxiliary/cfged_out.rs
@@ -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() {}
diff --git a/tests/ui/cfg/diagnostics-cross-crate.rs b/tests/ui/cfg/diagnostics-cross-crate.rs
new file mode 100644
index 00000000000..d2725c94b08
--- /dev/null
+++ b/tests/ui/cfg/diagnostics-cross-crate.rs
@@ -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`
+}
diff --git a/tests/ui/cfg/diagnostics-cross-crate.stderr b/tests/ui/cfg/diagnostics-cross-crate.stderr
new file mode 100644
index 00000000000..046929bc260
--- /dev/null
+++ b/tests/ui/cfg/diagnostics-cross-crate.stderr
@@ -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`.
diff --git a/tests/ui/cfg/diagnostics-not-a-def.rs b/tests/ui/cfg/diagnostics-not-a-def.rs
new file mode 100644
index 00000000000..72939471226
--- /dev/null
+++ b/tests/ui/cfg/diagnostics-not-a-def.rs
@@ -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
+}
diff --git a/tests/ui/cfg/diagnostics-not-a-def.stderr b/tests/ui/cfg/diagnostics-not-a-def.stderr
new file mode 100644
index 00000000000..af0e1a17275
--- /dev/null
+++ b/tests/ui/cfg/diagnostics-not-a-def.stderr
@@ -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`.
diff --git a/tests/ui/cfg/diagnostics-reexport.rs b/tests/ui/cfg/diagnostics-reexport.rs
new file mode 100644
index 00000000000..1d43d6ba02f
--- /dev/null
+++ b/tests/ui/cfg/diagnostics-reexport.rs
@@ -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`
+}
diff --git a/tests/ui/cfg/diagnostics-reexport.stderr b/tests/ui/cfg/diagnostics-reexport.stderr
new file mode 100644
index 00000000000..6c977cbfa41
--- /dev/null
+++ b/tests/ui/cfg/diagnostics-reexport.stderr
@@ -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`.
diff --git a/tests/ui/cfg/diagnostics-same-crate.rs b/tests/ui/cfg/diagnostics-same-crate.rs
new file mode 100644
index 00000000000..f76ace06a76
--- /dev/null
+++ b/tests/ui/cfg/diagnostics-same-crate.rs
@@ -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
+}
diff --git a/tests/ui/cfg/diagnostics-same-crate.stderr b/tests/ui/cfg/diagnostics-same-crate.stderr
new file mode 100644
index 00000000000..30ee6479bd2
--- /dev/null
+++ b/tests/ui/cfg/diagnostics-same-crate.stderr
@@ -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`.
diff --git a/tests/ui/macros/builtin-std-paths-fail.stderr b/tests/ui/macros/builtin-std-paths-fail.stderr
index ba626101190..004a39043b7 100644
--- a/tests/ui/macros/builtin-std-paths-fail.stderr
+++ b/tests/ui/macros/builtin-std-paths-fail.stderr
@@ -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
 
diff --git a/tests/ui/macros/macro-outer-attributes.stderr b/tests/ui/macros/macro-outer-attributes.stderr
index 0bdc3416f80..0418e611604 100644
--- a/tests/ui/macros/macro-outer-attributes.stderr
+++ b/tests/ui/macros/macro-outer-attributes.stderr
@@ -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;