array reflection

This commit is contained in:
2026-07-22 10:12:12 +02:00
parent 0d04925b3a
commit 07e89e23e1
12 changed files with 675 additions and 68 deletions
+125 -46
View File
@@ -231,7 +231,6 @@ Checker :: struct {
constants: []Constant,
template_diagnostics: []source.Diagnostic_Id,
constant_stack: [dynamic]Constant_Frame,
ast_expr_stack: [dynamic]ast.Expr_Id,
hir_expr_stack: [dynamic]hir.Expr_Id,
infer_stack: [dynamic]Infer_Frame,
build_stack: [dynamic]Build_Expr_Frame,
@@ -2090,6 +2089,9 @@ explicit_comptime_argument_valid :: proc(
_, ok := resolve_type_argument(checker, arg, pkg, file)
return ok
}
if type_pattern_mentions_comptime(checker, function, comptime_param_count(function), param.type) {
return true
}
if is_comptime_string_param(checker, param, function) {
_, ok := comptime_string_argument(checker, arg, pkg, file)
return ok
@@ -2097,9 +2099,6 @@ 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
}
if type_pattern_mentions_comptime(checker, function, comptime_param_count(function), param.type) {
return true
}
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
@@ -2307,6 +2306,38 @@ call_argument_mapping :: proc(
}
}
}
if selected_index < 0 {
// Prefer the mapping that treats an array literal as a dependent aggregate.
// It can then report the contextual element error instead of an ambiguous
// hidden-comptime-parameter mapping failure.
dependent_array_candidate := -1
ambiguous := false
for candidate, index in candidates {
matches := false
for arg_id, source_index in args {
param_index := call_param_index(candidate, source_index)
if param_index < 0 || param_index >= len(function.params) ||
!function.params[param_index].comptime_value ||
arg_id == ast.INVALID_EXPR || int(arg_id) >= len(checker.ast_module.exprs) ||
checker.ast_module.exprs[arg_id].kind != .Array {
continue
}
matches = matches || type_pattern_mentions_comptime(
checker, function^, comptime_param_count(function^), function.params[param_index].type,
)
}
if matches {
if dependent_array_candidate >= 0 {
ambiguous = true
} else {
dependent_array_candidate = index
}
}
}
if dependent_array_candidate >= 0 && !ambiguous {
selected_index = dependent_array_candidate
}
}
if selected_index < 0 {
if len(failures) > 0 {
builder := strings.builder_make(checker.allocator)
@@ -2638,7 +2669,10 @@ infer_call_comptime_values :: proc(
continue
}
values[ordinal].name = param.name
if is_comptime_type_param(checker, param) {
dependent := type_pattern_mentions_comptime(checker, function, ordinal, param.type)
if dependent {
values[ordinal].kind = .Static
} else if is_comptime_type_param(checker, param) {
values[ordinal].kind = .Type
} else if is_comptime_string_param(checker, param, function) {
values[ordinal].kind = .String
@@ -2653,6 +2687,65 @@ infer_call_comptime_values :: proc(
ordinal += 1
}
matched := true
all_bound := true
for value_bound in bound {
all_bound = all_bound && value_bound
}
if !all_bound && is_runtime_type(checker, expected) {
if types.is_valid(function.error) && types.kind(expected, &checker.module.types) == .Fallible {
matched = match_inferred_type_pattern(
checker, function, prefix, function.result,
types.fallible_success(expected, &checker.module.types), values, bound,
source.Span{}, diagnose,
) && matched
matched = match_inferred_type_pattern(
checker, function, prefix, function.error,
types.fallible_error(expected, &checker.module.types), values, bound,
source.Span{}, diagnose,
) && matched
} else if type_pattern_mentions_comptime(checker, function, prefix, function.result) {
matched = match_inferred_type_pattern(
checker, function, prefix, function.result, expected, values, bound,
source.Span{}, diagnose,
) && matched
}
}
// A comptime aggregate is evaluated only after its dependent type is known.
// Bind a direct `[N]T` parameter's N from an array literal's syntax first;
// this supplies the contextual element type for strings and enum literals.
for arg_id, source_index in args {
param_index := call_param_index(mapping, source_index)
if param_index < 0 || param_index >= len(function.params) ||
!function.params[param_index].comptime_value ||
arg_id == ast.INVALID_EXPR || int(arg_id) >= len(checker.ast_module.exprs) ||
checker.ast_module.exprs[arg_id].kind != .Array {
continue
}
pattern, pattern_ok := types.node(&checker.module.types, function.params[param_index].type)
if !pattern_ok || pattern.kind != .Array || !pattern.unresolved_count ||
pattern.count_expr == u32(ast.INVALID_EXPR) || int(pattern.count_expr) >= len(checker.ast_module.exprs) {
continue
}
count_expr := checker.ast_module.exprs[ast.Expr_Id(pattern.count_expr)]
if count_expr.kind != .Name || symbol.is_valid(count_expr.qualifier) {
continue
}
binding_index, is_binding := comptime_binding_index(function, prefix, count_expr.name)
if !is_binding || bound[binding_index] {
continue
}
param, param_ok := comptime_param_for_name(function, count_expr.name)
if !param_ok || is_comptime_type_param(checker, param) {
continue
}
values[binding_index] = Comptime_Value{
name=count_expr.name,
type=type_from_syntax(checker, param.type, function.pkg, function.file),
value=i128(len(checker.ast_module.exprs[arg_id].args)),
kind=.Integer,
}
bound[binding_index] = true
}
for arg_id, source_index in args {
param_index := call_param_index(mapping, source_index)
if param_index < 0 || param_index >= len(function.params) || !function.params[param_index].comptime_value ||
@@ -2669,6 +2762,7 @@ infer_call_comptime_values :: proc(
matched = false
continue
}
dependent := type_pattern_mentions_comptime(checker, function, prefix, param.type)
if is_comptime_type_param(checker, param) {
actual, ok := resolve_type_argument(checker, arg_id, pkg, file)
if !ok {
@@ -2688,7 +2782,7 @@ infer_call_comptime_values :: proc(
}
values[binding_index] = Comptime_Value{name=param.name, type=actual, kind=.Type}
bound[binding_index] = true
} else if is_comptime_string_param(checker, param, function) {
} else if !dependent && is_comptime_string_param(checker, param, function) {
text, text_ok := comptime_string_argument(checker, arg_id, pkg, file)
if !text_ok {
if failure != nil && len(failure^) == 0 {
@@ -2712,7 +2806,7 @@ infer_call_comptime_values :: proc(
kind=.String,
}
bound[binding_index] = true
} else if types.is_concrete_integer(type_from_syntax(checker, param.type, function.pkg, function.file)) {
} else if !dependent && 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 {
@@ -2777,29 +2871,6 @@ infer_call_comptime_values :: proc(
bound[binding_index] = true
}
}
all_bound := true
for value_bound in bound {
all_bound = all_bound && value_bound
}
if !all_bound && is_runtime_type(checker, expected) {
if types.is_valid(function.error) && types.kind(expected, &checker.module.types) == .Fallible {
matched = match_inferred_type_pattern(
checker, function, prefix, function.result,
types.fallible_success(expected, &checker.module.types), values, bound,
source.Span{}, diagnose,
) && matched
matched = match_inferred_type_pattern(
checker, function, prefix, function.error,
types.fallible_error(expected, &checker.module.types), values, bound,
source.Span{}, diagnose,
) && matched
} else if type_pattern_mentions_comptime(checker, function, prefix, function.result) {
matched = match_inferred_type_pattern(
checker, function, prefix, function.result, expected, values, bound,
source.Span{}, diagnose,
) && matched
}
}
all_bound = true
for value_bound in bound {
all_bound = all_bound && value_bound
@@ -3461,12 +3532,9 @@ contains_name :: proc(names: []symbol.Id, name: symbol.Id) -> bool {
}
mark_expr_imports_used :: proc(checker: ^Checker, expr_id: ast.Expr_Id, file: ast.File_Id) {
stack := checker.ast_expr_stack
clear_dynamic_array(&stack)
defer {
clear_dynamic_array(&stack)
checker.ast_expr_stack = stack
}
stack: [dynamic]ast.Expr_Id
stack.allocator = checker.allocator
defer delete(stack)
append(&stack, expr_id)
for len(stack) > 0 {
id := pop(&stack)
@@ -3658,14 +3726,14 @@ validate_declarations :: proc(checker: ^Checker) {
locals.allocator = checker.allocator
comptime_prefix := 0
for param in function.params {
dependent := param.comptime_value && type_pattern_mentions_comptime(
checker, function, comptime_prefix, param.type,
)
param_type := types.INVALID
if param.comptime_value || !has_comptime {
if param.comptime_value && !dependent || !has_comptime {
param_type = type_from_syntax(checker, param.type, function.pkg, function.file)
}
if param.comptime_value && !signature_poisoned {
dependent := type_pattern_mentions_comptime(
checker, function, comptime_prefix, param.type, type_params_only=true,
)
if function.c_abi {
checker.template_diagnostics[function_id] = source.add(
checker.diagnostics,
@@ -4091,11 +4159,12 @@ validate_meta_schema :: proc(checker: ^Checker) {
return types.find_named(&checker.module.types, u32(pkg), u32(symbol.intern(checker.symbols, name)))
}
field_info := find(checker, meta_package, "FieldInfo")
array_info := find(checker, meta_package, "ArrayInfo")
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) && types.is_valid(enum_info) &&
valid := types.is_valid(array_info) && 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)
@@ -4104,12 +4173,22 @@ validate_meta_schema :: proc(checker: ^Checker) {
valid = symbol_text(checker, symbol.Id(layout_members[0].name)) == "auto" &&
symbol_text(checker, symbol.Id(layout_members[1].name)) == "c"
}
array_item, array_ok := types.node(&checker.module.types, array_info)
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 &&
valid = valid && array_ok && array_item.kind == .Struct && !array_item.tuple && !array_item.c_layout &&
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 &&
enum_ok && enum_item.kind == .Struct && !enum_item.tuple && !enum_item.c_layout
array_fields := types.fields_for(&checker.module.types, array_info)
valid = valid && len(array_fields) == 2
if valid {
valid = symbol_text(checker, symbol.Id(array_fields[0].name)) == "child" &&
is_type_metatype_syntax(checker, array_fields[0].type) &&
symbol_text(checker, symbol.Id(array_fields[1].name)) == "len" &&
types.equal(array_fields[1].type, types.USIZE)
}
field_fields := types.fields_for(&checker.module.types, field_info)
valid = valid && len(field_fields) == 3
if valid {
@@ -4151,9 +4230,10 @@ validate_meta_schema :: proc(checker: ^Checker) {
for tag, index in expected_tags {
field := type_fields[index]
if symbol_text(checker, symbol.Id(field.name)) != tag ||
(tag == "array" && !types.equal(field.type, array_info)) ||
(tag == "record" && !types.equal(field.type, record_info)) ||
(tag == "enum" && !types.equal(field.type, enum_info)) ||
(tag != "record" && tag != "enum" && !types.is_void(field.type)) {
(tag != "array" && tag != "record" && tag != "enum" && !types.is_void(field.type)) {
valid = false
break
}
@@ -4242,7 +4322,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 == "EnumInfo" || item_name == "TypeInfo")
(item_name == "ArrayInfo" || 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,
@@ -14047,7 +14127,6 @@ check :: proc(
append(&checker.module.strings, strings.clone(value, allocator))
}
checker.constant_stack.allocator = allocator
checker.ast_expr_stack.allocator = allocator
checker.hir_expr_stack.allocator = allocator
checker.infer_stack.allocator = allocator
checker.build_stack.allocator = allocator
@@ -14120,7 +14199,6 @@ check :: proc(
delete(checker.constants, allocator)
delete(checker.template_diagnostics, allocator)
delete(checker.constant_stack)
delete(checker.ast_expr_stack)
delete(checker.hir_expr_stack)
delete(checker.infer_stack)
delete(checker.build_stack)
@@ -14154,6 +14232,7 @@ check :: proc(
delete(checker.comptime_keys)
delete(checker.comptime_static_values)
delete(checker.expand_context)
delete(checker.anon_globals)
}
for function, index in ast_module.functions {