Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
a1c665d
rdar://101694801 (Essentials String API: Case mapping)
itingliu Jan 11, 2023
4803651
rdar://102214045 (Re-core NSUUID on Swift UUID struct)
parkera Feb 20, 2023
55db333
rdar://102214045 -- Use NSString instead of NSObject
parkera Mar 1, 2023
6d13795
rdar://105100825 (NSLocale: Implement localeIdentifier and regionCode…
itingliu Mar 5, 2023
c698299
rdar://105186248 (Fix more warnings in Swift)
parkera Feb 20, 2023
0fa573b
rdar://106155597 (Fix regression)
parkera Mar 7, 2023
204cdec
rdar://102214045: Override supportsSecureCoding in Swift subclass
parkera Feb 27, 2023
f65a4df
rdar://106155597 -- Clean up
parkera Mar 8, 2023
344ecfe
rdar://106155597 -- Enable tests to run by refactoring out LocalePref…
parkera Mar 7, 2023
0b409c1
rdar://106155597 - Clarify purpose of keeping CFDictionary around in …
parkera Mar 8, 2023
e6986a0
rdar://106200399 (Fix Calendar Tests)
parkera Mar 4, 2023
8f9af73
rdar://106217659 (Fix Locale.currentLocale.decimalSeparator)
parkera Mar 14, 2023
3a2adee
rdar://106320664 (Added debug description to UUID)
jmschonfeld Mar 7, 2023
1ba7cb8
rdar://106555323 (NSLocale countryCode should behave the same as regi…
itingliu Mar 21, 2023
d4a070c
rdar://106792309 (Fix regression)
parkera Mar 21, 2023
fb29eff
rdar://106898040 (Fix currency text field)
parkera Mar 22, 2023
55669e4
rdar://107156343 (Move JSONEncoder to FoundationPreview)
iCharlesHu Mar 15, 2023
2b3a84d
rdar://106190030 (Fix crash)
parkera Mar 9, 2023
ec6077b
rdar://107156343 (Rebased JSONEncoder on top of new String changes)
itingliu Mar 23, 2023
f4d3605
Rebased on top of the new String APIs
iCharlesHu Mar 24, 2023
e2fb807
Enabled more tests
iCharlesHu Mar 27, 2023
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
Prev Previous commit
Next Next commit
rdar://106190030 (Fix crash)
  • Loading branch information
