use super::*;
#[test]
fn function_parts_are_the_source_tokens() {
let f = syn::parse_quote!(
pub fn publish(
key: &'a KeyExpr,
payload: Vec<u8>,
count: Option<i32>,
) -> Result<(), Error> {
unimplemented!()
}
);
let element = parse_one(syn::Item::Fn(f));
let func = as_fn(&element);
assert_eq!(
func.params
.iter()
.map(|p| tokens(p.origin.as_syn()))
.collect::<Vec<_>>(),
vec![
"key : & 'a KeyExpr",
"payload : Vec < u8 >",
"count : Option < i32 >",
]
);
assert_eq!(tokens(&func.params[0].ty.origin.spell()), "& 'a KeyExpr");
assert!(matches!(func.params[0].ty.kind, TypeKind::Ref { .. }));
assert_eq!(tokens(func.ret.origin.as_syn()), "Result < () , Error >");
}
#[test]
fn a_defaulted_return_spells_as_the_unit() {
for item in [
syn::parse_quote!(
pub fn a() {}
),
syn::parse_quote!(
pub fn b() -> () {}
),
] {
let element = parse_one(item);
let ret = &as_fn(&element).ret;
assert!(matches!(ret.kind, TypeKind::Unit));
assert_eq!(tokens(ret.origin.as_syn()), "()");
}
let defaulted = parse_one(syn::parse_quote!(
pub fn a() {}
));
assert!(matches!(
as_fn(&defaulted).origin.as_syn().sig.output,
syn::ReturnType::Default
));
}
#[test]
fn struct_field_slices_keep_attributes() {
let element = parse_one(syn::parse_quote!(
pub struct Sample {
pub key: String,
#[allow(dead_code)]
pub(crate) seq: u64,
}
));
let fields = &as_struct(&element).fields;
assert_eq!(fields.len(), 2);
assert!(tokens(&fields[0].origin.spell()).contains("The key it was published on."));
assert_eq!(
tokens(&fields[1].origin.spell()),
"# [allow (dead_code)] pub (crate) seq : u64"
);
}
#[test]
fn an_item_and_its_components_share_one_location() {
let element = parse_one(syn::parse_quote!(
pub struct Sample {
pub key: String,
pub tags: Vec<u8>,
}
));
let s = as_struct(&element);
let item = &s.origin.location;
for field in &s.fields {
assert!(Rc::ptr_eq(item, &field.origin.location), "field");
assert!(Rc::ptr_eq(item, &field.ty.origin.location), "field type");
}
let TypeKind::Vec(elem) = &s.fields[1].ty.kind else {
panic!("a sequence");
};
assert!(Rc::ptr_eq(item, &elem.origin.location), "element type");
let element = parse_one(syn::parse_quote!(
pub enum E {
A { x: [u8; 4] },
}
));
let v = as_variant(&element);
let item = &v.origin.location;
let a = &v.alternatives[0];
assert!(Rc::ptr_eq(item, &a.origin.location), "alternative");
let f = &a.fields[0];
assert!(Rc::ptr_eq(item, &f.origin.location), "alternative field");
let extent = f.ty.array_extent().expect("an extent");
assert!(Rc::ptr_eq(item, &extent.origin.location), "extent");
assert_eq!(tokens(extent.origin.as_syn()), "4");
let element = parse_one(syn::parse_quote!(
pub fn f(a: u8) {}
));
let func = as_fn(&element);
let item = &func.origin.location;
assert!(Rc::ptr_eq(item, &func.params[0].origin.location), "param");
assert!(Rc::ptr_eq(item, &func.ret.origin.location), "elided return");
}
#[test]
fn a_component_diagnosis_carries_the_items_location() {
let element = parse_one(syn::parse_quote!(
pub struct Sample {
pub bad: (u8, u8),
}
));
let Element::Unsupported(u) = &element else {
panic!("a tuple field is outside the language");
};
assert!(matches!(*u.error, ItemError::FieldType { .. }));
assert!(std::ptr::eq(element.location(), &*u.origin.location));
}
#[test]
fn empty_delimiters_survive_and_spell() {
let element = parse_one(syn::parse_quote!(
pub enum E {
A,
B(),
C {},
D(u32),
}
));
let v = as_variant(&element);
assert_eq!(
v.alternatives
.iter()
.map(|a| a.is_empty())
.collect::<Vec<_>>(),
vec![true, true, true, false]
);
let spell = |a: &Alternative| {
let name = &a.name;
crate::flat::emit::Emit::for_test()
.shape(a, quote::quote!(E::#name), &[])
.to_string()
};
assert_eq!(spell(&v.alternatives[0]), "E :: A");
assert_eq!(spell(&v.alternatives[1]), "E :: B ()");
assert_eq!(spell(&v.alternatives[2]), "E :: C { }");
let element = parse_one(syn::parse_quote!(
pub enum F {
A,
B(),
C {},
}
));
let e = as_enum(&element);
let spell = |v: &EnumValue| {
let name = &v.name;
crate::flat::emit::Emit::for_test()
.shape(v, quote::quote!(F::#name), &[])
.to_string()
};
assert_eq!(spell(&e.values[0]), "F :: A");
assert_eq!(spell(&e.values[1]), "F :: B ()");
assert_eq!(spell(&e.values[2]), "F :: C { }");
let element = parse_one(syn::parse_quote!(
pub enum Reading {
Exact(i64),
Range { low: i64, high: i64 },
}
));
let v = as_variant(&element);
let bind = |a: &Alternative| {
let parts: Vec<_> = a
.fields
.iter()
.map(|f| f.bind("e::format_ident!("__f{}", f.index)))
.collect();
let name = &a.name;
crate::flat::emit::Emit::for_test()
.shape(a, quote::quote!(Reading::#name), &parts)
.to_string()
};
assert_eq!(bind(&v.alternatives[0]), "Reading :: Exact (__f0)");
assert_eq!(
bind(&v.alternatives[1]),
"Reading :: Range { low : __f0 , high : __f1 }"
);
}
#[test]
fn struct_delimiters_survive_and_spell() {
let spell = |item: syn::Item, parts: &[proc_macro2::TokenStream]| {
let element = parse_one(item);
let s = as_struct(&element);
let name = &s.name;
(
s.fields.len(),
crate::flat::emit::Emit::for_test()
.shape(s, quote::quote!(#name), parts)
.to_string(),
)
};
assert_eq!(
spell(
syn::parse_quote!(
pub struct A;
),
&[]
),
(0, "A".to_string())
);
assert_eq!(
spell(
syn::parse_quote!(
pub struct B {}
),
&[]
),
(0, "B { }".to_string())
);
assert_eq!(
spell(
syn::parse_quote!(
pub struct C {
pub x: u8,
}
),
&[quote::quote!(x: __f0)]
),
(1, "C { x : __f0 }".to_string())
);
let element = parse_one(syn::parse_quote!(
pub struct D(Whatever<'_, dyn Trait>);
));
let o = as_extern(&element);
assert_eq!(o.name, "D");
assert!(tokens(o.origin.as_syn()).contains("Whatever"));
}
#[test]
fn discriminant_number_and_spelling_both_survive() {
let element = parse_one(syn::parse_quote!(
pub enum Priority {
Low = 0x07,
High,
}
));
let e = as_enum(&element);
assert_eq!(
e.discriminant_values().expect("literal discriminants"),
vec![(&e.values[0].name, 7), (&e.values[1].name, 8)]
);
let (_, expr) = e.values[0]
.origin
.syntax
.discriminant
.as_ref()
.expect("an explicit discriminant");
assert_eq!(tokens(expr), "0x07");
assert!(e.values[1].origin.as_syn().discriminant.is_none());
}
#[test]
fn an_unevaluable_discriminant_keeps_its_spelling() {
let element = parse_one(syn::parse_quote!(
pub enum E {
A = OTHER,
B,
}
));
let e = as_enum(&element);
assert!(e.values.iter().all(|v| v.discriminant.is_none()));
assert_eq!(e.discriminant_values().expect_err("no numbers"), "A");
let (_, expr) = e.values[0]
.origin
.syntax
.discriminant
.as_ref()
.expect("explicit");
assert_eq!(tokens(expr), "OTHER");
}
#[test]
fn a_discriminant_at_the_top_of_the_range_does_not_overflow() {
let element = parse_one(syn::parse_quote!(
#[repr(u64)]
pub enum E {
A = 9223372036854775807,
B,
}
));
let e = as_enum(&element);
assert_eq!(e.values[0].discriminant, Some(i64::MAX));
assert_eq!(e.values[1].discriminant, None);
let element = parse_one(syn::parse_quote!(
pub enum E {
A = 9223372036854775807,
}
));
assert_eq!(
as_enum(&element).discriminant_values().expect("a number")[0].1,
i64::MAX
);
}
#[test]
fn a_discriminant_at_the_bottom_of_the_range_evaluates() {
let element = parse_one(syn::parse_quote!(
#[repr(i64)]
pub enum E {
A = -9223372036854775808,
B,
}
));
let e = as_enum(&element);
assert_eq!(e.values[0].discriminant, Some(i64::MIN));
assert_eq!(e.values[1].discriminant, Some(i64::MIN + 1));
let (_, expr) = e.values[0]
.origin
.syntax
.discriminant
.as_ref()
.expect("explicit");
assert_eq!(tokens(expr), "- 9223372036854775808");
let element = parse_one(syn::parse_quote!(
#[repr(i128)]
pub enum E {
A = -9223372036854775809,
B,
}
));
let e = as_enum(&element);
assert_eq!(e.values[0].discriminant, None);
assert_eq!(e.values[1].discriminant, None);
assert_eq!(e.discriminant_values().expect_err("no numbers"), "A");
}
#[test]
fn array_extent_carries_number_const_and_spelling() {
let elements = parse(vec![
tag_len_const(),
syn::parse_quote!(
pub struct Marker {
pub tag: [u8; TAG_LEN],
pub pad: [u8; 2],
}
),
]);
let fields = &as_struct(&elements[1]).fields;
let named = fields[0].ty.array_extent().expect("an extent");
assert_eq!(named.value, 4);
assert_eq!(named.const_id().expect("a const").name, "TAG_LEN");
assert_eq!(tokens(&fields[0].ty.origin.spell()), "[u8 ; TAG_LEN]");
let literal = fields[1].ty.array_extent().expect("an extent");
assert_eq!(literal.value, 2);
assert!(literal.const_id().is_none());
}
#[test]
fn the_whole_item_survives() {
let source: syn::ItemFn = syn::parse_quote!(
#[inline]
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
);
let element = parse_one(syn::Item::Fn(source.clone()));
assert_eq!(tokens(&as_fn(&element).origin.spell()), tokens(&source));
assert_eq!(tokens(&element.as_syn()), tokens(&syn::Item::Fn(source)));
}
#[test]
fn an_unsupported_item_keeps_its_tokens() {
let source: syn::Item = syn::parse_quote!(
pub union U {
a: u8,
}
);
let element = parse_one(source.clone());
assert!(matches!(element, Element::Unsupported(_)));
assert_eq!(tokens(&element.as_syn()), tokens(&source));
}
#[test]
fn syntax_is_recoverable_from_kind() {
for spelling in [
quote::quote!(u8),
quote::quote!(bool),
quote::quote!(f64),
quote::quote!(()),
quote::quote!(String),
quote::quote!(&str),
quote::quote!(Option<u8>),
quote::quote!(Vec<u8>),
quote::quote!(Result<u8, Error>),
quote::quote!(Box<String>),
quote::quote!(Cow<'_, [u8]>),
quote::quote!(Cow<'a, str>),
quote::quote!(&[u8]),
quote::quote!(&'a Sample),
quote::quote!(&mut Sample),
quote::quote!(&mut MaybeUninit<Sample>),
quote::quote!([u8; 4]),
quote::quote!([u8; TAG_LEN]),
quote::quote!([[u8; 4]; TAG_LEN]),
quote::quote!(Sample),
quote::quote!(Sample<'a>),
quote::quote!(Sample<'a, u8, Vec<u8>>),
quote::quote!(Option<Box<String>>),
quote::quote!(Box<Cow<'_, [u8]>>),
quote::quote!(Result<Option<Vec<Sample>>, Error>),
quote::quote!(Vec<&'a Sample>),
] {
let ty = lower(spelling).expect("in the language");
assert_eq!(
tokens(&ty.kind().to_syn()),
tokens(ty.as_syn()),
"`{}` must spell back as itself",
tokens(ty.as_syn()),
);
}
}
#[test]
fn the_two_forms_that_do_not_spell_back_verbatim() {
let cb = lower(quote::quote!(impl Fn(&Sample, u8) + Sync + Send + 'static))
.expect("in the language");
assert_eq!(
tokens(&cb.kind().to_syn()),
"impl Fn (& Sample , u8) + Send + Sync + 'static"
);
let parens = lower(quote::quote!((u8))).expect("in the language");
assert_eq!(tokens(&parens.kind().to_syn()), "u8");
assert_eq!(
tokens(parens.as_syn()),
"u8",
"the slice is the inner node's"
);
}
#[test]
fn an_element_answers_for_its_docs() {
let element = parse_one(syn::parse_quote!(
pub fn put(payload: u8) {}
));
assert_eq!(
as_fn(&element).docs().expect("docs present"),
"Puts a payload.\n\nSecond paragraph with *\u{200B}/ inside.",
"one leading space off each line, joined, and `*/` defanged"
);
let bare = parse_one(syn::parse_quote!(
pub fn g() {}
));
assert_eq!(as_fn(&bare).docs(), None);
}