diff --git a/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs b/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs index e405462bbb8..12bcd939bd6 100644 --- a/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs +++ b/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs @@ -322,11 +322,7 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> { let generated_code = self .generate_inner_field_code( attr, - FieldInfo { - binding: binding_info, - ty: inner_ty.inner_type().unwrap_or(&field.ty), - span: &field.span(), - }, + FieldInfo { binding: binding_info, ty: inner_ty, span: &field.span() }, binding, ) .unwrap_or_else(|v| v.to_compile_error()); @@ -418,9 +414,9 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> { Ok(self.add_spanned_subdiagnostic(binding, &fn_ident, slug)) } SubdiagnosticKind::Note | SubdiagnosticKind::Help | SubdiagnosticKind::Warn => { - if type_matches_path(info.ty, &["rustc_span", "Span"]) { + if type_matches_path(info.ty.inner_type(), &["rustc_span", "Span"]) { Ok(self.add_spanned_subdiagnostic(binding, &fn_ident, slug)) - } else if type_is_unit(info.ty) { + } else if type_is_unit(info.ty.inner_type()) { Ok(self.add_subdiagnostic(&fn_ident, slug)) } else { report_type_error(attr, "`Span` or `()`")? @@ -432,6 +428,15 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> { code_field, code_init, } => { + if let FieldInnerTy::Vec(_) = info.ty { + throw_invalid_attr!(attr, &meta, |diag| { + diag + .note("`#[suggestion(...)]` applied to `Vec` field is ambiguous") + .help("to show a suggestion consisting of multiple parts, use a `Subdiagnostic` annotated with `#[multipart_suggestion(...)]`") + .help("to show a variable set of suggestions, use a `Vec` of `Subdiagnostic`s annotated with `#[suggestion(...)]`") + }); + } + let (span_field, mut applicability) = self.span_and_applicability_of_ty(info)?; if let Some((static_applicability, span)) = static_applicability { @@ -489,7 +494,7 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> { &self, info: FieldInfo<'_>, ) -> Result<(TokenStream, SpannedOption), DiagnosticDeriveError> { - match &info.ty { + match &info.ty.inner_type() { // If `ty` is `Span` w/out applicability, then use `Applicability::Unspecified`. ty @ Type::Path(..) if type_matches_path(ty, &["rustc_span", "Span"]) => { let binding = &info.binding.binding; diff --git a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs index baffd3cec9c..906e4c0b0e1 100644 --- a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs +++ b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs @@ -247,11 +247,7 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> { return quote! {}; } - let info = FieldInfo { - binding, - ty: inner_ty.inner_type().unwrap_or(&ast.ty), - span: &ast.span(), - }; + let info = FieldInfo { binding, ty: inner_ty, span: &ast.span() }; let generated = self .generate_field_code_inner(kind_stats, attr, info, inner_ty.will_iterate()) @@ -312,6 +308,21 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> { let binding = info.binding.binding.clone(); // FIXME(#100717): support `Option` on `primary_span` like in the // diagnostic derive + if !matches!(info.ty, FieldInnerTy::Plain(_)) { + throw_invalid_attr!(attr, &Meta::Path(path), |diag| { + let diag = diag.note("there must be exactly one primary span"); + + if kind_stats.has_normal_suggestion { + diag.help( + "to create a suggestion with multiple spans, \ + use `#[multipart_suggestion]` instead", + ) + } else { + diag + } + }); + } + self.span_field.set_once(binding, span); } diff --git a/compiler/rustc_macros/src/diagnostics/utils.rs b/compiler/rustc_macros/src/diagnostics/utils.rs index 6f52a3de1b1..27b8f676f3f 100644 --- a/compiler/rustc_macros/src/diagnostics/utils.rs +++ b/compiler/rustc_macros/src/diagnostics/utils.rs @@ -80,7 +80,7 @@ fn report_error_if_not_applied_to_ty( path: &[&str], ty_name: &str, ) -> Result<(), DiagnosticDeriveError> { - if !type_matches_path(info.ty, path) { + if !type_matches_path(info.ty.inner_type(), path) { report_type_error(attr, ty_name)?; } @@ -105,8 +105,8 @@ pub(crate) fn report_error_if_not_applied_to_span( attr: &Attribute, info: &FieldInfo<'_>, ) -> Result<(), DiagnosticDeriveError> { - if !type_matches_path(info.ty, &["rustc_span", "Span"]) - && !type_matches_path(info.ty, &["rustc_errors", "MultiSpan"]) + if !type_matches_path(info.ty.inner_type(), &["rustc_span", "Span"]) + && !type_matches_path(info.ty.inner_type(), &["rustc_errors", "MultiSpan"]) { report_type_error(attr, "`Span` or `MultiSpan`")?; } @@ -115,44 +115,50 @@ pub(crate) fn report_error_if_not_applied_to_span( } /// Inner type of a field and type of wrapper. +#[derive(Copy, Clone)] pub(crate) enum FieldInnerTy<'ty> { /// Field is wrapped in a `Option<$inner>`. Option(&'ty Type), /// Field is wrapped in a `Vec<$inner>`. Vec(&'ty Type), /// Field isn't wrapped in an outer type. - None, + Plain(&'ty Type), } impl<'ty> FieldInnerTy<'ty> { /// Returns inner type for a field, if there is one. /// - /// - If `ty` is an `Option`, returns `FieldInnerTy::Option { inner: (inner type) }`. - /// - If `ty` is a `Vec`, returns `FieldInnerTy::Vec { inner: (inner type) }`. - /// - Otherwise returns `None`. + /// - If `ty` is an `Option`, returns `FieldInnerTy::Option(Inner)`. + /// - If `ty` is a `Vec`, returns `FieldInnerTy::Vec(Inner)`. + /// - Otherwise returns `FieldInnerTy::Plain(ty)`. pub(crate) fn from_type(ty: &'ty Type) -> Self { - let variant: &dyn Fn(&'ty Type) -> FieldInnerTy<'ty> = - if type_matches_path(ty, &["std", "option", "Option"]) { - &FieldInnerTy::Option - } else if type_matches_path(ty, &["std", "vec", "Vec"]) { - &FieldInnerTy::Vec - } else { - return FieldInnerTy::None; + fn single_generic_type(ty: &Type) -> &Type { + let Type::Path(ty_path) = ty else { + panic!("expected path type"); }; - if let Type::Path(ty_path) = ty { let path = &ty_path.path; let ty = path.segments.iter().last().unwrap(); - if let syn::PathArguments::AngleBracketed(bracketed) = &ty.arguments { - if bracketed.args.len() == 1 { - if let syn::GenericArgument::Type(ty) = &bracketed.args[0] { - return variant(ty); - } - } - } + let syn::PathArguments::AngleBracketed(bracketed) = &ty.arguments else { + panic!("expected bracketed generic arguments"); + }; + + assert_eq!(bracketed.args.len(), 1); + + let syn::GenericArgument::Type(ty) = &bracketed.args[0] else { + panic!("expected generic parameter to be a type generic"); + }; + + ty } - unreachable!(); + if type_matches_path(ty, &["std", "option", "Option"]) { + FieldInnerTy::Option(single_generic_type(ty)) + } else if type_matches_path(ty, &["std", "vec", "Vec"]) { + FieldInnerTy::Vec(single_generic_type(ty)) + } else { + FieldInnerTy::Plain(ty) + } } /// Returns `true` if `FieldInnerTy::with` will result in iteration for this inner type (i.e. @@ -160,15 +166,16 @@ impl<'ty> FieldInnerTy<'ty> { pub(crate) fn will_iterate(&self) -> bool { match self { FieldInnerTy::Vec(..) => true, - FieldInnerTy::Option(..) | FieldInnerTy::None => false, + FieldInnerTy::Option(..) | FieldInnerTy::Plain(_) => false, } } - /// Returns `Option` containing inner type if there is one. - pub(crate) fn inner_type(&self) -> Option<&'ty Type> { + /// Returns the inner type. + pub(crate) fn inner_type(&self) -> &'ty Type { match self { - FieldInnerTy::Option(inner) | FieldInnerTy::Vec(inner) => Some(inner), - FieldInnerTy::None => None, + FieldInnerTy::Option(inner) | FieldInnerTy::Vec(inner) | FieldInnerTy::Plain(inner) => { + inner + } } } @@ -185,7 +192,7 @@ impl<'ty> FieldInnerTy<'ty> { #inner } }, - FieldInnerTy::None => quote! { #inner }, + FieldInnerTy::Plain(..) => quote! { #inner }, } } } @@ -194,7 +201,7 @@ impl<'ty> FieldInnerTy<'ty> { /// `generate_*` methods from walking the attributes themselves. pub(crate) struct FieldInfo<'a> { pub(crate) binding: &'a BindingInfo<'a>, - pub(crate) ty: &'a Type, + pub(crate) ty: FieldInnerTy<'a>, pub(crate) span: &'a proc_macro2::Span, } diff --git a/tests/ui-fulldeps/session-diagnostic/diagnostic-derive.rs b/tests/ui-fulldeps/session-diagnostic/diagnostic-derive.rs index 65d9601e78a..07f95d13937 100644 --- a/tests/ui-fulldeps/session-diagnostic/diagnostic-derive.rs +++ b/tests/ui-fulldeps/session-diagnostic/diagnostic-derive.rs @@ -799,3 +799,11 @@ struct SuggestionStyleGood { #[suggestion(code = "", style = "hidden")] sub: Span, } + +#[derive(Diagnostic)] +#[diag(compiletest_example)] +struct SuggestionOnVec { + #[suggestion(suggestion, code = "")] + //~^ ERROR `#[suggestion(...)]` is not a valid attribute + sub: Vec, +} diff --git a/tests/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr b/tests/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr index 13e806a434f..61806c80efc 100644 --- a/tests/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr +++ b/tests/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr @@ -589,6 +589,16 @@ error: `code = "..."`/`code(...)` must contain only string literals LL | #[suggestion(code = 3)] | ^^^^^^^^ +error: `#[suggestion(...)]` is not a valid attribute + --> $DIR/diagnostic-derive.rs:806:5 + | +LL | #[suggestion(suggestion, code = "")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `#[suggestion(...)]` applied to `Vec` field is ambiguous + = help: to show a suggestion consisting of multiple parts, use a `Subdiagnostic` annotated with `#[multipart_suggestion(...)]` + = help: to show a variable set of suggestions, use a `Vec` of `Subdiagnostic`s annotated with `#[suggestion(...)]` + error: cannot find attribute `nonsense` in this scope --> $DIR/diagnostic-derive.rs:55:3 | @@ -660,7 +670,7 @@ note: required by a bound in `DiagnosticBuilder::<'a, G>::set_arg` --> $COMPILER_DIR/rustc_errors/src/diagnostic_builder.rs:LL:CC = note: this error originates in the derive macro `Diagnostic` which comes from the expansion of the macro `forward` (in Nightly builds, run with -Z macro-backtrace for more info) -error: aborting due to 83 previous errors +error: aborting due to 84 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 61ac456a6b6..09ad6964909 100644 --- a/tests/ui-fulldeps/session-diagnostic/subdiagnostic-derive.rs +++ b/tests/ui-fulldeps/session-diagnostic/subdiagnostic-derive.rs @@ -798,3 +798,13 @@ struct SuggestionStyleInvalid4 { #[primary_span] sub: Span, } + +#[derive(Subdiagnostic)] +#[suggestion(parse_add_paren, code = "")] +//~^ ERROR suggestion without `#[primary_span]` field +struct PrimarySpanOnVec { + #[primary_span] + //~^ ERROR `#[primary_span]` is not a valid attribute + //~| NOTE there must be exactly one primary span + sub: Vec, +} diff --git a/tests/ui-fulldeps/session-diagnostic/subdiagnostic-derive.stderr b/tests/ui-fulldeps/session-diagnostic/subdiagnostic-derive.stderr index b594fa6cde1..f9d1a63031d 100644 --- a/tests/ui-fulldeps/session-diagnostic/subdiagnostic-derive.stderr +++ b/tests/ui-fulldeps/session-diagnostic/subdiagnostic-derive.stderr @@ -501,6 +501,27 @@ error: `#[suggestion(style(...))]` is not a valid attribute LL | #[suggestion(parse_add_paren, code = "", style("foo"))] | ^^^^^^^^^^^^ +error: `#[primary_span]` is not a valid attribute + --> $DIR/subdiagnostic-derive.rs:806:5 + | +LL | #[primary_span] + | ^^^^^^^^^^^^^^^ + | + = note: there must be exactly one primary span + = help: to create a suggestion with multiple spans, use `#[multipart_suggestion]` instead + +error: suggestion without `#[primary_span]` field + --> $DIR/subdiagnostic-derive.rs:803:1 + | +LL | / #[suggestion(parse_add_paren, code = "")] +LL | | +LL | | struct PrimarySpanOnVec { +LL | | #[primary_span] +... | +LL | | sub: Vec, +LL | | } + | |_^ + error: cannot find attribute `foo` in this scope --> $DIR/subdiagnostic-derive.rs:63:3 | @@ -561,6 +582,6 @@ error[E0425]: cannot find value `slug` in module `rustc_errors::fluent` LL | #[label(slug)] | ^^^^ not found in `rustc_errors::fluent` -error: aborting due to 79 previous errors +error: aborting due to 81 previous errors For more information about this error, try `rustc --explain E0425`.