|
3 | 3 | // FIXME: Once the portability lint RFC is implemented (see tracking issue #41619),
|
4 | 4 | // switch to use those structures instead.
|
5 | 5 |
|
| 6 | +use std::sync::Arc; |
6 | 7 | use std::{fmt, mem, ops};
|
7 | 8 |
|
8 | 9 | use itertools::Either;
|
9 | 10 | 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; |
11 | 14 | use rustc_session::parse::ParseSess;
|
12 | 15 | use rustc_span::Span;
|
13 | 16 | use rustc_span::symbol::{Symbol, sym};
|
| 17 | +use {rustc_ast as ast, rustc_hir as hir}; |
14 | 18 |
|
15 | 19 | use crate::display::{Joined as _, MaybeDisplay, Wrapped};
|
16 | 20 | use crate::html::escape::Escape;
|
@@ -600,3 +604,264 @@ impl fmt::Display for Display<'_> {
|
600 | 604 | }
|
601 | 605 | }
|
602 | 606 | }
|
| 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 | +} |
0 commit comments