Improve Typed Jerboa diagnostics

ober

d06b8b185feb86210bfe20bb141ed6ad85a195ba

diff --git a/docs/typed-jerboa.md b/docs/typed-jerboa.md
index 3be4b68..9977526 100644
--- a/docs/typed-jerboa.md
+++ b/docs/typed-jerboa.md
@@ -142,7 +142,9 @@ Current landing:
   arities, duplicate fields, duplicate params, duplicate variant cases, and a
   small expression subset for return checking.
 - `support/typecheck.ss` and the `make typecheck` / `make typed-test` targets
-  run the parser/checker over typed source files without invoking Rust.
+  run the parser/checker over typed source files without invoking Rust. The
+  typecheck CLI now prints structured diagnostics with module context, detail,
+  and a short hint for common errors.
 - The parser currently recognizes explicit `export` forms, `type` declarations,
   immutable and `mut` record fields, variants including nullary cases, and
   `def` forms with typed parameters and `:` or `->` return markers.
@@ -761,7 +763,9 @@ Minimum excluded features:
 - Check match exhaustiveness. Same-module variant `match` now checks case
   coverage, duplicate cases, pattern arity, field bindings, and branch result
   types.
-- Produce useful errors.
+- Produce useful errors. Checker diagnostics now include stable kind, message,
+  detail, and hint fields; `support/typecheck.ss` prints those fields for typed
+  source files.
 
 ### Milestone 3: Rust Backend
 
diff --git a/lib/jerboa/typed/checker.ss b/lib/jerboa/typed/checker.ss
index d782819..e0a7830 100644
--- a/lib/jerboa/typed/checker.ss
+++ b/lib/jerboa/typed/checker.ss
@@ -8,6 +8,8 @@
   (export
     typed-check-error? make-typed-check-error
     typed-check-error-kind typed-check-error-message typed-check-error-detail
+    typed-check-error-hint
+    typed-check-error->string
     check-typed-module
     typecheck-typed-library-form
     typed-module-valid?)
@@ -37,6 +39,63 @@
   (def (make-check-error kind message detail)
     (make-typed-check-error kind message detail))
 
+  (def (typed-check-error-hint err)
+    (case (typed-check-error-kind err)
+      [(unknown-type)
+       "Define the type in this typed-library, import it later when imports exist, or use a builtin type name."]
+      [(unknown-type-constructor)
+       "Use a supported compound type constructor: List, Vector, Option, Result, Pair, or ->."]
+      [(bad-type-arity)
+       "Check the number of type arguments for the compound type."]
+      [(duplicate-type duplicate-value duplicate-field duplicate-param
+        duplicate-variant-case duplicate-export duplicate-local
+        duplicate-pattern-binding duplicate-match-case)
+       "Rename one of the duplicate bindings so each generated name is unique."]
+      [(undefined-export)
+       "Export a def, record operation, or variant operation that this typed-library actually provides."]
+      [(unknown-value)
+       "Bind the value as a parameter or let binding, or call a supported same-module operation."]
+      [(unsupported-expression)
+       "This expression is outside the current Typed Jerboa checker subset; simplify it or add checker support before using it in typed code."]
+      [(return-type-mismatch)
+       "Change the declared return type or make the function body produce that type."]
+      [(condition-type-mismatch)
+       "Make the if condition produce Bool."]
+      [(branch-type-mismatch)
+       "Make every branch produce the same type."]
+      [(operand-type-mismatch)
+       "Use operands whose types match the primitive operation."]
+      [(bad-call-arity)
+       "Pass exactly the number of arguments required by the typed function, constructor, accessor, or predicate."]
+      [(argument-type-mismatch)
+       "Change the argument expression or the callee signature so the argument type matches the parameter type."]
+      [(match-type-mismatch)
+       "Match only on a value whose type is a variant declared in this typed-library."]
+      [(unknown-match-case)
+       "Use a case constructor from the matched variant."]
+      [(bad-match-arity)
+       "Bind exactly one field name for each field in the variant case."]
+      [(bad-match-pattern bad-match-clause bad-match)
+       "Use match clauses shaped like ((Case field ...) body ...) or an _/else fallback."]
+      [(non-exhaustive-match)
+       "Add clauses for every variant case, or use _/else as an explicit fallback."]
+      [else #f]))
+
+  (def (typed-check-error->string path module-name err)
+    (let ([port (open-output-string)]
+          [hint (typed-check-error-hint err)])
+      (fprintf port "~a: module ~s: ~s~%"
+               path
+               module-name
+               (typed-check-error-kind err))
+      (fprintf port "  message: ~a~%"
+               (typed-check-error-message err))
+      (fprintf port "  detail: ~s~%"
+               (typed-check-error-detail err))
+      (when hint
+        (fprintf port "  hint: ~a~%" hint))
+      (get-output-string port)))
+
   (def (append-map f xs)
     (let loop ([rest xs] [out '()])
       (if (null? rest)
diff --git a/support/typecheck.ss b/support/typecheck.ss
index 9b2a09d..9e1eb99 100644
--- a/support/typecheck.ss
+++ b/support/typecheck.ss
@@ -25,12 +25,7 @@
        (loop (cdr rest) out)])))
 
 (define (display-check-error path module-name err)
-  (printf "~a: ~s: ~s: ~a: ~s~%"
-          path
-          module-name
-          (typed-check-error-kind err)
-          (typed-check-error-message err)
-          (typed-check-error-detail err)))
+  (display (typed-check-error->string path module-name err)))
 
 (define (check-typed-form path form)
   (let* ([module (parse-typed-library form)]
diff --git a/tests/test-typed-checker.ss b/tests/test-typed-checker.ss
index 17afb0f..cb94cf5 100644
--- a/tests/test-typed-checker.ss
+++ b/tests/test-typed-checker.ss
@@ -25,6 +25,9 @@
   (map typed-check-error-kind
        (typecheck-typed-library-form form)))
 
+(define (first-error form)
+  (car (typecheck-typed-library-form form)))
+
 (define valid-form
   '(typed-library (sample typed pane)
      (export make-Pane Pane? Pane-id Pane-focused? Pane-focused?-set!
@@ -161,6 +164,29 @@
        (def (f (x : Nat)) : Nat y)))
   '(unknown-value))
 
+(test "diagnostic string includes hint"
+  (typed-check-error->string
+    "bad.ss"
+    '(body unknown-value)
+    (first-error
+      '(typed-library (body unknown-value)
+         (export f)
+         (def (f (x : Nat)) : Nat y))))
+  "bad.ss: module (body unknown-value): unknown-value\n  message: unknown value in expression\n  detail: y\n  hint: Bind the value as a parameter or let binding, or call a supported same-module operation.\n")
+
+(test "diagnostic hint for non-exhaustive match"
+  (typed-check-error-hint
+    (first-error
+      '(typed-library (body match-missing)
+         (export f)
+         (variant EditOp
+           (Insert (at : Nat))
+           (Noop))
+         (def (f (op : EditOp)) : Nat
+           (match op
+             ((Insert at) at))))))
+  "Add clauses for every variant case, or use _/else as an explicit fallback.")
+
 (test "let infers local type"
   (error-kinds
     '(typed-library (body let-ok)