Skip to content

Diagnostics

Accepted

Accepted for the V1 compiler diagnostic and comptime validation API boundary; richer lint configuration remains deferred.

Diagnostics should be deterministic, source-oriented, and phase-specific.

A diagnostic records:

  • severity
  • primary span
  • message
  • optional secondary spans and labels
  • optional notes
  • optional hints
  • optional stable diagnostic code
  • optional machine-readable details

Diagnostic Pipeline

Compiler phases emit structured diagnostics into a diagnostic sink. They do not print directly and they do not construct terminal-formatted output.

The diagnostic renderer consumes the structured diagnostics and source store to produce programmer-facing output for stderr. Rendered output may include source excerpts, labels, notes, hints, and terminal color when the selected output mode supports it. Color and terminal styling are renderer concerns, not part of diagnostic meaning.

Structured diagnostic output is the technical source of truth for tools, tests, and renderers. Rendered stderr is a presentation of that same data for humans.

Renderer Contract

The stderr renderer should support source excerpts, primary labels, secondary labels, notes, and hints from the first parser diagnostics. Non-color output is the deterministic snapshot mode. Color is optional and must be controlled by output mode or terminal capability.

Rendered diagnostics should be easy for programmers to process, but exact line art, color palette, and terminal styling are not frozen by V1. The stable contract is the structured diagnostic stream plus deterministic non-color rendering for selected presentation tests.

Hard Errors

Hard compiler errors reject the program or the demanded compile-time evaluation. A hard error means source invalidity, a violated phase contract, an unsupported V1 boundary, or a deterministic comptime failure such as Compiler.err, resource exhaustion, unsupported host access, runtime-value dependency, or a checked safety trap during comptime evaluation.

Hard errors are owned by the canonical feature or compiler phase that detects them. This page defines the category and payload boundary; source-form, type-system, attribute, module, ownership, interop, and backend pages own the concrete invalid forms for their areas.

Diagnostic snapshots should treat hard compiler diagnostics as part of the public compiler contract. Stable diagnostic codes are optional in V1, but diagnostics that tools are expected to match long-term should use codes or deterministic machine-readable details when practical.

Warnings

Warnings are non-fatal compiler diagnostics. They report a condition the compiler or comptime code can identify deterministically while still allowing compilation to continue.

V1 warnings are limited to explicitly owned cases:

  • Compiler.warn during comptime execution;
  • deterministic phase-owned checks that a canonical compiler or feature page explicitly classifies as warnings;
  • tool-specific warnings, such as formatter warnings, when the tool owns that behavior.

Compiler.warn does not reject the program. V1 does not define warning-as-error mode, warning suppression, or warning policy configuration. Future build profiles may add warning policy, but that policy is not part of V1 source validity.

Standalone Compiler.note diagnostics are allowed. They are informational only, do not affect source validity, and share the same comptime cache replay boundary as Compiler.warn.

Comptime Diagnostics

Compiler.note, Compiler.warn, and Compiler.err are the canonical V1 comptime diagnostic APIs:

Compiler.note(diagnostic: Compiler.Diagnostic)
Compiler.warn(diagnostic: Compiler.Diagnostic)
Compiler.err(diagnostic: Compiler.Diagnostic)

Compiler.err aborts the current comptime evaluation and emits a compile error. Compiler.warn and Compiler.note report and continue.

Compiler.note and Compiler.warn are execution-time effects of comptime evaluation. Successful cached comptime results are not required to replay notes or warnings when reused. Use sema diagnostics, lints, or ordinary phase diagnostics for messages that must appear independently of comptime cache behavior.

These APIs are used by ordinary comptime validation in:

  • prelude constructors such as satisfies(...) and mutable_fields(...)
  • semantic contract and type factories
  • attribute providers
  • forced comptime expressions and blocks

No separate custom-constraint diagnostics system exists in V1. Custom validation is ordinary comptime code. It can accept or reject code with Compiler.*, but it does not create reusable sema facts unless it returns or composes compiler-produced Type.Predicate values.

Diagnostic Payload

V1 does not use function overloading for diagnostics. Every Compiler.* diagnostic function takes one Compiler.Diagnostic payload. Expected-type shorthand keeps the common message-only case concise:

Compiler.err(.{
  .message = "element type must be concrete",
})

Use the same payload shape when a comptime API has better source metadata:

Compiler.err(.{
  .code = "catalyst.type.not_concrete",
  .message = "element type must be concrete",
  .primary = T.source(),
  .notes = .{
    .{
      .message = "required by Sequence(T)",
      .source = Scope.caller().source(),
    },
  },
})

Compiler.Diagnostic has this V1 shape:

message
Required human-readable text.
primary
Optional primary source span.
secondary
Optional ordered secondary span list with labels.
notes
Optional ordered note list.
hints
Optional ordered hint list.
code
Optional stable diagnostic identifier.
details
Optional deterministic machine-readable payload for tooling and snapshots.

If primary is omitted, the compiler uses the current comptime call site or the best available source span for the evaluated construct. Notes may point at helper implementations, attribute targets, or the caller site when that improves the explanation.

Structured payloads must be deterministic. They should not include unstable compiler-internal IDs, nondeterministic ordering, host paths unless already part of source metadata, or rendered strings that depend on terminal formatting.

Validation and Facts

Comptime diagnostics are separate from predicate facts:

  • Compiler.err rejects the current comptime evaluation.
  • Compiler.warn and Compiler.note report information but do not change type checking.
  • Type.Predicate carries reusable facts for sema.
  • Arbitrary comptime bool checks and diagnostics do not create facts.

This keeps custom validation powerful without making ad hoc comptime code part of the narrowing model.

Compiler phases should report errors through a diagnostic sink instead of printing directly. The driver decides how diagnostics are rendered.

Snapshot tests should treat structured diagnostic output and selected rendered stderr output as part of the public contract.

Lint Boundary

Lints are optional tooling findings, not V1 compiler diagnostics. A lint may be suggested as info, warn, or error by the lint catalog, but those suggested severities are lint policy, not compiler diagnostic severities. A lint severity of error means a future enabled lint profile may fail a build; it does not mean the core compiler rejects the source.

Documented conventions are guidance unless the relevant feature page gives them a semantic rule or a stable lint identity. When a V1 page calls a pattern lintable, Lint Catalog should own the lint ID and suggested policy.

V1 does not define lint configuration, lint suppression, lint-as-error profiles, autofix representation, generated-code policy, or LSP/editor reporting. Those belong to First-Class Linting and the deferred lint-engine design.

Diagnostics vs Runtime Errors

Compiler diagnostics and runtime error values are separate concepts.

Runtime errors should stay cheap and explicit in function signatures. Rich diagnostic reporting should flow through explicit values such as Diagnostics, often carried in destructured ordinary struct parameters in compiler-like tools.

Checked safety failures such as signed overflow and bounds-check failure are also separate from ordinary runtime error values. They trap in a deterministic panic-like way and do not change function signatures to return T!E.

Phase Ownership

Each phase owns the diagnostics for its responsibility:

lexer
Invalid tokens and lexical errors.
parser
Syntax errors.
sema
Name resolution and type errors.
IR verifier
Malformed lowered IR.
backend
Backend contract violations and emission errors.

Diagnostics should not be printed directly by individual phases.