-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[Typechecker] Fix _modify for wrapped properties with observers #29931
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
Also, I would prefer if this had a SILGen test as well, demonstrating that the observers are getting called. |
You mean calling the observers (like the setter does)?
Sure, I will add one! |
Yeah, exactly. Can you also try to write a test case that demonstrates the breakage with a protocol conformance? |
Great. I need to add that code anyway for SE-0268 to work. I have one question though - how will this work if we have a willSet(newValue) // not sure
yield &wrappedValue
didSet(oldValue) // possible
When would this breakage occur exactly? (since you can't explicitly add |
var tmp = wrappedValue
yield &tmp
call setter(tmp) This could work I suppose... but if we are gonna delegate to the setter then might as well use it in the first place? Maybe not because of the protocol requirement issue you mentioned before. |
The modify coroutine in this case should just be the standard one we synthesize for computed properties:
The synthesized setter already calls willSet/didSet in the right order. I suspect what's happening is we're synthesizing the modify coroutine as if this was a plain stored property, ie
|
Hmm, yeah. Looking at the SIL, it seems like we're basically doing: var tmp = wrappedValue.getter()
yield &tmp
wrappedValue.setter(tmp) I was thinking that not changing the |
Well, I thought it would work in theory, but I just tried and it doesn’t work as one of the member ref expressions in the chain to the wrappedValue end up with an LValue type which SILGen doesn’t like. I’m not sure if I’m doing something wrong because I’m aware that lvalue computation is sort of broken (but should be fixed if we merge PR 29069). |
Hi @theblixguy, were you able to find any more time to look into this? |
@slavapestov Sorry I got caught up with work so didn't have time to investigate beyond my last update above, but I'll take another look shortly! I'll also try including commits from #29069 to see if that fixes the lvalue computation issue I mentioned above. It seems like the AST just has the yield statement which gets decomposed into the getter-yield-setter pattern above in SILGen so I need to take a look there as well, but I suspect the fix lies in Sema only. |
@swift-ci please smoke test |
@theblixguy Are you planning to look into this, or will you need some help? |
@DougGregor Yep, I had other PRs in the queue I wanted to finish off but now that's done, I'll can now look into what Slava suggested tomorrow. I tried a couple of things to get it to emit a modify like we do for a normal observed property but ran into problems (as mentioned above). I am hoping that #29069 could help here, if not then I'll need to figure out another way. If you have any suggestions then please let me know! Also, in the meantime, if you want to get this fix in, feel free to (test failures looks unrelated). |
Okay, I pulled in the changes from #29069 and I don't get the LValue crash I was getting before, which is great, however now I get an infinite loop at runtime. I think this is expected because what I tried to do was to keep the From discussions above, it seems like the
and for observed wrapped properties:
but to switch between these forms I need to change the If the current approach in this PR is not correct, then I think I am kinda stuck (at least because I've been thinking the solution to this bug is in Sema and not SILGen, etc). |
This is the "universal" form of a modify coroutine that should work with any implementation:
I need to re-familiarize myself with the code before giving you further feedback though. I'll pull in your change and play around with it and see if I can get it working. |
Thank you! So I suppose there are two parts to this - what the _modify looks like in AST (there’s just a yield statement in the body) and what it looks like in SIL (getter-yield-setter). I admit I haven’t spent time looking at the SIL implementation, so I haven’t got the full understanding of how exactly it works. At the moment I was trying to discover whether I have got the first part right (StorageImplInfo and TargetInfo). But maybe I was looking at the wrong thing... |
I think the second part should work even with a property wrapper property, as long as the first part works. I'll let you know what I find. |
I got your test case working by making the following changes on master (without this PR applied):
However, now I suspect this will break SE-0268 semantics since we'll still call the getter even if the observer doesn't need it. |
Also, unrelated: it feels to me that synthesizeModifyCoroutineBodyWithSimpleDidSet() is redundant. The default coroutine should already do the right thing, with calling willSet and didSet, etc. Is it just an optimization at this point? |
I cleaned up my version of the fix slightly and this is what I have now. It's almost identical to your change, but a little bit simpler perhaps. Do you want to see if it passes tests?
|
Hmm it would be a bit unfortunate if we need to suppress it for property wrappers.
I guess so, let me check and I will create a PR to remove it if its indeed redundant!
Thanks! Two questions:
|
I think I was wrong about this part. As long as the setter's body is synthesized correctly we should be fine.
Yeah, I suggest changing my code so that the read coroutine is not affected.
Not quite. getMutableComputed() returns a StorageInfo with ReadWriteImpl::MaterializeToTemporary, whereas ordinary wrapped properties use ReadWriteImpl::Modify. |
The difference is that an inout access of a ::MaterializeToTemporary is SILGen'd to a call of the getter followed by a call of the setter. A ::Modify SILGen's as a call to the modify coroutine. This means that for a ::MaterializeToTemporary, modify is only used as a protocol witness, and a valid implementation of modify is to yield the property itself. With a ::Modify, you can't do that; the modify coroutine then calls itself, causing an infinite loop. |
Thanks so much for the explanation! So I have done some cleanups and I also added a |
Btw it seems like the |
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 looks good. Do you mind adding a SILGen test as well?
You're right, a custom modify implementation can be more efficient here because it can yield the underlying storage and then call the observer. We can consider optimizing it later. |
…mplInfo and check for AccessorKind in synthesizeCoroutineAccessorBody
@swift-ci please smoke test |
Should I cherry-pick this to 5.2 branch as well? |
Please do cherry-pick to 5.2 |
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!
Since we implemented
_modify
synthesis for property wrappers, it seems like we go through the_modify
even when the wrapped property has explicit observers attached to it, which means the observer is never called. For example:This used to work fine on Swift 5.1, but not anymore.
So, if the wrapped property has any observers, then fix _modify to call setter.
Resolves SR-12178, SR-12089
Resolves rdar://problem/59496047