Skip to content
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-6650] Conditional conformance not identifying protocol extension implementation #49199

Closed
swift-ci opened this issue Dec 20, 2017 · 5 comments
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself

Comments

@swift-ci
Copy link
Collaborator

Previous ID SR-6650
Radar None
Original Reporter edisonlsm (JIRA User)
Type Bug
Status Resolved
Resolution Done

Attachment: Download

Environment

Xcode 9.2, Swift 4.1-dev 2017-12-19

Additional Detail from JIRA
Votes 0
Component/s Compiler
Labels Bug
Assignee None
Priority Medium

md5: 3b53df29384c766a5be3cfc258b16e91

relates to:

  • SR-6474 Conditional conformance to refined protocol inappropriately fails

Issue Description:

I've been using the Development Snapshot from 2017-12-19 to start working with conditional conformances. For some reason when using a conditional conformance on a protocol that inherits from another protocol the default implementation for the parent protocol is not recognized, but it works when using the "regular" conformance.

I have an hierarchy of protocols that implement resources on the objects that conforms to it.

protocol MainProtocol {}

protocol ResourceProtocol {}

protocol SpecializedResourceProtocol: ResourceProtocol {}

I also have a hierarchy of protocols that provide code to handle this objects. The implementation for the function required by `UseResourceProtocol` is given by `UseSpecializedResourceProtocol`.

protocol UseResourceProtocol {
    associatedtype T: MainProtocol, ResourceProtocol
    
    static func foo(t: T) -> String
}

protocol UseSpecializedResourceProtocol: UseResourceProtocol where T: SpecializedResourceProtocol {
}

extension UseSpecializedResourceProtocol where T: SpecializedResourceProtocol {
    static func foo(t: T) -> String {
        return "bar"
    }
}

When creating a struct conforming to `UseSpecializedResourceProtocol` the code compiles as expected and the struct `UseSpecializedResource` has the implementation of `foo(t: T)` provided by the extension of `UseSpecializedResourceProtocol`.

struct UseSpecializedResource<T: MainProtocol & SpecializedResourceProtocol>: UseSpecializedResourceProtocol {
}

However, when trying to make a struct conform to `UseSpecializedResourceProtocol` using conditional conformance I got a compilation error saying that UseResource<T> does not conform to `UseResourceProtocol`, the parent protocol. I receive no error about the child protocol, only about the parent.

struct UseResource<T: MainProtocol> {}

extension UseResource: UseSpecializedResourceProtocol where T: SpecializedResourceProtocol {}

ERROR: Type 'UseResource<T>' does not conform to protocol 'UseResourceProtocol'

After some testing I've discovered that making an extension of the parent protocol and conforming `UseResource<T>` to the parent protocol makes the code compile and works as expected.

extension UseResourceProtocol {
    static func foo(t: T) -> String {
        return "foo"
    }
}

struct UseResource<T: MainProtocol> {}

extension UseResource: UseResourceProtocol where T: ResourceProtocol {}

extension UseResource: UseSpecializedResourceProtocol where T: SpecializedResourceProtocol {}

After adding this extension and the conformance to the parent protocol the code compiles and a call to `UseResource<T>.foo(t:T)` calls the implementation provided by the child protocol as expected, printing "bar".

The extension of `UseResourceProtocol` is never used, but without it this extension the code does not compile.

Also, the compiler does not give any warning about the "redundance" conformance of UseResource to `UseResourceProtocol`, despite conforming to UseResource twice.

UPDATE: Just give the code one more try using the 2017-12-25 Development Snapshot and now the error messages are much clearer.

If I comment out the `UseResourceProtocol` extension (on line 38) the compiler complains that the implementation provided by `UseSpecializedResourceProtocol` is non-matching and give the suggestion of adding the implementation on the struct itself.

Candidate has non-matching type '<Self> (t: Self.T) -> String' [with T = T]

If I keep the `UseResourceProtocol` extension and comment out the redundant conformance of `UseResource` to `UseResourceProtocol` I get a Abort trap: 6 signal.

main.swift:32:1: error: type 'UseResource<T>' does not conform to protocol 'UseResourceProtocol'
extension UseResource: UseSpecializedResourceProtocol where T: SpecializedResourceProtocol {}
^
/Documents/Swift/CondConf/Sources/CondConf/main.swift:15:10: note: type 'UseResource<T>' does not conform to inherited protocol 'UseResourceProtocol'
protocol UseSpecializedResourceProtocol: UseResourceProtocol where T: SpecializedResourceProtocol {}

