Skip to content

Commit 9e1b24d

Browse files
authored
Rollup merge of #147270 - GuillaumeGomez:move-cfg-code, r=lolbinarycat
Move doc_cfg-specific code into `cfg.rs` Follow-up of #138907. r? lolbinarycat
2 parents 87c7946 + 6275a19 commit 9e1b24d

File tree

3 files changed

+268
-264
lines changed

3 files changed

+268
-264
lines changed

src/librustdoc/clean/cfg.rs

Lines changed: 266 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,18 @@
33
// FIXME: Once the portability lint RFC is implemented (see tracking issue #41619),
44
// switch to use those structures instead.
55

6+
use std::sync::Arc;
67
use std::{fmt, mem, ops};
78

89
use itertools::Either;
910
use rustc_ast::{LitKind, MetaItem, MetaItemInner, MetaItemKind, MetaItemLit};
10-
use rustc_data_structures::fx::FxHashSet;
11+
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
12+
use rustc_hir::attrs::AttributeKind;
13+
use rustc_middle::ty::TyCtxt;
1114
use rustc_session::parse::ParseSess;
1215
use rustc_span::Span;
1316
use rustc_span::symbol::{Symbol, sym};
17+
use {rustc_ast as ast, rustc_hir as hir};
1418

1519
use crate::display::{Joined as _, MaybeDisplay, Wrapped};
1620
use crate::html::escape::Escape;
@@ -600,3 +604,264 @@ impl fmt::Display for Display<'_> {
600604
}
601605
}
602606
}
607+
608+
/// This type keeps track of (doc) cfg information as we go down the item tree.
609+
#[derive(Clone, Debug)]
610+
pub(crate) struct CfgInfo {
611+
/// List of currently active `doc(auto_cfg(hide(...)))` cfgs, minus currently active
612+
/// `doc(auto_cfg(show(...)))` cfgs.
613+
hidden_cfg: FxHashSet<Cfg>,
614+
/// Current computed `cfg`. Each time we enter a new item, this field is updated as well while
615+
/// taking into account the `hidden_cfg` information.
616+
current_cfg: Cfg,
617+
/// Whether the `doc(auto_cfg())` feature is enabled or not at this point.
618+
auto_cfg_active: bool,
619+
/// If the parent item used `doc(cfg(...))`, then we don't want to overwrite `current_cfg`,
620+
/// instead we will concatenate with it. However, if it's not the case, we need to overwrite
621+
/// `current_cfg`.
622+
parent_is_doc_cfg: bool,
623+
}
624+
625+
impl Default for CfgInfo {
626+
fn default() -> Self {
627+
Self {
628+
hidden_cfg: FxHashSet::from_iter([
629+
Cfg::Cfg(sym::test, None),
630+
Cfg::Cfg(sym::doc, None),
631+
Cfg::Cfg(sym::doctest, None),
632+
]),
633+
current_cfg: Cfg::True,
634+
auto_cfg_active: true,
635+
parent_is_doc_cfg: false,
636+
}
637+
}
638+
}
639+
640+
fn show_hide_show_conflict_error(
641+
tcx: TyCtxt<'_>,
642+
item_span: rustc_span::Span,
643+
previous: rustc_span::Span,
644+
) {
645+
let mut diag = tcx.sess.dcx().struct_span_err(
646+
item_span,
647+
format!(
648+
"same `cfg` was in `auto_cfg(hide(...))` and `auto_cfg(show(...))` on the same item"
649+
),
650+
);
651+
diag.span_note(previous, "first change was here");
652+
diag.emit();
653+
}
654+
655+
/// This functions updates the `hidden_cfg` field of the provided `cfg_info` argument.
656+
///
657+
/// It also checks if a same `cfg` is present in both `auto_cfg(hide(...))` and
658+
/// `auto_cfg(show(...))` on the same item and emits an error if it's the case.
659+
///
660+
/// Because we go through a list of `cfg`s, we keep track of the `cfg`s we saw in `new_show_attrs`
661+
/// and in `new_hide_attrs` arguments.
662+
fn handle_auto_cfg_hide_show(
663+
tcx: TyCtxt<'_>,
664+
cfg_info: &mut CfgInfo,
665+
sub_attr: &MetaItemInner,
666+
is_show: bool,
667+
new_show_attrs: &mut FxHashMap<(Symbol, Option<Symbol>), rustc_span::Span>,
668+
new_hide_attrs: &mut FxHashMap<(Symbol, Option<Symbol>), rustc_span::Span>,
669+
) {
670+
if let MetaItemInner::MetaItem(item) = sub_attr
671+
&& let MetaItemKind::List(items) = &item.kind
672+
{
673+
for item in items {
674+
// FIXME: Report in case `Cfg::parse` reports an error?
675+
if let Ok(Cfg::Cfg(key, value)) = Cfg::parse(item) {
676+
if is_show {
677+
if let Some(span) = new_hide_attrs.get(&(key, value)) {
678+
show_hide_show_conflict_error(tcx, item.span(), *span);
679+
} else {
680+
new_show_attrs.insert((key, value), item.span());
681+
}
682+
cfg_info.hidden_cfg.remove(&Cfg::Cfg(key, value));
683+
} else {
684+
if let Some(span) = new_show_attrs.get(&(key, value)) {
685+
show_hide_show_conflict_error(tcx, item.span(), *span);
686+
} else {
687+
new_hide_attrs.insert((key, value), item.span());
688+
}
689+
cfg_info.hidden_cfg.insert(Cfg::Cfg(key, value));
690+
}
691+
}
692+
}
693+
}
694+
}
695+
696+
pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator<Item = &'a hir::Attribute> + Clone>(
697+
attrs: I,
698+
tcx: TyCtxt<'_>,
699+
cfg_info: &mut CfgInfo,
700+
) -> Option<Arc<Cfg>> {
701+
fn single<T: IntoIterator>(it: T) -> Option<T::Item> {
702+
let mut iter = it.into_iter();
703+
let item = iter.next()?;
704+
if iter.next().is_some() {
705+
return None;
706+
}
707+
Some(item)
708+
}
709+
710+
fn check_changed_auto_active_status(
711+
changed_auto_active_status: &mut Option<rustc_span::Span>,
712+
attr: &ast::MetaItem,
713+
cfg_info: &mut CfgInfo,
714+
tcx: TyCtxt<'_>,
715+
new_value: bool,
716+
) -> bool {
717+
if let Some(first_change) = changed_auto_active_status {
718+
if cfg_info.auto_cfg_active != new_value {
719+
tcx.sess
720+
.dcx()
721+
.struct_span_err(
722+
vec![*first_change, attr.span],
723+
"`auto_cfg` was disabled and enabled more than once on the same item",
724+
)
725+
.emit();
726+
return true;
727+
}
728+
} else {
729+
*changed_auto_active_status = Some(attr.span);
730+
}
731+
cfg_info.auto_cfg_active = new_value;
732+
false
733+
}
734+
735+
let mut new_show_attrs = FxHashMap::default();
736+
let mut new_hide_attrs = FxHashMap::default();
737+
738+
let mut doc_cfg = attrs
739+
.clone()
740+
.filter(|attr| attr.has_name(sym::doc))
741+
.flat_map(|attr| attr.meta_item_list().unwrap_or_default())
742+
.filter(|attr| attr.has_name(sym::cfg))
743+
.peekable();
744+
// If the item uses `doc(cfg(...))`, then we ignore the other `cfg(...)` attributes.
745+
if doc_cfg.peek().is_some() {
746+
let sess = tcx.sess;
747+
// We overwrite existing `cfg`.
748+
if !cfg_info.parent_is_doc_cfg {
749+
cfg_info.current_cfg = Cfg::True;
750+
cfg_info.parent_is_doc_cfg = true;
751+
}
752+
for attr in doc_cfg {
753+
if let Some(cfg_mi) =
754+
attr.meta_item().and_then(|attr| rustc_expand::config::parse_cfg(attr, sess))
755+
{
756+
match Cfg::parse(cfg_mi) {
757+
Ok(new_cfg) => cfg_info.current_cfg &= new_cfg,
758+
Err(e) => {
759+
sess.dcx().span_err(e.span, e.msg);
760+
}
761+
}
762+
}
763+
}
764+
} else {
765+
cfg_info.parent_is_doc_cfg = false;
766+
}
767+
768+
let mut changed_auto_active_status = None;
769+
770+
// We get all `doc(auto_cfg)`, `cfg` and `target_feature` attributes.
771+
for attr in attrs {
772+
if let Some(ident) = attr.ident()
773+
&& ident.name == sym::doc
774+
&& let Some(attrs) = attr.meta_item_list()
775+
{
776+
for attr in attrs.iter().filter(|attr| attr.has_name(sym::auto_cfg)) {
777+
let MetaItemInner::MetaItem(attr) = attr else {
778+
continue;
779+
};
780+
match &attr.kind {
781+
MetaItemKind::Word => {
782+
if check_changed_auto_active_status(
783+
&mut changed_auto_active_status,
784+
attr,
785+
cfg_info,
786+
tcx,
787+
true,
788+
) {
789+
return None;
790+
}
791+
}
792+
MetaItemKind::NameValue(lit) => {
793+
if let LitKind::Bool(value) = lit.kind {
794+
if check_changed_auto_active_status(
795+
&mut changed_auto_active_status,
796+
attr,
797+
cfg_info,
798+
tcx,
799+
value,
800+
) {
801+
return None;
802+
}
803+
}
804+
}
805+
MetaItemKind::List(sub_attrs) => {
806+
if check_changed_auto_active_status(
807+
&mut changed_auto_active_status,
808+
attr,
809+
cfg_info,
810+
tcx,
811+
true,
812+
) {
813+
return None;
814+
}
815+
for sub_attr in sub_attrs.iter() {
816+
if let Some(ident) = sub_attr.ident()
817+
&& (ident.name == sym::show || ident.name == sym::hide)
818+
{
819+
handle_auto_cfg_hide_show(
820+
tcx,
821+
cfg_info,
822+
&sub_attr,
823+
ident.name == sym::show,
824+
&mut new_show_attrs,
825+
&mut new_hide_attrs,
826+
);
827+
}
828+
}
829+
}
830+
}
831+
}
832+
} else if let hir::Attribute::Parsed(AttributeKind::TargetFeature { features, .. }) = attr {
833+
// Treat `#[target_feature(enable = "feat")]` attributes as if they were
834+
// `#[doc(cfg(target_feature = "feat"))]` attributes as well.
835+
for (feature, _) in features {
836+
cfg_info.current_cfg &= Cfg::Cfg(sym::target_feature, Some(*feature));
837+
}
838+
continue;
839+
} else if !cfg_info.parent_is_doc_cfg
840+
&& let Some(ident) = attr.ident()
841+
&& matches!(ident.name, sym::cfg | sym::cfg_trace)
842+
&& let Some(attr) = single(attr.meta_item_list()?)
843+
&& let Ok(new_cfg) = Cfg::parse(&attr)
844+
{
845+
cfg_info.current_cfg &= new_cfg;
846+
}
847+
}
848+
849+
// If `doc(auto_cfg)` feature is disabled and `doc(cfg())` wasn't used, there is nothing
850+
// to be done here.
851+
if !cfg_info.auto_cfg_active && !cfg_info.parent_is_doc_cfg {
852+
None
853+
} else if cfg_info.parent_is_doc_cfg {
854+
if cfg_info.current_cfg == Cfg::True {
855+
None
856+
} else {
857+
Some(Arc::new(cfg_info.current_cfg.clone()))
858+
}
859+
} else {
860+
// If `doc(auto_cfg)` feature is enabled, we want to collect all `cfg` items, we remove the
861+
// hidden ones afterward.
862+
match cfg_info.current_cfg.strip_hidden(&cfg_info.hidden_cfg) {
863+
None | Some(Cfg::True) => None,
864+
Some(cfg) => Some(Arc::new(cfg)),
865+
}
866+
}
867+
}

src/librustdoc/clean/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ use tracing::{debug, instrument};
5858
use utils::*;
5959
use {rustc_ast as ast, rustc_hir as hir};
6060

61+
pub(crate) use self::cfg::{CfgInfo, extract_cfg_from_attrs};
6162
pub(crate) use self::types::*;
6263
pub(crate) use self::utils::{krate, register_res, synthesize_auto_trait_and_blanket_impls};
6364
use crate::core::DocContext;

0 commit comments

Comments
 (0)