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 import CoreFoundation
#endif #endif
#if FOUNDATION_FRAMEWORK && canImport(_FoundationICU)
internal func _calendarICUClass() -> _CalendarProtocol.Type? {
_CalendarICU.self
}
#else
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. /// 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 { 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
}
#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
}
#endif
}
// MARK: - State // MARK: - State
struct State : Sendable { struct State : Sendable {
@ -78,7 +74,7 @@ struct CalendarCache : Sendable {
} else { } else {
let id = Locale.current._calendarIdentifier let id = Locale.current._calendarIdentifier
// If we cannot create the right kind of class, we fail immediately here // 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) let calendar = calendarClass.init(identifier: id, timeZone: nil, locale: Locale.current, firstWeekday: nil, minimumDaysInFirstWeek: nil, gregorianStartDate: nil)
currentCalendar = calendar currentCalendar = calendar
return calendar return calendar
@ -101,7 +97,7 @@ struct CalendarCache : Sendable {
return cached return cached
} else { } else {
// If we cannot create the right kind of class, we fail immediately here // 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) let new = calendarClass.init(identifier: id, timeZone: nil, locale: nil, firstWeekday: nil, minimumDaysInFirstWeek: nil, gregorianStartDate: nil)
fixedCalendars[id] = new fixedCalendars[id] = new
return 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 { 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. // 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 // 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) 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 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 _typeByName("Foundation._FoundationNSNumberInitializer") as? any _NSNumberInitializer.Type
}() }
#endif #endif
func _writeFileAttributePrimitive<T: BinaryInteger, U: BinaryInteger>(_ value: T, as type: U.Type) -> Any { 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)) NSNumber(value: UInt64(value))
} }
#else #else
if let ns = _nsNumberInitializer?.initialize(value: value) { if let ns = _nsNumberInitializer()?.initialize(value: value) {
return ns return ns
} else { } else {
return U(value) return U(value)
@ -143,7 +145,7 @@ func _writeFileAttributePrimitive(_ value: Bool) -> Any {
#if FOUNDATION_FRAMEWORK #if FOUNDATION_FRAMEWORK
NSNumber(value: value) NSNumber(value: value)
#else #else
if let ns = _nsNumberInitializer?.initialize(value: value) { if let ns = _nsNumberInitializer()?.initialize(value: value) {
return ns return ns
} else { } else {
return value return value

View File

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

View File

@ -31,37 +31,25 @@ internal import _ForSwiftFoundation
internal import CoreFoundation_Private.CFNotificationCenter internal import CoreFoundation_Private.CFNotificationCenter
#endif #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. /// Singleton which listens for notifications about preference changes for TimeZone and holds cached values for current, fixed time zones, etc.
struct TimeZoneCache : Sendable { 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 // MARK: - State
struct State { struct State {
@ -239,7 +227,7 @@ struct TimeZoneCache : Sendable {
} else if let cached = fixedTimeZones[identifier] { } else if let cached = fixedTimeZones[identifier] {
return cached return cached
} else { } else {
if let innerTz = TimeZoneCache.timeZoneICUClass?.init(identifier: identifier) { if let innerTz = _timeZoneICUClass()?.init(identifier: identifier) {
fixedTimeZones[identifier] = innerTz fixedTimeZones[identifier] = innerTz
return innerTz return innerTz
} else { } else {
@ -254,7 +242,7 @@ struct TimeZoneCache : Sendable {
} else { } else {
// In order to avoid bloating a cache with weird time zones, only cache values that are 30min offsets (including 1hr offsets). // 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 let doCache = abs(offset) % 1800 == 0
if let innerTz = TimeZoneCache.timeZoneGMTClass.init(secondsFromGMT: offset) { if let innerTz = _timeZoneGMTClass().init(secondsFromGMT: offset) {
if doCache { if doCache {
offsetTimeZones[offset] = innerTz offsetTimeZones[offset] = innerTz
} }

View File

@ -158,17 +158,18 @@ package protocol UIDNAHook {
static func decode(_ host: some StringProtocol) -> String? 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 { internal struct RFC3986Parser: URLParserProtocol {
static let kind: URLParserKind = .RFC3986 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 // MARK: - Encoding
@ -233,7 +234,7 @@ internal struct RFC3986Parser: URLParserProtocol {
} }
static func shouldPercentEncodeHost(_ host: some StringProtocol, forScheme scheme: (some StringProtocol)?) -> Bool { 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 // Always percent-encode the host if we can't access UIDNA encoding functions
return true return true
} }
@ -279,13 +280,13 @@ internal struct RFC3986Parser: URLParserProtocol {
static func IDNAEncodeHost(_ host: (some StringProtocol)?) -> String? { static func IDNAEncodeHost(_ host: (some StringProtocol)?) -> String? {
guard let host else { return nil } guard let host else { return nil }
guard !host.isEmpty else { return "" } guard !host.isEmpty else { return "" }
return uidnaHook?.encode(host) return _uidnaHook()?.encode(host)
} }
static func IDNADecodeHost(_ host: (some StringProtocol)?) -> String? { static func IDNADecodeHost(_ host: (some StringProtocol)?) -> String? {
guard let host else { return nil } guard let host else { return nil }
guard !host.isEmpty else { return "" } guard !host.isEmpty else { return "" }
guard let uidnaHook else { return String(host) } guard let uidnaHook = _uidnaHook() else { return String(host) }
return uidnaHook.decode(host) return uidnaHook.decode(host)
} }

View File

@ -26,6 +26,13 @@ import Darwin
internal import _FoundationICU 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 { internal final class _CalendarICU: _CalendarProtocol, @unchecked Sendable {
let lock: LockedState<Void> let lock: LockedState<Void>
let identifier: Calendar.Identifier let identifier: Calendar.Identifier

View File

@ -27,6 +27,13 @@ internal import _FoundationICU
import Glibc import Glibc
#endif #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 let MAX_ICU_NAME_SIZE: Int32 = 1024
internal final class _LocaleICU: _LocaleProtocol, Sendable { internal final class _LocaleICU: _LocaleProtocol, Sendable {

View File

@ -16,6 +16,13 @@ import FoundationEssentials
internal import _FoundationICU 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 { internal final class _TimeZoneGMTICU : _TimeZoneProtocol, @unchecked Sendable {
let offset: Int let offset: Int
let name: String let name: String

View File

@ -25,6 +25,13 @@ import ucrt
#if canImport(_FoundationICU) #if canImport(_FoundationICU)
internal import _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 { internal final class _TimeZoneICU: _TimeZoneProtocol, Sendable {
init?(secondsFromGMT: Int) { init?(secondsFromGMT: Int) {
fatalError("Unexpected init") fatalError("Unexpected init")

View File

@ -15,7 +15,14 @@ import FoundationEssentials
internal import _FoundationICU 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. // `Sendable` notes: `UIDNA` from ICU is thread safe.
struct UIDNAPointer : @unchecked Sendable { struct UIDNAPointer : @unchecked Sendable {
init(_ ptr: OpaquePointer?) { self.idnaTranscoder = ptr } init(_ ptr: OpaquePointer?) { self.idnaTranscoder = ptr }