Thanks to visit codestin.com
Credit goes to github.com

Skip to content

WIP: 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

Draft
wants to merge 21 commits into
base: master
Choose a base branch
from

Conversation

juliusikkala
Copy link
Contributor

The current status of the error handling mechanism is that throws in function signatures and try expressions work, throw only exists on the IR level (to allow try to re-throw) and catch is completely unimplemented.

This PR finishes throw such that it can be used directly, and aims to implement catch.

WIP: Might take a while. Finishing up throw seemed simple as it was mostly done already, but catch is a bit more complex. One non-trivial issue is the lifetime of variables that may be referenced from catch:

{
    let f = try func(); // throws SomeError
    let g = otherFunc();
    catch(e: SomeError) // btw, do we want to support "traditional" syntax here, with `catch(SomeError e)`?
    {
        // Should we be able to refer to `g` here? I don't think so.
        // So `catch` should probably either be unable to reference anything from its scope,
        // or only variables declared before the first `try` expression.
        // The former would probably be easier to handle.
    }
}

It already existed in the IR, so only parsing, checking and lowering was
missing.
@csyonghe
Copy link
Collaborator

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.
@juliusikkala
Copy link
Contributor Author

juliusikkala commented Apr 27, 2025

I noticed one "gotcha" in the existing design of the Result<T,E> type. It appears that it requires the error type to be an integer, and the zero value is reserved for success. I didn't yet add proper checking for the integer-ness. This is also very easy to make subtle mistakes with:

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 enums specifically being the most common way to create error types, maybe it'd be safer to reserve the maximum value of the integer type instead?

I'm also still missing a check for catch being the last statement in the block. I initially thought this wasn't necessary, but it's possible to declare variables after try and before catch that should be visible after catch if it isn't the last statement in the block.

@csyonghe
Copy link
Collaborator

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).

@juliusikkala
Copy link
Contributor Author

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.
A new attribute [ErrorType] for enums, just like [UnscopedEnum] or [Flags]. Since there already are attributes that change how enums behave, this would fit right in line. It would make it easier to add a compiler error to prevent the user from accidentally overlapping an enum case with a success value. It would also allow the error types to not include an explicit success value, making it impossible to throw MyError.Success; and observe uninitialized return values.

As those may actually not be available at that point.
@csyonghe
Copy link
Collaborator

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.

@juliusikkala juliusikkala added pr: new feature pr: non-breaking PRs without breaking changes labels Apr 30, 2025
@juliusikkala juliusikkala force-pushed the add-throw-statement branch from 54d24f1 to 2d97ba7 Compare May 4, 2025 17:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
pr: new feature pr: non-breaking PRs without breaking changes
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants