1mod emit;
9
10use emit::{Alternative, Body, Field, Only, Part, Ty, Variant};
11use proc_macro::TokenStream;
12use proc_macro2::Span;
13use syn::ext::IdentExt;
14use syn::spanned::Spanned;
15use syn::{Attribute, Data, DeriveInput, Error, Fields, GenericParam, LitStr, Result, Type};
16
17#[proc_macro_derive(Debuggable, attributes(debuggable))]
19pub fn derive_debuggable(input: TokenStream) -> TokenStream {
20 let input = syn::parse_macro_input!(input as DeriveInput);
21 match expand(&input) {
22 Ok(tokens) => tokens.into(),
23 Err(e) => e.to_compile_error().into(),
24 }
25}
26
27fn expand(input: &DeriveInput) -> Result<proc_macro2::TokenStream> {
28 let ty = model(input)?;
29 Ok(emit::entry_call(&ty).parse().expect("generated tokens are valid"))
30}
31
32#[derive(Clone, Copy, PartialEq)]
36enum Place {
37 Struct,
38 Enum,
39 Variant,
40 StructField,
41 VariantField,
42}
43
44#[derive(Default)]
45struct Opts {
46 summary: Option<LitStr>,
47 hide: Option<Span>,
48 rename: Option<LitStr>,
49 items: Option<Span>,
50 text: Option<Span>,
51 len: Option<LitStr>,
52 only: Option<LitStr>,
53 value: Option<LitStr>,
54 alternatives: Vec<(LitStr, Option<LitStr>)>,
56}
57
58fn opts(attrs: &[Attribute], place: Place) -> Result<Opts> {
59 let mut o = Opts::default();
60 let on_type = matches!(place, Place::Struct | Place::Enum);
61 for attr in attrs.iter().filter(|a| a.path().is_ident("debuggable")) {
62 let (mut alt_items, mut alt_len): (Option<LitStr>, Option<LitStr>) = (None, None);
64 attr.parse_nested_meta(|m| {
65 let key = m.path.get_ident().map(|i| i.to_string()).unwrap_or_default();
66 let span = m.path.span();
67 let allowed = match key.as_str() {
68 "summary" => matches!(place, Place::Struct | Place::Variant),
69 "hide" | "rename" => matches!(place, Place::StructField | Place::VariantField),
70 "items" | "len" if on_type => true,
71 "items" | "text" | "len" | "only" | "value" => place == Place::StructField,
72 _ => {
73 const OPTIONS: [&str; 8] = ["summary", "hide", "rename", "items", "text", "len", "only", "value"];
74 return Err(m.error(match closest(&key, OPTIONS) {
75 Some(best) => format!("unknown `debuggable` option `{key}`; did you mean `{best}`?"),
76 None => "unknown `debuggable` option; expected one of: `summary`, `hide`, `rename`, `items`, `text`, `len`, `only`, `value`"
77 .to_string(),
78 }));
79 }
80 };
81 if !allowed {
82 return Err(Error::new(span, misplaced(&key, place)));
83 }
84 let flag = |m: &syn::meta::ParseNestedMeta, set: &mut Option<Span>| -> Result<()> {
85 if !m.input.is_empty() && !m.input.peek(syn::Token![,]) {
86 return Err(m.error(format!("`{key}` takes no value")));
87 }
88 if set.replace(span).is_some() {
89 return Err(Error::new(span, format!("duplicate `{key}`")));
90 }
91 Ok(())
92 };
93 let value = |m: &syn::meta::ParseNestedMeta, set: &mut Option<LitStr>| -> Result<()> {
94 let lit: LitStr = m.value()?.parse().map_err(|_| m.error(format!("expected `{key} = \"...\"`")))?;
95 if set.replace(lit).is_some() {
96 return Err(Error::new(span, format!("duplicate `{key}`")));
97 }
98 Ok(())
99 };
100 if on_type {
101 let has_value = m.input.peek(syn::Token![=]);
102 return match key.as_str() {
103 "items" if !has_value => Err(m.error("on a type, write `items = \"path\"` (one attribute per place the elements can be)")),
104 "items" => value(&m, &mut alt_items),
105 "len" => value(&m, &mut alt_len),
106 _ => value(&m, &mut o.summary),
107 };
108 }
109 if key == "items" && m.input.peek(syn::Token![=]) {
110 return Err(m.error("on a field, write `items` without a value (`items = \"path\"` goes on the type)"));
111 }
112 match key.as_str() {
113 "summary" => value(&m, &mut o.summary),
114 "rename" => value(&m, &mut o.rename),
115 "len" => value(&m, &mut o.len),
116 "only" => value(&m, &mut o.only),
117 "value" => value(&m, &mut o.value),
118 "hide" => flag(&m, &mut o.hide),
119 "text" => flag(&m, &mut o.text),
120 _ => flag(&m, &mut o.items),
121 }
122 })?;
123 match (alt_items, alt_len) {
124 (Some(items), len) => o.alternatives.push((items, len)),
125 (None, Some(len)) => {
126 return Err(Error::new(len.span(), "`len` on a type needs `items = \"...\"` in the same attribute"))
127 }
128 (None, None) => {}
129 }
130 }
131 Ok(o)
132}
133
134fn misplaced(key: &str, place: Place) -> String {
135 match (key, place) {
136 ("summary", Place::Enum) => {
137 "`summary` on an enum goes on each variant: `#[debuggable(summary = \"...\")]` above the variant".into()
138 }
139 ("summary", _) => "`summary` is allowed on a struct or an enum variant, not on a field".into(),
140 ("items" | "text" | "len" | "only" | "value", Place::VariantField) => {
141 format!("`{key}` is not supported inside enum variants")
142 }
143 (_, Place::Struct | Place::Enum | Place::Variant) => format!("`{key}` is allowed on fields only"),
144 _ => format!("`{key}` is not allowed here"),
145 }
146}
147
148fn is_phantom(ty: &Type) -> bool {
151 matches!(ty, Type::Path(p) if p.qself.is_none() && p.path.segments.last().is_some_and(|s| s.ident == "PhantomData"))
152}
153
154fn fields(fields: &Fields, place: Place) -> Result<Vec<Field>> {
156 let mut out = Vec::new();
157 let mut items_seen = false;
158 for (i, f) in fields.iter().enumerate() {
159 let o = opts(&f.attrs, place)?;
160 if let (Some(_), Some(rename)) = (o.hide, &o.rename) {
161 return Err(Error::new(rename.span(), "a hidden field can't be renamed"));
162 }
163 if let (Some(_), Some(items)) = (o.hide, o.items) {
164 return Err(Error::new(items, "`items` already replaces the field with its elements; remove `hide`"));
165 }
166 if let (Some(_), Some(text)) = (o.items, o.text) {
167 return Err(Error::new(text, "a field can't have both `items` and `text`"));
168 }
169 for (lit, key) in [(&o.only, "only"), (&o.value, "value")] {
170 if let (Some(lit), None) = (lit, o.items) {
171 return Err(Error::new(lit.span(), format!("`{key}` needs `items` on the same field")));
172 }
173 }
174 if let (Some(len), None, None) = (&o.len, o.items, o.text) {
175 return Err(Error::new(len.span(), "`len` needs `items` or `text` on the same field"));
176 }
177 if let Some(items) = o.items {
178 if items_seen {
179 return Err(Error::new(items, "`items` may be used on only one field"));
180 }
181 items_seen = true;
182 }
183 if let Some(r) = &o.rename {
184 if r.value().is_empty() {
185 return Err(Error::new(r.span(), "`rename` needs a non-empty name"));
186 }
187 }
188 out.push(Field {
189 name: f.ident.as_ref().map(|id| id.unraw().to_string()).unwrap_or_else(|| i.to_string()),
190 hide: o.hide.is_some() || is_phantom(&f.ty),
191 rename: o.rename.map(|r| r.value()),
192 items: o.items.is_some(),
193 text: o.text.is_some(),
194 only: o.only.as_ref().map(parse_only).transpose()?,
195 value: o.value.as_ref().map(|v| parse_path(v, "value")).transpose()?,
196 len: o.len.map(|l| (l.value(), l.span())),
197 });
198 }
199 for f in &out {
200 if let Some((len, span)) = &f.len {
201 if !out.iter().any(|g| &g.name == len) {
202 return Err(Error::new(*span, unknown_field(len, &out, "`len`")));
203 }
204 }
205 }
206 Ok(out)
207}
208
209fn parse_path(lit: &LitStr, what: &str) -> Result<Vec<String>> {
211 parse_path_str(&lit.value(), lit, what)
212}
213
214fn parse_path_str(s: &str, lit: &LitStr, what: &str) -> Result<Vec<String>> {
215 let err = |msg: String| Error::new(lit.span(), msg);
216 if s.trim().is_empty() {
217 return Err(err(format!("`{what}` expects a field or variant name, like `\"value\"` or `\"u.value\"`")));
218 }
219 let mut out = Vec::new();
220 for seg in s.split('.') {
221 let seg = seg.trim();
222 if seg.is_empty() {
223 return Err(err(format!("empty segment in `{what}` path `{s}`")));
224 }
225 if let Some((_, rest)) = seg.split_once(char::is_whitespace) {
226 return Err(err(format!("unexpected `{}` in `{what}`", rest.trim())));
227 }
228 let seg = seg.strip_prefix("r#").unwrap_or(seg);
229 let is_ident = syn::parse::Parser::parse_str(syn::Ident::parse_any, seg).is_ok();
230 if !(is_ident || seg.bytes().all(|b| b.is_ascii_digit())) {
231 return Err(err(format!("`{seg}` in `{what}` is not a field name")));
232 }
233 out.push(seg.to_string());
234 }
235 Ok(out)
236}
237
238fn parse_only(lit: &LitStr) -> Result<Only> {
240 let s = lit.value();
241 let err = |msg: String| Error::new(lit.span(), msg);
242 let (path, mask) = match s.split_once('&') {
243 None => (s.as_str(), None),
244 Some((path, mask)) => {
245 let mask = mask.trim();
246 if mask.is_empty() {
247 return Err(err("`only` expects a mask after `&`, like `\"version & 1\"`".into()));
248 }
249 let parsed = match mask.strip_prefix("0x").or_else(|| mask.strip_prefix("0X")) {
250 Some(hex) => u64::from_str_radix(&hex.replace('_', ""), 16),
251 None => mask.replace('_', "").parse::<u64>(),
252 };
253 let n = parsed.map_err(|_| err(format!("`only` mask `{mask}` is not an integer")))?;
254 if n == 0 {
255 return Err(err("`only` mask must be non-zero".into()));
256 }
257 if n > (1 << 53) - 1 {
258 return Err(err("`only` mask must be at most 2^53 - 1".into()));
259 }
260 (path, Some(n))
261 }
262 };
263 if path.trim().is_empty() {
264 return Err(err("`only` expects a field or variant name, optionally `& mask`: `\"version & 1\"`".into()));
265 }
266 Ok(Only { path: parse_path_str(path, lit, "only")?, mask })
267}
268
269fn closest<'a>(name: &str, candidates: impl IntoIterator<Item = &'a str>) -> Option<&'a str> {
272 fn distance(a: &str, b: &str) -> usize {
275 let (a, b): (Vec<char>, Vec<char>) = (a.chars().collect(), b.chars().collect());
276 let mut d: Vec<Vec<usize>> = (0..=a.len()).map(|i| vec![i; b.len() + 1]).collect();
277 d[0] = (0..=b.len()).collect();
278 for i in 1..=a.len() {
279 for j in 1..=b.len() {
280 let cost = usize::from(a[i - 1] != b[j - 1]);
281 d[i][j] = (d[i - 1][j - 1] + cost).min(d[i - 1][j] + 1).min(d[i][j - 1] + 1);
282 if i > 1 && j > 1 && a[i - 1] == b[j - 2] && a[i - 2] == b[j - 1] {
283 d[i][j] = d[i][j].min(d[i - 2][j - 2] + 1);
284 }
285 }
286 }
287 d[a.len()][b.len()]
288 }
289 let limit = (name.chars().count() / 3).max(1);
290 candidates
291 .into_iter()
292 .map(|c| (distance(name, c), c))
293 .filter(|&(d, _)| d <= limit && d < name.chars().count())
294 .min()
295 .map(|(_, c)| c)
296}
297
298fn unknown_field(name: &str, fields: &[Field], what: &str) -> String {
299 if fields.is_empty() {
300 return format!("{what} refers to `{name}`, but there are no fields");
301 }
302 if let Some(best) = closest(name, fields.iter().map(|f| f.name.as_str())) {
303 return format!("{what} refers to unknown field `{name}`; did you mean `{best}`?");
304 }
305 let names: Vec<String> = fields.iter().map(|f| format!("`{}`", f.name)).collect();
306 format!("{what} refers to unknown field `{name}`; available: {}", names.join(", "))
307}
308
309fn summary(lit: &LitStr, fields: &[Field], has_items: bool) -> Result<Vec<Part>> {
311 let s = lit.value();
312 let err = |msg: String| Error::new(lit.span(), msg);
313 let mut parts = Vec::new();
314 let mut text = String::new();
315 let mut chars = s.chars().peekable();
316 while let Some(c) = chars.next() {
317 match c {
318 '{' if chars.peek() == Some(&'{') => {
319 chars.next();
320 text.push('{');
321 }
322 '}' if chars.peek() == Some(&'}') => {
323 chars.next();
324 text.push('}');
325 }
326 '{' if chars.peek() == Some(&'#') => {
327 chars.next();
328 if chars.next() != Some('}') {
329 return Err(err("`{#` must be followed by `}`: `{#}` is the number of elements".into()));
330 }
331 if !has_items {
332 return Err(err("`{#}` counts elements, but this type has no `items`".into()));
333 }
334 if !text.is_empty() {
335 parts.push(Part::Lit(std::mem::take(&mut text)));
336 }
337 parts.push(Part::Count);
338 }
339 '{' => {
340 let mut name = String::new();
341 loop {
342 match chars.next() {
343 Some('}') => break,
344 Some(c) if c.is_alphanumeric() || c == '_' => name.push(c),
345 Some(':') => {
346 return Err(err(format!(
347 "format specs are not supported in summaries: use `{{{name}}}`"
348 )))
349 }
350 Some(c) => {
351 return Err(err(format!(
352 "`{c}` in `{{{name}...}}`: summaries can only refer to fields by name, like `{{len}}` or `{{0}}`"
353 )))
354 }
355 None => return Err(err("unclosed `{` in summary (write `{{` for a literal brace)".into())),
356 }
357 }
358 if name.is_empty() {
359 return Err(err("empty `{}` in summary: name a field, like `{len}`".into()));
360 }
361 let name = name.strip_prefix("r#").map(str::to_string).unwrap_or(name);
362 if !fields.iter().any(|f| f.name == name) {
363 return Err(err(unknown_field(&name, fields, "summary")));
364 }
365 if !text.is_empty() {
366 parts.push(Part::Lit(std::mem::take(&mut text)));
367 }
368 parts.push(Part::Field(name));
369 }
370 '}' => return Err(err("unmatched `}` in summary (write `}}` for a literal brace)".into())),
371 c => text.push(c),
372 }
373 }
374 if !text.is_empty() {
375 parts.push(Part::Lit(text));
376 }
377 Ok(parts)
378}
379
380fn model(input: &DeriveInput) -> Result<Ty> {
381 let generic = input.generics.params.iter().any(|p| !matches!(p, GenericParam::Lifetime(_)));
382 let (summary_lit, body, alts) = match &input.data {
383 Data::Struct(s) => {
384 let o = opts(&input.attrs, Place::Struct)?;
385 let fs = fields(&s.fields, Place::StructField)?;
386 let alts = alternatives(&o.alternatives)?;
387 if let (Some((lit, _)), true) = (o.alternatives.first(), fs.iter().any(|f| f.items)) {
388 return Err(Error::new(
389 lit.span(),
390 "use either field-level `items` or `items = \"...\"` on the type, not both",
391 ));
392 }
393 for (alt, (lit, len)) in alts.iter().zip(&o.alternatives) {
394 check_struct_path(&alt.items, lit, &fs, "`items`")?;
395 if let (Some(path), Some(len)) = (&alt.len, len) {
396 check_struct_path(path, len, &fs, "`len`")?;
397 }
398 }
399 (o.summary, Body::Struct(fs), alts)
400 }
401 Data::Enum(e) => {
402 let o = opts(&input.attrs, Place::Enum)?;
403 let alts = alternatives(&o.alternatives)?;
404 let mut variants = Vec::new();
405 for v in &e.variants {
406 let o = opts(&v.attrs, Place::Variant)?;
407 let fs = fields(&v.fields, Place::VariantField)?;
408 let summary = o.summary.as_ref().map(|lit| summary(lit, &fs, !alts.is_empty())).transpose()?;
409 variants.push(Variant { name: v.ident.unraw().to_string(), summary, fields: fs });
410 }
411 for (alt, (lit, len)) in alts.iter().zip(&o.alternatives) {
412 check_enum_path(&alt.items, lit, &variants)?;
413 if let (Some(path), Some(len)) = (&alt.len, len) {
414 check_enum_path(path, len, &variants)?;
415 }
416 }
417 (None, Body::Enum(variants), alts)
418 }
419 Data::Union(u) => {
420 return Err(Error::new(u.union_token.span, "`Debuggable` can't be derived for unions"));
421 }
422 };
423 let summary = match (&summary_lit, &body) {
424 (Some(lit), Body::Struct(fs)) => Some(summary(lit, fs, !alts.is_empty() || fs.iter().any(|f| f.items))?),
425 _ => None,
426 };
427 Ok(Ty { name: input.ident.unraw().to_string(), generic, summary, body, alternatives: alts })
428}
429
430fn alternatives(raw: &[(LitStr, Option<LitStr>)]) -> Result<Vec<Alternative>> {
432 raw.iter()
433 .map(|(items, len)| {
434 Ok(Alternative {
435 items: parse_path(items, "items")?,
436 len: len.as_ref().map(|l| parse_path(l, "len")).transpose()?,
437 })
438 })
439 .collect()
440}
441
442fn check_struct_path(path: &[String], lit: &LitStr, fields: &[Field], what: &str) -> Result<()> {
444 if fields.iter().any(|f| f.name == path[0]) {
445 return Ok(());
446 }
447 Err(Error::new(lit.span(), unknown_field(&path[0], fields, what)))
448}
449
450fn check_enum_path(path: &[String], lit: &LitStr, variants: &[Variant]) -> Result<()> {
452 let Some(v) = variants.iter().find(|v| v.name == path[0]) else {
453 let msg = match closest(&path[0], variants.iter().map(|v| v.name.as_str())) {
454 Some(best) => format!("unknown variant `{}`; did you mean `{best}`?", path[0]),
455 None => {
456 let names: Vec<String> = variants.iter().map(|v| format!("`{}`", v.name)).collect();
457 format!("unknown variant `{}`; the enum has {}", path[0], names.join(", "))
458 }
459 };
460 return Err(Error::new(lit.span(), msg));
461 };
462 match path.get(1) {
463 Some(field) if !v.fields.iter().any(|f| &f.name == field) => {
464 let mut msg = format!("variant `{}` has no field `{field}`", v.name);
465 if let Some(best) = closest(field, v.fields.iter().map(|f| f.name.as_str())) {
466 msg.push_str(&format!("; did you mean `{best}`?"));
467 }
468 Err(Error::new(lit.span(), msg))
469 }
470 _ => Ok(()),
471 }
472}
473
474#[rustfmt::skip]
476#[cfg(test)]
477mod tests {
478 use super::*;
479
480 fn json(src: &str) -> String {
481 let input: DeriveInput = syn::parse_str(src).unwrap();
482 let call = emit::entry_call(&model(&input).unwrap());
483 let lit: LitStr = syn::parse_str(call.rsplit_once(", ").unwrap().1.trim_end_matches(");")).unwrap();
485 lit.value()
486 }
487
488 fn error(src: &str) -> String {
489 let input: DeriveInput = syn::parse_str(src).unwrap();
490 match model(&input) {
491 Ok(_) => panic!("expected an error for: {src}"),
492 Err(e) => e.to_string(),
493 }
494 }
495
496 #[test]
497 fn struct_with_everything() {
498 assert_eq!(
499 json(r#"#[debuggable(summary = "{len} items")] struct M<K, V> {
500 #[debuggable(items, len = "len")] slots: Vec<V>,
501 #[debuggable(hide)] free_head: u32,
502 len: u32,
503 _k: std::marker::PhantomData<K>,
504 #[debuggable(rename = "count")] n: u8,
505 }"#),
506 r#""generic":true,"kind":"struct","summary":[["field","len"],["lit"," items"]],"hide":["free_head","_k"],"rename":{"n":"count"},"items":{"field":"slots","len":"len"}}"#
507 );
508 }
509
510 #[test]
511 fn text_fields() {
512 assert_eq!(
513 json(r#"#[debuggable(summary = "{xs}")] struct S<const N: usize> {
514 #[debuggable(hide)] len: u8,
515 #[debuggable(text, len = "len", hide)] xs: [u8; N],
516 #[debuggable(text)] tag: [u8; 4],
517 }"#),
518 r#""generic":true,"kind":"struct","summary":[["field","xs"]],"hide":["len","xs"],"text":[{"field":"xs","len":"len"},{"field":"tag"}]}"#
519 );
520 }
521
522 #[test]
523 fn slots_fields() {
524 assert_eq!(
525 json(r#"#[debuggable(summary = "{len} items")] struct S<T> {
526 #[debuggable(items, len = "n", only = "version & 0x1", value = "u.r#value")] slots: Vec<T>,
527 n: usize, len: u32,
528 }"#),
529 r#""generic":true,"kind":"struct","summary":[["field","len"],["lit"," items"]],"slots":{"field":"slots","len":"n","only":{"path":["version"],"mask":1},"value":["u","value"]}}"#
530 );
531 assert_eq!(
532 json(r#"struct S { #[debuggable(items, only = "Occupied", value = "0")] e: Vec<u8> }"#),
533 r#""generic":false,"kind":"struct","slots":{"field":"e","only":{"path":["Occupied"]},"value":["0"]}}"#
534 );
535 assert_eq!(
536 json(r#"struct S { #[debuggable(items)] e: Vec<u8> }"#),
538 r#""generic":false,"kind":"struct","items":{"field":"e"}}"#
539 );
540 }
541
542 #[test]
543 fn alternatives_on_struct_and_enum() {
544 assert_eq!(
545 json(r#"#[debuggable(summary = "{#} items")]
546 #[debuggable(items = "data.Inline.0", len = "capacity")]
547 #[debuggable(items = "data.Heap.ptr", len = "data.Heap.len")]
548 struct SmallVec<A> { #[debuggable(hide)] capacity: usize, #[debuggable(hide)] data: D<A> }"#),
549 r#""generic":true,"kind":"struct","summary":[["count",""],["lit"," items"]],"hide":["capacity","data"],"alternatives":[{"items":["data","Inline","0"],"len":["capacity"]},{"items":["data","Heap","ptr"],"len":["data","Heap","len"]}]}"#
550 );
551 assert_eq!(
552 json(r#"#[debuggable(items = "Inline.0.data", len = "Inline.0.len")]
553 #[debuggable(items = "Heap.0")]
554 enum TinyVec<A> { #[debuggable(summary = "{#} items")] Inline(#[debuggable(hide)] AV<A>), Heap(#[debuggable(hide)] Vec<A>) }"#),
555 r#""generic":true,"kind":"enum","variants":{"Inline":{"summary":[["count",""],["lit"," items"]],"hide":["0"]},"Heap":{"hide":["0"]}},"alternatives":[{"items":["Inline","0","data"],"len":["Inline","0","len"]},{"items":["Heap","0"]}]}"#
556 );
557 assert_eq!(
558 json(r#"#[debuggable(summary = "{#}")] struct S { #[debuggable(items)] v: Vec<u8> }"#),
560 r#""generic":false,"kind":"struct","summary":[["count",""]],"items":{"field":"v"}}"#
561 );
562 }
563
564 #[test]
565 fn enum_variants_and_defaults() {
566 assert_eq!(
567 json(r#"enum Token { #[debuggable(summary = "Ident({name})")] Ident { name: String }, #[debuggable(summary = "Num({0})")] Num(i64), Eof }"#),
568 r#""generic":false,"kind":"enum","variants":{"Ident":{"summary":[["lit","Ident("],["field","name"],["lit",")"]]},"Num":{"summary":[["lit","Num("],["field","0"],["lit",")"]]}}}"#
569 );
570 assert_eq!(json("enum E { A, B }"), r#""generic":false,"kind":"enum","variants":{}}"#);
571 }
572
573 #[test]
574 fn lifetimes_alone_are_not_generic_and_raw_idents_are_unraw() {
575 assert_eq!(
576 json(r#"#[debuggable(summary = "{type}")] struct r#S<'a> { r#type: &'a str }"#),
577 r#""generic":false,"kind":"struct","summary":[["field","type"]]}"#
578 );
579 }
580
581 #[test]
582 fn json_is_ascii_and_quote_safe() {
583 let bs = '\\';
585 assert_eq!(
586 json(r#"#[debuggable(summary = "{0}°C ''' \"q\" {{x}}")] struct C(f32);"#),
587 format!(r#""generic":false,"kind":"struct","summary":[["field","0"],["lit","{bs}u00b0C {bs}u0027{bs}u0027{bs}u0027 {bs}"q{bs}" {{x}}"]]}}"#)
588 );
589 }
590
591 #[test]
592 fn errors() {
593 let cases = [
594 (r#"#[debuggable(sumary = "x")] struct S;"#, "unknown `debuggable` option `sumary`; did you mean `summary`?"),
595 (r#"#[debuggable(colour = "x")] struct S;"#, "expected one of: `summary`"),
596 (r#"#[debuggable(summary = "{lenght}")] struct S { length: u8 }"#, "did you mean `length`?"),
597 (r#"#[debuggable(summary = "a", summary = "b")] struct S;"#, "duplicate `summary`"),
598 (r#"#[debuggable(summary = "x")] enum E { A }"#, "goes on each variant"),
599 (r#"#[debuggable(hide)] struct S { a: u8 }"#, "fields only"),
600 (r#"struct S { #[debuggable(summary = "x")] a: u8 }"#, "not on a field"),
601 (r#"enum E { A { #[debuggable(items)] v: Vec<u8> } }"#, "not supported inside enum variants"),
602 (r#"struct S { #[debuggable(len = "n")] v: Vec<u8>, n: usize }"#, "`len` needs `items` or `text`"),
603 (r#"struct S { #[debuggable(items, text)] v: Vec<u8> }"#, "both `items` and `text`"),
604 (r#"struct S { #[debuggable(text, text)] v: Vec<u8> }"#, "duplicate `text`"),
605 (r#"struct S { #[debuggable(txt)] v: Vec<u8> }"#, "did you mean `text`?"),
606 (r#"enum E { A { #[debuggable(text)] v: Vec<u8> } }"#, "`text` is not supported inside enum variants"),
607 (r#"struct S { #[debuggable(items)] a: Vec<u8>, #[debuggable(items)] b: Vec<u8> }"#, "only one field"),
608 (r#"struct S { #[debuggable(items, len = "count")] v: Vec<u8>, n: usize }"#, "unknown field `count`; available: `v`, `n`"),
609 (r#"#[debuggable(summary = "{x}")] struct S { a: u8 }"#, "unknown field `x`; available: `a`"),
610 (r#"#[debuggable(summary = "{0}")] struct S { a: u8 }"#, "unknown field `0`"),
611 (r#"#[debuggable(summary = "{a:?}")] struct S { a: u8 }"#, "format specs are not supported"),
612 (r#"#[debuggable(summary = "{a.b}")] struct S { a: u8 }"#, "can only refer to fields by name"),
613 (r#"#[debuggable(summary = "{a")] struct S { a: u8 }"#, "unclosed `{`"),
614 (r#"#[debuggable(summary = "a}")] struct S { a: u8 }"#, "unmatched `}`"),
615 (r#"#[debuggable(summary = "{}")] struct S { a: u8 }"#, "empty `{}`"),
616 (r#"struct S { #[debuggable(hide, rename = "b")] a: u8 }"#, "hidden field can't be renamed"),
617 (r#"struct S { #[debuggable(hide, items)] a: Vec<u8> }"#, "remove `hide`"),
618 (r#"struct S { #[debuggable(hide = "yes")] a: u8 }"#, "takes no value"),
619 (r#"struct S { #[debuggable(rename = 3)] a: u8 }"#, "expected `rename = \"...\"`"),
620 (r#"struct S { #[debuggable(rename = "")] a: u8 }"#, "non-empty"),
621 (r#"union U { a: u8 }"#, "can't be derived for unions"),
622 (r#"struct S { #[debuggable(only = "x")] v: Vec<u8> }"#, "`only` needs `items`"),
623 (r#"struct S { #[debuggable(value = "x")] v: Vec<u8> }"#, "`value` needs `items`"),
624 (r#"struct S { #[debuggable(items, only = "")] v: Vec<u8> }"#, "`only` expects a field or variant name"),
625 (r#"struct S { #[debuggable(items, only = "a..b")] v: Vec<u8> }"#, "empty segment"),
626 (r#"struct S { #[debuggable(items, only = "a & 0")] v: Vec<u8> }"#, "mask must be non-zero"),
627 (r#"struct S { #[debuggable(items, only = "a & 9007199254740992")] v: Vec<u8> }"#, "at most 2^53 - 1"),
628 (r#"struct S { #[debuggable(items, only = "a &")] v: Vec<u8> }"#, "expects a mask"),
629 (r#"struct S { #[debuggable(items, only = "a b")] v: Vec<u8> }"#, "unexpected `b`"),
630 (r#"struct S { #[debuggable(items, value = "a.-b")] v: Vec<u8> }"#, "not a field name"),
631 (r#"struct S { #[debuggable(items, only = "a", only = "b")] v: Vec<u8> }"#, "duplicate `only`"),
632 (r#"struct S { #[debuggable(items, onyl = "x")] v: Vec<u8> }"#, "did you mean `only`?"),
633 (r#"#[debuggable(items = "dta.x")] struct S { data: u8 }"#, "`items` refers to unknown field `dta`; did you mean `data`?"),
634 (r#"#[debuggable(items = "Hep.0")] enum E { Heap(Vec<u8>) }"#, "unknown variant `Hep`; did you mean `Heap`?"),
635 (r#"#[debuggable(items = "Heap.x")] enum E { Heap(Vec<u8>) }"#, "variant `Heap` has no field `x`"),
636 (r#"#[debuggable(len = "n")] struct S { n: u8 }"#, "`len` on a type needs `items = \"...\"` in the same attribute"),
637 (r#"struct S { #[debuggable(items = "v")] v: Vec<u8> }"#, "on a field, write `items` without a value"),
638 (r#"#[debuggable(items = "v")] struct S { #[debuggable(items)] v: Vec<u8> }"#, "either field-level `items` or `items = \"...\"` on the type, not both"),
639 (r#"#[debuggable(summary = "{#}")] struct S { a: u8 }"#, "`{#}` counts elements, but this type has no `items`"),
640 (r#"#[debuggable(items = "v")] struct S { #[debuggable(only = "x")] v: Vec<u8> }"#, "`only` needs `items`"),
641 (r#"#[debuggable(items, len = "n")] struct S { n: u8 }"#, "on a type, write `items = \"path\"`"),
642 ];
643 for (src, want) in cases {
644 let got = error(src);
645 assert!(got.contains(want), "for {src}\n want: {want}\n got: {got}");
646 }
647 }
648}