Skip to content

Fix generic type inference#1074

Closed
Dragon-Hatcher wants to merge 5 commits into
strata-org:mainfrom
Dragon-Hatcher:main
Closed

Fix generic type inference#1074
Dragon-Hatcher wants to merge 5 commits into
strata-org:mainfrom
Dragon-Hatcher:main

Conversation

@Dragon-Hatcher
Copy link
Copy Markdown

Fix generic type inference for user-defined polymorphic functions. See #1022 and #1050.

Calling multiple user-defined generic functions together in one expression would fail with a
spurious type error. For example:

function left<T, U>(t: T, u: U) : T { t }                       
function right<T, U>(t: T, u: U) : U { u }                                                   
function double(a: int) : int {                                                              
  right(a, a) + left(a, a)  -- error: Expression has type T when U expected                  
}                                                                                            

The issue is that type inference in the DDM layer treated the polymorphic types as literal strings. So the return type of left is .tvar "T" and the return type of right is .tvar "U". As a result, swapping the names of the parameters on one of the functions would make the code compile.

To fix this, each time we instantiate a function call, we replace each .tvar with a .uvar with a unique index. When we infer a type for one of these .uvars, we store it in the ElabState. An error is produced if identical .uvars get solved to two different types.

Dragon-Hatcher and others added 4 commits April 27, 2026 10:52
Adds TypeExprF.uvar (id : Nat) as a fresh per-call-site unification
variable for instantiating polymorphic user-defined functions.

Previously, inferType returned raw .tvar String nodes from a function's
stored declaration type. When two generic calls appeared as operands to a
builtin (e.g. right(a,a) + left(a,a)), the tvar names from different
declarations ("W" vs "T") were compared and rejected even though both
instantiated to the same concrete type.

The fix applies Hindley-Milner instantiation at each call site:
- freshenTVars replaces tvar nodes with fresh uvar nodes (one per unique
  tvar name, unique ids across call sites via ElabState.nextUVarId)
- unifyTypes now solves uvar nodes, recording id→type in ElabState.uvarMap
- resolveUVars substitutes solved uvars to produce the concrete return type
- ExprInfo.resolvedType stores this resolved type so elabSyntaxArg uses
  it instead of re-calling inferType (which would return raw tvars again)

uvar nodes are transient elaboration artifacts and never appear in the
final output tree; all downstream passes (Translate, Ion, ToExpr) panic
or error if they encounter one.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds test programs covering:
- Identity function with int and bool
- Nested generic calls (id(id(x)), id(left(a, b)))
- Three type parameters (fst3, snd3, thd3)
- Mixed concrete types (int vs bool as distinct type args)
- Generics inside conditional expressions
- Same generic called multiple times in one expression

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…l sites

Adds differentInstantiationsPrg with three functions that verify each call
site gets independent unification variables:
- mixedId: id<T=int> and id<T=bool> in a single boolean expression
- mixedLeft: left<T=bool,U=int> and left<T=int,U=bool> in one expression
- chainDifferent: id wrapping left (int result) and id wrapping right (int
  result), where T instantiates to int via two independent paths

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Resolves conflict between the incoming tvar propagation in the arrow case
of unifyTypes (which recurses into sub-components rather than passing
through) and our uvar passthrough for fresh unification variables.

Both cases are now distinct: tvar propagates into the arrow's domain and
codomain; uvar passes through since it will be resolved after call-site
elaboration completes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@Dragon-Hatcher Dragon-Hatcher requested a review from a team April 29, 2026 06:29
@Dragon-Hatcher Dragon-Hatcher changed the title main Fix generic type inference Apr 29, 2026
@Dragon-Hatcher Dragon-Hatcher marked this pull request as draft April 29, 2026 15:07
@Dragon-Hatcher Dragon-Hatcher marked this pull request as draft April 29, 2026 15:07
@joscoh
Copy link
Copy Markdown
Contributor

joscoh commented May 19, 2026

Closing since #1125 will bypass the issue for now

@joscoh joscoh closed this May 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants