Files
libvct-spec/spec/011-diagnostics.md
T

8.7 KiB

011 — Diagnostics

  • Status: Draft
  • Normative language: MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY are to be interpreted as described in RFC 2119.

1. Purpose

libVCT has a single central diagnostics service (vct.diag). Every module emits diagnostics through it; no module prints directly to a stream. This gives embedders one uniform surface for errors, warnings, notes, optimizer remarks, and internal compiler errors, and it lets the CLI present them in several formats without every module knowing which format is active.

2. Scope

This file specifies the diagnostics service, the Diagnostic structure and its severity and code vocabulary, source maps, output channels, optimizer remarks, the internal-compiler-error policy, and process exit codes. The pipeline that produces these diagnostics is specified in 010 — Driver & CLI; the backends that consume source maps are specified in 008 — C Backend and 009 — LLVM Backend.

3. Definitions

Term Meaning
Diagnostic One emitted message with severity, code, span, and optional notes and suggestions.
Severity Error, Warning, Note, Remark, or Ice.
DiagCode A stable, namespaced identifier such as VCT1002.
Span A source range: file, line, column, and length.
Source map The association of HIR nodes with frontend source locations, resolved for VIR through the lowering map.
Consumer A DiagnosticConsumer callback that receives emitted diagnostics.
ICE Internal compiler error.

4. Model

The service is emit-only from the perspective of compilation modules. A module builds a Diagnostic and hands it to the service. The service forwards it to the configured consumer. Fatal conditions (an error diagnostic, or an ICE) stop the pipeline at the next safe boundary; they never let compilation continue on invalid state.

The service is GC-allocated, consistent with the cold path rule in 001 — Architecture.

struct Diagnostic {
    Severity      severity;     // Error | Warning | Note | Remark | Ice
    DiagCode      code;         // stable namespaced, e.g. VCT1002
    string        message;
    Span          primary;
    Span[]        notes;
    Suggestion[]  suggestions;
}

5. Normative requirements

5.1 Central service

R1. Every module MUST emit diagnostics through the central service. No module MUST print to standard output or standard error directly.

R2. The service MUST be GC-allocated.

R3. The service MUST forward each emitted diagnostic to the configured consumer in emission order.

R4. An Error or Ice diagnostic MUST prevent further compilation on the invalid path. The pipeline MUST stop at the next safe boundary after such a diagnostic.

5.2 Diagnostic structure

R5. Every diagnostic MUST carry a Severity and a DiagCode. A diagnostic with no code MUST NOT be emitted.

R6. Severity MUST be one of the values in Table 1.

Table 1. Severity levels

Severity Meaning
Error Compilation cannot proceed correctly.
Warning Suspicious but continuable.
Note Supporting context attached to another diagnostic.
Remark An optimizer decision record.
Ice Internal compiler error.

R7. A DiagCode MUST be a stable, namespaced identifier in the form VCT followed by digits. Once published, a code's meaning MUST NOT change and the code MUST NOT be renumbered.

R8. A diagnostic MUST carry a primary Span whenever a source location is known, and it MAY carry additional note spans and suggestions. When no location can be resolved, the diagnostic MUST carry no location instead (see R12).

R9. A Note diagnostic MUST be attached to a parent diagnostic rather than emitted as a standalone top-level message.

5.3 Source maps

R10. The frontend MUST be able to register a SourceLocation (file, line, column, length) against any HIR node.

R11. For VIR entities, the service MUST resolve source locations through the lowering map that associates HIR nodes with the VIR entities they became (see 005 — Lowering).

R12. When a location cannot be resolved, the service MUST degrade gracefully to a diagnostic with no location. It MUST NOT fabricate a location.

R13. The C backend MUST emit #line directives from the registered source map (see 008 — C Backend), and the driver MUST re-map compiler errors through it (see 010 — Driver & CLI).

5.4 Channels

R14. The service MUST expose a single canonical DiagnosticConsumer callback for embedders.

R15. The CLI MUST provide a human-readable channel that enables color when standard error is a terminal.

R16. The CLI MUST provide a JSON channel selected by --diagnostics=json.

R17. The JSON channel MUST use a versioned schema. The schema version MUST appear in the output, and the schema MUST be stable within a major version.

R18. A single diagnostic MUST render equivalently in content across all channels; the channels differ only in presentation.

Example JSON shape:

{
  "version": 1,
  "diagnostics": [
    {
      "severity": "error",
      "code": "VCT1002",
      "message": "missing trait at lowering",
      "file": "input.vox",
      "line": 12,
      "column": 5,
      "length": 3,
      "notes": []
    }
  ]
}

5.5 Optimizer remarks

R19. Optimizer remarks MUST be off by default and MUST be enabled by --remarks.

R20. Optimizer remarks MUST be enabled automatically at -O3.

R21. Every remark MUST be tied to a specific trait-pipeline decision, naming the request or attribute that drove it.

Examples:

HIR: replaced 3 uses of node#412 (comptime 64)
VIR: denied Inline on @foo - CostModel (callee 2.4x size budget)

5.6 Internal compiler error policy

R22. The following conditions MUST be treated as internal compiler errors:

  • a missing or contradictory trait encountered at lowering (see 005 — Lowering);
  • a VIR invariant violation detected in a debug build (see 006 — VIR);
  • a Strong request denied for reason Illegality or ContradictsTrait (see 007 — VIR Optimizer).

R23. In release builds, an internal compiler error MUST return a failure result and emit an Ice diagnostic that requests a module dump.

R24. In debug builds, an internal compiler error MUST assert.

R25. An internal compiler error MUST NEVER be silently continued past.

5.7 Warning policy

R26. The trait-mismatch warning raised when a suggested trait conflicts with the derived value MUST be promoted to an error under -Werror.

R27. A frontend MUST be able to opt out of warning promotions: -Wno-error=<category> MUST disable the promotion of that category, and a bare -Wno-error MUST disable every promotion.

5.8 Exit codes

R28. The CLI MUST exit with the codes in Table 2.

Table 2. Exit codes

Code Meaning
0 Success.
1 Diagnostics present.
2 Usage or configuration error.
3 Internal compiler error.

R29. Exit code 3 MUST take precedence over exit code 1 when an ICE has been emitted.

6. Invariants

  • I1. Every emitted diagnostic has a severity and a DiagCode.
  • I2. No compilation module writes directly to a stream.
  • I3. Diagnostics are emitted and rendered in a deterministic order for a fixed input and Config.
  • I4. An ICE never resolves to a success exit code.
  • I5. A location is either resolved or omitted; it is never invented.
  • I6. The JSON channel's schema version is always present.

7. Examples

A frontend registers a location and a later lowering failure reports it:

diag.registerLocation(node, SourceLocation("input.vox", 12, 5, 3));
// later, during lowering:
emit(Diagnostic(Severity.Error, DiagCode(1002),
                "missing trait at lowering", span));

A MustTail request on GCC becomes a hard error rather than a silent fallback:

error[VCT1044]: MustTail requires [[clang::musttail]]; current compiler is GCC
  --> input.vox:7:5

8. Cross-references