1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
use darling::FromDeriveInput;
use proc_macro2::TokenStream;
use quote::quote;
use syn::{Data, DeriveInput, Fields};
use crate::attrs::{get_repr, ContainerAttrs, FieldAttrs, VariantAttrs};
use crate::util::{compile_error, has_lifetime};
// region: Entry point
pub fn derive(input: DeriveInput) -> TokenStream {
let container = match ContainerAttrs::from_derive_input(&input) {
Ok(c) => c,
Err(e) => return e.write_errors(),
};
match &input.data {
Data::Struct(data) => derive_struct(&input, &container, data),
Data::Enum(data) => derive_enum(&input, &container, data),
Data::Union(_) => compile_error(
proc_macro2::Span::call_site(),
"FrameRead cannot be derived for unions",
),
}
}
// endregion: Entry point
// region: Struct
fn derive_struct(
input: &DeriveInput,
container: &ContainerAttrs,
data: &syn::DataStruct,
) -> TokenStream {
let name = &input.ident;
let error = &container.error;
let named = match &data.fields {
Fields::Named(f) => &f.named,
_ => {
return compile_error(
proc_macro2::Span::call_site(),
"FrameRead requires named fields",
)
}
};
let mut stmts: Vec<TokenStream> = Vec::new();
let mut field_names: Vec<syn::Ident> = Vec::new();
for field in named {
let ident = field.ident.as_ref().unwrap();
let ty = &field.ty;
let attrs: FieldAttrs = match darling::FromField::from_field(field) {
Ok(a) => a,
Err(e) => return e.write_errors(),
};
if let Err(e) = attrs.validate(&ident.to_string()) {
return e.write_errors();
}
field_names.push(ident.clone());
let stmt = if attrs.skip {
// Skipped - receive Default::default(), cursor not advanced
quote! {
let #ident: #ty = Default::default();
}
} else if let Some(length_expr) = &attrs.length {
// Computed length - take_n returns DiagError directly,
// container must implement From<DiagError>.
quote! {
let #ident: #ty = ace_core::codec::take_n(
buf,
(#length_expr) as usize,
).map_err(|e| <#error as From<ace_core::DiagError>>::from(e))?;
}
} else if attrs.read_all {
// Consume all remaining bytes - no error possible
quote! {
let #ident: #ty = {
let __slice = *buf;
*buf = &buf[buf.len()..];
__slice
};
}
} else {
// Default - delegate to the field type's FrameRead impl.
// Direct routing: FieldError -> ContainerError via From<FieldError>.
// No intermediate DiagError - container error only needs to implement
// From for each field error type it encounters.
quote! {
let #ident = <#ty as ace_core::codec::FrameRead>::decode(buf)
.map_err(|e| <#error as From<_>>::from(e))?;
}
};
stmts.push(stmt);
}
let needs_lt = has_lifetime(&input.generics);
let (ig, tg, wc) = input.generics.split_for_impl();
let decode_lt = if needs_lt {
quote! { 'a }
} else {
quote! { '_ }
};
quote! {
impl #ig ace_core::codec::FrameRead<#decode_lt> for #name #tg #wc {
type Error = #error;
fn decode(buf: &mut &#decode_lt [u8]) -> Result<Self, Self::Error> {
#(#stmts)*
Ok(Self {
#(#field_names),*
})
}
}
}
}
// endregion: Struct
// region: Enum
fn derive_enum(
input: &DeriveInput,
container: &ContainerAttrs,
data: &syn::DataEnum,
) -> TokenStream {
let name = &input.ident;
let error = &container.error;
let disc_ty = get_repr(input);
let mut arms: Vec<TokenStream> = Vec::new();
for variant in &data.variants {
let vname = &variant.ident;
let attrs: VariantAttrs = match darling::FromVariant::from_variant(variant) {
Ok(a) => a,
Err(e) => return e.write_errors(),
};
if let Err(e) = attrs.validate(&vname.to_string()) {
return e.write_errors();
}
let arm = match &variant.fields {
// Newtype variant - decode inner type from remaining buffer
Fields::Unnamed(f) if f.unnamed.len() == 1 => {
let inner_ty = &f.unnamed.first().unwrap().ty;
if let Some(id_expr) = &attrs.id {
quote! {
__disc if __disc == (#id_expr) as #disc_ty => {
Ok(#name::#vname(
<#inner_ty as ace_core::codec::FrameRead>::decode(buf)
.map_err(|e| <#error as From<_>>::from(e))?
))
}
}
} else {
// id_pat - raw discriminant byte passed directly into newtype(u8).
// Uses matches!() guard style so all arms bind __disc consistently -
// mixing `__disc @ pat` with `__disc if __disc == x` arms causes E0408.
// matches!() also correctly handles compound patterns like
// `0x05..=0x3F | 0x7F` that would otherwise need special parsing.
let pat: TokenStream = attrs
.id_pat
.as_ref()
.unwrap()
.value()
.parse()
.unwrap_or_else(|_| {
compile_error(proc_macro2::Span::call_site(), "invalid id_pat pattern")
});
if attrs.decode_inner {
quote! {
__disc if matches!(__disc, #pat) => {
Ok(#name::#vname(
<#inner_ty as ace_core::codec::FrameRead>::decode(&mut { __saved }).map_err(|e| <#error as From<_>>::from(e))?
))
}
}
} else {
quote! {
__disc if matches!(__disc, #pat) => Ok(#name::#vname(__disc)),
}
}
}
}
// Unit variant - no inner data to decode
Fields::Unit => {
let id_expr = attrs.id.as_ref().unwrap();
quote! {
__disc if __disc == (#id_expr) as #disc_ty => Ok(#name::#vname),
}
}
_ => {
return compile_error(
proc_macro2::Span::call_site(),
"FrameRead enum variants must be newtype(T) or unit",
)
}
};
arms.push(arm);
}
// Always emit a catch-all error arm. Even when an id_pat variant is present
// it may not cover the full u8 range - the catch-all handles any remaining
// values and ensures the match is exhaustive.
// Routes through DiagError since unknown discriminant is always a transport-
// level framing error - container must implement From<DiagError>.
arms.push(quote! {
_ => Err(<#error as From<ace_core::DiagError>>::from(
ace_core::DiagError::InvalidFrame(
heapless::String::try_from("unknown discriminant").unwrap_or_default()
)
)),
});
// Enums never borrow from the buffer directly - only use 'a if the enum
// itself has lifetime parameters (e.g. newtype variants wrapping borrowed types).
// Never inject 'a via with_lifetime_a - that would add a spurious lifetime
// to types like ServiceIdentifier that have no lifetime parameters at all.
let needs_lt = has_lifetime(&input.generics);
let (ig, tg, wc) = input.generics.split_for_impl();
let decode_lt = if needs_lt {
quote! { 'a }
} else {
quote! { '_ }
};
quote! {
impl #ig ace_core::codec::FrameRead<#decode_lt> for #name #tg #wc {
type Error = #error;
fn decode(buf: &mut &#decode_lt [u8]) -> Result<Self, Self::Error> {
let __saved = *buf;
let __disc = <#disc_ty as ace_core::codec::FrameRead>::decode(buf)
.map_err(|e| <#error as From<ace_core::DiagError>>::from(e))?;
match __disc {
#(#arms)*
}
}
}
}
}
// endregion: Enum