Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

- Fix indexing of xib/storyboard files in SPM projects.
- Fix types conforming to App Intents protocols being reported as unused.
- Fix superclass initializer reported as unused when called on subclass.

## 3.3.0 (2025-12-13)

Expand Down
1 change: 1 addition & 0 deletions Sources/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ swift_library(
"SourceGraph/Mutators/ExternalOverrideRetainer.swift",
"SourceGraph/Mutators/ExternalTypeProtocolConformanceReferenceRemover.swift",
"SourceGraph/Mutators/GenericClassAndStructConstructorReferenceBuilder.swift",
"SourceGraph/Mutators/InheritedImplicitInitializerReferenceBuilder.swift",
"SourceGraph/Mutators/InterfaceBuilderPropertyRetainer.swift",
"SourceGraph/Mutators/ObjCAccessibleRetainer.swift",
"SourceGraph/Mutators/PropertyWrapperRetainer.swift",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import Configuration
import Foundation
import Shared

/// Builds references from implicit inherited initializers to the superclass initializer they inherit.
///
/// When a subclass inherits an initializer from its superclass, the index store records a relationship
/// from the superclass initializer to the subclass implicit initializer, but not the inverse. This mutator
/// adds the inverse reference so that when the implicit initializer is used, the superclass initializer
/// is also marked as used.
final class InheritedImplicitInitializerReferenceBuilder: SourceGraphMutator {
private let graph: SourceGraph

required init(graph: SourceGraph, configuration _: Configuration, swiftVersion _: SwiftVersion) {
self.graph = graph
}

func mutate() {
for classDecl in graph.declarations(ofKind: .class) {
// Find explicit (non-implicit) initializers in this class
let explicitInitializers = classDecl.declarations.filter {
$0.kind == .functionConstructor && !$0.isImplicit
}

for explicitInit in explicitInitializers {
// Check if this initializer has related references to implicit initializers in subclasses
for relatedRef in explicitInit.related {
guard relatedRef.kind == .functionConstructor else { continue }

// Find the declaration this related reference points to
guard let implicitInit = graph.declaration(withUsr: relatedRef.usr),
implicitInit.isImplicit,
implicitInit.kind == .functionConstructor
else { continue }

// Add the inverse reference: implicit init -> explicit init
for usr in explicitInit.usrs {
let reference = Reference(
kind: .functionConstructor,
usr: usr,
location: implicitInit.location,
isRelated: true
)
reference.name = explicitInit.name
reference.parent = implicitInit
graph.add(reference, from: implicitInit)
}
}
}
}
}
}
1 change: 1 addition & 0 deletions Sources/SourceGraph/SourceGraphMutatorRunner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public final class SourceGraphMutatorRunner {
EnumCaseReferenceBuilder.self,
DefaultConstructorReferenceBuilder.self,
StructImplicitInitializerReferenceBuilder.self,
InheritedImplicitInitializerReferenceBuilder.self,

DynamicMemberRetainer.self,
UnusedParameterRetainer.self,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class FixtureClass221Parent {
init(param: String) {}
}

class FixtureClass221Child: FixtureClass221Parent {}

public class FixtureClass221Retainer {
public func retain() {
_ = FixtureClass221Child(param: "foo")
}
}
12 changes: 12 additions & 0 deletions Tests/PeripheryTests/RetentionTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1679,6 +1679,18 @@ final class RetentionTest: FixtureSourceGraphTestCase {
}
}

// MARK: - Inherited Initializers

// https://github.com/peripheryapp/periphery/issues/957
func testRetainsSuperclassInitializerCalledOnSubclass() {
analyze(retainPublic: true) {
assertReferenced(.class("FixtureClass221Parent")) {
self.assertReferenced(.functionConstructor("init(param:)"))
}
assertReferenced(.class("FixtureClass221Child"))
}
}

// MARK: - Known Failures

// https://github.com/peripheryapp/periphery/issues/676
Expand Down
Loading