add yield
This commit is contained in:
+185
-13
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user