parkera authored and Charles Hu committed Mar 28, 2023
commit 2b3a84d1f068ffdfd7ea9e82a0f62ea6d1699fba
18 changes: 6 additions & 12 deletions Sources/FoundationInternationalization/Locale/Locale.swift
Original file line number Diff line number Diff line change
Expand Up @@ -122,26 +122,19 @@ public struct Locale : Hashable, Equatable, Sendable {
#if FOUNDATION_FRAMEWORK
/// This returns an instance of `Locale` that's set up exactly like it would be if the user changed the current locale to that identifier, set the preferences keys in the overrides dictionary, then called `current`.
internal static func localeAsIfCurrent(name: String?, cfOverrides: CFDictionary? = nil, disableBundleMatching: Bool = false) -> Locale {
let (inner, _) = _Locale._currentLocaleWithCFOverrides(name: name, overrides: cfOverrides, disableBundleMatching: disableBundleMatching)
return Locale(.fixed(inner))
return LocaleCache.cache.localeAsIfCurrent(name: name, cfOverrides: cfOverrides, disableBundleMatching: disableBundleMatching)
}
#endif
/// This returns an instance of `Locale` that's set up exactly like it would be if the user changed the current locale to that identifier, set the preferences keys in the overrides dictionary, then called `current`.
internal static func localeAsIfCurrent(name: String?, overrides: LocalePreferences? = nil, disableBundleMatching: Bool = false) -> Locale {
// On Darwin, this overrides are applied on top of CFPreferences.
let (inner, _) = _Locale._currentLocaleWithOverrides(name: name, overrides: overrides, disableBundleMatching: disableBundleMatching)
return Locale(.fixed(inner))
return LocaleCache.cache.localeAsIfCurrent(name: name, overrides: overrides, disableBundleMatching: disableBundleMatching)
}


internal static func localeAsIfCurrentWithBundleLocalizations(_ availableLocalizations: [String], allowsMixedLocalizations: Bool) -> Locale? {
guard let inner = _Locale._currentLocaleWithBundleLocalizations(availableLocalizations, allowsMixedLocalizations: allowsMixedLocalizations) else {
return nil
}
return Locale(.fixed(inner))
return LocaleCache.cache.localeAsIfCurrentWithBundleLocalizations(availableLocalizations, allowsMixedLocalizations: allowsMixedLocalizations)
}


// MARK: -
//

Expand All @@ -168,7 +161,8 @@ public struct Locale : Hashable, Equatable, Sendable {
self = .init(components: comps)
}

private init(_ kind: Kind) {
/// To be used only by `LocaleCache`.
internal init(_ kind: Kind) {
self.kind = kind
}

Expand Down Expand Up @@ -930,7 +924,7 @@ public struct Locale : Hashable, Equatable, Sendable {
/// - seealso: `Bundle.preferredLocalizations(from:)`
/// - seealso: `Bundle.preferredLocalizations(from:forPreferences:)`
public static var preferredLanguages: [String] {
_Locale.preferredLanguages(forCurrentUser: false)
LocaleCache.cache.preferredLanguages(forCurrentUser: false)
}


Expand Down
190 changes: 165 additions & 25 deletions Sources/FoundationInternationalization/Locale/Locale_Cache.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,18 +60,26 @@ struct LocaleCache : Sendable {
}
}

mutating func current() -> _Locale {
mutating func current(preferences: LocalePreferences?, cache: Bool) -> _Locale? {
resetCurrentIfNeeded()

if let cachedCurrentLocale {
return cachedCurrentLocale
} else {
let (locale, doCache) = _Locale._currentLocaleWithOverrides(name: nil, overrides: nil, disableBundleMatching: false)
if doCache {
self.cachedCurrentLocale = locale
}
return locale
}

// At this point we know we need to create, or re-create, the Locale instance.
// If we do not have a set of preferences to use, we have to return nil.
guard let preferences else {
return nil
}

let locale = _Locale(name: nil, prefs: preferences, disableBundleMatching: false)
if cache {
// It's possible this was an 'incomplete locale', in which case we will want to calculate it again later.
self.cachedCurrentLocale = locale
}

return locale
}

mutating func fixed(_ id: String) -> _Locale {
Expand All @@ -98,7 +106,7 @@ struct LocaleCache : Sendable {
}
}

mutating func currentNSLocale() -> _NSSwiftLocale {
mutating func currentNSLocale(preferences: LocalePreferences?, cache: Bool) -> _NSSwiftLocale? {
resetCurrentIfNeeded()

if let currentNSLocale = cachedCurrentNSLocale {
Expand All @@ -108,17 +116,25 @@ struct LocaleCache : Sendable {
let nsLocale = _NSSwiftLocale(Locale(inner: current))
cachedCurrentNSLocale = nsLocale
return nsLocale
} else {
// We have neither a Swift Locale nor an NSLocale. Recalculate and set both.
let (locale, doCache) = _Locale._currentLocaleWithOverrides(name: nil, overrides: nil, disableBundleMatching: false)
let nsLocale = _NSSwiftLocale(Locale(inner: locale))
if doCache {
// It's possible this was an 'incomplete locale', in which case we will want to calculate it again later.
self.cachedCurrentLocale = locale
cachedCurrentNSLocale = nsLocale
}
return nsLocale
}

// At this point we know we need to create, or re-create, the Locale instance.

// If we do not have a set of preferences to use, we have to return nil.
guard let preferences else {
return nil
}

// We have neither a Swift Locale nor an NSLocale. Recalculate and set both.
let locale = _Locale(name: nil, prefs: preferences, disableBundleMatching: false)
let nsLocale = _NSSwiftLocale(Locale(inner: locale))
if cache {
// It's possible this was an 'incomplete locale', in which case we will want to calculate it again later.
self.cachedCurrentLocale = locale
cachedCurrentNSLocale = nsLocale
}

return nsLocale
}

mutating func autoupdatingNSLocale() -> _NSSwiftLocale {
Expand Down Expand Up @@ -179,18 +195,30 @@ struct LocaleCache : Sendable {
}

var current: _Locale {
lock.withLock { $0.current() }
var result = lock.withLock {
$0.current(preferences: nil, cache: false)
}

if let result { return result }

// We need to fetch prefs and try again
let (prefs, doCache) = preferences()

result = lock.withLock {
$0.current(preferences: prefs, cache: doCache)
}

guard let result else {
fatalError("Nil result getting current Locale with preferences")
}

return result
}

var system: _Locale {
lock.withLock { $0.system() }
}

var preferred: _Locale {
let (locale, _) = _Locale._currentLocaleWithOverrides(name: nil, overrides: nil, disableBundleMatching: false)
return locale
}

func fixed(_ id: String) -> _Locale {
lock.withLock { $0.fixed(id) }
}
Expand All @@ -205,7 +233,24 @@ struct LocaleCache : Sendable {
}

func currentNSLocale() -> _NSSwiftLocale {
lock.withLock { $0.currentNSLocale() }
var result = lock.withLock {
$0.currentNSLocale(preferences: nil, cache: false)
}

if let result { return result }

// We need to fetch prefs and try again. Don't do this inside a lock (106190030). On Darwin it is possible to get a KVO callout from fetching the preferences, which could ask for the current Locale, which could cause a reentrant lock.
let (prefs, doCache) = preferences()

result = lock.withLock {
$0.currentNSLocale(preferences: prefs, cache: doCache)
}

guard let result else {
fatalError("Nil result getting current NSLocale with preferences")
}

return result
}

func systemNSLocale() -> _NSSwiftLocale {
Expand All @@ -216,4 +261,99 @@ struct LocaleCache : Sendable {
func fixedComponents(_ comps: Locale.Components) -> _Locale {
lock.withLock { $0.fixedComponents(comps) }
}

#if FOUNDATION_FRAMEWORK
func preferences() -> (LocalePreferences, Bool) {
// On Darwin, we check the current user preferences for Locale values
var wouldDeadlock: DarwinBoolean = false
let cfPrefs = __CFXPreferencesCopyCurrentApplicationStateWithDeadlockAvoidance(&wouldDeadlock).takeRetainedValue()

var prefs = LocalePreferences()
prefs.apply(cfPrefs)

if wouldDeadlock.boolValue {
// Don't cache a locale built with incomplete prefs
return (prefs, false)
} else {
return (prefs, true)
}
}

func preferredLanguages(forCurrentUser: Bool) -> [String] {
var languages: [String] = []
if forCurrentUser {
languages = CFPreferencesCopyValue("AppleLanguages" as CFString, kCFPreferencesAnyApplication, kCFPreferencesCurrentUser, kCFPreferencesAnyHost) as? [String] ?? []
} else {
languages = CFPreferencesCopyAppValue("AppleLanguages" as CFString, kCFPreferencesCurrentApplication) as? [String] ?? []
}

return languages.compactMap {
Locale.canonicalLanguageIdentifier(from: $0)
}
}

func preferredLocale() -> String? {
guard let preferredLocaleID = CFPreferencesCopyAppValue("AppleLocale" as CFString, kCFPreferencesCurrentApplication) as? String else {
return nil
}
return preferredLocaleID
}
#else
func preferences() -> (LocalePreferences, Bool) {
var prefs = LocalePreferences()
prefs.locale = "en_US"
prefs.languages = ["en-US"]
return (prefs, true)
}

func preferredLanguages(forCurrentUser: Bool) -> [String] {
[Locale.canonicalLanguageIdentifier(from: "en-US")]
}

func preferredLocale() -> String? {
"en_US"
}
#endif

#if FOUNDATION_FRAMEWORK
/// This returns an instance of `Locale` that's set up exactly like it would be if the user changed the current locale to that identifier, set the preferences keys in the overrides dictionary, then called `current`.
func localeAsIfCurrent(name: String?, cfOverrides: CFDictionary? = nil, disableBundleMatching: Bool = false) -> Locale {

var (prefs, _) = preferences()
if let cfOverrides { prefs.apply(cfOverrides) }

let inner = _Locale(name: name, prefs: prefs, disableBundleMatching: disableBundleMatching)
return Locale(.fixed(inner))
}
#endif

/// This returns an instance of `Locale` that's set up exactly like it would be if the user changed the current locale to that identifier, set the preferences keys in the overrides dictionary, then called `current`.
func localeAsIfCurrent(name: String?, overrides: LocalePreferences? = nil, disableBundleMatching: Bool = false) -> Locale {
var (prefs, _) = preferences()
if let overrides { prefs.apply(overrides) }

let inner = _Locale(name: name, prefs: prefs, disableBundleMatching: disableBundleMatching)
return Locale(.fixed(inner))
}


func localeAsIfCurrentWithBundleLocalizations(_ availableLocalizations: [String], allowsMixedLocalizations: Bool) -> Locale? {
guard !allowsMixedLocalizations else {
let (prefs, _) = preferences()
let inner = _Locale(name: nil, prefs: prefs, disableBundleMatching: true)
return Locale(.fixed(inner))
}

let preferredLanguages = preferredLanguages(forCurrentUser: false)
guard let preferredLocaleID = preferredLocale() else { return nil }

let canonicalizedLocalizations = availableLocalizations.compactMap { Locale.canonicalLanguageIdentifier(from: $0) }
let identifier = _Locale.localeIdentifierForCanonicalizedLocalizations(canonicalizedLocalizations, preferredLanguages: preferredLanguages, preferredLocaleID: preferredLocaleID)
guard let identifier else {
return nil
}

let inner = _Locale(identifier: identifier)
return Locale(.fixed(inner))
}
}
Loading