Skip to content

Use dynamic replacement instead of _typeByName for internationalization upcalls #756

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 18 additions & 22 deletions Sources/FoundationEssentials/Calendar/Calendar_Cache.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
}

Expand Down
10 changes: 6 additions & 4 deletions Sources/FoundationEssentials/FileManager/FileManager+Files.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
Expand All @@ -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
Expand Down
35 changes: 16 additions & 19 deletions Sources/FoundationEssentials/Locale/Locale_Cache.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand All @@ -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
}
Expand Down Expand Up @@ -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
Expand All @@ -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
}
Expand Down Expand Up @@ -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))
Expand Down
46 changes: 17 additions & 29 deletions Sources/FoundationEssentials/TimeZone/TimeZone_Cache.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,37 +31,25 @@ internal import _ForSwiftFoundation
internal import CoreFoundation_Private.CFNotificationCenter
#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
internal func _timeZoneICUClass() -> _TimeZoneProtocol.Type? {
_TimeZoneICU.self
}
internal func _timeZoneGMTClass() -> _TimeZoneProtocol.Type {
_TimeZoneGMTICU.self
}
#else
if let name = _typeByName("FoundationInternationalization._TimeZoneGMTICU"), let t = name as? _TimeZoneProtocol.Type {
return t
} else {
return _TimeZoneGMT.self
}
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: - State

struct State {
Expand Down Expand Up @@ -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 {
Expand All @@ -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
}
Expand Down
25 changes: 13 additions & 12 deletions Sources/FoundationEssentials/URL/URLParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
9 changes: 8 additions & 1 deletion Sources/FoundationInternationalization/URLParser+ICU.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down