stage1: Require a block after suspend

Closes #8603
This commit is contained in:
LemonBoy 2021-04-24 10:20:16 +02:00
parent 4ec6d174ad
commit 0aede1a8fc
10 changed files with 100 additions and 109 deletions

View File

@ -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#}
<p>
@ -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#}

View File

@ -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;

View File

@ -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,
},
});

View File

@ -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);
}

View File

@ -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);

View File

@ -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));
}

View File

@ -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;

View File

@ -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",

View File

@ -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();
\\}
\\

View File

@ -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{};
}
};