Skip to content

Commit 196376b

Browse files
authored
Use dynamic replacement instead of _typeByName for internationalization upcalls (#756)
* Use dynamic replacement instead of _typeByName for internationalization upcalls * Make FOUNDATION_FRAMEWORK function non-dynamic * Fix build failures
1 parent 27439d1 commit 196376b

File tree

10 files changed

+106
-87
lines changed

10 files changed

+106
-87
lines changed

Sources/FoundationEssentials/Calendar/Calendar_Cache.swift

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,30 +15,26 @@ internal import _ForSwiftFoundation
1515
import CoreFoundation
1616
#endif
1717

18-
/// Singleton which listens for notifications about preference changes for Calendar and holds cached singletons for the current locale, calendar, and time zone.
19-
struct CalendarCache : Sendable {
20-
21-
// MARK: - Concrete Classes
22-
23-
// _CalendarICU, if present
24-
static func calendarICUClass(identifier: Calendar.Identifier, useGregorian: Bool) -> _CalendarProtocol.Type? {
2518
#if FOUNDATION_FRAMEWORK && canImport(_FoundationICU)
26-
if useGregorian && identifier == .gregorian {
27-
return _CalendarGregorian.self
28-
} else {
29-
return _CalendarICU.self
30-
}
19+
internal func _calendarICUClass() -> _CalendarProtocol.Type? {
20+
_CalendarICU.self
21+
}
3122
#else
32-
if useGregorian && identifier == .gregorian {
33-
return _CalendarGregorian.self
34-
} else if let name = _typeByName("FoundationInternationalization._CalendarICU"), let t = name as? _CalendarProtocol.Type {
35-
return t
36-
} else {
37-
return nil
38-
}
23+
dynamic package func _calendarICUClass() -> _CalendarProtocol.Type? {
24+
nil
25+
}
3926
#endif
27+
28+
func _calendarClass(identifier: Calendar.Identifier, useGregorian: Bool) -> _CalendarProtocol.Type? {
29+
if useGregorian && identifier == .gregorian {
30+
return _CalendarGregorian.self
31+
} else {
32+
return _calendarICUClass()
4033
}
34+
}
4135

36+
/// Singleton which listens for notifications about preference changes for Calendar and holds cached singletons for the current locale, calendar, and time zone.
37+
struct CalendarCache : Sendable {
4238
// MARK: - State
4339

4440
struct State : Sendable {
@@ -78,7 +74,7 @@ struct CalendarCache : Sendable {
7874
} else {
7975
let id = Locale.current._calendarIdentifier
8076
// If we cannot create the right kind of class, we fail immediately here
81-
let calendarClass = CalendarCache.calendarICUClass(identifier: id, useGregorian: true)!
77+
let calendarClass = _calendarClass(identifier: id, useGregorian: true)!
8278
let calendar = calendarClass.init(identifier: id, timeZone: nil, locale: Locale.current, firstWeekday: nil, minimumDaysInFirstWeek: nil, gregorianStartDate: nil)
8379
currentCalendar = calendar
8480
return calendar
@@ -101,7 +97,7 @@ struct CalendarCache : Sendable {
10197
return cached
10298
} else {
10399
// If we cannot create the right kind of class, we fail immediately here
104-
let calendarClass = CalendarCache.calendarICUClass(identifier: id, useGregorian: true)!
100+
let calendarClass = _calendarClass(identifier: id, useGregorian: true)!
105101
let new = calendarClass.init(identifier: id, timeZone: nil, locale: nil, firstWeekday: nil, minimumDaysInFirstWeek: nil, gregorianStartDate: nil)
106102
fixedCalendars[id] = new
107103
return new
@@ -140,7 +136,7 @@ struct CalendarCache : Sendable {
140136
func fixed(identifier: Calendar.Identifier, locale: Locale?, timeZone: TimeZone?, firstWeekday: Int?, minimumDaysInFirstWeek: Int?, gregorianStartDate: Date?) -> any _CalendarProtocol {
141137
// Note: Only the ObjC NSCalendar initWithCoder supports gregorian start date values. For Swift it is always nil.
142138
// If we cannot create the right kind of class, we fail immediately here
143-
let calendarClass = CalendarCache.calendarICUClass(identifier: identifier, useGregorian: true)!
139+
let calendarClass = _calendarClass(identifier: identifier, useGregorian: true)!
144140
return calendarClass.init(identifier: identifier, timeZone: timeZone, locale: locale, firstWeekday: firstWeekday, minimumDaysInFirstWeek: minimumDaysInFirstWeek, gregorianStartDate: gregorianStartDate)
145141
}
146142

Sources/FoundationEssentials/FileManager/FileManager+Files.swift

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,9 +118,11 @@ public protocol _NSNumberInitializer {
118118
static func initialize(value: some BinaryInteger) -> Any
119119
}
120120

121-
private let _nsNumberInitializer: (any _NSNumberInitializer.Type)? = {
121+
@_spi(SwiftCorelibsFoundation)
122+
dynamic public func _nsNumberInitializer() -> (any _NSNumberInitializer.Type)? {
123+
// TODO: return nil here after swift-corelibs-foundation begins dynamically replacing this function
122124
_typeByName("Foundation._FoundationNSNumberInitializer") as? any _NSNumberInitializer.Type
123-
}()
125+
}
124126
#endif
125127

126128
func _writeFileAttributePrimitive<T: BinaryInteger, U: BinaryInteger>(_ value: T, as type: U.Type) -> Any {
@@ -131,7 +133,7 @@ func _writeFileAttributePrimitive<T: BinaryInteger, U: BinaryInteger>(_ value: T
131133
NSNumber(value: UInt64(value))
132134
}
133135
#else
134-
if let ns = _nsNumberInitializer?.initialize(value: value) {
136+
if let ns = _nsNumberInitializer()?.initialize(value: value) {
135137
return ns
136138
} else {
137139
return U(value)
@@ -143,7 +145,7 @@ func _writeFileAttributePrimitive(_ value: Bool) -> Any {
143145
#if FOUNDATION_FRAMEWORK
144146
NSNumber(value: value)
145147
#else
146-
if let ns = _nsNumberInitializer?.initialize(value: value) {
148+
if let ns = _nsNumberInitializer()?.initialize(value: value) {
147149
return ns
148150
} else {
149151
return value

Sources/FoundationEssentials/Locale/Locale_Cache.swift

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,20 @@ internal import os
1919

2020
internal import _FoundationCShims
2121

22-
/// Singleton which listens for notifications about preference changes for Locale and holds cached singletons.
23-
struct LocaleCache : Sendable {
24-
// MARK: - Concrete Classes
25-
26-
// _LocaleICU, if present. Otherwise we use _LocaleUnlocalized. The `Locale` initializers are not failable, so we just fall back to the unlocalized type when needed without failure.
27-
static let localeICUClass: _LocaleProtocol.Type = {
2822
#if FOUNDATION_FRAMEWORK && canImport(_FoundationICU)
29-
return _LocaleICU.self
23+
// Here, we always have access to _LocaleICU
24+
internal func _localeICUClass() -> _LocaleProtocol.Type {
25+
_LocaleICU.self
26+
}
3027
#else
31-
if let name = _typeByName("FoundationInternationalization._LocaleICU"), let t = name as? _LocaleProtocol.Type {
32-
return t
33-
} else {
34-
return _LocaleUnlocalized.self
35-
}
28+
dynamic package func _localeICUClass() -> _LocaleProtocol.Type {
29+
// Return _LocaleUnlocalized if FoundationInternationalization isn't loaded. The `Locale` initializers are not failable, so we just fall back to the unlocalized type when needed without failure.
30+
_LocaleUnlocalized.self
31+
}
3632
#endif
37-
}()
3833

34+
/// Singleton which listens for notifications about preference changes for Locale and holds cached singletons.
35+
struct LocaleCache : Sendable {
3936
// MARK: - State
4037

4138
struct State {
@@ -99,7 +96,7 @@ struct LocaleCache : Sendable {
9996
return nil
10097
}
10198

102-
let locale = LocaleCache.localeICUClass.init(name: nil, prefs: preferences, disableBundleMatching: disableBundleMatching)
99+
let locale = _localeICUClass().init(name: nil, prefs: preferences, disableBundleMatching: disableBundleMatching)
103100
if cache {
104101
// It's possible this was an 'incomplete locale', in which case we will want to calculate it again later.
105102
self.cachedCurrentLocale = locale
@@ -122,7 +119,7 @@ struct LocaleCache : Sendable {
122119
if let locale = cachedFixedLocales[id] {
123120
return locale
124121
} else {
125-
let locale = LocaleCache.localeICUClass.init(identifier: id, prefs: nil)
122+
let locale = _localeICUClass().init(identifier: id, prefs: nil)
126123
cachedFixedLocales[id] = locale
127124
return locale
128125
}
@@ -217,7 +214,7 @@ struct LocaleCache : Sendable {
217214
if let l = cachedFixedComponentsLocales[comps] {
218215
return l
219216
} else {
220-
let new = LocaleCache.localeICUClass.init(components: comps)
217+
let new = _localeICUClass().init(components: comps)
221218

222219
cachedFixedComponentsLocales[comps] = new
223220
return new
@@ -229,7 +226,7 @@ struct LocaleCache : Sendable {
229226
return locale
230227
}
231228

232-
let locale = LocaleCache.localeICUClass.init(identifier: "", prefs: nil)
229+
let locale = _localeICUClass().init(identifier: "", prefs: nil)
233230
cachedSystemLocale = locale
234231
return locale
235232
}
@@ -415,13 +412,13 @@ struct LocaleCache : Sendable {
415412
var (prefs, _) = preferences()
416413
if let overrides { prefs.apply(overrides) }
417414

418-
let inner = LocaleCache.localeICUClass.init(name: name, prefs: prefs, disableBundleMatching: disableBundleMatching)
415+
let inner = _localeICUClass().init(name: name, prefs: prefs, disableBundleMatching: disableBundleMatching)
419416
return Locale(inner: inner)
420417
}
421418

422419
func localeWithPreferences(identifier: String, prefs: LocalePreferences?) -> Locale {
423420
if let prefs {
424-
let inner = LocaleCache.localeICUClass.init(identifier: identifier, prefs: prefs)
421+
let inner = _localeICUClass().init(identifier: identifier, prefs: prefs)
425422
return Locale(inner: inner)
426423
} else {
427424
return Locale(inner: LocaleCache.cache.fixed(identifier))

Sources/FoundationEssentials/TimeZone/TimeZone_Cache.swift

Lines changed: 17 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -31,37 +31,25 @@ internal import _ForSwiftFoundation
3131
internal import CoreFoundation_Private.CFNotificationCenter
3232
#endif
3333

34-
/// Singleton which listens for notifications about preference changes for TimeZone and holds cached values for current, fixed time zones, etc.
35-
struct TimeZoneCache : Sendable {
36-
37-
// MARK: - Concrete Classes
38-
39-
// _TimeZoneICU, if present
40-
static let timeZoneICUClass: _TimeZoneProtocol.Type? = {
41-
#if FOUNDATION_FRAMEWORK && canImport(_FoundationICU)
42-
_TimeZoneICU.self
43-
#else
44-
if let name = _typeByName("FoundationInternationalization._TimeZoneICU"), let t = name as? _TimeZoneProtocol.Type {
45-
return t
46-
} else {
47-
return nil
48-
}
49-
#endif
50-
}()
51-
52-
// _TimeZoneGMTICU or _TimeZoneGMT
53-
static let timeZoneGMTClass: _TimeZoneProtocol.Type = {
34+
5435
#if FOUNDATION_FRAMEWORK && canImport(_FoundationICU)
55-
_TimeZoneGMTICU.self
36+
internal func _timeZoneICUClass() -> _TimeZoneProtocol.Type? {
37+
_TimeZoneICU.self
38+
}
39+
internal func _timeZoneGMTClass() -> _TimeZoneProtocol.Type {
40+
_TimeZoneGMTICU.self
41+
}
5642
#else
57-
if let name = _typeByName("FoundationInternationalization._TimeZoneGMTICU"), let t = name as? _TimeZoneProtocol.Type {
58-
return t
59-
} else {
60-
return _TimeZoneGMT.self
61-
}
43+
dynamic package func _timeZoneICUClass() -> _TimeZoneProtocol.Type? {
44+
nil
45+
}
46+
dynamic package func _timeZoneGMTClass() -> _TimeZoneProtocol.Type {
47+
_TimeZoneGMT.self
48+
}
6249
#endif
63-
}()
6450

51+
/// Singleton which listens for notifications about preference changes for TimeZone and holds cached values for current, fixed time zones, etc.
52+
struct TimeZoneCache : Sendable {
6553
// MARK: - State
6654

6755
struct State {
@@ -239,7 +227,7 @@ struct TimeZoneCache : Sendable {
239227
} else if let cached = fixedTimeZones[identifier] {
240228
return cached
241229
} else {
242-
if let innerTz = TimeZoneCache.timeZoneICUClass?.init(identifier: identifier) {
230+
if let innerTz = _timeZoneICUClass()?.init(identifier: identifier) {
243231
fixedTimeZones[identifier] = innerTz
244232
return innerTz
245233
} else {
@@ -254,7 +242,7 @@ struct TimeZoneCache : Sendable {
254242
} else {
255243
// In order to avoid bloating a cache with weird time zones, only cache values that are 30min offsets (including 1hr offsets).
256244
let doCache = abs(offset) % 1800 == 0
257-
if let innerTz = TimeZoneCache.timeZoneGMTClass.init(secondsFromGMT: offset) {
245+
if let innerTz = _timeZoneGMTClass().init(secondsFromGMT: offset) {
258246
if doCache {
259247
offsetTimeZones[offset] = innerTz
260248
}

Sources/FoundationEssentials/URL/URLParser.swift

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -158,17 +158,18 @@ package protocol UIDNAHook {
158158
static func decode(_ host: some StringProtocol) -> String?
159159
}
160160

161+
#if FOUNDATION_FRAMEWORK && canImport(_FoundationICU)
162+
internal func _uidnaHook() -> UIDNAHook.Type? {
163+
UIDNAHookICU.self
164+
}
165+
#else
166+
dynamic package func _uidnaHook() -> UIDNAHook.Type? {
167+
nil
168+
}
169+
#endif
170+
161171
internal struct RFC3986Parser: URLParserProtocol {
162172
static let kind: URLParserKind = .RFC3986
163-
static let uidnaHook: UIDNAHook.Type? = {
164-
#if FOUNDATION_FRAMEWORK && !canImport(_FoundationICU)
165-
nil
166-
#elseif FOUNDATION_FRAMEWORK
167-
UIDNAHookICU.self
168-
#else
169-
_typeByName("FoundationInternationalization.UIDNAHookICU") as? UIDNAHook.Type
170-
#endif
171-
}()
172173

173174
// MARK: - Encoding
174175

@@ -233,7 +234,7 @@ internal struct RFC3986Parser: URLParserProtocol {
233234
}
234235

235236
static func shouldPercentEncodeHost(_ host: some StringProtocol, forScheme scheme: (some StringProtocol)?) -> Bool {
236-
guard uidnaHook != nil else {
237+
guard _uidnaHook() != nil else {
237238
// Always percent-encode the host if we can't access UIDNA encoding functions
238239
return true
239240
}
@@ -279,13 +280,13 @@ internal struct RFC3986Parser: URLParserProtocol {
279280
static func IDNAEncodeHost(_ host: (some StringProtocol)?) -> String? {
280281
guard let host else { return nil }
281282
guard !host.isEmpty else { return "" }
282-
return uidnaHook?.encode(host)
283+
return _uidnaHook()?.encode(host)
283284
}
284285

285286
static func IDNADecodeHost(_ host: (some StringProtocol)?) -> String? {
286287
guard let host else { return nil }
287288
guard !host.isEmpty else { return "" }
288-
guard let uidnaHook else { return String(host) }
289+
guard let uidnaHook = _uidnaHook() else { return String(host) }
289290
return uidnaHook.decode(host)
290291
}
291292

Sources/FoundationInternationalization/Calendar/Calendar_ICU.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,13 @@ import Darwin
2626

2727
internal import _FoundationICU
2828

29+
#if !FOUNDATION_FRAMEWORK
30+
@_dynamicReplacement(for: _calendarICUClass())
31+
private func _calendarICUClass_localized() -> _CalendarProtocol.Type? {
32+
return _CalendarICU.self
33+
}
34+
#endif
35+
2936
internal final class _CalendarICU: _CalendarProtocol, @unchecked Sendable {
3037
let lock: LockedState<Void>
3138
let identifier: Calendar.Identifier

Sources/FoundationInternationalization/Locale/Locale_ICU.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ internal import _FoundationICU
2727
import Glibc
2828
#endif
2929

30+
#if !FOUNDATION_FRAMEWORK
31+
@_dynamicReplacement(for: _localeICUClass())
32+
private func _localeICUClass_localized() -> any _LocaleProtocol.Type {
33+
return _LocaleICU.self
34+
}
35+
#endif
36+
3037
let MAX_ICU_NAME_SIZE: Int32 = 1024
3138

3239
internal final class _LocaleICU: _LocaleProtocol, Sendable {

Sources/FoundationInternationalization/TimeZone/TimeZone_GMTICU.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ import FoundationEssentials
1616

1717
internal import _FoundationICU
1818

19+
#if !FOUNDATION_FRAMEWORK
20+
@_dynamicReplacement(for: _timeZoneGMTClass())
21+
private func _timeZoneGMTClass_localized() -> _TimeZoneProtocol.Type {
22+
return _TimeZoneGMTICU.self
23+
}
24+
#endif
25+
1926
internal final class _TimeZoneGMTICU : _TimeZoneProtocol, @unchecked Sendable {
2027
let offset: Int
2128
let name: String

Sources/FoundationInternationalization/TimeZone/TimeZone_ICU.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@ import ucrt
2525
#if canImport(_FoundationICU)
2626
internal import _FoundationICU
2727

28+
#if !FOUNDATION_FRAMEWORK
29+
@_dynamicReplacement(for: _timeZoneICUClass())
30+
private func _timeZoneICUClass_localized() -> _TimeZoneProtocol.Type? {
31+
return _TimeZoneICU.self
32+
}
33+
#endif
34+
2835
internal final class _TimeZoneICU: _TimeZoneProtocol, Sendable {
2936
init?(secondsFromGMT: Int) {
3037
fatalError("Unexpected init")

Sources/FoundationInternationalization/URLParser+ICU.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,14 @@ import FoundationEssentials
1515

1616
internal import _FoundationICU
1717

18-
internal final class UIDNAHookICU: UIDNAHook {
18+
#if !FOUNDATION_FRAMEWORK
19+
@_dynamicReplacement(for: _uidnaHook())
20+
private func _uidnaHook_localized() -> UIDNAHook.Type? {
21+
return UIDNAHookICU.self
22+
}
23+
#endif
24+
25+
struct UIDNAHookICU: UIDNAHook {
1926
// `Sendable` notes: `UIDNA` from ICU is thread safe.
2027
struct UIDNAPointer : @unchecked Sendable {
2128
init(_ ptr: OpaquePointer?) { self.idnaTranscoder = ptr }

0 commit comments

Comments
 (0)