diff --git a/compiler/rustc_error_messages/locales/en-US/passes.ftl b/compiler/rustc_error_messages/locales/en-US/passes.ftl index 995ad4fe25859..4a8245ebbe0e9 100644 --- a/compiler/rustc_error_messages/locales/en-US/passes.ftl +++ b/compiler/rustc_error_messages/locales/en-US/passes.ftl @@ -75,6 +75,15 @@ passes_doc_alias_not_string_literal = `#[doc(alias("a"))]` expects string litera passes_doc_alias_malformed = doc alias attribute expects a string `#[doc(alias = "a")]` or a list of strings `#[doc(alias("a", "b"))]` +passes_doc_auto_cfg_malformed = `#![doc({$attr_str})]` attribute doesn't expect a value + +passes_doc_both_auto_cfg = conflicting attributes `doc(auto_cfg)` and `doc(no_auto_cfg)` + .label = the other is specified here + +passes_doc_no_auto_cfg_enabled_by_default = `doc(no_auto_cfg)` is enabled by default before the 2024 edition + +passes_doc_auto_cfg_enabled_by_default = `doc(auto_cfg)` is enabled by default since the 2024 edition + passes_doc_keyword_empty_mod = `#[doc(keyword = "...")]` should be used on empty modules passes_doc_keyword_not_mod = `#[doc(keyword = "...")]` should be used on modules diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 87433538512b9..229b46dda6a1d 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -5,7 +5,7 @@ //! item. use crate::errors; -use rustc_ast::{ast, AttrStyle, Attribute, Lit, LitKind, MetaItemKind, NestedMetaItem}; +use rustc_ast::{ast, AttrStyle, Attribute, Lit, LitKind, MetaItem, MetaItemKind, NestedMetaItem}; use rustc_data_structures::fx::FxHashMap; use rustc_errors::{fluent, struct_span_err, Applicability, MultiSpan}; use rustc_expand::base::resolve_path; @@ -25,6 +25,7 @@ use rustc_session::lint::builtin::{ CONFLICTING_REPR_HINTS, INVALID_DOC_ATTRIBUTES, UNUSED_ATTRIBUTES, }; use rustc_session::parse::feature_err; +use rustc_span::edition::Edition; use rustc_span::symbol::{kw, sym, Symbol}; use rustc_span::{Span, DUMMY_SP}; use rustc_target::spec::abi::Abi; @@ -97,6 +98,7 @@ impl CheckAttrVisitor<'_> { target, &mut specified_inline, &mut doc_aliases, + &mut seen, ), sym::no_link => self.check_no_link(hir_id, &attr, span, target), sym::export_name => self.check_export_name(hir_id, &attr, span, target), @@ -945,6 +947,61 @@ impl CheckAttrVisitor<'_> { is_valid } + /// Checks that `doc(auto_cfg)` is valid (i.e. no value) and warn if it's used whereas the + /// "equivalent feature" is already enabled. + fn check_auto_cfg( + &self, + meta: &MetaItem, + hir_id: HirId, + seen: &mut FxHashMap, + ) -> bool { + let name = meta.name_or_empty(); + if !meta.is_word() { + self.tcx + .sess + .emit_err(errors::DocAutoCfgMalformed { span: meta.span, attr_str: name.as_str() }); + return false; + } + let mut is_valid = true; + let other = if name == sym::no_auto_cfg { + if self.tcx.sess.edition() < Edition::Edition2024 { + self.tcx.emit_spanned_lint( + UNUSED_ATTRIBUTES, + hir_id, + meta.span, + errors::DocNoAutoCfgEnabledByDefault, + ); + is_valid = false; + } + sym::auto_cfg + } else { + if self.tcx.sess.edition() > Edition::Edition2021 { + self.tcx.emit_spanned_lint( + UNUSED_ATTRIBUTES, + hir_id, + meta.span, + errors::DocAutoCfgEnabledByDefault, + ); + is_valid = false; + } + sym::no_auto_cfg + }; + + match seen.entry(other) { + Entry::Occupied(entry) => { + self.tcx + .sess + .emit_err(errors::BothDocAutoCfg { span: *entry.get(), attr_span: meta.span }); + is_valid = false; + } + Entry::Vacant(entry) => { + entry.insert(meta.span); + } + } + + is_valid + } + /// Runs various checks on `#[doc]` attributes. Returns `true` if valid. /// /// `specified_inline` should be initialized to `None` and kept for the scope @@ -958,6 +1015,7 @@ impl CheckAttrVisitor<'_> { target: Target, specified_inline: &mut Option<(bool, Span)>, aliases: &mut FxHashMap, + seen: &mut FxHashMap, ) -> bool { let mut is_valid = true; @@ -993,6 +1051,8 @@ impl CheckAttrVisitor<'_> { | sym::html_root_url | sym::html_no_source | sym::test + | sym::auto_cfg + | sym::no_auto_cfg if !self.check_attr_crate_level(attr, meta, hir_id) => { is_valid = false; @@ -1010,6 +1070,11 @@ impl CheckAttrVisitor<'_> { is_valid = false; } + sym::auto_cfg | sym::no_auto_cfg + if !self.check_auto_cfg(i_meta, hir_id, seen) => { + is_valid = false; + } + // no_default_passes: deprecated // passes: deprecated // plugins: removed, but rustdoc warns about it itself @@ -1031,6 +1096,8 @@ impl CheckAttrVisitor<'_> { | sym::notable_trait | sym::passes | sym::plugins + | sym::auto_cfg + | sym::no_auto_cfg | sym::fake_variadic => {} sym::test => { diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index cc231af71a274..827807d7636ca 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -189,6 +189,31 @@ pub struct DocAliasMalformed { pub span: Span, } +#[derive(Diagnostic)] +#[diag(passes::doc_auto_cfg_malformed)] +pub struct DocAutoCfgMalformed<'a> { + #[primary_span] + pub span: Span, + pub attr_str: &'a str, +} + +#[derive(Diagnostic)] +#[diag(passes::doc_both_auto_cfg)] +pub struct BothDocAutoCfg { + #[primary_span] + pub span: Span, + #[label] + pub attr_span: Span, +} + +#[derive(LintDiagnostic)] +#[diag(passes::doc_auto_cfg_enabled_by_default)] +pub struct DocAutoCfgEnabledByDefault; + +#[derive(LintDiagnostic)] +#[diag(passes::doc_no_auto_cfg_enabled_by_default)] +pub struct DocNoAutoCfgEnabledByDefault; + #[derive(Diagnostic)] #[diag(passes::doc_keyword_empty_mod)] pub struct DocKeywordEmptyMod { diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 8cb7d147d0253..2eb3fc083464a 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -408,6 +408,7 @@ symbols! { attr_literals, attributes, augmented_assignments, + auto_cfg, auto_traits, automatically_derived, avx, @@ -1009,6 +1010,7 @@ symbols! { next, nll, no, + no_auto_cfg, no_builtins, no_core, no_coverage, diff --git a/src/doc/rustdoc/src/unstable-features.md b/src/doc/rustdoc/src/unstable-features.md index b8b5014ab42e6..baab6963713af 100644 --- a/src/doc/rustdoc/src/unstable-features.md +++ b/src/doc/rustdoc/src/unstable-features.md @@ -96,6 +96,7 @@ previous source code: ```rust #![feature(doc_auto_cfg)] +#![doc(auto_cfg)] // To enable the "auto_cfg" discovery. /// Token struct that can only be used on Windows. #[cfg(any(windows, doc))] diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs index 7893429f26f80..1ac39eb69a9c0 100644 --- a/src/librustdoc/clean/inline.rs +++ b/src/librustdoc/clean/inline.rs @@ -318,10 +318,13 @@ pub(crate) fn merge_attrs( } else { Attributes::from_ast(&both) }, - both.cfg(cx.tcx, &cx.cache.hidden_cfg), + both.cfg(cx.tcx, &cx.cache.hidden_cfg, cx.cache.doc_auto_cfg_active), ) } else { - (Attributes::from_ast(&old_attrs), old_attrs.cfg(cx.tcx, &cx.cache.hidden_cfg)) + ( + Attributes::from_ast(&old_attrs), + old_attrs.cfg(cx.tcx, &cx.cache.hidden_cfg, cx.cache.doc_auto_cfg_active), + ) } } diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 279e762d597a0..5c1c38488ce90 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -2112,7 +2112,7 @@ fn clean_extern_crate<'tcx>( item_id: crate_def_id.into(), visibility: clean_visibility(ty_vis), kind: Box::new(ExternCrateItem { src: orig_name }), - cfg: attrs.cfg(cx.tcx, &cx.cache.hidden_cfg), + cfg: attrs.cfg(cx.tcx, &cx.cache.hidden_cfg, cx.cache.doc_auto_cfg_active), }] } diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 7e0533800694e..fc3e7e9191a84 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -463,7 +463,7 @@ impl Item { kind, Box::new(Attributes::from_ast(ast_attrs)), cx, - ast_attrs.cfg(cx.tcx, &cx.cache.hidden_cfg), + ast_attrs.cfg(cx.tcx, &cx.cache.hidden_cfg, cx.cache.doc_auto_cfg_active), ) } @@ -838,7 +838,12 @@ pub(crate) trait AttributesExt { fn inner_docs(&self) -> bool; - fn cfg(&self, tcx: TyCtxt<'_>, hidden_cfg: &FxHashSet) -> Option>; + fn cfg( + &self, + tcx: TyCtxt<'_>, + hidden_cfg: &FxHashSet, + doc_auto_cfg_active: bool, + ) -> Option>; } impl AttributesExt for [ast::Attribute] { @@ -864,10 +869,14 @@ impl AttributesExt for [ast::Attribute] { self.iter().find(|a| a.doc_str().is_some()).map_or(true, |a| a.style == AttrStyle::Inner) } - fn cfg(&self, tcx: TyCtxt<'_>, hidden_cfg: &FxHashSet) -> Option> { + fn cfg( + &self, + tcx: TyCtxt<'_>, + hidden_cfg: &FxHashSet, + doc_auto_cfg_active: bool, + ) -> Option> { let sess = tcx.sess; let doc_cfg_active = tcx.features().doc_cfg; - let doc_auto_cfg_active = tcx.features().doc_auto_cfg; fn single(it: T) -> Option { let mut iter = it.into_iter(); diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs index b463b934e292d..c0d87fc312d68 100644 --- a/src/librustdoc/core.rs +++ b/src/librustdoc/core.rs @@ -245,6 +245,7 @@ pub(crate) fn create_config( rustc_lint::builtin::UNEXPECTED_CFGS.name.to_string(), // this lint is needed to support `#[expect]` attributes rustc_lint::builtin::UNFULFILLED_LINT_EXPECTATIONS.name.to_string(), + rustc_lint::builtin::UNUSED_ATTRIBUTES.name.to_string(), ]; lints_to_show.extend(crate::lint::RUSTDOC_LINTS.iter().map(|lint| lint.name.to_string())); diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs index f4ec60735a8dd..7f1d73590d7a3 100644 --- a/src/librustdoc/doctest.rs +++ b/src/librustdoc/doctest.rs @@ -1210,7 +1210,7 @@ impl<'a, 'hir, 'tcx> HirCollector<'a, 'hir, 'tcx> { nested: F, ) { let ast_attrs = self.tcx.hir().attrs(hir_id); - if let Some(ref cfg) = ast_attrs.cfg(self.tcx, &FxHashSet::default()) { + if let Some(ref cfg) = ast_attrs.cfg(self.tcx, &FxHashSet::default(), false) { if !cfg.matches(&self.sess.parse_sess, Some(self.sess.features_untracked())) { return; } diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs index 2e428cfddcf0a..e919d0d9a0838 100644 --- a/src/librustdoc/formats/cache.rs +++ b/src/librustdoc/formats/cache.rs @@ -121,6 +121,8 @@ pub(crate) struct Cache { pub(crate) intra_doc_links: FxHashMap>, /// Cfg that have been hidden via #![doc(cfg_hide(...))] pub(crate) hidden_cfg: FxHashSet, + /// Whether or not the `#![doc(auto_cfg)]` attribute was used. + pub(crate) doc_auto_cfg_active: bool, } /// This struct is used to wrap the `cache` and `tcx` in order to run `DocFolder`. diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index 6327817364a55..8eee9f66d14c2 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -351,7 +351,11 @@ fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items: let import_item = clean::Item { item_id: import_def_id.into(), attrs: import_attrs, - cfg: ast_attrs.cfg(cx.tcx(), &cx.cache().hidden_cfg), + cfg: ast_attrs.cfg( + cx.tcx(), + &cx.cache().hidden_cfg, + cx.cache().doc_auto_cfg_active, + ), ..myitem.clone() }; diff --git a/src/librustdoc/visit_ast.rs b/src/librustdoc/visit_ast.rs index e6cef4a326ac0..042b275278c50 100644 --- a/src/librustdoc/visit_ast.rs +++ b/src/librustdoc/visit_ast.rs @@ -10,6 +10,7 @@ use rustc_hir::CRATE_HIR_ID; use rustc_middle::middle::privacy::AccessLevel; use rustc_middle::ty::TyCtxt; use rustc_span::def_id::{CRATE_DEF_ID, LOCAL_CRATE}; +use rustc_span::edition::Edition; use rustc_span::symbol::{kw, sym, Symbol}; use rustc_span::Span; @@ -146,6 +147,33 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { .into_iter(), ) .collect(); + if self.cx.tcx.features().doc_auto_cfg { + // This feature is enabled by default starting the 2024 edition. + self.cx.cache.doc_auto_cfg_active = self.cx.tcx.sess.edition() > Edition::Edition2021; + if let Some(attr) = self + .cx + .tcx + .hir() + .attrs(CRATE_HIR_ID) + .iter() + .filter(|attr| attr.has_name(sym::doc)) + .flat_map(|attr| attr.meta_item_list().into_iter().flatten()) + .find(|attr| attr.has_name(sym::auto_cfg) || attr.has_name(sym::no_auto_cfg)) + { + // If we find one of the two attributes, we update the default value of + // `doc_auto_cfg_active`. + self.cx.cache.doc_auto_cfg_active = attr.has_name(sym::auto_cfg); + } else { + self.cx + .sess() + .diagnostic() + .struct_warn( + "feature `doc_auto_cfg` now requires attribute `#![doc(auto_cfg)]` to \ + enable the behavior", + ) + .emit(); + } + } self.cx.cache.exact_paths = self.exact_paths; top_level_module diff --git a/src/test/rustdoc-ui/doc_auto_cfg.rs b/src/test/rustdoc-ui/doc_auto_cfg.rs new file mode 100644 index 0000000000000..d3b6b221e89f0 --- /dev/null +++ b/src/test/rustdoc-ui/doc_auto_cfg.rs @@ -0,0 +1,2 @@ +// check-pass +#![feature(doc_auto_cfg)] diff --git a/src/test/rustdoc-ui/doc_auto_cfg.stderr b/src/test/rustdoc-ui/doc_auto_cfg.stderr new file mode 100644 index 0000000000000..b9c4c6483ba96 --- /dev/null +++ b/src/test/rustdoc-ui/doc_auto_cfg.stderr @@ -0,0 +1,4 @@ +warning: feature `doc_auto_cfg` now requires attribute `#![doc(auto_cfg)]` to enable the behavior + +warning: 1 warning emitted + diff --git a/src/test/rustdoc-ui/doc_no_auto_cfg.rs b/src/test/rustdoc-ui/doc_no_auto_cfg.rs new file mode 100644 index 0000000000000..89d9659c76ff4 --- /dev/null +++ b/src/test/rustdoc-ui/doc_no_auto_cfg.rs @@ -0,0 +1,15 @@ +// edition: 2021 +#![deny(warnings)] +#![doc(no_auto_cfg)] //~ ERROR +//~^ ERROR +#![doc(auto_cfg, no_auto_cfg)] //~ ERROR +#![doc(no_auto_cfg(1))] //~ ERROR +#![doc(no_auto_cfg = 1)] //~ ERROR +#![doc(auto_cfg(1))] //~ ERROR +#![doc(auto_cfg = 1)] //~ ERROR + +#[doc(auto_cfg)] //~ ERROR +//~^ WARN +#[doc(no_auto_cfg)] //~ ERROR +//~^ WARN +pub struct Bar; diff --git a/src/test/rustdoc-ui/doc_no_auto_cfg.stderr b/src/test/rustdoc-ui/doc_no_auto_cfg.stderr new file mode 100644 index 0000000000000..af47df28d9383 --- /dev/null +++ b/src/test/rustdoc-ui/doc_no_auto_cfg.stderr @@ -0,0 +1,83 @@ +error: this attribute can only be applied at the crate level + --> $DIR/doc_no_auto_cfg.rs:11:7 + | +LL | #[doc(auto_cfg)] + | ^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #82730 + = note: read for more information +note: the lint level is defined here + --> $DIR/doc_no_auto_cfg.rs:2:9 + | +LL | #![deny(warnings)] + | ^^^^^^^^ + = note: `#[deny(invalid_doc_attributes)]` implied by `#[deny(warnings)]` +help: to apply to the crate, use an inner attribute + | +LL | #![doc(auto_cfg)] + | ~~~~~~~~~~~~~~~~~ + +error: this attribute can only be applied at the crate level + --> $DIR/doc_no_auto_cfg.rs:13:7 + | +LL | #[doc(no_auto_cfg)] + | ^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #82730 + = note: read for more information +help: to apply to the crate, use an inner attribute + | +LL | #![doc(no_auto_cfg)] + | ~~~~~~~~~~~~~~~~~~~~ + +error: `doc(no_auto_cfg)` is enabled by default before the 2024 edition + --> $DIR/doc_no_auto_cfg.rs:3:8 + | +LL | #![doc(no_auto_cfg)] + | ^^^^^^^^^^^ + | + = note: `#[deny(unused_attributes)]` implied by `#[deny(warnings)]` + +error: `doc(no_auto_cfg)` is enabled by default before the 2024 edition + --> $DIR/doc_no_auto_cfg.rs:5:18 + | +LL | #![doc(auto_cfg, no_auto_cfg)] + | ^^^^^^^^^^^ + +error: conflicting attributes `doc(auto_cfg)` and `doc(no_auto_cfg)` + --> $DIR/doc_no_auto_cfg.rs:3:8 + | +LL | #![doc(no_auto_cfg)] + | ^^^^^^^^^^^ +LL | +LL | #![doc(auto_cfg, no_auto_cfg)] + | ----------- the other is specified here + +error: `#![doc(no_auto_cfg)]` attribute doesn't expect a value + --> $DIR/doc_no_auto_cfg.rs:6:8 + | +LL | #![doc(no_auto_cfg(1))] + | ^^^^^^^^^^^^^^ + +error: `#![doc(no_auto_cfg)]` attribute doesn't expect a value + --> $DIR/doc_no_auto_cfg.rs:7:8 + | +LL | #![doc(no_auto_cfg = 1)] + | ^^^^^^^^^^^^^^^ + +error: `#![doc(auto_cfg)]` attribute doesn't expect a value + --> $DIR/doc_no_auto_cfg.rs:8:8 + | +LL | #![doc(auto_cfg(1))] + | ^^^^^^^^^^^ + +error: `#![doc(auto_cfg)]` attribute doesn't expect a value + --> $DIR/doc_no_auto_cfg.rs:9:8 + | +LL | #![doc(auto_cfg = 1)] + | ^^^^^^^^^^^^ + +error: aborting due to 9 previous errors + diff --git a/src/test/rustdoc/doc-auto-cfg.rs b/src/test/rustdoc/doc-auto-cfg.rs index 7842ee69c9f67..64e0d990b448a 100644 --- a/src/test/rustdoc/doc-auto-cfg.rs +++ b/src/test/rustdoc/doc-auto-cfg.rs @@ -1,6 +1,8 @@ #![feature(doc_auto_cfg)] #![crate_name = "foo"] +#![doc(auto_cfg)] + // @has foo/fn.foo.html // @has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-meowmeow' #[cfg(not(meowmeow))] diff --git a/src/test/rustdoc/doc-cfg-hide.rs b/src/test/rustdoc/doc-cfg-hide.rs index 636957fe9980d..8e4a316ae8c23 100644 --- a/src/test/rustdoc/doc-cfg-hide.rs +++ b/src/test/rustdoc/doc-cfg-hide.rs @@ -1,6 +1,7 @@ #![crate_name = "oud"] #![feature(doc_auto_cfg, doc_cfg, doc_cfg_hide)] +#![doc(auto_cfg)] #![doc(cfg_hide(feature = "solecism"))] // @has 'oud/struct.Solecism.html' diff --git a/src/test/rustdoc/doc-cfg-implicit-gate.rs b/src/test/rustdoc/doc-cfg-implicit-gate.rs index 92804d3729bba..e4cfa15e1164f 100644 --- a/src/test/rustdoc/doc-cfg-implicit-gate.rs +++ b/src/test/rustdoc/doc-cfg-implicit-gate.rs @@ -1,6 +1,8 @@ // compile-flags:--cfg feature="worricow" #![crate_name = "xenogenous"] +#![doc(auto_cfg)] + // @has 'xenogenous/struct.Worricow.html' // @count - '//*[@class="stab portability"]' 0 #[cfg(feature = "worricow")] diff --git a/src/test/rustdoc/doc-cfg-implicit.rs b/src/test/rustdoc/doc-cfg-implicit.rs index 5d17a4ede6adc..0e3c77d74f91d 100644 --- a/src/test/rustdoc/doc-cfg-implicit.rs +++ b/src/test/rustdoc/doc-cfg-implicit.rs @@ -1,6 +1,8 @@ #![crate_name = "funambulism"] #![feature(doc_auto_cfg, doc_cfg)] +#![doc(auto_cfg)] + // @has 'funambulism/struct.Disorbed.html' // @count - '//*[@class="stab portability"]' 1 // @matches - '//*[@class="stab portability"]' 'crate feature disorbed' diff --git a/src/test/rustdoc/doc_auto_cfg_nested_impl.rs b/src/test/rustdoc/doc_auto_cfg_nested_impl.rs index 4d73e0d829ad5..0c312ce6cb4b2 100644 --- a/src/test/rustdoc/doc_auto_cfg_nested_impl.rs +++ b/src/test/rustdoc/doc_auto_cfg_nested_impl.rs @@ -4,6 +4,8 @@ #![crate_type = "lib"] #![crate_name = "foo"] +#![doc(auto_cfg)] + pub struct S; pub trait MyTrait1 {} pub trait MyTrait2 {} diff --git a/src/test/rustdoc/inline_local/please_inline.rs b/src/test/rustdoc/inline_local/please_inline.rs index e4429ef33a928..126c342c08c94 100644 --- a/src/test/rustdoc/inline_local/please_inline.rs +++ b/src/test/rustdoc/inline_local/please_inline.rs @@ -14,6 +14,5 @@ pub mod a { pub mod b { // @hasraw - 'pub use foo::' // @!has please_inline/b/struct.Foo.html - #[feature(inline)] pub use foo::Foo; }