Skip to content

Commit 865eb65

Browse files
committed
better diagnostics for arguments
2 parents 365b5ea + ca8d53a commit 865eb65

19 files changed

+262
-82
lines changed

compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ impl<S: Stage> AttributeParser<S> for NakedParser {
159159
const ATTRIBUTES: AcceptMapping<Self, S> =
160160
&[(&[sym::naked], template!(Word), |this, cx, args| {
161161
if let Err(span) = args.no_args() {
162-
cx.expected_no_args(span);
162+
cx.expected_no_args(&cx.attr_path, span);
163163
return;
164164
}
165165

compiler/rustc_attr_parsing/src/attributes/crate_level.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ impl<S: Stage> CombineAttributeParser<S> for FeatureParser {
214214
continue;
215215
};
216216
if let Err(arg_span) = elem.args().no_args() {
217-
cx.expected_no_args(arg_span);
217+
cx.expected_no_args(&elem.path().get_attribute_path(), arg_span);
218218
continue;
219219
}
220220

compiler/rustc_attr_parsing/src/attributes/macro_attrs.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,10 @@ impl<S: Stage> AttributeParser<S> for MacroUseParser {
8888
continue;
8989
};
9090
if let Err(err_span) = item.args().no_args() {
91-
cx.expected_no_args(err_span);
91+
cx.expected_no_args(
92+
&item.path().get_attribute_path(),
93+
err_span,
94+
);
9295
continue;
9396
}
9497
let Some(item) = item.path().word() else {

compiler/rustc_attr_parsing/src/attributes/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ impl<T: NoArgsAttributeParser<S>, S: Stage> SingleAttributeParser<S> for Without
280280

281281
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
282282
if let Err(span) = args.no_args() {
283-
cx.expected_no_args(span);
283+
cx.expected_no_args(&cx.attr_path, span);
284284
}
285285
Some(T::CREATE(cx.attr_span))
286286
}

compiler/rustc_attr_parsing/src/attributes/proc_macro_attrs.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,13 +88,19 @@ fn parse_derive_like<S: Stage>(
8888
return None;
8989
}
9090
if let Err(e) = trait_attr.args().no_args() {
91-
cx.expected_no_args(e);
91+
cx.expected_no_args(&trait_attr.path().get_attribute_path(), e);
9292
return None;
9393
};
9494

95+
// updated if we see `attributes(...)` to keep track of the last
96+
// argument we did accept for the final diagnostic
97+
let mut last = trait_ident.span;
98+
9599
// Parse optional attributes
96100
let mut attributes = ThinVec::new();
97101
if let Some(attrs) = items.next() {
102+
last = attrs.span();
103+
98104
let Some(attr_list) = attrs.meta_item() else {
99105
cx.expected_list(attrs.span());
100106
return None;
@@ -115,7 +121,7 @@ fn parse_derive_like<S: Stage>(
115121
return None;
116122
};
117123
if let Err(e) = attr.args().no_args() {
118-
cx.expected_no_args(e);
124+
cx.expected_no_args(&attr.path().get_attribute_path(), e);
119125
return None;
120126
};
121127
let Some(ident) = attr.path().word() else {
@@ -132,7 +138,7 @@ fn parse_derive_like<S: Stage>(
132138

133139
// If anything else is specified, we should reject it
134140
if let Some(next) = items.next() {
135-
cx.expected_no_args(next.span());
141+
cx.expected_end_of_list(last, next.span());
136142
}
137143

138144
Some((Some(trait_ident.name), attributes))

compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ impl<S: Stage> SingleAttributeParser<S> for RustcObjectLifetimeDefaultParser {
4242

4343
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
4444
if let Err(span) = args.no_args() {
45-
cx.expected_no_args(span);
45+
cx.expected_no_args(&cx.attr_path, span);
4646
return None;
4747
}
4848

compiler/rustc_attr_parsing/src/attributes/traits.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ impl<S: Stage> SingleAttributeParser<S> for SkipDuringMethodDispatchParser {
3535
continue;
3636
};
3737
if let Err(span) = arg.args().no_args() {
38-
cx.expected_no_args(span);
38+
cx.expected_no_args(&arg.path().get_attribute_path(), span);
3939
}
4040
let path = arg.path();
4141
let (key, skip): (Symbol, &mut bool) = match path.word_sym() {

compiler/rustc_attr_parsing/src/context.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -448,13 +448,13 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> {
448448
})
449449
}
450450

451-
pub(crate) fn expected_no_args(&self, args_span: Span) -> ErrorGuaranteed {
451+
pub(crate) fn expected_no_args(&self, path: &AttrPath, args_span: Span) -> ErrorGuaranteed {
452452
self.emit_err(AttributeParseError {
453453
span: args_span,
454454
attr_span: self.attr_span,
455455
template: self.template.clone(),
456456
attribute: self.attr_path.clone(),
457-
reason: AttributeParseErrorReason::ExpectedNoArgs,
457+
reason: AttributeParseErrorReason::ExpectedNoArgs { path },
458458
attr_style: self.attr_style,
459459
})
460460
}
@@ -509,6 +509,21 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> {
509509
})
510510
}
511511

512+
/// Expected the end of an argument list.
513+
///
514+
/// Note: only useful when arguments in an attribute are ordered and we've seen the last one we expected.
515+
/// Most attributes shouldn't care about their argument order.
516+
pub(crate) fn expected_end_of_list(&self, last_item_span: Span, span: Span) -> ErrorGuaranteed {
517+
self.emit_err(AttributeParseError {
518+
span,
519+
attr_span: self.attr_span,
520+
template: self.template.clone(),
521+
attribute: self.attr_path.clone(),
522+
reason: AttributeParseErrorReason::ExpectedEnd { last: last_item_span },
523+
attr_style: self.attr_style,
524+
})
525+
}
526+
512527
pub(crate) fn expected_single_argument(&self, span: Span) -> ErrorGuaranteed {
513528
self.emit_err(AttributeParseError {
514529
span,

compiler/rustc_attr_parsing/src/session_diagnostics.rs

Lines changed: 56 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -559,7 +559,9 @@ pub(crate) struct LinkOrdinalOutOfRange {
559559
}
560560

561561
pub(crate) enum AttributeParseErrorReason<'a> {
562-
ExpectedNoArgs,
562+
ExpectedNoArgs {
563+
path: &'a AttrPath,
564+
},
563565
ExpectedStringLiteral {
564566
byte_string: Option<Span>,
565567
},
@@ -577,6 +579,9 @@ pub(crate) enum AttributeParseErrorReason<'a> {
577579
list: bool,
578580
},
579581
ExpectedIdentifier,
582+
ExpectedEnd {
583+
last: Span,
584+
},
580585
}
581586

582587
pub(crate) struct AttributeParseError<'a> {
@@ -588,13 +593,28 @@ pub(crate) struct AttributeParseError<'a> {
588593
pub(crate) reason: AttributeParseErrorReason<'a>,
589594
}
590595

596+
/// based on the attribute's template we add relevant suggestions to the error automatically.
597+
enum DefaultSuggestionStyle {
598+
/// give a hint about the valid forms of the attribute.
599+
/// Useful if there's already a better suggestion given than the automatic ones can provide
600+
/// but we'd still like to show which syntax forms are valid.
601+
Hint,
602+
/// Use the template to suggest changes to the attribute
603+
Suggestion,
604+
/// Don't show any default suggestions
605+
None,
606+
}
607+
591608
impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for AttributeParseError<'_> {
592609
fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, G> {
593610
let name = self.attribute.to_string();
594611

595612
let mut diag = Diag::new(dcx, level, format!("malformed `{name}` attribute input"));
596613
diag.span(self.attr_span);
597614
diag.code(E0539);
615+
616+
let mut show_default_suggestions = DefaultSuggestionStyle::Suggestion;
617+
598618
match self.reason {
599619
AttributeParseErrorReason::ExpectedStringLiteral { byte_string } => {
600620
if let Some(start_point_span) = byte_string {
@@ -606,7 +626,7 @@ impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for AttributeParseError<'_> {
606626
);
607627
diag.note("expected a normal string literal, not a byte string literal");
608628

609-
return diag;
629+
show_default_suggestions = DefaultSuggestionStyle::None;
610630
} else {
611631
diag.span_label(self.span, "expected a string literal here");
612632
}
@@ -632,9 +652,19 @@ impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for AttributeParseError<'_> {
632652
diag.span_label(self.span, "didn't expect a literal here");
633653
diag.code(E0565);
634654
}
635-
AttributeParseErrorReason::ExpectedNoArgs => {
655+
AttributeParseErrorReason::ExpectedNoArgs { path } => {
636656
diag.span_label(self.span, "didn't expect any arguments here");
637657
diag.code(E0565);
658+
659+
if path.span != self.attribute.span {
660+
diag.span_suggestion(
661+
path.span.to(self.span),
662+
"remove this argument",
663+
path,
664+
Applicability::MachineApplicable,
665+
);
666+
show_default_suggestions = DefaultSuggestionStyle::Hint;
667+
}
638668
}
639669
AttributeParseErrorReason::ExpectedNameValue(None) => {
640670
// If the span is the entire attribute, the suggestion we add below this match already contains enough information
@@ -716,23 +746,36 @@ impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for AttributeParseError<'_> {
716746
AttributeParseErrorReason::ExpectedIdentifier => {
717747
diag.span_label(self.span, "expected a valid identifier here");
718748
}
749+
AttributeParseErrorReason::ExpectedEnd { last } => {
750+
diag.span_label(last, "expected no more arguments after this");
751+
diag.span_label(self.span, "remove this argument");
752+
}
719753
}
720754

721755
if let Some(link) = self.template.docs {
722756
diag.note(format!("for more information, visit <{link}>"));
723757
}
758+
724759
let suggestions = self.template.suggestions(self.attr_style, &name);
760+
let text = match show_default_suggestions {
761+
DefaultSuggestionStyle::Hint => {
762+
if suggestions.len() == 1 {
763+
"the only valid form of the attribute is"
764+
} else {
765+
"these are the valid forms of the attribute"
766+
}
767+
}
768+
DefaultSuggestionStyle::Suggestion => {
769+
if suggestions.len() == 1 {
770+
"must be of the form"
771+
} else {
772+
"try changing it to one of the following valid forms of the attribute"
773+
}
774+
}
775+
DefaultSuggestionStyle::None => return diag,
776+
};
725777

726-
diag.span_suggestions(
727-
self.attr_span,
728-
if suggestions.len() == 1 {
729-
"must be of the form"
730-
} else {
731-
"try changing it to one of the following valid forms of the attribute"
732-
},
733-
suggestions,
734-
Applicability::HasPlaceholders,
735-
);
778+
diag.span_suggestions(self.attr_span, text, suggestions, Applicability::HasPlaceholders);
736779

737780
diag
738781
}

tests/ui/attributes/invalid-macro-use.stderr

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,12 @@ LL | #[macro_use(a = "b")]
4343
| didn't expect any arguments here
4444
|
4545
= note: for more information, visit <https://doc.rust-lang.org/reference/macros-by-example.html#the-macro_use-attribute>
46-
help: try changing it to one of the following valid forms of the attribute
46+
help: remove this argument
47+
|
48+
LL - #[macro_use(a = "b")]
49+
LL + #[macro_use(a)]
50+
|
51+
help: these are the valid forms of the attribute
4752
|
4853
LL - #[macro_use(a = "b")]
4954
LL + #[macro_use(name1, name2, ...)]
@@ -61,7 +66,12 @@ LL | #[macro_use(a(b))]
6166
| didn't expect any arguments here
6267
|
6368
= note: for more information, visit <https://doc.rust-lang.org/reference/macros-by-example.html#the-macro_use-attribute>
64-
help: try changing it to one of the following valid forms of the attribute
69+
help: remove this argument
70+
|
71+
LL - #[macro_use(a(b))]
72+
LL + #[macro_use(a)]
73+
|
74+
help: these are the valid forms of the attribute
6575
|
6676
LL - #[macro_use(a(b))]
6777
LL + #[macro_use(name1, name2, ...)]

0 commit comments

Comments
 (0)