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