From 6f8a7ca3f5d7f27ab87c7a8e76ab9cceb357538f Mon Sep 17 00:00:00 2001 From: Dan Wood <207080+danwood@users.noreply.github.com> Date: Thu, 29 Jan 2026 08:51:23 -0800 Subject: [PATCH 1/2] Fix to issue 1062, with tests --- .../Mutators/UsedDeclarationMarker.swift | 11 ++++++++ ...pertyTypeReferencesOfUsedDeclaration.swift | 19 ++++++++++++++ ...sStaticMethodOnExternalTypeExtension.swift | 21 +++++++++++++++ Tests/PeripheryTests/RetentionTest.swift | 26 ++++++++++++++++++- 4 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 Tests/Fixtures/Sources/RetentionFixtures/testRetainsPropertyTypeReferencesOfUsedDeclaration.swift create mode 100644 Tests/Fixtures/Sources/RetentionFixtures/testRetainsStaticMethodOnExternalTypeExtension.swift diff --git a/Sources/SourceGraph/Mutators/UsedDeclarationMarker.swift b/Sources/SourceGraph/Mutators/UsedDeclarationMarker.swift index b3b054558..18185665d 100644 --- a/Sources/SourceGraph/Mutators/UsedDeclarationMarker.swift +++ b/Sources/SourceGraph/Mutators/UsedDeclarationMarker.swift @@ -66,6 +66,17 @@ final class UsedDeclarationMarker: SourceGraphMutator { for ref in declaration.related { markUsed(declarationsReferenced(by: ref)) } + + // Follow type references from child property declarations. + // Property type references are associated with the property declaration + // by the indexer, not the containing type. Walking varType references + // ensures types used as property types are marked used when the parent + // type is used. + for childDecl in declaration.declarations where childDecl.kind.isVariableKind { + for ref in childDecl.references where ref.role == .varType { + markUsed(declarationsReferenced(by: ref)) + } + } } } diff --git a/Tests/Fixtures/Sources/RetentionFixtures/testRetainsPropertyTypeReferencesOfUsedDeclaration.swift b/Tests/Fixtures/Sources/RetentionFixtures/testRetainsPropertyTypeReferencesOfUsedDeclaration.swift new file mode 100644 index 000000000..d5e04fd92 --- /dev/null +++ b/Tests/Fixtures/Sources/RetentionFixtures/testRetainsPropertyTypeReferencesOfUsedDeclaration.swift @@ -0,0 +1,19 @@ +import Foundation + +struct FixtureItem222: Identifiable, Hashable { + let id: UUID + let name: String +} + +public struct FixtureViewModel222 { + struct FixtureSection222: Identifiable, Hashable { + let id: UUID + var equipment: [FixtureItem222] + } + + var sections: [FixtureSection222] = [] + + public mutating func addSection() { + sections.append(FixtureSection222(id: UUID(), equipment: [])) + } +} diff --git a/Tests/Fixtures/Sources/RetentionFixtures/testRetainsStaticMethodOnExternalTypeExtension.swift b/Tests/Fixtures/Sources/RetentionFixtures/testRetainsStaticMethodOnExternalTypeExtension.swift new file mode 100644 index 000000000..defb6fa0b --- /dev/null +++ b/Tests/Fixtures/Sources/RetentionFixtures/testRetainsStaticMethodOnExternalTypeExtension.swift @@ -0,0 +1,21 @@ +import Foundation + +public class FixtureClassStaticExtMethod { + public func someMethod() { + let _ = [Int].emptyArray() + let _ = [String].constrainedFactory("hello") + NumberFormatter.customFormat() + } +} + +extension Array { + static func emptyArray() -> [Any] { [] } +} + +extension Array where Element == String { + static func constrainedFactory(_ value: String) -> [String] { [value] } +} + +extension NumberFormatter { + static func customFormat() {} +} diff --git a/Tests/PeripheryTests/RetentionTest.swift b/Tests/PeripheryTests/RetentionTest.swift index b81868b78..168542872 100644 --- a/Tests/PeripheryTests/RetentionTest.swift +++ b/Tests/PeripheryTests/RetentionTest.swift @@ -594,6 +594,23 @@ final class RetentionTest: FixtureSourceGraphTestCase { } } + func testRetainsStaticMethodOnExternalTypeExtension() { + analyze(retainPublic: true) { + assertReferenced(.class("FixtureClassStaticExtMethod")) { + self.assertReferenced(.functionMethodInstance("someMethod()")) + } + assertReferenced(.extensionStruct("Array", line: 11)) { + self.assertReferenced(.functionMethodStatic("emptyArray()")) + } + assertReferenced(.extensionStruct("Array", line: 15)) { + self.assertReferenced(.functionMethodStatic("constrainedFactory(_:)")) + } + assertReferenced(.extensionClass("NumberFormatter", line: 19)) { + self.assertReferenced(.functionMethodStatic("customFormat()")) + } + } + } + func testRetainsExtendedTypeAlias() { analyze(retainPublic: true) { assertReferenced(.typealias("Fixture214TypeAlias")) @@ -848,7 +865,14 @@ final class RetentionTest: FixtureSourceGraphTestCase { assertReferenced(.class("FixtureClass71")) { self.assertNotReferenced(.varInstance("someVar")) } - assertNotReferenced(.class("FixtureClass72")) + assertReferenced(.class("FixtureClass72")) + } + } + + func testRetainsPropertyTypeReferencesOfUsedDeclaration() { + analyze(retainPublic: true) { + assertReferenced(.struct("FixtureViewModel222")) + assertReferenced(.struct("FixtureItem222")) } } From 4c3a8a7b22876dc4713f2b4d8a4f458283a0131a Mon Sep 17 00:00:00 2001 From: Dan Wood <207080+danwood@users.noreply.github.com> Date: Thu, 29 Jan 2026 09:43:12 -0800 Subject: [PATCH 2/2] trivial formatting change to kick off swift lint again --- .../testRetainsPropertyTypeReferencesOfUsedDeclaration.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Tests/Fixtures/Sources/RetentionFixtures/testRetainsPropertyTypeReferencesOfUsedDeclaration.swift b/Tests/Fixtures/Sources/RetentionFixtures/testRetainsPropertyTypeReferencesOfUsedDeclaration.swift index d5e04fd92..6ecc0cd04 100644 --- a/Tests/Fixtures/Sources/RetentionFixtures/testRetainsPropertyTypeReferencesOfUsedDeclaration.swift +++ b/Tests/Fixtures/Sources/RetentionFixtures/testRetainsPropertyTypeReferencesOfUsedDeclaration.swift @@ -10,7 +10,6 @@ public struct FixtureViewModel222 { let id: UUID var equipment: [FixtureItem222] } - var sections: [FixtureSection222] = [] public mutating func addSection() {