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