flux_macros/diagnostics/
diagnostic.rs1#![allow(clippy::pedantic)]
2#![deny(unused_must_use)]
3
4use std::cell::RefCell;
5
6use proc_macro2::TokenStream;
7use quote::quote;
8use syn::spanned::Spanned;
9use synstructure::Structure;
10
11use crate::diagnostics::{
12 diagnostic_builder::DiagnosticDeriveKind,
13 error::{DiagnosticDeriveError, span_err},
14 utils::SetOnce,
15};
16
17pub(crate) struct DiagnosticDerive<'a> {
19 structure: Structure<'a>,
20}
21
22impl<'a> DiagnosticDerive<'a> {
23 pub(crate) fn new(structure: Structure<'a>) -> Self {
24 Self { structure }
25 }
26
27 pub(crate) fn into_tokens(self) -> TokenStream {
28 let DiagnosticDerive { mut structure } = self;
29 let kind = DiagnosticDeriveKind::Diagnostic;
30 let slugs = RefCell::new(Vec::new());
31 let implementation = kind.each_variant(&mut structure, |mut builder, variant| {
32 let preamble = builder.preamble(variant);
33 let body = builder.body(variant);
34
35 let init = match builder.slug.value_ref() {
36 None => {
37 span_err(builder.span, "diagnostic slug not specified")
38 .help(
39 "specify the slug as the first argument to the `#[diag(...)]` \
40 attribute, such as `#[diag(hir_analysis_example_error)]`",
41 )
42 .emit();
43 return DiagnosticDeriveError::ErrorHandled.to_compile_error();
44 }
45 Some(slug)
46 if let Some(Mismatch { slug_name, crate_name, slug_prefix }) =
47 Mismatch::check(slug) =>
48 {
49 span_err(slug.span().unwrap(), "diagnostic slug and crate name do not match")
50 .note(format!("slug is `{slug_name}` but the crate name is `{crate_name}`"))
51 .help(format!("expected a slug starting with `{slug_prefix}_...`"))
52 .emit();
53 return DiagnosticDeriveError::ErrorHandled.to_compile_error();
54 }
55 Some(slug) => {
56 slugs.borrow_mut().push(slug.clone());
57 quote! {
58 let mut diag = rustc_errors::Diag::new(
59 dcx,
60 level,
61 crate::fluent_generated::#slug
62 );
63 }
64 }
65 };
66
67 let formatting_init = &builder.formatting_init;
68 quote! {
69 #init
70 #formatting_init
71 #preamble
72 #body
73 diag
74 }
75 });
76
77 let mut imp = structure.gen_impl(quote! {
79 gen impl<'_sess, G> rustc_errors::Diagnostic<'_sess, G> for @Self
80 where G: rustc_errors::EmissionGuarantee
81 {
82 #[track_caller]
83 fn into_diag(
84 self,
85 dcx: rustc_errors::DiagCtxtHandle<'_sess>,
86 level: rustc_errors::Level
87 ) -> rustc_errors::Diag<'_sess, G> {
88 #implementation
89 }
90 }
91 });
92 for test in slugs.borrow().iter().map(|s| generate_test(s, &structure)) {
93 imp.extend(test);
94 }
95 imp
96 }
97}
98
99pub(crate) struct LintDiagnosticDerive<'a> {
101 structure: Structure<'a>,
102}
103
104impl<'a> LintDiagnosticDerive<'a> {
105 pub(crate) fn new(structure: Structure<'a>) -> Self {
106 Self { structure }
107 }
108
109 pub(crate) fn into_tokens(self) -> TokenStream {
110 let LintDiagnosticDerive { mut structure } = self;
111 let kind = DiagnosticDeriveKind::LintDiagnostic;
112 let slugs = RefCell::new(Vec::new());
113 let implementation = kind.each_variant(&mut structure, |mut builder, variant| {
114 let preamble = builder.preamble(variant);
115 let body = builder.body(variant);
116
117 let primary_message = match builder.slug.value_ref() {
118 None => {
119 span_err(builder.span, "diagnostic slug not specified")
120 .help(
121 "specify the slug as the first argument to the attribute, such as \
122 `#[diag(compiletest_example)]`",
123 )
124 .emit();
125 DiagnosticDeriveError::ErrorHandled.to_compile_error()
126 }
127 Some(slug)
128 if let Some(Mismatch { slug_name, crate_name, slug_prefix }) =
129 Mismatch::check(slug) =>
130 {
131 span_err(slug.span().unwrap(), "diagnostic slug and crate name do not match")
132 .note(format!("slug is `{slug_name}` but the crate name is `{crate_name}`"))
133 .help(format!("expected a slug starting with `{slug_prefix}_...`"))
134 .emit();
135 DiagnosticDeriveError::ErrorHandled.to_compile_error()
136 }
137 Some(slug) => {
138 slugs.borrow_mut().push(slug.clone());
139 quote! {
140 diag.primary_message(crate::fluent_generated::#slug);
141 }
142 }
143 };
144
145 let formatting_init = &builder.formatting_init;
146 quote! {
147 #primary_message
148 #preamble
149 #formatting_init
150 #body
151 diag
152 }
153 });
154
155 let mut imp = structure.gen_impl(quote! {
156 gen impl<'__a> rustc_errors::LintDiagnostic<'__a, ()> for @Self {
157 #[track_caller]
158 fn decorate_lint<'__b>(
159 self,
160 diag: &'__b mut rustc_errors::Diag<'__a, ()>
161 ) {
162 #implementation;
163 }
164 }
165 });
166 for test in slugs.borrow().iter().map(|s| generate_test(s, &structure)) {
167 imp.extend(test);
168 }
169
170 imp
171 }
172}
173
174struct Mismatch {
175 slug_name: String,
176 crate_name: String,
177 slug_prefix: String,
178}
179
180impl Mismatch {
181 fn check(slug: &syn::Path) -> Option<Mismatch> {
183 let crate_name = std::env::var("CARGO_CRATE_NAME").ok()?;
185
186 let Some(("flux", slug_prefix)) = crate_name.split_once('-') else { return None };
188
189 let slug_name = slug.segments.first()?.ident.to_string();
190 if !slug_name.starts_with(slug_prefix) {
191 Some(Mismatch { slug_name, slug_prefix: slug_prefix.to_string(), crate_name })
192 } else {
193 None
194 }
195 }
196}
197
198fn generate_test(slug: &syn::Path, structure: &Structure<'_>) -> TokenStream {
201 for field in structure
203 .variants()
204 .iter()
205 .flat_map(|v| v.ast().fields.iter())
206 {
207 for attr_name in field.attrs.iter().filter_map(|at| at.path().get_ident()) {
208 if attr_name == "subdiagnostic" {
209 return quote!();
210 }
211 }
212 }
213 use std::sync::atomic::{AtomicUsize, Ordering};
214 static COUNTER: AtomicUsize = AtomicUsize::new(0);
217 let slug = slug.get_ident().unwrap();
218 let ident = quote::format_ident!("verify_{slug}_{}", COUNTER.fetch_add(1, Ordering::Relaxed));
219 let ref_slug = quote::format_ident!("{slug}_refs");
220 let struct_name = &structure.ast().ident;
221 let variables: Vec<_> = structure
222 .variants()
223 .iter()
224 .flat_map(|v| {
225 v.ast()
226 .fields
227 .iter()
228 .filter_map(|f| f.ident.as_ref().map(|i| i.to_string()))
229 })
230 .collect();
231 quote! {
233 #[cfg(test)]
234 #[test ]
235 fn #ident() {
236 let variables = [#(#variables),*];
237 for vref in crate::fluent_generated::#ref_slug {
238 assert!(variables.contains(vref), "{}: variable `{vref}` not found ({})", stringify!(#struct_name), stringify!(#slug));
239 }
240 }
241 }
242}