reifydb_macro_impl/
from_frame.rs

1// Copyright (c) reifydb.com 2025
2// This file is licensed under the MIT, see license.md file
3
4//! Code generation for the FromFrame derive macro.
5
6use proc_macro2::{TokenStream, TokenTree};
7
8use crate::{
9	generate::{
10		arrow, braces, brackets, fat_arrow, ident, literal_str, literal_usize, parens, path, path_sep, punct,
11		punct_joint, underscore,
12	},
13	parse::{ParsedField, ParsedStruct},
14};
15
16/// Expand a parsed struct into the FromFrame implementation.
17pub fn expand(parsed: ParsedStruct) -> TokenStream {
18	let struct_name = parsed.name.to_string();
19	let struct_name_lit = literal_str(&struct_name);
20	let crate_path = &parsed.crate_path;
21
22	let mut tokens = Vec::new();
23
24	// impl ::crate_path::FromFrame for StructName
25	tokens.push(ident("impl"));
26	tokens.extend(path(&["", crate_path, "FromFrame"]));
27	tokens.push(ident("for"));
28	tokens.push(TokenTree::Ident(parsed.name.clone()));
29
30	// Implementation body
31	let impl_body = generate_from_frame_impl(&parsed.fields, &struct_name_lit, crate_path);
32	tokens.push(braces(impl_body));
33
34	// Note: TryFrom<&Frame> impl is NOT generated because it would violate orphan rules
35	// when the derive is used outside reifydb-type. Use FromFrame::from_frame() instead.
36
37	tokens.into_iter().collect()
38}
39
40/// Generate the FromFrame::from_frame method body.
41fn generate_from_frame_impl(fields: &[ParsedField], struct_name_lit: &TokenTree, crate_path: &str) -> Vec<TokenTree> {
42	let mut tokens = Vec::new();
43
44	// fn from_frame(frame: &::crate_path::Frame) -> Result<Vec<Self>, ::crate_path::FromFrameError>
45	tokens.push(ident("fn"));
46	tokens.push(ident("from_frame"));
47	tokens.push(parens([ident("frame"), punct(':'), punct('&')]
48		.into_iter()
49		.chain(path(&["", crate_path, "Frame"]))));
50	tokens.extend(arrow());
51	tokens.push(ident("Result"));
52	tokens.push(punct('<'));
53	tokens.push(ident("Vec"));
54	tokens.push(punct('<'));
55	tokens.push(ident("Self"));
56	tokens.push(punct('>'));
57	tokens.push(punct(','));
58	tokens.extend(path(&["", crate_path, "FromFrameError"]));
59	tokens.push(punct('>'));
60
61	// Method body
62	let mut body = Vec::new();
63
64	// Column lookups
65	for field in fields {
66		if field.attrs.skip {
67			continue;
68		}
69		body.extend(generate_column_lookup(field, struct_name_lit, crate_path));
70	}
71
72	// Row count
73	body.push(ident("let"));
74	body.push(ident("row_count"));
75	body.push(punct('='));
76	body.push(ident("frame"));
77	body.push(punct('.'));
78	body.push(ident("columns"));
79	body.push(punct('.'));
80	body.push(ident("first"));
81	body.push(parens([]));
82	body.push(punct('.'));
83	body.push(ident("map"));
84	body.push(parens([
85		punct('|'),
86		ident("c"),
87		punct('|'),
88		ident("c"),
89		punct('.'),
90		ident("data"),
91		punct('.'),
92		ident("len"),
93		parens([]),
94	]));
95	body.push(punct('.'));
96	body.push(ident("unwrap_or"));
97	body.push(parens([literal_usize(0)]));
98	body.push(punct(';'));
99
100	// Field extractions
101	for field in fields {
102		if field.attrs.skip {
103			continue;
104		}
105		body.extend(generate_field_extraction(field, crate_path));
106	}
107
108	// Result construction
109	body.push(ident("let"));
110	body.push(ident("mut"));
111	body.push(ident("result"));
112	body.push(punct('='));
113	body.push(ident("Vec"));
114	body.extend(path_sep());
115	body.push(ident("with_capacity"));
116	body.push(parens([ident("row_count")]));
117	body.push(punct(';'));
118
119	// for loop
120	body.push(ident("for"));
121	body.push(ident("i"));
122	body.push(ident("in"));
123	body.push(literal_usize(0));
124	body.extend([punct_joint('.'), punct('.')]);
125	body.push(ident("row_count"));
126
127	// Loop body
128	let mut loop_body = Vec::new();
129	loop_body.push(ident("result"));
130	loop_body.push(punct('.'));
131	loop_body.push(ident("push"));
132
133	// Self { field: values_field[i].clone(), ... }
134	let mut constructor = vec![ident("Self")];
135	let mut field_inits = Vec::new();
136	for (i, field) in fields.iter().enumerate() {
137		if i > 0 {
138			field_inits.push(punct(','));
139		}
140		field_inits.push(TokenTree::Ident(field.name.clone()));
141		field_inits.push(punct(':'));
142		if field.attrs.skip {
143			field_inits.extend(path(&["", "std", "default", "Default", "default"]));
144			field_inits.push(parens([]));
145		} else {
146			let values_var = format!("values_{}", field.safe_name());
147			field_inits.push(ident(&values_var));
148			field_inits.push(brackets([ident("i")]));
149			field_inits.push(punct('.'));
150			field_inits.push(ident("clone"));
151			field_inits.push(parens([]));
152		}
153	}
154	constructor.push(braces(field_inits));
155	loop_body.push(parens(constructor));
156	loop_body.push(punct(';'));
157
158	body.push(braces(loop_body));
159
160	// Ok(result)
161	body.push(ident("Ok"));
162	body.push(parens([ident("result")]));
163
164	tokens.push(braces(body));
165	tokens
166}
167
168/// Generate column lookup for a field.
169fn generate_column_lookup(field: &ParsedField, struct_name_lit: &TokenTree, crate_path: &str) -> Vec<TokenTree> {
170	let mut tokens = Vec::new();
171	let column_name = field.column_name();
172	let col_var = format!("col_{}", field.safe_name());
173
174	tokens.push(ident("let"));
175	tokens.push(ident(&col_var));
176	tokens.push(punct(':'));
177
178	if field.attrs.optional {
179		// Option<&::crate_path::FrameColumn>
180		tokens.push(ident("Option"));
181		tokens.push(punct('<'));
182		tokens.push(punct('&'));
183		tokens.extend(path(&["", crate_path, "FrameColumn"]));
184		tokens.push(punct('>'));
185		tokens.push(punct('='));
186		tokens.push(ident("frame"));
187		tokens.push(punct('.'));
188		tokens.push(ident("columns"));
189		tokens.push(punct('.'));
190		tokens.push(ident("iter"));
191		tokens.push(parens([]));
192		tokens.push(punct('.'));
193		tokens.push(ident("find"));
194		tokens.push(parens([
195			punct('|'),
196			ident("c"),
197			punct('|'),
198			ident("c"),
199			punct('.'),
200			ident("name"),
201			punct_joint('='),
202			punct('='),
203			literal_str(&column_name),
204		]));
205	} else {
206		// &::crate_path::FrameColumn
207		tokens.push(punct('&'));
208		tokens.extend(path(&["", crate_path, "FrameColumn"]));
209		tokens.push(punct('='));
210		tokens.push(ident("frame"));
211		tokens.push(punct('.'));
212		tokens.push(ident("columns"));
213		tokens.push(punct('.'));
214		tokens.push(ident("iter"));
215		tokens.push(parens([]));
216		tokens.push(punct('.'));
217		tokens.push(ident("find"));
218		tokens.push(parens([
219			punct('|'),
220			ident("c"),
221			punct('|'),
222			ident("c"),
223			punct('.'),
224			ident("name"),
225			punct_joint('='),
226			punct('='),
227			literal_str(&column_name),
228		]));
229		tokens.push(punct('.'));
230		tokens.push(ident("ok_or_else"));
231
232		// || ::crate_path::FromFrameError::MissingColumn { ... }
233		let mut error_closure = Vec::new();
234		error_closure.extend([punct('|'), punct('|')]);
235		error_closure.extend(path(&["", crate_path, "FromFrameError", "MissingColumn"]));
236		error_closure.push(braces([
237			ident("column"),
238			punct(':'),
239			literal_str(&column_name),
240			punct('.'),
241			ident("to_string"),
242			parens([]),
243			punct(','),
244			ident("struct_name"),
245			punct(':'),
246			struct_name_lit.clone(),
247			punct(','),
248		]));
249		tokens.push(parens(error_closure));
250		tokens.push(punct('?'));
251	}
252
253	tokens.push(punct(';'));
254	tokens
255}
256
257/// Generate field extraction for a field.
258fn generate_field_extraction(field: &ParsedField, crate_path: &str) -> Vec<TokenTree> {
259	let mut tokens = Vec::new();
260	let column_name = field.column_name();
261	let col_var = format!("col_{}", field.safe_name());
262	let values_var = format!("values_{}", field.safe_name());
263
264	tokens.push(ident("let"));
265	tokens.push(ident(&values_var));
266	tokens.push(punct(':'));
267
268	// Type: Vec<FieldType>
269	tokens.push(ident("Vec"));
270	tokens.push(punct('<'));
271	tokens.extend(field.ty.iter().cloned());
272	tokens.push(punct('>'));
273
274	let trait_name = if field.attrs.coerce {
275		"TryFromValueCoerce"
276	} else {
277		"TryFromValue"
278	};
279
280	let method_name = if field.attrs.coerce {
281		"try_from_value_coerce"
282	} else {
283		"try_from_value"
284	};
285
286	if field.attrs.optional {
287		// let values_x = match col_x { Some(col) => ..., None => vec![None; row_count] };
288		tokens.push(punct('='));
289
290		// match col_var { Some(col) => ..., None => vec![None; row_count] }
291		tokens.push(ident("match"));
292		tokens.push(ident(&col_var));
293
294		let mut match_body = Vec::new();
295
296		// Some(col) => { ... }
297		match_body.push(ident("Some"));
298		match_body.push(parens([ident("col")]));
299		match_body.extend(fat_arrow());
300
301		let mut some_body = Vec::new();
302		some_body.push(ident("col"));
303		some_body.push(punct('.'));
304		some_body.push(ident("data"));
305		some_body.push(punct('.'));
306		some_body.push(ident("iter"));
307		some_body.push(parens([]));
308		some_body.push(punct('.'));
309		some_body.push(ident("enumerate"));
310		some_body.push(parens([]));
311		some_body.push(punct('.'));
312		some_body.push(ident("map"));
313		some_body.push(parens(generate_optional_map_closure(
314			trait_name,
315			method_name,
316			&column_name,
317			crate_path,
318		)));
319		some_body.push(punct('.'));
320		some_body.push(ident("collect"));
321		// ::<Result<_, ::crate_path::FromFrameError>>
322		some_body.extend(path_sep());
323		some_body.push(punct('<'));
324		some_body.push(ident("Result"));
325		some_body.push(punct('<'));
326		some_body.push(underscore());
327		some_body.push(punct(','));
328		some_body.extend(path(&["", crate_path, "FromFrameError"]));
329		some_body.push(punct('>'));
330		some_body.push(punct('>'));
331		some_body.push(parens([]));
332		some_body.push(punct('?'));
333
334		match_body.push(braces(some_body));
335		match_body.push(punct(','));
336
337		// None => vec![None; row_count]
338		match_body.push(ident("None"));
339		match_body.extend(fat_arrow());
340		match_body.push(ident("vec"));
341		match_body.push(punct('!'));
342		match_body.push(brackets([ident("None"), punct(';'), ident("row_count")]));
343		match_body.push(punct(','));
344
345		tokens.push(braces(match_body));
346	} else {
347		// let values_x = col_x.data.iter()...collect::<Result<_, FromFrameError>>()?;
348		tokens.push(punct('='));
349
350		tokens.push(ident(&col_var));
351		tokens.push(punct('.'));
352		tokens.push(ident("data"));
353		tokens.push(punct('.'));
354		tokens.push(ident("iter"));
355		tokens.push(parens([]));
356		tokens.push(punct('.'));
357		tokens.push(ident("enumerate"));
358		tokens.push(parens([]));
359		tokens.push(punct('.'));
360		tokens.push(ident("map"));
361		tokens.push(parens(generate_required_map_closure(
362			trait_name,
363			method_name,
364			&column_name,
365			&field.ty,
366			crate_path,
367		)));
368		tokens.push(punct('.'));
369		tokens.push(ident("collect"));
370		// ::<Result<_, ::crate_path::FromFrameError>>
371		tokens.extend(path_sep());
372		tokens.push(punct('<'));
373		tokens.push(ident("Result"));
374		tokens.push(punct('<'));
375		tokens.push(underscore());
376		tokens.push(punct(','));
377		tokens.extend(path(&["", crate_path, "FromFrameError"]));
378		tokens.push(punct('>'));
379		tokens.push(punct('>'));
380		tokens.push(parens([]));
381		tokens.push(punct('?'));
382	}
383
384	tokens.push(punct(';'));
385	tokens
386}
387
388/// Generate the map closure for optional fields.
389fn generate_optional_map_closure(
390	trait_name: &str,
391	method_name: &str,
392	column_name: &str,
393	crate_path: &str,
394) -> Vec<TokenTree> {
395	let mut tokens = Vec::new();
396
397	// |(row, v)| { if matches!(v, ...) { Ok(None) } else { ... } }
398	tokens.push(punct('|'));
399	tokens.push(parens([ident("row"), punct(','), ident("v")]));
400	tokens.push(punct('|'));
401
402	let mut body = Vec::new();
403
404	// if matches!(v, ::crate_path::Value::Undefined)
405	body.push(ident("if"));
406	body.push(ident("matches"));
407	body.push(punct('!'));
408	body.push(parens([ident("v"), punct(',')].into_iter().chain(path(&["", crate_path, "Value", "Undefined"]))));
409
410	// { Ok(None) }
411	body.push(braces([ident("Ok"), parens([ident("None")])]));
412
413	// else { <_ as Trait>::method(&v).map(Some).map_err(...) }
414	body.push(ident("else"));
415
416	let mut else_body = Vec::new();
417	else_body.push(punct('<'));
418	else_body.push(underscore());
419	else_body.push(ident("as"));
420	else_body.extend(path(&["", crate_path, trait_name]));
421	else_body.push(punct('>'));
422	else_body.extend(path_sep());
423	else_body.push(ident(method_name));
424	else_body.push(parens([punct('&'), ident("v")]));
425	else_body.push(punct('.'));
426	else_body.push(ident("map"));
427	else_body.push(parens([ident("Some")]));
428	else_body.push(punct('.'));
429	else_body.push(ident("map_err"));
430	else_body.push(parens(generate_error_closure(column_name, crate_path)));
431
432	body.push(braces(else_body));
433
434	tokens.push(braces(body));
435	tokens
436}
437
438/// Generate the map closure for required fields.
439fn generate_required_map_closure(
440	trait_name: &str,
441	method_name: &str,
442	column_name: &str,
443	field_ty: &[TokenTree],
444	crate_path: &str,
445) -> Vec<TokenTree> {
446	let mut tokens = Vec::new();
447
448	// |(row, v)| { <Type as Trait>::method(&v).map_err(...) }
449	tokens.push(punct('|'));
450	tokens.push(parens([ident("row"), punct(','), ident("v")]));
451	tokens.push(punct('|'));
452
453	let mut body = Vec::new();
454	body.push(punct('<'));
455	body.extend(field_ty.iter().cloned());
456	body.push(ident("as"));
457	body.extend(path(&["", crate_path, trait_name]));
458	body.push(punct('>'));
459	body.extend(path_sep());
460	body.push(ident(method_name));
461	body.push(parens([punct('&'), ident("v")]));
462	body.push(punct('.'));
463	body.push(ident("map_err"));
464	body.push(parens(generate_error_closure(column_name, crate_path)));
465
466	tokens.push(braces(body));
467	tokens
468}
469
470/// Generate the error closure for map_err.
471fn generate_error_closure(column_name: &str, crate_path: &str) -> Vec<TokenTree> {
472	let mut tokens = Vec::new();
473
474	// |e| ::crate_path::FromFrameError::ValueError { column: "...", row, error: e }
475	tokens.push(punct('|'));
476	tokens.push(ident("e"));
477	tokens.push(punct('|'));
478	tokens.extend(path(&["", crate_path, "FromFrameError", "ValueError"]));
479	tokens.push(braces([
480		ident("column"),
481		punct(':'),
482		literal_str(column_name),
483		punct('.'),
484		ident("to_string"),
485		parens([]),
486		punct(','),
487		ident("row"),
488		punct(','),
489		ident("error"),
490		punct(':'),
491		ident("e"),
492		punct(','),
493	]));
494
495	tokens
496}