3630 lines
118 KiB
Odin
3630 lines
118 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,
|
|
}
|
|
|
|
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, .Address, .Deref, .Field, .Unwrap, .Keyed:
|
|
append(&stack, expr.left)
|
|
case .Add, .Index, .Orelse:
|
|
append(&stack, expr.left, expr.right)
|
|
case .Invalid, .Integer, .Float, .String, .None, .Name:
|
|
}
|
|
}
|
|
}
|
|
|
|
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",
|
|
)
|
|
}
|
|
}
|
|
for statement_id in function.body {
|
|
statement := checker.ast_module.statements[statement_id]
|
|
switch statement.kind {
|
|
case .Declaration, .Assignment, .Return, .Expression:
|
|
mark_expr_imports_used(checker, statement.expr, function.file)
|
|
if statement.target != ast.INVALID_EXPR {
|
|
mark_expr_imports_used(checker, statement.target, function.file)
|
|
}
|
|
case .Invalid:
|
|
}
|
|
}
|
|
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 .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:
|
|
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_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
|
|
for statement_id in function.body {
|
|
statement := checker.ast_module.statements[statement_id]
|
|
#partial switch statement.kind {
|
|
case .Declaration:
|
|
value_type := infer_expr(checker, statement.expr, locals[:], function.pkg, function.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[:], function.pkg, function.file, demanded)
|
|
}
|
|
_ = infer_expr(checker, statement.expr, locals[:], function.pkg, function.file, demanded)
|
|
case .Return:
|
|
if statement.expr != ast.INVALID_EXPR {
|
|
returned := infer_expr(checker, statement.expr, locals[:], function.pkg, function.file, demanded)
|
|
if !types.is_valid(result) {
|
|
result = returned
|
|
} else {
|
|
result = types.widest(result, returned)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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 .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:
|
|
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_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(¶ms, 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,
|
|
},
|
|
)
|
|
}
|
|
for statement_id in function.body {
|
|
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,
|
|
locals[:],
|
|
&global_reads,
|
|
&calls,
|
|
expected,
|
|
function.pkg,
|
|
function.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(locals[:], 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,
|
|
},
|
|
)
|
|
problematic = true
|
|
continue
|
|
}
|
|
local_id := hir.local_id(len(hir_locals))
|
|
append(
|
|
&hir_locals,
|
|
hir.Local {
|
|
name = statement.name,
|
|
type = value_type,
|
|
mutable = !statement.immutable,
|
|
},
|
|
)
|
|
append(
|
|
&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,
|
|
},
|
|
)
|
|
problematic = problematic || checker.module.exprs[value].kind == .Invalid
|
|
case .Assignment:
|
|
if statement.target != ast.INVALID_EXPR {
|
|
target_expr := build_expr(
|
|
checker, statement.target, locals[:], &global_reads, &calls,
|
|
types.INVALID, function.pkg, function.file,
|
|
)
|
|
target_type := checker.module.exprs[target_expr].type
|
|
if !hir_location_writable(checker, target_expr, 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,
|
|
})
|
|
problematic = true
|
|
continue
|
|
}
|
|
value := build_expr(
|
|
checker, statement.expr, locals[:], &global_reads, &calls,
|
|
target_type, function.pkg, function.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,
|
|
})
|
|
problematic = problematic || checker.module.exprs[value].kind == .Invalid
|
|
continue
|
|
}
|
|
if statement.name == checker.sink_symbol {
|
|
value := build_expr(checker, statement.expr, locals[:], &global_reads, &calls, types.INVALID, function.pkg, function.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,
|
|
},
|
|
)
|
|
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(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,
|
|
},
|
|
)
|
|
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,
|
|
},
|
|
)
|
|
problematic = true
|
|
continue
|
|
}
|
|
value := build_expr(
|
|
checker,
|
|
statement.expr,
|
|
locals[:],
|
|
&global_reads,
|
|
&calls,
|
|
local.type,
|
|
function.pkg,
|
|
function.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,
|
|
},
|
|
)
|
|
problematic = problematic || checker.module.exprs[value].kind == .Invalid
|
|
case .Return:
|
|
has_return = true
|
|
if statement.expr == ast.INVALID_EXPR {
|
|
if !types.is_void(spec.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,
|
|
},
|
|
)
|
|
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(spec.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,
|
|
},
|
|
)
|
|
problematic = true
|
|
continue
|
|
}
|
|
value := build_expr(
|
|
checker,
|
|
statement.expr,
|
|
locals[:],
|
|
&global_reads,
|
|
&calls,
|
|
spec.result,
|
|
function.pkg,
|
|
function.file,
|
|
)
|
|
value = coerce_expr(checker, value, spec.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,
|
|
},
|
|
)
|
|
problematic = problematic || checker.module.exprs[value].kind == .Invalid
|
|
case .Expression:
|
|
value := build_expr(checker, statement.expr, locals[:], &global_reads, &calls, types.INVALID, function.pkg, function.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,
|
|
},
|
|
)
|
|
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 .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,
|
|
},
|
|
)
|
|
problematic = true
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|