Documentation pages
Diagnostics
This page describes what SultanC prints when it reports a problem: the terminal layout, the JSON schema, the public codes and their explanations, suggestions and fixes, and the guarantees each of them keeps. How diagnostics are built inside the compiler is described in iSpeak, and a comparison with other compilers is in Diagnostics compared.
Selecting output
| Option | Effect |
|---|---|
--lang=ar (default), --lang=en, --lang=auto |
language of every message |
--diagnostic-format=human (default), short, json |
terminal layout, one line per diagnostic, or JSON lines |
| `--color=always | never |
sultanc explain CODE, sultanc --explain CODE |
the long explanation of a public code |
Terminal layout
error[SULTANC-NAME-UNKNOWN]: unknown name: `cuont` — The required name was not found during resolution.
--> main.sn:6:18..6:23
|
6 | let y: i32 = cuont + 1;
| ^^^^^
|
= help: a name with a similar spelling is visible here — The suggested replacement is `count`.
- The first line is the severity, the public code in brackets and the message: a headline and one sentence for each fact.
-->gives the file, the line and the column of the start, and the end when the span has one. Lines and columns are 1-based. Columns count Unicode characters (scalar values).- The caret line counts extended grapheme clusters (UAX #29), so a letter with its harakat takes one caret. A tab is copied as a tab.
- A related location (
:::) has its own snippet and label. It can be in another file. = note:and= help:lines follow.- In Arabic messages each quoted fact is wrapped in Unicode first-strong isolates (U+2068 to U+2069), so a left-to-right fact keeps its order inside the sentence. JSON carries no isolates.
Safe snippets
The source line of a snippet is shown as written, except for characters that would change how the line is displayed or act on the terminal:
- bidirectional embedding, override and isolate controls (U+202A to U+202E, U+2066 to U+2069) are
shown as
\u{202E}. The lexer rejects them in source anyway (SULTANC-LEX-INVALID-TOKEN), because they can make code display in another order than it compiles (CVE-2021-42574); - other control characters (C0 except tab, DEL, C1) are shown as
\u{001B}; - a byte that is not part of a UTF-8 character is shown as
\xFF.
The caret stands under the escape.
JSON
--diagnostic-format=json writes one JSON object per line, schema 3. Fields:
| Field | Meaning |
|---|---|
schema, language |
3, and en or ar |
id |
the public code, for example SULTANC-NAME-UNKNOWN. It does not depend on the language |
kind |
the machine kind, for example unknown-name |
severity |
error, warning or note |
category |
user, environment, internal or unimplemented |
message |
the full message in language |
primary_span, primary_role, primary_label |
the main location |
arguments |
typed facts: {"key", "type", "value"} (see iSpeak) |
related_spans |
{"role", "label", "span"} |
dependencies |
spans of earlier errors this one follows from |
notes, help |
{"message", "arguments"} |
fixes |
{"span", "replacement", "message", "applicability", "key", "edits"}; each edit is {"span", "replacement", "range_utf16"} |
trace, trace_count |
detailed trace entries |
A span is {"file", "source_id", "start_byte", "end_byte", "line", "column", "end_line", "end_column", "column_utf16", "end_column_utf16", "snippet"}. Bytes are offsets into the file.
line, column and end_column are 1-based and count characters. column_utf16 and
end_column_utf16 are the same columns counted in UTF-16 code units (1-based), as LSP positions are.
An LSP client subtracts 1. For a span built from its source they are exact at both ends, on any line;
otherwise they are derived from the shown line, and end_column_utf16 is null when the span ends on
another line. They are null when the line is not UTF-8. C locations have source_id 4294967295 and
the C file name.
A fix's key is the stable machine kind of the suggestion (insert-semicolon, import-module,
declare-mutable, ...; null for a key without one). Each edit's range_utf16 is an LSP Range,
{"start": {"line", "character"}, "end": {"line", "character"}}, 0-based, or null when the source
was not available. The edits of one fix apply together and never overlap.
Applicability of a fix:
| Value | Meaning |
|---|---|
machine-applicable |
the edit is certainly what was meant; a tool may apply it |
maybe-incorrect |
the edit is probably right, but a person should look (spelling suggestions) |
has-placeholders |
the edit contains text that must be filled in |
unspecified |
no claim |
The schema only grows. A new field is added; an existing field keeps its name, type and meaning.
Public codes
Every diagnostic has one public code. Codes are descriptive (SULTANC-<AREA>-<WHAT>) and never
reused or renumbered. tests/{ar,en}/ispeak/check_public_codes.sn pins every code and machine kind in
registry order: renaming, removing or reordering one fails that test. A new code is added at the end of
the registry and of both lists in that test, with its Arabic and English words, its explanation group
and its template decision.
Suggestions
A suggestion is given only with evidence:
- A similar name. For an unknown value name, the compiler compares its canonical spelling (the
identity the resolver compares, after identifier normalization) with the names visible from the
same scope in the same namespace: locals from the innermost scope outward, the module's
declarations, compiler primitives and the public names of whole-module imports. The distance is the
number of single-character insertions, deletions, substitutions and swaps of two neighbours. It must
be at most a third of the length, and names shorter than three characters get none. On equal
distance the innermost, earliest name wins. The candidate is resolved again from the same scope
before it is offered, and a local is not offered inside its own declaration statement. It comes as a
help line and a
maybe-incorrectfix. - A missing import. When a standard library module that the compilation loaded declares the
name public, a help line names that module, and a fix inserts its
importafter the file's last import (or before its first code line), in the file's own keywords (importorاستورد). The fix ismachine-applicablewhen one module declares the name, elsemaybe-incorrect. Modules that the compilation did not load are not searched. mutable. A write to aletwithoutmutable: a help and amachine-applicablefix that insertsmutable(متغير) before the declared Type.&and*. When a reference to the value's Type is expected and the value is a name or a field, amaybe-incorrectfix borrows it (&, or&mutable). When the value is a reference to the expected Type, a fix reads through it (*):machine-applicablefor a number, boolean or character, elsemaybe-incorrect.as. When two numeric Types are joined by an explicit conversion, a fix wraps the value as(value as T), with the Type in the file's surface. It ismachine-applicablewhen every value of the found Type is a value of the expected one (a wider integer that keeps the sign, or a wider signed integer), elsemaybe-incorrect.- Reserved words. A keyword where a name is expected (a field, a parameter, a type parameter) gets a help to choose another name.
Each fix is checked against the source bytes before it is offered. Unused names have no warning yet, so they have no fix.
Tools that import the compiler (the LSP server) read the same data as the JSON from
compiler.diagnostics.suggestions: اقتراحات_تشخيص(&diagnostic, language) returns each suggestion
(اقتراح_تشخيص: key, machine kind, words, applicability, edits) with every edit as byte offsets and
LSP positions (تعديل_اقتراح_تشخيص); اقتراحات_آلية_لتشخيص keeps the machine-applicable ones;
طبق_اقتراحا_على_نص applies one to the file text, or gives none when an edit is out of range or two
overlap. tests/{en,ar}/ispeak/check_suggestion_roundtrip.sn applies each machine-applicable fix and
compiles the result.
Several errors
Unknown names are reported for the whole module, each with its own evidence. The first is the error
the compiler reported before. When a later error of another kind follows an unknown name, it is not
reported: it may come from the unbound name. Ownership and borrowing errors (SSF) are all reported.
The lexer, the parser and the type checker still stop at their first error. A collection holds at
most 100 errors; warnings are never dropped. A diagnostic that names the source failure it depends on
(dependencies) is dropped when that failure was reported.
C diagnostics
C diagnostics of C imports (SULTANC-C-*) use the same layout and JSON. The location is the C file,
line and column. Columns are derived from the C line, and an #include chain is shown as
included-from related spans. Warnings of a C import are shown even when the compilation succeeds.
Internal errors
A compiler failure that is not the program's fault has the category internal and a code
SULTANC-ICE-* or SULTANC-INTERNAL-*. Its message names the compiler phase. Its help asks you to
rerun with JSON diagnostics and report the bug. A runtime failure of the program itself is not a
compiler diagnostic: see Errors.