1use std::collections::{BTreeSet, HashMap};
2
3use proc_macro2::TokenStream;
4use quote::{format_ident, quote, ToTokens};
5use syn::{
6 spanned::Spanned, Attribute, Error, Ident, Index, Lit, Meta, MetaNameValue, Path, Result, Type,
7};
8use synstructure::{decl_derive, Structure};
9
10decl_derive!([IntoDiagnostic, attributes(file_id, message, render, note, primary, secondary)] => diagnostic_derive);
11
12#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)]
13enum FieldName {
14 Named(Ident),
15 Numbered(u32),
16}
17
18fn diagnostic_derive(s: Structure) -> Result<TokenStream> {
19 let file_id_attr = syn::parse_str("file_id")?;
20 let message_attr = syn::parse_str("message")?;
21 let render_attr = syn::parse_str("render")?;
22 let note_attr = syn::parse_str("note")?;
23 let primary_attr = syn::parse_str("primary")?;
24 let secondary_attr = syn::parse_str("secondary")?;
25 let primary_style: Path = syn::parse_str("Primary")?;
26 let secondary_style: Path = syn::parse_str("Secondary")?;
27
28 let struct_span = s.ast().span();
29
30 let mut file_id = None;
31
32 for attr in &s.ast().attrs {
33 if attr.path == file_id_attr {
34 if let Some((_, other_span)) = &file_id {
35 let mut err = Error::new(*other_span, "Duplicated #[file_id(...)] attribute");
36 err.combine(Error::new(attr.span(), "Second occurrence is here"));
37 return Err(err);
38 }
39
40 file_id = Some((attr.parse_args::<Type>()?, attr.span()));
41 } else if attr.path == message_attr
42 || attr.path == render_attr
43 || attr.path == note_attr
44 || attr.path == primary_attr
45 || attr.path == secondary_attr
46 {
47 return Err(Error::new(
48 attr.span(),
49 format!("Unexpected attribute `{}`", attr.path.to_token_stream()),
50 ));
51 }
52 }
53
54 let file_id = file_id
55 .ok_or_else(|| Error::new(struct_span, "Expected `#[file_id(Type)]` attribute"))?
56 .0;
57
58 let mut branches = vec![];
59
60 for v in s.variants() {
61 let members = match &v.ast().fields {
64 syn::Fields::Unit => HashMap::new(),
65 syn::Fields::Named(f) => f
66 .named
67 .iter()
68 .enumerate()
69 .map(|(i, field)| {
70 (
71 FieldName::Named(field.ident.as_ref().unwrap().clone()),
72 format_ident!("__binding_{}", i),
73 )
74 })
75 .collect(),
76 syn::Fields::Unnamed(f) => f
77 .unnamed
78 .iter()
79 .enumerate()
80 .map(|(i, _)| {
81 (
82 FieldName::Numbered(i as u32),
83 format_ident!("__binding_{}", i),
84 )
85 })
86 .collect(),
87 };
88
89 let members_ordered: Vec<_> = (0..members.len())
90 .map(|i| format_ident!("__binding_{}", i))
91 .collect();
92
93 let mut why = None;
96 let mut render = None;
99 let mut labels = vec![];
101 let mut notes = vec![];
103
104 for attr in v.ast().attrs.iter() {
105 if attr.path == message_attr {
106 if let Some((_, other_span)) = &why {
107 let mut err = Error::new(*other_span, "Duplicated #[message = ...] attribute");
108 err.combine(Error::new(attr.span(), "Second occurrence is here"));
109 return Err(err);
110 } else if let Some((_, other_span)) = &render {
111 let mut err = Error::new(
112 *other_span,
113 "#[message = ...] attribute not compatible with #[render(...)] attribute",
114 );
115 err.combine(Error::new(
116 attr.span(),
117 "#[render(...)] attribute occurs here",
118 ));
119 return Err(err);
120 }
121
122 why = Some((attr_to_format(attr, &members)?, attr.span()));
123 } else if attr.path == render_attr {
124 if let Some((_, other_span)) = &why {
125 let mut err = Error::new(
126 *other_span,
127 "#[message = ...] attribute not compatible with #[render(...)] attribute",
128 );
129 err.combine(Error::new(
130 attr.span(),
131 "#[message = ...] attribute occurs here",
132 ));
133 return Err(err);
134 } else if let Some((_, other_span)) = &render {
135 let mut err = Error::new(*other_span, "Duplicated #[render(...)] attribute");
136 err.combine(Error::new(attr.span(), "Second occurrence is here"));
137 return Err(err);
138 }
139
140 render = Some((attr_to_render(attr, &members_ordered)?, attr.span()));
141 } else if attr.path == note_attr {
142 let note = attr_to_format(attr, &members)?;
143 notes.push(note);
144 } else if attr.path == primary_attr
145 || attr.path == secondary_attr
146 || attr.path == file_id_attr
147 {
148 return Err(Error::new(
149 attr.span(),
150 format!("Unexpected attribute `{}`", attr.path.to_token_stream()),
151 ));
152 }
153 }
154
155 for b in v.bindings() {
156 let binding = &b.binding;
157
158 for attr in &b.ast().attrs {
159 if attr.path == primary_attr || attr.path == secondary_attr {
160 let style = if attr.path == primary_attr {
161 &primary_style
162 } else {
163 &secondary_style
164 };
165
166 let label = match attr.parse_meta()? {
167 Meta::Path(_) => {
168 quote! {
169 ::codespan_derive::IntoLabel::into_label( #binding, ::codespan_derive::LabelStyle::#style )
170 }
171 }
172 Meta::NameValue(MetaNameValue { .. }) => {
173 let message = attr_to_format(&attr, &members)?;
174
175 quote! {
176 ::codespan_derive::IntoLabel::into_label( #binding, ::codespan_derive::LabelStyle::#style )
177 .with_message( #message )
178 }
179 }
180 _ => return Err(Error::new(attr.span(),
181 format!("Expected `span` attribute to be of the form: `#[span]` or `#[span = \"Message...\"]`"))),
182 };
183
184 labels.push(label);
185 } else if attr.path == message_attr
186 || attr.path == render_attr
187 || attr.path == note_attr
188 || attr.path == file_id_attr
189 {
190 return Err(Error::new(
191 attr.span(),
192 format!("Unexpected attribute `{}`", attr.path.to_token_stream()),
193 ));
194 }
195 }
196 }
197
198 let pat = v.pat();
199
200 if let Some((why, _)) = why {
201 branches.push(quote! {
202 #pat => {
203 ::codespan_derive::Diagnostic::< #file_id >::error()
204 .with_message( #why )
205 .with_labels(vec![ #(#labels),* ])
206 .with_notes(vec![ #(#notes),* ])
207 }
208 });
209 } else if let Some((render, _)) = render {
210 branches.push(quote! {
211 #pat => {
212 #render
213 .with_labels(vec![ #(#labels),* ])
214 .with_notes(vec![ #(#notes),* ])
215 }
216 });
217 } else {
218 return Err(Error::new(
219 v.ast().ident.span(),
220 "Expected `#[message = \"Message...\"]` or `#[render(function)]` attribute",
221 ));
222 }
223 }
224
225 Ok(s.gen_impl(quote! {
226 gen impl ::codespan_derive::IntoDiagnostic for @Self {
227 type FileId = #file_id ;
228
229 #[allow(dead_code)]
230 fn into_diagnostic(&self) -> ::codespan_derive::Diagnostic::< #file_id > {
231 match self {
232 #(#branches),*
233 _ => { panic!("Uninhabited type cannot be turned into a Diagnostic") }
234 }
235 }
236 }
237 }))
238}
239
240fn attr_to_format(attr: &Attribute, members: &HashMap<FieldName, Ident>) -> Result<TokenStream> {
242 match attr.parse_meta()? {
243 Meta::NameValue(MetaNameValue {
244 lit: Lit::Str(msg), ..
245 }) => {
246 let msg_span = msg.span();
247 let mut msg: &str = &msg.value();
248
249 let mut idents = BTreeSet::new();
250 let mut out = String::new();
251
252 while !msg.is_empty() {
253 if let Some(i) = msg.find(&['{', '}'][..]) {
254 out += &msg[..i];
255
256 if msg[i..].starts_with("{{") {
257 out += "{{";
258 msg = &msg[i + 2..];
259 } else if msg[i..].starts_with("}}") {
260 out += "}}";
261 msg = &msg[i + 2..];
262 } else if msg[i..].starts_with('}') {
263 return Err(Error::new(msg_span, "Unterminated `}` in format string"));
264 } else {
265 msg = &msg[i + 1..];
266
267 if let Some(j) = msg.find('}') {
268 let (field, rest) = if let Some(k) = msg[0..j].find(":") {
269 (&msg[0..k], Some(&msg[k..j]))
270 } else {
271 (&msg[0..j], None)
272 };
273
274 msg = &msg[j + 1..];
276
277 let member = if let Ok(ident) = syn::parse_str::<Ident>(field) {
278 FieldName::Named(ident)
279 } else if let Ok(num) = syn::parse_str::<Index>(field) {
280 FieldName::Numbered(num.index)
281 } else {
282 return Err(Error::new(
283 msg_span,
284 format!(
285 "Expected either a struct member name or index, got `{}`",
286 field
287 ),
288 ));
289 };
290
291 out += "{";
292
293 if let Some(ident) = members.get(&member) {
294 out += &ident.to_string();
295 idents.insert(ident.clone());
296 } else {
297 return Err(Error::new(
298 msg_span,
299 format!(
300 "Struct member name or index `{}` is not a valid field",
301 field
302 ),
303 ));
304 }
305
306 if let Some(rest) = rest {
307 out += rest;
308 }
309
310 out += "}";
311 } else {
312 return Err(Error::new(msg_span, "Unterminated `{` in format string"));
313 }
314 }
315 } else {
316 out += msg;
317 msg = "";
318 }
319 }
320
321 Ok(quote! {
322 format!(#out, #(#idents = #idents),*)
323 })
324 }
325 _ => Err(Error::new(
326 attr.span(),
327 format!(
328 "Expected {name} attribute to be of the form: `#[{name} = \"FormatString\"]`",
329 name = attr.path.to_token_stream()
330 ),
331 )),
332 }
333}
334
335fn attr_to_render(attr: &Attribute, members: &Vec<Ident>) -> Result<TokenStream> {
336 let path: Path = attr.parse_args()?;
337
338 Ok(quote! {
339 #path (#(#members),*)
340 })
341}