Improve Typed Jerboa diagnostics
ober
d06b8b185feb86210bfe20bb141ed6ad85a195ba
--- 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 --- 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) --- 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)] --- 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)