|
| 1 | +use std::collections::HashMap; |
| 2 | +use std::fmt::Write; |
| 3 | + |
1 | 4 | use thiserror; |
2 | 5 |
|
3 | 6 | use crate::cst::QualifiedIdent; |
@@ -649,4 +652,267 @@ impl TypeError { |
649 | 652 | } |
650 | 653 | } |
651 | 654 | } |
| 655 | + |
| 656 | + /// Format the error with readable multi-line layout and normalized type variables. |
| 657 | + /// Unification variables like `?120` become `t0`, `t1`, etc. |
| 658 | + pub fn format_pretty(&self) -> String { |
| 659 | + let var_map = self.build_var_map(); |
| 660 | + match self { |
| 661 | + TypeError::UnificationError { expected, found, .. } => { |
| 662 | + format!( |
| 663 | + "Could not match type\n\n {}\n\n with type\n\n {}", |
| 664 | + pretty_type(expected, &var_map), |
| 665 | + pretty_type(found, &var_map), |
| 666 | + ) |
| 667 | + } |
| 668 | + TypeError::KindsDoNotUnify { expected, found, .. } => { |
| 669 | + format!( |
| 670 | + "Could not match kind\n\n {}\n\n with kind\n\n {}", |
| 671 | + pretty_type(expected, &var_map), |
| 672 | + pretty_type(found, &var_map), |
| 673 | + ) |
| 674 | + } |
| 675 | + TypeError::HoleInferredType { name, ty, .. } => { |
| 676 | + format!( |
| 677 | + "Hole ?{} has the inferred type\n\n {}", |
| 678 | + interner::resolve(*name).unwrap_or_default(), |
| 679 | + pretty_type(ty, &var_map), |
| 680 | + ) |
| 681 | + } |
| 682 | + TypeError::InfiniteType { var, ty, .. } => { |
| 683 | + format!( |
| 684 | + "An infinite type was inferred for type variable t{}\n\n {}", |
| 685 | + var.0, |
| 686 | + pretty_type(ty, &var_map), |
| 687 | + ) |
| 688 | + } |
| 689 | + TypeError::InfiniteKind { var, ty, .. } => { |
| 690 | + format!( |
| 691 | + "An infinite kind was inferred for type t{}\n\n {}", |
| 692 | + var.0, |
| 693 | + pretty_type(ty, &var_map), |
| 694 | + ) |
| 695 | + } |
| 696 | + TypeError::NoInstanceFound { class_name, type_args, .. } => { |
| 697 | + let args: Vec<String> = type_args.iter().map(|ty| pretty_type(ty, &var_map)).collect(); |
| 698 | + format!( |
| 699 | + "No type class instance was found for\n\n {} {}", |
| 700 | + class_name, |
| 701 | + args.join(" "), |
| 702 | + ) |
| 703 | + } |
| 704 | + TypeError::CannotGeneralizeRecursiveFunction { name, type_, .. } => { |
| 705 | + format!( |
| 706 | + "Unable to generalize the type of the recursive function {}.\n The inferred type was\n\n {}\n\n Try adding a type signature.", |
| 707 | + interner::resolve(*name).unwrap_or_default(), |
| 708 | + pretty_type(type_, &var_map), |
| 709 | + ) |
| 710 | + } |
| 711 | + TypeError::MissingClassMember { class_name, members, .. } => { |
| 712 | + let mut s = format!("The class {} is missing the following members:\n", class_name); |
| 713 | + for (name, ty) in members { |
| 714 | + let _ = write!(s, "\n {} :: {}", interner::resolve(*name).unwrap_or_default(), pretty_type(ty, &var_map)); |
| 715 | + } |
| 716 | + s |
| 717 | + } |
| 718 | + TypeError::EscapedSkolem { name, ty, .. } => { |
| 719 | + format!( |
| 720 | + "A type variable has escaped its scope: {}\n\n {}", |
| 721 | + interner::resolve(*name).unwrap_or_default(), |
| 722 | + pretty_type(ty, &var_map), |
| 723 | + ) |
| 724 | + } |
| 725 | + TypeError::ExpectedType { found, .. } => { |
| 726 | + format!( |
| 727 | + "Expected type of kind Type, but found kind\n\n {}", |
| 728 | + pretty_type(found, &var_map), |
| 729 | + ) |
| 730 | + } |
| 731 | + TypeError::CannotApplyExpressionOfTypeOnType { type_, .. } => { |
| 732 | + format!( |
| 733 | + "Cannot apply expression of type\n\n {}\n\n to a type argument", |
| 734 | + pretty_type(type_, &var_map), |
| 735 | + ) |
| 736 | + } |
| 737 | + // For all other errors, use the default Display but with normalized unif vars |
| 738 | + _ => { |
| 739 | + if var_map.is_empty() { |
| 740 | + format!("{self}") |
| 741 | + } else { |
| 742 | + // Replace ?N patterns in the default display string |
| 743 | + let s = format!("{self}"); |
| 744 | + replace_unif_vars_in_string(&s, &var_map) |
| 745 | + } |
| 746 | + } |
| 747 | + } |
| 748 | + } |
| 749 | + |
| 750 | + /// Collect all types referenced by this error and build a normalized var mapping. |
| 751 | + fn build_var_map(&self) -> HashMap<u32, usize> { |
| 752 | + let mut unif_ids = Vec::new(); |
| 753 | + self.collect_types(&mut |ty| collect_unif_vars(ty, &mut unif_ids)); |
| 754 | + let mut map = HashMap::new(); |
| 755 | + for id in &unif_ids { |
| 756 | + let len = map.len(); |
| 757 | + map.entry(*id).or_insert(len); |
| 758 | + } |
| 759 | + map |
| 760 | + } |
| 761 | + |
| 762 | + /// Visit all Type values in this error variant. |
| 763 | + fn collect_types(&self, visitor: &mut dyn FnMut(&Type)) { |
| 764 | + match self { |
| 765 | + TypeError::UnificationError { expected, found, .. } => { visitor(expected); visitor(found); } |
| 766 | + TypeError::InfiniteType { ty, .. } | TypeError::InfiniteKind { ty, .. } => visitor(ty), |
| 767 | + TypeError::HoleInferredType { ty, .. } => visitor(ty), |
| 768 | + TypeError::NoInstanceFound { type_args, .. } |
| 769 | + | TypeError::OverlappingInstances { type_args, .. } |
| 770 | + | TypeError::PossiblyInfiniteInstance { type_args, .. } |
| 771 | + | TypeError::PossiblyInfiniteCoercibleInstance { type_args, .. } => { |
| 772 | + for ty in type_args { visitor(ty); } |
| 773 | + } |
| 774 | + TypeError::CannotGeneralizeRecursiveFunction { type_, .. } |
| 775 | + | TypeError::CannotApplyExpressionOfTypeOnType { type_, .. } => visitor(type_), |
| 776 | + TypeError::MissingClassMember { members, .. } => { |
| 777 | + for (_, ty) in members { visitor(ty); } |
| 778 | + } |
| 779 | + TypeError::KindsDoNotUnify { expected, found, .. } => { visitor(expected); visitor(found); } |
| 780 | + TypeError::ExpectedType { found, .. } => visitor(found), |
| 781 | + TypeError::EscapedSkolem { ty, .. } => visitor(ty), |
| 782 | + TypeError::QuantificationCheckFailureInType { ty, .. } |
| 783 | + | TypeError::QuantificationCheckFailureInKind { ty, .. } => visitor(ty), |
| 784 | + _ => {} |
| 785 | + } |
| 786 | + } |
| 787 | +} |
| 788 | + |
| 789 | +/// Collect unification variable IDs from a type in order of first appearance. |
| 790 | +fn collect_unif_vars(ty: &Type, ids: &mut Vec<u32>) { |
| 791 | + match ty { |
| 792 | + Type::Unif(id) => { |
| 793 | + if !ids.contains(&id.0) { |
| 794 | + ids.push(id.0); |
| 795 | + } |
| 796 | + } |
| 797 | + Type::App(f, a) => { collect_unif_vars(f, ids); collect_unif_vars(a, ids); } |
| 798 | + Type::Fun(a, b) => { collect_unif_vars(a, ids); collect_unif_vars(b, ids); } |
| 799 | + Type::Forall(_, t) => collect_unif_vars(t, ids), |
| 800 | + Type::Record(fields, tail) => { |
| 801 | + for (_, t) in fields { collect_unif_vars(t, ids); } |
| 802 | + if let Some(t) = tail { collect_unif_vars(t, ids); } |
| 803 | + } |
| 804 | + _ => {} |
| 805 | + } |
| 806 | +} |
| 807 | + |
| 808 | +/// Format a type with normalized unification variable names. |
| 809 | +fn pretty_type(ty: &Type, var_map: &HashMap<u32, usize>) -> String { |
| 810 | + if var_map.is_empty() { |
| 811 | + return format!("{ty}"); |
| 812 | + } |
| 813 | + let mut out = String::new(); |
| 814 | + fmt_type(&mut out, ty, var_map, false); |
| 815 | + out |
| 816 | +} |
| 817 | + |
| 818 | +fn fmt_type(out: &mut String, ty: &Type, var_map: &HashMap<u32, usize>, nested: bool) { |
| 819 | + match ty { |
| 820 | + Type::Unif(id) => { |
| 821 | + if let Some(&idx) = var_map.get(&id.0) { |
| 822 | + let _ = write!(out, "t{idx}"); |
| 823 | + } else { |
| 824 | + let _ = write!(out, "t{}", id.0); |
| 825 | + } |
| 826 | + } |
| 827 | + Type::Var(sym) => { |
| 828 | + let _ = write!(out, "{}", interner::resolve(*sym).unwrap_or_default()); |
| 829 | + } |
| 830 | + Type::Con(sym) => { |
| 831 | + let _ = write!(out, "{sym}"); |
| 832 | + } |
| 833 | + Type::App(func, arg) => { |
| 834 | + if nested { out.push('('); } |
| 835 | + match func.as_ref() { |
| 836 | + Type::App(..) | Type::Con(..) | Type::Var(..) | Type::Unif(..) => fmt_type(out, func, var_map, false), |
| 837 | + _ => fmt_type(out, func, var_map, true), |
| 838 | + } |
| 839 | + out.push(' '); |
| 840 | + fmt_type(out, arg, var_map, true); |
| 841 | + if nested { out.push(')'); } |
| 842 | + } |
| 843 | + Type::Fun(from, to) => { |
| 844 | + if nested { out.push('('); } |
| 845 | + fmt_type(out, from, var_map, true); |
| 846 | + out.push_str(" -> "); |
| 847 | + fmt_type(out, to, var_map, false); |
| 848 | + if nested { out.push(')'); } |
| 849 | + } |
| 850 | + Type::Forall(vars, body) => { |
| 851 | + if nested { out.push('('); } |
| 852 | + out.push_str("forall"); |
| 853 | + for (v, visible) in vars { |
| 854 | + if *visible { |
| 855 | + let _ = write!(out, " @{}", interner::resolve(*v).unwrap_or_default()); |
| 856 | + } else { |
| 857 | + let _ = write!(out, " {}", interner::resolve(*v).unwrap_or_default()); |
| 858 | + } |
| 859 | + } |
| 860 | + out.push_str(". "); |
| 861 | + fmt_type(out, body, var_map, false); |
| 862 | + if nested { out.push(')'); } |
| 863 | + } |
| 864 | + Type::TypeString(sym) => { |
| 865 | + let _ = write!(out, "\"{}\"", interner::resolve(*sym).unwrap_or_default()); |
| 866 | + } |
| 867 | + Type::TypeInt(n) => { |
| 868 | + let _ = write!(out, "{n}"); |
| 869 | + } |
| 870 | + Type::Record(fields, tail) => { |
| 871 | + out.push_str("{ "); |
| 872 | + for (i, (label, field_ty)) in fields.iter().enumerate() { |
| 873 | + if i > 0 { out.push_str(", "); } |
| 874 | + let _ = write!(out, "{} :: ", interner::resolve(*label).unwrap_or_default()); |
| 875 | + fmt_type(out, field_ty, var_map, false); |
| 876 | + } |
| 877 | + if let Some(tail) = tail { |
| 878 | + if !fields.is_empty() { out.push_str(" | "); } |
| 879 | + fmt_type(out, tail, var_map, false); |
| 880 | + } |
| 881 | + out.push_str(" }"); |
| 882 | + } |
| 883 | + } |
| 884 | +} |
| 885 | + |
| 886 | +/// Replace `?N` patterns in a pre-formatted string with normalized `tN` names. |
| 887 | +fn replace_unif_vars_in_string(s: &str, var_map: &HashMap<u32, usize>) -> String { |
| 888 | + let mut result = String::with_capacity(s.len()); |
| 889 | + let mut chars = s.char_indices().peekable(); |
| 890 | + while let Some((i, c)) = chars.next() { |
| 891 | + if c == '?' { |
| 892 | + // Try to parse a number after '?' |
| 893 | + let start = i + 1; |
| 894 | + let mut end = start; |
| 895 | + while let Some(&(j, d)) = chars.peek() { |
| 896 | + if d.is_ascii_digit() { |
| 897 | + end = j + 1; |
| 898 | + chars.next(); |
| 899 | + } else { |
| 900 | + break; |
| 901 | + } |
| 902 | + } |
| 903 | + if end > start { |
| 904 | + if let Ok(id) = s[start..end].parse::<u32>() { |
| 905 | + if let Some(&idx) = var_map.get(&id) { |
| 906 | + let _ = write!(result, "t{idx}"); |
| 907 | + continue; |
| 908 | + } |
| 909 | + } |
| 910 | + } |
| 911 | + result.push(c); |
| 912 | + result.push_str(&s[start..end]); |
| 913 | + } else { |
| 914 | + result.push(c); |
| 915 | + } |
| 916 | + } |
| 917 | + result |
652 | 918 | } |
0 commit comments