mz_expr_derive_impl/modifiers.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//! The `#[sqlfunc]` attribute's key-value modifiers.
10//!
11//! [`Modifiers`] is the parsed attribute. [`Modifiers::validate`] checks it before
12//! anything is generated. Modifiers that feed `Display` or the output-type body have
13//! their own accessors, and the ones that map onto a trait method are reached through
14//! [`Modifiers::get`].
15
16use proc_macro2::TokenStream;
17use quote::quote;
18use syn::{Expr, Lit};
19
20use crate::shape::{Modifier, Shape};
21
22/// Modifiers passed as key-value pairs to the `#[sqlfunc]` macro.
23#[derive(Debug, Default, darling::FromMeta)]
24pub(crate) struct Modifiers {
25 /// Whether the function is monotone with respect to its arguments. Binary functions
26 /// take a pair of expressions, one per argument.
27 is_monotone: Option<Expr>,
28 /// Whether `is_monotone`'s endpoint-sampling guarantee still holds when an operand
29 /// may be infinite. Set `false` for multiplication and division.
30 is_infinity_monotone: Option<Expr>,
31 /// The SQL name for the function.
32 sqlname: Option<SqlName>,
33 /// Whether the function preserves uniqueness.
34 preserves_uniqueness: Option<Expr>,
35 /// The inverse of the function, if it exists.
36 inverse: Option<Expr>,
37 /// The negated function, if it exists.
38 negate: Option<Expr>,
39 /// Whether the function is an infix operator.
40 is_infix_op: Option<Expr>,
41 /// The output type of the function, if it cannot be inferred.
42 output_type: Option<syn::Path>,
43 /// The output type of the function as an expression.
44 output_type_expr: Option<Expr>,
45 /// Whether the function could error.
46 could_error: Option<Expr>,
47 /// Whether the function propagates nulls.
48 propagates_nulls: Option<Expr>,
49 /// Whether the function introduces nulls.
50 introduces_nulls: Option<Expr>,
51 /// Whether the function is associative.
52 is_associative: Option<Expr>,
53 /// Whether the function is a noop cast.
54 is_eliminable_cast: Option<Expr>,
55 /// Whether to generate a snapshot test for the function. Defaults to false.
56 test: Option<bool>,
57 /// Suppresses the generated `fmt::Display`. Set this when the struct's name
58 /// depends on its state, so the call site keeps a hand-written impl.
59 skip_display: Option<bool>,
60}
61
62impl Modifiers {
63 /// Whether the call site asked for a generated snapshot test.
64 pub(crate) fn generates_test(&self) -> bool {
65 self.test.unwrap_or(false)
66 }
67
68 /// The SQL name the generated `Display` writes, if the call site overrides the
69 /// function's name.
70 pub(crate) fn sqlname(&self) -> Option<&SqlName> {
71 self.sqlname.as_ref()
72 }
73
74 /// The type whose column type is the function's output type, if the call site
75 /// overrides the one derived from the return type.
76 pub(crate) fn output_type(&self) -> Option<&syn::Path> {
77 self.output_type.as_ref()
78 }
79
80 /// An expression computing the function's output column type, if the call site
81 /// overrides the one derived from the return type.
82 pub(crate) fn output_type_expr(&self) -> Option<&Expr> {
83 self.output_type_expr.as_ref()
84 }
85
86 /// Whether the call site asked to suppress the generated `Display` impl.
87 pub(crate) fn skip_display(&self) -> bool {
88 self.skip_display.unwrap_or(false)
89 }
90
91 /// The expression the call site gave a method-producing modifier, if any.
92 pub(crate) fn get(&self, modifier: Modifier) -> Option<&Expr> {
93 match modifier {
94 Modifier::CouldError => self.could_error.as_ref(),
95 Modifier::IntroducesNulls => self.introduces_nulls.as_ref(),
96 Modifier::IsMonotone => self.is_monotone.as_ref(),
97 Modifier::IsInfixOp => self.is_infix_op.as_ref(),
98 Modifier::PropagatesNulls => self.propagates_nulls.as_ref(),
99 Modifier::Inverse => self.inverse.as_ref(),
100 Modifier::PreservesUniqueness => self.preserves_uniqueness.as_ref(),
101 Modifier::IsEliminableCast => self.is_eliminable_cast.as_ref(),
102 Modifier::Negate => self.negate.as_ref(),
103 Modifier::IsInfinityMonotone => self.is_infinity_monotone.as_ref(),
104 Modifier::IsAssociative => self.is_associative.as_ref(),
105 }
106 }
107
108 /// Errors if a modifier does not apply to `shape`, or conflicts with another
109 /// modifier or with the function's receiver. Reports every problem, each at the
110 /// modifier it concerns.
111 pub(crate) fn validate(&self, shape: Shape, has_self: bool) -> darling::Result<()> {
112 let mut errors = darling::Error::accumulator();
113 for modifier in Modifier::ALL {
114 let Some(expr) = self.get(modifier) else {
115 continue;
116 };
117 if !shape.accepts(modifier) {
118 errors.push(
119 darling::Error::custom(format!(
120 "`{}` is not supported for {} functions",
121 modifier.name(),
122 shape.label(),
123 ))
124 .with_span(expr),
125 );
126 }
127 }
128 if let (Some(_), Some(expr)) = (&self.output_type, &self.output_type_expr) {
129 errors.push(
130 darling::Error::custom("output_type and output_type_expr cannot be used together")
131 .with_span(expr),
132 );
133 }
134 if self.skip_display() {
135 if let Some(sqlname) = &self.sqlname {
136 errors.push(
137 darling::Error::custom(
138 "sqlname has no effect with skip_display, which suppresses the only impl that reads it",
139 )
140 .with_span(sqlname),
141 );
142 }
143 // A hand-written `Display` is only needed when the name depends on struct
144 // state, and without `&self` the macro defines a unit struct that has none.
145 if !has_self {
146 errors.push(darling::Error::custom(
147 "skip_display requires a &self receiver, since a unit struct has no state for a hand-written Display to read",
148 ));
149 }
150 }
151 errors.finish()
152 }
153}
154
155/// A name for the SQL function. It can be either a literal or a macro, thus we
156/// can't use `String` or `syn::Expr` directly.
157#[derive(Debug)]
158pub(crate) enum SqlName {
159 /// A literal string.
160 Literal(syn::Lit),
161 /// A macro expression.
162 Macro(syn::ExprMacro),
163}
164
165impl quote::ToTokens for SqlName {
166 fn to_tokens(&self, tokens: &mut TokenStream) {
167 let name = match self {
168 SqlName::Literal(lit) => quote! { #lit },
169 SqlName::Macro(mac) => quote! { #mac },
170 };
171 tokens.extend(name);
172 }
173}
174
175impl darling::FromMeta for SqlName {
176 fn from_value(value: &Lit) -> darling::Result<Self> {
177 Ok(Self::Literal(value.clone()))
178 }
179 fn from_expr(expr: &Expr) -> darling::Result<Self> {
180 match expr {
181 Expr::Lit(lit) => Self::from_value(&lit.lit),
182 Expr::Macro(mac) => Ok(Self::Macro(mac.clone())),
183 // Syn sometimes inserts groups, see `FromMeta::from_expr` for
184 // details.
185 Expr::Group(mac) => Self::from_expr(&mac.expr),
186 _ => Err(darling::Error::unexpected_expr_type(expr)),
187 }
188 }
189}