A library for parsing, loading, and typechecking a complete Gleam project. It wraps the AST produced by glance with:
- a representation of a whole program — multiple interdependent modules — rather than a single module.
- typechecking both within and between modules.
Glimpse is not filesystem-aware: modules are loaded externally through a loader function. It mirrors the official Gleam compiler's behavior closely; it has been validated with differential mutation testing against the real compiler across a wide range of popular Gleam projects.
Glimpse 1.0.0-rc.3 is available on hex.pm.
Docs: https://hexdocs.pm/glimpse/ Repo: https://github.com/dusty-phillips/glimpse
gleam add glimpseLoad a package, then typecheck it for the Erlang target:
import gleam/io
import gleam/result
import glimpse
import glimpse/target
import glimpse/typecheck
fn load_module(module_name: String) -> Result(String, Nil) {
// read the module contents from the filesystem here
Ok("<contents of " <> module_name <> ".gleam>")
}
pub fn main() {
let package =
glimpse.load_package("my_package", load_module)
|> result.map(typecheck.package(_, target.Erlang))
case package {
Ok(_) -> io.println("typechecked ok")
Error(_) -> io.println("typecheck failed")
}
}The package is loaded with a loader function and returned with inferred types filled in; you can then iterate the modules and inspect the resolved AST.
glimpse.load_package accepts the package name and a function that takes a
module name and returns that module's contents. The loader is called with the
main module (the package name) and, recursively, for every module that is
imported:
pub fn load_glimpse_package(
project: project.Project,
) -> Result(glimpse.Package, errors.Error) {
glimpse.load_package(project.name, fn(module_name) {
let path =
filepath.join(project.build_src_dir(project), module_name <> ".gleam")
filesystem.read(path)
})
|> result.map_error(fn(error) {
case error {
glimpse.LoadError(error) -> error
glimpse.ParseError(glance_error, name, content) ->
errors.GlanceParseError(glance_error, name, content)
}
})
}Glimpse is a full Hindley-Milner type checker with exhaustiveness checking,
across module boundaries. typecheck.package sorts the modules by their
dependencies and checks each in turn, returning the package with inferred types
filled in:
Typechecking is optimized for whole-package workloads: constructor candidates
are memoized per (module, type) pair during exhaustiveness checking, and
instantiated namespace type dicts are built in O(n) rather than one insert at a
time.
pub fn package(
package: glimpse.Package,
target: target.Target,
) -> Result(glimpse.Package, error.GlimpseError(a))@target(erlang) / @target(javascript) definitions that are not active for
the target being checked are filtered out before typechecking, mirroring the
real compiler. Pass target.Erlang or target.Javascript. Experimental
backends can use target.Named(name) so a @target(python) definition is
active when checking for target.Named("python"); the same matching applies to
@external(...) annotations.
glimpse/typecheck also checks a single module, constant, or function against
an existing type environment:
module(glimpse_module, module_envs, target)— typechecks one module; any modules it imports must already have been checked.constant(environment, constant)— typechecks a module constant.function(environment, function)— typechecks a function body.
Functions return a Result, with errors reported as glimpse/error's
GlimpseError:
LoadError— a module failed to load.ParseError— a module failed to parse.ImportError— a missing import, a circular dependency, or a source module importing a development dependency.TypeCheckError— an error in the code being checked, such as a type mismatch, unknown custom type, or invalid argument.
Because Glimpse is not filesystem-aware, it cannot discover dev-only modules on
its own. Set Package.dev_dependencies to those module names after loading so
that a source module importing one is reported as an ImportError, mirroring
the real compiler's src/dev split.
The repo ships two dev tools in dev/. Pass their arguments after a --
separator:
gleam test # Run the unit tests
gleam run -m dev_check # Typecheck glimpse against itself
gleam run -m dev_check -- --typecheck <root>
gleam run -m mutate_check -- --root <root> --src <src_rel> [--jobs <n>] [--kind <kind>] [--count] [--both] [--resume]dev_checktypechecks a project checkout with itsbuild/packages/dependencies, or (with no arguments) glimpse against itself.mutate_checkis a differential mutation harness: it rewrites small pieces of a source module and judges each mutant against the realgleam check(ground truth) and glimpse, reporting false negatives (glimpse accepts what the real compiler rejects) and false positives. Verdicts are appended to durable files under/tmp/mutcheck/as they are judged, so interrupted runs can continue with--resume. Do not run two sweeps against the same root concurrently; different roots in parallel are fine.