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