Empty Function Body
ID |
swift.empty_function_body |
Severity |
high |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Code Smell |
Language |
Swift |
Tags |
code-style, dead-code |
Description
Reports a function, initializer, deinitializer or named closure whose body is present but holds no statement, and no comment explaining why. A protocol requirement has no body at all and is never a candidate — only a declaration that opened a pair of braces and left them blank.
Rationale
class ReportView {
init(rows: [String]) {} // ignores every argument it was given
func reload() {} // brings no functionality
}
let handler: () -> Void = { } // reads as a stub
An empty body brings no functionality yet reads as if it fulfils a requirement. A caller sees a
method named reload and assumes reloading happens; a reviewer sees an initializer taking rows
and assumes the rows are stored. Almost always the body is an unfinished stub or a placeholder
that was never wired up, and the failure it causes surfaces far from the declaration.
A genuinely intentional no-op is not the problem — an undocumented one is, because a reader cannot tell the two apart. Several Swift constructs say "deliberate" on their own and are therefore exempt:
-
a body holding only a comment, or a comment written immediately above the declaration: either is the documented reason for the blank implementation, and Swift documents a member from outside its braces (
/// Nothing to do hereabove thefunc); -
an
override. Swift requires the keyword, so the signal is exact: the author cannot change the signature, and a no-op override is the documented way to neutralise inherited behaviour; -
an
opendeclaration. It exists to be overridden from another module, so a blank body is the base implementation a subclass replaces — theoverridesignal read from the other end of the relationship; -
a member that may be satisfying a contract its enclosing type signed up to. Swift has no
overridekeyword for protocol conformance, so nothing on the member itself says it is a requirement; the declaration that does is the enclosing type’s supertype list, and that is what is read. Each name in it is resolved against the protocols declared in the same file and then against the project-wide model: a resolved protocol answers exactly, and a name that resolves to nothing is a supertype outside the scanned sources whose requirements cannot be listed at all — an unknown contract is not read as the absence of one. This covers a conformance stub, a test double implementing the protocol it stands in for, and a delegate method a type has nothing to do for. A type that declares no supertype signed up to nothing, and its blank members are still reported; -
a default implementation in a protocol extension, which is how Swift expresses an "optional" requirement. The extended type is matched against the protocols declared in the same file first, and then against the project-wide model for a protocol declared elsewhere in the scan;
-
a parameterless initializer.
public init() { }is the standard way to expose a default initializer, since a struct’s implicit memberwise initializer is internal. An initializer that takes parameters and ignores them is not exempt by arity — that is the stub this rule exists to catch; -
an empty closure in a callback slot — a call argument, a trailing closure, or a parameter default.
items.forEach { }andonChange: { }are deliberate do-nothing wiring. A closure bound to a name is still reported: like a named function, it reads as a stub.
@objc and @IBAction are deliberately not exempt. Objective-C visibility says nothing about
whether the body was meant to stay blank, and both attributes are common enough on ordinary
methods that exempting them would empty the rule of most of its findings.
An empty loop or if body belongs to swift.empty_blocks, and an empty catch to
swift.empty_catch_block. An empty computed-property accessor is out of scope.
Remediation
Implement the body, remove the declaration, throw so the gap is loud, or state in a comment why the blank body is intended.
class ReportView {
private let rows: [String]
init(rows: [String]) {
self.rows = rows
}
func reload() {
fatalError("reload() is not available on a detached view")
}
func flush() {
// Nothing to flush: rows are written through on every append.
}
}