1use std::collections::HashMap;
7use std::ops::Range;
8use std::sync::Arc;
9
10use assura_parser::ast::{Expr, SpExpr, Spanned, expr_to_string};
11use assura_resolve::ResolvedFile;
12
13use crate::checkers::PendingDecreaseCheck;
14
15pub(crate) const DEFAULT_BUFFER_CAPACITY: i64 = 256;
20pub(crate) const DEFAULT_DEADLINE_MS: i64 = 1000;
22pub(crate) const DEFAULT_BIT_CONTAINER_BITS: i64 = 64;
24pub(crate) const DEFAULT_REGION_SIZE: i64 = 1024;
26pub(crate) const DEFAULT_PAGE_SIZE: i64 = 1024;
28pub(crate) const DEFAULT_FEATURE_MAX: i64 = 256;
30pub(crate) const DEFAULT_HASH_BITS: i64 = 32;
32
33pub(crate) const DEFAULT_ULP_TOLERANCE: f64 = 1.0;
37
38pub(crate) const DEFAULT_PARAM_ZERO: i64 = 0;
43pub(crate) const DEFAULT_PARAM_ONE: i64 = 1;
45
46#[derive(Debug, Clone, PartialEq)]
66pub enum Type {
67 Int,
69 Nat,
70 Float,
71 Bool,
72 String,
73 Bytes,
74 Unit,
75 Never,
76
77 U8,
79 U16,
80 U32,
81 U64,
82 I8,
83 I16,
84 I32,
85 I64,
86 F32,
87 F64,
88
89 List(Box<Type>),
91 Map(Box<Type>, Box<Type>),
92 Set(Box<Type>),
93 Option(Box<Type>),
94 Result(Box<Type>, Box<Type>),
95
96 Sequence(Box<Type>),
98
99 Named(String),
101
102 TypeParam(String),
104
105 Fn {
107 params: Vec<Type>,
108 ret: Box<Type>,
109 },
110
111 Tuple(Vec<Type>),
113
114 Refined {
116 base: Box<Type>,
117 predicate: Box<SpExpr>,
119 bound_var: String,
121 },
122
123 Unknown,
125
126 Error,
130}
131
132impl Type {
133 pub(crate) fn is_indeterminate(&self) -> bool {
138 matches!(self, Type::Unknown | Type::Error)
139 }
140
141 pub fn refined_from_str(base: Type, bound_var: &str, predicate_text: &str) -> Self {
147 let tokens: Vec<String> = if predicate_text.is_empty() {
148 vec![]
149 } else {
150 predicate_text
151 .split_whitespace()
152 .map(String::from)
153 .collect()
154 };
155 Type::Refined {
156 base: Box::new(base),
157 predicate: Box::new(Spanned::no_span(Expr::Raw(tokens))),
158 bound_var: bound_var.to_string(),
159 }
160 }
161
162 pub fn predicate_str(&self) -> Option<String> {
164 if let Type::Refined { predicate, .. } = self {
165 let s = expr_to_string(predicate);
166 if s.is_empty() { None } else { Some(s) }
167 } else {
168 None
169 }
170 }
171}
172
173#[derive(Debug, Clone, Default)]
180pub struct TypeEnv {
181 scopes: Vec<Scope>,
184 pub struct_fields: HashMap<String, Vec<(String, Type)>>,
187}
188
189#[derive(Debug, Clone, Default)]
191struct Scope {
192 bindings: HashMap<String, Type>,
193}
194
195impl TypeEnv {
196 pub fn new() -> Self {
198 Self {
199 scopes: vec![Scope::default()],
200 struct_fields: HashMap::new(),
201 }
202 }
203
204 pub fn push_scope(&mut self) {
207 self.scopes.push(Scope::default());
208 }
209
210 pub fn pop_scope(&mut self) {
215 assert!(
216 self.scopes.len() > 1,
217 "cannot pop the global scope from TypeEnv"
218 );
219 self.scopes.pop();
220 }
221
222 pub fn depth(&self) -> usize {
224 self.scopes.len() - 1
225 }
226
227 pub fn insert(&mut self, name: String, ty: Type) -> Option<Type> {
231 self.scopes
232 .last_mut()
233 .expect("TypeEnv must have at least one scope")
234 .bindings
235 .insert(name, ty)
236 }
237
238 pub fn lookup(&self, name: &str) -> Option<&Type> {
240 for scope in self.scopes.iter().rev() {
241 if let Some(ty) = scope.bindings.get(name) {
242 return Some(ty);
243 }
244 }
245 None
246 }
247
248 pub(crate) fn lookup_field(&self, struct_name: &str, field_name: &str) -> Option<&Type> {
250 self.struct_fields
251 .get(struct_name)
252 .and_then(|fields| fields.iter().find(|(n, _)| n == field_name).map(|(_, t)| t))
253 }
254
255 pub fn len(&self) -> usize {
257 self.scopes.iter().map(|s| s.bindings.len()).sum()
258 }
259
260 pub fn is_empty(&self) -> bool {
262 self.scopes.iter().all(|s| s.bindings.is_empty())
263 }
264
265 pub fn iter(&self) -> impl Iterator<Item = (&str, &Type)> {
269 let mut seen = HashMap::new();
270 for scope in &self.scopes {
271 for (name, ty) in &scope.bindings {
272 seen.insert(name.as_str(), ty);
273 }
274 }
275 seen.into_iter()
276 }
277}
278
279#[derive(Debug, Clone)]
285pub struct TypeError {
286 pub code: assura_diagnostics::ErrorCode,
288 pub message: String,
290 pub span: Range<usize>,
292 pub secondary: Option<(Range<usize>, String)>,
294 pub suggestion: Option<String>,
298}
299
300impl TypeError {
301 pub fn with_context(self, context: &str) -> Self {
303 Self {
304 message: format!("{} ({context})", self.message),
305 ..self
306 }
307 }
308}
309
310impl From<TypeError> for assura_diagnostics::Diagnostic {
311 fn from(e: TypeError) -> Self {
312 let mut d = assura_diagnostics::Diagnostic::error(e.code.clone(), e.message, e.span);
313 if let Some((span, label)) = e.secondary {
314 d.secondary.push(assura_diagnostics::SecondaryLabel {
315 span,
316 message: label,
317 });
318 }
319 let suggestion_text = e.suggestion.or_else(|| {
322 assura_diagnostics::explain(e.code.as_str()).map(|info| info.fix.to_string())
323 });
324 if let Some(text) = suggestion_text {
325 let span = d.primary.clone();
326 d = d.with_suggestion(text, span, "");
327 }
328 d
329 }
330}
331
332#[derive(Debug, Clone)]
339pub struct TypedFile {
340 pub resolved: Arc<ResolvedFile>,
341 pub type_env: TypeEnv,
342 pub pending_decrease_checks: Vec<PendingDecreaseCheck>,
345 pub generated_tests: Vec<crate::GeneratedTest>,
348 pub warnings: Vec<TypeError>,
351}
352
353pub(crate) fn builtin_type(name: &str) -> Option<Type> {
359 match name {
360 "Int" => Some(Type::Int),
361 "Nat" => Some(Type::Nat),
362 "Float" => Some(Type::Float),
363 "Bool" => Some(Type::Bool),
364 "String" => Some(Type::String),
365 "Bytes" => Some(Type::Bytes),
366 "Unit" => Some(Type::Unit),
367 "Never" => Some(Type::Never),
368 "U8" => Some(Type::U8),
369 "U16" => Some(Type::U16),
370 "U32" => Some(Type::U32),
371 "U64" => Some(Type::U64),
372 "I8" => Some(Type::I8),
373 "I16" => Some(Type::I16),
374 "I32" => Some(Type::I32),
375 "I64" => Some(Type::I64),
376 "F32" => Some(Type::F32),
377 "F64" => Some(Type::F64),
378 "List" => Some(Type::List(Box::new(Type::Unknown))),
381 "Map" => Some(Type::Map(Box::new(Type::Unknown), Box::new(Type::Unknown))),
382 "Set" => Some(Type::Set(Box::new(Type::Unknown))),
383 "Option" => Some(Type::Option(Box::new(Type::Unknown))),
384 "Result" => Some(Type::Result(
385 Box::new(Type::Unknown),
386 Box::new(Type::Unknown),
387 )),
388 "Sequence" => Some(Type::Sequence(Box::new(Type::Unknown))),
389 _ => None,
390 }
391}
392
393impl std::fmt::Display for Type {
398 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
399 match self {
400 Type::Int => write!(f, "Int"),
401 Type::Nat => write!(f, "Nat"),
402 Type::Float => write!(f, "Float"),
403 Type::Bool => write!(f, "Bool"),
404 Type::String => write!(f, "String"),
405 Type::Bytes => write!(f, "Bytes"),
406 Type::Unit => write!(f, "Unit"),
407 Type::Never => write!(f, "Never"),
408 Type::U8 => write!(f, "U8"),
409 Type::U16 => write!(f, "U16"),
410 Type::U32 => write!(f, "U32"),
411 Type::U64 => write!(f, "U64"),
412 Type::I8 => write!(f, "I8"),
413 Type::I16 => write!(f, "I16"),
414 Type::I32 => write!(f, "I32"),
415 Type::I64 => write!(f, "I64"),
416 Type::F32 => write!(f, "F32"),
417 Type::F64 => write!(f, "F64"),
418 Type::List(t) => write!(f, "List<{t}>"),
419 Type::Map(k, v) => write!(f, "Map<{k}, {v}>"),
420 Type::Set(t) => write!(f, "Set<{t}>"),
421 Type::Option(t) => write!(f, "Option<{t}>"),
422 Type::Result(t, e) => write!(f, "Result<{t}, {e}>"),
423 Type::Sequence(t) => write!(f, "Sequence<{t}>"),
424 Type::Named(n) => write!(f, "{n}"),
425 Type::TypeParam(n) => write!(f, "{n}"),
426 Type::Fn { params, ret } => {
427 write!(f, "fn(")?;
428 for (i, p) in params.iter().enumerate() {
429 if i > 0 {
430 write!(f, ", ")?;
431 }
432 write!(f, "{p}")?;
433 }
434 write!(f, ") -> {ret}")
435 }
436 Type::Tuple(elems) => {
437 write!(f, "(")?;
438 for (i, t) in elems.iter().enumerate() {
439 if i > 0 {
440 write!(f, ", ")?;
441 }
442 write!(f, "{t}")?;
443 }
444 if elems.len() == 1 {
446 write!(f, ",")?;
447 }
448 write!(f, ")")
449 }
450 Type::Refined {
451 base,
452 predicate,
453 bound_var,
454 } => {
455 let pred_str = expr_to_string(predicate);
456 if pred_str.is_empty() {
457 write!(f, "{base}")
458 } else {
459 write!(f, "{{ {bound_var} : {base} | {pred_str} }}")
460 }
461 }
462 Type::Unknown => write!(f, "Unknown"),
463 Type::Error => write!(f, "<error>"),
464 }
465 }
466}
467
468#[cfg(test)]
469mod tests {
470 use super::*;
471
472 #[test]
475 fn is_indeterminate_unknown() {
476 assert!(Type::Unknown.is_indeterminate());
477 }
478
479 #[test]
480 fn is_indeterminate_error() {
481 assert!(Type::Error.is_indeterminate());
482 }
483
484 #[test]
485 fn is_indeterminate_concrete_types_return_false() {
486 let concrete = [
487 Type::Int,
488 Type::Nat,
489 Type::Float,
490 Type::Bool,
491 Type::String,
492 Type::Bytes,
493 Type::Unit,
494 Type::Never,
495 Type::U8,
496 Type::U16,
497 Type::U32,
498 Type::U64,
499 Type::I8,
500 Type::I16,
501 Type::I32,
502 Type::I64,
503 Type::F32,
504 Type::F64,
505 Type::List(Box::new(Type::Int)),
506 Type::Map(Box::new(Type::String), Box::new(Type::Int)),
507 Type::Set(Box::new(Type::Nat)),
508 Type::Option(Box::new(Type::Bool)),
509 Type::Result(Box::new(Type::Int), Box::new(Type::String)),
510 Type::Sequence(Box::new(Type::Int)),
511 Type::Named("Foo".into()),
512 Type::TypeParam("T".into()),
513 Type::Fn {
514 params: vec![Type::Int],
515 ret: Box::new(Type::Bool),
516 },
517 Type::Tuple(vec![Type::Int, Type::Bool]),
518 Type::refined_from_str(Type::Int, "x", "x > 0"),
519 ];
520 for ty in &concrete {
521 assert!(!ty.is_indeterminate(), "{ty} should not be indeterminate");
522 }
523 }
524
525 #[test]
528 fn display_base_types() {
529 assert_eq!(Type::Int.to_string(), "Int");
530 assert_eq!(Type::Nat.to_string(), "Nat");
531 assert_eq!(Type::Float.to_string(), "Float");
532 assert_eq!(Type::Bool.to_string(), "Bool");
533 assert_eq!(Type::String.to_string(), "String");
534 assert_eq!(Type::Bytes.to_string(), "Bytes");
535 assert_eq!(Type::Unit.to_string(), "Unit");
536 assert_eq!(Type::Never.to_string(), "Never");
537 }
538
539 #[test]
540 fn display_fixed_width_integers() {
541 assert_eq!(Type::U8.to_string(), "U8");
542 assert_eq!(Type::U64.to_string(), "U64");
543 assert_eq!(Type::I32.to_string(), "I32");
544 assert_eq!(Type::F64.to_string(), "F64");
545 }
546
547 #[test]
548 fn display_generic_containers() {
549 assert_eq!(Type::List(Box::new(Type::Int)).to_string(), "List<Int>");
550 assert_eq!(
551 Type::Map(Box::new(Type::String), Box::new(Type::Nat)).to_string(),
552 "Map<String, Nat>"
553 );
554 assert_eq!(Type::Set(Box::new(Type::Bool)).to_string(), "Set<Bool>");
555 assert_eq!(
556 Type::Option(Box::new(Type::Float)).to_string(),
557 "Option<Float>"
558 );
559 assert_eq!(
560 Type::Result(Box::new(Type::Int), Box::new(Type::String)).to_string(),
561 "Result<Int, String>"
562 );
563 assert_eq!(
564 Type::Sequence(Box::new(Type::Bytes)).to_string(),
565 "Sequence<Bytes>"
566 );
567 }
568
569 #[test]
570 fn display_fn_type() {
571 let ty = Type::Fn {
572 params: vec![Type::Int, Type::Bool],
573 ret: Box::new(Type::String),
574 };
575 assert_eq!(ty.to_string(), "fn(Int, Bool) -> String");
576 }
577
578 #[test]
579 fn display_fn_no_params() {
580 let ty = Type::Fn {
581 params: vec![],
582 ret: Box::new(Type::Unit),
583 };
584 assert_eq!(ty.to_string(), "fn() -> Unit");
585 }
586
587 #[test]
588 fn display_tuple() {
589 let ty = Type::Tuple(vec![Type::Int, Type::Bool, Type::String]);
590 assert_eq!(ty.to_string(), "(Int, Bool, String)");
591 }
592
593 #[test]
594 fn display_tuple_single_element_trailing_comma() {
595 let ty = Type::Tuple(vec![Type::Int]);
596 assert_eq!(ty.to_string(), "(Int,)");
597 }
598
599 #[test]
600 fn display_refined_with_predicate() {
601 let ty = Type::refined_from_str(Type::Int, "x", "x > 0");
602 assert_eq!(ty.to_string(), "{ x : Int | x > 0 }");
603 }
604
605 #[test]
606 fn display_refined_empty_predicate() {
607 let ty = Type::refined_from_str(Type::Nat, "x", "");
608 assert_eq!(ty.to_string(), "Nat");
610 }
611
612 #[test]
613 fn display_unknown_and_error() {
614 assert_eq!(Type::Unknown.to_string(), "Unknown");
615 assert_eq!(Type::Error.to_string(), "<error>");
616 }
617
618 #[test]
619 fn display_named_and_type_param() {
620 assert_eq!(Type::Named("MyStruct".into()).to_string(), "MyStruct");
621 assert_eq!(Type::TypeParam("T".into()).to_string(), "T");
622 }
623
624 #[test]
625 fn display_nested_generics() {
626 let ty = Type::List(Box::new(Type::Option(Box::new(Type::Int))));
628 assert_eq!(ty.to_string(), "List<Option<Int>>");
629 }
630
631 #[test]
634 fn builtin_type_base_types() {
635 assert_eq!(builtin_type("Int"), Some(Type::Int));
636 assert_eq!(builtin_type("Nat"), Some(Type::Nat));
637 assert_eq!(builtin_type("Float"), Some(Type::Float));
638 assert_eq!(builtin_type("Bool"), Some(Type::Bool));
639 assert_eq!(builtin_type("String"), Some(Type::String));
640 assert_eq!(builtin_type("Bytes"), Some(Type::Bytes));
641 assert_eq!(builtin_type("Unit"), Some(Type::Unit));
642 assert_eq!(builtin_type("Never"), Some(Type::Never));
643 }
644
645 #[test]
646 fn builtin_type_fixed_width() {
647 assert_eq!(builtin_type("U8"), Some(Type::U8));
648 assert_eq!(builtin_type("U16"), Some(Type::U16));
649 assert_eq!(builtin_type("U32"), Some(Type::U32));
650 assert_eq!(builtin_type("U64"), Some(Type::U64));
651 assert_eq!(builtin_type("I8"), Some(Type::I8));
652 assert_eq!(builtin_type("I64"), Some(Type::I64));
653 assert_eq!(builtin_type("F32"), Some(Type::F32));
654 assert_eq!(builtin_type("F64"), Some(Type::F64));
655 }
656
657 #[test]
658 fn builtin_type_generic_containers_bare() {
659 assert_eq!(
661 builtin_type("List"),
662 Some(Type::List(Box::new(Type::Unknown)))
663 );
664 assert_eq!(
665 builtin_type("Set"),
666 Some(Type::Set(Box::new(Type::Unknown)))
667 );
668 assert_eq!(
669 builtin_type("Option"),
670 Some(Type::Option(Box::new(Type::Unknown)))
671 );
672 }
673
674 #[test]
675 fn builtin_type_unknown_name() {
676 assert_eq!(builtin_type("FooBar"), None);
677 assert_eq!(builtin_type(""), None);
678 assert_eq!(builtin_type("int"), None); }
680
681 #[test]
684 fn type_env_insert_and_lookup() {
685 let mut env = TypeEnv::new();
686 assert!(env.is_empty());
687 assert_eq!(env.len(), 0);
688
689 env.insert("x".into(), Type::Int);
690 assert_eq!(env.lookup("x"), Some(&Type::Int));
691 assert_eq!(env.len(), 1);
692 assert!(!env.is_empty());
693 }
694
695 #[test]
696 fn type_env_insert_overwrites() {
697 let mut env = TypeEnv::new();
698 let prev = env.insert("x".into(), Type::Int);
699 assert!(prev.is_none());
700
701 let prev = env.insert("x".into(), Type::Bool);
702 assert_eq!(prev, Some(Type::Int));
703 assert_eq!(env.lookup("x"), Some(&Type::Bool));
704 }
705
706 #[test]
707 fn type_env_lookup_missing() {
708 let env = TypeEnv::new();
709 assert_eq!(env.lookup("nonexistent"), None);
710 }
711
712 #[test]
713 fn type_env_lookup_field() {
714 let mut env = TypeEnv::new();
715 env.struct_fields.insert(
716 "Point".into(),
717 vec![("x".into(), Type::Float), ("y".into(), Type::Float)],
718 );
719 assert_eq!(env.lookup_field("Point", "x"), Some(&Type::Float));
720 assert_eq!(env.lookup_field("Point", "z"), None);
721 assert_eq!(env.lookup_field("Unknown", "x"), None);
722 }
723
724 #[test]
727 fn type_error_with_context() {
728 let err = TypeError {
729 code: "A03001".into(),
730 message: "type mismatch".into(),
731 span: 10..20,
732 secondary: None,
733 suggestion: None,
734 };
735 let enriched = err.with_context("in function foo");
736 assert_eq!(enriched.message, "type mismatch (in function foo)");
737 assert_eq!(enriched.span, 10..20);
738 }
739
740 #[test]
741 fn type_error_to_diagnostic_with_explicit_suggestion() {
742 let err = TypeError {
743 code: "A03001".into(),
744 message: "type mismatch".into(),
745 span: 10..20,
746 secondary: None,
747 suggestion: Some("use `as Int` to cast".into()),
748 };
749 let diag: assura_diagnostics::Diagnostic = err.into();
750 assert_eq!(diag.code, "A03001");
751 let s = diag.suggestion.expect("should have suggestion");
752 assert_eq!(s.message, "use `as Int` to cast");
753 }
754
755 #[test]
756 fn type_error_to_diagnostic_falls_back_to_catalog() {
757 let err = TypeError {
759 code: "A03001".into(),
760 message: "type mismatch".into(),
761 span: 0..5,
762 secondary: None,
763 suggestion: None,
764 };
765 let diag: assura_diagnostics::Diagnostic = err.into();
766 let s = diag
768 .suggestion
769 .expect("catalog fallback should produce suggestion");
770 assert!(
771 !s.message.is_empty(),
772 "catalog fix text should not be empty"
773 );
774 }
775
776 #[test]
778 fn a03005_catalog_help_is_field_oriented() {
779 let err = TypeError {
780 code: "A03005".into(),
781 message: "tuple index `2` out of range for type `(Int, Bool)` (arity 2)".into(),
782 span: 0..5,
783 secondary: None,
784 suggestion: None,
785 };
786 let diag: assura_diagnostics::Diagnostic = err.into();
787 let s = diag
788 .suggestion
789 .expect("A03005 catalog should provide Help/suggestion");
790 let help = s.message.to_lowercase();
791 assert!(
792 !help.contains("calling a function"),
793 "A03005 Help must not mention calling a function, got: {}",
794 s.message
795 );
796 assert!(
797 help.contains("field") || help.contains("tuple"),
798 "A03005 Help should be field-oriented, got: {}",
799 s.message
800 );
801 }
802
803 #[test]
804 fn type_error_to_diagnostic_no_suggestion_for_unknown_code() {
805 let err = TypeError {
806 code: "A00000".into(),
807 message: "unknown error".into(),
808 span: 0..1,
809 secondary: None,
810 suggestion: None,
811 };
812 let diag: assura_diagnostics::Diagnostic = err.into();
813 assert!(diag.suggestion.is_none());
815 }
816
817 #[test]
820 fn typeenv_global_scope_lookup() {
821 let mut env = TypeEnv::new();
822 env.insert("x".into(), Type::Int);
823 assert_eq!(env.lookup("x"), Some(&Type::Int));
824 assert_eq!(env.depth(), 0);
825 }
826
827 #[test]
828 fn typeenv_push_pop_scope() {
829 let mut env = TypeEnv::new();
830 env.insert("x".into(), Type::Int);
831 assert_eq!(env.depth(), 0);
832
833 env.push_scope();
834 assert_eq!(env.depth(), 1);
835 assert_eq!(env.lookup("x"), Some(&Type::Int));
837
838 env.insert("x".into(), Type::Bool);
840 assert_eq!(env.lookup("x"), Some(&Type::Bool));
841
842 env.pop_scope();
843 assert_eq!(env.depth(), 0);
844 assert_eq!(env.lookup("x"), Some(&Type::Int));
846 }
847
848 #[test]
849 fn typeenv_nested_scopes() {
850 let mut env = TypeEnv::new();
851 env.insert("a".into(), Type::Int);
852
853 env.push_scope();
854 env.insert("b".into(), Type::Bool);
855
856 env.push_scope();
857 env.insert("c".into(), Type::String);
858
859 assert_eq!(env.lookup("a"), Some(&Type::Int));
861 assert_eq!(env.lookup("b"), Some(&Type::Bool));
862 assert_eq!(env.lookup("c"), Some(&Type::String));
863 assert_eq!(env.depth(), 2);
864
865 env.pop_scope();
866 assert!(env.lookup("c").is_none());
867 assert_eq!(env.lookup("b"), Some(&Type::Bool));
868
869 env.pop_scope();
870 assert!(env.lookup("b").is_none());
871 assert_eq!(env.lookup("a"), Some(&Type::Int));
872 }
873
874 #[test]
875 fn typeenv_inner_binding_does_not_leak() {
876 let mut env = TypeEnv::new();
877 env.push_scope();
878 env.insert("local".into(), Type::Nat);
879 assert_eq!(env.lookup("local"), Some(&Type::Nat));
880 env.pop_scope();
881 assert!(env.lookup("local").is_none());
882 }
883
884 #[test]
885 fn typeenv_len_counts_all_scopes() {
886 let mut env = TypeEnv::new();
887 env.insert("x".into(), Type::Int);
888 env.push_scope();
889 env.insert("y".into(), Type::Bool);
890 assert_eq!(env.len(), 2);
891 env.pop_scope();
892 assert_eq!(env.len(), 1);
893 }
894
895 #[test]
896 fn typeenv_is_empty_across_scopes() {
897 let mut env = TypeEnv::new();
898 assert!(env.is_empty());
899 env.push_scope();
900 assert!(env.is_empty());
901 env.insert("x".into(), Type::Int);
902 assert!(!env.is_empty());
903 env.pop_scope();
904 assert!(env.is_empty());
905 }
906
907 #[test]
908 #[should_panic(expected = "cannot pop the global scope")]
909 fn typeenv_pop_global_panics() {
910 let mut env = TypeEnv::new();
911 env.pop_scope(); }
913
914 #[test]
915 fn typeenv_struct_fields_not_scope_dependent() {
916 let mut env = TypeEnv::new();
917 env.struct_fields
918 .insert("Point".into(), vec![("x".into(), Type::Float)]);
919 env.push_scope();
920 assert_eq!(env.lookup_field("Point", "x"), Some(&Type::Float));
921 env.pop_scope();
922 assert_eq!(env.lookup_field("Point", "x"), Some(&Type::Float));
923 }
924
925 #[test]
928 fn refined_from_str_creates_expr_raw() {
929 let ty = Type::refined_from_str(Type::Int, "x", "x > 0");
930 if let Type::Refined {
931 base,
932 predicate,
933 bound_var,
934 } = &ty
935 {
936 assert_eq!(**base, Type::Int);
937 assert_eq!(bound_var, "x");
938 assert!(
940 matches!(&predicate.node, Expr::Raw(tokens) if tokens.len() == 3),
941 "expected 3-token Raw, got {:?}",
942 predicate.node
943 );
944 } else {
945 panic!("expected Refined");
946 }
947 }
948
949 #[test]
950 fn refined_predicate_str_returns_text() {
951 let ty = Type::refined_from_str(Type::Nat, "v", "v >= 0");
952 assert_eq!(ty.predicate_str(), Some("v >= 0".into()));
953 }
954
955 #[test]
956 fn refined_predicate_str_empty_returns_none() {
957 let ty = Type::refined_from_str(Type::Int, "x", "");
958 assert_eq!(ty.predicate_str(), None);
959 }
960
961 #[test]
962 fn refined_display_uses_bound_var() {
963 let ty = Type::refined_from_str(Type::Int, "v", "v > 0");
964 assert_eq!(ty.to_string(), "{ v : Int | v > 0 }");
965 }
966
967 #[test]
968 fn refined_with_structural_expr() {
969 use assura_parser::ast::BinOp;
971 let pred = Spanned::no_span(Expr::BinOp {
972 lhs: Box::new(Spanned::no_span(Expr::Ident("x".into()))),
973 op: BinOp::Gt,
974 rhs: Box::new(Spanned::no_span(Expr::Literal(
975 assura_parser::ast::Literal::Int("0".into()),
976 ))),
977 });
978 let ty = Type::Refined {
979 base: Box::new(Type::Int),
980 predicate: Box::new(pred),
981 bound_var: "x".into(),
982 };
983 let s = ty.to_string();
985 assert!(s.contains("x") && s.contains("Int"), "got: {s}");
986 }
987
988 #[test]
989 fn refined_non_refined_type_predicate_str_is_none() {
990 assert_eq!(Type::Int.predicate_str(), None);
991 assert_eq!(Type::Bool.predicate_str(), None);
992 }
993}