Supply result type to operand of `try`
Short lil proposal today!
In general, syntax forms which "extract" components of an operand cannot provide result types to said operands. For instance, a field access x.foo clearly cannot provide a result type to the expression x, as there may be arbitrarily many aggregate types with a field named foo. In general, the same is true of error handling: for instance, x catch ... cannot provide a result type to x even if the overall expression has a result type, because while this type may contain sufficient information to determine the payload type, we cannot determine the error type.
However, there is one interesting exception here! The try operator could, in theory, provide a correct result type to its operand. If an expression try x has result type T, then the sub-expression x can be assigned result type E!T, where E is the error set of the current function. Because the behavior of try is more limited than that of e.g. catch, we may not know the error set type that the expression will actually return, but we do know that it must be coercible to the error set of this function, and we lose no information from applying this rule (the result would have been coerced to this type by the ret_node ZIR in the error path regardless). This coercion will correctly handle growing an IES, both runtime and ad-hoc. The more I think about it, the more this seems to me like a no-brainer.
Many thanks to @silversquirl for suggesting this to me :D
Speaking as a bit of a layman to the compiler jargon, might I ask what type of code this would allow which currently is not allowed?
@InKryption the usecase I was initially considering when suggesting this was #9938. With decl literals and this both implemented, you'd be able to do stuff like const foo: ArrayList = try .initCapacity(allocator, 10); without needing to special-case try in the decl literal logic.
Other usecases are a bit more niche, but one example is const foo: u32 = try if (cond) 3 else error.Foo;
That example is a bit contrived though haha
A slightly less esoteric use-case today is try @errorCast(xyz) in a function with a concrete error set.
Proposal seems fine, let's see if the implementation turns up any complications.
Related:
- #2761
There is a really esoteric annoying case, which you'd need something like this to repro:
const my_slice: []const u32 = &try (if (foo) error.Bar else .{@intCast(bar)});
We can't forward slice ref result locations through try because the operand needs to be a *const E![n]u32 and we don't know n; we usually lower this case by saying we need a type which when refd gives a []const u32, but there's no meaningful equivalent here ([]const E!u32 would be wrong, we only want one possible error).
This is ridiculously esoteric. We could solve it in the future by changing how we represent ref result locations in the compiler, but for now I'm just making this drop the result type. If someone actually hits this in the wild, I will visit a clothing store and ingest every available hat.