SultanC
  1. Home
  2. Documentation
  3. Reference

Diagnostics

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-incorrect fix.
  • 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 import after the file's last import (or before its first code line), in the file's own keywords (import or استورد). The fix is machine-applicable when one module declares the name, else maybe-incorrect. Modules that the compilation did not load are not searched.
  • mutable. A write to a let without mutable: a help and a machine-applicable fix that inserts mutable (متغير ) before the declared Type.
  • & and *. When a reference to the value's Type is expected and the value is a name or a field, a maybe-incorrect fix borrows it (&, or &mutable). When the value is a reference to the expected Type, a fix reads through it (*): machine-applicable for a number, boolean or character, else maybe-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 is machine-applicable when 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), else maybe-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.

See also