Typhon’s diagnostics are rendered through miette — the same library used by oxc, ty, Pyrefly. Every diagnostic has a stable code, a severity, a source span, and a help message. This page explains the format; subsequent pages enumerate each diagnostic group.
Anatomy of a diagnostic
error[tyc::nullable_use]: cannot pass `str | None` where `str` is required
┌─ src/main.ty:9:11
│
9 │ greet(found)
│ ^^^^^ check this is not `None` before passing it
│
= help: narrow with `if found is not None:` or use `guard found = ... else: ...`
Stable code. Every diagnostic has one; search by it.
cannot pass ...
Human message describing the problem.
┌─ src/main.ty:9:11
Primary source span: file, line, column.
^^^^^ underline
The exact substring at fault.
= help: ...
Suggested fix.
= url: ...
Catalog page deep-link, miette url(...) clause. Click in any terminal that linkifies URLs, or run tyc explain <code> to get the same content offline.
Severity
Severity
Exit code impact
Typical cause
error
Sets exit to 2.
Type mismatch, missing annotation, broken syntax.
warning
No exit code change.
Unused import (with unused-import = "warn"), async def with no await, methods in class body.
advice
No exit code change.
Missed optimisation hints, e.g. auto_gather_missed.
note
No exit code change.
Informational; rare.
[strictness] in typhon.toml controls some severities — unused-import = "error" and methods-in-class-body = "error" both promote their respective warnings to errors.
Stable codes
Every diagnostic has a tyc::* code, and every code tyc explain --list prints has a section on one of the pages below, grouped by what triggers them (a test in tyc/crates/tyc/tests/shipped_docs.rs keeps it that way). The cards after the first eleven are release notes, not an index:
Added in v0.1.6 — orphan_py_import (relative .py outside src/), contains_secret_literal (credential-named comptime literal), invalid_config_value (unknown [emit] class-default / etc.), main_not_called (script defines main() but never calls it).
Phase 5.5 Arity Safety
Added in v0.2.0 — tyc::arg_count now fires on auto-generated class constructors and impl methods, including cross-module via from foo import Cls and import foo as f; f.Cls(…). New tyc::missing_field_init post-construction audit catches X.__new__(X) bypass patterns when required fields escape uninitialised.
Phase 6 Effects & v0.8.0 Lints
Added in v0.3.0 — blocking_in_async (sync I/O inside async def), resource_not_managed (open() / socket() / sqlite3.connect() outside with), div_by_zero_literal (constant zero divisor), newtype_violation (wrong-typed arg to a newtype constructor), unsafe_value_leak (Unsafe[T] escaping a concrete-return function), extend_builtin (rejected built-in extension shape), stdlib_module_shadow (v0.6.0 — top-level .ty shadowing Python stdlib), field_default_ordering (v0.7.0). Added in v0.8.0 — pattern_shadows_outer firing site, newtype_invalid_base (newtype Foo = "literal"), and three lint warnings: empty_collection_no_annotation (let xs = []), typing_alias_in_annotation (bare List[…] / Optional[…] / Dict[…] / Union[…]), contains_secret_literal (inline string literals named *_(TOKEN|SECRET|PASSWORD|PWD|KEY|API_KEY)).
v0.9.0 Stress-Test Cleanup
Added in v0.9.0 — tyc::freeze_not_freezable validates freeze let X = <expr> RHS at check time (was a runtime TypeError before). Polish on existing diagnostics: interface_not_conforming arity message reads “got N non-self parameter(s), expected M”; invalid_question_op help text covers both the Result-return cause and the comprehension carve-out; sealed-union impl distribution deduplicates by (code, rendered message) so a 10-variant union no longer reports 10 identical errors; class_attr_shadows_slot no longer false-positives on mutable-literal defaults; MissingAnnotation text drops the double-backtick wrapping.
v0.13.0 Footguns & Surface Tightening
Added in v0.13.0 — four new warnings: mutable_default_param (shared mutable parameter default), loop_closure_capture (closure over a loop variable observes its final value), is_literal_comparison (is against a literal — use ==), incompatible_override (Liskov-violating method override; refined to compare argument ranges, so an added optional parameter is not flagged). Checker-surface changes: enum exhaustiveness now fires non_exhaustive_match naming the missing member (was a generic missing_return), and exhaustiveness now covers expression scrutinees (match items[-1]:) plus match-arm narrowing; the Result / Ok / Err method surface is closed, so an unknown method (typo like .unwarp()) fires attribute_not_found at check time; quoted annotations (next: "Node", -> "Tree[T]") resolve as forward references instead of literal-singleton types (bare quoted scalar builtin names in a union, e.g. "int" | "str", stay literal singletons).
Performance & Parallelism Advice
Nine advice-level lints — never block a build, never rewrite code. Seven form the perf_* family plus lazy_import_opportunity (gated by [strictness] suggest-perf, default on): perf_membership_in_loop, perf_list_shift_in_loop, perf_str_concat_in_loop, perf_sort_in_loop, perf_sorted_first, perf_keys_membership, lazy_import_opportunity. Two fire only on a free-threaded target (gated by [strictness] suggest-parallel, default on, AND [python] free-threaded = true): parallel_opportunity, shared_mut_across_tasks — see Async Errors for the latter two.
Filtering / formatting
tyc does not currently expose a global --quiet / --verbose / --color switch — diagnostics always render through miette with its built-in ANSI heuristics. To gate CI on warnings, raise the relevant [strictness] keys (e.g. unused-import = "error", methods-in-class-body = "error"); the promotion happens before any rendering.
The LSP publishes diagnostics directly to your editor; the editor renders them.
Searching for a code
When you hit error[tyc::some_code]:
tyc explain <code> — print the catalog entry directly to your terminal (mirrors rustc --explain). Accepts short (nullable_use) or fully-qualified (tyc::nullable_use) forms.
Click the url: deep-link on the diagnostic — every tyc:: diagnostic carries a url(https://github.com/CodeHalwell/Typhon/blob/main/docs/diagnostics/<code>.md) clause that linkifies in modern terminals.
Look up the page in the sidebar — the catalog groups codes by what triggers them.
rg "tyc::some_code" tyc/crates in the compiler source if you’re hacking on tyc — every code is registered once.
tyc lsp “Remove unused import” code action fires on the relevant diagnostics; check your editor’s quick-fix menu.
What if the diagnostic is wrong?
Most diagnostics are unambiguous. If you think one is mis-firing: