Implement a warning, if a subclass doesn't conform to the protocol of the parent class

UPDATE:

My first question was wrong, please skip to the reasked question here.

OLD POST:

While working with SwiftNIO, I faced a problem, that was very hard to find out (for the background: I made a ChannelInboundHandler subclass and implemented the channelRead-func, but that function was never called because of a typo in my subclass).

But let's abstract the problem with dummy code:

protocol DummyProtocol {
    
    func test(parameter1: String, parameter2: Int)
}

class DummyClass: DummyProtocol {
    
}

Of course I receive the Error: "Type 'DummyClass' does not conform to protocol 'DummyProtocol'"

Now let's say, that we implement the protocol correctly like this:

class DummyClass: DummyProtocol {
    
    func test(parameter1: String, parameter2: Int) {
        print("do sth. with \(parameter1) and \(parameter2+3)")
    }
    
}

So the DummyClass now has kind of a default implementation if someone wants to subclass it. So let's go further and make a subclass like this:

class DummySubClass: DummyClass {
    
}

Adding nothing to the class works fine, because the parent class implements the protocol.

The main problem starts, if you want to override these default implementations in the subclass, but if there is a mistake in the func name or func parameters like this:

class DummySubClass: DummyClass {
    
    func test(parameter1Wrong: Float, parameter2Wrong: Bool) {
        print("we don't get a warning, that class does not conform to the protocol")
    }
    
}

I understand, that this is completely okay, because the code is not wrong. It's just implementing a new func with different parameters. But I was searching a long time in current project for the bug, because I had a typo in the parameter name and as the Framework (in this case SwiftNIO) will call the test-method, the implemented code in the subclass never gets called, because it's a completely different method.

What do you think? In my opinion the compiler should check, if there are methods, that have the same name as those of the protocols and it should give a warning about that, that maybe the protocol isn't implemented correctly.

1 Like

I can confirm that we have seen the issues of 'near misses' of protocol methods that have default implementations many times before.

I agree that it would be great if Swift provided a way (similar to override func for subclasses) where the programmer could specify that they are intending to implement a protocol method (as opposed to adding a new method with a name that may or may not be close to a protocol method).

1 Like

It's not possible to implement methods in a superclass's protocol (unless it's an @objc protocol), so I'm not sure there's much to do in this specific case. (Which is separate from whether or not override-but-for-protocols is a good idea, but that's discussed elsewhere.)

In SwiftNIO's case the problem can be reduced to:

NIO code:

protocol ChannelInboundHandler {
    // many other events here
    func channelActive(context: ChannelHandlerContext)
}

extension ChannelInboundHandler {
    // default implementation
    func channelActive(context: ChannelHandlerContext) {
        context.fireChannelActive()
    }
}

user code:

final class MyHandler: ChannelInboundHandler {
                    //  +--- typo here
                    //  v
    func channelActive(cntext: ChannelHandlerContext) {
        // do something
    }
}

In SwiftNIO, this will lead to the user code just silently not handling the channelActive event because of the typo in the intended protocol method implementation. It would be great if the programmer could somehow tell the compiler about the intention to implement the protocol method.

Right. That's a separate issue that has been discussed many times. @Lupurus's issue is closer to SR-103, though it's not the same.

1 Like

This is a superclass–subclass relationship, and the protocol is irrelevant (the separate superclass–protocol relationship cannot be affected by subclasses, as @jrose pointed out). In any superclass–subclass relationship, you need to use override when you intend to override, and to not use it when you don’t. If you try to use override, but nothing matches, the compiler will complain; if you are not overriding, but it happens to match, the compiler will also complain. In your specific case, the two separate errors (missing override and mismatched parameters) added up such that they successfully described a different yet valid intention. Had you made either error in isolation, the compiler would have objected just like you want.

However...

ChannelInboundHandler is a protocol, not a class, so maybe you really are talking about a protocol–conformer relationship like @johannesweiss understood, and the demonstration example doesn’t really describe what you were trying to ask?

I'm sorry, I was too fast creating my example. Of course I meant the case, that Johannes described. @jrose: is this problem already discussed anywhere? However, this thread should be closed or should I update my question?

2 Likes

I reask again:

I am using SwiftNIO and I faced a problem with protocols, that caused me searching a long time for the bug, because my app didn't work as expected. I try to abstract the problem with an example code:

Let's say, we have a protocol, that implements methods for handling values:

protocol HandlerProtocol {
    
    func handleMethod(parameter1: String, parameter2: Int)
    
}

The framework, that implements this protocol, could look like this:

struct MyFramework {
    
    var handler: HandlerProtocol?
    
    // some other methods, doing very important stuff
    // and of course one of them will call:
    func handleMessages(parameter1: String, parameter2: Int) {
        self.handler?.handleMethod(parameter1: parameter1, parameter2: parameter2)
    }
    
}

Now let's assume, that the protocol has a lot more methods and while creating all theses methods, I came to the decision, that the user will not need all of those methods. So I will make some "default implementations" with an extension:

extension HandlerProtocol {
    
    func handleMethod(parameter1: String, parameter2: Int) {
        print("do sth. with \(parameter1) and \(parameter2)")
    }
    
}

Now the problem starts, if I implement this protocol to a class but if I will make a typo like this ("param" insteand of "parameter"):

class DummyHandler: HandlerProtocol {
    
    func handleMethod(param1: Float, param2: Int) {
        // I'm never called by the handleMessages-method :( :(
    }
    
}

