Add ability to make a call to super a requirement in subclass overrides

Many functions require a call to super at some point in the overriden
function. This is however not checked by the compiler and is left for
the developer to check the documentation whether a call to super is
required. Not calling super when expected leads to bugs that are many
times hard to track.

I propose adding a keyword (ex. overriderequiressupercall or
requiressupercall) to mark a function that requires its subclass
override to call super at some point, thus making it a compile failure
when the super call is omitted.

6 Likes

This is a nice idea. If there are no complications that are not worth implementing it, that is.

Something like this doesn't look too bad:

superrequired func foo(...) -> ... {...}

Sometimes documentation notes that super has to be called either first, or as a final step.

So complementing your proposal, how about

superrequired(prefix | postfix) func foo(...) -> ... {...}
// or
superrequired(start | end) func foo(...) -> ... {...}
1 Like

Adding the ability to additionally specify superrequired(start | end) func foo(...) -> ... {...} makes sense as then everything would be checkable at compile time. Leaving out (start | end) would imply that it makes no difference where super is called as long as it is called at some point.

Another option could be:
superrequired(first | last) func foo(...) -> ... {...}

Please search the forum for this topic if you want to revive it, because it was already discussed before. Then summarize everything that already has been considered, so that we don't rehash it from zero.

2 Likes

Ok. I did try to search for the topic without success before. What is the topic where this was discussed?

I don't remember myself, but I just tried myself by searching for super and found this. You can do further research then. ;)

After skimming through the previous threads the proposal did not seem to make it to a formal review. Not sure why, I noticed no important objections. There were just good arguments against the before/after flags, since they would disallow fairly legitimate cases like logging or mangling the arguments before calling super. Also, the existing NS_REQUIRES_SUPER macro from the old world is worth mentioning in this context.

2 Likes

I too skimmed thru the previous threads thinking that there were legitimate arguments against the before/after flags, but I remain unconvinced that some form of superrequired would not be beneficial. Is not one of the main goals in Swift to catch as many errors/bugs in the compile stage? Leaving a call to super as only a matter of documentation does not seem in line with this philosophy to me.

Does anyone think that this would warrant a more rigorous summary of the previous threads and some further investigation/discussion?

It is a regression compared to the NS_REQUIRES_SUPER available for Objective-C this all things considered, no?

Thank you for reviving this everyone. I never understood why it was never actually reviewed. And at some point it was just closed as out of scope and I was unable to reopen it. I’d love to get a better explanation. I have found numerous issues with people making these mistakes in our codebase.

Is the final(ish) proposal draft to be seen somewhere? Can we revive it and try again? But:

The broader range of proposals for Swift 5 compared to Swift 4 incurs the risk of diluting the focus on ABI stability. To mitigate that risk, every evolution proposal will need a working implementation, with test cases, in order to be considered for review. An idea can be pitched and a proposal written prior to providing an implementation, but a pull request for a proposal will not be accepted for review until an implementation is available.

Here is the closed PR: https://github.com/apple/swift-evolution/pull/211

Hm, I see. It seems that this is out of scope for Swift 5, too, unless we can augment the proposal with a working implementation?

NS_REQUIRES_SUPER is a workaround for poorly design classes. A class that requires a method to call super should declare this method final and provide other hooks that don't have this requirement for subclasses.

5 Likes

Unfortunately these “poorly designed classes” are the bread & butter for many people working in Swift. Would it make sense to introduce rules for the UIViewController methods into a Swift linter instead of the Swift compiler? That would be at least some progress.

2 Likes

A final method with a separate hook scales poorly in deep class hierarchies. For example, it is unreasonable to require every subclass of UIView to define a unique hook for draw(_:).

5 Likes

Excellent point! I've always found that saying x is poor design, has more to do with the latest fad then actually solving programming problems (I'm not suggesting that there are no bad designs). There is no one technique that solves all problems and therefore supporting different techniques is a good thing at least in my opinion. Having a requiressuper formalizes the intent of the class designer and adds robustness to the codebase.

It's definitely a useful feature that addresses a real problem, at least in Cocoa: There are some methods calls that you have to forward when you override, and others that require you not to do so - and the compiler doesn't help you with those rules that are formulated in the documentation of UIViewController ;-)

A way to express exactly what you have to do when you override could also replace property observers, which imho are quite heavyweight (willSet, didSet, oldValue, newValue...) for such a small (albeit useful) feature.

Have you considered something like:

override func viewDidLoad() {
    willCall {
        // Do pre-super call stuff.
    }
    didCall {
        // Do post-super call stuff.
    }
}

This way you follow the willSet, didSet, ... that @Tino mentioned, but will maintain Swift-like nature without introducing additional attributes.

A subsequent thought is something like:

final(body) func viewDidLoad()

which would mark the method as not overridable in a sense that you can fully replace the body of it, but you can still override it with willCall and didCall. But that may be too wild...

2 Likes