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

Proposal: improved semantics for noreturn-like types #15909

Open
mlugg opened this issue May 30, 2023 · 4 comments
Open

Proposal: improved semantics for noreturn-like types #15909

mlugg opened this issue May 30, 2023 · 4 comments
Labels
proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Milestone

Comments

@mlugg
Copy link
Member

mlugg commented May 30, 2023

This is in a sense a continuation of #3257. That issue laid some of the groundwork for the current status of noreturn and similar types, but there are a few things missing. Also, thanks to @ifreund for helping me figure out some of the details of this.

Lots of types can be constructed which are similar to noreturn. In type theory terms, they are "empty types": types for which no values exist. I'll call these types "noreturn-like". Here's an exhaustive list of types which should be considered noreturn-like (note the special cases around pointers mentioned at the end):

  • noreturn
  • enum {}
  • enum(T) {} for any T
  • error {}
  • union / extern union where all fields are noreturn-like (including the case where the union has no fields)
  • tagged unions where the tag type is noreturn-like or all fields are noreturn-like
  • structs containing any noreturn-like field
  • E!T where E and T are both noreturn-like (so, error{}!T)
  • [n]T / @Vector(n, T) where T is noreturn-like and n != 0
  • *T / [*]T (and any qualifiers) where T is noreturn-like
    • []T has the value &.{} so is not noreturn-like, but rather zero-bit OPV
    • [*c]T has the value null so is not noreturn-like. In principle it's zero-bit OPV, but this is a footgun, so instead is disallowed.

Currently, most of these aren't "truly" considered noreturn-like. Such types should terminate semantic analysis when found (akin to unreachable). Additionally, undefined should not coerce to noreturn-like types, to avoid being able to actually "construct" them without terminating analysis.

We currently have the restriction that (at function scope) var x: T = ..., where T is noreturn-like, is a compile error. This, however, is inconsistent - const x: T = ... is allowed, and moreover, so is var x = @as(T, ...). I propose that we remove this needless restriction. Allocs for noreturn-like types can be special-cased to emit no AIR instructions, and analysis will terminate before we attempt to store to one, as by that point we will have had an instruction whose result type is the child type.

Note, however, that global vars may not be of a noreturn-like type, nor may a value of a noreturn-like type be exported or marked extern. This is a compile error.

One last notable change in semantics here is that switching on a noreturn-like enum is no longer supported in the same way that it is in status quo. The idea currently is that you turn such an enum into the true noreturn type using an empty switch, switch (e) {}. This code will still be accepted under this proposal, but for a different reason: it's never analyzed! The moment e is created, semantic analysis terminates, since we reached a noreturn instruction.

@ifreund ifreund added the proposal This issue suggests modifications. If it also has the "accepted" label then it is planned. label May 30, 2023
@ifreund ifreund added this to the 0.12.0 milestone May 30, 2023
@Vexu
Copy link
Member

Vexu commented May 30, 2023

Duplicate/related #13807

@ifreund
Copy link
Member

ifreund commented May 30, 2023

I think we need to reconsider the proposed semantics of pointers to noreturn types. As @jacobly0 pointed out, a pointer isn't required to point to valid memory unless it is dereferenced. As I see it, the logical extension of those semantics for pointers to noreturn types is that a pointer of type *noreturn may exist with arbitrary value. However, dereferencing a pointer of type *noreturn is a compile error as that would produce a value of type noreturn (which does not exist since noreturn is an empty type).

This also resolves an inconsistency in the current proposal when viewing a slice as a

struct { ptr: [*]noreturn, len: usize }

By the struct rule and originally proposed pointer rule this should be a noreturn type. However, there is a valid possible value of this type, the empty slice.

Another issue with the current semantics that @jacobly0 brought up is that determining if a pointer is noreturn equivalent or not could cause dependency loops, e.g. due to needing to analyze struct field types in order to determine if a struct is noreturn equivalent.

To summarize for all pointer types:

  • *T, [*]T, [*c]T are not empty types/"noreturn-like"
  • Derefencing a pointer to noreturn-like type is a complie error.
  • []T is not a noreturn-like type and is treated like struct { ptr: [*]T, len: usize }
  • A sentinel terminated pointer to a noreturn-like type cannot be written as it requires a sentinel value as part of the type.

@andrewrk
Copy link
Member

This proposal needs a set of test cases to act as acceptance criteria and so we have some very specific semantics to decide between.

mlugg added a commit to mlugg/zig that referenced this issue May 31, 2023
This is a bit odd, because this value doesn't actually exist:
see ziglang#15909. This gets all the empty enum/union behavior tests passing.

Also adds an assertion to `Sema.analyzeBodyInner` which would have
helped figure out the issue here much more quickly.
jacobly0 pushed a commit to jacobly0/zig that referenced this issue May 31, 2023
This is a bit odd, because this value doesn't actually exist:
see ziglang#15909. This gets all the empty enum/union behavior tests passing.

Also adds an assertion to `Sema.analyzeBodyInner` which would have
helped figure out the issue here much more quickly.
andrewrk pushed a commit that referenced this issue May 31, 2023
This is a bit odd, because this value doesn't actually exist:
see #15909. This gets all the empty enum/union behavior tests passing.

Also adds an assertion to `Sema.analyzeBodyInner` which would have
helped figure out the issue here much more quickly.
andrewrk pushed a commit that referenced this issue Jun 4, 2023
This is a bit odd, because this value doesn't actually exist:
see #15909. This gets all the empty enum/union behavior tests passing.

Also adds an assertion to `Sema.analyzeBodyInner` which would have
helped figure out the issue here much more quickly.
jacobly0 pushed a commit to jacobly0/zig that referenced this issue Jun 11, 2023
This is a bit odd, because this value doesn't actually exist:
see ziglang#15909. This gets all the empty enum/union behavior tests passing.

Also adds an assertion to `Sema.analyzeBodyInner` which would have
helped figure out the issue here much more quickly.
andrewrk pushed a commit that referenced this issue Jun 11, 2023
This is a bit odd, because this value doesn't actually exist:
see #15909. This gets all the empty enum/union behavior tests passing.

Also adds an assertion to `Sema.analyzeBodyInner` which would have
helped figure out the issue here much more quickly.
@andrewrk andrewrk modified the milestones: 0.13.0, 0.12.0 Jul 9, 2023
@andrewCodeDev
Copy link

andrewCodeDev commented Sep 13, 2023

This is a small nitpick, but I'd suggest a name like "vacuous types" instead of noreturn types. Because, you can in fact return them, they just happen to be empty :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
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

5 participants