Skip to content

[SE-0365] Allow implicit self for weak self captures #40702

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

Merged
merged 40 commits into from
Sep 30, 2022

Conversation

calda
Copy link
Contributor

@calda calda commented Dec 24, 2021

This PR adds support for implicit self for weak self captures, as long as self has been unwrapped.

This implements SE-0365.

For example:

{ [weak self] in
    guard let self else { return }
    dismiss() // equivalent to `self.dismiss()`
}

@calda calda changed the title Support implicit self for weak self captures Allow implicit self for weak self captures Dec 24, 2021
@xwu xwu added the swift evolution pending discussion Flag → feature: A feature that has a Swift evolution proposal currently in review label Jan 6, 2022
@compnerd
Copy link
Member

@swift-ci please smoke test

@compnerd
Copy link
Member

@swift-ci please build toolchain Windows platform

Copy link
Contributor

@xedin xedin left a comment

Choose a reason for hiding this comment

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

LGTM! I have left two tiny comments but they can be addressed in a follow-up as well.

@@ -327,7 +332,7 @@ LookupResult TypeChecker::lookupMember(DeclContext *dc,
dc->lookupQualified(type, name, subOptions, lookupResults);

for (auto found : lookupResults)
builder.add(found, nullptr, type, /*isOuter=*/false);
builder.add(found, nullptr, nullptr, type, /*isOuter=*/false);
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: Please add /*baseDecl=*/ before new nullptr.

}
}

if (conditionalStmt == nullptr) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: if (!conditionalStmt)

@xedin
Copy link
Contributor

xedin commented Sep 29, 2022

@swift-ci please test

@xedin
Copy link
Contributor

xedin commented Sep 30, 2022

@swift-ci please test Linux platform

@xedin xedin merged commit 1a79d93 into swiftlang:main Sep 30, 2022
@xedin
Copy link
Contributor

xedin commented Sep 30, 2022

Thank you for the great contribution, @calda!

@calda
Copy link
Contributor Author

calda commented Sep 30, 2022

Thank you for the quick reviews and thorough feedback @xedin! Really appreciate it. This was my first non-trivial compiler change so your input was really helpful.

@@ -3,6 +3,48 @@ CHANGELOG

_**Note:** This is in reverse chronological order, so newer entries are added to the top._

## Swift 6.0
Copy link
Member

Choose a reason for hiding this comment

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

Minor nit: this should go under Swift 5.8, since part of the feature is there, and we aren't release "Swift 6.0" any time soon.

@xedin
Copy link
Contributor

xedin commented Oct 4, 2022

@calda The language workgroup discussed the problem and came to the conclusion that the compiler should:

  1. Keep existing lookup behavior in Swift 5 (so no changes there);
  2. Produce a tailored warning about guard let and if let statements that introduce self would be used in Swift 6 mode but isn't in Swift 5 - it has to describe the issue and avoid the fix-it to remove the statement. (We can brainstorm on the actual text).
  3. Produce a new warning in < Swift 6 modes that detects an incorrect use of implicit self in non-escaping closures and suggests to add guard let statement if there isn't one.

Would you be interested in working on changes?

@calda
Copy link
Contributor Author

calda commented Oct 4, 2022

Sure! I can work on these changes

@calda
Copy link
Contributor Author

calda commented Oct 5, 2022

Produce a tailored warning about guard let and if let statements that introduce self would be used in Swift 6 mode but isn't in Swift 5

In what situations should this warning be emitted, and how would the warning be silenced?

@xedin
Copy link
Contributor

xedin commented Oct 5, 2022

In what situations should this warning be emitted, and how would the warning be silenced?

The same situations where the warning about "unused self" had to be suppressed in Swift 5 mode because it picks self from [weak self] instead of guard let/if let statements.

@calda
Copy link
Contributor Author

calda commented Oct 5, 2022

If we want a warning and this case:

closure { [weak self] in
  // warning: self is not unwrapped, add `guard let self else { return }`
  method() 
}

and this case:

closure { [weak self] in
  // warning: guard condition is unused in Swift 5, but used in Swift 6
  guard let self else { return }
  method() 
}

then how do you write a weak self closure without triggering a warning?

It seems like we'd want a way to suppress the first warning without introducing a new warning. Thoughts?

@xedin
Copy link
Contributor

xedin commented Oct 5, 2022

Probably the only way is to write explicit self. in this case?

@calda
Copy link
Contributor Author

calda commented Oct 5, 2022

Should the fix-it suggest using explicit self instead of adding a guard statement?

@xedin
Copy link
Contributor

xedin commented Oct 5, 2022

As far as I understand the guard/if let statements have to stay to produce self unwrap for the Swift 6 mode, so all the fix-it would do is to add self. which is going to silence the warning in Swift 5 mode and not going to break code in Swift 6 mode. Does that make sense?

@calda
Copy link
Contributor Author

calda commented Oct 5, 2022

Do we need a warning at all in this case, since the code is well-formed in Swift 6?

closure { [weak self] in
  guard let self else { return }
  method() 
}

Allowing this without a warning may be better than having multiple warnings that needs to be suppressed in stages.

@xedin
Copy link
Contributor

xedin commented Oct 5, 2022

This warning should be emitted only for language modes < 6 because in 6 and on it would pickup the right self :) We just need to warn people that okay previous the behavior here wasn't ideal and we are going to change it.

@calda
Copy link
Contributor Author

calda commented Oct 5, 2022

Sorry, I just want to make sure any new warning we add here has an actionable fix-it that silences it.

For the warning in this example, what fixit would we use?

closure { [weak self] in
  // Warning: guard statement condition is unused in Swift 5 language mode
  guard let self else { return }
  method()
  otherMethod()
}

Would we emit the warning any time there's an implicit self reference that had its lookup behavior changed by Swift 6? e.g. would this case emit a warning?

closure { [weak self] in
  // warning?
  guard let self else { return }
  self.method()
  otherMethod() // still implicit
}

(the guard statement is not unused, but there's still an implicit self call that has its behavior changed in Swift 6)

@xedin
Copy link
Contributor

xedin commented Oct 5, 2022

Would we emit the warning any time there's an implicit self reference that had its lookup behavior changed by Swift 6?

Yes, guard statement gets one warning and both method() and otherMethod() would emit a warning and ask to add explicit self.. In other later case only otherMethod() would get a warning.

@xedin
Copy link
Contributor

xedin commented Oct 5, 2022

We could also go in a direction where guard let self and if let self trigger a special diagnostic walker that only emits a single warning attached to the statement and a bunch of notes with fix-its per incorrect use of self, so your first example would be diagnosed something like this:

error: declaring self in guard statement has to effect on implicit self use in this closure, this behavior is an error in Swift 6
guard let self else { return }
          ^~~

note: use of weak implicit 'self'
   method()
   ^
   self.

note: use of weak implicit 'self'
   ohterMethod()
   ^
  self.

@calda
Copy link
Contributor Author

calda commented Oct 5, 2022

Ok, this makes sense to me -- we emit a warning for each these implicit self calls that will have their behavior changed with a fix-it to use explicit self. This also silences the "unused guard statement" warning, since that is no longer unused after applying the fixits. Does that sound right?

@xedin
Copy link
Contributor

xedin commented Oct 5, 2022

Take a look at my previous comment (i might have posted it while you where writing this message), you might find that a more interesting approach which reduces number of warnings produced.

@calda
Copy link
Contributor Author

calda commented Oct 5, 2022

Thanks for the in-depth discussion! I think your suggestion of having a single warning with multiple notes would work really nicely here.

@xedin
Copy link
Contributor

xedin commented Oct 5, 2022

Absolutely! Let's try to make it happen then, if it turns out too hard there is always a fallback to multiple warnings.

@calda
Copy link
Contributor Author

calda commented Oct 9, 2022

Here you go: #61513 -- turned out to be pretty straightforward!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
swift evolution approved Flag → feature: A feature that was approved through the Swift evolution process
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants