Hello Swift evolution community,
This is the first draft of my proposal "Super call enforcement". I look forward to hearing your thoughts.
Thank you.
Super call enforcement
- Proposal: SE-XXXX
- Author: Suyash Srijan
- Review Manager: TBD
- Status: Pending Review
- Implementation: apple/swift#32712
- Toolchain: macOS and Linux
Introduction
Introduce a new attribute to enforce that an overridden function should call the super
method in its body.
Swift-evolution thread: Super call enforcement
Motivation
It is quite common to override a method in order to add additional functionality to it, rather than to completely replace it. For example - one might override UIViewController.viewDidLoad()
to add additional setup code to it:
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Setup some views
}
}
On top of that, the superclass method may also sometimes require a call to itself in an override for the overall functionality to work correctly.
At present, there is no way to communiate this need other than adding it to the documentation and even experienced developers sometimes overlook this small detail and later run into various issues at runtime. In Objective-C, one can annotate a superclass method with the objc_requires_super
attribute and the compiler emits a warning when an overridden method is missing a call to the super
method in its body.
However, there is no such attribute in Swift. There are some sub-optimal solutions to this problem, for example:
class C1 {
final func foo() {
bar()
}
func bar() {}
}
class C2: C1 {
override func bar() {
// It's okay to elide the super.bar() because 'foo()' calls 'bar()'
}
}
However, this has a couple of problems:
- It doesn't work when you have a subclass of
C2
. - If your class is
public
, then you now have an additional API method in your interface that you have to document as something that no one should ever call directly. - The user of the API method loses control over when
super
is called.
Proposed solution
Introduce a new @requiresSuper
attribute to control super
calls in overridden methods, which will be equivalent to the objc_requires_super
attribute in Clang.
When a @requiresSuper
attribute is present on a method, any overrides of that method should call the super
method in their body. If the super
call is missing, the compiler will emit an error. The error can be suppressed by inserting the super
call and the compiler places no restrictions on where or how many times the call appears.
class C1 {
@requiresSuper
func foo() {}
}
class C2: C1 {
override func foo() {} // error: method override is missing 'super.foo()' call
}
class C3: C1 {
override func foo() { // Okay
super.foo()
}
}
An optional message can also be specified on the @requiresSuper
attribute to provide any additional information, which will be shown with the error message:
class C1 {
@requiresSuper("Call super as the final step in your implementation")
func foo() {}
}
class C2: C1 {
override func foo() {} // error: method override is missing 'super.foo()' call: Call super as the final step in your implementation
}
Any overrides of a method annotated with @requiresSuper
will also implicitly inherit the attribute, to make sure that all the overrides in a subclass chain call back to the super
method. This will be suppressed if the override is marked with @ignoresSuper
or if the override is in a final
class:
class C1 {
@requiresSuper
func foo() {}
}
class C2: C1 {
// Implicitly inherits '@requiresSuper' from 'C1.foo'
override func foo() {
super.foo()
// Do some other stuff
}
}
class C3: C2 {
override func foo() {} // error: method override is missing 'super.foo()' call
}
class C4: C2 {
@ignoresSuper
override func foo() {} // Okay
}
In some scenarios, the lack of super
call could be intentional. So, a new @ignoresSuper
attribute is also introduced. This will provide an alternate way to suppress the error if necessary:
class C1 {
@requiresSuper
func foo() {}
}
class C3: C1 {
@ignoresSuper
override func foo() {} // Also okay
}
Finally, a @requiresSuper
attribute will also be implicitly added for imported Clang decls which have been annotated with objc_requires_super
. This enables errors for any Objective-C method with such an attribute, such as UIViewController.viewDidLoad()
:
import UIKit
class MyViewController1: UIViewController {
override func viewDidLoad() { } // error: method override is missing 'super.viewDidLoad()' call
}
class MyViewController2: UIViewController {
override func viewDidLoad() { super.viewDidLoad() } // Okay
}
class MyViewController3: UIViewController {
@ignoresSuper
override func viewDidLoad() { } // Also okay
}
Source compatibility
This is an additive change, so it does not break any existing Swift code in general. However, there is a possibility of breaking existing Swift code if a method which overrides an ObjC method annotated with objc_requires_super
skips the super
call in its body. This would likely be quite rare in practice though, because any such code would likely not behave correctly without calling super
anyway.
Effect on ABI stability
This change has no effect on the ABI.
Effect on API resilience
Adding @requiresSuper
to any existing API method can break code if an override of that method does not already call super
in its body. Removing the attribute does not have any effect. Adding or removing @ignoresSuper
also has no effect, because it only applies to the API method itself and not any overrides of it.
Alternatives considered
- Do not diagnose missing 'super' calls.
- Diagnose missing 'super' calls but also enforce its placement: The ordering of a 'super' call (first, middle, last, etc) is not syntactical requirement. Adding such a requirement will complicate API design and usage and will also not be possible to prove statically in some scenarios. For simplicitly, it would be better to only dictate that a superclass method is called at some point, rather than also dictating its order and how many times its called.
Future Directions
We could also make the super
call check the default and get rid of the @requiresSuper
attribute. This would be source-breaking in practice, but can be mitigated by downgrading the error to a warning in a compatibility mode.