Skip to content

[Sema] Always look through optionals for unresolved member lookup #34715

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 2 commits into from
Dec 5, 2020

Conversation

Jumhyn
Copy link
Member

@Jumhyn Jumhyn commented Nov 12, 2020

Previously, we only looked through optionals on UnresolvedMemberExprs when there were no results from Optional itself. However, in cases where Optional has a member with the same name, this would prevent programs from compiling even if the member on Optional was obviously invalid in the context of the UnresolvedMemberExpr. Consider this example:

import SwiftUI

extension UIColor {
  static let background: UIColor? = UIColor.black
}

let backgroundColor: UIColor? = .background

In SwiftUI, Optional conditionally conforms to View whenever Wrapped conforms to View. Additionally, View has an extension which provides an instance method named background(_:alignment:). This means that unresolved member lookup of background will find the method on Optional, and won't continue to look in UIColor for the user-defined background member.

This PR allows the above code to compile by unconditionally looking into Wrapped for unresolved member lookup, even if we already found results in Optional<Wrapped>. Because we already prefer overloads found on Optional to those found on Wrapped in CSRanking, situations where there are valid members on both should continue to choose the member on Optional (though this definitely needs a source-compat run).

Resolves SR-13815.

@Jumhyn
Copy link
Member Author

Jumhyn commented Nov 12, 2020

cc @xedin @hborla interested in both of your thoughts on this! It seems like this should be source compatible given our ranking logic but I'd like more eyes to make sure I'm not missing something...

