1use rustc_ast::ast::{AttrStyle, LitKind, MetaItemLit};
2use rustc_attr_ir::target::{AstTarget, ConstTypeAstTarget, Target};
3use rustc_attr_ir::{
4 AttributeKind, CfgEntry, CfgHideShow, DocAttribute, DocCfgHideShow, DocCfgHideShowValue,
5 DocInline, HideOrShow, NotableTraitColor,
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, kw, 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, DocAttrNotParameters, DocAttrNotTraitLevel, DocAttributeNotAttribute,
19 DocAutoCfgExpectsHideOrShow, DocAutoCfgHideShowExpectsList,
20 DocAutoCfgHideShowNoIdentBeforeValues, DocAutoCfgHideShowUnexpectedItem,
21 DocAutoCfgHideShowUnexpectedItemAfterValues, DocAutoCfgHideShowValuesMix,
22 DocAutoCfgWrongLiteral, DocInlineConflict, DocInlineOnlyUse, DocKeywordAttributeNotAnonConst,
23 DocKeywordNotKeyword, DocTestLiteral, DocTestTakesList, DocTestUnknown, DocUnknownAny,
24 DocUnknownInclude, DocUnknownPasses, DocUnknownPlugins, DocUnknownSpotlight,
25 DuplicateNotableTraitColor, ExpectedNameValue, ExpectedNoArgs, IllFormedAttributeInput,
26 InvalidNotableTraitAttr, InvalidNotableTraitColor, MalformedDoc, UnusedDuplicate,
27};
28use crate::parser::{
29 ArgParser, MetaItemListParser, MetaItemOrLitParser, MetaItemParser, OwnedPathParser,
30};
31use crate::target_checking::Policy::{Allow, AllowSilent};
32
33fn check_keyword(cx: &mut AcceptContext<'_, '_>, keyword: Symbol, span: Span) -> bool {
34 if keyword.is_reserved(|| edition::LATEST_STABLE_EDITION)
38 || keyword.is_weak()
39 || keyword == sym::SelfTy
40 {
41 return true;
42 }
43 cx.emit_err(DocKeywordNotKeyword { span, keyword });
44 false
45}
46
47fn check_attribute(cx: &mut AcceptContext<'_, '_>, attribute: Symbol, span: Span) -> bool {
48 if rustc_feature::BUILTIN_ATTRIBUTE_SET.contains(&attribute) {
50 return true;
51 }
52 cx.emit_err(DocAttributeNotAttribute { span, attribute });
53 false
54}
55
56fn check_attr_not_crate_level(
58 cx: &mut AcceptContext<'_, '_>,
59 span: Span,
60 attr_name: Symbol,
61) -> bool {
62 if cx.shared.target == Target::Crate {
63 cx.emit_err(DocAttrNotCrateLevel { span, attr_name });
64 return false;
65 }
66 true
67}
68
69fn expected_name_value(cx: &mut AcceptContext<'_, '_>, span: Span, _name: Option<Symbol>) {
71 cx.emit_lint(INVALID_DOC_ATTRIBUTES, ExpectedNameValue, span);
72}
73
74fn expected_no_args(cx: &mut AcceptContext<'_, '_>, span: Span) {
76 cx.emit_lint(INVALID_DOC_ATTRIBUTES, ExpectedNoArgs, span);
77}
78
79fn expected_string_literal(
82 cx: &mut AcceptContext<'_, '_>,
83 span: Span,
84 _actual_literal: Option<&MetaItemLit>,
85) {
86 cx.emit_lint(INVALID_DOC_ATTRIBUTES, MalformedDoc, span);
87}
88
89fn parse_notable_trait(
90 cx: &mut AcceptContext<'_, '_>,
91 path: &OwnedPathParser,
92 args: &ArgParser,
93 attr_value: &mut Option<(Option<(NotableTraitColor, Span)>, Span)>,
94 attr_name: Symbol,
95) {
96 let span = path.span();
97
98 let notable_trait_color_attr = match args {
99 ArgParser::NoArgs => None,
100 ArgParser::List(meta_item_list_parser) => {
101 if meta_item_list_parser.is_empty() {
102 None
103 } else if let Some(meta_item) = meta_item_list_parser.as_single() {
104 Some(meta_item)
105 } else {
106 let color_spans: Option<Vec<_>> = meta_item_list_parser
107 .mixed()
108 .map(|item| {
109 let meta = item.meta_item()?;
110 meta.path().word_is(sym::color).then(|| meta.path().span())
111 })
112 .collect();
113
114 if let Some(color_spans) = color_spans {
115 let first_span = color_spans[0];
116 let duplicate_spans = &color_spans[1..];
117
118 let mut spans = MultiSpan::from_spans(duplicate_spans.to_vec());
119 spans.push_span_label(first_span, "");
120
121 for &duplicate_span in duplicate_spans {
122 spans.push_span_label(duplicate_span, rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed("multiple colors specified"))msg!("multiple colors specified"));
123 }
124
125 cx.emit_lint(INVALID_DOC_ATTRIBUTES, DuplicateNotableTraitColor, spans);
126 } else {
127 let spans = meta_item_list_parser
128 .mixed()
129 .map(|item| match item.meta_item() {
130 Some(meta) => meta.path().span(),
131 None => item.span(),
132 })
133 .collect();
134
135 cx.emit_lint(
136 INVALID_DOC_ATTRIBUTES,
137 InvalidNotableTraitAttr,
138 MultiSpan::from_spans(spans),
139 );
140 }
141
142 return;
143 }
144 }
145 ArgParser::NameValue(_) => {
146 cx.emit_lint(INVALID_DOC_ATTRIBUTES, InvalidNotableTraitAttr, span);
149 return;
150 }
151 };
152
153 let notable_trait_color_and_span = if let Some(notable_trait_color_attr) =
154 notable_trait_color_attr
155 {
156 let Some(notable_trait_color_attr) = notable_trait_color_attr.meta_item() else {
157 cx.emit_lint(INVALID_DOC_ATTRIBUTES, InvalidNotableTraitAttr, span);
158 return;
159 };
160 if !notable_trait_color_attr.path().word_is(sym::color) {
161 cx.emit_lint(INVALID_DOC_ATTRIBUTES, InvalidNotableTraitAttr, span);
162 return;
163 }
164 let Some(notable_trait_color) = notable_trait_color_attr.args().as_name_value() else {
165 cx.emit_lint(
166 INVALID_DOC_ATTRIBUTES,
167 InvalidNotableTraitAttr,
168 notable_trait_color_attr.path().span(),
169 );
170 return;
171 };
172
173 let color_value_span = notable_trait_color.value_span;
174
175 let Some(notable_trait_color) = notable_trait_color.value_as_str() else {
176 cx.emit_lint(INVALID_DOC_ATTRIBUTES, InvalidNotableTraitAttr, span);
177 return;
178 };
179
180 let notable_trait_color = match notable_trait_color.as_str() {
181 "grey" | "gray" => NotableTraitColor::Grey,
182 "red" => NotableTraitColor::Red,
183 "green" => NotableTraitColor::Green,
184 "yellow" => NotableTraitColor::Yellow,
185 "blue" => NotableTraitColor::Blue,
186 "magenta" => NotableTraitColor::Magenta,
187 "cyan" => NotableTraitColor::Cyan,
188 "transparent" => NotableTraitColor::Transparent,
189 _ => {
190 cx.emit_lint(INVALID_DOC_ATTRIBUTES, InvalidNotableTraitColor, color_value_span);
191 return;
192 }
193 };
194 Some((notable_trait_color, notable_trait_color_attr.span()))
195 } else {
196 None
197 };
198
199 if cx.shared.target != Target::Trait {
200 cx.emit_lint(INVALID_DOC_ATTRIBUTES, DocAttrNotTraitLevel { span, attr_name }, span);
201 return;
202 }
203
204 *attr_value = Some((notable_trait_color_and_span, span));
205}
206
207fn parse_keyword_and_attribute(
208 cx: &mut AcceptContext<'_, '_>,
209 path: &OwnedPathParser,
210 args: &ArgParser,
211 attr_value: &mut Option<(Symbol, Span)>,
212 attr_name: Symbol,
213) {
214 let Some(nv) = args.as_name_value() else {
215 expected_name_value(cx, args.span().unwrap_or(path.span()), path.word_sym());
216 return;
217 };
218
219 let Some(value) = nv.value_as_str() else {
220 expected_string_literal(cx, nv.value_span, Some(nv.value_as_lit()));
221 return;
222 };
223
224 let ret = if attr_name == sym::keyword {
225 check_keyword(cx, value, nv.value_span)
226 } else {
227 check_attribute(cx, value, nv.value_span)
228 };
229 if !ret {
230 return;
231 }
232
233 let span = path.span();
234 if attr_value.is_some() {
235 cx.adcx().duplicate_key(span, path.word_sym().unwrap());
236 return;
237 }
238
239 if !check_attr_not_crate_level(cx, span, attr_name) {
240 return;
241 }
242
243 if !#[allow(non_exhaustive_omitted_patterns)] match cx.ast_target {
AstTarget::Const(ConstTypeAstTarget::ConstItem(item)) if
item.ident.name == kw::Underscore => true,
_ => false,
}matches!(
244 cx.ast_target,
245 AstTarget::Const(ConstTypeAstTarget::ConstItem(item))
246 if item.ident.name == kw::Underscore)
247 {
248 cx.emit_err(DocKeywordAttributeNotAnonConst { span, attr_name });
249 return;
250 }
251
252 *attr_value = Some((value, span));
253}
254
255#[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 nb_doc_attrs", &self.attribute,
&&self.nb_doc_attrs)
}
}Debug)]
256pub(crate) struct DocParser {
257 attribute: DocAttribute,
258 nb_doc_attrs: usize,
259}
260
261impl DocParser {
262 fn parse_single_test_doc_attr_item(
263 &mut self,
264 cx: &mut AcceptContext<'_, '_>,
265 mip: &MetaItemParser,
266 ) {
267 let path = mip.path();
268 let args = mip.args();
269
270 match path.word_sym() {
271 Some(sym::no_crate_inject) => {
272 if let Err(span) = args.as_no_args() {
273 expected_no_args(cx, span);
274 return;
275 }
276
277 if let Some(used_span) = self.attribute.no_crate_inject {
278 let unused_span = path.span();
279 cx.emit_lint(
280 INVALID_DOC_ATTRIBUTES,
281 UnusedDuplicate { this: unused_span, other: used_span, warning: true },
282 unused_span,
283 );
284 return;
285 }
286
287 cx.check_target(
288 sym::no_crate_inject.as_str(),
289 &AllowedTargets::AllowListWarnRest(&[
290 Allow(Target::Crate),
291 AllowSilent(Target::Param),
292 ]),
293 );
294
295 self.attribute.no_crate_inject = Some(path.span())
296 }
297 Some(sym::attr) => {
298 let Some(list) = args.as_list() else {
299 let span = cx.attr_span;
302 cx.emit_lint(INVALID_DOC_ATTRIBUTES, MalformedDoc, span);
303 return;
304 };
305 for attr in list.mixed() {
308 attr.ignore_args();
310 self.attribute.test_attrs.push(attr.span());
311 }
312 }
313 Some(name) => {
314 cx.emit_lint(INVALID_DOC_ATTRIBUTES, DocTestUnknown { name }, path.span());
315 }
316 None => {
317 cx.emit_lint(INVALID_DOC_ATTRIBUTES, DocTestLiteral, path.span());
318 }
319 }
320 }
321
322 fn add_alias(&mut self, cx: &mut AcceptContext<'_, '_>, alias: Symbol, span: Span) {
323 let attr_str = "`#[doc(alias = \"...\")]`";
324 if alias == sym::empty {
325 cx.emit_err(DocAliasEmpty { span, attr_str });
326 return;
327 }
328
329 let alias_str = alias.as_str();
330 if let Some(c) =
331 alias_str.chars().find(|&c| c == '"' || c == '\'' || (c.is_whitespace() && c != ' '))
332 {
333 cx.emit_err(DocAliasBadChar { span, attr_str, char_: c });
334 return;
335 }
336 if alias_str.starts_with(' ') || alias_str.ends_with(' ') {
337 cx.emit_err(DocAliasStartEnd { span, attr_str });
338 return;
339 }
340 if !check_attr_not_crate_level(cx, span, sym::alias) {
341 return;
342 }
343
344 if let Some(first_definition) = self.attribute.aliases.get(&alias).copied() {
345 cx.emit_lint(UNUSED_ATTRIBUTES, DocAliasDuplicated { first_definition }, span);
346 }
347
348 self.attribute.aliases.insert(alias, span);
349 }
350
351 fn parse_alias(
352 &mut self,
353 cx: &mut AcceptContext<'_, '_>,
354 path: &OwnedPathParser,
355 args: &ArgParser,
356 ) {
357 match args {
358 ArgParser::NoArgs => {
359 cx.emit_err(DocAliasMalformed { span: args.span().unwrap_or(path.span()) });
360 }
361 ArgParser::List(list) => {
362 for i in list.mixed() {
363 let Some(alias) = cx.expect_string_literal(i) else {
364 continue;
365 };
366
367 self.add_alias(cx, alias, i.span());
368 }
369 }
370 ArgParser::NameValue(nv) => {
371 let Some(alias) = cx.expect_string_literal(nv) else {
372 return;
373 };
374 self.add_alias(cx, alias, nv.value_span);
375 }
376 }
377 }
378
379 fn parse_inline(
380 &mut self,
381 cx: &mut AcceptContext<'_, '_>,
382 path: &OwnedPathParser,
383 args: &ArgParser,
384 inline: DocInline,
385 ) {
386 if let Err(span) = args.as_no_args() {
387 expected_no_args(cx, span);
388 return;
389 }
390
391 let span = path.span();
392 if let Some((previous_inline, previous_span)) = self.attribute.inline {
393 if previous_inline != inline {
394 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]);
395 spans.push_span_label(previous_span, rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed("this attribute..."))msg!("this attribute..."));
396 spans.push_span_label(span, rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed("{\".\"}..conflicts with this attribute"))msg!("{\".\"}..conflicts with this attribute"));
397 cx.emit_err(DocInlineConflict { spans });
398 }
399 } else {
400 self.attribute.inline = Some((inline, span));
401 }
402
403 match cx.target {
404 Target::Use | Target::ExternCrate | Target::Param => {} _ => {
406 let item_span = cx.target_span;
407 cx.emit_lint(
408 INVALID_DOC_ATTRIBUTES,
409 DocInlineOnlyUse { attr_span: span, item_span },
410 span,
411 );
412 }
413 }
414 }
415
416 fn parse_cfg(&mut self, cx: &mut AcceptContext<'_, '_>, args: &ArgParser) {
417 fn simplify_cfg(cfg_entry: &mut CfgEntry) {
419 match cfg_entry {
420 CfgEntry::All(cfgs, span) if cfgs.is_empty() => {
421 *cfg_entry = CfgEntry::Bool(true, *span)
422 }
423 CfgEntry::Any(cfgs, span) if cfgs.is_empty() => {
424 *cfg_entry = CfgEntry::Bool(false, *span)
425 }
426 CfgEntry::Not(cfg, _) => simplify_cfg(cfg),
427 _ => {}
428 }
429 }
430 if let Some(mut cfg_entry) = super::cfg::parse_cfg(cx, args) {
431 simplify_cfg(&mut cfg_entry);
432 self.attribute.cfg.push(cfg_entry);
433 }
434 }
435
436 fn parse_auto_cfg_values(
438 &self,
439 cx: &mut AcceptContext<'_, '_>,
440 list: &MetaItemListParser,
441 values: &mut Option<DocCfgHideShow>,
442 ) {
443 let mut cfg_values = DocCfgHideShow::new();
444
445 let mut values_set = FxHashSet::default();
446 for item in list.mixed() {
447 match item {
448 MetaItemOrLitParser::Lit(MetaItemLit {
450 kind: LitKind::Str(symbol, _),
451 span,
452 ..
453 }) => match &mut cfg_values {
454 DocCfgHideShow::Any(any_span) => {
455 cx.emit_lint(
456 INVALID_DOC_ATTRIBUTES,
457 DocAutoCfgHideShowValuesMix { value_span: *span },
458 *any_span,
459 );
460 }
461 DocCfgHideShow::List(symbols) => {
462 if values_set.insert(symbol) {
463 symbols.push(DocCfgHideShowValue::new(*symbol, *span));
464 }
465 }
466 },
467 MetaItemOrLitParser::Lit(lit) => cx.emit_lint(
469 INVALID_DOC_ATTRIBUTES,
470 DocAutoCfgHideShowUnexpectedItem { attr_name: lit.symbol },
471 lit.span,
472 ),
473 MetaItemOrLitParser::MetaItemParser(sub_item) => {
476 let Some(ident) = sub_item.ident() else {
477 cx.adcx().expected_identifier(sub_item.path().span());
478 continue;
479 };
480 if [sym::any, sym::none].contains(&ident.name)
481 && let ArgParser::List(list) = sub_item.args()
482 && list.mixed().count() == 0
483 {
484 if ident.name == sym::any {
485 if let DocCfgHideShow::List(values) = &cfg_values
486 && let Some(value) = values.first()
487 {
488 cx.emit_lint(
489 INVALID_DOC_ATTRIBUTES,
490 DocAutoCfgHideShowValuesMix { value_span: value.span },
491 sub_item.span(),
492 );
493 } else {
494 cfg_values.merge_with(&DocCfgHideShow::Any(sub_item.span()));
495 }
496 } else {
497 cfg_values.push_none(sub_item.span());
498 }
499 } else {
500 cx.emit_lint(
501 INVALID_DOC_ATTRIBUTES,
502 DocAutoCfgHideShowUnexpectedItem { attr_name: ident.name },
503 sub_item.span(),
504 );
505 }
506 }
507 }
508 }
509 *values = Some(cfg_values);
510 }
511
512 fn parse_auto_cfg(
513 &mut self,
514 cx: &mut AcceptContext<'_, '_>,
515 path: &OwnedPathParser,
516 args: &ArgParser,
517 ) {
518 match args {
519 ArgParser::NoArgs => {
520 self.attribute.auto_cfg_change.push((true, path.span()));
521 }
522 ArgParser::List(list) => {
523 'main: for meta in list.mixed() {
524 let MetaItemOrLitParser::MetaItemParser(item) = meta else {
525 cx.emit_lint(
526 INVALID_DOC_ATTRIBUTES,
527 DocAutoCfgExpectsHideOrShow,
528 meta.span(),
529 );
530 continue;
531 };
532 let (kind, attr_name) = match item.path().word_sym() {
535 Some(sym::hide) => (HideOrShow::Hide, sym::hide),
536 Some(sym::show) => (HideOrShow::Show, sym::show),
537 _ => {
538 cx.emit_lint(
539 INVALID_DOC_ATTRIBUTES,
540 DocAutoCfgExpectsHideOrShow,
541 item.span(),
542 );
543 continue;
544 }
545 };
546 let ArgParser::List(list) = item.args() else {
547 cx.emit_lint(
548 INVALID_DOC_ATTRIBUTES,
549 DocAutoCfgHideShowExpectsList { attr_name },
550 item.span(),
551 );
552 continue;
553 };
554
555 let mut cfg_hide_show = CfgHideShow { kind, values: FxIndexMap::default() };
556
557 let mut cfg_names = FxHashSet::default();
558 let mut values = None;
559 for item in list.mixed() {
560 let MetaItemOrLitParser::MetaItemParser(sub_item) = item else {
561 cx.emit_lint(
562 INVALID_DOC_ATTRIBUTES,
563 DocAutoCfgHideShowUnexpectedItem { attr_name },
564 item.span(),
565 );
566 continue 'main;
567 };
568 match sub_item.args() {
569 ArgParser::NoArgs if values.is_none() => {
570 let Some(name) = sub_item.path().word_sym() else {
571 cx.adcx().expected_identifier(sub_item.path().span());
572 continue 'main;
573 };
574 cfg_names.insert(name);
575 }
576 ArgParser::List(list) if values.is_none() => {
578 let Some(sym::values) = sub_item.path().word_sym() else {
579 cx.adcx().expected_identifier(sub_item.path().span());
580 continue 'main;
581 };
582 if cfg_names.is_empty() {
583 cx.emit_lint(
584 INVALID_DOC_ATTRIBUTES,
585 DocAutoCfgHideShowNoIdentBeforeValues,
586 sub_item.span(),
587 );
588 continue 'main;
589 }
590 self.parse_auto_cfg_values(cx, list, &mut values);
591 }
592 ArgParser::NameValue(_) => {
594 cx.emit_lint(
595 INVALID_DOC_ATTRIBUTES,
596 DocAutoCfgHideShowUnexpectedItem { attr_name },
597 sub_item.span(),
598 );
599 }
600 _ => {
602 cx.emit_lint(
603 INVALID_DOC_ATTRIBUTES,
604 DocAutoCfgHideShowUnexpectedItemAfterValues,
605 sub_item.span(),
606 );
607 }
608 }
609 }
610
611 let values = values.unwrap_or(DocCfgHideShow::new_with_only_key(item.span()));
612 #[allow(rustc::potential_query_instability)]
613 for cfg_name in &cfg_names {
614 match cfg_hide_show.values.entry(*cfg_name) {
615 IndexEntry::Vacant(v) => {
616 v.insert(values.clone());
617 }
618 IndexEntry::Occupied(mut o) => {
619 o.get_mut().merge_with(&values);
620 }
621 }
622 }
623 self.attribute.auto_cfg.push((cfg_hide_show, path.span()));
624 }
625 }
626 ArgParser::NameValue(nv) => {
627 let MetaItemLit { kind: LitKind::Bool(bool_value), span, .. } = nv.value_as_lit()
628 else {
629 cx.emit_lint(INVALID_DOC_ATTRIBUTES, DocAutoCfgWrongLiteral, nv.value_span);
630 return;
631 };
632 self.attribute.auto_cfg_change.push((*bool_value, *span));
633 }
634 }
635 }
636
637 fn parse_single_doc_attr_item(&mut self, cx: &mut AcceptContext<'_, '_>, mip: &MetaItemParser) {
638 let path = mip.path();
639 let args = mip.args();
640
641 macro_rules! no_args {
642 ($ident: ident) => {{
643 if let Err(span) = args.as_no_args() {
644 expected_no_args(cx, span);
645 return;
646 }
647
648 self.attribute.$ident = Some(path.span());
658 }};
659 }
660 macro_rules! no_args_and_not_crate_level {
661 ($ident: ident) => {{
662 if let Err(span) = args.as_no_args() {
663 expected_no_args(cx, span);
664 return;
665 }
666 let span = path.span();
667 if !check_attr_not_crate_level(cx, span, sym::$ident) {
668 return;
669 }
670 self.attribute.$ident = Some(span);
671 }};
672 }
673 macro_rules! no_args_and_crate_level {
674 ($ident: ident) => {{
675 if let Err(span) = args.as_no_args() {
676 expected_no_args(cx, span);
677 return;
678 }
679 let span = path.span();
680 cx.check_target(
681 concat!("(", stringify!($ident), ")"),
682 &AllowedTargets::AllowListWarnRest(&[
683 Allow(Target::Crate),
684 AllowSilent(Target::Param), ]),
686 );
687 self.attribute.$ident = Some(span);
688 }};
689 }
690 macro_rules! string_arg_and_crate_level {
691 ($ident: ident) => {{
692 let Some(nv) = args.as_name_value() else {
693 expected_name_value(cx, args.span().unwrap_or(path.span()), path.word_sym());
694 return;
695 };
696
697 let Some(s) = nv.value_as_str() else {
698 expected_string_literal(cx, nv.value_span, Some(nv.value_as_lit()));
699 return;
700 };
701
702 cx.check_target(
703 s.as_str(),
704 &AllowedTargets::AllowListWarnRest(&[
705 Allow(Target::Crate),
706 AllowSilent(Target::Param), ]),
708 );
709
710 self.attribute.$ident = Some((s, path.span()));
720 }};
721 }
722 macro_rules! gated {
723 ($feature:ident $(,$notes:expr)*) => {
724 let stability = $crate::unstable!($feature $(, $notes)*);
725 cx.shared.cx.check_attribute_stability(&cx.attr_path, path.span(), stability);
726 };
727 }
728
729 match path.word_sym() {
730 Some(sym::alias) => self.parse_alias(cx, path, args),
731 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),
732 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),
AllowSilent(Target::Param)]));
self.attribute.html_favicon_url = Some((s, path.span()));
}string_arg_and_crate_level!(html_favicon_url),
733 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),
AllowSilent(Target::Param)]));
self.attribute.html_logo_url = Some((s, path.span()));
}string_arg_and_crate_level!(html_logo_url),
734 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),
AllowSilent(Target::Param)]));
self.attribute.html_no_source = Some(span);
}no_args_and_crate_level!(html_no_source),
735 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),
AllowSilent(Target::Param)]));
self.attribute.html_playground_url = Some((s, path.span()));
}string_arg_and_crate_level!(html_playground_url),
736 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),
AllowSilent(Target::Param)]));
self.attribute.html_root_url = Some((s, path.span()));
}string_arg_and_crate_level!(html_root_url),
737 Some(sym::issue_tracker_base_url) => {
738 {
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),
AllowSilent(Target::Param)]));
self.attribute.issue_tracker_base_url = Some((s, path.span()));
}string_arg_and_crate_level!(issue_tracker_base_url)
739 }
740 Some(sym::inline) => self.parse_inline(cx, path, args, DocInline::Inline),
741 Some(sym::no_inline) => self.parse_inline(cx, path, args, DocInline::NoInline),
742 Some(sym::masked) => {
743 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);
744 {
if let Err(span) = args.as_no_args() {
expected_no_args(cx, span);
return;
}
self.attribute.masked = Some(path.span());
}no_args!(masked)
745 }
746 Some(sym::cfg) => {
747 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);
748 self.parse_cfg(cx, args)
749 }
750 Some(sym::notable_trait) => {
751 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);
752 parse_notable_trait(
753 cx,
754 path,
755 args,
756 &mut self.attribute.notable_trait,
757 sym::notable_trait,
758 )
759 }
760 Some(sym::keyword) => {
761 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);
762 parse_keyword_and_attribute(
763 cx,
764 path,
765 args,
766 &mut self.attribute.keyword,
767 sym::keyword,
768 )
769 }
770 Some(sym::attribute) => {
771 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);
772 parse_keyword_and_attribute(
773 cx,
774 path,
775 args,
776 &mut self.attribute.attribute,
777 sym::attribute,
778 )
779 }
780 Some(sym::fake_variadic) => {
781 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);
782 {
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)
783 }
784 Some(sym::search_unbox) => {
785 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);
786 {
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)
787 }
788 Some(sym::rust_logo) => {
789 if cx.target == Target::Crate {
791 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!(
792 rustdoc_internals,
793 "the `#[doc(rust_logo)]` attribute is used for Rust branding"
794 );
795 }
796 {
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),
AllowSilent(Target::Param)]));
self.attribute.rust_logo = Some(span);
}no_args_and_crate_level!(rust_logo)
797 }
798 Some(sym::auto_cfg) => {
799 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);
800 self.parse_auto_cfg(cx, path, args)
801 }
802 Some(sym::test) => {
803 let Some(list) = args.as_list() else {
804 cx.emit_lint(
805 INVALID_DOC_ATTRIBUTES,
806 DocTestTakesList,
807 args.span().unwrap_or(path.span()),
808 );
809 return;
810 };
811
812 for i in list.mixed() {
813 match i {
814 MetaItemOrLitParser::MetaItemParser(mip) => {
815 self.parse_single_test_doc_attr_item(cx, mip);
816 }
817 MetaItemOrLitParser::Lit(lit) => {
818 cx.emit_lint(INVALID_DOC_ATTRIBUTES, MalformedDoc, lit.span);
822 }
823 }
824 }
825 }
826 Some(sym::spotlight) => {
827 let span = path.span();
828 cx.emit_lint(INVALID_DOC_ATTRIBUTES, DocUnknownSpotlight { sugg_span: span }, span);
829 }
830 Some(sym::include) if let Some(nv) = args.as_name_value() => {
831 let inner = match cx.attr_style {
832 AttrStyle::Outer => "",
833 AttrStyle::Inner => "!",
834 };
835 let value = nv.value_as_lit().symbol;
836 let span = path.span();
837 cx.emit_lint(
838 INVALID_DOC_ATTRIBUTES,
839 DocUnknownInclude { inner, value, sugg: (span, Applicability::MaybeIncorrect) },
840 span,
841 );
842 }
843 Some(name @ (sym::passes | sym::no_default_passes)) => {
844 let span = path.span();
845 cx.emit_lint(
846 INVALID_DOC_ATTRIBUTES,
847 DocUnknownPasses { name, note_span: span },
848 span,
849 );
850 }
851 Some(sym::plugins) => {
852 let span = path.span();
853 cx.emit_lint(INVALID_DOC_ATTRIBUTES, DocUnknownPlugins { label_span: span }, span);
854 }
855 Some(name) => {
856 cx.emit_lint(INVALID_DOC_ATTRIBUTES, DocUnknownAny { name }, path.span());
857 }
858 None => {
859 let full_name =
860 path.segments().map(|s| s.as_str()).intersperse("::").collect::<String>();
861 let name = Symbol::intern(&full_name);
862 cx.emit_lint(INVALID_DOC_ATTRIBUTES, DocUnknownAny { name }, path.span());
863 }
864 }
865 }
866
867 fn accept_single_doc_attr(&mut self, cx: &mut AcceptContext<'_, '_>, args: &ArgParser) {
868 if cx.shared.target == Target::Param {
869 cx.emit_err(DocAttrNotParameters { span: cx.attr_span });
870 }
871 match args {
872 ArgParser::List(items) if !items.is_empty() => {
873 for i in items.mixed() {
874 match i {
875 MetaItemOrLitParser::MetaItemParser(mip) => {
876 if self.nb_doc_attrs == 0 {
877 self.attribute.first_span = cx.attr_span;
878 }
879 self.nb_doc_attrs += 1;
880 self.parse_single_doc_attr_item(cx, mip);
881 }
882 MetaItemOrLitParser::Lit(lit) => {
883 expected_name_value(cx, lit.span, None);
884 }
885 }
886 }
887 }
888 ArgParser::NameValue(nv) => {
889 if nv.value_as_str().is_none() {
890 expected_string_literal(cx, nv.value_span, Some(nv.value_as_lit()));
891 } else {
892 {
::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!(
893 "Should have been handled at the same time as sugar-syntaxed doc comments"
894 );
895 }
896 }
897 _ => {
898 let suggestions = cx.adcx().suggestions();
899 let span = cx.inner_span;
900 cx.emit_lint(
901 INVALID_DOC_ATTRIBUTES,
902 IllFormedAttributeInput::new(&suggestions, None, None),
903 span,
904 );
905 }
906 }
907 }
908}
909
910impl AttributeParser for DocParser {
911 const ATTRIBUTES: AcceptMapping<Self> = &[(
912 &[sym::doc],
913 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!(
914 List: &[
915 "alias",
916 "hidden",
917 "html_favicon_url",
918 "html_logo_url",
919 "html_no_source",
920 "html_playground_url",
921 "html_root_url",
922 "issue_tracker_base_url",
923 "inline",
924 "no_inline",
925 "masked",
926 "cfg",
927 "notable_trait",
928 "auto_cfg",
929 "test",
930 ],
931 NameValueStr: "doc comment"
932 ),
933 AttributeStability::Stable, |this, cx, args| {
935 this.accept_single_doc_attr(cx, args);
936 },
937 )];
938 const ALLOWED_TARGETS: AllowedTargets<'_> = AllowedTargets::AllowList(ALL_TARGETS);
940 fn finalize(self, _cx: &FinalizeContext<'_, '_>) -> Option<AttributeKind> {
977 if self.nb_doc_attrs != 0 {
978 Some(AttributeKind::Doc(Box::new(self.attribute)))
979 } else {
980 None
981 }
982 }
983}