Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new ast-check compile error: control flow implicitly returns from non-void function #10420

Open
andrewrk opened this issue Dec 27, 2021 · 9 comments
Labels
breaking Implementing this issue could cause existing code to no longer compile or have different behavior. proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Milestone

Comments

@andrewrk
Copy link
Member

andrewrk commented Dec 27, 2021

Functions end with an implicit return; which implicitly returns a void value. Therefore, functions which return void have no requirement to have an explicit return expression:

fn foo(c: bool) void {
    if (c) {
        bar();
    }
}

However functions which do not return void require a return statement or there will be an error such as this:

fn foo(c: bool) i32 {
    if (c) {
        return 1234;
    }
}
./test.zig:1:21: error: expected type 'i32', found 'void'
fn foo(c: bool) i32 {
                    ^

Currently this error occurs during type checking, and so it won't trigger if the function is not referenced. It also will allow something like this:

const MyVoid = void;
fn foo(c: bool) MyVoid {
    if (c) {
        bar();
    }
}

Again, no error on this one.

This proposal is to add an additional check into zig ast-check (also known as AstGen). It makes the rules a bit stricter; functions which do not have a trivial void return type (literally the identifier void as the return type; no detection of aliasing) are required to terminate the function body block with an expression of type noreturn. This could be return or it could be, for example, unreachable, or it could be a while(true) {} loop.

The above example (with MyVoid) would become:

./test.zig:6:1: error: control flow implicitly returns from non-void function
}
^

This compile error is directly related to all the unreachable code compile errors done by ast-check.


Additionally accepted amendment to the proposal

@andrewrk andrewrk added breaking Implementing this issue could cause existing code to no longer compile or have different behavior. proposal This issue suggests modifications. If it also has the "accepted" label then it is planned. accepted This proposal is planned. labels Dec 27, 2021
@andrewrk andrewrk added this to the 0.10.0 milestone Dec 27, 2021
@andrewrk andrewrk added the contributor friendly This issue is limited in scope and/or knowledge of Zig internals. label Dec 27, 2021
@ifreund
Copy link
Member

ifreund commented Dec 27, 2021

I think we should amend this proposal to allow implicit returns in functions with return type noreturn. For example:

fn fatal() noreturn {
    std.log.err("something bad happened, can't recover\n", .{});
    std.os.exit(1);
}

@andrewrk
Copy link
Member Author

andrewrk commented Dec 27, 2021

Ah, the existence of noreturn functions complicates this a bit, will remove the accepted label to think about it some more. Without type-checking, zig must assume that any function might be a noreturn function.

fn foo(c: bool) i32 {
    if (c) {
        return 1234;
    }
    exit(1);
}

This code should be allowed, for example.

@andrewrk andrewrk removed contributor friendly This issue is limited in scope and/or knowledge of Zig internals. accepted This proposal is planned. labels Dec 27, 2021
@rohlem
Copy link
Contributor

rohlem commented Jan 4, 2022

the existence of noreturn functions complicates this a bit

Here's a proposal an idea: Require the user to explicitly mark noreturn function calls by following them with unreachable:

fn foo(c: bool) i32 {
    if (c) {
        return 1234;
    }
    exit(1);
    unreachable; // this clearly communicates intent, to both early compiler stages, and readers!
}

I think that adequately solves this problem?
Though honestly, I would really like to advocate for this (if proposals were still allowed) from a stylistic viewpoint alone.

@ifreund
Copy link
Member

ifreund commented Jan 4, 2022

@rohlem then you don't get a compile error at the call site if, for example, the return type of exit() is changed to void.

@rohlem
Copy link
Contributor

rohlem commented Jan 4, 2022

@ifreund Maybe we could introduce such a compile error?
To me unreachable sounds like the natural tool for signaling unreachable code.
IIUC, you would suggest something to the effect of @compileErrorIfReachable() (obviously with a better name)? - EDIT: Here I'm naively hoping that evaluating its reachability analysis could be sufficiently postponed, don't know about the hurdles of implementing that.

@ifreund
Copy link
Member

ifreund commented Jan 4, 2022

IIUC, you would suggest something to the effect of @compileErrorIfReachable() (obviously with a better name)?

We have this, it's called @compileError() :P

@ifreund
Copy link
Member

ifreund commented Jan 4, 2022

I'm naively hoping that evaluating its reachability analysis could be sufficiently postponed, don't know about the hurdles of implementing that.

Yeah, that's not how things work in practice. Unreachable code errors MUST happen in AstGen (i.e. before semantic analysis) as branching at comptime allows arbitrary code to be only reachable when targeting a certain OS for example.

@rohlem
Copy link
Contributor

rohlem commented Jan 4, 2022

Unreachable code errors MUST happen in AstGen

@ifreund Why can't we emit a simple "unreachable poison" AstNode (-> ZIR -> AIR)? (Special-case it if normal unreachable is not fit for this use case.)
Later in AIR, we know types and can determine whether to discard it (because we're in a dead code path) or error at this later stage.

I realize that this means the problem isn't isolated to be fully-verified during AstGen.
However, it would still allow the proposed ast-check to be implemented, with later validation whether the user-provided unreachable turned out true.
If it didn't, our function's missing a return value, so that's another problem we already need to deal with.
(If the user wanted no return value, that should be written as return undefined; anyway.)


EDIT: If what I'm suggesting here is basically already implemented or planned in later stages anyway, then you can ignore these details and I'll yield to my original suggestion that I'd prefer noreturn statements (except for explicit return) to be explicitly followed by unreachable; statements.

@rohlem
Copy link
Contributor

rohlem commented Sep 22, 2023

Ah, the existence of noreturn functions complicates this a bit, will remove the accepted label to think about it some more. Without type-checking, zig must assume that any function might be a noreturn function.

I believe this would be unblocked by #17234 .

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
breaking Implementing this issue could cause existing code to no longer compile or have different behavior. proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Projects
None yet
Development

No branches or pull requests

3 participants