rukalang/
semantic.rs

1//! Pre-expansion semantic analysis scaffolding for frontend programs.
2
3use std::collections::BTreeMap;
4
5use cranelift_entity::SecondaryMap;
6use ruka_frontend::{
7    Expr, ExprId, FrontendNodeIndex, MetaExpr, MetaExprId, MetaMatchPattern, MetaStmtId, Program,
8    Stmt, TypeExpr,
9};
10
11use crate::meta_builtins::{builtin_result_type, builtin_spec_by_name};
12use crate::meta_types::{format_type_expr, meta_types_compatible};
13
14/// Runtime binding visible inside a typed expression builder.
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct BuilderBindingInfo {
17    /// Binding name visible from the surrounding runtime scope.
18    pub name: String,
19    /// Best-known type for the binding before expansion.
20    pub ty: Option<TypeExpr>,
21}
22
23/// Resolved meaning of an identifier before expansion.
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub enum ResolvedName {
26    /// Lexically scoped runtime binding.
27    RuntimeLocal,
28    /// Lexically scoped meta binding.
29    MetaLocal,
30    /// Runtime function declaration.
31    RuntimeFunction,
32    /// Meta function declaration.
33    MetaFunction,
34    /// Struct declaration.
35    Struct,
36    /// Extern module declaration.
37    ExternModule,
38    /// Known type name.
39    Type,
40}
41
42/// Compile-time type/category associated with a meta expression.
43#[derive(Debug, Clone, PartialEq, Eq)]
44pub enum MetaType {
45    /// Compile-time integer.
46    Int,
47    /// Compile-time boolean.
48    Bool,
49    /// Compile-time string.
50    String,
51    /// Compile-time type object.
52    Type,
53    /// Generated runtime expression with known runtime type.
54    Expr(TypeExpr),
55}
56
57/// Pre-expansion semantic issue discovered before meta expansion.
58#[derive(Debug, Clone, PartialEq, Eq)]
59pub enum SemanticIssue {
60    /// Runtime binding used directly in meta evaluation.
61    RuntimeBindingUnavailable {
62        /// Binding name referenced from meta code.
63        name: String,
64    },
65    /// Known non-meta symbol used as if it were a meta value.
66    KnownNameNotMetaValue {
67        /// Symbol name referenced from meta code.
68        name: String,
69        /// Resolved symbol category.
70        kind: ResolvedName,
71    },
72    /// Statically known meta-type mismatch.
73    MetaTypeMismatch {
74        /// Expected meta-visible type.
75        expected: String,
76        /// Best-known actual meta-visible type.
77        actual: String,
78    },
79}
80
81/// Staged semantic facts for a meta call expression.
82#[derive(Debug, Clone, PartialEq, Eq)]
83pub struct MetaCallInfo {
84    /// Callee name resolved from the call site.
85    pub callee: String,
86    /// Whether the callee is a known builtin meta function.
87    pub builtin: Option<BuiltinMetaCallKind>,
88    /// Staged argument kinds at the call site.
89    pub arg_types: Vec<Option<MetaType>>,
90    /// Expected argument kinds for known builtin calls.
91    pub expected_arg_kinds: Option<Vec<MetaArgKind>>,
92    /// Best-known staged result type for the call.
93    pub result_type: Option<MetaType>,
94}
95
96/// Known builtin meta call categories.
97#[derive(Debug, Clone, Copy, PartialEq, Eq)]
98pub enum BuiltinMetaCallKind {
99    /// `std::string::concat`.
100    StringConcat,
101    /// `std::string::starts_with`.
102    StringStartsWith,
103    /// `std::string::drop`.
104    StringDrop,
105    /// `std::string::take`.
106    StringTake,
107    /// `std::tuple::is_empty`.
108    TupleIsEmpty,
109    /// `std::tuple::head`.
110    TupleHead,
111    /// `std::tuple::tail`.
112    TupleTail,
113    /// `std::tuple::type_head`.
114    TupleTypeHead,
115    /// `std::tuple::type_tail`.
116    TupleTypeTail,
117}
118
119/// Broad meta-value kind used for staged builtin argument expectations.
120#[derive(Debug, Clone, Copy, PartialEq, Eq)]
121pub enum MetaArgKind {
122    /// Integer meta value.
123    Int,
124    /// Boolean meta value.
125    Bool,
126    /// String meta value.
127    String,
128    /// Type meta value.
129    Type,
130    /// Typed expression meta value.
131    Expr,
132}
133
134/// Broad pattern category used by staged meta-match facts.
135#[derive(Debug, Clone, Copy, PartialEq, Eq)]
136pub enum MetaPatternKind {
137    /// Wildcard pattern.
138    Wildcard,
139    /// Integer literal pattern.
140    Int,
141    /// Boolean literal pattern.
142    Bool,
143    /// String literal pattern.
144    String,
145    /// Quoted code pattern.
146    Quote,
147    /// Quoted type pattern.
148    Type,
149}
150
151/// Staged semantic facts for a meta match statement.
152#[derive(Debug, Clone, PartialEq, Eq)]
153pub struct MetaMatchInfo {
154    /// Best-known type of the matched value.
155    pub matched_type: Option<MetaType>,
156    /// Best-known converged type across all match arms.
157    pub converged_arm_type: Option<MetaType>,
158    /// Broad categories of the arm patterns.
159    pub pattern_kinds: Vec<MetaPatternKind>,
160}
161
162/// Pre-expansion semantic information keyed by stable frontend node ids.
163#[derive(Debug, Clone)]
164pub struct StagedSemanticInfo {
165    /// Deterministic frontend node index for the source program.
166    pub nodes: FrontendNodeIndex,
167    /// Runtime bindings visible at each `expr { ... }` builder site.
168    pub builder_envs: SecondaryMap<MetaExprId, Option<Vec<BuilderBindingInfo>>>,
169    /// Name-resolution result for runtime expressions.
170    pub runtime_name_resolution: SecondaryMap<ExprId, Option<ResolvedName>>,
171    /// Name-resolution result for meta expressions.
172    pub meta_name_resolution: SecondaryMap<MetaExprId, Option<ResolvedName>>,
173    /// Best-known pre-expansion runtime expression type.
174    pub runtime_expr_types: SecondaryMap<ExprId, Option<TypeExpr>>,
175    /// Best-known pre-expansion meta expression kind.
176    pub meta_expr_types: SecondaryMap<MetaExprId, Option<MetaType>>,
177    /// Known semantic facts for meta call expressions.
178    pub meta_call_info: SecondaryMap<MetaExprId, Option<MetaCallInfo>>,
179    /// Known semantic facts for meta match statements.
180    pub meta_match_info: SecondaryMap<MetaStmtId, Option<MetaMatchInfo>>,
181    /// Pre-expansion semantic issues found during analysis.
182    pub issues: Vec<(MetaExprId, SemanticIssue)>,
183    /// Obvious return-type issues for meta functions.
184    pub meta_function_issues: BTreeMap<String, SemanticIssue>,
185}
186
187/// Analyze a parsed frontend program and collect reusable pre-expansion metadata.
188pub fn analyze_program(program: &Program) -> StagedSemanticInfo {
189    let nodes = FrontendNodeIndex::build(program);
190    let mut analyzer = SemanticAnalyzer {
191        runtime_expr_ids: nodes.expressions.keys().collect(),
192        meta_expr_ids: nodes.meta_expressions.keys().collect(),
193        builder_ids: nodes
194            .meta_expressions
195            .iter()
196            .filter_map(|(id, kind)| {
197                matches!(kind, ruka_frontend::MetaExprKind::BuildExpr).then_some(id)
198            })
199            .collect(),
200        meta_stmt_ids: nodes.meta_statements.keys().collect(),
201        runtime_function_names: program
202            .functions
203            .iter()
204            .map(|function| function.name.clone())
205            .collect(),
206        meta_function_signatures: program
207            .meta_functions
208            .iter()
209            .map(|function| {
210                (
211                    function.name.clone(),
212                    function
213                        .params
214                        .iter()
215                        .map(|param| param.ty.clone())
216                        .collect(),
217                )
218            })
219            .collect(),
220        meta_function_return_types: program
221            .meta_functions
222            .iter()
223            .map(|function| (function.name.clone(), function.return_type.clone()))
224            .collect(),
225        meta_function_inferred_returns: BTreeMap::new(),
226        meta_function_names: program
227            .meta_functions
228            .iter()
229            .map(|function| function.name.clone())
230            .collect(),
231        struct_names: program
232            .structs
233            .iter()
234            .map(|decl| decl.name.clone())
235            .collect(),
236        extern_module_names: program
237            .extern_modules
238            .iter()
239            .map(|module| module.name.clone())
240            .collect(),
241        next_runtime_expr: 0,
242        next_meta_expr: 0,
243        next_meta_stmt: 0,
244        next_builder: 0,
245        builder_envs: SecondaryMap::new(),
246        runtime_name_resolution: SecondaryMap::new(),
247        meta_name_resolution: SecondaryMap::new(),
248        runtime_expr_types: SecondaryMap::new(),
249        meta_expr_types: SecondaryMap::new(),
250        meta_call_info: SecondaryMap::new(),
251        meta_match_info: SecondaryMap::new(),
252        issues: Vec::new(),
253        meta_function_issues: BTreeMap::new(),
254    };
255    analyzer.visit_program(program);
256    StagedSemanticInfo {
257        nodes,
258        builder_envs: analyzer.builder_envs,
259        runtime_name_resolution: analyzer.runtime_name_resolution,
260        meta_name_resolution: analyzer.meta_name_resolution,
261        runtime_expr_types: analyzer.runtime_expr_types,
262        meta_expr_types: analyzer.meta_expr_types,
263        meta_call_info: analyzer.meta_call_info,
264        meta_match_info: analyzer.meta_match_info,
265        issues: analyzer.issues,
266        meta_function_issues: analyzer.meta_function_issues,
267    }
268}
269
270struct SemanticAnalyzer {
271    runtime_expr_ids: Vec<ExprId>,
272    meta_expr_ids: Vec<MetaExprId>,
273    meta_stmt_ids: Vec<MetaStmtId>,
274    builder_ids: Vec<MetaExprId>,
275    runtime_function_names: Vec<String>,
276    meta_function_signatures: BTreeMap<String, Vec<TypeExpr>>,
277    meta_function_return_types: BTreeMap<String, TypeExpr>,
278    meta_function_inferred_returns: BTreeMap<String, MetaType>,
279    meta_function_names: Vec<String>,
280    struct_names: Vec<String>,
281    extern_module_names: Vec<String>,
282    next_runtime_expr: usize,
283    next_meta_expr: usize,
284    next_meta_stmt: usize,
285    next_builder: usize,
286    builder_envs: SecondaryMap<MetaExprId, Option<Vec<BuilderBindingInfo>>>,
287    runtime_name_resolution: SecondaryMap<ExprId, Option<ResolvedName>>,
288    meta_name_resolution: SecondaryMap<MetaExprId, Option<ResolvedName>>,
289    runtime_expr_types: SecondaryMap<ExprId, Option<TypeExpr>>,
290    meta_expr_types: SecondaryMap<MetaExprId, Option<MetaType>>,
291    meta_call_info: SecondaryMap<MetaExprId, Option<MetaCallInfo>>,
292    meta_match_info: SecondaryMap<MetaStmtId, Option<MetaMatchInfo>>,
293    issues: Vec<(MetaExprId, SemanticIssue)>,
294    meta_function_issues: BTreeMap<String, SemanticIssue>,
295}
296
297impl SemanticAnalyzer {
298    fn visit_program(&mut self, program: &Program) {
299        self.infer_meta_function_returns(&program.meta_functions);
300        for function in &program.functions {
301            let mut runtime_env = function
302                .params
303                .iter()
304                .map(|param| (param.name.clone(), Some(param.ty.ty.clone())))
305                .collect::<Vec<_>>();
306            let mut meta_env = Vec::new();
307            self.visit_block(&function.body, &mut runtime_env, &mut meta_env);
308        }
309        for function in &program.meta_functions {
310            let mut runtime_env = Vec::new();
311            let mut meta_env = function
312                .params
313                .iter()
314                .map(|param| {
315                    (
316                        param.name.clone(),
317                        infer_meta_type_from_type_expr(&param.ty),
318                    )
319                })
320                .collect::<Vec<_>>();
321            self.visit_meta_block(&function.body, &mut runtime_env, &mut meta_env);
322        }
323    }
324
325    fn infer_meta_function_returns(&mut self, functions: &[ruka_frontend::MetaFunctionDecl]) {
326        loop {
327            let mut changed = false;
328            for function in functions {
329                let mut meta_env = function
330                    .params
331                    .iter()
332                    .map(|param| {
333                        (
334                            param.name.clone(),
335                            infer_meta_type_from_type_expr(&param.ty),
336                        )
337                    })
338                    .collect::<Vec<_>>();
339                let Some(actual_return) =
340                    self.infer_meta_block_result_type(&function.body, &mut meta_env)
341                else {
342                    continue;
343                };
344                let prev = self
345                    .meta_function_inferred_returns
346                    .insert(function.name.clone(), actual_return.clone());
347                if prev.as_ref() != Some(&actual_return) {
348                    changed = true;
349                }
350            }
351            if !changed {
352                break;
353            }
354        }
355
356        for function in functions {
357            let Some(actual_return) = self.meta_function_inferred_returns.get(&function.name)
358            else {
359                continue;
360            };
361            if meta_value_fits_type(&function.return_type, actual_return) {
362                continue;
363            }
364            self.meta_function_issues.insert(
365                function.name.clone(),
366                SemanticIssue::MetaTypeMismatch {
367                    expected: format_type_expr(&function.return_type),
368                    actual: format_meta_type(actual_return),
369                },
370            );
371        }
372    }
373
374    fn infer_meta_block_result_type(
375        &self,
376        block: &ruka_frontend::MetaBlock,
377        meta_env: &mut Vec<(String, Option<MetaType>)>,
378    ) -> Option<MetaType> {
379        let mut last_value = None;
380        for stmt in &block.statements {
381            match stmt {
382                ruka_frontend::MetaStmt::Let { name, value } => {
383                    meta_env.push((
384                        name.clone(),
385                        self.infer_meta_expr_type_with_env(value, meta_env),
386                    ));
387                }
388                ruka_frontend::MetaStmt::Expr { expr } => {
389                    if let Some(value) = self.infer_meta_expr_type_with_env(expr, meta_env) {
390                        last_value = Some(value);
391                    }
392                }
393                ruka_frontend::MetaStmt::Match { arms, .. } => {
394                    let arm_types = arms
395                        .iter()
396                        .map(|arm| self.infer_meta_expr_type_with_env(&arm.result, meta_env))
397                        .collect::<Vec<_>>();
398                    last_value = converge_meta_types(&arm_types).or(last_value);
399                }
400                ruka_frontend::MetaStmt::Meta { body } => {
401                    if let Some(value) = self.infer_meta_block_result_type(body, meta_env) {
402                        last_value = Some(value);
403                    }
404                }
405            }
406        }
407        last_value
408    }
409
410    fn visit_block(
411        &mut self,
412        block: &ruka_frontend::Block,
413        runtime_env: &mut Vec<(String, Option<TypeExpr>)>,
414        meta_env: &mut Vec<(String, Option<MetaType>)>,
415    ) {
416        for stmt in &block.statements {
417            self.visit_stmt(stmt, runtime_env, meta_env);
418        }
419    }
420
421    fn visit_stmt(
422        &mut self,
423        stmt: &Stmt,
424        runtime_env: &mut Vec<(String, Option<TypeExpr>)>,
425        meta_env: &mut Vec<(String, Option<MetaType>)>,
426    ) {
427        match stmt {
428            Stmt::Let { name, value, .. } => {
429                self.visit_expr(value, runtime_env, meta_env);
430                runtime_env.push((name.clone(), infer_runtime_expr_type(value)));
431            }
432            Stmt::Assign { value, .. } => self.visit_expr(value, runtime_env, meta_env),
433            Stmt::If {
434                condition,
435                then_block,
436                else_block,
437                ..
438            } => {
439                self.visit_expr(condition, runtime_env, meta_env);
440                let mut then_env = runtime_env.clone();
441                let mut then_meta_env = meta_env.clone();
442                self.visit_block(then_block, &mut then_env, &mut then_meta_env);
443                if let Some(block) = else_block {
444                    let mut else_env = runtime_env.clone();
445                    let mut else_meta_env = meta_env.clone();
446                    self.visit_block(block, &mut else_env, &mut else_meta_env);
447                }
448            }
449            Stmt::For {
450                iterable,
451                body,
452                binding,
453            } => {
454                self.visit_expr(iterable, runtime_env, meta_env);
455                let mut loop_env = runtime_env.clone();
456                let mut loop_meta_env = meta_env.clone();
457                loop_env.push((binding.name.clone(), None));
458                self.visit_block(body, &mut loop_env, &mut loop_meta_env);
459            }
460            Stmt::While { condition, body } => {
461                self.visit_expr(condition, runtime_env, meta_env);
462                let mut loop_env = runtime_env.clone();
463                let mut loop_meta_env = meta_env.clone();
464                self.visit_block(body, &mut loop_env, &mut loop_meta_env);
465            }
466            Stmt::Match { value, arms } => {
467                self.visit_expr(value, runtime_env, meta_env);
468                for arm in arms {
469                    let mut arm_env = runtime_env.clone();
470                    let mut arm_meta_env = meta_env.clone();
471                    for binder in match &arm.pattern {
472                        ruka_frontend::MatchPattern::Wildcard => Vec::new(),
473                        ruka_frontend::MatchPattern::Variant { binders, .. } => {
474                            binders.iter().filter_map(|binder| binder.clone()).collect()
475                        }
476                    } {
477                        arm_env.push((binder.name, None));
478                    }
479                    self.visit_block(&arm.body, &mut arm_env, &mut arm_meta_env);
480                }
481            }
482            Stmt::Meta { body } => self.visit_meta_block(body, runtime_env, meta_env),
483            Stmt::Expr { expr, .. } => self.visit_expr(expr, runtime_env, meta_env),
484        }
485    }
486
487    fn visit_expr(
488        &mut self,
489        expr: &Expr,
490        runtime_env: &mut Vec<(String, Option<TypeExpr>)>,
491        meta_env: &mut Vec<(String, Option<MetaType>)>,
492    ) {
493        let id = self.runtime_expr_ids[self.next_runtime_expr];
494        self.next_runtime_expr += 1;
495        self.runtime_expr_types[id] = infer_runtime_expr_type(expr);
496        self.runtime_name_resolution[id] = resolve_runtime_name(
497            expr,
498            runtime_env,
499            &self.runtime_function_names,
500            &self.struct_names,
501            &self.extern_module_names,
502        );
503
504        match expr {
505            Expr::Array { items, .. } | Expr::Tuple { items, .. } => {
506                for item in items {
507                    self.visit_expr(item, runtime_env, meta_env);
508                }
509            }
510            Expr::StructLit { fields, .. } => {
511                for field in fields {
512                    self.visit_expr(&field.value, runtime_env, meta_env);
513                }
514            }
515            Expr::Call { callee, args, .. } => {
516                self.visit_expr(callee, runtime_env, meta_env);
517                for arg in args {
518                    match arg {
519                        ruka_frontend::CallArg::Expr(expr)
520                        | ruka_frontend::CallArg::Spread(expr) => {
521                            self.visit_expr(expr, runtime_env, meta_env)
522                        }
523                        ruka_frontend::CallArg::Type(_) => {}
524                    }
525                }
526            }
527            Expr::IntrinsicCall { args, .. } => {
528                for arg in args {
529                    match arg {
530                        ruka_frontend::CallArg::Expr(expr)
531                        | ruka_frontend::CallArg::Spread(expr) => {
532                            self.visit_expr(expr, runtime_env, meta_env)
533                        }
534                        ruka_frontend::CallArg::Type(_) => {}
535                    }
536                }
537            }
538            Expr::Field { base, .. } => self.visit_expr(base, runtime_env, meta_env),
539            Expr::Index { base, index } => {
540                self.visit_expr(base, runtime_env, meta_env);
541                self.visit_expr(index, runtime_env, meta_env);
542            }
543            Expr::SliceRange { base, start, end } => {
544                self.visit_expr(base, runtime_env, meta_env);
545                if let Some(start) = start {
546                    self.visit_expr(start, runtime_env, meta_env);
547                }
548                if let Some(end) = end {
549                    self.visit_expr(end, runtime_env, meta_env);
550                }
551            }
552            Expr::Prefix { value, .. } => self.visit_expr(value, runtime_env, meta_env),
553            Expr::Binary { lhs, rhs, .. } | Expr::Relational { lhs, rhs, .. } => {
554                self.visit_expr(lhs, runtime_env, meta_env);
555                self.visit_expr(rhs, runtime_env, meta_env);
556            }
557            Expr::Block(block) => {
558                let mut block_env = runtime_env.clone();
559                let mut block_meta_env = meta_env.clone();
560                self.visit_block(block, &mut block_env, &mut block_meta_env);
561            }
562            Expr::Splice(meta_expr) => self.visit_meta_expr(meta_expr, runtime_env, meta_env),
563            Expr::Ident { .. }
564            | Expr::Int(_)
565            | Expr::Number(_)
566            | Expr::String(_)
567            | Expr::Bool(_) => {}
568        }
569    }
570
571    fn visit_meta_block(
572        &mut self,
573        block: &ruka_frontend::MetaBlock,
574        runtime_env: &mut Vec<(String, Option<TypeExpr>)>,
575        meta_env: &mut Vec<(String, Option<MetaType>)>,
576    ) {
577        for stmt in &block.statements {
578            self.visit_meta_stmt(stmt, runtime_env, meta_env);
579        }
580    }
581
582    fn visit_meta_stmt(
583        &mut self,
584        stmt: &ruka_frontend::MetaStmt,
585        runtime_env: &mut Vec<(String, Option<TypeExpr>)>,
586        meta_env: &mut Vec<(String, Option<MetaType>)>,
587    ) {
588        let stmt_id = self.meta_stmt_ids[self.next_meta_stmt];
589        self.next_meta_stmt += 1;
590        match stmt {
591            ruka_frontend::MetaStmt::Let { name, value } => {
592                self.meta_match_info[stmt_id] = None;
593                self.visit_meta_expr(value, runtime_env, meta_env);
594                meta_env.push((
595                    name.clone(),
596                    self.infer_meta_expr_type_with_env(value, meta_env),
597                ));
598            }
599            ruka_frontend::MetaStmt::Match { value, arms } => {
600                let matched_type = self.infer_meta_expr_type_with_env(value, meta_env);
601                let arm_types = arms
602                    .iter()
603                    .map(|arm| self.infer_meta_expr_type_with_env(&arm.result, meta_env))
604                    .collect::<Vec<_>>();
605                self.meta_match_info[stmt_id] = Some(MetaMatchInfo {
606                    matched_type,
607                    converged_arm_type: converge_meta_types(&arm_types),
608                    pattern_kinds: arms
609                        .iter()
610                        .map(|arm| meta_pattern_kind(&arm.pattern))
611                        .collect(),
612                });
613                self.visit_meta_expr(value, runtime_env, meta_env);
614                for arm in arms {
615                    self.visit_meta_expr(&arm.result, runtime_env, meta_env);
616                }
617            }
618            ruka_frontend::MetaStmt::Meta { body } => {
619                self.meta_match_info[stmt_id] = None;
620                self.visit_meta_block(body, runtime_env, meta_env)
621            }
622            ruka_frontend::MetaStmt::Expr { expr } => {
623                self.meta_match_info[stmt_id] = None;
624                self.visit_meta_expr(expr, runtime_env, meta_env)
625            }
626        }
627    }
628
629    fn visit_meta_expr(
630        &mut self,
631        expr: &MetaExpr,
632        runtime_env: &[(String, Option<TypeExpr>)],
633        meta_env: &[(String, Option<MetaType>)],
634    ) {
635        let id = self.meta_expr_ids[self.next_meta_expr];
636        self.next_meta_expr += 1;
637        self.meta_name_resolution[id] = resolve_meta_name(
638            expr,
639            meta_env,
640            &self.runtime_function_names,
641            &self.meta_function_names,
642            &self.struct_names,
643            &self.extern_module_names,
644        );
645        self.meta_expr_types[id] = self.infer_meta_expr_type_with_env(expr, meta_env);
646        self.meta_call_info[id] = meta_call_info(expr, self.meta_expr_types[id].clone(), meta_env);
647        let resolved_name = self.meta_name_resolution[id].clone();
648        self.record_meta_issue(id, expr, runtime_env, resolved_name.as_ref());
649
650        match expr {
651            MetaExpr::Call { args, .. } => {
652                self.record_meta_call_issues(id, expr, meta_env);
653                for arg in args {
654                    self.visit_meta_expr(arg, runtime_env, meta_env);
655                }
656            }
657            MetaExpr::BuildExpr(runtime_expr) => {
658                let builder_id = self.builder_ids[self.next_builder];
659                self.next_builder += 1;
660                self.builder_envs[builder_id] = Some(
661                    runtime_env
662                        .iter()
663                        .map(|(name, ty)| BuilderBindingInfo {
664                            name: name.clone(),
665                            ty: ty.clone(),
666                        })
667                        .collect(),
668                );
669                let mut nested_env = runtime_env.to_vec();
670                let mut nested_meta_env = meta_env.to_vec();
671                self.visit_expr(runtime_expr, &mut nested_env, &mut nested_meta_env);
672            }
673            MetaExpr::Quote(quoted) => match quoted.as_ref() {
674                ruka_frontend::QuotedCode::Expr(expr) => {
675                    let mut nested_env = runtime_env.to_vec();
676                    let mut nested_meta_env = meta_env.to_vec();
677                    self.visit_expr(expr, &mut nested_env, &mut nested_meta_env);
678                }
679                ruka_frontend::QuotedCode::Type(_) => {}
680            },
681            MetaExpr::Splice(expr) => self.visit_meta_expr(expr, runtime_env, meta_env),
682            MetaExpr::Ident(_)
683            | MetaExpr::Int(_)
684            | MetaExpr::Bool(_)
685            | MetaExpr::String(_)
686            | MetaExpr::Type(_) => {}
687        }
688    }
689
690    fn record_meta_issue(
691        &mut self,
692        id: MetaExprId,
693        expr: &MetaExpr,
694        runtime_env: &[(String, Option<TypeExpr>)],
695        resolved_name: Option<&ResolvedName>,
696    ) {
697        let MetaExpr::Ident(name) = expr else {
698            return;
699        };
700        if runtime_env.iter().rev().any(|(binding, _)| binding == name) {
701            self.issues.push((
702                id,
703                SemanticIssue::RuntimeBindingUnavailable { name: name.clone() },
704            ));
705            return;
706        }
707        match resolved_name {
708            Some(ResolvedName::RuntimeFunction)
709            | Some(ResolvedName::MetaFunction)
710            | Some(ResolvedName::Struct)
711            | Some(ResolvedName::ExternModule)
712            | Some(ResolvedName::Type) => {
713                self.issues.push((
714                    id,
715                    SemanticIssue::KnownNameNotMetaValue {
716                        name: name.clone(),
717                        kind: resolved_name.cloned().expect("resolved name should exist"),
718                    },
719                ));
720            }
721            Some(ResolvedName::RuntimeLocal) | Some(ResolvedName::MetaLocal) | None => {}
722        }
723    }
724
725    fn record_meta_call_issues(
726        &mut self,
727        id: MetaExprId,
728        expr: &MetaExpr,
729        meta_env: &[(String, Option<MetaType>)],
730    ) {
731        let MetaExpr::Call { callee, args } = expr else {
732            return;
733        };
734        let Some(param_types) = self.meta_function_signatures.get(callee) else {
735            return;
736        };
737        for (arg, expected_ty) in args.iter().zip(param_types.iter()) {
738            let Some(actual_ty) = self.infer_meta_expr_type_with_env(arg, meta_env) else {
739                continue;
740            };
741            if meta_value_fits_type(expected_ty, &actual_ty) {
742                continue;
743            }
744            self.issues.push((
745                id,
746                SemanticIssue::MetaTypeMismatch {
747                    expected: format_type_expr(expected_ty),
748                    actual: format_meta_type(&actual_ty),
749                },
750            ));
751        }
752    }
753
754    fn infer_meta_expr_type_with_env(
755        &self,
756        expr: &MetaExpr,
757        meta_env: &[(String, Option<MetaType>)],
758    ) -> Option<MetaType> {
759        match expr {
760            MetaExpr::Ident(name) => meta_env
761                .iter()
762                .rev()
763                .find(|(binding, _)| binding == name)
764                .and_then(|(_, ty)| ty.clone()),
765            MetaExpr::Call { callee, .. } => self
766                .meta_function_inferred_returns
767                .get(callee)
768                .cloned()
769                .or_else(|| {
770                    self.meta_function_return_types
771                        .get(callee)
772                        .and_then(infer_meta_type_from_type_expr)
773                }),
774            _ => infer_meta_expr_type(expr),
775        }
776    }
777}
778
779fn resolve_runtime_name(
780    expr: &Expr,
781    runtime_env: &[(String, Option<TypeExpr>)],
782    runtime_functions: &[String],
783    structs: &[String],
784    extern_modules: &[String],
785) -> Option<ResolvedName> {
786    let Expr::Ident { name, .. } = expr else {
787        return None;
788    };
789    if runtime_env.iter().rev().any(|(binding, _)| binding == name) {
790        Some(ResolvedName::RuntimeLocal)
791    } else if runtime_functions.iter().any(|binding| binding == name) {
792        Some(ResolvedName::RuntimeFunction)
793    } else if structs.iter().any(|binding| binding == name) {
794        Some(ResolvedName::Struct)
795    } else if extern_modules.iter().any(|binding| binding == name) {
796        Some(ResolvedName::ExternModule)
797    } else if looks_like_type_name(name) {
798        Some(ResolvedName::Type)
799    } else {
800        None
801    }
802}
803
804fn resolve_meta_name(
805    expr: &MetaExpr,
806    meta_env: &[(String, Option<MetaType>)],
807    runtime_functions: &[String],
808    meta_functions: &[String],
809    structs: &[String],
810    extern_modules: &[String],
811) -> Option<ResolvedName> {
812    let MetaExpr::Ident(name) = expr else {
813        return None;
814    };
815    if meta_env.iter().rev().any(|(binding, _)| binding == name) {
816        Some(ResolvedName::MetaLocal)
817    } else if runtime_functions.iter().any(|binding| binding == name) {
818        Some(ResolvedName::RuntimeFunction)
819    } else if meta_functions.iter().any(|binding| binding == name) {
820        Some(ResolvedName::MetaFunction)
821    } else if structs.iter().any(|binding| binding == name) {
822        Some(ResolvedName::Struct)
823    } else if extern_modules.iter().any(|binding| binding == name) {
824        Some(ResolvedName::ExternModule)
825    } else if looks_like_type_name(name) {
826        Some(ResolvedName::Type)
827    } else {
828        None
829    }
830}
831
832fn looks_like_type_name(name: &str) -> bool {
833    name.chars()
834        .next()
835        .is_some_and(|ch| ch.is_ascii_uppercase())
836}
837
838fn infer_meta_type_from_type_expr(ty: &TypeExpr) -> Option<MetaType> {
839    match ty {
840        TypeExpr::Named(name) if name == "i64" => Some(MetaType::Int),
841        TypeExpr::Named(name) if name == "Bool" => Some(MetaType::Bool),
842        TypeExpr::Named(name) if name == "String" => Some(MetaType::String),
843        TypeExpr::TypeKind => Some(MetaType::Type),
844        TypeExpr::Apply { callee, args } if callee == "Expr" && args.len() == 1 => {
845            Some(MetaType::Expr(args[0].clone()))
846        }
847        _ => None,
848    }
849}
850
851fn infer_meta_expr_type(expr: &MetaExpr) -> Option<MetaType> {
852    match expr {
853        MetaExpr::Int(_) => Some(MetaType::Int),
854        MetaExpr::Bool(_) => Some(MetaType::Bool),
855        MetaExpr::String(_) => Some(MetaType::String),
856        MetaExpr::Type(_) => Some(MetaType::Type),
857        MetaExpr::BuildExpr(expr) => infer_runtime_expr_type(expr).map(MetaType::Expr),
858        MetaExpr::Ident(_) | MetaExpr::Call { .. } | MetaExpr::Quote(_) | MetaExpr::Splice(_) => {
859            None
860        }
861    }
862}
863
864fn meta_value_fits_type(expected_ty: &TypeExpr, actual_ty: &MetaType) -> bool {
865    match actual_ty {
866        MetaType::Int => {
867            matches!(expected_ty, TypeExpr::Named(name) if name == "i64" || name == "any")
868        }
869        MetaType::Bool => {
870            matches!(expected_ty, TypeExpr::Named(name) if name == "Bool" || name == "any")
871        }
872        MetaType::String => {
873            matches!(expected_ty, TypeExpr::Named(name) if name == "String" || name == "any")
874        }
875        MetaType::Type => match expected_ty {
876            TypeExpr::TypeKind => true,
877            TypeExpr::Named(name) if name == "any" => true,
878            _ => false,
879        },
880        MetaType::Expr(actual_expr_ty) => match expected_ty {
881            TypeExpr::Named(name) if name == "any" => true,
882            TypeExpr::Apply { callee, args } if callee == "Expr" && args.len() == 1 => {
883                meta_types_compatible(&args[0], actual_expr_ty)
884            }
885            _ => false,
886        },
887    }
888}
889
890fn format_meta_type(ty: &MetaType) -> String {
891    match ty {
892        MetaType::Int => "i64".to_owned(),
893        MetaType::Bool => "Bool".to_owned(),
894        MetaType::String => "String".to_owned(),
895        MetaType::Type => "type".to_owned(),
896        MetaType::Expr(item) => format!("Expr[{}]", format_type_expr(item)),
897    }
898}
899
900fn meta_call_info(
901    expr: &MetaExpr,
902    result_type: Option<MetaType>,
903    meta_env: &[(String, Option<MetaType>)],
904) -> Option<MetaCallInfo> {
905    let MetaExpr::Call { callee, args } = expr else {
906        return None;
907    };
908    let builtin_spec = builtin_spec_by_name(callee);
909    let builtin = builtin_spec.map(|spec| spec.kind);
910    Some(MetaCallInfo {
911        callee: callee.clone(),
912        builtin,
913        arg_types: args
914            .iter()
915            .map(|arg| infer_meta_expr_type_with_env_shallow(arg, meta_env))
916            .collect(),
917        expected_arg_kinds: builtin_spec.map(|spec| spec.arg_kinds.to_vec()),
918        result_type: result_type.or_else(|| {
919            builtin.and_then(|kind| {
920                builtin_result_type(
921                    kind,
922                    &args
923                        .iter()
924                        .map(|arg| infer_meta_expr_type_with_env_shallow(arg, meta_env))
925                        .collect::<Vec<_>>(),
926                )
927            })
928        }),
929    })
930}
931
932fn infer_meta_expr_type_with_env_shallow(
933    expr: &MetaExpr,
934    meta_env: &[(String, Option<MetaType>)],
935) -> Option<MetaType> {
936    match expr {
937        MetaExpr::Ident(name) => meta_env
938            .iter()
939            .rev()
940            .find(|(binding, _)| binding == name)
941            .and_then(|(_, ty)| ty.clone()),
942        _ => infer_meta_expr_type(expr),
943    }
944}
945
946fn meta_pattern_kind(pattern: &MetaMatchPattern) -> MetaPatternKind {
947    match pattern {
948        MetaMatchPattern::Wildcard => MetaPatternKind::Wildcard,
949        MetaMatchPattern::Int(_) => MetaPatternKind::Int,
950        MetaMatchPattern::Bool(_) => MetaPatternKind::Bool,
951        MetaMatchPattern::String(_) => MetaPatternKind::String,
952        MetaMatchPattern::Quote(_) => MetaPatternKind::Quote,
953        MetaMatchPattern::Type(_) => MetaPatternKind::Type,
954    }
955}
956
957fn converge_meta_types(types: &[Option<MetaType>]) -> Option<MetaType> {
958    let mut iter = types.iter();
959    let first = iter.next().and_then(Clone::clone)?;
960    if iter.all(|ty| ty.as_ref() == Some(&first)) {
961        return Some(first);
962    }
963    match first {
964        MetaType::Expr(first_ty) => {
965            if iter_all_expr_types(types, &first_ty) {
966                Some(MetaType::Expr(first_ty))
967            } else {
968                None
969            }
970        }
971        _ => None,
972    }
973}
974
975fn iter_all_expr_types(types: &[Option<MetaType>], first_ty: &TypeExpr) -> bool {
976    types.iter().all(|ty| match ty {
977        Some(MetaType::Expr(item_ty)) => meta_types_compatible(first_ty, item_ty),
978        _ => false,
979    })
980}
981
982fn infer_runtime_expr_type(expr: &Expr) -> Option<TypeExpr> {
983    match expr {
984        Expr::Int(_) => Some(TypeExpr::Named("i64".to_owned())),
985        Expr::Number(value) => {
986            let raw = value.raw.as_str();
987            let inferred = if raw.ends_with("u8") {
988                "u8"
989            } else if raw.ends_with("u16") {
990                "u16"
991            } else if raw.ends_with("u32") {
992                "u32"
993            } else if raw.ends_with("u64") {
994                "u64"
995            } else if raw.ends_with("i8") {
996                "i8"
997            } else if raw.ends_with("i16") {
998                "i16"
999            } else if raw.ends_with("i32") {
1000                "i32"
1001            } else if raw.ends_with("i64") {
1002                "i64"
1003            } else if raw.ends_with("f32") {
1004                "f32"
1005            } else if raw.ends_with("f64") || raw.contains('.') {
1006                "f64"
1007            } else {
1008                "i64"
1009            };
1010            Some(TypeExpr::Named(inferred.to_owned()))
1011        }
1012        Expr::Bool(_) => Some(TypeExpr::Named("Bool".to_owned())),
1013        Expr::String(_) => Some(TypeExpr::Named("String".to_owned())),
1014        Expr::Tuple { items, .. } => Some(TypeExpr::Tuple(
1015            items
1016                .iter()
1017                .map(infer_runtime_expr_type)
1018                .collect::<Option<Vec<_>>>()?,
1019        )),
1020        Expr::Array { items, .. } => {
1021            let item_ty = items.first().and_then(infer_runtime_expr_type)?;
1022            if items
1023                .iter()
1024                .all(|item| infer_runtime_expr_type(item).as_ref() == Some(&item_ty))
1025            {
1026                Some(TypeExpr::Array {
1027                    item: Box::new(item_ty),
1028                    len: items.len(),
1029                })
1030            } else {
1031                None
1032            }
1033        }
1034        Expr::Block(block) => block.statements.last().and_then(|stmt| match stmt {
1035            Stmt::Expr {
1036                expr,
1037                has_semi: false,
1038            } => infer_runtime_expr_type(expr),
1039            _ => None,
1040        }),
1041        Expr::Ident { .. }
1042        | Expr::StructLit { .. }
1043        | Expr::Call { .. }
1044        | Expr::IntrinsicCall { .. }
1045        | Expr::Field { .. }
1046        | Expr::Index { .. }
1047        | Expr::SliceRange { .. }
1048        | Expr::Prefix { .. }
1049        | Expr::Binary { .. }
1050        | Expr::Relational { .. }
1051        | Expr::Splice(_) => None,
1052    }
1053}
1054
1055#[cfg(test)]
1056mod tests;