Skip to content

Commit 1c12eb5

Browse files
committed
SwiftExtract: cover extractsGenericTypeInitializers & availableImportModules
`DefaultSwiftExtractConfiguration` exposed `extractsOperators` and `availableImportModules` as stored properties with init parameters but omitted `extractsGenericTypeInitializers`, so callers using the default config could not opt into it programmatically and silently fell back to the protocol-extension default of `false`. Add it as a stored property and init parameter alongside the other two. Also add four targeted tests in `AnalysisResultTests` exercising the non-default paths of the two analysis-shaping knobs: - `unspecializedGenericInitializersAreSkippedByDefault` and `extractsGenericTypeInitializersKeepsBaseInitializers` confirm the base `Tank<Fish>` flips between 0 and 2 initializers as the knob toggles. - `canImportGuardedDeclsAreSkippedWhenModuleNotAvailable` and `availableImportModulesActivatesCanImportClause` confirm `#if canImport(MadeUpModule)`-guarded types are extracted only when the module is listed in `availableImportModules`.
1 parent 7b87b10 commit 1c12eb5

2 files changed

Lines changed: 117 additions & 0 deletions

File tree

Sources/SwiftExtract/SwiftExtractConfiguration.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,13 +110,15 @@ public struct DefaultSwiftExtractConfiguration: SwiftExtractConfiguration {
110110
public var swiftExtractAccessLevel: AccessLevelMode
111111
public var swiftExtractLogLevel: Logger.Level?
112112
public var extractsOperators: Bool
113+
public var extractsGenericTypeInitializers: Bool
113114
public var availableImportModules: Set<String>
114115

115116
public init(
116117
swiftModule: String? = nil,
117118
accessLevel: AccessLevelMode = .public,
118119
logLevel: Logger.Level? = nil,
119120
extractsOperators: Bool = false,
121+
extractsGenericTypeInitializers: Bool = false,
120122
staticBuildConfigurationFile: String? = nil,
121123
swiftFilterInclude: [String]? = nil,
122124
swiftFilterExclude: [String]? = nil,
@@ -127,6 +129,7 @@ public struct DefaultSwiftExtractConfiguration: SwiftExtractConfiguration {
127129
self.swiftExtractAccessLevel = accessLevel
128130
self.swiftExtractLogLevel = logLevel
129131
self.extractsOperators = extractsOperators
132+
self.extractsGenericTypeInitializers = extractsGenericTypeInitializers
130133
self.staticBuildConfigurationFile = staticBuildConfigurationFile
131134
self.swiftFilterInclude = swiftFilterInclude
132135
self.swiftFilterExclude = swiftFilterExclude

Tests/SwiftExtractTests/AnalysisResultTests.swift

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,4 +313,118 @@ struct AnalysisResultSuite {
313313
#expect(result.extractedGlobalFuncs.isEmpty)
314314
#expect(result.extractedGlobalVariables.isEmpty)
315315
}
316+
317+
// ==== -----------------------------------------------------------------------
318+
// MARK: Configuration knobs
319+
320+
/// By default, initializers on an unspecialized generic type are NOT
321+
/// extracted: the open generic isn't directly constructible, so swift-java
322+
/// (and any caller leaving the knob at its default `false`) drops them.
323+
@Test func unspecializedGenericInitializersAreSkippedByDefault() throws {
324+
let result = try SwiftAnalyzer.analyze(
325+
sources: [
326+
(
327+
"/fake/Source.swift",
328+
"""
329+
public struct Tank<Fish> {
330+
public init() {}
331+
public init(capacity: Int) {}
332+
}
333+
"""
334+
)
335+
],
336+
moduleName: "Aquarium"
337+
)
338+
339+
let tank = try #require(result.extractedTypes["Tank"])
340+
#expect(tank.swiftNominal.isGeneric)
341+
#expect(!tank.isSpecialization)
342+
#expect(tank.initializers.isEmpty)
343+
}
344+
345+
/// Opting into `extractsGenericTypeInitializers` keeps the base type's
346+
/// initializers in the analysis result so post-analysis specializers can
347+
/// clone them onto a concrete `Tank<Fish>`.
348+
@Test func extractsGenericTypeInitializersKeepsBaseInitializers() throws {
349+
var config = DefaultSwiftExtractConfiguration()
350+
config.extractsGenericTypeInitializers = true
351+
352+
let result = try SwiftAnalyzer.analyze(
353+
sources: [
354+
(
355+
"/fake/Source.swift",
356+
"""
357+
public struct Tank<Fish> {
358+
public init() {}
359+
public init(capacity: Int) {}
360+
}
361+
"""
362+
)
363+
],
364+
moduleName: "Aquarium",
365+
config: config
366+
)
367+
368+
let tank = try #require(result.extractedTypes["Tank"])
369+
#expect(tank.swiftNominal.isGeneric)
370+
#expect(!tank.isSpecialization)
371+
#expect(tank.initializers.count == 2)
372+
}
373+
374+
/// `#if canImport(<module>)` blocks are inactive by default for modules the
375+
/// build configuration doesn't know about — the type guarded behind them
376+
/// must not appear in the analysis result.
377+
@Test func canImportGuardedDeclsAreSkippedWhenModuleNotAvailable() throws {
378+
let result = try SwiftAnalyzer.analyze(
379+
sources: [
380+
(
381+
"/fake/Source.swift",
382+
"""
383+
public struct AlwaysHere {
384+
public init() {}
385+
}
386+
#if canImport(MadeUpModule)
387+
public struct OnlyWhenImportable {
388+
public init() {}
389+
}
390+
#endif
391+
"""
392+
)
393+
],
394+
moduleName: "Aquarium"
395+
)
396+
397+
#expect(result.extractedTypes["AlwaysHere"] != nil)
398+
#expect(result.extractedTypes["OnlyWhenImportable"] == nil)
399+
}
400+
401+
/// Adding the module to `availableImportModules` activates the
402+
/// `#if canImport(<module>)` clause so its declarations are extracted.
403+
@Test func availableImportModulesActivatesCanImportClause() throws {
404+
var config = DefaultSwiftExtractConfiguration()
405+
config.availableImportModules = ["MadeUpModule"]
406+
407+
let result = try SwiftAnalyzer.analyze(
408+
sources: [
409+
(
410+
"/fake/Source.swift",
411+
"""
412+
public struct AlwaysHere {
413+
public init() {}
414+
}
415+
#if canImport(MadeUpModule)
416+
public struct OnlyWhenImportable {
417+
public init() {}
418+
}
419+
#endif
420+
"""
421+
)
422+
],
423+
moduleName: "Aquarium",
424+
config: config
425+
)
426+
427+
#expect(result.extractedTypes["AlwaysHere"] != nil)
428+
#expect(result.extractedTypes["OnlyWhenImportable"] != nil)
429+
}
316430
}

0 commit comments

Comments
 (0)