From 0aede1a8fcd59b259215c05b43827405ad5da9c7 Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Sat, 24 Apr 2021 10:20:16 +0200 Subject: [PATCH] stage1: Require a block after suspend Closes #8603 --- doc/langref.html.in | 10 ++-- lib/std/event/rwlock.zig | 4 +- lib/std/zig/parse.zig | 11 ++-- lib/std/zig/parser_test.zig | 18 +++++-- lib/std/zig/render.zig | 13 +---- src/stage1/ir.cpp | 16 +++--- src/stage1/parser.cpp | 5 +- test/compile_errors.zig | 14 +++--- test/runtime_safety.zig | 34 ++++++------- test/stage1/behavior/async_fn.zig | 84 +++++++++++++++---------------- 10 files changed, 100 insertions(+), 109 deletions(-) diff --git a/doc/langref.html.in b/doc/langref.html.in index 41d8838892..f52abeab73 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -6528,7 +6528,7 @@ test "suspend with no resume" { fn func() void { x += 1; - suspend; + suspend {} // This line is never reached because the suspend has no matching resume. x += 1; } @@ -6593,7 +6593,7 @@ fn testResumeFromSuspend(my_result: *i32) void { resume @frame(); } my_result.* += 1; - suspend; + suspend {} my_result.* += 1; } {#code_end#} @@ -6632,7 +6632,7 @@ fn amain() void { } fn func() void { - suspend; + suspend {} } {#code_end#}

