Sigil Type System Specification
Version: 1.0.0 Last Updated: 2026-03-14
Overview
Sigil currently uses bidirectional type checking with:
- algebraic data types
- exact record types
- map types
- explicit top-level parametric polymorphism
- effect annotations
Current Sigil does not implement:
- borrow checking
- ownership/lifetimes
- Hindley-Milner let-polymorphism
This spec describes the implemented checker in the current repository.
Bidirectional Checking
Sigil alternates between:
- synthesis: infer a type from expression structure
- checking: verify an expression against an expected type
This matches Sigil’s explicit surface:
- function parameter types are required
- function return types are required
- lambda parameter types are required
- lambda return types are required
Local Bindings
Local l bindings are monomorphic.
Sigil does not perform Hindley-Milner let-generalization for locals. Polymorphism comes from explicitly generic top-level declarations.
Explicit Top-Level Polymorphism
Generic declarations are allowed at top level:
λidentity[T](x:T)=>T=x
λmapOption[T,U](fn:λ(T)=>U,opt:Option[T])=>Option[U]
Generic instantiation is driven by ordinary bidirectional typing.
Current Sigil does not include:
- generic lambdas
- explicit call-site type arguments like
f[Int](x)
Core Type Forms
Primitive types:
IntFloatBoolStringCharUnitNever
Constructed types:
[T]{K↦V}λ(T1,T2,...)=>R- named ADTs and aliases
Project-Defined Named Types
In projects with sigil.json, project-defined named types live in src/types.lib.sigil and are referenced elsewhere with µ....
Example:
t BirthYear=Int where value>1800 and value<10000
t User={birthYear:BirthYear,name:String}
λtodoId(todo:µTodo)=>Int=todo.id
src/types.lib.sigil is types-only and may reference only §... and ¶... inside type definitions and constraints.
Algebraic Data Types
Examples:
t ConcurrentOutcome[T,E]=Aborted()|Failure(E)|Success(T)
t Option[T]=Some(T)|None()
t Result[T,E]=Ok(T)|Err(E)
t Color=Red()|Green()|Blue()
Imported constructors use fully qualified module syntax in expressions and patterns.
Records and Maps
Records and maps are distinct:
- records are exact fixed-shape products using
: - maps are dynamic keyed collections using
↦
Sigil currently has:
- no row polymorphism
- no width subtyping for records
- no open records
If a field may be absent, the canonical representation is Option[T] inside an exact record.
Constrained Types
Named user-defined types may carry a pure where clause:
t BirthYear=Int where value>1800 and value<10000
t DateRange={end:Int,start:Int} where value.end≥value.start
Constraint rules:
- only
valueis in scope - the constraint must typecheck to
Bool - constraints are pure and world-independent
- v1 uses constraints as richer type meaning, not automatic runtime validation
- the checker currently rejects only obvious literal contradictions
Structural Equality
Type equality normalizes unconstrained aliases and unconstrained named product types before comparison.
That means:
- unconstrained aliases compare structurally
- unconstrained named product types compare structurally after normalization
- constrained aliases and named product types remain distinct
- sum types remain nominal
This is a checker invariant, not inference.
Effects
Sigil supports explicit effect annotations in function and test signatures.
Sigil ships with primitive effects:
ClockFsHttpLogProcessRandomTcpTimer
Projects may define reusable multi-effect aliases only in src/effects.lib.sigil. Aliases must expand to at least two primitive effects.
Example:
effect CliIo=!Fs!Log!Process
Examples:
e axios:{get:λ(String)=>!Http String}
e console:{log:λ(String)=>!Log Unit}
λfetch()=>!Http String=axios.get("https://example.com")
λmain()=>!Http!Log Unit={
l _=(fetch():String);
console.log("hello")
}
Effects are explicit surface syntax. The checker tracks them as part of the typed program and rejects callees or bodies whose required effects are not covered by the enclosing signature.
Canonical Typed Rules
Some canonical rules depend on typing information.
Current important example:
- a pure local binding used exactly once is rejected and must be inlined
This rule is applied after type checking by the canonical validator.
What This Spec Does Not Claim
This document intentionally does not specify:
- ownership semantics
- borrow rules
- inferred lifetimes
- Algorithm W
Those are not part of the implemented Sigil type system in this repository.