feat: lower typed programs and interpret IR
This commit is contained in:
@@ -27,7 +27,7 @@ Result :: struct { storage Storage }
|
||||
no_index :: maxval!(usize)
|
||||
|
||||
@hide:file
|
||||
Role :: enum { none, expression, declaration, parameter, function, annotation, invalid, body }
|
||||
Role :: enum { none, expression, declaration, parameter, function, annotation, invalid, body, return_stmt }
|
||||
|
||||
@hide:file
|
||||
Cell :: struct {
|
||||
@@ -40,6 +40,7 @@ Cell :: struct {
|
||||
demand TypeKind = .none
|
||||
fixed bool = false
|
||||
first_edge usize = no_index
|
||||
return_annotation ast.NodeId = ast.NodeId(maxval!(u32))
|
||||
}
|
||||
|
||||
@hide:file
|
||||
@@ -194,6 +195,11 @@ check proc(
|
||||
try demand(&state, declaration.initializer, types[usize(annotation)], .type_mismatch)
|
||||
}
|
||||
}
|
||||
if (cells[i].role == .return_stmt) {
|
||||
if (ast.return_stmt(tree, id).value) |value| {
|
||||
try demand(&state, value, types[usize(cells[i].return_annotation)], .return_type_mismatch)
|
||||
}
|
||||
}
|
||||
if (cells[i].role != .expression or ast.expr_kind(tree, id) != .call) continue
|
||||
signature :: group_type(&state, ast.call_expr(tree, id).callee)
|
||||
if (signature.function == ast.NodeId(maxval!(u32))) continue
|
||||
@@ -427,6 +433,15 @@ collect_function proc(state @mut State, id ast.NodeId) void ! (mem.AllocError |
|
||||
match ast.stmt_kind(tree, statement) {
|
||||
.declaration: try collect_declaration(state, statement)
|
||||
.expression: try collect_expression(state, ast.statement_expr(tree, statement))
|
||||
.return_stmt: {
|
||||
state.cells[usize(statement)].role = .return_stmt
|
||||
state.cells[usize(statement)].type = Type{ kind = .void }
|
||||
state.cells[usize(statement)].return_annotation = function.return_type
|
||||
if (ast.return_stmt(tree, statement).value) |value| {
|
||||
try collect_expression(state, value)
|
||||
try depend(state, value, statement)
|
||||
}
|
||||
}
|
||||
.invalid: collect_invalid(state, statement)
|
||||
}
|
||||
source :: if (ast.stmt_kind(tree, statement) == .expression) ast.statement_expr(tree, statement) else statement
|
||||
@@ -557,6 +572,9 @@ validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
|
||||
function :: ast.function_decl(tree, id)
|
||||
result :: state.types[usize(function.return_type)]
|
||||
if (result.kind == .void or result.kind == .invalid or state.types[usize(id)].kind == .invalid) return
|
||||
for (0..ast.block_statement_count(tree, function.body)) |i| {
|
||||
if (ast.stmt_kind(tree, ast.block_statement(tree, function.body, i)) == .return_stmt) return
|
||||
}
|
||||
if (syntactically_valid_body(tree, function.body)) {
|
||||
diagnostic :: try diagnostics.add(state.diagnostics, .missing_return, ast.node_span(tree, function.body))
|
||||
if (state.types[usize(function.body)].kind != .invalid) {
|
||||
@@ -564,6 +582,22 @@ validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
|
||||
}
|
||||
}
|
||||
}
|
||||
.return_stmt: {
|
||||
statement :: ast.return_stmt(tree, id)
|
||||
expected :: state.types[usize(state.cells[usize(id)].return_annotation)]
|
||||
if (expected.kind == .invalid) return
|
||||
if (statement.value) |value| {
|
||||
actual :: state.types[usize(value)]
|
||||
if (actual.kind == .invalid) {
|
||||
state.types[usize(id)] = actual
|
||||
} else if (actual.kind != expected.kind) {
|
||||
try fail(state, value, .return_type_mismatch, ast.node_span(tree, value))
|
||||
state.types[usize(id)] = state.types[usize(value)]
|
||||
}
|
||||
} else if (expected.kind != .void) {
|
||||
try fail(state, id, .return_type_mismatch, statement.span)
|
||||
}
|
||||
}
|
||||
.expression: {
|
||||
match ast.expr_kind(tree, id) {
|
||||
.unary: {
|
||||
@@ -644,6 +678,11 @@ syntactically_valid_body proc(tree @ast.Tree, id ast.NodeId) bool {
|
||||
.expression: {
|
||||
if (!syntactically_valid_expression(tree, ast.statement_expr(tree, statement))) return false
|
||||
}
|
||||
.return_stmt: {
|
||||
if (ast.return_stmt(tree, statement).value) |value| {
|
||||
if (!syntactically_valid_expression(tree, value)) return false
|
||||
}
|
||||
}
|
||||
.declaration: {
|
||||
if (!syntactically_valid_expression(tree, ast.statement_decl(tree, statement).initializer)) return false
|
||||
}
|
||||
|
||||
@@ -606,3 +606,128 @@ preserves_existing_syntax_name_and_duplicate_ids_while_checking_siblings test {
|
||||
try testing.expect_equal(3, code_count(&diagnostic_store, .invalid_operand))
|
||||
try testing.expect_equal(previous_count + 3, diagnostics.count(&diagnostic_store))
|
||||
}
|
||||
|
||||
returns_supply_numeric_context_and_resolve_forward_local_and_parameter_names test {
|
||||
strpool.strings = strpool.init(mem.c_allocator)
|
||||
defer strpool.deinit(&strpool.strings)
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
source ::
|
||||
`fraction proc() float {
|
||||
` return -later / 2
|
||||
` later := seed
|
||||
`}
|
||||
`identity proc(value int) int { return value }
|
||||
`wrapper proc() void { return noop() }
|
||||
`noop proc() void { return }
|
||||
`text proc() string { return "honey" }
|
||||
`seed :: 3
|
||||
scan_state := lexer.init(mem.c_allocator)
|
||||
defer lexer.deinit(&scan_state)
|
||||
try lexer.scan(&scan_state, source, &diagnostic_store)
|
||||
tree := try parser.parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||
defer ast.deinit(&tree)
|
||||
resolution := try sema.resolve(mem.c_allocator, &tree, &diagnostic_store)
|
||||
defer sema.deinit(&resolution)
|
||||
result := try check(mem.c_allocator, &tree, &resolution, &diagnostic_store)
|
||||
defer deinit(&result)
|
||||
|
||||
expected [5]TypeKind :: [.float, .int, .void, .void, .string]
|
||||
for (expected) |kind, index| {
|
||||
function :: ast.package_decl(&tree, index)
|
||||
view :: ast.function_decl(&tree, function)
|
||||
try expect_kind(&result, function, .function)
|
||||
try expect_kind(&result, view.return_type, kind)
|
||||
statement :: ast.block_statement(&tree, view.body, 0)
|
||||
try expect_kind(&result, statement, .void)
|
||||
if (ast.return_stmt(&tree, statement).value) |value| try expect_kind(&result, value, kind)
|
||||
}
|
||||
fraction :: ast.function_decl(&tree, ast.package_decl(&tree, 0))
|
||||
local :: ast.block_statement(&tree, fraction.body, 1)
|
||||
try expect_kind(&result, local, .float)
|
||||
try expect_kind(&result, ast.package_decl(&tree, 5), .float)
|
||||
if (ast.return_stmt(&tree, ast.block_statement(&tree, fraction.body, 0)).value) |value| {
|
||||
division :: ast.binary_expr(&tree, value)
|
||||
negate :: ast.unary_expr(&tree, division.left)
|
||||
try expect_kind(&result, division.left, .float)
|
||||
try expect_kind(&result, division.right, .float)
|
||||
try testing.expect_equal(local, sema.binding(&resolution, negate.operand).data.declaration)
|
||||
} else {
|
||||
return .expectation_failed
|
||||
}
|
||||
identity :: ast.package_decl(&tree, 1)
|
||||
identity_body :: ast.function_decl(&tree, identity).body
|
||||
if (ast.return_stmt(&tree, ast.block_statement(&tree, identity_body, 0)).value) |value| {
|
||||
try testing.expect_equal(ast.function_param(&tree, identity, 0), sema.binding(&resolution, value).data.declaration)
|
||||
} else {
|
||||
return .expectation_failed
|
||||
}
|
||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||
}
|
||||
|
||||
return_errors_preserve_identity_check_unreachable_code_and_suppress_missing_return_cascades test {
|
||||
strpool.strings = strpool.init(mem.c_allocator)
|
||||
defer strpool.deinit(&strpool.strings)
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
source ::
|
||||
`bare proc() int { return }
|
||||
`wrong proc() int { return "text" }
|
||||
`void_value proc() void { return 1 }
|
||||
`unknown proc() int { return absent }
|
||||
`syntax proc() int { return (1 }
|
||||
`unreachable proc() int {
|
||||
` return 7
|
||||
` -"bad"
|
||||
`}
|
||||
`fallthrough proc() int {}
|
||||
`recovered proc() int {
|
||||
` return 1 stray
|
||||
` valid :: 2
|
||||
`}
|
||||
scan_state := lexer.init(mem.c_allocator)
|
||||
defer lexer.deinit(&scan_state)
|
||||
try lexer.scan(&scan_state, source, &diagnostic_store)
|
||||
tree := try parser.parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||
defer ast.deinit(&tree)
|
||||
resolution := try sema.resolve(mem.c_allocator, &tree, &diagnostic_store)
|
||||
defer sema.deinit(&resolution)
|
||||
result := try check(mem.c_allocator, &tree, &resolution, &diagnostic_store)
|
||||
defer deinit(&result)
|
||||
|
||||
for (usize(0)..usize(3)) |index| {
|
||||
function :: ast.package_decl(&tree, index)
|
||||
view :: ast.function_decl(&tree, function)
|
||||
try expect_kind(&result, function, .function)
|
||||
statement :: ast.block_statement(&tree, view.body, 0)
|
||||
returned :: ast.return_stmt(&tree, statement)
|
||||
span :: if (returned.value) |value| ast.node_span(&tree, value) else returned.span
|
||||
diagnostic :: try expect_error(&result, &diagnostic_store, statement, .return_type_mismatch, span)
|
||||
if (returned.value) |value| try testing.expect_equal(diagnostic, type_of(&result, value).diagnostic)
|
||||
}
|
||||
for (usize(3)..usize(5)) |index| {
|
||||
view :: ast.function_decl(&tree, ast.package_decl(&tree, index))
|
||||
statement :: ast.block_statement(&tree, view.body, 0)
|
||||
if (ast.return_stmt(&tree, statement).value) |value| {
|
||||
original :: type_of(&result, value).diagnostic
|
||||
try testing.expect(original != diagnostics.no_diagnostic)
|
||||
try testing.expect_equal(original, type_of(&result, statement).diagnostic)
|
||||
try testing.expect_equal(original, type_of(&result, view.body).diagnostic)
|
||||
} else {
|
||||
return .expectation_failed
|
||||
}
|
||||
}
|
||||
unreachable_body :: ast.function_decl(&tree, ast.package_decl(&tree, 5)).body
|
||||
try expect_kind(&result, ast.block_statement(&tree, unreachable_body, 0), .void)
|
||||
bad :: ast.statement_expr(&tree, ast.block_statement(&tree, unreachable_body, 1))
|
||||
_ = try expect_error(&result, &diagnostic_store, bad, .invalid_operand, ast.node_span(&tree, bad))
|
||||
missing_body :: ast.function_decl(&tree, ast.package_decl(&tree, 6)).body
|
||||
_ = try expect_error(&result, &diagnostic_store, missing_body, .missing_return, ast.node_span(&tree, missing_body))
|
||||
recovered_body :: ast.function_decl(&tree, ast.package_decl(&tree, 7)).body
|
||||
try expect_kind(&result, ast.block_statement(&tree, recovered_body, 1), .int)
|
||||
try testing.expect_equal(3, code_count(&diagnostic_store, .return_type_mismatch))
|
||||
try testing.expect_equal(1, code_count(&diagnostic_store, .missing_return))
|
||||
try testing.expect_equal(1, code_count(&diagnostic_store, .unknown_name))
|
||||
try testing.expect_equal(2, code_count(&diagnostic_store, .unexpected_token))
|
||||
try testing.expect_equal(8, diagnostics.count(&diagnostic_store))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user