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