Can Not Conform to Protocol by Creating Extension with Where Clauses

Can not conform to protocol by creating extension with Where Clauses

This is pretty interesting. Long story short (okay maybe not that short) – it's an intentional side effect of #12174, which allows for protocol extension methods that return Self to satisfy protocol requirements for non-final classes, meaning that you can now say this in 4.1:

protocol P {
init()
static func f() -> Self
}

extension P {
static func f() -> Self {
return self.init()
}
}

class C : P {
required init() {}
}

In Swift 4.0.3, you would get a confusing error on the extension implementation of f() saying:

Method 'f()' in non-final class 'C' must return Self to conform to protocol 'P'

How does this apply to your example? Well, consider this somewhat similar example:

class C {}
class D : C {}

protocol P {
func copy() -> Self
}

extension P where Self == C {
func copy() -> C {
return C()
}
}

extension C : P {}

let d: P = D()
print(d.copy()) // C (assuming we could actually compile it)

If Swift allowed the protocol extension's implementation of copy() to satisfy the requirement, we'd construct C instances even when called on a D instance, breaking the protocol contract. Therefore Swift 4.1 makes the conformance illegal (in order to make the conformance in the first example legal), and it does this whether or not there are Self returns in play.

What we actually want to express with the extension is that Self must be, or inherit from C, which forces us to consider the case when a subclass is using the conformance.

In your example, that would look like this:

protocol Typographable {
func setTypography(_ typography: Typography)
}

extension UILabel: Typographable {}

extension Typographable where Self : UILabel {

func setTypography(_ typography: Typography) {

self.font = typography.font
self.textColor = typography.textColor
self.textAlignment = typography.textAlignment
self.numberOfLines = typography.numberOfLines
}
}

which, as Martin says, compiles just fine in Swift 4.1. Although as Martin also says, this can be re-written in a much more straightforward manner:

protocol Typographable {
func setTypography(_ typography: Typography)
}

extension UILabel : Typographable {

func setTypography(_ typography: Typography) {

self.font = typography.font
self.textColor = typography.textColor
self.textAlignment = typography.textAlignment
self.numberOfLines = typography.numberOfLines
}
}

In slightly more technical detail, what #12174 does is allow the propagation of the implicit Self parameter through witness (conforming implementation) thunks. It does this by adding a generic placeholder to that thunk constrained to the conforming class.

So for a conformance like this:

class C {}

protocol P {
func foo()
}

extension P {
func foo() {}
}

extension C : P {}

In Swift 4.0.3, C's protocol witness table (I have a little ramble on PWTs here that might be useful in understanding them) contains an entry to a thunk that has the signature:

(C) -> Void

(note that in the ramble I link to, I skip over the detail of there being thunks and just say the PWT contains an entry to the implementation used to satisfy the requirement. The semantics are, for the most part, the same though)

However in Swift 4.1, the thunk's signature now looks like this:

<Self : C>(Self) -> Void

Why? Because this allows us to propagate type information for Self, allowing us to preserve the dynamic type of the instance to construct in the first example (and so make it legal).

Now, for an extension that looks like this:

extension P where Self == C {
func foo() {}
}

there's a mismatch with the signature of the extension implementation, (C) -> Void, and the signature of the thunk, <Self : C>(Self) -> Void. So the compiler rejects the conformance (arguably this is too stringent as Self is a subtype of C and we could apply contravariance here, but it's the current behaviour).

If however, we have the extension:

extension P where Self : C {
func foo() {}
}

everything's fine again, as both signatures are now <Self : C>(Self) -> Void.

One interesting thing to note about #12174 though is that is preserves the old thunk signatures when the requirements contain associated types. So this works:

class C {}

protocol P {
associatedtype T
func foo() -> T
}

extension P where Self == C {
func foo() {} // T is inferred to be Void for C.
}

extension C : P {}

But you probably shouldn't resort to such horrific workarounds. Just change the protocol extension constraint to where Self : C.

Protocols with where clause don't work when referring them as protocols

Unlike e.g. Objective-C protocols, a Swift protocol doesn't conform to itself. As a result, protocol MyModel: Naming doesn’t satisfy the Self.Model: Naming constraint of your protocol extension.

Class does not conform to protocol, but struct does

It does not compile because your

extension MyProtocol where Self == MyClass

provides a default method only for MyClass itself, but not for possible subclasses. Changing the constraint to

extension MyProtocol where Self: MyClass

makes the code compile. Alternatively prevent the creation of subclasses with

final class MyClass : MyProtocol {}

This is not a problem for MyStruct because struct types cannot be inherited from in Swift.

Swift protocol with where Self clause

The ability to put superclass constraints on protocols declarations (that is, being able to define protocol P where Self : C where C is the type of a class) was a premature consequence of
SE-0156, and the syntax should have been rejected in Swift 4.x until the feature was implemented. Attempting to use this feature in Swift 4.x can cause miscompilation and crashes, so I would avoid using it until Swift 5.

In Swift 5 (Xcode 10.2) the feature has now been implemented. From the release notes:

Protocols can now constrain their conforming types to those that
subclass a given class. Two equivalent forms are supported:

protocol MyView: UIView { /*...*/ }
protocol MyView where Self: UIView { /*...*/ }

Swift 4.2 accepted the second form, but it wasn’t fully implemented
and could sometimes crash at compile time or runtime. (SR-5581)
(38077232)

This syntax places a superclass constraint on MyView which restricts conforming types to those inheriting from (or being) UIView. In addition, the usage of MyView is semantically equivalent to a class existential (e.g UIView & MyView) in that you can access both members of the class and requirements of the protocol on the value.

For example, expanding upon the release notes' example:

protocol MyView : UIView {
var foo: Int { get }
}

class C : MyView {} // error: 'P' requires that 'C' inherit from 'UIView'

class CustomView : UIView, MyView {
var foo: Int = 0
}

// ...

let myView: MyView = CustomView(frame: .zero)

// We can access both `UIView` members on a `MyView` value
print(myView.backgroundColor as Any)

// ... and `MyView` members as usual.
print(myView.foo)

Can you conditionally extend RawRepresentable to conform to another protocol?

As your error message is already telling you there is no way to add inheritance to protocols. You only can add inheritance to objects.

Therefore this won't work:

extension RawRepresentable: DatabaseStoreable {}

However, you could add an extension as this:

protocol DatabaseStoreable {}

extension Int: DatabaseStoreable {}
extension Double: DatabaseStoreable {}
extension String: DatabaseStoreable {}
extension Data: DatabaseStoreable {}

func storeValue<T: DatabaseStoreable>(_ value: T) {
print("T store(\(value))")
}

extension RawRepresentable {
func storeValue<T: DatabaseStoreable>(_ value: T) {
print("RawRepresentable store(\(value))")
}
}

enum Test: String {
case A
case B
}

class DataBaseStore: DatabaseStoreable {}

let myTest = Test.A
let databaseStore = DataBaseStore()
myTest.storeValue(databaseStore) // prints RawRepresentable store(__lldb_expr_15.DataBaseStore)


Related Topics



Leave a reply



Submit