1- use rustc_ast:: ast:: { AttrStyle , LitKind , MetaItemLit } ;
1+ use rustc_ast:: ExprKind ;
2+ use rustc_ast:: ast:: { self , AttrArgs , AttrKind , AttrStyle , LitKind , MetaItemLit } ;
23use rustc_data_structures:: fx:: { FxHashSet , FxIndexMap , IndexEntry } ;
3- use rustc_errors:: { Applicability , msg } ;
4+ use rustc_errors:: { Applicability , DiagArgValue , Diagnostic , MultiSpan } ;
45use rustc_feature:: AttributeStability ;
56use rustc_hir:: Target ;
67use rustc_hir:: attrs:: {
78 AttributeKind , CfgEntry , CfgHideShow , DocAttribute , DocCfgHideShow , DocCfgHideShowValue ,
89 DocInline , HideOrShow ,
910} ;
10- use rustc_session :: diagnostics :: feature_err ;
11+ use rustc_lint_defs :: LintId ;
1112use rustc_span:: { Span , Symbol , edition, sym} ;
1213
1314use super :: prelude:: { ALL_TARGETS , AllowedTargets } ;
1415use super :: { AcceptMapping , AttributeParser , template} ;
16+ use crate :: EmitAttribute ;
1517use crate :: context:: { AcceptContext , FinalizeContext } ;
1618use crate :: diagnostics:: {
1719 AttrCrateLevelOnly , DocAliasBadChar , DocAliasDuplicated , DocAliasEmpty , DocAliasMalformed ,
@@ -21,7 +23,8 @@ use crate::diagnostics::{
2123 DocAutoCfgHideShowValuesMix , DocAutoCfgWrongLiteral , DocKeywordNotKeyword , DocTestLiteral ,
2224 DocTestTakesList , DocTestUnknown , DocUnknownAny , DocUnknownInclude , DocUnknownPasses ,
2325 DocUnknownPlugins , DocUnknownSpotlight , ExpectedNameValue , ExpectedNoArgs ,
24- IllFormedAttributeInput , MalformedDoc , UnusedDuplicate ,
26+ IllFormedAttributeInput , InvalidExprInDocAttrOnMacro , InvalidTarget , MalformedDoc ,
27+ UnusedDuplicate ,
2528} ;
2629use crate :: parser:: {
2730 ArgParser , MetaItemListParser , MetaItemOrLitParser , MetaItemParser , OwnedPathParser ,
@@ -549,19 +552,15 @@ impl DocParser {
549552 }
550553 macro_rules! no_args_and_crate_level {
551554 ( $ident: ident) => { {
552- no_args_and_crate_level!( $ident, |span| { } ) ;
553- } } ;
554- ( $ident: ident, |$span: ident| $extra_validation: block) => { {
555555 if let Err ( span) = args. as_no_args( ) {
556556 expected_no_args( cx, span) ;
557557 return ;
558558 }
559- let $ span = path. span( ) ;
560- if !check_attr_crate_level( cx, $ span) {
559+ let span = path. span( ) ;
560+ if !check_attr_crate_level( cx, span) {
561561 return ;
562562 }
563- $extra_validation
564- self . attribute. $ident = Some ( $span) ;
563+ self . attribute. $ident = Some ( span) ;
565564 } } ;
566565 }
567566 macro_rules! string_arg_and_crate_level {
@@ -592,6 +591,12 @@ impl DocParser {
592591 self . attribute. $ident = Some ( ( s, path. span( ) ) ) ;
593592 } } ;
594593 }
594+ macro_rules! gated {
595+ ( $feature: ident $( , $notes: expr) * ) => {
596+ let stability = $crate:: unstable!( $feature $( , $notes) * ) ;
597+ cx. shared. cx. check_attribute_stability( & cx. attr_path, cx. attr_span, stability) ;
598+ } ;
599+ }
595600
596601 match path. word_sym ( ) {
597602 Some ( sym:: alias) => self . parse_alias ( cx, path, args) ,
@@ -606,37 +611,60 @@ impl DocParser {
606611 }
607612 Some ( sym:: inline) => self . parse_inline ( cx, path, args, DocInline :: Inline ) ,
608613 Some ( sym:: no_inline) => self . parse_inline ( cx, path, args, DocInline :: NoInline ) ,
609- Some ( sym:: masked) => no_args ! ( masked) ,
610- Some ( sym:: cfg) => self . parse_cfg ( cx, args) ,
611- Some ( sym:: notable_trait) => no_args ! ( notable_trait) ,
612- Some ( sym:: keyword) => parse_keyword_and_attribute (
613- cx,
614- path,
615- args,
616- & mut self . attribute . keyword ,
617- sym:: keyword,
618- ) ,
619- Some ( sym:: attribute) => parse_keyword_and_attribute (
620- cx,
621- path,
622- args,
623- & mut self . attribute . attribute ,
624- sym:: attribute,
625- ) ,
626- Some ( sym:: fake_variadic) => no_args_and_not_crate_level ! ( fake_variadic) ,
627- Some ( sym:: search_unbox) => no_args_and_not_crate_level ! ( search_unbox) ,
628- Some ( sym:: rust_logo) => no_args_and_crate_level ! ( rust_logo, |span| {
629- if !cx. features( ) . rustdoc_internals( ) {
630- feature_err(
631- cx. sess( ) ,
632- sym:: rustdoc_internals,
633- span,
634- msg!( "the `#[doc(rust_logo)]` attribute is used for Rust branding" ) ,
635- )
636- . emit( ) ;
614+ Some ( sym:: masked) => {
615+ gated ! ( doc_masked) ;
616+ no_args ! ( masked)
617+ }
618+ Some ( sym:: cfg) => {
619+ gated ! ( doc_cfg) ;
620+ self . parse_cfg ( cx, args)
621+ }
622+ Some ( sym:: notable_trait) => {
623+ gated ! ( doc_notable_trait) ;
624+ no_args ! ( notable_trait)
625+ }
626+ Some ( sym:: keyword) => {
627+ gated ! ( rustdoc_internals) ;
628+ parse_keyword_and_attribute (
629+ cx,
630+ path,
631+ args,
632+ & mut self . attribute . keyword ,
633+ sym:: keyword,
634+ )
635+ }
636+ Some ( sym:: attribute) => {
637+ gated ! ( rustdoc_internals) ;
638+ parse_keyword_and_attribute (
639+ cx,
640+ path,
641+ args,
642+ & mut self . attribute . attribute ,
643+ sym:: attribute,
644+ )
645+ }
646+ Some ( sym:: fake_variadic) => {
647+ gated ! ( rustdoc_internals) ;
648+ no_args_and_not_crate_level ! ( fake_variadic)
649+ }
650+ Some ( sym:: search_unbox) => {
651+ gated ! ( rustdoc_internals) ;
652+ no_args_and_not_crate_level ! ( search_unbox)
653+ }
654+ Some ( sym:: rust_logo) => {
655+ // FIXME: Only feature gated at the crate level (!!)
656+ if cx. target == Target :: Crate {
657+ gated ! (
658+ rustdoc_internals,
659+ "the `#[doc(rust_logo)]` attribute is used for Rust branding"
660+ ) ;
637661 }
638- } ) ,
639- Some ( sym:: auto_cfg) => self . parse_auto_cfg ( cx, path, args) ,
662+ no_args_and_crate_level ! ( rust_logo)
663+ }
664+ Some ( sym:: auto_cfg) => {
665+ gated ! ( doc_cfg) ;
666+ self . parse_auto_cfg ( cx, path, args)
667+ }
640668 Some ( sym:: test) => {
641669 let Some ( list) = args. as_list ( ) else {
642670 cx. emit_lint (
@@ -797,6 +825,23 @@ impl AttributeParser for DocParser {
797825 ) ,
798826 AttributeStability :: Stable , // Some parts of the attribute are unstable, manually checked in parser
799827 |this, cx, args| {
828+ // FIXME: use actual target checking, see also `ALLOWED_TARGETS` comment
829+ if cx. target == Target :: MacroCall {
830+ let attr_span = cx. attr_span ;
831+ let diag = InvalidTarget {
832+ span : cx. inner_span ,
833+ attr_span,
834+ name : cx. attr_path . clone ( ) ,
835+ target : cx. target . plural_name ( ) ,
836+ only : "" ,
837+ applied : DiagArgValue :: Str ( "most other positions" . into ( ) ) ,
838+ attribute_args : "(...)" . to_string ( ) ,
839+ help : None ,
840+ previously_accepted : false ,
841+ on_macro_call : true ,
842+ } ;
843+ cx. emit_lint ( rustc_session:: lint:: builtin:: UNUSED_ATTRIBUTES , diag, attr_span) ;
844+ }
800845 this. accept_single_doc_attr ( cx, args) ;
801846 } ,
802847 ) ] ;
@@ -842,3 +887,41 @@ impl AttributeParser for DocParser {
842887 }
843888 }
844889}
890+
891+ /// Is this a `#[doc = mac!()]`?
892+ ///
893+ /// Or perhaps something as spicy as this?
894+ /// ```rust
895+ /// #[doc = {
896+ /// let a = 1;
897+ /// let b = 1;
898+ /// let sum = a + b;
899+ /// assert_eq!(sum, 2);
900+ /// }]
901+ /// println!();
902+ /// ```
903+ pub ( crate ) fn lint_non_lit_doc_attr (
904+ mut emit_lint : impl FnMut ( LintId , MultiSpan , EmitAttribute ) ,
905+ attr : & ast:: Attribute ,
906+ ) -> bool {
907+ if !attr. has_name ( sym:: doc) {
908+ return false ;
909+ }
910+ let AttrKind :: Normal ( n) = & attr. kind else { return false } ;
911+ let AttrArgs :: Eq { expr, .. } = & n. item . args else { return false } ;
912+ if matches ! ( expr. kind, ExprKind :: Lit ( _) ) {
913+ return false ;
914+ } ;
915+
916+ let attr_span = attr. span ;
917+ let expr_span = expr. span ;
918+
919+ emit_lint (
920+ LintId :: of ( rustc_session:: lint:: builtin:: UNUSED_ATTRIBUTES ) ,
921+ attr_span. into ( ) ,
922+ EmitAttribute ( Box :: new ( move |dcx, level, _| {
923+ InvalidExprInDocAttrOnMacro { attr_span, expr_span } . into_diag ( dcx, level)
924+ } ) ) ,
925+ ) ;
926+ true
927+ }
0 commit comments