Compiler Diagnostics
This document specifies the human-readable diagnostic format emitted by the Silk toolchain, including:
- the
silkCLI (silk check,silk build), - the embedding ABI (
libsilk.aviasilk_compiler_last_error/silk_error_format), - and tooling that reuses the front-end (for example
silk-lsp).
The goal is to provide diagnostics that are:
- precise (file + line + column + source span),
- stable (consistent wording and stable error codes for known error kinds),
- consumable by humans (caret snippets, notes/help where appropriate),
- easy to test (deterministic formatting; the canonical text contains no ANSI escapes).
Diagnostic Policy#
The compiler should diagnose the actual class of failure, not hide it behind bring-up terminology.
User-facing diagnostics should distinguish at least these categories:
- Spec/type-check failure: the program violates the documented Silk language or standard-library contract.
- Unimplemented language feature: the program uses a language feature that is not yet implemented in the compiler and is not part of the shipped documented language contract.
- Backend/target limitation: the program parses and type-checks, but a target-specific lowering/codegen path cannot yet emit the requested output.
- Stdlib/module availability gap: the program refers to a standard-library or package surface that is absent or unavailable for the current build/host.
- Internal compiler error: the compiler lost required context or reached an unexpected internal failure.
The term subset should not be the primary explanation in new diagnostics. It may still appear in historical notes or implementation-status prose, but user-facing errors should say what is actually wrong: for example "unimplemented language feature", "unsupported backend target path", or a specific contract violation.
Rule for feature-vs-rejection wording:
- If a construct is part of the documented language/spec surface, a compiler rejection must be described as an implementation gap, not as a language-level rejection.
- In particular:
u128/f128are language features, soE2114/E2115describe missing implementation work in some compiler paths rather than forbidden types.- monomorphized generics are language features, so
E2016is for generic forms the current compiler has not implemented yet, not for generic syntax that is outside the language.
Terminology#
- Source span: a byte range in the UTF‑8 source buffer (
offset,length). - Displayed line and column numbers are 1-based.
- Columns are measured in UTF‑8 bytes (matching the lexer’s current
Token.columnbehavior). - Primary label: the main span where the error is reported (single span in the implementation).
- Note / Help: supplemental lines that explain context or suggest a fix.
Text Format (CLI and ABI)#
The standard human-readable diagnostic format is:
error[E<code>]: <message>
--> <path>:<line>:<column>
|
<line> | <source line text>
| <caret underline>
= note: <note text> (optional, repeatable)
= help: <help text> (optional, repeatable)
Rules:
- The
error[...]line always appears for known error kinds;<code>is stable for that error kind. - For diagnostics with no usable location, the
--> ...and snippet block may be omitted. - The snippet block uses the 1-based line number and includes the full line text as it appears in the source.
- The caret underline is placed under the primary span:
- for a zero-length span, print a single
^, - otherwise print
^repeated for the span length, clipped to the line end if needed. - The canonical text format contains no ANSI color escapes.
Manifest and Config Errors#
The CLI uses the same caret diagnostic format for errors in tooling/config inputs,
including the package manifest silk.toml and build-module-generated manifests.
These diagnostics may not yet have stable error codes.
Example (missing = in silk.toml):
error: invalid TOML in package manifest
--> silk.toml:2:6
|
2 | name "app"
| ^ expected `=`
ANSI Color (CLI)#
The silk CLI may decorate the canonical diagnostic format with ANSI SGR escape codes
when writing to a terminal. The visible text (after stripping ANSI escapes) must still
match the canonical format.
Color is enabled only when:
- stderr is a TTY that supports ANSI escapes,
NO_COLORis not set,TERMis notdumb.
Color is never used for the embedding ABI (silk_error_format / silk_compiler_last_error),
and is not used when stderr is not a TTY (for example when piping diagnostics to a file).
Suggestions and Help Text#
Diagnostics may include one or more = help: lines that suggest concrete fixes.
These are heuristic and may be omitted when the compiler cannot compute a safe
suggestion.
Examples of help/suggestion content the compiler may emit:
- for unknown imports, a
"did you mean ...?"suggestion based on nearby names, - for file imports, a note about the resolved import path,
- reminders about enabling or configuring the standard library (
--nostd,--std-root,SILK_STD_ROOT) when importingstd::..., - guidance to include additional modules in the build/module set when an import refers to a package or file that is not present.
Diagnostic Lookup Command#
silk error is the terminal lookup surface for stable compiler diagnostics:
silk error <code>prints the canonical code, category, short description, documentation references, any bundled example for that diagnostic, and asilk guide <code>follow-up only when the installed guide catalog actually links that diagnostic code.silk error --listandsilk error -lprint every stable compiler error code and its short description in deterministic order.<code>accepts copied forms such asE2028,2028,diag:E2028, anderror[E2028].- Examples are syntax-highlighted when stdout is a color-capable TTY; piped
output,
NO_COLOR, andTERM=dumbremain plain text.
The command is backed by compiler-owned diagnostic metadata rather than scraped documentation. diagnostics remains the normative prose catalog for error-code meanings and policy.
silk-lsp publishes these stable codes in LSP diagnostics as well. Structured
resolve/type-check diagnostics include a silk error <code> help item, and
parse diagnostics publish E0001 with a silk error E0001 lookup hint in the
diagnostic data payload.
Error Codes#
The compiler assigns a stable code to each currently supported error kind.
Parsing#
E0001— unexpected token / invalid top-level ordering.
Import and Package Resolution#
E1001— unknown imported package.E1002— cyclic package imports.E1003— unknown imported file.E1004— cyclic file imports.E1005— duplicate exported symbol within a package.E1006— file imports require a module file path.
Type Checking#
E2001— type mismatch.- The primary message stays stable, but the diagnostic detail should explain the exact failed contract when available, for example:
in IntFlag.usage param fs: expected ..., found ...,while initializing binding count: expected ..., found ...,in assignment to queue.reader: expected ..., found ...,in return statement: expected ..., found ....E2002— language feature is not implemented yet.- The diagnostic detail should identify the exact rejected construct (statement / expression / declaration / type) and why it failed.
- The public wording should describe an unimplemented feature, not a vague "subset" category.
- Common examples of the required detail quality:
- field access on an optional value should explain that
opt.fieldmust be rewritten asopt?.fieldor preceded by an unwrap, yield <task_handle>;in statement position should explain that statementyieldis the send form and that receiving from a task handle requires value position (let x = yield h) oryield * h;for drain/forward.E2003— unknown imported name.E2004— duplicate imported name.E2005— invalid assignment.E2006— invalid borrow.E2007— invalidbreak.E2008— invalidcontinue.E2009— invalidreturn.E2010— missingreturn.E2011— opaque struct used by value.E2012— cannot instantiate opaque struct.E2013— cannot access fields on opaque struct.E2014— formal Silk declaration used in runtime expression.E2015— binding requires an initializer.E2016— generic form is not implemented yet (for example const parameters / const type arguments / genericimplmethods).E2017— builtinmap(K, V)type form was removed (usestd::map::{HashMap, TreeMap}instead).E2018— namespace import is not callable.E2019— duplicate default export in a module.E2020— invalidpanicstatement.E2021— unknown error type.E2022— error not declared in function signature.E2023— error-producing call must be handled withmatchor?.E2024— match scrutinee is not an error-producing call.E2025— match is missing an arm.E2026— typed error-handling match arm must end with a terminal statement.E2027— heap allocation is disabled (--noheap) and heap-backed allocation is rejected (newoutsidewith, libc allocatorext, capturing closures, and concurrency use that declares/formsTask(...)/Promise(...)handles; imported stdlib async declarations alone do not trigger it).E2028— unknown name.E2029— ambiguous implicit coercion.E2030—awaitrequires anasyncfunction.E2031—async { ... }/task { ... }requires anasyncfunction.E2032— ambiguous constructor call.E2033—awaitrequires a Promise operand.E2034— cannot copy a Task/Promise handle.E2035— Task/Promise handle used afterawait/yield *.E2036— cannot consume an outer Task/Promise handle inside a loop.E2037—task fnuses a non-task-safe type at a task boundary.E2038—?requires an error contract (-> T | ErrorType...).E2039—?requires a fallible call operand.E2040— propagated error is not declared in the function signature.E2041—constinitializer is not compile-time evaluable.E2042—pure fnmay not have a typed-error contract (|in return type).E2043—pure fnmay not containpanicstatements.E2044—pure fnmay not havemutparameters.E2045—pure fnmay not declare mutable locals (varorlet mut).E2046—pure fnmay not perform mutation via assignment.E2047—pure fnmay not allocate (new).E2048—pure fnmay not call impure functions.E2049—pure fnmay not be combined withtaskorasync.E2050— theories are not callable as runtime functions (use#theory Name(...);).E2051— module does not satisfy the declared interface (missing exported function).E2052— module does not satisfy the declared interface (signature mismatch).E2053— unknown re-export name.E2054— duplicate exported name.E2055— prototype implementation is missing required import of its prototype module.E2056— function expression may not have non-scalar&Tparameters.E2057— duplicate type alias name.E2058— type alias cycle.E2059— type alias kind mismatch.E2060— unknownextendsbase.E2061— invalidextendsbase.E2062— cyclicextendschain.E2063— derived struct redeclares an inherited field name.E2064— derived interface redeclares an inherited method name.E2065— opaque structs may not useextends.E2066— prototype and implementation signatures do not match.E2067— capturing closure is not allowed inpurecode.E2068— capturing closure uses a capture type that is not implemented yet.E2069— capturing closure may not capture a mutable binding yet.E2070—yieldrequires ataskcontext.E2071—yieldin value position requires a Task operand.E2072—yield *requires a Task operand.E2073—yieldas a statement requires an enclosing task function.yield <value>;is the send form.- Receiving from a
Task(T)handle is a value-position form:let x = yield h. E2074—await *requires a Promise-array operand.E2075— duplicate type name.E2076— generic type arguments must be fully specified at the use site (missing a required, non-default type argument).E2077— invalidregiondeclaration.E2078—withrequires a region binding.E2079— invalidwith ... fromregion slice.E2080— reserved (previously: indexing a slice cast fromu64required an explicit length).E2081— cast-length suffix requires au64/usizepointer operand and a slice/string target.E2082—const fnmay not betaskorasync.E2083—const fnmay not have a typed-error contract (|in return type).E2084—const fnparameter types must be compile-time value types.E2085—const fnresult type must be a compile-time value type.E2086—const fnmay not allocate (new).E2087—const fnmay not call a non-const fn.E2088—const fnmay not containpanicstatements.E2089— unsupported construct in aconst fnbody (outside the current const-eval subset).E2090—const fnmay be called only from compile-time contexts.E2091— generic function call type arguments could not be inferred at the call site.E2092— use of moved value.E2093—moverequires a local binding name.E2094— slice borrow escapes its lexical scope.E2095— reference borrow escapes its lexical scope.E2096— unknownusingtarget.E2097—usingalias conflicts with an existing name.E2098—usingtarget is ambiguous.E2099—usingcannot importconstructoryet.E2100—usingcannot import methods that require mutableSelfborrows yet.E2101—usingmethod reuse requires compatible struct layouts.E2102— cannot move value while it is borrowed.E2103— invalid regexp flags (unknown or duplicate).E2104— invalid regexp literal (pattern compile failed).E2105— method is private to itsimplblock (not visible from the call site).E2106— interface-required methods may not be declaredprivate.E2107— destructuring requires a non-opaque struct value.E2108— cannot destructure opaque struct.E2109— destructuring pattern does not match the struct type (wrong arity, unknown field, or duplicate binder/field).E2110— array destructuring requires an array/slice value.E2111— array destructuring pattern does not match the array type (wrong arity for fixed arrays, or duplicate binder).E2112— enum destructuring requires an enum value.E2113— enum destructuring pattern does not match the enum type (unknown variant or wrong arity).E2114—u128is not implemented yet in all compiler paths.E2115—f128is not implemented yet in all compiler paths.E2116— invalid inline assembly (inline asm failed to assemble, or uses unsupported features currently).E2117—let ... else { ... };requires theelseblock to end with a terminal statement.E2118— borrowed-view type may not appear in anasync fnresult.E2119— borrowed-view type may not cross anext/ unnamed C-facingexport fnboundary.E2120— local borrow may not remain live acrossawait.E2121— cannot mutate local storage while it is borrowed.E2122— borrowed control-flow expression is ambiguous.E2123— local borrow may not escape through an async call.E2124— type does not satisfy the declared interface (missing required method).E2125— type does not satisfy the declared interface (signature mismatch).E2126— interface method must omit an explicit receiver parameter; ordinary interface methods already have an implicit receiver and must not spellself: &Selfin the interface declaration.E2127— invalid atomic memory ordering; for example,loadmay not useRelease/AcqRel,storemay not useAcquire/AcqRel, andcompare_exchangefailure ordering may not useRelease/AcqRel.
Formal Silk Verification#
E3001— loop invariant may not hold.E3002— loop variant may be negative.E3003— loop variant may not decrease.E3004— postcondition may not hold.E3005— Formal Silk verification failed to initialize or encountered an unsupported construct. Unsupported verified-code diagnostics should name the exact construct, for example an optional-field, index, nested-field, or compound assignment target in a verified method.E3006— assertion or struct requirement may not hold (#assert, theory assertions, and struct#requirechecks). Struct requirement failures include the rejected predicate plus referenced construction/default field values, and direct verified field writes recheck struct requirements after the write.E3007— call precondition may not hold. Contracted function/method preconditions are checked at ordinary call sites as well as inside explicitly verified code.E3008— loop monovariant may not be monotonic.
Notes:
- When
silk build --debugorsilk test --debugis used, failed Formal Silk checks emit additional Z3 debug output and write an SMT-LIB2 reproduction script under.silk/z3/in the current working directory (or$SILK_WORK_DIR/z3).
Code Generation / Backend Lowering#
E4001— backend/target limitation prevented code generation for the requested program/output.E4002— code generation failed in the backend (unexpected backend error).
Notes:
- This error is reported when a program successfully parses and type-checks, but IR lowering or native code generation cannot yet handle a construct.
- The detail should identify the actual backend-stage blocker:
- rejected statement/expression/function shape,
- missing target/output support,
- or a specific collector/layout/codegen stage failure.
- The diagnostic detail names the rejected construct kind (statement / expression / function / declaration) and its surface form tag when available.
- Executable entrypoint shape failures should name the rejected form directly (for example ``unsupported executable entrypoint form: `async task fn main``` ) and say which executable entrypoint forms are currently supported.
- When executable lowering fails during runtime-support setup before ordinary
function-body lowering begins,
E4001should name the blocked runtime stage directly (for example ``unsupported executable runtime support:debug panic runtime support``` ) instead of falling back to a misleadingunsupported function: main`. - When lowering cannot isolate a narrower statement / expression site,
E4001falls back to the offending function or declaration collector stage and names that function / declaration directly. - For declaration-stage layout collection failures, the note should carry the
collector context and, when available, the rejected field or payload type
shape so users do not have to infer it from a generic carrier such as
Result. - GPU execution-boundary violations use
E4001with a precise message. A host function cannot call anattr(device=gpu)function or a public/compiler-owned portable GPU device operation directly, and a GPU function cannot call an impure host function. A GPU function may call another GPU function or a pure host function; aconst fnremains compile-time-only and is not a runtime GPU helper. Raw__silk_amdgpu_*declarations remain accepted only as input to the documented standalone AMDGPU source-intrinsic object path, whose target-specific selector validates them. - GPU launch-block violations also use
E4001and identify the rejected contract directly: malformedgrid/workspaceheaders and non-call bodies are parse diagnostics; semantic diagnostics distinguish unknown targets, known non-GPU functions, non-launchable GPU helper signatures, dependency-package targets outside the executable bundle, argument mismatch, use from device code, and use without an available standard library. A launch-block diagnostic must point at the target call or rejected option rather than the compiler-generatedstd::gpu::launch_and_synchronizecall. - A mixed executable requested with
--gpu-targetrequires at least one launchable root-packageattr(device=gpu)entry. If the package contains only non-entry GPU helpers, the diagnostic must say that no launchable entry exists; it must not incorrectly claim that the package has no GPU function.
Tooling Integration Notes#
silk-lspshould map the compiler’s primary source span to the LSP diagnostic range directly.silk-lspshould preserve structured compiler guidance in the published LSP payload:- keep the primary
messageshort and stable, - surface compiler
detail,notes, andhelpsas structured diagnostic metadata, - and avoid collapsing all follow-up guidance into one opaque message blob when the protocol surface can carry structured fields.
- When the compiler grows multi-span diagnostics (labels and secondary spans), the LSP implementation must be updated to surface them.
Source repository · Edit this page · View Markdown