feat: support existing enum map initialization and lookup
This commit is contained in:
@@ -121,6 +121,7 @@ Code :: enum {
|
||||
invalid_c_record_field_type
|
||||
missing_metadata_import
|
||||
invalid_metadata_abi
|
||||
invalid_undefined
|
||||
}
|
||||
|
||||
Diagnostic :: struct {
|
||||
@@ -263,5 +264,6 @@ messages :: $enummap.init(Code, []u8, {
|
||||
invalid_c_record_field_type = "record field is not a supported C-layout value type",
|
||||
missing_metadata_import = "typeinfo! requires importing @std/meta",
|
||||
invalid_metadata_abi = "@std/meta declarations do not match the compiler reflection ABI",
|
||||
invalid_undefined = "undefined requires a concrete mutable local declaration",
|
||||
})
|
||||
|
||||
|
||||
@@ -229,6 +229,13 @@ execute proc(
|
||||
value := ir.Value{}
|
||||
match instruction.op {
|
||||
.constant: value = instruction.constant
|
||||
.undefined_value: {
|
||||
destination :: &frames.items[frame_index].values[usize(instruction.destination)]
|
||||
if (ir.aggregate(instruction.type)) {
|
||||
mem.set(destination.data.elements, ir.Value{ type = .invalid, uninitialized = true })
|
||||
} else destination^ = ir.Value{ type = instruction.type, uninitialized = true }
|
||||
continue
|
||||
}
|
||||
.load_local: {
|
||||
local :: &frames.items[frame_index].locals[usize(instruction.slot)]
|
||||
if (!local.initialized) return try runtime_trap(diagnostic_store, .read_before_initialization, instruction.span)
|
||||
@@ -663,6 +670,7 @@ execute proc(
|
||||
value = ir.Value{ type = .bool, data = ir.ValueData{ boolean = result } }
|
||||
}
|
||||
}
|
||||
if (value.uninitialized) return try runtime_trap(diagnostic_store, .read_before_initialization, instruction.span)
|
||||
if (instruction.destination != ir.no_value) copy_value(module, &frames.items[frame_index].values[usize(instruction.destination)], value)
|
||||
}
|
||||
unreachable
|
||||
|
||||
+8
-1
@@ -88,6 +88,8 @@ Value :: struct {
|
||||
type Type = .void
|
||||
array_type ArrayId = no_array
|
||||
raw bool = false
|
||||
# interpreter poison survives copies; native storage uses LLVM poison directly.
|
||||
uninitialized bool = false
|
||||
data ValueData = ValueData{ integer = 0 }
|
||||
}
|
||||
|
||||
@@ -126,6 +128,7 @@ ArrayStorage :: struct {
|
||||
|
||||
Op :: enum {
|
||||
constant
|
||||
undefined_value
|
||||
load_local
|
||||
store_local
|
||||
assign_local
|
||||
@@ -372,6 +375,7 @@ aggregate_item proc(module @Module, value Value, index usize) Value {
|
||||
raw :: value.raw or shape.is_union
|
||||
if (!aggregate(type)) {
|
||||
if (raw) return read_storage(module, type, child, value.data.elements[start..], true)
|
||||
if (value.data.elements[start].uninitialized) return Value{ type = type, uninitialized = true }
|
||||
return value.data.elements[start]
|
||||
}
|
||||
width = if (raw) storage_size(module, type, child) else module.arrays.items[usize(child)].cells
|
||||
@@ -438,6 +442,9 @@ read_storage proc(module @Module, type Type, shape ArrayId, cells []mut Value, r
|
||||
bits u64 := 0
|
||||
size :: storage_size(module, type, shape)
|
||||
count :: if (size > 8) usize(8) else size
|
||||
for (0..count) |i| {
|
||||
if (cells[i].uninitialized) return Value{ type = type, uninitialized = true }
|
||||
}
|
||||
for (0..count) |i| bits += cells[i].data.unsigned << u64(i * 8)
|
||||
data := ValueData{ unsigned = bits }
|
||||
match type {
|
||||
@@ -485,7 +492,7 @@ write_storage proc(module @Module, cells []mut Value, value Value) void {
|
||||
byte u64 := 0
|
||||
if (i < 8) byte = (bits >> u64(i * 8)) & 255
|
||||
else byte = (u64(string_bytes(module, value.data.string).len) >> u64((i - 8) * 8)) & 255
|
||||
cells[i] = Value{ type = .u8, data = ValueData{ unsigned = byte } }
|
||||
cells[i] = Value{ type = .u8, uninitialized = value.uninitialized, data = ValueData{ unsigned = byte } }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,12 @@ emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruct
|
||||
try io.print(", ptr %v{}\n", output, {u32(instruction.destination)})
|
||||
return
|
||||
}
|
||||
.undefined_value: {
|
||||
try io.write_all(output, " store ")
|
||||
try write_type(output, state.module, type)
|
||||
try io.print(" poison, ptr %v{}\n", output, {u32(instruction.destination)})
|
||||
return
|
||||
}
|
||||
.load_local: {
|
||||
try check_local(state, instruction.slot, true, instruction.span)
|
||||
try io.print(" %r{} = load ", output, {result})
|
||||
|
||||
+18
-2
@@ -1725,8 +1725,16 @@ lower_slice proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError |
|
||||
lower_builtin proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
tree :: state.tree
|
||||
span :: ast.node_span(tree, id)
|
||||
name :: ast.source_text(tree, ast.node_span(tree, ast.builtin_call(tree, id).name))
|
||||
name :: ast.literal_text(tree, ast.builtin_call(tree, id).name)
|
||||
if (trap_type(state, id)) return ir.no_value
|
||||
if (mem.eql(name, "some")) {
|
||||
type :: typecheck.type_of(state.checked, id)
|
||||
child :: typecheck.array_type(state.checked, type.array_type).element
|
||||
payload :: try lower_as(state, ast.call_arg(tree, id, 0), child)
|
||||
if (state.terminated or child.kind == .pointer or child.kind == .function) return payload
|
||||
present :: try constant(state, ir.Value{ type = .bool, data = ir.ValueData{ boolean = true } }, span)
|
||||
return try record_pair(state, try array_shape(state, type), present, payload, span)
|
||||
}
|
||||
if (mem.eql(name, "sizeof") or mem.eql(name, "alignof")) {
|
||||
checked :: typecheck.type_of(state.checked, ast.call_arg(tree, id, 0))
|
||||
type :: lower_type(state, checked)
|
||||
@@ -1893,7 +1901,15 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
|
||||
if (!trap_type(state, id)) trap(state, try diagnostics.add(state.diagnostics, .type_not_value, span), span)
|
||||
}
|
||||
.alias_expr: return try lower_expression(state, ast.type_operand(tree, id))
|
||||
.enum_literal, .literal_undefined: {
|
||||
.literal_undefined: {
|
||||
if (trap_type(state, id)) return ir.no_value
|
||||
checked :: typecheck.type_of(state.checked, id)
|
||||
return try emit(state, ir.Instruction{
|
||||
op = .undefined_value, type = lower_type(state, checked),
|
||||
array_type = try array_shape(state, checked), span = span,
|
||||
})
|
||||
}
|
||||
.enum_literal: {
|
||||
if (!trap_type(state, id)) unreachable
|
||||
}
|
||||
.match_expr: return try lower_match(state, id)
|
||||
|
||||
@@ -174,6 +174,66 @@ supports_bare_void_value_returns_and_fallthrough test {
|
||||
}
|
||||
}
|
||||
|
||||
constructs_present_optionals_without_losing_nested_null_or_repeating_values test {
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
source ::
|
||||
`next proc(calls @mut i16) i16 {
|
||||
` calls^ = calls^ + 1
|
||||
` return 42
|
||||
`}
|
||||
`nested proc() ??i16 { return some!(null) }
|
||||
`Box :: struct { values [2]i16 }
|
||||
`main proc() bool {
|
||||
` calls i16 := 0
|
||||
` absent ??i16 :: null
|
||||
` present ??i16 :: nested()
|
||||
` full ??i16 :: some!(some!(42))
|
||||
` exact i16 :: 42
|
||||
` value ?i16 :: some!(exact)
|
||||
` once ?i16 :: some!(next(&calls))
|
||||
` boxed ?Box :: some!(Box{ values = [20, 22] })
|
||||
` pointer ?@i16 :: some!(&exact)
|
||||
` nested_pointer ??@i16 :: some!(null)
|
||||
` return (
|
||||
` absent == null and present != null and present? == null
|
||||
` and full != null and full? != null and full?? == 42
|
||||
` and value? == 42 and once? == 42 and calls == 1
|
||||
` and boxed?.values[0] + boxed?.values[1] == 42
|
||||
` and pointer?^ == 42 and nested_pointer != null and nested_pointer? == null
|
||||
` )
|
||||
`}
|
||||
module := try compile_owned(source, &diagnostic_store)
|
||||
defer ir.deinit(&module)
|
||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||
defer interpreter.deinit(&outcome)
|
||||
try testing.expect(outcome.trap == null)
|
||||
try testing.expect_equal(ir.Type.bool, outcome.value.type)
|
||||
try testing.expect(outcome.value.data.boolean)
|
||||
}
|
||||
|
||||
rejects_some_without_optional_context_wrong_arity_and_inexact_payloads test {
|
||||
sources [6][]u8 :: [
|
||||
"main proc() ?i16 { return some!() }\n",
|
||||
"main proc() ?i16 { return some!(1, 2) }\n",
|
||||
"main proc() bool { return some!(true) }\n",
|
||||
"main proc() ?i16 { return some!(true) }\n",
|
||||
"main proc() ?i16 { value i32 :: 42\nreturn some!(value) }\n",
|
||||
"main proc() ?i16 { return some!(null) }\n",
|
||||
]
|
||||
for (sources) |source, i| {
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
module := try compile_owned(source, &diagnostic_store)
|
||||
defer ir.deinit(&module)
|
||||
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||
defer interpreter.deinit(&outcome)
|
||||
code diagnostics.Code :: if (i < 2) .argument_count_mismatch else if (i == 2) .invalid_operand else .type_mismatch
|
||||
try expect_trap(outcome, &diagnostic_store, code)
|
||||
}
|
||||
}
|
||||
|
||||
preserves_demanded_diagnostic_identity_and_source_location test {
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
|
||||
@@ -924,6 +924,20 @@ collect_declaration proc(state @mut State, id ast.NodeId) void ! (mem.AllocError
|
||||
state.cells[usize(id)].fixed = true
|
||||
state.cells[usize(id)].type = invalid_type(binding.data.diagnostic)
|
||||
}
|
||||
if (ast.expr_kind(state.tree, declaration.initializer) == .literal_undefined) {
|
||||
type :: state.cells[usize(id)].type
|
||||
if (
|
||||
!state.cells[usize(id)].local or declaration.mutability != .variable
|
||||
or type.kind == .none or type.kind == .type_value or has_inferred_count(state, type)
|
||||
) {
|
||||
try fail(state, declaration.initializer, .invalid_undefined, ast.node_span(state.tree, declaration.initializer))
|
||||
} else {
|
||||
state.cells[usize(declaration.initializer)].role = .expression
|
||||
seed(state, declaration.initializer, type)
|
||||
}
|
||||
try depend(state, declaration.initializer, id)
|
||||
return
|
||||
}
|
||||
try collect_expression(state, declaration.initializer)
|
||||
if (
|
||||
type_syntax(ast.expr_kind(state.tree, declaration.initializer))
|
||||
@@ -1201,7 +1215,7 @@ collect_expression proc(state @mut State, id ast.NodeId) void ! (mem.AllocError
|
||||
.literal_string: state.cells[usize(id)].type = Type{ kind = .string }
|
||||
.literal_null: state.cells[usize(id)].type = Type{ kind = .null }
|
||||
.literal_void: state.cells[usize(id)].type = Type{ kind = .void }
|
||||
.literal_undefined: try fail(state, id, .comptime_argument_required, ast.node_span(tree, id))
|
||||
.literal_undefined: try fail(state, id, .invalid_undefined, ast.node_span(tree, id))
|
||||
.builtin_call: try collect_builtin(state, id)
|
||||
.slice: {
|
||||
view :: ast.slice_expr(tree, id)
|
||||
@@ -1550,6 +1564,17 @@ demand proc(state @mut State, id ast.NodeId, expected Type, code diagnostics.Cod
|
||||
seed(state, id, expected)
|
||||
return
|
||||
}
|
||||
if (mem.eql(name, "some") and expected.kind != .fallible) {
|
||||
state.cells[index].expected = expected
|
||||
if (expected.kind == .optional) {
|
||||
seed(state, id, expected)
|
||||
if (ast.call_arg_count(state.tree, id) == 1) {
|
||||
try demand(state, ast.call_arg(state.tree, id, 0),
|
||||
state.arrays.items[usize(expected.array_type)].element, code)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
if (expected.kind == .fallible) {
|
||||
descriptor :: state.arrays.items[usize(expected.array_type)]
|
||||
@@ -2368,7 +2393,7 @@ c_abi_type proc(state @State, type Type) bool {
|
||||
|
||||
@hide:file
|
||||
builtin_name proc(state @State, id ast.NodeId) []u8 {
|
||||
return ast.source_text(state.tree, ast.node_span(state.tree, ast.builtin_call(state.tree, id).name))
|
||||
return ast.literal_text(state.tree, ast.builtin_call(state.tree, id).name)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
@@ -2460,13 +2485,31 @@ infer_builtin proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.A
|
||||
layout :: mem.eql(name, "sizeof") or mem.eql(name, "alignof")
|
||||
cast :: mem.eql(name, "ptrcast") or mem.eql(name, "constcast")
|
||||
memory :: mem.eql(name, "memcopy") or mem.eql(name, "memset")
|
||||
valid_count :: (layout and count == 1) or (cast and (count == 1 or count == 2)) or (memory and count == 2)
|
||||
some :: mem.eql(name, "some")
|
||||
valid_count :: (layout and count == 1) or (cast and (count == 1 or count == 2)) or (memory and count == 2) or (some and count == 1)
|
||||
if (!valid_count) {
|
||||
code diagnostics.Code :: if (layout or cast or memory) .argument_count_mismatch else .invalid_operand
|
||||
code diagnostics.Code :: if (layout or cast or memory or some) .argument_count_mismatch else .invalid_operand
|
||||
if (defaults) try fail(state, id, code, span)
|
||||
return group_type(state, id)
|
||||
}
|
||||
if (layout) return group_type(state, id)
|
||||
if (some) {
|
||||
result :: state.cells[root(state, usize(id))].expected
|
||||
if (result.kind != .optional) {
|
||||
if (defaults) try fail(state, id, .invalid_operand, span)
|
||||
return group_type(state, id)
|
||||
}
|
||||
child :: state.arrays.items[usize(result.array_type)].element
|
||||
operand :: ast.call_arg(state.tree, id, 0)
|
||||
try demand(state, operand, child, .type_mismatch)
|
||||
actual :: try infer_shape(state, operand, defaults)
|
||||
if (
|
||||
defaults and actual.kind != .none and actual.kind != .invalid
|
||||
and !assignable_value(state, operand, actual, child)
|
||||
) try fail(state, operand, .type_mismatch, ast.node_span(state.tree, operand))
|
||||
seed(state, id, result)
|
||||
return group_type(state, id)
|
||||
}
|
||||
if (cast) {
|
||||
result := state.cells[root(state, usize(id))].expected
|
||||
if (count == 2) result = state.types[usize(ast.call_arg(state.tree, id, 0))]
|
||||
|
||||
@@ -1571,10 +1571,19 @@ specialize_call proc(state @mut State, call, function ast.NodeId) void ! Error {
|
||||
if (view.comptime) continue
|
||||
argument :: runtime_arguments.items[position]
|
||||
position += 1
|
||||
contextual :: flexible(state, argument)
|
||||
contextual := flexible(state, argument) or contextual_literal(state, argument)
|
||||
if (ast.expr_kind(&state.tree, argument) == .record_literal) {
|
||||
annotation :: ast.record_literal(&state.tree, argument).type
|
||||
contextual = annotation == null or ast.expr_kind(&state.tree, annotation?) == .enum_literal
|
||||
}
|
||||
if (contextual != (phase == 1)) continue
|
||||
actual :: try expression_type(state, argument, 0)
|
||||
if (actual == no_node or !try infer(state, function, view.type, actual, arguments, contextual)) {
|
||||
# contextual literals need not have an independent type; the rewritten
|
||||
# call checks them against the substituted signature after inference.
|
||||
if (
|
||||
(actual == no_node or !try infer(state, function, view.type, actual, arguments, contextual))
|
||||
and !contextual
|
||||
) {
|
||||
try reject(state, call, .cannot_infer_generic)
|
||||
return
|
||||
}
|
||||
@@ -1872,6 +1881,8 @@ evaluation_owned proc(state @State, id ast.NodeId, flags []mut u8) void {
|
||||
@hide:file
|
||||
contextual_literal proc(state @State, id ast.NodeId) bool {
|
||||
kind :: ast.expr_kind(&state.tree, id)
|
||||
if (kind == .builtin_call)
|
||||
return mem.eql(ast.literal_text(&state.tree, ast.builtin_call(&state.tree, id).name), "some")
|
||||
return kind == .record_literal or kind == .array_literal or kind == .enum_literal
|
||||
}
|
||||
|
||||
@@ -1937,6 +1948,17 @@ serialize_value proc(
|
||||
state @mut State, module @ir.Module, checked @typecheck.Result,
|
||||
type typecheck.Type, value ir.Value, span ast.Span,
|
||||
) ast.NodeId ! Error {
|
||||
uninitialized := value.uninitialized
|
||||
if (ir.aggregate(value.type)) {
|
||||
if (module.arrays.items[usize(value.array_type)].is_union)
|
||||
uninitialized = ir.union_tag(module, value).uninitialized
|
||||
else if (type.kind == .optional)
|
||||
uninitialized = ir.aggregate_item(module, value, 0).uninitialized
|
||||
}
|
||||
if (uninitialized) {
|
||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .read_before_initialization, span)
|
||||
return no_node
|
||||
}
|
||||
result := no_node
|
||||
if (type.kind == .array) {
|
||||
array :: typecheck.array_type(checked, type.array_type)
|
||||
@@ -2013,7 +2035,12 @@ serialize_value proc(
|
||||
result = try ast.add_literal(state.builder, .null, span)
|
||||
} else {
|
||||
child :: ir.aggregate_item(module, value, 1)
|
||||
return try serialize_value(state, module, checked, container.element, child, span)
|
||||
payload :: try serialize_value(state, module, checked, container.element, child, span)
|
||||
if (payload == no_node) return no_node
|
||||
name :: try ast.add_generated_identifier(state.builder, span,
|
||||
try strpool.intern(state.strings, "some"), "some")
|
||||
arguments [1]ast.NodeId :: [payload]
|
||||
result = try ast.add_builtin_call(state.builder, span, name, arguments[..])
|
||||
}
|
||||
} else if (ir.integer_bits(value.type) != 0 and type.kind != .pointer) {
|
||||
negative :: !ir.unsigned_integer(value.type) and value.data.integer < 0
|
||||
@@ -2063,6 +2090,9 @@ constant_equal proc(state @mut State, left, right ast.NodeId) bool ! Error {
|
||||
}
|
||||
return true
|
||||
}
|
||||
if (kind == .builtin_call and contextual_literal(state, left) and contextual_literal(state, right)) {
|
||||
return try constant_equal(state, ast.call_arg(&state.tree, left, 0), ast.call_arg(&state.tree, right, 0))
|
||||
}
|
||||
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))
|
||||
@@ -2092,6 +2122,8 @@ constant_hash proc(state @mut State, id ast.NodeId) usize ! Error {
|
||||
}
|
||||
return result
|
||||
}
|
||||
if (kind == .builtin_call and contextual_literal(state, id))
|
||||
return mix(hash, try constant_hash(state, ast.call_arg(&state.tree, id, 0)))
|
||||
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))
|
||||
}
|
||||
@@ -2199,7 +2231,8 @@ evaluate_ir_body proc(state @mut State, at, body, result_type ast.NodeId) Value
|
||||
source_type :: typecheck.type_of(&checked, result_type)
|
||||
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)
|
||||
if (state.evaluation_diagnostic == diagnostics.no_diagnostic)
|
||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .comptime_value_escape, span)
|
||||
return Value{}
|
||||
}
|
||||
if (ir.aggregate(value.type) or source_type.kind == .enumeration or source_type.kind == .void)
|
||||
|
||||
Reference in New Issue
Block a user