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.
704 lines
28 KiB
Plaintext
704 lines
28 KiB
Plaintext
import "@std/debug"
|
|
import "@std/mem"
|
|
import "@std/hashmap"
|
|
import "@std/arraylist"
|
|
import "@source/strpool"
|
|
|
|
import "@source/ast"
|
|
import "@source/diagnostics"
|
|
|
|
BuiltinType :: enum {
|
|
int, float,
|
|
bool, string,
|
|
void, i64,
|
|
f64, usize,
|
|
i8, i16, i32, isize,
|
|
u8, u16, u32, u64, uint,
|
|
anyopaque
|
|
c_int, c_uint, c_long, c_ulong, c_longlong, c_ulonglong
|
|
c_char, c_uchar, c_short, c_ushort
|
|
type
|
|
unavailable
|
|
}
|
|
|
|
@hide:file
|
|
Builtin :: struct {
|
|
name []u8
|
|
type BuiltinType
|
|
}
|
|
|
|
@hide:file
|
|
builtins [_]Builtin :: [
|
|
{ name = "int", type = .int },
|
|
{ name = "float", type = .float },
|
|
{ name = "bool", type = .bool },
|
|
{ name = "string", type = .string },
|
|
{ name = "void", type = .void },
|
|
{ name = "i64", type = .i64 },
|
|
{ name = "f64", type = .f64 },
|
|
{ name = "usize", type = .usize },
|
|
{ name = "i8", type = .i8 },
|
|
{ name = "i16", type = .i16 },
|
|
{ name = "i32", type = .i32 },
|
|
{ name = "isize", type = .isize },
|
|
{ name = "u8", type = .u8 },
|
|
{ name = "u16", type = .u16 },
|
|
{ name = "u32", type = .u32 },
|
|
{ name = "u64", type = .u64 },
|
|
{ name = "uint", type = .uint },
|
|
{ name = "anyopaque", type = .anyopaque },
|
|
{ name = "c_int", type = .c_int },
|
|
{ name = "c_uint", type = .c_uint },
|
|
{ name = "c_long", type = .c_long },
|
|
{ name = "c_ulong", type = .c_ulong },
|
|
{ name = "c_longlong", type = .c_longlong },
|
|
{ name = "c_ulonglong", type = .c_ulonglong },
|
|
{ name = "c_char", type = .c_char },
|
|
{ name = "c_schar", type = .c_char },
|
|
{ name = "c_uchar", type = .c_uchar },
|
|
{ name = "c_short", type = .c_short },
|
|
{ name = "c_ushort", type = .c_ushort },
|
|
{ name = "c_double", type = .f64 },
|
|
{ name = "c_float", type = .unavailable },
|
|
{ name = "c_longdouble", type = .unavailable },
|
|
{ name = "type", type = .type },
|
|
]
|
|
|
|
@hide:file
|
|
intern_builtins proc(
|
|
$N usize,
|
|
pool @mut strpool.StringPool,
|
|
entries @[N]Builtin,
|
|
) [N]mut strpool.StringId ! (mem.AllocError | strpool.InternError) {
|
|
ids [N]mut strpool.StringId := undefined
|
|
for (entries) |builtin, index| ids[index] = try strpool.intern(pool, builtin.name)
|
|
return ids
|
|
}
|
|
|
|
BindingKind :: enum { none, declaration, namespace, builtin, invalid }
|
|
|
|
BindingData :: union {
|
|
declaration ast.NodeId
|
|
namespace ast.NodeId
|
|
builtin BuiltinType
|
|
diagnostic diagnostics.DiagnosticId
|
|
}
|
|
|
|
Binding :: struct {
|
|
kind BindingKind = .none
|
|
data BindingData = BindingData{ diagnostic = diagnostics.no_diagnostic }
|
|
}
|
|
|
|
#! links a file-local import to a package root; failed loads retain their diagnostic.
|
|
Import :: struct {
|
|
node ast.NodeId
|
|
name strpool.StringId
|
|
target ?ast.NodeId
|
|
diagnostic ?diagnostics.DiagnosticId = null
|
|
}
|
|
|
|
@hide:file
|
|
Storage :: struct {
|
|
bindings []mut Binding
|
|
allocator mem.Allocator
|
|
}
|
|
|
|
#! owns binding storage and retains its allocator; release with deinit.
|
|
#! node and diagnostic IDs refer to the caller's Tree and diagnostics.Store.
|
|
Resolution :: struct { storage Storage }
|
|
|
|
@hide:file
|
|
Scope :: struct {
|
|
names hashmap.AutoHashMap(strpool.StringId, Binding)
|
|
parent ?@Scope
|
|
}
|
|
|
|
@hide:file
|
|
State :: struct {
|
|
tree @ast.Tree
|
|
diagnostics @mut diagnostics.Store
|
|
bindings []mut Binding
|
|
builtin_ids []strpool.StringId
|
|
allocator mem.Allocator
|
|
namespaces hashmap.AutoHashMap(ast.NodeId, Scope)
|
|
aliases []mut ast.NodeId
|
|
selections arraylist.ArrayList(ast.NodeId)
|
|
}
|
|
|
|
#! borrows `tree`, its identifier `pool`, and `diagnostic_store` during resolution.
|
|
#! `allocator` supplies caller-owned bindings and temporary scopes released before return.
|
|
#! release the returned Resolution with deinit, which uses the retained allocator.
|
|
resolve proc(
|
|
allocator mem.Allocator,
|
|
tree @ast.Tree,
|
|
pool @mut strpool.StringPool,
|
|
diagnostic_store @mut diagnostics.Store,
|
|
) Resolution ! (
|
|
mem.AllocError
|
|
| strpool.InternError
|
|
| diagnostics.CapacityError
|
|
) {
|
|
roots [1]ast.NodeId :: [tree.root]
|
|
return try resolve_packages(
|
|
allocator,
|
|
tree,
|
|
roots[..],
|
|
&[],
|
|
pool,
|
|
diagnostic_store,
|
|
)
|
|
}
|
|
|
|
#! resolves each package root in its own namespace; AST IDs share the compilation arena.
|
|
resolve_packages proc(
|
|
allocator mem.Allocator,
|
|
tree @ast.Tree,
|
|
roots []ast.NodeId,
|
|
imports []Import,
|
|
pool @mut strpool.StringPool,
|
|
diagnostic_store @mut diagnostics.Store,
|
|
) Resolution ! (mem.AllocError | strpool.InternError | diagnostics.CapacityError) {
|
|
bindings :: try mem.alloc(Binding, allocator, ast.node_count(tree))
|
|
errdefer mem.free(allocator, bindings)
|
|
mem.set(bindings, Binding{})
|
|
builtin_ids :: try intern_builtins(pool, &builtins)
|
|
aliases :: try mem.alloc(ast.NodeId, allocator, ast.node_count(tree))
|
|
defer mem.free(allocator, aliases)
|
|
mem.set(aliases, maxval!(ast.NodeId))
|
|
|
|
state := State{
|
|
tree = tree,
|
|
diagnostics = diagnostic_store,
|
|
bindings = bindings,
|
|
builtin_ids = builtin_ids[..],
|
|
allocator = allocator,
|
|
namespaces = hashmap.init(allocator),
|
|
aliases = aliases,
|
|
selections = arraylist.init(allocator),
|
|
}
|
|
defer arraylist.deinit(&state.selections)
|
|
defer {
|
|
for (state.namespaces.entries) |entry| {
|
|
if (entry.hash == 0) continue
|
|
scope := entry.value
|
|
hashmap.deinit(&scope.names)
|
|
}
|
|
hashmap.deinit(&state.namespaces)
|
|
}
|
|
|
|
package_scopes :: try mem.alloc(Scope, allocator, roots.len)
|
|
defer mem.free(allocator, package_scopes)
|
|
file_scopes :: try mem.alloc(Scope, allocator, ast.source_files(tree).len)
|
|
defer mem.free(allocator, file_scopes)
|
|
for (file_scopes) |@scope| scope^ = Scope{ names = hashmap.init(allocator), parent = null }
|
|
defer { for (file_scopes) |@scope| hashmap.deinit(&scope.names) }
|
|
for (roots) |root, index| {
|
|
package_scopes[index] = Scope{ names = hashmap.init(allocator), parent = null }
|
|
scope := &package_scopes[index]
|
|
errdefer hashmap.deinit(&scope.names)
|
|
for (0..ast.package_decl_count_at(tree, root)) |i| {
|
|
id :: ast.package_decl_at(tree, root, i)
|
|
file_scopes[usize(ast.node_span(tree, id).source)].parent = scope
|
|
if (ast.file_local(tree, id)) continue
|
|
match ast.decl_kind(tree, id) {
|
|
.statement: try predeclare(&state, scope, id, ast.statement_decl(tree, id).name)
|
|
.function: try predeclare(&state, scope, id, ast.function_decl(tree, id).name)
|
|
.import, .invalid: {}
|
|
}
|
|
}
|
|
hashmap.put(&state.namespaces, root, scope^) catch |err| {
|
|
match err {
|
|
.key_exists: unreachable
|
|
else: return err
|
|
}
|
|
}
|
|
}
|
|
for (roots) |root| {
|
|
for (0..ast.package_decl_count_at(tree, root)) |i| {
|
|
id :: ast.package_decl_at(tree, root, i)
|
|
if (!ast.file_local(tree, id)) continue
|
|
scope :: &file_scopes[usize(ast.node_span(tree, id).source)]
|
|
match ast.decl_kind(tree, id) {
|
|
.statement: try predeclare(&state, scope, id, ast.statement_decl(tree, id).name)
|
|
.function: try predeclare(&state, scope, id, ast.function_decl(tree, id).name)
|
|
.import, .invalid: {}
|
|
}
|
|
}
|
|
}
|
|
for (imports) |imported| try resolve_import(&state, &file_scopes[usize(ast.node_span(tree, imported.node).source)], imported)
|
|
for (roots) |root| try resolve_package(&state, root, file_scopes)
|
|
try canonicalize_aliases(&state)
|
|
|
|
return Resolution{ storage = Storage{
|
|
bindings = bindings,
|
|
allocator = allocator,
|
|
} }
|
|
}
|
|
|
|
@hide:file
|
|
resolve_package proc(
|
|
state @mut State, root ast.NodeId, file_scopes []Scope,
|
|
) void ! (mem.AllocError | diagnostics.CapacityError) {
|
|
tree := state.tree^
|
|
tree.root = root
|
|
for (0..ast.package_decl_count(&tree)) |i| {
|
|
id :: ast.package_decl(&tree, i)
|
|
scope :: &file_scopes[usize(ast.node_span(&tree, id).source)]
|
|
match ast.decl_kind(&tree, id) {
|
|
.statement: try resolve_statement_decl(state, scope, id)
|
|
.function: try resolve_function(state, scope, id)
|
|
.invalid: resolve_invalid(state, id)
|
|
.import: {}
|
|
}
|
|
}
|
|
}
|
|
|
|
@hide:file
|
|
resolve_import proc(
|
|
state @mut State, scope @mut Scope, imported Import,
|
|
) void ! (mem.AllocError | diagnostics.CapacityError) {
|
|
span :: ast.import_decl(state.tree, imported.node).span
|
|
value := if (imported.diagnostic) |diagnostic|
|
|
Binding{ kind = .invalid, data = BindingData{ diagnostic = diagnostic } }
|
|
else if (imported.target) |target|
|
|
Binding{ kind = .namespace, data = BindingData{ namespace = target } }
|
|
else
|
|
Binding{ kind = .invalid, data = BindingData{
|
|
diagnostic = try diagnostics.add(state.diagnostics, .package_unavailable, span),
|
|
} }
|
|
if (imported.name == strpool.no_str) {
|
|
state.bindings[usize(imported.node)] = value
|
|
return
|
|
}
|
|
if hashmap.get(&scope.names, imported.name) |_| {
|
|
state.bindings[usize(imported.node)] = Binding{ kind = .invalid, data = BindingData{
|
|
diagnostic = try diagnostics.add(state.diagnostics, .duplicate_name, span),
|
|
} }
|
|
return
|
|
}
|
|
if lookup(state, scope.parent, imported.name) |_| {
|
|
value = Binding{ kind = .invalid, data = BindingData{
|
|
diagnostic = try diagnostics.add(state.diagnostics, .duplicate_name, span),
|
|
} }
|
|
}
|
|
try put_name(scope, imported.name, value)
|
|
state.bindings[usize(imported.node)] = value
|
|
}
|
|
|
|
#! releases binding storage with the allocator retained by resolve.
|
|
deinit proc(resolution @mut Resolution) void {
|
|
mem.free(resolution.storage.allocator, resolution.storage.bindings)
|
|
}
|
|
|
|
binding proc(resolution @Resolution, id ast.NodeId) Binding {
|
|
debug.assert(usize(id) < resolution.storage.bindings.len)
|
|
return resolution.storage.bindings[usize(id)]
|
|
}
|
|
|
|
#! replaces owned binding storage after AST elaboration; takes ownership of `bindings`.
|
|
replace_bindings proc(resolution @mut Resolution, bindings []mut Binding) void {
|
|
mem.free(resolution.storage.allocator, resolution.storage.bindings)
|
|
resolution.storage.bindings = bindings
|
|
}
|
|
|
|
@hide:file
|
|
lookup proc(state @State, scope ?@Scope, name strpool.StringId) ?Binding {
|
|
if scope |current| {
|
|
if hashmap.get(¤t.names, name) |found| return found
|
|
return lookup(state, current.parent, name)
|
|
}
|
|
for (state.builtin_ids) |id, index| {
|
|
if (id == name) return Binding{
|
|
kind = .builtin,
|
|
data = BindingData{ builtin = builtins[index].type },
|
|
}
|
|
}
|
|
return null
|
|
}
|
|
|
|
@hide:file
|
|
put_name proc(scope @mut Scope, name strpool.StringId, value Binding) void ! mem.AllocError {
|
|
hashmap.put(&scope.names, name, value) catch |err| {
|
|
match err {
|
|
.key_exists: unreachable
|
|
else: return err
|
|
}
|
|
}
|
|
}
|
|
|
|
@hide:file
|
|
predeclare proc(
|
|
state @mut State,
|
|
scope @mut Scope,
|
|
id ast.NodeId,
|
|
name_span ast.Span,
|
|
) void ! (mem.AllocError | diagnostics.CapacityError) {
|
|
name :: ast.name_id(state.tree, id)
|
|
if (mem.eql(ast.source_text(state.tree, name_span), "_")) {
|
|
state.bindings[usize(id)] = Binding{ kind = .declaration, data = BindingData{ declaration = id } }
|
|
return
|
|
}
|
|
if hashmap.get(&scope.names, name) |_| {
|
|
diagnostic :: try diagnostics.add(state.diagnostics, .duplicate_name, name_span)
|
|
state.bindings[usize(id)] = Binding{
|
|
kind = .invalid,
|
|
data = BindingData{ diagnostic = diagnostic },
|
|
}
|
|
return
|
|
}
|
|
|
|
value := Binding{ kind = .declaration, data = BindingData{ declaration = id } }
|
|
if lookup(state, scope.parent, name) |_| {
|
|
diagnostic :: try diagnostics.add(state.diagnostics, .duplicate_name, name_span)
|
|
# poison the inner name rather than silently binding uses to its ancestor.
|
|
value = Binding{ kind = .invalid, data = BindingData{ diagnostic = diagnostic } }
|
|
}
|
|
try put_name(scope, name, value)
|
|
state.bindings[usize(id)] = value
|
|
}
|
|
|
|
@hide:file
|
|
resolve_function proc(
|
|
state @mut State,
|
|
package_scope @Scope,
|
|
id ast.NodeId,
|
|
) void ! (mem.AllocError | diagnostics.CapacityError) {
|
|
tree :: state.tree
|
|
function :: ast.function_decl(tree, id)
|
|
# compile-time parameters are in scope for subsequent dependent annotations.
|
|
signature := Scope{ names = hashmap.init(state.allocator), parent = package_scope }
|
|
defer hashmap.deinit(&signature.names)
|
|
for (0..ast.function_param_count(tree, id)) |i| {
|
|
parameter :: ast.function_param(tree, id, i)
|
|
try resolve_expr(state, &signature, ast.param(tree, parameter).type)
|
|
if (ast.param(tree, parameter).comptime) try predeclare(state, &signature, parameter, ast.param(tree, parameter).name)
|
|
}
|
|
try resolve_expr(state, &signature, function.return_type)
|
|
scope := Scope{
|
|
names = hashmap.init(state.allocator),
|
|
parent = package_scope,
|
|
}
|
|
defer hashmap.deinit(&scope.names)
|
|
for (0..ast.function_param_count(tree, id)) |i| {
|
|
parameter :: ast.function_param(tree, id, i)
|
|
try predeclare(state, &scope, parameter, ast.param(tree, parameter).name)
|
|
}
|
|
try resolve_block(state, &scope, function.body)
|
|
}
|
|
|
|
@hide:file
|
|
resolve_arm proc(state @mut State, parent @Scope, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
|
|
scope := Scope{
|
|
names = hashmap.init(state.allocator),
|
|
parent = parent,
|
|
}
|
|
defer hashmap.deinit(&scope.names)
|
|
try resolve_block(state, &scope, id)
|
|
}
|
|
|
|
@hide:file
|
|
resolve_block proc(state @mut State, scope @mut Scope, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
|
|
tree :: state.tree
|
|
for (0..ast.block_statement_count(tree, id)) |i| {
|
|
statement :: ast.block_statement(tree, id, i)
|
|
if (ast.stmt_kind(tree, statement) == .declaration) try predeclare(state, scope, statement, ast.statement_decl(tree, statement).name)
|
|
}
|
|
for (0..ast.block_statement_count(tree, id)) |i| {
|
|
statement :: ast.block_statement(tree, id, i)
|
|
match ast.stmt_kind(tree, statement) {
|
|
.declaration: try resolve_statement_decl(state, scope, statement)
|
|
.expression: try resolve_expr(state, scope, ast.statement_expr(tree, statement))
|
|
.return_stmt: {
|
|
if (ast.return_stmt(tree, statement).value) |value| try resolve_expr(state, scope, value)
|
|
}
|
|
.yield_stmt: try resolve_expr(state, scope, ast.yield_stmt(tree, statement).value?)
|
|
.break_stmt, .continue_stmt, .unreachable_stmt: {}
|
|
.block_stmt: try resolve_arm(state, scope, ast.statement_block(tree, statement))
|
|
.defer_stmt, .errdefer_stmt: {
|
|
cleanup :: ast.cleanup_stmt(tree, statement)
|
|
try resolve_capture_arm(state, scope, cleanup.body, cleanup.capture)
|
|
}
|
|
.if_stmt: {
|
|
conditional :: ast.if_stmt(tree, statement)
|
|
try resolve_expr(state, scope, conditional.condition)
|
|
try resolve_capture_arm(state, scope, conditional.then_block, conditional.capture)
|
|
if (conditional.else_block) |other| try resolve_arm(state, scope, other)
|
|
}
|
|
.assignment_stmt: {
|
|
assignment :: ast.assignment_stmt(tree, statement)
|
|
try resolve_expr(state, scope, assignment.target)
|
|
try resolve_expr(state, scope, assignment.value)
|
|
}
|
|
.while_stmt: {
|
|
loop :: ast.while_stmt(tree, statement)
|
|
try resolve_expr(state, scope, loop.condition)
|
|
try resolve_capture_arm(state, scope, loop.body, loop.capture)
|
|
}
|
|
.for_stmt: {
|
|
loop :: ast.for_stmt(tree, statement)
|
|
try resolve_expr(state, scope, loop.iterable)
|
|
body_scope := Scope{
|
|
names = hashmap.init(state.allocator),
|
|
parent = scope,
|
|
}
|
|
defer hashmap.deinit(&body_scope.names)
|
|
if (loop.capture) |capture| try predeclare(state, &body_scope, capture, ast.node_span(tree, capture))
|
|
if (loop.index) |index| try predeclare(state, &body_scope, index, ast.node_span(tree, index))
|
|
try resolve_block(state, &body_scope, loop.body)
|
|
}
|
|
.invalid: resolve_invalid(state, statement)
|
|
}
|
|
}
|
|
}
|
|
|
|
@hide:file
|
|
resolve_capture_arm proc(state @mut State, scope @Scope, block ast.NodeId, capture ?ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
|
|
child := Scope{ names = hashmap.init(state.allocator), parent = scope }
|
|
defer hashmap.deinit(&child.names)
|
|
if (capture) |id| try predeclare(state, &child, id, ast.node_span(state.tree, id))
|
|
try resolve_block(state, &child, block)
|
|
}
|
|
|
|
@hide:file
|
|
resolve_statement_decl proc(
|
|
state @mut State,
|
|
scope @Scope,
|
|
id ast.NodeId,
|
|
) void ! (mem.AllocError | diagnostics.CapacityError) {
|
|
declaration :: ast.statement_decl(state.tree, id)
|
|
if (ast.expr_kind(state.tree, declaration.initializer) == .alias_expr) state.aliases[usize(id)] = ast.type_operand(state.tree, declaration.initializer)
|
|
if (declaration.type) |type| try resolve_expr(state, scope, type)
|
|
try resolve_expr(state, scope, declaration.initializer)
|
|
}
|
|
|
|
@hide:file
|
|
canonical_binding proc(state @State, original Binding) Binding {
|
|
value := original
|
|
for (0..(state.aliases.len)) |_| {
|
|
if (value.kind != .declaration) break
|
|
operand :: state.aliases[usize(value.data.declaration)]
|
|
if (operand == maxval!(ast.NodeId)) break
|
|
target :: state.bindings[usize(operand)]
|
|
if (target.kind == .none) break
|
|
if (target.kind == .declaration and target.data.declaration == value.data.declaration) break
|
|
value = target
|
|
}
|
|
return value
|
|
}
|
|
|
|
@hide:file
|
|
canonicalize_aliases proc(state @mut State) void ! (mem.AllocError | diagnostics.CapacityError) {
|
|
# aliases may forward-reference other aliases or imported namespace members.
|
|
for (0..(state.aliases.len)) |_| {
|
|
changed := false
|
|
for (state.bindings) |@entry| {
|
|
value :: canonical_binding(state, entry^)
|
|
if (value.kind != entry.kind) changed = true
|
|
else if (value.kind == .declaration and value.data.declaration != entry.data.declaration) changed = true
|
|
entry^ = value
|
|
}
|
|
for (state.selections.items) |id| {
|
|
expression :: ast.selection_expr(state.tree, id)
|
|
base :: state.bindings[usize(expression.base)]
|
|
if (base.kind != .namespace or state.bindings[usize(id)].kind != .none) continue
|
|
namespace :: hashmap.get(&state.namespaces, base.data.namespace)?
|
|
found :: if (ast.expr_kind(state.tree, expression.member) == .identifier)
|
|
hashmap.get(&namespace.names, ast.name_id(state.tree, expression.member))
|
|
else
|
|
null
|
|
value :: if (found) |member| canonical_binding(state, member) else Binding{
|
|
kind = .invalid,
|
|
data = BindingData{ diagnostic = try diagnostics.add(
|
|
state.diagnostics, .unknown_name, ast.node_span(state.tree, expression.member),
|
|
) },
|
|
}
|
|
state.bindings[usize(id)] = value
|
|
state.bindings[usize(expression.member)] = value
|
|
changed = true
|
|
}
|
|
if (!changed) break
|
|
}
|
|
}
|
|
|
|
@hide:file
|
|
resolve_invalid proc(state @mut State, id ast.NodeId) void {
|
|
if (state.bindings[usize(id)].kind != .none) return
|
|
state.bindings[usize(id)] = Binding{
|
|
kind = .invalid,
|
|
data = BindingData{ diagnostic = ast.invalid(state.tree, id).diagnostic },
|
|
}
|
|
}
|
|
|
|
@hide:file
|
|
resolve_expr proc(
|
|
state @mut State,
|
|
scope @Scope,
|
|
id ast.NodeId,
|
|
) void ! (mem.AllocError | diagnostics.CapacityError) {
|
|
# grouped parameters share their type node, including any diagnostic it owns.
|
|
if (state.bindings[usize(id)].kind != .none) return
|
|
tree :: state.tree
|
|
match ast.expr_kind(tree, id) {
|
|
.comptime_expr: try resolve_expr(state, scope, ast.comptime_operand(tree, id))
|
|
.block_expr: try resolve_arm(state, scope, ast.block_expr(tree, id))
|
|
.typed_constant: {
|
|
value :: ast.typed_constant(tree, id)
|
|
try resolve_expr(state, scope, value.type)
|
|
try resolve_expr(state, scope, value.value)
|
|
}
|
|
.union_constant: {
|
|
value :: ast.union_constant(tree, id)
|
|
try resolve_expr(state, scope, value.type)
|
|
try resolve_expr(state, scope, value.value)
|
|
}
|
|
.literal_int, .literal_float, .literal_bool, .literal_string, .literal_null, .literal_void,
|
|
.literal_undefined: {}
|
|
.enum_literal: {}
|
|
.distinct_type, .alias_expr, .union_tag_type, .optional_type, .try_expr: try resolve_expr(state, scope, ast.type_operand(tree, id))
|
|
.enum_type: {
|
|
if (ast.enum_type(tree, id).backing) |backing| try resolve_expr(state, scope, backing)
|
|
for (0..ast.enum_member_count(tree, id)) |i| {
|
|
member :: ast.field(tree, ast.enum_member(tree, id, i))
|
|
if (member.value) |value| try resolve_expr(state, scope, value)
|
|
}
|
|
}
|
|
.match_expr: {
|
|
try resolve_expr(state, scope, ast.match_expr(tree, id).subject)
|
|
for (0..ast.match_arm_count(tree, id)) |i| {
|
|
arm_id :: ast.match_arm(tree, id, i)
|
|
arm :: ast.match_arm_view(tree, arm_id)
|
|
for (0..ast.match_pattern_count(tree, arm_id)) |j| {
|
|
try resolve_expr(state, scope, ast.match_pattern(tree, arm_id, j))
|
|
}
|
|
arm_scope := Scope{ names = hashmap.init(state.allocator), parent = scope }
|
|
defer hashmap.deinit(&arm_scope.names)
|
|
if (arm.capture) |capture| try predeclare(state, &arm_scope, capture, ast.node_span(tree, capture))
|
|
if (arm.tag_capture) |capture| try predeclare(state, &arm_scope, capture, ast.node_span(tree, capture))
|
|
if (arm.expression) try resolve_expr(state, &arm_scope, arm.body)
|
|
else try resolve_block(state, &arm_scope, arm.body)
|
|
}
|
|
}
|
|
.catch_expr: {
|
|
expression :: ast.catch_expr(tree, id)
|
|
try resolve_expr(state, scope, expression.operand)
|
|
handler_scope := Scope{ names = hashmap.init(state.allocator), parent = scope }
|
|
defer hashmap.deinit(&handler_scope.names)
|
|
if (expression.capture) |capture| try predeclare(state, &handler_scope, capture, ast.node_span(tree, capture))
|
|
if (expression.handler_is_block) try resolve_block(state, &handler_scope, expression.handler)
|
|
else try resolve_expr(state, &handler_scope, expression.handler)
|
|
}
|
|
.identifier: {
|
|
span :: ast.node_span(tree, id)
|
|
name :: ast.name_id(tree, id)
|
|
if lookup(state, scope, name) |found| {
|
|
state.bindings[usize(id)] = found
|
|
} else {
|
|
diagnostic :: try diagnostics.add(state.diagnostics, .unknown_name, span)
|
|
state.bindings[usize(id)] = Binding{
|
|
kind = .invalid,
|
|
data = BindingData{ diagnostic = diagnostic },
|
|
}
|
|
}
|
|
}
|
|
.selection: {
|
|
expression :: ast.selection_expr(tree, id)
|
|
try arraylist.append(&state.selections, id)
|
|
try resolve_expr(state, scope, expression.base)
|
|
base :: state.bindings[usize(expression.base)]
|
|
value := Binding{}
|
|
if (base.kind == .invalid) {
|
|
value = base
|
|
} else if (base.kind == .namespace) {
|
|
namespace :: hashmap.get(&state.namespaces, base.data.namespace)?
|
|
found :: if (ast.expr_kind(tree, expression.member) == .identifier)
|
|
hashmap.get(&namespace.names, ast.name_id(tree, expression.member))
|
|
else
|
|
null
|
|
if (found) |member| {
|
|
value = member
|
|
} else {
|
|
value = Binding{ kind = .invalid, data = BindingData{
|
|
diagnostic = try diagnostics.add(
|
|
state.diagnostics,
|
|
.unknown_name,
|
|
ast.node_span(tree, expression.member),
|
|
),
|
|
} }
|
|
}
|
|
}
|
|
state.bindings[usize(id)] = value
|
|
state.bindings[usize(expression.member)] = value
|
|
}
|
|
.unary: try resolve_expr(state, scope, ast.unary_expr(tree, id).operand)
|
|
.binary, .fallible_type, .sum_type: {
|
|
expression :: ast.binary_expr(tree, id)
|
|
try resolve_expr(state, scope, expression.left)
|
|
try resolve_expr(state, scope, expression.right)
|
|
}
|
|
.range: {
|
|
expression :: ast.range_expr(tree, id)
|
|
try resolve_expr(state, scope, expression.start)
|
|
try resolve_expr(state, scope, expression.end)
|
|
}
|
|
.call: {
|
|
try resolve_expr(state, scope, ast.call_expr(tree, id).callee)
|
|
for (0..ast.call_arg_count(tree, id)) |i| {
|
|
try resolve_expr(state, scope, ast.call_arg(tree, id, i))
|
|
}
|
|
}
|
|
.builtin_call: {
|
|
for (0..ast.call_arg_count(tree, id)) |i| try resolve_expr(state, scope, ast.call_arg(tree, id, i))
|
|
}
|
|
.slice: {
|
|
expression :: ast.slice_expr(tree, id)
|
|
try resolve_expr(state, scope, expression.base)
|
|
if (expression.start) |node| try resolve_expr(state, scope, node)
|
|
if (expression.end) |node| try resolve_expr(state, scope, node)
|
|
if (expression.sentinel) |node| try resolve_expr(state, scope, node)
|
|
}
|
|
.slice_type: {
|
|
type :: ast.slice_type(tree, id)
|
|
try resolve_expr(state, scope, type.element)
|
|
if (type.sentinel) |node| try resolve_expr(state, scope, node)
|
|
}
|
|
.pointer_type: try resolve_expr(state, scope, ast.pointer_type(tree, id).element)
|
|
.function_type: {
|
|
try resolve_expr(state, scope, ast.callable_result(tree, id))
|
|
for 0..ast.function_param_count(tree, id) |i| {
|
|
parameter :: ast.function_param(tree, id, i)
|
|
try resolve_expr(state, scope, ast.param(tree, parameter).type)
|
|
}
|
|
}
|
|
.array_type: {
|
|
type :: ast.array_type(tree, id)
|
|
if (type.count) |count| try resolve_expr(state, scope, count)
|
|
try resolve_expr(state, scope, type.element)
|
|
}
|
|
.array_literal: {
|
|
for (0..ast.array_element_count(tree, id)) |i| {
|
|
try resolve_expr(state, scope, ast.array_element(tree, id, i))
|
|
}
|
|
}
|
|
.record_type, .record_literal, .union_type: {
|
|
if (ast.expr_kind(tree, id) == .record_literal) {
|
|
if (ast.record_literal(tree, id).type) |type| try resolve_expr(state, scope, type)
|
|
}
|
|
if (ast.expr_kind(tree, id) == .union_type) {
|
|
if (ast.union_type(tree, id).tag) |tag| try resolve_expr(state, scope, tag)
|
|
}
|
|
for (0..ast.record_field_count(tree, id)) |i| {
|
|
field :: ast.field(tree, ast.record_field(tree, id, i))
|
|
if (field.type) |type| try resolve_expr(state, scope, type)
|
|
if (field.value) |value| try resolve_expr(state, scope, value)
|
|
}
|
|
}
|
|
.index: {
|
|
expression :: ast.index_expr(tree, id)
|
|
try resolve_expr(state, scope, expression.base)
|
|
try resolve_expr(state, scope, expression.index)
|
|
}
|
|
.invalid: resolve_invalid(state, id)
|
|
}
|
|
}
|
|
|