Back to list
purefunctor

type-checker-tests

by purefunctor

Compiler frontend for PureScript in Rust

78🍴 8📅 Jan 22, 2026

SKILL.md


name: type-checker-tests description: Add integration tests for type checker inference and checking functions allowed-tools: Bash(mkdir:*)

Type Checker Integration Tests

Use this skill when adding new type checker functions or expanding behavior.

Language: Test fixtures use PureScript syntax, not Haskell.

Quick Reference

ActionCommand
Find next test numberls tests-integration/fixtures/checking/ | tail -5
Run a test or multiple testsjust tc NNN or just tc 101 102
Run with tracing enabledjust tc --debug NNN
Run all checking testsjust tc
Accept all pending snapshotscargo insta accept

Use just tc --help for all options.

Creating a Test

1. Create fixture directory

mkdir tests-integration/fixtures/checking/{NNN_descriptive_name}

Tests are auto-discovered by build.rs - no manual registration needed.

2. Write Main.purs

Standard pattern - pair typed (checking) and untyped (inference) variants:

module Main where

-- Checking mode: explicit signature constrains type checker
test :: Array Int -> Int
test [x] = x

-- Inference mode: type checker infers unconstrained
test' [x] = x

Guidelines:

  • Test ONE specific behavior per fixture
  • Name tests descriptively: test, test', test2, test2', etc.
  • Include edge cases relevant to the behavior being tested

3. Generate and review snapshot

just tc NNN

This outputs:

  • CREATED path (green) with numbered lines showing full content
  • UPDATED path (yellow) with chunked diff (2 lines context, line numbers)

Multi-File Tests

For testing imports, re-exports, or cross-module behavior, add multiple .purs files to the same fixture directory. The type checker loads all .purs files in the folder.

Example structure:

tests-integration/fixtures/checking/NNN_import_test/
├── Main.purs    # The test file (snapshot generated for Main)
├── Lib.purs     # Supporting module
└── Main.snap    # Generated snapshot

Lib.purs:

module Lib where

life :: Int
life = 42

data Maybe a = Just a | Nothing

Main.purs:

module Main where

import Lib (life, Maybe(..))

test :: Maybe Int
test = Just life

Key points:

  • Module name must match filename (Lib.purs -> module Lib where)
  • Only Main.purs generates a snapshot (the test runs against Main)
  • Use standard PureScript import syntax

Reviewing Snapshots

Snapshots have this structure:

Terms
functionName :: InferredOrCheckedType
...

Types
TypeName :: Kind
...

Errors
ErrorKind { details } at [location]

Acceptance Criteria

Before accepting, verify:

  1. Types are correct - Check that inferred types match expectations

    • test :: Array Int -> Int - explicit signature preserved
    • test' :: forall t. Array t -> t - polymorphism inferred correctly
  2. No unexpected ??? - This indicates inference failure

    • test :: ??? - STOP: the term failed to type check
    • CannotUnify { ??? -> ???, Int } - OK in error tests, shows unresolved unification variables
  3. Errors appear where expected - For tests validating error behavior

    • Confirm error kind matches expectations (e.g., NoInstanceFound, CannotUnify)
    • Verify error location points to the correct declaration
  4. Polymorphism is appropriate

    • Check type variable names (t6, a, etc.) are scoped correctly
    • Verify constraints propagate as expected

Common Issues

SymptomLikely Cause
test :: ???Test code has syntax error or uses undefined names
Unexpected monomorphismMissing polymorphic context or over-constrained signature
Wrong error locationCheck binder/expression placement in source
Missing types in snapshotModule header or imports incorrect

Accept and Verify

# Accept only after thorough review
cargo insta accept

# Verify all checking tests pass
just tc

Debugging

When investigating a potential compiler bug:

# Focus on single test to reduce noise
just tc NNN

# Enable tracing to see type checker behaviour
just tc --debug NNN

Trace Files

The --debug flag emits detailed type checker traces to target/compiler-tracing/.

Trace file naming: {test_id}_{module_name}.jsonl

  • Example: 200_int_compare_transitive_Main.jsonl

Output format: JSON Lines (one JSON object per line), containing:

  • timestamp - when the event occurred
  • level - DEBUG, INFO, or TRACE
  • fields - trace data (e.g., types being unified)
  • target - the module emitting the trace (e.g., checking::algorithm::unification)
  • span/spans - current span and span stack

Example trace line:

{"timestamp":"...","level":"DEBUG","fields":{"t1":"?0","t2":"Int"},"target":"checking::algorithm::unification","span":{"name":"unify"}}

When --debug is used, the trace file path is shown alongside pending snapshots:

UPDATED tests-integration/fixtures/checking/200_int_compare_transitive/Main.snap
  TRACE target/compiler-tracing/200_int_compare_transitive_Main.jsonl

Analysing Traces

Trace files can be large for complex tests. Use sampling and filtering:

# Check file size and line count
wc -l target/compiler-tracing/NNN_*.jsonl

# Sample random lines to get an overview
shuf -n 20 target/compiler-tracing/NNN_*.jsonl | jq .

# Filter by level
jq 'select(.level == "DEBUG")' target/compiler-tracing/NNN_*.jsonl

# Filter by target module
jq 'select(.target | contains("unification"))' target/compiler-tracing/NNN_*.jsonl

# Extract specific fields
jq '{level, target, fields}' target/compiler-tracing/NNN_*.jsonl

You should run just tc to check for regressions.

Score

Total Score

65/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

+10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

1ヶ月以内に更新

+10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

+5

Reviews

💬

Reviews coming soon