@@ -6934,7 +6934,7 @@ test "async fn pointer in a struct field" { fn func(y: *i32) void { defer y.* += 2; y.* += 1; - suspend; + suspend {} } {#code_end#} {#header_close#} @@ -7667,7 +7667,7 @@ test "heap allocated frame" { } fn func() void { - suspend; + suspend {} } {#code_end#} {#header_close#} diff --git a/lib/std/event/rwlock.zig b/lib/std/event/rwlock.zig index 750131beda..04b45ee308 100644 --- a/lib/std/event/rwlock.zig +++ b/lib/std/event/rwlock.zig @@ -264,7 +264,7 @@ var shared_test_data = [1]i32{0} ** 10; var shared_test_index: usize = 0; var shared_count: usize = 0; fn writeRunner(lock: *RwLock) callconv(.Async) void { - suspend; // resumed by onNextTick + suspend {} // resumed by onNextTick var i: usize = 0; while (i < shared_test_data.len) : (i += 1) { @@ -281,7 +281,7 @@ fn writeRunner(lock: *RwLock) callconv(.Async) void { } } fn readRunner(lock: *RwLock) callconv(.Async) void { - suspend; // resumed by onNextTick + suspend {} // resumed by onNextTick std.time.sleep(1); var i: usize = 0; diff --git a/lib/std/zig/parse.zig b/lib/std/zig/parse.zig index 029d8ede50..778075bdee 100644 --- a/lib/std/zig/parse.zig +++ b/lib/std/zig/parse.zig @@ -852,7 +852,7 @@ const Parser = struct { /// <- KEYWORD_comptime? VarDecl /// / KEYWORD_comptime BlockExprStatement /// / KEYWORD_nosuspend BlockExprStatement - /// / KEYWORD_suspend (SEMICOLON / BlockExprStatement) + /// / KEYWORD_suspend BlockExprStatement /// / KEYWORD_defer BlockExprStatement /// / KEYWORD_errdefer Payload? BlockExprStatement /// / IfStatement @@ -891,16 +891,11 @@ const Parser = struct { }); }, .keyword_suspend => { - const token = p.nextToken(); - const block_expr: Node.Index = if (p.eatToken(.semicolon) != null) - 0 - else - try p.expectBlockExprStatement(); return p.addNode(.{ .tag = .@"suspend", - .main_token = token, + .main_token = p.nextToken(), .data = .{ - .lhs = block_expr, + .lhs = try p.expectBlockExprStatement(), .rhs = undefined, }, }); diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index c433ee5ad8..479da2b50c 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -3665,9 +3665,9 @@ test "zig fmt: async functions" { \\fn simpleAsyncFn() void { \\ const a = async a.b(); \\ x += 1; - \\ suspend; + \\ suspend {} \\ x += 1; - \\ suspend; + \\ suspend {} \\ const p: anyframe->void = async simpleAsyncFn() catch unreachable; \\ await p; \\} @@ -5022,6 +5022,18 @@ test "recovery: invalid comptime" { }); } +test "recovery: missing block after suspend" { + try testError( + \\fn foo() void { + \\ suspend; + \\ nosuspend; + \\} + , &[_]Error{ + .expected_block_or_expr, + .expected_block_or_expr, + }); +} + test "recovery: missing block after for/while loops" { try testError( \\test "" { while (foo) } @@ -5165,7 +5177,7 @@ fn testError(source: []const u8, expected_errors: []const Error) !void { var tree = try std.zig.parse(std.testing.allocator, source); defer tree.deinit(std.testing.allocator); - std.testing.expect(tree.errors.len == expected_errors.len); + std.testing.expectEqual(expected_errors.len, tree.errors.len); for (expected_errors) |expected, i| { std.testing.expectEqual(expected, tree.errors[i].tag); } diff --git a/lib/std/zig/render.zig b/lib/std/zig/render.zig index 9291f63e2f..a417947c6b 100644 --- a/lib/std/zig/render.zig +++ b/lib/std/zig/render.zig @@ -255,24 +255,13 @@ fn renderExpression(gpa: *Allocator, ais: *Ais, tree: ast.Tree, node: ast.Node.I try renderToken(ais, tree, defer_token, .space); return renderExpression(gpa, ais, tree, expr, space); }, - .@"comptime", .@"nosuspend" => { + .@"comptime", .@"suspend", .@"nosuspend" => { const comptime_token = main_tokens[node]; const block = datas[node].lhs; try renderToken(ais, tree, comptime_token, .space); return renderExpression(gpa, ais, tree, block, space); }, - .@"suspend" => { - const suspend_token = main_tokens[node]; - const body = datas[node].lhs; - if (body != 0) { - try renderToken(ais, tree, suspend_token, .space); - return renderExpression(gpa, ais, tree, body, space); - } else { - return renderToken(ais, tree, suspend_token, space); - } - }, - .@"catch" => { const main_token = main_tokens[node]; const fallback_first = tree.firstToken(datas[node].rhs); diff --git a/src/stage1/ir.cpp b/src/stage1/ir.cpp index 61a1345327..c59f63399c 100644 --- a/src/stage1/ir.cpp +++ b/src/stage1/ir.cpp @@ -9534,7 +9534,7 @@ static IrInstSrc *ir_gen_nosuspend(IrBuilderSrc *irb, Scope *parent_scope, AstNo Scope *child_scope = create_nosuspend_scope(irb->codegen, node, parent_scope); // purposefully pass null for result_loc and let EndExpr handle it - return ir_gen_node_extra(irb, node->data.comptime_expr.expr, child_scope, lval, nullptr); + return ir_gen_node_extra(irb, node->data.nosuspend_expr.expr, child_scope, lval, nullptr); } static IrInstSrc *ir_gen_return_from_block(IrBuilderSrc *irb, Scope *break_scope, AstNode *node, ScopeBlock *block_scope) { @@ -10199,14 +10199,12 @@ static IrInstSrc *ir_gen_suspend(IrBuilderSrc *irb, Scope *parent_scope, AstNode } IrInstSrcSuspendBegin *begin = ir_build_suspend_begin_src(irb, parent_scope, node); - if (node->data.suspend.block != nullptr) { - ScopeSuspend *suspend_scope = create_suspend_scope(irb->codegen, node, parent_scope); - Scope *child_scope = &suspend_scope->base; - IrInstSrc *susp_res = ir_gen_node(irb, node->data.suspend.block, child_scope); - if (susp_res == irb->codegen->invalid_inst_src) - return irb->codegen->invalid_inst_src; - ir_mark_gen(ir_build_check_statement_is_void(irb, child_scope, node->data.suspend.block, susp_res)); - } + ScopeSuspend *suspend_scope = create_suspend_scope(irb->codegen, node, parent_scope); + Scope *child_scope = &suspend_scope->base; + IrInstSrc *susp_res = ir_gen_node(irb, node->data.suspend.block, child_scope); + if (susp_res == irb->codegen->invalid_inst_src) + return irb->codegen->invalid_inst_src; + ir_mark_gen(ir_build_check_statement_is_void(irb, child_scope, node->data.suspend.block, susp_res)); return ir_mark_gen(ir_build_suspend_finish_src(irb, parent_scope, node, begin)); } diff --git a/src/stage1/parser.cpp b/src/stage1/parser.cpp index c37b3ffefb..d57277cd51 100644 --- a/src/stage1/parser.cpp +++ b/src/stage1/parser.cpp @@ -946,10 +946,7 @@ static AstNode *ast_parse_statement(ParseContext *pc) { Token *suspend = eat_token_if(pc, TokenIdKeywordSuspend); if (suspend != nullptr) { - AstNode *statement = nullptr; - if (eat_token_if(pc, TokenIdSemicolon) == nullptr) - statement = ast_expect(pc, ast_parse_block_expr_statement); - + AstNode *statement = ast_expect(pc, ast_parse_block_expr_statement); AstNode *res = ast_create_node(pc, NodeTypeSuspend, suspend); res->data.suspend.block = statement; return res; diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 808f0b31dc..bfa9b592b4 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -1021,7 +1021,7 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { \\export fn entry() void { \\ nosuspend { \\ const bar = async foo(); - \\ suspend; + \\ suspend {} \\ resume bar; \\ } \\} @@ -2120,7 +2120,7 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { \\ non_async_fn = func; \\} \\fn func() void { - \\ suspend; + \\ suspend {} \\} , &[_][]const u8{ "tmp.zig:5:1: error: 'func' cannot be async", @@ -2198,7 +2198,7 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { \\ var x: anyframe = &f; \\} \\fn func() void { - \\ suspend; + \\ suspend {} \\} , &[_][]const u8{ "tmp.zig:3:12: error: expected type 'anyframe', found '*const @Frame(func)'", @@ -2231,10 +2231,10 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { \\ frame = async bar(); \\} \\fn foo() void { - \\ suspend; + \\ suspend {} \\} \\fn bar() void { - \\ suspend; + \\ suspend {} \\} , &[_][]const u8{ "tmp.zig:3:13: error: expected type '*@Frame(bar)', found '*@Frame(foo)'", @@ -2269,7 +2269,7 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { \\ var result = await frame; \\} \\fn func() void { - \\ suspend; + \\ suspend {} \\} , &[_][]const u8{ "tmp.zig:1:1: error: function with calling convention 'C' cannot be async", @@ -2347,7 +2347,7 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { \\ bar(); \\} \\fn bar() void { - \\ suspend; + \\ suspend {} \\} , &[_][]const u8{ "tmp.zig:1:1: error: function with calling convention 'C' cannot be async", diff --git a/test/runtime_safety.zig b/test/runtime_safety.zig index eb49b2dbc1..03a26a2c32 100644 --- a/test/runtime_safety.zig +++ b/test/runtime_safety.zig @@ -13,7 +13,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { cases.addRuntimeSafety("switch on corrupted enum value", \\const std = @import("std"); - ++ check_panic_msg ++ + ++ check_panic_msg ++ \\const E = enum(u32) { \\ X = 1, \\}; @@ -28,7 +28,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { cases.addRuntimeSafety("switch on corrupted union value", \\const std = @import("std"); - ++ check_panic_msg ++ + ++ check_panic_msg ++ \\const U = union(enum(u32)) { \\ X: u8, \\}; @@ -54,7 +54,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { cases.addRuntimeSafety("@tagName on corrupted enum value", \\const std = @import("std"); - ++ check_panic_msg ++ + ++ check_panic_msg ++ \\const E = enum(u32) { \\ X = 1, \\}; @@ -67,7 +67,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { cases.addRuntimeSafety("@tagName on corrupted union value", \\const std = @import("std"); - ++ check_panic_msg ++ + ++ check_panic_msg ++ \\const U = union(enum(u32)) { \\ X: u8, \\}; @@ -92,7 +92,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { cases.addRuntimeSafety("slicing operator with sentinel", \\const std = @import("std"); - ++ check_panic_msg ++ + ++ check_panic_msg ++ \\pub fn main() void { \\ var buf = [4]u8{'a','b','c',0}; \\ const slice = buf[0..4 :0]; @@ -100,7 +100,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { ); cases.addRuntimeSafety("slicing operator with sentinel", \\const std = @import("std"); - ++ check_panic_msg ++ + ++ check_panic_msg ++ \\pub fn main() void { \\ var buf = [4]u8{'a','b','c',0}; \\ const slice = buf[0..:0]; @@ -108,7 +108,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { ); cases.addRuntimeSafety("slicing operator with sentinel", \\const std = @import("std"); - ++ check_panic_msg ++ + ++ check_panic_msg ++ \\pub fn main() void { \\ var buf_zero = [0]u8{}; \\ const slice = buf_zero[0..0 :0]; @@ -116,7 +116,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { ); cases.addRuntimeSafety("slicing operator with sentinel", \\const std = @import("std"); - ++ check_panic_msg ++ + ++ check_panic_msg ++ \\pub fn main() void { \\ var buf_zero = [0]u8{}; \\ const slice = buf_zero[0..:0]; @@ -124,7 +124,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { ); cases.addRuntimeSafety("slicing operator with sentinel", \\const std = @import("std"); - ++ check_panic_msg ++ + ++ check_panic_msg ++ \\pub fn main() void { \\ var buf_sentinel = [2:0]u8{'a','b'}; \\ @ptrCast(*[3]u8, &buf_sentinel)[2] = 0; @@ -133,7 +133,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { ); cases.addRuntimeSafety("slicing operator with sentinel", \\const std = @import("std"); - ++ check_panic_msg ++ + ++ check_panic_msg ++ \\pub fn main() void { \\ var buf_slice: []const u8 = &[3]u8{ 'a', 'b', 0 }; \\ const slice = buf_slice[0..3 :0]; @@ -141,7 +141,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { ); cases.addRuntimeSafety("slicing operator with sentinel", \\const std = @import("std"); - ++ check_panic_msg ++ + ++ check_panic_msg ++ \\pub fn main() void { \\ var buf_slice: []const u8 = &[3]u8{ 'a', 'b', 0 }; \\ const slice = buf_slice[0.. :0]; @@ -367,7 +367,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\} \\fn add(a: i32, b: i32) i32 { \\ if (a > 100) { - \\ suspend; + \\ suspend {} \\ } \\ return a + b; \\} @@ -407,7 +407,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\ var frame = @asyncCall(&bytes, {}, ptr, .{}); \\} \\fn other() callconv(.Async) void { - \\ suspend; + \\ suspend {} \\} ); @@ -424,7 +424,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\ await frame; \\} \\fn other() void { - \\ suspend; + \\ suspend {} \\} ); @@ -440,7 +440,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\ other(); \\} \\fn other() void { - \\ suspend; + \\ suspend {} \\} ); @@ -454,7 +454,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\ resume p; //bad \\} \\fn suspendOnce() void { - \\ suspend; + \\ suspend {} \\} ); @@ -1019,7 +1019,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\} \\ \\fn failing() anyerror!void { - \\ suspend; + \\ suspend {} \\ return second(); \\} \\ diff --git a/test/stage1/behavior/async_fn.zig b/test/stage1/behavior/async_fn.zig index 40269df5ec..0765eac7e8 100644 --- a/test/stage1/behavior/async_fn.zig +++ b/test/stage1/behavior/async_fn.zig @@ -18,9 +18,9 @@ test "simple coroutine suspend and resume" { } fn simpleAsyncFn() void { global_x += 1; - suspend; + suspend {} global_x += 1; - suspend; + suspend {} global_x += 1; } @@ -34,7 +34,7 @@ test "pass parameter to coroutine" { } fn simpleAsyncFnWithArg(delta: i32) void { global_y += delta; - suspend; + suspend {} global_y += delta; } @@ -50,7 +50,7 @@ test "suspend at end of function" { fn suspendAtEnd() void { x += 1; - suspend; + suspend {} } }; S.doTheTest(); @@ -74,11 +74,11 @@ test "local variable in async function" { fn add(a: i32, b: i32) void { var accum: i32 = 0; - suspend; + suspend {} accum += a; - suspend; + suspend {} accum += b; - suspend; + suspend {} x = accum; } }; @@ -102,7 +102,7 @@ test "calling an inferred async function" { } fn other() void { other_frame = @frame(); - suspend; + suspend {} x += 1; } }; @@ -129,7 +129,7 @@ test "@frameSize" { } fn other(param: i32) void { var local: i32 = undefined; - suspend; + suspend {} } }; S.doTheTest(); @@ -269,7 +269,7 @@ test "async function with dot syntax" { var y: i32 = 1; fn foo() callconv(.Async) void { y += 1; - suspend; + suspend {} } }; const p = async S.foo(); @@ -298,7 +298,7 @@ fn doTheAwait(f: anyframe->void) void { fn simpleAsyncFn2(y: *i32) callconv(.Async) void { defer y.* += 2; y.* += 1; - suspend; + suspend {} } test "@asyncCall with return type" { @@ -312,7 +312,7 @@ test "@asyncCall with return type" { fn afunc() i32 { global_frame = @frame(); - suspend; + suspend {} return 1234; } }; @@ -348,7 +348,7 @@ test "async fn with inferred error set" { fn failing() !void { global_frame = @frame(); - suspend; + suspend {} return error.Fail; } }; @@ -375,7 +375,7 @@ fn nonFailing() (anyframe->anyerror!void) { return &Static.frame; } fn suspendThenFail() callconv(.Async) anyerror!void { - suspend; + suspend {} return error.Fail; } fn printTrace(p: anyframe->(anyerror!void)) callconv(.Async) void { @@ -400,7 +400,7 @@ fn testBreakFromSuspend(my_result: *i32) callconv(.Async) void { resume @frame(); } my_result.* += 1; - suspend; + suspend {} my_result.* += 1; } @@ -421,7 +421,7 @@ test "heap allocated async function frame" { fn someFunc() void { x += 1; - suspend; + suspend {} x += 1; } }; @@ -454,7 +454,7 @@ test "async function call return value" { fn other(x: i32, y: i32) Point { frame = @frame(); - suspend; + suspend {} return Point{ .x = x, .y = y, @@ -487,7 +487,7 @@ test "suspension points inside branching control flow" { fn func(b: bool) void { while (b) { - suspend; + suspend {} result += 1; } } @@ -541,7 +541,7 @@ test "pass string literal to async function" { fn hello(msg: []const u8) void { frame = @frame(); - suspend; + suspend {} expectEqualStrings("hello", msg); ok = true; } @@ -566,7 +566,7 @@ test "await inside an errdefer" { fn func() void { frame = @frame(); - suspend; + suspend {} } }; S.doTheTest(); @@ -590,7 +590,7 @@ test "try in an async function with error union and non-zero-bit payload" { fn theProblem() ![]u8 { frame = @frame(); - suspend; + suspend {} const result = try other(); return result; } @@ -622,7 +622,7 @@ test "returning a const error from async function" { fn fetchUrl(unused: i32, url: []const u8) ![]u8 { frame = @frame(); - suspend; + suspend {} ok = true; return error.OutOfMemory; } @@ -967,7 +967,7 @@ test "@asyncCall with comptime-known function, but not awaited directly" { fn failing() !void { global_frame = @frame(); - suspend; + suspend {} return error.Fail; } }; @@ -977,7 +977,7 @@ test "@asyncCall with comptime-known function, but not awaited directly" { test "@asyncCall with actual frame instead of byte buffer" { const S = struct { fn func() i32 { - suspend; + suspend {} return 1234; } }; @@ -993,7 +993,7 @@ test "@asyncCall using the result location inside the frame" { fn simple2(y: *i32) callconv(.Async) i32 { defer y.* += 2; y.* += 1; - suspend; + suspend {} return 1234; } fn getAnswer(f: anyframe->i32, out: *i32) void { @@ -1095,7 +1095,7 @@ test "nosuspend function call" { } fn add(a: i32, b: i32) i32 { if (a > 100) { - suspend; + suspend {} } return a + b; } @@ -1170,7 +1170,7 @@ test "suspend in for loop" { global_frame = @frame(); var sum: u32 = 0; for (stuff) |x| { - suspend; + suspend {} sum += x; } global_frame = null; @@ -1197,7 +1197,7 @@ test "suspend in while loop" { global_frame = @frame(); defer global_frame = null; while (stuff) |val| { - suspend; + suspend {} return val; } return 0; @@ -1206,7 +1206,7 @@ test "suspend in while loop" { global_frame = @frame(); defer global_frame = null; while (stuff) |val| { - suspend; + suspend {} return val; } else |err| { return 0; @@ -1339,7 +1339,7 @@ test "async function passed 0-bit arg after non-0-bit arg" { fn bar(x: i32, args: anytype) anyerror!void { global_frame = @frame(); - suspend; + suspend {} global_int = x; } }; @@ -1361,7 +1361,7 @@ test "async function passed align(16) arg after align(8) arg" { fn bar(x: u64, args: anytype) anyerror!void { expect(x == 10); global_frame = @frame(); - suspend; + suspend {} global_int = args[0]; } }; @@ -1383,7 +1383,7 @@ test "async function call resolves target fn frame, comptime func" { fn bar() anyerror!void { global_frame = @frame(); - suspend; + suspend {} global_int += 1; } }; @@ -1406,7 +1406,7 @@ test "async function call resolves target fn frame, runtime func" { fn bar() anyerror!void { global_frame = @frame(); - suspend; + suspend {} global_int += 1; } }; @@ -1430,7 +1430,7 @@ test "properly spill optional payload capture value" { fn bar() void { global_frame = @frame(); - suspend; + suspend {} global_int += 1; } }; @@ -1466,13 +1466,13 @@ test "handle defer interfering with return value spill" { fn bar() anyerror!void { global_frame1 = @frame(); - suspend; + suspend {} return error.Bad; } fn baz() void { global_frame2 = @frame(); - suspend; + suspend {} baz_happened = true; } }; @@ -1497,7 +1497,7 @@ test "take address of temporary async frame" { fn foo(arg: i32) i32 { global_frame = @frame(); - suspend; + suspend {} return arg + 1234; } @@ -1520,7 +1520,7 @@ test "nosuspend await" { fn foo(want_suspend: bool) i32 { if (want_suspend) { - suspend; + suspend {} } return 42; } @@ -1569,11 +1569,11 @@ test "nosuspend on async function calls" { // }; // const S1 = struct { // fn c() S0 { -// suspend; +// suspend {} // return S0{}; // } // fn d() !S0 { -// suspend; +// suspend {} // return S0{}; // } // }; @@ -1591,11 +1591,11 @@ test "nosuspend resume async function calls" { }; const S1 = struct { fn c() S0 { - suspend; + suspend {} return S0{}; } fn d() !S0 { - suspend; + suspend {} return S0{}; } };