undefined assignment
This commit is contained in:
@@ -375,6 +375,32 @@ main :: func() void {}
|
||||
testing.expect(t, strings.contains(diagnostics.items[0].message, "followed by a newline"))
|
||||
}
|
||||
|
||||
@(test)
|
||||
parser_accepts_undefined_expression :: proc(t: ^testing.T) {
|
||||
text := `main :: func() void {
|
||||
value i32 = undefined
|
||||
}
|
||||
`
|
||||
source_file := source.Source{path="test.bro", text=text}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||
defer delete(stream.items)
|
||||
module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
defer ast.destroy_module(&module)
|
||||
|
||||
found_keyword := false
|
||||
for tok in stream.items {
|
||||
found_keyword = found_keyword || tok.kind == .Keyword_Undefined
|
||||
}
|
||||
statement := module.statements[module.functions[0].body[0]]
|
||||
testing.expect_value(t, len(diagnostics.items), 0)
|
||||
testing.expect(t, found_keyword)
|
||||
testing.expect_value(t, module.exprs[statement.expr].kind, ast.Expr_Kind.Undefined)
|
||||
}
|
||||
|
||||
@(test)
|
||||
pratt_parser_preserves_left_associative_addition_shape :: proc(t: ^testing.T) {
|
||||
source_file := source.Source{path="test.bro", text="value :: 1 + 2 + 3\nmain :: func() void {}\n"}
|
||||
@@ -5013,6 +5039,187 @@ compound_assignment_preserves_operation_and_rhs :: proc(t: ^testing.T) {
|
||||
testing.expect_value(t, module.exprs[statement.expr].integer, u64(5))
|
||||
}
|
||||
|
||||
@(test)
|
||||
undefined_inferred_local_lowers_to_fill :: proc(t: ^testing.T) {
|
||||
text := `choose :: func(flag bool) i32 {
|
||||
value int = undefined
|
||||
if flag {
|
||||
value = 42
|
||||
} else {
|
||||
value = -2
|
||||
}
|
||||
return value
|
||||
}
|
||||
main :: func() i32 {
|
||||
return choose(true)
|
||||
}
|
||||
`
|
||||
source_file := source.Source{path="test.bro", text=text}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||
defer delete(stream.items)
|
||||
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
defer ast.destroy_module(&ast_module)
|
||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
ir_module := lower.lower(&hir_module)
|
||||
defer ir.destroy_module(&ir_module)
|
||||
llvm_text := llvm.emit(&ir_module, &diagnostics, &symbols)
|
||||
defer delete(llvm_text)
|
||||
|
||||
choose_symbol := symbol.intern(&symbols, "choose")
|
||||
value_symbol := symbol.intern(&symbols, "value")
|
||||
found_value_i32 := false
|
||||
fill_count := 0
|
||||
for function, function_index in hir_module.functions {
|
||||
if function.name != choose_symbol {
|
||||
continue
|
||||
}
|
||||
for local in function.locals {
|
||||
found_value_i32 = found_value_i32 || local.name == value_symbol && local.type == types.I32
|
||||
}
|
||||
for instruction in ir_module.functions[function_index].instructions {
|
||||
fill_count += 1 if instruction.op == .Fill else 0
|
||||
}
|
||||
}
|
||||
|
||||
testing.expect_value(t, len(diagnostics.items), 0)
|
||||
testing.expect(t, found_value_i32)
|
||||
testing.expect_value(t, fill_count, 1)
|
||||
testing.expect(t, strings.contains(llvm_text, "declare void @llvm.memset.p0.i64"))
|
||||
testing.expect(t, strings.contains(llvm_text, "call void @llvm.memset.p0.i64"))
|
||||
testing.expect(t, strings.contains(llvm_text, "i8 -86"))
|
||||
}
|
||||
|
||||
@(test)
|
||||
local_int_inference_widens_from_assignments :: proc(t: ^testing.T) {
|
||||
text := `wide :: func() int {
|
||||
value int = 1
|
||||
value = 1000
|
||||
return value
|
||||
}
|
||||
main :: func() void {
|
||||
_ = wide()
|
||||
}
|
||||
`
|
||||
source_file := source.Source{path="test.bro", text=text}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||
defer delete(stream.items)
|
||||
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
defer ast.destroy_module(&ast_module)
|
||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
|
||||
wide_symbol := symbol.intern(&symbols, "wide")
|
||||
value_symbol := symbol.intern(&symbols, "value")
|
||||
found_value_i16 := false
|
||||
found_result_i16 := false
|
||||
for function in hir_module.functions {
|
||||
if function.name != wide_symbol {
|
||||
continue
|
||||
}
|
||||
found_result_i16 = function.result == types.I16
|
||||
for local in function.locals {
|
||||
found_value_i16 = found_value_i16 || local.name == value_symbol && local.type == types.I16
|
||||
}
|
||||
}
|
||||
|
||||
testing.expect_value(t, len(diagnostics.items), 0)
|
||||
testing.expect(t, found_result_i16)
|
||||
testing.expect(t, found_value_i16)
|
||||
}
|
||||
|
||||
@(test)
|
||||
undefined_accepts_concrete_runtime_annotations :: proc(t: ^testing.T) {
|
||||
text := `Point :: struct {
|
||||
x i32
|
||||
y i32
|
||||
}
|
||||
main :: func() void {
|
||||
point Point = undefined
|
||||
pointer @i32 = undefined
|
||||
maybe ?i32 = undefined
|
||||
}
|
||||
`
|
||||
source_file := source.Source{path="test.bro", text=text}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||
defer delete(stream.items)
|
||||
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
defer ast.destroy_module(&ast_module)
|
||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
ir_module := lower.lower(&hir_module)
|
||||
defer ir.destroy_module(&ir_module)
|
||||
|
||||
main_symbol := symbol.intern(&symbols, "main")
|
||||
fill_count := 0
|
||||
for function, function_index in hir_module.functions {
|
||||
if function.name != main_symbol {
|
||||
continue
|
||||
}
|
||||
for instruction in ir_module.functions[function_index].instructions {
|
||||
fill_count += 1 if instruction.op == .Fill else 0
|
||||
}
|
||||
}
|
||||
|
||||
testing.expect_value(t, len(diagnostics.items), 0)
|
||||
testing.expect_value(t, fill_count, 3)
|
||||
}
|
||||
|
||||
@(test)
|
||||
undefined_rejects_non_declaration_uses_and_unresolved_inference :: proc(t: ^testing.T) {
|
||||
text := `global :: undefined
|
||||
main :: func() void {
|
||||
immutable :: undefined
|
||||
typed_immutable int :: undefined
|
||||
unresolved int = undefined
|
||||
existing i32 = 1
|
||||
existing = undefined
|
||||
mismatch int = undefined
|
||||
mismatch = 1
|
||||
mismatch = 1.0
|
||||
}
|
||||
`
|
||||
source_file := source.Source{path="test.bro", text=text}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||
defer delete(stream.items)
|
||||
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
defer ast.destroy_module(&ast_module)
|
||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
|
||||
immutable_count := 0
|
||||
found_unresolved := false
|
||||
found_assignment := false
|
||||
found_incompatible := false
|
||||
for diagnostic in diagnostics.items {
|
||||
immutable_count += 1 if strings.contains(diagnostic.message, "'undefined' requires a mutable local declaration") else 0
|
||||
found_unresolved = found_unresolved || strings.contains(diagnostic.message, "could not infer a concrete type for local 'unresolved'")
|
||||
found_assignment = found_assignment || strings.contains(diagnostic.message, "'undefined' is only valid as a mutable local declaration initializer")
|
||||
found_incompatible = found_incompatible || strings.contains(diagnostic.message, "cannot implicitly convert f64 to i8")
|
||||
}
|
||||
|
||||
testing.expect(t, immutable_count >= 2)
|
||||
testing.expect(t, found_unresolved)
|
||||
testing.expect(t, found_assignment)
|
||||
testing.expect(t, found_incompatible)
|
||||
}
|
||||
|
||||
@(test)
|
||||
compound_assignment_evaluates_lvalue_once :: proc(t: ^testing.T) {
|
||||
// A compound assignment to an indexed lvalue must compute the element address
|
||||
|
||||
Reference in New Issue
Block a user