reflection foundation, tuples, debug.print

This commit is contained in:
2026-07-14 23:45:30 +02:00
parent 267947e79d
commit 0b2055d64b
24 changed files with 126598 additions and 115394 deletions
+3
View File
@@ -72,6 +72,7 @@ Expr_Kind :: enum u8 {
Array,
None,
Undefined,
Inference_Hole,
Type,
Name,
Enum_Literal,
@@ -121,6 +122,7 @@ Expr :: struct {
diagnostic: source.Diagnostic_Id,
parenthesized: bool,
intrinsic: bool,
tuple: bool,
kind: Expr_Kind,
}
@@ -170,6 +172,7 @@ Stmt :: struct {
immutable: bool,
value_control_flow: bool,
pointer_capture: bool,
inline: bool,
error_only: bool,
// Assignments store the lvalue in `target`, the right-hand side in `expr`,
// and the source operator in `assignment_op`. `Set` is ordinary `=`;
File diff suppressed because it is too large Load Diff
+476 -2
View File
@@ -7,20 +7,24 @@ import "../symbol"
import "../types"
import "base:intrinsics"
import "core:fmt"
import "core:math"
import "core:mem"
import "core:strings"
COMPTIME_EVAL_QUOTA :: 100_000
Comptime_Value_Kind :: enum u8 {
Integer,
Type,
String,
}
Comptime_Value :: struct {
name: symbol.Id,
type: types.Type,
value: i128,
text: string,
kind: Comptime_Value_Kind,
}
@@ -57,6 +61,15 @@ current_comptime_value :: proc(checker: ^Checker, name: symbol.Id) -> (Comptime_
return find_comptime_value(checker.current_comptime_values, name)
}
current_static_binding :: proc(checker: ^Checker, name: symbol.Id) -> (Static_Binding, bool) {
for index := len(checker.static_bindings)-1; index >= 0; index -= 1 {
if checker.static_bindings[index].name == name {
return checker.static_bindings[index], true
}
}
return {}, false
}
current_comptime_type :: proc(checker: ^Checker, name: symbol.Id) -> (types.Type, bool) {
if value, ok := current_comptime_value(checker, name); ok && value.kind == .Type {
return value.type, true
@@ -71,7 +84,8 @@ comptime_values_equal :: proc(left, right: []Comptime_Value) -> bool {
for value, index in left {
other := right[index]
if value.name != other.name || value.kind != other.kind || !types.equal(value.type, other.type) ||
(value.kind == .Integer && value.value != other.value) {
(value.kind == .Integer && value.value != other.value) ||
(value.kind == .String && value.text != other.text) {
return false
}
}
@@ -337,8 +351,22 @@ ct_state_make :: proc(
} 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)
} else if value.kind == .String {
string_id := u64(0)
for text, index in checker.ast_module.strings {
if text == value.text {
string_id = u64(index)
break
}
}
id := ct_add_value(&state, Ct_Value{kind=.String, type=value.type, index=string_id})
ct_bind_value(&state, value.name, value.type, id, false)
}
}
for binding in checker.static_bindings {
id := ct_clone_graph(&state, &checker.static_state, binding.value)
ct_bind_value(&state, binding.name, binding.type, id, false)
}
return state
}
@@ -543,6 +571,12 @@ ct_coerce_value :: proc(state: ^Ct_State, id: Ct_Value_Id, expected: types.Type,
return id, true
}
store := &state.checker.module.types
if value.kind == .String {
if item, ok := types.node(store, expected); ok && item.kind == .Slice && !item.mutable && item.child == types.U8 {
value.type = expected
return ct_add_value(state, value), true
}
}
if value.kind == .Pointer && types.can_weaken_pointer(value.type, expected, store) {
value.type = expected
return ct_add_value(state, value), true
@@ -881,6 +915,50 @@ ct_materialize_value :: proc(
return invalid_hir_expr(checker, span, source.INVALID_DIAGNOSTIC, value.type)
}
ct_undefined_value :: proc(state: ^Ct_State, value_type: types.Type, depth := 0) -> (Ct_Value_Id, bool) {
if depth > 64 || !types.is_valid(value_type) {
return INVALID_CT_VALUE, false
}
store := &state.checker.module.types
if types.is_concrete_integer(value_type) || types.is_enum(value_type, store) {
return ct_add_value(state, Ct_Value{kind=.Integer, type=value_type}), true
}
if types.is_bool(value_type) {
return ct_add_value(state, Ct_Value{kind=.Bool, type=value_type}), true
}
if types.is_float(value_type, state.checker.target) {
return ct_add_value(state, Ct_Value{kind=.Float, type=value_type}), true
}
item, ok := types.node(store, value_type)
if !ok {
return INVALID_CT_VALUE, false
}
if item.kind == .Array {
children := make([]Ct_Value_Id, int(item.count), state.checker.allocator)
defer delete(children, state.checker.allocator)
for &child in children {
child, ok = ct_undefined_value(state, item.child, depth+1)
if !ok { return INVALID_CT_VALUE, false }
}
start := u32(len(state.children))
append(&state.children, ..children)
return ct_add_value(state, Ct_Value{kind=.Array, type=value_type, start=start, count=u32(len(children))}), true
}
if item.kind == .Struct && !item.opaque {
fields := types.fields_for(store, value_type)
children := make([]Ct_Value_Id, len(fields), state.checker.allocator)
defer delete(children, state.checker.allocator)
for field, index in fields {
children[index], ok = ct_undefined_value(state, field.type, depth+1)
if !ok { return INVALID_CT_VALUE, false }
}
start := u32(len(state.children))
append(&state.children, ..children)
return ct_add_value(state, Ct_Value{kind=.Struct, type=value_type, start=start, count=u32(len(children))}), true
}
return INVALID_CT_VALUE, false
}
ct_eval_expr :: proc(
state: ^Ct_State,
expr_id: ast.Expr_Id,
@@ -921,6 +999,16 @@ 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
}
if value.kind == .String {
string_id := u64(0)
for text, index in checker.ast_module.strings {
if text == value.text {
string_id = u64(index)
break
}
}
return ct_add_value(state, Ct_Value{kind=.String, type=value.type, index=string_id}), ct_flow(.Normal), true
}
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 {
@@ -1164,7 +1252,12 @@ ct_eval_expr :: proc(
return value, ct_flow(.Normal), true
case .Slice:
return ct_eval_slice_expr(state, expr, depth+1)
case .Undefined, .Keyed:
case .Undefined:
if value, ok := ct_undefined_value(state, expected); ok {
return value, ct_flow(.Normal), true
}
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "undefined comptime value requires a concrete scalar or aggregate type")
case .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")
@@ -1237,9 +1330,49 @@ ct_eval_struct_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Ty
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)
} else if expr.tuple {
struct_type = types.INVALID
} else {
struct_type = types.resolve_alias(expected, store)
}
if expr.tuple {
resolved := types.is_valid(struct_type)
fields := types.fields_for(store, struct_type) if resolved else nil
if resolved {
item, item_ok := types.node(store, struct_type)
if !item_ok || item.kind != .Struct || !item.tuple || len(fields) != len(expr.args) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "invalid tuple construction")
}
}
values := make([]Ct_Value_Id, len(expr.args), checker.allocator)
inferred_fields := make([]types.Field, len(expr.args), checker.allocator)
defer {
delete(values, checker.allocator)
delete(inferred_fields, checker.allocator)
}
for arg, index in expr.args {
expected_field := fields[index].type if resolved else types.INVALID
value, flow, ok := ct_eval_expr(state, arg, expected_field, depth+1)
if !ok || flow.kind != .Normal {
return INVALID_CT_VALUE, flow, ok
}
if resolved {
value, ok = ct_coerce_value(state, value, expected_field, checker.ast_module.exprs[arg].span)
if !ok {
return INVALID_CT_VALUE, ct_flow(.Normal), false
}
} else {
inferred_fields[index].type = state.values[value].type
}
values[index] = value
}
if !resolved {
struct_type = types.struct_anonymous(store, inferred_fields, true)
}
start := u32(len(state.children))
append(&state.children, ..values)
return ct_add_value(state, Ct_Value{kind=.Struct, type=struct_type, start=start, count=u32(len(values))}), ct_flow(.Normal), true
}
if !types.is_record(struct_type, store) || types.is_opaque_struct(struct_type, store) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unknown or opaque record type '%s'", symbol_text(checker, expr.name))
}
@@ -1502,6 +1635,30 @@ ct_eval_field_value :: proc(state: ^Ct_State, base_id: Ct_Value_Id, name: symbol
return children[index], ct_flow(.Normal), true
}
ct_eval_tuple_field_value :: proc(state: ^Ct_State, base_id: Ct_Value_Id, index: u64, span: source.Span) -> (Ct_Value_Id, Ct_Flow, bool) {
if base_id == INVALID_CT_VALUE || int(base_id) >= len(state.values) {
return INVALID_CT_VALUE, ct_flow(.Normal), false
}
base := state.values[base_id]
base_type := base.type
if base.kind == .Pointer {
place, child, _ := ct_pointer_place(state, base)
field_index, field, ok := find_tuple_field(state.checker, child, index)
if place == INVALID_CT_PLACE || !ok {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "tuple field index is out of bounds")
}
field_place := ct_extend_place(state, place, Ct_Path_Elem{kind=.Field, index=u32(field_index)}, field.type, false)
value, value_ok := ct_place_get(state, field_place)
return value, ct_flow(.Normal), value_ok
}
field_index, _, ok := find_tuple_field(state.checker, base_type, index)
children := ct_child_slice(state, base)
if !ok || field_index < 0 || field_index >= len(children) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "tuple field index is out of bounds")
}
return children[field_index], ct_flow(.Normal), true
}
ct_eval_index_value :: proc(state: ^Ct_State, base_id: Ct_Value_Id, index: int, span: source.Span) -> (Ct_Value_Id, Ct_Flow, bool) {
checker := state.checker
if base_id == INVALID_CT_VALUE || int(base_id) >= len(state.values) {
@@ -2045,6 +2202,179 @@ ct_scalar_cast :: proc(state: ^Ct_State, id: Ct_Value_Id, target: types.Type, sp
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "scalar cast requires numeric scalar types")
}
intern_comptime_string :: proc(checker: ^Checker, text: string) -> u64 {
string_id := u64(len(checker.ast_module.strings))
for value, index in checker.ast_module.strings {
if value == text {
string_id = u64(index)
break
}
}
if string_id == u64(len(checker.ast_module.strings)) {
append(&checker.ast_module.strings, strings.clone(text, checker.ast_module.allocator))
append(&checker.module.strings, strings.clone(text, checker.allocator))
}
return string_id
}
ct_reflection_string :: proc(state: ^Ct_State, text: string) -> Ct_Value_Id {
checker := state.checker
string_id := intern_comptime_string(checker, text)
return ct_add_value(state, Ct_Value{
kind=.String,
type=types.slice(&checker.module.types, types.U8, false),
index=string_id,
})
}
ct_value_bytes :: proc(state: ^Ct_State, id: Ct_Value_Id) -> (string, bool) {
if id == INVALID_CT_VALUE || int(id) >= len(state.values) {
return "", false
}
value := state.values[id]
if value.kind == .String {
if value.index < u64(len(state.checker.ast_module.strings)) {
return state.checker.ast_module.strings[value.index], true
}
return "", false
}
item, ok := types.container(value.type, &state.checker.module.types)
if !ok || item.child != types.U8 || item.mutable ||
(value.kind != .Array && value.kind != .Slice) {
return "", false
}
count := int(value.count)
bytes := make([]u8, count, state.checker.allocator)
defer delete(bytes, state.checker.allocator)
for index in 0..<count {
child := INVALID_CT_VALUE
if value.kind == .Array {
children := ct_child_slice(state, value)
if index >= len(children) { return "", false }
child = children[index]
} else {
place, _, place_ok := ct_slice_element_place(state, value, index)
if !place_ok { return "", false }
child, place_ok = ct_place_get(state, place)
if !place_ok { return "", false }
}
integer, integer_ok := ct_integer_value(state, child)
if !integer_ok || integer < 0 || integer > 255 { return "", false }
bytes[index] = u8(integer)
}
string_id := intern_comptime_string(state.checker, string(bytes))
return state.checker.ast_module.strings[string_id], true
}
ct_struct_value :: proc(state: ^Ct_State, value_type: types.Type, children: []Ct_Value_Id) -> Ct_Value_Id {
start := u32(len(state.children))
append(&state.children, ..children)
return ct_add_value(state, Ct_Value{kind=.Struct, type=value_type, start=start, count=u32(len(children))})
}
ct_typeinfo_value :: proc(state: ^Ct_State, target: types.Type, span: source.Span) -> (Ct_Value_Id, Ct_Flow, bool) {
checker := state.checker
store := &checker.module.types
typeinfo_type := std_named_type(checker, "@std/meta", "TypeInfo")
fieldinfo_type := std_named_type(checker, "@std/meta", "FieldInfo")
recordinfo_type := std_named_type(checker, "@std/meta", "RecordInfo")
layout_type := std_named_type(checker, "@std/meta", "Layout")
if !types.is_valid(typeinfo_type) || !types.is_valid(fieldinfo_type) ||
!types.is_valid(recordinfo_type) || !types.is_valid(layout_type) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(
state, .Not_Comptime, span,
"typeinfo! requires importing @std/meta",
)
}
resolved := types.resolve_alias(target, store)
item, item_ok := types.node(store, resolved)
tag := "invalid"
if !item_ok {
if types.is_void(resolved) { tag = "void" }
else if types.is_anyopaque(resolved) { tag = "anyopaque" }
else if types.is_bool(resolved) { tag = "bool" }
else if types.is_concrete_integer(resolved) { tag = "integer" }
else if types.is_float(resolved, checker.target) { tag = "float" }
} else {
#partial switch item.kind {
case .Array: tag = "array"
case .Pointer: tag = "pointer"
case .Slice: tag = "slice"
case .Range: tag = "range"
case .Optional: tag = "optional"
case .Function: tag = "function"
case .Enum: tag = "enum"
case .Struct: tag = "record"
case .Union: tag = "union"
case .Fallible: tag = "fallible"
case .Distinct: tag = "distinct"
case .Alias:
tag = "invalid"
case:
tag = "invalid"
}
}
variant_index, _, variant_ok := find_struct_field(checker, typeinfo_type, symbol.intern(checker.symbols, tag))
if !variant_ok {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "@std/meta TypeInfo is malformed")
}
if tag != "record" {
start := u32(len(state.children))
return ct_add_value(state, Ct_Value{
kind=.Struct, type=typeinfo_type, start=start, count=0, active=i64(variant_index),
}), ct_flow(.Normal), true
}
fields := types.fields_for(store, resolved)
field_values := make([]Ct_Value_Id, len(fields), checker.allocator)
defer delete(field_values, checker.allocator)
for field, index in fields {
name := ""
if item.tuple {
name = fmt.aprintf("%d", index, allocator=checker.allocator)
} else {
name = symbol_text(checker, symbol.Id(field.name))
}
name_value := ct_reflection_string(state, name)
if item.tuple {
delete(name, checker.allocator)
}
type_value := ct_add_value(state, Ct_Value{kind=.Type, type=types.INVALID, index=u64(field.type)})
index_value := ct_add_value(state, Ct_Value{kind=.Integer, type=types.USIZE, integer=i128(index)})
children := []Ct_Value_Id{name_value, type_value, index_value}
field_values[index] = ct_struct_value(state, fieldinfo_type, children)
}
fields_start := u32(len(state.children))
append(&state.children, ..field_values)
fields_type := types.array(store, fieldinfo_type, u64(len(field_values)), false)
fields_value := ct_add_value(state, Ct_Value{
kind=.Array, type=fields_type, start=fields_start, count=u32(len(field_values)),
})
name_optional_type := types.optional(store, types.slice(store, types.U8, false))
record_name := ct_add_value(state, Ct_Value{kind=.None, type=name_optional_type})
if item.name != 0 {
name_value := ct_reflection_string(state, symbol_text(checker, symbol.Id(item.name)))
name_start := u32(len(state.children))
append(&state.children, name_value)
record_name = ct_add_value(state, Ct_Value{
kind=.Optional_Some, type=name_optional_type, start=name_start, count=1,
})
}
tuple_value := ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=1 if item.tuple else 0})
layout_name := symbol.intern(checker.symbols, "c" if item.c_layout else "auto")
layout_member, layout_ok := find_enum_member(checker, layout_type, layout_name)
if !layout_ok {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "@std/meta Layout is malformed")
}
layout_value := ct_add_value(state, Ct_Value{kind=.Integer, type=layout_type, integer=layout_member.value})
record_children := []Ct_Value_Id{record_name, fields_value, tuple_value, layout_value}
record_value := ct_struct_value(state, recordinfo_type, record_children)
payload_start := u32(len(state.children))
append(&state.children, record_value)
return ct_add_value(state, Ct_Value{
kind=.Struct, type=typeinfo_type, start=payload_start, count=1, active=i64(variant_index),
}), ct_flow(.Normal), true
}
ct_eval_call_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type, depth: int) -> (Ct_Value_Id, Ct_Flow, bool) {
checker := state.checker
if expr.left != ast.INVALID_EXPR {
@@ -2057,6 +2387,49 @@ ct_eval_call_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type
}
return ct_eval_template_call(state, ast.Function_Id(u32(state.values[callee].index)), expr.args, expr.span, expected, depth+1)
}
if is_intrinsic_call(checker, expr, "compile_error") {
message := "compile_error! requires one comptime string argument"
if len(expr.args) == 1 {
value, flow, ok := ct_eval_expr(state, expr.args[0], types.INVALID, depth+1)
if ok && flow.kind == .Normal {
if text, text_ok := ct_value_bytes(state, value); text_ok {
message = text
}
}
}
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, message)
}
if is_intrinsic_call(checker, expr, "field") {
if len(expr.args) != 2 {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "field! expects 2 arguments, got %d", len(expr.args))
}
base, flow, ok := ct_eval_expr(state, expr.args[0], types.INVALID, depth+1)
if !ok || flow.kind != .Normal {
return INVALID_CT_VALUE, flow, ok
}
name_value, name_flow, name_ok := ct_eval_expr(state, expr.args[1], types.INVALID, depth+1)
if !name_ok || name_flow.kind != .Normal {
return INVALID_CT_VALUE, name_flow, name_ok
}
name, text_ok := ct_value_bytes(state, name_value)
if !text_ok {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "field! name must be a comptime immutable byte string")
}
if index, numeric := canonical_decimal_index(name); numeric {
return ct_eval_tuple_field_value(state, base, index, expr.span)
}
return ct_eval_field_value(state, base, symbol.intern(checker.symbols, name), expr.span)
}
if is_intrinsic_call(checker, expr, "typeinfo") {
if len(expr.args) != 1 {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "typeinfo! expects 1 argument, got %d", len(expr.args))
}
target, ok := resolve_type_argument(checker, expr.args[0], state.pkg, state.file)
if !ok {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "typeinfo! argument must be a type")
}
return ct_typeinfo_value(state, target, expr.span)
}
if builtin := type_builtin_call(checker, expr); builtin != .None {
if len(expr.args) != 1 {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "%s! expects 1 argument, got %d", symbol_text(checker, expr.name), len(expr.args))
@@ -2234,6 +2607,107 @@ ct_clone_value :: proc(dst, src: ^Ct_State, id: Ct_Value_Id) -> Ct_Value_Id {
return ct_add_value(dst, value)
}
Ct_Clone_Context :: struct {
dst, src: ^Ct_State,
values: []Ct_Value_Id,
cells: []Ct_Cell_Id,
places: []Ct_Place_Id,
}
ct_clone_graph_value :: proc(ctx: ^Ct_Clone_Context, id: Ct_Value_Id) -> Ct_Value_Id {
if id == INVALID_CT_VALUE || int(id) >= len(ctx.src.values) {
return INVALID_CT_VALUE
}
if ctx.values[id] != INVALID_CT_VALUE {
return ctx.values[id]
}
value := ctx.src.values[id]
children := ct_child_slice(ctx.src, value)
value.start = 0
value.count = 0
dst_id := ct_add_value(ctx.dst, value)
ctx.values[id] = dst_id
if len(children) > 0 {
cloned := make([]Ct_Value_Id, len(children), ctx.dst.checker.allocator)
defer delete(cloned, ctx.dst.checker.allocator)
for child, index in children {
cloned[index] = ct_clone_graph_value(ctx, child)
}
start := u32(len(ctx.dst.children))
append(&ctx.dst.children, ..cloned)
ctx.dst.values[dst_id].start = start
ctx.dst.values[dst_id].count = u32(len(children))
}
if value.kind == .Pointer || value.kind == .Slice {
ctx.dst.values[dst_id].index = u64(ct_clone_graph_place(ctx, Ct_Place_Id(value.index)))
}
return dst_id
}
ct_clone_graph_cell :: proc(ctx: ^Ct_Clone_Context, id: Ct_Cell_Id) -> Ct_Cell_Id {
if id == INVALID_CT_CELL || int(id) >= len(ctx.src.cells) {
return INVALID_CT_CELL
}
if ctx.cells[id] != INVALID_CT_CELL {
return ctx.cells[id]
}
cell := ctx.src.cells[id]
cell.value = INVALID_CT_VALUE
dst_id := ct_cell_id(len(ctx.dst.cells))
append(&ctx.dst.cells, cell)
ctx.cells[id] = dst_id
ctx.dst.cells[dst_id].value = ct_clone_graph_value(ctx, ctx.src.cells[id].value)
return dst_id
}
ct_clone_graph_place :: proc(ctx: ^Ct_Clone_Context, id: Ct_Place_Id) -> Ct_Place_Id {
if id == INVALID_CT_PLACE || int(id) >= len(ctx.src.places) {
return INVALID_CT_PLACE
}
if ctx.places[id] != INVALID_CT_PLACE {
return ctx.places[id]
}
place := ctx.src.places[id]
path := ct_place_path(ctx.src, place)
place.start = u32(len(ctx.dst.paths))
place.count = u32(len(path))
append(&ctx.dst.paths, ..path)
place.cell = INVALID_CT_CELL
dst_id := Ct_Place_Id(len(ctx.dst.places))
append(&ctx.dst.places, place)
ctx.places[id] = dst_id
ctx.dst.places[dst_id].cell = ct_clone_graph_cell(ctx, ctx.src.places[id].cell)
return dst_id
}
ct_clone_graph :: proc(dst, src: ^Ct_State, id: Ct_Value_Id) -> Ct_Value_Id {
ctx := Ct_Clone_Context{
dst=dst,
src=src,
values=make([]Ct_Value_Id, len(src.values), dst.checker.allocator),
cells=make([]Ct_Cell_Id, len(src.cells), dst.checker.allocator),
places=make([]Ct_Place_Id, len(src.places), dst.checker.allocator),
}
defer {
delete(ctx.values, dst.checker.allocator)
delete(ctx.cells, dst.checker.allocator)
delete(ctx.places, dst.checker.allocator)
}
for &value in ctx.values { value = INVALID_CT_VALUE }
for &cell in ctx.cells { cell = INVALID_CT_CELL }
for &place in ctx.places { place = INVALID_CT_PLACE }
return ct_clone_graph_value(&ctx, id)
}
store_static_binding :: proc(checker: ^Checker, source: ^Ct_State, id: Ct_Value_Id, name: symbol.Id) -> Static_Binding {
value := ct_clone_graph(&checker.static_state, source, id)
value_type := types.INVALID
if value != INVALID_CT_VALUE && int(value) < len(checker.static_state.values) {
value_type = checker.static_state.values[value].type
}
return Static_Binding{name=name, type=value_type, value=value}
}
ct_eval_try_expr :: proc(state: ^Ct_State, expr: ast.Expr, depth: int) -> (Ct_Value_Id, Ct_Flow, bool) {
if state.defer_depth > 0 {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "cannot 'try' inside a 'defer'")
+25 -3
View File
@@ -630,13 +630,18 @@ lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instructi
lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
stack := state.expr_stack
state.expr_stack = nil
clear_dynamic_array(&stack)
defer {
for frame in stack {
delete(frame.args, state.allocator)
}
clear_dynamic_array(&stack)
state.expr_stack = stack
if state.expr_stack == nil {
state.expr_stack = stack
} else {
delete(stack)
}
}
append(&stack, Lower_Expr_Frame{expr=expr_id})
last := ir.INVALID_INSTRUCTION
@@ -1656,12 +1661,29 @@ append_injected_main :: proc(module: ^ir.Module, hir_module: ^hir.Module, alloca
b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
main_function := &hir_module.functions[main_index]
param_index, param_ok := hir.index(main_function.params[0], hir.INVALID_LOCAL, len(main_function.locals))
if !param_ok {
return
}
init_args := make([]ir.Instruction_Id, 1, allocator)
init_args[0] = provider_call
init_value := ir.instruction_id(len(instructions))
append(&instructions, ir.Instruction{
op=.Aggregate,
type=main_function.locals[param_index].type,
args=init_args,
target=ir.INVALID_REF,
a=ir.INVALID_INSTRUCTION,
b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
args := make([]ir.Instruction_Id, 1, allocator)
args[0] = provider_call
args[0] = init_value
main_call := ir.instruction_id(len(instructions))
append(&instructions, ir.Instruction{
op=.Call,
type=hir_module.functions[main_index].result,
type=main_function.result,
args=args,
target=ir.function_ref(ir.Function_Id(main_index)),
a=ir.INVALID_INSTRUCTION,
+186 -19
View File
@@ -497,7 +497,17 @@ parse_call_args :: proc(parser: ^Parser, nesting: int) -> ([]ast.Expr_Id, token.
args.allocator = parser.module.allocator
skip_newlines(parser)
for current(parser).kind != .Right_Paren && current(parser).kind != .Eof {
append(&args, parse_expression_bp(parser, 0, nesting+1))
if hole, ok := allow(parser, .Underscore); ok {
append(&args, add_expr(parser, ast.Expr{
kind=.Inference_Hole,
span=hole.span,
left=ast.INVALID_EXPR,
right=ast.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
}))
} else {
append(&args, parse_expression_bp(parser, 0, nesting+1))
}
skip_newlines(parser)
if _, ok := allow(parser, .Comma); ok {
skip_newlines(parser)
@@ -612,6 +622,74 @@ parse_keyed_initializers :: proc(
return args[:], right_brace
}
parse_positional_initializers :: proc(
parser: ^Parser,
left_brace: token.Token,
nesting: int,
close_message: string,
) -> ([]ast.Expr_Id, token.Token) {
parser.delimiter_depth += 1
defer parser.delimiter_depth -= 1
args: [dynamic]ast.Expr_Id
args.allocator = parser.module.allocator
skip_newlines(parser)
for current(parser).kind != .Right_Brace && current(parser).kind != .Eof {
append(&args, parse_expression_bp(parser, 0, nesting+1))
skip_newlines(parser)
if _, ok := allow(parser, .Comma); ok {
skip_newlines(parser)
continue
}
break
}
right_brace, ok := allow(parser, .Right_Brace)
if !ok {
source.add(parser.diagnostics, current(parser).span, close_message)
right_brace = left_brace
}
return args[:], right_brace
}
brace_starts_tuple :: proc(parser: ^Parser) -> bool {
if current(parser).kind != .Left_Brace {
return false
}
depth := 0
for cursor := parser.cursor; cursor < len(parser.tokens.items); cursor += 1 {
#partial switch parser.tokens.items[cursor].kind {
case .Left_Brace, .Left_Paren, .Left_Bracket:
depth += 1
case .Right_Brace, .Right_Paren, .Right_Bracket:
depth -= 1
if depth == 0 {
return cursor == parser.cursor+1
}
case .Comma:
if depth == 1 {
return true
}
case .Eof:
return false
case:
}
}
return false
}
parse_tuple_literal :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
left_brace := advance(parser)
args, right_brace := parse_positional_initializers(parser, left_brace, nesting, "expected '}' after tuple literal")
return add_expr(parser, ast.Expr{
kind=.Struct_Literal,
span=span_from(left_brace.span, right_brace.span),
args=args,
tuple=true,
left=ast.INVALID_EXPR,
right=ast.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
parse_struct_literal :: proc(
parser: ^Parser,
qualifier: symbol.Id,
@@ -619,13 +697,24 @@ parse_struct_literal :: proc(
nesting: int,
) -> ast.Expr_Id {
left_brace := advance(parser)
args, right_brace := parse_keyed_initializers(parser, left_brace, nesting, "expected '}' after struct literal")
skip_newlines(parser)
keyed_start := (current(parser).kind == .Identifier || token.is_keyword(current(parser).kind)) &&
(peek(parser).kind == .Equal || peek(parser).kind == .Right_Brace || token.is_keyword(current(parser).kind))
positional := current(parser).kind == .Right_Brace || !keyed_start
args: []ast.Expr_Id
right_brace: token.Token
if positional {
args, right_brace = parse_positional_initializers(parser, left_brace, nesting, "expected '}' after tuple literal")
} else {
args, right_brace = parse_keyed_initializers(parser, left_brace, nesting, "expected '}' after struct literal")
}
return add_expr(parser, ast.Expr{
kind=.Struct_Literal,
span=source.Span{file=name.span.file, start=first.span.start, end=right_brace.span.end},
qualifier=qualifier,
name=name.symbol,
args=args[:],
tuple=positional,
left=ast.INVALID_EXPR,
right=ast.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
@@ -636,7 +725,8 @@ 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") {
tuple := false
if !parse_record_body(parser, &fields, "expected '{' after anonymous struct type", tuple_result=&tuple) {
delete(fields)
return invalid_expr(parser, start.span, "invalid anonymous struct type")
}
@@ -649,6 +739,7 @@ parse_anonymous_struct_type_expr :: proc(parser: ^Parser) -> ast.Expr_Id {
kind=.Anonymous_Struct_Type,
span=span_from(start.span, end.span),
integer=u64(field_start)<<32 | u64(field_count),
tuple=tuple,
left=ast.INVALID_EXPR,
right=ast.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
@@ -837,6 +928,8 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
})
}
return parse_array_literal(parser, nesting)
case .Left_Brace:
return parse_tuple_literal(parser, nesting)
case .Dot:
start := advance(parser)
member, member_ok := parse_member_name(parser)
@@ -901,7 +994,8 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
first := advance(parser)
name := first
qualifier := symbol.INVALID
if _, ok := allow(parser, .Dot); ok {
if current(parser).kind == .Dot && peek(parser).kind != .Integer {
advance(parser)
member, member_ok := parse_member_name(parser)
if !member_ok {
return invalid_expr(parser, current(parser).span, "expected a package member after '.'")
@@ -914,11 +1008,22 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
call := parse_call(parser, qualifier, first, name, nesting, intrinsic)
if !intrinsic && 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")
skip_newlines(parser)
keyed_start := (current(parser).kind == .Identifier || token.is_keyword(current(parser).kind)) &&
(peek(parser).kind == .Equal || peek(parser).kind == .Right_Brace || token.is_keyword(current(parser).kind))
positional := current(parser).kind == .Right_Brace || !keyed_start
args: []ast.Expr_Id
right_brace: token.Token
if positional {
args, right_brace = parse_positional_initializers(parser, left_brace, nesting, "expected '}' after tuple literal")
} else {
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,
tuple=positional,
left=call,
right=ast.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
@@ -1113,6 +1218,26 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int
}
if current(parser).kind == .Dot {
advance(parser)
if current(parser).kind == .Integer {
field := advance(parser)
text := token_text(parser, field)
index, index_ok := parse_integer_magnitude(text)
left_expr := parser.module.exprs[left]
if !index_ok || (len(text) > 1 && text[0] == '0') {
left = invalid_expr(parser, field.span, "tuple field indices must be canonical decimal integers")
} else {
left = add_expr(parser, ast.Expr{
kind=.Field,
span=span_from(left_expr.span, field.span),
name=symbol.INVALID,
integer=index,
left=left,
right=ast.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
continue
}
field, field_ok := parse_member_name(parser)
if !field_ok {
left = invalid_expr(parser, field.span, "expected a field name after '.'")
@@ -1544,6 +1669,14 @@ parse_value_control_flow :: proc(parser: ^Parser) -> (ast.Stmt_Id, bool) {
}
parse_statement :: proc(parser: ^Parser) -> ast.Stmt_Id {
if current(parser).kind == .Identifier && token_text(parser, current(parser)) == "inline" &&
peek(parser).kind == .Keyword_For {
start := advance(parser)
id := parse_for(parser)
parser.module.statements[id].inline = true
parser.module.statements[id].span = span_from(start.span, parser.module.statements[id].span)
return id
}
if current(parser).kind == .Keyword_Return {
return parse_return(parser)
}
@@ -1623,7 +1756,7 @@ parse_statement :: proc(parser: ^Parser) -> ast.Stmt_Id {
}
// A `{` on the right is a value block: parse its statements now; the
// checker turns its final `yield` into the declared/assigned value.
if current(parser).kind == .Left_Brace {
if current(parser).kind == .Left_Brace && !brace_starts_tuple(parser) {
brace := current(parser)
body := parse_block(parser)
id := ast.stmt_id(len(parser.module.statements))
@@ -1697,7 +1830,7 @@ parse_statement :: proc(parser: ^Parser) -> ast.Stmt_Id {
return id
}
// A value block assigned to a complex target (`a[i] = { ... }`, `p.f = { ... }`).
if current(parser).kind == .Left_Brace {
if current(parser).kind == .Left_Brace && !brace_starts_tuple(parser) {
brace := current(parser)
body := parse_block(parser)
id := ast.stmt_id(len(parser.module.statements))
@@ -2339,26 +2472,51 @@ parse_record_body :: proc(
expected_open: string,
allow_anonymous_struct_payload := false,
allow_keyword_names := false,
tuple_result: ^bool = nil,
) -> bool {
if _, ok := allow(parser, .Left_Brace); !ok {
source.add(parser.diagnostics, current(parser).span, expected_open)
return false
}
skip_newlines(parser)
mode := 0 // 0 unknown, 1 named, 2 tuple
for current(parser).kind != .Right_Brace && current(parser).kind != .Eof {
field_name, field_ok := parse_member_name(parser, allow_keyword_names)
if !field_ok {
source.add(parser.diagnostics, current(parser).span, "expected a struct field name")
for current(parser).kind != .Newline &&
current(parser).kind != .Right_Brace &&
current(parser).kind != .Eof {
advance(parser)
unnamed := false
if !allow_keyword_names {
if current(parser).kind == .Identifier {
next := peek(parser).kind
unnamed = next == .Comma || next == .Newline || next == .Right_Brace ||
next == .Dot || next == .Left_Paren
} else {
unnamed = is_type_token(current(parser).kind)
}
skip_newlines(parser)
continue
}
field_type := parse_record_field_type(parser, allow_anonymous_struct_payload)
append(fields, types.Field{name=u32(field_name.symbol), type=field_type})
if unnamed {
if mode == 1 {
source.add(parser.diagnostics, current(parser).span, "struct fields cannot mix named and unnamed forms")
}
mode = 2
field_type := parse_type(parser)
append(fields, types.Field{name=0, type=field_type})
} else {
if mode == 2 {
source.add(parser.diagnostics, current(parser).span, "struct fields cannot mix named and unnamed forms")
}
mode = 1
field_name, field_ok := parse_member_name(parser, allow_keyword_names)
if !field_ok {
source.add(parser.diagnostics, current(parser).span, "expected a struct field name or tuple element type")
for current(parser).kind != .Newline &&
current(parser).kind != .Right_Brace &&
current(parser).kind != .Eof {
advance(parser)
}
skip_newlines(parser)
continue
}
field_type := parse_record_field_type(parser, allow_anonymous_struct_payload)
append(fields, types.Field{name=u32(field_name.symbol), type=field_type})
}
if _, ok := allow(parser, .Comma); ok {
skip_newlines(parser)
continue
@@ -2368,6 +2526,9 @@ parse_record_body :: proc(
if _, ok := allow(parser, .Right_Brace); !ok {
source.add(parser.diagnostics, current(parser).span, "expected '}' after struct fields")
}
if tuple_result != nil {
tuple_result^ = mode == 2
}
return true
}
@@ -2459,17 +2620,23 @@ parse_struct :: proc(parser: ^Parser, name: token.Token, c_layout, file_hidden:
fields.allocator = parser.module.allocator
defer delete(fields)
allow_anonymous_struct_payload := is_union && (inferred_tag || types.is_valid(declared_tag))
tuple := false
_ = parse_record_body(
parser,
&fields,
"expected '{' after struct fields",
allow_anonymous_struct_payload,
allow_anonymous_struct_payload,
&tuple,
)
if tuple && (c_layout || is_union) {
source.add(parser.diagnostics, start.span, "unnamed fields are only supported by native structs")
tuple = false
}
if is_union && (inferred_tag || types.is_valid(declared_tag)) {
tag = synthesize_union_tag(parser, fields[:])
}
if !types.define_record(&parser.module.type_store, id, fields[:], c_layout, false, is_union, tag=tag, declared_tag=declared_tag) {
if !types.define_record(&parser.module.type_store, id, fields[:], c_layout, false, is_union, tag=tag, declared_tag=declared_tag, tuple=tuple) {
source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name))
}
_ = finish_statement(parser)
+8 -3
View File
@@ -101,6 +101,7 @@ Node :: struct {
c_abi: bool,
variadic: bool,
c_layout: bool,
tuple: bool,
opaque: bool,
declared: bool,
file_hidden: bool,
@@ -273,6 +274,7 @@ define_record :: proc(
explicit_alignment: u32 = 0,
tag: Type = INVALID,
declared_tag: Type = INVALID,
tuple := false,
) -> bool {
existing, ok := node(store, id)
if !ok || (existing.kind != .Named && existing.kind != .Struct && existing.kind != .Union) ||
@@ -282,6 +284,7 @@ define_record :: proc(
index := int(id-DYNAMIC_START)
store.nodes[index].kind = .Union if is_union else .Struct
store.nodes[index].c_layout = c_layout
store.nodes[index].tuple = tuple
store.nodes[index].opaque = opaque
store.nodes[index].declared = true
store.nodes[index].explicit_size = explicit_size
@@ -342,10 +345,10 @@ anonymous_struct_fields_equal :: proc(store: ^Store, item: Node, fields: []Field
return true
}
struct_anonymous :: proc(store: ^Store, fields: []Field) -> Type {
struct_anonymous :: proc(store: ^Store, fields: []Field, tuple := false) -> Type {
for existing, index in store.nodes {
if existing.kind == .Struct && existing.name == 0 && existing.declared &&
!existing.c_layout && !existing.opaque &&
!existing.c_layout && existing.tuple == tuple && !existing.opaque &&
anonymous_struct_fields_equal(store, existing, fields) {
return DYNAMIC_START+Type(index)
}
@@ -356,13 +359,14 @@ struct_anonymous :: proc(store: ^Store, fields: []Field) -> Type {
kind=.Struct,
field_start=start,
field_count=u32(len(fields)),
tuple=tuple,
declared=true,
})
}
// 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 {
struct_generated :: proc(store: ^Store, fields: []Field, tuple := false) -> Type {
start := u32(len(store.fields))
append(&store.fields, ..fields)
id := DYNAMIC_START+Type(len(store.nodes))
@@ -370,6 +374,7 @@ struct_generated :: proc(store: ^Store, fields: []Field) -> Type {
kind=.Struct,
field_start=start,
field_count=u32(len(fields)),
tuple=tuple,
declared=true,
})
return id