rustc_attr_parsing/attributes/
doc.rs

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