broaden type inference from context (first pass)

This commit is contained in:
2026-06-25 23:57:12 +02:00
parent 70a6d69d29
commit ff69e1da83
3 changed files with 855 additions and 38 deletions
+294 -34
View File
@@ -38,6 +38,12 @@ Infer_Local :: struct {
declared: types.Type,
statement: ast.Stmt_Id,
mutable: bool,
// open_const marks a local whose initializer is a compile-time integer with no
// concrete annotation: like an open-constant global, it is sign-agnostic until a
// backward demand from a use picks its family/width (see merge_local_demand).
open_const: bool,
const_value: i128,
demanded: bool,
}
Build_Local :: struct {
@@ -105,6 +111,14 @@ Checker :: struct {
global_index: []Global_Index_Entry,
import_index: []Import_Index_Entry,
global_types: []types.Type,
// Backward type-demand state for open-constant globals (milestone 14). global_demands
// accumulates demands reachable from any use (other globals' initializers and function
// bodies); global_demands_dirty lets a demand pushed from a function body re-trigger the
// inference fixpoint.
global_demands: []types.Type,
global_open_const: []bool,
global_const_value: []i128,
global_demands_dirty: bool,
external_global_canonical: []ast.Global_Id,
external_global_diagnostics: []source.Diagnostic_Id,
constants: []Constant,
@@ -1088,11 +1102,12 @@ infer_nested_expr :: proc(
pkg: ast.Package_Id,
file: ast.File_Id,
demanded: ^[dynamic]Spec_Id,
local_types: []types.Type = nil,
) -> types.Type {
outer := checker.infer_stack
checker.infer_stack = nil
checker.infer_stack.allocator = checker.allocator
result := infer_expr(checker, expr_id, locals, pkg, file, demanded)
result := infer_expr(checker, expr_id, locals, pkg, file, demanded, local_types)
delete(checker.infer_stack)
checker.infer_stack = outer
return result
@@ -1105,21 +1120,22 @@ infer_compound_expr :: proc(
pkg: ast.Package_Id,
file: ast.File_Id,
demanded: ^[dynamic]Spec_Id,
local_types: []types.Type = nil,
) -> types.Type {
store := &checker.module.types
#partial switch expr.kind {
case .Bool:
return types.BOOL
case .Not:
_ = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded)
_ = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
return types.BOOL
case .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or:
_ = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded)
_ = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded)
_ = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
_ = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded, local_types)
return types.BOOL
case .Range:
left := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded)
right := infer_nested_expr(checker, expr.right, locals, pkg, file, demanded)
left := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
right := infer_nested_expr(checker, expr.right, locals, pkg, file, demanded, local_types)
left_const := eval_constant(checker, expr.left)
right_const := eval_constant(checker, expr.right)
child := types.INVALID
@@ -1139,7 +1155,7 @@ infer_compound_expr :: proc(
case .Array:
element := types.INVALID
for arg in expr.args {
actual := infer_nested_expr(checker, arg, locals, pkg, file, demanded)
actual := infer_nested_expr(checker, arg, locals, pkg, file, demanded, local_types)
if !types.is_valid(element) {
element = actual
} else if !types.equal(element, actual) {
@@ -1157,25 +1173,25 @@ infer_compound_expr :: proc(
case .Enum_Literal:
return types.INVALID
case .Address:
child := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded)
child := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
return types.pointer(store, child, false, false)
case .Deref:
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded)
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
return types.child_type(value, store) if types.is_pointer(value, store) else types.INVALID
case .Index:
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded)
_ = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded)
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
_ = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded, local_types)
item, ok := types.container(value, store)
return item.child if ok else types.INVALID
case .Slice:
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded)
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
item, ok := types.container(value, store)
if !ok || item.kind == .Pointer {
return types.INVALID
}
for bound in expr.args {
if bound != ast.INVALID_EXPR {
_ = infer_nested_expr(checker, bound, locals, pkg, file, demanded)
_ = infer_nested_expr(checker, bound, locals, pkg, file, demanded, local_types)
}
}
preserve := item.has_sentinel && expr.args[1] == ast.INVALID_EXPR
@@ -1185,7 +1201,7 @@ infer_compound_expr :: proc(
_, member_ok := find_enum_member(checker, enum_type, expr.name)
return enum_type if member_ok else types.INVALID
}
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded)
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
field_name := symbol_text(checker, expr.name)
item, has_item := types.container(value, store)
if has_item && (item.kind == .Array || item.kind == .Slice) {
@@ -1203,21 +1219,21 @@ infer_compound_expr :: proc(
_, field, ok := find_struct_field(checker, value, expr.name)
return field.type if ok else types.INVALID
case .Unwrap:
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded)
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
return types.child_type(value, store) if types.is_optional(value, store) else types.INVALID
case .Orelse:
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded)
_ = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded)
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
_ = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded, local_types)
return types.child_type(value, store) if types.is_optional(value, store) else types.INVALID
case .Struct_Literal:
for keyed in expr.args {
_ = infer_nested_expr(checker, checker.ast_module.exprs[keyed].left, locals, pkg, file, demanded)
_ = infer_nested_expr(checker, checker.ast_module.exprs[keyed].left, locals, pkg, file, demanded, local_types)
}
target_pkg, available := expr_package(checker, expr, pkg, file)
value := types.find_named(store, u32(target_pkg), u32(expr.name)) if available else types.INVALID
return types.resolve_alias(value, store)
case .Keyed:
return infer_nested_expr(checker, expr.left, locals, pkg, file, demanded)
return infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
case:
return types.INVALID
}
@@ -1230,6 +1246,7 @@ infer_expr :: proc(
pkg := ast.Package_Id(0),
file := ast.File_Id(0),
demanded: ^[dynamic]Spec_Id = nil,
local_types: []types.Type = nil,
) -> types.Type {
stack := checker.infer_stack
clear_dynamic_array(&stack)
@@ -1281,7 +1298,7 @@ infer_expr :: proc(
case .String, .Array, .None, .Undefined, .Address, .Deref, .Index, .Slice,
.Field, .Unwrap, .Orelse, .Struct_Literal, .Keyed, .Enum_Literal,
.Bool, .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
last = infer_compound_expr(checker, expr, locals, pkg, file, demanded)
last = infer_compound_expr(checker, expr, locals, pkg, file, demanded, local_types)
_ = pop(&stack)
case .Name:
last = types.INVALID
@@ -1349,7 +1366,7 @@ infer_expr :: proc(
append(&stack, Infer_Frame{expr=expr.left, template=ast.INVALID_FUNCTION})
case .Call:
if expr.left != ast.INVALID_EXPR {
callee_type := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded)
callee_type := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
_, function_item, function_type, ok := types.function_pointer(callee_type, &checker.module.types)
if !ok {
last = types.INVALID
@@ -1464,6 +1481,13 @@ infer_expr :: proc(
}
}
function := checker.ast_module.functions[frame.template]
// A bare-name argument passed to a concrete (non-constraint) parameter pushes
// that parameter type back onto the argument's slot, so an open constant adopts
// it (e.g. `take_u16(a)` resolves `a` to u16). Constraint params have no single
// type to demand; the callee's result flowing back is milestone 14.5.
for arg_index in 0..<len(expr.args) {
record_demand(checker, expr.args[arg_index], call_arg_expected(function, arg_index), locals, local_types, pkg, file)
}
if valid_call_arity(function, len(expr.args)) &&
can_specialize(checker, function, stack[frame_index].args) {
spec := INVALID_SPEC
@@ -1612,7 +1636,7 @@ infer_statements :: proc(
declared_local := resolve_inferred_array(checker, type_from_syntax(statement.type), statement.expr)
value_type := types.INVALID
if !is_undefined_expr(checker, statement.expr) {
value_type = infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded)
value_type = infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types)
}
if is_runtime_type(checker, declared_local) {
value_type = declared_local
@@ -1621,19 +1645,33 @@ infer_statements :: proc(
// build_block reports). FLOAT defaults integers to f64.
value_type = types.constraint_target(declared_local, value_type, &checker.module.types)
}
open := false
const_val := i128(0)
if !is_runtime_type(checker, declared_local) && !is_undefined_expr(checker, statement.expr) {
constant := eval_constant(checker, statement.expr)
if constant.kind == .Value && fits_i64(constant.value) {
open = true
const_val = constant.value
}
}
local := Infer_Local{
name=statement.name,
type=value_type,
declared=declared_local,
statement=statement_id,
mutable=!statement.immutable,
open_const=open,
const_value=const_val,
}
append(locals, local)
record_infer_local_type(local, local_types)
// A typed/constraint declaration initialized by a bare name pushes its resolved
// type backward onto that name (mirrors the `Y int :: X; Z i32 :: Y` global chain).
record_demand(checker, statement.expr, value_type, locals^[:], local_types, pkg, file)
case .Assignment:
value_type := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded)
value_type := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types)
if statement.target != ast.INVALID_EXPR {
_ = infer_expr(checker, statement.target, locals^[:], pkg, file, demanded)
_ = infer_expr(checker, statement.target, locals^[:], pkg, file, demanded, local_types)
target_expr := checker.ast_module.exprs[statement.target]
if target_expr.kind == .Name && !symbol.is_valid(target_expr.qualifier) {
if local_index, ok := find_infer_local_index(locals^[:], target_expr.name); ok &&
@@ -1648,16 +1686,23 @@ infer_statements :: proc(
}
}
case .Expression:
_ = infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded)
_ = infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types)
case .Return:
if statement.expr != ast.INVALID_EXPR {
returned := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded)
returned := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types)
if is_runtime_type(checker, result_hint) {
expr := checker.ast_module.exprs[statement.expr]
if expr.kind == .Name && !symbol.is_valid(expr.qualifier) {
if local_index, ok := find_infer_local_index(locals^[:], expr.name); ok {
_ = merge_infer_local_type(checker, &locals^[local_index], result_hint, local_types)
// Open constants can adopt the result type cross-family; other
// locals widen within family as before.
if !merge_local_demand(checker, &locals^[local_index], result_hint, local_types) {
_ = merge_infer_local_type(checker, &locals^[local_index], result_hint, local_types)
}
returned = result_hint
} else {
// `return G` for a global const: demand the result type onto it.
record_demand(checker, statement.expr, result_hint, locals^[:], local_types, pkg, file)
}
}
}
@@ -1674,7 +1719,7 @@ infer_statements :: proc(
flatten_conditional_unwrap_operands(checker.ast_module, statement.expr, &operands)
operand_types := make([]types.Type, len(operands), checker.allocator)
for operand, index in operands {
operand_types[index] = infer_expr(checker, operand, locals^[:], pkg, file, demanded)
operand_types[index] = infer_expr(checker, operand, locals^[:], pkg, file, demanded, local_types)
}
capture_start := len(locals^)
for capture, index in statement.captures {
@@ -1689,7 +1734,7 @@ infer_statements :: proc(
append(locals, Infer_Local{name=capture, type=capture_type, declared=capture_type, statement=ast.INVALID_STMT})
}
if statement.guard != ast.INVALID_EXPR {
_ = infer_expr(checker, statement.guard, locals^[:], pkg, file, demanded)
_ = infer_expr(checker, statement.guard, locals^[:], pkg, file, demanded, local_types)
}
infer_statements(checker, statement.body, locals, local_types, pkg, file, demanded, result, result_hint)
resize(locals, capture_start)
@@ -1697,19 +1742,19 @@ infer_statements :: proc(
delete(operand_types, checker.allocator)
delete(operands)
} else {
_ = infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded)
_ = infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types)
infer_statements(checker, statement.body, locals, local_types, pkg, file, demanded, result, result_hint)
infer_statements(checker, statement.else_body, locals, local_types, pkg, file, demanded, result, result_hint)
}
case .While:
_ = infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded)
_ = infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types)
infer_statements(checker, statement.body, locals, local_types, pkg, file, demanded, result, result_hint)
if statement.update != ast.INVALID_STMT {
update := [1]ast.Stmt_Id{statement.update}
infer_statements(checker, update[:], locals, local_types, pkg, file, demanded, result, result_hint)
}
case .For:
iterable_type := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded)
iterable_type := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types)
capture_start := len(locals^)
capture_type := types.INVALID
if types.is_range(iterable_type, &checker.module.types) {
@@ -1797,11 +1842,158 @@ merge_inferred_type :: proc(store: ^types.Store, current: ^types.Type, inferred:
return false
}
// root_demand_target returns the global that an initializer pushes a backward type
// demand onto: when the initializer's root expression is a bare name referencing a
// global (e.g. `Z i32 :: Y`). Returns INVALID_GLOBAL for any other shape — demands
// deliberately do not flow through arithmetic, calls, or other operators (that is L3).
root_demand_target :: proc(checker: ^Checker, expr_id: ast.Expr_Id, pkg: ast.Package_Id, file: ast.File_Id) -> ast.Global_Id {
if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
return ast.INVALID_GLOBAL
}
expr := checker.ast_module.exprs[expr_id]
if expr.kind != .Name {
return ast.INVALID_GLOBAL
}
target_pkg, available := expr_package(checker, expr, pkg, file)
if !available {
return ast.INVALID_GLOBAL
}
return find_global(checker, expr.name, target_pkg)
}
// merge_open_const_demand records a concrete integer demand onto an open-constant
// global's slot. The constant is sign-agnostic until used, so it may adopt any
// integer family/width whose range holds its value (first demand wins; later demands
// may only widen within the chosen family). Non-integer or non-fitting demands are
// ignored, leaving the constant to default and the genuine mismatch to surface at the
// use's boundary coercion.
merge_open_const_demand :: proc(checker: ^Checker, slot: ^types.Type, demand: types.Type, value: i128) -> bool {
if !types.is_concrete_integer(demand) || !fits_integer_type(value, demand, checker.target) {
return false
}
if !is_runtime_type(checker, slot^) {
slot^ = demand
return true
}
if types.equal(slot^, demand) {
return false
}
merged := types.widest(slot^, demand)
if types.is_concrete_scalar(merged) && !types.equal(slot^, merged) {
slot^ = merged
return true
}
return false
}
// merge_global_demand routes a concrete demand onto a global's slot: open constants
// adopt any fitting family, other referents widen within family. Sets a dirty flag so
// a demand pushed from a function body re-triggers the inference fixpoint.
merge_global_demand :: proc(checker: ^Checker, global: ast.Global_Id, demand: types.Type) -> bool {
index := int(global)
if index < 0 || index >= len(checker.global_demands) {
return false
}
changed: bool
if checker.global_open_const[index] {
changed = merge_open_const_demand(checker, &checker.global_demands[index], demand, checker.global_const_value[index])
} else {
changed = merge_inferred_type(&checker.module.types, &checker.global_demands[index], demand)
}
checker.global_demands_dirty = checker.global_demands_dirty || changed
return changed
}
// merge_local_demand records a concrete integer demand onto an open-constant local.
// Like an open-constant global it adopts any integer family/width whose range holds its
// value (gated by its constraint family if it has one); the first demand replaces the
// literal's signed default, later demands may only widen within the chosen family.
merge_local_demand :: proc(checker: ^Checker, local: ^Infer_Local, demand: types.Type, local_types: []types.Type) -> bool {
if !local.open_const || !types.is_concrete_integer(demand) {
return false
}
if types.is_constraint(local.declared) &&
!types.constraint_accepts(local.declared, demand, &checker.module.types) {
return false
}
if !fits_integer_type(local.const_value, demand, checker.target) {
return false
}
if !local.demanded {
local.type = demand
local.demanded = true
record_infer_local_type(local^, local_types)
return true
}
if types.equal(local.type, demand) {
return false
}
merged := types.widest(local.type, demand)
if types.is_concrete_scalar(merged) && !types.equal(local.type, merged) {
local.type = merged
record_infer_local_type(local^, local_types)
return true
}
return false
}
// record_demand pushes a concrete type demand onto the slot of a bare-name expression
// (a typed declaration's initializer, a call argument, a return value). When the name
// resolves to an open-constant local or global, that slot adopts the demand; any other
// shape is ignored — demands flow only through bare names, never through arithmetic or
// across a call's result (the latter is milestone 14.5).
record_demand :: proc(
checker: ^Checker,
expr_id: ast.Expr_Id,
demand: types.Type,
locals: []Infer_Local,
local_types: []types.Type,
pkg: ast.Package_Id,
file: ast.File_Id,
) {
if !is_runtime_type(checker, demand) ||
expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
return
}
expr := checker.ast_module.exprs[expr_id]
if expr.kind != .Name {
return
}
if !symbol.is_valid(expr.qualifier) {
if index, ok := find_infer_local_index(locals, expr.name); ok {
_ = merge_local_demand(checker, &locals[index], demand, local_types)
return
}
}
target_pkg, available := expr_package(checker, expr, pkg, file)
if !available {
return
}
global := find_global(checker, expr.name, target_pkg)
if global != ast.INVALID_GLOBAL {
_ = merge_global_demand(checker, global, demand)
}
}
infer_all :: proc(checker: ^Checker) {
// An "open constant" global has no concrete declared type and a compile-time
// integer initializer. Its slot stays sign-agnostic so a backward demand from any
// reachable use can pick its family/width; absent a demand it defaults to the
// smallest signed type (legacy behaviour). Demands accumulate in global_demands so
// the default never blocks a later cross-family (e.g. unsigned) demand.
for global, index in checker.ast_module.globals {
declared := type_from_syntax(global.type)
if is_runtime_type(checker, declared) {
checker.global_types[index] = declared
continue
}
if global.external {
continue
}
constant := eval_constant(checker, global.expr)
if constant.kind == .Value && fits_i64(constant.value) {
checker.global_open_const[index] = true
checker.global_const_value[index] = constant.value
}
}
@@ -1812,7 +2004,29 @@ infer_all :: proc(checker: ^Checker) {
for {
changed := false
checker.global_demands_dirty = false
spec_count := len(checker.specs)
// Backward demands: a global whose initializer's root is a bare name referencing
// another global pushes its own (declared or already-resolved) type onto that
// referent. Open constants adopt any fitting family; other referents widen only.
for global, index in checker.ast_module.globals {
if global.external {
continue
}
demand := checker.global_types[index]
if !is_runtime_type(checker, demand) {
continue
}
target := root_demand_target(checker, global.expr, global.pkg, global.file)
if target != ast.INVALID_GLOBAL {
merge_global_demand(checker, target, demand)
}
}
// Forward / resolution. infer_expr runs for every non-external global (even
// concrete-typed ones) for its side effect of specializing called functions and
// recording demands from call arguments in their initializers.
for global, index in checker.ast_module.globals {
if global.external {
continue
@@ -1821,8 +2035,24 @@ infer_all :: proc(checker: ^Checker) {
if is_runtime_type(checker, type_from_syntax(global.type)) {
continue
}
changed = merge_inferred_type(&checker.module.types, &checker.global_types[index], inferred) || changed
if is_runtime_type(checker, checker.global_demands[index]) {
// A backward demand is authoritative; assign directly (it may cross the
// signed/unsigned family that widening would reject).
if !types.equal(checker.global_types[index], checker.global_demands[index]) {
checker.global_types[index] = checker.global_demands[index]
changed = true
}
} else if checker.global_open_const[index] {
resolved := types.smallest_signed_for_literal(i64(checker.global_const_value[index]))
if !types.equal(checker.global_types[index], resolved) {
checker.global_types[index] = resolved
changed = true
}
} else {
changed = merge_inferred_type(&checker.module.types, &checker.global_types[index], inferred) || changed
}
}
for index := 0; index < len(checker.specs); index += 1 {
id := spec_id(index)
inferred := infer_spec_result(checker, id)
@@ -1831,6 +2061,11 @@ infer_all :: proc(checker: ^Checker) {
if len(checker.specs) != spec_count {
changed = true
}
// A demand pushed onto a global from inside a function body (via the spec loop)
// is picked up by the next pass's resolution, so keep iterating for it.
if checker.global_demands_dirty {
changed = true
}
if !changed {
break
}
@@ -3487,9 +3722,19 @@ build_block :: proc(
switch statement.kind {
case .Declaration:
declared := resolve_inferred_array(checker, type_from_syntax(statement.type), statement.expr)
// Adopt the type inference resolved for this local when the declaration has no
// concrete annotation (a constraint, `undefined`, or an un-annotated open
// integer constant): the slot may have absorbed a backward demand (e.g. `a :: 10`
// built as u16 after `take_u16(a)`). Gated to compile-time integer constants so
// strings/arrays/pointers keep their own initializer type.
open_const_decl := !is_runtime_type(checker, declared) && !is_undefined_expr(checker, statement.expr)
if open_const_decl {
constant := eval_constant(checker, statement.expr)
open_const_decl = constant.kind == .Value && fits_i64(constant.value)
}
if statement_id != ast.INVALID_STMT && int(statement_id) < len(ctx.local_types) &&
is_runtime_type(checker, ctx.local_types[statement_id]) &&
(types.is_constraint(declared) || is_undefined_expr(checker, statement.expr)) {
(types.is_constraint(declared) || is_undefined_expr(checker, statement.expr) || open_const_decl) {
declared = ctx.local_types[statement_id]
}
// A still-unresolved constraint means the initializer's numeric
@@ -4352,6 +4597,15 @@ build_globals :: proc(checker: ^Checker) {
expected := types.INVALID
if is_runtime_type(checker, declared) {
expected = declared
} else if constant := eval_constant(checker, global.expr);
constant.kind == .Value && fits_i64(constant.value) &&
is_runtime_type(checker, checker.global_types[global_index]) {
// Open constant: build the initializer against the type inference resolved
// for this slot, so it adopts its demanded/defaulted type (e.g. `A :: 10`
// built as u16 when a use demanded u16). Gated to compile-time values fitting
// i64 — exactly the infer-side open-constant condition — so out-of-range
// constants keep their original "exceeds signed i64 range" diagnostic.
expected = checker.global_types[global_index]
}
expr := build_expr(checker, global.expr, nil, &dependencies, &calls, expected, global.pkg, global.file)
global_type := checker.global_types[global_index]
@@ -4645,6 +4899,9 @@ check :: proc(
checker.cycle_stack.allocator = allocator
build_symbol_indexes(&checker)
checker.global_types = make([]types.Type, len(ast_module.globals), allocator)
checker.global_demands = make([]types.Type, len(ast_module.globals), allocator)
checker.global_open_const = make([]bool, len(ast_module.globals), allocator)
checker.global_const_value = make([]i128, len(ast_module.globals), allocator)
checker.external_global_canonical = make([]ast.Global_Id, len(ast_module.globals), allocator)
checker.external_global_diagnostics = make([]source.Diagnostic_Id, len(ast_module.globals), allocator)
for &canonical in checker.external_global_canonical {
@@ -4667,6 +4924,9 @@ check :: proc(
delete(checker.global_index, allocator)
delete(checker.import_index, allocator)
delete(checker.global_types, allocator)
delete(checker.global_demands, allocator)
delete(checker.global_open_const, allocator)
delete(checker.global_const_value, allocator)
delete(checker.external_global_canonical, allocator)
delete(checker.external_global_diagnostics, allocator)
delete(checker.constants, allocator)