Skip to content

Commit 5c3d2bb

Browse files
committed
Restructured and deduplicated derive code
1 parent 7652822 commit 5c3d2bb

15 files changed

Lines changed: 2989 additions & 2790 deletions

File tree

epserde-derive/src/attrs/mod.rs

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2023 Inria
3+
* SPDX-FileCopyrightText: 2023 Sebastiano Vigna
4+
*
5+
* SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
6+
*/
7+
8+
//! Parsing of the `#[epserde(...)]` and `#[repr(...)]` attributes.
9+
10+
use quote::ToTokens;
11+
use syn::{DeriveInput, WherePredicate, punctuated::Punctuated, token};
12+
13+
/// Returns true if the given field carries `#[epserde(force_full_copy)]`.
14+
pub(crate) fn is_force_full_copy(field: &syn::Field) -> bool {
15+
let mut found = false;
16+
for attr in &field.attrs {
17+
if !attr.meta.path().is_ident("epserde") {
18+
continue;
19+
}
20+
// Parse errors are intentionally swallowed; the per-field validator
21+
// runs the same walk with proper error propagation.
22+
let _ = attr.parse_nested_meta(|meta| {
23+
if meta.path.is_ident("force_full_copy") {
24+
found = true;
25+
}
26+
Ok(())
27+
});
28+
}
29+
found
30+
}
31+
32+
/// Parsed epserde attributes.
33+
pub(crate) struct EpserdeAttrs {
34+
/// Whether the type has `#[repr(C)]`.
35+
pub(crate) is_repr_c: bool,
36+
/// Whether `#[epserde(zero_copy)]` was specified.
37+
pub(crate) is_zero_copy: bool,
38+
/// Whether `#[epserde(deep_copy)]` was specified.
39+
pub(crate) is_deep_copy: bool,
40+
/// Additional where-clause predicates for `DeserInner` impl.
41+
pub(crate) deser_bounds: Vec<WherePredicate>,
42+
/// Additional where-clause predicates for `SerInner` impl.
43+
pub(crate) ser_bounds: Vec<WherePredicate>,
44+
/// Type-parameter idents listed in `#[epserde(full_copy(...))]`. These are
45+
/// pinned to full-copy: removed from the `DeserType` substitution set, and
46+
/// kept verbatim in `DeserType<'a>`.
47+
pub(crate) full_copy_params: Vec<syn::Ident>,
48+
/// Type-parameter idents listed in `#[epserde(phantom(...))]`. These are
49+
/// declared phantom throughout the type and left completely untouched: no
50+
/// `SerType`/`DeserType` substitution and no `SerInner`/`DeserInner`
51+
/// bounds.
52+
pub(crate) phantom_params: Vec<syn::Ident>,
53+
}
54+
55+
/// Collects the representation hints of all `repr` attributes of a type,
56+
/// individually normalized (e.g., `align(16)`) and sorted.
57+
///
58+
/// The normalization guarantees that equivalent spellings such as
59+
/// `#[repr(C, align(16))]` and `#[repr(align(16))] #[repr(C)]` yield the same
60+
/// hints, and thus the same alignment hash.
61+
pub(crate) fn repr_hints(attrs: &[syn::Attribute]) -> syn::Result<Vec<String>> {
62+
let mut hints = Vec::new();
63+
for attr in attrs {
64+
if attr.path().is_ident("repr") {
65+
// A repr attribute may combine several hints, as in
66+
// #[repr(C, align(16))]
67+
attr.parse_nested_meta(|meta| {
68+
let mut hint = meta.path.to_token_stream().to_string();
69+
// Append the argument of hints such as align(16) or packed(2)
70+
if meta.input.peek(syn::token::Paren) {
71+
let content;
72+
syn::parenthesized!(content in meta.input);
73+
let args: proc_macro2::TokenStream = content.parse()?;
74+
hint = format!("{hint}({args})");
75+
}
76+
hints.push(hint);
77+
Ok(())
78+
})?;
79+
}
80+
}
81+
hints.sort();
82+
Ok(hints)
83+
}
84+
85+
/// Returns an error for the first `#[epserde(...)]` attribute in `attrs`.
86+
///
87+
/// Used to reject attributes in positions where they are syntactically
88+
/// accepted (the derives register the `epserde` helper attribute, so the
89+
/// compiler forwards them anywhere on the item) but have no effect, instead
90+
/// of silently ignoring them.
91+
pub(crate) fn reject_epserde_attrs(attrs: &[syn::Attribute], msg: &str) -> syn::Result<()> {
92+
for attr in attrs {
93+
if attr.meta.path().is_ident("epserde") {
94+
return Err(syn::Error::new_spanned(attr, msg));
95+
}
96+
}
97+
Ok(())
98+
}
99+
100+
/// Parses the string value of a `bound(deser = "...")` or `bound(ser = "...")`
101+
/// key into where-clause predicates, extending `out`.
102+
fn parse_bound_predicates(
103+
inner: &syn::meta::ParseNestedMeta,
104+
out: &mut Vec<WherePredicate>,
105+
) -> syn::Result<()> {
106+
let value = inner.value()?;
107+
let lit: syn::LitStr = value.parse()?;
108+
let preds = lit.parse_with(Punctuated::<WherePredicate, token::Comma>::parse_terminated)?;
109+
out.extend(preds);
110+
Ok(())
111+
}
112+
113+
/// Parses the parenthesized type-parameter list of a type-level attribute such
114+
/// as `full_copy(T, U)` or `phantom(T, U)`, extending `out` with the listed
115+
/// identifiers.
116+
fn parse_param_list(
117+
meta: &syn::meta::ParseNestedMeta,
118+
attr_name: &str,
119+
out: &mut Vec<syn::Ident>,
120+
) -> syn::Result<()> {
121+
if !meta.input.peek(token::Paren) {
122+
return Err(meta.error(format!(
123+
"\"{attr_name}\" is a type-level attribute and requires a parenthesized \
124+
list of type parameters, e.g. #[epserde({attr_name}(T))]"
125+
)));
126+
}
127+
meta.parse_nested_meta(|inner| {
128+
if let Some(ident) = inner.path.get_ident() {
129+
out.push(ident.clone());
130+
Ok(())
131+
} else {
132+
Err(inner.error("expected a type-parameter identifier"))
133+
}
134+
})
135+
}
136+
137+
/// Parses `#[epserde(...)]` attributes.
138+
pub(crate) fn parse_epserde_attrs(input: &DeriveInput) -> syn::Result<EpserdeAttrs> {
139+
let is_repr_c = repr_hints(&input.attrs)?.iter().any(|hint| hint == "C");
140+
141+
let mut is_zero_copy = false;
142+
let mut is_deep_copy = false;
143+
let mut deser_bounds = Vec::new();
144+
let mut ser_bounds = Vec::new();
145+
let mut full_copy_params = Vec::new();
146+
let mut phantom_params = Vec::new();
147+
148+
for attr in &input.attrs {
149+
if attr.meta.path().is_ident("epserde") {
150+
attr.parse_nested_meta(|meta| {
151+
if meta.path.is_ident("zero_copy") {
152+
is_zero_copy = true;
153+
Ok(())
154+
} else if meta.path.is_ident("deep_copy") {
155+
is_deep_copy = true;
156+
Ok(())
157+
} else if meta.path.is_ident("bound") {
158+
meta.parse_nested_meta(|inner| {
159+
if inner.path.is_ident("deser") {
160+
parse_bound_predicates(&inner, &mut deser_bounds)
161+
} else if inner.path.is_ident("ser") {
162+
parse_bound_predicates(&inner, &mut ser_bounds)
163+
} else {
164+
Err(inner.error("expected `deser` or `ser`"))
165+
}
166+
})
167+
} else if meta.path.is_ident("full_copy") {
168+
parse_param_list(&meta, "full_copy", &mut full_copy_params)
169+
} else if meta.path.is_ident("phantom") {
170+
parse_param_list(&meta, "phantom", &mut phantom_params)
171+
} else {
172+
Err(meta.error(
173+
"expected \"zero_copy\", \"deep_copy\", \"bound\", \"full_copy\", or \"phantom\"",
174+
))
175+
}
176+
})?;
177+
}
178+
}
179+
180+
if is_zero_copy && !is_repr_c {
181+
return Err(syn::Error::new_spanned(
182+
&input.ident,
183+
format!(
184+
"Type {} is declared as zero-copy, but it is not repr(C)",
185+
input.ident
186+
),
187+
));
188+
}
189+
if is_zero_copy && is_deep_copy {
190+
return Err(syn::Error::new_spanned(
191+
&input.ident,
192+
format!(
193+
"Type {} is declared as both zero-copy and deep-copy",
194+
input.ident
195+
),
196+
));
197+
}
198+
199+
Ok(EpserdeAttrs {
200+
is_repr_c,
201+
is_zero_copy,
202+
is_deep_copy,
203+
deser_bounds,
204+
ser_bounds,
205+
full_copy_params,
206+
phantom_params,
207+
})
208+
}

0 commit comments

Comments
 (0)