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