Description
Problem
Comptime emulation will sometimes assign different types to the same syntax as runtime code:
A. if
/switch
don't perform peer type resolution for comptime
arguments, affecting downstream code:
comptime { // works if we comment out `comptime`
const base: usize = 15;
var y: union(enum) { offset: usize, unknown: void } = .{ .offset = 12 };
const maybe_x = switch(y) {
.offset => |off| off + base,
.unknown => null,
};
if (maybe_x) |x| { // error: expected optional type, found 'usize'
...
}
}
B. In a comptime context, all tuple fields are all inferred to be comptime
, making them suddenly immutable:
comptime { // works if we comment out `comptime`
var x: usize = 1;
var y = .{ x, x + 1 };
y = .{ 3, 4 }; // error: value stored in comptime field does not match the default value of the field
}
(A) is particularly problematic, since it means that Zig cannot improve its semantic analysis like Andrews mentions here:
... zig reserves the right to make the if static if it can prove the value is compile-time known (which it should be able to do in this example with more sophisticated semantic analysis).
Unfortunately if we don't fix this problem, improving semantic analysis is a breaking change for users.
So what can be done about it?
Proposed solution
A value is "semantically comptime" if it is:
- a literal constant, OR
- labeled with
comptime
at its declaration site, OR - the result of a
comptime ...
expression, OR - the result of an operator/builtin with semantically comptime inputs, OR
- a
const
whose initialization expression is semantically comptime
In a runtime context, all comptime-known values are also semantically comptime. However in a comptime context, there are values that are comptime-known but not semantically comptime. For example:
const x = comptime b: { // x is both comptime-known and semantically comptime
var y: usize = 1; // comptime-known, but not semantically comptime
break :b y;
};
A. Branching in switch
/if
When branching on a comptime-known but not semantically comptime value in a switch
/if
/orelse
/catch
or a non-inline while
/for
, Sema must perform "runtime-equivalent analysis" as follows:
- Semantically comptime values (from before branch entry) are treated as immutable.
- Peer type resolution is performed the same way it would be at runtime.
- In dead branches:
- Comptime-known, but not semantically comptime values are treated as runtime values.
- No runtime code is generated during this analysis.
B. Tuple fields
Tuple fields should only be inferred to be comptime
fields if they are initialized with a semantically comptime value.
This would resolve #12261. The existing tuple semantics also accidentally make the rejected #7539 implementable in userspace, which is resolved by this proposal.