|
|
|
@@ -724,11 +724,13 @@ mark_block_imports_used :: proc(checker: ^Checker, statements: []ast.Stmt_Id, fi
|
|
|
|
|
for statement_id in statements {
|
|
|
|
|
statement := checker.ast_module.statements[statement_id]
|
|
|
|
|
switch statement.kind {
|
|
|
|
|
case .Declaration, .Assignment, .Return, .Expression:
|
|
|
|
|
case .Declaration, .Assignment, .Return, .Expression, .Yield:
|
|
|
|
|
mark_expr_imports_used(checker, statement.expr, file)
|
|
|
|
|
if statement.target != ast.INVALID_EXPR {
|
|
|
|
|
mark_expr_imports_used(checker, statement.target, file)
|
|
|
|
|
}
|
|
|
|
|
// A value-block declaration/assignment carries its block in `body`.
|
|
|
|
|
mark_block_imports_used(checker, statement.body, file)
|
|
|
|
|
case .If:
|
|
|
|
|
mark_expr_imports_used(checker, statement.expr, file)
|
|
|
|
|
if statement.guard != ast.INVALID_EXPR {
|
|
|
|
@@ -1721,6 +1723,22 @@ infer_statements :: proc(
|
|
|
|
|
statement := checker.ast_module.statements[statement_id]
|
|
|
|
|
#partial switch statement.kind {
|
|
|
|
|
case .Declaration:
|
|
|
|
|
if statement.expr == ast.INVALID_EXPR {
|
|
|
|
|
// Value block (`x :: { ... yield v }` / `x T = { ... }`): register the
|
|
|
|
|
// binding (its declared type when annotated, else left open) and walk
|
|
|
|
|
// the block body. The build pass resolves the yielded value's type
|
|
|
|
|
// independently — value blocks don't join the demand fixpoint.
|
|
|
|
|
declared_block := type_from_syntax(statement.type)
|
|
|
|
|
block_type := declared_block if is_runtime_type(checker, declared_block) else types.INVALID
|
|
|
|
|
local := Infer_Local{
|
|
|
|
|
name=statement.name, type=block_type, declared=declared_block,
|
|
|
|
|
statement=statement_id, mutable=!statement.immutable,
|
|
|
|
|
}
|
|
|
|
|
append(locals, local)
|
|
|
|
|
record_infer_local_type(local, local_types)
|
|
|
|
|
infer_statements(checker, statement.body, locals, local_types, pkg, file, demanded, result, result_hint)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
declared_local := resolve_inferred_array(checker, type_from_syntax(statement.type), statement.expr)
|
|
|
|
|
value_type := types.INVALID
|
|
|
|
|
if !is_undefined_expr(checker, statement.expr) {
|
|
|
|
@@ -1770,6 +1788,12 @@ infer_statements :: proc(
|
|
|
|
|
record_demand(checker, statement.expr, value_type, locals^[:], local_types, pkg, file)
|
|
|
|
|
}
|
|
|
|
|
case .Assignment:
|
|
|
|
|
if statement.expr == ast.INVALID_EXPR {
|
|
|
|
|
// Value block assigned to a target: walk the block body; the build
|
|
|
|
|
// pass handles the target coercion.
|
|
|
|
|
infer_statements(checker, statement.body, locals, local_types, pkg, file, demanded, result, result_hint)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
value_type := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types)
|
|
|
|
|
// Only push the target's type back onto a bare-name RHS (e.g. `x += speed`):
|
|
|
|
|
// pushing through an arithmetic RHS would feed the target's (often provisional)
|
|
|
|
@@ -3996,6 +4020,7 @@ build_block :: proc(
|
|
|
|
|
ctx: ^Build_Ctx,
|
|
|
|
|
statements: []ast.Stmt_Id,
|
|
|
|
|
duplicate_scope_start := -1,
|
|
|
|
|
close := true,
|
|
|
|
|
) -> []hir.Stmt_Id {
|
|
|
|
|
checker := ctx.checker
|
|
|
|
|
body: [dynamic]hir.Stmt_Id
|
|
|
|
@@ -4007,6 +4032,43 @@ build_block :: proc(
|
|
|
|
|
statement := checker.ast_module.statements[statement_id]
|
|
|
|
|
switch statement.kind {
|
|
|
|
|
case .Declaration:
|
|
|
|
|
// A value block (`x :: { ... yield v }` / `x T = { ... }`): the parser
|
|
|
|
|
// leaves `expr` invalid and stashes the block in `body`. Build it, then
|
|
|
|
|
// declare the local from the yielded value (its type for an untyped `::`).
|
|
|
|
|
if statement.expr == ast.INVALID_EXPR {
|
|
|
|
|
expected := types.INVALID
|
|
|
|
|
typed := is_runtime_type(checker, type_from_syntax(statement.type))
|
|
|
|
|
if typed {
|
|
|
|
|
expected = type_from_syntax(statement.type)
|
|
|
|
|
}
|
|
|
|
|
value, value_type := build_value_block(ctx, &body, statement.body, expected, statement.span)
|
|
|
|
|
if _, found := find_build_local(ctx.locals^[duplicate_start:], statement.name); found {
|
|
|
|
|
id := source.addf(
|
|
|
|
|
checker.diagnostics, statement.span,
|
|
|
|
|
"duplicate local '%s'", symbol_text(checker, statement.name),
|
|
|
|
|
)
|
|
|
|
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
|
|
|
append(&checker.module.statements, hir.Stmt{
|
|
|
|
|
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
|
|
|
|
|
local = hir.INVALID_LOCAL, diagnostic = id,
|
|
|
|
|
})
|
|
|
|
|
ctx.problematic^ = true
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
local_id := hir.local_id(len(ctx.hir_locals^))
|
|
|
|
|
append(ctx.hir_locals, hir.Local{
|
|
|
|
|
name = statement.name, type = value_type, mutable = !statement.immutable,
|
|
|
|
|
})
|
|
|
|
|
append(ctx.locals, Build_Local{
|
|
|
|
|
name = statement.name, type = value_type, mutable = !statement.immutable, id = local_id,
|
|
|
|
|
})
|
|
|
|
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
|
|
|
append(&checker.module.statements, hir.Stmt{
|
|
|
|
|
kind = .Declaration, span = statement.span, local = local_id, expr = value,
|
|
|
|
|
diagnostic = source.INVALID_DIAGNOSTIC,
|
|
|
|
|
})
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
declared := resolve_inferred_array(checker, type_from_syntax(statement.type), statement.expr)
|
|
|
|
|
// Adopt the type inference resolved for this local when the declaration has no
|
|
|
|
|
// concrete annotation and inference carried useful numeric context: constraints,
|
|
|
|
@@ -4187,6 +4249,10 @@ build_block :: proc(
|
|
|
|
|
value = coerce_expr(checker, value, target_type, statement.span)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if statement.expr == ast.INVALID_EXPR {
|
|
|
|
|
// `target = { ... yield v }`: build the value block against the
|
|
|
|
|
// target's type (build_value_block coerces internally).
|
|
|
|
|
value, _ = build_value_block(ctx, &body, statement.body, target_type, statement.span)
|
|
|
|
|
} else {
|
|
|
|
|
value = build_expr(
|
|
|
|
|
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
|
|
|
@@ -4204,7 +4270,12 @@ build_block :: proc(
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if statement.name == checker.sink_symbol {
|
|
|
|
|
value := build_expr(checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, types.INVALID, ctx.pkg, ctx.file)
|
|
|
|
|
value: hir.Expr_Id
|
|
|
|
|
if statement.expr == ast.INVALID_EXPR {
|
|
|
|
|
value, _ = build_value_block(ctx, &body, statement.body, types.INVALID, statement.span)
|
|
|
|
|
} else {
|
|
|
|
|
value = build_expr(checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, types.INVALID, ctx.pkg, ctx.file)
|
|
|
|
|
}
|
|
|
|
|
if types.is_void(checker.module.exprs[value].type) {
|
|
|
|
|
id := source.add(checker.diagnostics, statement.span, "cannot assign a void expression to '_'")
|
|
|
|
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
|
|
@@ -4243,11 +4314,16 @@ build_block :: proc(
|
|
|
|
|
ctx.problematic^ = true
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
value := build_expr(
|
|
|
|
|
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
|
|
|
|
local.type, ctx.pkg, ctx.file,
|
|
|
|
|
)
|
|
|
|
|
value = coerce_expr(checker, value, local.type, statement.span)
|
|
|
|
|
value: hir.Expr_Id
|
|
|
|
|
if statement.expr == ast.INVALID_EXPR {
|
|
|
|
|
value, _ = build_value_block(ctx, &body, statement.body, local.type, statement.span)
|
|
|
|
|
} else {
|
|
|
|
|
value = build_expr(
|
|
|
|
|
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
|
|
|
|
local.type, ctx.pkg, ctx.file,
|
|
|
|
|
)
|
|
|
|
|
value = coerce_expr(checker, value, local.type, statement.span)
|
|
|
|
|
}
|
|
|
|
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
|
|
|
append(&checker.module.statements, hir.Stmt{
|
|
|
|
|
kind = .Assignment, span = statement.span, expr = value, local = local.id,
|
|
|
|
@@ -4663,6 +4739,21 @@ build_block :: proc(
|
|
|
|
|
append(&body, stmt)
|
|
|
|
|
}
|
|
|
|
|
delete(block, checker.allocator)
|
|
|
|
|
case .Yield:
|
|
|
|
|
// A legitimate yield is peeled off by build_value_block as the value
|
|
|
|
|
// block's final statement; reaching it here means it is misplaced
|
|
|
|
|
// (nested in an if/loop/inner block, or in a non-value block).
|
|
|
|
|
// ponytail: yield from if/loops/labeled blocks is a later milestone.
|
|
|
|
|
id := source.add(
|
|
|
|
|
checker.diagnostics, statement.span,
|
|
|
|
|
"'yield' is only valid as the final statement of a value block",
|
|
|
|
|
)
|
|
|
|
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
|
|
|
append(&checker.module.statements, hir.Stmt{
|
|
|
|
|
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
|
|
|
|
|
local = hir.INVALID_LOCAL, diagnostic = id,
|
|
|
|
|
})
|
|
|
|
|
ctx.problematic^ = true
|
|
|
|
|
case .Defer:
|
|
|
|
|
deferred := checker.ast_module.statements[statement.update]
|
|
|
|
|
if deferred.kind == .Return || deferred.kind == .Break ||
|
|
|
|
@@ -4701,18 +4792,99 @@ build_block :: proc(
|
|
|
|
|
}
|
|
|
|
|
// Normal fall-through exit: run this block's own deferred statements, unless
|
|
|
|
|
// every path already exited early (return/break/continue) — that would only
|
|
|
|
|
// emit unreachable duplicates.
|
|
|
|
|
if !all_paths_exit(&checker.module, body[:]) {
|
|
|
|
|
flush_defers(ctx, &body, defer_start)
|
|
|
|
|
// emit unreachable duplicates. A value block (`close=false`) skips this so its
|
|
|
|
|
// caller can capture the yielded value before flushing the block's defers.
|
|
|
|
|
if close {
|
|
|
|
|
if !all_paths_exit(&checker.module, body[:]) {
|
|
|
|
|
flush_defers(ctx, &body, defer_start)
|
|
|
|
|
}
|
|
|
|
|
// Free this block's deferred-statement entry slices (their stmt ids were
|
|
|
|
|
// already replayed at every path that can leave this block) and pop the frame.
|
|
|
|
|
for i := defer_start; i < len(ctx.defers^); i += 1 {
|
|
|
|
|
delete(ctx.defers^[i], checker.allocator)
|
|
|
|
|
}
|
|
|
|
|
resize(ctx.defers, defer_start)
|
|
|
|
|
resize(ctx.locals, scope_start)
|
|
|
|
|
}
|
|
|
|
|
// Free this block's deferred-statement entry slices (their stmt ids were already
|
|
|
|
|
// replayed at every path that can leave this block) and pop the frame.
|
|
|
|
|
return body[:]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// build_value_block builds a `{ ... yield v }` value block whose final statement
|
|
|
|
|
// must be a `yield`: it builds the leading statements inline (their own scope and
|
|
|
|
|
// defers), evaluates the yield expression in that scope, then — capturing the value
|
|
|
|
|
// first, like a function return — runs the block's defers and closes the scope. The
|
|
|
|
|
// resulting `value`/`value_type` are spliced into the enclosing declaration or
|
|
|
|
|
// assignment. `expected` is the binding's type (INVALID for an untyped `::`, where
|
|
|
|
|
// the yield's natural type is taken). Statements are appended to `body`.
|
|
|
|
|
build_value_block :: proc(
|
|
|
|
|
ctx: ^Build_Ctx,
|
|
|
|
|
body: ^[dynamic]hir.Stmt_Id,
|
|
|
|
|
body_stmts: []ast.Stmt_Id,
|
|
|
|
|
expected: types.Type,
|
|
|
|
|
span: source.Span,
|
|
|
|
|
) -> (value: hir.Expr_Id, value_type: types.Type) {
|
|
|
|
|
checker := ctx.checker
|
|
|
|
|
n := len(body_stmts)
|
|
|
|
|
if n == 0 || checker.ast_module.statements[body_stmts[n - 1]].kind != .Yield {
|
|
|
|
|
// Build whatever is there so inner errors (and misplaced yields) surface, then
|
|
|
|
|
// report the missing trailing yield.
|
|
|
|
|
inner := build_block(ctx, body_stmts)
|
|
|
|
|
for s in inner {
|
|
|
|
|
append(body, s)
|
|
|
|
|
}
|
|
|
|
|
delete(inner, checker.allocator)
|
|
|
|
|
id := source.add(checker.diagnostics, span, "a value block must end with an explicit 'yield'")
|
|
|
|
|
ctx.problematic^ = true
|
|
|
|
|
return invalid_hir_expr(checker, span, id), types.INVALID
|
|
|
|
|
}
|
|
|
|
|
scope_start := len(ctx.locals^)
|
|
|
|
|
defer_start := len(ctx.defers^)
|
|
|
|
|
// Leading statements keep the scope open (close=false) so the yield can still see
|
|
|
|
|
// the block's locals; any nested `yield` hits the erroring `.Yield` switch case.
|
|
|
|
|
leading := build_block(ctx, body_stmts[:n - 1], close = false)
|
|
|
|
|
for s in leading {
|
|
|
|
|
append(body, s)
|
|
|
|
|
}
|
|
|
|
|
delete(leading, checker.allocator)
|
|
|
|
|
|
|
|
|
|
yield_stmt := checker.ast_module.statements[body_stmts[n - 1]]
|
|
|
|
|
value = build_expr(
|
|
|
|
|
checker, yield_stmt.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
|
|
|
|
expected, ctx.pkg, ctx.file,
|
|
|
|
|
)
|
|
|
|
|
value_type = checker.module.exprs[value].type
|
|
|
|
|
if is_runtime_type(checker, expected) {
|
|
|
|
|
value = coerce_expr(checker, value, expected, yield_stmt.span)
|
|
|
|
|
value_type = checker.module.exprs[value].type
|
|
|
|
|
}
|
|
|
|
|
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[value].kind == .Invalid
|
|
|
|
|
|
|
|
|
|
// Run the block's deferred statements before the value escapes, but capture the
|
|
|
|
|
// value first (spill to a temp) so a defer can't change what is yielded — the same
|
|
|
|
|
// rule as `return`.
|
|
|
|
|
if len(ctx.defers^) > defer_start {
|
|
|
|
|
if checker.module.exprs[value].kind != .Invalid {
|
|
|
|
|
tmp := hir.local_id(len(ctx.hir_locals^))
|
|
|
|
|
append(ctx.hir_locals, hir.Local{name = checker.sink_symbol, type = value_type, mutable = false})
|
|
|
|
|
append(body, hir.stmt_id(len(checker.module.statements)))
|
|
|
|
|
append(&checker.module.statements, hir.Stmt{
|
|
|
|
|
kind = .Declaration, span = yield_stmt.span, local = tmp, expr = value,
|
|
|
|
|
diagnostic = source.INVALID_DIAGNOSTIC,
|
|
|
|
|
})
|
|
|
|
|
value = hir.expr_id(len(checker.module.exprs))
|
|
|
|
|
append(&checker.module.exprs, hir.Expr{
|
|
|
|
|
kind = .Local, span = yield_stmt.span, type = value_type, target = hir.local_ref(tmp),
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
flush_defers(ctx, body, defer_start)
|
|
|
|
|
}
|
|
|
|
|
// Close the scope (build_block left it open for us).
|
|
|
|
|
for i := defer_start; i < len(ctx.defers^); i += 1 {
|
|
|
|
|
delete(ctx.defers^[i], checker.allocator)
|
|
|
|
|
}
|
|
|
|
|
resize(ctx.defers, defer_start)
|
|
|
|
|
resize(ctx.locals, scope_start)
|
|
|
|
|
return body[:]
|
|
|
|
|
return value, value_type
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Reports whether every control-flow path through `stmts` terminates (returns or traps),
|
|
|
|
|