feat: evaluate compile-time aggregates and explicit expressions

This commit is contained in:
2026-10-26 15:43:28 +01:00
parent ecd20675ea
commit 4de7c8bfc3
11 changed files with 518 additions and 45 deletions
+40 -2
View File
@@ -6,6 +6,7 @@
#! * expr ..... -> identifier | literal | type | '.' member | '[' [expr {',' expr} [',']] ']' | [type] '{' [initializer {',' initializer} [',']] '}' | '(' expr ')' | ('-' | '!' | '&' | 'try') expr | expr ('^' | '?') | expr op expr | expr '(' [expr {',' expr} [',']] ')' | identifier '!' '(' [expr {',' expr} [',']] ')' | expr '[' expr ']' | expr '[' [expr] '..' [expr] [';' expr] ']' | expr '.' (member | int) | expr ('..' | '..=') expr | 'match' expr '{' match_arms '}' | expr 'catch' ['|' identifier '|'] (expr | block)
#! * decl ..... -> identifier [type] binding expr | identifier 'c_func' '(' parameters ')' type | ['test'] [identifier '::'] 'import' string
#! * parameters -> [['$'] identifier {',' ['$'] identifier} type {',' ['$'] identifier {',' ['$'] identifier} type} [',']]; '$' marks each compile-time parameter independently.
#! '$(' expr ')' is an explicit compile-time expression with primary-expression precedence.
#! * block .... -> '{' {stmt} '}'
#! * arm ...... -> block | stmt (single statements become canonical blocks)
#! * match_arm -> (expr {',' expr} ['|' identifier '|'] | 'else') ':' (block | stmt | expr); arms are comma/newline separated.
@@ -69,6 +70,7 @@ ExprKind :: enum {
try_expr
catch_expr
typed_constant
comptime_expr
}
DeclKind :: enum {
@@ -172,6 +174,8 @@ NodeKind :: enum {
stmt_errdefer
stmt_block
expr_typed_constant
expr_comptime
literal_float_value
}
@hide:file
@@ -540,6 +544,23 @@ add_typed_constant proc(
}, at)
}
add_comptime_expr proc(builder @mut Builder, at Span, operand NodeId) NodeId ! mem.AllocError {
return try add_node(builder, Node{
kind = .expr_comptime,
main = NodeData{ node_id = operand },
}, at)
}
#! retains the exact IEEE representation, including signed zero and NaN payloads.
add_float proc(builder @mut Builder, at Span, value f64) NodeId ! mem.AllocError {
bits :: ptrcast!(@u64, &value)^
return try add_node(builder, Node{
kind = .literal_float_value,
data0 = NodeData{ extra = u32(bits & 4294967295) },
data1 = NodeData{ extra = u32(bits >> 32) },
}, at)
}
add_binary_type proc(
builder @mut Builder, span Span, left, right NodeId, fallible bool,
) NodeId ! mem.AllocError {
@@ -1385,6 +1406,8 @@ expression_kind proc(kind NodeKind) ExprKind {
.expr_try: .try_expr
.expr_catch: .catch_expr
.expr_typed_constant: .typed_constant
.expr_comptime: .comptime_expr
.literal_float_value: .literal_float
else: unreachable
}
}
@@ -1410,6 +1433,20 @@ binary_expr proc(tree @Tree, id NodeId) BinaryExprView {
}
}
comptime_operand proc(tree @Tree, id NodeId) NodeId {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_comptime)
return node.main.node_id
}
#! generated IEEE literals have no textual spelling; parsed float literals return null.
float_value proc(tree @Tree, id NodeId) ?f64 {
node :: get_node(&tree.storage, id)
if (node.kind != .literal_float_value) return null
bits :: u64(node.data0.extra) | (u64(node.data1.extra) << 32)
return ptrcast!(@f64, &bits)^
}
typed_constant proc(tree @Tree, id NodeId) TypedConstantView {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_typed_constant)
@@ -1765,7 +1802,7 @@ NodeLayout :: struct {
node_layout proc(node Node) NodeLayout {
layout := NodeLayout{}
match node.kind {
.literal_int, .literal_float, .literal_string, .literal_bool, .literal_null,
.literal_int, .literal_float, .literal_float_value, .literal_string, .literal_bool, .literal_null,
.expr_identifier, .invalid_expr: {}
.expr_unary: layout.direct_children = 2
.expr_binary, .expr_range, .expr_index, .expr_selection, .expr_typed_constant,
@@ -1773,7 +1810,7 @@ node_layout proc(node Node) NodeLayout {
layout.direct_children = 6
.expr_pointer_type: layout.direct_children = 4
.expr_optional_type, .expr_distinct_type, .expr_alias, .expr_enum_literal,
.expr_try: layout.direct_children = 1
.expr_try, .expr_comptime: layout.direct_children = 1
.expr_call, .expr_builtin_call, .expr_function_type, .expr_array_literal,
.expr_record_type, .expr_record_literal, .expr_enum_type, .expr_match,
.block, .package: {
@@ -2089,6 +2126,7 @@ add_owned_literal proc(
}
#! borrows a literal's spelling, independently of its diagnostic source span.
#! query float_value first for generated IEEE literals.
literal_text proc(tree @Tree, id NodeId) []u8 {
node :: get_node(&tree.storage, id)
if (node.data0.extra == 1) return tree.storage.generated.items[usize(node.main.extra)]
+4 -2
View File
@@ -85,11 +85,13 @@ recompiles_specialized_calls_without_borrowing_previous_ir test {
` return result
`}
`Fixed proc($N usize) type { return struct { values [N]i16 } }
`data proc() [2]i16 { return [40, 2] }
`main proc() i16 {
` box Box(i16) :: { value = identity(i16, 40) }
` values :: $(data())
` box Box(i16) :: { value = identity(i16, values[0]) }
` fixed Fixed(count()) :: { values = [box.value, 2] }
` same Fixed(2) :: fixed
` return identity(same.values[0]) + same.values[1]
` return identity(same.values[0]) + same.values[$(usize(1))]
`}
,
)
+2
View File
@@ -112,6 +112,7 @@ Code :: enum {
specialization_cycle
specialization_limit
comptime_execution_limit
comptime_value_escape
}
Diagnostic :: struct {
@@ -245,5 +246,6 @@ messages :: $enummap.init(Code, []u8, {
specialization_cycle = "compile-time specialization depends on its unfinished result",
specialization_limit = "compile-time specialization exceeds its recursion or instance limit",
comptime_execution_limit = "compile-time execution exceeds its recursion or step limit",
comptime_value_escape = "compile-time result cannot escape evaluation as a source value",
})
+9
View File
@@ -1795,6 +1795,9 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
}
if (typecheck.is_union_variant(state.checked, id)) return try lower_union(state, id)
match ast.expr_kind(tree, id) {
.comptime_expr: {
if (!trap_type(state, id)) unreachable
}
.typed_constant: {
if (trap_type(state, id)) return ir.no_value
return try lower_as(state, ast.typed_constant(tree, id).value, typecheck.type_of(state.checked, id))
@@ -2130,6 +2133,12 @@ exact_float_integer proc(text []u8, value f64) bool {
@hide:file
lower_number proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
span :: ast.node_span(state.tree, id)
if (ast.float_value(state.tree, id)) |value| {
return try constant(state, ir.Value{
type = .float,
data = ir.ValueData{ real = value },
}, span)
}
text :: ast.literal_text(state.tree, id)
type :: lower_type(state, typecheck.type_of(state.checked, id))
if (ir.integer_bits(type) != 0 and !ir.unsigned_integer(type)) {
+15
View File
@@ -1606,6 +1606,21 @@ parse_primary proc(state @mut State) ast.NodeId ! ParseError {
.union: return try parse_union_type(state)
.distinct, .alias: return try parse_type_wrapper(state)
.match: return try parse_match(state)
.dollar: {
state.next_token += 1
_ = try consume(state, .open_paren)
previous :: state.allow_record_literals
state.allow_record_literals = true
defer state.allow_record_literals = previous
state.paren_depth += 1
defer state.paren_depth -= 1
expression :: try parse_expr(state)
skip_operand_newlines(state)
close :: try consume(state, .close_paren)
return try ast.add_comptime_expr(state.builder, ast.Span{
start = token.span.start, end = close.span.end, source = token.span.source,
}, expression)
}
.dot: {
state.next_token += 1
member :: try consume_member(state, false)
+2
View File
@@ -1144,6 +1144,7 @@ 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) {
.comptime_expr: try fail(state, id, .comptime_argument_required, ast.node_span(tree, id))
.typed_constant: {
value :: ast.typed_constant(tree, id)
state.cells[usize(id)].fixed = true
@@ -3978,6 +3979,7 @@ constant_order proc(left, right ConstantInteger) i32 {
@hide:file
constant_real proc(state @mut State, id ast.NodeId) ?f64 ! mem.AllocError {
if (ast.float_value(state.tree, id)) |value| return value
missing ?f64 :: null
if (ast.expr_kind(state.tree, id) == .literal_float) {
return strconv.parse_float(state.edges.allocator, ast.literal_text(state.tree, id)) catch |err| {
+1
View File
@@ -526,6 +526,7 @@ resolve_expr proc(
if (state.bindings[usize(id)].kind != .none) return
tree :: state.tree
match ast.expr_kind(tree, id) {
.comptime_expr: try resolve_expr(state, scope, ast.comptime_operand(tree, id))
.typed_constant: {
value :: ast.typed_constant(tree, id)
try resolve_expr(state, scope, value.type)
+291 -36
View File
@@ -26,6 +26,8 @@ ValueKind :: enum {
string
function
null
real
aggregate
}
@hide:file
@@ -101,19 +103,21 @@ elaborate proc(
) Result ! Error {
result := Result{ builder = builder, changes = arraylist.init(allocator), constraints = arraylist.init(allocator) }
errdefer deinit(&result)
# Explicit evaluation is useful even in packages without generic declarations.
original :: ast.view(builder, root)
generic := false
for (0..ast.package_decl_count(&original)) |i| {
id :: ast.package_decl(&original, i)
if (ast.decl_kind(&original, id) != .function) continue
result_type :: sema.binding(resolution, ast.function_decl(&original, id).return_type)
if (ast.function_generic(&original, id)
or (result_type.kind == .builtin and result_type.data.builtin == .type)) {
generic = true
break
needed := false
for (0..ast.node_count(&original)) |i| {
id :: ast.NodeId(i)
class :: ast.node_class(&original, id)
if (class == .expression and ast.expr_kind(&original, id) == .comptime_expr) needed = true
if (class == .function and ast.function_generic(&original, id)) needed = true
if (class == .function) {
result_type :: sema.binding(resolution, ast.function_decl(&original, id).return_type)
if (result_type.kind == .builtin and result_type.data.builtin == .type) needed = true
}
if (needed) break
}
if (!generic) return result
if (!needed) return result
mapping :: try mem.alloc(ast.NodeId, allocator, ast.node_count(&original))
defer mem.free(allocator, mapping)
state := State{
@@ -185,6 +189,26 @@ 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 == .expression and ast.expr_kind(&state.tree, id) == .comptime_expr) {
operand :: ast.comptime_operand(&state.tree, id)
try arraylist.append(&state.rewrites.constraints, operand)
state.evaluation_diagnostic = diagnostics.no_diagnostic
value :: try evaluate(state, operand)
if (value.kind == .invalid) {
try reject_evaluation(state, id)
return
}
concrete :: try materialize(state, value)
replacement := concrete.node
if (value.annotation != no_node and value.kind != .type) {
replacement = try ast.add_typed_constant(state.builder, ast.node_span(&state.tree, id),
value.annotation, replacement)
try refresh(state)
}
try replace(state, id, replacement)
try refresh(state)
return
}
if (class == .expression and ast.expr_kind(&state.tree, id) == .call) {
callee :: ast.call_expr(&state.tree, id).callee
try expand(state, callee)
@@ -277,7 +301,10 @@ specialize_call proc(state @mut State, call, function ast.NodeId) void ! Error {
try expand(state, argument)
if (view.comptime) {
state.evaluation_diagnostic = diagnostics.no_diagnostic
arguments[i] = try evaluate(state, argument)
annotation := view.type
if (contextual_literal(state, argument))
annotation = try argument_annotation(state, function, view.type, arguments)
arguments[i] = try evaluate_expected(state, argument, annotation)
if (arguments[i].kind == .invalid) {
try reject_evaluation(state, call)
return
@@ -527,9 +554,10 @@ evaluation_dependencies proc(
resolved :: binding(state, id)
if (resolved.kind == .declaration) {
target :: resolved.data.declaration
if ((flags[usize(target)] & 1) != 0 and (flags[usize(target)] & 2) == 0) {
if (ast.node_class(&state.tree, target) == .statement
and ast.statement_decl(&state.tree, target).mutability == .variable) return false
if ((flags[usize(target)] & 8) == 0 and (flags[usize(target)] & 2) == 0) {
class :: ast.node_class(&state.tree, target)
if (class == .parameter) return false
if (class == .statement and ast.statement_decl(&state.tree, target).mutability == .variable) return false
flags[usize(target)] = flags[usize(target)] | 2
try arraylist.append(declarations, target)
}
@@ -540,20 +568,212 @@ evaluation_dependencies proc(
return true
}
@hide:file
evaluation_owned proc(state @State, id ast.NodeId, flags []mut u8) void {
if ((flags[usize(id)] & 8) != 0) return
flags[usize(id)] = flags[usize(id)] | 8
for (0..ast.child_count(&state.tree, id)) |i| evaluation_owned(state, ast.child(&state.tree, id, i), flags)
}
@hide:file
contextual_literal proc(state @State, id ast.NodeId) bool {
kind :: ast.expr_kind(&state.tree, id)
return kind == .record_literal or kind == .array_literal or kind == .enum_literal
}
@hide:file
argument_annotation proc(
state @mut State, function, annotation ast.NodeId, arguments []Value,
) ast.NodeId ! Error {
mapping :: try mem.alloc(ast.NodeId, state.allocator, state.original_count)
defer mem.free(state.allocator, mapping)
mem.set(mapping, no_node)
for (0..ast.function_param_count(&state.tree, function)) |i| {
if (arguments[i].kind == .invalid) continue
parameter :: ast.function_param(&state.tree, function, i)
value :: try materialize(state, arguments[i])
for (0..mapping.len) |n| {
resolved :: binding(state, ast.NodeId(n))
if (resolved.kind == .declaration and resolved.data.declaration == parameter
and ast.node_class(&state.tree, ast.NodeId(n)) == .expression) mapping[n] = value.node
}
}
start :: ast.node_count(&state.tree)
result :: try ast.clone_subtree(state.builder, annotation, mapping)
try refresh(state)
for (0..mapping.len) |i| {
target :: mapping[i]
if (target != no_node and usize(target) >= start) state.bindings.items[usize(target)] = state.bindings.items[i]
}
return result
}
@hide:file
evaluate_expected proc(state @mut State, id, annotation ast.NodeId) Value ! Error {
if (contextual_literal(state, id)) {
type :: try canonical_type(state, annotation)
if (type != no_node) return try evaluate_ir(state, id, type)
}
return try evaluate(state, id)
}
@hide:file
aggregate_child proc(module @ir.Module, parent ir.Value, type ir.Type, shape ir.ArrayId, offset usize) ir.Value {
if (!ir.aggregate(type)) return parent.data.elements[offset]
cells :: module.arrays.items[usize(shape)].cells
return ir.Value{ type = type, array_type = shape,
data = ir.ValueData{ elements = parent.data.elements[offset..offset + cells] } }
}
# Values leaving the temporary interpreter must own no interpreter addresses or module IDs.
# Rebuild contextual source literals while the module and its escaped flat cells are alive.
@hide:file
serialize_value proc(
state @mut State, module @ir.Module, checked @typecheck.Result,
type typecheck.Type, value ir.Value, span ast.Span,
) ast.NodeId ! Error {
result := no_node
if (type.kind == .array) {
array :: typecheck.array_type(checked, type.array_type)
shape :: module.arrays.items[usize(value.array_type)]
stride :: if (ir.aggregate(shape.element)) module.arrays.items[usize(shape.element_array)].cells else usize(1)
elements := arraylist.init(ast.NodeId, state.allocator)
defer arraylist.deinit(&elements)
for (0..array.len) |i| {
child :: aggregate_child(module, value, shape.element, shape.element_array, i * stride)
node :: try serialize_value(state, module, checked, array.element, child, span)
if (node == no_node) return no_node
try arraylist.append(&elements, node)
}
result = try ast.add_array_literal(state.builder, span, elements.items)
} else if (type.kind == .record) {
record :: typecheck.record_type(checked, type.record_type)
if (record.is_union) return no_node
shape :: module.arrays.items[usize(value.array_type)]
fields := arraylist.init(ast.NodeId, state.allocator)
defer arraylist.deinit(&fields)
for (record.fields) |field, i| {
layout :: shape.fields[i]
child :: aggregate_child(module, value, layout.type, layout.array_type, layout.offset)
node :: try serialize_value(state, module, checked, field.type, child, span)
if (node == no_node) return no_node
generated :: try ast.add_field(state.builder, span, field.name, null, node)
try refresh(state)
try arraylist.append(&fields, generated)
}
result = try ast.add_record_literal(state.builder, span, null, fields.items)
} else if (type.kind == .enumeration) {
enumeration :: typecheck.enum_type(checked, type.enum_type)
negative :: !ir.unsigned_integer(value.type) and value.data.integer < 0
magnitude :: if (negative) u64(-(value.data.integer + 1)) + 1 else value.data.unsigned
for (enumeration.members) |member| {
if (member.magnitude == magnitude and member.negative == negative) {
result = try ast.add_enum_literal(state.builder, span, member.name)
break
}
}
} else if (type.kind == .optional) {
container :: typecheck.array_type(checked, type.array_type)
if (container.element.kind == .pointer or container.element.kind == .function) {
if (value.data.unsigned != 0) return no_node
result = try ast.add_literal(state.builder, .null, span)
} else if (!value.data.elements[0].data.boolean) {
result = try ast.add_literal(state.builder, .null, span)
} else {
shape :: module.arrays.items[usize(value.array_type)]
payload :: shape.fields[1]
child :: aggregate_child(module, value, payload.type, payload.array_type, payload.offset)
return try serialize_value(state, module, checked, container.element, child, span)
}
} else if (ir.integer_bits(value.type) != 0 and type.kind != .pointer) {
negative :: !ir.unsigned_integer(value.type) and value.data.integer < 0
magnitude :: if (negative) u64(-(value.data.integer + 1)) + 1 else value.data.unsigned
result = try ast.add_integer(state.builder, span, magnitude)
if (negative) result = try ast.add_unary(state.builder, span, .negate, result)
} else if (type.kind == .float) {
result = try ast.add_float(state.builder, span, value.data.real)
} else if (type.kind == .bool) {
result = try ast.add_boolean(state.builder, span, value.data.boolean)
} else if (type.kind == .string) {
result = try ast.add_string(state.builder, span, ir.string_bytes(module, value.data.string))
}
try refresh(state)
return result
}
@hide:file
constant_equal proc(state @mut State, left, right ast.NodeId) bool ! Error {
kind :: ast.expr_kind(&state.tree, left)
if (kind != ast.expr_kind(&state.tree, right)) return false
if (kind == .array_literal) {
count :: ast.array_element_count(&state.tree, left)
if (count != ast.array_element_count(&state.tree, right)) return false
for (0..count) |i| {
if (!try constant_equal(state, ast.array_element(&state.tree, left, i), ast.array_element(&state.tree, right, i))) return false
}
return true
}
if (kind == .record_literal) {
count :: ast.record_field_count(&state.tree, left)
if (count != ast.record_field_count(&state.tree, right)) return false
for (0..count) |i| {
a :: ast.field(&state.tree, ast.record_field(&state.tree, left, i))
b :: ast.field(&state.tree, ast.record_field(&state.tree, right, i))
if ((a.name == null) != (b.name == null)) return false
if (a.name != null and ast.name_id(&state.tree, a.name?) != ast.name_id(&state.tree, b.name?)) return false
if (!try constant_equal(state, a.value?, b.value?)) return false
}
return true
}
if (kind == .enum_literal)
return ast.name_id(&state.tree, ast.enum_literal(&state.tree, left)) == ast.name_id(&state.tree, ast.enum_literal(&state.tree, right))
return try value_equal(state, try evaluate(state, left), try evaluate(state, right))
}
@hide:file
constant_hash proc(state @mut State, id ast.NodeId) usize ! Error {
kind :: ast.expr_kind(&state.tree, id)
hash :: enum_index(kind)
if (kind == .array_literal) {
result := hash
for (0..ast.array_element_count(&state.tree, id)) |i|
result = mix(result, try constant_hash(state, ast.array_element(&state.tree, id, i)))
return result
}
if (kind == .record_literal) {
result := hash
for (0..ast.record_field_count(&state.tree, id)) |i| {
field :: ast.field(&state.tree, ast.record_field(&state.tree, id, i))
if (field.name) |name| result = mix(result, usize(ast.name_id(&state.tree, name)))
result = mix(result, try constant_hash(state, field.value?))
}
return result
}
if (kind == .enum_literal) return mix(hash, usize(ast.name_id(&state.tree, ast.enum_literal(&state.tree, id))))
return try value_hash(state, try evaluate(state, id))
}
@hide:file
evaluate_call proc(state @mut State, call ast.NodeId) Value ! Error {
callee :: try evaluate(state, ast.call_expr(&state.tree, call).callee)
if (callee.kind != .function and callee.kind != .type) return Value{}
result_type := callee.node
function := no_node
if (callee.kind == .function) {
resolved :: binding(state, callee.node)
if (resolved.kind != .declaration) return Value{}
function = resolved.data.declaration
result_type = ast.function_decl(&state.tree, resolved.data.declaration).return_type
}
arguments := arraylist.init(ast.NodeId, state.allocator)
defer arraylist.deinit(&arguments)
for (0..ast.call_arg_count(&state.tree, call)) |i| {
value :: try evaluate(state, ast.call_arg(&state.tree, call, i))
argument :: ast.call_arg(&state.tree, call, i)
value := Value{}
if (function != no_node and i < ast.function_param_count(&state.tree, function)) {
annotation :: ast.param(&state.tree, ast.function_param(&state.tree, function, i)).type
value = try evaluate_expected(state, argument, annotation)
} else value = try evaluate(state, argument)
if (value.kind == .invalid) return Value{}
concrete := try materialize(state, value)
if (value.annotation != no_node) {
@@ -567,7 +787,15 @@ evaluate_call proc(state @mut State, call ast.NodeId) Value ! Error {
# inference must have exactly the same semantics as an ordinary runtime call.
span :: ast.node_span(&state.tree, call)
invocation :: try ast.add_call(state.builder, span, callee.node, arguments.items)
returned :: try ast.add_return_stmt(state.builder, span, invocation)
try refresh(state)
return try evaluate_ir(state, invocation, result_type)
}
@hide:file
evaluate_ir proc(state @mut State, expression, result_type ast.NodeId) Value ! Error {
if (result_type == no_node) return Value{}
span :: ast.node_span(&state.tree, expression)
returned :: try ast.add_return_stmt(state.builder, span, expression)
statements [1]ast.NodeId :: [returned]
body :: try ast.add_block(state.builder, span, statements[..])
wrapper :: try ast.add_function_decl(state.builder, span, span, strpool.no_str, result_type, &[], body)
@@ -578,17 +806,12 @@ evaluate_call proc(state @mut State, call ast.NodeId) Value ! Error {
flags := arraylist.init(u8, state.allocator)
defer arraylist.deinit(&flags)
index := usize(0)
registered := usize(0)
while (index < declarations.items.len) {
declaration :: declarations.items[index]
try expand(state, declaration)
while (flags.items.len < ast.node_count(&state.tree)) try arraylist.append(&flags, u8(0))
while (registered < state.declarations.items.len) {
target :: state.declarations.items[registered]
flags.items[usize(target)] = flags.items[usize(target)] | 1
registered += 1
}
flags.items[usize(declaration)] = flags.items[usize(declaration)] | 2
evaluation_owned(state, declaration, flags.items)
if (!try evaluation_dependencies(state, declaration, flags.items, &declarations)) return Value{}
index += 1
}
@@ -605,7 +828,11 @@ evaluate_call proc(state @mut State, call ast.NodeId) Value ! Error {
original_diagnostics :: diagnostics.count(&diagnostic_store)
checked := try typecheck.check(state.allocator, &tree, state.resolution, &diagnostic_store)
defer typecheck.deinit(&checked)
if (diagnostics.count(&diagnostic_store) != original_diagnostics) return Value{}
if (diagnostics.count(&diagnostic_store) != original_diagnostics) {
diagnostic :: diagnostic_store.list.items[original_diagnostics]
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, diagnostic.code, diagnostic.span)
return Value{}
}
module := try lower.lower_evaluation(state.allocator, &tree, state.resolution, &checked, wrapper, &diagnostic_store)
defer ir.deinit(&module)
outcome := try interpreter.run_evaluation(state.allocator, &module, &diagnostic_store)
@@ -619,20 +846,17 @@ evaluate_call proc(state @mut State, call ast.NodeId) Value ! Error {
return Value{}
}
value :: outcome.value
if (ir.integer_bits(value.type) != 0) {
negative :: !ir.unsigned_integer(value.type) and value.data.integer < 0
magnitude :: if (negative) u64(-(value.data.integer + 1)) + 1 else value.data.unsigned
return Value{ kind = .integer, node = call, magnitude = magnitude, negative = negative, annotation = result_type }
source_type :: typecheck.type_of(&checked, expression)
node :: try serialize_value(state, &module, &checked, source_type, value, span)
if (node == no_node) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .comptime_value_escape, span)
return Value{}
}
if (value.type == .bool) return Value{
kind = .boolean, node = call, boolean = value.data.boolean, annotation = result_type,
}
if (value.type == .string) {
literal :: try ast.add_string(state.builder, span, ir.string_bytes(&module, value.data.string))
try refresh(state)
return Value{ kind = .string, node = literal, annotation = result_type }
}
return Value{}
if (ir.aggregate(value.type) or source_type.kind == .enumeration)
return Value{ kind = .aggregate, node = node, annotation = result_type }
result := try evaluate(state, node)
result.annotation = result_type
return result
}
@hide:file
@@ -726,6 +950,12 @@ evaluate proc(state @mut State, id ast.NodeId) Value ! Error {
}
return Value{ kind = .integer, node = id, magnitude = value }
}
.literal_float: {
if (ast.float_value(&state.tree, id)) |value| return Value{
kind = .real, node = id, magnitude = ptrcast!(@u64, &value)^,
}
return try evaluate_ir(state, id, try builtin(state, id, .float))
}
.literal_bool: return Value{ kind = .boolean, node = id,
boolean = mem.eql(ast.literal_text(&state.tree, id), "true") }
.literal_string: {
@@ -749,6 +979,8 @@ evaluate proc(state @mut State, id ast.NodeId) Value ! Error {
value := try evaluate(state, view.operand)
if (view.operator == .negate and value.kind == .integer) {
value.negative = value.magnitude != 0 and !value.negative
} else if (view.operator == .negate and value.kind == .real) {
value.magnitude = value.magnitude xor (u64(1) << 63)
} else if (view.operator == .logical_not and value.kind == .boolean) {
value.boolean = !value.boolean
} else return Value{}
@@ -757,6 +989,8 @@ evaluate proc(state @mut State, id ast.NodeId) Value ! Error {
}
.binary: return try evaluate_binary(state, id)
.call: return try evaluate_call(state, id)
.record_literal, .array_literal, .selection, .index, .match_expr, .builtin_call:
return try evaluate_ir(state, id, try expression_type(state, id, 0))
else: return Value{}
}
}
@@ -764,6 +998,7 @@ evaluate proc(state @mut State, id ast.NodeId) Value ! Error {
@hide:file
evaluate_binary proc(state @mut State, id ast.NodeId) Value ! Error {
view :: ast.binary_expr(&state.tree, id)
if (view.operator == .divide) return try evaluate_ir(state, id, try builtin(state, id, .float))
left :: try evaluate(state, view.left)
if (left.kind == .invalid) return Value{}
if (left.kind == .boolean) {
@@ -772,6 +1007,15 @@ evaluate_binary proc(state @mut State, id ast.NodeId) Value ! Error {
}
right := try evaluate(state, view.right)
if (left.kind == .invalid or right.kind == .invalid) return Value{}
if (left.kind == .real or right.kind == .real) {
boolean :: (
view.operator == .equal or view.operator == .not_equal or view.operator == .less
or view.operator == .less_equal or view.operator == .greater or view.operator == .greater_equal
)
kind sema.BuiltinType :: if (boolean) .bool else .float
result_type :: try builtin(state, id, kind)
return try evaluate_ir(state, id, result_type)
}
if (left.annotation != no_node and right.annotation != no_node
and left.kind == .integer and right.kind == .integer
and !try same_type(state, left.annotation, right.annotation)) return Value{}
@@ -888,6 +1132,11 @@ value_equal proc(state @mut State, left, right Value) bool ! Error {
.integer: left.magnitude == right.magnitude and left.negative == right.negative
.boolean: left.boolean == right.boolean
.function: binding(state, left.node).data.declaration == binding(state, right.node).data.declaration
.real: left.magnitude == right.magnitude
.aggregate: (
try same_type(state, left.annotation, right.annotation)
and try constant_equal(state, left.node, right.node)
)
.string: try same_string(state, left.node, right.node)
}
}
@@ -903,6 +1152,8 @@ value_hash proc(state @mut State, value Value) usize ! Error {
.boolean: mix(hash, boolean)
.function: mix(hash, usize(binding(state, value.node).data.declaration))
.string: string_hash(hash, ast.literal_text(&state.tree, value.node))
.real: mix(hash, usize(value.magnitude))
.aggregate: mix(mix(hash, try type_hash(state, value.annotation)), try constant_hash(state, value.node))
.invalid, .null: hash
}
}
@@ -1216,6 +1467,8 @@ materialize proc(state @mut State, value Value) Value ! Error {
if (value.negative) result.node = try ast.add_unary(state.builder, span, .negate, result.node)
} else if (value.kind == .boolean) {
result.node = try ast.add_boolean(state.builder, span, value.boolean)
} else if (value.kind == .real) {
result.node = try ast.add_float(state.builder, span, ptrcast!(@f64, &value.magnitude)^)
}
try refresh(state)
return result
@@ -1230,6 +1483,7 @@ valid_value proc(state @mut State, annotation ast.NodeId, value Value) bool ! Er
kind :: resolved.data.builtin
if (kind == .bool) return value.kind == .boolean
if (kind == .string) return value.kind == .string
if (kind == .float or kind == .f64) return value.kind == .real
if (value.kind != .integer) return false
bits := u32(64)
signed := false
@@ -1264,6 +1518,7 @@ valid_value proc(state @mut State, annotation ast.NodeId, value Value) bool ! Er
return value.magnitude <= limit
}
kind :: ast.expr_kind(&state.tree, type)
if (value.kind == .aggregate) return value.annotation != no_node and try same_type(state, type, value.annotation)
if (kind == .optional_type) return value.kind == .null or try valid_value(state, ast.type_operand(&state.tree, type), value)
if (kind == .slice_type and value.kind == .string) {
view :: ast.slice_type(&state.tree, type)