upgrade enum discriminants
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user