Skip to main content

candela/compiler/
type_system.rs

1use super::expr::Expr;
2use super::expr::Span;
3use super::expr::mangle_method;
4use super::expr::symbol_of_expr;
5use crate::compiler::Namespace;
6use crate::compiler::compiler_data::Ctx;
7use crate::compiler::compiler_data::FnSignature;
8use crate::compiler::compiler_data::Function;
9use crate::compiler::compiler_data::Source;
10use crate::compiler::compiler_data::State;
11use crate::compiler::compiler_data::Variable;
12use crate::compiler::compiler_errors::error_invalid_type;
13use crate::compiler::compiler_errors::error_op;
14use crate::compiler::compiler_errors::error_struct_unknown_field;
15use crate::compiler::compiler_errors::error_unknown_function;
16use crate::compiler::compiler_errors::error_unknown_function_in_namespace;
17use crate::compiler::compiler_errors::error_unknown_struct;
18use crate::compiler::compiler_errors::error_unknown_type;
19use crate::compiler::compiler_errors::error_unknown_type_with_namespace;
20use crate::compiler::compiler_errors::error_unknown_variable;
21use rustc_hash::FxHashSet;
22use smol_strc::SmolStr;
23use smol_strc::ToSmolStr;
24use std::cell::RefCell;
25use std::hint::cold_path;
26use std::hint::unreachable_unchecked;
27use std::rc::Rc;
28
29pub use crate::rt::DataType;
30
31/// Name prefix for the synthetic top-level function an anonymous function is
32/// hoisted to. `<` is not a legal identifier character, so a hoisted name can
33/// never collide with a user-written function.
34const ANON_FN_PREFIX: &str = "<anon>";
35
36// Tracks which user-defined functions are currently being analysed for their
37// return type. Used to break mutual-recursion cycles in type inference
38thread_local! {
39    static RETURN_TYPE_INFERRING: RefCell<FxHashSet<usize>> =
40        RefCell::new(FxHashSet::default());
41}
42
43/// Clears inference bookkeeping left behind when a previous compilation on
44/// this thread was aborted by an error unwind (see `errors::collect_diagnostic`).
45pub fn reset_inference_state() {
46    RETURN_TYPE_INFERRING.with(|s| s.borrow_mut().clear());
47}
48
49#[derive(Debug, PartialEq, Eq, Clone)]
50pub enum TypeExpr {
51    Identifier(SmolStr, Span),
52    NamespacedIdentifier(Box<[SmolStr]>, Span),
53    Array(Box<Self>),
54    Map(Box<Self>, Box<Self>),
55    Union(Box<[Self]>),
56}
57
58impl TypeExpr {
59    #[must_use]
60    pub fn to_datatype(
61        &self,
62        file_idx: u16,
63        namespace: &Namespace,
64        sources: &[Source],
65    ) -> DataType {
66        match self {
67            Self::Identifier(s, span) => match s.as_str() {
68                "int" => DataType::Int,
69                "float" => DataType::Float,
70                "bool" => DataType::Bool,
71                "string" => DataType::String,
72                "null" => DataType::Null,
73                // A dynamically-typed slot. Written `any`; modeled as `Unknown`,
74                // which the type checker already treats permissively. Used for
75                // enum payloads that hold a value of any type (option/result).
76                "any" => DataType::Unknown,
77                struct_name => {
78                    if let Some(struct_id) =
79                        namespace.find_struct(&[], struct_name, *span, file_idx, sources)
80                    {
81                        DataType::Struct(struct_id as u16)
82                    } else if let Some(enum_id) = namespace.find_enum(&[], struct_name) {
83                        DataType::Enum(enum_id as u16)
84                    } else {
85                        error_unknown_type(*span, file_idx, struct_name, sources, namespace);
86                    }
87                }
88            },
89            Self::NamespacedIdentifier(s, span) => {
90                if let Some(struct_id) = namespace.find_struct(
91                    &s[..s.len() - 1],
92                    unsafe { s.last().unwrap_unchecked() },
93                    *span,
94                    file_idx,
95                    sources,
96                ) {
97                    DataType::Struct(struct_id as u16)
98                } else if let Some(enum_id) =
99                    namespace.find_enum(&s[..s.len() - 1], unsafe { s.last().unwrap_unchecked() })
100                {
101                    DataType::Enum(enum_id as u16)
102                } else {
103                    cold_path();
104                    error_unknown_type_with_namespace(
105                        *span,
106                        file_idx,
107                        unsafe { s.last().unwrap_unchecked() },
108                        sources,
109                        namespace,
110                        &s[..s.len() - 1],
111                    )
112                }
113            }
114            Self::Array(inner_t) => DataType::Array(Some(Box::new(
115                inner_t.to_datatype(file_idx, namespace, sources),
116            ))),
117            Self::Map(k_t, v_t) => DataType::Map(Box::from((
118                Some(k_t.to_datatype(file_idx, namespace, sources)),
119                Some(v_t.to_datatype(file_idx, namespace, sources)),
120            ))),
121            Self::Union(poly) => DataType::Union(
122                poly.iter()
123                    .map(|t| t.to_datatype(file_idx, namespace, sources))
124                    .collect(),
125            )
126            .check_poly(),
127        }
128    }
129}
130
131/// Renders a [`DataType`] with full struct/function detail for diagnostics.
132///
133/// Field and argument names are resolved against the compiler `State` by
134/// `Struct`/`Fn` id. The plain `Display` impl (in `candela-vm`) has no
135/// `State`, so it renders those variants opaquely; this is the compiler-side
136/// detailed form.
137#[must_use]
138pub fn format_detailed(t: &DataType, state: &State<'_>) -> SmolStr {
139    match t {
140        DataType::Float => SmolStr::new_static("float"),
141        DataType::Int => SmolStr::new_static("int"),
142        DataType::Bool => SmolStr::new_static("bool"),
143        DataType::String => SmolStr::new_static("string"),
144        DataType::Array(array_type) => match array_type {
145            Some(array_type) => {
146                format_args!("{}[]", format_detailed(array_type, state)).to_smolstr()
147            }
148            None => SmolStr::new_static("Unknown[]"),
149        },
150        DataType::Null => SmolStr::new_static("null"),
151        DataType::Unknown => SmolStr::new_static("Unknown"),
152        DataType::Union(types) => format_args!(
153            "{}",
154            types
155                .into_iter()
156                .map(|x| format_detailed(x, state))
157                .collect::<Vec<SmolStr>>()
158                .join("|")
159        )
160        .to_smolstr(),
161        DataType::Struct(s) => {
162            let s = &state.structs[*s as usize];
163            format_args!(
164                "{} {{{}}}",
165                s.name,
166                s.fields
167                    .iter()
168                    .map(|(n, t, _)| {
169                        format_args!("{n}: {}", format_detailed(t, state)).to_smolstr()
170                    })
171                    .collect::<Vec<SmolStr>>()
172                    .join(", ")
173            )
174            .to_smolstr()
175        }
176        DataType::Enum(e) => state.enums[*e as usize].name.clone(),
177        DataType::Map(m) => format_args!(
178            "{{{}: {}}}",
179            m.0.as_ref().unwrap_or(&DataType::Unknown),
180            m.1.as_ref().unwrap_or(&DataType::Unknown)
181        )
182        .to_smolstr(),
183        DataType::Fn(id) => {
184            let f = &state.fns[*id as usize];
185            format_args!(
186                "fn ({})",
187                f.args
188                    .iter()
189                    .map(|(a, _)| a.clone())
190                    .collect::<Vec<SmolStr>>()
191                    .join(", ")
192            )
193            .to_smolstr()
194        }
195    }
196}
197
198#[inline(always)]
199#[must_use]
200pub fn struct_field_type_matches(expected: &DataType, received: &DataType) -> bool {
201    received == &DataType::Null || expected == received
202}
203
204/// Whether an argument of type `received` satisfies a parameter declared as
205/// `expected`.
206///
207/// `Unknown` is the `any` slot and the type of a value the checker cannot pin
208/// down (a `json::parse` result, for instance). It stands in for every type on
209/// either side, so annotating a parameter `any` keeps the parameter dynamic and
210/// passing a dynamic value to a typed parameter is still allowed. Every other
211/// pair uses the ordinary type equality.
212#[inline(always)]
213#[must_use]
214pub fn param_type_matches(expected: &DataType, received: &DataType) -> bool {
215    *expected == DataType::Unknown || *received == DataType::Unknown || expected == received
216}
217
218/// Equality for monomorphization and return-type cache keys.
219///
220/// Identical to the loose type `==` except that function-typed arguments compare
221/// by exact `Fn` id, so each distinct function passed to a higher-order function
222/// keys its own specialization. Function references are always top-level
223/// arguments (a function is passed directly, never nested inside an array or
224/// map), so only the top-level `Fn` case needs the stricter rule.
225#[must_use]
226pub fn arg_types_specialize_equal(a: &[DataType], b: &[DataType]) -> bool {
227    a.len() == b.len()
228        && a.iter().zip(b).all(|(x, y)| match (x, y) {
229            (DataType::Fn(i), DataType::Fn(j)) => i == j,
230            (DataType::Fn(_), _) | (_, DataType::Fn(_)) => false,
231            _ => x == y,
232        })
233}
234
235/// Collect all the function calls in the given code
236///
237/// # Panics
238///
239/// Panics when a `FunctionCall` node carries an empty namespace path, which
240/// the parser never produces.
241pub fn collect_direct_fn_calls(content: &[Expr], calls: &mut Vec<SmolStr>) {
242    let mut expr_stack: Vec<&Expr> = content.iter().collect();
243    while let Some(expression) = expr_stack.pop() {
244        match expression {
245            Expr::FunctionCall(args, namespace, _, _) => {
246                calls.push(namespace.last().unwrap().clone());
247                expr_stack.extend(args.iter());
248            }
249            Expr::Condition(x, y, _)
250            | Expr::InlineCondition(x, y, _)
251            | Expr::ElseIfBlock(x, y)
252            | Expr::WhileBlock(x, y)
253            | Expr::ObjFunctionCall(x, y, _, _, _, _) => {
254                expr_stack.push(x);
255                expr_stack.extend(y.iter());
256            }
257            Expr::ElseBlock(x) | Expr::EvalBlock(x) | Expr::LoopBlock(x) => {
258                expr_stack.extend(x.iter());
259            }
260            Expr::ReturnVal(code) => {
261                if let Some(code) = code.as_ref() {
262                    expr_stack.push(code);
263                }
264            }
265            Expr::FunctionDecl(_, _, x, _, _) => expr_stack.extend(x.iter()),
266            Expr::ArrayGetSlice(x, y, z, _) => {
267                expr_stack.push(x);
268                expr_stack.push(y);
269                expr_stack.push(z);
270            }
271            Expr::VarDeclare(_, x)
272            | Expr::VarAssign(_, x, _)
273            | Expr::Neg(x, _, _)
274            | Expr::BoolNeg(x, _, _) => expr_stack.push(x),
275            Expr::ForLoop(_, _, code, _) => expr_stack.extend(code.iter()),
276            Expr::IntForLoop(_, start, end, code, _, _) => {
277                expr_stack.push(start);
278                expr_stack.push(end);
279                expr_stack.extend(code.iter());
280            }
281            Expr::ArrayModify(array, index, value, _, _) => {
282                expr_stack.push(array);
283                expr_stack.push(index);
284                expr_stack.push(value);
285            }
286            Expr::Array(elems, _) => expr_stack.extend(elems.iter()),
287            Expr::Struct(_, fields, _) => {
288                expr_stack.extend(fields.iter().map(|(_, expr, _, _)| expr));
289            }
290            Expr::GetStructField(expr, _, _, _) => expr_stack.push(expr),
291            Expr::SetStructField(expr, _, value, _, _, _) => {
292                expr_stack.push(expr);
293                expr_stack.push(value);
294            }
295            Expr::TryCatchBlock(try_code, _, catch_code) => {
296                expr_stack.extend(try_code.iter());
297                expr_stack.extend(catch_code.iter());
298            }
299            Expr::Match(scrutinee, arms, wildcard, _) => {
300                expr_stack.push(scrutinee);
301                for (pat, body) in arms {
302                    expr_stack.push(pat);
303                    expr_stack.extend(body.iter());
304                }
305                if let Some(w) = wildcard {
306                    expr_stack.extend(w.iter());
307                }
308            }
309            Expr::ArrayGetIndex(x, y, _)
310            | Expr::Mul(x, y, _, _)
311            | Expr::Div(x, y, _, _)
312            | Expr::Add(x, y, _, _)
313            | Expr::Sub(x, y, _, _)
314            | Expr::Mod(x, y, _, _)
315            | Expr::Pow(x, y, _, _)
316            | Expr::Eq(x, y)
317            | Expr::NotEq(x, y)
318            | Expr::Sup(x, y, _, _)
319            | Expr::SupEq(x, y, _, _)
320            | Expr::Inf(x, y, _, _)
321            | Expr::InfEq(x, y, _, _)
322            | Expr::BoolAnd(x, y, _, _)
323            | Expr::BoolOr(x, y, _, _) => {
324                expr_stack.push(x);
325                expr_stack.push(y);
326            }
327            _ => {}
328        }
329    }
330}
331
332/// Check if the function src_fn can call target_fn
333pub fn can_reach<S: std::hash::BuildHasher>(
334    src_fn: &str,
335    target_fn: &str,
336    fns: &[Function],
337    visited: &mut std::collections::HashSet<SmolStr, S>,
338) -> bool {
339    if let Some(from_fn) = fns.iter().find(|f| f.name.as_str() == src_fn) {
340        for callee in &from_fn.direct_calls {
341            if callee == target_fn {
342                return true;
343            }
344            if visited.insert(callee.clone()) && can_reach(callee, target_fn, fns, visited) {
345                return true;
346            }
347        }
348    }
349    false
350}
351
352#[must_use]
353pub fn check_if_returns_void(content: &[Expr]) -> bool {
354    for content in content {
355        match content {
356            Expr::ElseIfBlock(_, code)
357            | Expr::ElseBlock(code)
358            | Expr::Condition(_, code, _)
359            | Expr::InlineCondition(_, code, _)
360            | Expr::WhileBlock(_, code)
361            | Expr::ForLoop(_, _, code, _)
362            | Expr::EvalBlock(code)
363            | Expr::LoopBlock(code)
364            | Expr::IntForLoop(_, _, _, code, _, _) => {
365                if !check_if_returns_void(code) {
366                    return false;
367                }
368            }
369            Expr::Match(_, arms, wildcard, _) => {
370                for (_, body) in arms {
371                    if !check_if_returns_void(body) {
372                        return false;
373                    }
374                }
375                if let Some(w) = wildcard
376                    && !check_if_returns_void(w)
377                {
378                    return false;
379                }
380            }
381            Expr::ReturnVal(return_val) if return_val.is_some() => {
382                return false;
383            }
384            _ => {}
385        }
386    }
387    true
388}
389
390macro_rules! add_return_type {
391    ($return_types: expr, $return_type: expr) => {
392        if $return_type != DataType::Unknown && !($return_types).contains(&($return_type)) {
393            ($return_types).push($return_type);
394        }
395    };
396}
397
398macro_rules! extend_return_types {
399    ($return_types: expr, $new_types: expr) => {
400        for return_type in $new_types {
401            add_return_type!($return_types, return_type);
402        }
403    };
404}
405
406pub fn track_returns(
407    content: &[Expr],
408    v: &mut Vec<Variable>,
409    ctx: Ctx,
410    state: &mut State<'_>,
411    fn_name: &str,
412) -> Vec<DataType> {
413    let mut flow = track_return_flow(content, v, ctx, state, fn_name);
414    if !flow.always_returns && !flow.types.is_empty() {
415        add_return_type!(&mut flow.types, DataType::Null);
416    }
417    flow.types
418}
419
420struct FnReturnFlow {
421    types: Vec<DataType>,
422    always_returns: bool,
423}
424
425fn track_scoped_returns(
426    code: &[Expr],
427    v: &mut Vec<Variable>,
428    ctx: Ctx,
429    state: &mut State<'_>,
430    fn_name: &str,
431) -> FnReturnFlow {
432    let v_len = v.len();
433    let flow = track_return_flow(code, v, ctx, state, fn_name);
434    v.truncate(v_len);
435    flow
436}
437
438fn track_condition_returns(
439    code: &[Expr],
440    v: &mut Vec<Variable>,
441    ctx: Ctx,
442    state: &mut State<'_>,
443    fn_name: &str,
444) -> FnReturnFlow {
445    let mut return_types = Vec::new();
446    let first_branch_end = code
447        .iter()
448        .position(|expr| matches!(expr, Expr::ElseIfBlock(_, _) | Expr::ElseBlock(_)))
449        .unwrap_or(code.len());
450
451    let first_flow = track_scoped_returns(&code[..first_branch_end], v, ctx, state, fn_name);
452    let mut all_branches_return = first_flow.always_returns;
453    let mut has_else = false;
454    extend_return_types!(&mut return_types, first_flow.types);
455
456    for expr in &code[first_branch_end..] {
457        match expr {
458            Expr::ElseIfBlock(_, branch_code) => {
459                let flow = track_scoped_returns(branch_code, v, ctx, state, fn_name);
460                all_branches_return &= flow.always_returns;
461                extend_return_types!(&mut return_types, flow.types);
462            }
463            Expr::ElseBlock(branch_code) => {
464                has_else = true;
465                let flow = track_scoped_returns(branch_code, v, ctx, state, fn_name);
466                all_branches_return &= flow.always_returns;
467                extend_return_types!(&mut return_types, flow.types);
468            }
469            _ => {}
470        }
471    }
472
473    FnReturnFlow {
474        types: return_types,
475        always_returns: has_else && all_branches_return,
476    }
477}
478
479fn track_return_flow(
480    content: &[Expr],
481    v: &mut Vec<Variable>,
482    ctx: Ctx,
483    state: &mut State<'_>,
484    fn_name: &str,
485) -> FnReturnFlow {
486    let mut return_types: Vec<DataType> = Vec::new();
487    for expr in content {
488        match expr {
489            Expr::Condition(_, code, _) | Expr::InlineCondition(_, code, _) => {
490                let flow = track_condition_returns(code, v, ctx, state, fn_name);
491                extend_return_types!(&mut return_types, flow.types);
492                if flow.always_returns {
493                    return FnReturnFlow {
494                        types: return_types,
495                        always_returns: true,
496                    };
497                }
498            }
499            Expr::ElseIfBlock(_, code)
500            | Expr::ElseBlock(code)
501            | Expr::EvalBlock(code)
502            | Expr::LoopBlock(code) => {
503                let flow = track_scoped_returns(code, v, ctx, state, fn_name);
504                extend_return_types!(&mut return_types, flow.types);
505                if flow.always_returns {
506                    return FnReturnFlow {
507                        types: return_types,
508                        always_returns: true,
509                    };
510                }
511            }
512            Expr::VarDeclare(name, expr) => {
513                let var_type = expr.infer_type(v, ctx, state);
514                v.push(Variable {
515                    name: name.clone(),
516                    register_id: 0,
517                    var_type,
518                });
519            }
520            Expr::VarAssign(name, expr, _) => {
521                let var_type = expr.infer_type(v, ctx, state);
522                if let Some(var) = v.iter_mut().rfind(|var| &var.name == name) {
523                    var.var_type = var_type;
524                }
525            }
526            Expr::WhileBlock(_, code) => {
527                let flow = track_scoped_returns(code, v, ctx, state, fn_name);
528                extend_return_types!(&mut return_types, flow.types);
529            }
530            Expr::IntForLoop(var_name, _, _, code, _, _) => {
531                let v_len = v.len();
532                v.push(Variable {
533                    name: var_name.clone(),
534                    register_id: 0,
535                    var_type: DataType::Int,
536                });
537                let flow = track_return_flow(code, v, ctx, state, fn_name);
538                extend_return_types!(&mut return_types, flow.types);
539                v.truncate(v_len);
540            }
541            Expr::ForLoop(var_name, array_expr, array_code, _) => {
542                let inferred_collection_type = array_expr.infer_type(v, ctx, state);
543                let elem_type = match inferred_collection_type {
544                    DataType::Array(inner) => inner.map_or(DataType::Unknown, |t| *t),
545                    DataType::String => DataType::String,
546                    DataType::Unknown => DataType::Unknown,
547                    // A map iterates its keys.
548                    DataType::Map(m) => m.0.map_or(DataType::Unknown, |t| t),
549                    _ => unsafe { unreachable_unchecked() },
550                };
551                let v_len = v.len();
552                if var_name.as_str() != "_" {
553                    v.push(Variable {
554                        name: var_name.clone(),
555                        register_id: 0,
556                        var_type: elem_type,
557                    });
558                }
559                let flow = track_return_flow(array_code, v, ctx, state, fn_name);
560                extend_return_types!(&mut return_types, flow.types);
561                v.truncate(v_len);
562            }
563            Expr::ObjFunctionCall(obj, args, namespace, _, _, _)
564                if namespace.last().unwrap().as_str() == "push" =>
565            {
566                if let Expr::Var(var_name, _) = obj.as_ref()
567                    && v.iter()
568                        .rfind(|var| &var.name == var_name)
569                        .is_some_and(|var| var.var_type == DataType::Array(None))
570                {
571                    let arg_type = args[0].infer_type(v, ctx, state);
572                    if let Some(var) = v.iter_mut().rfind(|var| &var.name == var_name) {
573                        var.var_type = DataType::Array(Some(Box::new(arg_type)));
574                    }
575                }
576            }
577            Expr::Match(scrutinee, arms, wildcard, span) => {
578                let scrut_type = scrutinee.infer_type(v, ctx, state);
579                let is_enum = matches!(scrut_type, DataType::Enum(_));
580                let mut all_return = true;
581                for (pat, body) in arms {
582                    let v_len = v.len();
583                    if let DataType::Enum(enum_id) = scrut_type {
584                        let (vidx, binders) = crate::compiler::resolve_variant_pattern(
585                            enum_id, pat, *span, ctx, state,
586                        );
587                        for (i, binder) in binders.iter().enumerate() {
588                            if binder.as_str() != "_" {
589                                let payload_type = state.enums[enum_id as usize].variants
590                                    [vidx as usize]
591                                    .payload[i]
592                                    .clone();
593                                v.push(Variable {
594                                    name: binder.clone(),
595                                    register_id: 0,
596                                    var_type: payload_type,
597                                });
598                            }
599                        }
600                    }
601                    let flow = track_return_flow(body, v, ctx, state, fn_name);
602                    v.truncate(v_len);
603                    all_return &= flow.always_returns;
604                    extend_return_types!(&mut return_types, flow.types);
605                }
606                let exhaustive = if wildcard.is_some() {
607                    if let Some(w) = wildcard {
608                        let flow = track_scoped_returns(w, v, ctx, state, fn_name);
609                        all_return &= flow.always_returns;
610                        extend_return_types!(&mut return_types, flow.types);
611                    }
612                    true
613                } else {
614                    // An enum match with no wildcard is compile-time exhaustive.
615                    is_enum
616                };
617                if exhaustive && all_return {
618                    return FnReturnFlow {
619                        types: return_types,
620                        always_returns: true,
621                    };
622                }
623            }
624            Expr::ReturnVal(return_val) => {
625                if let Some(val) = return_val.as_ref() {
626                    let infered = val.infer_type(v, ctx, state);
627                    add_return_type!(&mut return_types, infered);
628                } else {
629                    add_return_type!(&mut return_types, DataType::Null);
630                }
631                return FnReturnFlow {
632                    types: return_types,
633                    always_returns: true,
634                };
635            }
636            _ => {}
637        }
638    }
639    FnReturnFlow {
640        types: return_types,
641        always_returns: false,
642    }
643}
644
645/// Infers the return type of a user function specialised for `infered_arg_types`,
646/// caching the result on the function. Shared by direct `FunctionCall`s and by
647/// `impl` method calls (which resolve to a mangled free function with the
648/// receiver as argument 0). `function_name` is only used for diagnostics inside
649/// `track_returns`.
650fn infer_user_fn_return_type(
651    fn_id: usize,
652    infered_arg_types: Vec<DataType>,
653    function_name: &str,
654    v: &mut Vec<Variable>,
655    ctx: Ctx,
656    state: &mut State<'_>,
657) -> DataType {
658    let func = &state.fns[fn_id];
659    // Check the return type cache
660    if let Some((_, ret)) = func
661        .return_type_cache
662        .iter()
663        .find(|(args, _)| arg_types_specialize_equal(args, &infered_arg_types))
664    {
665        return ret.clone();
666    }
667
668    let fn_args = func.args.clone();
669    let fn_code = func.code.clone();
670    let fn_src_file = func.src_file;
671    let v_len_before_args = v.len();
672    for (i, infered_type) in infered_arg_types.iter().cloned().enumerate() {
673        // 0 => placeholder id, it's never used
674        v.push(Variable {
675            name: fn_args[i].0.clone(),
676            register_id: 0,
677            var_type: infered_type,
678        });
679    }
680
681    // Mutual-recursion cycle guard -> if we are already in the middle of
682    // inferring this function's return type, return Unknown to break the cycle
683    let already_inferring = RETURN_TYPE_INFERRING.with(|s| s.borrow().contains(&fn_id));
684    if already_inferring {
685        v.truncate(v_len_before_args);
686        return DataType::Unknown;
687    }
688
689    RETURN_TYPE_INFERRING.with(|s| s.borrow_mut().insert(fn_id));
690
691    let fn_ctx = Ctx {
692        file_idx: fn_src_file,
693        ..ctx
694    };
695    let fn_type = track_returns(&fn_code, v, fn_ctx, state, function_name);
696
697    RETURN_TYPE_INFERRING.with(|s| s.borrow_mut().remove(&fn_id));
698
699    let to_return = if fn_type.is_empty() {
700        // No tracked type means either no value is returned at all, or every
701        // returned value was itself dynamic (return-type tracking records no
702        // type for `Unknown`). A function handing back an `any` payload is
703        // dynamic, not null.
704        if check_if_returns_void(&fn_code) {
705            DataType::Null
706        } else {
707            DataType::Unknown
708        }
709    } else {
710        // If function returns anything, check if it returns the same thing each time
711        DataType::Union(Box::from(fn_type)).check_poly()
712    };
713
714    v.truncate(v_len_before_args);
715
716    // Cache the result
717    state.fns[fn_id]
718        .return_type_cache
719        .push((Box::from(infered_arg_types), to_return.clone()));
720
721    to_return
722}
723
724impl Expr {
725    /// Infers this expression's static [`DataType`] without emitting code.
726    ///
727    /// # Panics
728    ///
729    /// Panics when a call node carries an empty namespace path, which the
730    /// parser never produces.
731    pub fn infer_type(&self, v: &mut Vec<Variable>, ctx: Ctx, state: &mut State<'_>) -> DataType {
732        match self {
733            Self::Var(name, span) => {
734                if let Some(var) = v.iter().rfind(|x| &x.name == name) {
735                    var.var_type.clone()
736                } else if let Some(fn_id) =
737                    state
738                        .namespace
739                        .find_function(&[], name, *span, ctx.file_idx, state.sources)
740                {
741                    // A bare identifier that names a function is a function
742                    // reference (a compile-time value passed to a higher-order
743                    // function). Its static type is the callee's Fn id.
744                    DataType::Fn(fn_id as u16)
745                } else if let Some((enum_id, _)) =
746                    crate::compiler::resolve_enum_variant(std::slice::from_ref(name), state)
747                {
748                    DataType::Enum(enum_id)
749                } else {
750                    error_unknown_variable(name, *span, v, ctx.file_idx, state.sources);
751                }
752            }
753            Self::Float(_) => DataType::Float,
754            Self::Int(_) => DataType::Int,
755            Self::String(_) => DataType::String,
756            Self::Bool(_) | Self::Eq(_, _) | Self::NotEq(_, _) => DataType::Bool,
757            Self::Null => DataType::Null,
758            Self::Array(x, _) => DataType::Array(if x.is_empty() {
759                None
760            } else {
761                let elem_type = x
762                    .iter()
763                    .map(|elem| elem.infer_type(v, ctx, state))
764                    .find(|elem_type| *elem_type != DataType::Unknown)
765                    .unwrap_or(DataType::Unknown);
766                Some(Box::from(elem_type))
767            }),
768            Self::Map(kv_pairs, _) => {
769                if kv_pairs.is_empty() {
770                    // An empty map literal has no key/value types yet, like an
771                    // empty array (`Array(None)`); `insert` fills them in.
772                    DataType::Map(Box::from((None, None)))
773                } else {
774                    let kv_type = kv_pairs
775                        .iter()
776                        .map(|(key, _, value, _)| {
777                            (
778                                key.infer_type(v, ctx, state),
779                                value.infer_type(v, ctx, state),
780                            )
781                        })
782                        .find(|(key_t, val_t)| {
783                            key_t != &DataType::Unknown || val_t != &DataType::Unknown
784                        })
785                        .map_or(
786                            (Some(DataType::Unknown), Some(DataType::Unknown)),
787                            |(key_t, val_t)| (Some(key_t), Some(val_t)),
788                        );
789                    DataType::Map(Box::from(kv_type))
790                }
791            }
792            Self::Add(x, y, span_l, span_r) => {
793                match (x.infer_type(v, ctx, state), y.infer_type(v, ctx, state)) {
794                    (DataType::Unknown, t) | (t, DataType::Unknown) => t,
795                    (DataType::Float, DataType::Float) => DataType::Float,
796                    (DataType::Int, DataType::Int) => DataType::Int,
797                    (DataType::String, DataType::String) => DataType::String,
798                    (DataType::Array(t1), DataType::Array(t2)) => DataType::Array(t1.or(t2)),
799                    (l, r) => {
800                        error_op(&l, &r, "+", *span_l, *span_r, ctx.file_idx, state.sources);
801                    }
802                }
803            }
804            Self::Mul(x, y, span_l, span_r)
805            | Self::Div(x, y, span_l, span_r)
806            | Self::Sub(x, y, span_l, span_r)
807            | Self::Mod(x, y, span_l, span_r)
808            | Self::Pow(x, y, span_l, span_r) => {
809                match (x.infer_type(v, ctx, state), y.infer_type(v, ctx, state)) {
810                    (DataType::Unknown, t) | (t, DataType::Unknown)
811                        if matches!(t, DataType::Float | DataType::Int | DataType::Unknown) =>
812                    {
813                        t
814                    }
815                    (DataType::Float, DataType::Float) => DataType::Float,
816                    (DataType::Int, DataType::Int) => DataType::Int,
817                    (l, r) => {
818                        error_op(
819                            &l,
820                            &r,
821                            symbol_of_expr(self),
822                            *span_l,
823                            *span_r,
824                            ctx.file_idx,
825                            state.sources,
826                        );
827                    }
828                }
829            }
830            Self::Sup(x, y, span_l, span_r)
831            | Self::SupEq(x, y, span_l, span_r)
832            | Self::Inf(x, y, span_l, span_r)
833            | Self::InfEq(x, y, span_l, span_r) => {
834                match (x.infer_type(v, ctx, state), y.infer_type(v, ctx, state)) {
835                    (DataType::Unknown, DataType::Float | DataType::Int)
836                    | (DataType::Float | DataType::Int, DataType::Unknown)
837                    | (DataType::Float, DataType::Float)
838                    | (DataType::Int, DataType::Int) => DataType::Bool,
839                    (l, r) => error_op(
840                        &l,
841                        &r,
842                        symbol_of_expr(self),
843                        *span_l,
844                        *span_r,
845                        ctx.file_idx,
846                        state.sources,
847                    ),
848                }
849            }
850            Self::BoolAnd(x, y, span_l, span_r) | Self::BoolOr(x, y, span_l, span_r) => {
851                match (x.infer_type(v, ctx, state), y.infer_type(v, ctx, state)) {
852                    (DataType::Unknown | DataType::Bool, DataType::Bool)
853                    | (DataType::Bool, DataType::Unknown) => DataType::Bool,
854                    (l, r) => {
855                        error_op(&l, &r, "&&", *span_l, *span_r, ctx.file_idx, state.sources);
856                    }
857                }
858            }
859            Self::Neg(e, span_l, span_r) => match e.infer_type(v, ctx, state) {
860                DataType::Float => DataType::Float,
861                DataType::Int => DataType::Int,
862                DataType::Unknown => DataType::Unknown,
863                operand_type => error_op(
864                    &DataType::Null,
865                    &operand_type,
866                    "-",
867                    *span_l,
868                    *span_r,
869                    ctx.file_idx,
870                    state.sources,
871                ),
872            },
873            Self::BoolNeg(e, span_l, span_r) => match e.infer_type(v, ctx, state) {
874                DataType::Bool => DataType::Bool,
875                operand_type => error_op(
876                    &DataType::Null,
877                    &operand_type,
878                    "!",
879                    *span_l,
880                    *span_r,
881                    ctx.file_idx,
882                    state.sources,
883                ),
884            },
885            Self::ArrayGetIndex(array, _, _) => match array.infer_type(v, ctx, state) {
886                DataType::Array(array_type) => array_type.map_or(DataType::Null, |t| *t),
887                DataType::String => DataType::String,
888                DataType::Unknown => DataType::Unknown,
889                _ => unsafe { unreachable_unchecked() },
890            },
891            Self::GetStructField(s, field, struct_span, field_span) => {
892                let s = s.infer_type(v, ctx, state);
893                if let DataType::Struct(s_id) = s {
894                    state.structs[s_id as usize]
895                        .fields
896                        .iter()
897                        .find(|x| &x.0 == field)
898                        .unwrap_or_else(|| {
899                            let s = &state.structs[s_id as usize];
900                            error_struct_unknown_field(
901                                ctx.file_idx,
902                                *field_span,
903                                field,
904                                &s.name,
905                                &s.fields,
906                                state.sources,
907                            )
908                        })
909                        .1
910                        .clone()
911                } else {
912                    error_invalid_type(
913                        &DataType::Struct(0),
914                        &s,
915                        *struct_span,
916                        None,
917                        None,
918                        ctx.file_idx,
919                        state.sources,
920                    );
921                }
922            }
923            Self::ArrayGetSlice(array, _, _, _) => match array.infer_type(v, ctx, state) {
924                DataType::Array(array_type) => DataType::Array(array_type),
925                DataType::String => DataType::String,
926                DataType::Unknown => DataType::Unknown,
927                _ => unsafe { unreachable_unchecked() },
928            },
929            Self::FunctionCall(args, namespace, span, _) => {
930                // A qualified enum-variant construction (`Color::Red(x)`) has an
931                // enum type; intercept before the namespaced-function paths.
932                if namespace.len() >= 2
933                    && let Some((enum_id, _)) =
934                        crate::compiler::resolve_enum_variant(namespace, state)
935                {
936                    return DataType::Enum(enum_id);
937                }
938                match namespace.last().unwrap().as_str() {
939                    "print" | "write" | "append" | "delete" | "delete_dir" => DataType::Null,
940                    "type" | "str" | "input" | "read" | "json_stringify" | "as_str" => {
941                        DataType::String
942                    }
943                    "float" | "as_float" => DataType::Float,
944                    "int" | "the_answer" | "as_int" => DataType::Int,
945                    "bool" | "exists" | "as_bool" | "is_int" | "is_float" | "is_str"
946                    | "is_bool" | "is_list" | "is_map" | "is_null" => DataType::Bool,
947                    "range" => DataType::Array(Some(Box::from(DataType::Int))),
948                    "argv" => DataType::Array(Some(Box::from(DataType::String))),
949                    // A downcast to a collection yields an element/entry type of
950                    // `any` (Unknown); json::parse yields a fully dynamic value.
951                    "as_list" => DataType::Array(None),
952                    "as_map" => DataType::Map(Box::from((None, None))),
953                    "json_parse" => DataType::Unknown,
954                    function_name => {
955                        // A call to a function-typed parameter (a higher-order
956                        // function calling the function it was handed): resolve
957                        // the concrete callee from the parameter's static Fn type
958                        // and infer that function's return type.
959                        if namespace.len() == 1
960                            && let Some(DataType::Fn(fn_id)) = v
961                                .iter()
962                                .rfind(|var| var.name.as_str() == function_name)
963                                .map(|var| var.var_type.clone())
964                        {
965                            let infered_arg_types = args
966                                .iter()
967                                .map(|x| x.infer_type(v, ctx, state))
968                                .collect::<Vec<DataType>>();
969                            return infer_user_fn_return_type(
970                                fn_id as usize,
971                                infered_arg_types,
972                                function_name,
973                                v,
974                                ctx,
975                                state,
976                            );
977                        }
978                        if let Some(lib) = state.dyn_libs.iter().find(|l| l.name == namespace[0])
979                            && let Some(FnSignature {
980                                return_type: fn_return_type,
981                                ..
982                            }) = lib.fns.iter().find(|x| x.name == function_name)
983                        {
984                            return fn_return_type.clone();
985                        }
986                        let infered_arg_types = args
987                            .iter()
988                            .map(|x| x.infer_type(v, ctx, state))
989                            .collect::<Vec<DataType>>();
990
991                        let Some(fn_id) = state
992                            .fns
993                            .iter()
994                            .rposition(|func| func.name == function_name)
995                        else {
996                            // An unqualified call whose name is an enum variant
997                            // (`Some(x)`) constructs that variant. User functions
998                            // above keep priority.
999                            if let Some((enum_id, _)) =
1000                                crate::compiler::resolve_enum_variant(namespace, state)
1001                            {
1002                                return DataType::Enum(enum_id);
1003                            }
1004                            if namespace.len() == 1 {
1005                                error_unknown_function(
1006                                    function_name,
1007                                    *span,
1008                                    state.namespace,
1009                                    ctx.file_idx,
1010                                    state.sources,
1011                                );
1012                            } else {
1013                                error_unknown_function_in_namespace(
1014                                    function_name,
1015                                    state.namespace,
1016                                    &namespace[..namespace.len() - 1],
1017                                    *span,
1018                                    ctx.file_idx,
1019                                    state.sources,
1020                                );
1021                            }
1022                        };
1023
1024                        infer_user_fn_return_type(
1025                            fn_id,
1026                            infered_arg_types,
1027                            function_name,
1028                            v,
1029                            ctx,
1030                            state,
1031                        )
1032                    }
1033                }
1034            }
1035            Self::ObjFunctionCall(obj, args, namespace, _, fn_span, _) => {
1036                let method = namespace.last().unwrap().as_str();
1037                let obj_type = obj.infer_type(v, ctx, state);
1038                // A user-defined impl method resolves by the receiver's static
1039                // struct type to the mangled free function `Type#method`; its
1040                // return type is inferred exactly like any free function's. This
1041                // is checked before the builtin-method table so a struct method
1042                // that happens to share a name with a builtin (e.g. `len`) uses
1043                // its own return type rather than the builtin's.
1044                if let DataType::Struct(struct_id) = obj_type {
1045                    let struct_name = state.structs[struct_id as usize].name.clone();
1046                    let mangled = mangle_method(&struct_name, method);
1047                    if let Some(fn_id) = state.fns.iter().position(|f| f.name == mangled) {
1048                        let mut arg_types: Vec<DataType> = Vec::with_capacity(args.len() + 1);
1049                        arg_types.push(DataType::Struct(struct_id));
1050                        for a in args {
1051                            arg_types.push(a.infer_type(v, ctx, state));
1052                        }
1053                        return infer_user_fn_return_type(
1054                            fn_id, arg_types, &mangled, v, ctx, state,
1055                        );
1056                    }
1057                    // No matching method: mirror the compile-time error path so
1058                    // inference does not hit the builtin arms with a struct type.
1059                    crate::compiler::compiler_errors::error_no_such_method(
1060                        method,
1061                        &struct_name,
1062                        *fn_span,
1063                        ctx.file_idx,
1064                        state.sources,
1065                    );
1066                }
1067                if let DataType::Enum(enum_id) = obj_type {
1068                    let enum_name = state.enums[enum_id as usize].name.clone();
1069                    let mangled = mangle_method(&enum_name, method);
1070                    if let Some(fn_id) = state.fns.iter().position(|f| f.name == mangled) {
1071                        let mut arg_types: Vec<DataType> = Vec::with_capacity(args.len() + 1);
1072                        arg_types.push(DataType::Enum(enum_id));
1073                        for a in args {
1074                            arg_types.push(a.infer_type(v, ctx, state));
1075                        }
1076                        return infer_user_fn_return_type(
1077                            fn_id, arg_types, &mangled, v, ctx, state,
1078                        );
1079                    }
1080                    crate::compiler::compiler_errors::error_no_such_method(
1081                        method,
1082                        &enum_name,
1083                        *fn_span,
1084                        ctx.file_idx,
1085                        state.sources,
1086                    );
1087                }
1088                // An array collection method routed to a `std/list` helper
1089                // infers its return type from that helper, specialized for the
1090                // receiver and argument types.
1091                if let Some(fn_id) = crate::compiler::methods::routed_list_method(
1092                    method, &obj_type, args, v, ctx, state,
1093                ) {
1094                    let mut arg_types: Vec<DataType> = Vec::with_capacity(args.len() + 1);
1095                    arg_types.push(obj_type.clone());
1096                    for a in args {
1097                        arg_types.push(a.infer_type(v, ctx, state));
1098                    }
1099                    return infer_user_fn_return_type(fn_id, arg_types, method, v, ctx, state);
1100                }
1101                match method {
1102                    "uppercase"
1103                    | "lowercase"
1104                    | "replace"
1105                    | "trim"
1106                    | "trim_sequence"
1107                    | "trim_left"
1108                    | "trim_right"
1109                    | "trim_sequence_left"
1110                    | "trim_sequence_right"
1111                    | "join" => DataType::String,
1112                    "starts_with" | "ends_with" | "contains" | "is_float" | "is_int" => {
1113                        DataType::Bool
1114                    }
1115                    "len" | "find" => DataType::Int,
1116                    "repeat" | "reverse" => {
1117                        let obj_type = obj.infer_type(v, ctx, state);
1118                        if obj_type == DataType::String {
1119                            DataType::String
1120                        } else if let DataType::Array(array_type) = obj_type {
1121                            DataType::Array(array_type)
1122                        } else {
1123                            unsafe { unreachable_unchecked() }
1124                        }
1125                    }
1126                    "push" | "sort" | "remove" | "insert" => DataType::Null,
1127                    "sqrt" | "round" | "floor" => DataType::Float,
1128                    "abs" => {
1129                        let obj_type = obj.infer_type(v, ctx, state);
1130                        if obj_type == DataType::Float {
1131                            DataType::Float
1132                        } else if obj_type == DataType::Int {
1133                            DataType::Int
1134                        } else {
1135                            unsafe { unreachable_unchecked() }
1136                        }
1137                    }
1138                    "split" => DataType::Array(Some(Box::from(DataType::String))),
1139                    "partition" => {
1140                        let obj_type = obj.infer_type(v, ctx, state);
1141                        if let DataType::Array(array_type) = obj_type {
1142                            DataType::Array(Some(Box::from(DataType::Array(array_type))))
1143                        } else {
1144                            unsafe { unreachable_unchecked() }
1145                        }
1146                    }
1147                    "get" => {
1148                        let obj_type = obj.infer_type(v, ctx, state);
1149                        if let DataType::Map(m) = obj_type {
1150                            m.1.unwrap_or(DataType::Unknown)
1151                        } else {
1152                            unsafe { unreachable_unchecked() }
1153                        }
1154                    }
1155                    "keys" => {
1156                        let obj_type = obj.infer_type(v, ctx, state);
1157                        if let DataType::Map(m) = obj_type {
1158                            DataType::Array(m.0.map(Box::new))
1159                        } else {
1160                            unsafe { unreachable_unchecked() }
1161                        }
1162                    }
1163                    "values" => {
1164                        let obj_type = obj.infer_type(v, ctx, state);
1165                        if let DataType::Map(m) = obj_type {
1166                            DataType::Array(m.1.map(Box::new))
1167                        } else {
1168                            unsafe { unreachable_unchecked() }
1169                        }
1170                    }
1171                    _ => unsafe { unreachable_unchecked() },
1172                }
1173            }
1174            Self::InlineCondition(_, code, _) => {
1175                let mut types: Vec<DataType> = Vec::with_capacity(code.len());
1176                types.push(code[0].infer_type(v, ctx, state));
1177                for t in &code[0..] {
1178                    if let Self::ElseIfBlock(_, code) = t {
1179                        let infered = code[0].infer_type(v, ctx, state);
1180                        if !types.contains(&infered) {
1181                            types.push(infered);
1182                        }
1183                    } else if let Self::ElseBlock(code) = t {
1184                        let infered = code[0].infer_type(v, ctx, state);
1185                        if !types.contains(&infered) {
1186                            types.push(infered);
1187                        }
1188                    }
1189                }
1190                DataType::Union(Box::from(types)).check_poly()
1191            }
1192            Self::NamespacedRef(path, span) => {
1193                if let Some((enum_id, _)) = crate::compiler::resolve_enum_variant(path, state) {
1194                    DataType::Enum(enum_id)
1195                } else {
1196                    crate::compiler::compiler_errors::error_enum(
1197                        "Unknown enum variant",
1198                        &format!("{} does not name an enum variant", path.join("::")),
1199                        *span,
1200                        ctx.file_idx,
1201                        state.sources,
1202                    );
1203                }
1204            }
1205            Self::Struct(namespace, _, span) => {
1206                let struct_name = &namespace[namespace.len() - 1];
1207                let namespace = &namespace[..(namespace.len() - 1)];
1208                DataType::Struct(
1209                    state
1210                        .namespace
1211                        .find_struct(namespace, struct_name, *span, ctx.file_idx, state.sources)
1212                        .unwrap_or_else(|| {
1213                            error_unknown_struct(struct_name, *span, state.sources, ctx.file_idx);
1214                        }) as u16,
1215                )
1216            }
1217            Self::AnonymousFunction(args, code, span) => {
1218                // An anonymous function is hoisted to a synthetic non-capturing
1219                // top-level function and referred to by its Fn id, exactly like a
1220                // named function reference. Inference runs many times, so the
1221                // hoist is keyed by source span and reused: the first encounter
1222                // registers the function, later ones resolve to the same id.
1223                let fn_name =
1224                    format_args!("{ANON_FN_PREFIX}{}:{}", span.start, span.end).to_smolstr();
1225                if let Some(id) = state.fns.iter().rposition(|f| f.name == fn_name) {
1226                    return DataType::Fn(id as u16);
1227                }
1228                let returns_null = check_if_returns_void(code);
1229                let mut callees = Vec::new();
1230                collect_direct_fn_calls(code, &mut callees);
1231                let id = state.fns.len() as u16;
1232                state.fns.push(Function {
1233                    name: fn_name,
1234                    args: args.iter().map(|a| (a.clone(), None)).collect(),
1235                    code: Rc::from(code.clone()),
1236                    impls: Vec::new(),
1237                    is_recursive: None,
1238                    returns_null,
1239                    src_file: ctx.file_idx,
1240                    return_type_cache: Vec::new(),
1241                    direct_calls: callees.into_boxed_slice(),
1242                    name_span: *span,
1243                    // An anonymous function takes no return annotation.
1244                    return_type: None,
1245                });
1246                state.fn_registers.push(Vec::new());
1247                DataType::Fn(id)
1248            }
1249            _ => unsafe { unreachable_unchecked() },
1250        }
1251    }
1252}