array reflection
This commit is contained in:
+125
-46
@@ -231,7 +231,6 @@ Checker :: struct {
|
|||||||
constants: []Constant,
|
constants: []Constant,
|
||||||
template_diagnostics: []source.Diagnostic_Id,
|
template_diagnostics: []source.Diagnostic_Id,
|
||||||
constant_stack: [dynamic]Constant_Frame,
|
constant_stack: [dynamic]Constant_Frame,
|
||||||
ast_expr_stack: [dynamic]ast.Expr_Id,
|
|
||||||
hir_expr_stack: [dynamic]hir.Expr_Id,
|
hir_expr_stack: [dynamic]hir.Expr_Id,
|
||||||
infer_stack: [dynamic]Infer_Frame,
|
infer_stack: [dynamic]Infer_Frame,
|
||||||
build_stack: [dynamic]Build_Expr_Frame,
|
build_stack: [dynamic]Build_Expr_Frame,
|
||||||
@@ -2090,6 +2089,9 @@ explicit_comptime_argument_valid :: proc(
|
|||||||
_, ok := resolve_type_argument(checker, arg, pkg, file)
|
_, ok := resolve_type_argument(checker, arg, pkg, file)
|
||||||
return ok
|
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) {
|
if is_comptime_string_param(checker, param, function) {
|
||||||
_, ok := comptime_string_argument(checker, arg, pkg, file)
|
_, ok := comptime_string_argument(checker, arg, pkg, file)
|
||||||
return ok
|
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)) {
|
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 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)
|
declared := type_from_syntax(checker, param.type, function.pkg, function.file)
|
||||||
_, ok := eval_static_comptime_value(checker, param.name, arg, declared, pkg, file)
|
_, ok := eval_static_comptime_value(checker, param.name, arg, declared, pkg, file)
|
||||||
return ok
|
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 selected_index < 0 {
|
||||||
if len(failures) > 0 {
|
if len(failures) > 0 {
|
||||||
builder := strings.builder_make(checker.allocator)
|
builder := strings.builder_make(checker.allocator)
|
||||||
@@ -2638,7 +2669,10 @@ infer_call_comptime_values :: proc(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
values[ordinal].name = param.name
|
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
|
values[ordinal].kind = .Type
|
||||||
} else if is_comptime_string_param(checker, param, function) {
|
} else if is_comptime_string_param(checker, param, function) {
|
||||||
values[ordinal].kind = .String
|
values[ordinal].kind = .String
|
||||||
@@ -2653,6 +2687,65 @@ infer_call_comptime_values :: proc(
|
|||||||
ordinal += 1
|
ordinal += 1
|
||||||
}
|
}
|
||||||
matched := true
|
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 {
|
for arg_id, source_index in args {
|
||||||
param_index := call_param_index(mapping, source_index)
|
param_index := call_param_index(mapping, source_index)
|
||||||
if param_index < 0 || param_index >= len(function.params) || !function.params[param_index].comptime_value ||
|
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
|
matched = false
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
dependent := type_pattern_mentions_comptime(checker, function, prefix, param.type)
|
||||||
if is_comptime_type_param(checker, param) {
|
if is_comptime_type_param(checker, param) {
|
||||||
actual, ok := resolve_type_argument(checker, arg_id, pkg, file)
|
actual, ok := resolve_type_argument(checker, arg_id, pkg, file)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -2688,7 +2782,7 @@ infer_call_comptime_values :: proc(
|
|||||||
}
|
}
|
||||||
values[binding_index] = Comptime_Value{name=param.name, type=actual, kind=.Type}
|
values[binding_index] = Comptime_Value{name=param.name, type=actual, kind=.Type}
|
||||||
bound[binding_index] = true
|
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)
|
text, text_ok := comptime_string_argument(checker, arg_id, pkg, file)
|
||||||
if !text_ok {
|
if !text_ok {
|
||||||
if failure != nil && len(failure^) == 0 {
|
if failure != nil && len(failure^) == 0 {
|
||||||
@@ -2712,7 +2806,7 @@ infer_call_comptime_values :: proc(
|
|||||||
kind=.String,
|
kind=.String,
|
||||||
}
|
}
|
||||||
bound[binding_index] = true
|
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)
|
declared := type_from_syntax(checker, param.type, function.pkg, function.file)
|
||||||
constant := eval_integer_constant_in_context(checker, arg_id, pkg, file)
|
constant := eval_integer_constant_in_context(checker, arg_id, pkg, file)
|
||||||
if constant.kind != .Value {
|
if constant.kind != .Value {
|
||||||
@@ -2777,29 +2871,6 @@ infer_call_comptime_values :: proc(
|
|||||||
bound[binding_index] = true
|
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
|
all_bound = true
|
||||||
for value_bound in bound {
|
for value_bound in bound {
|
||||||
all_bound = all_bound && value_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) {
|
mark_expr_imports_used :: proc(checker: ^Checker, expr_id: ast.Expr_Id, file: ast.File_Id) {
|
||||||
stack := checker.ast_expr_stack
|
stack: [dynamic]ast.Expr_Id
|
||||||
clear_dynamic_array(&stack)
|
stack.allocator = checker.allocator
|
||||||
defer {
|
defer delete(stack)
|
||||||
clear_dynamic_array(&stack)
|
|
||||||
checker.ast_expr_stack = stack
|
|
||||||
}
|
|
||||||
append(&stack, expr_id)
|
append(&stack, expr_id)
|
||||||
for len(stack) > 0 {
|
for len(stack) > 0 {
|
||||||
id := pop(&stack)
|
id := pop(&stack)
|
||||||
@@ -3658,14 +3726,14 @@ validate_declarations :: proc(checker: ^Checker) {
|
|||||||
locals.allocator = checker.allocator
|
locals.allocator = checker.allocator
|
||||||
comptime_prefix := 0
|
comptime_prefix := 0
|
||||||
for param in function.params {
|
for param in function.params {
|
||||||
|
dependent := param.comptime_value && type_pattern_mentions_comptime(
|
||||||
|
checker, function, comptime_prefix, param.type,
|
||||||
|
)
|
||||||
param_type := types.INVALID
|
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)
|
param_type = type_from_syntax(checker, param.type, function.pkg, function.file)
|
||||||
}
|
}
|
||||||
if param.comptime_value && !signature_poisoned {
|
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 {
|
if function.c_abi {
|
||||||
checker.template_diagnostics[function_id] = source.add(
|
checker.template_diagnostics[function_id] = source.add(
|
||||||
checker.diagnostics,
|
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)))
|
return types.find_named(&checker.module.types, u32(pkg), u32(symbol.intern(checker.symbols, name)))
|
||||||
}
|
}
|
||||||
field_info := find(checker, meta_package, "FieldInfo")
|
field_info := find(checker, meta_package, "FieldInfo")
|
||||||
|
array_info := find(checker, meta_package, "ArrayInfo")
|
||||||
record_info := find(checker, meta_package, "RecordInfo")
|
record_info := find(checker, meta_package, "RecordInfo")
|
||||||
enum_info := find(checker, meta_package, "EnumInfo")
|
enum_info := find(checker, meta_package, "EnumInfo")
|
||||||
type_info := find(checker, meta_package, "TypeInfo")
|
type_info := find(checker, meta_package, "TypeInfo")
|
||||||
layout := find(checker, meta_package, "Layout")
|
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)
|
types.is_valid(type_info) && types.is_valid(layout)
|
||||||
layout_item, layout_ok := types.node(&checker.module.types, layout)
|
layout_item, layout_ok := types.node(&checker.module.types, layout)
|
||||||
layout_members := types.enum_members_for(&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" &&
|
valid = symbol_text(checker, symbol.Id(layout_members[0].name)) == "auto" &&
|
||||||
symbol_text(checker, symbol.Id(layout_members[1].name)) == "c"
|
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)
|
field_item, field_ok := types.node(&checker.module.types, field_info)
|
||||||
record_item, record_ok := types.node(&checker.module.types, record_info)
|
record_item, record_ok := types.node(&checker.module.types, record_info)
|
||||||
enum_item, enum_ok := types.node(&checker.module.types, enum_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 &&
|
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
|
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)
|
field_fields := types.fields_for(&checker.module.types, field_info)
|
||||||
valid = valid && len(field_fields) == 3
|
valid = valid && len(field_fields) == 3
|
||||||
if valid {
|
if valid {
|
||||||
@@ -4151,9 +4230,10 @@ validate_meta_schema :: proc(checker: ^Checker) {
|
|||||||
for tag, index in expected_tags {
|
for tag, index in expected_tags {
|
||||||
field := type_fields[index]
|
field := type_fields[index]
|
||||||
if symbol_text(checker, symbol.Id(field.name)) != tag ||
|
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 == "record" && !types.equal(field.type, record_info)) ||
|
||||||
(tag == "enum" && !types.equal(field.type, enum_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
|
valid = false
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -4242,7 +4322,7 @@ validate_type_nodes :: proc(checker: ^Checker) {
|
|||||||
}
|
}
|
||||||
item_name := symbol_text(checker, symbol.Id(item.name))
|
item_name := symbol_text(checker, symbol.Id(item.name))
|
||||||
comptime_meta := meta_package &&
|
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 {
|
if item.c_layout && !item.opaque && item.field_count == 0 {
|
||||||
source.add(
|
source.add(
|
||||||
checker.diagnostics,
|
checker.diagnostics,
|
||||||
@@ -14047,7 +14127,6 @@ check :: proc(
|
|||||||
append(&checker.module.strings, strings.clone(value, allocator))
|
append(&checker.module.strings, strings.clone(value, allocator))
|
||||||
}
|
}
|
||||||
checker.constant_stack.allocator = allocator
|
checker.constant_stack.allocator = allocator
|
||||||
checker.ast_expr_stack.allocator = allocator
|
|
||||||
checker.hir_expr_stack.allocator = allocator
|
checker.hir_expr_stack.allocator = allocator
|
||||||
checker.infer_stack.allocator = allocator
|
checker.infer_stack.allocator = allocator
|
||||||
checker.build_stack.allocator = allocator
|
checker.build_stack.allocator = allocator
|
||||||
@@ -14120,7 +14199,6 @@ check :: proc(
|
|||||||
delete(checker.constants, allocator)
|
delete(checker.constants, allocator)
|
||||||
delete(checker.template_diagnostics, allocator)
|
delete(checker.template_diagnostics, allocator)
|
||||||
delete(checker.constant_stack)
|
delete(checker.constant_stack)
|
||||||
delete(checker.ast_expr_stack)
|
|
||||||
delete(checker.hir_expr_stack)
|
delete(checker.hir_expr_stack)
|
||||||
delete(checker.infer_stack)
|
delete(checker.infer_stack)
|
||||||
delete(checker.build_stack)
|
delete(checker.build_stack)
|
||||||
@@ -14154,6 +14232,7 @@ check :: proc(
|
|||||||
delete(checker.comptime_keys)
|
delete(checker.comptime_keys)
|
||||||
delete(checker.comptime_static_values)
|
delete(checker.comptime_static_values)
|
||||||
delete(checker.expand_context)
|
delete(checker.expand_context)
|
||||||
|
delete(checker.anon_globals)
|
||||||
}
|
}
|
||||||
|
|
||||||
for function, index in ast_module.functions {
|
for function, index in ast_module.functions {
|
||||||
|
|||||||
+153
-14
@@ -325,6 +325,8 @@ Ct_State :: struct {
|
|||||||
diagnostic: source.Diagnostic_Id,
|
diagnostic: source.Diagnostic_Id,
|
||||||
silent: bool,
|
silent: bool,
|
||||||
demanded: ^[dynamic]Spec_Id,
|
demanded: ^[dynamic]Spec_Id,
|
||||||
|
promoted_cells: [dynamic]Ct_Cell_Id,
|
||||||
|
promoted_globals: [dynamic]hir.Global_Id,
|
||||||
}
|
}
|
||||||
|
|
||||||
Ct_Defer :: struct {
|
Ct_Defer :: struct {
|
||||||
@@ -359,6 +361,8 @@ ct_state_make :: proc(
|
|||||||
state.bindings.allocator = checker.allocator
|
state.bindings.allocator = checker.allocator
|
||||||
state.error_refinements.allocator = checker.allocator
|
state.error_refinements.allocator = checker.allocator
|
||||||
state.defers.allocator = checker.allocator
|
state.defers.allocator = checker.allocator
|
||||||
|
state.promoted_cells.allocator = checker.allocator
|
||||||
|
state.promoted_globals.allocator = checker.allocator
|
||||||
for value in values {
|
for value in values {
|
||||||
if value.kind == .Integer {
|
if value.kind == .Integer {
|
||||||
id := ct_add_value(&state, Ct_Value{kind=.Integer, type=value.type, integer=value.value})
|
id := ct_add_value(&state, Ct_Value{kind=.Integer, type=value.type, integer=value.value})
|
||||||
@@ -397,6 +401,8 @@ ct_state_destroy :: proc(state: ^Ct_State) {
|
|||||||
delete(state.bindings)
|
delete(state.bindings)
|
||||||
delete(state.error_refinements)
|
delete(state.error_refinements)
|
||||||
delete(state.defers)
|
delete(state.defers)
|
||||||
|
delete(state.promoted_cells)
|
||||||
|
delete(state.promoted_globals)
|
||||||
}
|
}
|
||||||
|
|
||||||
ct_add_value :: proc(state: ^Ct_State, value: Ct_Value) -> Ct_Value_Id {
|
ct_add_value :: proc(state: ^Ct_State, value: Ct_Value) -> Ct_Value_Id {
|
||||||
@@ -703,7 +709,7 @@ ct_coerce_value :: proc(state: ^Ct_State, id: Ct_Value_Id, expected: types.Type,
|
|||||||
}
|
}
|
||||||
return INVALID_CT_VALUE, ct_failf(
|
return INVALID_CT_VALUE, ct_failf(
|
||||||
state, .Not_Comptime, span, "cannot implicitly convert %s to %s at comptime",
|
state, .Not_Comptime, span, "cannot implicitly convert %s to %s at comptime",
|
||||||
types.name(value.type), types.name(expected),
|
type_label(state.checker, value.type), type_label(state.checker, expected),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -841,6 +847,112 @@ ct_value_references_dead_storage :: proc(state: ^Ct_State, id: Ct_Value_Id) -> b
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ct_materialize_array_pointer :: proc(
|
||||||
|
state: ^Ct_State,
|
||||||
|
value: Ct_Value,
|
||||||
|
span: source.Span,
|
||||||
|
) -> (hir.Expr_Id, bool) {
|
||||||
|
checker := state.checker
|
||||||
|
store := &checker.module.types
|
||||||
|
pointer, pointer_ok := types.node(store, value.type)
|
||||||
|
place_id := Ct_Place_Id(value.index)
|
||||||
|
if !pointer_ok || pointer.kind != .Pointer || pointer.mutable ||
|
||||||
|
place_id == INVALID_CT_PLACE || int(place_id) >= len(state.places) {
|
||||||
|
return hir.INVALID_EXPR, false
|
||||||
|
}
|
||||||
|
place := state.places[place_id]
|
||||||
|
if place.cell == INVALID_CT_CELL || int(place.cell) >= len(state.cells) || len(ct_place_path(state, place)) != 0 {
|
||||||
|
return hir.INVALID_EXPR, false
|
||||||
|
}
|
||||||
|
root_id := state.cells[place.cell].value
|
||||||
|
if root_id == INVALID_CT_VALUE || int(root_id) >= len(state.values) {
|
||||||
|
return hir.INVALID_EXPR, false
|
||||||
|
}
|
||||||
|
root := state.values[root_id]
|
||||||
|
array, array_ok := types.node(store, root.type)
|
||||||
|
if !array_ok || array.kind != .Array ||
|
||||||
|
(pointer.many && (value.active != 0 || !types.equal(pointer.child, array.child))) ||
|
||||||
|
(!pointer.many && (value.active != -1 || !types.equal(pointer.child, root.type))) {
|
||||||
|
return hir.INVALID_EXPR, false
|
||||||
|
}
|
||||||
|
global_id := hir.INVALID_GLOBAL
|
||||||
|
for cell, index in state.promoted_cells {
|
||||||
|
if cell == place.cell {
|
||||||
|
global_id = state.promoted_globals[index]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if global_id == hir.INVALID_GLOBAL {
|
||||||
|
root_expr := ct_materialize_value(state, root_id, span, root.type)
|
||||||
|
if root_expr == hir.INVALID_EXPR || expr_problematic(checker, root_expr) {
|
||||||
|
return hir.INVALID_EXPR, false
|
||||||
|
}
|
||||||
|
global_id = hir.Global_Id(len(checker.ast_module.globals)+len(checker.anon_globals))
|
||||||
|
append(&checker.anon_globals, hir.Global{
|
||||||
|
name=symbol.intern(checker.symbols, "__comptime.array"),
|
||||||
|
type=root.type,
|
||||||
|
expr=root_expr,
|
||||||
|
eager=true,
|
||||||
|
writable=false,
|
||||||
|
external=false,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
append(&state.promoted_cells, place.cell)
|
||||||
|
append(&state.promoted_globals, global_id)
|
||||||
|
}
|
||||||
|
if checker.current_build_ctx != nil {
|
||||||
|
add_unique_global(checker.current_build_ctx.global_reads, global_id)
|
||||||
|
}
|
||||||
|
global := add_hir_expr(checker, hir.Expr{
|
||||||
|
kind=.Global, span=span, type=root.type, target=hir.global_ref(global_id),
|
||||||
|
left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
address := add_hir_expr(checker, hir.Expr{
|
||||||
|
kind=.Address, span=span, type=types.pointer(store, root.type, false, false), left=global,
|
||||||
|
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
return coerce_expr(checker, address, value.type, span), true
|
||||||
|
}
|
||||||
|
|
||||||
|
ct_materialize_array_slice :: proc(
|
||||||
|
state: ^Ct_State,
|
||||||
|
value: Ct_Value,
|
||||||
|
span: source.Span,
|
||||||
|
) -> (hir.Expr_Id, bool) {
|
||||||
|
store := &state.checker.module.types
|
||||||
|
slice, slice_ok := types.node(store, value.type)
|
||||||
|
place_id := Ct_Place_Id(value.index)
|
||||||
|
if !slice_ok || slice.kind != .Slice || slice.mutable || value.start != 0 ||
|
||||||
|
place_id == INVALID_CT_PLACE || int(place_id) >= len(state.places) {
|
||||||
|
return hir.INVALID_EXPR, false
|
||||||
|
}
|
||||||
|
place := state.places[place_id]
|
||||||
|
if place.cell == INVALID_CT_CELL || int(place.cell) >= len(state.cells) || len(ct_place_path(state, place)) != 0 {
|
||||||
|
return hir.INVALID_EXPR, false
|
||||||
|
}
|
||||||
|
root_id := state.cells[place.cell].value
|
||||||
|
if root_id == INVALID_CT_VALUE || int(root_id) >= len(state.values) {
|
||||||
|
return hir.INVALID_EXPR, false
|
||||||
|
}
|
||||||
|
root := state.values[root_id]
|
||||||
|
array, array_ok := types.node(store, root.type)
|
||||||
|
if !array_ok || array.kind != .Array || u64(value.count) != array.count ||
|
||||||
|
!types.equal(slice.child, array.child) {
|
||||||
|
return hir.INVALID_EXPR, false
|
||||||
|
}
|
||||||
|
pointer := Ct_Value{
|
||||||
|
kind=.Pointer,
|
||||||
|
type=types.pointer(store, root.type, false, false),
|
||||||
|
index=value.index,
|
||||||
|
active=-1,
|
||||||
|
}
|
||||||
|
address, ok := ct_materialize_array_pointer(state, pointer, span)
|
||||||
|
if !ok {
|
||||||
|
return hir.INVALID_EXPR, false
|
||||||
|
}
|
||||||
|
return coerce_expr(state.checker, address, value.type, span), true
|
||||||
|
}
|
||||||
|
|
||||||
ct_materialize_value :: proc(
|
ct_materialize_value :: proc(
|
||||||
state: ^Ct_State,
|
state: ^Ct_State,
|
||||||
id: Ct_Value_Id,
|
id: Ct_Value_Id,
|
||||||
@@ -893,11 +1005,13 @@ ct_materialize_value :: proc(
|
|||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
case .String:
|
case .String:
|
||||||
return add_hir_expr(checker, hir.Expr{
|
literal_type := string_literal_type(checker, value.index)
|
||||||
kind=.String, span=span, type=value.type, integer=i64(value.index),
|
literal := add_hir_expr(checker, hir.Expr{
|
||||||
|
kind=.String, span=span, type=literal_type, integer=i64(value.index),
|
||||||
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
|
return coerce_expr(checker, literal, value.type, span)
|
||||||
case .Range:
|
case .Range:
|
||||||
children := ct_child_slice(state, value)
|
children := ct_child_slice(state, value)
|
||||||
args := make([]hir.Expr_Id, 2, checker.allocator)
|
args := make([]hir.Expr_Id, 2, checker.allocator)
|
||||||
@@ -942,9 +1056,20 @@ ct_materialize_value :: proc(
|
|||||||
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
case .Pointer, .Slice:
|
case .Pointer:
|
||||||
|
if result, ok := ct_materialize_array_pointer(state, value, span); ok {
|
||||||
|
return result
|
||||||
|
}
|
||||||
if state.diagnostic == source.INVALID_DIAGNOSTIC {
|
if state.diagnostic == source.INVALID_DIAGNOSTIC {
|
||||||
state.diagnostic = source.add(checker.diagnostics, span, "comptime storage pointers and slices cannot materialize as runtime memory")
|
state.diagnostic = source.add(checker.diagnostics, span, "only immutable pointers to whole comptime arrays can materialize as runtime memory")
|
||||||
|
}
|
||||||
|
return invalid_hir_expr(checker, span, state.diagnostic, value.type)
|
||||||
|
case .Slice:
|
||||||
|
if result, ok := ct_materialize_array_slice(state, value, span); ok {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
if state.diagnostic == source.INVALID_DIAGNOSTIC {
|
||||||
|
state.diagnostic = source.add(checker.diagnostics, span, "only immutable full-array comptime slices can materialize as runtime memory")
|
||||||
}
|
}
|
||||||
return invalid_hir_expr(checker, span, state.diagnostic, value.type)
|
return invalid_hir_expr(checker, span, state.diagnostic, value.type)
|
||||||
case .Function:
|
case .Function:
|
||||||
@@ -1184,6 +1309,9 @@ ct_eval_expr :: proc(
|
|||||||
if !ok || flow.kind != .Normal {
|
if !ok || flow.kind != .Normal {
|
||||||
return INVALID_CT_VALUE, flow, ok
|
return INVALID_CT_VALUE, flow, ok
|
||||||
}
|
}
|
||||||
|
if !symbol.is_valid(expr.name) {
|
||||||
|
return ct_eval_tuple_field_value(state, base_id, expr.integer, expr.span)
|
||||||
|
}
|
||||||
return ct_eval_field_value(state, base_id, expr.name, expr.span)
|
return ct_eval_field_value(state, base_id, expr.name, expr.span)
|
||||||
case .Index:
|
case .Index:
|
||||||
index_id, index_flow, index_ok := ct_eval_expr(state, expr.right, types.USIZE, depth+1)
|
index_id, index_flow, index_ok := ct_eval_expr(state, expr.right, types.USIZE, depth+1)
|
||||||
@@ -1419,8 +1547,9 @@ ct_eval_array_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Typ
|
|||||||
has_expected = false
|
has_expected = false
|
||||||
result_type = types.INVALID
|
result_type = types.INVALID
|
||||||
}
|
}
|
||||||
start := u32(len(state.children))
|
values := make([]Ct_Value_Id, len(expr.args), checker.allocator)
|
||||||
for arg in expr.args {
|
defer delete(values, checker.allocator)
|
||||||
|
for arg, index in expr.args {
|
||||||
value, flow, ok := ct_eval_expr(state, arg, element_type, depth+1)
|
value, flow, ok := ct_eval_expr(state, arg, element_type, depth+1)
|
||||||
if !ok || flow.kind != .Normal {
|
if !ok || flow.kind != .Normal {
|
||||||
return INVALID_CT_VALUE, flow, ok
|
return INVALID_CT_VALUE, flow, ok
|
||||||
@@ -1430,7 +1559,7 @@ ct_eval_array_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Typ
|
|||||||
} else if !types.equal(element_type, state.values[value].type) {
|
} else if !types.equal(element_type, state.values[value].type) {
|
||||||
element_type = types.widest(element_type, state.values[value].type)
|
element_type = types.widest(element_type, state.values[value].type)
|
||||||
}
|
}
|
||||||
append(&state.children, value)
|
values[index] = value
|
||||||
}
|
}
|
||||||
if !types.is_valid(element_type) {
|
if !types.is_valid(element_type) {
|
||||||
element_type = types.I64
|
element_type = types.I64
|
||||||
@@ -1438,14 +1567,15 @@ ct_eval_array_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Typ
|
|||||||
if !has_expected {
|
if !has_expected {
|
||||||
result_type = types.array(store, element_type, u64(len(expr.args)), false)
|
result_type = types.array(store, element_type, u64(len(expr.args)), false)
|
||||||
}
|
}
|
||||||
children := state.children[int(start):int(start)+len(expr.args)]
|
for &child in values {
|
||||||
for &child in children {
|
|
||||||
coerced, ok := ct_coerce_value(state, child, element_type, expr.span)
|
coerced, ok := ct_coerce_value(state, child, element_type, expr.span)
|
||||||
if !ok {
|
if !ok {
|
||||||
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
||||||
}
|
}
|
||||||
child = coerced
|
child = coerced
|
||||||
}
|
}
|
||||||
|
start := u32(len(state.children))
|
||||||
|
append(&state.children, ..values)
|
||||||
return ct_add_value(state, Ct_Value{kind=.Array, type=result_type, start=start, count=u32(len(expr.args))}), ct_flow(.Normal), true
|
return ct_add_value(state, Ct_Value{kind=.Array, type=result_type, start=start, count=u32(len(expr.args))}), ct_flow(.Normal), true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1460,8 +1590,6 @@ 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)
|
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.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)
|
struct_type = types.resolve_alias(struct_type, store)
|
||||||
} else if expr.tuple {
|
|
||||||
struct_type = types.INVALID
|
|
||||||
} else {
|
} else {
|
||||||
struct_type = types.resolve_alias(expected, store)
|
struct_type = types.resolve_alias(expected, store)
|
||||||
}
|
}
|
||||||
@@ -2694,11 +2822,12 @@ ct_typeinfo_value :: proc(state: ^Ct_State, target: types.Type, span: source.Spa
|
|||||||
checker := state.checker
|
checker := state.checker
|
||||||
store := &checker.module.types
|
store := &checker.module.types
|
||||||
typeinfo_type := std_named_type(checker, "@std/meta", "TypeInfo")
|
typeinfo_type := std_named_type(checker, "@std/meta", "TypeInfo")
|
||||||
|
arrayinfo_type := std_named_type(checker, "@std/meta", "ArrayInfo")
|
||||||
fieldinfo_type := std_named_type(checker, "@std/meta", "FieldInfo")
|
fieldinfo_type := std_named_type(checker, "@std/meta", "FieldInfo")
|
||||||
recordinfo_type := std_named_type(checker, "@std/meta", "RecordInfo")
|
recordinfo_type := std_named_type(checker, "@std/meta", "RecordInfo")
|
||||||
enuminfo_type := std_named_type(checker, "@std/meta", "EnumInfo")
|
enuminfo_type := std_named_type(checker, "@std/meta", "EnumInfo")
|
||||||
layout_type := std_named_type(checker, "@std/meta", "Layout")
|
layout_type := std_named_type(checker, "@std/meta", "Layout")
|
||||||
if !types.is_valid(typeinfo_type) || !types.is_valid(fieldinfo_type) ||
|
if !types.is_valid(typeinfo_type) || !types.is_valid(arrayinfo_type) || !types.is_valid(fieldinfo_type) ||
|
||||||
!types.is_valid(recordinfo_type) || !types.is_valid(enuminfo_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(
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(
|
||||||
state, .Not_Comptime, span,
|
state, .Not_Comptime, span,
|
||||||
@@ -2738,6 +2867,16 @@ ct_typeinfo_value :: proc(state: ^Ct_State, target: types.Type, span: source.Spa
|
|||||||
if !variant_ok {
|
if !variant_ok {
|
||||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "@std/meta TypeInfo is malformed")
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "@std/meta TypeInfo is malformed")
|
||||||
}
|
}
|
||||||
|
if tag == "array" {
|
||||||
|
child_value := ct_add_value(state, Ct_Value{kind=.Type, type=types.INVALID, index=u64(item.child)})
|
||||||
|
len_value := ct_add_value(state, Ct_Value{kind=.Integer, type=types.USIZE, integer=i128(item.count)})
|
||||||
|
array_value := ct_struct_value(state, arrayinfo_type, []Ct_Value_Id{child_value, len_value})
|
||||||
|
payload_start := u32(len(state.children))
|
||||||
|
append(&state.children, array_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 == "enum" {
|
if tag == "enum" {
|
||||||
members := types.enum_members_for(store, resolved)
|
members := types.enum_members_for(store, resolved)
|
||||||
field_values := make([]Ct_Value_Id, len(members), checker.allocator)
|
field_values := make([]Ct_Value_Id, len(members), checker.allocator)
|
||||||
@@ -3659,7 +3798,7 @@ eval_static_comptime_value :: proc(
|
|||||||
values: []Comptime_Value = nil,
|
values: []Comptime_Value = nil,
|
||||||
diagnose := false,
|
diagnose := false,
|
||||||
) -> (Comptime_Value, bool) {
|
) -> (Comptime_Value, bool) {
|
||||||
state := ct_state_make(checker, pkg, file, values=values, diagnose=false)
|
state := ct_state_make(checker, pkg, file, values=values, diagnose=diagnose)
|
||||||
defer ct_state_destroy(&state)
|
defer ct_state_destroy(&state)
|
||||||
id, flow, ok := ct_eval_expr(&state, expr, declared, 0)
|
id, flow, ok := ct_eval_expr(&state, expr, declared, 0)
|
||||||
if ok && flow.kind == .Normal {
|
if ok && flow.kind == .Normal {
|
||||||
|
|||||||
@@ -271,6 +271,7 @@ Global :: struct {
|
|||||||
expr: Expr_Id,
|
expr: Expr_Id,
|
||||||
static_value: i64,
|
static_value: i64,
|
||||||
is_static: bool,
|
is_static: bool,
|
||||||
|
eager: bool,
|
||||||
external: bool,
|
external: bool,
|
||||||
writable: bool,
|
writable: bool,
|
||||||
dependencies: [dynamic]Global_Id,
|
dependencies: [dynamic]Global_Id,
|
||||||
|
|||||||
@@ -179,6 +179,7 @@ Global :: struct {
|
|||||||
link_name: string,
|
link_name: string,
|
||||||
type: types.Type,
|
type: types.Type,
|
||||||
is_static: bool,
|
is_static: bool,
|
||||||
|
eager: bool,
|
||||||
external: bool,
|
external: bool,
|
||||||
writable: bool,
|
writable: bool,
|
||||||
static_value: i64,
|
static_value: i64,
|
||||||
|
|||||||
@@ -2642,7 +2642,12 @@ emit_constructor :: proc(emitter: ^Emitter) {
|
|||||||
)
|
)
|
||||||
strings.write_string(&emitter.builder, "define internal void @bro.init() {\nentry:\n")
|
strings.write_string(&emitter.builder, "define internal void @bro.init() {\nentry:\n")
|
||||||
for global, global_id in emitter.module.globals {
|
for global, global_id in emitter.module.globals {
|
||||||
if !global.is_static && !global.external && !global.problematic {
|
if global.eager && !global.is_static && !global.external && !global.problematic {
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%g%d = call %s @bro.get.%d()\n", global_id, llvm_type(global.type, &emitter.module.types), global_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for global, global_id in emitter.module.globals {
|
||||||
|
if !global.eager && !global.is_static && !global.external && !global.problematic {
|
||||||
fmt.sbprintf(&emitter.builder, " %%g%d = call %s @bro.get.%d()\n", global_id, llvm_type(global.type, &emitter.module.types), global_id)
|
fmt.sbprintf(&emitter.builder, " %%g%d = call %s @bro.get.%d()\n", global_id, llvm_type(global.type, &emitter.module.types), global_id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1844,6 +1844,7 @@ lower :: proc(hir_module: ^hir.Module, allocator := context.allocator) -> ir.Mod
|
|||||||
link_name=fmt.aprintf("%s", global.link_name, allocator=allocator),
|
link_name=fmt.aprintf("%s", global.link_name, allocator=allocator),
|
||||||
type=global.type,
|
type=global.type,
|
||||||
is_static=global.is_static,
|
is_static=global.is_static,
|
||||||
|
eager=global.eager,
|
||||||
external=global.external,
|
external=global.external,
|
||||||
writable=global.writable,
|
writable=global.writable,
|
||||||
static_value=global.static_value,
|
static_value=global.static_value,
|
||||||
|
|||||||
+219
-3
@@ -4990,6 +4990,10 @@ missing func() i32 {
|
|||||||
if true {
|
if true {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
escape_slice func() []i32 {
|
||||||
|
values [2]mut i32 = [1, 2]
|
||||||
|
return values[..]
|
||||||
|
}
|
||||||
GLOBAL :: 1
|
GLOBAL :: 1
|
||||||
main func() void {
|
main func() void {
|
||||||
runtime i32 = 1
|
runtime i32 = 1
|
||||||
@@ -5000,6 +5004,30 @@ main func() void {
|
|||||||
yield callback()
|
yield callback()
|
||||||
}
|
}
|
||||||
_ = $&GLOBAL
|
_ = $&GLOBAL
|
||||||
|
_ = ${
|
||||||
|
values [2]mut i32 = [1, 2]
|
||||||
|
pointer *mut i32 :: (&values).ptr
|
||||||
|
yield pointer
|
||||||
|
}
|
||||||
|
_ = ${
|
||||||
|
values [2]mut i32 = [1, 2]
|
||||||
|
yield values[1..].ptr
|
||||||
|
}
|
||||||
|
_ = ${
|
||||||
|
values [2]mut i32 = [1, 2]
|
||||||
|
yield &values[0]
|
||||||
|
}
|
||||||
|
_ = ${
|
||||||
|
values [2]mut i32 = [1, 2]
|
||||||
|
view []mut i32 = values[..]
|
||||||
|
yield view
|
||||||
|
}
|
||||||
|
_ = ${
|
||||||
|
values [2]mut i32 = [1, 2]
|
||||||
|
view []i32 = values[1..]
|
||||||
|
yield view
|
||||||
|
}
|
||||||
|
_ = $escape_slice()
|
||||||
_ = $spin()
|
_ = $spin()
|
||||||
_ = $missing()
|
_ = $missing()
|
||||||
_ = ${
|
_ = ${
|
||||||
@@ -5021,7 +5049,9 @@ main func() void {
|
|||||||
|
|
||||||
found_runtime := false
|
found_runtime := false
|
||||||
runtime_only_count := 0
|
runtime_only_count := 0
|
||||||
found_pointer := false
|
pointer_errors := 0
|
||||||
|
slice_errors := 0
|
||||||
|
found_expired := false
|
||||||
found_quota := false
|
found_quota := false
|
||||||
found_missing := false
|
found_missing := false
|
||||||
found_yield := false
|
found_yield := false
|
||||||
@@ -5029,14 +5059,18 @@ main func() void {
|
|||||||
message := diagnostic.message
|
message := diagnostic.message
|
||||||
found_runtime = found_runtime || strings.contains(message, "unresolved comptime value 'runtime'")
|
found_runtime = found_runtime || strings.contains(message, "unresolved comptime value 'runtime'")
|
||||||
runtime_only_count += 1 if strings.contains(message, "runtime-only") else 0
|
runtime_only_count += 1 if strings.contains(message, "runtime-only") else 0
|
||||||
found_pointer = found_pointer || strings.contains(message, "comptime storage pointers and slices cannot materialize as runtime memory")
|
pointer_errors += 1 if strings.contains(message, "only immutable pointers to whole comptime arrays can materialize as runtime memory") else 0
|
||||||
|
slice_errors += 1 if strings.contains(message, "only immutable full-array comptime slices can materialize as runtime memory") else 0
|
||||||
|
found_expired = found_expired || strings.contains(message, "expired storage")
|
||||||
found_quota = found_quota || strings.contains(message, "comptime evaluation exceeded the step quota")
|
found_quota = found_quota || strings.contains(message, "comptime evaluation exceeded the step quota")
|
||||||
found_missing = found_missing || strings.contains(message, "did not return a value")
|
found_missing = found_missing || strings.contains(message, "did not return a value")
|
||||||
found_yield = found_yield || strings.contains(message, "comptime block must yield a value")
|
found_yield = found_yield || strings.contains(message, "comptime block must yield a value")
|
||||||
}
|
}
|
||||||
testing.expect(t, found_runtime)
|
testing.expect(t, found_runtime)
|
||||||
testing.expect(t, runtime_only_count >= 2)
|
testing.expect(t, runtime_only_count >= 2)
|
||||||
testing.expect(t, found_pointer)
|
testing.expect(t, pointer_errors >= 4)
|
||||||
|
testing.expect(t, slice_errors >= 2)
|
||||||
|
testing.expect(t, found_expired)
|
||||||
testing.expect(t, found_quota)
|
testing.expect(t, found_quota)
|
||||||
testing.expect(t, found_missing)
|
testing.expect(t, found_missing)
|
||||||
testing.expect(t, found_yield)
|
testing.expect(t, found_yield)
|
||||||
@@ -14706,6 +14740,188 @@ std_meta_tests_compile_and_run :: proc(t: ^testing.T) {
|
|||||||
testing.expect_value(t, state.exit_code, 0)
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
array_reflection_reports_child_and_logical_length :: proc(t: ^testing.T) {
|
||||||
|
directory := "/tmp/brolang-test-array-reflection"
|
||||||
|
main_path := "/tmp/brolang-test-array-reflection/main.bro"
|
||||||
|
output := "/tmp/brolang-test-array-reflection-output"
|
||||||
|
text := `meta :: import "@std/meta"
|
||||||
|
|
||||||
|
Alias :: alias [3]u16
|
||||||
|
|
||||||
|
matches func($Array, $Child type, $len usize) bool {
|
||||||
|
match typeinfo!(Array) {
|
||||||
|
.array |info|: return info.child == Child and info.len == len
|
||||||
|
else: return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main func() i32 {
|
||||||
|
if !$(matches([4]i32, i32, 4)) { return 1 }
|
||||||
|
if !$(matches([0]bool, bool, 0)) { return 2 }
|
||||||
|
if !$(matches(Alias, u16, 3)) { return 3 }
|
||||||
|
if !$(matches([2]mut i64, i64, 2)) { return 4 }
|
||||||
|
if !$(matches([2;0]u8, u8, 2)) { return 5 }
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
`
|
||||||
|
_ = os2.remove_all(directory)
|
||||||
|
defer _ = os2.remove_all(directory)
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
testing.expect(t, os.make_directory(directory) == nil)
|
||||||
|
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)text))
|
||||||
|
testing.expect_value(t, compiler_core.compile_package(
|
||||||
|
directory, output, nil, target.DEFAULT, cimport.Options{}, ".",
|
||||||
|
), 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
static_string_map_infers_array_size_and_preserves_promoted_backing :: proc(t: ^testing.T) {
|
||||||
|
directory := "/tmp/brolang-test-static-string-map"
|
||||||
|
main_path := "/tmp/brolang-test-static-string-map/main.bro"
|
||||||
|
output := "/tmp/brolang-test-static-string-map-output"
|
||||||
|
text := `std :: import "@std"
|
||||||
|
static_string_map :: import "@std/static_string_map"
|
||||||
|
|
||||||
|
TokenKind :: enum {
|
||||||
|
keyword_if
|
||||||
|
keyword_else
|
||||||
|
keyword_for
|
||||||
|
keyword_return
|
||||||
|
keyword_while
|
||||||
|
}
|
||||||
|
|
||||||
|
keywords std.StaticStringMap(TokenKind) = static_string_map.init([
|
||||||
|
{"if", .keyword_if},
|
||||||
|
{"else", .keyword_else},
|
||||||
|
{"for", .keyword_for},
|
||||||
|
{"return", .keyword_return},
|
||||||
|
])
|
||||||
|
fallback :: static_string_map.init(TokenKind, [
|
||||||
|
{"while", .keyword_while},
|
||||||
|
])
|
||||||
|
empty std.StaticStringMap(TokenKind) = static_string_map.init([])
|
||||||
|
numbers []i32 = ${
|
||||||
|
values [3]mut i32 = [7, 8, 9]
|
||||||
|
yield values[..]
|
||||||
|
}
|
||||||
|
|
||||||
|
main func() i32 {
|
||||||
|
if keywords.keys.len != 4 or keywords.values.len != 4 or keywords.len_indexes.len != 7 { return 1 }
|
||||||
|
if keywords.min_len != 2 or keywords.max_len != 6 { return 17 }
|
||||||
|
if empty.keys.len != 0 or empty.values.len != 0 or empty.len_indexes.len != 0 { return 18 }
|
||||||
|
if numbers.len != 3 or numbers[0] != 7 or numbers[2] != 9 { return 19 }
|
||||||
|
if static_string_map.get(&keywords, "if") |value| {
|
||||||
|
if value != TokenKind.keyword_if { return 2 }
|
||||||
|
} else { return 3 }
|
||||||
|
if static_string_map.get(&keywords, "else") |value| {
|
||||||
|
if value != TokenKind.keyword_else { return 4 }
|
||||||
|
} else { return 5 }
|
||||||
|
if static_string_map.get(&keywords, "for") |value| {
|
||||||
|
if value != TokenKind.keyword_for { return 6 }
|
||||||
|
} else { return 7 }
|
||||||
|
if static_string_map.get(&keywords, "return") |value| {
|
||||||
|
if value != TokenKind.keyword_return { return 8 }
|
||||||
|
} else { return 9 }
|
||||||
|
if static_string_map.get(&keywords, "no") |_| { return 10 }
|
||||||
|
if static_string_map.get(&keywords, "four") |_| { return 11 }
|
||||||
|
if static_string_map.get(&keywords, "x") |_| { return 12 }
|
||||||
|
if static_string_map.get(&keywords, "longer-than-any-key") |_| { return 13 }
|
||||||
|
if static_string_map.get(&empty, "if") |_| { return 14 }
|
||||||
|
if static_string_map.get(&fallback, "while") |value| {
|
||||||
|
if value != TokenKind.keyword_while { return 15 }
|
||||||
|
} else { return 16 }
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
`
|
||||||
|
_ = os2.remove_all(directory)
|
||||||
|
defer _ = os2.remove_all(directory)
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
testing.expect(t, os.make_directory(directory) == nil)
|
||||||
|
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)text))
|
||||||
|
testing.expect_value(t, compiler_core.compile_package(
|
||||||
|
directory, output, nil, target.DEFAULT, cimport.Options{}, ".",
|
||||||
|
), 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
static_string_map_reports_duplicate_and_malformed_entries :: proc(t: ^testing.T) {
|
||||||
|
directory := "/tmp/brolang-test-static-string-map-errors"
|
||||||
|
main_path := "/tmp/brolang-test-static-string-map-errors/main.bro"
|
||||||
|
_ = os2.remove_all(directory)
|
||||||
|
defer _ = os2.remove_all(directory)
|
||||||
|
testing.expect(t, os.make_directory(directory) == nil)
|
||||||
|
|
||||||
|
duplicate_text := `std :: import "@std"
|
||||||
|
static_string_map :: import "@std/static_string_map"
|
||||||
|
TokenKind :: enum { keyword_if, keyword_else }
|
||||||
|
bad std.StaticStringMap(TokenKind) = static_string_map.init([
|
||||||
|
{"if", .keyword_if},
|
||||||
|
{"if", .keyword_else},
|
||||||
|
])
|
||||||
|
main func() void {}
|
||||||
|
`
|
||||||
|
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)duplicate_text))
|
||||||
|
{
|
||||||
|
sources := source.init_store()
|
||||||
|
defer source.destroy_store(&sources)
|
||||||
|
diagnostics := source.init_store_diagnostics(&sources)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
module, loaded := loader.load(directory, &sources, &diagnostics, &symbols, project_root_path=".")
|
||||||
|
defer ast.destroy_module(&module)
|
||||||
|
hir_module := checker.check(&module, &diagnostics, &symbols)
|
||||||
|
defer hir.destroy_module(&hir_module)
|
||||||
|
testing.expect(t, loaded)
|
||||||
|
found := false
|
||||||
|
for diagnostic in diagnostics.items {
|
||||||
|
found = found || strings.contains(diagnostic.message, "duplicate static string map key")
|
||||||
|
}
|
||||||
|
testing.expect(t, found)
|
||||||
|
}
|
||||||
|
|
||||||
|
malformed_text := `std :: import "@std"
|
||||||
|
static_string_map :: import "@std/static_string_map"
|
||||||
|
TokenKind :: enum { keyword_if }
|
||||||
|
bad std.StaticStringMap(TokenKind) = static_string_map.init([
|
||||||
|
{123, .keyword_if},
|
||||||
|
])
|
||||||
|
bad_value std.StaticStringMap(TokenKind) = static_string_map.init([
|
||||||
|
{"if", "bad"},
|
||||||
|
])
|
||||||
|
main func() void {}
|
||||||
|
`
|
||||||
|
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)malformed_text))
|
||||||
|
{
|
||||||
|
sources := source.init_store()
|
||||||
|
defer source.destroy_store(&sources)
|
||||||
|
diagnostics := source.init_store_diagnostics(&sources)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
module, loaded := loader.load(directory, &sources, &diagnostics, &symbols, project_root_path=".")
|
||||||
|
defer ast.destroy_module(&module)
|
||||||
|
hir_module := checker.check(&module, &diagnostics, &symbols)
|
||||||
|
defer hir.destroy_module(&hir_module)
|
||||||
|
testing.expect(t, loaded)
|
||||||
|
found_key := false
|
||||||
|
found_value := false
|
||||||
|
for diagnostic in diagnostics.items {
|
||||||
|
found_key = found_key || strings.contains(diagnostic.message, "cannot implicitly convert i8 to []u8 at comptime")
|
||||||
|
found_value = found_value ||
|
||||||
|
strings.contains(diagnostic.message, "cannot implicitly convert") &&
|
||||||
|
strings.contains(diagnostic.message, "to TokenKind at comptime")
|
||||||
|
}
|
||||||
|
testing.expect(t, found_key)
|
||||||
|
testing.expect(t, found_value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
noreturn_functions_function_pointers_and_peer_types_compile_and_run :: proc(t: ^testing.T) {
|
noreturn_functions_function_pointers_and_peer_types_compile_and_run :: proc(t: ^testing.T) {
|
||||||
directory := "/tmp/brolang-test-noreturn"
|
directory := "/tmp/brolang-test-noreturn"
|
||||||
|
|||||||
+6
-4
@@ -1,6 +1,8 @@
|
|||||||
Layout :: enum {
|
Layout :: enum { auto c }
|
||||||
auto
|
|
||||||
c
|
ArrayInfo :: struct {
|
||||||
|
child type
|
||||||
|
len usize
|
||||||
}
|
}
|
||||||
|
|
||||||
FieldInfo :: struct {
|
FieldInfo :: struct {
|
||||||
@@ -28,7 +30,7 @@ TypeInfo :: union(enum) {
|
|||||||
bool void
|
bool void
|
||||||
integer void
|
integer void
|
||||||
float void
|
float void
|
||||||
array void
|
array ArrayInfo
|
||||||
pointer void
|
pointer void
|
||||||
slice void
|
slice void
|
||||||
range void
|
range void
|
||||||
|
|||||||
@@ -7,6 +7,22 @@ TestTokenKind :: enum(u8) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TestNames :: alias EnumFieldStruct(TestTokenKind, ?[]u8, some!(null))
|
TestNames :: alias EnumFieldStruct(TestTokenKind, ?[]u8, some!(null))
|
||||||
|
TestArrayAlias :: alias [3]u16
|
||||||
|
|
||||||
|
hide array_info_matches func($Array, $Child type, $len usize) bool {
|
||||||
|
match typeinfo!(Array) {
|
||||||
|
.array |info|: return info.child == Child and info.len == len
|
||||||
|
else: return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
array_reflection_exposes_child_and_logical_length test {
|
||||||
|
try testing.expect($(array_info_matches([4]i32, i32, 4)))
|
||||||
|
try testing.expect($(array_info_matches([0]bool, bool, 0)))
|
||||||
|
try testing.expect($(array_info_matches(TestArrayAlias, u16, 3)))
|
||||||
|
try testing.expect($(array_info_matches([2]mut i64, i64, 2)))
|
||||||
|
try testing.expect($(array_info_matches([2;0]u8, u8, 2)))
|
||||||
|
}
|
||||||
|
|
||||||
enum_field_struct_defaults test {
|
enum_field_struct_defaults test {
|
||||||
names TestNames = {
|
names TestNames = {
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
import "@std/mem"
|
||||||
|
|
||||||
|
StaticStringMap func($V type) type {
|
||||||
|
return struct {
|
||||||
|
keys [][]u8
|
||||||
|
values []V
|
||||||
|
len_indexes []u32
|
||||||
|
min_len u32
|
||||||
|
max_len u32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hide Pair func($V type) type {
|
||||||
|
return struct { []u8, V }
|
||||||
|
}
|
||||||
|
|
||||||
|
init func($V type, $N usize, $entries [N]Pair(V)) StaticStringMap(V) {
|
||||||
|
return ${
|
||||||
|
if N > usize(maxval!(u32)) {
|
||||||
|
compile_error!("static string map has too many entries")
|
||||||
|
}
|
||||||
|
|
||||||
|
keys [N]mut []u8 = undefined
|
||||||
|
values [N]mut V = undefined
|
||||||
|
for entries |entry, i| {
|
||||||
|
if entry.0.len > usize(maxval!(u32)) {
|
||||||
|
compile_error!("static string map key is too long")
|
||||||
|
}
|
||||||
|
for (usize(0))..i |prior| {
|
||||||
|
other :: entries[prior].0
|
||||||
|
equal bool = entry.0.len == other.len
|
||||||
|
byte_index usize = 0
|
||||||
|
while equal and byte_index < entry.0.len : byte_index += 1 {
|
||||||
|
equal = entry.0[byte_index] == other[byte_index]
|
||||||
|
}
|
||||||
|
if equal {
|
||||||
|
compile_error!("duplicate static string map key")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
keys[i] = entry.0
|
||||||
|
values[i] = entry.1
|
||||||
|
}
|
||||||
|
|
||||||
|
if N == 0 {
|
||||||
|
len_indexes [0]mut u32 = undefined
|
||||||
|
yield StaticStringMap(V) {
|
||||||
|
keys = keys[..],
|
||||||
|
values = values[..],
|
||||||
|
len_indexes = len_indexes[..],
|
||||||
|
min_len = 0,
|
||||||
|
max_len = 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ponytail: insertion sort is compile-time O(N²); replace if large maps affect builds.
|
||||||
|
i usize = 1
|
||||||
|
while i < N : i += 1 {
|
||||||
|
key :: keys[i]
|
||||||
|
value :: values[i]
|
||||||
|
j usize = i
|
||||||
|
while j > 0 and keys[j - 1].len > key.len : j -= 1 {
|
||||||
|
keys[j] = keys[j - 1]
|
||||||
|
values[j] = values[j - 1]
|
||||||
|
}
|
||||||
|
keys[j] = key
|
||||||
|
values[j] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
min_len u32 :: u32(keys[0].len)
|
||||||
|
max_len u32 :: u32(keys[N - 1].len)
|
||||||
|
len_indexes [usize(max_len) + 1]mut u32 = undefined
|
||||||
|
entry_index usize = 0
|
||||||
|
length usize = 0
|
||||||
|
while length <= usize(max_len) : length += 1 {
|
||||||
|
while entry_index < N and keys[entry_index].len < length : entry_index += 1 {}
|
||||||
|
len_indexes[length] = u32(entry_index)
|
||||||
|
}
|
||||||
|
|
||||||
|
yield StaticStringMap(V) {
|
||||||
|
keys = keys[..],
|
||||||
|
values = values[..],
|
||||||
|
len_indexes = len_indexes[..],
|
||||||
|
min_len = min_len,
|
||||||
|
max_len = max_len,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get func($V type, map @StaticStringMap(V), key []u8) ?V {
|
||||||
|
if map.keys.len == 0 or key.len > usize(maxval!(u32)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
length u32 :: u32(key.len)
|
||||||
|
if length < map.min_len or length > map.max_len {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
index usize = usize(map.len_indexes[usize(length)])
|
||||||
|
while index < map.keys.len {
|
||||||
|
candidate :: map.keys[index]
|
||||||
|
if candidate.len != key.len {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if mem.eql(u8, candidate, key) {
|
||||||
|
return map.values[index]
|
||||||
|
}
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import "@std/testing"
|
||||||
|
|
||||||
|
TokenKind :: enum {
|
||||||
|
keyword_if
|
||||||
|
keyword_else
|
||||||
|
keyword_for
|
||||||
|
keyword_return
|
||||||
|
}
|
||||||
|
|
||||||
|
keywords StaticStringMap(TokenKind) = init([
|
||||||
|
{"return", .keyword_return},
|
||||||
|
{"if", .keyword_if},
|
||||||
|
{"for", .keyword_for},
|
||||||
|
{"else", .keyword_else},
|
||||||
|
])
|
||||||
|
|
||||||
|
handles_length_bucket_lookups test {
|
||||||
|
try testing.expect(keywords.keys.len == 4)
|
||||||
|
try testing.expect(keywords.values.len == keywords.keys.len)
|
||||||
|
try testing.expect(keywords.len_indexes.len == 7)
|
||||||
|
try testing.expect_equal(some!(TokenKind.keyword_if), get(&keywords, "if"))
|
||||||
|
try testing.expect_equal(some!(TokenKind.keyword_else), get(&keywords, "else"))
|
||||||
|
try testing.expect_equal(some!(TokenKind.keyword_for), get(&keywords, "for"))
|
||||||
|
try testing.expect_equal(some!(TokenKind.keyword_return), get(&keywords, "return"))
|
||||||
|
try testing.expect_equal(null, get(&keywords, "no"))
|
||||||
|
try testing.expect_equal(null, get(&keywords, "four"))
|
||||||
|
try testing.expect_equal(null, get(&keywords, "longer-than-any-key"))
|
||||||
|
}
|
||||||
|
|
||||||
|
handles_empty_maps test {
|
||||||
|
empty StaticStringMap(TokenKind) = init([])
|
||||||
|
try testing.expect(empty.keys.len == 0)
|
||||||
|
try testing.expect(empty.values.len == 0)
|
||||||
|
try testing.expect(empty.len_indexes.len == 0)
|
||||||
|
try testing.expect_equal(null, get(&empty, "if"))
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import "io"
|
import "io"
|
||||||
import "enums"
|
import "enums"
|
||||||
import "arraylist"
|
import "arraylist"
|
||||||
|
import "static_string_map"
|
||||||
|
|
||||||
Io :: alias io.Io
|
Io :: alias io.Io
|
||||||
ArrayList :: alias arraylist.ArrayList
|
ArrayList :: alias arraylist.ArrayList
|
||||||
EnumMap :: alias enums.EnumMap
|
EnumMap :: alias enums.EnumMap
|
||||||
|
StaticStringMap :: alias static_string_map.StaticStringMap
|
||||||
|
|||||||
Reference in New Issue
Block a user