feat: specialize generic type and value parameters

This commit is contained in:
2026-10-26 13:10:52 +01:00
parent 9058135326
commit 6886d743b5
18 changed files with 2499 additions and 103 deletions
+38 -3
View File
@@ -234,6 +234,17 @@ check proc(
tree @ast.Tree,
resolution @sema.Resolution,
diagnostic_store @mut diagnostics.Store,
) Result ! (mem.AllocError | diagnostics.CapacityError) {
return try check_elaborated(allocator, tree, resolution, &[], diagnostic_store)
}
#! checks additional compile-time expressions retained when elaboration erased their calls.
check_elaborated proc(
allocator mem.Allocator,
tree @ast.Tree,
resolution @sema.Resolution,
constraints []ast.NodeId,
diagnostic_store @mut diagnostics.Store,
) Result ! (mem.AllocError | diagnostics.CapacityError) {
count :: ast.node_count(tree)
types :: try mem.alloc(Type, allocator, count)
@@ -305,6 +316,12 @@ check proc(
.import: {}
}
}
for (constraints) |constraint| {
if (ast.node_class(tree, constraint) == .statement) {
state.cells[usize(constraint)].local = true
try collect_declaration(&state, constraint)
} else try collect_expression(&state, constraint)
}
# Preserve binding errors before RHS errors propagate. Indexed writability
# waits for shapes; neither check poisons the target's type.
for 0..count |i| {
@@ -1127,6 +1144,14 @@ collect_expression proc(state @mut State, id ast.NodeId) void ! (mem.AllocError
state.cells[usize(id)].in_cleanup = state.in_cleanup
state.cells[usize(id)].narrowing_arm = state.narrowing_arm
match ast.expr_kind(tree, id) {
.typed_constant: {
value :: ast.typed_constant(tree, id)
state.cells[usize(id)].fixed = true
state.cells[usize(id)].type = try check_annotation(state, value.type, false)
try collect_expression(state, value.value)
try depend(state, value.type, id)
try depend(state, value.value, id)
}
.try_expr: {
operand :: ast.type_operand(tree, id)
try collect_expression(state, operand)
@@ -1371,6 +1396,7 @@ builtin_kind proc(type sema.BuiltinType) TypeKind {
.string: .string
.void: .void
.anyopaque: .anyopaque
.type: .type_value
.c_int: .i32
.c_uint: .u32
.c_long, .c_longlong: .int
@@ -1886,6 +1912,10 @@ validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
}
.expression: {
match ast.expr_kind(tree, id) {
.typed_constant: {
value :: ast.typed_constant(tree, id)
try compatible(state, value.value, state.cells[usize(id)].type)
}
.try_expr, .catch_expr: try validate_fallible_expression(state, id)
.unary: {
expression :: ast.unary_expr(tree, id)
@@ -2114,8 +2144,9 @@ evaluate_integer proc(state @mut State, id ast.NodeId, allow_local bool) ?Consta
state.cells[usize(id)].constant_visiting = true
defer state.cells[usize(id)].constant_visiting = false
match ast.expr_kind(state.tree, id) {
.typed_constant: return evaluate_integer(state, ast.typed_constant(state.tree, id).value, allow_local)
.literal_int: {
text :: ast.source_text(state.tree, ast.node_span(state.tree, id))
text :: ast.literal_text(state.tree, id)
value := u64(0)
for text |byte| {
if (byte < '0' or byte > '9') return null
@@ -2935,6 +2966,10 @@ apply_contexts proc(state @mut State, assignment_values, early bool) void ! (mem
for 0..(state.cells.len) |i| {
id :: ast.NodeId(i)
if (state.cells[i].role == .expression and state.cells[i].assignment_value == assignment_values) {
if (ast.expr_kind(tree, id) == .typed_constant) {
value :: ast.typed_constant(tree, id)
try context_demand(state, value.value, state.cells[i].type, .type_mismatch, early)
}
if (ast.expr_kind(tree, id) == .match_expr) _ = try infer_match(state, id, false)
if (ast.expr_kind(tree, id) == .binary) {
expression :: ast.binary_expr(tree, id)
@@ -3945,7 +3980,7 @@ constant_order proc(left, right ConstantInteger) i32 {
constant_real proc(state @mut State, id ast.NodeId) ?f64 ! mem.AllocError {
missing ?f64 :: null
if (ast.expr_kind(state.tree, id) == .literal_float) {
return strconv.parse_float(state.edges.allocator, ast.source_text(state.tree, ast.node_span(state.tree, id))) catch |err| {
return strconv.parse_float(state.edges.allocator, ast.literal_text(state.tree, id)) catch |err| {
match err {
.out_of_memory: return err
.invalid_number, .out_of_range: return missing
@@ -3994,7 +4029,7 @@ pattern_interval proc(state @mut State, id ast.NodeId, key Type) ?PatternInterva
if (try constant_real(state, high)) |value| interval.real_high = value else return missing
} else if (key.kind == .bool) {
if (ast.expr_kind(state.tree, low) != .literal_bool or low != high) return missing
value :: mem.eql(ast.source_text(state.tree, ast.node_span(state.tree, low)), "true")
value :: mem.eql(ast.literal_text(state.tree, low), "true")
interval.low.magnitude = if (value) 1 else 0
interval.high = interval.low
} else {
+21 -4
View File
@@ -17,6 +17,7 @@ BuiltinType :: enum {
anyopaque
c_int, c_uint, c_long, c_ulong, c_longlong, c_ulonglong
c_char, c_uchar, c_short, c_ushort
type
}
@hide:file
@@ -55,6 +56,7 @@ builtins [_]Builtin :: [
{ name = "c_uchar", type = .c_uchar },
{ name = "c_short", type = .c_short },
{ name = "c_ushort", type = .c_ushort },
{ name = "type", type = .type },
]
@hide:file
@@ -275,6 +277,12 @@ binding proc(resolution @Resolution, id ast.NodeId) Binding {
return resolution.storage.bindings[usize(id)]
}
#! replaces owned binding storage after AST elaboration; takes ownership of `bindings`.
replace_bindings proc(resolution @mut Resolution, bindings []mut Binding) void {
mem.free(resolution.storage.allocator, resolution.storage.bindings)
resolution.storage.bindings = bindings
}
@hide:file
lookup proc(state @State, scope ?@Scope, name strpool.StringId) ?Binding {
if scope |current| {
@@ -335,13 +343,17 @@ resolve_function proc(
) void ! (mem.AllocError | diagnostics.CapacityError) {
tree :: state.tree
function :: ast.function_decl(tree, id)
# signatures cannot see parameters or body-local declarations.
try resolve_expr(state, package_scope, function.return_type)
# compile-time parameters are in scope for subsequent dependent annotations.
signature := Scope{ names = hashmap.init(state.allocator), parent = package_scope }
defer hashmap.deinit(&signature.names)
for (0..ast.function_param_count(tree, id)) |i| {
parameter :: ast.function_param(tree, id, i)
try resolve_expr(state, package_scope, ast.param(tree, parameter).type)
try resolve_expr(state, &signature, ast.param(tree, parameter).type)
if (ast.param(tree, parameter).comptime) {
try predeclare(state, &signature, parameter, ast.param(tree, parameter).name)
}
}
try resolve_expr(state, &signature, function.return_type)
scope := Scope{
names = hashmap.init(state.allocator),
parent = package_scope,
@@ -514,6 +526,11 @@ resolve_expr proc(
if (state.bindings[usize(id)].kind != .none) return
tree :: state.tree
match ast.expr_kind(tree, id) {
.typed_constant: {
value :: ast.typed_constant(tree, id)
try resolve_expr(state, scope, value.type)
try resolve_expr(state, scope, value.value)
}
.literal_int, .literal_float, .literal_bool, .literal_string, .literal_null: {}
.enum_literal: {}
.distinct_type, .alias_expr, .optional_type, .try_expr: try resolve_expr(state, scope, ast.type_operand(tree, id))
+1 -1
View File
@@ -719,7 +719,7 @@ resolves_every_array_child_error_and_preserves_lexer_diagnostics test {
` return
` data [missing_count]MissingElement :: [absent, missing_element]
` (missing_base[missing_index])[missing_outer] = missing_rhs
` malformed [$]i64 :: [$]
` malformed [~]i64 :: [~]
`}
scan_state := lexer.init(mem.c_allocator)
defer lexer.deinit(&scan_state)
File diff suppressed because it is too large Load Diff