stlib arraylist

This commit is contained in:
2026-07-12 13:38:29 +02:00
parent b0c716537e
commit 0706188b98
16 changed files with 600 additions and 22 deletions
+4
View File
@@ -105,6 +105,7 @@ Expr_Kind :: enum u8 {
Try,
Catch,
Function_Literal,
Anonymous_Struct_Type,
}
Expr :: struct {
@@ -294,6 +295,7 @@ Module :: struct {
unsupported: [dynamic]Unsupported,
c_trampolines: [dynamic]Trampoline,
strings: [dynamic]string,
type_fields: [dynamic]types.Field,
type_store: types.Store,
allocator: mem.Allocator,
}
@@ -312,6 +314,7 @@ init_module :: proc(allocator := context.allocator) -> Module {
module.unsupported.allocator = allocator
module.c_trampolines.allocator = allocator
module.strings.allocator = allocator
module.type_fields.allocator = allocator
return module
}
@@ -362,5 +365,6 @@ destroy_module :: proc(module: ^Module) {
delete(module.unsupported)
delete(module.c_trampolines)
delete(module.strings)
delete(module.type_fields)
types.destroy_store(&module.type_store)
}
+167 -3
View File
@@ -130,6 +130,19 @@ Import_Index_Entry :: struct {
id: ast.Import_Id,
}
Type_Factory_Entry :: struct {
template: ast.Function_Id,
values: []Comptime_Value,
result: types.Type,
resolving: bool,
}
Generated_Type_Entry :: struct {
expr: ast.Expr_Id,
values: []Comptime_Value,
result: types.Type,
}
Checker :: struct {
ast_module: ^ast.Module,
diagnostics: ^source.Diagnostics,
@@ -169,6 +182,8 @@ Checker :: struct {
current_result: types.Type,
current_build_ctx: ^Build_Ctx,
current_comptime_values: []Comptime_Value,
type_factories: [dynamic]Type_Factory_Entry,
generated_types: [dynamic]Generated_Type_Entry,
target: target.Target,
allocator: mem.Allocator,
}
@@ -340,6 +355,8 @@ write_type_label :: proc(checker: ^Checker, builder: ^strings.Builder, value: ty
write_type_label(checker, builder, item.child)
strings.write_string(builder, " ! ")
write_type_label(checker, builder, item.extra)
case .Type_Call:
strings.write_string(builder, "<type factory call>")
case .Struct:
strings.write_string(builder, "struct")
case .Union:
@@ -597,6 +614,8 @@ type_from_syntax :: proc(
if params_changed || result != item.child {
return types.function(store, resolved_params, result, item.c_abi, item.variadic)
}
case .Type_Call:
return resolve_type_factory_call(checker, ast.Expr_Id(item.count_expr), pkg, file)
}
if changed {
return types.intern(store, item)
@@ -1130,10 +1149,133 @@ resolve_type_argument :: proc(
value := types.find_named(&checker.module.types, u32(target_pkg), u32(expr.name), file=u32(expr_lookup_file(expr, file)))
value = types.resolve_alias(value, &checker.module.types)
return value, types.is_valid(value)
case .Call:
value := resolve_type_factory_call(checker, expr_id, pkg, file)
return value, types.is_valid(value)
}
return types.INVALID, false
}
clone_comptime_values :: proc(values: []Comptime_Value, allocator: mem.Allocator) -> []Comptime_Value {
result := make([]Comptime_Value, len(values), allocator)
copy(result, values)
return result
}
resolve_generated_struct_type :: proc(checker: ^Checker, expr_id: ast.Expr_Id, pkg: ast.Package_Id, file: ast.File_Id) -> types.Type {
for entry in checker.generated_types {
if entry.expr == expr_id && comptime_values_equal(entry.values, checker.current_comptime_values) {
return entry.result
}
}
if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
return types.INVALID
}
expr := checker.ast_module.exprs[expr_id]
field_start := int(u32(expr.integer>>32))
field_count := int(u32(expr.integer))
if field_start < 0 || field_count < 0 || field_start+field_count > len(checker.ast_module.type_fields) {
return types.INVALID
}
template_fields := checker.ast_module.type_fields[field_start:field_start+field_count]
fields := make([]types.Field, len(template_fields), checker.allocator)
defer delete(fields, checker.allocator)
for field, index in template_fields {
resolved := type_from_syntax(checker, field.type, pkg, file)
if !is_runtime_type(checker, resolved) || types.is_void(resolved) {
source.addf(checker.diagnostics, expr.span, "anonymous struct field '%s' requires a concrete runtime type, got %s", symbol_text(checker, symbol.Id(field.name)), type_label(checker, resolved))
return types.INVALID
}
fields[index] = types.Field{name=field.name, type=resolved}
}
result := types.struct_generated(&checker.module.types, fields)
append(&checker.generated_types, Generated_Type_Entry{
expr=expr_id,
values=clone_comptime_values(checker.current_comptime_values, checker.allocator),
result=result,
})
return result
}
resolve_type_factory_call :: proc(checker: ^Checker, expr_id: ast.Expr_Id, pkg: ast.Package_Id, file: ast.File_Id) -> types.Type {
if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
return types.INVALID
}
expr := checker.ast_module.exprs[expr_id]
if expr.kind != .Call || expr.left != ast.INVALID_EXPR {
source.add(checker.diagnostics, expr.span, "type position requires a direct type-factory call")
return types.INVALID
}
target_pkg, available := expr_package(checker, expr, pkg, file, true)
if !available {
return types.INVALID
}
template := find_template(checker, expr.name, target_pkg, expr_lookup_file(expr, file))
if template == ast.INVALID_FUNCTION || int(template) >= len(checker.ast_module.functions) {
source.addf(checker.diagnostics, expr.span, "unknown type factory '%s'", symbol_text(checker, expr.name))
return types.INVALID
}
function := checker.ast_module.functions[template]
if !is_type_metatype_syntax(checker, function.result) || types.is_valid(function.error) {
source.addf(checker.diagnostics, expr.span, "function '%s' does not return a type", symbol_text(checker, expr.name))
return types.INVALID
}
for param in function.params {
if !param.comptime_value {
source.addf(checker.diagnostics, param.span, "type-factory parameter '%s' must be comptime", symbol_text(checker, param.name))
return types.INVALID
}
}
if !valid_call_arity(function, len(expr.args)) {
source.addf(checker.diagnostics, expr.span, "type factory '%s' expects %d arguments, got %d", symbol_text(checker, expr.name), len(function.params), len(expr.args))
return types.INVALID
}
values, ok := collect_comptime_values(checker, function, expr.args, pkg, file, true, checker.current_comptime_values)
defer delete(values, checker.allocator)
if !ok {
return types.INVALID
}
// A generic function's declaration is validated before it has a specialization.
// Leave calls containing its unresolved type parameters pending until then.
for value in values {
if value.kind != .Type {
continue
}
if item, item_ok := types.node(&checker.module.types, value.type); item_ok && item.kind == .Named && !item.declared {
return types.INVALID
}
}
for &entry in checker.type_factories {
if entry.template != template || !comptime_values_equal(entry.values, values) {
continue
}
if entry.resolving {
source.addf(checker.diagnostics, expr.span, "recursive type-factory specialization of '%s'", symbol_text(checker, expr.name))
return types.INVALID
}
return entry.result
}
entry_index := len(checker.type_factories)
append(&checker.type_factories, Type_Factory_Entry{
template=template,
values=clone_comptime_values(values, checker.allocator),
result=types.INVALID,
resolving=true,
})
state := ct_state_make(checker, pkg, file)
value, flow, eval_ok := ct_eval_call_expr(&state, expr, function.result, 0)
result := types.INVALID
if eval_ok && flow.kind == .Normal && value != INVALID_CT_VALUE && int(value) < len(state.values) && state.values[value].kind == .Type {
result = types.Type(state.values[value].index)
} else if state.diagnostic == source.INVALID_DIAGNOSTIC {
source.addf(checker.diagnostics, expr.span, "type factory '%s' did not return a type", symbol_text(checker, expr.name))
}
ct_state_destroy(&state)
checker.type_factories[entry_index].result = result
checker.type_factories[entry_index].resolving = false
return result
}
collect_comptime_values :: proc(
checker: ^Checker,
function: ast.Function,
@@ -1362,7 +1504,7 @@ mark_expr_imports_used :: proc(checker: ^Checker, expr_id: ast.Expr_Id, file: as
}
case .Add, .Sub, .Mul, .Div, .Index, .Orelse, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
append(&stack, expr.left, expr.right)
case .Invalid, .Integer, .Float, .String, .Bool, .None, .Undefined, .Type, .Name:
case .Invalid, .Integer, .Float, .String, .Bool, .None, .Undefined, .Type, .Name, .Anonymous_Struct_Type:
}
}
}
@@ -2173,6 +2315,9 @@ infer_expr :: proc(
case .Type:
last = types.INVALID
_ = pop(&stack)
case .Anonymous_Struct_Type:
last = types.INVALID
_ = pop(&stack)
case .Integer:
last = types.I64
if expr.integer <= 0x7fff_ffff_ffff_ffff {
@@ -4789,7 +4934,10 @@ build_compound_expr :: proc(
})
case .Struct_Literal:
struct_type := types.INVALID
if symbol.is_valid(expr.name) {
if expr.left != ast.INVALID_EXPR {
struct_type, _ = resolve_type_argument(checker, expr.left, pkg, file)
struct_type = types.resolve_alias(struct_type, store)
} else if symbol.is_valid(expr.name) {
target_pkg, available := expr_package(checker, expr, pkg, file, true)
struct_type = types.find_named(store, u32(target_pkg), u32(expr.name), file=u32(expr_lookup_file(expr, file))) if available else types.INVALID
struct_type = types.resolve_alias(struct_type, store)
@@ -4977,7 +5125,7 @@ build_expr :: proc(
template := ast.Function_Id(u32(expr.integer))
last = build_function_value(checker, template, expr.span, frame.expected)
_ = pop(&stack)
case .Type:
case .Type, .Anonymous_Struct_Type:
id := source.add(checker.diagnostics, expr.span, "type is not a runtime value")
last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack)
@@ -5294,6 +5442,12 @@ build_expr :: proc(
continue
}
function := checker.ast_module.functions[template]
if is_type_metatype_syntax(checker, function.result) {
id := source.addf(checker.diagnostics, expr.span, "type factory '%s' is only valid in type position", symbol_text(checker, expr.name))
last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack)
continue
}
if !valid_call_arity(function, len(expr.args)) {
message := "function '%s' expects at least %d arguments, got %d" if function.variadic else
"function '%s' expects %d arguments, got %d"
@@ -8821,6 +8975,8 @@ check :: proc(
checker.build_stack.allocator = allocator
checker.cycle_stack.allocator = allocator
checker.anon_globals.allocator = allocator
checker.type_factories.allocator = allocator
checker.generated_types.allocator = allocator
build_symbol_indexes(&checker)
checker.global_types = make([]types.Type, len(ast_module.globals), allocator)
checker.global_demands = make([]types.Type, len(ast_module.globals), allocator)
@@ -8864,6 +9020,14 @@ check :: proc(
delete(checker.infer_stack)
delete(checker.build_stack)
delete(checker.cycle_stack)
for entry in checker.type_factories {
delete(entry.values, allocator)
}
for entry in checker.generated_types {
delete(entry.values, allocator)
}
delete(checker.type_factories)
delete(checker.generated_types)
}
for function, index in ast_module.functions {
+19 -3
View File
@@ -215,6 +215,7 @@ Ct_Value_Kind :: enum u8 {
Pointer,
Slice,
Function,
Type,
None,
Optional_Some,
Fallible,
@@ -333,6 +334,9 @@ ct_state_make :: proc(
if value.kind == .Integer {
id := ct_add_value(&state, Ct_Value{kind=.Integer, type=value.type, integer=value.value})
ct_bind_value(&state, value.name, value.type, id, false)
} else if value.kind == .Type {
id := ct_add_value(&state, Ct_Value{kind=.Type, type=types.INVALID, index=u64(value.type)})
ct_bind_value(&state, value.name, types.INVALID, id, false)
}
}
return state
@@ -532,6 +536,9 @@ ct_coerce_value :: proc(state: ^Ct_State, id: Ct_Value_Id, expected: types.Type,
return id, true
}
value := state.values[id]
if value.kind == .Type && is_type_metatype_syntax(state.checker, expected) {
return id, true
}
if types.equal(value.type, expected) {
return id, true
}
@@ -914,7 +921,7 @@ ct_eval_expr :: proc(
id := ct_add_value(state, Ct_Value{kind=.Integer, type=value.type, integer=value.value})
return id, ct_flow(.Normal), true
}
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "type parameter '%s' is not a runtime value", symbol_text(checker, expr.name))
return ct_add_value(state, Ct_Value{kind=.Type, type=types.INVALID, index=u64(value.type)}), ct_flow(.Normal), true
}
} else if find_import(checker, state.file, expr.qualifier) == ast.INVALID_IMPORT {
if index, ok := ct_find_binding_index(state, expr.qualifier); ok {
@@ -971,6 +978,12 @@ ct_eval_expr :: proc(
return ct_eval_array_expr(state, expr, expected, depth+1)
case .Struct_Literal:
return ct_eval_struct_expr(state, expr, expected, depth+1)
case .Type:
resolved := type_from_syntax(checker, expr.type, state.pkg, state.file)
return ct_add_value(state, Ct_Value{kind=.Type, type=types.INVALID, index=u64(resolved)}), ct_flow(.Normal), types.is_valid(resolved)
case .Anonymous_Struct_Type:
resolved := resolve_generated_struct_type(checker, expr_id, state.pkg, state.file)
return ct_add_value(state, Ct_Value{kind=.Type, type=types.INVALID, index=u64(resolved)}), ct_flow(.Normal), types.is_valid(resolved)
case .Enum_Literal:
return ct_eval_enum_literal(state, expr, expected, depth+1)
case .None:
@@ -1150,7 +1163,7 @@ ct_eval_expr :: proc(
return value, ct_flow(.Normal), true
case .Slice:
return ct_eval_slice_expr(state, expr, depth+1)
case .Type, .Undefined, .Keyed:
case .Undefined, .Keyed:
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "expression cannot be evaluated at comptime")
}
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "expression cannot be evaluated at comptime")
@@ -1216,7 +1229,10 @@ ct_eval_struct_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Ty
checker := state.checker
store := &checker.module.types
struct_type := types.INVALID
if symbol.is_valid(expr.name) {
if expr.left != ast.INVALID_EXPR {
struct_type, _ = resolve_type_argument(checker, expr.left, state.pkg, state.file)
struct_type = types.resolve_alias(struct_type, store)
} else if symbol.is_valid(expr.name) {
target_pkg, available := expr_package(checker, expr, state.pkg, state.file, false)
struct_type = types.find_named(store, u32(target_pkg), u32(expr.name), file=u32(expr_lookup_file(expr, state.file))) if available else types.INVALID
struct_type = types.resolve_alias(struct_type, store)
+3
View File
@@ -1402,6 +1402,9 @@ canonicalize_types :: proc(module: ^ast.Module, allocator: mem.Allocator) {
for &statement in module.statements {
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 index := 0; index < original_count; index += 1 {
_ = canonical_type(module, types.DYNAMIC_START+types.Type(index), mapping, visiting)
}
+18 -10
View File
@@ -396,11 +396,14 @@ lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instructi
success_lbl := fresh_label(state)
error_lbl := fresh_label(state)
merge_lbl := fresh_label(state)
slot := append_instruction(state, ir.Instruction{
op=.Alloca, span=expr.span, type=success,
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
slot := ir.INVALID_INSTRUCTION
if !types.is_void(success) {
slot = append_instruction(state, ir.Instruction{
op=.Alloca, span=expr.span, type=success,
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
append_instruction(state, ir.Instruction{
op=.Cond_Br, span=expr.span, type=types.VOID,
integer=success_lbl, target=ir.Ref(u32(error_lbl)), a=ok,
@@ -468,10 +471,12 @@ lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instructi
}
if expr.right != hir.INVALID_EXPR {
fallback := lower_nested_expr(state, expr.right)
append_instruction(state, ir.Instruction{
op=.Store, span=expr.span, type=success,
target=ir.INVALID_REF, a=slot, b=fallback, diagnostic=source.INVALID_DIAGNOSTIC,
})
if !types.is_void(success) {
append_instruction(state, ir.Instruction{
op=.Store, span=expr.span, type=success,
target=ir.INVALID_REF, a=slot, b=fallback, diagnostic=source.INVALID_DIAGNOSTIC,
})
}
append_instruction(state, ir.Instruction{
op=.Br, span=expr.span, type=types.VOID, integer=merge_lbl,
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
@@ -505,11 +510,14 @@ lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instructi
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
append_instruction(state, ir.Instruction{
merge := append_instruction(state, ir.Instruction{
op=.Label, span=expr.span, type=types.VOID, integer=merge_lbl,
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
if types.is_void(success) {
return merge
}
return append_instruction(state, ir.Instruction{
op=.Load, span=expr.span, type=success,
target=ir.INVALID_REF, a=slot, b=ir.INVALID_INSTRUCTION,
+48 -2
View File
@@ -378,7 +378,7 @@ parse_type_atom :: proc(parser: ^Parser) -> ast.Type_Syntax {
}
name = advance(parser)
}
return types.named(
named := types.named(
&parser.module.type_store,
u32(parser.pkg),
u32(name.symbol),
@@ -386,6 +386,14 @@ parse_type_atom :: proc(parser: ^Parser) -> ast.Type_Syntax {
u32(parser.file),
!symbol.is_valid(qualifier) && file_hidden_name(parser, name),
)
if current(parser).kind == .Left_Paren {
call := parse_call(parser, qualifier, first, name, 0)
return types.intern(&parser.module.type_store, types.Node{
kind=.Type_Call,
count_expr=u32(call),
})
}
return named
}
source.add(parser.diagnostics, tok.span, "expected a type")
return types.INVALID
@@ -589,6 +597,29 @@ parse_struct_literal :: proc(
})
}
parse_anonymous_struct_type_expr :: proc(parser: ^Parser) -> ast.Expr_Id {
start := advance(parser)
fields: [dynamic]types.Field
fields.allocator = parser.module.allocator
if !parse_record_body(parser, &fields, "expected '{' after anonymous struct type") {
delete(fields)
return invalid_expr(parser, start.span, "invalid anonymous struct type")
}
end := previous(parser)
field_start := u32(len(parser.module.type_fields))
field_count := u32(len(fields))
append(&parser.module.type_fields, ..fields[:])
delete(fields)
return add_expr(parser, ast.Expr{
kind=.Anonymous_Struct_Type,
span=span_from(start.span, end.span),
integer=u64(field_start)<<32 | u64(field_count),
left=ast.INVALID_EXPR,
right=ast.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
parse_integer_magnitude :: proc(text: string) -> (u64, bool) {
value: u64
for byte in transmute([]byte)text {
@@ -755,6 +786,8 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
})
case .Keyword_Func:
return parse_function_literal(parser)
case .Keyword_Struct:
return parse_anonymous_struct_type_expr(parser)
case .Left_Bracket:
if starts_declared_type(parser) {
start := tok
@@ -842,7 +875,20 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
name = advance(parser)
}
if current(parser).kind == .Left_Paren {
return parse_call(parser, qualifier, first, name, nesting)
call := parse_call(parser, qualifier, first, name, nesting)
if current(parser).kind == .Left_Brace && !(parser.no_struct_literal && parser.delimiter_depth == 0) {
left_brace := advance(parser)
args, right_brace := parse_keyed_initializers(parser, left_brace, nesting, "expected '}' after struct literal")
return add_expr(parser, ast.Expr{
kind=.Struct_Literal,
span=span_from(parser.module.exprs[call].span, right_brace.span),
args=args,
left=call,
right=ast.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
return call
}
if current(parser).kind == .Left_Brace && !(parser.no_struct_literal && parser.delimiter_depth == 0) {
return parse_struct_literal(parser, qualifier, first, name, nesting)
+16
View File
@@ -75,6 +75,7 @@ Kind :: enum u8 {
Struct,
Union,
Fallible,
Type_Call,
}
Node :: struct {
@@ -359,6 +360,21 @@ struct_anonymous :: proc(store: ^Store, fields: []Field) -> Type {
})
}
// Generated structs are nominal per comptime type-expression specialization.
// The checker owns canonicalization; this routine deliberately creates a fresh node.
struct_generated :: proc(store: ^Store, fields: []Field) -> Type {
start := u32(len(store.fields))
append(&store.fields, ..fields)
id := DYNAMIC_START+Type(len(store.nodes))
append(&store.nodes, Node{
kind=.Struct,
field_start=start,
field_count=u32(len(fields)),
declared=true,
})
return id
}
variant_id :: proc(store: ^Store, name: u32, payload: Type) -> (u16, bool) {
for variant in store.variants {
if variant.name == name && variant.payload == payload {