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::Generator(inner) => format!("Generator<{}>", format_type(inner)),
32        TypeExpr::Stream(inner) => format!("Stream<{}>", format_type(inner)),
33        TypeExpr::DictType(k, v) => format!("dict<{}, {}>", format_type(k), format_type(v)),
34        TypeExpr::Applied { name, args } => {
35            let args_str = args.iter().map(format_type).collect::<Vec<_>>().join(", ");
36            format!("{name}<{args_str}>")
37        }
38        TypeExpr::FnType {
39            params,
40            return_type,
41        } => {
42            let params_str = params
43                .iter()
44                .map(format_type)
45                .collect::<Vec<_>>()
46                .join(", ");
47            format!("fn({}) -> {}", params_str, format_type(return_type))
48        }
49        TypeExpr::Never => "never".to_string(),
50        TypeExpr::LitString(s) => format!("\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\"")),
51        TypeExpr::LitInt(v) => v.to_string(),
52    }
53}
54
55/// Produce a detail string describing why a Shape type is incompatible with
56/// another Shape type — e.g. "missing field 'age' (int)" or "field 'name'
57/// has type int, expected string". Returns `None` if both types are not shapes.
58pub fn shape_mismatch_detail(expected: &TypeExpr, actual: &TypeExpr) -> Option<String> {
59    if let (TypeExpr::Shape(ef), TypeExpr::Shape(af)) = (expected, actual) {
60        let mut details = Vec::new();
61        for field in ef {
62            if field.optional {
63                continue;
64            }
65            match af.iter().find(|f| f.name == field.name) {
66                None => details.push(format!(
67                    "missing field '{}' ({})",
68                    field.name,
69                    format_type(&field.type_expr)
70                )),
71                Some(actual_field) => {
72                    let e_str = format_type(&field.type_expr);
73                    let a_str = format_type(&actual_field.type_expr);
74                    if e_str != a_str {
75                        details.push(format!(
76                            "field '{}' has type {}, expected {}",
77                            field.name, a_str, e_str
78                        ));
79                    }
80                }
81            }
82        }
83        if details.is_empty() {
84            None
85        } else {
86            Some(details.join("; "))
87        }
88    } else {
89        None
90    }
91}
92
93/// Returns true when the type is obvious from the RHS expression
94/// (e.g. `let x = 42` is obviously int — no hint needed).
95pub(super) fn is_obvious_type(value: &SNode, _ty: &TypeExpr) -> bool {
96    matches!(
97        &value.node,
98        Node::IntLiteral(_)
99            | Node::FloatLiteral(_)
100            | Node::StringLiteral(_)
101            | Node::BoolLiteral(_)
102            | Node::NilLiteral
103            | Node::ListLiteral(_)
104            | Node::DictLiteral(_)
105            | Node::InterpolatedString(_)
106    )
107}