cc39c96fc0
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.
5181 lines
235 KiB
Plaintext
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
|
|
}
|