Skip to content

First-Class Linting

Deferred

Deferred future lint-engine architecture. V1 only accepts lint identities and advisory suggested policy in Lint Catalog.

Catalyst should eventually treat linting as a first-class compiler concept. Lints are not an external style afterthought; they are how tooling reports conventions, exposes API risks, and guides users without turning every convention into a hard compiler rule.

Linting Goal

The compiler ecosystem should provide a structured lint layer for:

  • API clarity
  • resource conventions
  • ownership conventions
  • realtime and operational-dependency restrictions
  • suspicious public contracts
  • misleading or unused declarations
  • project policy checks
  • mechanical autofixes where possible

This lets Catalyst keep the core language smaller while still making important conventions reliable.

Why It Matters

The ideation direction intentionally prefers conventions over hardcoded language features for areas like constructors, destructors, ownership vocabulary, public API inference, and resource cleanup.

Those conventions only work well if tooling can report them consistently. A convention that every project must reimplement with separate tools will fragment quickly.

Compiler, Warning, Lint, Convention

Catalyst distinguishes these reporting categories:

Compiler error
Program is invalid or phase contracts are violated.
Warning
Non-fatal compiler diagnostic explicitly produced by an owning compiler phase, tool, or comptime call to Compiler.warn.
Lint
Policy or convention check that can be enabled, disabled, configured, or elevated.
Convention
Documented guidance that may later become a lint.

The active cross-domain list of lint candidates is Lint Catalog. This page describes the future lint engine that may implement, configure, suppress, and autofix those findings.

Example Lints

Public API inference:

pub fn parse(source: Source) {
}

Potential lint:

public API has inferred signature

This includes inferred success types and inferred error sets.

Resource conventions:

fn create(alloc: *impl Allocator, ...) *Self

Expected convention:

  • init returns Self
  • create allocates and returns *Self
  • dispose is the Disposable cleanup operation
  • destroy releases internals and frees self

Operational-dependency and realtime-suitability checks:

fn process(buffer: []f32) void {
}

Future checked-region hard semantic error:

function in a checked no-allocation region calls an allocation API

Ownership/resource checks:

  • initialized resource is never disposed
  • destroy does not require an allocator
  • convention-only resource-like value is copied through a suspicious API
  • type implements Disposable but remains copyable, unless the type documents copy-safe cleanup semantics
  • copyable Disposable value is boxed or passed to an owner without explicit move, when the copy may make cleanup ownership unclear
  • resource-like value is overwritten while still live
  • live resource binding is shadowed before cleanup
  • resource-like value is used after cleanup
  • Disposable.dispose is called through a borrowed dynamic pointer to a payload still owned by an owning container such as Box
  • borrowed pointer is stored beyond a likely lifetime boundary
  • loop continue, break, or early exit leaves a yielded resource item live
  • concrete type implements both Iterable(Item) and Iterator(Item) without documenting why the value is both a source and iterator state; this does not apply to Iterator(Item) plus Disposable
  • ignore binding pattern _ discards a resource value that requires explicit cleanup
  • project convention for identifiers such as _name meaning intentionally unused; semantically they are normal bindings

Discarded-value checks:

  • expression statement discards a meaningful non-void value without assigning it to _
  • if without else discards a value-producing branch because the whole expression has type void

The V1 ownership reference records lintable resource patterns and cleanup-state vocabulary. See Ownership and Resources and the ownership catalog. These checks should start pragmatic and conservative rather than becoming a pervasive proof system.

Optional presence checks:

  • ?bool coerced to bool, because Some(false) tests as present rather than false
  • plain if maybe_bool { ... } sugar that shadows maybe_bool as a bool payload
  • explicit if const marker = maybe_void { ... }, because binding a void payload is usually useless
  • ?void optional binding where a plain presence check would be clearer
  • field optional presence checks where the body appears to use the field as if it were narrowed

Autofixes

Some lints should support compiler-provided autofixes:

  • expand inferred public signatures
  • add missing explicit priv
  • rename resource functions to convention names
  • expand destructured struct parameters when tooling can do so unambiguously
  • remove unused declarations

Autofixes must be deterministic and should preserve formatting once a formatter exists.

Phase Model

Linting should operate over structured compiler data, not raw text alone.

Possible lint inputs:

  • AST for syntax and declaration shape
  • SIR for resolved names, types, signatures, and capabilities
  • IR metadata for backend-visible performance facts
  • package/module metadata later

Phase boundary

Lints must not bypass phase boundaries. If a lint needs semantic information, it should run after sema over SIR, not re-resolve names independently.

Configuration and Suppression

Open configuration shape

The exact configuration and suppression format is deferred to CEP-0044: First-Class Lint Engine. The design should leave room for:

  • default lint sets
  • project-local policy
  • warning-as-error mode
  • per-lint suppression
  • source-level suppression with a required reason for serious lints

Suppressions should be explicit and searchable.

V1 Boundaries

V1 does not require:

  • a full lint engine
  • project configuration
  • formatter integration
  • LSP integration
  • exhaustive ownership/resource analysis

The first useful step is to keep diagnostics structured enough that lint tooling can be added without rewriting phase outputs, and to keep lint identities stable through the Lint Catalog.