-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[Async Refactoring] Support Obj-C style bool flag checks #37629
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
Conversation
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.
Thanks for the cleanup as usual 🙇. No major comments
// RUN: %refactor -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+1):1 -I %S/Inputs -sdk %clang-importer-sdk-path | %FileCheck -check-prefix=BOOL-DONT-HANDLE2 %s | ||
boolWithErr { success, err in | ||
if !success { | ||
func doThings() { |
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.
See comment above - is there a good reason not to handle this case? Ie. if the force unwrap is used at all in the scope (nested or not), treat it as the error condition?
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.
The problem is that would count for things like this:
if !success {
func doThings() {
print(err!)
}
if err != nil {
doThings()
}
}
Where the !success
condition doesn't appear to be relevant to the force unwrap. We could probably perform a more in-depth heuristic that also looks for other references to err
, but I'm not sure whether or not that's worth it. What do you think?
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.
Makes sense, I wouldn't expect that case to be super common though. Another would be returning multiples bools and checking each both (nested). Though again, I assume that's uncommon. Erring on the side of leaving it and adding placeholders is probably okay for now though 👍
|
||
// RUN: %refactor -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+1):1 -I %S/Inputs -sdk %clang-importer-sdk-path | %FileCheck -check-prefix=BOOL-WITH-ERR-SILLY %s | ||
boolWithErr { success, err in | ||
if success == false && err == nil { |
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.
I agree kind of silly, but this sort of implies that success
is actually error
. We could handle it as such... Thoughts?
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.
Hmm, the problem is that we're only classifying it as a success flag to begin with because of the force unwrap of the error in the body, so even if we classify both checks as success paths, the code still doesn't make much sense. We could apply a more general heuristic that allows for bool condition checks if they're also with other conditions that are able to classify as success or failure, but I don't really have a good sense of how often that comes up.
On a related note, I've just noticed that if the force unwrap of the error in this example was in an else
block here, we would still classify the condition as the same, which doesn't seem right. It seems we should adjust the classification logic so we make the determination of whether the flag is a success check or failure check based on the branch in which the force unwrap of the error occurs rather than the other way around, which would let us classify a flag as an error
check if we then force unwrap the error.
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.
Probably a couple of problems here really. Yes, ideally if the force unwrap was in the else we would then consider success
to be a flag. Could get a bit tricky when the else
can itself be another if
though.
Another problem is that if we don't classify the success
as being a flag then there are unhandled conditions and so we don't remove the if
at all. Ideally we'd classify the whole if as being in the success block and then remove the && err == nil
. But that's an existing issue so... 🤷
161f9b1
to
d7b13d2
Compare
Updated to handle the unwrap case, and changed the flag classification logic to consider the branch the force unwrap happens in, rather than assuming the flag always denotes success (and with that change, it didn't seem like we really needed the check to discard the flag if it disagrees with the rest of the conditions). Also noticed we were missing a couple of |
d7b13d2
to
2723a44
Compare
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.
One final comment, but we can do that in another PR if we want it.
// It's a little unfortunate that print("a \(<#err#>!)") gets classified in the success | ||
// block below, but it doesn't seem like a case that's likely to come up, as optBool | ||
// would then be inaccessible in the error block. |
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.
For both a
and f
we could just assume that if there's a force unwrap, that == the error branch (only where the conditions aren't explicitly success/error and involve known params). I'm happy with this as is though, just thought I'd mention it.
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.
Yeah it's possible we'll want to generalise the heuristic to better handle cases like these, though I'm not really sure how often they'll come up in practice as it seems a little odd to e.g check a success param for non-nil and then assume you're on an error path without looking at the success param again. I think for now at least I'd rather tend on the side on keeping the heuristic a little more limited and seeing what kind of feedback we get from users.
This will make it a little nicer to switch on ConditionType.
- Expand ConditionType to include `.success` and `.failure` patterns. - Introduce ConditionPath to represent whether a classified condition represents a success or failure path. - Add methods to CallbackClassifier that deal with deciding whether a given condition is a success or failure path. - Use these methods to simplify `classifyConditional`.
Add an additional case to `CallbackCondition` to support boolean checks, and add the necessary classification logic to determine whether a bool flag check is for a success or failure path. The logic currently first checks to see if the async alternative has a convention that specifies which parameter the flag is, and whether it's a success or failure flag. If so, it classifies the flag check accordingly. If there's no async convention specified, we use a heuristic to see if the error parameter is force unwrapped in the failure block. If so, we treat that as the error path. If there's no force unwrap of the error, we leave the condition unhandled. rdar://74063899
2723a44
to
7df0786
Compare
@swift-ci please smoke test |
Add an additional case to
CallbackCondition
to support boolean checks, and add the necessary classification logic to determine whether a bool flag check is for a success or failure path.The logic currently first checks to see if the async alternative has a convention that specifies which parameter the flag is, and whether it's a success or failure flag. If so, it classifies the flag check accordingly. If there's no async convention specified, we use a heuristic to see if the error parameter is force unwrapped in the failure block. If so, we treat that as the error path. If there's no force unwrap of the error, we leave the condition unhandled.
rdar://74063899