constraintKind == ConstraintKind::UnresolvedValueMember) {
if (auto objectType = instanceTy->getOptionalObjectType()) {
// If we don't have a wrapped type yet, we can't look through the optional
// type.
if (objectType->getAs<TypeVariableType>()) {
if (objectType->getAs<TypeVariableType>() && result.ViableCandidates.empty()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this would break source compatibility when score of something found on Wrapped be better than score on Optional itself...

Copy link
Member Author

Choose a reason for hiding this comment

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

Since we already score Decl better than DeclViaUnwrappedOptional, the score on Wrapped would have to outweigh the disadvantage from the OverloadChoiceKind mismatch. Do you think such situations are likely to occur in practice?

Is there any way to unconditionally prefer the non-optional choices, in the case where we've found multiple solutions?

Copy link
Contributor

Choose a reason for hiding this comment

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

Since we already score Decl better than DeclViaUnwrappedOptional

Solutions are first ranked on the score produced by the solver (https://github.com/apple/swift/blob/main/lib/Sema/CSRanking.cpp#L758) and then their declaration diff is compared if scores match. It's quite possible that choices could have different optionality of require other implicit conversion which would change the behavior here.

Copy link
Member Author

Choose a reason for hiding this comment

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

@xedin Ah, thank you for pointing that out. The situation where we have identically-named static members on both Optional<Wrapped> and Wrapped where both properly type-check seems pretty unlikely to me, but if it's above the risk level for being considered source-compatible (as opposed to just a bug) then so be it.

Is there a way to add the Wrapped overloads but not skip over them whenever any Optional overloads successfully type check?

Copy link
Contributor

Choose a reason for hiding this comment

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

One way to implement this without changing existing behavior would be to lift DeclViaUnwrappedOptional into the solver score to make sure regular declarations are always preferred over the ones found on wrapped type.

@Jumhyn
Copy link
Member Author

Jumhyn commented Nov 13, 2020

@xedin I've taken your suggestion and lifted DeclViaUnwrappedOptional into ScoreKind for UnresolvedMemberExpr overload resolution. I've also added a couple tests to make sure that we resolve to overloads on Optional when there are matching overloads on Wrapped that would otherwise be preferred. Let me know what you think!

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 think might be worth it to add a unit test for this behavior as it might be much easier to make sure the behavior is correct without having to rely on debug output formatting of the solver.

@Jumhyn
Copy link
Member Author

Jumhyn commented Nov 14, 2020

@xedin Yeah that makes sense! Is there a good example I could refer to about how to do that? I haven't written a test like that before.

@xedin
Copy link
Contributor

xedin commented Nov 14, 2020

I added a couple of unit tests which use ConstraintSystem and related types in unittests/Sema. You’d need to create a couple of fake declarations, generate constraints for a fake unresolved member reference and check that solution has an expected declaration picked and score doesn’t have member-via-unwrap increased.

@Jumhyn
Copy link
Member Author

Jumhyn commented Nov 14, 2020

Alright, I'll give those a look—thanks! Will ping you when this PR is ready for another look.

@xedin
Copy link
Contributor

xedin commented Nov 14, 2020

Thank you!

@Jumhyn
Copy link
Member Author

Jumhyn commented Nov 15, 2020

@xedin Finally got a chance to sit down with this and realized that relying on the solver output is overkill. All we really need is the type-checked AST, so I've switched to just checking the output of -dump-ast instead. Do you think that's sufficient, or do you still want a unit test here as well?

@xedin
Copy link
Contributor

xedin commented Nov 16, 2020

@Jumhyn I'd still prefer if we had that as a unit test but I think it's okay to use -dump-ast to verify that, at least it's more stable than -debug-constraints.

@LucianoPAlmeida
Copy link
Contributor

@Jumhyn @xedin After those changes member lookup here https://github.com/apple/swift/blob/a9f5107b44c71929521ffbf3a91c5081554f272d/lib/Sema/CSSimplify.cpp#L7183 will return both candidates for Optional and Wrapped types? Just checking as I'm trying to change the CSApply warning to a fix and right now we have to do another lookup ...

@Jumhyn
Copy link
Member Author

Jumhyn commented Nov 18, 2020

@LucianoPAlmeida Yes, the results from that line should include candidates from both Optional and Wrapped (but only when we’re doing lookup for an UnresolvedMemberExpr).

@LucianoPAlmeida
Copy link
Contributor

@LucianoPAlmeida Yes, the results from that line should include candidates from both Optional and Wrapped (but only when we’re doing lookup for an UnresolvedMemberExpr).

Great thanks, I think that could simplify things in that case :)

@Jumhyn
Copy link
Member Author

Jumhyn commented Nov 19, 2020

@LucianoPAlmeida Unfortunately, I probably won't have a chance for the next couple of days to write the unit test to get this mergeable, so you may have to wait for a bit for the changes here to be usable for your fix.

@LucianoPAlmeida
Copy link
Contributor

@LucianoPAlmeida Unfortunately, I probably won't have a chance for the next couple of days to write the unit test to get this mergeable, so you may have to wait for a bit for the changes here to be usable for your fix.

No worries @Jumhyn :)
Those are very simple changes so I've already "cherry-picked" to my branch to work with them. It did break the implementation that I had already so it will take some time for me to have something also.

@LucianoPAlmeida
Copy link
Contributor

@xedin @Jumhyn Quick question, in this case

enum Foo {
  case bar
  case none
}

let _: Foo?? = .none

Name lookup is actually returning only 2 viable candidates where the base is Foo?? and Foo?, so my question is if that is expected and intentional or it should have dig deeper and find also base Foo?

@xedin
Copy link
Contributor

xedin commented Nov 21, 2020

@LucianoPAlmeida It intentionally only unwrap a single layer of optionality at the moment.

@LucianoPAlmeida
Copy link
Contributor

@LucianoPAlmeida It intentionally only unwrap a single layer of optionality at the moment.

Ah, thank you @xedin :)
I think that will require another lookup then in order to record the fix correctly then

@xedin
Copy link
Contributor

xedin commented Nov 21, 2020

That would be okay since it's performed in diagnostic mode anyway.

@LucianoPAlmeida
Copy link
Contributor

That would be okay since it's performed in diagnostic mode anyway.

Humm, that may be a problem because since it is a warning we have to always record it ... but on the other hand the extra lookup will be done only in certain situations involving deep optionals. I'm finishing up the changes and pushing, so we can discuss it there :)

@Jumhyn Jumhyn force-pushed the SR-13815 branch 2 times, most recently from ce979a0 to ef52a0a Compare December 3, 2020 19:35
@Jumhyn
Copy link
Member Author

Jumhyn commented Dec 3, 2020

Thank you for your patience @xedin! Finally had a chance to sit down an write up the unit test(s). Let me know if this matches what you were picturing.

cc @LucianoPAlmeida, this will hopefully be merge-ready soon! :)

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.

Looks great! That’s exactly what I had in mind.

@Jumhyn
Copy link
Member Author

Jumhyn commented Dec 3, 2020

@xedin Updated per your comment! Would you mind kicking off the tests on this PR?

@xedin
Copy link
Contributor

xedin commented Dec 3, 2020

@swift-ci please test

@xedin
Copy link
Contributor

xedin commented Dec 3, 2020

@swift-ci please test source compatibility

@swift-ci
Copy link
Contributor

swift-ci commented Dec 3, 2020

Build failed
Swift Test Linux Platform
Git Sha - cd745f3c5d250b831b3ae680ca01afbd7c4fe852

@swift-ci
Copy link
Contributor

swift-ci commented Dec 3, 2020

Build failed
Swift Test OS X Platform
Git Sha - cd745f3c5d250b831b3ae680ca01afbd7c4fe852

@Jumhyn
Copy link
Member Author

Jumhyn commented Dec 4, 2020

@LucianoPAlmeida Latest push addresses your comments and fixes the test failure on Windows (in theory🤞). From what I could tell the Linux and macOS test failures were not related to the code touched here... mind kicking off another round of tests?

@xedin
Copy link
Contributor

xedin commented Dec 4, 2020

@swift-ci please test

@xedin
Copy link
Contributor

xedin commented Dec 4, 2020

@swift-ci please test source compatibility

@swift-ci
Copy link
Contributor

swift-ci commented Dec 4, 2020

Build failed
Swift Test OS X Platform
Git Sha - 7b35e577c8d1103e05018a87badac6b392daa47e

@LucianoPAlmeida
Copy link
Contributor

@swift-ci please test MacOS Platform

@LucianoPAlmeida
Copy link
Contributor

@swift-ci Please test OS X platform

@Jumhyn
Copy link
Member Author

Jumhyn commented Dec 4, 2020

Windows failure looks like a flake. Rebased against main to see if that resolves the (seemingly unrelated) macOS failure... @xedin @LucianoPAlmeida one more round of tests, please?

@LucianoPAlmeida
Copy link
Contributor

@swift-ci please test

@xedin
Copy link
Contributor

xedin commented Dec 5, 2020

@swift-ci please test source compatibility

@Jumhyn
Copy link
Member Author

Jumhyn commented Dec 5, 2020

@xedin All checks passed!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants