Annotation/Keyword To Automatically Call Super in Override Methods

Introduction

This proposal outlines a solution to a common problem: overriding a method but forgetting to call its super method first. This is a common problem that creates hard-to-find bugs in an application and can be very confusing to debug for programmers who are new to Swift and/or iOS, or just new programmers in general.

Motivation

When programming it is common to override methods in order to add functionality, not necessary replace the existing functionality. For example, UIViewController's viewDidLoad() method is commonly overridden to add more setup code. Therefore the first line of these methods are very commonly super.someMethod(). Often bugs will creep into an application when a programmer forgets to add this super line (what, a programmer forgetting to add something?!) which can be very difficult to debug at times.

Proposed Solutions

First Proposal (@super annotation)

This proposal seeks to add a @super annotation to Swift that when added before a method will automatically call the super's method.

This common pattern:

override func viewDidLoad() {
    super.viewDidLoad()

    // More code goes here...
}

Now becomes this:

@super override func viewDidLoad() {
    // super.viewDidLoad() is automatically called here.

    // More code goes here...
}

Second Proposal (overrideWithSuper keyword)

This proposal seeks to add a overrideWithSuper keyword to Swift that when used will automatically call the super's method.

overrideWithSuper func viewDidLoad() {
    // super.viewDidLoad() is automatically called here.

    // More code goes here...
}
1 Like

This is an idea that has been discussed a few times on this list:

I think you'll find these links useful for illustrating what the difficulties have been in the past when advancing the idea. Additionally, these conversations show how others have tackled the problem in their own code (for instance, there is a linter rule mentioned in the most recent discussion linked above).

The forums now have a convenient search function where anyone can easily call up these past conversations; you may find additional discussions using that function which I've missed.

5 Likes

