Skip to content

Commit b65643a

Browse files
authored
Refactor some delayed-init patterns to eliminate unnecessary mutability (swiftlang#71)
1 parent f7e63a3 commit b65643a

File tree

3 files changed

+99
-42
lines changed

3 files changed

+99
-42
lines changed

Sources/SWBCore/Core.swift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -133,10 +133,11 @@ public final class Core: Sendable {
133133
/// The configured delegate.
134134
@_spi(Testing) public let delegate: any CoreDelegate
135135

136-
// registryDelegate is an implicitly-unwrapped optional var because the delegate refers to the core which creates it, and making it a let property would violate the initialization constraints.
137-
//
136+
let _registryDelegate: UnsafeDelayedInitializationSendableWrapper<CoreRegistryDelegate> = .init()
138137
/// The self-referencing delegate to convey information about the core to registry subsystems.
139-
private(set) var registryDelegate: CoreRegistryDelegate! = nil
138+
var registryDelegate: CoreRegistryDelegate {
139+
_registryDelegate.value
140+
}
140141

141142
/// The host operating system.
142143
public let hostOperatingSystem: OperatingSystem
@@ -233,7 +234,7 @@ public final class Core: Sendable {
233234
return toolchainPaths
234235
}()
235236

236-
self.registryDelegate = CoreRegistryDelegate(core: self)
237+
_registryDelegate.initialize(to: CoreRegistryDelegate(core: self))
237238
}
238239

239240
/// The shared core settings object.

Sources/SWBCore/ProjectModel/Target.swift

Lines changed: 62 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -323,45 +323,54 @@ public class BuildPhaseTarget: Target
323323
public let buildPhases: [BuildPhase]
324324

325325
/// The canonical sources build phase in the target, if there is one. There should only be one in a target; if there is more than one, then a warning will be emitted and the project might not build as expected.
326-
public private(set) var sourcesBuildPhase: SourcesBuildPhase? = nil
326+
public let sourcesBuildPhase: SourcesBuildPhase?
327327
/// The canonical frameworks build phase in the target, if there is one. There should only be one in a target; if there is more than one, then a warning will be emitted and the project might not build as expected.
328-
public private(set) var frameworksBuildPhase: FrameworksBuildPhase? = nil
328+
public let frameworksBuildPhase: FrameworksBuildPhase?
329329
/// The canonical headers build phase in the target, if there is one. There should only be one in a target; if there is more than one, then a warning will be emitted and the project might not build as expected.
330-
public private(set) var headersBuildPhase: HeadersBuildPhase? = nil
330+
public let headersBuildPhase: HeadersBuildPhase?
331331
/// The canonical resources build phase in the target, if there is one. There should only be one in a target; if there is more than one, then a warning will be emitted and the project might not build as expected.
332-
public private(set) var resourcesBuildPhase: ResourcesBuildPhase? = nil
332+
public let resourcesBuildPhase: ResourcesBuildPhase?
333333

334334
init(_ model: SWBProtocol.BuildPhaseTarget, _ pifLoader: PIFLoader, signature: String) {
335335
buildPhases = model.buildPhases.map{ BuildPhase.create($0, pifLoader) }
336-
super.init(model, pifLoader, signature: signature)
337-
338336
// Populate the convenience build phase properties.
337+
var warnings: [String] = []
338+
var sourcesBuildPhase: SourcesBuildPhase? = nil
339+
var frameworksBuildPhase: FrameworksBuildPhase? = nil
340+
var headersBuildPhase: HeadersBuildPhase? = nil
341+
var resourcesBuildPhase: ResourcesBuildPhase? = nil
339342
for buildPhase in self.buildPhases {
340343
switch buildPhase {
341-
case let sourcesBuildPhase as SourcesBuildPhase:
342-
if self.sourcesBuildPhase != nil {
344+
case let sourcesPhase as SourcesBuildPhase:
345+
if sourcesBuildPhase != nil {
343346
warnings.append("target has multiple \(buildPhase.name) build phases, which may cause it to build incorrectly - all but one should be deleted")
344347
}
345-
self.sourcesBuildPhase = sourcesBuildPhase
346-
case let frameworksBuildPhase as FrameworksBuildPhase:
347-
if self.frameworksBuildPhase != nil {
348+
sourcesBuildPhase = sourcesPhase
349+
case let frameworksPhase as FrameworksBuildPhase:
350+
if frameworksBuildPhase != nil {
348351
warnings.append("target has multiple \(buildPhase.name) build phases, which may cause it to build incorrectly - all but one should be deleted")
349352
}
350-
self.frameworksBuildPhase = frameworksBuildPhase
351-
case let headersBuildPhase as HeadersBuildPhase:
352-
if self.headersBuildPhase != nil {
353+
frameworksBuildPhase = frameworksPhase
354+
case let headersPhase as HeadersBuildPhase:
355+
if headersBuildPhase != nil {
353356
warnings.append("target has multiple \(buildPhase.name) build phases, which may cause it to build incorrectly - all but one should be deleted")
354357
}
355-
self.headersBuildPhase = headersBuildPhase
356-
case let resourcesBuildPhase as ResourcesBuildPhase:
357-
if self.resourcesBuildPhase != nil {
358+
headersBuildPhase = headersPhase
359+
case let resourcesPhase as ResourcesBuildPhase:
360+
if resourcesBuildPhase != nil {
358361
warnings.append("target has multiple \(buildPhase.name) build phases, which may cause it to build incorrectly - all but one should be deleted")
359362
}
360-
self.resourcesBuildPhase = resourcesBuildPhase
363+
resourcesBuildPhase = resourcesPhase
361364
default:
362365
break
363366
}
364367
}
368+
self.sourcesBuildPhase = sourcesBuildPhase
369+
self.frameworksBuildPhase = frameworksBuildPhase
370+
self.headersBuildPhase = headersBuildPhase
371+
self.resourcesBuildPhase = resourcesBuildPhase
372+
super.init(model, pifLoader, signature: signature)
373+
self.warnings.append(contentsOf: warnings)
365374
}
366375

367376
@_spi(Testing) public override init( fromDictionary pifDict: ProjectModelItemPIF, signature: String, withPIFLoader pifLoader: PIFLoader ) throws
@@ -382,37 +391,47 @@ public class BuildPhaseTarget: Target
382391
}
383392
}
384393

385-
try super.init(fromDictionary: pifDict, signature: signature, withPIFLoader: pifLoader)
386-
387394
// Populate the convenience build phase properties.
395+
var warnings: [String] = []
396+
var sourcesBuildPhase: SourcesBuildPhase? = nil
397+
var frameworksBuildPhase: FrameworksBuildPhase? = nil
398+
var headersBuildPhase: HeadersBuildPhase? = nil
399+
var resourcesBuildPhase: ResourcesBuildPhase? = nil
388400
for buildPhase in self.buildPhases
389401
{
390402
switch buildPhase
391403
{
392-
case let sourcesBuildPhase as SourcesBuildPhase:
393-
if self.sourcesBuildPhase != nil {
404+
case let sourcesPhase as SourcesBuildPhase:
405+
if sourcesBuildPhase != nil {
394406
warnings.append("target has multiple \(buildPhase.name) build phases, which may cause it to build incorrectly - all but one should be deleted")
395407
}
396-
self.sourcesBuildPhase = sourcesBuildPhase
397-
case let frameworksBuildPhase as FrameworksBuildPhase:
398-
if self.frameworksBuildPhase != nil {
408+
sourcesBuildPhase = sourcesPhase
409+
case let frameworksPhase as FrameworksBuildPhase:
410+
if frameworksBuildPhase != nil {
399411
warnings.append("target has multiple \(buildPhase.name) build phases, which may cause it to build incorrectly - all but one should be deleted")
400412
}
401-
self.frameworksBuildPhase = frameworksBuildPhase
402-
case let headersBuildPhase as HeadersBuildPhase:
403-
if self.headersBuildPhase != nil {
413+
frameworksBuildPhase = frameworksPhase
414+
case let headersPhase as HeadersBuildPhase:
415+
if headersBuildPhase != nil {
404416
warnings.append("target has multiple \(buildPhase.name) build phases, which may cause it to build incorrectly - all but one should be deleted")
405417
}
406-
self.headersBuildPhase = headersBuildPhase
407-
case let resourcesBuildPhase as ResourcesBuildPhase:
408-
if self.resourcesBuildPhase != nil {
418+
headersBuildPhase = headersPhase
419+
case let resourcesPhase as ResourcesBuildPhase:
420+
if resourcesBuildPhase != nil {
409421
warnings.append("target has multiple \(buildPhase.name) build phases, which may cause it to build incorrectly - all but one should be deleted")
410422
}
411-
self.resourcesBuildPhase = resourcesBuildPhase
423+
resourcesBuildPhase = resourcesPhase
412424
default:
413425
break
414426
}
415427
}
428+
self.sourcesBuildPhase = sourcesBuildPhase
429+
self.frameworksBuildPhase = frameworksBuildPhase
430+
self.headersBuildPhase = headersBuildPhase
431+
self.resourcesBuildPhase = resourcesBuildPhase
432+
433+
try super.init(fromDictionary: pifDict, signature: signature, withPIFLoader: pifLoader)
434+
self.warnings.append(contentsOf: warnings)
416435
}
417436
}
418437

@@ -484,7 +503,10 @@ public final class StandardTarget: BuildPhaseTarget
484503
/// The class prefix used in this target
485504
public let classPrefix: String
486505

487-
private(set) var provisioningSourceData: [ProvisioningSourceData]
506+
private let _provisioningSourceData: UnsafeDelayedInitializationSendableWrapper<[ProvisioningSourceData]> = .init()
507+
var provisioningSourceData: [ProvisioningSourceData] {
508+
_provisioningSourceData.value
509+
}
488510

489511
init(_ model: SWBProtocol.StandardTarget, _ pifLoader: PIFLoader, signature: String) throws {
490512
buildRules = model.buildRules.map{ BuildRule($0, pifLoader) }
@@ -493,14 +515,15 @@ public final class StandardTarget: BuildPhaseTarget
493515
isPackageTarget = model.isPackageTarget
494516
performanceTestsBaselinesPath = model.performanceTestsBaselinesPath != nil ? Path(model.performanceTestsBaselinesPath!) : nil
495517
predominantSourceCodeLanguage = SourceCodeLanguage.from(string: model.predominantSourceCodeLanguage)
496-
provisioningSourceData = model.provisioningSourceData
518+
var provisioningSourceData = model.provisioningSourceData
497519
classPrefix = model.classPrefix ?? ""
498520
super.init(model, pifLoader, signature: signature)
499521

500522
// Set our product reference's backpointer to ourself.
501523
productReference.target = self
502524

503-
try fixupProvisioningSourceData()
525+
try StandardTarget.fixupProvisioningSourceData(&provisioningSourceData, name: name, buildConfigurations: buildConfigurations)
526+
_provisioningSourceData.initialize(to: provisioningSourceData)
504527
}
505528

506529
@_spi(Testing) public override init(fromDictionary pifDict: ProjectModelItemPIF, signature: String, withPIFLoader pifLoader: PIFLoader) throws
@@ -525,7 +548,7 @@ public final class StandardTarget: BuildPhaseTarget
525548
classPrefix = try Self.parseOptionalValueForKeyAsString(PIFKey_Project_classPrefix, pifDict: pifDict) ?? ""
526549

527550
// The list of provisioning source data dictionaries is optional. For any configurations for which we were not given a provisioning source data object we will create a default one.
528-
provisioningSourceData = [ProvisioningSourceData]()
551+
var provisioningSourceData = [ProvisioningSourceData]()
529552

530553
if let sourceDataDicts = try Self.parseOptionalValueForKeyAsArrayOfPropertyListItems(PIFKey_Target_provisioningSourceData, pifDict: pifDict) {
531554
provisioningSourceData = try sourceDataDicts.map { try PropertyList.decode(ProvisioningSourceData.self, from: $0) }
@@ -536,10 +559,11 @@ public final class StandardTarget: BuildPhaseTarget
536559
// Set our product reference's backpointer to ourself.
537560
productReference.target = self
538561

539-
try fixupProvisioningSourceData()
562+
try StandardTarget.fixupProvisioningSourceData(&provisioningSourceData, name: name, buildConfigurations: buildConfigurations)
563+
_provisioningSourceData.initialize(to: provisioningSourceData)
540564
}
541565

542-
private func fixupProvisioningSourceData() throws {
566+
private static func fixupProvisioningSourceData(_ provisioningSourceData: inout [ProvisioningSourceData], name: String, buildConfigurations: [BuildConfiguration]) throws {
543567
// Make sure all of our provisioning source data objects match existing configurations, and that there are no duplicate source data structs for a configuration.
544568
var configurationNames = Set<String>(buildConfigurations.map({ $0.name }))
545569
var foundConfigurationNames = Set<String>()
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See http://swift.org/LICENSE.txt for license information
9+
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
/// An Unsafe wrapper for values that are initialized exactly once
14+
public final class UnsafeDelayedInitializationSendableWrapper<T: Sendable>: @unchecked Sendable {
15+
private var _value: T?
16+
17+
public init() {
18+
self._value = nil
19+
}
20+
21+
public func initialize(to initializedValue: T) {
22+
precondition(_value == nil, "value is already initialized")
23+
_value = initializedValue
24+
}
25+
26+
public var value: T {
27+
guard let _value else {
28+
preconditionFailure("value was accessed before it was initialized")
29+
}
30+
return _value
31+
}
32+
}

0 commit comments

Comments
 (0)