Skip to content

Commit bf5244a

Browse files
committed
better diagnostics for arguments
2 parents 950ea0b + 665024c commit bf5244a

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
@@ -226,7 +226,7 @@ impl<S: Stage> AttributeParser<S> for NakedParser {
226226
const ATTRIBUTES: AcceptMapping<Self, S> =
227227
&[(&[sym::naked], template!(Word), |this, cx, args| {
228228
if let Err(span) = args.no_args() {
229-
cx.expected_no_args(span);
229+
cx.expected_no_args(&cx.attr_path, span);
230230
return;
231231
}
232232

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
@@ -450,13 +450,13 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> {
450450
})
451451
}
452452

453-
pub(crate) fn expected_no_args(&self, args_span: Span) -> ErrorGuaranteed {
453+
pub(crate) fn expected_no_args(&self, path: &AttrPath, args_span: Span) -> ErrorGuaranteed {
454454
self.emit_err(AttributeParseError {
455455
span: args_span,
456456
attr_span: self.attr_span,
457457
template: self.template.clone(),
458458
attribute: self.attr_path.clone(),
459-
reason: AttributeParseErrorReason::ExpectedNoArgs,
459+
reason: AttributeParseErrorReason::ExpectedNoArgs { path },
460460
attr_style: self.attr_style,
461461
})
462462
}
@@ -511,6 +511,21 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> {
511511
})
512512
}
513513

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

compiler/rustc_attr_parsing/src/session_diagnostics.rs

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

589589
pub(crate) enum AttributeParseErrorReason<'a> {
590-
ExpectedNoArgs,
590+
ExpectedNoArgs {
591+
path: &'a AttrPath,
592+
},
591593
ExpectedStringLiteral {
592594
byte_string: Option<Span>,
593595
},
@@ -605,6 +607,9 @@ pub(crate) enum AttributeParseErrorReason<'a> {
605607
list: bool,
606608
},
607609
ExpectedIdentifier,
610+
ExpectedEnd {
611+
last: Span,
612+
},
608613
}
609614

610615
pub(crate) struct AttributeParseError<'a> {
@@ -616,13 +621,28 @@ pub(crate) struct AttributeParseError<'a> {
616621
pub(crate) reason: AttributeParseErrorReason<'a>,
617622
}
618623

624+
/// based on the attribute's template we add relevant suggestions to the error automatically.
625+
enum DefaultSuggestionStyle {
626+
/// give a hint about the valid forms of the attribute.
627+
/// Useful if there's already a better suggestion given than the automatic ones can provide
628+
/// but we'd still like to show which syntax forms are valid.
629+
Hint,
630+
/// Use the template to suggest changes to the attribute
631+
Suggestion,
632+
/// Don't show any default suggestions
633+
None,
634+
}
635+
619636
impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for AttributeParseError<'_> {
620637
fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, G> {
621638
let name = self.attribute.to_string();
622639

623640
let mut diag = Diag::new(dcx, level, format!("malformed `{name}` attribute input"));
624641
diag.span(self.attr_span);
625642
diag.code(E0539);
643+
644+
let mut show_default_suggestions = DefaultSuggestionStyle::Suggestion;
645+
626646
match self.reason {
627647
AttributeParseErrorReason::ExpectedStringLiteral { byte_string } => {
628648
if let Some(start_point_span) = byte_string {
@@ -634,7 +654,7 @@ impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for AttributeParseError<'_> {
634654
);
635655
diag.note("expected a normal string literal, not a byte string literal");
636656

637-
return diag;
657+
show_default_suggestions = DefaultSuggestionStyle::None;
638658
} else {
639659
diag.span_label(self.span, "expected a string literal here");
640660
}
@@ -660,9 +680,19 @@ impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for AttributeParseError<'_> {
660680
diag.span_label(self.span, "didn't expect a literal here");
661681
diag.code(E0565);
662682
}
663-
AttributeParseErrorReason::ExpectedNoArgs => {
683+
AttributeParseErrorReason::ExpectedNoArgs { path } => {
664684
diag.span_label(self.span, "didn't expect any arguments here");
665685
diag.code(E0565);
686+
687+
if path.span != self.attribute.span {
688+
diag.span_suggestion(
689+
path.span.to(self.span),
690+
"remove this argument",
691+
path,
692+
Applicability::MachineApplicable,
693+
);
694+
show_default_suggestions = DefaultSuggestionStyle::Hint;
695+
}
666696
}
667697
AttributeParseErrorReason::ExpectedNameValue(None) => {
668698
// If the span is the entire attribute, the suggestion we add below this match already contains enough information
@@ -744,23 +774,36 @@ impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for AttributeParseError<'_> {
744774
AttributeParseErrorReason::ExpectedIdentifier => {
745775
diag.span_label(self.span, "expected a valid identifier here");
746776
}
777+
AttributeParseErrorReason::ExpectedEnd { last } => {
778+
diag.span_label(last, "expected no more arguments after this");
779+
diag.span_label(self.span, "remove this argument");
780+
}
747781
}
748782

749783
if let Some(link) = self.template.docs {
750784
diag.note(format!("for more information, visit <{link}>"));
751785
}
786+
752787
let suggestions = self.template.suggestions(self.attr_style, &name);
788+
let text = match show_default_suggestions {
789+
DefaultSuggestionStyle::Hint => {
790+
if suggestions.len() == 1 {
791+
"the only valid form of the attribute is"
792+
} else {
793+
"these are the valid forms of the attribute"
794+
}
795+
}
796+
DefaultSuggestionStyle::Suggestion => {
797+
if suggestions.len() == 1 {
798+
"must be of the form"
799+
} else {
800+
"try changing it to one of the following valid forms of the attribute"
801+
}
802+
}
803+
DefaultSuggestionStyle::None => return diag,
804+
};
753805

754-
diag.span_suggestions(
755-
self.attr_span,
756-
if suggestions.len() == 1 {
757-
"must be of the form"
758-
} else {
759-
"try changing it to one of the following valid forms of the attribute"
760-
},
761-
suggestions,
762-
Applicability::HasPlaceholders,
763-
);
806+
diag.span_suggestions(self.attr_span, text, suggestions, Applicability::HasPlaceholders);
764807

765808
diag
766809
}

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)