richer formatting

This commit is contained in:
2026-07-15 20:38:54 +02:00
parent 1165cfb7c0
commit c4fa8e930f
9 changed files with 807 additions and 99 deletions
+160 -49
View File
@@ -232,6 +232,8 @@ Checker :: struct {
current_comptime_values: []Comptime_Value,
static_state: Ct_State,
static_bindings: [dynamic]Static_Binding,
comptime_keys: [dynamic]string,
comptime_static_values: [dynamic]Ct_Value_Id,
inline_context: [dynamic]Inline_Expansion,
type_factories: [dynamic]Type_Factory_Entry,
generated_types: [dynamic]Generated_Type_Entry,
@@ -528,7 +530,14 @@ static_field_value :: proc(checker: ^Checker, base, name: symbol.Id) -> (Ct_Valu
if !ok || binding.value == INVALID_CT_VALUE || int(binding.value) >= len(checker.static_state.values) {
return {}, false
}
value := checker.static_state.values[binding.value]
return persistent_field_value(checker, binding.value, name)
}
persistent_field_value :: proc(checker: ^Checker, root: Ct_Value_Id, name: symbol.Id) -> (Ct_Value, bool) {
if root == INVALID_CT_VALUE || int(root) >= len(checker.static_state.values) {
return {}, false
}
value := checker.static_state.values[root]
index, _, found := find_struct_field(checker, value.type, name)
children := ct_child_slice(&checker.static_state, value)
if !found || index < 0 || index >= len(children) || children[index] == INVALID_CT_VALUE ||
@@ -1705,7 +1714,9 @@ explicit_comptime_argument_valid :: proc(
if types.is_concrete_integer(type_from_syntax(checker, param.type, function.pkg, function.file)) {
return eval_integer_constant_in_context(checker, arg, pkg, file).kind == .Value
}
return false
declared := type_from_syntax(checker, param.type, function.pkg, function.file)
_, ok := eval_static_comptime_value(checker, param.name, arg, declared, pkg, file)
return ok
}
search_call_mappings :: proc(search: ^Call_Mapping_Search, param_index, source_index: int) {
@@ -1966,13 +1977,15 @@ bind_inferred_comptime :: proc(
if matches {
if existing.kind == .Type {
matches = types.equal(types.resolve_alias(existing.type, &checker.module.types), types.resolve_alias(value.type, &checker.module.types))
} else if existing.kind == .Static {
matches = existing.fingerprint == value.fingerprint && existing.key == value.key && types.equal(existing.type, value.type)
} else {
matches = existing.value == value.value && types.equal(existing.type, value.type)
}
}
if !matches && diagnose {
left := type_label(checker, existing.type) if existing.kind == .Type else fmt.aprintf("%d", existing.value, allocator=checker.allocator)
right := type_label(checker, value.type) if value.kind == .Type else fmt.aprintf("%d", value.value, allocator=checker.allocator)
left := type_label(checker, existing.type) if existing.kind == .Type else fmt.aprintf("<comptime value>", allocator=checker.allocator) if existing.kind == .Static else fmt.aprintf("%d", existing.value, allocator=checker.allocator)
right := type_label(checker, value.type) if value.kind == .Type else fmt.aprintf("<comptime value>", allocator=checker.allocator) if value.kind == .Static else fmt.aprintf("%d", value.value, allocator=checker.allocator)
source.addf(checker.diagnostics, span, "conflicting inference for comptime parameter '%s': %s and %s", symbol_text(checker, name), left, right)
if existing.kind != .Type {
delete(left, checker.allocator)
@@ -2221,9 +2234,12 @@ infer_call_comptime_values :: proc(
} else if is_comptime_string_param(checker, param, function) {
values[ordinal].kind = .String
values[ordinal].type = type_from_syntax(checker, param.type, function.pkg, function.file)
} else {
} else if types.is_concrete_integer(type_from_syntax(checker, param.type, function.pkg, function.file)) {
values[ordinal].kind = .Integer
values[ordinal].type = type_from_syntax(checker, param.type, function.pkg, function.file)
} else {
values[ordinal].kind = .Static
values[ordinal].type = type_from_syntax(checker, param.type, function.pkg, function.file)
}
ordinal += 1
}
@@ -2287,7 +2303,7 @@ infer_call_comptime_values :: proc(
kind=.String,
}
bound[binding_index] = true
} else {
} else if types.is_concrete_integer(type_from_syntax(checker, param.type, function.pkg, function.file)) {
declared := type_from_syntax(checker, param.type, function.pkg, function.file)
constant := eval_integer_constant_in_context(checker, arg_id, pkg, file)
if constant.kind != .Value {
@@ -2321,6 +2337,32 @@ infer_call_comptime_values :: proc(
}
values[binding_index] = Comptime_Value{name=param.name, type=declared, value=constant.value, kind=.Integer}
bound[binding_index] = true
} else {
declared := type_from_syntax(checker, param.type, function.pkg, function.file)
available: [dynamic]Comptime_Value
available.allocator = checker.allocator
append(&available, ..checker.current_comptime_values)
for prior, prior_index in values[:binding_index] {
if bound[prior_index] {
append(&available, prior)
}
}
value, value_ok := eval_static_comptime_value(
checker, param.name, arg_id, declared, pkg, file, available[:], diagnose,
)
delete(available)
if !value_ok {
if failure != nil && len(failure^) == 0 {
failure^ = fmt.aprintf(
"argument %d for comptime parameter '%s' has no stable comptime identity",
source_index+1, symbol_text(checker, param.name), allocator=checker.allocator,
)
}
matched = false
continue
}
values[binding_index] = value
bound[binding_index] = true
}
}
all_bound := true
@@ -2768,48 +2810,56 @@ collect_comptime_values :: proc(
continue
}
declared := type_from_syntax(checker, param.type, function.pkg, function.file)
if !types.is_concrete_integer(declared) {
if diagnose {
source.addf(
checker.diagnostics,
param.span,
"comptime parameter '%s' requires a concrete integer type",
symbol_text(checker, param.name),
)
if types.is_concrete_integer(declared) {
constant := Constant{kind = .Not_Constant}
if index < len(args) {
constant = eval_integer_constant_in_context(checker, args[index], pkg, file, values=extra_values)
}
if constant.kind != .Value {
if diagnose {
source.addf(
checker.diagnostics,
span,
"argument for comptime parameter '%s' must be a compile-time integer expression",
symbol_text(checker, param.name),
)
}
ok = false
continue
}
if !fits_integer_type(constant.value, declared, checker.target) {
if diagnose {
source.addf(
checker.diagnostics,
span,
"integer constant %d does not fit in %s",
constant.value,
types.name(declared),
)
}
ok = false
continue
}
append(&values, Comptime_Value{name=param.name, type=declared, value=constant.value})
continue
}
if index >= len(args) || args[index] == ast.INVALID_EXPR {
ok = false
continue
}
constant := Constant{kind = .Not_Constant}
if index < len(args) {
constant = eval_integer_constant_in_context(checker, args[index], pkg, file, values=extra_values)
}
if constant.kind != .Value {
if diagnose {
source.addf(
checker.diagnostics,
span,
"argument for comptime parameter '%s' must be a compile-time integer expression",
symbol_text(checker, param.name),
)
}
available: [dynamic]Comptime_Value
available.allocator = checker.allocator
append(&available, ..extra_values)
append(&available, ..values[:])
value, value_ok := eval_static_comptime_value(
checker, param.name, args[index], declared, pkg, file, available[:], diagnose,
)
delete(available)
if !value_ok {
ok = false
continue
}
if !fits_integer_type(constant.value, declared, checker.target) {
if diagnose {
source.addf(
checker.diagnostics,
span,
"integer constant %d does not fit in %s",
constant.value,
types.name(declared),
)
}
ok = false
continue
}
append(&values, Comptime_Value{name=param.name, type=declared, value=constant.value})
append(&values, value)
}
if !ok {
delete(values)
@@ -3111,12 +3161,11 @@ validate_declarations :: proc(checker: ^Checker) {
)
}
if !is_type_metatype_syntax(checker, param.type) &&
!types.is_concrete_integer(param_type) &&
!is_comptime_string_param(checker, param, function) {
!is_runtime_type(checker, param_type) && param_type != types.RANGE {
checker.template_diagnostics[function_id] = source.addf(
checker.diagnostics,
param.span,
"comptime parameter '%s' requires type, a concrete integer type, or immutable []u8",
"comptime parameter '%s' requires type or a concrete value type",
symbol_text(checker, param.name),
)
}
@@ -3503,9 +3552,10 @@ validate_meta_schema :: proc(checker: ^Checker) {
}
field_info := find(checker, meta_package, "FieldInfo")
record_info := find(checker, meta_package, "RecordInfo")
enum_info := find(checker, meta_package, "EnumInfo")
type_info := find(checker, meta_package, "TypeInfo")
layout := find(checker, meta_package, "Layout")
valid := types.is_valid(field_info) && types.is_valid(record_info) &&
valid := types.is_valid(field_info) && types.is_valid(record_info) && types.is_valid(enum_info) &&
types.is_valid(type_info) && types.is_valid(layout)
layout_item, layout_ok := types.node(&checker.module.types, layout)
layout_members := types.enum_members_for(&checker.module.types, layout)
@@ -3516,8 +3566,10 @@ validate_meta_schema :: proc(checker: ^Checker) {
}
field_item, field_ok := types.node(&checker.module.types, field_info)
record_item, record_ok := types.node(&checker.module.types, record_info)
enum_item, enum_ok := types.node(&checker.module.types, enum_info)
valid = valid && field_ok && field_item.kind == .Struct && !field_item.tuple && !field_item.c_layout &&
record_ok && record_item.kind == .Struct && !record_item.tuple && !record_item.c_layout
record_ok && record_item.kind == .Struct && !record_item.tuple && !record_item.c_layout &&
enum_ok && enum_item.kind == .Struct && !enum_item.tuple && !enum_item.c_layout
field_fields := types.fields_for(&checker.module.types, field_info)
valid = valid && len(field_fields) == 3
if valid {
@@ -3528,6 +3580,13 @@ validate_meta_schema :: proc(checker: ^Checker) {
symbol_text(checker, symbol.Id(field_fields[2].name)) == "index" &&
types.equal(field_fields[2].type, types.USIZE)
}
enum_fields := types.fields_for(&checker.module.types, enum_info)
valid = valid && len(enum_fields) == 1
if valid {
fields_item, fields_ok := types.node(&checker.module.types, enum_fields[0].type)
valid = symbol_text(checker, symbol.Id(enum_fields[0].name)) == "fields" && fields_ok &&
fields_item.kind == .Slice && !fields_item.mutable && types.equal(fields_item.child, field_info)
}
record_fields := types.fields_for(&checker.module.types, record_info)
valid = valid && len(record_fields) == 4
if valid {
@@ -3553,7 +3612,8 @@ validate_meta_schema :: proc(checker: ^Checker) {
field := type_fields[index]
if symbol_text(checker, symbol.Id(field.name)) != tag ||
(tag == "record" && !types.equal(field.type, record_info)) ||
(tag != "record" && !types.is_void(field.type)) {
(tag == "enum" && !types.equal(field.type, enum_info)) ||
(tag != "record" && tag != "enum" && !types.is_void(field.type)) {
valid = false
break
}
@@ -3623,7 +3683,7 @@ validate_type_nodes :: proc(checker: ^Checker) {
}
item_name := symbol_text(checker, symbol.Id(item.name))
comptime_meta := meta_package &&
(item_name == "FieldInfo" || item_name == "RecordInfo" || item_name == "TypeInfo")
(item_name == "FieldInfo" || item_name == "RecordInfo" || item_name == "EnumInfo" || item_name == "TypeInfo")
if item.c_layout && !item.opaque && item.field_count == 0 {
source.add(
checker.diagnostics,
@@ -4263,6 +4323,12 @@ infer_expr :: proc(
}
if !types.is_valid(last) && symbol.is_valid(expr.qualifier) &&
find_import(checker, file, expr.qualifier) == ast.INVALID_IMPORT {
if value, ok := current_comptime_value(checker, expr.qualifier);
ok && value.kind == .Static {
if _, field, found := find_struct_field(checker, value.type, expr.name); found {
last = field.type
}
}
if value, ok := current_static_binding(checker, expr.qualifier); ok {
if _, field, found := find_struct_field(checker, value.type, expr.name); found {
last = field.type
@@ -4298,7 +4364,7 @@ infer_expr :: proc(
if !types.is_valid(last) {
if !symbol.is_valid(expr.qualifier) {
if value, ok := current_comptime_value(checker, expr.name); ok {
if value.kind == .Integer || value.kind == .String {
if value.kind == .Integer || value.kind == .String || value.kind == .Static {
last = value.type
}
}
@@ -4385,6 +4451,13 @@ infer_expr :: proc(
continue
}
if field_expr, handled := field_intrinsic_expr(checker, expr, pkg, file); handled {
if enum_type, ok := resolve_type_argument(checker, field_expr.left, pkg, file);
ok && types.is_enum(enum_type, &checker.module.types) {
_, member_ok := find_enum_member(checker, enum_type, field_expr.name)
last = enum_type if member_ok else types.INVALID
_ = pop(&stack)
continue
}
base_type := infer_nested_expr(
checker, field_expr.left, locals, pkg, file, demanded, local_types,
)
@@ -6360,6 +6433,15 @@ build_qualified_value_field :: proc(
value, ok := build_field_from_value(checker, expr, base, local.type)
return value, true, ok
}
if value, ok := current_comptime_value(checker, expr.qualifier);
ok && value.kind == .Static && value.static_value != INVALID_CT_VALUE &&
int(value.static_value) < len(checker.static_state.values) {
field_value, field_ok := persistent_field_value(checker, value.static_value, expr.name)
if !field_ok {
return hir.INVALID_EXPR, true, false
}
return build_static_value(checker, field_value, expr.span, types.INVALID), true, true
}
if global := find_global(checker, expr.qualifier, pkg, file); global != ast.INVALID_GLOBAL {
base := build_global_reference(checker, global, expr.span, global_reads)
value, ok := build_field_from_value(checker, expr, base, checker.global_types[global])
@@ -7482,6 +7564,15 @@ build_expr :: proc(
})
}
}
if last == hir.INVALID_EXPR && symbol.is_valid(expr.qualifier) {
if value, ok := current_comptime_value(checker, expr.qualifier);
ok && value.kind == .Static && value.static_value != INVALID_CT_VALUE &&
int(value.static_value) < len(checker.static_state.values) {
if field_value, found := persistent_field_value(checker, value.static_value, expr.name); found {
last = build_static_value(checker, field_value, expr.span, frame.expected)
}
}
}
if last == hir.INVALID_EXPR && symbol.is_valid(expr.qualifier) {
if value, ok := static_field_value(checker, expr.qualifier, expr.name); ok {
last = build_static_value(checker, value, expr.span, frame.expected)
@@ -7558,6 +7649,11 @@ build_expr :: proc(
checker, constant_expr, locals, global_reads, calls,
frame.expected, pkg, file,
)
} else if value.kind == .Static && value.static_value != INVALID_CT_VALUE &&
int(value.static_value) < len(checker.static_state.values) {
last = build_static_value(
checker, checker.static_state.values[value.static_value], expr.span, frame.expected,
)
} else {
id := source.addf(
checker.diagnostics,
@@ -7628,6 +7724,12 @@ build_expr :: proc(
continue
}
if field_expr, handled := field_intrinsic_expr(checker, expr, pkg, file); handled {
if enum_type, ok := resolve_type_argument(checker, field_expr.left, pkg, file);
ok && types.is_enum(enum_type, &checker.module.types) {
last = enum_member_hir(checker, enum_type, field_expr.name, field_expr.span)
_ = pop(&stack)
continue
}
base := build_nested_expr(
checker, field_expr.left, locals, global_reads, calls,
types.INVALID, pkg, file,
@@ -8315,6 +8417,8 @@ make_link_name :: proc(checker: ^Checker, id: Spec_Id) -> string {
strings.write_byte(&builder, hex[byte>>4])
strings.write_byte(&builder, hex[byte&0xf])
}
} else if value.kind == .Static {
fmt.sbprintf(&builder, "__ca%d_%016x", len(value.key), value.fingerprint)
} else {
strings.write_string(&builder, "__cv")
if value.value < 0 {
@@ -12070,6 +12174,8 @@ check :: proc(
checker.type_factory_origins.allocator = allocator
checker.call_resolutions.allocator = allocator
checker.static_bindings.allocator = allocator
checker.comptime_keys.allocator = allocator
checker.comptime_static_values.allocator = allocator
checker.inline_context.allocator = allocator
checker.static_state = ct_state_make(&checker, 0, ast.INVALID_FILE)
build_symbol_indexes(&checker)
@@ -12146,6 +12252,11 @@ check :: proc(
delete(checker.call_resolutions)
ct_state_destroy(&checker.static_state)
delete(checker.static_bindings)
for key in checker.comptime_keys {
delete(key, allocator)
}
delete(checker.comptime_keys)
delete(checker.comptime_static_values)
delete(checker.inline_context)
}
+215 -7
View File
@@ -8,6 +8,7 @@ import "../types"
import "base:intrinsics"
import "core:fmt"
import "core:hash"
import "core:math"
import "core:mem"
import "core:strings"
@@ -18,14 +19,18 @@ Comptime_Value_Kind :: enum u8 {
Integer,
Type,
String,
Static,
}
Comptime_Value :: struct {
name: symbol.Id,
type: types.Type,
value: i128,
text: string,
kind: Comptime_Value_Kind,
name: symbol.Id,
type: types.Type,
value: i128,
text: string,
static_value: Ct_Value_Id,
key: string,
fingerprint: u64,
kind: Comptime_Value_Kind,
}
Constant_Kind :: enum {
@@ -85,7 +90,8 @@ comptime_values_equal :: proc(left, right: []Comptime_Value) -> bool {
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 == .String && value.text != other.text) {
(value.kind == .String && value.text != other.text) ||
(value.kind == .Static && (value.fingerprint != other.fingerprint || value.key != other.key)) {
return false
}
}
@@ -362,6 +368,9 @@ ct_state_make :: proc(
}
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)
} else if value.kind == .Static {
id := ct_clone_graph(&state, &checker.static_state, value.static_value)
ct_bind_value(&state, value.name, value.type, id, false)
}
}
for binding in checker.static_bindings {
@@ -2312,9 +2321,10 @@ ct_typeinfo_value :: proc(state: ^Ct_State, target: types.Type, span: source.Spa
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")
enuminfo_type := std_named_type(checker, "@std/meta", "EnumInfo")
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) {
!types.is_valid(recordinfo_type) || !types.is_valid(enuminfo_type) || !types.is_valid(layout_type) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(
state, .Not_Comptime, span,
"typeinfo! requires importing @std/meta",
@@ -2352,6 +2362,31 @@ ct_typeinfo_value :: proc(state: ^Ct_State, target: types.Type, span: source.Spa
if !variant_ok {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "@std/meta TypeInfo is malformed")
}
if tag == "enum" {
members := types.enum_members_for(store, resolved)
field_values := make([]Ct_Value_Id, len(members), checker.allocator)
defer delete(field_values, checker.allocator)
for member, index in members {
name_value := ct_reflection_string(state, symbol_text(checker, symbol.Id(member.name)))
type_value := ct_add_value(state, Ct_Value{kind=.Type, type=types.INVALID, index=u64(resolved)})
index_value := ct_add_value(state, Ct_Value{kind=.Integer, type=types.USIZE, integer=i128(index)})
field_values[index] = ct_struct_value(
state, fieldinfo_type, []Ct_Value_Id{name_value, type_value, index_value},
)
}
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)),
})
enum_value := ct_struct_value(state, enuminfo_type, []Ct_Value_Id{fields_value})
payload_start := u32(len(state.children))
append(&state.children, enum_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
}
if tag != "record" {
start := u32(len(state.children))
return ct_add_value(state, Ct_Value{
@@ -2437,6 +2472,22 @@ ct_eval_call_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type
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))
}
if enum_type, ok := resolve_type_argument(checker, expr.args[0], state.pkg, state.file);
ok && types.is_enum(enum_type, &checker.module.types) {
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")
}
member, member_ok := find_enum_member(checker, enum_type, symbol.intern(checker.symbols, name))
if !member_ok {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unknown enum member '%s'", name)
}
return ct_add_value(state, Ct_Value{kind=.Integer, type=enum_type, integer=member.value}), ct_flow(.Normal), true
}
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
@@ -2753,6 +2804,163 @@ store_static_binding :: proc(checker: ^Checker, source: ^Ct_State, id: Ct_Value_
return Static_Binding{name=name, type=value_type, value=value}
}
ct_write_comptime_key :: proc(state: ^Ct_State, id: Ct_Value_Id, builder: ^strings.Builder, depth := 0) -> bool {
if depth > 256 || id == INVALID_CT_VALUE || int(id) >= len(state.values) {
return false
}
value := state.values[id]
fmt.sbprintf(builder, "t%d:", value.type)
#partial switch value.kind {
case .Integer:
fmt.sbprintf(builder, "i%d;", value.integer)
return true
case .Float:
if types.bits(value.type, state.checker.target) == 32 {
fmt.sbprintf(builder, "f%08x;", transmute(u32)f32(value.float))
} else {
fmt.sbprintf(builder, "f%016x;", transmute(u64)value.float)
}
return true
case .Bool:
fmt.sbprintf(builder, "b%d;", value.integer)
return true
case .Type:
fmt.sbprintf(builder, "y%d;", value.index)
return true
case .String:
if value.index >= u64(len(state.checker.ast_module.strings)) {
return false
}
text := state.checker.ast_module.strings[value.index]
fmt.sbprintf(builder, "s%d:", len(text))
hex := "0123456789abcdef"
for byte in transmute([]byte)text {
strings.write_byte(builder, hex[byte>>4])
strings.write_byte(builder, hex[byte&0xf])
}
strings.write_byte(builder, ';')
return true
case .Slice:
item, item_ok := types.container(value.type, &state.checker.module.types)
if !item_ok || item.kind != .Slice || item.mutable || item.child != types.U8 {
return false
}
text, text_ok := ct_value_bytes(state, id)
if !text_ok {
return false
}
fmt.sbprintf(builder, "s%d:", len(text))
hex := "0123456789abcdef"
for byte in transmute([]byte)text {
strings.write_byte(builder, hex[byte>>4])
strings.write_byte(builder, hex[byte&0xf])
}
strings.write_byte(builder, ';')
return true
case .Array:
children := ct_child_slice(state, value)
fmt.sbprintf(builder, "a%d[", len(children))
for child in children {
if !ct_write_comptime_key(state, child, builder, depth+1) {
return false
}
}
strings.write_string(builder, "];")
return true
case .Struct:
if types.is_union(value.type, &state.checker.module.types) &&
!types.is_tagged_union(value.type, &state.checker.module.types) {
return false
}
children := ct_child_slice(state, value)
fmt.sbprintf(builder, "r%d:%d[", value.active, len(children))
for child in children {
if child != INVALID_CT_VALUE && !ct_write_comptime_key(state, child, builder, depth+1) {
return false
}
}
strings.write_string(builder, "];")
return true
case .None:
strings.write_string(builder, "n;")
return true
case .Optional_Some:
children := ct_child_slice(state, value)
if len(children) != 1 || !ct_write_comptime_key(state, children[0], builder, depth+1) {
return false
}
strings.write_string(builder, "o;")
return true
case .Invalid, .Void, .Undefined, .Range, .Pointer, .Function, .Fallible:
return false
}
return false
}
eval_static_comptime_value :: proc(
checker: ^Checker,
name: symbol.Id,
expr: ast.Expr_Id,
declared: types.Type,
pkg: ast.Package_Id,
file: ast.File_Id,
values: []Comptime_Value = nil,
diagnose := false,
) -> (Comptime_Value, bool) {
state := ct_state_make(checker, pkg, file, values=values, diagnose=false)
defer ct_state_destroy(&state)
id, flow, ok := ct_eval_expr(&state, expr, declared, 0)
if ok && flow.kind == .Normal {
id, ok = ct_coerce_value(&state, id, declared, checker.ast_module.exprs[expr].span)
}
if !ok || flow.kind != .Normal || ct_value_contains_undefined(&state, id) {
if diagnose {
source.add(
checker.diagnostics,
checker.ast_module.exprs[expr].span,
"comptime argument has no stable comptime identity",
)
}
return {}, false
}
builder := strings.builder_make(checker.allocator)
defer strings.builder_destroy(&builder)
if !ct_write_comptime_key(&state, id, &builder) {
if diagnose {
source.add(
checker.diagnostics,
checker.ast_module.exprs[expr].span,
"comptime argument has no stable comptime identity",
)
}
return {}, false
}
key_view := strings.to_string(builder)
static_value := INVALID_CT_VALUE
key := ""
for existing, index in checker.comptime_keys {
if existing == key_view {
key = existing
static_value = checker.comptime_static_values[index]
break
}
}
if static_value == INVALID_CT_VALUE {
key = strings.clone(key_view, checker.allocator)
static_value = ct_clone_graph(&checker.static_state, &state, id)
append(&checker.comptime_keys, key)
append(&checker.comptime_static_values, static_value)
}
return Comptime_Value{
name=name,
type=declared,
static_value=static_value,
key=key,
fingerprint=hash.fnv64a(transmute([]byte)key),
kind=.Static,
}, true
}
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'")