1use proc_macro2::{Ident, Span, TokenStream};
16use quote::quote;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub(crate) enum Shape {
21 Unary,
22 Binary,
23 Variadic,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub(crate) enum Modifier {
34 CouldError,
35 IntroducesNulls,
36 IsMonotone,
37 IsInfixOp,
38 PropagatesNulls,
39 Inverse,
40 PreservesUniqueness,
41 IsEliminableCast,
42 Negate,
43 IsInfinityMonotone,
44 IsAssociative,
45}
46
47impl Modifier {
48 pub(crate) const ALL: [Modifier; 11] = [
50 Modifier::CouldError,
51 Modifier::IntroducesNulls,
52 Modifier::IsMonotone,
53 Modifier::IsInfixOp,
54 Modifier::PropagatesNulls,
55 Modifier::Inverse,
56 Modifier::PreservesUniqueness,
57 Modifier::IsEliminableCast,
58 Modifier::Negate,
59 Modifier::IsInfinityMonotone,
60 Modifier::IsAssociative,
61 ];
62
63 pub(crate) fn name(&self) -> &'static str {
65 match self {
66 Modifier::CouldError => "could_error",
67 Modifier::IntroducesNulls => "introduces_nulls",
68 Modifier::IsMonotone => "is_monotone",
69 Modifier::IsInfixOp => "is_infix_op",
70 Modifier::PropagatesNulls => "propagates_nulls",
71 Modifier::Inverse => "inverse",
72 Modifier::PreservesUniqueness => "preserves_uniqueness",
73 Modifier::IsEliminableCast => "is_eliminable_cast",
74 Modifier::Negate => "negate",
75 Modifier::IsInfinityMonotone => "is_infinity_monotone",
76 Modifier::IsAssociative => "is_associative",
77 }
78 }
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
83pub(crate) enum ReturnTy {
84 Bool,
85 BoolPair,
86 OptUnaryFunc,
87 OptBinaryFunc,
88}
89
90impl ReturnTy {
91 pub(crate) fn tokens(&self) -> TokenStream {
92 match self {
93 ReturnTy::Bool => quote! { bool },
94 ReturnTy::BoolPair => quote! { (bool, bool) },
95 ReturnTy::OptUnaryFunc => quote! { Option<crate::UnaryFunc> },
96 ReturnTy::OptBinaryFunc => quote! { Option<crate::BinaryFunc> },
97 }
98 }
99
100 pub(crate) fn body(&self, expr: &syn::Expr) -> TokenStream {
103 match self {
104 ReturnTy::Bool | ReturnTy::BoolPair => quote! { #expr },
105 ReturnTy::OptUnaryFunc => quote! { Some(crate::UnaryFunc::from(#expr)) },
106 ReturnTy::OptBinaryFunc => quote! { Some(crate::BinaryFunc::from(#expr)) },
107 }
108 }
109}
110
111const UNARY_MODIFIERS: &[(Modifier, ReturnTy)] = &[
114 (Modifier::CouldError, ReturnTy::Bool),
115 (Modifier::IntroducesNulls, ReturnTy::Bool),
116 (Modifier::Inverse, ReturnTy::OptUnaryFunc),
117 (Modifier::IsMonotone, ReturnTy::Bool),
118 (Modifier::PreservesUniqueness, ReturnTy::Bool),
119 (Modifier::IsEliminableCast, ReturnTy::Bool),
120];
121
122const BINARY_MODIFIERS: &[(Modifier, ReturnTy)] = &[
123 (Modifier::CouldError, ReturnTy::Bool),
124 (Modifier::IntroducesNulls, ReturnTy::Bool),
125 (Modifier::IsInfixOp, ReturnTy::Bool),
126 (Modifier::IsMonotone, ReturnTy::BoolPair),
127 (Modifier::IsInfinityMonotone, ReturnTy::Bool),
128 (Modifier::Negate, ReturnTy::OptBinaryFunc),
129 (Modifier::PropagatesNulls, ReturnTy::Bool),
130];
131
132const VARIADIC_MODIFIERS: &[(Modifier, ReturnTy)] = &[
133 (Modifier::CouldError, ReturnTy::Bool),
134 (Modifier::IntroducesNulls, ReturnTy::Bool),
135 (Modifier::IsInfixOp, ReturnTy::Bool),
136 (Modifier::IsMonotone, ReturnTy::Bool),
137 (Modifier::IsAssociative, ReturnTy::Bool),
138 (Modifier::PropagatesNulls, ReturnTy::Bool),
139];
140
141impl Shape {
142 pub(crate) fn modifiers(&self) -> &'static [(Modifier, ReturnTy)] {
145 match self {
146 Shape::Unary => UNARY_MODIFIERS,
147 Shape::Binary => BINARY_MODIFIERS,
148 Shape::Variadic => VARIADIC_MODIFIERS,
149 }
150 }
151
152 pub(crate) fn accepts(&self, modifier: Modifier) -> bool {
154 self.modifiers()
155 .iter()
156 .any(|(candidate, _)| *candidate == modifier)
157 }
158
159 pub(crate) fn label(&self) -> &'static str {
160 match self {
161 Shape::Unary => "unary",
162 Shape::Binary => "binary",
163 Shape::Variadic => "variadic",
164 }
165 }
166
167 pub(crate) fn trait_path(&self) -> TokenStream {
169 match self {
170 Shape::Unary => quote! { crate::func::EagerUnaryFunc },
171 Shape::Binary => quote! { crate::func::binary::EagerBinaryFunc },
172 Shape::Variadic => quote! { crate::func::variadic::EagerVariadicFunc },
173 }
174 }
175
176 pub(crate) fn output_method(&self) -> (Ident, TokenStream) {
184 let span = Span::call_site();
185 match self {
186 Shape::Unary => (
187 Ident::new("output_sql_type", span),
188 quote! { input_type: mz_repr::SqlColumnType },
189 ),
190 Shape::Binary => (
191 Ident::new("output_sql_type", span),
192 quote! { input_types: &[mz_repr::SqlColumnType] },
193 ),
194 Shape::Variadic => (
195 Ident::new("output_type", span),
196 quote! { input_types: &[mz_repr::SqlColumnType] },
197 ),
198 }
199 }
200
201 pub(crate) fn nullability(&self, checks: &[TokenStream]) -> TokenStream {
214 match self {
215 Shape::Unary => quote! {
224 output.nullable(nullable || (propagates_nulls && input_type.nullable))
225 },
226 Shape::Binary | Shape::Variadic => quote! {
227 let non_nullable_input_is_nullable = false #(#checks)*;
228 let inputs_nullable = input_types.iter().any(|it| it.nullable);
229 let is_null = nullable
230 || non_nullable_input_is_nullable
231 || (propagates_nulls && inputs_nullable);
232 output.nullable(is_null)
233 },
234 }
235 }
236}
237
238#[cfg(test)]
239mod tests {
240 use super::{Modifier, Shape};
241
242 #[mz_ore::test]
243 fn unary_accepts_inverse_and_rejects_negate() {
244 assert!(Shape::Unary.accepts(Modifier::Inverse));
245 assert!(Shape::Unary.accepts(Modifier::PreservesUniqueness));
246 assert!(Shape::Unary.accepts(Modifier::IsEliminableCast));
247 assert!(!Shape::Unary.accepts(Modifier::Negate));
248 assert!(!Shape::Unary.accepts(Modifier::IsInfixOp));
249 assert!(
250 !Shape::Unary.accepts(Modifier::PropagatesNulls),
251 "Shape::nullability omits unary's non-nullable-position term on the strength of \
252 this absence"
253 );
254 assert!(!Shape::Unary.accepts(Modifier::IsAssociative));
255 }
256}