Skip to main content

rustc_attr_parsing/attributes/
doc.rs

1use rustc_ast::ast::{AttrStyle, LitKind, MetaItemLit};
2use rustc_attr_ir::target::{AstTarget, ConstTypeAstTarget, Target};
3use rustc_attr_ir::{
4    AttributeKind, CfgEntry, CfgHideShow, DocAttribute, DocCfgHideShow, DocCfgHideShowValue,
5    DocInline, HideOrShow, NotableTraitColor,
6};
7use rustc_data_structures::fx::{FxHashSet, FxIndexMap, IndexEntry};
8use rustc_errors::{Applicability, MultiSpan, msg};
9use rustc_feature::AttributeStability;
10use rustc_lint_defs::builtin::{INVALID_DOC_ATTRIBUTES, UNUSED_ATTRIBUTES};
11use rustc_span::{Span, Symbol, edition, kw, sym};
12
13use super::prelude::{ALL_TARGETS, AllowedTargets};
14use super::{AcceptMapping, AttributeParser, template};
15use crate::context::{AcceptContext, FinalizeContext};
16use crate::diagnostics::{
17    DocAliasBadChar, DocAliasDuplicated, DocAliasEmpty, DocAliasMalformed, DocAliasStartEnd,
18    DocAttrNotCrateLevel, DocAttrNotParameters, DocAttrNotTraitLevel, DocAttributeNotAttribute,
19    DocAutoCfgExpectsHideOrShow, DocAutoCfgHideShowExpectsList,
20    DocAutoCfgHideShowNoIdentBeforeValues, DocAutoCfgHideShowUnexpectedItem,
21    DocAutoCfgHideShowUnexpectedItemAfterValues, DocAutoCfgHideShowValuesMix,
22    DocAutoCfgWrongLiteral, DocInlineConflict, DocInlineOnlyUse, DocKeywordAttributeNotAnonConst,
23    DocKeywordNotKeyword, DocTestLiteral, DocTestTakesList, DocTestUnknown, DocUnknownAny,
24    DocUnknownInclude, DocUnknownPasses, DocUnknownPlugins, DocUnknownSpotlight,
25    DuplicateNotableTraitColor, ExpectedNameValue, ExpectedNoArgs, IllFormedAttributeInput,
26    InvalidNotableTraitAttr, InvalidNotableTraitColor, MalformedDoc, UnusedDuplicate,
27};
28use crate::parser::{
29    ArgParser, MetaItemListParser, MetaItemOrLitParser, MetaItemParser, OwnedPathParser,
30};
31use crate::target_checking::Policy::{Allow, AllowSilent};
32
33fn check_keyword(cx: &mut AcceptContext<'_, '_>, keyword: Symbol, span: Span) -> bool {
34    // FIXME: Once rustdoc can handle URL conflicts on case insensitive file systems, we
35    // can remove the `SelfTy` case here, remove `sym::SelfTy`, and update the
36    // `#[doc(keyword = "SelfTy")` attribute in `library/std/src/keyword_docs.rs`.
37    if keyword.is_reserved(|| edition::LATEST_STABLE_EDITION)
38        || keyword.is_weak()
39        || keyword == sym::SelfTy
40    {
41        return true;
42    }
43    cx.emit_err(DocKeywordNotKeyword { span, keyword });
44    false
45}
46
47fn check_attribute(cx: &mut AcceptContext<'_, '_>, attribute: Symbol, span: Span) -> bool {
48    // FIXME: This should support attributes with namespace like `diagnostic::do_not_recommend`.
49    if rustc_feature::BUILTIN_ATTRIBUTE_SET.contains(&attribute) {
50        return true;
51    }
52    cx.emit_err(DocAttributeNotAttribute { span, attribute });
53    false
54}
55
56/// Checks that an attribute is *not* used at the crate level. Returns `true` if valid.
57fn check_attr_not_crate_level(
58    cx: &mut AcceptContext<'_, '_>,
59    span: Span,
60    attr_name: Symbol,
61) -> bool {
62    if cx.shared.target == Target::Crate {
63        cx.emit_err(DocAttrNotCrateLevel { span, attr_name });
64        return false;
65    }
66    true
67}
68
69// FIXME: To be removed once merged and replace with `cx.expected_name_value(span, _name)`.
70fn expected_name_value(cx: &mut AcceptContext<'_, '_>, span: Span, _name: Option<Symbol>) {
71    cx.emit_lint(INVALID_DOC_ATTRIBUTES, ExpectedNameValue, span);
72}
73
74// FIXME: remove this method once merged and use `cx.expected_no_args(span)` instead.
75fn expected_no_args(cx: &mut AcceptContext<'_, '_>, span: Span) {
76    cx.emit_lint(INVALID_DOC_ATTRIBUTES, ExpectedNoArgs, span);
77}
78
79// FIXME: remove this method once merged and use `cx.expected_no_args(span)` instead.
80// cx.expected_string_literal(span, _actual_literal);
81fn expected_string_literal(
82    cx: &mut AcceptContext<'_, '_>,
83    span: Span,
84    _actual_literal: Option<&MetaItemLit>,
85) {
86    cx.emit_lint(INVALID_DOC_ATTRIBUTES, MalformedDoc, span);
87}
88
89fn parse_notable_trait(
90    cx: &mut AcceptContext<'_, '_>,
91    path: &OwnedPathParser,
92    args: &ArgParser,
93    attr_value: &mut Option<(Option<(NotableTraitColor, Span)>, Span)>,
94    attr_name: Symbol,
95) {
96    let span = path.span();
97
98    let notable_trait_color_attr = match args {
99        ArgParser::NoArgs => None,
100        ArgParser::List(meta_item_list_parser) => {
101            if meta_item_list_parser.is_empty() {
102                None
103            } else if let Some(meta_item) = meta_item_list_parser.as_single() {
104                Some(meta_item)
105            } else {
106                let color_spans: Option<Vec<_>> = meta_item_list_parser
107                    .mixed()
108                    .map(|item| {
109                        let meta = item.meta_item()?;
110                        meta.path().word_is(sym::color).then(|| meta.path().span())
111                    })
112                    .collect();
113
114                if let Some(color_spans) = color_spans {
115                    let first_span = color_spans[0];
116                    let duplicate_spans = &color_spans[1..];
117
118                    let mut spans = MultiSpan::from_spans(duplicate_spans.to_vec());
119                    spans.push_span_label(first_span, "");
120
121                    for &duplicate_span in duplicate_spans {
122                        spans.push_span_label(duplicate_span, rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed("multiple colors specified"))msg!("multiple colors specified"));
123                    }
124
125                    cx.emit_lint(INVALID_DOC_ATTRIBUTES, DuplicateNotableTraitColor, spans);
126                } else {
127                    let spans = meta_item_list_parser
128                        .mixed()
129                        .map(|item| match item.meta_item() {
130                            Some(meta) => meta.path().span(),
131                            None => item.span(),
132                        })
133                        .collect();
134
135                    cx.emit_lint(
136                        INVALID_DOC_ATTRIBUTES,
137                        InvalidNotableTraitAttr,
138                        MultiSpan::from_spans(spans),
139                    );
140                }
141
142                return;
143            }
144        }
145        ArgParser::NameValue(_) => {
146            // FIXME(164728): every invalid notable trait attribute lint produces exactly
147            // the same warning and we should switch to more specific diagnostics
148            cx.emit_lint(INVALID_DOC_ATTRIBUTES, InvalidNotableTraitAttr, span);
149            return;
150        }
151    };
152
153    let notable_trait_color_and_span = if let Some(notable_trait_color_attr) =
154        notable_trait_color_attr
155    {
156        let Some(notable_trait_color_attr) = notable_trait_color_attr.meta_item() else {
157            cx.emit_lint(INVALID_DOC_ATTRIBUTES, InvalidNotableTraitAttr, span);
158            return;
159        };
160        if !notable_trait_color_attr.path().word_is(sym::color) {
161            cx.emit_lint(INVALID_DOC_ATTRIBUTES, InvalidNotableTraitAttr, span);
162            return;
163        }
164        let Some(notable_trait_color) = notable_trait_color_attr.args().as_name_value() else {
165            cx.emit_lint(
166                INVALID_DOC_ATTRIBUTES,
167                InvalidNotableTraitAttr,
168                notable_trait_color_attr.path().span(),
169            );
170            return;
171        };
172
173        let color_value_span = notable_trait_color.value_span;
174
175        let Some(notable_trait_color) = notable_trait_color.value_as_str() else {
176            cx.emit_lint(INVALID_DOC_ATTRIBUTES, InvalidNotableTraitAttr, span);
177            return;
178        };
179
180        let notable_trait_color = match notable_trait_color.as_str() {
181            "grey" | "gray" => NotableTraitColor::Grey,
182            "red" => NotableTraitColor::Red,
183            "green" => NotableTraitColor::Green,
184            "yellow" => NotableTraitColor::Yellow,
185            "blue" => NotableTraitColor::Blue,
186            "magenta" => NotableTraitColor::Magenta,
187            "cyan" => NotableTraitColor::Cyan,
188            "transparent" => NotableTraitColor::Transparent,
189            _ => {
190                cx.emit_lint(INVALID_DOC_ATTRIBUTES, InvalidNotableTraitColor, color_value_span);
191                return;
192            }
193        };
194        Some((notable_trait_color, notable_trait_color_attr.span()))
195    } else {
196        None
197    };
198
199    if cx.shared.target != Target::Trait {
200        cx.emit_lint(INVALID_DOC_ATTRIBUTES, DocAttrNotTraitLevel { span, attr_name }, span);
201        return;
202    }
203
204    *attr_value = Some((notable_trait_color_and_span, span));
205}
206
207fn parse_keyword_and_attribute(
208    cx: &mut AcceptContext<'_, '_>,
209    path: &OwnedPathParser,
210    args: &ArgParser,
211    attr_value: &mut Option<(Symbol, Span)>,
212    attr_name: Symbol,
213) {
214    let Some(nv) = args.as_name_value() else {
215        expected_name_value(cx, args.span().unwrap_or(path.span()), path.word_sym());
216        return;
217    };
218
219    let Some(value) = nv.value_as_str() else {
220        expected_string_literal(cx, nv.value_span, Some(nv.value_as_lit()));
221        return;
222    };
223
224    let ret = if attr_name == sym::keyword {
225        check_keyword(cx, value, nv.value_span)
226    } else {
227        check_attribute(cx, value, nv.value_span)
228    };
229    if !ret {
230        return;
231    }
232
233    let span = path.span();
234    if attr_value.is_some() {
235        cx.adcx().duplicate_key(span, path.word_sym().unwrap());
236        return;
237    }
238
239    if !check_attr_not_crate_level(cx, span, attr_name) {
240        return;
241    }
242
243    if !#[allow(non_exhaustive_omitted_patterns)] match cx.ast_target {
    AstTarget::Const(ConstTypeAstTarget::ConstItem(item)) if
        item.ident.name == kw::Underscore => true,
    _ => false,
}matches!(
244        cx.ast_target,
245        AstTarget::Const(ConstTypeAstTarget::ConstItem(item))
246        if item.ident.name == kw::Underscore)
247    {
248        cx.emit_err(DocKeywordAttributeNotAnonConst { span, attr_name });
249        return;
250    }
251
252    *attr_value = Some((value, span));
253}
254
255#[derive(#[automatically_derived]impl ::core::default::Default for DocParser {
    #[inline]
    fn default() -> Self {
        DocParser {
            attribute: ::core::default::Default::default(),
            nb_doc_attrs: ::core::default::Default::default(),
        }
    }
}Default, #[automatically_derived]impl ::core::fmt::Debug for DocParser {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field2_finish(f,
            "DocParser attribute nb_doc_attrs", &self.attribute,
            &&self.nb_doc_attrs)
    }
}Debug)]
256pub(crate) struct DocParser {
257    attribute: DocAttribute,
258    nb_doc_attrs: usize,
259}
260
261impl DocParser {
262    fn parse_single_test_doc_attr_item(
263        &mut self,
264        cx: &mut AcceptContext<'_, '_>,
265        mip: &MetaItemParser,
266    ) {
267        let path = mip.path();
268        let args = mip.args();
269
270        match path.word_sym() {
271            Some(sym::no_crate_inject) => {
272                if let Err(span) = args.as_no_args() {
273                    expected_no_args(cx, span);
274                    return;
275                }
276
277                if let Some(used_span) = self.attribute.no_crate_inject {
278                    let unused_span = path.span();
279                    cx.emit_lint(
280                        INVALID_DOC_ATTRIBUTES,
281                        UnusedDuplicate { this: unused_span, other: used_span, warning: true },
282                        unused_span,
283                    );
284                    return;
285                }
286
287                cx.check_target(
288                    sym::no_crate_inject.as_str(),
289                    &AllowedTargets::AllowListWarnRest(&[
290                        Allow(Target::Crate),
291                        AllowSilent(Target::Param),
292                    ]),
293                );
294
295                self.attribute.no_crate_inject = Some(path.span())
296            }
297            Some(sym::attr) => {
298                let Some(list) = args.as_list() else {
299                    // FIXME(164728): remove this method once merged and uncomment the line
300                    // below instead. cx.expected_list(cx.attr_span, args);
301                    let span = cx.attr_span;
302                    cx.emit_lint(INVALID_DOC_ATTRIBUTES, MalformedDoc, span);
303                    return;
304                };
305                // FIXME(164728): remove this method once merged and uncomment the line
306                // below instead. cx.expected_list(cx.attr_span, args);
307                for attr in list.mixed() {
308                    // Arguments of `attr` are checked via the span, so can be safely ignored
309                    attr.ignore_args();
310                    self.attribute.test_attrs.push(attr.span());
311                }
312            }
313            Some(name) => {
314                cx.emit_lint(INVALID_DOC_ATTRIBUTES, DocTestUnknown { name }, path.span());
315            }
316            None => {
317                cx.emit_lint(INVALID_DOC_ATTRIBUTES, DocTestLiteral, path.span());
318            }
319        }
320    }
321
322    fn add_alias(&mut self, cx: &mut AcceptContext<'_, '_>, alias: Symbol, span: Span) {
323        let attr_str = "`#[doc(alias = \"...\")]`";
324        if alias == sym::empty {
325            cx.emit_err(DocAliasEmpty { span, attr_str });
326            return;
327        }
328
329        let alias_str = alias.as_str();
330        if let Some(c) =
331            alias_str.chars().find(|&c| c == '"' || c == '\'' || (c.is_whitespace() && c != ' '))
332        {
333            cx.emit_err(DocAliasBadChar { span, attr_str, char_: c });
334            return;
335        }
336        if alias_str.starts_with(' ') || alias_str.ends_with(' ') {
337            cx.emit_err(DocAliasStartEnd { span, attr_str });
338            return;
339        }
340        if !check_attr_not_crate_level(cx, span, sym::alias) {
341            return;
342        }
343
344        if let Some(first_definition) = self.attribute.aliases.get(&alias).copied() {
345            cx.emit_lint(UNUSED_ATTRIBUTES, DocAliasDuplicated { first_definition }, span);
346        }
347
348        self.attribute.aliases.insert(alias, span);
349    }
350
351    fn parse_alias(
352        &mut self,
353        cx: &mut AcceptContext<'_, '_>,
354        path: &OwnedPathParser,
355        args: &ArgParser,
356    ) {
357        match args {
358            ArgParser::NoArgs => {
359                cx.emit_err(DocAliasMalformed { span: args.span().unwrap_or(path.span()) });
360            }
361            ArgParser::List(list) => {
362                for i in list.mixed() {
363                    let Some(alias) = cx.expect_string_literal(i) else {
364                        continue;
365                    };
366
367                    self.add_alias(cx, alias, i.span());
368                }
369            }
370            ArgParser::NameValue(nv) => {
371                let Some(alias) = cx.expect_string_literal(nv) else {
372                    return;
373                };
374                self.add_alias(cx, alias, nv.value_span);
375            }
376        }
377    }
378
379    fn parse_inline(
380        &mut self,
381        cx: &mut AcceptContext<'_, '_>,
382        path: &OwnedPathParser,
383        args: &ArgParser,
384        inline: DocInline,
385    ) {
386        if let Err(span) = args.as_no_args() {
387            expected_no_args(cx, span);
388            return;
389        }
390
391        let span = path.span();
392        if let Some((previous_inline, previous_span)) = self.attribute.inline {
393            if previous_inline != inline {
394                let mut spans = MultiSpan::from_spans(::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [previous_span, span]))vec![previous_span, span]);
395                spans.push_span_label(previous_span, rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed("this attribute..."))msg!("this attribute..."));
396                spans.push_span_label(span, rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed("{\".\"}..conflicts with this attribute"))msg!("{\".\"}..conflicts with this attribute"));
397                cx.emit_err(DocInlineConflict { spans });
398            }
399        } else {
400            self.attribute.inline = Some((inline, span));
401        }
402
403        match cx.target {
404            Target::Use | Target::ExternCrate | Target::Param => {} // Param has a custom error
405            _ => {
406                let item_span = cx.target_span;
407                cx.emit_lint(
408                    INVALID_DOC_ATTRIBUTES,
409                    DocInlineOnlyUse { attr_span: span, item_span },
410                    span,
411                );
412            }
413        }
414    }
415
416    fn parse_cfg(&mut self, cx: &mut AcceptContext<'_, '_>, args: &ArgParser) {
417        // This function replaces cases like `cfg(all())` with `true`.
418        fn simplify_cfg(cfg_entry: &mut CfgEntry) {
419            match cfg_entry {
420                CfgEntry::All(cfgs, span) if cfgs.is_empty() => {
421                    *cfg_entry = CfgEntry::Bool(true, *span)
422                }
423                CfgEntry::Any(cfgs, span) if cfgs.is_empty() => {
424                    *cfg_entry = CfgEntry::Bool(false, *span)
425                }
426                CfgEntry::Not(cfg, _) => simplify_cfg(cfg),
427                _ => {}
428            }
429        }
430        if let Some(mut cfg_entry) = super::cfg::parse_cfg(cx, args) {
431            simplify_cfg(&mut cfg_entry);
432            self.attribute.cfg.push(cfg_entry);
433        }
434    }
435
436    // Parses the `doc(auto_cfg(hide/show(..., values())))` attribute.
437    fn parse_auto_cfg_values(
438        &self,
439        cx: &mut AcceptContext<'_, '_>,
440        list: &MetaItemListParser,
441        values: &mut Option<DocCfgHideShow>,
442    ) {
443        let mut cfg_values = DocCfgHideShow::new();
444
445        let mut values_set = FxHashSet::default();
446        for item in list.mixed() {
447            match item {
448                // If it's a string literal, all good.
449                MetaItemOrLitParser::Lit(MetaItemLit {
450                    kind: LitKind::Str(symbol, _),
451                    span,
452                    ..
453                }) => match &mut cfg_values {
454                    DocCfgHideShow::Any(any_span) => {
455                        cx.emit_lint(
456                            INVALID_DOC_ATTRIBUTES,
457                            DocAutoCfgHideShowValuesMix { value_span: *span },
458                            *any_span,
459                        );
460                    }
461                    DocCfgHideShow::List(symbols) => {
462                        if values_set.insert(symbol) {
463                            symbols.push(DocCfgHideShowValue::new(*symbol, *span));
464                        }
465                    }
466                },
467                // If it's any other kind of literal, then it's wrong and we emit a lint.
468                MetaItemOrLitParser::Lit(lit) => cx.emit_lint(
469                    INVALID_DOC_ATTRIBUTES,
470                    DocAutoCfgHideShowUnexpectedItem { attr_name: lit.symbol },
471                    lit.span,
472                ),
473                // If it's a list, then only `any()` and `none()` are allowed and they must not
474                // contain any item.
475                MetaItemOrLitParser::MetaItemParser(sub_item) => {
476                    let Some(ident) = sub_item.ident() else {
477                        cx.adcx().expected_identifier(sub_item.path().span());
478                        continue;
479                    };
480                    if [sym::any, sym::none].contains(&ident.name)
481                        && let ArgParser::List(list) = sub_item.args()
482                        && list.mixed().count() == 0
483                    {
484                        if ident.name == sym::any {
485                            if let DocCfgHideShow::List(values) = &cfg_values
486                                && let Some(value) = values.first()
487                            {
488                                cx.emit_lint(
489                                    INVALID_DOC_ATTRIBUTES,
490                                    DocAutoCfgHideShowValuesMix { value_span: value.span },
491                                    sub_item.span(),
492                                );
493                            } else {
494                                cfg_values.merge_with(&DocCfgHideShow::Any(sub_item.span()));
495                            }
496                        } else {
497                            cfg_values.push_none(sub_item.span());
498                        }
499                    } else {
500                        cx.emit_lint(
501                            INVALID_DOC_ATTRIBUTES,
502                            DocAutoCfgHideShowUnexpectedItem { attr_name: ident.name },
503                            sub_item.span(),
504                        );
505                    }
506                }
507            }
508        }
509        *values = Some(cfg_values);
510    }
511
512    fn parse_auto_cfg(
513        &mut self,
514        cx: &mut AcceptContext<'_, '_>,
515        path: &OwnedPathParser,
516        args: &ArgParser,
517    ) {
518        match args {
519            ArgParser::NoArgs => {
520                self.attribute.auto_cfg_change.push((true, path.span()));
521            }
522            ArgParser::List(list) => {
523                'main: for meta in list.mixed() {
524                    let MetaItemOrLitParser::MetaItemParser(item) = meta else {
525                        cx.emit_lint(
526                            INVALID_DOC_ATTRIBUTES,
527                            DocAutoCfgExpectsHideOrShow,
528                            meta.span(),
529                        );
530                        continue;
531                    };
532                    // Only `hide` and `show` are allowed in `auto_cfg` if it's a list, and both
533                    // must be a list.
534                    let (kind, attr_name) = match item.path().word_sym() {
535                        Some(sym::hide) => (HideOrShow::Hide, sym::hide),
536                        Some(sym::show) => (HideOrShow::Show, sym::show),
537                        _ => {
538                            cx.emit_lint(
539                                INVALID_DOC_ATTRIBUTES,
540                                DocAutoCfgExpectsHideOrShow,
541                                item.span(),
542                            );
543                            continue;
544                        }
545                    };
546                    let ArgParser::List(list) = item.args() else {
547                        cx.emit_lint(
548                            INVALID_DOC_ATTRIBUTES,
549                            DocAutoCfgHideShowExpectsList { attr_name },
550                            item.span(),
551                        );
552                        continue;
553                    };
554
555                    let mut cfg_hide_show = CfgHideShow { kind, values: FxIndexMap::default() };
556
557                    let mut cfg_names = FxHashSet::default();
558                    let mut values = None;
559                    for item in list.mixed() {
560                        let MetaItemOrLitParser::MetaItemParser(sub_item) = item else {
561                            cx.emit_lint(
562                                INVALID_DOC_ATTRIBUTES,
563                                DocAutoCfgHideShowUnexpectedItem { attr_name },
564                                item.span(),
565                            );
566                            continue 'main;
567                        };
568                        match sub_item.args() {
569                            ArgParser::NoArgs if values.is_none() => {
570                                let Some(name) = sub_item.path().word_sym() else {
571                                    cx.adcx().expected_identifier(sub_item.path().span());
572                                    continue 'main;
573                                };
574                                cfg_names.insert(name);
575                            }
576                            // The only accepted list is `values()`.
577                            ArgParser::List(list) if values.is_none() => {
578                                let Some(sym::values) = sub_item.path().word_sym() else {
579                                    cx.adcx().expected_identifier(sub_item.path().span());
580                                    continue 'main;
581                                };
582                                if cfg_names.is_empty() {
583                                    cx.emit_lint(
584                                        INVALID_DOC_ATTRIBUTES,
585                                        DocAutoCfgHideShowNoIdentBeforeValues,
586                                        sub_item.span(),
587                                    );
588                                    continue 'main;
589                                }
590                                self.parse_auto_cfg_values(cx, list, &mut values);
591                            }
592                            // No `name = value` is allowed.
593                            ArgParser::NameValue(_) => {
594                                cx.emit_lint(
595                                    INVALID_DOC_ATTRIBUTES,
596                                    DocAutoCfgHideShowUnexpectedItem { attr_name },
597                                    sub_item.span(),
598                                );
599                            }
600                            // If `values()` was already used, no item should come after it.
601                            _ => {
602                                cx.emit_lint(
603                                    INVALID_DOC_ATTRIBUTES,
604                                    DocAutoCfgHideShowUnexpectedItemAfterValues,
605                                    sub_item.span(),
606                                );
607                            }
608                        }
609                    }
610
611                    let values = values.unwrap_or(DocCfgHideShow::new_with_only_key(item.span()));
612                    #[allow(rustc::potential_query_instability)]
613                    for cfg_name in &cfg_names {
614                        match cfg_hide_show.values.entry(*cfg_name) {
615                            IndexEntry::Vacant(v) => {
616                                v.insert(values.clone());
617                            }
618                            IndexEntry::Occupied(mut o) => {
619                                o.get_mut().merge_with(&values);
620                            }
621                        }
622                    }
623                    self.attribute.auto_cfg.push((cfg_hide_show, path.span()));
624                }
625            }
626            ArgParser::NameValue(nv) => {
627                let MetaItemLit { kind: LitKind::Bool(bool_value), span, .. } = nv.value_as_lit()
628                else {
629                    cx.emit_lint(INVALID_DOC_ATTRIBUTES, DocAutoCfgWrongLiteral, nv.value_span);
630                    return;
631                };
632                self.attribute.auto_cfg_change.push((*bool_value, *span));
633            }
634        }
635    }
636
637    fn parse_single_doc_attr_item(&mut self, cx: &mut AcceptContext<'_, '_>, mip: &MetaItemParser) {
638        let path = mip.path();
639        let args = mip.args();
640
641        macro_rules! no_args {
642            ($ident: ident) => {{
643                if let Err(span) = args.as_no_args() {
644                    expected_no_args(cx, span);
645                    return;
646                }
647
648                // FIXME: It's errorring when the attribute is passed multiple times on the command
649                // line.
650                // The right fix for this would be to only check this rule if the attribute is
651                // not set on the command line but directly in the code.
652                // if self.attribute.$ident.is_some() {
653                //     cx.duplicate_key(path.span(), path.word_sym().unwrap());
654                //     return;
655                // }
656
657                self.attribute.$ident = Some(path.span());
658            }};
659        }
660        macro_rules! no_args_and_not_crate_level {
661            ($ident: ident) => {{
662                if let Err(span) = args.as_no_args() {
663                    expected_no_args(cx, span);
664                    return;
665                }
666                let span = path.span();
667                if !check_attr_not_crate_level(cx, span, sym::$ident) {
668                    return;
669                }
670                self.attribute.$ident = Some(span);
671            }};
672        }
673        macro_rules! no_args_and_crate_level {
674            ($ident: ident) => {{
675                if let Err(span) = args.as_no_args() {
676                    expected_no_args(cx, span);
677                    return;
678                }
679                let span = path.span();
680                cx.check_target(
681                    concat!("(", stringify!($ident), ")"),
682                    &AllowedTargets::AllowListWarnRest(&[
683                        Allow(Target::Crate),
684                        AllowSilent(Target::Param), // Param has a custom error
685                    ]),
686                );
687                self.attribute.$ident = Some(span);
688            }};
689        }
690        macro_rules! string_arg_and_crate_level {
691            ($ident: ident) => {{
692                let Some(nv) = args.as_name_value() else {
693                    expected_name_value(cx, args.span().unwrap_or(path.span()), path.word_sym());
694                    return;
695                };
696
697                let Some(s) = nv.value_as_str() else {
698                    expected_string_literal(cx, nv.value_span, Some(nv.value_as_lit()));
699                    return;
700                };
701
702                cx.check_target(
703                    s.as_str(),
704                    &AllowedTargets::AllowListWarnRest(&[
705                        Allow(Target::Crate),
706                        AllowSilent(Target::Param), // Param has a custom error
707                    ]),
708                );
709
710                // FIXME: It's errorring when the attribute is passed multiple times on the command
711                // line.
712                // The right fix for this would be to only check this rule if the attribute is
713                // not set on the command line but directly in the code.
714                // if self.attribute.$ident.is_some() {
715                //     cx.duplicate_key(path.span(), path.word_sym().unwrap());
716                //     return;
717                // }
718
719                self.attribute.$ident = Some((s, path.span()));
720            }};
721        }
722        macro_rules! gated {
723            ($feature:ident $(,$notes:expr)*) => {
724                let stability = $crate::unstable!($feature $(, $notes)*);
725                cx.shared.cx.check_attribute_stability(&cx.attr_path, path.span(), stability);
726            };
727        }
728
729        match path.word_sym() {
730            Some(sym::alias) => self.parse_alias(cx, path, args),
731            Some(sym::hidden) => {
    if let Err(span) = args.as_no_args() {
        expected_no_args(cx, span);
        return;
    }
    self.attribute.hidden = Some(path.span());
}no_args!(hidden),
732            Some(sym::html_favicon_url) => {
    let Some(nv) =
        args.as_name_value() else {
            expected_name_value(cx, args.span().unwrap_or(path.span()),
                path.word_sym());
            return;
        };
    let Some(s) =
        nv.value_as_str() else {
            expected_string_literal(cx, nv.value_span,
                Some(nv.value_as_lit()));
            return;
        };
    cx.check_target(s.as_str(),
        &AllowedTargets::AllowListWarnRest(&[Allow(Target::Crate),
                            AllowSilent(Target::Param)]));
    self.attribute.html_favicon_url = Some((s, path.span()));
}string_arg_and_crate_level!(html_favicon_url),
733            Some(sym::html_logo_url) => {
    let Some(nv) =
        args.as_name_value() else {
            expected_name_value(cx, args.span().unwrap_or(path.span()),
                path.word_sym());
            return;
        };
    let Some(s) =
        nv.value_as_str() else {
            expected_string_literal(cx, nv.value_span,
                Some(nv.value_as_lit()));
            return;
        };
    cx.check_target(s.as_str(),
        &AllowedTargets::AllowListWarnRest(&[Allow(Target::Crate),
                            AllowSilent(Target::Param)]));
    self.attribute.html_logo_url = Some((s, path.span()));
}string_arg_and_crate_level!(html_logo_url),
734            Some(sym::html_no_source) => {
    if let Err(span) = args.as_no_args() {
        expected_no_args(cx, span);
        return;
    }
    let span = path.span();
    cx.check_target("(html_no_source)",
        &AllowedTargets::AllowListWarnRest(&[Allow(Target::Crate),
                            AllowSilent(Target::Param)]));
    self.attribute.html_no_source = Some(span);
}no_args_and_crate_level!(html_no_source),
735            Some(sym::html_playground_url) => {
    let Some(nv) =
        args.as_name_value() else {
            expected_name_value(cx, args.span().unwrap_or(path.span()),
                path.word_sym());
            return;
        };
    let Some(s) =
        nv.value_as_str() else {
            expected_string_literal(cx, nv.value_span,
                Some(nv.value_as_lit()));
            return;
        };
    cx.check_target(s.as_str(),
        &AllowedTargets::AllowListWarnRest(&[Allow(Target::Crate),
                            AllowSilent(Target::Param)]));
    self.attribute.html_playground_url = Some((s, path.span()));
}string_arg_and_crate_level!(html_playground_url),
736            Some(sym::html_root_url) => {
    let Some(nv) =
        args.as_name_value() else {
            expected_name_value(cx, args.span().unwrap_or(path.span()),
                path.word_sym());
            return;
        };
    let Some(s) =
        nv.value_as_str() else {
            expected_string_literal(cx, nv.value_span,
                Some(nv.value_as_lit()));
            return;
        };
    cx.check_target(s.as_str(),
        &AllowedTargets::AllowListWarnRest(&[Allow(Target::Crate),
                            AllowSilent(Target::Param)]));
    self.attribute.html_root_url = Some((s, path.span()));
}string_arg_and_crate_level!(html_root_url),
737            Some(sym::issue_tracker_base_url) => {
738                {
    let Some(nv) =
        args.as_name_value() else {
            expected_name_value(cx, args.span().unwrap_or(path.span()),
                path.word_sym());
            return;
        };
    let Some(s) =
        nv.value_as_str() else {
            expected_string_literal(cx, nv.value_span,
                Some(nv.value_as_lit()));
            return;
        };
    cx.check_target(s.as_str(),
        &AllowedTargets::AllowListWarnRest(&[Allow(Target::Crate),
                            AllowSilent(Target::Param)]));
    self.attribute.issue_tracker_base_url = Some((s, path.span()));
}string_arg_and_crate_level!(issue_tracker_base_url)
739            }
740            Some(sym::inline) => self.parse_inline(cx, path, args, DocInline::Inline),
741            Some(sym::no_inline) => self.parse_inline(cx, path, args, DocInline::NoInline),
742            Some(sym::masked) => {
743                let stability =
    {
        _ = rustc_feature::Features::doc_masked;
        AttributeStability::Unstable {
            gate_name: rustc_span::sym::doc_masked,
            notes: &[],
        }
    };
cx.shared.cx.check_attribute_stability(&cx.attr_path, path.span(), stability);gated!(doc_masked);
744                {
    if let Err(span) = args.as_no_args() {
        expected_no_args(cx, span);
        return;
    }
    self.attribute.masked = Some(path.span());
}no_args!(masked)
745            }
746            Some(sym::cfg) => {
747                let stability =
    {
        _ = rustc_feature::Features::doc_cfg;
        AttributeStability::Unstable {
            gate_name: rustc_span::sym::doc_cfg,
            notes: &[],
        }
    };
cx.shared.cx.check_attribute_stability(&cx.attr_path, path.span(), stability);gated!(doc_cfg);
748                self.parse_cfg(cx, args)
749            }
750            Some(sym::notable_trait) => {
751                let stability =
    {
        _ = rustc_feature::Features::doc_notable_trait;
        AttributeStability::Unstable {
            gate_name: rustc_span::sym::doc_notable_trait,
            notes: &[],
        }
    };
cx.shared.cx.check_attribute_stability(&cx.attr_path, path.span(), stability);gated!(doc_notable_trait);
752                parse_notable_trait(
753                    cx,
754                    path,
755                    args,
756                    &mut self.attribute.notable_trait,
757                    sym::notable_trait,
758                )
759            }
760            Some(sym::keyword) => {
761                let stability =
    {
        _ = rustc_feature::Features::rustdoc_internals;
        AttributeStability::Unstable {
            gate_name: rustc_span::sym::rustdoc_internals,
            notes: &[],
        }
    };
cx.shared.cx.check_attribute_stability(&cx.attr_path, path.span(), stability);gated!(rustdoc_internals);
762                parse_keyword_and_attribute(
763                    cx,
764                    path,
765                    args,
766                    &mut self.attribute.keyword,
767                    sym::keyword,
768                )
769            }
770            Some(sym::attribute) => {
771                let stability =
    {
        _ = rustc_feature::Features::rustdoc_internals;
        AttributeStability::Unstable {
            gate_name: rustc_span::sym::rustdoc_internals,
            notes: &[],
        }
    };
cx.shared.cx.check_attribute_stability(&cx.attr_path, path.span(), stability);gated!(rustdoc_internals);
772                parse_keyword_and_attribute(
773                    cx,
774                    path,
775                    args,
776                    &mut self.attribute.attribute,
777                    sym::attribute,
778                )
779            }
780            Some(sym::fake_variadic) => {
781                let stability =
    {
        _ = rustc_feature::Features::rustdoc_internals;
        AttributeStability::Unstable {
            gate_name: rustc_span::sym::rustdoc_internals,
            notes: &[],
        }
    };
cx.shared.cx.check_attribute_stability(&cx.attr_path, path.span(), stability);gated!(rustdoc_internals);
782                {
    if let Err(span) = args.as_no_args() {
        expected_no_args(cx, span);
        return;
    }
    let span = path.span();
    if !check_attr_not_crate_level(cx, span, sym::fake_variadic) { return; }
    self.attribute.fake_variadic = Some(span);
}no_args_and_not_crate_level!(fake_variadic)
783            }
784            Some(sym::search_unbox) => {
785                let stability =
    {
        _ = rustc_feature::Features::rustdoc_internals;
        AttributeStability::Unstable {
            gate_name: rustc_span::sym::rustdoc_internals,
            notes: &[],
        }
    };
cx.shared.cx.check_attribute_stability(&cx.attr_path, path.span(), stability);gated!(rustdoc_internals);
786                {
    if let Err(span) = args.as_no_args() {
        expected_no_args(cx, span);
        return;
    }
    let span = path.span();
    if !check_attr_not_crate_level(cx, span, sym::search_unbox) { return; }
    self.attribute.search_unbox = Some(span);
}no_args_and_not_crate_level!(search_unbox)
787            }
788            Some(sym::rust_logo) => {
789                // FIXME: Only feature gated at the crate level (!!)
790                if cx.target == Target::Crate {
791                    let stability =
    {
        _ = rustc_feature::Features::rustdoc_internals;
        AttributeStability::Unstable {
            gate_name: rustc_span::sym::rustdoc_internals,
            notes: &["the `#[doc(rust_logo)]` attribute is used for Rust branding"],
        }
    };
cx.shared.cx.check_attribute_stability(&cx.attr_path, path.span(), stability);gated!(
792                        rustdoc_internals,
793                        "the `#[doc(rust_logo)]` attribute is used for Rust branding"
794                    );
795                }
796                {
    if let Err(span) = args.as_no_args() {
        expected_no_args(cx, span);
        return;
    }
    let span = path.span();
    cx.check_target("(rust_logo)",
        &AllowedTargets::AllowListWarnRest(&[Allow(Target::Crate),
                            AllowSilent(Target::Param)]));
    self.attribute.rust_logo = Some(span);
}no_args_and_crate_level!(rust_logo)
797            }
798            Some(sym::auto_cfg) => {
799                let stability =
    {
        _ = rustc_feature::Features::doc_cfg;
        AttributeStability::Unstable {
            gate_name: rustc_span::sym::doc_cfg,
            notes: &[],
        }
    };
cx.shared.cx.check_attribute_stability(&cx.attr_path, path.span(), stability);gated!(doc_cfg);
800                self.parse_auto_cfg(cx, path, args)
801            }
802            Some(sym::test) => {
803                let Some(list) = args.as_list() else {
804                    cx.emit_lint(
805                        INVALID_DOC_ATTRIBUTES,
806                        DocTestTakesList,
807                        args.span().unwrap_or(path.span()),
808                    );
809                    return;
810                };
811
812                for i in list.mixed() {
813                    match i {
814                        MetaItemOrLitParser::MetaItemParser(mip) => {
815                            self.parse_single_test_doc_attr_item(cx, mip);
816                        }
817                        MetaItemOrLitParser::Lit(lit) => {
818                            // FIXME: remove this method once merged and uncomment the line
819                            // below instead.
820                            // cx.unexpected_literal(lit.span);
821                            cx.emit_lint(INVALID_DOC_ATTRIBUTES, MalformedDoc, lit.span);
822                        }
823                    }
824                }
825            }
826            Some(sym::spotlight) => {
827                let span = path.span();
828                cx.emit_lint(INVALID_DOC_ATTRIBUTES, DocUnknownSpotlight { sugg_span: span }, span);
829            }
830            Some(sym::include) if let Some(nv) = args.as_name_value() => {
831                let inner = match cx.attr_style {
832                    AttrStyle::Outer => "",
833                    AttrStyle::Inner => "!",
834                };
835                let value = nv.value_as_lit().symbol;
836                let span = path.span();
837                cx.emit_lint(
838                    INVALID_DOC_ATTRIBUTES,
839                    DocUnknownInclude { inner, value, sugg: (span, Applicability::MaybeIncorrect) },
840                    span,
841                );
842            }
843            Some(name @ (sym::passes | sym::no_default_passes)) => {
844                let span = path.span();
845                cx.emit_lint(
846                    INVALID_DOC_ATTRIBUTES,
847                    DocUnknownPasses { name, note_span: span },
848                    span,
849                );
850            }
851            Some(sym::plugins) => {
852                let span = path.span();
853                cx.emit_lint(INVALID_DOC_ATTRIBUTES, DocUnknownPlugins { label_span: span }, span);
854            }
855            Some(name) => {
856                cx.emit_lint(INVALID_DOC_ATTRIBUTES, DocUnknownAny { name }, path.span());
857            }
858            None => {
859                let full_name =
860                    path.segments().map(|s| s.as_str()).intersperse("::").collect::<String>();
861                let name = Symbol::intern(&full_name);
862                cx.emit_lint(INVALID_DOC_ATTRIBUTES, DocUnknownAny { name }, path.span());
863            }
864        }
865    }
866
867    fn accept_single_doc_attr(&mut self, cx: &mut AcceptContext<'_, '_>, args: &ArgParser) {
868        if cx.shared.target == Target::Param {
869            cx.emit_err(DocAttrNotParameters { span: cx.attr_span });
870        }
871        match args {
872            ArgParser::List(items) if !items.is_empty() => {
873                for i in items.mixed() {
874                    match i {
875                        MetaItemOrLitParser::MetaItemParser(mip) => {
876                            if self.nb_doc_attrs == 0 {
877                                self.attribute.first_span = cx.attr_span;
878                            }
879                            self.nb_doc_attrs += 1;
880                            self.parse_single_doc_attr_item(cx, mip);
881                        }
882                        MetaItemOrLitParser::Lit(lit) => {
883                            expected_name_value(cx, lit.span, None);
884                        }
885                    }
886                }
887            }
888            ArgParser::NameValue(nv) => {
889                if nv.value_as_str().is_none() {
890                    expected_string_literal(cx, nv.value_span, Some(nv.value_as_lit()));
891                } else {
892                    {
    ::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
            format_args!("Should have been handled at the same time as sugar-syntaxed doc comments")));
};unreachable!(
893                        "Should have been handled at the same time as sugar-syntaxed doc comments"
894                    );
895                }
896            }
897            _ => {
898                let suggestions = cx.adcx().suggestions();
899                let span = cx.inner_span;
900                cx.emit_lint(
901                    INVALID_DOC_ATTRIBUTES,
902                    IllFormedAttributeInput::new(&suggestions, None, None),
903                    span,
904                );
905            }
906        }
907    }
908}
909
910impl AttributeParser for DocParser {
911    const ATTRIBUTES: AcceptMapping<Self> = &[(
912        &[sym::doc],
913        crate::AttributeTemplate {
    word: false,
    list: Some(&["alias", "hidden", "html_favicon_url", "html_logo_url",
                    "html_no_source", "html_playground_url", "html_root_url",
                    "issue_tracker_base_url", "inline", "no_inline", "masked",
                    "cfg", "notable_trait", "auto_cfg", "test"]),
    one_of: &[],
    name_value_str: Some(&["doc comment"]),
    docs: None,
}template!(
914            List: &[
915                "alias",
916                "hidden",
917                "html_favicon_url",
918                "html_logo_url",
919                "html_no_source",
920                "html_playground_url",
921                "html_root_url",
922                "issue_tracker_base_url",
923                "inline",
924                "no_inline",
925                "masked",
926                "cfg",
927                "notable_trait",
928                "auto_cfg",
929                "test",
930            ],
931            NameValueStr: "doc comment"
932        ),
933        AttributeStability::Stable, // Some parts of the attribute are unstable, manually checked in parser
934        |this, cx, args| {
935            this.accept_single_doc_attr(cx, args);
936        },
937    )];
938    // FIXME: Currently emitted from 2 different places, generating duplicated warnings.
939    const ALLOWED_TARGETS: AllowedTargets<'_> = AllowedTargets::AllowList(ALL_TARGETS);
940    // const ALLOWED_TARGETS: AllowedTargets<'_> = AllowedTargets::AllowListWarnRest(&[
941    //     Allow(Target::ExternCrate),
942    //     Allow(Target::Use),
943    //     Allow(Target::Static),
944    //     Allow(Target::Const),
945    //     Allow(Target::Fn),
946    //     Allow(Target::Mod),
947    //     Allow(Target::ForeignMod),
948    //     Allow(Target::TyAlias),
949    //     Allow(Target::Enum),
950    //     Allow(Target::Variant),
951    //     Allow(Target::Struct),
952    //     Allow(Target::Field),
953    //     Allow(Target::Union),
954    //     Allow(Target::Trait),
955    //     Allow(Target::TraitAlias),
956    //     Allow(Target::Impl { of_trait: true }),
957    //     Allow(Target::Impl { of_trait: false }),
958    //     Allow(Target::AssocConst(AssocCtxt::Impl { of_trait: false })),
959    //     Allow(Target::AssocConst(AssocCtxt::Trait)),
960    //     Allow(Target::AssocConst(AssocCtxt::Impl { of_trait: true })),
961    //     Allow(Target::Method(MethodKind::Inherent)),
962    //     Allow(Target::Method(MethodKind::Trait { body: true })),
963    //     Allow(Target::Method(MethodKind::Trait { body: false })),
964    //     Allow(Target::Method(MethodKind::TraitImpl)),
965    //     Allow(Target::AssocTy(AssocCtxt::Impl { of_trait: false })),
966    //     Allow(Target::AssocTy(AssocCtxt::Trait)),
967    //     Allow(Target::AssocTy(AssocCtxt::Impl { of_trait: true })),
968    //     Allow(Target::ForeignFn),
969    //     Allow(Target::ForeignStatic),
970    //     Allow(Target::ForeignTy),
971    //     Allow(Target::MacroDef),
972    //     Allow(Target::Crate),
973    //     Error(Target::WherePredicate),
974    // ]);
975
976    fn finalize(self, _cx: &FinalizeContext<'_, '_>) -> Option<AttributeKind> {
977        if self.nb_doc_attrs != 0 {
978            Some(AttributeKind::Doc(Box::new(self.attribute)))
979        } else {
980            None
981        }
982    }
983}