@@ -8,7 +8,7 @@ use rustc_middle::hir::map::Map;
88use rustc_middle:: ty:: query:: Providers ;
99use rustc_middle:: ty:: TyCtxt ;
1010
11- use rustc_ast:: { Attribute , Lit , LitKind , NestedMetaItem } ;
11+ use rustc_ast:: { AttrStyle , Attribute , Lit , LitKind , NestedMetaItem } ;
1212use rustc_errors:: { pluralize, struct_span_err, Applicability } ;
1313use rustc_hir as hir;
1414use rustc_hir:: def_id:: LocalDefId ;
@@ -22,7 +22,7 @@ use rustc_session::lint::builtin::{
2222} ;
2323use rustc_session:: parse:: feature_err;
2424use rustc_span:: symbol:: { sym, Symbol } ;
25- use rustc_span:: { Span , DUMMY_SP } ;
25+ use rustc_span:: { MultiSpan , Span , DUMMY_SP } ;
2626
2727pub ( crate ) fn target_from_impl_item < ' tcx > (
2828 tcx : TyCtxt < ' tcx > ,
@@ -67,6 +67,7 @@ impl CheckAttrVisitor<'tcx> {
6767 item : Option < ItemLike < ' _ > > ,
6868 ) {
6969 let mut is_valid = true ;
70+ let mut specified_inline = None ;
7071 let attrs = self . tcx . hir ( ) . attrs ( hir_id) ;
7172 for attr in attrs {
7273 is_valid &= match attr. name_or_empty ( ) {
@@ -77,7 +78,7 @@ impl CheckAttrVisitor<'tcx> {
7778 sym:: track_caller => {
7879 self . check_track_caller ( hir_id, & attr. span , attrs, span, target)
7980 }
80- sym:: doc => self . check_doc_attrs ( attr, hir_id, target) ,
81+ sym:: doc => self . check_doc_attrs ( attr, hir_id, target, & mut specified_inline ) ,
8182 sym:: no_link => self . check_no_link ( hir_id, & attr, span, target) ,
8283 sym:: export_name => self . check_export_name ( hir_id, & attr, span, target) ,
8384 sym:: rustc_args_required_const => {
@@ -564,7 +565,71 @@ impl CheckAttrVisitor<'tcx> {
564565 true
565566 }
566567
567- fn check_attr_crate_level (
568+ /// Checks `#[doc(inline)]`/`#[doc(no_inline)]` attributes. Returns `true` if valid.
569+ ///
570+ /// A doc inlining attribute is invalid if it is applied to a non-`use` item, or
571+ /// if there are conflicting attributes for one item.
572+ ///
573+ /// `specified_inline` is used to keep track of whether we have
574+ /// already seen an inlining attribute for this item.
575+ /// If so, `specified_inline` holds the value and the span of
576+ /// the first `inline`/`no_inline` attribute.
577+ fn check_doc_inline (
578+ & self ,
579+ attr : & Attribute ,
580+ meta : & NestedMetaItem ,
581+ hir_id : HirId ,
582+ target : Target ,
583+ specified_inline : & mut Option < ( bool , Span ) > ,
584+ ) -> bool {
585+ if target == Target :: Use {
586+ let do_inline = meta. name_or_empty ( ) == sym:: inline;
587+ if let Some ( ( prev_inline, prev_span) ) = * specified_inline {
588+ if do_inline != prev_inline {
589+ let mut spans = MultiSpan :: from_spans ( vec ! [ prev_span, meta. span( ) ] ) ;
590+ spans. push_span_label ( prev_span, String :: from ( "this attribute..." ) ) ;
591+ spans. push_span_label (
592+ meta. span ( ) ,
593+ String :: from ( "...conflicts with this attribute" ) ,
594+ ) ;
595+ self . tcx
596+ . sess
597+ . struct_span_err ( spans, "conflicting doc inlining attributes" )
598+ . help ( "remove one of the conflicting attributes" )
599+ . emit ( ) ;
600+ return false ;
601+ }
602+ true
603+ } else {
604+ * specified_inline = Some ( ( do_inline, meta. span ( ) ) ) ;
605+ true
606+ }
607+ } else {
608+ self . tcx . struct_span_lint_hir (
609+ INVALID_DOC_ATTRIBUTES ,
610+ hir_id,
611+ meta. span ( ) ,
612+ |lint| {
613+ let mut err = lint. build (
614+ "this attribute can only be applied to a `use` item" ,
615+ ) ;
616+ err. span_label ( meta. span ( ) , "only applicable on `use` items" ) ;
617+ if attr. style == AttrStyle :: Outer {
618+ err. span_label (
619+ self . tcx . hir ( ) . span ( hir_id) ,
620+ "not a `use` item" ,
621+ ) ;
622+ }
623+ err. note ( "read https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#docno_inlinedocinline for more information" )
624+ . emit ( ) ;
625+ } ,
626+ ) ;
627+ false
628+ }
629+ }
630+
631+ /// Checks that an attribute is *not* used at the crate level. Returns `true` if valid.
632+ fn check_attr_not_crate_level (
568633 & self ,
569634 meta : & NestedMetaItem ,
570635 hir_id : HirId ,
@@ -586,40 +651,103 @@ impl CheckAttrVisitor<'tcx> {
586651 true
587652 }
588653
589- fn check_doc_attrs ( & self , attr : & Attribute , hir_id : HirId , target : Target ) -> bool {
654+ /// Checks that an attribute is used at the crate level. Returns `true` if valid.
655+ fn check_attr_crate_level (
656+ & self ,
657+ attr : & Attribute ,
658+ meta : & NestedMetaItem ,
659+ hir_id : HirId ,
660+ ) -> bool {
661+ if hir_id != CRATE_HIR_ID {
662+ self . tcx . struct_span_lint_hir (
663+ INVALID_DOC_ATTRIBUTES ,
664+ hir_id,
665+ meta. span ( ) ,
666+ |lint| {
667+ let mut err = lint. build (
668+ "this attribute can only be applied at the crate level" ,
669+ ) ;
670+ if attr. style == AttrStyle :: Outer && self . tcx . hir ( ) . get_parent_item ( hir_id) == CRATE_HIR_ID {
671+ if let Ok ( mut src) =
672+ self . tcx . sess . source_map ( ) . span_to_snippet ( attr. span )
673+ {
674+ src. insert ( 1 , '!' ) ;
675+ err. span_suggestion_verbose (
676+ attr. span ,
677+ "to apply to the crate, use an inner attribute" ,
678+ src,
679+ Applicability :: MaybeIncorrect ,
680+ ) ;
681+ } else {
682+ err. span_help (
683+ attr. span ,
684+ "to apply to the crate, use an inner attribute" ,
685+ ) ;
686+ }
687+ }
688+ err. note ( "read https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#at-the-crate-level for more information" )
689+ . emit ( ) ;
690+ } ,
691+ ) ;
692+ return false ;
693+ }
694+ true
695+ }
696+
697+ /// Runs various checks on `#[doc]` attributes. Returns `true` if valid.
698+ ///
699+ /// `specified_inline` should be initialized to `None` and kept for the scope
700+ /// of one item. Read the documentation of [`check_doc_inline`] for more information.
701+ ///
702+ /// [`check_doc_inline`]: Self::check_doc_inline
703+ fn check_doc_attrs (
704+ & self ,
705+ attr : & Attribute ,
706+ hir_id : HirId ,
707+ target : Target ,
708+ specified_inline : & mut Option < ( bool , Span ) > ,
709+ ) -> bool {
590710 let mut is_valid = true ;
591711
592712 if let Some ( list) = attr. meta ( ) . and_then ( |mi| mi. meta_item_list ( ) . map ( |l| l. to_vec ( ) ) ) {
593713 for meta in list {
594714 if let Some ( i_meta) = meta. meta_item ( ) {
595715 match i_meta. name_or_empty ( ) {
596716 sym:: alias
597- if !self . check_attr_crate_level ( & meta, hir_id, "alias" )
717+ if !self . check_attr_not_crate_level ( & meta, hir_id, "alias" )
598718 || !self . check_doc_alias ( & meta, hir_id, target) =>
599719 {
600720 is_valid = false
601721 }
602722
603723 sym:: keyword
604- if !self . check_attr_crate_level ( & meta, hir_id, "keyword" )
724+ if !self . check_attr_not_crate_level ( & meta, hir_id, "keyword" )
605725 || !self . check_doc_keyword ( & meta, hir_id) =>
606726 {
607727 is_valid = false
608728 }
609729
610- sym:: test if CRATE_HIR_ID != hir_id => {
611- self . tcx . struct_span_lint_hir (
612- INVALID_DOC_ATTRIBUTES ,
730+ sym:: html_favicon_url
731+ | sym:: html_logo_url
732+ | sym:: html_playground_url
733+ | sym:: issue_tracker_base_url
734+ | sym:: html_root_url
735+ | sym:: html_no_source
736+ | sym:: test
737+ if !self . check_attr_crate_level ( & attr, & meta, hir_id) =>
738+ {
739+ is_valid = false ;
740+ }
741+
742+ sym:: inline | sym:: no_inline
743+ if !self . check_doc_inline (
744+ & attr,
745+ & meta,
613746 hir_id,
614- meta. span ( ) ,
615- |lint| {
616- lint. build (
617- "`#![doc(test(...)]` is only allowed \
618- as a crate-level attribute",
619- )
620- . emit ( ) ;
621- } ,
622- ) ;
747+ target,
748+ specified_inline,
749+ ) =>
750+ {
623751 is_valid = false ;
624752 }
625753
0 commit comments