Do not take `doc(cfg())` into account when filtering doctests
diff --git a/src/librustdoc/clean/cfg.rs b/src/librustdoc/clean/cfg.rs index 74a04b4..04dd0ea 100644 --- a/src/librustdoc/clean/cfg.rs +++ b/src/librustdoc/clean/cfg.rs
@@ -30,7 +30,7 @@ // Because `CfgEntry` includes `Span`, we must NEVER use `==`/`!=` operators on `Cfg` and instead // use `is_equivalent_to`. #[cfg_attr(test, derive(PartialEq))] -pub(crate) struct Cfg(CfgEntry); +pub(crate) struct Cfg(pub(crate) CfgEntry); // Similar to `hir::DocCfgHideShow` but allows to handle both `show` and `hide` as with the `except` // field in `Any` variant. @@ -744,7 +744,7 @@ pub(crate) struct CfgInfo { hidden_cfg: FxHashMap<Symbol, DocCfgHide>, /// Current computed `cfg`. Each time we enter a new item, this field is updated as well while /// taking into account the `hidden_cfg` information. - current_cfg: Cfg, + pub(crate) current_cfg: Cfg, /// Whether the `doc(auto_cfg())` feature is enabled or not at this point. auto_cfg_active: bool, /// If the parent item used `doc(cfg(...))`, then we don't want to overwrite `current_cfg`,
diff --git a/src/librustdoc/doctest/rust.rs b/src/librustdoc/doctest/rust.rs index d89fb2a..13f705f 100644 --- a/src/librustdoc/doctest/rust.rs +++ b/src/librustdoc/doctest/rust.rs
@@ -6,17 +6,18 @@ use proc_macro2::{TokenStream, TokenTree}; use rustc_attr_parsing::eval_config_entry; -use rustc_hir::attrs::AttributeKind; +use rustc_hir::attrs::{AttributeKind, CfgEntry}; use rustc_hir::def_id::{CRATE_DEF_ID, LocalDefId}; -use rustc_hir::{self as hir, Attribute, CRATE_HIR_ID, intravisit}; +use rustc_hir::{self as hir, CRATE_HIR_ID, intravisit}; use rustc_middle::hir::nested_filter; use rustc_middle::ty::TyCtxt; use rustc_resolve::rustdoc::span_of_fragments; use rustc_span::source_map::SourceMap; -use rustc_span::{BytePos, DUMMY_SP, FileName, Pos, Span}; +use rustc_span::{BytePos, DUMMY_SP, FileName, Pos, Span, sym}; use super::{DocTestVisitor, ScrapedDocTest}; -use crate::clean::{Attributes, CfgInfo, extract_cfg_from_attrs}; +use crate::clean::cfg::Cfg; +use crate::clean::{Attributes, CfgInfo}; use crate::html::markdown::{self, CodeLineMapping, ErrorCodes, LangString, MdRelLine}; struct RustCollector { @@ -118,58 +119,73 @@ fn visit_testable<F: FnOnce(&mut Self)>( sp: Span, nested: F, ) { - let ast_attrs = self.tcx.hir_attrs(self.tcx.local_def_id_to_hir_id(def_id)); - if let Some(ref cfg) = - extract_cfg_from_attrs(ast_attrs.iter(), self.tcx, &mut CfgInfo::default()) - && !eval_config_entry(&self.tcx.sess, cfg.inner()).as_bool() - { - return; - } + let hir_attrs = self.tcx.hir_attrs(self.tcx.local_def_id_to_hir_id(def_id)); + + let mut cfg_info = CfgInfo::default(); + let mut found_features = 0; let source_map = self.tcx.sess.source_map(); - // Try collecting `#[doc(test(attr(...)))]` let old_global_crate_attrs_len = self.collector.global_crate_attrs.len(); - for attr in ast_attrs { - let Attribute::Parsed(AttributeKind::Doc(d)) = attr else { continue }; - for attr_span in &d.test_attrs { - // FIXME: This is ugly, remove when `test_attrs` has been ported to new attribute API. - if let Ok(snippet) = source_map.span_to_snippet(*attr_span) - && let Ok(stream) = TokenStream::from_str(&snippet) - { - let mut iter = stream.into_iter().peekable(); - while let Some(token) = iter.next() { - if let TokenTree::Ident(i) = token { - let i = i.to_string(); - let peek = iter.peek(); - // From this ident, we can have things like: - // - // * Group: `allow(...)` - // * Name/value: `crate_name = "..."` - // * Tokens: `html_no_url` - // - // So we peek next element to know what case we are in. - match peek { - Some(TokenTree::Group(g)) => { - let g = g.to_string(); - iter.next(); - // Add the additional attributes to the global_crate_attrs vector - self.collector.global_crate_attrs.push(format!("{i}{g}")); - } - // If next item is `=`, it means it's a name value so we will need - // to get the value as well. - Some(TokenTree::Punct(p)) if p.as_char() == '=' => { - let p = p.to_string(); - iter.next(); - if let Some(last) = iter.next() { - // Add the additional attributes to the global_crate_attrs vector - self.collector - .global_crate_attrs - .push(format!("{i}{p}{last}")); + // This loop does two things: + // + // 1. Collect `#[target_feature(...)]`. + // 2. Collect `#[doc(test(attr(...)))]`. + for attr in hir_attrs.iter() { + let hir::Attribute::Parsed(attr) = attr else { continue }; + if let AttributeKind::TargetFeature { features, .. } = attr { + for (feature, _) in features { + found_features += 1; + cfg_info.current_cfg &= Cfg(CfgEntry::NameValue { + name: sym::target_feature, + value: Some(*feature), + span: DUMMY_SP, + }); + } + } else if let AttributeKind::Doc(d) = attr { + for attr_span in &d.test_attrs { + // FIXME: This is ugly, remove when `test_attrs` has been ported to new + // attribute API. + if let Ok(snippet) = source_map.span_to_snippet(*attr_span) + && let Ok(stream) = TokenStream::from_str(&snippet) + { + let mut iter = stream.into_iter().peekable(); + while let Some(token) = iter.next() { + if let TokenTree::Ident(i) = token { + let i = i.to_string(); + let peek = iter.peek(); + // From this ident, we can have things like: + // + // * Group: `allow(...)` + // * Name/value: `crate_name = "..."` + // * Tokens: `html_no_url` + // + // So we peek next element to know what case we are in. + match peek { + Some(TokenTree::Group(g)) => { + let g = g.to_string(); + iter.next(); + // Add the additional attributes to the `global_crate_attrs` + // vector + self.collector.global_crate_attrs.push(format!("{i}{g}")); } - } - _ => { - // Add the additional attributes to the global_crate_attrs vector - self.collector.global_crate_attrs.push(i.to_string()); + // If next item is `=`, it means it's a name value so we will + // need to get the value as well. + Some(TokenTree::Punct(p)) if p.as_char() == '=' => { + let p = p.to_string(); + iter.next(); + if let Some(last) = iter.next() { + // Add the additional attributes to the + // `global_crate_attrs` vector. + self.collector + .global_crate_attrs + .push(format!("{i}{p}{last}")); + } + } + _ => { + // Add the additional attributes to the `global_crate_attrs` + // vector. + self.collector.global_crate_attrs.push(i.to_string()); + } } } } @@ -178,6 +194,14 @@ fn visit_testable<F: FnOnce(&mut Self)>( } } + // We only look at the `target_feature` attributes as the `cfg` attributes have already been + // applied at this point, so no need to take them into account again. + if found_features != 0 + && !eval_config_entry(&self.tcx.sess, &cfg_info.current_cfg.inner()).as_bool() + { + return; + } + let mut has_name = false; if let Some(name) = name { self.collector.cur_path.push(name); @@ -186,7 +210,7 @@ fn visit_testable<F: FnOnce(&mut Self)>( // The collapse-docs pass won't combine sugared/raw doc attributes, or included files with // anything else, this will combine them for us. - let attrs = Attributes::from_hir(ast_attrs); + let attrs = Attributes::from_hir(hir_attrs); if let Some(doc) = attrs.opt_doc_value() { let span = span_of_fragments(&attrs.doc_strings).unwrap_or(sp); self.collector.position = if span.edition().at_least_rust_2024() { @@ -194,7 +218,7 @@ fn visit_testable<F: FnOnce(&mut Self)>( } else { // this span affects filesystem path resolution, // so we need to keep it the same as it was previously - ast_attrs + hir_attrs .iter() .find(|attr| attr.doc_str().is_some()) .map(|attr| {