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.
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:
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:
Case 2: "fn: all parameters except body must be bare symbols or a binder (x : T)" for empty params
Reproducer:
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:
Case 3: "Multiplicity violation" surfacing for kind-level mult mismatch
Reproducer:
What happens:
List's kind isPi(m0, Type, Type);map's{C : Type → Type}elaborates toPi(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
fnbody, blames the closureSuggested message:
(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:
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:
What happens: spec doesn't match any defn, defn has no spec, both proceed with hole types and produce confusing downstream errors.
Suggested behavior:
Scope
This issue tracks UX improvements to error messages. Implementation can land incrementally — each case can be addressed independently.
Out of scope
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
constantlydiscussion 2026-04-26consntantly)m0 <: mw(and broader subtyping infrastructure) #23 (multiplicity subtyping — would change the surface of Case 3)Priority
Medium. Compounds across all language features. Each case improved is a small UX win that's permanently visible.