Skip to content

Improve error messages for common typo and inference-failure cases #27

Description

@hierophantos

Summary

Several elaborator error messages produce diagnostics that are technically accurate but unhelpful for users — surfacing internal-feeling errors without guidance toward a fix. This issue tracks specific cases identified during PR review and language-exploration work for improvement, with concrete suggestions for each.

Specific cases identified

Case 1: "Expression is not a valid type" with hole types

Reproducer:

;; Typo or missing spec → defn elaborates with hole types
defn constantly [x] (fn [u : Unit] x)
def my-3 := [constantly 3]
;; Error: Expression is not a valid type / Expression: Unit -> _

What happens: defn has no matching spec, elaborates with _ -> Unit -> _. Partial application infers first hole but not second. System reports the partially-resolved type with unsolved hole.

Current message: Expression is not a valid type / Expression: Unit -> _

Suggested message:

Could not fully infer type at: [constantly 3]
  Inferred so far: Unit -> ?A
  Where ?A is unconstrained.
  
  Suggestions:
  - Add a type annotation: def my-3 : <Unit -> Int> := ...
  - Provide a `spec` for `constantly` so the elaborator can resolve
  - Apply all arguments inline: [constantly 3 unit]

Case 2: "fn: all parameters except body must be bare symbols or a binder (x : T)" for empty params

Reproducer:

def f := (fn [] 42)
;; Error: fn: all parameters except body must be bare symbols or a binder (x : T)

What happens: 0-arg lambdas don't fit the Pi-type structure (no binder to bind). The parser rejects empty parameter lists.

Current message: technically accurate but doesn't tell the user what to do

Suggested message:

fn: parameters cannot be empty in this dependent type system.
  
  "Function of no arguments" is encoded as Unit -> A:
    (fn [u : Unit] body)
  
  Alternatively, in pure code, (fn [] x) ≡ x — just bind x directly.
  
  See <link to docs section on Unit-encoded thunks>

Case 3: "Multiplicity violation" surfacing for kind-level mult mismatch

Reproducer:

spec scale-vec Rat [List Rat] -> [List Rat]
defn scale-vec [s xs]
  [map (fn [x : Rat] [rat* s x]) xs]
;; Error: Multiplicity violation [fn [x <Rat>] ...]

What happens: List's kind is Pi(m0, Type, Type); map's {C : Type → Type} elaborates to Pi(mw, Type, Type). The kind-level mult mismatch surfaces as a value-level "Multiplicity violation" — confusing because the closure looks fine.

Current message: surfaces at the fn body, blames the closure

Suggested message:

Type-constructor kind mismatch in implicit position:
  List has kind: Pi(m0, Type, Type)   ;; erased type arg
  map expects: Pi(mw, Type, Type)     ;; the `->` defaults to mw
  
  This is the kind-level "m0 vs mw" mismatch (eigentrust pitfall #2).
  The fix is in #23 (multiplicity subtyping); for now, workaround:
  <suggest workaround when known>

(Pending #23 / multiplicity subtyping work — once that lands, the diagnostic should distinguish the kind-level case from genuine value-level mult violations.)

Case 4: "Unbound variable: NAME" cascading from upstream errors

Reproducer: any failing def NAME := ... produces "Unbound variable: NAME" at every downstream site.

What happens: the def silently fails (returns error, doesn't bind), then every reference reports "Unbound variable" — the actual error is upstream.

Current message: just "Unbound variable: NAME" — doesn't explain why NAME isn't bound

Suggested message:

Unbound variable: my-3
  
  This name was attempted to be defined at line N but the def failed:
    def my-3 := ...
    ↑ failed with: <upstream error>
  
  Fix the def to make my-3 available.

Or, better: have the elaborator track failed-def names and report a single "definition NAME failed; downstream uses suppressed" rather than cascading.

Case 5: Typo detection in spec/defn pairs

Reproducer:

spec consntantly Int -> Int   ;; typo
defn constantly [x] x          ;; no matching spec

What happens: spec doesn't match any defn, defn has no spec, both proceed with hole types and produce confusing downstream errors.

Suggested behavior:

defn `constantly` has no matching spec.
  Did you mean `consntantly` (declared at line N)? Edit distance: 2.
  
  Without a spec, the type will be inferred from usage with hole defaults.

Scope

This issue tracks UX improvements to error messages. Implementation can land incrementally — each case can be addressed independently.

  • Add user-facing diagnostic messages with suggestions
  • Add typo-detection (Levenshtein distance) for unmatched spec/defn pairs
  • Improve cascading-error suppression (don't report "Unbound variable" for names whose def failed; report the def failure once)
  • Add documentation links from common errors to relevant guide sections
  • Test that suggested messages render correctly for each reproducer

Out of scope

  • Underlying type-system changes (most of these are orthogonal to language semantics)
  • Internationalization of error messages
  • IDE/LSP integration (separate)

Why this matters

Error messages are the user's primary feedback loop. A concise, actionable diagnostic costs us a small amount of code and saves users debugging time, especially during their first encounter with dependent typing concepts (Unit, multiplicity, hole types).

The current language-design exploration (PRs from external contributors, internal experimentation) repeatedly surfaces these errors without clear paths forward. Improving the diagnostics directly improves contributor and learner experience.

References

Priority

Medium. Compounds across all language features. Each case improved is a small UX win that's permanently visible.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions