feat: specialize compile-time conditionals and type comparisons

This commit is contained in:
2026-10-26 16:14:37 +01:00
parent 4de7c8bfc3
commit 565733666e
6 changed files with 286 additions and 7 deletions
+4 -1
View File
@@ -77,7 +77,9 @@ recompiles_specialized_calls_without_borrowing_previous_ir test {
defer deinit(&session)
root :: try add_package(&session)
bytes := try owned_text(
`identity proc($T type, value T) T { return value }
`identity proc($T type, value T) T {
` if (T == i16) return value else return value.missing
`}
`Box proc($T type) type { return struct { value T } }
`count proc() usize {
` result := 0
@@ -87,6 +89,7 @@ recompiles_specialized_calls_without_borrowing_previous_ir test {
`Fixed proc($N usize) type { return struct { values [N]i16 } }
`data proc() [2]i16 { return [40, 2] }
`main proc() i16 {
` if $(i16 != i16) { return $(i8(128)) }
` values :: $(data())
` box Box(i16) :: { value = identity(i16, values[0]) }
` fixed Fixed(count()) :: { values = [box.value, 2] }
+2 -1
View File
@@ -3422,7 +3422,8 @@ type_syntax proc(kind ast.ExprKind) bool {
@hide:file
type_reference proc(state @State, id ast.NodeId) bool {
if (type_syntax(ast.expr_kind(state.tree, id))) return true
kind :: ast.expr_kind(state.tree, id)
if (type_syntax(kind) or kind == .array_type) return true
binding :: sema.binding(state.resolution, id)
if (binding.kind == .builtin) return true
if (binding.kind != .declaration) return false
+127 -2
View File
@@ -189,6 +189,41 @@ expand proc(state @mut State, id ast.NodeId) void ! Error {
if (state.expanded.items[usize(id)] != 0) return
state.expanded.items[usize(id)] = 1
class :: ast.node_class(&state.tree, id)
if (class == .statement and ast.stmt_kind(&state.tree, id) == .if_stmt) {
conditional :: ast.if_stmt(&state.tree, id)
explicit :: ast.expr_kind(&state.tree, conditional.condition) == .comptime_expr
if (conditional.capture == null and (explicit or usize(id) >= state.original_count)) {
condition :: if (explicit) ast.comptime_operand(&state.tree, conditional.condition) else conditional.condition
try expand(state, condition)
if (explicit or branch_constant(state, condition, 0)) {
state.evaluation_diagnostic = diagnostics.no_diagnostic
boolean :: try builtin(state, condition, .bool)
value :: try evaluate_ir(state, condition, boolean)
if (explicit and value.kind == .invalid and state.evaluation_diagnostic == diagnostics.no_diagnostic)
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
.comptime_argument_required, ast.node_span(&state.tree, condition))
if (state.evaluation_diagnostic != diagnostics.no_diagnostic) {
invalid :: try ast.add_invalid_stmt(state.builder, ast.node_span(&state.tree, id),
state.evaluation_diagnostic)
try replace(state, id, invalid)
try refresh(state)
return
}
if (value.kind == .boolean) {
branch := conditional.then_block
if (!value.boolean) {
if (conditional.else_block) |other| branch = other
else branch = try ast.add_block(state.builder, ast.node_span(&state.tree, id), &[])
}
replacement :: try ast.add_block_stmt(state.builder, branch)
try replace(state, id, replacement)
try refresh(state)
try expand(state, branch)
return
}
}
}
}
if (class == .expression and ast.expr_kind(&state.tree, id) == .comptime_expr) {
operand :: ast.comptime_operand(&state.tree, id)
try arraylist.append(&state.rewrites.constraints, operand)
@@ -225,6 +260,17 @@ expand proc(state @mut State, id ast.NodeId) void ! Error {
child :: ast.child(&state.tree, id, i)
try expand(state, child)
}
if (class == .expression and ast.expr_kind(&state.tree, id) == .binary) {
view :: ast.binary_expr(&state.tree, id)
if (
(view.operator == .equal or view.operator == .not_equal)
and branch_constant(state, view.left, 0) and branch_constant(state, view.right, 0)
) {
left :: try evaluate(state, view.left)
right :: try evaluate(state, view.right)
if (left.kind == .type and right.kind == .type) _ = try evaluate_binary(state, id)
}
}
if (usize(id) >= state.original_count and class == .expression and ast.expr_kind(&state.tree, id) == .array_type) {
view :: ast.array_type(&state.tree, id)
if (view.count) |count_node| {
@@ -241,6 +287,48 @@ expand proc(state @mut State, id ast.NodeId) void ! Error {
}
}
# Only probe side-effect-free expressions. Ordinary calls stay runtime unless the
# source requests evaluation with $(); probing must not execute FFI or emit traps.
@hide:file
branch_constant proc(state @State, id ast.NodeId, depth usize) bool {
if (depth >= 128) return false
resolved :: binding(state, id)
if (resolved.kind == .builtin) return true
if (resolved.kind == .declaration) {
target :: resolved.data.declaration
if (ast.node_class(&state.tree, target) == .function) return true
if (ast.node_class(&state.tree, target) != .statement) return false
declaration :: ast.statement_decl(&state.tree, target)
return (
declaration.mutability == .constant
and branch_constant(state, declaration.initializer, depth + 1)
)
}
match ast.expr_kind(&state.tree, id) {
.literal_int, .literal_float, .literal_bool, .literal_string, .literal_null: return true
.typed_constant: return branch_constant(state, ast.typed_constant(&state.tree, id).value, depth + 1)
.unary: {
view :: ast.unary_expr(&state.tree, id)
return (
(view.operator == .negate or view.operator == .logical_not)
and branch_constant(state, view.operand, depth + 1)
)
}
.binary: {
view :: ast.binary_expr(&state.tree, id)
return (
branch_constant(state, view.left, depth + 1)
and branch_constant(state, view.right, depth + 1)
)
}
.pointer_type: return branch_constant(state, ast.pointer_type(&state.tree, id).element, depth + 1)
.optional_type, .alias_expr: return branch_constant(state, ast.type_operand(&state.tree, id), depth + 1)
.record_type, .union_type, .enum_type, .distinct_type, .array_type, .slice_type, .function_type,
.fallible_type, .sum_type: return true
else: return false
}
}
@hide:file
builtin proc(state @mut State, at ast.NodeId, kind sema.BuiltinType) ast.NodeId ! Error {
id :: try ast.add_identifier(state.builder, ast.node_span(&state.tree, at), strpool.no_str)
@@ -1022,6 +1110,19 @@ evaluate_binary proc(state @mut State, id ast.NodeId) Value ! Error {
if (view.operator == .equal or view.operator == .not_equal) {
if (left.kind != right.kind) return Value{}
equal :: try value_equal(state, left, right)
if (left.kind == .type) {
operands [2]ast.NodeId :: [view.left, view.right]
for (operands) |operand| {
constraint :: try ast.add_type_operand(state.builder,
ast.node_span(&state.tree, operand), operand, true)
try refresh(state)
try arraylist.append(&state.rewrites.constraints, constraint)
}
folded :: try ast.add_boolean(state.builder, ast.node_span(&state.tree, id),
equal == (view.operator == .equal))
try replace(state, id, folded)
try refresh(state)
}
return Value{ kind = .boolean, node = id, boolean = equal == (view.operator == .equal) }
}
if (left.kind == .boolean and right.kind == .boolean) {
@@ -1078,6 +1179,8 @@ same_type proc(state @mut State, left, right ast.NodeId) bool ! Error {
return x.kind == .builtin and y.kind == .builtin and builtin_identity(x.data.builtin) == builtin_identity(y.data.builtin)
}
kind :: ast.expr_kind(&state.tree, a)
if (kind == .sum_type or ast.expr_kind(&state.tree, b) == .sum_type)
return try type_subset(state, a, b) and try type_subset(state, b, a)
if (kind != ast.expr_kind(&state.tree, b)) return false
match kind {
.pointer_type: {
@@ -1103,7 +1206,7 @@ same_type proc(state @mut State, left, right ast.NodeId) bool ! Error {
return try same_type(state, p.element, q.element)
}
.optional_type: return try same_type(state, ast.type_operand(&state.tree, a), ast.type_operand(&state.tree, b))
.fallible_type, .sum_type: {
.fallible_type: {
p :: ast.binary_expr(&state.tree, a)
q :: ast.binary_expr(&state.tree, b)
return try same_type(state, p.left, q.left) and try same_type(state, p.right, q.right)
@@ -1123,6 +1226,22 @@ same_type proc(state @mut State, left, right ast.NodeId) bool ! Error {
}
}
@hide:file
type_subset proc(state @mut State, left, right ast.NodeId) bool ! Error {
a :: try canonical_type(state, left)
b :: try canonical_type(state, right)
if (a == no_node or b == no_node) return false
if (ast.expr_kind(&state.tree, a) == .sum_type) {
view :: ast.binary_expr(&state.tree, a)
return try type_subset(state, view.left, b) and try type_subset(state, view.right, b)
}
if (ast.expr_kind(&state.tree, b) == .sum_type) {
view :: ast.binary_expr(&state.tree, b)
return try type_subset(state, a, view.left) or try type_subset(state, a, view.right)
}
return try same_type(state, a, b)
}
@hide:file
value_equal proc(state @mut State, left, right Value) bool ! Error {
if (left.kind != right.kind) return false
@@ -1201,7 +1320,13 @@ type_hash proc(state @mut State, id ast.NodeId) usize ! Error {
return mix(hash, try type_hash(state, view.element))
}
.optional_type: return mix(hash, try type_hash(state, ast.type_operand(&state.tree, node)))
.fallible_type, .sum_type: {
.sum_type: {
view :: ast.binary_expr(&state.tree, node)
# Set fingerprints must ignore ordering, grouping and repeated members.
# Colliding fingerprints still use full type equality in the cache chain.
return try type_hash(state, view.left) | try type_hash(state, view.right)
}
.fallible_type: {
view :: ast.binary_expr(&state.tree, node)
return mix(mix(hash, try type_hash(state, view.left)), try type_hash(state, view.right))
}