* Documentation

Diagnostic Engine Architecture

How TurfError/TurfWarning, related locations, and error codes work — the always-on diagnostic system beneath --smart.

Every diagnostic Turf produces — with or without --smart — funnels through the same DiagnosticEngine, built around structural data the compiler already has, not string pattern-matching over a formatted message.

TurfError / TurfWarning

Every diagnostic is either fatal (TurfError) or non-fatal (TurfWarning):

Structural context, not string-scanning

Every diagnostic class that has something meaningful to point to — a variable’s declaration site on a type mismatch, every candidate overload on an arity mismatch, the array’s declaration on an out-of-bounds access, the prior declaration on a duplicate — attaches a RelatedLocation ({SourceLocation, Description}) at the exact point in the Resolver/TypeChecker/Codegen where that information is already known, because real symbol-table resolution already happened there. This is threaded through TurfError/TurfWarning via a chainable .withRelated(loc, "declared here") call, into the DiagnosticEngine, and stored on the diagnostic itself.

Nothing about this is recovered later by regex over an error string or by re-scanning source text for a keyword — the data is real from the moment the compiler pass that produces it runs. This structural-context design is also the foundation the --smart pipeline builds on; see The --smart Pipeline.

Error codes

Every registered error class has a stable E0NN code, assigned once and never reassigned — 68 codes as of v1.0.0 (E001–E068). Assignment order is alphabetical by (source file, class name) at the time each code was added; it’s an opaque identifier, not a severity or category ranking. A small curated subset is flagged Important (see Tooling) — genuinely non-obvious concepts where the one-line message alone probably isn’t enough.

turf --explain E0NN resolves its explanation text from docs/errors/codes/ using a three-tier fallback: a per-code doc file first, then a construct-specific doc routed by keyword classification of the error’s short description, then a plain per-category doc as the coarsest fallback.

Cascade suppression and ordering

If a diagnostic has already been recorded on a given source line, every subsequent diagnostic on that same line — error or warning — is silently dropped. This keeps one root-cause typo from producing a wall of downstream noise. All collected diagnostics are sorted by (line, column) before being printed, so output order always reflects the source file, not the order in which compiler passes happened to raise them.

”Did you mean?” suggestions

When you reference an unknown name — a variable, type, keyword, struct field, struct method, module symbol, or enum variant — the compiler searches names actually in scope for close matches, using restricted Damerau-Levenshtein edit distance (an adjacent-character transposition counts as one edit, not two). The distance threshold scales with query length: 1 for queries of four characters or fewer, 2 for up to seven characters, and roughly a third of the string’s length beyond that. Keyword matching deliberately excludes static — it sits at edit-distance 2 from the very common identifier state, which produced constant false-positive suggestions on ordinary parameter names.

Compile-time static analysis (CFG)

Independent of the diagnostic-rendering machinery above, a control-flow graph is built for every function body and a fixed sequence of dataflow analyses runs over it on every compile, unconditionally. This is where use-before-initialization, dead-store/unused-variable warnings, missing-return, unreachable code, infinite-loop/infinite-recursion detection, use-after-free/double-free, and compile-time array-bounds checking all live. The full list, with one example each, is in Tooling — this page covers the diagnostic plumbing, that page covers what fires.

Where AI fits in

None of the above involves a model call. --smart sits downstream of this exact same DiagnosticEngine — it only ever engages after a normal TurfError has already been constructed with its real structural context attached, and only for the first error in a given compile pass. See The --smart Pipeline for what happens next.