diff options
Diffstat (limited to 'src/librustdoc/clean/cfg.rs')
| -rw-r--r-- | src/librustdoc/clean/cfg.rs | 267 |
1 files changed, 266 insertions, 1 deletions
diff --git a/src/librustdoc/clean/cfg.rs b/src/librustdoc/clean/cfg.rs index aa614b73dea..881a81b22f0 100644 --- a/src/librustdoc/clean/cfg.rs +++ b/src/librustdoc/clean/cfg.rs @@ -3,14 +3,18 @@ // FIXME: Once the portability lint RFC is implemented (see tracking issue #41619), // switch to use those structures instead. +use std::sync::Arc; use std::{fmt, mem, ops}; use itertools::Either; use rustc_ast::{LitKind, MetaItem, MetaItemInner, MetaItemKind, MetaItemLit}; -use rustc_data_structures::fx::FxHashSet; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_hir::attrs::AttributeKind; +use rustc_middle::ty::TyCtxt; use rustc_session::parse::ParseSess; use rustc_span::Span; use rustc_span::symbol::{Symbol, sym}; +use {rustc_ast as ast, rustc_hir as hir}; use crate::display::{Joined as _, MaybeDisplay, Wrapped}; use crate::html::escape::Escape; @@ -600,3 +604,264 @@ impl fmt::Display for Display<'_> { } } } + +/// This type keeps track of (doc) cfg information as we go down the item tree. +#[derive(Clone, Debug)] +pub(crate) struct CfgInfo { + /// List of currently active `doc(auto_cfg(hide(...)))` cfgs, minus currently active + /// `doc(auto_cfg(show(...)))` cfgs. + hidden_cfg: FxHashSet<Cfg>, + /// 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, + /// 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`, + /// instead we will concatenate with it. However, if it's not the case, we need to overwrite + /// `current_cfg`. + parent_is_doc_cfg: bool, +} + +impl Default for CfgInfo { + fn default() -> Self { + Self { + hidden_cfg: FxHashSet::from_iter([ + Cfg::Cfg(sym::test, None), + Cfg::Cfg(sym::doc, None), + Cfg::Cfg(sym::doctest, None), + ]), + current_cfg: Cfg::True, + auto_cfg_active: true, + parent_is_doc_cfg: false, + } + } +} + +fn show_hide_show_conflict_error( + tcx: TyCtxt<'_>, + item_span: rustc_span::Span, + previous: rustc_span::Span, +) { + let mut diag = tcx.sess.dcx().struct_span_err( + item_span, + format!( + "same `cfg` was in `auto_cfg(hide(...))` and `auto_cfg(show(...))` on the same item" + ), + ); + diag.span_note(previous, "first change was here"); + diag.emit(); +} + +/// This functions updates the `hidden_cfg` field of the provided `cfg_info` argument. +/// +/// It also checks if a same `cfg` is present in both `auto_cfg(hide(...))` and +/// `auto_cfg(show(...))` on the same item and emits an error if it's the case. +/// +/// Because we go through a list of `cfg`s, we keep track of the `cfg`s we saw in `new_show_attrs` +/// and in `new_hide_attrs` arguments. +fn handle_auto_cfg_hide_show( + tcx: TyCtxt<'_>, + cfg_info: &mut CfgInfo, + sub_attr: &MetaItemInner, + is_show: bool, + new_show_attrs: &mut FxHashMap<(Symbol, Option<Symbol>), rustc_span::Span>, + new_hide_attrs: &mut FxHashMap<(Symbol, Option<Symbol>), rustc_span::Span>, +) { + if let MetaItemInner::MetaItem(item) = sub_attr + && let MetaItemKind::List(items) = &item.kind + { + for item in items { + // FIXME: Report in case `Cfg::parse` reports an error? + if let Ok(Cfg::Cfg(key, value)) = Cfg::parse(item) { + if is_show { + if let Some(span) = new_hide_attrs.get(&(key, value)) { + show_hide_show_conflict_error(tcx, item.span(), *span); + } else { + new_show_attrs.insert((key, value), item.span()); + } + cfg_info.hidden_cfg.remove(&Cfg::Cfg(key, value)); + } else { + if let Some(span) = new_show_attrs.get(&(key, value)) { + show_hide_show_conflict_error(tcx, item.span(), *span); + } else { + new_hide_attrs.insert((key, value), item.span()); + } + cfg_info.hidden_cfg.insert(Cfg::Cfg(key, value)); + } + } + } + } +} + +pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator<Item = &'a hir::Attribute> + Clone>( + attrs: I, + tcx: TyCtxt<'_>, + cfg_info: &mut CfgInfo, +) -> Option<Arc<Cfg>> { + fn single<T: IntoIterator>(it: T) -> Option<T::Item> { + let mut iter = it.into_iter(); + let item = iter.next()?; + if iter.next().is_some() { + return None; + } + Some(item) + } + + fn check_changed_auto_active_status( + changed_auto_active_status: &mut Option<rustc_span::Span>, + attr: &ast::MetaItem, + cfg_info: &mut CfgInfo, + tcx: TyCtxt<'_>, + new_value: bool, + ) -> bool { + if let Some(first_change) = changed_auto_active_status { + if cfg_info.auto_cfg_active != new_value { + tcx.sess + .dcx() + .struct_span_err( + vec![*first_change, attr.span], + "`auto_cfg` was disabled and enabled more than once on the same item", + ) + .emit(); + return true; + } + } else { + *changed_auto_active_status = Some(attr.span); + } + cfg_info.auto_cfg_active = new_value; + false + } + + let mut new_show_attrs = FxHashMap::default(); + let mut new_hide_attrs = FxHashMap::default(); + + let mut doc_cfg = attrs + .clone() + .filter(|attr| attr.has_name(sym::doc)) + .flat_map(|attr| attr.meta_item_list().unwrap_or_default()) + .filter(|attr| attr.has_name(sym::cfg)) + .peekable(); + // If the item uses `doc(cfg(...))`, then we ignore the other `cfg(...)` attributes. + if doc_cfg.peek().is_some() { + let sess = tcx.sess; + // We overwrite existing `cfg`. + if !cfg_info.parent_is_doc_cfg { + cfg_info.current_cfg = Cfg::True; + cfg_info.parent_is_doc_cfg = true; + } + for attr in doc_cfg { + if let Some(cfg_mi) = + attr.meta_item().and_then(|attr| rustc_expand::config::parse_cfg(attr, sess)) + { + match Cfg::parse(cfg_mi) { + Ok(new_cfg) => cfg_info.current_cfg &= new_cfg, + Err(e) => { + sess.dcx().span_err(e.span, e.msg); + } + } + } + } + } else { + cfg_info.parent_is_doc_cfg = false; + } + + let mut changed_auto_active_status = None; + + // We get all `doc(auto_cfg)`, `cfg` and `target_feature` attributes. + for attr in attrs { + if let Some(ident) = attr.ident() + && ident.name == sym::doc + && let Some(attrs) = attr.meta_item_list() + { + for attr in attrs.iter().filter(|attr| attr.has_name(sym::auto_cfg)) { + let MetaItemInner::MetaItem(attr) = attr else { + continue; + }; + match &attr.kind { + MetaItemKind::Word => { + if check_changed_auto_active_status( + &mut changed_auto_active_status, + attr, + cfg_info, + tcx, + true, + ) { + return None; + } + } + MetaItemKind::NameValue(lit) => { + if let LitKind::Bool(value) = lit.kind { + if check_changed_auto_active_status( + &mut changed_auto_active_status, + attr, + cfg_info, + tcx, + value, + ) { + return None; + } + } + } + MetaItemKind::List(sub_attrs) => { + if check_changed_auto_active_status( + &mut changed_auto_active_status, + attr, + cfg_info, + tcx, + true, + ) { + return None; + } + for sub_attr in sub_attrs.iter() { + if let Some(ident) = sub_attr.ident() + && (ident.name == sym::show || ident.name == sym::hide) + { + handle_auto_cfg_hide_show( + tcx, + cfg_info, + &sub_attr, + ident.name == sym::show, + &mut new_show_attrs, + &mut new_hide_attrs, + ); + } + } + } + } + } + } else if let hir::Attribute::Parsed(AttributeKind::TargetFeature { features, .. }) = attr { + // Treat `#[target_feature(enable = "feat")]` attributes as if they were + // `#[doc(cfg(target_feature = "feat"))]` attributes as well. + for (feature, _) in features { + cfg_info.current_cfg &= Cfg::Cfg(sym::target_feature, Some(*feature)); + } + continue; + } else if !cfg_info.parent_is_doc_cfg + && let Some(ident) = attr.ident() + && matches!(ident.name, sym::cfg | sym::cfg_trace) + && let Some(attr) = single(attr.meta_item_list()?) + && let Ok(new_cfg) = Cfg::parse(&attr) + { + cfg_info.current_cfg &= new_cfg; + } + } + + // If `doc(auto_cfg)` feature is disabled and `doc(cfg())` wasn't used, there is nothing + // to be done here. + if !cfg_info.auto_cfg_active && !cfg_info.parent_is_doc_cfg { + None + } else if cfg_info.parent_is_doc_cfg { + if cfg_info.current_cfg == Cfg::True { + None + } else { + Some(Arc::new(cfg_info.current_cfg.clone())) + } + } else { + // If `doc(auto_cfg)` feature is enabled, we want to collect all `cfg` items, we remove the + // hidden ones afterward. + match cfg_info.current_cfg.strip_hidden(&cfg_info.hidden_cfg) { + None | Some(Cfg::True) => None, + Some(cfg) => Some(Arc::new(cfg)), + } + } +} |
