Skip to content

[CSRanking] Favour concrete members over protocol requirements in Swift 5 mode #18951

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 6 commits into from
Aug 30, 2018

Conversation

hamishknight
Copy link
Contributor

(This is the Swift 5 mode portion of #18927)

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.

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.
@rudkx
Copy link
Contributor

rudkx commented Aug 25, 2018

@swift-ci Please smoke test

@rudkx
Copy link
Contributor

rudkx commented Aug 28, 2018

@swift-ci Please test source compatibility

@rudkx rudkx self-assigned this Aug 28, 2018
@hamishknight
Copy link
Contributor Author

@rudkx Source compat failed to clone the repo – could you try again?

@rudkx
Copy link
Contributor

rudkx commented Aug 28, 2018

@swift-ci Please test source compatibility

@hamishknight
Copy link
Contributor Author

Just resolved a merge conflict. Given the source compat suite passed, should I revert 37a9490?

@rudkx
Copy link
Contributor

rudkx commented Aug 29, 2018

@hamishknight Yes, sounds good.

@rudkx
Copy link
Contributor

rudkx commented Aug 29, 2018

@swift-ci Please smoke test

@hamishknight
Copy link
Contributor Author

Linux failure looks unrelated – same failure as https://ci.swift.org/job/swift-PR-Linux-smoke-test/9516/

@rudkx
Copy link
Contributor

rudkx commented Aug 30, 2018

@swift-ci Please smoke test Linux platform

@rudkx rudkx merged commit ea00508 into swiftlang:master Aug 30, 2018
@hamishknight hamishknight deleted the concrete-is-better-swift5 branch August 30, 2018 09:21
if (tc.Context.isSwiftVersionAtLeast(5) && !isDynamicOverloadComparison) {
auto *proto1 = dyn_cast<ProtocolDecl>(outerDC1);
auto *proto2 = dyn_cast<ProtocolDecl>(outerDC2);
if (proto1 != proto2)
Copy link
Contributor

@rudkx rudkx Sep 25, 2018

Choose a reason for hiding this comment

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

I just ran into an issue with this change.

If you compare two declarations in two different protocols, e.g.:

protocol P {
  func f()
}

protocol Q : P {
  func f()
}

it always returns proto2.

For the concrete vs. protocol case it doesn't seem (from a quick look at the code) to be doing the right thing either.

[EDIT: I meant to make one protocol refine the other; my expectation is that we would return Q's f() as being more specialized.]

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, I see – not sure how I missed that! We should really be doing an is-a check rather than comparing pointers, i.e:

        auto inProto1 = isa<ProtocolDecl>(outerDC1);
        auto inProto2 = isa<ProtocolDecl>(outerDC2);
        if (inProto1 != inProto2)
          return inProto2;

I can't immediately think of a test case that trips up the current logic though – for the case you mention of having a protocol refine another, we should be filtering out overridden protocols requirements in name lookup.

Do you have a test case that trips up the current logic? Would be nice to have one for the fix.

For the concrete vs. protocol case it doesn't seem (from a quick look at the code) to be doing the right thing either.

Could you please elaborate on this? I can't immediately think of a concrete vs. protocol case where it does the wrong thing.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm experimenting with changes that call compareDeclarations on arbitrary overloads in a disjunction to see if they are ordered. I don't have a test case that I know will hit this otherwise.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah okay, sorry about that! I've submitted a fix: #19561

hamishknight added a commit to hamishknight/swift that referenced this pull request Sep 26, 2018
As discussed in swiftlang#18951, we don't want to be comparing two different protocol decls here, as that could lead to spurious 'is more specialized' results. I'm not aware of any case that currently trips this logic up though, so no tests to accompany.
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.

2 participants