Files
brolang/compiler/checker/checker.odin
T

3668 lines
125 KiB
Odin

package checker
import "../ast"
import "../hir"
import "../source"
import "../symbol"
import "../target"
import "../types"
import "base:intrinsics"
import "core:fmt"
import "core:mem"
import "core:slice"
import "core:strings"
Spec_Id :: distinct u32
INVALID_SPEC :: Spec_Id(0xffff_ffff)
spec_id :: proc(index: int) -> Spec_Id {
assert(index >= 0 && u64(index) < u64(INVALID_SPEC))
return Spec_Id(index)
}
spec_index :: proc(id: Spec_Id, count: int) -> (int, bool) {
index := int(id)
return index, id != INVALID_SPEC && index < count
}
Spec :: struct {
template: ast.Function_Id,
args: []types.Type,
result: types.Type,
hir_id: hir.Function_Id,
}
Infer_Local :: struct {
name: symbol.Id,
type: types.Type,
}
Build_Local :: struct {
name: symbol.Id,
type: types.Type,
mutable: bool,
id: hir.Local_Id,
}
// Build_Ctx threads the per-function accumulators through build_block so that
// nested control-flow blocks (if/else) can be built recursively. `locals` is a
// scope stack: each block records its entry length and truncates back to it on
// exit, while `hir_locals` keeps every allocated slot for the function.
Build_Ctx :: struct {
checker: ^Checker,
pkg: ast.Package_Id,
file: ast.File_Id,
result: types.Type,
locals: ^[dynamic]Build_Local,
hir_locals: ^[dynamic]hir.Local,
global_reads: ^[dynamic]hir.Global_Id,
calls: ^[dynamic]hir.Function_Id,
problematic: ^bool,
has_return: ^bool,
}
Constant_Kind :: enum {
Unknown,
Not_Constant,
Value,
Overflow,
}
Constant :: struct {
kind: Constant_Kind,
value: i128,
}
Function_Index_Entry :: struct {
scope: ast.Package_Id,
name: symbol.Id,
id: ast.Function_Id,
}
Global_Index_Entry :: struct {
scope: ast.Package_Id,
name: symbol.Id,
id: ast.Global_Id,
}
Import_Index_Entry :: struct {
scope: ast.File_Id,
name: symbol.Id,
id: ast.Import_Id,
}
Checker :: struct {
ast_module: ^ast.Module,
diagnostics: ^source.Diagnostics,
symbols: ^symbol.Table,
module: hir.Module,
specs: [dynamic]Spec,
function_index: []Function_Index_Entry,
global_index: []Global_Index_Entry,
import_index: []Import_Index_Entry,
global_types: []types.Type,
external_global_canonical: []ast.Global_Id,
external_global_diagnostics: []source.Diagnostic_Id,
constants: []Constant,
template_diagnostics: []source.Diagnostic_Id,
constant_stack: [dynamic]Constant_Frame,
ast_expr_stack: [dynamic]ast.Expr_Id,
hir_expr_stack: [dynamic]hir.Expr_Id,
infer_stack: [dynamic]Infer_Frame,
build_stack: [dynamic]Build_Expr_Frame,
cycle_stack: [dynamic]Cycle_Frame,
main_symbol: symbol.Id,
sink_symbol: symbol.Id,
target: target.Target,
allocator: mem.Allocator,
}
symbol_text :: proc(checker: ^Checker, id: symbol.Id) -> string {
return symbol.resolve(checker.symbols, id)
}
Constant_Frame :: struct {
expr: ast.Expr_Id,
stage: u8,
}
eval_constant :: proc(checker: ^Checker, expr_id: ast.Expr_Id) -> Constant {
if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
return Constant{kind = .Not_Constant}
}
stack := checker.constant_stack
clear_dynamic_array(&stack)
defer {
clear_dynamic_array(&stack)
checker.constant_stack = stack
}
append(&stack, Constant_Frame{expr=expr_id})
for len(stack) > 0 {
frame_index := len(stack)-1
frame := stack[frame_index]
if checker.constants[frame.expr].kind != .Unknown {
_ = pop(&stack)
continue
}
expr := checker.ast_module.exprs[frame.expr]
if expr.kind != .Add && expr.kind != .Negate {
result := Constant{kind = .Not_Constant}
if expr.kind == .Integer {
result = Constant{kind = .Value, value = i128(expr.integer)}
}
checker.constants[frame.expr] = result
_ = pop(&stack)
continue
}
if frame.stage == 0 {
stack[frame_index].stage = 1
if expr.left != ast.INVALID_EXPR && int(expr.left) < len(checker.ast_module.exprs) &&
checker.constants[expr.left].kind == .Unknown {
append(&stack, Constant_Frame{expr=expr.left})
}
continue
}
if frame.stage == 1 && expr.kind == .Negate {
operand := Constant{kind = .Not_Constant}
if expr.left != ast.INVALID_EXPR && int(expr.left) < len(checker.constants) {
operand = checker.constants[expr.left]
}
result := Constant{kind = .Not_Constant}
if operand.kind == .Overflow {
result = Constant{kind = .Overflow}
} else if operand.kind == .Value {
value, overflow := intrinsics.overflow_sub(i128(0), operand.value)
result = Constant{kind = .Overflow} if overflow else Constant{kind = .Value, value = value}
}
checker.constants[frame.expr] = result
_ = pop(&stack)
continue
}
if frame.stage == 1 {
stack[frame_index].stage = 2
if expr.right != ast.INVALID_EXPR && int(expr.right) < len(checker.ast_module.exprs) &&
checker.constants[expr.right].kind == .Unknown {
append(&stack, Constant_Frame{expr=expr.right})
}
continue
}
left := Constant{kind = .Not_Constant}
right := Constant{kind = .Not_Constant}
if expr.left != ast.INVALID_EXPR && int(expr.left) < len(checker.constants) {
left = checker.constants[expr.left]
}
if expr.right != ast.INVALID_EXPR && int(expr.right) < len(checker.constants) {
right = checker.constants[expr.right]
}
result := Constant{kind = .Not_Constant}
if left.kind == .Overflow || right.kind == .Overflow {
result = Constant{kind = .Overflow}
} else if left.kind == .Value && right.kind == .Value {
value, overflow := intrinsics.overflow_add(left.value, right.value)
result = Constant{kind = .Overflow} if overflow else Constant{kind = .Value, value = value}
}
checker.constants[frame.expr] = result
_ = pop(&stack)
}
return checker.constants[expr_id]
}
fits_signed_type :: proc(value: i128, value_type: types.Type, selected := target.DEFAULT) -> bool {
if !types.is_signed(value_type, selected) {
return false
}
limit := i128(1) << u32(types.bits(value_type, selected) - 1)
return value >= -limit && value < limit
}
fits_unsigned_type :: proc(value: i128, value_type: types.Type, selected := target.DEFAULT) -> bool {
if !types.is_unsigned(value_type, selected) || value < 0 {
return false
}
limit := i128(1) << u32(types.bits(value_type, selected))
return value < limit
}
fits_integer_type :: proc(value: i128, value_type: types.Type, selected := target.DEFAULT) -> bool {
return fits_signed_type(value, value_type, selected) || fits_unsigned_type(value, value_type, selected)
}
fits_i64 :: proc(value: i128) -> bool {
return fits_signed_type(value, types.I64)
}
type_from_syntax :: proc(value: ast.Type_Syntax) -> types.Type {
return value
}
is_runtime_type :: proc(checker: ^Checker, value: types.Type) -> bool {
return types.is_runtime_value(value, &checker.module.types)
}
string_literal_type :: proc(checker: ^Checker, string_id: u64) -> types.Type {
length: u64
if string_id < u64(len(checker.ast_module.strings)) {
length = u64(len(checker.ast_module.strings[string_id]))
}
array := types.array(&checker.module.types, types.U8, length, false, true, 0)
return types.pointer(&checker.module.types, array, false, false)
}
container_pointer_type :: proc(store: ^types.Store, item: types.Node) -> types.Type {
return types.pointer(store, item.child, item.mutable, true, item.has_sentinel, item.sentinel)
}
resolve_inferred_array :: proc(checker: ^Checker, value: types.Type, expr_id: ast.Expr_Id) -> types.Type {
item, ok := types.node(&checker.module.types, value)
if !ok || item.kind != .Array || !item.inferred_count ||
expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
return value
}
expr := checker.ast_module.exprs[expr_id]
if expr.kind != .Array {
return value
}
return types.with_array_count(&checker.module.types, value, u64(len(expr.args)))
}
function_index_less :: proc(left, right: Function_Index_Entry) -> bool {
if left.scope != right.scope {
return left.scope < right.scope
}
if left.name != right.name {
return int(left.name) < int(right.name)
}
return left.id < right.id
}
global_index_less :: proc(left, right: Global_Index_Entry) -> bool {
if left.scope != right.scope {
return left.scope < right.scope
}
if left.name != right.name {
return int(left.name) < int(right.name)
}
return left.id < right.id
}
import_index_less :: proc(left, right: Import_Index_Entry) -> bool {
if left.scope != right.scope {
return left.scope < right.scope
}
if left.name != right.name {
return int(left.name) < int(right.name)
}
return left.id < right.id
}
find_function_symbol :: proc(index: []Function_Index_Entry, scope: ast.Package_Id, name: symbol.Id) -> ast.Function_Id {
low := 0
high := len(index)
for low < high {
middle := low + (high-low)/2
entry := index[middle]
if entry.scope < scope || entry.scope == scope && int(entry.name) < int(name) {
low = middle + 1
} else {
high = middle
}
}
if low < len(index) && index[low].scope == scope && index[low].name == name {
return index[low].id
}
return ast.INVALID_FUNCTION
}
find_global_symbol :: proc(index: []Global_Index_Entry, scope: ast.Package_Id, name: symbol.Id) -> ast.Global_Id {
low := 0
high := len(index)
for low < high {
middle := low + (high-low)/2
entry := index[middle]
if entry.scope < scope || entry.scope == scope && int(entry.name) < int(name) {
low = middle + 1
} else {
high = middle
}
}
if low < len(index) && index[low].scope == scope && index[low].name == name {
return index[low].id
}
return ast.INVALID_GLOBAL
}
find_import_symbol :: proc(index: []Import_Index_Entry, scope: ast.File_Id, name: symbol.Id) -> ast.Import_Id {
low := 0
high := len(index)
for low < high {
middle := low + (high-low)/2
entry := index[middle]
if entry.scope < scope || entry.scope == scope && int(entry.name) < int(name) {
low = middle + 1
} else {
high = middle
}
}
if low < len(index) && index[low].scope == scope && index[low].name == name {
return index[low].id
}
return ast.INVALID_IMPORT
}
build_symbol_indexes :: proc(checker: ^Checker) {
checker.function_index = make([]Function_Index_Entry, len(checker.ast_module.functions), checker.allocator)
for function, id in checker.ast_module.functions {
checker.function_index[id] = Function_Index_Entry{scope=function.pkg, name=function.name, id=ast.function_id(id)}
}
slice.sort_by(checker.function_index, function_index_less)
checker.global_index = make([]Global_Index_Entry, len(checker.ast_module.globals), checker.allocator)
for global, id in checker.ast_module.globals {
checker.global_index[id] = Global_Index_Entry{scope=global.pkg, name=global.name, id=ast.global_id(id)}
}
slice.sort_by(checker.global_index, global_index_less)
checker.import_index = make([]Import_Index_Entry, len(checker.ast_module.imports), checker.allocator)
for import_item, id in checker.ast_module.imports {
checker.import_index[id] = Import_Index_Entry{scope=import_item.file, name=import_item.alias, id=ast.import_id(id)}
}
slice.sort_by(checker.import_index, import_index_less)
}
find_template :: proc(checker: ^Checker, name: symbol.Id, pkg := ast.Package_Id(0)) -> ast.Function_Id {
return find_function_symbol(checker.function_index, pkg, name)
}
find_global :: proc(checker: ^Checker, name: symbol.Id, pkg := ast.Package_Id(0)) -> ast.Global_Id {
return find_global_symbol(checker.global_index, pkg, name)
}
find_import :: proc(checker: ^Checker, file: ast.File_Id, alias: symbol.Id, mark_used := false) -> ast.Import_Id {
id := find_import_symbol(checker.import_index, file, alias)
if id != ast.INVALID_IMPORT && mark_used {
checker.ast_module.imports[id].used = true
}
return id
}
expr_package :: proc(checker: ^Checker, expr: ast.Expr, pkg: ast.Package_Id, file: ast.File_Id, mark_used := false) -> (ast.Package_Id, bool) {
if !symbol.is_valid(expr.qualifier) {
return pkg, true
}
import_id := find_import(checker, file, expr.qualifier, mark_used)
if import_id == ast.INVALID_IMPORT {
return ast.INVALID_PACKAGE, false
}
import_item := checker.ast_module.imports[import_id]
if import_item.target == ast.INVALID_PACKAGE || int(import_item.target) >= len(checker.ast_module.packages) ||
!checker.ast_module.packages[import_item.target].available {
return import_item.target, false
}
return import_item.target, true
}
add_package_resolution_diagnostic :: proc(checker: ^Checker, expr: ast.Expr, file: ast.File_Id) -> source.Diagnostic_Id {
if find_import(checker, file, expr.qualifier) == ast.INVALID_IMPORT {
return source.addf(checker.diagnostics, expr.span, "unknown package alias '%s'", symbol_text(checker, expr.qualifier))
}
return source.addf(checker.diagnostics, expr.span, "unavailable imported package '%s'", symbol_text(checker, expr.qualifier))
}
add_name_resolution_diagnostic :: proc(checker: ^Checker, expr: ast.Expr, target_pkg: ast.Package_Id) -> source.Diagnostic_Id {
if find_template(checker, expr.name, target_pkg) != ast.INVALID_FUNCTION {
return source.addf(checker.diagnostics, expr.span, "'%s' is a function, not a global value", symbol_text(checker, expr.name))
}
if symbol.is_valid(expr.qualifier) {
return source.addf(
checker.diagnostics,
expr.span,
"package '%s' has no member '%s'",
symbol_text(checker, expr.qualifier),
symbol_text(checker, expr.name),
)
}
return source.addf(checker.diagnostics, expr.span, "unresolved global '%s'", symbol_text(checker, expr.name))
}
add_call_resolution_diagnostic :: proc(checker: ^Checker, expr: ast.Expr, target_pkg: ast.Package_Id) -> source.Diagnostic_Id {
if find_global(checker, expr.name, target_pkg) != ast.INVALID_GLOBAL {
return source.addf(checker.diagnostics, expr.span, "'%s' is a global, not a function", symbol_text(checker, expr.name))
}
if symbol.is_valid(expr.qualifier) {
return source.addf(
checker.diagnostics,
expr.span,
"package '%s' has no member '%s'",
symbol_text(checker, expr.qualifier),
symbol_text(checker, expr.name),
)
}
return source.addf(checker.diagnostics, expr.span, "unresolved function '%s'", symbol_text(checker, expr.name))
}
find_unsupported :: proc(checker: ^Checker, pkg: ast.Package_Id, name: symbol.Id) -> (ast.Unsupported, bool) {
for item in checker.ast_module.unsupported {
if item.pkg == pkg && item.name == name {
return item, true
}
}
return {}, false
}
add_unsupported_diagnostic :: proc(checker: ^Checker, span: source.Span, pkg: ast.Package_Id, name: symbol.Id) -> source.Diagnostic_Id {
if item, ok := find_unsupported(checker, pkg, name); ok {
return source.addf(
checker.diagnostics,
span,
"C declaration '%s' is unavailable: %s",
symbol_text(checker, name),
item.reason,
)
}
return source.INVALID_DIAGNOSTIC
}
add_unsupported_type_diagnostic :: proc(
checker: ^Checker,
span: source.Span,
value: types.Type,
depth := 0,
) -> source.Diagnostic_Id {
if depth > 64 {
return source.INVALID_DIAGNOSTIC
}
item, ok := types.node(&checker.module.types, value)
if !ok {
return source.INVALID_DIAGNOSTIC
}
if item.kind == .Alias {
return add_unsupported_diagnostic(checker, span, ast.Package_Id(item.pkg), symbol.Id(item.name))
}
if types.is_valid(item.child) {
return add_unsupported_type_diagnostic(checker, span, item.child, depth+1)
}
return source.INVALID_DIAGNOSTIC
}
function_signatures_equal :: proc(left, right: ast.Function) -> bool {
if left.result != right.result || left.variadic != right.variadic || len(left.params) != len(right.params) {
return false
}
for param, index in left.params {
if param.type != right.params[index].type {
return false
}
}
return true
}
valid_call_arity :: proc(function: ast.Function, count: int) -> bool {
return count >= len(function.params) if function.variadic else count == len(function.params)
}
call_arg_expected :: proc(function: ast.Function, index: int) -> types.Type {
if index < 0 || index >= len(function.params) {
return types.INVALID
}
return type_from_syntax(function.params[index].type)
}
callable_arg_expected :: proc(function_type: types.Type, function_item: types.Node, store: ^types.Store, index: int) -> types.Type {
if index < 0 || index >= int(function_item.field_count) {
return types.INVALID
}
params := types.params_for(store, function_type)
if index >= len(params) {
return types.INVALID
}
return params[index].type
}
valid_callable_arity :: proc(function_item: types.Node, count: int) -> bool {
return count >= int(function_item.field_count) if function_item.variadic else count == int(function_item.field_count)
}
function_value_signature :: proc(
checker: ^Checker,
template: ast.Function_Id,
) -> (params: []types.Type, result: types.Type, ok: bool) {
if template == ast.INVALID_FUNCTION || int(template) >= len(checker.ast_module.functions) {
return nil, types.INVALID, false
}
function := checker.ast_module.functions[template]
if !function.c_abi {
return nil, types.INVALID, false
}
result = type_from_syntax(function.result)
if !types.is_void(result) && !is_runtime_type(checker, result) {
return nil, types.INVALID, false
}
params = make([]types.Type, len(function.params), checker.allocator)
for param, index in function.params {
param_type := type_from_syntax(param.type)
if !is_runtime_type(checker, param_type) {
delete(params, checker.allocator)
return nil, types.INVALID, false
}
params[index] = param_type
}
return params, result, true
}
function_pointer_type_for_template :: proc(
checker: ^Checker,
template: ast.Function_Id,
demanded: ^[dynamic]Spec_Id = nil,
) -> (types.Type, Spec_Id, bool) {
params, result, ok := function_value_signature(checker, template)
if !ok {
return types.INVALID, INVALID_SPEC, false
}
defer delete(params, checker.allocator)
function := checker.ast_module.functions[template]
function_type := types.function(&checker.module.types, params, result, true, function.variadic)
pointer_type := types.pointer(&checker.module.types, function_type, false, true)
spec := INVALID_SPEC
if demanded == nil {
spec = ensure_spec(checker, template, params)
} else {
spec = find_spec(checker, template, params)
mark_spec_demanded(checker, spec, demanded)
}
return pointer_type, spec, spec != INVALID_SPEC
}
contains_name :: proc(names: []symbol.Id, name: symbol.Id) -> bool {
for existing in names {
if existing == name {
return true
}
}
return false
}
mark_expr_imports_used :: proc(checker: ^Checker, expr_id: ast.Expr_Id, file: ast.File_Id) {
stack := checker.ast_expr_stack
clear_dynamic_array(&stack)
defer {
clear_dynamic_array(&stack)
checker.ast_expr_stack = stack
}
append(&stack, expr_id)
for len(stack) > 0 {
id := pop(&stack)
if id == ast.INVALID_EXPR || int(id) >= len(checker.ast_module.exprs) {
continue
}
expr := checker.ast_module.exprs[id]
if (expr.kind == .Name || expr.kind == .Call) && symbol.is_valid(expr.qualifier) {
_ = find_import(checker, file, expr.qualifier, true)
}
switch expr.kind {
case .Call:
append(&stack, ..expr.args)
if expr.left != ast.INVALID_EXPR {
append(&stack, expr.left)
}
case .Array, .Struct_Literal, .Slice:
append(&stack, ..expr.args)
if expr.left != ast.INVALID_EXPR {
append(&stack, expr.left)
}
case .Negate, .Not, .Address, .Deref, .Field, .Unwrap, .Keyed:
append(&stack, expr.left)
case .Add, .Index, .Orelse, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or:
append(&stack, expr.left, expr.right)
case .Invalid, .Integer, .Float, .String, .Bool, .None, .Name:
}
}
}
mark_block_imports_used :: proc(checker: ^Checker, statements: []ast.Stmt_Id, file: ast.File_Id) {
for statement_id in statements {
statement := checker.ast_module.statements[statement_id]
switch statement.kind {
case .Declaration, .Assignment, .Return, .Expression:
mark_expr_imports_used(checker, statement.expr, file)
if statement.target != ast.INVALID_EXPR {
mark_expr_imports_used(checker, statement.target, file)
}
case .If:
mark_expr_imports_used(checker, statement.expr, file)
mark_block_imports_used(checker, statement.body, file)
mark_block_imports_used(checker, statement.else_body, file)
case .Invalid:
}
}
}
validate_external_globals :: proc(checker: ^Checker) {
for global, global_index in checker.ast_module.globals {
checker.external_global_canonical[global_index] = ast.global_id(global_index)
if !global.external {
continue
}
switch global.link_name {
case "main":
checker.external_global_diagnostics[global_index] = source.add(
checker.diagnostics,
global.span,
"external C variable 'main' conflicts with the program entry point",
)
case "write":
checker.external_global_diagnostics[global_index] = source.add(
checker.diagnostics,
global.span,
"external C variable 'write' conflicts with the compiler runtime",
)
}
for previous, previous_index in checker.ast_module.globals[:global_index] {
if !previous.external || previous.link_name != global.link_name {
continue
}
canonical := checker.external_global_canonical[previous_index]
if canonical == ast.INVALID_GLOBAL {
canonical = ast.global_id(previous_index)
}
canonical_index := int(canonical)
if canonical_index < 0 || canonical_index >= len(checker.ast_module.globals) {
canonical = ast.global_id(previous_index)
canonical_index = previous_index
}
checker.external_global_canonical[global_index] = canonical
canonical_global := checker.ast_module.globals[canonical_index]
canonical_type := checker.global_types[canonical_index]
if !types.equal(checker.global_types[global_index], canonical_type) ||
global.writable != canonical_global.writable {
checker.external_global_diagnostics[global_index] = source.addf(
checker.diagnostics,
global.span,
"conflicting external C variable declarations for '%s'",
global.link_name,
)
}
checker.global_types[global_index] = canonical_type
break
}
for function in checker.ast_module.functions {
if !function.c_abi || function.has_body || len(function.unsupported_reason) > 0 ||
symbol_text(checker, function.name) != global.link_name {
continue
}
if checker.external_global_diagnostics[global_index] == source.INVALID_DIAGNOSTIC {
checker.external_global_diagnostics[global_index] = source.addf(
checker.diagnostics,
global.span,
"external C variable '%s' conflicts with a C function declaration",
global.link_name,
)
}
break
}
}
}
validate_declarations :: proc(checker: ^Checker) {
for function, function_id in checker.ast_module.functions {
if len(function.unsupported_reason) > 0 {
continue
}
locals: [dynamic]symbol.Id
locals.allocator = checker.allocator
for param in function.params {
if diagnostic := add_unsupported_type_diagnostic(checker, param.span, type_from_syntax(param.type));
diagnostic != source.INVALID_DIAGNOSTIC {
checker.template_diagnostics[function_id] = diagnostic
continue
}
if param.type == types.VOID {
source.add(
checker.diagnostics,
param.span,
"void is only valid as a function result type",
)
}
if contains_name(locals[:], param.name) {
source.addf(
checker.diagnostics,
param.span,
"duplicate parameter '%s'",
symbol_text(checker, param.name),
)
}
append(&locals, param.name)
if types.contains_c_struct_by_value(type_from_syntax(param.type), &checker.module.types) {
checker.template_diagnostics[function_id] = source.addf(
checker.diagnostics,
param.span,
"C records cannot be passed by value to '%s'",
symbol_text(checker, function.name),
)
}
}
if diagnostic := add_unsupported_type_diagnostic(checker, function.span, type_from_syntax(function.result));
diagnostic != source.INVALID_DIAGNOSTIC {
checker.template_diagnostics[function_id] = diagnostic
}
if types.contains_c_struct_by_value(type_from_syntax(function.result), &checker.module.types) {
checker.template_diagnostics[function_id] = source.addf(
checker.diagnostics,
function.span,
"C records cannot be returned by value from '%s'",
symbol_text(checker, function.name),
)
}
if !function.has_body && !function.c_abi {
checker.template_diagnostics[function_id] = source.addf(
checker.diagnostics,
function.span,
"bodyless function '%s' must use 'c_func'",
symbol_text(checker, function.name),
)
}
if function.variadic && (!function.c_abi || function.has_body) {
checker.template_diagnostics[function_id] = source.addf(
checker.diagnostics,
function.span,
"variadic function '%s' must be a bodyless 'c_func' declaration",
symbol_text(checker, function.name),
)
}
if !function.has_body && function.c_abi {
for param in function.params {
param_type := type_from_syntax(param.type)
if add_unsupported_type_diagnostic(checker, param.span, param_type) !=
source.INVALID_DIAGNOSTIC {
continue
}
if types.contains_c_struct_by_value(param_type, &checker.module.types) {
continue
}
if !types.is_c_signature_type(param_type, &checker.module.types) {
checker.template_diagnostics[function_id] = source.addf(
checker.diagnostics,
param.span,
"foreign function '%s' requires concrete parameter types",
symbol_text(checker, function.name),
)
}
}
result := type_from_syntax(function.result)
if add_unsupported_type_diagnostic(checker, function.span, result) == source.INVALID_DIAGNOSTIC &&
!types.contains_c_struct_by_value(result, &checker.module.types) &&
!types.is_c_signature_type(result, &checker.module.types, true) {
checker.template_diagnostics[function_id] = source.addf(
checker.diagnostics,
function.span,
"foreign function '%s' requires a concrete or void result type",
symbol_text(checker, function.name),
)
}
if function.pkg == 0 && function.name == checker.main_symbol {
checker.template_diagnostics[function_id] = source.add(
checker.diagnostics,
function.span,
"main must have a body",
)
}
}
mark_block_imports_used(checker, function.body, function.file)
delete(locals)
}
for function, function_id in checker.ast_module.functions {
if function.has_body || !function.c_abi {
continue
}
for other, other_id in checker.ast_module.functions {
if other_id == function_id || other.has_body || !other.c_abi || other.name != function.name {
continue
}
if function.imported && other.imported && function_signatures_equal(function, other) {
continue
}
checker.template_diagnostics[function_id] = source.addf(
checker.diagnostics,
function.span,
"duplicate foreign symbol '%s'",
symbol_text(checker, function.name),
)
break
}
}
}
validate_type_nodes :: proc(checker: ^Checker) {
for item, index in checker.module.types.nodes {
id := types.DYNAMIC_START+types.Type(index)
if item.has_sentinel {
value := i128(item.sentinel)
if types.is_signed(item.child, checker.target) {
value = i128(i64(item.sentinel))
}
if !types.is_concrete_integer(item.child) || !fits_integer_type(value, item.child, checker.target) {
source.addf(
checker.diagnostics,
source.Span{},
"sentinel value does not fit array, slice, or pointer element type %s",
types.name(item.child),
)
}
}
if item.kind == .Struct || item.kind == .Union {
if item.c_layout && !item.opaque && item.field_count == 0 {
source.add(
checker.diagnostics,
source.Span{},
"c_struct definitions require at least one field",
)
}
for field in types.fields_for(&checker.module.types, id) {
if !types.is_runtime_value(field.type, &checker.module.types) {
source.add(
checker.diagnostics,
source.Span{},
"record fields must have runtime value types",
)
} else if item.c_layout && !types.is_c_record_field_type(field.type, &checker.module.types) {
source.add(
checker.diagnostics,
source.Span{},
"c_struct fields must have C-layout-compatible types",
)
}
}
}
if item.kind == .Function {
if !item.c_abi {
source.add(checker.diagnostics, source.Span{}, "only c_func function pointer types are supported")
}
for param in types.params_for(&checker.module.types, id) {
if types.is_void(param.type) || !types.is_c_signature_type(param.type, &checker.module.types) {
source.add(checker.diagnostics, source.Span{}, "function pointer parameters must be concrete C signature types")
}
}
if !types.is_c_signature_type(item.child, &checker.module.types, true) {
source.add(checker.diagnostics, source.Span{}, "function pointer results must be concrete C signature types or void")
}
}
}
}
find_infer_local :: proc(locals: []Infer_Local, name: symbol.Id) -> types.Type {
for index := len(locals) - 1; index >= 0; index -= 1 {
if locals[index].name == name {
return locals[index].type
}
}
return types.INVALID
}
find_spec :: proc(checker: ^Checker, template: ast.Function_Id, actual_args: []types.Type) -> Spec_Id {
function := checker.ast_module.functions[template]
for spec, index in checker.specs {
if spec.template != template || len(spec.args) != len(function.params) {
continue
}
matches := true
for param, param_index in function.params {
actual := types.INVALID
if param_index < len(actual_args) {
actual = actual_args[param_index]
}
if !types.equal(spec.args[param_index], specialized_param_type(param.type, actual)) {
matches = false
break
}
}
if matches {
return spec_id(index)
}
}
return INVALID_SPEC
}
specialized_param_type :: proc(syntax: ast.Type_Syntax, actual: types.Type) -> types.Type {
declared := type_from_syntax(syntax)
if types.is_constraint(declared) {
return actual
}
return declared
}
can_specialize :: proc(checker: ^Checker, function: ast.Function, actual_args: []types.Type) -> bool {
for param, index in function.params {
actual := types.INVALID
if index < len(actual_args) {
actual = actual_args[index]
}
if !is_runtime_type(checker, specialized_param_type(param.type, actual)) {
return false
}
}
return true
}
ensure_spec :: proc(checker: ^Checker, template: ast.Function_Id, actual_args: []types.Type) -> Spec_Id {
if existing := find_spec(checker, template, actual_args); existing != INVALID_SPEC {
return existing
}
function := checker.ast_module.functions[template]
signature: [dynamic]types.Type
signature.allocator = checker.allocator
for param, index in function.params {
actual := types.INVALID
if index < len(actual_args) {
actual = actual_args[index]
}
append(&signature, specialized_param_type(param.type, actual))
}
result := type_from_syntax(function.result)
if function.pkg == 0 && function.name == checker.main_symbol && function.result == types.INT {
result = types.I32
}
index := spec_id(len(checker.specs))
append(
&checker.specs,
Spec{template = template, args = signature[:], result = result, hir_id = hir.INVALID_FUNCTION},
)
return index
}
mark_spec_demanded :: proc(checker: ^Checker, id: Spec_Id, stack: ^[dynamic]Spec_Id) {
if id == INVALID_SPEC || checker.specs[id].hir_id != hir.INVALID_FUNCTION {
return
}
checker.specs[id].hir_id = hir.Function_Id(0)
append(stack, id)
}
Infer_Frame :: struct {
expr: ast.Expr_Id,
stage: u8,
left: types.Type,
arg_index: int,
args: []types.Type,
template: ast.Function_Id,
}
infer_nested_expr :: proc(
checker: ^Checker,
expr_id: ast.Expr_Id,
locals: []Infer_Local,
pkg: ast.Package_Id,
file: ast.File_Id,
demanded: ^[dynamic]Spec_Id,
) -> 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)
delete(checker.infer_stack)
checker.infer_stack = outer
return result
}
infer_compound_expr :: proc(
checker: ^Checker,
expr: ast.Expr,
locals: []Infer_Local,
pkg: ast.Package_Id,
file: ast.File_Id,
demanded: ^[dynamic]Spec_Id,
) -> 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)
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)
return types.BOOL
case .String:
return string_literal_type(checker, expr.integer)
case .Array:
element := types.INVALID
for arg in expr.args {
actual := infer_nested_expr(checker, arg, locals, pkg, file, demanded)
if !types.is_valid(element) {
element = actual
} else if !types.equal(element, actual) {
element = types.widest(element, actual)
}
}
if !types.is_valid(element) {
element = types.I64
}
return types.array(store, element, u64(len(expr.args)), false)
case .None:
return types.INVALID
case .Address:
child := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded)
return types.pointer(store, child, false, false)
case .Deref:
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded)
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)
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)
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)
}
}
preserve := item.has_sentinel && expr.args[1] == ast.INVALID_EXPR
return types.slice(store, item.child, item.mutable, preserve, item.sentinel)
case .Field:
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded)
field_name := symbol_text(checker, expr.name)
item, has_item := types.container(value, store)
if has_item && (item.kind == .Array || item.kind == .Slice) {
if field_name == "len" {
return types.USIZE
}
if field_name == "ptr" &&
(item.kind == .Slice || types.is_pointer(value, store)) {
return container_pointer_type(store, item)
}
}
if types.is_pointer(value, store) {
value = types.child_type(value, store)
}
_, 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)
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)
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)
}
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)
case:
return types.INVALID
}
}
infer_expr :: proc(
checker: ^Checker,
expr_id: ast.Expr_Id,
locals: []Infer_Local,
pkg := ast.Package_Id(0),
file := ast.File_Id(0),
demanded: ^[dynamic]Spec_Id = nil,
) -> types.Type {
stack := checker.infer_stack
clear_dynamic_array(&stack)
defer {
for frame in stack {
delete(frame.args, checker.allocator)
}
clear_dynamic_array(&stack)
checker.infer_stack = stack
}
append(&stack, Infer_Frame{expr=expr_id, template=ast.INVALID_FUNCTION})
last := types.INVALID
for len(stack) > 0 {
frame_index := len(stack)-1
frame := stack[frame_index]
if frame.expr == ast.INVALID_EXPR || int(frame.expr) >= len(checker.ast_module.exprs) {
last = types.INVALID
_ = pop(&stack)
continue
}
expr := checker.ast_module.exprs[frame.expr]
if frame.stage == 0 {
constant := eval_constant(checker, frame.expr)
if constant.kind == .Overflow || (constant.kind == .Value && !fits_i64(constant.value)) {
last = types.I64
_ = pop(&stack)
continue
}
if constant.kind == .Value {
last = types.smallest_signed_for_literal(i64(constant.value))
_ = pop(&stack)
continue
}
switch expr.kind {
case .Invalid:
last = types.INVALID
_ = pop(&stack)
case .Integer:
last = types.I64
if expr.integer <= 0x7fff_ffff_ffff_ffff {
last = types.smallest_signed_for_literal(i64(expr.integer))
}
_ = pop(&stack)
case .Float:
last = types.F64
_ = pop(&stack)
case .String, .Array, .None, .Address, .Deref, .Index, .Slice,
.Field, .Unwrap, .Orelse, .Struct_Literal, .Keyed,
.Bool, .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or:
last = infer_compound_expr(checker, expr, locals, pkg, file, demanded)
_ = pop(&stack)
case .Name:
last = types.INVALID
if !symbol.is_valid(expr.qualifier) {
last = find_infer_local(locals, expr.name)
} else {
base_type := find_infer_local(locals, expr.qualifier)
item, has_item := types.container(base_type, &checker.module.types)
field_name := symbol_text(checker, expr.name)
if has_item && (item.kind == .Array || item.kind == .Slice) {
if field_name == "len" {
last = types.USIZE
} else if field_name == "ptr" &&
(item.kind == .Slice || types.is_pointer(base_type, &checker.module.types)) {
last = container_pointer_type(&checker.module.types, item)
}
}
if types.is_pointer(base_type, &checker.module.types) {
base_type = types.child_type(base_type, &checker.module.types)
}
if !types.is_valid(last) {
_, field, ok := find_struct_field(checker, base_type, expr.name)
if ok {
last = field.type
}
}
}
if !types.is_valid(last) {
target_pkg, available := expr_package(checker, expr, pkg, file)
if available {
global := find_global(checker, expr.name, target_pkg)
if global != ast.INVALID_GLOBAL {
last = checker.global_types[global]
}
}
}
if !types.is_valid(last) {
target_pkg, available := expr_package(checker, expr, pkg, file)
if available {
template := find_template(checker, expr.name, target_pkg)
if template != ast.INVALID_FUNCTION &&
len(checker.ast_module.functions[template].unsupported_reason) == 0 &&
checker.template_diagnostics[template] == source.INVALID_DIAGNOSTIC {
pointer_type, _, ok := function_pointer_type_for_template(checker, template, demanded)
if ok {
last = pointer_type
}
}
}
}
_ = pop(&stack)
case .Negate:
stack[frame_index].stage = 5
append(&stack, Infer_Frame{expr=expr.left, template=ast.INVALID_FUNCTION})
case .Add:
stack[frame_index].stage = 1
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)
_, function_item, function_type, ok := types.function_pointer(callee_type, &checker.module.types)
if !ok {
last = types.INVALID
_ = pop(&stack)
continue
}
stack[frame_index].left = function_type
stack[frame_index].args = make([]types.Type, len(expr.args), checker.allocator)
stack[frame_index].stage = 6
if len(expr.args) > 0 {
append(&stack, Infer_Frame{expr=expr.args[0], template=ast.INVALID_FUNCTION})
} else if valid_callable_arity(function_item, 0) {
last = function_item.child
delete(stack[frame_index].args, checker.allocator)
stack[frame_index].args = nil
_ = pop(&stack)
}
continue
}
target_pkg, available := expr_package(checker, expr, pkg, file)
template := ast.INVALID_FUNCTION
if available {
template = find_template(checker, expr.name, target_pkg)
}
if template == ast.INVALID_FUNCTION {
callee_type := types.INVALID
if !symbol.is_valid(expr.qualifier) {
callee_type = find_infer_local(locals, expr.name)
}
if !types.is_valid(callee_type) && available {
global := find_global(checker, expr.name, target_pkg)
if global != ast.INVALID_GLOBAL {
callee_type = checker.global_types[global]
}
}
_, function_item, function_type, ok := types.function_pointer(callee_type, &checker.module.types)
if !ok {
last = types.INVALID
_ = pop(&stack)
continue
}
stack[frame_index].left = function_type
stack[frame_index].args = make([]types.Type, len(expr.args), checker.allocator)
stack[frame_index].stage = 6
if len(expr.args) > 0 {
append(&stack, Infer_Frame{expr=expr.args[0], template=ast.INVALID_FUNCTION})
} else if valid_callable_arity(function_item, 0) {
last = function_item.child
delete(stack[frame_index].args, checker.allocator)
stack[frame_index].args = nil
_ = pop(&stack)
}
continue
}
if len(checker.ast_module.functions[template].unsupported_reason) > 0 {
last = types.INVALID
_ = pop(&stack)
continue
}
if checker.template_diagnostics[template] != source.INVALID_DIAGNOSTIC {
declared := type_from_syntax(checker.ast_module.functions[template].result)
last = declared if is_runtime_type(checker, declared) || types.is_void(declared) else types.INVALID
_ = pop(&stack)
continue
}
stack[frame_index].template = template
stack[frame_index].args = make([]types.Type, len(expr.args), checker.allocator)
stack[frame_index].stage = 3
if len(expr.args) > 0 {
append(&stack, Infer_Frame{expr=expr.args[0], template=ast.INVALID_FUNCTION})
}
}
continue
}
if frame.stage == 5 {
if !types.is_signed(last, checker.target) && !types.is_float(last, checker.target) {
last = types.INVALID
}
_ = pop(&stack)
continue
}
if frame.stage == 1 {
stack[frame_index].left = last
stack[frame_index].stage = 2
append(&stack, Infer_Frame{expr=expr.right, template=ast.INVALID_FUNCTION})
continue
}
if frame.stage == 2 {
if types.is_many_pointer(frame.left, &checker.module.types) && types.is_concrete_integer(last) {
last = frame.left
} else {
last = types.widest(frame.left, last)
}
_ = pop(&stack)
continue
}
if frame.stage == 3 {
if frame.arg_index < len(expr.args) {
stack[frame_index].args[frame.arg_index] = last
stack[frame_index].arg_index += 1
if frame.arg_index+1 < len(expr.args) {
append(&stack, Infer_Frame{expr=expr.args[frame.arg_index+1], template=ast.INVALID_FUNCTION})
continue
}
}
function := checker.ast_module.functions[frame.template]
if valid_call_arity(function, len(expr.args)) &&
can_specialize(checker, function, stack[frame_index].args) {
spec := INVALID_SPEC
if demanded == nil {
spec = ensure_spec(checker, frame.template, stack[frame_index].args)
} else {
spec = find_spec(checker, frame.template, stack[frame_index].args)
mark_spec_demanded(checker, spec, demanded)
}
if spec != INVALID_SPEC {
last = checker.specs[spec].result
} else {
declared := type_from_syntax(function.result)
last = declared if is_runtime_type(checker, declared) || types.is_void(declared) else types.INVALID
}
} else {
declared := type_from_syntax(function.result)
if function.pkg == 0 && function.name == checker.main_symbol && function.result == types.INT {
last = types.I32
} else {
last = declared if is_runtime_type(checker, declared) || types.is_void(declared) else types.INVALID
}
}
delete(stack[frame_index].args, checker.allocator)
stack[frame_index].args = nil
_ = pop(&stack)
}
if frame.stage == 6 {
if frame.arg_index < len(expr.args) {
stack[frame_index].args[frame.arg_index] = last
stack[frame_index].arg_index += 1
if frame.arg_index+1 < len(expr.args) {
append(&stack, Infer_Frame{expr=expr.args[frame.arg_index+1], template=ast.INVALID_FUNCTION})
continue
}
}
function_item, ok := types.node(&checker.module.types, frame.left)
if ok && function_item.kind == .Function && valid_callable_arity(function_item, len(expr.args)) {
last = function_item.child
} else {
last = types.INVALID
}
delete(stack[frame_index].args, checker.allocator)
stack[frame_index].args = nil
_ = pop(&stack)
}
}
return last
}
infer_statements :: proc(
checker: ^Checker,
statements: []ast.Stmt_Id,
locals: ^[dynamic]Infer_Local,
pkg: ast.Package_Id,
file: ast.File_Id,
demanded: ^[dynamic]Spec_Id,
result: ^types.Type,
) {
scope_start := len(locals^)
for statement_id in statements {
statement := checker.ast_module.statements[statement_id]
#partial switch statement.kind {
case .Declaration:
value_type := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded)
declared_local := type_from_syntax(statement.type)
if is_runtime_type(checker, declared_local) {
value_type = declared_local
}
append(locals, Infer_Local{name = statement.name, type = value_type})
case .Assignment, .Expression:
if statement.target != ast.INVALID_EXPR {
_ = infer_expr(checker, statement.target, locals^[:], pkg, file, demanded)
}
_ = infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded)
case .Return:
if statement.expr != ast.INVALID_EXPR {
returned := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded)
if !types.is_valid(result^) {
result^ = returned
} else {
result^ = types.widest(result^, returned)
}
}
case .If:
_ = infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded)
infer_statements(checker, statement.body, locals, pkg, file, demanded, result)
infer_statements(checker, statement.else_body, locals, pkg, file, demanded, result)
}
}
resize(locals, scope_start)
}
infer_spec_result :: proc(checker: ^Checker, id: Spec_Id, demanded: ^[dynamic]Spec_Id = nil) -> types.Type {
spec := checker.specs[id]
function := checker.ast_module.functions[spec.template]
declared := type_from_syntax(function.result)
if function.pkg == 0 && function.name == checker.main_symbol && function.result == types.INT {
declared = types.I32
}
locals: [dynamic]Infer_Local
locals.allocator = checker.allocator
defer delete(locals)
for param, index in function.params {
param_type := types.INVALID
if index < len(spec.args) {
param_type = spec.args[index]
}
append(&locals, Infer_Local{name = param.name, type = param_type})
}
result := types.INVALID
infer_statements(checker, function.body, &locals, function.pkg, function.file, demanded, &result)
if types.is_constraint(declared) {
return result
}
return declared
}
merge_inferred_type :: proc(store: ^types.Store, current: ^types.Type, inferred: types.Type) -> bool {
if !types.is_runtime_value(inferred, store) {
return false
}
if !types.is_runtime_value(current^, store) {
current^ = inferred
return true
}
if types.equal(current^, inferred) {
return false
}
merged := types.widest(current^, inferred)
if types.is_concrete_scalar(merged) && !types.equal(current^, merged) {
current^ = merged
return true
}
return false
}
infer_all :: proc(checker: ^Checker) {
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
}
}
main_template := find_template(checker, checker.main_symbol, 0)
if main_template != ast.INVALID_FUNCTION {
ensure_spec(checker, main_template, nil)
}
for {
changed := false
spec_count := len(checker.specs)
for global, index in checker.ast_module.globals {
if global.external {
continue
}
inferred := infer_expr(checker, global.expr, nil, global.pkg, global.file)
if is_runtime_type(checker, type_from_syntax(global.type)) {
continue
}
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)
changed = merge_inferred_type(&checker.module.types, &checker.specs[id].result, inferred) || changed
}
if len(checker.specs) != spec_count {
changed = true
}
if !changed {
break
}
}
}
prune_specs :: proc(checker: ^Checker) {
stack: [dynamic]Spec_Id
stack.allocator = checker.allocator
defer delete(stack)
main_template := find_template(checker, checker.main_symbol, 0)
if main_template != ast.INVALID_FUNCTION {
mark_spec_demanded(checker, find_spec(checker, main_template, nil), &stack)
}
for global in checker.ast_module.globals {
if global.external {
continue
}
_ = infer_expr(checker, global.expr, nil, global.pkg, global.file, &stack)
}
for len(stack) > 0 {
id := pop(&stack)
_ = infer_spec_result(checker, id, &stack)
}
retained := 0
for spec in checker.specs {
if spec.hir_id == hir.INVALID_FUNCTION {
delete(spec.args, checker.allocator)
continue
}
checker.specs[retained] = spec
checker.specs[retained].hir_id = hir.function_id(retained)
retained += 1
}
for len(checker.specs) > retained {
_ = pop(&checker.specs)
}
}
add_hir_expr :: proc(checker: ^Checker, expr: hir.Expr) -> hir.Expr_Id {
id := hir.expr_id(len(checker.module.exprs))
append(&checker.module.exprs, expr)
return id
}
invalid_hir_expr :: proc(
checker: ^Checker,
span: source.Span,
diagnostic: source.Diagnostic_Id,
recovery_type := types.INVALID,
) -> hir.Expr_Id {
return add_hir_expr(
checker,
hir.Expr {
kind = .Invalid,
span = span,
type = recovery_type,
target = hir.INVALID_REF,
left = hir.INVALID_EXPR,
right = hir.INVALID_EXPR,
diagnostic = diagnostic,
},
)
}
add_unique_global :: proc(values: ^[dynamic]hir.Global_Id, value: hir.Global_Id) {
for existing in values {
if existing == value {
return
}
}
append(values, value)
}
build_global_reference :: proc(
checker: ^Checker,
global: ast.Global_Id,
span: source.Span,
global_reads: ^[dynamic]hir.Global_Id,
) -> hir.Expr_Id {
if int(global) < len(checker.external_global_diagnostics) {
diagnostic := checker.external_global_diagnostics[global]
if diagnostic != source.INVALID_DIAGNOSTIC {
return invalid_hir_expr(checker, span, diagnostic, checker.global_types[global])
}
}
hir_global := hir.Global_Id(global)
add_unique_global(global_reads, hir_global)
return add_hir_expr(checker, hir.Expr{
kind=.Global, span=span, type=checker.global_types[global],
target=hir.global_ref(hir_global), left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
add_unique_function :: proc(values: ^[dynamic]hir.Function_Id, value: hir.Function_Id) {
for existing in values {
if existing == value {
return
}
}
append(values, value)
}
find_build_local :: proc(locals: []Build_Local, name: symbol.Id) -> (Build_Local, bool) {
for index := len(locals) - 1; index >= 0; index -= 1 {
if locals[index].name == name {
return locals[index], true
}
}
return Build_Local{}, false
}
coerce_expr :: proc(
checker: ^Checker,
expr_id: hir.Expr_Id,
expected: types.Type,
span: source.Span,
) -> hir.Expr_Id {
if expr_id == hir.INVALID_EXPR {
return expr_id
}
actual := checker.module.exprs[expr_id].type
if types.equal(actual, expected) {
return expr_id
}
if types.can_weaken_pointer(actual, expected, &checker.module.types) {
return add_hir_expr(checker, hir.Expr{
kind=.Weaken_Pointer,
span=span,
type=expected,
left=expr_id,
target=hir.INVALID_REF,
right=hir.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
if types.can_weaken_slice(actual, expected, &checker.module.types) {
return add_hir_expr(checker, hir.Expr{
kind=.Weaken_Slice,
span=span,
type=expected,
left=expr_id,
target=hir.INVALID_REF,
right=hir.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
if types.can_decay_array_pointer(actual, expected, &checker.module.types) {
return add_hir_expr(checker, hir.Expr{
kind=.Decay_Array_Pointer,
span=span,
type=expected,
left=expr_id,
target=hir.INVALID_REF,
right=hir.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
if types.is_optional(expected, &checker.module.types) {
child := types.child_type(expected, &checker.module.types)
if types.equal(actual, child) ||
types.can_widen(actual, child) ||
types.can_weaken_pointer(actual, child, &checker.module.types) ||
types.can_weaken_slice(actual, child, &checker.module.types) ||
types.can_decay_array_pointer(actual, child, &checker.module.types) {
value := coerce_expr(checker, expr_id, child, span)
return add_hir_expr(checker, hir.Expr{
kind=.Optional_Some,
span=span,
type=expected,
left=value,
target=hir.INVALID_REF,
right=hir.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
}
if types.can_widen(actual, expected) {
return add_hir_expr(
checker,
hir.Expr {
kind = .Widen,
span = span,
type = expected,
left = expr_id,
target = hir.INVALID_REF,
right = hir.INVALID_EXPR,
diagnostic = source.INVALID_DIAGNOSTIC,
},
)
}
id := source.addf(
checker.diagnostics,
span,
"cannot implicitly convert %s to %s",
types.name(actual),
types.name(expected),
)
return invalid_hir_expr(checker, span, id, expected)
}
promote_c_vararg_expr :: proc(checker: ^Checker, expr_id: hir.Expr_Id, span: source.Span) -> hir.Expr_Id {
actual := checker.module.exprs[expr_id].type
if !types.is_c_vararg_type(actual, &checker.module.types) {
id := source.addf(
checker.diagnostics,
span,
"C variadic argument must be a concrete scalar or pointer, got %s",
types.name(actual),
)
return invalid_hir_expr(checker, span, id, types.C_INT)
}
promoted := types.c_vararg_promotion(actual, checker.target)
if types.equal(actual, promoted) {
return expr_id
}
return add_hir_expr(checker, hir.Expr{
kind=.C_Vararg_Promote,
span=span,
type=promoted,
left=expr_id,
target=hir.INVALID_REF,
right=hir.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
build_constant_expr :: proc(
checker: ^Checker,
expr: ast.Expr,
constant: Constant,
expected: types.Type,
) -> hir.Expr_Id {
recovery_type := types.I64
if types.is_concrete_integer(expected) {
recovery_type = expected
}
if constant.kind == .Overflow ||
(!types.is_concrete_integer(expected) && !fits_i64(constant.value)) {
id := source.add(
checker.diagnostics,
expr.span,
"integer constant expression exceeds signed i64 range",
)
return invalid_hir_expr(checker, expr.span, id, recovery_type)
}
value := i64(constant.value)
if constant.value >= 0 && constant.value <= i128(0xffff_ffff_ffff_ffff) {
value = transmute(i64)u64(constant.value)
}
result_type := types.smallest_signed_for_literal(value)
if types.is_concrete_integer(expected) {
if !fits_integer_type(constant.value, expected, checker.target) {
id := source.addf(
checker.diagnostics,
expr.span,
"integer constant %d does not fit in %s",
constant.value,
types.name(expected),
)
return invalid_hir_expr(checker, expr.span, id, expected)
}
result_type = expected
}
return add_hir_expr(
checker,
hir.Expr {
kind = .Integer,
span = expr.span,
type = result_type,
integer = value,
target = hir.INVALID_REF,
left = hir.INVALID_EXPR,
right = hir.INVALID_EXPR,
diagnostic = source.INVALID_DIAGNOSTIC,
},
)
}
build_float_expr :: proc(checker: ^Checker, expr: ast.Expr, expected: types.Type) -> hir.Expr_Id {
result_type := types.F64
if types.is_float(expected, checker.target) {
result_type = expected
} else if types.is_valid(expected) {
id := source.addf(
checker.diagnostics,
expr.span,
"cannot implicitly convert f64 to %s",
types.name(expected),
)
return invalid_hir_expr(checker, expr.span, id, expected)
}
value := transmute(f64)expr.integer
bits := transmute(i64)value
if types.bits(result_type, checker.target) == 32 {
bits = i64(transmute(u32)f32(value))
}
return add_hir_expr(checker, hir.Expr{
kind=.Float,
span=expr.span,
type=result_type,
integer=bits,
target=hir.INVALID_REF,
left=hir.INVALID_EXPR,
right=hir.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
Build_Expr_Frame :: struct {
expr: ast.Expr_Id,
expected: types.Type,
stage: u8,
left: hir.Expr_Id,
arg_index: int,
built_args: []hir.Expr_Id,
arg_types: []types.Type,
template: ast.Function_Id,
}
hir_location_writable :: proc(checker: ^Checker, expr_id: hir.Expr_Id, locals: []Build_Local) -> bool {
if expr_id == hir.INVALID_EXPR || int(expr_id) >= len(checker.module.exprs) {
return false
}
expr := checker.module.exprs[expr_id]
#partial switch expr.kind {
case .Local:
id := hir.as_local(expr.target)
for local in locals {
if local.id == id {
return local.mutable
}
}
case .Global:
id := hir.as_global(expr.target)
return id != hir.INVALID_GLOBAL && int(id) < len(checker.module.globals) &&
checker.module.globals[id].writable
case .Deref:
pointer_type := checker.module.exprs[expr.left].type
return types.is_mutable(pointer_type, &checker.module.types)
case .Index:
container_type := checker.module.exprs[expr.left].type
item, ok := types.container(container_type, &checker.module.types)
if !ok || !item.mutable {
return false
}
if types.is_array(container_type, &checker.module.types) {
return hir_location_writable(checker, expr.left, locals)
}
return true
case .Field:
base_type := checker.module.exprs[expr.left].type
if types.is_pointer(base_type, &checker.module.types) {
return types.is_mutable(base_type, &checker.module.types)
}
return hir_location_writable(checker, expr.left, locals)
case:
}
return false
}
hir_is_location :: proc(checker: ^Checker, expr_id: hir.Expr_Id) -> bool {
if expr_id == hir.INVALID_EXPR || int(expr_id) >= len(checker.module.exprs) {
return false
}
#partial switch checker.module.exprs[expr_id].kind {
case .Local, .Global, .Deref, .Index, .Field:
return true
}
return false
}
find_struct_field :: proc(checker: ^Checker, struct_type: types.Type, name: symbol.Id) -> (int, types.Field, bool) {
for field, index in types.fields_for(&checker.module.types, struct_type) {
if field.name == u32(name) {
return index, field, true
}
}
return 0, {}, false
}
build_function_value :: proc(
checker: ^Checker,
template: ast.Function_Id,
span: source.Span,
expected: types.Type,
) -> hir.Expr_Id {
if template == ast.INVALID_FUNCTION || int(template) >= len(checker.ast_module.functions) {
return hir.INVALID_EXPR
}
function := checker.ast_module.functions[template]
if len(function.unsupported_reason) > 0 {
id := source.addf(
checker.diagnostics,
span,
"C declaration '%s' is unavailable: %s",
symbol_text(checker, function.name),
function.unsupported_reason,
)
return invalid_hir_expr(checker, span, id)
}
if checker.template_diagnostics[template] != source.INVALID_DIAGNOSTIC {
return invalid_hir_expr(checker, span, checker.template_diagnostics[template])
}
params, result, ok := function_value_signature(checker, template)
if !ok {
id := source.addf(
checker.diagnostics,
span,
"function '%s' cannot be used as a C callback; expected a concrete c_func",
symbol_text(checker, function.name),
)
return invalid_hir_expr(checker, span, id)
}
defer delete(params, checker.allocator)
function_type := types.function(&checker.module.types, params, result, true, function.variadic)
pointer_type := types.pointer(&checker.module.types, function_type, false, true)
spec := find_spec(checker, template, params)
if spec == INVALID_SPEC {
id := source.addf(
checker.diagnostics,
span,
"could not resolve callback specialization of '%s'",
symbol_text(checker, function.name),
)
return invalid_hir_expr(checker, span, id, pointer_type)
}
function_id := checker.specs[spec].hir_id
assert(function_id != hir.INVALID_FUNCTION)
return add_hir_expr(checker, hir.Expr{
kind=.Function,
span=span,
type=pointer_type,
target=hir.function_ref(function_id),
left=hir.INVALID_EXPR,
right=hir.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
build_nested_expr :: proc(
checker: ^Checker,
expr_id: ast.Expr_Id,
locals: []Build_Local,
global_reads: ^[dynamic]hir.Global_Id,
calls: ^[dynamic]hir.Function_Id,
expected: types.Type,
pkg: ast.Package_Id,
file: ast.File_Id,
) -> hir.Expr_Id {
outer := checker.build_stack
checker.build_stack = nil
checker.build_stack.allocator = checker.allocator
result := build_expr(checker, expr_id, locals, global_reads, calls, expected, pkg, file)
delete(checker.build_stack)
checker.build_stack = outer
return result
}
build_compound_expr :: proc(
checker: ^Checker,
expr: ast.Expr,
locals: []Build_Local,
global_reads: ^[dynamic]hir.Global_Id,
calls: ^[dynamic]hir.Function_Id,
expected: types.Type,
pkg: ast.Package_Id,
file: ast.File_Id,
) -> hir.Expr_Id {
store := &checker.module.types
#partial switch expr.kind {
case .String:
string_type := string_literal_type(checker, expr.integer)
return add_hir_expr(checker, hir.Expr{
kind=.String, span=expr.span, type=string_type, integer=i64(expr.integer),
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
case .Array:
element_type := types.INVALID
result_type := expected
expected_node, has_expected := types.node(store, expected)
if !has_expected || expected_node.kind != .Array {
has_expected = false
result_type = types.INVALID
} else {
element_type = expected_node.child
if !expected_node.inferred_count && expected_node.count != u64(len(expr.args)) {
id := source.addf(
checker.diagnostics, expr.span,
"array literal expects %d elements, got %d",
expected_node.count, len(expr.args),
)
return invalid_hir_expr(checker, expr.span, id, expected)
}
if expected_node.inferred_count {
result_type = types.with_array_count(store, expected, u64(len(expr.args)))
}
}
if !has_expected {
infer_locals := make([]Infer_Local, len(locals), checker.allocator)
defer delete(infer_locals, checker.allocator)
for local, index in locals {
infer_locals[index] = Infer_Local{name=local.name, type=local.type}
}
for arg in expr.args {
actual := infer_nested_expr(checker, arg, infer_locals, pkg, file, nil)
if !types.is_valid(element_type) {
element_type = actual
} else {
element_type = types.widest(element_type, actual)
}
}
if !types.is_valid(element_type) {
element_type = types.I64
}
result_type = types.array(store, element_type, u64(len(expr.args)), false)
}
args := make([]hir.Expr_Id, len(expr.args), checker.allocator)
for arg, index in expr.args {
args[index] = build_nested_expr(
checker, arg, locals, global_reads, calls, element_type, pkg, file,
)
args[index] = coerce_expr(checker, args[index], element_type, checker.module.exprs[args[index]].span)
}
return add_hir_expr(checker, hir.Expr{
kind=.Array, span=expr.span, type=result_type, args=args,
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
case .None:
if !types.is_optional(expected, store) {
id := source.add(checker.diagnostics, expr.span, "'none' requires an optional context")
return invalid_hir_expr(checker, expr.span, id, expected)
}
return add_hir_expr(checker, hir.Expr{
kind=.None, span=expr.span, type=expected, target=hir.INVALID_REF,
left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
})
case .Address:
value := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
if !hir_is_location(checker, value) {
id := source.add(checker.diagnostics, expr.span, "'&' requires an addressable location")
return invalid_hir_expr(checker, expr.span, id)
}
value_type := checker.module.exprs[value].type
writable := hir_location_writable(checker, value, locals)
result_type := types.pointer(store, value_type, writable, false)
if types.is_pointer(expected, store) &&
types.equal(types.child_type(expected, store), value_type) &&
(!types.is_mutable(expected, store) || writable) {
result_type = expected
}
return add_hir_expr(checker, hir.Expr{
kind=.Address, span=expr.span, type=result_type, left=value,
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
})
case .Deref:
pointer := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
pointer_type := checker.module.exprs[pointer].type
if !types.is_pointer(pointer_type, store) {
id := source.add(checker.diagnostics, expr.span, "postfix '^' requires a pointer")
return invalid_hir_expr(checker, expr.span, id)
}
return add_hir_expr(checker, hir.Expr{
kind=.Deref, span=expr.span, type=types.child_type(pointer_type, store), left=pointer,
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
})
case .Index:
container := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
index := build_nested_expr(checker, expr.right, locals, global_reads, calls, types.USIZE, pkg, file)
container_type := checker.module.exprs[container].type
item, ok := types.container(container_type, store)
if !ok {
id := source.add(checker.diagnostics, expr.span, "indexing requires an array, slice, pointer-to-array, or many-item pointer")
return invalid_hir_expr(checker, expr.span, id)
}
return add_hir_expr(checker, hir.Expr{
kind=.Index, span=expr.span, type=item.child, left=container, right=index,
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
})
case .Slice:
container := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
container_type := checker.module.exprs[container].type
item, ok := types.container(container_type, store)
if !ok || item.kind == .Pointer {
id := source.add(checker.diagnostics, expr.span, "slicing requires an array, slice, or pointer-to-array")
return invalid_hir_expr(checker, expr.span, id)
}
bounds := make([]hir.Expr_Id, 2, checker.allocator)
bounds[0] = hir.INVALID_EXPR
bounds[1] = hir.INVALID_EXPR
for bound, index in expr.args {
if bound != ast.INVALID_EXPR {
bounds[index] = build_nested_expr(checker, bound, locals, global_reads, calls, types.USIZE, pkg, file)
}
}
preserve_sentinel := item.has_sentinel && expr.args[1] == ast.INVALID_EXPR
result_type := types.slice(store, item.child, item.mutable, preserve_sentinel, item.sentinel)
return add_hir_expr(checker, hir.Expr{
kind=.Slice, span=expr.span, type=result_type, args=bounds, left=container,
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
})
case .Field:
base := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
base_type := checker.module.exprs[base].type
field_name := symbol_text(checker, expr.name)
item, has_item := types.container(base_type, store)
if has_item && (item.kind == .Array || item.kind == .Slice) {
if field_name == "len" {
return add_hir_expr(checker, hir.Expr{
kind=.Length, span=expr.span, type=types.USIZE, left=base,
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
})
}
if field_name == "ptr" &&
(item.kind == .Slice || types.is_pointer(base_type, store)) {
return add_hir_expr(checker, hir.Expr{
kind=.Slice_Ptr, span=expr.span,
type=container_pointer_type(store, item), left=base,
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
})
}
if field_name == "ptr" && item.kind == .Array {
id := source.add(checker.diagnostics, expr.span, "arrays do not expose '.ptr'; take their address first")
return invalid_hir_expr(checker, expr.span, id)
}
}
if types.is_pointer(base_type, store) {
base_type = types.child_type(base_type, store)
}
index, field, ok := find_struct_field(checker, base_type, expr.name)
if !ok {
id := source.addf(checker.diagnostics, expr.span, "unknown struct field '%s'", symbol_text(checker, expr.name))
return invalid_hir_expr(checker, expr.span, id)
}
return add_hir_expr(checker, hir.Expr{
kind=.Field, span=expr.span, type=field.type, integer=i64(index), left=base,
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
})
case .Unwrap:
optional := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
optional_type := checker.module.exprs[optional].type
if !types.is_optional(optional_type, store) {
id := source.add(checker.diagnostics, expr.span, "postfix '?' requires an optional")
return invalid_hir_expr(checker, expr.span, id)
}
return add_hir_expr(checker, hir.Expr{
kind=.Unwrap, span=expr.span, type=types.child_type(optional_type, store), left=optional,
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
})
case .Orelse:
optional := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
optional_type := checker.module.exprs[optional].type
if !types.is_optional(optional_type, store) {
id := source.add(checker.diagnostics, expr.span, "'orelse' requires an optional left operand")
return invalid_hir_expr(checker, expr.span, id)
}
child := types.child_type(optional_type, store)
fallback := build_nested_expr(checker, expr.right, locals, global_reads, calls, child, pkg, file)
fallback = coerce_expr(checker, fallback, child, checker.module.exprs[fallback].span)
return add_hir_expr(checker, hir.Expr{
kind=.Orelse, span=expr.span, type=child, left=optional, right=fallback,
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
})
case .Bool:
return add_hir_expr(checker, hir.Expr{
kind=.Bool, span=expr.span, type=types.BOOL, integer=i64(expr.integer),
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
case .Not:
operand := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.BOOL, pkg, file)
operand_type := checker.module.exprs[operand].type
if checker.module.exprs[operand].kind != .Invalid && !types.is_bool(operand_type) {
id := source.add(checker.diagnostics, expr.span, "'!' requires a bool operand")
return invalid_hir_expr(checker, expr.span, id, types.BOOL)
}
return add_hir_expr(checker, hir.Expr{
kind=.Not, span=expr.span, type=types.BOOL, left=operand,
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
})
case .And, .Or:
left := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.BOOL, pkg, file)
right := build_nested_expr(checker, expr.right, locals, global_reads, calls, types.BOOL, pkg, file)
left_type := checker.module.exprs[left].type
right_type := checker.module.exprs[right].type
left_ok := checker.module.exprs[left].kind == .Invalid || types.is_bool(left_type)
right_ok := checker.module.exprs[right].kind == .Invalid || types.is_bool(right_type)
if !left_ok || !right_ok {
id := source.add(checker.diagnostics, expr.span, "'and'/'or' require bool operands")
return invalid_hir_expr(checker, expr.span, id, types.BOOL)
}
return add_hir_expr(checker, hir.Expr{
kind=.And if expr.kind == .And else .Or, span=expr.span, type=types.BOOL,
left=left, right=right, target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
})
case .Eq, .Ne, .Lt, .Le, .Gt, .Ge:
// Contextualize a bare integer-literal operand to the other operand's type
// so comparisons like `count > 0` or `0 < count` type-check.
left_const := eval_constant(checker, expr.left)
right_const := eval_constant(checker, expr.right)
left, right: hir.Expr_Id
if right_const.kind == .Value && left_const.kind != .Value {
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
hint := checker.module.exprs[left].type
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, hint, pkg, file)
} else if left_const.kind == .Value && right_const.kind != .Value {
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, types.INVALID, pkg, file)
hint := checker.module.exprs[right].type
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, hint, pkg, file)
} else {
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, types.INVALID, pkg, file)
}
left_type := checker.module.exprs[left].type
right_type := checker.module.exprs[right].type
if checker.module.exprs[left].kind == .Invalid || checker.module.exprs[right].kind == .Invalid {
return invalid_hir_expr(checker, expr.span, expr.diagnostic, types.BOOL)
}
operand_type := types.INVALID
if types.is_bool(left_type) && types.is_bool(right_type) {
if expr.kind != .Eq && expr.kind != .Ne {
id := source.add(checker.diagnostics, expr.span, "bool values only support '==' and '!='")
return invalid_hir_expr(checker, expr.span, id, types.BOOL)
}
operand_type = types.BOOL
} else {
operand_type = types.widest(left_type, right_type)
if !types.is_concrete_scalar(operand_type) || types.is_bool(operand_type) {
id := source.add(checker.diagnostics, expr.span, "comparison requires compatible numeric operands")
return invalid_hir_expr(checker, expr.span, id, types.BOOL)
}
}
left = coerce_expr(checker, left, operand_type, checker.module.exprs[left].span)
right = coerce_expr(checker, right, operand_type, checker.module.exprs[right].span)
compare_kind := hir.Expr_Kind.Eq
#partial switch expr.kind {
case .Eq: compare_kind = .Eq
case .Ne: compare_kind = .Ne
case .Lt: compare_kind = .Lt
case .Le: compare_kind = .Le
case .Gt: compare_kind = .Gt
case .Ge: compare_kind = .Ge
}
return add_hir_expr(checker, hir.Expr{
kind=compare_kind, span=expr.span, type=types.BOOL, left=left, right=right,
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
})
case .Struct_Literal:
target_pkg, available := expr_package(checker, expr, pkg, file, true)
struct_type := types.find_named(store, u32(target_pkg), u32(expr.name)) if available else types.INVALID
struct_type = types.resolve_alias(struct_type, store)
if !types.is_record(struct_type, store) || types.is_opaque_struct(struct_type, store) {
id := source.addf(checker.diagnostics, expr.span, "unknown or opaque record type '%s'", symbol_text(checker, expr.name))
return invalid_hir_expr(checker, expr.span, id)
}
fields := types.fields_for(store, struct_type)
union_record := types.is_union(struct_type, store)
if union_record && len(expr.args) != 1 {
id := source.add(checker.diagnostics, expr.span, "union literal requires exactly one field initializer")
return invalid_hir_expr(checker, expr.span, id, struct_type)
}
values := make([]hir.Expr_Id, 1 if union_record else len(fields), checker.allocator)
initialized := make([]bool, len(fields), checker.allocator)
defer delete(initialized, checker.allocator)
for &value in values {
value = hir.INVALID_EXPR
}
for keyed in expr.args {
keyed_expr := checker.ast_module.exprs[keyed]
index, field, ok := find_struct_field(checker, struct_type, keyed_expr.name)
if !ok {
source.addf(checker.diagnostics, keyed_expr.span, "unknown struct field '%s'", symbol_text(checker, keyed_expr.name))
continue
}
if initialized[index] {
source.addf(checker.diagnostics, keyed_expr.span, "duplicate initializer for struct field '%s'", symbol_text(checker, keyed_expr.name))
continue
}
initialized[index] = true
value_index := 0 if union_record else index
values[value_index] = build_nested_expr(checker, keyed_expr.left, locals, global_reads, calls, field.type, pkg, file)
values[value_index] = coerce_expr(checker, values[value_index], field.type, keyed_expr.span)
}
if !union_record {
for field, index in fields {
if values[index] != hir.INVALID_EXPR {
continue
}
id := source.addf(checker.diagnostics, expr.span, "missing initializer for struct field '%s'", symbol_text(checker, symbol.Id(field.name)))
delete(values, checker.allocator)
return invalid_hir_expr(checker, expr.span, id, struct_type)
}
}
active_field: i64
if union_record {
if values[0] == hir.INVALID_EXPR {
delete(values, checker.allocator)
return invalid_hir_expr(checker, expr.span, source.add(checker.diagnostics, expr.span, "union literal requires a known field"), struct_type)
}
for value, index in initialized {
if value {
active_field = i64(index)
break
}
}
}
return add_hir_expr(checker, hir.Expr{
kind=.Struct, span=expr.span, type=struct_type, args=values, integer=active_field,
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
case .Keyed:
id := source.add(checker.diagnostics, expr.span, "keyed initializer is only valid inside a struct literal")
return invalid_hir_expr(checker, expr.span, id)
case:
return invalid_hir_expr(checker, expr.span, expr.diagnostic)
}
}
build_expr :: proc(
checker: ^Checker,
expr_id: ast.Expr_Id,
locals: []Build_Local,
global_reads: ^[dynamic]hir.Global_Id,
calls: ^[dynamic]hir.Function_Id,
expected := types.INVALID,
pkg := ast.Package_Id(0),
file := ast.File_Id(0),
) -> hir.Expr_Id {
stack := checker.build_stack
clear_dynamic_array(&stack)
defer {
for frame in stack {
delete(frame.built_args, checker.allocator)
delete(frame.arg_types, checker.allocator)
}
clear_dynamic_array(&stack)
checker.build_stack = stack
}
append(&stack, Build_Expr_Frame{expr=expr_id, expected=expected, template=ast.INVALID_FUNCTION})
last := hir.INVALID_EXPR
for len(stack) > 0 {
frame_index := len(stack)-1
frame := stack[frame_index]
if frame.expr == ast.INVALID_EXPR || int(frame.expr) >= len(checker.ast_module.exprs) {
id := source.add(checker.diagnostics, source.Span{}, "missing expression")
last = invalid_hir_expr(checker, source.Span{}, id)
_ = pop(&stack)
continue
}
expr := checker.ast_module.exprs[frame.expr]
if frame.stage == 0 {
constant := eval_constant(checker, frame.expr)
if constant.kind == .Value || constant.kind == .Overflow {
last = build_constant_expr(checker, expr, constant, frame.expected)
_ = pop(&stack)
continue
}
switch expr.kind {
case .String, .Array, .None, .Address, .Deref, .Index, .Slice,
.Field, .Unwrap, .Orelse, .Struct_Literal, .Keyed,
.Bool, .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or:
last = build_compound_expr(
checker, expr, locals, global_reads, calls, frame.expected, pkg, file,
)
_ = pop(&stack)
case .Invalid, .Integer:
last = invalid_hir_expr(checker, expr.span, expr.diagnostic)
_ = pop(&stack)
case .Float:
last = build_float_expr(checker, expr, frame.expected)
_ = pop(&stack)
case .Name:
last = hir.INVALID_EXPR
if !symbol.is_valid(expr.qualifier) {
if local, ok := find_build_local(locals, expr.name); ok {
last = add_hir_expr(checker, hir.Expr{
kind=.Local, span=expr.span, type=local.type, target=hir.local_ref(local.id),
left = hir.INVALID_EXPR, right = hir.INVALID_EXPR, diagnostic = source.INVALID_DIAGNOSTIC,
})
}
} else if local, ok := find_build_local(locals, expr.qualifier); ok {
base := add_hir_expr(checker, hir.Expr{
kind=.Local, span=expr.span, type=local.type, target=hir.local_ref(local.id),
left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
})
base_type := local.type
item, has_item := types.container(base_type, &checker.module.types)
field_name := symbol_text(checker, expr.name)
if has_item && (item.kind == .Array || item.kind == .Slice) {
if field_name == "len" {
last = add_hir_expr(checker, hir.Expr{
kind=.Length, span=expr.span, type=types.USIZE, left=base,
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
})
} else if field_name == "ptr" &&
(item.kind == .Slice || types.is_pointer(base_type, &checker.module.types)) {
last = add_hir_expr(checker, hir.Expr{
kind=.Slice_Ptr, span=expr.span,
type=container_pointer_type(&checker.module.types, item), left=base,
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
})
} else if field_name == "ptr" && item.kind == .Array {
id := source.add(checker.diagnostics, expr.span, "arrays do not expose '.ptr'; take their address first")
last = invalid_hir_expr(checker, expr.span, id)
}
}
if types.is_pointer(base_type, &checker.module.types) {
base_type = types.child_type(base_type, &checker.module.types)
}
index, field, found := find_struct_field(checker, base_type, expr.name)
if last == hir.INVALID_EXPR && found {
last = add_hir_expr(checker, hir.Expr{
kind=.Field, span=expr.span, type=field.type, integer=i64(index), left=base,
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
})
}
}
if last == hir.INVALID_EXPR {
target_pkg, available := expr_package(checker, expr, pkg, file, true)
if !available {
id := add_package_resolution_diagnostic(checker, expr, file)
last = invalid_hir_expr(checker, expr.span, id)
} else if global := find_global(checker, expr.name, target_pkg); global != ast.INVALID_GLOBAL {
last = build_global_reference(checker, global, expr.span, global_reads)
} else {
template := find_template(checker, expr.name, target_pkg)
if template != ast.INVALID_FUNCTION && checker.ast_module.functions[template].c_abi {
last = build_function_value(checker, template, expr.span, frame.expected)
} else {
id := add_unsupported_diagnostic(checker, expr.span, target_pkg, expr.name)
if id == source.INVALID_DIAGNOSTIC {
id = add_name_resolution_diagnostic(checker, expr, target_pkg)
}
last = invalid_hir_expr(checker, expr.span, id)
}
}
}
_ = pop(&stack)
case .Negate:
stack[frame_index].stage = 5
append(&stack, Build_Expr_Frame{expr=expr.left, expected=types.INVALID, template=ast.INVALID_FUNCTION})
case .Add:
stack[frame_index].stage = 1
append(&stack, Build_Expr_Frame{expr=expr.left, expected=types.INVALID, template=ast.INVALID_FUNCTION})
case .Call:
if expr.left != ast.INVALID_EXPR {
stack[frame_index].stage = 6
append(&stack, Build_Expr_Frame{expr=expr.left, expected=types.INVALID, template=ast.INVALID_FUNCTION})
continue
}
target_pkg, available := expr_package(checker, expr, pkg, file, true)
if !available {
id := add_package_resolution_diagnostic(checker, expr, file)
last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack)
continue
}
template := find_template(checker, expr.name, target_pkg)
if template == ast.INVALID_FUNCTION {
callee := hir.INVALID_EXPR
callee_from_global := false
if !symbol.is_valid(expr.qualifier) {
if local, ok := find_build_local(locals, expr.name); ok {
callee = add_hir_expr(checker, hir.Expr{
kind=.Local, span=expr.span, type=local.type, target=hir.local_ref(local.id),
left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
})
}
}
if callee == hir.INVALID_EXPR {
if global := find_global(checker, expr.name, target_pkg); global != ast.INVALID_GLOBAL {
callee = build_global_reference(checker, global, expr.span, global_reads)
callee_from_global = true
}
}
if callee == hir.INVALID_EXPR {
id := add_unsupported_diagnostic(checker, expr.span, target_pkg, expr.name)
if id == source.INVALID_DIAGNOSTIC {
id = add_call_resolution_diagnostic(checker, expr, target_pkg)
}
last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack)
continue
}
_, function_item, function_type, ok := types.function_pointer(checker.module.exprs[callee].type, &checker.module.types)
if !ok {
id := add_call_resolution_diagnostic(checker, expr, target_pkg) if callee_from_global else
source.add(checker.diagnostics, expr.span, "call target is not a function pointer")
last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack)
continue
}
if !valid_callable_arity(function_item, len(expr.args)) {
message := "function pointer expects at least %d arguments, got %d" if function_item.variadic else
"function pointer expects %d arguments, got %d"
id := source.addf(checker.diagnostics, expr.span, message, function_item.field_count, len(expr.args))
last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack)
continue
}
stack[frame_index].left = callee
stack[frame_index].built_args = make([]hir.Expr_Id, len(expr.args), checker.allocator)
stack[frame_index].stage = 7
if len(expr.args) > 0 {
arg_expected := callable_arg_expected(function_type, function_item, &checker.module.types, 0)
if !is_runtime_type(checker, arg_expected) {
arg_expected = types.INVALID
}
append(&stack, Build_Expr_Frame{expr=expr.args[0], expected=arg_expected, template=ast.INVALID_FUNCTION})
}
continue
}
if len(checker.ast_module.functions[template].unsupported_reason) > 0 {
id := source.addf(
checker.diagnostics,
expr.span,
"C declaration '%s' is unavailable: %s",
symbol_text(checker, expr.name),
checker.ast_module.functions[template].unsupported_reason,
)
last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack)
continue
}
if checker.template_diagnostics[template] != source.INVALID_DIAGNOSTIC {
last = invalid_hir_expr(checker, expr.span, checker.template_diagnostics[template])
_ = pop(&stack)
continue
}
function := checker.ast_module.functions[template]
if !valid_call_arity(function, len(expr.args)) {
message := "function '%s' expects at least %d arguments, got %d" if function.variadic else
"function '%s' expects %d arguments, got %d"
id := source.addf(
checker.diagnostics,
expr.span,
message,
symbol_text(checker, expr.name),
len(function.params),
len(expr.args),
)
last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack)
continue
}
stack[frame_index].template = template
stack[frame_index].built_args = make([]hir.Expr_Id, len(expr.args), checker.allocator)
stack[frame_index].arg_types = make([]types.Type, len(expr.args), checker.allocator)
stack[frame_index].stage = 3
if len(expr.args) > 0 {
arg_expected := call_arg_expected(function, 0)
if !is_runtime_type(checker, arg_expected) {
arg_expected = types.INVALID
}
append(&stack, Build_Expr_Frame{expr=expr.args[0], expected=arg_expected, template=ast.INVALID_FUNCTION})
}
}
continue
}
if frame.stage == 5 {
operand := last
operand_type := checker.module.exprs[operand].type
if !types.is_signed(operand_type, checker.target) && !types.is_float(operand_type, checker.target) {
id := source.add(checker.diagnostics, expr.span, "negation requires a signed integer or float")
last = invalid_hir_expr(checker, expr.span, id)
} else {
last = add_hir_expr(checker, hir.Expr{
kind=.Negate, span=expr.span, type=operand_type, left=operand,
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
})
if types.is_signed(frame.expected, checker.target) || types.is_float(frame.expected, checker.target) {
last = coerce_expr(checker, last, frame.expected, expr.span)
}
}
_ = pop(&stack)
continue
}
if frame.stage == 1 {
stack[frame_index].left = last
stack[frame_index].stage = 2
right_expected := types.INVALID
if types.is_many_pointer(checker.module.exprs[last].type, &checker.module.types) {
right_expected = types.USIZE
}
append(&stack, Build_Expr_Frame{expr=expr.right, expected=right_expected, template=ast.INVALID_FUNCTION})
continue
}
if frame.stage == 2 {
left := frame.left
right := last
if types.is_many_pointer(checker.module.exprs[left].type, &checker.module.types) &&
types.equal(checker.module.exprs[right].type, types.USIZE) {
last = add_hir_expr(checker, hir.Expr{
kind=.Pointer_Add, span=expr.span, type=checker.module.exprs[left].type,
left=left, right=right, target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
})
_ = pop(&stack)
continue
}
result := types.widest(checker.module.exprs[left].type, checker.module.exprs[right].type)
if !types.is_concrete_scalar(result) {
id := source.add(checker.diagnostics, expr.span, "addition requires compatible numeric operands")
last = invalid_hir_expr(checker, expr.span, id)
} else {
left = coerce_expr(checker, left, result, checker.module.exprs[left].span)
right = coerce_expr(checker, right, result, checker.module.exprs[right].span)
last = add_hir_expr(checker, hir.Expr{
kind=.Add, span=expr.span, type=result, left=left, right=right,
target = hir.INVALID_REF, diagnostic = source.INVALID_DIAGNOSTIC,
})
}
_ = pop(&stack)
continue
}
if frame.stage == 3 {
if frame.arg_index < len(expr.args) {
stack[frame_index].built_args[frame.arg_index] = last
stack[frame_index].arg_types[frame.arg_index] = checker.module.exprs[last].type
stack[frame_index].arg_index += 1
if frame.arg_index+1 < len(expr.args) {
next := frame.arg_index+1
arg_expected := call_arg_expected(checker.ast_module.functions[frame.template], next)
if !is_runtime_type(checker, arg_expected) {
arg_expected = types.INVALID
}
append(&stack, Build_Expr_Frame{expr=expr.args[next], expected=arg_expected, template=ast.INVALID_FUNCTION})
continue
}
}
spec := find_spec(checker, frame.template, stack[frame_index].arg_types)
delete(stack[frame_index].arg_types, checker.allocator)
stack[frame_index].arg_types = nil
if spec == INVALID_SPEC {
id := source.addf(
checker.diagnostics,
expr.span,
"could not resolve specialization of '%s'",
symbol_text(checker, expr.name),
)
delete(stack[frame_index].built_args, checker.allocator)
stack[frame_index].built_args = nil
last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack)
continue
}
fixed_count := len(checker.ast_module.functions[frame.template].params)
for index in 0..<fixed_count {
stack[frame_index].built_args[index] = coerce_expr(
checker,
stack[frame_index].built_args[index],
checker.specs[spec].args[index],
checker.module.exprs[stack[frame_index].built_args[index]].span,
)
}
for index in fixed_count..<len(stack[frame_index].built_args) {
arg := stack[frame_index].built_args[index]
stack[frame_index].built_args[index] = promote_c_vararg_expr(
checker,
arg,
checker.module.exprs[arg].span,
)
}
function_id := checker.specs[spec].hir_id
assert(function_id != hir.INVALID_FUNCTION)
add_unique_function(calls, function_id)
result := checker.specs[spec].result
if !types.is_valid(result) {
id := source.addf(
checker.diagnostics,
expr.span,
"could not resolve result type for specialization of '%s'",
symbol_text(checker, expr.name),
)
delete(stack[frame_index].built_args, checker.allocator)
stack[frame_index].built_args = nil
last = invalid_hir_expr(checker, expr.span, id)
} else {
last = add_hir_expr(checker, hir.Expr{
kind=.Call, span=expr.span, type=result, target=hir.function_ref(function_id),
left = hir.INVALID_EXPR, right = hir.INVALID_EXPR, args=stack[frame_index].built_args, diagnostic = source.INVALID_DIAGNOSTIC,
})
stack[frame_index].built_args = nil
}
_ = pop(&stack)
}
if frame.stage == 6 {
callee := last
_, function_item, function_type, ok := types.function_pointer(checker.module.exprs[callee].type, &checker.module.types)
if !ok {
id := source.add(checker.diagnostics, expr.span, "call target is not a function pointer")
last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack)
continue
}
if !valid_callable_arity(function_item, len(expr.args)) {
message := "function pointer expects at least %d arguments, got %d" if function_item.variadic else
"function pointer expects %d arguments, got %d"
id := source.addf(checker.diagnostics, expr.span, message, function_item.field_count, len(expr.args))
last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack)
continue
}
stack[frame_index].left = callee
stack[frame_index].built_args = make([]hir.Expr_Id, len(expr.args), checker.allocator)
stack[frame_index].stage = 7
if len(expr.args) > 0 {
arg_expected := callable_arg_expected(function_type, function_item, &checker.module.types, 0)
if !is_runtime_type(checker, arg_expected) {
arg_expected = types.INVALID
}
append(&stack, Build_Expr_Frame{expr=expr.args[0], expected=arg_expected, template=ast.INVALID_FUNCTION})
}
continue
}
if frame.stage == 7 {
if frame.arg_index < len(expr.args) {
stack[frame_index].built_args[frame.arg_index] = last
stack[frame_index].arg_index += 1
if frame.arg_index+1 < len(expr.args) {
next := frame.arg_index+1
callee_type := checker.module.exprs[frame.left].type
_, function_item, function_type, _ := types.function_pointer(callee_type, &checker.module.types)
arg_expected := callable_arg_expected(function_type, function_item, &checker.module.types, next)
if !is_runtime_type(checker, arg_expected) {
arg_expected = types.INVALID
}
append(&stack, Build_Expr_Frame{expr=expr.args[next], expected=arg_expected, template=ast.INVALID_FUNCTION})
continue
}
}
callee_type := checker.module.exprs[frame.left].type
_, function_item, function_type, ok := types.function_pointer(callee_type, &checker.module.types)
if !ok {
id := source.add(checker.diagnostics, expr.span, "call target is not a function pointer")
delete(stack[frame_index].built_args, checker.allocator)
stack[frame_index].built_args = nil
last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack)
continue
}
fixed_count := int(function_item.field_count)
for index in 0..<min(fixed_count, len(stack[frame_index].built_args)) {
expected_arg := callable_arg_expected(function_type, function_item, &checker.module.types, index)
stack[frame_index].built_args[index] = coerce_expr(
checker,
stack[frame_index].built_args[index],
expected_arg,
checker.module.exprs[stack[frame_index].built_args[index]].span,
)
}
for index in fixed_count..<len(stack[frame_index].built_args) {
arg := stack[frame_index].built_args[index]
stack[frame_index].built_args[index] = promote_c_vararg_expr(
checker,
arg,
checker.module.exprs[arg].span,
)
}
result := function_item.child
if !types.is_valid(result) {
id := source.add(checker.diagnostics, expr.span, "could not resolve function pointer result type")
delete(stack[frame_index].built_args, checker.allocator)
stack[frame_index].built_args = nil
last = invalid_hir_expr(checker, expr.span, id)
} else {
last = add_hir_expr(checker, hir.Expr{
kind=.Call, span=expr.span, type=result, target=hir.INVALID_REF,
left=frame.left, right=hir.INVALID_EXPR, args=stack[frame_index].built_args,
diagnostic=source.INVALID_DIAGNOSTIC,
})
stack[frame_index].built_args = nil
}
_ = pop(&stack)
}
}
return last
}
make_link_name :: proc(checker: ^Checker, id: Spec_Id) -> string {
spec := checker.specs[id]
function := checker.ast_module.functions[spec.template]
if function.pkg == 0 && function.name == checker.main_symbol {
return fmt.aprintf("main", allocator = checker.allocator)
}
if !function.has_body && function.c_abi {
if len(function.link_name) > 0 {
return strings.clone(function.link_name, checker.allocator)
}
return fmt.aprintf("%s", symbol_text(checker, function.name), allocator = checker.allocator)
}
builder := strings.builder_make(checker.allocator)
defer strings.builder_destroy(&builder)
strings.write_string(&builder, "bro_c__" if function.c_abi else "bro__")
fmt.sbprintf(&builder, "p%d__", function.pkg)
strings.write_string(&builder, symbol_text(checker, function.name))
for arg in spec.args {
strings.write_string(&builder, "__")
if arg >= types.DYNAMIC_START {
fmt.sbprintf(&builder, "t%d", arg)
} else {
strings.write_string(&builder, types.name(arg))
}
}
return fmt.aprintf("%s", strings.to_string(builder), allocator = checker.allocator)
}
build_block :: proc(ctx: ^Build_Ctx, statements: []ast.Stmt_Id) -> []hir.Stmt_Id {
checker := ctx.checker
body: [dynamic]hir.Stmt_Id
body.allocator = checker.allocator
scope_start := len(ctx.locals^)
for statement_id in statements {
statement := checker.ast_module.statements[statement_id]
switch statement.kind {
case .Declaration:
declared := resolve_inferred_array(checker, type_from_syntax(statement.type), statement.expr)
expected := types.INVALID
if is_runtime_type(checker, declared) {
expected = declared
}
value := build_expr(
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
expected, ctx.pkg, ctx.file,
)
value_type := checker.module.exprs[value].type
if is_runtime_type(checker, declared) {
value = coerce_expr(checker, value, declared, statement.span)
value_type = checker.module.exprs[value].type
} else if types.is_void(declared) {
id := source.add(checker.diagnostics, statement.span, "locals cannot have type void")
value = invalid_hir_expr(checker, statement.span, id)
value_type = types.INVALID
}
if _, found := find_build_local(ctx.locals^[scope_start:], statement.name); found {
id := source.addf(
checker.diagnostics, statement.span,
"duplicate local '%s'", symbol_text(checker, statement.name),
)
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
local = hir.INVALID_LOCAL, diagnostic = id,
})
ctx.problematic^ = true
continue
}
local_id := hir.local_id(len(ctx.hir_locals^))
append(ctx.hir_locals, hir.Local{
name = statement.name, type = value_type, mutable = !statement.immutable,
})
append(ctx.locals, Build_Local{
name = statement.name, type = value_type, mutable = !statement.immutable, id = local_id,
})
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind = .Declaration, span = statement.span, local = local_id, expr = value,
diagnostic = source.INVALID_DIAGNOSTIC,
})
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[value].kind == .Invalid
case .Assignment:
if statement.target != ast.INVALID_EXPR {
target_expr := build_expr(
checker, statement.target, ctx.locals^[:], ctx.global_reads, ctx.calls,
types.INVALID, ctx.pkg, ctx.file,
)
target_type := checker.module.exprs[target_expr].type
if !hir_location_writable(checker, target_expr, ctx.locals^[:]) {
id := source.add(checker.diagnostics, statement.span, "assignment target is not writable")
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind=.Trap, span=statement.span, local=hir.INVALID_LOCAL,
target=hir.INVALID_EXPR, expr=hir.INVALID_EXPR, diagnostic=id,
})
ctx.problematic^ = true
continue
}
value := build_expr(
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
target_type, ctx.pkg, ctx.file,
)
value = coerce_expr(checker, value, target_type, statement.span)
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind=.Assignment, span=statement.span, local=hir.INVALID_LOCAL,
target=target_expr, expr=value, diagnostic=source.INVALID_DIAGNOSTIC,
})
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[value].kind == .Invalid
continue
}
if statement.name == checker.sink_symbol {
value := build_expr(checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, types.INVALID, ctx.pkg, ctx.file)
if types.is_void(checker.module.exprs[value].type) {
id := source.add(checker.diagnostics, statement.span, "cannot assign a void expression to '_'")
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
local = hir.INVALID_LOCAL, diagnostic = id,
})
ctx.problematic^ = true
} else {
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind = .Sink, span = statement.span, expr = value,
local = hir.INVALID_LOCAL, diagnostic = source.INVALID_DIAGNOSTIC,
})
}
continue
}
local, found := find_build_local(ctx.locals^[:], statement.name)
if !found {
id := source.addf(checker.diagnostics, statement.span, "cannot assign unresolved local '%s'", symbol_text(checker, statement.name))
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
local = hir.INVALID_LOCAL, diagnostic = id,
})
ctx.problematic^ = true
continue
}
if !local.mutable {
id := source.addf(checker.diagnostics, statement.span, "cannot assign immutable local '%s'", symbol_text(checker, statement.name))
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
local = hir.INVALID_LOCAL, diagnostic = id,
})
ctx.problematic^ = true
continue
}
value := build_expr(
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
local.type, ctx.pkg, ctx.file,
)
value = coerce_expr(checker, value, local.type, statement.span)
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind = .Assignment, span = statement.span, expr = value, local = local.id,
target = hir.INVALID_EXPR, diagnostic = source.INVALID_DIAGNOSTIC,
})
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[value].kind == .Invalid
case .Return:
ctx.has_return^ = true
if statement.expr == ast.INVALID_EXPR {
if !types.is_void(ctx.result) {
id := source.add(checker.diagnostics, statement.span, "'return _' is only valid in a void function")
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
local = hir.INVALID_LOCAL, diagnostic = id,
})
ctx.problematic^ = true
} else {
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind = .Return, span = statement.span, expr = hir.INVALID_EXPR,
local = hir.INVALID_LOCAL, diagnostic = source.INVALID_DIAGNOSTIC,
})
}
continue
}
if types.is_void(ctx.result) {
id := source.add(checker.diagnostics, statement.span, "void function cannot return a value")
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
local = hir.INVALID_LOCAL, diagnostic = id,
})
ctx.problematic^ = true
continue
}
value := build_expr(
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
ctx.result, ctx.pkg, ctx.file,
)
value = coerce_expr(checker, value, ctx.result, statement.span)
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind = .Return, span = statement.span, expr = value,
local = hir.INVALID_LOCAL, diagnostic = source.INVALID_DIAGNOSTIC,
})
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[value].kind == .Invalid
case .Expression:
value := build_expr(checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, types.INVALID, ctx.pkg, ctx.file)
if !types.is_void(checker.module.exprs[value].type) {
id := source.add(checker.diagnostics, statement.span, "non-void expression result must be consumed or assigned to '_'")
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
local = hir.INVALID_LOCAL, diagnostic = id,
})
ctx.problematic^ = true
} else {
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind = .Expression, span = statement.span, expr = value,
local = hir.INVALID_LOCAL, diagnostic = source.INVALID_DIAGNOSTIC,
})
}
case .If:
condition := build_expr(checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, types.BOOL, ctx.pkg, ctx.file)
if checker.module.exprs[condition].kind != .Invalid && !types.is_bool(checker.module.exprs[condition].type) {
id := source.add(checker.diagnostics, statement.span, "'if' condition must be a bool")
condition = invalid_hir_expr(checker, statement.span, id, types.BOOL)
ctx.problematic^ = true
}
then_body := build_block(ctx, statement.body)
else_body: []hir.Stmt_Id = nil
if statement.else_body != nil {
else_body = build_block(ctx, statement.else_body)
}
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind = .If, span = statement.span, expr = condition,
then_body = then_body, else_body = else_body,
local = hir.INVALID_LOCAL, target = hir.INVALID_EXPR,
diagnostic = source.INVALID_DIAGNOSTIC,
})
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[condition].kind == .Invalid
case .Invalid:
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
local = hir.INVALID_LOCAL, diagnostic = statement.diagnostic,
})
ctx.problematic^ = true
}
}
resize(ctx.locals, scope_start)
return body[:]
}
build_function :: proc(checker: ^Checker, id: Spec_Id) {
spec := checker.specs[id]
function := checker.ast_module.functions[spec.template]
signature_diagnostic := source.INVALID_DIAGNOSTIC
if !types.is_void(spec.result) && !is_runtime_type(checker, spec.result) {
checker.specs[id].result = types.I64
spec.result = types.I64
signature_diagnostic = source.addf(
checker.diagnostics,
function.span,
"could not resolve a concrete result type for '%s'",
symbol_text(checker, function.name),
)
}
for arg in spec.args {
if !is_runtime_type(checker, arg) {
signature_diagnostic = source.addf(
checker.diagnostics,
function.span,
"could not resolve a concrete parameter type for '%s'",
symbol_text(checker, function.name),
)
break
}
}
assert(spec.hir_id == hir.function_id(len(checker.module.functions)))
locals: [dynamic]Build_Local
locals.allocator = checker.allocator
hir_locals: [dynamic]hir.Local
hir_locals.allocator = checker.allocator
params: [dynamic]hir.Local_Id
params.allocator = checker.allocator
body: [dynamic]hir.Stmt_Id
body.allocator = checker.allocator
global_reads: [dynamic]hir.Global_Id
global_reads.allocator = checker.allocator
calls: [dynamic]hir.Function_Id
calls.allocator = checker.allocator
for param, index in function.params {
local_id := hir.local_id(len(hir_locals))
param_type := types.INVALID
if index < len(spec.args) {
param_type = spec.args[index]
}
append(&hir_locals, hir.Local{name = param.name, type = param_type, parameter = true})
append(&locals, Build_Local{name = param.name, type = param_type, id = local_id})
append(&params, local_id)
}
problematic := signature_diagnostic != source.INVALID_DIAGNOSTIC ||
checker.template_diagnostics[spec.template] != source.INVALID_DIAGNOSTIC
if !function.has_body {
assert(spec.hir_id == hir.function_id(len(checker.module.functions)))
append(
&checker.module.functions,
hir.Function {
name = function.name,
link_name = make_link_name(checker, id),
calling_convention = .C if function.c_abi else .Brolang,
implementation = .Declaration,
linkage = .External if function.c_abi else .Internal,
is_main = function.pkg == 0 && function.name == checker.main_symbol,
variadic = function.variadic,
params = params[:],
result = spec.result,
locals = hir_locals[:],
body = body[:],
direct_global_reads = global_reads,
calls = calls[:],
problematic = problematic,
diagnostic = checker.template_diagnostics[spec.template],
},
)
delete(locals)
return
}
has_return := false
if signature_diagnostic != source.INVALID_DIAGNOSTIC {
append(&body, hir.stmt_id(len(checker.module.statements)))
append(
&checker.module.statements,
hir.Stmt {
kind = .Trap,
span = function.span,
expr = hir.INVALID_EXPR,
local = hir.INVALID_LOCAL,
diagnostic = signature_diagnostic,
},
)
}
ctx := Build_Ctx{
checker = checker,
pkg = function.pkg,
file = function.file,
result = spec.result,
locals = &locals,
hir_locals = &hir_locals,
global_reads = &global_reads,
calls = &calls,
problematic = &problematic,
has_return = &has_return,
}
block := build_block(&ctx, function.body)
for block_stmt in block {
append(&body, block_stmt)
}
delete(block, checker.allocator)
if !types.is_void(spec.result) && !has_return {
id := source.addf(
checker.diagnostics,
function.span,
"function '%s' does not return a value",
symbol_text(checker, function.name),
)
append(&body, hir.stmt_id(len(checker.module.statements)))
append(
&checker.module.statements,
hir.Stmt{kind = .Trap, span = function.span, expr = hir.INVALID_EXPR, local = hir.INVALID_LOCAL, diagnostic = id},
)
problematic = true
}
assert(spec.hir_id == hir.function_id(len(checker.module.functions)))
append(
&checker.module.functions,
hir.Function {
name = function.name,
link_name = make_link_name(checker, id),
calling_convention = .C if function.c_abi || (function.pkg == 0 && function.name == checker.main_symbol) else .Brolang,
implementation = .Definition,
linkage = .External if function.c_abi || (function.pkg == 0 && function.name == checker.main_symbol) else .Internal,
is_main = function.pkg == 0 && function.name == checker.main_symbol,
variadic = function.variadic,
params = params[:],
result = spec.result,
locals = hir_locals[:],
body = body[:],
direct_global_reads = global_reads,
calls = calls[:],
problematic = problematic,
diagnostic = source.INVALID_DIAGNOSTIC,
},
)
delete(locals)
}
expr_problematic :: proc(checker: ^Checker, expr_id: hir.Expr_Id) -> bool {
module := &checker.module
stack := checker.hir_expr_stack
clear_dynamic_array(&stack)
defer {
clear_dynamic_array(&stack)
checker.hir_expr_stack = stack
}
append(&stack, expr_id)
for len(stack) > 0 {
id := pop(&stack)
if id == hir.INVALID_EXPR || int(id) >= len(module.exprs) {
return true
}
expr := module.exprs[id]
if expr.kind == .Invalid {
return true
}
if expr.left != hir.INVALID_EXPR {
append(&stack, expr.left)
}
if expr.right != hir.INVALID_EXPR {
append(&stack, expr.right)
}
append(&stack, ..expr.args)
}
return false
}
build_globals :: proc(checker: ^Checker) {
for global, global_index in checker.ast_module.globals {
if global.external {
global_type := checker.global_types[global_index]
writable := global.writable
canonical := checker.external_global_canonical[global_index]
if canonical != ast.INVALID_GLOBAL && int(canonical) < len(checker.ast_module.globals) {
canonical_global := checker.ast_module.globals[canonical]
writable = canonical_global.writable
global_type = checker.global_types[canonical]
}
diagnostic := checker.external_global_diagnostics[global_index]
if !is_runtime_type(checker, global_type) {
if diagnostic == source.INVALID_DIAGNOSTIC {
diagnostic = source.addf(
checker.diagnostics,
global.span,
"could not resolve a concrete type for external global '%s'",
symbol_text(checker, global.name),
)
}
global_type = types.I64
}
_ = hir.global_id(len(checker.module.globals))
append(&checker.module.globals, hir.Global{
name=global.name,
link_name=strings.clone(global.link_name, checker.allocator),
type=global_type,
expr=hir.INVALID_EXPR,
external=true,
writable=writable,
direct_problem=diagnostic != source.INVALID_DIAGNOSTIC,
problematic=diagnostic != source.INVALID_DIAGNOSTIC,
diagnostic=diagnostic,
})
continue
}
dependencies: [dynamic]hir.Global_Id
dependencies.allocator = checker.allocator
calls: [dynamic]hir.Function_Id
calls.allocator = checker.allocator
declared := resolve_inferred_array(checker, type_from_syntax(global.type), global.expr)
expected := types.INVALID
if is_runtime_type(checker, declared) {
expected = declared
}
expr := build_expr(checker, global.expr, nil, &dependencies, &calls, expected, global.pkg, global.file)
global_type := checker.global_types[global_index]
if is_runtime_type(checker, declared) {
expr = coerce_expr(checker, expr, declared, global.span)
global_type = checker.module.exprs[expr].type
} else if is_runtime_type(checker, checker.module.exprs[expr].type) {
global_type = checker.module.exprs[expr].type
}
diagnostic := source.INVALID_DIAGNOSTIC
if !is_runtime_type(checker, global_type) {
diagnostic = source.addf(
checker.diagnostics,
global.span,
"could not resolve a concrete type for global '%s'",
symbol_text(checker, global.name),
)
global_type = types.I64
expr = invalid_hir_expr(checker, global.span, diagnostic, global_type)
}
if global.type == types.VOID {
diagnostic = source.add(
checker.diagnostics,
global.span,
"void is only valid as a function result type",
)
expr = invalid_hir_expr(checker, global.span, diagnostic)
}
if !global.immutable {
diagnostic = source.add(
checker.diagnostics,
global.span,
"mutable declarations are only valid inside functions",
)
expr = invalid_hir_expr(checker, global.span, diagnostic)
}
is_static := checker.module.exprs[expr].kind == .Integer && diagnostic == source.INVALID_DIAGNOSTIC
static_value: i64
if is_static {
static_value = checker.module.exprs[expr].integer
}
_ = hir.global_id(len(checker.module.globals))
append(
&checker.module.globals,
hir.Global {
name = global.name,
link_name = strings.clone(global.link_name, checker.allocator),
type = global_type,
expr = expr,
static_value = static_value,
is_static = is_static,
external = false,
writable = false,
dependencies = dependencies,
calls = calls[:],
direct_problem = expr_problematic(checker, expr),
problematic = expr_problematic(checker, expr),
diagnostic = diagnostic,
},
)
}
}
propagate_problems :: proc(checker: ^Checker) {
changed := true
for changed {
changed = false
for &function in checker.module.functions {
if function.problematic {
continue
}
for call in function.calls {
if call != hir.INVALID_FUNCTION && int(call) < len(checker.module.functions) &&
checker.module.functions[call].problematic {
function.problematic = true
changed = true
break
}
}
}
for &global in checker.module.globals {
if global.problematic {
continue
}
for dependency in global.dependencies {
if dependency != hir.INVALID_GLOBAL &&
int(dependency) < len(checker.module.globals) &&
checker.module.globals[dependency].problematic {
global.problematic = true
changed = true
break
}
}
if global.problematic {
continue
}
for call in global.calls {
if call != hir.INVALID_FUNCTION && int(call) < len(checker.module.functions) &&
checker.module.functions[call].problematic {
global.problematic = true
changed = true
break
}
}
}
}
}
append_unique_global :: proc(values: ^[dynamic]hir.Global_Id, value: hir.Global_Id) -> bool {
for existing in values^ {
if existing == value {
return false
}
}
append(values, value)
return true
}
propagate_global_reads :: proc(checker: ^Checker) {
changed := true
for changed {
changed = false
for &function in checker.module.functions {
for call in function.calls {
if call == hir.INVALID_FUNCTION || int(call) >= len(checker.module.functions) {
continue
}
for global_id in checker.module.functions[call].direct_global_reads {
if append_unique_global(&function.direct_global_reads, global_id) {
changed = true
}
}
}
}
}
for &global in checker.module.globals {
for call in global.calls {
if call == hir.INVALID_FUNCTION || int(call) >= len(checker.module.functions) {
continue
}
for dependency in checker.module.functions[call].direct_global_reads {
_ = append_unique_global(&global.dependencies, dependency)
}
}
}
}
Cycle_Frame :: struct {
global: hir.Global_Id,
next_dependency: int,
}
detect_global_cycles_visit :: proc(checker: ^Checker, global_id: hir.Global_Id, states: []u8) {
if states[global_id] == 2 {
return
}
stack := checker.cycle_stack
clear_dynamic_array(&stack)
defer {
clear_dynamic_array(&stack)
checker.cycle_stack = stack
}
append(&stack, Cycle_Frame{global=global_id})
for len(stack) > 0 {
frame_index := len(stack)-1
frame := &stack[frame_index]
if states[frame.global] == 0 {
states[frame.global] = 1
}
dependencies := checker.module.globals[frame.global].dependencies
if frame.next_dependency >= len(dependencies) {
states[frame.global] = 2
if checker.module.globals[frame.global].problematic && frame_index > 0 {
checker.module.globals[stack[frame_index-1].global].problematic = true
}
_ = pop(&stack)
continue
}
dependency := dependencies[frame.next_dependency]
frame.next_dependency += 1
if dependency == hir.INVALID_GLOBAL || int(dependency) >= len(states) {
continue
}
if states[dependency] == 1 {
id := source.addf(
checker.diagnostics,
checker.ast_module.globals[dependency].span,
"global initialization cycle involving '%s'",
symbol_text(checker, checker.module.globals[dependency].name),
)
checker.module.globals[dependency].diagnostic = id
checker.module.globals[dependency].problematic = true
checker.module.globals[frame.global].problematic = true
continue
}
if states[dependency] == 2 {
if checker.module.globals[dependency].problematic {
checker.module.globals[frame.global].problematic = true
}
continue
}
append(&stack, Cycle_Frame{global=dependency})
}
}
synthesize_trap_main :: proc(checker: ^Checker) {
id := source.add(checker.diagnostics, source.Span{}, "missing or unusable main function")
statement_id := hir.stmt_id(len(checker.module.statements))
_ = hir.function_id(len(checker.module.functions))
append(
&checker.module.statements,
hir.Stmt{kind = .Trap, span = source.Span{}, expr = hir.INVALID_EXPR, local = hir.INVALID_LOCAL, diagnostic = id},
)
body := make([]hir.Stmt_Id, 1, checker.allocator)
body[0] = statement_id
append(
&checker.module.functions,
hir.Function {
name = checker.main_symbol,
link_name = fmt.aprintf("main", allocator = checker.allocator),
calling_convention = .C,
implementation = .Definition,
linkage = .External,
is_main = true,
result = types.VOID,
body = body,
problematic = true,
diagnostic = id,
},
)
}
replace_main_with_trap :: proc(checker: ^Checker, diagnostic: source.Diagnostic_Id) {
for &function in checker.module.functions {
if !function.is_main {
continue
}
delete(function.params, checker.allocator)
delete(function.body, checker.allocator)
function.params = nil
function.result = types.VOID
function.calling_convention = .C
function.implementation = .Definition
function.linkage = .External
function.problematic = true
function.diagnostic = diagnostic
statement_id := hir.stmt_id(len(checker.module.statements))
append(
&checker.module.statements,
hir.Stmt {
kind = .Trap,
span = source.Span{},
expr = hir.INVALID_EXPR,
local = hir.INVALID_LOCAL,
diagnostic = diagnostic,
},
)
function.body = make([]hir.Stmt_Id, 1, checker.allocator)
function.body[0] = statement_id
return
}
synthesize_trap_main(checker)
}
check :: proc(
ast_module: ^ast.Module,
diagnostics: ^source.Diagnostics,
symbols: ^symbol.Table,
selected := target.DEFAULT,
allocator := context.allocator,
) -> hir.Module {
checker := Checker {
ast_module = ast_module,
diagnostics = diagnostics,
symbols = symbols,
module = hir.init_module(selected, allocator),
main_symbol = symbol.intern(symbols, "main"),
sink_symbol = symbol.intern(symbols, "_"),
target = selected,
allocator = allocator,
}
checker.specs.allocator = allocator
types.destroy_store(&checker.module.types)
checker.module.types = types.clone_store(&ast_module.type_store, allocator)
checker.module.types.selected = selected
for value in ast_module.strings {
append(&checker.module.strings, strings.clone(value, allocator))
}
checker.constant_stack.allocator = allocator
checker.ast_expr_stack.allocator = allocator
checker.hir_expr_stack.allocator = allocator
checker.infer_stack.allocator = allocator
checker.build_stack.allocator = allocator
checker.cycle_stack.allocator = allocator
build_symbol_indexes(&checker)
checker.global_types = make([]types.Type, 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 {
canonical = ast.INVALID_GLOBAL
}
for &diagnostic in checker.external_global_diagnostics {
diagnostic = source.INVALID_DIAGNOSTIC
}
checker.constants = make([]Constant, len(ast_module.exprs), allocator)
checker.template_diagnostics = make([]source.Diagnostic_Id, len(ast_module.functions), allocator)
for &diagnostic in checker.template_diagnostics {
diagnostic = source.INVALID_DIAGNOSTIC
}
defer {
for spec in checker.specs {
delete(spec.args, allocator)
}
delete(checker.specs)
delete(checker.function_index, allocator)
delete(checker.global_index, allocator)
delete(checker.import_index, allocator)
delete(checker.global_types, allocator)
delete(checker.external_global_canonical, allocator)
delete(checker.external_global_diagnostics, allocator)
delete(checker.constants, allocator)
delete(checker.template_diagnostics, allocator)
delete(checker.constant_stack)
delete(checker.ast_expr_stack)
delete(checker.hir_expr_stack)
delete(checker.infer_stack)
delete(checker.build_stack)
delete(checker.cycle_stack)
}
for function, index in ast_module.functions {
for previous in ast_module.functions[:index] {
if previous.pkg == function.pkg && previous.name == function.name {
source.addf(diagnostics, function.span, "duplicate function '%s'", symbol_text(&checker, function.name))
}
}
for global in ast_module.globals {
if global.pkg == function.pkg && global.name == function.name {
source.addf(diagnostics, function.span, "package declaration '%s' conflicts with a global", symbol_text(&checker, function.name))
}
}
}
for global, index in ast_module.globals {
for previous in ast_module.globals[:index] {
if previous.pkg == global.pkg && previous.name == global.name {
source.addf(diagnostics, global.span, "duplicate global '%s'", symbol_text(&checker, global.name))
}
}
}
validate_type_nodes(&checker)
validate_declarations(&checker)
infer_all(&checker)
validate_external_globals(&checker)
prune_specs(&checker)
build_globals(&checker)
for index := 0; index < len(checker.specs); index += 1 {
build_function(&checker, spec_id(index))
}
propagate_global_reads(&checker)
main_template := find_template(&checker, checker.main_symbol, 0)
main_declarations := 0
for function in ast_module.functions {
if function.pkg == 0 && function.name == checker.main_symbol {
main_declarations += 1
}
}
if main_declarations == 0 {
synthesize_trap_main(&checker)
} else {
template := ast_module.functions[main_template]
if main_declarations != 1 ||
!template.has_body ||
len(template.params) != 0 ||
!(template.result == types.VOID || template.result == types.I32 || template.result == types.INT) {
id := checker.template_diagnostics[main_template]
if id == source.INVALID_DIAGNOSTIC {
id = source.add(
diagnostics,
template.span,
"main must be unique, have a body, take no parameters, and return void, i32, or int",
)
}
replace_main_with_trap(&checker, id)
}
}
propagate_problems(&checker)
states := make([]u8, len(checker.module.globals), allocator)
for index in 0 ..< len(checker.module.globals) {
detect_global_cycles_visit(&checker, hir.global_id(index), states)
}
delete(states, allocator)
propagate_problems(&checker)
for import_item in ast_module.imports {
if import_item.valid && !import_item.used {
source.addf(diagnostics, import_item.span, "unused import '%s'", symbol_text(&checker, import_item.alias))
}
}
return checker.module
}