1use 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#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct BuilderBindingInfo {
17 pub name: String,
19 pub ty: Option<TypeExpr>,
21}
22
23#[derive(Debug, Clone, PartialEq, Eq)]
25pub enum ResolvedName {
26 RuntimeLocal,
28 MetaLocal,
30 RuntimeFunction,
32 MetaFunction,
34 Struct,
36 ExternModule,
38 Type,
40}
41
42#[derive(Debug, Clone, PartialEq, Eq)]
44pub enum MetaType {
45 Int,
47 Bool,
49 String,
51 Type,
53 Expr(TypeExpr),
55}
56
57#[derive(Debug, Clone, PartialEq, Eq)]
59pub enum SemanticIssue {
60 RuntimeBindingUnavailable {
62 name: String,
64 },
65 KnownNameNotMetaValue {
67 name: String,
69 kind: ResolvedName,
71 },
72 MetaTypeMismatch {
74 expected: String,
76 actual: String,
78 },
79}
80
81#[derive(Debug, Clone, PartialEq, Eq)]
83pub struct MetaCallInfo {
84 pub callee: String,
86 pub builtin: Option<BuiltinMetaCallKind>,
88 pub arg_types: Vec<Option<MetaType>>,
90 pub expected_arg_kinds: Option<Vec<MetaArgKind>>,
92 pub result_type: Option<MetaType>,
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq)]
98pub enum BuiltinMetaCallKind {
99 StringConcat,
101 StringStartsWith,
103 StringDrop,
105 StringTake,
107 TupleIsEmpty,
109 TupleHead,
111 TupleTail,
113 TupleTypeHead,
115 TupleTypeTail,
117}
118
119#[derive(Debug, Clone, Copy, PartialEq, Eq)]
121pub enum MetaArgKind {
122 Int,
124 Bool,
126 String,
128 Type,
130 Expr,
132}
133
134#[derive(Debug, Clone, Copy, PartialEq, Eq)]
136pub enum MetaPatternKind {
137 Wildcard,
139 Int,
141 Bool,
143 String,
145 Quote,
147 Type,
149}
150
151#[derive(Debug, Clone, PartialEq, Eq)]
153pub struct MetaMatchInfo {
154 pub matched_type: Option<MetaType>,
156 pub converged_arm_type: Option<MetaType>,
158 pub pattern_kinds: Vec<MetaPatternKind>,
160}
161
162#[derive(Debug, Clone)]
164pub struct StagedSemanticInfo {
165 pub nodes: FrontendNodeIndex,
167 pub builder_envs: SecondaryMap<MetaExprId, Option<Vec<BuilderBindingInfo>>>,
169 pub runtime_name_resolution: SecondaryMap<ExprId, Option<ResolvedName>>,
171 pub meta_name_resolution: SecondaryMap<MetaExprId, Option<ResolvedName>>,
173 pub runtime_expr_types: SecondaryMap<ExprId, Option<TypeExpr>>,
175 pub meta_expr_types: SecondaryMap<MetaExprId, Option<MetaType>>,
177 pub meta_call_info: SecondaryMap<MetaExprId, Option<MetaCallInfo>>,
179 pub meta_match_info: SecondaryMap<MetaStmtId, Option<MetaMatchInfo>>,
181 pub issues: Vec<(MetaExprId, SemanticIssue)>,
183 pub meta_function_issues: BTreeMap<String, SemanticIssue>,
185}
186
187pub 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(¶m.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(¶m.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;