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
[SR-11138] Nested property wrappers result in too strict 'mutability' keywords. #53534
Comments
As Doug explained, the current behavior is correct. Just because the mutating setter does not currently mutate the struct doesn't mean it couldn't do so in the future. Swift simply doesn't have the expressive power to say " |
But my point is that the mutating setter is never being called. Consider: @A @b var c: Int So I am not suggesting any change to Swift except how the synthesized getter and setter is marked with respect to mutability. Am I completely missing something here? |
:-/ Either you're missing something about how the setter is synthesized, or I'm missing something about how property wrappers are composed. @DougGregor? |
Right - I am a bit nervous that I am wasting everyone's time due to my own misunderstanding. :-/ But I'll try explaining how I think the property wrappers are composed. Maybe someone can see where my understanding is wrong and correct me. For the example above:
My understanding is that c is synthesized as: var c: Int { get { _c.wrappedValue.wrappedValue } set { _c.wrappedValue.wrappedValue = newValue } } So: The type of _c is A<B<Int>> Calling the setter of c with a value of 1 will:
So - if this interpretation is correct we only have: The wrappedValue getter of A is called. The wrappedValue setter of B is called. That's all. And when _c is not mutated, the setter might just as well be marked as nonmutating. If my interpretation is not correct I hope that you can show me which of my assumptions are wrong. |
@belkadan Did you by any chance have time to look at the description of my understanding of how the property wrappers are composed in the comment above? |
I'm deferring to Doug as the author of the property wrappers proposal, but Doug has been super busy. |
Thanks! (sorry, jira messes up my formatting when I post this - here is a gist with the code instead: struct A<T> {
} class B<T> {
} struct DontMutateMe {
// c = 1 _c.wrappedValue.wrappedValue = 1
} Here setting the value 'manually' through _c demonstrates that no mutation is necessary while the out-commented line does not compile. |
Thanks a lot, Jordan. I’m happy to hear that I wasn’t imagining things. 🙂 Looking forward to see this amazing language feature be even more awesome! |
Here's a fix in the master branch: #26326 You should be able to try it out in future snapshots of the master branch. |
Thank you so much! I look forward to trying it out in a future snapshot. :-) The 'composeWith' algorithm is a very elegant solution - and really excellent that it also deals with wrappers with missing setters. Do you think that there is any chance that the fix will be cherry-picked to swift-5.1-branch? Thanks again! :-) |
Environment
Swift 5.1 prerelease from Xcode 11 beta 3.
Additional Detail from JIRA
md5: 276670fd883a17c2a90bf7d0fb6d93f4
Issue Description:
With the Property Wrapper feature, the synthesized setter and getter are marked as mutating or nonmutating based on the corresponding keywords on the getter and setter of the wrappedValue.
For nested property wrappers this currently appears to be decided solely on the outermost wrapper - and this behavior could be improved.
Imagine the property wrappers A and B and the following nesting:
@A @B var c: Int
If A is a value type and has a mutating setter, and B is a reference type (with a nonmutating setter), this will currently (in Xcode 11 beta 3) generate a mutating setter for c.
But the synthesized setter is:
set { _c.wrappedValue.wrappedValue = newValue }
In other words - only the getter of A is called while just the setter on reference type B is called.
Since B is a reference type in this example nothing on _c is being mutated, so the setter might as well have been generated as nonmutating.
For a discussion about this please see the Swift Forums thread: Question about nested property wrappers and mutability
My own humble attempt at an 'algorithm' for deciding the mutability keywords. I hope that I am not mixing things up:
For a chain of nested property wrappers, the mutating keyword on the synthesized getter can be calculated as follows:
Consider the wrappers from the outermost to the innermost in order:
If a wrapper with a mutating getter appears before one with a nonmutating setter, then the synthesized getter must be mutating - ** otherwise it can be nonmutating.
Similarly for setters:
Consider the wrappers from the outermost to (but not including) the innermost:
If a wrapper with a mutating getter appears before one with a nonmutating setter OR if the innermost wrapper has a mutating setter, then the synthesized setter must be mutating. Otherwise it can be nonmutating.
Examples:
@A @b @C @d @e var f: Int
If A and B have nonmutating getters and C is a reference type (and thus has a nonmutating setter), then even though D and E had mutating getters and setters, both getter and setter of _f can be nonmutating. The mutating getter of D would not matter due to the nonmutating setter of C.
But if A, B, and C had nonmutating getters and mutating setters, then if D had a mutating getter, then both the getter and setter of _f should be mutating.
If A, B, C, D and E all had nonmutating getters and mutating setters, then _f would have a nonmutating getter and a mutating setter.
The text was updated successfully, but these errors were encountered: