Skip to main content

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}