struct type construction
This commit is contained in:
+205
-22
@@ -166,6 +166,15 @@ Generated_Type_Entry :: struct {
|
||||
result: types.Type,
|
||||
pkg: ast.Package_Id,
|
||||
file: ast.File_Id,
|
||||
defaults: []Ct_Value_Id,
|
||||
}
|
||||
|
||||
Resolved_Field_Default :: struct {
|
||||
expr: ast.Expr_Id,
|
||||
pkg: ast.Package_Id,
|
||||
file: ast.File_Id,
|
||||
static_value: Ct_Value_Id,
|
||||
span: source.Span,
|
||||
}
|
||||
|
||||
Type_Factory_Origin :: struct {
|
||||
@@ -1227,6 +1236,9 @@ is_comptime_value_type :: proc(checker: ^Checker, value: types.Type, depth := 0)
|
||||
if depth > 256 || !types.is_valid(value) {
|
||||
return false
|
||||
}
|
||||
if is_type_metatype_syntax(checker, value) {
|
||||
return true
|
||||
}
|
||||
if is_runtime_type(checker, value) {
|
||||
return true
|
||||
}
|
||||
@@ -1930,12 +1942,15 @@ call_mapping_mode :: proc(function: ast.Function, mapping: []int) -> Call_Argume
|
||||
return .Explicit
|
||||
}
|
||||
|
||||
comptime_binding_index :: proc(function: ast.Function, _: int, name: symbol.Id) -> (int, bool) {
|
||||
comptime_binding_index :: proc(function: ast.Function, prefix: int, name: symbol.Id) -> (int, bool) {
|
||||
ordinal := 0
|
||||
for param in function.params {
|
||||
if !param.comptime_value {
|
||||
continue
|
||||
}
|
||||
if ordinal >= prefix {
|
||||
return -1, false
|
||||
}
|
||||
if param.name == name {
|
||||
return ordinal, true
|
||||
}
|
||||
@@ -2299,6 +2314,7 @@ type_pattern_mentions_comptime :: proc(
|
||||
prefix: int,
|
||||
pattern: types.Type,
|
||||
depth := 0,
|
||||
type_params_only := false,
|
||||
) -> bool {
|
||||
if depth > 64 {
|
||||
return false
|
||||
@@ -2309,7 +2325,13 @@ type_pattern_mentions_comptime :: proc(
|
||||
}
|
||||
if item.qualifier == 0 && item.name != 0 {
|
||||
if _, found := comptime_binding_index(function, prefix, symbol.Id(item.name)); found {
|
||||
return true
|
||||
if !type_params_only {
|
||||
return true
|
||||
}
|
||||
param, param_ok := comptime_param_for_name(function, symbol.Id(item.name))
|
||||
if param_ok && is_comptime_type_param(checker, param) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
if item.kind == .Array && item.unresolved_count {
|
||||
@@ -2318,7 +2340,13 @@ type_pattern_mentions_comptime :: proc(
|
||||
expr := checker.ast_module.exprs[expr_id]
|
||||
if expr.kind == .Name && !symbol.is_valid(expr.qualifier) {
|
||||
if _, found := comptime_binding_index(function, prefix, expr.name); found {
|
||||
return true
|
||||
if !type_params_only {
|
||||
return true
|
||||
}
|
||||
param, param_ok := comptime_param_for_name(function, expr.name)
|
||||
if param_ok && is_comptime_type_param(checker, param) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2333,21 +2361,27 @@ type_pattern_mentions_comptime :: proc(
|
||||
arg := checker.ast_module.exprs[arg_id]
|
||||
if arg.kind == .Name && !symbol.is_valid(arg.qualifier) {
|
||||
if _, found := comptime_binding_index(function, prefix, arg.name); found {
|
||||
return true
|
||||
if !type_params_only {
|
||||
return true
|
||||
}
|
||||
param, param_ok := comptime_param_for_name(function, arg.name)
|
||||
if param_ok && is_comptime_type_param(checker, param) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if types.is_valid(item.child) && type_pattern_mentions_comptime(checker, function, prefix, item.child, depth+1) {
|
||||
if types.is_valid(item.child) && type_pattern_mentions_comptime(checker, function, prefix, item.child, depth+1, type_params_only) {
|
||||
return true
|
||||
}
|
||||
if types.is_valid(item.extra) && type_pattern_mentions_comptime(checker, function, prefix, item.extra, depth+1) {
|
||||
if types.is_valid(item.extra) && type_pattern_mentions_comptime(checker, function, prefix, item.extra, depth+1, type_params_only) {
|
||||
return true
|
||||
}
|
||||
if item.kind == .Function {
|
||||
for field in types.params_for(&checker.module.types, pattern) {
|
||||
if type_pattern_mentions_comptime(checker, function, prefix, field.type, depth+1) {
|
||||
if type_pattern_mentions_comptime(checker, function, prefix, field.type, depth+1, type_params_only) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -2690,6 +2724,10 @@ infer_call_comptime_values :: proc(
|
||||
) && matched
|
||||
}
|
||||
}
|
||||
all_bound = true
|
||||
for value_bound in bound {
|
||||
all_bound = all_bound && value_bound
|
||||
}
|
||||
// Concrete arguments bind first. Numeric constants are contextual and therefore
|
||||
// only contribute their default type after stronger evidence has had a chance.
|
||||
weak_passes := [2]bool{false, true}
|
||||
@@ -2709,6 +2747,14 @@ infer_call_comptime_values :: proc(
|
||||
if !type_pattern_mentions_comptime(checker, function, prefix, function.params[param_index].type) {
|
||||
continue
|
||||
}
|
||||
// Once the result type has fixed every comptime parameter, an anonymous
|
||||
// keyed record must be checked against the specialized parameter type.
|
||||
// Its provisional structural type intentionally contains only the supplied
|
||||
// fields, so comparing that type here would reject omitted defaulted fields.
|
||||
arg_expr := checker.ast_module.exprs[arg_id]
|
||||
if all_bound && arg_expr.kind == .Struct_Literal && !arg_expr.tuple && !symbol.is_valid(arg_expr.name) {
|
||||
continue
|
||||
}
|
||||
if weak {
|
||||
if item, ok := types.node(&checker.module.types, function.params[param_index].type); ok &&
|
||||
item.qualifier == 0 && item.name != 0 {
|
||||
@@ -2951,6 +2997,19 @@ resolve_type_factory_call :: proc(checker: ^Checker, expr_id: ast.Expr_Id, pkg:
|
||||
source.add(checker.diagnostics, expr.span, "type position requires a direct type-factory call")
|
||||
return types.INVALID
|
||||
}
|
||||
if expr.intrinsic {
|
||||
if !is_intrinsic_call(checker, expr, "struct_type") {
|
||||
return types.INVALID
|
||||
}
|
||||
state := ct_state_make(checker, pkg, file, values=checker.current_comptime_values)
|
||||
value, flow, ok := ct_eval_call_expr(&state, expr, types.INVALID, 0, expr_id)
|
||||
result := types.INVALID
|
||||
if ok && flow.kind == .Normal && value != INVALID_CT_VALUE && int(value) < len(state.values) && state.values[value].kind == .Type {
|
||||
result = types.Type(state.values[value].index)
|
||||
}
|
||||
ct_state_destroy(&state)
|
||||
return result
|
||||
}
|
||||
target_pkg, available := expr_package(checker, expr, pkg, file, true)
|
||||
if !available {
|
||||
_ = add_package_resolution_diagnostic(checker, expr, file)
|
||||
@@ -3009,7 +3068,7 @@ resolve_type_factory_call :: proc(checker: ^Checker, expr_id: ast.Expr_Id, pkg:
|
||||
resolving=true,
|
||||
})
|
||||
state := ct_state_make(checker, pkg, file)
|
||||
value, flow, eval_ok := ct_eval_call_expr(&state, expr, function.result, 0)
|
||||
value, flow, eval_ok := ct_eval_call_expr(&state, expr, function.result, 0, expr_id)
|
||||
result := types.INVALID
|
||||
if eval_ok && flow.kind == .Normal && value != INVALID_CT_VALUE && int(value) < len(state.values) && state.values[value].kind == .Type {
|
||||
result = types.Type(state.values[value].index)
|
||||
@@ -3502,12 +3561,16 @@ validate_declarations :: proc(checker: ^Checker) {
|
||||
signature_poisoned := function.diagnostic != source.INVALID_DIAGNOSTIC
|
||||
locals: [dynamic]symbol.Id
|
||||
locals.allocator = checker.allocator
|
||||
comptime_prefix := 0
|
||||
for param in function.params {
|
||||
param_type := types.INVALID
|
||||
if param.comptime_value || !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,
|
||||
@@ -3516,7 +3579,7 @@ validate_declarations :: proc(checker: ^Checker) {
|
||||
)
|
||||
}
|
||||
if !is_type_metatype_syntax(checker, param.type) &&
|
||||
!is_comptime_value_type(checker, param_type) && param_type != types.RANGE {
|
||||
!dependent && !is_comptime_value_type(checker, param_type) && param_type != types.RANGE {
|
||||
checker.template_diagnostics[function_id] = source.addf(
|
||||
checker.diagnostics,
|
||||
param.span,
|
||||
@@ -3538,6 +3601,9 @@ validate_declarations :: proc(checker: ^Checker) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if param.comptime_value {
|
||||
comptime_prefix += 1
|
||||
}
|
||||
if param.type == types.VOID {
|
||||
source.add(
|
||||
checker.diagnostics,
|
||||
@@ -4594,6 +4660,26 @@ infer_compound_expr :: proc(
|
||||
}
|
||||
return value
|
||||
}
|
||||
if !types.is_valid(value) {
|
||||
fields := make([]types.Field, len(expr.args), checker.allocator)
|
||||
defer delete(fields, checker.allocator)
|
||||
valid := true
|
||||
for keyed, index in expr.args {
|
||||
keyed_expr := checker.ast_module.exprs[keyed]
|
||||
fields[index].name = u32(keyed_expr.name)
|
||||
if keyed_expr.left == ast.INVALID_EXPR {
|
||||
valid = false
|
||||
continue
|
||||
}
|
||||
for previous in fields[:index] {
|
||||
valid = valid && previous.name != fields[index].name
|
||||
}
|
||||
fields[index].type = infer_nested_expr(
|
||||
checker, keyed_expr.left, locals, pkg, file, demanded, local_types,
|
||||
)
|
||||
}
|
||||
return types.struct_anonymous(store, fields) if valid else types.INVALID
|
||||
}
|
||||
fields := types.fields_for(store, value)
|
||||
item, item_ok := types.node(store, value)
|
||||
initialized := make([]bool, len(fields), checker.allocator)
|
||||
@@ -4627,6 +4713,9 @@ infer_compound_expr :: proc(
|
||||
continue
|
||||
}
|
||||
if field_default, default_values, ok := find_struct_field_default(checker, value, symbol.Id(field.name)); ok {
|
||||
if field_default.static_value != INVALID_CT_VALUE {
|
||||
continue
|
||||
}
|
||||
previous := checker.current_comptime_values
|
||||
checker.current_comptime_values = default_values
|
||||
_ = infer_nested_expr(
|
||||
@@ -4893,6 +4982,17 @@ infer_expr :: proc(
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
if is_intrinsic_call(checker, expr, "some") {
|
||||
if len(expr.args) == 1 && types.is_optional(frame.expected, &checker.module.types) {
|
||||
child := types.child_type(frame.expected, &checker.module.types)
|
||||
_ = infer_nested_expr(checker, expr.args[0], locals, pkg, file, demanded, local_types, child)
|
||||
last = frame.expected
|
||||
} else {
|
||||
last = types.INVALID
|
||||
}
|
||||
_ = pop(&stack)
|
||||
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) {
|
||||
@@ -6925,12 +7025,18 @@ find_struct_field_default :: proc(
|
||||
checker: ^Checker,
|
||||
struct_type: types.Type,
|
||||
name: symbol.Id,
|
||||
) -> (ast.Struct_Field_Default, []Comptime_Value, bool) {
|
||||
) -> (Resolved_Field_Default, []Comptime_Value, bool) {
|
||||
resolved := types.resolve_alias(struct_type, &checker.module.types)
|
||||
for field_default in checker.ast_module.struct_field_defaults {
|
||||
if types.resolve_alias(field_default.record, &checker.module.types) == resolved &&
|
||||
field_default.field == name {
|
||||
return field_default, nil, true
|
||||
return Resolved_Field_Default{
|
||||
expr=field_default.expr,
|
||||
pkg=field_default.pkg,
|
||||
file=field_default.file,
|
||||
static_value=INVALID_CT_VALUE,
|
||||
span=checker.ast_module.exprs[field_default.expr].span,
|
||||
}, nil, true
|
||||
}
|
||||
}
|
||||
for entry in checker.generated_types {
|
||||
@@ -6941,16 +7047,30 @@ find_struct_field_default :: proc(
|
||||
expr := checker.ast_module.exprs[entry.expr]
|
||||
fields := types.fields_for(&checker.module.types, resolved)
|
||||
for field, index in fields {
|
||||
if field.name != u32(name) || index >= len(expr.args) || expr.args[index] == ast.INVALID_EXPR {
|
||||
if field.name != u32(name) {
|
||||
continue
|
||||
}
|
||||
return ast.Struct_Field_Default{
|
||||
record=resolved,
|
||||
field=name,
|
||||
expr=expr.args[index],
|
||||
pkg=entry.pkg,
|
||||
file=entry.file,
|
||||
}, entry.values, true
|
||||
if index < len(entry.defaults) && entry.defaults[index] != INVALID_CT_VALUE {
|
||||
return Resolved_Field_Default{
|
||||
expr=ast.INVALID_EXPR,
|
||||
pkg=entry.pkg,
|
||||
file=entry.file,
|
||||
static_value=entry.defaults[index],
|
||||
span=expr.span,
|
||||
}, entry.values, true
|
||||
}
|
||||
if is_intrinsic_call(checker, expr, "struct_type") {
|
||||
continue
|
||||
}
|
||||
if index < len(expr.args) && expr.args[index] != ast.INVALID_EXPR {
|
||||
return Resolved_Field_Default{
|
||||
expr=expr.args[index],
|
||||
pkg=entry.pkg,
|
||||
file=entry.file,
|
||||
static_value=INVALID_CT_VALUE,
|
||||
span=checker.ast_module.exprs[expr.args[index]].span,
|
||||
}, entry.values, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return {}, nil, false
|
||||
@@ -8083,10 +8203,11 @@ build_compound_expr :: proc(
|
||||
return invalid_hir_expr(checker, expr.span, id)
|
||||
}
|
||||
} else if expr.tuple {
|
||||
struct_type = types.INVALID
|
||||
struct_type = types.resolve_alias(expected, store)
|
||||
} else {
|
||||
struct_type = types.resolve_alias(expected, store)
|
||||
if !types.is_struct(struct_type, store) || types.is_opaque_struct(struct_type, store) {
|
||||
if types.is_valid(struct_type) &&
|
||||
(!types.is_struct(struct_type, store) || types.is_opaque_struct(struct_type, store)) {
|
||||
id := source.add(checker.diagnostics, expr.span, "keyed contextual payload requires a struct payload")
|
||||
return invalid_hir_expr(checker, expr.span, id, expected)
|
||||
}
|
||||
@@ -8131,6 +8252,34 @@ build_compound_expr :: proc(
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
if !types.is_valid(struct_type) {
|
||||
fields := make([]types.Field, len(expr.args), checker.allocator)
|
||||
defer delete(fields, checker.allocator)
|
||||
values := make([]hir.Expr_Id, len(expr.args), checker.allocator)
|
||||
for keyed, index in expr.args {
|
||||
keyed_expr := checker.ast_module.exprs[keyed]
|
||||
fields[index].name = u32(keyed_expr.name)
|
||||
for previous in fields[:index] {
|
||||
if previous.name == fields[index].name {
|
||||
source.addf(
|
||||
checker.diagnostics, keyed_expr.span,
|
||||
"duplicate initializer for struct field '%s'",
|
||||
symbol_text(checker, keyed_expr.name),
|
||||
)
|
||||
}
|
||||
}
|
||||
values[index] = build_nested_expr(
|
||||
checker, keyed_expr.left, locals, global_reads, calls, types.INVALID, pkg, file,
|
||||
)
|
||||
fields[index].type = checker.module.exprs[values[index]].type
|
||||
}
|
||||
struct_type = types.struct_anonymous(store, fields)
|
||||
return add_hir_expr(checker, hir.Expr{
|
||||
kind=.Struct, span=expr.span, type=struct_type, args=values,
|
||||
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
fields := types.fields_for(store, struct_type)
|
||||
union_record := types.is_union(struct_type, store)
|
||||
if union_record && len(expr.args) != 1 {
|
||||
@@ -8177,6 +8326,16 @@ build_compound_expr :: proc(
|
||||
continue
|
||||
}
|
||||
if field_default, default_values, ok := find_struct_field_default(checker, struct_type, symbol.Id(field.name)); ok {
|
||||
if field_default.static_value != INVALID_CT_VALUE {
|
||||
if int(field_default.static_value) >= len(checker.static_state.values) {
|
||||
continue
|
||||
}
|
||||
values[index] = build_static_value(
|
||||
checker, checker.static_state.values[field_default.static_value], field_default.span, field.type,
|
||||
)
|
||||
values[index] = coerce_expr(checker, values[index], field.type, field_default.span)
|
||||
continue
|
||||
}
|
||||
previous := checker.current_comptime_values
|
||||
checker.current_comptime_values = default_values
|
||||
values[index] = build_nested_expr(
|
||||
@@ -8186,7 +8345,7 @@ build_compound_expr :: proc(
|
||||
checker.current_comptime_values = previous
|
||||
values[index] = coerce_expr(
|
||||
checker, values[index], field.type,
|
||||
checker.ast_module.exprs[field_default.expr].span,
|
||||
field_default.span,
|
||||
)
|
||||
continue
|
||||
}
|
||||
@@ -8565,6 +8724,29 @@ build_expr :: proc(
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
if is_intrinsic_call(checker, expr, "some") {
|
||||
if len(expr.args) != 1 {
|
||||
id := source.addf(checker.diagnostics, expr.span, "some! expects 1 argument, got %d", len(expr.args))
|
||||
last = invalid_hir_expr(checker, expr.span, id)
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
if !types.is_optional(frame.expected, &checker.module.types) {
|
||||
id := source.add(checker.diagnostics, expr.span, "some! requires an optional context")
|
||||
last = invalid_hir_expr(checker, expr.span, id)
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
child_type := types.child_type(frame.expected, &checker.module.types)
|
||||
child := build_nested_expr(checker, expr.args[0], locals, global_reads, calls, child_type, pkg, file)
|
||||
child = coerce_expr(checker, child, child_type, checker.ast_module.exprs[expr.args[0]].span)
|
||||
last = add_hir_expr(checker, hir.Expr{
|
||||
kind=.Optional_Some, span=expr.span, type=frame.expected, left=child,
|
||||
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
_ = pop(&stack)
|
||||
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) {
|
||||
@@ -13436,6 +13618,7 @@ check :: proc(
|
||||
}
|
||||
for entry in checker.generated_types {
|
||||
delete(entry.values, allocator)
|
||||
delete(entry.defaults, allocator)
|
||||
}
|
||||
for origin in checker.type_factory_origins {
|
||||
delete(origin.values, allocator)
|
||||
|
||||
@@ -1323,7 +1323,7 @@ ct_eval_expr :: proc(
|
||||
}
|
||||
return ct_eval_expr(state, expr.right, types.BOOL, depth+1)
|
||||
case .Call:
|
||||
return ct_eval_call_expr(state, expr, expected, depth+1)
|
||||
return ct_eval_call_expr(state, expr, expected, depth+1, expr_id)
|
||||
case .Cast:
|
||||
target := type_from_syntax(checker, expr.type, state.pkg, state.file)
|
||||
value, flow, ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1)
|
||||
@@ -1531,6 +1531,14 @@ ct_eval_struct_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Ty
|
||||
if !has_default {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "missing initializer for struct field '%s'", symbol_text(checker, symbol.Id(field.name)))
|
||||
}
|
||||
if field_default.static_value != INVALID_CT_VALUE {
|
||||
value := ct_clone_graph(state, &checker.static_state, field_default.static_value)
|
||||
if value == INVALID_CT_VALUE {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
||||
}
|
||||
values[index] = value
|
||||
continue
|
||||
}
|
||||
previous_pkg, previous_file := state.pkg, state.file
|
||||
previous_comptime := checker.current_comptime_values
|
||||
state.pkg, state.file = field_default.pkg, field_default.file
|
||||
@@ -1541,7 +1549,7 @@ ct_eval_struct_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Ty
|
||||
if !ok || flow.kind != .Normal {
|
||||
return INVALID_CT_VALUE, flow, ok
|
||||
}
|
||||
value, ok = ct_coerce_value(state, value, field.type, checker.ast_module.exprs[field_default.expr].span)
|
||||
value, ok = ct_coerce_value(state, value, field.type, field_default.span)
|
||||
if !ok {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
||||
}
|
||||
@@ -2612,7 +2620,277 @@ ct_typeinfo_value :: proc(state: ^Ct_State, target: types.Type, span: source.Spa
|
||||
}), ct_flow(.Normal), true
|
||||
}
|
||||
|
||||
ct_eval_call_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type, depth: int) -> (Ct_Value_Id, Ct_Flow, bool) {
|
||||
valid_generated_field_name :: proc(name: string) -> bool {
|
||||
if len(name) == 0 {
|
||||
return false
|
||||
}
|
||||
is_start := proc(value: byte) -> bool {
|
||||
return value == '_' || value >= 'a' && value <= 'z' || value >= 'A' && value <= 'Z'
|
||||
}
|
||||
if !is_start(name[0]) {
|
||||
return false
|
||||
}
|
||||
for value in transmute([]byte)name[1:] {
|
||||
if !is_start(value) && !(value >= '0' && value <= '9') {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
ct_collection_children :: proc(
|
||||
state: ^Ct_State,
|
||||
expr_id: ast.Expr_Id,
|
||||
depth: int,
|
||||
label: string,
|
||||
) -> ([]Ct_Value_Id, Ct_Flow, bool) {
|
||||
value, flow, ok := ct_eval_expr(state, expr_id, types.INVALID, depth+1)
|
||||
if !ok || flow.kind != .Normal {
|
||||
return nil, flow, ok
|
||||
}
|
||||
if value == INVALID_CT_VALUE || int(value) >= len(state.values) {
|
||||
return nil, ct_flow(.Normal), false
|
||||
}
|
||||
item := state.values[value]
|
||||
if item.kind != .Array && item.kind != .Struct {
|
||||
return nil, ct_flow(.Normal), ct_failf(
|
||||
state, .Not_Comptime, state.checker.ast_module.exprs[expr_id].span,
|
||||
"struct_type! %s must be a fixed array or tuple", label,
|
||||
)
|
||||
}
|
||||
if item.kind == .Struct {
|
||||
node, node_ok := types.node(&state.checker.module.types, item.type)
|
||||
if !node_ok || !node.tuple {
|
||||
return nil, ct_flow(.Normal), ct_failf(
|
||||
state, .Not_Comptime, state.checker.ast_module.exprs[expr_id].span,
|
||||
"struct_type! %s must be a fixed array or tuple", label,
|
||||
)
|
||||
}
|
||||
}
|
||||
children := ct_child_slice(state, item)
|
||||
result := make([]Ct_Value_Id, len(children), state.checker.allocator)
|
||||
copy(result, children)
|
||||
return result, ct_flow(.Normal), true
|
||||
}
|
||||
|
||||
ct_literal_collection :: proc(checker: ^Checker, expr_id: ast.Expr_Id) -> ([]ast.Expr_Id, bool) {
|
||||
if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
|
||||
return nil, false
|
||||
}
|
||||
expr := checker.ast_module.exprs[expr_id]
|
||||
if expr.kind == .Array || expr.kind == .Struct_Literal && expr.tuple {
|
||||
return expr.args, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
ct_struct_type :: proc(
|
||||
state: ^Ct_State,
|
||||
expr_id: ast.Expr_Id,
|
||||
expr: ast.Expr,
|
||||
depth: int,
|
||||
) -> (Ct_Value_Id, Ct_Flow, bool) {
|
||||
checker := state.checker
|
||||
if len(expr.args) != 4 {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
|
||||
state, .Not_Comptime, expr.span,
|
||||
"struct_type! expects 4 arguments, got %d", len(expr.args),
|
||||
)
|
||||
}
|
||||
for entry in checker.generated_types {
|
||||
if entry.expr == expr_id && comptime_values_equal(entry.values, checker.current_comptime_values) {
|
||||
return ct_add_value(state, Ct_Value{kind=.Type, type=types.INVALID, index=u64(entry.result)}), ct_flow(.Normal), true
|
||||
}
|
||||
}
|
||||
|
||||
layout_expr := checker.ast_module.exprs[expr.args[0]]
|
||||
c_layout := false
|
||||
layout_ok := false
|
||||
if layout_expr.kind == .Enum_Literal {
|
||||
name := symbol_text(checker, layout_expr.name)
|
||||
c_layout = name == "c"
|
||||
layout_ok = name == "auto" || name == "c"
|
||||
} else {
|
||||
layout_value, flow, ok := ct_eval_expr(state, expr.args[0], types.INVALID, depth+1)
|
||||
if !ok || flow.kind != .Normal {
|
||||
return INVALID_CT_VALUE, flow, ok
|
||||
}
|
||||
if layout_value != INVALID_CT_VALUE && int(layout_value) < len(state.values) {
|
||||
value := state.values[layout_value]
|
||||
if value.kind == .Integer && types.is_enum(value.type, &checker.module.types) {
|
||||
if name, found := enum_member_name_from_value(checker, value.type, value.integer); found {
|
||||
c_layout = name == "c"
|
||||
layout_ok = name == "auto" || name == "c"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !layout_ok {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(
|
||||
state, .Not_Comptime, layout_expr.span,
|
||||
"struct_type! layout must be .auto or .c",
|
||||
)
|
||||
}
|
||||
|
||||
name_values, name_flow, names_ok := ct_collection_children(state, expr.args[1], depth+1, "field names")
|
||||
defer delete(name_values, checker.allocator)
|
||||
if !names_ok || name_flow.kind != .Normal {
|
||||
return INVALID_CT_VALUE, name_flow, names_ok
|
||||
}
|
||||
names := make([]string, len(name_values), checker.allocator)
|
||||
defer delete(names, checker.allocator)
|
||||
for value, index in name_values {
|
||||
name, ok := ct_value_bytes(state, value)
|
||||
if !ok || !valid_generated_field_name(name) {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(
|
||||
state, .Not_Comptime, checker.ast_module.exprs[expr.args[1]].span,
|
||||
"struct_type! field names must be valid comptime immutable byte strings",
|
||||
)
|
||||
}
|
||||
for previous in names[:index] {
|
||||
if previous == name {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
|
||||
state, .Not_Comptime, checker.ast_module.exprs[expr.args[1]].span,
|
||||
"duplicate struct_type! field name '%s'", name,
|
||||
)
|
||||
}
|
||||
}
|
||||
names[index] = name
|
||||
}
|
||||
|
||||
field_types: []types.Type
|
||||
if type_exprs, literal := ct_literal_collection(checker, expr.args[2]); literal {
|
||||
field_types = make([]types.Type, len(type_exprs), checker.allocator)
|
||||
for item, index in type_exprs {
|
||||
field_types[index], _ = resolve_type_argument(checker, item, state.pkg, state.file)
|
||||
}
|
||||
} else {
|
||||
type_values, flow, ok := ct_collection_children(state, expr.args[2], depth+1, "field types")
|
||||
defer delete(type_values, checker.allocator)
|
||||
if !ok || flow.kind != .Normal {
|
||||
return INVALID_CT_VALUE, flow, ok
|
||||
}
|
||||
field_types = make([]types.Type, len(type_values), checker.allocator)
|
||||
for value, index in type_values {
|
||||
if value != INVALID_CT_VALUE && int(value) < len(state.values) && state.values[value].kind == .Type {
|
||||
field_types[index] = types.Type(state.values[value].index)
|
||||
}
|
||||
}
|
||||
}
|
||||
defer delete(field_types, checker.allocator)
|
||||
if len(names) != len(field_types) {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
|
||||
state, .Not_Comptime, expr.span,
|
||||
"struct_type! collection lengths differ: %d names and %d types", len(names), len(field_types),
|
||||
)
|
||||
}
|
||||
if c_layout && len(names) == 0 {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "c struct types require at least one field")
|
||||
}
|
||||
fields := make([]types.Field, len(names), checker.allocator)
|
||||
defer delete(fields, checker.allocator)
|
||||
for field_type, index in field_types {
|
||||
valid := types.is_valid(field_type) && !types.is_void(field_type)
|
||||
if c_layout {
|
||||
valid = valid && types.is_runtime_value(field_type, &checker.module.types) &&
|
||||
types.is_c_record_field_type(field_type, &checker.module.types)
|
||||
} else {
|
||||
valid = valid && is_runtime_type(checker, field_type)
|
||||
}
|
||||
if !valid {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
|
||||
state, .Not_Comptime, checker.ast_module.exprs[expr.args[2]].span,
|
||||
"struct_type! field '%s' has invalid %s type %s",
|
||||
names[index], "C-layout" if c_layout else "value", type_label(checker, field_type),
|
||||
)
|
||||
}
|
||||
fields[index] = types.Field{name=u32(symbol.intern(checker.symbols, names[index])), type=field_type}
|
||||
}
|
||||
|
||||
default_exprs, literal_defaults := ct_literal_collection(checker, expr.args[3])
|
||||
default_values: []Ct_Value_Id
|
||||
if !literal_defaults {
|
||||
flow: Ct_Flow
|
||||
ok: bool
|
||||
default_values, flow, ok = ct_collection_children(state, expr.args[3], depth+1, "field defaults")
|
||||
if !ok || flow.kind != .Normal {
|
||||
delete(default_values, checker.allocator)
|
||||
return INVALID_CT_VALUE, flow, ok
|
||||
}
|
||||
}
|
||||
defer delete(default_values, checker.allocator)
|
||||
default_count := len(default_exprs) if literal_defaults else len(default_values)
|
||||
if default_count != len(fields) {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
|
||||
state, .Not_Comptime, expr.span,
|
||||
"struct_type! collection lengths differ: %d fields and %d defaults", len(fields), default_count,
|
||||
)
|
||||
}
|
||||
persistent_defaults := make([]Ct_Value_Id, len(fields), checker.allocator)
|
||||
for &value in persistent_defaults {
|
||||
value = INVALID_CT_VALUE
|
||||
}
|
||||
for field, index in fields {
|
||||
expected := types.optional(&checker.module.types, field.type)
|
||||
value := INVALID_CT_VALUE
|
||||
if literal_defaults {
|
||||
flow: Ct_Flow
|
||||
ok: bool
|
||||
value, flow, ok = ct_eval_expr(state, default_exprs[index], expected, depth+1)
|
||||
if !ok || flow.kind != .Normal {
|
||||
delete(persistent_defaults, checker.allocator)
|
||||
return INVALID_CT_VALUE, flow, ok
|
||||
}
|
||||
} else {
|
||||
value = default_values[index]
|
||||
}
|
||||
if value == INVALID_CT_VALUE {
|
||||
delete(persistent_defaults, checker.allocator)
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(
|
||||
state, .Not_Comptime, expr.span,
|
||||
"struct_type! field defaults contain an invalid comptime value",
|
||||
)
|
||||
}
|
||||
coerced, ok := ct_coerce_value(state, value, expected, expr.span)
|
||||
value = coerced
|
||||
key := strings.builder_make(checker.allocator)
|
||||
undefined := ct_value_contains_undefined(state, value)
|
||||
key_ok := ok && ct_write_comptime_key(state, value, &key)
|
||||
stable := ok && !undefined && key_ok
|
||||
strings.builder_destroy(&key)
|
||||
if !stable {
|
||||
delete(persistent_defaults, checker.allocator)
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
|
||||
state, .Not_Comptime, expr.span,
|
||||
"struct_type! default for field '%s' must have a stable comptime identity coercible to ?%s",
|
||||
names[index], type_label(checker, field.type),
|
||||
)
|
||||
}
|
||||
default := state.values[value]
|
||||
if default.kind == .None {
|
||||
continue
|
||||
}
|
||||
children := ct_child_slice(state, default)
|
||||
if default.kind != .Optional_Some || len(children) != 1 {
|
||||
delete(persistent_defaults, checker.allocator)
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
||||
}
|
||||
persistent_defaults[index] = ct_clone_graph(&checker.static_state, state, children[0])
|
||||
}
|
||||
|
||||
result := types.struct_generated(&checker.module.types, fields, c_layout=c_layout)
|
||||
append(&checker.generated_types, Generated_Type_Entry{
|
||||
expr=expr_id,
|
||||
values=clone_comptime_values(checker.current_comptime_values, checker.allocator),
|
||||
result=result,
|
||||
pkg=state.pkg,
|
||||
file=state.file,
|
||||
defaults=persistent_defaults,
|
||||
})
|
||||
return ct_add_value(state, Ct_Value{kind=.Type, type=types.INVALID, index=u64(result)}), ct_flow(.Normal), true
|
||||
}
|
||||
|
||||
ct_eval_call_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type, depth: int, expr_id := ast.INVALID_EXPR) -> (Ct_Value_Id, Ct_Flow, bool) {
|
||||
checker := state.checker
|
||||
if expr.left != ast.INVALID_EXPR {
|
||||
callee, flow, ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1)
|
||||
@@ -2636,6 +2914,29 @@ ct_eval_call_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type
|
||||
}
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, message)
|
||||
}
|
||||
if is_intrinsic_call(checker, expr, "some") {
|
||||
if len(expr.args) != 1 {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "some! expects 1 argument, got %d", len(expr.args))
|
||||
}
|
||||
if !types.is_optional(expected, &checker.module.types) {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "some! requires an optional context")
|
||||
}
|
||||
child_type := types.child_type(expected, &checker.module.types)
|
||||
child, flow, ok := ct_eval_expr(state, expr.args[0], child_type, depth+1)
|
||||
if !ok || flow.kind != .Normal {
|
||||
return INVALID_CT_VALUE, flow, ok
|
||||
}
|
||||
child, ok = ct_coerce_value(state, child, child_type, checker.ast_module.exprs[expr.args[0]].span)
|
||||
if !ok {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
||||
}
|
||||
start := u32(len(state.children))
|
||||
append(&state.children, child)
|
||||
return ct_add_value(state, Ct_Value{kind=.Optional_Some, type=expected, start=start, count=1}), ct_flow(.Normal), true
|
||||
}
|
||||
if is_intrinsic_call(checker, expr, "struct_type") {
|
||||
return ct_struct_type(state, expr_id, expr, depth+1)
|
||||
}
|
||||
if is_intrinsic_call(checker, expr, "field") {
|
||||
if len(expr.args) != 2 {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "field! expects 2 arguments, got %d", len(expr.args))
|
||||
|
||||
@@ -434,6 +434,18 @@ parse_type_atom :: proc(parser: ^Parser) -> ast.Type_Syntax {
|
||||
u32(parser.file),
|
||||
!symbol.is_valid(qualifier) && file_hidden_name(parser, name.symbol),
|
||||
)
|
||||
if current(parser).kind == .Bang && peek(parser).kind == .Left_Paren {
|
||||
advance(parser)
|
||||
if symbol.is_valid(qualifier) {
|
||||
source.add(parser.diagnostics, first.span, "intrinsic calls must be unqualified")
|
||||
return types.INVALID
|
||||
}
|
||||
call := parse_call(parser, qualifier, first, name, 0, true)
|
||||
return types.intern(&parser.module.type_store, types.Node{
|
||||
kind=.Type_Call,
|
||||
count_expr=u32(call),
|
||||
})
|
||||
}
|
||||
if current(parser).kind == .Left_Paren {
|
||||
call := parse_call(parser, qualifier, first, name, 0, false)
|
||||
return types.intern(&parser.module.type_store, types.Node{
|
||||
@@ -620,6 +632,7 @@ parse_keyed_initializers :: proc(
|
||||
left_brace: token.Token,
|
||||
nesting: int,
|
||||
close_message: string,
|
||||
require_values := false,
|
||||
) -> ([]ast.Expr_Id, token.Token) {
|
||||
parser.delimiter_depth += 1
|
||||
defer parser.delimiter_depth -= 1
|
||||
@@ -640,6 +653,8 @@ parse_keyed_initializers :: proc(
|
||||
skip_newlines(parser)
|
||||
value = parse_expression_bp(parser, 0, nesting+1)
|
||||
key_end = parser.module.exprs[value].span
|
||||
} else if require_values {
|
||||
source.add(parser.diagnostics, field.span, "anonymous record fields require '= value'")
|
||||
}
|
||||
append(&args, add_expr(parser, ast.Expr{
|
||||
kind=.Keyed,
|
||||
@@ -696,6 +711,15 @@ brace_starts_tuple :: proc(parser: ^Parser) -> bool {
|
||||
if current(parser).kind != .Left_Brace {
|
||||
return false
|
||||
}
|
||||
first := parser.cursor+1
|
||||
for first < len(parser.tokens.items) && parser.tokens.items[first].kind == .Newline {
|
||||
first += 1
|
||||
}
|
||||
if first+1 < len(parser.tokens.items) &&
|
||||
(parser.tokens.items[first].kind == .Identifier || token.is_keyword(parser.tokens.items[first].kind)) &&
|
||||
parser.tokens.items[first+1].kind == .Equal {
|
||||
return true
|
||||
}
|
||||
depth := 0
|
||||
for cursor := parser.cursor; cursor < len(parser.tokens.items); cursor += 1 {
|
||||
#partial switch parser.tokens.items[cursor].kind {
|
||||
@@ -720,12 +744,23 @@ brace_starts_tuple :: proc(parser: ^Parser) -> bool {
|
||||
|
||||
parse_tuple_literal :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
|
||||
left_brace := advance(parser)
|
||||
args, right_brace := parse_positional_initializers(parser, left_brace, nesting, "expected '}' after tuple literal")
|
||||
skip_newlines(parser)
|
||||
keyed := (current(parser).kind == .Identifier || token.is_keyword(current(parser).kind)) &&
|
||||
peek(parser).kind == .Equal
|
||||
args: []ast.Expr_Id
|
||||
right_brace: token.Token
|
||||
if keyed {
|
||||
args, right_brace = parse_keyed_initializers(
|
||||
parser, left_brace, nesting, "expected '}' after anonymous record literal", true,
|
||||
)
|
||||
} else {
|
||||
args, right_brace = parse_positional_initializers(parser, left_brace, nesting, "expected '}' after tuple literal")
|
||||
}
|
||||
return add_expr(parser, ast.Expr{
|
||||
kind=.Struct_Literal,
|
||||
span=span_from(left_brace.span, right_brace.span),
|
||||
args=args,
|
||||
tuple=true,
|
||||
tuple=!keyed,
|
||||
left=ast.INVALID_EXPR,
|
||||
right=ast.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
|
||||
@@ -369,7 +369,7 @@ struct_anonymous :: proc(store: ^Store, fields: []Field, tuple := false) -> Type
|
||||
|
||||
// Generated structs are nominal per comptime type-expression specialization.
|
||||
// The checker owns canonicalization; this routine deliberately creates a fresh node.
|
||||
struct_generated :: proc(store: ^Store, fields: []Field, tuple := false) -> Type {
|
||||
struct_generated :: proc(store: ^Store, fields: []Field, tuple := false, c_layout := false) -> Type {
|
||||
start := u32(len(store.fields))
|
||||
append(&store.fields, ..fields)
|
||||
id := DYNAMIC_START+Type(len(store.nodes))
|
||||
@@ -378,6 +378,7 @@ struct_generated :: proc(store: ^Store, fields: []Field, tuple := false) -> Type
|
||||
field_start=start,
|
||||
field_count=u32(len(fields)),
|
||||
tuple=tuple,
|
||||
c_layout=c_layout,
|
||||
declared=true,
|
||||
})
|
||||
return id
|
||||
|
||||
Reference in New Issue
Block a user