1984 lines
61 KiB
Odin
1984 lines
61 KiB
Odin
package loader
|
|
|
|
import "../ast"
|
|
import "../cimport"
|
|
import "../lexer"
|
|
import "../parser"
|
|
import "../source"
|
|
import "../symbol"
|
|
import "../target"
|
|
import "../types"
|
|
import "core:fmt"
|
|
import "core:math"
|
|
import "core:mem"
|
|
import "core:os"
|
|
import "core:path/filepath"
|
|
import "core:slice"
|
|
import "core:strings"
|
|
|
|
State :: struct {
|
|
module: ^ast.Module,
|
|
sources: ^source.Store,
|
|
diagnostics: ^source.Diagnostics,
|
|
symbols: ^symbol.Table,
|
|
token_allocator: mem.Allocator,
|
|
allocator: mem.Allocator,
|
|
c_options: cimport.Options,
|
|
selected: target.Target,
|
|
project_root: string,
|
|
mode: ast.Compile_Mode,
|
|
record_identities: [dynamic]string,
|
|
record_types: [dynamic]types.Type,
|
|
root_failed: bool,
|
|
}
|
|
|
|
is_identifier :: proc(value: string) -> bool {
|
|
if len(value) == 0 {
|
|
return false
|
|
}
|
|
is_start := proc(value: byte) -> bool {
|
|
return value == '_' || value >= 'a' && value <= 'z' || value >= 'A' && value <= 'Z'
|
|
}
|
|
if !is_start(value[0]) {
|
|
return false
|
|
}
|
|
for byte_value in transmute([]byte)value[1:] {
|
|
if !is_start(byte_value) && !(byte_value >= '0' && byte_value <= '9') {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
find_package :: proc(state: ^State, path: string) -> ast.Package_Id {
|
|
for pkg, id in state.module.packages {
|
|
if pkg.path == path {
|
|
return ast.package_id(id)
|
|
}
|
|
}
|
|
return ast.INVALID_PACKAGE
|
|
}
|
|
|
|
add_placeholder :: proc(state: ^State, path: string) -> ast.Package_Id {
|
|
if existing := find_package(state, path); existing != ast.INVALID_PACKAGE {
|
|
return existing
|
|
}
|
|
id := ast.package_id(len(state.module.packages))
|
|
append(&state.module.packages, ast.Package{
|
|
path=strings.clone(path, state.allocator),
|
|
name=symbol.intern(state.symbols, filepath.base(path)),
|
|
available=false,
|
|
})
|
|
return id
|
|
}
|
|
|
|
read_package_files :: proc(state: ^State, path: string) -> ([]os.File_Info, bool) {
|
|
handle, open_error := os.open(path, os.O_RDONLY)
|
|
if open_error != nil {
|
|
return nil, false
|
|
}
|
|
defer os.close(handle)
|
|
entries, read_error := os.read_dir(handle, -1, state.allocator)
|
|
if read_error != nil {
|
|
return nil, false
|
|
}
|
|
slice.sort_by(entries, proc(a, b: os.File_Info) -> bool {
|
|
return a.name < b.name
|
|
})
|
|
files: [dynamic]os.File_Info
|
|
files.allocator = state.allocator
|
|
for entry in entries {
|
|
extension := filepath.ext(entry.name)
|
|
if !entry.is_dir && (extension == ".bro" || extension == ".hon") {
|
|
append(&files, entry)
|
|
} else {
|
|
os.file_info_delete(entry, state.allocator)
|
|
}
|
|
}
|
|
delete(entries, state.allocator)
|
|
return files[:], true
|
|
}
|
|
|
|
resolve_import_path :: proc(state: ^State, importing_path, import_path: string) -> (string, bool) {
|
|
if filepath.is_abs(import_path) {
|
|
return "", false
|
|
}
|
|
base := importing_path
|
|
path := import_path
|
|
if strings.has_prefix(import_path, "@") {
|
|
base = state.project_root
|
|
path = import_path[1:]
|
|
}
|
|
joined, join_error := filepath.join({base, path}, state.allocator)
|
|
if join_error != nil {
|
|
return "", false
|
|
}
|
|
canonical, ok := filepath.abs(joined, state.allocator)
|
|
if ok {
|
|
delete(joined, state.allocator)
|
|
return canonical, true
|
|
}
|
|
return joined, false
|
|
}
|
|
|
|
header_package_name :: proc(path: string, symbols: ^symbol.Table) -> symbol.Id {
|
|
base := filepath.base(path)
|
|
extension := filepath.ext(base)
|
|
if len(extension) > 0 {
|
|
base = base[:len(base)-len(extension)]
|
|
}
|
|
return symbol.intern(symbols, base)
|
|
}
|
|
|
|
find_record_identity :: proc(state: ^State, identity: string) -> types.Type {
|
|
for existing, index in state.record_identities {
|
|
if existing == identity {
|
|
return state.record_types[index]
|
|
}
|
|
}
|
|
return types.INVALID
|
|
}
|
|
|
|
translate_c_type :: proc(
|
|
state: ^State,
|
|
result: ^cimport.Result,
|
|
value: cimport.Type_Id,
|
|
pkg: ast.Package_Id,
|
|
record_mapping: []types.Type,
|
|
type_mapping: []types.Type,
|
|
) -> types.Type {
|
|
if value == cimport.INVALID_TYPE || int(value) < 0 || int(value) >= len(result.types) {
|
|
return types.INVALID
|
|
}
|
|
if types.is_valid(type_mapping[value]) {
|
|
return type_mapping[value]
|
|
}
|
|
item := result.types[value]
|
|
translated := types.INVALID
|
|
switch item.kind {
|
|
case .Invalid: translated = types.INVALID
|
|
case .Void: translated = types.VOID
|
|
case .C_Bool: translated = types.BOOL
|
|
case .C_Char: translated = types.C_CHAR
|
|
case .C_Schar: translated = types.C_SCHAR
|
|
case .C_Uchar: translated = types.C_UCHAR
|
|
case .C_Short: translated = types.C_SHORT
|
|
case .C_Ushort: translated = types.C_USHORT
|
|
case .C_Int: translated = types.C_INT
|
|
case .C_Uint: translated = types.C_UINT
|
|
case .C_Long: translated = types.C_LONG
|
|
case .C_Ulong: translated = types.C_ULONG
|
|
case .C_Longlong: translated = types.C_LONGLONG
|
|
case .C_Ulonglong: translated = types.C_ULONGLONG
|
|
case .C_Float: translated = types.C_FLOAT
|
|
case .C_Double: translated = types.C_DOUBLE
|
|
case .C_Longdouble: translated = types.C_LONGDOUBLE
|
|
case .Pointer:
|
|
child := translate_c_type(state, result, item.child, pkg, record_mapping, type_mapping)
|
|
if child == types.VOID {
|
|
child = types.ANYOPAQUE
|
|
}
|
|
if types.is_valid(child) {
|
|
pointer := types.pointer(&state.module.type_store, child, item.mutable, true)
|
|
translated = types.optional(&state.module.type_store, pointer)
|
|
}
|
|
case .Array:
|
|
child := translate_c_type(state, result, item.child, pkg, record_mapping, type_mapping)
|
|
if types.is_valid(child) {
|
|
translated = types.array(&state.module.type_store, child, item.count, item.mutable)
|
|
}
|
|
case .Function:
|
|
params := make([]types.Type, len(item.params), state.allocator)
|
|
for param, index in item.params {
|
|
params[index] = translate_c_type(state, result, param, pkg, record_mapping, type_mapping)
|
|
if !types.is_valid(params[index]) {
|
|
delete(params, state.allocator)
|
|
type_mapping[value] = types.INVALID
|
|
return types.INVALID
|
|
}
|
|
}
|
|
result_type := translate_c_type(state, result, item.child, pkg, record_mapping, type_mapping)
|
|
if types.is_valid(result_type) {
|
|
translated = types.function(&state.module.type_store, params, result_type, true, item.variadic)
|
|
}
|
|
delete(params, state.allocator)
|
|
case .Record:
|
|
if int(item.record) < len(record_mapping) {
|
|
translated = record_mapping[item.record]
|
|
}
|
|
}
|
|
type_mapping[value] = translated
|
|
return translated
|
|
}
|
|
|
|
function_signatures_equal :: proc(left: ast.Function, params: []ast.Param, result: types.Type, variadic: bool) -> bool {
|
|
if left.result != result || types.is_valid(left.error) || left.variadic != variadic || len(left.params) != len(params) {
|
|
return false
|
|
}
|
|
for param, index in params {
|
|
if left.params[index].type != param.type {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
RECORD_DEPENDENCY_PENDING :: "C record contains an incomplete or unsupported record field"
|
|
|
|
record_layout_reason :: proc(
|
|
store: ^types.Store,
|
|
record: cimport.Record,
|
|
fields: []types.Field,
|
|
selected: target.Target,
|
|
) -> string {
|
|
if len(fields) == 0 {
|
|
if record.size > 0 {
|
|
return "anonymous C record fields are not supported"
|
|
}
|
|
return "empty C records are not supported"
|
|
}
|
|
if len(fields) != len(record.fields) || record.alignment == 0 {
|
|
return "C record metadata is incomplete"
|
|
}
|
|
size: u64
|
|
alignment: u64 = 1
|
|
if record.kind == .Union {
|
|
for field in fields {
|
|
if !types.is_runtime_value(field.type, store) {
|
|
return RECORD_DEPENDENCY_PENDING
|
|
}
|
|
if field.offset != 0 {
|
|
return "non-natural C record layouts are not supported"
|
|
}
|
|
size = max(size, types.size(field.type, store, selected))
|
|
alignment = max(alignment, u64(types.alignment_of(field.type, store, selected)))
|
|
}
|
|
} else {
|
|
offset: u64
|
|
for field in fields {
|
|
if !types.is_runtime_value(field.type, store) {
|
|
return RECORD_DEPENDENCY_PENDING
|
|
}
|
|
field_alignment := u64(types.alignment_of(field.type, store, selected))
|
|
offset = (offset+field_alignment-1)/field_alignment*field_alignment
|
|
if field.offset != offset {
|
|
if field.offset < offset {
|
|
return "packed C records are not supported"
|
|
}
|
|
return "non-natural C record layouts are not supported"
|
|
}
|
|
offset += types.size(field.type, store, selected)
|
|
alignment = max(alignment, field_alignment)
|
|
}
|
|
size = offset
|
|
}
|
|
size = (size+alignment-1)/alignment*alignment
|
|
if u64(record.alignment) > alignment {
|
|
return "over-aligned C records are not supported"
|
|
}
|
|
if u64(record.alignment) < alignment || record.size < size {
|
|
return "packed C records are not supported"
|
|
}
|
|
if record.size != size {
|
|
return "non-natural C record layouts are not supported"
|
|
}
|
|
return ""
|
|
}
|
|
|
|
c_record_by_value_reason :: proc(result: ^cimport.Result, value: cimport.Type_Id, depth := 0) -> string {
|
|
if depth > 64 || value == cimport.INVALID_TYPE || int(value) < 0 || int(value) >= len(result.types) {
|
|
return ""
|
|
}
|
|
item := result.types[value]
|
|
#partial switch item.kind {
|
|
case .Pointer:
|
|
return ""
|
|
case .Array:
|
|
return c_record_by_value_reason(result, item.child, depth+1)
|
|
case .Record:
|
|
if int(item.record) >= len(result.records) {
|
|
return "C record metadata is incomplete"
|
|
}
|
|
record := result.records[item.record]
|
|
if len(record.reason) > 0 {
|
|
return record.reason
|
|
}
|
|
if !record.complete {
|
|
return "incomplete C records are pointer-only"
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
add_import_unsupported :: proc(state: ^State, pkg: ast.Package_Id, name: string, reason: string) {
|
|
if len(name) == 0 || len(reason) == 0 {
|
|
return
|
|
}
|
|
append(&state.module.unsupported, ast.Unsupported{
|
|
pkg=pkg,
|
|
name=symbol.intern(state.symbols, name),
|
|
reason=strings.clone(reason, state.allocator),
|
|
})
|
|
}
|
|
|
|
find_trampoline :: proc(result: ^cimport.Result, symbol: string) -> (cimport.Trampoline, bool) {
|
|
for trampoline in result.trampolines {
|
|
if trampoline.symbol == symbol {
|
|
return trampoline, true
|
|
}
|
|
}
|
|
return {}, false
|
|
}
|
|
|
|
add_c_trampoline :: proc(state: ^State, trampoline: cimport.Trampoline) {
|
|
if len(trampoline.symbol) == 0 || len(trampoline.source) == 0 {
|
|
return
|
|
}
|
|
for existing in state.module.c_trampolines {
|
|
if existing.symbol == trampoline.symbol {
|
|
return
|
|
}
|
|
}
|
|
append(&state.module.c_trampolines, ast.Trampoline{
|
|
symbol=strings.clone(trampoline.symbol, state.allocator),
|
|
source=strings.clone(trampoline.source, state.allocator),
|
|
header=strings.clone(trampoline.header, state.allocator),
|
|
})
|
|
}
|
|
|
|
add_import_expr :: proc(state: ^State, expr: ast.Expr) -> ast.Expr_Id {
|
|
id := ast.expr_id(len(state.module.exprs))
|
|
append(&state.module.exprs, expr)
|
|
return id
|
|
}
|
|
|
|
add_macro_value_expr :: proc(state: ^State, value: cimport.Macro_Value, span: source.Span) -> ast.Expr_Id {
|
|
#partial switch value.kind {
|
|
case .Integer:
|
|
if value.negative {
|
|
magnitude := value.integer
|
|
operand := add_import_expr(state, ast.Expr{
|
|
kind=.Integer, span=span, integer=magnitude,
|
|
left=ast.INVALID_EXPR, right=ast.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
return add_import_expr(state, ast.Expr{
|
|
kind=.Negate, span=span, left=operand, right=ast.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
return add_import_expr(state, ast.Expr{
|
|
kind=.Integer, span=span, integer=value.integer,
|
|
left=ast.INVALID_EXPR, right=ast.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .Float:
|
|
return add_import_expr(state, ast.Expr{
|
|
kind=.Float, span=span, integer=value.integer,
|
|
left=ast.INVALID_EXPR, right=ast.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case:
|
|
}
|
|
return add_import_expr(state, ast.Expr{
|
|
kind=.Invalid, span=span, left=ast.INVALID_EXPR, right=ast.INVALID_EXPR,
|
|
diagnostic=source.add(state.diagnostics, span, "unsupported C macro value"),
|
|
})
|
|
}
|
|
|
|
MAX_MACRO_ZERO_DEPTH :: 64
|
|
MAX_MACRO_ZERO_NODES :: 65_536
|
|
|
|
find_macro_record_name :: proc(
|
|
state: ^State,
|
|
pkg: ast.Package_Id,
|
|
value: types.Type,
|
|
) -> (symbol.Id, bool) {
|
|
resolved := types.resolve_alias(value, &state.module.type_store)
|
|
for item, index in state.module.type_store.nodes {
|
|
if item.pkg != u32(pkg) || item.name == 0 {
|
|
continue
|
|
}
|
|
candidate := types.DYNAMIC_START+types.Type(index)
|
|
if types.resolve_alias(candidate, &state.module.type_store) == resolved {
|
|
return symbol.Id(item.name), true
|
|
}
|
|
}
|
|
return symbol.INVALID, false
|
|
}
|
|
|
|
add_macro_zero_expr :: proc(
|
|
state: ^State,
|
|
pkg: ast.Package_Id,
|
|
value_type: types.Type,
|
|
span: source.Span,
|
|
depth: int,
|
|
remaining: ^int,
|
|
) -> (ast.Expr_Id, bool) {
|
|
if depth > MAX_MACRO_ZERO_DEPTH || remaining^ <= 0 {
|
|
return ast.INVALID_EXPR, false
|
|
}
|
|
remaining^ -= 1
|
|
store := &state.module.type_store
|
|
resolved := types.resolve_alias(value_type, store)
|
|
#partial switch types.kind(resolved, store) {
|
|
case .Scalar:
|
|
kind := ast.Expr_Kind.Integer
|
|
if types.is_float(resolved, state.selected) {
|
|
kind = .Float
|
|
}
|
|
return add_import_expr(state, ast.Expr{
|
|
kind=kind, span=span, integer=0,
|
|
left=ast.INVALID_EXPR, right=ast.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
}), true
|
|
case .Optional:
|
|
if !types.is_optional_pointer(resolved, store) {
|
|
return ast.INVALID_EXPR, false
|
|
}
|
|
return add_import_expr(state, ast.Expr{
|
|
kind=.Null, span=span,
|
|
left=ast.INVALID_EXPR, right=ast.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
}), true
|
|
case .Array:
|
|
item, ok := types.node(store, resolved)
|
|
if !ok || item.count > u64(max(int)) || item.count > u64(remaining^) {
|
|
return ast.INVALID_EXPR, false
|
|
}
|
|
args := make([]ast.Expr_Id, int(item.count), state.allocator)
|
|
for index in 0..<len(args) {
|
|
value, value_ok := add_macro_zero_expr(
|
|
state, pkg, item.child, span, depth+1, remaining,
|
|
)
|
|
if !value_ok {
|
|
delete(args, state.allocator)
|
|
return ast.INVALID_EXPR, false
|
|
}
|
|
args[index] = value
|
|
}
|
|
return add_import_expr(state, ast.Expr{
|
|
kind=.Array, span=span, args=args,
|
|
left=ast.INVALID_EXPR, right=ast.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
}), true
|
|
case .Struct, .Union:
|
|
item, ok := types.node(store, resolved)
|
|
fields := types.fields_for(store, resolved)
|
|
if !ok || len(fields) == 0 {
|
|
return ast.INVALID_EXPR, false
|
|
}
|
|
name, found_name := find_macro_record_name(state, pkg, resolved)
|
|
if !found_name {
|
|
return ast.INVALID_EXPR, false
|
|
}
|
|
count := 1 if item.kind == .Union else len(fields)
|
|
args := make([]ast.Expr_Id, count, state.allocator)
|
|
for index in 0..<count {
|
|
field := fields[index]
|
|
value, value_ok := add_macro_zero_expr(
|
|
state, pkg, field.type, span, depth+1, remaining,
|
|
)
|
|
if !value_ok {
|
|
delete(args, state.allocator)
|
|
return ast.INVALID_EXPR, false
|
|
}
|
|
args[index] = add_import_expr(state, ast.Expr{
|
|
kind=.Keyed, span=span, name=symbol.Id(field.name), left=value,
|
|
right=ast.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
return add_import_expr(state, ast.Expr{
|
|
kind=.Struct_Literal, span=span, name=name, args=args,
|
|
left=ast.INVALID_EXPR, right=ast.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
}), true
|
|
case:
|
|
}
|
|
return ast.INVALID_EXPR, false
|
|
}
|
|
|
|
Converted_Macro_Value_Kind :: enum u8 {
|
|
Invalid,
|
|
Integer,
|
|
Float,
|
|
Null,
|
|
}
|
|
|
|
Converted_Macro_Value :: struct {
|
|
kind: Converted_Macro_Value_Kind,
|
|
integer: u64,
|
|
negative: bool,
|
|
}
|
|
|
|
macro_integer_value :: proc(value: cimport.Macro_Value) -> i128 {
|
|
magnitude := i128(value.integer)
|
|
return -magnitude if value.negative else magnitude
|
|
}
|
|
|
|
convert_macro_integer :: proc(
|
|
value: i128,
|
|
destination: types.Type,
|
|
selected: target.Target,
|
|
) -> (Converted_Macro_Value, bool) {
|
|
bits := types.bits(destination, selected)
|
|
if bits <= 0 || bits > 64 {
|
|
return {}, false
|
|
}
|
|
if types.is_unsigned(destination, selected) {
|
|
modulus := i128(1) << u32(bits)
|
|
wrapped := value % modulus
|
|
if wrapped < 0 {
|
|
wrapped += modulus
|
|
}
|
|
return Converted_Macro_Value{
|
|
kind=.Integer,
|
|
integer=u64(wrapped),
|
|
}, true
|
|
}
|
|
if !types.is_signed(destination, selected) {
|
|
return {}, false
|
|
}
|
|
limit := i128(1) << u32(bits-1)
|
|
if value < -limit || value >= limit {
|
|
return {}, false
|
|
}
|
|
if value < 0 {
|
|
return Converted_Macro_Value{
|
|
kind=.Integer,
|
|
integer=u64(-value),
|
|
negative=true,
|
|
}, true
|
|
}
|
|
return Converted_Macro_Value{kind=.Integer, integer=u64(value)}, true
|
|
}
|
|
|
|
macro_float_value :: proc(
|
|
value: cimport.Macro_Value,
|
|
source_type: types.Type,
|
|
selected: target.Target,
|
|
) -> (f64, bool) {
|
|
if value.kind != .Float || !types.is_float(source_type, selected) {
|
|
return 0, false
|
|
}
|
|
number := transmute(f64)value.integer
|
|
if types.bits(source_type, selected) == 32 {
|
|
number = f64(f32(number))
|
|
}
|
|
if math.is_nan(number) || math.is_inf(number) {
|
|
return 0, false
|
|
}
|
|
return number, true
|
|
}
|
|
|
|
convert_macro_float :: proc(
|
|
number: f64,
|
|
destination: types.Type,
|
|
selected: target.Target,
|
|
) -> (Converted_Macro_Value, bool) {
|
|
if !types.is_float(destination, selected) {
|
|
return {}, false
|
|
}
|
|
converted := number
|
|
if types.bits(destination, selected) == 32 {
|
|
rounded := f64(f32(number))
|
|
if math.is_inf(rounded) {
|
|
return {}, false
|
|
}
|
|
converted = rounded
|
|
}
|
|
return Converted_Macro_Value{
|
|
kind=.Float,
|
|
integer=transmute(u64)converted,
|
|
}, true
|
|
}
|
|
|
|
convert_macro_field_value :: proc(
|
|
state: ^State,
|
|
result: ^cimport.Result,
|
|
value: cimport.Macro_Value,
|
|
field_type: types.Type,
|
|
pkg: ast.Package_Id,
|
|
record_mapping: []types.Type,
|
|
type_mapping: []types.Type,
|
|
) -> (Converted_Macro_Value, bool) {
|
|
store := &state.module.type_store
|
|
destination := types.resolve_alias(field_type, store)
|
|
source_type := translate_c_type(
|
|
state, result, value.type, pkg, record_mapping, type_mapping,
|
|
)
|
|
if !types.is_concrete_scalar(source_type) {
|
|
return {}, false
|
|
}
|
|
if types.is_optional_pointer(destination, store) {
|
|
if types.is_concrete_integer(source_type) &&
|
|
value.kind == .Integer && macro_integer_value(value) == 0 {
|
|
return Converted_Macro_Value{kind=.Null}, true
|
|
}
|
|
return {}, false
|
|
}
|
|
if types.is_concrete_integer(source_type) {
|
|
if value.kind != .Integer {
|
|
return {}, false
|
|
}
|
|
integer := macro_integer_value(value)
|
|
if types.is_concrete_integer(destination) {
|
|
return convert_macro_integer(integer, destination, state.selected)
|
|
}
|
|
if types.is_float(destination, state.selected) {
|
|
return convert_macro_float(f64(integer), destination, state.selected)
|
|
}
|
|
return {}, false
|
|
}
|
|
number, number_ok := macro_float_value(value, source_type, state.selected)
|
|
if !number_ok {
|
|
return {}, false
|
|
}
|
|
if types.is_float(destination, state.selected) {
|
|
return convert_macro_float(number, destination, state.selected)
|
|
}
|
|
if types.is_concrete_integer(destination) {
|
|
truncated := math.trunc(number)
|
|
bits := types.bits(destination, state.selected)
|
|
if bits <= 0 || bits > 64 {
|
|
return {}, false
|
|
}
|
|
if types.is_signed(destination, state.selected) {
|
|
limit := f64(i128(1) << u32(bits-1))
|
|
if truncated < -limit || truncated >= limit {
|
|
return {}, false
|
|
}
|
|
} else if types.is_unsigned(destination, state.selected) {
|
|
limit := f64(i128(1) << u32(bits))
|
|
if truncated < 0 || truncated >= limit {
|
|
return {}, false
|
|
}
|
|
} else {
|
|
return {}, false
|
|
}
|
|
return convert_macro_integer(i128(truncated), destination, state.selected)
|
|
}
|
|
return {}, false
|
|
}
|
|
|
|
add_converted_macro_value_expr :: proc(
|
|
state: ^State,
|
|
value: Converted_Macro_Value,
|
|
span: source.Span,
|
|
) -> (ast.Expr_Id, bool) {
|
|
#partial switch value.kind {
|
|
case .Integer:
|
|
return add_macro_value_expr(state, cimport.Macro_Value{
|
|
kind=.Integer,
|
|
integer=value.integer,
|
|
negative=value.negative,
|
|
}, span), true
|
|
case .Float:
|
|
return add_macro_value_expr(state, cimport.Macro_Value{
|
|
kind=.Float,
|
|
integer=value.integer,
|
|
}, span), true
|
|
case .Null:
|
|
return add_import_expr(state, ast.Expr{
|
|
kind=.Null, span=span,
|
|
left=ast.INVALID_EXPR, right=ast.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
}), true
|
|
case:
|
|
}
|
|
return ast.INVALID_EXPR, false
|
|
}
|
|
|
|
add_macro_aggregate_expr :: proc(
|
|
state: ^State,
|
|
result: ^cimport.Result,
|
|
macro: cimport.Macro_Constant,
|
|
record_type: types.Type,
|
|
pkg: ast.Package_Id,
|
|
record_mapping: []types.Type,
|
|
type_mapping: []types.Type,
|
|
span: source.Span,
|
|
) -> (ast.Expr_Id, bool) {
|
|
fields := types.fields_for(&state.module.type_store, record_type)
|
|
union_record := types.is_union(record_type, &state.module.type_store)
|
|
initializer_count := 1 if union_record else len(fields)
|
|
if len(fields) == 0 || len(macro.values) > initializer_count {
|
|
return ast.INVALID_EXPR, false
|
|
}
|
|
converted := make([]Converted_Macro_Value, len(macro.values), state.allocator)
|
|
defer delete(converted, state.allocator)
|
|
for value, index in macro.values {
|
|
converted[index], _ = convert_macro_field_value(
|
|
state,
|
|
result,
|
|
value,
|
|
fields[index].type,
|
|
pkg,
|
|
record_mapping,
|
|
type_mapping,
|
|
)
|
|
if converted[index].kind == .Invalid {
|
|
return ast.INVALID_EXPR, false
|
|
}
|
|
}
|
|
args := make([]ast.Expr_Id, initializer_count, state.allocator)
|
|
remaining := MAX_MACRO_ZERO_NODES
|
|
for index in 0..<initializer_count {
|
|
field := fields[index]
|
|
value_expr := ast.INVALID_EXPR
|
|
value_ok := false
|
|
if index < len(macro.values) {
|
|
value_expr, value_ok = add_converted_macro_value_expr(
|
|
state, converted[index], span,
|
|
)
|
|
} else {
|
|
value_expr, value_ok = add_macro_zero_expr(
|
|
state, pkg, field.type, span, 0, &remaining,
|
|
)
|
|
}
|
|
if !value_ok {
|
|
delete(args, state.allocator)
|
|
return ast.INVALID_EXPR, false
|
|
}
|
|
args[index] = add_import_expr(state, ast.Expr{
|
|
kind=.Keyed, span=span, name=symbol.Id(field.name), left=value_expr,
|
|
right=ast.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
return add_import_expr(state, ast.Expr{
|
|
kind=.Struct_Literal,
|
|
span=span,
|
|
name=symbol.intern(state.symbols, macro.type_name),
|
|
args=args,
|
|
left=ast.INVALID_EXPR,
|
|
right=ast.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
}), true
|
|
}
|
|
|
|
find_global_in_package :: proc(module: ^ast.Module, pkg: ast.Package_Id, name: symbol.Id) -> (ast.Global_Id, bool) {
|
|
for global, index in module.globals {
|
|
if global.pkg == pkg && global.name == name {
|
|
return ast.global_id(index), true
|
|
}
|
|
}
|
|
return ast.INVALID_GLOBAL, false
|
|
}
|
|
|
|
find_function_in_package :: proc(module: ^ast.Module, pkg: ast.Package_Id, name: symbol.Id) -> bool {
|
|
for function in module.functions {
|
|
if function.pkg == pkg && function.name == name {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
remove_value_declarations_in_package :: proc(
|
|
state: ^State,
|
|
pkg: ast.Package_Id,
|
|
name: symbol.Id,
|
|
) {
|
|
function_index := 0
|
|
for function_index < len(state.module.functions) {
|
|
function := state.module.functions[function_index]
|
|
if function.pkg != pkg || function.name != name {
|
|
function_index += 1
|
|
continue
|
|
}
|
|
delete(function.params, state.allocator)
|
|
delete(function.body, state.allocator)
|
|
delete(function.link_name, state.allocator)
|
|
delete(function.unsupported_reason, state.allocator)
|
|
ordered_remove(&state.module.functions, function_index)
|
|
}
|
|
global_index := 0
|
|
for global_index < len(state.module.globals) {
|
|
global := state.module.globals[global_index]
|
|
if global.pkg != pkg || global.name != name {
|
|
global_index += 1
|
|
continue
|
|
}
|
|
delete(global.link_name, state.allocator)
|
|
ordered_remove(&state.module.globals, global_index)
|
|
}
|
|
unsupported_index := 0
|
|
for unsupported_index < len(state.module.unsupported) {
|
|
item := state.module.unsupported[unsupported_index]
|
|
if item.pkg != pkg || item.name != name {
|
|
unsupported_index += 1
|
|
continue
|
|
}
|
|
delete(item.reason, state.allocator)
|
|
ordered_remove(&state.module.unsupported, unsupported_index)
|
|
}
|
|
}
|
|
|
|
add_external_variable_global :: proc(
|
|
state: ^State,
|
|
result: ^cimport.Result,
|
|
pkg: ast.Package_Id,
|
|
variable: cimport.Variable,
|
|
record_mapping: []types.Type,
|
|
type_mapping: []types.Type,
|
|
span: source.Span,
|
|
) {
|
|
name := symbol.intern(state.symbols, variable.name)
|
|
variable_type := translate_c_type(state, result, variable.type, pkg, record_mapping, type_mapping)
|
|
unsupported_reason := variable.reason
|
|
if len(unsupported_reason) == 0 {
|
|
unsupported_reason = c_record_by_value_reason(result, variable.type)
|
|
}
|
|
if len(unsupported_reason) == 0 && !types.is_runtime_value(variable_type, &state.module.type_store) {
|
|
unsupported_reason = "C variable type is not supported"
|
|
}
|
|
if len(unsupported_reason) > 0 {
|
|
add_import_unsupported(state, pkg, variable.name, unsupported_reason)
|
|
return
|
|
}
|
|
if find_function_in_package(state.module, pkg, name) {
|
|
add_import_unsupported(state, pkg, variable.name, "C variable conflicts with a function declaration")
|
|
return
|
|
}
|
|
if existing, ok := find_global_in_package(state.module, pkg, name); ok {
|
|
global := state.module.globals[existing]
|
|
if !global.external || !types.equal(global.type, variable_type) || global.writable != variable.mutable {
|
|
add_import_unsupported(state, pkg, variable.name, "conflicting C declarations for variable")
|
|
}
|
|
return
|
|
}
|
|
_ = ast.global_id(len(state.module.globals))
|
|
append(&state.module.globals, ast.Global{
|
|
span=span,
|
|
name=name,
|
|
link_name=strings.clone(variable.name, state.allocator),
|
|
pkg=pkg,
|
|
file=ast.INVALID_FILE,
|
|
type=variable_type,
|
|
immutable=true,
|
|
external=true,
|
|
writable=variable.mutable,
|
|
expr=ast.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
|
|
add_macro_constant_global :: proc(
|
|
state: ^State,
|
|
result: ^cimport.Result,
|
|
pkg: ast.Package_Id,
|
|
macro: cimport.Macro_Constant,
|
|
record_mapping: []types.Type,
|
|
type_mapping: []types.Type,
|
|
span: source.Span,
|
|
) {
|
|
name := symbol.intern(state.symbols, macro.name)
|
|
remove_value_declarations_in_package(state, pkg, name)
|
|
if macro.aggregate {
|
|
type_name := symbol.intern(state.symbols, macro.type_name)
|
|
named := types.find_named(&state.module.type_store, u32(pkg), u32(type_name))
|
|
record_type := types.resolve_alias(named, &state.module.type_store)
|
|
if !types.is_record(record_type, &state.module.type_store) ||
|
|
types.is_opaque_struct(record_type, &state.module.type_store) {
|
|
add_import_unsupported(state, pkg, macro.name, "C macro aggregate type is not supported")
|
|
return
|
|
}
|
|
expr, ok := add_macro_aggregate_expr(
|
|
state,
|
|
result,
|
|
macro,
|
|
record_type,
|
|
pkg,
|
|
record_mapping,
|
|
type_mapping,
|
|
span,
|
|
)
|
|
if !ok {
|
|
add_import_unsupported(state, pkg, macro.name, "C macro aggregate initializer is not representable")
|
|
return
|
|
}
|
|
_ = ast.global_id(len(state.module.globals))
|
|
append(&state.module.globals, ast.Global{
|
|
span=span,
|
|
name=name,
|
|
pkg=pkg,
|
|
file=ast.INVALID_FILE,
|
|
type=record_type,
|
|
immutable=true,
|
|
expr=expr,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
return
|
|
}
|
|
macro_type := translate_c_type(state, result, macro.type, pkg, record_mapping, type_mapping)
|
|
if !types.is_runtime_value(macro_type, &state.module.type_store) {
|
|
add_import_unsupported(state, pkg, macro.name, "C macro constant type is not supported")
|
|
return
|
|
}
|
|
expr := add_macro_value_expr(state, macro.value, span)
|
|
_ = ast.global_id(len(state.module.globals))
|
|
append(&state.module.globals, ast.Global{
|
|
span=span,
|
|
name=name,
|
|
pkg=pkg,
|
|
file=ast.INVALID_FILE,
|
|
type=macro_type,
|
|
immutable=true,
|
|
expr=expr,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
|
|
load_header :: proc(state: ^State, path: string, import_span: source.Span) -> ast.Package_Id {
|
|
canonical, ok := filepath.abs(path, state.allocator)
|
|
if !ok {
|
|
id := add_placeholder(state, path)
|
|
source.addf(state.diagnostics, import_span, "could not resolve C header '%s'", path)
|
|
return id
|
|
}
|
|
if existing := find_package(state, canonical); existing != ast.INVALID_PACKAGE {
|
|
delete(canonical, state.allocator)
|
|
return existing
|
|
}
|
|
pkg_id := ast.package_id(len(state.module.packages))
|
|
append(&state.module.packages, ast.Package{
|
|
path=canonical,
|
|
name=header_package_name(canonical, state.symbols),
|
|
available=false,
|
|
kind=.C_Header,
|
|
})
|
|
result := cimport.import_header(state.c_options, canonical, state.selected, state.allocator)
|
|
defer cimport.destroy_result(&result)
|
|
if !result.available {
|
|
message := result.error_message if len(result.error_message) > 0 else "C header import failed"
|
|
source.addf(state.diagnostics, import_span, "could not import C header '%s': %s", path, message)
|
|
if result.infrastructure {
|
|
state.root_failed = true
|
|
}
|
|
return pkg_id
|
|
}
|
|
state.module.packages[pkg_id].available = true
|
|
|
|
record_mapping := make([]types.Type, len(result.records), state.allocator)
|
|
defer delete(record_mapping, state.allocator)
|
|
for record, index in result.records {
|
|
record_type := find_record_identity(state, record.identity)
|
|
if !types.is_valid(record_type) {
|
|
name := record.name
|
|
if len(name) == 0 {
|
|
name = fmt.tprintf("__c_record_%d", len(state.record_types))
|
|
}
|
|
record_type = types.named(&state.module.type_store, u32(pkg_id), u32(symbol.intern(state.symbols, name)))
|
|
_ = types.define_record(&state.module.type_store, record_type, nil, false, true, record.kind == .Union)
|
|
append(&state.record_identities, strings.clone(record.identity, state.allocator))
|
|
append(&state.record_types, record_type)
|
|
}
|
|
record_mapping[index] = record_type
|
|
}
|
|
type_mapping := make([]types.Type, len(result.types), state.allocator)
|
|
defer delete(type_mapping, state.allocator)
|
|
|
|
for _ in 0..<len(result.records) {
|
|
changed := false
|
|
for record, record_index in result.records {
|
|
if !record.complete || len(record.reason) > 0 {
|
|
continue
|
|
}
|
|
record_type := record_mapping[record_index]
|
|
item, ok := types.node(&state.module.type_store, record_type)
|
|
if !ok || (item.declared && !item.opaque) {
|
|
continue
|
|
}
|
|
fields := make([]types.Field, len(record.fields), state.allocator)
|
|
for field, field_index in record.fields {
|
|
fields[field_index] = types.Field{
|
|
name=u32(symbol.intern(state.symbols, field.name)),
|
|
type=translate_c_type(state, &result, field.type, pkg_id, record_mapping, type_mapping),
|
|
offset=field.offset,
|
|
}
|
|
}
|
|
layout_reason := record_layout_reason(&state.module.type_store, record, fields, state.selected)
|
|
if len(layout_reason) == 0 {
|
|
changed = types.define_record(
|
|
&state.module.type_store, record_type, fields, true, false,
|
|
record.kind == .Union, record.size, record.alignment,
|
|
) || changed
|
|
} else if layout_reason != RECORD_DEPENDENCY_PENDING && len(record.reason) == 0 {
|
|
delete(result.records[record_index].reason, result.allocator)
|
|
result.records[record_index].reason = fmt.aprintf("%s", layout_reason, allocator=result.allocator)
|
|
}
|
|
delete(fields, state.allocator)
|
|
}
|
|
if !changed {
|
|
break
|
|
}
|
|
}
|
|
|
|
for alias in result.aliases {
|
|
name := symbol.intern(state.symbols, alias.name)
|
|
id := types.named(&state.module.type_store, u32(pkg_id), u32(name))
|
|
child := translate_c_type(state, &result, alias.type, pkg_id, record_mapping, type_mapping)
|
|
_ = types.define_alias(&state.module.type_store, id, child)
|
|
if len(alias.reason) > 0 {
|
|
append(&state.module.unsupported, ast.Unsupported{
|
|
pkg=pkg_id,
|
|
name=name,
|
|
reason=strings.clone(alias.reason, state.allocator),
|
|
})
|
|
}
|
|
}
|
|
|
|
for function in result.functions {
|
|
params := make([]ast.Param, len(function.params), state.allocator)
|
|
for param_type, index in function.params {
|
|
params[index] = ast.Param{
|
|
name=symbol.intern(state.symbols, fmt.tprintf("arg%d", index)),
|
|
span=import_span,
|
|
type=translate_c_type(state, &result, param_type, pkg_id, record_mapping, type_mapping),
|
|
}
|
|
}
|
|
function_result := translate_c_type(state, &result, function.result, pkg_id, record_mapping, type_mapping)
|
|
unsupported_reason := function.reason
|
|
if len(unsupported_reason) == 0 {
|
|
for param_type in function.params {
|
|
if reason := c_record_by_value_reason(&result, param_type); len(reason) > 0 {
|
|
unsupported_reason = reason
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if len(unsupported_reason) == 0 {
|
|
unsupported_reason = c_record_by_value_reason(&result, function.result)
|
|
}
|
|
if len(unsupported_reason) == 0 {
|
|
for param in params {
|
|
if types.contains_c_struct_by_value(param.type, &state.module.type_store) {
|
|
unsupported_reason = "C record parameter is incomplete or has an unsupported layout"
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if len(unsupported_reason) == 0 &&
|
|
types.contains_c_struct_by_value(function_result, &state.module.type_store) {
|
|
unsupported_reason = "C record result is incomplete or has an unsupported layout"
|
|
}
|
|
name := symbol.intern(state.symbols, function.name)
|
|
duplicate := false
|
|
for &existing in state.module.functions {
|
|
if existing.pkg != pkg_id || existing.name != name {
|
|
continue
|
|
}
|
|
duplicate = true
|
|
if !function_signatures_equal(existing, params, function_result, function.variadic) && len(existing.unsupported_reason) == 0 {
|
|
existing.unsupported_reason = fmt.aprintf(
|
|
"conflicting C declarations for '%s'",
|
|
function.name,
|
|
allocator=state.allocator,
|
|
)
|
|
}
|
|
break
|
|
}
|
|
if duplicate {
|
|
delete(params, state.allocator)
|
|
continue
|
|
}
|
|
append(&state.module.functions, ast.Function{
|
|
span=import_span,
|
|
name=name,
|
|
pkg=pkg_id,
|
|
file=ast.INVALID_FILE,
|
|
c_abi=true,
|
|
imported=true,
|
|
has_body=false,
|
|
variadic=function.variadic,
|
|
params=params,
|
|
result=function_result,
|
|
link_name=strings.clone(function.link_name, state.allocator),
|
|
unsupported_reason=strings.clone(unsupported_reason, state.allocator),
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
// Emit the wrapper only for a `static inline` that survives as supported.
|
|
// cimport may translate its signature fine yet the by-value record layout
|
|
// checks above can still reject it; a wrapper for an uncallable function
|
|
// would just be dead external code.
|
|
if len(unsupported_reason) == 0 && len(function.link_name) > 0 {
|
|
if trampoline, ok := find_trampoline(&result, function.link_name); ok {
|
|
add_c_trampoline(state, trampoline)
|
|
}
|
|
}
|
|
}
|
|
for variable in result.variables {
|
|
add_external_variable_global(
|
|
state, &result, pkg_id, variable, record_mapping, type_mapping, import_span,
|
|
)
|
|
}
|
|
for macro in result.macros {
|
|
add_macro_constant_global(
|
|
state, &result, pkg_id, macro, record_mapping, type_mapping, import_span,
|
|
)
|
|
}
|
|
for item in result.unsupported {
|
|
name := symbol.intern(state.symbols, item.name)
|
|
if item.final_macro {
|
|
remove_value_declarations_in_package(state, pkg_id, name)
|
|
}
|
|
append(&state.module.unsupported, ast.Unsupported{
|
|
pkg=pkg_id,
|
|
name=name,
|
|
reason=strings.clone(item.reason, state.allocator),
|
|
})
|
|
}
|
|
return pkg_id
|
|
}
|
|
|
|
resolve_package_imports :: proc(state: ^State, pkg_id: ast.Package_Id) {
|
|
if int(pkg_id) >= len(state.module.packages) {
|
|
return
|
|
}
|
|
canonical := state.module.packages[pkg_id].path
|
|
import_count := len(state.module.imports)
|
|
for import_id in 0..<import_count {
|
|
import_item := state.module.imports[import_id]
|
|
if import_item.pkg != pkg_id || import_item.target != ast.INVALID_PACKAGE ||
|
|
import_item.test_only && (state.mode != .Test || !state.module.packages[pkg_id].test) {
|
|
continue
|
|
}
|
|
if filepath.is_abs(import_item.path) {
|
|
state.module.imports[import_id].diagnostic = source.add(state.diagnostics, import_item.span, "absolute import paths are invalid")
|
|
state.module.imports[import_id].valid = false
|
|
state.module.imports[import_id].target = add_placeholder(state, import_item.path)
|
|
continue
|
|
}
|
|
target_path, target_ok := resolve_import_path(state, canonical, import_item.path)
|
|
target := load_header(state, target_path, import_item.span) if filepath.ext(import_item.path) == ".h" else
|
|
load_package(state, target_path, import_item.span, include_tests=import_item.test_only)
|
|
state.module.imports[import_id].target = target
|
|
if !target_ok || target == ast.INVALID_PACKAGE || !state.module.packages[target].available {
|
|
state.module.imports[import_id].valid = false
|
|
}
|
|
delete(target_path, state.allocator)
|
|
}
|
|
}
|
|
|
|
load_package :: proc(
|
|
state: ^State,
|
|
path: string,
|
|
import_span: source.Span,
|
|
is_root := false,
|
|
include_tests := false,
|
|
) -> ast.Package_Id {
|
|
canonical, ok := filepath.abs(path, state.allocator)
|
|
if !ok || !os.is_dir(path) {
|
|
if is_root {
|
|
state.root_failed = true
|
|
if len(canonical) > 0 {
|
|
delete(canonical, state.allocator)
|
|
}
|
|
return ast.INVALID_PACKAGE
|
|
}
|
|
placeholder := path
|
|
if len(canonical) > 0 {
|
|
placeholder = canonical
|
|
}
|
|
id := add_placeholder(state, placeholder)
|
|
source.addf(state.diagnostics, import_span, "could not import package directory '%s'", path)
|
|
if len(canonical) > 0 {
|
|
delete(canonical, state.allocator)
|
|
}
|
|
return id
|
|
}
|
|
if existing := find_package(state, canonical); existing != ast.INVALID_PACKAGE {
|
|
if include_tests && !state.module.packages[existing].test {
|
|
state.module.packages[existing].test = true
|
|
resolve_package_imports(state, existing)
|
|
}
|
|
delete(canonical, state.allocator)
|
|
return existing
|
|
}
|
|
|
|
pkg_id := ast.package_id(len(state.module.packages))
|
|
append(&state.module.packages, ast.Package{
|
|
path=canonical,
|
|
name=symbol.intern(state.symbols, filepath.base(canonical)),
|
|
available=true,
|
|
test=state.mode == .Test && is_root || include_tests,
|
|
})
|
|
files, files_ok := read_package_files(state, canonical)
|
|
if !files_ok {
|
|
state.root_failed = true
|
|
return pkg_id
|
|
}
|
|
if len(files) == 0 {
|
|
if is_root {
|
|
state.root_failed = true
|
|
} else {
|
|
source.addf(state.diagnostics, import_span, "package '%s' contains no readable .bro or .hon files", canonical)
|
|
state.module.packages[pkg_id].available = false
|
|
}
|
|
os.file_info_slice_delete(files, state.allocator)
|
|
return pkg_id
|
|
}
|
|
|
|
for file_info in files {
|
|
if file_info.size < 0 || !source.fits_source_length(u64(file_info.size)) {
|
|
source.addf(state.diagnostics, import_span, "source file '%s' exceeds the 4 GiB source limit", file_info.fullpath)
|
|
state.root_failed = true
|
|
continue
|
|
}
|
|
bytes, read_ok := os.read_entire_file(file_info.fullpath, state.sources.allocator)
|
|
if !read_ok {
|
|
state.root_failed = true
|
|
continue
|
|
}
|
|
if !source.fits_source_length(u64(len(bytes))) {
|
|
source.addf(state.diagnostics, import_span, "source file '%s' exceeds the 4 GiB source limit", file_info.fullpath)
|
|
delete(bytes, state.sources.allocator)
|
|
state.root_failed = true
|
|
continue
|
|
}
|
|
source_id := source.add_source_owned(state.sources, file_info.fullpath, bytes)
|
|
file_id := ast.file_id(len(state.module.files))
|
|
append(&state.module.files, ast.File{source=source_id, pkg=pkg_id})
|
|
stream := lexer.lex(&state.sources.items[source_id], state.diagnostics, state.symbols, state.token_allocator)
|
|
parser.parse_into(&stream, &state.sources.items[source_id], state.diagnostics, state.module, pkg_id, file_id)
|
|
delete(stream.items)
|
|
}
|
|
os.file_info_slice_delete(files, state.allocator)
|
|
|
|
resolve_package_imports(state, pkg_id)
|
|
return pkg_id
|
|
}
|
|
|
|
declaration_scopes_overlap :: proc(
|
|
left_visibility: types.Visibility,
|
|
left_file: ast.File_Id,
|
|
right_visibility: types.Visibility,
|
|
right_file: ast.File_Id,
|
|
) -> bool {
|
|
return left_visibility != .File || right_visibility != .File || left_file == right_file
|
|
}
|
|
|
|
declaration_visible_in_file :: proc(visibility: types.Visibility, declaration_file, file: ast.File_Id) -> bool {
|
|
return visibility != .File || declaration_file == file
|
|
}
|
|
|
|
declaration_conflicts :: proc(module: ^ast.Module, pkg: ast.Package_Id, file: ast.File_Id, name: symbol.Id) -> bool {
|
|
for function in module.functions {
|
|
if function.pkg == pkg && function.name == name &&
|
|
declaration_visible_in_file(function.visibility, function.file, file) {
|
|
return true
|
|
}
|
|
}
|
|
for global in module.globals {
|
|
if global.pkg == pkg && global.name == name &&
|
|
declaration_visible_in_file(global.visibility, global.file, file) {
|
|
return true
|
|
}
|
|
}
|
|
type_id := types.find_named(&module.type_store, u32(pkg), u32(name), file=u32(file))
|
|
type_item, type_ok := types.node(&module.type_store, type_id)
|
|
if type_ok && type_item.declared {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
validate_imports :: proc(state: ^State) {
|
|
for import_item, import_id in state.module.imports {
|
|
if import_item.test_only {
|
|
continue
|
|
}
|
|
if !symbol.is_valid(import_item.alias) && import_item.target != ast.INVALID_PACKAGE {
|
|
state.module.imports[import_id].alias = state.module.packages[import_item.target].name
|
|
}
|
|
alias := state.module.imports[import_id].alias
|
|
alias_text := symbol.resolve(state.symbols, alias)
|
|
if !is_identifier(alias_text) {
|
|
state.module.imports[import_id].diagnostic = source.add(
|
|
state.diagnostics,
|
|
import_item.span,
|
|
"import requires an explicit valid identifier alias",
|
|
)
|
|
state.module.imports[import_id].valid = false
|
|
}
|
|
if declaration_conflicts(state.module, import_item.pkg, import_item.file, alias) {
|
|
state.module.imports[import_id].diagnostic = source.addf(
|
|
state.diagnostics,
|
|
import_item.span,
|
|
"import alias '%s' conflicts with a package declaration",
|
|
alias_text,
|
|
)
|
|
state.module.imports[import_id].valid = false
|
|
}
|
|
for previous in state.module.imports[:import_id] {
|
|
if previous.file == import_item.file && previous.alias == alias {
|
|
state.module.imports[import_id].diagnostic = source.addf(
|
|
state.diagnostics,
|
|
import_item.span,
|
|
"duplicate import alias '%s' in the same file",
|
|
alias_text,
|
|
)
|
|
state.module.imports[import_id].valid = false
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
find_type_import :: proc(module: ^ast.Module, file: ast.File_Id, alias: symbol.Id) -> ast.Import_Id {
|
|
for import_item, index in module.imports {
|
|
if !import_item.test_only && import_item.file == file && import_item.alias == alias {
|
|
return ast.import_id(index)
|
|
}
|
|
}
|
|
return ast.INVALID_IMPORT
|
|
}
|
|
|
|
find_visible_enum_global :: proc(
|
|
module: ^ast.Module,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
name: symbol.Id,
|
|
public_only := false,
|
|
) -> ast.Global_Id {
|
|
for global, index in module.globals {
|
|
if global.pkg == pkg && global.name == name &&
|
|
((public_only && global.visibility == .Public) ||
|
|
(!public_only && declaration_visible_in_file(global.visibility, global.file, file))) {
|
|
return ast.global_id(index)
|
|
}
|
|
}
|
|
return ast.INVALID_GLOBAL
|
|
}
|
|
|
|
eval_enum_global :: proc(
|
|
state: ^State,
|
|
id: ast.Global_Id,
|
|
visiting: []bool,
|
|
depth: int,
|
|
) -> (i128, bool) {
|
|
index := int(id)
|
|
if id == ast.INVALID_GLOBAL || index < 0 || index >= len(state.module.globals) ||
|
|
depth > 64 || visiting[index] {
|
|
return 0, false
|
|
}
|
|
global := state.module.globals[index]
|
|
if !global.immutable || global.external || global.expr == ast.INVALID_EXPR {
|
|
return 0, false
|
|
}
|
|
if types.is_valid(global.type) && !types.is_concrete_integer(global.type) {
|
|
return 0, false
|
|
}
|
|
visiting[index] = true
|
|
defer visiting[index] = false
|
|
return eval_enum_constant(state, global.expr, global.pkg, global.file, visiting, depth+1)
|
|
}
|
|
|
|
eval_enum_constant :: proc(
|
|
state: ^State,
|
|
id: ast.Expr_Id,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
visiting: []bool,
|
|
depth: int,
|
|
) -> (i128, bool) {
|
|
index := int(id)
|
|
if id == ast.INVALID_EXPR || index < 0 || index >= len(state.module.exprs) || depth > 64 {
|
|
return 0, false
|
|
}
|
|
expr := state.module.exprs[index]
|
|
#partial switch expr.kind {
|
|
case .Integer:
|
|
return i128(expr.integer), true
|
|
case .Negate:
|
|
value, ok := eval_enum_constant(state, expr.left, pkg, file, visiting, depth+1)
|
|
return -value, ok
|
|
case .Name:
|
|
if symbol.is_valid(expr.qualifier) {
|
|
import_id := find_type_import(state.module, file, expr.qualifier)
|
|
if import_id == ast.INVALID_IMPORT {
|
|
return 0, false
|
|
}
|
|
state.module.imports[import_id].used = true
|
|
import_item := state.module.imports[import_id]
|
|
if !import_item.valid || import_item.target == ast.INVALID_PACKAGE ||
|
|
int(import_item.target) >= len(state.module.packages) ||
|
|
!state.module.packages[import_item.target].available {
|
|
return 0, false
|
|
}
|
|
global := find_visible_enum_global(
|
|
state.module,
|
|
import_item.target,
|
|
ast.INVALID_FILE,
|
|
expr.name,
|
|
public_only=true,
|
|
)
|
|
return eval_enum_global(state, global, visiting, depth+1)
|
|
}
|
|
global := find_visible_enum_global(state.module, pkg, file, expr.name)
|
|
return eval_enum_global(state, global, visiting, depth+1)
|
|
}
|
|
return 0, false
|
|
}
|
|
|
|
resolve_enum_values :: proc(state: ^State) {
|
|
visiting := make([]bool, len(state.module.globals), state.allocator)
|
|
defer delete(visiting, state.allocator)
|
|
for declaration in state.module.enum_declarations {
|
|
members := types.enum_members_for(&state.module.type_store, declaration.type)
|
|
if len(members) != len(declaration.values) {
|
|
continue
|
|
}
|
|
next_value: i128
|
|
previous: i128
|
|
has_previous := false
|
|
previous_known := true
|
|
for &member, index in members {
|
|
spec := declaration.values[index]
|
|
value := next_value
|
|
known := true
|
|
if spec.explicit {
|
|
if spec.expr == ast.INVALID_EXPR {
|
|
value = member.value
|
|
} else if resolved, ok := eval_enum_constant(
|
|
state, spec.expr, declaration.pkg, declaration.file, visiting, 0,
|
|
); ok {
|
|
value = resolved
|
|
} else {
|
|
source.add(state.diagnostics, spec.span, "enum value must be an immutable integer constant")
|
|
known = false
|
|
}
|
|
}
|
|
if known {
|
|
member.value = value
|
|
if has_previous && previous_known && value <= previous {
|
|
source.add(state.diagnostics, spec.span, "enum values must be strictly increasing")
|
|
}
|
|
next_value = value+1
|
|
} else {
|
|
next_value = member.value+1
|
|
}
|
|
previous = value
|
|
previous_known = known
|
|
has_previous = true
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
alias_conflicts_with_declaration :: proc(module: ^ast.Module, alias: ast.Declaration_Alias) -> bool {
|
|
for function in module.functions {
|
|
if function.pkg == alias.pkg && function.name == alias.name &&
|
|
declaration_scopes_overlap(function.visibility, function.file, alias.visibility, alias.file) {
|
|
return true
|
|
}
|
|
}
|
|
for global in module.globals {
|
|
if global.pkg == alias.pkg && global.name == alias.name &&
|
|
declaration_scopes_overlap(global.visibility, global.file, alias.visibility, alias.file) {
|
|
return true
|
|
}
|
|
}
|
|
for item in module.type_store.nodes {
|
|
if item.declared && item.pkg == u32(alias.pkg) && item.name == u32(alias.name) &&
|
|
declaration_scopes_overlap(item.visibility, ast.File_Id(item.file), alias.visibility, alias.file) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
direct_alias_target :: proc(module: ^ast.Module, pkg: ast.Package_Id, name: symbol.Id) -> (ast.Declaration_Alias_Kind, u32, int) {
|
|
kind := ast.Declaration_Alias_Kind.Invalid
|
|
target: u32
|
|
kinds := 0
|
|
for function, index in module.functions {
|
|
if function.pkg == pkg && function.name == name && !function.generated && function.visibility == .Public {
|
|
kind = .Function
|
|
target = u32(ast.function_id(index))
|
|
kinds += 1
|
|
break
|
|
}
|
|
}
|
|
for global, index in module.globals {
|
|
if global.pkg == pkg && global.name == name && global.visibility == .Public {
|
|
kind = .Global
|
|
target = u32(ast.global_id(index))
|
|
kinds += 1
|
|
break
|
|
}
|
|
}
|
|
if value := types.find_named(&module.type_store, u32(pkg), u32(name)); types.is_valid(value) {
|
|
if item, ok := types.node(&module.type_store, value); ok && item.declared {
|
|
kind = .Type
|
|
target = u32(value)
|
|
kinds += 1
|
|
}
|
|
}
|
|
return kind, target, kinds
|
|
}
|
|
|
|
non_public_alias_target_exists :: proc(module: ^ast.Module, pkg: ast.Package_Id, name: symbol.Id) -> bool {
|
|
for function in module.functions {
|
|
if function.pkg == pkg && function.name == name && function.visibility != .Public {
|
|
return true
|
|
}
|
|
}
|
|
for global in module.globals {
|
|
if global.pkg == pkg && global.name == name && global.visibility != .Public {
|
|
return true
|
|
}
|
|
}
|
|
for item in module.type_store.nodes {
|
|
if item.declared && item.pkg == u32(pkg) && item.name == u32(name) && item.visibility != .Public {
|
|
return true
|
|
}
|
|
}
|
|
for alias in module.aliases {
|
|
if alias.valid && alias.pkg == pkg && alias.name == name && alias.visibility != .Public {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
find_public_alias :: proc(module: ^ast.Module, pkg: ast.Package_Id, name: symbol.Id) -> int {
|
|
for alias, index in module.aliases {
|
|
if alias.valid && alias.pkg == pkg && alias.name == name && alias.visibility == .Public {
|
|
return index
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
resolve_declaration_alias :: proc(state: ^State, index: int, states: []u8) -> bool {
|
|
alias := &state.module.aliases[index]
|
|
if !alias.valid {
|
|
return false
|
|
}
|
|
if states[index] == 2 {
|
|
return alias.kind != .Invalid
|
|
}
|
|
if states[index] == 1 {
|
|
alias.diagnostic = source.addf(
|
|
state.diagnostics,
|
|
alias.span,
|
|
"declaration alias cycle involving '%s'",
|
|
symbol.resolve(state.symbols, alias.name),
|
|
)
|
|
alias.valid = false
|
|
return false
|
|
}
|
|
states[index] = 1
|
|
defer states[index] = 2
|
|
|
|
kind, target, kinds := direct_alias_target(state.module, alias.target_pkg, alias.member)
|
|
if kinds > 1 {
|
|
alias.diagnostic = source.addf(
|
|
state.diagnostics,
|
|
alias.span,
|
|
"package member '%s.%s' is ambiguous",
|
|
symbol.resolve(state.symbols, alias.qualifier),
|
|
symbol.resolve(state.symbols, alias.member),
|
|
)
|
|
alias.valid = false
|
|
return false
|
|
}
|
|
if kinds == 1 {
|
|
alias.kind = kind
|
|
alias.target = target
|
|
return true
|
|
}
|
|
|
|
if target_alias := find_public_alias(state.module, alias.target_pkg, alias.member); target_alias >= 0 {
|
|
if resolve_declaration_alias(state, target_alias, states) {
|
|
resolved := state.module.aliases[target_alias]
|
|
alias.kind = resolved.kind
|
|
alias.target = resolved.target
|
|
return true
|
|
}
|
|
alias.valid = false
|
|
return false
|
|
}
|
|
|
|
if non_public_alias_target_exists(state.module, alias.target_pkg, alias.member) {
|
|
alias.diagnostic = source.addf(
|
|
state.diagnostics,
|
|
alias.span,
|
|
"package member '%s.%s' is not public",
|
|
symbol.resolve(state.symbols, alias.qualifier),
|
|
symbol.resolve(state.symbols, alias.member),
|
|
)
|
|
} else {
|
|
alias.diagnostic = source.addf(
|
|
state.diagnostics,
|
|
alias.span,
|
|
"package '%s' has no member '%s'",
|
|
symbol.resolve(state.symbols, alias.qualifier),
|
|
symbol.resolve(state.symbols, alias.member),
|
|
)
|
|
}
|
|
alias.valid = false
|
|
return false
|
|
}
|
|
|
|
validate_declaration_aliases :: proc(state: ^State) {
|
|
for &alias, index in state.module.aliases {
|
|
name := symbol.resolve(state.symbols, alias.name)
|
|
if alias_conflicts_with_declaration(state.module, alias) {
|
|
alias.diagnostic = source.addf(state.diagnostics, alias.span, "declaration alias '%s' conflicts with a package declaration", name)
|
|
alias.valid = false
|
|
continue
|
|
}
|
|
for previous in state.module.aliases[:index] {
|
|
if previous.pkg == alias.pkg && previous.name == alias.name &&
|
|
declaration_scopes_overlap(previous.visibility, previous.file, alias.visibility, alias.file) {
|
|
alias.diagnostic = source.addf(state.diagnostics, alias.span, "duplicate declaration alias '%s'", name)
|
|
alias.valid = false
|
|
break
|
|
}
|
|
}
|
|
if !alias.valid {
|
|
continue
|
|
}
|
|
for import_item in state.module.imports {
|
|
if import_item.file == alias.file && import_item.alias == alias.name {
|
|
alias.diagnostic = source.addf(state.diagnostics, alias.span, "declaration alias '%s' conflicts with an import", name)
|
|
alias.valid = false
|
|
break
|
|
}
|
|
}
|
|
if !alias.valid {
|
|
continue
|
|
}
|
|
|
|
import_id := find_type_import(state.module, alias.file, alias.qualifier)
|
|
if import_id == ast.INVALID_IMPORT {
|
|
alias.diagnostic = source.addf(state.diagnostics, alias.span, "unknown symbol '%s'", symbol.resolve(state.symbols, alias.qualifier))
|
|
alias.valid = false
|
|
continue
|
|
}
|
|
state.module.imports[import_id].used = true
|
|
import_item := state.module.imports[import_id]
|
|
alias.target_pkg = import_item.target
|
|
if !import_item.valid || import_item.target == ast.INVALID_PACKAGE ||
|
|
int(import_item.target) >= len(state.module.packages) || !state.module.packages[import_item.target].available {
|
|
alias.diagnostic = source.addf(state.diagnostics, alias.span, "unavailable imported package '%s'", symbol.resolve(state.symbols, alias.qualifier))
|
|
alias.valid = false
|
|
}
|
|
}
|
|
|
|
states := make([]u8, len(state.module.aliases), state.allocator)
|
|
defer delete(states, state.allocator)
|
|
for _, index in state.module.aliases {
|
|
_ = resolve_declaration_alias(state, index, states)
|
|
}
|
|
for &alias in state.module.aliases {
|
|
if !alias.valid || alias.kind != .Type {
|
|
continue
|
|
}
|
|
id := types.named(
|
|
&state.module.type_store,
|
|
u32(alias.pkg),
|
|
u32(alias.name),
|
|
file=u32(alias.file),
|
|
visibility=alias.visibility,
|
|
)
|
|
if !types.define_alias(&state.module.type_store, id, types.Type(alias.target), alias.visibility) {
|
|
alias.diagnostic = source.addf(state.diagnostics, alias.span, "duplicate type declaration '%s'", symbol.resolve(state.symbols, alias.name))
|
|
alias.valid = false
|
|
}
|
|
}
|
|
}
|
|
|
|
canonical_type :: proc(
|
|
module: ^ast.Module,
|
|
value: types.Type,
|
|
mapping: []types.Type,
|
|
visiting: []bool,
|
|
) -> types.Type {
|
|
if value < types.DYNAMIC_START {
|
|
return value
|
|
}
|
|
index := int(value-types.DYNAMIC_START)
|
|
if index < 0 || index >= len(mapping) {
|
|
return value
|
|
}
|
|
if types.is_valid(mapping[index]) {
|
|
return mapping[index]
|
|
}
|
|
if visiting[index] {
|
|
return value
|
|
}
|
|
visiting[index] = true
|
|
defer visiting[index] = false
|
|
item := module.type_store.nodes[index]
|
|
if item.kind == .Named {
|
|
if item.qualifier != 0 {
|
|
import_id := find_type_import(module, ast.File_Id(item.file), symbol.Id(item.qualifier))
|
|
if import_id != ast.INVALID_IMPORT {
|
|
module.imports[import_id].used = true
|
|
import_item := module.imports[import_id]
|
|
resolved := types.find_named(&module.type_store, u32(import_item.target), item.name)
|
|
if types.is_valid(resolved) {
|
|
mapping[index] = canonical_type(module, resolved, mapping, visiting)
|
|
return mapping[index]
|
|
}
|
|
}
|
|
}
|
|
mapping[index] = value
|
|
return value
|
|
}
|
|
if item.kind == .Alias {
|
|
resolved := canonical_type(module, item.child, mapping, visiting)
|
|
mapping[index] = value if !types.is_valid(resolved) else resolved
|
|
return mapping[index]
|
|
}
|
|
if item.kind == .Distinct {
|
|
mapping[index] = value
|
|
module.type_store.nodes[index].child = canonical_type(module, item.child, mapping, visiting)
|
|
return value
|
|
}
|
|
if item.kind == .Enum {
|
|
mapping[index] = value
|
|
module.type_store.nodes[index].child = canonical_type(module, item.child, mapping, visiting)
|
|
return value
|
|
}
|
|
if item.kind == .Struct || item.kind == .Union {
|
|
mapping[index] = value
|
|
module.type_store.nodes[index].extra = canonical_type(module, item.extra, mapping, visiting)
|
|
fields := types.fields_for(&module.type_store, value)
|
|
for &field in fields {
|
|
field.type = canonical_type(module, field.type, mapping, visiting)
|
|
}
|
|
return value
|
|
}
|
|
if item.kind == .Fallible {
|
|
item.child = canonical_type(module, item.child, mapping, visiting)
|
|
item.extra = canonical_type(module, item.extra, mapping, visiting)
|
|
resolved := types.fallible(&module.type_store, item.child, item.extra)
|
|
mapping[index] = resolved
|
|
return resolved
|
|
}
|
|
if item.kind == .Sum {
|
|
left := canonical_type(module, item.child, mapping, visiting)
|
|
right := canonical_type(module, item.extra, mapping, visiting)
|
|
if resolved, compose_error := types.compose_sum(&module.type_store, left, right); compose_error == .None {
|
|
mapping[index] = resolved
|
|
return resolved
|
|
}
|
|
item.child = left
|
|
item.extra = right
|
|
resolved := types.intern(&module.type_store, item)
|
|
mapping[index] = resolved
|
|
return resolved
|
|
}
|
|
if item.kind == .Function {
|
|
params := make([]types.Type, int(item.field_count), context.temp_allocator)
|
|
for param, param_index in types.params_for(&module.type_store, value) {
|
|
params[param_index] = canonical_type(module, param.type, mapping, visiting)
|
|
}
|
|
result := canonical_type(module, item.child, mapping, visiting)
|
|
resolved := types.function(&module.type_store, params, result, item.c_abi, item.variadic)
|
|
mapping[index] = resolved
|
|
return resolved
|
|
}
|
|
if types.is_valid(item.child) {
|
|
item.child = canonical_type(module, item.child, mapping, visiting)
|
|
}
|
|
if types.is_valid(item.extra) {
|
|
item.extra = canonical_type(module, item.extra, mapping, visiting)
|
|
}
|
|
resolved := types.intern(&module.type_store, item)
|
|
mapping[index] = resolved
|
|
return resolved
|
|
}
|
|
|
|
diagnose_qualified_type_uses :: proc(state: ^State) {
|
|
module := state.module
|
|
for &type_use in module.type_uses {
|
|
item, ok := types.node(&module.type_store, type_use.type)
|
|
if !ok || item.qualifier == 0 || item.kind != .Named {
|
|
continue
|
|
}
|
|
qualifier := symbol.Id(item.qualifier)
|
|
name := symbol.Id(item.name)
|
|
import_id := find_type_import(module, type_use.file, qualifier)
|
|
if import_id == ast.INVALID_IMPORT {
|
|
type_use.diagnostic = source.addf(
|
|
state.diagnostics,
|
|
type_use.span,
|
|
"unknown symbol '%s'",
|
|
symbol.resolve(state.symbols, qualifier),
|
|
)
|
|
source.set_primary_label(state.diagnostics, type_use.diagnostic, "unknown symbol")
|
|
continue
|
|
}
|
|
import_item := module.imports[import_id]
|
|
if !import_item.valid || import_item.target == ast.INVALID_PACKAGE ||
|
|
int(import_item.target) >= len(module.packages) || !module.packages[import_item.target].available {
|
|
type_use.diagnostic = source.addf(
|
|
state.diagnostics,
|
|
type_use.span,
|
|
"unavailable imported package '%s'",
|
|
symbol.resolve(state.symbols, qualifier),
|
|
)
|
|
continue
|
|
}
|
|
if !types.is_valid(types.find_named(&module.type_store, u32(import_item.target), u32(name))) {
|
|
type_use.diagnostic = source.addf(
|
|
state.diagnostics,
|
|
type_use.span,
|
|
"package '%s' has no member '%s'",
|
|
symbol.resolve(state.symbols, qualifier),
|
|
symbol.resolve(state.symbols, name),
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
type_resolution_diagnostic :: proc(module: ^ast.Module, value: types.Type, file: ast.File_Id, depth := 0) -> source.Diagnostic_Id {
|
|
if depth > 64 {
|
|
return source.INVALID_DIAGNOSTIC
|
|
}
|
|
for type_use in module.type_uses {
|
|
if type_use.file == file && type_use.type == value && type_use.diagnostic != source.INVALID_DIAGNOSTIC {
|
|
return type_use.diagnostic
|
|
}
|
|
}
|
|
item, ok := types.node(&module.type_store, value)
|
|
if !ok {
|
|
return source.INVALID_DIAGNOSTIC
|
|
}
|
|
if diagnostic := type_resolution_diagnostic(module, item.child, file, depth+1);
|
|
diagnostic != source.INVALID_DIAGNOSTIC {
|
|
return diagnostic
|
|
}
|
|
if diagnostic := type_resolution_diagnostic(module, item.extra, file, depth+1);
|
|
diagnostic != source.INVALID_DIAGNOSTIC {
|
|
return diagnostic
|
|
}
|
|
if item.kind == .Function {
|
|
for param in types.params_for(&module.type_store, value) {
|
|
if diagnostic := type_resolution_diagnostic(module, param.type, file, depth+1);
|
|
diagnostic != source.INVALID_DIAGNOSTIC {
|
|
return diagnostic
|
|
}
|
|
}
|
|
}
|
|
return source.INVALID_DIAGNOSTIC
|
|
}
|
|
|
|
canonicalize_types :: proc(module: ^ast.Module, allocator: mem.Allocator) {
|
|
original_count := len(module.type_store.nodes)
|
|
mapping := make([]types.Type, original_count, allocator)
|
|
visiting := make([]bool, original_count, allocator)
|
|
defer delete(mapping, allocator)
|
|
defer delete(visiting, allocator)
|
|
for &function in module.functions {
|
|
for param in function.params {
|
|
if diagnostic := type_resolution_diagnostic(module, param.type, function.file);
|
|
diagnostic != source.INVALID_DIAGNOSTIC && function.diagnostic == source.INVALID_DIAGNOSTIC {
|
|
function.diagnostic = diagnostic
|
|
}
|
|
}
|
|
signature_results := [2]types.Type{function.result, function.error}
|
|
for value in signature_results {
|
|
if diagnostic := type_resolution_diagnostic(module, value, function.file);
|
|
diagnostic != source.INVALID_DIAGNOSTIC && function.diagnostic == source.INVALID_DIAGNOSTIC {
|
|
function.diagnostic = diagnostic
|
|
}
|
|
}
|
|
for ¶m in function.params {
|
|
param.type = canonical_type(module, param.type, mapping, visiting)
|
|
}
|
|
function.result = canonical_type(module, function.result, mapping, visiting)
|
|
function.error = canonical_type(module, function.error, mapping, visiting)
|
|
}
|
|
for &global in module.globals {
|
|
if diagnostic := type_resolution_diagnostic(module, global.type, global.file);
|
|
diagnostic != source.INVALID_DIAGNOSTIC && global.diagnostic == source.INVALID_DIAGNOSTIC {
|
|
global.diagnostic = diagnostic
|
|
}
|
|
global.type = canonical_type(module, global.type, mapping, visiting)
|
|
}
|
|
for &statement in module.statements {
|
|
if diagnostic := type_resolution_diagnostic(module, statement.type, ast.File_Id(statement.span.file));
|
|
diagnostic != source.INVALID_DIAGNOSTIC && statement.diagnostic == source.INVALID_DIAGNOSTIC {
|
|
statement.diagnostic = diagnostic
|
|
}
|
|
statement.type = canonical_type(module, statement.type, mapping, visiting)
|
|
}
|
|
for &field in module.type_fields {
|
|
field.type = canonical_type(module, field.type, mapping, visiting)
|
|
}
|
|
for &field_default in module.struct_field_defaults {
|
|
field_default.record = canonical_type(module, field_default.record, mapping, visiting)
|
|
}
|
|
for index := 0; index < original_count; index += 1 {
|
|
_ = canonical_type(module, types.DYNAMIC_START+types.Type(index), mapping, visiting)
|
|
}
|
|
for &variant in module.type_store.variants {
|
|
variant.payload = canonical_type(module, variant.payload, mapping, visiting)
|
|
}
|
|
}
|
|
|
|
load :: proc(
|
|
root_path: string,
|
|
sources: ^source.Store,
|
|
diagnostics: ^source.Diagnostics,
|
|
symbols: ^symbol.Table,
|
|
token_allocator := context.allocator,
|
|
allocator := context.allocator,
|
|
c_options := cimport.Options{},
|
|
selected := target.DEFAULT,
|
|
project_root_path := "",
|
|
mode := ast.Compile_Mode.Executable,
|
|
) -> (ast.Module, bool) {
|
|
module := ast.init_module(allocator)
|
|
project_root_source := project_root_path if len(project_root_path) > 0 else root_path
|
|
project_root, project_root_ok := filepath.abs(project_root_source, allocator)
|
|
if !project_root_ok {
|
|
project_root = strings.clone(project_root_source, allocator)
|
|
}
|
|
state := State{
|
|
module=&module,
|
|
sources=sources,
|
|
diagnostics=diagnostics,
|
|
symbols=symbols,
|
|
token_allocator=token_allocator,
|
|
allocator=allocator,
|
|
c_options=c_options,
|
|
selected=selected,
|
|
project_root=project_root,
|
|
mode=mode,
|
|
}
|
|
state.record_identities.allocator = allocator
|
|
state.record_types.allocator = allocator
|
|
defer {
|
|
delete(state.project_root, allocator)
|
|
for identity in state.record_identities {
|
|
delete(identity, allocator)
|
|
}
|
|
delete(state.record_identities)
|
|
delete(state.record_types)
|
|
}
|
|
root := load_package(&state, root_path, source.Span{}, true)
|
|
if root != ast.Package_Id(0) && root != ast.INVALID_PACKAGE {
|
|
state.root_failed = true
|
|
}
|
|
if mode == .Test {
|
|
testing_path, testing_error := filepath.join({project_root, "std", "testing"}, allocator)
|
|
if testing_error == nil {
|
|
_ = load_package(&state, testing_path, source.Span{})
|
|
delete(testing_path, allocator)
|
|
}
|
|
}
|
|
validate_imports(&state)
|
|
validate_declaration_aliases(&state)
|
|
resolve_enum_values(&state)
|
|
diagnose_qualified_type_uses(&state)
|
|
canonicalize_types(&module, allocator)
|
|
return module, !state.root_failed
|
|
}
|