Skip to main content

rustc_attr_parsing/attributes/
doc.rs

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