Diagnostics and silk check.
When the compiler rejects your code, it prints one line per problem: where it is, a stable code, and a message. silk check gets you those lines fastest, because it analyzes the package without generating code. This page shows how to read a diagnostic, what the code families mean, and how to get diagnostics as JSON for editors and CI. The full list of codes is in the Appendix, under Diagnostic codes.
Check a package#
silk checkWith no errors, you get a heading, one row per compiler phase, and a final line:
Checking hello (x86_64-unknown-linux-gnu)
load 143 modules, 1 in package 0.06s
check 2 modules 0.14s
borrow 123 bodies 0.00s
Finished hello in 0.22sThe heading names the package and the target. load reads and parses the modules your package needs: 143 here, the std lib modules included, and 1 of them in your package. check analyzes the modules that matter to the program, and borrow checks ownership and borrowing in each function body. The times are wall-clock times and vary from run to run. Progress output below explains how these lines change in a terminal and how to turn them off.
silk build, silk run and silk test report the same diagnostics, in the same form, before they stop. silk check is the quick way to see them. It exits 0 when every target checks, and 1 when any target has an error.
Read a diagnostic#
This program passes an i64 where an i32 is expected:
fn half(value: i32) -> i32 {
return value / 2
}
pub fn main() -> i32 {
let count: i64 = 4
return half(count)
}Checking hello (x86_64-unknown-linux-gnu)
load 135 modules, 1 in package 0.01s
check failed: 2 modules, 1 error 0.07s
[x86_64-unknown-linux-gnu]
src/main.silk:7:15: error[SEM0012] Expected i32 but received i64
Failed hello in 0.10sRead it piece by piece:
| Part | Here | Meaning |
|---|---|---|
| Target header | [x86_64-unknown-linux-gnu] | The target being checked. The diagnostics below it belong to that target |
| Location | src/main.silk:7:15 | File, line and column, both counted from 1. Column 15 is the start of count. The file is relative to the package folder, or to the workspace root for a workspace member, so it reads the same wherever you run the command. A file outside that folder, such as a std lib module, is printed in full |
| Severity | error | Every diagnostic is an error today; there are no warnings |
| Code | SEM0012 | A stable identifier. Search for it in the Appendix |
| Message | Expected i32 but received i64 | What went wrong, with the names and types filled in |
Some diagnostics also point at a second place that matters. Each such place follows the diagnostic on its own indented note: line. Declaring half twice names the first declaration:
fn half(value: i32) -> i32 {
return value / 2
}
fn half(value: i32) -> i32 {
return value / 2
}
pub fn main() -> i32 {
return half(8)
}Checking hello (x86_64-unknown-linux-gnu)
load 135 modules, 1 in package 0.02s
check failed: 2 modules, 2 errors 0.06s
[x86_64-unknown-linux-gnu]
src/main.silk:5:4: error[SEM0003] Duplicate declaration name half
note: src/main.silk:1:4: related location
src/main.silk:5:4: error[SEM0016] Multiple bindings claim half; rename one with `as`, as in `import a.b as other` or `import a.b { half as other }`
note: src/main.silk:1:4: related location
Failed hello in 0.10sThe second error follows from the first: fix the duplicate and it goes away.
A syntax error is reported once, where the source is damaged, and the parser carries on with the rest of the file. Here the parameter list of half is missing its ):
fn half(value: i32 -> i32 {
return value / 2
}
pub fn main() -> i32 {
let total: i32 = "8"
return half(total)
}Checking hello (x86_64-unknown-linux-gnu)
load 135 modules, 1 in package 0.02s
check failed: 2 modules, 2 errors 0.11s
[x86_64-unknown-linux-gnu]
src/main.silk:1:20: error[PAR0001] Expected `)`
src/main.silk:6:20: error[SEM0037] Assignment expected i32 but received string<'static>
Failed hello in 0.17sThe missing ) gets one diagnostic, not one for each token the parser had to skip, and the type error in main is still found. When a declaration is damaged beyond repair, the parser skips to the next line that starts a declaration.
When you check several targets, each target with errors gets its own header, and the same mistake usually appears under each:
silk check --target host --target wasm32-unknown-unknownChecking hello (x86_64-unknown-linux-gnu)
load 135 modules, 1 in package 0.06s
check failed: 2 modules, 1 error 0.09s
[x86_64-unknown-linux-gnu]
src/main.silk:7:15: error[SEM0012] Expected i32 but received i64
Failed hello in 0.17s
Checking hello (wasm32-unknown-unknown)
load 126 modules, 1 in package 0.02s
check failed: 2 modules, 1 error 0.03s
[wasm32-unknown-unknown]
src/main.silk:7:15: error[SEM0012] Expected i32 but received i64
Failed hello in 0.07sThe targets are checked one after the other, and each one prints its own heading, rows and final line. A failed phase says failed: and counts the errors it reported. The diagnostics follow the rows, and the borrow phase does not run once check has failed.
When the location is not your file#
Most diagnostics point into your own source. Two kinds point elsewhere:
At your call, for a std lib check. Some std lib functions check their arguments while your code compiles. When a check fails, the diagnostic is located at your call, with the message the library gave. A misspelled placeholder in a log template is SEM0177 at the
Effect.logcall:template field {versoin} not found; available visible fields: version. Chapter 1 of the book walks through this case. A diagnostic can still point into the std lib, with the install shown as<silk>, when the std lib's own code is what the compiler cannot handle.Into a dependency. An error in a package you depend on is located in that package's file.
A message is sometimes a general description rather than a specific one, such as A `run` whose failures the enclosing signature does not declare. That is the code's catalog meaning, printed when the compiler could not fill in the details. It often follows an earlier error; fix the first diagnostic in the list and check again.
Code families#
The letters of a code say which part of the compiler found the problem. A syntax error does not stop analysis of the rest of the file, so a PAR error can be followed by SEM errors that it caused.
| Family | Phase | Typical causes | Example |
|---|---|---|---|
LEX | Lexical: reading characters into tokens | A number like 0x with no digits, a misplaced digit separator, an unterminated literal | LEX0004 |
PAR | Parser: building the syntax tree | A missing token, an unexpected token such as a ; after a statement, a habit from another language | error[PAR0002] Unexpected `;`; expected valid syntax |
MOD | Modules: resolving imports | An import of a module that does not exist, or that its package does not export | error[MOD0001] Unknown module silk.vector |
SEM | Semantic analysis | Unknown names, type mismatches, wrong arguments, effect rows, services, generics, static evaluation | error[SEM0012] Expected i32 but received i64 |
OWN | Ownership and borrowing | Use after move, conflicting borrows, a borrow that outlives its value | error[OWN0001] data was moved and cannot be used again |
Some PAR codes catch habits from other languages and name the Silk spelling. PAR0010 covers x++, c ? a : b, << and >>, and tuple literals with field names; PAR0011 covers a with clause after a declaration header, which Silk replaced by @ directives before it:
src/main.silk:1:18: error[PAR0011] Silk has no `with` clauses; write `@inline` on the line before the declarationSEM is by far the largest family. The ownership rules behind OWN codes are in Ownership and moves and Borrowing in the language reference.
silk check and silk build report a lexical mistake at the bad token and skip parse errors inside it:
src/main.silk:2:16: error[LEX0004] Base-16 integer literal without digitssilk format refuses such a file and lists its codes, parse errors included: cannot format damaged source (LEX0004, PAR0002).
Lines that are not coded diagnostics#
A few other lines can appear around diagnostics:
| Line | Meaning |
|---|---|
path:line:column: unsupported: this construct is not supported by the native compiler yet | The code may be valid, but this compiler cannot handle it yet. The command exits 2 |
SILK_GAP ... | A notice about a known gap in the native compiler, such as the test result cache. Informational unless the command stops |
silk: ... | The command itself failed: no manifest, an unknown package, an invalid option |
ARTIFACT-001 and similar | The build setup was refused, for example a target with no runtime or a missing native tool. The line names the subject and how to fix it |
Progress output#
The heading, the phase rows and the final line are progress output. silk check, silk build and silk run print them on standard error, so standard output stays free for a program's own output. silk run prints them for its build, then starts the program after the Finished line. silk test prints no progress rows.
| Row | Meaning |
|---|---|
load | Modules read and parsed, and how many of them are in your package |
check | Modules analyzed |
borrow | Function bodies checked for ownership and borrowing |
codegen | Functions turned into machine code (silk build and silk run) |
link | The native linker run (silk build and silk run) |
silk build and silk run end with Finished and the path of the program they built, relative to the current directory. When the program is outside it, as when you build inside a workspace member, the path is absolute; it never starts with ../. A command that stops on an error ends with Failed <package> in <time>.
A phase that fails says so in its row: check failed: 2 modules, 1 error. A row with nothing more to say reads just failed, as the link row does when Clang is missing:
Compiling hello (x86_64-unknown-linux-gnu, debug)
load 143 modules, 1 in package 0.06s
check 1 module 0.14s
codegen 689 functions 0.13s
borrow 229 bodies 0.01s
silk: cannot find clang on PATH; set SILK_CLANG or pass --clang the path of the Clang executable
link failed 0.00s
Failed hello in 0.35sA build that is refused before any module is checked, such as an exports entry that names no module, still gets a load row, then the message:
Checking text-utils (x86_64-unknown-linux-gnu)
load failed: 1 error 0.00s
Invalid Silk project manifest /home/you/text-utils/silk.toml:6: exports of text-utils@0.1.0: Stores is not a module of the package
Failed text-utils in 0.01sHow the rows look depends on where standard error goes:
Plain, when standard error is a file, a pipe or a CI log: each row is printed once, when its phase ends. All the examples in these docs are plain output.
Live, when standard error is a terminal: each running phase is redrawn in place with a spinner, a bar, a count and the elapsed time, and a finished phase gets a check mark or a cross. Colors are used unless the
NO_COLORenvironment variable is set to a non-empty value. The width followsCOLUMNSand defaults to 80. A terminal withTERM=dumbgets plain output.
When one command covers several targets or workspace packages, live output keeps one line per finished package, and the running one is labeled like [1/2] pricing. A command that selects workspace packages ends with a total, such as Finished 2 packages in 1.92s or Failed 1 of 2 packages in 0.84s.
Choose the style with --progress, or with the SILK_PROGRESS environment variable when you cannot change the command line:
| Value | Effect |
|---|---|
auto | Live in a terminal, plain otherwise. The default |
live | Always live |
plain | Always plain |
never | No progress output at all. Diagnostics still print |
The option wins over the variable, and an invalid SILK_PROGRESS value is ignored. --quiet (or -q) keeps only the final line and the diagnostics:
silk check --quietFinished hello in 0.15s--json and --replay print no progress output at all.
Find slow modules#
silk check --timings and silk build --timings add the five slowest items of each phase before the final line:
silk check --timingsChecking hello (x86_64-unknown-linux-gnu)
load 143 modules, 1 in package 0.02s
check 2 modules 0.09s
borrow 104 bodies 0.00s
Slowest modules to check
46.3ms silk/platform/native/start.silk
17.1ms silk/text/format.silk
5.21ms silk/platform/native/host_input.silk
2.27ms silk/concurrency/execution.silk
1.92ms silk/core/effect.silk
Slowest bodies to borrow-check
1.01ms silk/text/format.writeFillGroup
0.25ms silk/platform/native/start.failed
0.25ms silk/platform/native/host_input.copyArguments
0.23ms silk/platform/native/start.silkNativeFault
0.22ms silk/text/format.signedDisplay
Finished hello in 0.13ssilk build --timings also lists the slowest functions to emit. Std lib modules appear in the lists because they are checked with your program. A body is named by its module and its name as written: silk/text/format.writeFillGroup for a function, silk/collections/list.List<T>.appendBytes for a method. A body inside a conformance shows the conformance in parentheses, such as main.(Equal for Point).equals. A header written over several lines is shown on one line, with its spacing and trailing commas dropped. --quiet turns the lists off.
Replay edits#
silk check --replay <script> measures the incremental checker, the part of the compiler that rechecks only what an edit affects. It is a tool for working on the compiler and for reporting slow rechecks, not for checking your code: it prints counters, not diagnostics. The script is a TOML file of [[edit]] tables, applied in memory one after the other. Your files are not changed.
[[edit]]
kind = "trivia"
module = "main.silk"
find = "pub effect fn main"
replace = "// entry point\npub effect fn main"
[[edit]]
kind = "body"
module = "main.silk"
find = "Hello, world!"
replace = "Hello, Silk!"| Key | Meaning |
|---|---|
kind | What the edit changes: trivia, body, header-same-type, header-new-type, add, delete or rename |
module | The file, relative to the package's source folder |
find, replace | The text to replace. find must occur exactly once in the file |
to | The new path, for a rename |
silk check --replay edits.tomlsilk-replay step=0 kind=initial module=- demands=18480 hits=15897 units-run=2772 marked-outdated=0 fingerprinted=1869 unchanged=0 verified=0 graph-units=2862 graph-bytes=2015744 answers=2768 mir-functions=202 rejections=0 update-us=0 check-us=120670
silk-replay step=1 kind=trivia module=main.silk demands=162 hits=551 units-run=3 marked-outdated=5 fingerprinted=0 unchanged=0 verified=0 graph-units=2863 graph-bytes=2015744 answers=2768 mir-functions=202 rejections=0 update-us=54 check-us=1987
silk-replay step=2 kind=body module=main.silk demands=167 hits=554 units-run=5 marked-outdated=6 fingerprinted=1 unchanged=1 verified=0 graph-units=2863 graph-bytes=2015744 answers=2768 mir-functions=202 rejections=0 update-us=33 check-us=1534Step 0 is the first full check. Each later step is one edit: units-run counts the pieces of work the checker redid, hits the ones it reused, and check-us the time in microseconds. The lines go to standard output. Add --differential to also check each step from scratch and compare: each step gets a silk-replay-differential step=N rejections=0 differences=0 line, and the command exits 1 when the two disagree. A bad script exits 2 with a silk: line, such as silk: replay edit 2 (main.silk): its find text does not occur exactly once.
Diagnostics as JSON#
silk check --json writes each diagnostic as one JSON object on its own line, on standard error, in place of the human line. The heading, phase rows and final line are left out, so standard error holds only JSON. The exit status is the same.
silk check --json{"target":"x86_64-unknown-linux-gnu","code":"SEM0012","severity":"error","path":"/home/you/hello/src/main.silk","line":7,"column":15,"start":109,"end":114,"message":"Expected i32 but received i64","notes":[],"related":[]}| Field | Type | Meaning |
|---|---|---|
target | string | The target triple being checked |
code | string | The stable code, such as SEM0012 |
severity | string | Always "error" today |
path | string | Absolute path of the file |
line, column | number | 1-based position of the start |
start, end | number | Byte offsets of the span in the file, end exclusive |
message | string | The same text as the human line |
notes | array of strings | Reserved; empty today |
related | array of objects | Other places that matter, each with path, line, column, start, end and label |
A duplicate declaration, for example, points at the second half and lists the first one as related:
{"target":"x86_64-unknown-linux-gnu","code":"SEM0003","severity":"error","path":"/home/you/hello/src/main.silk","line":5,"column":4,"start":54,"end":58,"message":"Duplicate declaration name half","notes":[],"related":[{"path":"/home/you/hello/src/main.silk","line":1,"column":4,"start":3,"end":7,"label":null}]}The human format prints the same related location as the note: line shown in Read a diagnostic.
For CI, silk check on its own is enough: it exits 1 on any error. Use --json when a tool needs to place the errors, such as an editor or a review bot. Read standard error line by line and parse the lines that start with {.
Summary#
A diagnostic reads
path:line:column: error[CODE] message, under a[target]header, followed by an indentednote:line for each related location.The code family tells you the phase:
LEX,PAR,MOD,SEMorOWN.A std lib check that rejects your arguments is reported at your call. A location inside the std lib is rare and usually an
unsupportedline.Progress rows go to standard error.
--progress,SILK_PROGRESSand--quietcontrol them, and--timingslists the slowest modules.silk check --jsonwrites one JSON object per diagnostic on standard error, with byte offsets and related locations.Fix the first diagnostic first; later ones are often consequences.