Skip to main content

harn_parser/typechecker/
format.rs

1//! Display helpers for type expressions and shape mismatches.
2//!
3//! `format_type` is the canonical pretty-printer for `TypeExpr` (also used
4//! by `harn-lsp` and `harn-fmt` via re-export). `shape_mismatch_detail`
5//! produces a one-line "missing field …" / "field 'x' has type …" diff that
6//! enriches type-error messages.
7
8use crate::ast::*;
9
10/// Pretty-print a type expression for display in error messages.
11pub fn format_type(ty: &TypeExpr) -> String {
12    match ty {
13        TypeExpr::Named(n) => n.clone(),
14        TypeExpr::Union(types) => types
15            .iter()
16            .map(format_type)
17            .collect::<Vec<_>>()
18            .join(" | "),
19        TypeExpr::Shape(fields) => {
20            let inner: Vec<String> = fields
21                .iter()
22                .map(|f| {
23                    let opt = if f.optional { "?" } else { "" };
24                    format!("{}{opt}: {}", f.name, format_type(&f.type_expr))
25                })
26                .collect();
27            format!("{{{}}}", inner.join(", "))
28        }
29        TypeExpr::List(inner) => format!("list<{}>", format_type(inner)),
30        TypeExpr::Iter(inner) => format!("iter<{}>", format_type(inner)),
31        TypeExpr::DictType(k, v) => format!("dict<{}, {}>", format_type(k), format_type(v)),
32        TypeExpr::Applied { name, args } => {
33            let args_str = args.iter().map(format_type).collect::<Vec<_>>().join(", ");
34            format!("{name}<{args_str}>")
35        }
36        TypeExpr::FnType {
37            params,
38            return_type,
39        } => {
40            let params_str = params
41                .iter()
42                .map(format_type)
43                .collect::<Vec<_>>()
44                .join(", ");
45            format!("fn({}) -> {}", params_str, format_type(return_type))
46        }
47        TypeExpr::Never => "never".to_string(),
48        TypeExpr::LitString(s) => format!("\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\"")),
49        TypeExpr::LitInt(v) => v.to_string(),
50    }
51}
52
53/// Produce a detail string describing why a Shape type is incompatible with
54/// another Shape type — e.g. "missing field 'age' (int)" or "field 'name'
55/// has type int, expected string". Returns `None` if both types are not shapes.
56pub fn shape_mismatch_detail(expected: &TypeExpr, actual: &TypeExpr) -> Option<String> {
57    if let (TypeExpr::Shape(ef), TypeExpr::Shape(af)) = (expected, actual) {
58        let mut details = Vec::new();
59        for field in ef {
60            if field.optional {
61                continue;
62            }
63            match af.iter().find(|f| f.name == field.name) {
64                None => details.push(format!(
65                    "missing field '{}' ({})",
66                    field.name,
67                    format_type(&field.type_expr)
68                )),
69                Some(actual_field) => {
70                    let e_str = format_type(&field.type_expr);
71                    let a_str = format_type(&actual_field.type_expr);
72                    if e_str != a_str {
73                        details.push(format!(
74                            "field '{}' has type {}, expected {}",
75                            field.name, a_str, e_str
76                        ));
77                    }
78                }
79            }
80        }
81        if details.is_empty() {
82            None
83        } else {
84            Some(details.join("; "))
85        }
86    } else {
87        None
88    }
89}
90
91/// Returns true when the type is obvious from the RHS expression
92/// (e.g. `let x = 42` is obviously int — no hint needed).
93pub(super) fn is_obvious_type(value: &SNode, _ty: &TypeExpr) -> bool {
94    matches!(
95        &value.node,
96        Node::IntLiteral(_)
97            | Node::FloatLiteral(_)
98            | Node::StringLiteral(_)
99            | Node::BoolLiteral(_)
100            | Node::NilLiteral
101            | Node::ListLiteral(_)
102            | Node::DictLiteral(_)
103            | Node::InterpolatedString(_)
104    )
105}