Browse docs

Start here

examplesGetting started with Flowdocumentation

Design

FoundationsLanguage architecturePhilosophy

Language specification

Program checkingConcurrencyDataEffectsResults, Tool problems, and faultsGrammarHistoryModules and importsLanguage specificationEvaluationStandard libraryToolsTypes

Runtime

Runtime architectureThe host boundaryDiagnosticsRunning a programThe history format

Guides

Writing programs that reach checkpointsImplementing Tools with a toolkitLoops that never returnRecursive delegationSharing types between Tool modulesHarnesses over tool registries

Diagnostics

This specification owns the shape of a diagnostic: what a diagnostic is, how it is identified and located, what a fix is, how it is rendered, and the rules that keep a diagnostic from becoming a second, informal semantics.

Diagnostics are a tooling contract, not language semantics. What decides the verdicts a diagnostic reports lives elsewhere: checking.md owns what a checked program proves and which programs are refused; errors.md owns faults and how a run ends; history.md owns replay verdicts. Within the runtime track, execution.md owns run states, refusals to start and exit codes, trace.md owns storage failures, and abi.md owns how a failure crosses the Tool protocol.

Flow source is often repaired through a program as well as by hand, so a diagnostic is designed for a reader that acts on it without parsing prose.

Errors only

Every diagnostic the checker reports is an error, and an error refuses the program. There are no warnings, no advice and no severities to learn to ignore: anything worth reporting blocks the build.

  • Style is a separate linter's. A linter is a separate tool. Its findings are not diagnostics in this sense, never refuse a program, and never change what a program means.
  • Command output is not a diagnostic. What a command reports beside its errors, such as where checkpoints can happen or which @tool declarations a program can reach, is ordinary output of that command.
  • One pass. The checker reports every independent error at once. An error caused only by another reported error is not reported again.

A diagnostic is data

A diagnostic is a value. Text is rendered from it; the text is never its source of truth.

{
  "code": "unhandled-value",
  "message": "this statement produces Result<Unit, ToolProblem<SendError>>, not Unit",
  "at": { "file": "main.flow", "line": 42, "col": 5, "end_col": 23 },
  "notes": [
    { "at": { "file": "mail.flow", "line": 3 }, "message": "send returns a Result" }
  ],
  "fixes": [
    { "title": "discard it explicitly", "safe": true,
      "edits": [{ "at": { "line": 42, "col": 5 }, "insert": "let _ = " }] },
    { "title": "pass the error on", "safe": false,
      "edits": [{ "at": { "line": 42, "col": 23 }, "insert": "?" }] }
  ]
}
Field Holds
code the stable name of the rule that refused the program
message rendered text naming the offending type or value
at the place that needs changing
notes other places that matter, each with a short message
fixes zero or more exact repairs

Every fact a consumer needs is a field or is named in one: the types, names and values that made the judgment appear in the message, and the places appear as locations. A diagnostic stays interpretable in another process, and by a later reader, without the toolchain that produced it.

The Diagnostic type a Flow run as a Tool returns from check (execution.md) has this shape, declared as a Flow record in std's runner module.

Codes

A code is a lower-case name with hyphens, such as unhandled-value or not-data: words of ASCII letters and digits, each starting with a letter, joined by single hyphens. It names the rule, not the place or the party.

  • Names, never numbers. A code is read by people and by programs, and a name says what went wrong.
  • Stable. A code means one thing for as long as it exists. Its message may be rewritten freely; its meaning may not drift.
  • Never reused. A retired code is never given to a different rule.
  • Versioned with the language. Codes belong to the language version (modules.md). A code never changes meaning between versions; a new version may add codes.

No code is published before the first public release (runtime architecture). Serves foundations: Stable meaning.

Each code has an explanation, with examples and the usual repairs, that tooling looks up by code. The explanation is presentation keyed by the code, not part of the diagnostic value.

Locations

