9687 lines
354 KiB
Odin
9687 lines
354 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,
|
|
}
|
|
|
|
// 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 `none` (a `{T, none}` 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,
|
|
}
|
|
|
|
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,
|
|
// 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][]hir.Stmt_Id,
|
|
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,
|
|
file: ast.File_Id,
|
|
hidden: bool,
|
|
name: symbol.Id,
|
|
id: ast.Function_Id,
|
|
}
|
|
|
|
Global_Index_Entry :: struct {
|
|
scope: ast.Package_Id,
|
|
file: ast.File_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,
|
|
}
|
|
|
|
Type_Factory_Origin :: struct {
|
|
result: types.Type,
|
|
template: ast.Function_Id,
|
|
values: []Comptime_Value,
|
|
}
|
|
|
|
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,
|
|
external_global_canonical: []ast.Global_Id,
|
|
external_global_diagnostics: []source.Diagnostic_Id,
|
|
constants: []Constant,
|
|
template_diagnostics: []source.Diagnostic_Id,
|
|
constant_stack: [dynamic]Constant_Frame,
|
|
ast_expr_stack: [dynamic]ast.Expr_Id,
|
|
hir_expr_stack: [dynamic]hir.Expr_Id,
|
|
infer_stack: [dynamic]Infer_Frame,
|
|
build_stack: [dynamic]Build_Expr_Frame,
|
|
cycle_stack: [dynamic]Cycle_Frame,
|
|
// 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,
|
|
sink_symbol: symbol.Id,
|
|
type_symbol: symbol.Id,
|
|
current_result: types.Type,
|
|
current_build_ctx: ^Build_Ctx,
|
|
current_comptime_values: []Comptime_Value,
|
|
type_factories: [dynamic]Type_Factory_Entry,
|
|
generated_types: [dynamic]Generated_Type_Entry,
|
|
type_factory_origins: [dynamic]Type_Factory_Origin,
|
|
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,
|
|
})
|
|
}
|
|
|
|
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 .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, .Anyopaque, .Int_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_ptr_cast_call :: proc(checker: ^Checker, expr: ast.Expr) -> bool {
|
|
return expr.left == ast.INVALID_EXPR &&
|
|
!symbol.is_valid(expr.qualifier) &&
|
|
symbol_text(checker, expr.name) == "ptr_cast"
|
|
}
|
|
|
|
Type_Builtin :: enum u8 {
|
|
None,
|
|
Size_Of,
|
|
Align_Of,
|
|
Min_Value,
|
|
Max_Value,
|
|
}
|
|
|
|
type_builtin_call :: proc(checker: ^Checker, expr: ast.Expr) -> Type_Builtin {
|
|
if expr.kind != .Call || expr.left != ast.INVALID_EXPR || symbol.is_valid(expr.qualifier) {
|
|
return .None
|
|
}
|
|
name := symbol_text(checker, expr.name)
|
|
if name == "size_of" {
|
|
return .Size_Of
|
|
}
|
|
if name == "align_of" {
|
|
return .Align_Of
|
|
}
|
|
if name == "min_value" {
|
|
return .Min_Value
|
|
}
|
|
if name == "max_value" {
|
|
return .Max_Value
|
|
}
|
|
return .None
|
|
}
|
|
|
|
valid_ptr_cast_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,
|
|
)
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
type_from_syntax :: proc(
|
|
checker: ^Checker,
|
|
value: ast.Type_Syntax,
|
|
pkg := ast.Package_Id(0),
|
|
file := ast.File_Id(0),
|
|
depth := 0,
|
|
) -> 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)
|
|
case .Array:
|
|
child := type_from_syntax(checker, item.child, pkg, file, depth+1)
|
|
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_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 {
|
|
source.add(checker.diagnostics, span, "array count must be a compile-time integer expression")
|
|
return types.INVALID
|
|
}
|
|
}
|
|
case .Pointer, .Slice, .Optional, .Range, .Distinct, .Enum, .Fallible:
|
|
child := type_from_syntax(checker, item.child, pkg, file, depth+1)
|
|
extra := type_from_syntax(checker, item.extra, pkg, file, depth+1)
|
|
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)
|
|
params_changed := false
|
|
for param, index in params {
|
|
resolved_params[index] = type_from_syntax(checker, param.type, pkg, file, depth+1)
|
|
params_changed = params_changed || resolved_params[index] != param.type
|
|
}
|
|
result := type_from_syntax(checker, item.child, pkg, file, depth+1)
|
|
if params_changed || result != item.child {
|
|
return types.function(store, resolved_params, result, item.c_abi, item.variadic)
|
|
}
|
|
case .Type_Call:
|
|
return resolve_type_factory_call(checker, ast.Expr_Id(item.count_expr), pkg, 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)
|
|
}
|
|
|
|
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_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 entry.file == 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 entry.file == 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
|
|
}
|
|
}
|
|
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, file=function.file, hidden=function.file_hidden, name=function.name, id=ast.function_id(id)}
|
|
function_index += 1
|
|
}
|
|
slice.sort_by(checker.function_index, function_index_less)
|
|
|
|
checker.global_index = make([]Global_Index_Entry, len(checker.ast_module.globals), checker.allocator)
|
|
for global, id in checker.ast_module.globals {
|
|
checker.global_index[id] = Global_Index_Entry{scope=global.pkg, file=global.file, hidden=global.file_hidden, name=global.name, id=ast.global_id(id)}
|
|
}
|
|
slice.sort_by(checker.global_index, global_index_less)
|
|
|
|
checker.import_index = make([]Import_Index_Entry, len(checker.ast_module.imports), checker.allocator)
|
|
for import_item, id in checker.ast_module.imports {
|
|
checker.import_index[id] = Import_Index_Entry{scope=import_item.file, name=import_item.alias, id=ast.import_id(id)}
|
|
}
|
|
slice.sort_by(checker.import_index, import_index_less)
|
|
}
|
|
|
|
find_template :: proc(checker: ^Checker, name: symbol.Id, pkg := ast.Package_Id(0), file := ast.INVALID_FILE) -> ast.Function_Id {
|
|
return find_function_symbol(checker.function_index, pkg, name, file)
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
declarations_conflict :: proc(left_file: ast.File_Id, left_hidden: bool, right_file: ast.File_Id, right_hidden: bool) -> bool {
|
|
return left_file == right_file if left_hidden && right_hidden else true
|
|
}
|
|
|
|
type_declaration_conflicts :: proc(checker: ^Checker, pkg: ast.Package_Id, name: symbol.Id, file: ast.File_Id, hidden: bool) -> bool {
|
|
for item in checker.module.types.nodes {
|
|
if item.declared && item.pkg == u32(pkg) && item.name == u32(name) &&
|
|
declarations_conflict(file, hidden, ast.File_Id(item.file), item.file_hidden) {
|
|
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
|
|
}
|
|
|
|
add_package_resolution_diagnostic :: proc(checker: ^Checker, expr: ast.Expr, file: ast.File_Id) -> source.Diagnostic_Id {
|
|
if find_import(checker, file, expr.qualifier) == ast.INVALID_IMPORT {
|
|
return source.addf(checker.diagnostics, expr.span, "unknown package alias '%s'", symbol_text(checker, expr.qualifier))
|
|
}
|
|
return source.addf(checker.diagnostics, expr.span, "unavailable imported package '%s'", symbol_text(checker, expr.qualifier))
|
|
}
|
|
|
|
add_name_resolution_diagnostic :: proc(checker: ^Checker, expr: ast.Expr, target_pkg: ast.Package_Id, file: ast.File_Id) -> source.Diagnostic_Id {
|
|
if find_template(checker, expr.name, target_pkg, expr_lookup_file(expr, file)) != ast.INVALID_FUNCTION {
|
|
return source.addf(checker.diagnostics, expr.span, "'%s' is a function, not a global value", symbol_text(checker, expr.name))
|
|
}
|
|
if symbol.is_valid(expr.qualifier) {
|
|
return source.addf(
|
|
checker.diagnostics,
|
|
expr.span,
|
|
"package '%s' has no member '%s'",
|
|
symbol_text(checker, expr.qualifier),
|
|
symbol_text(checker, expr.name),
|
|
)
|
|
}
|
|
return source.addf(checker.diagnostics, expr.span, "unresolved global '%s'", symbol_text(checker, expr.name))
|
|
}
|
|
|
|
add_call_resolution_diagnostic :: proc(checker: ^Checker, expr: ast.Expr, target_pkg: ast.Package_Id, file: ast.File_Id) -> source.Diagnostic_Id {
|
|
if find_global(checker, expr.name, target_pkg, expr_lookup_file(expr, file)) != ast.INVALID_GLOBAL {
|
|
return source.addf(checker.diagnostics, expr.span, "'%s' is a global, not a function", symbol_text(checker, expr.name))
|
|
}
|
|
if symbol.is_valid(expr.qualifier) {
|
|
return source.addf(
|
|
checker.diagnostics,
|
|
expr.span,
|
|
"package '%s' has no member '%s'",
|
|
symbol_text(checker, expr.qualifier),
|
|
symbol_text(checker, expr.name),
|
|
)
|
|
}
|
|
return source.addf(checker.diagnostics, expr.span, "unresolved function '%s'", symbol_text(checker, expr.name))
|
|
}
|
|
|
|
find_unsupported :: proc(checker: ^Checker, pkg: ast.Package_Id, name: symbol.Id) -> (ast.Unsupported, bool) {
|
|
for item in checker.ast_module.unsupported {
|
|
if item.pkg == pkg && item.name == name {
|
|
return item, true
|
|
}
|
|
}
|
|
return {}, false
|
|
}
|
|
|
|
add_unsupported_diagnostic :: proc(checker: ^Checker, span: source.Span, pkg: ast.Package_Id, name: symbol.Id) -> source.Diagnostic_Id {
|
|
if item, ok := find_unsupported(checker, pkg, name); ok {
|
|
return source.addf(
|
|
checker.diagnostics,
|
|
span,
|
|
"C declaration '%s' is unavailable: %s",
|
|
symbol_text(checker, name),
|
|
item.reason,
|
|
)
|
|
}
|
|
return source.INVALID_DIAGNOSTIC
|
|
}
|
|
|
|
add_unsupported_type_diagnostic :: proc(
|
|
checker: ^Checker,
|
|
span: source.Span,
|
|
value: types.Type,
|
|
depth := 0,
|
|
) -> source.Diagnostic_Id {
|
|
if depth > 64 {
|
|
return source.INVALID_DIAGNOSTIC
|
|
}
|
|
item, ok := types.node(&checker.module.types, value)
|
|
if !ok {
|
|
return source.INVALID_DIAGNOSTIC
|
|
}
|
|
if item.kind == .Alias {
|
|
return add_unsupported_diagnostic(checker, span, ast.Package_Id(item.pkg), symbol.Id(item.name))
|
|
}
|
|
if types.is_valid(item.child) {
|
|
return add_unsupported_type_diagnostic(checker, span, item.child, depth+1)
|
|
}
|
|
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,
|
|
}
|
|
|
|
comptime_prefix_count :: proc(function: ast.Function) -> (int, bool) {
|
|
count := 0
|
|
for param in function.params {
|
|
if param.comptime_value {
|
|
count += 1
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
for param in function.params[count:] {
|
|
if param.comptime_value {
|
|
return count, false
|
|
}
|
|
}
|
|
return count, count > 0
|
|
}
|
|
|
|
call_argument_mode :: proc(function: ast.Function, count: int) -> (Call_Argument_Mode, int) {
|
|
if valid_call_arity(function, count) {
|
|
return .Explicit, 0
|
|
}
|
|
prefix, inferable := comptime_prefix_count(function)
|
|
if inferable && !function.c_abi && !function.variadic && count == len(function.params)-prefix {
|
|
return .Inferred, prefix
|
|
}
|
|
return .Invalid, prefix
|
|
}
|
|
|
|
call_param_index :: proc(mode: Call_Argument_Mode, prefix, source_index: int) -> int {
|
|
return source_index+prefix if mode == .Inferred else source_index
|
|
}
|
|
|
|
next_runtime_call_arg :: proc(function: ast.Function, mode: Call_Argument_Mode, prefix, start: int, source_count: int) -> int {
|
|
index := start
|
|
for index < source_count {
|
|
param_index := call_param_index(mode, prefix, index)
|
|
if param_index >= len(function.params) || !function.params[param_index].comptime_value {
|
|
break
|
|
}
|
|
index += 1
|
|
}
|
|
return index
|
|
}
|
|
|
|
comptime_binding_index :: proc(function: ast.Function, prefix: int, name: symbol.Id) -> (int, bool) {
|
|
for param, index in function.params[:prefix] {
|
|
if param.name == name {
|
|
return index, true
|
|
}
|
|
}
|
|
return -1, false
|
|
}
|
|
|
|
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 := existing.kind == value.kind
|
|
if matches {
|
|
if existing.kind == .Type {
|
|
matches = types.equal(types.resolve_alias(existing.type, &checker.module.types), types.resolve_alias(value.type, &checker.module.types))
|
|
} else {
|
|
matches = existing.value == value.value && types.equal(existing.type, value.type)
|
|
}
|
|
}
|
|
if !matches && diagnose {
|
|
left := type_label(checker, existing.type) if existing.kind == .Type else fmt.aprintf("%d", existing.value, allocator=checker.allocator)
|
|
right := type_label(checker, value.type) if value.kind == .Type 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,
|
|
) -> 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 {
|
|
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 {
|
|
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 {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if types.is_valid(item.child) && type_pattern_mentions_comptime(checker, function, prefix, item.child, depth+1) {
|
|
return true
|
|
}
|
|
if types.is_valid(item.extra) && type_pattern_mentions_comptime(checker, function, prefix, item.extra, depth+1) {
|
|
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) {
|
|
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 index, is_binding := comptime_binding_index(function, prefix, name); is_binding &&
|
|
is_comptime_type_param(checker, function.params[index]) {
|
|
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
|
|
for candidate in checker.type_factory_origins {
|
|
if candidate.template == template && types.equal(candidate.result, actual_type) {
|
|
origin = candidate
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found || len(expr.args) != len(origin.values) {
|
|
return false
|
|
}
|
|
matched := true
|
|
for arg_id, index in expr.args {
|
|
if arg_id == ast.INVALID_EXPR || int(arg_id) >= len(checker.ast_module.exprs) {
|
|
matched = false
|
|
continue
|
|
}
|
|
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 !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 &&
|
|
!is_comptime_type_param(checker, function.params[binding_index]) {
|
|
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,
|
|
args: []ast.Expr_Id,
|
|
actual_args: []types.Type,
|
|
expected: types.Type,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
diagnose := false,
|
|
) -> ([]Comptime_Value, bool) {
|
|
values := make([]Comptime_Value, prefix, checker.allocator)
|
|
bound := make([]bool, prefix, checker.allocator)
|
|
defer delete(bound, checker.allocator)
|
|
for param, index in function.params[:prefix] {
|
|
values[index].name = param.name
|
|
if is_comptime_type_param(checker, param) {
|
|
values[index].kind = .Type
|
|
} else {
|
|
values[index].kind = .Integer
|
|
values[index].type = type_from_syntax(checker, param.type, function.pkg, function.file)
|
|
}
|
|
}
|
|
matched := true
|
|
if 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
|
|
}
|
|
}
|
|
// Concrete arguments bind first. Numeric constants are contextual and therefore
|
|
// only contribute their default type after stronger evidence has had a chance.
|
|
weak_passes := [2]bool{false, true}
|
|
for weak in weak_passes {
|
|
for arg_id, source_index in args {
|
|
param_index := prefix+source_index
|
|
if param_index >= len(function.params) || param_index >= len(actual_args) {
|
|
continue
|
|
}
|
|
is_weak := is_numeric_constant_expr(checker, arg_id)
|
|
if is_weak != weak {
|
|
continue
|
|
}
|
|
if !type_pattern_mentions_comptime(checker, function, prefix, function.params[param_index].type) {
|
|
continue
|
|
}
|
|
if 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
|
|
}
|
|
}
|
|
}
|
|
matched = match_inferred_type_pattern(
|
|
checker, function, prefix, function.params[param_index].type,
|
|
actual_args[param_index], values, bound,
|
|
checker.ast_module.exprs[arg_id].span, diagnose,
|
|
) && matched
|
|
}
|
|
}
|
|
for param, index in function.params[:prefix] {
|
|
if bound[index] {
|
|
continue
|
|
}
|
|
matched = false
|
|
if diagnose {
|
|
source.addf(
|
|
checker.diagnostics, param.span,
|
|
"cannot infer comptime parameter '%s'; pass it explicitly",
|
|
symbol_text(checker, param.name),
|
|
)
|
|
}
|
|
}
|
|
if !matched {
|
|
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.
|
|
// `int`/`range` constraints have no single default and keep building naturally.
|
|
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) {
|
|
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
|
|
}
|
|
|
|
resolve_generated_struct_type :: proc(checker: ^Checker, expr_id: ast.Expr_Id, pkg: ast.Package_Id, file: ast.File_Id) -> 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)
|
|
if !is_runtime_type(checker, resolved) || types.is_void(resolved) {
|
|
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)
|
|
append(&checker.generated_types, Generated_Type_Entry{
|
|
expr=expr_id,
|
|
values=clone_comptime_values(checker.current_comptime_values, checker.allocator),
|
|
result=result,
|
|
})
|
|
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
|
|
}
|
|
target_pkg, available := expr_package(checker, expr, pkg, file, true)
|
|
if !available {
|
|
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)
|
|
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
|
|
if types.is_valid(result) {
|
|
generated := false
|
|
for entry in checker.generated_types {
|
|
if types.equal(entry.result, result) {
|
|
generated = true
|
|
break
|
|
}
|
|
}
|
|
if generated {
|
|
has_origin := false
|
|
for origin in checker.type_factory_origins {
|
|
if types.equal(origin.result, result) {
|
|
has_origin = true
|
|
break
|
|
}
|
|
}
|
|
if !has_origin {
|
|
append(&checker.type_factory_origins, Type_Factory_Origin{
|
|
result=result,
|
|
template=template,
|
|
values=clone_comptime_values(values, checker.allocator),
|
|
})
|
|
}
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
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
|
|
}
|
|
declared := type_from_syntax(checker, param.type, function.pkg, function.file)
|
|
if !types.is_concrete_integer(declared) {
|
|
if diagnose {
|
|
source.addf(
|
|
checker.diagnostics,
|
|
param.span,
|
|
"comptime parameter '%s' requires a concrete integer type",
|
|
symbol_text(checker, param.name),
|
|
)
|
|
}
|
|
ok = false
|
|
continue
|
|
}
|
|
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})
|
|
}
|
|
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) && !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_pointer_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)
|
|
pointer_type := types.pointer(&checker.module.types, function_type, false, false)
|
|
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)
|
|
mark_spec_demanded(checker, spec, demanded)
|
|
}
|
|
return pointer_type, spec, spec != INVALID_SPEC || !demand_spec
|
|
}
|
|
|
|
contains_name :: proc(names: []symbol.Id, name: symbol.Id) -> bool {
|
|
for existing in names {
|
|
if existing == name {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
mark_expr_imports_used :: proc(checker: ^Checker, expr_id: ast.Expr_Id, file: ast.File_Id) {
|
|
stack := checker.ast_expr_stack
|
|
clear_dynamic_array(&stack)
|
|
defer {
|
|
clear_dynamic_array(&stack)
|
|
checker.ast_expr_stack = stack
|
|
}
|
|
append(&stack, expr_id)
|
|
for len(stack) > 0 {
|
|
id := pop(&stack)
|
|
if id == ast.INVALID_EXPR || int(id) >= len(checker.ast_module.exprs) {
|
|
continue
|
|
}
|
|
expr := checker.ast_module.exprs[id]
|
|
if (expr.kind == .Name || expr.kind == .Call) && symbol.is_valid(expr.qualifier) {
|
|
_ = find_import(checker, file, expr.qualifier, true)
|
|
}
|
|
switch expr.kind {
|
|
case .Call:
|
|
append(&stack, ..expr.args)
|
|
if expr.left != ast.INVALID_EXPR {
|
|
append(&stack, expr.left)
|
|
}
|
|
case .Array, .Struct_Literal, .Slice:
|
|
append(&stack, ..expr.args)
|
|
if expr.left != ast.INVALID_EXPR {
|
|
append(&stack, expr.left)
|
|
}
|
|
case .Negate, .Not, .Address, .Deref, .Field, .Unwrap, .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, .Index, .Orelse, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
|
|
append(&stack, expr.left, expr.right)
|
|
case .Invalid, .Integer, .Float, .String, .Bool, .None, .Undefined, .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]
|
|
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
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
locals: [dynamic]symbol.Id
|
|
locals.allocator = checker.allocator
|
|
seen_runtime_param := false
|
|
comptime_order_reported := false
|
|
for param in function.params {
|
|
if param.comptime_value {
|
|
if seen_runtime_param && !comptime_order_reported {
|
|
checker.template_diagnostics[function_id] = source.add(
|
|
checker.diagnostics,
|
|
param.span,
|
|
"comptime parameters must form a leading parameter prefix",
|
|
)
|
|
comptime_order_reported = true
|
|
}
|
|
} else {
|
|
seen_runtime_param = true
|
|
}
|
|
param_type := types.INVALID
|
|
if param.comptime_value || !has_comptime {
|
|
param_type = type_from_syntax(checker, param.type, function.pkg, function.file)
|
|
}
|
|
if param.comptime_value {
|
|
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) && !types.is_concrete_integer(param_type) {
|
|
checker.template_diagnostics[function_id] = source.addf(
|
|
checker.diagnostics,
|
|
param.span,
|
|
"comptime parameter '%s' requires a concrete integer type",
|
|
symbol_text(checker, param.name),
|
|
)
|
|
}
|
|
} else if !has_comptime {
|
|
if diagnostic := add_unsupported_type_diagnostic(checker, param.span, param_type);
|
|
diagnostic != source.INVALID_DIAGNOSTIC {
|
|
checker.template_diagnostics[function_id] = diagnostic
|
|
continue
|
|
}
|
|
}
|
|
if param.type == types.VOID {
|
|
source.add(
|
|
checker.diagnostics,
|
|
param.span,
|
|
"void is only valid as a function result type",
|
|
)
|
|
}
|
|
if 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 && 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 {
|
|
result_type := type_from_syntax(checker, function.result, function.pkg, function.file)
|
|
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) {
|
|
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 {
|
|
checker.template_diagnostics[function_id] = source.addf(
|
|
checker.diagnostics,
|
|
function.span,
|
|
"bodyless function '%s' must use 'c_func'",
|
|
symbol_text(checker, function.name),
|
|
)
|
|
}
|
|
if function.variadic && (!function.c_abi || function.has_body) {
|
|
checker.template_diagnostics[function_id] = source.addf(
|
|
checker.diagnostics,
|
|
function.span,
|
|
"variadic function '%s' must be a bodyless 'c_func' declaration",
|
|
symbol_text(checker, function.name),
|
|
)
|
|
}
|
|
if !function.has_body && function.c_abi {
|
|
for param in function.params {
|
|
param_type := type_from_syntax(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.contains_c_struct_by_value(result, &checker.module.types) &&
|
|
!types.is_c_signature_type(result, &checker.module.types, true) {
|
|
checker.template_diagnostics[function_id] = source.addf(
|
|
checker.diagnostics,
|
|
function.span,
|
|
"foreign function '%s' requires a concrete or void result type",
|
|
symbol_text(checker, function.name),
|
|
)
|
|
}
|
|
if function.pkg == 0 && function.name == checker.main_symbol {
|
|
checker.template_diagnostics[function_id] = source.add(
|
|
checker.diagnostics,
|
|
function.span,
|
|
"main must have a body",
|
|
)
|
|
}
|
|
}
|
|
mark_block_imports_used(checker, function.body, function.file)
|
|
delete(locals)
|
|
}
|
|
for function, function_id in checker.ast_module.functions {
|
|
if function.has_body || !function.c_abi {
|
|
continue
|
|
}
|
|
for other, other_id in checker.ast_module.functions {
|
|
if other_id == function_id || other.has_body || !other.c_abi || other.name != function.name {
|
|
continue
|
|
}
|
|
if function.imported && other.imported && function_signatures_equal(function, other) {
|
|
continue
|
|
}
|
|
checker.template_diagnostics[function_id] = source.addf(
|
|
checker.diagnostics,
|
|
function.span,
|
|
"duplicate foreign symbol '%s'",
|
|
symbol_text(checker, function.name),
|
|
)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
validate_type_nodes :: proc(checker: ^Checker) {
|
|
for item, index in checker.module.types.nodes {
|
|
id := types.DYNAMIC_START+types.Type(index)
|
|
if item.kind == .Distinct &&
|
|
(!item.declared || !types.is_runtime_value(item.child, &checker.module.types)) {
|
|
source.addf(
|
|
checker.diagnostics,
|
|
source.Span{},
|
|
"distinct type '%s' requires a concrete runtime backing type",
|
|
symbol_text(checker, symbol.Id(item.name)),
|
|
)
|
|
}
|
|
if item.kind == .Enum {
|
|
if !item.declared || !types.is_concrete_integer(item.child) {
|
|
source.addf(
|
|
checker.diagnostics,
|
|
source.Span{},
|
|
"enum type '%s' requires a concrete integer backing type",
|
|
symbol_text(checker, symbol.Id(item.name)),
|
|
)
|
|
} else {
|
|
for member in types.enum_members_for(&checker.module.types, id) {
|
|
if !fits_integer_type(member.value, item.child, checker.target) {
|
|
source.addf(
|
|
checker.diagnostics,
|
|
source.Span{},
|
|
"enum value %d does not fit in %s",
|
|
member.value,
|
|
types.name(item.child),
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if item.has_sentinel {
|
|
value := i128(item.sentinel)
|
|
if types.is_signed(item.child, checker.target) {
|
|
value = i128(i64(item.sentinel))
|
|
}
|
|
if !types.is_concrete_integer(item.child) || !fits_integer_type(value, item.child, checker.target) {
|
|
source.addf(
|
|
checker.diagnostics,
|
|
source.Span{},
|
|
"sentinel value does not fit array, slice, or pointer element type %s",
|
|
types.name(item.child),
|
|
)
|
|
}
|
|
}
|
|
if item.kind == .Struct || item.kind == .Union {
|
|
if item.c_layout && !item.opaque && item.field_count == 0 {
|
|
source.add(
|
|
checker.diagnostics,
|
|
source.Span{},
|
|
"c_struct definitions require at least one field",
|
|
)
|
|
}
|
|
// 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 in types.fields_for(&checker.module.types, id) {
|
|
if tagged_union && types.is_void(field.type) {
|
|
// void variant: no payload to validate.
|
|
} else if !types.is_runtime_value(field.type, &checker.module.types) {
|
|
source.add(
|
|
checker.diagnostics,
|
|
source.Span{},
|
|
"record fields must have runtime value types",
|
|
)
|
|
} else if item.c_layout && !types.is_c_record_field_type(field.type, &checker.module.types) {
|
|
source.add(
|
|
checker.diagnostics,
|
|
source.Span{},
|
|
"c_struct fields must have C-layout-compatible types",
|
|
)
|
|
}
|
|
}
|
|
// 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 {
|
|
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) && !is_runtime_type(checker, item.child) {
|
|
source.add(checker.diagnostics, source.Span{}, "native function pointer results must be concrete runtime types or void")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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`/`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,
|
|
args: []types.Type,
|
|
template: ast.Function_Id,
|
|
}
|
|
|
|
infer_nested_expr :: proc(
|
|
checker: ^Checker,
|
|
expr_id: ast.Expr_Id,
|
|
locals: []Infer_Local,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
demanded: ^[dynamic]Spec_Id,
|
|
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_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 .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or:
|
|
_ = 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 .None:
|
|
return types.INVALID
|
|
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:
|
|
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)
|
|
field_name := symbol_text(checker, expr.name)
|
|
item, has_item := types.container(value, store)
|
|
if has_item && (item.kind == .Array || item.kind == .Slice) {
|
|
if field_name == "len" {
|
|
return types.USIZE
|
|
}
|
|
if field_name == "ptr" &&
|
|
(item.kind == .Slice || types.is_pointer(value, store)) {
|
|
return container_pointer_type(store, item)
|
|
}
|
|
}
|
|
if types.is_pointer(value, store) {
|
|
value = types.child_type(value, store)
|
|
}
|
|
_, field, ok := find_struct_field(checker, value, expr.name)
|
|
return field.type if ok else types.INVALID
|
|
case .Unwrap:
|
|
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, 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)
|
|
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)
|
|
if expr.right != ast.INVALID_EXPR {
|
|
fallback := infer_nested_expr(checker, expr.right, 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
|
|
}
|
|
block_locals: [dynamic]Infer_Local
|
|
block_locals.allocator = checker.allocator
|
|
defer delete(block_locals)
|
|
append(&block_locals, ..locals)
|
|
capture_start := len(block_locals)
|
|
if symbol.is_valid(expr.name) && expr.name != checker.sink_symbol && types.is_valid(error_type) {
|
|
append(&block_locals, Infer_Local{name=expr.name, type=error_type, declared=error_type, statement=ast.INVALID_STMT})
|
|
}
|
|
infer_statements(checker, expr.body, &block_locals, local_types, pkg, file, demanded, &success, success)
|
|
resize(&block_locals, capture_start)
|
|
return success
|
|
case .Struct_Literal:
|
|
for keyed in expr.args {
|
|
_ = infer_nested_expr(checker, checker.ast_module.exprs[keyed].left, locals, pkg, file, demanded, local_types)
|
|
}
|
|
if !symbol.is_valid(expr.name) {
|
|
return types.INVALID
|
|
}
|
|
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
|
|
return types.resolve_alias(value, store)
|
|
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
|
|
clear_dynamic_array(&stack)
|
|
defer {
|
|
for frame in stack {
|
|
delete(frame.args, checker.allocator)
|
|
}
|
|
clear_dynamic_array(&stack)
|
|
checker.infer_stack = stack
|
|
}
|
|
append(&stack, Infer_Frame{expr=expr_id, 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 := eval_constant(checker, frame.expr)
|
|
if constant.kind == .Overflow || constant.kind == .Div_By_Zero ||
|
|
(constant.kind == .Value && !fits_i64(constant.value)) {
|
|
last = types.I64
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
if constant.kind == .Value {
|
|
last = types.smallest_signed_for_literal(i64(constant.value))
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
switch expr.kind {
|
|
case .Invalid:
|
|
last = types.INVALID
|
|
_ = pop(&stack)
|
|
case .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, .None, .Undefined, .Address, .Deref, .Index, .Slice,
|
|
.Field, .Unwrap, .Orelse, .Try, .Catch, .Struct_Literal, .Keyed, .Enum_Literal, .Cast,
|
|
.Comptime, .Bool, .Not, .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))
|
|
pointer_type, _, ok := function_pointer_type_for_template(checker, template, demanded)
|
|
last = pointer_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) {
|
|
_, field, ok := find_struct_field(checker, base_type, expr.name)
|
|
if ok {
|
|
last = field.type
|
|
}
|
|
}
|
|
}
|
|
if !types.is_valid(last) && 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_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) {
|
|
_, field, ok := find_struct_field(checker, base_type, expr.name)
|
|
if ok {
|
|
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 {
|
|
last = value.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 {
|
|
pointer_type, _, ok := function_pointer_type_for_template(checker, template, demanded)
|
|
if ok {
|
|
last = pointer_type
|
|
}
|
|
}
|
|
}
|
|
}
|
|
_ = pop(&stack)
|
|
case .Negate:
|
|
stack[frame_index].stage = 5
|
|
append(&stack, Infer_Frame{expr=expr.left, template=ast.INVALID_FUNCTION})
|
|
case .Add, .Sub, .Mul, .Div:
|
|
stack[frame_index].stage = 1
|
|
append(&stack, Infer_Frame{expr=expr.left, template=ast.INVALID_FUNCTION})
|
|
case .Call:
|
|
if expr.left != ast.INVALID_EXPR {
|
|
callee_type := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
|
_, function_item, function_type, ok := types.function_pointer(callee_type, &checker.module.types)
|
|
if !ok {
|
|
last = types.INVALID
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
stack[frame_index].left = function_type
|
|
stack[frame_index].args = make([]types.Type, len(expr.args), checker.allocator)
|
|
stack[frame_index].stage = 6
|
|
if len(expr.args) > 0 {
|
|
append(&stack, Infer_Frame{expr=expr.args[0], template=ast.INVALID_FUNCTION})
|
|
} else if valid_callable_arity(function_item, 0) {
|
|
last = function_item.child
|
|
delete(stack[frame_index].args, checker.allocator)
|
|
stack[frame_index].args = nil
|
|
_ = pop(&stack)
|
|
}
|
|
continue
|
|
}
|
|
if 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 is_ptr_cast_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_ptr_cast_child(checker, child) {
|
|
result, _ = types.replace_pointer_child(&checker.module.types, operand, child)
|
|
}
|
|
last = result
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
if callee_type, handled := infer_qualified_value_field_type(checker, expr, locals, pkg, file); handled {
|
|
_, function_item, function_type, ok := types.function_pointer(callee_type, &checker.module.types)
|
|
if !ok {
|
|
last = types.INVALID
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
stack[frame_index].left = function_type
|
|
stack[frame_index].args = make([]types.Type, len(expr.args), checker.allocator)
|
|
stack[frame_index].stage = 6
|
|
if len(expr.args) > 0 {
|
|
append(&stack, Infer_Frame{expr=expr.args[0], template=ast.INVALID_FUNCTION})
|
|
} else if valid_callable_arity(function_item, 0) {
|
|
last = function_item.child
|
|
delete(stack[frame_index].args, checker.allocator)
|
|
stack[frame_index].args = nil
|
|
_ = pop(&stack)
|
|
}
|
|
continue
|
|
}
|
|
target_pkg, available := expr_package(checker, expr, pkg, file)
|
|
template := ast.INVALID_FUNCTION
|
|
if available {
|
|
template = find_template(checker, expr.name, target_pkg, 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) && 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.function_pointer(callee_type, &checker.module.types)
|
|
if !ok {
|
|
distinct_type := types.find_named(&checker.module.types, u32(target_pkg), u32(expr.name), file=u32(expr_lookup_file(expr, file)))
|
|
distinct_item, distinct_ok := types.node(&checker.module.types, distinct_type)
|
|
if available && distinct_ok && distinct_item.kind == .Distinct && len(expr.args) == 1 {
|
|
stack[frame_index].left = distinct_type
|
|
stack[frame_index].stage = 7
|
|
append(&stack, Infer_Frame{expr=expr.args[0], template=ast.INVALID_FUNCTION})
|
|
continue
|
|
}
|
|
last = types.INVALID
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
stack[frame_index].left = function_type
|
|
stack[frame_index].args = make([]types.Type, len(expr.args), checker.allocator)
|
|
stack[frame_index].stage = 6
|
|
if len(expr.args) > 0 {
|
|
append(&stack, Infer_Frame{expr=expr.args[0], template=ast.INVALID_FUNCTION})
|
|
} else if valid_callable_arity(function_item, 0) {
|
|
last = function_item.child
|
|
delete(stack[frame_index].args, checker.allocator)
|
|
stack[frame_index].args = nil
|
|
_ = pop(&stack)
|
|
}
|
|
continue
|
|
}
|
|
if len(checker.ast_module.functions[template].unsupported_reason) > 0 {
|
|
last = types.INVALID
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
if checker.template_diagnostics[template] != source.INVALID_DIAGNOSTIC {
|
|
declared := function_channel_type(checker, checker.ast_module.functions[template])
|
|
last = declared if is_runtime_type(checker, declared) || types.is_void(declared) else types.INVALID
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
function := checker.ast_module.functions[template]
|
|
mode, prefix := call_argument_mode(function, len(expr.args))
|
|
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].args = make([]types.Type, len(function.params), checker.allocator)
|
|
stack[frame_index].arg_index = next_runtime_call_arg(function, mode, prefix, 0, len(expr.args))
|
|
stack[frame_index].stage = 3
|
|
if stack[frame_index].arg_index < len(expr.args) {
|
|
param_index := call_param_index(mode, prefix, 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.arg_mode, frame.prefix, 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.arg_mode, frame.prefix,
|
|
frame.arg_index+1, len(expr.args),
|
|
)
|
|
stack[frame_index].arg_index = next
|
|
if next < len(expr.args) {
|
|
next_param := call_param_index(frame.arg_mode, frame.prefix, 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.arg_mode, frame.prefix, 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
|
|
if frame.arg_mode == .Inferred {
|
|
comptime_values, comptime_ok = infer_call_comptime_values(
|
|
checker, function, frame.prefix, expr.args, stack[frame_index].args,
|
|
frame.expected, pkg, file,
|
|
)
|
|
} else {
|
|
comptime_values, comptime_ok = collect_comptime_values(checker, function, expr.args, pkg, file)
|
|
}
|
|
defer delete(comptime_values, checker.allocator)
|
|
if comptime_ok &&
|
|
can_specialize(checker, function, stack[frame_index].args, comptime_values) {
|
|
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.arg_mode, frame.prefix, 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)
|
|
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) 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) else types.INVALID
|
|
}
|
|
}
|
|
delete(stack[frame_index].args, checker.allocator)
|
|
stack[frame_index].args = nil
|
|
_ = pop(&stack)
|
|
}
|
|
if frame.stage == 6 {
|
|
if frame.arg_index < len(expr.args) {
|
|
stack[frame_index].args[frame.arg_index] = last
|
|
stack[frame_index].arg_index += 1
|
|
if frame.arg_index+1 < len(expr.args) {
|
|
append(&stack, Infer_Frame{expr=expr.args[frame.arg_index+1], template=ast.INVALID_FUNCTION})
|
|
continue
|
|
}
|
|
}
|
|
function_item, ok := types.node(&checker.module.types, frame.left)
|
|
if ok && function_item.kind == .Function && valid_callable_arity(function_item, len(expr.args)) {
|
|
last = function_item.child
|
|
} else {
|
|
last = types.INVALID
|
|
}
|
|
delete(stack[frame_index].args, checker.allocator)
|
|
stack[frame_index].args = nil
|
|
_ = pop(&stack)
|
|
}
|
|
if frame.stage == 7 {
|
|
last = frame.left
|
|
_ = pop(&stack)
|
|
}
|
|
}
|
|
return last
|
|
}
|
|
|
|
flatten_conditional_unwrap_operands :: proc(
|
|
module: ^ast.Module,
|
|
expr_id: ast.Expr_Id,
|
|
operands: ^[dynamic]ast.Expr_Id,
|
|
) {
|
|
if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(module.exprs) {
|
|
append(operands, expr_id)
|
|
return
|
|
}
|
|
expr := module.exprs[expr_id]
|
|
if expr.kind == .And {
|
|
flatten_conditional_unwrap_operands(module, expr.left, operands)
|
|
flatten_conditional_unwrap_operands(module, expr.right, operands)
|
|
return
|
|
}
|
|
append(operands, expr_id)
|
|
}
|
|
|
|
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) 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) {
|
|
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)
|
|
} else if statement.name != checker.sink_symbol {
|
|
if local_index, ok := find_infer_local_index(locals^[:], statement.name); ok {
|
|
expected_assignment = locals^[local_index].type
|
|
} else if global := find_global(checker, statement.name, pkg, file); global != ast.INVALID_GLOBAL {
|
|
expected_assignment = 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)
|
|
}
|
|
}
|
|
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)
|
|
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:
|
|
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:
|
|
deferred := [1]ast.Stmt_Id{statement.update}
|
|
infer_statements(checker, deferred[:], locals, local_types, pkg, file, demanded, result, result_hint)
|
|
case .Match:
|
|
// 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)
|
|
for arm_id in statement.body {
|
|
arm := checker.ast_module.statements[arm_id]
|
|
if arm.kind != .Match_Arm {
|
|
continue
|
|
}
|
|
for pattern in arm.patterns {
|
|
_ = infer_expr(checker, pattern, locals^[:], pkg, file, demanded, local_types)
|
|
}
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
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) 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
|
|
infer_statements(checker, function.body, &locals, local_types, function.pkg, function.file, demanded, &result, result_hint)
|
|
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 types.smallest_signed_for_literal(i64(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] {
|
|
return types.smallest_signed_for_literal(i64(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)
|
|
}
|
|
|
|
// record_demand pushes a concrete type demand onto open numeric slots reachable
|
|
// through bare names 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 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 .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
|
|
}
|
|
}
|
|
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 {
|
|
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) {
|
|
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
|
|
}
|
|
}
|
|
|
|
main_template := find_template(checker, checker.main_symbol, 0)
|
|
if main_template != ast.INVALID_FUNCTION {
|
|
ensure_spec(checker, main_template, nil)
|
|
}
|
|
|
|
defaults_applied := false
|
|
for {
|
|
changed := false
|
|
checker.global_demands_dirty = false
|
|
spec_count := len(checker.specs)
|
|
|
|
// 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 {
|
|
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 {
|
|
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) 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)
|
|
inferred := infer_spec_result(checker, id)
|
|
changed = merge_inferred_type(&checker.module.types, &checker.specs[id].result, inferred) || changed
|
|
}
|
|
if len(checker.specs) != spec_count {
|
|
changed = true
|
|
}
|
|
// 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 !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 || is_runtime_type(checker, checker.global_types[index]) {
|
|
continue
|
|
}
|
|
if checker.global_open_const[index] {
|
|
checker.global_types[index] = types.smallest_signed_for_literal(i64(checker.global_const_value[index]))
|
|
defaulted = true
|
|
} else if checker.global_open_float[index] {
|
|
checker.global_types[index] = types.F64
|
|
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)
|
|
}
|
|
for global in checker.ast_module.globals {
|
|
if global.external {
|
|
continue
|
|
}
|
|
_ = infer_expr(checker, global.expr, nil, global.pkg, global.file, &stack)
|
|
}
|
|
for len(stack) > 0 {
|
|
id := pop(&stack)
|
|
_ = infer_spec_result(checker, id, &stack)
|
|
}
|
|
|
|
retained := 0
|
|
for spec in checker.specs {
|
|
if spec.hir_id == hir.INVALID_FUNCTION {
|
|
delete(spec.args, checker.allocator)
|
|
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,
|
|
},
|
|
)
|
|
}
|
|
|
|
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.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_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
|
|
}
|
|
actual := checker.module.exprs[expr_id].type
|
|
if types.equal(actual, expected) {
|
|
return expr_id
|
|
}
|
|
if types.can_weaken_pointer(actual, expected, &checker.module.types) {
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Weaken_Pointer,
|
|
span=span,
|
|
type=expected,
|
|
left=expr_id,
|
|
target=hir.INVALID_REF,
|
|
right=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
if types.can_weaken_slice(actual, expected, &checker.module.types) {
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Weaken_Slice,
|
|
span=span,
|
|
type=expected,
|
|
left=expr_id,
|
|
target=hir.INVALID_REF,
|
|
right=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
if types.can_decay_array_pointer(actual, expected, &checker.module.types) {
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Decay_Array_Pointer,
|
|
span=span,
|
|
type=expected,
|
|
left=expr_id,
|
|
target=hir.INVALID_REF,
|
|
right=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
if types.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_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 == .Overflow ||
|
|
(!types.is_concrete_integer(expected) && !fits_i64(constant.value)) {
|
|
id := source.add(
|
|
checker.diagnostics,
|
|
expr.span,
|
|
"integer constant expression exceeds signed i64 range",
|
|
)
|
|
return invalid_hir_expr(checker, expr.span, id, recovery_type)
|
|
}
|
|
|
|
value := i64(constant.value)
|
|
if constant.value >= 0 && constant.value <= i128(0xffff_ffff_ffff_ffff) {
|
|
value = transmute(i64)u64(constant.value)
|
|
}
|
|
result_type := types.smallest_signed_for_literal(value)
|
|
if types.is_concrete_integer(expected) {
|
|
if !fits_integer_type(constant.value, expected, checker.target) {
|
|
id := source.addf(
|
|
checker.diagnostics,
|
|
expr.span,
|
|
"integer constant %d does not fit in %s",
|
|
constant.value,
|
|
types.name(expected),
|
|
)
|
|
return invalid_hir_expr(checker, expr.span, id, expected)
|
|
}
|
|
result_type = expected
|
|
}
|
|
return add_hir_expr(
|
|
checker,
|
|
hir.Expr {
|
|
kind = .Integer,
|
|
span = expr.span,
|
|
type = result_type,
|
|
integer = value,
|
|
target = hir.INVALID_REF,
|
|
left = hir.INVALID_EXPR,
|
|
right = hir.INVALID_EXPR,
|
|
diagnostic = source.INVALID_DIAGNOSTIC,
|
|
},
|
|
)
|
|
}
|
|
|
|
build_float_expr :: proc(checker: ^Checker, expr: ast.Expr, expected: types.Type) -> hir.Expr_Id {
|
|
result_type := types.F64
|
|
if types.is_float(expected, checker.target) {
|
|
result_type = expected
|
|
} else if types.is_valid(expected) {
|
|
id := source.addf(
|
|
checker.diagnostics,
|
|
expr.span,
|
|
"cannot implicitly convert f64 to %s",
|
|
types.name(expected),
|
|
)
|
|
return invalid_hir_expr(checker, expr.span, id, expected)
|
|
}
|
|
value := transmute(f64)expr.integer
|
|
bits := transmute(i64)value
|
|
if types.bits(result_type, checker.target) == 32 {
|
|
bits = i64(transmute(u32)f32(value))
|
|
}
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Float,
|
|
span=expr.span,
|
|
type=result_type,
|
|
integer=bits,
|
|
target=hir.INVALID_REF,
|
|
left=hir.INVALID_EXPR,
|
|
right=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
|
|
Build_Expr_Frame :: struct {
|
|
expr: ast.Expr_Id,
|
|
expected: types.Type,
|
|
target_type: types.Type,
|
|
stage: u8,
|
|
left: hir.Expr_Id,
|
|
arg_index: int,
|
|
arg_mode: Call_Argument_Mode,
|
|
prefix: int,
|
|
built_args: []hir.Expr_Id,
|
|
arg_types: []types.Type,
|
|
template: ast.Function_Id,
|
|
}
|
|
|
|
hir_location_writable :: proc(checker: ^Checker, expr_id: hir.Expr_Id, locals: []Build_Local) -> bool {
|
|
if expr_id == hir.INVALID_EXPR || int(expr_id) >= len(checker.module.exprs) {
|
|
return false
|
|
}
|
|
expr := checker.module.exprs[expr_id]
|
|
#partial switch expr.kind {
|
|
case .Local:
|
|
id := hir.as_local(expr.target)
|
|
for local in locals {
|
|
if local.id == id {
|
|
return local.mutable
|
|
}
|
|
}
|
|
case .Global:
|
|
id := hir.as_global(expr.target)
|
|
return id != hir.INVALID_GLOBAL && int(id) < len(checker.module.globals) &&
|
|
checker.module.globals[id].writable
|
|
case .Deref:
|
|
pointer_type := checker.module.exprs[expr.left].type
|
|
return types.is_mutable(pointer_type, &checker.module.types)
|
|
case .Index:
|
|
container_type := checker.module.exprs[expr.left].type
|
|
item, ok := types.container(container_type, &checker.module.types)
|
|
if !ok || !item.mutable {
|
|
return false
|
|
}
|
|
if types.is_array(container_type, &checker.module.types) {
|
|
return hir_location_writable(checker, expr.left, locals)
|
|
}
|
|
return true
|
|
case .Field:
|
|
base_type := checker.module.exprs[expr.left].type
|
|
if types.is_pointer(base_type, &checker.module.types) {
|
|
return types.is_mutable(base_type, &checker.module.types)
|
|
}
|
|
return hir_location_writable(checker, expr.left, locals)
|
|
case:
|
|
}
|
|
return false
|
|
}
|
|
|
|
hir_is_location :: proc(checker: ^Checker, expr_id: hir.Expr_Id) -> bool {
|
|
if expr_id == hir.INVALID_EXPR || int(expr_id) >= len(checker.module.exprs) {
|
|
return false
|
|
}
|
|
#partial switch checker.module.exprs[expr_id].kind {
|
|
case .Local, .Global, .Deref, .Index, .Field:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
sequence_item :: proc(value: types.Type, store: ^types.Store) -> (types.Node, bool) {
|
|
item, ok := types.node(store, value)
|
|
if ok && (item.kind == .Array || item.kind == .Slice) {
|
|
return item, true
|
|
}
|
|
pointer, array, pointer_ok := types.array_pointer(value, store)
|
|
if pointer_ok {
|
|
array.mutable = pointer.mutable && array.mutable
|
|
return array, true
|
|
}
|
|
return {}, false
|
|
}
|
|
|
|
find_struct_field :: proc(checker: ^Checker, struct_type: types.Type, name: symbol.Id) -> (int, types.Field, bool) {
|
|
for field, index in types.fields_for(&checker.module.types, struct_type) {
|
|
if field.name == u32(name) {
|
|
return index, field, true
|
|
}
|
|
}
|
|
return 0, {}, false
|
|
}
|
|
|
|
field_type_from_value :: proc(checker: ^Checker, expr: ast.Expr, base_type: types.Type) -> types.Type {
|
|
store := &checker.module.types
|
|
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,
|
|
) -> (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 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
|
|
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 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_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)
|
|
pointer_type := types.pointer(&checker.module.types, function_type, false, false)
|
|
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) {
|
|
pointer_type = expected_pointer
|
|
}
|
|
spec := find_spec(checker, template, params)
|
|
if spec == INVALID_SPEC {
|
|
id := source.addf(
|
|
checker.diagnostics,
|
|
span,
|
|
"could not resolve callback specialization of '%s'",
|
|
symbol_text(checker, function.name),
|
|
)
|
|
return invalid_hir_expr(checker, span, id, pointer_type)
|
|
}
|
|
function_id := checker.specs[spec].hir_id
|
|
assert(function_id != hir.INVALID_FUNCTION)
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Function,
|
|
span=span,
|
|
type=pointer_type,
|
|
target=hir.function_ref(function_id),
|
|
left=hir.INVALID_EXPR,
|
|
right=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
|
|
build_nested_expr :: proc(
|
|
checker: ^Checker,
|
|
expr_id: ast.Expr_Id,
|
|
locals: []Build_Local,
|
|
global_reads: ^[dynamic]hir.Global_Id,
|
|
calls: ^[dynamic]hir.Function_Id,
|
|
expected: types.Type,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
) -> hir.Expr_Id {
|
|
outer := checker.build_stack
|
|
checker.build_stack = nil
|
|
checker.build_stack.allocator = checker.allocator
|
|
result := build_expr(checker, expr_id, locals, global_reads, calls, expected, pkg, file)
|
|
delete(checker.build_stack)
|
|
checker.build_stack = outer
|
|
return result
|
|
}
|
|
|
|
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,
|
|
})
|
|
}
|
|
|
|
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 .None:
|
|
if !types.is_optional(expected, store) {
|
|
id := source.add(checker.diagnostics, expr.span, "'none' requires an optional context")
|
|
return invalid_hir_expr(checker, expr.span, id, expected)
|
|
}
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.None, span=expr.span, type=expected, target=hir.INVALID_REF,
|
|
left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .Undefined:
|
|
id := source.add(
|
|
checker.diagnostics,
|
|
expr.span,
|
|
"'undefined' is only valid as a mutable local declaration initializer",
|
|
)
|
|
return invalid_hir_expr(checker, expr.span, id, expected)
|
|
case .Enum_Literal:
|
|
if types.is_tagged_union(expected, store) {
|
|
index, field, found := find_struct_field(checker, 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, 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, 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)
|
|
}
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Struct, span=expr.span, type=expected, args=values, integer=i64(index),
|
|
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
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(expected, store) {
|
|
id := source.addf(
|
|
checker.diagnostics,
|
|
expr.span,
|
|
"'.%s' requires an enum context",
|
|
symbol_text(checker, expr.name),
|
|
)
|
|
return invalid_hir_expr(checker, expr.span, id, expected)
|
|
}
|
|
return enum_member_hir(checker, expected, expr.name, expr.span)
|
|
case .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)
|
|
actual := checker.module.exprs[value].type
|
|
valid_target := types.is_concrete_scalar(target) && !types.is_bool(target)
|
|
valid_actual := types.is_concrete_scalar(actual) && !types.is_bool(actual)
|
|
if !valid_target || !valid_actual {
|
|
id := source.addf(
|
|
checker.diagnostics,
|
|
expr.span,
|
|
"scalar cast requires numeric scalar types, got %s to %s",
|
|
types.name(actual),
|
|
types.name(target),
|
|
)
|
|
return invalid_hir_expr(checker, expr.span, id, target)
|
|
}
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Scalar_Cast,
|
|
span=expr.span,
|
|
type=target,
|
|
left=value,
|
|
target=hir.INVALID_REF,
|
|
right=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
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 !hir_is_location(checker, value) {
|
|
id := source.add(checker.diagnostics, expr.span, "'&' requires an addressable location")
|
|
return invalid_hir_expr(checker, expr.span, id)
|
|
}
|
|
value_type := checker.module.exprs[value].type
|
|
writable := hir_location_writable(checker, value, locals)
|
|
result_type := types.pointer(store, value_type, writable, false)
|
|
if types.is_pointer(expected, store) &&
|
|
types.equal(types.child_type(expected, store), value_type) &&
|
|
(!types.is_mutable(expected, store) || writable) {
|
|
result_type = expected
|
|
}
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Address, span=expr.span, type=result_type, left=value,
|
|
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .Deref:
|
|
pointer := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
|
pointer_type := checker.module.exprs[pointer].type
|
|
if !types.is_pointer(pointer_type, store) {
|
|
id := source.add(checker.diagnostics, expr.span, "postfix '^' requires a pointer")
|
|
return invalid_hir_expr(checker, expr.span, id)
|
|
}
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Deref, span=expr.span, type=types.child_type(pointer_type, store), left=pointer,
|
|
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .Index:
|
|
container := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
|
index := build_nested_expr(checker, expr.right, locals, global_reads, calls, types.USIZE, pkg, file)
|
|
index = coerce_expr(checker, index, types.USIZE, expr.span)
|
|
container_type := checker.module.exprs[container].type
|
|
item, ok := types.container(container_type, store)
|
|
if !ok {
|
|
id := source.add(checker.diagnostics, expr.span, "indexing requires an array, slice, pointer-to-array, or many-item pointer")
|
|
return invalid_hir_expr(checker, expr.span, id)
|
|
}
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Index, span=expr.span, type=item.child, left=container, right=index,
|
|
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .Slice:
|
|
container := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
|
container_type := checker.module.exprs[container].type
|
|
item, ok := types.container(container_type, store)
|
|
if !ok {
|
|
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)
|
|
}
|
|
}
|
|
preserve_sentinel := item.has_sentinel && expr.args[1] == ast.INVALID_EXPR
|
|
result_type := types.slice(store, item.child, item.mutable, preserve_sentinel, item.sentinel)
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Slice, span=expr.span, type=result_type, args=bounds, left=container,
|
|
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .Field:
|
|
if enum_type, enum_ok := enum_type_from_field_expr(checker, expr, pkg, file, true); enum_ok {
|
|
return enum_member_hir(checker, enum_type, expr.name, expr.span)
|
|
}
|
|
base := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
|
base_type := checker.module.exprs[base].type
|
|
field_name := symbol_text(checker, expr.name)
|
|
item, has_item := types.container(base_type, store)
|
|
if has_item && (item.kind == .Array || item.kind == .Slice) {
|
|
if field_name == "len" {
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Length, span=expr.span, type=types.USIZE, left=base,
|
|
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
if field_name == "ptr" &&
|
|
(item.kind == .Slice || types.is_pointer(base_type, store)) {
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Slice_Ptr, span=expr.span,
|
|
type=container_pointer_type(store, item), left=base,
|
|
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
if field_name == "ptr" && item.kind == .Array {
|
|
id := source.add(checker.diagnostics, expr.span, "arrays do not expose '.ptr'; take their address first")
|
|
return invalid_hir_expr(checker, expr.span, id)
|
|
}
|
|
}
|
|
if types.is_pointer(base_type, store) {
|
|
base_type = types.child_type(base_type, store)
|
|
}
|
|
index, field, ok := find_struct_field(checker, base_type, expr.name)
|
|
if !ok {
|
|
id := source.addf(checker.diagnostics, expr.span, "unknown struct field '%s'", symbol_text(checker, expr.name))
|
|
return invalid_hir_expr(checker, expr.span, id)
|
|
}
|
|
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)
|
|
}
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Field, span=expr.span, type=field.type, integer=i64(index), left=base,
|
|
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .Unwrap:
|
|
optional := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
|
optional_type := checker.module.exprs[optional].type
|
|
if !types.is_optional(optional_type, store) {
|
|
id := source.add(checker.diagnostics, expr.span, "postfix '?' requires an optional")
|
|
return invalid_hir_expr(checker, expr.span, id)
|
|
}
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Unwrap, span=expr.span, type=types.child_type(optional_type, store), left=optional,
|
|
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .Orelse:
|
|
optional := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
|
optional_type := checker.module.exprs[optional].type
|
|
if !types.is_optional(optional_type, store) {
|
|
id := source.add(checker.diagnostics, expr.span, "'orelse' requires an optional left operand")
|
|
return invalid_hir_expr(checker, expr.span, id)
|
|
}
|
|
child := types.child_type(optional_type, store)
|
|
fallback := build_nested_expr(checker, expr.right, locals, global_reads, calls, child, pkg, file)
|
|
fallback = coerce_expr(checker, fallback, child, checker.module.exprs[fallback].span)
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Orelse, span=expr.span, type=child, left=optional, right=fallback,
|
|
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .Try:
|
|
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)
|
|
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)
|
|
}
|
|
if !types.equal(success, enclosing_success) {
|
|
id := source.add(checker.diagnostics, expr.span, "'try' success type must match the enclosing fallible result")
|
|
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)
|
|
}
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Try,
|
|
span=expr.span,
|
|
type=success,
|
|
left=channel,
|
|
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)
|
|
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
|
|
fallback := hir.INVALID_EXPR
|
|
if expr.right != ast.INVALID_EXPR {
|
|
fallback = build_nested_expr(checker, expr.right, locals, global_reads, calls, success, pkg, file)
|
|
fallback = coerce_expr(checker, fallback, success, checker.module.exprs[fallback].span)
|
|
} else {
|
|
block_handler = true
|
|
ctx := checker.current_build_ctx
|
|
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)
|
|
}
|
|
capture_start := len(ctx.locals^)
|
|
error_type := types.fallible_error(channel_type, store)
|
|
if symbol.is_valid(expr.name) && expr.name != checker.sink_symbol {
|
|
capture = append_build_local(ctx, expr.name, error_type, false, expr.span)
|
|
}
|
|
handler: [dynamic]hir.Stmt_Id
|
|
handler.allocator = checker.allocator
|
|
fallback, _ = build_value_source(ctx, &handler, expr.body, success, expr.span, allow_exit=true)
|
|
body = handler[:]
|
|
resize(ctx.locals, capture_start)
|
|
}
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Catch,
|
|
span=expr.span,
|
|
type=success,
|
|
integer=1 if block_handler else 0,
|
|
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)
|
|
}
|
|
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)
|
|
operand_type := checker.module.exprs[operand].type
|
|
if checker.module.exprs[operand].kind != .Invalid && !types.is_bool(operand_type) {
|
|
id := source.add(checker.diagnostics, expr.span, "'!' requires a bool operand")
|
|
return invalid_hir_expr(checker, expr.span, id, types.BOOL)
|
|
}
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Not, span=expr.span, type=types.BOOL, left=operand,
|
|
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .And, .Or:
|
|
left := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.BOOL, pkg, file)
|
|
right := build_nested_expr(checker, expr.right, locals, global_reads, calls, types.BOOL, pkg, file)
|
|
left_type := checker.module.exprs[left].type
|
|
right_type := checker.module.exprs[right].type
|
|
left_ok := checker.module.exprs[left].kind == .Invalid || types.is_bool(left_type)
|
|
right_ok := checker.module.exprs[right].kind == .Invalid || types.is_bool(right_type)
|
|
if !left_ok || !right_ok {
|
|
id := source.add(checker.diagnostics, expr.span, "'and'/'or' require bool operands")
|
|
return invalid_hir_expr(checker, expr.span, id, types.BOOL)
|
|
}
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.And if expr.kind == .And else .Or, span=expr.span, type=types.BOOL,
|
|
left=left, right=right, target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .Eq, .Ne, .Lt, .Le, .Gt, .Ge:
|
|
// Contextualize a bare integer-literal operand to the other operand's type
|
|
// so comparisons like `count > 0` or `0 < count` type-check.
|
|
left_const := eval_constant(checker, expr.left)
|
|
right_const := eval_constant(checker, expr.right)
|
|
left, right: hir.Expr_Id
|
|
left_expr := checker.ast_module.exprs[expr.left]
|
|
right_expr := checker.ast_module.exprs[expr.right]
|
|
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 == .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)
|
|
}
|
|
left_type := checker.module.exprs[left].type
|
|
right_type := checker.module.exprs[right].type
|
|
if checker.module.exprs[left].kind == .Invalid || checker.module.exprs[right].kind == .Invalid {
|
|
return invalid_hir_expr(checker, expr.span, expr.diagnostic, types.BOOL)
|
|
}
|
|
operand_type := types.INVALID
|
|
if types.is_enum(left_type, store) || types.is_enum(right_type, store) {
|
|
if !types.equal(left_type, right_type) || (expr.kind != .Eq && expr.kind != .Ne) {
|
|
id := source.add(checker.diagnostics, expr.span, "enum values only support '==' and '!=' with the same enum type")
|
|
return invalid_hir_expr(checker, expr.span, id, types.BOOL)
|
|
}
|
|
operand_type = left_type
|
|
} else if types.is_bool(left_type) && types.is_bool(right_type) {
|
|
if expr.kind != .Eq && expr.kind != .Ne {
|
|
id := source.add(checker.diagnostics, expr.span, "bool values only support '==' and '!='")
|
|
return invalid_hir_expr(checker, expr.span, id, types.BOOL)
|
|
}
|
|
operand_type = types.BOOL
|
|
} else {
|
|
operand_type = types.widest(left_type, right_type)
|
|
if !types.is_concrete_scalar(operand_type) || types.is_bool(operand_type) {
|
|
id := source.add(checker.diagnostics, expr.span, "comparison requires compatible numeric operands")
|
|
return invalid_hir_expr(checker, expr.span, id, types.BOOL)
|
|
}
|
|
}
|
|
left = coerce_expr(checker, left, operand_type, checker.module.exprs[left].span)
|
|
right = coerce_expr(checker, right, operand_type, checker.module.exprs[right].span)
|
|
compare_kind := hir.Expr_Kind.Eq
|
|
#partial switch expr.kind {
|
|
case .Eq: compare_kind = .Eq
|
|
case .Ne: compare_kind = .Ne
|
|
case .Lt: compare_kind = .Lt
|
|
case .Le: compare_kind = .Le
|
|
case .Gt: compare_kind = .Gt
|
|
case .Ge: compare_kind = .Ge
|
|
}
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=compare_kind, span=expr.span, type=types.BOOL, left=left, right=right,
|
|
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .Struct_Literal:
|
|
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 {
|
|
struct_type = types.resolve_alias(expected, store)
|
|
if !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)
|
|
}
|
|
}
|
|
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
|
|
}
|
|
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 {
|
|
// Pointer arithmetic is only defined for `+` (many-pointer + usize).
|
|
if op == .Add &&
|
|
types.is_many_pointer(checker.module.exprs[left].type, &checker.module.types) &&
|
|
types.equal(checker.module.exprs[right].type, types.USIZE) {
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Pointer_Add, span=span, type=checker.module.exprs[left].type,
|
|
left=left, right=right, target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
result := types.widest(checker.module.exprs[left].type, checker.module.exprs[right].type)
|
|
if !types.is_concrete_scalar(result) {
|
|
id := source.add(checker.diagnostics, span, "arithmetic requires compatible numeric operands")
|
|
return invalid_hir_expr(checker, span, id)
|
|
}
|
|
result_kind := hir.Expr_Kind.Add
|
|
#partial switch op {
|
|
case .Sub: result_kind = .Sub
|
|
case .Mul: result_kind = .Mul
|
|
case .Div: result_kind = .Div
|
|
}
|
|
coerced_left := coerce_expr(checker, left, result, checker.module.exprs[left].span)
|
|
coerced_right := coerce_expr(checker, right, result, checker.module.exprs[right].span)
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=result_kind, span=span, type=result, left=coerced_left, right=coerced_right,
|
|
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
|
|
build_expr :: proc(
|
|
checker: ^Checker,
|
|
expr_id: ast.Expr_Id,
|
|
locals: []Build_Local,
|
|
global_reads: ^[dynamic]hir.Global_Id,
|
|
calls: ^[dynamic]hir.Function_Id,
|
|
expected := types.INVALID,
|
|
pkg := ast.Package_Id(0),
|
|
file := ast.File_Id(0),
|
|
) -> hir.Expr_Id {
|
|
stack := checker.build_stack
|
|
clear_dynamic_array(&stack)
|
|
defer {
|
|
for frame in stack {
|
|
delete(frame.built_args, checker.allocator)
|
|
delete(frame.arg_types, checker.allocator)
|
|
}
|
|
clear_dynamic_array(&stack)
|
|
checker.build_stack = stack
|
|
}
|
|
append(&stack, Build_Expr_Frame{expr=expr_id, expected=expected, template=ast.INVALID_FUNCTION})
|
|
last := hir.INVALID_EXPR
|
|
|
|
for len(stack) > 0 {
|
|
frame_index := len(stack)-1
|
|
frame := stack[frame_index]
|
|
if frame.expr == ast.INVALID_EXPR || int(frame.expr) >= len(checker.ast_module.exprs) {
|
|
id := source.add(checker.diagnostics, source.Span{}, "missing expression")
|
|
last = invalid_hir_expr(checker, source.Span{}, id)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
expr := checker.ast_module.exprs[frame.expr]
|
|
if frame.stage == 0 {
|
|
constant := eval_constant(checker, frame.expr)
|
|
if constant.kind == .Value || constant.kind == .Overflow || constant.kind == .Div_By_Zero {
|
|
last = build_constant_expr(checker, expr, constant, frame.expected)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
switch expr.kind {
|
|
case .String, .Array, .None, .Undefined, .Address, .Deref, .Index, .Slice,
|
|
.Field, .Unwrap, .Orelse, .Try, .Catch, .Struct_Literal, .Keyed,
|
|
.Bool, .Cast, .Comptime, .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range,
|
|
.Enum_Literal:
|
|
last = build_compound_expr(
|
|
checker, expr, locals, global_reads, calls, frame.expected, pkg, file,
|
|
)
|
|
_ = pop(&stack)
|
|
case .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, .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) &&
|
|
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 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 {
|
|
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 is_ptr_cast_call(checker, expr) {
|
|
if len(expr.args) != 2 {
|
|
id := source.addf(checker.diagnostics, expr.span, "ptr_cast 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, "ptr_cast target must be a type")
|
|
last = invalid_hir_expr(checker, expr.span, id)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
if !valid_ptr_cast_child(checker, target) {
|
|
id := source.addf(checker.diagnostics, checker.ast_module.exprs[expr.args[0]].span, "ptr_cast 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 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.function_pointer(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 global := find_global(checker, expr.name, target_pkg, expr_lookup_file(expr, file)); global != ast.INVALID_GLOBAL {
|
|
if _, _, _, callable := types.function_pointer(checker.global_types[global], &checker.module.types); callable {
|
|
callee = build_global_reference(checker, global, expr.span, global_reads)
|
|
} else {
|
|
non_callable = true
|
|
non_callable_global = true
|
|
}
|
|
}
|
|
}
|
|
if callee == hir.INVALID_EXPR {
|
|
distinct_type := types.find_named(&checker.module.types, u32(target_pkg), u32(expr.name), file=u32(expr_lookup_file(expr, file)))
|
|
distinct_item, distinct_ok := types.node(&checker.module.types, distinct_type)
|
|
if distinct_ok && distinct_item.kind == .Distinct {
|
|
if !is_runtime_type(checker, distinct_type) {
|
|
id := source.addf(
|
|
checker.diagnostics,
|
|
expr.span,
|
|
"distinct type '%s' has no concrete runtime backing type",
|
|
symbol_text(checker, expr.name),
|
|
)
|
|
last = invalid_hir_expr(checker, expr.span, id)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
if len(expr.args) != 1 {
|
|
id := source.addf(
|
|
checker.diagnostics,
|
|
expr.span,
|
|
"distinct type '%s' expects 1 argument, got %d",
|
|
symbol_text(checker, expr.name),
|
|
len(expr.args),
|
|
)
|
|
last = invalid_hir_expr(checker, expr.span, id, distinct_type)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
stack[frame_index].target_type = distinct_type
|
|
stack[frame_index].stage = 8
|
|
append(&stack, Build_Expr_Frame{
|
|
expr=expr.args[0],
|
|
expected=distinct_item.child,
|
|
template=ast.INVALID_FUNCTION,
|
|
})
|
|
continue
|
|
}
|
|
id := source.INVALID_DIAGNOSTIC
|
|
if non_callable {
|
|
id = add_call_resolution_diagnostic(checker, expr, target_pkg, file) if non_callable_global else
|
|
source.add(checker.diagnostics, expr.span, "call target is not a function pointer")
|
|
} else {
|
|
id = add_unsupported_diagnostic(checker, expr.span, target_pkg, expr.name)
|
|
if id == source.INVALID_DIAGNOSTIC {
|
|
id = add_call_resolution_diagnostic(checker, expr, target_pkg, file)
|
|
}
|
|
}
|
|
last = invalid_hir_expr(checker, expr.span, id)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
_, function_item, function_type, _ := types.function_pointer(checker.module.exprs[callee].type, &checker.module.types)
|
|
if !valid_callable_arity(function_item, len(expr.args)) {
|
|
message := "function pointer expects at least %d arguments, got %d" if function_item.variadic else
|
|
"function pointer expects %d arguments, got %d"
|
|
id := source.addf(checker.diagnostics, expr.span, message, function_item.field_count, len(expr.args))
|
|
last = invalid_hir_expr(checker, expr.span, id)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
stack[frame_index].left = callee
|
|
stack[frame_index].built_args = make([]hir.Expr_Id, len(expr.args), checker.allocator)
|
|
stack[frame_index].stage = 7
|
|
if len(expr.args) > 0 {
|
|
arg_expected := callable_arg_expected(function_type, function_item, &checker.module.types, 0)
|
|
if !is_runtime_type(checker, arg_expected) {
|
|
arg_expected = types.INVALID
|
|
}
|
|
append(&stack, Build_Expr_Frame{expr=expr.args[0], expected=arg_expected, template=ast.INVALID_FUNCTION})
|
|
}
|
|
continue
|
|
}
|
|
if len(checker.ast_module.functions[template].unsupported_reason) > 0 {
|
|
id := source.addf(
|
|
checker.diagnostics,
|
|
expr.span,
|
|
"C declaration '%s' is unavailable: %s",
|
|
symbol_text(checker, expr.name),
|
|
checker.ast_module.functions[template].unsupported_reason,
|
|
)
|
|
last = invalid_hir_expr(checker, expr.span, id)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
if checker.template_diagnostics[template] != source.INVALID_DIAGNOSTIC {
|
|
last = invalid_hir_expr(checker, expr.span, checker.template_diagnostics[template])
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
function := checker.ast_module.functions[template]
|
|
if 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
|
|
}
|
|
mode, prefix := call_argument_mode(function, len(expr.args))
|
|
if mode == .Invalid {
|
|
message := "function '%s' expects at least %d arguments, got %d" if function.variadic else
|
|
"function '%s' expects %d arguments, got %d"
|
|
if inferred_prefix, inferable := comptime_prefix_count(function); inferable && !function.c_abi && !function.variadic {
|
|
id := source.addf(
|
|
checker.diagnostics, expr.span,
|
|
"function '%s' expects %d arguments with explicit comptime parameters or %d with inferred comptime parameters, got %d",
|
|
symbol_text(checker, expr.name), len(function.params), len(function.params)-inferred_prefix, len(expr.args),
|
|
)
|
|
last = invalid_hir_expr(checker, expr.span, id)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
id := source.addf(
|
|
checker.diagnostics,
|
|
expr.span,
|
|
message,
|
|
symbol_text(checker, expr.name),
|
|
len(function.params),
|
|
len(expr.args),
|
|
)
|
|
last = invalid_hir_expr(checker, expr.span, id)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
stack[frame_index].template = template
|
|
stack[frame_index].arg_mode = mode
|
|
stack[frame_index].prefix = prefix
|
|
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, mode, prefix, 0, len(expr.args))
|
|
stack[frame_index].stage = 3
|
|
if stack[frame_index].arg_index < len(expr.args) {
|
|
param_index := call_param_index(mode, prefix, 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, 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
|
|
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.arg_mode, frame.prefix, 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.arg_mode, frame.prefix,
|
|
frame.arg_index+1, len(expr.args),
|
|
)
|
|
stack[frame_index].arg_index = next
|
|
if next < len(expr.args) {
|
|
next_param := call_param_index(frame.arg_mode, frame.prefix, 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, Build_Expr_Frame{expr=expr.args[next], expected=arg_expected, template=ast.INVALID_FUNCTION})
|
|
continue
|
|
}
|
|
}
|
|
function := checker.ast_module.functions[frame.template]
|
|
comptime_values: []Comptime_Value
|
|
comptime_ok := false
|
|
if frame.arg_mode == .Inferred {
|
|
comptime_values, comptime_ok = infer_call_comptime_values(
|
|
checker, function, frame.prefix, expr.args, stack[frame_index].arg_types,
|
|
frame.expected, pkg, file, diagnose=true,
|
|
)
|
|
} else {
|
|
comptime_values, comptime_ok = collect_comptime_values(checker, function, expr.args, 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.arg_mode, frame.prefix, 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
|
|
}
|
|
spec := INVALID_SPEC
|
|
if comptime_ok {
|
|
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
|
|
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
|
|
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 := param_index if frame.arg_mode == .Explicit else param_index-frame.prefix
|
|
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
|
|
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
|
|
_, function_item, function_type, ok := types.function_pointer(checker.module.exprs[callee].type, &checker.module.types)
|
|
if !ok {
|
|
id := source.add(checker.diagnostics, expr.span, "call target is not a function pointer")
|
|
last = invalid_hir_expr(checker, expr.span, id)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
if !valid_callable_arity(function_item, len(expr.args)) {
|
|
message := "function pointer expects at least %d arguments, got %d" if function_item.variadic else
|
|
"function pointer expects %d arguments, got %d"
|
|
id := source.addf(checker.diagnostics, expr.span, message, function_item.field_count, len(expr.args))
|
|
last = invalid_hir_expr(checker, expr.span, id)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
stack[frame_index].left = callee
|
|
stack[frame_index].built_args = make([]hir.Expr_Id, len(expr.args), checker.allocator)
|
|
stack[frame_index].stage = 7
|
|
if len(expr.args) > 0 {
|
|
arg_expected := callable_arg_expected(function_type, function_item, &checker.module.types, 0)
|
|
if !is_runtime_type(checker, arg_expected) {
|
|
arg_expected = types.INVALID
|
|
}
|
|
append(&stack, Build_Expr_Frame{expr=expr.args[0], expected=arg_expected, template=ast.INVALID_FUNCTION})
|
|
}
|
|
continue
|
|
}
|
|
if frame.stage == 7 {
|
|
if frame.arg_index < len(expr.args) {
|
|
stack[frame_index].built_args[frame.arg_index] = last
|
|
stack[frame_index].arg_index += 1
|
|
if frame.arg_index+1 < len(expr.args) {
|
|
next := frame.arg_index+1
|
|
callee_type := checker.module.exprs[frame.left].type
|
|
_, function_item, function_type, _ := types.function_pointer(callee_type, &checker.module.types)
|
|
arg_expected := callable_arg_expected(function_type, function_item, &checker.module.types, next)
|
|
if !is_runtime_type(checker, arg_expected) {
|
|
arg_expected = types.INVALID
|
|
}
|
|
append(&stack, Build_Expr_Frame{expr=expr.args[next], expected=arg_expected, template=ast.INVALID_FUNCTION})
|
|
continue
|
|
}
|
|
}
|
|
callee_type := checker.module.exprs[frame.left].type
|
|
_, function_item, function_type, ok := types.function_pointer(callee_type, &checker.module.types)
|
|
if !ok {
|
|
id := source.add(checker.diagnostics, expr.span, "call target is not a function pointer")
|
|
delete(stack[frame_index].built_args, checker.allocator)
|
|
stack[frame_index].built_args = nil
|
|
last = invalid_hir_expr(checker, expr.span, id)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
fixed_count := int(function_item.field_count)
|
|
for index in 0..<min(fixed_count, len(stack[frame_index].built_args)) {
|
|
expected_arg := callable_arg_expected(function_type, function_item, &checker.module.types, index)
|
|
stack[frame_index].built_args[index] = coerce_expr(
|
|
checker,
|
|
stack[frame_index].built_args[index],
|
|
expected_arg,
|
|
checker.module.exprs[stack[frame_index].built_args[index]].span,
|
|
)
|
|
}
|
|
for index in fixed_count..<len(stack[frame_index].built_args) {
|
|
arg := stack[frame_index].built_args[index]
|
|
stack[frame_index].built_args[index] = promote_c_vararg_expr(
|
|
checker,
|
|
arg,
|
|
checker.module.exprs[arg].span,
|
|
)
|
|
}
|
|
result := function_item.child
|
|
if !types.is_valid(result) {
|
|
id := source.add(checker.diagnostics, expr.span, "could not resolve function pointer result type")
|
|
delete(stack[frame_index].built_args, checker.allocator)
|
|
stack[frame_index].built_args = nil
|
|
last = invalid_hir_expr(checker, expr.span, id)
|
|
} else {
|
|
last = add_hir_expr(checker, hir.Expr{
|
|
kind=.Call, span=expr.span, type=result, target=hir.INVALID_REF,
|
|
left=frame.left, right=hir.INVALID_EXPR, args=stack[frame_index].built_args,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
stack[frame_index].built_args = nil
|
|
}
|
|
_ = pop(&stack)
|
|
}
|
|
if frame.stage == 8 {
|
|
distinct_item, ok := types.node(&checker.module.types, frame.target_type)
|
|
actual := checker.module.exprs[last].type
|
|
if !ok || distinct_item.kind != .Distinct || !types.equal(actual, distinct_item.child) {
|
|
id := source.addf(
|
|
checker.diagnostics,
|
|
expr.span,
|
|
"distinct type '%s' requires an exact %s value, got %s",
|
|
symbol_text(checker, expr.name),
|
|
types.name(distinct_item.child),
|
|
types.name(actual),
|
|
)
|
|
last = invalid_hir_expr(checker, expr.span, id, frame.target_type)
|
|
} else {
|
|
last = add_hir_expr(checker, hir.Expr{
|
|
kind=.Retype,
|
|
span=expr.span,
|
|
type=frame.target_type,
|
|
left=last,
|
|
target=hir.INVALID_REF,
|
|
right=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
_ = pop(&stack)
|
|
}
|
|
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, "ptr_cast 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)
|
|
}
|
|
}
|
|
return last
|
|
}
|
|
|
|
make_link_name :: proc(checker: ^Checker, id: Spec_Id) -> string {
|
|
spec := checker.specs[id]
|
|
function := checker.ast_module.functions[spec.template]
|
|
if function.pkg == 0 && function.name == checker.main_symbol {
|
|
return fmt.aprintf("main", allocator = checker.allocator)
|
|
}
|
|
if function.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)
|
|
if function.file_hidden {
|
|
fmt.sbprintf(&builder, "f%d__", function.file)
|
|
}
|
|
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 {
|
|
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 the deferred statements in frames `[lo, len(defers))` into `body`,
|
|
// innermost-most-recent first (LIFO across frames); each frame's own statements
|
|
// keep their forward order. Used at every scope-exit path in `build_block`.
|
|
flush_defers :: proc(ctx: ^Build_Ctx, body: ^[dynamic]hir.Stmt_Id, lo: int) {
|
|
for i := len(ctx.defers^) - 1; i >= lo; i -= 1 {
|
|
for stmt_id in ctx.defers^[i] {
|
|
append(body, stmt_id)
|
|
}
|
|
}
|
|
}
|
|
|
|
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]
|
|
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 = checker.module.exprs[value].type
|
|
} else if types.is_void(declared) {
|
|
id := source.add(checker.diagnostics, statement.span, "locals cannot have type void")
|
|
value = invalid_hir_expr(checker, statement.span, id)
|
|
value_type = types.INVALID
|
|
}
|
|
}
|
|
if _, found := find_build_local(ctx.locals^[duplicate_start:], statement.name); found {
|
|
id := source.addf(
|
|
checker.diagnostics, statement.span,
|
|
"duplicate local '%s'", symbol_text(checker, statement.name),
|
|
)
|
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
|
|
local = hir.INVALID_LOCAL, diagnostic = id,
|
|
})
|
|
ctx.problematic^ = true
|
|
continue
|
|
}
|
|
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,
|
|
)
|
|
target_type := checker.module.exprs[target_expr].type
|
|
if !hir_location_writable(checker, target_expr, ctx.locals^[:]) {
|
|
id := source.add(checker.diagnostics, statement.span, "assignment target is not writable")
|
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind=.Trap, span=statement.span, local=hir.INVALID_LOCAL,
|
|
target=hir.INVALID_EXPR, expr=hir.INVALID_EXPR, diagnostic=id,
|
|
})
|
|
ctx.problematic^ = true
|
|
continue
|
|
}
|
|
value: hir.Expr_Id
|
|
assignment_op := hir.Assignment_Op.Set
|
|
if statement.assignment_op != .Set {
|
|
rhs_expected := target_type
|
|
if types.is_many_pointer(target_type, &checker.module.types) {
|
|
rhs_expected = types.USIZE if statement.assignment_op == .Add else types.INVALID
|
|
}
|
|
value = build_expr(
|
|
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
|
rhs_expected, ctx.pkg, ctx.file,
|
|
)
|
|
if types.is_many_pointer(target_type, &checker.module.types) {
|
|
assignment_op = .Pointer_Add
|
|
if statement.assignment_op != .Add {
|
|
id := source.add(
|
|
checker.diagnostics,
|
|
statement.span,
|
|
"many-item pointers only support '+=' compound assignment",
|
|
)
|
|
value = invalid_hir_expr(checker, statement.span, id, types.USIZE)
|
|
} else {
|
|
value = coerce_expr(checker, value, types.USIZE, statement.span)
|
|
}
|
|
} else {
|
|
#partial switch statement.assignment_op {
|
|
case .Add: assignment_op = .Add
|
|
case .Sub: assignment_op = .Sub
|
|
case .Mul: assignment_op = .Mul
|
|
case .Div: assignment_op = .Div
|
|
}
|
|
rhs_type := checker.module.exprs[value].type
|
|
result_type := types.widest(target_type, rhs_type)
|
|
if !types.is_concrete_scalar(result_type) ||
|
|
types.is_bool(result_type) {
|
|
id := source.add(
|
|
checker.diagnostics,
|
|
statement.span,
|
|
"arithmetic requires compatible numeric operands",
|
|
)
|
|
value = invalid_hir_expr(checker, statement.span, id, target_type)
|
|
} else {
|
|
// Compound assignment stores back into the original
|
|
// target type, so only an equal or widening RHS
|
|
// conversion is permitted.
|
|
value = coerce_expr(checker, value, target_type, statement.span)
|
|
}
|
|
}
|
|
} else 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)
|
|
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, "'return _' is only valid in a void function")
|
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
|
|
local = hir.INVALID_LOCAL, diagnostic = id,
|
|
})
|
|
ctx.problematic^ = true
|
|
} else {
|
|
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
|
|
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)
|
|
error_path := false
|
|
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_path = 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_path = 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
|
|
if can_implicitly_convert_type(checker, probe_type, error_type) &&
|
|
!can_implicitly_convert_type(checker, probe_type, success) {
|
|
error_path = true
|
|
value = probe
|
|
} else if can_implicitly_convert_type(checker, probe_type, success) {
|
|
value = probe
|
|
}
|
|
}
|
|
if value == hir.INVALID_EXPR {
|
|
expected := error_type if error_path 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_path else success
|
|
value = coerce_expr(checker, value, expected, statement.span)
|
|
value = fallible_aggregate(checker, statement.span, ctx.result, value, error_path)
|
|
} 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 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)
|
|
}
|
|
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 !types.is_void(checker.module.exprs[value].type) {
|
|
id := source.add(checker.diagnostics, statement.span, "non-void expression result must be consumed or assigned to '_'")
|
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
|
|
local = hir.INVALID_LOCAL, diagnostic = id,
|
|
})
|
|
ctx.problematic^ = true
|
|
} else {
|
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind = .Expression, span = statement.span, expr = value,
|
|
local = hir.INVALID_LOCAL, diagnostic = source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
case .If:
|
|
if len(statement.captures) > 0 {
|
|
ast_operands: [dynamic]ast.Expr_Id
|
|
ast_operands.allocator = checker.allocator
|
|
flatten_conditional_unwrap_operands(checker.ast_module, statement.expr, &ast_operands)
|
|
|
|
valid_unwrap := true
|
|
diagnostic := source.INVALID_DIAGNOSTIC
|
|
if len(ast_operands) != len(statement.captures) {
|
|
diagnostic = source.addf(
|
|
checker.diagnostics,
|
|
statement.span,
|
|
"'if' unwrap has %d operands but %d captures",
|
|
len(ast_operands),
|
|
len(statement.captures),
|
|
)
|
|
valid_unwrap = false
|
|
}
|
|
|
|
values := make([]hir.Expr_Id, len(ast_operands), checker.allocator)
|
|
child_types := make([]types.Type, len(ast_operands), checker.allocator)
|
|
for operand, index in ast_operands {
|
|
child_types[index] = types.INVALID
|
|
value := build_expr(
|
|
checker, operand, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
|
types.INVALID, ctx.pkg, ctx.file,
|
|
)
|
|
values[index] = value
|
|
value_type := checker.module.exprs[value].type
|
|
if checker.module.exprs[value].kind == .Invalid {
|
|
valid_unwrap = false
|
|
if diagnostic == source.INVALID_DIAGNOSTIC {
|
|
diagnostic = checker.module.exprs[value].diagnostic
|
|
}
|
|
} else if !types.is_optional(value_type, &checker.module.types) {
|
|
diagnostic = source.addf(
|
|
checker.diagnostics,
|
|
checker.ast_module.exprs[operand].span,
|
|
"'if' unwrap requires an optional value (operand %d)",
|
|
index + 1,
|
|
)
|
|
valid_unwrap = false
|
|
} else {
|
|
child_types[index] = types.child_type(value_type, &checker.module.types)
|
|
}
|
|
}
|
|
|
|
capture_start := len(ctx.locals^)
|
|
unwraps: [dynamic]hir.Conditional_Unwrap
|
|
unwraps.allocator = checker.allocator
|
|
for capture, index in statement.captures {
|
|
child := child_types[index] if index < len(child_types) else types.INVALID
|
|
local := hir.INVALID_LOCAL
|
|
if capture != checker.sink_symbol {
|
|
if _, duplicate := find_build_local(ctx.locals^[capture_start:], capture); duplicate {
|
|
diagnostic = source.add(
|
|
checker.diagnostics,
|
|
statement.span,
|
|
"'if' unwrap captures must have distinct names",
|
|
)
|
|
valid_unwrap = false
|
|
} 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) {
|
|
diagnostic = source.add(
|
|
checker.diagnostics,
|
|
checker.ast_module.exprs[statement.guard].span,
|
|
"'if' unwrap guard must be a bool",
|
|
)
|
|
valid_unwrap = false
|
|
}
|
|
}
|
|
|
|
then_body := build_block(ctx, statement.body, capture_start)
|
|
resize(ctx.locals, capture_start)
|
|
else_body: []hir.Stmt_Id = nil
|
|
if statement.else_body != nil {
|
|
else_body = build_block(ctx, statement.else_body)
|
|
}
|
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
if valid_unwrap {
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind=.If,
|
|
span=statement.span,
|
|
expr=hir.INVALID_EXPR,
|
|
unwraps=unwraps[:],
|
|
guard=guard,
|
|
then_body=then_body,
|
|
else_body=else_body,
|
|
local=hir.INVALID_LOCAL,
|
|
target=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
} else {
|
|
delete(unwraps)
|
|
delete(then_body, checker.allocator)
|
|
delete(else_body, checker.allocator)
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind=.Trap,
|
|
span=statement.span,
|
|
expr=hir.INVALID_EXPR,
|
|
guard=hir.INVALID_EXPR,
|
|
local=hir.INVALID_LOCAL,
|
|
target=hir.INVALID_EXPR,
|
|
diagnostic=diagnostic,
|
|
})
|
|
ctx.problematic^ = true
|
|
}
|
|
delete(values, checker.allocator)
|
|
delete(child_types, checker.allocator)
|
|
delete(ast_operands)
|
|
continue
|
|
}
|
|
condition := build_expr(checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, types.BOOL, ctx.pkg, ctx.file)
|
|
if checker.module.exprs[condition].kind != .Invalid && !types.is_bool(checker.module.exprs[condition].type) {
|
|
id := source.add(checker.diagnostics, statement.span, "'if' condition must be a bool")
|
|
condition = invalid_hir_expr(checker, statement.span, id, types.BOOL)
|
|
ctx.problematic^ = true
|
|
}
|
|
then_body := build_block(ctx, statement.body)
|
|
else_body: []hir.Stmt_Id = nil
|
|
if statement.else_body != nil {
|
|
else_body = build_block(ctx, statement.else_body)
|
|
}
|
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
append(&checker.module.statements, hir.Stmt{
|
|
kind = .If, span = statement.span, expr = condition,
|
|
guard = hir.INVALID_EXPR,
|
|
then_body = then_body, else_body = else_body,
|
|
local = hir.INVALID_LOCAL, target = hir.INVALID_EXPR,
|
|
diagnostic = source.INVALID_DIAGNOSTIC,
|
|
})
|
|
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[condition].kind == .Invalid
|
|
case .While:
|
|
condition := build_expr(
|
|
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
|
types.BOOL, ctx.pkg, ctx.file,
|
|
)
|
|
if checker.module.exprs[condition].kind != .Invalid &&
|
|
!types.is_bool(checker.module.exprs[condition].type) {
|
|
id := source.add(checker.diagnostics, statement.span, "'while' condition must be a bool")
|
|
condition = invalid_hir_expr(checker, statement.span, id, types.BOOL)
|
|
ctx.problematic^ = true
|
|
}
|
|
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:
|
|
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 {
|
|
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)
|
|
}
|
|
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
|
|
}
|
|
// 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.
|
|
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
|
|
append(ctx.defers, entry)
|
|
case .Match:
|
|
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], 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, 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 && 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 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], 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) {
|
|
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) {
|
|
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)
|
|
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,
|
|
})
|
|
|
|
// 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
|
|
|
|
for arm_id in statement.body {
|
|
arm := checker.ast_module.statements[arm_id]
|
|
if arm.kind != .Match_Arm {
|
|
ok = false
|
|
continue
|
|
}
|
|
if has_else {
|
|
source.add(checker.diagnostics, arm.span, "arms after 'else' are unreachable")
|
|
ok = false
|
|
}
|
|
is_else := len(arm.patterns) == 0
|
|
condition := hir.INVALID_EXPR
|
|
field_index := -1
|
|
payload_type := types.INVALID
|
|
has_capture := len(arm.captures) > 0
|
|
|
|
if is_else {
|
|
if has_capture {
|
|
source.add(checker.diagnostics, arm.span, "the 'else' arm cannot capture a payload")
|
|
ok = false
|
|
}
|
|
has_else = true
|
|
} 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)
|
|
}
|
|
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, as_value, slot, slot_type, span)
|
|
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,
|
|
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^)
|
|
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)
|
|
return result[:], body_ok
|
|
}
|
|
|
|
// build_value_arm appends a value-match arm's slot assignment(s) to `out`: a single bare
|
|
// expression yields implicitly; anything else reuses the value-branch rule (trailing
|
|
// `yield`, or exit on every path).
|
|
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 {
|
|
checker := ctx.checker
|
|
if len(arm_body) == 1 && checker.ast_module.statements[arm_body[0]].kind == .Expression {
|
|
expr_stmt := checker.ast_module.statements[arm_body[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
|
|
}
|
|
vtype := checker.module.exprs[value].type
|
|
value = adopt_value_slot(ctx, slot, slot_type, value, vtype, span)
|
|
emit_slot_assign(checker, out, slot^, value, span)
|
|
return true
|
|
}
|
|
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 := 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_none reports whether any `yield` that targets this loop (a labeled
|
|
// `yield :blk` inside `if`/block branches, or the trailing fall-through) yields the
|
|
// literal `none` — 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_none :: 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 == .None {
|
|
return true
|
|
}
|
|
case .If:
|
|
if loop_yields_none(checker, s.body) || loop_yields_none(checker, s.else_body) {
|
|
return true
|
|
}
|
|
case .Block:
|
|
if loop_yields_none(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 `none`) and coerces `value` into it.
|
|
// Returns INVALID when the type can't be fixed yet (a `none`/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 `none`, descending into `if`/block branches but not nested loops or value
|
|
// sources (whose yields belong to them). INVALID when the loop yields only `none`.
|
|
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 != .None {
|
|
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-`none`) yield, so a `none` 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 `none`.
|
|
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], 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 `none`).
|
|
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_none(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 `none`: pre-type the element from the first
|
|
// concrete yield (regardless of source order) so a `none` 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 `none`).
|
|
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_none(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 `none`: pre-type the element from the first
|
|
// concrete yield (regardless of source order) so a `none` 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)
|
|
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
|
|
}
|
|
|
|
// Reports whether every control-flow path through `stmts` terminates (returns or traps),
|
|
// so the end of the block is unreachable. A `.Return` or `.Trap` terminates outright; an
|
|
// `.If` terminates only when it has an `else` and both arms terminate. A literal
|
|
// `while true` cannot fall through because the language has no `break` statement.
|
|
// Recursion into the branch slices handles nested ifs and `else if` chains.
|
|
all_paths_return :: proc(module: ^hir.Module, stmts: []hir.Stmt_Id) -> bool {
|
|
for id in stmts {
|
|
statement := module.statements[id]
|
|
#partial switch statement.kind {
|
|
case .Return, .Trap:
|
|
return true
|
|
case .If:
|
|
if statement.else_body != nil &&
|
|
all_paths_return(module, statement.then_body) &&
|
|
all_paths_return(module, statement.else_body) {
|
|
return true
|
|
}
|
|
case .While:
|
|
// 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 false
|
|
}
|
|
|
|
// 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]
|
|
#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
|
|
if !types.is_void(spec.result) && !is_runtime_type(checker, spec.result) {
|
|
checker.specs[id].result = types.I64
|
|
spec.result = types.I64
|
|
signature_diagnostic = source.addf(
|
|
checker.diagnostics,
|
|
function.span,
|
|
"could not resolve a concrete result type for '%s'",
|
|
symbol_text(checker, function.name),
|
|
)
|
|
}
|
|
for arg in spec.args {
|
|
if !is_runtime_type(checker, arg) {
|
|
signature_diagnostic = source.addf(
|
|
checker.diagnostics,
|
|
function.span,
|
|
"could not resolve a concrete parameter type for '%s'",
|
|
symbol_text(checker, function.name),
|
|
)
|
|
break
|
|
}
|
|
}
|
|
assert(spec.hir_id == hir.function_id(len(checker.module.functions)))
|
|
|
|
locals: [dynamic]Build_Local
|
|
locals.allocator = checker.allocator
|
|
hir_locals: [dynamic]hir.Local
|
|
hir_locals.allocator = checker.allocator
|
|
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,
|
|
)
|
|
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
|
|
if !function.has_body {
|
|
assert(spec.hir_id == hir.function_id(len(checker.module.functions)))
|
|
append(
|
|
&checker.module.functions,
|
|
hir.Function {
|
|
name = function.name,
|
|
link_name = make_link_name(checker, id),
|
|
calling_convention = .C if function.c_abi else .Brolang,
|
|
implementation = .Declaration,
|
|
linkage = .External if function.c_abi else .Internal,
|
|
is_main = function.pkg == 0 && function.name == checker.main_symbol,
|
|
variadic = function.variadic,
|
|
params = params[:],
|
|
result = spec.result,
|
|
locals = hir_locals[:],
|
|
body = body[:],
|
|
direct_global_reads = global_reads,
|
|
calls = calls[:],
|
|
problematic = problematic,
|
|
diagnostic = checker.template_diagnostics[spec.template],
|
|
},
|
|
)
|
|
delete(locals)
|
|
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][]hir.Stmt_Id
|
|
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
|
|
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,
|
|
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)
|
|
for block_stmt in block {
|
|
append(&body, block_stmt)
|
|
}
|
|
delete(block, checker.allocator)
|
|
|
|
if !types.is_void(spec.result) && !returns {
|
|
id := source.addf(
|
|
checker.diagnostics,
|
|
function.span,
|
|
"function '%s' does not return a value",
|
|
symbol_text(checker, function.name),
|
|
)
|
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
append(
|
|
&checker.module.statements,
|
|
hir.Stmt{kind = .Trap, span = function.span, expr = hir.INVALID_EXPR, local = hir.INVALID_LOCAL, diagnostic = id},
|
|
)
|
|
problematic = true
|
|
}
|
|
|
|
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 || (function.pkg == 0 && function.name == checker.main_symbol) else .Brolang,
|
|
implementation = .Definition,
|
|
linkage = .External if function.c_abi || (function.pkg == 0 && function.name == checker.main_symbol) else .Internal,
|
|
is_main = function.pkg == 0 && function.name == checker.main_symbol,
|
|
variadic = function.variadic,
|
|
params = params[:],
|
|
result = spec.result,
|
|
locals = hir_locals[:],
|
|
body = body[:],
|
|
direct_global_reads = global_reads,
|
|
calls = calls[:],
|
|
problematic = problematic,
|
|
diagnostic = source.INVALID_DIAGNOSTIC,
|
|
},
|
|
)
|
|
for entry in defers {
|
|
delete(entry, checker.allocator)
|
|
}
|
|
delete(defers)
|
|
delete(loop_defer_starts)
|
|
delete(loop_labels)
|
|
delete(loop_is_loop)
|
|
delete(yield_targets)
|
|
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.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) &&
|
|
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 "exceeds signed i64 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 := build_expr(checker, global.expr, nil, &dependencies, &calls, expected, global.pkg, global.file)
|
|
global_type := checker.global_types[global_index]
|
|
if is_runtime_type(checker, declared) {
|
|
expr = coerce_expr(checker, expr, declared, global.span)
|
|
global_type = checker.module.exprs[expr].type
|
|
} else if is_runtime_type(checker, checker.module.exprs[expr].type) {
|
|
global_type = checker.module.exprs[expr].type
|
|
}
|
|
diagnostic := source.INVALID_DIAGNOSTIC
|
|
if !global.immutable && is_undefined_expr(checker, global.expr) {
|
|
diagnostic = source.add(
|
|
checker.diagnostics,
|
|
global.span,
|
|
"'undefined' is only valid as a mutable local declaration initializer",
|
|
)
|
|
if !is_runtime_type(checker, global_type) {
|
|
global_type = types.I64
|
|
}
|
|
expr = invalid_hir_expr(checker, global.span, diagnostic, global_type)
|
|
}
|
|
if diagnostic == source.INVALID_DIAGNOSTIC && !is_runtime_type(checker, global_type) {
|
|
diagnostic = source.addf(
|
|
checker.diagnostics,
|
|
global.span,
|
|
"could not resolve a concrete type for global '%s'",
|
|
symbol_text(checker, global.name),
|
|
)
|
|
global_type = types.I64
|
|
expr = invalid_hir_expr(checker, global.span, diagnostic, global_type)
|
|
}
|
|
if global.type == types.VOID {
|
|
diagnostic = source.add(
|
|
checker.diagnostics,
|
|
global.span,
|
|
"void is only valid as a function result type",
|
|
)
|
|
expr = invalid_hir_expr(checker, global.span, diagnostic)
|
|
}
|
|
if !global.immutable {
|
|
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"),
|
|
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.ast_expr_stack.allocator = allocator
|
|
checker.hir_expr_stack.allocator = allocator
|
|
checker.infer_stack.allocator = allocator
|
|
checker.build_stack.allocator = allocator
|
|
checker.cycle_stack.allocator = allocator
|
|
checker.anon_globals.allocator = allocator
|
|
checker.type_factories.allocator = allocator
|
|
checker.generated_types.allocator = allocator
|
|
checker.type_factory_origins.allocator = allocator
|
|
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.external_global_canonical = make([]ast.Global_Id, len(ast_module.globals), allocator)
|
|
checker.external_global_diagnostics = make([]source.Diagnostic_Id, len(ast_module.globals), allocator)
|
|
for &canonical in checker.external_global_canonical {
|
|
canonical = ast.INVALID_GLOBAL
|
|
}
|
|
for &diagnostic in checker.external_global_diagnostics {
|
|
diagnostic = source.INVALID_DIAGNOSTIC
|
|
}
|
|
checker.constants = make([]Constant, len(ast_module.exprs), allocator)
|
|
checker.template_diagnostics = make([]source.Diagnostic_Id, len(ast_module.functions), allocator)
|
|
for &diagnostic in checker.template_diagnostics {
|
|
diagnostic = source.INVALID_DIAGNOSTIC
|
|
}
|
|
defer {
|
|
for spec in checker.specs {
|
|
delete(spec.args, allocator)
|
|
delete(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.external_global_canonical, allocator)
|
|
delete(checker.external_global_diagnostics, allocator)
|
|
delete(checker.constants, allocator)
|
|
delete(checker.template_diagnostics, allocator)
|
|
delete(checker.constant_stack)
|
|
delete(checker.ast_expr_stack)
|
|
delete(checker.hir_expr_stack)
|
|
delete(checker.infer_stack)
|
|
delete(checker.build_stack)
|
|
delete(checker.cycle_stack)
|
|
for entry in checker.type_factories {
|
|
delete(entry.values, allocator)
|
|
}
|
|
for entry in checker.generated_types {
|
|
delete(entry.values, allocator)
|
|
}
|
|
for origin in checker.type_factory_origins {
|
|
delete(origin.values, allocator)
|
|
}
|
|
delete(checker.type_factories)
|
|
delete(checker.generated_types)
|
|
delete(checker.type_factory_origins)
|
|
}
|
|
|
|
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 &&
|
|
declarations_conflict(function.file, function.file_hidden, previous.file, previous.file_hidden) {
|
|
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 &&
|
|
declarations_conflict(function.file, function.file_hidden, global.file, global.file_hidden) {
|
|
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, function.file, function.file_hidden) {
|
|
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 &&
|
|
declarations_conflict(global.file, global.file_hidden, previous.file, previous.file_hidden) {
|
|
source.addf(diagnostics, global.span, "duplicate global '%s'", symbol_text(&checker, global.name))
|
|
}
|
|
}
|
|
if type_declaration_conflicts(&checker, global.pkg, global.name, global.file, global.file_hidden) {
|
|
source.addf(diagnostics, global.span, "global '%s' shadows visible type", symbol_text(&checker, global.name))
|
|
}
|
|
}
|
|
|
|
validate_type_nodes(&checker)
|
|
validate_declarations(&checker)
|
|
infer_all(&checker)
|
|
validate_external_globals(&checker)
|
|
prune_specs(&checker)
|
|
build_globals(&checker)
|
|
for index := 0; index < len(checker.specs); index += 1 {
|
|
build_function(&checker, spec_id(index))
|
|
}
|
|
// 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.pkg == 0 && function.name == checker.main_symbol {
|
|
main_declarations += 1
|
|
}
|
|
}
|
|
if main_declarations == 0 {
|
|
synthesize_trap_main(&checker)
|
|
} else {
|
|
template := ast_module.functions[main_template]
|
|
if main_declarations != 1 ||
|
|
!template.has_body ||
|
|
len(template.params) != 0 ||
|
|
!(template.result == types.VOID || template.result == types.I32 || template.result == types.INT) {
|
|
id := checker.template_diagnostics[main_template]
|
|
if id == source.INVALID_DIAGNOSTIC {
|
|
id = source.add(
|
|
diagnostics,
|
|
template.span,
|
|
"main must be unique, have a body, take no parameters, and return void, i32, or int",
|
|
)
|
|
}
|
|
replace_main_with_trap(&checker, id)
|
|
}
|
|
}
|
|
|
|
propagate_problems(&checker)
|
|
states := make([]u8, len(checker.module.globals), allocator)
|
|
for index in 0 ..< len(checker.module.globals) {
|
|
detect_global_cycles_visit(&checker, hir.global_id(index), states)
|
|
}
|
|
delete(states, allocator)
|
|
propagate_problems(&checker)
|
|
for import_item in ast_module.imports {
|
|
if import_item.valid && !import_item.used {
|
|
source.addf_warning(diagnostics, import_item.span, "unused import '%s'", symbol_text(&checker, import_item.alias))
|
|
}
|
|
}
|
|
return checker.module
|
|
}
|