Skip to content

[CSRanking] Favour concrete members over protocol requirements #18927

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

Closed

Conversation

hamishknight
Copy link
Contributor

This PR comes in two pieces:

1. A narrow Swift 4.1 compatibility hack that favours properties in concrete types over protocol requirements

This was unintentionally regressed by #15412, and is tracked by SR-7425. This is added as a ranking tie-breaker in order to ensure we don’t introduce any new ambiguities.

2. A new Swift 5 mode ranking rule that favours arbitrary concrete type members over protocol requirements

We already have a similar rule that prefers concrete members over protocol extension members – IMO this new rule is a natural extension of it.

This allows the following code to become unambiguous in Swift 5 mode:

protocol X {
  func baz() -> Int
}

class Y {
  func baz() -> Int { return 0 }
}

func foo(_ x: X & Y) {
  // currently ambiguous, will be made unambigous in Swift 5 mode (favouring Y's `baz()`)
  x.baz()
}

Note that this new rule isn’t applied to overloads found through dynamic lookup in order to ensure we keep cases such as SR-7299 ambiguous.

Note also that this new rule is potentially source breaking (hence why it requires Swift 5 mode) – for example, the following contrived case is currently unambiguous, but will be made ambiguous by the change:

protocol P {
  static func foo() -> Int
}

class C {
  func foo() -> Int { return 0 }
}

extension P where Self : C {
  static func bar() {
    let m = foo // currently unambiguous, will be made ambiguous in Swift 5 mode
  }
}

Arguably this change is unifying the behaviour with that of protocol extensions, where a similar example is already ambiguous:

protocol P {}
extension P {
  static func foo() -> Int { return 0 }
}

class C {
  func foo() -> Int { return 0 }
}

extension P where Self : C {
  static func bar() {
    let m = foo // error: Ambiguous use of 'foo()'
  }
}

And currently, adding static func foo() as a protocol requirement to the above suddenly resolves the ambiguity – with this Swift 5 mode change, the ambiguity is kept in both cases, which seems more consistent.

I’m happy to split this PR up if the Swift 5 mode change requires more discussion.

Resolves SR-7425.

…concrete types over protocols

Changes in shadowing behaviour by swiftlang#15412 caused a property on a concrete type to no longer shadow a protocol property member, which created unintentional ambiguities in 4.2. This commit ensures we at least keep these cases unambiguous in Swift 5 under Swift 4 compatibility mode. This is intentionally narrow in order to best preserve source compatibility under Swift 4 mode by ensuring we don't introduce any new ambiguities.

Resolves SR-7425, SR-7940 & SR-8343.
This commit adds a new rule to `isDeclAsSpecializedAs` in order to favour a member on a concrete type over a protocol member. This rule is effectively an extension of the existing rule that prefers concrete type members over protocol extension members.
…anking

This rule caused us to lose ambiguities in places where we really want ambiguity for `AnyObject` lookup, so only apply it when not comparing such overloads. This whole situation is a bit of a hack – really we shouldn't be applying any type-based or context-based overload ranking rules to overloads found through `AnyObject` lookup, but unfortunately we don't have syntax to precisely disambiguate overloads.

This commit can be reverted if/when we ever remove `AnyObject` lookup.
@hamishknight
Copy link
Contributor Author

hamishknight commented Aug 23, 2018

cc. @slavapestov – I believe this can supersede #18206.

@slavapestov
Copy link
Contributor

Excellent!

@slavapestov
Copy link
Contributor

I'll defer to @rudkx and @xedin for the review though.

@slavapestov
Copy link
Contributor

@swift-ci Please test

@slavapestov
Copy link
Contributor

@swift-ci Please test source compatibility

@swift-ci
Copy link
Contributor

Build failed
Swift Test Linux Platform
Git Sha - 2e6ba53

Apparently, we cannot use an initializer list to construct a std::tuple there. Use `make_tuple` instead, and also simplify a condition to use `isDynamicOverloadComparison` while I'm here.
@hamishknight
Copy link
Contributor Author

hamishknight commented Aug 23, 2018

Just fixed the Linux failure. The source compatibility suite succeeded, though looking through the projects I realise that we wouldn't have exercised -swift-version 5 at all (assuming I've understood how the suite is run). Is there a way to do so?

@rudkx
Copy link
Contributor

rudkx commented Aug 23, 2018

@hamishknight No. What I would suggest is removing the version check for the sake of running the compat suite to check that there aren't any unexpected breaks (and see what actually will break for people moving to Swift 5).

@hamishknight
Copy link
Contributor Author

@rudkx Thanks, I've gone ahead and done that – could you kick off the compat suite again?

@rudkx
Copy link
Contributor

rudkx commented Aug 24, 2018

@swift-ci Please test source compatibility

@rudkx
Copy link
Contributor

rudkx commented Aug 24, 2018

Can you split the changes up into separate PRs we get some feedback from the source compatibility suite?

I'll take a closer look at each once that's done. I am a little concerned that at first glance some of the changes remind me of #17995

@hamishknight
Copy link
Contributor Author

Sure thing – I've split it up into #18952 & #18951. Shall I go ahead and close this PR?

// x.i // ensure ambiguous.
// }
//
if (true && !isDynamicOverloadComparison) {
Copy link
Contributor

Choose a reason for hiding this comment

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

It should probably be simplified to if (!isDynamicOverloadComparison)

Copy link
Contributor Author

@hamishknight hamishknight Aug 24, 2018

Choose a reason for hiding this comment

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

@xedin I removed the version checks temporarily in 685ec32 to allow the compat suite to exercise the Swift 5 path. true should be tc.Context.isSwiftVersionAtLeast(5), and below, false should be !tc.Context.isSwiftVersionAtLeast(5).

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, gotcha!

// compatibility under Swift 4 mode by ensuring we don't introduce any new
// ambiguities. This will become a more general "is more specialised" rule
// in Swift 5 mode.
if (false && !isDynamicOverloadComparison &&
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this mean that this code shouldn't exist?

// All other things being equal, apply the Swift 4 compatibility hack for
// preferring var members in concrete types over a protocol requirement
// (see the comment above for the rationale of this hack).
if (false && score1 == score2) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here?

@xedin
Copy link
Contributor

xedin commented Aug 24, 2018

@rudkx As this branch stands today it preserves behavior when it comes to protocol vs. protocol extension declarations, and would always pick declaration on protocol...

@rudkx
Copy link
Contributor

rudkx commented Aug 25, 2018

@hamishknight Yes, we can close this out in favor of the two new PRs. Thanks!

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.

5 participants