1use proc_macro2::TokenStream;
2use quote::{format_ident, quote};
3use syn::{
4 parenthesized,
5 parse::{Parse, ParseStream},
6 spanned::Spanned,
7 Token,
8};
9
10use crate::{
11 diagnostic::{DiagnosticConcreteArgs, DiagnosticDef},
12 fmt::{self, Display},
13 forward::WhichFn,
14 utils::{display_pat_members, gen_all_variants_with},
15};
16
17pub struct Labels(Vec<Label>);
18
19#[derive(PartialEq, Eq)]
20enum LabelType {
21 Default,
22 Primary,
23 Collection,
24}
25
26struct Label {
27 label: Option<Display>,
28 ty: syn::Type,
29 span: syn::Member,
30 lbl_ty: LabelType,
31}
32
33struct LabelAttr {
34 label: Option<Display>,
35 lbl_ty: LabelType,
36}
37
38impl Parse for LabelAttr {
39 fn parse(input: ParseStream) -> syn::Result<Self> {
40 let _ = input.step(|cursor| {
45 if let Some((_, next)) = cursor.token_tree() {
46 Ok(((), next))
47 } else {
48 Err(cursor.error("unexpected empty attribute"))
49 }
50 });
51 let la = input.lookahead1();
52 let (lbl_ty, label) = if la.peek(syn::token::Paren) {
53 let content;
55 parenthesized!(content in input);
56
57 let attr = match content.parse::<Option<syn::Ident>>()? {
58 Some(ident) if ident == "primary" => {
59 let _ = content.parse::<Token![,]>();
60 LabelType::Primary
61 }
62 Some(ident) if ident == "collection" => {
63 let _ = content.parse::<Token![,]>();
64 LabelType::Collection
65 }
66 Some(_) => {
67 return Err(syn::Error::new(input.span(), "Invalid argument to label() attribute. The argument must be a literal string or either the keyword `primary` or `collection`."));
68 }
69 _ => LabelType::Default,
70 };
71
72 if content.peek(syn::LitStr) {
73 let fmt = content.parse()?;
74 let args = if content.is_empty() {
75 TokenStream::new()
76 } else {
77 fmt::parse_token_expr(&content, false)?
78 };
79 let display = Display {
80 fmt,
81 args,
82 has_bonus_display: false,
83 };
84 (attr, Some(display))
85 } else if !content.is_empty() {
86 return Err(syn::Error::new(input.span(), "Invalid argument to label() attribute. The argument must be a literal string or either the keyword `primary` or `collection`."));
87 } else {
88 (attr, None)
89 }
90 } else if la.peek(Token![=]) {
91 input.parse::<Token![=]>()?;
93 (
94 LabelType::Default,
95 Some(Display {
96 fmt: input.parse()?,
97 args: TokenStream::new(),
98 has_bonus_display: false,
99 }),
100 )
101 } else {
102 (LabelType::Default, None)
103 };
104 Ok(LabelAttr { label, lbl_ty })
105 }
106}
107
108impl Labels {
109 pub fn from_fields(fields: &syn::Fields) -> syn::Result<Option<Self>> {
110 match fields {
111 syn::Fields::Named(named) => Self::from_fields_vec(named.named.iter().collect()),
112 syn::Fields::Unnamed(unnamed) => {
113 Self::from_fields_vec(unnamed.unnamed.iter().collect())
114 }
115 syn::Fields::Unit => Ok(None),
116 }
117 }
118
119 fn from_fields_vec(fields: Vec<&syn::Field>) -> syn::Result<Option<Self>> {
120 let mut labels = Vec::new();
121 for (i, field) in fields.iter().enumerate() {
122 for attr in &field.attrs {
123 if attr.path().is_ident("label") {
124 let span = if let Some(ident) = field.ident.clone() {
125 syn::Member::Named(ident)
126 } else {
127 syn::Member::Unnamed(syn::Index {
128 index: i as u32,
129 span: field.span(),
130 })
131 };
132 use quote::ToTokens;
133 let LabelAttr { label, lbl_ty } =
134 syn::parse2::<LabelAttr>(attr.meta.to_token_stream())?;
135
136 if lbl_ty == LabelType::Primary
137 && labels
138 .iter()
139 .any(|l: &Label| l.lbl_ty == LabelType::Primary)
140 {
141 return Err(syn::Error::new(
142 field.span(),
143 "Cannot have more than one primary label.",
144 ));
145 }
146
147 labels.push(Label {
148 label,
149 span,
150 ty: field.ty.clone(),
151 lbl_ty,
152 });
153 }
154 }
155 }
156 if labels.is_empty() {
157 Ok(None)
158 } else {
159 Ok(Some(Labels(labels)))
160 }
161 }
162
163 pub(crate) fn gen_struct(&self, fields: &syn::Fields) -> Option<TokenStream> {
164 let (display_pat, display_members) = display_pat_members(fields);
165 let labels = self.0.iter().filter_map(|highlight| {
166 let Label {
167 span,
168 label,
169 ty,
170 lbl_ty,
171 } = highlight;
172 if *lbl_ty == LabelType::Collection {
173 return None;
174 }
175 let var = quote! { __miette_internal_var };
176 let display = if let Some(display) = label {
177 let (fmt, args) = display.expand_shorthand_cloned(&display_members);
178 quote! { std::option::Option::Some(format!(#fmt #args)) }
179 } else {
180 quote! { std::option::Option::None }
181 };
182 let ctor = if *lbl_ty == LabelType::Primary {
183 quote! { miette::LabeledSpan::new_primary_with_span }
184 } else {
185 quote! { miette::LabeledSpan::new_with_span }
186 };
187
188 Some(quote! {
189 miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(&self.#span)
190 .map(|#var| #ctor(
191 #display,
192 #var.clone(),
193 ))
194 })
195 });
196 let collections_chain = self.0.iter().filter_map(|label| {
197 let Label {
198 span,
199 label,
200 ty: _,
201 lbl_ty,
202 } = label;
203 if *lbl_ty != LabelType::Collection {
204 return None;
205 }
206 let display = if let Some(display) = label {
207 let (fmt, args) = display.expand_shorthand_cloned(&display_members);
208 quote! { std::option::Option::Some(format!(#fmt #args)) }
209 } else {
210 quote! { std::option::Option::None }
211 };
212 Some(quote! {
213 .chain({
214 let display = #display;
215 self.#span.iter().map(move |span| {
216 use miette::macro_helpers::{ToLabelSpanWrapper,ToLabeledSpan};
217 let mut labeled_span = ToLabelSpanWrapper::to_labeled_span(span.clone());
218 if display.is_some() && labeled_span.label().is_none() {
219 labeled_span.set_label(display.clone())
220 }
221 Some(labeled_span)
222 })
223 })
224 })
225 });
226
227 Some(quote! {
228 #[allow(unused_variables)]
229 fn labels(&self) -> std::option::Option<std::boxed::Box<dyn std::iter::Iterator<Item = miette::LabeledSpan> + '_>> {
230 use miette::macro_helpers::ToOption;
231 let Self #display_pat = self;
232
233 let labels_iter = vec![
234 #(#labels),*
235 ]
236 .into_iter()
237 #(#collections_chain)*;
238
239 std::option::Option::Some(Box::new(labels_iter.filter(Option::is_some).map(Option::unwrap)))
240 }
241 })
242 }
243
244 pub(crate) fn gen_enum(variants: &[DiagnosticDef]) -> Option<TokenStream> {
245 gen_all_variants_with(
246 variants,
247 WhichFn::Labels,
248 |ident, fields, DiagnosticConcreteArgs { labels, .. }| {
249 let (display_pat, display_members) = display_pat_members(fields);
250 labels.as_ref().and_then(|labels| {
251 let variant_labels = labels.0.iter().filter_map(|label| {
252 let Label { span, label, ty, lbl_ty } = label;
253 if *lbl_ty == LabelType::Collection {
254 return None;
255 }
256 let field = match &span {
257 syn::Member::Named(ident) => ident.clone(),
258 syn::Member::Unnamed(syn::Index { index, .. }) => {
259 format_ident!("_{}", index)
260 }
261 };
262 let var = quote! { __miette_internal_var };
263 let display = if let Some(display) = label {
264 let (fmt, args) = display.expand_shorthand_cloned(&display_members);
265 quote! { std::option::Option::Some(format!(#fmt #args)) }
266 } else {
267 quote! { std::option::Option::None }
268 };
269 let ctor = if *lbl_ty == LabelType::Primary {
270 quote! { miette::LabeledSpan::new_primary_with_span }
271 } else {
272 quote! { miette::LabeledSpan::new_with_span }
273 };
274
275 Some(quote! {
276 miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(#field)
277 .map(|#var| #ctor(
278 #display,
279 #var.clone(),
280 ))
281 })
282 });
283 let collections_chain = labels.0.iter().filter_map(|label| {
284 let Label { span, label, ty: _, lbl_ty } = label;
285 if *lbl_ty != LabelType::Collection {
286 return None;
287 }
288 let field = match &span {
289 syn::Member::Named(ident) => ident.clone(),
290 syn::Member::Unnamed(syn::Index { index, .. }) => {
291 format_ident!("_{}", index)
292 }
293 };
294 let display = if let Some(display) = label {
295 let (fmt, args) = display.expand_shorthand_cloned(&display_members);
296 quote! { std::option::Option::Some(format!(#fmt #args)) }
297 } else {
298 quote! { std::option::Option::None }
299 };
300 Some(quote! {
301 .chain({
302 let display = #display;
303 #field.iter().map(move |span| {
304 use miette::macro_helpers::{ToLabelSpanWrapper,ToLabeledSpan};
305 let mut labeled_span = ToLabelSpanWrapper::to_labeled_span(span.clone());
306 if display.is_some() && labeled_span.label().is_none() {
307 labeled_span.set_label(display.clone());
308 }
309 Some(labeled_span)
310 })
311 })
312 })
313 });
314 let variant_name = ident.clone();
315 match &fields {
316 syn::Fields::Unit => None,
317 _ => Some(quote! {
318 Self::#variant_name #display_pat => {
319 use miette::macro_helpers::ToOption;
320 let labels_iter = vec![
321 #(#variant_labels),*
322 ]
323 .into_iter()
324 #(#collections_chain)*;
325 std::option::Option::Some(std::boxed::Box::new(labels_iter.filter(Option::is_some).map(Option::unwrap)))
326 }
327 }),
328 }
329 })
330 },
331 )
332 }
333}