Skip to main content

rustc_attr_parsing/attributes/
doc.rs

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