In my opinion the compiler should check, if there are methods, that have the same name as those of the protocols and it should give a warning about that, that maybe the protocol isn't implemented correctly.

The other suggestion would be to make sth. like this (instead of an extension)?

implement protocol HandlerProtocol {

    func handleMethod(parameter1: String, parameter2: Int) {
        ....
    }

}

In this case, the compiler and everyone looking to the code would know, that there are default implementations (I am still confused about the way of extension, because to be honest, the code above gives methods with bodies to a protocol, but normally protocol methods doesn't allow bodies).

Xcode could then tell the user: "Hey, the protocol has default implementations! Do you want to implement one, two or all methods by yourself?"

It's not the param/parameter problem. Your function's signature in the DummyHandler class identifies the first parameter as a Float, not a String. This could legitimately be a method you've defined for the class that takes a Float and an Int. The entire function signature, name, types, return type(?) are used to match function definitions and define protocol conformance.

Sorry, but unfortunately it is:

class DummyHandler: HandlerProtocol {
    
    // case 1: different types
    func handleMethod(parameter1: Float, parameter2: Int) {
        // I'm never called by the handleMessages-method :( :(
    }
    
    // case 2: different parameter names
    func handleMethod(param1: String, param2: Int) {
        // I'm also never called
    }
    
}

It doesn't mind, if the method parameters (names or types!) are different, you don't get a warning and this can be very confusing, because it's very hard to find out, what's wrong.

Please copy the whole example code and then remove the extension. Also remove the method of "case 1" in my example above. You will get the error:

"Method 'handleMethod(param1:param2:)' has different argument labels from those required by protocol 'HandlerProtocol' ('handleMethod(parameter1:parameter2:)')
Replace 'param1: String, ' with 'parameter1 param1: String, parameter2 '"

You even can let Xcode fix this automatically!

Based on your revised example, @johannesweiss’s original response was perfect. No such functionality exists yet. Many of us would like to have such functionality. It needs someone to design it and carry it through the evolution process.

In the meantime, your best defences against this sort of typo are static analysis and comprehensive tests. If your intended override sticks out as just dead code, it is an immediate signal that something is mismatched. Dead code can be caught by various static analysis tools or by simply enforcing 100% test coverage.

Hm, I just searched the forum for this topic and I found some corresponding threads:


I also found a topic to the override thing:

It seems, that there is no review or these pitches, but obviously the language now supports the way of default implementations via adding an extension.

In my opinion, this way is very confusing. An extension is described in the documentation as: "Extensions add new functionality to an existing class, structure, enumeration, or protocol type"

Bringing a default implementation is no added functionality. It is an implementation. My understand of extending a protocol would mean, that I add a new method, that also needs to be implemented by all classes or structs, that uses this protocol. This way can be necessery, if I don't have access to the source code, that is defining this protocol.

Interestingly, this is at least not possible!

extension HandlerProtocol {

    func newMethod()

}

This brings the error "Expected '{' in body of function declaration". So extending a protocol is not possible in Swift. Or do I miss sth.?

So in my opinion, this kind of default implementations should be made with a keword like implement protocol (or sth. like this), not with an extension.

No one with any thoughts or comments on this?

Of course it will. Both of your functions are overloads of handleMethod, and because a default handleMethod is defined, the protocol constraints are satisfied, so, no error.

This is the fine line between allowing for a useful capability (function overloading), and the potential for unwanted specifications because of a, for example, text editing error.

Clearly the problem is that we currently lack any syntax to convey that something is supposed to be a witness for a protocol requirement. Where exactly requirements or default implementations are allowed - in protocols or their extensions - is a whole different topic.
People have already mentioned a keyword like implement or, as I would prefer it, witness would be the straightforward approach, but someone has to at least write a proposal and start a pitch to begin with.
Note that a simple warning telling you that the signatures almost match is not enough, because we need a proper way to silence it.

You extend a protocol by defining a new protocol that conforms to the original.

protocol A { }
protocol B : A {
    func Bfunc() -> Int
}

It does not seem feasible to allow for re-defining a protocol with new requirements, like you intend, especially since you don't necessarily have the source code for classes, structs, or enums that would allow you to modify them to meet the new requirements. Placing new requirements on existing protocols, unless you have access to all the code that uses the original protocol definition so you can modify that code, is not feasible.

Swift, in my opinion, is already suffering from surfeit of new keywords, attributes, other language additions, etc., which is making the language look more and more like C++ in it's level of complexity. Note that I am a long-time C++ developer, and still use if for a good portion of programming, and it's complexities have taken a long time to absorb and be successful most of the time. I'm afraid that Swift is "swiftly" approaching that level of complexity.

1 Like

The compiler guys know what a "witness" is. I think to most users, "implement" would be more intuitive as to what concept the keyword is representing.

..Or we could actually just reuse required. I still feel uneasy about the inevitable style discrepancies this would stir up, but the good news is that since the keyword is only meaningful to the type-checker, there will be an opportunity to update official guidelines and have resilient libraries adopt it.

There's already @_implements (at least I think so - documentation is poor :-), so basically the functionality is there and just has no proper syntax.
I'd still prefer no new keyword for this, but rather have something like func Protocol.requirement() {}.

@_implements is used to satisfy a protocol requirement using a member with a different name. It can't be used to assert that a member implements a requirement.

Terms of Service

Privacy Policy

Cookie Policy