A location is evidence: it lets a reader find what is judged.

  • at names a file and a line, and may name col, end_line and end_col. Lines and columns count from 1; a column counts code points (data.md). An extent ends at end_col, the column just past its last character, on end_line, which is left out when it is line; a location that covers no text names no end.
  • The primary at is the place that needs changing: the line that uses the value, not only where the value came from. A note points to the other place that matters, such as the signature that caused the problem or the declaration it conflicts with.
  • An import that fails to resolve is located at its use line. A cycle that is refused carries every use line on it as notes. A module imported by several others is judged once and reported once.

Diagnostics about a run rather than its source locate it by the identities the language already defines: a call id, a task id, a checkpoint, a fact's position in history (history.md). A storage problem may add a byte offset or a file path as detail (trace.md); that detail never stands in for a fact's position. A diagnostic produced during replay reports the position where re-execution reached the problem, together with the recorded fact it contradicts.

Such an at names the run and may name fact, call, task and checkpoint, with a storage problem's detail as path and offset. One at may hold a place in source and a place in a run together, as a fault's does: the operation that faulted, and the End that records it.

No location ever holds wall-clock time, elapsed time, thread, worker or process ids, memory addresses, or an implementation's request ids. A runtime may put any of these in its own logs.

Fixes

A fix is a list of exact text edits with a title.

  • An edit has an at and the text to insert. When at covers a range, the edit replaces that range; an empty insert over a range deletes it. An edit's at with no file is in the diagnostic's file.
  • safe: true means applying the fix cannot change what the program means, so a tool may apply it without asking.
  • safe: false means the fix is one reasonable choice among others, and someone must choose.
  • A fix's edits are applied together or not at all. Applying a fix never depends on applying another.
  • No two edits of one fix touch the same text or start at the same place, so their order never decides the result. A fix with such edits is refused whole.

A diagnostic offers a fix only when it can state the exact edits. A repair it cannot state exactly is described in the message or the explanation instead.

Rendering

A runtime renders a diagnostic from its fields, and everything about rendering may change without changing what the diagnostic means: wording, ordering, punctuation, color, grouping, terminal width and translation.

  • Consumers key on the code and the fields, never on message text. A tool that parses prose depends on presentation, and this specification does not protect it.
  • A message names the offending type or value and the line that needs it. "Type mismatch" alone is not a message.
  • Rendering has a bound. When there are very many errors, a renderer shows a bounded number and says how many remain. Hiding the count is misreporting.
  • The rendered form is never the record. History holds facts; diagnostics describe judgments. A fact that appeared only in a rendered message was never recorded.

Reports that are not checker errors

The same shape carries everything the toolchain reports to a person or a tool. These reports differ in what has already happened, and a runtime must not collapse them:

Report Owner What has happened
a checker error checking.md the program is refused; no program exists to run
a refusal to start execution.md a binding is missing or the input does not decode; no run exists
a fault errors.md the run ended as faulted, with its location recorded
a replay verdict history.md replay reported Incomplete, or Diverged at a fact
a storage failure trace.md stored history is corrupt, truncated or unreachable
a host outcome execution.md the host halted the run or refused a call under its own policy

A storage failure is a statement about the store, never about the run, and is never reported as divergence: a disk fault is not a program contradicting itself. A host outcome names the host rule that produced it and is never reported as a language verdict. A halted run did not fault. Serves foundations: Honest withholding, Honest uncertainty.

What a diagnostic may not do

  • Change what a program means, whether it is accepted, its program identity, its history, or replay.
  • Become a value of the program it judges. A program never sees its own diagnostics; an outer program sees another program's diagnostics only as the reply to an ordinary Tool call.
  • Waive, downgrade or stand in for a check.
  • Assert what the record does not carry: that an implementation told the truth, that an outside effect did or did not happen, or that an approval was genuine.
  • Promise a retry. Saying a run was refused and nothing was recorded says a new attempt is possible, never that the runtime will make one.
  • Blame a party because of its code or its kind of report.