-
Notifications
You must be signed in to change notification settings - Fork 322
Implement throw & catch statements #6916
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
Implement throw & catch statements #6916
Conversation
It already existed in the IR, so only parsing, checking and lowering was missing.
Thank for taking initiative to continue the error handling implementation! I think we can just made the current scope not visible to catch block for simplicity. |
Likely very broken.
I noticed one "gotcha" in the existing design of the enum MyError
{
Failure1,
Failure2
};
float f() throws MyError
{
// Problem: the value of Failure1 is 0, so this throw gets
// interpreted as a valid return value.
throw MyError.Failure1;
}
export __extern_cpp int main()
{
{
let res = try f();
printf("%f\n", res); // This is executed, with undefined value for 'res'
catch(MyError err)
{ // This is not executed.
printf("Caught an error %d\n", err);
}
}
return 0;
} Because I'd expect I'm also still missing a check for |
We want to reserve 0 for success so that it works with COM interface convention. The problem with using max instead of 0 for success is you will lose forward binary compatibility. I think we should only allow enum types that inherit Error type to be used as error type, where Error is just a builtin enum that declares a general success code (0) and a general failure code (1). |
Inheriting from an enum doesn't seem to exist as a feature yet. Should I implement that? It doesn't seem terribly difficult, assuming it just means inheriting the underlying type and cases. I have a couple of other options in mind as well: 1. interface IError
{
bool isSuccess();
} The above could allow non-integer error types as well, but admittedly doesn't enforce adherence to the COM interface convention. 2. |
As those may actually not be available at that point.
Whenever possible, we should solve the problem with the type system instead of through new modifiers or attributes. We could add support for enum type inheritance or IError type. I think we will need something like IError in the long term so it is a good idea to consider implementing it now. |
It may not exist if the function returns void, but getting the error value is still legitimate.
... To make SPIR-V happy.
54d24f1
to
2d97ba7
Compare
If we represent Result as Result<T, E:IError>, then the witnesses should be referenced in an IRResultType as the third operand, right? |
I am fine with changing the syntax to try{}catch, but @tangent-vector may want to weight in here as she did the original design and I think there should be a legit reason that this is preferred. |
I retried without KeepAliveDecoration and realized that I misremembered the details. The witness table itself is not removed from IR, but all of the functions inside it are removed before Result type lowering. |
I don’t fully remember my reasons for avoiding the traditional try/catch syntax, but a large part of it was probably just because the traditional syntax adds a pointless extra level of nesting in many cases, which makes code uglier than it needs to be. I’d be fine with supporting (or switching to) the traditional try/catch form, since that’s what our users are almost all going to be more familiar with it. In a vacuum, I’d claim that a much better way to design a syntax for “catching” exceptions would be to write handlers at the point where they come into scope, and have them be in effect for the rest of the scope, just like any other binding. In toy language projects I’ve used
That approach avoids the introduction of a nested scope, marks the specific operations/lines that might throw, and makes it so that you can easily keep handlers close to the code that might branch to them. (I’m aware that the historical reason for why I support having an The original idea was that For the initial version of error handling, we were primarily focused on making the COM |
I like If we get rid of the success values, then I don't really see the point of keeping |
I've now implemented the general case tagged union version of I'm a bit out of the loop on the HRESULT stuff. I assume this is about
Regarding the syntax, I feel that adding an extra level of nesting is often necessary anyway, because the catch must occur last in scope: int val;
{
val = try getDifficultValue();
catch(err: Error)
{
val = getFallbackValue();
}
}
// continue doing something with val The classical style would actually have less indented code in total in these cases, as the int val;
do // using `do` here to match Swift and avoid overlap with the `try` keyword below.
{
val = try getDifficultValue();
}
catch(err: Error)
{
val = getFallbackValue();
}
// continue doing something with val Still, you're completely right that there are also many cases where this isn't needed, and the current syntax does have less nesting in those cases. I don't have any strong feelings towards either option, and supporting both also seems viable, but it might seem "messy" if there's two ways to do a very similar thing. |
If |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good. But we want to have an official opinon on whether error type should include a value for success.
Reading the implmementation, I am leaning towards that we just support Do we want to also support a catch-all clause, e.g. I think if we call |
I initially thought so as well, and thought that this would be super convenient. However, I reckon the limitation is given in the proposal for a very good reason. It's hard to define how int a = try something();
int b = try other();
catch(err: MyError)
{
...
}
int c = a + b; Now, the block introducing I'm also in favor of a catch-all clause, I think it would be simple to implement and could often be practical. |
Can you also create a PR to the spec repo to update the error handling proposal so it reflects what’s being implemented here? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me! This is a significant enhancement to the language. Thank you for finalizing and implementing the error handling feature for Slang!
The current status of the error handling mechanism is that
throws
in function signatures andtry
expressions work,throw
only exists on the IR level (to allowtry
to re-throw) andcatch
is completely unimplemented.This PR finishes
throw
such that it can be used directly, and aims to implementcatch
.WIP: Might take a while. Finishing up
throw
seemed simple as it was mostly done already, butcatch
is a bit more complex. One non-trivial issue is the lifetime of variables that may be referenced fromcatch
: