507 lines
18 KiB
Plaintext
507 lines
18 KiB
Plaintext
import "@std/mem"
|
|
import "@source/ast"
|
|
import "@source/diagnostics"
|
|
import "@source/strpool"
|
|
sema :: import "@source/sema"
|
|
typecheck :: import "@source/sema/check"
|
|
import "@std/testing"
|
|
import "@source/lexer"
|
|
import "@source/parser"
|
|
|
|
@hide:file
|
|
expect_specialization proc(
|
|
source []u8, expected diagnostics.Code, expected_count usize,
|
|
) void ! (mem.AllocError | diagnostics.CapacityError | strpool.InternError | lexer.SourceError | testing.Error) {
|
|
try expect_specialization_guards(source, expected, expected_count, maxval!(usize))
|
|
}
|
|
|
|
@hide:file
|
|
expect_specialization_guards proc(
|
|
source []u8, expected diagnostics.Code, expected_count, expected_guards usize,
|
|
) void ! (mem.AllocError | diagnostics.CapacityError | strpool.InternError | lexer.SourceError | testing.Error) {
|
|
strings := strpool.init(mem.c_allocator)
|
|
defer strpool.deinit(&strings)
|
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
|
defer diagnostics.deinit(&diagnostic_store)
|
|
scan_state := lexer.init(mem.c_allocator)
|
|
defer lexer.deinit(&scan_state)
|
|
try lexer.scan(&scan_state, source, &strings, &diagnostic_store)
|
|
builder := ast.init_builder(mem.c_allocator, source)
|
|
defer ast.deinit_builder(&builder)
|
|
root :: try parser.parse_source(&builder, scan_state.tokens.items, &diagnostic_store)
|
|
tree := ast.view(&builder, root)
|
|
resolution := try sema.resolve(mem.c_allocator, &tree, &strings, &diagnostic_store)
|
|
defer sema.deinit(&resolution)
|
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
|
elaborated := try elaborate(mem.c_allocator, &builder, root, &resolution, &strings, &diagnostic_store)
|
|
defer deinit(&elaborated)
|
|
tree = ast.view(&builder, root)
|
|
checked := try typecheck.check_elaborated(mem.c_allocator, &tree, &resolution, elaborated.constraints.items, &diagnostic_store)
|
|
defer typecheck.deinit(&checked)
|
|
if (expected_count == 0) {
|
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
|
for (0..ast.package_decl_count(&tree)) |i| {
|
|
declaration :: ast.package_decl(&tree, i)
|
|
if (ast.node_class(&tree, declaration) != .function) continue
|
|
try testing.expect(!ast.function_generic(&tree, declaration))
|
|
for (0..ast.function_param_count(&tree, declaration)) |j|
|
|
try testing.expect(!ast.param(&tree, ast.function_param(&tree, declaration, j)).comptime)
|
|
}
|
|
if (expected_guards != maxval!(usize))
|
|
try testing.expect_equal(expected_guards, runtime_guard_count(&tree, root))
|
|
} else {
|
|
count := usize(0)
|
|
for (diagnostic_store.list.items) |diagnostic| {
|
|
if (diagnostic.code == expected) count += 1
|
|
}
|
|
if (expected_count == maxval!(usize)) try testing.expect(count > 0)
|
|
else try testing.expect_equal(expected_count, count)
|
|
}
|
|
}
|
|
|
|
@hide:file
|
|
runtime_guard_count proc(tree @ast.Tree, id ast.NodeId) usize {
|
|
count := usize(0)
|
|
if (ast.node_class(tree, id) == .statement and ast.stmt_kind(tree, id) == .if_stmt) count += 1
|
|
for (0..ast.child_count(tree, id)) |i| count += runtime_guard_count(tree, ast.child(tree, id, i))
|
|
return count
|
|
}
|
|
|
|
selects_known_type_helper_guards_without_explicit_comptime test {
|
|
try expect_specialization_guards(
|
|
`Id :: distinct i16
|
|
`primitive proc($T type) bool { return T == i16 or T == Id }
|
|
`scalar proc($T type) bool { return primitive(T) }
|
|
`accept proc($T type, value T) T {
|
|
` if scalar(T) { return value }
|
|
` else { compile_error!("expected scalar") }
|
|
`}
|
|
`main proc() i16 { return accept(i16(42)) }
|
|
, .compile_error, 0, 0,
|
|
)
|
|
try expect_specialization(
|
|
`scalar proc($T type) bool { return T == i16 }
|
|
`accept proc($T type, value T) T {
|
|
` if scalar(T) { return value }
|
|
` else { compile_error!("expected scalar") }
|
|
`}
|
|
`main proc() bool { return accept(true) }
|
|
, .compile_error, 1,
|
|
)
|
|
}
|
|
|
|
selects_matched_type_helper_guards_before_expanding_invalid_arms test {
|
|
try expect_specialization_guards(
|
|
`scalar proc($T type) bool {
|
|
` match T == i16 {
|
|
` true: return true
|
|
` else: return false
|
|
` }
|
|
`}
|
|
`accept proc($T type, value T) T {
|
|
` if scalar(T) { return value }
|
|
` else { compile_error!("expected scalar") }
|
|
`}
|
|
`main proc() i16 { return accept(i16(42)) }
|
|
, .compile_error, 0, 0,
|
|
)
|
|
}
|
|
|
|
preserves_runtime_guards_with_constant_arguments_or_type_dependent_effects test {
|
|
sources [6][]u8 :: [
|
|
`advance proc(value @mut i16) void { value^ += 1 }
|
|
`predicate proc(value i16) bool {
|
|
` local i16 := value
|
|
` advance(&local)
|
|
` return local == 42
|
|
`}
|
|
`choose proc($T type, value T) T {
|
|
` if predicate(42) { return value } else { return value }
|
|
`}
|
|
`main proc() i16 { return choose(i16(42)) }
|
|
,
|
|
`advance proc(value @mut i16) void { value^ += 1 }
|
|
`predicate proc($T type) bool {
|
|
` local i16 := 41
|
|
` advance(&local)
|
|
` return T == i16 and local == 42
|
|
`}
|
|
`choose proc($T type, value T) T {
|
|
` if predicate(T) { return value } else { return value }
|
|
`}
|
|
`main proc() i16 { return choose(i16(42)) }
|
|
,
|
|
`enabled := true
|
|
`predicate proc($T type) bool { return enabled }
|
|
`choose proc($T type, value T) T {
|
|
` if predicate(T) { return value } else { return value }
|
|
`}
|
|
`main proc() i16 { return choose(i16(42)) }
|
|
,
|
|
`observe c_func() bool
|
|
`predicate proc($T type) bool { return observe() }
|
|
`choose proc($T type, value T) T {
|
|
` if predicate(T) { return value } else { return value }
|
|
`}
|
|
`main proc() i16 { return choose(i16(42)) }
|
|
,
|
|
`predicate proc($T type) bool { unreachable }
|
|
`choose proc($T type, value T) T {
|
|
` if predicate(T) { return value } else { return value }
|
|
`}
|
|
`main proc() i16 { return choose(i16(42)) }
|
|
,
|
|
`predicate proc($T type, flag bool) bool { return flag }
|
|
`choose proc($T type, value T) T {
|
|
` if predicate(T, true) { return value } else { return value }
|
|
`}
|
|
`main proc() i16 { return choose(i16(42)) }
|
|
,
|
|
]
|
|
for (sources) |source|
|
|
try expect_specialization_guards(source, .compile_error, 0, 1)
|
|
}
|
|
|
|
demands_inline_iterable_calls_before_expanding_their_bodies test {
|
|
try expect_specialization(
|
|
`fill proc(values []mut i16, value i16) void {
|
|
` for (0..values.len) |i| values[i] = value
|
|
`}
|
|
`produce proc($N usize, $seed i16) [N]mut i16 {
|
|
` values [N]mut i16 := undefined
|
|
` fill(values[..], seed)
|
|
` if values[0] != seed { compile_error!("unexpected initialization") }
|
|
` return values
|
|
`}
|
|
`main proc() i16 {
|
|
` total i16 := 0
|
|
` inline for (produce(3, 14)) |value| total += value
|
|
` return total
|
|
`}
|
|
, .compile_error, 0,
|
|
)
|
|
try expect_specialization(
|
|
`produce proc($N usize) [N]i16 {
|
|
` if N == 2 { compile_error!("reached failure") }
|
|
` return [1, 2]
|
|
`}
|
|
`main proc() void { inline for (produce(2)) |value| {} }
|
|
, .compile_error, 1,
|
|
)
|
|
}
|
|
|
|
explicit_demand_executes_ordinary_calls_and_preserves_borrow_aliases test {
|
|
try expect_specialization(
|
|
`update proc(left, right []mut i16) void {
|
|
` left[0] = 19
|
|
` right[1] = left[0] + 4
|
|
`}
|
|
`produce proc() [2]mut i16 {
|
|
` values [2]mut i16 := undefined
|
|
` update(values[..], values[..])
|
|
` if values[1] != 23 { compile_error!("lost mutable alias") }
|
|
` return values
|
|
`}
|
|
`main proc() i16 { values :: $(produce())
|
|
` return values[0] + values[1]
|
|
`}
|
|
, .compile_error, 0,
|
|
)
|
|
}
|
|
|
|
demanded_calls_still_infer_omitted_compiletime_type_arguments test {
|
|
try expect_specialization(
|
|
`identity proc($T type, value T) T { return value }
|
|
`main proc() i16 { return $(identity(i16(42))) }
|
|
, .cannot_infer_generic, 0,
|
|
)
|
|
}
|
|
|
|
demanded_expression_inline_matches_bind_payloads_and_static_tags test {
|
|
try expect_specialization(
|
|
`Detail :: union(enum) { count usize, empty void }
|
|
`score proc(input Detail) i16 {
|
|
` return match input {
|
|
` inline |payload, tag|: match tag { .count: i16(payload), else: 0 },
|
|
` }
|
|
`}
|
|
`main proc() i16 { return $(score(.count{42})) }
|
|
, .invalid_match_capture, 0,
|
|
)
|
|
}
|
|
|
|
keeps_borrowed_snapshots_owned_and_explicit_numeric_annotations_rigid test {
|
|
try expect_specialization(
|
|
`borrow proc(values []mut i16) []mut i16 { return values }
|
|
`produce proc() []mut i16 {
|
|
` values [2]mut i16 := [19, 23]
|
|
` return borrow(values[..])
|
|
`}
|
|
`main proc() void { values :: $(produce()) }
|
|
, .comptime_value_escape, 1,
|
|
)
|
|
try expect_specialization(
|
|
`produce proc() usize {
|
|
` value i16 := 1
|
|
` if value == 0 { compile_error!("unselected typed guard") }
|
|
` return value
|
|
`}
|
|
`main proc() void { value :: $(produce()) }
|
|
, .type_mismatch, 1,
|
|
)
|
|
}
|
|
|
|
keeps_inferred_compiletime_literals_contextual_until_a_rigid_source test {
|
|
try expect_specialization(
|
|
`produce proc() usize {
|
|
` width := 2
|
|
` width = 3
|
|
` cursor := 0
|
|
` cursor += width
|
|
` values [4]i16 :: [1, 2, 3, 42]
|
|
` if values[cursor] != 42 { compile_error!("wrong contextual index") }
|
|
` count := 0
|
|
` count = values.len
|
|
` return count
|
|
`}
|
|
`main proc() usize { return $(produce()) }
|
|
, .compile_error, 0,
|
|
)
|
|
try expect_specialization(
|
|
`produce proc() usize {
|
|
` width i16 := 2
|
|
` cursor usize := 0
|
|
` if cursor == 99 { compile_error!("unselected typed guard") }
|
|
` cursor += width
|
|
` return cursor
|
|
`}
|
|
`main proc() usize { return $(produce()) }
|
|
, .invalid_operand, maxval!(usize),
|
|
)
|
|
}
|
|
|
|
demanded_local_execution_retains_overflow_and_initialization_diagnostics test {
|
|
try expect_specialization(
|
|
`produce proc() u8 {
|
|
` value u8 := 255
|
|
` value += 1
|
|
` return value
|
|
`}
|
|
`main proc() u8 { return $(produce()) }
|
|
, .integer_overflow, 1,
|
|
)
|
|
try expect_specialization(
|
|
`initialize proc(values []mut i16) void { values[0] = 42 }
|
|
`produce proc() i16 {
|
|
` values [2]mut i16 := undefined
|
|
` initialize(values[..])
|
|
` return values[1]
|
|
`}
|
|
`main proc() i16 { return $(produce()) }
|
|
, .read_before_initialization, 1,
|
|
)
|
|
}
|
|
|
|
infers_nested_nominal_pointer_parameters_and_readonly_slice_views test {
|
|
try expect_specialization(
|
|
`Storage proc($T type) type { return struct { items []mut T, capacity usize } }
|
|
`make proc($T type, values []mut T) Storage(T) {
|
|
` return Storage(T) { items = values, capacity = values.len }
|
|
`}
|
|
`observe proc($T type, values []T) usize { return values.len }
|
|
`reserve proc($T type, storage @mut Storage(T), count usize) void {
|
|
` observe(storage.items.ptr[..count])
|
|
`}
|
|
`append proc($T type, storage @mut Storage(T), value T) void {
|
|
` reserve(storage, storage.items.len)
|
|
` storage.items[0] = value
|
|
`}
|
|
`release proc($T type, storage @mut Storage(T)) void {
|
|
` allocation :: storage.items.ptr[..storage.capacity]
|
|
` observe(allocation)
|
|
`}
|
|
`forward proc(storage @mut Storage(i16)) void {
|
|
` append(storage, 7)
|
|
` release(storage)
|
|
`}
|
|
`main proc() i16 {
|
|
` values [2]mut i16 := [1, 2]
|
|
` storage := make(i16, values[..])
|
|
` forward(&storage)
|
|
` append(&storage, 9)
|
|
` release(&storage)
|
|
` return storage.items[0]
|
|
`}
|
|
, .cannot_infer_generic, 0,
|
|
)
|
|
}
|
|
|
|
rejects_conflicting_rigid_types_and_unrelated_nominal_generators test {
|
|
try expect_specialization(
|
|
`same proc($T type, left, right T) void {}
|
|
`main proc() void { same(i16(1), u8(2)) }
|
|
, .cannot_infer_generic, 1,
|
|
)
|
|
try expect_specialization(
|
|
`Box proc($T type) type { return struct { value T } }
|
|
`Other proc($T type) type { return struct { value T } }
|
|
`read proc($T type, value @Box(T)) void {}
|
|
`main proc() void {
|
|
` value Other(i16) :: { value = 1 }
|
|
` read(&value)
|
|
`}
|
|
, .cannot_infer_generic, 1,
|
|
)
|
|
try expect_specialization(
|
|
`unknown proc($T type, value usize) void {}
|
|
`main proc() void { unknown(1) }
|
|
, .cannot_infer_generic, 1,
|
|
)
|
|
}
|
|
|
|
preserves_rigid_inference_before_contextual_literals test {
|
|
try expect_specialization(
|
|
`same proc($T type, left, right T) T { return right }
|
|
`main proc() i16 { return same(i16(1), 2) }
|
|
, .cannot_infer_generic, 0,
|
|
)
|
|
}
|
|
|
|
infers_integer_builtin_results_from_their_type_or_rigid_operand test {
|
|
try expect_specialization(
|
|
`same proc($T type, left, right T) T { return right }
|
|
`main proc() i16 {
|
|
` upper :: same(maxval!(i16), i16(1))
|
|
` lower :: same(minval!(i16), i16(2))
|
|
` left :: same(divtrunc!(i16(8), 2), i16(3))
|
|
` right :: same(divtrunc!(8, i16(2)), i16(4))
|
|
` return upper + lower + left + right
|
|
`}
|
|
, .cannot_infer_generic, 0,
|
|
)
|
|
}
|
|
|
|
infers_direct_results_before_contextual_literal_defaults test {
|
|
try expect_specialization(
|
|
`make proc($T type) T { return T(7) }
|
|
`identity proc($T type, value T) T { return value }
|
|
`sized proc($N usize, values [N]i16) [N]i16 { return values }
|
|
`consume proc(value i16) i16 { return value }
|
|
`main proc() i16 {
|
|
` value i16 := identity(1)
|
|
` value = make()
|
|
` row [2]i16 :: sized([2, 3])
|
|
` return consume(make()) + value + row[0]
|
|
`}
|
|
, .cannot_infer_generic, 0,
|
|
)
|
|
}
|
|
|
|
infers_type_and_value_arguments_from_generated_nominal_results test {
|
|
try expect_specialization(
|
|
`Fixed proc($T type, $N usize) type {
|
|
` return struct { value T = 0, count usize = N }
|
|
`}
|
|
`make proc($T type, $N usize) Fixed(T, N) { return {} }
|
|
`identity proc($T type, $N usize, value T) Fixed(T, N) {
|
|
` return { value = value }
|
|
`}
|
|
`main proc() i16 {
|
|
` first Fixed(i16, 2) :: make()
|
|
` second Fixed(i16, 3) :: identity(i16(7))
|
|
` cached Fixed(i16, 2) :: make()
|
|
` return first.value + second.value + cached.value
|
|
`}
|
|
, .cannot_infer_generic, 0,
|
|
)
|
|
}
|
|
|
|
propagates_expected_results_through_nested_aggregates_and_success_channels test {
|
|
try expect_specialization(
|
|
`Error :: enum { failed }
|
|
`Record :: struct { value i16, rows [2]mut i16 }
|
|
`make proc($T type) T { return 7 }
|
|
`optional proc() ?i16 { return make() }
|
|
`fallible proc() i16 ! Error { return make() }
|
|
`main proc() Record {
|
|
` record Record := { value = make(), rows = [make(), make()] }
|
|
` record.value = make()
|
|
` record.rows[0] = make()
|
|
` maybe ?i16 :: make()
|
|
` result i16 ! Error :: make()
|
|
` return { value = make(), rows = [make(), make()] }
|
|
`}
|
|
, .cannot_infer_generic, 0,
|
|
)
|
|
}
|
|
|
|
propagates_expected_results_through_match_arms_and_value_block_yields test {
|
|
try expect_specialization(
|
|
`make proc($T type) T { return T(7) }
|
|
`choose proc(flag bool) i16 {
|
|
` return match flag {
|
|
` true: make(),
|
|
` false: { yield make() },
|
|
` }
|
|
`}
|
|
`main proc() i16 {
|
|
` value i16 :: { yield make() }
|
|
` return value + choose(true)
|
|
`}
|
|
, .cannot_infer_generic, 0,
|
|
)
|
|
}
|
|
|
|
rejects_expected_results_that_conflict_with_rigid_runtime_arguments test {
|
|
try expect_specialization(
|
|
`identity proc($T type, value T) T { return value }
|
|
`main proc() i16 { return identity(u8(7)) }
|
|
, .cannot_infer_generic, 1,
|
|
)
|
|
try expect_specialization(
|
|
`Box proc($T type) type { return struct { value T } }
|
|
`make proc($T type, value T) Box(T) { return { value = value } }
|
|
`main proc() Box(i16) { return make(u8(7)) }
|
|
, .cannot_infer_generic, 1,
|
|
)
|
|
}
|
|
|
|
rejects_unrelated_nominal_results_and_unresolved_compile_time_arguments test {
|
|
try expect_specialization(
|
|
`Box proc($T type) type { return struct { value T = 0 } }
|
|
`Other proc($T type) type { return struct { value T = 0 } }
|
|
`make proc($T type) Box(T) { return {} }
|
|
`main proc() Other(i16) { return make() }
|
|
, .cannot_infer_generic, 1,
|
|
)
|
|
try expect_specialization(
|
|
`make proc($T type, $N usize) T { return T(7) }
|
|
`main proc() i16 { return make() }
|
|
, .cannot_infer_generic, 1,
|
|
)
|
|
try expect_specialization(
|
|
`make proc($T type) T { return T(7) }
|
|
`main proc() void { make() }
|
|
, .cannot_infer_generic, 1,
|
|
)
|
|
}
|
|
|
|
preserves_explicit_arguments_and_explicit_type_procedure_inputs test {
|
|
try expect_specialization(
|
|
`identity proc($T type, value T) T { return value }
|
|
`main proc() i16 { return identity(u8, 7) }
|
|
, .return_type_mismatch, 1,
|
|
)
|
|
try expect_specialization(
|
|
`Box proc($T type) type { return struct { value T = 0 } }
|
|
`make proc($T type) Box(T) { return {} }
|
|
`main proc() Box(i16) { return make(u8) }
|
|
, .return_type_mismatch, 1,
|
|
)
|
|
try expect_specialization(
|
|
`Box proc($T type) type { return struct { value T } }
|
|
`main proc() void { value Box() :: { value = 7 } }
|
|
, .argument_count_mismatch, 1,
|
|
)
|
|
}
|