You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
After commit 3325d39f (M-SCHEME-IMPORT-PRESERVE-ADT-HEAD, 2026-05-20), exported function calls fail to unify when a parameter contains a nested list of a record-type alias imported from another module. The substitution-tightening fix correctly removes leaky TVars but exposes an alias-env-propagation gap: imported aliases aren't visible to the unifier when checking cross-module call sites, so expandAlias() cannot resolve TCon("Inner") to its underlying TRecord and unification falls through to:
cannot unify type constructor Inner with *types.TRecord
Pre-3325d39f the case incidentally passed because over-polymorphic TVars satisfied unification at any shape.
ailang --version
AILANG v0.21.0-4-gdf2ed8de-dirty
Commit: df2ed8d
Minimal reproduction (3 files, ~25 lines)
ailang.toml:
[package]
name = "local/typebug"version = "0.1.0"edition = "1"module_prefix = ""ailang = ">=0.16.1"
[effects]
max = ["IO"]
typebug/types.ail:
module typebug/types
export type Inner = {
name: string
}
typebug/lib.ail:
module typebug/lib
import typebug/types (Inner)
export type Outer = {
items: [Inner]
}
pure func make_inner(n: string) -> Inner {
{ name: n }
}
pure func make_inners() -> [Inner] {
[make_inner("a"), make_inner("b")]
}
export pure func build() -> Outer {
{ items: make_inners() }
}
export pure func use_outer(o: Outer) -> int {
match o.items {
[] => 0,
_ :: _ => 1
}
}
typebug/main.ail:
module typebug/main
import typebug/lib (build, use_outer)
export func main() -> int {
let v = build();
use_outer(v)
}
Expected: build(): Outer is then passed to use_outer(o: Outer). Both refer to the same nominal type; should type-check cleanly (and does on v0.19.1).
Actual: unification fails at the call-site of use_outer(v) with the error above. The error names field 'items' correctly but cannot reconcile its element type Inner (a TCon after substitution) with the structural TRecord it sees on the other side.
Bisect
git bisect start df2ed8de v0.19.1
git bisect run /tmp/typebug-root/bisect-test.sh
→ first bad commit: 3325d39fd52d39dc17a4a7ae059f7027555de388
"M-SCHEME-IMPORT-PRESERVE-ADT-HEAD: fix exported function schemes losing ADT heads"
Root cause analysis
expandAlias() at internal/types/unification_core.go:88 returns the input TCon unchanged when u.aliasEnv does not contain the alias name:
func (u*Unifier) expandAlias(tType) Type {
ifu.aliasEnv==nil { returnt }
ifcon, ok:=t.(*TCon); ok {
iftarget, exists:=u.aliasEnv[con.Name]; exists { ...return... }
}
returnt// ← fall-through when alias not in env
}
The TCon → TRecord dispatch path at unification_core.go:233-238 is in place and calls this helper — but the helper can't find Inner because the alias was declared in typebug/types and the unifier instance servicing typebug/main's call-site type-check has an aliasEnv that doesn't include imports' alias declarations.
Most likely the fix lives wherever Unifier.aliasEnv is constructed for inter-module function-application checking: it needs to merge in imported modules' alias-type declarations (probably from their iface.json / ModuleRegistry). The M-WASM-TYPECHECK-FLOAT-DIVERGENCE fix (ad84b68d) did something similar for the WASM type-checker — "propagate imported type aliases + param annotations in ModuleRegistry" — but the native path appears not to have the equivalent merge for aliasEnv.
File: pkg/sunholo/motoko_ext_mcp@0.2.7/register.ail:10:13 — call make_hooks(cfg) where cfg: McpConfig and McpConfig.servers[].tool_mappings: [McpToolMapping], with McpToolMapping imported from a separate types.ail module within the same package.
Symmetric pattern: any AILANG package that splits its record-type aliases into a types.ail and uses them nested in other modules' record-typed parameters will hit this in v0.21.0+. motoko_ext_omnigraph, motoko_ext_context_mode, motoko_ext_compaction_ai and similar packages have the same structure and will break on their next republish against current dev.
Suggested fix
Trace Unifier.aliasEnv construction; ensure it's populated from ModuleRegistry imports at the point where function-application type-checking creates a Unifier for a cross-module call. A regression test cloning the 3-file repro into internal/pipeline/ would lock this in — the minimal repro above is suitable.
Symptom
After commit
3325d39f(M-SCHEME-IMPORT-PRESERVE-ADT-HEAD, 2026-05-20), exported function calls fail to unify when a parameter contains a nested list of a record-type alias imported from another module. The substitution-tightening fix correctly removes leaky TVars but exposes an alias-env-propagation gap: imported aliases aren't visible to the unifier when checking cross-module call sites, soexpandAlias()cannot resolveTCon("Inner")to its underlyingTRecordand unification falls through to:Pre-3325d39f the case incidentally passed because over-polymorphic TVars satisfied unification at any shape.
ailang --versionMinimal reproduction (3 files, ~25 lines)
ailang.toml:typebug/types.ail:typebug/lib.ail:typebug/main.ail:Run:
ailang check typebug/main.ail(withAILANG_RELAX_MODULES=1).Expected vs actual
build(): Outeris then passed touse_outer(o: Outer). Both refer to the same nominal type; should type-check cleanly (and does on v0.19.1).use_outer(v)with the error above. The error namesfield 'items'correctly but cannot reconcile its element typeInner(aTConafter substitution) with the structuralTRecordit sees on the other side.Bisect
Root cause analysis
expandAlias()atinternal/types/unification_core.go:88returns the inputTConunchanged whenu.aliasEnvdoes not contain the alias name:The
TCon → TRecorddispatch path atunification_core.go:233-238is in place and calls this helper — but the helper can't findInnerbecause the alias was declared intypebug/typesand the unifier instance servicingtypebug/main's call-site type-check has analiasEnvthat doesn't include imports' alias declarations.Most likely the fix lives wherever
Unifier.aliasEnvis constructed for inter-module function-application checking: it needs to merge in imported modules' alias-type declarations (probably from theiriface.json/ModuleRegistry). TheM-WASM-TYPECHECK-FLOAT-DIVERGENCEfix (ad84b68d) did something similar for the WASM type-checker — "propagate imported type aliases + param annotations in ModuleRegistry" — but the native path appears not to have the equivalent merge foraliasEnv.Where it surfaced
arniwesth/motoko_agentPR [cli] Bug #27 followup: math codegen has two issues ... #28 (CI: https://github.com/arniwesth/motoko_agent/actions/runs/26002230244).pkg/sunholo/motoko_ext_mcp@0.2.7/register.ail:10:13— callmake_hooks(cfg)wherecfg: McpConfigandMcpConfig.servers[].tool_mappings: [McpToolMapping], withMcpToolMappingimported from a separatetypes.ailmodule within the same package.types.ailand uses them nested in other modules' record-typed parameters will hit this in v0.21.0+.motoko_ext_omnigraph,motoko_ext_context_mode,motoko_ext_compaction_aiand similar packages have the same structure and will break on their next republish against currentdev.Suggested fix
Trace
Unifier.aliasEnvconstruction; ensure it's populated fromModuleRegistryimports at the point where function-application type-checking creates a Unifier for a cross-module call. A regression test cloning the 3-file repro intointernal/pipeline/would lock this in — the minimal repro above is suitable.Binary info (auto-attached):
ailang version: v0.21.0-4-gdf2ed8de-dirty
binary md5: 96ffb6c26f311f9fdcdb01deece69f4d
binary path: /Users/voightkampff/go/bin/ailang
git commit: df2ed8d
Reported by: motoko_agent via ailang messages