Skip to main content

mz_expr_derive_impl/
generate.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9//! Codegen shared by every scalar function arity.
10
11use proc_macro2::{Ident, Span, TokenStream};
12use quote::quote;
13
14use crate::modifiers::Modifiers;
15use crate::shape::{Modifier, Shape};
16use crate::signature;
17use crate::source::sqlfunc_source;
18
19/// One `fn name(&self) -> Ret { expr }` per modifier present in `mods`, in the arity
20/// table's order rather than the attribute's order, so the generated code is stable
21/// against how a call site happens to spell its modifiers.
22///
23/// `introduces_nulls_fn` fills the table's `introduces_nulls` slot, because
24/// [`generate`] synthesizes that method from the output type and the modifier together.
25/// The walk never builds it from `mods`.
26fn override_methods(
27    shape: Shape,
28    mods: &Modifiers,
29    mut introduces_nulls_fn: Option<TokenStream>,
30) -> Vec<TokenStream> {
31    shape
32        .modifiers()
33        .iter()
34        .filter_map(|(modifier, ret)| {
35            if *modifier == Modifier::IntroducesNulls {
36                return introduces_nulls_fn.take();
37            }
38            let expr = mods.get(*modifier)?;
39            let name = Ident::new(modifier.name(), Span::call_site());
40            let body = ret.body(expr);
41            let ret = ret.tokens();
42            Some(quote! {
43                fn #name(&self) -> #ret {
44                    #body
45                }
46            })
47        })
48        .collect()
49}
50
51/// What the shared codegen needs that is not arity specific.
52struct Codegen {
53    struct_name: Ident,
54    /// True when the struct is defined at the call site, so expansion attaches an
55    /// inherent method instead of defining a unit struct.
56    has_self: bool,
57    sqlname: TokenStream,
58    /// The registry's record of this function's source, generated as members of the
59    /// `FuncName` impl.
60    source: TokenStream,
61    /// Suppresses the generated `Display` impl, so a call site whose SQL name
62    /// depends on struct state can supply its own.
63    skip_display: bool,
64}
65
66/// Wraps an arity's trait impl with the parts every arity shares: the unit-struct
67/// definition (unless `has_self`), the `Display` impl (unless `skip_display`), the
68/// `FuncName` impl, and the annotated function itself.
69fn expand(e: &Codegen, func: &syn::ItemFn, trait_impl: TokenStream) -> TokenStream {
70    let func_name = &func.sig.ident;
71    let Codegen {
72        struct_name,
73        has_self,
74        sqlname,
75        source,
76        skip_display,
77    } = e;
78
79    let display_impl = if *skip_display {
80        quote! {}
81    } else {
82        quote! {
83            impl std::fmt::Display for #struct_name {
84                fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
85                    f.write_str(#sqlname)
86                }
87            }
88        }
89    };
90
91    let funcname_impl = quote! {
92        impl crate::func::FuncName for #struct_name {
93            const NAME: &'static str = stringify!(#func_name);
94            #source
95        }
96    };
97
98    if *has_self {
99        quote! {
100            impl #struct_name {
101                #func
102            }
103            #trait_impl
104            #display_impl
105            #funcname_impl
106        }
107    } else {
108        quote! {
109            #[derive(
110                Ord, PartialOrd, Clone,
111                Debug, Eq, PartialEq, serde::Serialize,
112                serde::Deserialize, Hash,
113            )]
114            #[cfg_attr(any(test, feature = "proptest"), derive(proptest_derive::Arbitrary))]
115            pub struct #struct_name;
116
117            #trait_impl
118            #display_impl
119            #funcname_impl
120
121            #func
122        }
123    }
124}
125
126/// The SQL arguments' pre-erasure types, and the names `call` binds them to.
127///
128/// Unary and binary bind their inputs positionally as `a` and `b`, ignoring how the
129/// function spells its parameters, while variadic forwards the real names.
130fn sql_params(shape: Shape, func: &syn::ItemFn) -> darling::Result<(Vec<syn::Type>, Vec<Ident>)> {
131    // `crate::sqlfunc::determine_shape` gives unary one SQL argument and binary two.
132    const POSITIONAL: [&str; 2] = ["a", "b"];
133    let mut types = Vec::new();
134    let mut names = Vec::new();
135    for (position, param) in signature::sql_args(func).into_iter().enumerate() {
136        let syn::FnArg::Typed(pat) = param else {
137            return Err(darling::Error::custom("unexpected self parameter").with_span(param));
138        };
139        let name = match shape {
140            Shape::Unary | Shape::Binary => Ident::new(POSITIONAL[position], Span::call_site()),
141            Shape::Variadic => {
142                let syn::Pat::Ident(ident) = &*pat.pat else {
143                    return Err(
144                        darling::Error::custom("unsupported parameter pattern").with_span(&pat.pat)
145                    );
146                };
147                ident.ident.clone()
148            }
149        };
150        types.push(signature::patch_lifetimes(&pat.ty));
151        names.push(name);
152    }
153    Ok((types, names))
154}
155
156/// The column type expression the output-type method starts from, and the
157/// `introduces_nulls` method if one is generated.
158///
159/// `output_type`, `output_type_expr` and `introduces_nulls` decide both together. A
160/// generic signature that sets neither output modifier derives them from where its
161/// type parameters appear.
162fn output_type_and_introduces_nulls(
163    shape: Shape,
164    mods: &Modifiers,
165    param_types_raw: &[syn::Type],
166    output_ty_raw: &syn::Type,
167    generic_params: &[Ident],
168) -> darling::Result<(TokenStream, Option<TokenStream>)> {
169    let output_type = mods.output_type();
170    let mut output_type_expr = mods.output_type_expr().map(|expr| quote! { #expr });
171    let mut introduces_nulls = mods
172        .get(Modifier::IntroducesNulls)
173        .map(|expr| quote! { #expr });
174
175    // Derive the output type from where the generic parameters land. This reads the
176    // pre-erasure types, because erasure is what removes those parameters.
177    if !generic_params.is_empty() && output_type.is_none() && output_type_expr.is_none() {
178        if let Some(derived) = signature::derive_output_type_for_generics(
179            param_types_raw,
180            output_ty_raw,
181            generic_params,
182            shape == Shape::Unary,
183        )? {
184            output_type_expr = Some(derived);
185            if introduces_nulls.is_none() {
186                let nullable = signature::is_option_wrapped(output_ty_raw);
187                introduces_nulls = Some(quote! { #nullable });
188            }
189        }
190    }
191
192    let column_type = match (output_type, output_type_expr) {
193        (Some(output_type), _) => quote! { <#output_type>::as_column_type() },
194        (None, Some(output_type_expr)) => output_type_expr,
195        (None, None) => quote! { Self::Output::as_column_type() },
196    };
197    // Without an `introduces_nulls` method the trait default reads `Self::Output`,
198    // which an explicit `output_type` replaces.
199    let introduces_nulls = introduces_nulls.or_else(|| {
200        output_type.map(|output_type| {
201            quote! { <#output_type as ::mz_repr::OutputDatumType<'_, ()>>::nullable() }
202        })
203    });
204    let introduces_nulls_fn = introduces_nulls.map(|introduces_nulls| {
205        quote! {
206            fn introduces_nulls(&self) -> bool {
207                #introduces_nulls
208            }
209        }
210    });
211    Ok((column_type, introduces_nulls_fn))
212}
213
214/// Generates the trait impl for one `#[sqlfunc]`-annotated function, together with the
215/// items [`expand`] wraps around it.
216///
217/// `struct_ty` names the generated struct when the call site spells one.
218pub(crate) fn generate(
219    shape: Shape,
220    func: &syn::ItemFn,
221    mods: Modifiers,
222    struct_ty: Option<syn::Path>,
223    attr: &TokenStream,
224) -> darling::Result<TokenStream> {
225    let fn_name = &func.sig.ident;
226    let struct_name = struct_ty
227        .as_ref()
228        .and_then(|ty| ty.segments.last())
229        .map_or_else(|| signature::camel_case(fn_name), |seg| seg.ident.clone());
230
231    // A receiver makes `call` dispatch through `self` to a method on a struct defined
232    // at the call site.
233    let has_self = signature::has_receiver(func);
234    mods.validate(shape, has_self)?;
235    // Whether the annotated function takes the arena. The trait's `call` always receives
236    // one.
237    let arena = signature::last_is_arena(func);
238    let output_ty_raw = signature::output_type(func)?;
239    let generic_params = signature::find_generic_type_params(func);
240
241    let (param_types_raw, param_names) = sql_params(shape, func)?;
242
243    // Replace generic type params with `Datum<'a>` for use in the trait impl's associated
244    // types, where the function's parameters are not in scope.
245    let param_types: Vec<syn::Type> = param_types_raw
246        .iter()
247        .map(|ty| signature::erase_all_generic_params(ty, &generic_params))
248        .collect();
249    let output_ty = signature::erase_all_generic_params(output_ty_raw, &generic_params);
250
251    // A lone input passes through bare. Several become a tuple.
252    let input_ty: syn::Type = if let [ty] = param_types.as_slice() {
253        ty.clone()
254    } else {
255        syn::parse_quote! { (#(#param_types),*) }
256    };
257    let destructure = if let [name] = param_names.as_slice() {
258        quote! { #name }
259    } else {
260        quote! { (#(#param_names),*) }
261    };
262
263    let (output_type_code, introduces_nulls_fn) = output_type_and_introduces_nulls(
264        shape,
265        &mods,
266        &param_types_raw,
267        output_ty_raw,
268        &generic_params,
269    )?;
270    let methods = override_methods(shape, &mods, introduces_nulls_fn);
271
272    let arena_arg = if arena {
273        quote! { , temp_storage }
274    } else {
275        quote! {}
276    };
277    let receiver = if has_self {
278        quote! { self. }
279    } else {
280        quote! {}
281    };
282
283    let trait_path = shape.trait_path();
284    let (output_method, output_method_param) = shape.output_method();
285    let nullability = shape.nullability(&signature::non_nullable_position_checks(&param_types));
286
287    let trait_impl = quote! {
288        impl #trait_path for #struct_name {
289            type Input<'a> = #input_ty;
290            type Output<'a> = #output_ty;
291
292            fn call<'a>(
293                &self,
294                #destructure: Self::Input<'a>,
295                temp_storage: &'a mz_repr::RowArena,
296            ) -> Self::Output<'a> {
297                #receiver #fn_name(#(#param_names),* #arena_arg)
298            }
299
300            fn #output_method(
301                &self,
302                #output_method_param
303            ) -> mz_repr::SqlColumnType {
304                use mz_repr::AsColumnType;
305                let output = #output_type_code;
306                let propagates_nulls = #trait_path::propagates_nulls(self);
307                let nullable = output.nullable;
308                #nullability
309            }
310
311            #(#methods)*
312        }
313    };
314
315    let sqlname = mods
316        .sqlname()
317        .map_or_else(|| quote! { stringify!(#fn_name) }, |name| quote! { #name });
318    let codegen = Codegen {
319        struct_name,
320        has_self,
321        sqlname,
322        source: sqlfunc_source(attr, func, &param_types_raw, output_ty_raw, &param_types),
323        skip_display: mods.skip_display(),
324    };
325    Ok(expand(&codegen, func, trait_impl))
326}