Skip to main content

colvec_derive/
lib.rs

1#[cfg(not(test))]
2use proc_macro::TokenStream;
3#[cfg(test)]
4use proc_macro2::TokenStream;
5use quote::quote;
6use syn::DeriveInput;
7
8#[cfg(not(test))]
9#[proc_macro_derive(ColVec)]
10pub fn colvec_derive(input:TokenStream)->TokenStream{
11	let input:DeriveInput=syn::parse_macro_input!(input);
12	colvec_derive_inner(input)
13}
14
15fn colvec_derive_inner(input:DeriveInput)->TokenStream{
16	match input.data{
17		syn::Data::Struct(syn::DataStruct{fields:syn::Fields::Named(fields_named),..})=>derive_struct(input.ident,input.vis,fields_named),
18		_=>unimplemented!("Only structs are supported"),
19	}
20}
21
22fn derive_struct(ident:syn::Ident,vis:syn::Visibility,fields:syn::FieldsNamed)->TokenStream{
23	let colvec_ident_string=format!("{ident}ColVec");
24	let colvec_ident=syn::Ident::new(&colvec_ident_string,ident.span());
25
26	let fields_count=fields.named.len();
27	#[cfg_attr(not(feature = "std"), expect(unused_mut))]
28	let mut colvec: syn::ItemStruct = syn::parse_quote!{
29		#vis struct #colvec_ident<A: ::colvec::alloc::Allocator>{
30			buf: ::colvec::raw::RawColVec<#fields_count, #ident, A>,
31			len: usize,
32		}
33	};
34
35	#[cfg(feature = "std")]
36	match colvec.generics.params.first_mut(){
37		Some(syn::GenericParam::Type(type_param))=>{
38			let eq_token=syn::Token![=](type_param.ident.span());
39			let default=syn::parse_quote!{::colvec::alloc::Global};
40			type_param.default=Some((eq_token,default));
41		},
42		// colvec expression always contains type param
43		_ => unreachable!(),
44	}
45
46	#[cfg(feature = "std")]
47	let global = quote! {
48		impl #colvec_ident<::colvec::alloc::Global>{
49			#[inline]
50			#[must_use]
51			pub const fn new() -> Self {
52				Self { buf: ::colvec::raw::RawColVec::new_in(::colvec::alloc::Global), len: 0 }
53			}
54			#[inline]
55			#[must_use]
56			#[track_caller]
57			pub fn with_capacity(capacity: usize) -> Self {
58				Self::with_capacity_in(capacity, ::colvec::alloc::Global)
59			}
60			#[inline]
61			pub unsafe fn from_raw_parts(ptr: *mut u8, length: usize, capacity: usize) -> Self {
62				unsafe { Self::from_raw_parts_in(ptr, length, capacity, ::colvec::alloc::Global) }
63			}
64		}
65	};
66
67	// this trait smuggles information about the input type into RawColVec and RawColVecInner
68	let fields_types=fields.named.iter().map(|field|field.ty.clone());
69	let struct_info = quote! {
70		impl ::colvec::raw::StructInfo<#fields_count> for #ident{
71			const LAYOUT: ::core::alloc::Layout = unsafe {
72				let size = Self::FIELDS.size();
73				let align = align_of::<#ident>();
74				::core::alloc::Layout::from_size_align_unchecked(size, align)
75			};
76			const FIELDS: ::colvec::fields::Fields<#fields_count> = ::colvec::fields::Fields::from_sizes([
77				#(size_of::<#fields_types>()),*
78			]);
79		}
80	};
81
82	let field_indices=0..fields_count;
83	let field_types=fields.named.iter().map(|field|field.ty.clone());
84	let field_idents=fields.named.iter().map(|field|field.ident.as_ref().unwrap().clone());
85	let impls = quote! {
86		impl<A: ::colvec::alloc::Allocator> #colvec_ident<A>{
87			#[inline]
88			pub const fn new_in(alloc: A) -> Self {
89				Self { buf: ::colvec::raw::RawColVec::new_in(alloc), len: 0 }
90			}
91			#[inline]
92			#[track_caller]
93			pub fn with_capacity_in(capacity: usize, alloc: A) -> Self {
94				Self { buf: ::colvec::raw::RawColVec::with_capacity_in(capacity, alloc), len: 0 }
95			}
96			#[inline]
97			pub unsafe fn from_raw_parts_in(ptr: *mut u8, length: usize, capacity: usize, alloc: A) -> Self {
98				unsafe { Self { buf: ::colvec::raw::RawColVec::from_raw_parts_in(ptr, capacity, alloc), len: length } }
99			}
100			#[must_use = "losing the pointer will leak memory"]
101			pub fn into_raw_parts(self) -> (*mut u8, usize, usize) {
102				let mut me = ::core::mem::ManuallyDrop::new(self);
103				(me.as_mut_ptr(), me.len(), me.capacity())
104			}
105			#[must_use = "losing the pointer will leak memory"]
106			pub fn into_raw_parts_with_alloc(self) -> (*mut u8, usize, usize, A) {
107				let mut me = ::core::mem::ManuallyDrop::new(self);
108				let len = me.len();
109				let capacity = me.capacity();
110				let ptr = me.as_mut_ptr();
111				let alloc = unsafe { ::core::ptr::read(me.allocator()) };
112				(ptr, len, capacity, alloc)
113			}
114			#[inline]
115			pub const fn capacity(&self) -> usize {
116				self.buf.capacity()
117			}
118			#[track_caller]
119			pub fn reserve(&mut self, additional: usize) {
120				self.buf.reserve(self.len, additional);
121			}
122			#[inline]
123			const fn as_ptr(&self) -> *const u8 {
124				// We shadow the slice method of the same name to avoid going through
125				// `deref`, which creates an intermediate reference.
126				self.buf.ptr()
127			}
128			#[inline]
129			const fn as_mut_ptr(&mut self) -> *mut u8 {
130				// We shadow the slice method of the same name to avoid going through
131				// `deref_mut`, which creates an intermediate reference.
132				self.buf.ptr()
133			}
134			#[inline]
135			pub fn allocator(&self) -> &A {
136				self.buf.allocator()
137			}
138			#[inline]
139			pub unsafe fn set_len(&mut self, new_len: usize) {
140				debug_assert!(new_len <= self.capacity());
141
142				self.len = new_len;
143			}
144			pub fn push(&mut self, value: #ident){
145				// Inform codegen that the length does not change across grow_one().
146				let len = self.len;
147				// This will panic or abort if we would allocate > isize::MAX bytes
148				// or if the length increment would overflow for zero-sized types.
149				if len == self.buf.capacity() {
150					self.buf.grow_one();
151				}
152				unsafe {
153					#(
154						let end = self.as_mut_ptr()
155							.add(self.buf.capacity() * <#ident as ::colvec::raw::StructInfo<#fields_count>>::FIELDS.offset_of(#field_indices))
156							.cast::<#field_types>()
157							.add(len);
158						::core::ptr::write(end, value.#field_idents);
159					)*
160				}
161				self.len = len + 1;
162			}
163			#[inline]
164			#[track_caller]
165			pub fn append(&mut self, other: &mut Self) {
166				unsafe {
167					self.append_elements(other);
168					other.set_len(0);
169				}
170			}
171
172			/// Appends elements to `self` from other buffer.
173			#[inline]
174			#[track_caller]
175			unsafe fn append_elements(&mut self, other: &Self) {
176				let count = other.len();
177				self.reserve(count);
178				let len = self.len();
179				unsafe {
180					<#ident as ::colvec::raw::StructInfo<#fields_count>>::FIELDS.move_fields(
181						other.as_ptr(),
182						self.as_mut_ptr(),
183						other.capacity(),
184						self.capacity(),
185						len,
186						count,
187					)
188				}
189				self.len += count;
190			}
191		   #[inline]
192			pub const fn len(&self) -> usize {
193				self.len
194			}
195		}
196	};
197
198	let field_slice_mut_fn_idents=fields.named.iter().map(|field|{
199		let ident=field.ident.as_ref().unwrap();
200		let slice_ident=format!("{ident}_slice_mut");
201		syn::Ident::new(&slice_ident,ident.span())
202	});
203	let drop = quote! {
204		impl<A: ::colvec::alloc::Allocator> Drop for #colvec_ident<A>{
205			fn drop(&mut self){
206				unsafe {
207					#(
208						::core::ptr::drop_in_place(self.#field_slice_mut_fn_idents());
209					)*
210				}
211			}
212		}
213	};
214
215	let field_indices=0..fields_count;
216	let field_types1=fields.named.iter().map(|field|field.ty.clone());
217	let field_types2=field_types1.clone();
218	let field_idents1=fields.named.iter().map(|field|field.ident.as_ref().unwrap().clone());
219	let field_idents2=field_idents1.clone();
220	let field_slice_fn_idents1=fields.named.iter().map(|field|{
221		let ident=field.ident.as_ref().unwrap();
222		let slice_ident=format!("{ident}_slice");
223		syn::Ident::new(&slice_ident,ident.span())
224	});
225	let field_slice_fn_idents2=field_slice_fn_idents1.clone();
226	let field_slice_fn_idents3=field_slice_fn_idents1.clone();
227	let field_slice_mut_fn_idents1=fields.named.iter().map(|field|{
228		let ident=field.ident.as_ref().unwrap();
229		let slice_ident=format!("{ident}_slice_mut");
230		syn::Ident::new(&slice_ident,ident.span())
231	});
232	let field_slice_mut_fn_idents2=field_slice_mut_fn_idents1.clone();
233	let clone = quote! {
234		impl<A: ::colvec::alloc::Allocator + Clone> Clone for #colvec_ident<A>
235			where
236				#(#field_types1: Clone,)*
237		{
238			fn clone(&self)->Self{
239				let alloc = self.allocator().clone();
240				struct DropGuard<'a, A: ::colvec::alloc::Allocator> {
241					colvec: &'a mut #colvec_ident<A>,
242					num_init: usize,
243				}
244				impl<'a, A: ::colvec::alloc::Allocator> Drop for DropGuard<'a, A> {
245					#[inline]
246					fn drop(&mut self) {
247						// SAFETY:
248						// items were marked initialized in the loop below
249						unsafe {
250							self.colvec.set_len(self.num_init);
251						}
252					}
253				}
254				let mut colvec = Self::with_capacity_in(self.len(), alloc);
255				let mut guard = DropGuard { colvec: &mut colvec, num_init: 0 };
256				#(
257					let #field_slice_fn_idents1 = self.#field_slice_fn_idents2();
258				)*
259				// Create the pointer once to satisfy miri
260				let cap = guard.colvec.buf.capacity();
261				let ptr = guard.colvec.as_mut_ptr();
262				#(
263					let #field_slice_mut_fn_idents1 = unsafe {
264						::core::slice::from_raw_parts_mut(
265							ptr.add(cap * <#ident as ::colvec::raw::StructInfo<#fields_count>>::FIELDS.offset_of(#field_indices))
266								.cast::<::core::mem::MaybeUninit<#field_types2>>(),
267							self.len
268						)
269					};
270				)*
271				for i in 0..self.len() {
272					guard.num_init = i;
273					// Clone all fields first. Ensures the cloned values are dropped if clone panics.
274					#(
275						let #field_idents1 = unsafe { #field_slice_fn_idents3.get_unchecked(i) }.clone();
276					)*
277					#(
278						unsafe{ #field_slice_mut_fn_idents2.get_unchecked_mut(i).write(#field_idents2) };
279					)*
280				}
281				::core::mem::forget(guard);
282				// SAFETY:
283				// the colvec was allocated and initialized above to at least this length.
284				unsafe {
285					colvec.set_len(self.len());
286				}
287				colvec
288			}
289		}
290	};
291
292	let field_indices=0..fields_count;
293	let field_types=fields.named.iter().map(|field|field.ty.clone());
294	let field_slice_fn_idents=fields.named.iter().map(|field|{
295		let ident=field.ident.as_ref().unwrap();
296		let slice_ident=format!("{ident}_slice");
297		syn::Ident::new(&slice_ident,ident.span())
298	});
299	let field_slice_mut_fn_idents=fields.named.iter().map(|field|{
300		let ident=field.ident.as_ref().unwrap();
301		let slice_ident=format!("{ident}_slice_mut");
302		syn::Ident::new(&slice_ident,ident.span())
303	});
304	let field_access = quote! {
305		impl<A: ::colvec::alloc::Allocator> #colvec_ident<A>{
306			#(
307				#[inline]
308				pub const fn #field_slice_fn_idents(&self) -> &[#field_types] {
309					unsafe {
310						::core::slice::from_raw_parts(
311							self.as_ptr()
312								.add(self.buf.capacity() * <#ident as ::colvec::raw::StructInfo<#fields_count>>::FIELDS.offset_of(#field_indices))
313								.cast::<#field_types>(),
314							self.len
315						)
316					}
317				}
318				#[inline]
319				pub const fn #field_slice_mut_fn_idents(&mut self) -> &mut [#field_types] {
320					unsafe {
321						::core::slice::from_raw_parts_mut(
322							self.as_mut_ptr()
323								.add(self.buf.capacity() * <#ident as ::colvec::raw::StructInfo<#fields_count>>::FIELDS.offset_of(#field_indices))
324								.cast::<#field_types>(),
325							self.len
326						)
327					}
328				}
329			)*
330		}
331	};
332
333	#[cfg_attr(not(feature = "std"), expect(unused_mut))]
334	let mut output=quote! {
335		#colvec
336
337		#struct_info
338
339		#drop
340		#clone
341		#impls
342		#field_access
343	};
344
345	#[cfg(feature = "std")]
346	output.extend(global);
347
348	output.into()
349}
350
351#[cfg(test)]
352mod tests {
353	use super::*;
354	use syn::parse_quote;
355
356	#[test]
357	fn snapshot_test1() {
358		let test1:syn::ItemStruct = parse_quote! {
359			pub struct Test{
360				field0:u8,
361				field1:Option<u8>,
362				field2:i16,
363				field3:u32,
364			}
365		};
366
367		let output = colvec_derive_inner(test1.into());
368
369		// pretend it outputs a file
370		let as_file = syn::parse_file(&output.to_string()).unwrap();
371
372		// format it in a pretty way
373		let formatted = prettyplease::unparse(&as_file);
374
375		// snapshot-test it
376		insta::assert_snapshot!(formatted);
377	}
378}