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