Files
honey/source/sema/check/check.hon
T
hl-valdemar cc39c96fc0 feat: materialize address-free compile-time untagged unions
Snapshot full union storage rather than choosing an overlapping field. Preserve structural union source syntax independently of nominal identity so direct literal arguments materialize and share normalized specialization keys. Reject uninitialized bytes and address-bearing member layouts.
2026-10-30 16:35:11 +01:00

5181 lines
235 KiB
Plaintext

import "@std/debug"
import "@std/mem"
import "@std/arraylist"
import "@std/hashmap"
import "@std/strconv"
import "@std/enummap"
import "@source/ast"
import "@source/diagnostics"
sema :: import "@source/sema"
TypeKind :: enum {
none
invalid
int
float
bool
string
void
function
range
array
pointer
record
enumeration
type_value
u8, u16, u32, u64, usize
i8, i16, i32, isize
slice
optional
null
anyopaque
fallible
}
RecordId :: distinct u32
no_record :: maxval!(RecordId)
EnumId :: distinct u32
no_enum :: maxval!(EnumId)
EnumMember :: struct {
name ast.NodeId
magnitude u64
negative bool = false
}
EnumType :: struct {
declaration ast.NodeId
backing TypeKind
explicit_backing bool = false
members []EnumMember
composed bool = false
}
RecordField :: struct {
name ?ast.NodeId
type Type
default ?ast.NodeId
}
RecordType :: struct {
declaration ast.NodeId
# union syntax remains available even when structural checking erases nominal identity.
source ast.NodeId = ast.NodeId(maxval!(u32))
fields []RecordField
is_union bool = false
tag_type Type = Type{}
declared_tag_type Type = Type{}
composed bool = false
}
@hide:file
RecordInitializer :: struct {
fields []mut ast.NodeId
}
ArrayId :: distinct u32
no_array :: maxval!(ArrayId)
ArrayType :: struct {
element Type
len usize = 0
mutable bool = false
kind TypeKind = .array
sentinel ?i64 = null
error_type Type = Type{}
}
PointerId :: distinct u32
no_pointer :: maxval!(PointerId)
PointerType :: struct {
element Type
mutable bool
many bool
}
Type :: struct {
kind TypeKind = .none
function ast.NodeId = ast.NodeId(maxval!(u32))
diagnostic diagnostics.DiagnosticId = diagnostics.no_diagnostic
array_type ArrayId = no_array
pointer_type PointerId = no_pointer
range_element TypeKind = .none
record_type RecordId = no_record
enum_type EnumId = no_enum
nominal ast.NodeId = ast.NodeId(maxval!(u32))
value_kind TypeKind = .none
}
@hide:file
Storage :: struct {
tree @ast.Tree
types []mut Type
arrays arraylist.ArrayList(ArrayType)
pointers arraylist.ArrayList(PointerType)
records arraylist.ArrayList(RecordType)
enums arraylist.ArrayList(EnumType)
backings []mut Type
exhaustive []mut bool
variant_slots []mut usize
initializers arraylist.ArrayList(RecordInitializer)
slots []mut usize
fallthroughs hashmap.AutoHashMap(ast.NodeId, diagnostics.DiagnosticId)
allocator mem.Allocator
}
#! owns node-indexed types, aggregate descriptors, initializer mappings, and function/value-block fallthrough obligations; release with deinit.
#! borrows the caller-owned AST for signature comparisons; diagnostic IDs refer to the caller-owned store.
Result :: struct { storage Storage }
@hide:file
no_index :: maxval!(usize)
@hide:file
Role :: enum {
none,
expression,
declaration,
parameter,
function,
annotation,
invalid,
body,
return_stmt,
yield_stmt,
value_body,
if_stmt,
assignment_stmt,
while_stmt,
for_stmt,
loop_capture,
loop_control,
cleanup,
}
@hide:file
GroupLink :: struct {
parent usize = 0
size usize = 1
}
@hide:file
Cell :: struct {
role Role = .none
type Type = Type{}
numeric bool = false
anchored bool = false
demand TypeKind = .none
conversion_unsigned bool = false
fixed bool = false
local bool = false
assignment_value bool = false
for_iterable bool = false
expected Type = Type{}
visiting bool = false
constant_visiting bool = false
count_declaration bool = false
literal ast.NodeId = ast.NodeId(maxval!(u32))
producer ast.NodeId = ast.NodeId(maxval!(u32))
capture ast.NodeId = ast.NodeId(maxval!(u32))
for_loop ast.NodeId = ast.NodeId(maxval!(u32))
record_checking bool = false
record_pointer_depth usize = 0
nominal bool = false
match_pattern bool = false
first_edge usize = no_index
return_annotation ast.NodeId = ast.NodeId(maxval!(u32))
in_cleanup bool = false
narrowing_arm ast.NodeId = ast.NodeId(maxval!(u32))
enclosing_arm ast.NodeId = ast.NodeId(maxval!(u32))
match_owner ast.NodeId = ast.NodeId(maxval!(u32))
narrowed_type Type = Type{}
yield_owner ast.NodeId = ast.NodeId(maxval!(u32))
first_yield ast.NodeId = ast.NodeId(maxval!(u32))
next_yield ast.NodeId = ast.NodeId(maxval!(u32))
}
@hide:file
Edge :: struct {
dependent ast.NodeId
next usize
}
@hide:file
Variant :: struct {
name ast.NodeId
payload Type
}
@hide:file
State :: struct {
tree @ast.Tree
resolution @sema.Resolution
diagnostics @mut diagnostics.Store
types []mut Type
cells []mut Cell
groups []mut GroupLink
edges arraylist.ArrayList(Edge)
arrays arraylist.ArrayList(ArrayType)
pointers arraylist.ArrayList(PointerType)
records arraylist.ArrayList(RecordType)
initializers arraylist.ArrayList(RecordInitializer)
enums arraylist.ArrayList(EnumType)
variants arraylist.ArrayList(Variant)
backings []mut Type
exhaustive []mut bool
variant_slots []mut usize
return_annotation ast.NodeId = ast.NodeId(maxval!(u32))
in_loop bool = false
in_cleanup bool = false
narrowing_arm ast.NodeId = ast.NodeId(maxval!(u32))
slots []mut usize
collecting_assignment bool = false
has_assignments bool = false
pointer_depth usize = 0
yield_owner ast.NodeId = ast.NodeId(maxval!(u32))
}
#! borrows `resolution` and `diagnostic_store` during checking; Result retains the AST borrow for signature comparisons.
#! temporary constraints are freed on every exit; Result owns types, aggregate descriptors, initializer mappings, and fallthrough obligations.
check proc(
allocator mem.Allocator,
tree @ast.Tree,
resolution @sema.Resolution,
diagnostic_store @mut diagnostics.Store,
) Result ! (mem.AllocError | diagnostics.CapacityError) {
return try check_elaborated(allocator, tree, resolution, &[], diagnostic_store)
}
#! checks additional compile-time expressions retained when elaboration erased their calls.
check_elaborated proc(
allocator mem.Allocator,
tree @ast.Tree,
resolution @sema.Resolution,
constraints []ast.NodeId,
diagnostic_store @mut diagnostics.Store,
) Result ! (mem.AllocError | diagnostics.CapacityError) {
count :: ast.node_count(tree)
types :: try mem.alloc(Type, allocator, count)
errdefer mem.free(allocator, types)
mem.set(types, Type{})
cells :: try mem.alloc(Cell, allocator, count)
defer mem.free(allocator, cells)
mem.set(cells, Cell{})
groups :: try mem.alloc(GroupLink, allocator, count)
defer mem.free(allocator, groups)
mem.set(groups, GroupLink{})
for (0..count) |i| groups[i].parent = i
queue :: try mem.alloc(ast.NodeId, allocator, count)
defer mem.free(allocator, queue)
slots :: try mem.alloc(usize, allocator, count)
errdefer mem.free(allocator, slots)
mem.set(slots, no_index)
backings :: try mem.alloc(Type, allocator, count)
errdefer mem.free(allocator, backings)
mem.set(backings, Type{})
exhaustive :: try mem.alloc(bool, allocator, count)
errdefer mem.free(allocator, exhaustive)
mem.set(exhaustive, false)
variant_slots :: try mem.alloc(usize, allocator, count)
errdefer mem.free(allocator, variant_slots)
mem.set(variant_slots, no_index)
state := State{
tree = tree,
resolution = resolution,
diagnostics = diagnostic_store,
types = types,
cells = cells,
groups = groups,
edges = arraylist.init(allocator),
arrays = arraylist.init(allocator),
pointers = arraylist.init(allocator),
records = arraylist.init(allocator),
initializers = arraylist.init(allocator),
slots = slots,
enums = arraylist.init(allocator),
backings = backings,
exhaustive = exhaustive,
variant_slots = variant_slots,
variants = arraylist.init(allocator),
}
defer arraylist.deinit(&state.edges)
defer arraylist.deinit(&state.variants)
errdefer arraylist.deinit(&state.arrays)
errdefer arraylist.deinit(&state.pointers)
errdefer {
for (state.enums.items) |descriptor| mem.free(allocator, descriptor.members)
arraylist.deinit(&state.enums)
for (state.records.items) |record| mem.free(allocator, record.fields)
arraylist.deinit(&state.records)
for (state.initializers.items) |initializer| mem.free(allocator, initializer.fields)
arraylist.deinit(&state.initializers)
}
for 0..ast.package_decl_count(tree) |i| {
id :: ast.package_decl(tree, i)
if (ast.decl_kind(tree, id) == .statement) cells[usize(id)].count_declaration = true
}
for (0..ast.package_decl_count(tree)) |i| {
id :: ast.package_decl(tree, i)
match ast.decl_kind(tree, id) {
.statement: try collect_declaration(&state, id)
.function: {
if (!ast.function_external(tree, id)
or mem.eql(ast.source_text(tree, ast.function_decl(tree, id).name), "main"))
try collect_function(&state, id)
}
.invalid: collect_invalid(&state, id)
.import: {}
}
}
for (constraints) |constraint| {
if (ast.node_class(tree, constraint) == .statement) {
state.cells[usize(constraint)].local = true
try collect_declaration(&state, constraint)
} else try collect_expression(&state, constraint)
}
# preserve binding errors before RHS errors propagate. indexed writability
# waits for shapes; neither check poisons the target's type.
for 0..count |i| {
if (cells[i].role != .assignment_stmt) continue
target :: ast.assignment_stmt(tree, ast.NodeId(i)).target
if (!declaration_reference(tree, target)) continue
binding :: sema.binding(resolution, target)
if (binding.kind == .invalid) {
cells[i].type = invalid_type(binding.data.diagnostic)
} else if (binding.kind == .declaration and !mutable_target(&state, target, false)) {
cells[i].type = invalid_type(try diagnostics.add(
diagnostic_store, .invalid_assignment_target, ast.node_span(tree, target),
))
}
}
# aliases share a type variable; explicit annotations and parameters are boundaries.
for (0..count) |i| {
id :: ast.NodeId(i)
if (cells[i].role != .expression or !declaration_reference(tree, id)) continue
if (cells[i].type.kind == .invalid) continue
binding :: sema.binding(resolution, id)
if (binding.kind != .declaration) continue
target :: binding.data.declaration
try depend(&state, target, id)
if (cells[usize(target)].fixed) cells[i].type = cells[usize(target)].type
}
for (0..count) |i| {
id :: ast.NodeId(i)
if (cells[i].role == .declaration and !cells[i].fixed) {
join(&state, id, ast.statement_decl(tree, id).initializer)
}
if (cells[i].role != .expression or !declaration_reference(tree, id)) continue
if (cells[i].type.kind == .invalid) continue
binding :: sema.binding(resolution, id)
if (
binding.kind == .declaration and !cells[i].assignment_value
and !cells[usize(binding.data.declaration)].fixed and !has_narrowing(&state, id)
) join(&state, id, binding.data.declaration)
}
# assignment inputs observe established declaration types, never contribute
# constraints back into those declarations.
for 0..2 |phase| {
if (phase == 1 and !state.has_assignments) break
assignment_values :: phase == 1
if (assignment_values) {
for (0..count) |i| {
id :: ast.NodeId(i)
if (
!cells[i].assignment_value
or cells[i].role != .expression
or !declaration_reference(tree, id)
) continue
if (cells[i].type.kind == .invalid) continue
binding :: sema.binding(resolution, id)
if (binding.kind == .declaration) cells[i].type = types[usize(binding.data.declaration)]
}
}
# calls use the signature result before contextual literal inference.
try prepare_for_captures(&state, false)
try prepare_optional_captures(&state, false)
for (0..count) |i| {
id :: ast.NodeId(i)
if (cells[i].role != .expression or cells[i].assignment_value != assignment_values or ast.expr_kind(tree, id) != .call) continue
if (conversion_kind(&state, id) != .none or state.backings[i].kind != .none) continue
callee :: ast.call_expr(tree, id).callee
signature :: try infer_shape(&state, callee, false)
if (signature.function != ast.NodeId(maxval!(u32))) {
result :: ast.callable_result(tree, signature.function)
seed(&state, id, types[usize(result)])
} else if (signature.kind == .invalid) {
seed(&state, id, signature)
}
}
for (0..count) |i| {
id :: ast.NodeId(i)
if (cells[i].role != .expression or cells[i].assignment_value != assignment_values or ast.expr_kind(tree, id) != .call) continue
if (conversion_kind(&state, id) != .none or state.backings[i].kind != .none) continue
callee :: ast.call_expr(tree, id).callee
signature :: group_type(&state, callee)
if (signature.function != ast.NodeId(maxval!(u32))) continue
if (signature.kind == .invalid) {
seed(&state, id, signature)
} else if (signature.kind != .none or cells[root(&state, usize(callee))].numeric) {
seed(&state, id, invalid_type(try diagnostics.add(diagnostic_store, .not_callable, ast.node_span(tree, callee))))
} else {
# without a function anchor, share the eventual inference error.
join(&state, id, callee)
}
}
for (0..count) |i| {
if (cells[i].role == .none or cells[i].role == .body or cells[i].assignment_value != assignment_values or types[i].kind == .invalid) continue
types[i] = cells[root(&state, i)].type
}
# reject nonnumeric operations before connecting otherwise valid inputs.
try apply_contexts(&state, assignment_values, true)
try prepare_shapes(&state, assignment_values, false)
try prepare_ranges(&state, assignment_values, false)
for (0..count) |i| {
id :: ast.NodeId(i)
if (cells[i].role != .expression or cells[i].assignment_value != assignment_values) continue
match ast.expr_kind(tree, id) {
.unary: {
expression :: ast.unary_expr(tree, id)
if (expression.operator == .negate or expression.operator == .bit_not) try require_numeric(&state, id, expression.operand)
}
.binary: {
expression :: ast.binary_expr(tree, id)
if (logical(expression.operator) or bool_equality(&state, id) or pointer_addition(&state, id)) continue
try require_numeric(&state, id, expression.left)
try require_numeric(&state, id, expression.right)
}
.builtin_call: {
if (mem.eql(builtin_name(&state, id), "divtrunc") and ast.call_arg_count(tree, id) == 2) {
try require_numeric(&state, id, ast.call_arg(tree, id, 0))
try require_numeric(&state, id, ast.call_arg(tree, id, 1))
}
}
else: {}
}
}
propagate(&state, queue)
# numeric edges join flexible operands; rigid inputs supply only demands.
for (0..count) |i| {
id :: ast.NodeId(i)
if (cells[i].role != .expression or cells[i].assignment_value != assignment_values) continue
match ast.expr_kind(tree, id) {
.unary: {
expression :: ast.unary_expr(tree, id)
if (expression.operator == .negate or expression.operator == .bit_not) try numeric_operand(&state, id, expression.operand)
}
.binary: {
expression :: ast.binary_expr(tree, id)
if (logical(expression.operator) or pointer_addition(&state, id)) continue
if (comparison(expression.operator)) {
if (!bool_equality(&state, id)) try comparison_operands(&state, id)
} else {
try numeric_operand(&state, id, expression.left)
if (shift(expression.operator)) {
count_group :: root(&state, usize(expression.right))
if (state.cells[count_group].type.kind == .none and state.cells[count_group].demand == .none)
try impose(&state, expression.right, .usize, .invalid_operand, expression.span)
} else try numeric_operand(&state, id, expression.right)
if (expression.operator == .divide) {
try impose(&state, id, .float, .integer_division, expression.span)
}
}
}
.builtin_call: {
if (mem.eql(builtin_name(&state, id), "divtrunc") and ast.call_arg_count(tree, id) == 2) {
try numeric_operand(&state, id, ast.call_arg(tree, id, 0))
try numeric_operand(&state, id, ast.call_arg(tree, id, 1))
}
}
else: {}
}
}
try apply_contexts(&state, assignment_values, false)
try prepare_shapes(&state, assignment_values, false)
try prepare_ranges(&state, assignment_values, true)
for 0..count |i| {
id :: ast.NodeId(i)
if (cells[i].role != .expression or cells[i].assignment_value != assignment_values or ast.expr_kind(tree, id) != .index) continue
try demand(&state, ast.index_expr(tree, id).index, Type{ kind = .usize }, .invalid_index_type)
}
# defaults follow ordinary program demands, then isolated assignment demands.
for (0..count) |i| {
if (root(&state, i) != i or cells[i].assignment_value != assignment_values) continue
if (cells[i].type.kind == .invalid) continue
if (cells[i].numeric and cells[i].anchored) {
kind TypeKind :: if (cells[i].demand != .none)
cells[i].demand
else if (cells[i].conversion_unsigned)
.u64
else
.int
cells[i].type = if (cells[i].expected.kind == kind)
cells[i].expected
else
Type{ kind = kind }
}
}
try prepare_shapes(&state, assignment_values, true)
if (!assignment_values) try specialize_annotations(&state)
for (0..count) |i| {
id :: ast.NodeId(i)
if (cells[i].role != .expression or cells[i].assignment_value != assignment_values or ast.expr_kind(tree, id) != .call) continue
if (conversion_kind(&state, id) != .none or state.backings[i].kind != .none) continue
callee :: ast.call_expr(tree, id).callee
signature :: group_type(&state, callee)
if (signature.function != ast.NodeId(maxval!(u32)) or signature.kind == .none or signature.kind == .invalid) continue
if (group_type(&state, id).kind != .invalid) {
seed(&state, id, invalid_type(try diagnostics.add(diagnostic_store, .not_callable, ast.node_span(tree, callee))))
}
}
for (0..count) |i| {
if (assignment_values or cells[i].role != .declaration or types[i].kind == .invalid) continue
index :: root(&state, i)
if (cells[index].type.kind != .none) continue
diagnostic :: try diagnostics.add(diagnostic_store, .cannot_infer_type, ast.statement_decl(tree, ast.NodeId(i)).name)
cells[index].type = invalid_type(diagnostic)
}
for (0..count) |i| {
if (cells[i].role == .none or cells[i].role == .body or cells[i].assignment_value != assignment_values or types[i].kind == .invalid) continue
types[i] = cells[root(&state, i)].type
}
}
for (0..count) |i| try validate(&state, ast.NodeId(i))
for (0..ast.package_decl_count(tree)) |i| {
id :: ast.package_decl(tree, i)
if (ast.decl_kind(tree, id) != .function or !ast.function_external(tree, id)) continue
if (cells[usize(id)].role == .none) continue
for (0..i) |j| {
previous :: ast.package_decl(tree, j)
if (ast.decl_kind(tree, previous) != .function or !ast.function_external(tree, previous)) continue
if (cells[usize(previous)].role == .none) continue
if (ast.name_id(tree, id) == ast.name_id(tree, previous) and !same_signature(&state, id, previous)) {
try fail(&state, id, .type_mismatch, ast.function_decl(tree, id).name)
}
}
}
for state.records.items |record| {
for record.fields |field| {
if (field.default) |value| try compatible(&state, value, field.type)
}
}
# errors travel only from dependencies to users. A rejected use never poisons
# an explicitly typed declaration, and body errors do not erase signatures.
propagate(&state, queue)
# validate conditions after operand errors have reached their users.
for (0..count) |i| {
id :: ast.NodeId(i)
if (cells[i].role != .if_stmt and cells[i].role != .while_stmt) continue
condition :: if (cells[i].role == .if_stmt) ast.if_stmt(tree, id).condition else ast.while_stmt(tree, id).condition
capture :: if (cells[i].role == .if_stmt) ast.if_stmt(tree, id).capture else ast.while_stmt(tree, id).capture
actual :: types[usize(condition)]
if (capture != null and actual.kind == .optional) continue
if (actual.kind != .invalid and (capture != null or actual.kind != .bool or actual.nominal != ast.NodeId(maxval!(u32)))) {
try fail(&state, condition, .condition_not_bool, ast.node_span(tree, condition))
}
}
for (0..count) |i| {
if (cells[i].role != .for_stmt) continue
iterable :: ast.for_stmt(tree, ast.NodeId(i)).iterable
actual :: types[usize(iterable)]
if (actual.kind != .invalid and actual.kind != .range and !collection_type(&state, actual)) {
try fail(&state, iterable, .invalid_for_iterable, ast.node_span(tree, iterable))
}
}
propagate(&state, queue)
fallthroughs hashmap.AutoHashMap(ast.NodeId, diagnostics.DiagnosticId) := hashmap.init(allocator)
errdefer hashmap.deinit(&fallthroughs)
for (0..ast.package_decl_count(tree)) |i| {
id :: ast.package_decl(tree, i)
if (ast.decl_kind(tree, id) != .function) continue
if (ast.function_external(tree, id)) continue
function :: ast.function_decl(tree, id)
result :: types[usize(function.return_type)]
if (result.kind == .void or result.kind == .invalid or types[usize(id)].kind == .invalid) continue
if (result.kind == .fallible and state.arrays.items[usize(result.array_type)].element.kind == .void) continue
if (block_falls_through(&state, function.body)) {
# fallthrough is independent of errors in paths that may not execute.
diagnostic :: try diagnostics.add(diagnostic_store, .missing_return, ast.node_span(tree, function.body))
hashmap.put(&fallthroughs, id, diagnostic) catch |err| {
match err {
.key_exists: unreachable
else: return err
}
}
}
}
for 0..count |i| {
id :: ast.NodeId(i)
explicit :: cells[i].role == .expression and ast.expr_kind(tree, id) == .block_expr
if (!explicit and cells[i].role != .value_body) continue
result :: types[i]
if (result.kind == .none or result.kind == .void or result.kind == .invalid) continue
body :: if (explicit) ast.block_expr(tree, id) else id
if (!block_falls_through(&state, body)) continue
diagnostic :: try diagnostics.add(diagnostic_store, .missing_yield, ast.node_span(tree, body))
hashmap.put(&fallthroughs, id, diagnostic) catch |err| {
match err {
.key_exists: unreachable
else: return err
}
}
if (explicit) hashmap.put(&fallthroughs, body, diagnostic) catch |err| {
match err {
.key_exists: unreachable
else: return err
}
}
}
return Result{ storage = Storage{
tree = tree,
types = types,
arrays = state.arrays,
pointers = state.pointers,
records = state.records,
initializers = state.initializers,
enums = state.enums,
backings = backings,
exhaustive = exhaustive,
variant_slots = variant_slots,
slots = slots,
fallthroughs = fallthroughs,
allocator = allocator,
} }
}
#! returns a type without retaining a reference to checker temporaries.
type_of proc(result @Result, id ast.NodeId) Type {
debug.assert(usize(id) < result.storage.types.len)
return result.storage.types[usize(id)]
}
#! returns the original diagnostic for a function or value block's nonvoid fallthrough.
#! null means there is no fallthrough obligation; lowering still owns trap placement.
fallthrough_diagnostic proc(result @Result, owner ast.NodeId) ?diagnostics.DiagnosticId {
return hashmap.get(&result.storage.fallthroughs, owner)
}
array_type_count proc(result @Result) usize {
return result.storage.arrays.items.len
}
array_type proc(result @Result, id ArrayId) ArrayType {
debug.assert(usize(id) < result.storage.arrays.items.len)
return result.storage.arrays.items[usize(id)]
}
pointer_type proc(result @Result, id PointerId) PointerType {
return result.storage.pointers.items[usize(id)]
}
record_type_count proc(result @Result) usize {
return result.storage.records.items.len
}
record_type proc(result @Result, id RecordId) RecordType {
debug.assert(usize(id) < result.storage.records.items.len)
return result.storage.records.items[usize(id)]
}
enum_type_count proc(result @Result) usize {
return result.storage.enums.items.len
}
enum_type proc(result @Result, id EnumId) EnumType {
return result.storage.enums.items[usize(id)]
}
backing_type proc(result @Result, type Type) Type {
if (type.nominal == ast.NodeId(maxval!(u32))) return type
return result.storage.backings[usize(type.nominal)]
}
is_type_constructor proc(result @Result, id ast.NodeId) bool {
return result.storage.backings[usize(id)].kind != .none
}
constructor_backing proc(result @Result, id ast.NodeId) Type {
return result.storage.backings[usize(id)]
}
match_exhaustive proc(result @Result, id ast.NodeId) bool {
return result.storage.exhaustive[usize(id)]
}
is_enum_member proc(result @Result, id ast.NodeId) bool {
return (
result.storage.backings[usize(id)].kind == .enumeration
and result.storage.slots[usize(id)] != no_index
)
}
enum_member_value proc(result @Result, id ast.NodeId) EnumMember {
type :: result.storage.backings[usize(id)]
return result.storage.enums.items[usize(type.enum_type)].members[result.storage.slots[usize(id)]]
}
enum_member_index proc(result @Result, id ast.NodeId) usize {
return result.storage.slots[usize(id)]
}
is_union_variant proc(result @Result, id ast.NodeId) bool {
return result.storage.variant_slots[usize(id)] != no_index
}
union_variant_index proc(result @Result, id ast.NodeId) usize {
return result.storage.variant_slots[usize(id)]
}
union_payload_type proc(result @Result, id ast.NodeId) Type {
return result.storage.backings[usize(id)]
}
field_index proc(result @Result, selection ast.NodeId) usize {
debug.assert(result.storage.slots[usize(selection)] != no_index)
return result.storage.slots[usize(selection)]
}
initializer_field proc(result @Result, literal ast.NodeId, index usize) ast.NodeId {
slot :: result.storage.slots[usize(literal)]
debug.assert(slot != no_index)
return result.storage.initializers.items[slot].fields[index]
}
#! releases type, descriptor, and fallthrough storage using the allocator retained by check.
deinit proc(result @mut Result) void {
mem.free(result.storage.allocator, result.storage.types)
arraylist.deinit(&result.storage.arrays)
arraylist.deinit(&result.storage.pointers)
for (result.storage.enums.items) |descriptor| mem.free(result.storage.allocator, descriptor.members)
arraylist.deinit(&result.storage.enums)
mem.free(result.storage.allocator, result.storage.backings)
mem.free(result.storage.allocator, result.storage.exhaustive)
mem.free(result.storage.allocator, result.storage.variant_slots)
hashmap.deinit(&result.storage.fallthroughs)
for (result.storage.records.items) |record| mem.free(result.storage.allocator, record.fields)
arraylist.deinit(&result.storage.records)
for (result.storage.initializers.items) |initializer| mem.free(result.storage.allocator, initializer.fields)
arraylist.deinit(&result.storage.initializers)
mem.free(result.storage.allocator, result.storage.slots)
}
@hide:file
propagate proc(state @mut State, queue []mut ast.NodeId) void {
next := usize(0)
end := usize(0)
for (0..(state.types.len)) |i| {
if (state.types[i].kind != .invalid) continue
queue[end] = ast.NodeId(i)
end += 1
}
while (next < end) {
source :: queue[next]
next += 1
edge := state.cells[usize(source)].first_edge
while (edge != no_index) {
dependent :: state.edges.items[edge].dependent
if (state.types[usize(dependent)].kind != .invalid) {
state.types[usize(dependent)] = invalid_type(state.types[usize(source)].diagnostic)
queue[end] = dependent
end += 1
}
edge = state.edges.items[edge].next
}
}
}
@hide:file
require_numeric proc(state @mut State, expression, operand ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
index :: root(state, usize(operand))
type :: state.cells[index].type
if (type.kind == .invalid) {
state.types[usize(expression)] = type
return
}
if (type.kind == .none or state.cells[index].numeric or numeric_kind(type.kind)) return
try fail(state, expression, .invalid_operand, ast.node_span(state.tree, expression))
}
@hide:file
invalid_type proc(diagnostic diagnostics.DiagnosticId) Type {
return Type{ kind = .invalid, diagnostic = diagnostic }
}
@hide:file
root proc(state @mut State, index usize) usize {
current := index
while (state.groups[current].parent != current) current = state.groups[current].parent
next := index
while (state.groups[next].parent != next) {
parent :: state.groups[next].parent
state.groups[next].parent = current
next = parent
}
return current
}
@hide:file
group_type proc(state @mut State, id ast.NodeId) Type {
return state.cells[root(state, usize(id))].type
}
@hide:file
seed proc(state @mut State, id ast.NodeId, type Type) void {
index :: root(state, usize(id))
if (state.cells[index].type.kind != .invalid) state.cells[index].type = type
}
@hide:file
join proc(state @mut State, left, right ast.NodeId) void {
a := root(state, usize(left))
b := root(state, usize(right))
if (a == b) return
if (state.groups[a].size < state.groups[b].size) {
saved :: a
a = b
b = saved
}
state.groups[b].parent = a
state.groups[a].size += state.groups[b].size
if (state.cells[a].type.kind == .none or state.cells[b].type.kind == .invalid) {
state.cells[a].type = state.cells[b].type
}
state.cells[a].numeric = state.cells[a].numeric or state.cells[b].numeric
state.cells[a].anchored = state.cells[a].anchored or state.cells[b].anchored
if (state.cells[a].demand == .none) state.cells[a].demand = state.cells[b].demand
if (state.cells[a].expected.kind == .none) state.cells[a].expected = state.cells[b].expected
state.cells[a].conversion_unsigned = state.cells[a].conversion_unsigned or state.cells[b].conversion_unsigned
if (state.cells[a].literal == ast.NodeId(maxval!(u32))) state.cells[a].literal = state.cells[b].literal
if (state.cells[a].producer == ast.NodeId(maxval!(u32))) state.cells[a].producer = state.cells[b].producer
}
@hide:file
depend proc(state @mut State, source, dependent ast.NodeId) void ! mem.AllocError {
# a value block keeps its result type when only an unexecuted path is invalid.
if (state.cells[usize(dependent)].role == .value_body) return
next :: state.cells[usize(source)].first_edge
try arraylist.append(&state.edges, Edge{ dependent = dependent, next = next })
state.cells[usize(source)].first_edge = state.edges.items.len - 1
}
@hide:file
collect_invalid proc(state @mut State, id ast.NodeId) void {
state.cells[usize(id)].role = .invalid
state.cells[usize(id)].type = invalid_type(ast.invalid(state.tree, id).diagnostic)
}
@hide:file
check_annotation proc(state @mut State, id ast.NodeId, allow_void bool) Type ! (mem.AllocError | diagnostics.CapacityError) {
if (state.cells[usize(id)].role == .annotation) return state.types[usize(id)]
state.cells[usize(id)].role = .annotation
if (ast.expr_kind(state.tree, id) == .slice_type or ast.expr_kind(state.tree, id) == .optional_type) {
type :: try check_container_type(state, id)
state.types[usize(id)] = type
state.cells[usize(id)].type = type
return type
}
if (ast.expr_kind(state.tree, id) == .pointer_type) {
type :: try check_pointer_type(state, id)
state.types[usize(id)] = type
state.cells[usize(id)].type = type
return type
}
if (type_syntax(ast.expr_kind(state.tree, id))) {
type :: try check_type_expression(state, id, false)
state.types[usize(id)] = type
state.cells[usize(id)].type = type
return type
}
if (ast.expr_kind(state.tree, id) == .array_type) {
view :: ast.array_type(state.tree, id)
element := try check_annotation(state, view.element, true)
element_binding :: sema.binding(state.resolution, view.element)
if (
(element_binding.kind == .builtin
and (element_binding.data.builtin == .int or element_binding.data.builtin == .float))
or element.kind == .void or element.kind == .range or element.kind == .anyopaque
) {
element = invalid_type(try diagnostics.add(state.diagnostics, .invalid_array_element_type, ast.node_span(state.tree, view.element)))
state.types[usize(view.element)] = element
state.cells[usize(view.element)].type = element
}
len := no_index
type := element
if (view.count) |count| {
try collect_expression(state, count)
if (constant_integer(state, count)) |value| {
if (!value.negative and value.magnitude <= u64(maxval!(usize))) {
len = usize(value.magnitude)
if (len == no_index) type = invalid_type(try diagnostics.add(state.diagnostics, .array_size_overflow, ast.node_span(state.tree, count)))
} else {
type = invalid_type(try diagnostics.add(state.diagnostics, .invalid_array_length, ast.node_span(state.tree, count)))
}
} else {
actual :: group_type(state, count)
type = if (actual.kind == .invalid) actual else invalid_type(try diagnostics.add(state.diagnostics, .invalid_array_length, ast.node_span(state.tree, count)))
}
}
if (type.kind != .invalid) type = try intern_array(state, ArrayType{ element = element, len = len, mutable = view.mutable })
state.types[usize(id)] = type
state.cells[usize(id)].type = type
return type
}
binding :: sema.binding(state.resolution, id)
type := Type{}
match binding.kind {
.invalid: type = invalid_type(binding.data.diagnostic)
.builtin: {
type.kind = enummap.get(&builtin_kinds, binding.data.builtin)?
if (binding.data.builtin == .unavailable)
type = invalid_type(try diagnostics.add(state.diagnostics, .invalid_operand, ast.node_span(state.tree, id)))
if (type.kind == .anyopaque and state.pointer_depth == 0) {
type = invalid_type(try diagnostics.add(state.diagnostics, .expected_type, ast.node_span(state.tree, id)))
}
if (type.kind == .void and !allow_void) {
type = invalid_type(try diagnostics.add(state.diagnostics, .invalid_void_type, ast.node_span(state.tree, id)))
}
}
.declaration: {
target :: binding.data.declaration
role :: state.cells[usize(target)].role
if (role != .parameter and role != .loop_capture and ast.decl_kind(state.tree, target) == .statement) {
initializer :: ast.statement_decl(state.tree, target).initializer
if (type_syntax(ast.expr_kind(state.tree, initializer))) {
type = try check_type_expression(state, initializer, true)
if (type.kind == .anyopaque and state.pointer_depth == 0)
type = invalid_type(try diagnostics.add(state.diagnostics, .expected_type, ast.node_span(state.tree, id)))
} else {
type = invalid_type(try diagnostics.add(state.diagnostics, .expected_type, ast.node_span(state.tree, id)))
}
} else {
type = invalid_type(try diagnostics.add(state.diagnostics, .expected_type, ast.node_span(state.tree, id)))
}
}
else: type = invalid_type(try diagnostics.add(state.diagnostics, .expected_type, ast.node_span(state.tree, id)))
}
state.types[usize(id)] = type
state.cells[usize(id)].type = type
return type
}
@hide:file
collect_declaration proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
declaration :: ast.statement_decl(state.tree, id)
state.cells[usize(id)].role = .declaration
if (type_syntax(ast.expr_kind(state.tree, declaration.initializer))) {
state.cells[usize(declaration.initializer)].nominal = true
}
if (declaration.type) |type| {
state.cells[usize(id)].fixed = true
state.cells[usize(id)].type = try check_annotation(state, type, false)
try depend(state, type, id)
}
binding :: sema.binding(state.resolution, id)
if (binding.kind == .invalid) {
state.cells[usize(id)].fixed = true
state.cells[usize(id)].type = invalid_type(binding.data.diagnostic)
}
if (ast.expr_kind(state.tree, declaration.initializer) == .literal_undefined) {
type :: state.cells[usize(id)].type
if (
!state.cells[usize(id)].local or declaration.mutability != .variable
or type.kind == .none or type.kind == .type_value or has_inferred_count(state, type)
) {
try fail(state, declaration.initializer, .invalid_undefined, ast.node_span(state.tree, declaration.initializer))
} else {
state.cells[usize(declaration.initializer)].role = .expression
seed(state, declaration.initializer, type)
}
try depend(state, declaration.initializer, id)
return
}
try collect_expression(state, declaration.initializer)
if (
type_syntax(ast.expr_kind(state.tree, declaration.initializer))
and state.cells[usize(id)].type.kind != .invalid
and state.cells[usize(declaration.initializer)].type.kind != .none
) {
state.cells[usize(id)].fixed = true
state.cells[usize(id)].type = state.cells[usize(declaration.initializer)].type
}
try depend(state, declaration.initializer, id)
}
@hide:file
collect_function proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
if (state.cells[usize(id)].role == .function) return
tree :: state.tree
function :: ast.function_decl(tree, id)
state.cells[usize(id)].role = .function
state.cells[usize(id)].fixed = true
state.cells[usize(id)].type = Type{ kind = .function, function = id }
_ = try check_annotation(state, function.return_type, true)
try depend(state, function.return_type, id)
for (0..ast.function_param_count(tree, id)) |i| {
parameter :: ast.function_param(tree, id, i)
state.cells[usize(parameter)].role = .parameter
state.cells[usize(parameter)].fixed = true
state.cells[usize(parameter)].type = try check_annotation(state, ast.param(tree, parameter).type, false)
binding :: sema.binding(state.resolution, parameter)
if (binding.kind == .invalid) state.cells[usize(parameter)].type = invalid_type(binding.data.diagnostic)
try depend(state, ast.param(tree, parameter).type, parameter)
try depend(state, parameter, id)
}
binding :: sema.binding(state.resolution, id)
if (binding.kind == .invalid) {
state.cells[usize(id)].type = invalid_type(binding.data.diagnostic)
state.cells[usize(id)].type.function = id
}
if (!ast.function_external(tree, id)) try collect_block(state, function.body, function.return_type, false)
else {
if (mem.eql(ast.source_text(tree, function.name), "main")) try fail(state, id, .invalid_entry, function.name)
if (ast.callable_variadic(tree, id)) try fail(state, id, .invalid_operand, function.span)
if (!c_abi_type(state, state.types[usize(function.return_type)])) try fail(state, id, .invalid_operand, function.span)
for (0..ast.function_param_count(tree, id)) |i| {
parameter :: ast.function_param(tree, id, i)
if (!c_abi_type(state, state.cells[usize(parameter)].type)) try fail(state, id, .invalid_operand, ast.node_span(tree, parameter))
}
}
}
@hide:file
collect_foreign_symbol proc(state @mut State, target ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
if (state.cells[usize(target)].role == .function) return
tree :: state.tree
# declarations of the same external symbol must agree even across package namespaces.
for 0..ast.package_decl_count(tree) |i| {
id :: ast.package_decl(tree, i)
if (ast.decl_kind(tree, id) == .function and ast.function_external(tree, id)
and ast.name_id(tree, id) == ast.name_id(tree, target))
try collect_function(state, id)
}
}
@hide:file
collect_capture proc(state @mut State, id, body ast.NodeId) void ! mem.AllocError {
state.cells[usize(id)].role = .loop_capture
state.cells[usize(id)].fixed = true
state.cells[usize(id)].local = true
state.cells[usize(id)].type = Type{ kind = .int }
binding :: sema.binding(state.resolution, id)
if (binding.kind == .invalid) {
state.cells[usize(id)].type = invalid_type(binding.data.diagnostic)
}
try depend(state, id, body)
}
@hide:file
collect_value_block proc(state @mut State, body, owner ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
saved_owner :: state.yield_owner
state.yield_owner = owner
defer state.yield_owner = saved_owner
# both explicit and implicit bodies preserve errors on their executed paths.
state.cells[usize(body)].role = .value_body
state.cells[usize(body)].assignment_value = state.collecting_assignment
try collect_block(state, body, state.return_annotation, state.in_loop)
}
@hide:file
collect_block proc(state @mut State, id, return_annotation ast.NodeId, in_loop bool) void ! (mem.AllocError | diagnostics.CapacityError) {
saved_annotation :: state.return_annotation
saved_loop :: state.in_loop
state.return_annotation = return_annotation
state.in_loop = in_loop
defer {
state.return_annotation = saved_annotation
state.in_loop = saved_loop
}
tree :: state.tree
if (state.cells[usize(id)].role == .none) state.cells[usize(id)].role = .body
for (0..ast.block_statement_count(tree, id)) |i| {
statement :: ast.block_statement(tree, id, i)
match ast.stmt_kind(tree, statement) {
.declaration: {
state.cells[usize(statement)].local = true
try collect_declaration(state, statement)
}
.expression: try collect_expression(state, ast.statement_expr(tree, statement))
.unreachable_stmt: state.cells[usize(statement)].type = Type{ kind = .void }
.return_stmt: {
state.cells[usize(statement)].role = .return_stmt
state.cells[usize(statement)].type = Type{ kind = .void }
state.cells[usize(statement)].return_annotation = return_annotation
if (state.in_cleanup) state.cells[usize(statement)].type = invalid_type(try diagnostics.add(
state.diagnostics, .cleanup_control_flow, ast.node_span(tree, statement),
))
if (ast.return_stmt(tree, statement).value) |value| {
try collect_expression(state, value)
try depend(state, value, statement)
}
}
.yield_stmt: {
state.cells[usize(statement)].role = .yield_stmt
state.cells[usize(statement)].type = Type{ kind = .void }
owner :: state.yield_owner
state.cells[usize(statement)].yield_owner = owner
if (owner == ast.NodeId(maxval!(u32))) {
code :: if (state.in_cleanup) diagnostics.Code.cleanup_control_flow else diagnostics.Code.yield_outside_block
try fail(state, statement, code, ast.node_span(tree, statement))
} else {
state.cells[usize(statement)].next_yield = state.cells[usize(owner)].first_yield
state.cells[usize(owner)].first_yield = statement
}
if (ast.yield_stmt(tree, statement).value) |value| {
try collect_expression(state, value)
try depend(state, value, statement)
}
}
.if_stmt: {
conditional :: ast.if_stmt(tree, statement)
state.cells[usize(statement)].role = .if_stmt
state.cells[usize(statement)].type = Type{ kind = .void }
try collect_expression(state, conditional.condition)
try depend(state, conditional.condition, statement)
if (conditional.capture) |capture| {
try collect_capture(state, capture, conditional.then_block)
state.cells[usize(capture)].type = Type{}
state.cells[usize(conditional.condition)].capture = capture
}
try collect_block(state, conditional.then_block, return_annotation, in_loop)
try depend(state, conditional.then_block, id)
if (conditional.else_block) |other| {
try collect_block(state, other, return_annotation, in_loop)
try depend(state, other, id)
}
}
.assignment_stmt: {
assignment :: ast.assignment_stmt(tree, statement)
state.cells[usize(statement)].role = .assignment_stmt
state.has_assignments = true
state.cells[usize(statement)].type = Type{ kind = .void }
binding :: sema.binding(state.resolution, assignment.target)
if (binding.kind == .builtin or binding.kind == .namespace) {
state.cells[usize(assignment.target)].role = .expression
state.cells[usize(assignment.target)].type = invalid_type(try diagnostics.add(
state.diagnostics, .invalid_assignment_target, ast.node_span(tree, assignment.target),
))
} else {
try collect_expression(state, assignment.target)
}
state.collecting_assignment = true
try collect_expression(state, assignment.value)
state.collecting_assignment = false
try depend(state, assignment.target, statement)
try depend(state, assignment.value, statement)
}
.while_stmt: {
loop :: ast.while_stmt(tree, statement)
state.cells[usize(statement)].role = .while_stmt
state.cells[usize(statement)].type = Type{ kind = .void }
try collect_expression(state, loop.condition)
try depend(state, loop.condition, statement)
if (loop.capture) |capture| {
try collect_capture(state, capture, loop.body)
state.cells[usize(capture)].type = Type{}
state.cells[usize(loop.condition)].capture = capture
}
try collect_block(state, loop.body, return_annotation, true)
try depend(state, loop.body, id)
}
.for_stmt: {
loop :: ast.for_stmt(tree, statement)
state.cells[usize(statement)].role = .for_stmt
state.cells[usize(statement)].type = Type{ kind = .void }
state.cells[usize(loop.iterable)].for_iterable = true
try collect_expression(state, loop.iterable)
try depend(state, loop.iterable, statement)
direct_range :: ast.expr_kind(tree, loop.iterable) == .range
if (direct_range and loop.pointer_capture) {
state.cells[usize(statement)].type = invalid_type(try diagnostics.add(
state.diagnostics, .range_pointer_capture, loop.span,
))
}
if (direct_range and loop.index_capture) {
diagnostic :: try diagnostics.add(state.diagnostics, .range_index_capture, loop.span)
if (state.cells[usize(statement)].type.kind != .invalid) {
state.cells[usize(statement)].type = invalid_type(diagnostic)
}
}
if (loop.capture) |capture| {
try collect_capture(state, capture, loop.body)
if (!direct_range) {
state.cells[usize(capture)].for_loop = statement
if (state.cells[usize(capture)].type.kind != .invalid) state.cells[usize(capture)].type = Type{}
}
state.cells[usize(loop.iterable)].capture = capture
}
if (loop.index) |index| {
try collect_capture(state, index, loop.body)
if (!direct_range and state.cells[usize(index)].type.kind != .invalid) {
state.cells[usize(index)].type = Type{ kind = .usize }
}
}
try collect_block(state, loop.body, return_annotation, true)
try depend(state, loop.body, id)
}
.block_stmt: {
body :: ast.statement_block(tree, statement)
state.cells[usize(statement)].role = .body
try collect_block(state, body, return_annotation, in_loop)
try depend(state, body, statement)
}
.defer_stmt, .errdefer_stmt: {
cleanup :: ast.cleanup_stmt(tree, statement)
state.cells[usize(statement)].role = .cleanup
state.cells[usize(statement)].type = Type{ kind = .void }
result :: if (return_annotation == ast.NodeId(maxval!(u32))) Type{} else state.types[usize(return_annotation)]
if (ast.stmt_kind(tree, statement) == .errdefer_stmt and result.kind != .fallible and result.kind != .invalid) {
state.cells[usize(statement)].type = invalid_type(try diagnostics.add(
state.diagnostics, .errdefer_outside_fallible, cleanup.span,
))
}
if (cleanup.capture) |capture| {
try collect_capture(state, capture, cleanup.body)
state.cells[usize(capture)].type = if (result.kind == .fallible)
state.arrays.items[usize(result.array_type)].error_type else result
}
saved_cleanup :: state.in_cleanup
saved_owner :: state.yield_owner
state.yield_owner = ast.NodeId(maxval!(u32))
state.in_cleanup = true
try collect_block(state, cleanup.body, return_annotation, false)
state.in_cleanup = saved_cleanup
state.yield_owner = saved_owner
}
.break_stmt, .continue_stmt: {
state.cells[usize(statement)].role = .loop_control
state.cells[usize(statement)].type = Type{ kind = .void }
if (!in_loop) {
code :: if (state.in_cleanup) diagnostics.Code.cleanup_control_flow
else if (ast.stmt_kind(tree, statement) == .break_stmt)
diagnostics.Code.break_outside_loop
else
diagnostics.Code.continue_outside_loop
state.cells[usize(statement)].type = invalid_type(try diagnostics.add(
state.diagnostics, code, ast.node_span(tree, statement),
))
}
}
.invalid: collect_invalid(state, statement)
}
source :: if (ast.stmt_kind(tree, statement) == .expression) ast.statement_expr(tree, statement) else statement
if (state.cells[usize(id)].role != .value_body) try depend(state, source, id)
}
}
@hide:file
collect_expression proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
if (state.cells[usize(id)].role != .none) return
tree :: state.tree
state.cells[usize(id)].role = .expression
state.cells[usize(id)].assignment_value = state.collecting_assignment
state.cells[usize(id)].return_annotation = state.return_annotation
state.cells[usize(id)].in_cleanup = state.in_cleanup
state.cells[usize(id)].narrowing_arm = state.narrowing_arm
match ast.expr_kind(tree, id) {
.block_expr: {
try collect_value_block(state, ast.block_expr(tree, id), id)
}
.comptime_expr: try fail(state, id, .comptime_argument_required, ast.node_span(tree, id))
.typed_constant: {
value :: ast.typed_constant(tree, id)
state.cells[usize(id)].fixed = true
state.cells[usize(id)].type = try check_annotation(state, value.type, false)
try collect_expression(state, value.value)
try depend(state, value.type, id)
try depend(state, value.value, id)
}
.union_constant: {
value :: ast.union_constant(tree, id)
state.cells[usize(id)].fixed = true
state.cells[usize(id)].type = try check_annotation(state, value.type, false)
try collect_expression(state, value.value)
try depend(state, value.type, id)
try depend(state, value.value, id)
}
.try_expr: {
operand :: ast.type_operand(tree, id)
try collect_expression(state, operand)
try depend(state, operand, id)
}
.catch_expr: {
expression :: ast.catch_expr(tree, id)
try collect_expression(state, expression.operand)
try depend(state, expression.operand, id)
if (expression.capture) |capture| {
try collect_capture(state, capture, expression.handler)
state.cells[usize(capture)].type = Type{}
}
if (expression.handler_is_block) {
try collect_value_block(state, expression.handler, expression.handler)
try depend(state, expression.handler, id)
} else try collect_expression(state, expression.handler)
}
.literal_int: {
state.cells[usize(id)].numeric = true
state.cells[usize(id)].anchored = true
}
.literal_float: {
state.cells[usize(id)].type = Type{ kind = .float }
state.cells[usize(id)].numeric = true
state.cells[usize(id)].anchored = true
state.cells[usize(id)].demand = .float
}
.literal_bool: state.cells[usize(id)].type = Type{ kind = .bool }
.literal_string: state.cells[usize(id)].type = Type{ kind = .string }
.literal_null: state.cells[usize(id)].type = Type{ kind = .null }
.literal_void: state.cells[usize(id)].type = Type{ kind = .void }
.literal_undefined: try fail(state, id, .invalid_undefined, ast.node_span(tree, id))
.builtin_call: try collect_builtin(state, id)
.slice: {
view :: ast.slice_expr(tree, id)
try collect_expression(state, view.base)
try depend(state, view.base, id)
if (view.start) |child| {
try collect_expression(state, child)
try depend(state, child, id)
}
if (view.end) |child| {
try collect_expression(state, child)
try depend(state, child, id)
}
if (view.sentinel) |child| {
try collect_expression(state, child)
try depend(state, child, id)
}
}
.identifier, .selection: {
binding :: sema.binding(state.resolution, id)
if (binding.kind == .none and ast.expr_kind(tree, id) == .selection) {
base :: ast.selection_expr(tree, id).base
if (type_reference(state, base)) {
checked :: try check_annotation(state, base, true)
if (
checked.kind != .invalid
and (checked.kind != .enumeration or checked.nominal != ast.NodeId(maxval!(u32)))
) {
try fail(state, base, .type_not_value, ast.node_span(tree, base))
state.cells[usize(base)].type = state.types[usize(base)]
}
} else try collect_expression(state, base)
try depend(state, base, id)
}
if (binding.kind == .declaration) {
target :: binding.data.declaration
if (ast.function_external(tree, target)) try collect_foreign_symbol(state, target)
role :: state.cells[usize(target)].role
if (
role != .parameter and role != .loop_capture and ast.decl_kind(tree, target) == .statement
and type_syntax(ast.expr_kind(tree, ast.statement_decl(tree, target).initializer))
) state.cells[usize(id)].type = invalid_type(try diagnostics.add(state.diagnostics, .type_not_value, ast.node_span(tree, id)))
}
match binding.kind {
.invalid: state.cells[usize(id)].type = invalid_type(binding.data.diagnostic)
.builtin: state.cells[usize(id)].type = invalid_type(try diagnostics.add(state.diagnostics, .type_not_value, ast.node_span(tree, id)))
.namespace: state.cells[usize(id)].type = invalid_type(try diagnostics.add(state.diagnostics, .namespace_not_value, ast.node_span(tree, id)))
else: {}
}
}
.unary: {
expression :: ast.unary_expr(tree, id)
if (expression.operator == .negate or expression.operator == .bit_not) state.cells[usize(id)].producer = id
if (expression.operator == .logical_not) {
state.cells[usize(id)].type = Type{ kind = .bool }
} else if (expression.operator == .negate or expression.operator == .bit_not) {
state.cells[usize(id)].numeric = true
}
operand :: expression.operand
try collect_expression(state, operand)
try depend(state, operand, id)
}
.binary: {
expression :: ast.binary_expr(tree, id)
if (!comparison(expression.operator) and !logical(expression.operator)) state.cells[usize(id)].producer = id
if (comparison(expression.operator) or logical(expression.operator)) {
state.cells[usize(id)].type = Type{ kind = .bool }
} else {
state.cells[usize(id)].numeric = true
}
try collect_expression(state, expression.left)
try collect_expression(state, expression.right)
try depend(state, expression.left, id)
# the RHS may be skipped, so its errors do not erase the bool result.
if (!logical(expression.operator)) try depend(state, expression.right, id)
}
.range: {
expression :: ast.range_expr(tree, id)
state.cells[usize(id)].type = Type{ kind = .range }
if (!state.cells[usize(id)].for_iterable and !state.cells[usize(id)].match_pattern) {
state.cells[usize(id)].type = invalid_type(try diagnostics.add(
state.diagnostics, .range_value_not_supported, expression.span,
))
}
try collect_expression(state, expression.start)
try collect_expression(state, expression.end)
try depend(state, expression.start, id)
try depend(state, expression.end, id)
}
.call: {
callee :: ast.call_expr(tree, id).callee
destination :: conversion_kind(state, id)
if (destination != .none) {
state.cells[usize(callee)].role = .annotation
state.cells[usize(callee)].type = Type{ kind = destination }
state.types[usize(callee)] = Type{ kind = destination }
state.cells[usize(id)].type = Type{ kind = destination }
} else if (type_reference(state, callee)) {
destination_type :: try check_annotation(state, callee, true)
state.cells[usize(id)].type = destination_type
state.backings[usize(id)] = immediate_backing(state, destination_type)
} else {
try collect_expression(state, callee)
try depend(state, callee, id)
}
for (0..ast.call_arg_count(tree, id)) |i| {
argument :: ast.call_arg(tree, id, i)
try collect_expression(state, argument)
try depend(state, argument, id)
}
}
.array_type: {
state.cells[usize(id)].type = invalid_type(try diagnostics.add(state.diagnostics, .type_not_value, ast.node_span(tree, id)))
}
.record_type, .union_type, .enum_type, .distinct_type, .union_tag_type,
.pointer_type, .function_type, .slice_type, .optional_type, .fallible_type, .sum_type: {
type := try check_type_expression(state, id, state.cells[usize(id)].nominal)
if (type.kind != .invalid) {
type.value_kind = type.kind
type.kind = .type_value
}
state.cells[usize(id)].type = type
}
.enum_literal: state.cells[usize(id)].literal = id
.match_expr: try collect_match(state, id)
.alias_expr: {
operand :: ast.type_operand(tree, id)
if (type_reference(state, operand)) {
type := try check_type_expression(state, id, false)
if (type.kind != .invalid) {
type.value_kind = type.kind
type.kind = .type_value
}
state.cells[usize(id)].type = type
} else {
try collect_expression(state, operand)
join(state, id, operand)
try depend(state, operand, id)
}
}
.record_literal: {
state.cells[usize(id)].literal = id
if (ast.record_literal(tree, id).type) |annotation| {
if (ast.expr_kind(tree, annotation) == .enum_literal) {
try collect_expression(state, annotation)
} else {
state.cells[usize(id)].expected = try check_annotation(state, annotation, false)
try depend(state, annotation, id)
}
}
for 0..ast.record_field_count(tree, id) |i| {
value :: ast.field(tree, ast.record_field(tree, id, i)).value?
try collect_expression(state, value)
try depend(state, value, id)
}
}
.array_literal: {
state.cells[usize(id)].literal = id
for 0..ast.array_element_count(tree, id) |i| {
element :: ast.array_element(tree, id, i)
try collect_expression(state, element)
try depend(state, element, id)
}
}
.index: {
expression :: ast.index_expr(tree, id)
try collect_expression(state, expression.base)
try collect_expression(state, expression.index)
try depend(state, expression.base, id)
try depend(state, expression.index, id)
}
.invalid: collect_invalid(state, id)
}
}
@hide:file
declaration_reference proc(tree @ast.Tree, id ast.NodeId) bool {
kind :: ast.expr_kind(tree, id)
return kind == .identifier or kind == .selection
}
@hide:file
integer_kind proc(kind TypeKind) bool {
return (
kind == .int or kind == .i8 or kind == .i16 or kind == .i32 or kind == .isize
or unsigned_kind(kind)
)
}
@hide:file
unsigned_kind proc(kind TypeKind) bool {
return kind == .u8 or kind == .u16 or kind == .u32 or kind == .u64 or kind == .usize
}
@hide:file
numeric_kind proc(kind TypeKind) bool {
return integer_kind(kind) or kind == .float
}
@hide:file
builtin_kinds :: $enummap.init(sema.BuiltinType, TypeKind, {
int = .int, i64 = .int,
float = .float, f64 = .float,
i8 = .i8,
i16 = .i16,
i32 = .i32,
isize = .isize,
u8 = .u8,
u16 = .u16,
u32 = .u32,
u64 = .u64, uint = .u64,
usize = .usize,
bool = .bool,
string = .string,
void = .void,
anyopaque = .anyopaque,
type = .type_value,
c_int = .i32,
c_uint = .u32,
c_long = .int, c_longlong = .int,
c_ulong = .u64, c_ulonglong = .u64,
c_char = .i8,
c_uchar = .u8,
c_short = .i16,
c_ushort = .u16,
unavailable = .none,
})
@hide:file
conversion_kind proc(state @State, id ast.NodeId) TypeKind {
callee :: ast.call_expr(state.tree, id).callee
binding :: sema.binding(state.resolution, callee)
if (binding.kind != .builtin) return .none
kind :: enummap.get(&builtin_kinds, binding.data.builtin)?
return if (numeric_kind(kind)) kind else .none
}
@hide:file
comparison proc(operator ast.BinaryOperator) bool {
return (
operator == .equal or operator == .not_equal or operator == .less
or operator == .less_equal or operator == .greater or operator == .greater_equal
)
}
@hide:file
logical proc(operator ast.BinaryOperator) bool {
return operator == .logical_and or operator == .logical_or
}
@hide:file
shift proc(operator ast.BinaryOperator) bool {
return operator == .shift_left or operator == .shift_right or operator == .shift_left_saturating
}
@hide:file
bitwise proc(operator ast.BinaryOperator) bool {
return operator == .bit_and or operator == .bit_or or operator == .bit_xor or shift(operator)
}
@hide:file
integer_widths :: $enummap.init_default(
TypeKind, usize, usize(0), {
i8 = 8, u8 = 8,
i16 = 16, u16 = 16,
i32 = 32, u32 = 32,
int = 64, isize = 64, u64 = 64, usize = 64,
})
@hide:file
integer_width proc(kind TypeKind) usize {
if (enummap.get(&integer_widths, kind)) |width| return width
return 0
}
@hide:file
pointer_addition proc(state @mut State, id ast.NodeId) bool {
expression :: ast.binary_expr(state.tree, id)
left :: group_type(state, expression.left)
return (
expression.operator == .add and left.kind == .pointer
and left.nominal == ast.NodeId(maxval!(u32))
and state.pointers.items[usize(left.pointer_type)].many
and state.pointers.items[usize(left.pointer_type)].element.kind != .anyopaque
)
}
@hide:file
bool_equality proc(state @mut State, id ast.NodeId) bool {
expression :: ast.binary_expr(state.tree, id)
left :: group_type(state, expression.left)
right :: group_type(state, expression.right)
return (
(expression.operator == .equal or expression.operator == .not_equal)
and ((left.kind == .bool and right.kind == .bool)
or left.kind == .enumeration or right.kind == .enumeration
or left.kind == .pointer or right.kind == .pointer
or left.kind == .optional or right.kind == .optional
or left.kind == .null or right.kind == .null)
)
}
@hide:file
comparison_operands proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
if (state.types[usize(id)].kind == .invalid) return
expression :: ast.binary_expr(state.tree, id)
left :: root(state, usize(expression.left))
right :: root(state, usize(expression.right))
# only operand groups receive numeric demands; the result stays bool.
if (state.cells[left].numeric or state.cells[left].type.kind == .none) {
state.cells[left].numeric = true
try numeric_constraint(state, expression.left, expression.right, id)
} else if (state.cells[right].numeric or state.cells[right].type.kind == .none) {
state.cells[right].numeric = true
try numeric_constraint(state, expression.right, expression.left, id)
}
}
@hide:file
impose proc(state @mut State, id ast.NodeId, kind TypeKind, code diagnostics.Code, span ast.Span) void ! (mem.AllocError | diagnostics.CapacityError) {
index :: root(state, usize(id))
if (state.cells[index].type.kind == .invalid) return
state.cells[index].anchored = true
if (state.cells[index].demand == .none) {
state.cells[index].demand = kind
} else if (state.cells[index].demand != kind) {
state.cells[index].type = invalid_type(try diagnostics.add(state.diagnostics, code, span))
}
}
@hide:file
numeric_operand proc(state @mut State, expression, operand ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
try numeric_constraint(state, expression, operand, expression)
}
@hide:file
numeric_constraint proc(state @mut State, expression, operand, operation ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
index :: root(state, usize(operand))
type :: state.cells[index].type
if (state.types[usize(expression)].kind == .invalid or state.types[usize(operand)].kind == .invalid) return
if (group_type(state, expression).kind == .invalid) return
if (state.cells[index].numeric or type.kind == .none) {
expression_demand :: state.cells[root(state, usize(expression))].demand
operand_demand :: state.cells[index].demand
a :: state.cells[root(state, usize(expression))].expected
b :: state.cells[index].expected
if (a.kind != .none and b.kind != .none and !same_type(state, a, b)) {
try fail(state, operation, .invalid_operand, ast.node_span(state.tree, operation))
return
}
join(state, expression, operand)
if (expression_demand != .none) {
try impose(state, expression, expression_demand, .invalid_operand, ast.node_span(state.tree, operation))
}
if (operand_demand != .none) {
try impose(state, expression, operand_demand, .invalid_operand, ast.node_span(state.tree, operation))
}
} else if (numeric_kind(type.kind)) {
try demand(state, expression, type, .invalid_operand)
}
}
@hide:file
demand proc(state @mut State, id ast.NodeId, expected Type, code diagnostics.Code) void ! (mem.AllocError | diagnostics.CapacityError) {
if (state.types[usize(id)].kind == .invalid) return
index :: root(state, usize(id))
if (state.cells[usize(id)].role == .expression and ast.expr_kind(state.tree, id) == .builtin_call) {
name :: builtin_name(state, id)
if (mem.eql(name, "ptrcast") or mem.eql(name, "constcast")) {
state.cells[index].expected = expected
seed(state, id, expected)
return
}
if (mem.eql(name, "some") and expected.kind != .fallible) {
state.cells[index].expected = expected
if (expected.kind == .optional) {
seed(state, id, expected)
if (ast.call_arg_count(state.tree, id) == 1) {
try demand(state, ast.call_arg(state.tree, id, 0),
state.arrays.items[usize(expected.array_type)].element, code)
}
}
return
}
}
if (
state.cells[usize(id)].role == .value_body
or (state.cells[usize(id)].role == .expression and ast.expr_kind(state.tree, id) == .block_expr)
) {
state.cells[index].expected = expected
try demand_yields(state, id, expected, code)
seed(state, id, expected)
return
}
if (state.cells[usize(id)].role == .expression and ast.expr_kind(state.tree, id) == .match_expr) {
state.cells[index].expected = expected
for 0..ast.match_arm_count(state.tree, id) |i| {
arm :: ast.match_arm_view(state.tree, ast.match_arm(state.tree, id, i))
try demand(state, arm.body, expected, code)
}
return
}
if (expected.kind == .fallible) {
descriptor :: state.arrays.items[usize(expected.array_type)]
actual := group_type(state, id)
if (
state.cells[usize(id)].role == .expression and declaration_reference(state.tree, id)
and has_narrowing(state, id)
) actual = try infer_shape(state, id, false)
if (actual.kind == .fallible) return
target := descriptor.element
literal :: state.cells[index].literal
if (actual.kind == .none and state.cells[index].expected.kind != .none) actual = state.cells[index].expected
if (literal != ast.NodeId(maxval!(u32))) {
name := ast.NodeId(maxval!(u32))
if (ast.expr_kind(state.tree, literal) == .enum_literal) name = ast.enum_literal(state.tree, literal)
if (ast.expr_kind(state.tree, literal) == .record_literal) {
if (ast.record_literal(state.tree, literal).type) |annotation| {
if (ast.expr_kind(state.tree, annotation) == .enum_literal) name = ast.enum_literal(state.tree, annotation)
}
if (
name == ast.NodeId(maxval!(u32)) and actual.kind == .none
and descriptor.element.kind != .record and descriptor.error_type.kind == .record
and !state.records.items[usize(descriptor.error_type.record_type)].is_union
) target = descriptor.error_type
}
if (
name != ast.NodeId(maxval!(u32)) and type_has_variant(state, descriptor.error_type, name)
and !type_has_variant(state, descriptor.element, name)
) target = descriptor.error_type
else if (
actual.kind != .none and !assignable_type(state, actual, target)
and assignable_type(state, actual, descriptor.error_type)
) target = descriptor.error_type
} else if (
actual.kind != .none and !assignable_type(state, actual, target)
and assignable_type(state, actual, descriptor.error_type)
) target = descriptor.error_type
try demand(state, id, target, code)
return
}
if (expected.kind == .optional) {
if (group_type(state, id).kind != .optional and group_type(state, id).kind != .null) {
try demand(state, id, state.arrays.items[usize(expected.array_type)].element, code)
}
return
}
if (expected.kind == .slice) {
state.cells[index].expected = expected
if (ast.expr_kind(state.tree, id) == .unary and ast.unary_expr(state.tree, id).operator == .address_of) {
operand :: ast.unary_expr(state.tree, id).operand
if (ast.expr_kind(state.tree, operand) == .array_literal) {
descriptor :: state.arrays.items[usize(expected.array_type)]
array :: try intern_array(state, ArrayType{
element = descriptor.element, len = ast.array_element_count(state.tree, operand), mutable = descriptor.mutable,
})
try demand(state, operand, array, code)
seed(state, id, expected)
}
} else if (ast.expr_kind(state.tree, id) == .array_literal) {
descriptor :: state.arrays.items[usize(expected.array_type)]
array :: try intern_array(state, ArrayType{
element = descriptor.element, len = ast.array_element_count(state.tree, id), mutable = descriptor.mutable,
})
try demand(state, id, array, code)
}
return
}
if (
expected.kind == .pointer and state.cells[usize(id)].role == .expression
and ast.expr_kind(state.tree, id) == .unary
and ast.unary_expr(state.tree, id).operator == .address_of
) {
state.cells[index].expected = expected
try demand(state, ast.unary_expr(state.tree, id).operand,
state.pointers.items[usize(expected.pointer_type)].element, code)
return
}
literal_node :: state.cells[index].literal
if (
expected.nominal != ast.NodeId(maxval!(u32))
and (expected.kind == .record or expected.kind == .array)
) return
if (literal_node != ast.NodeId(maxval!(u32)) and ast.expr_kind(state.tree, literal_node) == .enum_literal) {
previous :: state.cells[index].expected
if (previous.kind != .none and !same_type(state, previous, expected)) {
try fail(state, id, code, ast.node_span(state.tree, id))
return
}
state.cells[index].expected = expected
_ = try infer_enum_literal(state, literal_node, expected, false)
return
}
if (expected.kind == .record) {
literal :: state.cells[index].literal
if (literal == ast.NodeId(maxval!(u32)) or ast.expr_kind(state.tree, literal) != .record_literal) return
annotation :: ast.record_literal(state.tree, literal).type
contextual :: annotation == null or ast.expr_kind(state.tree, annotation?) == .enum_literal
if (contextual) {
previous :: state.cells[index].expected
if (previous.kind == .record and !same_type(state, previous, expected)) {
try fail(state, literal, code, ast.node_span(state.tree, literal))
return
}
state.cells[index].expected = expected
}
_ = try infer_record_literal(state, literal, index, false)
return
}
if (expected.kind == .array) {
literal :: state.cells[index].literal
if (literal == ast.NodeId(maxval!(u32))) return
if (ast.expr_kind(state.tree, literal) != .array_literal) return
previous :: state.cells[index].expected
if (previous.kind == .array and !same_type(state, previous, expected)) {
if (!has_inferred_count(state, previous) and !has_inferred_count(state, expected)) {
state.cells[index].type = invalid_type(try diagnostics.add(state.diagnostics, code, ast.node_span(state.tree, id)))
return
}
}
state.cells[index].expected = expected
descriptor :: state.arrays.items[usize(expected.array_type)]
len :: ast.array_element_count(state.tree, literal)
if (descriptor.len != no_index and descriptor.len != len) {
try fail(state, literal, .array_length_mismatch, ast.node_span(state.tree, literal))
}
for (0..len) |i| try demand(state, ast.array_element(state.tree, literal, i), descriptor.element, code)
return
}
if (!numeric_kind(expected.kind)) return
if (!state.cells[index].numeric and state.cells[index].type.kind != .none) return
state.cells[index].numeric = true
if (
state.cells[index].expected.kind != .none
and !same_type(state, state.cells[index].expected, expected)
) {
state.cells[index].type = invalid_type(try diagnostics.add(state.diagnostics, code, ast.node_span(state.tree, id)))
return
}
state.cells[index].expected = expected
try impose(state, id, expected.kind, code, ast.node_span(state.tree, id))
}
@hide:file
range_bound_context proc(state @mut State, id ast.NodeId, family TypeKind) void ! (mem.AllocError | diagnostics.CapacityError) {
if (state.types[usize(id)].kind == .invalid) return
index :: root(state, usize(id))
if (state.cells[index].type.kind == .invalid) return
if (state.cells[index].demand != .none and state.cells[index].demand != family) {
try fail(state, id, .invalid_range_bound, ast.node_span(state.tree, id))
return
}
try demand(state, id, Type{ kind = family }, .invalid_range_bound)
}
@hide:file
fail proc(state @mut State, id ast.NodeId, code diagnostics.Code, span ast.Span) void ! (mem.AllocError | diagnostics.CapacityError) {
if (state.types[usize(id)].kind == .invalid) return
state.types[usize(id)] = invalid_type(try diagnostics.add(state.diagnostics, code, span))
}
@hide:file
require_bool proc(state @mut State, operand ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
type :: state.types[usize(operand)]
if (type.kind != .invalid and (type.kind != .bool or type.nominal != ast.NodeId(maxval!(u32)))) {
try fail(state, operand, .invalid_operand, ast.node_span(state.tree, operand))
}
}
@hide:file
mutable_target proc(state @mut State, id ast.NodeId, allow_global bool) bool {
kind :: ast.expr_kind(state.tree, id)
if (kind == .unary) {
view :: ast.unary_expr(state.tree, id)
base :: group_type(state, view.operand)
return (
view.operator == .dereference and base.kind == .pointer
and state.pointers.items[usize(base.pointer_type)].mutable
)
}
if (kind == .index) {
view :: ast.index_expr(state.tree, id)
base :: group_type(state, view.base)
if (base.kind == .pointer) {
pointer :: state.pointers.items[usize(base.pointer_type)]
return (
pointer.mutable
and (pointer.many
or (pointer.element.kind == .array and state.arrays.items[usize(pointer.element.array_type)].mutable))
)
}
if (base.kind == .slice) return state.arrays.items[usize(base.array_type)].mutable
return base.kind == .array and state.arrays.items[usize(base.array_type)].mutable and mutable_target(state, view.base, allow_global)
}
if (kind == .selection and sema.binding(state.resolution, id).kind == .none) {
view :: ast.selection_expr(state.tree, id)
base :: group_type(state, view.base)
if (base.kind == .pointer) {
pointer :: state.pointers.items[usize(base.pointer_type)]
return pointer.element.kind == .record and pointer.mutable
}
return base.kind == .record and mutable_target(state, view.base, allow_global)
}
binding :: sema.binding(state.resolution, id)
if (binding.kind != .declaration) return false
target :: binding.data.declaration
if (state.cells[usize(target)].role != .declaration or (!allow_global and !state.cells[usize(target)].local)) return false
return ast.statement_decl(state.tree, target).mutability == .variable
}
@hide:file
indexable_type proc(state @State, type Type) bool {
if (type.nominal != ast.NodeId(maxval!(u32))) return false
if (type.kind == .array or type.kind == .slice or type.kind == .string) return true
if (type.kind != .pointer) return false
pointer :: state.pointers.items[usize(type.pointer_type)]
return (pointer.many and pointer.element.kind != .anyopaque) or pointer.element.kind == .array
}
@hide:file
collection_type proc(state @State, type Type) bool {
if (type.nominal != ast.NodeId(maxval!(u32))) return false
if (type.kind == .array or type.kind == .slice or type.kind == .string) return true
if (type.kind != .pointer) return false
pointer :: state.pointers.items[usize(type.pointer_type)]
return !pointer.many and pointer.element.kind == .array and pointer.element.nominal == ast.NodeId(maxval!(u32))
}
@hide:file
addressable_target proc(state @mut State, id ast.NodeId) bool {
kind :: ast.expr_kind(state.tree, id)
if (kind == .unary) {
view :: ast.unary_expr(state.tree, id)
return view.operator == .dereference and group_type(state, view.operand).kind == .pointer
}
if (kind == .index) {
view :: ast.index_expr(state.tree, id)
base :: group_type(state, view.base)
return indexable_type(state, base) and (base.kind == .pointer or base.kind == .slice or base.kind == .string or addressable_target(state, view.base))
}
if (kind == .selection and sema.binding(state.resolution, id).kind == .none) {
view :: ast.selection_expr(state.tree, id)
base :: group_type(state, view.base)
if (base.kind == .pointer) {
pointer :: state.pointers.items[usize(base.pointer_type)]
return pointer.element.kind == .record
}
return base.kind == .record and addressable_target(state, view.base)
}
binding :: sema.binding(state.resolution, id)
if (binding.kind != .declaration) return false
role :: state.cells[usize(binding.data.declaration)].role
return role == .declaration or role == .parameter or role == .loop_capture
}
@hide:file
same_signature proc(state @State, left, right ast.NodeId) bool {
if (left == right) return true
if (ast.callable_foreign(state.tree, left) != ast.callable_foreign(state.tree, right)) return false
if (ast.callable_variadic(state.tree, left) != ast.callable_variadic(state.tree, right)) return false
tree :: state.tree
if (ast.function_param_count(tree, left) != ast.function_param_count(tree, right)) return false
left_result :: ast.callable_result(tree, left)
right_result :: ast.callable_result(tree, right)
if (!same_type(state, state.types[usize(left_result)], state.types[usize(right_result)])) return false
for (0..ast.function_param_count(tree, left)) |i| {
left_param :: ast.param(tree, ast.function_param(tree, left, i)).type
right_param :: ast.param(tree, ast.function_param(tree, right, i)).type
if (!same_type(state, state.types[usize(left_param)], state.types[usize(right_param)])) return false
}
return true
}
@hide:file
assignable_type proc(state @State, actual, expected Type) bool {
if (same_type(state, actual, expected)) return true
if (expected.kind == .fallible) {
target :: state.arrays.items[usize(expected.array_type)]
if (actual.kind == .fallible) {
source :: state.arrays.items[usize(actual.array_type)]
return (
assignable_type(state, source.element, target.element)
and error_subset(state.records.items, state.enums.items, source.error_type, target.error_type)
)
}
return (
assignable_type(state, actual, target.element)
or error_subset(state.records.items, state.enums.items, actual, target.error_type)
)
}
if (
(composed_type(state, actual) or composed_type(state, expected))
and error_subset(state.records.items, state.enums.items, actual, expected)
) return true
if (expected.kind == .optional) {
child :: state.arrays.items[usize(expected.array_type)].element
if (actual.kind == .null) return true
if (actual.kind == .optional) {
source :: state.arrays.items[usize(actual.array_type)].element
if (source.kind != child.kind) return false
return assignable_type(state, source, child)
}
return assignable_type(state, actual, child)
}
if (expected.kind == .slice) {
b :: state.arrays.items[usize(expected.array_type)]
if (actual.kind == .string) return !b.mutable and b.element.kind == .u8 and (b.sentinel == null or b.sentinel? == 0)
if (actual.kind == .slice or actual.kind == .array) {
a :: state.arrays.items[usize(actual.array_type)]
return (
(!b.mutable or a.mutable) and (b.sentinel == null or same_sentinel(a.sentinel, b.sentinel))
and same_type(state, a.element, b.element)
)
}
}
if (actual.kind != .pointer or expected.kind != .pointer or actual.nominal != expected.nominal) return false
a :: state.pointers.items[usize(actual.pointer_type)]
b :: state.pointers.items[usize(expected.pointer_type)]
if (a.many != b.many or (!a.mutable and b.mutable)) return false
if (b.element.kind == .anyopaque and b.element.nominal == ast.NodeId(maxval!(u32))) return true
if (b.mutable) return same_type(state, a.element, b.element)
return assignable_type(state, a.element, b.element)
}
@hide:file
composed_type proc(state @State, type Type) bool {
if (type.kind == .enumeration) return state.enums.items[usize(type.enum_type)].composed
if (type.kind == .record) return state.records.items[usize(type.record_type)].composed
return false
}
@hide:file
compatible proc(state @mut State, value ast.NodeId, expected Type) void ! (mem.AllocError | diagnostics.CapacityError) {
actual :: state.types[usize(value)]
if (actual.kind == .invalid or expected.kind == .invalid) return
if (!assignable_value(state, value, actual, expected)) {
try fail(state, value, .type_mismatch, ast.node_span(state.tree, value))
}
}
@hide:file
assignable_value proc(state @mut State, value ast.NodeId, actual, expected Type) bool {
if (!assignable_type(state, actual, expected)) return false
target := expected
while (target.kind == .optional) target = state.arrays.items[usize(target.array_type)].element
if (
actual.kind == .array and target.kind == .slice
and state.arrays.items[usize(target.array_type)].mutable
and addressable_target(state, value)
) return mutable_target(state, value, true)
return true
}
@hide:file
validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
tree :: state.tree
if (state.cells[usize(id)].role == .expression and ast.expr_kind(tree, id) == .match_expr) {
try validate_match(state, id)
return
}
if (state.cells[usize(id)].role == .yield_stmt) {
owner :: state.cells[usize(id)].yield_owner
if (owner == ast.NodeId(maxval!(u32)) or state.types[usize(id)].kind == .invalid) return
expected :: state.types[usize(owner)]
if (expected.kind == .none or expected.kind == .invalid) return
if (ast.yield_stmt(tree, id).value) |value| {
actual :: state.types[usize(value)]
if (actual.kind != .invalid and !assignable_value(state, value, actual, expected)) {
try fail(state, value, .yield_type_mismatch, ast.node_span(tree, value))
}
}
return
}
match state.cells[usize(id)].role {
.declaration: {
declaration :: ast.statement_decl(tree, id)
if (!expression_falls_through(state, declaration.initializer)) return
if (declaration.type == null and state.types[usize(declaration.initializer)].kind == .null) {
try fail(state, declaration.initializer, .cannot_infer_type, ast.node_span(tree, declaration.initializer))
}
if (state.types[usize(declaration.initializer)].kind == .void) {
try fail(state, declaration.initializer, .type_mismatch, ast.node_span(tree, declaration.initializer))
}
if (declaration.type) |type| try compatible(state, declaration.initializer, state.types[usize(type)])
}
.assignment_stmt: {
assignment :: ast.assignment_stmt(tree, id)
target_type :: state.types[usize(assignment.target)]
if (target_type.kind == .invalid) return
if (mutable_target(state, assignment.target, false)) {
try compatible(state, assignment.value, target_type)
} else {
kind :: ast.expr_kind(tree, assignment.target)
code := if (kind == .index) diagnostics.Code.invalid_index_assignment else diagnostics.Code.invalid_assignment_target
if (kind == .unary and ast.unary_expr(tree, assignment.target).operator == .dereference) {
code = .invalid_pointer_assignment
} else if (kind == .index and state.types[usize(ast.index_expr(tree, assignment.target).base)].kind == .pointer) {
code = .invalid_pointer_assignment
} else if (kind == .selection and state.types[usize(ast.selection_expr(tree, assignment.target).base)].kind == .pointer) {
code = .invalid_pointer_assignment
}
try fail(state, id, code, ast.node_span(tree, assignment.target))
}
}
.return_stmt: {
statement :: ast.return_stmt(tree, id)
if (state.cells[usize(id)].return_annotation == ast.NodeId(maxval!(u32))) {
try fail(state, id, .return_type_mismatch, statement.span)
return
}
expected :: state.types[usize(state.cells[usize(id)].return_annotation)]
if (expected.kind == .invalid) return
if (state.types[usize(id)].kind == .invalid) return
if (statement.value) |value| {
actual :: state.types[usize(value)]
if (actual.kind == .invalid) {
state.types[usize(id)] = actual
} else if (!assignable_value(state, value, actual, expected)) {
try fail(state, value, .return_type_mismatch, ast.node_span(tree, value))
state.types[usize(id)] = state.types[usize(value)]
}
} else if (
expected.kind != .void
and !(expected.kind == .fallible and state.arrays.items[usize(expected.array_type)].element.kind == .void)
) try fail(state, id, .return_type_mismatch, statement.span)
}
.expression: {
match ast.expr_kind(tree, id) {
.typed_constant: {
value :: ast.typed_constant(tree, id)
try compatible(state, value.value, state.cells[usize(id)].type)
}
.union_constant: {
value :: ast.union_constant(tree, id)
type :: state.cells[usize(id)].type
if (type.kind == .invalid) return
if (type.kind != .record or ast.expr_kind(tree, value.value) != .literal_string) {
try fail(state, id, .invalid_operand, ast.node_span(tree, id))
return
}
descriptor :: state.records.items[usize(type.record_type)]
if (!descriptor.is_union or descriptor.tag_type.kind != .none)
try fail(state, id, .invalid_operand, ast.node_span(tree, id))
}
.try_expr, .catch_expr: try validate_fallible_expression(state, id)
.unary: {
expression :: ast.unary_expr(tree, id)
if (expression.operator == .logical_not) {
try require_bool(state, expression.operand)
return
}
if (expression.operator == .address_of) {
if (!addressable_target(state, expression.operand)) {
if (state.types[usize(id)].kind == .slice) return
try fail(state, id, .invalid_address_operand, expression.span)
}
return
}
if (expression.operator == .unwrap) {
if (state.types[usize(expression.operand)].kind != .optional and state.types[usize(expression.operand)].kind != .invalid) try fail(state, id, .invalid_operand, expression.span)
return
}
if (expression.operator == .dereference) {
if (
state.types[usize(expression.operand)].kind != .invalid
and state.types[usize(expression.operand)].kind != .pointer
) try fail(state, id, .invalid_dereference, expression.span)
return
}
operand :: state.types[usize(expression.operand)]
if (expression.operator == .bit_not) {
if (operand.kind != .invalid and !integer_kind(operand.kind)) try fail(state, id, .invalid_operand, expression.span)
} else if (operand.kind != .invalid and (!numeric_kind(operand.kind) or unsigned_kind(operand.kind))) try fail(state, id, .invalid_operand, ast.node_span(tree, id))
}
.binary: {
expression :: ast.binary_expr(tree, id)
if (logical(expression.operator)) {
try require_bool(state, expression.left)
try require_bool(state, expression.right)
return
}
left :: state.types[usize(expression.left)]
right :: state.types[usize(expression.right)]
if (left.kind == .invalid or right.kind == .invalid) return
if (shift(expression.operator)) {
if (!integer_kind(left.kind) or !unsigned_kind(right.kind)) {
try fail(state, id, .invalid_operand, expression.span)
} else if (expression.operator != .shift_left_saturating) {
if (evaluate_integer(state, expression.right, true)) |count| {
if (count.negative or count.magnitude >= u64(integer_width(left.kind)))
try fail(state, id, .invalid_shift_count, expression.span)
}
}
return
}
if (bitwise(expression.operator) and (!integer_kind(left.kind) or !integer_kind(right.kind))) {
try fail(state, id, .invalid_operand, expression.span)
return
}
if (pointer_addition(state, id)) {
if (right.kind != .usize or right.nominal != ast.NodeId(maxval!(u32))) try fail(state, id, .invalid_operand, expression.span)
return
}
if (
(expression.operator == .equal or expression.operator == .not_equal)
and ((left.kind == .optional and right.kind == .null) or (left.kind == .null and right.kind == .optional))
) return
if (
(expression.operator == .equal or expression.operator == .not_equal)
and (left.kind == .bool or left.kind == .enumeration or left.kind == .pointer)
and (assignable_type(state, left, right) or assignable_type(state, right, left))
) return
if (!numeric_kind(left.kind) or !numeric_kind(right.kind) or !same_type(state, left, right)) try fail(state, id, .invalid_operand, expression.span)
else if (expression.operator == .divide and left.kind != .float) try fail(state, id, .integer_division, expression.span)
}
.range: {
if (state.cells[usize(id)].match_pattern) return
expression :: ast.range_expr(tree, id)
start_type :: state.types[usize(expression.start)]
end_type :: state.types[usize(expression.end)]
family TypeKind :: if (integer_kind(start_type.kind)) start_type.kind
else if (integer_kind(end_type.kind)) end_type.kind else .int
if (start_type.kind != .invalid and (!integer_kind(start_type.kind) or start_type.kind != family)) try fail(state, expression.start, .invalid_range_bound, ast.node_span(tree, expression.start))
if (end_type.kind != .invalid and (!integer_kind(end_type.kind) or end_type.kind != family)) try fail(state, expression.end, .invalid_range_bound, ast.node_span(tree, expression.end))
}
.array_literal: {
actual :: state.types[usize(id)]
if (actual.kind != .array) return
element :: state.arrays.items[usize(actual.array_type)].element
for 0..ast.array_element_count(tree, id) |i| {
child :: ast.array_element(tree, id, i)
if (element.kind == .void or element.kind == .range) try fail(state, child, .invalid_array_element_type, ast.node_span(tree, child))
else try compatible(state, child, element)
}
if (array_cells(state, actual) == null) try fail(state, id, .array_size_overflow, ast.node_span(tree, id))
}
.record_literal: {
actual :: state.types[usize(id)]
if (actual.kind != .record) return
descriptor := state.records.items[usize(actual.record_type)]
if (state.variant_slots[usize(id)] != no_index and state.backings[usize(id)].kind == .record) descriptor = state.records.items[usize(state.backings[usize(id)].record_type)]
fields :: descriptor.fields
slot :: state.slots[usize(id)]
if (slot == no_index) return
values :: state.initializers.items[slot].fields
for fields |field, i| {
if (values[i] != ast.NodeId(maxval!(u32))) try compatible(state, values[i], field.type)
}
if (array_cells(state, actual) == null) try fail(state, id, .array_size_overflow, ast.node_span(tree, id))
}
.index: {
view :: ast.index_expr(tree, id)
base :: state.types[usize(view.base)]
index :: state.types[usize(view.index)]
if (index.kind != .invalid and (index.kind != .usize or index.nominal != ast.NodeId(maxval!(u32)))) try fail(state, view.index, .invalid_index_type, ast.node_span(tree, view.index))
if (base.kind != .invalid and !indexable_type(state, base)) try fail(state, id, .not_indexable, ast.node_span(tree, view.base))
}
.call: try validate_call(state, id)
.builtin_call: _ = try infer_builtin(state, id, true)
.slice: {
view :: ast.slice_expr(tree, id)
if (view.start) |node| try compatible(state, node, Type{ kind = .usize })
if (view.end) |node| try compatible(state, node, Type{ kind = .usize })
}
else: {}
}
}
else: {}
}
}
@hide:file
validate_call proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
tree :: state.tree
expression :: ast.call_expr(tree, id)
if (state.backings[usize(id)].kind != .none) {
try validate_constructor(state, id)
return
}
if (conversion_kind(state, id) != .none) {
if (ast.call_arg_count(tree, id) != 1) {
try fail(state, id, .argument_count_mismatch, expression.span)
}
for 0..ast.call_arg_count(tree, id) |i| {
argument :: ast.call_arg(tree, id, i)
actual :: state.types[usize(argument)]
allowed_enum :: (
actual.kind == .enumeration
and state.enums.items[usize(actual.enum_type)].explicit_backing
)
backing :: immediate_backing(state, actual)
if (
actual.kind != .invalid
and (!numeric_kind(backing.kind) and !allowed_enum
or backing.nominal != ast.NodeId(maxval!(u32)))
) try fail(state, argument, .invalid_operand, ast.node_span(tree, argument))
}
return
}
callee :: state.types[usize(expression.callee)]
signature :: group_type(state, expression.callee)
if (signature.function == ast.NodeId(maxval!(u32))) {
if (callee.kind != .invalid) {
try fail(state, id, .not_callable, ast.node_span(tree, expression.callee))
}
return
}
parameter_count :: ast.function_param_count(tree, signature.function)
if (parameter_count != ast.call_arg_count(tree, id)) {
diagnostic :: try diagnostics.add(state.diagnostics, .argument_count_mismatch, expression.span)
if (callee.kind != .invalid) state.types[usize(id)] = invalid_type(diagnostic)
}
for (0..ast.call_arg_count(tree, id)) |i| {
if (i >= parameter_count) continue
parameter :: ast.function_param(tree, signature.function, i)
try compatible(state, ast.call_arg(tree, id, i), state.types[usize(parameter)])
}
}
@hide:file
BlockFlow :: struct {
falls bool = true
yields bool = false
breaks bool = false
}
@hide:file
block_falls_through proc(state @State, id ast.NodeId) bool {
return block_flow(state, id).falls
}
@hide:file
value_block_completes proc(state @State, id ast.NodeId) bool {
flow :: block_flow(state, id)
return flow.falls or flow.yields
}
@hide:file
expression_falls_through proc(state @State, id ast.NodeId) bool {
tree :: state.tree
match ast.expr_kind(tree, id) {
.block_expr: return value_block_completes(state, ast.block_expr(tree, id))
.match_expr: {
if (!state.exhaustive[usize(id)]) return true
for 0..ast.match_arm_count(tree, id) |i| {
arm :: ast.match_arm_view(tree, ast.match_arm(tree, id, i))
if (arm.expression) {
if (expression_falls_through(state, arm.body)) return true
} else if (value_block_completes(state, arm.body)) return true
}
return false
}
else: return true
}
}
@hide:file
expression_breaks proc(state @State, id ast.NodeId) bool {
tree :: state.tree
if (ast.node_class(tree, id) == .block) return block_flow(state, id).breaks
for 0..ast.child_count(tree, id) |i| {
if (expression_breaks(state, ast.child(tree, id, i))) return true
}
return false
}
@hide:file
block_flow proc(state @State, id ast.NodeId) BlockFlow {
tree :: state.tree
result := BlockFlow{}
for (0..ast.block_statement_count(tree, id)) |i| {
statement :: ast.block_statement(tree, id, i)
kind :: ast.stmt_kind(tree, statement)
source :: if (kind == .expression) ast.statement_expr(tree, statement) else statement
if (state.types[usize(source)].kind == .invalid) {
result.falls = false
return result
}
match kind {
.yield_stmt: {
if (ast.yield_stmt(tree, statement).value) |value|
result.breaks = result.breaks or expression_breaks(state, value)
result.yields = true
result.falls = false
}
.return_stmt: {
if (ast.return_stmt(tree, statement).value) |value|
result.breaks = result.breaks or expression_breaks(state, value)
result.falls = false
}
.break_stmt: {
result.breaks = true
result.falls = false
}
.continue_stmt, .unreachable_stmt, .invalid: result.falls = false
.block_stmt: {
nested :: block_flow(state, ast.statement_block(tree, statement))
result.yields = result.yields or nested.yields
result.breaks = result.breaks or nested.breaks
result.falls = nested.falls
}
.if_stmt: {
conditional :: ast.if_stmt(tree, statement)
then_flow :: block_flow(state, conditional.then_block)
result.yields = result.yields or then_flow.yields
result.breaks = result.breaks or then_flow.breaks or expression_breaks(state, conditional.condition)
if (conditional.else_block) |other| {
else_flow :: block_flow(state, other)
result.yields = result.yields or else_flow.yields
result.breaks = result.breaks or else_flow.breaks
result.falls = then_flow.falls or else_flow.falls
}
}
.while_stmt: {
loop :: ast.while_stmt(tree, statement)
body :: block_flow(state, loop.body)
result.yields = result.yields or body.yields
result.breaks = result.breaks or expression_breaks(state, loop.condition)
if (loop.capture == null and ast.expr_kind(tree, loop.condition) == .literal_bool
and mem.eql(ast.literal_text(tree, loop.condition), "true"))
result.falls = body.breaks
}
.for_stmt: {
loop :: ast.for_stmt(tree, statement)
result.yields = result.yields or block_flow(state, loop.body).yields
result.breaks = result.breaks or expression_breaks(state, loop.iterable)
}
.expression, .declaration, .assignment_stmt: {
value :: if (kind == .expression) source
else if (kind == .declaration) ast.statement_decl(tree, statement).initializer
else ast.assignment_stmt(tree, statement).value
result.breaks = result.breaks or expression_breaks(state, value)
if (kind == .assignment_stmt)
result.breaks = result.breaks or expression_breaks(state, ast.assignment_stmt(tree, statement).target)
result.falls = expression_falls_through(state, value)
}
else: {}
}
if (!result.falls) return result
}
return result
}
@hide:file
ConstantInteger :: struct {
magnitude u64
negative bool = false
}
@hide:file
constant_integer proc(state @mut State, id ast.NodeId) ?ConstantInteger {
return evaluate_integer(state, id, false)
}
@hide:file
constant_bits proc(value ConstantInteger, mask u64) u64 {
if (value.negative) return (value.magnitude - 1) xor mask
return value.magnitude & mask
}
@hide:file
constant_from_bits proc(bits u64, kind TypeKind) ConstantInteger {
mask :: maxval!(u64) >> u64(64 - integer_width(kind))
negative :: !unsigned_kind(kind) and bits > (mask >> 1)
magnitude :: if (negative) 1 + (bits xor mask) else bits
return ConstantInteger{
magnitude = magnitude,
negative = negative,
}
}
@hide:file
constant_bitwise proc(state @mut State, id ast.NodeId, left, right ConstantInteger) ?ConstantInteger {
view :: ast.binary_expr(state.tree, id)
family :: bound_family(state, id)
kind :: if (family == .none) TypeKind.int else family
width :: integer_width(kind)
if (width == 0 or !integer_fits(left, kind)) return null
mask :: maxval!(u64) >> u64(64 - width)
a :: constant_bits(left, mask)
b :: constant_bits(right, mask)
if (view.operator == .bit_and) return constant_from_bits(a & b, kind)
if (view.operator == .bit_or) return constant_from_bits(a | b, kind)
if (view.operator == .bit_xor) return constant_from_bits(a xor b, kind)
if (right.negative) return null
count :: right.magnitude
if (view.operator == .shift_left_saturating) {
signed_limit :: if (left.negative) 1 + (mask >> 1) else mask >> 1
limit :: if (unsigned_kind(kind)) mask else signed_limit
if (left.magnitude == 0) return left
if (count >= u64(width) or left.magnitude > (limit >> count))
return ConstantInteger{ magnitude = limit, negative = left.negative }
} else if (count >= u64(width)) return null
if (view.operator == .shift_right) {
shifted :: a >> count
bits :: if (left.negative) shifted | (mask xor (mask >> count)) else shifted
return constant_from_bits(bits, kind)
}
return constant_from_bits((a << count) & mask, kind)
}
@hide:file
evaluate_integer proc(state @mut State, id ast.NodeId, allow_local bool) ?ConstantInteger {
if (state.cells[usize(id)].constant_visiting) return null
state.cells[usize(id)].constant_visiting = true
defer state.cells[usize(id)].constant_visiting = false
match ast.expr_kind(state.tree, id) {
.typed_constant: return evaluate_integer(state, ast.typed_constant(state.tree, id).value, allow_local)
.literal_int: {
text :: ast.literal_text(state.tree, id)
value := u64(0)
for text |byte| {
if (byte < '0' or byte > '9') return null
digit :: u64(byte - '0')
if (value > divtrunc!(maxval!(u64) - digit, u64(10))) return null
value = value * 10 + digit
}
return ConstantInteger{ magnitude = value }
}
.identifier, .selection: {
if (state.backings[usize(id)].kind == .enumeration and state.slots[usize(id)] != no_index) {
type :: state.backings[usize(id)]
member :: state.enums.items[usize(type.enum_type)].members[state.slots[usize(id)]]
return ConstantInteger{ magnitude = member.magnitude, negative = member.negative }
}
binding :: sema.binding(state.resolution, id)
if (binding.kind != .declaration) return null
target :: binding.data.declaration
if (
!state.cells[usize(target)].count_declaration
and (!allow_local or !state.cells[usize(target)].local)
) return null
declaration :: ast.statement_decl(state.tree, target)
if (declaration.mutability != .constant) return null
if (declaration.type) |annotation| {
type_binding :: sema.binding(state.resolution, annotation)
if (
!integer_kind(state.types[usize(annotation)].kind)
and (type_binding.kind != .builtin or !integer_kind(enummap.get(&builtin_kinds, type_binding.data.builtin)?))
) return null
}
return evaluate_integer(state, declaration.initializer, allow_local)
}
.call: {
if (ast.call_arg_count(state.tree, id) != 1) return null
destination :: state.cells[usize(id)].type
if (!integer_kind(destination.kind)) return null
if (evaluate_integer(state, ast.call_arg(state.tree, id, 0), allow_local)) |value| {
if (integer_fits(value, destination.kind)) return value
}
return null
}
.unary: {
view :: ast.unary_expr(state.tree, id)
if (view.operator != .negate and view.operator != .bit_not) return null
if (evaluate_integer(state, view.operand, allow_local)) |value| {
if (view.operator == .bit_not) {
family :: bound_family(state, id)
kind :: if (family == .none) TypeKind.int else family
if (!integer_kind(kind) or !integer_fits(value, kind)) return null
mask :: maxval!(u64) >> u64(64 - integer_width(kind))
return constant_from_bits(constant_bits(value, mask) xor mask, kind)
}
return ConstantInteger{ magnitude = value.magnitude, negative = value.magnitude != 0 and !value.negative }
}
return null
}
.binary: {
view :: ast.binary_expr(state.tree, id)
left := ConstantInteger{ magnitude = 0 }
right := ConstantInteger{ magnitude = 0 }
if (evaluate_integer(state, view.left, allow_local)) |value| left = value else return null
if (evaluate_integer(state, view.right, allow_local)) |value| right = value else return null
if (bitwise(view.operator)) return constant_bitwise(state, id, left, right)
if (view.operator == .multiply) {
if (right.magnitude != 0 and left.magnitude > divtrunc!(maxval!(u64), right.magnitude)) return null
magnitude :: left.magnitude * right.magnitude
return ConstantInteger{ magnitude = magnitude, negative = magnitude != 0 and left.negative != right.negative }
}
if (view.operator != .add and view.operator != .subtract) return null
if (view.operator == .subtract and right.magnitude != 0) right.negative = !right.negative
if (left.negative == right.negative) {
if (left.magnitude > maxval!(u64) - right.magnitude) return null
return ConstantInteger{ magnitude = left.magnitude + right.magnitude, negative = left.negative }
}
if (left.magnitude < right.magnitude) {
return ConstantInteger{ magnitude = right.magnitude - left.magnitude, negative = right.negative }
}
return ConstantInteger{ magnitude = left.magnitude - right.magnitude, negative = left.magnitude != right.magnitude and left.negative }
}
else: return null
}
}
@hide:file
intern_pointer proc(state @mut State, descriptor PointerType) Type ! mem.AllocError {
for state.pointers.items |existing, i| {
if (
existing.mutable == descriptor.mutable and existing.many == descriptor.many
and same_type(state, existing.element, descriptor.element)
) return Type{ kind = .pointer, pointer_type = PointerId(i) }
}
try arraylist.append(&state.pointers, descriptor)
return Type{ kind = .pointer, pointer_type = PointerId(state.pointers.items.len - 1) }
}
@hide:file
check_pointer_type proc(state @mut State, id ast.NodeId) Type ! (mem.AllocError | diagnostics.CapacityError) {
view :: ast.pointer_type(state.tree, id)
state.pointer_depth += 1
defer state.pointer_depth -= 1
element :: try check_annotation(state, view.element, true)
if (element.kind == .invalid) return element
binding :: sema.binding(state.resolution, view.element)
if (
element.kind == .none or element.kind == .void or element.kind == .range
or element.kind == .type_value
or has_inferred_count(state, element)
or (binding.kind == .builtin and (binding.data.builtin == .int or binding.data.builtin == .float))
) return invalid_type(try diagnostics.add(state.diagnostics, .invalid_pointer_type, ast.node_span(state.tree, id)))
return try intern_pointer(state, PointerType{ element = element, mutable = view.mutable, many = view.many })
}
@hide:file
check_function_type proc(state @mut State, id ast.NodeId) Type ! (mem.AllocError | diagnostics.CapacityError) {
cached := state.cells[usize(id)].type
if (cached.kind == .type_value) cached.kind = cached.value_kind
if (cached.kind != .none) return cached
type := Type{ kind = .function, function = id }
state.cells[usize(id)].type = type
result :: ast.callable_result(state.tree, id)
result_type :: try check_annotation(state, result, true)
if (result_type.kind == .invalid) type = result_type
if (ast.callable_variadic(state.tree, id) or (ast.callable_foreign(state.tree, id) and !c_abi_type(state, result_type)))
type = invalid_type(try diagnostics.add(state.diagnostics, .invalid_operand, ast.node_span(state.tree, id)))
try depend(state, result, id)
for 0..ast.function_param_count(state.tree, id) |i| {
parameter :: ast.function_param(state.tree, id, i)
annotation :: ast.param(state.tree, parameter).type
parameter_type :: try check_annotation(state, annotation, false)
state.cells[usize(parameter)].role = .annotation
state.cells[usize(parameter)].type = parameter_type
state.types[usize(parameter)] = parameter_type
if (parameter_type.kind == .invalid and type.kind != .invalid) type = parameter_type
if (ast.callable_foreign(state.tree, id) and !c_abi_type(state, parameter_type))
type = invalid_type(try diagnostics.add(state.diagnostics, .invalid_operand, ast.node_span(state.tree, parameter)))
try depend(state, annotation, parameter)
try depend(state, parameter, id)
}
type.function = id
state.cells[usize(id)].type = type
return type
}
@hide:file
intern_array proc(state @mut State, descriptor ArrayType) Type ! mem.AllocError {
for state.arrays.items |existing, i| {
if (
existing.kind == descriptor.kind and existing.len == descriptor.len and existing.mutable == descriptor.mutable
and same_sentinel(existing.sentinel, descriptor.sentinel) and same_type(state, existing.element, descriptor.element)
and same_type(state, existing.error_type, descriptor.error_type)
) return Type{ kind = descriptor.kind, array_type = ArrayId(i) }
}
try arraylist.append(&state.arrays, descriptor)
return Type{ kind = descriptor.kind, array_type = ArrayId(state.arrays.items.len - 1) }
}
@hide:file
check_container_type proc(state @mut State, id ast.NodeId) Type ! (mem.AllocError | diagnostics.CapacityError) {
optional :: ast.expr_kind(state.tree, id) == .optional_type
node :: if (optional) ast.type_operand(state.tree, id) else ast.slice_type(state.tree, id).element
if (!optional) state.pointer_depth += 1
child :: try check_annotation(state, node, false)
if (!optional) state.pointer_depth -= 1
if (child.kind == .invalid) return child
if (child.kind == .void or child.kind == .anyopaque or child.kind == .range or has_inferred_count(state, child)) {
return invalid_type(try diagnostics.add(state.diagnostics, .invalid_array_element_type, ast.node_span(state.tree, node)))
}
kind TypeKind :: if (optional) .optional else .slice
descriptor := ArrayType{ kind = kind, element = child }
if (!optional) {
view :: ast.slice_type(state.tree, id)
descriptor.mutable = view.mutable
if (view.sentinel) |sentinel| {
try collect_expression(state, sentinel)
try demand(state, sentinel, child, .type_mismatch)
if (constant_integer(state, sentinel)) |value| {
if (!integer_kind(child.kind) or !integer_fits(value, child.kind)) {
return invalid_type(try diagnostics.add(state.diagnostics, .invalid_operand, ast.node_span(state.tree, sentinel)))
}
descriptor.sentinel = sentinel_bits(value)
} else return invalid_type(try diagnostics.add(state.diagnostics, .invalid_operand, ast.node_span(state.tree, sentinel)))
}
}
return try intern_array(state, descriptor)
}
@hide:file
c_abi_type proc(state @State, type Type) bool {
if (type.kind == .optional) return c_abi_pointer(state, state.arrays.items[usize(type.array_type)].element)
if (type.kind == .function) return ast.callable_foreign(state.tree, type.function) and !ast.callable_variadic(state.tree, type.function)
return integer_kind(type.kind) or type.kind == .float or type.kind == .bool or type.kind == .pointer or type.kind == .void or type.kind == .invalid
}
@hide:file
c_abi_pointer proc(state @State, type Type) bool {
return type.kind == .pointer or (type.kind == .function and c_abi_type(state, type))
}
@hide:file
builtin_name proc(state @State, id ast.NodeId) []u8 {
return ast.literal_text(state.tree, ast.builtin_call(state.tree, id).name)
}
@hide:file
collect_builtin proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
name :: builtin_name(state, id)
count :: ast.call_arg_count(state.tree, id)
layout :: mem.eql(name, "sizeof") or mem.eql(name, "alignof")
limit :: mem.eql(name, "maxval") or mem.eql(name, "minval")
cast :: mem.eql(name, "ptrcast") or mem.eql(name, "constcast")
for (0..count) |i| {
argument :: ast.call_arg(state.tree, id, i)
if (layout or limit or (cast and count == 2 and i == 0)) {
_ = try check_annotation(state, argument, true)
} else try collect_expression(state, argument)
try depend(state, argument, id)
}
if (layout) state.cells[usize(id)].type = Type{ kind = .usize }
if (limit and count == 1) state.cells[usize(id)].type = state.types[usize(ast.call_arg(state.tree, id, 0))]
if (mem.eql(name, "divtrunc")) {
state.cells[usize(id)].numeric = true
state.cells[usize(id)].producer = id
}
if (mem.eql(name, "memcopy") or mem.eql(name, "memset")) state.cells[usize(id)].type = Type{ kind = .void }
}
@hide:file
sequence_type proc(state @mut State, node ast.NodeId, defaults bool) ArrayType ! (mem.AllocError | diagnostics.CapacityError) {
type := try infer_shape(state, node, defaults)
if (type.kind == .pointer) {
pointer :: state.pointers.items[usize(type.pointer_type)]
if (pointer.many or pointer.element.kind != .array) return ArrayType{ element = Type{} }
type = pointer.element
descriptor := state.arrays.items[usize(type.array_type)]
descriptor.mutable = descriptor.mutable and pointer.mutable
return descriptor
}
if (type.kind == .array or type.kind == .slice) {
descriptor := state.arrays.items[usize(type.array_type)]
if (type.kind == .array) descriptor.mutable = descriptor.mutable and mutable_target(state, node, true)
return descriptor
}
if (type.kind == .string) return ArrayType{ element = Type{ kind = .u8 }, kind = .slice, sentinel = 0 }
return ArrayType{ element = Type{} }
}
@hide:file
infer_slice proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.AllocError | diagnostics.CapacityError) {
view :: ast.slice_expr(state.tree, id)
base :: try infer_shape(state, view.base, defaults)
descriptor := try sequence_type(state, view.base, defaults)
if (base.kind == .pointer and state.pointers.items[usize(base.pointer_type)].many) {
pointer :: state.pointers.items[usize(base.pointer_type)]
descriptor = ArrayType{ element = pointer.element, mutable = pointer.mutable }
if (view.end == null or pointer.element.kind == .anyopaque) {
try fail(state, id, .invalid_operand, ast.node_span(state.tree, id))
seed(state, id, state.types[usize(id)])
return group_type(state, id)
}
}
if (view.start) |node| try demand(state, node, Type{ kind = .usize }, .invalid_index_type)
if (view.end) |node| try demand(state, node, Type{ kind = .usize }, .invalid_index_type)
if (descriptor.element.kind == .none) {
if (defaults and base.kind != .none and base.kind != .invalid) try fail(state, id, .not_indexable, ast.node_span(state.tree, view.base))
return group_type(state, id)
}
descriptor.kind = .slice
descriptor.len = 0
descriptor.sentinel = null
if (view.sentinel) |node| {
try demand(state, node, descriptor.element, .invalid_operand)
if (constant_integer(state, node)) |value| {
if (!integer_kind(descriptor.element.kind) or !integer_fits(value, descriptor.element.kind)) {
try fail(state, id, .invalid_operand, ast.node_span(state.tree, node))
} else {
descriptor.sentinel = sentinel_bits(value)
}
} else if (defaults) try fail(state, id, .invalid_operand, ast.node_span(state.tree, node))
}
type :: try intern_array(state, descriptor)
seed(state, id, type)
return group_type(state, id)
}
@hide:file
cast_pointer_type proc(state @State, type Type) Type {
if (type.kind == .optional) return state.arrays.items[usize(type.array_type)].element
return type
}
@hide:file
infer_builtin proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.AllocError | diagnostics.CapacityError) {
name :: builtin_name(state, id)
count :: ast.call_arg_count(state.tree, id)
span :: ast.node_span(state.tree, id)
layout :: mem.eql(name, "sizeof") or mem.eql(name, "alignof")
limit :: mem.eql(name, "maxval") or mem.eql(name, "minval")
division :: mem.eql(name, "divtrunc")
cast :: mem.eql(name, "ptrcast") or mem.eql(name, "constcast")
memory :: mem.eql(name, "memcopy") or mem.eql(name, "memset")
some :: mem.eql(name, "some")
valid_count :: ((layout or limit) and count == 1) or (cast and (count == 1 or count == 2)) or ((memory or division) and count == 2) or (some and count == 1)
if (!valid_count) {
code diagnostics.Code :: if (layout or limit or division or cast or memory or some) .argument_count_mismatch else .invalid_operand
if (defaults) try fail(state, id, code, span)
return group_type(state, id)
}
if (layout) return group_type(state, id)
if (limit) {
type :: state.types[usize(ast.call_arg(state.tree, id, 0))]
if (defaults and type.kind != .invalid and !integer_kind(type.kind)) try fail(state, id, .invalid_operand, span)
return group_type(state, id)
}
if (division) {
left :: try infer_shape(state, ast.call_arg(state.tree, id, 0), defaults)
right :: try infer_shape(state, ast.call_arg(state.tree, id, 1), defaults)
if (defaults and left.kind != .invalid and right.kind != .invalid
and (!integer_kind(left.kind) or !same_type(state, left, right))) try fail(state, id, .invalid_operand, span)
return group_type(state, id)
}
if (some) {
result :: state.cells[root(state, usize(id))].expected
if (result.kind != .optional) {
if (defaults) try fail(state, id, .invalid_operand, span)
return group_type(state, id)
}
child :: state.arrays.items[usize(result.array_type)].element
operand :: ast.call_arg(state.tree, id, 0)
try demand(state, operand, child, .type_mismatch)
actual :: try infer_shape(state, operand, defaults)
if (
defaults and actual.kind != .none and actual.kind != .invalid
and !assignable_value(state, operand, actual, child)
) try fail(state, operand, .type_mismatch, ast.node_span(state.tree, operand))
seed(state, id, result)
return group_type(state, id)
}
if (cast) {
result := state.cells[root(state, usize(id))].expected
if (count == 2) result = state.types[usize(ast.call_arg(state.tree, id, 0))]
operand :: ast.call_arg(state.tree, id, count - 1)
source :: try infer_shape(state, operand, defaults)
if (result.kind == .none and mem.eql(name, "constcast")) {
result = source
if (source.kind == .slice) {
descriptor := state.arrays.items[usize(source.array_type)]
descriptor.mutable = true
result = try intern_array(state, descriptor)
} else if (source.kind == .pointer) {
descriptor := state.pointers.items[usize(source.pointer_type)]
descriptor.mutable = true
result = try intern_pointer(state, descriptor)
}
}
if (result.kind == .none) {
if (defaults) try fail(state, id, .cannot_infer_type, span)
return group_type(state, id)
}
a :: cast_pointer_type(state, source)
b :: cast_pointer_type(state, result)
allowed := false
if (a.kind == .pointer and b.kind == .pointer) {
left :: state.pointers.items[usize(a.pointer_type)]
right :: state.pointers.items[usize(b.pointer_type)]
if (mem.eql(name, "constcast")) allowed = left.many == right.many and same_type(state, left.element, right.element)
else allowed = left.mutable or !right.mutable
if (source.kind == .optional and result.kind != .optional) allowed = false
} else if (a.kind == .slice and b.kind == .slice and mem.eql(name, "constcast")) {
left :: state.arrays.items[usize(a.array_type)]
right :: state.arrays.items[usize(b.array_type)]
allowed = same_sentinel(left.sentinel, right.sentinel) and same_type(state, left.element, right.element)
} else if (source.kind == .null and result.kind == .optional and b.kind == .pointer) allowed = true
if (!allowed and source.kind != .none and source.kind != .invalid and defaults) {
try fail(state, id, .invalid_operand, span)
seed(state, id, state.types[usize(id)])
return group_type(state, id)
}
seed(state, id, result)
return group_type(state, id)
}
destination :: try sequence_type(state, ast.call_arg(state.tree, id, 0), defaults)
if (mem.eql(name, "memset") and destination.element.kind != .none) {
try demand(state, ast.call_arg(state.tree, id, 1), destination.element, .type_mismatch)
}
if (defaults) {
if (!destination.mutable or destination.element.kind == .none) {
try fail(state, id, .invalid_operand, span)
} else if (mem.eql(name, "memcopy")) {
source :: try sequence_type(state, ast.call_arg(state.tree, id, 1), defaults)
if (!same_type(state, destination.element, source.element)) try fail(state, id, .type_mismatch, span)
} else {
argument :: ast.call_arg(state.tree, id, 1)
actual :: try infer_shape(state, argument, defaults)
if (actual.kind != .none and actual.kind != .invalid and !assignable_value(state, argument, actual, destination.element)) {
try fail(state, argument, .type_mismatch, ast.node_span(state.tree, argument))
}
}
}
return group_type(state, id)
}
@hide:file
same_type proc(state @State, left, right Type) bool {
if (left.kind != right.kind) return false
if (left.nominal != right.nominal) return false
if (left.kind == .enumeration) {
if (left.enum_type == right.enum_type) return true
if (!state.enums.items[usize(left.enum_type)].composed and !state.enums.items[usize(right.enum_type)].composed) return false
return (
error_subset(state.records.items, state.enums.items, left, right)
and error_subset(state.records.items, state.enums.items, right, left)
)
}
if (left.kind == .pointer) {
a :: state.pointers.items[usize(left.pointer_type)]
b :: state.pointers.items[usize(right.pointer_type)]
return a.mutable == b.mutable and a.many == b.many and same_type(state, a.element, b.element)
}
if (left.kind == .type_value) {
a := left
b := right
a.kind = a.value_kind
b.kind = b.value_kind
return same_type(state, a, b)
}
if (left.kind == .function) return same_signature(state, left.function, right.function)
if (left.kind == .record) {
if (left.record_type == right.record_type) return true
a :: state.records.items[usize(left.record_type)]
b :: state.records.items[usize(right.record_type)]
if (a.composed or b.composed) return (
error_subset(state.records.items, state.enums.items, left, right)
and error_subset(state.records.items, state.enums.items, right, left)
)
if (a.is_union or b.is_union) return false
if (
a.declaration != ast.NodeId(maxval!(u32)) or b.declaration != ast.NodeId(maxval!(u32))
or a.fields.len != b.fields.len
) return false
for a.fields |field, i| {
other :: b.fields[i]
if ((field.name == null) != (other.name == null)) return false
if (field.name) |name| {
if (ast.name_id(state.tree, name) != ast.name_id(state.tree, other.name?)) return false
}
if (!same_type(state, field.type, other.type)) return false
}
return true
}
if (left.kind != .array and left.kind != .slice and left.kind != .optional and left.kind != .fallible) return true
a :: state.arrays.items[usize(left.array_type)]
b :: state.arrays.items[usize(right.array_type)]
return (
a.len == b.len and a.mutable == b.mutable and same_sentinel(a.sentinel, b.sentinel)
and same_type(state, a.element, b.element) and same_type(state, a.error_type, b.error_type)
)
}
@hide:file
same_sentinel proc(left, right ?i64) bool {
if (left) |a| {
if (right) |b| return a == b
return false
}
return right == null
}
@hide:file
sentinel_bits proc(value ConstantInteger) i64 {
if (!value.negative) return ptrcast!(@i64, &value.magnitude)^
if (value.magnitude == u64(maxval!(i64)) + 1) return minval!(i64)
return -i64(value.magnitude)
}
@hide:file
array_cells proc(state @State, type Type) ?usize {
if (type.kind == .void) return usize(0)
if (type.kind == .fallible) {
descriptor :: state.arrays.items[usize(type.array_type)]
success := usize(0)
failure := usize(0)
if (array_cells(state, descriptor.element)) |count| success = count else return null
if (array_cells(state, descriptor.error_type)) |count| failure = count else return null
payload :: if (success > failure) success else failure
if (payload == maxval!(usize)) return null
return payload + 1
}
if (type.kind == .slice) return usize(2)
if (type.kind == .optional) {
child :: state.arrays.items[usize(type.array_type)].element
if (child.kind == .pointer or child.kind == .function) return usize(1)
if (array_cells(state, child)) |count| {
if (count < maxval!(usize)) return count + 1
}
return null
}
if (type.kind == .record) {
total := usize(0)
descriptor :: state.records.items[usize(type.record_type)]
for state.records.items[usize(type.record_type)].fields |field| {
child := usize(0)
if (array_cells(state, field.type)) |value| child = value else return null
if (descriptor.is_union) {
if (child > total) total = child
} else {
if (total > maxval!(usize) - child) return null
total += child
}
}
if (descriptor.tag_type.kind != .none) {
if (total == maxval!(usize)) return null
total += 1
}
return total
}
if (type.kind != .array) return usize(1)
view :: state.arrays.items[usize(type.array_type)]
if (view.len == no_index) return null
child := usize(0)
if (array_cells(state, view.element)) |value| child = value else return null
if (child != 0 and view.len > divtrunc!(maxval!(usize), child)) return null
return view.len * child
}
@hide:file
array_origin proc(state @mut State, id ast.NodeId) ast.NodeId {
origin :: state.cells[root(state, usize(id))].literal
if (origin != ast.NodeId(maxval!(u32)) and ast.expr_kind(state.tree, origin) == .array_literal) return origin
if (state.cells[usize(id)].role == .expression and ast.expr_kind(state.tree, id) == .index) {
base :: array_origin(state, ast.index_expr(state.tree, id).base)
if (base != ast.NodeId(maxval!(u32)) and ast.array_element_count(state.tree, base) != 0) {
return array_origin(state, ast.array_element(state.tree, base, 0))
}
}
return ast.NodeId(maxval!(u32))
}
@hide:file
unify_elements proc(state @mut State, left, right ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
if (state.types[usize(left)].kind == .invalid or state.types[usize(right)].kind == .invalid) return
a :: array_origin(state, left)
b :: array_origin(state, right)
if (a != ast.NodeId(maxval!(u32)) and b != ast.NodeId(maxval!(u32))) {
count :: ast.array_element_count(state.tree, a)
if (count != ast.array_element_count(state.tree, b)) return
for (0..count) |i| try unify_elements(state, ast.array_element(state.tree, a, i), ast.array_element(state.tree, b, i))
return
}
left_type :: group_type(state, left)
right_type :: group_type(state, right)
if (numeric_kind(left_type.kind)) try demand(state, right, left_type, .type_mismatch)
if (numeric_kind(right_type.kind)) try demand(state, left, right_type, .type_mismatch)
left_root :: root(state, usize(left))
right_root :: root(state, usize(right))
if (state.cells[left_root].numeric and state.cells[right_root].numeric) {
try numeric_constraint(state, left, right, right)
}
if (left_type.kind == .array or left_type.kind == .record) try demand(state, right, left_type, .type_mismatch)
if (right_type.kind == .array or right_type.kind == .record) try demand(state, left, right_type, .type_mismatch)
}
@hide:file
demand_yields proc(state @mut State, owner ast.NodeId, expected Type, code diagnostics.Code) void ! (mem.AllocError | diagnostics.CapacityError) {
yield_id := state.cells[usize(owner)].first_yield
while (yield_id != ast.NodeId(maxval!(u32))) {
if (ast.yield_stmt(state.tree, yield_id).value) |value| try demand(state, value, expected, code)
yield_id = state.cells[usize(yield_id)].next_yield
}
}
@hide:file
infer_value_block proc(state @mut State, owner ast.NodeId, defaults bool) Type ! (mem.AllocError | diagnostics.CapacityError) {
expected := state.cells[root(state, usize(owner))].expected
yield_id := state.cells[usize(owner)].first_yield
while (yield_id != ast.NodeId(maxval!(u32))) {
if (ast.yield_stmt(state.tree, yield_id).value) |value| {
if (expected.kind != .none) try demand(state, value, expected, .yield_type_mismatch)
actual :: try infer_shape(state, value, defaults)
if (expected.kind == .none and actual.kind != .none and actual.kind != .invalid) expected = actual
}
yield_id = state.cells[usize(yield_id)].next_yield
}
if (expected.kind == .none and defaults and state.cells[usize(owner)].first_yield == ast.NodeId(maxval!(u32))) {
expected = Type{ kind = .void }
}
if (expected.kind != .none) {
try demand_yields(state, owner, expected, .yield_type_mismatch)
seed(state, owner, expected)
}
return group_type(state, owner)
}
@hide:file
infer_shape proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.AllocError | diagnostics.CapacityError) {
index :: root(state, usize(id))
if (state.cells[index].visiting) return state.cells[index].type
if (state.cells[index].type.kind == .invalid) return state.cells[index].type
state.cells[index].visiting = true
defer state.cells[index].visiting = false
if (
state.cells[usize(id)].role == .value_body
or (state.cells[usize(id)].role == .expression and ast.expr_kind(state.tree, id) == .block_expr)
) return try infer_value_block(state, id, defaults)
if (state.cells[usize(id)].role == .loop_capture and state.cells[usize(id)].for_loop != ast.NodeId(maxval!(u32))) {
loop :: ast.for_stmt(state.tree, state.cells[usize(id)].for_loop)
actual :: try infer_shape(state, loop.iterable, defaults)
if (actual.kind == .invalid or state.types[usize(loop.iterable)].kind == .invalid) {
type :: if (actual.kind == .invalid) actual else state.types[usize(loop.iterable)]
seed(state, id, type)
return type
}
if (!collection_type(state, actual)) {
if (actual.kind != .none) {
try fail(state, loop.iterable, .invalid_for_iterable, ast.node_span(state.tree, loop.iterable))
seed(state, id, state.types[usize(loop.iterable)])
}
return group_type(state, id)
}
descriptor :: try sequence_type(state, loop.iterable, defaults)
type :: if (loop.pointer_capture)
try intern_pointer(state, PointerType{ element = descriptor.element, mutable = descriptor.mutable, many = false })
else descriptor.element
seed(state, id, type)
return type
}
if (state.cells[usize(id)].role == .expression) {
kind :: ast.expr_kind(state.tree, id)
if (declaration_reference(state.tree, id) and has_narrowing(state, id)) {
narrowed :: try narrow_reference(state, id)
if (narrowed.kind != .none) {
seed(state, id, narrowed)
return narrowed
}
}
if (declaration_reference(state.tree, id)) {
binding :: sema.binding(state.resolution, id)
if (binding.kind == .declaration and state.cells[usize(binding.data.declaration)].role == .loop_capture) {
type :: try infer_shape(state, binding.data.declaration, defaults)
if (type.kind != .none) seed(state, id, type)
return group_type(state, id)
}
}
if (kind == .try_expr or kind == .catch_expr) return try infer_fallible_expression(state, id, defaults)
if (kind == .builtin_call) return try infer_builtin(state, id, defaults)
if (kind == .slice) return try infer_slice(state, id, defaults)
if (kind == .unary and ast.unary_expr(state.tree, id).operator == .unwrap) {
operand :: try infer_shape(state, ast.unary_expr(state.tree, id).operand, defaults)
if (operand.kind == .optional) {
child :: state.arrays.items[usize(operand.array_type)].element
seed(state, id, child)
return child
}
return group_type(state, id)
}
}
if (state.cells[usize(id)].role == .expression and ast.expr_kind(state.tree, id) == .binary) {
view :: ast.binary_expr(state.tree, id)
if (view.operator == .add) {
_ = try infer_shape(state, view.left, defaults)
if (pointer_addition(state, id)) {
type :: group_type(state, view.left)
state.cells[index].numeric = false
seed(state, id, type)
try demand(state, view.right, Type{ kind = .usize }, .invalid_operand)
return group_type(state, id)
}
}
}
if (state.cells[usize(id)].role == .expression and ast.expr_kind(state.tree, id) == .unary) {
view :: ast.unary_expr(state.tree, id)
if (view.operator == .address_of or view.operator == .dereference) {
operand :: try infer_shape(state, view.operand, defaults)
if (operand.kind == .none) return Type{}
if (operand.kind == .invalid) {
seed(state, id, operand)
return operand
}
if (view.operator == .dereference) {
if (operand.kind != .pointer) {
try fail(state, id, .invalid_dereference, view.span)
seed(state, id, state.types[usize(id)])
return group_type(state, id)
}
element :: state.pointers.items[usize(operand.pointer_type)].element
if (element.kind == .anyopaque) {
try fail(state, id, .invalid_dereference, view.span)
seed(state, id, state.types[usize(id)])
return group_type(state, id)
}
seed(state, id, element)
return element
}
mutable :: mutable_target(state, view.operand, true)
expected :: state.cells[index].expected
if (expected.kind == .slice and operand.kind == .array) {
array :: state.arrays.items[usize(operand.array_type)]
writable :: array.mutable and (mutable or ast.expr_kind(state.tree, view.operand) == .array_literal)
type :: try intern_array(state, ArrayType{ kind = .slice, element = array.element, mutable = writable })
seed(state, id, type)
return type
}
many :: expected.kind == .pointer and state.pointers.items[usize(expected.pointer_type)].many
type :: try intern_pointer(state, PointerType{ element = operand, mutable = mutable, many = many })
seed(state, id, type)
return type
}
}
if (state.cells[usize(id)].role == .expression and ast.expr_kind(state.tree, id) == .match_expr) {
return try infer_match(state, id, defaults)
}
if (state.cells[usize(id)].role == .expression and ast.expr_kind(state.tree, id) == .call) {
destination :: conversion_kind(state, id)
if (destination != .none) {
seed(state, id, Type{ kind = destination })
return group_type(state, id)
}
if (state.backings[usize(id)].kind == .none) {
signature :: try infer_shape(state, ast.call_expr(state.tree, id).callee, defaults)
if (signature.kind == .function) {
seed(state, id, state.types[usize(ast.callable_result(state.tree, signature.function))])
return group_type(state, id)
}
}
}
if (
state.cells[usize(id)].role == .expression
and ast.expr_kind(state.tree, id) == .selection
and sema.binding(state.resolution, id).kind == .none
) {
view :: ast.selection_expr(state.tree, id)
if (type_reference(state, view.base)) {
enum_shape :: try check_annotation(state, view.base, false)
if (enum_shape.kind == .enumeration) return try select_enum_member(state, id, view.member, enum_shape)
}
base := try infer_shape(state, view.base, defaults)
if ((base.kind == .array or base.kind == .slice or base.kind == .string) and base.nominal == ast.NodeId(maxval!(u32))) {
member :: ast.literal_text(state.tree, view.member)
if (mem.eql(member, "len")) {
seed(state, id, Type{ kind = .usize })
return group_type(state, id)
}
if (mem.eql(member, "ptr")) {
element :: if (base.kind == .string) Type{ kind = .u8 } else state.arrays.items[usize(base.array_type)].element
mutable :: (
base.kind != .string and state.arrays.items[usize(base.array_type)].mutable
and (base.kind == .slice or mutable_target(state, view.base, true))
)
type :: try intern_pointer(state, PointerType{ element = element, mutable = mutable, many = true })
seed(state, id, type)
return type
}
}
if (base.kind == .pointer and base.nominal == ast.NodeId(maxval!(u32))) {
pointer :: state.pointers.items[usize(base.pointer_type)]
if (
!pointer.many and pointer.element.kind == .array
and ast.expr_kind(state.tree, view.member) == .identifier
) {
member :: ast.literal_text(state.tree, view.member)
if (mem.eql(member, "len")) {
type :: Type{ kind = .usize }
seed(state, id, type)
return type
}
if (mem.eql(member, "ptr")) {
array :: state.arrays.items[usize(pointer.element.array_type)]
type :: try intern_pointer(state, PointerType{
element = array.element, mutable = pointer.mutable and array.mutable, many = true,
})
seed(state, id, type)
return type
}
}
if (!pointer.many or pointer.element.kind == .record) base = pointer.element
}
if (base.kind == .record and base.nominal == ast.NodeId(maxval!(u32))) {
fields :: state.records.items[usize(base.record_type)].fields
slot :: selected_field(state, fields, view.member)
if (slot != no_index) {
state.slots[usize(id)] = slot
seed(state, id, fields[slot].type)
return fields[slot].type
}
try fail(state, id, .unknown_field, ast.node_span(state.tree, view.member))
} else if (base.kind == .invalid) {
seed(state, id, base)
} else if (base.kind != .none) {
try fail(state, id, .not_record, ast.node_span(state.tree, view.base))
}
if (base.kind == .none) {
origin :: state.cells[root(state, usize(view.base))].literal
if (origin != ast.NodeId(maxval!(u32)) and ast.expr_kind(state.tree, origin) == .record_literal) {
for 0..ast.record_field_count(state.tree, origin) |i| {
field :: ast.field(state.tree, ast.record_field(state.tree, origin, i))
matches := false
if (field.name) |name| {
matches = (
ast.expr_kind(state.tree, view.member) == .identifier
and ast.name_id(state.tree, name) == ast.name_id(state.tree, view.member)
)
} else if (constant_integer(state, view.member)) |ordinal| {
matches = !ordinal.negative and ordinal.magnitude == u64(i)
}
if (!matches) continue
child :: field.value?
if (state.cells[root(state, usize(child))].numeric) try numeric_constraint(state, id, child, id)
break
}
}
}
return group_type(state, id)
}
if (state.cells[usize(id)].role == .expression and declaration_reference(state.tree, id)) {
binding :: sema.binding(state.resolution, id)
if (binding.kind == .declaration) {
target :: binding.data.declaration
if (
state.cells[usize(target)].role == .declaration and state.cells[usize(target)].fixed
and has_inferred_count(state, state.cells[usize(target)].type)
) {
declaration :: ast.statement_decl(state.tree, target)
actual :: try infer_shape(state, declaration.initializer, defaults)
if (actual.kind == .array and !has_inferred_count(state, actual)) {
try specialize_annotation(state, declaration.type?, actual)
state.cells[usize(target)].type = state.types[usize(declaration.type?)]
seed(state, id, state.cells[usize(target)].type)
}
}
}
}
literal :: state.cells[index].literal
if (literal != ast.NodeId(maxval!(u32)) and ast.expr_kind(state.tree, literal) == .enum_literal) {
return try infer_enum_literal(state, literal, state.cells[index].expected, defaults)
}
if (literal != ast.NodeId(maxval!(u32)) and ast.expr_kind(state.tree, literal) == .record_literal) {
return try infer_record_literal(state, literal, index, defaults)
}
if (literal != ast.NodeId(maxval!(u32))) {
len :: ast.array_element_count(state.tree, literal)
expected :: state.cells[index].expected
element := Type{}
mutable := false
if (expected.kind == .array) {
view :: state.arrays.items[usize(expected.array_type)]
element = view.element
mutable = view.mutable
}
for 0..len |i| {
child :: ast.array_element(state.tree, literal, i)
actual :: try infer_shape(state, child, defaults)
if (element.kind == .none and actual.kind != .none and actual.kind != .invalid) element = actual
if (element.kind == .array and actual.kind == .array) element = try specialize_type(state, element, actual)
if (element.kind != .none) try demand(state, child, element, .type_mismatch)
}
if (element.kind == .none and len == 0 and defaults) element = Type{ kind = .int }
if (element.kind != .none) {
type :: try intern_array(state, ArrayType{ element = element, len = len, mutable = mutable })
state.cells[index].type = type
return type
}
return Type{}
}
if (state.cells[usize(id)].role == .expression and ast.expr_kind(state.tree, id) == .index) {
view :: ast.index_expr(state.tree, id)
base := try infer_shape(state, view.base, defaults)
if (base.kind == .pointer and base.nominal == ast.NodeId(maxval!(u32))) {
pointer :: state.pointers.items[usize(base.pointer_type)]
if (pointer.many) {
seed(state, id, pointer.element)
return pointer.element
}
base = pointer.element
}
if (base.kind == .string) {
seed(state, id, Type{ kind = .u8 })
return group_type(state, id)
}
if ((base.kind == .array or base.kind == .slice) and base.nominal == ast.NodeId(maxval!(u32))) {
element :: state.arrays.items[usize(base.array_type)].element
seed(state, id, element)
return element
}
origin :: array_origin(state, view.base)
if (origin != ast.NodeId(maxval!(u32)) and ast.array_element_count(state.tree, origin) != 0) {
child :: ast.array_element(state.tree, origin, 0)
child_root :: root(state, usize(child))
if (state.cells[child_root].numeric) {
try numeric_constraint(state, id, child, id)
}
}
}
return group_type(state, id)
}
@hide:file
prepare_for_captures proc(state @mut State, defaults bool) void ! (mem.AllocError | diagnostics.CapacityError) {
for 0..(state.cells.len) |i| {
if (state.cells[i].role == .loop_capture and state.cells[i].for_loop != ast.NodeId(maxval!(u32))) {
_ = try infer_shape(state, ast.NodeId(i), defaults)
}
}
}
@hide:file
prepare_optional_captures proc(state @mut State, defaults bool) void ! (mem.AllocError | diagnostics.CapacityError) {
for 0..(state.cells.len) |i| {
if (state.cells[i].role == .expression and ast.expr_kind(state.tree, ast.NodeId(i)) == .catch_expr) {
_ = try infer_shape(state, ast.NodeId(i), defaults)
}
}
for (0..(state.cells.len)) |i| {
capture :: state.cells[i].capture
if (capture == ast.NodeId(maxval!(u32)) or state.cells[i].role != .expression) continue
if (state.cells[usize(capture)].role != .loop_capture) continue
type :: try infer_shape(state, ast.NodeId(i), defaults)
if (type.kind != .optional) continue
child :: state.arrays.items[usize(type.array_type)].element
seed(state, capture, child)
for (0..(state.cells.len)) |j| {
if (state.cells[j].role != .expression or !declaration_reference(state.tree, ast.NodeId(j))) continue
binding :: sema.binding(state.resolution, ast.NodeId(j))
if (
binding.kind == .declaration and binding.data.declaration == capture
and !has_narrowing(state, ast.NodeId(j))
) seed(state, ast.NodeId(j), child)
}
}
}
@hide:file
prepare_shapes proc(state @mut State, assignment_values, defaults bool) void ! (mem.AllocError | diagnostics.CapacityError) {
try prepare_for_captures(state, defaults)
try prepare_optional_captures(state, defaults)
for 0..(state.cells.len) |i| {
if (state.cells[i].role != .expression or state.cells[i].assignment_value != assignment_values) continue
id :: ast.NodeId(i)
if (
ast.expr_kind(state.tree, id) == .array_literal
and state.cells[root(state, i)].expected.kind != .array
) {
len :: ast.array_element_count(state.tree, id)
for (usize(1)..len) |j| try unify_elements(state, ast.array_element(state.tree, id, 0), ast.array_element(state.tree, id, j))
}
_ = try infer_shape(state, id, defaults)
}
}
@hide:file
apply_contexts proc(state @mut State, assignment_values, early bool) void ! (mem.AllocError | diagnostics.CapacityError) {
tree :: state.tree
if (!assignment_values) {
for 0..(state.records.items.len) |i| {
record :: state.records.items[i]
for record.fields |field| {
if (field.default) |value| try context_demand(state, value, field.type, .type_mismatch, early)
}
}
}
for 0..(state.cells.len) |i| {
id :: ast.NodeId(i)
if (state.cells[i].role == .expression and state.cells[i].assignment_value == assignment_values) {
if (ast.expr_kind(tree, id) == .typed_constant) {
value :: ast.typed_constant(tree, id)
try context_demand(state, value.value, state.cells[i].type, .type_mismatch, early)
}
if (ast.expr_kind(tree, id) == .match_expr) _ = try infer_match(state, id, false)
if (ast.expr_kind(tree, id) == .binary) {
expression :: ast.binary_expr(tree, id)
if (expression.operator == .equal or expression.operator == .not_equal) {
left :: try infer_shape(state, expression.left, false)
right :: try infer_shape(state, expression.right, false)
if (left.kind == .enumeration or left.kind == .bool) try demand(state, expression.right, left, .invalid_operand)
if (right.kind == .enumeration or right.kind == .bool) try demand(state, expression.left, right, .invalid_operand)
}
}
}
if (!assignment_values and state.cells[i].role == .declaration) {
view :: ast.statement_decl(tree, id)
if (view.type) |annotation| try context_demand(state, view.initializer, state.types[usize(annotation)], .type_mismatch, early)
}
if (!assignment_values and state.cells[i].role == .return_stmt and state.cells[i].return_annotation != ast.NodeId(maxval!(u32))) {
if (ast.return_stmt(tree, id).value) |value| {
try context_demand(state, value, state.types[usize(state.cells[i].return_annotation)], .return_type_mismatch, early)
}
}
if (assignment_values and state.cells[i].role == .assignment_stmt) {
view :: ast.assignment_stmt(tree, id)
if (mutable_target(state, view.target, false)) try context_demand(state, view.value, state.types[usize(view.target)], .type_mismatch, early)
}
if (state.cells[i].role != .expression or state.cells[i].assignment_value != assignment_values or ast.expr_kind(tree, id) != .call) continue
if (state.backings[i].kind != .none) {
backing :: state.backings[i]
if (
ast.call_arg_count(tree, id) == 1
and (backing.nominal != ast.NodeId(maxval!(u32)) or !numeric_kind(backing.kind))
) try context_demand(state, ast.call_arg(tree, id, 0), backing, .type_mismatch, early)
continue
}
if (conversion_kind(state, id) != .none) {
for 0..ast.call_arg_count(tree, id) |j| {
argument :: ast.call_arg(tree, id, j)
if (constant_integer(state, argument)) |value| {
if (!value.negative and value.magnitude > u64(maxval!(i64))) {
state.cells[root(state, usize(argument))].conversion_unsigned = true
}
}
}
continue
}
signature :: group_type(state, ast.call_expr(tree, id).callee)
if (signature.function == ast.NodeId(maxval!(u32))) continue
count :: ast.function_param_count(tree, signature.function)
for 0..ast.call_arg_count(tree, id) |j| {
if (j >= count) continue
parameter :: ast.function_param(tree, signature.function, j)
try context_demand(state, ast.call_arg(tree, id, j), state.cells[usize(parameter)].type, .type_mismatch, early)
}
}
}
@hide:file
context_demand proc(state @mut State, id ast.NodeId, expected Type, code diagnostics.Code, early bool) void ! (mem.AllocError | diagnostics.CapacityError) {
if (
early and expected.kind != .array and expected.kind != .record
and expected.kind != .pointer and expected.kind != .slice and expected.kind != .optional and expected.kind != .fallible
and expected.kind != .enumeration and !integer_kind(expected.kind)
) return
try demand(state, id, expected, code)
}
@hide:file
bound_family proc(state @mut State, id ast.NodeId) TypeKind {
index :: root(state, usize(id))
if (state.cells[index].type.kind != .none) return state.cells[index].type.kind
if (state.cells[index].demand != .none) return state.cells[index].demand
producer :: state.cells[index].producer
if (producer == ast.NodeId(maxval!(u32)) or state.cells[index].visiting) return .none
state.cells[index].visiting = true
defer state.cells[index].visiting = false
if (ast.expr_kind(state.tree, producer) == .unary) return bound_family(state, ast.unary_expr(state.tree, producer).operand)
view :: ast.binary_expr(state.tree, producer)
left :: bound_family(state, view.left)
if (shift(view.operator)) return left
right :: bound_family(state, view.right)
if (left == .float or right == .float) return .float
if (integer_kind(left)) return left
if (integer_kind(right)) return right
return .none
}
@hide:file
prepare_ranges proc(state @mut State, assignment_values, constrain bool) void ! (mem.AllocError | diagnostics.CapacityError) {
tree :: state.tree
for 0..(state.cells.len) |i| {
id :: ast.NodeId(i)
if (state.cells[i].match_pattern) continue
if (state.cells[i].role != .expression or state.cells[i].assignment_value != assignment_values or ast.expr_kind(tree, id) != .range) continue
view :: ast.range_expr(tree, id)
left :: bound_family(state, view.start)
right :: bound_family(state, view.end)
family TypeKind :: if (integer_kind(left)) left else if (integer_kind(right)) right else .int
if (constrain) {
try range_bound_context(state, view.start, family)
try range_bound_context(state, view.end, family)
}
if (group_type(state, id).kind != .invalid) {
seed(state, id, Type{ kind = .range, range_element = family })
state.types[i] = group_type(state, id)
}
capture :: state.cells[i].capture
if (capture != ast.NodeId(maxval!(u32)) and state.cells[usize(capture)].type.kind != .invalid) {
seed(state, capture, Type{ kind = family })
}
}
for 0..(state.cells.len) |i| {
id :: ast.NodeId(i)
if (
state.cells[i].role != .expression
or state.cells[i].assignment_value
or !declaration_reference(tree, id)
) continue
binding :: sema.binding(state.resolution, id)
if (
binding.kind == .declaration and state.cells[usize(binding.data.declaration)].role == .loop_capture
and !has_narrowing(state, id)
) seed(state, id, group_type(state, binding.data.declaration))
}
}
@hide:file
has_inferred_count proc(state @State, type Type) bool {
if (type.kind != .array) return false
view :: state.arrays.items[usize(type.array_type)]
return view.len == no_index or has_inferred_count(state, view.element)
}
@hide:file
specialize_type proc(state @mut State, expected, actual Type) Type ! mem.AllocError {
if (expected.kind != .array or actual.kind != .array) return expected
view := state.arrays.items[usize(expected.array_type)]
source :: state.arrays.items[usize(actual.array_type)]
if (view.len == no_index) view.len = source.len
view.element = try specialize_type(state, view.element, source.element)
return try intern_array(state, view)
}
@hide:file
specialize_annotation proc(state @mut State, id ast.NodeId, actual Type) void ! mem.AllocError {
expected :: try specialize_type(state, state.types[usize(id)], actual)
state.types[usize(id)] = expected
state.cells[usize(id)].type = expected
if (expected.kind == .array and ast.expr_kind(state.tree, id) == .array_type) {
try specialize_annotation(state, ast.array_type(state.tree, id).element, state.arrays.items[usize(expected.array_type)].element)
}
}
@hide:file
specialize_annotations proc(state @mut State) void ! (mem.AllocError | diagnostics.CapacityError) {
for 0..(state.cells.len) |i| {
if (state.cells[i].role != .declaration) continue
view :: ast.statement_decl(state.tree, ast.NodeId(i))
if (view.type) |annotation| {
actual :: try infer_shape(state, view.initializer, true)
try specialize_annotation(state, annotation, actual)
if (state.cells[i].type.kind != .invalid) state.cells[i].type = state.types[usize(annotation)]
}
}
for 0..(state.cells.len) |i| {
if (state.cells[i].role != .annotation or state.types[i].kind != .array) continue
if (has_inferred_count(state, state.types[i])) {
try fail(state, ast.NodeId(i), .invalid_array_length, ast.node_span(state.tree, ast.NodeId(i)))
state.cells[i].type = state.types[i]
} else if (array_cells(state, state.types[i]) == null) {
try fail(state, ast.NodeId(i), .array_size_overflow, ast.node_span(state.tree, ast.NodeId(i)))
state.cells[i].type = state.types[i]
}
}
for 0..(state.cells.len) |i| {
id :: ast.NodeId(i)
if (
state.cells[i].role != .expression
or state.cells[i].assignment_value
or !declaration_reference(state.tree, id)
) continue
binding :: sema.binding(state.resolution, id)
if (
binding.kind == .declaration and state.cells[usize(binding.data.declaration)].fixed
and !has_narrowing(state, id)
) seed(state, id, state.cells[usize(binding.data.declaration)].type)
}
}
@hide:file
named_field proc(tree @ast.Tree, fields []RecordField, name ast.NodeId) usize {
for fields |field, i| {
if (field.name) |existing| {
if (ast.name_id(tree, existing) == ast.name_id(tree, name)) return i
}
}
return no_index
}
@hide:file
selected_field proc(state @State, fields []RecordField, member ast.NodeId) usize {
if (ast.expr_kind(state.tree, member) == .identifier) return named_field(state.tree, fields, member)
value := usize(0)
for ast.literal_text(state.tree, member) |byte| {
if (byte < '0' or byte > '9') return no_index
digit :: usize(byte - '0')
if (value > divtrunc!(maxval!(usize) - digit, usize(10))) return no_index
value = value * 10 + digit
}
if (value >= fields.len or fields[value].name != null) return no_index
return value
}
@hide:file
intern_record proc(state @mut State, declaration ast.NodeId, fields []mut RecordField) Type ! mem.AllocError {
for state.records.items |existing, i| {
if (existing.is_union or existing.declaration != declaration or existing.fields.len != fields.len) continue
equal := true
same_defaults := true
for fields |field, j| {
slot := j
if (field.name) |name| slot = named_field(state.tree, existing.fields, name)
if (
slot == no_index or (field.name == null and existing.fields[slot].name != null)
or !same_type(state, field.type, existing.fields[slot].type)
) {
equal = false
break
}
other_default :: existing.fields[slot].default
if ((field.default == null) != (other_default == null)) {
same_defaults = false
} else if (field.default) |value| {
if (value != other_default?) same_defaults = false
}
}
if (equal and same_defaults) {
mem.free(state.records.allocator, fields)
return Type{ kind = .record, record_type = RecordId(i) }
}
if (equal) {
for existing.fields |field, j| {
if (field.name) |name| {
slot :: named_field(state.tree, fields, name)
saved :: fields[j]
fields[j] = fields[slot]
fields[slot] = saved
}
}
}
}
try arraylist.append(&state.records, RecordType{ declaration = declaration, fields = fields })
return Type{ kind = .record, record_type = RecordId(state.records.items.len - 1) }
}
@hide:file
c_record_field_type proc(state @State, type Type, depth usize) bool {
if (depth >= 128 or type.nominal != ast.NodeId(maxval!(u32))) return false
if (numeric_kind(type.kind) or type.kind == .bool or type.kind == .pointer) return true
if (type.kind == .function) return c_abi_type(state, type)
if (type.kind == .optional)
return c_abi_pointer(state, state.arrays.items[usize(type.array_type)].element)
if (type.kind == .enumeration)
return state.enums.items[usize(type.enum_type)].explicit_backing
if (type.kind == .array) {
array :: state.arrays.items[usize(type.array_type)]
return array.len > 0 and c_record_field_type(state, array.element, depth + 1)
}
if (type.kind != .record) return false
record :: state.records.items[usize(type.record_type)]
if (record.is_union or record.declaration == ast.NodeId(maxval!(u32))
or !ast.record_c_layout(state.tree, record.declaration) or record.fields.len == 0) return false
for (record.fields) |field| {
if (!c_record_field_type(state, field.type, depth + 1)) return false
}
return true
}
@hide:file
check_record proc(state @mut State, id ast.NodeId, nominal bool) Type ! (mem.AllocError | diagnostics.CapacityError) {
if (ast.record_opaque(state.tree, id)) return Type{ kind = .anyopaque, nominal = id }
cached := state.cells[usize(id)].type
if (cached.kind == .invalid) return cached
if (!state.cells[usize(id)].record_checking and (cached.kind == .record or cached.kind == .type_value)) {
cached.kind = .record
return cached
}
if (state.cells[usize(id)].record_checking) {
if (cached.kind == .record and state.pointer_depth > state.cells[usize(id)].record_pointer_depth) {
cached.kind = .record
return cached
}
type :: invalid_type(try diagnostics.add(state.diagnostics, .recursive_record, ast.node_span(state.tree, id)))
state.cells[usize(id)].type = type
return type
}
state.cells[usize(id)].record_checking = true
defer state.cells[usize(id)].record_checking = false
state.cells[usize(id)].record_pointer_depth = state.pointer_depth
record_id := no_record
result_type := Type{}
if (nominal) {
record_id = RecordId(state.records.items.len)
empty :: try mem.alloc(RecordField, state.records.allocator, 0)
arraylist.append(&state.records, RecordType{ declaration = id, fields = empty }) catch |err| {
mem.free(state.records.allocator, empty)
return err
}
result_type = Type{ kind = .record, record_type = record_id }
state.cells[usize(id)].type = result_type
}
count :: ast.record_field_count(state.tree, id)
fields :: try mem.alloc(RecordField, state.records.allocator, count)
owned := true
defer {
if (owned) mem.free(state.records.allocator, fields)
}
failure := Type{}
for 0..count |i| {
node :: ast.record_field(state.tree, id, i)
view :: ast.field(state.tree, node)
type := try check_annotation(state, view.type?, false)
if (type.kind == .void or type.kind == .range or type.kind == .type_value or type.kind == .none) {
type = invalid_type(try diagnostics.add(state.diagnostics, .invalid_record_field_type, view.span))
}
if (type.kind != .invalid and ast.record_c_layout(state.tree, id)
and !c_record_field_type(state, type, 0))
type = invalid_type(try diagnostics.add(state.diagnostics, .invalid_c_record_field_type, view.span))
if (view.name) |name| {
if (named_field(state.tree, fields[0..i], name) != no_index) {
type = invalid_type(try diagnostics.add(state.diagnostics, .duplicate_field, ast.node_span(state.tree, name)))
}
}
fields[i] = RecordField{ name = view.name, type = type, default = view.value }
if (type.kind == .invalid) failure = type
}
if (failure.kind == .invalid) {
state.cells[usize(id)].type = failure
return failure
}
if (nominal) {
mem.free(state.records.allocator, state.records.items[usize(record_id)].fields)
state.records.items[usize(record_id)].fields = fields
} else result_type = try intern_record(state, ast.NodeId(maxval!(u32)), fields)
owned = false
state.cells[usize(id)].type = result_type
state.cells[usize(id)].record_checking = false
saved_assignment :: state.collecting_assignment
state.collecting_assignment = false
defer state.collecting_assignment = saved_assignment
for 0..count |i| {
if (ast.field(state.tree, ast.record_field(state.tree, id, i)).value) |value| {
try collect_expression(state, value)
}
}
return result_type
}
@hide:file
infer_record_literal proc(state @mut State, literal ast.NodeId, index usize, defaults bool) Type ! (mem.AllocError | diagnostics.CapacityError) {
if (state.types[usize(literal)].kind == .invalid) return state.types[usize(literal)]
count :: ast.record_field_count(state.tree, literal)
expected := state.cells[index].expected
if (expected.kind == .none and state.cells[index].type.kind == .record) expected = state.cells[index].type
if (ast.record_literal(state.tree, literal).type) |annotation| {
if (ast.expr_kind(state.tree, annotation) == .enum_literal) {
return try infer_variant_payload(state, literal, expected, defaults)
}
}
if (expected.kind == .invalid) {
seed(state, literal, expected)
return expected
}
if (expected.nominal != ast.NodeId(maxval!(u32))) {
try fail(state, literal, .type_mismatch, ast.node_span(state.tree, literal))
return state.types[usize(literal)]
}
if (expected.kind != .none and expected.kind != .record) {
try fail(state, literal, .expected_type, ast.node_span(state.tree, literal))
return state.types[usize(literal)]
}
if (expected.kind == .record and state.records.items[usize(expected.record_type)].is_union and count != 1) {
try fail(state, literal, .union_initializer_count, ast.node_span(state.tree, literal))
return state.types[usize(literal)]
}
if (expected.kind == .none) {
ready := true
for 0..count |i| {
value :: ast.field(state.tree, ast.record_field(state.tree, literal, i)).value?
actual :: try infer_shape(state, value, defaults)
if (actual.kind == .none) ready = false
if (actual.kind == .invalid) {
seed(state, literal, actual)
return actual
}
}
if (!ready) return Type{}
fields :: try mem.alloc(RecordField, state.records.allocator, count)
errdefer mem.free(state.records.allocator, fields)
for 0..count |i| {
view :: ast.field(state.tree, ast.record_field(state.tree, literal, i))
type := group_type(state, view.value?)
if (type.kind == .void or type.kind == .type_value or type.kind == .range) {
try fail(state, literal, .invalid_record_field_type, view.span)
mem.free(state.records.allocator, fields)
return state.types[usize(literal)]
}
if (view.name) |name| {
if (named_field(state.tree, fields[0..i], name) != no_index) {
try fail(state, literal, .duplicate_field, ast.node_span(state.tree, name))
mem.free(state.records.allocator, fields)
return state.types[usize(literal)]
}
}
fields[i] = RecordField{ name = view.name, type = type, default = null }
}
expected = try intern_record(state, ast.NodeId(maxval!(u32)), fields)
}
fields :: state.records.items[usize(expected.record_type)].fields
slot := state.slots[usize(literal)]
fresh :: slot == no_index
if (slot == no_index) {
values :: try mem.alloc(ast.NodeId, state.initializers.allocator, fields.len)
errdefer mem.free(state.initializers.allocator, values)
try arraylist.append(&state.initializers, RecordInitializer{ fields = values })
slot = state.initializers.items.len - 1
state.slots[usize(literal)] = slot
}
values :: state.initializers.items[slot].fields
if (values.len != fields.len) {
try fail(state, literal, .type_mismatch, ast.node_span(state.tree, literal))
return state.types[usize(literal)]
}
mem.set(values, ast.NodeId(maxval!(u32)))
for 0..count |i| {
node :: ast.record_field(state.tree, literal, i)
view :: ast.field(state.tree, node)
if (state.records.items[usize(expected.record_type)].is_union and view.name == null) {
try fail(state, literal, .unknown_field, view.span)
continue
}
field_slot := i
if (view.name) |name| field_slot = named_field(state.tree, fields, name)
if (field_slot >= fields.len) {
code :: if (view.name != null) diagnostics.Code.unknown_field else diagnostics.Code.tuple_arity_mismatch
try fail(state, literal, code, view.span)
continue
}
if (values[field_slot] != ast.NodeId(maxval!(u32))) {
try fail(state, literal, .duplicate_field, view.span)
continue
}
values[field_slot] = view.value?
if (state.records.items[usize(expected.record_type)].is_union) {
state.variant_slots[usize(literal)] = field_slot
}
try demand(state, view.value?, fields[field_slot].type, .type_mismatch)
_ = try infer_shape(state, view.value?, defaults)
}
for fields |field, i| {
if (state.records.items[usize(expected.record_type)].is_union) continue
if (values[i] != ast.NodeId(maxval!(u32))) continue
if (field.default) |value| {
values[i] = value
if (fresh) try depend(state, value, literal)
} else {
code :: if (field.name == null) diagnostics.Code.tuple_arity_mismatch else diagnostics.Code.missing_field
try fail(state, literal, code, ast.node_span(state.tree, literal))
}
}
if (state.types[usize(literal)].kind == .invalid) return state.types[usize(literal)]
state.cells[index].type = expected
return expected
}
@hide:file
type_syntax proc(kind ast.ExprKind) bool {
return (
kind == .record_type or kind == .union_type or kind == .enum_type
or kind == .distinct_type or kind == .alias_expr
or kind == .pointer_type or kind == .function_type or kind == .slice_type or kind == .optional_type
or kind == .fallible_type or kind == .sum_type or kind == .union_tag_type
)
}
@hide:file
type_reference proc(state @State, id ast.NodeId) bool {
kind :: ast.expr_kind(state.tree, id)
if (type_syntax(kind) or kind == .array_type) return true
binding :: sema.binding(state.resolution, id)
if (binding.kind == .builtin) return true
if (binding.kind != .declaration) return false
target :: binding.data.declaration
role :: state.cells[usize(target)].role
if (role == .parameter or role == .loop_capture) return false
return (
ast.decl_kind(state.tree, target) == .statement
and type_syntax(ast.expr_kind(state.tree, ast.statement_decl(state.tree, target).initializer))
)
}
@hide:file
immediate_backing proc(state @State, type Type) Type {
if (type.nominal == ast.NodeId(maxval!(u32))) return type
return state.backings[usize(type.nominal)]
}
@hide:file
check_type_expression proc(state @mut State, id ast.NodeId, nominal bool) Type ! (mem.AllocError | diagnostics.CapacityError) {
kind :: ast.expr_kind(state.tree, id)
if (kind == .record_type) return try check_record(state, id, nominal)
if (kind == .pointer_type) return try check_pointer_type(state, id)
if (kind == .function_type) return try check_function_type(state, id)
if (kind == .slice_type or kind == .optional_type) return try check_container_type(state, id)
if (kind == .fallible_type) return try check_fallible_type(state, id)
if (kind == .sum_type) return try check_sum_type(state, id)
if (kind == .union_type) {
type :: try check_union(state, id, nominal)
state.cells[usize(id)].type = type
return type
}
if (kind == .union_tag_type) {
operand :: try check_annotation(state, ast.type_operand(state.tree, id), false)
if (operand.kind == .invalid) return operand
if (operand.kind == .record and operand.nominal == ast.NodeId(maxval!(u32))) {
descriptor :: state.records.items[usize(operand.record_type)]
if (descriptor.is_union and descriptor.tag_type.kind == .enumeration) {
tag :: if (descriptor.declared_tag_type.kind == .enumeration)
descriptor.declared_tag_type else descriptor.tag_type
state.cells[usize(id)].type = tag
return tag
}
}
return invalid_type(try diagnostics.add(state.diagnostics, .invalid_inline_match_subject,
ast.node_span(state.tree, id)))
}
cached := state.cells[usize(id)].type
if (cached.kind == .type_value) {
cached.kind = cached.value_kind
return cached
}
if (cached.kind != .none) return cached
if (kind == .enum_type) {
type :: try check_enum(state, id)
state.cells[usize(id)].type = type
return type
}
if (state.cells[usize(id)].record_checking) {
return invalid_type(try diagnostics.add(state.diagnostics, .recursive_record, ast.node_span(state.tree, id)))
}
state.cells[usize(id)].record_checking = true
defer state.cells[usize(id)].record_checking = false
type := try check_annotation(state, ast.type_operand(state.tree, id), true)
operand_binding :: sema.binding(state.resolution, ast.type_operand(state.tree, id))
constraint :: (
operand_binding.kind == .builtin
and (operand_binding.data.builtin == .int or operand_binding.data.builtin == .float)
)
if (kind == .distinct_type and type.kind != .invalid) {
if (
type.kind == .none or type.kind == .void or type.kind == .range or type.kind == .type_value
or type.kind == .function or has_inferred_count(state, type) or constraint
) {
type = invalid_type(try diagnostics.add(state.diagnostics, .invalid_distinct_backing, ast.node_span(state.tree, id)))
} else {
state.backings[usize(id)] = type
type.nominal = id
}
}
state.cells[usize(id)].type = type
return type
}
@hide:file
integer_fits proc(value ConstantInteger, kind TypeKind) bool {
bits := u32(64)
match kind {
.u8, .i8: bits = 8
.u16, .i16: bits = 16
.u32, .i32: bits = 32
else: {}
}
limit := maxval!(u64)
if (unsigned_kind(kind)) {
if (value.negative) return false
if (bits < 64) limit = (u64(1) << bits) - 1
} else {
limit = u64(1) << (bits - 1)
if (!value.negative) limit -= 1
}
return value.magnitude <= limit
}
@hide:file
enum_member_slot proc(state @State, descriptor EnumType, name ast.NodeId) usize {
for descriptor.members |member, i| {
if (ast.name_id(state.tree, member.name) == ast.name_id(state.tree, name)) return i
}
return no_index
}
@hide:file
check_enum proc(state @mut State, id ast.NodeId) Type ! (mem.AllocError | diagnostics.CapacityError) {
cached := state.cells[usize(id)].type
if (cached.kind == .type_value) cached.kind = cached.value_kind
if (cached.kind != .none) return cached
if (state.cells[usize(id)].record_checking) {
return invalid_type(try diagnostics.add(state.diagnostics, .invalid_enum_backing, ast.node_span(state.tree, id)))
}
state.cells[usize(id)].record_checking = true
defer state.cells[usize(id)].record_checking = false
backing := Type{ kind = .u16 }
explicit := false
if (ast.enum_type(state.tree, id).backing) |annotation| {
backing = try check_annotation(state, annotation, false)
explicit = true
if (backing.kind == .invalid) return backing
binding :: sema.binding(state.resolution, annotation)
if (binding.kind == .builtin and binding.data.builtin == .int) {
return invalid_type(try diagnostics.add(state.diagnostics, .invalid_enum_backing, ast.node_span(state.tree, annotation)))
}
if (!integer_kind(backing.kind) or backing.nominal != ast.NodeId(maxval!(u32))) {
return invalid_type(try diagnostics.add(state.diagnostics, .invalid_enum_backing, ast.node_span(state.tree, annotation)))
}
}
count :: ast.enum_member_count(state.tree, id)
if (count == 0) {
return invalid_type(try diagnostics.add(state.diagnostics, .invalid_enum_value, ast.node_span(state.tree, id)))
}
members :: try mem.alloc(EnumMember, state.enums.allocator, count)
owned := true
defer {
if (owned) mem.free(state.enums.allocator, members)
}
next := ConstantInteger{ magnitude = 0 }
overflow := false
for 0..count |i| {
node :: ast.enum_member(state.tree, id, i)
field :: ast.field(state.tree, node)
value := next
valid := !overflow
if (!explicit) value = ConstantInteger{ magnitude = try global_variant(state, field.name?, Type{ kind = .void }) }
if (field.value) |expression| {
if (!explicit) valid = false
try collect_expression(state, expression)
if (evaluate_integer(state, expression, true)) |constant| {
value = constant
valid = explicit
} else valid = false
}
if (!integer_fits(value, backing.kind)) valid = false
if (
explicit and i != 0
and constant_order(
ConstantInteger{ magnitude = members[i - 1].magnitude, negative = members[i - 1].negative },
value,
) >= 0
) valid = false
for members[0..i] |member| {
if (
ast.name_id(state.tree, member.name) == ast.name_id(state.tree, field.name?)
or (member.magnitude == value.magnitude and member.negative == value.negative)
) valid = false
}
if (!valid) {
failure :: invalid_type(try diagnostics.add(state.diagnostics, .invalid_enum_value, field.span))
state.cells[usize(id)].type = failure
return failure
}
members[i] = EnumMember{ name = field.name?, magnitude = value.magnitude, negative = value.negative }
next = value
overflow = !value.negative and value.magnitude == maxval!(u64)
if (value.negative) {
next.magnitude -= 1
if (next.magnitude == 0) next.negative = false
} else if (!overflow) next.magnitude += 1
}
try arraylist.append(&state.enums, EnumType{
declaration = id, backing = backing.kind, explicit_backing = explicit, members = members,
})
owned = false
type :: Type{ kind = .enumeration, enum_type = EnumId(state.enums.items.len - 1) }
state.cells[usize(id)].type = type
return type
}
@hide:file
check_union proc(state @mut State, id ast.NodeId, nominal bool) Type ! (mem.AllocError | diagnostics.CapacityError) {
cached := state.cells[usize(id)].type
if (cached.kind == .invalid) return cached
if (!state.cells[usize(id)].record_checking and (cached.kind == .record or cached.kind == .type_value)) {
cached.kind = .record
return cached
}
if (state.cells[usize(id)].record_checking) {
if (state.pointer_depth > state.cells[usize(id)].record_pointer_depth) {
cached.kind = .record
return cached
}
return invalid_type(try diagnostics.add(state.diagnostics, .recursive_record, ast.node_span(state.tree, id)))
}
state.cells[usize(id)].record_checking = true
defer state.cells[usize(id)].record_checking = false
state.cells[usize(id)].record_pointer_depth = state.pointer_depth
record_id :: RecordId(state.records.items.len)
empty :: try mem.alloc(RecordField, state.records.allocator, 0)
declaration :: if (nominal) id else ast.NodeId(maxval!(u32))
arraylist.append(&state.records, RecordType{
declaration = declaration, source = id, fields = empty, is_union = true,
}) catch |err| {
mem.free(state.records.allocator, empty)
return err
}
result_type :: Type{ kind = .record, record_type = record_id }
state.cells[usize(id)].type = result_type
count :: ast.record_field_count(state.tree, id)
fields :: try mem.alloc(RecordField, state.records.allocator, count)
owned := true
defer {
if (owned) mem.free(state.records.allocator, fields)
}
view :: ast.union_type(state.tree, id)
tag := Type{}
if (view.tag) |annotation| {
tag = try check_annotation(state, annotation, false)
if (tag.kind == .invalid) return tag
if (tag.kind != .enumeration or tag.nominal != ast.NodeId(maxval!(u32))) {
return invalid_type(try diagnostics.add(state.diagnostics, .invalid_union_tag, ast.node_span(state.tree, annotation)))
}
}
for 0..count |i| {
field :: ast.field(state.tree, ast.record_field(state.tree, id, i))
type :: try check_annotation(state, field.type?, true)
if (type.kind == .invalid) return type
if (
type.kind == .none or type.kind == .range or type.kind == .type_value or field.value != null
or (type.kind == .void and tag.kind == .none and !view.auto_tag)
) return invalid_type(try diagnostics.add(state.diagnostics, .invalid_record_field_type, field.span))
if (field.name == null or named_field(state.tree, fields[0..i], field.name?) != no_index) {
return invalid_type(try diagnostics.add(state.diagnostics, .duplicate_field, field.span))
}
fields[i] = RecordField{ name = field.name, type = type, default = null }
}
if (tag.kind == .enumeration) {
descriptor :: state.enums.items[usize(tag.enum_type)]
for fields |field| {
if (enum_member_slot(state, descriptor, field.name?) == no_index) {
return invalid_type(try diagnostics.add(state.diagnostics, .invalid_union_tag, ast.node_span(state.tree, field.name?)))
}
}
}
declared_tag :: tag
if (view.auto_tag or tag.kind == .enumeration) {
members :: try mem.alloc(EnumMember, state.enums.allocator, count)
members_owned := true
defer if (members_owned) mem.free(state.enums.allocator, members)
for fields |field, i| {
members[i] = EnumMember{
name = field.name?, magnitude = try global_variant(state, field.name?, field.type),
}
if (members[i].magnitude > u64(maxval!(u16))) {
return invalid_type(try diagnostics.add(state.diagnostics, .invalid_union_tag, ast.node_span(state.tree, id)))
}
}
try arraylist.append(&state.enums, EnumType{ declaration = id, backing = .u16, members = members })
members_owned = false
tag = Type{ kind = .enumeration, enum_type = EnumId(state.enums.items.len - 1) }
}
mem.free(state.records.allocator, state.records.items[usize(record_id)].fields)
state.records.items[usize(record_id)].fields = fields
state.records.items[usize(record_id)].tag_type = tag
state.records.items[usize(record_id)].declared_tag_type = declared_tag
owned = false
state.cells[usize(id)].type = result_type
return result_type
}
@hide:file
select_enum_member proc(state @mut State, id, name ast.NodeId, type Type) Type ! (mem.AllocError | diagnostics.CapacityError) {
slot :: enum_member_slot(state, state.enums.items[usize(type.enum_type)], name)
if (slot == no_index) {
try fail(state, id, .unknown_field, ast.node_span(state.tree, name))
seed(state, id, state.types[usize(id)])
return state.types[usize(id)]
}
state.slots[usize(id)] = slot
state.backings[usize(id)] = type
state.cells[root(state, usize(id))].expected = type
seed(state, id, type)
return type
}
@hide:file
infer_enum_literal proc(state @mut State, id ast.NodeId, expected Type, defaults bool) Type ! (mem.AllocError | diagnostics.CapacityError) {
if (state.types[usize(id)].kind == .invalid) return state.types[usize(id)]
name :: ast.enum_literal(state.tree, id)
if (expected.kind == .enumeration and expected.nominal == ast.NodeId(maxval!(u32))) {
return try select_enum_member(state, id, name, expected)
}
if (expected.kind == .record and expected.nominal == ast.NodeId(maxval!(u32))) {
descriptor :: state.records.items[usize(expected.record_type)]
if (descriptor.is_union and descriptor.tag_type.kind == .enumeration) {
slot :: named_field(state.tree, descriptor.fields, name)
if (slot == no_index) {
try fail(state, id, .unknown_field, ast.node_span(state.tree, name))
return state.types[usize(id)]
}
if (descriptor.fields[slot].type.kind != .void) {
try fail(state, id, .type_mismatch, ast.node_span(state.tree, id))
return state.types[usize(id)]
}
state.variant_slots[usize(id)] = slot
seed(state, id, expected)
return expected
}
}
if (defaults or expected.kind != .none) {
try fail(state, id, .enum_context_required, ast.node_span(state.tree, id))
seed(state, id, state.types[usize(id)])
return state.types[usize(id)]
}
return Type{}
}
@hide:file
validate_constructor proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
destination :: state.types[usize(id)]
if (destination.kind == .invalid) return
if (ast.call_arg_count(state.tree, id) != 1) {
try fail(state, id, .argument_count_mismatch, ast.node_span(state.tree, id))
return
}
argument :: ast.call_arg(state.tree, id, 0)
actual :: state.types[usize(argument)]
if (actual.kind == .invalid) return
if (actual.nominal != ast.NodeId(maxval!(u32)) and same_type(state, immediate_backing(state, actual), destination)) return
if (destination.nominal == ast.NodeId(maxval!(u32))) {
try fail(state, id, .not_callable, ast.node_span(state.tree, ast.call_expr(state.tree, id).callee))
return
}
backing :: state.backings[usize(id)]
if (backing.nominal == ast.NodeId(maxval!(u32)) and numeric_kind(backing.kind)) {
source :: immediate_backing(state, actual)
if (!numeric_kind(source.kind) or source.nominal != ast.NodeId(maxval!(u32))) {
try fail(state, argument, .invalid_operand, ast.node_span(state.tree, argument))
}
} else try compatible(state, argument, backing)
}
@hide:file
infer_variant_payload proc(state @mut State, id ast.NodeId, expected Type, defaults bool) Type ! (mem.AllocError | diagnostics.CapacityError) {
annotation :: ast.record_literal(state.tree, id).type?
if (expected.kind == .none and !defaults) return Type{}
if (expected.kind != .record) {
try fail(state, id, .enum_context_required, ast.node_span(state.tree, id))
return state.types[usize(id)]
}
descriptor :: state.records.items[usize(expected.record_type)]
if (!descriptor.is_union or descriptor.tag_type.kind != .enumeration) {
try fail(state, id, .enum_context_required, ast.node_span(state.tree, id))
return state.types[usize(id)]
}
name :: ast.enum_literal(state.tree, annotation)
slot :: named_field(state.tree, descriptor.fields, name)
if (slot == no_index) {
try fail(state, id, .unknown_field, ast.node_span(state.tree, name))
return state.types[usize(id)]
}
state.variant_slots[usize(id)] = slot
_ = try select_enum_member(state, annotation, name, descriptor.tag_type)
payload :: descriptor.fields[slot].type
count :: ast.record_field_count(state.tree, id)
direct :: count == 1 and ast.field(state.tree, ast.record_field(state.tree, id, 0)).name == null
if (direct) {
value :: ast.field(state.tree, ast.record_field(state.tree, id, 0)).value?
try demand(state, value, payload, .type_mismatch)
_ = try infer_shape(state, value, defaults)
if (state.slots[usize(id)] == no_index) {
values :: try mem.alloc(ast.NodeId, state.initializers.allocator, descriptor.fields.len)
errdefer mem.free(state.initializers.allocator, values)
mem.set(values, ast.NodeId(maxval!(u32)))
values[slot] = value
try arraylist.append(&state.initializers, RecordInitializer{ fields = values })
state.slots[usize(id)] = state.initializers.items.len - 1
}
} else {
if (payload.kind != .record and !(payload.kind == .void and count == 0)) {
try fail(state, id, .type_mismatch, ast.node_span(state.tree, id))
return state.types[usize(id)]
}
state.backings[usize(id)] = payload
if (payload.kind == .record) {
fields :: state.records.items[usize(payload.record_type)].fields
if (state.slots[usize(id)] == no_index) {
values :: try mem.alloc(ast.NodeId, state.initializers.allocator, fields.len)
errdefer mem.free(state.initializers.allocator, values)
try arraylist.append(&state.initializers, RecordInitializer{ fields = values })
state.slots[usize(id)] = state.initializers.items.len - 1
}
values :: state.initializers.items[state.slots[usize(id)]].fields
mem.set(values, ast.NodeId(maxval!(u32)))
for 0..count |i| {
field :: ast.field(state.tree, ast.record_field(state.tree, id, i))
index := i
if (field.name) |field_name| index = named_field(state.tree, fields, field_name)
if (index >= fields.len) {
try fail(state, id, .unknown_field, field.span)
continue
}
if (values[index] != ast.NodeId(maxval!(u32))) {
try fail(state, id, .duplicate_field, field.span)
continue
}
values[index] = field.value?
try demand(state, field.value?, fields[index].type, .type_mismatch)
_ = try infer_shape(state, field.value?, defaults)
}
for fields |field, i| {
if (values[i] != ast.NodeId(maxval!(u32))) continue
if (field.default) |value| {
values[i] = value
} else try fail(state, id, .missing_field, ast.node_span(state.tree, id))
}
}
}
if (state.types[usize(id)].kind == .invalid) return state.types[usize(id)]
seed(state, id, expected)
return expected
}
@hide:file
collect_match proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
subject :: ast.match_expr(state.tree, id).subject
try collect_expression(state, subject)
try depend(state, subject, id)
for 0..ast.match_arm_count(state.tree, id) |i| {
arm_id :: ast.match_arm(state.tree, id, i)
arm :: ast.match_arm_view(state.tree, arm_id)
for 0..ast.match_pattern_count(state.tree, arm_id) |j| {
pattern :: ast.match_pattern(state.tree, arm_id, j)
state.cells[usize(pattern)].match_pattern = true
try collect_expression(state, pattern)
try depend(state, pattern, id)
}
if (arm.capture) |capture| {
state.cells[usize(capture)].role = .loop_capture
state.cells[usize(capture)].fixed = true
state.cells[usize(capture)].local = true
binding :: sema.binding(state.resolution, capture)
if (binding.kind == .invalid) state.cells[usize(capture)].type = invalid_type(binding.data.diagnostic)
if (ast.match_expr(state.tree, id).optional_capture and i == 0) {
state.cells[usize(subject)].capture = capture
}
try depend(state, capture, arm.body)
}
saved_arm :: state.narrowing_arm
state.cells[usize(arm_id)].enclosing_arm = saved_arm
state.cells[usize(arm_id)].match_owner = id
state.narrowing_arm = arm_id
if (arm.expression) {
try collect_expression(state, arm.body)
} else {
try collect_value_block(state, arm.body, arm.body)
}
state.narrowing_arm = saved_arm
}
}
@hide:file
infer_match proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.AllocError | diagnostics.CapacityError) {
view :: ast.match_expr(state.tree, id)
subject :: try infer_shape(state, view.subject, defaults)
if (subject.kind == .invalid) {
seed(state, id, subject)
return subject
}
key := subject
if (view.optional_capture) {
if (subject.kind != .none and subject.kind != .optional) {
try fail(state, id, .condition_not_bool, ast.node_span(state.tree, view.subject))
seed(state, id, state.types[usize(id)])
return state.types[usize(id)]
}
if (subject.kind == .optional) key = Type{ kind = .bool }
}
tagged := false
if (subject.kind == .record) {
descriptor :: state.records.items[usize(subject.record_type)]
tagged = (
descriptor.is_union and descriptor.tag_type.kind == .enumeration
and subject.nominal == ast.NodeId(maxval!(u32))
)
if (tagged) key = descriptor.tag_type
}
if (
!view.optional_capture and subject.kind != .none and !numeric_kind(subject.kind) and subject.kind != .bool
and !(subject.kind == .enumeration and subject.nominal == ast.NodeId(maxval!(u32))) and !tagged
) {
try fail(state, id, .invalid_match_subject, ast.node_span(state.tree, id))
seed(state, id, state.types[usize(id)])
return state.types[usize(id)]
}
result :: root(state, usize(id))
expected := state.cells[result].expected
has_block := false
for 0..ast.match_arm_count(state.tree, id) |i| {
arm_id :: ast.match_arm(state.tree, id, i)
arm :: ast.match_arm_view(state.tree, arm_id)
payload := Type{}
capture_valid := tagged and !arm.catch_all
if (view.optional_capture and subject.kind == .optional and i == 0) {
payload = state.arrays.items[usize(subject.array_type)].element
capture_valid = true
}
for 0..ast.match_pattern_count(state.tree, arm_id) |j| {
pattern :: ast.match_pattern(state.tree, arm_id, j)
kind :: ast.expr_kind(state.tree, pattern)
if (key.kind == .enumeration and kind == .enum_literal) {
_ = try infer_enum_literal(state, pattern, key, false)
if (tagged) {
fields :: state.records.items[usize(subject.record_type)].fields
slot :: named_field(state.tree, fields, ast.enum_literal(state.tree, pattern))
if (slot != no_index) {
state.variant_slots[usize(pattern)] = slot
if (payload.kind == .none) {
payload = fields[slot].type
} else if (!same_type(state, payload, fields[slot].type)) capture_valid = false
} else capture_valid = false
}
} else if (key.kind != .none and key.kind != .enumeration) {
if (kind == .range) {
range_view :: ast.range_expr(state.tree, pattern)
try demand(state, range_view.start, key, .invalid_match_pattern)
try demand(state, range_view.end, key, .invalid_match_pattern)
} else try demand(state, pattern, key, .invalid_match_pattern)
}
}
if (arm.capture) |capture| {
if (key.kind != .none and (!capture_valid or (!view.optional_capture and payload.kind == .void) or payload.kind == .none)) {
try fail(state, id, .invalid_match_capture, ast.node_span(state.tree, capture))
} else if (payload.kind != .none and state.cells[usize(capture)].type.kind != .invalid) {
seed(state, capture, payload)
for 0..(state.cells.len) |j| {
if (state.cells[j].role != .expression or !declaration_reference(state.tree, ast.NodeId(j))) continue
binding :: sema.binding(state.resolution, ast.NodeId(j))
if (
binding.kind == .declaration and binding.data.declaration == capture
and !has_narrowing(state, ast.NodeId(j))
) seed(state, ast.NodeId(j), payload)
}
}
}
if (arm.expression or state.cells[usize(arm.body)].first_yield != ast.NodeId(maxval!(u32))) {
actual :: try infer_shape(state, arm.body, defaults)
if (expected.kind == .none and actual.kind != .none and actual.kind != .invalid) expected = actual
} else has_block = true
}
if (expected.kind == .none and has_block and defaults) expected = Type{ kind = .void }
if (expected.kind != .none) {
for 0..ast.match_arm_count(state.tree, id) |i| {
arm :: ast.match_arm_view(state.tree, ast.match_arm(state.tree, id, i))
try demand(state, arm.body, expected, .type_mismatch)
}
seed(state, id, expected)
}
return group_type(state, id)
}
@hide:file
constant_order proc(left, right ConstantInteger) i32 {
if (left.negative != right.negative) {
if (left.negative) return -1
return 1
}
if (left.magnitude == right.magnitude) return 0
less :: left.magnitude < right.magnitude
if (less != left.negative) return -1
return 1
}
@hide:file
constant_real proc(state @mut State, id ast.NodeId) ?f64 ! mem.AllocError {
if (ast.float_value(state.tree, id)) |value| return value
missing ?f64 :: null
if (ast.expr_kind(state.tree, id) == .literal_float) {
return strconv.parse_float(state.edges.allocator, ast.literal_text(state.tree, id)) catch |err| {
match err {
.out_of_memory: return err
.invalid_number, .out_of_range: return missing
}
}
}
if (evaluate_integer(state, id, true)) |value| {
real :: f64(value.magnitude)
if (value.negative) return -real
return real
}
if (ast.expr_kind(state.tree, id) == .unary) {
view :: ast.unary_expr(state.tree, id)
if (view.operator == .negate) {
if (try constant_real(state, view.operand)) |value| return -value
}
}
return missing
}
@hide:file
PatternInterval :: struct {
low ConstantInteger = ConstantInteger{ magnitude = 0 }
high ConstantInteger = ConstantInteger{ magnitude = 0 }
real_low f64 = 0.0
real_high f64 = 0.0
real bool = false
inclusive bool = true
}
@hide:file
pattern_interval proc(state @mut State, id ast.NodeId, key Type) ?PatternInterval ! mem.AllocError {
missing ?PatternInterval :: null
low := id
high := id
inclusive := true
if (ast.expr_kind(state.tree, id) == .range) {
range_view :: ast.range_expr(state.tree, id)
low = range_view.start
high = range_view.end
inclusive = range_view.inclusive
}
interval := PatternInterval{ inclusive = inclusive, real = key.kind == .float }
if (interval.real) {
if (try constant_real(state, low)) |value| interval.real_low = value else return missing
if (try constant_real(state, high)) |value| interval.real_high = value else return missing
} else if (key.kind == .bool) {
if (ast.expr_kind(state.tree, low) != .literal_bool or low != high) return missing
value :: mem.eql(ast.literal_text(state.tree, low), "true")
interval.low.magnitude = if (value) 1 else 0
interval.high = interval.low
} else {
if (evaluate_integer(state, low, true)) |value| interval.low = value else return missing
if (evaluate_integer(state, high, true)) |value| interval.high = value else return missing
}
return interval
}
@hide:file
interval_overlap proc(left, right PatternInterval) bool {
if (left.real) {
return (
(left.real_low < right.real_high or (right.inclusive and left.real_low == right.real_high))
and (right.real_low < left.real_high or (left.inclusive and right.real_low == left.real_high))
)
}
return (
(constant_order(left.low, right.high) < 0 or (right.inclusive and constant_order(left.low, right.high) == 0))
and (constant_order(right.low, left.high) < 0 or (left.inclusive and constant_order(right.low, left.high) == 0))
)
}
@hide:file
validate_match proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
view :: ast.match_expr(state.tree, id)
subject :: state.types[usize(view.subject)]
if (subject.kind == .invalid) return
key := subject
if (view.optional_capture) {
if (subject.kind != .optional) {
try fail(state, id, .condition_not_bool, ast.node_span(state.tree, view.subject))
return
}
key = Type{ kind = .bool }
}
if (subject.kind == .record) key = state.records.items[usize(subject.record_type)].tag_type
if (key.kind != .enumeration and !numeric_kind(key.kind) and key.kind != .bool) {
try fail(state, id, .invalid_match_subject, ast.node_span(state.tree, id))
return
}
covered := usize(0)
seen_false := false
seen_true := false
catch_all := false
for 0..ast.match_arm_count(state.tree, id) |i| {
arm_id :: ast.match_arm(state.tree, id, i)
arm :: ast.match_arm_view(state.tree, arm_id)
if (catch_all) try fail(state, id, .unreachable_match_arm, ast.node_span(state.tree, arm_id))
if (arm.catch_all) {
if (key.kind == .enumeration and covered == state.enums.items[usize(key.enum_type)].members.len) {
try fail(state, id, .unreachable_match_arm, ast.node_span(state.tree, arm_id))
}
if (key.kind == .bool and seen_false and seen_true) {
try fail(state, id, .unreachable_match_arm, ast.node_span(state.tree, arm_id))
}
catch_all = true
}
for 0..ast.match_pattern_count(state.tree, arm_id) |j| {
pattern :: ast.match_pattern(state.tree, arm_id, j)
actual :: state.types[usize(pattern)]
if (actual.kind == .invalid) continue
kind :: ast.expr_kind(state.tree, pattern)
if (key.kind == .enumeration) {
if (kind != .enum_literal or !same_type(state, actual, key)) {
try fail(state, id, .invalid_match_pattern, ast.node_span(state.tree, pattern))
continue
}
covered += 1
} else if (kind == .range) {
range_view :: ast.range_expr(state.tree, pattern)
if (key.kind == .bool) try fail(state, id, .invalid_match_pattern, ast.node_span(state.tree, pattern))
try compatible(state, range_view.start, key)
try compatible(state, range_view.end, key)
} else try compatible(state, pattern, key)
current :: try pattern_interval(state, pattern, key)
if (current) |interval| {
if (
(interval.real and (
interval.real_low > interval.real_high
or (!interval.inclusive and interval.real_low == interval.real_high)))
or (!interval.real and (
constant_order(interval.low, interval.high) > 0
or (!interval.inclusive and constant_order(interval.low, interval.high) == 0)))
) try fail(state, id, .invalid_match_pattern, ast.node_span(state.tree, pattern))
if (key.kind == .bool) {
if (interval.low.magnitude == 0) seen_false = true else seen_true = true
}
}
for 0..(i + 1) |previous_i| {
previous_arm :: ast.match_arm(state.tree, id, previous_i)
count :: if (previous_i == i) j else ast.match_pattern_count(state.tree, previous_arm)
for 0..count |previous_j| {
previous :: ast.match_pattern(state.tree, previous_arm, previous_j)
duplicate := false
if (key.kind == .enumeration) {
duplicate = (
state.slots[usize(previous)] != no_index
and state.slots[usize(previous)] == state.slots[usize(pattern)]
)
} else if (current) |interval| {
if (try pattern_interval(state, previous, key)) |other| duplicate = interval_overlap(interval, other)
}
if (duplicate) try fail(state, id, .duplicate_match_pattern, ast.node_span(state.tree, pattern))
}
}
}
if (arm.expression) try compatible(state, arm.body, state.types[usize(id)])
}
exhaustive :: (
catch_all
or (key.kind == .enumeration and covered == state.enums.items[usize(key.enum_type)].members.len)
or (key.kind == .bool and seen_false and seen_true)
)
state.exhaustive[usize(id)] = exhaustive
if (!exhaustive) try fail(state, id, .non_exhaustive_match, ast.node_span(state.tree, id))
}
@hide:file
global_variant proc(state @mut State, name ast.NodeId, payload Type) u64 ! mem.AllocError {
for state.variants.items |variant, i| {
if (
ast.name_id(state.tree, variant.name) == ast.name_id(state.tree, name)
and same_type(state, variant.payload, payload)
) return u64(i + 1)
}
try arraylist.append(&state.variants, Variant{ name = name, payload = payload })
return u64(state.variants.items.len)
}
#! applies the same value-type coercion rules used for storage, arguments, and returns.
value_assignable proc(result @Result, actual, expected Type) bool {
# assignability reads only descriptors, signature annotations, and the borrowed AST.
state State := undefined
state.tree = result.storage.tree
state.types = result.storage.types
state.arrays = result.storage.arrays
state.pointers = result.storage.pointers
state.records = result.storage.records
state.enums = result.storage.enums
return assignable_type(&state, actual, expected)
}
#! tests source-error subset membership using the descriptors retained by checking.
error_assignable proc(result @Result, actual, expected Type) bool {
return error_subset(result.storage.records.items, result.storage.enums.items, actual, expected)
}
@hide:file
error_subset proc(records []RecordType, enums []EnumType, actual, expected Type) bool {
if (actual.nominal != expected.nominal) return false
if (actual.kind == .record and expected.kind == .record and actual.record_type == expected.record_type) return true
if (actual.kind == .enumeration and expected.kind == .enumeration and actual.enum_type == expected.enum_type) return true
target := expected
if (expected.kind == .record) {
descriptor :: records[usize(expected.record_type)]
if (!descriptor.is_union or descriptor.tag_type.kind != .enumeration) return false
if (descriptor.composed and actual.kind == .record and !records[usize(actual.record_type)].is_union) {
for descriptor.fields |field| {
if (
field.type.kind == .record and field.type.record_type == actual.record_type
and field.type.nominal == actual.nominal
) return true
}
return false
}
target = descriptor.tag_type
}
if (target.kind != .enumeration) return false
destination :: enums[usize(target.enum_type)]
source := actual
if (actual.kind == .record) {
descriptor :: records[usize(actual.record_type)]
if (!descriptor.is_union or descriptor.tag_type.kind != .enumeration) return false
source = descriptor.tag_type
}
if (source.kind != .enumeration) return false
origin :: enums[usize(source.enum_type)]
if (origin.explicit_backing or destination.explicit_backing) {
if (
!origin.explicit_backing or !destination.explicit_backing
or origin.declaration != destination.declaration or origin.backing != destination.backing
) return false
}
for origin.members |member| {
found := false
for destination.members |other| {
if (member.magnitude == other.magnitude and member.negative == other.negative) found = true
}
if (!found) return false
}
return true
}
@hide:file
type_has_variant proc(state @State, type Type, name ast.NodeId) bool {
if (type.kind == .enumeration) return enum_member_slot(state, state.enums.items[usize(type.enum_type)], name) != no_index
if (type.kind != .record) return false
descriptor :: state.records.items[usize(type.record_type)]
return (
descriptor.is_union and descriptor.tag_type.kind == .enumeration
and named_field(state.tree, descriptor.fields, name) != no_index
)
}
@hide:file
check_fallible_type proc(state @mut State, id ast.NodeId) Type ! (mem.AllocError | diagnostics.CapacityError) {
cached := state.cells[usize(id)].type
if (cached.kind == .type_value) cached.kind = cached.value_kind
if (cached.kind != .none) return cached
expression :: ast.binary_expr(state.tree, id)
success :: try check_annotation(state, expression.left, true)
failure :: try check_annotation(state, expression.right, false)
if (success.kind == .invalid) return success
if (failure.kind == .invalid) return failure
valid_error :: (
failure.kind == .enumeration
or (failure.kind == .record
and (!state.records.items[usize(failure.record_type)].is_union
or state.records.items[usize(failure.record_type)].tag_type.kind == .enumeration))
)
if (
!valid_error or success.kind == .none or success.kind == .range or success.kind == .type_value
or success.kind == .anyopaque or has_inferred_count(state, success)
) return invalid_type(try diagnostics.add(state.diagnostics, .invalid_fallible_type, ast.node_span(state.tree, id)))
type :: try intern_array(state, ArrayType{ kind = .fallible, element = success, error_type = failure })
state.cells[usize(id)].type = type
return type
}
@hide:file
SumVariant :: struct {
name ast.NodeId
payload Type
tag u64
}
@hide:file
append_sum_variants proc(state @mut State, variants @mut arraylist.ArrayList(SumVariant), type Type) bool ! mem.AllocError {
if (type.kind == .enumeration) {
descriptor :: state.enums.items[usize(type.enum_type)]
if (descriptor.explicit_backing) return false
for (descriptor.members) |member| try arraylist.append(variants, SumVariant{
name = member.name, payload = Type{ kind = .void }, tag = member.magnitude,
})
return true
}
if (type.kind != .record or type.nominal != ast.NodeId(maxval!(u32))) return false
descriptor :: state.records.items[usize(type.record_type)]
if (descriptor.is_union) {
if (descriptor.tag_type.kind != .enumeration) return false
members :: state.enums.items[usize(descriptor.tag_type.enum_type)].members
for (descriptor.fields) |field, i| try arraylist.append(variants, SumVariant{
name = field.name?, payload = field.type, tag = members[i].magnitude,
})
return true
}
name := ast.NodeId(maxval!(u32))
for 0..ast.package_decl_count(state.tree) |i| {
declaration :: ast.package_decl(state.tree, i)
if (
ast.decl_kind(state.tree, declaration) == .statement
and ast.statement_decl(state.tree, declaration).initializer == descriptor.declaration
) name = declaration
}
if (name == ast.NodeId(maxval!(u32))) {
for 0..(state.cells.len) |i| {
if (state.cells[i].role != .declaration) continue
if (ast.statement_decl(state.tree, ast.NodeId(i)).initializer == descriptor.declaration) name = ast.NodeId(i)
}
}
if (name == ast.NodeId(maxval!(u32))) return false
try arraylist.append(variants, SumVariant{ name = name, payload = type, tag = try global_variant(state, name, type) })
return true
}
@hide:file
check_sum_type proc(state @mut State, id ast.NodeId) Type ! (mem.AllocError | diagnostics.CapacityError) {
cached := state.cells[usize(id)].type
if (cached.kind == .type_value) cached.kind = cached.value_kind
if (cached.kind != .none) return cached
if (state.cells[usize(id)].record_checking) {
return invalid_type(try diagnostics.add(state.diagnostics, .recursive_record, ast.node_span(state.tree, id)))
}
state.cells[usize(id)].record_checking = true
defer state.cells[usize(id)].record_checking = false
expression :: ast.binary_expr(state.tree, id)
left :: try check_annotation(state, expression.left, false)
right :: try check_annotation(state, expression.right, false)
if (left.kind == .invalid) return left
if (right.kind == .invalid) return right
variants arraylist.ArrayList(SumVariant) := arraylist.init(state.records.allocator)
defer arraylist.deinit(&variants)
valid := try append_sum_variants(state, &variants, left)
if (valid) valid = try append_sum_variants(state, &variants, right)
if (!valid) {
return invalid_type(try diagnostics.add(state.diagnostics, .invalid_sum_type, ast.node_span(state.tree, id)))
}
count := usize(0)
all_void := true
for 0..(variants.items.len) |i| {
variant :: variants.items[i]
duplicate := false
for variants.items[0..count] |previous| {
if (ast.name_id(state.tree, previous.name) != ast.name_id(state.tree, variant.name)) continue
if (previous.tag != variant.tag) {
return invalid_type(try diagnostics.add(state.diagnostics, .invalid_sum_type, ast.node_span(state.tree, id)))
}
duplicate = true
}
if (duplicate) continue
variants.items[count] = variant
count += 1
if (variant.payload.kind != .void) all_void = false
}
members :: try mem.alloc(EnumMember, state.enums.allocator, count)
members_owned := true
defer if (members_owned) mem.free(state.enums.allocator, members)
for variants.items[0..count] |variant, i| {
if (variant.tag > u64(maxval!(u16))) {
return invalid_type(try diagnostics.add(state.diagnostics, .invalid_sum_type, ast.node_span(state.tree, id)))
}
members[i] = EnumMember{ name = variant.name, magnitude = variant.tag }
}
try arraylist.append(&state.enums, EnumType{ declaration = id, backing = .u16, members = members, composed = true })
members_owned = false
tag :: Type{ kind = .enumeration, enum_type = EnumId(state.enums.items.len - 1) }
type := tag
if (!all_void) {
fields :: try mem.alloc(RecordField, state.records.allocator, count)
errdefer mem.free(state.records.allocator, fields)
for (variants.items[0..count]) |variant, i| fields[i] = RecordField{
name = variant.name, type = variant.payload, default = null,
}
try arraylist.append(&state.records, RecordType{
declaration = id, fields = fields, is_union = true, tag_type = tag, composed = true,
})
type = Type{ kind = .record, record_type = RecordId(state.records.items.len - 1) }
}
state.cells[usize(id)].type = type
return type
}
@hide:file
seed_capture proc(state @mut State, capture ast.NodeId, type Type) void {
seed(state, capture, type)
for 0..(state.cells.len) |i| {
if (state.cells[i].role != .expression or !declaration_reference(state.tree, ast.NodeId(i))) continue
binding :: sema.binding(state.resolution, ast.NodeId(i))
if (
binding.kind == .declaration and binding.data.declaration == capture
and !has_narrowing(state, ast.NodeId(i))
) seed(state, ast.NodeId(i), type)
}
}
@hide:file
infer_fallible_expression proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.AllocError | diagnostics.CapacityError) {
catching :: ast.expr_kind(state.tree, id) == .catch_expr
operand :: if (catching) ast.catch_expr(state.tree, id).operand else ast.type_operand(state.tree, id)
source :: try infer_shape(state, operand, defaults)
if (source.kind == .invalid) {
seed(state, id, source)
return source
}
if (source.kind != .fallible) {
if (defaults and source.kind != .none) {
try fail(state, id, .invalid_fallible_operand, ast.node_span(state.tree, operand))
seed(state, id, state.types[usize(id)])
}
return group_type(state, id)
}
descriptor :: state.arrays.items[usize(source.array_type)]
seed(state, id, descriptor.element)
if (catching) {
expression :: ast.catch_expr(state.tree, id)
if (expression.capture) |capture| seed_capture(state, capture, descriptor.error_type)
try demand(state, expression.handler, descriptor.element, .catch_type_mismatch)
_ = try infer_shape(state, expression.handler, defaults)
}
return group_type(state, id)
}
@hide:file
validate_fallible_expression proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
catching :: ast.expr_kind(state.tree, id) == .catch_expr
operand :: if (catching) ast.catch_expr(state.tree, id).operand else ast.type_operand(state.tree, id)
source :: state.types[usize(operand)]
if (source.kind == .invalid) return
if (source.kind != .fallible) {
try fail(state, id, .invalid_fallible_operand, ast.node_span(state.tree, operand))
return
}
descriptor :: state.arrays.items[usize(source.array_type)]
if (catching) {
expression :: ast.catch_expr(state.tree, id)
if (!expression.handler_is_block) {
actual :: state.types[usize(expression.handler)]
if (actual.kind != .invalid and !assignable_value(state, expression.handler, actual, descriptor.element)) try fail(state, expression.handler, .catch_type_mismatch, ast.node_span(state.tree, expression.handler))
}
return
}
if (state.cells[usize(id)].in_cleanup) {
try fail(state, id, .cleanup_control_flow, ast.node_span(state.tree, id))
return
}
annotation :: state.cells[usize(id)].return_annotation
if (annotation == ast.NodeId(maxval!(u32))) {
try fail(state, id, .try_outside_fallible, ast.node_span(state.tree, id))
return
}
result :: state.types[usize(annotation)]
if (result.kind == .invalid) return
if (result.kind != .fallible) try fail(state, id, .try_outside_fallible, ast.node_span(state.tree, id))
else if (
!error_subset(
state.records.items,
state.enums.items,
descriptor.error_type,
state.arrays.items[usize(result.array_type)].error_type,
)
) try fail(state, id, .incompatible_propagated_error, ast.node_span(state.tree, id))
}
@hide:file
matching_narrowing_arm proc(state @State, id ast.NodeId) ast.NodeId {
binding :: sema.binding(state.resolution, id)
if (binding.kind != .declaration) return ast.NodeId(maxval!(u32))
target :: binding.data.declaration
if (
state.cells[usize(target)].role == .declaration
and ast.statement_decl(state.tree, target).mutability == .variable
) return ast.NodeId(maxval!(u32))
arm := state.cells[usize(id)].narrowing_arm
while (arm != ast.NodeId(maxval!(u32))) {
owner :: state.cells[usize(arm)].match_owner
view :: ast.match_expr(state.tree, owner)
if (!view.optional_capture) {
original :: sema.binding(state.resolution, view.subject)
if (original.kind == .declaration and original.data.declaration == binding.data.declaration) return arm
}
arm = state.cells[usize(arm)].enclosing_arm
}
return ast.NodeId(maxval!(u32))
}
@hide:file
has_narrowing proc(state @State, id ast.NodeId) bool {
return matching_narrowing_arm(state, id) != ast.NodeId(maxval!(u32))
}
@hide:file
arm_names_variant proc(state @State, arm, name ast.NodeId) bool {
for 0..ast.match_pattern_count(state.tree, arm) |i| {
pattern :: ast.match_pattern(state.tree, arm, i)
if (
ast.expr_kind(state.tree, pattern) == .enum_literal
and ast.name_id(state.tree, ast.enum_literal(state.tree, pattern)) == ast.name_id(state.tree, name)
) return true
}
return false
}
@hide:file
arm_keeps_variant proc(state @State, owner, arm, name ast.NodeId) bool {
if (!ast.match_arm_view(state.tree, arm).catch_all) return arm_names_variant(state, arm, name)
for 0..ast.match_arm_count(state.tree, owner) |i| {
previous :: ast.match_arm(state.tree, owner, i)
if (previous == arm) break
if (ast.match_arm_view(state.tree, previous).catch_all or arm_names_variant(state, previous, name)) return false
}
return true
}
@hide:file
narrow_reference proc(state @mut State, id ast.NodeId) Type ! (mem.AllocError | diagnostics.CapacityError) {
arm :: matching_narrowing_arm(state, id)
if (arm == ast.NodeId(maxval!(u32))) return Type{}
cached :: state.cells[usize(arm)].narrowed_type
if (cached.kind != .none) return cached
owner :: state.cells[usize(arm)].match_owner
subject :: ast.match_expr(state.tree, owner).subject
source :: try infer_shape(state, subject, false)
key := source
if (source.kind == .record) {
descriptor :: state.records.items[usize(source.record_type)]
if (!descriptor.is_union) return source
key = descriptor.tag_type
}
if (key.kind != .enumeration) return source
descriptor :: state.enums.items[usize(key.enum_type)]
count := usize(0)
for descriptor.members |member| {
if (arm_keeps_variant(state, owner, arm, member.name)) count += 1
}
if (count == descriptor.members.len) {
state.cells[usize(arm)].narrowed_type = source
return source
}
members :: try mem.alloc(EnumMember, state.enums.allocator, count)
members_owned := true
defer if (members_owned) mem.free(state.enums.allocator, members)
next := usize(0)
for descriptor.members |member| {
if (!arm_keeps_variant(state, owner, arm, member.name)) continue
members[next] = member
next += 1
}
try arraylist.append(&state.enums, EnumType{
declaration = descriptor.declaration, backing = descriptor.backing,
explicit_backing = descriptor.explicit_backing, members = members, composed = true,
})
members_owned = false
tag :: Type{ kind = .enumeration, enum_type = EnumId(state.enums.items.len - 1) }
narrowed := tag
if (source.kind == .record) {
original :: state.records.items[usize(source.record_type)]
fields :: try mem.alloc(RecordField, state.records.allocator, count)
errdefer mem.free(state.records.allocator, fields)
next = 0
for original.fields |field| {
if (!arm_keeps_variant(state, owner, arm, field.name?)) continue
fields[next] = field
next += 1
}
try arraylist.append(&state.records, RecordType{
declaration = ast.NodeId(maxval!(u32)), fields = fields, is_union = true,
tag_type = tag, composed = true,
})
narrowed = Type{ kind = .record, record_type = RecordId(state.records.items.len - 1) }
}
state.cells[usize(arm)].narrowed_type = narrowed
return narrowed
}