4475 lines
155 KiB
Odin
4475 lines
155 KiB
Odin
package checker
|
|
|
|
import "../ast"
|
|
import "../hir"
|
|
import "../source"
|
|
import "../symbol"
|
|
import "../target"
|
|
import "../types"
|
|
import "base:intrinsics"
|
|
import "core:fmt"
|
|
import "core:mem"
|
|
import "core:slice"
|
|
import "core:strings"
|
|
|
|
Spec_Id :: distinct u32
|
|
INVALID_SPEC :: Spec_Id(0xffff_ffff)
|
|
|
|
spec_id :: proc(index: int) -> Spec_Id {
|
|
assert(index >= 0 && u64(index) < u64(INVALID_SPEC))
|
|
return Spec_Id(index)
|
|
}
|
|
|
|
spec_index :: proc(id: Spec_Id, count: int) -> (int, bool) {
|
|
index := int(id)
|
|
return index, id != INVALID_SPEC && index < count
|
|
}
|
|
|
|
Spec :: struct {
|
|
template: ast.Function_Id,
|
|
args: []types.Type,
|
|
result: types.Type,
|
|
hir_id: hir.Function_Id,
|
|
}
|
|
|
|
Infer_Local :: struct {
|
|
name: symbol.Id,
|
|
type: types.Type,
|
|
}
|
|
|
|
Build_Local :: struct {
|
|
name: symbol.Id,
|
|
type: types.Type,
|
|
mutable: bool,
|
|
id: hir.Local_Id,
|
|
}
|
|
|
|
// Build_Ctx threads the per-function accumulators through build_block so that
|
|
// nested control-flow blocks (if/else) can be built recursively. `locals` is a
|
|
// scope stack: each block records its entry length and truncates back to it on
|
|
// exit, while `hir_locals` keeps every allocated slot for the function.
|
|
Build_Ctx :: struct {
|
|
checker: ^Checker,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
result: types.Type,
|
|
locals: ^[dynamic]Build_Local,
|
|
hir_locals: ^[dynamic]hir.Local,
|
|
global_reads: ^[dynamic]hir.Global_Id,
|
|
calls: ^[dynamic]hir.Function_Id,
|
|
problematic: ^bool,
|
|
}
|
|
|
|
Constant_Kind :: enum {
|
|
Unknown,
|
|
Not_Constant,
|
|
Value,
|
|
Overflow,
|
|
Div_By_Zero,
|
|
}
|
|
|
|
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 != .Sub && expr.kind != .Mul &&
|
|
expr.kind != .Div && 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 == .Div_By_Zero {
|
|
result = Constant{kind = .Div_By_Zero}
|
|
} else 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 == .Div_By_Zero || right.kind == .Div_By_Zero {
|
|
result = Constant{kind = .Div_By_Zero}
|
|
} else if left.kind == .Overflow || right.kind == .Overflow {
|
|
result = Constant{kind = .Overflow}
|
|
} else if left.kind == .Value && right.kind == .Value {
|
|
value: i128
|
|
overflow: bool
|
|
div_by_zero: bool
|
|
#partial switch expr.kind {
|
|
case .Sub: value, overflow = intrinsics.overflow_sub(left.value, right.value)
|
|
case .Mul: value, overflow = intrinsics.overflow_mul(left.value, right.value)
|
|
case .Div:
|
|
if right.value == 0 {
|
|
div_by_zero = true
|
|
} else {
|
|
value = left.value / right.value
|
|
}
|
|
case: value, overflow = intrinsics.overflow_add(left.value, right.value)
|
|
}
|
|
switch {
|
|
case div_by_zero: result = Constant{kind = .Div_By_Zero}
|
|
case overflow: result = Constant{kind = .Overflow}
|
|
case: result = Constant{kind = .Value, value = value}
|
|
}
|
|
}
|
|
checker.constants[frame.expr] = result
|
|
_ = pop(&stack)
|
|
}
|
|
return checker.constants[expr_id]
|
|
}
|
|
|
|
fits_signed_type :: proc(value: i128, value_type: types.Type, selected := target.DEFAULT) -> bool {
|
|
if !types.is_signed(value_type, selected) {
|
|
return false
|
|
}
|
|
limit := i128(1) << u32(types.bits(value_type, selected) - 1)
|
|
return value >= -limit && value < limit
|
|
}
|
|
|
|
fits_unsigned_type :: proc(value: i128, value_type: types.Type, selected := target.DEFAULT) -> bool {
|
|
if !types.is_unsigned(value_type, selected) || value < 0 {
|
|
return false
|
|
}
|
|
limit := i128(1) << u32(types.bits(value_type, selected))
|
|
return value < limit
|
|
}
|
|
|
|
fits_integer_type :: proc(value: i128, value_type: types.Type, selected := target.DEFAULT) -> bool {
|
|
return fits_signed_type(value, value_type, selected) || fits_unsigned_type(value, value_type, selected)
|
|
}
|
|
|
|
fits_i64 :: proc(value: i128) -> bool {
|
|
return fits_signed_type(value, types.I64)
|
|
}
|
|
|
|
type_from_syntax :: proc(value: ast.Type_Syntax) -> types.Type {
|
|
return value
|
|
}
|
|
|
|
is_runtime_type :: proc(checker: ^Checker, value: types.Type) -> bool {
|
|
return types.is_runtime_value(value, &checker.module.types)
|
|
}
|
|
|
|
string_literal_type :: proc(checker: ^Checker, string_id: u64) -> types.Type {
|
|
length: u64
|
|
if string_id < u64(len(checker.ast_module.strings)) {
|
|
length = u64(len(checker.ast_module.strings[string_id]))
|
|
}
|
|
array := types.array(&checker.module.types, types.U8, length, false, true, 0)
|
|
return types.pointer(&checker.module.types, array, false, false)
|
|
}
|
|
|
|
container_pointer_type :: proc(store: ^types.Store, item: types.Node) -> types.Type {
|
|
return types.pointer(store, item.child, item.mutable, true, item.has_sentinel, item.sentinel)
|
|
}
|
|
|
|
resolve_inferred_array :: proc(checker: ^Checker, value: types.Type, expr_id: ast.Expr_Id) -> types.Type {
|
|
item, ok := types.node(&checker.module.types, value)
|
|
if !ok || item.kind != .Array || !item.inferred_count ||
|
|
expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
|
|
return value
|
|
}
|
|
expr := checker.ast_module.exprs[expr_id]
|
|
if expr.kind != .Array {
|
|
return value
|
|
}
|
|
return types.with_array_count(&checker.module.types, value, u64(len(expr.args)))
|
|
}
|
|
|
|
function_index_less :: proc(left, right: Function_Index_Entry) -> bool {
|
|
if left.scope != right.scope {
|
|
return left.scope < right.scope
|
|
}
|
|
if left.name != right.name {
|
|
return int(left.name) < int(right.name)
|
|
}
|
|
return left.id < right.id
|
|
}
|
|
|
|
global_index_less :: proc(left, right: Global_Index_Entry) -> bool {
|
|
if left.scope != right.scope {
|
|
return left.scope < right.scope
|
|
}
|
|
if left.name != right.name {
|
|
return int(left.name) < int(right.name)
|
|
}
|
|
return left.id < right.id
|
|
}
|
|
|
|
import_index_less :: proc(left, right: Import_Index_Entry) -> bool {
|
|
if left.scope != right.scope {
|
|
return left.scope < right.scope
|
|
}
|
|
if left.name != right.name {
|
|
return int(left.name) < int(right.name)
|
|
}
|
|
return left.id < right.id
|
|
}
|
|
|
|
find_function_symbol :: proc(index: []Function_Index_Entry, scope: ast.Package_Id, name: symbol.Id) -> ast.Function_Id {
|
|
low := 0
|
|
high := len(index)
|
|
for low < high {
|
|
middle := low + (high-low)/2
|
|
entry := index[middle]
|
|
if entry.scope < scope || entry.scope == scope && int(entry.name) < int(name) {
|
|
low = middle + 1
|
|
} else {
|
|
high = middle
|
|
}
|
|
}
|
|
if low < len(index) && index[low].scope == scope && index[low].name == name {
|
|
return index[low].id
|
|
}
|
|
return ast.INVALID_FUNCTION
|
|
}
|
|
|
|
find_global_symbol :: proc(index: []Global_Index_Entry, scope: ast.Package_Id, name: symbol.Id) -> ast.Global_Id {
|
|
low := 0
|
|
high := len(index)
|
|
for low < high {
|
|
middle := low + (high-low)/2
|
|
entry := index[middle]
|
|
if entry.scope < scope || entry.scope == scope && int(entry.name) < int(name) {
|
|
low = middle + 1
|
|
} else {
|
|
high = middle
|
|
}
|
|
}
|
|
if low < len(index) && index[low].scope == scope && index[low].name == name {
|
|
return index[low].id
|
|
}
|
|
return ast.INVALID_GLOBAL
|
|
}
|
|
|
|
find_import_symbol :: proc(index: []Import_Index_Entry, scope: ast.File_Id, name: symbol.Id) -> ast.Import_Id {
|
|
low := 0
|
|
high := len(index)
|
|
for low < high {
|
|
middle := low + (high-low)/2
|
|
entry := index[middle]
|
|
if entry.scope < scope || entry.scope == scope && int(entry.name) < int(name) {
|
|
low = middle + 1
|
|
} else {
|
|
high = middle
|
|
}
|
|
}
|
|
if low < len(index) && index[low].scope == scope && index[low].name == name {
|
|
return index[low].id
|
|
}
|
|
return ast.INVALID_IMPORT
|
|
}
|
|
|
|
build_symbol_indexes :: proc(checker: ^Checker) {
|
|
checker.function_index = make([]Function_Index_Entry, len(checker.ast_module.functions), checker.allocator)
|
|
for function, id in checker.ast_module.functions {
|
|
checker.function_index[id] = Function_Index_Entry{scope=function.pkg, name=function.name, id=ast.function_id(id)}
|
|
}
|
|
slice.sort_by(checker.function_index, function_index_less)
|
|
|
|
checker.global_index = make([]Global_Index_Entry, len(checker.ast_module.globals), checker.allocator)
|
|
for global, id in checker.ast_module.globals {
|
|
checker.global_index[id] = Global_Index_Entry{scope=global.pkg, name=global.name, id=ast.global_id(id)}
|
|
}
|
|
slice.sort_by(checker.global_index, global_index_less)
|
|
|
|
checker.import_index = make([]Import_Index_Entry, len(checker.ast_module.imports), checker.allocator)
|
|
for import_item, id in checker.ast_module.imports {
|
|
checker.import_index[id] = Import_Index_Entry{scope=import_item.file, name=import_item.alias, id=ast.import_id(id)}
|
|
}
|
|
slice.sort_by(checker.import_index, import_index_less)
|
|
}
|
|
|
|
find_template :: proc(checker: ^Checker, name: symbol.Id, pkg := ast.Package_Id(0)) -> ast.Function_Id {
|
|
return find_function_symbol(checker.function_index, pkg, name)
|
|
}
|
|
|
|
find_global :: proc(checker: ^Checker, name: symbol.Id, pkg := ast.Package_Id(0)) -> ast.Global_Id {
|
|
return find_global_symbol(checker.global_index, pkg, name)
|
|
}
|
|
|
|
find_import :: proc(checker: ^Checker, file: ast.File_Id, alias: symbol.Id, mark_used := false) -> ast.Import_Id {
|
|
id := find_import_symbol(checker.import_index, file, alias)
|
|
if id != ast.INVALID_IMPORT && mark_used {
|
|
checker.ast_module.imports[id].used = true
|
|
}
|
|
return id
|
|
}
|
|
|
|
expr_package :: proc(checker: ^Checker, expr: ast.Expr, pkg: ast.Package_Id, file: ast.File_Id, mark_used := false) -> (ast.Package_Id, bool) {
|
|
if !symbol.is_valid(expr.qualifier) {
|
|
return pkg, true
|
|
}
|
|
import_id := find_import(checker, file, expr.qualifier, mark_used)
|
|
if import_id == ast.INVALID_IMPORT {
|
|
return ast.INVALID_PACKAGE, false
|
|
}
|
|
import_item := checker.ast_module.imports[import_id]
|
|
if import_item.target == ast.INVALID_PACKAGE || int(import_item.target) >= len(checker.ast_module.packages) ||
|
|
!checker.ast_module.packages[import_item.target].available {
|
|
return import_item.target, false
|
|
}
|
|
return import_item.target, true
|
|
}
|
|
|
|
add_package_resolution_diagnostic :: proc(checker: ^Checker, expr: ast.Expr, file: ast.File_Id) -> source.Diagnostic_Id {
|
|
if find_import(checker, file, expr.qualifier) == ast.INVALID_IMPORT {
|
|
return source.addf(checker.diagnostics, expr.span, "unknown package alias '%s'", symbol_text(checker, expr.qualifier))
|
|
}
|
|
return source.addf(checker.diagnostics, expr.span, "unavailable imported package '%s'", symbol_text(checker, expr.qualifier))
|
|
}
|
|
|
|
add_name_resolution_diagnostic :: proc(checker: ^Checker, expr: ast.Expr, target_pkg: ast.Package_Id) -> source.Diagnostic_Id {
|
|
if find_template(checker, expr.name, target_pkg) != ast.INVALID_FUNCTION {
|
|
return source.addf(checker.diagnostics, expr.span, "'%s' is a function, not a global value", symbol_text(checker, expr.name))
|
|
}
|
|
if symbol.is_valid(expr.qualifier) {
|
|
return source.addf(
|
|
checker.diagnostics,
|
|
expr.span,
|
|
"package '%s' has no member '%s'",
|
|
symbol_text(checker, expr.qualifier),
|
|
symbol_text(checker, expr.name),
|
|
)
|
|
}
|
|
return source.addf(checker.diagnostics, expr.span, "unresolved global '%s'", symbol_text(checker, expr.name))
|
|
}
|
|
|
|
add_call_resolution_diagnostic :: proc(checker: ^Checker, expr: ast.Expr, target_pkg: ast.Package_Id) -> source.Diagnostic_Id {
|
|
if find_global(checker, expr.name, target_pkg) != ast.INVALID_GLOBAL {
|
|
return source.addf(checker.diagnostics, expr.span, "'%s' is a global, not a function", symbol_text(checker, expr.name))
|
|
}
|
|
if symbol.is_valid(expr.qualifier) {
|
|
return source.addf(
|
|
checker.diagnostics,
|
|
expr.span,
|
|
"package '%s' has no member '%s'",
|
|
symbol_text(checker, expr.qualifier),
|
|
symbol_text(checker, expr.name),
|
|
)
|
|
}
|
|
return source.addf(checker.diagnostics, expr.span, "unresolved function '%s'", symbol_text(checker, expr.name))
|
|
}
|
|
|
|
find_unsupported :: proc(checker: ^Checker, pkg: ast.Package_Id, name: symbol.Id) -> (ast.Unsupported, bool) {
|
|
for item in checker.ast_module.unsupported {
|
|
if item.pkg == pkg && item.name == name {
|
|
return item, true
|
|
}
|
|
}
|
|
return {}, false
|
|
}
|
|
|
|
add_unsupported_diagnostic :: proc(checker: ^Checker, span: source.Span, pkg: ast.Package_Id, name: symbol.Id) -> source.Diagnostic_Id {
|
|
if item, ok := find_unsupported(checker, pkg, name); ok {
|
|
return source.addf(
|
|
checker.diagnostics,
|
|
span,
|
|
"C declaration '%s' is unavailable: %s",
|
|
symbol_text(checker, name),
|
|
item.reason,
|
|
)
|
|
}
|
|
return source.INVALID_DIAGNOSTIC
|
|
}
|
|
|
|
add_unsupported_type_diagnostic :: proc(
|
|
checker: ^Checker,
|
|
span: source.Span,
|
|
value: types.Type,
|
|
depth := 0,
|
|
) -> source.Diagnostic_Id {
|
|
if depth > 64 {
|
|
return source.INVALID_DIAGNOSTIC
|
|
}
|
|
item, ok := types.node(&checker.module.types, value)
|
|
if !ok {
|
|
return source.INVALID_DIAGNOSTIC
|
|
}
|
|
if item.kind == .Alias {
|
|
return add_unsupported_diagnostic(checker, span, ast.Package_Id(item.pkg), symbol.Id(item.name))
|
|
}
|
|
if types.is_valid(item.child) {
|
|
return add_unsupported_type_diagnostic(checker, span, item.child, depth+1)
|
|
}
|
|
return source.INVALID_DIAGNOSTIC
|
|
}
|
|
|
|
function_signatures_equal :: proc(left, right: ast.Function) -> bool {
|
|
if left.result != right.result || left.variadic != right.variadic || len(left.params) != len(right.params) {
|
|
return false
|
|
}
|
|
for param, index in left.params {
|
|
if param.type != right.params[index].type {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
valid_call_arity :: proc(function: ast.Function, count: int) -> bool {
|
|
return count >= len(function.params) if function.variadic else count == len(function.params)
|
|
}
|
|
|
|
call_arg_expected :: proc(function: ast.Function, index: int) -> types.Type {
|
|
if index < 0 || index >= len(function.params) {
|
|
return types.INVALID
|
|
}
|
|
return type_from_syntax(function.params[index].type)
|
|
}
|
|
|
|
callable_arg_expected :: proc(function_type: types.Type, function_item: types.Node, store: ^types.Store, index: int) -> types.Type {
|
|
if index < 0 || index >= int(function_item.field_count) {
|
|
return types.INVALID
|
|
}
|
|
params := types.params_for(store, function_type)
|
|
if index >= len(params) {
|
|
return types.INVALID
|
|
}
|
|
return params[index].type
|
|
}
|
|
|
|
valid_callable_arity :: proc(function_item: types.Node, count: int) -> bool {
|
|
return count >= int(function_item.field_count) if function_item.variadic else count == int(function_item.field_count)
|
|
}
|
|
|
|
function_value_signature :: proc(
|
|
checker: ^Checker,
|
|
template: ast.Function_Id,
|
|
) -> (params: []types.Type, result: types.Type, ok: bool) {
|
|
if template == ast.INVALID_FUNCTION || int(template) >= len(checker.ast_module.functions) {
|
|
return nil, types.INVALID, false
|
|
}
|
|
function := checker.ast_module.functions[template]
|
|
if !function.c_abi {
|
|
return nil, types.INVALID, false
|
|
}
|
|
result = type_from_syntax(function.result)
|
|
if !types.is_void(result) && !is_runtime_type(checker, result) {
|
|
return nil, types.INVALID, false
|
|
}
|
|
params = make([]types.Type, len(function.params), checker.allocator)
|
|
for param, index in function.params {
|
|
param_type := type_from_syntax(param.type)
|
|
if !is_runtime_type(checker, param_type) {
|
|
delete(params, checker.allocator)
|
|
return nil, types.INVALID, false
|
|
}
|
|
params[index] = param_type
|
|
}
|
|
return params, result, true
|
|
}
|
|
|
|
function_pointer_type_for_template :: proc(
|
|
checker: ^Checker,
|
|
template: ast.Function_Id,
|
|
demanded: ^[dynamic]Spec_Id = nil,
|
|
) -> (types.Type, Spec_Id, bool) {
|
|
params, result, ok := function_value_signature(checker, template)
|
|
if !ok {
|
|
return types.INVALID, INVALID_SPEC, false
|
|
}
|
|
defer delete(params, checker.allocator)
|
|
function := checker.ast_module.functions[template]
|
|
function_type := types.function(&checker.module.types, params, result, true, function.variadic)
|
|
pointer_type := types.pointer(&checker.module.types, function_type, false, true)
|
|
spec := INVALID_SPEC
|
|
if demanded == nil {
|
|
spec = ensure_spec(checker, template, params)
|
|
} else {
|
|
spec = find_spec(checker, template, params)
|
|
mark_spec_demanded(checker, spec, demanded)
|
|
}
|
|
return pointer_type, spec, spec != INVALID_SPEC
|
|
}
|
|
|
|
contains_name :: proc(names: []symbol.Id, name: symbol.Id) -> bool {
|
|
for existing in names {
|
|
if existing == name {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
mark_expr_imports_used :: proc(checker: ^Checker, expr_id: ast.Expr_Id, file: ast.File_Id) {
|
|
stack := checker.ast_expr_stack
|
|
clear_dynamic_array(&stack)
|
|
defer {
|
|
clear_dynamic_array(&stack)
|
|
checker.ast_expr_stack = stack
|
|
}
|
|
append(&stack, expr_id)
|
|
for len(stack) > 0 {
|
|
id := pop(&stack)
|
|
if id == ast.INVALID_EXPR || int(id) >= len(checker.ast_module.exprs) {
|
|
continue
|
|
}
|
|
expr := checker.ast_module.exprs[id]
|
|
if (expr.kind == .Name || expr.kind == .Call) && symbol.is_valid(expr.qualifier) {
|
|
_ = find_import(checker, file, expr.qualifier, true)
|
|
}
|
|
switch expr.kind {
|
|
case .Call:
|
|
append(&stack, ..expr.args)
|
|
if expr.left != ast.INVALID_EXPR {
|
|
append(&stack, expr.left)
|
|
}
|
|
case .Array, .Struct_Literal, .Slice:
|
|
append(&stack, ..expr.args)
|
|
if expr.left != ast.INVALID_EXPR {
|
|
append(&stack, expr.left)
|
|
}
|
|
case .Negate, .Not, .Address, .Deref, .Field, .Unwrap, .Keyed:
|
|
append(&stack, expr.left)
|
|
case .Add, .Sub, .Mul, .Div, .Index, .Orelse, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
|
|
append(&stack, expr.left, expr.right)
|
|
case .Invalid, .Integer, .Float, .String, .Bool, .None, .Name, .Enum_Literal:
|
|
}
|
|
}
|
|
}
|
|
|
|
mark_block_imports_used :: proc(checker: ^Checker, statements: []ast.Stmt_Id, file: ast.File_Id) {
|
|
for statement_id in statements {
|
|
statement := checker.ast_module.statements[statement_id]
|
|
switch statement.kind {
|
|
case .Declaration, .Assignment, .Return, .Expression:
|
|
mark_expr_imports_used(checker, statement.expr, file)
|
|
if statement.target != ast.INVALID_EXPR {
|
|
mark_expr_imports_used(checker, statement.target, file)
|
|
}
|
|
case .If:
|
|
mark_expr_imports_used(checker, statement.expr, file)
|
|
if statement.guard != ast.INVALID_EXPR {
|
|
mark_expr_imports_used(checker, statement.guard, file)
|
|
}
|
|
mark_block_imports_used(checker, statement.body, file)
|
|
mark_block_imports_used(checker, statement.else_body, file)
|
|
case .While:
|
|
mark_expr_imports_used(checker, statement.expr, file)
|
|
mark_block_imports_used(checker, statement.body, file)
|
|
if statement.update != ast.INVALID_STMT {
|
|
update := [1]ast.Stmt_Id{statement.update}
|
|
mark_block_imports_used(checker, update[:], file)
|
|
}
|
|
case .For:
|
|
mark_expr_imports_used(checker, statement.expr, file)
|
|
mark_block_imports_used(checker, statement.body, file)
|
|
case .Invalid:
|
|
}
|
|
}
|
|
}
|
|
|
|
validate_external_globals :: proc(checker: ^Checker) {
|
|
for global, global_index in checker.ast_module.globals {
|
|
checker.external_global_canonical[global_index] = ast.global_id(global_index)
|
|
if !global.external {
|
|
continue
|
|
}
|
|
switch global.link_name {
|
|
case "main":
|
|
checker.external_global_diagnostics[global_index] = source.add(
|
|
checker.diagnostics,
|
|
global.span,
|
|
"external C variable 'main' conflicts with the program entry point",
|
|
)
|
|
case "write":
|
|
checker.external_global_diagnostics[global_index] = source.add(
|
|
checker.diagnostics,
|
|
global.span,
|
|
"external C variable 'write' conflicts with the compiler runtime",
|
|
)
|
|
}
|
|
for previous, previous_index in checker.ast_module.globals[:global_index] {
|
|
if !previous.external || previous.link_name != global.link_name {
|
|
continue
|
|
}
|
|
canonical := checker.external_global_canonical[previous_index]
|
|
if canonical == ast.INVALID_GLOBAL {
|
|
canonical = ast.global_id(previous_index)
|
|
}
|
|
canonical_index := int(canonical)
|
|
if canonical_index < 0 || canonical_index >= len(checker.ast_module.globals) {
|
|
canonical = ast.global_id(previous_index)
|
|
canonical_index = previous_index
|
|
}
|
|
checker.external_global_canonical[global_index] = canonical
|
|
canonical_global := checker.ast_module.globals[canonical_index]
|
|
canonical_type := checker.global_types[canonical_index]
|
|
if !types.equal(checker.global_types[global_index], canonical_type) ||
|
|
global.writable != canonical_global.writable {
|
|
checker.external_global_diagnostics[global_index] = source.addf(
|
|
checker.diagnostics,
|
|
global.span,
|
|
"conflicting external C variable declarations for '%s'",
|
|
global.link_name,
|
|
)
|
|
}
|
|
checker.global_types[global_index] = canonical_type
|
|
break
|
|
}
|
|
for function in checker.ast_module.functions {
|
|
if !function.c_abi || function.has_body || len(function.unsupported_reason) > 0 ||
|
|
symbol_text(checker, function.name) != global.link_name {
|
|
continue
|
|
}
|
|
if checker.external_global_diagnostics[global_index] == source.INVALID_DIAGNOSTIC {
|
|
checker.external_global_diagnostics[global_index] = source.addf(
|
|
checker.diagnostics,
|
|
global.span,
|
|
"external C variable '%s' conflicts with a C function declaration",
|
|
global.link_name,
|
|
)
|
|
}
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
validate_declarations :: proc(checker: ^Checker) {
|
|
for function, function_id in checker.ast_module.functions {
|
|
if len(function.unsupported_reason) > 0 {
|
|
continue
|
|
}
|
|
locals: [dynamic]symbol.Id
|
|
locals.allocator = checker.allocator
|
|
for param in function.params {
|
|
if diagnostic := add_unsupported_type_diagnostic(checker, param.span, type_from_syntax(param.type));
|
|
diagnostic != source.INVALID_DIAGNOSTIC {
|
|
checker.template_diagnostics[function_id] = diagnostic
|
|
continue
|
|
}
|
|
if param.type == types.VOID {
|
|
source.add(
|
|
checker.diagnostics,
|
|
param.span,
|
|
"void is only valid as a function result type",
|
|
)
|
|
}
|
|
if contains_name(locals[:], param.name) {
|
|
source.addf(
|
|
checker.diagnostics,
|
|
param.span,
|
|
"duplicate parameter '%s'",
|
|
symbol_text(checker, param.name),
|
|
)
|
|
}
|
|
append(&locals, param.name)
|
|
if types.contains_c_struct_by_value(type_from_syntax(param.type), &checker.module.types) {
|
|
checker.template_diagnostics[function_id] = source.addf(
|
|
checker.diagnostics,
|
|
param.span,
|
|
"C records cannot be passed by value to '%s'",
|
|
symbol_text(checker, function.name),
|
|
)
|
|
}
|
|
}
|
|
if diagnostic := add_unsupported_type_diagnostic(checker, function.span, type_from_syntax(function.result));
|
|
diagnostic != source.INVALID_DIAGNOSTIC {
|
|
checker.template_diagnostics[function_id] = diagnostic
|
|
}
|
|
if types.contains_c_struct_by_value(type_from_syntax(function.result), &checker.module.types) {
|
|
checker.template_diagnostics[function_id] = source.addf(
|
|
checker.diagnostics,
|
|
function.span,
|
|
"C records cannot be returned by value from '%s'",
|
|
symbol_text(checker, function.name),
|
|
)
|
|
}
|
|
if !function.has_body && !function.c_abi {
|
|
checker.template_diagnostics[function_id] = source.addf(
|
|
checker.diagnostics,
|
|
function.span,
|
|
"bodyless function '%s' must use 'c_func'",
|
|
symbol_text(checker, function.name),
|
|
)
|
|
}
|
|
if function.variadic && (!function.c_abi || function.has_body) {
|
|
checker.template_diagnostics[function_id] = source.addf(
|
|
checker.diagnostics,
|
|
function.span,
|
|
"variadic function '%s' must be a bodyless 'c_func' declaration",
|
|
symbol_text(checker, function.name),
|
|
)
|
|
}
|
|
if !function.has_body && function.c_abi {
|
|
for param in function.params {
|
|
param_type := type_from_syntax(param.type)
|
|
if add_unsupported_type_diagnostic(checker, param.span, param_type) !=
|
|
source.INVALID_DIAGNOSTIC {
|
|
continue
|
|
}
|
|
if types.contains_c_struct_by_value(param_type, &checker.module.types) {
|
|
continue
|
|
}
|
|
if !types.is_c_signature_type(param_type, &checker.module.types) {
|
|
checker.template_diagnostics[function_id] = source.addf(
|
|
checker.diagnostics,
|
|
param.span,
|
|
"foreign function '%s' requires concrete parameter types",
|
|
symbol_text(checker, function.name),
|
|
)
|
|
}
|
|
}
|
|
result := type_from_syntax(function.result)
|
|
if add_unsupported_type_diagnostic(checker, function.span, result) == source.INVALID_DIAGNOSTIC &&
|
|
!types.contains_c_struct_by_value(result, &checker.module.types) &&
|
|
!types.is_c_signature_type(result, &checker.module.types, true) {
|
|
checker.template_diagnostics[function_id] = source.addf(
|
|
checker.diagnostics,
|
|
function.span,
|
|
"foreign function '%s' requires a concrete or void result type",
|
|
symbol_text(checker, function.name),
|
|
)
|
|
}
|
|
if function.pkg == 0 && function.name == checker.main_symbol {
|
|
checker.template_diagnostics[function_id] = source.add(
|
|
checker.diagnostics,
|
|
function.span,
|
|
"main must have a body",
|
|
)
|
|
}
|
|
}
|
|
mark_block_imports_used(checker, function.body, function.file)
|
|
delete(locals)
|
|
}
|
|
for function, function_id in checker.ast_module.functions {
|
|
if function.has_body || !function.c_abi {
|
|
continue
|
|
}
|
|
for other, other_id in checker.ast_module.functions {
|
|
if other_id == function_id || other.has_body || !other.c_abi || other.name != function.name {
|
|
continue
|
|
}
|
|
if function.imported && other.imported && function_signatures_equal(function, other) {
|
|
continue
|
|
}
|
|
checker.template_diagnostics[function_id] = source.addf(
|
|
checker.diagnostics,
|
|
function.span,
|
|
"duplicate foreign symbol '%s'",
|
|
symbol_text(checker, function.name),
|
|
)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
validate_type_nodes :: proc(checker: ^Checker) {
|
|
for item, index in checker.module.types.nodes {
|
|
id := types.DYNAMIC_START+types.Type(index)
|
|
if item.kind == .Distinct &&
|
|
(!item.declared || !types.is_runtime_value(item.child, &checker.module.types)) {
|
|
source.addf(
|
|
checker.diagnostics,
|
|
source.Span{},
|
|
"distinct type '%s' requires a concrete runtime backing type",
|
|
symbol_text(checker, symbol.Id(item.name)),
|
|
)
|
|
}
|
|
if item.kind == .Enum {
|
|
if !item.declared || !types.is_concrete_integer(item.child) {
|
|
source.addf(
|
|
checker.diagnostics,
|
|
source.Span{},
|
|
"enum type '%s' requires a concrete integer backing type",
|
|
symbol_text(checker, symbol.Id(item.name)),
|
|
)
|
|
} else {
|
|
for member in types.enum_members_for(&checker.module.types, id) {
|
|
if !fits_integer_type(member.value, item.child, checker.target) {
|
|
source.addf(
|
|
checker.diagnostics,
|
|
source.Span{},
|
|
"enum value %d does not fit in %s",
|
|
member.value,
|
|
types.name(item.child),
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if item.has_sentinel {
|
|
value := i128(item.sentinel)
|
|
if types.is_signed(item.child, checker.target) {
|
|
value = i128(i64(item.sentinel))
|
|
}
|
|
if !types.is_concrete_integer(item.child) || !fits_integer_type(value, item.child, checker.target) {
|
|
source.addf(
|
|
checker.diagnostics,
|
|
source.Span{},
|
|
"sentinel value does not fit array, slice, or pointer element type %s",
|
|
types.name(item.child),
|
|
)
|
|
}
|
|
}
|
|
if item.kind == .Struct || item.kind == .Union {
|
|
if item.c_layout && !item.opaque && item.field_count == 0 {
|
|
source.add(
|
|
checker.diagnostics,
|
|
source.Span{},
|
|
"c_struct definitions require at least one field",
|
|
)
|
|
}
|
|
for field in types.fields_for(&checker.module.types, id) {
|
|
if !types.is_runtime_value(field.type, &checker.module.types) {
|
|
source.add(
|
|
checker.diagnostics,
|
|
source.Span{},
|
|
"record fields must have runtime value types",
|
|
)
|
|
} else if item.c_layout && !types.is_c_record_field_type(field.type, &checker.module.types) {
|
|
source.add(
|
|
checker.diagnostics,
|
|
source.Span{},
|
|
"c_struct fields must have C-layout-compatible types",
|
|
)
|
|
}
|
|
}
|
|
}
|
|
if item.kind == .Function {
|
|
if !item.c_abi {
|
|
source.add(checker.diagnostics, source.Span{}, "only c_func function pointer types are supported")
|
|
}
|
|
for param in types.params_for(&checker.module.types, id) {
|
|
if types.is_void(param.type) || !types.is_c_signature_type(param.type, &checker.module.types) {
|
|
source.add(checker.diagnostics, source.Span{}, "function pointer parameters must be concrete C signature types")
|
|
}
|
|
}
|
|
if !types.is_c_signature_type(item.child, &checker.module.types, true) {
|
|
source.add(checker.diagnostics, source.Span{}, "function pointer results must be concrete C signature types or void")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
find_infer_local :: proc(locals: []Infer_Local, name: symbol.Id) -> types.Type {
|
|
for index := len(locals) - 1; index >= 0; index -= 1 {
|
|
if locals[index].name == name {
|
|
return locals[index].type
|
|
}
|
|
}
|
|
return types.INVALID
|
|
}
|
|
|
|
find_spec :: proc(checker: ^Checker, template: ast.Function_Id, actual_args: []types.Type) -> Spec_Id {
|
|
function := checker.ast_module.functions[template]
|
|
for spec, index in checker.specs {
|
|
if spec.template != template || len(spec.args) != len(function.params) {
|
|
continue
|
|
}
|
|
matches := true
|
|
for param, param_index in function.params {
|
|
actual := types.INVALID
|
|
if param_index < len(actual_args) {
|
|
actual = actual_args[param_index]
|
|
}
|
|
if !types.equal(spec.args[param_index], specialized_param_type(param.type, actual)) {
|
|
matches = false
|
|
break
|
|
}
|
|
}
|
|
if matches {
|
|
return spec_id(index)
|
|
}
|
|
}
|
|
return INVALID_SPEC
|
|
}
|
|
|
|
specialized_param_type :: proc(syntax: ast.Type_Syntax, actual: types.Type) -> types.Type {
|
|
declared := type_from_syntax(syntax)
|
|
if types.is_constraint(declared) {
|
|
return actual
|
|
}
|
|
return declared
|
|
}
|
|
|
|
can_specialize :: proc(checker: ^Checker, function: ast.Function, actual_args: []types.Type) -> bool {
|
|
for param, index in function.params {
|
|
actual := types.INVALID
|
|
if index < len(actual_args) {
|
|
actual = actual_args[index]
|
|
}
|
|
if !is_runtime_type(checker, specialized_param_type(param.type, actual)) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
ensure_spec :: proc(checker: ^Checker, template: ast.Function_Id, actual_args: []types.Type) -> Spec_Id {
|
|
if existing := find_spec(checker, template, actual_args); existing != INVALID_SPEC {
|
|
return existing
|
|
}
|
|
function := checker.ast_module.functions[template]
|
|
signature: [dynamic]types.Type
|
|
signature.allocator = checker.allocator
|
|
for param, index in function.params {
|
|
actual := types.INVALID
|
|
if index < len(actual_args) {
|
|
actual = actual_args[index]
|
|
}
|
|
append(&signature, specialized_param_type(param.type, actual))
|
|
}
|
|
result := type_from_syntax(function.result)
|
|
if function.pkg == 0 && function.name == checker.main_symbol && function.result == types.INT {
|
|
result = types.I32
|
|
}
|
|
index := spec_id(len(checker.specs))
|
|
append(
|
|
&checker.specs,
|
|
Spec{template = template, args = signature[:], result = result, hir_id = hir.INVALID_FUNCTION},
|
|
)
|
|
return index
|
|
}
|
|
|
|
mark_spec_demanded :: proc(checker: ^Checker, id: Spec_Id, stack: ^[dynamic]Spec_Id) {
|
|
if id == INVALID_SPEC || checker.specs[id].hir_id != hir.INVALID_FUNCTION {
|
|
return
|
|
}
|
|
checker.specs[id].hir_id = hir.Function_Id(0)
|
|
append(stack, id)
|
|
}
|
|
|
|
Infer_Frame :: struct {
|
|
expr: ast.Expr_Id,
|
|
stage: u8,
|
|
left: types.Type,
|
|
arg_index: int,
|
|
args: []types.Type,
|
|
template: ast.Function_Id,
|
|
}
|
|
|
|
infer_nested_expr :: proc(
|
|
checker: ^Checker,
|
|
expr_id: ast.Expr_Id,
|
|
locals: []Infer_Local,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
demanded: ^[dynamic]Spec_Id,
|
|
) -> types.Type {
|
|
outer := checker.infer_stack
|
|
checker.infer_stack = nil
|
|
checker.infer_stack.allocator = checker.allocator
|
|
result := infer_expr(checker, expr_id, locals, pkg, file, demanded)
|
|
delete(checker.infer_stack)
|
|
checker.infer_stack = outer
|
|
return result
|
|
}
|
|
|
|
infer_compound_expr :: proc(
|
|
checker: ^Checker,
|
|
expr: ast.Expr,
|
|
locals: []Infer_Local,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
demanded: ^[dynamic]Spec_Id,
|
|
) -> types.Type {
|
|
store := &checker.module.types
|
|
#partial switch expr.kind {
|
|
case .Bool:
|
|
return types.BOOL
|
|
case .Not:
|
|
_ = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded)
|
|
return types.BOOL
|
|
case .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or:
|
|
_ = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded)
|
|
_ = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded)
|
|
return types.BOOL
|
|
case .Range:
|
|
left := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded)
|
|
right := infer_nested_expr(checker, expr.right, locals, pkg, file, demanded)
|
|
left_const := eval_constant(checker, expr.left)
|
|
right_const := eval_constant(checker, expr.right)
|
|
child := types.INVALID
|
|
if left_const.kind == .Value && right_const.kind != .Value {
|
|
child = right
|
|
} else if right_const.kind == .Value && left_const.kind != .Value {
|
|
child = left
|
|
} else {
|
|
child = types.widest(left, right)
|
|
}
|
|
if !types.is_concrete_integer(child) {
|
|
return types.INVALID
|
|
}
|
|
return types.range(store, child)
|
|
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 .Enum_Literal:
|
|
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:
|
|
if enum_type, enum_ok := enum_type_from_field_expr(checker, expr, pkg, file); enum_ok {
|
|
_, member_ok := find_enum_member(checker, enum_type, expr.name)
|
|
return enum_type if member_ok else types.INVALID
|
|
}
|
|
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded)
|
|
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 == .Div_By_Zero ||
|
|
(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, .Enum_Literal,
|
|
.Bool, .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
|
|
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) {
|
|
if enum_type, enum_ok := enum_type_from_name_expr(checker, expr, pkg, file); enum_ok {
|
|
_, member_ok := find_enum_member(checker, enum_type, expr.name)
|
|
if member_ok {
|
|
last = enum_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, .Sub, .Mul, .Div:
|
|
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 {
|
|
distinct_type := types.find_named(&checker.module.types, u32(target_pkg), u32(expr.name))
|
|
distinct_item, distinct_ok := types.node(&checker.module.types, distinct_type)
|
|
if available && distinct_ok && distinct_item.kind == .Distinct && len(expr.args) == 1 {
|
|
stack[frame_index].left = distinct_type
|
|
stack[frame_index].stage = 7
|
|
append(&stack, Infer_Frame{expr=expr.args[0], template=ast.INVALID_FUNCTION})
|
|
continue
|
|
}
|
|
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 expr.kind == .Add && 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)
|
|
}
|
|
if frame.stage == 7 {
|
|
last = frame.left
|
|
_ = pop(&stack)
|
|
}
|
|
}
|
|
return last
|
|
}
|
|
|
|
flatten_conditional_unwrap_operands :: proc(
|
|
module: ^ast.Module,
|
|
expr_id: ast.Expr_Id,
|
|
operands: ^[dynamic]ast.Expr_Id,
|
|
) {
|
|
if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(module.exprs) {
|
|
append(operands, expr_id)
|
|
return
|
|
}
|
|
expr := module.exprs[expr_id]
|
|
if expr.kind == .And {
|
|
flatten_conditional_unwrap_operands(module, expr.left, operands)
|
|
flatten_conditional_unwrap_operands(module, expr.right, operands)
|
|
return
|
|
}
|
|
append(operands, expr_id)
|
|
}
|
|
|
|
infer_statements :: proc(
|
|
checker: ^Checker,
|
|
statements: []ast.Stmt_Id,
|
|
locals: ^[dynamic]Infer_Local,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
demanded: ^[dynamic]Spec_Id,
|
|
result: ^types.Type,
|
|
) {
|
|
scope_start := len(locals^)
|
|
for statement_id in statements {
|
|
statement := checker.ast_module.statements[statement_id]
|
|
#partial switch statement.kind {
|
|
case .Declaration:
|
|
value_type := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded)
|
|
declared_local := type_from_syntax(statement.type)
|
|
if is_runtime_type(checker, declared_local) {
|
|
value_type = declared_local
|
|
}
|
|
append(locals, Infer_Local{name = statement.name, type = value_type})
|
|
case .Assignment, .Expression:
|
|
if statement.target != ast.INVALID_EXPR {
|
|
_ = infer_expr(checker, statement.target, locals^[:], pkg, file, demanded)
|
|
}
|
|
_ = infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded)
|
|
case .Return:
|
|
if statement.expr != ast.INVALID_EXPR {
|
|
returned := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded)
|
|
if !types.is_valid(result^) {
|
|
result^ = returned
|
|
} else if !types.equal(result^, returned) {
|
|
result^ = types.widest(result^, returned)
|
|
}
|
|
}
|
|
case .If:
|
|
if len(statement.captures) > 0 {
|
|
operands: [dynamic]ast.Expr_Id
|
|
operands.allocator = checker.allocator
|
|
flatten_conditional_unwrap_operands(checker.ast_module, statement.expr, &operands)
|
|
operand_types := make([]types.Type, len(operands), checker.allocator)
|
|
for operand, index in operands {
|
|
operand_types[index] = infer_expr(checker, operand, locals^[:], pkg, file, demanded)
|
|
}
|
|
capture_start := len(locals^)
|
|
for capture, index in statement.captures {
|
|
if capture == checker.sink_symbol {
|
|
continue
|
|
}
|
|
capture_type := types.INVALID
|
|
if index < len(operand_types) &&
|
|
types.is_optional(operand_types[index], &checker.module.types) {
|
|
capture_type = types.child_type(operand_types[index], &checker.module.types)
|
|
}
|
|
append(locals, Infer_Local{name=capture, type=capture_type})
|
|
}
|
|
if statement.guard != ast.INVALID_EXPR {
|
|
_ = infer_expr(checker, statement.guard, locals^[:], pkg, file, demanded)
|
|
}
|
|
infer_statements(checker, statement.body, locals, pkg, file, demanded, result)
|
|
resize(locals, capture_start)
|
|
infer_statements(checker, statement.else_body, locals, pkg, file, demanded, result)
|
|
delete(operand_types, checker.allocator)
|
|
delete(operands)
|
|
} else {
|
|
_ = infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded)
|
|
infer_statements(checker, statement.body, locals, pkg, file, demanded, result)
|
|
infer_statements(checker, statement.else_body, locals, pkg, file, demanded, result)
|
|
}
|
|
case .While:
|
|
_ = infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded)
|
|
infer_statements(checker, statement.body, locals, pkg, file, demanded, result)
|
|
if statement.update != ast.INVALID_STMT {
|
|
update := [1]ast.Stmt_Id{statement.update}
|
|
infer_statements(checker, update[:], locals, pkg, file, demanded, result)
|
|
}
|
|
case .For:
|
|
iterable_type := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded)
|
|
capture_start := len(locals^)
|
|
capture_type := types.INVALID
|
|
if types.is_range(iterable_type, &checker.module.types) {
|
|
capture_type = types.child_type(iterable_type, &checker.module.types)
|
|
} else {
|
|
item, ok := sequence_item(iterable_type, &checker.module.types)
|
|
if ok {
|
|
capture_type = item.child
|
|
if statement.pointer_capture {
|
|
capture_type = types.pointer(&checker.module.types, item.child, item.mutable, false)
|
|
}
|
|
}
|
|
}
|
|
if symbol.is_valid(statement.name) {
|
|
append(locals, Infer_Local{name=statement.name, type=capture_type})
|
|
}
|
|
if symbol.is_valid(statement.index_name) {
|
|
append(locals, Infer_Local{name=statement.index_name, type=types.USIZE})
|
|
}
|
|
infer_statements(checker, statement.body, locals, pkg, file, demanded, result)
|
|
resize(locals, capture_start)
|
|
}
|
|
}
|
|
resize(locals, scope_start)
|
|
}
|
|
|
|
infer_spec_result :: proc(checker: ^Checker, id: Spec_Id, demanded: ^[dynamic]Spec_Id = nil) -> types.Type {
|
|
spec := checker.specs[id]
|
|
function := checker.ast_module.functions[spec.template]
|
|
declared := type_from_syntax(function.result)
|
|
if function.pkg == 0 && function.name == checker.main_symbol && function.result == types.INT {
|
|
declared = types.I32
|
|
}
|
|
|
|
locals: [dynamic]Infer_Local
|
|
locals.allocator = checker.allocator
|
|
defer delete(locals)
|
|
for param, index in function.params {
|
|
param_type := types.INVALID
|
|
if index < len(spec.args) {
|
|
param_type = spec.args[index]
|
|
}
|
|
append(&locals, Infer_Local{name = param.name, type = param_type})
|
|
}
|
|
|
|
result := types.INVALID
|
|
infer_statements(checker, function.body, &locals, function.pkg, function.file, demanded, &result)
|
|
if types.is_constraint(declared) {
|
|
return result
|
|
}
|
|
return declared
|
|
}
|
|
|
|
merge_inferred_type :: proc(store: ^types.Store, current: ^types.Type, inferred: types.Type) -> bool {
|
|
if !types.is_runtime_value(inferred, store) {
|
|
return false
|
|
}
|
|
if !types.is_runtime_value(current^, store) {
|
|
current^ = inferred
|
|
return true
|
|
}
|
|
if types.equal(current^, inferred) {
|
|
return false
|
|
}
|
|
merged := types.widest(current^, inferred)
|
|
if types.is_concrete_scalar(merged) && !types.equal(current^, merged) {
|
|
current^ = merged
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
infer_all :: proc(checker: ^Checker) {
|
|
for global, index in checker.ast_module.globals {
|
|
declared := type_from_syntax(global.type)
|
|
if is_runtime_type(checker, declared) {
|
|
checker.global_types[index] = declared
|
|
}
|
|
}
|
|
|
|
main_template := find_template(checker, checker.main_symbol, 0)
|
|
if main_template != ast.INVALID_FUNCTION {
|
|
ensure_spec(checker, main_template, nil)
|
|
}
|
|
|
|
for {
|
|
changed := false
|
|
spec_count := len(checker.specs)
|
|
for global, index in checker.ast_module.globals {
|
|
if global.external {
|
|
continue
|
|
}
|
|
inferred := infer_expr(checker, global.expr, nil, global.pkg, global.file)
|
|
if is_runtime_type(checker, type_from_syntax(global.type)) {
|
|
continue
|
|
}
|
|
changed = merge_inferred_type(&checker.module.types, &checker.global_types[index], inferred) || changed
|
|
}
|
|
for index := 0; index < len(checker.specs); index += 1 {
|
|
id := spec_id(index)
|
|
inferred := infer_spec_result(checker, id)
|
|
changed = merge_inferred_type(&checker.module.types, &checker.specs[id].result, inferred) || changed
|
|
}
|
|
if len(checker.specs) != spec_count {
|
|
changed = true
|
|
}
|
|
if !changed {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
prune_specs :: proc(checker: ^Checker) {
|
|
stack: [dynamic]Spec_Id
|
|
stack.allocator = checker.allocator
|
|
defer delete(stack)
|
|
|
|
main_template := find_template(checker, checker.main_symbol, 0)
|
|
if main_template != ast.INVALID_FUNCTION {
|
|
mark_spec_demanded(checker, find_spec(checker, main_template, nil), &stack)
|
|
}
|
|
for global in checker.ast_module.globals {
|
|
if global.external {
|
|
continue
|
|
}
|
|
_ = infer_expr(checker, global.expr, nil, global.pkg, global.file, &stack)
|
|
}
|
|
for len(stack) > 0 {
|
|
id := pop(&stack)
|
|
_ = infer_spec_result(checker, id, &stack)
|
|
}
|
|
|
|
retained := 0
|
|
for spec in checker.specs {
|
|
if spec.hir_id == hir.INVALID_FUNCTION {
|
|
delete(spec.args, checker.allocator)
|
|
continue
|
|
}
|
|
checker.specs[retained] = spec
|
|
checker.specs[retained].hir_id = hir.function_id(retained)
|
|
retained += 1
|
|
}
|
|
for len(checker.specs) > retained {
|
|
_ = pop(&checker.specs)
|
|
}
|
|
}
|
|
|
|
add_hir_expr :: proc(checker: ^Checker, expr: hir.Expr) -> hir.Expr_Id {
|
|
id := hir.expr_id(len(checker.module.exprs))
|
|
append(&checker.module.exprs, expr)
|
|
return id
|
|
}
|
|
|
|
invalid_hir_expr :: proc(
|
|
checker: ^Checker,
|
|
span: source.Span,
|
|
diagnostic: source.Diagnostic_Id,
|
|
recovery_type := types.INVALID,
|
|
) -> hir.Expr_Id {
|
|
return add_hir_expr(
|
|
checker,
|
|
hir.Expr {
|
|
kind = .Invalid,
|
|
span = span,
|
|
type = recovery_type,
|
|
target = hir.INVALID_REF,
|
|
left = hir.INVALID_EXPR,
|
|
right = hir.INVALID_EXPR,
|
|
diagnostic = diagnostic,
|
|
},
|
|
)
|
|
}
|
|
|
|
add_unique_global :: proc(values: ^[dynamic]hir.Global_Id, value: hir.Global_Id) {
|
|
for existing in values {
|
|
if existing == value {
|
|
return
|
|
}
|
|
}
|
|
append(values, value)
|
|
}
|
|
|
|
build_global_reference :: proc(
|
|
checker: ^Checker,
|
|
global: ast.Global_Id,
|
|
span: source.Span,
|
|
global_reads: ^[dynamic]hir.Global_Id,
|
|
) -> hir.Expr_Id {
|
|
if int(global) < len(checker.external_global_diagnostics) {
|
|
diagnostic := checker.external_global_diagnostics[global]
|
|
if diagnostic != source.INVALID_DIAGNOSTIC {
|
|
return invalid_hir_expr(checker, span, diagnostic, checker.global_types[global])
|
|
}
|
|
}
|
|
hir_global := hir.Global_Id(global)
|
|
add_unique_global(global_reads, hir_global)
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Global, span=span, type=checker.global_types[global],
|
|
target=hir.global_ref(hir_global), left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
|
|
add_unique_function :: proc(values: ^[dynamic]hir.Function_Id, value: hir.Function_Id) {
|
|
for existing in values {
|
|
if existing == value {
|
|
return
|
|
}
|
|
}
|
|
append(values, value)
|
|
}
|
|
|
|
find_build_local :: proc(locals: []Build_Local, name: symbol.Id) -> (Build_Local, bool) {
|
|
for index := len(locals) - 1; index >= 0; index -= 1 {
|
|
if locals[index].name == name {
|
|
return locals[index], true
|
|
}
|
|
}
|
|
return Build_Local{}, false
|
|
}
|
|
|
|
coerce_expr :: proc(
|
|
checker: ^Checker,
|
|
expr_id: hir.Expr_Id,
|
|
expected: types.Type,
|
|
span: source.Span,
|
|
) -> hir.Expr_Id {
|
|
if expr_id == hir.INVALID_EXPR {
|
|
return expr_id
|
|
}
|
|
actual := checker.module.exprs[expr_id].type
|
|
if types.equal(actual, expected) {
|
|
return expr_id
|
|
}
|
|
if types.can_weaken_pointer(actual, expected, &checker.module.types) {
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Weaken_Pointer,
|
|
span=span,
|
|
type=expected,
|
|
left=expr_id,
|
|
target=hir.INVALID_REF,
|
|
right=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
if types.can_weaken_slice(actual, expected, &checker.module.types) {
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Weaken_Slice,
|
|
span=span,
|
|
type=expected,
|
|
left=expr_id,
|
|
target=hir.INVALID_REF,
|
|
right=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
if types.can_decay_array_pointer(actual, expected, &checker.module.types) {
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Decay_Array_Pointer,
|
|
span=span,
|
|
type=expected,
|
|
left=expr_id,
|
|
target=hir.INVALID_REF,
|
|
right=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
if types.is_optional(expected, &checker.module.types) {
|
|
child := types.child_type(expected, &checker.module.types)
|
|
if types.equal(actual, child) ||
|
|
types.can_widen(actual, child) ||
|
|
types.can_weaken_pointer(actual, child, &checker.module.types) ||
|
|
types.can_weaken_slice(actual, child, &checker.module.types) ||
|
|
types.can_decay_array_pointer(actual, child, &checker.module.types) {
|
|
value := coerce_expr(checker, expr_id, child, span)
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Optional_Some,
|
|
span=span,
|
|
type=expected,
|
|
left=value,
|
|
target=hir.INVALID_REF,
|
|
right=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
}
|
|
if types.can_widen(actual, expected) {
|
|
return add_hir_expr(
|
|
checker,
|
|
hir.Expr {
|
|
kind = .Widen,
|
|
span = span,
|
|
type = expected,
|
|
left = expr_id,
|
|
target = hir.INVALID_REF,
|
|
right = hir.INVALID_EXPR,
|
|
diagnostic = source.INVALID_DIAGNOSTIC,
|
|
},
|
|
)
|
|
}
|
|
id := source.addf(
|
|
checker.diagnostics,
|
|
span,
|
|
"cannot implicitly convert %s to %s",
|
|
types.name(actual),
|
|
types.name(expected),
|
|
)
|
|
return invalid_hir_expr(checker, span, id, expected)
|
|
}
|
|
|
|
promote_c_vararg_expr :: proc(checker: ^Checker, expr_id: hir.Expr_Id, span: source.Span) -> hir.Expr_Id {
|
|
actual := checker.module.exprs[expr_id].type
|
|
if !types.is_c_vararg_type(actual, &checker.module.types) {
|
|
id := source.addf(
|
|
checker.diagnostics,
|
|
span,
|
|
"C variadic argument must be a concrete scalar or pointer, got %s",
|
|
types.name(actual),
|
|
)
|
|
return invalid_hir_expr(checker, span, id, types.C_INT)
|
|
}
|
|
promoted := types.c_vararg_promotion(actual, checker.target, &checker.module.types)
|
|
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 == .Div_By_Zero {
|
|
id := source.add(checker.diagnostics, expr.span, "division by zero in constant expression")
|
|
return invalid_hir_expr(checker, expr.span, id, recovery_type)
|
|
}
|
|
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,
|
|
target_type: 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
|
|
}
|
|
|
|
sequence_item :: proc(value: types.Type, store: ^types.Store) -> (types.Node, bool) {
|
|
item, ok := types.node(store, value)
|
|
if ok && (item.kind == .Array || item.kind == .Slice) {
|
|
return item, true
|
|
}
|
|
pointer, array, pointer_ok := types.array_pointer(value, store)
|
|
if pointer_ok {
|
|
array.mutable = pointer.mutable && array.mutable
|
|
return array, 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
|
|
}
|
|
|
|
find_enum_member :: proc(checker: ^Checker, enum_type: types.Type, name: symbol.Id) -> (types.Enum_Member, bool) {
|
|
for member in types.enum_members_for(&checker.module.types, enum_type) {
|
|
if member.name == u32(name) {
|
|
return member, true
|
|
}
|
|
}
|
|
return {}, false
|
|
}
|
|
|
|
enum_type_from_name_expr :: proc(
|
|
checker: ^Checker,
|
|
expr: ast.Expr,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
) -> (types.Type, bool) {
|
|
if !symbol.is_valid(expr.qualifier) ||
|
|
find_import(checker, file, expr.qualifier) != ast.INVALID_IMPORT {
|
|
return types.INVALID, false
|
|
}
|
|
enum_type := types.find_named(&checker.module.types, u32(pkg), u32(expr.qualifier))
|
|
return enum_type, types.is_enum(enum_type, &checker.module.types)
|
|
}
|
|
|
|
enum_type_from_field_expr :: proc(
|
|
checker: ^Checker,
|
|
expr: ast.Expr,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
mark_used := false,
|
|
) -> (types.Type, bool) {
|
|
if expr.left == ast.INVALID_EXPR || int(expr.left) >= len(checker.ast_module.exprs) {
|
|
return types.INVALID, false
|
|
}
|
|
base := checker.ast_module.exprs[expr.left]
|
|
if base.kind != .Name || !symbol.is_valid(base.qualifier) {
|
|
return types.INVALID, false
|
|
}
|
|
target_pkg, available := expr_package(checker, base, pkg, file, mark_used)
|
|
if !available {
|
|
return types.INVALID, false
|
|
}
|
|
enum_type := types.find_named(&checker.module.types, u32(target_pkg), u32(base.name))
|
|
return enum_type, types.is_enum(enum_type, &checker.module.types)
|
|
}
|
|
|
|
enum_member_hir :: proc(
|
|
checker: ^Checker,
|
|
enum_type: types.Type,
|
|
name: symbol.Id,
|
|
span: source.Span,
|
|
) -> hir.Expr_Id {
|
|
member, ok := find_enum_member(checker, enum_type, name)
|
|
if !ok {
|
|
id := source.addf(checker.diagnostics, span, "unknown enum member '%s'", symbol_text(checker, name))
|
|
return invalid_hir_expr(checker, span, id, enum_type)
|
|
}
|
|
value := i64(member.value) if member.value < 0 else transmute(i64)u64(member.value)
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Integer,
|
|
span=span,
|
|
type=enum_type,
|
|
integer=value,
|
|
target=hir.INVALID_REF,
|
|
left=hir.INVALID_EXPR,
|
|
right=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
|
|
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 .Enum_Literal:
|
|
if !types.is_enum(expected, store) {
|
|
id := source.addf(
|
|
checker.diagnostics,
|
|
expr.span,
|
|
"'.%s' requires an enum context",
|
|
symbol_text(checker, expr.name),
|
|
)
|
|
return invalid_hir_expr(checker, expr.span, id, expected)
|
|
}
|
|
return enum_member_hir(checker, expected, expr.name, expr.span)
|
|
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:
|
|
if enum_type, enum_ok := enum_type_from_field_expr(checker, expr, pkg, file, true); enum_ok {
|
|
return enum_member_hir(checker, enum_type, expr.name, expr.span)
|
|
}
|
|
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 .Range:
|
|
expected_child := types.INVALID
|
|
if types.is_range(expected, store) {
|
|
expected_child = types.child_type(expected, store)
|
|
}
|
|
left_const := eval_constant(checker, expr.left)
|
|
right_const := eval_constant(checker, expr.right)
|
|
left, right: hir.Expr_Id
|
|
if types.is_valid(expected_child) {
|
|
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, expected_child, pkg, file)
|
|
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, expected_child, pkg, file)
|
|
} else if right_const.kind == .Value && left_const.kind != .Value {
|
|
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
|
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, checker.module.exprs[left].type, pkg, file)
|
|
} else if left_const.kind == .Value && right_const.kind != .Value {
|
|
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, types.INVALID, pkg, file)
|
|
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, checker.module.exprs[right].type, pkg, file)
|
|
} else {
|
|
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
|
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, types.INVALID, pkg, file)
|
|
}
|
|
child := expected_child
|
|
if !types.is_valid(child) {
|
|
child = types.widest(checker.module.exprs[left].type, checker.module.exprs[right].type)
|
|
}
|
|
if !types.is_concrete_integer(child) {
|
|
id := source.add(checker.diagnostics, expr.span, "range bounds must be compatible concrete integers")
|
|
return invalid_hir_expr(checker, expr.span, id)
|
|
}
|
|
left = coerce_expr(checker, left, child, checker.module.exprs[left].span)
|
|
right = coerce_expr(checker, right, child, checker.module.exprs[right].span)
|
|
args := make([]hir.Expr_Id, 2, checker.allocator)
|
|
args[0] = left
|
|
args[1] = right
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Range, span=expr.span, type=types.range(store, child),
|
|
integer=i64(expr.integer), args=args,
|
|
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .Bool:
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Bool, span=expr.span, type=types.BOOL, integer=i64(expr.integer),
|
|
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .Not:
|
|
operand := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.BOOL, pkg, file)
|
|
operand_type := checker.module.exprs[operand].type
|
|
if checker.module.exprs[operand].kind != .Invalid && !types.is_bool(operand_type) {
|
|
id := source.add(checker.diagnostics, expr.span, "'!' requires a bool operand")
|
|
return invalid_hir_expr(checker, expr.span, id, types.BOOL)
|
|
}
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Not, span=expr.span, type=types.BOOL, left=operand,
|
|
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .And, .Or:
|
|
left := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.BOOL, pkg, file)
|
|
right := build_nested_expr(checker, expr.right, locals, global_reads, calls, types.BOOL, pkg, file)
|
|
left_type := checker.module.exprs[left].type
|
|
right_type := checker.module.exprs[right].type
|
|
left_ok := checker.module.exprs[left].kind == .Invalid || types.is_bool(left_type)
|
|
right_ok := checker.module.exprs[right].kind == .Invalid || types.is_bool(right_type)
|
|
if !left_ok || !right_ok {
|
|
id := source.add(checker.diagnostics, expr.span, "'and'/'or' require bool operands")
|
|
return invalid_hir_expr(checker, expr.span, id, types.BOOL)
|
|
}
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.And if expr.kind == .And else .Or, span=expr.span, type=types.BOOL,
|
|
left=left, right=right, target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .Eq, .Ne, .Lt, .Le, .Gt, .Ge:
|
|
// Contextualize a bare integer-literal operand to the other operand's type
|
|
// so comparisons like `count > 0` or `0 < count` type-check.
|
|
left_const := eval_constant(checker, expr.left)
|
|
right_const := eval_constant(checker, expr.right)
|
|
left, right: hir.Expr_Id
|
|
left_expr := checker.ast_module.exprs[expr.left]
|
|
right_expr := checker.ast_module.exprs[expr.right]
|
|
if right_expr.kind == .Enum_Literal && left_expr.kind != .Enum_Literal {
|
|
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
|
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, checker.module.exprs[left].type, pkg, file)
|
|
} else if left_expr.kind == .Enum_Literal && right_expr.kind != .Enum_Literal {
|
|
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, types.INVALID, pkg, file)
|
|
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, checker.module.exprs[right].type, pkg, file)
|
|
} else if right_const.kind == .Value && left_const.kind != .Value {
|
|
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
|
hint := checker.module.exprs[left].type
|
|
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, hint, pkg, file)
|
|
} else if left_const.kind == .Value && right_const.kind != .Value {
|
|
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, types.INVALID, pkg, file)
|
|
hint := checker.module.exprs[right].type
|
|
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, hint, pkg, file)
|
|
} else {
|
|
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
|
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, types.INVALID, pkg, file)
|
|
}
|
|
left_type := checker.module.exprs[left].type
|
|
right_type := checker.module.exprs[right].type
|
|
if checker.module.exprs[left].kind == .Invalid || checker.module.exprs[right].kind == .Invalid {
|
|
return invalid_hir_expr(checker, expr.span, expr.diagnostic, types.BOOL)
|
|
}
|
|
operand_type := types.INVALID
|
|
if types.is_enum(left_type, store) || types.is_enum(right_type, store) {
|
|
if !types.equal(left_type, right_type) || (expr.kind != .Eq && expr.kind != .Ne) {
|
|
id := source.add(checker.diagnostics, expr.span, "enum values only support '==' and '!=' with the same enum type")
|
|
return invalid_hir_expr(checker, expr.span, id, types.BOOL)
|
|
}
|
|
operand_type = left_type
|
|
} else if types.is_bool(left_type) && types.is_bool(right_type) {
|
|
if expr.kind != .Eq && expr.kind != .Ne {
|
|
id := source.add(checker.diagnostics, expr.span, "bool values only support '==' and '!='")
|
|
return invalid_hir_expr(checker, expr.span, id, types.BOOL)
|
|
}
|
|
operand_type = types.BOOL
|
|
} else {
|
|
operand_type = types.widest(left_type, right_type)
|
|
if !types.is_concrete_scalar(operand_type) || types.is_bool(operand_type) {
|
|
id := source.add(checker.diagnostics, expr.span, "comparison requires compatible numeric operands")
|
|
return invalid_hir_expr(checker, expr.span, id, types.BOOL)
|
|
}
|
|
}
|
|
left = coerce_expr(checker, left, operand_type, checker.module.exprs[left].span)
|
|
right = coerce_expr(checker, right, operand_type, checker.module.exprs[right].span)
|
|
compare_kind := hir.Expr_Kind.Eq
|
|
#partial switch expr.kind {
|
|
case .Eq: compare_kind = .Eq
|
|
case .Ne: compare_kind = .Ne
|
|
case .Lt: compare_kind = .Lt
|
|
case .Le: compare_kind = .Le
|
|
case .Gt: compare_kind = .Gt
|
|
case .Ge: compare_kind = .Ge
|
|
}
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=compare_kind, span=expr.span, type=types.BOOL, left=left, right=right,
|
|
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .Struct_Literal:
|
|
target_pkg, available := expr_package(checker, expr, pkg, file, true)
|
|
struct_type := types.find_named(store, u32(target_pkg), u32(expr.name)) if available else types.INVALID
|
|
struct_type = types.resolve_alias(struct_type, store)
|
|
if !types.is_record(struct_type, store) || types.is_opaque_struct(struct_type, store) {
|
|
id := source.addf(checker.diagnostics, expr.span, "unknown or opaque record type '%s'", symbol_text(checker, expr.name))
|
|
return invalid_hir_expr(checker, expr.span, id)
|
|
}
|
|
fields := types.fields_for(store, struct_type)
|
|
union_record := types.is_union(struct_type, store)
|
|
if union_record && len(expr.args) != 1 {
|
|
id := source.add(checker.diagnostics, expr.span, "union literal requires exactly one field initializer")
|
|
return invalid_hir_expr(checker, expr.span, id, struct_type)
|
|
}
|
|
values := make([]hir.Expr_Id, 1 if union_record else len(fields), checker.allocator)
|
|
initialized := make([]bool, len(fields), checker.allocator)
|
|
defer delete(initialized, checker.allocator)
|
|
for &value in values {
|
|
value = hir.INVALID_EXPR
|
|
}
|
|
for keyed in expr.args {
|
|
keyed_expr := checker.ast_module.exprs[keyed]
|
|
index, field, ok := find_struct_field(checker, struct_type, keyed_expr.name)
|
|
if !ok {
|
|
source.addf(checker.diagnostics, keyed_expr.span, "unknown struct field '%s'", symbol_text(checker, keyed_expr.name))
|
|
continue
|
|
}
|
|
if initialized[index] {
|
|
source.addf(checker.diagnostics, keyed_expr.span, "duplicate initializer for struct field '%s'", symbol_text(checker, keyed_expr.name))
|
|
continue
|
|
}
|
|
initialized[index] = true
|
|
value_index := 0 if union_record else index
|
|
values[value_index] = build_nested_expr(checker, keyed_expr.left, locals, global_reads, calls, field.type, pkg, file)
|
|
values[value_index] = coerce_expr(checker, values[value_index], field.type, keyed_expr.span)
|
|
}
|
|
if !union_record {
|
|
for field, index in fields {
|
|
if values[index] != hir.INVALID_EXPR {
|
|
continue
|
|
}
|
|
id := source.addf(checker.diagnostics, expr.span, "missing initializer for struct field '%s'", symbol_text(checker, symbol.Id(field.name)))
|
|
delete(values, checker.allocator)
|
|
return invalid_hir_expr(checker, expr.span, id, struct_type)
|
|
}
|
|
}
|
|
active_field: i64
|
|
if union_record {
|
|
if values[0] == hir.INVALID_EXPR {
|
|
delete(values, checker.allocator)
|
|
return invalid_hir_expr(checker, expr.span, source.add(checker.diagnostics, expr.span, "union literal requires a known field"), struct_type)
|
|
}
|
|
for value, index in initialized {
|
|
if value {
|
|
active_field = i64(index)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Struct, span=expr.span, type=struct_type, args=values, integer=active_field,
|
|
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .Keyed:
|
|
id := source.add(checker.diagnostics, expr.span, "keyed initializer is only valid inside a struct literal")
|
|
return invalid_hir_expr(checker, expr.span, id)
|
|
case:
|
|
return invalid_hir_expr(checker, expr.span, expr.diagnostic)
|
|
}
|
|
}
|
|
|
|
// build_binary_arith constructs the HIR node for `left op right`, where `op` is
|
|
// an arithmetic AST kind (`Add`/`Sub`/`Mul`/`Div`). It models many-pointer `+`
|
|
// as `Pointer_Add`, coerces both operands to their common type, and emits the
|
|
// "arithmetic requires compatible numeric operands" diagnostic when they have no
|
|
// shared numeric type.
|
|
build_binary_arith :: proc(
|
|
checker: ^Checker,
|
|
op: ast.Expr_Kind,
|
|
left, right: hir.Expr_Id,
|
|
span: source.Span,
|
|
) -> hir.Expr_Id {
|
|
// Pointer arithmetic is only defined for `+` (many-pointer + usize).
|
|
if op == .Add &&
|
|
types.is_many_pointer(checker.module.exprs[left].type, &checker.module.types) &&
|
|
types.equal(checker.module.exprs[right].type, types.USIZE) {
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Pointer_Add, span=span, type=checker.module.exprs[left].type,
|
|
left=left, right=right, target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
result := types.widest(checker.module.exprs[left].type, checker.module.exprs[right].type)
|
|
if !types.is_concrete_scalar(result) {
|
|
id := source.add(checker.diagnostics, span, "arithmetic requires compatible numeric operands")
|
|
return invalid_hir_expr(checker, span, id)
|
|
}
|
|
result_kind := hir.Expr_Kind.Add
|
|
#partial switch op {
|
|
case .Sub: result_kind = .Sub
|
|
case .Mul: result_kind = .Mul
|
|
case .Div: result_kind = .Div
|
|
}
|
|
coerced_left := coerce_expr(checker, left, result, checker.module.exprs[left].span)
|
|
coerced_right := coerce_expr(checker, right, result, checker.module.exprs[right].span)
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=result_kind, span=span, type=result, left=coerced_left, right=coerced_right,
|
|
target=hir.INVALID_REF, diagnostic=source.INVALID_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 || constant.kind == .Div_By_Zero {
|
|
last = build_constant_expr(checker, expr, constant, frame.expected)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
switch expr.kind {
|
|
case .String, .Array, .None, .Address, .Deref, .Index, .Slice,
|
|
.Field, .Unwrap, .Orelse, .Struct_Literal, .Keyed,
|
|
.Bool, .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range,
|
|
.Enum_Literal:
|
|
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 {
|
|
if enum_type, enum_ok := enum_type_from_name_expr(checker, expr, pkg, file); enum_ok {
|
|
last = enum_member_hir(checker, enum_type, expr.name, expr.span)
|
|
}
|
|
}
|
|
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, .Sub, .Mul, .Div:
|
|
stack[frame_index].stage = 1
|
|
// Preserve assignment/return context for literal operands, e.g.
|
|
// assigning `i + 1` back into a `u32` local.
|
|
left_expected := types.INVALID
|
|
if types.is_concrete_scalar(frame.expected) && !types.is_bool(frame.expected) {
|
|
left_expected = frame.expected
|
|
}
|
|
append(&stack, Build_Expr_Frame{expr=expr.left, expected=left_expected, 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
|
|
non_callable := false
|
|
non_callable_global := false
|
|
if !symbol.is_valid(expr.qualifier) {
|
|
if local, ok := find_build_local(locals, expr.name); ok {
|
|
if _, _, _, callable := types.function_pointer(local.type, &checker.module.types); callable {
|
|
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,
|
|
})
|
|
} else {
|
|
non_callable = true
|
|
}
|
|
}
|
|
}
|
|
if callee == hir.INVALID_EXPR && !non_callable {
|
|
if global := find_global(checker, expr.name, target_pkg); global != ast.INVALID_GLOBAL {
|
|
if _, _, _, callable := types.function_pointer(checker.global_types[global], &checker.module.types); callable {
|
|
callee = build_global_reference(checker, global, expr.span, global_reads)
|
|
} else {
|
|
non_callable = true
|
|
non_callable_global = true
|
|
}
|
|
}
|
|
}
|
|
if callee == hir.INVALID_EXPR {
|
|
distinct_type := types.find_named(&checker.module.types, u32(target_pkg), u32(expr.name))
|
|
distinct_item, distinct_ok := types.node(&checker.module.types, distinct_type)
|
|
if distinct_ok && distinct_item.kind == .Distinct {
|
|
if !is_runtime_type(checker, distinct_type) {
|
|
id := source.addf(
|
|
checker.diagnostics,
|
|
expr.span,
|
|
"distinct type '%s' has no concrete runtime backing type",
|
|
symbol_text(checker, expr.name),
|
|
)
|
|
last = invalid_hir_expr(checker, expr.span, id)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
if len(expr.args) != 1 {
|
|
id := source.addf(
|
|
checker.diagnostics,
|
|
expr.span,
|
|
"distinct type '%s' expects 1 argument, got %d",
|
|
symbol_text(checker, expr.name),
|
|
len(expr.args),
|
|
)
|
|
last = invalid_hir_expr(checker, expr.span, id, distinct_type)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
stack[frame_index].target_type = distinct_type
|
|
stack[frame_index].stage = 8
|
|
append(&stack, Build_Expr_Frame{
|
|
expr=expr.args[0],
|
|
expected=distinct_item.child,
|
|
template=ast.INVALID_FUNCTION,
|
|
})
|
|
continue
|
|
}
|
|
id := source.INVALID_DIAGNOSTIC
|
|
if non_callable {
|
|
id = add_call_resolution_diagnostic(checker, expr, target_pkg) if non_callable_global else
|
|
source.add(checker.diagnostics, expr.span, "call target is not a function pointer")
|
|
} else {
|
|
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, _ := types.function_pointer(checker.module.exprs[callee].type, &checker.module.types)
|
|
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
|
|
} else if eval_constant(checker, expr.right).kind == .Value {
|
|
// A constant RHS adopts the concrete LHS type before numeric
|
|
// compatibility is checked.
|
|
right_expected = checker.module.exprs[last].type
|
|
} else if types.is_concrete_scalar(frame.expected) && !types.is_bool(frame.expected) {
|
|
right_expected = frame.expected
|
|
}
|
|
append(&stack, Build_Expr_Frame{expr=expr.right, expected=right_expected, template=ast.INVALID_FUNCTION})
|
|
continue
|
|
}
|
|
if frame.stage == 2 {
|
|
last = build_binary_arith(checker, expr.kind, frame.left, last, expr.span)
|
|
_ = 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)
|
|
}
|
|
if frame.stage == 8 {
|
|
distinct_item, ok := types.node(&checker.module.types, frame.target_type)
|
|
actual := checker.module.exprs[last].type
|
|
if !ok || distinct_item.kind != .Distinct || !types.equal(actual, distinct_item.child) {
|
|
id := source.addf(
|
|
checker.diagnostics,
|
|
expr.span,
|
|
"distinct type '%s' requires an exact %s value, got %s",
|
|
symbol_text(checker, expr.name),
|
|
types.name(distinct_item.child),
|
|
types.name(actual),
|
|
)
|
|
last = invalid_hir_expr(checker, expr.span, id, frame.target_type)
|
|
} else {
|
|
last = add_hir_expr(checker, hir.Expr{
|
|
kind=.Retype,
|
|
span=expr.span,
|
|
type=frame.target_type,
|
|
left=last,
|
|
target=hir.INVALID_REF,
|
|
right=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
_ = pop(&stack)
|
|
}
|
|
}
|
|
return last
|
|
}
|
|
|
|
make_link_name :: proc(checker: ^Checker, id: Spec_Id) -> string {
|
|
spec := checker.specs[id]
|
|
function := checker.ast_module.functions[spec.template]
|
|
if function.pkg == 0 && function.name == checker.main_symbol {
|
|
return fmt.aprintf("main", allocator = checker.allocator)
|
|
}
|
|
if !function.has_body && function.c_abi {
|
|
if len(function.link_name) > 0 {
|
|
return strings.clone(function.link_name, checker.allocator)
|
|
}
|
|
return fmt.aprintf("%s", symbol_text(checker, function.name), allocator = checker.allocator)
|
|
}
|
|
builder := strings.builder_make(checker.allocator)
|
|
defer strings.builder_destroy(&builder)
|
|
strings.write_string(&builder, "bro_c__" if function.c_abi else "bro__")
|
|
fmt.sbprintf(&builder, "p%d__", function.pkg)
|
|
strings.write_string(&builder, symbol_text(checker, function.name))
|
|
for arg in spec.args {
|
|
strings.write_string(&builder, "__")
|
|
if arg >= types.DYNAMIC_START {
|
|
fmt.sbprintf(&builder, "t%d", arg)
|
|
} else {
|
|
strings.write_string(&builder, types.name(arg))
|
|
}
|
|
}
|
|
return fmt.aprintf("%s", strings.to_string(builder), allocator = checker.allocator)
|
|
}
|
|
|
|
build_block :: proc(
|
|
ctx: ^Build_Ctx,
|
|
statements: []ast.Stmt_Id,
|
|
duplicate_scope_start := -1,
|
|
) -> []hir.Stmt_Id {
|
|
checker := ctx.checker
|
|
body: [dynamic]hir.Stmt_Id
|
|
body.allocator = checker.allocator
|
|
scope_start := len(ctx.locals^)
|
|
duplicate_start := scope_start if duplicate_scope_start < 0 else duplicate_scope_start
|
|
for statement_id in statements {
|
|
statement := checker.ast_module.statements[statement_id]
|
|
switch statement.kind {
|
|
case .Declaration:
|
|
declared := resolve_inferred_array(checker, type_from_syntax(statement.type), statement.expr)
|
|
expected := types.INVALID
|
|
if is_runtime_type(checker, declared) {
|
|
expected = declared
|
|
}
|
|
value := build_expr(
|
|
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
|
expected, ctx.pkg, ctx.file,
|
|
)
|
|
value_type := checker.module.exprs[value].type
|
|
if is_runtime_type(checker, declared) {
|
|
value = coerce_expr(checker, value, declared, statement.span)
|
|
value_type = checker.module.exprs[value].type
|
|
} else if types.is_void(declared) {
|
|
id := source.add(checker.diagnostics, statement.span, "locals cannot have type void")
|
|
value = invalid_hir_expr(checker, statement.span, id)
|
|
value_type = types.INVALID
|
|
}
|
|
if _, found := find_build_local(ctx.locals^[duplicate_start:], statement.name); found {
|
|
id := source.addf(
|
|
checker.diagnostics, statement.span,
|
|
"duplicate local '%s'", symbol_text(checker, statement.name),
|
|
)
|
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
|
|
local = hir.INVALID_LOCAL, diagnostic = id,
|
|
})
|
|
ctx.problematic^ = true
|
|
continue
|
|
}
|
|
local_id := hir.local_id(len(ctx.hir_locals^))
|
|
append(ctx.hir_locals, hir.Local{
|
|
name = statement.name, type = value_type, mutable = !statement.immutable,
|
|
})
|
|
append(ctx.locals, Build_Local{
|
|
name = statement.name, type = value_type, mutable = !statement.immutable, id = local_id,
|
|
})
|
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind = .Declaration, span = statement.span, local = local_id, expr = value,
|
|
diagnostic = source.INVALID_DIAGNOSTIC,
|
|
})
|
|
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[value].kind == .Invalid
|
|
case .Assignment:
|
|
if statement.target != ast.INVALID_EXPR {
|
|
target_expr := build_expr(
|
|
checker, statement.target, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
|
types.INVALID, ctx.pkg, ctx.file,
|
|
)
|
|
target_type := checker.module.exprs[target_expr].type
|
|
if !hir_location_writable(checker, target_expr, ctx.locals^[:]) {
|
|
id := source.add(checker.diagnostics, statement.span, "assignment target is not writable")
|
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind=.Trap, span=statement.span, local=hir.INVALID_LOCAL,
|
|
target=hir.INVALID_EXPR, expr=hir.INVALID_EXPR, diagnostic=id,
|
|
})
|
|
ctx.problematic^ = true
|
|
continue
|
|
}
|
|
value: hir.Expr_Id
|
|
assignment_op := hir.Assignment_Op.Set
|
|
if statement.assignment_op != .Set {
|
|
rhs_expected := target_type
|
|
if types.is_many_pointer(target_type, &checker.module.types) {
|
|
rhs_expected = types.USIZE if statement.assignment_op == .Add else types.INVALID
|
|
}
|
|
value = build_expr(
|
|
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
|
rhs_expected, ctx.pkg, ctx.file,
|
|
)
|
|
if types.is_many_pointer(target_type, &checker.module.types) {
|
|
assignment_op = .Pointer_Add
|
|
if statement.assignment_op != .Add {
|
|
id := source.add(
|
|
checker.diagnostics,
|
|
statement.span,
|
|
"many-item pointers only support '+=' compound assignment",
|
|
)
|
|
value = invalid_hir_expr(checker, statement.span, id, types.USIZE)
|
|
} else {
|
|
value = coerce_expr(checker, value, types.USIZE, statement.span)
|
|
}
|
|
} else {
|
|
#partial switch statement.assignment_op {
|
|
case .Add: assignment_op = .Add
|
|
case .Sub: assignment_op = .Sub
|
|
case .Mul: assignment_op = .Mul
|
|
case .Div: assignment_op = .Div
|
|
}
|
|
rhs_type := checker.module.exprs[value].type
|
|
result_type := types.widest(target_type, rhs_type)
|
|
if !types.is_concrete_scalar(result_type) ||
|
|
types.is_bool(result_type) {
|
|
id := source.add(
|
|
checker.diagnostics,
|
|
statement.span,
|
|
"arithmetic requires compatible numeric operands",
|
|
)
|
|
value = invalid_hir_expr(checker, statement.span, id, target_type)
|
|
} else {
|
|
// Compound assignment stores back into the original
|
|
// target type, so only an equal or widening RHS
|
|
// conversion is permitted.
|
|
value = coerce_expr(checker, value, target_type, statement.span)
|
|
}
|
|
}
|
|
} else {
|
|
value = build_expr(
|
|
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
|
target_type, ctx.pkg, ctx.file,
|
|
)
|
|
value = coerce_expr(checker, value, target_type, statement.span)
|
|
}
|
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind=.Assignment, span=statement.span, local=hir.INVALID_LOCAL,
|
|
assignment_op=assignment_op,
|
|
target=target_expr, expr=value, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[value].kind == .Invalid
|
|
continue
|
|
}
|
|
if statement.name == checker.sink_symbol {
|
|
value := build_expr(checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, types.INVALID, ctx.pkg, ctx.file)
|
|
if types.is_void(checker.module.exprs[value].type) {
|
|
id := source.add(checker.diagnostics, statement.span, "cannot assign a void expression to '_'")
|
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
|
|
local = hir.INVALID_LOCAL, diagnostic = id,
|
|
})
|
|
ctx.problematic^ = true
|
|
} else {
|
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind = .Sink, span = statement.span, expr = value,
|
|
local = hir.INVALID_LOCAL, diagnostic = source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
continue
|
|
}
|
|
local, found := find_build_local(ctx.locals^[:], statement.name)
|
|
if !found {
|
|
id := source.addf(checker.diagnostics, statement.span, "cannot assign unresolved local '%s'", symbol_text(checker, statement.name))
|
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
|
|
local = hir.INVALID_LOCAL, diagnostic = id,
|
|
})
|
|
ctx.problematic^ = true
|
|
continue
|
|
}
|
|
if !local.mutable {
|
|
id := source.addf(checker.diagnostics, statement.span, "cannot assign immutable local '%s'", symbol_text(checker, statement.name))
|
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
|
|
local = hir.INVALID_LOCAL, diagnostic = id,
|
|
})
|
|
ctx.problematic^ = true
|
|
continue
|
|
}
|
|
value := build_expr(
|
|
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
|
local.type, ctx.pkg, ctx.file,
|
|
)
|
|
value = coerce_expr(checker, value, local.type, statement.span)
|
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind = .Assignment, span = statement.span, expr = value, local = local.id,
|
|
target = hir.INVALID_EXPR, diagnostic = source.INVALID_DIAGNOSTIC,
|
|
})
|
|
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[value].kind == .Invalid
|
|
case .Return:
|
|
if statement.expr == ast.INVALID_EXPR {
|
|
if !types.is_void(ctx.result) {
|
|
id := source.add(checker.diagnostics, statement.span, "'return _' is only valid in a void function")
|
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
|
|
local = hir.INVALID_LOCAL, diagnostic = id,
|
|
})
|
|
ctx.problematic^ = true
|
|
} else {
|
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind = .Return, span = statement.span, expr = hir.INVALID_EXPR,
|
|
local = hir.INVALID_LOCAL, diagnostic = source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
continue
|
|
}
|
|
if types.is_void(ctx.result) {
|
|
id := source.add(checker.diagnostics, statement.span, "void function cannot return a value")
|
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
|
|
local = hir.INVALID_LOCAL, diagnostic = id,
|
|
})
|
|
ctx.problematic^ = true
|
|
continue
|
|
}
|
|
value := build_expr(
|
|
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
|
ctx.result, ctx.pkg, ctx.file,
|
|
)
|
|
value = coerce_expr(checker, value, ctx.result, statement.span)
|
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind = .Return, span = statement.span, expr = value,
|
|
local = hir.INVALID_LOCAL, diagnostic = source.INVALID_DIAGNOSTIC,
|
|
})
|
|
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[value].kind == .Invalid
|
|
case .Expression:
|
|
value := build_expr(checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, types.INVALID, ctx.pkg, ctx.file)
|
|
if !types.is_void(checker.module.exprs[value].type) {
|
|
id := source.add(checker.diagnostics, statement.span, "non-void expression result must be consumed or assigned to '_'")
|
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
|
|
local = hir.INVALID_LOCAL, diagnostic = id,
|
|
})
|
|
ctx.problematic^ = true
|
|
} else {
|
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind = .Expression, span = statement.span, expr = value,
|
|
local = hir.INVALID_LOCAL, diagnostic = source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
case .If:
|
|
if len(statement.captures) > 0 {
|
|
ast_operands: [dynamic]ast.Expr_Id
|
|
ast_operands.allocator = checker.allocator
|
|
flatten_conditional_unwrap_operands(checker.ast_module, statement.expr, &ast_operands)
|
|
|
|
valid_unwrap := true
|
|
diagnostic := source.INVALID_DIAGNOSTIC
|
|
if len(ast_operands) != len(statement.captures) {
|
|
diagnostic = source.addf(
|
|
checker.diagnostics,
|
|
statement.span,
|
|
"'if' unwrap has %d operands but %d captures",
|
|
len(ast_operands),
|
|
len(statement.captures),
|
|
)
|
|
valid_unwrap = false
|
|
}
|
|
|
|
values := make([]hir.Expr_Id, len(ast_operands), checker.allocator)
|
|
child_types := make([]types.Type, len(ast_operands), checker.allocator)
|
|
for operand, index in ast_operands {
|
|
child_types[index] = types.INVALID
|
|
value := build_expr(
|
|
checker, operand, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
|
types.INVALID, ctx.pkg, ctx.file,
|
|
)
|
|
values[index] = value
|
|
value_type := checker.module.exprs[value].type
|
|
if checker.module.exprs[value].kind == .Invalid {
|
|
valid_unwrap = false
|
|
if diagnostic == source.INVALID_DIAGNOSTIC {
|
|
diagnostic = checker.module.exprs[value].diagnostic
|
|
}
|
|
} else if !types.is_optional(value_type, &checker.module.types) {
|
|
diagnostic = source.addf(
|
|
checker.diagnostics,
|
|
checker.ast_module.exprs[operand].span,
|
|
"'if' unwrap requires an optional value (operand %d)",
|
|
index + 1,
|
|
)
|
|
valid_unwrap = false
|
|
} else {
|
|
child_types[index] = types.child_type(value_type, &checker.module.types)
|
|
}
|
|
}
|
|
|
|
capture_start := len(ctx.locals^)
|
|
unwraps: [dynamic]hir.Conditional_Unwrap
|
|
unwraps.allocator = checker.allocator
|
|
for capture, index in statement.captures {
|
|
child := child_types[index] if index < len(child_types) else types.INVALID
|
|
local := hir.INVALID_LOCAL
|
|
if capture != checker.sink_symbol {
|
|
if _, duplicate := find_build_local(ctx.locals^[capture_start:], capture); duplicate {
|
|
diagnostic = source.add(
|
|
checker.diagnostics,
|
|
statement.span,
|
|
"'if' unwrap captures must have distinct names",
|
|
)
|
|
valid_unwrap = false
|
|
}
|
|
local = hir.local_id(len(ctx.hir_locals^))
|
|
append(ctx.hir_locals, hir.Local{name=capture, type=child, mutable=false})
|
|
append(ctx.locals, Build_Local{name=capture, type=child, mutable=false, id=local})
|
|
}
|
|
if index < len(values) {
|
|
append(&unwraps, hir.Conditional_Unwrap{expr=values[index], local=local})
|
|
}
|
|
}
|
|
|
|
guard := hir.INVALID_EXPR
|
|
if statement.guard != ast.INVALID_EXPR {
|
|
guard = build_expr(
|
|
checker, statement.guard, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
|
types.BOOL, ctx.pkg, ctx.file,
|
|
)
|
|
if checker.module.exprs[guard].kind == .Invalid {
|
|
valid_unwrap = false
|
|
if diagnostic == source.INVALID_DIAGNOSTIC {
|
|
diagnostic = checker.module.exprs[guard].diagnostic
|
|
}
|
|
} else if !types.is_bool(checker.module.exprs[guard].type) {
|
|
diagnostic = source.add(
|
|
checker.diagnostics,
|
|
checker.ast_module.exprs[statement.guard].span,
|
|
"'if' unwrap guard must be a bool",
|
|
)
|
|
valid_unwrap = false
|
|
}
|
|
}
|
|
|
|
then_body := build_block(ctx, statement.body, capture_start)
|
|
resize(ctx.locals, capture_start)
|
|
else_body: []hir.Stmt_Id = nil
|
|
if statement.else_body != nil {
|
|
else_body = build_block(ctx, statement.else_body)
|
|
}
|
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
if valid_unwrap {
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind=.If,
|
|
span=statement.span,
|
|
expr=hir.INVALID_EXPR,
|
|
unwraps=unwraps[:],
|
|
guard=guard,
|
|
then_body=then_body,
|
|
else_body=else_body,
|
|
local=hir.INVALID_LOCAL,
|
|
target=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
} else {
|
|
delete(unwraps)
|
|
delete(then_body, checker.allocator)
|
|
delete(else_body, checker.allocator)
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind=.Trap,
|
|
span=statement.span,
|
|
expr=hir.INVALID_EXPR,
|
|
guard=hir.INVALID_EXPR,
|
|
local=hir.INVALID_LOCAL,
|
|
target=hir.INVALID_EXPR,
|
|
diagnostic=diagnostic,
|
|
})
|
|
ctx.problematic^ = true
|
|
}
|
|
delete(values, checker.allocator)
|
|
delete(child_types, checker.allocator)
|
|
delete(ast_operands)
|
|
continue
|
|
}
|
|
condition := build_expr(checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, types.BOOL, ctx.pkg, ctx.file)
|
|
if checker.module.exprs[condition].kind != .Invalid && !types.is_bool(checker.module.exprs[condition].type) {
|
|
id := source.add(checker.diagnostics, statement.span, "'if' condition must be a bool")
|
|
condition = invalid_hir_expr(checker, statement.span, id, types.BOOL)
|
|
ctx.problematic^ = true
|
|
}
|
|
then_body := build_block(ctx, statement.body)
|
|
else_body: []hir.Stmt_Id = nil
|
|
if statement.else_body != nil {
|
|
else_body = build_block(ctx, statement.else_body)
|
|
}
|
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind = .If, span = statement.span, expr = condition,
|
|
guard = hir.INVALID_EXPR,
|
|
then_body = then_body, else_body = else_body,
|
|
local = hir.INVALID_LOCAL, target = hir.INVALID_EXPR,
|
|
diagnostic = source.INVALID_DIAGNOSTIC,
|
|
})
|
|
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[condition].kind == .Invalid
|
|
case .While:
|
|
condition := build_expr(
|
|
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
|
types.BOOL, ctx.pkg, ctx.file,
|
|
)
|
|
if checker.module.exprs[condition].kind != .Invalid &&
|
|
!types.is_bool(checker.module.exprs[condition].type) {
|
|
id := source.add(checker.diagnostics, statement.span, "'while' condition must be a bool")
|
|
condition = invalid_hir_expr(checker, statement.span, id, types.BOOL)
|
|
ctx.problematic^ = true
|
|
}
|
|
loop_body := build_block(ctx, statement.body)
|
|
update := hir.INVALID_STMT
|
|
if statement.update != ast.INVALID_STMT {
|
|
update_ast := [1]ast.Stmt_Id{statement.update}
|
|
update_body := build_block(ctx, update_ast[:])
|
|
if len(update_body) > 0 {
|
|
update = update_body[0]
|
|
}
|
|
delete(update_body, checker.allocator)
|
|
}
|
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind=.While,
|
|
span=statement.span,
|
|
expr=condition,
|
|
then_body=loop_body,
|
|
update=update,
|
|
local=hir.INVALID_LOCAL,
|
|
target=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[condition].kind == .Invalid
|
|
case .For:
|
|
iterable := build_expr(
|
|
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
|
types.INVALID, ctx.pkg, ctx.file,
|
|
)
|
|
iterable_type := checker.module.exprs[iterable].type
|
|
capture_type := types.I64
|
|
iterator_type := types.INVALID
|
|
valid_loop := checker.module.exprs[iterable].kind != .Invalid
|
|
diagnostic := source.INVALID_DIAGNOSTIC
|
|
is_range := types.is_range(iterable_type, &checker.module.types)
|
|
if is_range {
|
|
capture_type = types.child_type(iterable_type, &checker.module.types)
|
|
if statement.pointer_capture {
|
|
diagnostic = source.add(checker.diagnostics, statement.span, "range loops do not support pointer captures")
|
|
valid_loop = false
|
|
}
|
|
if symbol.is_valid(statement.index_name) {
|
|
diagnostic = source.add(checker.diagnostics, statement.span, "range loops do not support index captures")
|
|
valid_loop = false
|
|
}
|
|
} else {
|
|
item, ok := sequence_item(iterable_type, &checker.module.types)
|
|
if !ok {
|
|
diagnostic = source.add(
|
|
checker.diagnostics,
|
|
statement.span,
|
|
"for-loop iterable must be a range, array, slice, or pointer-to-array",
|
|
)
|
|
valid_loop = false
|
|
} else {
|
|
iterator_type = types.pointer(
|
|
&checker.module.types,
|
|
item.child,
|
|
item.mutable,
|
|
true,
|
|
item.has_sentinel,
|
|
item.sentinel,
|
|
)
|
|
capture_type = item.child
|
|
if statement.pointer_capture {
|
|
_, _, array_pointer_ok := types.array_pointer(iterable_type, &checker.module.types)
|
|
if !types.is_slice(iterable_type, &checker.module.types) && !array_pointer_ok {
|
|
diagnostic = source.add(
|
|
checker.diagnostics,
|
|
statement.span,
|
|
"pointer capture over an array requires a pointer-to-array such as '&items'",
|
|
)
|
|
valid_loop = false
|
|
}
|
|
capture_type = types.pointer(&checker.module.types, item.child, item.mutable, false)
|
|
}
|
|
}
|
|
}
|
|
|
|
capture_start := len(ctx.locals^)
|
|
item_local := hir.local_id(len(ctx.hir_locals^))
|
|
append(ctx.hir_locals, hir.Local{name=statement.name, type=capture_type, mutable=false})
|
|
append(ctx.locals, Build_Local{name=statement.name, type=capture_type, mutable=false, id=item_local})
|
|
index_local := hir.INVALID_LOCAL
|
|
if symbol.is_valid(statement.index_name) {
|
|
if statement.index_name == statement.name {
|
|
diagnostic = source.add(checker.diagnostics, statement.span, "for-loop captures must have distinct names")
|
|
valid_loop = false
|
|
} else {
|
|
index_local = hir.local_id(len(ctx.hir_locals^))
|
|
append(ctx.hir_locals, hir.Local{name=statement.index_name, type=types.USIZE, mutable=false})
|
|
append(ctx.locals, Build_Local{name=statement.index_name, type=types.USIZE, mutable=false, id=index_local})
|
|
}
|
|
}
|
|
loop_body := build_block(ctx, statement.body, capture_start)
|
|
resize(ctx.locals, capture_start)
|
|
|
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
if valid_loop {
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind=.For,
|
|
span=statement.span,
|
|
local=item_local,
|
|
index_local=index_local,
|
|
expr=iterable,
|
|
iterator_type=iterator_type,
|
|
pointer_capture=statement.pointer_capture,
|
|
then_body=loop_body,
|
|
update=hir.INVALID_STMT,
|
|
target=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
} else {
|
|
delete(loop_body, checker.allocator)
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind=.Trap,
|
|
span=statement.span,
|
|
local=hir.INVALID_LOCAL,
|
|
index_local=hir.INVALID_LOCAL,
|
|
expr=hir.INVALID_EXPR,
|
|
target=hir.INVALID_EXPR,
|
|
diagnostic=diagnostic,
|
|
})
|
|
ctx.problematic^ = true
|
|
}
|
|
case .Invalid:
|
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
|
|
local = hir.INVALID_LOCAL, diagnostic = statement.diagnostic,
|
|
})
|
|
ctx.problematic^ = true
|
|
}
|
|
}
|
|
resize(ctx.locals, scope_start)
|
|
return body[:]
|
|
}
|
|
|
|
// Reports whether every control-flow path through `stmts` terminates (returns or traps),
|
|
// so the end of the block is unreachable. A `.Return` or `.Trap` terminates outright; an
|
|
// `.If` terminates only when it has an `else` and both arms terminate. A literal
|
|
// `while true` cannot fall through because the language has no `break` statement.
|
|
// Recursion into the branch slices handles nested ifs and `else if` chains.
|
|
all_paths_return :: proc(module: ^hir.Module, stmts: []hir.Stmt_Id) -> bool {
|
|
for id in stmts {
|
|
statement := module.statements[id]
|
|
#partial switch statement.kind {
|
|
case .Return, .Trap:
|
|
return true
|
|
case .If:
|
|
if statement.else_body != nil &&
|
|
all_paths_return(module, statement.then_body) &&
|
|
all_paths_return(module, statement.else_body) {
|
|
return true
|
|
}
|
|
case .While:
|
|
if statement.expr != hir.INVALID_EXPR && int(statement.expr) < len(module.exprs) {
|
|
condition := module.exprs[statement.expr]
|
|
if condition.kind == .Bool && condition.integer != 0 {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
if signature_diagnostic != source.INVALID_DIAGNOSTIC {
|
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
append(
|
|
&checker.module.statements,
|
|
hir.Stmt {
|
|
kind = .Trap,
|
|
span = function.span,
|
|
expr = hir.INVALID_EXPR,
|
|
local = hir.INVALID_LOCAL,
|
|
diagnostic = signature_diagnostic,
|
|
},
|
|
)
|
|
}
|
|
ctx := Build_Ctx{
|
|
checker = checker,
|
|
pkg = function.pkg,
|
|
file = function.file,
|
|
result = spec.result,
|
|
locals = &locals,
|
|
hir_locals = &hir_locals,
|
|
global_reads = &global_reads,
|
|
calls = &calls,
|
|
problematic = &problematic,
|
|
}
|
|
block := build_block(&ctx, function.body)
|
|
returns := all_paths_return(&checker.module, block)
|
|
for block_stmt in block {
|
|
append(&body, block_stmt)
|
|
}
|
|
delete(block, checker.allocator)
|
|
|
|
if !types.is_void(spec.result) && !returns {
|
|
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
|
|
}
|
|
|
|
static_integer_value :: proc(module: ^hir.Module, expr_id: hir.Expr_Id) -> (i64, bool) {
|
|
current := expr_id
|
|
for current != hir.INVALID_EXPR && int(current) < len(module.exprs) {
|
|
expr := module.exprs[current]
|
|
if expr.kind == .Integer {
|
|
return expr.integer, true
|
|
}
|
|
if expr.kind != .Retype {
|
|
break
|
|
}
|
|
current = expr.left
|
|
}
|
|
return 0, 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)
|
|
}
|
|
static_value, is_static := static_integer_value(&checker.module, expr)
|
|
is_static = is_static && diagnostic == source.INVALID_DIAGNOSTIC
|
|
_ = 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
|
|
}
|