girard

A type annotator for Gleam, written in Gleam.

Reports the inferred type of every expression — keyed by its source span — and the signature of every top-level function and constant, for a single module (annotate) or a whole package (annotate_package). Give it source text or a glance AST you parsed yourself.

Imported modules are resolved through a Resolver to obtain their public interfaces.

Types

Everything girard inferred for one module: each top-level definition’s signature, plus the type of every expression in their bodies.

functions and constants have one entry per top-level definition — its generalized Scheme (a type_ plus the ids of its quantified Vars). expressions is finer-grained: the Type of every expression — literals, calls, operators, sub-expressions — keyed by its glance source span, so you can join inferred types onto your own AST. Render any type with type_to_string.

pub type AnnotatedModule {
  AnnotatedModule(
    functions: List(#(String, types.Scheme)),
    constants: List(#(String, types.Scheme)),
    expressions: List(Annotation),
  )
}

Constructors

  • AnnotatedModule(
      functions: List(#(String, types.Scheme)),
      constants: List(#(String, types.Scheme)),
      expressions: List(Annotation),
    )

    Arguments

    functions

    Top-level function name to inferred signature scheme, in source order.

    constants

    Top-level constant name to inferred scheme, in source order.

    expressions

    Expression span to inferred type, sorted by start offset.

The inferred type of a single expression, identified by its source span. type_ is a structured Type you can pattern-match on; render it with type_to_string.

pub type Annotation {
  Annotation(span: glance.Span, type_: types.Type)
}

Constructors

Why a module could not be typed (re-exported from girard/types).

pub type Error =
  types.Error

The result of annotating one module of a package: its AnnotatedModule plus the definitions that could not be typed. skipped names each top-level function or constant girard declined, with the error that declined it; a definition in skipped is absent from annotated.

pub type ModuleResult {
  ModuleResult(
    annotated: AnnotatedModule,
    skipped: List(#(String, types.Error)),
  )
}

Constructors

How a module is annotated: which Resolver finds imported modules, and which build Target to type for. Build one from default_options and customize it with with_target and with_resolver:

default_options()
|> with_target(JavaScript)
pub opaque type Options

Resolves an imported module path (e.g. "gleam/list") to its source.

pub type Resolver =
  fn(String) -> Result(String, Nil)

The build target a module is compiled for. The target is a whole-build setting in Gleam, so it applies to every module in one annotation run. Definitions and imports annotated @target(...) are kept only when they match the active target. default_options() selects Erlang (matching gleam build’s default); use with_target for JavaScript.

pub type Target {
  Erlang
  JavaScript
}

Constructors

  • Erlang
  • JavaScript

Values

pub fn annotate(
  source: String,
  options: Options,
) -> Result(AnnotatedModule, types.Error)

Annotate a Gleam source string: parse it with glance, then annotate as annotate_module. Returns the inferred error if the module does not type. The quick path is annotate(source, default_options()).

pub fn annotate_module(
  module: glance.Module,
  options: Options,
) -> Result(AnnotatedModule, types.Error)

Annotate an already-parsed glance.Module. Use this when you have parsed the source with glance yourself — the returned spans are glance’s, so they line up with your AST’s node spans and you avoid parsing the same source twice. (Imported modules are still parsed internally, via the resolver.) Returns the inferred error if the module does not type; for partial results on an ill-typed module, use annotate_package.

pub fn annotate_package(
  modules: List(#(String, glance.Module)),
  options: Options,
) -> dict.Dict(String, ModuleResult)

Annotate every module in a package in one pass, sharing inference of common imports across modules. modules maps each module’s path (e.g. "my_app/router") to its parsed glance.Module; the result maps the same paths to a ModuleResult.

This is the batch counterpart to annotate_module: a dependency imported by several modules is inferred once for the whole run rather than once per importing module. Cross-module references within the package are resolved through the options’ resolver, so it must also resolve the package’s own modules (a resolver wrapping the build’s module sources does); a module reached only that way is inferred for its interface and again here for its annotations.

Best-effort per definition: a top-level function or constant that does not type — along with any that depend on it — is reported in that module’s skipped list rather than failing the module, while every other definition is still annotated. A module thus always appears in the result; a fully strict check is result.skipped == [].

pub fn default_options() -> Options

Default options: resolve imports from disk (disk_resolver()) and type for the Erlang target (matching gleam build’s default).

pub fn describe_error(error: types.Error) -> String

A short, human-readable description of an inference error.

pub fn disk_resolver() -> fn(String) -> Result(String, Nil)

The default resolver: looks for an imported module’s source under src/ and the build/packages/*/src dependency sources, relative to the current working directory. The build/packages listing is read once and captured, so resolving many imports does not re-scan the directory each time.

pub fn main() -> Nil

gleam run -- <file.gleam> annotates a file; gleam run -- - (or no arguments, or piped input) annotates stdin. Imports are resolved from disk.

pub fn report(source: String) -> String

Annotate a source string and render the result as a human-readable text report (signatures and per-expression types). On failure the report is a single // error: line.

Example

report("pub fn double(x) { x + x }")
double: fn(Int) -> Int
19-20: Int
19-24: Int
23-24: Int
pub fn type_to_string(type_: types.Type) -> String

Render an inferred Type to Gleam syntax (e.g. fn(Int) -> a), naming type variables a, b, c, …. Each call names variables independently: an a in one rendered type is unrelated to an a in another.

pub fn with_resolver(
  options: Options,
  resolver: fn(String) -> Result(String, Nil),
) -> Options

Resolve imported modules with resolver — e.g. fn(_) { Error(Nil) } to resolve none, or a custom in-memory resolver.

pub fn with_target(options: Options, target: Target) -> Options

Type for target. @target(...) definitions that do not match are dropped, exactly as the compiler omits them from the build.

Search Document