Skip to content

Commit fbac1f2

Browse files
committed
macros: simplify field ordering in diag derive
Following the approach taken in earlier commits to separate formatting initialization from use in the subdiagnostic derive, simplify the diagnostic derive by removing the field-ordering logic that previously solved this problem. Signed-off-by: David Wood <[email protected]>
1 parent 7e20929 commit fbac1f2

File tree

3 files changed

+38
-102
lines changed

3 files changed

+38
-102
lines changed

compiler/rustc_macros/src/diagnostics/diagnostic.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,10 @@ impl<'a> DiagnosticDerive<'a> {
5252
}
5353
};
5454

55+
let formatting_init = &builder.formatting_init;
5556
quote! {
5657
#init
58+
#formatting_init
5759
#preamble
5860
#body
5961
#diag
@@ -101,9 +103,10 @@ impl<'a> LintDiagnosticDerive<'a> {
101103
let body = builder.body(&variant);
102104

103105
let diag = &builder.parent.diag;
104-
106+
let formatting_init = &builder.formatting_init;
105107
quote! {
106108
#preamble
109+
#formatting_init
107110
#body
108111
#diag
109112
}

compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs

+33-42
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ use crate::diagnostics::error::{
55
DiagnosticDeriveError,
66
};
77
use crate::diagnostics::utils::{
8-
bind_style_of_field, build_field_mapping, report_error_if_not_applied_to_span,
9-
report_type_error, should_generate_set_arg, type_is_unit, type_matches_path, FieldInfo,
10-
FieldInnerTy, FieldMap, HasFieldMap, SetOnce, SpannedOption, SubdiagnosticKind,
8+
build_field_mapping, report_error_if_not_applied_to_span, report_type_error,
9+
should_generate_set_arg, type_is_unit, type_matches_path, FieldInfo, FieldInnerTy, FieldMap,
10+
HasFieldMap, SetOnce, SpannedOption, SubdiagnosticKind,
1111
};
1212
use proc_macro2::{Ident, Span, TokenStream};
1313
use quote::{format_ident, quote};
@@ -40,6 +40,9 @@ pub(crate) struct DiagnosticDeriveVariantBuilder<'parent> {
4040
/// The parent builder for the entire type.
4141
pub parent: &'parent DiagnosticDeriveBuilder,
4242

43+
/// Initialization of format strings for code suggestions.
44+
pub formatting_init: TokenStream,
45+
4346
/// Span of the struct or the enum variant.
4447
pub span: proc_macro::Span,
4548

@@ -88,19 +91,7 @@ impl DiagnosticDeriveBuilder {
8891
}
8992
}
9093

91-
for variant in structure.variants_mut() {
92-
// First, change the binding style of each field based on the code that will be
93-
// generated for the field - e.g. `set_arg` calls needs by-move bindings, whereas
94-
// `set_primary_span` only needs by-ref.
95-
variant.bind_with(|bi| bind_style_of_field(bi.ast()).0);
96-
97-
// Then, perform a stable sort on bindings which generates code for by-ref bindings
98-
// before code generated for by-move bindings. Any code generated for the by-ref
99-
// bindings which creates a reference to the by-move fields will happen before the
100-
// by-move bindings move those fields and make them inaccessible.
101-
variant.bindings_mut().sort_by_cached_key(|bi| bind_style_of_field(bi.ast()));
102-
}
103-
94+
structure.bind_with(|_| synstructure::BindStyle::Move);
10495
let variants = structure.each_variant(|variant| {
10596
let span = match structure.ast().data {
10697
syn::Data::Struct(..) => span,
@@ -112,6 +103,7 @@ impl DiagnosticDeriveBuilder {
112103
parent: &self,
113104
span,
114105
field_map: build_field_mapping(variant),
106+
formatting_init: TokenStream::new(),
115107
slug: None,
116108
code: None,
117109
};
@@ -143,16 +135,14 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
143135

144136
/// Generates calls to `span_label` and similar functions based on the attributes on fields or
145137
/// calls to `set_arg` when no attributes are present.
146-
///
147-
/// Expects use of `Self::each_variant` which will have sorted bindings so that by-ref bindings
148-
/// (which may create references to by-move bindings) have their code generated first -
149-
/// necessary as code for suggestions uses formatting machinery and the value of other fields
150-
/// (any given field can be referenced multiple times, so must be accessed through a borrow);
151-
/// and when passing fields to `add_subdiagnostic` or `set_arg` for Fluent, fields must be
152-
/// accessed by-move.
153138
pub fn body<'s>(&mut self, variant: &VariantInfo<'s>) -> TokenStream {
154139
let mut body = quote! {};
155-
for binding in variant.bindings() {
140+
// Generate `set_arg` calls first..
141+
for binding in variant.bindings().iter().filter(|bi| should_generate_set_arg(bi.ast())) {
142+
body.extend(self.generate_field_code(binding));
143+
}
144+
// ..and then subdiagnostic additions.
145+
for binding in variant.bindings().iter().filter(|bi| !should_generate_set_arg(bi.ast())) {
156146
body.extend(self.generate_field_attrs_code(binding));
157147
}
158148
body
@@ -274,24 +264,27 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
274264
}
275265
}
276266

277-
fn generate_field_attrs_code(&mut self, binding_info: &BindingInfo<'_>) -> TokenStream {
267+
fn generate_field_code(&mut self, binding_info: &BindingInfo<'_>) -> TokenStream {
268+
let diag = &self.parent.diag;
269+
278270
let field = binding_info.ast();
279271
let field_binding = &binding_info.binding;
280272

281-
if should_generate_set_arg(&field) {
282-
let diag = &self.parent.diag;
283-
let ident = field.ident.as_ref().unwrap();
284-
// strip `r#` prefix, if present
285-
let ident = format_ident!("{}", ident);
286-
return quote! {
287-
#diag.set_arg(
288-
stringify!(#ident),
289-
#field_binding
290-
);
291-
};
273+
let ident = field.ident.as_ref().unwrap();
274+
let ident = format_ident!("{}", ident); // strip `r#` prefix, if present
275+
276+
quote! {
277+
#diag.set_arg(
278+
stringify!(#ident),
279+
#field_binding
280+
);
292281
}
282+
}
283+
284+
fn generate_field_attrs_code(&mut self, binding_info: &BindingInfo<'_>) -> TokenStream {
285+
let field = binding_info.ast();
286+
let field_binding = &binding_info.binding;
293287

294-
let needs_move = bind_style_of_field(&field).is_move();
295288
let inner_ty = FieldInnerTy::from_type(&field.ty);
296289

297290
field
@@ -304,10 +297,8 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
304297
let (binding, needs_destructure) = if needs_clone {
305298
// `primary_span` can accept a `Vec<Span>` so don't destructure that.
306299
(quote! { #field_binding.clone() }, false)
307-
} else if needs_move {
308-
(quote! { #field_binding }, true)
309300
} else {
310-
(quote! { *#field_binding }, true)
301+
(quote! { #field_binding }, true)
311302
};
312303

313304
let generated_code = self
@@ -440,8 +431,8 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
440431
.unwrap_or_else(|| quote! { rustc_errors::Applicability::Unspecified });
441432
let style = suggestion_kind.to_suggestion_style();
442433

434+
self.formatting_init.extend(code_init);
443435
Ok(quote! {
444-
#code_init
445436
#diag.span_suggestion_with_style(
446437
#span_field,
447438
rustc_errors::fluent::#slug,
@@ -490,7 +481,7 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
490481
// If `ty` is `Span` w/out applicability, then use `Applicability::Unspecified`.
491482
ty @ Type::Path(..) if type_matches_path(ty, &["rustc_span", "Span"]) => {
492483
let binding = &info.binding.binding;
493-
Ok((quote!(*#binding), None))
484+
Ok((quote!(#binding), None))
494485
}
495486
// If `ty` is `(Span, Applicability)` then return tokens accessing those.
496487
Type::Tuple(tup) => {

compiler/rustc_macros/src/diagnostics/utils.rs

+1-59
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,12 @@ use proc_macro::Span;
55
use proc_macro2::TokenStream;
66
use quote::{format_ident, quote, ToTokens};
77
use std::cell::RefCell;
8-
use std::cmp::Ordering;
98
use std::collections::{BTreeSet, HashMap};
109
use std::fmt;
1110
use std::str::FromStr;
1211
use syn::{spanned::Spanned, Attribute, Field, Meta, Type, TypeTuple};
1312
use syn::{MetaList, MetaNameValue, NestedMeta, Path};
14-
use synstructure::{BindStyle, BindingInfo, VariantInfo};
13+
use synstructure::{BindingInfo, VariantInfo};
1514

1615
use super::error::invalid_nested_attr;
1716

@@ -650,65 +649,8 @@ impl quote::IdentFragment for SubdiagnosticKind {
650649
}
651650
}
652651

653-
/// Wrapper around `synstructure::BindStyle` which implements `Ord`.
654-
#[derive(PartialEq, Eq)]
655-
pub(super) struct OrderedBindStyle(pub(super) BindStyle);
656-
657-
impl OrderedBindStyle {
658-
/// Is `BindStyle::Move` or `BindStyle::MoveMut`?
659-
pub(super) fn is_move(&self) -> bool {
660-
matches!(self.0, BindStyle::Move | BindStyle::MoveMut)
661-
}
662-
}
663-
664-
impl Ord for OrderedBindStyle {
665-
fn cmp(&self, other: &Self) -> Ordering {
666-
match (self.is_move(), other.is_move()) {
667-
// If both `self` and `other` are the same, then ordering is equal.
668-
(true, true) | (false, false) => Ordering::Equal,
669-
// If `self` is not a move then it should be considered less than `other` (so that
670-
// references are sorted first).
671-
(false, _) => Ordering::Less,
672-
// If `self` is a move then it must be greater than `other` (again, so that references
673-
// are sorted first).
674-
(true, _) => Ordering::Greater,
675-
}
676-
}
677-
}
678-
679-
impl PartialOrd for OrderedBindStyle {
680-
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
681-
Some(self.cmp(other))
682-
}
683-
}
684-
685652
/// Returns `true` if `field` should generate a `set_arg` call rather than any other diagnostic
686653
/// call (like `span_label`).
687654
pub(super) fn should_generate_set_arg(field: &Field) -> bool {
688655
field.attrs.is_empty()
689656
}
690-
691-
/// Returns `true` if `field` needs to have code generated in the by-move branch of the
692-
/// generated derive rather than the by-ref branch.
693-
pub(super) fn bind_style_of_field(field: &Field) -> OrderedBindStyle {
694-
let generates_set_arg = should_generate_set_arg(field);
695-
let is_multispan = type_matches_path(&field.ty, &["rustc_errors", "MultiSpan"]);
696-
// FIXME(davidtwco): better support for one field needing to be in the by-move and
697-
// by-ref branches.
698-
let is_subdiagnostic = field
699-
.attrs
700-
.iter()
701-
.map(|attr| attr.path.segments.last().unwrap().ident.to_string())
702-
.any(|attr| attr == "subdiagnostic");
703-
704-
// `set_arg` calls take their argument by-move..
705-
let needs_move = generates_set_arg
706-
// If this is a `MultiSpan` field then it needs to be moved to be used by any
707-
// attribute..
708-
|| is_multispan
709-
// If this a `#[subdiagnostic]` then it needs to be moved as the other diagnostic is
710-
// unlikely to be `Copy`..
711-
|| is_subdiagnostic;
712-
713-
OrderedBindStyle(if needs_move { BindStyle::Move } else { BindStyle::Ref })
714-
}

0 commit comments

Comments
 (0)