diff --git a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs index dcd0116d804d..189d83c42160 100644 --- a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs +++ b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs @@ -1,9 +1,10 @@ #![deny(unused_must_use)] -use proc_macro2::TokenStream; +use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote}; +use syn::parse::ParseStream; use syn::spanned::Spanned; -use syn::{Attribute, Meta, MetaList, Path}; +use syn::{Attribute, Meta, MetaList, Path, Token}; use synstructure::{BindingInfo, Structure, VariantInfo}; use super::utils::SubdiagnosticVariant; @@ -437,23 +438,35 @@ fn generate_field_code_inner_list( let mut code = None; - list.parse_nested_meta(|nested| { - if nested.path.is_ident("code") { - let code_field = new_code_ident(); - let span = nested.path.span().unwrap(); - let formatting_init = build_suggestion_code( - &code_field, - nested, - self, - AllowMultipleAlternatives::No, - ); - code.set_once((code_field, formatting_init), span); - } else { - span_err( - nested.path.span().unwrap(), - "`code` is the only valid nested attribute", - ) - .emit(); + list.parse_args_with(|input: ParseStream<'_>| { + while !input.is_empty() { + let arg_name = input.parse::()?; + match arg_name.to_string().as_str() { + "code" => { + let code_field = new_code_ident(); + let formatting_init = build_suggestion_code( + &code_field, + input, + self, + AllowMultipleAlternatives::No, + )?; + code.set_once( + (code_field, formatting_init), + arg_name.span().unwrap(), + ); + } + _ => { + span_err( + arg_name.span().unwrap(), + "`code` is the only valid nested attribute", + ) + .emit(); + } + } + if input.is_empty() { + break; + } + input.parse::()?; } Ok(()) })?; diff --git a/compiler/rustc_macros/src/diagnostics/utils.rs b/compiler/rustc_macros/src/diagnostics/utils.rs index c310b99d5351..6ca2c90abb90 100644 --- a/compiler/rustc_macros/src/diagnostics/utils.rs +++ b/compiler/rustc_macros/src/diagnostics/utils.rs @@ -6,7 +6,7 @@ use proc_macro::Span; use proc_macro2::{Ident, TokenStream}; use quote::{ToTokens, format_ident, quote}; -use syn::meta::ParseNestedMeta; +use syn::parse::ParseStream; use syn::punctuated::Punctuated; use syn::spanned::Spanned; use syn::{Attribute, Field, LitStr, Meta, Path, Token, Type, TypeTuple, parenthesized}; @@ -428,64 +428,51 @@ pub(super) enum AllowMultipleAlternatives { } fn parse_suggestion_values( - nested: ParseNestedMeta<'_>, + nested: ParseStream<'_>, allow_multiple: AllowMultipleAlternatives, ) -> syn::Result> { - let values = if let Ok(val) = nested.value() { - vec![val.parse()?] - } else { - let content; - parenthesized!(content in nested.input); + if nested.parse::().is_ok() { + return Ok(vec![nested.parse::()?]); + } - if let AllowMultipleAlternatives::No = allow_multiple { + let content; + parenthesized!(content in nested); + if let AllowMultipleAlternatives::No = allow_multiple { + span_err(content.span().unwrap(), "expected exactly one string literal for `code = ...`") + .emit(); + return Ok(vec![]); + } + + let literals = Punctuated::::parse_terminated(&content); + Ok(match literals { + Ok(p) if p.is_empty() => { span_err( - nested.input.span().unwrap(), - "expected exactly one string literal for `code = ...`", + content.span().unwrap(), + "expected at least one string literal for `code(...)`", ) .emit(); vec![] - } else { - let literals = Punctuated::::parse_terminated(&content); - - match literals { - Ok(p) if p.is_empty() => { - span_err( - content.span().unwrap(), - "expected at least one string literal for `code(...)`", - ) - .emit(); - vec![] - } - Ok(p) => p.into_iter().collect(), - Err(_) => { - span_err( - content.span().unwrap(), - "`code(...)` must contain only string literals", - ) - .emit(); - vec![] - } - } } - }; - - Ok(values) + Ok(p) => p.into_iter().collect(), + Err(_) => { + span_err(content.span().unwrap(), "`code(...)` must contain only string literals") + .emit(); + vec![] + } + }) } /// Constructs the `format!()` invocation(s) necessary for a `#[suggestion*(code = "foo")]` or /// `#[suggestion*(code("foo", "bar"))]` attribute field pub(super) fn build_suggestion_code( code_field: &Ident, - nested: ParseNestedMeta<'_>, + nested: ParseStream<'_>, fields: &impl HasFieldMap, allow_multiple: AllowMultipleAlternatives, -) -> TokenStream { - let values = match parse_suggestion_values(nested, allow_multiple) { - Ok(x) => x, - Err(e) => return e.into_compile_error(), - }; +) -> Result { + let values = parse_suggestion_values(nested, allow_multiple)?; - if let AllowMultipleAlternatives::Yes = allow_multiple { + Ok(if let AllowMultipleAlternatives::Yes = allow_multiple { let formatted_strings: Vec<_> = values .into_iter() .map(|value| fields.build_format(&value.value(), value.span())) @@ -497,7 +484,7 @@ pub(super) fn build_suggestion_code( } else { // error handled previously quote! { let #code_field = String::new(); } - } + }) } /// Possible styles for suggestion subdiagnostics. @@ -709,112 +696,95 @@ pub(super) fn from_attr( let mut code = None; let mut suggestion_kind = None; - let mut first = true; let mut slug = None; let mut no_span = false; - list.parse_nested_meta(|nested| { - if nested.input.is_empty() || nested.input.peek(Token![,]) { - if first { - slug = Some(nested.path); - } else if nested.path.is_ident("no_span") { - no_span = true; - } else { - span_err(nested.input.span().unwrap(), "a diagnostic slug must be the first argument to the attribute").emit(); + list.parse_args_with(|input: ParseStream<'_>| { + let mut is_first = true; + while !input.is_empty() { + let arg_name: Path = input.parse::()?; + let arg_name_span = arg_name.span().unwrap(); + if input.is_empty() || input.parse::().is_ok() { + if is_first { + slug = Some(arg_name); + is_first = false; + } else { + span_err(arg_name_span, "a diagnostic slug must be the first argument to the attribute").emit(); + } + continue } + is_first = false; - first = false; - return Ok(()); - } + match (arg_name.require_ident()?.to_string().as_str(), &mut kind) { + // ("no_span", _) => no_span = true, + ("code", SubdiagnosticKind::Suggestion { code_field, .. }) => { + let code_init = build_suggestion_code( + &code_field, + &input, + fields, + AllowMultipleAlternatives::Yes, + )?; + code.set_once(code_init, arg_name_span); + } + ( + "applicability", + SubdiagnosticKind::Suggestion { applicability, .. } + | SubdiagnosticKind::MultipartSuggestion { applicability, .. }, + ) => { + input.parse::()?; + let value = input.parse::()?; + let value = Applicability::from_str(&value.value()).unwrap_or_else(|()| { + span_err(value.span().unwrap(), "invalid applicability").emit(); + Applicability::Unspecified + }); + applicability.set_once(value, span); + } + ( + "style", + SubdiagnosticKind::Suggestion { .. } + | SubdiagnosticKind::MultipartSuggestion { .. }, + ) => { + input.parse::()?; + let value = input.parse::()?; - first = false; + let value = value.value().parse().unwrap_or_else(|()| { + span_err(value.span().unwrap(), "invalid suggestion style") + .help("valid styles are `normal`, `short`, `hidden`, `verbose` and `tool-only`") + .emit(); + SuggestionKind::Normal + }); - let nested_name = nested.path.segments.last().unwrap().ident.to_string(); - let nested_name = nested_name.as_str(); + suggestion_kind.set_once(value, span); + } - let path_span = nested.path.span().unwrap(); - let val_span = nested.input.span().unwrap(); - macro_rules! get_string { - () => {{ - let Ok(value) = nested.value().and_then(|x| x.parse::()) else { - span_err(val_span, "expected `= \"xxx\"`").emit(); - return Ok(()); - }; - value - }}; - } - - let mut has_errors = false; - let input = nested.input; - - match (nested_name, &mut kind) { - ("code", SubdiagnosticKind::Suggestion { code_field, .. }) => { - let code_init = build_suggestion_code( - code_field, - nested, - fields, - AllowMultipleAlternatives::Yes, - ); - code.set_once(code_init, path_span); - } - ( - "applicability", - SubdiagnosticKind::Suggestion { applicability, .. } - | SubdiagnosticKind::MultipartSuggestion { applicability, .. }, - ) => { - let value = get_string!(); - let value = Applicability::from_str(&value.value()).unwrap_or_else(|()| { - span_err(value.span().unwrap(), "invalid applicability").emit(); - has_errors = true; - Applicability::Unspecified - }); - applicability.set_once(value, span); - } - ( - "style", - SubdiagnosticKind::Suggestion { .. } - | SubdiagnosticKind::MultipartSuggestion { .. }, - ) => { - let value = get_string!(); - - let value = value.value().parse().unwrap_or_else(|()| { - span_err(value.span().unwrap(), "invalid suggestion style") - .help("valid styles are `normal`, `short`, `hidden`, `verbose` and `tool-only`") + // Invalid nested attribute + (_, SubdiagnosticKind::Suggestion { .. }) => { + span_err(arg_name_span, "invalid nested attribute") + .help( + "only `no_span`, `style`, `code` and `applicability` are valid nested attributes", + ) .emit(); - has_errors = true; - SuggestionKind::Normal - }); - - suggestion_kind.set_once(value, span); + // Consume the rest of the input to avoid spamming errors + let _ = input.parse::(); + } + (_, SubdiagnosticKind::MultipartSuggestion { .. }) => { + span_err(arg_name_span, "invalid nested attribute") + .help("only `no_span`, `style` and `applicability` are valid nested attributes") + .emit(); + // Consume the rest of the input to avoid spamming errors + let _ = input.parse::(); + } + _ => { + span_err(arg_name_span, "only `no_span` is a valid nested attribute").emit(); + // Consume the rest of the input to avoid spamming errors + let _ = input.parse::(); + } } - // Invalid nested attribute - (_, SubdiagnosticKind::Suggestion { .. }) => { - span_err(path_span, "invalid nested attribute") - .help( - "only `no_span`, `style`, `code` and `applicability` are valid nested attributes", - ) - .emit(); - has_errors = true; - } - (_, SubdiagnosticKind::MultipartSuggestion { .. }) => { - span_err(path_span, "invalid nested attribute") - .help("only `no_span`, `style` and `applicability` are valid nested attributes") - .emit(); - has_errors = true; - } - _ => { - span_err(path_span, "only `no_span` is a valid nested attribute").emit(); - has_errors = true; - } + if input.is_empty() { break } + input.parse::()?; } - - if has_errors { - // Consume the rest of the input to avoid spamming errors - let _ = input.parse::(); - } - Ok(()) })?; diff --git a/tests/ui-fulldeps/session-diagnostic/diagnostic-derive.rs b/tests/ui-fulldeps/session-diagnostic/diagnostic-derive.rs index 506ec7f88a82..72b414362c72 100644 --- a/tests/ui-fulldeps/session-diagnostic/diagnostic-derive.rs +++ b/tests/ui-fulldeps/session-diagnostic/diagnostic-derive.rs @@ -803,7 +803,7 @@ struct SuggestionsInvalidItem { sub: Span, } -#[derive(Diagnostic)] //~ ERROR cannot find value `__code_34` in this scope +#[derive(Diagnostic)] #[diag(no_crate_example)] struct SuggestionsInvalidLiteral { #[suggestion(code = 3)] diff --git a/tests/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr b/tests/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr index 29132b8325f3..3ccf89cedec7 100644 --- a/tests/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr +++ b/tests/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr @@ -277,10 +277,10 @@ LL | #[help(no_crate_help)] | ^ error: derive(Diagnostic): a diagnostic slug must be the first argument to the attribute - --> $DIR/diagnostic-derive.rs:533:32 + --> $DIR/diagnostic-derive.rs:533:29 | LL | #[label(no_crate_label, foo)] - | ^ + | ^^^ error: derive(Diagnostic): only `no_span` is a valid nested attribute --> $DIR/diagnostic-derive.rs:541:29 @@ -606,14 +606,6 @@ error[E0425]: cannot find value `nonsense` in module `crate::fluent_generated` LL | #[diag(nonsense, code = E0123)] | ^^^^^^^^ not found in `crate::fluent_generated` -error[E0425]: cannot find value `__code_34` in this scope - --> $DIR/diagnostic-derive.rs:806:10 - | -LL | #[derive(Diagnostic)] - | ^^^^^^^^^^ not found in this scope - | - = note: this error originates in the derive macro `Diagnostic` (in Nightly builds, run with -Z macro-backtrace for more info) - error[E0277]: the trait bound `Hello: IntoDiagArg` is not satisfied --> $DIR/diagnostic-derive.rs:347:12 | @@ -636,7 +628,7 @@ note: required by a bound in `Diag::<'a, G>::arg` = note: in this macro invocation = note: this error originates in the macro `with_fn` (in Nightly builds, run with -Z macro-backtrace for more info) -error: aborting due to 81 previous errors +error: aborting due to 80 previous errors Some errors have detailed explanations: E0277, E0425. For more information about an error, try `rustc --explain E0277`. diff --git a/tests/ui-fulldeps/session-diagnostic/subdiagnostic-derive.rs b/tests/ui-fulldeps/session-diagnostic/subdiagnostic-derive.rs index 941668ad602e..578fc728de53 100644 --- a/tests/ui-fulldeps/session-diagnostic/subdiagnostic-derive.rs +++ b/tests/ui-fulldeps/session-diagnostic/subdiagnostic-derive.rs @@ -95,7 +95,7 @@ struct G { #[derive(Subdiagnostic)] #[label("...")] -//~^ ERROR unexpected literal in nested attribute, expected ident +//~^ ERROR expected identifier struct H { #[primary_span] span: Span, @@ -775,7 +775,7 @@ struct SuggestionStyleInvalid1 { #[derive(Subdiagnostic)] #[suggestion(no_crate_example, code = "", style = 42)] -//~^ ERROR expected `= "xxx"` +//~^ ERROR expected string literal struct SuggestionStyleInvalid2 { #[primary_span] sub: Span, @@ -791,8 +791,7 @@ struct SuggestionStyleInvalid3 { #[derive(Subdiagnostic)] #[suggestion(no_crate_example, code = "", style("foo"))] -//~^ ERROR expected `= "xxx"` -//~| ERROR expected `,` +//~^ ERROR expected `=` struct SuggestionStyleInvalid4 { #[primary_span] sub: Span, diff --git a/tests/ui-fulldeps/session-diagnostic/subdiagnostic-derive.stderr b/tests/ui-fulldeps/session-diagnostic/subdiagnostic-derive.stderr index c31da4421d25..568d75d838fe 100644 --- a/tests/ui-fulldeps/session-diagnostic/subdiagnostic-derive.stderr +++ b/tests/ui-fulldeps/session-diagnostic/subdiagnostic-derive.stderr @@ -34,7 +34,7 @@ error: derive(Diagnostic): diagnostic slug must be first argument of a `#[label( LL | #[label(bug = "...")] | ^ -error: unexpected literal in nested attribute, expected ident +error: expected identifier --> $DIR/subdiagnostic-derive.rs:97:9 | LL | #[label("...")] @@ -175,10 +175,10 @@ LL | | } | |_^ error: derive(Diagnostic): a diagnostic slug must be the first argument to the attribute - --> $DIR/subdiagnostic-derive.rs:317:44 + --> $DIR/subdiagnostic-derive.rs:317:27 | LL | #[label(no_crate_example, no_crate::example)] - | ^ + | ^^^^^^^^ error: derive(Diagnostic): attribute specified multiple times --> $DIR/subdiagnostic-derive.rs:330:5 @@ -381,10 +381,10 @@ LL | #[applicability] | ^ error: derive(Diagnostic): expected exactly one string literal for `code = ...` - --> $DIR/subdiagnostic-derive.rs:663:34 + --> $DIR/subdiagnostic-derive.rs:663:28 | LL | #[suggestion_part(code("foo"))] - | ^ + | ^^^^^ error: unexpected token, expected `)` --> $DIR/subdiagnostic-derive.rs:663:28 @@ -393,10 +393,10 @@ LL | #[suggestion_part(code("foo"))] | ^^^^^ error: derive(Diagnostic): expected exactly one string literal for `code = ...` - --> $DIR/subdiagnostic-derive.rs:673:41 + --> $DIR/subdiagnostic-derive.rs:673:28 | LL | #[suggestion_part(code("foo", "bar"))] - | ^ + | ^^^^^ error: unexpected token, expected `)` --> $DIR/subdiagnostic-derive.rs:673:28 @@ -405,10 +405,10 @@ LL | #[suggestion_part(code("foo", "bar"))] | ^^^^^ error: derive(Diagnostic): expected exactly one string literal for `code = ...` - --> $DIR/subdiagnostic-derive.rs:683:30 + --> $DIR/subdiagnostic-derive.rs:683:28 | LL | #[suggestion_part(code(3))] - | ^ + | ^ error: unexpected token, expected `)` --> $DIR/subdiagnostic-derive.rs:683:28 @@ -417,10 +417,10 @@ LL | #[suggestion_part(code(3))] | ^ error: derive(Diagnostic): expected exactly one string literal for `code = ...` - --> $DIR/subdiagnostic-derive.rs:693:29 + --> $DIR/subdiagnostic-derive.rs:693:28 | LL | #[suggestion_part(code())] - | ^ + | ^ error: expected string literal --> $DIR/subdiagnostic-derive.rs:702:30 @@ -464,32 +464,26 @@ LL | #[suggestion(no_crate_example, code = "", style = "foo")] | = help: valid styles are `normal`, `short`, `hidden`, `verbose` and `tool-only` -error: derive(Diagnostic): expected `= "xxx"` - --> $DIR/subdiagnostic-derive.rs:777:49 +error: expected string literal + --> $DIR/subdiagnostic-derive.rs:777:51 | LL | #[suggestion(no_crate_example, code = "", style = 42)] - | ^ + | ^^ error: derive(Diagnostic): a diagnostic slug must be the first argument to the attribute - --> $DIR/subdiagnostic-derive.rs:785:48 + --> $DIR/subdiagnostic-derive.rs:785:43 | LL | #[suggestion(no_crate_example, code = "", style)] - | ^ + | ^^^^^ -error: derive(Diagnostic): expected `= "xxx"` - --> $DIR/subdiagnostic-derive.rs:793:48 - | -LL | #[suggestion(no_crate_example, code = "", style("foo"))] - | ^ - -error: expected `,` +error: expected `=` --> $DIR/subdiagnostic-derive.rs:793:48 | LL | #[suggestion(no_crate_example, code = "", style("foo"))] | ^ error: derive(Diagnostic): `#[primary_span]` is not a valid attribute - --> $DIR/subdiagnostic-derive.rs:805:5 + --> $DIR/subdiagnostic-derive.rs:804:5 | LL | #[primary_span] | ^ @@ -498,7 +492,7 @@ LL | #[primary_span] = help: to create a suggestion with multiple spans, use `#[multipart_suggestion]` instead error: derive(Diagnostic): suggestion without `#[primary_span]` field - --> $DIR/subdiagnostic-derive.rs:802:1 + --> $DIR/subdiagnostic-derive.rs:801:1 | LL | #[suggestion(no_crate_example, code = "")] | ^ @@ -557,5 +551,5 @@ error: cannot find attribute `bar` in this scope LL | #[bar("...")] | ^^^ -error: aborting due to 84 previous errors +error: aborting due to 83 previous errors