Duplicate Branches

ID

swift.duplicate_branches

Severity

high

Remediation Complexity

medium

Remediation Risk

low

Remediation Effort

medium

Resource

Code Smell

Language

Swift

Tags

code-style, duplicate-code

Description

Reports a conditional structure whose branches all run exactly the same code: an if / else pair or an if / else if / else chain where every body is identical, or a switch where every case is identical. The Swift 5.9 if- and switch-expression forms are covered alongside the statement forms.

Rationale

func bumpIfVerbose(_ verbose: Bool, _ count: Int) -> Int {
    if verbose {
        print("count", count)
        return count + 1
    } else {
        print("count", count)
        return count + 1
    }
}

When every branch does the same thing, the condition decides nothing. Either the whole structure is dead weight that should collapse to the body alone, or — far more often — one branch was copy-pasted and the edit that was supposed to follow never happened, so one of the cases is silently mishandled. The second reading is the reason this is reported as a defect rather than as tidiness: the code compiles, the tests for the copied branch pass, and the branch that was never edited is the one that goes wrong in production.

The trigger is deliberately all branches, not any two of them. Two arms of a long dispatch chain sharing a body is a normal, intentional idiom; every arm sharing a body never is, at any body size.

Notes

The check stays narrow so that idiomatic code never fires:

  • The chain must be complete. An if / else if with no trailing else is not a candidate — falling through the whole chain does something different from entering any branch, so the conditions still decide something.

  • The switch must carry a default, exactly as the Go sibling requires. A switch that can fall past every listed case behaves differently from entering one of them, as an else-less chain does. Enumerating an enum’s cases instead of writing a default is not the same thing and is not accepted: it is how an exhaustive reducer is written — switch action { case .a: .none; case .b: .none } — and collapsing those arms into a default would give up the compile error that a new enum case is unhandled, which is the whole reason the cases were listed.

  • Empty bodies are left alone. An empty branch may be a deliberate no-op, and an empty block is already reported by swift.empty_blocks.

  • Cases that bind a value or a type are excluded. In case .some(let x), case let x as Foo or case is Foo the arm introduces a binding of its own, or narrows the subject to a different static type, so two textually identical bodies are not running the same code.

Branch equivalence is computed over the parsed-token sequence, so comments and formatting differences between two branches do not hide a match.

Remediation

Decide which reading applies, then act on it.

If the branches were meant to differ, fix the branch that was never edited:

func bumpIfVerbose(_ verbose: Bool, _ count: Int) -> Int {
    if verbose {
        print("count", count)
        return count + 1
    } else {
        return count + 1
    }
}

If they were always meant to be the same, drop the condition and keep the body:

func bumpIfVerbose(_ verbose: Bool, _ count: Int) -> Int {
    if verbose {
        print("count", count)
    }
    return count + 1
}

© 2026 Xygeni. All rights reserved