-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Enable SE-0365 behavior in Swift 5 mode #61520
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
@xwu @xedin This is a follow-up to our discussion here: #61513 (comment) |
// If this is an "implicit self" expr from the LHS of a shorthand | ||
// condition like `guard let self` or `if let self`, then this is | ||
// always allowed and we shouldn't run any diagnostics. | ||
if (UnwrapStmtImplicitSelfExprs.count(E)) { |
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 noticed the previous implementation was incorrectly rejecting guard let self
and if let self
shorthand conditions, which have an "implicit self" expr on the RHS. These are always allowed so we shouldn't be running these diagnostics on them.
8d64e79
to
093535d
Compare
func method() { } | ||
|
||
private init() { | ||
doVoidStuff { [weak self] in | ||
method() // expected-error {{call to method 'method' in closure requires explicit use of 'self' to make capture semantics explicit}} | ||
guard let self = self else { return } | ||
method() // expected-error {{call to method 'method' in closure requires explicit use of 'self' to make capture semantics explicit}} | ||
self.method() | ||
method() |
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.
This now compiles in Swift 5 mode
} | ||
|
||
doVoidStuffNonEscaping { [weak self] in | ||
method() | ||
method() // expected-warning {{call to method 'method' in closure requires explicit use of 'self' to make capture semantics explicit}} |
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.
This now warns in Swift 5 mode (it's an error in Swift 6 mode)
093535d
to
22e4313
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.
The implementation approach looks good! I only have a few minor comments about the language used in the names and comments in the code.
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.
To me, these explanations are a lot more clear, thanks!
Thank you all for the feedback and discussion -- I really appreciate it! Unfortunately, I was running some additional tests and found an issue with this approach. Since the implicit self decl in the AST refers to the closure's strongly-typed self Here's the sample I was testing with (collapsed for brevity): func runInOneSecond(_ closure: @escaping @Sendable () -> Void) {
Task {
try! await Task.sleep(nanoseconds: 1_000_000_000)
closure()
}
}
final class Weak: Sendable {
let property = "Self exists"
func test() {
runInOneSecond { [weak self] in
guard let self else {
print("Self doesn't exist")
return
}
print(property)
}
}
}
Weak().test()
try await Task.sleep(nanoseconds: 2_000_000_000) In Swift 6 mode, Fixing this for escaping closures in Swift 5 while maintaining source compatibility would require modifying how captures are generated, and ignoring the decl in the AST in favor of doing a manual lookup like we do in @xwu, given this information I think implementing SE-0365 for escaping closures in Swift 5 is not viable. I think our best option here would be to:
Thoughts? You mentioned in #61513 (comment) that this may require an amendment / re-review of SE-0365. If that's the case, I can submit an amendment to the proposal. |
In shipping versions of Swift, there is a bug involving func f(_ closure: @escaping () -> Void) { closure() }
final class C {
var property = "Hello, world!"
func test() {
f { [weak self] in print(property) }
// error: reference to property 'property' in closure requires explicit use of 'self' to make capture semantics explicit
}
} Put another way, unless I'm mistaken, it's not accurate to say that the code in your example "is captured strongly" in Swift 5; the Swift 5 compiler refuses to capture it strongly or weakly as it shouldn't compile at all. If this PR causes the code in your example to compile in the Swift 5 language mode with a strong capture, then it should not land in that condition; and if #40702 is what caused the code to compile in that way, then it will need to be backed out. We need to maintain source compatibility with the shipping compiler's existing buggy behavior, but we absolutely shouldn't widen the bug in the Swift 5 language mode to involve escaping closures too. |
Yes, this is an issue specifically introduced by this pull request (and not present on I think we should update this PR to disallow implicit self in escaping That would mean that, in Swift 5, SE-0365 would only apply to non-escaping closures and not escaping closures (it applies to both in Swift 6). Is that acceptable? Would that change require a re-review of SE-0365? |
What is the technical barrier to implementing SE-0365 as approved for the Swift 5 language mode—i.e., to capturing
Yes, it would require a re-review. However, the language workgroup discussed that if the reason for limiting a feature to the Swift 6 language mode is technical only, they would like to see that addressed in the implementation side, and if it cannot be, they would favor not landing the feature at all (i.e., backing out #40702). |
We would have to fix this issue where using implicit self causes I personally agree with the discussion above that circumventing the AST and doing manual lookups like this in |
What would this mean for SE-0365? Would it effectively move from being approved to being rejected / returned? Would there be a path for landing this change in Swift 6 mode (as currently implemented on |
Sorry, I'm not understanding something. In the shipping Swift compiler, the escaping example above doesn't compile at all, so there's nothing to "change" as there's no status quo—your implementation of SE-0365 which generates the "correct" AST shouldn't need to be gated to the Swift 6 language mode, and thus there wouldn't be anything to "circumvent"?
I think the consensus of the language workgroup discussion is that we'll want to clarify the technical issues here before determining if there's a process issue. |
This change must be gated to the Swift 6 language mode, because it's source breaking. This "incorrect" AST in Swift 5 is the specific reason why implicit self is unexpectedly permitted in non-escaping |
I'm okay to agree to disagree on this but the point I was trying (and keep trying) to make is that diagnostics shouldn't be misled about what declaration has been selected. It's okay to perform a lookup and double-check (there are examples of that) as a dedicated method but we shouldn't be returning results which are inconsistent with solutions produced by the solver. |
I guess what I'm asking is, can permitting implicit strong self in non-escaping weak self closures for the Swift 5 language mode be re-implemented (specifically for non-escaping weak self closures, of course) on top of the "correct" AST? |
I think the answer is yes but that is a source breaking change. |
Sorry, what source would break? Perhaps I am expressing myself in a confusing way, so let me try to rephrase. Is it technically possible to implement a version of Swift where both the behavior of SE-0365 is implemented and non-escaping closures capturing weak self are permitted to use implicit strong self? |
I investigated that approach and I don't think it's feasible. I believe the main obstacle with that approach is that determining whether a closure is escaping or non-escaping happens very late in the type-checking process, after the closure body has already been type-checked with the "incorrect" AST. So by the time we have enough information to know how implicit self should behave, it's too late to actually do anything about it. |
I concur with that assessment, we cannot actually go either or here because of how type and effects checking are implemented. |
Got it, I think that’s grounds to have the workgroup revisit what it would like to do here, then… |
c8dc836
to
f8ca293
Compare
@xwu, here's a PR that updates the proposal text with my proposed amendments: swiftlang/swift-evolution#1819 |
I apologize if you've already considered this approach, but I do think there's a way to make SE-0365 fully supported on Swift <6 regardless of the escaping-ness of closures. SE-0365 introduces a predicate on variables for whether they are non-optional rebindings of the contextual
While this might look superficially like it will cause the bad lookup rule to start affecting escaping closures in Swift <6 modes, that's not actually the case. Recall that the existing lookup rule for implicit Now, we might want a specialized diagnostic when diagnosing the implicit reference to |
Thanks for the idea @rjmccall! I hadn't considered that specific approach -- it sounds really promising to me. I'll try this out and let folks know how it goes |
This reverts commit f8ca293.
That approach works perfectly, thank you very much for the suggestion @rjmccall! I pushed a commit with these changes (8528021) -- in Swift 5 mode we now preserve the old lookup behavior in cases where @xedin, would you mind taking a look at the latest changes? This approach seems much better since we no longer need to perform manual lookups in |
One question: we probably also want a test case that validates that I've been testing this locally by running this code and checking the console output. Is there a way to write a test case that does this, or is there a different preferred way to test this sort of thing? |
@calda Sure! I will take a look tomorrow. |
b43a956
to
8528021
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.
LGTM!
You can turn this in an executable test. |
3a95db9
to
c081d56
Compare
Thanks, I added an additional test case for that. Tests pass for me locally, could you trigger smoke tests and source compatibility tests? |
@swift-ci please test |
@swift-ci please test source compatibility |
This failure is weird -- it doesn't seem to be a compiler error:
Are those tests flaky sometimes? |
Yep, let's re-trigger. |
@swift-ci please test source compatibility |
@shahmishal Is the sandbox issue that occurs in protobuf project being tracked? |
@swift-ci please test source compatibility |
Tests are passing, great! Really appreciate your help with this change over the past few months @xedin, couldn't have done it without you :) |
No worries, happy to help! |
Thanks for seeing this through! |
This PR enables the behavior defined in SE-0365 for Swift 5 language modes.
The expands on #40702, which implemented SE-0365 only in Swift 6 mode.