1use proc_macro2::{Ident, TokenStream};
18use quote::quote;
19use syn::Lifetime;
20use syn::spanned::Spanned;
21
22pub(crate) fn last_is_arena(func: &syn::ItemFn) -> bool {
24 func.sig.inputs.last().map_or(false, |last| {
25 if let syn::FnArg::Typed(pat) = last {
26 if let syn::Type::Reference(reference) = &*pat.ty {
27 if let syn::Type::Path(path) = &*reference.elem {
28 return path.path.is_ident("RowArena");
29 }
30 }
31 }
32 false
33 })
34}
35
36pub(crate) fn sql_args(func: &syn::ItemFn) -> Vec<&syn::FnArg> {
39 let end = func.sig.inputs.len() - usize::from(last_is_arena(func));
40 func.sig
41 .inputs
42 .iter()
43 .take(end)
44 .skip(usize::from(has_receiver(func)))
45 .collect()
46}
47
48pub(crate) fn has_receiver(func: &syn::ItemFn) -> bool {
50 matches!(func.sig.inputs.first(), Some(syn::FnArg::Receiver(_)))
51}
52
53fn is_nullable_type(ty: &syn::Type) -> bool {
59 if let syn::Type::Path(type_path) = ty {
60 if let Some(last_segment) = type_path.path.segments.last() {
61 let ident = &last_segment.ident;
62 if ident == "Option" || ident == "Datum" {
63 return true;
64 }
65 if ident == "OptionalArg" {
66 if let syn::PathArguments::AngleBracketed(args) = &last_segment.arguments {
68 if let Some(syn::GenericArgument::Type(inner_ty)) = args.args.first() {
69 return is_nullable_type(inner_ty);
70 }
71 }
72 return false;
73 }
74 }
75 }
76 false
77}
78
79pub(crate) fn is_variadic_arg(arg: &syn::FnArg) -> bool {
82 let syn::FnArg::Typed(pat) = arg else {
83 return false;
84 };
85 let syn::Type::Path(path) = &*pat.ty else {
86 return false;
87 };
88 path.path
89 .segments
90 .last()
91 .is_some_and(|segment| segment.ident == "Variadic" || segment.ident == "OptionalArg")
92}
93
94fn is_variadic_type(ty: &syn::Type) -> bool {
96 if let syn::Type::Path(type_path) = ty {
97 if let Some(last_segment) = type_path.path.segments.last() {
98 return last_segment.ident == "Variadic";
99 }
100 }
101 false
102}
103
104fn variadic_element_is_nullable(ty: &syn::Type) -> bool {
106 if let syn::Type::Path(type_path) = ty {
107 if let Some(last_segment) = type_path.path.segments.last() {
108 if let syn::PathArguments::AngleBracketed(args) = &last_segment.arguments {
109 if let Some(syn::GenericArgument::Type(inner_ty)) = args.args.first() {
110 return is_nullable_type(inner_ty);
111 }
112 }
113 }
114 }
115 false
116}
117
118pub(crate) fn non_nullable_position_checks(param_types: &[syn::Type]) -> Vec<TokenStream> {
124 let mut checks = Vec::new();
125 for (i, ty) in param_types.iter().enumerate() {
126 if is_variadic_type(ty) {
127 if !variadic_element_is_nullable(ty) {
128 checks.push(quote! { || input_types.iter().skip(#i).any(|t| t.nullable) });
129 }
130 } else if !is_nullable_type(ty) {
131 checks.push(quote! { || input_types.get(#i).map_or(false, |t| t.nullable) });
132 }
133 }
134 checks
135}
136
137pub(crate) fn camel_case(ident: &Ident) -> Ident {
139 let mut result = String::new();
140 let mut capitalize_next = true;
141 for c in ident.to_string().chars() {
142 if c == '_' {
143 capitalize_next = true;
144 } else if capitalize_next {
145 result.push(c.to_ascii_uppercase());
146 capitalize_next = false;
147 } else {
148 result.push(c);
149 }
150 }
151 Ident::new(&result, ident.span())
152}
153
154pub(crate) fn find_generic_type_params(func: &syn::ItemFn) -> Vec<Ident> {
157 func.sig
158 .generics
159 .params
160 .iter()
161 .filter_map(|p| {
162 if let syn::GenericParam::Type(tp) = p {
163 Some(tp.ident.clone())
164 } else {
165 None
166 }
167 })
168 .collect()
169}
170
171#[derive(Debug, Clone)]
173enum GenericUsage {
174 Absent,
176 Bare,
178 InContainer(syn::TypePath),
181}
182
183impl PartialEq for GenericUsage {
184 fn eq(&self, other: &Self) -> bool {
185 match (self, other) {
186 (GenericUsage::Absent, GenericUsage::Absent) => true,
187 (GenericUsage::Bare, GenericUsage::Bare) => true,
188 (GenericUsage::InContainer(a), GenericUsage::InContainer(b)) => {
189 container_idents_match(a, b)
190 }
191 _ => false,
192 }
193 }
194}
195
196impl Eq for GenericUsage {}
197
198fn container_idents_match(a: &syn::TypePath, b: &syn::TypePath) -> bool {
204 let a_idents: Vec<_> = a.path.segments.iter().map(|s| &s.ident).collect();
205 let b_idents: Vec<_> = b.path.segments.iter().map(|s| &s.ident).collect();
206 a_idents == b_idents
207}
208
209fn classify_generic_usage(ty: &syn::Type, generic_name: &Ident) -> GenericUsage {
216 match ty {
217 syn::Type::Path(type_path) => {
218 if type_path.path.is_ident(generic_name) {
219 return GenericUsage::Bare;
220 }
221 if let Some(last) = type_path.path.segments.last() {
222 let ident_str = last.ident.to_string();
223 if ident_str == "Option" || ident_str == "Result" || ident_str == "ExcludeNull" {
225 if let syn::PathArguments::AngleBracketed(args) = &last.arguments {
226 if let Some(syn::GenericArgument::Type(inner)) = args.args.first() {
227 return classify_generic_usage(inner, generic_name);
228 }
229 }
230 }
231 if let syn::PathArguments::AngleBracketed(args) = &last.arguments {
234 let has_generic_arg = args.args.iter().any(|arg| {
235 if let syn::GenericArgument::Type(inner) = arg {
236 type_contains_ident(inner, generic_name)
237 } else {
238 false
239 }
240 });
241 if has_generic_arg {
242 let erased = erase_generic_param(ty, generic_name);
244 if let syn::Type::Path(erased_path) = erased {
245 return GenericUsage::InContainer(erased_path);
246 }
247 }
248 for arg in &args.args {
252 if let syn::GenericArgument::Type(inner) = arg {
253 let inner_usage = classify_generic_usage(inner, generic_name);
254 if inner_usage != GenericUsage::Absent {
255 return inner_usage;
256 }
257 }
258 }
259 }
260 }
261 GenericUsage::Absent
262 }
263 syn::Type::Reference(r) => classify_generic_usage(&r.elem, generic_name),
264 syn::Type::Tuple(t) => {
265 let mut best = GenericUsage::Absent;
268 for elem in &t.elems {
269 let usage = classify_generic_usage(elem, generic_name);
270 match (&best, &usage) {
271 (GenericUsage::Absent, _) => best = usage,
272 (GenericUsage::Bare, u) if *u != GenericUsage::Absent => best = usage.clone(),
273 _ => {
274 if usage != GenericUsage::Absent && usage != best {
275 return GenericUsage::Bare;
277 }
278 }
279 }
280 }
281 best
282 }
283 _ => GenericUsage::Absent,
284 }
285}
286
287fn type_contains_ident(ty: &syn::Type, ident: &Ident) -> bool {
289 match ty {
290 syn::Type::Path(type_path) => {
291 if type_path.path.is_ident(ident) {
292 return true;
293 }
294 if let Some(last) = type_path.path.segments.last() {
295 if let syn::PathArguments::AngleBracketed(args) = &last.arguments {
296 return args.args.iter().any(|arg| {
297 if let syn::GenericArgument::Type(inner) = arg {
298 type_contains_ident(inner, ident)
299 } else {
300 false
301 }
302 });
303 }
304 }
305 false
306 }
307 syn::Type::Reference(r) => type_contains_ident(&r.elem, ident),
308 syn::Type::Tuple(t) => t.elems.iter().any(|e| type_contains_ident(e, ident)),
309 _ => false,
310 }
311}
312
313pub(crate) fn is_option_wrapped(ty: &syn::Type) -> bool {
315 if let syn::Type::Path(type_path) = ty {
316 if let Some(last) = type_path.path.segments.last() {
317 return last.ident == "Option";
318 }
319 }
320 false
321}
322
323pub(crate) fn derive_output_type_for_generics(
335 input_types: &[syn::Type],
336 output_ty: &syn::Type,
337 generic_names: &[Ident],
338 is_unary: bool,
339) -> darling::Result<Option<TokenStream>> {
340 let Some((generic_name, output_usage)) = generic_names.iter().find_map(|gn| {
342 let usage = classify_generic_usage(output_ty, gn);
343 (usage != GenericUsage::Absent).then_some((gn, usage))
344 }) else {
345 return Ok(None);
346 };
347 derive_output_type_for_generic(input_types, output_ty, generic_name, output_usage, is_unary)
348 .map(Some)
349}
350
351fn derive_output_type_for_generic(
358 input_types: &[syn::Type],
359 output_ty: &syn::Type,
360 generic_name: &Ident,
361 output_usage: GenericUsage,
362 is_unary: bool,
363) -> darling::Result<TokenStream> {
364 let nullable = is_option_wrapped(output_ty);
365
366 let mut container_input: Option<(usize, GenericUsage)> = None;
369 for (i, ty) in input_types.iter().enumerate() {
370 let usage = classify_generic_usage(ty, generic_name);
371 match &usage {
372 GenericUsage::InContainer(_) => {
373 container_input = Some((i, usage));
374 break;
375 }
376 GenericUsage::Bare => {
377 if container_input.is_none() {
379 container_input = Some((i, usage));
380 }
381 }
382 GenericUsage::Absent => {}
383 }
384 }
385
386 let (pos, source_usage) = container_input.ok_or_else(|| {
387 darling::Error::custom(format!(
388 "generic parameter {generic_name} appears in the output type but not in any input type",
389 ))
390 })?;
391
392 let input_access = if is_unary {
394 quote! { input_type }
395 } else {
396 let pos_lit = syn::Index::from(pos);
397 quote! { input_types[#pos_lit] }
398 };
399
400 let consistency_checks = if !is_unary {
404 let mut checks = Vec::new();
405 for (i, ty) in input_types.iter().enumerate() {
406 if i == pos {
407 continue;
408 }
409 let usage = classify_generic_usage(ty, generic_name);
410 if usage == GenericUsage::Absent {
411 continue;
412 }
413 let primary_elem = element_type_expr(&input_access, &source_usage);
414 let i_lit = syn::Index::from(i);
415 let other_access = quote! { input_types[#i_lit] };
416 let other_elem = element_type_expr(&other_access, &usage);
417 let generic_str = generic_name.to_string();
418 checks.push(quote! {
419 mz_ore::soft_assert_or_log!(
420 #primary_elem.base_eq(#other_elem),
421 "auto-derived sqlfunc output type inference found inconsistent \
422 SQL types for generic {} across inputs: {:?} vs {:?}; \
423 this indicates a bug in polymorphic coercion, builtin \
424 declaration, or sqlfunc inference",
425 #generic_str,
426 #primary_elem,
427 #other_elem,
428 );
429 });
430 }
431 quote! { #(#checks)* }
432 } else {
433 quote! {}
434 };
435
436 let expr = match (&output_usage, &source_usage) {
439 (GenericUsage::Bare, GenericUsage::InContainer(in_container)) => {
441 let in_c = elide_lifetimes(in_container);
442 quote! {
443 {
444 #consistency_checks
445 <#in_c as mz_repr::SqlContainerType>::unwrap_element_type(
446 &#input_access.scalar_type
447 ).clone().nullable(#nullable)
448 }
449 }
450 }
451 (GenericUsage::Bare, GenericUsage::Bare) => {
453 quote! {
454 {
455 #consistency_checks
456 #input_access.scalar_type.clone().nullable(#nullable)
457 }
458 }
459 }
460 (GenericUsage::InContainer(out_container), GenericUsage::InContainer(in_container)) => {
463 let out_c = elide_lifetimes(out_container);
464 let in_c = elide_lifetimes(in_container);
465 quote! {
466 {
467 #consistency_checks
468 <#out_c as mz_repr::SqlContainerType>::wrap_element_type(
469 <#in_c as mz_repr::SqlContainerType>::unwrap_element_type(
470 &#input_access.scalar_type
471 ).clone()
472 ).nullable(#nullable)
473 }
474 }
475 }
476 _ => {
478 return Err(darling::Error::custom(format!(
479 "cannot auto-derive output_type_expr: output uses T as {:?} but \
480 the first T-containing input uses T as {:?}",
481 output_usage, source_usage
482 )));
483 }
484 };
485
486 Ok(expr)
487}
488
489fn element_type_expr(input_access: &TokenStream, usage: &GenericUsage) -> TokenStream {
492 match usage {
493 GenericUsage::Bare => {
494 quote! { &#input_access.scalar_type }
495 }
496 GenericUsage::InContainer(container) => {
497 let c = elide_lifetimes(container);
498 quote! {
499 <#c as mz_repr::SqlContainerType>::unwrap_element_type(
500 &#input_access.scalar_type
501 )
502 }
503 }
504 GenericUsage::Absent => unreachable!("element_type_expr called with Absent usage"),
505 }
506}
507
508fn elide_lifetimes(tp: &syn::TypePath) -> syn::TypePath {
515 let mut tp = tp.clone();
516 for segment in &mut tp.path.segments {
517 if let syn::PathArguments::AngleBracketed(args) = &mut segment.arguments {
518 for arg in &mut args.args {
519 match arg {
520 syn::GenericArgument::Lifetime(lt) => {
521 *lt = Lifetime::new("'_", lt.span());
522 }
523 syn::GenericArgument::Type(ty) => {
524 elide_lifetimes_in_type(ty);
525 }
526 _ => {}
527 }
528 }
529 }
530 }
531 tp
532}
533
534fn elide_lifetimes_in_type(ty: &mut syn::Type) {
536 match ty {
537 syn::Type::Path(tp) => {
538 *tp = elide_lifetimes(tp);
539 }
540 syn::Type::Reference(r) => {
541 if let Some(lt) = &mut r.lifetime {
542 *lt = Lifetime::new("'_", lt.span());
543 }
544 elide_lifetimes_in_type(&mut r.elem);
545 }
546 syn::Type::Tuple(t) => {
547 for elem in &mut t.elems {
548 elide_lifetimes_in_type(elem);
549 }
550 }
551 _ => {}
552 }
553}
554
555fn erase_generic_param(ty: &syn::Type, generic_name: &Ident) -> syn::Type {
560 match ty {
561 syn::Type::Path(type_path) => {
562 if type_path.path.is_ident(generic_name) {
563 return syn::parse_quote!(Datum<'a>);
564 }
565 let mut type_path = type_path.clone();
566 for segment in &mut type_path.path.segments {
567 if let syn::PathArguments::AngleBracketed(args) = &mut segment.arguments {
568 for arg in &mut args.args {
569 if let syn::GenericArgument::Type(inner) = arg {
570 *inner = erase_generic_param(inner, generic_name);
571 }
572 }
573 }
574 }
575 syn::Type::Path(type_path)
576 }
577 syn::Type::Reference(r) => {
578 let elem = Box::new(erase_generic_param(&r.elem, generic_name));
579 syn::Type::Reference(syn::TypeReference { elem, ..r.clone() })
580 }
581 syn::Type::Tuple(t) => {
582 let elems = t
583 .elems
584 .iter()
585 .map(|e| erase_generic_param(e, generic_name))
586 .collect();
587 syn::Type::Tuple(syn::TypeTuple { elems, ..t.clone() })
588 }
589 _ => ty.clone(),
590 }
591}
592
593pub(crate) fn erase_all_generic_params(ty: &syn::Type, generic_names: &[Ident]) -> syn::Type {
595 let mut ty = ty.clone();
596 for gn in generic_names {
597 ty = erase_generic_param(&ty, gn);
598 }
599 ty
600}
601
602pub(crate) fn patch_lifetimes(ty: &syn::Type) -> syn::Type {
605 match ty {
606 syn::Type::Reference(r) => {
607 let elem = Box::new(patch_lifetimes(&r.elem));
608 if r.lifetime.is_none() {
609 syn::Type::Reference(syn::TypeReference {
610 lifetime: Some(Lifetime::new("'a", r.span())),
611 elem,
612 ..r.clone()
613 })
614 } else {
615 syn::Type::Reference(syn::TypeReference { elem, ..r.clone() })
616 }
617 }
618 syn::Type::Tuple(t) => {
619 let elems = t.elems.iter().map(patch_lifetimes).collect();
620 syn::Type::Tuple(syn::TypeTuple { elems, ..t.clone() })
621 }
622 syn::Type::Path(p) => {
623 let mut p = p.clone();
624 for segment in &mut p.path.segments {
625 if let syn::PathArguments::AngleBracketed(args) = &mut segment.arguments {
626 for arg in &mut args.args {
627 if let syn::GenericArgument::Type(ty) = arg {
628 *ty = patch_lifetimes(ty);
629 }
630 }
631 }
632 }
633 syn::Type::Path(p)
634 }
635 _ => ty.clone(),
636 }
637}
638
639pub(crate) fn output_type(arg: &syn::ItemFn) -> Result<&syn::Type, syn::Error> {
642 match &arg.sig.output {
643 syn::ReturnType::Type(_, ty) => Ok(&*ty),
644 syn::ReturnType::Default => Err(syn::Error::new(
645 arg.sig.output.span(),
646 "function needs to return a value",
647 )),
648 }
649}