diff options
| author | Xiretza <xiretza@xiretza.xyz> | 2022-10-17 19:41:49 +0200 |
|---|---|---|
| committer | Xiretza <xiretza@xiretza.xyz> | 2022-10-23 18:59:13 +0200 |
| commit | 8bc43f99e91a94868fe08bb72b7ce66d7656d0b5 (patch) | |
| tree | a22595844fc5ab4f427e44a7735dc8d4cf50587f /compiler | |
| parent | 9be2f35a4c1ed1b04aa4a6945b64763f599259ff (diff) | |
| download | rust-8bc43f99e91a94868fe08bb72b7ce66d7656d0b5.tar.gz rust-8bc43f99e91a94868fe08bb72b7ce66d7656d0b5.zip | |
Allow specifying multiple alternative suggestions
This allows porting uses of span_suggestions() to diagnostic structs. Doesn't work for multipart_suggestions() because the rank would be reversed - the struct would specify multiple spans, each of which has multiple possible replacements, while multipart_suggestions() creates multiple possible replacements, each with multiple spans.
Diffstat (limited to 'compiler')
| -rw-r--r-- | compiler/rustc_errors/src/diagnostic.rs | 26 | ||||
| -rw-r--r-- | compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs | 2 | ||||
| -rw-r--r-- | compiler/rustc_macros/src/diagnostics/subdiagnostic.rs | 21 | ||||
| -rw-r--r-- | compiler/rustc_macros/src/diagnostics/utils.rs | 105 |
4 files changed, 130 insertions, 24 deletions
diff --git a/compiler/rustc_errors/src/diagnostic.rs b/compiler/rustc_errors/src/diagnostic.rs index a63fc0ca285..23f29a24fe7 100644 --- a/compiler/rustc_errors/src/diagnostic.rs +++ b/compiler/rustc_errors/src/diagnostic.rs @@ -691,6 +691,24 @@ impl Diagnostic { suggestions: impl Iterator<Item = String>, applicability: Applicability, ) -> &mut Self { + self.span_suggestions_with_style( + sp, + msg, + suggestions, + applicability, + SuggestionStyle::ShowCode, + ) + } + + /// [`Diagnostic::span_suggestions()`] but you can set the [`SuggestionStyle`]. + pub fn span_suggestions_with_style( + &mut self, + sp: Span, + msg: impl Into<SubdiagnosticMessage>, + suggestions: impl Iterator<Item = String>, + applicability: Applicability, + style: SuggestionStyle, + ) -> &mut Self { let mut suggestions: Vec<_> = suggestions.collect(); suggestions.sort(); @@ -706,14 +724,15 @@ impl Diagnostic { self.push_suggestion(CodeSuggestion { substitutions, msg: self.subdiagnostic_message_to_diagnostic_message(msg), - style: SuggestionStyle::ShowCode, + style, applicability, }); self } - /// Prints out a message with multiple suggested edits of the code. - /// See also [`Diagnostic::span_suggestion()`]. + /// Prints out a message with multiple suggested edits of the code, where each edit consists of + /// multiple parts. + /// See also [`Diagnostic::multipart_suggestion()`]. pub fn multipart_suggestions( &mut self, msg: impl Into<SubdiagnosticMessage>, @@ -745,6 +764,7 @@ impl Diagnostic { }); self } + /// Prints out a message with a suggested edit of the code. If the suggestion is presented /// inline, it will only show the message and not the suggestion. /// diff --git a/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs b/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs index 9f7d2661a3e..3ea83fd09c7 100644 --- a/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs +++ b/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs @@ -454,7 +454,7 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> { self.formatting_init.extend(code_init); Ok(quote! { - #diag.span_suggestion_with_style( + #diag.span_suggestions_with_style( #span_field, rustc_errors::fluent::#slug, #code_field, diff --git a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs index d1acb713842..fa0ca5a5242 100644 --- a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs +++ b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs @@ -11,9 +11,11 @@ use crate::diagnostics::utils::{ }; use proc_macro2::TokenStream; use quote::{format_ident, quote}; -use syn::{spanned::Spanned, Attribute, Meta, MetaList, MetaNameValue, NestedMeta, Path}; +use syn::{spanned::Spanned, Attribute, Meta, MetaList, NestedMeta, Path}; use synstructure::{BindingInfo, Structure, VariantInfo}; +use super::utils::{build_suggestion_code, AllowMultipleAlternatives}; + /// The central struct for constructing the `add_to_diagnostic` method from an annotated struct. pub(crate) struct SubdiagnosticDeriveBuilder { diag: syn::Ident, @@ -414,15 +416,16 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> { let nested_name = meta.path().segments.last().unwrap().ident.to_string(); let nested_name = nested_name.as_str(); - let Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(value), .. }) = meta else { - throw_invalid_nested_attr!(attr, &nested_attr); - }; - match nested_name { "code" => { - let formatted_str = self.build_format(&value.value(), value.span()); let code_field = new_code_ident(); - code.set_once((code_field, formatted_str), span); + let formatting_init = build_suggestion_code( + &code_field, + meta, + self, + AllowMultipleAlternatives::No, + ); + code.set_once((code_field, formatting_init), span); } _ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| { diag.help("`code` is the only valid nested attribute") @@ -430,14 +433,14 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> { } } - let Some((code_field, formatted_str)) = code.value() else { + let Some((code_field, formatting_init)) = code.value() else { span_err(span, "`#[suggestion_part(...)]` attribute without `code = \"...\"`") .emit(); return Ok(quote! {}); }; let binding = info.binding; - self.formatting_init.extend(quote! { let #code_field = #formatted_str; }); + self.formatting_init.extend(formatting_init); let code_field = if clone_suggestion_code { quote! { #code_field.clone() } } else { diff --git a/compiler/rustc_macros/src/diagnostics/utils.rs b/compiler/rustc_macros/src/diagnostics/utils.rs index 61d5007fc30..374c795d0a6 100644 --- a/compiler/rustc_macros/src/diagnostics/utils.rs +++ b/compiler/rustc_macros/src/diagnostics/utils.rs @@ -2,7 +2,7 @@ use crate::diagnostics::error::{ span_err, throw_invalid_attr, throw_invalid_nested_attr, throw_span_err, DiagnosticDeriveError, }; use proc_macro::Span; -use proc_macro2::TokenStream; +use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote, ToTokens}; use std::cell::RefCell; use std::collections::{BTreeSet, HashMap}; @@ -395,6 +395,82 @@ pub(super) fn build_field_mapping<'v>(variant: &VariantInfo<'v>) -> HashMap<Stri fields_map } +#[derive(Copy, Clone, Debug)] +pub(super) enum AllowMultipleAlternatives { + No, + Yes, +} + +/// 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, + meta: &Meta, + fields: &impl HasFieldMap, + allow_multiple: AllowMultipleAlternatives, +) -> TokenStream { + let values = match meta { + // `code = "foo"` + Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) => vec![s], + // `code("foo", "bar")` + Meta::List(MetaList { nested, .. }) => { + if let AllowMultipleAlternatives::No = allow_multiple { + span_err( + meta.span().unwrap(), + "expected exactly one string literal for `code = ...`", + ) + .emit(); + vec![] + } else if nested.is_empty() { + span_err( + meta.span().unwrap(), + "expected at least one string literal for `code(...)`", + ) + .emit(); + vec![] + } else { + nested + .into_iter() + .filter_map(|item| { + if let NestedMeta::Lit(syn::Lit::Str(s)) = item { + Some(s) + } else { + span_err( + item.span().unwrap(), + "`code(...)` must contain only string literals", + ) + .emit(); + None + } + }) + .collect() + } + } + _ => { + span_err( + meta.span().unwrap(), + r#"`code = "..."`/`code(...)` must contain only string literals"#, + ) + .emit(); + vec![] + } + }; + + if let AllowMultipleAlternatives::Yes = allow_multiple { + let formatted_strings: Vec<_> = values + .into_iter() + .map(|value| fields.build_format(&value.value(), value.span())) + .collect(); + quote! { let #code_field = [#(#formatted_strings),*].into_iter(); } + } else if let [value] = values.as_slice() { + let formatted_str = fields.build_format(&value.value(), value.span()); + quote! { let #code_field = #formatted_str; } + } else { + // error handled previously + quote! { let #code_field = String::new(); } + } +} + /// Possible styles for suggestion subdiagnostics. #[derive(Clone, Copy)] pub(super) enum SuggestionKind { @@ -571,21 +647,23 @@ impl SubdiagnosticKind { let nested_name = meta.path().segments.last().unwrap().ident.to_string(); let nested_name = nested_name.as_str(); - let value = match meta { - Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(value), .. }) => value, + let string_value = match meta { + Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(value), .. }) => Some(value), + Meta::Path(_) => throw_invalid_nested_attr!(attr, &nested_attr, |diag| { diag.help("a diagnostic slug must be the first argument to the attribute") }), - _ => { - invalid_nested_attr(attr, &nested_attr).emit(); - continue; - } + _ => None, }; match (nested_name, &mut kind) { ("code", SubdiagnosticKind::Suggestion { code_field, .. }) => { - let formatted_str = fields.build_format(&value.value(), value.span()); - let code_init = quote! { let #code_field = #formatted_str; }; + let code_init = build_suggestion_code( + code_field, + meta, + fields, + AllowMultipleAlternatives::Yes, + ); code.set_once(code_init, span); } ( @@ -593,6 +671,11 @@ impl SubdiagnosticKind { SubdiagnosticKind::Suggestion { ref mut applicability, .. } | SubdiagnosticKind::MultipartSuggestion { ref mut applicability, .. }, ) => { + let Some(value) = string_value else { + invalid_nested_attr(attr, &nested_attr).emit(); + continue; + }; + let value = Applicability::from_str(&value.value()).unwrap_or_else(|()| { span_err(span, "invalid applicability").emit(); Applicability::Unspecified @@ -623,7 +706,7 @@ impl SubdiagnosticKind { init } else { span_err(span, "suggestion without `code = \"...\"`").emit(); - quote! { let #code_field: String = unreachable!(); } + quote! { let #code_field = std::iter::empty(); } }; } SubdiagnosticKind::Label @@ -644,7 +727,7 @@ impl quote::IdentFragment for SubdiagnosticKind { SubdiagnosticKind::Note => write!(f, "note"), SubdiagnosticKind::Help => write!(f, "help"), SubdiagnosticKind::Warn => write!(f, "warn"), - SubdiagnosticKind::Suggestion { .. } => write!(f, "suggestion_with_style"), + SubdiagnosticKind::Suggestion { .. } => write!(f, "suggestions_with_style"), SubdiagnosticKind::MultipartSuggestion { .. } => { write!(f, "multipart_suggestion_with_style") } |
