1use std::collections::{HashMap, HashSet};
8
9use super::ast::*;
10
11pub struct TypeCheckResult {
17 pub errors: Vec<String>,
18}
19
20pub fn typecheck(contract: &ContractNode) -> TypeCheckResult {
22 let mut errors = Vec::new();
23 let mut checker = TypeChecker::new(contract, &mut errors);
24
25 checker.check_constructor();
26 for method in &contract.methods {
27 checker.check_method(method);
28 }
29
30 TypeCheckResult { errors }
31}
32
33type TType = String;
39
40const VOID: &str = "void";
41const BIGINT: &str = "bigint";
42const BOOLEAN: &str = "boolean";
43const BYTESTRING: &str = "ByteString";
44
45struct FuncSig {
50 params: Vec<&'static str>,
51 return_type: &'static str,
52}
53
54fn builtin_functions() -> HashMap<&'static str, FuncSig> {
55 let mut m = HashMap::new();
56
57 m.insert("sha256", FuncSig { params: vec!["ByteString"], return_type: "Sha256" });
58 m.insert("ripemd160", FuncSig { params: vec!["ByteString"], return_type: "Ripemd160" });
59 m.insert("hash160", FuncSig { params: vec!["ByteString"], return_type: "Ripemd160" });
60 m.insert("hash256", FuncSig { params: vec!["ByteString"], return_type: "Sha256" });
61 m.insert("checkSig", FuncSig { params: vec!["Sig", "PubKey"], return_type: "boolean" });
62 m.insert("checkMultiSig", FuncSig { params: vec!["Sig[]", "PubKey[]"], return_type: "boolean" });
63 m.insert("assert", FuncSig { params: vec!["boolean"], return_type: "void" });
64 m.insert("len", FuncSig { params: vec!["ByteString"], return_type: "bigint" });
65 m.insert("cat", FuncSig { params: vec!["ByteString", "ByteString"], return_type: "ByteString" });
66 m.insert("substr", FuncSig { params: vec!["ByteString", "bigint", "bigint"], return_type: "ByteString" });
67 m.insert("num2bin", FuncSig { params: vec!["bigint", "bigint"], return_type: "ByteString" });
68 m.insert("bin2num", FuncSig { params: vec!["ByteString"], return_type: "bigint" });
69 m.insert("checkPreimage", FuncSig { params: vec!["SigHashPreimage"], return_type: "boolean" });
70 m.insert("verifyRabinSig", FuncSig { params: vec!["ByteString", "RabinSig", "ByteString", "RabinPubKey"], return_type: "boolean" });
71 m.insert("verifyWOTS", FuncSig { params: vec!["ByteString", "ByteString", "ByteString"], return_type: "boolean" });
72 m.insert("verifySLHDSA_SHA2_128s", FuncSig { params: vec!["ByteString", "ByteString", "ByteString"], return_type: "boolean" });
73 m.insert("verifySLHDSA_SHA2_128f", FuncSig { params: vec!["ByteString", "ByteString", "ByteString"], return_type: "boolean" });
74 m.insert("verifySLHDSA_SHA2_192s", FuncSig { params: vec!["ByteString", "ByteString", "ByteString"], return_type: "boolean" });
75 m.insert("verifySLHDSA_SHA2_192f", FuncSig { params: vec!["ByteString", "ByteString", "ByteString"], return_type: "boolean" });
76 m.insert("verifySLHDSA_SHA2_256s", FuncSig { params: vec!["ByteString", "ByteString", "ByteString"], return_type: "boolean" });
77 m.insert("verifySLHDSA_SHA2_256f", FuncSig { params: vec!["ByteString", "ByteString", "ByteString"], return_type: "boolean" });
78 m.insert("ecAdd", FuncSig { params: vec!["Point", "Point"], return_type: "Point" });
79 m.insert("ecMul", FuncSig { params: vec!["Point", "bigint"], return_type: "Point" });
80 m.insert("ecMulGen", FuncSig { params: vec!["bigint"], return_type: "Point" });
81 m.insert("ecNegate", FuncSig { params: vec!["Point"], return_type: "Point" });
82 m.insert("ecOnCurve", FuncSig { params: vec!["Point"], return_type: "boolean" });
83 m.insert("ecModReduce", FuncSig { params: vec!["bigint", "bigint"], return_type: "bigint" });
84 m.insert("ecEncodeCompressed", FuncSig { params: vec!["Point"], return_type: "ByteString" });
85 m.insert("ecMakePoint", FuncSig { params: vec!["bigint", "bigint"], return_type: "Point" });
86 m.insert("ecPointX", FuncSig { params: vec!["Point"], return_type: "bigint" });
87 m.insert("ecPointY", FuncSig { params: vec!["Point"], return_type: "bigint" });
88 m.insert("abs", FuncSig { params: vec!["bigint"], return_type: "bigint" });
89 m.insert("min", FuncSig { params: vec!["bigint", "bigint"], return_type: "bigint" });
90 m.insert("max", FuncSig { params: vec!["bigint", "bigint"], return_type: "bigint" });
91 m.insert("within", FuncSig { params: vec!["bigint", "bigint", "bigint"], return_type: "boolean" });
92 m.insert("reverseBytes", FuncSig { params: vec!["ByteString"], return_type: "ByteString" });
93 m.insert("left", FuncSig { params: vec!["ByteString", "bigint"], return_type: "ByteString" });
94 m.insert("right", FuncSig { params: vec!["ByteString", "bigint"], return_type: "ByteString" });
95 m.insert("int2str", FuncSig { params: vec!["bigint", "bigint"], return_type: "ByteString" });
96 m.insert("toByteString", FuncSig { params: vec!["ByteString"], return_type: "ByteString" });
97 m.insert("exit", FuncSig { params: vec!["boolean"], return_type: "void" });
98 m.insert("pack", FuncSig { params: vec!["bigint"], return_type: "ByteString" });
99 m.insert("unpack", FuncSig { params: vec!["ByteString"], return_type: "bigint" });
100 m.insert("safediv", FuncSig { params: vec!["bigint", "bigint"], return_type: "bigint" });
101 m.insert("safemod", FuncSig { params: vec!["bigint", "bigint"], return_type: "bigint" });
102 m.insert("clamp", FuncSig { params: vec!["bigint", "bigint", "bigint"], return_type: "bigint" });
103 m.insert("sign", FuncSig { params: vec!["bigint"], return_type: "bigint" });
104 m.insert("pow", FuncSig { params: vec!["bigint", "bigint"], return_type: "bigint" });
105 m.insert("mulDiv", FuncSig { params: vec!["bigint", "bigint", "bigint"], return_type: "bigint" });
106 m.insert("percentOf", FuncSig { params: vec!["bigint", "bigint"], return_type: "bigint" });
107 m.insert("sqrt", FuncSig { params: vec!["bigint"], return_type: "bigint" });
108 m.insert("gcd", FuncSig { params: vec!["bigint", "bigint"], return_type: "bigint" });
109 m.insert("divmod", FuncSig { params: vec!["bigint", "bigint"], return_type: "bigint" });
110 m.insert("log2", FuncSig { params: vec!["bigint"], return_type: "bigint" });
111 m.insert("bool", FuncSig { params: vec!["bigint"], return_type: "boolean" });
112 m.insert("split", FuncSig { params: vec!["ByteString", "bigint"], return_type: "ByteString" });
113 m.insert("sha256Compress", FuncSig { params: vec!["ByteString", "ByteString"], return_type: "ByteString" });
114 m.insert("sha256Finalize", FuncSig { params: vec!["ByteString", "ByteString", "bigint"], return_type: "ByteString" });
115 m.insert("blake3Compress", FuncSig { params: vec!["ByteString", "ByteString"], return_type: "ByteString" });
116 m.insert("blake3Hash", FuncSig { params: vec!["ByteString"], return_type: "ByteString" });
117
118 m.insert("extractVersion", FuncSig { params: vec!["SigHashPreimage"], return_type: "bigint" });
120 m.insert("extractHashPrevouts", FuncSig { params: vec!["SigHashPreimage"], return_type: "Sha256" });
121 m.insert("extractHashSequence", FuncSig { params: vec!["SigHashPreimage"], return_type: "Sha256" });
122 m.insert("extractOutpoint", FuncSig { params: vec!["SigHashPreimage"], return_type: "ByteString" });
123 m.insert("extractInputIndex", FuncSig { params: vec!["SigHashPreimage"], return_type: "bigint" });
124 m.insert("extractScriptCode", FuncSig { params: vec!["SigHashPreimage"], return_type: "ByteString" });
125 m.insert("extractAmount", FuncSig { params: vec!["SigHashPreimage"], return_type: "bigint" });
126 m.insert("extractSequence", FuncSig { params: vec!["SigHashPreimage"], return_type: "bigint" });
127 m.insert("extractOutputHash", FuncSig { params: vec!["SigHashPreimage"], return_type: "Sha256" });
128 m.insert("extractOutputs", FuncSig { params: vec!["SigHashPreimage"], return_type: "Sha256" });
129 m.insert("extractLocktime", FuncSig { params: vec!["SigHashPreimage"], return_type: "bigint" });
130 m.insert("extractSigHashType", FuncSig { params: vec!["SigHashPreimage"], return_type: "bigint" });
131
132 m
133}
134
135fn is_bytestring_subtype(t: &str) -> bool {
141 matches!(
142 t,
143 "ByteString" | "PubKey" | "Sig" | "Sha256" | "Ripemd160" | "Addr" | "SigHashPreimage" | "Point"
144 )
145}
146
147fn is_bigint_subtype(t: &str) -> bool {
149 matches!(t, "bigint" | "RabinSig" | "RabinPubKey")
150}
151
152fn is_subtype(actual: &str, expected: &str) -> bool {
153 if actual == expected {
154 return true;
155 }
156
157 if expected == "ByteString" && is_bytestring_subtype(actual) {
159 return true;
160 }
161 if actual == "ByteString" && is_bytestring_subtype(expected) {
162 return true;
163 }
164
165 if is_bytestring_subtype(actual) && is_bytestring_subtype(expected) {
167 return true;
168 }
169
170 if expected == "bigint" && is_bigint_subtype(actual) {
172 return true;
173 }
174 if actual == "bigint" && is_bigint_subtype(expected) {
175 return true;
176 }
177
178 if is_bigint_subtype(actual) && is_bigint_subtype(expected) {
180 return true;
181 }
182
183 if expected.ends_with("[]") && actual.ends_with("[]") {
185 return is_subtype(
186 &actual[..actual.len() - 2],
187 &expected[..expected.len() - 2],
188 );
189 }
190
191 false
192}
193
194fn is_bigint_family(t: &str) -> bool {
195 is_bigint_subtype(t)
196}
197
198struct TypeEnv {
203 scopes: Vec<HashMap<String, TType>>,
204}
205
206impl TypeEnv {
207 fn new() -> Self {
208 TypeEnv {
209 scopes: vec![HashMap::new()],
210 }
211 }
212
213 fn push_scope(&mut self) {
214 self.scopes.push(HashMap::new());
215 }
216
217 fn pop_scope(&mut self) {
218 self.scopes.pop();
219 }
220
221 fn define(&mut self, name: &str, t: TType) {
222 if let Some(top) = self.scopes.last_mut() {
223 top.insert(name.to_string(), t);
224 }
225 }
226
227 fn lookup(&self, name: &str) -> Option<&TType> {
228 for scope in self.scopes.iter().rev() {
229 if let Some(t) = scope.get(name) {
230 return Some(t);
231 }
232 }
233 None
234 }
235}
236
237fn is_affine_type(t: &str) -> bool {
243 matches!(t, "Sig" | "SigHashPreimage")
244}
245
246fn consuming_param_indices(func_name: &str) -> Option<&'static [usize]> {
249 match func_name {
250 "checkSig" => Some(&[0]),
251 "checkMultiSig" => Some(&[0]),
252 "checkPreimage" => Some(&[0]),
253 _ => None,
254 }
255}
256
257struct TypeChecker<'a> {
258 contract: &'a ContractNode,
259 errors: &'a mut Vec<String>,
260 prop_types: HashMap<String, TType>,
261 method_sigs: HashMap<String, (Vec<TType>, TType)>,
262 builtins: HashMap<&'static str, FuncSig>,
263 consumed_values: HashSet<String>,
264}
265
266impl<'a> TypeChecker<'a> {
267 fn new(contract: &'a ContractNode, errors: &'a mut Vec<String>) -> Self {
268 let mut prop_types = HashMap::new();
269 for prop in &contract.properties {
270 prop_types.insert(prop.name.clone(), type_node_to_ttype(&prop.prop_type));
271 }
272
273 if contract.parent_class == "StatefulSmartContract" {
275 prop_types.insert("txPreimage".to_string(), "SigHashPreimage".to_string());
276 }
277
278 let mut method_sigs = HashMap::new();
279 for method in &contract.methods {
280 let params: Vec<TType> = method
281 .params
282 .iter()
283 .map(|p| type_node_to_ttype(&p.param_type))
284 .collect();
285 let return_type = if method.visibility == Visibility::Public {
286 VOID.to_string()
287 } else {
288 infer_method_return_type(method)
289 };
290 method_sigs.insert(method.name.clone(), (params, return_type));
291 }
292
293 TypeChecker {
294 contract,
295 errors,
296 prop_types,
297 method_sigs,
298 builtins: builtin_functions(),
299 consumed_values: HashSet::new(),
300 }
301 }
302
303 fn check_constructor(&mut self) {
304 let ctor = &self.contract.constructor;
305 let mut env = TypeEnv::new();
306
307 self.consumed_values.clear();
309
310 for param in &ctor.params {
312 env.define(¶m.name, type_node_to_ttype(¶m.param_type));
313 }
314
315 for prop in &self.contract.properties {
317 env.define(&prop.name, type_node_to_ttype(&prop.prop_type));
318 }
319
320 self.check_statements(&ctor.body, &mut env);
321 }
322
323 fn check_method(&mut self, method: &MethodNode) {
324 let mut env = TypeEnv::new();
325
326 self.consumed_values.clear();
328
329 for param in &method.params {
331 env.define(¶m.name, type_node_to_ttype(¶m.param_type));
332 }
333
334 self.check_statements(&method.body, &mut env);
335 }
336
337 fn check_statements(&mut self, stmts: &[Statement], env: &mut TypeEnv) {
338 for stmt in stmts {
339 self.check_statement(stmt, env);
340 }
341 }
342
343 fn check_statement(&mut self, stmt: &Statement, env: &mut TypeEnv) {
344 match stmt {
345 Statement::VariableDecl {
346 name,
347 var_type,
348 init,
349 ..
350 } => {
351 let init_type = self.infer_expr_type(init, env);
352 if let Some(declared) = var_type {
353 let declared_type = type_node_to_ttype(declared);
354 if !is_subtype(&init_type, &declared_type) {
355 self.errors.push(format!(
356 "Type '{}' is not assignable to type '{}'",
357 init_type, declared_type
358 ));
359 }
360 env.define(name, declared_type);
361 } else {
362 env.define(name, init_type);
363 }
364 }
365
366 Statement::Assignment { target, value, .. } => {
367 let target_type = self.infer_expr_type(target, env);
368 let value_type = self.infer_expr_type(value, env);
369 if !is_subtype(&value_type, &target_type) {
370 self.errors.push(format!(
371 "Type '{}' is not assignable to type '{}'",
372 value_type, target_type
373 ));
374 }
375 }
376
377 Statement::IfStatement {
378 condition,
379 then_branch,
380 else_branch,
381 ..
382 } => {
383 let cond_type = self.infer_expr_type(condition, env);
384 if cond_type != BOOLEAN {
385 self.errors.push(format!(
386 "If condition must be boolean, got '{}'",
387 cond_type
388 ));
389 }
390 env.push_scope();
391 self.check_statements(then_branch, env);
392 env.pop_scope();
393 if let Some(else_stmts) = else_branch {
394 env.push_scope();
395 self.check_statements(else_stmts, env);
396 env.pop_scope();
397 }
398 }
399
400 Statement::ForStatement {
401 init,
402 condition,
403 body,
404 ..
405 } => {
406 env.push_scope();
407 self.check_statement(init, env);
408 let cond_type = self.infer_expr_type(condition, env);
409 if cond_type != BOOLEAN {
410 self.errors.push(format!(
411 "For loop condition must be boolean, got '{}'",
412 cond_type
413 ));
414 }
415 self.check_statements(body, env);
416 env.pop_scope();
417 }
418
419 Statement::ExpressionStatement { expression, .. } => {
420 self.infer_expr_type(expression, env);
421 }
422
423 Statement::ReturnStatement { value, .. } => {
424 if let Some(v) = value {
425 self.infer_expr_type(v, env);
426 }
427 }
428 }
429 }
430
431 fn infer_expr_type(&mut self, expr: &Expression, env: &mut TypeEnv) -> TType {
433 match expr {
434 Expression::BigIntLiteral { .. } => BIGINT.to_string(),
435
436 Expression::BoolLiteral { .. } => BOOLEAN.to_string(),
437
438 Expression::ByteStringLiteral { .. } => BYTESTRING.to_string(),
439
440 Expression::Identifier { name } => {
441 if name == "this" {
442 return "<this>".to_string();
443 }
444 if name == "super" {
445 return "<super>".to_string();
446 }
447 if name == "true" || name == "false" {
448 return BOOLEAN.to_string();
449 }
450
451 if let Some(t) = env.lookup(name) {
452 return t.clone();
453 }
454
455 if self.builtins.contains_key(name.as_str()) {
457 return "<builtin>".to_string();
458 }
459
460 "<unknown>".to_string()
461 }
462
463 Expression::PropertyAccess { property } => {
464 if let Some(t) = self.prop_types.get(property) {
465 return t.clone();
466 }
467
468 self.errors.push(format!(
469 "Property '{}' does not exist on the contract",
470 property
471 ));
472 "<unknown>".to_string()
473 }
474
475 Expression::MemberExpr { object, property } => {
476 let obj_type = self.infer_expr_type(object, env);
477
478 if obj_type == "<this>" {
479 if let Some(t) = self.prop_types.get(property) {
481 return t.clone();
482 }
483 if self.method_sigs.contains_key(property) {
485 return "<method>".to_string();
486 }
487 if property == "getStateScript" {
489 return "<method>".to_string();
490 }
491
492 self.errors.push(format!(
493 "Property or method '{}' does not exist on the contract",
494 property
495 ));
496 return "<unknown>".to_string();
497 }
498
499 if let Expression::Identifier { name } = object.as_ref() {
501 if name == "SigHash" {
502 return BIGINT.to_string();
503 }
504 }
505
506 "<unknown>".to_string()
507 }
508
509 Expression::BinaryExpr { op, left, right } => {
510 self.check_binary_expr(op, left, right, env)
511 }
512
513 Expression::UnaryExpr { op, operand } => self.check_unary_expr(op, operand, env),
514
515 Expression::CallExpr { callee, args } => self.check_call_expr(callee, args, env),
516
517 Expression::TernaryExpr {
518 condition,
519 consequent,
520 alternate,
521 } => {
522 let cond_type = self.infer_expr_type(condition, env);
523 if cond_type != BOOLEAN {
524 self.errors.push(format!(
525 "Ternary condition must be boolean, got '{}'",
526 cond_type
527 ));
528 }
529 let cons_type = self.infer_expr_type(consequent, env);
530 let alt_type = self.infer_expr_type(alternate, env);
531
532 if cons_type != alt_type {
533 if is_subtype(&alt_type, &cons_type) {
534 return cons_type;
535 }
536 if is_subtype(&cons_type, &alt_type) {
537 return alt_type;
538 }
539 self.errors.push(format!(
540 "Ternary branches have incompatible types: '{}' and '{}'",
541 cons_type, alt_type
542 ));
543 }
544 cons_type
545 }
546
547 Expression::IndexAccess { object, index } => {
548 let obj_type = self.infer_expr_type(object, env);
549 let index_type = self.infer_expr_type(index, env);
550
551 if !is_bigint_family(&index_type) {
552 self.errors.push(format!(
553 "Array index must be bigint, got '{}'",
554 index_type
555 ));
556 }
557
558 if obj_type.ends_with("[]") {
559 return obj_type[..obj_type.len() - 2].to_string();
560 }
561
562 "<unknown>".to_string()
563 }
564
565 Expression::IncrementExpr { operand, .. }
566 | Expression::DecrementExpr { operand, .. } => {
567 let operand_type = self.infer_expr_type(operand, env);
568 if !is_bigint_family(&operand_type) {
569 let op_str = if matches!(expr, Expression::IncrementExpr { .. }) {
570 "++"
571 } else {
572 "--"
573 };
574 self.errors.push(format!(
575 "{} operator requires bigint, got '{}'",
576 op_str, operand_type
577 ));
578 }
579 BIGINT.to_string()
580 }
581 }
582 }
583
584 fn check_binary_expr(
585 &mut self,
586 op: &BinaryOp,
587 left: &Expression,
588 right: &Expression,
589 env: &mut TypeEnv,
590 ) -> TType {
591 let left_type = self.infer_expr_type(left, env);
592 let right_type = self.infer_expr_type(right, env);
593
594 match op {
595 BinaryOp::Add => {
597 if is_bytestring_subtype(&left_type) && is_bytestring_subtype(&right_type) {
598 return BYTESTRING.to_string();
599 }
600 if !is_bigint_family(&left_type) {
601 self.errors.push(format!(
602 "Left operand of '{}' must be bigint or ByteString, got '{}'",
603 op.as_str(),
604 left_type
605 ));
606 }
607 if !is_bigint_family(&right_type) {
608 self.errors.push(format!(
609 "Right operand of '{}' must be bigint or ByteString, got '{}'",
610 op.as_str(),
611 right_type
612 ));
613 }
614 BIGINT.to_string()
615 }
616
617 BinaryOp::Sub | BinaryOp::Mul | BinaryOp::Div | BinaryOp::Mod => {
619 if !is_bigint_family(&left_type) {
620 self.errors.push(format!(
621 "Left operand of '{}' must be bigint, got '{}'",
622 op.as_str(),
623 left_type
624 ));
625 }
626 if !is_bigint_family(&right_type) {
627 self.errors.push(format!(
628 "Right operand of '{}' must be bigint, got '{}'",
629 op.as_str(),
630 right_type
631 ));
632 }
633 BIGINT.to_string()
634 }
635
636 BinaryOp::Lt | BinaryOp::Le | BinaryOp::Gt | BinaryOp::Ge => {
638 if !is_bigint_family(&left_type) {
639 self.errors.push(format!(
640 "Left operand of '{}' must be bigint, got '{}'",
641 op.as_str(),
642 left_type
643 ));
644 }
645 if !is_bigint_family(&right_type) {
646 self.errors.push(format!(
647 "Right operand of '{}' must be bigint, got '{}'",
648 op.as_str(),
649 right_type
650 ));
651 }
652 BOOLEAN.to_string()
653 }
654
655 BinaryOp::StrictEq | BinaryOp::StrictNe => {
657 if !is_subtype(&left_type, &right_type)
658 && !is_subtype(&right_type, &left_type)
659 {
660 if left_type != "<unknown>" && right_type != "<unknown>" {
661 self.errors.push(format!(
662 "Cannot compare '{}' and '{}' with '{}'",
663 left_type,
664 right_type,
665 op.as_str()
666 ));
667 }
668 }
669 BOOLEAN.to_string()
670 }
671
672 BinaryOp::And | BinaryOp::Or => {
674 if left_type != BOOLEAN && left_type != "<unknown>" {
675 self.errors.push(format!(
676 "Left operand of '{}' must be boolean, got '{}'",
677 op.as_str(),
678 left_type
679 ));
680 }
681 if right_type != BOOLEAN && right_type != "<unknown>" {
682 self.errors.push(format!(
683 "Right operand of '{}' must be boolean, got '{}'",
684 op.as_str(),
685 right_type
686 ));
687 }
688 BOOLEAN.to_string()
689 }
690
691 BinaryOp::BitAnd | BinaryOp::BitOr | BinaryOp::BitXor => {
693 if is_bytestring_subtype(&left_type) && is_bytestring_subtype(&right_type) {
694 return BYTESTRING.to_string();
695 }
696 if !is_bigint_family(&left_type) {
697 self.errors.push(format!(
698 "Left operand of '{}' must be bigint or ByteString, got '{}'",
699 op.as_str(),
700 left_type
701 ));
702 }
703 if !is_bigint_family(&right_type) {
704 self.errors.push(format!(
705 "Right operand of '{}' must be bigint or ByteString, got '{}'",
706 op.as_str(),
707 right_type
708 ));
709 }
710 BIGINT.to_string()
711 }
712
713 BinaryOp::Shl | BinaryOp::Shr => {
715 if !is_bigint_family(&left_type) {
716 self.errors.push(format!(
717 "Left operand of '{}' must be bigint, got '{}'",
718 op.as_str(),
719 left_type
720 ));
721 }
722 if !is_bigint_family(&right_type) {
723 self.errors.push(format!(
724 "Right operand of '{}' must be bigint, got '{}'",
725 op.as_str(),
726 right_type
727 ));
728 }
729 BIGINT.to_string()
730 }
731 }
732 }
733
734 fn check_unary_expr(
735 &mut self,
736 op: &UnaryOp,
737 operand: &Expression,
738 env: &mut TypeEnv,
739 ) -> TType {
740 let operand_type = self.infer_expr_type(operand, env);
741
742 match op {
743 UnaryOp::Not => {
744 if operand_type != BOOLEAN && operand_type != "<unknown>" {
745 self.errors.push(format!(
746 "Operand of '!' must be boolean, got '{}'",
747 operand_type
748 ));
749 }
750 BOOLEAN.to_string()
751 }
752 UnaryOp::Neg => {
753 if !is_bigint_family(&operand_type) {
754 self.errors.push(format!(
755 "Operand of unary '-' must be bigint, got '{}'",
756 operand_type
757 ));
758 }
759 BIGINT.to_string()
760 }
761 UnaryOp::BitNot => {
762 if is_bytestring_subtype(&operand_type) {
763 return BYTESTRING.to_string();
764 }
765 if !is_bigint_family(&operand_type) {
766 self.errors.push(format!(
767 "Operand of '~' must be bigint or ByteString, got '{}'",
768 operand_type
769 ));
770 }
771 BIGINT.to_string()
772 }
773 }
774 }
775
776 fn check_call_expr(
777 &mut self,
778 callee: &Expression,
779 args: &[Expression],
780 env: &mut TypeEnv,
781 ) -> TType {
782 if let Expression::Identifier { name } = callee {
784 if name == "super" {
785 for arg in args {
786 self.infer_expr_type(arg, env);
787 }
788 return VOID.to_string();
789 }
790 }
791
792 if let Expression::Identifier { name } = callee {
794 if let Some(sig) = self.builtins.get(name.as_str()) {
795 let sig_params = sig.params.clone();
796 let sig_return_type = sig.return_type;
797 return self.check_call_args(name, &sig_params, sig_return_type, args, env);
798 }
799
800 if let Some((params, return_type)) = self.method_sigs.get(name).cloned() {
802 let param_strs: Vec<&str> = params.iter().map(|s| s.as_str()).collect();
803 return self.check_call_args(name, ¶m_strs, &return_type, args, env);
804 }
805
806 if env.lookup(name).is_some() {
808 for arg in args {
809 self.infer_expr_type(arg, env);
810 }
811 return "<unknown>".to_string();
812 }
813
814 self.errors.push(format!(
815 "unknown function '{}' — only Rúnar built-in functions and contract methods are allowed",
816 name
817 ));
818 for arg in args {
819 self.infer_expr_type(arg, env);
820 }
821 return "<unknown>".to_string();
822 }
823
824 if let Expression::PropertyAccess { property } = callee {
826 if property == "getStateScript" {
827 if !args.is_empty() {
828 self.errors
829 .push("getStateScript() takes no arguments".to_string());
830 }
831 return BYTESTRING.to_string();
832 }
833
834 if property == "addOutput" || property == "addRawOutput" {
835 for arg in args {
836 self.infer_expr_type(arg, env);
837 }
838 return VOID.to_string();
839 }
840
841 if let Some((params, return_type)) = self.method_sigs.get(property).cloned() {
843 let param_strs: Vec<&str> = params.iter().map(|s| s.as_str()).collect();
844 return self.check_call_args(property, ¶m_strs, &return_type, args, env);
845 }
846
847 self.errors.push(format!(
848 "unknown method 'self.{}' — only Rúnar built-in methods and contract methods are allowed",
849 property
850 ));
851 for arg in args {
852 self.infer_expr_type(arg, env);
853 }
854 return "<unknown>".to_string();
855 }
856
857 if let Expression::MemberExpr { object, property } = callee {
859 if property == "clone" {
861 return self.infer_expr_type(object, env);
862 }
863
864 let obj_type = self.infer_expr_type(object, env);
865
866 if obj_type == "<this>"
867 || matches!(object.as_ref(), Expression::Identifier { name } if name == "this")
868 {
869 if property == "getStateScript" {
870 return BYTESTRING.to_string();
871 }
872
873 if let Some((params, return_type)) = self.method_sigs.get(property).cloned() {
874 let param_strs: Vec<&str> = params.iter().map(|s| s.as_str()).collect();
875 return self.check_call_args(
876 property,
877 ¶m_strs,
878 &return_type,
879 args,
880 env,
881 );
882 }
883 }
884
885 let obj_name = match object.as_ref() {
887 Expression::Identifier { name } => name.clone(),
888 _ => "<expr>".to_string(),
889 };
890 self.errors.push(format!(
891 "unknown function '{}.{}' — only Rúnar built-in functions and contract methods are allowed",
892 obj_name, property
893 ));
894 for arg in args {
895 self.infer_expr_type(arg, env);
896 }
897 return "<unknown>".to_string();
898 }
899
900 self.errors.push(
902 "unsupported function call expression — only Rúnar built-in functions and contract methods are allowed".to_string()
903 );
904 self.infer_expr_type(callee, env);
905 for arg in args {
906 self.infer_expr_type(arg, env);
907 }
908 "<unknown>".to_string()
909 }
910
911 fn check_call_args(
912 &mut self,
913 func_name: &str,
914 sig_params: &[&str],
915 return_type: &str,
916 args: &[Expression],
917 env: &mut TypeEnv,
918 ) -> TType {
919 if func_name == "assert" {
921 if args.is_empty() || args.len() > 2 {
922 self.errors.push(format!(
923 "assert() expects 1 or 2 arguments, got {}",
924 args.len()
925 ));
926 }
927 if !args.is_empty() {
928 let cond_type = self.infer_expr_type(&args[0], env);
929 if cond_type != BOOLEAN && cond_type != "<unknown>" {
930 self.errors.push(format!(
931 "assert() condition must be boolean, got '{}'",
932 cond_type
933 ));
934 }
935 }
936 if args.len() >= 2 {
937 self.infer_expr_type(&args[1], env);
938 }
939 return return_type.to_string();
940 }
941
942 if func_name == "checkSig" {
946 if args.len() != 2 {
947 self.errors.push(format!(
948 "checkSig() expects 2 argument(s), got {}",
949 args.len()
950 ));
951 }
952 if args.len() >= 1 {
953 let arg0_type = self.infer_expr_type(&args[0], env);
954 if arg0_type != "Sig" && arg0_type != "<unknown>" {
956 self.errors.push(format!(
957 "Argument 1 of checkSig(): expected 'Sig', got '{}'",
958 arg0_type
959 ));
960 }
961 }
962 if args.len() >= 2 {
963 let arg1_type = self.infer_expr_type(&args[1], env);
964 if arg1_type != "PubKey" && arg1_type != "<unknown>" {
966 self.errors.push(format!(
967 "Argument 2 of checkSig(): expected 'PubKey', got '{}'",
968 arg1_type
969 ));
970 }
971 }
972 for i in 2..args.len() {
974 self.infer_expr_type(&args[i], env);
975 }
976 self.check_affine_consumption(func_name, args, env);
977 return return_type.to_string();
978 }
979
980 if func_name == "checkMultiSig" {
982 if args.len() != 2 {
983 self.errors.push(format!(
984 "checkMultiSig() expects 2 arguments, got {}",
985 args.len()
986 ));
987 }
988 for arg in args {
989 self.infer_expr_type(arg, env);
990 }
991 self.check_affine_consumption(func_name, args, env);
992 return return_type.to_string();
993 }
994
995 if args.len() != sig_params.len() {
997 self.errors.push(format!(
998 "{}() expects {} argument(s), got {}",
999 func_name,
1000 sig_params.len(),
1001 args.len()
1002 ));
1003 }
1004
1005 let count = args.len().min(sig_params.len());
1006 for i in 0..count {
1007 let arg_type = self.infer_expr_type(&args[i], env);
1008 let expected = sig_params[i];
1009
1010 if !is_subtype(&arg_type, expected) && arg_type != "<unknown>" {
1011 self.errors.push(format!(
1012 "Argument {} of {}(): expected '{}', got '{}'",
1013 i + 1,
1014 func_name,
1015 expected,
1016 arg_type
1017 ));
1018 }
1019 }
1020
1021 for i in count..args.len() {
1023 self.infer_expr_type(&args[i], env);
1024 }
1025
1026 self.check_affine_consumption(func_name, args, env);
1028
1029 return_type.to_string()
1030 }
1031
1032 fn check_affine_consumption(
1035 &mut self,
1036 func_name: &str,
1037 args: &[Expression],
1038 env: &mut TypeEnv,
1039 ) {
1040 let indices = match consuming_param_indices(func_name) {
1041 Some(indices) => indices,
1042 None => return,
1043 };
1044
1045 for ¶m_index in indices {
1046 if param_index >= args.len() {
1047 continue;
1048 }
1049
1050 let arg = &args[param_index];
1051 if let Expression::Identifier { name } = arg {
1052 if let Some(arg_type) = env.lookup(name) {
1053 let arg_type = arg_type.clone();
1054 if !is_affine_type(&arg_type) {
1055 continue;
1056 }
1057
1058 if self.consumed_values.contains(name) {
1059 self.errors.push(format!(
1060 "affine value '{}' has already been consumed",
1061 name
1062 ));
1063 } else {
1064 self.consumed_values.insert(name.clone());
1065 }
1066 }
1067 }
1068 }
1069 }
1070}
1071
1072fn infer_method_return_type(method: &MethodNode) -> TType {
1084 let return_types = collect_return_types(&method.body);
1085
1086 if return_types.is_empty() {
1087 return VOID.to_string();
1088 }
1089
1090 let first = &return_types[0];
1091 let all_same = return_types.iter().all(|t| t == first);
1092 if all_same {
1093 return first.clone();
1094 }
1095
1096 if return_types.iter().all(|t| is_bigint_subtype(t)) {
1098 return BIGINT.to_string();
1099 }
1100
1101 if return_types.iter().all(|t| is_bytestring_subtype(t)) {
1103 return BYTESTRING.to_string();
1104 }
1105
1106 if return_types.iter().all(|t| t == BOOLEAN) {
1108 return BOOLEAN.to_string();
1109 }
1110
1111 first.clone()
1113}
1114
1115fn collect_return_types(stmts: &[Statement]) -> Vec<TType> {
1117 let mut types = Vec::new();
1118 for stmt in stmts {
1119 match stmt {
1120 Statement::ReturnStatement { value, .. } => {
1121 if let Some(v) = value {
1122 types.push(infer_expr_type_static(v));
1123 }
1124 }
1125 Statement::IfStatement {
1126 then_branch,
1127 else_branch,
1128 ..
1129 } => {
1130 types.extend(collect_return_types(then_branch));
1131 if let Some(else_stmts) = else_branch {
1132 types.extend(collect_return_types(else_stmts));
1133 }
1134 }
1135 Statement::ForStatement { body, .. } => {
1136 types.extend(collect_return_types(body));
1137 }
1138 _ => {}
1139 }
1140 }
1141 types
1142}
1143
1144fn infer_expr_type_static(expr: &Expression) -> TType {
1148 match expr {
1149 Expression::BigIntLiteral { .. } => BIGINT.to_string(),
1150 Expression::BoolLiteral { .. } => BOOLEAN.to_string(),
1151 Expression::ByteStringLiteral { .. } => BYTESTRING.to_string(),
1152 Expression::Identifier { name } => {
1153 if name == "true" || name == "false" {
1154 BOOLEAN.to_string()
1155 } else {
1156 "<unknown>".to_string()
1157 }
1158 }
1159 Expression::BinaryExpr { op, .. } => match op {
1160 BinaryOp::Add
1161 | BinaryOp::Sub
1162 | BinaryOp::Mul
1163 | BinaryOp::Div
1164 | BinaryOp::Mod
1165 | BinaryOp::BitAnd
1166 | BinaryOp::BitOr
1167 | BinaryOp::BitXor
1168 | BinaryOp::Shl
1169 | BinaryOp::Shr => BIGINT.to_string(),
1170 _ => BOOLEAN.to_string(),
1171 },
1172 Expression::UnaryExpr { op, .. } => match op {
1173 UnaryOp::Not => BOOLEAN.to_string(),
1174 _ => BIGINT.to_string(),
1175 },
1176 Expression::CallExpr { callee, .. } => {
1177 let builtins = builtin_functions();
1178 if let Expression::Identifier { name } = callee.as_ref() {
1179 if let Some(sig) = builtins.get(name.as_str()) {
1180 return sig.return_type.to_string();
1181 }
1182 }
1183 if let Expression::PropertyAccess { property } = callee.as_ref() {
1184 if let Some(sig) = builtins.get(property.as_str()) {
1185 return sig.return_type.to_string();
1186 }
1187 }
1188 "<unknown>".to_string()
1189 }
1190 Expression::TernaryExpr {
1191 consequent,
1192 alternate,
1193 ..
1194 } => {
1195 let cons_type = infer_expr_type_static(consequent);
1196 if cons_type != "<unknown>" {
1197 cons_type
1198 } else {
1199 infer_expr_type_static(alternate)
1200 }
1201 }
1202 Expression::IncrementExpr { .. } | Expression::DecrementExpr { .. } => {
1203 BIGINT.to_string()
1204 }
1205 _ => "<unknown>".to_string(),
1206 }
1207}
1208
1209fn type_node_to_ttype(node: &TypeNode) -> TType {
1210 match node {
1211 TypeNode::Primitive(name) => name.as_str().to_string(),
1212 TypeNode::FixedArray { element, .. } => {
1213 format!("{}[]", type_node_to_ttype(element))
1214 }
1215 TypeNode::Custom(name) => name.clone(),
1216 }
1217}
1218
1219#[cfg(test)]
1224mod tests {
1225 use super::*;
1226 use crate::frontend::parser::parse_source;
1227 use crate::frontend::validator;
1228
1229 fn parse_and_validate(source: &str) -> ContractNode {
1231 let result = parse_source(source, Some("test.runar.ts"));
1232 assert!(
1233 result.errors.is_empty(),
1234 "parse errors: {:?}",
1235 result.errors
1236 );
1237 let contract = result.contract.expect("expected a contract from parse");
1238 let validation = validator::validate(&contract);
1239 assert!(
1240 validation.errors.is_empty(),
1241 "validation errors: {:?}",
1242 validation.errors
1243 );
1244 contract
1245 }
1246
1247 #[test]
1248 fn test_valid_p2pkh_passes_typecheck() {
1249 let source = r#"
1250import { SmartContract, Addr, PubKey, Sig } from 'runar-lang';
1251
1252class P2PKH extends SmartContract {
1253 readonly pubKeyHash: Addr;
1254
1255 constructor(pubKeyHash: Addr) {
1256 super(pubKeyHash);
1257 this.pubKeyHash = pubKeyHash;
1258 }
1259
1260 public unlock(sig: Sig, pubKey: PubKey) {
1261 assert(hash160(pubKey) === this.pubKeyHash);
1262 assert(checkSig(sig, pubKey));
1263 }
1264}
1265"#;
1266 let contract = parse_and_validate(source);
1267 let result = typecheck(&contract);
1268 assert!(
1269 result.errors.is_empty(),
1270 "expected no typecheck errors, got: {:?}",
1271 result.errors
1272 );
1273 }
1274
1275 #[test]
1276 fn test_unknown_function_call_produces_error() {
1277 let source = r#"
1278import { SmartContract } from 'runar-lang';
1279
1280class Bad extends SmartContract {
1281 readonly x: bigint;
1282
1283 constructor(x: bigint) {
1284 super(x);
1285 this.x = x;
1286 }
1287
1288 public check(v: bigint) {
1289 const y = Math.floor(v);
1290 assert(y === this.x);
1291 }
1292}
1293"#;
1294 let contract = parse_and_validate(source);
1295 let result = typecheck(&contract);
1296 assert!(
1297 !result.errors.is_empty(),
1298 "expected typecheck errors for unknown function Math.floor"
1299 );
1300 let has_unknown_error = result
1301 .errors
1302 .iter()
1303 .any(|e| e.to_lowercase().contains("unknown"));
1304 assert!(
1305 has_unknown_error,
1306 "expected error about unknown function, got: {:?}",
1307 result.errors
1308 );
1309 }
1310
1311 #[test]
1312 fn test_builtin_with_wrong_arg_count_produces_error() {
1313 let source = r#"
1314import { SmartContract, PubKey, Sig } from 'runar-lang';
1315
1316class Bad extends SmartContract {
1317 readonly x: bigint;
1318
1319 constructor(x: bigint) {
1320 super(x);
1321 this.x = x;
1322 }
1323
1324 public check(v: bigint) {
1325 assert(min(v));
1326 }
1327}
1328"#;
1329 let contract = parse_and_validate(source);
1330 let result = typecheck(&contract);
1331 assert!(
1332 !result.errors.is_empty(),
1333 "expected typecheck errors for wrong arg count"
1334 );
1335 let has_arg_count_error = result
1336 .errors
1337 .iter()
1338 .any(|e| e.contains("expects") && e.contains("argument"));
1339 assert!(
1340 has_arg_count_error,
1341 "expected error about wrong argument count, got: {:?}",
1342 result.errors
1343 );
1344 }
1345
1346 #[test]
1347 fn test_arithmetic_on_boolean_produces_error() {
1348 let source = r#"
1349import { SmartContract } from 'runar-lang';
1350
1351class Bad extends SmartContract {
1352 readonly x: bigint;
1353
1354 constructor(x: bigint) {
1355 super(x);
1356 this.x = x;
1357 }
1358
1359 public check(v: bigint, flag: boolean) {
1360 const sum = v + flag;
1361 assert(sum === this.x);
1362 }
1363}
1364"#;
1365 let contract = parse_and_validate(source);
1366 let result = typecheck(&contract);
1367 assert!(
1368 !result.errors.is_empty(),
1369 "expected typecheck errors for arithmetic on boolean"
1370 );
1371 let has_type_error = result
1372 .errors
1373 .iter()
1374 .any(|e| e.contains("bigint") || e.contains("boolean"));
1375 assert!(
1376 has_type_error,
1377 "expected type mismatch error, got: {:?}",
1378 result.errors
1379 );
1380 }
1381
1382 #[test]
1383 fn test_valid_stateful_contract_passes_typecheck() {
1384 let source = r#"
1385import { StatefulSmartContract } from 'runar-lang';
1386
1387class Counter extends StatefulSmartContract {
1388 count: bigint;
1389
1390 constructor(count: bigint) {
1391 super(count);
1392 this.count = count;
1393 }
1394
1395 public increment() {
1396 this.count++;
1397 }
1398}
1399"#;
1400 let contract = parse_and_validate(source);
1401 let result = typecheck(&contract);
1402 assert!(
1403 result.errors.is_empty(),
1404 "expected no typecheck errors for stateful contract, got: {:?}",
1405 result.errors
1406 );
1407 }
1408
1409 #[test]
1414 fn test_valid_arithmetic() {
1415 let source = r#"
1416import { SmartContract, assert } from 'runar-lang';
1417
1418class Arithmetic extends SmartContract {
1419 readonly target: bigint;
1420
1421 constructor(target: bigint) {
1422 super(target);
1423 this.target = target;
1424 }
1425
1426 public verify(a: bigint, b: bigint) {
1427 const sum: bigint = a + b;
1428 const diff: bigint = a - b;
1429 const prod: bigint = a * b;
1430 const quot: bigint = a / b;
1431 const result: bigint = sum + diff + prod + quot;
1432 assert(result === this.target);
1433 }
1434}
1435"#;
1436 let contract = parse_and_validate(source);
1437 let result = typecheck(&contract);
1438 assert!(
1439 result.errors.is_empty(),
1440 "expected no typecheck errors for Arithmetic, got: {:?}",
1441 result.errors
1442 );
1443 }
1444
1445 #[test]
1450 fn test_valid_boolean_logic() {
1451 let source = r#"
1452import { SmartContract, assert } from 'runar-lang';
1453
1454class BoolLogic extends SmartContract {
1455 readonly threshold: bigint;
1456
1457 constructor(threshold: bigint) {
1458 super(threshold);
1459 this.threshold = threshold;
1460 }
1461
1462 public verify(a: bigint, b: bigint, flag: boolean) {
1463 const aAbove: boolean = a > this.threshold;
1464 const bAbove: boolean = b > this.threshold;
1465 const bothAbove: boolean = aAbove && bAbove;
1466 const eitherAbove: boolean = aAbove || bAbove;
1467 const notFlag: boolean = !flag;
1468 assert(bothAbove || (eitherAbove && notFlag));
1469 }
1470}
1471"#;
1472 let contract = parse_and_validate(source);
1473 let result = typecheck(&contract);
1474 assert!(
1475 result.errors.is_empty(),
1476 "expected no typecheck errors for BoolLogic, got: {:?}",
1477 result.errors
1478 );
1479 }
1480
1481 #[test]
1486 fn test_subtype_compatibility() {
1487 let source = r#"
1488import { SmartContract, assert, PubKey, sha256 } from 'runar-lang';
1489
1490class HashCheck extends SmartContract {
1491 readonly expectedHash: Sha256;
1492
1493 constructor(expectedHash: Sha256) {
1494 super(expectedHash);
1495 this.expectedHash = expectedHash;
1496 }
1497
1498 public verify(pubKey: PubKey) {
1499 assert(sha256(pubKey) === this.expectedHash);
1500 }
1501}
1502"#;
1503 let result = crate::frontend::parser::parse_source(source, Some("test.runar.ts"));
1506 if result.errors.is_empty() {
1507 if let Some(contract) = result.contract {
1508 let tc_result = typecheck(&contract);
1509 let pubkey_arg_error = tc_result.errors.iter().any(|e| {
1512 e.contains("argument") && e.contains("PubKey")
1513 });
1514 assert!(
1515 !pubkey_arg_error,
1516 "PubKey should be assignable to ByteString, but got error about PubKey argument: {:?}",
1517 tc_result.errors
1518 );
1519 }
1520 }
1521 }
1523
1524 #[test]
1530 fn test_unknown_function_console_log() {
1531 let source = r#"
1532import { SmartContract } from 'runar-lang';
1533
1534class Bad extends SmartContract {
1535 constructor() {
1536 super();
1537 }
1538
1539 public check(val: bigint) {
1540 console.log(val);
1541 assert(val > 0n);
1542 }
1543}
1544"#;
1545 let result = crate::frontend::parser::parse_source(source, Some("test.runar.ts"));
1546 if result.errors.is_empty() {
1547 if let Some(contract) = result.contract {
1548 let tc_result = typecheck(&contract);
1549 assert!(
1550 !tc_result.errors.is_empty(),
1551 "expected typecheck errors for unknown function console.log"
1552 );
1553 let has_unknown_error = tc_result.errors.iter().any(|e| {
1554 e.to_lowercase().contains("unknown")
1555 || e.to_lowercase().contains("console")
1556 });
1557 assert!(
1558 has_unknown_error,
1559 "expected error about unknown function console.log, got: {:?}",
1560 tc_result.errors
1561 );
1562 }
1563 }
1564 }
1567}