Skip to content
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

Support Control Flow type Refinements for "break" and "continue" statements #1004

Conversation

AmberGraceSoftware
Copy link
Contributor

@AmberGraceSoftware AmberGraceSoftware commented Aug 6, 2023

Fixes: #913

This PR adds support for type refinements around guard clauses that use break and continue statements inside a loop, similar to how guard clauses with return is supported.

I had some free time today, so I figure I'd give a shot at a naïve fix for this at the very least.


Resulting Change:

Luau now supports type refinements within loops where a continue or break guard clause was used.
For example:

for _, object in objects :: {{value: string?}} do
    if not object.value then
        continue
    end
    local x: string = object.value -- OK; Used to emit "Type 'string?' could not be converted into 'string'"
end

ControlFlow enum

I added some bit flags to the ControlFlow enum, as it looks like was defined in an incomplete/imprecise way. There are inline comments in the ControlFlow.h file that you are free to remove; I added them for the sake of documenting my reasoning.

image

The changes to the ControlFlow enum make it more explicit that edge cases of mixed exits need to be considered in future additions to control flow analysis.

A "MixedExit" control flow looks something like this, where a conditional always has an exit, but this exit might be to the nearest loop scope, or to the nearest function scope:

if condition then
    break
else
    return
end

A "MixedFunctionExit" is specifically a mixed exit that always exits the nearest function scope:

if condition then
    return
else
    error("...")
end

A "MixedLoopExit" is specifically a mixed exit that always exits the nearest loop scope:

if condition then
    break
else
    continue
end

It looks like this proliferation was what the previous author of this type refinement code was getting at, but didn't completely represent. For example, calling TypeInfer::check() on an if statement would always return ControlFlow::Return for any mixed function return, even if that mixed return might actually include an exception rather than a return.

Probably makes no difference either way, but I figured specificity is better than ambiguity in this instance.

Copy link
Contributor

@alexmccord alexmccord left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the extremely delayed review! I have some changes that I want to see first before I can merge this in.

Analysis/include/Luau/ControlFlow.h Outdated Show resolved Hide resolved
Analysis/src/ConstraintGraphBuilder.cpp Outdated Show resolved Hide resolved
Analysis/src/ConstraintGraphBuilder.cpp Outdated Show resolved Hide resolved
Analysis/src/ConstraintGraphBuilder.cpp Outdated Show resolved Hide resolved
@alexmccord
Copy link
Contributor

Ended up pushing the changes I wanted to see because it's been sitting in limbo for too long and I didn't want it to wait longer.

@alexmccord alexmccord merged commit d00e93c into luau-lang:master Sep 21, 2023
6 checks passed
@alexmccord
Copy link
Contributor

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

Guard clauses using continue and break should refine types
2 participants