-
-
Notifications
You must be signed in to change notification settings - Fork 224
Description
Describe the bug
Periphery reports an initializer as unused when it is the sole initializer for a class/struct that has stored properties without default values. Removing such an initializer would cause a compilation error, making this warning actionable only by removing the entire type.
The warning is technically correct (the init is never called), but following the suggestion to remove it produces non-compiling code.
Reproduction
fileprivate class TempoMap {
private let tempoEvents: [TempoEvent]
private let fallbackTempo: Double
init(tempoEvents: [TempoEvent], fallbackTempo: Double) { // ⚠️ reported as unused
self.tempoEvents = tempoEvents.sorted { $0.positionBars < $1.positionBars }
self.fallbackTempo = fallbackTempo
}
func tempo(atTime: TimeInterval) -> Double {
return fallbackTempo
}
func tempo(atBars bars: Int) -> Double {
let applicableEvents = tempoEvents.filter { $0.positionBars <= bars }
return applicableEvents.last?.tempo ?? fallbackTempo
}
}
struct TempoEvent: Codable, Equatable {
let positionBars: Int
let positionBeats: Int
let tempo: Double
}Running Periphery produces:
warning: Initializer 'init(tempoEvents:fallbackTempo:)' is unused
If you remove the initializer as suggested, the code fails to compile:
error: class 'TempoMap' has no initializers
note: stored property 'tempoEvents' without initial value prevents synthesized initializers
note: stored property 'fallbackTempo' without initial value prevents synthesized initializers
Expected behavior
When an initializer is the only initializer for a type that requires one (due to stored properties without defaults), it should not be reported as unused. Instead, either:
- Don't report the initializer (it's required for the type to compile)
- Report the entire type as unused (if the type itself is never instantiated)
Potential fix location
DefaultConstructorReferenceBuilder.swift currently creates synthetic references only for init() (no parameters) and implicit initializers. It could be extended to also reference initializers that are the sole initializer for their parent type:
// In referenceDefaultConstructors()
let constructors = graph.declarations(ofKind: .functionConstructor)
let constructorsByParent = Dictionary(grouping: constructors) { $0.parent }
for (parent, parentConstructors) in constructorsByParent {
guard let parent = parent else { continue }
for constructor in parentConstructors {
let isDefault = constructor.name == "init()" || constructor.isImplicit
let isSoleConstructor = parentConstructors.count == 1
if isDefault || isSoleConstructor {
// Create synthetic reference from parent to constructor
}
}
}Environment
3.4.0
swift-driver version: 1.127.14.1 Apple Swift version 6.2.3 (swiftlang-6.2.3.3.21 clang-1700.6.3.2)
Target: arm64-apple-macosx26.0
Xcode 26.2
Build version 17C52