The `UseSpecializedResource` "regular" conformance (line 27) still works as expected, conforming to the inherited protocol even if I comment out the `UseResourceProtocol` extension.

@belkadan
Copy link
Contributor

cc @DougGregor

@swift-ci
Copy link
Collaborator Author

Comment by Edison Santiago (JIRA)

Just tried to compile it with the new Xcode 9.3 Beta and still got the same error =/ Am I doing something wrong? Wasn't that supposed to work this way?

@huonw
Copy link
Mannequin

huonw mannequin commented Apr 5, 2018

The problem here was the compiler was "accidentally" not implying conformances, that is, writing a conformance to a child protocol wouldn't automatically create the conformance to the parent. As of #15268 (which is included in the 2018-04-04 development snapshot), the compiler gives even better errors:

Conditional conformance of type 'UseResource<T>' to protocol 'UseSpecializedResourceProtocol' does not imply conformance to inherited protocol 'UseResourceProtocol'

It also includes some (currently slightly syntactically incorrect, but fixing them now) fixits that offer two options for an appropriate conformance.

Additionally, the extension UseResourceProtocol declaring foo is used: every protocol conformance needs to provide all the methods and properties that the protocol wants, and just the plain extension UseResource: UseResourceProtocol where T: ResourceProtocol (let's call this extension A) doesn't have a foo that applies (the one from extension UseSpecializedResourceProtocol where T: SpecializedResourceProtocol doesn't work, because the UseResource in extension A isn't known to conform to UseSpecializedResourceProtocol, as that conformance needs T: SpecializedResourceProtocol, but we only have T: ResourceProtocol). The extension UseResourceProtocol is behaving the same as

extension UseResource: UseResourceProtocol where T: ResourceProtocol {
    static func foo(t: T) -> String { 
        return "foo"
    }
}

UseResource<MyModel>.foo(t: MyModel()) calls the UseSpecializedResourceProtocol version of foo, because that's the most specialised foo that works (MyModel conforms to SpecializedResourceProtocol, so UseResource conforms to UseSpecializedResourceProtocol). If we do the same call with a type that only conforms to ResourceProtocol, it prints "foo" as expected:

struct MyModel2: MainProtocol, ResourceProtocol {}
print(UseResource<MyModel2>.foo(t: MyModel2())) 

As the diagnostics have dramatically improved, I'm going to tentatively closed this as resolved. What do you think edisonlsm (JIRA User)?

@swift-ci
Copy link
Collaborator Author

swift-ci commented Apr 5, 2018

Comment by Edison Santiago (JIRA)

Hello @huonw![]( Thanks for the clarification and the fixes)

Just downloaded the 2018-04-04 toolchain and the new compiler error message made it much better![]( Now I managed to make it work just by following the fixit and going with the first option) And it worked with the Xcode 9.3 default Swift, yay 😃

extension UseResource: UseResourceProtocol where T: SpecializedResourceProtocol {}

Since I have a lot of protocols like `UseResourceProtocol` in my code here (I only used one to illustrate the problem, but I have more), I need for the conformance to be on the extension and making it conform to `UseResourceProtocol` and `UseSpecializedResourceProtocol` solved it for me!

I assumed that the conformance would be implied to the parent protocol and that's what caused my entire problem here. Is this a bug at this moment or is this the intended behavior of the language?

@huonw
Copy link
Mannequin

huonw mannequin commented Apr 5, 2018

It was a good assumption to make, and it was incorrect of the compiler to be so confusing when people make that assumption.

Having the implied conformance is how it works for non-conditional ones, and was actually what the proposal for conditional conformances originally said. However, we realised that this behaviour may be a surprising and suboptimal in some cases, so we recently amended that proposal to change the behaviour. If you're interested in the reasoning, that pull request and the discussion thread go into some detail, and feel free to ask any questions here or on the forum.

For your specific code, one could phrase the reasoning as: what if UseResource: UseResourceProtocol was actually meant to apply for T: ResourceProtocol, not T: SpecializedResourceProtocol. If the conformance was implied, you wouldn't notice that you're accidentally getting the second bound instead of the first.

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself
Projects
None yet
Development

No branches or pull requests

2 participants