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
This commit is contained in:
Jeremy Schonfeld 2024-07-23 14:43:01 -07:00 committed by GitHub
parent 27439d1ecf
commit 196376b5fd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 113 additions and 94 deletions

View File

@ -15,30 +15,26 @@ internal import _ForSwiftFoundation
import CoreFoundation
#endif
/// Singleton which listens for notifications about preference changes for Calendar and holds cached singletons for the current locale, calendar, and time zone.
struct CalendarCache : Sendable {
// MARK: - Concrete Classes
// _CalendarICU, if present
static func calendarICUClass(identifier: Calendar.Identifier, useGregorian: Bool) -> _CalendarProtocol.Type? {
#if FOUNDATION_FRAMEWORK && canImport(_FoundationICU)
if useGregorian && identifier == .gregorian {
return _CalendarGregorian.self
} else {
return _CalendarICU.self
internal func _calendarICUClass() -> _CalendarProtocol.Type? {
_CalendarICU.self
}
#else
if useGregorian && identifier == .gregorian {
return _CalendarGregorian.self
} else if let name = _typeByName("FoundationInternationalization._CalendarICU"), let t = name as? _CalendarProtocol.Type {
return t
} else {
return nil
dynamic package func _calendarICUClass() -> _CalendarProtocol.Type? {
nil
}
#endif
func _calendarClass(identifier: Calendar.Identifier, useGregorian: Bool) -> _CalendarProtocol.Type? {
if useGregorian && identifier == .gregorian {
return _CalendarGregorian.self
} else {
return _calendarICUClass()
}
}
/// Singleton which listens for notifications about preference changes for Calendar and holds cached singletons for the current locale, calendar, and time zone.
struct CalendarCache : Sendable {
// MARK: - State
struct State : Sendable {
@ -78,7 +74,7 @@ struct CalendarCache : Sendable {
} else {
let id = Locale.current._calendarIdentifier
// If we cannot create the right kind of class, we fail immediately here
let calendarClass = CalendarCache.calendarICUClass(identifier: id, useGregorian: true)!
let calendarClass = _calendarClass(identifier: id, useGregorian: true)!
let calendar = calendarClass.init(identifier: id, timeZone: nil, locale: Locale.current, firstWeekday: nil, minimumDaysInFirstWeek: nil, gregorianStartDate: nil)
currentCalendar = calendar
return calendar
@ -101,7 +97,7 @@ struct CalendarCache : Sendable {
return cached
} else {
// If we cannot create the right kind of class, we fail immediately here
let calendarClass = CalendarCache.calendarICUClass(identifier: id, useGregorian: true)!
let calendarClass = _calendarClass(identifier: id, useGregorian: true)!
let new = calendarClass.init(identifier: id, timeZone: nil, locale: nil, firstWeekday: nil, minimumDaysInFirstWeek: nil, gregorianStartDate: nil)
fixedCalendars[id] = new
return new
@ -140,7 +136,7 @@ struct CalendarCache : Sendable {
func fixed(identifier: Calendar.Identifier, locale: Locale?, timeZone: TimeZone?, firstWeekday: Int?, minimumDaysInFirstWeek: Int?, gregorianStartDate: Date?) -> any _CalendarProtocol {
// Note: Only the ObjC NSCalendar initWithCoder supports gregorian start date values. For Swift it is always nil.
// If we cannot create the right kind of class, we fail immediately here
let calendarClass = CalendarCache.calendarICUClass(identifier: identifier, useGregorian: true)!
let calendarClass = _calendarClass(identifier: identifier, useGregorian: true)!
return calendarClass.init(identifier: identifier, timeZone: timeZone, locale: locale, firstWeekday: firstWeekday, minimumDaysInFirstWeek: minimumDaysInFirstWeek, gregorianStartDate: gregorianStartDate)
}

View File

@ -118,9 +118,11 @@ public protocol _NSNumberInitializer {
static func initialize(value: some BinaryInteger) -> Any
}
private let _nsNumberInitializer: (any _NSNumberInitializer.Type)? = {
@_spi(SwiftCorelibsFoundation)
dynamic public func _nsNumberInitializer() -> (any _NSNumberInitializer.Type)? {
// TODO: return nil here after swift-corelibs-foundation begins dynamically replacing this function
_typeByName("Foundation._FoundationNSNumberInitializer") as? any _NSNumberInitializer.Type
}()
}
#endif
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
NSNumber(value: UInt64(value))
}
#else
if let ns = _nsNumberInitializer?.initialize(value: value) {
if let ns = _nsNumberInitializer()?.initialize(value: value) {
return ns
} else {
return U(value)
@ -143,7 +145,7 @@ func _writeFileAttributePrimitive(_ value: Bool) -> Any {
#if FOUNDATION_FRAMEWORK
NSNumber(value: value)
#else
if let ns = _nsNumberInitializer?.initialize(value: value) {
if let ns = _nsNumberInitializer()?.initialize(value: value) {
return ns
} else {
return value

View File

@ -19,23 +19,20 @@ internal import os
internal import _FoundationCShims
/// Singleton which listens for notifications about preference changes for Locale and holds cached singletons.
struct LocaleCache : Sendable {
// MARK: - Concrete Classes
// _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.
static let localeICUClass: _LocaleProtocol.Type = {
#if FOUNDATION_FRAMEWORK && canImport(_FoundationICU)
return _LocaleICU.self
// Here, we always have access to _LocaleICU
internal func _localeICUClass() -> _LocaleProtocol.Type {
_LocaleICU.self
}
#else
if let name = _typeByName("FoundationInternationalization._LocaleICU"), let t = name as? _LocaleProtocol.Type {
return t
} else {
return _LocaleUnlocalized.self
dynamic package func _localeICUClass() -> _LocaleProtocol.Type {
// 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.
_LocaleUnlocalized.self
}
#endif
}()
/// Singleton which listens for notifications about preference changes for Locale and holds cached singletons.
struct LocaleCache : Sendable {
// MARK: - State
struct State {
@ -99,7 +96,7 @@ struct LocaleCache : Sendable {
return nil
}
let locale = LocaleCache.localeICUClass.init(name: nil, prefs: preferences, disableBundleMatching: disableBundleMatching)
let locale = _localeICUClass().init(name: nil, prefs: preferences, disableBundleMatching: disableBundleMatching)
if cache {
// It's possible this was an 'incomplete locale', in which case we will want to calculate it again later.
self.cachedCurrentLocale = locale
@ -122,7 +119,7 @@ struct LocaleCache : Sendable {
if let locale = cachedFixedLocales[id] {
return locale
} else {
let locale = LocaleCache.localeICUClass.init(identifier: id, prefs: nil)
let locale = _localeICUClass().init(identifier: id, prefs: nil)
cachedFixedLocales[id] = locale
return locale
}
@ -217,7 +214,7 @@ struct LocaleCache : Sendable {
if let l = cachedFixedComponentsLocales[comps] {
return l
} else {
let new = LocaleCache.localeICUClass.init(components: comps)
let new = _localeICUClass().init(components: comps)
cachedFixedComponentsLocales[comps] = new
return new
@ -229,7 +226,7 @@ struct LocaleCache : Sendable {
return locale
}
let locale = LocaleCache.localeICUClass.init(identifier: "", prefs: nil)
let locale = _localeICUClass().init(identifier: "", prefs: nil)
cachedSystemLocale = locale
return locale
}
@ -415,13 +412,13 @@ struct LocaleCache : Sendable {
var (prefs, _) = preferences()
if let overrides { prefs.apply(overrides) }
let inner = LocaleCache.localeICUClass.init(name: name, prefs: prefs, disableBundleMatching: disableBundleMatching)
let inner = _localeICUClass().init(name: name, prefs: prefs, disableBundleMatching: disableBundleMatching)
return Locale(inner: inner)
}
func localeWithPreferences(identifier: String, prefs: LocalePreferences?) -> Locale {
if let prefs {
let inner = LocaleCache.localeICUClass.init(identifier: identifier, prefs: prefs)
let inner = _localeICUClass().init(identifier: identifier, prefs: prefs)
return Locale(inner: inner)
} else {
return Locale(inner: LocaleCache.cache.fixed(identifier))

View File

@ -31,37 +31,25 @@ internal import _ForSwiftFoundation
internal import CoreFoundation_Private.CFNotificationCenter
#endif
#if FOUNDATION_FRAMEWORK && canImport(_FoundationICU)
internal func _timeZoneICUClass() -> _TimeZoneProtocol.Type? {
_TimeZoneICU.self
}
internal func _timeZoneGMTClass() -> _TimeZoneProtocol.Type {
_TimeZoneGMTICU.self
}
#else
dynamic package func _timeZoneICUClass() -> _TimeZoneProtocol.Type? {
nil
}
dynamic package func _timeZoneGMTClass() -> _TimeZoneProtocol.Type {
_TimeZoneGMT.self
}
#endif
/// Singleton which listens for notifications about preference changes for TimeZone and holds cached values for current, fixed time zones, etc.
struct TimeZoneCache : Sendable {
// MARK: - Concrete Classes
// _TimeZoneICU, if present
static let timeZoneICUClass: _TimeZoneProtocol.Type? = {
#if FOUNDATION_FRAMEWORK && canImport(_FoundationICU)
_TimeZoneICU.self
#else
if let name = _typeByName("FoundationInternationalization._TimeZoneICU"), let t = name as? _TimeZoneProtocol.Type {
return t
} else {
return nil
}
#endif
}()
// _TimeZoneGMTICU or _TimeZoneGMT
static let timeZoneGMTClass: _TimeZoneProtocol.Type = {
#if FOUNDATION_FRAMEWORK && canImport(_FoundationICU)
_TimeZoneGMTICU.self
#else
if let name = _typeByName("FoundationInternationalization._TimeZoneGMTICU"), let t = name as? _TimeZoneProtocol.Type {
return t
} else {
return _TimeZoneGMT.self
}
#endif
}()
// MARK: - State
struct State {
@ -239,7 +227,7 @@ struct TimeZoneCache : Sendable {
} else if let cached = fixedTimeZones[identifier] {
return cached
} else {
if let innerTz = TimeZoneCache.timeZoneICUClass?.init(identifier: identifier) {
if let innerTz = _timeZoneICUClass()?.init(identifier: identifier) {
fixedTimeZones[identifier] = innerTz
return innerTz
} else {
@ -254,7 +242,7 @@ struct TimeZoneCache : Sendable {
} else {
// In order to avoid bloating a cache with weird time zones, only cache values that are 30min offsets (including 1hr offsets).
let doCache = abs(offset) % 1800 == 0
if let innerTz = TimeZoneCache.timeZoneGMTClass.init(secondsFromGMT: offset) {
if let innerTz = _timeZoneGMTClass().init(secondsFromGMT: offset) {
if doCache {
offsetTimeZones[offset] = innerTz
}

View File

@ -158,17 +158,18 @@ package protocol UIDNAHook {
static func decode(_ host: some StringProtocol) -> String?
}
#if FOUNDATION_FRAMEWORK && canImport(_FoundationICU)
internal func _uidnaHook() -> UIDNAHook.Type? {
UIDNAHookICU.self
}
#else
dynamic package func _uidnaHook() -> UIDNAHook.Type? {
nil
}
#endif
internal struct RFC3986Parser: URLParserProtocol {
static let kind: URLParserKind = .RFC3986
static let uidnaHook: UIDNAHook.Type? = {
#if FOUNDATION_FRAMEWORK && !canImport(_FoundationICU)
nil
#elseif FOUNDATION_FRAMEWORK
UIDNAHookICU.self
#else
_typeByName("FoundationInternationalization.UIDNAHookICU") as? UIDNAHook.Type
#endif
}()
// MARK: - Encoding
@ -233,7 +234,7 @@ internal struct RFC3986Parser: URLParserProtocol {
}
static func shouldPercentEncodeHost(_ host: some StringProtocol, forScheme scheme: (some StringProtocol)?) -> Bool {
guard uidnaHook != nil else {
guard _uidnaHook() != nil else {
// Always percent-encode the host if we can't access UIDNA encoding functions
return true
}
@ -279,13 +280,13 @@ internal struct RFC3986Parser: URLParserProtocol {
static func IDNAEncodeHost(_ host: (some StringProtocol)?) -> String? {
guard let host else { return nil }
guard !host.isEmpty else { return "" }
return uidnaHook?.encode(host)
return _uidnaHook()?.encode(host)
}
static func IDNADecodeHost(_ host: (some StringProtocol)?) -> String? {
guard let host else { return nil }
guard !host.isEmpty else { return "" }
guard let uidnaHook else { return String(host) }
guard let uidnaHook = _uidnaHook() else { return String(host) }
return uidnaHook.decode(host)
}

View File

@ -26,6 +26,13 @@ import Darwin
internal import _FoundationICU
#if !FOUNDATION_FRAMEWORK
@_dynamicReplacement(for: _calendarICUClass())
private func _calendarICUClass_localized() -> _CalendarProtocol.Type? {
return _CalendarICU.self
}
#endif
internal final class _CalendarICU: _CalendarProtocol, @unchecked Sendable {
let lock: LockedState<Void>
let identifier: Calendar.Identifier

View File

@ -27,6 +27,13 @@ internal import _FoundationICU
import Glibc
#endif
#if !FOUNDATION_FRAMEWORK
@_dynamicReplacement(for: _localeICUClass())
private func _localeICUClass_localized() -> any _LocaleProtocol.Type {
return _LocaleICU.self
}
#endif
let MAX_ICU_NAME_SIZE: Int32 = 1024
internal final class _LocaleICU: _LocaleProtocol, Sendable {

View File

@ -16,6 +16,13 @@ import FoundationEssentials
internal import _FoundationICU
#if !FOUNDATION_FRAMEWORK
@_dynamicReplacement(for: _timeZoneGMTClass())
private func _timeZoneGMTClass_localized() -> _TimeZoneProtocol.Type {
return _TimeZoneGMTICU.self
}
#endif
internal final class _TimeZoneGMTICU : _TimeZoneProtocol, @unchecked Sendable {
let offset: Int
let name: String

View File

@ -25,6 +25,13 @@ import ucrt
#if canImport(_FoundationICU)
internal import _FoundationICU
#if !FOUNDATION_FRAMEWORK
@_dynamicReplacement(for: _timeZoneICUClass())
private func _timeZoneICUClass_localized() -> _TimeZoneProtocol.Type? {
return _TimeZoneICU.self
}
#endif
internal final class _TimeZoneICU: _TimeZoneProtocol, Sendable {
init?(secondsFromGMT: Int) {
fatalError("Unexpected init")

View File

@ -15,7 +15,14 @@ import FoundationEssentials
internal import _FoundationICU
internal final class UIDNAHookICU: UIDNAHook {
#if !FOUNDATION_FRAMEWORK
@_dynamicReplacement(for: _uidnaHook())
private func _uidnaHook_localized() -> UIDNAHook.Type? {
return UIDNAHookICU.self
}
#endif
struct UIDNAHookICU: UIDNAHook {
// `Sendable` notes: `UIDNA` from ICU is thread safe.
struct UIDNAPointer : @unchecked Sendable {
init(_ ptr: OpaquePointer?) { self.idnaTranscoder = ptr }