14558 lines
535 KiB
Odin
14558 lines
535 KiB
Odin
package checker
|
|
|
|
import "../ast"
|
|
import "../hir"
|
|
import "../source"
|
|
import "../symbol"
|
|
import "../target"
|
|
import "../types"
|
|
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,
|
|
comptime_values: []Comptime_Value,
|
|
result: types.Type,
|
|
hir_id: hir.Function_Id,
|
|
}
|
|
|
|
Infer_Local :: struct {
|
|
name: symbol.Id,
|
|
type: types.Type,
|
|
declared: types.Type,
|
|
statement: ast.Stmt_Id,
|
|
mutable: bool,
|
|
// open_const/open_float mark a local whose initializer is an unannotated numeric
|
|
// constant: like an open-constant global, it can adopt a backward demand from use.
|
|
open_const: bool,
|
|
open_float: bool,
|
|
const_value: i128,
|
|
demanded: bool,
|
|
}
|
|
|
|
Build_Local :: struct {
|
|
name: symbol.Id,
|
|
type: types.Type,
|
|
mutable: bool,
|
|
id: hir.Local_Id,
|
|
}
|
|
|
|
Static_Binding :: struct {
|
|
name: symbol.Id,
|
|
type: types.Type,
|
|
value: Ct_Value_Id,
|
|
}
|
|
|
|
Expand_Expansion :: struct {
|
|
statement: ast.Stmt_Id,
|
|
index: u32,
|
|
}
|
|
|
|
Entry_Point_Kind :: enum u8 {
|
|
Invalid,
|
|
Plain,
|
|
Process,
|
|
}
|
|
|
|
// 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.
|
|
// A labeled value-loop currently being built. A `yield :label x` inside the loop
|
|
// body assigns `x` to the loop's result `slot` (typed `slot_type`) and `break`s.
|
|
// Pushed by `build_value_loop` while its body is built; innermost is last.
|
|
Yield_Target :: struct {
|
|
label: symbol.Id,
|
|
slot: hir.Local_Id,
|
|
slot_type: types.Type,
|
|
// True when the loop also yields `null` (a `{T, null}` set → `?T`); set from a
|
|
// pure-AST scan, used to pick the slot's element type on the first concrete yield.
|
|
result_optional: bool,
|
|
// `len(defers)` when this target's body began; a `yield :label` flushes defers down
|
|
// to here before breaking, so an outer-loop / value-block yield runs inner defers too.
|
|
defer_floor: int,
|
|
}
|
|
|
|
Defer_Entry :: struct {
|
|
body: []hir.Stmt_Id,
|
|
error_only: bool,
|
|
capture: hir.Local_Id,
|
|
}
|
|
|
|
Error_Refinement :: struct {
|
|
local: hir.Local_Id,
|
|
variants: []u32,
|
|
}
|
|
|
|
Build_Ctx :: struct {
|
|
checker: ^Checker,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
result: types.Type,
|
|
local_types: []types.Type,
|
|
locals: ^[dynamic]Build_Local,
|
|
hir_locals: ^[dynamic]hir.Local,
|
|
local_spans: ^[dynamic]source.Span,
|
|
local_used: ^[dynamic]bool,
|
|
local_warnable: ^[dynamic]bool,
|
|
global_reads: ^[dynamic]hir.Global_Id,
|
|
calls: ^[dynamic]hir.Function_Id,
|
|
problematic: ^bool,
|
|
error_refinements: ^[dynamic]Error_Refinement,
|
|
// Stack of labeled value-loops being built (innermost last); see Yield_Target.
|
|
yield_targets: ^[dynamic]Yield_Target,
|
|
// `defer` lowering. Deferred statements are built once at the `defer` site and
|
|
// their hir stmt ids stored here as a flat stack across scopes (one entry per
|
|
// deferred statement); they are replayed (appended) at each scope exit in LIFO
|
|
// order. `loop_defer_starts` records `len(defers)` at each enclosing loop body
|
|
// entry: `break`/`continue` flush down to that mark (and need `len > loop_floor`
|
|
// to be valid). `defer_depth`/`loop_floor` guard control flow inside a deferred
|
|
// statement: `return` is rejected while `defer_depth > 0`, and `break`/`continue`
|
|
// only see loops opened within the defer (those past `loop_floor`).
|
|
defers: ^[dynamic]Defer_Entry,
|
|
loop_defer_starts: ^[dynamic]int,
|
|
// Parallel to `loop_defer_starts`: the label of each enclosing break target (INVALID
|
|
// when unlabeled), so a `break :L` / `continue :L` can target an outer one. A labeled
|
|
// block statement is a break target too; `loop_is_loop` distinguishes loops (which
|
|
// `continue` and unlabeled `break`/`continue` target) from value/labeled blocks.
|
|
loop_labels: ^[dynamic]symbol.Id,
|
|
loop_is_loop: ^[dynamic]bool,
|
|
defer_depth: int,
|
|
loop_floor: int,
|
|
}
|
|
|
|
Function_Index_Entry :: struct {
|
|
scope: ast.Package_Id,
|
|
hidden: bool,
|
|
name: symbol.Id,
|
|
id: ast.Function_Id,
|
|
}
|
|
|
|
Global_Index_Entry :: struct {
|
|
scope: ast.Package_Id,
|
|
hidden: bool,
|
|
name: symbol.Id,
|
|
id: ast.Global_Id,
|
|
}
|
|
|
|
Import_Index_Entry :: struct {
|
|
scope: ast.File_Id,
|
|
name: symbol.Id,
|
|
id: ast.Import_Id,
|
|
}
|
|
|
|
Type_Factory_Entry :: struct {
|
|
template: ast.Function_Id,
|
|
values: []Comptime_Value,
|
|
result: types.Type,
|
|
resolving: bool,
|
|
}
|
|
|
|
Generated_Type_Entry :: struct {
|
|
expr: ast.Expr_Id,
|
|
values: []Comptime_Value,
|
|
result: types.Type,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
defaults: []Ct_Value_Id,
|
|
}
|
|
|
|
Resolved_Field_Default :: struct {
|
|
expr: ast.Expr_Id,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
static_value: Ct_Value_Id,
|
|
span: source.Span,
|
|
}
|
|
|
|
Type_Factory_Origin :: struct {
|
|
result: types.Type,
|
|
template: ast.Function_Id,
|
|
values: []Comptime_Value,
|
|
}
|
|
|
|
Call_Fold :: enum u8 {
|
|
Unknown,
|
|
Runtime,
|
|
Value,
|
|
Compile_Error,
|
|
}
|
|
|
|
Call_Resolution :: struct {
|
|
expr: ast.Expr_Id,
|
|
ctx: []Comptime_Value,
|
|
expand_ctx: []Expand_Expansion,
|
|
mapping: []int,
|
|
comptime_values: []Comptime_Value,
|
|
runtime_types: []types.Type,
|
|
fold: Call_Fold,
|
|
folded_value: Ct_Value_Id,
|
|
diagnostic: source.Diagnostic_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,
|
|
// Backward type-demand state for open-constant globals (milestone 14). global_demands
|
|
// accumulates demands reachable from any use (other globals' initializers and function
|
|
// bodies); global_demands_dirty lets a demand pushed from a function body re-trigger the
|
|
// inference fixpoint.
|
|
global_demands: []types.Type,
|
|
global_open_const: []bool,
|
|
global_open_float: []bool,
|
|
global_const_value: []i128,
|
|
global_demands_dirty: bool,
|
|
// Program-wide inference for direct constraint fields in named native records.
|
|
// The arrays use Store.fields' existing dense indices; resolved concrete types
|
|
// live directly in module.types.fields so layout and lowering need no side table.
|
|
record_field_constraints: []types.Type,
|
|
record_field_defaults: []types.Type,
|
|
record_field_conflicts: []types.Type,
|
|
record_field_conflict_spans: []source.Span,
|
|
record_field_demands_dirty: bool,
|
|
poisoned_packages: []bool,
|
|
external_global_canonical: []ast.Global_Id,
|
|
external_global_diagnostics: []source.Diagnostic_Id,
|
|
constants: []Constant,
|
|
template_diagnostics: []source.Diagnostic_Id,
|
|
constant_stack: [dynamic]Constant_Frame,
|
|
hir_expr_stack: [dynamic]hir.Expr_Id,
|
|
infer_stack: [dynamic]Infer_Frame,
|
|
build_stack: [dynamic]Build_Expr_Frame,
|
|
cycle_stack: [dynamic]Cycle_Frame,
|
|
// Anonymous globals synthesized for `&<array literal>` (Zig's `&.{...}`). Staged
|
|
// here during global/function building and flushed into module.globals AFTER
|
|
// build_globals, so the 1:1 module.globals <-> ast.globals index identity holds.
|
|
anon_globals: [dynamic]hir.Global,
|
|
main_symbol: symbol.Id,
|
|
entry_point: Entry_Point_Kind,
|
|
io_provider_template: ast.Function_Id,
|
|
sink_symbol: symbol.Id,
|
|
type_symbol: symbol.Id,
|
|
current_result: types.Type,
|
|
inferred_test_error: ^types.Type,
|
|
current_build_ctx: ^Build_Ctx,
|
|
current_comptime_values: []Comptime_Value,
|
|
static_state: Ct_State,
|
|
static_bindings: [dynamic]Static_Binding,
|
|
comptime_keys: [dynamic]string,
|
|
comptime_static_values: [dynamic]Ct_Value_Id,
|
|
expand_context: [dynamic]Expand_Expansion,
|
|
type_factories: [dynamic]Type_Factory_Entry,
|
|
generated_types: [dynamic]Generated_Type_Entry,
|
|
type_factory_origins: [dynamic]Type_Factory_Origin,
|
|
call_resolutions: [dynamic]Call_Resolution,
|
|
building_hir: bool,
|
|
target: target.Target,
|
|
allocator: mem.Allocator,
|
|
}
|
|
|
|
symbol_text :: proc(checker: ^Checker, id: symbol.Id) -> string {
|
|
return symbol.resolve(checker.symbols, id)
|
|
}
|
|
|
|
append_tracked_local :: proc(
|
|
locals: ^[dynamic]hir.Local,
|
|
spans: ^[dynamic]source.Span,
|
|
used: ^[dynamic]bool,
|
|
warnable: ^[dynamic]bool,
|
|
local: hir.Local,
|
|
span: source.Span,
|
|
) -> hir.Local_Id {
|
|
id := hir.local_id(len(locals^))
|
|
append(locals, local)
|
|
append(spans, span)
|
|
append(used, false)
|
|
append(warnable, true)
|
|
return id
|
|
}
|
|
|
|
append_build_local :: proc(
|
|
ctx: ^Build_Ctx,
|
|
name: symbol.Id,
|
|
type: types.Type,
|
|
mutable: bool,
|
|
span: source.Span,
|
|
) -> hir.Local_Id {
|
|
id := append_tracked_local(
|
|
ctx.hir_locals,
|
|
ctx.local_spans,
|
|
ctx.local_used,
|
|
ctx.local_warnable,
|
|
hir.Local{name=name, type=type, mutable=mutable},
|
|
span,
|
|
)
|
|
append(ctx.locals, Build_Local{name=name, type=type, mutable=mutable, id=id})
|
|
return id
|
|
}
|
|
|
|
ignore_tracked_locals :: proc(ctx: ^Build_Ctx, start: int) {
|
|
for i := start; i < len(ctx.local_warnable^); i += 1 {
|
|
ctx.local_warnable^[i] = false
|
|
}
|
|
}
|
|
|
|
mark_local_used :: proc(checker: ^Checker, id: hir.Local_Id) {
|
|
ctx := checker.current_build_ctx
|
|
if ctx == nil || id == hir.INVALID_LOCAL {
|
|
return
|
|
}
|
|
index := int(id)
|
|
if index >= 0 && index < len(ctx.local_used^) {
|
|
ctx.local_used^[index] = true
|
|
}
|
|
}
|
|
|
|
build_local_expr :: proc(checker: ^Checker, local: Build_Local, span: source.Span) -> hir.Expr_Id {
|
|
mark_local_used(checker, local.id)
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Local, span=span, type=local.type, target=hir.local_ref(local.id),
|
|
left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
|
|
block_reads_name :: proc(checker: ^Checker, statements: []ast.Stmt_Id, name: symbol.Id) -> bool {
|
|
// Imported C expressions can be deeply nested, so walk the source graph iteratively.
|
|
statement_stack: [dynamic]ast.Stmt_Id
|
|
statement_stack.allocator = checker.allocator
|
|
defer delete(statement_stack)
|
|
expr_stack: [dynamic]ast.Expr_Id
|
|
expr_stack.allocator = checker.allocator
|
|
defer delete(expr_stack)
|
|
append(&statement_stack, ..statements)
|
|
|
|
for len(statement_stack) > 0 || len(expr_stack) > 0 {
|
|
if len(statement_stack) > 0 {
|
|
statement_id := pop(&statement_stack)
|
|
if statement_id == ast.INVALID_STMT || int(statement_id) >= len(checker.ast_module.statements) {
|
|
continue
|
|
}
|
|
statement := checker.ast_module.statements[statement_id]
|
|
#partial switch statement.kind {
|
|
case .Declaration, .Assignment, .Return, .Expression, .Yield:
|
|
append(&expr_stack, statement.expr, statement.target)
|
|
append(&statement_stack, ..statement.body)
|
|
case .If:
|
|
append(&expr_stack, statement.expr, statement.guard)
|
|
append(&statement_stack, ..statement.body)
|
|
append(&statement_stack, ..statement.else_body)
|
|
case .While:
|
|
append(&expr_stack, statement.expr)
|
|
append(&statement_stack, ..statement.body)
|
|
if statement.update != ast.INVALID_STMT {
|
|
append(&statement_stack, statement.update)
|
|
}
|
|
case .For:
|
|
append(&expr_stack, statement.expr)
|
|
append(&statement_stack, ..statement.body)
|
|
case .Block:
|
|
append(&statement_stack, ..statement.body)
|
|
case .Defer:
|
|
if statement.update != ast.INVALID_STMT {
|
|
append(&statement_stack, statement.update)
|
|
}
|
|
case .Match, .Match_Arm:
|
|
append(&expr_stack, statement.expr)
|
|
append(&expr_stack, ..statement.patterns)
|
|
append(&statement_stack, ..statement.body)
|
|
case .Break, .Continue, .Invalid:
|
|
}
|
|
continue
|
|
}
|
|
|
|
expr_id := pop(&expr_stack)
|
|
if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
|
|
continue
|
|
}
|
|
expr := checker.ast_module.exprs[expr_id]
|
|
if expr.kind == .Name || expr.kind == .Call {
|
|
if !symbol.is_valid(expr.qualifier) && expr.name == name || expr.qualifier == name {
|
|
return true
|
|
}
|
|
}
|
|
switch expr.kind {
|
|
case .Call, .Array, .Struct_Literal, .Slice:
|
|
append(&expr_stack, ..expr.args)
|
|
append(&expr_stack, expr.left)
|
|
case .Negate, .Not, .Bit_Not, .Address, .Deref, .Field, .Unwrap, .Try, .Keyed, .Enum_Literal, .Cast:
|
|
append(&expr_stack, expr.left)
|
|
case .Comptime:
|
|
append(&expr_stack, expr.left)
|
|
append(&statement_stack, ..expr.body)
|
|
case .Catch:
|
|
append(&expr_stack, expr.left, expr.right)
|
|
append(&statement_stack, ..expr.body)
|
|
case .Add, .Sub, .Mul, .Div, .Bit_And, .Bit_Or, .Bit_Xor, .Shift_Left, .Shift_Right,
|
|
.Shift_Left_Saturating, .Index, .Orelse, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
|
|
append(&expr_stack, expr.left, expr.right)
|
|
case .Invalid, .Integer, .Float, .String, .Bool, .Null, .Unreachable, .Undefined, .Inference_Hole,
|
|
.Type, .Name, .Function_Literal, .Anonymous_Struct_Type:
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
record_unused_locals :: proc(
|
|
checker: ^Checker,
|
|
locals: []hir.Local,
|
|
spans: []source.Span,
|
|
used: []bool,
|
|
warnable: []bool,
|
|
) {
|
|
for local, index in locals {
|
|
if local.name == checker.sink_symbol ||
|
|
!symbol.is_valid(local.name) ||
|
|
index >= len(warnable) ||
|
|
!warnable[index] ||
|
|
index >= len(used) ||
|
|
used[index] {
|
|
continue
|
|
}
|
|
span := source.Span{}
|
|
if index < len(spans) {
|
|
span = spans[index]
|
|
}
|
|
if local.parameter {
|
|
source.addf_warning(checker.diagnostics, span, "unused parameter '%s'", symbol_text(checker, local.name))
|
|
} else {
|
|
source.addf_warning(checker.diagnostics, span, "unused local '%s'", symbol_text(checker, local.name))
|
|
}
|
|
}
|
|
}
|
|
|
|
write_type_label :: proc(checker: ^Checker, builder: ^strings.Builder, value: types.Type) {
|
|
store := &checker.module.types
|
|
item, ok := types.node(store, value)
|
|
if !ok {
|
|
strings.write_string(builder, types.name(value))
|
|
return
|
|
}
|
|
if item.name != 0 {
|
|
strings.write_string(builder, symbol_text(checker, symbol.Id(item.name)))
|
|
return
|
|
}
|
|
|
|
switch item.kind {
|
|
case .Array:
|
|
strings.write_byte(builder, '[')
|
|
if item.inferred_count {
|
|
strings.write_byte(builder, '_')
|
|
} else {
|
|
fmt.sbprintf(builder, "%d", item.count)
|
|
}
|
|
if item.has_sentinel {
|
|
fmt.sbprintf(builder, ";%d", item.sentinel)
|
|
}
|
|
strings.write_byte(builder, ']')
|
|
if item.mutable {
|
|
strings.write_string(builder, "mut ")
|
|
}
|
|
write_type_label(checker, builder, item.child)
|
|
case .Pointer:
|
|
if item.has_sentinel {
|
|
fmt.sbprintf(builder, "[*;%d]", item.sentinel)
|
|
} else {
|
|
strings.write_byte(builder, '*' if item.many else '@')
|
|
}
|
|
if item.mutable {
|
|
strings.write_string(builder, "mut ")
|
|
}
|
|
write_type_label(checker, builder, item.child)
|
|
case .Slice:
|
|
if item.has_sentinel {
|
|
fmt.sbprintf(builder, "[;%d]", item.sentinel)
|
|
} else {
|
|
strings.write_string(builder, "[]")
|
|
}
|
|
if item.mutable {
|
|
strings.write_string(builder, "mut ")
|
|
}
|
|
write_type_label(checker, builder, item.child)
|
|
case .Range:
|
|
strings.write_string(builder, "range(")
|
|
write_type_label(checker, builder, item.child)
|
|
strings.write_byte(builder, ')')
|
|
case .Optional:
|
|
strings.write_byte(builder, '?')
|
|
write_type_label(checker, builder, item.child)
|
|
case .Function:
|
|
strings.write_string(builder, "c_func(" if item.c_abi else "func(")
|
|
for param, index in types.params_for(store, value) {
|
|
if index > 0 {
|
|
strings.write_string(builder, ", ")
|
|
}
|
|
write_type_label(checker, builder, param.type)
|
|
}
|
|
if item.variadic {
|
|
if item.field_count > 0 {
|
|
strings.write_string(builder, ", ")
|
|
}
|
|
strings.write_string(builder, "...")
|
|
}
|
|
strings.write_string(builder, ") ")
|
|
write_type_label(checker, builder, item.child)
|
|
case .Fallible:
|
|
write_type_label(checker, builder, item.child)
|
|
strings.write_string(builder, " ! ")
|
|
write_type_label(checker, builder, item.extra)
|
|
case .Sum:
|
|
write_type_label(checker, builder, item.child)
|
|
strings.write_string(builder, " | ")
|
|
write_type_label(checker, builder, item.extra)
|
|
case .Type_Call:
|
|
strings.write_string(builder, "<type factory call>")
|
|
case .Struct:
|
|
strings.write_string(builder, "struct")
|
|
case .Union:
|
|
strings.write_string(builder, "union")
|
|
case .Enum:
|
|
strings.write_string(builder, "enum")
|
|
case .Alias, .Distinct, .Named:
|
|
write_type_label(checker, builder, item.child)
|
|
case .Invalid, .Void, .Noreturn, .Anyopaque, .Int_Constraint, .Uint_Constraint, .Float_Constraint, .Range_Constraint, .Scalar:
|
|
strings.write_string(builder, types.name(value))
|
|
}
|
|
}
|
|
|
|
// Render dynamic types using source syntax so diagnostics never expose internal
|
|
// type-store ids such as `<type 230>`.
|
|
type_label :: proc(checker: ^Checker, value: types.Type) -> string {
|
|
builder := strings.builder_make(context.temp_allocator)
|
|
write_type_label(checker, &builder, value)
|
|
return strings.to_string(builder)
|
|
}
|
|
|
|
is_ptrcast_call :: proc(checker: ^Checker, expr: ast.Expr) -> bool {
|
|
return expr.intrinsic &&
|
|
expr.left == ast.INVALID_EXPR &&
|
|
!symbol.is_valid(expr.qualifier) &&
|
|
symbol_text(checker, expr.name) == "ptrcast"
|
|
}
|
|
|
|
is_intrinsic_call :: proc(checker: ^Checker, expr: ast.Expr, name: string) -> bool {
|
|
return expr.intrinsic && expr.kind == .Call && expr.left == ast.INVALID_EXPR &&
|
|
!symbol.is_valid(expr.qualifier) && symbol_text(checker, expr.name) == name
|
|
}
|
|
|
|
static_field_value :: proc(checker: ^Checker, base, name: symbol.Id) -> (Ct_Value, bool) {
|
|
binding, ok := current_static_binding(checker, base)
|
|
if !ok || binding.value == INVALID_CT_VALUE || int(binding.value) >= len(checker.static_state.values) {
|
|
return {}, false
|
|
}
|
|
return persistent_field_value(checker, binding.value, name)
|
|
}
|
|
|
|
persistent_field_value :: proc(checker: ^Checker, root: Ct_Value_Id, name: symbol.Id) -> (Ct_Value, bool) {
|
|
if root == INVALID_CT_VALUE || int(root) >= len(checker.static_state.values) {
|
|
return {}, false
|
|
}
|
|
value := checker.static_state.values[root]
|
|
index, _, found := find_struct_field(checker, value.type, name)
|
|
children := ct_child_slice(&checker.static_state, value)
|
|
if !found || index < 0 || index >= len(children) || children[index] == INVALID_CT_VALUE ||
|
|
int(children[index]) >= len(checker.static_state.values) {
|
|
return {}, false
|
|
}
|
|
return checker.static_state.values[children[index]], true
|
|
}
|
|
|
|
build_static_value :: proc(checker: ^Checker, value: Ct_Value, span: source.Span, expected: types.Type) -> hir.Expr_Id {
|
|
if value.kind == .Void {
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Void, span=span, type=types.VOID,
|
|
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
if value.kind == .Integer {
|
|
if types.is_enum(value.type, &checker.module.types) {
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Integer, span=span, type=value.type, integer=i64(value.integer),
|
|
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
expr := ast.Expr{
|
|
kind=.Integer, span=span, integer=u64(value.integer),
|
|
left=ast.INVALID_EXPR, right=ast.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
}
|
|
return build_constant_expr(checker, expr, Constant{kind=.Value, value=value.integer}, value.type)
|
|
}
|
|
if value.kind == .Float {
|
|
bits := transmute(i64)value.float
|
|
if types.bits(value.type, checker.target) == 32 {
|
|
bits = i64(transmute(u32)f32(value.float))
|
|
}
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Float, span=span, type=value.type, integer=bits,
|
|
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
if value.kind == .Bool {
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Bool, span=span, type=types.BOOL, integer=i64(value.integer),
|
|
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
if value.kind == .String && value.index < u64(len(checker.ast_module.strings)) {
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.String, span=span, type=string_literal_type(checker, value.index), integer=i64(value.index),
|
|
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
if value.kind == .Function {
|
|
value_expected := expected if types.is_valid(expected) else value.type
|
|
return build_function_value(checker, ast.Function_Id(u32(value.index)), span, value_expected)
|
|
}
|
|
if value.kind == .Array || value.kind == .Struct || value.kind == .Range {
|
|
children := ct_child_slice(&checker.static_state, value)
|
|
args := make([]hir.Expr_Id, len(children), checker.allocator)
|
|
for child, index in children {
|
|
if child == INVALID_CT_VALUE || int(child) >= len(checker.static_state.values) {
|
|
delete(args, checker.allocator)
|
|
id := source.add(checker.diagnostics, span, "invalid persistent compile-time aggregate")
|
|
return invalid_hir_expr(checker, span, id, expected)
|
|
}
|
|
args[index] = build_static_value(checker, checker.static_state.values[child], span, types.INVALID)
|
|
}
|
|
kind := hir.Expr_Kind.Array
|
|
if value.kind == .Struct {
|
|
kind = .Struct
|
|
} else if value.kind == .Range {
|
|
kind = .Range
|
|
}
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=kind, span=span, type=value.type, integer=value.active, args=args,
|
|
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
if value.kind == .Null {
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Null, span=span, type=value.type,
|
|
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
if value.kind == .Optional_Some {
|
|
children := ct_child_slice(&checker.static_state, value)
|
|
if len(children) == 1 && children[0] != INVALID_CT_VALUE &&
|
|
int(children[0]) < len(checker.static_state.values) {
|
|
child_type := types.child_type(value.type, &checker.module.types)
|
|
child := build_static_value(checker, checker.static_state.values[children[0]], span, child_type)
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Optional_Some, span=span, type=value.type, left=child,
|
|
target=hir.INVALID_REF, right=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
}
|
|
id := source.add(checker.diagnostics, span, "compile-time-only value cannot be used at runtime")
|
|
return invalid_hir_expr(checker, span, id, expected)
|
|
}
|
|
|
|
push_static_integer_binding :: proc(checker: ^Checker, name: symbol.Id, value_type: types.Type, value: i128) -> int {
|
|
start := len(checker.static_bindings)
|
|
if symbol.is_valid(name) && name != checker.sink_symbol {
|
|
id := ct_add_value(&checker.static_state, Ct_Value{kind=.Integer, type=value_type, integer=value})
|
|
append(&checker.static_bindings, Static_Binding{name=name, type=value_type, value=id})
|
|
}
|
|
return start
|
|
}
|
|
|
|
push_static_void_binding :: proc(checker: ^Checker, name: symbol.Id) -> int {
|
|
start := len(checker.static_bindings)
|
|
if symbol.is_valid(name) && name != checker.sink_symbol {
|
|
id := ct_add_value(&checker.static_state, Ct_Value{kind=.Void, type=types.VOID})
|
|
append(&checker.static_bindings, Static_Binding{name=name, type=types.VOID, value=id})
|
|
}
|
|
return start
|
|
}
|
|
|
|
pop_static_bindings :: proc(checker: ^Checker, start: int) {
|
|
resize(&checker.static_bindings, start)
|
|
}
|
|
|
|
comptime_string_argument :: proc(
|
|
checker: ^Checker,
|
|
id: ast.Expr_Id,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
) -> (string, bool) {
|
|
if id == ast.INVALID_EXPR || int(id) >= len(checker.ast_module.exprs) {
|
|
return "", false
|
|
}
|
|
expr := checker.ast_module.exprs[id]
|
|
if expr.kind == .String && expr.integer < u64(len(checker.ast_module.strings)) {
|
|
return checker.ast_module.strings[expr.integer], true
|
|
}
|
|
if expr.kind == .Name && !symbol.is_valid(expr.qualifier) {
|
|
value, ok := current_comptime_value(checker, expr.name)
|
|
if ok && value.kind == .String {
|
|
return value.text, true
|
|
}
|
|
}
|
|
if expr.kind == .Name && symbol.is_valid(expr.qualifier) &&
|
|
symbol_text(checker, expr.name) == "name" {
|
|
if value, ok := static_field_value(checker, expr.qualifier, expr.name); ok &&
|
|
value.kind == .String && value.index < u64(len(checker.ast_module.strings)) {
|
|
return checker.ast_module.strings[value.index], true
|
|
}
|
|
}
|
|
state := ct_state_make(checker, pkg, file, diagnose=false)
|
|
defer ct_state_destroy(&state)
|
|
value, flow, ok := ct_eval_expr(&state, id, types.INVALID, 0)
|
|
if ok && flow.kind == .Normal {
|
|
return ct_value_bytes(&state, value)
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
comptime_slice_bytes :: proc(
|
|
checker: ^Checker,
|
|
expr: ast.Expr,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
) -> (string, bool) {
|
|
state := ct_state_make(checker, pkg, file, diagnose=false)
|
|
defer ct_state_destroy(&state)
|
|
value, flow, ok := ct_eval_slice_expr(&state, expr, 0)
|
|
if !ok || flow.kind != .Normal {
|
|
return "", false
|
|
}
|
|
return ct_value_bytes(&state, value)
|
|
}
|
|
|
|
canonical_decimal_index :: proc(text: string) -> (u64, bool) {
|
|
if len(text) == 0 || len(text) > 1 && text[0] == '0' {
|
|
return 0, false
|
|
}
|
|
value: u64
|
|
for byte in text {
|
|
if byte < '0' || byte > '9' {
|
|
return 0, false
|
|
}
|
|
digit := u64(byte-'0')
|
|
if value > (0xffff_ffff_ffff_ffff-digit)/10 {
|
|
return 0, false
|
|
}
|
|
value = value*10+digit
|
|
}
|
|
return value, true
|
|
}
|
|
|
|
field_intrinsic_expr :: proc(
|
|
checker: ^Checker,
|
|
expr: ast.Expr,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
) -> (ast.Expr, bool) {
|
|
if !is_intrinsic_call(checker, expr, "field") || len(expr.args) != 2 {
|
|
return {}, false
|
|
}
|
|
name, ok := comptime_string_argument(checker, expr.args[1], pkg, file)
|
|
if !ok {
|
|
return {}, false
|
|
}
|
|
result := ast.Expr{
|
|
kind=.Field, span=expr.span, left=expr.args[0], right=ast.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
}
|
|
if index, numeric := canonical_decimal_index(name); numeric {
|
|
result.integer = index
|
|
result.name = symbol.INVALID
|
|
} else {
|
|
result.name = symbol.intern(checker.symbols, name)
|
|
}
|
|
return result, true
|
|
}
|
|
|
|
std_named_type :: proc(checker: ^Checker, path, name: string) -> types.Type {
|
|
name_id := symbol.intern(checker.symbols, name)
|
|
result := types.INVALID
|
|
for &import_item in checker.ast_module.imports {
|
|
if import_item.valid && import_item.path == path {
|
|
import_item.used = true
|
|
if !types.is_valid(result) {
|
|
result = types.find_named(&checker.module.types, u32(import_item.target), u32(name_id))
|
|
}
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
Type_Builtin :: enum u8 {
|
|
None,
|
|
Size_Of,
|
|
Align_Of,
|
|
Min_Value,
|
|
Max_Value,
|
|
}
|
|
|
|
Division_Builtin :: enum u8 {
|
|
None,
|
|
Trunc,
|
|
Floor,
|
|
Exact,
|
|
Ceil,
|
|
Rem,
|
|
Mod,
|
|
}
|
|
|
|
Memory_Builtin :: enum u8 {
|
|
None,
|
|
Copy,
|
|
Set,
|
|
}
|
|
|
|
memory_builtin_call :: proc(checker: ^Checker, expr: ast.Expr) -> Memory_Builtin {
|
|
if !expr.intrinsic || expr.kind != .Call || expr.left != ast.INVALID_EXPR || symbol.is_valid(expr.qualifier) {
|
|
return .None
|
|
}
|
|
switch symbol_text(checker, expr.name) {
|
|
case "memcopy": return .Copy
|
|
case "memset": return .Set
|
|
}
|
|
return .None
|
|
}
|
|
|
|
memory_region_type :: proc(checker: ^Checker, value: types.Type) -> (child: types.Type, mutable: bool, ok: bool) {
|
|
store := &checker.module.types
|
|
resolved := types.resolve_alias(value, store)
|
|
if item, item_ok := types.node(store, resolved); item_ok && item.kind == .Slice {
|
|
return item.child, item.mutable, true
|
|
}
|
|
pointer, pointer_ok := types.node(store, resolved)
|
|
if pointer_ok && pointer.kind == .Pointer && !pointer.many {
|
|
array, array_ok := types.node(store, types.resolve_alias(pointer.child, store))
|
|
if array_ok && array.kind == .Array {
|
|
return array.child, pointer.mutable && array.mutable, true
|
|
}
|
|
}
|
|
return types.INVALID, false, false
|
|
}
|
|
|
|
infer_memory_builtin :: proc(
|
|
checker: ^Checker,
|
|
expr: ast.Expr,
|
|
kind: Memory_Builtin,
|
|
locals: []Infer_Local,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
demanded: ^[dynamic]Spec_Id,
|
|
local_types: []types.Type,
|
|
) -> types.Type {
|
|
if len(expr.args) != 2 {
|
|
return types.INVALID
|
|
}
|
|
destination := infer_nested_expr(checker, expr.args[0], locals, pkg, file, demanded, local_types)
|
|
child, _, ok := memory_region_type(checker, destination)
|
|
if !ok {
|
|
_ = infer_nested_expr(checker, expr.args[1], locals, pkg, file, demanded, local_types)
|
|
return types.INVALID
|
|
}
|
|
if kind == .Set {
|
|
if !is_undefined_expr(checker, expr.args[1]) {
|
|
_ = infer_nested_expr(checker, expr.args[1], locals, pkg, file, demanded, local_types, child)
|
|
}
|
|
} else {
|
|
_ = infer_nested_expr(checker, expr.args[1], locals, pkg, file, demanded, local_types)
|
|
}
|
|
return types.VOID
|
|
}
|
|
|
|
division_builtin_call :: proc(checker: ^Checker, expr: ast.Expr) -> Division_Builtin {
|
|
if !expr.intrinsic || expr.kind != .Call || expr.left != ast.INVALID_EXPR || symbol.is_valid(expr.qualifier) {
|
|
return .None
|
|
}
|
|
switch symbol_text(checker, expr.name) {
|
|
case "divtrunc": return .Trunc
|
|
case "divfloor": return .Floor
|
|
case "divexact": return .Exact
|
|
case "divceil": return .Ceil
|
|
case "rem": return .Rem
|
|
case "mod": return .Mod
|
|
}
|
|
return .None
|
|
}
|
|
|
|
type_builtin_call :: proc(checker: ^Checker, expr: ast.Expr) -> Type_Builtin {
|
|
if !expr.intrinsic || expr.kind != .Call || expr.left != ast.INVALID_EXPR || symbol.is_valid(expr.qualifier) {
|
|
return .None
|
|
}
|
|
name := symbol_text(checker, expr.name)
|
|
if name == "sizeof" {
|
|
return .Size_Of
|
|
}
|
|
if name == "alignof" {
|
|
return .Align_Of
|
|
}
|
|
if name == "minval" {
|
|
return .Min_Value
|
|
}
|
|
if name == "maxval" {
|
|
return .Max_Value
|
|
}
|
|
return .None
|
|
}
|
|
|
|
valid_ptrcast_child :: proc(checker: ^Checker, value: types.Type) -> bool {
|
|
return types.is_valid(value) &&
|
|
!types.is_void(value) &&
|
|
!types.is_anyopaque(value) &&
|
|
!types.is_function(value, &checker.module.types) &&
|
|
(types.is_runtime_value(value, &checker.module.types) ||
|
|
types.is_opaque_struct(value, &checker.module.types))
|
|
}
|
|
|
|
valid_layout_type :: proc(checker: ^Checker, value: types.Type) -> bool {
|
|
return types.is_runtime_value(value, &checker.module.types)
|
|
}
|
|
|
|
type_builtin_value :: proc(checker: ^Checker, kind: Type_Builtin, value: types.Type) -> i128 {
|
|
#partial switch kind {
|
|
case .Size_Of:
|
|
return i128(types.size(value, &checker.module.types, checker.target))
|
|
case .Align_Of:
|
|
return i128(types.alignment_of(value, &checker.module.types, checker.target))
|
|
case .Min_Value:
|
|
if types.is_unsigned(value, checker.target) {
|
|
return 0
|
|
}
|
|
return -(i128(1) << u32(types.bits(value, checker.target)-1))
|
|
case .Max_Value:
|
|
bit_count := types.bits(value, checker.target)
|
|
sign_bit_count := 1 if types.is_signed(value, checker.target) else 0
|
|
return (i128(1) << u32(bit_count-sign_bit_count))-1
|
|
case:
|
|
return 0
|
|
}
|
|
}
|
|
|
|
build_type_builtin :: proc(
|
|
checker: ^Checker,
|
|
expr: ast.Expr,
|
|
kind: Type_Builtin,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
) -> hir.Expr_Id {
|
|
if len(expr.args) != 1 {
|
|
id := source.addf(checker.diagnostics, expr.span, "%s! expects 1 argument, got %d", symbol_text(checker, expr.name), len(expr.args))
|
|
return invalid_hir_expr(checker, expr.span, id, types.USIZE)
|
|
}
|
|
target, target_ok := resolve_type_argument(checker, expr.args[0], pkg, file)
|
|
if !target_ok {
|
|
label := "layout" if kind == .Size_Of || kind == .Align_Of else "integer bound"
|
|
id := source.addf(checker.diagnostics, checker.ast_module.exprs[expr.args[0]].span, "%s target must be a type", label)
|
|
return invalid_hir_expr(checker, expr.span, id, types.USIZE)
|
|
}
|
|
if (kind == .Size_Of || kind == .Align_Of) && !valid_layout_type(checker, target) {
|
|
id := source.addf(checker.diagnostics, checker.ast_module.exprs[expr.args[0]].span, "layout target must be a sized runtime value type, got %s", type_label(checker, target))
|
|
return invalid_hir_expr(checker, expr.span, id, types.USIZE)
|
|
}
|
|
if (kind == .Min_Value || kind == .Max_Value) && !types.is_concrete_integer(target) {
|
|
id := source.addf(checker.diagnostics, checker.ast_module.exprs[expr.args[0]].span, "integer bound target must be a concrete integer type, got %s", type_label(checker, target))
|
|
return invalid_hir_expr(checker, expr.span, id, types.USIZE)
|
|
}
|
|
result_type := types.USIZE if kind == .Size_Of || kind == .Align_Of else target
|
|
return build_constant_expr(
|
|
checker,
|
|
expr,
|
|
Constant{kind=.Value, value=type_builtin_value(checker, kind, target)},
|
|
result_type,
|
|
)
|
|
}
|
|
|
|
tag_result_type :: proc(checker: ^Checker, value: types.Type) -> (types.Type, bool) {
|
|
if !types.is_tagged_union(value, &checker.module.types) {
|
|
return types.INVALID, false
|
|
}
|
|
return types.union_tag_enum(value, &checker.module.types), true
|
|
}
|
|
|
|
enum_member_name_from_value :: proc(checker: ^Checker, enum_type: types.Type, value: i128) -> (string, bool) {
|
|
if !types.is_enum(enum_type, &checker.module.types) {
|
|
return "", false
|
|
}
|
|
for member in types.enum_members_for(&checker.module.types, enum_type) {
|
|
if member.value == value {
|
|
return symbol_text(checker, symbol.Id(member.name)), true
|
|
}
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
build_tag_intrinsic :: proc(
|
|
checker: ^Checker,
|
|
expr: ast.Expr,
|
|
locals: []Build_Local,
|
|
global_reads: ^[dynamic]hir.Global_Id,
|
|
calls: ^[dynamic]hir.Function_Id,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
) -> hir.Expr_Id {
|
|
if len(expr.args) != 1 {
|
|
id := source.addf(checker.diagnostics, expr.span, "tag! expects 1 argument, got %d", len(expr.args))
|
|
return invalid_hir_expr(checker, expr.span, id)
|
|
}
|
|
state := ct_state_make(checker, pkg, file, values=checker.current_comptime_values, diagnose=false)
|
|
value_id, flow, comptime_ok := ct_eval_expr(&state, expr.args[0], types.INVALID, 0)
|
|
if comptime_ok && flow.kind == .Normal && value_id != INVALID_CT_VALUE && int(value_id) < len(state.values) {
|
|
value := state.values[value_id]
|
|
if tag_type, tagged := tag_result_type(checker, value.type); tagged && value.kind == .Struct &&
|
|
value.active >= 0 {
|
|
fields := types.fields_for(&checker.module.types, value.type)
|
|
if int(value.active) < len(fields) {
|
|
if member, found := find_enum_member(checker, tag_type, symbol.Id(fields[value.active].name)); found {
|
|
ct_state_destroy(&state)
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Integer, span=expr.span, type=tag_type, integer=i64(member.value),
|
|
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ct_state_destroy(&state)
|
|
value := build_nested_expr(checker, expr.args[0], locals, global_reads, calls, types.INVALID, pkg, file)
|
|
if checker.module.exprs[value].kind == .Invalid {
|
|
return value
|
|
}
|
|
tag_type, ok := tag_result_type(checker, checker.module.exprs[value].type)
|
|
if !ok {
|
|
id := source.add(checker.diagnostics, expr.span, "tag! requires a tagged-union value")
|
|
return invalid_hir_expr(checker, expr.span, id)
|
|
}
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Union_Tag, span=expr.span, type=tag_type, left=value,
|
|
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
|
|
build_tagname_intrinsic :: proc(
|
|
checker: ^Checker,
|
|
expr: ast.Expr,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
) -> hir.Expr_Id {
|
|
if len(expr.args) != 1 {
|
|
id := source.addf(checker.diagnostics, expr.span, "tagname! expects 1 argument, got %d", len(expr.args))
|
|
return invalid_hir_expr(checker, expr.span, id)
|
|
}
|
|
state := ct_state_make(checker, pkg, file, values=checker.current_comptime_values, diagnose=false)
|
|
defer ct_state_destroy(&state)
|
|
value_id, flow, ok := ct_eval_expr(&state, expr.args[0], types.INVALID, 0)
|
|
if !ok || flow.kind != .Normal || value_id == INVALID_CT_VALUE || int(value_id) >= len(state.values) {
|
|
id := source.add(checker.diagnostics, expr.span, "tagname! requires a comptime-known enum value")
|
|
return invalid_hir_expr(checker, expr.span, id)
|
|
}
|
|
value := state.values[value_id]
|
|
name, name_ok := enum_member_name_from_value(checker, value.type, value.integer)
|
|
if value.kind != .Integer || !name_ok {
|
|
id := source.add(checker.diagnostics, expr.span, "tagname! requires a comptime-known enum value")
|
|
return invalid_hir_expr(checker, expr.span, id)
|
|
}
|
|
string_id := intern_comptime_string(checker, name)
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.String, span=expr.span, type=string_literal_type(checker, string_id), integer=i64(string_id),
|
|
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
|
|
is_type_metatype_syntax :: proc(checker: ^Checker, value: ast.Type_Syntax) -> bool {
|
|
item, ok := types.node(&checker.module.types, value)
|
|
return ok && item.name == u32(checker.type_symbol) && item.qualifier == 0
|
|
}
|
|
|
|
is_comptime_type_param :: proc(checker: ^Checker, param: ast.Param) -> bool {
|
|
return param.comptime_value && is_type_metatype_syntax(checker, param.type)
|
|
}
|
|
|
|
function_has_comptime_params :: proc(function: ast.Function) -> bool {
|
|
for param in function.params {
|
|
if param.comptime_value {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
runtime_param_count :: proc(function: ast.Function) -> int {
|
|
count := 0
|
|
for param in function.params {
|
|
if !param.comptime_value {
|
|
count += 1
|
|
}
|
|
}
|
|
return count
|
|
}
|
|
|
|
can_fold_zero_runtime_call :: proc(checker: ^Checker, function: ast.Function) -> bool {
|
|
return function.has_body && !function.c_abi && runtime_param_count(function) == 0 &&
|
|
!types.is_void(function.result) &&
|
|
!types.is_constraint(function.result) &&
|
|
!is_type_metatype_syntax(checker, function.result)
|
|
}
|
|
|
|
comptime_param_count :: proc(function: ast.Function) -> int {
|
|
count := 0
|
|
for param in function.params {
|
|
if param.comptime_value {
|
|
count += 1
|
|
}
|
|
}
|
|
return count
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
fits_u64 :: proc(value: i128) -> bool {
|
|
return value >= 0 && value <= i128(0xffff_ffff_ffff_ffff)
|
|
}
|
|
|
|
constraint_integer_literal_type :: proc(constraint: types.Type, value: i128) -> types.Type {
|
|
if constraint == types.UINT {
|
|
return types.smallest_unsigned_for_literal(u64(value)) if fits_u64(value) else types.INVALID
|
|
}
|
|
return types.smallest_signed_for_literal(i64(value)) if fits_i64(value) else types.INVALID
|
|
}
|
|
|
|
constraint_recovery_type :: proc(checker: ^Checker, constraint: types.Type) -> types.Type {
|
|
switch constraint {
|
|
case types.UINT: return types.U64
|
|
case types.FLOAT: return types.F64
|
|
case types.RANGE: return types.range(&checker.module.types, types.I64)
|
|
case: return types.I64
|
|
}
|
|
}
|
|
|
|
type_from_syntax :: proc(
|
|
checker: ^Checker,
|
|
value: ast.Type_Syntax,
|
|
pkg := ast.Package_Id(0),
|
|
file := ast.File_Id(0),
|
|
depth := 0,
|
|
active_state: ^Ct_State = nil,
|
|
) -> types.Type {
|
|
if depth > 64 {
|
|
return types.INVALID
|
|
}
|
|
item, ok := types.node(&checker.module.types, value)
|
|
if !ok {
|
|
return value
|
|
}
|
|
if item.qualifier == 0 && item.name != 0 {
|
|
if actual, ok := current_comptime_type(checker, symbol.Id(item.name)); ok {
|
|
return actual
|
|
}
|
|
}
|
|
store := &checker.module.types
|
|
changed := false
|
|
#partial switch item.kind {
|
|
case .Alias:
|
|
return type_from_syntax(checker, item.child, pkg, file, depth+1, active_state)
|
|
case .Array:
|
|
child := type_from_syntax(checker, item.child, pkg, file, depth+1, active_state)
|
|
changed = changed || child != item.child
|
|
item.child = child
|
|
if item.unresolved_count {
|
|
expr_id := ast.Expr_Id(item.count_expr)
|
|
span := source.Span{}
|
|
if expr_id != ast.INVALID_EXPR && int(expr_id) < len(checker.ast_module.exprs) {
|
|
span = checker.ast_module.exprs[expr_id].span
|
|
}
|
|
constant := eval_integer_constant_in_state(active_state, expr_id) if active_state != nil else
|
|
eval_integer_constant_in_context(checker, expr_id, pkg, file)
|
|
if constant.kind == .Value {
|
|
switch {
|
|
case constant.value < 0:
|
|
source.add(checker.diagnostics, span, "array count must be non-negative")
|
|
return types.INVALID
|
|
case constant.value > i128(0xffff_ffff_ffff_ffff):
|
|
source.add(checker.diagnostics, span, "array count does not fit in u64")
|
|
return types.INVALID
|
|
case:
|
|
item.count = u64(constant.value)
|
|
item.unresolved_count = false
|
|
item.count_expr = 0
|
|
changed = true
|
|
}
|
|
} else {
|
|
if constant.kind == .Integer_Division {
|
|
source.add(checker.diagnostics, span, "integer '/' is not allowed; use divtrunc!, divfloor!, divexact!, or divceil!")
|
|
return types.INVALID
|
|
}
|
|
source.add(checker.diagnostics, span, "array count must be a compile-time integer expression")
|
|
return types.INVALID
|
|
}
|
|
}
|
|
return types.intern(store, item)
|
|
case .Pointer, .Slice, .Optional, .Range, .Fallible:
|
|
child := type_from_syntax(checker, item.child, pkg, file, depth+1, active_state)
|
|
extra := type_from_syntax(checker, item.extra, pkg, file, depth+1, active_state)
|
|
item.child = child
|
|
item.extra = extra
|
|
return types.intern(store, item)
|
|
case .Distinct, .Enum:
|
|
child := type_from_syntax(checker, item.child, pkg, file, depth+1, active_state)
|
|
extra := type_from_syntax(checker, item.extra, pkg, file, depth+1, active_state)
|
|
changed = child != item.child || extra != item.extra
|
|
item.child = child
|
|
item.extra = extra
|
|
case .Function:
|
|
params := types.params_for(store, value)
|
|
resolved_params := make([]types.Type, len(params), checker.allocator)
|
|
defer delete(resolved_params, checker.allocator)
|
|
for param, index in params {
|
|
resolved_params[index] = type_from_syntax(checker, param.type, pkg, file, depth+1, active_state)
|
|
}
|
|
result := type_from_syntax(checker, item.child, pkg, file, depth+1, active_state)
|
|
return types.function(store, resolved_params, result, item.c_abi, item.variadic)
|
|
case .Sum:
|
|
left := type_from_syntax(checker, item.child, pkg, file, depth+1, active_state)
|
|
right := type_from_syntax(checker, item.extra, pkg, file, depth+1, active_state)
|
|
composed, compose_error := types.compose_sum(store, left, right)
|
|
if compose_error == .Unsupported {
|
|
source.add(checker.diagnostics, source.Span{}, "only native unbacked enums and tagged unions can be composed with '|'")
|
|
return types.INVALID
|
|
}
|
|
if compose_error == .Conflict {
|
|
source.add(checker.diagnostics, source.Span{}, "sum composition contains the same variant name with different payload types")
|
|
return types.INVALID
|
|
}
|
|
return composed
|
|
case .Type_Call:
|
|
expr_id := ast.Expr_Id(item.count_expr)
|
|
call_file := file
|
|
if expr_id != ast.INVALID_EXPR && int(expr_id) < len(checker.ast_module.exprs) {
|
|
call_file = ast.File_Id(checker.ast_module.exprs[expr_id].span.file)
|
|
}
|
|
return resolve_type_factory_call(checker, expr_id, pkg, call_file)
|
|
}
|
|
if changed {
|
|
return types.intern(store, item)
|
|
}
|
|
return value
|
|
}
|
|
|
|
function_channel_type :: proc(checker: ^Checker, function: ast.Function) -> types.Type {
|
|
result := type_from_syntax(checker, function.result, function.pkg, function.file)
|
|
if types.is_valid(function.error) {
|
|
return types.fallible(&checker.module.types, result, type_from_syntax(checker, function.error, function.pkg, function.file))
|
|
}
|
|
return result
|
|
}
|
|
|
|
is_runtime_type :: proc(checker: ^Checker, value: types.Type) -> bool {
|
|
return types.is_runtime_value(value, &checker.module.types)
|
|
}
|
|
|
|
type_contains_unresolved_named :: proc(checker: ^Checker, value: types.Type, depth := 0) -> bool {
|
|
if depth > 64 {
|
|
return true
|
|
}
|
|
item, ok := types.node(&checker.module.types, value)
|
|
if !ok {
|
|
return false
|
|
}
|
|
if item.kind == .Named && !item.declared {
|
|
return true
|
|
}
|
|
if type_contains_unresolved_named(checker, item.child, depth+1) ||
|
|
type_contains_unresolved_named(checker, item.extra, depth+1) {
|
|
return true
|
|
}
|
|
if item.kind == .Function {
|
|
for param in types.params_for(&checker.module.types, value) {
|
|
if type_contains_unresolved_named(checker, param.type, depth+1) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
is_comptime_value_type :: proc(checker: ^Checker, value: types.Type, depth := 0) -> bool {
|
|
if depth > 256 || !types.is_valid(value) {
|
|
return false
|
|
}
|
|
if is_type_metatype_syntax(checker, value) {
|
|
return true
|
|
}
|
|
if is_runtime_type(checker, value) {
|
|
return true
|
|
}
|
|
item, ok := types.node(&checker.module.types, value)
|
|
if !ok {
|
|
return false
|
|
}
|
|
if item.kind == .Function {
|
|
return true
|
|
}
|
|
if item.kind == .Array || item.kind == .Optional || item.kind == .Alias || item.kind == .Distinct {
|
|
return is_comptime_value_type(checker, item.child, depth+1)
|
|
}
|
|
if item.kind == .Struct || item.kind == .Union {
|
|
if !item.declared || item.opaque || item.c_layout ||
|
|
(item.kind == .Union && !types.is_tagged_union(value, &checker.module.types)) {
|
|
return false
|
|
}
|
|
for field in types.fields_for(&checker.module.types, value) {
|
|
if !types.is_void(field.type) && !is_comptime_value_type(checker, field.type, depth+1) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
is_undefined_expr :: proc(checker: ^Checker, expr_id: ast.Expr_Id) -> bool {
|
|
if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
|
|
return false
|
|
}
|
|
return checker.ast_module.exprs[expr_id].kind == .Undefined
|
|
}
|
|
|
|
is_float_constant_expr :: proc(checker: ^Checker, expr_id: ast.Expr_Id) -> bool {
|
|
if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
|
|
return false
|
|
}
|
|
expr := checker.ast_module.exprs[expr_id]
|
|
if expr.kind == .Float {
|
|
return true
|
|
}
|
|
return expr.kind == .Negate && is_float_constant_expr(checker, expr.left)
|
|
}
|
|
|
|
is_numeric_arithmetic_expr :: proc(checker: ^Checker, expr_id: ast.Expr_Id) -> bool {
|
|
if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
|
|
return false
|
|
}
|
|
#partial switch checker.ast_module.exprs[expr_id].kind {
|
|
case .Add, .Sub, .Mul, .Div, .Negate:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
is_numeric_constant_expr :: proc(checker: ^Checker, expr_id: ast.Expr_Id) -> bool {
|
|
if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
|
|
return false
|
|
}
|
|
return eval_constant(checker, expr_id).kind == .Value || is_float_constant_expr(checker, expr_id)
|
|
}
|
|
|
|
is_typed_integer_fold_candidate :: proc(checker: ^Checker, expr_id: ast.Expr_Id, depth := 0) -> bool {
|
|
if depth > 64 || expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
|
|
return false
|
|
}
|
|
expr := checker.ast_module.exprs[expr_id]
|
|
#partial switch expr.kind {
|
|
case .Integer:
|
|
return true
|
|
case .Negate, .Bit_Not, .Cast:
|
|
return is_typed_integer_fold_candidate(checker, expr.left, depth+1)
|
|
case .Add, .Sub, .Mul, .Bit_And, .Bit_Or, .Bit_Xor, .Shift_Left, .Shift_Right, .Shift_Left_Saturating:
|
|
return is_typed_integer_fold_candidate(checker, expr.left, depth+1) &&
|
|
is_typed_integer_fold_candidate(checker, expr.right, depth+1)
|
|
}
|
|
return false
|
|
}
|
|
|
|
is_numeric_demand :: proc(value: types.Type, selected := target.DEFAULT) -> bool {
|
|
return types.is_concrete_scalar(value) && !types.is_bool(value) ||
|
|
types.is_float(value, selected)
|
|
}
|
|
|
|
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)))
|
|
}
|
|
|
|
has_inferred_array_count :: proc(checker: ^Checker, value: types.Type) -> bool {
|
|
item, ok := types.node(&checker.module.types, value)
|
|
return ok && item.kind == .Array && item.inferred_count
|
|
}
|
|
|
|
resolve_inferred_array_from_type :: proc(checker: ^Checker, value, inferred: types.Type) -> types.Type {
|
|
item, ok := types.node(&checker.module.types, value)
|
|
actual, actual_ok := types.node(&checker.module.types, inferred)
|
|
if !ok || !actual_ok || item.kind != .Array || actual.kind != .Array || !item.inferred_count {
|
|
return value
|
|
}
|
|
if item.child != actual.child || item.mutable != actual.mutable ||
|
|
item.has_sentinel != actual.has_sentinel ||
|
|
(item.has_sentinel && item.sentinel != actual.sentinel) {
|
|
return value
|
|
}
|
|
return types.with_array_count(&checker.module.types, value, actual.count)
|
|
}
|
|
|
|
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, file := ast.INVALID_FILE) -> 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
|
|
}
|
|
}
|
|
visible := ast.INVALID_FUNCTION
|
|
for low < len(index) && index[low].scope == scope && index[low].name == name {
|
|
entry := index[low]
|
|
if entry.hidden {
|
|
if file != ast.INVALID_FILE {
|
|
return entry.id
|
|
}
|
|
} else {
|
|
visible = entry.id
|
|
}
|
|
low += 1
|
|
}
|
|
return visible
|
|
}
|
|
|
|
find_global_symbol :: proc(index: []Global_Index_Entry, scope: ast.Package_Id, name: symbol.Id, file := ast.INVALID_FILE) -> 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
|
|
}
|
|
}
|
|
visible := ast.INVALID_GLOBAL
|
|
for low < len(index) && index[low].scope == scope && index[low].name == name {
|
|
entry := index[low]
|
|
if entry.hidden {
|
|
if file != ast.INVALID_FILE {
|
|
return entry.id
|
|
}
|
|
} else {
|
|
visible = entry.id
|
|
}
|
|
low += 1
|
|
}
|
|
return visible
|
|
}
|
|
|
|
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) {
|
|
function_count := 0
|
|
for function in checker.ast_module.functions {
|
|
if !function.generated {
|
|
function_count += 1
|
|
}
|
|
}
|
|
for alias in checker.ast_module.aliases {
|
|
if alias.valid && alias.kind == .Function {
|
|
function_count += 1
|
|
}
|
|
}
|
|
checker.function_index = make([]Function_Index_Entry, function_count, checker.allocator)
|
|
function_index := 0
|
|
for function, id in checker.ast_module.functions {
|
|
if function.generated {
|
|
continue
|
|
}
|
|
checker.function_index[function_index] = Function_Index_Entry{scope=function.pkg, hidden=function.package_hidden, name=function.name, id=ast.function_id(id)}
|
|
function_index += 1
|
|
}
|
|
for alias in checker.ast_module.aliases {
|
|
if alias.valid && alias.kind == .Function {
|
|
checker.function_index[function_index] = Function_Index_Entry{scope=alias.pkg, hidden=alias.package_hidden, name=alias.name, id=ast.Function_Id(alias.target)}
|
|
function_index += 1
|
|
}
|
|
}
|
|
slice.sort_by(checker.function_index, function_index_less)
|
|
|
|
global_count := len(checker.ast_module.globals)
|
|
for alias in checker.ast_module.aliases {
|
|
if alias.valid && alias.kind == .Global {
|
|
global_count += 1
|
|
}
|
|
}
|
|
checker.global_index = make([]Global_Index_Entry, global_count, checker.allocator)
|
|
for global, id in checker.ast_module.globals {
|
|
checker.global_index[id] = Global_Index_Entry{scope=global.pkg, hidden=global.package_hidden, name=global.name, id=ast.global_id(id)}
|
|
}
|
|
global_index := len(checker.ast_module.globals)
|
|
for alias in checker.ast_module.aliases {
|
|
if alias.valid && alias.kind == .Global {
|
|
checker.global_index[global_index] = Global_Index_Entry{scope=alias.pkg, hidden=alias.package_hidden, name=alias.name, id=ast.Global_Id(alias.target)}
|
|
global_index += 1
|
|
}
|
|
}
|
|
slice.sort_by(checker.global_index, global_index_less)
|
|
|
|
import_count := 0
|
|
for import_item in checker.ast_module.imports {
|
|
if !import_item.test_only {
|
|
import_count += 1
|
|
}
|
|
}
|
|
checker.import_index = make([]Import_Index_Entry, import_count, checker.allocator)
|
|
import_index := 0
|
|
for import_item, id in checker.ast_module.imports {
|
|
if import_item.test_only {
|
|
continue
|
|
}
|
|
checker.import_index[import_index] = Import_Index_Entry{scope=import_item.file, name=import_item.alias, id=ast.import_id(id)}
|
|
import_index += 1
|
|
}
|
|
slice.sort_by(checker.import_index, import_index_less)
|
|
}
|
|
|
|
find_template :: proc(checker: ^Checker, name: symbol.Id, pkg := ast.Package_Id(0), file := ast.INVALID_FILE) -> ast.Function_Id {
|
|
return find_function_symbol(checker.function_index, pkg, name, file)
|
|
}
|
|
|
|
configure_entry_point :: proc(checker: ^Checker) {
|
|
main_template := find_template(checker, checker.main_symbol, 0)
|
|
if main_template == ast.INVALID_FUNCTION {
|
|
return
|
|
}
|
|
main := checker.ast_module.functions[main_template]
|
|
if len(main.params) == 0 {
|
|
checker.entry_point = .Plain
|
|
return
|
|
}
|
|
if len(main.params) != 1 || main.params[0].comptime_value {
|
|
return
|
|
}
|
|
|
|
parameter_type := types.resolve_alias(
|
|
type_from_syntax(checker, main.params[0].type, main.pkg, main.file),
|
|
&checker.module.types,
|
|
)
|
|
init_name := symbol.intern(checker.symbols, "Init")
|
|
process_package := ast.INVALID_PACKAGE
|
|
for import_item in checker.ast_module.imports {
|
|
if import_item.valid && import_item.path == "@std/process" {
|
|
candidate := types.find_named(&checker.module.types, u32(import_item.target), u32(init_name))
|
|
if types.equal(parameter_type, candidate) {
|
|
process_package = import_item.target
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if process_package == ast.INVALID_PACKAGE {
|
|
return
|
|
}
|
|
|
|
io_name := symbol.intern(checker.symbols, "Io")
|
|
io_package := ast.INVALID_PACKAGE
|
|
io_type := types.INVALID
|
|
for import_item in checker.ast_module.imports {
|
|
if import_item.valid && import_item.path == "@std/io" {
|
|
candidate := types.find_named(&checker.module.types, u32(import_item.target), u32(io_name))
|
|
io_package = import_item.target
|
|
io_type = candidate
|
|
break
|
|
}
|
|
}
|
|
if io_package == ast.INVALID_PACKAGE {
|
|
return
|
|
}
|
|
init_item, init_ok := types.node(&checker.module.types, parameter_type)
|
|
init_fields := types.fields_for(&checker.module.types, parameter_type)
|
|
io_field_name := symbol.intern(checker.symbols, "io")
|
|
if !init_ok || init_item.kind != .Struct || init_item.tuple || init_item.c_layout ||
|
|
len(init_fields) != 1 || init_fields[0].name != u32(io_field_name) ||
|
|
!types.equal(types.resolve_alias(init_fields[0].type, &checker.module.types), io_type) {
|
|
checker.template_diagnostics[main_template] = source.add(
|
|
checker.diagnostics,
|
|
main.span,
|
|
"@std/process Init must be an auto-layout record containing exactly 'io io.Io'",
|
|
)
|
|
return
|
|
}
|
|
|
|
provider_name := symbol.intern(checker.symbols, "system")
|
|
provider := ast.INVALID_FUNCTION
|
|
provider_count := 0
|
|
for function, function_id in checker.ast_module.functions {
|
|
if function.pkg != io_package || function.name != provider_name {
|
|
continue
|
|
}
|
|
provider_count += 1
|
|
result := types.resolve_alias(
|
|
type_from_syntax(checker, function.result, function.pkg, function.file),
|
|
&checker.module.types,
|
|
)
|
|
if function.package_hidden && function.has_body && !function.c_abi && len(function.params) == 0 &&
|
|
!types.is_valid(function.error) && types.equal(result, io_type) {
|
|
provider = ast.function_id(function_id)
|
|
}
|
|
}
|
|
if provider_count != 1 || provider == ast.INVALID_FUNCTION {
|
|
checker.template_diagnostics[main_template] = source.add(
|
|
checker.diagnostics,
|
|
main.span,
|
|
"@std/io does not provide the required 'hide system func() Io' startup implementation",
|
|
)
|
|
return
|
|
}
|
|
|
|
checker.entry_point = .Process
|
|
checker.io_provider_template = provider
|
|
}
|
|
|
|
find_global :: proc(checker: ^Checker, name: symbol.Id, pkg := ast.Package_Id(0), file := ast.INVALID_FILE) -> ast.Global_Id {
|
|
return find_global_symbol(checker.global_index, pkg, name, file)
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
declared_type_named :: proc(checker: ^Checker, pkg: ast.Package_Id, name: symbol.Id, file := ast.INVALID_FILE) -> bool {
|
|
id := types.find_named(&checker.module.types, u32(pkg), u32(name), file=u32(file))
|
|
item, ok := types.node(&checker.module.types, id)
|
|
return ok && item.declared
|
|
}
|
|
|
|
|
|
type_declaration_conflicts :: proc(checker: ^Checker, pkg: ast.Package_Id, name: symbol.Id) -> bool {
|
|
for item in checker.module.types.nodes {
|
|
if item.declared && item.pkg == u32(pkg) && item.name == u32(name) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
visible_name_kind :: proc(
|
|
checker: ^Checker,
|
|
name: symbol.Id,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
locals: []Build_Local = nil,
|
|
labels: []symbol.Id = nil,
|
|
yield_targets: []Yield_Target = nil,
|
|
) -> string {
|
|
if !symbol.is_valid(name) || name == checker.sink_symbol {
|
|
return ""
|
|
}
|
|
if _, ok := current_comptime_value(checker, name); ok {
|
|
return "comptime parameter"
|
|
}
|
|
if _, ok := find_build_local(locals, name); ok {
|
|
return "local"
|
|
}
|
|
for label in labels {
|
|
if label == name {
|
|
return "label"
|
|
}
|
|
}
|
|
for target in yield_targets {
|
|
if target.label == name {
|
|
return "label"
|
|
}
|
|
}
|
|
if find_import(checker, file, name) != ast.INVALID_IMPORT {
|
|
return "import"
|
|
}
|
|
if find_global(checker, name, pkg, file) != ast.INVALID_GLOBAL {
|
|
return "global"
|
|
}
|
|
if find_template(checker, name, pkg, file) != ast.INVALID_FUNCTION {
|
|
return "function"
|
|
}
|
|
if declared_type_named(checker, pkg, name, file) {
|
|
return "type"
|
|
}
|
|
return ""
|
|
}
|
|
|
|
add_shadow_diagnostic :: proc(
|
|
checker: ^Checker,
|
|
span: source.Span,
|
|
name: symbol.Id,
|
|
decl_kind: string,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
locals: []Build_Local = nil,
|
|
labels: []symbol.Id = nil,
|
|
yield_targets: []Yield_Target = nil,
|
|
) -> source.Diagnostic_Id {
|
|
kind := visible_name_kind(checker, name, pkg, file, locals, labels, yield_targets)
|
|
if len(kind) == 0 {
|
|
return source.INVALID_DIAGNOSTIC
|
|
}
|
|
return source.addf(
|
|
checker.diagnostics,
|
|
span,
|
|
"%s '%s' shadows visible %s",
|
|
decl_kind,
|
|
symbol_text(checker, name),
|
|
kind,
|
|
)
|
|
}
|
|
|
|
add_label_shadow_diagnostic :: proc(ctx: ^Build_Ctx, span: source.Span, label: symbol.Id) -> source.Diagnostic_Id {
|
|
if !symbol.is_valid(label) {
|
|
return source.INVALID_DIAGNOSTIC
|
|
}
|
|
yield_targets := ctx.yield_targets^[:]
|
|
if len(yield_targets) > 0 && yield_targets[len(yield_targets) - 1].label == label {
|
|
label_is_active_loop := false
|
|
for loop_label in ctx.loop_labels^[:] {
|
|
if loop_label == label {
|
|
label_is_active_loop = true
|
|
break
|
|
}
|
|
}
|
|
if !label_is_active_loop {
|
|
yield_targets = yield_targets[:len(yield_targets) - 1]
|
|
}
|
|
}
|
|
return add_shadow_diagnostic(
|
|
ctx.checker, span, label, "label",
|
|
ctx.pkg, ctx.file, ctx.locals^[:], ctx.loop_labels^[:], yield_targets,
|
|
)
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
expr_lookup_file :: proc(expr: ast.Expr, file: ast.File_Id) -> ast.File_Id {
|
|
return ast.INVALID_FILE if symbol.is_valid(expr.qualifier) else file
|
|
}
|
|
|
|
expr_symbol_span :: proc(checker: ^Checker, expr: ast.Expr, name: symbol.Id) -> source.Span {
|
|
span := expr.span
|
|
span.end = span.start+source.Offset(len(symbol_text(checker, name)))
|
|
return span
|
|
}
|
|
|
|
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 {
|
|
span := expr_symbol_span(checker, expr, expr.qualifier)
|
|
id := source.addf(checker.diagnostics, span, "unknown symbol '%s'", symbol_text(checker, expr.qualifier))
|
|
source.set_primary_label(checker.diagnostics, id, "unknown symbol")
|
|
return id
|
|
}
|
|
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, file: ast.File_Id) -> source.Diagnostic_Id {
|
|
if find_template(checker, expr.name, target_pkg, expr_lookup_file(expr, file)) != ast.INVALID_FUNCTION {
|
|
template := find_template(checker, expr.name, target_pkg, expr_lookup_file(expr, file))
|
|
id := source.addf(checker.diagnostics, expr_symbol_span(checker, expr, expr.name), "'%s' is a function, not a global value", symbol_text(checker, expr.name))
|
|
if !checker.ast_module.functions[template].imported {
|
|
source.add_secondary_label(checker.diagnostics, id, checker.ast_module.functions[template].span, "function declared here")
|
|
}
|
|
return id
|
|
}
|
|
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),
|
|
)
|
|
}
|
|
span := expr_symbol_span(checker, expr, expr.name)
|
|
id := source.addf(checker.diagnostics, span, "unknown symbol '%s'", symbol_text(checker, expr.name))
|
|
source.set_primary_label(checker.diagnostics, id, "unknown symbol")
|
|
return id
|
|
}
|
|
|
|
add_call_resolution_diagnostic :: proc(checker: ^Checker, expr: ast.Expr, target_pkg: ast.Package_Id, file: ast.File_Id) -> source.Diagnostic_Id {
|
|
if find_global(checker, expr.name, target_pkg, expr_lookup_file(expr, file)) != ast.INVALID_GLOBAL {
|
|
global := find_global(checker, expr.name, target_pkg, expr_lookup_file(expr, file))
|
|
id := source.addf(checker.diagnostics, expr_symbol_span(checker, expr, expr.name), "'%s' is a global, not a function", symbol_text(checker, expr.name))
|
|
if !checker.ast_module.globals[global].external {
|
|
source.add_secondary_label(checker.diagnostics, id, checker.ast_module.globals[global].span, "global declared here")
|
|
}
|
|
return id
|
|
}
|
|
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),
|
|
)
|
|
}
|
|
span := expr_symbol_span(checker, expr, expr.name)
|
|
id := source.addf(checker.diagnostics, span, "unknown symbol '%s'", symbol_text(checker, expr.name))
|
|
source.set_primary_label(checker.diagnostics, id, "unknown symbol")
|
|
return id
|
|
}
|
|
|
|
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)
|
|
}
|
|
if types.is_valid(item.extra) {
|
|
return add_unsupported_type_diagnostic(checker, span, item.extra, depth+1)
|
|
}
|
|
return source.INVALID_DIAGNOSTIC
|
|
}
|
|
|
|
function_signatures_equal :: proc(left, right: ast.Function) -> bool {
|
|
if left.result != right.result || left.error != right.error ||
|
|
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 ||
|
|
param.comptime_value != right.params[index].comptime_value {
|
|
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_Argument_Mode :: enum u8 {
|
|
Invalid,
|
|
Explicit,
|
|
Inferred,
|
|
}
|
|
|
|
call_param_index :: proc(mapping: []int, source_index: int) -> int {
|
|
if source_index < 0 || source_index >= len(mapping) {
|
|
return -1
|
|
}
|
|
return mapping[source_index]
|
|
}
|
|
|
|
next_runtime_call_arg :: proc(function: ast.Function, mapping: []int, start, source_count: int) -> int {
|
|
index := start
|
|
for index < source_count {
|
|
param_index := call_param_index(mapping, index)
|
|
if param_index >= len(function.params) || !function.params[param_index].comptime_value {
|
|
break
|
|
}
|
|
index += 1
|
|
}
|
|
return index
|
|
}
|
|
|
|
call_mapping_mode :: proc(function: ast.Function, mapping: []int) -> Call_Argument_Mode {
|
|
if len(mapping) != len(function.params) {
|
|
return .Inferred
|
|
}
|
|
for value, index in mapping {
|
|
if value != index {
|
|
return .Inferred
|
|
}
|
|
}
|
|
return .Explicit
|
|
}
|
|
|
|
comptime_binding_index :: proc(function: ast.Function, prefix: int, name: symbol.Id) -> (int, bool) {
|
|
ordinal := 0
|
|
for param in function.params {
|
|
if !param.comptime_value {
|
|
continue
|
|
}
|
|
if ordinal >= prefix {
|
|
return -1, false
|
|
}
|
|
if param.name == name {
|
|
return ordinal, true
|
|
}
|
|
ordinal += 1
|
|
}
|
|
return -1, false
|
|
}
|
|
|
|
comptime_param_for_name :: proc(function: ast.Function, name: symbol.Id) -> (ast.Param, bool) {
|
|
for param in function.params {
|
|
if param.comptime_value && param.name == name {
|
|
return param, true
|
|
}
|
|
}
|
|
return {}, false
|
|
}
|
|
|
|
Call_Mapping_Search :: struct {
|
|
checker: ^Checker,
|
|
function: ^ast.Function,
|
|
args: []ast.Expr_Id,
|
|
current: []int,
|
|
candidates: ^[dynamic][]int,
|
|
}
|
|
|
|
is_comptime_string_param :: proc(checker: ^Checker, param: ast.Param, function: ast.Function) -> bool {
|
|
declared := types.resolve_alias(type_from_syntax(checker, param.type, function.pkg, function.file), &checker.module.types)
|
|
item, ok := types.container(declared, &checker.module.types)
|
|
return ok && item.kind == .Slice && !item.mutable && item.child == types.U8
|
|
}
|
|
|
|
explicit_comptime_argument_valid :: proc(
|
|
checker: ^Checker,
|
|
function: ast.Function,
|
|
param: ast.Param,
|
|
arg: ast.Expr_Id,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
) -> bool {
|
|
if arg == ast.INVALID_EXPR || int(arg) >= len(checker.ast_module.exprs) {
|
|
return false
|
|
}
|
|
expr := checker.ast_module.exprs[arg]
|
|
if expr.kind == .Inference_Hole {
|
|
return true
|
|
}
|
|
if is_comptime_type_param(checker, param) {
|
|
// Avoid asking the ordinary type resolver to diagnose while candidates are
|
|
// being probed. A call can be a type argument only when its declaration is
|
|
// a type factory.
|
|
if expr.kind == .Call {
|
|
target_pkg, available := expr_package(checker, expr, pkg, file, false)
|
|
if !available {
|
|
return false
|
|
}
|
|
template := find_template(checker, expr.name, target_pkg, expr_lookup_file(expr, file))
|
|
if template == ast.INVALID_FUNCTION ||
|
|
!is_type_metatype_syntax(checker, checker.ast_module.functions[template].result) {
|
|
return false
|
|
}
|
|
}
|
|
_, ok := resolve_type_argument(checker, arg, pkg, file)
|
|
return ok
|
|
}
|
|
if type_pattern_mentions_comptime(checker, function, comptime_param_count(function), param.type) {
|
|
return true
|
|
}
|
|
if is_comptime_string_param(checker, param, function) {
|
|
_, ok := comptime_string_argument(checker, arg, pkg, file)
|
|
return ok
|
|
}
|
|
if types.is_concrete_integer(type_from_syntax(checker, param.type, function.pkg, function.file)) {
|
|
return eval_integer_constant_in_context(checker, arg, pkg, file).kind == .Value
|
|
}
|
|
declared := type_from_syntax(checker, param.type, function.pkg, function.file)
|
|
_, ok := eval_static_comptime_value(checker, param.name, arg, declared, pkg, file)
|
|
return ok
|
|
}
|
|
|
|
search_call_mappings :: proc(search: ^Call_Mapping_Search, param_index, source_index: int) {
|
|
if len(search.candidates^) >= COMPTIME_EVAL_QUOTA {
|
|
return
|
|
}
|
|
function := search.function^
|
|
if param_index == len(function.params) {
|
|
if source_index != len(search.args) {
|
|
return
|
|
}
|
|
candidate := make([]int, len(search.current), search.checker.allocator)
|
|
copy(candidate, search.current)
|
|
append(search.candidates, candidate)
|
|
return
|
|
}
|
|
param := function.params[param_index]
|
|
if param.comptime_value {
|
|
if source_index < len(search.args) {
|
|
search.current[source_index] = param_index
|
|
search_call_mappings(search, param_index+1, source_index+1)
|
|
search.current[source_index] = -1
|
|
}
|
|
search_call_mappings(search, param_index+1, source_index)
|
|
return
|
|
}
|
|
if source_index >= len(search.args) {
|
|
return
|
|
}
|
|
search.current[source_index] = param_index
|
|
search_call_mappings(search, param_index+1, source_index+1)
|
|
search.current[source_index] = -1
|
|
}
|
|
|
|
call_mapping_semantically_valid :: proc(
|
|
checker: ^Checker,
|
|
function: ast.Function,
|
|
mapping: []int,
|
|
args: []ast.Expr_Id,
|
|
expected: types.Type,
|
|
locals: []Infer_Local,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
) -> (bool, string) {
|
|
actual_args := make([]types.Type, len(function.params), checker.allocator)
|
|
defer delete(actual_args, checker.allocator)
|
|
for source_index in 0..<len(args) {
|
|
param_index := call_param_index(mapping, source_index)
|
|
if param_index < 0 || param_index >= len(function.params) {
|
|
continue
|
|
}
|
|
if function.params[param_index].comptime_value {
|
|
if !explicit_comptime_argument_valid(
|
|
checker, function, function.params[param_index], args[source_index], pkg, file,
|
|
) {
|
|
return false, fmt.aprintf(
|
|
"argument %d is not a valid comptime value for parameter '%s'",
|
|
source_index+1, symbol_text(checker, function.params[param_index].name),
|
|
allocator=checker.allocator,
|
|
)
|
|
}
|
|
continue
|
|
}
|
|
actual_args[param_index] = infer_expr(checker, args[source_index], locals, pkg, file)
|
|
}
|
|
inference_failure := ""
|
|
values, ok := infer_call_comptime_values(
|
|
checker, function, comptime_param_count(function), mapping, args, actual_args,
|
|
expected, pkg, file, diagnose=false, failure=&inference_failure,
|
|
)
|
|
defer delete(values, checker.allocator)
|
|
if !ok {
|
|
if len(inference_failure) > 0 {
|
|
return false, inference_failure
|
|
}
|
|
return false, fmt.aprintf("could not infer or evaluate every comptime parameter", allocator=checker.allocator)
|
|
}
|
|
delete(inference_failure, checker.allocator)
|
|
previous := checker.current_comptime_values
|
|
checker.current_comptime_values = values
|
|
defer checker.current_comptime_values = previous
|
|
for param, index in function.params {
|
|
if param.comptime_value {
|
|
continue
|
|
}
|
|
actual := actual_args[index]
|
|
declared := type_from_syntax(checker, param.type, function.pkg, function.file)
|
|
source_index := -1
|
|
for mapped_param, candidate_source in mapping {
|
|
if mapped_param == index {
|
|
source_index = candidate_source
|
|
break
|
|
}
|
|
}
|
|
if !is_runtime_type(checker, actual) && !can_implicitly_convert_type(checker, actual, declared) {
|
|
return false, fmt.aprintf(
|
|
"argument %d is not a runtime value", source_index+1,
|
|
allocator=checker.allocator,
|
|
)
|
|
}
|
|
if types.is_constraint(declared) {
|
|
if !types.is_valid(types.constraint_target(declared, actual, &checker.module.types)) {
|
|
return false, fmt.aprintf(
|
|
"argument %d of type %s does not satisfy %s",
|
|
source_index+1, type_label(checker, actual), type_label(checker, declared),
|
|
allocator=checker.allocator,
|
|
)
|
|
}
|
|
} else if !can_implicitly_convert_type(checker, actual, declared) {
|
|
if source_index < 0 ||
|
|
!is_numeric_constant_expr(checker, args[source_index]) ||
|
|
!expr_accepts_numeric_demand(checker, args[source_index], declared, locals, pkg, file) {
|
|
return false, fmt.aprintf(
|
|
"argument %d of type %s cannot convert to %s",
|
|
source_index+1, type_label(checker, actual), type_label(checker, declared),
|
|
allocator=checker.allocator,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
if is_runtime_type(checker, expected) {
|
|
result := function_channel_type(checker, function)
|
|
if !types.is_noreturn(result) && !is_runtime_type(checker, result) ||
|
|
!can_implicitly_convert_type(checker, result, expected) {
|
|
return false, fmt.aprintf(
|
|
"result type %s cannot convert to expected type %s",
|
|
type_label(checker, result), type_label(checker, expected),
|
|
allocator=checker.allocator,
|
|
)
|
|
}
|
|
}
|
|
return true, ""
|
|
}
|
|
|
|
call_argument_mapping :: proc(
|
|
checker: ^Checker,
|
|
function: ^ast.Function,
|
|
args: []ast.Expr_Id,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
expected := types.INVALID,
|
|
locals: []Infer_Local = nil,
|
|
) -> (mapping: []int, mode: Call_Argument_Mode, comptime_count: int, failure: string) {
|
|
if function.variadic || function.c_abi {
|
|
if !valid_call_arity(function^, len(args)) {
|
|
return nil, .Invalid, 0, ""
|
|
}
|
|
mapping = make([]int, len(args), checker.allocator)
|
|
for &value, index in mapping {
|
|
value = index
|
|
}
|
|
return mapping, .Explicit, 0, ""
|
|
}
|
|
current := make([]int, len(args), checker.allocator)
|
|
defer delete(current, checker.allocator)
|
|
for &value in current {
|
|
value = -1
|
|
}
|
|
candidates: [dynamic][]int
|
|
candidates.allocator = checker.allocator
|
|
defer {
|
|
for candidate in candidates {
|
|
delete(candidate, checker.allocator)
|
|
}
|
|
delete(candidates)
|
|
}
|
|
search := Call_Mapping_Search{
|
|
checker=checker,
|
|
function=function,
|
|
args=args,
|
|
current=current,
|
|
candidates=&candidates,
|
|
}
|
|
search_call_mappings(&search, 0, 0)
|
|
selected_index := -1
|
|
failures: [dynamic]string
|
|
failures.allocator = checker.allocator
|
|
defer {
|
|
for item in failures {
|
|
delete(item, checker.allocator)
|
|
}
|
|
delete(failures)
|
|
}
|
|
if len(candidates) == 1 {
|
|
selected_index = 0
|
|
}
|
|
if selected_index < 0 && len(candidates) > 1 {
|
|
for candidate, index in candidates {
|
|
valid, reason := call_mapping_semantically_valid(
|
|
checker, function^, candidate, args, expected, locals, pkg, file,
|
|
)
|
|
if valid {
|
|
if selected_index >= 0 {
|
|
return nil, .Invalid, comptime_param_count(function^), fmt.aprintf(
|
|
"multiple complete argument mappings satisfy the call",
|
|
allocator=checker.allocator,
|
|
)
|
|
}
|
|
selected_index = index
|
|
} else {
|
|
append(&failures, reason)
|
|
}
|
|
}
|
|
}
|
|
if selected_index < 0 {
|
|
// Prefer the mapping that treats an array literal as a dependent aggregate.
|
|
// It can then report the contextual element error instead of an ambiguous
|
|
// hidden-comptime-parameter mapping failure.
|
|
dependent_array_candidate := -1
|
|
ambiguous := false
|
|
for candidate, index in candidates {
|
|
matches := false
|
|
for arg_id, source_index in args {
|
|
param_index := call_param_index(candidate, source_index)
|
|
if param_index < 0 || param_index >= len(function.params) ||
|
|
!function.params[param_index].comptime_value ||
|
|
arg_id == ast.INVALID_EXPR || int(arg_id) >= len(checker.ast_module.exprs) ||
|
|
checker.ast_module.exprs[arg_id].kind != .Array {
|
|
continue
|
|
}
|
|
matches = matches || type_pattern_mentions_comptime(
|
|
checker, function^, comptime_param_count(function^), function.params[param_index].type,
|
|
)
|
|
}
|
|
if matches {
|
|
if dependent_array_candidate >= 0 {
|
|
ambiguous = true
|
|
} else {
|
|
dependent_array_candidate = index
|
|
}
|
|
}
|
|
}
|
|
if dependent_array_candidate >= 0 && !ambiguous {
|
|
selected_index = dependent_array_candidate
|
|
}
|
|
}
|
|
if selected_index < 0 {
|
|
if len(failures) > 0 {
|
|
builder := strings.builder_make(checker.allocator)
|
|
defer strings.builder_destroy(&builder)
|
|
for reason, index in failures {
|
|
if index > 0 {
|
|
strings.write_string(&builder, "; ")
|
|
}
|
|
fmt.sbprintf(&builder, "candidate %d: %s", index+1, reason)
|
|
}
|
|
return nil, .Invalid, comptime_param_count(function^), fmt.aprintf(
|
|
"%s", strings.to_string(builder), allocator=checker.allocator,
|
|
)
|
|
}
|
|
return nil, .Invalid, comptime_param_count(function^), ""
|
|
}
|
|
selected := make([]int, len(args), checker.allocator)
|
|
copy(selected, candidates[selected_index])
|
|
identity := len(args) == len(function.params)
|
|
if identity {
|
|
for value, index in selected {
|
|
if value != index {
|
|
identity = false
|
|
break
|
|
}
|
|
}
|
|
}
|
|
return selected, .Explicit if identity else .Inferred, comptime_param_count(function^), ""
|
|
}
|
|
|
|
inferred_comptime_value_equal :: proc(checker: ^Checker, left, right: Comptime_Value) -> bool {
|
|
if left.kind != right.kind {
|
|
return false
|
|
}
|
|
if left.kind == .Type {
|
|
return types.equal(
|
|
types.resolve_alias(left.type, &checker.module.types),
|
|
types.resolve_alias(right.type, &checker.module.types),
|
|
)
|
|
}
|
|
if !types.equal(left.type, right.type) {
|
|
return false
|
|
}
|
|
if left.kind == .Static {
|
|
return left.fingerprint == right.fingerprint && left.key == right.key
|
|
}
|
|
if left.kind == .String {
|
|
return left.text == right.text
|
|
}
|
|
return left.value == right.value
|
|
}
|
|
|
|
bind_inferred_comptime :: proc(
|
|
checker: ^Checker,
|
|
function: ast.Function,
|
|
prefix: int,
|
|
values: []Comptime_Value,
|
|
bound: []bool,
|
|
name: symbol.Id,
|
|
candidate: Comptime_Value,
|
|
span: source.Span,
|
|
diagnose: bool,
|
|
) -> bool {
|
|
index, ok := comptime_binding_index(function, prefix, name)
|
|
if !ok {
|
|
return false
|
|
}
|
|
value := candidate
|
|
value.name = name
|
|
if !bound[index] {
|
|
values[index] = value
|
|
bound[index] = true
|
|
return true
|
|
}
|
|
existing := values[index]
|
|
matches := inferred_comptime_value_equal(checker, existing, value)
|
|
if !matches && diagnose {
|
|
left := type_label(checker, existing.type) if existing.kind == .Type else fmt.aprintf("<comptime value>", allocator=checker.allocator) if existing.kind == .Static else fmt.aprintf("%d", existing.value, allocator=checker.allocator)
|
|
right := type_label(checker, value.type) if value.kind == .Type else fmt.aprintf("<comptime value>", allocator=checker.allocator) if value.kind == .Static else fmt.aprintf("%d", value.value, allocator=checker.allocator)
|
|
source.addf(checker.diagnostics, span, "conflicting inference for comptime parameter '%s': %s and %s", symbol_text(checker, name), left, right)
|
|
if existing.kind != .Type {
|
|
delete(left, checker.allocator)
|
|
}
|
|
if value.kind != .Type {
|
|
delete(right, checker.allocator)
|
|
}
|
|
}
|
|
return matches
|
|
}
|
|
|
|
type_pattern_mentions_comptime :: proc(
|
|
checker: ^Checker,
|
|
function: ast.Function,
|
|
prefix: int,
|
|
pattern: types.Type,
|
|
depth := 0,
|
|
type_params_only := false,
|
|
) -> bool {
|
|
if depth > 64 {
|
|
return false
|
|
}
|
|
item, ok := types.node(&checker.module.types, pattern)
|
|
if !ok {
|
|
return false
|
|
}
|
|
if item.qualifier == 0 && item.name != 0 {
|
|
if _, found := comptime_binding_index(function, prefix, symbol.Id(item.name)); found {
|
|
if !type_params_only {
|
|
return true
|
|
}
|
|
param, param_ok := comptime_param_for_name(function, symbol.Id(item.name))
|
|
if param_ok && is_comptime_type_param(checker, param) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
if item.kind == .Array && item.unresolved_count {
|
|
expr_id := ast.Expr_Id(item.count_expr)
|
|
if expr_id != ast.INVALID_EXPR && int(expr_id) < len(checker.ast_module.exprs) {
|
|
expr := checker.ast_module.exprs[expr_id]
|
|
if expr.kind == .Name && !symbol.is_valid(expr.qualifier) {
|
|
if _, found := comptime_binding_index(function, prefix, expr.name); found {
|
|
if !type_params_only {
|
|
return true
|
|
}
|
|
param, param_ok := comptime_param_for_name(function, expr.name)
|
|
if param_ok && is_comptime_type_param(checker, param) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if item.kind == .Type_Call {
|
|
expr_id := ast.Expr_Id(item.count_expr)
|
|
if expr_id != ast.INVALID_EXPR && int(expr_id) < len(checker.ast_module.exprs) {
|
|
for arg_id in checker.ast_module.exprs[expr_id].args {
|
|
if arg_id == ast.INVALID_EXPR || int(arg_id) >= len(checker.ast_module.exprs) {
|
|
continue
|
|
}
|
|
arg := checker.ast_module.exprs[arg_id]
|
|
if arg.kind == .Name && !symbol.is_valid(arg.qualifier) {
|
|
if _, found := comptime_binding_index(function, prefix, arg.name); found {
|
|
if !type_params_only {
|
|
return true
|
|
}
|
|
param, param_ok := comptime_param_for_name(function, arg.name)
|
|
if param_ok && is_comptime_type_param(checker, param) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if types.is_valid(item.child) && type_pattern_mentions_comptime(checker, function, prefix, item.child, depth+1, type_params_only) {
|
|
return true
|
|
}
|
|
if types.is_valid(item.extra) && type_pattern_mentions_comptime(checker, function, prefix, item.extra, depth+1, type_params_only) {
|
|
return true
|
|
}
|
|
if item.kind == .Function {
|
|
for field in types.params_for(&checker.module.types, pattern) {
|
|
if type_pattern_mentions_comptime(checker, function, prefix, field.type, depth+1, type_params_only) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
type_pattern_contains_type_call :: proc(checker: ^Checker, pattern: types.Type, depth := 0) -> bool {
|
|
if depth > 64 {
|
|
return false
|
|
}
|
|
item, ok := types.node(&checker.module.types, pattern)
|
|
if !ok {
|
|
return false
|
|
}
|
|
if item.kind == .Type_Call {
|
|
return true
|
|
}
|
|
if types.is_valid(item.child) && type_pattern_contains_type_call(checker, item.child, depth+1) {
|
|
return true
|
|
}
|
|
if types.is_valid(item.extra) && type_pattern_contains_type_call(checker, item.extra, depth+1) {
|
|
return true
|
|
}
|
|
if item.kind == .Function {
|
|
for field in types.params_for(&checker.module.types, pattern) {
|
|
if type_pattern_contains_type_call(checker, field.type, depth+1) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
match_inferred_type_pattern :: proc(
|
|
checker: ^Checker,
|
|
function: ast.Function,
|
|
prefix: int,
|
|
pattern, actual: types.Type,
|
|
values: []Comptime_Value,
|
|
bound: []bool,
|
|
span: source.Span,
|
|
diagnose: bool,
|
|
depth := 0,
|
|
) -> bool {
|
|
if depth > 64 || !types.is_valid(actual) {
|
|
return false
|
|
}
|
|
store := &checker.module.types
|
|
actual_type := types.resolve_alias(actual, store)
|
|
pattern_item, pattern_ok := types.node(store, pattern)
|
|
if pattern_ok && pattern_item.qualifier == 0 && pattern_item.name != 0 {
|
|
name := symbol.Id(pattern_item.name)
|
|
if binding_index, is_binding := comptime_binding_index(function, prefix, name); is_binding {
|
|
param, param_ok := comptime_param_for_name(function, name)
|
|
if !param_ok || !is_comptime_type_param(checker, param) {
|
|
return false
|
|
}
|
|
if bound[binding_index] && can_implicitly_convert_type(checker, actual_type, values[binding_index].type) {
|
|
return true
|
|
}
|
|
return bind_inferred_comptime(
|
|
checker, function, prefix, values, bound, name,
|
|
Comptime_Value{type=actual_type, kind=.Type}, span, diagnose,
|
|
)
|
|
}
|
|
}
|
|
if !pattern_ok {
|
|
return types.equal(pattern, actual_type)
|
|
}
|
|
if pattern_item.kind == .Type_Call {
|
|
expr_id := ast.Expr_Id(pattern_item.count_expr)
|
|
if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
|
|
return false
|
|
}
|
|
expr := checker.ast_module.exprs[expr_id]
|
|
target_pkg, available := expr_package(checker, expr, function.pkg, function.file, true)
|
|
if !available {
|
|
return false
|
|
}
|
|
template := find_template(checker, expr.name, target_pkg, expr_lookup_file(expr, function.file))
|
|
origin: Type_Factory_Origin
|
|
found := false
|
|
ambiguous := false
|
|
for candidate in checker.type_factory_origins {
|
|
if candidate.template != template || !types.equal(candidate.result, actual_type) ||
|
|
len(expr.args) != len(candidate.values) {
|
|
continue
|
|
}
|
|
compatible := true
|
|
for arg_id, index in expr.args {
|
|
if arg_id == ast.INVALID_EXPR || int(arg_id) >= len(checker.ast_module.exprs) {
|
|
compatible = false
|
|
break
|
|
}
|
|
arg := checker.ast_module.exprs[arg_id]
|
|
if arg.kind == .Name && !symbol.is_valid(arg.qualifier) {
|
|
if binding_index, is_binding := comptime_binding_index(function, prefix, arg.name);
|
|
is_binding && bound[binding_index] &&
|
|
!inferred_comptime_value_equal(checker, values[binding_index], candidate.values[index]) {
|
|
compatible = false
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if !compatible {
|
|
continue
|
|
}
|
|
if !found {
|
|
origin = candidate
|
|
found = true
|
|
continue
|
|
}
|
|
for arg_id, index in expr.args {
|
|
arg := checker.ast_module.exprs[arg_id]
|
|
if arg.kind == .Name && !symbol.is_valid(arg.qualifier) {
|
|
if _, is_binding := comptime_binding_index(function, prefix, arg.name);
|
|
is_binding && !inferred_comptime_value_equal(checker, origin.values[index], candidate.values[index]) {
|
|
ambiguous = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if !found || ambiguous {
|
|
return false
|
|
}
|
|
matched := true
|
|
for arg_id, index in expr.args {
|
|
arg := checker.ast_module.exprs[arg_id]
|
|
if arg.kind == .Name && !symbol.is_valid(arg.qualifier) {
|
|
if _, is_binding := comptime_binding_index(function, prefix, arg.name); is_binding {
|
|
matched = bind_inferred_comptime(
|
|
checker, function, prefix, values, bound, arg.name,
|
|
origin.values[index], span, diagnose,
|
|
) && matched
|
|
}
|
|
}
|
|
}
|
|
return matched
|
|
}
|
|
actual_item, actual_ok := types.node(store, actual_type)
|
|
if pattern_item.kind == .Slice {
|
|
actual_pointer, actual_array, array_pointer_ok := types.array_pointer(actual_type, store)
|
|
if array_pointer_ok {
|
|
mutable := actual_pointer.mutable && actual_array.mutable
|
|
if pattern_item.mutable && !mutable ||
|
|
pattern_item.has_sentinel && (!actual_array.has_sentinel || pattern_item.sentinel != actual_array.sentinel) {
|
|
return false
|
|
}
|
|
return match_inferred_type_pattern(
|
|
checker, function, prefix, pattern_item.child, actual_array.child,
|
|
values, bound, span, diagnose, depth+1,
|
|
)
|
|
}
|
|
}
|
|
if !actual_ok || pattern_item.kind != actual_item.kind {
|
|
resolved := type_from_syntax(checker, pattern, function.pkg, function.file)
|
|
return types.is_valid(resolved) && types.equal(types.resolve_alias(resolved, store), actual_type)
|
|
}
|
|
if pattern_item.many != actual_item.many || pattern_item.has_sentinel != actual_item.has_sentinel ||
|
|
pattern_item.has_sentinel && pattern_item.sentinel != actual_item.sentinel {
|
|
return false
|
|
}
|
|
matched := true
|
|
if pattern_item.kind == .Array {
|
|
if pattern_item.unresolved_count {
|
|
count_expr := ast.Expr_Id(pattern_item.count_expr)
|
|
if count_expr != ast.INVALID_EXPR && int(count_expr) < len(checker.ast_module.exprs) {
|
|
expr := checker.ast_module.exprs[count_expr]
|
|
if expr.kind == .Name && !symbol.is_valid(expr.qualifier) {
|
|
if binding_index, is_binding := comptime_binding_index(function, prefix, expr.name); is_binding {
|
|
param, param_ok := comptime_param_for_name(function, expr.name)
|
|
if param_ok && !is_comptime_type_param(checker, param) {
|
|
matched = bind_inferred_comptime(
|
|
checker, function, prefix, values, bound, expr.name,
|
|
Comptime_Value{type=values[binding_index].type, value=i128(actual_item.count), kind=.Integer},
|
|
span, diagnose,
|
|
) && matched
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else if pattern_item.count != actual_item.count {
|
|
matched = false
|
|
}
|
|
}
|
|
if types.is_valid(pattern_item.child) {
|
|
matched = match_inferred_type_pattern(
|
|
checker, function, prefix, pattern_item.child, actual_item.child,
|
|
values, bound, span, diagnose, depth+1,
|
|
) && matched
|
|
}
|
|
if types.is_valid(pattern_item.extra) {
|
|
matched = match_inferred_type_pattern(
|
|
checker, function, prefix, pattern_item.extra, actual_item.extra,
|
|
values, bound, span, diagnose, depth+1,
|
|
) && matched
|
|
}
|
|
if pattern_item.kind == .Function {
|
|
pattern_params := types.params_for(store, pattern)
|
|
actual_params := types.params_for(store, actual_type)
|
|
if len(pattern_params) != len(actual_params) || pattern_item.c_abi != actual_item.c_abi || pattern_item.variadic != actual_item.variadic {
|
|
return false
|
|
}
|
|
for field, index in pattern_params {
|
|
matched = match_inferred_type_pattern(
|
|
checker, function, prefix, field.type, actual_params[index].type,
|
|
values, bound, span, diagnose, depth+1,
|
|
) && matched
|
|
}
|
|
}
|
|
return matched
|
|
}
|
|
|
|
infer_call_comptime_values :: proc(
|
|
checker: ^Checker,
|
|
function: ast.Function,
|
|
prefix: int,
|
|
mapping: []int,
|
|
args: []ast.Expr_Id,
|
|
actual_args: []types.Type,
|
|
expected: types.Type,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
diagnose := false,
|
|
failure: ^string = nil,
|
|
) -> ([]Comptime_Value, bool) {
|
|
values := make([]Comptime_Value, prefix, checker.allocator)
|
|
bound := make([]bool, prefix, checker.allocator)
|
|
defer delete(bound, checker.allocator)
|
|
ordinal := 0
|
|
for param in function.params {
|
|
if !param.comptime_value {
|
|
continue
|
|
}
|
|
values[ordinal].name = param.name
|
|
dependent := type_pattern_mentions_comptime(checker, function, ordinal, param.type)
|
|
if dependent {
|
|
values[ordinal].kind = .Static
|
|
} else if is_comptime_type_param(checker, param) {
|
|
values[ordinal].kind = .Type
|
|
} else if is_comptime_string_param(checker, param, function) {
|
|
values[ordinal].kind = .String
|
|
values[ordinal].type = type_from_syntax(checker, param.type, function.pkg, function.file)
|
|
} else if types.is_concrete_integer(type_from_syntax(checker, param.type, function.pkg, function.file)) {
|
|
values[ordinal].kind = .Integer
|
|
values[ordinal].type = type_from_syntax(checker, param.type, function.pkg, function.file)
|
|
} else {
|
|
values[ordinal].kind = .Static
|
|
values[ordinal].type = type_from_syntax(checker, param.type, function.pkg, function.file)
|
|
}
|
|
ordinal += 1
|
|
}
|
|
matched := true
|
|
all_bound := true
|
|
for value_bound in bound {
|
|
all_bound = all_bound && value_bound
|
|
}
|
|
if !all_bound && is_runtime_type(checker, expected) {
|
|
if types.is_valid(function.error) && types.kind(expected, &checker.module.types) == .Fallible {
|
|
matched = match_inferred_type_pattern(
|
|
checker, function, prefix, function.result,
|
|
types.fallible_success(expected, &checker.module.types), values, bound,
|
|
source.Span{}, diagnose,
|
|
) && matched
|
|
matched = match_inferred_type_pattern(
|
|
checker, function, prefix, function.error,
|
|
types.fallible_error(expected, &checker.module.types), values, bound,
|
|
source.Span{}, diagnose,
|
|
) && matched
|
|
} else if type_pattern_mentions_comptime(checker, function, prefix, function.result) {
|
|
matched = match_inferred_type_pattern(
|
|
checker, function, prefix, function.result, expected, values, bound,
|
|
source.Span{}, diagnose,
|
|
) && matched
|
|
}
|
|
}
|
|
// A comptime aggregate is evaluated only after its dependent type is known.
|
|
// Bind a direct `[N]T` parameter's N from an array literal's syntax first;
|
|
// this supplies the contextual element type for strings and enum literals.
|
|
for arg_id, source_index in args {
|
|
param_index := call_param_index(mapping, source_index)
|
|
if param_index < 0 || param_index >= len(function.params) ||
|
|
!function.params[param_index].comptime_value ||
|
|
arg_id == ast.INVALID_EXPR || int(arg_id) >= len(checker.ast_module.exprs) ||
|
|
checker.ast_module.exprs[arg_id].kind != .Array {
|
|
continue
|
|
}
|
|
pattern, pattern_ok := types.node(&checker.module.types, function.params[param_index].type)
|
|
if !pattern_ok || pattern.kind != .Array || !pattern.unresolved_count ||
|
|
pattern.count_expr == u32(ast.INVALID_EXPR) || int(pattern.count_expr) >= len(checker.ast_module.exprs) {
|
|
continue
|
|
}
|
|
count_expr := checker.ast_module.exprs[ast.Expr_Id(pattern.count_expr)]
|
|
if count_expr.kind != .Name || symbol.is_valid(count_expr.qualifier) {
|
|
continue
|
|
}
|
|
binding_index, is_binding := comptime_binding_index(function, prefix, count_expr.name)
|
|
if !is_binding || bound[binding_index] {
|
|
continue
|
|
}
|
|
param, param_ok := comptime_param_for_name(function, count_expr.name)
|
|
if !param_ok || is_comptime_type_param(checker, param) {
|
|
continue
|
|
}
|
|
values[binding_index] = Comptime_Value{
|
|
name=count_expr.name,
|
|
type=type_from_syntax(checker, param.type, function.pkg, function.file),
|
|
value=i128(len(checker.ast_module.exprs[arg_id].args)),
|
|
kind=.Integer,
|
|
}
|
|
bound[binding_index] = true
|
|
}
|
|
for arg_id, source_index in args {
|
|
param_index := call_param_index(mapping, source_index)
|
|
if param_index < 0 || param_index >= len(function.params) || !function.params[param_index].comptime_value ||
|
|
arg_id == ast.INVALID_EXPR || int(arg_id) >= len(checker.ast_module.exprs) {
|
|
continue
|
|
}
|
|
expr := checker.ast_module.exprs[arg_id]
|
|
if expr.kind == .Inference_Hole {
|
|
continue
|
|
}
|
|
param := function.params[param_index]
|
|
binding_index, binding_ok := comptime_binding_index(function, prefix, param.name)
|
|
if !binding_ok {
|
|
matched = false
|
|
continue
|
|
}
|
|
dependent := type_pattern_mentions_comptime(checker, function, prefix, param.type)
|
|
if is_comptime_type_param(checker, param) {
|
|
actual, ok := resolve_type_argument(checker, arg_id, pkg, file)
|
|
if !ok {
|
|
if failure != nil && len(failure^) == 0 {
|
|
failure^ = fmt.aprintf(
|
|
"argument %d for comptime type parameter '%s' is not a type",
|
|
source_index+1, symbol_text(checker, param.name), allocator=checker.allocator,
|
|
)
|
|
}
|
|
if diagnose {
|
|
source.addf(checker.diagnostics, expr.span,
|
|
"argument for comptime type parameter '%s' must be a type",
|
|
symbol_text(checker, param.name))
|
|
}
|
|
matched = false
|
|
continue
|
|
}
|
|
values[binding_index] = Comptime_Value{name=param.name, type=actual, kind=.Type}
|
|
bound[binding_index] = true
|
|
} else if !dependent && is_comptime_string_param(checker, param, function) {
|
|
text, text_ok := comptime_string_argument(checker, arg_id, pkg, file)
|
|
if !text_ok {
|
|
if failure != nil && len(failure^) == 0 {
|
|
failure^ = fmt.aprintf(
|
|
"argument %d for comptime string parameter '%s' does not evaluate to immutable bytes",
|
|
source_index+1, symbol_text(checker, param.name), allocator=checker.allocator,
|
|
)
|
|
}
|
|
if diagnose {
|
|
source.addf(checker.diagnostics, expr.span,
|
|
"argument for comptime string parameter '%s' must evaluate to immutable bytes",
|
|
symbol_text(checker, param.name))
|
|
}
|
|
matched = false
|
|
continue
|
|
}
|
|
values[binding_index] = Comptime_Value{
|
|
name=param.name,
|
|
type=type_from_syntax(checker, param.type, function.pkg, function.file),
|
|
text=text,
|
|
kind=.String,
|
|
}
|
|
bound[binding_index] = true
|
|
} else if !dependent && types.is_concrete_integer(type_from_syntax(checker, param.type, function.pkg, function.file)) {
|
|
declared := type_from_syntax(checker, param.type, function.pkg, function.file)
|
|
constant := eval_integer_constant_in_context(checker, arg_id, pkg, file)
|
|
if constant.kind != .Value {
|
|
if failure != nil && len(failure^) == 0 {
|
|
failure^ = fmt.aprintf(
|
|
"argument %d for comptime parameter '%s' is not a compile-time integer expression",
|
|
source_index+1, symbol_text(checker, param.name), allocator=checker.allocator,
|
|
)
|
|
}
|
|
if diagnose {
|
|
source.addf(checker.diagnostics, expr.span,
|
|
"argument for comptime parameter '%s' must be a compile-time integer expression",
|
|
symbol_text(checker, param.name))
|
|
}
|
|
matched = false
|
|
continue
|
|
}
|
|
if !fits_integer_type(constant.value, declared, checker.target) {
|
|
if failure != nil && len(failure^) == 0 {
|
|
failure^ = fmt.aprintf(
|
|
"argument %d integer constant %d does not fit in %s",
|
|
source_index+1, constant.value, types.name(declared), allocator=checker.allocator,
|
|
)
|
|
}
|
|
if diagnose {
|
|
source.addf(checker.diagnostics, expr.span,
|
|
"integer constant %d does not fit in %s", constant.value, types.name(declared))
|
|
}
|
|
matched = false
|
|
continue
|
|
}
|
|
values[binding_index] = Comptime_Value{name=param.name, type=declared, value=constant.value, kind=.Integer}
|
|
bound[binding_index] = true
|
|
} else {
|
|
available: [dynamic]Comptime_Value
|
|
available.allocator = checker.allocator
|
|
append(&available, ..checker.current_comptime_values)
|
|
for prior, prior_index in values[:binding_index] {
|
|
if bound[prior_index] {
|
|
append(&available, prior)
|
|
}
|
|
}
|
|
previous := checker.current_comptime_values
|
|
checker.current_comptime_values = available[:]
|
|
declared := type_from_syntax(checker, param.type, function.pkg, function.file)
|
|
checker.current_comptime_values = previous
|
|
value, value_ok := eval_static_comptime_value(
|
|
checker, param.name, arg_id, declared, pkg, file, available[:], diagnose,
|
|
)
|
|
delete(available)
|
|
if !value_ok {
|
|
if failure != nil && len(failure^) == 0 {
|
|
failure^ = fmt.aprintf(
|
|
"argument %d for comptime parameter '%s' has no stable comptime identity",
|
|
source_index+1, symbol_text(checker, param.name), allocator=checker.allocator,
|
|
)
|
|
}
|
|
matched = false
|
|
continue
|
|
}
|
|
values[binding_index] = value
|
|
bound[binding_index] = true
|
|
}
|
|
}
|
|
all_bound = true
|
|
for value_bound in bound {
|
|
all_bound = all_bound && value_bound
|
|
}
|
|
// Factory provenance is authoritative when unique, so try it before direct
|
|
// arguments. Strong and then weak direct evidence can disambiguate a factory
|
|
// result; retry factory patterns once after both evidence passes.
|
|
Evidence_Pass :: struct {
|
|
type_call: bool,
|
|
weak: bool,
|
|
final: bool,
|
|
}
|
|
evidence_passes := [?]Evidence_Pass{
|
|
{type_call=true},
|
|
{final=true},
|
|
{weak=true, final=true},
|
|
{type_call=true, final=true},
|
|
}
|
|
for pass in evidence_passes {
|
|
for arg_id, source_index in args {
|
|
param_index := call_param_index(mapping, source_index)
|
|
if param_index >= len(function.params) || param_index >= len(actual_args) {
|
|
continue
|
|
}
|
|
if param_index < 0 || function.params[param_index].comptime_value {
|
|
continue
|
|
}
|
|
arg_expr := checker.ast_module.exprs[arg_id]
|
|
is_null := arg_expr.kind == .Null
|
|
is_weak := is_numeric_constant_expr(checker, arg_id) ||
|
|
arg_expr.kind == .String || is_null
|
|
contains_type_call := type_pattern_contains_type_call(
|
|
checker, function.params[param_index].type,
|
|
)
|
|
if contains_type_call != pass.type_call ||
|
|
!pass.type_call && is_weak != pass.weak {
|
|
continue
|
|
}
|
|
if !type_pattern_mentions_comptime(checker, function, prefix, function.params[param_index].type) {
|
|
continue
|
|
}
|
|
// Once the result type has fixed every comptime parameter, an anonymous
|
|
// keyed record must be checked against the specialized parameter type.
|
|
// Its provisional structural type intentionally contains only the supplied
|
|
// fields, so comparing that type here would reject omitted defaulted fields.
|
|
if all_bound && arg_expr.kind == .Struct_Literal && !arg_expr.tuple && !symbol.is_valid(arg_expr.name) {
|
|
continue
|
|
}
|
|
if pass.weak {
|
|
if item, ok := types.node(&checker.module.types, function.params[param_index].type); ok &&
|
|
item.qualifier == 0 && item.name != 0 {
|
|
if binding_index, is_binding := comptime_binding_index(function, prefix, symbol.Id(item.name));
|
|
is_binding && bound[binding_index] {
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
actual := actual_args[param_index]
|
|
if is_null {
|
|
previous := checker.current_comptime_values
|
|
checker.current_comptime_values = values
|
|
contextual := type_from_syntax(
|
|
checker, function.params[param_index].type, function.pkg, function.file,
|
|
)
|
|
checker.current_comptime_values = previous
|
|
if types.is_optional(contextual, &checker.module.types) {
|
|
actual = contextual
|
|
actual_args[param_index] = contextual
|
|
}
|
|
}
|
|
pass_matched := match_inferred_type_pattern(
|
|
checker, function, prefix, function.params[param_index].type,
|
|
actual, values, bound,
|
|
checker.ast_module.exprs[arg_id].span, diagnose && pass.final,
|
|
)
|
|
if pass.final {
|
|
matched = pass_matched && matched
|
|
}
|
|
}
|
|
}
|
|
ordinal = 0
|
|
for param in function.params {
|
|
if !param.comptime_value {
|
|
continue
|
|
}
|
|
if bound[ordinal] {
|
|
ordinal += 1
|
|
continue
|
|
}
|
|
matched = false
|
|
if failure != nil && len(failure^) == 0 {
|
|
failure^ = fmt.aprintf(
|
|
"cannot infer comptime parameter '%s'",
|
|
symbol_text(checker, param.name), allocator=checker.allocator,
|
|
)
|
|
}
|
|
if diagnose {
|
|
source.addf(
|
|
checker.diagnostics, param.span,
|
|
"cannot infer comptime parameter '%s'; pass it explicitly",
|
|
symbol_text(checker, param.name),
|
|
)
|
|
}
|
|
ordinal += 1
|
|
}
|
|
if !matched {
|
|
if failure != nil && len(failure^) == 0 {
|
|
failure^ = fmt.aprintf("comptime inference produced conflicting bindings", allocator=checker.allocator)
|
|
}
|
|
delete(values, checker.allocator)
|
|
return nil, false
|
|
}
|
|
return values, true
|
|
}
|
|
|
|
call_arg_expected :: proc(checker: ^Checker, function: ast.Function, index: int) -> types.Type {
|
|
if index < 0 || index >= len(function.params) {
|
|
return types.INVALID
|
|
}
|
|
if is_comptime_type_param(checker, function.params[index]) {
|
|
return types.INVALID
|
|
}
|
|
for param in function.params {
|
|
if !param.comptime_value {
|
|
continue
|
|
}
|
|
if _, ok := current_comptime_value(checker, param.name); !ok {
|
|
return types.INVALID
|
|
}
|
|
}
|
|
declared := type_from_syntax(checker, function.params[index].type, function.pkg, function.file)
|
|
// A `float` param defaults to f64 so an integer-literal argument builds as a
|
|
// float constant (e.g. `f(3)` -> 3.0), mirroring `pi float = 3` for locals.
|
|
// Integer/range constraints keep building naturally from their literals.
|
|
if declared == types.FLOAT {
|
|
return types.F64
|
|
}
|
|
return declared
|
|
}
|
|
|
|
resolve_type_argument :: proc(
|
|
checker: ^Checker,
|
|
expr_id: ast.Expr_Id,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
) -> (types.Type, bool) {
|
|
if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
|
|
return types.INVALID, false
|
|
}
|
|
expr := checker.ast_module.exprs[expr_id]
|
|
#partial switch expr.kind {
|
|
case .Type:
|
|
resolved := type_from_syntax(checker, expr.type, pkg, file)
|
|
return resolved, types.is_valid(resolved)
|
|
case .Name:
|
|
if symbol.is_valid(expr.qualifier) && symbol_text(checker, expr.name) == "type" {
|
|
if value, ok := static_field_value(checker, expr.qualifier, expr.name); ok && value.kind == .Type {
|
|
return types.Type(value.index), true
|
|
}
|
|
}
|
|
if !symbol.is_valid(expr.qualifier) {
|
|
if actual, ok := current_comptime_type(checker, expr.name); ok {
|
|
return actual, true
|
|
}
|
|
}
|
|
target_pkg, available := expr_package(checker, expr, pkg, file)
|
|
if !available {
|
|
return types.INVALID, false
|
|
}
|
|
value := types.find_named(&checker.module.types, u32(target_pkg), u32(expr.name), file=u32(expr_lookup_file(expr, file)))
|
|
value = types.resolve_alias(value, &checker.module.types)
|
|
return value, types.is_valid(value)
|
|
case .Call:
|
|
value := resolve_type_factory_call(checker, expr_id, pkg, file)
|
|
return value, types.is_valid(value)
|
|
}
|
|
return types.INVALID, false
|
|
}
|
|
|
|
clone_comptime_values :: proc(values: []Comptime_Value, allocator: mem.Allocator) -> []Comptime_Value {
|
|
result := make([]Comptime_Value, len(values), allocator)
|
|
copy(result, values)
|
|
return result
|
|
}
|
|
|
|
expand_expansions_equal :: proc(left, right: []Expand_Expansion) -> bool {
|
|
if len(left) != len(right) {
|
|
return false
|
|
}
|
|
for value, index in left {
|
|
if value != right[index] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
find_call_resolution :: proc(
|
|
checker: ^Checker,
|
|
expr: ast.Expr_Id,
|
|
) -> (int, bool) {
|
|
for index := len(checker.call_resolutions)-1; index >= 0; index -= 1 {
|
|
entry := checker.call_resolutions[index]
|
|
if entry.expr == expr &&
|
|
comptime_values_equal(entry.ctx, checker.current_comptime_values) &&
|
|
expand_expansions_equal(entry.expand_ctx, checker.expand_context[:]) {
|
|
return index, true
|
|
}
|
|
}
|
|
return -1, false
|
|
}
|
|
|
|
call_resolution_matches :: proc(
|
|
entry: Call_Resolution,
|
|
mapping: []int,
|
|
comptime_values: []Comptime_Value,
|
|
runtime_types: []types.Type,
|
|
) -> bool {
|
|
if len(entry.mapping) != len(mapping) || len(entry.runtime_types) != len(runtime_types) ||
|
|
!comptime_values_equal(entry.comptime_values, comptime_values) {
|
|
return false
|
|
}
|
|
for value, index in mapping {
|
|
if entry.mapping[index] != value {
|
|
return false
|
|
}
|
|
}
|
|
for value, index in runtime_types {
|
|
if !types.equal(entry.runtime_types[index], value) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
store_call_resolution :: proc(
|
|
checker: ^Checker,
|
|
expr: ast.Expr_Id,
|
|
mapping: []int,
|
|
comptime_values: []Comptime_Value,
|
|
runtime_types: []types.Type,
|
|
) -> int {
|
|
if index, ok := find_call_resolution(checker, expr); ok && call_resolution_matches(
|
|
checker.call_resolutions[index], mapping, comptime_values, runtime_types,
|
|
) {
|
|
return index
|
|
}
|
|
entry := Call_Resolution{
|
|
expr=expr,
|
|
ctx=clone_comptime_values(checker.current_comptime_values, checker.allocator),
|
|
expand_ctx=slice.clone(checker.expand_context[:], checker.allocator),
|
|
mapping=slice.clone(mapping, checker.allocator),
|
|
comptime_values=clone_comptime_values(comptime_values, checker.allocator),
|
|
runtime_types=slice.clone(runtime_types, checker.allocator),
|
|
folded_value=INVALID_CT_VALUE,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
}
|
|
if index, ok := find_call_resolution(checker, expr); ok {
|
|
previous := checker.call_resolutions[index]
|
|
delete(previous.ctx, checker.allocator)
|
|
delete(previous.expand_ctx, checker.allocator)
|
|
delete(previous.mapping, checker.allocator)
|
|
delete(previous.comptime_values, checker.allocator)
|
|
delete(previous.runtime_types, checker.allocator)
|
|
checker.call_resolutions[index] = entry
|
|
return index
|
|
}
|
|
append(&checker.call_resolutions, entry)
|
|
return len(checker.call_resolutions)-1
|
|
}
|
|
|
|
resolved_call_arg_expected :: proc(
|
|
checker: ^Checker,
|
|
function: ast.Function,
|
|
param_index: int,
|
|
resolution_index: int,
|
|
) -> types.Type {
|
|
if resolution_index < 0 || resolution_index >= len(checker.call_resolutions) {
|
|
return call_arg_expected(checker, function, param_index)
|
|
}
|
|
previous := checker.current_comptime_values
|
|
checker.current_comptime_values = checker.call_resolutions[resolution_index].comptime_values
|
|
result := call_arg_expected(checker, function, param_index)
|
|
checker.current_comptime_values = previous
|
|
return result
|
|
}
|
|
|
|
resolve_generated_struct_type :: proc(checker: ^Checker, expr_id: ast.Expr_Id, pkg: ast.Package_Id, file: ast.File_Id, active_state: ^Ct_State) -> types.Type {
|
|
for entry in checker.generated_types {
|
|
if entry.expr == expr_id && comptime_values_equal(entry.values, checker.current_comptime_values) {
|
|
return entry.result
|
|
}
|
|
}
|
|
if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
|
|
return types.INVALID
|
|
}
|
|
expr := checker.ast_module.exprs[expr_id]
|
|
field_start := int(u32(expr.integer>>32))
|
|
field_count := int(u32(expr.integer))
|
|
if field_start < 0 || field_count < 0 || field_start+field_count > len(checker.ast_module.type_fields) {
|
|
return types.INVALID
|
|
}
|
|
template_fields := checker.ast_module.type_fields[field_start:field_start+field_count]
|
|
fields := make([]types.Field, len(template_fields), checker.allocator)
|
|
defer delete(fields, checker.allocator)
|
|
for field, index in template_fields {
|
|
resolved := type_from_syntax(checker, field.type, pkg, file, active_state=active_state)
|
|
if (!is_runtime_type(checker, resolved) && !is_comptime_value_type(checker, resolved)) || types.is_void(resolved) {
|
|
if expr.tuple {
|
|
source.addf(checker.diagnostics, expr.span, "tuple element %d requires a concrete runtime type, got %s", index, type_label(checker, resolved))
|
|
} else {
|
|
source.addf(checker.diagnostics, expr.span, "anonymous struct field '%s' requires a concrete runtime type, got %s", symbol_text(checker, symbol.Id(field.name)), type_label(checker, resolved))
|
|
}
|
|
return types.INVALID
|
|
}
|
|
fields[index] = types.Field{name=field.name, type=resolved}
|
|
}
|
|
result := types.struct_generated(&checker.module.types, fields, expr.tuple)
|
|
append(&checker.generated_types, Generated_Type_Entry{
|
|
expr=expr_id,
|
|
values=clone_comptime_values(checker.current_comptime_values, checker.allocator),
|
|
result=result,
|
|
pkg=pkg,
|
|
file=file,
|
|
})
|
|
return result
|
|
}
|
|
|
|
resolve_type_factory_call :: proc(checker: ^Checker, expr_id: ast.Expr_Id, pkg: ast.Package_Id, file: ast.File_Id) -> types.Type {
|
|
if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
|
|
return types.INVALID
|
|
}
|
|
expr := checker.ast_module.exprs[expr_id]
|
|
if expr.kind != .Call || expr.left != ast.INVALID_EXPR {
|
|
source.add(checker.diagnostics, expr.span, "type position requires a direct type-factory call")
|
|
return types.INVALID
|
|
}
|
|
if expr.intrinsic {
|
|
if !is_intrinsic_call(checker, expr, "struct_type") {
|
|
return types.INVALID
|
|
}
|
|
state := ct_state_make(checker, pkg, file, values=checker.current_comptime_values)
|
|
value, flow, ok := ct_eval_call_expr(&state, expr, types.INVALID, 0, expr_id)
|
|
result := types.INVALID
|
|
if ok && flow.kind == .Normal && value != INVALID_CT_VALUE && int(value) < len(state.values) && state.values[value].kind == .Type {
|
|
result = types.Type(state.values[value].index)
|
|
}
|
|
ct_state_destroy(&state)
|
|
return result
|
|
}
|
|
target_pkg, available := expr_package(checker, expr, pkg, file, true)
|
|
if !available {
|
|
_ = add_package_resolution_diagnostic(checker, expr, file)
|
|
return types.INVALID
|
|
}
|
|
template := find_template(checker, expr.name, target_pkg, expr_lookup_file(expr, file))
|
|
if template == ast.INVALID_FUNCTION || int(template) >= len(checker.ast_module.functions) {
|
|
source.addf(checker.diagnostics, expr.span, "unknown type factory '%s'", symbol_text(checker, expr.name))
|
|
return types.INVALID
|
|
}
|
|
function := checker.ast_module.functions[template]
|
|
if !is_type_metatype_syntax(checker, function.result) || types.is_valid(function.error) {
|
|
source.addf(checker.diagnostics, expr.span, "function '%s' does not return a type", symbol_text(checker, expr.name))
|
|
return types.INVALID
|
|
}
|
|
for param in function.params {
|
|
if !param.comptime_value {
|
|
source.addf(checker.diagnostics, param.span, "type-factory parameter '%s' must be comptime", symbol_text(checker, param.name))
|
|
return types.INVALID
|
|
}
|
|
}
|
|
if !valid_call_arity(function, len(expr.args)) {
|
|
source.addf(checker.diagnostics, expr.span, "type factory '%s' expects %d arguments, got %d", symbol_text(checker, expr.name), len(function.params), len(expr.args))
|
|
return types.INVALID
|
|
}
|
|
values, ok := collect_comptime_values(checker, function, expr.args, pkg, file, true, checker.current_comptime_values)
|
|
defer delete(values, checker.allocator)
|
|
if !ok {
|
|
return types.INVALID
|
|
}
|
|
// A generic function's declaration is validated before it has a specialization.
|
|
// Leave calls containing its unresolved type parameters pending until then.
|
|
for value in values {
|
|
if value.kind != .Type {
|
|
continue
|
|
}
|
|
if item, item_ok := types.node(&checker.module.types, value.type); item_ok && item.kind == .Named && !item.declared {
|
|
return types.INVALID
|
|
}
|
|
}
|
|
for &entry in checker.type_factories {
|
|
if entry.template != template || !comptime_values_equal(entry.values, values) {
|
|
continue
|
|
}
|
|
if entry.resolving {
|
|
source.addf(checker.diagnostics, expr.span, "recursive type-factory specialization of '%s'", symbol_text(checker, expr.name))
|
|
return types.INVALID
|
|
}
|
|
return entry.result
|
|
}
|
|
entry_index := len(checker.type_factories)
|
|
append(&checker.type_factories, Type_Factory_Entry{
|
|
template=template,
|
|
values=clone_comptime_values(values, checker.allocator),
|
|
result=types.INVALID,
|
|
resolving=true,
|
|
})
|
|
state := ct_state_make(checker, pkg, file)
|
|
value, flow, eval_ok := ct_eval_call_expr(&state, expr, function.result, 0, expr_id)
|
|
result := types.INVALID
|
|
if eval_ok && flow.kind == .Normal && value != INVALID_CT_VALUE && int(value) < len(state.values) && state.values[value].kind == .Type {
|
|
result = types.Type(state.values[value].index)
|
|
} else if state.diagnostic == source.INVALID_DIAGNOSTIC {
|
|
source.addf(checker.diagnostics, expr.span, "type factory '%s' did not return a type", symbol_text(checker, expr.name))
|
|
}
|
|
ct_state_destroy(&state)
|
|
checker.type_factories[entry_index].result = result
|
|
checker.type_factories[entry_index].resolving = false
|
|
record_type_factory_origin(checker, result, template, values)
|
|
return result
|
|
}
|
|
|
|
record_type_factory_origin :: proc(
|
|
checker: ^Checker,
|
|
result: types.Type,
|
|
template: ast.Function_Id,
|
|
values: []Comptime_Value,
|
|
) {
|
|
if !types.is_valid(result) {
|
|
return
|
|
}
|
|
generated := false
|
|
for entry in checker.generated_types {
|
|
if types.equal(entry.result, result) {
|
|
generated = true
|
|
break
|
|
}
|
|
}
|
|
if !generated {
|
|
return
|
|
}
|
|
for origin in checker.type_factory_origins {
|
|
if origin.template == template && types.equal(origin.result, result) &&
|
|
comptime_values_equal(origin.values, values) {
|
|
return
|
|
}
|
|
}
|
|
append(&checker.type_factory_origins, Type_Factory_Origin{
|
|
result=result,
|
|
template=template,
|
|
values=clone_comptime_values(values, checker.allocator),
|
|
})
|
|
}
|
|
|
|
collect_comptime_values :: proc(
|
|
checker: ^Checker,
|
|
function: ast.Function,
|
|
args: []ast.Expr_Id,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
diagnose := false,
|
|
extra_values: []Comptime_Value = nil,
|
|
) -> ([]Comptime_Value, bool) {
|
|
if !function_has_comptime_params(function) {
|
|
return nil, true
|
|
}
|
|
values: [dynamic]Comptime_Value
|
|
values.allocator = checker.allocator
|
|
ok := true
|
|
for param, index in function.params {
|
|
if !param.comptime_value {
|
|
continue
|
|
}
|
|
span := param.span
|
|
if index < len(args) && args[index] != ast.INVALID_EXPR && int(args[index]) < len(checker.ast_module.exprs) {
|
|
span = checker.ast_module.exprs[args[index]].span
|
|
}
|
|
if is_comptime_type_param(checker, param) {
|
|
actual, actual_ok := types.INVALID, false
|
|
if index < len(args) {
|
|
actual, actual_ok = resolve_type_argument(checker, args[index], pkg, file)
|
|
}
|
|
if !actual_ok {
|
|
if diagnose {
|
|
source.addf(
|
|
checker.diagnostics,
|
|
span,
|
|
"argument for comptime type parameter '%s' must be a type",
|
|
symbol_text(checker, param.name),
|
|
)
|
|
}
|
|
ok = false
|
|
continue
|
|
}
|
|
append(&values, Comptime_Value{name=param.name, type=actual, kind=.Type})
|
|
continue
|
|
}
|
|
if is_comptime_string_param(checker, param, function) {
|
|
text, text_ok := "", false
|
|
if index < len(args) {
|
|
text, text_ok = comptime_string_argument(checker, args[index], pkg, file)
|
|
}
|
|
if !text_ok {
|
|
if diagnose {
|
|
source.addf(
|
|
checker.diagnostics, span,
|
|
"argument for comptime string parameter '%s' must evaluate to immutable bytes",
|
|
symbol_text(checker, param.name),
|
|
)
|
|
}
|
|
ok = false
|
|
continue
|
|
}
|
|
append(&values, Comptime_Value{
|
|
name=param.name,
|
|
type=type_from_syntax(checker, param.type, function.pkg, function.file),
|
|
text=text,
|
|
kind=.String,
|
|
})
|
|
continue
|
|
}
|
|
resolution_values: [dynamic]Comptime_Value
|
|
resolution_values.allocator = checker.allocator
|
|
append(&resolution_values, ..extra_values)
|
|
append(&resolution_values, ..values[:])
|
|
previous := checker.current_comptime_values
|
|
checker.current_comptime_values = resolution_values[:]
|
|
declared := type_from_syntax(checker, param.type, function.pkg, function.file)
|
|
checker.current_comptime_values = previous
|
|
delete(resolution_values)
|
|
if types.is_concrete_integer(declared) {
|
|
constant := Constant{kind = .Not_Constant}
|
|
if index < len(args) {
|
|
constant = eval_integer_constant_in_context(checker, args[index], pkg, file, values=extra_values)
|
|
}
|
|
if constant.kind != .Value {
|
|
if diagnose {
|
|
source.addf(
|
|
checker.diagnostics,
|
|
span,
|
|
"argument for comptime parameter '%s' must be a compile-time integer expression",
|
|
symbol_text(checker, param.name),
|
|
)
|
|
}
|
|
ok = false
|
|
continue
|
|
}
|
|
if !fits_integer_type(constant.value, declared, checker.target) {
|
|
if diagnose {
|
|
source.addf(
|
|
checker.diagnostics,
|
|
span,
|
|
"integer constant %d does not fit in %s",
|
|
constant.value,
|
|
types.name(declared),
|
|
)
|
|
}
|
|
ok = false
|
|
continue
|
|
}
|
|
append(&values, Comptime_Value{name=param.name, type=declared, value=constant.value})
|
|
continue
|
|
}
|
|
if index >= len(args) || args[index] == ast.INVALID_EXPR {
|
|
ok = false
|
|
continue
|
|
}
|
|
available: [dynamic]Comptime_Value
|
|
available.allocator = checker.allocator
|
|
append(&available, ..extra_values)
|
|
append(&available, ..values[:])
|
|
value, value_ok := eval_static_comptime_value(
|
|
checker, param.name, args[index], declared, pkg, file, available[:], diagnose,
|
|
)
|
|
delete(available)
|
|
if !value_ok {
|
|
ok = false
|
|
continue
|
|
}
|
|
append(&values, value)
|
|
}
|
|
if !ok {
|
|
delete(values)
|
|
return nil, false
|
|
}
|
|
return values[:], true
|
|
}
|
|
|
|
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_has_comptime_params(function) {
|
|
return nil, types.INVALID, false
|
|
}
|
|
if function.c_abi && types.is_valid(function.error) {
|
|
return nil, types.INVALID, false
|
|
}
|
|
if !function.c_abi && (!function.has_body || function.variadic) {
|
|
return nil, types.INVALID, false
|
|
}
|
|
result = function_channel_type(checker, function)
|
|
if !types.is_void(result) && !types.is_noreturn(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(checker, param.type, function.pkg, function.file)
|
|
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_type_for_template :: proc(
|
|
checker: ^Checker,
|
|
template: ast.Function_Id,
|
|
demanded: ^[dynamic]Spec_Id = nil,
|
|
demand_spec := true,
|
|
) -> (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, function.c_abi, function.variadic)
|
|
spec := INVALID_SPEC
|
|
if demanded == nil {
|
|
if demand_spec {
|
|
spec = ensure_spec(checker, template, params)
|
|
} else {
|
|
spec = find_spec(checker, template, params)
|
|
}
|
|
} else {
|
|
spec = find_spec(checker, template, params)
|
|
if spec == INVALID_SPEC {
|
|
spec = ensure_spec(checker, template, params)
|
|
}
|
|
mark_spec_demanded(checker, spec, demanded)
|
|
}
|
|
return function_type, spec, spec != INVALID_SPEC || !demand_spec
|
|
}
|
|
|
|
function_pointer_type_for_template :: proc(
|
|
checker: ^Checker,
|
|
template: ast.Function_Id,
|
|
demanded: ^[dynamic]Spec_Id = nil,
|
|
demand_spec := true,
|
|
) -> (types.Type, Spec_Id, bool) {
|
|
function_type, spec, ok := function_type_for_template(checker, template, demanded, demand_spec)
|
|
if !ok {
|
|
return types.INVALID, spec, false
|
|
}
|
|
return types.pointer(&checker.module.types, function_type, false, false), spec, true
|
|
}
|
|
|
|
function_expr_type_for_template :: proc(
|
|
checker: ^Checker,
|
|
template: ast.Function_Id,
|
|
expected: types.Type,
|
|
demanded: ^[dynamic]Spec_Id = nil,
|
|
) -> (types.Type, bool) {
|
|
function_type, _, ok := function_type_for_template(checker, template, demanded)
|
|
if !ok {
|
|
return types.INVALID, false
|
|
}
|
|
pointer_expected := expected
|
|
if types.is_optional(pointer_expected, &checker.module.types) {
|
|
pointer_expected = types.child_type(pointer_expected, &checker.module.types)
|
|
}
|
|
if types.can_coerce_function_pointer(function_type, pointer_expected, &checker.module.types) {
|
|
return pointer_expected, true
|
|
}
|
|
return function_type, true
|
|
}
|
|
|
|
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: [dynamic]ast.Expr_Id
|
|
stack.allocator = checker.allocator
|
|
defer delete(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 is_intrinsic_call(checker, expr, "typeinfo") {
|
|
for &import_item in checker.ast_module.imports {
|
|
if import_item.valid && import_item.path == "@std/meta" {
|
|
import_item.used = true
|
|
}
|
|
}
|
|
}
|
|
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, .Bit_Not, .Address, .Deref, .Field, .Unwrap, .Try, .Keyed, .Enum_Literal, .Cast:
|
|
append(&stack, expr.left)
|
|
case .Comptime:
|
|
if expr.left != ast.INVALID_EXPR {
|
|
append(&stack, expr.left)
|
|
}
|
|
mark_block_imports_used(checker, expr.body, file)
|
|
case .Catch:
|
|
append(&stack, expr.left)
|
|
if expr.right != ast.INVALID_EXPR {
|
|
append(&stack, expr.right)
|
|
}
|
|
mark_block_imports_used(checker, expr.body, file)
|
|
case .Function_Literal:
|
|
function_id := ast.Function_Id(u32(expr.integer))
|
|
if function_id != ast.INVALID_FUNCTION && int(function_id) < len(checker.ast_module.functions) {
|
|
function := checker.ast_module.functions[function_id]
|
|
mark_block_imports_used(checker, function.body, function.file)
|
|
}
|
|
case .Add, .Sub, .Mul, .Div, .Bit_And, .Bit_Or, .Bit_Xor, .Shift_Left, .Shift_Right,
|
|
.Shift_Left_Saturating, .Index, .Orelse, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
|
|
append(&stack, expr.left, expr.right)
|
|
case .Invalid, .Integer, .Float, .String, .Bool, .Null, .Unreachable, .Undefined, .Inference_Hole, .Type, .Name, .Anonymous_Struct_Type:
|
|
}
|
|
}
|
|
}
|
|
|
|
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]
|
|
#partial switch statement.kind {
|
|
case .Declaration, .Assignment, .Return, .Expression, .Yield:
|
|
mark_expr_imports_used(checker, statement.expr, file)
|
|
if statement.target != ast.INVALID_EXPR {
|
|
mark_expr_imports_used(checker, statement.target, file)
|
|
}
|
|
// A value-block declaration/assignment carries its block in `body`.
|
|
mark_block_imports_used(checker, statement.body, 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 .Block:
|
|
mark_block_imports_used(checker, statement.body, file)
|
|
case .Defer:
|
|
deferred := [1]ast.Stmt_Id{statement.update}
|
|
mark_block_imports_used(checker, deferred[:], file)
|
|
case .Match, .Match_Arm:
|
|
// `Match` carries the subject in `expr` and arms in `body`; each `Match_Arm`
|
|
// carries its patterns in `patterns` and the arm body in `body`.
|
|
mark_expr_imports_used(checker, statement.expr, file)
|
|
for pattern in statement.patterns {
|
|
mark_expr_imports_used(checker, pattern, file)
|
|
}
|
|
mark_block_imports_used(checker, statement.body, file)
|
|
case .Break, .Continue:
|
|
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
|
|
}
|
|
}
|
|
}
|
|
|
|
runtime_write_declaration_matches :: proc(checker: ^Checker, function: ast.Function) -> bool {
|
|
if function.variadic || len(function.params) != 3 || types.is_valid(function.error) {
|
|
return false
|
|
}
|
|
store := &checker.module.types
|
|
buffer := types.optional(store, types.pointer(store, types.ANYOPAQUE, false, true))
|
|
return type_from_syntax(checker, function.params[0].type, function.pkg, function.file) == types.C_INT &&
|
|
type_from_syntax(checker, function.params[1].type, function.pkg, function.file) == buffer &&
|
|
type_from_syntax(checker, function.params[2].type, function.pkg, function.file) == types.C_ULONG &&
|
|
type_from_syntax(checker, function.result, function.pkg, function.file) == types.C_LONG
|
|
}
|
|
|
|
validate_declarations :: proc(checker: ^Checker) {
|
|
for function, function_id in checker.ast_module.functions {
|
|
if len(function.unsupported_reason) > 0 {
|
|
continue
|
|
}
|
|
has_comptime := function_has_comptime_params(function)
|
|
signature_poisoned := function.diagnostic != source.INVALID_DIAGNOSTIC
|
|
locals: [dynamic]symbol.Id
|
|
locals.allocator = checker.allocator
|
|
comptime_prefix := 0
|
|
for param in function.params {
|
|
dependent := param.comptime_value && type_pattern_mentions_comptime(
|
|
checker, function, comptime_prefix, param.type,
|
|
)
|
|
param_type := types.INVALID
|
|
if param.comptime_value && !dependent || !has_comptime {
|
|
param_type = type_from_syntax(checker, param.type, function.pkg, function.file)
|
|
}
|
|
if param.comptime_value && !signature_poisoned {
|
|
if function.c_abi {
|
|
checker.template_diagnostics[function_id] = source.add(
|
|
checker.diagnostics,
|
|
param.span,
|
|
"comptime parameters require 'func', not 'c_func'",
|
|
)
|
|
}
|
|
if !is_type_metatype_syntax(checker, param.type) &&
|
|
!dependent && !is_comptime_value_type(checker, param_type) && param_type != types.RANGE {
|
|
checker.template_diagnostics[function_id] = source.addf(
|
|
checker.diagnostics,
|
|
param.span,
|
|
"comptime parameter '%s' requires type or a concrete value type",
|
|
symbol_text(checker, param.name),
|
|
)
|
|
}
|
|
} else if !has_comptime && !signature_poisoned && types.is_comptime_only(param_type, &checker.module.types) {
|
|
checker.template_diagnostics[function_id] = source.addf(
|
|
checker.diagnostics,
|
|
param.span,
|
|
"parameter '%s' has a comptime-only type; prefix it with '$'",
|
|
symbol_text(checker, param.name),
|
|
)
|
|
} else if !has_comptime && !signature_poisoned {
|
|
if diagnostic := add_unsupported_type_diagnostic(checker, param.span, param_type);
|
|
diagnostic != source.INVALID_DIAGNOSTIC {
|
|
checker.template_diagnostics[function_id] = diagnostic
|
|
continue
|
|
}
|
|
}
|
|
if param.comptime_value {
|
|
comptime_prefix += 1
|
|
}
|
|
if param.type == types.VOID {
|
|
source.add(
|
|
checker.diagnostics,
|
|
param.span,
|
|
"void is only valid as a function result type",
|
|
)
|
|
}
|
|
if !param.comptime_value && types.is_noreturn(param_type) {
|
|
checker.template_diagnostics[function_id] = source.add(
|
|
checker.diagnostics,
|
|
param.span,
|
|
"noreturn is only valid as a native function result type",
|
|
)
|
|
}
|
|
if param.name != checker.sink_symbol && contains_name(locals[:], param.name) {
|
|
source.addf(
|
|
checker.diagnostics,
|
|
param.span,
|
|
"duplicate parameter '%s'",
|
|
symbol_text(checker, param.name),
|
|
)
|
|
} else {
|
|
_ = add_shadow_diagnostic(
|
|
checker, param.span, param.name, "parameter",
|
|
function.pkg, function.file,
|
|
)
|
|
}
|
|
append(&locals, param.name)
|
|
if !has_comptime && !signature_poisoned && types.contains_c_struct_by_value(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 !has_comptime && !signature_poisoned {
|
|
result_type := type_from_syntax(checker, function.result, function.pkg, function.file)
|
|
if function.c_abi && types.is_noreturn(result_type) {
|
|
checker.template_diagnostics[function_id] = source.add(
|
|
checker.diagnostics,
|
|
function.span,
|
|
"noreturn is not supported across the C ABI",
|
|
)
|
|
}
|
|
if diagnostic := add_unsupported_type_diagnostic(checker, function.span, result_type);
|
|
diagnostic != source.INVALID_DIAGNOSTIC {
|
|
checker.template_diagnostics[function_id] = diagnostic
|
|
}
|
|
if types.contains_c_struct_by_value(result_type, &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 types.is_valid(function.error) && !signature_poisoned {
|
|
error_type := type_from_syntax(checker, function.error, function.pkg, function.file)
|
|
error_sum := types.is_enum(error_type, &checker.module.types) ||
|
|
types.is_tagged_union(error_type, &checker.module.types)
|
|
if function.c_abi {
|
|
checker.template_diagnostics[function_id] = source.add(
|
|
checker.diagnostics,
|
|
function.span,
|
|
"fallible functions must use 'func', not 'c_func'",
|
|
)
|
|
} else if !error_sum {
|
|
checker.template_diagnostics[function_id] = source.add(
|
|
checker.diagnostics,
|
|
function.span,
|
|
"fallible function error type must be a native enum or tagged union",
|
|
)
|
|
}
|
|
}
|
|
if !function.has_body && !function.c_abi && !signature_poisoned {
|
|
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) && !signature_poisoned {
|
|
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 && !signature_poisoned {
|
|
for param in function.params {
|
|
param_type := type_from_syntax(checker, param.type, function.pkg, function.file)
|
|
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(checker, function.result, function.pkg, function.file)
|
|
if add_unsupported_type_diagnostic(checker, function.span, result) == source.INVALID_DIAGNOSTIC &&
|
|
!types.is_noreturn(result) &&
|
|
!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",
|
|
)
|
|
}
|
|
external_name := function.link_name if len(function.link_name) > 0 else symbol_text(checker, function.name)
|
|
if external_name == "write" && !runtime_write_declaration_matches(checker, function) {
|
|
checker.template_diagnostics[function_id] = source.add(
|
|
checker.diagnostics,
|
|
function.span,
|
|
"external C function 'write' conflicts with the compiler runtime declaration",
|
|
)
|
|
}
|
|
}
|
|
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
|
|
}
|
|
}
|
|
}
|
|
|
|
init_record_field_inference :: proc(checker: ^Checker) {
|
|
store := &checker.module.types
|
|
for item in store.nodes {
|
|
if !(item.declared && (item.kind == .Struct || item.kind == .Union) &&
|
|
!item.c_layout && symbol.is_valid(symbol.Id(item.name))) {
|
|
continue
|
|
}
|
|
start := int(item.field_start)
|
|
end := start+int(item.field_count)
|
|
if start < 0 || end > len(store.fields) {
|
|
continue
|
|
}
|
|
for slot in start..<end {
|
|
if types.is_constraint(store.fields[slot].type) {
|
|
checker.record_field_constraints[slot] = store.fields[slot].type
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
is_inferred_record_field :: proc(checker: ^Checker, slot: int) -> bool {
|
|
return slot >= 0 && slot < len(checker.record_field_constraints) &&
|
|
types.is_constraint(checker.record_field_constraints[slot])
|
|
}
|
|
|
|
record_field_owner :: proc(checker: ^Checker, slot: int) -> (symbol.Id, symbol.Id, ast.Package_Id, bool) {
|
|
for item in checker.module.types.nodes {
|
|
if !(item.declared && (item.kind == .Struct || item.kind == .Union) &&
|
|
!item.c_layout && symbol.is_valid(symbol.Id(item.name))) {
|
|
continue
|
|
}
|
|
start := int(item.field_start)
|
|
if slot >= start && slot < start+int(item.field_count) &&
|
|
slot >= 0 && slot < len(checker.module.types.fields) {
|
|
return symbol.Id(item.name), symbol.Id(checker.module.types.fields[slot].name), ast.Package_Id(item.pkg), true
|
|
}
|
|
}
|
|
return symbol.INVALID, symbol.INVALID, ast.INVALID_PACKAGE, false
|
|
}
|
|
|
|
record_field_conflict :: proc(checker: ^Checker, slot: int, actual: types.Type, span: source.Span) {
|
|
if !is_inferred_record_field(checker, slot) ||
|
|
types.is_valid(checker.record_field_conflicts[slot]) {
|
|
return
|
|
}
|
|
checker.record_field_conflicts[slot] = actual
|
|
checker.record_field_conflict_spans[slot] = span
|
|
}
|
|
|
|
merge_record_field_default :: proc(checker: ^Checker, slot: int, candidate: types.Type, span: source.Span) -> bool {
|
|
if !is_inferred_record_field(checker, slot) || !is_runtime_type(checker, candidate) {
|
|
return false
|
|
}
|
|
constraint := checker.record_field_constraints[slot]
|
|
if !types.constraint_accepts(constraint, candidate, &checker.module.types) {
|
|
record_field_conflict(checker, slot, candidate, span)
|
|
return false
|
|
}
|
|
current := checker.record_field_defaults[slot]
|
|
if !is_runtime_type(checker, current) {
|
|
checker.record_field_defaults[slot] = candidate
|
|
checker.record_field_demands_dirty = true
|
|
return true
|
|
}
|
|
if types.equal(current, candidate) {
|
|
return false
|
|
}
|
|
merged := types.widest(current, candidate)
|
|
if types.is_concrete_scalar(merged) {
|
|
if !types.equal(current, merged) {
|
|
checker.record_field_defaults[slot] = merged
|
|
checker.record_field_demands_dirty = true
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
record_field_conflict(checker, slot, candidate, span)
|
|
return false
|
|
}
|
|
|
|
merge_record_field_demand :: proc(checker: ^Checker, slot: int, demand: types.Type, span: source.Span) -> bool {
|
|
if !is_inferred_record_field(checker, slot) || !is_runtime_type(checker, demand) {
|
|
return false
|
|
}
|
|
constraint := checker.record_field_constraints[slot]
|
|
if !types.constraint_accepts(constraint, demand, &checker.module.types) {
|
|
record_field_conflict(checker, slot, demand, span)
|
|
return false
|
|
}
|
|
current := checker.module.types.fields[slot].type
|
|
if types.is_constraint(current) {
|
|
checker.module.types.fields[slot].type = demand
|
|
checker.record_field_demands_dirty = true
|
|
return true
|
|
}
|
|
if types.equal(current, demand) {
|
|
return false
|
|
}
|
|
merged := types.widest(current, demand)
|
|
if types.is_concrete_scalar(merged) {
|
|
if !types.equal(current, merged) {
|
|
checker.module.types.fields[slot].type = merged
|
|
checker.record_field_demands_dirty = true
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
record_field_conflict(checker, slot, demand, span)
|
|
return false
|
|
}
|
|
|
|
record_field_expr_candidate :: proc(
|
|
checker: ^Checker,
|
|
slot: int,
|
|
expr_id: ast.Expr_Id,
|
|
inferred: types.Type,
|
|
locals: []Infer_Local,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
) -> bool {
|
|
if !is_inferred_record_field(checker, slot) || expr_id == ast.INVALID_EXPR {
|
|
return false
|
|
}
|
|
expr := checker.ast_module.exprs[expr_id]
|
|
constraint := checker.record_field_constraints[slot]
|
|
if !numeric_operand_is_open(checker, expr_id, locals, pkg, file) {
|
|
concrete := types.constraint_target(constraint, inferred, &checker.module.types)
|
|
if is_runtime_type(checker, concrete) {
|
|
return merge_record_field_demand(checker, slot, concrete, expr.span)
|
|
}
|
|
}
|
|
if constant := eval_integer_constant_in_context(checker, expr_id, pkg, file);
|
|
constant.kind == .Value {
|
|
candidate := constraint_integer_literal_type(constraint, constant.value)
|
|
if constraint == types.FLOAT {
|
|
candidate = types.F64 if fits_i64(constant.value) else types.INVALID
|
|
}
|
|
if types.is_valid(candidate) {
|
|
return merge_record_field_default(checker, slot, candidate, expr.span)
|
|
}
|
|
}
|
|
if is_float_constant_expr(checker, expr_id) {
|
|
return merge_record_field_default(checker, slot, types.F64, expr.span)
|
|
}
|
|
if numeric_operand_is_open(checker, expr_id, locals, pkg, file) {
|
|
candidate := inferred
|
|
if constraint == types.FLOAT && types.is_concrete_integer(candidate) {
|
|
candidate = types.F64
|
|
}
|
|
return merge_record_field_default(checker, slot, candidate, expr.span)
|
|
}
|
|
concrete := types.constraint_target(constraint, inferred, &checker.module.types)
|
|
if !is_runtime_type(checker, concrete) {
|
|
if is_runtime_type(checker, inferred) {
|
|
record_field_conflict(checker, slot, inferred, expr.span)
|
|
}
|
|
return false
|
|
}
|
|
return merge_record_field_demand(checker, slot, concrete, expr.span)
|
|
}
|
|
|
|
finalize_record_field_inference :: proc(checker: ^Checker) {
|
|
for constraint, slot in checker.record_field_constraints {
|
|
if !types.is_constraint(constraint) {
|
|
continue
|
|
}
|
|
record_name, field_name, record_pkg, ok := record_field_owner(checker, slot)
|
|
if !ok {
|
|
continue
|
|
}
|
|
current := checker.module.types.fields[slot].type
|
|
conflict := checker.record_field_conflicts[slot]
|
|
if types.is_valid(conflict) {
|
|
if is_runtime_type(checker, current) {
|
|
source.addf(
|
|
checker.diagnostics,
|
|
checker.record_field_conflict_spans[slot],
|
|
"conflicting types %s and %s for field '%s.%s' declared as '%s'",
|
|
types.name(current), types.name(conflict),
|
|
symbol_text(checker, record_name), symbol_text(checker, field_name),
|
|
types.name(constraint),
|
|
)
|
|
} else {
|
|
source.addf(
|
|
checker.diagnostics,
|
|
checker.record_field_conflict_spans[slot],
|
|
"type %s does not satisfy the '%s' constraint for field '%s.%s'",
|
|
types.name(conflict), types.name(constraint),
|
|
symbol_text(checker, record_name), symbol_text(checker, field_name),
|
|
)
|
|
}
|
|
} else if types.is_constraint(current) &&
|
|
!(int(record_pkg) < len(checker.poisoned_packages) && checker.poisoned_packages[record_pkg]) {
|
|
source.addf(
|
|
checker.diagnostics,
|
|
source.Span{},
|
|
"could not resolve the '%s' constraint for field '%s.%s'",
|
|
types.name(constraint),
|
|
symbol_text(checker, record_name), symbol_text(checker, field_name),
|
|
)
|
|
}
|
|
if types.is_constraint(current) {
|
|
checker.module.types.fields[slot].type = constraint_recovery_type(checker, constraint)
|
|
}
|
|
}
|
|
}
|
|
|
|
is_immutable_u8_slice :: proc(checker: ^Checker, value: types.Type) -> bool {
|
|
item, ok := types.node(&checker.module.types, value)
|
|
return ok && item.kind == .Slice && !item.mutable && item.child == types.U8
|
|
}
|
|
|
|
validate_meta_schema :: proc(checker: ^Checker) {
|
|
meta_package := ast.INVALID_PACKAGE
|
|
for import_item in checker.ast_module.imports {
|
|
if import_item.valid && import_item.path == "@std/meta" {
|
|
meta_package = import_item.target
|
|
break
|
|
}
|
|
}
|
|
if meta_package == ast.INVALID_PACKAGE {
|
|
return
|
|
}
|
|
find := proc(checker: ^Checker, pkg: ast.Package_Id, name: string) -> types.Type {
|
|
return types.find_named(&checker.module.types, u32(pkg), u32(symbol.intern(checker.symbols, name)))
|
|
}
|
|
field_info := find(checker, meta_package, "FieldInfo")
|
|
array_info := find(checker, meta_package, "ArrayInfo")
|
|
record_info := find(checker, meta_package, "RecordInfo")
|
|
enum_info := find(checker, meta_package, "EnumInfo")
|
|
type_info := find(checker, meta_package, "TypeInfo")
|
|
layout := find(checker, meta_package, "Layout")
|
|
valid := types.is_valid(array_info) && types.is_valid(field_info) && types.is_valid(record_info) && types.is_valid(enum_info) &&
|
|
types.is_valid(type_info) && types.is_valid(layout)
|
|
layout_item, layout_ok := types.node(&checker.module.types, layout)
|
|
layout_members := types.enum_members_for(&checker.module.types, layout)
|
|
valid = valid && layout_ok && layout_item.kind == .Enum && len(layout_members) == 2
|
|
if valid {
|
|
valid = symbol_text(checker, symbol.Id(layout_members[0].name)) == "auto" &&
|
|
symbol_text(checker, symbol.Id(layout_members[1].name)) == "c"
|
|
}
|
|
array_item, array_ok := types.node(&checker.module.types, array_info)
|
|
field_item, field_ok := types.node(&checker.module.types, field_info)
|
|
record_item, record_ok := types.node(&checker.module.types, record_info)
|
|
enum_item, enum_ok := types.node(&checker.module.types, enum_info)
|
|
valid = valid && array_ok && array_item.kind == .Struct && !array_item.tuple && !array_item.c_layout &&
|
|
field_ok && field_item.kind == .Struct && !field_item.tuple && !field_item.c_layout &&
|
|
record_ok && record_item.kind == .Struct && !record_item.tuple && !record_item.c_layout &&
|
|
enum_ok && enum_item.kind == .Struct && !enum_item.tuple && !enum_item.c_layout
|
|
array_fields := types.fields_for(&checker.module.types, array_info)
|
|
valid = valid && len(array_fields) == 2
|
|
if valid {
|
|
valid = symbol_text(checker, symbol.Id(array_fields[0].name)) == "child" &&
|
|
is_type_metatype_syntax(checker, array_fields[0].type) &&
|
|
symbol_text(checker, symbol.Id(array_fields[1].name)) == "len" &&
|
|
types.equal(array_fields[1].type, types.USIZE)
|
|
}
|
|
field_fields := types.fields_for(&checker.module.types, field_info)
|
|
valid = valid && len(field_fields) == 3
|
|
if valid {
|
|
valid = symbol_text(checker, symbol.Id(field_fields[0].name)) == "name" &&
|
|
is_immutable_u8_slice(checker, field_fields[0].type) &&
|
|
symbol_text(checker, symbol.Id(field_fields[1].name)) == "type" &&
|
|
is_type_metatype_syntax(checker, field_fields[1].type) &&
|
|
symbol_text(checker, symbol.Id(field_fields[2].name)) == "index" &&
|
|
types.equal(field_fields[2].type, types.USIZE)
|
|
}
|
|
enum_fields := types.fields_for(&checker.module.types, enum_info)
|
|
valid = valid && len(enum_fields) == 1
|
|
if valid {
|
|
fields_item, fields_ok := types.node(&checker.module.types, enum_fields[0].type)
|
|
valid = symbol_text(checker, symbol.Id(enum_fields[0].name)) == "fields" && fields_ok &&
|
|
fields_item.kind == .Slice && !fields_item.mutable && types.equal(fields_item.child, field_info)
|
|
}
|
|
record_fields := types.fields_for(&checker.module.types, record_info)
|
|
valid = valid && len(record_fields) == 4
|
|
if valid {
|
|
name_type := types.child_type(record_fields[0].type, &checker.module.types)
|
|
fields_item, fields_ok := types.node(&checker.module.types, record_fields[1].type)
|
|
valid = symbol_text(checker, symbol.Id(record_fields[0].name)) == "name" &&
|
|
types.is_optional(record_fields[0].type, &checker.module.types) && is_immutable_u8_slice(checker, name_type) &&
|
|
symbol_text(checker, symbol.Id(record_fields[1].name)) == "fields" && fields_ok &&
|
|
fields_item.kind == .Slice && !fields_item.mutable && types.equal(fields_item.child, field_info) &&
|
|
symbol_text(checker, symbol.Id(record_fields[2].name)) == "is_tuple" && types.is_bool(record_fields[2].type) &&
|
|
symbol_text(checker, symbol.Id(record_fields[3].name)) == "layout" && types.equal(record_fields[3].type, layout)
|
|
}
|
|
type_item, type_ok := types.node(&checker.module.types, type_info)
|
|
type_fields := types.fields_for(&checker.module.types, type_info)
|
|
expected_tags := []string{
|
|
"invalid", "void", "noreturn", "anyopaque", "bool", "integer", "float", "array", "pointer", "slice",
|
|
"range", "optional", "function", "enum", "record", "union", "fallible", "distinct",
|
|
}
|
|
valid = valid && type_ok && type_item.kind == .Union &&
|
|
types.is_enum(type_item.child, &checker.module.types) && len(type_fields) == len(expected_tags)
|
|
if valid {
|
|
for tag, index in expected_tags {
|
|
field := type_fields[index]
|
|
if symbol_text(checker, symbol.Id(field.name)) != tag ||
|
|
(tag == "array" && !types.equal(field.type, array_info)) ||
|
|
(tag == "record" && !types.equal(field.type, record_info)) ||
|
|
(tag == "enum" && !types.equal(field.type, enum_info)) ||
|
|
(tag != "array" && tag != "record" && tag != "enum" && !types.is_void(field.type)) {
|
|
valid = false
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if !valid {
|
|
source.add(checker.diagnostics, source.Span{}, "@std/meta declarations do not match the compiler reflection ABI")
|
|
}
|
|
}
|
|
|
|
validate_type_nodes :: proc(checker: ^Checker) {
|
|
node_count := len(checker.module.types.nodes)
|
|
for index in 0..<node_count {
|
|
item := checker.module.types.nodes[index]
|
|
if item.kind != .Struct && item.kind != .Union {
|
|
continue
|
|
}
|
|
start := int(item.field_start)
|
|
end := start+int(item.field_count)
|
|
if start < 0 || end > len(checker.module.types.fields) {
|
|
continue
|
|
}
|
|
for slot in start..<end {
|
|
resolved := type_from_syntax(
|
|
checker, checker.module.types.fields[slot].type,
|
|
ast.Package_Id(item.pkg), ast.File_Id(item.file),
|
|
)
|
|
checker.module.types.fields[slot].type = resolved
|
|
}
|
|
}
|
|
validate_meta_schema(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 {
|
|
meta_package := false
|
|
for import_item in checker.ast_module.imports {
|
|
if import_item.valid && import_item.target == ast.Package_Id(item.pkg) && import_item.path == "@std/meta" {
|
|
meta_package = true
|
|
break
|
|
}
|
|
}
|
|
item_name := symbol_text(checker, symbol.Id(item.name))
|
|
comptime_meta := meta_package &&
|
|
(item_name == "ArrayInfo" || item_name == "FieldInfo" || item_name == "RecordInfo" || item_name == "EnumInfo" || item_name == "TypeInfo")
|
|
if item.c_layout && !item.opaque && item.field_count == 0 {
|
|
source.add(
|
|
checker.diagnostics,
|
|
source.Span{},
|
|
"c_struct definitions require at least one field",
|
|
)
|
|
}
|
|
// A tagged union may carry `void`-payload variants (`.quit void`): the
|
|
// variant has no runtime value, only a tag. Allowed only here, not for
|
|
// structs, untagged unions, or c_structs.
|
|
tagged_union := item.kind == .Union && types.is_enum(item.child, &checker.module.types)
|
|
for field, field_index in types.fields_for(&checker.module.types, id) {
|
|
field_slot := int(item.field_start)+field_index
|
|
if tagged_union && types.is_void(field.type) {
|
|
// void variant: no payload to validate.
|
|
} else if is_inferred_record_field(checker, field_slot) {
|
|
// Direct constraints in named native records are validated after the
|
|
// program-wide inference fixpoint. C, nested, and anonymous fields do
|
|
// not enter this state and retain the existing validation below.
|
|
} else if comptime_meta {
|
|
// Reflection metadata is compile-time-only and may contain `type`.
|
|
} else if item.c_layout && !types.is_runtime_value(field.type, &checker.module.types) {
|
|
source.add(
|
|
checker.diagnostics,
|
|
source.Span{},
|
|
"c_struct fields must have C-layout-compatible types",
|
|
)
|
|
} else if !types.is_runtime_value(field.type, &checker.module.types) &&
|
|
!is_comptime_value_type(checker, field.type) {
|
|
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",
|
|
)
|
|
}
|
|
}
|
|
// A tagged union stores a hidden runtime tag enum keyed by global variant IDs.
|
|
// An explicit `union(Enum)` keeps that declared enum only for validation.
|
|
if item.kind == .Union && types.is_valid(item.child) {
|
|
declared_tag := types.union_declared_tag_enum(id, &checker.module.types)
|
|
if !types.is_enum(declared_tag, &checker.module.types) {
|
|
source.add(checker.diagnostics, source.Span{}, "a tagged union's tag must be an enum")
|
|
} else {
|
|
for field in types.fields_for(&checker.module.types, id) {
|
|
if _, ok := find_enum_member(checker, declared_tag, symbol.Id(field.name)); !ok {
|
|
source.addf(
|
|
checker.diagnostics,
|
|
source.Span{},
|
|
"union variant '%s' is not a member of the tag enum",
|
|
symbol_text(checker, symbol.Id(field.name)),
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if item.kind == .Function {
|
|
unresolved := type_contains_unresolved_named(checker, item.child)
|
|
for param in types.params_for(&checker.module.types, id) {
|
|
unresolved = unresolved || type_contains_unresolved_named(checker, param.type)
|
|
}
|
|
if unresolved {
|
|
continue
|
|
}
|
|
if item.c_abi {
|
|
if types.kind(item.child, &checker.module.types) == .Fallible {
|
|
source.add(checker.diagnostics, source.Span{}, "c_func pointer results cannot be fallible")
|
|
}
|
|
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{}, "c_func 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{}, "c_func pointer results must be concrete C signature types or void")
|
|
}
|
|
} else {
|
|
if item.variadic {
|
|
source.add(checker.diagnostics, source.Span{}, "native function pointer types do not support variadic parameters")
|
|
}
|
|
for param in types.params_for(&checker.module.types, id) {
|
|
if types.is_void(param.type) || !is_runtime_type(checker, param.type) {
|
|
source.add(checker.diagnostics, source.Span{}, "native function pointer parameters must be concrete runtime types")
|
|
}
|
|
}
|
|
if !types.is_void(item.child) && !types.is_noreturn(item.child) && !is_runtime_type(checker, item.child) {
|
|
source.add(checker.diagnostics, source.Span{}, "native function pointer results must be concrete runtime types, void, or noreturn")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
find_infer_local :: proc(locals: []Infer_Local, name: symbol.Id) -> types.Type {
|
|
if index, ok := find_infer_local_index(locals, name); ok {
|
|
return locals[index].type
|
|
}
|
|
return types.INVALID
|
|
}
|
|
|
|
find_infer_local_index :: proc(locals: []Infer_Local, name: symbol.Id) -> (int, bool) {
|
|
for index := len(locals) - 1; index >= 0; index -= 1 {
|
|
if locals[index].name == name {
|
|
return index, true
|
|
}
|
|
}
|
|
return -1, false
|
|
}
|
|
|
|
find_spec :: proc(
|
|
checker: ^Checker,
|
|
template: ast.Function_Id,
|
|
actual_args: []types.Type,
|
|
comptime_values: []Comptime_Value = nil,
|
|
) -> Spec_Id {
|
|
function := checker.ast_module.functions[template]
|
|
for spec, index in checker.specs {
|
|
if spec.template != template || len(spec.args) != runtime_param_count(function) ||
|
|
!comptime_values_equal(spec.comptime_values, comptime_values) {
|
|
continue
|
|
}
|
|
matches := true
|
|
runtime_index := 0
|
|
previous_comptime := checker.current_comptime_values
|
|
checker.current_comptime_values = comptime_values
|
|
for param, param_index in function.params {
|
|
if param.comptime_value {
|
|
continue
|
|
}
|
|
actual := types.INVALID
|
|
if param_index < len(actual_args) {
|
|
actual = actual_args[param_index]
|
|
}
|
|
if runtime_index >= len(spec.args) ||
|
|
!types.equal(spec.args[runtime_index], specialized_param_type(checker, param.type, actual, function.pkg, function.file)) {
|
|
matches = false
|
|
break
|
|
}
|
|
runtime_index += 1
|
|
}
|
|
checker.current_comptime_values = previous_comptime
|
|
if matches {
|
|
return spec_id(index)
|
|
}
|
|
}
|
|
return INVALID_SPEC
|
|
}
|
|
|
|
// specialized_param_type maps a parameter's declared type to its monomorphized
|
|
// type for a given actual argument. A constraint param (`int`/`uint`/`float`/`range`)
|
|
// resolves to the actual's family member (INVALID if out of family), so a call
|
|
// passing an out-of-family argument fails to specialize and is rejected.
|
|
specialized_param_type :: proc(
|
|
checker: ^Checker,
|
|
syntax: ast.Type_Syntax,
|
|
actual: types.Type,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
) -> types.Type {
|
|
declared := type_from_syntax(checker, syntax, pkg, file)
|
|
if types.is_constraint(declared) {
|
|
return types.constraint_target(declared, actual, &checker.module.types)
|
|
}
|
|
return declared
|
|
}
|
|
|
|
can_specialize :: proc(
|
|
checker: ^Checker,
|
|
function: ast.Function,
|
|
actual_args: []types.Type,
|
|
comptime_values: []Comptime_Value = nil,
|
|
) -> bool {
|
|
previous_comptime := checker.current_comptime_values
|
|
checker.current_comptime_values = comptime_values
|
|
defer checker.current_comptime_values = previous_comptime
|
|
for param, index in function.params {
|
|
if param.comptime_value {
|
|
continue
|
|
}
|
|
actual := types.INVALID
|
|
if index < len(actual_args) {
|
|
actual = actual_args[index]
|
|
}
|
|
if !is_runtime_type(checker, specialized_param_type(checker, param.type, actual, function.pkg, function.file)) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
ensure_spec :: proc(
|
|
checker: ^Checker,
|
|
template: ast.Function_Id,
|
|
actual_args: []types.Type,
|
|
comptime_values: []Comptime_Value = nil,
|
|
) -> Spec_Id {
|
|
if existing := find_spec(checker, template, actual_args, comptime_values); existing != INVALID_SPEC {
|
|
return existing
|
|
}
|
|
function := checker.ast_module.functions[template]
|
|
previous_comptime := checker.current_comptime_values
|
|
checker.current_comptime_values = comptime_values
|
|
defer checker.current_comptime_values = previous_comptime
|
|
signature: [dynamic]types.Type
|
|
signature.allocator = checker.allocator
|
|
for param, index in function.params {
|
|
if param.comptime_value {
|
|
continue
|
|
}
|
|
actual := types.INVALID
|
|
if index < len(actual_args) {
|
|
actual = actual_args[index]
|
|
}
|
|
append(&signature, specialized_param_type(checker, param.type, actual, function.pkg, function.file))
|
|
}
|
|
comptime_signature: [dynamic]Comptime_Value
|
|
comptime_signature.allocator = checker.allocator
|
|
append(&comptime_signature, ..comptime_values)
|
|
result := function_channel_type(checker, function)
|
|
if function.pkg == 0 && function.name == checker.main_symbol && function.result == types.INT &&
|
|
!types.is_valid(function.error) {
|
|
result = types.I32
|
|
}
|
|
index := spec_id(len(checker.specs))
|
|
append(
|
|
&checker.specs,
|
|
Spec{
|
|
template = template,
|
|
args = signature[:],
|
|
comptime_values = comptime_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,
|
|
expected: types.Type,
|
|
stage: u8,
|
|
left: types.Type,
|
|
arg_index: int,
|
|
arg_mode: Call_Argument_Mode,
|
|
prefix: int,
|
|
mapping: []int,
|
|
args: []types.Type,
|
|
template: ast.Function_Id,
|
|
}
|
|
|
|
merge_inferred_test_error :: proc(checker: ^Checker, incoming: types.Type) {
|
|
current := checker.inferred_test_error
|
|
if current == nil || !types.is_valid(incoming) ||
|
|
types.can_sum_widen(incoming, current^, &checker.module.types) {
|
|
return
|
|
}
|
|
if merged, err := types.compose_sum(&checker.module.types, current^, incoming); err == .None {
|
|
current^ = merged
|
|
}
|
|
}
|
|
|
|
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,
|
|
local_types: []types.Type = nil,
|
|
expected := types.INVALID,
|
|
) -> 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, local_types, expected)
|
|
delete(checker.infer_stack)
|
|
checker.infer_stack = outer
|
|
return result
|
|
}
|
|
|
|
infer_division_builtin :: proc(
|
|
checker: ^Checker,
|
|
expr: ast.Expr,
|
|
locals: []Infer_Local,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
demanded: ^[dynamic]Spec_Id,
|
|
local_types: []types.Type,
|
|
expected: types.Type,
|
|
) -> types.Type {
|
|
if len(expr.args) != 2 {
|
|
return types.INVALID
|
|
}
|
|
hint := expected if types.is_concrete_scalar(expected) && !types.is_bool(expected) else types.INVALID
|
|
left_const := is_numeric_constant_expr(checker, expr.args[0])
|
|
right_const := is_numeric_constant_expr(checker, expr.args[1])
|
|
left, right := types.INVALID, types.INVALID
|
|
if left_const && !right_const && !types.is_valid(hint) {
|
|
right = infer_nested_expr(checker, expr.args[1], locals, pkg, file, demanded, local_types)
|
|
left = infer_nested_expr(checker, expr.args[0], locals, pkg, file, demanded, local_types, right)
|
|
} else {
|
|
left = infer_nested_expr(checker, expr.args[0], locals, pkg, file, demanded, local_types, hint)
|
|
right_hint := hint if types.is_valid(hint) else left
|
|
right = infer_nested_expr(checker, expr.args[1], locals, pkg, file, demanded, local_types, right_hint)
|
|
}
|
|
result := types.widest(left, right)
|
|
return result if types.is_concrete_scalar(result) && !types.is_bool(result) else types.INVALID
|
|
}
|
|
|
|
infer_compound_expr :: proc(
|
|
checker: ^Checker,
|
|
expr: ast.Expr,
|
|
locals: []Infer_Local,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
demanded: ^[dynamic]Spec_Id,
|
|
local_types: []types.Type = nil,
|
|
expected := types.INVALID,
|
|
) -> types.Type {
|
|
store := &checker.module.types
|
|
#partial switch expr.kind {
|
|
case .Comptime:
|
|
return infer_comptime_expr_type(checker, expr, pkg, file, demanded)
|
|
case .Bool:
|
|
return types.BOOL
|
|
case .Not:
|
|
_ = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
|
return types.BOOL
|
|
case .Bit_Not:
|
|
hint := expected if types.is_concrete_integer(expected) else types.INVALID
|
|
operand := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types, hint)
|
|
return operand if types.is_concrete_integer(operand) else types.INVALID
|
|
case .Bit_And, .Bit_Or, .Bit_Xor:
|
|
hint := expected if types.is_concrete_integer(expected) else types.INVALID
|
|
left_const := is_numeric_constant_expr(checker, expr.left)
|
|
right_const := is_numeric_constant_expr(checker, expr.right)
|
|
left, right := types.INVALID, types.INVALID
|
|
if left_const && !right_const && !types.is_valid(hint) {
|
|
right = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded, local_types)
|
|
left = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types, right)
|
|
} else {
|
|
left = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types, hint)
|
|
right = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded, local_types, hint if types.is_valid(hint) else left)
|
|
}
|
|
result := types.widest(left, right)
|
|
return result if types.is_concrete_integer(result) else types.INVALID
|
|
case .Shift_Left, .Shift_Right, .Shift_Left_Saturating:
|
|
hint := expected if types.is_concrete_integer(expected) else types.INVALID
|
|
left := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types, hint)
|
|
right := infer_nested_expr(checker, expr.right, locals, pkg, file, demanded, local_types, types.U64)
|
|
return left if types.is_concrete_integer(left) && types.is_unsigned(right, checker.target) else types.INVALID
|
|
case .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or:
|
|
left_expr := checker.ast_module.exprs[expr.left]
|
|
right_expr := checker.ast_module.exprs[expr.right]
|
|
if left_expr.kind == .Null && right_expr.kind != .Null {
|
|
right := infer_nested_expr(checker, expr.right, locals, pkg, file, demanded, local_types)
|
|
_ = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types, right)
|
|
} else if right_expr.kind == .Null && left_expr.kind != .Null {
|
|
left := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
|
_ = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded, local_types, left)
|
|
} else {
|
|
_ = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
|
_ = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded, local_types)
|
|
}
|
|
return types.BOOL
|
|
case .Range:
|
|
left := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
|
right := infer_nested_expr(checker, expr.right, locals, pkg, file, demanded, local_types)
|
|
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, local_types)
|
|
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 .Null:
|
|
return expected if types.is_optional(expected, store) else types.INVALID
|
|
case .Unreachable:
|
|
return types.NORETURN
|
|
case .Undefined:
|
|
return types.INVALID
|
|
case .Enum_Literal:
|
|
if expr.left != ast.INVALID_EXPR {
|
|
_ = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
|
}
|
|
return types.INVALID
|
|
case .Cast:
|
|
_ = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
|
return type_from_syntax(checker, expr.type, pkg, file)
|
|
case .Address:
|
|
child := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
|
return types.pointer(store, child, false, false)
|
|
case .Deref:
|
|
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
|
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, local_types)
|
|
_ = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded, local_types)
|
|
item, ok := types.container(value, store)
|
|
return item.child if ok else types.INVALID
|
|
case .Slice:
|
|
text, comptime_ok := comptime_slice_bytes(checker, expr, pkg, file)
|
|
if comptime_ok {
|
|
string_id := intern_comptime_string(checker, text)
|
|
return string_literal_type(checker, string_id)
|
|
}
|
|
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
|
item, ok := types.container(value, store)
|
|
if !ok || (item.kind == .Pointer && expr.args[1] == ast.INVALID_EXPR) {
|
|
return types.INVALID
|
|
}
|
|
for bound in expr.args {
|
|
if bound != ast.INVALID_EXPR {
|
|
_ = infer_nested_expr(checker, bound, locals, pkg, file, demanded, local_types)
|
|
}
|
|
}
|
|
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, local_types)
|
|
if !symbol.is_valid(expr.name) {
|
|
return field_type_from_value(checker, expr, value)
|
|
}
|
|
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)
|
|
}
|
|
slot, field, ok := find_struct_field_slot(checker, value, expr.name)
|
|
if ok && is_inferred_record_field(checker, slot) && is_runtime_type(checker, expected) {
|
|
_ = merge_record_field_demand(checker, slot, expected, expr.span)
|
|
field = checker.module.types.fields[slot]
|
|
}
|
|
return field.type if ok else types.INVALID
|
|
case .Unwrap:
|
|
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
|
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, local_types)
|
|
_ = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded, local_types)
|
|
return types.child_type(value, store) if types.is_optional(value, store) else types.INVALID
|
|
case .Try:
|
|
left_expected := expected if checker.ast_module.exprs[expr.left].kind == .Call else types.INVALID
|
|
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types, left_expected)
|
|
merge_inferred_test_error(checker, types.fallible_error(value, store))
|
|
return types.fallible_success(value, store) if types.kind(value, store) == .Fallible else types.INVALID
|
|
case .Catch:
|
|
left_expected := expected if checker.ast_module.exprs[expr.left].kind == .Call else types.INVALID
|
|
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types, left_expected)
|
|
success := types.fallible_success(value, store)
|
|
error_type := types.fallible_error(value, store)
|
|
fallback_locals: [dynamic]Infer_Local
|
|
fallback_locals.allocator = checker.allocator
|
|
defer delete(fallback_locals)
|
|
append(&fallback_locals, ..locals)
|
|
if symbol.is_valid(expr.name) && expr.name != checker.sink_symbol && types.is_valid(error_type) {
|
|
append(&fallback_locals, Infer_Local{name=expr.name, type=error_type, declared=error_type, statement=ast.INVALID_STMT})
|
|
}
|
|
if expr.right != ast.INVALID_EXPR {
|
|
fallback := infer_nested_expr(checker, expr.right, fallback_locals[:], pkg, file, demanded, local_types)
|
|
if types.is_valid(success) && types.is_valid(fallback) && !types.equal(success, fallback) {
|
|
return types.widest(success, fallback)
|
|
}
|
|
return success if types.is_valid(success) else fallback
|
|
}
|
|
infer_statements(checker, expr.body, &fallback_locals, local_types, pkg, file, demanded, &success, success)
|
|
return success
|
|
case .Struct_Literal:
|
|
value := types.INVALID
|
|
if expr.left != ast.INVALID_EXPR {
|
|
value, _ = resolve_type_argument(checker, expr.left, pkg, file)
|
|
} else if symbol.is_valid(expr.name) {
|
|
target_pkg, available := expr_package(checker, expr, pkg, file)
|
|
value = types.find_named(store, u32(target_pkg), u32(expr.name), file=u32(expr_lookup_file(expr, file))) if available else types.INVALID
|
|
} else {
|
|
value = expected
|
|
}
|
|
value = types.resolve_alias(value, store)
|
|
tuple_literal := expr.tuple
|
|
if tuple_literal && len(expr.args) == 0 && types.is_valid(value) {
|
|
item, ok := types.node(store, value)
|
|
tuple_literal = !ok || item.kind != .Struct || item.tuple
|
|
}
|
|
if tuple_literal {
|
|
if !types.is_valid(value) {
|
|
fields := make([]types.Field, len(expr.args), checker.allocator)
|
|
defer delete(fields, checker.allocator)
|
|
for arg, index in expr.args {
|
|
fields[index].type = infer_nested_expr(checker, arg, locals, pkg, file, demanded, local_types)
|
|
}
|
|
return types.struct_anonymous(store, fields, true)
|
|
}
|
|
item, ok := types.node(store, value)
|
|
if !ok || item.kind != .Struct || !item.tuple {
|
|
for arg in expr.args {
|
|
_ = infer_nested_expr(checker, arg, locals, pkg, file, demanded, local_types)
|
|
}
|
|
return types.INVALID
|
|
}
|
|
fields := types.fields_for(store, value)
|
|
for arg, index in expr.args {
|
|
expected_field := fields[index].type if index < len(fields) else types.INVALID
|
|
_ = infer_nested_expr(checker, arg, locals, pkg, file, demanded, local_types, expected_field)
|
|
}
|
|
return value
|
|
}
|
|
if !types.is_valid(value) {
|
|
fields := make([]types.Field, len(expr.args), checker.allocator)
|
|
defer delete(fields, checker.allocator)
|
|
valid := true
|
|
for keyed, index in expr.args {
|
|
keyed_expr := checker.ast_module.exprs[keyed]
|
|
fields[index].name = u32(keyed_expr.name)
|
|
if keyed_expr.left == ast.INVALID_EXPR {
|
|
valid = false
|
|
continue
|
|
}
|
|
for previous in fields[:index] {
|
|
valid = valid && previous.name != fields[index].name
|
|
}
|
|
fields[index].type = infer_nested_expr(
|
|
checker, keyed_expr.left, locals, pkg, file, demanded, local_types,
|
|
)
|
|
}
|
|
return types.struct_anonymous(store, fields) if valid else types.INVALID
|
|
}
|
|
fields := types.fields_for(store, value)
|
|
item, item_ok := types.node(store, value)
|
|
initialized := make([]bool, len(fields), checker.allocator)
|
|
defer delete(initialized, checker.allocator)
|
|
for keyed in expr.args {
|
|
keyed_expr := checker.ast_module.exprs[keyed]
|
|
if keyed_expr.left == ast.INVALID_EXPR {
|
|
continue
|
|
}
|
|
slot, field, ok := find_struct_field_slot(checker, value, keyed_expr.name)
|
|
if ok && item_ok {
|
|
initialized[slot-int(item.field_start)] = true
|
|
}
|
|
if !ok {
|
|
_ = infer_nested_expr(checker, keyed_expr.left, locals, pkg, file, demanded, local_types)
|
|
continue
|
|
}
|
|
field_expected := field.type if is_runtime_type(checker, field.type) else types.INVALID
|
|
actual := infer_nested_expr(checker, keyed_expr.left, locals, pkg, file, demanded, local_types, field_expected)
|
|
if !is_inferred_record_field(checker, slot) {
|
|
continue
|
|
}
|
|
_ = record_field_expr_candidate(checker, slot, keyed_expr.left, actual, locals, pkg, file)
|
|
if is_runtime_type(checker, checker.module.types.fields[slot].type) {
|
|
_ = record_demand(
|
|
checker, keyed_expr.left, checker.module.types.fields[slot].type,
|
|
locals, local_types, pkg, file,
|
|
)
|
|
}
|
|
}
|
|
if item_ok && item.kind == .Struct && !item.tuple {
|
|
for field, index in fields {
|
|
if initialized[index] {
|
|
continue
|
|
}
|
|
if field_default, default_values, ok := find_struct_field_default(checker, value, symbol.Id(field.name)); ok {
|
|
if field_default.static_value != INVALID_CT_VALUE {
|
|
continue
|
|
}
|
|
previous := checker.current_comptime_values
|
|
checker.current_comptime_values = default_values
|
|
_ = infer_nested_expr(
|
|
checker, field_default.expr, nil, field_default.pkg, field_default.file,
|
|
demanded, expected=field.type,
|
|
)
|
|
checker.current_comptime_values = previous
|
|
}
|
|
}
|
|
}
|
|
return value
|
|
case .Keyed:
|
|
return infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
|
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,
|
|
local_types: []types.Type = nil,
|
|
expected := types.INVALID,
|
|
) -> types.Type {
|
|
stack := checker.infer_stack
|
|
checker.infer_stack = nil
|
|
clear_dynamic_array(&stack)
|
|
defer {
|
|
for frame in stack {
|
|
delete(frame.args, checker.allocator)
|
|
delete(frame.mapping, checker.allocator)
|
|
}
|
|
clear_dynamic_array(&stack)
|
|
if checker.infer_stack == nil {
|
|
checker.infer_stack = stack
|
|
} else {
|
|
delete(stack)
|
|
}
|
|
}
|
|
append(&stack, Infer_Frame{expr=expr_id, expected=expected, 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 := Constant{}
|
|
_, static_name := current_static_binding(checker, expr.name)
|
|
if expr.kind != .Name || symbol.is_valid(expr.qualifier) || !static_name {
|
|
constant = eval_constant(checker, frame.expr)
|
|
}
|
|
if constant.kind == .Overflow || constant.kind == .Div_By_Zero {
|
|
last = types.I64
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
if constant.kind == .Value {
|
|
last = constraint_integer_literal_type(frame.expected, constant.value)
|
|
if !types.is_valid(last) {
|
|
last = types.I64
|
|
}
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
switch expr.kind {
|
|
case .Invalid:
|
|
last = types.INVALID
|
|
_ = pop(&stack)
|
|
case .Inference_Hole:
|
|
last = types.INVALID
|
|
_ = pop(&stack)
|
|
case .Type:
|
|
last = types.INVALID
|
|
_ = pop(&stack)
|
|
case .Anonymous_Struct_Type:
|
|
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, .Null, .Unreachable, .Undefined, .Address, .Deref, .Index, .Slice,
|
|
.Field, .Unwrap, .Orelse, .Try, .Catch, .Struct_Literal, .Keyed, .Enum_Literal, .Cast,
|
|
.Comptime, .Bool, .Not, .Bit_Not, .Bit_And, .Bit_Or, .Bit_Xor, .Shift_Left,
|
|
.Shift_Right, .Shift_Left_Saturating, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
|
|
last = infer_compound_expr(checker, expr, locals, pkg, file, demanded, local_types, frame.expected)
|
|
_ = pop(&stack)
|
|
case .Function_Literal:
|
|
template := ast.Function_Id(u32(expr.integer))
|
|
function_type, ok := function_expr_type_for_template(checker, template, frame.expected, demanded)
|
|
last = function_type if ok else types.INVALID
|
|
_ = 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) {
|
|
slot, field, ok := find_struct_field_slot(checker, base_type, expr.name)
|
|
if ok {
|
|
if is_inferred_record_field(checker, slot) && is_runtime_type(checker, frame.expected) {
|
|
_ = merge_record_field_demand(checker, slot, frame.expected, expr.span)
|
|
field = checker.module.types.fields[slot]
|
|
}
|
|
last = field.type
|
|
}
|
|
}
|
|
}
|
|
if !types.is_valid(last) && symbol.is_valid(expr.qualifier) &&
|
|
find_import(checker, file, expr.qualifier) == ast.INVALID_IMPORT {
|
|
if value, ok := current_comptime_value(checker, expr.qualifier);
|
|
ok && value.kind == .Static {
|
|
if _, field, found := find_struct_field(checker, value.type, expr.name); found {
|
|
last = field.type
|
|
}
|
|
}
|
|
if value, ok := current_static_binding(checker, expr.qualifier); ok {
|
|
if _, field, found := find_struct_field(checker, value.type, expr.name); found {
|
|
last = field.type
|
|
}
|
|
}
|
|
if global := find_global(checker, expr.qualifier, pkg, file); global != ast.INVALID_GLOBAL {
|
|
base_type := checker.global_types[global]
|
|
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) {
|
|
slot, field, ok := find_struct_field_slot(checker, base_type, expr.name)
|
|
if ok {
|
|
if is_inferred_record_field(checker, slot) && is_runtime_type(checker, frame.expected) {
|
|
_ = merge_record_field_demand(checker, slot, frame.expected, expr.span)
|
|
field = checker.module.types.fields[slot]
|
|
}
|
|
last = field.type
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if !types.is_valid(last) {
|
|
if !symbol.is_valid(expr.qualifier) {
|
|
if value, ok := current_comptime_value(checker, expr.name); ok {
|
|
if value.kind == .Integer || value.kind == .String || value.kind == .Static {
|
|
last = value.type
|
|
}
|
|
}
|
|
if binding, ok := current_static_binding(checker, expr.name); ok {
|
|
last = binding.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, expr_lookup_file(expr, file))
|
|
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, expr_lookup_file(expr, file))
|
|
if template != ast.INVALID_FUNCTION &&
|
|
len(checker.ast_module.functions[template].unsupported_reason) == 0 &&
|
|
checker.template_diagnostics[template] == source.INVALID_DIAGNOSTIC {
|
|
function_type, ok := function_expr_type_for_template(checker, template, frame.expected, demanded)
|
|
if ok {
|
|
last = function_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, local_types)
|
|
_, function_item, function_type, ok := types.callable_function(callee_type, &checker.module.types)
|
|
if !ok {
|
|
last = types.INVALID
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
stack[frame_index].left = function_type
|
|
stack[frame_index].args = make([]types.Type, len(expr.args), checker.allocator)
|
|
stack[frame_index].stage = 6
|
|
if len(expr.args) > 0 {
|
|
append(&stack, Infer_Frame{expr=expr.args[0], template=ast.INVALID_FUNCTION})
|
|
} else if valid_callable_arity(function_item, 0) {
|
|
last = function_item.child
|
|
delete(stack[frame_index].args, checker.allocator)
|
|
stack[frame_index].args = nil
|
|
_ = pop(&stack)
|
|
}
|
|
continue
|
|
}
|
|
if builtin := type_builtin_call(checker, expr); builtin != .None {
|
|
if builtin == .Size_Of || builtin == .Align_Of {
|
|
last = types.USIZE
|
|
} else if len(expr.args) == 1 {
|
|
target, ok := resolve_type_argument(checker, expr.args[0], pkg, file)
|
|
last = target if ok && types.is_concrete_integer(target) else types.INVALID
|
|
} else {
|
|
last = types.INVALID
|
|
}
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
if division_builtin_call(checker, expr) != .None {
|
|
last = infer_division_builtin(checker, expr, locals, pkg, file, demanded, local_types, frame.expected)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
if builtin := memory_builtin_call(checker, expr); builtin != .None {
|
|
last = infer_memory_builtin(checker, expr, builtin, locals, pkg, file, demanded, local_types)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
if is_intrinsic_call(checker, expr, "some") {
|
|
if len(expr.args) == 1 && types.is_optional(frame.expected, &checker.module.types) {
|
|
child := types.child_type(frame.expected, &checker.module.types)
|
|
_ = infer_nested_expr(checker, expr.args[0], locals, pkg, file, demanded, local_types, child)
|
|
last = frame.expected
|
|
} else {
|
|
last = types.INVALID
|
|
}
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
if field_expr, handled := field_intrinsic_expr(checker, expr, pkg, file); handled {
|
|
if enum_type, ok := resolve_type_argument(checker, field_expr.left, pkg, file);
|
|
ok && types.is_enum(enum_type, &checker.module.types) {
|
|
_, member_ok := find_enum_member(checker, enum_type, field_expr.name)
|
|
last = enum_type if member_ok else types.INVALID
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
base_type := infer_nested_expr(
|
|
checker, field_expr.left, locals, pkg, file, demanded, local_types,
|
|
)
|
|
last = field_type_from_value(checker, field_expr, base_type)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
if is_intrinsic_call(checker, expr, "typeinfo") {
|
|
_, target_ok := resolve_type_argument(
|
|
checker, expr.args[0] if len(expr.args) == 1 else ast.INVALID_EXPR, pkg, file,
|
|
)
|
|
last = std_named_type(checker, "@std/meta", "TypeInfo") if target_ok else types.INVALID
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
if is_intrinsic_call(checker, expr, "tag") {
|
|
if len(expr.args) == 1 {
|
|
value_type := infer_nested_expr(checker, expr.args[0], locals, pkg, file, demanded, local_types)
|
|
last, _ = tag_result_type(checker, value_type)
|
|
} else {
|
|
last = types.INVALID
|
|
}
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
if is_intrinsic_call(checker, expr, "tagname") {
|
|
last = types.slice(&checker.module.types, types.U8, false)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
if is_ptrcast_call(checker, expr) {
|
|
if len(expr.args) != 2 {
|
|
last = types.INVALID
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
child, child_ok := resolve_type_argument(checker, expr.args[0], pkg, file)
|
|
operand := infer_nested_expr(checker, expr.args[1], locals, pkg, file, demanded, local_types)
|
|
result := types.INVALID
|
|
if child_ok && valid_ptrcast_child(checker, child) {
|
|
result, _ = types.replace_pointer_child(&checker.module.types, operand, child)
|
|
}
|
|
last = result
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
if is_intrinsic_call(checker, expr, "constcast") {
|
|
if len(expr.args) == 1 {
|
|
operand := infer_nested_expr(checker, expr.args[0], locals, pkg, file, demanded, local_types)
|
|
last, _ = types.restore_mutability(&checker.module.types, operand)
|
|
} else {
|
|
last = types.INVALID
|
|
}
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
if is_intrinsic_call(checker, expr, "compile_error") {
|
|
last = types.VOID
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
if expr.intrinsic {
|
|
last = types.INVALID
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
if callee_type, handled := infer_qualified_value_field_type(checker, expr, locals, pkg, file, demanded); handled {
|
|
_, function_item, function_type, ok := types.callable_function(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, expr_lookup_file(expr, file))
|
|
}
|
|
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) {
|
|
if binding, ok := current_static_binding(checker, expr.name); ok {
|
|
callee_type = binding.type
|
|
if binding.value != INVALID_CT_VALUE && int(binding.value) < len(checker.static_state.values) {
|
|
function_value := checker.static_state.values[binding.value]
|
|
if function_value.kind == .Function {
|
|
_, _, _ = function_pointer_type_for_template(
|
|
checker, ast.Function_Id(u32(function_value.index)), demanded,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if !types.is_valid(callee_type) {
|
|
if value, ok := current_comptime_value(checker, expr.name); ok && value.kind == .Static {
|
|
callee_type = value.type
|
|
if value.static_value != INVALID_CT_VALUE && int(value.static_value) < len(checker.static_state.values) {
|
|
function_value := checker.static_state.values[value.static_value]
|
|
if function_value.kind == .Function {
|
|
_, _, _ = function_pointer_type_for_template(
|
|
checker, ast.Function_Id(u32(function_value.index)), demanded,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if !types.is_valid(callee_type) && available {
|
|
global := find_global(checker, expr.name, target_pkg, expr_lookup_file(expr, file))
|
|
if global != ast.INVALID_GLOBAL {
|
|
callee_type = checker.global_types[global]
|
|
}
|
|
}
|
|
_, function_item, function_type, ok := types.callable_function(callee_type, &checker.module.types)
|
|
if !ok {
|
|
named_type := types.find_named(&checker.module.types, u32(target_pkg), u32(expr.name), file=u32(expr_lookup_file(expr, file)))
|
|
named_item, named_ok := types.node(&checker.module.types, named_type)
|
|
constructor_type := types.resolve_alias(named_type, &checker.module.types)
|
|
constructor_item, constructor_ok := types.node(&checker.module.types, constructor_type)
|
|
scalar_alias := named_ok && named_item.kind == .Alias &&
|
|
types.is_concrete_scalar(constructor_type) && !types.is_bool(constructor_type)
|
|
distinct_constructor := constructor_ok && constructor_item.kind == .Distinct
|
|
if available && len(expr.args) == 1 && (scalar_alias || distinct_constructor) {
|
|
stack[frame_index].left = constructor_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 := function_channel_type(checker, checker.ast_module.functions[template])
|
|
last = declared if is_runtime_type(checker, declared) || types.is_void(declared) || types.is_noreturn(declared) else types.INVALID
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
function := checker.ast_module.functions[template]
|
|
mapping, mode, prefix, mapping_failure := call_argument_mapping(
|
|
checker, &function, expr.args, pkg, file, frame.expected, locals,
|
|
)
|
|
delete(mapping_failure, checker.allocator)
|
|
if mode == .Invalid {
|
|
last = types.INVALID
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
stack[frame_index].template = template
|
|
stack[frame_index].arg_mode = mode
|
|
stack[frame_index].prefix = prefix
|
|
stack[frame_index].mapping = mapping
|
|
stack[frame_index].args = make([]types.Type, len(function.params), checker.allocator)
|
|
stack[frame_index].arg_index = next_runtime_call_arg(function, mapping, 0, len(expr.args))
|
|
stack[frame_index].stage = 3
|
|
if stack[frame_index].arg_index < len(expr.args) {
|
|
param_index := call_param_index(mapping, stack[frame_index].arg_index)
|
|
arg_expected := call_arg_expected(checker, function, param_index)
|
|
if !is_runtime_type(checker, arg_expected) {
|
|
arg_expected = types.INVALID
|
|
}
|
|
append(&stack, Infer_Frame{expr=expr.args[stack[frame_index].arg_index], expected=arg_expected, 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 {
|
|
right := last
|
|
if expr.kind == .Add && types.is_many_pointer(frame.left, &checker.module.types) && types.is_concrete_integer(right) {
|
|
last = frame.left
|
|
} else if is_numeric_constant_expr(checker, expr.right) &&
|
|
is_numeric_demand(frame.left, checker.target) &&
|
|
expr_accepts_numeric_demand(checker, expr.right, frame.left, locals, pkg, file) {
|
|
last = frame.left
|
|
} else if is_numeric_constant_expr(checker, expr.left) &&
|
|
is_numeric_demand(right, checker.target) &&
|
|
expr_accepts_numeric_demand(checker, expr.left, right, locals, pkg, file) {
|
|
last = right
|
|
} else if is_numeric_demand(frame.left, checker.target) &&
|
|
!numeric_operand_is_open(checker, expr.left, locals, pkg, file) &&
|
|
expr_accepts_numeric_demand(checker, expr.right, frame.left, locals, pkg, file) {
|
|
// Propagate only from an authoritative (fixed-type) left operand. A left
|
|
// operand that is still a provisional open constant carries only its
|
|
// smallest-signed default, which must not poison the sibling's family;
|
|
// two provisional operands are resolved together by the backward demand
|
|
// from the declaration/use.
|
|
_ = record_demand(checker, expr.right, frame.left, locals, local_types, pkg, file)
|
|
last = frame.left
|
|
} else if is_numeric_demand(right, checker.target) &&
|
|
!numeric_operand_is_open(checker, expr.right, locals, pkg, file) &&
|
|
expr_accepts_numeric_demand(checker, expr.left, right, locals, pkg, file) {
|
|
_ = record_demand(checker, expr.left, right, locals, local_types, pkg, file)
|
|
last = right
|
|
} else {
|
|
last = types.widest(frame.left, right)
|
|
}
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
if frame.stage == 3 {
|
|
if frame.arg_index < len(expr.args) {
|
|
param_index := call_param_index(frame.mapping, frame.arg_index)
|
|
if param_index < len(stack[frame_index].args) {
|
|
stack[frame_index].args[param_index] = last
|
|
}
|
|
next := next_runtime_call_arg(
|
|
checker.ast_module.functions[frame.template], frame.mapping,
|
|
frame.arg_index+1, len(expr.args),
|
|
)
|
|
stack[frame_index].arg_index = next
|
|
if next < len(expr.args) {
|
|
next_param := call_param_index(frame.mapping, next)
|
|
arg_expected := call_arg_expected(checker, checker.ast_module.functions[frame.template], next_param)
|
|
if !is_runtime_type(checker, arg_expected) {
|
|
arg_expected = types.INVALID
|
|
}
|
|
append(&stack, Infer_Frame{expr=expr.args[next], expected=arg_expected, template=ast.INVALID_FUNCTION})
|
|
continue
|
|
}
|
|
}
|
|
function := checker.ast_module.functions[frame.template]
|
|
// Deferred defaulting leaves an undemanded open constant typeless; give such an
|
|
// argument its default so the call can still monomorphize (the default feeds only
|
|
// the spec arg vector, not a demand).
|
|
for source_index in 0..<len(expr.args) {
|
|
param_index := call_param_index(frame.mapping, source_index)
|
|
if param_index < len(stack[frame_index].args) && !is_runtime_type(checker, stack[frame_index].args[param_index]) {
|
|
fallback := open_const_default_type(checker, expr.args[source_index], locals, pkg, file)
|
|
if is_runtime_type(checker, fallback) {
|
|
stack[frame_index].args[param_index] = fallback
|
|
}
|
|
}
|
|
}
|
|
comptime_values: []Comptime_Value
|
|
comptime_ok := false
|
|
comptime_values, comptime_ok = infer_call_comptime_values(
|
|
checker, function, frame.prefix, frame.mapping, expr.args, stack[frame_index].args,
|
|
frame.expected, pkg, file,
|
|
)
|
|
defer delete(comptime_values, checker.allocator)
|
|
if comptime_ok &&
|
|
can_specialize(checker, function, stack[frame_index].args, comptime_values) {
|
|
resolution := store_call_resolution(
|
|
checker, frame.expr, frame.mapping,
|
|
comptime_values, stack[frame_index].args,
|
|
)
|
|
fold := Call_Fold.Runtime
|
|
if can_fold_zero_runtime_call(checker, function) {
|
|
fold = cache_zero_runtime_call(checker, resolution, frame.expr, pkg, file)
|
|
}
|
|
if fold == .Compile_Error {
|
|
last = types.INVALID
|
|
} else if fold == .Value {
|
|
// Folded specializations are still inferred to validate their bodies and
|
|
// participate in the type-demand fixpoint. Leaving them undemanded keeps
|
|
// prune_specs from emitting an otherwise unused runtime function.
|
|
if !checker.building_hir && demanded == nil && !function_has_comptime_params(function) {
|
|
_ = ensure_spec(checker, frame.template, stack[frame_index].args, comptime_values)
|
|
}
|
|
value := checker.call_resolutions[resolution].folded_value
|
|
last = checker.static_state.values[value].type
|
|
} else {
|
|
previous_comptime := checker.current_comptime_values
|
|
checker.current_comptime_values = comptime_values
|
|
for source_index in 0..<len(expr.args) {
|
|
param_index := call_param_index(frame.mapping, source_index)
|
|
if param_index >= len(function.params) || function.params[param_index].comptime_value {
|
|
continue
|
|
}
|
|
demand := call_arg_expected(checker, function, param_index)
|
|
record_demand(checker, expr.args[source_index], demand, locals, local_types, pkg, file)
|
|
}
|
|
checker.current_comptime_values = previous_comptime
|
|
spec := INVALID_SPEC
|
|
if demanded == nil {
|
|
spec = ensure_spec(checker, frame.template, stack[frame_index].args, comptime_values)
|
|
} else {
|
|
spec = find_spec(checker, frame.template, stack[frame_index].args, comptime_values)
|
|
if spec == INVALID_SPEC {
|
|
spec = ensure_spec(checker, frame.template, stack[frame_index].args, comptime_values)
|
|
}
|
|
mark_spec_demanded(checker, spec, demanded)
|
|
}
|
|
if spec != INVALID_SPEC {
|
|
last = checker.specs[spec].result
|
|
} else {
|
|
previous_comptime = checker.current_comptime_values
|
|
checker.current_comptime_values = comptime_values
|
|
declared := function_channel_type(checker, function)
|
|
checker.current_comptime_values = previous_comptime
|
|
last = declared if is_runtime_type(checker, declared) || types.is_void(declared) || types.is_noreturn(declared) else types.INVALID
|
|
}
|
|
}
|
|
} else {
|
|
declared := function_channel_type(checker, function)
|
|
if function.pkg == 0 && function.name == checker.main_symbol && function.result == types.INT &&
|
|
!types.is_valid(function.error) {
|
|
last = types.I32
|
|
} else {
|
|
last = declared if is_runtime_type(checker, declared) || types.is_void(declared) || types.is_noreturn(declared) else types.INVALID
|
|
}
|
|
}
|
|
delete(stack[frame_index].args, checker.allocator)
|
|
stack[frame_index].args = nil
|
|
delete(stack[frame_index].mapping, checker.allocator)
|
|
stack[frame_index].mapping = 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)
|
|
}
|
|
|
|
record_infer_local_type :: proc(local: Infer_Local, local_types: []types.Type) {
|
|
if local.statement != ast.INVALID_STMT && int(local.statement) < len(local_types) {
|
|
local_types[local.statement] = local.type
|
|
}
|
|
}
|
|
|
|
merge_infer_local_type :: proc(
|
|
checker: ^Checker,
|
|
local: ^Infer_Local,
|
|
inferred: types.Type,
|
|
local_types: []types.Type,
|
|
) -> bool {
|
|
if !is_runtime_type(checker, inferred) {
|
|
return false
|
|
}
|
|
if types.is_constraint(local.declared) {
|
|
if !types.constraint_accepts(local.declared, inferred, &checker.module.types) {
|
|
return false
|
|
}
|
|
if !is_runtime_type(checker, local.type) {
|
|
local.type = inferred
|
|
record_infer_local_type(local^, local_types)
|
|
return true
|
|
}
|
|
if types.equal(local.type, inferred) {
|
|
return false
|
|
}
|
|
merged := types.widest(local.type, inferred)
|
|
if types.constraint_accepts(local.declared, merged, &checker.module.types) {
|
|
local.type = merged
|
|
record_infer_local_type(local^, local_types)
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
if is_runtime_type(checker, local.declared) {
|
|
local.type = local.declared
|
|
record_infer_local_type(local^, local_types)
|
|
return false
|
|
}
|
|
if !is_runtime_type(checker, local.type) {
|
|
local.type = inferred
|
|
record_infer_local_type(local^, local_types)
|
|
return true
|
|
}
|
|
if types.equal(local.type, inferred) {
|
|
return false
|
|
}
|
|
merged := types.widest(local.type, inferred)
|
|
if types.is_concrete_scalar(merged) {
|
|
local.type = merged
|
|
record_infer_local_type(local^, local_types)
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
infer_statements :: proc(
|
|
checker: ^Checker,
|
|
statements: []ast.Stmt_Id,
|
|
locals: ^[dynamic]Infer_Local,
|
|
local_types: []types.Type,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
demanded: ^[dynamic]Spec_Id,
|
|
result: ^types.Type,
|
|
result_hint := types.INVALID,
|
|
) {
|
|
scope_start := len(locals^)
|
|
for statement_id in statements {
|
|
statement := checker.ast_module.statements[statement_id]
|
|
#partial switch statement.kind {
|
|
case .Declaration:
|
|
if statement.expr == ast.INVALID_EXPR {
|
|
// Value block (`x :: { ... yield v }` / `x T = { ... }`): register the
|
|
// binding (its declared type when annotated, else left open) and walk
|
|
// the block body. The build pass resolves the yielded value's type
|
|
// independently — value blocks don't join the demand fixpoint.
|
|
declared_block := type_from_syntax(checker, statement.type, pkg, file)
|
|
block_type := declared_block if is_runtime_type(checker, declared_block) else types.INVALID
|
|
local := Infer_Local{
|
|
name=statement.name, type=block_type, declared=declared_block,
|
|
statement=statement_id, mutable=!statement.immutable,
|
|
}
|
|
append(locals, local)
|
|
record_infer_local_type(local, local_types)
|
|
infer_statements(checker, statement.body, locals, local_types, pkg, file, demanded, result, result_hint)
|
|
continue
|
|
}
|
|
declared_local := resolve_inferred_array(checker, type_from_syntax(checker, statement.type, pkg, file), statement.expr)
|
|
value_type := types.INVALID
|
|
if !is_undefined_expr(checker, statement.expr) {
|
|
expected := declared_local if is_runtime_type(checker, declared_local) || declared_local == types.UINT else types.INVALID
|
|
value_type = infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types, expected)
|
|
declared_local = resolve_inferred_array_from_type(checker, declared_local, value_type)
|
|
}
|
|
if is_runtime_type(checker, declared_local) && !has_inferred_array_count(checker, declared_local) {
|
|
value_type = declared_local
|
|
} else if types.is_constraint(declared_local) {
|
|
// Seed the binding in-family (INVALID on mismatch, which
|
|
// build_block reports). FLOAT defaults integers to f64.
|
|
value_type = types.constraint_target(declared_local, value_type, &checker.module.types)
|
|
}
|
|
open := false
|
|
open_float := false
|
|
const_val := i128(0)
|
|
if !is_runtime_type(checker, declared_local) && !is_undefined_expr(checker, statement.expr) {
|
|
constant := eval_integer_constant_in_context(checker, statement.expr, pkg, file)
|
|
if constant.kind == .Value &&
|
|
(fits_i64(constant.value) || declared_local == types.UINT && fits_u64(constant.value)) {
|
|
open = true
|
|
const_val = constant.value
|
|
} else if is_float_constant_expr(checker, statement.expr) {
|
|
open_float = true
|
|
}
|
|
}
|
|
local := Infer_Local{
|
|
name=statement.name,
|
|
type=value_type,
|
|
declared=declared_local,
|
|
statement=statement_id,
|
|
mutable=!statement.immutable,
|
|
open_const=open,
|
|
open_float=open_float,
|
|
const_value=const_val,
|
|
}
|
|
append(locals, local)
|
|
record_infer_local_type(local, local_types)
|
|
// A typed/constraint declaration initialized by a bare name pushes its resolved
|
|
// type backward onto that name (mirrors the `Y int :: X; Z i32 :: Y` global chain).
|
|
// Skip this for a constraint/untyped declaration whose initializer is arithmetic:
|
|
// value_type is then only a provisional default (e.g. `x int = a - b` resolving to
|
|
// i16 before the operands' real uses are seen) and would poison the open-constant
|
|
// operands' family. The operands resolve from their own authoritative uses, and the
|
|
// local adopts their resolved type forward. Concrete-declared arithmetic (e.g.
|
|
// `b u16 :: a + 2`) still pushes, since value_type is the concrete declared type.
|
|
init_is_arith := is_arith_kind(checker.ast_module.exprs[statement.expr].kind)
|
|
if is_runtime_type(checker, declared_local) || !init_is_arith {
|
|
record_demand(checker, statement.expr, value_type, locals^[:], local_types, pkg, file)
|
|
}
|
|
case .Assignment:
|
|
if statement.expr == ast.INVALID_EXPR {
|
|
// Value block assigned to a target: walk the block body; the build
|
|
// pass handles the target coercion.
|
|
infer_statements(checker, statement.body, locals, local_types, pkg, file, demanded, result, result_hint)
|
|
continue
|
|
}
|
|
expected_assignment := types.INVALID
|
|
if statement.target != ast.INVALID_EXPR {
|
|
expected_assignment = infer_expr(checker, statement.target, locals^[:], pkg, file, demanded, local_types)
|
|
target_expr := checker.ast_module.exprs[statement.target]
|
|
if target_expr.kind == .Name && !symbol.is_valid(target_expr.qualifier) {
|
|
if local_index, ok := find_infer_local_index(locals^[:], target_expr.name); ok &&
|
|
locals^[local_index].declared == types.UINT {
|
|
expected_assignment = types.UINT
|
|
} else if global := find_global(checker, target_expr.name, pkg, file); global != ast.INVALID_GLOBAL {
|
|
ast_global := checker.ast_module.globals[global]
|
|
if type_from_syntax(checker, ast_global.type, ast_global.pkg, ast_global.file) == types.UINT {
|
|
expected_assignment = types.UINT
|
|
}
|
|
}
|
|
}
|
|
} else if statement.name != checker.sink_symbol {
|
|
if local_index, ok := find_infer_local_index(locals^[:], statement.name); ok {
|
|
expected_assignment = types.UINT if locals^[local_index].declared == types.UINT else locals^[local_index].type
|
|
} else if global := find_global(checker, statement.name, pkg, file); global != ast.INVALID_GLOBAL {
|
|
ast_global := checker.ast_module.globals[global]
|
|
declared_global := type_from_syntax(checker, ast_global.type, ast_global.pkg, ast_global.file)
|
|
expected_assignment = types.UINT if declared_global == types.UINT else checker.global_types[global]
|
|
}
|
|
}
|
|
value_type := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types, expected_assignment)
|
|
// Only push the target's type back onto a bare-name RHS (e.g. `x += speed`):
|
|
// pushing through an arithmetic RHS would feed the target's (often provisional)
|
|
// type onto open-constant operands and poison their family. Operands of an
|
|
// arithmetic RHS resolve from their own authoritative uses.
|
|
rhs_is_arith := is_arith_kind(checker.ast_module.exprs[statement.expr].kind)
|
|
if statement.target != ast.INVALID_EXPR {
|
|
target_type := expected_assignment
|
|
target_expr := checker.ast_module.exprs[statement.target]
|
|
if target_expr.kind == .Name && !symbol.is_valid(target_expr.qualifier) {
|
|
if local_index, ok := find_infer_local_index(locals^[:], target_expr.name); ok &&
|
|
locals^[local_index].mutable {
|
|
_ = merge_infer_local_type(checker, &locals^[local_index], value_type, local_types)
|
|
}
|
|
} else if target_expr.kind == .Field ||
|
|
target_expr.kind == .Name && symbol.is_valid(target_expr.qualifier) {
|
|
if slot, ok := inferred_record_field_slot_from_expr(
|
|
checker, target_expr, locals^[:], local_types, pkg, file,
|
|
); ok {
|
|
_ = record_field_expr_candidate(
|
|
checker, slot, statement.expr, value_type, locals^[:], pkg, file,
|
|
)
|
|
target_type = checker.module.types.fields[slot].type
|
|
}
|
|
}
|
|
if !rhs_is_arith {
|
|
_ = record_demand(checker, statement.expr, target_type, locals^[:], local_types, pkg, file)
|
|
}
|
|
} else if statement.name != checker.sink_symbol {
|
|
if local_index, ok := find_infer_local_index(locals^[:], statement.name); ok &&
|
|
locals^[local_index].mutable {
|
|
_ = merge_infer_local_type(checker, &locals^[local_index], value_type, local_types)
|
|
if !rhs_is_arith {
|
|
_ = record_demand(checker, statement.expr, locals^[local_index].type, locals^[:], local_types, pkg, file)
|
|
}
|
|
} else if global := find_global(checker, statement.name, pkg, file); global != ast.INVALID_GLOBAL {
|
|
_ = merge_global_demand(checker, global, value_type)
|
|
if !rhs_is_arith {
|
|
_ = record_demand(checker, statement.expr, checker.global_types[global], locals^[:], local_types, pkg, file)
|
|
}
|
|
}
|
|
}
|
|
case .Expression:
|
|
_ = infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types)
|
|
case .Yield:
|
|
if statement.expr != ast.INVALID_EXPR {
|
|
_ = infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types)
|
|
}
|
|
case .Return:
|
|
if statement.expr != ast.INVALID_EXPR {
|
|
returned := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types, result_hint)
|
|
if is_runtime_type(checker, result_hint) {
|
|
_ = record_demand_shallow(checker, statement.expr, result_hint, locals^[:], local_types, pkg, file)
|
|
expr := checker.ast_module.exprs[statement.expr]
|
|
if expr.kind == .Name && !symbol.is_valid(expr.qualifier) {
|
|
if local_index, ok := find_infer_local_index(locals^[:], expr.name); ok {
|
|
// Open constants can adopt the result type cross-family; other
|
|
// locals widen within family as before.
|
|
if !merge_local_demand(checker, &locals^[local_index], result_hint, local_types) {
|
|
_ = merge_infer_local_type(checker, &locals^[local_index], result_hint, local_types)
|
|
}
|
|
returned = result_hint
|
|
} else {
|
|
// `return G` for a global const: demand the result type onto it.
|
|
record_demand_shallow(checker, statement.expr, result_hint, locals^[:], local_types, pkg, file)
|
|
}
|
|
}
|
|
}
|
|
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, local_types)
|
|
}
|
|
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, declared=capture_type, statement=ast.INVALID_STMT})
|
|
}
|
|
if statement.guard != ast.INVALID_EXPR {
|
|
_ = infer_expr(checker, statement.guard, locals^[:], pkg, file, demanded, local_types)
|
|
}
|
|
infer_statements(checker, statement.body, locals, local_types, pkg, file, demanded, result, result_hint)
|
|
resize(locals, capture_start)
|
|
infer_statements(checker, statement.else_body, locals, local_types, pkg, file, demanded, result, result_hint)
|
|
delete(operand_types, checker.allocator)
|
|
delete(operands)
|
|
} else {
|
|
_ = infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types)
|
|
if selected, comptime_ok := specialization_bool(checker, statement.expr, pkg, file); comptime_ok {
|
|
selected_body := statement.body if selected else statement.else_body
|
|
infer_statements(checker, selected_body, locals, local_types, pkg, file, demanded, result, result_hint)
|
|
} else {
|
|
infer_statements(checker, statement.body, locals, local_types, pkg, file, demanded, result, result_hint)
|
|
infer_statements(checker, statement.else_body, locals, local_types, pkg, file, demanded, result, result_hint)
|
|
}
|
|
}
|
|
case .While:
|
|
_ = infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types)
|
|
infer_statements(checker, statement.body, locals, local_types, pkg, file, demanded, result, result_hint)
|
|
if statement.update != ast.INVALID_STMT {
|
|
update := [1]ast.Stmt_Id{statement.update}
|
|
infer_statements(checker, update[:], locals, local_types, pkg, file, demanded, result, result_hint)
|
|
}
|
|
case .For:
|
|
if statement.expand {
|
|
bindings, expand_error := expand_field_bindings(checker, statement.expr, statement.name, pkg, file)
|
|
if expand_error == .None {
|
|
for binding, expand_index in bindings {
|
|
binding_start := push_expand_binding(checker, binding, statement.index_name, expand_index, statement_id)
|
|
iteration: [dynamic]ast.Stmt_Id
|
|
iteration.allocator = checker.allocator
|
|
control := flatten_expand_iteration(
|
|
checker, statement.body, pkg, file, &iteration, statement.label, nil,
|
|
)
|
|
if control != .Invalid {
|
|
infer_statements(checker, iteration[:], locals, local_types, pkg, file, demanded, result, result_hint)
|
|
}
|
|
delete(iteration)
|
|
pop_expand_binding(checker, binding_start)
|
|
if control == .Break || control == .Invalid {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
delete(bindings, checker.allocator)
|
|
continue
|
|
}
|
|
iterable_type := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types)
|
|
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, declared=capture_type, statement=ast.INVALID_STMT})
|
|
}
|
|
if symbol.is_valid(statement.index_name) {
|
|
append(locals, Infer_Local{name=statement.index_name, type=types.USIZE, declared=types.USIZE, statement=ast.INVALID_STMT})
|
|
}
|
|
infer_statements(checker, statement.body, locals, local_types, pkg, file, demanded, result, result_hint)
|
|
resize(locals, capture_start)
|
|
case .Block:
|
|
infer_statements(checker, statement.body, locals, local_types, pkg, file, demanded, result, result_hint)
|
|
case .Defer:
|
|
capture_start := len(locals^)
|
|
if statement.error_only && len(statement.captures) > 0 &&
|
|
statement.captures[0] != checker.sink_symbol {
|
|
append(locals, Infer_Local{
|
|
name=statement.captures[0],
|
|
type=types.fallible_error(result^, &checker.module.types),
|
|
declared=types.fallible_error(result^, &checker.module.types),
|
|
statement=ast.INVALID_STMT,
|
|
})
|
|
}
|
|
deferred := [1]ast.Stmt_Id{statement.update}
|
|
infer_statements(checker, deferred[:], locals, local_types, pkg, file, demanded, result, result_hint)
|
|
resize(locals, capture_start)
|
|
case .Match:
|
|
if selected_body, capture, has_capture, comptime_ok := specialization_match_body(checker, statement, pkg, file); comptime_ok {
|
|
if has_capture {
|
|
append(&checker.static_bindings, capture)
|
|
}
|
|
infer_statements(checker, selected_body, locals, local_types, pkg, file, demanded, result, result_hint)
|
|
if has_capture {
|
|
_ = pop(&checker.static_bindings)
|
|
}
|
|
continue
|
|
}
|
|
// The build pass desugars `match` to an if/else chain, but inference runs first
|
|
// and must still visit the subject and arm bodies so calls there get specialized
|
|
// (e.g. `match get()`). Mirror the `.For`/unwrap-`.If` capture handling.
|
|
subject_type := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types)
|
|
is_tagged := types.is_tagged_union(subject_type, &checker.module.types)
|
|
is_enum_subject := types.is_enum(subject_type, &checker.module.types)
|
|
covered: [dynamic]symbol.Id
|
|
covered.allocator = checker.allocator
|
|
for arm_id in statement.body {
|
|
arm := checker.ast_module.statements[arm_id]
|
|
if arm.kind != .Match_Arm {
|
|
continue
|
|
}
|
|
if arm.expand && (is_tagged || is_enum_subject) {
|
|
tag_type := types.union_tag_enum(subject_type, &checker.module.types) if is_tagged else subject_type
|
|
if is_tagged {
|
|
for field in types.fields_for(&checker.module.types, subject_type) {
|
|
name := symbol.Id(field.name)
|
|
if contains_name(covered[:], name) {
|
|
continue
|
|
}
|
|
member, found := find_enum_member(checker, tag_type, name)
|
|
if !found {
|
|
continue
|
|
}
|
|
capture_start := len(locals^)
|
|
static_start := len(checker.static_bindings)
|
|
if len(arm.captures) > 1 {
|
|
_ = push_static_integer_binding(checker, arm.captures[1], tag_type, member.value)
|
|
}
|
|
if len(arm.captures) > 0 && arm.captures[0] != checker.sink_symbol {
|
|
if types.is_void(field.type) && !arm.pointer_capture {
|
|
_ = push_static_void_binding(checker, arm.captures[0])
|
|
} else {
|
|
capture_type := field.type
|
|
if arm.pointer_capture {
|
|
capture_type = types.pointer(&checker.module.types, field.type, true, false)
|
|
}
|
|
append(locals, Infer_Local{name=arm.captures[0], type=capture_type, declared=capture_type, statement=ast.INVALID_STMT})
|
|
}
|
|
}
|
|
infer_statements(checker, arm.body, locals, local_types, pkg, file, demanded, result, result_hint)
|
|
resize(locals, capture_start)
|
|
pop_static_bindings(checker, static_start)
|
|
}
|
|
} else {
|
|
for member in types.enum_members_for(&checker.module.types, subject_type) {
|
|
name := symbol.Id(member.name)
|
|
if contains_name(covered[:], name) {
|
|
continue
|
|
}
|
|
static_start := push_static_integer_binding(checker, arm.captures[0] if len(arm.captures) > 0 else symbol.INVALID, tag_type, member.value)
|
|
infer_statements(checker, arm.body, locals, local_types, pkg, file, demanded, result, result_hint)
|
|
pop_static_bindings(checker, static_start)
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
for pattern in arm.patterns {
|
|
_ = infer_expr(checker, pattern, locals^[:], pkg, file, demanded, local_types)
|
|
pattern_expr := checker.ast_module.exprs[pattern]
|
|
if pattern_expr.kind == .Enum_Literal {
|
|
append(&covered, pattern_expr.name)
|
|
}
|
|
}
|
|
capture_start := len(locals^)
|
|
if len(arm.captures) > 0 && is_tagged && len(arm.patterns) > 0 {
|
|
capture := arm.captures[0]
|
|
if capture != checker.sink_symbol {
|
|
capture_type := types.INVALID
|
|
pattern := checker.ast_module.exprs[arm.patterns[0]]
|
|
if pattern.kind == .Enum_Literal {
|
|
if _, field, ok := find_struct_field(checker, subject_type, pattern.name); ok {
|
|
capture_type = field.type
|
|
if arm.pointer_capture {
|
|
// Mutability is best-effort here; the build pass finalizes
|
|
// the exact pointer type and coerces the captured value.
|
|
capture_type = types.pointer(&checker.module.types, field.type, true, false)
|
|
}
|
|
}
|
|
}
|
|
append(locals, Infer_Local{name=capture, type=capture_type, declared=capture_type, statement=ast.INVALID_STMT})
|
|
}
|
|
}
|
|
infer_statements(checker, arm.body, locals, local_types, pkg, file, demanded, result, result_hint)
|
|
resize(locals, capture_start)
|
|
}
|
|
delete(covered)
|
|
}
|
|
}
|
|
resize(locals, scope_start)
|
|
}
|
|
|
|
infer_spec_locals_and_result :: proc(
|
|
checker: ^Checker,
|
|
id: Spec_Id,
|
|
demanded: ^[dynamic]Spec_Id = nil,
|
|
) -> ([]types.Type, types.Type) {
|
|
spec := checker.specs[id]
|
|
function := checker.ast_module.functions[spec.template]
|
|
previous_comptime := checker.current_comptime_values
|
|
checker.current_comptime_values = spec.comptime_values
|
|
defer checker.current_comptime_values = previous_comptime
|
|
declared := type_from_syntax(checker, function.result, function.pkg, function.file)
|
|
if function.pkg == 0 && function.name == checker.main_symbol && function.result == types.INT {
|
|
declared = types.I32
|
|
}
|
|
result_hint := declared if is_runtime_type(checker, declared) || types.is_noreturn(declared) || declared == types.UINT else types.INVALID
|
|
|
|
locals: [dynamic]Infer_Local
|
|
locals.allocator = checker.allocator
|
|
defer delete(locals)
|
|
local_types := make([]types.Type, len(checker.ast_module.statements), checker.allocator)
|
|
runtime_index := 0
|
|
for param, index in function.params {
|
|
if param.comptime_value {
|
|
continue
|
|
}
|
|
param_type := types.INVALID
|
|
if runtime_index < len(spec.args) {
|
|
param_type = spec.args[runtime_index]
|
|
}
|
|
append(&locals, Infer_Local{name=param.name, type=param_type, declared=param_type, statement=ast.INVALID_STMT})
|
|
runtime_index += 1
|
|
}
|
|
|
|
result := types.INVALID
|
|
test_error := function.error
|
|
previous_test_error := checker.inferred_test_error
|
|
checker.inferred_test_error = &test_error if function.test else nil
|
|
infer_statements(checker, function.body, &locals, local_types, function.pkg, function.file, demanded, &result, result_hint)
|
|
checker.inferred_test_error = previous_test_error
|
|
if function.test && !types.equal(function.error, test_error) {
|
|
checker.ast_module.functions[spec.template].error = test_error
|
|
success := types.fallible_success(checker.specs[id].result, &checker.module.types)
|
|
checker.specs[id].result = types.fallible(&checker.module.types, success, test_error)
|
|
}
|
|
if types.is_constraint(declared) {
|
|
// Narrow the inferred result to the constraint's family; an out-of-family
|
|
// result (e.g. returning a non-integer from an `int` function) yields
|
|
// INVALID and is rejected downstream.
|
|
return local_types, types.constraint_target(declared, result, &checker.module.types)
|
|
}
|
|
return local_types, declared
|
|
}
|
|
|
|
infer_spec_result :: proc(checker: ^Checker, id: Spec_Id, demanded: ^[dynamic]Spec_Id = nil) -> types.Type {
|
|
local_types, result := infer_spec_locals_and_result(checker, id, demanded)
|
|
delete(local_types, checker.allocator)
|
|
return result
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
open_integer_accepts_demand :: proc(checker: ^Checker, value: i128, demand: types.Type) -> bool {
|
|
if types.is_concrete_integer(demand) {
|
|
return fits_integer_type(value, demand, checker.target)
|
|
}
|
|
return types.is_float(demand, checker.target)
|
|
}
|
|
|
|
open_float_accepts_demand :: proc(checker: ^Checker, demand: types.Type) -> bool {
|
|
return types.is_float(demand, checker.target)
|
|
}
|
|
|
|
// merge_open_const_demand records a concrete numeric demand onto an open numeric
|
|
// global's slot. Integer constants may adopt integer or float demands; float
|
|
// constants may adopt float demands. Later demands only widen within the chosen family.
|
|
merge_open_const_demand :: proc(
|
|
checker: ^Checker,
|
|
slot: ^types.Type,
|
|
demand: types.Type,
|
|
int_open: bool,
|
|
float_open: bool,
|
|
value: i128,
|
|
) -> bool {
|
|
if !(int_open && open_integer_accepts_demand(checker, value, demand) ||
|
|
float_open && open_float_accepts_demand(checker, demand)) {
|
|
return false
|
|
}
|
|
if !is_runtime_type(checker, slot^) {
|
|
slot^ = demand
|
|
return true
|
|
}
|
|
if types.equal(slot^, demand) {
|
|
return false
|
|
}
|
|
merged := types.widest(slot^, demand)
|
|
if types.is_concrete_scalar(merged) && !types.equal(slot^, merged) {
|
|
slot^ = merged
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// merge_global_demand routes a concrete demand onto a global's slot: open constants
|
|
// adopt any fitting family, other referents widen within family. Sets a dirty flag so
|
|
// a demand pushed from a function body re-triggers the inference fixpoint.
|
|
merge_global_demand :: proc(checker: ^Checker, global: ast.Global_Id, demand: types.Type) -> bool {
|
|
index := int(global)
|
|
if index < 0 || index >= len(checker.global_demands) {
|
|
return false
|
|
}
|
|
ast_global := checker.ast_module.globals[global]
|
|
declared := type_from_syntax(checker, ast_global.type, ast_global.pkg, ast_global.file)
|
|
if types.is_constraint(declared) && !types.constraint_accepts(declared, demand, &checker.module.types) {
|
|
return false
|
|
}
|
|
changed: bool
|
|
if checker.global_open_const[index] || checker.global_open_float[index] {
|
|
changed = merge_open_const_demand(
|
|
checker,
|
|
&checker.global_demands[index],
|
|
demand,
|
|
checker.global_open_const[index],
|
|
checker.global_open_float[index],
|
|
checker.global_const_value[index],
|
|
)
|
|
} else {
|
|
changed = merge_inferred_type(&checker.module.types, &checker.global_demands[index], demand)
|
|
}
|
|
checker.global_demands_dirty = checker.global_demands_dirty || changed
|
|
return changed
|
|
}
|
|
|
|
// merge_local_demand records a concrete numeric demand onto an open-constant local.
|
|
// The first demand replaces the literal's default type; later demands may only widen
|
|
// within the chosen family.
|
|
merge_local_demand :: proc(checker: ^Checker, local: ^Infer_Local, demand: types.Type, local_types: []types.Type) -> bool {
|
|
if !(local.open_const && open_integer_accepts_demand(checker, local.const_value, demand) ||
|
|
local.open_float && open_float_accepts_demand(checker, demand)) {
|
|
return false
|
|
}
|
|
if types.is_constraint(local.declared) &&
|
|
!types.constraint_accepts(local.declared, demand, &checker.module.types) {
|
|
return false
|
|
}
|
|
if !local.demanded {
|
|
local.type = demand
|
|
local.demanded = true
|
|
record_infer_local_type(local^, local_types)
|
|
return true
|
|
}
|
|
if types.equal(local.type, demand) {
|
|
return false
|
|
}
|
|
merged := types.widest(local.type, demand)
|
|
if types.is_concrete_scalar(merged) && !types.equal(local.type, merged) {
|
|
local.type = merged
|
|
record_infer_local_type(local^, local_types)
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
is_arith_kind :: proc(k: ast.Expr_Kind) -> bool {
|
|
return k == .Add || k == .Sub || k == .Mul || k == .Div || k == .Negate
|
|
}
|
|
|
|
// open_const_default_type returns the fallback type a bare open-constant reference
|
|
// (local or global) would take if no use ever demands it: the smallest signed type
|
|
// that holds an integer constant, or f64 for a float constant. Used to give a call's
|
|
// argument a concrete type for monomorphization when deferred defaulting has left the
|
|
// open constant typeless (its param is a constraint, so the call records no demand on
|
|
// it). The fallback feeds only the specialization's arg vector, never a demand, so it
|
|
// cannot leak back onto the constant or a sibling.
|
|
open_const_default_type :: proc(
|
|
checker: ^Checker,
|
|
expr_id: ast.Expr_Id,
|
|
locals: []Infer_Local,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
) -> types.Type {
|
|
if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
|
|
return types.INVALID
|
|
}
|
|
expr := checker.ast_module.exprs[expr_id]
|
|
if expr.kind != .Name {
|
|
return types.INVALID
|
|
}
|
|
if !symbol.is_valid(expr.qualifier) {
|
|
if index, ok := find_infer_local_index(locals, expr.name); ok {
|
|
local := locals[index]
|
|
if local.open_const {
|
|
return constraint_integer_literal_type(local.declared, local.const_value)
|
|
}
|
|
if local.open_float {
|
|
return types.F64
|
|
}
|
|
return types.INVALID
|
|
}
|
|
}
|
|
target_pkg, available := expr_package(checker, expr, pkg, file)
|
|
if !available {
|
|
return types.INVALID
|
|
}
|
|
global := find_global(checker, expr.name, target_pkg, expr_lookup_file(expr, file))
|
|
index := int(global)
|
|
if global == ast.INVALID_GLOBAL || index < 0 || index >= len(checker.global_open_const) {
|
|
return types.INVALID
|
|
}
|
|
if checker.global_open_const[index] {
|
|
ast_global := checker.ast_module.globals[global]
|
|
declared := type_from_syntax(checker, ast_global.type, ast_global.pkg, ast_global.file)
|
|
return constraint_integer_literal_type(declared, checker.global_const_value[index])
|
|
}
|
|
if checker.global_open_float[index] {
|
|
return types.F64
|
|
}
|
|
return types.INVALID
|
|
}
|
|
|
|
// numeric_operand_is_open reports whether an arithmetic operand still carries a
|
|
// provisional type (an open constant at its smallest-signed default, or a bare
|
|
// numeric literal) rather than an authoritative one. A provisional operand must not
|
|
// propagate its type onto a sibling open constant: doing so locks the sibling into a
|
|
// default family and blocks the real backward demand from the declaration/use. Two
|
|
// provisional operands are instead resolved together by that backward demand.
|
|
numeric_operand_is_open :: proc(
|
|
checker: ^Checker,
|
|
expr_id: ast.Expr_Id,
|
|
locals: []Infer_Local,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
) -> bool {
|
|
if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
|
|
return false
|
|
}
|
|
expr := checker.ast_module.exprs[expr_id]
|
|
#partial switch expr.kind {
|
|
case .Name:
|
|
// A bare open-constant name is open iff it has a deferred default to assign.
|
|
return is_runtime_type(checker, open_const_default_type(checker, expr_id, locals, pkg, file))
|
|
case .Negate:
|
|
return numeric_operand_is_open(checker, expr.left, locals, pkg, file)
|
|
case .Add, .Sub, .Mul, .Div:
|
|
return numeric_operand_is_open(checker, expr.left, locals, pkg, file) ||
|
|
numeric_operand_is_open(checker, expr.right, locals, pkg, file)
|
|
}
|
|
// A bare integer/float literal adapts freely, so it too is provisional.
|
|
return is_numeric_constant_expr(checker, expr_id)
|
|
}
|
|
|
|
expr_accepts_numeric_demand :: proc(
|
|
checker: ^Checker,
|
|
expr_id: ast.Expr_Id,
|
|
demand: types.Type,
|
|
locals: []Infer_Local,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
) -> bool {
|
|
if !is_numeric_demand(demand, checker.target) ||
|
|
expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
|
|
return false
|
|
}
|
|
if constant := eval_integer_constant_in_context(checker, expr_id, pkg, file); constant.kind == .Value {
|
|
return open_integer_accepts_demand(checker, constant.value, demand)
|
|
}
|
|
if is_float_constant_expr(checker, expr_id) {
|
|
return open_float_accepts_demand(checker, demand)
|
|
}
|
|
expr := checker.ast_module.exprs[expr_id]
|
|
#partial switch expr.kind {
|
|
case .Name:
|
|
if !symbol.is_valid(expr.qualifier) {
|
|
if index, ok := find_infer_local_index(locals, expr.name); ok {
|
|
local := locals[index]
|
|
return local.open_const && open_integer_accepts_demand(checker, local.const_value, demand) ||
|
|
local.open_float && open_float_accepts_demand(checker, demand)
|
|
}
|
|
}
|
|
target_pkg, available := expr_package(checker, expr, pkg, file)
|
|
if !available {
|
|
return false
|
|
}
|
|
global := find_global(checker, expr.name, target_pkg, expr_lookup_file(expr, file))
|
|
index := int(global)
|
|
if global == ast.INVALID_GLOBAL || index < 0 || index >= len(checker.global_open_const) {
|
|
return false
|
|
}
|
|
return checker.global_open_const[index] &&
|
|
open_integer_accepts_demand(checker, checker.global_const_value[index], demand) ||
|
|
checker.global_open_float[index] && open_float_accepts_demand(checker, demand)
|
|
case .Negate:
|
|
if !types.is_signed(demand, checker.target) && !types.is_float(demand, checker.target) {
|
|
return false
|
|
}
|
|
return expr_accepts_numeric_demand(checker, expr.left, demand, locals, pkg, file)
|
|
case .Add, .Sub, .Mul, .Div:
|
|
return expr_accepts_numeric_demand(checker, expr.left, demand, locals, pkg, file) &&
|
|
expr_accepts_numeric_demand(checker, expr.right, demand, locals, pkg, file)
|
|
}
|
|
return false
|
|
}
|
|
|
|
DEMAND_RECURSION_LIMIT :: 4096
|
|
|
|
record_demand_too_deep :: proc(checker: ^Checker, expr_id: ast.Expr_Id) -> bool {
|
|
stack: [dynamic]ast.Expr_Id
|
|
stack.allocator = checker.allocator
|
|
defer delete(stack)
|
|
append(&stack, expr_id)
|
|
seen := 0
|
|
for len(stack) > 0 {
|
|
current := pop(&stack)
|
|
if current == ast.INVALID_EXPR || int(current) >= len(checker.ast_module.exprs) {
|
|
continue
|
|
}
|
|
seen += 1
|
|
if seen > DEMAND_RECURSION_LIMIT {
|
|
return true
|
|
}
|
|
expr := checker.ast_module.exprs[current]
|
|
#partial switch expr.kind {
|
|
case .Negate:
|
|
append(&stack, expr.left)
|
|
case .Add, .Sub, .Mul, .Div:
|
|
append(&stack, expr.left, expr.right)
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
record_demand_shallow :: proc(
|
|
checker: ^Checker,
|
|
expr_id: ast.Expr_Id,
|
|
demand: types.Type,
|
|
locals: []Infer_Local,
|
|
local_types: []types.Type,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
) -> bool {
|
|
if record_demand_too_deep(checker, expr_id) {
|
|
return false
|
|
}
|
|
return record_demand(checker, expr_id, demand, locals, local_types, pkg, file)
|
|
}
|
|
|
|
inferred_record_field_slot_from_expr :: proc(
|
|
checker: ^Checker,
|
|
expr: ast.Expr,
|
|
locals: []Infer_Local,
|
|
local_types: []types.Type,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
) -> (int, bool) {
|
|
base := types.INVALID
|
|
field_name := expr.name
|
|
if expr.kind == .Field {
|
|
base = infer_nested_expr(checker, expr.left, locals, pkg, file, nil, local_types)
|
|
} else if expr.kind == .Name && symbol.is_valid(expr.qualifier) &&
|
|
find_import(checker, file, expr.qualifier) == ast.INVALID_IMPORT {
|
|
base = find_infer_local(locals, expr.qualifier)
|
|
if !types.is_valid(base) {
|
|
if global := find_global(checker, expr.qualifier, pkg, file); global != ast.INVALID_GLOBAL {
|
|
base = checker.global_types[global]
|
|
}
|
|
}
|
|
} else {
|
|
return -1, false
|
|
}
|
|
if types.is_pointer(base, &checker.module.types) {
|
|
base = types.child_type(base, &checker.module.types)
|
|
}
|
|
slot, _, ok := find_struct_field_slot(checker, base, field_name)
|
|
return slot, ok && is_inferred_record_field(checker, slot)
|
|
}
|
|
|
|
// record_demand pushes a concrete type demand onto open numeric slots reachable
|
|
// through bare names, inferred record fields, and numeric arithmetic. Calls remain
|
|
// a boundary (milestone 14.5).
|
|
record_demand :: proc(
|
|
checker: ^Checker,
|
|
expr_id: ast.Expr_Id,
|
|
demand: types.Type,
|
|
locals: []Infer_Local,
|
|
local_types: []types.Type,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
) -> bool {
|
|
if !is_runtime_type(checker, demand) ||
|
|
expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
|
|
return false
|
|
}
|
|
expr := checker.ast_module.exprs[expr_id]
|
|
#partial switch expr.kind {
|
|
case .Name:
|
|
if symbol.is_valid(expr.qualifier) {
|
|
if slot, ok := inferred_record_field_slot_from_expr(
|
|
checker, expr, locals, local_types, pkg, file,
|
|
); ok {
|
|
return merge_record_field_demand(checker, slot, demand, expr.span)
|
|
}
|
|
}
|
|
if !symbol.is_valid(expr.qualifier) {
|
|
if index, ok := find_infer_local_index(locals, expr.name); ok {
|
|
return merge_local_demand(checker, &locals[index], demand, local_types)
|
|
}
|
|
}
|
|
target_pkg, available := expr_package(checker, expr, pkg, file)
|
|
if !available {
|
|
return false
|
|
}
|
|
global := find_global(checker, expr.name, target_pkg, expr_lookup_file(expr, file))
|
|
if global != ast.INVALID_GLOBAL {
|
|
return merge_global_demand(checker, global, demand)
|
|
}
|
|
case .Field:
|
|
if slot, ok := inferred_record_field_slot_from_expr(
|
|
checker, expr, locals, local_types, pkg, file,
|
|
); ok {
|
|
return merge_record_field_demand(checker, slot, demand, expr.span)
|
|
}
|
|
case .Negate:
|
|
if types.is_signed(demand, checker.target) || types.is_float(demand, checker.target) {
|
|
return record_demand(checker, expr.left, demand, locals, local_types, pkg, file)
|
|
}
|
|
case .Add, .Sub, .Mul, .Div:
|
|
if is_numeric_demand(demand, checker.target) {
|
|
left := record_demand(checker, expr.left, demand, locals, local_types, pkg, file)
|
|
right := record_demand(checker, expr.right, demand, locals, local_types, pkg, file)
|
|
return left || right
|
|
}
|
|
case .Call:
|
|
if division_builtin_call(checker, expr) != .None && len(expr.args) == 2 && is_numeric_demand(demand, checker.target) {
|
|
left := record_demand(checker, expr.args[0], demand, locals, local_types, pkg, file)
|
|
right := record_demand(checker, expr.args[1], demand, locals, local_types, pkg, file)
|
|
return left || right
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
infer_all :: proc(checker: ^Checker) {
|
|
// An "open constant" global has no concrete declared type and a compile-time
|
|
// numeric initializer. Its slot can adopt a backward demand from any reachable use.
|
|
// Demands accumulate in global_demands so the default never blocks a later
|
|
// cross-family demand (e.g. integer literal -> unsigned or float).
|
|
for global, index in checker.ast_module.globals {
|
|
if global.diagnostic != source.INVALID_DIAGNOSTIC {
|
|
checker.global_types[index] = types.I64
|
|
continue
|
|
}
|
|
declared := resolve_inferred_array(
|
|
checker,
|
|
type_from_syntax(checker, global.type, global.pkg, global.file),
|
|
global.expr,
|
|
)
|
|
if is_runtime_type(checker, declared) && !has_inferred_array_count(checker, declared) {
|
|
checker.global_types[index] = declared
|
|
continue
|
|
}
|
|
if global.external {
|
|
continue
|
|
}
|
|
if global.expr != ast.INVALID_EXPR && int(global.expr) < len(checker.ast_module.exprs) {
|
|
expr := checker.ast_module.exprs[global.expr]
|
|
if builtin := type_builtin_call(checker, expr); builtin != .None {
|
|
if builtin == .Size_Of || builtin == .Align_Of {
|
|
checker.global_types[index] = types.USIZE
|
|
} else if len(expr.args) == 1 {
|
|
target, ok := resolve_type_argument(checker, expr.args[0], global.pkg, global.file)
|
|
if ok && types.is_concrete_integer(target) {
|
|
checker.global_types[index] = target
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
}
|
|
constant := eval_integer_constant_in_context(checker, global.expr, global.pkg, global.file)
|
|
if constant.kind == .Value &&
|
|
(fits_i64(constant.value) || declared == types.UINT && fits_u64(constant.value)) {
|
|
checker.global_open_const[index] = true
|
|
checker.global_const_value[index] = constant.value
|
|
} else if is_float_constant_expr(checker, global.expr) {
|
|
checker.global_open_float[index] = true
|
|
}
|
|
}
|
|
|
|
for function, index in checker.ast_module.functions {
|
|
if function.analysis_root {
|
|
ensure_spec(checker, ast.function_id(index), nil)
|
|
}
|
|
}
|
|
main_template := find_template(checker, checker.main_symbol, 0)
|
|
if main_template != ast.INVALID_FUNCTION {
|
|
ensure_spec(checker, main_template, nil)
|
|
}
|
|
if checker.entry_point == .Process {
|
|
ensure_spec(checker, checker.io_provider_template, nil)
|
|
}
|
|
|
|
defaults_applied := false
|
|
for {
|
|
changed := false
|
|
checker.global_demands_dirty = false
|
|
checker.record_field_demands_dirty = false
|
|
spec_count := len(checker.specs)
|
|
|
|
for field_default in checker.ast_module.struct_field_defaults {
|
|
slot, field, ok := find_struct_field_slot(checker, field_default.record, field_default.field)
|
|
if !ok {
|
|
continue
|
|
}
|
|
expected := field.type if is_runtime_type(checker, field.type) else types.INVALID
|
|
inferred := infer_expr(
|
|
checker, field_default.expr, nil, field_default.pkg, field_default.file,
|
|
expected=expected,
|
|
)
|
|
_ = record_field_expr_candidate(
|
|
checker, slot, field_default.expr, inferred, nil, field_default.pkg, field_default.file,
|
|
)
|
|
if is_runtime_type(checker, checker.module.types.fields[slot].type) {
|
|
_ = record_demand(
|
|
checker, field_default.expr, checker.module.types.fields[slot].type,
|
|
nil, nil, field_default.pkg, field_default.file,
|
|
)
|
|
}
|
|
}
|
|
|
|
// Backward demands: a global pushes its own (declared or already-resolved) type
|
|
// onto open numeric slots reachable through names and numeric arithmetic.
|
|
for global, index in checker.ast_module.globals {
|
|
if global.external || global.diagnostic != source.INVALID_DIAGNOSTIC {
|
|
continue
|
|
}
|
|
demand := checker.global_types[index]
|
|
if !is_runtime_type(checker, demand) {
|
|
continue
|
|
}
|
|
_ = record_demand(checker, global.expr, demand, nil, nil, global.pkg, global.file)
|
|
}
|
|
|
|
// Forward / resolution. infer_expr runs for every non-external global (even
|
|
// concrete-typed ones) for its side effect of specializing called functions and
|
|
// recording demands from call arguments in their initializers.
|
|
for global, index in checker.ast_module.globals {
|
|
if global.external || global.diagnostic != source.INVALID_DIAGNOSTIC {
|
|
continue
|
|
}
|
|
declared := resolve_inferred_array(
|
|
checker,
|
|
type_from_syntax(checker, global.type, global.pkg, global.file),
|
|
global.expr,
|
|
)
|
|
expected := declared if is_runtime_type(checker, declared) || declared == types.UINT else types.INVALID
|
|
inferred := infer_expr(checker, global.expr, nil, global.pkg, global.file, expected=expected)
|
|
if resolved := resolve_inferred_array_from_type(checker, declared, inferred);
|
|
resolved != declared {
|
|
if !types.equal(checker.global_types[index], resolved) {
|
|
checker.global_types[index] = resolved
|
|
changed = true
|
|
}
|
|
continue
|
|
}
|
|
if is_runtime_type(checker, declared) && !has_inferred_array_count(checker, declared) {
|
|
continue
|
|
}
|
|
if types.is_constraint(declared) {
|
|
resolved := types.INVALID
|
|
if is_runtime_type(checker, checker.global_demands[index]) &&
|
|
types.constraint_accepts(declared, checker.global_demands[index], &checker.module.types) {
|
|
resolved = checker.global_demands[index]
|
|
} else {
|
|
resolved = types.constraint_target(declared, inferred, &checker.module.types)
|
|
}
|
|
if is_runtime_type(checker, resolved) && !types.equal(checker.global_types[index], resolved) {
|
|
checker.global_types[index] = resolved
|
|
changed = true
|
|
}
|
|
continue
|
|
}
|
|
if is_runtime_type(checker, checker.global_demands[index]) {
|
|
// A backward demand is authoritative; assign directly (it may cross the
|
|
// signed/unsigned family that widening would reject).
|
|
if !types.equal(checker.global_types[index], checker.global_demands[index]) {
|
|
checker.global_types[index] = checker.global_demands[index]
|
|
changed = true
|
|
}
|
|
} else if checker.global_open_const[index] || checker.global_open_float[index] {
|
|
// Defer defaulting: an undemanded open constant stays typeless during the
|
|
// fixpoint so its provisional smallest-signed default never leaks as a
|
|
// demand and poisons a sibling open constant used in the same arithmetic
|
|
// (e.g. the lagging type in `x += speed`). The pass after this loop assigns
|
|
// the default once the fixpoint settles and no further demand can arrive.
|
|
} else {
|
|
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)
|
|
before := checker.specs[id].result
|
|
inferred := infer_spec_result(checker, id)
|
|
changed = !types.equal(before, checker.specs[id].result) || changed
|
|
changed = merge_inferred_type(&checker.module.types, &checker.specs[id].result, inferred) || changed
|
|
}
|
|
if len(checker.specs) != spec_count {
|
|
changed = true
|
|
}
|
|
// A demand pushed onto a global from inside a function body (via the spec loop)
|
|
// is picked up by the next pass's resolution, so keep iterating for it.
|
|
if checker.global_demands_dirty {
|
|
changed = true
|
|
}
|
|
if checker.record_field_demands_dirty {
|
|
changed = true
|
|
}
|
|
if !changed {
|
|
if !defaults_applied {
|
|
defaults_applied = true
|
|
defaulted := false
|
|
// No authoritative demand can still arrive. Assign final defaults, then
|
|
// continue the same fixpoint so dependent globals/specs observe them.
|
|
for global, index in checker.ast_module.globals {
|
|
if global.external || global.diagnostic != source.INVALID_DIAGNOSTIC ||
|
|
is_runtime_type(checker, checker.global_types[index]) {
|
|
continue
|
|
}
|
|
if checker.global_open_const[index] {
|
|
declared := type_from_syntax(checker, global.type, global.pkg, global.file)
|
|
checker.global_types[index] = constraint_integer_literal_type(declared, checker.global_const_value[index])
|
|
defaulted = true
|
|
} else if checker.global_open_float[index] {
|
|
checker.global_types[index] = types.F64
|
|
defaulted = true
|
|
}
|
|
}
|
|
for fallback, slot in checker.record_field_defaults {
|
|
if !is_inferred_record_field(checker, slot) ||
|
|
!types.is_constraint(checker.module.types.fields[slot].type) ||
|
|
!is_runtime_type(checker, fallback) {
|
|
continue
|
|
}
|
|
checker.module.types.fields[slot].type = fallback
|
|
defaulted = true
|
|
}
|
|
if defaulted {
|
|
continue
|
|
}
|
|
}
|
|
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)
|
|
}
|
|
if checker.entry_point == .Process {
|
|
mark_spec_demanded(checker, find_spec(checker, checker.io_provider_template, nil), &stack)
|
|
}
|
|
for global, index in checker.ast_module.globals {
|
|
if global.external || global.diagnostic != source.INVALID_DIAGNOSTIC {
|
|
continue
|
|
}
|
|
expected := checker.global_types[index] if is_runtime_type(checker, checker.global_types[index]) else types.INVALID
|
|
_ = infer_expr(checker, global.expr, nil, global.pkg, global.file, &stack, expected=expected)
|
|
}
|
|
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)
|
|
delete(spec.comptime_values, 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,
|
|
},
|
|
)
|
|
}
|
|
|
|
invalid_expr_diagnostic :: proc(checker: ^Checker, id: hir.Expr_Id) -> (source.Diagnostic_Id, bool) {
|
|
if id == hir.INVALID_EXPR || int(id) >= len(checker.module.exprs) {
|
|
return source.INVALID_DIAGNOSTIC, false
|
|
}
|
|
expr := checker.module.exprs[id]
|
|
return expr.diagnostic, expr.kind == .Invalid && expr.diagnostic != source.INVALID_DIAGNOSTIC
|
|
}
|
|
|
|
propagate_invalid_expr :: proc(checker: ^Checker, span: source.Span, values: ..hir.Expr_Id) -> (hir.Expr_Id, bool) {
|
|
for value in values {
|
|
if diagnostic, invalid := invalid_expr_diagnostic(checker, value); invalid {
|
|
return invalid_hir_expr(checker, span, diagnostic), true
|
|
}
|
|
}
|
|
return hir.INVALID_EXPR, false
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
can_implicitly_convert_type :: proc(checker: ^Checker, actual, expected: types.Type) -> bool {
|
|
store := &checker.module.types
|
|
if types.is_noreturn(actual) ||
|
|
types.equal(actual, expected) ||
|
|
types.can_widen(actual, expected) ||
|
|
types.can_coerce_c_integer(actual, expected, checker.target) ||
|
|
types.can_coerce_c_scalar(actual, expected, checker.target) ||
|
|
types.can_weaken_pointer(actual, expected, store) ||
|
|
types.can_coerce_function_pointer(actual, expected, store) ||
|
|
types.can_weaken_slice(actual, expected, store) ||
|
|
types.can_decay_slice_c_string(actual, expected, store) ||
|
|
types.can_decay_array_pointer(actual, expected, store) ||
|
|
types.can_sum_widen(actual, expected, store) {
|
|
return true
|
|
}
|
|
if types.is_optional(expected, store) {
|
|
return can_implicitly_convert_type(checker, actual, types.child_type(expected, store))
|
|
}
|
|
return 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
|
|
}
|
|
if _, invalid := invalid_expr_diagnostic(checker, expr_id); invalid {
|
|
return expr_id
|
|
}
|
|
actual := checker.module.exprs[expr_id].type
|
|
if types.is_noreturn(actual) {
|
|
return expr_id
|
|
}
|
|
if types.equal(actual, expected) {
|
|
return expr_id
|
|
}
|
|
if types.can_coerce_function_pointer(actual, expected, &checker.module.types) {
|
|
expr := checker.module.exprs[expr_id]
|
|
if expr.kind == .Function {
|
|
expr.type = expected
|
|
return add_hir_expr(checker, expr)
|
|
}
|
|
}
|
|
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.can_decay_slice_c_string(actual, expected, &checker.module.types) {
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Slice_Ptr,
|
|
span=span,
|
|
type=expected,
|
|
left=expr_id,
|
|
target=hir.INVALID_REF,
|
|
right=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
if types.can_sum_widen(actual, expected, &checker.module.types) {
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Sum_Widen,
|
|
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_coerce_function_pointer(actual, child, &checker.module.types) ||
|
|
types.can_widen(actual, child) ||
|
|
types.can_coerce_c_integer(actual, child, checker.target) ||
|
|
types.can_coerce_c_scalar(actual, child, checker.target) ||
|
|
types.can_weaken_pointer(actual, child, &checker.module.types) ||
|
|
types.can_weaken_slice(actual, child, &checker.module.types) ||
|
|
types.can_decay_slice_c_string(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,
|
|
},
|
|
)
|
|
}
|
|
if types.can_coerce_c_integer(actual, expected, checker.target) ||
|
|
types.can_coerce_c_scalar(actual, expected, checker.target) {
|
|
return add_hir_expr(
|
|
checker,
|
|
hir.Expr {
|
|
kind = .C_Coerce,
|
|
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",
|
|
type_label(checker, actual),
|
|
type_label(checker, 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
|
|
}
|
|
// An integer constant in a float context (e.g. `pi float = 3`) folds to a
|
|
// float literal, mirroring build_float_expr's bit packing.
|
|
if constant.kind == .Value && types.is_float(expected, checker.target) {
|
|
fval := f64(constant.value) // ponytail: silent precision loss past 2^53, like C int->double
|
|
bits := transmute(i64)fval
|
|
if types.bits(expected, checker.target) == 32 {
|
|
bits = i64(transmute(u32)f32(fval))
|
|
}
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind = .Float,
|
|
span = expr.span,
|
|
type = expected,
|
|
integer = bits,
|
|
target = hir.INVALID_REF,
|
|
left = hir.INVALID_EXPR,
|
|
right = hir.INVALID_EXPR,
|
|
diagnostic = source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
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 == .Non_Exact {
|
|
id := source.add(checker.diagnostics, expr.span, "exact division has a remainder")
|
|
return invalid_hir_expr(checker, expr.span, id, recovery_type)
|
|
}
|
|
if constant.kind == .Integer_Division {
|
|
id := source.add(checker.diagnostics, expr.span, "integer '/' is not allowed; use divtrunc!, divfloor!, divexact!, or divceil!")
|
|
return invalid_hir_expr(checker, expr.span, id, recovery_type)
|
|
}
|
|
if constant.kind == .Overflow ||
|
|
(!types.is_concrete_integer(expected) && expected != types.UINT && !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 := constraint_integer_literal_type(expected, constant.value)
|
|
if expected == types.UINT && !types.is_valid(result_type) {
|
|
id := source.addf(
|
|
checker.diagnostics,
|
|
expr.span,
|
|
"integer constant %d does not satisfy uint",
|
|
constant.value,
|
|
)
|
|
return invalid_hir_expr(checker, expr.span, id, types.U64)
|
|
}
|
|
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,
|
|
arg_mode: Call_Argument_Mode,
|
|
prefix: int,
|
|
mapping: []int,
|
|
built_args: []hir.Expr_Id,
|
|
arg_types: []types.Type,
|
|
template: ast.Function_Id,
|
|
resolution: int,
|
|
}
|
|
|
|
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_struct_field_default :: proc(
|
|
checker: ^Checker,
|
|
struct_type: types.Type,
|
|
name: symbol.Id,
|
|
) -> (Resolved_Field_Default, []Comptime_Value, bool) {
|
|
resolved := types.resolve_alias(struct_type, &checker.module.types)
|
|
for field_default in checker.ast_module.struct_field_defaults {
|
|
if types.resolve_alias(field_default.record, &checker.module.types) == resolved &&
|
|
field_default.field == name {
|
|
return Resolved_Field_Default{
|
|
expr=field_default.expr,
|
|
pkg=field_default.pkg,
|
|
file=field_default.file,
|
|
static_value=INVALID_CT_VALUE,
|
|
span=checker.ast_module.exprs[field_default.expr].span,
|
|
}, nil, true
|
|
}
|
|
}
|
|
for entry in checker.generated_types {
|
|
if !types.equal(entry.result, resolved) || entry.expr == ast.INVALID_EXPR ||
|
|
int(entry.expr) >= len(checker.ast_module.exprs) {
|
|
continue
|
|
}
|
|
expr := checker.ast_module.exprs[entry.expr]
|
|
fields := types.fields_for(&checker.module.types, resolved)
|
|
for field, index in fields {
|
|
if field.name != u32(name) {
|
|
continue
|
|
}
|
|
if index < len(entry.defaults) && entry.defaults[index] != INVALID_CT_VALUE {
|
|
return Resolved_Field_Default{
|
|
expr=ast.INVALID_EXPR,
|
|
pkg=entry.pkg,
|
|
file=entry.file,
|
|
static_value=entry.defaults[index],
|
|
span=expr.span,
|
|
}, entry.values, true
|
|
}
|
|
if is_intrinsic_call(checker, expr, "struct_type") {
|
|
continue
|
|
}
|
|
if index < len(expr.args) && expr.args[index] != ast.INVALID_EXPR {
|
|
return Resolved_Field_Default{
|
|
expr=expr.args[index],
|
|
pkg=entry.pkg,
|
|
file=entry.file,
|
|
static_value=INVALID_CT_VALUE,
|
|
span=checker.ast_module.exprs[expr.args[index]].span,
|
|
}, entry.values, true
|
|
}
|
|
}
|
|
}
|
|
return {}, nil, false
|
|
}
|
|
|
|
find_tuple_field :: proc(checker: ^Checker, tuple_type: types.Type, index: u64) -> (int, types.Field, bool) {
|
|
item, ok := types.node(&checker.module.types, tuple_type)
|
|
if !ok || item.kind != .Struct || !item.tuple || index >= u64(item.field_count) {
|
|
return 0, {}, false
|
|
}
|
|
fields := types.fields_for(&checker.module.types, tuple_type)
|
|
return int(index), fields[index], true
|
|
}
|
|
|
|
find_struct_field_slot :: proc(checker: ^Checker, struct_type: types.Type, name: symbol.Id) -> (int, types.Field, bool) {
|
|
item, ok := types.node(&checker.module.types, struct_type)
|
|
if !ok || (item.kind != .Struct && item.kind != .Union) {
|
|
return -1, {}, false
|
|
}
|
|
for field, index in types.fields_for(&checker.module.types, struct_type) {
|
|
if field.name == u32(name) {
|
|
return int(item.field_start)+index, field, true
|
|
}
|
|
}
|
|
return -1, {}, false
|
|
}
|
|
|
|
field_type_from_value :: proc(checker: ^Checker, expr: ast.Expr, base_type: types.Type) -> types.Type {
|
|
store := &checker.module.types
|
|
if !symbol.is_valid(expr.name) {
|
|
value_type := types.child_type(base_type, store) if types.is_pointer(base_type, store) else base_type
|
|
_, field, ok := find_tuple_field(checker, value_type, expr.integer)
|
|
return field.type if ok else types.INVALID
|
|
}
|
|
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 types.USIZE
|
|
}
|
|
if field_name == "ptr" &&
|
|
(item.kind == .Slice || types.is_pointer(base_type, store)) {
|
|
return container_pointer_type(store, item)
|
|
}
|
|
}
|
|
value_type := base_type
|
|
if types.is_pointer(value_type, store) {
|
|
value_type = types.child_type(value_type, store)
|
|
}
|
|
_, field, ok := find_struct_field(checker, value_type, expr.name)
|
|
return field.type if ok else types.INVALID
|
|
}
|
|
|
|
infer_qualified_value_field_type :: proc(
|
|
checker: ^Checker,
|
|
expr: ast.Expr,
|
|
locals: []Infer_Local,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
demanded: ^[dynamic]Spec_Id = nil,
|
|
) -> (types.Type, bool) {
|
|
if !symbol.is_valid(expr.qualifier) ||
|
|
find_import(checker, file, expr.qualifier) != ast.INVALID_IMPORT {
|
|
return types.INVALID, false
|
|
}
|
|
base_type := find_infer_local(locals, expr.qualifier)
|
|
if !types.is_valid(base_type) {
|
|
if value, ok := current_comptime_value(checker, expr.qualifier); ok && value.kind == .Static {
|
|
base_type = value.type
|
|
if field_value, found := persistent_field_value(checker, value.static_value, expr.name);
|
|
found && field_value.kind == .Function {
|
|
_, _, _ = function_pointer_type_for_template(
|
|
checker, ast.Function_Id(u32(field_value.index)), demanded,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
if !types.is_valid(base_type) {
|
|
if global := find_global(checker, expr.qualifier, pkg, file); global != ast.INVALID_GLOBAL {
|
|
base_type = checker.global_types[global]
|
|
}
|
|
}
|
|
if !types.is_valid(base_type) {
|
|
return types.INVALID, false
|
|
}
|
|
return field_type_from_value(checker, expr, base_type), true
|
|
}
|
|
|
|
build_field_from_value :: proc(
|
|
checker: ^Checker,
|
|
expr: ast.Expr,
|
|
base: hir.Expr_Id,
|
|
base_type: types.Type,
|
|
) -> (hir.Expr_Id, bool) {
|
|
store := &checker.module.types
|
|
if !symbol.is_valid(expr.name) {
|
|
value_type := types.child_type(base_type, store) if types.is_pointer(base_type, store) else base_type
|
|
index, field, ok := find_tuple_field(checker, value_type, expr.integer)
|
|
if !ok {
|
|
id := source.addf(checker.diagnostics, expr.span, "tuple field index %d is out of range or the value is not a tuple", expr.integer)
|
|
return invalid_hir_expr(checker, expr.span, id), false
|
|
}
|
|
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,
|
|
}), true
|
|
}
|
|
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,
|
|
}), true
|
|
}
|
|
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,
|
|
}), true
|
|
}
|
|
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), false
|
|
}
|
|
}
|
|
value_type := base_type
|
|
if types.is_pointer(value_type, store) {
|
|
value_type = types.child_type(value_type, store)
|
|
}
|
|
index, field, ok := find_struct_field(checker, value_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), false
|
|
}
|
|
if types.is_void(field.type) {
|
|
id := source.addf(checker.diagnostics, expr.span, "variant '%s' has no payload to read", symbol_text(checker, expr.name))
|
|
return invalid_hir_expr(checker, expr.span, id), false
|
|
}
|
|
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,
|
|
}), true
|
|
}
|
|
|
|
build_qualified_value_field :: proc(
|
|
checker: ^Checker,
|
|
expr: ast.Expr,
|
|
locals: []Build_Local,
|
|
global_reads: ^[dynamic]hir.Global_Id,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
) -> (hir.Expr_Id, bool, bool) {
|
|
if !symbol.is_valid(expr.qualifier) ||
|
|
find_import(checker, file, expr.qualifier, true) != ast.INVALID_IMPORT {
|
|
return hir.INVALID_EXPR, false, false
|
|
}
|
|
if local, ok := find_build_local(locals, expr.qualifier); ok {
|
|
base := build_local_expr(checker, local, expr.span)
|
|
value, ok := build_field_from_value(checker, expr, base, local.type)
|
|
return value, true, ok
|
|
}
|
|
if value, ok := current_comptime_value(checker, expr.qualifier);
|
|
ok && value.kind == .Static && value.static_value != INVALID_CT_VALUE &&
|
|
int(value.static_value) < len(checker.static_state.values) {
|
|
field_value, field_ok := persistent_field_value(checker, value.static_value, expr.name)
|
|
if !field_ok {
|
|
return hir.INVALID_EXPR, true, false
|
|
}
|
|
return build_static_value(checker, field_value, expr.span, types.INVALID), true, true
|
|
}
|
|
if global := find_global(checker, expr.qualifier, pkg, file); global != ast.INVALID_GLOBAL {
|
|
base := build_global_reference(checker, global, expr.span, global_reads)
|
|
value, ok := build_field_from_value(checker, expr, base, checker.global_types[global])
|
|
return value, true, ok
|
|
}
|
|
return hir.INVALID_EXPR, false, 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), file=u32(file))
|
|
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), file=u32(expr_lookup_file(base, file)))
|
|
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_scalar_cast :: proc(
|
|
checker: ^Checker,
|
|
value: hir.Expr_Id,
|
|
target: types.Type,
|
|
span: source.Span,
|
|
) -> hir.Expr_Id {
|
|
store := &checker.module.types
|
|
actual := checker.module.exprs[value].type
|
|
valid_target := types.is_concrete_scalar(target) && !types.is_bool(target)
|
|
actual_repr := types.runtime_representation(actual, store)
|
|
actual_item, actual_item_ok := types.node(store, actual)
|
|
explicit_enum := actual_item_ok && actual_item.kind == .Enum && actual_item.explicit_backing
|
|
valid_actual := (types.is_concrete_scalar(actual) || explicit_enum) &&
|
|
types.is_concrete_scalar(actual_repr) && !types.is_bool(actual_repr)
|
|
if !valid_target || !valid_actual {
|
|
id := source.addf(
|
|
checker.diagnostics,
|
|
span,
|
|
"scalar cast requires numeric scalar types, got %s to %s",
|
|
types.name(actual),
|
|
types.name(target),
|
|
)
|
|
return invalid_hir_expr(checker, span, id, target)
|
|
}
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Scalar_Cast,
|
|
span=span,
|
|
type=target,
|
|
left=value,
|
|
target=hir.INVALID_REF,
|
|
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 function value; expected a concrete non-comptime signature",
|
|
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, function.c_abi, function.variadic)
|
|
value_type := function_type
|
|
expected_pointer := expected
|
|
if types.is_optional(expected_pointer, &checker.module.types) {
|
|
expected_pointer = types.child_type(expected_pointer, &checker.module.types)
|
|
}
|
|
if _, _, expected_function, ok := types.function_pointer(expected_pointer, &checker.module.types); ok &&
|
|
types.equal(expected_function, function_type) {
|
|
value_type = expected_pointer
|
|
} else if types.equal(expected, function_type) {
|
|
value_type = expected
|
|
}
|
|
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, value_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=value_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
|
|
}
|
|
|
|
try_build_comptime_division :: proc(
|
|
checker: ^Checker,
|
|
expr: ast.Expr,
|
|
kind: Division_Builtin,
|
|
expected: types.Type,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
) -> (hir.Expr_Id, bool) {
|
|
state := ct_state_make(checker, pkg, file, diagnose=false)
|
|
defer ct_state_destroy(&state)
|
|
value, flow, ok := ct_eval_division_call(&state, expr, kind, expected, 0)
|
|
if ok && flow.kind == .Normal && value != INVALID_CT_VALUE {
|
|
return ct_materialize_value(&state, value, expr.span, expected), true
|
|
}
|
|
message := ""
|
|
#partial switch state.error {
|
|
case .Div_By_Zero: message = "division builtin denominator is zero"
|
|
case .Overflow: message = "signed integer division overflow"
|
|
case .Non_Exact: message = "exact division has a remainder"
|
|
}
|
|
if len(message) == 0 {
|
|
return hir.INVALID_EXPR, false
|
|
}
|
|
id := source.add(checker.diagnostics, expr.span, message)
|
|
recovery := expected if types.is_concrete_scalar(expected) else types.I64
|
|
return invalid_hir_expr(checker, expr.span, id, recovery), true
|
|
}
|
|
|
|
build_division_builtin :: proc(
|
|
checker: ^Checker,
|
|
expr: ast.Expr,
|
|
kind: Division_Builtin,
|
|
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 {
|
|
if len(expr.args) != 2 {
|
|
id := source.addf(
|
|
checker.diagnostics, expr.span, "%s! expects 2 arguments, got %d",
|
|
symbol_text(checker, expr.name), len(expr.args),
|
|
)
|
|
return invalid_hir_expr(checker, expr.span, id)
|
|
}
|
|
if value, handled := try_build_comptime_division(checker, expr, kind, expected, pkg, file); handled {
|
|
return value
|
|
}
|
|
hint := expected if types.is_concrete_scalar(expected) && !types.is_bool(expected) else types.INVALID
|
|
left_const := is_numeric_constant_expr(checker, expr.args[0])
|
|
right_const := is_numeric_constant_expr(checker, expr.args[1])
|
|
left, right := hir.INVALID_EXPR, hir.INVALID_EXPR
|
|
if left_const && !right_const && !types.is_valid(hint) {
|
|
right = build_nested_expr(checker, expr.args[1], locals, global_reads, calls, types.INVALID, pkg, file)
|
|
left = build_nested_expr(checker, expr.args[0], locals, global_reads, calls, checker.module.exprs[right].type, pkg, file)
|
|
} else {
|
|
left = build_nested_expr(checker, expr.args[0], locals, global_reads, calls, hint, pkg, file)
|
|
right_hint := hint if types.is_valid(hint) else checker.module.exprs[left].type
|
|
right = build_nested_expr(checker, expr.args[1], locals, global_reads, calls, right_hint, pkg, file)
|
|
}
|
|
result := types.widest(checker.module.exprs[left].type, checker.module.exprs[right].type)
|
|
if !types.is_concrete_scalar(result) || types.is_bool(result) {
|
|
id := source.add(checker.diagnostics, expr.span, "division builtins require compatible numeric operands")
|
|
return invalid_hir_expr(checker, expr.span, id)
|
|
}
|
|
left = coerce_expr(checker, left, result, checker.module.exprs[left].span)
|
|
right = coerce_expr(checker, right, result, checker.module.exprs[right].span)
|
|
result_kind := hir.Expr_Kind.Div_Trunc
|
|
#partial switch kind {
|
|
case .Floor: result_kind = .Div_Floor
|
|
case .Exact: result_kind = .Div_Exact
|
|
case .Ceil: result_kind = .Div_Ceil
|
|
case .Rem: result_kind = .Rem
|
|
case .Mod: result_kind = .Mod
|
|
case:
|
|
}
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=result_kind, span=expr.span, type=result, left=left, right=right,
|
|
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
|
|
build_memory_builtin :: proc(
|
|
checker: ^Checker,
|
|
expr: ast.Expr,
|
|
kind: Memory_Builtin,
|
|
locals: []Build_Local,
|
|
global_reads: ^[dynamic]hir.Global_Id,
|
|
calls: ^[dynamic]hir.Function_Id,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
) -> hir.Expr_Id {
|
|
name := symbol_text(checker, expr.name)
|
|
if len(expr.args) != 2 {
|
|
id := source.addf(checker.diagnostics, expr.span, "%s! expects 2 arguments, got %d", name, len(expr.args))
|
|
return invalid_hir_expr(checker, expr.span, id, types.VOID)
|
|
}
|
|
destination := build_nested_expr(checker, expr.args[0], locals, global_reads, calls, types.INVALID, pkg, file)
|
|
destination_type := checker.module.exprs[destination].type
|
|
destination_child, destination_mutable, destination_ok := memory_region_type(checker, destination_type)
|
|
if !destination_ok || !destination_mutable {
|
|
id := source.addf(
|
|
checker.diagnostics, checker.ast_module.exprs[expr.args[0]].span,
|
|
"%s! destination must be a mutable slice or mutable pointer-to-array", name,
|
|
)
|
|
return invalid_hir_expr(checker, expr.span, id, types.VOID)
|
|
}
|
|
right := hir.INVALID_EXPR
|
|
result_kind := hir.Expr_Kind.Mem_Copy
|
|
if kind == .Set {
|
|
if is_undefined_expr(checker, expr.args[1]) {
|
|
right = add_hir_expr(checker, hir.Expr{
|
|
kind=.Undefined, span=checker.ast_module.exprs[expr.args[1]].span, type=destination_child,
|
|
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
} else {
|
|
right = build_nested_expr(checker, expr.args[1], locals, global_reads, calls, destination_child, pkg, file)
|
|
right = coerce_expr(checker, right, destination_child, checker.ast_module.exprs[expr.args[1]].span)
|
|
}
|
|
result_kind = .Mem_Set
|
|
} else {
|
|
source_expr := build_nested_expr(checker, expr.args[1], locals, global_reads, calls, types.INVALID, pkg, file)
|
|
source_type := checker.module.exprs[source_expr].type
|
|
source_child, _, source_ok := memory_region_type(checker, source_type)
|
|
if !source_ok {
|
|
id := source.add(
|
|
checker.diagnostics, checker.ast_module.exprs[expr.args[1]].span,
|
|
"memcopy! source must be a slice or pointer-to-array",
|
|
)
|
|
return invalid_hir_expr(checker, expr.span, id, types.VOID)
|
|
}
|
|
if !types.equal(
|
|
types.resolve_alias(destination_child, &checker.module.types),
|
|
types.resolve_alias(source_child, &checker.module.types),
|
|
) {
|
|
id := source.add(checker.diagnostics, expr.span, "memcopy! source and destination element types must match")
|
|
return invalid_hir_expr(checker, expr.span, id, types.VOID)
|
|
}
|
|
right = source_expr
|
|
}
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=result_kind, span=expr.span, type=types.VOID, left=destination, right=right,
|
|
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
|
|
fallible_aggregate :: proc(
|
|
checker: ^Checker,
|
|
span: source.Span,
|
|
channel: types.Type,
|
|
value: hir.Expr_Id,
|
|
error_path: bool,
|
|
) -> hir.Expr_Id {
|
|
values := make([]hir.Expr_Id, 1, checker.allocator)
|
|
values[0] = value
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Struct,
|
|
span=span,
|
|
type=channel,
|
|
integer=1 if error_path else 0,
|
|
args=values,
|
|
target=hir.INVALID_REF,
|
|
left=hir.INVALID_EXPR,
|
|
right=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
|
|
refined_error_projection :: proc(
|
|
ctx: ^Build_Ctx,
|
|
expr_id: hir.Expr_Id,
|
|
target: types.Type,
|
|
span: source.Span,
|
|
) -> (hir.Expr_Id, bool) {
|
|
checker := ctx.checker
|
|
if expr_id == hir.INVALID_EXPR || int(expr_id) >= len(checker.module.exprs) {
|
|
return hir.INVALID_EXPR, false
|
|
}
|
|
expr := checker.module.exprs[expr_id]
|
|
if expr.kind != .Local {
|
|
return hir.INVALID_EXPR, false
|
|
}
|
|
local := hir.as_local(expr.target)
|
|
if local == hir.INVALID_LOCAL || int(local) >= len(ctx.hir_locals^) || ctx.hir_locals^[local].mutable {
|
|
return hir.INVALID_EXPR, false
|
|
}
|
|
candidates: []u32
|
|
for index := len(ctx.error_refinements^) - 1; index >= 0; index -= 1 {
|
|
refinement := ctx.error_refinements^[index]
|
|
if refinement.local == local {
|
|
candidates = refinement.variants
|
|
break
|
|
}
|
|
}
|
|
if len(candidates) == 0 {
|
|
return hir.INVALID_EXPR, false
|
|
}
|
|
selected: [dynamic]u32
|
|
selected.allocator = checker.allocator
|
|
defer delete(selected)
|
|
for candidate in candidates {
|
|
allowed := true
|
|
for refinement in ctx.error_refinements^ {
|
|
if refinement.local != local {
|
|
continue
|
|
}
|
|
found := false
|
|
for variant in refinement.variants {
|
|
found = found || variant == candidate
|
|
}
|
|
allowed = allowed && found
|
|
}
|
|
if allowed {
|
|
append(&selected, candidate)
|
|
}
|
|
}
|
|
if !types.selected_sum_fits(expr.type, target, selected[:], &checker.module.types) {
|
|
return hir.INVALID_EXPR, false
|
|
}
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Sum_Project, span=span, type=target, left=expr_id,
|
|
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
}), true
|
|
}
|
|
|
|
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, declared=local.type, statement=ast.INVALID_STMT}
|
|
}
|
|
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 .Null:
|
|
if !types.is_optional(expected, store) {
|
|
id := source.add(checker.diagnostics, expr.span, "'null' requires an optional context")
|
|
return invalid_hir_expr(checker, expr.span, id, expected)
|
|
}
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Null, span=expr.span, type=expected, target=hir.INVALID_REF,
|
|
left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .Unreachable:
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Unreachable, span=expr.span, type=types.NORETURN, target=hir.INVALID_REF,
|
|
left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .Undefined:
|
|
id := source.add(
|
|
checker.diagnostics,
|
|
expr.span,
|
|
"'undefined' is only valid as a mutable declaration initializer",
|
|
)
|
|
return invalid_hir_expr(checker, expr.span, id, expected)
|
|
case .Enum_Literal:
|
|
literal_expected := expected
|
|
optional_expected := types.is_optional(expected, store)
|
|
if optional_expected {
|
|
literal_expected = types.child_type(expected, store)
|
|
}
|
|
if types.is_tagged_union(literal_expected, store) {
|
|
index, field, found := find_struct_field(checker, literal_expected, expr.name)
|
|
if !found {
|
|
id := source.addf(checker.diagnostics, expr.span, "unknown variant '.%s' on '%s'", symbol_text(checker, expr.name), type_label(checker, literal_expected))
|
|
return invalid_hir_expr(checker, expr.span, id, expected)
|
|
}
|
|
values := make([]hir.Expr_Id, 1, checker.allocator)
|
|
if expr.left == ast.INVALID_EXPR {
|
|
if !types.is_void(field.type) {
|
|
id := source.addf(checker.diagnostics, expr.span, "variant '.%s' on '%s' needs a payload; only void variants can be built from a bare '.%s'",
|
|
symbol_text(checker, expr.name), type_label(checker, literal_expected), symbol_text(checker, expr.name))
|
|
delete(values, checker.allocator)
|
|
return invalid_hir_expr(checker, expr.span, id, expected)
|
|
}
|
|
values[0] = hir.INVALID_EXPR
|
|
} else {
|
|
if types.is_void(field.type) {
|
|
id := source.addf(checker.diagnostics, expr.span, "void variant '%s' takes no value", symbol_text(checker, expr.name))
|
|
delete(values, checker.allocator)
|
|
return invalid_hir_expr(checker, expr.span, id, expected)
|
|
}
|
|
values[0] = build_nested_expr(checker, expr.left, locals, global_reads, calls, field.type, pkg, file)
|
|
values[0] = coerce_expr(checker, values[0], field.type, checker.module.exprs[values[0]].span)
|
|
}
|
|
result := add_hir_expr(checker, hir.Expr{
|
|
kind=.Struct, span=expr.span, type=literal_expected, args=values, integer=i64(index),
|
|
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
return coerce_expr(checker, result, expected, expr.span) if optional_expected else result
|
|
}
|
|
if expr.left != ast.INVALID_EXPR {
|
|
id := source.addf(
|
|
checker.diagnostics,
|
|
expr.span,
|
|
"'.%s{...}' requires a tagged-union context",
|
|
symbol_text(checker, expr.name),
|
|
)
|
|
return invalid_hir_expr(checker, expr.span, id, expected)
|
|
}
|
|
if !types.is_enum(literal_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)
|
|
}
|
|
result := enum_member_hir(checker, literal_expected, expr.name, expr.span)
|
|
return coerce_expr(checker, result, expected, expr.span) if optional_expected else result
|
|
case .Cast:
|
|
target := type_from_syntax(checker, expr.type, pkg, file)
|
|
value := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
|
return build_scalar_cast(checker, value, target, expr.span)
|
|
case .Address:
|
|
// `&<array literal>` (Zig's `&.{...}`): the operand is an rvalue with no
|
|
// address, so promote it to an anonymous global constant and take *its*
|
|
// address. Reuses the existing non-scalar-global storage path; only the
|
|
// stable global address enters the expression, so it never dangles. The
|
|
// resulting `*[N]T` then decays to a slice via the usual coercion.
|
|
if expr.left != ast.INVALID_EXPR && checker.ast_module.exprs[expr.left].kind == .Array {
|
|
operand := checker.ast_module.exprs[expr.left]
|
|
// Propagate an element-expected type through `&` so literal elements
|
|
// coerce to the target slice's element type (e.g. string -> []u8).
|
|
// Without this, `&["x"]` infers `*[1]*[N:0]u8`, which won't decay to
|
|
// `[][]u8` because can_decay_array_pointer requires child equality.
|
|
element := types.INVALID
|
|
if node, ok := types.node(store, expected); ok && (node.kind == .Slice || node.kind == .Array) {
|
|
element = node.child
|
|
}
|
|
synth_expected := types.INVALID
|
|
if types.is_valid(element) {
|
|
synth_expected = types.array(store, element, u64(len(operand.args)), false)
|
|
}
|
|
value := build_nested_expr(checker, expr.left, locals, global_reads, calls, synth_expected, pkg, file)
|
|
array_type := checker.module.exprs[value].type
|
|
hidden_id := hir.Global_Id(len(checker.ast_module.globals) + len(checker.anon_globals))
|
|
append(&checker.anon_globals, hir.Global{
|
|
name = symbol.intern(checker.symbols, "__anon.array"),
|
|
type = array_type,
|
|
expr = value,
|
|
writable = false,
|
|
external = false,
|
|
diagnostic = source.INVALID_DIAGNOSTIC,
|
|
})
|
|
add_unique_global(global_reads, hidden_id)
|
|
global_ref := add_hir_expr(checker, hir.Expr{
|
|
kind=.Global, span=expr.span, type=array_type, target=hir.global_ref(hidden_id),
|
|
left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Address, span=expr.span, type=types.pointer(store, array_type, false, false),
|
|
left=global_ref, target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
value := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
|
if invalid, propagated := propagate_invalid_expr(checker, expr.span, value); propagated {
|
|
return invalid
|
|
}
|
|
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)
|
|
if invalid, propagated := propagate_invalid_expr(checker, expr.span, pointer); propagated {
|
|
return invalid
|
|
}
|
|
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)
|
|
index = coerce_expr(checker, index, types.USIZE, expr.span)
|
|
if invalid, propagated := propagate_invalid_expr(checker, expr.span, container, index); propagated {
|
|
return invalid
|
|
}
|
|
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:
|
|
text, comptime_ok := comptime_slice_bytes(checker, expr, pkg, file)
|
|
if comptime_ok {
|
|
string_id := intern_comptime_string(checker, text)
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.String, span=expr.span, type=string_literal_type(checker, string_id), integer=i64(string_id),
|
|
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
container := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
|
if invalid, propagated := propagate_invalid_expr(checker, expr.span, container); propagated {
|
|
return invalid
|
|
}
|
|
container_type := checker.module.exprs[container].type
|
|
item, ok := types.container(container_type, store)
|
|
if !ok {
|
|
id := source.add(checker.diagnostics, expr.span, "slicing requires an array, slice, pointer-to-array, or many-item pointer")
|
|
return invalid_hir_expr(checker, expr.span, id)
|
|
}
|
|
if item.kind == .Pointer && expr.args[1] == ast.INVALID_EXPR {
|
|
id := source.add(checker.diagnostics, expr.span, "many-item pointer slicing requires an explicit end bound")
|
|
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)
|
|
bounds[index] = coerce_expr(checker, bounds[index], types.USIZE, checker.ast_module.exprs[bound].span)
|
|
if invalid, propagated := propagate_invalid_expr(checker, expr.span, bounds[index]); propagated {
|
|
delete(bounds, checker.allocator)
|
|
return invalid
|
|
}
|
|
}
|
|
}
|
|
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)
|
|
if invalid, propagated := propagate_invalid_expr(checker, expr.span, base); propagated {
|
|
return invalid
|
|
}
|
|
base_type := checker.module.exprs[base].type
|
|
result, _ := build_field_from_value(checker, expr, base, base_type)
|
|
return result
|
|
case .Unwrap:
|
|
optional := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
|
if invalid, propagated := propagate_invalid_expr(checker, expr.span, optional); propagated {
|
|
return invalid
|
|
}
|
|
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)
|
|
if invalid, propagated := propagate_invalid_expr(checker, expr.span, optional); propagated {
|
|
return invalid
|
|
}
|
|
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 .Try:
|
|
if checker.current_build_ctx != nil && checker.current_build_ctx.defer_depth > 0 {
|
|
id := source.add(checker.diagnostics, expr.span, "cannot 'try' inside a 'defer'")
|
|
return invalid_hir_expr(checker, expr.span, id)
|
|
}
|
|
left_expected := expected if checker.ast_module.exprs[expr.left].kind == .Call else types.INVALID
|
|
channel := build_nested_expr(checker, expr.left, locals, global_reads, calls, left_expected, pkg, file)
|
|
if diagnostic, invalid := invalid_expr_diagnostic(checker, channel); invalid {
|
|
return invalid_hir_expr(checker, expr.span, diagnostic)
|
|
}
|
|
channel_type := checker.module.exprs[channel].type
|
|
success := types.fallible_success(channel_type, store)
|
|
if !types.is_valid(success) {
|
|
id := source.add(checker.diagnostics, expr.span, "'try' requires a fallible expression")
|
|
return invalid_hir_expr(checker, expr.span, id)
|
|
}
|
|
enclosing_success := types.fallible_success(checker.current_result, store)
|
|
enclosing_error := types.fallible_error(checker.current_result, store)
|
|
if !types.is_valid(enclosing_success) {
|
|
id := source.add(checker.diagnostics, expr.span, "'try' requires an enclosing fallible function")
|
|
return invalid_hir_expr(checker, expr.span, id, success)
|
|
}
|
|
error_type := types.fallible_error(channel_type, store)
|
|
if !types.equal(error_type, enclosing_error) &&
|
|
!types.can_sum_widen(error_type, enclosing_error, store) {
|
|
id := source.add(checker.diagnostics, expr.span, "'try' error channel cannot be widened to the enclosing error channel")
|
|
return invalid_hir_expr(checker, expr.span, id, success)
|
|
}
|
|
cleanup: []hir.Stmt_Id
|
|
captures: []hir.Expr_Id
|
|
if checker.current_build_ctx != nil {
|
|
cleanup, captures = try_cleanup(checker.current_build_ctx)
|
|
}
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Try,
|
|
span=expr.span,
|
|
type=success,
|
|
left=channel,
|
|
body=cleanup,
|
|
args=captures,
|
|
target=hir.INVALID_REF,
|
|
right=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .Catch:
|
|
left_expected := expected if checker.ast_module.exprs[expr.left].kind == .Call else types.INVALID
|
|
channel := build_nested_expr(checker, expr.left, locals, global_reads, calls, left_expected, pkg, file)
|
|
if diagnostic, invalid := invalid_expr_diagnostic(checker, channel); invalid {
|
|
return invalid_hir_expr(checker, expr.span, diagnostic)
|
|
}
|
|
channel_type := checker.module.exprs[channel].type
|
|
success := types.fallible_success(channel_type, store)
|
|
if !types.is_valid(success) {
|
|
id := source.add(checker.diagnostics, expr.span, "'catch' requires a fallible expression")
|
|
return invalid_hir_expr(checker, expr.span, id)
|
|
}
|
|
body: []hir.Stmt_Id
|
|
capture := hir.INVALID_LOCAL
|
|
block_handler := false
|
|
void_fallthrough := false
|
|
fallback := hir.INVALID_EXPR
|
|
ctx := checker.current_build_ctx
|
|
capture_start := 0
|
|
if symbol.is_valid(expr.name) && expr.name != checker.sink_symbol {
|
|
if ctx == nil {
|
|
id := source.add(checker.diagnostics, expr.span, "a captured catch fallback is only valid in a function body")
|
|
return invalid_hir_expr(checker, expr.span, id, success)
|
|
}
|
|
capture_start = len(ctx.locals^)
|
|
error_type := types.fallible_error(channel_type, store)
|
|
capture = append_build_local(ctx, expr.name, error_type, false, expr.span)
|
|
}
|
|
if expr.right != ast.INVALID_EXPR {
|
|
fallback_locals := locals
|
|
if capture != hir.INVALID_LOCAL {
|
|
fallback_locals = ctx.locals^[:]
|
|
}
|
|
fallback = build_nested_expr(checker, expr.right, fallback_locals, global_reads, calls, success, pkg, file)
|
|
fallback = coerce_expr(checker, fallback, success, checker.module.exprs[fallback].span)
|
|
} else {
|
|
block_handler = true
|
|
if ctx == nil {
|
|
id := source.add(checker.diagnostics, expr.span, "catch block form is only valid in a function body")
|
|
return invalid_hir_expr(checker, expr.span, id, success)
|
|
}
|
|
handler: [dynamic]hir.Stmt_Id
|
|
handler.allocator = checker.allocator
|
|
fallback, _ = build_value_source(ctx, &handler, expr.body, success, expr.span, value_control_flow=expr.integer != 0, allow_exit=true)
|
|
void_fallthrough = fallback == hir.INVALID_EXPR && !all_paths_exit(&checker.module, handler[:])
|
|
body = handler[:]
|
|
}
|
|
if capture != hir.INVALID_LOCAL {
|
|
resize(ctx.locals, capture_start)
|
|
}
|
|
catch_mode := hir.CATCH_EXPRESSION
|
|
if block_handler {
|
|
catch_mode = hir.CATCH_VOID_FALLTHROUGH if void_fallthrough else hir.CATCH_BLOCK
|
|
}
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Catch,
|
|
span=expr.span,
|
|
type=success,
|
|
integer=catch_mode,
|
|
left=channel,
|
|
right=fallback,
|
|
body=body,
|
|
target=hir.local_ref(capture) if capture != hir.INVALID_LOCAL else 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)
|
|
}
|
|
if invalid, propagated := propagate_invalid_expr(checker, expr.span, left, right); propagated {
|
|
return invalid
|
|
}
|
|
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 .Comptime:
|
|
return build_comptime_expr(checker, expr, expected, pkg, file)
|
|
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)
|
|
if invalid, propagated := propagate_invalid_expr(checker, expr.span, operand); propagated {
|
|
return invalid
|
|
}
|
|
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 .Bit_Not:
|
|
hint := expected if types.is_concrete_integer(expected) else types.INVALID
|
|
operand := build_nested_expr(checker, expr.left, locals, global_reads, calls, hint, pkg, file)
|
|
if invalid, propagated := propagate_invalid_expr(checker, expr.span, operand); propagated {
|
|
return invalid
|
|
}
|
|
operand_type := checker.module.exprs[operand].type
|
|
if !types.is_concrete_integer(operand_type) {
|
|
id := source.add(checker.diagnostics, expr.span, "'~' requires a concrete integer operand")
|
|
return invalid_hir_expr(checker, expr.span, id, operand_type)
|
|
}
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Bit_Not, span=expr.span, type=operand_type, left=operand,
|
|
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .Bit_And, .Bit_Or, .Bit_Xor:
|
|
hint := expected if types.is_concrete_integer(expected) else types.INVALID
|
|
left_const := is_numeric_constant_expr(checker, expr.left)
|
|
right_const := is_numeric_constant_expr(checker, expr.right)
|
|
left, right: hir.Expr_Id
|
|
if left_const && !right_const && !types.is_valid(hint) {
|
|
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, hint, pkg, file)
|
|
right_hint := hint if types.is_valid(hint) else checker.module.exprs[left].type
|
|
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, right_hint, pkg, file)
|
|
}
|
|
if invalid, propagated := propagate_invalid_expr(checker, expr.span, left, right); propagated {
|
|
return invalid
|
|
}
|
|
result_type := types.widest(checker.module.exprs[left].type, checker.module.exprs[right].type)
|
|
if !types.is_concrete_integer(result_type) {
|
|
id := source.add(checker.diagnostics, expr.span, "bitwise operation requires compatible concrete integer operands")
|
|
return invalid_hir_expr(checker, expr.span, id)
|
|
}
|
|
left = coerce_expr(checker, left, result_type, checker.module.exprs[left].span)
|
|
right = coerce_expr(checker, right, result_type, checker.module.exprs[right].span)
|
|
kind := hir.Expr_Kind.Bit_And
|
|
#partial switch expr.kind {
|
|
case .Bit_Or: kind = .Bit_Or
|
|
case .Bit_Xor: kind = .Bit_Xor
|
|
}
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=kind, span=expr.span, type=result_type, left=left, right=right,
|
|
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .Shift_Left, .Shift_Right, .Shift_Left_Saturating:
|
|
hint := expected if types.is_concrete_integer(expected) else types.INVALID
|
|
left := build_nested_expr(checker, expr.left, locals, global_reads, calls, hint, pkg, file)
|
|
right := build_nested_expr(checker, expr.right, locals, global_reads, calls, types.U64, pkg, file)
|
|
if invalid, propagated := propagate_invalid_expr(checker, expr.span, left, right); propagated {
|
|
return invalid
|
|
}
|
|
left_type := checker.module.exprs[left].type
|
|
right_type := checker.module.exprs[right].type
|
|
if !types.is_concrete_integer(left_type) {
|
|
id := source.add(checker.diagnostics, checker.module.exprs[left].span, "shifted value must be a concrete integer")
|
|
return invalid_hir_expr(checker, expr.span, id, left_type)
|
|
}
|
|
if !types.is_unsigned(right_type, checker.target) {
|
|
id := source.add(checker.diagnostics, checker.module.exprs[right].span, "shift count must be an unsigned integer")
|
|
return invalid_hir_expr(checker, expr.span, id, left_type)
|
|
}
|
|
if constant := eval_integer_constant_in_context(checker, expr.right, pkg, file);
|
|
constant.kind == .Value && expr.kind != .Shift_Left_Saturating &&
|
|
constant.value >= i128(types.bits(left_type, checker.target)) {
|
|
id := source.addf(
|
|
checker.diagnostics, checker.module.exprs[right].span,
|
|
"shift count %d exceeds %s width", constant.value, types.name(left_type),
|
|
)
|
|
return invalid_hir_expr(checker, expr.span, id, left_type)
|
|
}
|
|
kind := hir.Expr_Kind.Shift_Left
|
|
#partial switch expr.kind {
|
|
case .Shift_Right: kind = .Shift_Right
|
|
case .Shift_Left_Saturating: kind = .Shift_Left_Saturating
|
|
}
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=kind, span=expr.span, type=left_type, left=left, right=right,
|
|
target=hir.INVALID_REF, 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)
|
|
if invalid, propagated := propagate_invalid_expr(checker, expr.span, left, right); propagated {
|
|
return invalid
|
|
}
|
|
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 literals whose type comes from their peer. This covers
|
|
// integer and enum literals as well as optional presence tests such as
|
|
// `value == null` and `null != value`.
|
|
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]
|
|
left_numeric_const := left_const.kind == .Value || is_float_constant_expr(checker, expr.left)
|
|
right_numeric_const := right_const.kind == .Value || is_float_constant_expr(checker, expr.right)
|
|
if right_expr.kind == .Null && left_expr.kind != .Null {
|
|
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 == .Null && right_expr.kind != .Null {
|
|
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_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_numeric_const && !left_numeric_const {
|
|
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_numeric_const && !right_numeric_const {
|
|
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)
|
|
}
|
|
if invalid, propagated := propagate_invalid_expr(checker, expr.span, left, right); propagated {
|
|
return invalid
|
|
}
|
|
left_type := checker.module.exprs[left].type
|
|
right_type := checker.module.exprs[right].type
|
|
operand_type := types.INVALID
|
|
if left_expr.kind == .Null || right_expr.kind == .Null {
|
|
if expr.kind != .Eq && expr.kind != .Ne ||
|
|
!types.is_optional(left_type, store) || !types.equal(left_type, right_type) {
|
|
id := source.add(checker.diagnostics, expr.span, "'null' only supports '==' and '!=' with an optional value")
|
|
return invalid_hir_expr(checker, expr.span, id, types.BOOL)
|
|
}
|
|
operand_type = left_type
|
|
} else 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:
|
|
struct_type := types.INVALID
|
|
if expr.left != ast.INVALID_EXPR {
|
|
struct_type, _ = resolve_type_argument(checker, expr.left, pkg, file)
|
|
struct_type = types.resolve_alias(struct_type, store)
|
|
} else if symbol.is_valid(expr.name) {
|
|
target_pkg, available := expr_package(checker, expr, pkg, file, true)
|
|
struct_type = types.find_named(store, u32(target_pkg), u32(expr.name), file=u32(expr_lookup_file(expr, file))) 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)
|
|
}
|
|
} else if expr.tuple {
|
|
struct_type = types.resolve_alias(expected, store)
|
|
} else {
|
|
struct_type = types.resolve_alias(expected, store)
|
|
if types.is_valid(struct_type) &&
|
|
(!types.is_struct(struct_type, store) || types.is_opaque_struct(struct_type, store)) {
|
|
id := source.add(checker.diagnostics, expr.span, "keyed contextual payload requires a struct payload")
|
|
return invalid_hir_expr(checker, expr.span, id, expected)
|
|
}
|
|
}
|
|
tuple_literal := expr.tuple
|
|
if tuple_literal && len(expr.args) == 0 && types.is_valid(struct_type) {
|
|
item, ok := types.node(store, struct_type)
|
|
tuple_literal = !ok || item.kind != .Struct || item.tuple
|
|
}
|
|
if tuple_literal {
|
|
resolved := types.is_valid(struct_type)
|
|
if resolved {
|
|
item, ok := types.node(store, struct_type)
|
|
if !ok || item.kind != .Struct || !item.tuple {
|
|
id := source.add(checker.diagnostics, expr.span, "positional construction requires a tuple type")
|
|
return invalid_hir_expr(checker, expr.span, id, struct_type)
|
|
}
|
|
}
|
|
fields := types.fields_for(store, struct_type) if resolved else nil
|
|
if resolved && len(expr.args) != len(fields) {
|
|
id := source.addf(checker.diagnostics, expr.span, "tuple literal expects %d elements, got %d", len(fields), len(expr.args))
|
|
return invalid_hir_expr(checker, expr.span, id, struct_type)
|
|
}
|
|
values := make([]hir.Expr_Id, len(expr.args), checker.allocator)
|
|
inferred_fields := make([]types.Field, len(expr.args), checker.allocator)
|
|
defer delete(inferred_fields, checker.allocator)
|
|
for arg, index in expr.args {
|
|
expected_field := fields[index].type if resolved else types.INVALID
|
|
values[index] = build_nested_expr(checker, arg, locals, global_reads, calls, expected_field, pkg, file)
|
|
if resolved {
|
|
values[index] = coerce_expr(checker, values[index], expected_field, checker.ast_module.exprs[arg].span)
|
|
} else {
|
|
inferred_fields[index].type = checker.module.exprs[values[index]].type
|
|
}
|
|
}
|
|
if !resolved {
|
|
struct_type = types.struct_anonymous(store, inferred_fields, true)
|
|
}
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Struct, span=expr.span, type=struct_type, args=values,
|
|
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
if !types.is_valid(struct_type) {
|
|
fields := make([]types.Field, len(expr.args), checker.allocator)
|
|
defer delete(fields, checker.allocator)
|
|
values := make([]hir.Expr_Id, len(expr.args), checker.allocator)
|
|
for keyed, index in expr.args {
|
|
keyed_expr := checker.ast_module.exprs[keyed]
|
|
fields[index].name = u32(keyed_expr.name)
|
|
for previous in fields[:index] {
|
|
if previous.name == fields[index].name {
|
|
source.addf(
|
|
checker.diagnostics, keyed_expr.span,
|
|
"duplicate initializer for struct field '%s'",
|
|
symbol_text(checker, keyed_expr.name),
|
|
)
|
|
}
|
|
}
|
|
values[index] = build_nested_expr(
|
|
checker, keyed_expr.left, locals, global_reads, calls, types.INVALID, pkg, file,
|
|
)
|
|
fields[index].type = checker.module.exprs[values[index]].type
|
|
}
|
|
struct_type = types.struct_anonymous(store, fields)
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Struct, span=expr.span, type=struct_type, args=values,
|
|
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
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
|
|
is_void_field := union_record && types.is_void(field.type)
|
|
if keyed_expr.left == ast.INVALID_EXPR {
|
|
// Bare key `T{ variant }`: valid only to construct a void-payload
|
|
// variant (no value); the payload slot stays INVALID_EXPR.
|
|
if !is_void_field {
|
|
source.addf(checker.diagnostics, keyed_expr.span, "field '%s' requires a value", symbol_text(checker, keyed_expr.name))
|
|
}
|
|
} else {
|
|
if is_void_field {
|
|
source.addf(checker.diagnostics, keyed_expr.span, "void variant '%s' takes no value", symbol_text(checker, keyed_expr.name))
|
|
}
|
|
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
|
|
}
|
|
if field_default, default_values, ok := find_struct_field_default(checker, struct_type, symbol.Id(field.name)); ok {
|
|
if field_default.static_value != INVALID_CT_VALUE {
|
|
if int(field_default.static_value) >= len(checker.static_state.values) {
|
|
continue
|
|
}
|
|
values[index] = build_static_value(
|
|
checker, checker.static_state.values[field_default.static_value], field_default.span, field.type,
|
|
)
|
|
values[index] = coerce_expr(checker, values[index], field.type, field_default.span)
|
|
continue
|
|
}
|
|
previous := checker.current_comptime_values
|
|
checker.current_comptime_values = default_values
|
|
values[index] = build_nested_expr(
|
|
checker, field_default.expr, nil, global_reads, calls,
|
|
field.type, field_default.pkg, field_default.file,
|
|
)
|
|
checker.current_comptime_values = previous
|
|
values[index] = coerce_expr(
|
|
checker, values[index], field.type,
|
|
field_default.span,
|
|
)
|
|
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 {
|
|
// The active variant is the one initialized field. Its payload slot
|
|
// (`values[0]`) may legitimately be INVALID_EXPR for a void variant, so
|
|
// detect "no field" via `initialized`, not the payload value.
|
|
found_any := false
|
|
for value, index in initialized {
|
|
if value {
|
|
active_field = i64(index)
|
|
found_any = true
|
|
break
|
|
}
|
|
}
|
|
if !found_any {
|
|
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)
|
|
}
|
|
}
|
|
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 {
|
|
if invalid, propagated := propagate_invalid_expr(checker, span, left, right); propagated {
|
|
return invalid
|
|
}
|
|
// 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)
|
|
}
|
|
if op == .Div && !types.is_float(result, checker.target) {
|
|
id := source.add(
|
|
checker.diagnostics, span,
|
|
"integer '/' is not allowed; use divtrunc!, divfloor!, divexact!, or divceil!",
|
|
)
|
|
return invalid_hir_expr(checker, span, id, result)
|
|
}
|
|
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
|
|
checker.build_stack = nil
|
|
clear_dynamic_array(&stack)
|
|
defer {
|
|
for frame in stack {
|
|
delete(frame.built_args, checker.allocator)
|
|
delete(frame.arg_types, checker.allocator)
|
|
delete(frame.mapping, checker.allocator)
|
|
}
|
|
clear_dynamic_array(&stack)
|
|
if checker.build_stack == nil {
|
|
checker.build_stack = stack
|
|
} else {
|
|
delete(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 {
|
|
if expr.kind == .Name || expr.kind == .Field || expr.kind == .Index || expr.kind == .Unwrap {
|
|
if specialized, ok := try_build_specialization_expr(
|
|
checker, frame.expr, frame.expected, pkg, file,
|
|
); ok {
|
|
last = specialized
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
}
|
|
if expr.kind == .Bit_Not || expr.kind == .Bit_And || expr.kind == .Bit_Or || expr.kind == .Bit_Xor ||
|
|
expr.kind == .Shift_Left || expr.kind == .Shift_Right || expr.kind == .Shift_Left_Saturating {
|
|
if folded, ok := try_fold_typed_integer_expr(checker, frame.expr, frame.expected, pkg, file); ok {
|
|
last = folded
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
}
|
|
constant := Constant{}
|
|
_, static_name := current_static_binding(checker, expr.name)
|
|
if expr.kind != .Name || symbol.is_valid(expr.qualifier) || !static_name {
|
|
constant = eval_constant(checker, frame.expr)
|
|
}
|
|
if constant.kind == .Value || constant.kind == .Overflow || constant.kind == .Div_By_Zero || constant.kind == .Non_Exact {
|
|
last = build_constant_expr(checker, expr, constant, frame.expected)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
switch expr.kind {
|
|
case .String, .Array, .Null, .Unreachable, .Undefined, .Address, .Deref, .Index, .Slice,
|
|
.Field, .Unwrap, .Orelse, .Try, .Catch, .Struct_Literal, .Keyed,
|
|
.Bool, .Cast, .Comptime, .Not, .Bit_Not, .Bit_And, .Bit_Or, .Bit_Xor, .Shift_Left,
|
|
.Shift_Right, .Shift_Left_Saturating, .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 .Function_Literal:
|
|
template := ast.Function_Id(u32(expr.integer))
|
|
last = build_function_value(checker, template, expr.span, frame.expected)
|
|
_ = pop(&stack)
|
|
case .Type, .Anonymous_Struct_Type:
|
|
id := source.add(checker.diagnostics, expr.span, "type is not a runtime value")
|
|
last = invalid_hir_expr(checker, expr.span, id)
|
|
_ = pop(&stack)
|
|
case .Invalid, .Inference_Hole, .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 = build_local_expr(checker, local, expr.span)
|
|
}
|
|
} else if local, ok := find_build_local(locals, expr.qualifier); ok {
|
|
base := build_local_expr(checker, local, expr.span)
|
|
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 && symbol.is_valid(expr.qualifier) {
|
|
if value, ok := current_comptime_value(checker, expr.qualifier);
|
|
ok && value.kind == .Static && value.static_value != INVALID_CT_VALUE &&
|
|
int(value.static_value) < len(checker.static_state.values) {
|
|
if field_value, found := persistent_field_value(checker, value.static_value, expr.name); found {
|
|
last = build_static_value(checker, field_value, expr.span, frame.expected)
|
|
}
|
|
}
|
|
}
|
|
if last == hir.INVALID_EXPR && symbol.is_valid(expr.qualifier) {
|
|
if value, ok := static_field_value(checker, expr.qualifier, expr.name); ok {
|
|
last = build_static_value(checker, value, expr.span, frame.expected)
|
|
}
|
|
}
|
|
if last == hir.INVALID_EXPR && symbol.is_valid(expr.qualifier) &&
|
|
find_import(checker, file, expr.qualifier) == ast.INVALID_IMPORT {
|
|
if global := find_global(checker, expr.qualifier, pkg, file); global != ast.INVALID_GLOBAL {
|
|
base := build_global_reference(checker, global, expr.span, global_reads)
|
|
base_type := checker.global_types[global]
|
|
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 !symbol.is_valid(expr.qualifier) {
|
|
if binding, ok := current_static_binding(checker, expr.name); ok &&
|
|
binding.value != INVALID_CT_VALUE && int(binding.value) < len(checker.static_state.values) {
|
|
last = build_static_value(checker, checker.static_state.values[binding.value], expr.span, frame.expected)
|
|
}
|
|
if last == hir.INVALID_EXPR {
|
|
if value, ok := current_comptime_value(checker, expr.name); ok {
|
|
if value.kind == .Integer {
|
|
expected_type := value.type
|
|
if types.is_concrete_integer(frame.expected) || types.is_float(frame.expected, checker.target) {
|
|
expected_type = frame.expected
|
|
}
|
|
last = build_constant_expr(
|
|
checker,
|
|
expr,
|
|
Constant{kind=.Value, value=value.value},
|
|
expected_type,
|
|
)
|
|
} else if value.kind == .String {
|
|
string_id := u64(0)
|
|
for text, index in checker.ast_module.strings {
|
|
if text == value.text {
|
|
string_id = u64(index)
|
|
break
|
|
}
|
|
}
|
|
constant_expr := expr
|
|
constant_expr.kind = .String
|
|
constant_expr.integer = string_id
|
|
last = build_compound_expr(
|
|
checker, constant_expr, locals, global_reads, calls,
|
|
frame.expected, pkg, file,
|
|
)
|
|
} else if value.kind == .Static && value.static_value != INVALID_CT_VALUE &&
|
|
int(value.static_value) < len(checker.static_state.values) {
|
|
last = build_static_value(
|
|
checker, checker.static_state.values[value.static_value], expr.span, frame.expected,
|
|
)
|
|
} else {
|
|
id := source.addf(
|
|
checker.diagnostics,
|
|
expr.span,
|
|
"type parameter '%s' is not a runtime value",
|
|
symbol_text(checker, expr.name),
|
|
)
|
|
last = invalid_hir_expr(checker, expr.span, id)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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, expr_lookup_file(expr, file)); global != ast.INVALID_GLOBAL {
|
|
last = build_global_reference(checker, global, expr.span, global_reads)
|
|
} else {
|
|
template := find_template(checker, expr.name, target_pkg, expr_lookup_file(expr, file))
|
|
if template != ast.INVALID_FUNCTION {
|
|
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, file)
|
|
}
|
|
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
|
|
}
|
|
if builtin := type_builtin_call(checker, expr); builtin != .None {
|
|
last = build_type_builtin(checker, expr, builtin, pkg, file)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
if builtin := division_builtin_call(checker, expr); builtin != .None {
|
|
last = build_division_builtin(
|
|
checker, expr, builtin, locals, global_reads, calls, frame.expected, pkg, file,
|
|
)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
if builtin := memory_builtin_call(checker, expr); builtin != .None {
|
|
last = build_memory_builtin(checker, expr, builtin, locals, global_reads, calls, pkg, file)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
if is_intrinsic_call(checker, expr, "some") {
|
|
if len(expr.args) != 1 {
|
|
id := source.addf(checker.diagnostics, expr.span, "some! expects 1 argument, got %d", len(expr.args))
|
|
last = invalid_hir_expr(checker, expr.span, id)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
if !types.is_optional(frame.expected, &checker.module.types) {
|
|
id := source.add(checker.diagnostics, expr.span, "some! requires an optional context")
|
|
last = invalid_hir_expr(checker, expr.span, id)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
child_type := types.child_type(frame.expected, &checker.module.types)
|
|
child := build_nested_expr(checker, expr.args[0], locals, global_reads, calls, child_type, pkg, file)
|
|
child = coerce_expr(checker, child, child_type, checker.ast_module.exprs[expr.args[0]].span)
|
|
last = add_hir_expr(checker, hir.Expr{
|
|
kind=.Optional_Some, span=expr.span, type=frame.expected, left=child,
|
|
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
if field_expr, handled := field_intrinsic_expr(checker, expr, pkg, file); handled {
|
|
if enum_type, ok := resolve_type_argument(checker, field_expr.left, pkg, file);
|
|
ok && types.is_enum(enum_type, &checker.module.types) {
|
|
last = enum_member_hir(checker, enum_type, field_expr.name, field_expr.span)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
base := build_nested_expr(
|
|
checker, field_expr.left, locals, global_reads, calls,
|
|
types.INVALID, pkg, file,
|
|
)
|
|
last, _ = build_field_from_value(
|
|
checker, field_expr, base, checker.module.exprs[base].type,
|
|
)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
if is_intrinsic_call(checker, expr, "typeinfo") {
|
|
id := source.add(
|
|
checker.diagnostics, expr.span,
|
|
"typeinfo! produces compile-time-only metadata and cannot be stored or passed at runtime",
|
|
)
|
|
last = invalid_hir_expr(checker, expr.span, id)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
if is_intrinsic_call(checker, expr, "tag") {
|
|
last = build_tag_intrinsic(checker, expr, locals, global_reads, calls, pkg, file)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
if is_intrinsic_call(checker, expr, "tagname") {
|
|
last = build_tagname_intrinsic(checker, expr, pkg, file)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
if is_ptrcast_call(checker, expr) {
|
|
if len(expr.args) != 2 {
|
|
id := source.addf(checker.diagnostics, expr.span, "ptrcast! expects 2 arguments, got %d", len(expr.args))
|
|
last = invalid_hir_expr(checker, expr.span, id)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
target, target_ok := resolve_type_argument(checker, expr.args[0], pkg, file)
|
|
if !target_ok {
|
|
id := source.add(checker.diagnostics, checker.ast_module.exprs[expr.args[0]].span, "ptrcast! target must be a type")
|
|
last = invalid_hir_expr(checker, expr.span, id)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
if !valid_ptrcast_child(checker, target) {
|
|
id := source.addf(checker.diagnostics, checker.ast_module.exprs[expr.args[0]].span, "ptrcast! target must be a sized runtime object type, got %s", type_label(checker, target))
|
|
last = invalid_hir_expr(checker, expr.span, id)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
stack[frame_index].target_type = target
|
|
stack[frame_index].stage = 9
|
|
append(&stack, Build_Expr_Frame{expr=expr.args[1], expected=types.INVALID, template=ast.INVALID_FUNCTION})
|
|
continue
|
|
}
|
|
if is_intrinsic_call(checker, expr, "constcast") {
|
|
if len(expr.args) != 1 {
|
|
id := source.addf(checker.diagnostics, expr.span, "constcast! expects 1 argument, got %d", len(expr.args))
|
|
last = invalid_hir_expr(checker, expr.span, id)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
stack[frame_index].stage = 10
|
|
append(&stack, Build_Expr_Frame{expr=expr.args[0], expected=types.INVALID, template=ast.INVALID_FUNCTION})
|
|
continue
|
|
}
|
|
if is_intrinsic_call(checker, expr, "compile_error") {
|
|
message := "compile_error! requires one comptime string argument"
|
|
if len(expr.args) == 1 {
|
|
if text, ok := comptime_string_argument(checker, expr.args[0], pkg, file); ok {
|
|
message = text
|
|
}
|
|
}
|
|
id := source.add(checker.diagnostics, expr.span, message)
|
|
last = invalid_hir_expr(checker, expr.span, id, types.VOID)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
if expr.intrinsic {
|
|
id := source.INVALID_DIAGNOSTIC
|
|
if symbol.is_valid(expr.qualifier) {
|
|
id = source.add(checker.diagnostics, expr.span, "intrinsic calls must be unqualified")
|
|
} else {
|
|
id = source.addf(checker.diagnostics, expr.span, "unknown intrinsic '%s!'", symbol_text(checker, expr.name))
|
|
}
|
|
last = invalid_hir_expr(checker, expr.span, id)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
if callee, handled, ok := build_qualified_value_field(checker, expr, locals, global_reads, pkg, file); handled {
|
|
if !ok {
|
|
last = callee
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
stack[frame_index].stage = 6
|
|
last = callee
|
|
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, expr_lookup_file(expr, file))
|
|
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.callable_function(local.type, &checker.module.types); callable {
|
|
callee = build_local_expr(checker, local, expr.span)
|
|
} else {
|
|
non_callable = true
|
|
}
|
|
}
|
|
if callee == hir.INVALID_EXPR && !non_callable {
|
|
if binding, ok := current_static_binding(checker, expr.name); ok &&
|
|
binding.value != INVALID_CT_VALUE && int(binding.value) < len(checker.static_state.values) {
|
|
callee = build_static_value(
|
|
checker, checker.static_state.values[binding.value], expr.span, binding.type,
|
|
)
|
|
}
|
|
}
|
|
if callee == hir.INVALID_EXPR && !non_callable {
|
|
if value, ok := current_comptime_value(checker, expr.name);
|
|
ok && value.kind == .Static && value.static_value != INVALID_CT_VALUE &&
|
|
int(value.static_value) < len(checker.static_state.values) {
|
|
callee = build_static_value(
|
|
checker, checker.static_state.values[value.static_value], expr.span, types.INVALID,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
if callee == hir.INVALID_EXPR && !non_callable {
|
|
if global := find_global(checker, expr.name, target_pkg, expr_lookup_file(expr, file)); global != ast.INVALID_GLOBAL {
|
|
if _, _, _, callable := types.callable_function(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 {
|
|
named_type := types.find_named(&checker.module.types, u32(target_pkg), u32(expr.name), file=u32(expr_lookup_file(expr, file)))
|
|
named_item, named_ok := types.node(&checker.module.types, named_type)
|
|
constructor_type := types.resolve_alias(named_type, &checker.module.types)
|
|
constructor_item, constructor_ok := types.node(&checker.module.types, constructor_type)
|
|
scalar_alias := named_ok && named_item.kind == .Alias &&
|
|
types.is_concrete_scalar(constructor_type) && !types.is_bool(constructor_type)
|
|
if scalar_alias {
|
|
if len(expr.args) != 1 {
|
|
id := source.addf(
|
|
checker.diagnostics,
|
|
expr.span,
|
|
"type alias '%s' expects 1 argument, got %d",
|
|
symbol_text(checker, expr.name),
|
|
len(expr.args),
|
|
)
|
|
last = invalid_hir_expr(checker, expr.span, id, constructor_type)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
stack[frame_index].target_type = constructor_type
|
|
stack[frame_index].stage = 11
|
|
append(&stack, Build_Expr_Frame{
|
|
expr=expr.args[0],
|
|
expected=types.INVALID,
|
|
template=ast.INVALID_FUNCTION,
|
|
})
|
|
continue
|
|
}
|
|
if constructor_ok && constructor_item.kind == .Distinct {
|
|
if !is_runtime_type(checker, constructor_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, constructor_type)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
stack[frame_index].target_type = constructor_type
|
|
stack[frame_index].stage = 8
|
|
append(&stack, Build_Expr_Frame{
|
|
expr=expr.args[0],
|
|
expected=constructor_item.child,
|
|
template=ast.INVALID_FUNCTION,
|
|
})
|
|
continue
|
|
}
|
|
id := source.INVALID_DIAGNOSTIC
|
|
if non_callable {
|
|
id = add_call_resolution_diagnostic(checker, expr, target_pkg, file) if non_callable_global else
|
|
source.add(checker.diagnostics, expr.span, "call target is not callable")
|
|
} 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, file)
|
|
}
|
|
}
|
|
last = invalid_hir_expr(checker, expr.span, id)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
_, function_item, function_type, _ := types.callable_function(checker.module.exprs[callee].type, &checker.module.types)
|
|
if !valid_callable_arity(function_item, len(expr.args)) {
|
|
message := "function expects at least %d arguments, got %d" if function_item.variadic else
|
|
"function 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 is_type_metatype_syntax(checker, function.result) {
|
|
id := source.addf(checker.diagnostics, expr.span, "type factory '%s' is only valid in type position", symbol_text(checker, expr.name))
|
|
last = invalid_hir_expr(checker, expr.span, id)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
resolution_index, resolved := find_call_resolution(checker, frame.expr)
|
|
mapping: []int
|
|
mode := Call_Argument_Mode.Invalid
|
|
prefix := comptime_param_count(function)
|
|
mapping_failure := ""
|
|
if resolved {
|
|
mapping = slice.clone(checker.call_resolutions[resolution_index].mapping, checker.allocator)
|
|
mode = call_mapping_mode(function, mapping)
|
|
} else {
|
|
infer_locals := make([]Infer_Local, len(locals), checker.allocator)
|
|
for local, index in locals {
|
|
infer_locals[index] = Infer_Local{name=local.name, type=local.type, declared=local.type}
|
|
}
|
|
mapping, mode, prefix, mapping_failure = call_argument_mapping(
|
|
checker, &function, expr.args, pkg, file, frame.expected, infer_locals,
|
|
)
|
|
delete(infer_locals, checker.allocator)
|
|
}
|
|
if mode == .Invalid {
|
|
id := source.INVALID_DIAGNOSTIC
|
|
if !function_has_comptime_params(function) {
|
|
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),
|
|
)
|
|
if !function.imported {
|
|
source.add_secondary_label(checker.diagnostics, id, function.span, "function declared here")
|
|
}
|
|
} else if len(mapping_failure) > 0 {
|
|
id = source.addf(
|
|
checker.diagnostics, expr.span,
|
|
"call to '%s' has no unique complete argument mapping: %s",
|
|
symbol_text(checker, expr.name), mapping_failure,
|
|
)
|
|
} else {
|
|
id = source.addf(
|
|
checker.diagnostics, expr.span,
|
|
"call to '%s' has no unique complete argument mapping",
|
|
symbol_text(checker, expr.name),
|
|
)
|
|
}
|
|
delete(mapping_failure, checker.allocator)
|
|
last = invalid_hir_expr(checker, expr.span, id)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
stack[frame_index].template = template
|
|
stack[frame_index].arg_mode = mode
|
|
stack[frame_index].prefix = prefix
|
|
stack[frame_index].resolution = resolution_index if resolved else -1
|
|
stack[frame_index].mapping = mapping
|
|
stack[frame_index].built_args = make([]hir.Expr_Id, len(expr.args), checker.allocator)
|
|
stack[frame_index].arg_types = make([]types.Type, len(function.params), checker.allocator)
|
|
for &arg in stack[frame_index].built_args {
|
|
arg = hir.INVALID_EXPR
|
|
}
|
|
stack[frame_index].arg_index = next_runtime_call_arg(function, mapping, 0, len(expr.args))
|
|
stack[frame_index].stage = 3
|
|
if stack[frame_index].arg_index < len(expr.args) {
|
|
param_index := call_param_index(mapping, stack[frame_index].arg_index)
|
|
arg_expected := resolved_call_arg_expected(
|
|
checker, function, param_index, stack[frame_index].resolution,
|
|
)
|
|
if !is_runtime_type(checker, arg_expected) {
|
|
arg_expected = types.INVALID
|
|
}
|
|
append(&stack, Build_Expr_Frame{
|
|
expr=expr.args[stack[frame_index].arg_index],
|
|
expected=arg_expected,
|
|
template=ast.INVALID_FUNCTION,
|
|
})
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
if frame.stage == 5 {
|
|
operand := last
|
|
if invalid, propagated := propagate_invalid_expr(checker, expr.span, operand); propagated {
|
|
last = invalid
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
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
|
|
param_index := call_param_index(frame.mapping, frame.arg_index)
|
|
if param_index < len(stack[frame_index].arg_types) {
|
|
stack[frame_index].arg_types[param_index] = checker.module.exprs[last].type
|
|
}
|
|
next := next_runtime_call_arg(
|
|
checker.ast_module.functions[frame.template], frame.mapping,
|
|
frame.arg_index+1, len(expr.args),
|
|
)
|
|
stack[frame_index].arg_index = next
|
|
if next < len(expr.args) {
|
|
next_param := call_param_index(frame.mapping, next)
|
|
arg_expected := resolved_call_arg_expected(
|
|
checker, checker.ast_module.functions[frame.template], next_param,
|
|
frame.resolution,
|
|
)
|
|
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
|
|
}
|
|
}
|
|
if invalid, propagated := propagate_invalid_expr(checker, expr.span, ..stack[frame_index].built_args); propagated {
|
|
delete(stack[frame_index].arg_types, checker.allocator)
|
|
stack[frame_index].arg_types = nil
|
|
delete(stack[frame_index].built_args, checker.allocator)
|
|
stack[frame_index].built_args = nil
|
|
delete(stack[frame_index].mapping, checker.allocator)
|
|
stack[frame_index].mapping = nil
|
|
last = invalid
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
function := checker.ast_module.functions[frame.template]
|
|
comptime_values: []Comptime_Value
|
|
comptime_ok := false
|
|
if frame.resolution >= 0 && frame.resolution < len(checker.call_resolutions) {
|
|
comptime_values = clone_comptime_values(
|
|
checker.call_resolutions[frame.resolution].comptime_values,
|
|
checker.allocator,
|
|
)
|
|
comptime_ok = true
|
|
} else {
|
|
comptime_values, comptime_ok = infer_call_comptime_values(
|
|
checker, function, frame.prefix, frame.mapping, expr.args, stack[frame_index].arg_types,
|
|
frame.expected, pkg, file, diagnose=true,
|
|
)
|
|
}
|
|
defer delete(comptime_values, checker.allocator)
|
|
if comptime_ok {
|
|
previous_comptime := checker.current_comptime_values
|
|
checker.current_comptime_values = comptime_values
|
|
for source_index in 0..<len(expr.args) {
|
|
param_index := call_param_index(frame.mapping, source_index)
|
|
if param_index >= len(function.params) || function.params[param_index].comptime_value ||
|
|
!is_numeric_constant_expr(checker, expr.args[source_index]) {
|
|
continue
|
|
}
|
|
expected_arg := call_arg_expected(checker, function, param_index)
|
|
if !is_runtime_type(checker, expected_arg) {
|
|
continue
|
|
}
|
|
rebuilt := build_nested_expr(
|
|
checker, expr.args[source_index], locals, global_reads, calls,
|
|
expected_arg, pkg, file,
|
|
)
|
|
stack[frame_index].built_args[source_index] = rebuilt
|
|
stack[frame_index].arg_types[param_index] = checker.module.exprs[rebuilt].type
|
|
}
|
|
checker.current_comptime_values = previous_comptime
|
|
}
|
|
arg_violation := source.INVALID_DIAGNOSTIC
|
|
if comptime_ok {
|
|
previous_comptime := checker.current_comptime_values
|
|
checker.current_comptime_values = comptime_values
|
|
params := function.params
|
|
for index in 0..<len(params) {
|
|
if index >= len(stack[frame_index].arg_types) {
|
|
break
|
|
}
|
|
if params[index].comptime_value {
|
|
continue
|
|
}
|
|
declared := type_from_syntax(checker, params[index].type, function.pkg, function.file)
|
|
actual := stack[frame_index].arg_types[index]
|
|
if types.is_constraint(declared) && types.is_valid(actual) &&
|
|
!types.is_valid(types.constraint_target(declared, actual, &checker.module.types)) {
|
|
arg_violation = source.addf(
|
|
checker.diagnostics, expr.span,
|
|
"cannot pass %s to '%s' parameter '%s'",
|
|
types.name(actual), types.name(declared),
|
|
symbol_text(checker, params[index].name),
|
|
)
|
|
break
|
|
}
|
|
}
|
|
checker.current_comptime_values = previous_comptime
|
|
}
|
|
fold := Call_Fold.Runtime
|
|
if comptime_ok && can_fold_zero_runtime_call(checker, function) &&
|
|
frame.resolution >= 0 && frame.resolution < len(checker.call_resolutions) {
|
|
fold = cache_zero_runtime_call(checker, frame.resolution, frame.expr, pkg, file)
|
|
}
|
|
if comptime_ok && arg_violation == source.INVALID_DIAGNOSTIC &&
|
|
(fold == .Value || fold == .Compile_Error) {
|
|
if fold == .Value {
|
|
value := checker.call_resolutions[frame.resolution].folded_value
|
|
last = ct_materialize_value(&checker.static_state, value, expr.span, frame.expected)
|
|
} else {
|
|
diagnostic := checker.call_resolutions[frame.resolution].diagnostic
|
|
last = invalid_hir_expr(checker, expr.span, diagnostic, frame.expected)
|
|
}
|
|
delete(stack[frame_index].arg_types, checker.allocator)
|
|
stack[frame_index].arg_types = nil
|
|
delete(stack[frame_index].built_args, checker.allocator)
|
|
stack[frame_index].built_args = nil
|
|
delete(stack[frame_index].mapping, checker.allocator)
|
|
stack[frame_index].mapping = nil
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
spec := INVALID_SPEC
|
|
if comptime_ok {
|
|
if frame.resolution >= 0 && frame.resolution < len(checker.call_resolutions) {
|
|
spec = find_spec(
|
|
checker, frame.template,
|
|
checker.call_resolutions[frame.resolution].runtime_types,
|
|
comptime_values,
|
|
)
|
|
} else {
|
|
spec = find_spec(checker, frame.template, stack[frame_index].arg_types, comptime_values)
|
|
}
|
|
}
|
|
delete(stack[frame_index].arg_types, checker.allocator)
|
|
stack[frame_index].arg_types = nil
|
|
if !comptime_ok || arg_violation != source.INVALID_DIAGNOSTIC {
|
|
delete(stack[frame_index].built_args, checker.allocator)
|
|
stack[frame_index].built_args = nil
|
|
delete(stack[frame_index].mapping, checker.allocator)
|
|
stack[frame_index].mapping = nil
|
|
diagnostic := arg_violation
|
|
if diagnostic == source.INVALID_DIAGNOSTIC {
|
|
diagnostic = source.add(checker.diagnostics, expr.span, "invalid comptime argument")
|
|
}
|
|
last = invalid_hir_expr(checker, expr.span, diagnostic)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
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
|
|
delete(stack[frame_index].mapping, checker.allocator)
|
|
stack[frame_index].mapping = nil
|
|
last = invalid_hir_expr(checker, expr.span, id)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
source_args := stack[frame_index].built_args
|
|
runtime_count := runtime_param_count(function)
|
|
runtime_arg_count := runtime_count + max(0, len(source_args)-len(function.params))
|
|
runtime_args := make([]hir.Expr_Id, runtime_arg_count, checker.allocator)
|
|
runtime_index := 0
|
|
for param, param_index in function.params {
|
|
if param.comptime_value {
|
|
continue
|
|
}
|
|
source_index := -1
|
|
for mapped_param, mapped_source in frame.mapping {
|
|
if mapped_param == param_index {
|
|
source_index = mapped_source
|
|
break
|
|
}
|
|
}
|
|
assert(source_index >= 0)
|
|
arg := source_args[source_index]
|
|
runtime_args[runtime_index] = coerce_expr(
|
|
checker,
|
|
arg,
|
|
checker.specs[spec].args[runtime_index],
|
|
checker.module.exprs[arg].span,
|
|
)
|
|
runtime_index += 1
|
|
}
|
|
for source_index in len(function.params)..<len(source_args) {
|
|
arg := source_args[source_index]
|
|
runtime_args[runtime_index] = promote_c_vararg_expr(
|
|
checker,
|
|
arg,
|
|
checker.module.exprs[arg].span,
|
|
)
|
|
runtime_index += 1
|
|
}
|
|
delete(stack[frame_index].built_args, checker.allocator)
|
|
stack[frame_index].built_args = nil
|
|
delete(stack[frame_index].mapping, checker.allocator)
|
|
stack[frame_index].mapping = nil
|
|
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(runtime_args, checker.allocator)
|
|
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=runtime_args, diagnostic = source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
_ = pop(&stack)
|
|
}
|
|
if frame.stage == 6 {
|
|
callee := last
|
|
if invalid, propagated := propagate_invalid_expr(checker, expr.span, callee); propagated {
|
|
last = invalid
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
_, function_item, function_type, ok := types.callable_function(checker.module.exprs[callee].type, &checker.module.types)
|
|
if !ok {
|
|
id := source.add(checker.diagnostics, expr.span, "call target is not callable")
|
|
last = invalid_hir_expr(checker, expr.span, id)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
if !valid_callable_arity(function_item, len(expr.args)) {
|
|
message := "function expects at least %d arguments, got %d" if function_item.variadic else
|
|
"function 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.callable_function(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
|
|
}
|
|
}
|
|
invalid, propagated := propagate_invalid_expr(checker, expr.span, frame.left)
|
|
if !propagated {
|
|
invalid, propagated = propagate_invalid_expr(checker, expr.span, ..stack[frame_index].built_args)
|
|
}
|
|
if propagated {
|
|
delete(stack[frame_index].built_args, checker.allocator)
|
|
stack[frame_index].built_args = nil
|
|
last = invalid
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
callee_type := checker.module.exprs[frame.left].type
|
|
_, function_item, function_type, ok := types.callable_function(callee_type, &checker.module.types)
|
|
if !ok {
|
|
id := source.add(checker.diagnostics, expr.span, "call target is not callable")
|
|
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 callable 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 {
|
|
target := hir.INVALID_REF
|
|
callee := frame.left
|
|
callee_expr := checker.module.exprs[frame.left]
|
|
if callee_expr.kind == .Function {
|
|
function := hir.as_function(callee_expr.target)
|
|
if function != hir.INVALID_FUNCTION {
|
|
target = callee_expr.target
|
|
callee = hir.INVALID_EXPR
|
|
add_unique_function(calls, function)
|
|
}
|
|
}
|
|
last = add_hir_expr(checker, hir.Expr{
|
|
kind=.Call, span=expr.span, type=result, target=target,
|
|
left=callee, 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)
|
|
}
|
|
if frame.stage == 9 {
|
|
result, ok := types.replace_pointer_child(&checker.module.types, checker.module.exprs[last].type, frame.target_type)
|
|
if !ok {
|
|
id := source.add(checker.diagnostics, expr.span, "ptrcast! operand must be a pointer or optional pointer")
|
|
last = invalid_hir_expr(checker, expr.span, id)
|
|
} else {
|
|
last = add_hir_expr(checker, hir.Expr{
|
|
kind=.Pointer_Cast,
|
|
span=expr.span,
|
|
type=result,
|
|
left=last,
|
|
target=hir.INVALID_REF,
|
|
right=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
_ = pop(&stack)
|
|
}
|
|
if frame.stage == 10 {
|
|
result, ok := types.restore_mutability(&checker.module.types, checker.module.exprs[last].type)
|
|
if !ok {
|
|
id := source.add(checker.diagnostics, expr.span, "constcast! operand must be a pointer, optional pointer, or slice")
|
|
last = invalid_hir_expr(checker, expr.span, id)
|
|
} else {
|
|
last = add_hir_expr(checker, hir.Expr{
|
|
kind=.Const_Cast,
|
|
span=expr.span,
|
|
type=result,
|
|
left=last,
|
|
target=hir.INVALID_REF,
|
|
right=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
_ = pop(&stack)
|
|
}
|
|
if frame.stage == 11 {
|
|
last = build_scalar_cast(checker, last, frame.target_type, expr.span)
|
|
_ = 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 && checker.entry_point == .Plain {
|
|
return fmt.aprintf("main", allocator = checker.allocator)
|
|
}
|
|
if function.generated {
|
|
return fmt.aprintf("bro__p%d__anon%d", function.pkg, spec.template, 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))
|
|
}
|
|
}
|
|
for value in spec.comptime_values {
|
|
if value.kind == .Type {
|
|
strings.write_string(&builder, "__ct")
|
|
if value.type >= types.DYNAMIC_START {
|
|
fmt.sbprintf(&builder, "t%d", value.type)
|
|
} else {
|
|
strings.write_string(&builder, types.name(value.type))
|
|
}
|
|
} else if value.kind == .String {
|
|
fmt.sbprintf(&builder, "__cs%d_", len(value.text))
|
|
hex := "0123456789abcdef"
|
|
for byte in transmute([]byte)value.text {
|
|
strings.write_byte(&builder, hex[byte>>4])
|
|
strings.write_byte(&builder, hex[byte&0xf])
|
|
}
|
|
} else if value.kind == .Static {
|
|
fmt.sbprintf(&builder, "__ca%d_%016x", len(value.key), value.fingerprint)
|
|
} else {
|
|
strings.write_string(&builder, "__cv")
|
|
if value.value < 0 {
|
|
strings.write_string(&builder, "n")
|
|
fmt.sbprintf(&builder, "%d", -value.value)
|
|
} else {
|
|
fmt.sbprintf(&builder, "%d", value.value)
|
|
}
|
|
}
|
|
}
|
|
return fmt.aprintf("%s", strings.to_string(builder), allocator = checker.allocator)
|
|
}
|
|
|
|
// Replay eligible cleanup in `[lo, len(defers))` in LIFO order. Error exits run
|
|
// both defer forms; other exits skip errdefer. A direct error return supplies its
|
|
// preserved payload so captured errors can be initialized before each cleanup.
|
|
flush_defers :: proc(
|
|
ctx: ^Build_Ctx,
|
|
body: ^[dynamic]hir.Stmt_Id,
|
|
lo: int,
|
|
error_exit := false,
|
|
error_value := hir.INVALID_EXPR,
|
|
) {
|
|
for i := len(ctx.defers^) - 1; i >= lo; i -= 1 {
|
|
entry := ctx.defers^[i]
|
|
if entry.error_only && !error_exit {
|
|
continue
|
|
}
|
|
if error_exit && entry.capture != hir.INVALID_LOCAL && error_value != hir.INVALID_EXPR {
|
|
append(body, hir.stmt_id(len(ctx.checker.module.statements)))
|
|
append(&ctx.checker.module.statements, hir.Stmt{
|
|
kind=.Declaration, local=entry.capture, expr=error_value,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
for stmt_id in entry.body {
|
|
append(body, stmt_id)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Copy the active error-exit cleanup onto a Try expression. Capture locals are
|
|
// initialized by lowering once the propagated error has been extracted/widened.
|
|
try_cleanup :: proc(ctx: ^Build_Ctx) -> ([]hir.Stmt_Id, []hir.Expr_Id) {
|
|
body: [dynamic]hir.Stmt_Id
|
|
body.allocator = ctx.checker.allocator
|
|
captures: [dynamic]hir.Expr_Id
|
|
captures.allocator = ctx.checker.allocator
|
|
for i := len(ctx.defers^) - 1; i >= 0; i -= 1 {
|
|
entry := ctx.defers^[i]
|
|
if entry.error_only && entry.capture != hir.INVALID_LOCAL {
|
|
append(&captures, hir.Expr_Id(entry.capture))
|
|
}
|
|
append(&body, ..entry.body)
|
|
}
|
|
return body[:], captures[:]
|
|
}
|
|
|
|
specialization_bool :: proc(checker: ^Checker, expr: ast.Expr_Id, pkg: ast.Package_Id, file: ast.File_Id) -> (bool, bool) {
|
|
if len(checker.current_comptime_values) == 0 && len(checker.static_bindings) == 0 {
|
|
return false, false
|
|
}
|
|
state := ct_state_make(checker, pkg, file, values=checker.current_comptime_values, diagnose=false)
|
|
defer ct_state_destroy(&state)
|
|
value, flow, ok := ct_eval_expr(&state, expr, types.BOOL, 0)
|
|
if !ok || flow.kind != .Normal {
|
|
return false, false
|
|
}
|
|
return ct_bool_value(&state, value)
|
|
}
|
|
|
|
specialization_match_body :: proc(
|
|
checker: ^Checker,
|
|
statement: ast.Stmt,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
) -> ([]ast.Stmt_Id, Static_Binding, bool, bool) {
|
|
if len(checker.current_comptime_values) == 0 && len(checker.static_bindings) == 0 {
|
|
return nil, {}, false, false
|
|
}
|
|
state := ct_state_make(checker, pkg, file, values=checker.current_comptime_values, diagnose=false)
|
|
defer ct_state_destroy(&state)
|
|
subject, flow, ok := ct_eval_expr(&state, statement.expr, types.INVALID, 0)
|
|
if !ok || flow.kind != .Normal || subject == INVALID_CT_VALUE || int(subject) >= len(state.values) {
|
|
return nil, {}, false, false
|
|
}
|
|
selection, selected := ct_select_match_arm(&state, statement, subject, 0)
|
|
if !selected {
|
|
return nil, {}, false, false
|
|
}
|
|
arm := checker.ast_module.statements[selection.arm]
|
|
if arm.expand {
|
|
return nil, {}, false, false
|
|
}
|
|
if arm.pointer_capture {
|
|
return nil, {}, false, false
|
|
}
|
|
if len(arm.captures) > 0 {
|
|
if selection.payload == INVALID_CT_VALUE || types.is_void(selection.payload_type) {
|
|
return nil, {}, false, false
|
|
}
|
|
if arm.captures[0] != checker.sink_symbol {
|
|
return arm.body, store_static_binding(checker, &state, selection.payload, arm.captures[0]), true, true
|
|
}
|
|
}
|
|
return arm.body, {}, false, true
|
|
}
|
|
|
|
Expand_Binding_Error :: enum u8 {
|
|
None,
|
|
Invalid,
|
|
Quota,
|
|
Diagnosed,
|
|
}
|
|
|
|
expand_field_bindings :: proc(
|
|
checker: ^Checker,
|
|
expr: ast.Expr_Id,
|
|
capture: symbol.Id,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
diagnose := false,
|
|
) -> ([]Static_Binding, Expand_Binding_Error) {
|
|
state := ct_state_make(checker, pkg, file, values=checker.current_comptime_values, diagnose=diagnose)
|
|
defer ct_state_destroy(&state)
|
|
value_id, flow, ok := ct_eval_expr(&state, expr, types.INVALID, 0)
|
|
if !ok || flow.kind != .Normal || value_id == INVALID_CT_VALUE || int(value_id) >= len(state.values) {
|
|
return nil, .Diagnosed if state.diagnostic != source.INVALID_DIAGNOSTIC else .Invalid
|
|
}
|
|
value := state.values[value_id]
|
|
if ct_value_contains_undefined(&state, value_id) {
|
|
if diagnose {
|
|
_ = ct_fail(&state, .Not_Comptime, checker.ast_module.exprs[expr].span, "expand for cannot expand an undefined comptime value")
|
|
}
|
|
return nil, .Diagnosed if state.diagnostic != source.INVALID_DIAGNOSTIC else .Invalid
|
|
}
|
|
if value.kind == .Range {
|
|
parts := ct_child_slice(&state, value)
|
|
if len(parts) != 2 {
|
|
return nil, .Invalid
|
|
}
|
|
start, start_ok := ct_integer_value(&state, parts[0])
|
|
end, end_ok := ct_integer_value(&state, parts[1])
|
|
if !start_ok || !end_ok || end < start {
|
|
return nil, .Invalid
|
|
}
|
|
count := int(end-start)
|
|
if value.active != 0 {
|
|
count += 1
|
|
}
|
|
if count < 0 || count > COMPTIME_EVAL_QUOTA {
|
|
return nil, .Quota
|
|
}
|
|
bindings := make([]Static_Binding, count, checker.allocator)
|
|
for index in 0..<count {
|
|
id := ct_add_value(&state, Ct_Value{
|
|
kind=.Integer,
|
|
type=types.child_type(value.type, &checker.module.types),
|
|
integer=start+i128(index),
|
|
})
|
|
bindings[index] = store_static_binding(checker, &state, id, capture)
|
|
}
|
|
return bindings, .None
|
|
}
|
|
owned_children: [dynamic]Ct_Value_Id
|
|
owned_children.allocator = checker.allocator
|
|
defer delete(owned_children)
|
|
children: []Ct_Value_Id
|
|
if value.kind == .Array {
|
|
children = ct_child_slice(&state, value)
|
|
} else if value.kind == .Slice {
|
|
for index in 0..<int(value.count) {
|
|
place, _, _ := ct_slice_element_place(&state, value, index)
|
|
child, child_ok := ct_place_get(&state, place)
|
|
if !child_ok {
|
|
return nil, .Invalid
|
|
}
|
|
append(&owned_children, child)
|
|
}
|
|
children = owned_children[:]
|
|
} else if value.kind == .Struct {
|
|
item, tuple_ok := types.node(&checker.module.types, value.type)
|
|
if !tuple_ok || item.kind != .Struct || !item.tuple {
|
|
return nil, .Invalid
|
|
}
|
|
children = ct_child_slice(&state, value)
|
|
} else {
|
|
return nil, .Invalid
|
|
}
|
|
bindings := make([]Static_Binding, len(children), checker.allocator)
|
|
for child, index in children {
|
|
if child == INVALID_CT_VALUE || int(child) >= len(state.values) {
|
|
delete(bindings, checker.allocator)
|
|
return nil, .Invalid
|
|
}
|
|
bindings[index] = store_static_binding(checker, &state, child, capture)
|
|
}
|
|
return bindings, .None
|
|
}
|
|
|
|
push_expand_binding :: proc(
|
|
checker: ^Checker,
|
|
binding: Static_Binding,
|
|
index_name: symbol.Id,
|
|
index: int,
|
|
statement: ast.Stmt_Id,
|
|
) -> int {
|
|
start := len(checker.static_bindings)
|
|
append(&checker.static_bindings, binding)
|
|
append(&checker.expand_context, Expand_Expansion{statement=statement, index=u32(index)})
|
|
if symbol.is_valid(index_name) {
|
|
value := ct_add_value(&checker.static_state, Ct_Value{kind=.Integer, type=types.USIZE, integer=i128(index)})
|
|
append(&checker.static_bindings, Static_Binding{name=index_name, type=types.USIZE, value=value})
|
|
}
|
|
return start
|
|
}
|
|
|
|
pop_expand_binding :: proc(checker: ^Checker, start: int) {
|
|
resize(&checker.static_bindings, start)
|
|
_ = pop(&checker.expand_context)
|
|
}
|
|
|
|
Expand_Control :: enum u8 {
|
|
Normal,
|
|
Break,
|
|
Continue,
|
|
Invalid,
|
|
}
|
|
|
|
expand_control_target :: proc(statement: ast.Stmt, target_label: symbol.Id, allow_unlabeled: bool) -> Expand_Control {
|
|
if statement.kind != .Break && statement.kind != .Continue {
|
|
return .Normal
|
|
}
|
|
if symbol.is_valid(statement.label) {
|
|
if !symbol.is_valid(target_label) || statement.label != target_label {
|
|
return .Normal
|
|
}
|
|
} else if !allow_unlabeled {
|
|
return .Normal
|
|
}
|
|
return .Break if statement.kind == .Break else .Continue
|
|
}
|
|
|
|
contains_expand_control :: proc(
|
|
checker: ^Checker,
|
|
statements: []ast.Stmt_Id,
|
|
target_label: symbol.Id,
|
|
allow_unlabeled: bool,
|
|
) -> bool {
|
|
for statement_id in statements {
|
|
statement := checker.ast_module.statements[statement_id]
|
|
if expand_control_target(statement, target_label, allow_unlabeled) != .Normal {
|
|
return true
|
|
}
|
|
#partial switch statement.kind {
|
|
case .Block:
|
|
if contains_expand_control(checker, statement.body, target_label, allow_unlabeled) {
|
|
return true
|
|
}
|
|
case .If:
|
|
if contains_expand_control(checker, statement.body, target_label, allow_unlabeled) ||
|
|
contains_expand_control(checker, statement.else_body, target_label, allow_unlabeled) {
|
|
return true
|
|
}
|
|
case .Match, .Match_Arm:
|
|
if contains_expand_control(checker, statement.body, target_label, allow_unlabeled) {
|
|
return true
|
|
}
|
|
case .For, .While:
|
|
// Unlabelled control belongs to the nested loop. A labelled jump can still
|
|
// name the surrounding expand loop and is therefore relevant here.
|
|
if contains_expand_control(checker, statement.body, target_label, false) {
|
|
return true
|
|
}
|
|
case .Defer:
|
|
if statement.update != ast.INVALID_STMT &&
|
|
contains_expand_control(checker, []ast.Stmt_Id{statement.update}, target_label, false) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
copy_stmt_ids :: proc(checker: ^Checker, source_ids: []ast.Stmt_Id) -> []ast.Stmt_Id {
|
|
result := make([]ast.Stmt_Id, len(source_ids), checker.ast_module.allocator)
|
|
copy(result, source_ids)
|
|
return result
|
|
}
|
|
|
|
copy_symbols :: proc(checker: ^Checker, source_ids: []symbol.Id) -> []symbol.Id {
|
|
result := make([]symbol.Id, len(source_ids), checker.ast_module.allocator)
|
|
copy(result, source_ids)
|
|
return result
|
|
}
|
|
|
|
copy_expr_ids :: proc(checker: ^Checker, source_ids: []ast.Expr_Id) -> []ast.Expr_Id {
|
|
result := make([]ast.Expr_Id, len(source_ids), checker.ast_module.allocator)
|
|
copy(result, source_ids)
|
|
return result
|
|
}
|
|
|
|
clone_statement_body :: proc(checker: ^Checker, statement: ast.Stmt, body: []ast.Stmt_Id) -> ast.Stmt_Id {
|
|
clone := statement
|
|
clone.captures = copy_symbols(checker, statement.captures)
|
|
clone.patterns = copy_expr_ids(checker, statement.patterns)
|
|
clone.body = copy_stmt_ids(checker, body)
|
|
clone.else_body = copy_stmt_ids(checker, statement.else_body)
|
|
id := ast.stmt_id(len(checker.ast_module.statements))
|
|
append(&checker.ast_module.statements, clone)
|
|
return id
|
|
}
|
|
|
|
append_expand_block :: proc(checker: ^Checker, span: source.Span, body: []ast.Stmt_Id, out: ^[dynamic]ast.Stmt_Id) {
|
|
if len(body) == 0 {
|
|
return
|
|
}
|
|
id := ast.stmt_id(len(checker.ast_module.statements))
|
|
append(&checker.ast_module.statements, ast.Stmt{
|
|
kind=.Block,
|
|
span=span,
|
|
body=copy_stmt_ids(checker, body),
|
|
guard=ast.INVALID_EXPR,
|
|
target=ast.INVALID_EXPR,
|
|
expr=ast.INVALID_EXPR,
|
|
update=ast.INVALID_STMT,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
append(out, id)
|
|
}
|
|
|
|
same_stmt_ids :: proc(left, right: []ast.Stmt_Id) -> bool {
|
|
if len(left) != len(right) {
|
|
return false
|
|
}
|
|
for item, index in left {
|
|
if item != right[index] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
flatten_expand_iteration :: proc(
|
|
checker: ^Checker,
|
|
statements: []ast.Stmt_Id,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
out: ^[dynamic]ast.Stmt_Id,
|
|
target_label: symbol.Id,
|
|
diagnostic: ^source.Diagnostic_Id,
|
|
) -> Expand_Control {
|
|
for statement_id in statements {
|
|
statement := checker.ast_module.statements[statement_id]
|
|
if control := expand_control_target(statement, target_label, true); control != .Normal {
|
|
return control
|
|
}
|
|
if statement.kind == .If && len(statement.captures) == 0 {
|
|
if selected, comptime_ok := specialization_bool(checker, statement.expr, pkg, file); comptime_ok {
|
|
selected_body := statement.body if selected else statement.else_body
|
|
branch: [dynamic]ast.Stmt_Id
|
|
branch.allocator = checker.allocator
|
|
flow := flatten_expand_iteration(checker, selected_body, pkg, file, &branch, target_label, diagnostic)
|
|
append_expand_block(checker, statement.span, branch[:], out)
|
|
delete(branch)
|
|
if flow != .Normal {
|
|
return flow
|
|
}
|
|
continue
|
|
}
|
|
if contains_expand_control(checker, statement.body, target_label, true) ||
|
|
contains_expand_control(checker, statement.else_body, target_label, true) {
|
|
if diagnostic != nil {
|
|
diagnostic^ = source.add(
|
|
checker.diagnostics, statement.span,
|
|
"break or continue targeting an expand loop must be compile-time-resolvable",
|
|
)
|
|
}
|
|
return .Invalid
|
|
}
|
|
}
|
|
if statement.kind == .Match {
|
|
if selected_body, _, _, comptime_ok := specialization_match_body(checker, statement, pkg, file); comptime_ok {
|
|
selected: [dynamic]ast.Stmt_Id
|
|
selected.allocator = checker.allocator
|
|
flow := flatten_expand_iteration(checker, selected_body, pkg, file, &selected, target_label, diagnostic)
|
|
if flow != .Normal {
|
|
arm_index := -1
|
|
for arm_id, index in statement.body {
|
|
if same_stmt_ids(checker.ast_module.statements[arm_id].body, selected_body) {
|
|
arm_index = index
|
|
break
|
|
}
|
|
}
|
|
if arm_index >= 0 && len(selected) > 0 {
|
|
arm_id := statement.body[arm_index]
|
|
cloned_arm := clone_statement_body(checker, checker.ast_module.statements[arm_id], selected[:])
|
|
arms := copy_stmt_ids(checker, statement.body)
|
|
arms[arm_index] = cloned_arm
|
|
cloned_match := clone_statement_body(checker, statement, arms)
|
|
delete(arms, checker.ast_module.allocator)
|
|
append(out, cloned_match)
|
|
}
|
|
delete(selected)
|
|
return flow
|
|
}
|
|
delete(selected)
|
|
append(out, statement_id)
|
|
continue
|
|
}
|
|
if contains_expand_control(checker, statement.body, target_label, true) {
|
|
if diagnostic != nil {
|
|
diagnostic^ = source.add(
|
|
checker.diagnostics, statement.span,
|
|
"break or continue targeting an expand loop must be compile-time-resolvable",
|
|
)
|
|
}
|
|
return .Invalid
|
|
}
|
|
}
|
|
if statement.kind == .Block {
|
|
block: [dynamic]ast.Stmt_Id
|
|
block.allocator = checker.allocator
|
|
flow := flatten_expand_iteration(checker, statement.body, pkg, file, &block, target_label, diagnostic)
|
|
if flow == .Normal {
|
|
append(out, statement_id)
|
|
} else {
|
|
append_expand_block(checker, statement.span, block[:], out)
|
|
}
|
|
delete(block)
|
|
if flow != .Normal {
|
|
return flow
|
|
}
|
|
continue
|
|
}
|
|
if (statement.kind == .For || statement.kind == .While || statement.kind == .Defer) &&
|
|
contains_expand_control(checker, []ast.Stmt_Id{statement_id}, target_label, false) {
|
|
if diagnostic != nil {
|
|
diagnostic^ = source.add(
|
|
checker.diagnostics, statement.span,
|
|
"break or continue targeting an expand loop must be compile-time-resolvable",
|
|
)
|
|
}
|
|
return .Invalid
|
|
}
|
|
append(out, statement_id)
|
|
}
|
|
return .Normal
|
|
}
|
|
|
|
build_block :: proc(
|
|
ctx: ^Build_Ctx,
|
|
statements: []ast.Stmt_Id,
|
|
duplicate_scope_start := -1,
|
|
close := true,
|
|
) -> []hir.Stmt_Id {
|
|
checker := ctx.checker
|
|
store := &checker.module.types
|
|
body: [dynamic]hir.Stmt_Id
|
|
body.allocator = checker.allocator
|
|
scope_start := len(ctx.locals^)
|
|
defer_start := len(ctx.defers^)
|
|
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]
|
|
if statement.diagnostic != source.INVALID_DIAGNOSTIC {
|
|
if statement.kind == .Declaration && symbol.is_valid(statement.name) {
|
|
if _, found := find_build_local(ctx.locals^[duplicate_start:], statement.name); !found {
|
|
_ = append_build_local(
|
|
ctx, statement.name, types.I64, !statement.immutable, statement.span,
|
|
)
|
|
}
|
|
}
|
|
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
|
|
continue
|
|
}
|
|
switch statement.kind {
|
|
case .Declaration:
|
|
// A value block (`x :: { ... yield v }` / `x T = { ... }`): the parser
|
|
// leaves `expr` invalid and stashes the block in `body`. Build it, then
|
|
// declare the local from the yielded value (its type for an untyped `::`).
|
|
if statement.expr == ast.INVALID_EXPR {
|
|
expected := types.INVALID
|
|
typed := is_runtime_type(checker, type_from_syntax(checker, statement.type, ctx.pkg, ctx.file))
|
|
if typed {
|
|
expected = type_from_syntax(checker, statement.type, ctx.pkg, ctx.file)
|
|
}
|
|
value, value_type := build_value_source(ctx, &body, statement.body, expected, statement.span, statement.label, statement.value_control_flow)
|
|
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
|
|
}
|
|
if id := add_shadow_diagnostic(
|
|
checker, statement.span, statement.name, "local",
|
|
ctx.pkg, ctx.file, ctx.locals^[:], ctx.loop_labels^[:], ctx.yield_targets^[:],
|
|
); id != source.INVALID_DIAGNOSTIC {
|
|
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 := append_build_local(ctx, statement.name, value_type, !statement.immutable, statement.span)
|
|
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,
|
|
})
|
|
continue
|
|
}
|
|
declared := resolve_inferred_array(checker, type_from_syntax(checker, statement.type, ctx.pkg, ctx.file), statement.expr)
|
|
// Adopt the type inference resolved for this local when the declaration has no
|
|
// concrete annotation and inference carried useful numeric context: constraints,
|
|
// `undefined`, open numeric constants, or arithmetic expressions.
|
|
open_const_decl := !is_runtime_type(checker, declared) && !is_undefined_expr(checker, statement.expr)
|
|
if open_const_decl {
|
|
constant := eval_integer_constant_in_context(checker, statement.expr, ctx.pkg, ctx.file)
|
|
open_const_decl = constant.kind == .Value && fits_i64(constant.value) ||
|
|
is_float_constant_expr(checker, statement.expr)
|
|
}
|
|
numeric_arithmetic_decl := !is_runtime_type(checker, declared) &&
|
|
is_numeric_arithmetic_expr(checker, statement.expr)
|
|
if statement_id != ast.INVALID_STMT && int(statement_id) < len(ctx.local_types) &&
|
|
is_runtime_type(checker, ctx.local_types[statement_id]) &&
|
|
(types.is_constraint(declared) || is_undefined_expr(checker, statement.expr) ||
|
|
open_const_decl || numeric_arithmetic_decl || has_inferred_array_count(checker, declared)) {
|
|
declared = ctx.local_types[statement_id]
|
|
}
|
|
// A still-unresolved constraint means the initializer's numeric
|
|
// family did not satisfy `int`/`float` (`undefined` reports its own).
|
|
if types.is_constraint(declared) && !is_undefined_expr(checker, statement.expr) {
|
|
id := source.addf(
|
|
checker.diagnostics,
|
|
statement.span,
|
|
"could not resolve the '%s' constraint for local '%s'",
|
|
types.name(declared),
|
|
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
|
|
}
|
|
expected := types.INVALID
|
|
value := hir.INVALID_EXPR
|
|
value_type := types.INVALID
|
|
if is_undefined_expr(checker, statement.expr) {
|
|
if statement.immutable {
|
|
id := source.add(
|
|
checker.diagnostics,
|
|
statement.span,
|
|
"'undefined' requires a mutable local declaration",
|
|
)
|
|
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 !is_runtime_type(checker, declared) || has_inferred_array_count(checker, declared) {
|
|
id := source.addf(
|
|
checker.diagnostics,
|
|
statement.span,
|
|
"could not infer a concrete type for 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_type = declared
|
|
} else {
|
|
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 = declared
|
|
} 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 types.is_comptime_only(value_type, &checker.module.types) {
|
|
id := source.addf(
|
|
checker.diagnostics,
|
|
statement.span,
|
|
"local '%s' has a comptime-only type and cannot be stored at runtime",
|
|
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 types.is_noreturn(value_type) {
|
|
id := source.addf(
|
|
checker.diagnostics,
|
|
statement.span,
|
|
"local '%s' cannot store a noreturn value",
|
|
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,
|
|
})
|
|
if _, found := find_build_local(ctx.locals^[duplicate_start:], statement.name); !found {
|
|
_ = append_build_local(ctx, statement.name, types.I64, !statement.immutable, statement.span)
|
|
}
|
|
ctx.problematic^ = true
|
|
continue
|
|
}
|
|
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
|
|
}
|
|
if id := add_shadow_diagnostic(
|
|
checker, statement.span, statement.name, "local",
|
|
ctx.pkg, ctx.file, ctx.locals^[:], ctx.loop_labels^[:], ctx.yield_targets^[:],
|
|
); id != source.INVALID_DIAGNOSTIC {
|
|
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 := append_build_local(ctx, statement.name, value_type, !statement.immutable, statement.span)
|
|
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,
|
|
})
|
|
if value != hir.INVALID_EXPR {
|
|
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,
|
|
)
|
|
if diagnostic, invalid := invalid_expr_diagnostic(checker, target_expr); invalid {
|
|
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=diagnostic,
|
|
})
|
|
ctx.problematic^ = true
|
|
continue
|
|
}
|
|
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
|
|
} else if statement.assignment_op == .Shift_Left ||
|
|
statement.assignment_op == .Shift_Right ||
|
|
statement.assignment_op == .Shift_Left_Saturating {
|
|
rhs_expected = types.U64
|
|
}
|
|
value = build_expr(
|
|
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
|
rhs_expected, ctx.pkg, ctx.file,
|
|
)
|
|
if diagnostic, invalid := invalid_expr_diagnostic(checker, value); invalid {
|
|
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=diagnostic,
|
|
})
|
|
ctx.problematic^ = true
|
|
continue
|
|
}
|
|
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
|
|
case .Bit_And: assignment_op = .Bit_And
|
|
case .Bit_Or: assignment_op = .Bit_Or
|
|
case .Bit_Xor: assignment_op = .Bit_Xor
|
|
case .Shift_Left: assignment_op = .Shift_Left
|
|
case .Shift_Right: assignment_op = .Shift_Right
|
|
case .Shift_Left_Saturating: assignment_op = .Shift_Left_Saturating
|
|
}
|
|
rhs_type := checker.module.exprs[value].type
|
|
is_shift := statement.assignment_op == .Shift_Left ||
|
|
statement.assignment_op == .Shift_Right ||
|
|
statement.assignment_op == .Shift_Left_Saturating
|
|
is_bitwise := statement.assignment_op == .Bit_And ||
|
|
statement.assignment_op == .Bit_Or ||
|
|
statement.assignment_op == .Bit_Xor
|
|
result_type := types.widest(target_type, rhs_type)
|
|
if is_shift {
|
|
if !types.is_concrete_integer(target_type) || !types.is_unsigned(rhs_type, checker.target) {
|
|
id := source.add(checker.diagnostics, statement.span, "shift assignment requires an integer target and unsigned integer count")
|
|
value = invalid_hir_expr(checker, statement.span, id, target_type)
|
|
} else if constant := eval_integer_constant_in_context(checker, statement.expr, ctx.pkg, ctx.file);
|
|
constant.kind == .Value && statement.assignment_op != .Shift_Left_Saturating &&
|
|
constant.value >= i128(types.bits(target_type, checker.target)) {
|
|
id := source.addf(
|
|
checker.diagnostics, statement.span,
|
|
"shift count %d exceeds %s width", constant.value, types.name(target_type),
|
|
)
|
|
value = invalid_hir_expr(checker, statement.span, id, target_type)
|
|
}
|
|
} else if is_bitwise {
|
|
if !types.is_concrete_integer(result_type) {
|
|
id := source.add(checker.diagnostics, statement.span, "bitwise assignment requires compatible concrete integer operands")
|
|
value = invalid_hir_expr(checker, statement.span, id, target_type)
|
|
} else {
|
|
value = coerce_expr(checker, value, target_type, statement.span)
|
|
}
|
|
} else if statement.assignment_op == .Div && types.is_concrete_integer(result_type) {
|
|
id := source.add(
|
|
checker.diagnostics,
|
|
statement.span,
|
|
"integer '/=' is not allowed; assign through an explicit division builtin",
|
|
)
|
|
value = invalid_hir_expr(checker, statement.span, id, target_type)
|
|
} else 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 if statement.expr == ast.INVALID_EXPR {
|
|
// `target = { ... yield v }`: build the value block against the
|
|
// target's type (build_value_block coerces internally).
|
|
value, _ = build_value_source(ctx, &body, statement.body, target_type, statement.span, statement.label, statement.value_control_flow)
|
|
} 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: hir.Expr_Id
|
|
if statement.expr == ast.INVALID_EXPR {
|
|
value, _ = build_value_source(ctx, &body, statement.body, types.INVALID, statement.span, statement.label, statement.value_control_flow)
|
|
} else {
|
|
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
|
|
}
|
|
if _, is_comptime := current_comptime_value(checker, statement.name); is_comptime {
|
|
id := source.addf(
|
|
checker.diagnostics,
|
|
statement.span,
|
|
"cannot assign comptime parameter '%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, found := find_build_local(ctx.locals^[:], statement.name)
|
|
if !found {
|
|
global := find_global(checker, statement.name, ctx.pkg, ctx.file)
|
|
if global == ast.INVALID_GLOBAL {
|
|
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
|
|
}
|
|
target_expr := build_global_reference(checker, global, statement.span, ctx.global_reads)
|
|
if diagnostic, invalid := invalid_expr_diagnostic(checker, target_expr); invalid {
|
|
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=diagnostic,
|
|
})
|
|
ctx.problematic^ = true
|
|
continue
|
|
}
|
|
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
|
|
if statement.expr == ast.INVALID_EXPR {
|
|
value, _ = build_value_source(ctx, &body, statement.body, target_type, statement.span, statement.label, statement.value_control_flow)
|
|
} 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=.Set,
|
|
target=target_expr, expr=value, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[value].kind == .Invalid
|
|
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: hir.Expr_Id
|
|
if statement.expr == ast.INVALID_EXPR {
|
|
value, _ = build_value_source(ctx, &body, statement.body, local.type, statement.span, statement.label, statement.value_control_flow)
|
|
} else {
|
|
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 ctx.defer_depth > 0 {
|
|
id := source.add(checker.diagnostics, statement.span, "cannot 'return' inside a 'defer'")
|
|
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 statement.expr == ast.INVALID_EXPR && !statement.value_control_flow {
|
|
if types.kind(ctx.result, store) == .Fallible &&
|
|
types.is_void(types.fallible_success(ctx.result, store)) {
|
|
flush_defers(ctx, &body, 0)
|
|
value := fallible_aggregate(checker, statement.span, ctx.result, hir.INVALID_EXPR, false)
|
|
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,
|
|
})
|
|
} else if !types.is_void(ctx.result) {
|
|
id := source.add(checker.diagnostics, statement.span, "non-void function must 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
|
|
} else {
|
|
flush_defers(ctx, &body, 0)
|
|
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 := hir.INVALID_EXPR
|
|
error_exit := false
|
|
error_value := hir.INVALID_EXPR
|
|
if statement.value_control_flow {
|
|
if types.kind(ctx.result, store) == .Fallible {
|
|
success := types.fallible_success(ctx.result, store)
|
|
value, _ = build_value_source(ctx, &body, statement.body, success, statement.span, symbol.INVALID, statement.value_control_flow)
|
|
value = coerce_expr(checker, value, success, statement.span)
|
|
value = fallible_aggregate(checker, statement.span, ctx.result, value, false)
|
|
} else {
|
|
value, _ = build_value_source(ctx, &body, statement.body, ctx.result, statement.span, symbol.INVALID, statement.value_control_flow)
|
|
value = coerce_expr(checker, value, ctx.result, statement.span)
|
|
}
|
|
} else if types.kind(ctx.result, store) == .Fallible {
|
|
success := types.fallible_success(ctx.result, store)
|
|
error_type := types.fallible_error(ctx.result, store)
|
|
expr_ast := checker.ast_module.exprs[statement.expr]
|
|
if expr_ast.kind == .Enum_Literal {
|
|
success_has := types.sum_has_name(store, success, u32(expr_ast.name))
|
|
error_has := types.sum_has_name(store, error_type, u32(expr_ast.name))
|
|
if error_has && !success_has {
|
|
error_exit = true
|
|
} else if error_has && success_has {
|
|
id := source.add(checker.diagnostics, expr_ast.span, "ambiguous fallible return member")
|
|
value = invalid_hir_expr(checker, expr_ast.span, id, ctx.result)
|
|
}
|
|
} else if expr_ast.kind == .Struct_Literal {
|
|
target_pkg, available := expr_package(checker, expr_ast, ctx.pkg, ctx.file, true)
|
|
named := types.find_named(store, u32(target_pkg), u32(expr_ast.name), file=u32(expr_lookup_file(expr_ast, ctx.file))) if available else types.INVALID
|
|
named = types.resolve_alias(named, store)
|
|
error_exit = can_implicitly_convert_type(checker, named, error_type)
|
|
} else {
|
|
probe := build_expr(
|
|
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
|
types.INVALID, ctx.pkg, ctx.file,
|
|
)
|
|
probe_type := checker.module.exprs[probe].type
|
|
returns_success := can_implicitly_convert_type(checker, probe_type, success)
|
|
returns_error := can_implicitly_convert_type(checker, probe_type, error_type)
|
|
if returns_error && !returns_success {
|
|
error_exit = true
|
|
value = probe
|
|
} else if !returns_success {
|
|
if projected, refined := refined_error_projection(ctx, probe, error_type, statement.span); refined {
|
|
error_exit = true
|
|
value = projected
|
|
} else if types.is_enum(probe_type, store) || types.is_tagged_union(probe_type, store) {
|
|
id := source.addf(
|
|
checker.diagnostics, statement.span,
|
|
"cannot return %s as success type %s or error type %s",
|
|
type_label(checker, probe_type), type_label(checker, success), type_label(checker, error_type),
|
|
)
|
|
value = invalid_hir_expr(checker, statement.span, id, success)
|
|
}
|
|
} else {
|
|
value = probe
|
|
}
|
|
}
|
|
if value == hir.INVALID_EXPR {
|
|
expected := error_type if error_exit else success
|
|
value = build_expr(
|
|
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
|
expected, ctx.pkg, ctx.file,
|
|
)
|
|
}
|
|
expected := error_type if error_exit else success
|
|
value = coerce_expr(checker, value, expected, statement.span)
|
|
if error_exit && len(ctx.defers^) > 0 && checker.module.exprs[value].kind != .Invalid {
|
|
tmp := append_tracked_local(
|
|
ctx.hir_locals, ctx.local_spans, ctx.local_used, ctx.local_warnable,
|
|
hir.Local{name=checker.sink_symbol, type=error_type, mutable=false}, source.Span{},
|
|
)
|
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind=.Declaration, span=statement.span, local=tmp, expr=value,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
error_value = hir.expr_id(len(checker.module.exprs))
|
|
append(&checker.module.exprs, hir.Expr{
|
|
kind=.Local, span=statement.span, type=error_type, target=hir.local_ref(tmp),
|
|
})
|
|
value = error_value
|
|
}
|
|
value = fallible_aggregate(checker, statement.span, ctx.result, value, error_exit)
|
|
} else {
|
|
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)
|
|
}
|
|
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[value].kind == .Invalid
|
|
// Run deferred statements before returning, but capture the return value
|
|
// first (spill it to a temp) so a defer that mutates the returned local
|
|
// can't change what is returned — Zig evaluates the return value, then
|
|
// runs defers.
|
|
if len(ctx.defers^) > 0 {
|
|
if !error_exit && checker.module.exprs[value].kind != .Invalid {
|
|
tmp := append_tracked_local(
|
|
ctx.hir_locals,
|
|
ctx.local_spans,
|
|
ctx.local_used,
|
|
ctx.local_warnable,
|
|
hir.Local{name = checker.sink_symbol, type = ctx.result, mutable = false},
|
|
source.Span{},
|
|
)
|
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind = .Declaration, span = statement.span, local = tmp, expr = value,
|
|
diagnostic = source.INVALID_DIAGNOSTIC,
|
|
})
|
|
value = hir.expr_id(len(checker.module.exprs))
|
|
append(&checker.module.exprs, hir.Expr{
|
|
kind = .Local, span = statement.span, type = ctx.result, target = hir.local_ref(tmp),
|
|
})
|
|
}
|
|
flush_defers(ctx, &body, 0, error_exit, error_value)
|
|
}
|
|
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,
|
|
})
|
|
case .Expression:
|
|
value := build_expr(checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, types.INVALID, ctx.pkg, ctx.file)
|
|
if diagnostic, invalid := invalid_expr_diagnostic(checker, value); 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 = diagnostic,
|
|
})
|
|
ctx.problematic^ = true
|
|
} else if !types.is_void(checker.module.exprs[value].type) &&
|
|
!types.is_noreturn(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 {
|
|
if selected, comptime_ok := specialization_bool(checker, statement.expr, ctx.pkg, ctx.file); comptime_ok {
|
|
selected_body := statement.body if selected else statement.else_body
|
|
branch := build_block(ctx, selected_body)
|
|
append(&body, ..branch)
|
|
delete(branch, checker.allocator)
|
|
continue
|
|
}
|
|
}
|
|
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
|
|
} else if id := add_shadow_diagnostic(
|
|
checker, statement.span, capture, "capture",
|
|
ctx.pkg, ctx.file, ctx.locals^[:capture_start], ctx.loop_labels^[:], ctx.yield_targets^[:],
|
|
); id != source.INVALID_DIAGNOSTIC {
|
|
diagnostic = id
|
|
valid_unwrap = false
|
|
}
|
|
local = append_build_local(ctx, capture, child, false, statement.span)
|
|
}
|
|
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) &&
|
|
!types.is_noreturn(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) &&
|
|
!types.is_noreturn(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) &&
|
|
!types.is_noreturn(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
|
|
}
|
|
if id := add_label_shadow_diagnostic(ctx, statement.span, statement.label);
|
|
id != source.INVALID_DIAGNOSTIC {
|
|
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
|
|
}
|
|
append(ctx.loop_defer_starts, len(ctx.defers^))
|
|
append(ctx.loop_labels, statement.label)
|
|
append(ctx.loop_is_loop, true)
|
|
loop_body := build_block(ctx, statement.body)
|
|
pop(ctx.loop_is_loop)
|
|
pop(ctx.loop_labels)
|
|
pop(ctx.loop_defer_starts)
|
|
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,
|
|
label=statement.label,
|
|
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:
|
|
if statement.expand {
|
|
if statement.pointer_capture {
|
|
id := source.add(checker.diagnostics, statement.span, "expand for does not support pointer captures")
|
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
append(&checker.module.statements, hir.Stmt{kind=.Trap, span=statement.span, diagnostic=id})
|
|
ctx.problematic^ = true
|
|
continue
|
|
}
|
|
bindings, expand_error := expand_field_bindings(checker, statement.expr, statement.name, ctx.pkg, ctx.file, true)
|
|
if expand_error != .None && expand_error != .Diagnosed {
|
|
message := "expand for requires a comptime tuple, fixed array, range, slice, or reflection value"
|
|
if expand_error == .Quota {
|
|
message = "expand for expansion exceeds the compile-time evaluation quota"
|
|
}
|
|
id := source.add(
|
|
checker.diagnostics, statement.span,
|
|
message,
|
|
)
|
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
append(&checker.module.statements, hir.Stmt{kind=.Trap, span=statement.span, diagnostic=id})
|
|
ctx.problematic^ = true
|
|
} else if expand_error == .None {
|
|
for binding, expand_index in bindings {
|
|
binding_start := push_expand_binding(checker, binding, statement.index_name, expand_index, statement_id)
|
|
iteration: [dynamic]ast.Stmt_Id
|
|
iteration.allocator = checker.allocator
|
|
diagnostic := source.INVALID_DIAGNOSTIC
|
|
control := flatten_expand_iteration(
|
|
checker, statement.body, ctx.pkg, ctx.file, &iteration, statement.label, &diagnostic,
|
|
)
|
|
if control == .Invalid {
|
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind=.Trap, span=statement.span, diagnostic=diagnostic,
|
|
})
|
|
ctx.problematic^ = true
|
|
} else {
|
|
expanded := build_block(ctx, iteration[:])
|
|
append(&body, ..expanded)
|
|
delete(expanded, checker.allocator)
|
|
}
|
|
delete(iteration)
|
|
pop_expand_binding(checker, binding_start)
|
|
if control == .Break || control == .Invalid {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
delete(bindings, checker.allocator)
|
|
continue
|
|
}
|
|
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^)
|
|
if statement.name != checker.sink_symbol {
|
|
if id := add_shadow_diagnostic(
|
|
checker, statement.span, statement.name, "capture",
|
|
ctx.pkg, ctx.file, ctx.locals^[:], ctx.loop_labels^[:], ctx.yield_targets^[:],
|
|
); id != source.INVALID_DIAGNOSTIC {
|
|
diagnostic = id
|
|
valid_loop = false
|
|
}
|
|
}
|
|
item_local := append_build_local(ctx, statement.name, capture_type, false, statement.span)
|
|
index_local := hir.INVALID_LOCAL
|
|
if symbol.is_valid(statement.index_name) {
|
|
if statement.index_name == statement.name && statement.name != checker.sink_symbol {
|
|
diagnostic = source.add(checker.diagnostics, statement.span, "for-loop captures must have distinct names")
|
|
valid_loop = false
|
|
} else if id := add_shadow_diagnostic(
|
|
checker, statement.span, statement.index_name, "capture",
|
|
ctx.pkg, ctx.file, ctx.locals^[:capture_start], ctx.loop_labels^[:], ctx.yield_targets^[:],
|
|
); id != source.INVALID_DIAGNOSTIC {
|
|
diagnostic = id
|
|
valid_loop = false
|
|
} else {
|
|
index_local = append_build_local(ctx, statement.index_name, types.USIZE, false, statement.span)
|
|
}
|
|
}
|
|
if id := add_label_shadow_diagnostic(ctx, statement.span, statement.label);
|
|
id != source.INVALID_DIAGNOSTIC {
|
|
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
|
|
}
|
|
append(ctx.loop_defer_starts, len(ctx.defers^))
|
|
append(ctx.loop_labels, statement.label)
|
|
append(ctx.loop_is_loop, true)
|
|
loop_body := build_block(ctx, statement.body, capture_start)
|
|
pop(ctx.loop_is_loop)
|
|
pop(ctx.loop_labels)
|
|
pop(ctx.loop_defer_starts)
|
|
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,
|
|
label=statement.label,
|
|
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 .Break, .Continue:
|
|
// `break :L` / `continue :L` targets the innermost enclosing loop labeled `L`;
|
|
// an unlabeled one targets the innermost loop. Inside a `defer`, `loop_floor`
|
|
// hides the loops opened outside the defer.
|
|
target_index := -1
|
|
if symbol.is_valid(statement.label) {
|
|
// `break :L` targets a labeled loop or block; `continue :L` only a loop.
|
|
for i := len(ctx.loop_labels^) - 1; i >= ctx.loop_floor; i -= 1 {
|
|
if ctx.loop_labels^[i] == statement.label &&
|
|
(ctx.loop_is_loop^[i] || statement.kind == .Break) {
|
|
target_index = i
|
|
break
|
|
}
|
|
}
|
|
} else {
|
|
// Unlabeled `break`/`continue` targets the innermost loop, skipping blocks.
|
|
for i := len(ctx.loop_defer_starts^) - 1; i >= ctx.loop_floor; i -= 1 {
|
|
if ctx.loop_is_loop^[i] {
|
|
target_index = i
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if target_index < 0 {
|
|
keyword := "break" if statement.kind == .Break else "continue"
|
|
id: source.Diagnostic_Id
|
|
if symbol.is_valid(statement.label) {
|
|
id = source.addf(checker.diagnostics, statement.span,
|
|
"no enclosing loop is labeled '%s'", symbol_text(checker, statement.label))
|
|
} else {
|
|
id = source.addf(checker.diagnostics, statement.span, "'%s' outside of a loop", keyword)
|
|
}
|
|
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
|
|
}
|
|
// Exit the loop body and any blocks between here and the target loop: run their
|
|
// deferred statements down to and including the target loop body.
|
|
flush_defers(ctx, &body, ctx.loop_defer_starts^[target_index])
|
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind = .Break if statement.kind == .Break else .Continue,
|
|
span = statement.span, label = statement.label, expr = hir.INVALID_EXPR,
|
|
local = hir.INVALID_LOCAL, diagnostic = source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .Block:
|
|
if symbol.is_valid(statement.label) {
|
|
// A labeled block statement (`blk: { … break :blk … }`): a break target
|
|
// with an exit-label boundary, built as a HIR `.Block`. Not a loop, so
|
|
// unlabeled `break`/`continue` and `continue :blk` skip it.
|
|
if id := add_label_shadow_diagnostic(ctx, statement.span, statement.label);
|
|
id != source.INVALID_DIAGNOSTIC {
|
|
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
|
|
}
|
|
append(ctx.loop_defer_starts, len(ctx.defers^))
|
|
append(ctx.loop_labels, statement.label)
|
|
append(ctx.loop_is_loop, false)
|
|
built := build_block(ctx, statement.body)
|
|
pop(ctx.loop_is_loop)
|
|
pop(ctx.loop_labels)
|
|
pop(ctx.loop_defer_starts)
|
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind = .Block, span = statement.span, label = statement.label,
|
|
then_body = built, local = hir.INVALID_LOCAL, target = hir.INVALID_EXPR,
|
|
diagnostic = source.INVALID_DIAGNOSTIC,
|
|
})
|
|
continue
|
|
}
|
|
// A bare `{ ... }` scope: build it (its own locals/defers are scoped by
|
|
// the recursive call) and splice its statements in.
|
|
block := build_block(ctx, statement.body)
|
|
for stmt in block {
|
|
append(&body, stmt)
|
|
}
|
|
delete(block, checker.allocator)
|
|
case .Yield:
|
|
// A labeled `yield :blk x` exits the value-loop labeled `blk`: assign the
|
|
// result slot, then `break` (which flushes defers down to the loop body and
|
|
// branches to its exit). HIR holds no `.Yield` — it becomes Assignment + Break.
|
|
if symbol.is_valid(statement.label) {
|
|
target_index := -1
|
|
for i := len(ctx.yield_targets^) - 1; i >= 0; i -= 1 {
|
|
if ctx.yield_targets^[i].label == statement.label {
|
|
target_index = i
|
|
break
|
|
}
|
|
}
|
|
if target_index < 0 {
|
|
id := source.addf(checker.diagnostics, statement.span,
|
|
"no enclosing value loop or block is labeled '%s'", symbol_text(checker, statement.label))
|
|
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
|
|
}
|
|
target := &ctx.yield_targets^[target_index]
|
|
yielded := hir.INVALID_EXPR
|
|
if statement.value_control_flow {
|
|
yielded, _ = build_value_source(ctx, &body, statement.body, target.slot_type, statement.span, symbol.INVALID, statement.value_control_flow)
|
|
} else {
|
|
yielded = build_expr(checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, target.slot_type, ctx.pkg, ctx.file)
|
|
}
|
|
if yielded != hir.INVALID_EXPR && types.is_void(checker.module.exprs[yielded].type) {
|
|
id := source.add(checker.diagnostics, statement.span, "'yield' expression must produce a non-void 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
|
|
}
|
|
yielded = resolve_loop_slot(ctx, target, yielded, checker.module.exprs[yielded].type if yielded != hir.INVALID_EXPR else types.INVALID, statement.span)
|
|
if target.slot == hir.INVALID_LOCAL || yielded == hir.INVALID_EXPR {
|
|
id := source.add(checker.diagnostics, statement.span,
|
|
"could not determine the value loop's yield type; annotate the binding or yield a concrete value first")
|
|
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
|
|
}
|
|
// slot = value (the slot is un-nameable, so no defer can mutate it; no spill).
|
|
emit_slot_assign(checker, &body, target.slot, yielded, statement.span)
|
|
// Exit the target: flush defers down to its body, then a labeled break.
|
|
flush_defers(ctx, &body, target.defer_floor)
|
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind = .Break, span = statement.span, label = target.label, expr = hir.INVALID_EXPR,
|
|
local = hir.INVALID_LOCAL, diagnostic = source.INVALID_DIAGNOSTIC,
|
|
})
|
|
continue
|
|
}
|
|
// An unlabeled yield reaching here is misplaced: a legitimate trailing yield
|
|
// is peeled by the value builders (value block / if branch / loop fall-through).
|
|
id := source.add(
|
|
checker.diagnostics, statement.span,
|
|
"'yield' is only valid as the final statement of a value block, or as 'yield :label' inside a labeled value loop or block",
|
|
)
|
|
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
|
|
case .Defer:
|
|
deferred := checker.ast_module.statements[statement.update]
|
|
if deferred.kind == .Return || deferred.kind == .Break ||
|
|
deferred.kind == .Continue || deferred.kind == .Defer {
|
|
keyword := "return"
|
|
if deferred.kind == .Break { keyword = "break" }
|
|
if deferred.kind == .Continue { keyword = "continue" }
|
|
if deferred.kind == .Defer { keyword = "defer" }
|
|
id := source.addf(checker.diagnostics, statement.span, "cannot defer a '%s' statement", keyword)
|
|
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 statement.error_only && types.kind(ctx.result, store) != .Fallible {
|
|
id := source.add(checker.diagnostics, statement.span, "'errdefer' requires an enclosing fallible 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
|
|
continue
|
|
}
|
|
// Build the deferred statement once, guarded so a `return` inside it is
|
|
// rejected and `break`/`continue` only target loops opened within the
|
|
// defer; its hir is replayed at each scope exit, not emitted here.
|
|
capture_start := len(ctx.locals^)
|
|
capture := hir.INVALID_LOCAL
|
|
if statement.error_only && len(statement.captures) > 0 &&
|
|
statement.captures[0] != checker.sink_symbol {
|
|
capture = append_build_local(
|
|
ctx, statement.captures[0], types.fallible_error(ctx.result, store), false, statement.span,
|
|
)
|
|
}
|
|
saved_floor := ctx.loop_floor
|
|
ctx.defer_depth += 1
|
|
ctx.loop_floor = len(ctx.loop_defer_starts^)
|
|
entry := build_block(ctx, []ast.Stmt_Id{statement.update})
|
|
ctx.loop_floor = saved_floor
|
|
ctx.defer_depth -= 1
|
|
resize(ctx.locals, capture_start)
|
|
append(ctx.defers, Defer_Entry{
|
|
body=entry, error_only=statement.error_only, capture=capture,
|
|
})
|
|
case .Match:
|
|
if selected_body, capture, has_capture, comptime_ok := specialization_match_body(checker, statement, ctx.pkg, ctx.file); comptime_ok {
|
|
if has_capture {
|
|
append(&checker.static_bindings, capture)
|
|
}
|
|
expanded := build_block(ctx, selected_body)
|
|
if has_capture {
|
|
_ = pop(&checker.static_bindings)
|
|
}
|
|
append(&body, ..expanded)
|
|
delete(expanded, checker.allocator)
|
|
} else {
|
|
build_match(ctx, &body, statement)
|
|
}
|
|
case .Match_Arm:
|
|
// Arms are only reachable through their enclosing `.Match`; one on its own
|
|
// is a parser bug.
|
|
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 = source.add(checker.diagnostics, statement.span, "unexpected match arm outside 'match'"),
|
|
})
|
|
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
|
|
}
|
|
}
|
|
// Normal fall-through exit: run this block's own deferred statements, unless
|
|
// every path already exited early (return/break/continue) — that would only
|
|
// emit unreachable duplicates. A value block (`close=false`) skips this so its
|
|
// caller can capture the yielded value before flushing the block's defers.
|
|
if close {
|
|
if !all_paths_exit(&checker.module, body[:]) {
|
|
flush_defers(ctx, &body, defer_start)
|
|
}
|
|
// Free this block's deferred-statement entry slices (their stmt ids were
|
|
// already replayed at every path that can leave this block) and pop the frame.
|
|
for i := defer_start; i < len(ctx.defers^); i += 1 {
|
|
delete(ctx.defers^[i].body, checker.allocator)
|
|
}
|
|
resize(ctx.defers, defer_start)
|
|
resize(ctx.locals, scope_start)
|
|
}
|
|
return body[:]
|
|
}
|
|
|
|
// build_value_block builds a `{ ... yield v }` value block whose final statement
|
|
// must be a `yield`. Catch handlers may instead exit on every path or complete with
|
|
// void, in which case `allow_exit` leaves the fallback expression invalid. Otherwise
|
|
// it builds the leading statements inline, evaluates the yield in their scope, then
|
|
// captures the value before running defers. `expected` is the binding's type (INVALID
|
|
// for an untyped `::`).
|
|
build_value_block :: proc(
|
|
ctx: ^Build_Ctx,
|
|
body: ^[dynamic]hir.Stmt_Id,
|
|
body_stmts: []ast.Stmt_Id,
|
|
expected: types.Type,
|
|
span: source.Span,
|
|
allow_exit := false,
|
|
) -> (value: hir.Expr_Id, value_type: types.Type) {
|
|
checker := ctx.checker
|
|
n := len(body_stmts)
|
|
if n == 0 || checker.ast_module.statements[body_stmts[n - 1]].kind != .Yield {
|
|
// Build whatever is there so inner errors (and misplaced yields) surface, then
|
|
// report the missing trailing yield.
|
|
inner := build_block(ctx, body_stmts)
|
|
for s in inner {
|
|
append(body, s)
|
|
}
|
|
if allow_exit && (types.is_void(expected) || all_paths_exit(&checker.module, inner)) {
|
|
delete(inner, checker.allocator)
|
|
return hir.INVALID_EXPR, expected
|
|
}
|
|
delete(inner, checker.allocator)
|
|
id := source.add(checker.diagnostics, span, "a value block must end with an explicit 'yield'")
|
|
ctx.problematic^ = true
|
|
return invalid_hir_expr(checker, span, id), types.INVALID
|
|
}
|
|
scope_start := len(ctx.locals^)
|
|
defer_start := len(ctx.defers^)
|
|
// Leading statements keep the scope open (close=false) so the yield can still see
|
|
// the block's locals; any nested `yield` hits the erroring `.Yield` switch case.
|
|
leading := build_block(ctx, body_stmts[:n - 1], close = false)
|
|
for s in leading {
|
|
append(body, s)
|
|
}
|
|
delete(leading, checker.allocator)
|
|
|
|
yield_stmt := checker.ast_module.statements[body_stmts[n - 1]]
|
|
if yield_stmt.value_control_flow {
|
|
value, value_type = build_value_source(ctx, body, yield_stmt.body, expected, yield_stmt.span, symbol.INVALID, yield_stmt.value_control_flow)
|
|
} else {
|
|
value = build_expr(
|
|
checker, yield_stmt.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
|
expected, ctx.pkg, ctx.file,
|
|
)
|
|
value_type = checker.module.exprs[value].type
|
|
}
|
|
if types.is_void(value_type) {
|
|
id := source.add(checker.diagnostics, yield_stmt.span, "'yield' expression must produce a non-void value")
|
|
value = invalid_hir_expr(checker, yield_stmt.span, id)
|
|
value_type = types.INVALID
|
|
ctx.problematic^ = true
|
|
} else if types.is_void(expected) {
|
|
id := source.add(checker.diagnostics, yield_stmt.span, "void value context must fall through instead of yielding")
|
|
value = invalid_hir_expr(checker, yield_stmt.span, id)
|
|
value_type = types.INVALID
|
|
ctx.problematic^ = true
|
|
} else if is_runtime_type(checker, expected) {
|
|
value = coerce_expr(checker, value, expected, yield_stmt.span)
|
|
value_type = checker.module.exprs[value].type
|
|
}
|
|
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[value].kind == .Invalid
|
|
|
|
// Run the block's deferred statements before the value escapes, but capture the
|
|
// value first (spill to a temp) so a defer can't change what is yielded — the same
|
|
// rule as `return`.
|
|
if len(ctx.defers^) > defer_start {
|
|
if checker.module.exprs[value].kind != .Invalid {
|
|
tmp := append_tracked_local(
|
|
ctx.hir_locals,
|
|
ctx.local_spans,
|
|
ctx.local_used,
|
|
ctx.local_warnable,
|
|
hir.Local{name = checker.sink_symbol, type = value_type, mutable = false},
|
|
source.Span{},
|
|
)
|
|
append(body, hir.stmt_id(len(checker.module.statements)))
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind = .Declaration, span = yield_stmt.span, local = tmp, expr = value,
|
|
diagnostic = source.INVALID_DIAGNOSTIC,
|
|
})
|
|
value = hir.expr_id(len(checker.module.exprs))
|
|
append(&checker.module.exprs, hir.Expr{
|
|
kind = .Local, span = yield_stmt.span, type = value_type, target = hir.local_ref(tmp),
|
|
})
|
|
}
|
|
flush_defers(ctx, body, defer_start)
|
|
}
|
|
// Close the scope (build_block left it open for us).
|
|
for i := defer_start; i < len(ctx.defers^); i += 1 {
|
|
delete(ctx.defers^[i].body, checker.allocator)
|
|
}
|
|
resize(ctx.defers, defer_start)
|
|
resize(ctx.locals, scope_start)
|
|
return value, value_type
|
|
}
|
|
|
|
// build_value_source feeds a declaration/assignment RHS into the right value builder.
|
|
// Braced blocks are plain value blocks and must end in their own `yield`; bare RHS
|
|
// control flow (`x :: match ...`) sets `value_control_flow` and can produce directly.
|
|
// `label` is the labeled-block label (INVALID otherwise).
|
|
build_value_source :: proc(
|
|
ctx: ^Build_Ctx,
|
|
body: ^[dynamic]hir.Stmt_Id,
|
|
body_stmts: []ast.Stmt_Id,
|
|
expected: types.Type,
|
|
span: source.Span,
|
|
label := symbol.INVALID,
|
|
value_control_flow := false,
|
|
allow_exit := false,
|
|
) -> (value: hir.Expr_Id, value_type: types.Type) {
|
|
checker := ctx.checker
|
|
if symbol.is_valid(label) {
|
|
return build_value_labeled_block(ctx, body, body_stmts, label, expected, span)
|
|
}
|
|
if value_control_flow && len(body_stmts) == 1 {
|
|
#partial switch checker.ast_module.statements[body_stmts[0]].kind {
|
|
case .If:
|
|
return build_value_if(ctx, body, body_stmts[0], expected, span)
|
|
case .For, .While:
|
|
return build_value_loop(ctx, body, body_stmts[0], expected, span)
|
|
case .Match:
|
|
return build_value_match(ctx, body, body_stmts[0], expected, span)
|
|
}
|
|
}
|
|
return build_value_block(ctx, body, body_stmts, expected, span, allow_exit)
|
|
}
|
|
|
|
// new_value_slot allocates a fresh, un-nameable mutable local to hold a value-if/loop
|
|
// result. Branches/iterations assign it; the construct's value is a read of it.
|
|
new_value_slot :: proc(ctx: ^Build_Ctx, slot_type: types.Type) -> hir.Local_Id {
|
|
return append_tracked_local(
|
|
ctx.hir_locals,
|
|
ctx.local_spans,
|
|
ctx.local_used,
|
|
ctx.local_warnable,
|
|
hir.Local{name = ctx.checker.sink_symbol, type = slot_type, mutable = true},
|
|
source.Span{},
|
|
)
|
|
}
|
|
|
|
// slot_read builds a `.Local` read of a result slot.
|
|
slot_read :: proc(checker: ^Checker, slot: hir.Local_Id, slot_type: types.Type, span: source.Span) -> hir.Expr_Id {
|
|
id := hir.expr_id(len(checker.module.exprs))
|
|
append(&checker.module.exprs, hir.Expr{
|
|
kind = .Local, span = span, type = slot_type, target = hir.local_ref(slot),
|
|
})
|
|
return id
|
|
}
|
|
|
|
// emit_slot_assign appends a bare-local `slot = value` assignment to `out`.
|
|
emit_slot_assign :: proc(checker: ^Checker, out: ^[dynamic]hir.Stmt_Id, slot: hir.Local_Id, value: hir.Expr_Id, span: source.Span) {
|
|
append(out, hir.stmt_id(len(checker.module.statements)))
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind = .Assignment, span = span, expr = value, local = slot,
|
|
target = hir.INVALID_EXPR, diagnostic = source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
|
|
string_peer_slice_type :: proc(checker: ^Checker, value: types.Type) -> types.Type {
|
|
pointer, array, ok := types.array_pointer(value, &checker.module.types)
|
|
if ok && !pointer.mutable && !array.mutable &&
|
|
array.child == types.U8 && array.has_sentinel && array.sentinel == 0 {
|
|
return types.slice(&checker.module.types, types.U8, false, true, 0)
|
|
}
|
|
return types.INVALID
|
|
}
|
|
|
|
adopt_value_slot :: proc(
|
|
ctx: ^Build_Ctx,
|
|
slot: ^hir.Local_Id,
|
|
slot_type: ^types.Type,
|
|
value: hir.Expr_Id,
|
|
vtype: types.Type,
|
|
span: source.Span,
|
|
) -> hir.Expr_Id {
|
|
checker := ctx.checker
|
|
if slot^ == hir.INVALID_LOCAL {
|
|
peer := string_peer_slice_type(checker, vtype)
|
|
if types.is_valid(peer) {
|
|
slot_type^ = peer
|
|
slot^ = new_value_slot(ctx, slot_type^)
|
|
return coerce_expr(checker, value, slot_type^, span)
|
|
}
|
|
slot_type^ = vtype
|
|
slot^ = new_value_slot(ctx, slot_type^)
|
|
return value
|
|
}
|
|
return coerce_expr(checker, value, slot_type^, span)
|
|
}
|
|
|
|
// build_value_if turns `if c { … yield A } else { … yield B }` into a result slot
|
|
// each branch assigns, read after the if. Every path must yield: a mandatory `else`,
|
|
// each branch ends in `yield`, and all branches share a type (the first establishes it
|
|
// when untyped; later branches coerce). HIR holds an ordinary `.If` + a `.Local` read.
|
|
build_value_if :: proc(
|
|
ctx: ^Build_Ctx,
|
|
body: ^[dynamic]hir.Stmt_Id,
|
|
if_id: ast.Stmt_Id,
|
|
expected: types.Type,
|
|
span: source.Span,
|
|
) -> (value: hir.Expr_Id, value_type: types.Type) {
|
|
checker := ctx.checker
|
|
slot := hir.INVALID_LOCAL
|
|
slot_type := types.INVALID
|
|
if is_runtime_type(checker, expected) {
|
|
slot_type = expected
|
|
slot = new_value_slot(ctx, slot_type)
|
|
}
|
|
subtree: [dynamic]hir.Stmt_Id
|
|
subtree.allocator = checker.allocator
|
|
ok := emit_value_if(ctx, &subtree, if_id, &slot, &slot_type, span)
|
|
if !ok || slot == hir.INVALID_LOCAL {
|
|
for s in subtree {
|
|
append(body, s)
|
|
}
|
|
delete(subtree)
|
|
ctx.problematic^ = true
|
|
return invalid_hir_expr(checker, span, source.INVALID_DIAGNOSTIC), types.INVALID
|
|
}
|
|
// The slot's poison declaration precedes the if; every path assigns it.
|
|
append(body, hir.stmt_id(len(checker.module.statements)))
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind = .Declaration, span = span, local = slot, expr = hir.INVALID_EXPR,
|
|
diagnostic = source.INVALID_DIAGNOSTIC,
|
|
})
|
|
for s in subtree {
|
|
append(body, s)
|
|
}
|
|
delete(subtree)
|
|
value = slot_read(checker, slot, slot_type, span)
|
|
return value, slot_type
|
|
}
|
|
|
|
// emit_value_if builds one `if`/`else if`/`else` level of a value-if, appending the
|
|
// assembled `.If` to `out`. `slot`/`slot_type` thread through so the first branch can
|
|
// fix an untyped slot and `else if` chains share it.
|
|
emit_value_if :: proc(
|
|
ctx: ^Build_Ctx,
|
|
out: ^[dynamic]hir.Stmt_Id,
|
|
if_id: ast.Stmt_Id,
|
|
slot: ^hir.Local_Id,
|
|
slot_type: ^types.Type,
|
|
span: source.Span,
|
|
) -> bool {
|
|
checker := ctx.checker
|
|
if_stmt := checker.ast_module.statements[if_id]
|
|
|
|
condition := hir.INVALID_EXPR
|
|
guard := hir.INVALID_EXPR
|
|
unwraps: []hir.Conditional_Unwrap = nil
|
|
capture_start := len(ctx.locals^)
|
|
|
|
if len(if_stmt.captures) > 0 {
|
|
// Unwrap value-if (`name :: if opt |v| { yield v } else { yield 0 }`): mirror the
|
|
// build-pass unwrap arm to bind captures + guard; each branch then assigns the slot
|
|
// like any other branch, and the HIR `.If` carries the unwraps (lowering handles it).
|
|
ast_operands: [dynamic]ast.Expr_Id
|
|
ast_operands.allocator = checker.allocator
|
|
flatten_conditional_unwrap_operands(checker.ast_module, if_stmt.expr, &ast_operands)
|
|
ok := true
|
|
if len(ast_operands) != len(if_stmt.captures) {
|
|
source.addf(checker.diagnostics, if_stmt.span,
|
|
"'if' unwrap has %d operands but %d captures", len(ast_operands), len(if_stmt.captures))
|
|
ok = 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
|
|
v := build_expr(checker, operand, ctx.locals^[:], ctx.global_reads, ctx.calls, types.INVALID, ctx.pkg, ctx.file)
|
|
values[index] = v
|
|
vt := checker.module.exprs[v].type
|
|
if checker.module.exprs[v].kind == .Invalid {
|
|
ok = false
|
|
} else if !types.is_optional(vt, &checker.module.types) {
|
|
source.addf(checker.diagnostics, checker.ast_module.exprs[operand].span,
|
|
"'if' unwrap requires an optional value (operand %d)", index + 1)
|
|
ok = false
|
|
} else {
|
|
child_types[index] = types.child_type(vt, &checker.module.types)
|
|
}
|
|
}
|
|
unwrap_list: [dynamic]hir.Conditional_Unwrap
|
|
unwrap_list.allocator = checker.allocator
|
|
for capture, index in if_stmt.captures {
|
|
child := child_types[index] if index < len(child_types) else types.INVALID
|
|
local := hir.INVALID_LOCAL
|
|
if capture != checker.sink_symbol {
|
|
if _, dup := find_build_local(ctx.locals^[capture_start:], capture); dup {
|
|
source.add(checker.diagnostics, if_stmt.span, "'if' unwrap captures must have distinct names")
|
|
ok = false
|
|
} else if add_shadow_diagnostic(
|
|
checker, if_stmt.span, capture, "capture",
|
|
ctx.pkg, ctx.file, ctx.locals^[:capture_start], ctx.loop_labels^[:], ctx.yield_targets^[:],
|
|
) != source.INVALID_DIAGNOSTIC {
|
|
ok = false
|
|
}
|
|
local = append_build_local(ctx, capture, child, false, if_stmt.span)
|
|
}
|
|
if index < len(values) {
|
|
append(&unwrap_list, hir.Conditional_Unwrap{expr=values[index], local=local})
|
|
}
|
|
}
|
|
if if_stmt.guard != ast.INVALID_EXPR {
|
|
guard = build_expr(checker, if_stmt.guard, ctx.locals^[:], ctx.global_reads, ctx.calls, types.BOOL, ctx.pkg, ctx.file)
|
|
if checker.module.exprs[guard].kind == .Invalid {
|
|
ok = false
|
|
} else if !types.is_bool(checker.module.exprs[guard].type) &&
|
|
!types.is_noreturn(checker.module.exprs[guard].type) {
|
|
source.add(checker.diagnostics, checker.ast_module.exprs[if_stmt.guard].span, "'if' unwrap guard must be a bool")
|
|
ok = false
|
|
}
|
|
}
|
|
delete(values, checker.allocator)
|
|
delete(child_types, checker.allocator)
|
|
delete(ast_operands)
|
|
if !ok {
|
|
resize(ctx.locals, capture_start)
|
|
delete(unwrap_list)
|
|
ctx.problematic^ = true
|
|
return false
|
|
}
|
|
unwraps = unwrap_list[:]
|
|
} else {
|
|
condition = build_expr(checker, if_stmt.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) &&
|
|
!types.is_noreturn(checker.module.exprs[condition].type) {
|
|
id := source.add(checker.diagnostics, if_stmt.span, "'if' condition must be a bool")
|
|
condition = invalid_hir_expr(checker, if_stmt.span, id, types.BOOL)
|
|
ctx.problematic^ = true
|
|
}
|
|
}
|
|
|
|
// then-branch (unwrap captures, if any, are in scope here, then dropped before else).
|
|
then_body: [dynamic]hir.Stmt_Id
|
|
then_body.allocator = checker.allocator
|
|
then_ok := emit_value_branch(ctx, &then_body, if_stmt.body, slot, slot_type, span)
|
|
resize(ctx.locals, capture_start)
|
|
if !then_ok {
|
|
delete(then_body)
|
|
delete(unwraps)
|
|
return false
|
|
}
|
|
if if_stmt.else_body == nil {
|
|
source.add(checker.diagnostics, if_stmt.span, "an 'if' used as a value must have an 'else' so every path yields")
|
|
delete(then_body)
|
|
delete(unwraps)
|
|
ctx.problematic^ = true
|
|
return false
|
|
}
|
|
else_body: [dynamic]hir.Stmt_Id
|
|
else_body.allocator = checker.allocator
|
|
branch_ok := true
|
|
if len(if_stmt.else_body) == 1 && checker.ast_module.statements[if_stmt.else_body[0]].kind == .If {
|
|
branch_ok = emit_value_if(ctx, &else_body, if_stmt.else_body[0], slot, slot_type, span)
|
|
} else {
|
|
branch_ok = emit_value_branch(ctx, &else_body, if_stmt.else_body, slot, slot_type, span)
|
|
}
|
|
if !branch_ok {
|
|
delete(then_body)
|
|
delete(else_body)
|
|
delete(unwraps)
|
|
return false
|
|
}
|
|
append(out, hir.stmt_id(len(checker.module.statements)))
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind = .If, span = if_stmt.span, expr = condition, guard = guard, unwraps = unwraps,
|
|
then_body = then_body[:], else_body = else_body[:],
|
|
local = hir.INVALID_LOCAL, target = hir.INVALID_EXPR, diagnostic = source.INVALID_DIAGNOSTIC,
|
|
})
|
|
return true
|
|
}
|
|
|
|
// emit_value_branch builds one branch of a value-if as a value block (leading stmts +
|
|
// trailing yield) and appends `slot = <value>`. The first branch of an untyped value-if
|
|
// fixes the slot type; later branches coerce to it (a mismatch is the "same type" error).
|
|
// A branch that does not yield is valid only if it exits on every path (return/break/
|
|
// continue) — it then produces no value and never reaches the slot read.
|
|
emit_value_branch :: proc(
|
|
ctx: ^Build_Ctx,
|
|
out: ^[dynamic]hir.Stmt_Id,
|
|
branch_stmts: []ast.Stmt_Id,
|
|
slot: ^hir.Local_Id,
|
|
slot_type: ^types.Type,
|
|
span: source.Span,
|
|
) -> bool {
|
|
checker := ctx.checker
|
|
n := len(branch_stmts)
|
|
if n == 1 && checker.ast_module.statements[branch_stmts[0]].kind == .Expression {
|
|
expr_stmt := checker.ast_module.statements[branch_stmts[0]]
|
|
expected := slot_type^ if slot^ != hir.INVALID_LOCAL else types.INVALID
|
|
value := build_expr(checker, expr_stmt.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, expected, ctx.pkg, ctx.file)
|
|
if checker.module.exprs[value].kind == .Invalid {
|
|
ctx.problematic^ = true
|
|
return false
|
|
}
|
|
value = adopt_value_slot(ctx, slot, slot_type, value, checker.module.exprs[value].type, span)
|
|
emit_slot_assign(checker, out, slot^, value, span)
|
|
return true
|
|
}
|
|
ends_in_yield := n > 0 &&
|
|
checker.ast_module.statements[branch_stmts[n - 1]].kind == .Yield &&
|
|
!symbol.is_valid(checker.ast_module.statements[branch_stmts[n - 1]].label)
|
|
if !ends_in_yield {
|
|
// Not a value block: only allowed if every path exits (e.g. `else { return -1 }`),
|
|
// in which case it contributes no value to the slot.
|
|
built := build_block(ctx, branch_stmts)
|
|
for s in built {
|
|
append(out, s)
|
|
}
|
|
terminates := all_paths_exit(&checker.module, built)
|
|
delete(built, checker.allocator)
|
|
if terminates {
|
|
return true
|
|
}
|
|
source.add(checker.diagnostics, span,
|
|
"a value branch must end with 'yield' or exit on every path (return/break/continue)")
|
|
ctx.problematic^ = true
|
|
return false
|
|
}
|
|
value, vtype := build_value_block(ctx, out, branch_stmts, slot_type^, span)
|
|
if checker.module.exprs[value].kind == .Invalid {
|
|
return false
|
|
}
|
|
value = adopt_value_slot(ctx, slot, slot_type, value, vtype, span)
|
|
emit_slot_assign(checker, out, slot^, value, span)
|
|
return true
|
|
}
|
|
|
|
// Match_Built_Arm holds one already-built arm: its dispatch condition (`INVALID_EXPR`
|
|
// for the terminal `else`/exhaustive arm) and its body statements. The chain is
|
|
// assembled backward from these so diagnostics stay in source order.
|
|
Match_Built_Arm :: struct {
|
|
condition: hir.Expr_Id,
|
|
body: []hir.Stmt_Id,
|
|
terminal: bool,
|
|
}
|
|
|
|
// match_subject_location yields a fresh location expr for the `match` subject: a direct
|
|
// read of the value temp, or a deref of the pointer temp when an arm pointer-captures (so
|
|
// captures alias the original storage). Either way its lowered address is the subject's.
|
|
match_subject_location :: proc(checker: ^Checker, subj_local: hir.Local_Id, is_pointer: bool, subject_type, ptr_type: types.Type, span: source.Span) -> hir.Expr_Id {
|
|
if !is_pointer {
|
|
return slot_read(checker, subj_local, subject_type, span)
|
|
}
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind = .Deref, span = span, type = subject_type,
|
|
left = slot_read(checker, subj_local, ptr_type, span),
|
|
target = hir.INVALID_REF, right = hir.INVALID_EXPR, diagnostic = source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
|
|
// match_or ORs a fresh dispatch comparison into an arm's accumulating condition (for a
|
|
// multi-pattern arm), or returns it directly for the first pattern.
|
|
match_or :: proc(checker: ^Checker, condition, cmp: hir.Expr_Id, span: source.Span) -> hir.Expr_Id {
|
|
if condition == hir.INVALID_EXPR {
|
|
return cmp
|
|
}
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind = .Or, span = span, type = types.BOOL,
|
|
left = condition, right = cmp, target = hir.INVALID_REF, diagnostic = source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
|
|
// emit_match desugars a `match` into a single subject spill, one dispatch key read, and
|
|
// an `if`/`else if` chain. `as_value` (with `slot`/`slot_type`) routes each arm body
|
|
// through the value-branch machinery so the construct produces a value; otherwise arm
|
|
// bodies are ordinary statement blocks. Returns false (and emits a `.Trap`) on any error.
|
|
emit_match :: proc(
|
|
ctx: ^Build_Ctx,
|
|
out: ^[dynamic]hir.Stmt_Id,
|
|
statement: ast.Stmt,
|
|
as_value: bool,
|
|
slot: ^hir.Local_Id,
|
|
slot_type: ^types.Type,
|
|
) -> bool {
|
|
checker := ctx.checker
|
|
store := &checker.module.types
|
|
span := statement.span
|
|
|
|
fail :: proc(ctx: ^Build_Ctx, out: ^[dynamic]hir.Stmt_Id, span: source.Span, diagnostic: source.Diagnostic_Id) -> bool {
|
|
checker := ctx.checker
|
|
append(out, hir.stmt_id(len(checker.module.statements)))
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind = .Trap, span = span, expr = hir.INVALID_EXPR,
|
|
local = hir.INVALID_LOCAL, diagnostic = diagnostic,
|
|
})
|
|
ctx.problematic^ = true
|
|
return false
|
|
}
|
|
|
|
// 1. Subject. A pointer capture (`|@cap|`) must alias the original storage, so when
|
|
// any arm requests one we spill the subject's *address* (it must be an addressable
|
|
// lvalue) and route reads through a deref; otherwise we spill the value as a copy.
|
|
// Either spill evaluates the subject exactly once.
|
|
subject := build_expr(checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, types.INVALID, ctx.pkg, ctx.file)
|
|
subject_type := checker.module.exprs[subject].type
|
|
if checker.module.exprs[subject].kind == .Invalid {
|
|
return fail(ctx, out, span, checker.module.exprs[subject].diagnostic)
|
|
}
|
|
|
|
is_tagged := types.is_tagged_union(subject_type, store)
|
|
is_enum_subject := types.is_enum(subject_type, store)
|
|
if types.is_union(subject_type, store) && !is_tagged {
|
|
return fail(ctx, out, span, source.add(checker.diagnostics, span, "cannot 'match' on an untagged union; it has no tag to dispatch on"))
|
|
}
|
|
if !is_tagged && !is_enum_subject && !types.is_concrete_scalar(subject_type) {
|
|
return fail(ctx, out, span, source.addf(checker.diagnostics, span,
|
|
"'match' subject must be a tagged union, enum, or scalar value, not '%s'", type_label(checker, subject_type)))
|
|
}
|
|
|
|
ok := true
|
|
|
|
wants_pointer := false
|
|
for arm_id in statement.body {
|
|
arm := checker.ast_module.statements[arm_id]
|
|
if arm.kind == .Match_Arm && arm.pointer_capture {
|
|
wants_pointer = true
|
|
break
|
|
}
|
|
}
|
|
subj_is_pointer := false
|
|
subj_writable := false
|
|
ptr_type := types.INVALID
|
|
if wants_pointer && is_tagged {
|
|
if hir_is_location(checker, subject) {
|
|
subj_is_pointer = true
|
|
subj_writable = hir_location_writable(checker, subject, ctx.locals^[:])
|
|
ptr_type = types.pointer(store, subject_type, subj_writable, false)
|
|
} else {
|
|
source.add(checker.diagnostics, span, "a pointer capture requires an addressable 'match' subject (bind it to a variable first)")
|
|
ok = false
|
|
}
|
|
}
|
|
|
|
spill_type := ptr_type if subj_is_pointer else subject_type
|
|
spill_value := subject
|
|
if subj_is_pointer {
|
|
spill_value = add_hir_expr(checker, hir.Expr{
|
|
kind = .Address, span = span, type = ptr_type, left = subject,
|
|
target = hir.INVALID_REF, right = hir.INVALID_EXPR, diagnostic = source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
subj_local := append_tracked_local(
|
|
ctx.hir_locals,
|
|
ctx.local_spans,
|
|
ctx.local_used,
|
|
ctx.local_warnable,
|
|
hir.Local{name = checker.sink_symbol, type = spill_type, mutable = false},
|
|
source.Span{},
|
|
)
|
|
append(out, hir.stmt_id(len(checker.module.statements)))
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind = .Declaration, span = span, local = subj_local, expr = spill_value,
|
|
diagnostic = source.INVALID_DIAGNOSTIC,
|
|
})
|
|
refinement_local := hir.INVALID_LOCAL
|
|
if checker.module.exprs[subject].kind == .Local {
|
|
candidate := hir.as_local(checker.module.exprs[subject].target)
|
|
if candidate != hir.INVALID_LOCAL && int(candidate) < len(ctx.hir_locals^) &&
|
|
!ctx.hir_locals^[candidate].mutable {
|
|
refinement_local = candidate
|
|
}
|
|
}
|
|
|
|
// 2. Dispatch key: a tagged union reads its discriminant into its own temp; an enum
|
|
// or scalar compares the subject directly.
|
|
key_local := subj_local
|
|
key_type := spill_type
|
|
tag_enum := types.INVALID
|
|
if is_tagged {
|
|
tag_enum = types.union_tag_enum(subject_type, store)
|
|
tag_read := add_hir_expr(checker, hir.Expr{
|
|
kind = .Union_Tag, span = span, type = tag_enum,
|
|
left = match_subject_location(checker, subj_local, subj_is_pointer, subject_type, ptr_type, span),
|
|
target = hir.INVALID_REF, right = hir.INVALID_EXPR, diagnostic = source.INVALID_DIAGNOSTIC,
|
|
})
|
|
tag_local := append_tracked_local(
|
|
ctx.hir_locals,
|
|
ctx.local_spans,
|
|
ctx.local_used,
|
|
ctx.local_warnable,
|
|
hir.Local{name = checker.sink_symbol, type = tag_enum, mutable = false},
|
|
source.Span{},
|
|
)
|
|
append(out, hir.stmt_id(len(checker.module.statements)))
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind = .Declaration, span = span, local = tag_local, expr = tag_read,
|
|
diagnostic = source.INVALID_DIAGNOSTIC,
|
|
})
|
|
key_local = tag_local
|
|
key_type = tag_enum
|
|
}
|
|
|
|
// 3. Build each arm (forward, for source-order diagnostics). An arm's `patterns` may
|
|
// list several alternatives (`.a, .b:` / `0, 1:`); their conditions are OR'd.
|
|
built: [dynamic]Match_Built_Arm
|
|
built.allocator = checker.allocator
|
|
defer delete(built)
|
|
covered: [dynamic]symbol.Id
|
|
covered.allocator = checker.allocator
|
|
defer delete(covered)
|
|
has_else := false
|
|
has_expand := false
|
|
|
|
for arm_id in statement.body {
|
|
arm := checker.ast_module.statements[arm_id]
|
|
if arm.kind != .Match_Arm {
|
|
ok = false
|
|
continue
|
|
}
|
|
if has_else || has_expand {
|
|
message := "arms after 'else' are unreachable" if has_else else "arms after 'expand' are unreachable"
|
|
source.add(checker.diagnostics, arm.span, message)
|
|
ok = false
|
|
}
|
|
if arm.expand {
|
|
if !is_tagged && !is_enum_subject {
|
|
source.add(checker.diagnostics, arm.span, "'expand' requires an enum or tagged-union match subject")
|
|
ok = false
|
|
continue
|
|
}
|
|
expected_captures := 1 if is_enum_subject else 2
|
|
if len(arm.captures) == 0 || len(arm.captures) > expected_captures {
|
|
description := "exactly one capture" if is_enum_subject else "one or two captures"
|
|
source.addf(checker.diagnostics, arm.span, "expanded match on '%s' requires %s", type_label(checker, subject_type), description)
|
|
ok = false
|
|
}
|
|
if is_enum_subject && arm.pointer_capture {
|
|
source.add(checker.diagnostics, arm.span, "enum expansion does not support pointer captures")
|
|
ok = false
|
|
}
|
|
if len(arm.captures) > 1 && arm.captures[0] != checker.sink_symbol && arm.captures[0] == arm.captures[1] {
|
|
source.add(checker.diagnostics, arm.span, "expand captures must have distinct names")
|
|
ok = false
|
|
}
|
|
remaining := 0
|
|
member_enum := tag_enum if is_tagged else subject_type
|
|
if is_tagged {
|
|
for field, field_index in types.fields_for(store, subject_type) {
|
|
name := symbol.Id(field.name)
|
|
if contains_name(covered[:], name) {
|
|
continue
|
|
}
|
|
member, found := find_enum_member(checker, member_enum, name)
|
|
if !found {
|
|
ok = false
|
|
continue
|
|
}
|
|
remaining += 1
|
|
append(&covered, name)
|
|
member_expr := enum_member_hir(checker, member_enum, name, arm.span)
|
|
condition := add_hir_expr(checker, hir.Expr{
|
|
kind=.Eq, span=arm.span, type=types.BOOL,
|
|
left=slot_read(checker, key_local, key_type, arm.span), right=member_expr,
|
|
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
static_start := len(checker.static_bindings)
|
|
if len(arm.captures) > 1 {
|
|
_ = push_static_integer_binding(checker, arm.captures[1], member_enum, member.value)
|
|
}
|
|
body_arm := arm
|
|
if types.is_void(field.type) && !arm.pointer_capture {
|
|
if len(arm.captures) > 0 {
|
|
_ = push_static_void_binding(checker, arm.captures[0])
|
|
}
|
|
body_arm.captures = nil
|
|
}
|
|
arm_body, body_ok := build_match_arm_body(
|
|
ctx, body_arm, subject_type, subj_local, subj_is_pointer, ptr_type, subj_writable,
|
|
field_index, field.type, refinement_local, []u32{u32(name)}, as_value, slot, slot_type, span,
|
|
)
|
|
pop_static_bindings(checker, static_start)
|
|
ok = body_ok && ok
|
|
append(&built, Match_Built_Arm{condition=condition, body=arm_body})
|
|
}
|
|
} else {
|
|
for member in types.enum_members_for(store, subject_type) {
|
|
name := symbol.Id(member.name)
|
|
if contains_name(covered[:], name) {
|
|
continue
|
|
}
|
|
remaining += 1
|
|
append(&covered, name)
|
|
member_expr := enum_member_hir(checker, subject_type, name, arm.span)
|
|
condition := add_hir_expr(checker, hir.Expr{
|
|
kind=.Eq, span=arm.span, type=types.BOOL,
|
|
left=slot_read(checker, key_local, key_type, arm.span), right=member_expr,
|
|
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
static_start := push_static_integer_binding(
|
|
checker, arm.captures[0] if len(arm.captures) > 0 else symbol.INVALID,
|
|
subject_type, member.value,
|
|
)
|
|
body_arm := arm
|
|
body_arm.captures = nil
|
|
arm_body, body_ok := build_match_arm_body(
|
|
ctx, body_arm, subject_type, subj_local, subj_is_pointer, ptr_type, subj_writable,
|
|
-1, types.INVALID, refinement_local, []u32{u32(name)}, as_value, slot, slot_type, span,
|
|
)
|
|
pop_static_bindings(checker, static_start)
|
|
ok = body_ok && ok
|
|
append(&built, Match_Built_Arm{condition=condition, body=arm_body})
|
|
}
|
|
}
|
|
if remaining == 0 {
|
|
source.add(checker.diagnostics, arm.span, "redundant 'expand': the 'match' already covers every variant")
|
|
ok = false
|
|
}
|
|
has_expand = true
|
|
continue
|
|
}
|
|
is_else := len(arm.patterns) == 0
|
|
condition := hir.INVALID_EXPR
|
|
field_index := -1
|
|
payload_type := types.INVALID
|
|
has_capture := len(arm.captures) > 0
|
|
refinement_variants: [dynamic]u32
|
|
refinement_variants.allocator = checker.allocator
|
|
|
|
if is_else {
|
|
if has_capture {
|
|
source.add(checker.diagnostics, arm.span, "the 'else' arm cannot capture a payload")
|
|
ok = false
|
|
}
|
|
has_else = true
|
|
member_enum := tag_enum if is_tagged else subject_type
|
|
for member in types.enum_members_for(store, member_enum) {
|
|
if !contains_name(covered[:], symbol.Id(member.name)) {
|
|
append(&refinement_variants, member.name)
|
|
}
|
|
}
|
|
} else if is_tagged || is_enum_subject {
|
|
// The capture payload (if any) must be one type across every listed variant.
|
|
capture_field := -1
|
|
capture_payload := types.INVALID
|
|
capture_conflict := types.INVALID
|
|
member_enum := tag_enum if is_tagged else subject_type
|
|
for pat_id in arm.patterns {
|
|
pattern := checker.ast_module.exprs[pat_id]
|
|
if pattern.kind == .Range {
|
|
source.add(checker.diagnostics, arm.span, "range patterns only apply to scalar 'match' subjects")
|
|
ok = false
|
|
continue
|
|
}
|
|
if pattern.kind != .Enum_Literal {
|
|
source.add(checker.diagnostics, arm.span, "an enum or tagged-union 'match' arm must be a '.variant' pattern")
|
|
ok = false
|
|
continue
|
|
}
|
|
if contains_name(covered[:], pattern.name) {
|
|
source.addf(checker.diagnostics, arm.span, "duplicate 'match' arm for '.%s'", symbol_text(checker, pattern.name))
|
|
ok = false
|
|
} else {
|
|
append(&covered, pattern.name)
|
|
append(&refinement_variants, u32(pattern.name))
|
|
}
|
|
if is_tagged {
|
|
index, field, found := find_struct_field(checker, subject_type, pattern.name)
|
|
if !found {
|
|
source.addf(checker.diagnostics, arm.span, "unknown variant '.%s' on '%s'", symbol_text(checker, pattern.name), type_label(checker, subject_type))
|
|
ok = false
|
|
continue
|
|
}
|
|
if capture_field < 0 {
|
|
capture_field = index
|
|
capture_payload = field.type
|
|
} else if !types.equal(capture_payload, field.type) {
|
|
capture_conflict = field.type
|
|
}
|
|
} else {
|
|
if _, found := find_enum_member(checker, subject_type, pattern.name); !found {
|
|
source.addf(checker.diagnostics, arm.span, "unknown member '.%s' on '%s'", symbol_text(checker, pattern.name), type_label(checker, subject_type))
|
|
ok = false
|
|
continue
|
|
}
|
|
}
|
|
member := enum_member_hir(checker, member_enum, pattern.name, arm.span)
|
|
cmp := add_hir_expr(checker, hir.Expr{
|
|
kind = .Eq, span = arm.span, type = types.BOOL,
|
|
left = slot_read(checker, key_local, key_type, arm.span), right = member,
|
|
target = hir.INVALID_REF, diagnostic = source.INVALID_DIAGNOSTIC,
|
|
})
|
|
condition = match_or(checker, condition, cmp, arm.span)
|
|
}
|
|
if has_capture {
|
|
if !is_tagged {
|
|
source.add(checker.diagnostics, arm.span, "only tagged-union variants can capture a payload")
|
|
ok = false
|
|
} else if types.is_valid(capture_conflict) {
|
|
source.addf(checker.diagnostics, arm.span, "capture group with incompatible types '%s' and '%s'",
|
|
type_label(checker, capture_payload), type_label(checker, capture_conflict))
|
|
ok = false
|
|
} else if types.is_void(capture_payload) {
|
|
source.add(checker.diagnostics, arm.span, "this variant has a void payload; there is nothing to capture")
|
|
ok = false
|
|
} else {
|
|
field_index = capture_field
|
|
payload_type = capture_payload
|
|
}
|
|
}
|
|
} else {
|
|
// Scalar subject: each pattern is a literal or a range, compared to the subject.
|
|
if has_capture {
|
|
source.add(checker.diagnostics, arm.span, "only tagged-union variants can capture a payload")
|
|
ok = false
|
|
}
|
|
for pat_id in arm.patterns {
|
|
pat_ast := checker.ast_module.exprs[pat_id]
|
|
cmp := hir.INVALID_EXPR
|
|
if pat_ast.kind == .Range {
|
|
lo := build_expr(checker, pat_ast.left, ctx.locals^[:], ctx.global_reads, ctx.calls, subject_type, ctx.pkg, ctx.file)
|
|
lo = coerce_expr(checker, lo, subject_type, arm.span)
|
|
hi := build_expr(checker, pat_ast.right, ctx.locals^[:], ctx.global_reads, ctx.calls, subject_type, ctx.pkg, ctx.file)
|
|
hi = coerce_expr(checker, hi, subject_type, arm.span)
|
|
if checker.module.exprs[lo].kind == .Invalid || checker.module.exprs[hi].kind == .Invalid {
|
|
ok = false
|
|
continue
|
|
}
|
|
ge := add_hir_expr(checker, hir.Expr{
|
|
kind = .Ge, span = arm.span, type = types.BOOL,
|
|
left = slot_read(checker, key_local, key_type, arm.span), right = lo,
|
|
target = hir.INVALID_REF, diagnostic = source.INVALID_DIAGNOSTIC,
|
|
})
|
|
hi_cmp := add_hir_expr(checker, hir.Expr{
|
|
kind = .Le if pat_ast.integer == 1 else .Lt, span = arm.span, type = types.BOOL,
|
|
left = slot_read(checker, key_local, key_type, arm.span), right = hi,
|
|
target = hir.INVALID_REF, diagnostic = source.INVALID_DIAGNOSTIC,
|
|
})
|
|
cmp = add_hir_expr(checker, hir.Expr{
|
|
kind = .And, span = arm.span, type = types.BOOL,
|
|
left = ge, right = hi_cmp, target = hir.INVALID_REF, diagnostic = source.INVALID_DIAGNOSTIC,
|
|
})
|
|
} else {
|
|
pattern := build_expr(checker, pat_id, ctx.locals^[:], ctx.global_reads, ctx.calls, subject_type, ctx.pkg, ctx.file)
|
|
pattern = coerce_expr(checker, pattern, subject_type, arm.span)
|
|
if checker.module.exprs[pattern].kind == .Invalid {
|
|
ok = false
|
|
continue
|
|
}
|
|
cmp = add_hir_expr(checker, hir.Expr{
|
|
kind = .Eq, span = arm.span, type = types.BOOL,
|
|
left = slot_read(checker, key_local, key_type, arm.span), right = pattern,
|
|
target = hir.INVALID_REF, diagnostic = source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
condition = match_or(checker, condition, cmp, arm.span)
|
|
}
|
|
}
|
|
|
|
arm_body, body_ok := build_match_arm_body(
|
|
ctx, arm, subject_type, subj_local, subj_is_pointer, ptr_type, subj_writable,
|
|
field_index, payload_type, refinement_local, refinement_variants[:], as_value, slot, slot_type, span,
|
|
)
|
|
delete(refinement_variants)
|
|
if !body_ok {
|
|
ok = false
|
|
}
|
|
append(&built, Match_Built_Arm{condition = condition, body = arm_body, terminal = is_else})
|
|
}
|
|
|
|
// 4. Exhaustiveness. Enum/union matches must cover every variant or supply `else`;
|
|
// an already-exhaustive match must not carry a redundant `else`. The last
|
|
// covered arm is promoted to the unconditional `else` so the chain terminates.
|
|
if is_tagged || is_enum_subject {
|
|
all_names := types.enum_members_for(store, tag_enum) if is_tagged else types.enum_members_for(store, subject_type)
|
|
names: [dynamic]symbol.Id
|
|
names.allocator = checker.allocator
|
|
defer delete(names)
|
|
if is_tagged {
|
|
for field in types.fields_for(store, subject_type) {
|
|
append(&names, symbol.Id(field.name))
|
|
}
|
|
} else {
|
|
for member in all_names {
|
|
append(&names, symbol.Id(member.name))
|
|
}
|
|
}
|
|
missing: [dynamic]symbol.Id
|
|
missing.allocator = checker.allocator
|
|
defer delete(missing)
|
|
for name in names {
|
|
if !contains_name(covered[:], name) {
|
|
append(&missing, name)
|
|
}
|
|
}
|
|
if has_else {
|
|
if len(missing) == 0 {
|
|
source.add(checker.diagnostics, span, "redundant 'else': the 'match' already covers every variant")
|
|
ok = false
|
|
}
|
|
} else if len(missing) > 0 {
|
|
builder: strings.Builder
|
|
strings.builder_init(&builder, checker.allocator)
|
|
defer strings.builder_destroy(&builder)
|
|
for name, index in missing {
|
|
if index > 0 {
|
|
strings.write_string(&builder, ", ")
|
|
}
|
|
strings.write_string(&builder, ".")
|
|
strings.write_string(&builder, symbol_text(checker, name))
|
|
}
|
|
source.addf(checker.diagnostics, span, "'match' on '%s' is not exhaustive; missing variants: %s (add the arms or an 'else')",
|
|
type_label(checker, subject_type), strings.to_string(builder))
|
|
ok = false
|
|
} else if len(built) > 0 {
|
|
built[len(built) - 1].terminal = true
|
|
}
|
|
} else if !has_else {
|
|
source.addf(checker.diagnostics, span, "a 'match' on '%s' requires an 'else' arm", type_label(checker, subject_type))
|
|
ok = false
|
|
}
|
|
|
|
if !ok {
|
|
// The arm bodies never get wired into the (un-assembled) chain, so free them here.
|
|
for arm in built {
|
|
delete(arm.body, checker.allocator)
|
|
}
|
|
return fail(ctx, out, span, source.INVALID_DIAGNOSTIC)
|
|
}
|
|
|
|
// 5. Assemble the if/else chain backward from the built arms. The terminal arm is the
|
|
// final (else / promoted) one; the rest nest as `if cond { body } else { … }`.
|
|
else_chain: []hir.Stmt_Id = nil
|
|
start := len(built)
|
|
if len(built) > 0 && built[len(built) - 1].terminal {
|
|
else_chain = built[len(built) - 1].body
|
|
start = len(built) - 1
|
|
}
|
|
for i := start - 1; i >= 0; i -= 1 {
|
|
arm := built[i]
|
|
wrapper := make([]hir.Stmt_Id, 1, checker.allocator)
|
|
wrapper[0] = hir.stmt_id(len(checker.module.statements))
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind = .If, span = span, expr = arm.condition, guard = hir.INVALID_EXPR,
|
|
then_body = arm.body, else_body = else_chain,
|
|
local = hir.INVALID_LOCAL, target = hir.INVALID_EXPR, diagnostic = source.INVALID_DIAGNOSTIC,
|
|
})
|
|
else_chain = wrapper
|
|
}
|
|
for s in else_chain {
|
|
append(out, s)
|
|
}
|
|
// The outermost chain slice's ids are now copied into `out`; the inner slices are
|
|
// owned by their enclosing `.If` (freed with the HIR module).
|
|
delete(else_chain, checker.allocator)
|
|
return true
|
|
}
|
|
|
|
// build_match_arm_body builds one arm's body, prefixed with the optional payload capture
|
|
// (`cap := subject.variant`, an unchecked reinterpret like a Zig union field read). For a
|
|
// statement match it is a plain block; for a value match each path assigns the result slot
|
|
// (a single-expression arm yields implicitly).
|
|
build_match_arm_body :: proc(
|
|
ctx: ^Build_Ctx,
|
|
arm: ast.Stmt,
|
|
subject_type: types.Type,
|
|
subj_local: hir.Local_Id,
|
|
subj_is_pointer: bool,
|
|
ptr_type: types.Type,
|
|
subj_writable: bool,
|
|
field_index: int,
|
|
payload_type: types.Type,
|
|
refinement_local: hir.Local_Id,
|
|
refinement_variants: []u32,
|
|
as_value: bool,
|
|
slot: ^hir.Local_Id,
|
|
slot_type: ^types.Type,
|
|
span: source.Span,
|
|
) -> ([]hir.Stmt_Id, bool) {
|
|
checker := ctx.checker
|
|
result: [dynamic]hir.Stmt_Id
|
|
result.allocator = checker.allocator
|
|
capture_start := len(ctx.locals^)
|
|
refinement_start := len(ctx.error_refinements^)
|
|
if refinement_local != hir.INVALID_LOCAL && len(refinement_variants) > 0 {
|
|
append(ctx.error_refinements, Error_Refinement{local=refinement_local, variants=refinement_variants})
|
|
}
|
|
capture_ok := true
|
|
|
|
if len(arm.captures) > 0 && field_index >= 0 {
|
|
capture := arm.captures[0]
|
|
if capture != checker.sink_symbol {
|
|
if id := add_shadow_diagnostic(
|
|
checker, span, capture, "capture",
|
|
ctx.pkg, ctx.file, ctx.locals^[:], ctx.loop_labels^[:], ctx.yield_targets^[:],
|
|
); id != source.INVALID_DIAGNOSTIC {
|
|
append(&result, hir.stmt_id(len(checker.module.statements)))
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind = .Trap, span = span, expr = hir.INVALID_EXPR,
|
|
local = hir.INVALID_LOCAL, diagnostic = id,
|
|
})
|
|
capture_ok = false
|
|
}
|
|
// The payload sits at the subject's shared carrier offset. A value capture
|
|
// loads it; a `|@cap|` capture binds a pointer to it (mutability follows the
|
|
// subject), aliasing the original storage via the subject location.
|
|
field_read := add_hir_expr(checker, hir.Expr{
|
|
kind = .Field, span = span, type = payload_type, integer = i64(field_index),
|
|
left = match_subject_location(checker, subj_local, subj_is_pointer, subject_type, ptr_type, span),
|
|
target = hir.INVALID_REF, right = hir.INVALID_EXPR, diagnostic = source.INVALID_DIAGNOSTIC,
|
|
})
|
|
cap_type := payload_type
|
|
cap_value := field_read
|
|
if arm.pointer_capture {
|
|
cap_type = types.pointer(&checker.module.types, payload_type, subj_writable, false)
|
|
cap_value = add_hir_expr(checker, hir.Expr{
|
|
kind = .Address, span = span, type = cap_type, left = field_read,
|
|
target = hir.INVALID_REF, right = hir.INVALID_EXPR, diagnostic = source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
cap_local := append_build_local(ctx, capture, cap_type, false, span)
|
|
append(&result, hir.stmt_id(len(checker.module.statements)))
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind = .Declaration, span = span, local = cap_local, expr = cap_value,
|
|
diagnostic = source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
}
|
|
|
|
body_ok := capture_ok
|
|
if !as_value {
|
|
built := build_block(ctx, arm.body)
|
|
for s in built {
|
|
append(&result, s)
|
|
}
|
|
delete(built, checker.allocator)
|
|
} else {
|
|
body_ok = build_value_arm(ctx, &result, arm.body, slot, slot_type, span) && body_ok
|
|
}
|
|
resize(ctx.locals, capture_start)
|
|
resize(ctx.error_refinements, refinement_start)
|
|
return result[:], body_ok
|
|
}
|
|
|
|
// build_value_arm appends a value-match arm's slot assignment(s) to `out`.
|
|
build_value_arm :: proc(
|
|
ctx: ^Build_Ctx,
|
|
out: ^[dynamic]hir.Stmt_Id,
|
|
arm_body: []ast.Stmt_Id,
|
|
slot: ^hir.Local_Id,
|
|
slot_type: ^types.Type,
|
|
span: source.Span,
|
|
) -> bool {
|
|
return emit_value_branch(ctx, out, arm_body, slot, slot_type, span)
|
|
}
|
|
|
|
// build_match desugars a statement-position `match` into its if/else chain.
|
|
build_match :: proc(ctx: ^Build_Ctx, body: ^[dynamic]hir.Stmt_Id, statement: ast.Stmt) {
|
|
slot := hir.INVALID_LOCAL
|
|
slot_type := types.INVALID
|
|
emit_match(ctx, body, statement, false, &slot, &slot_type)
|
|
}
|
|
|
|
// build_value_match desugars a `match` used as a declaration/assignment RHS: a result slot
|
|
// each arm assigns, read after the chain. Mirrors build_value_if.
|
|
build_value_match :: proc(
|
|
ctx: ^Build_Ctx,
|
|
body: ^[dynamic]hir.Stmt_Id,
|
|
match_id: ast.Stmt_Id,
|
|
expected: types.Type,
|
|
span: source.Span,
|
|
) -> (value: hir.Expr_Id, value_type: types.Type) {
|
|
checker := ctx.checker
|
|
statement := checker.ast_module.statements[match_id]
|
|
slot := hir.INVALID_LOCAL
|
|
slot_type := types.INVALID
|
|
if is_runtime_type(checker, expected) {
|
|
slot_type = expected
|
|
slot = new_value_slot(ctx, slot_type)
|
|
}
|
|
subtree: [dynamic]hir.Stmt_Id
|
|
subtree.allocator = checker.allocator
|
|
ok := false
|
|
if selected_body, capture, has_capture, comptime_ok := specialization_match_body(
|
|
checker, statement, ctx.pkg, ctx.file,
|
|
); comptime_ok {
|
|
if has_capture {
|
|
append(&checker.static_bindings, capture)
|
|
}
|
|
ok = build_value_arm(ctx, &subtree, selected_body, &slot, &slot_type, span)
|
|
if has_capture {
|
|
_ = pop(&checker.static_bindings)
|
|
}
|
|
} else {
|
|
ok = emit_match(ctx, &subtree, statement, true, &slot, &slot_type)
|
|
}
|
|
if !ok || slot == hir.INVALID_LOCAL {
|
|
for s in subtree {
|
|
append(body, s)
|
|
}
|
|
delete(subtree)
|
|
ctx.problematic^ = true
|
|
return invalid_hir_expr(checker, span, source.INVALID_DIAGNOSTIC), types.INVALID
|
|
}
|
|
append(body, hir.stmt_id(len(checker.module.statements)))
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind = .Declaration, span = span, local = slot, expr = hir.INVALID_EXPR,
|
|
diagnostic = source.INVALID_DIAGNOSTIC,
|
|
})
|
|
for s in subtree {
|
|
append(body, s)
|
|
}
|
|
delete(subtree)
|
|
return slot_read(checker, slot, slot_type, span), slot_type
|
|
}
|
|
|
|
// loop_yields_null reports whether any `yield` that targets this loop (a labeled
|
|
// `yield :blk` inside `if`/block branches, or the trailing fall-through) yields the
|
|
// literal `null` — making the loop's result optional. Pure AST walk; does not descend
|
|
// into nested loops or value sources, whose yields belong to them.
|
|
loop_yields_null :: proc(checker: ^Checker, stmts: []ast.Stmt_Id) -> bool {
|
|
for id in stmts {
|
|
s := checker.ast_module.statements[id]
|
|
#partial switch s.kind {
|
|
case .Yield:
|
|
if s.expr != ast.INVALID_EXPR && checker.ast_module.exprs[s.expr].kind == .Null {
|
|
return true
|
|
}
|
|
case .If:
|
|
if loop_yields_null(checker, s.body) || loop_yields_null(checker, s.else_body) {
|
|
return true
|
|
}
|
|
case .Block:
|
|
if loop_yields_null(checker, s.body) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// resolve_loop_slot fixes a value-loop's result slot from its first concrete yield (an
|
|
// optional element type when the loop also yields `null`) and coerces `value` into it.
|
|
// Returns INVALID when the type can't be fixed yet (a `null`/invalid first yield).
|
|
resolve_loop_slot :: proc(ctx: ^Build_Ctx, target: ^Yield_Target, value: hir.Expr_Id, vtype: types.Type, span: source.Span) -> hir.Expr_Id {
|
|
checker := ctx.checker
|
|
if target.slot == hir.INVALID_LOCAL {
|
|
if !is_runtime_type(checker, vtype) {
|
|
return hir.INVALID_EXPR
|
|
}
|
|
target.slot_type = types.optional(&checker.module.types, vtype) if target.result_optional else vtype
|
|
target.slot = new_value_slot(ctx, target.slot_type)
|
|
}
|
|
return coerce_expr(checker, value, target.slot_type, span)
|
|
}
|
|
|
|
// first_concrete_yield_expr returns the AST expr of the first yield (source order) that is
|
|
// not the literal `null`, descending into `if`/block branches but not nested loops or value
|
|
// sources (whose yields belong to them). INVALID when the loop yields only `null`.
|
|
first_concrete_yield_expr :: proc(checker: ^Checker, stmts: []ast.Stmt_Id) -> ast.Expr_Id {
|
|
for id in stmts {
|
|
s := checker.ast_module.statements[id]
|
|
#partial switch s.kind {
|
|
case .Yield:
|
|
if s.expr != ast.INVALID_EXPR && checker.ast_module.exprs[s.expr].kind != .Null {
|
|
return s.expr
|
|
}
|
|
case .If:
|
|
if e := first_concrete_yield_expr(checker, s.body); e != ast.INVALID_EXPR {
|
|
return e
|
|
}
|
|
if e := first_concrete_yield_expr(checker, s.else_body); e != ast.INVALID_EXPR {
|
|
return e
|
|
}
|
|
case .Block:
|
|
if e := first_concrete_yield_expr(checker, s.body); e != ast.INVALID_EXPR {
|
|
return e
|
|
}
|
|
}
|
|
}
|
|
return ast.INVALID_EXPR
|
|
}
|
|
|
|
// value_loop_element_type pre-types the element of an untyped value loop from its first
|
|
// concrete (non-`null`) yield, so a `null` yielded before any concrete value still resolves
|
|
// the result to `?T`. The loop's captures are bound temporarily for the probe and the probe
|
|
// expr is discarded; returns INVALID when the loop yields only `null`.
|
|
value_loop_element_type :: proc(ctx: ^Build_Ctx, loop_stmt: ast.Stmt) -> types.Type {
|
|
checker := ctx.checker
|
|
yield_expr := first_concrete_yield_expr(checker, loop_stmt.body)
|
|
if yield_expr == ast.INVALID_EXPR {
|
|
return types.INVALID
|
|
}
|
|
capture_start := len(ctx.locals^)
|
|
local_start := len(ctx.hir_locals^)
|
|
if loop_stmt.kind == .For {
|
|
// Mirror the `.For` arm's capture-type computation just enough to type the probe.
|
|
iterable := build_expr(checker, loop_stmt.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, types.INVALID, ctx.pkg, ctx.file)
|
|
iterable_type := checker.module.exprs[iterable].type
|
|
capture_type := types.INVALID
|
|
if types.is_range(iterable_type, &checker.module.types) {
|
|
capture_type = types.child_type(iterable_type, &checker.module.types)
|
|
} else if item, ok := sequence_item(iterable_type, &checker.module.types); ok {
|
|
capture_type = item.child
|
|
if loop_stmt.pointer_capture {
|
|
capture_type = types.pointer(&checker.module.types, item.child, item.mutable, false)
|
|
}
|
|
}
|
|
if symbol.is_valid(loop_stmt.name) {
|
|
append_build_local(ctx, loop_stmt.name, capture_type, false, loop_stmt.span)
|
|
}
|
|
if symbol.is_valid(loop_stmt.index_name) {
|
|
append_build_local(ctx, loop_stmt.index_name, types.USIZE, false, loop_stmt.span)
|
|
}
|
|
}
|
|
probe := build_expr(checker, yield_expr, ctx.locals^[:], ctx.global_reads, ctx.calls, types.INVALID, ctx.pkg, ctx.file)
|
|
result := checker.module.exprs[probe].type if checker.module.exprs[probe].kind != .Invalid else types.INVALID
|
|
resize(ctx.locals, capture_start)
|
|
ignore_tracked_locals(ctx, local_start)
|
|
return result
|
|
}
|
|
|
|
// stmt_contains_yield reports whether a statement contains a `yield` anywhere within it
|
|
// (recursing through if/block/loop bodies). Used to stop the leading probe build before any
|
|
// statement that yields (the block's yield target is not pushed during the probe).
|
|
stmt_contains_yield :: proc(checker: ^Checker, id: ast.Stmt_Id) -> bool {
|
|
s := checker.ast_module.statements[id]
|
|
#partial switch s.kind {
|
|
case .Yield:
|
|
return true
|
|
case .If, .For, .While, .Block:
|
|
for sub in s.body {
|
|
if stmt_contains_yield(checker, sub) {
|
|
return true
|
|
}
|
|
}
|
|
for sub in s.else_body {
|
|
if stmt_contains_yield(checker, sub) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// block_element_type pre-types an untyped value block's element from its first concrete
|
|
// `yield :blk`, building the block's leading (yield-free) statements first so the probe can
|
|
// reference block locals declared before the first yield. The leading build is a throwaway
|
|
// (its scope is restored). INVALID when there is no concrete yield, or the concrete yield
|
|
// references a local only in scope past the first yield (annotate the binding instead).
|
|
block_element_type :: proc(ctx: ^Build_Ctx, block_stmts: []ast.Stmt_Id) -> types.Type {
|
|
checker := ctx.checker
|
|
concrete := first_concrete_yield_expr(checker, block_stmts)
|
|
if concrete == ast.INVALID_EXPR {
|
|
return types.INVALID
|
|
}
|
|
lead_end := len(block_stmts)
|
|
for id, i in block_stmts {
|
|
if stmt_contains_yield(checker, id) {
|
|
lead_end = i
|
|
break
|
|
}
|
|
}
|
|
scope_start := len(ctx.locals^)
|
|
local_start := len(ctx.hir_locals^)
|
|
defer_start := len(ctx.defers^)
|
|
lead := build_block(ctx, block_stmts[:lead_end], close = false)
|
|
delete(lead, checker.allocator)
|
|
probe := build_expr(checker, concrete, ctx.locals^[:], ctx.global_reads, ctx.calls, types.INVALID, ctx.pkg, ctx.file)
|
|
result := checker.module.exprs[probe].type if checker.module.exprs[probe].kind != .Invalid else types.INVALID
|
|
// Discard the throwaway leading build's scope (its hir stmts/locals are dead but stable).
|
|
for i := defer_start; i < len(ctx.defers^); i += 1 {
|
|
delete(ctx.defers^[i].body, checker.allocator)
|
|
}
|
|
resize(ctx.defers, defer_start)
|
|
resize(ctx.locals, scope_start)
|
|
ignore_tracked_locals(ctx, local_start)
|
|
return result
|
|
}
|
|
|
|
// build_value_labeled_block turns `x :: blk: { …; yield :blk v }` into a result slot each
|
|
// `yield :blk` assigns (via the build_block `.Yield` desugar → `slot = v; break :blk`), then
|
|
// reads it after the block. Every path must yield (or otherwise exit); HIR holds a `.Block`
|
|
// that emits the body and the exit label the labeled breaks branch to. No iteration / no
|
|
// fall-through (unlike a value loop). The type is the annotation when typed, else the first
|
|
// concrete `yield :blk`'s type (optional when any yield is `null`).
|
|
build_value_labeled_block :: proc(
|
|
ctx: ^Build_Ctx,
|
|
body: ^[dynamic]hir.Stmt_Id,
|
|
block_stmts: []ast.Stmt_Id,
|
|
label: symbol.Id,
|
|
expected: types.Type,
|
|
span: source.Span,
|
|
) -> (value: hir.Expr_Id, value_type: types.Type) {
|
|
checker := ctx.checker
|
|
result_optional := loop_yields_null(checker, block_stmts)
|
|
slot := hir.INVALID_LOCAL
|
|
slot_type := types.INVALID
|
|
if is_runtime_type(checker, expected) {
|
|
slot_type = expected
|
|
slot = new_value_slot(ctx, slot_type)
|
|
result_optional = types.is_optional(slot_type, &checker.module.types)
|
|
} else if result_optional {
|
|
// Untyped block that also yields `null`: pre-type the element from the first
|
|
// concrete yield (regardless of source order) so a `null` yielded first still
|
|
// resolves the result to `?T`.
|
|
elem := block_element_type(ctx, block_stmts)
|
|
if is_runtime_type(checker, elem) {
|
|
slot_type = types.optional(&checker.module.types, elem)
|
|
slot = new_value_slot(ctx, slot_type)
|
|
}
|
|
}
|
|
if id := add_label_shadow_diagnostic(ctx, span, label); id != source.INVALID_DIAGNOSTIC {
|
|
ctx.problematic^ = true
|
|
return invalid_hir_expr(checker, span, id), types.INVALID
|
|
}
|
|
append(ctx.yield_targets, Yield_Target{
|
|
label = label, slot = slot, slot_type = slot_type, result_optional = result_optional,
|
|
defer_floor = len(ctx.defers^),
|
|
})
|
|
built := build_block(ctx, block_stmts)
|
|
target := pop(ctx.yield_targets)
|
|
|
|
if target.slot == hir.INVALID_LOCAL {
|
|
for s in built {
|
|
append(body, s)
|
|
}
|
|
delete(built, checker.allocator)
|
|
id := source.add(checker.diagnostics, span,
|
|
"could not determine the value block's yield type; annotate the binding")
|
|
ctx.problematic^ = true
|
|
return invalid_hir_expr(checker, span, id), types.INVALID
|
|
}
|
|
// Every path must `yield :blk` (or return/break out); otherwise a path falls through
|
|
// to the slot read with a poison value.
|
|
if !all_paths_exit(&checker.module, built) {
|
|
for s in built {
|
|
append(body, s)
|
|
}
|
|
delete(built, checker.allocator)
|
|
id := source.add(checker.diagnostics, span, "a labeled value block must 'yield' on every path")
|
|
ctx.problematic^ = true
|
|
return invalid_hir_expr(checker, span, id), types.INVALID
|
|
}
|
|
|
|
append(body, hir.stmt_id(len(checker.module.statements)))
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind = .Declaration, span = span, local = target.slot, expr = hir.INVALID_EXPR,
|
|
diagnostic = source.INVALID_DIAGNOSTIC,
|
|
})
|
|
append(body, hir.stmt_id(len(checker.module.statements)))
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind = .Block, span = span, label = label, then_body = built,
|
|
local = hir.INVALID_LOCAL, target = hir.INVALID_EXPR, diagnostic = source.INVALID_DIAGNOSTIC,
|
|
})
|
|
value = slot_read(checker, target.slot, target.slot_type, span)
|
|
return value, target.slot_type
|
|
}
|
|
|
|
// build_value_loop turns a labeled `for/while ... blk: { … }` whose body ends in a
|
|
// fall-through `yield` (and may early-exit via `yield :blk x`) into a result slot:
|
|
// the fall-through value initializes the slot before the loop, each `yield :blk x`
|
|
// desugars (in build_block) to `slot = x; break`, and the construct's value is a read
|
|
// of the slot after the loop. Reuses the ordinary `.For`/`.While` build via a peeled
|
|
// copy; no new HIR. The yielded type is the annotation when typed, else the first
|
|
// concrete yield's type (optional when any yield is `null`).
|
|
build_value_loop :: proc(
|
|
ctx: ^Build_Ctx,
|
|
body: ^[dynamic]hir.Stmt_Id,
|
|
loop_id: ast.Stmt_Id,
|
|
expected: types.Type,
|
|
span: source.Span,
|
|
) -> (value: hir.Expr_Id, value_type: types.Type) {
|
|
checker := ctx.checker
|
|
loop_stmt := checker.ast_module.statements[loop_id]
|
|
n := len(loop_stmt.body)
|
|
last_is_fallthrough := n > 0 &&
|
|
checker.ast_module.statements[loop_stmt.body[n - 1]].kind == .Yield &&
|
|
!symbol.is_valid(checker.ast_module.statements[loop_stmt.body[n - 1]].label)
|
|
if !symbol.is_valid(loop_stmt.label) {
|
|
id := source.add(checker.diagnostics, span,
|
|
"a value loop must label its body (e.g. 'blk:') so a 'yield :blk' can exit it")
|
|
ctx.problematic^ = true
|
|
return invalid_hir_expr(checker, span, id), types.INVALID
|
|
}
|
|
if !last_is_fallthrough {
|
|
id := source.add(checker.diagnostics, span,
|
|
"a value loop's body must end with a 'yield' for when the loop completes")
|
|
ctx.problematic^ = true
|
|
return invalid_hir_expr(checker, span, id), types.INVALID
|
|
}
|
|
fall_stmt := checker.ast_module.statements[loop_stmt.body[n - 1]]
|
|
|
|
result_optional := loop_yields_null(checker, loop_stmt.body)
|
|
slot := hir.INVALID_LOCAL
|
|
slot_type := types.INVALID
|
|
if is_runtime_type(checker, expected) {
|
|
slot_type = expected
|
|
slot = new_value_slot(ctx, slot_type)
|
|
result_optional = types.is_optional(slot_type, &checker.module.types)
|
|
} else if result_optional {
|
|
// Untyped loop that also yields `null`: pre-type the element from the first
|
|
// concrete yield (regardless of source order) so a `null` built before any
|
|
// concrete yield still resolves the result to `?T`.
|
|
elem := value_loop_element_type(ctx, loop_stmt)
|
|
if is_runtime_type(checker, elem) {
|
|
slot_type = types.optional(&checker.module.types, elem)
|
|
slot = new_value_slot(ctx, slot_type)
|
|
}
|
|
}
|
|
append(ctx.yield_targets, Yield_Target{
|
|
label = loop_stmt.label, slot = slot, slot_type = slot_type, result_optional = result_optional,
|
|
defer_floor = len(ctx.defers^),
|
|
})
|
|
|
|
// Build the loop with the fall-through peeled off, reusing the normal For/While arm.
|
|
// The peeled body is a fresh copy so destroy_module won't double-free the original.
|
|
peeled := loop_stmt
|
|
peeled_body := make([]ast.Stmt_Id, n - 1, checker.ast_module.allocator)
|
|
copy(peeled_body, loop_stmt.body[:n - 1])
|
|
peeled.body = peeled_body
|
|
peeled_id := ast.stmt_id(len(checker.ast_module.statements))
|
|
append(&checker.ast_module.statements, peeled)
|
|
loop_block := build_block(ctx, []ast.Stmt_Id{peeled_id})
|
|
|
|
target := pop(ctx.yield_targets)
|
|
|
|
// The fall-through value initializes the slot before the loop (loop captures are
|
|
// out of scope here), so the loop completing leaves it as the result.
|
|
fall_value := build_expr(checker, fall_stmt.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, target.slot_type, ctx.pkg, ctx.file)
|
|
if fall_value != hir.INVALID_EXPR && types.is_void(checker.module.exprs[fall_value].type) {
|
|
for s in loop_block {
|
|
append(body, s)
|
|
}
|
|
delete(loop_block, checker.allocator)
|
|
id := source.add(checker.diagnostics, fall_stmt.span, "'yield' expression must produce a non-void value")
|
|
ctx.problematic^ = true
|
|
return invalid_hir_expr(checker, fall_stmt.span, id), types.INVALID
|
|
}
|
|
fall_value = resolve_loop_slot(ctx, &target, fall_value, checker.module.exprs[fall_value].type if fall_value != hir.INVALID_EXPR else types.INVALID, fall_stmt.span)
|
|
if target.slot == hir.INVALID_LOCAL || fall_value == hir.INVALID_EXPR {
|
|
for s in loop_block {
|
|
append(body, s)
|
|
}
|
|
delete(loop_block, checker.allocator)
|
|
id := source.add(checker.diagnostics, span,
|
|
"could not determine the value loop's yield type; annotate the binding")
|
|
ctx.problematic^ = true
|
|
return invalid_hir_expr(checker, span, id), types.INVALID
|
|
}
|
|
|
|
append(body, hir.stmt_id(len(checker.module.statements)))
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind = .Declaration, span = span, local = target.slot, expr = fall_value,
|
|
diagnostic = source.INVALID_DIAGNOSTIC,
|
|
})
|
|
for s in loop_block {
|
|
append(body, s)
|
|
}
|
|
delete(loop_block, checker.allocator)
|
|
|
|
value = slot_read(checker, target.slot, target.slot_type, span)
|
|
return value, target.slot_type
|
|
}
|
|
|
|
Return_Enum_Guard :: struct {
|
|
local: hir.Local_Id,
|
|
type: types.Type,
|
|
value: i64,
|
|
}
|
|
|
|
return_enum_guard :: proc(
|
|
module: ^hir.Module,
|
|
statement: hir.Stmt,
|
|
locals: []hir.Local,
|
|
) -> (Return_Enum_Guard, bool) {
|
|
if statement.kind != .If || statement.else_body != nil ||
|
|
statement.expr == hir.INVALID_EXPR || int(statement.expr) >= len(module.exprs) {
|
|
return {}, false
|
|
}
|
|
condition := module.exprs[statement.expr]
|
|
if condition.kind != .Eq || condition.left == hir.INVALID_EXPR || condition.right == hir.INVALID_EXPR ||
|
|
int(condition.left) >= len(module.exprs) || int(condition.right) >= len(module.exprs) {
|
|
return {}, false
|
|
}
|
|
left := module.exprs[condition.left]
|
|
right := module.exprs[condition.right]
|
|
if left.kind == .Integer {
|
|
left, right = right, left
|
|
}
|
|
if left.kind != .Local || right.kind != .Integer ||
|
|
!types.equal(left.type, right.type) || !types.is_enum(left.type, &module.types) {
|
|
return {}, false
|
|
}
|
|
local := hir.as_local(left.target)
|
|
if local == hir.INVALID_LOCAL || int(local) >= len(locals) || locals[local].mutable {
|
|
return {}, false
|
|
}
|
|
if !all_paths_return(module, statement.then_body, locals) {
|
|
return {}, false
|
|
}
|
|
return Return_Enum_Guard{local=local, type=left.type, value=right.integer}, true
|
|
}
|
|
|
|
enum_guards_return :: proc(module: ^hir.Module, stmts: []hir.Stmt_Id, locals: []hir.Local) -> bool {
|
|
guards: [dynamic]Return_Enum_Guard
|
|
guards.allocator = module.allocator
|
|
defer delete(guards)
|
|
for id in stmts {
|
|
if guard, ok := return_enum_guard(module, module.statements[id], locals); ok {
|
|
append(&guards, guard)
|
|
}
|
|
}
|
|
for guard, index in guards {
|
|
seen := false
|
|
for previous in guards[:index] {
|
|
seen = seen || previous.local == guard.local
|
|
}
|
|
if seen {
|
|
continue
|
|
}
|
|
members := types.enum_members_for(&module.types, guard.type)
|
|
if len(members) == 0 {
|
|
continue
|
|
}
|
|
complete := true
|
|
for member in members {
|
|
value := i64(member.value) if member.value < 0 else transmute(i64)u64(member.value)
|
|
covered := false
|
|
for candidate in guards {
|
|
covered = covered || candidate.local == guard.local && candidate.value == value
|
|
}
|
|
complete = complete && covered
|
|
}
|
|
if complete {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// 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.
|
|
// Exhaustive equality guards over one immutable enum local also terminate collectively.
|
|
all_paths_return :: proc(module: ^hir.Module, stmts: []hir.Stmt_Id, locals: []hir.Local = nil) -> bool {
|
|
for id in stmts {
|
|
statement := module.statements[id]
|
|
if statement.expr != hir.INVALID_EXPR && int(statement.expr) < len(module.exprs) &&
|
|
types.is_noreturn(module.exprs[statement.expr].type) {
|
|
return true
|
|
}
|
|
#partial switch statement.kind {
|
|
case .Return, .Trap:
|
|
return true
|
|
case .If:
|
|
if statement.else_body != nil &&
|
|
all_paths_return(module, statement.then_body, locals) &&
|
|
all_paths_return(module, statement.else_body, locals) {
|
|
return true
|
|
}
|
|
case .While:
|
|
// A literal `while true` makes the end of the block unreachable —
|
|
// unless its body can `break` out of this loop.
|
|
if statement.expr != hir.INVALID_EXPR && int(statement.expr) < len(module.exprs) {
|
|
condition := module.exprs[statement.expr]
|
|
if condition.kind == .Bool && condition.integer != 0 &&
|
|
!loop_body_breaks(module, statement.then_body) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return enum_guards_return(module, stmts, locals)
|
|
}
|
|
|
|
// Reports whether `stmts` contains a `break` that targets the enclosing loop:
|
|
// a `.Break` at this level or inside `if`/`else` branches counts, but a `break`
|
|
// inside a nested `.While`/`.For` targets that inner loop, so we do not descend.
|
|
loop_body_breaks :: proc(module: ^hir.Module, stmts: []hir.Stmt_Id) -> bool {
|
|
for id in stmts {
|
|
statement := module.statements[id]
|
|
#partial switch statement.kind {
|
|
case .Break:
|
|
return true
|
|
case .If:
|
|
if loop_body_breaks(module, statement.then_body) ||
|
|
loop_body_breaks(module, statement.else_body) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Like `all_paths_return`, but also treats `break`/`continue` as terminating the
|
|
// block. Used only to decide whether `build_block` may skip the fall-through defer
|
|
// flush (a block that always exits early would otherwise emit unreachable copies).
|
|
all_paths_exit :: proc(module: ^hir.Module, stmts: []hir.Stmt_Id) -> bool {
|
|
for id in stmts {
|
|
statement := module.statements[id]
|
|
if statement.expr != hir.INVALID_EXPR && int(statement.expr) < len(module.exprs) &&
|
|
types.is_noreturn(module.exprs[statement.expr].type) {
|
|
return true
|
|
}
|
|
#partial switch statement.kind {
|
|
case .Return, .Trap, .Break, .Continue:
|
|
return true
|
|
case .If:
|
|
if statement.else_body != nil &&
|
|
all_paths_exit(module, statement.then_body) &&
|
|
all_paths_exit(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 &&
|
|
!loop_body_breaks(module, statement.then_body) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
build_function :: proc(checker: ^Checker, id: Spec_Id) {
|
|
spec := checker.specs[id]
|
|
function := checker.ast_module.functions[spec.template]
|
|
previous_comptime := checker.current_comptime_values
|
|
checker.current_comptime_values = spec.comptime_values
|
|
defer checker.current_comptime_values = previous_comptime
|
|
signature_diagnostic := source.INVALID_DIAGNOSTIC
|
|
unresolved_result := !types.is_void(spec.result) && !types.is_noreturn(spec.result) && !is_runtime_type(checker, spec.result)
|
|
if unresolved_result {
|
|
if types.is_comptime_only(spec.result, &checker.module.types) {
|
|
signature_diagnostic = source.addf(
|
|
checker.diagnostics,
|
|
function.span,
|
|
"function '%s' has a comptime-only result and cannot return it through the runtime ABI",
|
|
symbol_text(checker, function.name),
|
|
)
|
|
}
|
|
checker.specs[id].result = types.I64
|
|
spec.result = types.I64
|
|
}
|
|
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
|
|
local_spans: [dynamic]source.Span
|
|
local_spans.allocator = checker.allocator
|
|
local_used: [dynamic]bool
|
|
local_used.allocator = checker.allocator
|
|
local_warnable: [dynamic]bool
|
|
local_warnable.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
|
|
demanded: [dynamic]Spec_Id
|
|
demanded.allocator = checker.allocator
|
|
local_types, _ := infer_spec_locals_and_result(checker, id, &demanded)
|
|
defer {
|
|
delete(local_types, checker.allocator)
|
|
delete(demanded)
|
|
}
|
|
|
|
runtime_index := 0
|
|
for param in function.params {
|
|
if param.comptime_value {
|
|
continue
|
|
}
|
|
param_type := types.INVALID
|
|
if runtime_index < len(spec.args) {
|
|
param_type = spec.args[runtime_index]
|
|
}
|
|
local_id := append_tracked_local(
|
|
&hir_locals,
|
|
&local_spans,
|
|
&local_used,
|
|
&local_warnable,
|
|
hir.Local{name = param.name, type = param_type, parameter = true},
|
|
param.span,
|
|
)
|
|
if param.name != checker.sink_symbol && block_reads_name(checker, function.body, param.name) {
|
|
local_used[int(local_id)] = true
|
|
}
|
|
append(&locals, Build_Local{name = param.name, type = param_type, id = local_id})
|
|
append(¶ms, local_id)
|
|
runtime_index += 1
|
|
}
|
|
|
|
problematic := signature_diagnostic != source.INVALID_DIAGNOSTIC ||
|
|
checker.template_diagnostics[spec.template] != source.INVALID_DIAGNOSTIC
|
|
native_main := function.pkg == 0 && function.name == checker.main_symbol && checker.entry_point == .Plain
|
|
if !function.has_body {
|
|
if unresolved_result && signature_diagnostic == source.INVALID_DIAGNOSTIC &&
|
|
checker.template_diagnostics[spec.template] == source.INVALID_DIAGNOSTIC {
|
|
signature_diagnostic = source.addf(
|
|
checker.diagnostics,
|
|
function.span,
|
|
"could not resolve a concrete result type for '%s'",
|
|
symbol_text(checker, function.name),
|
|
)
|
|
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 else .Brolang,
|
|
implementation = .Declaration,
|
|
linkage = .External if function.c_abi else .Internal,
|
|
is_main = native_main,
|
|
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)
|
|
delete(local_spans)
|
|
delete(local_used)
|
|
delete(local_warnable)
|
|
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,
|
|
},
|
|
)
|
|
}
|
|
defers: [dynamic]Defer_Entry
|
|
defers.allocator = checker.allocator
|
|
loop_defer_starts: [dynamic]int
|
|
loop_defer_starts.allocator = checker.allocator
|
|
loop_labels: [dynamic]symbol.Id
|
|
loop_labels.allocator = checker.allocator
|
|
loop_is_loop: [dynamic]bool
|
|
loop_is_loop.allocator = checker.allocator
|
|
yield_targets: [dynamic]Yield_Target
|
|
yield_targets.allocator = checker.allocator
|
|
error_refinements: [dynamic]Error_Refinement
|
|
error_refinements.allocator = checker.allocator
|
|
ctx := Build_Ctx{
|
|
checker = checker,
|
|
pkg = function.pkg,
|
|
file = function.file,
|
|
result = spec.result,
|
|
local_types = local_types,
|
|
locals = &locals,
|
|
hir_locals = &hir_locals,
|
|
local_spans = &local_spans,
|
|
local_used = &local_used,
|
|
local_warnable = &local_warnable,
|
|
global_reads = &global_reads,
|
|
calls = &calls,
|
|
problematic = &problematic,
|
|
error_refinements = &error_refinements,
|
|
defers = &defers,
|
|
loop_defer_starts = &loop_defer_starts,
|
|
loop_labels = &loop_labels,
|
|
loop_is_loop = &loop_is_loop,
|
|
yield_targets = &yield_targets,
|
|
}
|
|
previous_result := checker.current_result
|
|
previous_ctx := checker.current_build_ctx
|
|
checker.current_result = spec.result
|
|
checker.current_build_ctx = &ctx
|
|
block := build_block(&ctx, function.body)
|
|
checker.current_result = previous_result
|
|
checker.current_build_ctx = previous_ctx
|
|
returns := all_paths_return(&checker.module, block, hir_locals[:])
|
|
for block_stmt in block {
|
|
append(&body, block_stmt)
|
|
}
|
|
delete(block, checker.allocator)
|
|
if unresolved_result && signature_diagnostic == source.INVALID_DIAGNOSTIC &&
|
|
checker.template_diagnostics[spec.template] == source.INVALID_DIAGNOSTIC && !problematic {
|
|
signature_diagnostic = source.addf(
|
|
checker.diagnostics,
|
|
function.span,
|
|
"could not resolve a concrete result type for '%s'",
|
|
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=signature_diagnostic,
|
|
})
|
|
problematic = true
|
|
returns = true
|
|
}
|
|
|
|
fallible_void := types.kind(spec.result, &checker.module.types) == .Fallible &&
|
|
types.is_void(types.fallible_success(spec.result, &checker.module.types))
|
|
if !returns && fallible_void {
|
|
value := fallible_aggregate(checker, function.span, spec.result, hir.INVALID_EXPR, false)
|
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind=.Return, span=function.span, expr=value, local=hir.INVALID_LOCAL,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
} else 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
|
|
}
|
|
|
|
record_unused_locals(checker, hir_locals[:], local_spans[:], local_used[:], local_warnable[:])
|
|
|
|
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 || native_main else .Brolang,
|
|
implementation = .Definition,
|
|
linkage = .External if function.c_abi || native_main else .Internal,
|
|
is_main = native_main,
|
|
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,
|
|
},
|
|
)
|
|
for entry in defers {
|
|
delete(entry.body, checker.allocator)
|
|
}
|
|
delete(defers)
|
|
delete(loop_defer_starts)
|
|
delete(loop_labels)
|
|
delete(loop_is_loop)
|
|
delete(yield_targets)
|
|
delete(error_refinements)
|
|
delete(locals)
|
|
delete(local_spans)
|
|
delete(local_used)
|
|
delete(local_warnable)
|
|
}
|
|
|
|
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.diagnostic != source.INVALID_DIAGNOSTIC {
|
|
global_type := checker.global_types[global_index]
|
|
if !is_runtime_type(checker, global_type) {
|
|
global_type = types.I64
|
|
}
|
|
expr := hir.INVALID_EXPR
|
|
if !global.external {
|
|
expr = invalid_hir_expr(checker, global.span, global.diagnostic, global_type)
|
|
}
|
|
append(&checker.module.globals, hir.Global{
|
|
name=global.name,
|
|
link_name=strings.clone(global.link_name, checker.allocator),
|
|
type=global_type,
|
|
expr=expr,
|
|
external=global.external,
|
|
writable=global.writable || !global.immutable,
|
|
direct_problem=true,
|
|
problematic=true,
|
|
diagnostic=global.diagnostic,
|
|
})
|
|
continue
|
|
}
|
|
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(checker, global.type, global.pkg, global.file), global.expr)
|
|
if has_inferred_array_count(checker, declared) &&
|
|
is_runtime_type(checker, checker.global_types[global_index]) {
|
|
declared = checker.global_types[global_index]
|
|
}
|
|
expected := types.INVALID
|
|
if is_runtime_type(checker, declared) {
|
|
expected = declared
|
|
} else if constant := eval_integer_constant_in_context(checker, global.expr, global.pkg, global.file);
|
|
constant.kind == .Value &&
|
|
(fits_i64(constant.value) || declared == types.UINT && fits_u64(constant.value)) &&
|
|
is_runtime_type(checker, checker.global_types[global_index]) {
|
|
// Open integer constant: build against its demanded/defaulted type. Gated
|
|
// to the infer-side open-constant condition so out-of-range constants keep
|
|
// their original range diagnostic.
|
|
expected = checker.global_types[global_index]
|
|
} else if (is_float_constant_expr(checker, global.expr) ||
|
|
is_numeric_arithmetic_expr(checker, global.expr)) &&
|
|
is_runtime_type(checker, checker.global_types[global_index]) {
|
|
expected = checker.global_types[global_index]
|
|
}
|
|
expr := hir.INVALID_EXPR
|
|
if !global.immutable && is_undefined_expr(checker, global.expr) {
|
|
expr = add_hir_expr(checker, hir.Expr{
|
|
kind=.Undefined,
|
|
span=checker.ast_module.exprs[global.expr].span,
|
|
type=checker.global_types[global_index],
|
|
target=hir.INVALID_REF,
|
|
left=hir.INVALID_EXPR,
|
|
right=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
} else {
|
|
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, checker.global_types[global_index]) {
|
|
expr = coerce_expr(checker, expr, checker.global_types[global_index], 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 root, invalid := invalid_expr_diagnostic(checker, expr); invalid {
|
|
diagnostic = root
|
|
if !is_runtime_type(checker, global_type) {
|
|
global_type = types.I64
|
|
}
|
|
}
|
|
if diagnostic == source.INVALID_DIAGNOSTIC && types.is_constraint(declared) &&
|
|
!types.constraint_accepts(declared, global_type, &checker.module.types) {
|
|
diagnostic = source.addf(
|
|
checker.diagnostics,
|
|
global.span,
|
|
"could not resolve the '%s' constraint for global '%s'",
|
|
types.name(declared),
|
|
symbol_text(checker, global.name),
|
|
)
|
|
global_type = constraint_recovery_type(checker, declared)
|
|
expr = invalid_hir_expr(checker, global.span, diagnostic, global_type)
|
|
}
|
|
if diagnostic == source.INVALID_DIAGNOSTIC && !is_runtime_type(checker, global_type) {
|
|
if types.is_comptime_only(global_type, &checker.module.types) ||
|
|
types.is_comptime_only(declared, &checker.module.types) {
|
|
diagnostic = source.addf(
|
|
checker.diagnostics,
|
|
global.span,
|
|
"global '%s' has a comptime-only type and cannot be stored at runtime",
|
|
symbol_text(checker, global.name),
|
|
)
|
|
} else {
|
|
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 {
|
|
if !types.is_valid(global.type) {
|
|
diagnostic = source.addf(
|
|
checker.diagnostics,
|
|
global.span,
|
|
"mutable global '%s' requires a type annotation",
|
|
symbol_text(checker, global.name),
|
|
)
|
|
global_type = types.I64
|
|
expr = invalid_hir_expr(checker, global.span, diagnostic, global_type)
|
|
}
|
|
}
|
|
static_value, is_static := static_integer_value(&checker.module, expr)
|
|
is_static = is_static && diagnostic == source.INVALID_DIAGNOSTIC && global.immutable
|
|
_ = 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 = !global.immutable,
|
|
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,
|
|
}
|
|
|
|
cycle_global_location :: proc(checker: ^Checker, global_id: hir.Global_Id) -> (name, path: string, line: int) {
|
|
name = "<unknown>"
|
|
path = "<unknown>"
|
|
line = 1
|
|
index := int(global_id)
|
|
if global_id == hir.INVALID_GLOBAL || index >= len(checker.module.globals) {
|
|
return
|
|
}
|
|
name = symbol_text(checker, checker.module.globals[global_id].name)
|
|
if index >= len(checker.ast_module.globals) {
|
|
return
|
|
}
|
|
span := checker.ast_module.globals[global_id].span
|
|
source_file := source.source_for_span(checker.diagnostics, span)
|
|
if source_file == nil {
|
|
return
|
|
}
|
|
path = source_file.path
|
|
line, _ = source.line_and_column(source_file, span.start)
|
|
return
|
|
}
|
|
|
|
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 {
|
|
start_name, start_path, start_line := cycle_global_location(checker, dependency)
|
|
end_name, end_path, end_line := cycle_global_location(checker, frame.global)
|
|
id := source.addf(
|
|
checker.diagnostics,
|
|
checker.ast_module.globals[dependency].span,
|
|
"global initialization cycle from '%s' at %s:%d to '%s' at %s:%d",
|
|
start_name,
|
|
start_path,
|
|
start_line,
|
|
end_name,
|
|
end_path,
|
|
end_line,
|
|
)
|
|
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"),
|
|
io_provider_template = ast.INVALID_FUNCTION,
|
|
sink_symbol = symbol.intern(symbols, "_"),
|
|
type_symbol = symbol.intern(symbols, "type"),
|
|
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.hir_expr_stack.allocator = allocator
|
|
checker.infer_stack.allocator = allocator
|
|
checker.build_stack.allocator = allocator
|
|
checker.cycle_stack.allocator = allocator
|
|
checker.anon_globals.allocator = allocator
|
|
checker.type_factories.allocator = allocator
|
|
checker.generated_types.allocator = allocator
|
|
checker.type_factory_origins.allocator = allocator
|
|
checker.call_resolutions.allocator = allocator
|
|
checker.static_bindings.allocator = allocator
|
|
checker.comptime_keys.allocator = allocator
|
|
checker.comptime_static_values.allocator = allocator
|
|
checker.expand_context.allocator = allocator
|
|
checker.static_state = ct_state_make(&checker, 0, ast.INVALID_FILE)
|
|
build_symbol_indexes(&checker)
|
|
checker.global_types = make([]types.Type, len(ast_module.globals), allocator)
|
|
checker.global_demands = make([]types.Type, len(ast_module.globals), allocator)
|
|
checker.global_open_const = make([]bool, len(ast_module.globals), allocator)
|
|
checker.global_open_float = make([]bool, len(ast_module.globals), allocator)
|
|
checker.global_const_value = make([]i128, len(ast_module.globals), allocator)
|
|
checker.record_field_constraints = make([]types.Type, len(checker.module.types.fields), allocator)
|
|
checker.record_field_defaults = make([]types.Type, len(checker.module.types.fields), allocator)
|
|
checker.record_field_conflicts = make([]types.Type, len(checker.module.types.fields), allocator)
|
|
checker.record_field_conflict_spans = make([]source.Span, len(checker.module.types.fields), allocator)
|
|
checker.poisoned_packages = make([]bool, max(len(ast_module.packages), 1), allocator)
|
|
init_record_field_inference(&checker)
|
|
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, index in checker.template_diagnostics {
|
|
diagnostic = ast_module.functions[index].diagnostic
|
|
pkg := ast_module.functions[index].pkg
|
|
if diagnostic != source.INVALID_DIAGNOSTIC && int(pkg) < len(checker.poisoned_packages) {
|
|
checker.poisoned_packages[pkg] = true
|
|
}
|
|
}
|
|
for global in ast_module.globals {
|
|
if global.diagnostic != source.INVALID_DIAGNOSTIC && int(global.pkg) < len(checker.poisoned_packages) {
|
|
checker.poisoned_packages[global.pkg] = true
|
|
}
|
|
}
|
|
defer {
|
|
for spec in checker.specs {
|
|
delete(spec.args, allocator)
|
|
delete(spec.comptime_values, 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.global_demands, allocator)
|
|
delete(checker.global_open_const, allocator)
|
|
delete(checker.global_open_float, allocator)
|
|
delete(checker.global_const_value, allocator)
|
|
delete(checker.record_field_constraints, allocator)
|
|
delete(checker.record_field_defaults, allocator)
|
|
delete(checker.record_field_conflicts, allocator)
|
|
delete(checker.record_field_conflict_spans, allocator)
|
|
delete(checker.poisoned_packages, 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.hir_expr_stack)
|
|
delete(checker.infer_stack)
|
|
delete(checker.build_stack)
|
|
delete(checker.cycle_stack)
|
|
for entry in checker.type_factories {
|
|
delete(entry.values, allocator)
|
|
}
|
|
for entry in checker.generated_types {
|
|
delete(entry.values, allocator)
|
|
delete(entry.defaults, allocator)
|
|
}
|
|
for origin in checker.type_factory_origins {
|
|
delete(origin.values, allocator)
|
|
}
|
|
for resolution in checker.call_resolutions {
|
|
delete(resolution.ctx, allocator)
|
|
delete(resolution.expand_ctx, allocator)
|
|
delete(resolution.mapping, allocator)
|
|
delete(resolution.comptime_values, allocator)
|
|
delete(resolution.runtime_types, allocator)
|
|
}
|
|
delete(checker.type_factories)
|
|
delete(checker.generated_types)
|
|
delete(checker.type_factory_origins)
|
|
delete(checker.call_resolutions)
|
|
ct_state_destroy(&checker.static_state)
|
|
delete(checker.static_bindings)
|
|
for key in checker.comptime_keys {
|
|
delete(key, allocator)
|
|
}
|
|
delete(checker.comptime_keys)
|
|
delete(checker.comptime_static_values)
|
|
delete(checker.expand_context)
|
|
delete(checker.anon_globals)
|
|
}
|
|
|
|
for function, index in ast_module.functions {
|
|
if function.generated {
|
|
continue
|
|
}
|
|
for previous in ast_module.functions[:index] {
|
|
if !previous.generated && 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))
|
|
}
|
|
}
|
|
if type_declaration_conflicts(&checker, function.pkg, function.name) {
|
|
source.addf(diagnostics, function.span, "function '%s' shadows visible type", 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))
|
|
}
|
|
}
|
|
if type_declaration_conflicts(&checker, global.pkg, global.name) {
|
|
source.addf(diagnostics, global.span, "global '%s' shadows visible type", symbol_text(&checker, global.name))
|
|
}
|
|
}
|
|
|
|
validate_type_nodes(&checker)
|
|
validate_declarations(&checker)
|
|
configure_entry_point(&checker)
|
|
infer_all(&checker)
|
|
finalize_record_field_inference(&checker)
|
|
validate_external_globals(&checker)
|
|
prune_specs(&checker)
|
|
checker.building_hir = true
|
|
build_globals(&checker)
|
|
for index := 0; index < len(checker.specs); index += 1 {
|
|
build_function(&checker, spec_id(index))
|
|
}
|
|
// Flush anonymous globals synthesized for `&<array literal>`. Appended only now
|
|
// (after every ast global was built at its identity-mapped index) so their ids,
|
|
// pre-assigned as len(ast.globals)+stage_index, land exactly.
|
|
for anon in checker.anon_globals {
|
|
append(&checker.module.globals, anon)
|
|
}
|
|
propagate_global_reads(&checker)
|
|
|
|
main_template := find_template(&checker, checker.main_symbol, 0)
|
|
main_declarations := 0
|
|
for function in ast_module.functions {
|
|
if !function.generated && 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]
|
|
valid_params := checker.entry_point == .Plain && len(template.params) == 0 ||
|
|
checker.entry_point == .Process && len(template.params) == 1
|
|
if main_declarations != 1 ||
|
|
!template.has_body ||
|
|
!valid_params ||
|
|
!(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 or one @std/process Init, and return void, i32, or int",
|
|
)
|
|
}
|
|
checker.module.injected_main = hir.INVALID_FUNCTION
|
|
checker.module.io_provider = hir.INVALID_FUNCTION
|
|
replace_main_with_trap(&checker, id)
|
|
} else if checker.entry_point == .Process {
|
|
main_spec := find_spec(&checker, main_template, nil)
|
|
provider_spec := find_spec(&checker, checker.io_provider_template, nil)
|
|
if main_spec != INVALID_SPEC && provider_spec != INVALID_SPEC {
|
|
checker.module.injected_main = checker.specs[main_spec].hir_id
|
|
checker.module.io_provider = checker.specs[provider_spec].hir_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.test_only && import_item.valid && !import_item.used {
|
|
source.addf_warning(diagnostics, import_item.span, "unused import '%s'", symbol_text(&checker, import_item.alias))
|
|
}
|
|
}
|
|
return checker.module
|
|
}
|