Skip to content

Reading Diagnostics

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: ...`
= url: https://github.com/CodeHalwell/Typhon/blob/main/docs/diagnostics/nullable_use.md
PartMeaning
errorSeverity. One of error, warning, note.
[tyc::nullable_use]Stable code. Every diagnostic has one; search by it.
cannot pass ...Human message describing the problem.
┌─ src/main.ty:9:11Primary source span: file, line, column.
^^^^^ underlineThe 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

SeverityExit code impactTypical cause
errorSets exit to 2.Type mismatch, missing annotation, broken syntax.
warningNo exit code change.Unused import (with unused-import = "warn"), async def with no await, methods in class body.
adviceNo exit code change.Missed optimisation hints, e.g. auto_gather_missed.
noteNo 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:

Type & Inference Errors

→ — missing_annotation, type_mismatch, arg_count, missing_argument, missing_return, not_callable, unknown_name, attribute_not_found, generic, typevar_bound, generator_return_type, cyclic_type_alias, comptime, operator_type_mismatch, tuple_index_out_of_range, kind_mismatch, implicit_any, div_by_zero_literal, python_semantic_drift (not currently emitted).

Binding Errors

→ — missing_binding_kind, immutable_assign, unused_import, pattern_shadows_outer, mutable_default_param, loop_closure_capture, is_literal_comparison, possibly_unbound, no_block_shadow, use_of_uninitialised, missing_initialiser (not currently emitted), empty_collection_no_annotation.

Nullability Errors

→ — nullable_use.

Class Errors

→ — manual_init, frozen_assign, method_in_class_body, duplicate_class, impl_unknown_class, impl_forward_reference, missing_field_init, extend_builtin, class_attr_shadows_slot, duplicate_method, field_default_ordering, frozen_inheritance_conflict, newtype_violation, newtype_invalid_base, self_outside_impl, not_a_context_manager, raise_non_exception.

Result Errors

→ — invalid_question_op, result_error_mismatch.

Match Errors

→ — non_exhaustive_match, invalid_pattern, alias_not_a_class.

Async Errors

→ — missing_await, async_without_await, gather_opportunity, auto_gather_missed, parallel_opportunity, shared_mut_across_tasks, return_in_except_star, interface_isinstance, go_outside_async, blocking_in_async.

Purity Errors

→ — impure_pure_fn.

Stub Errors

→ — stub_mismatch.

Compile & Interface Errors

→ — io, parse, interface_not_conforming, incompatible_override, lazy_usage, freeze_not_freezable, unknown_module, unknown_kwarg, typevar_import_rejected, typing_alias_deprecated, typing_alias_in_annotation, unsafe_value_leak, invalid_config_value, pub_name_collision, pub_star_outside_init, stdlib_module_shadow, reserved_module_name, requires_newer_python, orphan_py_import, main_not_called.

Lints & Performance Advice

→ — resource_not_managed, contains_secret_literal, 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.

Phase 5 Interop & Lints

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]:

  1. 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.
  2. 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.
  3. Look up the page in the sidebar — the catalog groups codes by what triggers them.
  4. rg "tyc::some_code" tyc/crates in the compiler source if you’re hacking on tyc — every code is registered once.
  5. 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:

  1. Reduce the case to a minimal .ty file.
  2. Confirm tyc check reproduces.
  3. File an issue at github.com/CodeHalwell/Typhon/issues with the minimal repro and the diagnostic output.

Don’t disable strictness flags as a workaround — file the bug instead.