Skip to main content

surrealdb_expr/val/
closure.rs

1use std::cmp::Ordering;
2use std::sync::Arc;
3
4use revision::{DeserializeRevisioned, Revisioned, SerializeRevisioned};
5use storekey::{BorrowDecode, Encode};
6use surrealdb_types::{SqlFormat, ToSql, write_sql};
7
8use crate::expr::variables::Variables;
9use crate::expr::{ClosureExpr, Expr, Kind, Param};
10
11/// An engine-provided closure implementation, opaque to the value layer.
12///
13/// The value layer holds and clones it but cannot invoke, compare, render,
14/// or persist it. The execution layer downcasts via [`Self::as_any`] to the
15/// concrete type it constructed in order to invoke the closure.
16pub trait BuiltinClosureObj: Send + Sync + 'static {
17	fn as_any(&self) -> &dyn std::any::Any;
18
19	/// The values this closure captured by value, exposed so the value-depth
20	/// guard can walk them. A closure that captures a value deep-clones it, so a
21	/// closure capturing a closure nests exactly as a `Closure::Expr` does and
22	/// must be counted the same way, or an accumulator that wraps itself in a
23	/// closure each turn escapes the cap and overflows the stack on its own
24	/// recursive clone or drop. A closure that captures nothing by value (it
25	/// wraps a function pointer, say) returns an empty list, the default.
26	fn captured_values(&self) -> Vec<&crate::val::Value> {
27		Vec::new()
28	}
29}
30
31#[derive(Clone)]
32pub enum Closure {
33	Expr {
34		args: Vec<(Param, Kind)>,
35		returns: Option<Kind>,
36		body: Expr,
37		captures: Variables,
38	},
39	Builtin(Arc<dyn BuiltinClosureObj>),
40}
41
42impl std::fmt::Debug for Closure {
43	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44		match self {
45			Closure::Expr {
46				args,
47				returns,
48				body,
49				captures,
50			} => f
51				.debug_struct("Closure::Expr")
52				.field("args", args)
53				.field("returns", returns)
54				.field("body", body)
55				.field("captures", captures)
56				.finish(),
57			Closure::Builtin(_) => {
58				f.debug_tuple("Closure::Builtin")
59					// .field("_")
60					.finish()
61			}
62		}
63	}
64}
65
66impl PartialEq for Closure {
67	fn eq(&self, other: &Self) -> bool {
68		match (self, other) {
69			(
70				Closure::Expr {
71					args: args1,
72					returns: returns1,
73					body: body1,
74					captures: captures1,
75				},
76				Closure::Expr {
77					args: args2,
78					returns: returns2,
79					body: body2,
80					captures: captures2,
81				},
82			) => args1 == args2 && returns1 == returns2 && body1 == body2 && captures1 == captures2,
83			(
84				Closure::Builtin {
85					..
86				},
87				Closure::Builtin {
88					..
89				},
90			) => {
91				// Builtin closures cannot be meaningfully compared
92				false
93			}
94			_ => false,
95		}
96	}
97}
98
99impl Eq for Closure {}
100
101impl std::hash::Hash for Closure {
102	fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
103		match self {
104			Closure::Expr {
105				args,
106				returns,
107				body,
108				captures,
109			} => {
110				0u8.hash(state); // discriminant
111				args.hash(state);
112				returns.hash(state);
113				body.hash(state);
114				captures.hash(state);
115			}
116			Closure::Builtin(_) => {
117				1u8.hash(state); // discriminant
118				// Note: logic is not hashed as function pointers cannot be hashed
119			}
120		}
121	}
122}
123
124impl PartialOrd for Closure {
125	fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
126		Some(self.cmp(other))
127	}
128}
129
130impl Ord for Closure {
131	fn cmp(&self, _other: &Self) -> Ordering {
132		// Builtin closures cannot be meaningfully compared
133		Ordering::Equal
134	}
135}
136
137impl Closure {
138	pub fn into_expr(self) -> Expr {
139		match self {
140			Closure::Expr {
141				args,
142				returns,
143				body,
144				..
145			} => Expr::Closure(Box::new(ClosureExpr {
146				args,
147				returns,
148				body,
149			})),
150			Closure::Builtin {
151				..
152			} => Expr::Closure(Box::new(ClosureExpr {
153				args: vec![],
154				returns: None,
155				body: Expr::Block(Box::default()),
156			})),
157		}
158	}
159}
160
161impl ToSql for Closure {
162	fn fmt_sql(&self, f: &mut String, sql_fmt: SqlFormat) {
163		match self {
164			Closure::Expr {
165				args,
166				returns,
167				body,
168				..
169			} => {
170				write_sql!(f, sql_fmt, "|");
171				for (i, (name, kind)) in args.iter().enumerate() {
172					if i > 0 {
173						write_sql!(f, sql_fmt, ", ");
174					}
175					write_sql!(f, sql_fmt, "{name}: ");
176					match kind {
177						k @ Kind::Either(_) => write_sql!(f, sql_fmt, "<{k}>"),
178						k => write_sql!(f, sql_fmt, "{k}"),
179					}
180				}
181				write_sql!(f, sql_fmt, "|");
182				if let Some(returns) = &returns {
183					write_sql!(f, sql_fmt, " -> {returns}");
184				}
185				write_sql!(f, sql_fmt, " {}", body);
186			}
187			Closure::Builtin {
188				..
189			} => {
190				write_sql!(f, sql_fmt, r#"|| THROW "builtin""#);
191			}
192		}
193	}
194}
195
196impl<F> Encode<F> for Closure {
197	fn encode<W: std::io::Write>(
198		&self,
199		_: &mut storekey::Writer<W>,
200	) -> Result<(), storekey::EncodeError> {
201		Err(storekey::EncodeError::message("Closure cannot be encoded"))
202	}
203}
204
205impl<'de, F> BorrowDecode<'de, F> for Closure {
206	fn borrow_decode(_: &mut storekey::BorrowReader<'de>) -> Result<Self, storekey::DecodeError> {
207		Err(storekey::DecodeError::message("Closure cannot be decoded"))
208	}
209}
210
211impl Revisioned for Closure {
212	fn revision() -> u16 {
213		1
214	}
215}
216
217impl SerializeRevisioned for Closure {
218	fn serialize_revisioned<W: std::io::Write>(
219		&self,
220		_writer: &mut W,
221	) -> Result<(), revision::Error> {
222		Err(revision::Error::Conversion("Closures cannot be stored on disk".to_string()))
223	}
224}
225
226impl DeserializeRevisioned for Closure {
227	fn deserialize_revisioned<R: std::io::Read>(_reader: &mut R) -> Result<Self, revision::Error> {
228		Err(revision::Error::Conversion("Closures cannot be deserialized from disk".to_string()))
229	}
230}
231
232impl revision::SkipRevisioned for Closure {
233	fn skip_revisioned<R: std::io::Read>(_reader: &mut R) -> Result<(), revision::Error> {
234		Err(revision::Error::Conversion("Closures cannot be skipped on the wire".to_string()))
235	}
236}
237
238impl revision::WalkRevisioned for Closure {
239	type Walker<'r, R: revision::BorrowedReader + 'r> = revision::LeafWalker<'r, Closure, R>;
240
241	fn walk_revisioned<'r, R: revision::BorrowedReader>(
242		reader: &'r mut R,
243	) -> Result<Self::Walker<'r, R>, revision::Error> {
244		// Closures never appear on the wire; expose a leaf walker whose
245		// `decode`/`skip` invariably error so callers walking from outer
246		// types fail fast rather than silently advancing.
247		Ok(revision::LeafWalker::new(reader))
248	}
249}