Skip to main content

reifydb_macro_impl/
from_frame.rs

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