upgrade enum discriminants

This commit is contained in:
2026-07-17 20:32:12 +02:00
parent 866e28adb8
commit cedc63b28b
19 changed files with 3168 additions and 96 deletions
+131
View File
@@ -1294,6 +1294,136 @@ find_type_import :: proc(module: ^ast.Module, file: ast.File_Id, alias: symbol.I
return ast.INVALID_IMPORT
}
find_visible_enum_global :: proc(
module: ^ast.Module,
pkg: ast.Package_Id,
file: ast.File_Id,
name: symbol.Id,
public_only := false,
) -> ast.Global_Id {
for global, index in module.globals {
if global.pkg == pkg && global.name == name &&
(!global.file_hidden || !public_only && global.file == file) {
return ast.global_id(index)
}
}
return ast.INVALID_GLOBAL
}
eval_enum_global :: proc(
state: ^State,
id: ast.Global_Id,
visiting: []bool,
depth: int,
) -> (i128, bool) {
index := int(id)
if id == ast.INVALID_GLOBAL || index < 0 || index >= len(state.module.globals) ||
depth > 64 || visiting[index] {
return 0, false
}
global := state.module.globals[index]
if !global.immutable || global.external || global.expr == ast.INVALID_EXPR {
return 0, false
}
if types.is_valid(global.type) && !types.is_concrete_integer(global.type) {
return 0, false
}
visiting[index] = true
defer visiting[index] = false
return eval_enum_constant(state, global.expr, global.pkg, global.file, visiting, depth+1)
}
eval_enum_constant :: proc(
state: ^State,
id: ast.Expr_Id,
pkg: ast.Package_Id,
file: ast.File_Id,
visiting: []bool,
depth: int,
) -> (i128, bool) {
index := int(id)
if id == ast.INVALID_EXPR || index < 0 || index >= len(state.module.exprs) || depth > 64 {
return 0, false
}
expr := state.module.exprs[index]
#partial switch expr.kind {
case .Integer:
return i128(expr.integer), true
case .Negate:
value, ok := eval_enum_constant(state, expr.left, pkg, file, visiting, depth+1)
return -value, ok
case .Name:
if symbol.is_valid(expr.qualifier) {
import_id := find_type_import(state.module, file, expr.qualifier)
if import_id == ast.INVALID_IMPORT {
return 0, false
}
state.module.imports[import_id].used = true
import_item := state.module.imports[import_id]
if !import_item.valid || import_item.target == ast.INVALID_PACKAGE ||
int(import_item.target) >= len(state.module.packages) ||
!state.module.packages[import_item.target].available {
return 0, false
}
global := find_visible_enum_global(
state.module,
import_item.target,
ast.INVALID_FILE,
expr.name,
public_only=true,
)
return eval_enum_global(state, global, visiting, depth+1)
}
global := find_visible_enum_global(state.module, pkg, file, expr.name)
return eval_enum_global(state, global, visiting, depth+1)
}
return 0, false
}
resolve_enum_values :: proc(state: ^State) {
visiting := make([]bool, len(state.module.globals), state.allocator)
defer delete(visiting, state.allocator)
for declaration in state.module.enum_declarations {
members := types.enum_members_for(&state.module.type_store, declaration.type)
if len(members) != len(declaration.values) {
continue
}
next_value: i128
previous: i128
has_previous := false
previous_known := true
for &member, index in members {
spec := declaration.values[index]
value := next_value
known := true
if spec.explicit {
if spec.expr == ast.INVALID_EXPR {
value = member.value
} else if resolved, ok := eval_enum_constant(
state, spec.expr, declaration.pkg, declaration.file, visiting, 0,
); ok {
value = resolved
} else {
source.add(state.diagnostics, spec.span, "enum value must be an immutable integer constant")
known = false
}
}
if known {
member.value = value
if has_previous && previous_known && value <= previous {
source.add(state.diagnostics, spec.span, "enum values must be strictly increasing")
}
next_value = value+1
} else {
next_value = member.value+1
}
previous = value
previous_known = known
has_previous = true
}
}
}
alias_declarations_conflict :: proc(left_file: ast.File_Id, left_hidden: bool, right_file: ast.File_Id, right_hidden: bool) -> bool {
return left_file == right_file if left_hidden && right_hidden else true
}
@@ -1783,6 +1913,7 @@ load :: proc(
}
validate_imports(&state)
validate_declaration_aliases(&state)
resolve_enum_values(&state)
diagnose_qualified_type_uses(&state)
canonicalize_types(&module, allocator)
return module, !state.root_failed