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