Skip to main content

rustc_attr_parsing/attributes/
doc.rs

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