Thanks for the reply. I did find some of these pitches using the search function before I posted, but most of them are for making the super call a requirement. The pitch here is more similar to @Mustafa_Sabur's Add code to super methods pitch. In there I really like @BigZaphod's override(before) and override(after) syntax. It allows for the super method to be automatically called when desired, but does not make having to do as such a requirement (as there are of course legitimate cases to not call a method's super version).

I don't see the benefit of this - if you can remember to annotate the method with @super, then you can remember to call super.whatever(), right?

What I think it would be better to have something like NS_REQUIRES_SUPER, so we can annotate our methods with it, so we can throw a warning/error if the overridden method does not call super.whatever().

Automatically calling super.whatever() seems too magical to me and can hurt readability/understanding of the method.

27 Likes

I’m with @suyashsrijan on this one. If you can remember to annotate the method, you can remember to call super.method.

I think we can approach it from another perspective. Annotate a method in SuperClass to indicate that ChildClass’s should do something when overriding.

A mechanism like init Which requires you to call super.init at some point.

This syntax is silly, but as a start: @discardableSuperMethod

If you annotate your SuperClass’s method this attribute, compiler will not warn you that you forgot to call super, and will warn otherwise.

2 Likes

This proposal seems only to work with those methods return Void only. What if a method returns CGFloat and the method in a subclass wants to manipulate the result from super?

Besides, at which specific location should we insert the super call? The sequence may matter in difference cases. In one scenario the subclass may want to put the super call at the start of the method body, but in another scenario it maybe seems more appropriate to be put at the end.

4 Likes

This is not feasible since there are times when a call to super needs to happen after additional code, or even in the middle of it.

4 Likes

In case of layoutSubviews in UIKit, the super call can also happen multiple times as well.

The only requirement that the author of an API might want to set is that the overriden method or property must eventually call super at some point, but not explicitly dictate the order, position or number of calls.

3 Likes

This topic has been discussed previously a few times in various forms.

Automatically calling super is, I think, the least helpful and most restrictive version of the proposal because:

  1. You still need to remember to annotate the function with @super.
  2. You lose the ability to control when it is called.
  3. It completely falls down when the function needs to return a value. In that case, you almost always want to either manipulate the value returned by the super call, or ignore it entirely.
  4. The override(before) and override(after) add some clarity and resolve #2 above, but doesn't do anything to solve the issues described in #3 above.

I like @farzadshbfn's @discardableSuper (no need to add "method"), except that I see the potential for a lot of broken code. If the default becomes that a call to super is required unless the superclass' method is annotated with @discardableSuper, that will require super on a larger number of existing class methods where the super call is currently optional. Some of these may not be resolvable if they occur with 3rd party dependencies that you have no control over. It might make sense to do anyway, but problems and effort required to fix them should at least be considered. One possible solution would be a compiler flag that would essentially mark all of file's or project's methods as @discardableSuper, but that seems heavy-handed.

The @discardableSuper idea also assumes that the superclass' author will always know whether or not it is safe to not call its methods. While I think that is true in most cases, I expect there will be use cases the superclass' author did not foresee or could not have foreseen. This could lead to the compiler flag describe above being used in many projects, negating the benefits we were hoping to achieve.

In Objective-C we have NS_REQUIRES_SUPER, which would produce a compile warning if the subclass' overriding method didn't call super. We could certainly add something like that ("@requiresSuper" or similar), but this behavior is of little value, because:

  1. It requires the superclass to annotate the methods which require super to be called, for what should probably be the default case.
  2. Having the tag only result in a compile warning isn't consistent with the word "requires" in the name, and also means that the superclass author cannot depend on it being called, despite having marked it as @requiresSuper.

For all these reasons above, I'm in favor of these two additions:

  • Make it a compile error if the overriding function does not call super.
  • Allow it to opt out of this behavior by annotating the func definition with @ignoresSuper.

With this solution:

  • You don't have to remember to call super -- because you'll get an error if you don't.
  • You can still not call it if you really want to.
  • It doesn't have problems with return values like automatically calling super does.
  • You still get to choose when super is called.
  • It likely won't require many code changes, and there could be a "conversion mode" that simply adds @ignoreSuper for every overriding method where super is not called.
  • It avoids the possible large amount of work and possible compiler flag that @discardableSuper requires.
  • It prevents the errors we are trying to prevent!
2 Likes

Having the tag only result in a compile warning isn't consistent with the word "requires" in the name, and also means that the superclass author cannot depend on it being called, despite having marked it as @requiresSuper

Allow it to opt out of this behavior by annotating the func definition with @ignoresSuper

(emphasis mine)

What's the benefit then? You're arguing for it to be required, but then you can also opt out !?

Make it a compile error if the overriding function does not call super

This will definitely break source compatibility.

I suppose what we can do is to make calling super.method() the default behaviour and emit a warning if the user does not call super.method(), along with a fix-it to add @ignoresSuper to silence the warning. The warning can be upgraded to an error in a later Swift release.

The advantage is the you don't have to do anything until you want to ignore the call to super.method() for some reason and only in that scenario you would have to add an attribute. This is similar to how @discardableResult works - you only add it when you don't care about the result (which is not very often) and only in that scenario the compiler emits a warning along with a fix-it.

Just an idea, but if we added an NS_REQUIRES_SUPER equivalent, we might be able to sidestep the 'when to call super' problem by making the warning message configurable via the attribute (similar to availability messages). So, for example, one could write:

@requiresSuper("super should be called before any customization") func ...

And if super didn't appear in an override, the warning would read: super not called in foo: super should be called before any customization.

We would only be able to statically verify that super is present, and not that it's called in the intended way, but it would support all the different ways a superclass might want subclasses to call super in overrides.

What would be the benefit of adding a configurable warning diagnostic? Because we want super.method() to be called, but as @DevAndArtist mentioned, it doesn't matter where in the body its called or how many times its called, it should just appear at least once. So, what would be an example of a useful warning message (i.e. something that the client can add inside the attribute)?

Say super should be called at the very end of an override. The message could read: super should be called at the end of the method. The condition wouldn't be statically enforceable, but it would be a useful indicator to the programmer if they forgot to include super entirely.

I haven't thought about it much yet though, so the whole thing might be a bad idea.

I don't get the need to specify where super should be call. Can you give a relevant example where it is important that super be call first or last. The only method I can think about with such requirement is dealloc. And even for dealloc, as long as you are careful with what you do, there is nothing preventing you writing some code after the call to super (you may need to add some tracing code).

For instance, If I want to override a method to add some time profiling code, I must be able to add code before and after the call to super. Have you an example where it would be harmful for the superclass ?

1 Like

What happens when you need to wrap the super call within a closure? For instance:

func viewDidLoad() {
  something.withExtendedLiftime {
     ...
     super.viewDidLoad()
     ...
  }
}

The compiler has no way to tell in such a case whether the closure will be called zero, one, or many times. Given that Swift often rely on patterns like this, requiring super seems problematic to me, because I’m not sure we want to impede this.

About automatic calling of super at the end... in what order do things get called if you have a defer statement in the body of the function?

The benefit is reducing errors. The vast majority of times you subclass and override funcs, the correct action is to call super.method at some point. So most of the time when that call is missing, it is a mistake. Thus, it can be an error unless you specifically indicate that you know you are not calling super, and mean to do so, by annotating the overriding method with @ignoresSuper.

Yes, it definitely will break things, but there could be an automatic migration to add @ignoresSuper everywhere its is needed, so that it's not really an issue.

The thing is - now you have to remember two different attributes. It's much simpler to have one. You can either have (1) @requiresSuper, which can be added if you must call super.method(...) in the overridden function. This means there won't be a way to opt-out, because it will negate the need for the attribute in the first place, or (2) @ignoresSuper which can be added if you decide to not call super.method(...) for some reason. By default, the compiler will enforce that you always call super.method(...), which is probably going to be the common case.

The core team has a very high bar for changes that break source compatibility and I am not sure if this change would meet it. Typically, you add a warning + fix-it and then upgrade it to an error later, thus giving everyone enough time to adjust to the new change. For this change, I don't think we need to make it an error (or upgrade to an error in the future).

I am happy with @requiresSuper because it mirrors what you can do in Objective-C and it makes things consistent and simple. However, @ignoresSuper is also a very good solution and probably better than @requiresSuper because as you mentioned, most of the time when the call is missing, it's a mistake, so this would help catch it and if it's missing for a valid reason then the user can simply add @ignoresSuper to silence the warning.

I think you have misunderstood what I meant. There should not be a @requiresSuper, only @ignoresSuper.

I can see the appeal to match Obj-C, but I really think that @requiresSuper would be a mistake. Having an extra annotation on what should be the most common use case is poor design. Having it be absolute would also break existing source in a way that could not be worked around.

I was replying to @drewbenson who mentioned that both attributes to be present.