comptime type params

This commit is contained in:
2026-07-02 20:30:02 +02:00
parent a98b26446d
commit b94687c30a
8 changed files with 393 additions and 31 deletions
+1
View File
@@ -72,6 +72,7 @@ Expr_Kind :: enum u8 {
Array,
None,
Undefined,
Type,
Name,
Enum_Literal,
Address,
+155 -25
View File
@@ -33,10 +33,16 @@ Spec :: struct {
hir_id: hir.Function_Id,
}
Comptime_Value_Kind :: enum u8 {
Integer,
Type,
}
Comptime_Value :: struct {
name: symbol.Id,
type: types.Type,
value: i128,
kind: Comptime_Value_Kind,
}
Infer_Local :: struct {
@@ -174,6 +180,7 @@ Checker :: struct {
cycle_stack: [dynamic]Cycle_Frame,
main_symbol: symbol.Id,
sink_symbol: symbol.Id,
type_symbol: symbol.Id,
current_result: types.Type,
current_build_ctx: ^Build_Ctx,
current_comptime_values: []Comptime_Value,
@@ -208,6 +215,22 @@ current_comptime_value :: proc(checker: ^Checker, name: symbol.Id) -> (Comptime_
return find_comptime_value(checker.current_comptime_values, name)
}
current_comptime_type :: proc(checker: ^Checker, name: symbol.Id) -> (types.Type, bool) {
if value, ok := current_comptime_value(checker, name); ok && value.kind == .Type {
return value.type, true
}
return types.INVALID, false
}
is_type_metatype_syntax :: proc(checker: ^Checker, value: ast.Type_Syntax) -> bool {
item, ok := types.node(&checker.module.types, value)
return ok && item.name == u32(checker.type_symbol) && item.qualifier == 0
}
is_comptime_type_param :: proc(checker: ^Checker, param: ast.Param) -> bool {
return param.comptime_value && is_type_metatype_syntax(checker, param.type)
}
function_has_comptime_params :: proc(function: ast.Function) -> bool {
for param in function.params {
if param.comptime_value {
@@ -243,7 +266,8 @@ comptime_values_equal :: proc(left, right: []Comptime_Value) -> bool {
}
for value, index in left {
other := right[index]
if value.name != other.name || !types.equal(value.type, other.type) || value.value != other.value {
if value.name != other.name || value.kind != other.kind || !types.equal(value.type, other.type) ||
(value.kind == .Integer && value.value != other.value) {
return false
}
}
@@ -376,7 +400,9 @@ eval_integer_constant_in_context :: proc(
case .Name:
if !symbol.is_valid(expr.qualifier) {
if value, ok := current_comptime_value(checker, expr.name); ok {
return Constant{kind = .Value, value = value.value}
if value.kind == .Integer {
return Constant{kind = .Value, value = value.value}
}
}
}
target_pkg, available := expr_package(checker, expr, pkg, file, false)
@@ -469,6 +495,11 @@ type_from_syntax :: proc(
if !ok {
return value
}
if item.qualifier == 0 && item.name != 0 {
if actual, ok := current_comptime_type(checker, symbol.Id(item.name)); ok {
return actual
}
}
store := &checker.module.types
changed := false
#partial switch item.kind {
@@ -870,6 +901,9 @@ call_arg_expected :: proc(checker: ^Checker, function: ast.Function, index: int)
if index < 0 || index >= len(function.params) {
return types.INVALID
}
if is_comptime_type_param(checker, function.params[index]) {
return types.INVALID
}
declared := type_from_syntax(checker, function.params[index].type, function.pkg, function.file)
// A `float` param defaults to f64 so an integer-literal argument builds as a
// float constant (e.g. `f(3)` -> 3.0), mirroring `pi float = 3` for locals.
@@ -880,6 +914,37 @@ call_arg_expected :: proc(checker: ^Checker, function: ast.Function, index: int)
return declared
}
resolve_type_argument :: proc(
checker: ^Checker,
expr_id: ast.Expr_Id,
pkg: ast.Package_Id,
file: ast.File_Id,
) -> (types.Type, bool) {
if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
return types.INVALID, false
}
expr := checker.ast_module.exprs[expr_id]
#partial switch expr.kind {
case .Type:
resolved := type_from_syntax(checker, expr.type, pkg, file)
return resolved, types.is_valid(resolved)
case .Name:
if !symbol.is_valid(expr.qualifier) {
if actual, ok := current_comptime_type(checker, expr.name); ok {
return actual, true
}
}
target_pkg, available := expr_package(checker, expr, pkg, file)
if !available {
return types.INVALID, false
}
value := types.find_named(&checker.module.types, u32(target_pkg), u32(expr.name))
value = types.resolve_alias(value, &checker.module.types)
return value, types.is_valid(value)
}
return types.INVALID, false
}
collect_comptime_values :: proc(
checker: ^Checker,
function: ast.Function,
@@ -902,6 +967,26 @@ collect_comptime_values :: proc(
if index < len(args) && args[index] != ast.INVALID_EXPR && int(args[index]) < len(checker.ast_module.exprs) {
span = checker.ast_module.exprs[args[index]].span
}
if is_comptime_type_param(checker, param) {
actual, actual_ok := types.INVALID, false
if index < len(args) {
actual, actual_ok = resolve_type_argument(checker, args[index], pkg, file)
}
if !actual_ok {
if diagnose {
source.addf(
checker.diagnostics,
span,
"argument for comptime type parameter '%s' must be a type",
symbol_text(checker, param.name),
)
}
ok = false
continue
}
append(&values, Comptime_Value{name=param.name, type=actual, kind=.Type})
continue
}
declared := type_from_syntax(checker, param.type, function.pkg, function.file)
if !types.is_concrete_integer(declared) {
if diagnose {
@@ -1068,7 +1153,7 @@ mark_expr_imports_used :: proc(checker: ^Checker, expr_id: ast.Expr_Id, file: as
mark_block_imports_used(checker, expr.body, file)
case .Add, .Sub, .Mul, .Div, .Index, .Orelse, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
append(&stack, expr.left, expr.right)
case .Invalid, .Integer, .Float, .String, .Bool, .None, .Undefined, .Name:
case .Invalid, .Integer, .Float, .String, .Bool, .None, .Undefined, .Type, .Name:
}
}
}
@@ -1204,7 +1289,7 @@ validate_declarations :: proc(checker: ^Checker) {
"comptime parameters require 'func', not 'c_func'",
)
}
if !types.is_concrete_integer(param_type) {
if !is_type_metatype_syntax(checker, param.type) && !types.is_concrete_integer(param_type) {
checker.template_diagnostics[function_id] = source.addf(
checker.diagnostics,
param.span,
@@ -1855,6 +1940,9 @@ infer_expr :: proc(
case .Invalid:
last = types.INVALID
_ = pop(&stack)
case .Type:
last = types.INVALID
_ = pop(&stack)
case .Integer:
last = types.I64
if expr.integer <= 0x7fff_ffff_ffff_ffff {
@@ -1898,7 +1986,9 @@ infer_expr :: proc(
if !types.is_valid(last) {
if !symbol.is_valid(expr.qualifier) {
if value, ok := current_comptime_value(checker, expr.name); ok {
last = value.type
if value.kind == .Integer {
last = value.type
}
}
}
}
@@ -3363,6 +3453,14 @@ Build_Expr_Frame :: struct {
template: ast.Function_Id,
}
next_built_template_arg :: proc(checker: ^Checker, function: ast.Function, start: int) -> int {
index := start
for index < len(function.params) && is_comptime_type_param(checker, function.params[index]) {
index += 1
}
return index
}
hir_location_writable :: proc(checker: ^Checker, expr_id: hir.Expr_Id, locals: []Build_Local) -> bool {
if expr_id == hir.INVALID_EXPR || int(expr_id) >= len(checker.module.exprs) {
return false
@@ -4308,6 +4406,10 @@ build_expr :: proc(
checker, expr, locals, global_reads, calls, frame.expected, pkg, file,
)
_ = pop(&stack)
case .Type:
id := source.add(checker.diagnostics, expr.span, "type is not a runtime value")
last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack)
case .Invalid, .Integer:
last = invalid_hir_expr(checker, expr.span, expr.diagnostic)
_ = pop(&stack)
@@ -4363,16 +4465,26 @@ build_expr :: proc(
if last == hir.INVALID_EXPR {
if !symbol.is_valid(expr.qualifier) {
if value, ok := current_comptime_value(checker, expr.name); ok {
expected_type := value.type
if types.is_concrete_integer(frame.expected) || types.is_float(frame.expected, checker.target) {
expected_type = frame.expected
if value.kind == .Integer {
expected_type := value.type
if types.is_concrete_integer(frame.expected) || types.is_float(frame.expected, checker.target) {
expected_type = frame.expected
}
last = build_constant_expr(
checker,
expr,
Constant{kind=.Value, value=value.value},
expected_type,
)
} else {
id := source.addf(
checker.diagnostics,
expr.span,
"type parameter '%s' is not a runtime value",
symbol_text(checker, expr.name),
)
last = invalid_hir_expr(checker, expr.span, id)
}
last = build_constant_expr(
checker,
expr,
Constant{kind=.Value, value=value.value},
expected_type,
)
}
}
}
@@ -4561,13 +4673,21 @@ build_expr :: proc(
stack[frame_index].template = template
stack[frame_index].built_args = make([]hir.Expr_Id, len(expr.args), checker.allocator)
stack[frame_index].arg_types = make([]types.Type, len(expr.args), checker.allocator)
for &arg in stack[frame_index].built_args {
arg = hir.INVALID_EXPR
}
stack[frame_index].arg_index = next_built_template_arg(checker, function, 0)
stack[frame_index].stage = 3
if len(expr.args) > 0 {
arg_expected := call_arg_expected(checker, function, 0)
if stack[frame_index].arg_index < len(expr.args) {
arg_expected := call_arg_expected(checker, function, stack[frame_index].arg_index)
if !is_runtime_type(checker, arg_expected) {
arg_expected = types.INVALID
}
append(&stack, Build_Expr_Frame{expr=expr.args[0], expected=arg_expected, template=ast.INVALID_FUNCTION})
append(&stack, Build_Expr_Frame{
expr=expr.args[stack[frame_index].arg_index],
expected=arg_expected,
template=ast.INVALID_FUNCTION,
})
}
}
continue
@@ -4615,9 +4735,9 @@ build_expr :: proc(
if frame.arg_index < len(expr.args) {
stack[frame_index].built_args[frame.arg_index] = last
stack[frame_index].arg_types[frame.arg_index] = checker.module.exprs[last].type
stack[frame_index].arg_index += 1
if frame.arg_index+1 < len(expr.args) {
next := frame.arg_index+1
next := next_built_template_arg(checker, checker.ast_module.functions[frame.template], frame.arg_index+1)
stack[frame_index].arg_index = next
if next < len(expr.args) {
arg_expected := call_arg_expected(checker, checker.ast_module.functions[frame.template], next)
if !is_runtime_type(checker, arg_expected) {
arg_expected = types.INVALID
@@ -4881,12 +5001,21 @@ make_link_name :: proc(checker: ^Checker, id: Spec_Id) -> string {
}
}
for value in spec.comptime_values {
strings.write_string(&builder, "__cv")
if value.value < 0 {
strings.write_string(&builder, "n")
fmt.sbprintf(&builder, "%d", -value.value)
if value.kind == .Type {
strings.write_string(&builder, "__ct")
if value.type >= types.DYNAMIC_START {
fmt.sbprintf(&builder, "t%d", value.type)
} else {
strings.write_string(&builder, types.name(value.type))
}
} else {
fmt.sbprintf(&builder, "%d", value.value)
strings.write_string(&builder, "__cv")
if value.value < 0 {
strings.write_string(&builder, "n")
fmt.sbprintf(&builder, "%d", -value.value)
} else {
fmt.sbprintf(&builder, "%d", value.value)
}
}
}
return fmt.aprintf("%s", strings.to_string(builder), allocator = checker.allocator)
@@ -7836,6 +7965,7 @@ check :: proc(
module = hir.init_module(selected, allocator),
main_symbol = symbol.intern(symbols, "main"),
sink_symbol = symbol.intern(symbols, "_"),
type_symbol = symbol.intern(symbols, "type"),
target = selected,
allocator = allocator,
}
+19 -1
View File
@@ -591,6 +591,17 @@ parse_integer_magnitude :: proc(text: string) -> (u64, bool) {
parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
tok := current(parser)
#partial switch tok.kind {
case .Keyword_Int, .Keyword_Float, .Keyword_Range, .Keyword_Void, .Keyword_Bool:
start := tok
target := parse_type_atom(parser)
return add_expr(parser, ast.Expr{
kind=.Type,
span=start.span,
type=target,
left=ast.INVALID_EXPR,
right=ast.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
case .Keyword_I8, .Keyword_I16, .Keyword_I32, .Keyword_I64,
.Keyword_U8, .Keyword_U16, .Keyword_U32, .Keyword_U64,
.Keyword_Isize, .Keyword_Usize, .Keyword_F32, .Keyword_F64,
@@ -601,7 +612,14 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
start := tok
target := parse_type_atom(parser)
if _, ok := allow(parser, .Left_Paren); !ok {
return invalid_expr(parser, current(parser).span, "expected '(' after scalar cast type")
return add_expr(parser, ast.Expr{
kind=.Type,
span=start.span,
type=target,
left=ast.INVALID_EXPR,
right=ast.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
parser.delimiter_depth += 1
skip_newlines(parser)