-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
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
RLS for LHS of shift operands #16489
Comments
There's another idea I think we should consider which may also solve the underlying issue; supporting bitshift on comptime_int. I think you can come up with reasonable set of well-defined semantics for bitshifting comptime_int if you think of it as having an infinite number of bits available going to the left (not going to the right). The difference between positive/negative values is that this infinite set of digits are either all 0s or 1s respectively. With this solution you get to keep a little more information as you're evaluating an expression. Issues where a developer uses the wrong type become compile-errors instead of runtime errors because zig's comptime-analyzer will know whether a P.S. I just noticed your first example said |
Could the truncation before the upcast/coercion be achieved with a one-liner like this const x: u64 = @as(u8, 255 << 1); or would one need an intermediate result: const x: u64 = x: {
const intermediate: u8 = 255 << 1;
break :x intermediate
}; |
Did you mean to have a |
After looking through some of my bitshifting code, I seem to be using expressions like this at the moment: return @intCast(last_special_size + i * step_size_base * 1 << size_shift); where |
Rare or not, asking specifically for one type and getting another is really, realy not good, in my opinion. |
Shifting
Yes.
Oops, yeah I did, good catch.
Yeah, that's how I'd do it. To be honest, I'm kind of skeptical about comptime-int-downcasts-via-coercion as a feature, but that's a separate issue.
Is @zzyxyzz
Sure, but I really don't think this behavior can be considered "asking specifically for one type" in any reasonable way. In the cases where this could trigger a bug, the relevant line of code is either mentioning a single type, which is the type which will now be used, or it's mentioning two types, and we're just changing which should be used. i.e: const x: u64 = @as(u8, 255) << 2; // why should this be considered "asking for" u8 rather than u64?
const y: u8 = something;
// ...
const z: u64 = y << 2;
// this one's even worse - the definition of y could be 100 lines earlier
// meanwhile the line of the shift explicitly says the expression results in a u64 |
Because of the explicit I can see your point as well, but still, the proposed solution breaks the least surprise principle pretty badly here. If this proposal moves ahead, I'd recommend to at least make the use of |
|
I think this makes sense for readability. Without this, unless the reader is aware of/remembers precisely how RLS and coercion work for various operations I agree that shifts left shifts and wrapping/saturating operations. I'm sure there is a sensible/consistent general rule to achieve this though. |
Hmm, I'll have to check - the code passes tests but if it is being parsed that way something does seem off. Edit: I should have thought about it for a few moments longer, multiplication and |
Proposal
In status quo, it is common to have to write code like this:
This is because the LHS of a shift operand never has any known result type, so often must be explicitly provided, which (for a constant LHS) probably means using
@as
.The type returned by a shift operation is (in status quo) always the same as the type of its LHS. That means we could change the language to apply RLS.
The rule here is simple: if
a << b
ora >> b
has a result typeT
, forward that result type ontoa
. If there is no result type, work as normal. This would make, for instance, the following work:Breaking
This is almost a non-breaking change. There are two ways I can see for existing code to break.
The first is related to comptime integer coercions. Consider this code:
Here, the type of the shift expression is a
u64
, but it has value128
, so (since it is comptime-known) can coerce safely down to au8
.Under the new semantics, the result type of
u8
will be forwarded to the LHS, resulting in this code emitting a compile error since256
cannot coerce to au8
. The failure mode for this breaking change is a compile error.The second way code could break is the other way around: upcasts. Consider:
In status quo, the value stored in
x
is 254, because the shifted top bit is discarded (due to the shift being on au8
). Under the new semantics, the LHS is coerced up to au64
, making the result value instead510
. This change could cause incorrect behavior in a program. However, I anticipate this pattern of depending on truncation of a shift but immediately coercing it up afterwards is rare. Indeed, when presented differently, it looks more like this proposal is solving a footgun:It's not great that you currently have to look back at the type of
x
to understand this behavior: under the proposal it can instead be gained through local information, which is probably the intent in this snippet. Only if type inference is used fory
does the type ofx
matter.The text was updated successfully, but these errors were encountered: