feat: execute existing hash maps through Honey
This commit is contained in:
+9
-2
@@ -14,7 +14,7 @@
|
||||
#! * arm ...... -> block | stmt (single statements become canonical blocks)
|
||||
#! * match_arm -> (expr {',' expr} ['|' identifier '|'] | 'else') ':' (block | stmt | expr); arms are comma/newline separated.
|
||||
#! enum members and tagged-union variants accept keyword names; member names do not enter lexical scopes.
|
||||
#! * stmt ..... -> decl | expr | block | ('defer' | 'errdefer' ['|' identifier '|']) arm | expr assignment_op expr | 'return' [expr] | 'yield' expr | 'break' | 'continue' | 'if' expr ['|' identifier '|'] arm ['else' arm] | 'while' expr ['|' identifier '|'] arm | ['inline'] 'for' expr '|' ['@'] identifier [',' identifier] '|' arm
|
||||
#! * stmt ..... -> decl | expr | block | ('defer' | 'errdefer' ['|' identifier '|']) arm | expr assignment_op expr | 'return' [expr] | 'yield' expr | 'break' | 'continue' | 'unreachable' | 'if' expr ['|' identifier '|'] arm ['else' arm] | 'while' expr ['|' identifier '|'] arm | ['inline'] 'for' expr '|' ['@'] identifier [',' identifier] '|' arm
|
||||
#! assignment_op is '=' or a contiguous compound: '+=', '-=', '*=', '/=', '&=', '|=', 'xor=', '<<=', '>>=', '<<|='.
|
||||
#! braceless guards/iterables require parentheses unless they are calls, explicit comptime expressions or optional captures; else binds to the nearest if. bitwise-OR headers require parentheses before captures.
|
||||
#! precedence (tightest first): calls/indices/selections/dereference/unwrap, unary -, !, ~, &, try higher than multiplication/division, addition/subtraction, shifts << / >> / <<|, bitwise &/xor/|, relational comparisons, equality, and, or, ranges/catch. type composition | binds tighter than fallible !; pointer/optional/array prefixes bind to type atoms. compound range endpoints require their own parentheses.
|
||||
@@ -104,6 +104,7 @@ StmtKind :: enum {
|
||||
yield_stmt
|
||||
break_stmt
|
||||
continue_stmt
|
||||
unreachable_stmt
|
||||
if_stmt
|
||||
while_stmt
|
||||
for_stmt
|
||||
@@ -201,6 +202,7 @@ NodeKind :: enum {
|
||||
literal_undefined
|
||||
expr_block
|
||||
stmt_yield
|
||||
stmt_unreachable
|
||||
}
|
||||
|
||||
@hide:file
|
||||
@@ -999,6 +1001,10 @@ add_continue_stmt proc(builder @mut Builder, span Span) NodeId ! mem.AllocError
|
||||
return try add_node(builder, Node{ kind = .stmt_continue }, span)
|
||||
}
|
||||
|
||||
add_unreachable_stmt proc(builder @mut Builder, span Span) NodeId ! mem.AllocError {
|
||||
return try add_node(builder, Node{ kind = .stmt_unreachable }, span)
|
||||
}
|
||||
|
||||
add_if_stmt proc(
|
||||
builder @mut Builder,
|
||||
span Span,
|
||||
@@ -1352,6 +1358,7 @@ stmt_kind proc(tree @Tree, id NodeId) StmtKind {
|
||||
.stmt_yield: .yield_stmt
|
||||
.stmt_break: .break_stmt
|
||||
.stmt_continue: .continue_stmt
|
||||
.stmt_unreachable: .unreachable_stmt
|
||||
.stmt_if: .if_stmt
|
||||
.stmt_assignment: .assignment_stmt
|
||||
.stmt_while: .while_stmt
|
||||
@@ -2060,7 +2067,7 @@ node_layout proc(node Node) NodeLayout {
|
||||
if (node.kind == .stmt_defer or node.kind == .stmt_errdefer)
|
||||
layout.direct_children = 3
|
||||
}
|
||||
.stmt_break, .stmt_continue, .invalid_stmt: layout.class = .statement
|
||||
.stmt_break, .stmt_continue, .stmt_unreachable, .invalid_stmt: layout.class = .statement
|
||||
.stmt_assignment: {
|
||||
layout.class = .statement
|
||||
layout.direct_children = 3
|
||||
|
||||
@@ -65,6 +65,7 @@ render_block proc(renderer @mut Renderer, tree @ast.Tree, block ast.NodeId, dept
|
||||
}
|
||||
.break_stmt: try append_text(renderer, "break\n")
|
||||
.continue_stmt: try append_text(renderer, "continue\n")
|
||||
.unreachable_stmt: try append_text(renderer, "unreachable\n")
|
||||
.block_stmt: {
|
||||
try append_text(renderer, "block\n")
|
||||
try render_block(renderer, tree, ast.statement_block(tree, statement), depth + 1)
|
||||
|
||||
@@ -127,11 +127,15 @@ Code :: enum {
|
||||
invalid_metadata_abi
|
||||
invalid_undefined
|
||||
invalid_shift_count
|
||||
unreachable_executed
|
||||
compile_error
|
||||
invalid_compile_error
|
||||
}
|
||||
|
||||
Diagnostic :: struct {
|
||||
code Code
|
||||
span Span
|
||||
custom_message ?[]u8 = null
|
||||
}
|
||||
|
||||
#! owns diagnostic storage and retains its allocator; release with deinit.
|
||||
@@ -146,6 +150,9 @@ init proc(allocator mem.Allocator) Store {
|
||||
|
||||
#! releases diagnostic storage with the retained allocator.
|
||||
deinit proc(store @mut Store) void {
|
||||
for (store.list.items) |diagnostic| {
|
||||
if (diagnostic.custom_message) |text| mem.free(store.list.allocator, text)
|
||||
}
|
||||
arraylist.deinit(&store.list)
|
||||
}
|
||||
|
||||
@@ -157,6 +164,22 @@ add proc(store @mut Store, code Code, span Span) DiagnosticId ! (mem.AllocError
|
||||
return id
|
||||
}
|
||||
|
||||
#! copies `text`; the store owns the message until deinit.
|
||||
add_message proc(store @mut Store, code Code, span Span, text []u8) DiagnosticId ! (mem.AllocError | CapacityError) {
|
||||
owned :: try mem.alloc(u8, store.list.allocator, text.len)
|
||||
errdefer mem.free(store.list.allocator, owned)
|
||||
memcopy!(owned, text)
|
||||
id :: try add(store, code, span)
|
||||
store.list.items[usize(id)].custom_message = owned
|
||||
return id
|
||||
}
|
||||
|
||||
#! borrows either the store-owned custom message or process-lifetime code text.
|
||||
message proc(diagnostic Diagnostic) []u8 {
|
||||
if (diagnostic.custom_message) |text| return text
|
||||
return enummap.get(Code, []u8, &messages, diagnostic.code)?
|
||||
}
|
||||
|
||||
get proc(store @Store, id DiagnosticId) ?Diagnostic {
|
||||
if (usize(id) >= store.list.items.len) return null
|
||||
return store.list.items[usize(id)]
|
||||
@@ -185,6 +208,7 @@ messages :: $enummap.init(Code, []u8, {
|
||||
argument_count_mismatch = "argument count does not match the function signature",
|
||||
cannot_infer_type = "cannot infer a type without a type anchor",
|
||||
missing_return = "nonvoid function can finish without returning a value",
|
||||
unreachable_executed = "reached unreachable statement",
|
||||
return_type_mismatch = "return does not match the function result type",
|
||||
yield_outside_block = "yield requires an enclosing value block",
|
||||
missing_yield = "nonvoid value block can finish without yielding a value",
|
||||
@@ -275,5 +299,7 @@ messages :: $enummap.init(Code, []u8, {
|
||||
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",
|
||||
compile_error = "explicit compile-time error",
|
||||
invalid_compile_error = "compile_error! requires one compile-time string argument",
|
||||
})
|
||||
|
||||
|
||||
@@ -191,7 +191,7 @@ execute proc(
|
||||
if instruction_index == block.instructions.items.len {
|
||||
terminator :: block.terminator
|
||||
if (terminator.kind == .trap) return Outcome{ trap = terminator.diagnostic }
|
||||
if (terminator.kind == .unreachable) unreachable
|
||||
if (terminator.kind == .unreachable) return try runtime_trap(diagnostic_store, .unreachable_executed, terminator.span)
|
||||
if (terminator.kind == .jump or terminator.kind == .branch) {
|
||||
target := terminator.target
|
||||
if (terminator.kind == .branch) {
|
||||
|
||||
@@ -29,6 +29,7 @@ keywords strmap.StaticStringMap(TokenKind) :: strmap.init([
|
||||
{ "yield", .yield },
|
||||
{ "break", .break },
|
||||
{ "continue", .continue },
|
||||
{ "unreachable", .unreachable },
|
||||
{ "try", .try },
|
||||
{ "catch", .catch },
|
||||
{ "defer", .defer },
|
||||
|
||||
@@ -25,6 +25,7 @@ TokenKind :: enum {
|
||||
proc, return
|
||||
yield
|
||||
break, continue
|
||||
unreachable
|
||||
try, catch
|
||||
defer, errdefer
|
||||
true, false
|
||||
|
||||
@@ -61,7 +61,7 @@ emit proc(
|
||||
}
|
||||
for (diagnostic_store.list.items) |diagnostic, index| {
|
||||
try io.print("@d{} = private constant ", output, {index})
|
||||
try bytes_constant(output, enummap.get(diagnostics.Code, []u8, &diagnostics.messages, diagnostic.code)?, false)
|
||||
try bytes_constant(output, diagnostics.message(diagnostic), false)
|
||||
try io.write_all(output, "\n")
|
||||
}
|
||||
memory_codes [_]diagnostics.Code :: [
|
||||
@@ -69,6 +69,7 @@ emit proc(
|
||||
.overlapping_memory_copy, .unsupported_foreign_call, .invalid_pointer_access,
|
||||
.division_by_zero,
|
||||
.invalid_shift_count,
|
||||
.unreachable_executed,
|
||||
]
|
||||
for (memory_codes) |code| {
|
||||
try io.print("@memory_{s} = private constant ", output, {enummap.get(&memory_symbols, code)?})
|
||||
@@ -321,7 +322,7 @@ emit_reporting proc(
|
||||
try io.write_all(output, "define internal void @report(i64 %trap) {\nentry:\n")
|
||||
for (diagnostic_store.list.items) |diagnostic, index| {
|
||||
position :: location(line_starts, diagnostic.span)
|
||||
message :: enummap.get(diagnostics.Code, []u8, &diagnostics.messages, diagnostic.code)?
|
||||
message :: diagnostics.message(diagnostic)
|
||||
try io.print(" %t{} = icmp eq i64 %trap, {}\n", output, {index, index})
|
||||
try io.print(" call void @diagnose(i64 {}, i64 {}, i64 {}, ptr @d{}, i64 {}, i1 %t{})\n", output, {
|
||||
usize(diagnostic.span.source), position.line, position.column, index, message.len, index,
|
||||
@@ -511,6 +512,7 @@ memory_symbols :: $enummap.init_default(diagnostics.Code, []u8, "", {
|
||||
invalid_pointer_access = "invalid_pointer_access",
|
||||
division_by_zero = "division_by_zero",
|
||||
invalid_shift_count = "invalid_shift_count",
|
||||
unreachable_executed = "unreachable_executed",
|
||||
})
|
||||
|
||||
@hide:file
|
||||
@@ -650,7 +652,7 @@ emit_function proc(state @mut State, function @ir.Function, id usize) void ! io.
|
||||
}
|
||||
}
|
||||
.trap: try io.print(" call void @trap(i64 {}, i64 0, i64 0, i64 0, ptr null, i64 0)\n unreachable\n", output, {u32(terminator.diagnostic)})
|
||||
.unreachable: try io.write_all(output, " unreachable\n")
|
||||
.unreachable: try trap(state, .unreachable_executed, terminator.span)
|
||||
.jump: try io.print(" br label %b{}\n", output, {u32(terminator.target)})
|
||||
.branch: {
|
||||
condition :: try load(state, terminator.value)
|
||||
|
||||
+20
-1
@@ -23,6 +23,7 @@ LoopTargets :: struct {
|
||||
continue_target ir.BlockId
|
||||
exit ir.BlockId
|
||||
cleanup_depth usize
|
||||
break_reached bool = false
|
||||
}
|
||||
|
||||
@hide:file
|
||||
@@ -770,7 +771,14 @@ lower_while proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagn
|
||||
}
|
||||
}
|
||||
state.block = exit
|
||||
state.terminated = false
|
||||
state.terminated = (
|
||||
loop.capture == null
|
||||
and ast.expr_kind(state.tree, loop.condition) == .literal_bool
|
||||
and mem.eql(ast.literal_text(state.tree, loop.condition), "true")
|
||||
and !state.loop?.break_reached
|
||||
)
|
||||
if (state.terminated)
|
||||
state.module.functions.items[state.function].blocks.items[usize(exit)].terminator = ir.Terminator{ kind = .unreachable, span = loop.span }
|
||||
}
|
||||
|
||||
@hide:file
|
||||
@@ -920,6 +928,12 @@ lower_statement proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | d
|
||||
.if_stmt: try lower_if(state, id)
|
||||
.while_stmt: try lower_while(state, id)
|
||||
.for_stmt: try lower_for(state, id)
|
||||
.unreachable_stmt: {
|
||||
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
||||
kind = .unreachable, span = ast.node_span(tree, id),
|
||||
}
|
||||
state.terminated = true
|
||||
}
|
||||
.break_stmt, .continue_stmt: {
|
||||
if (trap_type(state, id)) return
|
||||
if (state.loop) |targets| {
|
||||
@@ -928,6 +942,11 @@ lower_statement proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | d
|
||||
if (ast.stmt_kind(tree, id) == .continue_stmt) target = targets.continue_target
|
||||
try lower_cleanups(state, targets.cleanup_depth, ir.no_value)
|
||||
if (state.terminated) return
|
||||
if (ast.stmt_kind(tree, id) == .break_stmt) {
|
||||
reached := targets
|
||||
reached.break_reached = true
|
||||
state.loop = reached
|
||||
}
|
||||
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
||||
kind = .jump, target = target, span = ast.node_span(tree, id),
|
||||
}
|
||||
|
||||
@@ -992,7 +992,7 @@ zero_iteration_body_errors_are_checked_but_not_executed test {
|
||||
}
|
||||
}
|
||||
|
||||
loop_returns_do_not_guarantee_return_and_assignment_arithmetic_still_checks_overflow test {
|
||||
terminal_true_loops_and_assignment_arithmetic_preserve_runtime_behavior test {
|
||||
sources [5][]u8 :: [
|
||||
"main proc() int {\n while true { return 42 }\n}\n",
|
||||
"main proc() int {\n while false { return 42 }\n}\n",
|
||||
@@ -1005,7 +1005,7 @@ loop_returns_do_not_guarantee_return_and_assignment_arithmetic_still_checks_over
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
module := try compile_owned(source, &diagnostic_store)
|
||||
defer ir.deinit(&module)
|
||||
if (index != 2) {
|
||||
if (index != 0 and index != 2) {
|
||||
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
||||
diagnostic :: diagnostics.get(&diagnostic_store, diagnostics.DiagnosticId(0))?
|
||||
try testing.expect_equal(diagnostics.Code.missing_return, diagnostic.code)
|
||||
|
||||
+1
-1
@@ -288,7 +288,7 @@ report_diagnostics proc(
|
||||
line + 1,
|
||||
offset - starts[line] + 1,
|
||||
label,
|
||||
enummap.get(diagnostics.Code, []u8, &diagnostics.messages, diagnostic.code)?,
|
||||
diagnostics.message(diagnostic),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+10
-7
@@ -323,7 +323,7 @@ parse_statement proc(state @mut State, allow_else bool) ast.NodeId ! ParseError
|
||||
.for, .inline: return try parse_for(state, allow_else)
|
||||
.return: return try parse_return(state, allow_else)
|
||||
.yield: return try parse_yield(state, allow_else)
|
||||
.break, .continue: return try parse_loop_control(state, allow_else)
|
||||
.break, .continue, .unreachable: return try parse_loop_control(state, allow_else)
|
||||
.defer, .errdefer: return try parse_cleanup(state, allow_else)
|
||||
.open_curly: return try ast.add_block_stmt(state.builder, try parse_block(state))
|
||||
.ident, .int, .float, .string, .true, .false, .minus, .bang, .tilde, .open_paren, .open_bracket, .struct, .c_struct, .opaque, .c_func,
|
||||
@@ -426,6 +426,8 @@ parse_loop_control proc(state @mut State, allow_else bool) ast.NodeId ! ParseErr
|
||||
|
||||
if (keyword.kind == .break)
|
||||
return try ast.add_break_stmt(state.builder, keyword.span)
|
||||
if (keyword.kind == .unreachable)
|
||||
return try ast.add_unreachable_stmt(state.builder, keyword.span)
|
||||
|
||||
return try ast.add_continue_stmt(state.builder, keyword.span)
|
||||
}
|
||||
@@ -688,6 +690,7 @@ parse_guard proc(state @mut State) ast.NodeId ! ParseError {
|
||||
and !next_is(state, .if) and !next_is(state, .while)
|
||||
and !next_is(state, .for) and !next_is(state, .inline)
|
||||
and !next_is(state, .break) and !next_is(state, .continue)
|
||||
and !next_is(state, .unreachable)
|
||||
) {
|
||||
if (next_is(state, .ident) and is_assignment_operator(state.tokens[state.next_token + 1].kind)) break
|
||||
state.next_token += 1
|
||||
@@ -958,7 +961,7 @@ member_names :: $enummap.init_default(lexer.TokenKind, bool, false, {
|
||||
struct = true, c_struct = true, opaque = true, enum = true, union = true,
|
||||
distinct = true, alias = true, match = true, import = true, test = true,
|
||||
c_func = true, null = true, undefined = true, try = true, catch = true,
|
||||
defer = true, errdefer = true,
|
||||
defer = true, errdefer = true, unreachable = true,
|
||||
})
|
||||
|
||||
@hide:file
|
||||
@@ -1192,7 +1195,7 @@ parse_match_arm proc(state @mut State) ast.NodeId ! ParseError {
|
||||
state.match_arm_body = true
|
||||
defer state.match_arm_body = previous
|
||||
statement_body :: match current(state).kind {
|
||||
.open_curly, .return, .yield, .break, .continue, .if, .while, .for, .inline, .defer, .errdefer: true
|
||||
.open_curly, .return, .yield, .break, .continue, .unreachable, .if, .while, .for, .inline, .defer, .errdefer: true
|
||||
else: false
|
||||
}
|
||||
body ast.NodeId := undefined
|
||||
@@ -1773,7 +1776,7 @@ brace_starts_block proc(state @State) bool {
|
||||
if (kind == .eof) return false
|
||||
if (depth > 0
|
||||
and (state.tokens[first].kind == .if or state.tokens[first].kind == .open_curly)
|
||||
and (kind == .yield or kind == .return or kind == .defer or kind == .errdefer)
|
||||
and (kind == .yield or kind == .return or kind == .unreachable or kind == .defer or kind == .errdefer)
|
||||
and state.tokens[index - 1].kind != .dot
|
||||
and !(index + 1 < state.tokens.len and state.tokens[index + 1].kind == .equal)) return true
|
||||
if (depth == 1) {
|
||||
@@ -1782,7 +1785,7 @@ brace_starts_block proc(state @State) bool {
|
||||
previous :: if (index > first) state.tokens[index - 1].kind else lexer.TokenKind.open_curly
|
||||
if (previous != .dot and !(index + 1 < state.tokens.len and state.tokens[index + 1].kind == .equal)) {
|
||||
match kind {
|
||||
.yield, .return, .break, .continue, .while, .for, .inline, .defer, .errdefer: return true
|
||||
.yield, .return, .break, .continue, .unreachable, .while, .for, .inline, .defer, .errdefer: return true
|
||||
else: {}
|
||||
}
|
||||
}
|
||||
@@ -1914,7 +1917,7 @@ parse_primary proc(state @mut State) ast.NodeId ! ParseError {
|
||||
@hide:file
|
||||
is_expression_boundary proc(kind lexer.TokenKind) bool {
|
||||
return match kind {
|
||||
.close_paren, .newline, .close_curly, .open_curly, .else, .return, .yield, .if, .while, .for, .inline, .defer, .errdefer, .eof: true
|
||||
.close_paren, .newline, .close_curly, .open_curly, .else, .return, .yield, .unreachable, .if, .while, .for, .inline, .defer, .errdefer, .eof: true
|
||||
else: false
|
||||
}
|
||||
}
|
||||
@@ -2054,7 +2057,7 @@ recovery_closes_delimiters proc(state @State, paren_depth, square_depth usize) b
|
||||
kind :: state.tokens[i].kind
|
||||
match kind {
|
||||
.open_curly, .close_curly, .eof, .if, .else, .while, .for, .inline,
|
||||
.return, .break, .continue, .defer, .errdefer: return false
|
||||
.return, .break, .continue, .unreachable, .defer, .errdefer: return false
|
||||
.ident: {
|
||||
if (i + 1 < state.tokens.len) {
|
||||
next :: state.tokens[i + 1].kind
|
||||
|
||||
@@ -1101,6 +1101,7 @@ collect_block proc(state @mut State, id, return_annotation ast.NodeId, in_loop b
|
||||
try collect_declaration(state, statement)
|
||||
}
|
||||
.expression: try collect_expression(state, ast.statement_expr(tree, statement))
|
||||
.unreachable_stmt: state.cells[usize(statement)].type = Type{ kind = .void }
|
||||
.return_stmt: {
|
||||
state.cells[usize(statement)].role = .return_stmt
|
||||
state.cells[usize(statement)].type = Type{ kind = .void }
|
||||
@@ -2335,6 +2336,7 @@ validate_call proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | dia
|
||||
BlockFlow :: struct {
|
||||
falls bool = true
|
||||
yields bool = false
|
||||
breaks bool = false
|
||||
}
|
||||
|
||||
@hide:file
|
||||
@@ -2367,6 +2369,16 @@ expression_falls_through proc(state @State, id ast.NodeId) bool {
|
||||
}
|
||||
}
|
||||
|
||||
@hide:file
|
||||
expression_breaks proc(state @State, id ast.NodeId) bool {
|
||||
tree :: state.tree
|
||||
if (ast.node_class(tree, id) == .block) return block_flow(state, id).breaks
|
||||
for 0..ast.child_count(tree, id) |i| {
|
||||
if (expression_breaks(state, ast.child(tree, id, i))) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@hide:file
|
||||
block_flow proc(state @State, id ast.NodeId) BlockFlow {
|
||||
tree :: state.tree
|
||||
@@ -2381,30 +2393,62 @@ block_flow proc(state @State, id ast.NodeId) BlockFlow {
|
||||
}
|
||||
match kind {
|
||||
.yield_stmt: {
|
||||
if (ast.yield_stmt(tree, statement).value) |value|
|
||||
result.breaks = result.breaks or expression_breaks(state, value)
|
||||
result.yields = true
|
||||
result.falls = false
|
||||
}
|
||||
.return_stmt, .break_stmt, .continue_stmt, .invalid: result.falls = false
|
||||
.return_stmt: {
|
||||
if (ast.return_stmt(tree, statement).value) |value|
|
||||
result.breaks = result.breaks or expression_breaks(state, value)
|
||||
result.falls = false
|
||||
}
|
||||
.break_stmt: {
|
||||
result.breaks = true
|
||||
result.falls = false
|
||||
}
|
||||
.continue_stmt, .unreachable_stmt, .invalid: result.falls = false
|
||||
.block_stmt: {
|
||||
nested :: block_flow(state, ast.statement_block(tree, statement))
|
||||
result.yields = result.yields or nested.yields
|
||||
result.breaks = result.breaks or nested.breaks
|
||||
result.falls = nested.falls
|
||||
}
|
||||
.if_stmt: {
|
||||
conditional :: ast.if_stmt(tree, statement)
|
||||
then_flow :: block_flow(state, conditional.then_block)
|
||||
result.yields = result.yields or then_flow.yields
|
||||
result.breaks = result.breaks or then_flow.breaks or expression_breaks(state, conditional.condition)
|
||||
if (conditional.else_block) |other| {
|
||||
else_flow :: block_flow(state, other)
|
||||
result.yields = result.yields or else_flow.yields
|
||||
result.breaks = result.breaks or else_flow.breaks
|
||||
result.falls = then_flow.falls or else_flow.falls
|
||||
}
|
||||
}
|
||||
.while_stmt: result.yields = result.yields or block_flow(state, ast.while_stmt(tree, statement).body).yields
|
||||
.for_stmt: result.yields = result.yields or block_flow(state, ast.for_stmt(tree, statement).body).yields
|
||||
.expression: result.falls = expression_falls_through(state, source)
|
||||
.declaration: result.falls = expression_falls_through(state, ast.statement_decl(tree, statement).initializer)
|
||||
.assignment_stmt: result.falls = expression_falls_through(state, ast.assignment_stmt(tree, statement).value)
|
||||
.while_stmt: {
|
||||
loop :: ast.while_stmt(tree, statement)
|
||||
body :: block_flow(state, loop.body)
|
||||
result.yields = result.yields or body.yields
|
||||
result.breaks = result.breaks or expression_breaks(state, loop.condition)
|
||||
if (loop.capture == null and ast.expr_kind(tree, loop.condition) == .literal_bool
|
||||
and mem.eql(ast.literal_text(tree, loop.condition), "true"))
|
||||
result.falls = body.breaks
|
||||
}
|
||||
.for_stmt: {
|
||||
loop :: ast.for_stmt(tree, statement)
|
||||
result.yields = result.yields or block_flow(state, loop.body).yields
|
||||
result.breaks = result.breaks or expression_breaks(state, loop.iterable)
|
||||
}
|
||||
.expression, .declaration, .assignment_stmt: {
|
||||
value :: if (kind == .expression) source
|
||||
else if (kind == .declaration) ast.statement_decl(tree, statement).initializer
|
||||
else ast.assignment_stmt(tree, statement).value
|
||||
result.breaks = result.breaks or expression_breaks(state, value)
|
||||
if (kind == .assignment_stmt)
|
||||
result.breaks = result.breaks or expression_breaks(state, ast.assignment_stmt(tree, statement).target)
|
||||
result.falls = expression_falls_through(state, value)
|
||||
}
|
||||
else: {}
|
||||
}
|
||||
if (!result.falls) return result
|
||||
|
||||
@@ -684,7 +684,7 @@ return_errors_preserve_identity_check_unreachable_code_and_suppress_missing_retu
|
||||
`void_value proc() void { return 1 }
|
||||
`unknown proc() int { return absent }
|
||||
`syntax proc() int { return (1 }
|
||||
`unreachable proc() int {
|
||||
`after_return proc() int {
|
||||
` return 7
|
||||
` -"bad"
|
||||
`}
|
||||
@@ -910,7 +910,7 @@ analyzes_return_paths_after_trap_propagation_without_poisoning_signatures test {
|
||||
`syntax_trap proc() int {
|
||||
` return 1 stray
|
||||
`}
|
||||
`unreachable proc() int {
|
||||
`after_return proc() int {
|
||||
` return 1
|
||||
` -"bad"
|
||||
`}
|
||||
@@ -1198,7 +1198,7 @@ while_checks_nested_bodies_without_claiming_a_zero_iteration_return test {
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
source ::
|
||||
`missing proc() int { while true { return 1 } }
|
||||
`terminal proc() int { while true { return 1 } }
|
||||
`bad_body proc() int { while false { -"bad" } }
|
||||
`continues proc() int {
|
||||
` while false { -"bad" }
|
||||
@@ -1226,12 +1226,11 @@ while_checks_nested_bodies_without_claiming_a_zero_iteration_return test {
|
||||
result := try check(mem.c_allocator, &tree, &resolution, &diagnostic_store)
|
||||
defer deinit(&result)
|
||||
|
||||
for usize(0)..usize(2) |index| {
|
||||
function :: ast.package_decl(&tree, index)
|
||||
body :: ast.function_decl(&tree, function).body
|
||||
_ = try expect_fallthrough(&result, &diagnostic_store, function, ast.node_span(&tree, body))
|
||||
try expect_kind(&result, ast.block_statement(&tree, body, 0), .void)
|
||||
}
|
||||
terminal :: ast.package_decl(&tree, 0)
|
||||
try testing.expect(fallthrough_diagnostic(&result, terminal) == null)
|
||||
body :: ast.function_decl(&tree, ast.package_decl(&tree, 1)).body
|
||||
_ = try expect_fallthrough(&result, &diagnostic_store, ast.package_decl(&tree, 1), ast.node_span(&tree, body))
|
||||
try expect_kind(&result, ast.block_statement(&tree, body, 0), .void)
|
||||
continues_body :: ast.function_decl(&tree, ast.package_decl(&tree, 2)).body
|
||||
loop_statement :: ast.block_statement(&tree, continues_body, 0)
|
||||
loop :: ast.while_stmt(&tree, loop_statement)
|
||||
@@ -1261,7 +1260,7 @@ while_checks_nested_bodies_without_claiming_a_zero_iteration_return test {
|
||||
try expect_kind(&result, assignment.target, .int)
|
||||
try expect_kind(&result, assignment.value, .int)
|
||||
}
|
||||
try testing.expect_equal(2, code_count(&diagnostic_store, .missing_return))
|
||||
try testing.expect_equal(1, code_count(&diagnostic_store, .missing_return))
|
||||
try testing.expect_equal(1, code_count(&diagnostic_store, .condition_not_bool))
|
||||
try testing.expect_equal(3, code_count(&diagnostic_store, .invalid_operand))
|
||||
}
|
||||
@@ -1518,7 +1517,7 @@ short_circuit_skip_paths_do_not_guarantee_a_return test {
|
||||
`call proc() int { take(true or 1) }
|
||||
`conditional proc() int { if false and missing_condition { return 1 } }
|
||||
`loop proc() int { while true or missing_guard { return 1 } }
|
||||
`loop_body proc() int { while true { true or 1 } }
|
||||
`loop_body proc(flag bool) int { while flag { true or 1 } }
|
||||
`arms proc(flag bool) int {
|
||||
` if flag { false and missing_arm } else { return 1 }
|
||||
`}
|
||||
|
||||
@@ -412,7 +412,7 @@ resolve_block proc(state @mut State, scope @mut Scope, id ast.NodeId) void ! (me
|
||||
if (ast.return_stmt(tree, statement).value) |value| try resolve_expr(state, scope, value)
|
||||
}
|
||||
.yield_stmt: try resolve_expr(state, scope, ast.yield_stmt(tree, statement).value?)
|
||||
.break_stmt, .continue_stmt: {}
|
||||
.break_stmt, .continue_stmt, .unreachable_stmt: {}
|
||||
.block_stmt: try resolve_arm(state, scope, ast.statement_block(tree, statement))
|
||||
.defer_stmt, .errdefer_stmt: {
|
||||
cleanup :: ast.cleanup_stmt(tree, statement)
|
||||
|
||||
@@ -134,7 +134,8 @@ elaborate proc(
|
||||
if (class == .expression and ast.expr_kind(&original, id) == .builtin_call) {
|
||||
name :: ast.builtin_call(&original, id).name
|
||||
text :: ast.source_text(&original, ast.node_span(&original, name))
|
||||
if (mem.eql(text, "field") or mem.eql(text, "struct_type") or mem.eql(text, "typeinfo")) needed = true
|
||||
if (mem.eql(text, "field") or mem.eql(text, "struct_type") or mem.eql(text, "typeinfo")
|
||||
or mem.eql(text, "compile_error")) needed = true
|
||||
}
|
||||
if (class == .function and ast.function_generic(&original, id)) needed = true
|
||||
if (class == .function) {
|
||||
@@ -237,6 +238,35 @@ reject proc(state @mut State, id ast.NodeId, code diagnostics.Code) void ! Error
|
||||
state.bindings.items[usize(id)] = sema.Binding{ kind = .invalid, data = sema.BindingData{ diagnostic = diagnostic } }
|
||||
}
|
||||
|
||||
@hide:file
|
||||
expand_compile_error proc(state @mut State, id ast.NodeId) void ! Error {
|
||||
if (ast.call_arg_count(&state.tree, id) != 1) {
|
||||
try reject(state, id, .invalid_compile_error)
|
||||
return
|
||||
}
|
||||
state.evaluation_diagnostic = diagnostics.no_diagnostic
|
||||
value :: try evaluate(state, ast.call_arg(&state.tree, id, 0))
|
||||
if (value.kind != .string) {
|
||||
if (state.evaluation_diagnostic != diagnostics.no_diagnostic) try reject_evaluation(state, id)
|
||||
else try reject(state, id, .invalid_compile_error)
|
||||
return
|
||||
}
|
||||
text :: ast.literal_text(&state.tree, value.node)
|
||||
bytes := arraylist.init(u8, state.allocator)
|
||||
defer arraylist.deinit(&bytes)
|
||||
cursor := usize(1)
|
||||
while (cursor < text.len - 1) {
|
||||
byte :: ast.string_next(text, &cursor) catch {
|
||||
try reject(state, id, .invalid_escape)
|
||||
return
|
||||
}
|
||||
try arraylist.append(&bytes, byte)
|
||||
}
|
||||
state.evaluation_diagnostic = try diagnostics.add_message(state.diagnostics,
|
||||
.compile_error, ast.node_span(&state.tree, id), bytes.items)
|
||||
try reject_evaluation(state, id)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
expand proc(state @mut State, id ast.NodeId) void ! Error {
|
||||
try refresh(state)
|
||||
@@ -287,6 +317,15 @@ expand proc(state @mut State, id ast.NodeId) void ! Error {
|
||||
if (class == .statement and ast.stmt_kind(&state.tree, id) == .for_stmt) {
|
||||
loop :: ast.for_stmt(&state.tree, id)
|
||||
try expand(state, loop.iterable)
|
||||
if (loop.index) |capture| {
|
||||
annotation :: try builtin(state, id, .usize)
|
||||
hashmap.put(&state.capture_types, capture, annotation) catch |err| {
|
||||
match err {
|
||||
.key_exists: {}
|
||||
else: return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!loop.pointer_capture and loop.capture != null) {
|
||||
iterable :: try expression_type(state, loop.iterable, 0)
|
||||
element := no_node
|
||||
@@ -417,6 +456,10 @@ expand proc(state @mut State, id ast.NodeId) void ! Error {
|
||||
}
|
||||
if (class == .expression and ast.expr_kind(&state.tree, id) == .builtin_call) {
|
||||
name :: ast.builtin_call(&state.tree, id).name
|
||||
if (mem.eql(ast.source_text(&state.tree, ast.node_span(&state.tree, name)), "compile_error")) {
|
||||
try expand_compile_error(state, id)
|
||||
return
|
||||
}
|
||||
if (mem.eql(ast.source_text(&state.tree, ast.node_span(&state.tree, name)), "field")) {
|
||||
try expand_field(state, id)
|
||||
return
|
||||
@@ -496,7 +539,7 @@ value_block_terminal proc(state @State, id ast.NodeId) bool {
|
||||
}
|
||||
if (class != .statement) return false
|
||||
match ast.stmt_kind(&state.tree, id) {
|
||||
.yield_stmt, .return_stmt, .break_stmt, .continue_stmt: return true
|
||||
.yield_stmt, .return_stmt, .break_stmt, .continue_stmt, .unreachable_stmt: return true
|
||||
.block_stmt: return value_block_terminal(state, ast.statement_block(&state.tree, id))
|
||||
.if_stmt: {
|
||||
view :: ast.if_stmt(&state.tree, id)
|
||||
@@ -2300,7 +2343,11 @@ evaluate_ir_body proc(state @mut State, at, body, result_type ast.NodeId) Value
|
||||
diagnostic_store := diagnostics.init(state.allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
# Resolution's existing diagnostic IDs remain valid in this temporary store.
|
||||
for (state.diagnostics.list.items) |diagnostic| try arraylist.append(&diagnostic_store.list, diagnostic)
|
||||
for (state.diagnostics.list.items) |diagnostic| {
|
||||
if (diagnostic.custom_message) |text| {
|
||||
_ = try diagnostics.add_message(&diagnostic_store, diagnostic.code, diagnostic.span, text)
|
||||
} else _ = try diagnostics.add(&diagnostic_store, diagnostic.code, diagnostic.span)
|
||||
}
|
||||
original_diagnostics :: diagnostics.count(&diagnostic_store)
|
||||
checked := try typecheck.check(state.allocator, &tree, state.resolution, &diagnostic_store)
|
||||
defer typecheck.deinit(&checked)
|
||||
@@ -2744,6 +2791,7 @@ inline_statement proc(state @mut State, id ast.NodeId) TypeFlow ! Error {
|
||||
kind :: ast.stmt_kind(&state.tree, id)
|
||||
if (kind == .break_stmt) return .break
|
||||
if (kind == .continue_stmt) return .continue
|
||||
if (kind == .unreachable_stmt) return .returned
|
||||
if (kind == .yield_stmt or kind == .return_stmt) {
|
||||
try expand(state, id)
|
||||
return if (kind == .yield_stmt) TypeFlow.yielded else TypeFlow.returned
|
||||
@@ -3911,6 +3959,11 @@ execute_type_statement proc(state @mut State, execution @mut TypeExecution, stat
|
||||
@hide:file
|
||||
execute_type_statement_body proc(state @mut State, execution @mut TypeExecution, statement ast.NodeId) TypeFlow ! Error {
|
||||
match ast.stmt_kind(&state.tree, statement) {
|
||||
.unreachable_stmt: {
|
||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
||||
.unreachable_executed, ast.node_span(&state.tree, statement))
|
||||
return .invalid
|
||||
}
|
||||
.defer_stmt: {}
|
||||
.errdefer_stmt: {
|
||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
||||
@@ -4465,6 +4518,18 @@ evaluate proc(state @mut State, id ast.NodeId) Value ! Error {
|
||||
view :: ast.unary_expr(&state.tree, id)
|
||||
if (view.operator == .bit_not)
|
||||
return try evaluate_ir(state, id, try expression_type(state, id, 0))
|
||||
if (view.operator == .unwrap) {
|
||||
# source-known callbacks already own declaration identities, not interpreter handles.
|
||||
value := try evaluate(state, view.operand)
|
||||
annotation :: try expression_type(state, view.operand, 0)
|
||||
if (value.kind == .function and annotation != no_node
|
||||
and ast.expr_kind(&state.tree, annotation) == .optional_type) {
|
||||
try arraylist.append(&state.rewrites.constraints, id)
|
||||
value.annotation = ast.type_operand(&state.tree, annotation)
|
||||
return value
|
||||
}
|
||||
return try evaluate_ir(state, id, try expression_type(state, id, 0))
|
||||
}
|
||||
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)
|
||||
@@ -4917,6 +4982,8 @@ expression_type proc(state @mut State, id ast.NodeId, depth usize) ast.NodeId !
|
||||
return try builtin(state, id, .usize)
|
||||
if ((mem.eql(name, "maxval") or mem.eql(name, "minval")) and count == 1)
|
||||
return try canonical_type(state, ast.call_arg(&state.tree, id, 0))
|
||||
if ((mem.eql(name, "ptrcast") or mem.eql(name, "constcast")) and count == 2)
|
||||
return try canonical_type(state, ast.call_arg(&state.tree, id, 0))
|
||||
if (mem.eql(name, "divtrunc") and count == 2) {
|
||||
left :: ast.call_arg(&state.tree, id, 0)
|
||||
right :: ast.call_arg(&state.tree, id, 1)
|
||||
@@ -4974,6 +5041,7 @@ expression_type proc(state @mut State, id ast.NodeId, depth usize) ast.NodeId !
|
||||
if (destination.kind == .builtin) return callee
|
||||
if (destination.kind == .declaration and ast.node_class(&state.tree, destination.data.declaration) == .function) return try canonical_type(state, ast.function_decl(&state.tree, destination.data.declaration).return_type)
|
||||
signature :: try expression_type(state, callee, depth + 1)
|
||||
if (signature != no_node and is_type(state, signature)) return try canonical_type(state, callee)
|
||||
if (signature != no_node and ast.expr_kind(&state.tree, signature) == .function_type)
|
||||
return try canonical_type(state, ast.callable_result(&state.tree, signature))
|
||||
return no_node
|
||||
@@ -5121,8 +5189,18 @@ infer_value proc(state @mut State, function, pattern ast.NodeId, actual Value, a
|
||||
@hide:file
|
||||
infer proc(state @mut State, function, pattern, actual ast.NodeId, arguments []mut Value, contextual bool) bool ! Error {
|
||||
resolved :: binding(state, pattern)
|
||||
if (resolved.kind == .declaration and parameter_index(state, function, resolved.data.declaration) != no_index)
|
||||
return try infer_value(state, function, pattern, Value{ kind = .type, node = actual }, arguments, contextual)
|
||||
if (resolved.kind == .declaration) {
|
||||
index :: parameter_index(state, function, resolved.data.declaration)
|
||||
if (index != no_index) {
|
||||
if (arguments[index].kind == .type) {
|
||||
expected :: try canonical_type(state, arguments[index].node)
|
||||
# a fixed slice type supplies borrowing/string context; ordinary
|
||||
# argument checking owns element, mutability and sentinel validation.
|
||||
if (expected != no_node and ast.expr_kind(&state.tree, expected) == .slice_type) return true
|
||||
}
|
||||
return try infer_value(state, function, pattern, Value{ kind = .type, node = actual }, arguments, contextual)
|
||||
}
|
||||
}
|
||||
# only normalize the actual type: the pattern may still contain unbound
|
||||
# parameters or a type-generator call whose arguments must be inferred.
|
||||
concrete :: try canonical_type(state, actual)
|
||||
|
||||
Reference in New Issue
Block a user