Commit ea1734773b

Andrew Kelley <andrew@ziglang.org>
2019-08-18 01:47:49
add compile error for async frames depending on themselves
1 parent 57b90d2
src/all_types.hpp
@@ -1279,6 +1279,12 @@ struct ZigTypeOpaque {
 struct ZigTypeFnFrame {
     ZigFn *fn;
     ZigType *locals_struct;
+
+    // This is set to the type that resolving the frame currently depends on, null if none.
+    // It's for generating a helpful error message.
+    ZigType *resolve_loop_type;
+    AstNode *resolve_loop_src_node;
+    bool reported_loop_err;
 };
 
 struct ZigTypeAnyFrame {
src/analyze.cpp
@@ -5197,6 +5197,27 @@ static ZigType *get_async_fn_type(CodeGen *g, ZigType *orig_fn_type) {
     return fn_type;
 }
 
+static void emit_error_notes_for_type_loop(CodeGen *g, ErrorMsg *msg, ZigType *stop_type,
+        ZigType *ty, AstNode *src_node)
+{
+    ErrorMsg *note = add_error_note(g, msg, src_node,
+        buf_sprintf("when analyzing type '%s' here", buf_ptr(&ty->name)));
+    if (ty == stop_type)
+        return;
+    switch (ty->id) {
+        case ZigTypeIdFnFrame: {
+            ty->data.frame.reported_loop_err = true;
+            ZigType *depending_type = ty->data.frame.resolve_loop_type;
+            if (depending_type == nullptr)
+                return;
+            emit_error_notes_for_type_loop(g, note, stop_type,
+                depending_type, ty->data.frame.resolve_loop_src_node);
+        }
+        default:
+            return;
+    }
+}
+
 static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {
     Error err;
 
@@ -5206,6 +5227,20 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {
     ZigFn *fn = frame_type->data.frame.fn;
     assert(!fn->type_entry->data.fn.is_generic);
 
+    if (frame_type->data.frame.resolve_loop_type != nullptr) {
+        if (!frame_type->data.frame.reported_loop_err) {
+            frame_type->data.frame.reported_loop_err = true;
+            ErrorMsg *msg = add_node_error(g, fn->proto_node,
+                    buf_sprintf("'%s' depends on itself", buf_ptr(&frame_type->name)));
+            emit_error_notes_for_type_loop(g, msg,
+                    frame_type,
+                    frame_type->data.frame.resolve_loop_type,
+                    frame_type->data.frame.resolve_loop_src_node);
+            emit_error_notes_for_ref_stack(g, msg);
+        }
+        return ErrorSemanticAnalyzeFail;
+    }
+
     switch (fn->anal_state) {
         case FnAnalStateInvalid:
             return ErrorSemanticAnalyzeFail;
@@ -5299,6 +5334,10 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {
             return ErrorSemanticAnalyzeFail;
         }
 
+        ZigType *callee_frame_type = get_fn_frame_type(g, callee);
+        frame_type->data.frame.resolve_loop_type = callee_frame_type;
+        frame_type->data.frame.resolve_loop_src_node = call->base.source_node;
+
         analyze_fn_body(g, callee);
         if (callee->anal_state == FnAnalStateInvalid) {
             frame_type->data.frame.locals_struct = g->builtin_types.entry_invalid;
@@ -5308,8 +5347,6 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {
         if (!fn_is_async(callee))
             continue;
 
-        ZigType *callee_frame_type = get_fn_frame_type(g, callee);
-
         IrInstructionAllocaGen *alloca_gen = allocate<IrInstructionAllocaGen>(1);
         alloca_gen->base.id = IrInstructionIdAllocaGen;
         alloca_gen->base.source_node = call->base.source_node;
@@ -5378,9 +5415,13 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {
                 continue;
             }
         }
+
+        frame_type->data.frame.resolve_loop_type = child_type;
+        frame_type->data.frame.resolve_loop_src_node = instruction->base.source_node;
         if ((err = type_resolve(g, child_type, ResolveStatusSizeKnown))) {
             return err;
         }
+
         const char *name;
         if (*instruction->name_hint == 0) {
             name = buf_ptr(buf_sprintf("@local%" ZIG_PRI_usize, alloca_i));
test/compile_errors.zig
@@ -2,6 +2,42 @@ const tests = @import("tests.zig");
 const builtin = @import("builtin");
 
 pub fn addCases(cases: *tests.CompileErrorContext) void {
+    cases.add(
+        "indirect recursion of async functions detected",
+        \\var frame: ?anyframe = null;
+        \\
+        \\export fn a() void {
+        \\    _ = async rangeSum(10);
+        \\    while (frame) |f| resume f;
+        \\}
+        \\
+        \\fn rangeSum(x: i32) i32 {
+        \\    suspend {
+        \\        frame = @frame();
+        \\    }
+        \\    frame = null;
+        \\
+        \\    if (x == 0) return 0;
+        \\    var child = rangeSumIndirect(x - 1);
+        \\    return child + 1;
+        \\}
+        \\
+        \\fn rangeSumIndirect(x: i32) i32 {
+        \\    suspend {
+        \\        frame = @frame();
+        \\    }
+        \\    frame = null;
+        \\
+        \\    if (x == 0) return 0;
+        \\    var child = rangeSum(x - 1);
+        \\    return child + 1;
+        \\}
+    ,
+        "tmp.zig:8:1: error: '@Frame(rangeSum)' depends on itself",
+        "tmp.zig:15:33: note: when analyzing type '@Frame(rangeSumIndirect)' here",
+        "tmp.zig:26:25: note: when analyzing type '@Frame(rangeSum)' here",
+    );
+
     cases.add(
         "non-async function pointer eventually is inferred to become async",
         \\export fn a() void {