candela/compiler/expr.rs
1// This file is derived from keel (https://github.com/horacehoff/keel),
2// Copyright 2026 Horace Hoff, licensed under the Apache License, Version 2.0.
3// It has been modified by the candela authors. See the NOTICE file.
4use super::type_system::ReturnAnnotation;
5use super::type_system::TypeExpr;
6use super::type_system::TypeParams;
7pub use crate::rt::Span;
8use smol_strc::SmolStr;
9use smol_strc::ToSmolStr;
10use std::{hint::unreachable_unchecked, rc::Rc};
11
12/// Separator between a type name and a method name in a mangled method symbol.
13///
14/// `impl` methods lower to per-type-unique free functions named `Type#method`.
15/// `#` is not a legal character in a candela identifier (which is
16/// `[a-zA-Z_][a-zA-Z0-9_]*`) and is never produced by the lexer, so a mangled
17/// name can never collide with a user-written free function or with a method of
18/// another type: `Point#len` and `Str#len` are distinct symbols, and both are
19/// distinct from a free `fn len`.
20pub const METHOD_SEP: char = '#';
21
22/// Builds the mangled free-function symbol an `impl Type { fn method ... }`
23/// lowers to.
24///
25/// Used by the parser when lowering method declarations and by the
26/// compiler/type-checker when resolving a `recv.method(...)` call site.
27#[must_use]
28pub fn mangle_method(type_name: &str, method_name: &str) -> SmolStr {
29 format_args!("{type_name}{METHOD_SEP}{method_name}").to_smolstr()
30}
31
32/// What an import adds to the scope of the file that writes it.
33#[derive(Debug, Clone, PartialEq, Eq)]
34pub enum ImportBinding {
35 /// The module under one name, reached with `::`: the last segment of the
36 /// path (`import "std/json";` binds `json`) or the name after `as`.
37 Namespace(SmolStr),
38 /// `import "std/assert" { eq, ne };`: the named items, each with the span
39 /// of its name, merged into the file's own scope. The module itself is
40 /// bound under no name.
41 Items(Box<[(SmolStr, Span)]>),
42}
43
44/// Where a struct literal written with `..` takes the fields it does not
45/// write from.
46#[derive(Debug, Clone, PartialEq)]
47pub enum StructRest {
48 /// `..base`: from another value of the struct's type.
49 Base(Expr),
50 /// A bare `..`: from the struct's default.
51 Default,
52}
53
54impl StructRest {
55 /// The expression a `..base` names, `None` for a bare `..`.
56 #[must_use]
57 pub const fn base(&self) -> Option<&Expr> {
58 match self {
59 Self::Base(base) => Some(base),
60 Self::Default => None,
61 }
62 }
63}
64
65/// What the right side of `is` names.
66#[derive(Debug, Clone, PartialEq)]
67pub enum IsTarget {
68 /// A type: `int`, `Point`, `int[]`, `{string: any}`, a union. A bare name
69 /// that names no type but a variant without a payload (`None`) is a
70 /// variant test.
71 Type(TypeExpr),
72 /// A variant written the way a `match` arm writes it, `Some(x)`: an
73 /// [`Expr::FunctionCall`] whose arguments name the payload binders.
74 Variant(Expr),
75}
76
77/// The type a `let` declares for its variable: `let xs: int[] = [];`.
78#[derive(Debug, Clone, PartialEq, Eq)]
79pub struct LetAnnotation {
80 pub ty: TypeExpr,
81 /// Where the type was written.
82 pub span: Span,
83 /// Where the value the variable starts with was written.
84 pub value_span: Span,
85}
86
87#[derive(Debug, Clone, PartialEq)]
88pub enum Expr {
89 Float(f64),
90 Int(i64),
91 Bool(bool),
92 Null,
93 String(SmolStr),
94 Var(SmolStr, Span),
95 /// Array(contents, [entire_array, elem_spans...])
96 Array(Box<[Self]>, Box<[Span]>),
97 /// Map(key-value pairs, span)
98 Map(Box<[(Self, Span, Self, Span)]>, Span),
99 /// Struct(name, fields, span, type_args, base)
100 ///
101 /// `type_args` holds the arguments of a generic struct literal
102 /// (`Cell<int>{ value: 3 }`) and is empty otherwise. `base` is what a
103 /// literal written with `..` takes the fields it does not write from,
104 /// `..base` (`Opts { cwd: "a", ..base }`) or a bare `..` for the struct's
105 /// default, with the span it was written at.
106 Struct(
107 Box<[SmolStr]>,
108 Box<[(SmolStr, Self, Span, Span)]>,
109 Span,
110 Box<[TypeExpr]>,
111 Option<Box<(StructRest, Span)>>,
112 ),
113 /// StructDeclare(name, fields, span, type_params, defaults)
114 ///
115 /// `defaults` holds the `= value` each field was declared with and the
116 /// span it was written at, one entry per field, and is empty when no field
117 /// declares one.
118 StructDeclare(
119 SmolStr,
120 Box<[(SmolStr, TypeExpr, Span)]>,
121 Span,
122 TypeParams,
123 Box<[Option<(Self, Span)>]>,
124 ),
125 /// StructDefault(struct_id, span)
126 ///
127 /// The default value of a struct, by its id: what `S::default()` and a
128 /// field of a struct type left to its default lower to. Built by the
129 /// compiler, never by the parser.
130 StructDefault(u16, Span),
131 /// EnumDeclare(name, variants: [(variant_name, payload_types, name_span)], span, type_params)
132 EnumDeclare(
133 SmolStr,
134 Box<[(SmolStr, Box<[TypeExpr]>, Span)]>,
135 Span,
136 TypeParams,
137 ),
138 /// NamespacedRef(path, span, type_args)
139 ///
140 /// A bare namespaced identifier (`Color::Red`) with no call parentheses or
141 /// struct braces. The only such form candela has is a nullary enum-variant
142 /// construction; the compiler resolves it against the enum registry.
143 /// `type_args` names the instantiation of a generic enum
144 /// (`Slot<int>::Empty`) and is empty otherwise.
145 NamespacedRef(Box<[SmolStr]>, Span, Box<[TypeExpr]>),
146 /// Match(scrutinee, arms: [(patterns, body)], wildcard_body, span, is_value)
147 ///
148 /// The arm patterns are parsed as ordinary expressions; the compiler picks
149 /// the lowering by the scrutinee's static type. For an enum scrutinee each
150 /// pattern is a variant pattern (`Circle(r)` binds the payload); otherwise
151 /// each pattern is an equality test against the scrutinee. An arm holds one
152 /// pattern per `|` alternative and runs when any of them matches.
153 ///
154 /// `is_value` marks a `match` written where a value goes. Each of its
155 /// bodies is one expression, the arm's value, and it covers every case.
156 Match(
157 Box<Self>,
158 Box<[(Box<[Self]>, Box<[Self]>)]>,
159 Option<Box<[Self]>>,
160 Span,
161 bool,
162 ),
163 /// GetStructField(struct_expr, field, struct_span, field_span, value_span)
164 GetStructField(Box<Self>, SmolStr, Span, Span),
165 /// SetStructField(struct_expr, field, new_expr, struct_span, field_span, value_span)
166 SetStructField(Box<Self>, SmolStr, Box<Self>, Span, Span, Span),
167 /// VarDeclare(name, value, annotation)
168 ///
169 /// `annotation` is the type a `let x: T = value;` declares, `None` for a
170 /// `let` written without one.
171 VarDeclare(SmolStr, Box<Self>, Option<Box<LetAnnotation>>),
172 /// VarAssign(name, value, name_span, value_span)
173 VarAssign(SmolStr, Box<Self>, Span, Span),
174 /// Condition(condition, code (contains else_if_blocks and potentially
175 /// else_block), span, condition_span)
176 ///
177 /// The condition carries its own span so a condition whose type is not
178 /// `bool` is reported against the condition rather than the whole `if`.
179 Condition(Box<Self>, Box<[Self]>, Span, Span),
180 /// InlineCondition - expression-form if/else, always produces a value, must have an else branch
181 InlineCondition(Box<Self>, Box<[Self]>, Span, Span),
182 /// ElseIfBlock(condition, code, condition_span)
183 ElseIfBlock(Box<Self>, Box<[Self]>, Span),
184 ElseBlock(Box<[Self]>),
185
186 /// AnonymousFunction(args, code, span)
187 AnonymousFunction(Box<[SmolStr]>, Box<[Self]>, Span),
188 // AnonymousFunction(Box<[(SmolStr, SmolStr)]>, SmolStr, Box<[Self]>, Span),
189 /// WhileBlock(condition, code, condition_span)
190 WhileBlock(Box<Self>, Box<[Self]>, Span),
191 /// FunctionCall(args, (optional namespace + name), span, (arg_start,arg_end), type_args)
192 ///
193 /// `type_args` holds the type arguments of a call written with them
194 /// (`first<int>(nums)`) and is empty otherwise.
195 FunctionCall(
196 Box<[Self]>,
197 Box<[SmolStr]>,
198 Span,
199 Box<[Span]>,
200 Box<[TypeExpr]>,
201 ),
202 /// ObjFunctionCall(obj, args, namespace, obj_span, fn_span, arg_markers, type_args)
203 ///
204 /// `type_args` holds the type arguments a method call is written with
205 /// (`b.tagged<string>("hi")`), which bind the method's own type parameters,
206 /// and is empty otherwise.
207 ObjFunctionCall(
208 // Will be removed soon.
209 Box<Self>,
210 Box<[Self]>,
211 Box<[SmolStr]>,
212 // obj_span
213 Span,
214 // fn_span
215 Span,
216 Box<[Span]>,
217 Box<[TypeExpr]>,
218 ),
219 /// CallValue(callee, args, callee_span, arg_markers)
220 ///
221 /// A call whose callee is an expression rather than a name: what another
222 /// call handed back, an element of a list, a value read out of a map. The
223 /// callee's static `Fn` type names the function the same way the type of a
224 /// `let`-bound closure does.
225 CallValue(Box<Self>, Box<[Self]>, Span, Box<[Span]>),
226 /// FunctionDecl(name, args, code, name_span, return_type, type_params)
227 ///
228 /// `return_type` carries the optional `-> Type` annotation together with the
229 /// span of the annotation itself, which is what a mismatch between the
230 /// declared and the returned type is reported against. `type_params` names
231 /// the type parameters of a generic function (`fn first<T>`) and is empty
232 /// otherwise.
233 FunctionDecl(
234 SmolStr,
235 Box<[(SmolStr, Option<TypeExpr>)]>,
236 Rc<[Self]>,
237 Span,
238 ReturnAnnotation,
239 TypeParams,
240 ),
241
242 ReturnVal(Box<Option<Self>>),
243
244 ArrayGetIndex(Box<Self>, Box<Self>, Span),
245 /// Propagate(operand, span)
246 ///
247 /// `operand?`: the value inside a `Some` or an `Ok`, or an early return of
248 /// the `None` or the `Err` from the function it is written in.
249 Propagate(Box<Self>, Span),
250 /// Is(operand, target, operand_span, target_span)
251 ///
252 /// `operand is T` tests the operand's type and `operand is Some(x)` its
253 /// variant; either is a `bool`. Where the test is known to hold (the body
254 /// of an `if` or `while` it is the condition of, and the right of an `&&`
255 /// after it) a variable operand has the type tested for, and a variant
256 /// test's binders hold the payload.
257 Is(Box<Self>, Box<IsTarget>, Span, Span),
258 /// Cast(operand, type, operand_span, type_span)
259 ///
260 /// `operand as T`: the operand, typed `T`, checked while the program runs
261 /// where its static type does not already settle it. A value of another
262 /// type raises `bad_downcast`.
263 Cast(Box<Self>, Box<TypeExpr>, Span, Span),
264 /// ArrayGetSlice(array, range_start, range_end, span)
265 ///
266 /// `range_end` is `None` for an open-ended slice, `a[i..]`, which runs to
267 /// the end of the list or string.
268 ArrayGetSlice(Box<Self>, Box<Self>, Option<Box<Self>>, Span),
269 ArrayModify(Box<Self>, Box<Self>, Box<Self>, Span, Span),
270
271 /// ForLoop(loop_var_name, loop_array+code, obj_markers)
272 ForLoop(SmolStr, Box<Self>, Box<[Self]>, Span),
273 /// IntForLoop(loop_var_name, first_elem, final_elem, code)
274 IntForLoop(SmolStr, Box<Self>, Box<Self>, Box<[Self]>, Span, Span),
275 /// ImportDylib(lib_path, [(fn_name, fn_args, fn_return_type, fn_name_span)], (start, end))
276 ImportDylib(
277 SmolStr,
278 Box<[(SmolStr, Box<[TypeExpr]>, TypeExpr, Span)]>,
279 Span,
280 ),
281
282 /// HostBlock(namespace, [(fn_name, fn_args, fn_return_type, fn_name_span)], (start, end), structs)
283 ///
284 /// Declares a host namespace whose functions are backed by Rust closures
285 /// registered on the embedding [`crate::Engine`]. Mirrors [`Self::ImportDylib`]
286 /// but dispatches to a registered closure instead of a C symbol. `structs`
287 /// holds the [`Self::StructDeclare`]s the block declares, which a script
288 /// names behind the namespace (`process::StartOptions`).
289 HostBlock(
290 SmolStr,
291 Box<[(SmolStr, Box<[TypeExpr]>, TypeExpr, Span)]>,
292 Span,
293 Box<[Self]>,
294 ),
295
296 /// ImportFile(path, binding, is_logical, (start, end))
297 ///
298 /// `is_logical` marks a library import (`import "std/string";`, an
299 /// extensionless path): the resolver maps it straight to the shipped
300 /// library directory and never looks source-relative. A `.cdl` file import
301 /// (`import "./local.cdl";`) has `is_logical` false and resolves
302 /// source-relative first. `binding` says what the import adds to the
303 /// importing file's scope; see [`ImportBinding`].
304 ImportFile(SmolStr, ImportBinding, bool, Span),
305
306 Break,
307 Continue,
308
309 EvalBlock(Box<[Self]>),
310 LoopBlock(Box<[Self]>),
311
312 /// TryCatchBlock(try_code, err_var, catch_code)
313 TryCatchBlock(Box<[Self]>, SmolStr, Box<[Self]>),
314
315 Mul(Box<Self>, Box<Self>, Span, Span),
316 Div(Box<Self>, Box<Self>, Span, Span),
317 Add(Box<Self>, Box<Self>, Span, Span),
318 Sub(Box<Self>, Box<Self>, Span, Span),
319 Mod(Box<Self>, Box<Self>, Span, Span),
320 Pow(Box<Self>, Box<Self>, Span, Span),
321 BitAnd(Box<Self>, Box<Self>, Span, Span),
322 BitOr(Box<Self>, Box<Self>, Span, Span),
323 BitXor(Box<Self>, Box<Self>, Span, Span),
324 Shl(Box<Self>, Box<Self>, Span, Span),
325 Shr(Box<Self>, Box<Self>, Span, Span),
326 BitNot(Box<Self>, Span, Span),
327 Eq(Box<Self>, Box<Self>, Span, Span),
328 NotEq(Box<Self>, Box<Self>, Span, Span),
329 Sup(Box<Self>, Box<Self>, Span, Span),
330 SupEq(Box<Self>, Box<Self>, Span, Span),
331 Inf(Box<Self>, Box<Self>, Span, Span),
332 InfEq(Box<Self>, Box<Self>, Span, Span),
333 BoolAnd(Box<Self>, Box<Self>, Span, Span),
334 BoolOr(Box<Self>, Box<Self>, Span, Span),
335 BoolNeg(Box<Self>, Span, Span),
336 Neg(Box<Self>, Span, Span),
337}
338
339impl Expr {
340 /// Whether the only way to compile this form is as a value, so the
341 /// register it lands in has to be read back.
342 ///
343 /// [`Expr::compile`] takes a `uses_id` flag saying whether the caller
344 /// reads the value produced, and the two answers reach different arms. A
345 /// literal, a variable, an operator and a field read have a value arm and
346 /// nothing else; a declaration, a loop, an assignment and a control-flow
347 /// form have a statement arm and nothing else; a call has both, and its
348 /// statement arm is the one that skips the result register. A form in the
349 /// first group still compiles as a value when it stands alone as a
350 /// statement, and the register is freed straight away.
351 ///
352 /// The list is the arms of [`Expr::compile_with_code_context`] that open
353 /// with `debug_assert!(uses_id)`, and the two are kept in step by hand: an
354 /// arm that gains or loses that assertion belongs here or leaves here in
355 /// the same change, or a statement of that form stops on the assertion it
356 /// no longer matches.
357 #[must_use]
358 pub const fn is_value_only(&self) -> bool {
359 matches!(
360 self,
361 Self::Float(_)
362 | Self::Int(_)
363 | Self::Bool(_)
364 | Self::Null
365 | Self::String(_)
366 | Self::Var(_, _)
367 | Self::Array(_, _)
368 | Self::Map(_, _)
369 | Self::Struct(..)
370 | Self::StructDefault(..)
371 | Self::NamespacedRef(_, _, _)
372 | Self::GetStructField(_, _, _, _)
373 | Self::InlineCondition(_, _, _, _)
374 | Self::Match(_, _, _, _, true)
375 | Self::AnonymousFunction(_, _, _)
376 | Self::ArrayGetIndex(_, _, _)
377 | Self::Propagate(_, _)
378 | Self::Is(..)
379 | Self::Cast(..)
380 | Self::ArrayGetSlice(..)
381 | Self::Mul(..)
382 | Self::Div(..)
383 | Self::Add(..)
384 | Self::Sub(..)
385 | Self::Mod(..)
386 | Self::Pow(..)
387 | Self::BitAnd(..)
388 | Self::BitOr(..)
389 | Self::BitXor(..)
390 | Self::Shl(..)
391 | Self::Shr(..)
392 | Self::BitNot(_, _, _)
393 | Self::Eq(..)
394 | Self::NotEq(..)
395 | Self::Sup(..)
396 | Self::SupEq(..)
397 | Self::Inf(..)
398 | Self::InfEq(..)
399 | Self::BoolAnd(..)
400 | Self::BoolOr(..)
401 | Self::BoolNeg(_, _, _)
402 | Self::Neg(_, _, _)
403 )
404 }
405}
406
407#[cold]
408#[inline(never)]
409#[must_use]
410pub const fn symbol_of_expr(expr: &Expr) -> &'static str {
411 match expr {
412 Expr::Mul(_, _, _, _) => "*",
413 Expr::Div(_, _, _, _) => "/",
414 Expr::Add(_, _, _, _) => "+",
415 Expr::Sub(_, _, _, _) | Expr::Neg(_, _, _) => "-",
416 Expr::Mod(_, _, _, _) => "%",
417 Expr::Pow(_, _, _, _) => "^",
418 Expr::BitAnd(_, _, _, _) => "&",
419 Expr::BitOr(_, _, _, _) => "|",
420 Expr::BitXor(_, _, _, _) => "^^",
421 Expr::Shl(_, _, _, _) => "<<",
422 Expr::Shr(_, _, _, _) => ">>",
423 Expr::BitNot(_, _, _) => "~",
424 Expr::Eq(..) => "==",
425 Expr::NotEq(..) => "!=",
426 Expr::Sup(_, _, _, _) => ">",
427 Expr::SupEq(_, _, _, _) => ">=",
428 Expr::Inf(_, _, _, _) => "<",
429 Expr::InfEq(_, _, _, _) => "<=",
430 Expr::BoolAnd(_, _, _, _) => "&&",
431 Expr::BoolOr(_, _, _, _) => "||",
432 _ => unsafe { unreachable_unchecked() },
433 }
434}
435
436/// The method name unary minus takes.
437///
438/// `-` is the one operator with two forms, and both may be defined on one
439/// type, so negation cannot share subtraction's symbol as a method name.
440/// Subtraction keeps `-` and negation takes this, which no identifier and no
441/// other operator spells.
442pub const UNARY_MINUS_METHOD: &str = "neg-";
443
444/// The operator symbols a method may be named after, as a diagnostic lists
445/// them. Unary minus is written `-` like subtraction; the parameter count
446/// tells the two apart.
447pub const OPERATOR_SYMBOLS: &str = "+ - * / % ^ & | ^^ << >> == < <= ~";
448
449/// How an operator reaches the method a type defines.
450#[derive(Debug, Clone, Copy, PartialEq, Eq)]
451pub enum OperatorForm {
452 /// The method named by this operator, operands in the order they are
453 /// written.
454 Direct,
455 /// `a != b` is `a == b` negated: the `==` method, then the bool flipped.
456 Negated,
457 /// `a > b` is `b < a`: the `<` method with the operands swapped, which is
458 /// why the right operand is evaluated first.
459 Swapped,
460}
461
462/// The method an operator symbol resolves to on a type that defines one, and
463/// the form the call takes.
464///
465/// `!=`, `>` and `>=` have no method of their own: they are derived from `==`,
466/// `<` and `<=`, so a type defines three comparisons and gets six operators.
467/// `&&`, `||`, `!` and assignment are not overloadable and answer `None`.
468#[must_use]
469pub fn operator_method(symbol: &str) -> Option<(&'static str, OperatorForm)> {
470 Some(match symbol {
471 "+" => ("+", OperatorForm::Direct),
472 "-" => ("-", OperatorForm::Direct),
473 "*" => ("*", OperatorForm::Direct),
474 "/" => ("/", OperatorForm::Direct),
475 "%" => ("%", OperatorForm::Direct),
476 "^" => ("^", OperatorForm::Direct),
477 "&" => ("&", OperatorForm::Direct),
478 "|" => ("|", OperatorForm::Direct),
479 "^^" => ("^^", OperatorForm::Direct),
480 "<<" => ("<<", OperatorForm::Direct),
481 ">>" => (">>", OperatorForm::Direct),
482 "==" => ("==", OperatorForm::Direct),
483 "<" => ("<", OperatorForm::Direct),
484 "<=" => ("<=", OperatorForm::Direct),
485 "~" => ("~", OperatorForm::Direct),
486 UNARY_MINUS_METHOD => (UNARY_MINUS_METHOD, OperatorForm::Direct),
487 "!=" => ("==", OperatorForm::Negated),
488 ">" => ("<", OperatorForm::Swapped),
489 ">=" => ("<=", OperatorForm::Swapped),
490 _ => return None,
491 })
492}
493
494/// Whether a method name is one of the operators, so it is declared with an
495/// operator symbol rather than an identifier.
496#[must_use]
497pub fn is_operator_method(name: &str) -> bool {
498 matches!(operator_method(name), Some((_, OperatorForm::Direct)))
499}
500
501/// Whether an operator method takes one operand rather than two.
502#[must_use]
503pub fn is_unary_operator_method(name: &str) -> bool {
504 name == UNARY_MINUS_METHOD || name == "~"
505}
506
507/// Whether an operator method has to hand back a `bool`.
508///
509/// `==`, `<` and `<=` answer a question about two values, and `!=`, `>` and
510/// `>=` are derived from them, so a program reading one of the six as a
511/// condition has to get a `bool` whatever the operands are.
512#[must_use]
513pub fn operator_method_answers_bool(name: &str) -> bool {
514 matches!(name, "==" | "<" | "<=")
515}
516
517/// The operator method this node reaches on the body's own receiver, for a
518/// node whose receiver is `self`.
519///
520/// A method that applies its own operator to `self` calls itself, and the
521/// recursion check works off the names a body calls; without this a method
522/// like `fn +(self, o)` returning `self + o` under a guard is inlined into
523/// itself forever. The derived forms swap or negate, so `>` looks at the
524/// right operand and `!=` at the left.
525#[must_use]
526pub fn self_operator_method(expr: &Expr) -> Option<&'static str> {
527 fn is_self(expr: &Expr) -> bool {
528 matches!(expr, Expr::Var(name, _) if name.as_str() == "self")
529 }
530 let (symbol, receiver) = match expr {
531 Expr::Neg(x, _, _) => (UNARY_MINUS_METHOD, &**x),
532 Expr::BitNot(x, _, _) => ("~", &**x),
533 Expr::Mul(l, r, _, _)
534 | Expr::Div(l, r, _, _)
535 | Expr::Add(l, r, _, _)
536 | Expr::Sub(l, r, _, _)
537 | Expr::Mod(l, r, _, _)
538 | Expr::Pow(l, r, _, _)
539 | Expr::BitAnd(l, r, _, _)
540 | Expr::BitOr(l, r, _, _)
541 | Expr::BitXor(l, r, _, _)
542 | Expr::Shl(l, r, _, _)
543 | Expr::Shr(l, r, _, _)
544 | Expr::Eq(l, r, _, _)
545 | Expr::NotEq(l, r, _, _)
546 | Expr::Inf(l, r, _, _)
547 | Expr::InfEq(l, r, _, _)
548 | Expr::Sup(l, r, _, _)
549 | Expr::SupEq(l, r, _, _) => {
550 let symbol = symbol_of_expr(expr);
551 let (method, form) = operator_method(symbol)?;
552 let receiver = if form == OperatorForm::Swapped { r } else { l };
553 return is_self(receiver).then_some(method);
554 }
555 _ => return None,
556 };
557 let (method, _) = operator_method(symbol)?;
558 is_self(receiver).then_some(method)
559}
560
561#[must_use]
562pub fn code_modifies_variable(var_name: &SmolStr, code: &[Expr]) -> bool {
563 code.iter().any(|expr| match expr {
564 Expr::VarAssign(n, _, _, _) => n == var_name,
565 Expr::Condition(_, body, _, _)
566 | Expr::WhileBlock(_, body, _)
567 | Expr::EvalBlock(body)
568 | Expr::LoopBlock(body)
569 | Expr::InlineCondition(_, body, _, _)
570 | Expr::ElseIfBlock(_, body, _)
571 | Expr::ElseBlock(body)
572 | Expr::ForLoop(_, _, body, _)
573 | Expr::IntForLoop(_, _, _, body, _, _) => code_modifies_variable(var_name, body),
574 // A catch binds its own name for the error; an assignment to a
575 // variable of the same name there writes the error binding instead.
576 Expr::TryCatchBlock(try_code, err_var, catch_code) => {
577 code_modifies_variable(var_name, try_code)
578 || (err_var != var_name && code_modifies_variable(var_name, catch_code))
579 }
580 Expr::Match(_, arms, wildcard, _, _) => {
581 arms.iter()
582 .any(|(_, body)| code_modifies_variable(var_name, body))
583 || wildcard
584 .as_ref()
585 .is_some_and(|body| code_modifies_variable(var_name, body))
586 }
587 _ => false,
588 })
589}
590
591#[must_use]
592pub fn var_assign(target: Expr, value: Expr, expr_span: Span, value_span: Span) -> Expr {
593 if let Expr::Var(n, s) = target {
594 Expr::VarAssign(n, Box::from(value), s, value_span)
595 } else if let Expr::ArrayGetIndex(base, idx, _) = target {
596 Expr::ArrayModify(base, idx, Box::from(value), expr_span, value_span)
597 } else if let Expr::GetStructField(obj, field, obj_span, field_span) = target {
598 Expr::SetStructField(
599 obj,
600 field,
601 Box::from(value),
602 obj_span,
603 field_span,
604 value_span,
605 )
606 } else {
607 unsafe { unreachable_unchecked() }
608 }
609}
610
611/// Records a name the code read or wrote without binding it first, when the
612/// walk is inside an anonymous function.
613fn use_free_name(name: &SmolStr, depth: u32, bound: &[SmolStr], out: &mut Vec<SmolStr>) {
614 if depth > 0 && !bound.contains(name) && !out.contains(name) {
615 out.push(name.clone());
616 }
617}
618
619/// Walks a block in its own scope: the names it declares go out of scope with
620/// it, so a closure written after it does not see them.
621fn scan_block_free_names(
622 code: &[Expr],
623 depth: u32,
624 bound: &mut Vec<SmolStr>,
625 out: &mut Vec<SmolStr>,
626) {
627 let bound_len = bound.len();
628 for expr in code {
629 scan_free_names(expr, depth, bound, out);
630 }
631 bound.truncate(bound_len);
632}
633
634/// Collects, in the order they first appear, the names the anonymous
635/// functions in `expr` read or write from the scope around them.
636///
637/// `bound` holds what is in scope at the point being walked, so a name it does
638/// not hold comes from further out, which is what a closure captures. `depth`
639/// counts the anonymous functions the walk is inside: a name used outside one
640/// is resolved where it stands and is not recorded. A call name counts as a
641/// use, since the callee may be a closure held in a variable; a name that
642/// turns out to name a declared function is dropped by the caller, which keeps
643/// only the names that resolve to variables. A nested function declaration has
644/// a scope of its own and reads nothing from around it, so its body is
645/// skipped.
646fn scan_free_names(expr: &Expr, depth: u32, bound: &mut Vec<SmolStr>, out: &mut Vec<SmolStr>) {
647 match expr {
648 Expr::Var(name, _) => use_free_name(name, depth, bound, out),
649 Expr::VarAssign(name, value, _, _) => {
650 scan_free_names(value, depth, bound, out);
651 use_free_name(name, depth, bound, out);
652 }
653 Expr::VarDeclare(name, value, _) => {
654 scan_free_names(value, depth, bound, out);
655 bound.push(name.clone());
656 }
657 Expr::AnonymousFunction(params, body, _) => {
658 let bound_len = bound.len();
659 bound.extend(params.iter().cloned());
660 scan_block_free_names(body, depth + 1, bound, out);
661 bound.truncate(bound_len);
662 }
663 Expr::FunctionCall(args, namespace, _, _, _) => {
664 for arg in args {
665 scan_free_names(arg, depth, bound, out);
666 }
667 if let [name] = &**namespace {
668 use_free_name(name, depth, bound, out);
669 }
670 }
671 Expr::ObjFunctionCall(obj, args, _, _, _, _, _) => {
672 scan_free_names(obj, depth, bound, out);
673 for arg in args {
674 scan_free_names(arg, depth, bound, out);
675 }
676 }
677 Expr::CallValue(callee, args, _, _) => {
678 scan_free_names(callee, depth, bound, out);
679 for arg in args {
680 scan_free_names(arg, depth, bound, out);
681 }
682 }
683 Expr::Array(items, _) => {
684 for item in items {
685 scan_free_names(item, depth, bound, out);
686 }
687 }
688 Expr::Map(pairs, _) => {
689 for (key, _, value, _) in pairs {
690 scan_free_names(key, depth, bound, out);
691 scan_free_names(value, depth, bound, out);
692 }
693 }
694 Expr::Struct(_, fields, _, _, base) => {
695 for (_, value, _, _) in fields {
696 scan_free_names(value, depth, bound, out);
697 }
698 if let Some(base) = base.as_ref().and_then(|b| b.0.base()) {
699 scan_free_names(base, depth, bound, out);
700 }
701 }
702 Expr::Match(scrutinee, arms, wildcard, _, _) => {
703 scan_free_names(scrutinee, depth, bound, out);
704 for (patterns, body) in arms {
705 let bound_len = bound.len();
706 // An enum pattern binds its payload for the arm body; any
707 // other pattern is a value the scrutinee is compared against.
708 for pattern in patterns {
709 if let Expr::FunctionCall(binders, _, _, _, _) = pattern {
710 for binder in binders {
711 if let Expr::Var(name, _) = binder {
712 bound.push(name.clone());
713 }
714 }
715 } else {
716 scan_free_names(pattern, depth, bound, out);
717 }
718 }
719 scan_block_free_names(body, depth, bound, out);
720 bound.truncate(bound_len);
721 }
722 if let Some(body) = wildcard {
723 scan_block_free_names(body, depth, bound, out);
724 }
725 }
726 Expr::Condition(condition, body, ..)
727 | Expr::InlineCondition(condition, body, ..)
728 | Expr::ElseIfBlock(condition, body, ..)
729 | Expr::WhileBlock(condition, body, ..) => {
730 // What an `is` in the condition binds is in scope for the
731 // condition and the body, and goes out of scope with them.
732 let bound_len = bound.len();
733 scan_free_names(condition, depth, bound, out);
734 scan_block_free_names(body, depth, bound, out);
735 bound.truncate(bound_len);
736 }
737 Expr::Is(operand, target, _, _) => {
738 scan_free_names(operand, depth, bound, out);
739 if let IsTarget::Variant(Expr::FunctionCall(binders, _, _, _, _)) = &**target {
740 for binder in binders {
741 if let Expr::Var(name, _) = binder {
742 bound.push(name.clone());
743 }
744 }
745 }
746 }
747 Expr::ElseBlock(body) | Expr::EvalBlock(body) | Expr::LoopBlock(body) => {
748 scan_block_free_names(body, depth, bound, out);
749 }
750 Expr::ForLoop(var_name, iterated, body, _) => {
751 scan_free_names(iterated, depth, bound, out);
752 let bound_len = bound.len();
753 bound.push(var_name.clone());
754 scan_block_free_names(body, depth, bound, out);
755 bound.truncate(bound_len);
756 }
757 Expr::IntForLoop(var_name, start, end, body, _, _) => {
758 scan_free_names(start, depth, bound, out);
759 scan_free_names(end, depth, bound, out);
760 let bound_len = bound.len();
761 bound.push(var_name.clone());
762 scan_block_free_names(body, depth, bound, out);
763 bound.truncate(bound_len);
764 }
765 Expr::TryCatchBlock(try_code, err_var, catch_code) => {
766 scan_block_free_names(try_code, depth, bound, out);
767 let bound_len = bound.len();
768 bound.push(err_var.clone());
769 scan_block_free_names(catch_code, depth, bound, out);
770 bound.truncate(bound_len);
771 }
772 Expr::ReturnVal(value) => {
773 if let Some(value) = value.as_ref() {
774 scan_free_names(value, depth, bound, out);
775 }
776 }
777 Expr::GetStructField(obj, _, _, _)
778 | Expr::Propagate(obj, _)
779 | Expr::Cast(obj, _, _, _)
780 | Expr::BoolNeg(obj, _, _)
781 | Expr::Neg(obj, _, _)
782 | Expr::BitNot(obj, _, _) => {
783 scan_free_names(obj, depth, bound, out);
784 }
785 Expr::SetStructField(obj, _, value, _, _, _) => {
786 scan_free_names(obj, depth, bound, out);
787 scan_free_names(value, depth, bound, out);
788 }
789 Expr::ArrayGetIndex(base, index, _) => {
790 scan_free_names(base, depth, bound, out);
791 scan_free_names(index, depth, bound, out);
792 }
793 Expr::ArrayGetSlice(base, start, end, _) => {
794 scan_free_names(base, depth, bound, out);
795 scan_free_names(start, depth, bound, out);
796 if let Some(end) = end {
797 scan_free_names(end, depth, bound, out);
798 }
799 }
800 Expr::ArrayModify(base, start, end, _, _) => {
801 scan_free_names(base, depth, bound, out);
802 scan_free_names(start, depth, bound, out);
803 scan_free_names(end, depth, bound, out);
804 }
805 Expr::Mul(l, r, _, _)
806 | Expr::Div(l, r, _, _)
807 | Expr::Add(l, r, _, _)
808 | Expr::Sub(l, r, _, _)
809 | Expr::Mod(l, r, _, _)
810 | Expr::Pow(l, r, _, _)
811 | Expr::BitAnd(l, r, _, _)
812 | Expr::BitOr(l, r, _, _)
813 | Expr::BitXor(l, r, _, _)
814 | Expr::Shl(l, r, _, _)
815 | Expr::Shr(l, r, _, _)
816 | Expr::Eq(l, r, _, _)
817 | Expr::NotEq(l, r, _, _)
818 | Expr::Sup(l, r, _, _)
819 | Expr::SupEq(l, r, _, _)
820 | Expr::Inf(l, r, _, _)
821 | Expr::InfEq(l, r, _, _)
822 | Expr::BoolAnd(l, r, _, _)
823 | Expr::BoolOr(l, r, _, _) => {
824 scan_free_names(l, depth, bound, out);
825 scan_free_names(r, depth, bound, out);
826 }
827 // A literal, a type declaration, an import and a nested function
828 // declaration read nothing from the scope around them.
829 Expr::Float(_)
830 | Expr::Int(_)
831 | Expr::Bool(_)
832 | Expr::Null
833 | Expr::String(_)
834 | Expr::NamespacedRef(_, _, _)
835 | Expr::StructDefault(_, _)
836 | Expr::StructDeclare(..)
837 | Expr::EnumDeclare(_, _, _, _)
838 | Expr::FunctionDecl(_, _, _, _, _, _)
839 | Expr::ImportDylib(_, _, _)
840 | Expr::HostBlock(..)
841 | Expr::ImportFile(_, _, _, _)
842 | Expr::Break
843 | Expr::Continue => {}
844 }
845}
846
847/// The names an anonymous function reads or writes from the scope it is
848/// written in, in the order they first appear.
849///
850/// The caller keeps the ones that name a variable there: those are the
851/// closure's captures, and the rest name declared functions or nothing at all.
852#[must_use]
853pub fn closure_free_names(params: &[SmolStr], body: &[Expr]) -> Vec<SmolStr> {
854 let mut bound: Vec<SmolStr> = params.to_vec();
855 let mut out = Vec::new();
856 scan_block_free_names(body, 1, &mut bound, &mut out);
857 out
858}
859
860/// Whether an anonymous function written anywhere in `code` reads or writes
861/// `name` from the scope around it.
862///
863/// This is what decides whether a variable lives in a cell: one no closure
864/// names keeps its register and costs what it costs today.
865#[must_use]
866pub fn code_captures_variable(name: &SmolStr, code: &[Expr]) -> bool {
867 let mut bound = Vec::new();
868 let mut out = Vec::new();
869 scan_block_free_names(code, 0, &mut bound, &mut out);
870 out.contains(name)
871}