Skip to content

Commit 0b6ce75

Browse files
committed
pread: make offset, ctx, and src all hygenic/fresh. this allows fields named these to not break the macro derive, in addition to allowing a field named ctx to be used as a ctx when using a custom ctx
1 parent 0f6e723 commit 0b6ce75

3 files changed

Lines changed: 134 additions & 42 deletions

File tree

scroll_derive/examples/derive_custom_ctx.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,15 +51,13 @@ struct Data {
5151
arr: [u16; 2],
5252
// You can use arbitrary expressions for the ctx.
5353
// You have access to the `ctx` parameter of the `{pread/gread}_with` inside the expression.
54-
// TODO(implement) you have access to previous fields.
55-
// TODO(check) will this break structs with fields named `ctx`?.
5654
#[scroll(ctx = EndianDependent(ctx.clone()).len())]
5755
custom_ctx: VariableLengthData,
5856
}
5957

6058
use scroll::{
59+
BE, Endian, LE, Pread, Pwrite,
6160
ctx::{SizeWith, TryFromCtx, TryIntoCtx},
62-
Endian, Pread, Pwrite, BE, LE,
6361
};
6462

6563
fn main() {

scroll_derive/src/lib.rs

Lines changed: 114 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,53 @@
11
#![recursion_limit = "1024"]
22

33
extern crate proc_macro;
4+
45
use proc_macro2::Span;
5-
use quote::{ToTokens, quote};
6+
use quote::{ToTokens, format_ident, quote};
7+
use syn::Ident;
8+
9+
fn extract_idents_and_offset(
10+
fields: &syn::punctuated::Punctuated<syn::Field, syn::token::Comma>,
11+
) -> (Vec<(proc_macro2::TokenStream, &syn::Field)>, syn::Ident) {
12+
// first iterate idents
13+
let idents: Vec<_> = fields
14+
.into_iter()
15+
.enumerate()
16+
.map(|(i, f)| {
17+
let ident = f.ident.as_ref().map(|i| quote! {#i}).unwrap_or({
18+
let t = proc_macro2::Literal::usize_unsuffixed(i);
19+
quote! {#t}
20+
});
21+
(ident, f)
22+
})
23+
.collect();
24+
// iterate until we have no field that matches our offset
25+
let offset = fresh_name(
26+
&fields,
27+
proc_macro2::Ident::new("offset", Span::call_site()),
28+
);
29+
30+
(idents, offset)
31+
}
632

7-
use proc_macro::TokenStream;
33+
/// Generates a fresh name that will not clash with any field named the same
34+
/// NB: there is probably a more efficient algorithm than this worst case O^2 runtime, but even for
35+
/// a struct with hundreds of fields all clashing with increasing _ prefixes, which is a highly
36+
/// degenerate example input, it is fine.
37+
fn fresh_name(
38+
fields: &syn::punctuated::Punctuated<syn::Field, syn::token::Comma>,
39+
mut target: proc_macro2::Ident,
40+
) -> Ident {
41+
while fields.iter().any(|f| {
42+
f.ident
43+
.as_ref()
44+
.map(|ident| ident == &target)
45+
.unwrap_or(false)
46+
}) {
47+
target = format_ident!("_{target}");
48+
}
49+
target
50+
}
851

952
fn extract_lifetime(
1053
gp: &syn::punctuated::Punctuated<syn::GenericParam, syn::token::Comma>,
@@ -33,21 +76,31 @@ fn extract_lifetime(
3376
fn impl_field(
3477
ident: &proc_macro2::TokenStream,
3578
ty: &syn::Type,
79+
src: &proc_macro2::Ident,
80+
default_ctx: &proc_macro2::TokenStream,
3681
custom_ctx: Option<&proc_macro2::TokenStream>,
82+
offset: &Ident,
3783
noctx: bool,
3884
) -> proc_macro2::TokenStream {
39-
let default_ctx = syn::Ident::new("ctx", proc_macro2::Span::call_site()).into_token_stream();
40-
let ctx = custom_ctx.unwrap_or(&default_ctx);
85+
let ctx = custom_ctx.unwrap_or(default_ctx);
4186
match ty {
42-
syn::Type::Group(group) => impl_field(ident, &group.elem, custom_ctx, noctx),
87+
syn::Type::Group(group) => impl_field(
88+
ident,
89+
&group.elem,
90+
src,
91+
default_ctx,
92+
custom_ctx,
93+
offset,
94+
noctx,
95+
),
4396
_ => {
4497
if noctx {
4598
quote! {
46-
let #ident = src.gread::<#ty>(offset)?;
99+
let #ident = #src.gread::<#ty>(#offset)?;
47100
}
48101
} else {
49102
quote! {
50-
let #ident = src.gread_with::<#ty>(offset, #ctx)?;
103+
let #ident = #src.gread_with::<#ty>(#offset, #ctx)?;
51104
}
52105
}
53106
}
@@ -107,6 +160,19 @@ fn impl_struct(
107160
generics: &syn::Generics,
108161
unnamed: bool,
109162
) -> proc_macro2::TokenStream {
163+
let offset = fresh_name(
164+
fields,
165+
syn::Ident::new("offset", proc_macro2::Span::call_site()),
166+
);
167+
let src = fresh_name(
168+
fields,
169+
syn::Ident::new("src", proc_macro2::Span::call_site()),
170+
);
171+
let ctx = fresh_name(
172+
fields,
173+
syn::Ident::new("ctx", proc_macro2::Span::call_site()),
174+
)
175+
.to_token_stream();
110176
let (items, item_assignments) = fields
111177
.iter()
112178
.enumerate()
@@ -130,7 +196,15 @@ fn impl_struct(
130196
let mut noctx = false;
131197
let custom_ctx = custom_ctx(f, &mut noctx);
132198
(
133-
impl_field(&prefixed_ident, ty, custom_ctx.as_ref(), noctx),
199+
impl_field(
200+
&prefixed_ident,
201+
ty,
202+
&src,
203+
&ctx,
204+
custom_ctx.as_ref(),
205+
&offset,
206+
noctx,
207+
),
134208
quote! { #ident: #prefixed_ident },
135209
)
136210
})
@@ -192,12 +266,11 @@ fn impl_struct(
192266
// TODO: allow passing user error here
193267
type Error = ::scroll::Error;
194268
#[inline]
195-
fn try_from_ctx(src: &#lifetime [u8], ctx: ::scroll::Endian) -> ::scroll::export::result::Result<(Self, usize), Self::Error> {
269+
fn try_from_ctx(#src: &#lifetime [u8], #ctx: ::scroll::Endian) -> ::scroll::export::result::Result<(Self, usize), Self::Error> {
196270
use ::scroll::Pread;
197-
let offset = &mut 0;
271+
let #offset = &mut 0;
198272
#(#items)*
199-
let data = Self { #(#item_assignments,)* };
200-
Ok((data, *offset))
273+
Ok((Self { #(#item_assignments,)* }, *#offset))
201274
}
202275
}
203276
}
@@ -219,7 +292,7 @@ fn impl_try_from_ctx(ast: &syn::DeriveInput) -> proc_macro2::TokenStream {
219292
}
220293

221294
#[proc_macro_derive(Pread, attributes(scroll))]
222-
pub fn derive_pread(input: TokenStream) -> TokenStream {
295+
pub fn derive_pread(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
223296
let ast: syn::DeriveInput = syn::parse(input).unwrap();
224297
let generated = impl_try_from_ctx(&ast);
225298
generated.into()
@@ -228,11 +301,12 @@ pub fn derive_pread(input: TokenStream) -> TokenStream {
228301
fn impl_pwrite_field(
229302
ident: &proc_macro2::TokenStream,
230303
ty: &syn::Type,
304+
default_ctx: &proc_macro2::TokenStream,
231305
custom_ctx: Option<&proc_macro2::TokenStream>,
306+
offset: &proc_macro2::Ident,
232307
noctx: bool,
233308
) -> proc_macro2::TokenStream {
234-
let default_ctx = syn::Ident::new("ctx", proc_macro2::Span::call_site()).into_token_stream();
235-
let ctx = custom_ctx.unwrap_or(&default_ctx);
309+
let ctx = custom_ctx.unwrap_or(default_ctx);
236310
match ty {
237311
syn::Type::Array(array) => match &array.len {
238312
syn::Expr::Lit(syn::ExprLit {
@@ -242,39 +316,41 @@ fn impl_pwrite_field(
242316
let size = int.base10_parse::<usize>().unwrap();
243317
quote! {
244318
for i in 0..#size {
245-
dst.gwrite_with(&self.#ident[i], offset, #ctx)?;
319+
dst.gwrite_with(&self.#ident[i], #offset, #ctx)?;
246320
}
247321
}
248322
}
249323
_ => panic!("Pwrite derive with bad array constexpr"),
250324
},
251-
syn::Type::Group(group) => impl_pwrite_field(ident, &group.elem, custom_ctx, noctx),
325+
syn::Type::Group(group) => {
326+
impl_pwrite_field(ident, &group.elem, default_ctx, custom_ctx, offset, noctx)
327+
}
252328
syn::Type::Reference(reference) => match *reference.elem {
253329
syn::Type::Slice(_) => {
254330
quote! {
255-
dst.gwrite_with(self.#ident, offset, ())?
331+
dst.gwrite_with(self.#ident, #offset, ())?
256332
}
257333
}
258334
syn::Type::Path(ref path) => {
259335
if path.path.get_ident().unwrap().to_string().as_str() == "str" {
260336
quote! {
261-
dst.gwrite(self.#ident, offset)?
337+
dst.gwrite(self.#ident, #offset)?
262338
}
263339
} else {
264340
quote! {
265-
dst.gwrite_with(self.#ident, offset, #ctx)?
341+
dst.gwrite_with(self.#ident, #offset, #ctx)?
266342
}
267343
}
268344
}
269345
_ => {
270346
quote! {
271-
dst.gwrite_with(self.#ident, offset, #ctx)?
347+
dst.gwrite_with(self.#ident, #offset, #ctx)?
272348
}
273349
}
274350
},
275351
_ => {
276352
quote! {
277-
dst.gwrite_with(&self.#ident, offset, #ctx)?
353+
dst.gwrite_with(&self.#ident, #offset, #ctx)?
278354
}
279355
}
280356
}
@@ -285,18 +361,19 @@ fn impl_try_into_ctx(
285361
fields: &syn::punctuated::Punctuated<syn::Field, syn::Token![,]>,
286362
generics: &syn::Generics,
287363
) -> proc_macro2::TokenStream {
288-
let items: Vec<_> = fields
364+
let (idents, offset) = extract_idents_and_offset(fields);
365+
let ctx = fresh_name(
366+
fields,
367+
syn::Ident::new("ctx", proc_macro2::Span::call_site()),
368+
)
369+
.to_token_stream();
370+
let items: Vec<_> = idents
289371
.iter()
290-
.enumerate()
291-
.map(|(i, f)| {
292-
let ident = &f.ident.as_ref().map(|i| quote! {#i}).unwrap_or({
293-
let t = proc_macro2::Literal::usize_unsuffixed(i);
294-
quote! {#t}
295-
});
372+
.map(|(ident, f)| {
296373
let ty = &f.ty;
297374
let mut noctx = false;
298375
let custom_ctx = custom_ctx(f, &mut noctx);
299-
impl_pwrite_field(ident, ty, custom_ctx.as_ref(), noctx)
376+
impl_pwrite_field(ident, ty, &ctx, custom_ctx.as_ref(), &offset, noctx)
300377
})
301378
.collect();
302379

@@ -356,11 +433,11 @@ fn impl_try_into_ctx(
356433
impl<#fresh_lifetime, #gp > ::scroll::ctx::TryIntoCtx<::scroll::Endian> for &#fresh_lifetime #name #gn #gwref {
357434
type Error = ::scroll::Error;
358435
#[inline]
359-
fn try_into_ctx(self, dst: &mut [u8], ctx: ::scroll::Endian) -> ::scroll::export::result::Result<usize, Self::Error> {
436+
fn try_into_ctx(self, dst: &mut [u8], #ctx: ::scroll::Endian) -> ::scroll::export::result::Result<usize, Self::Error> {
360437
use ::scroll::Pwrite;
361-
let offset = &mut 0;
438+
let #offset = &mut 0;
362439
#(#items;)*
363-
Ok(*offset)
440+
Ok(*#offset)
364441
}
365442
}
366443

@@ -390,7 +467,7 @@ fn impl_pwrite(ast: &syn::DeriveInput) -> proc_macro2::TokenStream {
390467
}
391468

392469
#[proc_macro_derive(Pwrite, attributes(scroll))]
393-
pub fn derive_pwrite(input: TokenStream) -> TokenStream {
470+
pub fn derive_pwrite(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
394471
let ast: syn::DeriveInput = syn::parse(input).unwrap();
395472
let generated = impl_pwrite(&ast);
396473
generated.into()
@@ -493,7 +570,7 @@ fn impl_size_with(ast: &syn::DeriveInput) -> proc_macro2::TokenStream {
493570
}
494571

495572
#[proc_macro_derive(SizeWith, attributes(scroll))]
496-
pub fn derive_sizewith(input: TokenStream) -> TokenStream {
573+
pub fn derive_sizewith(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
497574
let ast: syn::DeriveInput = syn::parse(input).unwrap();
498575
let generated = impl_size_with(&ast);
499576
generated.into()
@@ -600,7 +677,7 @@ fn impl_from_ctx(ast: &syn::DeriveInput) -> proc_macro2::TokenStream {
600677
}
601678

602679
#[proc_macro_derive(IOread, attributes(scroll))]
603-
pub fn derive_ioread(input: TokenStream) -> TokenStream {
680+
pub fn derive_ioread(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
604681
let ast: syn::DeriveInput = syn::parse(input).unwrap();
605682
let generated = impl_from_ctx(&ast);
606683
generated.into()
@@ -711,7 +788,7 @@ fn impl_iowrite(ast: &syn::DeriveInput) -> proc_macro2::TokenStream {
711788
}
712789

713790
#[proc_macro_derive(IOwrite, attributes(scroll))]
714-
pub fn derive_iowrite(input: TokenStream) -> TokenStream {
791+
pub fn derive_iowrite(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
715792
let ast: syn::DeriveInput = syn::parse(input).unwrap();
716793
let generated = impl_iowrite(&ast);
717794
generated.into()

scroll_derive/tests/debug.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ struct Data8<T, Y> {
1212
xyz: Y,
1313
}
1414

15-
//#[derive(Debug, PartialEq, Eq, Pread, Pwrite, IOread, IOwrite, SizeWith)]
16-
#[derive(Pread, Pwrite, SizeWith, IOread, IOwrite)]
15+
#[derive(Debug, PartialEq, Eq, Pread, Pwrite, IOread, IOwrite, SizeWith)]
1716
struct Data10I(u8, u16);
1817

1918
#[derive(Debug, Pread, Pwrite)]
@@ -23,3 +22,21 @@ struct Life1<'b> {
2322
#[scroll(ctx = 5)]
2423
data: &'b [u8],
2524
}
25+
26+
#[derive(Pread, Pwrite, SizeWith, IOread, IOwrite)]
27+
struct TestHygenic {
28+
ctx: u8,
29+
offset: u32,
30+
src: i8,
31+
__offset: u8,
32+
_offset_: u8,
33+
_src: u8,
34+
_offset: u8,
35+
}
36+
37+
#[derive(Pread, Pwrite)]
38+
struct TestHygenicCtx<'a> {
39+
ctx: u8,
40+
#[scroll(ctx = ctx as usize)]
41+
_ctx: &'a [u8],
42+
}

0 commit comments

Comments
 (0)