From a8707f5cf0a536310144f62ca79532c66329689b Mon Sep 17 00:00:00 2001 From: Tony Parker Date: Mon, 24 Jun 2024 11:34:42 -0700 Subject: [PATCH 01/15] Adopt Sendable, fix Sendable related warnings, and fix other warnings in both Swift and C code --- Package.swift | 28 ++- Sources/CoreFoundation/CFBundle_Resources.c | 4 +- Sources/CoreFoundation/CFDate.c | 3 + Sources/CoreFoundation/CFNumber.c | 3 + Sources/CoreFoundation/CFPreferences.c | 6 + Sources/CoreFoundation/CFPropertyList.c | 4 +- .../CFRelativeDateTimeFormatter.c | 2 +- Sources/CoreFoundation/CFRuntime.c | 4 + Sources/Foundation/AffineTransform.swift | 30 +-- Sources/Foundation/Boxing.swift | 197 +----------------- Sources/Foundation/ByteCountFormatter.swift | 7 +- Sources/Foundation/CharacterSet.swift | 176 +++++++++++++++- .../Foundation/DateComponentsFormatter.swift | 6 +- Sources/Foundation/DateFormatter.swift | 34 +-- .../Foundation/DateIntervalFormatter.swift | 7 +- .../DispatchData+DataProtocol.swift | 3 + Sources/Foundation/EnergyFormatter.swift | 5 +- Sources/Foundation/FileHandle.swift | 62 +++--- Sources/Foundation/FileManager.swift | 44 ++-- Sources/Foundation/Formatter.swift | 7 +- Sources/Foundation/Host.swift | 3 + Sources/Foundation/ISO8601DateFormatter.swift | 5 +- Sources/Foundation/IndexSet.swift | 11 +- Sources/Foundation/JSONSerialization.swift | 7 +- Sources/Foundation/LengthFormatter.swift | 5 +- Sources/Foundation/MassFormatter.swift | 5 +- Sources/Foundation/Measurement.swift | 2 + Sources/Foundation/MeasurementFormatter.swift | 5 +- Sources/Foundation/Morphology.swift | 12 +- Sources/Foundation/NSArray.swift | 45 ++-- Sources/Foundation/NSAttributedString.swift | 9 +- Sources/Foundation/NSCFBoolean.swift | 2 +- Sources/Foundation/NSCFString.swift | 6 + Sources/Foundation/NSCache.swift | 3 + Sources/Foundation/NSCalendar.swift | 9 +- Sources/Foundation/NSCharacterSet.swift | 6 + Sources/Foundation/NSCoder.swift | 7 +- .../Foundation/NSComparisonPredicate.swift | 9 +- Sources/Foundation/NSCompoundPredicate.swift | 5 +- Sources/Foundation/NSConcreteValue.swift | 4 +- Sources/Foundation/NSData.swift | 10 + Sources/Foundation/NSDate.swift | 28 +-- Sources/Foundation/NSDateComponents.swift | 3 + Sources/Foundation/NSDecimalNumber.swift | 10 +- Sources/Foundation/NSDictionary.swift | 10 +- Sources/Foundation/NSEnumerator.swift | 6 + Sources/Foundation/NSError.swift | 26 ++- Sources/Foundation/NSExpression.swift | 6 +- Sources/Foundation/NSIndexPath.swift | 2 + Sources/Foundation/NSIndexSet.swift | 9 + Sources/Foundation/NSKeyedArchiver.swift | 3 + Sources/Foundation/NSKeyedUnarchiver.swift | 3 + Sources/Foundation/NSLocale.swift | 67 +++--- Sources/Foundation/NSMeasurement.swift | 3 + Sources/Foundation/NSNotification.swift | 9 +- Sources/Foundation/NSNull.swift | 2 +- Sources/Foundation/NSNumber.swift | 6 +- Sources/Foundation/NSObjCRuntime.swift | 9 +- Sources/Foundation/NSObject.swift | 7 + Sources/Foundation/NSOrderedSet.swift | 7 + Sources/Foundation/NSPathUtilities.swift | 4 +- .../Foundation/NSPersonNameComponents.swift | 103 +++++---- Sources/Foundation/NSPredicate.swift | 14 +- Sources/Foundation/NSRange.swift | 2 +- Sources/Foundation/NSRegularExpression.swift | 18 +- Sources/Foundation/NSSet.swift | 16 +- Sources/Foundation/NSSortDescriptor.swift | 3 + Sources/Foundation/NSSpecialValue.swift | 4 +- Sources/Foundation/NSString.swift | 71 ++++--- Sources/Foundation/NSStringAPI.swift | 2 +- Sources/Foundation/NSSwiftRuntime.swift | 6 + Sources/Foundation/NSTextCheckingResult.swift | 36 ++-- Sources/Foundation/NSTimeZone.swift | 3 + Sources/Foundation/NSURL.swift | 50 ++--- Sources/Foundation/NSURLComponents.swift | 2 + Sources/Foundation/NSURLQueryItem.swift | 6 +- Sources/Foundation/NSUUID.swift | 2 +- Sources/Foundation/NSValue.swift | 2 +- Sources/Foundation/Notification.swift | 2 + Sources/Foundation/NotificationQueue.swift | 7 +- Sources/Foundation/NumberFormatter.swift | 49 +++-- Sources/Foundation/Operation.swift | 26 +-- Sources/Foundation/PersonNameComponents.swift | 114 +++++----- .../PersonNameComponentsFormatter.swift | 7 +- Sources/Foundation/Port.swift | 17 +- Sources/Foundation/PortMessage.swift | 2 + Sources/Foundation/Process.swift | 12 +- Sources/Foundation/Progress.swift | 16 +- .../PropertyListSerialization.swift | 5 +- Sources/Foundation/RunLoop.swift | 55 +++-- Sources/Foundation/Scanner.swift | 2 + Sources/Foundation/ScannerAPI.swift | 2 +- Sources/Foundation/Stream.swift | 28 ++- Sources/Foundation/Thread.swift | 9 +- Sources/Foundation/Timer.swift | 12 +- Sources/Foundation/URL.swift | 13 +- Sources/Foundation/URLResourceKey.swift | 8 +- Sources/Foundation/Unit.swift | 56 ++--- Sources/Foundation/UserDefaults.swift | 3 + Sources/FoundationNetworking/Boxing.swift | 190 +---------------- Sources/FoundationNetworking/HTTPCookie.swift | 21 +- .../HTTPCookieStorage.swift | 4 +- .../FoundationNetworking/NSURLRequest.swift | 10 +- .../URLAuthenticationChallenge.swift | 6 +- Sources/FoundationNetworking/URLCache.swift | 6 +- .../FoundationNetworking/URLCredential.swift | 23 +- .../URLCredentialStorage.swift | 10 +- .../URLProtectionSpace.swift | 11 +- .../FoundationNetworking/URLProtocol.swift | 7 +- Sources/FoundationNetworking/URLRequest.swift | 2 +- .../FoundationNetworking/URLResponse.swift | 12 +- .../URLSession/Configuration.swift | 5 +- .../URLSession/FTP/FTPURLProtocol.swift | 2 +- .../URLSession/HTTP/HTTPURLProtocol.swift | 2 +- .../URLSession/NativeProtocol.swift | 3 +- .../URLSession/NetworkingSpecific.swift | 11 +- .../URLSession/TaskRegistry.swift | 4 +- .../URLSession/URLSession.swift | 29 +-- .../URLSession/URLSessionConfiguration.swift | 4 +- .../URLSession/URLSessionDelegate.swift | 36 ++-- .../URLSession/URLSessionTask.swift | 44 ++-- .../URLSession/URLSessionTaskMetrics.swift | 12 +- .../URLSession/libcurl/EasyHandle.swift | 16 +- .../URLSession/libcurl/MultiHandle.swift | 2 +- Sources/FoundationXML/XMLDTD.swift | 3 + Sources/FoundationXML/XMLDTDNode.swift | 5 +- Sources/FoundationXML/XMLDocument.swift | 5 +- Sources/FoundationXML/XMLElement.swift | 3 + Sources/FoundationXML/XMLNode.swift | 9 +- Sources/FoundationXML/XMLParser.swift | 7 +- .../XCTNSNotificationExpectation.swift | 2 +- .../XCTNSPredicateExpectation.swift | 5 +- Tests/Foundation/FTPServer.swift | 6 +- Tests/Foundation/HTTPServer.swift | 9 +- Tests/Foundation/TestCalendar.swift | 2 - Tests/Foundation/TestDataURLProtocol.swift | 13 +- Tests/Foundation/TestFileManager.swift | 11 +- Tests/Foundation/TestHTTPCookieStorage.swift | 7 +- Tests/Foundation/TestJSONEncoder.swift | 16 +- Tests/Foundation/TestJSONSerialization.swift | 75 ++----- Tests/Foundation/TestNSArray.swift | 4 + Tests/Foundation/TestNSDictionary.swift | 1 + Tests/Foundation/TestNSKeyedUnarchiver.swift | 2 +- Tests/Foundation/TestNSLocale.swift | 1 + Tests/Foundation/TestNSLock.swift | 12 +- .../Foundation/TestNSRegularExpression.swift | 6 +- Tests/Foundation/TestNotificationCenter.swift | 12 +- Tests/Foundation/TestNotificationQueue.swift | 34 +-- Tests/Foundation/TestOperationQueue.swift | 163 ++++++++------- Tests/Foundation/TestProcess.swift | 50 +++-- .../TestPropertyListSerialization.swift | 5 +- Tests/Foundation/TestRunLoop.swift | 10 +- Tests/Foundation/TestScanner.swift | 1 + Tests/Foundation/TestTimer.swift | 42 ++-- Tests/Foundation/TestURL.swift | 23 +- Tests/Foundation/TestURLSession.swift | 84 +++++--- Tests/Foundation/TestURLSessionFTP.swift | 3 +- Tests/Foundation/TestXMLDocument.swift | 1 + 158 files changed, 1579 insertions(+), 1332 deletions(-) diff --git a/Package.swift b/Package.swift index 3fa7f28a91..e6936a5bb7 100644 --- a/Package.swift +++ b/Package.swift @@ -21,6 +21,7 @@ let coreFoundationBuildSettings: [CSetting] = [ "-Wno-unused-function", "-Wno-microsoft-enum-forward-reference", "-Wno-int-conversion", + "-Wno-switch", "-fconstant-cfstrings", "-fexceptions", // TODO: not on OpenBSD "-fdollars-in-identifiers", @@ -66,6 +67,10 @@ let interfaceBuildSettings: [CSetting] = [ let swiftBuildSettings: [SwiftSetting] = [ .define("DEPLOYMENT_RUNTIME_SWIFT"), .define("SWIFT_CORELIBS_FOUNDATION_HAS_THREADS"), + .unsafeFlags([ + "-Xfrontend", + "-require-explicit-sendable", + ]) ] var dependencies: [Package.Dependency] { @@ -110,6 +115,9 @@ let package = Package( "CoreFoundation" ], path: "Sources/Foundation", + exclude: [ + "CMakeLists.txt" + ], swiftSettings: swiftBuildSettings ), .target( @@ -121,6 +129,9 @@ let package = Package( "_CFXMLInterface" ], path: "Sources/FoundationXML", + exclude: [ + "CMakeLists.txt" + ], swiftSettings: swiftBuildSettings ), .target( @@ -132,6 +143,9 @@ let package = Package( "_CFURLSessionInterface" ], path: "Sources/FoundationNetworking", + exclude: [ + "CMakeLists.txt" + ], swiftSettings:swiftBuildSettings ), .target( @@ -140,7 +154,10 @@ let package = Package( .product(name: "_FoundationICU", package: "swift-foundation-icu"), ], path: "Sources/CoreFoundation", - exclude: ["BlockRuntime"], + exclude: [ + "BlockRuntime", + "CMakeLists.txt" + ], cSettings: coreFoundationBuildSettings ), .target( @@ -150,6 +167,9 @@ let package = Package( "Clibxml2", ], path: "Sources/_CFXMLInterface", + exclude: [ + "CMakeLists.txt" + ], cSettings: interfaceBuildSettings ), .target( @@ -159,6 +179,9 @@ let package = Package( "Clibcurl", ], path: "Sources/_CFURLSessionInterface", + exclude: [ + "CMakeLists.txt" + ], cSettings: interfaceBuildSettings ), .systemLibrary( @@ -181,6 +204,9 @@ let package = Package( name: "plutil", dependencies: [ "Foundation" + ], + exclude: [ + "CMakeLists.txt" ] ), .executableTarget( diff --git a/Sources/CoreFoundation/CFBundle_Resources.c b/Sources/CoreFoundation/CFBundle_Resources.c index 013987f53a..f32ce6df65 100644 --- a/Sources/CoreFoundation/CFBundle_Resources.c +++ b/Sources/CoreFoundation/CFBundle_Resources.c @@ -294,9 +294,9 @@ CF_PRIVATE _CFBundleVersion _CFBundleGetBundleVersionForURL(CFURLRef url) { foundSupportFiles2 = true; } else if (fileNameLen == supportFilesDirectoryLength && CFStringCompareWithOptions(fileName, _CFBundleSupportFilesDirectoryName1, CFRangeMake(0, supportFilesDirectoryLength), kCFCompareCaseInsensitive) == kCFCompareEqualTo) { foundSupportFiles1 = true; - } else if (fileNameLen == wrapperDirLength && CFStringCompareWithOptions(fileName, _CFBundleWrapperDirectoryName, CFRangeMake(0, wrapperDirLength), kCFCompareEqualTo) == kCFCompareEqualTo) { + } else if (fileNameLen == wrapperDirLength && CFStringCompareWithOptions(fileName, _CFBundleWrapperDirectoryName, CFRangeMake(0, wrapperDirLength), 0) == kCFCompareEqualTo) { foundAppWrapperDirectory = true; - } else if (fileType == DT_LNK && fileNameLen == wrapperLinkLength && CFStringCompareWithOptions(fileName, _CFBundleWrapperLinkName, CFRangeMake(0, wrapperLinkLength), kCFCompareEqualTo) == kCFCompareEqualTo) { + } else if (fileType == DT_LNK && fileNameLen == wrapperLinkLength && CFStringCompareWithOptions(fileName, _CFBundleWrapperLinkName, CFRangeMake(0, wrapperLinkLength), 0) == kCFCompareEqualTo) { foundAppWrapperLink = true; } } else if (fileType == DT_UNKNOWN) { diff --git a/Sources/CoreFoundation/CFDate.c b/Sources/CoreFoundation/CFDate.c index 58957d1ca9..098aee24a3 100644 --- a/Sources/CoreFoundation/CFDate.c +++ b/Sources/CoreFoundation/CFDate.c @@ -48,8 +48,11 @@ double __CFTSRRate = 0.0; static double __CF1_TSRRate = 0.0; CF_PRIVATE uint64_t __CFTimeIntervalToTSR(CFTimeInterval ti) { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wimplicit-const-int-float-conversion" if ((ti * __CFTSRRate) > INT64_MAX / 2) return (INT64_MAX / 2); return (uint64_t)(ti * __CFTSRRate); +#pragma GCC diagnostic pop } CF_PRIVATE CFTimeInterval __CFTSRToTimeInterval(uint64_t tsr) { diff --git a/Sources/CoreFoundation/CFNumber.c b/Sources/CoreFoundation/CFNumber.c index d5ea7fa55e..fca8f611e3 100644 --- a/Sources/CoreFoundation/CFNumber.c +++ b/Sources/CoreFoundation/CFNumber.c @@ -534,6 +534,8 @@ static Boolean __CFNumberGetValue(CFNumberRef number, CFNumberType type, void *v } } return true; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wimplicit-const-int-float-conversion" case kCFNumberSInt32Type: if (floatBit) { if (!storageBit) { @@ -564,6 +566,7 @@ static Boolean __CFNumberGetValue(CFNumberRef number, CFNumberType type, void *v } } return true; +#pragma GCC diagnostic pop case kCFNumberSInt128Type: if (floatBit) { if (!storageBit) { diff --git a/Sources/CoreFoundation/CFPreferences.c b/Sources/CoreFoundation/CFPreferences.c index 71e77a2381..053de44f13 100644 --- a/Sources/CoreFoundation/CFPreferences.c +++ b/Sources/CoreFoundation/CFPreferences.c @@ -234,14 +234,20 @@ CFDictionaryRef CFPreferencesCopyMultiple(CFArrayRef keysToFetch, CFStringRef ap __CFGenericValidateType(host, CFStringGetTypeID()); domain = _CFPreferencesStandardDomain(appName, user, host); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnonnull" if (!domain) return NULL; +#pragma GCC diagnostic pop if (!keysToFetch) { return _CFPreferencesDomainDeepCopyDictionary(domain); } else { __CFGenericValidateType(keysToFetch, CFArrayGetTypeID()); count = CFArrayGetCount(keysToFetch); result = CFDictionaryCreateMutable(CFGetAllocator(domain), count, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnonnull" if (!result) return NULL; +#pragma GCC diagnostic pop for (idx = 0; idx < count; idx ++) { CFStringRef key = (CFStringRef)CFArrayGetValueAtIndex(keysToFetch, idx); CFPropertyListRef value; diff --git a/Sources/CoreFoundation/CFPropertyList.c b/Sources/CoreFoundation/CFPropertyList.c index 9c95557545..b5a6919210 100644 --- a/Sources/CoreFoundation/CFPropertyList.c +++ b/Sources/CoreFoundation/CFPropertyList.c @@ -1587,6 +1587,8 @@ static Boolean parseDictTag(_CFXMLPlistParseInfo * _Nonnull pInfo, CFTypeRef * _ static Boolean parseDataTag(_CFXMLPlistParseInfo *pInfo, CFTypeRef *out) { const char *base = pInfo->curr; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wgnu-folding-constant" static const unsigned char dataDecodeTableSize = 128; static const signed char dataDecodeTable[dataDecodeTableSize] = { /* 000 */ -1, -1, -1, -1, -1, -1, -1, -1, @@ -1606,7 +1608,7 @@ static Boolean parseDataTag(_CFXMLPlistParseInfo *pInfo, CFTypeRef *out) { /* 'p' */ 41, 42, 43, 44, 45, 46, 47, 48, /* 'x' */ 49, 50, 51, -1, -1, -1, -1, -1 }; - +#pragma GCC diagnostic pop int tmpbufpos = 0; int tmpbuflen = 256; uint8_t *tmpbuf = pInfo->skip ? NULL : (uint8_t *)CFAllocatorAllocate(pInfo->allocator, tmpbuflen, 0); diff --git a/Sources/CoreFoundation/CFRelativeDateTimeFormatter.c b/Sources/CoreFoundation/CFRelativeDateTimeFormatter.c index 9e9adb66fe..bd9086340d 100644 --- a/Sources/CoreFoundation/CFRelativeDateTimeFormatter.c +++ b/Sources/CoreFoundation/CFRelativeDateTimeFormatter.c @@ -23,7 +23,7 @@ struct __CFRelativeDateTimeFormatter { CFRelativeDateTimeFormattingContext _formattingContext; }; -static UDateRelativeDateTimeFormatterStyle icuRelativeDateTimeStyleFromUnitsStyle(CFRelativeDateTimeFormatterStyle style) { +static UDateRelativeDateTimeFormatterStyle icuRelativeDateTimeStyleFromUnitsStyle(CFRelativeDateTimeFormatterUnitsStyle style) { switch (style) { case CFRelativeDateTimeFormatterUnitsStyleSpellOut: case CFRelativeDateTimeFormatterUnitsStyleFull: diff --git a/Sources/CoreFoundation/CFRuntime.c b/Sources/CoreFoundation/CFRuntime.c index 4b770d62d1..2c151b8cc4 100644 --- a/Sources/CoreFoundation/CFRuntime.c +++ b/Sources/CoreFoundation/CFRuntime.c @@ -1774,11 +1774,15 @@ struct _NSCFXMLBridgeUntyped __NSCFXMLBridgeUntyped = { CFDataGetBytePtr, CFDictionaryCreateMutable, CFDictionarySetValue, + // We cannot use the real types here because eventually it winds up exposed as API using CF types in Swift, which we do not want +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wincompatible-pointer-types-discards-qualifiers" &kCFAllocatorSystemDefault, &kCFAllocatorNull, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks, &kCFErrorLocalizedDescriptionKey, +#pragma GCC diagnostic pop }; // Call out to the CF-level finalizer, because the object is going to go away. diff --git a/Sources/Foundation/AffineTransform.swift b/Sources/Foundation/AffineTransform.swift index 34471e8975..82ed957248 100644 --- a/Sources/Foundation/AffineTransform.swift +++ b/Sources/Foundation/AffineTransform.swift @@ -14,7 +14,7 @@ /// [ m21 m22 0 ] /// [ tX tY 1 ] /// ``` -public struct AffineTransform: ReferenceConvertible { +public struct AffineTransform: ReferenceConvertible, Sendable { public typealias ReferenceType = NSAffineTransform public var m11: CGFloat @@ -382,7 +382,7 @@ extension AffineTransform: CustomStringConvertible { /// A structure that defines the three-by-three matrix that performs an affine transform between two coordinate systems. -public struct NSAffineTransformStruct { +public struct NSAffineTransformStruct : Sendable { public var m11: CGFloat public var m12: CGFloat public var m21: CGFloat @@ -412,7 +412,7 @@ public struct NSAffineTransformStruct { } } -open class NSAffineTransform: NSObject { +open class NSAffineTransform: NSObject, @unchecked Sendable { // Internal only for testing. internal var affineTransform: AffineTransform @@ -495,7 +495,7 @@ extension NSAffineTransform { } extension NSAffineTransform: NSCopying { - open func copy(with zone: NSZone? = nil) -> Any { + public func copy(with zone: NSZone? = nil) -> Any { NSAffineTransform(transform: affineTransform) } } @@ -503,7 +503,7 @@ extension NSAffineTransform: NSCopying { extension NSAffineTransform: NSSecureCoding { public static let supportsSecureCoding = true - open func encode(with aCoder: NSCoder) { + public func encode(with aCoder: NSCoder) { precondition(aCoder.allowsKeyedCoding, "Unkeyed coding is unsupported.") let array = [ @@ -526,32 +526,32 @@ extension NSAffineTransform: NSSecureCoding { extension NSAffineTransform { /// Applies the specified translation factors to the transformation matrix. - open func translateX(by deltaX: CGFloat, yBy deltaY: CGFloat) { + public func translateX(by deltaX: CGFloat, yBy deltaY: CGFloat) { affineTransform.translate(x: deltaX, y: deltaY) } /// Applies scaling factors to each axis of the transformation matrix. - open func scaleX(by scaleX: CGFloat, yBy scaleY: CGFloat) { + public func scaleX(by scaleX: CGFloat, yBy scaleY: CGFloat) { affineTransform.scale(x: scaleX, y: scaleY) } /// Applies the specified scaling factor along both x and y axes to the transformation matrix. - open func scale(by scale: CGFloat) { + public func scale(by scale: CGFloat) { affineTransform.scale(scale) } /// Applies a rotation factor (measured in degrees) to the transformation matrix. - open func rotate(byDegrees angle: CGFloat) { + public func rotate(byDegrees angle: CGFloat) { affineTransform.rotate(byDegrees: angle) } /// Applies a rotation factor (measured in radians) to the transformation matrix. - open func rotate(byRadians angle: CGFloat) { + public func rotate(byRadians angle: CGFloat) { affineTransform.rotate(byRadians: angle) } /// Replaces the matrix with its inverse matrix. - open func invert() { + public func invert() { guard let inverse = affineTransform.inverted() else { fatalError("NSAffineTransform: Transform has no inverse") } @@ -560,22 +560,22 @@ extension NSAffineTransform { } /// Appends the specified matrix. - open func append(_ transform: AffineTransform) { + public func append(_ transform: AffineTransform) { affineTransform.append(transform) } /// Prepends the specified matrix. - open func prepend(_ transform: AffineTransform) { + public func prepend(_ transform: AffineTransform) { affineTransform.prepend(transform) } /// Applies the transform to the specified point and returns the result. - open func transform(_ aPoint: CGPoint) -> CGPoint { + public func transform(_ aPoint: CGPoint) -> CGPoint { affineTransform.transform(aPoint) } /// Applies the transform to the specified size and returns the result. - open func transform(_ aSize: CGSize) -> CGSize { + public func transform(_ aSize: CGSize) -> CGSize { affineTransform.transform(aSize) } } diff --git a/Sources/Foundation/Boxing.swift b/Sources/Foundation/Boxing.swift index 48ec6b5496..53d6320548 100644 --- a/Sources/Foundation/Boxing.swift +++ b/Sources/Foundation/Boxing.swift @@ -15,7 +15,14 @@ /// A class type which acts as a handle (pointer-to-pointer) to a Foundation reference type which has only a mutable class (e.g., NSURLComponents). /// /// Note: This assumes that the result of calling copy() is mutable. The documentation says that classes which do not have a mutable/immutable distinction should just adopt NSCopying instead of NSMutableCopying. -internal final class _MutableHandle where MutableType : NSCopying { +/// +/// `Sendable` Note: A `_MutableHandle` can be considered safely `Sendable` if and only if the following conditions of the `_MutableBoxing`-conforming type are met: +/// - All calls within `mapWithoutMutation` calls are read-only, and are safe to execute concurrently across multiple actors +/// - The passed pointer to the `MutableType` does not escape any `mapWithoutMutation`/`_applyMutation` blocks +/// - Any and all mutations of the held mutable type are only performed in an `_applyMutation` block +/// If both of those conditions are met and verified, the Copy on Write protections will make the `_MutableHandle` safely `Sendable` (the `_MutableBoxing`-conforming type can be marked `Sendable` if these +/// conditions are met and the type is otherwise `Sendable`) +internal final class _MutableHandle : @unchecked Sendable where MutableType : NSCopying { @usableFromInline internal var _pointer : MutableType init(reference : MutableType) { @@ -39,191 +46,3 @@ internal final class _MutableHandle where MutableType : return _pointer } } - -/// Describes common operations for Foundation struct types that are bridged to a mutable object (e.g. NSURLComponents). -internal protocol _MutableBoxing : ReferenceConvertible { - var _handle : _MutableHandle { get set } - - /// Apply a mutating closure to the reference type, regardless if it is mutable or immutable. - /// - /// This function performs the correct copy-on-write check for efficient mutation. - mutating func _applyMutation(_ whatToDo : (ReferenceType) -> ReturnType) -> ReturnType -} - -extension _MutableBoxing { - @inline(__always) - mutating func _applyMutation(_ whatToDo : (ReferenceType) -> ReturnType) -> ReturnType { - // Only create a new box if we are not uniquely referenced - if !isKnownUniquelyReferenced(&_handle) { - let ref = _handle._pointer - _handle = _MutableHandle(reference: ref) - } - return whatToDo(_handle._pointer) - } -} - -internal enum _MutableUnmanagedWrapper where MutableType : NSMutableCopying { - case Immutable(Unmanaged) - case Mutable(Unmanaged) -} - -internal protocol _SwiftNativeFoundationType: AnyObject { - associatedtype ImmutableType : NSObject - associatedtype MutableType : NSObject, NSMutableCopying - var __wrapped : _MutableUnmanagedWrapper { get } - - init(unmanagedImmutableObject: Unmanaged) - init(unmanagedMutableObject: Unmanaged) - - func mutableCopy(with zone : NSZone) -> Any - - func hash(into hasher: inout Hasher) - var hashValue: Int { get } - - var description: String { get } - var debugDescription: String { get } - - func releaseWrappedObject() -} - -extension _SwiftNativeFoundationType { - - @inline(__always) - func _mapUnmanaged(_ whatToDo : (ImmutableType) throws -> ReturnType) rethrows -> ReturnType { - defer { _fixLifetime(self) } - - switch __wrapped { - case .Immutable(let i): - return try i._withUnsafeGuaranteedRef { - _onFastPath() - return try whatToDo($0) - } - case .Mutable(let m): - return try m._withUnsafeGuaranteedRef { - _onFastPath() - return try whatToDo(_unsafeReferenceCast($0, to: ImmutableType.self)) - } - } - } - - func releaseWrappedObject() { - switch __wrapped { - case .Immutable(let i): - i.release() - case .Mutable(let m): - m.release() - } - } - - func mutableCopy(with zone : NSZone) -> Any { - return _mapUnmanaged { ($0 as NSObject).mutableCopy() } - } - - func hash(into hasher: inout Hasher) { - _mapUnmanaged { hasher.combine($0) } - } - - var hashValue: Int { - return _mapUnmanaged { return $0.hashValue } - } - - var description: String { - return _mapUnmanaged { return $0.description } - } - - var debugDescription: String { - return _mapUnmanaged { return $0.debugDescription } - } - - func isEqual(_ other: AnyObject) -> Bool { - return _mapUnmanaged { return $0.isEqual(other) } - } -} - -internal protocol _MutablePairBoxing { - associatedtype WrappedSwiftNSType : _SwiftNativeFoundationType - var _wrapped : WrappedSwiftNSType { get set } -} - -extension _MutablePairBoxing { - @inline(__always) - func _mapUnmanaged(_ whatToDo : (WrappedSwiftNSType.ImmutableType) throws -> ReturnType) rethrows -> ReturnType { - // We are using Unmanaged. Make sure that the owning container class - // 'self' is guaranteed to be alive by extending the lifetime of 'self' - // to the end of the scope of this function. - // Note: At the time of this writing using withExtendedLifetime here - // instead of _fixLifetime causes different ARC pair matching behavior - // foiling optimization. This is why we explicitly use _fixLifetime here - // instead. - defer { _fixLifetime(self) } - - let unmanagedHandle = Unmanaged.passUnretained(_wrapped) - let wrapper = unmanagedHandle._withUnsafeGuaranteedRef { $0.__wrapped } - switch (wrapper) { - case .Immutable(let i): - return try i._withUnsafeGuaranteedRef { - return try whatToDo($0) - } - case .Mutable(let m): - return try m._withUnsafeGuaranteedRef { - return try whatToDo(_unsafeReferenceCast($0, to: WrappedSwiftNSType.ImmutableType.self)) - } - } - } - - @inline(__always) - mutating func _applyUnmanagedMutation(_ whatToDo : (WrappedSwiftNSType.MutableType) throws -> ReturnType) rethrows -> ReturnType { - // We are using Unmanaged. Make sure that the owning container class - // 'self' is guaranteed to be alive by extending the lifetime of 'self' - // to the end of the scope of this function. - // Note: At the time of this writing using withExtendedLifetime here - // instead of _fixLifetime causes different ARC pair matching behavior - // foiling optimization. This is why we explicitly use _fixLifetime here - // instead. - defer { _fixLifetime(self) } - - var unique = true - let _unmanagedHandle = Unmanaged.passUnretained(_wrapped) - let wrapper = _unmanagedHandle._withUnsafeGuaranteedRef { $0.__wrapped } - - // This check is done twice because: Value kept live for too long causing uniqueness check to fail - switch (wrapper) { - case .Immutable: - break - case .Mutable: - unique = isKnownUniquelyReferenced(&_wrapped) - } - - switch (wrapper) { - case .Immutable(let i): - // We need to become mutable; by creating a new instance we also become unique - let copy = Unmanaged.passRetained(i._withUnsafeGuaranteedRef { - return _unsafeReferenceCast($0.mutableCopy(), to: WrappedSwiftNSType.MutableType.self) } - ) - - // Be sure to set the var before calling out; otherwise references to the struct in the closure may be looking at the old value - _wrapped = WrappedSwiftNSType(unmanagedMutableObject: copy) - return try copy._withUnsafeGuaranteedRef { - _onFastPath() - return try whatToDo($0) - } - case .Mutable(let m): - // Only create a new box if we are not uniquely referenced - if !unique { - let copy = Unmanaged.passRetained(m._withUnsafeGuaranteedRef { - return _unsafeReferenceCast($0.mutableCopy(), to: WrappedSwiftNSType.MutableType.self) - }) - _wrapped = WrappedSwiftNSType(unmanagedMutableObject: copy) - return try copy._withUnsafeGuaranteedRef { - _onFastPath() - return try whatToDo($0) - } - } else { - return try m._withUnsafeGuaranteedRef { - _onFastPath() - return try whatToDo($0) - } - } - } - } -} diff --git a/Sources/Foundation/ByteCountFormatter.swift b/Sources/Foundation/ByteCountFormatter.swift index 70ab924730..75ecc4e908 100644 --- a/Sources/Foundation/ByteCountFormatter.swift +++ b/Sources/Foundation/ByteCountFormatter.swift @@ -9,7 +9,7 @@ extension ByteCountFormatter { - public struct Units : OptionSet { + public struct Units : OptionSet, Sendable { public let rawValue : UInt public init(rawValue: UInt) { self.rawValue = rawValue } @@ -27,7 +27,7 @@ extension ByteCountFormatter { public static let useAll = Units(rawValue: 0x0FFFF) } - public enum CountStyle : Int { + public enum CountStyle : Int, Sendable { // Specifies display of file or storage byte counts. The actual behavior for this is platform-specific; on OS X 10.8, this uses the decimal style, but that may change over time. case file @@ -39,6 +39,9 @@ extension ByteCountFormatter { } } +@available(*, unavailable) +extension ByteCountFormatter : Sendable { } + open class ByteCountFormatter : Formatter { public override init() { super.init() diff --git a/Sources/Foundation/CharacterSet.swift b/Sources/Foundation/CharacterSet.swift index d5a4540006..83b2b2d03e 100644 --- a/Sources/Foundation/CharacterSet.swift +++ b/Sources/Foundation/CharacterSet.swift @@ -18,11 +18,11 @@ private func _utfRangeToNSRange(_ inRange : ClosedRange) -> NSRan return NSRange(location: Int(inRange.lowerBound.value), length: Int(inRange.upperBound.value - inRange.lowerBound.value + 1)) } -internal final class _SwiftNSCharacterSet : NSCharacterSet, _SwiftNativeFoundationType { +internal final class _SwiftNSCharacterSet : NSCharacterSet, _SwiftNativeFoundationType, @unchecked Sendable { internal typealias ImmutableType = NSCharacterSet internal typealias MutableType = NSMutableCharacterSet - var __wrapped : _MutableUnmanagedWrapper + fileprivate var __wrapped : _MutableUnmanagedWrapper init(immutableObject: AnyObject) { // Take ownership. @@ -107,7 +107,7 @@ internal final class _SwiftNSCharacterSet : NSCharacterSet, _SwiftNativeFoundati This type provides "copy-on-write" behavior, and is also bridged to the Objective-C `NSCharacterSet` class. */ -public struct CharacterSet : ReferenceConvertible, Equatable, Hashable, SetAlgebra, _MutablePairBoxing { +public struct CharacterSet : ReferenceConvertible, Equatable, Hashable, SetAlgebra, Sendable, _MutablePairBoxing { public typealias ReferenceType = NSCharacterSet internal typealias SwiftNSWrapping = _SwiftNSCharacterSet @@ -528,3 +528,173 @@ extension CharacterSet : Codable { try container.encode(self.bitmapRepresentation, forKey: .bitmap) } } + +// MARK: - Boxing protocols +// Only used by CharacterSet at this time + +fileprivate enum _MutableUnmanagedWrapper where MutableType : NSMutableCopying { + case Immutable(Unmanaged) + case Mutable(Unmanaged) +} + +fileprivate protocol _SwiftNativeFoundationType: AnyObject { + associatedtype ImmutableType : NSObject + associatedtype MutableType : NSObject, NSMutableCopying + var __wrapped : _MutableUnmanagedWrapper { get } + + init(unmanagedImmutableObject: Unmanaged) + init(unmanagedMutableObject: Unmanaged) + + func mutableCopy(with zone : NSZone) -> Any + + func hash(into hasher: inout Hasher) + var hashValue: Int { get } + + var description: String { get } + var debugDescription: String { get } + + func releaseWrappedObject() +} + +extension _SwiftNativeFoundationType { + + @inline(__always) + func _mapUnmanaged(_ whatToDo : (ImmutableType) throws -> ReturnType) rethrows -> ReturnType { + defer { _fixLifetime(self) } + + switch __wrapped { + case .Immutable(let i): + return try i._withUnsafeGuaranteedRef { + _onFastPath() + return try whatToDo($0) + } + case .Mutable(let m): + return try m._withUnsafeGuaranteedRef { + _onFastPath() + return try whatToDo(_unsafeReferenceCast($0, to: ImmutableType.self)) + } + } + } + + func releaseWrappedObject() { + switch __wrapped { + case .Immutable(let i): + i.release() + case .Mutable(let m): + m.release() + } + } + + func mutableCopy(with zone : NSZone) -> Any { + return _mapUnmanaged { ($0 as NSObject).mutableCopy() } + } + + func hash(into hasher: inout Hasher) { + _mapUnmanaged { hasher.combine($0) } + } + + var hashValue: Int { + return _mapUnmanaged { return $0.hashValue } + } + + var description: String { + return _mapUnmanaged { return $0.description } + } + + var debugDescription: String { + return _mapUnmanaged { return $0.debugDescription } + } + + func isEqual(_ other: AnyObject) -> Bool { + return _mapUnmanaged { return $0.isEqual(other) } + } +} + +fileprivate protocol _MutablePairBoxing { + associatedtype WrappedSwiftNSType : _SwiftNativeFoundationType + var _wrapped : WrappedSwiftNSType { get set } +} + +extension _MutablePairBoxing { + @inline(__always) + func _mapUnmanaged(_ whatToDo : (WrappedSwiftNSType.ImmutableType) throws -> ReturnType) rethrows -> ReturnType { + // We are using Unmanaged. Make sure that the owning container class + // 'self' is guaranteed to be alive by extending the lifetime of 'self' + // to the end of the scope of this function. + // Note: At the time of this writing using withExtendedLifetime here + // instead of _fixLifetime causes different ARC pair matching behavior + // foiling optimization. This is why we explicitly use _fixLifetime here + // instead. + defer { _fixLifetime(self) } + + let unmanagedHandle = Unmanaged.passUnretained(_wrapped) + let wrapper = unmanagedHandle._withUnsafeGuaranteedRef { $0.__wrapped } + switch (wrapper) { + case .Immutable(let i): + return try i._withUnsafeGuaranteedRef { + return try whatToDo($0) + } + case .Mutable(let m): + return try m._withUnsafeGuaranteedRef { + return try whatToDo(_unsafeReferenceCast($0, to: WrappedSwiftNSType.ImmutableType.self)) + } + } + } + + @inline(__always) + mutating func _applyUnmanagedMutation(_ whatToDo : (WrappedSwiftNSType.MutableType) throws -> ReturnType) rethrows -> ReturnType { + // We are using Unmanaged. Make sure that the owning container class + // 'self' is guaranteed to be alive by extending the lifetime of 'self' + // to the end of the scope of this function. + // Note: At the time of this writing using withExtendedLifetime here + // instead of _fixLifetime causes different ARC pair matching behavior + // foiling optimization. This is why we explicitly use _fixLifetime here + // instead. + defer { _fixLifetime(self) } + + var unique = true + let _unmanagedHandle = Unmanaged.passUnretained(_wrapped) + let wrapper = _unmanagedHandle._withUnsafeGuaranteedRef { $0.__wrapped } + + // This check is done twice because: Value kept live for too long causing uniqueness check to fail + switch (wrapper) { + case .Immutable: + break + case .Mutable: + unique = isKnownUniquelyReferenced(&_wrapped) + } + + switch (wrapper) { + case .Immutable(let i): + // We need to become mutable; by creating a new instance we also become unique + let copy = Unmanaged.passRetained(i._withUnsafeGuaranteedRef { + return _unsafeReferenceCast($0.mutableCopy(), to: WrappedSwiftNSType.MutableType.self) } + ) + + // Be sure to set the var before calling out; otherwise references to the struct in the closure may be looking at the old value + _wrapped = WrappedSwiftNSType(unmanagedMutableObject: copy) + return try copy._withUnsafeGuaranteedRef { + _onFastPath() + return try whatToDo($0) + } + case .Mutable(let m): + // Only create a new box if we are not uniquely referenced + if !unique { + let copy = Unmanaged.passRetained(m._withUnsafeGuaranteedRef { + return _unsafeReferenceCast($0.mutableCopy(), to: WrappedSwiftNSType.MutableType.self) + }) + _wrapped = WrappedSwiftNSType(unmanagedMutableObject: copy) + return try copy._withUnsafeGuaranteedRef { + _onFastPath() + return try whatToDo($0) + } + } else { + return try m._withUnsafeGuaranteedRef { + _onFastPath() + return try whatToDo($0) + } + } + } + } +} + diff --git a/Sources/Foundation/DateComponentsFormatter.swift b/Sources/Foundation/DateComponentsFormatter.swift index 6bd39171b0..7f838aba7d 100644 --- a/Sources/Foundation/DateComponentsFormatter.swift +++ b/Sources/Foundation/DateComponentsFormatter.swift @@ -10,8 +10,8 @@ /* DateComponentsFormatter provides locale-correct and flexible string formatting of quantities of time, such as "1 day" or "1h 10m", as specified by NSDateComponents. For formatting intervals of time (such as "2PM to 5PM"), see DateIntervalFormatter. DateComponentsFormatter is thread-safe, in that calling methods on it from multiple threads will not cause crashes or incorrect results, but it makes no attempt to prevent confusion when one thread sets something and another thread isn't expecting it to change. */ @available(*, unavailable, message: "Not supported in swift-corelibs-foundation") -open class DateComponentsFormatter : Formatter { - public enum UnitsStyle : Int { +open class DateComponentsFormatter : Formatter, @unchecked Sendable { + public enum UnitsStyle : Int, Sendable { case positional // "1:10; may fall back to abbreviated units in some cases, e.g. 3d" case abbreviated // "1h 10m" case short // "1hr, 10min" @@ -20,7 +20,7 @@ open class DateComponentsFormatter : Formatter { case brief // "1hr 10min" } - public struct ZeroFormattingBehavior : OptionSet { + public struct ZeroFormattingBehavior : OptionSet, Sendable { public let rawValue : UInt public init(rawValue: UInt) { self.rawValue = rawValue } diff --git a/Sources/Foundation/DateFormatter.swift b/Sources/Foundation/DateFormatter.swift index dee69a03dd..4889319f5a 100644 --- a/Sources/Foundation/DateFormatter.swift +++ b/Sources/Foundation/DateFormatter.swift @@ -9,24 +9,28 @@ @_implementationOnly import CoreFoundation @_spi(SwiftCorelibsFoundation) import FoundationEssentials +internal import Synchronization -open class DateFormatter : Formatter { +open class DateFormatter : Formatter, @unchecked Sendable { typealias CFType = CFDateFormatter - private final var __cfObject: CFType? + private let _formatter: Mutex = .init(nil) + private final var _cfObject: CFType { - guard let obj = __cfObject else { - let dateStyle = CFDateFormatterStyle(rawValue: CFIndex(self.dateStyle.rawValue))! - let timeStyle = CFDateFormatterStyle(rawValue: CFIndex(self.timeStyle.rawValue))! - - let obj = CFDateFormatterCreate(kCFAllocatorSystemDefault, locale._cfObject, dateStyle, timeStyle)! - _setFormatterAttributes(obj) - if let dateFormat = _dateFormat { - CFDateFormatterSetFormat(obj, dateFormat._cfObject) + _formatter.withLock { cfDateFormatter in + guard let obj = cfDateFormatter else { + let dateStyle = CFDateFormatterStyle(rawValue: CFIndex(self.dateStyle.rawValue))! + let timeStyle = CFDateFormatterStyle(rawValue: CFIndex(self.timeStyle.rawValue))! + + let obj = CFDateFormatterCreate(kCFAllocatorSystemDefault, locale._cfObject, dateStyle, timeStyle)! + _setFormatterAttributes(obj) + if let dateFormat = _dateFormat { + CFDateFormatterSetFormat(obj, dateFormat._cfObject) + } + cfDateFormatter = obj + return obj } - __cfObject = obj return obj } - return obj } public override init() { @@ -145,7 +149,9 @@ open class DateFormatter : Formatter { } private func _reset() { - __cfObject = nil + _formatter.withLock { + $0 = nil + } } internal final func _setFormatterAttributes(_ formatter: CFDateFormatter) { @@ -571,7 +577,7 @@ open class DateFormatter : Formatter { } extension DateFormatter { - public enum Style : UInt { + public enum Style : UInt, Sendable { case none case short case medium diff --git a/Sources/Foundation/DateIntervalFormatter.swift b/Sources/Foundation/DateIntervalFormatter.swift index 7a08780b7c..ec14b2d2a4 100644 --- a/Sources/Foundation/DateIntervalFormatter.swift +++ b/Sources/Foundation/DateIntervalFormatter.swift @@ -22,7 +22,7 @@ internal let kCFDateIntervalFormatterBoundaryStyleMinimizeAdjacentMonths = _CFDa extension DateIntervalFormatter { // Keep these in sync with CFDateIntervalFormatterStyle. - public enum Style: UInt { + public enum Style: UInt, Sendable { case none = 0 case short = 1 case medium = 2 @@ -82,11 +82,10 @@ internal extension _CFDateIntervalFormatterBoundaryStyle { // DateIntervalFormatter is used to format the range between two NSDates in a locale-sensitive way. // DateIntervalFormatter returns nil and NO for all methods in Formatter. -open class DateIntervalFormatter: Formatter { - private var _core: AnyObject +open class DateIntervalFormatter: Formatter, @unchecked Sendable { + private let _core: AnyObject private final var core: CFDateIntervalFormatter { get { unsafeBitCast(_core, to: CFDateIntervalFormatter.self) } - set { _core = newValue } } public override init() { diff --git a/Sources/Foundation/DispatchData+DataProtocol.swift b/Sources/Foundation/DispatchData+DataProtocol.swift index e5026e833a..001b202baa 100644 --- a/Sources/Foundation/DispatchData+DataProtocol.swift +++ b/Sources/Foundation/DispatchData+DataProtocol.swift @@ -13,6 +13,9 @@ #if canImport(Dispatch) import Dispatch +@available(*, unavailable) +extension DispatchData.Region : Sendable { } + extension DispatchData : DataProtocol { public typealias Regions = [Region] diff --git a/Sources/Foundation/EnergyFormatter.swift b/Sources/Foundation/EnergyFormatter.swift index 8b82765343..64b09e30cb 100644 --- a/Sources/Foundation/EnergyFormatter.swift +++ b/Sources/Foundation/EnergyFormatter.swift @@ -8,7 +8,7 @@ // extension EnergyFormatter { - public enum Unit: Int { + public enum Unit: Int, Sendable { case joule = 11 case kilojoule = 14 @@ -60,6 +60,9 @@ extension EnergyFormatter { } } +@available(*, unavailable) +extension EnergyFormatter : Sendable { } + open class EnergyFormatter: Formatter { public override init() { diff --git a/Sources/Foundation/FileHandle.swift b/Sources/Foundation/FileHandle.swift index 72ab09a3f6..f09a382cfd 100644 --- a/Sources/Foundation/FileHandle.swift +++ b/Sources/Foundation/FileHandle.swift @@ -57,7 +57,7 @@ extension NSError { /* On Darwin, FileHandle conforms to NSSecureCoding for use with NSXPCConnection and related facilities only. On swift-corelibs-foundation, it does not conform to that protocol since those facilities are unavailable. */ -open class FileHandle : NSObject { +open class FileHandle : NSObject, @unchecked Sendable { #if os(Windows) public private(set) var _handle: HANDLE @@ -160,8 +160,8 @@ open class FileHandle : NSObject { return source } - private var _readabilityHandler: ((FileHandle) -> Void)? = nil // Guarded by privateAsyncVariablesLock - open var readabilityHandler: ((FileHandle) -> Void)? { + private var _readabilityHandler: (@Sendable (FileHandle) -> Void)? = nil // Guarded by privateAsyncVariablesLock + open var readabilityHandler: (@Sendable (FileHandle) -> Void)? { get { privateAsyncVariablesLock.lock() let handler = _readabilityHandler @@ -188,8 +188,8 @@ open class FileHandle : NSObject { } } - private var _writeabilityHandler: ((FileHandle) -> Void)? = nil // Guarded by privateAsyncVariablesLock - open var writeabilityHandler: ((FileHandle) -> Void)? { + private var _writeabilityHandler: (@Sendable (FileHandle) -> Void)? = nil // Guarded by privateAsyncVariablesLock + open var writeabilityHandler: (@Sendable (FileHandle) -> Void)? { get { privateAsyncVariablesLock.lock() let handler = _writeabilityHandler @@ -690,17 +690,17 @@ open class FileHandle : NSObject { // This matches the effect of API_TO_BE_DEPRECATED in ObjC headers: @available(swift, deprecated: 100000, renamed: "readToEnd()") - open func readDataToEndOfFile() -> Data { + public func readDataToEndOfFile() -> Data { return try! readToEnd() ?? Data() } @available(swift, deprecated: 100000, renamed: "read(upToCount:)") - open func readData(ofLength length: Int) -> Data { + public func readData(ofLength length: Int) -> Data { return try! read(upToCount: length) ?? Data() } @available(swift, deprecated: 100000, renamed: "write(contentsOf:)") - open func write(_ data: Data) { + public func write(_ data: Data) { try! write(contentsOf: data) } @@ -711,27 +711,27 @@ open class FileHandle : NSObject { @available(swift, deprecated: 100000, renamed: "seekToEnd()") @discardableResult - open func seekToEndOfFile() -> UInt64 { + public func seekToEndOfFile() -> UInt64 { return try! seekToEnd() } @available(swift, deprecated: 100000, renamed: "seek(toOffset:)") - open func seek(toFileOffset offset: UInt64) { + public func seek(toFileOffset offset: UInt64) { try! seek(toOffset: offset) } @available(swift, deprecated: 100000, renamed: "truncate(atOffset:)") - open func truncateFile(atOffset offset: UInt64) { + public func truncateFile(atOffset offset: UInt64) { try! truncate(atOffset: offset) } @available(swift, deprecated: 100000, renamed: "synchronize()") - open func synchronizeFile() { + public func synchronizeFile() { try! synchronize() } @available(swift, deprecated: 100000, renamed: "close()") - open func closeFile() { + public func closeFile() { try! self.close() } } @@ -742,7 +742,7 @@ extension FileHandle { return FileHandle(fileDescriptor: STDIN_FILENO, closeOnDealloc: false) }() - open class var standardInput: FileHandle { + public class var standardInput: FileHandle { return _stdinFileHandle } @@ -750,7 +750,7 @@ extension FileHandle { return FileHandle(fileDescriptor: STDOUT_FILENO, closeOnDealloc: false) }() - open class var standardOutput: FileHandle { + public class var standardOutput: FileHandle { return _stdoutFileHandle } @@ -758,12 +758,12 @@ extension FileHandle { return FileHandle(fileDescriptor: STDERR_FILENO, closeOnDealloc: false) }() - open class var standardError: FileHandle { + public class var standardError: FileHandle { return _stderrFileHandle } internal static var _nulldeviceFileHandle: FileHandle = { - class NullDevice: FileHandle { + class NullDevice: FileHandle, @unchecked Sendable { override var availableData: Data { return Data() } @@ -804,7 +804,7 @@ extension FileHandle { #endif }() - open class var nullDevice: FileHandle { + public class var nullDevice: FileHandle { return _nulldeviceFileHandle } @@ -865,11 +865,11 @@ public let NSFileHandleNotificationDataItem: String = "NSFileHandleNotificationD public let NSFileHandleNotificationFileHandleItem: String = "NSFileHandleNotificationFileHandleItem" extension FileHandle { - open func readInBackgroundAndNotify() { + public func readInBackgroundAndNotify() { readInBackgroundAndNotify(forModes: [.default]) } - open func readInBackgroundAndNotify(forModes modes: [RunLoop.Mode]?) { + public func readInBackgroundAndNotify(forModes modes: [RunLoop.Mode]?) { #if !canImport(Dispatch) NSUnsupported() #else @@ -930,18 +930,18 @@ extension FileHandle { #endif // canImport(Dispatch) } - open func readToEndOfFileInBackgroundAndNotify() { + public func readToEndOfFileInBackgroundAndNotify() { readToEndOfFileInBackgroundAndNotify(forModes: [.default]) } - open func readToEndOfFileInBackgroundAndNotify(forModes modes: [RunLoop.Mode]?) { + public func readToEndOfFileInBackgroundAndNotify(forModes modes: [RunLoop.Mode]?) { #if !canImport(Dispatch) || !canImport(Dispatch) NSUnsupported() #else privateAsyncVariablesLock.lock() guard currentBackgroundActivityOwner == nil else { fatalError("No two activities can occur at the same time") } - let token = NSObject() + nonisolated(unsafe) let token = NSObject() currentBackgroundActivityOwner = token privateAsyncVariablesLock.unlock() @@ -954,11 +954,7 @@ extension FileHandle { error = nil } catch let thrown { data = nil - if let thrown = thrown as? NSError { - error = thrown.errnoIfAvailable - } else { - error = nil - } + error = (thrown as NSError).errnoIfAvailable } DispatchQueue.main.async { @@ -983,7 +979,7 @@ extension FileHandle { } @available(Windows, unavailable, message: "A SOCKET cannot be treated as a fd") - open func acceptConnectionInBackgroundAndNotify() { + public func acceptConnectionInBackgroundAndNotify() { #if os(Windows) || !canImport(Dispatch) NSUnsupported() #else @@ -992,7 +988,7 @@ extension FileHandle { } @available(Windows, unavailable, message: "A SOCKET cannot be treated as a fd") - open func acceptConnectionInBackgroundAndNotify(forModes modes: [RunLoop.Mode]?) { + public func acceptConnectionInBackgroundAndNotify(forModes modes: [RunLoop.Mode]?) { #if os(Windows) || !canImport(Dispatch) NSUnsupported() #else @@ -1026,11 +1022,11 @@ extension FileHandle { #endif } - open func waitForDataInBackgroundAndNotify() { + public func waitForDataInBackgroundAndNotify() { waitForDataInBackgroundAndNotify(forModes: [.default]) } - open func waitForDataInBackgroundAndNotify(forModes modes: [RunLoop.Mode]?) { + public func waitForDataInBackgroundAndNotify(forModes modes: [RunLoop.Mode]?) { #if !canImport(Dispatch) NSUnsupported() #else @@ -1055,7 +1051,7 @@ extension FileHandle { } } -open class Pipe: NSObject { +open class Pipe: NSObject, @unchecked Sendable { public let fileHandleForReading: FileHandle public let fileHandleForWriting: FileHandle diff --git a/Sources/Foundation/FileManager.swift b/Sources/Foundation/FileManager.swift index 6e973c35b9..b8ba580701 100644 --- a/Sources/Foundation/FileManager.swift +++ b/Sources/Foundation/FileManager.swift @@ -52,7 +52,7 @@ private var _overriddenDisplayNameLanguages: [String]? = nil extension FileManager { /// Returns an array of URLs that identify the mounted volumes available on the device. - open func mountedVolumeURLs(includingResourceValuesForKeys propertyKeys: [URLResourceKey]?, options: VolumeEnumerationOptions = []) -> [URL]? { + public func mountedVolumeURLs(includingResourceValuesForKeys propertyKeys: [URLResourceKey]?, options: VolumeEnumerationOptions = []) -> [URL]? { return _mountedVolumeURLs(includingResourceValuesForKeys: propertyKeys, options: options) } @@ -65,7 +65,7 @@ extension FileManager { If you wish to only receive the URLs and no other attributes, then pass '0' for 'options' and an empty NSArray ('[NSArray array]') for 'keys'. If you wish to have the property caches of the vended URLs pre-populated with a default set of attributes, then pass '0' for 'options' and 'nil' for 'keys'. */ - open func contentsOfDirectory(at url: URL, includingPropertiesForKeys keys: [URLResourceKey]?, options mask: DirectoryEnumerationOptions = []) throws -> [URL] { + public func contentsOfDirectory(at url: URL, includingPropertiesForKeys keys: [URLResourceKey]?, options mask: DirectoryEnumerationOptions = []) throws -> [URL] { var error : Error? = nil let e = self.enumerator(at: url, includingPropertiesForKeys: keys, options: mask.union(.skipsSubdirectoryDescendants)) { (url, err) -> Bool in error = err @@ -91,7 +91,7 @@ extension FileManager { You may pass only one of the values from the NSSearchPathDomainMask enumeration, and you may not pass NSAllDomainsMask. */ - open func url(for directory: SearchPathDirectory, in domain: SearchPathDomainMask, appropriateFor reference: URL?, create + public func url(for directory: SearchPathDirectory, in domain: SearchPathDomainMask, appropriateFor reference: URL?, create shouldCreate: Bool) throws -> URL { var url: URL @@ -186,7 +186,8 @@ extension FileManager { url = attemptedURL break } catch { - if let error = error as? NSError, error.domain == NSCocoaErrorDomain, error.code == CocoaError.fileWriteFileExists.rawValue { + let error = error as NSError + if error.domain == NSCocoaErrorDomain, error.code == CocoaError.fileWriteFileExists.rawValue { attempt += 1 } else { throw error @@ -204,7 +205,7 @@ extension FileManager { /* Sets 'outRelationship' to NSURLRelationshipContains if the directory at 'directoryURL' directly or indirectly contains the item at 'otherURL', meaning 'directoryURL' is found while enumerating parent URLs starting from 'otherURL'. Sets 'outRelationship' to NSURLRelationshipSame if 'directoryURL' and 'otherURL' locate the same item, meaning they have the same NSURLFileResourceIdentifierKey value. If 'directoryURL' is not a directory, or does not contain 'otherURL' and they do not locate the same file, then sets 'outRelationship' to NSURLRelationshipOther. If an error occurs, returns NO and sets 'error'. */ - open func getRelationship(_ outRelationship: UnsafeMutablePointer, ofDirectoryAt directoryURL: URL, toItemAt otherURL: URL) throws { + public func getRelationship(_ outRelationship: UnsafeMutablePointer, ofDirectoryAt directoryURL: URL, toItemAt otherURL: URL) throws { let from = try _canonicalizedPath(toFileAtPath: directoryURL.path) let to = try _canonicalizedPath(toFileAtPath: otherURL.path) @@ -224,7 +225,7 @@ extension FileManager { /* Similar to -[NSFileManager getRelationship:ofDirectoryAtURL:toItemAtURL:error:], except that the directory is instead defined by an NSSearchPathDirectory and NSSearchPathDomainMask. Pass 0 for domainMask to instruct the method to automatically choose the domain appropriate for 'url'. For example, to discover if a file is contained by a Trash directory, call [fileManager getRelationship:&result ofDirectory:NSTrashDirectory inDomain:0 toItemAtURL:url error:&error]. */ - open func getRelationship(_ outRelationship: UnsafeMutablePointer, of directory: SearchPathDirectory, in domainMask: SearchPathDomainMask, toItemAt url: URL) throws { + public func getRelationship(_ outRelationship: UnsafeMutablePointer, of directory: SearchPathDirectory, in domainMask: SearchPathDomainMask, toItemAt url: URL) throws { let actualMask: SearchPathDomainMask if domainMask.isEmpty { @@ -359,7 +360,7 @@ extension FileManager { return try _recursiveDestinationOfSymbolicLink(atPath: path) } - open func fileExists(atPath path: String, isDirectory: UnsafeMutablePointer?) -> Bool { + public func fileExists(atPath path: String, isDirectory: UnsafeMutablePointer?) -> Bool { var isDir: Bool = false defer { if let isDirectory { @@ -383,7 +384,7 @@ extension FileManager { /* displayNameAtPath: returns an NSString suitable for presentation to the user. For directories which have localization information, this will return the appropriate localized string. This string is not suitable for passing to anything that must interact with the filesystem. */ - open func displayName(atPath path: String) -> String { + public func displayName(atPath path: String) -> String { let url = URL(fileURLWithPath: path) let name = url.lastPathComponent @@ -423,7 +424,7 @@ extension FileManager { /* componentsToDisplayForPath: returns an NSArray of display names for the path provided. Localization will occur as in displayNameAtPath: above. This array cannot and should not be reassembled into an usable filesystem path for any kind of access. */ - open func componentsToDisplay(forPath path: String) -> [String]? { + public func componentsToDisplay(forPath path: String) -> [String]? { var url = URL(fileURLWithPath: path) var count = url.pathComponents.count @@ -439,7 +440,7 @@ extension FileManager { /* enumeratorAtPath: returns an NSDirectoryEnumerator rooted at the provided path. If the enumerator cannot be created, this returns NULL. Because NSDirectoryEnumerator is a subclass of NSEnumerator, the returned object can be used in the for...in construct. */ - open func enumerator(atPath path: String) -> DirectoryEnumerator? { + public func enumerator(atPath path: String) -> DirectoryEnumerator? { return NSPathDirectoryEnumerator(path: path) } @@ -448,19 +449,19 @@ extension FileManager { If you wish to only receive the URLs and no other attributes, then pass '0' for 'options' and an empty NSArray ('[NSArray array]') for 'keys'. If you wish to have the property caches of the vended URLs pre-populated with a default set of attributes, then pass '0' for 'options' and 'nil' for 'keys'. */ // Note: Because the error handler is an optional block, the compiler treats it as @escaping by default. If that behavior changes, the @escaping will need to be added back. - open func enumerator(at url: URL, includingPropertiesForKeys keys: [URLResourceKey]?, options mask: DirectoryEnumerationOptions = [], errorHandler handler: (/* @escaping */ (URL, Error) -> Bool)? = nil) -> DirectoryEnumerator? { + public func enumerator(at url: URL, includingPropertiesForKeys keys: [URLResourceKey]?, options mask: DirectoryEnumerationOptions = [], errorHandler handler: (/* @escaping */ (URL, Error) -> Bool)? = nil) -> DirectoryEnumerator? { return NSURLDirectoryEnumerator(url: url, options: mask, errorHandler: handler) } /* subpathsAtPath: returns an NSArray of all contents and subpaths recursively from the provided path. This may be very expensive to compute for deep filesystem hierarchies, and should probably be avoided. */ - open func subpaths(atPath path: String) -> [String]? { + public func subpaths(atPath path: String) -> [String]? { return try? subpathsOfDirectory(atPath: path) } /* fileSystemRepresentationWithPath: returns an array of characters suitable for passing to lower-level POSIX style APIs. The string is provided in the representation most appropriate for the filesystem in question. */ - open func fileSystemRepresentation(withPath path: String) -> UnsafePointer { + public func fileSystemRepresentation(withPath path: String) -> UnsafePointer { precondition(path != "", "Empty path argument") return self.withFileSystemRepresentation(for: path) { ptr in guard let ptr else { @@ -475,7 +476,8 @@ extension FileManager { endIdx = endIdx.advanced(by: 1) let size = ptr.distance(to: endIdx) let buffer = UnsafeMutableBufferPointer.allocate(capacity: size) - buffer.initialize(fromContentsOf: UnsafeBufferPointer(start: ptr, count: size)) + // TODO: This whole function should be obsoleted as it returns a value that the caller must free. This works on Darwin, but is too easy to misuse without the presence of an autoreleasepool on other platforms. + _ = buffer.initialize(fromContentsOf: UnsafeBufferPointer(start: ptr, count: size)) return UnsafePointer(buffer.baseAddress!) } } @@ -521,7 +523,7 @@ extension FileManager { /// - Note: Since this API is under consideration it may be either removed or revised in the near future #if os(Windows) @available(Windows, deprecated, message: "Not yet implemented") - open func replaceItem(at originalItemURL: URL, withItemAt newItemURL: URL, backupItemName: String?, options: ItemReplacementOptions = []) throws -> URL? { + public func replaceItem(at originalItemURL: URL, withItemAt newItemURL: URL, backupItemName: String?, options: ItemReplacementOptions = []) throws -> URL? { NSUnimplemented() } @@ -531,7 +533,7 @@ extension FileManager { } #else - open func replaceItem(at originalItemURL: URL, withItemAt newItemURL: URL, backupItemName: String?, options: ItemReplacementOptions = []) throws -> URL? { + public func replaceItem(at originalItemURL: URL, withItemAt newItemURL: URL, backupItemName: String?, options: ItemReplacementOptions = []) throws -> URL? { return try _replaceItem(at: originalItemURL, withItemAt: newItemURL, backupItemName: backupItemName, options: options) } @@ -541,7 +543,7 @@ extension FileManager { #endif @available(*, unavailable, message: "Returning an object through an autoreleased pointer is not supported in swift-corelibs-foundation. Use replaceItem(at:withItemAt:backupItemName:options:) instead.", renamed: "replaceItem(at:withItemAt:backupItemName:options:)") - open func replaceItem(at originalItemURL: URL, withItemAt newItemURL: URL, backupItemName: String?, options: FileManager.ItemReplacementOptions = [], resultingItemURL resultingURL: UnsafeMutablePointer?) throws { + public func replaceItem(at originalItemURL: URL, withItemAt newItemURL: URL, backupItemName: String?, options: FileManager.ItemReplacementOptions = [], resultingItemURL resultingURL: UnsafeMutablePointer?) throws { NSUnsupported() } @@ -556,7 +558,7 @@ extension FileManager { } extension FileManager { - public struct VolumeEnumerationOptions : OptionSet { + public struct VolumeEnumerationOptions : OptionSet, Sendable { public let rawValue : UInt public init(rawValue: UInt) { self.rawValue = rawValue } @@ -606,6 +608,12 @@ extension FileAttributeKey { internal static let _accessDate = FileAttributeKey(rawValue: "org.swift.Foundation.FileAttributeKey._accessDate") } +@available(*, unavailable) +extension FileManager.DirectoryEnumerator : Sendable { } + +@available(*, unavailable) +extension FileManager.NSPathDirectoryEnumerator : Sendable { } + extension FileManager { open class DirectoryEnumerator : NSEnumerator { diff --git a/Sources/Foundation/Formatter.swift b/Sources/Foundation/Formatter.swift index 4da8096eee..2387514cdf 100644 --- a/Sources/Foundation/Formatter.swift +++ b/Sources/Foundation/Formatter.swift @@ -8,7 +8,7 @@ // extension Formatter { - public enum Context : Int { + public enum Context : Int, Sendable { // The capitalization context to be used is unknown (this is the default value). case unknown @@ -35,7 +35,7 @@ extension Formatter { * Long is "3 pounds"; medium is "3 lb"; short is "3#"; */ - public enum UnitStyle : Int { + public enum UnitStyle : Int, Sendable { case short case medium @@ -43,6 +43,9 @@ extension Formatter { } } +@available(*, unavailable) +extension Formatter : Sendable { } + open class Formatter : NSObject, NSCopying, NSCoding { public override init() { diff --git a/Sources/Foundation/Host.swift b/Sources/Foundation/Host.swift index b5205ebb76..9d9f0ebf35 100644 --- a/Sources/Foundation/Host.swift +++ b/Sources/Foundation/Host.swift @@ -55,6 +55,9 @@ import WinSDK } #endif +@available(*, unavailable) +extension Host : Sendable { } + open class Host: NSObject { enum ResolveType { case name diff --git a/Sources/Foundation/ISO8601DateFormatter.swift b/Sources/Foundation/ISO8601DateFormatter.swift index 9949b787d0..c8c7bdaf52 100644 --- a/Sources/Foundation/ISO8601DateFormatter.swift +++ b/Sources/Foundation/ISO8601DateFormatter.swift @@ -11,7 +11,7 @@ extension ISO8601DateFormatter { - public struct Options : OptionSet { + public struct Options : OptionSet, Sendable { public private(set) var rawValue: UInt @@ -48,6 +48,9 @@ extension ISO8601DateFormatter { } +@available(*, unavailable) +extension ISO8601DateFormatter : Sendable { } + open class ISO8601DateFormatter : Formatter, NSSecureCoding { typealias CFType = CFDateFormatter diff --git a/Sources/Foundation/IndexSet.swift b/Sources/Foundation/IndexSet.swift index 5a33576c1c..360fcc094f 100644 --- a/Sources/Foundation/IndexSet.swift +++ b/Sources/Foundation/IndexSet.swift @@ -49,14 +49,14 @@ extension IndexSet.RangeView { /// Manages a `Set` of integer values, which are commonly used as an index type in Cocoa API. /// /// The range of valid integer values is 0.. fileprivate var rangeIndex: Int @@ -833,6 +833,9 @@ extension NSIndexSet : _HasCustomAnyHashableRepresentation { // MARK: Protocol +@available(*, unavailable) +extension _MutablePair : Sendable { } + // TODO: This protocol should be replaced with a native Swift object like the other Foundation bridged types. However, NSIndexSet does not have an abstract zero-storage base class like NSCharacterSet, NSData, and NSAttributedString. Therefore the same trick of laying it out with Swift ref counting does not work.and /// Holds either the immutable or mutable version of a Foundation type. /// @@ -847,7 +850,7 @@ internal enum _MutablePair { /// /// a.k.a. Box @usableFromInline -internal final class _MutablePairHandle +internal final class _MutablePairHandle : @unchecked Sendable where ImmutableType : NSMutableCopying, MutableType : NSMutableCopying { @usableFromInline internal var _pointer: _MutablePair diff --git a/Sources/Foundation/JSONSerialization.swift b/Sources/Foundation/JSONSerialization.swift index e5f9eb0252..34c24d2490 100644 --- a/Sources/Foundation/JSONSerialization.swift +++ b/Sources/Foundation/JSONSerialization.swift @@ -13,7 +13,7 @@ @_implementationOnly import CoreFoundation extension JSONSerialization { - public struct ReadingOptions : OptionSet { + public struct ReadingOptions : OptionSet, Sendable { public let rawValue: UInt public init(rawValue: UInt) { self.rawValue = rawValue } @@ -25,7 +25,7 @@ extension JSONSerialization { public static let allowFragments = ReadingOptions(rawValue: 1 << 2) } - public struct WritingOptions : OptionSet { + public struct WritingOptions : OptionSet, Sendable { public let rawValue: UInt public init(rawValue: UInt) { self.rawValue = rawValue } @@ -53,6 +53,9 @@ extension JSONSerialization { - `NSNumber`s are not NaN or infinity */ +@available(*, unavailable) +extension JSONSerialization : Sendable { } + open class JSONSerialization : NSObject { /* Determines whether the given object can be converted to JSON. diff --git a/Sources/Foundation/LengthFormatter.swift b/Sources/Foundation/LengthFormatter.swift index 51527f62fe..490bd60953 100644 --- a/Sources/Foundation/LengthFormatter.swift +++ b/Sources/Foundation/LengthFormatter.swift @@ -8,7 +8,7 @@ // extension LengthFormatter { - public enum Unit: Int { + public enum Unit: Int, Sendable { case millimeter = 8 case centimeter = 9 case meter = 11 @@ -20,6 +20,9 @@ extension LengthFormatter { } } +@available(*, unavailable) +extension LengthFormatter : Sendable { } + open class LengthFormatter : Formatter { public override init() { diff --git a/Sources/Foundation/MassFormatter.swift b/Sources/Foundation/MassFormatter.swift index f1886e7832..7cd1753b9a 100644 --- a/Sources/Foundation/MassFormatter.swift +++ b/Sources/Foundation/MassFormatter.swift @@ -9,7 +9,7 @@ extension MassFormatter { - public enum Unit : Int { + public enum Unit : Int, Sendable { case gram case kilogram case ounce @@ -17,6 +17,9 @@ extension MassFormatter { case stone } } + +@available(*, unavailable) +extension MassFormatter : Sendable { } open class MassFormatter : Formatter { diff --git a/Sources/Foundation/Measurement.swift b/Sources/Foundation/Measurement.swift index 028048675f..4865f2cf99 100644 --- a/Sources/Foundation/Measurement.swift +++ b/Sources/Foundation/Measurement.swift @@ -17,6 +17,8 @@ import _SwiftCoreFoundationOverlayShims #endif +extension Measurement : Sendable where UnitType : Sendable { } + /// A `Measurement` is a model type that holds a `Double` value associated with a `Unit`. /// /// Measurements support a large set of operators, including `+`, `-`, `*`, `/`, and a full set of comparison operators. diff --git a/Sources/Foundation/MeasurementFormatter.swift b/Sources/Foundation/MeasurementFormatter.swift index 26d5d32708..ae177bcfc9 100644 --- a/Sources/Foundation/MeasurementFormatter.swift +++ b/Sources/Foundation/MeasurementFormatter.swift @@ -7,9 +7,12 @@ // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // +@available(*, unavailable) +extension MeasurementFormatter : Sendable { } + @available(*, unavailable, message: "Not supported in swift-corelibs-foundation") open class MeasurementFormatter : Formatter, NSSecureCoding { - public struct UnitOptions : OptionSet { + public struct UnitOptions : OptionSet, Sendable { public private(set) var rawValue: UInt public init(rawValue: UInt) { self.rawValue = rawValue } diff --git a/Sources/Foundation/Morphology.swift b/Sources/Foundation/Morphology.swift index d07a4457b7..bbc5608a04 100644 --- a/Sources/Foundation/Morphology.swift +++ b/Sources/Foundation/Morphology.swift @@ -11,11 +11,11 @@ //===----------------------------------------------------------------------===// @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *) -public struct Morphology { +public struct Morphology : Sendable { public init() {} @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *) - public enum GrammaticalGender: Int, Hashable { + public enum GrammaticalGender: Int, Hashable, Sendable { case feminine = 1 case masculine case neuter @@ -23,7 +23,7 @@ public struct Morphology { public var grammaticalGender: GrammaticalGender? @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *) - public enum PartOfSpeech: Int, Hashable { + public enum PartOfSpeech: Int, Hashable, Sendable { case determiner = 1 case pronoun case letter @@ -42,7 +42,7 @@ public struct Morphology { public var partOfSpeech: PartOfSpeech? @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *) - public enum GrammaticalNumber: Int, Hashable { + public enum GrammaticalNumber: Int, Hashable, Sendable { case singular = 1 case zero case plural @@ -56,7 +56,7 @@ public struct Morphology { } @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *) -public enum InflectionRule { +public enum InflectionRule : Sendable { case automatic case explicit(Morphology) @@ -294,7 +294,7 @@ extension Morphology { } @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *) - public struct CustomPronoun { + public struct CustomPronoun : Sendable { public init() {} public static func isSupported(forLanguage language: String) -> Bool { diff --git a/Sources/Foundation/NSArray.swift b/Sources/Foundation/NSArray.swift index 096c090b51..2f2089a8b5 100644 --- a/Sources/Foundation/NSArray.swift +++ b/Sources/Foundation/NSArray.swift @@ -9,6 +9,12 @@ @_implementationOnly import CoreFoundation +@available(*, unavailable) +extension NSArray : Sendable { } + +@available(*, unavailable) +extension NSArray.Iterator : Sendable { } + open class NSArray : NSObject, NSCopying, NSMutableCopying, NSSecureCoding, NSCoding, ExpressibleByArrayLiteral { private let _cfinfo = _CFInfo(typeID: CFArrayGetTypeID()) internal var _storage = [AnyObject]() @@ -737,7 +743,7 @@ extension Array : _NSBridgeable { internal var _cfObject: CFArray { return _nsObject._cfObject } } -public struct NSBinarySearchingOptions : OptionSet { +public struct NSBinarySearchingOptions : OptionSet, Sendable { public let rawValue : UInt public init(rawValue: UInt) { self.rawValue = rawValue } @@ -746,6 +752,9 @@ public struct NSBinarySearchingOptions : OptionSet { public static let insertionIndex = NSBinarySearchingOptions(rawValue: 1 << 10) } +@available(*, unavailable) +extension NSMutableArray : Sendable { } + open class NSMutableArray : NSArray { open func add(_ anObject: Any) { @@ -961,24 +970,26 @@ open class NSMutableArray : NSArray { open func sort(using sortDescriptors: [NSSortDescriptor]) { var descriptors = sortDescriptors._nsObject - CFArraySortValues(_cfMutableObject, CFRangeMake(0, count), { (lhsPointer, rhsPointer, context) -> CFComparisonResult in - let descriptors = context!.assumingMemoryBound(to: NSArray.self).pointee._swiftObject - - for item in descriptors { - let descriptor = item as! NSSortDescriptor - let result = - descriptor.compare(__SwiftValue.fetch(Unmanaged.fromOpaque(lhsPointer!).takeUnretainedValue())!, - to: __SwiftValue.fetch(Unmanaged.fromOpaque(rhsPointer!).takeUnretainedValue())!) + withUnsafeMutablePointer(to: &descriptors) { descriptors in + CFArraySortValues(_cfMutableObject, CFRangeMake(0, count), { (lhsPointer, rhsPointer, context) -> CFComparisonResult in + let descriptors = context!.assumingMemoryBound(to: NSArray.self).pointee._swiftObject - if result == .orderedAscending { - return kCFCompareLessThan - } else if result == .orderedDescending { - return kCFCompareGreaterThan + for item in descriptors { + let descriptor = item as! NSSortDescriptor + let result = + descriptor.compare(__SwiftValue.fetch(Unmanaged.fromOpaque(lhsPointer!).takeUnretainedValue())!, + to: __SwiftValue.fetch(Unmanaged.fromOpaque(rhsPointer!).takeUnretainedValue())!) + + if result == .orderedAscending { + return kCFCompareLessThan + } else if result == .orderedDescending { + return kCFCompareGreaterThan + } } - } - - return kCFCompareEqualTo - }, &descriptors) + + return kCFCompareEqualTo + }, descriptors) + } } } diff --git a/Sources/Foundation/NSAttributedString.swift b/Sources/Foundation/NSAttributedString.swift index 4f7523f179..9202708152 100644 --- a/Sources/Foundation/NSAttributedString.swift +++ b/Sources/Foundation/NSAttributedString.swift @@ -10,7 +10,7 @@ @_implementationOnly import CoreFoundation extension NSAttributedString { - public struct Key: RawRepresentable, Equatable, Hashable { + public struct Key: RawRepresentable, Equatable, Hashable, Sendable { public let rawValue: String public init(_ rawValue: String) { @@ -46,6 +46,9 @@ extension NSAttributedString.Key: _ObjectiveCBridgeable { @available(*, unavailable, renamed: "NSAttributedString.Key") public typealias NSAttributedStringKey = NSAttributedString.Key +@available(*, unavailable) +extension NSAttributedString : Sendable { } + open class NSAttributedString: NSObject, NSCopying, NSMutableCopying, NSSecureCoding { private let _cfinfo = _CFInfo(typeID: CFAttributedStringGetTypeID()) @@ -421,7 +424,7 @@ extension NSAttributedString { extension NSAttributedString { - public struct EnumerationOptions: OptionSet { + public struct EnumerationOptions: OptionSet, Sendable { public let rawValue: UInt public init(rawValue: UInt) { self.rawValue = rawValue @@ -432,6 +435,8 @@ extension NSAttributedString { } +@available(*, unavailable) +extension NSMutableAttributedString : Sendable { } open class NSMutableAttributedString : NSAttributedString { diff --git a/Sources/Foundation/NSCFBoolean.swift b/Sources/Foundation/NSCFBoolean.swift index adb48ac535..c54cca02a2 100644 --- a/Sources/Foundation/NSCFBoolean.swift +++ b/Sources/Foundation/NSCFBoolean.swift @@ -10,7 +10,7 @@ @_implementationOnly import CoreFoundation -internal class __NSCFBoolean : NSNumber { +internal class __NSCFBoolean : NSNumber, @unchecked Sendable { override var hash: Int { return Int(bitPattern: CFHash(unsafeBitCast(self, to: CFBoolean.self))) } diff --git a/Sources/Foundation/NSCFString.swift b/Sources/Foundation/NSCFString.swift index 40ee579f42..69468b3145 100644 --- a/Sources/Foundation/NSCFString.swift +++ b/Sources/Foundation/NSCFString.swift @@ -10,6 +10,9 @@ @_implementationOnly import CoreFoundation +@available(*, unavailable) +extension _NSCFString : Sendable { } + @usableFromInline internal class _NSCFString : NSMutableString { required init(characters: UnsafePointer, length: Int) { @@ -58,6 +61,9 @@ internal class _NSCFString : NSMutableString { } } +@available(*, unavailable) +extension _NSCFConstantString : Sendable { } + @usableFromInline internal final class _NSCFConstantString : _NSCFString { internal var _ptr : UnsafePointer { diff --git a/Sources/Foundation/NSCache.swift b/Sources/Foundation/NSCache.swift index cf616bc770..fda8d9ecd7 100644 --- a/Sources/Foundation/NSCache.swift +++ b/Sources/Foundation/NSCache.swift @@ -53,6 +53,9 @@ fileprivate class NSCacheKey: NSObject { } } +@available(*, unavailable) +extension NSCache : Sendable { } + open class NSCache : NSObject { private var _entries = Dictionary>() diff --git a/Sources/Foundation/NSCalendar.swift b/Sources/Foundation/NSCalendar.swift index 25e1a7054a..80835bfd64 100644 --- a/Sources/Foundation/NSCalendar.swift +++ b/Sources/Foundation/NSCalendar.swift @@ -31,7 +31,7 @@ internal func _CFCalendarUnitRawValue(_ unit: CFCalendarUnit) -> CFOptionFlags { extension NSCalendar { // This is not the same as Calendar.Identifier due to a spelling difference in ISO8601 - public struct Identifier : RawRepresentable, Equatable, Hashable, Comparable { + public struct Identifier : RawRepresentable, Equatable, Hashable, Comparable, Sendable { public private(set) var rawValue: String public init(_ rawValue: String) { self.rawValue = rawValue @@ -125,7 +125,7 @@ extension NSCalendar { } - public struct Unit: OptionSet { + public struct Unit: OptionSet, Sendable { public let rawValue: UInt public init(rawValue: UInt) { self.rawValue = rawValue @@ -198,7 +198,7 @@ extension NSCalendar { } - public struct Options : OptionSet { + public struct Options : OptionSet, Sendable { public let rawValue : UInt public init(rawValue: UInt) { self.rawValue = rawValue } @@ -219,6 +219,9 @@ extension NSCalendar.Identifier { } } +@available(*, unavailable) +extension NSCalendar : Sendable { } + open class NSCalendar : NSObject, NSCopying, NSSecureCoding { var _calendar: Calendar diff --git a/Sources/Foundation/NSCharacterSet.swift b/Sources/Foundation/NSCharacterSet.swift index b1491b340f..c36ea39eaa 100644 --- a/Sources/Foundation/NSCharacterSet.swift +++ b/Sources/Foundation/NSCharacterSet.swift @@ -60,6 +60,9 @@ fileprivate extension String { static let characterSetNewIsInvertedKey = "NSIsInverted2" } +@available(*, unavailable) +extension NSCharacterSet : Sendable { } + open class NSCharacterSet : NSObject, NSCopying, NSMutableCopying, NSSecureCoding { typealias CFType = CFCharacterSet private var _base = _CFInfo(typeID: CFCharacterSetGetTypeID()) @@ -391,6 +394,9 @@ open class NSCharacterSet : NSObject, NSCopying, NSMutableCopying, NSSecureCodin } } +@available(*, unavailable) +extension NSMutableCharacterSet : Sendable { } + open class NSMutableCharacterSet : NSCharacterSet { open func addCharacters(in aRange: NSRange) { diff --git a/Sources/Foundation/NSCoder.swift b/Sources/Foundation/NSCoder.swift index 3dd5d87641..24c27334fc 100644 --- a/Sources/Foundation/NSCoder.swift +++ b/Sources/Foundation/NSCoder.swift @@ -13,7 +13,7 @@ extension NSCoder { /// failures (e.g. corrupt data) for non-TopLevel decodes. Darwin platfrom /// supports exceptions here, and there may be other approaches supported /// in the future, so its included for completeness. - public enum DecodingFailurePolicy : Int { + public enum DecodingFailurePolicy : Int, Sendable { case raiseException case setErrorAndReturn } @@ -80,6 +80,9 @@ public protocol NSSecureCoding : NSCoding { static var supportsSecureCoding: Bool { get } } +@available(*, unavailable) +extension NSCoder : Sendable { } + /// The `NSCoder` abstract class declares the interface used by concrete /// subclasses to transfer objects and other values between memory and some /// other format. This capability provides the basis for archiving (where @@ -732,7 +735,7 @@ open class NSCoder : NSObject { } open func failWithError(_ error: Error) { - if let debugDescription = (error as? NSError)?.userInfo[NSDebugDescriptionErrorKey] { + if let debugDescription = (error as NSError).userInfo[NSDebugDescriptionErrorKey] { NSLog("*** NSKeyedUnarchiver.init: \(debugDescription)") } else { NSLog("*** NSKeyedUnarchiver.init: decoding error") diff --git a/Sources/Foundation/NSComparisonPredicate.swift b/Sources/Foundation/NSComparisonPredicate.swift index a7f5f00eb5..a0d1063a55 100644 --- a/Sources/Foundation/NSComparisonPredicate.swift +++ b/Sources/Foundation/NSComparisonPredicate.swift @@ -7,6 +7,9 @@ // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // +@available(*, unavailable) +extension NSComparisonPredicate : Sendable { } + // Comparison predicates are predicates which do some form of comparison between the results of two expressions and return a BOOL. They take an operator, a left expression, and a right expression, and return the result of invoking the operator with the results of evaluating the expressions. @available(*, deprecated, message: "NSExpression and classes that rely on its functionality are unsupported in swift-corelibs-foundation: NSComparisonPredicate is unavailable.") open class NSComparisonPredicate : NSPredicate { @@ -29,7 +32,7 @@ open class NSComparisonPredicate : NSPredicate { @available(*, unavailable, message: "NSComparisonPredicate is unavailable.") open var options: Options { NSUnsupported() } - public struct Options : OptionSet { + public struct Options : OptionSet, Sendable { public let rawValue : UInt public init(rawValue: UInt) { self.rawValue = rawValue } @@ -39,14 +42,14 @@ open class NSComparisonPredicate : NSPredicate { } // Describes how the operator is modified: can be direct, ALL, or ANY - public enum Modifier : UInt { + public enum Modifier : UInt, Sendable { case direct // Do a direct comparison case all // ALL toMany.x = y case any // ANY toMany.x = y } // Type basic set of operators defined. Most are obvious - public enum Operator : UInt { + public enum Operator : UInt, Sendable { case lessThan // compare: returns NSOrderedAscending case lessThanOrEqualTo // compare: returns NSOrderedAscending || NSOrderedSame case greaterThan // compare: returns NSOrderedDescending diff --git a/Sources/Foundation/NSCompoundPredicate.swift b/Sources/Foundation/NSCompoundPredicate.swift index 8f8ad79497..0e30e5fe0b 100644 --- a/Sources/Foundation/NSCompoundPredicate.swift +++ b/Sources/Foundation/NSCompoundPredicate.swift @@ -11,13 +11,16 @@ // Compound predicates are predicates which act on the results of evaluating other operators. We provide the basic boolean operators: AND, OR, and NOT. extension NSCompoundPredicate { - public enum LogicalType : UInt { + public enum LogicalType : UInt, Sendable { case not case and case or } } +@available(*, unavailable) +extension NSCompoundPredicate : Sendable { } + open class NSCompoundPredicate : NSPredicate { public init(type: LogicalType, subpredicates: [NSPredicate]) { if type == .not && subpredicates.isEmpty { diff --git a/Sources/Foundation/NSConcreteValue.swift b/Sources/Foundation/NSConcreteValue.swift index ea796c8825..cc950bde6d 100644 --- a/Sources/Foundation/NSConcreteValue.swift +++ b/Sources/Foundation/NSConcreteValue.swift @@ -9,7 +9,7 @@ @_implementationOnly import CoreFoundation -internal class NSConcreteValue : NSValue { +internal class NSConcreteValue : NSValue, @unchecked Sendable { struct TypeInfo : Equatable { let size : Int @@ -122,7 +122,7 @@ internal class NSConcreteValue : NSValue { let typep = type._swiftObject // FIXME: This will result in reading garbage memory. - self.init(bytes: [], objCType: typep) + self.init(bytes: Array(), objCType: typep) aDecoder.decodeValue(ofObjCType: typep, at: self.value) } diff --git a/Sources/Foundation/NSData.swift b/Sources/Foundation/NSData.swift index 21b1230d35..f5c25697fc 100644 --- a/Sources/Foundation/NSData.swift +++ b/Sources/Foundation/NSData.swift @@ -31,6 +31,12 @@ private let __kCFBytesInline: CFOptionFlags = 2 private let __kCFUseAllocator: CFOptionFlags = 3 private let __kCFDontDeallocate: CFOptionFlags = 4 +@available(*, unavailable) +extension NSData : Sendable { } + +@available(*, unavailable) +extension NSData.NSDataReadResult : Sendable { } + open class NSData : NSObject, NSCopying, NSMutableCopying, NSSecureCoding { typealias CFType = CFData @@ -928,6 +934,10 @@ extension CFData : _NSBridgeable, _SwiftBridgeable { } // MARK: - + +@available(*, unavailable) +extension NSMutableData : Sendable { } + open class NSMutableData : NSData { internal final var _cfMutableObject: CFMutableData { return unsafeBitCast(self, to: CFMutableData.self) } diff --git a/Sources/Foundation/NSDate.swift b/Sources/Foundation/NSDate.swift index 677d883c7e..ac7bd1bc9c 100644 --- a/Sources/Foundation/NSDate.swift +++ b/Sources/Foundation/NSDate.swift @@ -36,7 +36,7 @@ extension timeval { } #endif -open class NSDate : NSObject, NSCopying, NSSecureCoding, NSCoding { +open class NSDate : NSObject, NSCopying, NSSecureCoding, NSCoding, @unchecked Sendable { typealias CFType = CFDate open override var hash: Int { @@ -159,23 +159,23 @@ open class NSDate : NSObject, NSCopying, NSSecureCoding, NSCoding { extension NSDate { - open func timeIntervalSince(_ anotherDate: Date) -> TimeInterval { + public func timeIntervalSince(_ anotherDate: Date) -> TimeInterval { return self.timeIntervalSinceReferenceDate - anotherDate.timeIntervalSinceReferenceDate } - open var timeIntervalSinceNow: TimeInterval { + public var timeIntervalSinceNow: TimeInterval { return timeIntervalSince(Date()) } - open var timeIntervalSince1970: TimeInterval { + public var timeIntervalSince1970: TimeInterval { return timeIntervalSinceReferenceDate + NSTimeIntervalSince1970 } - open func addingTimeInterval(_ ti: TimeInterval) -> Date { + public func addingTimeInterval(_ ti: TimeInterval) -> Date { return Date(timeIntervalSinceReferenceDate:_timeIntervalSinceReferenceDate + ti) } - open func earlierDate(_ anotherDate: Date) -> Date { + public func earlierDate(_ anotherDate: Date) -> Date { if self.timeIntervalSinceReferenceDate < anotherDate.timeIntervalSinceReferenceDate { return Date(timeIntervalSinceReferenceDate: timeIntervalSinceReferenceDate) } else { @@ -183,7 +183,7 @@ extension NSDate { } } - open func laterDate(_ anotherDate: Date) -> Date { + public func laterDate(_ anotherDate: Date) -> Date { if self.timeIntervalSinceReferenceDate < anotherDate.timeIntervalSinceReferenceDate { return anotherDate } else { @@ -191,7 +191,7 @@ extension NSDate { } } - open func compare(_ other: Date) -> ComparisonResult { + public func compare(_ other: Date) -> ComparisonResult { let t1 = self.timeIntervalSinceReferenceDate let t2 = other.timeIntervalSinceReferenceDate if t1 < t2 { @@ -203,19 +203,19 @@ extension NSDate { } } - open func isEqual(to otherDate: Date) -> Bool { + public func isEqual(to otherDate: Date) -> Bool { return timeIntervalSinceReferenceDate == otherDate.timeIntervalSinceReferenceDate } } extension NSDate { internal static let _distantFuture = Date(timeIntervalSinceReferenceDate: 63113904000.0) - open class var distantFuture: Date { + public class var distantFuture: Date { return _distantFuture } internal static let _distantPast = Date(timeIntervalSinceReferenceDate: -63113904000.0) - open class var distantPast: Date { + public class var distantPast: Date { return _distantPast } @@ -241,14 +241,14 @@ extension Date : CustomPlaygroundDisplayConvertible { } } -open class NSDateInterval : NSObject, NSCopying, NSSecureCoding { +open class NSDateInterval : NSObject, NSCopying, NSSecureCoding, @unchecked Sendable { /* NSDateInterval represents a closed date interval in the form of [startDate, endDate]. It is possible for the start and end dates to be the same with a duration of 0. NSDateInterval does not support reverse intervals i.e. intervals where the duration is less than 0 and the end date occurs earlier in time than the start date. */ - open private(set) var startDate: Date + public let startDate: Date open var endDate: Date { get { @@ -260,7 +260,7 @@ open class NSDateInterval : NSObject, NSCopying, NSSecureCoding { } } - open private(set) var duration: TimeInterval + public let duration: TimeInterval // This method initializes an NSDateInterval object with start and end dates set to the current date and the duration set to 0. diff --git a/Sources/Foundation/NSDateComponents.swift b/Sources/Foundation/NSDateComponents.swift index 03757bb26e..0ed5793175 100644 --- a/Sources/Foundation/NSDateComponents.swift +++ b/Sources/Foundation/NSDateComponents.swift @@ -31,6 +31,9 @@ public var NSDateComponentUndefined: Int = Int.max +@available(*, unavailable) +extension NSDateComponents : Sendable { } + open class NSDateComponents: NSObject, NSCopying, NSSecureCoding { internal var _components: DateComponents diff --git a/Sources/Foundation/NSDecimalNumber.swift b/Sources/Foundation/NSDecimalNumber.swift index cb3e51dfeb..52b822e8ad 100644 --- a/Sources/Foundation/NSDecimalNumber.swift +++ b/Sources/Foundation/NSDecimalNumber.swift @@ -10,8 +10,8 @@ @_spi(SwiftCorelibsFoundation) import FoundationEssentials /*************** Exceptions ***********/ -public struct NSExceptionName : RawRepresentable, Equatable, Hashable { - public private(set) var rawValue: String +public struct NSExceptionName : RawRepresentable, Equatable, Hashable, Sendable { + public let rawValue: String public init(_ rawValue: String) { self.rawValue = rawValue @@ -60,7 +60,7 @@ fileprivate func handle(_ error: NSDecimalNumber.CalculationError, _ handler: NS } /*************** NSDecimalNumber: the class ***********/ -open class NSDecimalNumber : NSNumber { +open class NSDecimalNumber : NSNumber, @unchecked Sendable { fileprivate let decimal: Decimal public convenience init(mantissa: UInt64, exponent: Int16, isNegative: Bool) { @@ -214,7 +214,7 @@ open class NSDecimalNumber : NSNumber { return NSDecimalNumber(integerLiteral: 1) } open class var minimum: NSDecimalNumber { - return NSDecimalNumber(decimal:Decimal.leastFiniteMagnitude) + return NSDecimalNumber(decimal:-Decimal.greatestFiniteMagnitude) } open class var maximum: NSDecimalNumber { return NSDecimalNumber(decimal:Decimal.greatestFiniteMagnitude) @@ -416,7 +416,7 @@ open class NSDecimalNumber : NSNumber { /*********** A class for defining common behaviors *******/ -open class NSDecimalNumberHandler : NSObject, NSDecimalNumberBehaviors, NSCoding { +open class NSDecimalNumberHandler : NSObject, NSDecimalNumberBehaviors, NSCoding, @unchecked Sendable { static let defaultBehavior = NSDecimalNumberHandler() diff --git a/Sources/Foundation/NSDictionary.swift b/Sources/Foundation/NSDictionary.swift index c1d118088a..d656894a79 100644 --- a/Sources/Foundation/NSDictionary.swift +++ b/Sources/Foundation/NSDictionary.swift @@ -35,6 +35,11 @@ fileprivate func getDescription(of object: Any) -> String? { } } +@available(*, unavailable) +extension NSDictionary : Sendable { } + +@available(*, unavailable) +extension NSDictionary.Iterator : Sendable { } open class NSDictionary : NSObject, NSCopying, NSMutableCopying, NSSecureCoding, NSCoding, ExpressibleByDictionaryLiteral { private let _cfinfo = _CFInfo(typeID: CFDictionaryGetTypeID()) @@ -611,6 +616,9 @@ extension Dictionary : _NSBridgeable { internal var _cfObject: CFDictionary { return _nsObject._cfObject } } +@available(*, unavailable) +extension NSMutableDictionary : Sendable { } + open class NSMutableDictionary : NSDictionary { open func removeObject(forKey aKey: Any) { @@ -712,7 +720,7 @@ extension NSDictionary { As for any usage of hashing, is recommended that the keys have a well-distributed implementation of -hash, and the hash codes must satisfy the hash/isEqual: invariant. Keys with duplicate hash codes are allowed, but will cause lower performance and increase memory usage. */ - open class func sharedKeySet(forKeys keys: [NSCopying]) -> Any { + public class func sharedKeySet(forKeys keys: [NSCopying]) -> Any { return sharedKeySetPlaceholder } } diff --git a/Sources/Foundation/NSEnumerator.swift b/Sources/Foundation/NSEnumerator.swift index 57795783eb..751938e568 100644 --- a/Sources/Foundation/NSEnumerator.swift +++ b/Sources/Foundation/NSEnumerator.swift @@ -7,6 +7,12 @@ // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // +@available(*, unavailable) +extension NSEnumerator : Sendable { } + +@available(*, unavailable) +extension NSEnumerator.Iterator : Sendable { } + open class NSEnumerator : NSObject { open func nextObject() -> Any? { diff --git a/Sources/Foundation/NSError.swift b/Sources/Foundation/NSError.swift index d84e54e5c7..b5e7d4f97f 100644 --- a/Sources/Foundation/NSError.swift +++ b/Sources/Foundation/NSError.swift @@ -47,7 +47,7 @@ public let NSStringEncodingErrorKey: String = "NSStringEncodingErrorKey" public let NSURLErrorKey: String = "NSURL" public let NSFilePathErrorKey: String = "NSFilePath" -open class NSError : NSObject, NSCopying, NSSecureCoding, NSCoding { +open class NSError : NSObject, NSCopying, NSSecureCoding, NSCoding, @unchecked Sendable { typealias CFType = CFError internal final var _cfObject: CFType { @@ -55,13 +55,11 @@ open class NSError : NSObject, NSCopying, NSSecureCoding, NSCoding { } // ErrorType forbids this being internal - open var _domain: String - open var _code: Int - /// - Experiment: This is a draft API currently under consideration for official import into Foundation + public let _domain: String + public let _code: Int /// - Note: This API differs from Darwin because it uses [String : Any] as a type instead of [String : AnyObject]. This allows the use of Swift value types. - private var _userInfo: [String : Any]? + private let _userInfo: [String : Any]? - /// - Experiment: This is a draft API currently under consideration for official import into Foundation /// - Note: This API differs from Darwin because it uses [String : Any] as a type instead of [String : AnyObject]. This allows the use of Swift value types. public init(domain: String, code: Int, userInfo dict: [String : Any]? = nil) { _domain = domain @@ -84,6 +82,8 @@ open class NSError : NSObject, NSCopying, NSSecureCoding, NSCoding { } }) _userInfo = filteredUserInfo + } else { + _userInfo = nil } } @@ -219,6 +219,9 @@ extension CFError : _NSBridgeable { } } +@available(*, unavailable) +extension _CFErrorSPIForFoundationXMLUseOnly : Sendable { } + public struct _CFErrorSPIForFoundationXMLUseOnly { let error: AnyObject public init(unsafelyAssumingIsCFError error: AnyObject) { @@ -353,12 +356,7 @@ public extension Error where Self: CustomNSError, Self: RawRepresentable, Self.R public extension Error { /// Retrieve the localized description for this error. var localizedDescription: String { - if let nsError = self as? NSError { - return nsError.localizedDescription - } - - let defaultUserInfo = _swift_Foundation_getErrorDefaultUserInfo(self) as? [String : Any] - return NSError(domain: _domain, code: _code, userInfo: defaultUserInfo).localizedDescription + (self as NSError).localizedDescription } } @@ -721,7 +719,7 @@ extension CocoaError: _ObjectiveCBridgeable { } /// Describes errors in the URL error domain. -public struct URLError : _BridgedStoredNSError { +public struct URLError : _BridgedStoredNSError, Sendable { public let _nsError: NSError public init(_nsError error: NSError) { @@ -731,7 +729,7 @@ public struct URLError : _BridgedStoredNSError { public static var _nsErrorDomain: String { return NSURLErrorDomain } - public enum Code : Int, _ErrorCodeProtocol { + public enum Code : Int, _ErrorCodeProtocol, Sendable { public typealias _ErrorType = URLError case unknown = -1 diff --git a/Sources/Foundation/NSExpression.swift b/Sources/Foundation/NSExpression.swift index 4ed7c30a75..159c232051 100644 --- a/Sources/Foundation/NSExpression.swift +++ b/Sources/Foundation/NSExpression.swift @@ -9,8 +9,9 @@ // Expressions are the core of the predicate implementation. When expressionValueWithObject: is called, the expression is evaluated, and a value returned which can then be handled by an operator. Expressions can be anything from constants to method invocations. Scalars should be wrapped in appropriate NSValue classes. +@available(*, deprecated, message: "NSExpression is not available in swift-corelibs-foundation") extension NSExpression { - public enum ExpressionType : UInt { + public enum ExpressionType : UInt, Sendable { case constantValue // Expression that always returns the same value case evaluatedObject // Expression that always returns the parameter object itself @@ -28,6 +29,9 @@ extension NSExpression { } } +@available(*, unavailable) +extension NSExpression : Sendable { } + @available(*, deprecated, message: "NSExpression is not available in swift-corelibs-foundation") open class NSExpression : NSObject, NSCopying { diff --git a/Sources/Foundation/NSIndexPath.swift b/Sources/Foundation/NSIndexPath.swift index 9464d780c4..0cb838c91a 100644 --- a/Sources/Foundation/NSIndexPath.swift +++ b/Sources/Foundation/NSIndexPath.swift @@ -7,6 +7,8 @@ // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // +@available(*, unavailable) +extension NSIndexPath : Sendable { } open class NSIndexPath : NSObject, NSCopying, NSSecureCoding { diff --git a/Sources/Foundation/NSIndexSet.swift b/Sources/Foundation/NSIndexSet.swift index cd2cd2a893..49aa791a53 100644 --- a/Sources/Foundation/NSIndexSet.swift +++ b/Sources/Foundation/NSIndexSet.swift @@ -59,6 +59,9 @@ internal func __NSIndexSetIndexOfRangeContainingIndex(_ indexSet: NSIndexSet, _ return NSNotFound } +@available(*, unavailable) +extension NSIndexSet : Sendable { } + open class NSIndexSet : NSObject, NSCopying, NSMutableCopying, NSSecureCoding { // all instance variables are private @@ -576,6 +579,9 @@ open class NSIndexSet : NSObject, NSCopying, NSMutableCopying, NSSecureCoding { } } +@available(*, unavailable) +extension NSIndexSetIterator : Sendable { } + public struct NSIndexSetIterator : IteratorProtocol { public typealias Element = Int internal let _set: NSIndexSet @@ -607,6 +613,9 @@ extension NSIndexSet : Sequence { } } +@available(*, unavailable) +extension NSMutableIndexSet : Sendable { } + open class NSMutableIndexSet : NSIndexSet { open func add(_ indexSet: IndexSet) { diff --git a/Sources/Foundation/NSKeyedArchiver.swift b/Sources/Foundation/NSKeyedArchiver.swift index c09c682222..01b41252fb 100644 --- a/Sources/Foundation/NSKeyedArchiver.swift +++ b/Sources/Foundation/NSKeyedArchiver.swift @@ -36,6 +36,9 @@ internal let NSPropertyListClasses : [AnyClass] = [ NSNumber.self ] +@available(*, unavailable) +extension NSKeyedArchiver : Sendable { } + /// `NSKeyedArchiver`, a concrete subclass of `NSCoder`, provides a way to encode objects /// (and scalar values) into an architecture-independent format that can be stored in a file. /// When you archive a set of objects, the class information and instance variables for each object diff --git a/Sources/Foundation/NSKeyedUnarchiver.swift b/Sources/Foundation/NSKeyedUnarchiver.swift index 0c76f3a61b..696e968985 100644 --- a/Sources/Foundation/NSKeyedUnarchiver.swift +++ b/Sources/Foundation/NSKeyedUnarchiver.swift @@ -9,6 +9,9 @@ @_implementationOnly import CoreFoundation +@available(*, unavailable) +extension NSKeyedUnarchiver : Sendable { } + open class NSKeyedUnarchiver : NSCoder { enum InternalError: Error { /* diff --git a/Sources/Foundation/NSLocale.swift b/Sources/Foundation/NSLocale.swift index 6d4215a12f..10db78977f 100644 --- a/Sources/Foundation/NSLocale.swift +++ b/Sources/Foundation/NSLocale.swift @@ -12,9 +12,9 @@ @_spi(SwiftCorelibsFoundation) @_exported import FoundationEssentials @_exported import FoundationInternationalization -open class NSLocale: NSObject, NSCopying, NSSecureCoding { +open class NSLocale: NSObject, NSCopying, NSSecureCoding, @unchecked Sendable { // Our own data - var _locale: Locale + let _locale: Locale internal init(locale: Locale) { _locale = locale @@ -27,27 +27,16 @@ open class NSLocale: NSObject, NSCopying, NSSecureCoding { case .countryCode: return self.countryCode case .scriptCode: return self.scriptCode case .variantCode: return self.variantCode - //case .exemplarCharacterSet: return self.exemplarCharacterSet +#if FOUNDATION_FRAMEWORK + case .exemplarCharacterSet: return self.exemplarCharacterSet +#endif case .calendarIdentifier: return self.calendarIdentifier case .calendar: return _locale.calendar case .collationIdentifier: return self.collationIdentifier case .usesMetricSystem: return self.usesMetricSystem - // Foundation framework only - /* - case .measurementSystem: - switch locale.measurementSystem { - case .us: return NSLocaleMeasurementSystemUS - case .uk: return NSLocaleMeasurementSystemUK - case .metric: return NSLocaleMeasurementSystemMetric - default: return NSLocaleMeasurementSystemMetric - } - case .temperatureUnit: - switch _locale.temperatureUnit { - case .celsius: return NSLocaleTemperatureUnitCelsius - case .fahrenheit: return NSLocaleTemperatureUnitFahrenheit - default: return NSLocaleTemperatureUnitCelsius - } - */ +#if FOUNDATION_FRAMEWORK + case .measurementSystem: return self.measurementSystem +#endif case .decimalSeparator: return self.decimalSeparator case .groupingSeparator: return self.groupingSeparator case .currencySymbol: return self.currencySymbol @@ -70,10 +59,6 @@ open class NSLocale: NSObject, NSCopying, NSSecureCoding { } open func displayName(forKey key: Key, value: String) -> String? { - guard let value = value as? String else { - return nil - } - switch key { case .identifier: return self._nullableLocalizedString(forLocaleIdentifier: value) case .languageCode: return self.localizedString(forLanguageCode: value) @@ -282,62 +267,62 @@ open class NSLocale: NSObject, NSCopying, NSSecureCoding { } extension NSLocale { - open class var current: Locale { + public class var current: Locale { Locale.current } - open class var system: Locale { + public class var system: Locale { Locale(identifier: "") } } extension NSLocale { - open class var availableLocaleIdentifiers: [String] { + public class var availableLocaleIdentifiers: [String] { Locale.availableIdentifiers } - open class var isoLanguageCodes: [String] { + public class var isoLanguageCodes: [String] { // Map back from the type to strings Locale.LanguageCode.isoLanguageCodes.map { $0.identifier } } - open class var isoCountryCodes: [String] { + public class var isoCountryCodes: [String] { Locale.Region.isoRegions.map { $0.identifier } } - open class var isoCurrencyCodes: [String] { + public class var isoCurrencyCodes: [String] { Locale.Currency.isoCurrencies.map { $0.identifier } } - open class var commonISOCurrencyCodes: [String] { + public class var commonISOCurrencyCodes: [String] { Locale.commonISOCurrencyCodes } - open class var preferredLanguages: [String] { + public class var preferredLanguages: [String] { Locale.preferredLanguages } - open class func components(fromLocaleIdentifier string: String) -> [String : String] { + public class func components(fromLocaleIdentifier string: String) -> [String : String] { __SwiftValue.fetch(CFLocaleCreateComponentsFromLocaleIdentifier(kCFAllocatorSystemDefault, string._cfObject)) as? [String : String] ?? [:] } - open class func localeIdentifier(fromComponents dict: [String : String]) -> String { + public class func localeIdentifier(fromComponents dict: [String : String]) -> String { Locale.identifier(fromComponents: dict) } - open class func canonicalLocaleIdentifier(from string: String) -> String { + public class func canonicalLocaleIdentifier(from string: String) -> String { Locale.canonicalLanguageIdentifier(from: string) } - open class func canonicalLanguageIdentifier(from string: String) -> String { + public class func canonicalLanguageIdentifier(from string: String) -> String { Locale.canonicalLanguageIdentifier(from: string) } - open class func localeIdentifier(fromWindowsLocaleCode lcid: UInt32) -> String? { + public class func localeIdentifier(fromWindowsLocaleCode lcid: UInt32) -> String? { Locale.identifier(fromWindowsLocaleCode: Int(lcid)) } - open class func windowsLocaleCode(fromLocaleIdentifier localeIdentifier: String) -> UInt32 { + public class func windowsLocaleCode(fromLocaleIdentifier localeIdentifier: String) -> UInt32 { if let code = Locale.windowsLocaleCode(fromIdentifier: localeIdentifier) { return UInt32(code) } else { @@ -345,12 +330,12 @@ extension NSLocale { } } - open class func characterDirection(forLanguage isoLangCode: String) -> NSLocale.LanguageDirection { + public class func characterDirection(forLanguage isoLangCode: String) -> NSLocale.LanguageDirection { let language = Locale.Language(components: .init(identifier: isoLangCode)) return language.characterDirection } - open class func lineDirection(forLanguage isoLangCode: String) -> NSLocale.LanguageDirection { + public class func lineDirection(forLanguage isoLangCode: String) -> NSLocale.LanguageDirection { let language = Locale.Language(components: .init(identifier: isoLangCode)) return language.lineLayoutDirection } @@ -358,8 +343,8 @@ extension NSLocale { extension NSLocale { - public struct Key : RawRepresentable, Equatable, Hashable { - public private(set) var rawValue: String + public struct Key : RawRepresentable, Equatable, Hashable, Sendable { + public let rawValue: String public init(rawValue: String) { self.rawValue = rawValue } diff --git a/Sources/Foundation/NSMeasurement.swift b/Sources/Foundation/NSMeasurement.swift index 7f0f1742c7..3b341822aa 100644 --- a/Sources/Foundation/NSMeasurement.swift +++ b/Sources/Foundation/NSMeasurement.swift @@ -10,6 +10,9 @@ // //===----------------------------------------------------------------------===// +@available(*, unavailable) +extension NSMeasurement : Sendable { } + open class NSMeasurement : NSObject, NSCopying, NSSecureCoding { open private(set) var unit: Unit open private(set) var doubleValue: Double diff --git a/Sources/Foundation/NSNotification.swift b/Sources/Foundation/NSNotification.swift index ff25a3b136..d353f6d6a5 100644 --- a/Sources/Foundation/NSNotification.swift +++ b/Sources/Foundation/NSNotification.swift @@ -7,6 +7,9 @@ // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // +@available(*, unavailable) +extension NSNotification : Sendable { } + open class NSNotification: NSObject, NSCopying, NSCoding { public struct Name : RawRepresentable, Equatable, Hashable, Sendable { public private(set) var rawValue: String @@ -93,7 +96,7 @@ private class NSNotificationReceiver : NSObject { private let _defaultCenter: NotificationCenter = NotificationCenter() -open class NotificationCenter: NSObject { +open class NotificationCenter: NSObject, @unchecked Sendable { private lazy var _nilIdentifier: ObjectIdentifier = ObjectIdentifier(_observersLock) private lazy var _nilHashable: AnyHashable = AnyHashable(_nilIdentifier) @@ -130,7 +133,9 @@ open class NotificationCenter: NSObject { } if let queue = observer.queue, queue != OperationQueue.current { - queue.addOperation { block(notification) } + // Not entirely safe, but maintained for compatibility + nonisolated(unsafe) let unsafeNotification = notification + queue.addOperation { block(unsafeNotification) } queue.waitUntilAllOperationsAreFinished() } else { block(notification) diff --git a/Sources/Foundation/NSNull.swift b/Sources/Foundation/NSNull.swift index 901cbacbe2..4dbee5dc47 100644 --- a/Sources/Foundation/NSNull.swift +++ b/Sources/Foundation/NSNull.swift @@ -8,7 +8,7 @@ // -open class NSNull : NSObject, NSCopying, NSSecureCoding { +open class NSNull : NSObject, NSCopying, NSSecureCoding, @unchecked Sendable { open override func copy() -> Any { return copy(with: nil) diff --git a/Sources/Foundation/NSNumber.swift b/Sources/Foundation/NSNumber.swift index 82d4481a99..71e4f7e81f 100644 --- a/Sources/Foundation/NSNumber.swift +++ b/Sources/Foundation/NSNumber.swift @@ -604,11 +604,11 @@ fileprivate func cast(_ t: T) -> U { return t as! U } -open class NSNumber : NSValue { +open class NSNumber : NSValue, @unchecked Sendable { typealias CFType = CFNumber // This layout MUST be the same as CFNumber so that they are bridgeable - private var _base = _CFInfo(typeID: CFNumberGetTypeID()) - private var _pad: UInt64 = 0 + private let _base = _CFInfo(typeID: CFNumberGetTypeID()) + private let _pad: UInt64 = 0 internal final var _cfObject: CFType { return unsafeBitCast(self, to: CFType.self) diff --git a/Sources/Foundation/NSObjCRuntime.swift b/Sources/Foundation/NSObjCRuntime.swift index ce75a1fcc2..6906171191 100644 --- a/Sources/Foundation/NSObjCRuntime.swift +++ b/Sources/Foundation/NSObjCRuntime.swift @@ -146,7 +146,7 @@ extension ComparisonResult { } /* Note: QualityOfService enum is available on all platforms, but it may not be implemented on all platforms. */ -public enum QualityOfService : Int { +public enum QualityOfService : Int, Sendable { /* UserInteractive QoS is used for work directly involved in providing an interactive UI such as processing events or drawing to the screen. */ case userInteractive @@ -164,7 +164,7 @@ public enum QualityOfService : Int { case `default` } -public struct NSSortOptions: OptionSet { +public struct NSSortOptions: OptionSet, Sendable { public let rawValue : UInt public init(rawValue: UInt) { self.rawValue = rawValue } @@ -172,7 +172,7 @@ public struct NSSortOptions: OptionSet { public static let stable = NSSortOptions(rawValue: UInt(1 << 4)) } -public struct NSEnumerationOptions: OptionSet { +public struct NSEnumerationOptions: OptionSet, Sendable { public let rawValue : UInt public init(rawValue: UInt) { self.rawValue = rawValue } @@ -275,7 +275,8 @@ internal let _NSClassesRenamedByObjCAPINotes: [(class: AnyClass, objCName: Strin (MassFormatter.self, "NSMassFormatter"), (NumberFormatter.self, "NSNumberFormatter"), (OutputStream.self, "NSOutputStream"), - (PersonNameComponentsFormatter.self, "NSPersonNameComponentsFormatter"), + // This type is deprecated and unavailable in SCL-F. + //(PersonNameComponentsFormatter.self, "NSPersonNameComponentsFormatter"), (Pipe.self, "NSPipe"), (PropertyListSerialization.self, "NSPropertyListSerialization"), (Scanner.self, "NSScanner"), diff --git a/Sources/Foundation/NSObject.swift b/Sources/Foundation/NSObject.swift index a2bbd0b77d..089849964a 100644 --- a/Sources/Foundation/NSObject.swift +++ b/Sources/Foundation/NSObject.swift @@ -84,6 +84,9 @@ extension NSObjectProtocol { } } +@available(*, unavailable) +extension NSZone : Sendable { } + public struct NSZone : ExpressibleByNilLiteral { public init() { @@ -407,3 +410,7 @@ extension NSObject : CustomDebugStringConvertible { extension NSObject : CustomStringConvertible { } + +@available(*, unavailable) +extension NSObject : Sendable { } + diff --git a/Sources/Foundation/NSOrderedSet.swift b/Sources/Foundation/NSOrderedSet.swift index 6c99947516..f91d3bcd61 100644 --- a/Sources/Foundation/NSOrderedSet.swift +++ b/Sources/Foundation/NSOrderedSet.swift @@ -7,7 +7,11 @@ // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // + /**************** Immutable Ordered Set ****************/ +@available(*, unavailable) +extension NSOrderedSet : Sendable { } + open class NSOrderedSet: NSObject, NSCopying, NSMutableCopying, NSSecureCoding, ExpressibleByArrayLiteral { fileprivate var _storage: NSSet @@ -390,6 +394,9 @@ open class NSOrderedSet: NSObject, NSCopying, NSMutableCopying, NSSecureCoding, /**************** Mutable Ordered Set ****************/ +@available(*, unavailable) +extension NSMutableOrderedSet : Sendable { } + open class NSMutableOrderedSet: NSOrderedSet { fileprivate var _mutableStorage: NSMutableSet diff --git a/Sources/Foundation/NSPathUtilities.swift b/Sources/Foundation/NSPathUtilities.swift index 96c41c2816..f6cda6abf9 100644 --- a/Sources/Foundation/NSPathUtilities.swift +++ b/Sources/Foundation/NSPathUtilities.swift @@ -536,7 +536,7 @@ extension NSString { return String(decodingCString: fsr, as: UTF16.self).withCString() { let chars = strnlen_s($0, max) guard chars < max else { return false } - cname.assign(from: $0, count: chars + 1) + cname.update(from: $0, count: chars + 1) return true } #else @@ -584,7 +584,7 @@ extension NSString { return fsr.withCString(encodedAs: UTF16.self) { let wchars = wcsnlen_s($0, max) guard wchars < max else { return false } - cname.assign(from: $0, count: wchars + 1) + cname.update(from: $0, count: wchars + 1) return true } #else diff --git a/Sources/Foundation/NSPersonNameComponents.swift b/Sources/Foundation/NSPersonNameComponents.swift index 52e5a0df3d..6ac9cf4e99 100644 --- a/Sources/Foundation/NSPersonNameComponents.swift +++ b/Sources/Foundation/NSPersonNameComponents.swift @@ -7,11 +7,21 @@ // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // +@available(*, unavailable) +extension NSPersonNameComponents : Sendable { } open class NSPersonNameComponents : NSObject, NSCopying, NSSecureCoding { + override public init() { + _pnc = PersonNameComponents() + } + + internal init(pnc: PersonNameComponents) { + _pnc = pnc + } + public convenience required init?(coder aDecoder: NSCoder) { - self.init() + self.init(pnc: .init()) guard aDecoder.allowsKeyedCoding else { preconditionFailure("Unkeyed coding is unsupported.") } @@ -46,22 +56,7 @@ open class NSPersonNameComponents : NSObject, NSCopying, NSSecureCoding { open func copy(with zone: NSZone? = nil) -> Any { let copy = NSPersonNameComponents() - copy.namePrefix = namePrefix - copy.givenName = givenName - copy.middleName = middleName - copy.familyName = familyName - copy.nameSuffix = nameSuffix - copy.nickname = nickname - if let PR = phoneticRepresentation { - var copyPR = PersonNameComponents() - copyPR.namePrefix = PR.namePrefix - copyPR.givenName = PR.givenName - copyPR.middleName = PR.middleName - copyPR.familyName = PR.familyName - copyPR.nameSuffix = PR.nameSuffix - copyPR.nickname = PR.nickname - copy.phoneticRepresentation = copyPR - } + copy._pnc = _pnc return copy } @@ -69,48 +64,64 @@ open class NSPersonNameComponents : NSObject, NSCopying, NSSecureCoding { guard let object = object else { return false } switch object { - case let other as NSPersonNameComponents: return self.isEqual(other) - case let other as PersonNameComponents: return self.isEqual(other._bridgeToObjectiveC()) - default: return false + case let other as NSPersonNameComponents: + return _pnc == other._pnc + case let other as PersonNameComponents: + return _pnc == other + default: + return false } } private func isEqual(_ other: NSPersonNameComponents) -> Bool { - if self === other { return true } - - return (self.namePrefix == other.namePrefix - && self.givenName == other.givenName - && self.middleName == other.middleName - && self.familyName == other.familyName - && self.nameSuffix == other.nameSuffix - && self.nickname == other.nickname - && self.phoneticRepresentation == other.phoneticRepresentation) + _pnc == other._pnc } - /* The below examples all assume the full name Dr. Johnathan Maple Appleseed Esq., nickname "Johnny" */ + // Internal for ObjectiveCBridgable access + internal var _pnc = PersonNameComponents() - /* Pre-nominal letters denoting title, salutation, or honorific, e.g. Dr., Mr. */ - open var namePrefix: String? + /// Assuming the full name is: Dr. Johnathan Maple Appleseed Esq., nickname "Johnny", pre-nominal letters denoting title, salutation, or honorific, e.g. Dr., Mr. + open var namePrefix: String? { + get { _pnc.namePrefix } + set { _pnc.namePrefix = newValue } + } - /* Name bestowed upon an individual by one's parents, e.g. Johnathan */ - open var givenName: String? + /// Assuming the full name is: Dr. Johnathan Maple Appleseed Esq., nickname "Johnny", name bestowed upon an individual by one's parents, e.g. Johnathan + open var givenName: String? { + get { _pnc.givenName } + set { _pnc.givenName = newValue } + } - /* Secondary given name chosen to differentiate those with the same first name, e.g. Maple */ - open var middleName: String? + /// Assuming the full name is: Dr. Johnathan Maple Appleseed Esq., nickname "Johnny", secondary given name chosen to differentiate those with the same first name, e.g. Maple + open var middleName: String? { + get { _pnc.middleName } + set { _pnc.middleName = newValue } + } - /* Name passed from one generation to another to indicate lineage, e.g. Appleseed */ - open var familyName: String? + /// Assuming the full name is: Dr. Johnathan Maple Appleseed Esq., nickname "Johnny", name passed from one generation to another to indicate lineage, e.g. Appleseed + open var familyName: String? { + get { _pnc.familyName } + set { _pnc.familyName = newValue } + } - /* Post-nominal letters denoting degree, accreditation, or other honor, e.g. Esq., Jr., Ph.D. */ - open var nameSuffix: String? + /// Assuming the full name is: Dr. Johnathan Maple Appleseed Esq., nickname "Johnny", post-nominal letters denoting degree, accreditation, or other honor, e.g. Esq., Jr., Ph.D. + open var nameSuffix: String? { + get { _pnc.nameSuffix } + set { _pnc.nameSuffix = newValue } + } - /* Name substituted for the purposes of familiarity, e.g. "Johnny"*/ - open var nickname: String? + /// Assuming the full name is: Dr. Johnathan Maple Appleseed Esq., nickname "Johnny", name substituted for the purposes of familiarity, e.g. "Johnny" + open var nickname: String? { + get { _pnc.nickname } + set { _pnc.nickname = newValue } + } - /* Each element of the phoneticRepresentation should correspond to an element of the original PersonNameComponents instance. - The phoneticRepresentation of the phoneticRepresentation object itself will be ignored. nil by default, must be instantiated. - */ - /*@NSCopying*/ open var phoneticRepresentation: PersonNameComponents? + /// Each element of the phoneticRepresentation should correspond to an element of the original PersonNameComponents instance. + /// The phoneticRepresentation of the phoneticRepresentation object itself will be ignored. nil by default, must be instantiated. + open var phoneticRepresentation: PersonNameComponents? { + get { _pnc.phoneticRepresentation } + set { _pnc.phoneticRepresentation = newValue } + } } extension NSPersonNameComponents : _StructTypeBridgeable { diff --git a/Sources/Foundation/NSPredicate.swift b/Sources/Foundation/NSPredicate.swift index 56b155e8a0..12151c2c28 100644 --- a/Sources/Foundation/NSPredicate.swift +++ b/Sources/Foundation/NSPredicate.swift @@ -7,6 +7,8 @@ // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // +@available(*, unavailable) +extension NSPredicate : Sendable { } // Predicates wrap some combination of expressions and operators and when evaluated return a BOOL. @@ -110,7 +112,7 @@ extension NSPredicate { } extension NSArray { - open func filtered(using predicate: NSPredicate) -> [Any] { + public func filtered(using predicate: NSPredicate) -> [Any] { return allObjects.filter({ object in return predicate.evaluate(with: object) }) @@ -118,7 +120,7 @@ extension NSArray { } extension NSMutableArray { - open func filter(using predicate: NSPredicate) { + public func filter(using predicate: NSPredicate) { var indexesToRemove = IndexSet() for (index, object) in self.enumerated() { if !predicate.evaluate(with: object) { @@ -130,7 +132,7 @@ extension NSMutableArray { } extension NSSet { - open func filtered(using predicate: NSPredicate) -> Set { + public func filtered(using predicate: NSPredicate) -> Set { let objs = allObjects.filter { (object) -> Bool in return predicate.evaluate(with: object) } @@ -139,7 +141,7 @@ extension NSSet { } extension NSMutableSet { - open func filter(using predicate: NSPredicate) { + public func filter(using predicate: NSPredicate) { for object in self { if !predicate.evaluate(with: object) { self.remove(object) @@ -149,7 +151,7 @@ extension NSMutableSet { } extension NSOrderedSet { - open func filtered(using predicate: NSPredicate) -> NSOrderedSet { + public func filtered(using predicate: NSPredicate) -> NSOrderedSet { return NSOrderedSet(array: self.allObjects.filter({ object in return predicate.evaluate(with: object) })) @@ -157,7 +159,7 @@ extension NSOrderedSet { } extension NSMutableOrderedSet { - open func filter(using predicate: NSPredicate) { + public func filter(using predicate: NSPredicate) { var indexesToRemove = IndexSet() for (index, object) in self.enumerated() { if !predicate.evaluate(with: object) { diff --git a/Sources/Foundation/NSRange.swift b/Sources/Foundation/NSRange.swift index 3ec747b0db..1d5c85a045 100644 --- a/Sources/Foundation/NSRange.swift +++ b/Sources/Foundation/NSRange.swift @@ -11,7 +11,7 @@ @_implementationOnly import CoreFoundation -public struct _NSRange { +public struct _NSRange : Sendable { public var location: Int public var length: Int diff --git a/Sources/Foundation/NSRegularExpression.swift b/Sources/Foundation/NSRegularExpression.swift index 24adba513a..8b4376e742 100644 --- a/Sources/Foundation/NSRegularExpression.swift +++ b/Sources/Foundation/NSRegularExpression.swift @@ -13,7 +13,7 @@ @_implementationOnly import CoreFoundation extension NSRegularExpression { - public struct Options : OptionSet { + public struct Options : OptionSet, Sendable { public let rawValue : UInt public init(rawValue: UInt) { self.rawValue = rawValue } @@ -27,7 +27,7 @@ extension NSRegularExpression { } } -open class NSRegularExpression: NSObject, NSCopying, NSSecureCoding { +open class NSRegularExpression: NSObject, NSCopying, NSSecureCoding, @unchecked Sendable { internal var _internalStorage: AnyObject internal final var _internal: _CFRegularExpression { unsafeBitCast(_internalStorage, to: _CFRegularExpression.self) @@ -91,17 +91,17 @@ open class NSRegularExpression: NSObject, NSCopying, NSSecureCoding { } } - open var pattern: String { + public var pattern: String { return _CFRegularExpressionGetPattern(_internal)._swiftObject } - open var options: Options { + public var options: Options { let opt = _CFRegularExpressionGetOptions(_internal).rawValue return Options(rawValue: opt) } - open var numberOfCaptureGroups: Int { + public var numberOfCaptureGroups: Int { return _CFRegularExpressionGetNumberOfCaptureGroups(_internal) } @@ -111,14 +111,14 @@ open class NSRegularExpression: NSObject, NSCopying, NSSecureCoding { /* This class method will produce a string by adding backslash escapes as necessary to the given string, to escape any characters that would otherwise be treated as pattern metacharacters. */ - open class func escapedPattern(for string: String) -> String { + public class func escapedPattern(for string: String) -> String { return _CFRegularExpressionCreateEscapedPattern(string._cfObject)._swiftObject } } extension NSRegularExpression { - public struct MatchingOptions : OptionSet { + public struct MatchingOptions : OptionSet, Sendable { public let rawValue : UInt public init(rawValue: UInt) { self.rawValue = rawValue } @@ -130,7 +130,7 @@ extension NSRegularExpression { internal static let OmitResult = MatchingOptions(rawValue: 1 << 13) } - public struct MatchingFlags : OptionSet { + public struct MatchingFlags : OptionSet, Sendable { public let rawValue : UInt public init(rawValue: UInt) { self.rawValue = rawValue } @@ -349,7 +349,7 @@ extension NSRegularExpression { /* This class method will produce a string by adding backslash escapes as necessary to the given string, to escape any characters that would otherwise be treated as template metacharacters. */ - open class func escapedTemplate(for string: String) -> String { + public class func escapedTemplate(for string: String) -> String { return _CFRegularExpressionCreateEscapedPattern(string._cfObject)._swiftObject } } diff --git a/Sources/Foundation/NSSet.swift b/Sources/Foundation/NSSet.swift index 7f2318d6de..5962394811 100644 --- a/Sources/Foundation/NSSet.swift +++ b/Sources/Foundation/NSSet.swift @@ -10,6 +10,9 @@ @_implementationOnly import CoreFoundation +@available(*, unavailable) +extension NSSet : Sendable { } + open class NSSet : NSObject, NSCopying, NSMutableCopying, NSSecureCoding, NSCoding { private let _cfinfo = _CFInfo(typeID: CFSetGetTypeID()) internal var _storage: Set @@ -71,11 +74,7 @@ open class NSSet : NSObject, NSCopying, NSMutableCopying, NSSecureCoding, NSCodi public convenience init(set: Set, copyItems flag: Bool) { if flag { self.init(array: set.map { - if let item = $0 as? NSObject { - return item.copy() - } else { - return $0 - } + return ($0 as NSObject).copy() }) } else { self.init(array: Array(set)) @@ -370,6 +369,9 @@ extension NSSet: CustomReflectable { } } +@available(*, unavailable) +extension NSMutableSet : Sendable { } + open class NSMutableSet : NSSet { open func add(_ object: Any) { @@ -461,6 +463,10 @@ open class NSMutableSet : NSSet { } /**************** Counted Set ****************/ + +@available(*, unavailable) +extension NSCountedSet : Sendable { } + open class NSCountedSet : NSMutableSet { // Note: in 5.0 and earlier, _table contained the object's exact count. // In 5.1 and earlier, it contains the count minus one. This allows us to have a quick 'is this set just like a regular NSSet' flag (if this table is empty, then all objects in it exist at most once in it.) diff --git a/Sources/Foundation/NSSortDescriptor.swift b/Sources/Foundation/NSSortDescriptor.swift index 03b52bd659..9945fb379d 100644 --- a/Sources/Foundation/NSSortDescriptor.swift +++ b/Sources/Foundation/NSSortDescriptor.swift @@ -9,6 +9,9 @@ @_implementationOnly import CoreFoundation +@available(*, unavailable) +extension NSSortDescriptor : Sendable { } + // In swift-corelibs-foundation, key-value coding is not available. Since encoding and decoding a NSSortDescriptor requires interpreting key paths, NSSortDescriptor does not conform to NSCoding or NSSecureCoding in swift-corelibs-foundation only. open class NSSortDescriptor: NSObject, NSCopying { open override func copy() -> Any { diff --git a/Sources/Foundation/NSSpecialValue.swift b/Sources/Foundation/NSSpecialValue.swift index d8540de041..3f95cc47eb 100644 --- a/Sources/Foundation/NSSpecialValue.swift +++ b/Sources/Foundation/NSSpecialValue.swift @@ -28,7 +28,7 @@ internal protocol NSSpecialValueCoding { var description: String { get } } -internal class NSSpecialValue : NSValue { +internal class NSSpecialValue : NSValue, @unchecked Sendable { // Originally these were functions in NSSpecialValueCoding but it's probably // more convenient to keep it as a table here as nothing else really needs to @@ -59,7 +59,7 @@ internal class NSSpecialValue : NSValue { return _specialTypes.first(where: { $1.objCType() == objCType })?.1 } - internal var _value : NSSpecialValueCoding + internal let _value : NSSpecialValueCoding init(_ value: NSSpecialValueCoding) { self._value = value diff --git a/Sources/Foundation/NSString.swift b/Sources/Foundation/NSString.swift index d0a1649690..cc037a7756 100644 --- a/Sources/Foundation/NSString.swift +++ b/Sources/Foundation/NSString.swift @@ -55,7 +55,7 @@ internal let kCFStringNormalizationFormKC = CFStringNormalizationForm.KC extension NSString { - public struct EncodingConversionOptions : OptionSet { + public struct EncodingConversionOptions : OptionSet, Sendable { public let rawValue : UInt public init(rawValue: UInt) { self.rawValue = rawValue } @@ -64,7 +64,7 @@ extension NSString { internal static let failOnPartialEncodingConversion = EncodingConversionOptions(rawValue: 1 << 20) } - public struct EnumerationOptions : OptionSet { + public struct EnumerationOptions : OptionSet, Sendable { public let rawValue : UInt public init(rawValue: UInt) { self.rawValue = rawValue } @@ -96,7 +96,7 @@ extension NSString.CompareOptions { } } -public struct StringTransform: Equatable, Hashable, RawRepresentable { +public struct StringTransform: Equatable, Hashable, RawRepresentable, Sendable { typealias RawType = String public let rawValue: String @@ -199,6 +199,9 @@ internal func isAParagraphSeparatorTypeCharacter(_ ch: unichar) -> Bool { return ch == 0x0a || ch == 0x0d || ch == 0x2029 } +@available(*, unavailable) +extension NSString : Sendable { } + open class NSString : NSObject, NSCopying, NSMutableCopying, NSSecureCoding, NSCoding { private let _cfinfo = _CFInfo(typeID: CFStringGetTypeID()) internal var _storage: String @@ -320,15 +323,7 @@ open class NSString : NSObject, NSCopying, NSMutableCopying, NSSecureCoding, NSC } internal func _fastCStringContents(_ nullTerminated: Bool) -> UnsafePointer? { - guard !nullTerminated else { - // There is no way to fastly and safely retrieve a pointer to a null-terminated string from a String of Swift. - return nil - } - if type(of: self) == NSString.self || type(of: self) == NSMutableString.self { - if _storage._guts._isContiguousASCII { - return UnsafeRawPointer(_storage._guts.startASCII).assumingMemoryBound(to: Int8.self) - } - } + // There is no truly safe way to return an inner pointer for CFString here return nil } @@ -808,7 +803,7 @@ extension NSString { return NSRange(location: start, length: parEnd - start) } - private enum EnumerateBy { + private enum EnumerateBy : Sendable { case lines case paragraphs case composedCharacterSequences @@ -972,10 +967,15 @@ extension NSString { if type(of: self) == NSString.self || type(of: self) == NSMutableString.self { if _storage._guts._isContiguousASCII { used = min(self.length, maxBufferCount - 1) - _storage._guts.startASCII.withMemoryRebound(to: Int8.self, - capacity: used) { - buffer.moveAssign(from: $0, count: used) + + // This is mutable, but since we just checked the contiguous behavior, should not copy + var copy = _storage + copy.withUTF8 { + $0.withMemoryRebound(to: Int8.self) { + buffer.update(from: $0.baseAddress!, count: used) + } } + buffer.advanced(by: used).initialize(to: 0) return true } @@ -1026,7 +1026,7 @@ extension NSString { return convertedLen != len ? 0 : numBytes } - open class var availableStringEncodings: UnsafePointer { + public class var availableStringEncodings: UnsafePointer { struct once { static let encodings: UnsafePointer = { let cfEncodings = CFStringGetListOfAvailableEncodings()! @@ -1054,7 +1054,7 @@ extension NSString { return once.encodings } - open class func localizedName(of encoding: UInt) -> String { + public class func localizedName(of encoding: UInt) -> String { if let theString = CFStringGetNameOfEncoding(CFStringConvertNSStringEncodingToEncoding(numericCast(encoding))) { // TODO: read the localized version from the Foundation "bundle" return theString._swiftObject @@ -1063,39 +1063,39 @@ extension NSString { return "" } - open class var defaultCStringEncoding: UInt { + public class var defaultCStringEncoding: UInt { return numericCast(CFStringConvertEncodingToNSStringEncoding(CFStringGetSystemEncoding())) } - open var decomposedStringWithCanonicalMapping: String { + public var decomposedStringWithCanonicalMapping: String { let string = CFStringCreateMutable(kCFAllocatorSystemDefault, 0)! CFStringReplaceAll(string, self._cfObject) CFStringNormalize(string, kCFStringNormalizationFormD) return string._swiftObject } - open var precomposedStringWithCanonicalMapping: String { + public var precomposedStringWithCanonicalMapping: String { let string = CFStringCreateMutable(kCFAllocatorSystemDefault, 0)! CFStringReplaceAll(string, self._cfObject) CFStringNormalize(string, kCFStringNormalizationFormC) return string._swiftObject } - open var decomposedStringWithCompatibilityMapping: String { + public var decomposedStringWithCompatibilityMapping: String { let string = CFStringCreateMutable(kCFAllocatorSystemDefault, 0)! CFStringReplaceAll(string, self._cfObject) CFStringNormalize(string, kCFStringNormalizationFormKD) return string._swiftObject } - open var precomposedStringWithCompatibilityMapping: String { + public var precomposedStringWithCompatibilityMapping: String { let string = CFStringCreateMutable(kCFAllocatorSystemDefault, 0)! CFStringReplaceAll(string, self._cfObject) CFStringNormalize(string, kCFStringNormalizationFormKC) return string._swiftObject } - open func components(separatedBy separator: String) -> [String] { + public func components(separatedBy separator: String) -> [String] { let len = length var lrange = range(of: separator, options: [], range: NSRange(location: 0, length: len)) if lrange.length == 0 { @@ -1118,7 +1118,7 @@ extension NSString { } } - open func components(separatedBy separator: CharacterSet) -> [String] { + public func components(separatedBy separator: CharacterSet) -> [String] { let len = length var range = rangeOfCharacter(from: separator, options: [], range: NSRange(location: 0, length: len)) if range.length == 0 { @@ -1141,7 +1141,7 @@ extension NSString { } } - open func trimmingCharacters(in set: CharacterSet) -> String { + public func trimmingCharacters(in set: CharacterSet) -> String { let len = length var buf = _NSStringBuffer(string: self, start: 0, end: len) while !buf.isAtEnd, @@ -1168,7 +1168,7 @@ extension NSString { } } - open func padding(toLength newLength: Int, withPad padString: String, startingAt padIndex: Int) -> String { + public func padding(toLength newLength: Int, withPad padString: String, startingAt padIndex: Int) -> String { let len = length if newLength <= len { // The simple cases (truncation) return newLength == len ? _swiftObject : substring(with: NSRange(location: 0, length: newLength)) @@ -1186,7 +1186,7 @@ extension NSString { return mStr._swiftObject } - open func folding(options: CompareOptions = [], locale: Locale?) -> String { + public func folding(options: CompareOptions = [], locale: Locale?) -> String { let string = CFStringCreateMutable(kCFAllocatorSystemDefault, 0)! CFStringReplaceAll(string, self._cfObject) CFStringFold(string, options._cfValue(), locale?._cfObject) @@ -1202,7 +1202,7 @@ extension NSString { return "" } - open func replacingOccurrences(of target: String, with replacement: String, options: CompareOptions = [], range searchRange: NSRange) -> String { + public func replacingOccurrences(of target: String, with replacement: String, options: CompareOptions = [], range searchRange: NSRange) -> String { if options.contains(.regularExpression) { return _stringByReplacingOccurrencesOfRegularExpressionPattern(target, withTemplate: replacement, options: options, range: searchRange) } @@ -1214,17 +1214,17 @@ extension NSString { } } - open func replacingOccurrences(of target: String, with replacement: String) -> String { + public func replacingOccurrences(of target: String, with replacement: String) -> String { return replacingOccurrences(of: target, with: replacement, options: [], range: NSRange(location: 0, length: length)) } - open func replacingCharacters(in range: NSRange, with replacement: String) -> String { + public func replacingCharacters(in range: NSRange, with replacement: String) -> String { let str = mutableCopy(with: nil) as! NSMutableString str.replaceCharacters(in: range, with: replacement) return str._swiftObject } - open func applyingTransform(_ transform: StringTransform, reverse: Bool) -> String? { + public func applyingTransform(_ transform: StringTransform, reverse: Bool) -> String? { let string = CFStringCreateMutable(kCFAllocatorSystemDefault, 0)! CFStringReplaceAll(string, _cfObject) if (CFStringTransform(string, nil, transform.rawValue._cfObject, reverse)) { @@ -1262,11 +1262,11 @@ extension NSString { try data.write(to: url, options: useAuxiliaryFile ? .atomic : []) } - open func write(to url: URL, atomically useAuxiliaryFile: Bool, encoding enc: UInt) throws { + public func write(to url: URL, atomically useAuxiliaryFile: Bool, encoding enc: UInt) throws { try _writeTo(url, useAuxiliaryFile, enc) } - open func write(toFile path: String, atomically useAuxiliaryFile: Bool, encoding enc: UInt) throws { + public func write(toFile path: String, atomically useAuxiliaryFile: Bool, encoding enc: UInt) throws { try _writeTo(URL(fileURLWithPath: path), useAuxiliaryFile, enc) } @@ -1448,6 +1448,9 @@ extension NSString { extension NSString : ExpressibleByStringLiteral { } +@available(*, unavailable) +extension NSMutableString : Sendable { } + open class NSMutableString : NSString { open func replaceCharacters(in range: NSRange, with aString: String) { guard type(of: self) === NSString.self || type(of: self) === NSMutableString.self else { diff --git a/Sources/Foundation/NSStringAPI.swift b/Sources/Foundation/NSStringAPI.swift index 0fc972affb..7eeb93c62e 100644 --- a/Sources/Foundation/NSStringAPI.swift +++ b/Sources/Foundation/NSStringAPI.swift @@ -29,7 +29,7 @@ internal func _persistCString(_ p: UnsafePointer?) -> [CChar]? { } let bytesToCopy = UTF8._nullCodeUnitOffset(in: cString) + 1 // +1 for the terminating NUL let result = [CChar](unsafeUninitializedCapacity: bytesToCopy) { buf, initedCount in - buf.baseAddress!.assign(from: cString, count: bytesToCopy) + buf.baseAddress!.update(from: cString, count: bytesToCopy) initedCount = bytesToCopy } return result diff --git a/Sources/Foundation/NSSwiftRuntime.swift b/Sources/Foundation/NSSwiftRuntime.swift index 6d1aa0d372..cc75330661 100644 --- a/Sources/Foundation/NSSwiftRuntime.swift +++ b/Sources/Foundation/NSSwiftRuntime.swift @@ -92,6 +92,9 @@ extension ObjCBool : CustomStringConvertible { } #endif +@available(*, unavailable) +extension __NSCFType : Sendable { } + @usableFromInline internal class __NSCFType : NSObject { private var _cfinfo : _CFInfo @@ -403,6 +406,9 @@ internal enum _NSNonfileURLContentLoader { } } +@available(*, unavailable) +extension _NSCFXMLBridgeForFoundationXMLUseOnly : Sendable { } + public struct _NSCFXMLBridgeForFoundationXMLUseOnly { public var originalBridge: UnsafeMutableRawPointer public var CFArrayGetCount: UnsafeMutableRawPointer diff --git a/Sources/Foundation/NSTextCheckingResult.swift b/Sources/Foundation/NSTextCheckingResult.swift index d55176dc78..fa8528915a 100644 --- a/Sources/Foundation/NSTextCheckingResult.swift +++ b/Sources/Foundation/NSTextCheckingResult.swift @@ -11,7 +11,7 @@ /* NSTextCheckingType in this project is limited to regular expressions. */ extension NSTextCheckingResult { - public struct CheckingType : OptionSet { + public struct CheckingType : OptionSet, Sendable { public let rawValue: UInt64 public init(rawValue: UInt64) { self.rawValue = rawValue } @@ -19,6 +19,9 @@ extension NSTextCheckingResult { } } +@available(*, unavailable) +extension NSTextCheckingResult : Sendable { } + open class NSTextCheckingResult: NSObject, NSCopying, NSSecureCoding { public override init() { @@ -205,7 +208,7 @@ extension NSTextCheckingResult.CheckingType { public static let transitInformation = NSTextCheckingResult.CheckingType(rawValue: 1 << 12) } -public struct NSTextCheckingKey: RawRepresentable, Hashable { +public struct NSTextCheckingKey: RawRepresentable, Hashable, Sendable { public var rawValue: String init(_ string: String) { @@ -234,59 +237,62 @@ extension NSTextCheckingKey { @available(*, unavailable, message: "These types of results cannot be constructed in swift-corelibs-foundation") extension NSTextCheckingResult { - open class func orthographyCheckingResult(range: NSRange, orthography: NSOrthography) -> NSTextCheckingResult { + public class func orthographyCheckingResult(range: NSRange, orthography: NSOrthography) -> NSTextCheckingResult { NSUnsupported() } - open class func spellCheckingResult(range: NSRange) -> NSTextCheckingResult { + public class func spellCheckingResult(range: NSRange) -> NSTextCheckingResult { NSUnsupported() } - open class func grammarCheckingResult(range: NSRange, details: [[String : Any]]) -> NSTextCheckingResult { + public class func grammarCheckingResult(range: NSRange, details: [[String : Any]]) -> NSTextCheckingResult { NSUnsupported() } - open class func dateCheckingResult(range: NSRange, date: Date) -> NSTextCheckingResult { + public class func dateCheckingResult(range: NSRange, date: Date) -> NSTextCheckingResult { NSUnsupported() } - open class func dateCheckingResult(range: NSRange, date: Date, timeZone: TimeZone, duration: TimeInterval) -> NSTextCheckingResult { + public class func dateCheckingResult(range: NSRange, date: Date, timeZone: TimeZone, duration: TimeInterval) -> NSTextCheckingResult { NSUnsupported() } - open class func addressCheckingResult(range: NSRange, components: [NSTextCheckingKey : String]) -> NSTextCheckingResult { + public class func addressCheckingResult(range: NSRange, components: [NSTextCheckingKey : String]) -> NSTextCheckingResult { NSUnsupported() } - open class func linkCheckingResult(range: NSRange, url: URL) -> NSTextCheckingResult { + public class func linkCheckingResult(range: NSRange, url: URL) -> NSTextCheckingResult { NSUnsupported() } - open class func quoteCheckingResult(range: NSRange, replacementString: String) -> NSTextCheckingResult { + public class func quoteCheckingResult(range: NSRange, replacementString: String) -> NSTextCheckingResult { NSUnsupported() } - open class func dashCheckingResult(range: NSRange, replacementString: String) -> NSTextCheckingResult { + public class func dashCheckingResult(range: NSRange, replacementString: String) -> NSTextCheckingResult { NSUnsupported() } - open class func replacementCheckingResult(range: NSRange, replacementString: String) -> NSTextCheckingResult { + public class func replacementCheckingResult(range: NSRange, replacementString: String) -> NSTextCheckingResult { NSUnsupported() } - open class func correctionCheckingResult(range: NSRange, replacementString: String, alternativeStrings: [String]) -> NSTextCheckingResult { + public class func correctionCheckingResult(range: NSRange, replacementString: String, alternativeStrings: [String]) -> NSTextCheckingResult { NSUnsupported() } - open class func phoneNumberCheckingResult(range: NSRange, phoneNumber: String) -> NSTextCheckingResult { + public class func phoneNumberCheckingResult(range: NSRange, phoneNumber: String) -> NSTextCheckingResult { NSUnsupported() } - open class func transitInformationCheckingResult(range: NSRange, components: [NSTextCheckingKey : String]) -> NSTextCheckingResult { + public class func transitInformationCheckingResult(range: NSRange, components: [NSTextCheckingKey : String]) -> NSTextCheckingResult { NSUnsupported() } } +@available(*, unavailable) +extension NSOrthography : Sendable { } + @available(*, deprecated, message: "NSOrthography is not available in swift-corelibs-foundation") open class NSOrthography: NSObject, NSCopying, NSSecureCoding { @available(*, unavailable, message: "NSOrthography is not available in swift-corelibs-foundation") diff --git a/Sources/Foundation/NSTimeZone.swift b/Sources/Foundation/NSTimeZone.swift index 97480e96af..80f8947700 100644 --- a/Sources/Foundation/NSTimeZone.swift +++ b/Sources/Foundation/NSTimeZone.swift @@ -10,6 +10,9 @@ @_implementationOnly import CoreFoundation @_spi(SwiftCorelibsFoundation) @_exported import FoundationEssentials +@available(*, unavailable) +extension NSTimeZone : Sendable { } + open class NSTimeZone : NSObject, NSCopying, NSSecureCoding, NSCoding { var _timeZone: TimeZone diff --git a/Sources/Foundation/NSURL.swift b/Sources/Foundation/NSURL.swift index e12d527907..8727617fc6 100644 --- a/Sources/Foundation/NSURL.swift +++ b/Sources/Foundation/NSURL.swift @@ -79,7 +79,7 @@ internal func _pathComponents(_ path: String?) -> [String]? { return result } -open class NSURL : NSObject, NSSecureCoding, NSCopying { +open class NSURL : NSObject, NSSecureCoding, NSCopying, @unchecked Sendable { typealias CFType = CFURL internal var _base = _CFInfo(typeID: CFURLGetTypeID()) internal var _flags : UInt32 = 0 @@ -733,32 +733,32 @@ extension NSCharacterSet { // Predefined character sets for the six URL components and subcomponents which allow percent encoding. These character sets are passed to -stringByAddingPercentEncodingWithAllowedCharacters:. // Returns a character set containing the characters allowed in an URL's user subcomponent. - open class var urlUserAllowed: CharacterSet { + public class var urlUserAllowed: CharacterSet { return _CFURLComponentsGetURLUserAllowedCharacterSet()._swiftObject } // Returns a character set containing the characters allowed in an URL's password subcomponent. - open class var urlPasswordAllowed: CharacterSet { + public class var urlPasswordAllowed: CharacterSet { return _CFURLComponentsGetURLPasswordAllowedCharacterSet()._swiftObject } // Returns a character set containing the characters allowed in an URL's host subcomponent. - open class var urlHostAllowed: CharacterSet { + public class var urlHostAllowed: CharacterSet { return _CFURLComponentsGetURLHostAllowedCharacterSet()._swiftObject } // Returns a character set containing the characters allowed in an URL's path component. ';' is a legal path character, but it is recommended that it be percent-encoded for best compatibility with NSURL (-stringByAddingPercentEncodingWithAllowedCharacters: will percent-encode any ';' characters if you pass the URLPathAllowedCharacterSet). - open class var urlPathAllowed: CharacterSet { + public class var urlPathAllowed: CharacterSet { return _CFURLComponentsGetURLPathAllowedCharacterSet()._swiftObject } // Returns a character set containing the characters allowed in an URL's query component. - open class var urlQueryAllowed: CharacterSet { + public class var urlQueryAllowed: CharacterSet { return _CFURLComponentsGetURLQueryAllowedCharacterSet()._swiftObject } // Returns a character set containing the characters allowed in an URL's fragment component. - open class var urlFragmentAllowed: CharacterSet { + public class var urlFragmentAllowed: CharacterSet { return _CFURLComponentsGetURLFragmentAllowedCharacterSet()._swiftObject } } @@ -766,12 +766,12 @@ extension NSCharacterSet { extension NSString { // Returns a new string made from the receiver by replacing all characters not in the allowedCharacters set with percent encoded characters. UTF-8 encoding is used to determine the correct percent encoded characters. Entire URL strings cannot be percent-encoded. This method is intended to percent-encode an URL component or subcomponent string, NOT the entire URL string. Any characters in allowedCharacters outside of the 7-bit ASCII range are ignored. - open func addingPercentEncoding(withAllowedCharacters allowedCharacters: CharacterSet) -> String? { + public func addingPercentEncoding(withAllowedCharacters allowedCharacters: CharacterSet) -> String? { return _CFStringCreateByAddingPercentEncodingWithAllowedCharacters(kCFAllocatorSystemDefault, self._cfObject, allowedCharacters._cfObject)._swiftObject } // Returns a new string made from the receiver by replacing all percent encoded sequences with the matching UTF-8 characters. - open var removingPercentEncoding: String? { + public var removingPercentEncoding: String? { return _CFStringCreateByRemovingPercentEncoding(kCFAllocatorSystemDefault, self._cfObject)?._swiftObject } } @@ -780,7 +780,7 @@ extension NSURL { /* The following methods work on the path portion of a URL in the same manner that the NSPathUtilities methods on NSString do. */ - open class func fileURL(withPathComponents components: [String]) -> URL? { + public class func fileURL(withPathComponents components: [String]) -> URL? { let path = NSString.path(withComponents: components) if components.last == "/" { return URL(fileURLWithPath: path, isDirectory: true) @@ -826,11 +826,11 @@ extension NSURL { return result } - open var pathComponents: [String]? { + public var pathComponents: [String]? { return _pathComponents(path) } - open var lastPathComponent: String? { + public var lastPathComponent: String? { guard let fixedSelf = _pathByFixingSlashes() else { return nil } @@ -841,7 +841,7 @@ extension NSURL { return String(fixedSelf.suffix(from: fixedSelf._startOfLastPathComponent)) } - open var pathExtension: String? { + public var pathExtension: String? { guard let fixedSelf = _pathByFixingSlashes() else { return nil } @@ -856,7 +856,7 @@ extension NSURL { } } - open func appendingPathComponent(_ pathComponent: String) -> URL? { + public func appendingPathComponent(_ pathComponent: String) -> URL? { var result : URL? = appendingPathComponent(pathComponent, isDirectory: false) // File URLs can't be handled on WASI without file system access @@ -876,31 +876,31 @@ extension NSURL { return result } - open func appendingPathComponent(_ pathComponent: String, isDirectory: Bool) -> URL? { + public func appendingPathComponent(_ pathComponent: String, isDirectory: Bool) -> URL? { return CFURLCreateCopyAppendingPathComponent(kCFAllocatorSystemDefault, _cfObject, pathComponent._cfObject, isDirectory)?._swiftObject } - open var deletingLastPathComponent: URL? { + public var deletingLastPathComponent: URL? { return CFURLCreateCopyDeletingLastPathComponent(kCFAllocatorSystemDefault, _cfObject)?._swiftObject } - open func appendingPathExtension(_ pathExtension: String) -> URL? { + public func appendingPathExtension(_ pathExtension: String) -> URL? { return CFURLCreateCopyAppendingPathExtension(kCFAllocatorSystemDefault, _cfObject, pathExtension._cfObject)?._swiftObject } - open var deletingPathExtension: URL? { + public var deletingPathExtension: URL? { return CFURLCreateCopyDeletingPathExtension(kCFAllocatorSystemDefault, _cfObject)?._swiftObject } /* The following methods work only on `file:` scheme URLs; for non-`file:` scheme URLs, these methods return the URL unchanged. */ - open var standardizingPath: URL? { + public var standardizingPath: URL? { // Documentation says it should expand initial tilde, but it does't do this on OS X. // In remaining cases it works just like URLByResolvingSymlinksInPath. return _resolveSymlinksInPath(excludeSystemDirs: true, preserveDirectoryFlag: true) } - open var resolvingSymlinksInPath: URL? { + public var resolvingSymlinksInPath: URL? { return _resolveSymlinksInPath(excludeSystemDirs: true) } @@ -1070,7 +1070,7 @@ internal func _CFSwiftURLCopyResourcePropertyForKey(_ url: CFTypeRef, _ key: CFS return true } catch { if let errorPointer = errorPointer { - let nsError = (error as? NSError) ?? NSError(domain: NSCocoaErrorDomain, code: CocoaError.featureUnsupported.rawValue) + let nsError = NSError(domain: NSCocoaErrorDomain, code: CocoaError.featureUnsupported.rawValue) let cfError = Unmanaged.passRetained(nsError._cfObject) errorPointer.pointee = cfError } @@ -1097,7 +1097,7 @@ internal func _CFSwiftURLCopyResourcePropertiesForKeys(_ url: CFTypeRef, _ keys: return .passRetained(finalDictionary._cfObject) } catch { if let errorPointer = errorPointer { - let nsError = (error as? NSError) ?? NSError(domain: NSCocoaErrorDomain, code: CocoaError.featureUnsupported.rawValue) + let nsError = NSError(domain: NSCocoaErrorDomain, code: CocoaError.featureUnsupported.rawValue) let cfError = Unmanaged.passRetained(nsError._cfObject) errorPointer.pointee = cfError } @@ -1113,7 +1113,7 @@ internal func _CFSwiftURLSetResourcePropertyForKey(_ url: CFTypeRef, _ key: CFSt return true } catch { if let errorPointer = errorPointer { - let nsError = (error as? NSError) ?? NSError(domain: NSCocoaErrorDomain, code: CocoaError.featureUnsupported.rawValue) + let nsError = NSError(domain: NSCocoaErrorDomain, code: CocoaError.featureUnsupported.rawValue) let cfError = Unmanaged.passRetained(nsError._cfObject) errorPointer.pointee = cfError } @@ -1136,7 +1136,7 @@ internal func _CFSwiftURLSetResourcePropertiesForKeys(_ url: CFTypeRef, _ proper return true } catch { if let errorPointer = errorPointer { - let nsError = (error as? NSError) ?? NSError(domain: NSCocoaErrorDomain, code: CocoaError.featureUnsupported.rawValue) + let nsError = NSError(domain: NSCocoaErrorDomain, code: CocoaError.featureUnsupported.rawValue) let cfError = Unmanaged.passRetained(nsError._cfObject) errorPointer.pointee = cfError } @@ -1163,7 +1163,7 @@ internal func _CFSwiftURLResourceIsReachable(_ url: CFTypeRef, _ errorPointer: U return reachable ? true : false } catch { if let errorPointer = errorPointer { - let nsError = (error as? NSError) ?? NSError(domain: NSCocoaErrorDomain, code: CocoaError.featureUnsupported.rawValue) + let nsError = NSError(domain: NSCocoaErrorDomain, code: CocoaError.featureUnsupported.rawValue) let cfError = Unmanaged.passRetained(nsError._cfObject) errorPointer.pointee = cfError } diff --git a/Sources/Foundation/NSURLComponents.swift b/Sources/Foundation/NSURLComponents.swift index 87583cd27e..30564159f3 100644 --- a/Sources/Foundation/NSURLComponents.swift +++ b/Sources/Foundation/NSURLComponents.swift @@ -9,6 +9,8 @@ @_implementationOnly import CoreFoundation +@available(*, unavailable) +extension NSURLComponents : Sendable { } open class NSURLComponents: NSObject, NSCopying { private let _componentsStorage: AnyObject! diff --git a/Sources/Foundation/NSURLQueryItem.swift b/Sources/Foundation/NSURLQueryItem.swift index df76edb45c..3d4871320a 100644 --- a/Sources/Foundation/NSURLQueryItem.swift +++ b/Sources/Foundation/NSURLQueryItem.swift @@ -9,10 +9,10 @@ @_exported import FoundationEssentials // NSURLQueryItem encapsulates a single query name-value pair. The name and value strings of a query name-value pair are not percent encoded. For use with the NSURLComponents queryItems property. -open class NSURLQueryItem: NSObject, NSSecureCoding, NSCopying { +open class NSURLQueryItem: NSObject, NSSecureCoding, NSCopying, @unchecked Sendable { - open private(set) var name: String - open private(set) var value: String? + public let name: String + public let value: String? public init(name: String, value: String?) { self.name = name diff --git a/Sources/Foundation/NSUUID.swift b/Sources/Foundation/NSUUID.swift index 4bc4dd98ad..20509fe130 100644 --- a/Sources/Foundation/NSUUID.swift +++ b/Sources/Foundation/NSUUID.swift @@ -10,7 +10,7 @@ @_implementationOnly import CoreFoundation -open class NSUUID : NSObject, NSCopying, NSSecureCoding, NSCoding { +open class NSUUID : NSObject, NSCopying, NSSecureCoding, NSCoding, @unchecked Sendable { internal var buffer = UnsafeMutablePointer.allocate(capacity: 16) deinit { diff --git a/Sources/Foundation/NSValue.swift b/Sources/Foundation/NSValue.swift index de2ce70e7b..fad1680a1e 100644 --- a/Sources/Foundation/NSValue.swift +++ b/Sources/Foundation/NSValue.swift @@ -7,7 +7,7 @@ // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // -open class NSValue : NSObject, NSCopying, NSSecureCoding, NSCoding { +open class NSValue : NSObject, NSCopying, NSSecureCoding, NSCoding, @unchecked Sendable { open func getValue(_ value: UnsafeMutableRawPointer) { NSRequiresConcreteImplementation() diff --git a/Sources/Foundation/Notification.swift b/Sources/Foundation/Notification.swift index 2afd2bd29d..78dff8e7e6 100644 --- a/Sources/Foundation/Notification.swift +++ b/Sources/Foundation/Notification.swift @@ -10,6 +10,8 @@ // //===----------------------------------------------------------------------===// +@available(*, unavailable) +extension Notification : Sendable { } /** `Notification` encapsulates information broadcast to observers via a `NotificationCenter`. diff --git a/Sources/Foundation/NotificationQueue.swift b/Sources/Foundation/NotificationQueue.swift index bf3e5d8e39..2f83c57e1a 100644 --- a/Sources/Foundation/NotificationQueue.swift +++ b/Sources/Foundation/NotificationQueue.swift @@ -12,13 +12,13 @@ extension NotificationQueue { - public enum PostingStyle : UInt { + public enum PostingStyle : UInt, Sendable { case whenIdle = 1 case asap = 2 case now = 3 } - public struct NotificationCoalescing : OptionSet { + public struct NotificationCoalescing : OptionSet, Sendable { public let rawValue : UInt public init(rawValue: UInt) { self.rawValue = rawValue } @@ -28,6 +28,9 @@ extension NotificationQueue { } } +@available(*, unavailable) +extension NotificationQueue : Sendable { } + open class NotificationQueue: NSObject { internal typealias NotificationQueueList = NSMutableArray diff --git a/Sources/Foundation/NumberFormatter.swift b/Sources/Foundation/NumberFormatter.swift index 1fb0739cb4..d3d6665511 100644 --- a/Sources/Foundation/NumberFormatter.swift +++ b/Sources/Foundation/NumberFormatter.swift @@ -8,9 +8,10 @@ // @_implementationOnly import CoreFoundation +internal import Synchronization extension NumberFormatter { - public enum Style : UInt { + public enum Style : UInt, Sendable { case none = 0 case decimal = 1 case currency = 2 @@ -23,14 +24,14 @@ extension NumberFormatter { case currencyAccounting = 10 } - public enum PadPosition : UInt { + public enum PadPosition : UInt, Sendable { case beforePrefix case afterPrefix case beforeSuffix case afterSuffix } - public enum RoundingMode : UInt { + public enum RoundingMode : UInt, Sendable { case ceiling case floor case down @@ -41,28 +42,30 @@ extension NumberFormatter { } } -open class NumberFormatter : Formatter { - +open class NumberFormatter : Formatter, @unchecked Sendable { typealias CFType = CFNumberFormatter - private final var _currentCfFormatter: CFType? + private let _formatter: Mutex = .init(nil) + private final var _cfFormatter: CFType { - if let obj = _currentCfFormatter { - return obj - } else { - let numberStyle = CFNumberFormatterStyle(rawValue: CFIndex(self.numberStyle.rawValue))! - - let obj = CFNumberFormatterCreate(kCFAllocatorSystemDefault, locale._cfObject, numberStyle)! - _setFormatterAttributes(obj) - if _positiveFormat != nil || _negativeFormat != nil { - var format = _positiveFormat ?? "#" - if let negative = _negativeFormat { - format.append(";") - format.append(negative) + _formatter.withLock { _currentCfFormatter in + if let obj = _currentCfFormatter { + return obj + } else { + let numberStyle = CFNumberFormatterStyle(rawValue: CFIndex(self.numberStyle.rawValue))! + + let obj = CFNumberFormatterCreate(kCFAllocatorSystemDefault, locale._cfObject, numberStyle)! + _setFormatterAttributes(obj) + if _positiveFormat != nil || _negativeFormat != nil { + var format = _positiveFormat ?? "#" + if let negative = _negativeFormat { + format.append(";") + format.append(negative) + } + CFNumberFormatterSetFormat(obj, format._cfObject) } - CFNumberFormatterSetFormat(obj, format._cfObject) + _currentCfFormatter = obj + return obj } - _currentCfFormatter = obj - return obj } } @@ -189,7 +192,9 @@ open class NumberFormatter : Formatter { } private func _reset() { - _currentCfFormatter = nil + _formatter.withLock { + $0 = nil + } } private final func _setFormatterAttributes(_ formatter: CFNumberFormatter) { diff --git a/Sources/Foundation/Operation.swift b/Sources/Foundation/Operation.swift index 2f8fc54d4e..4b41911977 100644 --- a/Sources/Foundation/Operation.swift +++ b/Sources/Foundation/Operation.swift @@ -53,7 +53,7 @@ extension QualityOfService { } } -open class Operation : NSObject { +open class Operation : NSObject, @unchecked Sendable { struct PointerHashedUnmanagedBox: Hashable { var contents: Unmanaged func hash(into hasher: inout Hasher) { @@ -63,7 +63,7 @@ open class Operation : NSObject { return lhs.contents.toOpaque() == rhs.contents.toOpaque() } } - enum __NSOperationState : UInt8 { + enum __NSOperationState : UInt8, Sendable { case initialized = 0x00 case enqueuing = 0x48 case enqueued = 0x50 @@ -631,7 +631,7 @@ extension Operation { } extension Operation { - public enum QueuePriority : Int { + public enum QueuePriority : Int, Sendable { case veryLow = -8 case low = -4 case normal = 0 @@ -650,7 +650,7 @@ extension Operation { } } -open class BlockOperation : Operation { +open class BlockOperation : Operation, @unchecked Sendable { var _block: (() -> Void)? var _executionBlocks: [() -> Void]? @@ -658,12 +658,12 @@ open class BlockOperation : Operation { } - public convenience init(block: @escaping () -> Void) { + public convenience init(block: @Sendable @escaping () -> Void) { self.init() _block = block } - open func addExecutionBlock(_ block: @escaping () -> Void) { + open func addExecutionBlock(_ block: @Sendable @escaping () -> Void) { if isExecuting || isFinished { fatalError("blocks cannot be added after the operation has started executing or finished") } @@ -709,7 +709,7 @@ open class BlockOperation : Operation { } } -internal final class _BarrierOperation : Operation { +internal final class _BarrierOperation : Operation, @unchecked Sendable { var _block: (() -> Void)? init(_ block: @escaping () -> Void) { _block = block @@ -725,7 +725,7 @@ internal final class _BarrierOperation : Operation { } } -internal final class _OperationQueueProgress : Progress { +internal final class _OperationQueueProgress : Progress, @unchecked Sendable { var queue: Unmanaged? let lock = NSLock() @@ -758,7 +758,7 @@ extension OperationQueue { } @available(macOS 10.5, *) -open class OperationQueue : NSObject, ProgressReporting { +open class OperationQueue : NSObject, ProgressReporting, @unchecked Sendable { let __queueLock = NSLock() let __atomicLoad = NSLock() var __firstOperation: Unmanaged? @@ -1270,7 +1270,7 @@ open class OperationQueue : NSObject, ProgressReporting { } } - open func addOperation(_ block: @escaping () -> Void) { + open func addOperation(_ block: @Sendable @escaping () -> Void) { let op = BlockOperation(block: block) if let qos = __propertyQoS { op.qualityOfService = qos @@ -1278,7 +1278,7 @@ open class OperationQueue : NSObject, ProgressReporting { addOperation(op) } - open func addBarrierBlock(_ barrier: @escaping () -> Void) { + open func addBarrierBlock(_ barrier: @Sendable @escaping () -> Void) { var queue: DispatchQueue? _lock() if let op = __firstOperation { @@ -1425,7 +1425,7 @@ extension OperationQueue { // These two functions are inherently a race condition and should be avoided if possible @available(macOS, introduced: 10.5, deprecated: 100000, message: "access to operations is inherently a race condition, it should not be used. For barrier style behaviors please use addBarrierBlock: instead") - open var operations: [Operation] { + public var operations: [Operation] { get { return _operations(includingBarriers: false) } @@ -1433,7 +1433,7 @@ extension OperationQueue { @available(macOS, introduced: 10.6, deprecated: 100000) - open var operationCount: Int { + public var operationCount: Int { get { return _operationCount } diff --git a/Sources/Foundation/PersonNameComponents.swift b/Sources/Foundation/PersonNameComponents.swift index 192ebbcb67..df70d3fbcd 100644 --- a/Sources/Foundation/PersonNameComponents.swift +++ b/Sources/Foundation/PersonNameComponents.swift @@ -10,71 +10,85 @@ // //===----------------------------------------------------------------------===// -public struct PersonNameComponents : ReferenceConvertible, Hashable, Equatable, _MutableBoxing { +public struct PersonNameComponents : ReferenceConvertible, Hashable, Equatable, Sendable { public typealias ReferenceType = NSPersonNameComponents - internal var _handle: _MutableHandle public init() { - _handle = _MutableHandle(adoptingReference: NSPersonNameComponents()) + _phoneticRepresentation = .none } - fileprivate init(reference: NSPersonNameComponents) { - _handle = _MutableHandle(reference: reference) + @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) + public init( + namePrefix: String? = nil, + givenName: String? = nil, + middleName: String? = nil, + familyName: String? = nil, + nameSuffix: String? = nil, + nickname: String? = nil, + phoneticRepresentation: PersonNameComponents? = nil) { + self.init() + self.namePrefix = namePrefix + self.givenName = givenName + self.middleName = middleName + self.familyName = familyName + self.nameSuffix = nameSuffix + self.nickname = nickname + self.phoneticRepresentation = phoneticRepresentation } - - /* The below examples all assume the full name Dr. Johnathan Maple Appleseed Esq., nickname "Johnny" */ - /* Pre-nominal letters denoting title, salutation, or honorific, e.g. Dr., Mr. */ - public var namePrefix: String? { - get { return _handle.map { $0.namePrefix } } - set { _applyMutation { $0.namePrefix = newValue } } - } + /// Assuming the full name is: Dr. Johnathan Maple Appleseed Esq., nickname "Johnny", pre-nominal letters denoting title, salutation, or honorific, e.g. Dr., Mr. + public var namePrefix: String? - /* Name bestowed upon an individual by one's parents, e.g. Johnathan */ - public var givenName: String? { - get { return _handle.map { $0.givenName } } - set { _applyMutation { $0.givenName = newValue } } - } + /// Assuming the full name is: Dr. Johnathan Maple Appleseed Esq., nickname "Johnny", name bestowed upon an individual by one's parents, e.g. Johnathan + public var givenName: String? - /* Secondary given name chosen to differentiate those with the same first name, e.g. Maple */ - public var middleName: String? { - get { return _handle.map { $0.middleName } } - set { _applyMutation { $0.middleName = newValue } } - } + /// Assuming the full name is: Dr. Johnathan Maple Appleseed Esq., nickname "Johnny", secondary given name chosen to differentiate those with the same first name, e.g. Maple + public var middleName: String? - /* Name passed from one generation to another to indicate lineage, e.g. Appleseed */ - public var familyName: String? { - get { return _handle.map { $0.familyName } } - set { _applyMutation { $0.familyName = newValue } } - } + /// Assuming the full name is: Dr. Johnathan Maple Appleseed Esq., nickname "Johnny", name passed from one generation to another to indicate lineage, e.g. Appleseed + public var familyName: String? - /* Post-nominal letters denoting degree, accreditation, or other honor, e.g. Esq., Jr., Ph.D. */ - public var nameSuffix: String? { - get { return _handle.map { $0.nameSuffix } } - set { _applyMutation { $0.nameSuffix = newValue } } - } + /// Assuming the full name is: Dr. Johnathan Maple Appleseed Esq., nickname "Johnny", post-nominal letters denoting degree, accreditation, or other honor, e.g. Esq., Jr., Ph.D. + public var nameSuffix: String? - /* Name substituted for the purposes of familiarity, e.g. "Johnny"*/ - public var nickname: String? { - get { return _handle.map { $0.nickname } } - set { _applyMutation { $0.nickname = newValue } } - } + /// Assuming the full name is: Dr. Johnathan Maple Appleseed Esq., nickname "Johnny", name substituted for the purposes of familiarity, e.g. "Johnny" + public var nickname: String? - /* Each element of the phoneticRepresentation should correspond to an element of the original PersonNameComponents instance. - The phoneticRepresentation of the phoneticRepresentation object itself will be ignored. nil by default, must be instantiated. - */ + /// Each element of the phoneticRepresentation should correspond to an element of the original PersonNameComponents instance. + /// The phoneticRepresentation of the phoneticRepresentation object itself will be ignored. nil by default, must be instantiated. public var phoneticRepresentation: PersonNameComponents? { - get { return _handle.map { $0.phoneticRepresentation } } - set { _applyMutation { $0.phoneticRepresentation = newValue } } + get { + switch _phoneticRepresentation { + case .wrapped(let personNameComponents): + personNameComponents + case .none: + nil + } + } + set { + switch newValue { + case .some(let pnc): + _phoneticRepresentation = .wrapped(pnc) + case .none: + _phoneticRepresentation = .none + } + } } - public func hash(into hasher: inout Hasher) { - hasher.combine(_handle.map { $0 }) - } + private var _phoneticRepresentation: PhoneticRepresentation +} - public static func ==(lhs : PersonNameComponents, rhs: PersonNameComponents) -> Bool { - // Don't copy references here; no one should be storing anything - return lhs._handle._uncopiedReference().isEqual(rhs._handle._uncopiedReference()) +/// Allows for `PersonNameComponents` to store a `PersonNameComponents` for its phonetic representation. +private enum PhoneticRepresentation : Hashable, Equatable { + indirect case wrapped(PersonNameComponents) + case none + + func hash(into hasher: inout Hasher) { + let opt : PersonNameComponents? = switch self { + case .wrapped(let pnc): pnc + case .none: nil + } + hasher.combine(opt) } } @@ -109,7 +123,7 @@ extension PersonNameComponents : _ObjectiveCBridgeable { @_semantics("convertToObjectiveC") public func _bridgeToObjectiveC() -> NSPersonNameComponents { - return _handle._copiedReference() + return NSPersonNameComponents(pnc: self) } public static func _forceBridgeFromObjectiveC(_ personNameComponents: NSPersonNameComponents, result: inout PersonNameComponents?) { @@ -119,7 +133,7 @@ extension PersonNameComponents : _ObjectiveCBridgeable { } public static func _conditionallyBridgeFromObjectiveC(_ personNameComponents: NSPersonNameComponents, result: inout PersonNameComponents?) -> Bool { - result = PersonNameComponents(reference: personNameComponents) + result = personNameComponents._pnc return true } diff --git a/Sources/Foundation/PersonNameComponentsFormatter.swift b/Sources/Foundation/PersonNameComponentsFormatter.swift index 5226249b79..0da9410d47 100644 --- a/Sources/Foundation/PersonNameComponentsFormatter.swift +++ b/Sources/Foundation/PersonNameComponentsFormatter.swift @@ -8,8 +8,9 @@ // +@available(*, deprecated, message: "Person name components formatting isn't available in swift-corelibs-foundation") extension PersonNameComponentsFormatter { - public enum Style : Int { + public enum Style : Int, Sendable { case `default` @@ -27,7 +28,7 @@ extension PersonNameComponentsFormatter { case abbreviated } - public struct Options : OptionSet { + public struct Options : OptionSet, Sendable { public let rawValue : UInt public init(rawValue: UInt) { self.rawValue = rawValue } @@ -40,7 +41,7 @@ extension PersonNameComponentsFormatter { } @available(*, deprecated, message: "Person name components formatting isn't available in swift-corelibs-foundation") -open class PersonNameComponentsFormatter : Formatter { +open class PersonNameComponentsFormatter : Formatter, @unchecked Sendable { @available(*, unavailable, message: "Person name components formatting isn't available in swift-corelibs-foundation") public required init?(coder: NSCoder) { diff --git a/Sources/Foundation/Port.swift b/Sources/Foundation/Port.swift index f06f95a9fe..1f0d298aab 100644 --- a/Sources/Foundation/Port.swift +++ b/Sources/Foundation/Port.swift @@ -17,8 +17,11 @@ extension Port { public static let didBecomeInvalidNotification = NSNotification.Name(rawValue: "NSPortDidBecomeInvalidNotification") } +@available(*, unavailable) +extension Port : Sendable { } + open class Port : NSObject, NSCopying { - @available(*, deprecated, message: "On Darwin, you can invoke Port() directly to produce a MessagePort. Since MessagePort's functionality is not available in swift-corelibs-foundation, you should not invoke this initializer directly. Subclasses of Port can delegate to this initializer safely.") + /// On Darwin, you can invoke `Port()` directly to produce a `MessagePort`. Since `MessagePort` is not available in swift-corelibs-foundation, you should not invoke this initializer directly. Subclasses of `Port` can delegate to this initializer safely. public override init() { if type(of: self) == Port.self { NSRequiresConcreteImplementation() @@ -75,6 +78,12 @@ open class MessagePort: Port {} @available(*, unavailable, message: "NSMachPort is not available in swift-corelibs-foundation.") open class NSMachPort: Port {} +@available(*, unavailable) +extension MessagePort : Sendable { } + +@available(*, unavailable) +extension NSMachPort : Sendable { } + extension PortDelegate { func handle(_ message: PortMessage) { } } @@ -88,6 +97,9 @@ public protocol PortDelegate: AnyObject { @available(*, unavailable, message: "SocketPort is not available on this platform.") open class SocketPort: Port {} +@available(*, unavailable) +extension SocketPort : Sendable { } + #else #if canImport(Darwin) @@ -393,6 +405,9 @@ fileprivate func __NSFireSocketDatagram(_ socket: CFSocket?, _ type: CFSocketCal me.socketDidReceiveDatagram(socket, type, address, data) } +@available(*, unavailable) +extension SocketPort : Sendable { } + open class SocketPort : Port { struct SocketKind: Hashable { var protocolFamily: Int32 diff --git a/Sources/Foundation/PortMessage.swift b/Sources/Foundation/PortMessage.swift index aa0af1dfc3..ba466c085d 100644 --- a/Sources/Foundation/PortMessage.swift +++ b/Sources/Foundation/PortMessage.swift @@ -7,6 +7,8 @@ // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // +@available(*, unavailable) +extension PortMessage : Sendable { } open class PortMessage : NSObject, NSCopying { public init(sendPort: Port?, receivePort replyPort: Port?, components: [AnyObject]?) { diff --git a/Sources/Foundation/Process.swift b/Sources/Foundation/Process.swift index 76aeed1b4c..bad92d2374 100644 --- a/Sources/Foundation/Process.swift +++ b/Sources/Foundation/Process.swift @@ -21,7 +21,7 @@ import Darwin #endif extension Process { - public enum TerminationReason : Int { + public enum TerminationReason : Int, Sendable { case exit case uncaughtSignal } @@ -220,7 +220,7 @@ private func quoteWindowsCommandLine(_ commandLine: [String]) -> String { } #endif -open class Process: NSObject { +open class Process: NSObject, @unchecked Sendable { private static func setup() { struct Once { static var done = false @@ -953,7 +953,7 @@ open class Process: NSObject { defer { // Reset the previous working directory path. - fileManager.changeCurrentDirectoryPath(previousDirectoryPath) + _ = fileManager.changeCurrentDirectoryPath(previousDirectoryPath) } // Launch @@ -1110,11 +1110,11 @@ open class Process: NSObject { /* A block to be invoked when the process underlying the Process terminates. Setting the block to nil is valid, and stops the previous block from being invoked, as long as it hasn't started in any way. The Process is passed as the argument to the block so the block does not have to capture, and thus retain, it. The block is copied when set. Only one termination handler block can be set at any time. The execution context in which the block is invoked is undefined. If the Process has already finished, the block is executed immediately/soon (not necessarily on the current thread). If a terminationHandler is set on an Process, the ProcessDidTerminateNotification notification is not posted for that process. Also note that -waitUntilExit won't wait until the terminationHandler has been fully executed. You cannot use this property in a concrete subclass of Process which hasn't been updated to include an implementation of the storage and use of it. */ - open var terminationHandler: ((Process) -> Void)? + open var terminationHandler: (@Sendable (Process) -> Void)? open var qualityOfService: QualityOfService = .default // read-only after the process is launched - open class func run(_ url: URL, arguments: [String], terminationHandler: ((Process) -> Void)? = nil) throws -> Process { + open class func run(_ url: URL, arguments: [String], terminationHandler: (@Sendable (Process) -> Void)? = nil) throws -> Process { let process = Process() process.executableURL = url process.arguments = arguments @@ -1140,7 +1140,7 @@ open class Process: NSObject { let currentRunLoop = RunLoop.current let runRunLoop : () -> Void = (currentRunLoop == self.runLoop) - ? { currentRunLoop.run(mode: .default, before: Date(timeIntervalSinceNow: runInterval)) } + ? { _ = currentRunLoop.run(mode: .default, before: Date(timeIntervalSinceNow: runInterval)) } : { currentRunLoop.run(until: Date(timeIntervalSinceNow: runInterval)) } // update .runLoop to allow early wakeup triggered by terminateRunLoop. self.runLoop = currentRunLoop diff --git a/Sources/Foundation/Progress.swift b/Sources/Foundation/Progress.swift index efa278899f..0e9552807f 100644 --- a/Sources/Foundation/Progress.swift +++ b/Sources/Foundation/Progress.swift @@ -29,7 +29,7 @@ import Dispatch - note: In swift-corelibs-foundation, Key Value Observing is not yet available. */ -open class Progress : NSObject { +open class Progress : NSObject, @unchecked Sendable { private weak var _parent : Progress? private var _children : Set @@ -45,6 +45,7 @@ open class Progress : NSObject { /// The instance of `Progress` associated with the current thread by a previous invocation of `becomeCurrent(withPendingUnitCount:)`, if any. /// /// The purpose of this per-thread value is to allow code that does work to usefully report progress even when it is widely separated from the code that actually presents progress to the user, without requiring layers of intervening code to pass the instance of `Progress` through. Using the result of invoking this directly will often not be the right thing to do, because the invoking code will often not even know what units of work the current progress object deals in. Using `Progress(withTotalUnitCount:)` to create a child `Progress` object and then using that to report progress makes more sense in that situation. + @available(*, noasync) open class func current() -> Progress? { return (Thread.current.threadDictionary[Progress._tsdKey] as? _ProgressTSD)?.currentProgress } @@ -74,6 +75,7 @@ open class Progress : NSObject { } /// Initializes an instance of `Progress` with the specified parent and user info dictionary. + @available(*, noasync) public init(parent parentProgress: Progress?, userInfo userInfoOrNil: [ProgressUserInfoKey : Any]? = nil) { _children = Set() @@ -104,6 +106,7 @@ open class Progress : NSObject { /// MARK: - /// This is called when some other progress becomes an implicit child of this progress. + @available(*, noasync) private func _addImplicitChild(_ child : Progress) { guard let tsd = Thread.current.threadDictionary[Progress._tsdKey] as? _ProgressTSD else { preconditionFailure("A child was added without a current progress being set") } @@ -123,6 +126,7 @@ open class Progress : NSObject { /// The unit of work in a call to `becomeCurrent(withPendingUnitCount:)` has to be the same unit of work as that used for the value of the `totalUnitCount` property, but the unit of work used by the child can be a completely different one, and often will be. /// /// You must always balance invocations of this method with invocations of `resignCurrent`. + @available(*, noasync) open func becomeCurrent(withPendingUnitCount unitCount: Int64) { let oldTSD = Thread.current.threadDictionary[Progress._tsdKey] as? _ProgressTSD if let checkedTSD = oldTSD { @@ -136,6 +140,7 @@ open class Progress : NSObject { /// Balance the most recent previous invocation of `becomeCurrent(withPendingUnitCount:)` on the same thread. /// /// This restores the current progress object to what it was before `becomeCurrent(withPendingUnitCount:)` was invoked. + @available(*, noasync) open func resignCurrent() { guard let oldTSD = Thread.current.threadDictionary[Progress._tsdKey] as? _ProgressTSD else { preconditionFailure("This Progress was not the current progress on this thread.") @@ -396,7 +401,7 @@ open class Progress : NSObject { /// If the value of the `localizedDescription` property has not been set, then the default implementation of `localizedDescription` uses the progress kind to determine how to use the values of other properties, as well as values in the user info dictionary, to create a string that is presentable to the user. open var kind: ProgressKind? - public struct FileOperationKind : RawRepresentable, Equatable, Hashable { + public struct FileOperationKind : RawRepresentable, Equatable, Hashable, Sendable { public let rawValue: String public init(_ rawValue: String) { self.rawValue = rawValue } public init(rawValue: String) { self.rawValue = rawValue } @@ -473,7 +478,7 @@ public protocol ProgressReporting : NSObjectProtocol { var progress: Progress { get } } -public struct ProgressKind : RawRepresentable, Equatable, Hashable { +public struct ProgressKind : RawRepresentable, Equatable, Hashable, Sendable { public let rawValue: String public init(_ rawValue: String) { self.rawValue = rawValue } public init(rawValue: String) { self.rawValue = rawValue } @@ -484,7 +489,7 @@ public struct ProgressKind : RawRepresentable, Equatable, Hashable { public static let file = ProgressKind(rawValue: "NSProgressKindFile") } -public struct ProgressUserInfoKey : RawRepresentable, Equatable, Hashable { +public struct ProgressUserInfoKey : RawRepresentable, Equatable, Hashable, Sendable { public let rawValue: String public init(_ rawValue: String) { self.rawValue = rawValue } public init(rawValue: String) { self.rawValue = rawValue } @@ -526,6 +531,9 @@ public struct ProgressUserInfoKey : RawRepresentable, Equatable, Hashable { public static let fileCompletedCountKey = ProgressUserInfoKey(rawValue: "NSProgressFileCompletedCountKey") } +@available(*, unavailable) +extension _ProgressTSD : Sendable { } + fileprivate class _ProgressTSD : NSObject { /// The thread's default progress. fileprivate var currentProgress : Progress diff --git a/Sources/Foundation/PropertyListSerialization.swift b/Sources/Foundation/PropertyListSerialization.swift index a9cb8a46c3..47738de70d 100644 --- a/Sources/Foundation/PropertyListSerialization.swift +++ b/Sources/Foundation/PropertyListSerialization.swift @@ -15,7 +15,7 @@ let kCFPropertyListBinaryFormat_v1_0 = CFPropertyListFormat.binaryFormat_v1_0 extension PropertyListSerialization { - public struct MutabilityOptions : OptionSet { + public struct MutabilityOptions : OptionSet, Sendable { public let rawValue : UInt public init(rawValue: UInt) { self.rawValue = rawValue } @@ -29,6 +29,9 @@ extension PropertyListSerialization { public typealias WriteOptions = Int } +@available(*, unavailable) +extension PropertyListSerialization : Sendable { } + open class PropertyListSerialization : NSObject { open class func propertyList(_ plist: Any, isValidFor format: PropertyListFormat) -> Bool { diff --git a/Sources/Foundation/RunLoop.swift b/Sources/Foundation/RunLoop.swift index af64bf31ec..ad7d79a558 100644 --- a/Sources/Foundation/RunLoop.swift +++ b/Sources/Foundation/RunLoop.swift @@ -18,8 +18,8 @@ internal let kCFRunLoopExit = CFRunLoopActivity.exit.rawValue internal let kCFRunLoopAllActivities = CFRunLoopActivity.allActivities.rawValue extension RunLoop { - public struct Mode : RawRepresentable, Equatable, Hashable { - public private(set) var rawValue: String + public struct Mode : RawRepresentable, Equatable, Hashable, Sendable { + public let rawValue: String public init(_ rawValue: String) { self.rawValue = rawValue @@ -49,6 +49,9 @@ extension RunLoop.Mode { #if !canImport(Dispatch) +@available(*, unavailable) +extension RunLoop : Sendable { } + open class RunLoop: NSObject { @available(*, unavailable, message: "RunLoop is not available on WASI") open class var current: RunLoop { @@ -70,6 +73,9 @@ internal func _NSRunLoopNew(_ cf: CFRunLoop) -> Unmanaged { return unsafeBitCast(rl, to: Unmanaged.self) // this retain is balanced on the other side of the CF fence } +@available(*, unavailable) +extension RunLoop : Sendable { } + open class RunLoop: NSObject { internal var _cfRunLoopStorage : AnyObject! internal final var _cfRunLoop: CFRunLoop! { @@ -115,14 +121,12 @@ open class RunLoop: NSObject { #if os(Linux) || os(macOS) || os(iOS) || os(tvOS) || os(watchOS) || os(OpenBSD) internal var currentCFRunLoop: CFRunLoop { getCFRunLoop() } - @available(*, deprecated, message: "Directly accessing the run loop may cause your code to not become portable in the future.") internal func getCFRunLoop() -> CFRunLoop { return _cfRunLoop } #else internal final var currentCFRunLoop: CFRunLoop { _cfRunLoop } - @available(*, unavailable, message: "Core Foundation is not available on your platform.") internal func getCFRunLoop() -> Never { fatalError() } @@ -245,11 +249,11 @@ extension RunLoop { return true } - public func perform(inModes modes: [RunLoop.Mode], block: @escaping () -> Void) { + public func perform(inModes modes: [RunLoop.Mode], block: @Sendable @escaping () -> Void) { CFRunLoopPerformBlock(currentCFRunLoop, (modes.map { $0._cfStringUniquingKnown })._cfObject, block) } - public func perform(_ block: @escaping () -> Void) { + public func perform(_ block: @Sendable @escaping () -> Void) { perform(inModes: [.default], block: block) } } @@ -257,37 +261,49 @@ extension RunLoop { // These exist as SPI for XCTest for now. Do not rely on their contracts or continued existence. extension RunLoop { - @available(*, deprecated, message: "For XCTest use only.") + // TODO: Switch XCTest to SPI Annotation + // @available(*, deprecated, message: "For XCTest use only.") public func _stop() { CFRunLoopStop(currentCFRunLoop) } - @available(*, deprecated, message: "For XCTest use only.") + // TODO: Switch XCTest to SPI Annotation + // @available(*, deprecated, message: "For XCTest use only.") public func _observe(_ activities: _Activities, in mode: RunLoop.Mode = .default, repeats: Bool = true, order: Int = 0, handler: @escaping (_Activity) -> Void) -> _Observer { let observer = _Observer(activities: activities, repeats: repeats, order: order, handler: handler) CFRunLoopAddObserver(self.currentCFRunLoop, observer.cfObserver, mode._cfStringUniquingKnown) return observer } - @available(*, deprecated, message: "For XCTest use only.") + // TODO: Switch XCTest to SPI Annotation + // @available(*, deprecated, message: "For XCTest use only.") public func _observe(_ activity: _Activity, in mode: RunLoop.Mode = .default, repeats: Bool = true, order: Int = 0, handler: @escaping (_Activity) -> Void) -> _Observer { return _observe(_Activities(activity), in: mode, repeats: repeats, order: order, handler: handler) } - @available(*, deprecated, message: "For XCTest use only.") + // TODO: Switch XCTest to SPI Annotation + // @available(*, deprecated, message: "For XCTest use only.") public func _add(_ source: _Source, forMode mode: RunLoop.Mode) { CFRunLoopAddSource(_cfRunLoop, source.cfSource, mode._cfStringUniquingKnown) } - @available(*, deprecated, message: "For XCTest use only.") - open func _remove(_ source: _Source, for mode: RunLoop.Mode) { + // TODO: Switch XCTest to SPI Annotation + // @available(*, deprecated, message: "For XCTest use only.") + public func _remove(_ source: _Source, for mode: RunLoop.Mode) { CFRunLoopRemoveSource(_cfRunLoop, source.cfSource, mode._cfStringUniquingKnown) } } +@available(*, unavailable) +extension RunLoop._Observer : Sendable { } + +@available(*, unavailable) +extension RunLoop._Source : Sendable { } + extension RunLoop { - @available(*, deprecated, message: "For XCTest use only.") - public enum _Activity: UInt { + // TODO: Switch XCTest to SPI Annotation + // @available(*, deprecated, message: "For XCTest use only.") + public enum _Activity: UInt, Sendable { // These must match CFRunLoopActivity. case entry = 0 case beforeTimers = 1 @@ -297,8 +313,9 @@ extension RunLoop { case exit = 128 } - @available(*, deprecated, message: "For XCTest use only.") - public struct _Activities: OptionSet { + // TODO: Switch XCTest to SPI Annotation + // @available(*, deprecated, message: "For XCTest use only.") + public struct _Activities: OptionSet, Sendable { public var rawValue: UInt public init(rawValue: UInt) { self.rawValue = rawValue @@ -317,7 +334,8 @@ extension RunLoop { public static let allActivities = _Activities(rawValue: 0x0FFFFFFF) } - @available(*, deprecated, message: "For XCTest use only.") + // TODO: Switch XCTest to SPI Annotation + // @available(*, deprecated, message: "For XCTest use only.") public class _Observer { fileprivate let _cfObserverStorage: AnyObject fileprivate var cfObserver: CFRunLoopObserver { unsafeBitCast(_cfObserverStorage, to: CFRunLoopObserver.self) } @@ -346,7 +364,8 @@ extension RunLoop { } } - @available(*, deprecated, message: "For XCTest use only.") + // TODO: Switch XCTest to SPI Annotation + // @available(*, deprecated, message: "For XCTest use only.") open class _Source: NSObject { fileprivate var _cfSourceStorage: AnyObject! diff --git a/Sources/Foundation/Scanner.swift b/Sources/Foundation/Scanner.swift index a77718221c..0b82d51965 100644 --- a/Sources/Foundation/Scanner.swift +++ b/Sources/Foundation/Scanner.swift @@ -7,6 +7,8 @@ // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // +@available(*, unavailable) +extension Scanner : Sendable { } open class Scanner: NSObject, NSCopying { internal var _scanString: String diff --git a/Sources/Foundation/ScannerAPI.swift b/Sources/Foundation/ScannerAPI.swift index 500b2f1d92..2e0e892dfe 100644 --- a/Sources/Foundation/ScannerAPI.swift +++ b/Sources/Foundation/ScannerAPI.swift @@ -17,7 +17,7 @@ extension CharacterSet { @available(swift 5.0) extension Scanner { - public enum NumberRepresentation { + public enum NumberRepresentation : Sendable { case decimal // See the %d, %f and %F format conversions. case hexadecimal // See the %x, %X, %a and %A format conversions. For integers, a leading 0x or 0X is optional; for floating-point numbers, it is required. } diff --git a/Sources/Foundation/Stream.swift b/Sources/Foundation/Stream.swift index 3ea8a9d01f..e7a4863f0e 100644 --- a/Sources/Foundation/Stream.swift +++ b/Sources/Foundation/Stream.swift @@ -16,8 +16,8 @@ internal extension UInt { } extension Stream { - public struct PropertyKey : RawRepresentable, Equatable, Hashable { - public private(set) var rawValue: String + public struct PropertyKey : RawRepresentable, Equatable, Hashable, Sendable { + public let rawValue: String public init(_ rawValue: String) { self.rawValue = rawValue @@ -28,7 +28,7 @@ extension Stream { } } - public enum Status : UInt { + public enum Status : UInt, Sendable { case notOpen case opening @@ -40,7 +40,7 @@ extension Stream { case error } - public struct Event : OptionSet { + public struct Event : OptionSet, Sendable { public let rawValue : UInt public init(rawValue: UInt) { self.rawValue = rawValue } @@ -53,7 +53,8 @@ extension Stream { } } - +@available(*, unavailable) +extension Stream : Sendable { } // Stream is an abstract class encapsulating the common API to InputStream and OutputStream. // Subclassers of InputStream and OutputStream must also implement these methods. @@ -101,6 +102,9 @@ open class Stream: NSObject { } } +@available(*, unavailable) +extension InputStream : Sendable { } + // InputStream is an abstract class representing the base functionality of a read stream. // Subclassers are required to implement these methods. open class InputStream: Stream { @@ -176,6 +180,9 @@ open class InputStream: Stream { } } +@available(*, unavailable) +extension OutputStream : Sendable { } + // OutputStream is an abstract class representing the base functionality of a write stream. // Subclassers are required to implement these methods. // Currently this is left as named OutputStream due to conflicts with the standard library's text streaming target protocol named OutputStream (which ideally should be renamed) @@ -251,6 +258,9 @@ open class OutputStream : Stream { } } +@available(*, unavailable) +extension _InputStreamSPIForFoundationNetworkingUseOnly : Sendable { } + public struct _InputStreamSPIForFoundationNetworkingUseOnly { var inputStream: InputStream @@ -334,7 +344,7 @@ extension Stream.PropertyKey { } // MARK: - -public struct StreamSocketSecurityLevel : RawRepresentable, Equatable, Hashable { +public struct StreamSocketSecurityLevel : RawRepresentable, Equatable, Hashable, Sendable { public let rawValue: String public init(rawValue: String) { self.rawValue = rawValue @@ -350,7 +360,7 @@ extension StreamSocketSecurityLevel { // MARK: - -public struct StreamSOCKSProxyConfiguration : RawRepresentable, Equatable, Hashable { +public struct StreamSOCKSProxyConfiguration : RawRepresentable, Equatable, Hashable, Sendable { public let rawValue: String public init(rawValue: String) { self.rawValue = rawValue @@ -366,7 +376,7 @@ extension StreamSOCKSProxyConfiguration { // MARK: - -public struct StreamSOCKSProxyVersion : RawRepresentable, Equatable, Hashable { +public struct StreamSOCKSProxyVersion : RawRepresentable, Equatable, Hashable, Sendable { public let rawValue: String public init(rawValue: String) { self.rawValue = rawValue @@ -379,7 +389,7 @@ extension StreamSOCKSProxyVersion { // MARK: - Supported network service types -public struct StreamNetworkServiceTypeValue : RawRepresentable, Equatable, Hashable { +public struct StreamNetworkServiceTypeValue : RawRepresentable, Equatable, Hashable, Sendable { public let rawValue: String public init(rawValue: String) { self.rawValue = rawValue diff --git a/Sources/Foundation/Thread.swift b/Sources/Foundation/Thread.swift index 166a5d3fe5..7e2854a5c7 100644 --- a/Sources/Foundation/Thread.swift +++ b/Sources/Foundation/Thread.swift @@ -52,7 +52,7 @@ internal class NSThreadSpecific { } } -internal enum _NSThreadStatus { +internal enum _NSThreadStatus : Sendable { case initialized case starting case executing @@ -76,6 +76,9 @@ private func NSThreadStart(_ context: UnsafeMutableRawPointer?) -> UnsafeMutable return nil } +@available(*, unavailable) +extension Thread : Sendable { } + open class Thread : NSObject { static internal var _currentThread = NSThreadSpecific() @@ -112,7 +115,7 @@ open class Thread : NSObject { /// Alternative API for detached thread creation /// - Experiment: This is a draft API currently under consideration for official import into Foundation as a suitable alternative to creation via selector /// - Note: Since this API is under consideration it may be either removed or revised in the near future - open class func detachNewThread(_ block: @escaping () -> Swift.Void) { + open class func detachNewThread(_ block: @Sendable @escaping () -> Swift.Void) { let t = Thread(block: block) t.start() } @@ -245,7 +248,7 @@ open class Thread : NSObject { #endif } - public convenience init(block: @escaping () -> Swift.Void) { + public convenience init(block: @Sendable @escaping () -> Swift.Void) { self.init() _main = block } diff --git a/Sources/Foundation/Timer.swift b/Sources/Foundation/Timer.swift index 83a3efa0de..623a750dcc 100644 --- a/Sources/Foundation/Timer.swift +++ b/Sources/Foundation/Timer.swift @@ -15,6 +15,9 @@ internal func __NSFireTimer(_ timer: CFRunLoopTimer?, info: UnsafeMutableRawPoin t._fire(t) } +@available(*, unavailable) +extension Timer : Sendable { } + open class Timer : NSObject { internal final var _cfObject: CFRunLoopTimer { get { @@ -27,13 +30,14 @@ open class Timer : NSObject { internal var _timerStorage: AnyObject? internal final var _timer: CFRunLoopTimer? { unsafeBitCast(_timerStorage, to: CFRunLoopTimer?.self) } // has to be optional because this is a chicken/egg problem with initialization in swift - internal var _fire: (Timer) -> Void = { (_: Timer) in } + // This is a var but it is only initialized in the init methods and not mutated again after + internal var _fire: @Sendable (Timer) -> Void = { (_: Timer) in } /// Alternative API for timer creation with a block. /// - Experiment: This is a draft API currently under consideration for official import into Foundation as a suitable alternative to creation via selector /// - Note: Since this API is under consideration it may be either removed or revised in the near future /// - Warning: Capturing the timer or the owner of the timer inside of the block may cause retain cycles. Use with caution - public init(fire date: Date, interval: TimeInterval, repeats: Bool, block: @escaping (Timer) -> Swift.Void) { + public init(fire date: Date, interval: TimeInterval, repeats: Bool, block: @Sendable @escaping (Timer) -> Swift.Void) { super.init() _fire = block var context = CFRunLoopTimerContext() @@ -58,7 +62,7 @@ open class Timer : NSObject { /// - parameter timeInterval: The number of seconds between firings of the timer. If seconds is less than or equal to 0.0, this method chooses the nonnegative value of 0.1 milliseconds instead /// - parameter repeats: If YES, the timer will repeatedly reschedule itself until invalidated. If NO, the timer will be invalidated after it fires. /// - parameter block: The execution body of the timer; the timer itself is passed as the parameter to this block when executed to aid in avoiding cyclical references - public convenience init(timeInterval interval: TimeInterval, repeats: Bool, block: @escaping (Timer) -> Swift.Void) { + public convenience init(timeInterval interval: TimeInterval, repeats: Bool, block: @Sendable @escaping (Timer) -> Swift.Void) { self.init(fire: Date(), interval: interval, repeats: repeats, block: block) } @@ -66,7 +70,7 @@ open class Timer : NSObject { /// - Experiment: This is a draft API currently under consideration for official import into Foundation as a suitable alternative to creation via selector /// - Note: Since this API is under consideration it may be either removed or revised in the near future /// - Warning: Capturing the timer or the owner of the timer inside of the block may cause retain cycles. Use with caution - open class func scheduledTimer(withTimeInterval interval: TimeInterval, repeats: Bool, block: @escaping (Timer) -> Void) -> Timer { + open class func scheduledTimer(withTimeInterval interval: TimeInterval, repeats: Bool, block: @Sendable @escaping (Timer) -> Void) -> Timer { let timer = Timer(fire: Date(timeIntervalSinceNow: interval), interval: interval, repeats: repeats, block: block) CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer._timer!, kCFRunLoopDefaultMode) return timer diff --git a/Sources/Foundation/URL.swift b/Sources/Foundation/URL.swift index 3a215add2c..7bf0327686 100644 --- a/Sources/Foundation/URL.swift +++ b/Sources/Foundation/URL.swift @@ -10,6 +10,9 @@ // //===----------------------------------------------------------------------===// +@available(*, unavailable) +extension URLResourceValues : Sendable { } + /** URLs to file system resources support the properties defined below. Note that not all property values will exist for all file system URLs. For example, if a file is located on a volume that does not support creation dates, it is valid to request the creation date property, but the returned value will be nil, and no error will be generated. @@ -439,7 +442,7 @@ extension URL { /// /// `URLResourceValues` keeps track of which of its properties have been set. Those values are the ones used by this function to determine which properties to write. public mutating func setResourceValues(_ values: URLResourceValues) throws { - var ns = self as NSURL + let ns = self as NSURL try ns.setResourceValues(values._values) self = ns as URL } @@ -459,9 +462,9 @@ extension URL { /// /// Temporary resource values are for client use. Temporary resource values exist only in memory and are never written to the resource's backing store. Once set, a temporary resource value can be copied from the URL object with `func resourceValues(forKeys:)`. The values are stored in the loosely-typed `allValues` dictionary property. /// - /// To remove a temporary resource value from the URL object, use `func removeCachedResourceValue(forKey:)`. Care should be taken to ensure the key that identifies a temporary resource value is unique and does not conflict with system defined keys (using reverse domain name notation in your temporary resource value keys is recommended). This method is currently applicable only to URLs for file system resources. + /// To remove a temporalet resource value from the URL object, use `func removeCachedResourceValue(forKey:)`. Care should be taken to ensure the key that identifies a temporary resource value is unique and does not conflict with system defined keys (using reverse domain name notation in your temporary resource value keys is recommended). This method is currently applicable only to URLs for file system resources. public mutating func setTemporaryResourceValue(_ value : Any, forKey key: URLResourceKey) { - var ns = self as NSURL + let ns = self as NSURL ns.setTemporaryResourceValue(value, forKey: key) self = ns as URL } @@ -470,7 +473,7 @@ extension URL { /// /// This method is currently applicable only to URLs for file system resources. public mutating func removeAllCachedResourceValues() { - var ns = self as NSURL + let ns = self as NSURL ns.removeAllCachedResourceValues() self = ns as URL } @@ -479,7 +482,7 @@ extension URL { /// /// Removing a cached resource value may remove other cached resource values because some resource values are cached as a set of values, and because some resource values depend on other resource values (temporary resource values have no dependencies). This method is currently applicable only to URLs for file system resources. public mutating func removeCachedResourceValue(forKey key: URLResourceKey) { - var ns = self as NSURL + let ns = self as NSURL ns.removeCachedResourceValue(forKey: key) self = ns as URL } diff --git a/Sources/Foundation/URLResourceKey.swift b/Sources/Foundation/URLResourceKey.swift index b415111883..059499fae4 100644 --- a/Sources/Foundation/URLResourceKey.swift +++ b/Sources/Foundation/URLResourceKey.swift @@ -8,8 +8,8 @@ // -public struct URLResourceKey: RawRepresentable, Equatable, Hashable { - public private(set) var rawValue: String +public struct URLResourceKey: RawRepresentable, Equatable, Hashable, Sendable { + public let rawValue: String public init(rawValue: String) { self.rawValue = rawValue @@ -123,8 +123,8 @@ extension URLResourceKey { } -public struct URLFileResourceType: RawRepresentable, Equatable, Hashable { - public private(set) var rawValue: String +public struct URLFileResourceType: RawRepresentable, Equatable, Hashable, Sendable { + public let rawValue: String public init(rawValue: String) { self.rawValue = rawValue diff --git a/Sources/Foundation/Unit.swift b/Sources/Foundation/Unit.swift index 9c8d4a1400..c71ff52318 100644 --- a/Sources/Foundation/Unit.swift +++ b/Sources/Foundation/Unit.swift @@ -2,7 +2,7 @@ NSUnitConverter describes how to convert a unit to and from the base unit of its dimension. Use the NSUnitConverter protocol to implement new ways of converting a unit. */ -open class UnitConverter : NSObject { +open class UnitConverter : NSObject, @unchecked Sendable { /* @@ -35,7 +35,7 @@ open class UnitConverter : NSObject { } } -open class UnitConverterLinear : UnitConverter, NSSecureCoding { +open class UnitConverterLinear : UnitConverter, NSSecureCoding, @unchecked Sendable { open private(set) var coefficient: Double @@ -95,7 +95,7 @@ open class UnitConverterLinear : UnitConverter, NSSecureCoding { } // This must be named with a NS prefix because it can be sometimes encoded by Darwin, and we need to match the name in the archive. -internal class NSUnitConverterReciprocal : UnitConverter, NSSecureCoding { +internal class NSUnitConverterReciprocal : UnitConverter, NSSecureCoding, @unchecked Sendable { private var reciprocal: Double @@ -147,6 +147,9 @@ internal class NSUnitConverterReciprocal : UnitConverter, NSSecureCoding { NSUnit is the base class for all unit types (dimensional and dimensionless). */ +@available(*, unavailable) +extension Unit : Sendable { } + open class Unit : NSObject, NSCopying, NSSecureCoding { @@ -192,6 +195,9 @@ open class Unit : NSObject, NSCopying, NSSecureCoding { } } +@available(*, unavailable) +extension Dimension : Sendable { } + open class Dimension : Unit { @@ -249,7 +255,7 @@ open class Dimension : Unit { } } -public final class UnitAcceleration : Dimension { +public final class UnitAcceleration : Dimension, @unchecked Sendable { /* Base unit - metersPerSecondSquared @@ -298,7 +304,7 @@ public final class UnitAcceleration : Dimension { } } -public final class UnitAngle : Dimension { +public final class UnitAngle : Dimension, @unchecked Sendable { /* Base unit - degrees @@ -379,7 +385,7 @@ public final class UnitAngle : Dimension { } } -public final class UnitArea : Dimension { +public final class UnitArea : Dimension, @unchecked Sendable { /* Base unit - squareMeters @@ -524,7 +530,7 @@ public final class UnitArea : Dimension { } } -public final class UnitConcentrationMass : Dimension { +public final class UnitConcentrationMass : Dimension, @unchecked Sendable { /* Base unit - gramsPerLiter @@ -579,7 +585,7 @@ public final class UnitConcentrationMass : Dimension { } } -public final class UnitDispersion : Dimension { +public final class UnitDispersion : Dimension, @unchecked Sendable { /* Base unit - partsPerMillion @@ -620,7 +626,7 @@ public final class UnitDispersion : Dimension { } } -public final class UnitDuration : Dimension { +public final class UnitDuration : Dimension, @unchecked Sendable { /* Base unit - seconds @@ -709,7 +715,7 @@ public final class UnitDuration : Dimension { } } -public final class UnitElectricCharge : Dimension { +public final class UnitElectricCharge : Dimension, @unchecked Sendable { /* Base unit - coulombs */ @@ -789,7 +795,7 @@ public final class UnitElectricCharge : Dimension { } } -public final class UnitElectricCurrent : Dimension { +public final class UnitElectricCurrent : Dimension, @unchecked Sendable { /* Base unit - amperes @@ -863,7 +869,7 @@ public final class UnitElectricCurrent : Dimension { } } -public final class UnitElectricPotentialDifference : Dimension { +public final class UnitElectricPotentialDifference : Dimension, @unchecked Sendable { /* Base unit - volts @@ -937,7 +943,7 @@ public final class UnitElectricPotentialDifference : Dimension { } } -public final class UnitElectricResistance : Dimension { +public final class UnitElectricResistance : Dimension, @unchecked Sendable { /* Base unit - ohms @@ -1011,7 +1017,7 @@ public final class UnitElectricResistance : Dimension { } } -public final class UnitEnergy : Dimension { +public final class UnitEnergy : Dimension, @unchecked Sendable { /* Base unit - joules @@ -1085,7 +1091,7 @@ public final class UnitEnergy : Dimension { } } -public final class UnitFrequency : Dimension { +public final class UnitFrequency : Dimension, @unchecked Sendable { /* Base unit - hertz @@ -1182,7 +1188,7 @@ public final class UnitFrequency : Dimension { } } -public final class UnitFuelEfficiency : Dimension { +public final class UnitFuelEfficiency : Dimension, @unchecked Sendable { /* Base unit - litersPer100Kilometers @@ -1239,7 +1245,7 @@ public final class UnitFuelEfficiency : Dimension { } } -public final class UnitLength : Dimension { +public final class UnitLength : Dimension, @unchecked Sendable { /* Base unit - meters @@ -1448,7 +1454,7 @@ public final class UnitLength : Dimension { } } -public final class UnitIlluminance : Dimension { +public final class UnitIlluminance : Dimension, @unchecked Sendable { /* Base unit - lux @@ -1489,7 +1495,7 @@ public final class UnitIlluminance : Dimension { } } -public final class UnitInformationStorage : Dimension { +public final class UnitInformationStorage : Dimension, @unchecked Sendable { /* Base unit - bit @@ -1808,7 +1814,7 @@ public final class UnitInformationStorage : Dimension { } } -public final class UnitMass : Dimension { +public final class UnitMass : Dimension, @unchecked Sendable { /* Base unit - kilograms @@ -1969,7 +1975,7 @@ public final class UnitMass : Dimension { } } -public final class UnitPower : Dimension { +public final class UnitPower : Dimension, @unchecked Sendable { /* Base unit - watts @@ -2090,7 +2096,7 @@ public final class UnitPower : Dimension { } } -public final class UnitPressure : Dimension { +public final class UnitPressure : Dimension, @unchecked Sendable { /* Base unit - newtonsPerMetersSquared (equivalent to 1 pascal) @@ -2203,7 +2209,7 @@ public final class UnitPressure : Dimension { } } -public final class UnitSpeed : Dimension { +public final class UnitSpeed : Dimension, @unchecked Sendable { /* Base unit - metersPerSecond @@ -2268,7 +2274,7 @@ public final class UnitSpeed : Dimension { } } -public final class UnitTemperature : Dimension { +public final class UnitTemperature : Dimension, @unchecked Sendable { /* Base unit - kelvin @@ -2331,7 +2337,7 @@ public final class UnitTemperature : Dimension { } } -public final class UnitVolume : Dimension { +public final class UnitVolume : Dimension, @unchecked Sendable { /* Base unit - liters diff --git a/Sources/Foundation/UserDefaults.swift b/Sources/Foundation/UserDefaults.swift index 23f035b6d6..f975256d63 100644 --- a/Sources/Foundation/UserDefaults.swift +++ b/Sources/Foundation/UserDefaults.swift @@ -17,6 +17,9 @@ fileprivate func bridgeFromNSCFTypeIfNeeded(_ value: Any) -> Any { return __SwiftValue.fetch(nonOptional: object) } +@available(*, unavailable) +extension UserDefaults : Sendable { } + open class UserDefaults: NSObject { static private func _isValueAllowed(_ nonbridgedValue: Any) -> Bool { let value = bridgeFromNSCFTypeIfNeeded(nonbridgedValue) diff --git a/Sources/FoundationNetworking/Boxing.swift b/Sources/FoundationNetworking/Boxing.swift index d04f68eb4c..31154c1f4e 100644 --- a/Sources/FoundationNetworking/Boxing.swift +++ b/Sources/FoundationNetworking/Boxing.swift @@ -21,7 +21,7 @@ import Foundation /// A class type which acts as a handle (pointer-to-pointer) to a Foundation reference type which has only a mutable class (e.g., NSURLComponents). /// /// Note: This assumes that the result of calling copy() is mutable. The documentation says that classes which do not have a mutable/immutable distinction should just adopt NSCopying instead of NSMutableCopying. -internal final class _MutableHandle where MutableType : NSCopying { +internal final class _MutableHandle : @unchecked Sendable where MutableType : NSCopying { @usableFromInline internal var _pointer : MutableType init(reference : MutableType) { @@ -45,191 +45,3 @@ internal final class _MutableHandle where MutableType : return _pointer } } - -/// Describes common operations for Foundation struct types that are bridged to a mutable object (e.g. NSURLComponents). -internal protocol _MutableBoxing : ReferenceConvertible { - var _handle : _MutableHandle { get set } - - /// Apply a mutating closure to the reference type, regardless if it is mutable or immutable. - /// - /// This function performs the correct copy-on-write check for efficient mutation. - mutating func _applyMutation(_ whatToDo : (ReferenceType) -> ReturnType) -> ReturnType -} - -extension _MutableBoxing { - @inline(__always) - mutating func _applyMutation(_ whatToDo : (ReferenceType) -> ReturnType) -> ReturnType { - // Only create a new box if we are not uniquely referenced - if !isKnownUniquelyReferenced(&_handle) { - let ref = _handle._pointer - _handle = _MutableHandle(reference: ref) - } - return whatToDo(_handle._pointer) - } -} - -internal enum _MutableUnmanagedWrapper where MutableType : NSMutableCopying { - case Immutable(Unmanaged) - case Mutable(Unmanaged) -} - -internal protocol _SwiftNativeFoundationType: AnyObject { - associatedtype ImmutableType : NSObject - associatedtype MutableType : NSObject, NSMutableCopying - var __wrapped : _MutableUnmanagedWrapper { get } - - init(unmanagedImmutableObject: Unmanaged) - init(unmanagedMutableObject: Unmanaged) - - func mutableCopy(with zone : NSZone) -> Any - - func hash(into hasher: inout Hasher) - var hashValue: Int { get } - - var description: String { get } - var debugDescription: String { get } - - func releaseWrappedObject() -} - -extension _SwiftNativeFoundationType { - - @inline(__always) - func _mapUnmanaged(_ whatToDo : (ImmutableType) throws -> ReturnType) rethrows -> ReturnType { - defer { _fixLifetime(self) } - - switch __wrapped { - case .Immutable(let i): - return try i._withUnsafeGuaranteedRef { - _onFastPath() - return try whatToDo($0) - } - case .Mutable(let m): - return try m._withUnsafeGuaranteedRef { - _onFastPath() - return try whatToDo(_unsafeReferenceCast($0, to: ImmutableType.self)) - } - } - } - - func releaseWrappedObject() { - switch __wrapped { - case .Immutable(let i): - i.release() - case .Mutable(let m): - m.release() - } - } - - func mutableCopy(with zone : NSZone) -> Any { - return _mapUnmanaged { ($0 as NSObject).mutableCopy() } - } - - func hash(into hasher: inout Hasher) { - _mapUnmanaged { hasher.combine($0) } - } - - var hashValue: Int { - return _mapUnmanaged { return $0.hashValue } - } - - var description: String { - return _mapUnmanaged { return $0.description } - } - - var debugDescription: String { - return _mapUnmanaged { return $0.debugDescription } - } - - func isEqual(_ other: AnyObject) -> Bool { - return _mapUnmanaged { return $0.isEqual(other) } - } -} - -internal protocol _MutablePairBoxing { - associatedtype WrappedSwiftNSType : _SwiftNativeFoundationType - var _wrapped : WrappedSwiftNSType { get set } -} - -extension _MutablePairBoxing { - @inline(__always) - func _mapUnmanaged(_ whatToDo : (WrappedSwiftNSType.ImmutableType) throws -> ReturnType) rethrows -> ReturnType { - // We are using Unmanaged. Make sure that the owning container class - // 'self' is guaranteed to be alive by extending the lifetime of 'self' - // to the end of the scope of this function. - // Note: At the time of this writing using withExtendedLifetime here - // instead of _fixLifetime causes different ARC pair matching behavior - // foiling optimization. This is why we explicitly use _fixLifetime here - // instead. - defer { _fixLifetime(self) } - - let unmanagedHandle = Unmanaged.passUnretained(_wrapped) - let wrapper = unmanagedHandle._withUnsafeGuaranteedRef { $0.__wrapped } - switch (wrapper) { - case .Immutable(let i): - return try i._withUnsafeGuaranteedRef { - return try whatToDo($0) - } - case .Mutable(let m): - return try m._withUnsafeGuaranteedRef { - return try whatToDo(_unsafeReferenceCast($0, to: WrappedSwiftNSType.ImmutableType.self)) - } - } - } - - @inline(__always) - mutating func _applyUnmanagedMutation(_ whatToDo : (WrappedSwiftNSType.MutableType) throws -> ReturnType) rethrows -> ReturnType { - // We are using Unmanaged. Make sure that the owning container class - // 'self' is guaranteed to be alive by extending the lifetime of 'self' - // to the end of the scope of this function. - // Note: At the time of this writing using withExtendedLifetime here - // instead of _fixLifetime causes different ARC pair matching behavior - // foiling optimization. This is why we explicitly use _fixLifetime here - // instead. - defer { _fixLifetime(self) } - - var unique = true - let _unmanagedHandle = Unmanaged.passUnretained(_wrapped) - let wrapper = _unmanagedHandle._withUnsafeGuaranteedRef { $0.__wrapped } - - // This check is done twice because: Value kept live for too long causing uniqueness check to fail - switch (wrapper) { - case .Immutable: - break - case .Mutable: - unique = isKnownUniquelyReferenced(&_wrapped) - } - - switch (wrapper) { - case .Immutable(let i): - // We need to become mutable; by creating a new instance we also become unique - let copy = Unmanaged.passRetained(i._withUnsafeGuaranteedRef { - return _unsafeReferenceCast($0.mutableCopy(), to: WrappedSwiftNSType.MutableType.self) } - ) - - // Be sure to set the var before calling out; otherwise references to the struct in the closure may be looking at the old value - _wrapped = WrappedSwiftNSType(unmanagedMutableObject: copy) - return try copy._withUnsafeGuaranteedRef { - _onFastPath() - return try whatToDo($0) - } - case .Mutable(let m): - // Only create a new box if we are not uniquely referenced - if !unique { - let copy = Unmanaged.passRetained(m._withUnsafeGuaranteedRef { - return _unsafeReferenceCast($0.mutableCopy(), to: WrappedSwiftNSType.MutableType.self) - }) - _wrapped = WrappedSwiftNSType(unmanagedMutableObject: copy) - return try copy._withUnsafeGuaranteedRef { - _onFastPath() - return try whatToDo($0) - } - } else { - return try m._withUnsafeGuaranteedRef { - _onFastPath() - return try whatToDo($0) - } - } - } - } -} diff --git a/Sources/FoundationNetworking/HTTPCookie.swift b/Sources/FoundationNetworking/HTTPCookie.swift index 0534780e7b..e0d1cbbd9f 100644 --- a/Sources/FoundationNetworking/HTTPCookie.swift +++ b/Sources/FoundationNetworking/HTTPCookie.swift @@ -17,7 +17,7 @@ import Foundation import WinSDK #endif -public struct HTTPCookiePropertyKey : RawRepresentable, Equatable, Hashable { +public struct HTTPCookiePropertyKey : RawRepresentable, Equatable, Hashable, Sendable { public private(set) var rawValue: String public init(_ rawValue: String) { @@ -101,7 +101,7 @@ internal extension HTTPCookiePropertyKey { /// an immutable object initialized from a dictionary that contains /// the various cookie attributes. It has accessors to get the various /// attributes of a cookie. -open class HTTPCookie : NSObject { +open class HTTPCookie : NSObject, @unchecked Sendable { let _comment: String? let _commentURL: URL? @@ -115,7 +115,7 @@ open class HTTPCookie : NSObject { let _portList: [NSNumber]? let _value: String let _version: Int - var _properties: [HTTPCookiePropertyKey : Any] + let _properties: [HTTPCookiePropertyKey : Any] // See: https://tools.ietf.org/html/rfc2616#section-3.3.1 @@ -376,7 +376,7 @@ open class HTTPCookie : NSObject { _HTTPOnly = false } - _properties = [ + var props : [HTTPCookiePropertyKey : Any] = [ .created : Date().timeIntervalSinceReferenceDate, // Cocoa Compatibility .discard : _sessionOnly, .domain : _domain, @@ -387,23 +387,24 @@ open class HTTPCookie : NSObject { .version : _version ] if let comment = properties[.comment] { - _properties[.comment] = comment + props[.comment] = comment } if let commentURL = properties[.commentURL] { - _properties[.commentURL] = commentURL + props[.commentURL] = commentURL } if let expires = properties[.expires] { - _properties[.expires] = expires + props[.expires] = expires } if let maximumAge = properties[.maximumAge] { - _properties[.maximumAge] = maximumAge + props[.maximumAge] = maximumAge } if let originURL = properties[.originURL] { - _properties[.originURL] = originURL + props[.originURL] = originURL } if let _portList = _portList { - _properties[.port] = _portList + props[.port] = _portList } + _properties = props } /// Return a dictionary of header fields that can be used to add the diff --git a/Sources/FoundationNetworking/HTTPCookieStorage.swift b/Sources/FoundationNetworking/HTTPCookieStorage.swift index 22a1aff000..4c10667f63 100644 --- a/Sources/FoundationNetworking/HTTPCookieStorage.swift +++ b/Sources/FoundationNetworking/HTTPCookieStorage.swift @@ -23,7 +23,7 @@ import Foundation only from the main document domain */ extension HTTPCookie { - public enum AcceptPolicy : UInt { + public enum AcceptPolicy : UInt, Sendable { case always case never case onlyFromMainDocumentDomain @@ -39,7 +39,7 @@ extension HTTPCookie { set of cookies. It also has convenience methods to parse and generate cookie-related HTTP header fields. */ -open class HTTPCookieStorage: NSObject { +open class HTTPCookieStorage: NSObject, @unchecked Sendable { private static let sharedStorage = HTTPCookieStorage(cookieStorageName: "shared") private static var sharedCookieStorages: [String: HTTPCookieStorage] = [:] //for group storage containers diff --git a/Sources/FoundationNetworking/NSURLRequest.swift b/Sources/FoundationNetworking/NSURLRequest.swift index 616650daec..99bd1c6b1f 100644 --- a/Sources/FoundationNetworking/NSURLRequest.swift +++ b/Sources/FoundationNetworking/NSURLRequest.swift @@ -52,7 +52,7 @@ import Foundation /// with whether already-existing cache data is returned to satisfy a /// URL load request. extension NSURLRequest { - public enum CachePolicy : UInt { + public enum CachePolicy : UInt, Sendable { /// Specifies that the caching logic defined in the protocol /// implementation, if any, is used for a particular URL load request. This /// is the default policy for URL load requests. @@ -86,7 +86,7 @@ extension NSURLRequest { case reloadRevalidatingCacheData // Unimplemented } - public enum NetworkServiceType : UInt { + public enum NetworkServiceType : UInt, Sendable { case `default` // Standard internet traffic case voip // Voice over IP control traffic case video // Video traffic @@ -96,6 +96,9 @@ extension NSURLRequest { } } +@available(*, unavailable) +extension NSURLRequest : Sendable { } + /// An `NSURLRequest` object represents a URL load request in a /// manner independent of protocol and URL scheme. /// @@ -378,6 +381,9 @@ open class NSURLRequest : NSObject, NSSecureCoding, NSCopying, NSMutableCopying } } +@available(*, unavailable) +extension NSMutableURLRequest : Sendable { } + /// An `NSMutableURLRequest` object represents a mutable URL load /// request in a manner independent of protocol and URL scheme. /// diff --git a/Sources/FoundationNetworking/URLAuthenticationChallenge.swift b/Sources/FoundationNetworking/URLAuthenticationChallenge.swift index a31e39ba15..11cdbb1431 100644 --- a/Sources/FoundationNetworking/URLAuthenticationChallenge.swift +++ b/Sources/FoundationNetworking/URLAuthenticationChallenge.swift @@ -13,7 +13,7 @@ import SwiftFoundation import Foundation #endif -public protocol URLAuthenticationChallengeSender : NSObjectProtocol { +public protocol URLAuthenticationChallengeSender : NSObjectProtocol, Sendable { /*! @@ -52,7 +52,7 @@ public protocol URLAuthenticationChallengeSender : NSObjectProtocol { provides all the information about the challenge, and has a method to indicate when it's done. */ -open class URLAuthenticationChallenge : NSObject { +open class URLAuthenticationChallenge : NSObject, @unchecked Sendable { private let _protectionSpace: URLProtectionSpace private let _proposedCredential: URLCredential? @@ -186,7 +186,7 @@ open class URLAuthenticationChallenge : NSObject { } } -class URLSessionAuthenticationChallengeSender : NSObject, URLAuthenticationChallengeSender { +class URLSessionAuthenticationChallengeSender : NSObject, URLAuthenticationChallengeSender, @unchecked Sendable { func cancel(_ challenge: URLAuthenticationChallenge) { fatalError("swift-corelibs-foundation only supports URLSession; for challenges coming from URLSession, please implement the appropriate URLSessionTaskDelegate methods rather than using the sender argument.") } diff --git a/Sources/FoundationNetworking/URLCache.swift b/Sources/FoundationNetworking/URLCache.swift index 914898bdc5..5898355cf4 100644 --- a/Sources/FoundationNetworking/URLCache.swift +++ b/Sources/FoundationNetworking/URLCache.swift @@ -39,7 +39,7 @@ internal extension NSLock { disk. */ extension URLCache { - public enum StoragePolicy : UInt { + public enum StoragePolicy : UInt, Sendable { case allowed case allowedInMemoryOnly @@ -86,7 +86,7 @@ class StoredCachedURLResponse: NSObject, NSSecureCoding { It is used to maintain characteristics and attributes of a cached object. */ -open class CachedURLResponse : NSObject, NSCopying { +open class CachedURLResponse : NSObject, NSCopying, @unchecked Sendable { open override func copy() -> Any { return copy(with: nil) } @@ -193,7 +193,7 @@ open class CachedURLResponse : NSObject, NSCopying { } } -open class URLCache : NSObject { +open class URLCache : NSObject, @unchecked Sendable { private static let sharedLock = NSLock() private static var _shared: URLCache? diff --git a/Sources/FoundationNetworking/URLCredential.swift b/Sources/FoundationNetworking/URLCredential.swift index bdb844ba34..556d45f69a 100644 --- a/Sources/FoundationNetworking/URLCredential.swift +++ b/Sources/FoundationNetworking/URLCredential.swift @@ -24,13 +24,18 @@ import Foundation access only its own credentials. */ extension URLCredential { - public enum Persistence : UInt { - case none - case forSession - case permanent + public enum Persistence : UInt, Sendable { + case none = 0 + case forSession = 1 + case permanent = 2 @available(*, deprecated, message: "Synchronizable credential storage is not available in swift-corelibs-foundation. If you rely on synchronization for your functionality, please audit your code.") - case synchronizable + case synchronizable = 3 + + // Wraps the check for synchronizable to avoid deprecation warning + internal var isSynchronizable: Bool { + self.rawValue == 3 + } } } @@ -39,10 +44,10 @@ extension URLCredential { @class URLCredential @discussion This class is an immutable object representing an authentication credential. The actual type of the credential is determined by the constructor called in the categories declared below. */ -open class URLCredential : NSObject, NSSecureCoding, NSCopying { - private var _user : String - private var _password : String - private var _persistence : Persistence +open class URLCredential : NSObject, NSSecureCoding, NSCopying, @unchecked Sendable { + private let _user : String + private let _password : String + private let _persistence : Persistence /*! @method initWithUser:password:persistence: diff --git a/Sources/FoundationNetworking/URLCredentialStorage.swift b/Sources/FoundationNetworking/URLCredentialStorage.swift index 76fe88884f..fe77d9d292 100644 --- a/Sources/FoundationNetworking/URLCredentialStorage.swift +++ b/Sources/FoundationNetworking/URLCredentialStorage.swift @@ -17,9 +17,9 @@ import Foundation @class URLCredential.Storage @discussion URLCredential.Storage implements a singleton object (shared instance) which manages the shared credentials cache. Note: Whereas in Mac OS X any application can access any credential with a persistence of URLCredential.Persistence.permanent provided the user gives permission, in iPhone OS an application can access only its own credentials. */ -open class URLCredentialStorage: NSObject { +open class URLCredentialStorage: NSObject, @unchecked Sendable { - private static var _shared = URLCredentialStorage() + private static let _shared = URLCredentialStorage() /*! @method sharedCredentialStorage @@ -79,7 +79,7 @@ open class URLCredentialStorage: NSObject { the new one will replace it. */ open func set(_ credential: URLCredential, for space: URLProtectionSpace) { - guard credential.persistence != .synchronizable else { + guard !credential.persistence.isSynchronizable else { // Do what logged-out-from-iCloud Darwin does, and refuse to save synchronizable credentials when a sync service is not available (which, in s-c-f, is always) return } @@ -122,7 +122,7 @@ open class URLCredentialStorage: NSObject { @discussion The credential is removed from both persistent and temporary storage. */ open func remove(_ credential: URLCredential, for space: URLProtectionSpace, options: [String : AnyObject]? = [:]) { - if credential.persistence == .synchronizable { + if credential.persistence.isSynchronizable { guard let options = options, let removeSynchronizable = options[NSURLCredentialStorageRemoveSynchronizableCredentials] as? NSNumber, removeSynchronizable.boolValue == true else { @@ -178,7 +178,7 @@ open class URLCredentialStorage: NSObject { @discussion If the credential is not yet in the set for the protection space, it will be added to it. */ open func setDefaultCredential(_ credential: URLCredential, for space: URLProtectionSpace) { - guard credential.persistence != .synchronizable else { + guard !credential.persistence.isSynchronizable else { return } diff --git a/Sources/FoundationNetworking/URLProtectionSpace.swift b/Sources/FoundationNetworking/URLProtectionSpace.swift index 471811e8e3..6470657bc5 100644 --- a/Sources/FoundationNetworking/URLProtectionSpace.swift +++ b/Sources/FoundationNetworking/URLProtectionSpace.swift @@ -96,14 +96,14 @@ public let NSURLAuthenticationMethodNegotiate: String = "NSURLAuthenticationMeth @const NSURLAuthenticationMethodClientCertificate @abstract SSL Client certificate. Applies to any protocol. */ -@available(*, deprecated, message: "swift-corelibs-foundation does not currently support certificate authentication.") +@available(*, unavailable, message: "swift-corelibs-foundation does not currently support certificate authentication.") public let NSURLAuthenticationMethodClientCertificate: String = "NSURLAuthenticationMethodClientCertificate" /*! @const NSURLAuthenticationMethodServerTrust @abstract SecTrustRef validation required. Applies to any protocol. */ -@available(*, deprecated, message: "swift-corelibs-foundation does not support methods of authentication that rely on the Darwin Security framework.") +@available(*, unavailable, message: "swift-corelibs-foundation does not support methods of authentication that rely on the Darwin Security framework.") public let NSURLAuthenticationMethodServerTrust: String = "NSURLAuthenticationMethodServerTrust" @@ -111,7 +111,7 @@ public let NSURLAuthenticationMethodServerTrust: String = "NSURLAuthenticationMe @class URLProtectionSpace @discussion This class represents a protection space requiring authentication. */ -open class URLProtectionSpace : NSObject, NSCopying { +open class URLProtectionSpace : NSObject, NSCopying, @unchecked Sendable { private let _host: String private let _isProxy: Bool @@ -213,9 +213,6 @@ open class URLProtectionSpace : NSObject, NSCopying { case NSURLAuthenticationMethodNTLM: fallthrough case NSURLAuthenticationMethodNegotiate: fallthrough - case NSURLAuthenticationMethodClientCertificate: fallthrough - case NSURLAuthenticationMethodServerTrust: - return true default: return false @@ -289,8 +286,6 @@ open class URLProtectionSpace : NSObject, NSCopying { NSURLAuthenticationMethodHTMLForm, NSURLAuthenticationMethodNTLM, NSURLAuthenticationMethodNegotiate, - NSURLAuthenticationMethodClientCertificate, - NSURLAuthenticationMethodServerTrust ] var result = "<\(type(of: self)) \(Unmanaged.passUnretained(self).toOpaque())>: " result += "Host:\(host), " diff --git a/Sources/FoundationNetworking/URLProtocol.swift b/Sources/FoundationNetworking/URLProtocol.swift index 887ca268ac..7c12ddceaf 100644 --- a/Sources/FoundationNetworking/URLProtocol.swift +++ b/Sources/FoundationNetworking/URLProtocol.swift @@ -54,7 +54,7 @@ import Foundation loading system that is intended for use by URLProtocol implementors. */ -public protocol URLProtocolClient : NSObjectProtocol { +public protocol URLProtocolClient : NSObjectProtocol, Sendable { /*! @@ -148,12 +148,15 @@ public protocol URLProtocolClient : NSObjectProtocol { func urlProtocol(_ protocol: URLProtocol, didCancel challenge: URLAuthenticationChallenge) } -internal class _ProtocolClient : NSObject { +internal class _ProtocolClient : NSObject, @unchecked Sendable { var cachePolicy: URLCache.StoragePolicy = .notAllowed var cacheableData: [Data]? var cacheableResponse: URLResponse? } +@available(*, unavailable) +extension URLProtocol : Sendable { } + /*! @class NSURLProtocol diff --git a/Sources/FoundationNetworking/URLRequest.swift b/Sources/FoundationNetworking/URLRequest.swift index 67155e92ce..f8ab4c8c7d 100644 --- a/Sources/FoundationNetworking/URLRequest.swift +++ b/Sources/FoundationNetworking/URLRequest.swift @@ -16,7 +16,7 @@ import SwiftFoundation import Foundation #endif -public struct URLRequest : ReferenceConvertible, Equatable, Hashable { +public struct URLRequest : ReferenceConvertible, Equatable, Hashable, Sendable { public typealias ReferenceType = NSURLRequest public typealias CachePolicy = NSURLRequest.CachePolicy public typealias NetworkServiceType = NSURLRequest.NetworkServiceType diff --git a/Sources/FoundationNetworking/URLResponse.swift b/Sources/FoundationNetworking/URLResponse.swift index 3be8ffec49..de61e457b8 100644 --- a/Sources/FoundationNetworking/URLResponse.swift +++ b/Sources/FoundationNetworking/URLResponse.swift @@ -21,7 +21,7 @@ import Foundation /// the actual bytes representing the content of a URL. See /// `URLSession` for more information about receiving the content /// data for a URL load. -open class URLResponse : NSObject, NSSecureCoding, NSCopying { +open class URLResponse : NSObject, NSSecureCoding, NSCopying, @unchecked Sendable { static public var supportsSecureCoding: Bool { return true @@ -175,7 +175,7 @@ open class URLResponse : NSObject, NSSecureCoding, NSCopying { /// HTTP URL load. It is a specialization of URLResponse which /// provides conveniences for accessing information specific to HTTP /// protocol responses. -open class HTTPURLResponse : URLResponse { +open class HTTPURLResponse : URLResponse, @unchecked Sendable { /// Initializer for HTTPURLResponse objects. /// @@ -390,7 +390,7 @@ private func getSuggestedFilename(fromHeaderFields headerFields: [String : Strin return nil } /// Parts corresponding to the `Content-Type` header field in a HTTP message. -private struct ContentTypeComponents { +private struct ContentTypeComponents : Sendable { /// For `text/html; charset=ISO-8859-4` this would be `text/html` let mimeType: String /// For `text/html; charset=ISO-8859-4` this would be `ISO-8859-4`. Will be @@ -432,10 +432,10 @@ extension ContentTypeComponents { /// attribute = token /// value = token | quoted-string /// ``` -private struct ValueWithParameters { +private struct ValueWithParameters : Sendable { let value: String let parameters: [Parameter] - struct Parameter { + struct Parameter : Sendable { let attribute: String let value: String? } @@ -469,7 +469,7 @@ private extension String { let escape = UnicodeScalar(0x5c)! // \ let quote = UnicodeScalar(0x22)! // " let separator = UnicodeScalar(0x3b)! // ; - enum State { + enum State : Sendable { case nonQuoted(String) case nonQuotedEscaped(String) case quoted(String) diff --git a/Sources/FoundationNetworking/URLSession/Configuration.swift b/Sources/FoundationNetworking/URLSession/Configuration.swift index 7b3996e2e8..725263dfbf 100644 --- a/Sources/FoundationNetworking/URLSession/Configuration.swift +++ b/Sources/FoundationNetworking/URLSession/Configuration.swift @@ -24,7 +24,7 @@ import Foundation internal extension URLSession { /// This is an immutable / `struct` version of `URLSessionConfiguration`. - struct _Configuration { + struct _Configuration : Sendable { /// identifier for the background session configuration let identifier: String? @@ -47,7 +47,8 @@ internal extension URLSession { let isDiscretionary: Bool /// The proxy dictionary, as described by - let connectionProxyDictionary: [AnyHashable : Any]? + // TODO: It would be nice to have the type not be AnyHashable, but for now we need this for source compatibility. + nonisolated(unsafe) let connectionProxyDictionary: [AnyHashable : Any]? /// Allow the use of HTTP pipelining let httpShouldUsePipelining: Bool diff --git a/Sources/FoundationNetworking/URLSession/FTP/FTPURLProtocol.swift b/Sources/FoundationNetworking/URLSession/FTP/FTPURLProtocol.swift index 142a2c84f0..51c87d9064 100644 --- a/Sources/FoundationNetworking/URLSession/FTP/FTPURLProtocol.swift +++ b/Sources/FoundationNetworking/URLSession/FTP/FTPURLProtocol.swift @@ -66,7 +66,7 @@ internal class _FTPURLProtocol: _NativeProtocol { try easyHandle.set(url: url) } catch { self.internalState = .transferFailed - let nsError = error as? NSError ?? NSError(domain: NSURLErrorDomain, code: NSURLErrorBadURL) + let nsError = NSError(domain: NSURLErrorDomain, code: NSURLErrorBadURL) failWith(error: nsError, request: request) return } diff --git a/Sources/FoundationNetworking/URLSession/HTTP/HTTPURLProtocol.swift b/Sources/FoundationNetworking/URLSession/HTTP/HTTPURLProtocol.swift index ed7070c700..a3050722a8 100644 --- a/Sources/FoundationNetworking/URLSession/HTTP/HTTPURLProtocol.swift +++ b/Sources/FoundationNetworking/URLSession/HTTP/HTTPURLProtocol.swift @@ -321,7 +321,7 @@ internal class _HTTPURLProtocol: _NativeProtocol { try easyHandle.set(url: url) } catch { self.internalState = .transferFailed - let nsError = error as? NSError ?? NSError(domain: NSURLErrorDomain, code: NSURLErrorBadURL) + let nsError = NSError(domain: NSURLErrorDomain, code: NSURLErrorBadURL) failWith(error: nsError, request: request) return } diff --git a/Sources/FoundationNetworking/URLSession/NativeProtocol.swift b/Sources/FoundationNetworking/URLSession/NativeProtocol.swift index 941b9cd3b1..22931c8018 100644 --- a/Sources/FoundationNetworking/URLSession/NativeProtocol.swift +++ b/Sources/FoundationNetworking/URLSession/NativeProtocol.swift @@ -297,7 +297,8 @@ internal class _NativeProtocol: URLProtocol, _EasyHandleDelegate { // We will reset the body source and seek forward. guard let session = task?.session as? URLSession else { fatalError() } - var currentInputStream: InputStream? + // TODO: InputStream is not Sendable, but it seems safe here beacuse of the wait on the dispatch group. It would be nice to prove this to the compiler. + nonisolated(unsafe) var currentInputStream: InputStream? if let delegate = task?.delegate { let dispatchGroup = DispatchGroup() diff --git a/Sources/FoundationNetworking/URLSession/NetworkingSpecific.swift b/Sources/FoundationNetworking/URLSession/NetworkingSpecific.swift index 7fde05a297..c8ae632a45 100644 --- a/Sources/FoundationNetworking/URLSession/NetworkingSpecific.swift +++ b/Sources/FoundationNetworking/URLSession/NetworkingSpecific.swift @@ -36,7 +36,7 @@ internal func NSRequiresConcreteImplementation(_ fn: String = #function, file: S } @usableFromInline -class _NSNonfileURLContentLoader: _NSNonfileURLContentLoading { +class _NSNonfileURLContentLoader: _NSNonfileURLContentLoading, @unchecked Sendable { @usableFromInline required init() {} @@ -51,14 +51,15 @@ class _NSNonfileURLContentLoader: _NSNonfileURLContentLoading { return CocoaError.error(.fileReadUnknown, userInfo: userInfo, url: url) } - var urlResponse: URLResponse? let session = URLSession(configuration: URLSessionConfiguration.default) let cond = NSCondition() cond.lock() - var resError: Error? - var resData: Data? - var taskFinished = false + // protected by the condition above + nonisolated(unsafe) var urlResponse: URLResponse? + nonisolated(unsafe) var resError: Error? + nonisolated(unsafe) var resData: Data? + nonisolated(unsafe) var taskFinished = false let task = session.dataTask(with: url, completionHandler: { data, response, error in cond.lock() resData = data diff --git a/Sources/FoundationNetworking/URLSession/TaskRegistry.swift b/Sources/FoundationNetworking/URLSession/TaskRegistry.swift index 059010a07b..a676bd9fed 100644 --- a/Sources/FoundationNetworking/URLSession/TaskRegistry.swift +++ b/Sources/FoundationNetworking/URLSession/TaskRegistry.swift @@ -35,9 +35,9 @@ extension URLSession { /// - Note: This must **only** be accessed on the owning session's work queue. class _TaskRegistry { /// Completion handler for `URLSessionDataTask`, and `URLSessionUploadTask`. - typealias DataTaskCompletion = (Data?, URLResponse?, Error?) -> Void + typealias DataTaskCompletion = @Sendable (Data?, URLResponse?, Error?) -> Void /// Completion handler for `URLSessionDownloadTask`. - typealias DownloadTaskCompletion = (URL?, URLResponse?, Error?) -> Void + typealias DownloadTaskCompletion = @Sendable (URL?, URLResponse?, Error?) -> Void /// What to do upon events (such as completion) of a specific task. enum _Behaviour { /// Call the `URLSession`s delegate diff --git a/Sources/FoundationNetworking/URLSession/URLSession.swift b/Sources/FoundationNetworking/URLSession/URLSession.swift index a8f0e26155..da2e279a43 100644 --- a/Sources/FoundationNetworking/URLSession/URLSession.swift +++ b/Sources/FoundationNetworking/URLSession/URLSession.swift @@ -169,7 +169,7 @@ import Foundation #endif extension URLSession { - public enum DelayedRequestDisposition { + public enum DelayedRequestDisposition : Sendable { case cancel case continueLoading case useNewRequest @@ -186,13 +186,14 @@ fileprivate func nextSessionIdentifier() -> Int32 { } public let NSURLSessionTransferSizeUnknown: Int64 = -1 -open class URLSession : NSObject { +open class URLSession : NSObject, @unchecked Sendable { internal let _configuration: _Configuration fileprivate let multiHandle: _MultiHandle fileprivate var nextTaskIdentifier = 1 internal let workQueue: DispatchQueue internal let taskRegistry = URLSession._TaskRegistry() fileprivate let identifier: Int32 + // written to on workQueue, read from workQueue and elsewhere. Inherently somewhat racy, then, because it can change after reading the value asynchronously. fileprivate var invalidated = false fileprivate static let registerProtocols: () = { // TODO: We register all the native protocols here. @@ -348,7 +349,7 @@ open class URLSession : NSObject { } /* empty all cookies, cache and credential stores, removes disk files, issues -flushWithCompletionHandler:. Invokes completionHandler() on the delegate queue. */ - open func reset(completionHandler: @escaping () -> Void) { + open func reset(completionHandler: @Sendable @escaping () -> Void) { let configuration = self.configuration DispatchQueue.global(qos: .background).async { @@ -366,7 +367,7 @@ open class URLSession : NSObject { } /* flush storage to disk and clear transient network caches. Invokes completionHandler() on the delegate queue. */ - open func flush(completionHandler: @escaping () -> Void) { + open func flush(completionHandler: @Sendable @escaping () -> Void) { // We create new CURL handles every request. delegateQueue.addOperation { completionHandler() @@ -374,12 +375,12 @@ open class URLSession : NSObject { } @available(*, unavailable, renamed: "getTasksWithCompletionHandler(_:)") - open func getTasksWithCompletionHandler(completionHandler: @escaping ([URLSessionDataTask], [URLSessionUploadTask], [URLSessionDownloadTask]) -> Void) { + open func getTasksWithCompletionHandler(completionHandler: @Sendable @escaping ([URLSessionDataTask], [URLSessionUploadTask], [URLSessionDownloadTask]) -> Void) { getTasksWithCompletionHandler(completionHandler) } /* invokes completionHandler with outstanding data, upload and download tasks. */ - open func getTasksWithCompletionHandler(_ completionHandler: @escaping ([URLSessionDataTask], [URLSessionUploadTask], [URLSessionDownloadTask]) -> Void) { + open func getTasksWithCompletionHandler(_ completionHandler: @Sendable @escaping ([URLSessionDataTask], [URLSessionUploadTask], [URLSessionDownloadTask]) -> Void) { workQueue.async { self.delegateQueue.addOperation { var dataTasks = [URLSessionDataTask]() @@ -405,7 +406,7 @@ open class URLSession : NSObject { } /* invokes completionHandler with all outstanding tasks. */ - open func getAllTasks(completionHandler: @escaping ([URLSessionTask]) -> Void) { + open func getAllTasks(completionHandler: @Sendable @escaping ([URLSessionTask]) -> Void) { workQueue.async { self.delegateQueue.addOperation { completionHandler(self.taskRegistry.allTasks.filter { $0.state == .running || $0.isSuspendedAfterResume }) @@ -436,11 +437,11 @@ open class URLSession : NSObject { * see . The delegate, if any, will still be * called for authentication challenges. */ - open func dataTask(with request: URLRequest, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask { + open func dataTask(with request: URLRequest, completionHandler: @Sendable @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask { return dataTask(with: _Request(request), behaviour: .dataCompletionHandler(completionHandler)) } - open func dataTask(with url: URL, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask { + open func dataTask(with url: URL, completionHandler: @Sendable @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask { return dataTask(with: _Request(url), behaviour: .dataCompletionHandler(completionHandler)) } @@ -465,12 +466,12 @@ open class URLSession : NSObject { /* * upload convenience method. */ - open func uploadTask(with request: URLRequest, fromFile fileURL: URL, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionUploadTask { + open func uploadTask(with request: URLRequest, fromFile fileURL: URL, completionHandler: @Sendable @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionUploadTask { let r = URLSession._Request(request) return uploadTask(with: r, body: .file(fileURL), behaviour: .dataCompletionHandler(completionHandler)) } - open func uploadTask(with request: URLRequest, from bodyData: Data?, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionUploadTask { + open func uploadTask(with request: URLRequest, from bodyData: Data?, completionHandler: @Sendable @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionUploadTask { return uploadTask(with: _Request(request), body: .data(createDispatchData(bodyData!)), behaviour: .dataCompletionHandler(completionHandler)) } @@ -496,15 +497,15 @@ open class URLSession : NSObject { * copied during the invocation of the completion routine. The file * will be removed automatically. */ - open func downloadTask(with request: URLRequest, completionHandler: @escaping (URL?, URLResponse?, Error?) -> Void) -> URLSessionDownloadTask { + open func downloadTask(with request: URLRequest, completionHandler: @Sendable @escaping (URL?, URLResponse?, Error?) -> Void) -> URLSessionDownloadTask { return downloadTask(with: _Request(request), behavior: .downloadCompletionHandler(completionHandler)) } - open func downloadTask(with url: URL, completionHandler: @escaping (URL?, URLResponse?, Error?) -> Void) -> URLSessionDownloadTask { + open func downloadTask(with url: URL, completionHandler: @Sendable @escaping (URL?, URLResponse?, Error?) -> Void) -> URLSessionDownloadTask { return downloadTask(with: _Request(url), behavior: .downloadCompletionHandler(completionHandler)) } - open func downloadTask(withResumeData resumeData: Data, completionHandler: @escaping (URL?, URLResponse?, Error?) -> Void) -> URLSessionDownloadTask { + open func downloadTask(withResumeData resumeData: Data, completionHandler: @Sendable @escaping (URL?, URLResponse?, Error?) -> Void) -> URLSessionDownloadTask { return invalidDownloadTask(behavior: .downloadCompletionHandler(completionHandler)) } diff --git a/Sources/FoundationNetworking/URLSession/URLSessionConfiguration.swift b/Sources/FoundationNetworking/URLSession/URLSessionConfiguration.swift index 3f3b26bcb8..4c787ad501 100644 --- a/Sources/FoundationNetworking/URLSession/URLSessionConfiguration.swift +++ b/Sources/FoundationNetworking/URLSession/URLSessionConfiguration.swift @@ -35,7 +35,7 @@ import Foundation /// /// A background session can be used to perform networking operations /// on behalf of a suspended application, within certain constraints. -open class URLSessionConfiguration : NSObject, NSCopying { +open class URLSessionConfiguration : NSObject, NSCopying, @unchecked Sendable { // -init is silently incorrect in URLSessionCofiguration on the desktop. Ensure code that relied on swift-corelibs-foundation's init() being functional is redirected to the appropriate cross-platform class property. @available(*, deprecated, message: "Use .default instead.", renamed: "URLSessionConfiguration.default") public override init() { @@ -257,7 +257,7 @@ open class URLSessionConfiguration : NSObject, NSCopying { @available(*, unavailable, message: "Not available on non-Darwin platforms") extension URLSessionConfiguration { - public enum MultipathServiceType { + public enum MultipathServiceType : Sendable { case none case handover case interactive diff --git a/Sources/FoundationNetworking/URLSession/URLSessionDelegate.swift b/Sources/FoundationNetworking/URLSession/URLSessionDelegate.swift index bd061f55dc..8ec0729968 100644 --- a/Sources/FoundationNetworking/URLSession/URLSessionDelegate.swift +++ b/Sources/FoundationNetworking/URLSession/URLSessionDelegate.swift @@ -25,7 +25,7 @@ extension URLSession { /* * Disposition options for various delegate messages */ - public enum AuthChallengeDisposition : Int { + public enum AuthChallengeDisposition : Int, Sendable { case useCredential /* Use the specified credential, which may be nil */ case performDefaultHandling /* Default handling for the challenge - as if this delegate were not implemented; the credential parameter is ignored. */ @@ -33,7 +33,7 @@ extension URLSession { case rejectProtectionSpace /* This challenge is rejected and the next authentication protection space should be tried; the credential parameter is ignored. */ } - public enum ResponseDisposition : Int { + public enum ResponseDisposition : Int, Sendable { case cancel /* Cancel the load, this is the same as -[task cancel] */ case allow /* Allow the load to continue */ @@ -54,7 +54,7 @@ extension URLSession { /* * Messages related to the URL session as a whole */ -public protocol URLSessionDelegate : NSObjectProtocol { +public protocol URLSessionDelegate : NSObjectProtocol, Sendable { /* The last message a session receives. A session will only become * invalid because of a systemic error or when it has been @@ -71,12 +71,12 @@ public protocol URLSessionDelegate : NSObjectProtocol { * behavior will be to use the default handling, which may involve user * interaction. */ - func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) + func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @Sendable @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) } extension URLSessionDelegate { public func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) { } - public func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { } + public func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @Sendable @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { } } /* If an application has received an @@ -91,7 +91,7 @@ extension URLSessionDelegate { /* * Messages related to the operation of a specific task. */ -public protocol URLSessionTaskDelegate : URLSessionDelegate { +public protocol URLSessionTaskDelegate : URLSessionDelegate, Sendable { /* An HTTP request is attempting to perform a redirection to a different * URL. You must invoke the completion routine to allow the @@ -102,20 +102,20 @@ public protocol URLSessionTaskDelegate : URLSessionDelegate { * * For tasks in background sessions, redirections will always be followed and this method will not be called. */ - func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) + func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @Sendable @escaping (URLRequest?) -> Void) /* The task has received a request specific authentication challenge. * If this delegate is not implemented, the session specific authentication challenge * will *NOT* be called and the behavior will be the same as using the default handling * disposition. */ - func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) + func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @Sendable @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) /* Sent if a task requires a new, unopened body stream. This may be * necessary when authentication has failed for any request that * involves a body stream. */ - func urlSession(_ session: URLSession, task: URLSessionTask, needNewBodyStream completionHandler: @escaping (InputStream?) -> Void) + func urlSession(_ session: URLSession, task: URLSessionTask, needNewBodyStream completionHandler: @Sendable @escaping (InputStream?) -> Void) /* Sent periodically to notify the delegate of upload progress. This * information is also available as properties of the task. @@ -127,13 +127,13 @@ public protocol URLSessionTaskDelegate : URLSessionDelegate { */ func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) - func urlSession(_ session: URLSession, task: URLSessionTask, willBeginDelayedRequest request: URLRequest, completionHandler: @escaping (URLSession.DelayedRequestDisposition, URLRequest?) -> Void) + func urlSession(_ session: URLSession, task: URLSessionTask, willBeginDelayedRequest request: URLRequest, completionHandler: @Sendable @escaping (URLSession.DelayedRequestDisposition, URLRequest?) -> Void) func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) } extension URLSessionTaskDelegate { - public func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) { + public func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @Sendable @escaping (URLRequest?) -> Void) { // If the task's delegate does not implement this function, check if the session's delegate does if self === task.delegate, let sessionDelegate = session.delegate as? URLSessionTaskDelegate, self !== sessionDelegate { sessionDelegate.urlSession(session, task: task, willPerformHTTPRedirection: response, newRequest: request, completionHandler: completionHandler) @@ -143,7 +143,7 @@ extension URLSessionTaskDelegate { } } - public func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { + public func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @Sendable @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { if self === task.delegate, let sessionDelegate = session.delegate as? URLSessionTaskDelegate, self !== sessionDelegate { sessionDelegate.urlSession(session, task: task, didReceive: challenge, completionHandler: completionHandler) } else { @@ -151,7 +151,7 @@ extension URLSessionTaskDelegate { } } - public func urlSession(_ session: URLSession, task: URLSessionTask, needNewBodyStream completionHandler: @escaping (InputStream?) -> Void) { + public func urlSession(_ session: URLSession, task: URLSessionTask, needNewBodyStream completionHandler: @Sendable @escaping (InputStream?) -> Void) { if self === task.delegate, let sessionDelegate = session.delegate as? URLSessionTaskDelegate, self !== sessionDelegate { sessionDelegate.urlSession(session, task: task, needNewBodyStream: completionHandler) } else { @@ -171,7 +171,7 @@ extension URLSessionTaskDelegate { } } - public func urlSession(_ session: URLSession, task: URLSessionTask, willBeginDelayedRequest request: URLRequest, completionHandler: @escaping (URLSession.DelayedRequestDisposition, URLRequest?) -> Void) { + public func urlSession(_ session: URLSession, task: URLSessionTask, willBeginDelayedRequest request: URLRequest, completionHandler: @Sendable @escaping (URLSession.DelayedRequestDisposition, URLRequest?) -> Void) { if self === task.delegate, let sessionDelegate = session.delegate as? URLSessionTaskDelegate, self !== sessionDelegate { sessionDelegate.urlSession(session, task: task, willBeginDelayedRequest: request, completionHandler: completionHandler) } @@ -200,7 +200,7 @@ public protocol URLSessionDataDelegate : URLSessionTaskDelegate { /// (which cannot be converted to download tasks). /// - Bug: This will currently not wait for the completion handler to run, /// and it will ignore anything passed to the completion handler. - func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) + func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @Sendable @escaping (URLSession.ResponseDisposition) -> Void) /* Notification that a data task has become a download task. No * future messages will be sent to the data task. @@ -238,13 +238,13 @@ public protocol URLSessionDataDelegate : URLSessionTaskDelegate { * attempted for a given resource, and you should not rely on this * message to receive the resource data. */ - func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, willCacheResponse proposedResponse: CachedURLResponse, completionHandler: @escaping (CachedURLResponse?) -> Void) + func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, willCacheResponse proposedResponse: CachedURLResponse, completionHandler: @Sendable @escaping (CachedURLResponse?) -> Void) } extension URLSessionDataDelegate { - public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) { } + public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @Sendable @escaping (URLSession.ResponseDisposition) -> Void) { } public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didBecome downloadTask: URLSessionDownloadTask) { } @@ -252,7 +252,7 @@ extension URLSessionDataDelegate { public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { } - public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, willCacheResponse proposedResponse: CachedURLResponse, completionHandler: @escaping (CachedURLResponse?) -> Void) { } + public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, willCacheResponse proposedResponse: CachedURLResponse, completionHandler: @Sendable @escaping (CachedURLResponse?) -> Void) { } } /* diff --git a/Sources/FoundationNetworking/URLSession/URLSessionTask.swift b/Sources/FoundationNetworking/URLSession/URLSessionTask.swift index d1aa041333..c3e2660417 100644 --- a/Sources/FoundationNetworking/URLSession/URLSessionTask.swift +++ b/Sources/FoundationNetworking/URLSession/URLSessionTask.swift @@ -27,7 +27,7 @@ private class Bag { /// A cancelable object that refers to the lifetime /// of processing a given request. -open class URLSessionTask : NSObject, NSCopying { +open class URLSessionTask : NSObject, NSCopying, @unchecked Sendable { // These properties aren't heeded in swift-corelibs-foundation, but we may heed them in the future. They exist for source compatibility. open var countOfBytesClientExpectsToReceive: Int64 = NSURLSessionTransferSizeUnknown { @@ -386,6 +386,8 @@ open class URLSessionTask : NSObject, NSCopying { } guard !canceled else { return } self._getProtocol { (urlProtocol) in + // The combination of locking in getProtocol and dispatching to the work queue let us use the normally non-Sendable URLProtocol + nonisolated(unsafe) let urlProtocol = urlProtocol self.workQueue.async { var info = [NSLocalizedDescriptionKey: "\(URLError.Code.cancelled)" as Any] if let url = self.originalRequest?.url { @@ -455,6 +457,8 @@ open class URLSessionTask : NSObject, NSCopying { if self.suspendCount == 1 { self._getProtocol { (urlProtocol) in + // The combination of locking in getProtocol and dispatching to the work queue let us use the normally non-Sendable URLProtocol + nonisolated(unsafe) let urlProtocol = urlProtocol self.workQueue.async { urlProtocol?.stopLoading() } @@ -473,6 +477,8 @@ open class URLSessionTask : NSObject, NSCopying { if self.suspendCount == 0 { self.hasTriggeredResume = true self._getProtocol { (urlProtocol) in + // The combination of locking in getProtocol and dispatching to the work queue let us use the normally non-Sendable URLProtocol + nonisolated(unsafe) let urlProtocol = urlProtocol self.workQueue.async { if let _protocol = urlProtocol { _protocol.startLoading() @@ -522,7 +528,7 @@ open class URLSessionTask : NSObject, NSCopying { } extension URLSessionTask { - public enum State : Int { + public enum State : Int, Sendable { /// The task is currently being serviced by the session case running case suspended @@ -622,7 +628,7 @@ extension URLSessionTask { * functionality over an URLSessionTask and its presence is merely * to provide lexical differentiation from download and upload tasks. */ -open class URLSessionDataTask : URLSessionTask { +open class URLSessionDataTask : URLSessionTask, @unchecked Sendable { } /* @@ -631,14 +637,14 @@ open class URLSessionDataTask : URLSessionTask { * that may be sent referencing an URLSessionDataTask equally apply * to URLSessionUploadTasks. */ -open class URLSessionUploadTask : URLSessionDataTask { +open class URLSessionUploadTask : URLSessionDataTask, @unchecked Sendable { } /* * URLSessionDownloadTask is a task that represents a download to * local storage. */ -open class URLSessionDownloadTask : URLSessionTask { +open class URLSessionDownloadTask : URLSessionTask, @unchecked Sendable { var createdFromInvalidResumeData = false @@ -680,8 +686,8 @@ open class URLSessionDownloadTask : URLSessionTask { * and once the WebSocket handshake is successful, the client can read and write * messages that will be framed using the WebSocket protocol by the framework. */ -open class URLSessionWebSocketTask : URLSessionTask { - public enum CloseCode : Int, @unchecked Sendable { +open class URLSessionWebSocketTask : URLSessionTask, @unchecked Sendable { + public enum CloseCode : Int, Sendable { case invalid = 0 case normalClosure = 1000 case goingAway = 1001 @@ -697,7 +703,7 @@ open class URLSessionWebSocketTask : URLSessionTask { case tlsHandshakeFailure = 1015 } - public enum Message { + public enum Message : Sendable { case data(Data) case string(String) } @@ -762,6 +768,8 @@ open class URLSessionWebSocketTask : URLSessionTask { open func sendPing(pongReceiveHandler: @escaping (Error?) -> Void) { self.workQueue.async { self._getProtocol { urlProtocol in + // The combination of locking in getProtocol and dispatching to the work queue let us use the normally non-Sendable URLProtocol + nonisolated(unsafe) let urlProtocol = urlProtocol self.workQueue.async { if let webSocketProtocol = urlProtocol as? _WebSocketURLProtocol { do { @@ -821,7 +829,7 @@ open class URLSessionWebSocketTask : URLSessionTask { } } - private func send(_ message: Message, completionHandler: @escaping (Error?) -> Void) { + private func send(_ message: Message, completionHandler: @Sendable @escaping (Error?) -> Void) { self.workQueue.async { self.sendBuffer.append((message, completionHandler)) self.doPendingWork() @@ -837,7 +845,7 @@ open class URLSessionWebSocketTask : URLSessionTask { } } - private func receive(completionHandler: @escaping (Result) -> Void) { + private func receive(completionHandler: @Sendable @escaping (Result) -> Void) { self.workQueue.async { self.receiveCompletionHandlers.append(completionHandler) self.doPendingWork() @@ -867,6 +875,8 @@ open class URLSessionWebSocketTask : URLSessionTask { } self.pongCompletionHandlers.removeAll() self._getProtocol { urlProtocol in + // The combination of locking in getProtocol and dispatching to the work queue let us use the normally non-Sendable URLProtocol + nonisolated(unsafe) let urlProtocol = urlProtocol self.workQueue.async { if self.handshakeCompleted && self.state != .completed { if let webSocketProtocol = urlProtocol as? _WebSocketURLProtocol { @@ -882,6 +892,8 @@ open class URLSessionWebSocketTask : URLSessionTask { } } else { self._getProtocol { urlProtocol in + // The combination of locking in getProtocol and dispatching to the work queue let us use the normally non-Sendable URLProtocol + nonisolated(unsafe) let urlProtocol = urlProtocol self.workQueue.async { if self.handshakeCompleted { if let webSocketProtocol = urlProtocol as? _WebSocketURLProtocol { @@ -975,8 +987,7 @@ extension URLSessionWebSocketDelegate { * disassociated from the underlying session. */ -@available(*, deprecated, message: "URLSessionStreamTask is not available in swift-corelibs-foundation") -open class URLSessionStreamTask : URLSessionTask { +open class URLSessionStreamTask : URLSessionTask, @unchecked Sendable { /* Read minBytes, or at most maxBytes bytes and invoke the completion * handler on the sessions delegate queue with the data or an error. @@ -1153,8 +1164,9 @@ extension _ProtocolClient : URLProtocolClient { switch session.behaviour(for: task) { case .taskDelegate(let delegate): if let downloadDelegate = delegate as? URLSessionDownloadDelegate, let downloadTask = task as? URLSessionDownloadTask { + let temporaryFileURL = urlProtocol.properties[URLProtocol._PropertyKey.temporaryFileURL] as! URL session.delegateQueue.addOperation { - downloadDelegate.urlSession(session, downloadTask: downloadTask, didFinishDownloadingTo: urlProtocol.properties[URLProtocol._PropertyKey.temporaryFileURL] as! URL) + downloadDelegate.urlSession(session, downloadTask: downloadTask, didFinishDownloadingTo: temporaryFileURL) } } else if let webSocketDelegate = delegate as? URLSessionWebSocketDelegate, let webSocketTask = task as? URLSessionWebSocketTask { @@ -1224,7 +1236,7 @@ extension _ProtocolClient : URLProtocolClient { guard let task = `protocol`.task else { fatalError("Received response, but there's no task.") } guard let session = task.session as? URLSession else { fatalError("Task not associated with URLSession.") } - func proceed(using credential: URLCredential?) { + @Sendable func proceed(using credential: URLCredential?) { let protectionSpace = challenge.protectionSpace let authScheme = protectionSpace.authenticationMethod @@ -1247,7 +1259,7 @@ extension _ProtocolClient : URLProtocolClient { task.resume() } - func attemptProceedingWithDefaultCredential() { + @Sendable func attemptProceedingWithDefaultCredential() { if let credential = challenge.proposedCredential { let last = task._protocolLock.performLocked { task._lastCredentialUsedFromStorageDuringAuthentication } @@ -1403,7 +1415,7 @@ extension URLSessionTask { } extension URLProtocol { - enum _PropertyKey: String { + enum _PropertyKey: String, Sendable { case responseData case temporaryFileURL } diff --git a/Sources/FoundationNetworking/URLSession/URLSessionTaskMetrics.swift b/Sources/FoundationNetworking/URLSession/URLSessionTaskMetrics.swift index d745c9e0f8..5de19fdeef 100644 --- a/Sources/FoundationNetworking/URLSession/URLSessionTaskMetrics.swift +++ b/Sources/FoundationNetworking/URLSession/URLSessionTaskMetrics.swift @@ -21,19 +21,19 @@ import SwiftFoundation import Foundation #endif -open class URLSessionTaskMetrics : NSObject { +open class URLSessionTaskMetrics : NSObject, @unchecked Sendable { public internal(set) var transactionMetrics: [URLSessionTaskTransactionMetrics] = [] public internal(set) var taskInterval: DateInterval = .init() public internal(set) var redirectCount = 0 - public enum ResourceFetchType: Int { + public enum ResourceFetchType: Int, Sendable { case unknown = 0 case networkLoad = 1 case serverPush = 2 case localCache = 3 } - public enum DomainResolutionProtocol: Int { + public enum DomainResolutionProtocol: Int, Sendable { case unknown = 0 case udp = 1 case tcp = 2 @@ -42,7 +42,7 @@ open class URLSessionTaskMetrics : NSObject { } } -open class URLSessionTaskTransactionMetrics: NSObject { +open class URLSessionTaskTransactionMetrics: NSObject, @unchecked Sendable { public let request: URLRequest public internal(set) var response: URLResponse? @@ -86,7 +86,7 @@ open class URLSessionTaskTransactionMetrics: NSObject { } } -public enum tls_ciphersuite_t: UInt16 { +public enum tls_ciphersuite_t: UInt16, Sendable { case AES_128_GCM_SHA256 = 4865 case AES_256_GCM_SHA384 = 4866 @@ -118,7 +118,7 @@ public enum tls_ciphersuite_t: UInt16 { case RSA_WITH_AES_256_GCM_SHA384 = 157 } -public enum tls_protocol_version_t: UInt16 { +public enum tls_protocol_version_t: UInt16, Sendable { case TLSv10 = 769 case TLSv11 = 770 case TLSv12 = 771 diff --git a/Sources/FoundationNetworking/URLSession/libcurl/EasyHandle.swift b/Sources/FoundationNetworking/URLSession/libcurl/EasyHandle.swift index 1d9e3c7f54..cbbe60db81 100644 --- a/Sources/FoundationNetworking/URLSession/libcurl/EasyHandle.swift +++ b/Sources/FoundationNetworking/URLSession/libcurl/EasyHandle.swift @@ -26,7 +26,19 @@ import Foundation @_implementationOnly import _CFURLSessionInterface import Dispatch +// These helper functions avoid warnings about "will never be executed" code which checks the availability of the underlying libcurl features. +internal func curlInfoCAInfoSupported() -> Bool { + NS_CURL_CURLINFO_CAINFO_SUPPORTED == 1 +} + +internal func maxHostConnectionsSupported() -> Bool { + NS_CURL_MAX_HOST_CONNECTIONS_SUPPORTED == 1 +} + +internal func xferInfoFunctionSupported() -> Bool { + NS_CURL_XFERINFOFUNCTION_SUPPORTED == 1 +} /// Minimal wrapper around the [curl easy interface](https://curl.haxx.se/libcurl/c/) /// @@ -209,7 +221,7 @@ extension _EasyHandle { #endif #if !os(Windows) && !os(macOS) && !os(iOS) && !os(watchOS) && !os(tvOS) - if NS_CURL_CURLINFO_CAINFO_SUPPORTED == 1 { + if curlInfoCAInfoSupported() { // Check if there is a default path; if there is, it will already // be set, so leave things alone var p: UnsafeMutablePointer? = nil @@ -624,7 +636,7 @@ fileprivate extension _EasyHandle { try! CFURLSession_easy_setopt_ptr(rawHandle, CFURLSessionOptionPROGRESSDATA, UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())).asError() - if NS_CURL_XFERINFOFUNCTION_SUPPORTED == 1 { + if xferInfoFunctionSupported() { try! CFURLSession_easy_setopt_tc(rawHandle, CFURLSessionOptionXFERINFOFUNCTION, { (userdata: UnsafeMutableRawPointer?, dltotal :Int64, dlnow: Int64, ultotal: Int64, ulnow: Int64) -> Int32 in guard let handle = _EasyHandle.from(callbackUserData: userdata) else { return -1 } handle.updateProgressMeter(with: _Progress(totalBytesSent: ulnow, totalBytesExpectedToSend: ultotal, totalBytesReceived: dlnow, totalBytesExpectedToReceive: dltotal)) diff --git a/Sources/FoundationNetworking/URLSession/libcurl/MultiHandle.swift b/Sources/FoundationNetworking/URLSession/libcurl/MultiHandle.swift index ade21f458a..44389c0b2b 100644 --- a/Sources/FoundationNetworking/URLSession/libcurl/MultiHandle.swift +++ b/Sources/FoundationNetworking/URLSession/libcurl/MultiHandle.swift @@ -65,7 +65,7 @@ extension URLSession { extension URLSession._MultiHandle { func configure(with configuration: URLSession._Configuration) { - if NS_CURL_MAX_HOST_CONNECTIONS_SUPPORTED == 1 { + if maxHostConnectionsSupported() { try! CFURLSession_multi_setopt_l(rawHandle, CFURLSessionMultiOptionMAX_HOST_CONNECTIONS, numericCast(configuration.httpMaximumConnectionsPerHost)).asError() } diff --git a/Sources/FoundationXML/XMLDTD.swift b/Sources/FoundationXML/XMLDTD.swift index 089c1a088b..788a2840a6 100644 --- a/Sources/FoundationXML/XMLDTD.swift +++ b/Sources/FoundationXML/XMLDTD.swift @@ -14,6 +14,9 @@ import Foundation #endif @_implementationOnly import _CFXMLInterface +@available(*, unavailable) +extension XMLDTD : Sendable { } + /*! @class XMLDTD @abstract Defines the order, repetition, and allowable values for a document diff --git a/Sources/FoundationXML/XMLDTDNode.swift b/Sources/FoundationXML/XMLDTDNode.swift index c3859ec0b0..bcd1615cc3 100644 --- a/Sources/FoundationXML/XMLDTDNode.swift +++ b/Sources/FoundationXML/XMLDTDNode.swift @@ -19,7 +19,7 @@ import Foundation @abstract The subkind of a DTD node kind. */ extension XMLDTDNode { - public enum DTDKind : UInt { + public enum DTDKind : UInt, Sendable { case general @@ -66,6 +66,9 @@ extension XMLDTDNode { } } +@available(*, unavailable) +extension XMLDTDNode : Sendable { } + /*! @class XMLDTDNode @abstract The nodes that are exclusive to a DTD diff --git a/Sources/FoundationXML/XMLDocument.swift b/Sources/FoundationXML/XMLDocument.swift index b4fb5fdafa..3634471219 100644 --- a/Sources/FoundationXML/XMLDocument.swift +++ b/Sources/FoundationXML/XMLDocument.swift @@ -47,7 +47,7 @@ extension XMLDocument { @constant XMLDocument.ContentKind.html Outputs empty tags without a close tag, eg
@constant XMLDocument.ContentKind.text Output the string value of the document */ - public enum ContentKind : UInt { + public enum ContentKind : UInt, Sendable { case xml case xhtml @@ -56,6 +56,9 @@ extension XMLDocument { } } +@available(*, unavailable) +extension XMLDocument : Sendable { } + /*! @class XMLDocument @abstract An XML Document diff --git a/Sources/FoundationXML/XMLElement.swift b/Sources/FoundationXML/XMLElement.swift index 9355e8a704..066ce686d1 100644 --- a/Sources/FoundationXML/XMLElement.swift +++ b/Sources/FoundationXML/XMLElement.swift @@ -14,6 +14,9 @@ import Foundation #endif @_implementationOnly import _CFXMLInterface +@available(*, unavailable) +extension XMLElement : Sendable { } + /*! @class XMLElement @abstract An XML element diff --git a/Sources/FoundationXML/XMLNode.swift b/Sources/FoundationXML/XMLNode.swift index 035408bbd3..fe67588b80 100644 --- a/Sources/FoundationXML/XMLNode.swift +++ b/Sources/FoundationXML/XMLNode.swift @@ -32,6 +32,8 @@ import Foundation // Output options // NSXMLNodePrettyPrint +@available(*, unavailable) +extension XMLNode : Sendable { } /*! @class NSXMLNode @@ -39,7 +41,7 @@ import Foundation */ open class XMLNode: NSObject, NSCopying { - public enum Kind : UInt { + public enum Kind : UInt, Sendable { case invalid case document case element @@ -55,7 +57,7 @@ open class XMLNode: NSObject, NSCopying { case notationDeclaration } - public struct Options : OptionSet { + public struct Options : OptionSet, Sendable { public let rawValue : UInt public init(rawValue: UInt) { self.rawValue = rawValue } @@ -1024,6 +1026,9 @@ open class XMLNode: NSObject, NSCopying { internal protocol _NSXMLNodeCollectionType: Collection { } +@available(*, unavailable) +extension XMLNode.Index : Sendable { } + extension XMLNode: _NSXMLNodeCollectionType { public struct Index: Comparable { diff --git a/Sources/FoundationXML/XMLParser.swift b/Sources/FoundationXML/XMLParser.swift index 228f588e88..40d57b334e 100644 --- a/Sources/FoundationXML/XMLParser.swift +++ b/Sources/FoundationXML/XMLParser.swift @@ -16,7 +16,7 @@ import Foundation #endif extension XMLParser { - public enum ExternalEntityResolvingPolicy : UInt { + public enum ExternalEntityResolvingPolicy : UInt, Sendable { case never // default case noNetwork case sameOriginOnly //only applies to NSXMLParser instances initialized with -initWithContentsOfURL: @@ -393,6 +393,9 @@ internal func _structuredErrorFunc(_ interface: _CFXMLInterface, error: _CFXMLIn } } +@available(*, unavailable) +extension XMLParser : Sendable { } + open class XMLParser : NSObject { private var _handler: _CFXMLInterfaceSAXHandler #if !os(WASI) @@ -811,7 +814,7 @@ extension XMLParser { public static let errorDomain: String = "NSXMLParserErrorDomain" // for use with NSError. // Error reporting - public enum ErrorCode : Int { + public enum ErrorCode : Int, Sendable { case internalError diff --git a/Sources/XCTest/Public/Asynchronous/XCTNSNotificationExpectation.swift b/Sources/XCTest/Public/Asynchronous/XCTNSNotificationExpectation.swift index dc86780475..7368327bca 100644 --- a/Sources/XCTest/Public/Asynchronous/XCTNSNotificationExpectation.swift +++ b/Sources/XCTest/Public/Asynchronous/XCTNSNotificationExpectation.swift @@ -11,7 +11,7 @@ // /// Expectation subclass for waiting on a condition defined by a Foundation Notification instance. -open class XCTNSNotificationExpectation: XCTestExpectation { +open class XCTNSNotificationExpectation: XCTestExpectation, @unchecked Sendable { /// A closure to be invoked when a notification specified by the expectation is observed. /// diff --git a/Sources/XCTest/Public/Asynchronous/XCTNSPredicateExpectation.swift b/Sources/XCTest/Public/Asynchronous/XCTNSPredicateExpectation.swift index b41bca147c..13a33c186c 100644 --- a/Sources/XCTest/Public/Asynchronous/XCTNSPredicateExpectation.swift +++ b/Sources/XCTest/Public/Asynchronous/XCTNSPredicateExpectation.swift @@ -11,7 +11,7 @@ // /// Expectation subclass for waiting on a condition defined by an NSPredicate and an optional object. -open class XCTNSPredicateExpectation: XCTestExpectation { +open class XCTNSPredicateExpectation: XCTestExpectation, @unchecked Sendable { /// A closure to be invoked whenever evaluating the predicate against the object returns true. /// @@ -96,8 +96,9 @@ open class XCTNSPredicateExpectation: XCTestExpectation { } runLoop.add(timer, forMode: .default) + nonisolated(unsafe) let t = timer queue.async { - self.timer = timer + self.timer = t } } diff --git a/Tests/Foundation/FTPServer.swift b/Tests/Foundation/FTPServer.swift index 8bb4a9d779..3fcb1128d5 100644 --- a/Tests/Foundation/FTPServer.swift +++ b/Tests/Foundation/FTPServer.swift @@ -75,13 +75,13 @@ class _FTPSocket { let sa1 = createSockaddr(port+1) socketAddress1.initialize(to: sa1) try socketAddress1.withMemoryRebound(to: sockaddr.self, capacity: MemoryLayout.size, { - let addr = UnsafeMutablePointer($0) + nonisolated(unsafe) let addr = UnsafeMutablePointer($0) _ = try attempt("bind", valid: isZero, bind(dataSocket, addr, socklen_t(MemoryLayout.size))) - var sockLen = socklen_t(MemoryLayout.size) _ = try attempt("listen", valid: isZero, listen(dataSocket, SOMAXCONN)) // Open the data port asynchronously. Port should be opened before ESPV header communication. DispatchQueue(label: "delay").async { do { + var sockLen = socklen_t(MemoryLayout.size) self.dataSocket = try self.attempt("accept", valid: self.isNotMinusOne, accept(self.dataSocket, addr, &sockLen)) self.dataSocketPort = sa1.sin_port } catch { @@ -262,7 +262,7 @@ class LoopbackFTPServerTest: XCTestCase { override class func setUp() { super.setUp() - func runServer(with condition: ServerSemaphore, + @Sendable func runServer(with condition: ServerSemaphore, startDelay: TimeInterval? = nil, sendDelay: TimeInterval? = nil, bodyChunks: Int? = nil) throws { let start = 21961 // 21961 diff --git a/Tests/Foundation/HTTPServer.swift b/Tests/Foundation/HTTPServer.swift index 5d713aa702..aac4369f2e 100644 --- a/Tests/Foundation/HTTPServer.swift +++ b/Tests/Foundation/HTTPServer.swift @@ -1144,11 +1144,12 @@ class LoopbackServerTest : XCTestCase { super.tearDown() } - static func startServer() { - var _serverPort = 0 + static func startServer() { + // Protected by dispatchGroup + nonisolated(unsafe) var _serverPort = 0 let dispatchGroup = DispatchGroup() - func runServer() throws { + @Sendable func runServer() throws { testServer = try _HTTPServer(port: nil, backlog: options.serverBacklog) _serverPort = Int(testServer!.port) serverActive = true @@ -1158,7 +1159,7 @@ class LoopbackServerTest : XCTestCase { do { let httpServer = try testServer!.listen() - func handleRequest() { + @Sendable func handleRequest() { let subServer = TestURLSessionServer(httpServer: httpServer) do { try subServer.readAndRespond() diff --git a/Tests/Foundation/TestCalendar.swift b/Tests/Foundation/TestCalendar.swift index 61960d4695..de91500b71 100644 --- a/Tests/Foundation/TestCalendar.swift +++ b/Tests/Foundation/TestCalendar.swift @@ -244,8 +244,6 @@ class TestCalendar: XCTestCase { calendar.locale = Locale(identifier: "en_US_POSIX") calendar.timeZone = try XCTUnwrap(TimeZone(secondsFromGMT: 0)) - let expectedDescription = calendar.timeZone == TimeZone.current ? "GMT (current)" : "GMT (fixed)" - let calendarCopy = calendar XCTAssertEqual(calendarCopy.timeZone.identifier, "GMT") XCTAssertEqual(calendarCopy.timeZone.secondsFromGMT(), 0) diff --git a/Tests/Foundation/TestDataURLProtocol.swift b/Tests/Foundation/TestDataURLProtocol.swift index 30c21c6e6e..3b9bc484dd 100644 --- a/Tests/Foundation/TestDataURLProtocol.swift +++ b/Tests/Foundation/TestDataURLProtocol.swift @@ -7,14 +7,17 @@ // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // +import Synchronization -class DataURLTestDelegate: NSObject, URLSessionTaskDelegate, URLSessionDataDelegate { +final class DataURLTestDelegate: NSObject, URLSessionTaskDelegate, URLSessionDataDelegate, Sendable { - var callbacks: [String] = [] let expectation: XCTestExpectation? - var data: Data? - var error: Error? - var response: URLResponse? + + // This state is setup before running and checked after `expectation`. Unsafe, but would be better with a lock in the future. + nonisolated(unsafe) var callbacks: [String] = [] + nonisolated(unsafe) var data: Data? + nonisolated(unsafe) var error: Error? + nonisolated(unsafe) var response: URLResponse? init(expectation: XCTestExpectation?) { diff --git a/Tests/Foundation/TestFileManager.swift b/Tests/Foundation/TestFileManager.swift index fc2361c346..241fcbac7a 100644 --- a/Tests/Foundation/TestFileManager.swift +++ b/Tests/Foundation/TestFileManager.swift @@ -113,7 +113,7 @@ class TestFileManager : XCTestCase { func test_creatingDirectoryWithShortIntermediatePath() { let fileManager = FileManager.default let cwd = fileManager.currentDirectoryPath - fileManager.changeCurrentDirectoryPath(NSTemporaryDirectory()) + _ = fileManager.changeCurrentDirectoryPath(NSTemporaryDirectory()) let relativePath = NSUUID().uuidString @@ -123,7 +123,7 @@ class TestFileManager : XCTestCase { } catch { XCTFail("Failed to create and clean up directory") } - fileManager.changeCurrentDirectoryPath(cwd) + _ = fileManager.changeCurrentDirectoryPath(cwd) } func test_moveFile() { @@ -646,7 +646,7 @@ class TestFileManager : XCTestCase { try? fm.removeItem(at: root) try XCTAssertNoThrow(fm.createDirectory(at: subdirectory, withIntermediateDirectories: true, attributes: nil)) - try XCTAssertNoThrow(fm.createFile(atPath: file.path, contents: Data(), attributes: nil)) + _ = fm.createFile(atPath: file.path, contents: Data(), attributes: nil) let contents = try XCTUnwrap(fm.contentsOfDirectory(at: root, includingPropertiesForKeys: [.isDirectoryKey], options: [.skipsHiddenFiles])) XCTAssertEqual(contents.count, 1) XCTAssertEqual(contents, [subdirectory]) @@ -1752,8 +1752,9 @@ class TestFileManager : XCTestCase { let operationCount = 10 - var directoryURLs = [URL?](repeating: nil, count: operationCount) - var errors = [Error?](repeating: nil, count: operationCount) + // Protected by dispatchGroup + nonisolated(unsafe) var directoryURLs = [URL?](repeating: nil, count: operationCount) + nonisolated(unsafe) var errors = [Error?](repeating: nil, count: operationCount) let dispatchGroup = DispatchGroup() for operationIndex in 0.. Void in + let encode = { @Sendable (_ data: Date, _ encoder: Encoder) throws -> Void in var container = encoder.singleValueContainer() try container.encode(42) } - let decode = { (_: Decoder) throws -> Date in return timestamp } + let decode = { @Sendable (_: Decoder) throws -> Date in return timestamp } // We can't encode a top-level Date, so it'll be wrapped in an array. let expectedJSON = "[42]".data(using: .utf8)! @@ -351,8 +351,8 @@ class TestJSONEncoder : XCTestCase { let timestamp = Date() // Encoding nothing should encode an empty keyed container ({}). - let encode = { (_: Date, _: Encoder) throws -> Void in } - let decode = { (_: Decoder) throws -> Date in return timestamp } + let encode = { @Sendable (_: Date, _: Encoder) throws -> Void in } + let decode = { @Sendable (_: Decoder) throws -> Date in return timestamp } // We can't encode a top-level Date, so it'll be wrapped in an array. let expectedJSON = "[{}]".data(using: .utf8)! @@ -373,11 +373,11 @@ class TestJSONEncoder : XCTestCase { func test_encodingCustomData() { // We'll encode a number instead of data. - let encode = { (_ data: Data, _ encoder: Encoder) throws -> Void in + let encode = { @Sendable (_ data: Data, _ encoder: Encoder) throws -> Void in var container = encoder.singleValueContainer() try container.encode(42) } - let decode = { (_: Decoder) throws -> Data in return Data() } + let decode = { @Sendable (_: Decoder) throws -> Data in return Data() } // We can't encode a top-level Data, so it'll be wrapped in an array. let expectedJSON = "[42]".data(using: .utf8)! @@ -389,8 +389,8 @@ class TestJSONEncoder : XCTestCase { func test_encodingCustomDataEmpty() { // Encoding nothing should encode an empty keyed container ({}). - let encode = { (_: Data, _: Encoder) throws -> Void in } - let decode = { (_: Decoder) throws -> Data in return Data() } + let encode = { @Sendable (_: Data, _: Encoder) throws -> Void in } + let decode = { @Sendable (_: Decoder) throws -> Data in return Data() } // We can't encode a top-level Data, so it'll be wrapped in an array. let expectedJSON = "[{}]".data(using: .utf8)! diff --git a/Tests/Foundation/TestJSONSerialization.swift b/Tests/Foundation/TestJSONSerialization.swift index b8804ef73a..d1f957d203 100644 --- a/Tests/Foundation/TestJSONSerialization.swift +++ b/Tests/Foundation/TestJSONSerialization.swift @@ -360,9 +360,7 @@ extension TestJSONSerialization { let failingData = Data(failingString.utf8) XCTAssertThrowsError(try getjsonObjectResult(failingData, objectType)) { error in - guard let nserror = (error as? NSError) else { - return XCTFail("Unexpected error: \(error)") - } + let nserror = error as NSError XCTAssertEqual(nserror.domain, NSCocoaErrorDomain) XCTAssertEqual(CocoaError(_nsError: nserror).code, .propertyListReadCorrupt) XCTAssertEqual(nserror.userInfo[NSDebugDescriptionErrorKey] as? String, "Too many nested arrays or dictionaries around character 2561.") @@ -423,9 +421,7 @@ extension TestJSONSerialization { let failingData = Data(failingString.utf8) XCTAssertThrowsError(try getjsonObjectResult(failingData, objectType)) { error in - guard let nserror = (error as? NSError) else { - return XCTFail("Unexpected error: \(error)") - } + let nserror = error as NSError XCTAssertEqual(nserror.domain, NSCocoaErrorDomain) XCTAssertEqual(CocoaError(_nsError: nserror).code, .propertyListReadCorrupt) XCTAssertEqual(nserror.userInfo[NSDebugDescriptionErrorKey] as? String, "Too many nested arrays or dictionaries around character 513.") @@ -512,9 +508,7 @@ extension TestJSONSerialization { return } XCTAssertThrowsError(_ = try getjsonObjectResult(data, objectType)) { error in - guard let nserror = (error as? NSError) else { - return XCTFail("Unexpected error: \(error)") - } + let nserror = error as NSError XCTAssertEqual(nserror.domain, NSCocoaErrorDomain) XCTAssertEqual(CocoaError(_nsError: nserror).code, .propertyListReadCorrupt) XCTAssertEqual(nserror.userInfo[NSDebugDescriptionErrorKey] as? String, "Number with leading zero around character 2.") @@ -527,9 +521,7 @@ extension TestJSONSerialization { let data = Data(subject.utf8) XCTAssertThrowsError(try getjsonObjectResult(data, objectType)) { error in - guard let nserror = (error as? NSError) else { - return XCTFail("Unexpected error: \(error)") - } + let nserror = error as NSError XCTAssertEqual(nserror.domain, NSCocoaErrorDomain) XCTAssertEqual(CocoaError(_nsError: nserror).code, .propertyListReadCorrupt) } @@ -613,9 +605,7 @@ extension TestJSONSerialization { // Check failure to decode without .allowFragments XCTAssertThrowsError(try getjsonObjectResult(data, objectType)) { error in - guard let nserror = (error as? NSError) else { - return XCTFail("Unexpected error: \(error)") - } + let nserror = error as NSError XCTAssertEqual(nserror.domain, NSCocoaErrorDomain) XCTAssertEqual(CocoaError(_nsError: nserror).code, .propertyListReadCorrupt) XCTAssertEqual(nserror.userInfo[NSDebugDescriptionErrorKey] as? String, "JSON text did not start with array or object and option to allow fragments not set.") @@ -643,9 +633,7 @@ extension TestJSONSerialization { let data = Data(json.utf8) XCTAssertThrowsError(try getjsonObjectResult(data, objectType)) { error in - guard let nserror = (error as? NSError) else { - return XCTFail("Unexpected error: \(error)") - } + let nserror = error as NSError XCTAssertEqual(nserror.domain, NSCocoaErrorDomain) XCTAssertEqual(CocoaError(_nsError: nserror).code, .propertyListReadCorrupt) XCTAssertEqual(nserror.userInfo[NSDebugDescriptionErrorKey] as? String, "Unescaped control character around character 2.") @@ -655,9 +643,7 @@ extension TestJSONSerialization { func deserialize_unescapedReversedSolidus(objectType: ObjectType) { XCTAssertThrowsError(try getjsonObjectResult(Data(#"" \ ""#.utf8), objectType, options: .allowFragments)) { error in - guard let nserror = (error as? NSError) else { - return XCTFail("Unexpected error: \(error)") - } + let nserror = error as NSError XCTAssertEqual(nserror.domain, NSCocoaErrorDomain) XCTAssertEqual(CocoaError(_nsError: nserror).code, .propertyListReadCorrupt) XCTAssertEqual(nserror.userInfo[NSDebugDescriptionErrorKey] as? String, "Invalid escape sequence around character 2.") @@ -670,9 +656,7 @@ extension TestJSONSerialization { let data = Data(subject.utf8) XCTAssertThrowsError(_ = try getjsonObjectResult(data, objectType)) { error in - guard let nserror = (error as? NSError) else { - return XCTFail("Unexpected error: \(error)") - } + let nserror = error as NSError XCTAssertEqual(nserror.domain, NSCocoaErrorDomain) XCTAssertEqual(CocoaError(_nsError: nserror).code, .propertyListReadCorrupt) } @@ -683,9 +667,7 @@ extension TestJSONSerialization { let data = Data(subject.utf8) XCTAssertThrowsError(_ = try getjsonObjectResult(data, objectType)) { error in - guard let nserror = (error as? NSError) else { - return XCTFail("Unexpected error: \(error)") - } + let nserror = error as NSError XCTAssertEqual(nserror.domain, NSCocoaErrorDomain) XCTAssertEqual(CocoaError(_nsError: nserror).code, .propertyListReadCorrupt) } @@ -696,9 +678,7 @@ extension TestJSONSerialization { let data = Data(subject.utf8) XCTAssertThrowsError(_ = try getjsonObjectResult(data, objectType)) { error in - guard let nserror = (error as? NSError) else { - return XCTFail("Unexpected error: \(error)") - } + let nserror = error as NSError XCTAssertEqual(nserror.domain, NSCocoaErrorDomain) XCTAssertEqual(CocoaError(_nsError: nserror).code, .propertyListReadCorrupt) XCTAssertEqual(nserror.userInfo[NSDebugDescriptionErrorKey] as? String, "Unexpected end of file during JSON parse.") @@ -710,9 +690,7 @@ extension TestJSONSerialization { let data = Data(subject.utf8) XCTAssertThrowsError(_ = try getjsonObjectResult(data, objectType)) { error in - guard let nserror = (error as? NSError) else { - return XCTFail("Unexpected error: \(error)") - } + let nserror = error as NSError XCTAssertEqual(nserror.domain, NSCocoaErrorDomain) XCTAssertEqual(CocoaError(_nsError: nserror).code, .propertyListReadCorrupt) XCTAssertEqual(nserror.userInfo[NSDebugDescriptionErrorKey] as? String, "Invalid value around character 9.") @@ -724,9 +702,7 @@ extension TestJSONSerialization { let data = Data(subject.utf8) XCTAssertThrowsError(_ = try getjsonObjectResult(data, objectType)) { error in - guard let nserror = (error as? NSError) else { - return XCTFail("Unexpected error: \(error)") - } + let nserror = error as NSError XCTAssertEqual(nserror.domain, NSCocoaErrorDomain) XCTAssertEqual(CocoaError(_nsError: nserror).code, .propertyListReadCorrupt) } @@ -737,9 +713,7 @@ extension TestJSONSerialization { let data = Data(subject.utf8) XCTAssertThrowsError(_ = try getjsonObjectResult(data, objectType)) { error in - guard let nserror = (error as? NSError) else { - return XCTFail("Unexpected error: \(error)") - } + let nserror = error as NSError XCTAssertEqual(nserror.domain, NSCocoaErrorDomain) XCTAssertEqual(CocoaError(_nsError: nserror).code, .propertyListReadCorrupt) XCTAssertEqual(nserror.userInfo[NSDebugDescriptionErrorKey] as? String, "Invalid value around character 1.") @@ -751,9 +725,7 @@ extension TestJSONSerialization { let data = Data(subject.utf8) XCTAssertThrowsError(_ = try getjsonObjectResult(data, objectType)) { error in - guard let nserror = (error as? NSError) else { - return XCTFail("Unexpected error: \(error)") - } + let nserror = error as NSError XCTAssertEqual(nserror.domain, NSCocoaErrorDomain) XCTAssertEqual(CocoaError(_nsError: nserror).code, .propertyListReadCorrupt) } @@ -764,9 +736,7 @@ extension TestJSONSerialization { let data = Data(subject.utf8) XCTAssertThrowsError(_ = try getjsonObjectResult(data, objectType)) { error in - guard let nserror = (error as? NSError) else { - return XCTFail("Unexpected error: \(error)") - } + let nserror = error as NSError XCTAssertEqual(nserror.domain, NSCocoaErrorDomain) XCTAssertEqual(CocoaError(_nsError: nserror).code, .propertyListReadCorrupt) XCTAssertEqual(nserror.userInfo[NSDebugDescriptionErrorKey] as? String, "Invalid escape sequence around character 2.") @@ -777,9 +747,7 @@ extension TestJSONSerialization { let subject = "[\"\\uDFF3\"]" let data = Data(subject.utf8) XCTAssertThrowsError(_ = try getjsonObjectResult(data, objectType)) { error in - guard let nserror = (error as? NSError) else { - return XCTFail("Unexpected error: \(error)") - } + let nserror = error as NSError XCTAssertEqual(nserror.domain, NSCocoaErrorDomain) XCTAssertEqual(CocoaError(_nsError: nserror).code, .propertyListReadCorrupt) XCTAssertEqual(nserror.userInfo[NSDebugDescriptionErrorKey] as? String, "Unable to convert hex escape sequence (no high character) to UTF8-encoded character.") @@ -790,9 +758,7 @@ extension TestJSONSerialization { let subject = "[\"\\uD834\"]" let data = Data(subject.utf8) XCTAssertThrowsError(_ = try getjsonObjectResult(data, objectType)) { error in - guard let nserror = (error as? NSError) else { - return XCTFail("Unexpected error: \(error)") - } + let nserror = error as NSError XCTAssertEqual(nserror.domain, NSCocoaErrorDomain) XCTAssertEqual(CocoaError(_nsError: nserror).code, .propertyListReadCorrupt) XCTAssertEqual(nserror.userInfo[NSDebugDescriptionErrorKey] as? String, "Unexpected end of file during string parse (expected low-surrogate code point but did not find one).") @@ -1545,10 +1511,9 @@ extension TestJSONSerialization { _ = try JSONSerialization.jsonObject(with: data, options: []) } catch let nativeError { - if let error = nativeError as? NSError { - XCTAssertEqual(error.domain, "NSCocoaErrorDomain") - XCTAssertEqual(error.code, 3840) - } + let error = nativeError as NSError + XCTAssertEqual(error.domain, "NSCocoaErrorDomain") + XCTAssertEqual(error.code, 3840) } } diff --git a/Tests/Foundation/TestNSArray.swift b/Tests/Foundation/TestNSArray.swift index c05e6d2be2..fc767cdab4 100644 --- a/Tests/Foundation/TestNSArray.swift +++ b/Tests/Foundation/TestNSArray.swift @@ -594,6 +594,7 @@ class TestNSArray : XCTestCase { } } + @available(*, deprecated) // test of deprecated API, suppress deprecation warning func test_initWithContentsOfFile() { let testFilePath = createTestFile("TestFileOut.txt", _contents: Data(capacity: 234)) if let _ = testFilePath { @@ -611,6 +612,7 @@ class TestNSArray : XCTestCase { } } + @available(*, deprecated) // test of deprecated API, suppress deprecation warning func test_initMutableWithContentsOfFile() { if let testFilePath = createTestFile("TestFileOut.txt", _contents: Data(capacity: 234)) { let a1: NSArray = ["foo", "bar"] @@ -630,6 +632,7 @@ class TestNSArray : XCTestCase { } } + @available(*, deprecated) // test of deprecated API, suppress deprecation warning func test_initMutableWithContentsOfURL() { if let testFilePath = createTestFile("TestFileOut.txt", _contents: Data(capacity: 234)) { let a1: NSArray = ["foo", "bar"] @@ -650,6 +653,7 @@ class TestNSArray : XCTestCase { } } + @available(*, deprecated) // test of deprecated API, suppress deprecation warning func test_writeToFile() { let testFilePath = createTestFile("TestFileOut.txt", _contents: Data(capacity: 234)) if let _ = testFilePath { diff --git a/Tests/Foundation/TestNSDictionary.swift b/Tests/Foundation/TestNSDictionary.swift index 9829ef4091..c19feb93c7 100644 --- a/Tests/Foundation/TestNSDictionary.swift +++ b/Tests/Foundation/TestNSDictionary.swift @@ -179,6 +179,7 @@ class TestNSDictionary : XCTestCase { } } + @available(*, deprecated) // test of deprecated API, suppress deprecation warning func test_initWithContentsOfFile() { let testFilePath = createTestFile("TestFileOut.txt", _contents: Data(capacity: 256)) if let _ = testFilePath { diff --git a/Tests/Foundation/TestNSKeyedUnarchiver.swift b/Tests/Foundation/TestNSKeyedUnarchiver.swift index dc1e3f97fc..fce59cb446 100644 --- a/Tests/Foundation/TestNSKeyedUnarchiver.swift +++ b/Tests/Foundation/TestNSKeyedUnarchiver.swift @@ -37,7 +37,7 @@ class TestNSKeyedUnarchiver : XCTestCase { case .skip: classes = [] case .performWithDefaultClass: - classes = [ (expectedObject as! NSObject).classForCoder ] + classes = [ (expectedObject as NSObject).classForCoder ] case .performWithClasses(let specifiedClasses): classes = specifiedClasses } diff --git a/Tests/Foundation/TestNSLocale.swift b/Tests/Foundation/TestNSLocale.swift index 83ee503c22..0d76756544 100644 --- a/Tests/Foundation/TestNSLocale.swift +++ b/Tests/Foundation/TestNSLocale.swift @@ -100,6 +100,7 @@ class TestNSLocale : XCTestCase { XCTAssertEqual(a1.hashValue, a2.hashValue) } + @available(*, deprecated) // test of deprecated API, suppress deprecation warning func test_staticProperties() { let euroCurrencyCode = "EUR" let spainRegionCode = "ES" diff --git a/Tests/Foundation/TestNSLock.swift b/Tests/Foundation/TestNSLock.swift index 70bca31a73..6cd83b3616 100644 --- a/Tests/Foundation/TestNSLock.swift +++ b/Tests/Foundation/TestNSLock.swift @@ -53,7 +53,8 @@ class TestNSLock: XCTestCase { let endSeconds: Double = 2 let endTime = Date.init(timeIntervalSinceNow: endSeconds) - var threadsStarted = Array(repeating: false, count: threadCount) + // Protected by arrayLock + nonisolated(unsafe) var threadsStarted = Array(repeating: false, count: threadCount) let arrayLock = NSLock() for t in 0.. Void { self.totalBytesWritten = totalBytesWritten + expectation.fulfill() } } - let delegate = AsyncDownloadDelegate() + let expect = expectation(description: "test_asyncDownloadFromURLWithDelegate") + + let delegate = AsyncDownloadDelegate(expectation: expect) let urlString = "http://127.0.0.1:\(TestURLSession.serverPort)/country.txt" let (location, response) = try await URLSession.shared.download(from: URL(string: urlString)!, delegate: delegate) @@ -337,6 +352,7 @@ class TestURLSession: LoopbackServerTest { XCTFail("Did not get response") return } + await waitForExpectations(timeout: 12) XCTAssertEqual(200, httpResponse.statusCode, "HTTP response code is not 200") XCTAssertNotNil(location, "Download location was nil") XCTAssertTrue(delegate.totalBytesWritten > 0) @@ -1253,14 +1269,13 @@ class TestURLSession: LoopbackServerTest { } func test_dataTaskWithSharedDelegate() { - let sharedDelegate = SharedDelegate() let urlString0 = "http://127.0.0.1:\(TestURLSession.serverPort)/Nepal" + let sharedDelegate = SharedDelegate(dataCompletionExpectation: expectation(description: "GET \(urlString0)")) let session = URLSession(configuration: .default, delegate: sharedDelegate, delegateQueue: nil) let dataRequest = URLRequest(url: URL(string: urlString0)!) let dataTask = session.dataTask(with: dataRequest) - sharedDelegate.dataCompletionExpectation = expectation(description: "GET \(urlString0)") dataTask.resume() waitForExpectations(timeout: 20) } @@ -1552,9 +1567,7 @@ class TestURLSession: LoopbackServerTest { let urlString2 = "http://127.0.0.1:\(TestURLSession.serverPort)/echoHeaders" let expect2 = expectation(description: "POST \(urlString2)") - var req2 = URLRequest(url: URL(string: urlString2)!) - req2.httpMethod = "POST" - + let task1 = session.dataTask(with: req1) { (data, response, error) -> Void in defer { expect1.fulfill() } XCTAssertNotNil(data) @@ -1565,6 +1578,9 @@ class TestURLSession: LoopbackServerTest { } XCTAssertNotNil(httpResponse.allHeaderFields["Set-Cookie"]) + var req2 = URLRequest(url: URL(string: urlString2)!) + req2.httpMethod = "POST" + let task2 = session.dataTask(with: req2) { (data, _, error) -> Void in defer { expect2.fulfill() } guard let data = try? XCTUnwrap(data) else { @@ -1621,8 +1637,6 @@ class TestURLSession: LoopbackServerTest { let urlString2 = "http://127.0.0.1:\(TestURLSession.serverPort)/echoHeaders" let expect2 = expectation(description: "POST \(urlString2)") - var req2 = URLRequest(url: URL(string: urlString2)!) - req2.httpMethod = "POST" let task1 = session.dataTask(with: req1) { (data, response, error) -> Void in defer { expect1.fulfill() } @@ -1634,6 +1648,9 @@ class TestURLSession: LoopbackServerTest { } XCTAssertNotNil(httpResponse.allHeaderFields["Set-Cookie"]) + var req2 = URLRequest(url: URL(string: urlString2)!) + req2.httpMethod = "POST" + let task2 = session.dataTask(with: req2) { (data, _, error) -> Void in defer { expect2.fulfill() } guard let data = try? XCTUnwrap(data) else { @@ -1793,7 +1810,7 @@ class TestURLSession: LoopbackServerTest { let delegate = SessionDelegate(with: expectation) let session = URLSession(configuration: .default, delegate: delegate, delegateQueue: nil) - class EmptyTaskDelegate: NSObject, URLSessionTaskDelegate { } + final class EmptyTaskDelegate: NSObject, URLSessionTaskDelegate, Sendable { } let url = URL(string: "http://127.0.0.1:\(TestURLSession.serverPort)/country.txt")! let request = URLRequest(url: url) let task = session.dataTask(with: request) @@ -2111,7 +2128,7 @@ class TestURLSession: LoopbackServerTest { XCTAssertEqual(urlError._nsError.code, NSURLErrorNetworkConnectionLost) } - wait(for: [delegate.expectation], timeout: 50) + await fulfillment(of: [delegate.expectation], timeout: 50) do { _ = try await task.receive() @@ -2146,7 +2163,7 @@ class TestURLSession: LoopbackServerTest { task.cancel(with: .normalClosure, reason: "BuhBye".data(using: .utf8)) } - wait(for: [delegate.expectation], timeout: 50) + await fulfillment(of: [delegate.expectation], timeout: 50) let callbacks = [ "urlSession(_:webSocketTask:didOpenWithProtocol:)", "urlSession(_:webSocketTask:didCloseWith:reason:)", @@ -2180,7 +2197,7 @@ class TestURLSession: LoopbackServerTest { XCTAssertEqual(urlError._nsError.code, NSURLErrorBadServerResponse) } - wait(for: [delegate.expectation], timeout: 50) + await fulfillment(of: [delegate.expectation], timeout: 50) do { _ = try await task.receive() @@ -2220,7 +2237,7 @@ class TestURLSession: LoopbackServerTest { XCTAssertEqual(urlError._nsError.code, NSURLErrorNetworkConnectionLost) } - wait(for: [delegate.expectation], timeout: 50) + await fulfillment(of: [delegate.expectation], timeout: 50) do { _ = try await task.receive() @@ -2242,8 +2259,12 @@ class TestURLSession: LoopbackServerTest { #endif } -class SharedDelegate: NSObject { - var dataCompletionExpectation: XCTestExpectation! +class SharedDelegate: NSObject, @unchecked Sendable { + init(dataCompletionExpectation: XCTestExpectation!) { + self.dataCompletionExpectation = dataCompletionExpectation + } + + let dataCompletionExpectation: XCTestExpectation } extension SharedDelegate: URLSessionDataDelegate { @@ -2258,7 +2279,8 @@ extension SharedDelegate: URLSessionDownloadDelegate { } -class SessionDelegate: NSObject, URLSessionDelegate, URLSessionWebSocketDelegate { +// Sendable note: Access to ivars is essentially serialized by the XCTestExpectation. It would be better to do it with a lock, but this is sufficient for now. +class SessionDelegate: NSObject, URLSessionDelegate, URLSessionWebSocketDelegate, @unchecked Sendable { var expectation: XCTestExpectation! = nil var session: URLSession! = nil var task: URLSessionTask! = nil @@ -2427,7 +2449,8 @@ extension SessionDelegate: URLSessionDataDelegate { } } -class DataTask : NSObject { +// Sendable note: Access to ivars is essentially serialized by the XCTestExpectation. It would be better to do it with a lock, but this is sufficient for now. +class DataTask : NSObject, @unchecked Sendable { let syncQ = dispatchQueueMake("org.swift.TestFoundation.TestURLSession.DataTask.syncQ") let dataTaskExpectation: XCTestExpectation! let protocols: [AnyClass]? @@ -2557,7 +2580,8 @@ extension DataTask : URLSessionTaskDelegate { } } -class DownloadTask : NSObject { +// Sendable note: Access to ivars is essentially serialized by the XCTestExpectation. It would be better to do it with a lock, but this is sufficient for now. +class DownloadTask : NSObject, @unchecked Sendable { var totalBytesWritten: Int64 = 0 var didDownloadExpectation: XCTestExpectation? let didCompleteExpectation: XCTestExpectation @@ -2685,7 +2709,8 @@ class FailFastProtocol: URLProtocol { } } -class HTTPRedirectionDataTask: NSObject { +// Sendable note: Access to ivars is essentially serialized by the XCTestExpectation. It would be better to do it with a lock, but this is sufficient for now. +class HTTPRedirectionDataTask: NSObject, @unchecked Sendable { let dataTaskExpectation: XCTestExpectation! var session: URLSession! = nil var task: URLSessionDataTask! = nil @@ -2770,7 +2795,8 @@ extension HTTPRedirectionDataTask: URLSessionTaskDelegate { } } -class HTTPUploadDelegate: NSObject { +// Sendable note: Access to ivars is essentially serialized by the XCTestExpectation. It would be better to do it with a lock, but this is sufficient for now. +class HTTPUploadDelegate: NSObject, @unchecked Sendable { private(set) var callbacks: [String] = [] var uploadCompletedExpectation: XCTestExpectation! diff --git a/Tests/Foundation/TestURLSessionFTP.swift b/Tests/Foundation/TestURLSessionFTP.swift index ffc1e7fa89..0504422bca 100644 --- a/Tests/Foundation/TestURLSessionFTP.swift +++ b/Tests/Foundation/TestURLSessionFTP.swift @@ -55,7 +55,8 @@ class TestURLSessionFTP : LoopbackFTPServerTest { } } -class FTPDataTask : NSObject { +// Sendable note: Access to ivars is essentially serialized by the XCTestExpectation. It would be better to do it with a lock, but this is sufficient for now. +class FTPDataTask : NSObject, @unchecked Sendable { let dataTaskExpectation: XCTestExpectation! var fileData: NSMutableData = NSMutableData() var session: URLSession! = nil diff --git a/Tests/Foundation/TestXMLDocument.swift b/Tests/Foundation/TestXMLDocument.swift index 5dcbf888df..2f9eaaab9e 100644 --- a/Tests/Foundation/TestXMLDocument.swift +++ b/Tests/Foundation/TestXMLDocument.swift @@ -681,6 +681,7 @@ class TestXMLDocument : LoopbackServerTest { XCTAssertEqual(notationDecl.name, "otherNotation") } + @available(*, deprecated) // test of deprecated API, suppress deprecation warning func test_creatingAnEmptyDocumentAndNode() { _ = XMLDocument() _ = XMLNode() From 8a7cbc4c102b694a626a5cf93c078bc39e18a485 Mon Sep 17 00:00:00 2001 From: Tony Parker Date: Tue, 2 Jul 2024 16:47:35 -0700 Subject: [PATCH 02/15] Upgrade to Swift 6 - Fix associated warnings --- Package.swift | 14 +- Sources/CoreFoundation/CFPlatform.c | 1 + .../include/ForSwiftFoundationOnly.h | 8 +- Sources/Foundation/Bridging.swift | 2 +- Sources/Foundation/Bundle.swift | 2 +- .../Foundation/DateIntervalFormatter.swift | 11 +- Sources/Foundation/FileHandle.swift | 8 +- Sources/Foundation/FileManager+POSIX.swift | 10 +- Sources/Foundation/FileManager.swift | 6 +- Sources/Foundation/Host.swift | 4 +- Sources/Foundation/NSCFCharacterSet.swift | 3 +- Sources/Foundation/NSConcreteValue.swift | 13 +- Sources/Foundation/NSDateComponents.swift | 2 +- Sources/Foundation/NSDictionary.swift | 15 +- Sources/Foundation/NSError.swift | 15 +- Sources/Foundation/NSIndexSet.swift | 12 +- Sources/Foundation/NSKeyedArchiver.swift | 18 +- .../Foundation/NSKeyedArchiverHelpers.swift | 12 +- Sources/Foundation/NSKeyedUnarchiver.swift | 16 +- Sources/Foundation/NSLock.swift | 21 +++ Sources/Foundation/NSNotification.swift | 6 +- Sources/Foundation/NSNumber.swift | 9 +- Sources/Foundation/NSObjCRuntime.swift | 8 +- Sources/Foundation/NSRegularExpression.swift | 7 +- Sources/Foundation/NSString.swift | 77 ++++---- Sources/Foundation/NSStringAPI.swift | 32 +++- Sources/Foundation/NSSwiftRuntime.swift | 73 ++++---- Sources/Foundation/NSURL.swift | 16 +- Sources/Foundation/NSValue.swift | 4 +- Sources/Foundation/NotificationQueue.swift | 4 +- Sources/Foundation/Operation.swift | 23 ++- Sources/Foundation/Port.swift | 35 ++-- Sources/Foundation/Process.swift | 22 ++- Sources/Foundation/Progress.swift | 8 +- Sources/Foundation/RunLoop.swift | 17 +- Sources/Foundation/Thread.swift | 19 +- Sources/Foundation/Timer.swift | 1 + Sources/Foundation/UserDefaults.swift | 20 +- .../HTTPCookieStorage.swift | 10 +- Sources/FoundationNetworking/URLCache.swift | 17 +- .../FoundationNetworking/URLProtocol.swift | 47 +++-- .../URLSession/HTTP/HTTPURLProtocol.swift | 34 ++-- .../URLSession/Message.swift | 3 +- .../URLSession/NativeProtocol.swift | 20 +- .../URLSession/URLSession.swift | 37 +--- .../URLSession/URLSessionTask.swift | 27 +-- .../URLSession/libcurl/EasyHandle.swift | 3 + .../URLSession/libcurl/MultiHandle.swift | 14 +- Sources/FoundationXML/CFAccess.swift | 2 +- Sources/FoundationXML/XMLNode.swift | 6 +- Sources/XCTest/Private/PrintObserver.swift | 4 + Sources/XCTest/Private/SourceLocation.swift | 2 +- Sources/XCTest/Private/WaiterManager.swift | 2 +- .../Public/Asynchronous/XCTWaiter.swift | 13 +- .../XCTestCase+Asynchronous.swift | 3 +- .../Asynchronous/XCTestExpectation.swift | 2 +- .../Public/XCTestCase+Performance.swift | 2 +- Sources/XCTest/Public/XCTestCase.swift | 18 +- .../Public/XCTestObservationCenter.swift | 2 +- Sources/_CFXMLInterface/CFXMLInterface.c | 140 +++++++------- .../_CFXMLInterface/include/CFXMLInterface.h | 130 ++++++------- Tests/Foundation/FTPServer.swift | 35 ++-- Tests/Foundation/FixtureValues.swift | 88 ++++----- Tests/Foundation/HTTPServer.swift | 16 +- .../TestDateIntervalFormatter.swift | 2 +- Tests/Foundation/TestFileHandle.swift | 3 +- Tests/Foundation/TestJSONSerialization.swift | 1 - Tests/Foundation/TestMeasurement.swift | 4 +- Tests/Foundation/TestNotificationCenter.swift | 32 ++-- Tests/Foundation/TestNotificationQueue.swift | 24 +-- Tests/Foundation/TestOperationQueue.swift | 13 +- Tests/Foundation/TestProcess.swift | 55 +++--- Tests/Foundation/TestRunLoop.swift | 10 +- Tests/Foundation/TestURL.swift | 8 +- Tests/Foundation/TestURLSession.swift | 173 +++++++++--------- Tests/Foundation/TestUserDefaults.swift | 2 +- 76 files changed, 826 insertions(+), 752 deletions(-) diff --git a/Package.swift b/Package.swift index e6936a5bb7..6d1fb959d9 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.9 +// swift-tools-version: 6.0 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -67,6 +67,7 @@ let interfaceBuildSettings: [CSetting] = [ let swiftBuildSettings: [SwiftSetting] = [ .define("DEPLOYMENT_RUNTIME_SWIFT"), .define("SWIFT_CORELIBS_FOUNDATION_HAS_THREADS"), + .swiftLanguageVersion(.v6), .unsafeFlags([ "-Xfrontend", "-require-explicit-sendable", @@ -146,7 +147,7 @@ let package = Package( exclude: [ "CMakeLists.txt" ], - swiftSettings:swiftBuildSettings + swiftSettings: swiftBuildSettings ), .target( name: "CoreFoundation", @@ -207,6 +208,9 @@ let package = Package( ], exclude: [ "CMakeLists.txt" + ], + swiftSettings: [ + .swiftLanguageVersion(.v6) ] ), .executableTarget( @@ -215,6 +219,9 @@ let package = Package( "Foundation", "FoundationXML", "FoundationNetworking" + ], + swiftSettings: [ + .swiftLanguageVersion(.v6) ] ), .target( @@ -241,7 +248,8 @@ let package = Package( .copy("Foundation/Resources") ], swiftSettings: [ - .define("NS_FOUNDATION_ALLOWS_TESTABLE_IMPORT") + .define("NS_FOUNDATION_ALLOWS_TESTABLE_IMPORT"), + .swiftLanguageVersion(.v6) ] ), ] diff --git a/Sources/CoreFoundation/CFPlatform.c b/Sources/CoreFoundation/CFPlatform.c index f29378fff4..38d8c5278e 100644 --- a/Sources/CoreFoundation/CFPlatform.c +++ b/Sources/CoreFoundation/CFPlatform.c @@ -1814,6 +1814,7 @@ CF_CROSS_PLATFORM_EXPORT int _CFThreadSetName(_CFThreadRef thread, const char *_ #endif } +// `buf` must be null-terminated CF_CROSS_PLATFORM_EXPORT int _CFThreadGetName(char *buf, int length) { #if SWIFT_CORELIBS_FOUNDATION_HAS_THREADS #if TARGET_OS_MAC diff --git a/Sources/CoreFoundation/include/ForSwiftFoundationOnly.h b/Sources/CoreFoundation/include/ForSwiftFoundationOnly.h index dd41eeb2ee..315dbc23ba 100644 --- a/Sources/CoreFoundation/include/ForSwiftFoundationOnly.h +++ b/Sources/CoreFoundation/include/ForSwiftFoundationOnly.h @@ -335,10 +335,10 @@ struct _NSCFXMLBridgeUntyped { void *kCFErrorLocalizedDescriptionKey; }; -CF_EXPORT struct _NSCFXMLBridgeStrong __NSCFXMLBridgeStrong; -CF_EXPORT struct _NSCFXMLBridgeUntyped __NSCFXMLBridgeUntyped; +CF_EXPORT struct _NSCFXMLBridgeStrong __NSCFXMLBridgeStrong __attribute__((swift_attr("nonisolated(unsafe)"))); +CF_EXPORT struct _NSCFXMLBridgeUntyped __NSCFXMLBridgeUntyped __attribute__((swift_attr("nonisolated(unsafe)"))); -CF_EXPORT struct _CFSwiftBridge __CFSwiftBridge; +CF_EXPORT struct _CFSwiftBridge __CFSwiftBridge __attribute__((swift_attr("nonisolated(unsafe)"))); CF_EXPORT void *_Nullable _CFSwiftRetain(void *_Nullable t); CF_EXPORT void _CFSwiftRelease(void *_Nullable t); @@ -407,7 +407,7 @@ typedef void *_CFThreadSpecificKey; #endif CF_CROSS_PLATFORM_EXPORT Boolean _CFIsMainThread(void); -CF_EXPORT _CFThreadRef _CFMainPThread; +CF_EXPORT _CFThreadRef _CFMainPThread __attribute__((swift_attr("nonisolated(unsafe)"))); CF_EXPORT CFHashCode __CFHashDouble(double d); diff --git a/Sources/Foundation/Bridging.swift b/Sources/Foundation/Bridging.swift index 4539058325..2a4eba95f6 100644 --- a/Sources/Foundation/Bridging.swift +++ b/Sources/Foundation/Bridging.swift @@ -222,7 +222,7 @@ internal final class __SwiftValue : NSObject, NSCopying { return __SwiftValue(value) } - public static let null: AnyObject = NSNull() + public static nonisolated(unsafe) let null: AnyObject = NSNull() override var description: String { String(describing: value) } } diff --git a/Sources/Foundation/Bundle.swift b/Sources/Foundation/Bundle.swift index b8b36d6426..ac5cca3342 100644 --- a/Sources/Foundation/Bundle.swift +++ b/Sources/Foundation/Bundle.swift @@ -34,7 +34,7 @@ open class Bundle: NSObject, @unchecked Sendable { #endif } - private static var _mainBundle : Bundle = { + private static let _mainBundle : Bundle = { return Bundle(cfBundle: CFBundleGetMainBundle()) }() diff --git a/Sources/Foundation/DateIntervalFormatter.swift b/Sources/Foundation/DateIntervalFormatter.swift index ec14b2d2a4..c6a837ee83 100644 --- a/Sources/Foundation/DateIntervalFormatter.swift +++ b/Sources/Foundation/DateIntervalFormatter.swift @@ -83,18 +83,15 @@ internal extension _CFDateIntervalFormatterBoundaryStyle { // DateIntervalFormatter returns nil and NO for all methods in Formatter. open class DateIntervalFormatter: Formatter, @unchecked Sendable { - private let _core: AnyObject - private final var core: CFDateIntervalFormatter { - get { unsafeBitCast(_core, to: CFDateIntervalFormatter.self) } - } + private let core: CFDateIntervalFormatter public override init() { - _core = CFDateIntervalFormatterCreate(nil, nil, kCFDateIntervalFormatterShortStyle, kCFDateIntervalFormatterShortStyle) + core = CFDateIntervalFormatterCreate(nil, nil, kCFDateIntervalFormatterShortStyle, kCFDateIntervalFormatterShortStyle) super.init() } private init(cfFormatter: CFDateIntervalFormatter) { - self._core = cfFormatter + self.core = cfFormatter super.init() } @@ -120,7 +117,7 @@ open class DateIntervalFormatter: Formatter, @unchecked Sendable { object(of: NSLocale.self, from: coder, forKey: "NS.locale")?._cfObject, object(of: NSCalendar.self, from: coder, forKey: "NS.calendar")?._cfObject, object(of: NSTimeZone.self, from: coder, forKey: "NS.timeZone")?._cfObject) - self._core = core + self.core = core super.init(coder: coder) } diff --git a/Sources/Foundation/FileHandle.swift b/Sources/Foundation/FileHandle.swift index f09a382cfd..b07c49acca 100644 --- a/Sources/Foundation/FileHandle.swift +++ b/Sources/Foundation/FileHandle.swift @@ -738,7 +738,7 @@ open class FileHandle : NSObject, @unchecked Sendable { extension FileHandle { - internal static var _stdinFileHandle: FileHandle = { + internal static let _stdinFileHandle: FileHandle = { return FileHandle(fileDescriptor: STDIN_FILENO, closeOnDealloc: false) }() @@ -746,7 +746,7 @@ extension FileHandle { return _stdinFileHandle } - internal static var _stdoutFileHandle: FileHandle = { + internal static let _stdoutFileHandle: FileHandle = { return FileHandle(fileDescriptor: STDOUT_FILENO, closeOnDealloc: false) }() @@ -754,7 +754,7 @@ extension FileHandle { return _stdoutFileHandle } - internal static var _stderrFileHandle: FileHandle = { + internal static let _stderrFileHandle: FileHandle = { return FileHandle(fileDescriptor: STDERR_FILENO, closeOnDealloc: false) }() @@ -762,7 +762,7 @@ extension FileHandle { return _stderrFileHandle } - internal static var _nulldeviceFileHandle: FileHandle = { + internal static let _nulldeviceFileHandle: FileHandle = { class NullDevice: FileHandle, @unchecked Sendable { override var availableData: Data { return Data() diff --git a/Sources/Foundation/FileManager+POSIX.swift b/Sources/Foundation/FileManager+POSIX.swift index 5ec30db4ea..e89b3bf633 100644 --- a/Sources/Foundation/FileManager+POSIX.swift +++ b/Sources/Foundation/FileManager+POSIX.swift @@ -14,6 +14,7 @@ internal func &(left: UInt32, right: mode_t) -> mode_t { #endif @_implementationOnly import CoreFoundation +internal import Synchronization #if os(WASI) import WASILibc @@ -27,6 +28,10 @@ internal var O_TRUNC: Int32 { _getConst_O_TRUNC() } internal var O_WRONLY: Int32 { _getConst_O_WRONLY() } #endif +#if os(Linux) +fileprivate let previousStatxFailed = Mutex(false) +#endif + @_implementationOnly import CoreFoundation extension FileManager { @@ -257,7 +262,8 @@ extension FileManager { } return try _fileSystemRepresentation(withPath: path) { fsRep in - if supportsStatx { + let statxPreviouslyFailed = previousStatxFailed.withLock { $0 } + if supportsStatx && !statxPreviouslyFailed { var statInfo = stat() var btime = timespec() let statxErrno = _stat_with_btime(fsRep, &statInfo, &btime) @@ -266,7 +272,7 @@ extension FileManager { case EPERM, ENOSYS: // statx() may be blocked by a security mechanism (eg libseccomp or Docker) even if the kernel verison is new enough. EPERM or ENONSYS may be reported. // Dont try to use it in future and fallthough to a normal lstat() call. - supportsStatx = false + previousStatxFailed.withLock { $0 = true } return try _statxFallback(atPath: path, withFileSystemRepresentation: fsRep) default: diff --git a/Sources/Foundation/FileManager.swift b/Sources/Foundation/FileManager.swift index b8ba580701..999d6bc756 100644 --- a/Sources/Foundation/FileManager.swift +++ b/Sources/Foundation/FileManager.swift @@ -34,20 +34,20 @@ internal let NativeFSREncoding = String.Encoding.utf8.rawValue #if os(Linux) // statx() is only supported by Linux kernels >= 4.11.0 -internal var supportsStatx: Bool = { +internal let supportsStatx: Bool = { let requiredVersion = OperatingSystemVersion(majorVersion: 4, minorVersion: 11, patchVersion: 0) return ProcessInfo.processInfo.isOperatingSystemAtLeast(requiredVersion) }() // renameat2() is only supported by Linux kernels >= 3.15 -internal var kernelSupportsRenameat2: Bool = { +internal let kernelSupportsRenameat2: Bool = { let requiredVersion = OperatingSystemVersion(majorVersion: 3, minorVersion: 15, patchVersion: 0) return ProcessInfo.processInfo.isOperatingSystemAtLeast(requiredVersion) }() #endif // For testing only: this facility pins the language used by displayName to the passed-in language. -private var _overriddenDisplayNameLanguages: [String]? = nil +private nonisolated(unsafe) var _overriddenDisplayNameLanguages: [String]? = nil extension FileManager { diff --git a/Sources/Foundation/Host.swift b/Sources/Foundation/Host.swift index 9d9f0ebf35..5652b7d256 100644 --- a/Sources/Foundation/Host.swift +++ b/Sources/Foundation/Host.swift @@ -70,7 +70,7 @@ open class Host: NSObject { internal var _names = [String]() internal var _addresses = [String]() - static internal let _current = Host(currentHostName(), .current) + static internal let _cachedCurrentHostName = currentHostName() internal init(_ info: String?, _ type: ResolveType) { _info = info @@ -110,7 +110,7 @@ open class Host: NSObject { } open class func current() -> Host { - return _current + return Host(Self._cachedCurrentHostName, .current) } public convenience init(name: String?) { diff --git a/Sources/Foundation/NSCFCharacterSet.swift b/Sources/Foundation/NSCFCharacterSet.swift index b4431b7547..bff3e8a00c 100644 --- a/Sources/Foundation/NSCFCharacterSet.swift +++ b/Sources/Foundation/NSCFCharacterSet.swift @@ -93,7 +93,8 @@ internal func _CFSwiftCharacterSetCharacterIsMember(_ cset: CFTypeRef, _ ch: Un } internal func _CFSwiftCharacterSetMutableCopy(_ cset: CFTypeRef) -> Unmanaged { - return Unmanaged.passRetained(unsafeBitCast((cset as! NSCharacterSet).mutableCopy(), to: CFMutableCharacterSet.self)) + let copy = (cset as! NSCharacterSet).mutableCopy() as! NSMutableCharacterSet + return Unmanaged.passRetained(unsafeDowncast(copy, to: CFMutableCharacterSet.self)) } internal func _CFSwiftCharacterSetLongCharacterIsMember(_ cset: CFTypeRef, _ ch:UInt32) -> Bool { diff --git a/Sources/Foundation/NSConcreteValue.swift b/Sources/Foundation/NSConcreteValue.swift index cc950bde6d..c28f5b2547 100644 --- a/Sources/Foundation/NSConcreteValue.swift +++ b/Sources/Foundation/NSConcreteValue.swift @@ -8,6 +8,7 @@ // @_implementationOnly import CoreFoundation +internal import Synchronization internal class NSConcreteValue : NSValue, @unchecked Sendable { @@ -61,22 +62,20 @@ internal class NSConcreteValue : NSValue, @unchecked Sendable { } } - private static var _cachedTypeInfo = Dictionary() - private static var _cachedTypeInfoLock = NSLock() + private static let _cachedTypeInfo = Mutex(Dictionary()) private var _typeInfo : TypeInfo private var _storage : UnsafeMutableRawPointer required init(bytes value: UnsafeRawPointer, objCType type: UnsafePointer) { let spec = String(cString: type) - var typeInfo : TypeInfo? = nil - - NSConcreteValue._cachedTypeInfoLock.synchronized { - typeInfo = NSConcreteValue._cachedTypeInfo[spec] + let typeInfo = NSConcreteValue._cachedTypeInfo.withLock { + var typeInfo = $0[spec] if typeInfo == nil { typeInfo = TypeInfo(objCType: spec) - NSConcreteValue._cachedTypeInfo[spec] = typeInfo + $0[spec] = typeInfo } + return typeInfo } guard typeInfo != nil else { diff --git a/Sources/Foundation/NSDateComponents.swift b/Sources/Foundation/NSDateComponents.swift index 0ed5793175..6ba39e552a 100644 --- a/Sources/Foundation/NSDateComponents.swift +++ b/Sources/Foundation/NSDateComponents.swift @@ -29,7 +29,7 @@ // or quantities of the units. // When you create a new one of these, all values begin Undefined. -public var NSDateComponentUndefined: Int = Int.max +public let NSDateComponentUndefined: Int = Int.max @available(*, unavailable) extension NSDateComponents : Sendable { } diff --git a/Sources/Foundation/NSDictionary.swift b/Sources/Foundation/NSDictionary.swift index d656894a79..854368d5ef 100644 --- a/Sources/Foundation/NSDictionary.swift +++ b/Sources/Foundation/NSDictionary.swift @@ -551,19 +551,10 @@ open class NSDictionary : NSObject, NSCopying, NSMutableCopying, NSSecureCoding, } } -#if !os(WASI) - if opts.contains(.concurrent) { - DispatchQueue.concurrentPerform(iterations: count, execute: iteration) - } else { - for idx in 0.. Any? - internal static var userInfoProviders = [String: UserInfoProvider]() + internal typealias UserInfoProvider = @Sendable (_ error: Error, _ key: String) -> Any? + internal static let userInfoProviders = Mutex<[String: UserInfoProvider]>([:]) - open class func setUserInfoValueProvider(forDomain errorDomain: String, provider: (/* @escaping */ (Error, String) -> Any?)?) { - NSError.userInfoProviders[errorDomain] = provider + open class func setUserInfoValueProvider(forDomain errorDomain: String, provider: (@Sendable (Error, String) -> Any?)?) { + NSError.userInfoProviders.withLock { $0[errorDomain] = provider } } - open class func userInfoValueProvider(forDomain errorDomain: String) -> ((Error, String) -> Any?)? { - return NSError.userInfoProviders[errorDomain] + open class func userInfoValueProvider(forDomain errorDomain: String) -> (@Sendable (Error, String) -> Any?)? { + NSError.userInfoProviders.withLock { $0[errorDomain] } } override open var description: String { @@ -882,7 +883,7 @@ func _convertNSErrorToError(_ error: NSError?) -> Error { public // COMPILER_INTRINSIC func _convertErrorToNSError(_ error: Error) -> NSError { if let object = _extractDynamicValue(error as Any) { - return unsafeBitCast(object, to: NSError.self) + return unsafeDowncast(object, to: NSError.self) } else { let domain: String let code: Int diff --git a/Sources/Foundation/NSIndexSet.swift b/Sources/Foundation/NSIndexSet.swift index 49aa791a53..7c8ecd6c32 100644 --- a/Sources/Foundation/NSIndexSet.swift +++ b/Sources/Foundation/NSIndexSet.swift @@ -509,19 +509,11 @@ open class NSIndexSet : NSObject, NSCopying, NSMutableCopying, NSSecureCoding { } } } -#if !os(WASI) - if opts.contains(.concurrent) { - DispatchQueue.concurrentPerform(iterations: Int(rangeSequence.count), execute: iteration) - } else { - for idx in 0..() - private static var _classNameMapLock = NSLock() + private static let _globalClassNameMap = Mutex>([:]) private var _stream : AnyObject private var _flags = ArchiverFlags(rawValue: 0) @@ -226,7 +226,7 @@ open class NSKeyedArchiver : NSCoder { success = true } } else { - let stream = unsafeBitCast(self._stream, to: CFWriteStream.self) + let stream = unsafeDowncast(self._stream, to: CFWriteStream.self) success = CFPropertyListWrite(plist, stream, kCFPropertyListXMLFormat_v1_0, 0, nil) > 0 } @@ -302,8 +302,8 @@ open class NSKeyedArchiver : NSCoder { /// - cls: The class for which to set up a translation mapping. open class func setClassName(_ codedName: String?, for cls: AnyClass) { let clsName = String(describing: type(of: cls)) - _classNameMapLock.synchronized { - _classNameMap[clsName] = codedName + _globalClassNameMap.withLock { + $0[clsName] = codedName } } @@ -910,13 +910,9 @@ open class NSKeyedArchiver : NSCoder { /// Returns `nil` if `NSKeyedArchiver` does not have a translation mapping for `cls`. open class func classNameForClass(_ cls: AnyClass) -> String? { let clsName = String(reflecting: cls) - var mappedClass : String? - - _classNameMapLock.synchronized { - mappedClass = _classNameMap[clsName] + return _globalClassNameMap.withLock { + $0[clsName] } - - return mappedClass } /// Returns the class name with which the archiver encodes instances of a given class. diff --git a/Sources/Foundation/NSKeyedArchiverHelpers.swift b/Sources/Foundation/NSKeyedArchiverHelpers.swift index 341936d851..7460aca18a 100644 --- a/Sources/Foundation/NSKeyedArchiverHelpers.swift +++ b/Sources/Foundation/NSKeyedArchiverHelpers.swift @@ -15,24 +15,24 @@ extension CFKeyedArchiverUID : _NSBridgeable { internal var _nsObject: NSType { return unsafeBitCast(self, to: NSType.self) } } -internal class _NSKeyedArchiverUID : NSObject { +final internal class _NSKeyedArchiverUID : NSObject, Sendable { typealias CFType = CFKeyedArchiverUID - internal var _base = _CFInfo(typeID: _CFKeyedArchiverUIDGetTypeID()) - internal var value : UInt32 = 0 + internal let _base = _CFInfo(typeID: _CFKeyedArchiverUIDGetTypeID()) + internal let value : UInt32 internal var _cfObject : CFType { return unsafeBitCast(self, to: CFType.self) } - override open var _cfTypeID: CFTypeID { + override var _cfTypeID: CFTypeID { return _CFKeyedArchiverUIDGetTypeID() } - open override var hash: Int { + override var hash: Int { return Int(bitPattern: CFHash(_cfObject as CFTypeRef?)) } - open override func isEqual(_ object: Any?) -> Bool { + override func isEqual(_ object: Any?) -> Bool { // no need to compare these? return false } diff --git a/Sources/Foundation/NSKeyedUnarchiver.swift b/Sources/Foundation/NSKeyedUnarchiver.swift index 696e968985..f6eff38d08 100644 --- a/Sources/Foundation/NSKeyedUnarchiver.swift +++ b/Sources/Foundation/NSKeyedUnarchiver.swift @@ -8,6 +8,7 @@ // @_implementationOnly import CoreFoundation +internal import Synchronization @available(*, unavailable) extension NSKeyedUnarchiver : Sendable { } @@ -47,8 +48,7 @@ open class NSKeyedUnarchiver : NSCoder { } } - private static var _classNameMap : Dictionary = [:] - private static var _classNameMapLock = NSLock() + private static let _globalClassNameMap = Mutex>([:]) open weak var delegate: NSKeyedUnarchiverDelegate? @@ -630,8 +630,8 @@ open class NSKeyedUnarchiver : NSCoder { } open class func setClass(_ cls: AnyClass?, forClassName codedName: String) { - _classNameMapLock.synchronized { - _classNameMap[codedName] = cls + _globalClassNameMap.withLock { + $0[codedName] = cls } } @@ -643,13 +643,9 @@ open class NSKeyedUnarchiver : NSCoder { // own table, then if there was no mapping there, the class's. open class func `class`(forClassName codedName: String) -> AnyClass? { - var mappedClass : AnyClass? - - _classNameMapLock.synchronized { - mappedClass = _classNameMap[codedName] + _globalClassNameMap.withLock { + $0[codedName] } - - return mappedClass } open func `class`(forClassName codedName: String) -> AnyClass? { diff --git a/Sources/Foundation/NSLock.swift b/Sources/Foundation/NSLock.swift index fa56161caa..fe1d08b775 100644 --- a/Sources/Foundation/NSLock.swift +++ b/Sources/Foundation/NSLock.swift @@ -87,6 +87,7 @@ open class NSLock: NSObject, NSLocking, @unchecked Sendable { #endif } + @available(*, noasync, message: "Use async-safe scoped locking instead") open func lock() { #if !SWIFT_CORELIBS_FOUNDATION_HAS_THREADS // noop on no thread platforms @@ -97,6 +98,7 @@ open class NSLock: NSObject, NSLocking, @unchecked Sendable { #endif } + @available(*, noasync, message: "Use async-safe scoped locking instead") open func unlock() { #if !SWIFT_CORELIBS_FOUNDATION_HAS_THREADS // noop on no thread platforms @@ -116,6 +118,7 @@ open class NSLock: NSObject, NSLocking, @unchecked Sendable { #endif } + @available(*, noasync, message: "Use async-safe scoped locking instead") open func `try`() -> Bool { #if !SWIFT_CORELIBS_FOUNDATION_HAS_THREADS // noop on no thread platforms @@ -127,6 +130,7 @@ open class NSLock: NSObject, NSLocking, @unchecked Sendable { #endif } + @available(*, noasync, message: "Use async-safe scoped locking instead") open func lock(before limit: Date) -> Bool { #if !SWIFT_CORELIBS_FOUNDATION_HAS_THREADS // noop on no thread platforms @@ -178,10 +182,12 @@ open class NSConditionLock : NSObject, NSLocking, @unchecked Sendable { _value = condition } + @available(*, noasync, message: "Use async-safe scoped locking instead") open func lock() { let _ = lock(before: Date.distantFuture) } + @available(*, noasync, message: "Use async-safe scoped locking instead") open func unlock() { _cond.lock() #if os(Windows) @@ -197,18 +203,22 @@ open class NSConditionLock : NSObject, NSLocking, @unchecked Sendable { return _value } + @available(*, noasync, message: "Use async-safe scoped locking instead") open func lock(whenCondition condition: Int) { let _ = lock(whenCondition: condition, before: Date.distantFuture) } + @available(*, noasync, message: "Use async-safe scoped locking instead") open func `try`() -> Bool { return lock(before: Date.distantPast) } + @available(*, noasync, message: "Use async-safe scoped locking instead") open func tryLock(whenCondition condition: Int) -> Bool { return lock(whenCondition: condition, before: Date.distantPast) } + @available(*, noasync, message: "Use async-safe scoped locking instead") open func unlock(withCondition condition: Int) { _cond.lock() #if os(Windows) @@ -221,6 +231,7 @@ open class NSConditionLock : NSObject, NSLocking, @unchecked Sendable { _cond.unlock() } + @available(*, noasync, message: "Use async-safe scoped locking instead") open func lock(before limit: Date) -> Bool { _cond.lock() while _thread != nil { @@ -238,6 +249,7 @@ open class NSConditionLock : NSObject, NSLocking, @unchecked Sendable { return true } + @available(*, noasync, message: "Use async-safe scoped locking instead") open func lock(whenCondition condition: Int, before limit: Date) -> Bool { _cond.lock() while _thread != nil || _value != condition { @@ -312,6 +324,7 @@ open class NSRecursiveLock: NSObject, NSLocking, @unchecked Sendable { #endif } + @available(*, noasync, message: "Use async-safe scoped locking instead") open func lock() { #if !SWIFT_CORELIBS_FOUNDATION_HAS_THREADS // noop on no thread platforms @@ -322,6 +335,7 @@ open class NSRecursiveLock: NSObject, NSLocking, @unchecked Sendable { #endif } + @available(*, noasync, message: "Use async-safe scoped locking instead") open func unlock() { #if !SWIFT_CORELIBS_FOUNDATION_HAS_THREADS // noop on no thread platforms @@ -341,6 +355,7 @@ open class NSRecursiveLock: NSObject, NSLocking, @unchecked Sendable { #endif } + @available(*, noasync, message: "Use async-safe scoped locking instead") open func `try`() -> Bool { #if !SWIFT_CORELIBS_FOUNDATION_HAS_THREADS // noop on no thread platforms @@ -352,6 +367,7 @@ open class NSRecursiveLock: NSObject, NSLocking, @unchecked Sendable { #endif } + @available(*, noasync, message: "Use async-safe scoped locking instead") open func lock(before limit: Date) -> Bool { #if !SWIFT_CORELIBS_FOUNDATION_HAS_THREADS // noop on no thread platforms @@ -412,6 +428,7 @@ open class NSCondition: NSObject, NSLocking, @unchecked Sendable { cond.deallocate() } + @available(*, noasync, message: "Use async-safe scoped locking instead") open func lock() { #if !SWIFT_CORELIBS_FOUNDATION_HAS_THREADS // noop on no thread platforms @@ -422,6 +439,7 @@ open class NSCondition: NSObject, NSLocking, @unchecked Sendable { #endif } + @available(*, noasync, message: "Use async-safe scoped locking instead") open func unlock() { #if !SWIFT_CORELIBS_FOUNDATION_HAS_THREADS // noop on no thread platforms @@ -432,6 +450,7 @@ open class NSCondition: NSObject, NSLocking, @unchecked Sendable { #endif } + @available(*, noasync, message: "Use async-safe scoped locking instead") open func wait() { #if !SWIFT_CORELIBS_FOUNDATION_HAS_THREADS // noop on no thread platforms @@ -442,6 +461,7 @@ open class NSCondition: NSObject, NSLocking, @unchecked Sendable { #endif } + @available(*, noasync, message: "Use async-safe scoped locking instead") open func wait(until limit: Date) -> Bool { #if !SWIFT_CORELIBS_FOUNDATION_HAS_THREADS // noop on no thread platforms @@ -456,6 +476,7 @@ open class NSCondition: NSObject, NSLocking, @unchecked Sendable { #endif } + @available(*, noasync, message: "Use async-safe scoped locking instead") open func signal() { #if !SWIFT_CORELIBS_FOUNDATION_HAS_THREADS // noop on no thread platforms diff --git a/Sources/Foundation/NSNotification.swift b/Sources/Foundation/NSNotification.swift index d353f6d6a5..f1392513f3 100644 --- a/Sources/Foundation/NSNotification.swift +++ b/Sources/Foundation/NSNotification.swift @@ -89,7 +89,7 @@ open class NSNotification: NSObject, NSCopying, NSCoding { private class NSNotificationReceiver : NSObject { fileprivate var name: Notification.Name? - fileprivate var block: ((Notification) -> Void)? + fileprivate var block: (@Sendable (Notification) -> Void)? fileprivate var sender: AnyObject? fileprivate var queue: OperationQueue? } @@ -176,10 +176,10 @@ open class NotificationCenter: NSObject, @unchecked Sendable { @available(*, unavailable, renamed: "addObserver(forName:object:queue:using:)") open func addObserver(forName name: NSNotification.Name?, object obj: Any?, queue: OperationQueue?, usingBlock block: @escaping (Notification) -> Void) -> NSObjectProtocol { - return addObserver(forName: name, object: obj, queue: queue, using: block) + fatalError() } - open func addObserver(forName name: NSNotification.Name?, object obj: Any?, queue: OperationQueue?, using block: @escaping (Notification) -> Void) -> NSObjectProtocol { + open func addObserver(forName name: NSNotification.Name?, object obj: Any?, queue: OperationQueue?, using block: @Sendable @escaping (Notification) -> Void) -> NSObjectProtocol { let newObserver = NSNotificationReceiver() newObserver.name = name newObserver.block = block diff --git a/Sources/Foundation/NSNumber.swift b/Sources/Foundation/NSNumber.swift index 71e4f7e81f..fc035b380a 100644 --- a/Sources/Foundation/NSNumber.swift +++ b/Sources/Foundation/NSNumber.swift @@ -701,7 +701,8 @@ open class NSNumber : NSValue, @unchecked Sendable { } private convenience init(bytes: UnsafeRawPointer, numberType: CFNumberType) { - let cfnumber = CFNumberCreate(nil, numberType, bytes) + // CFNumber is not Sendable, but we know this is safe + nonisolated(unsafe) let cfnumber = CFNumberCreate(nil, numberType, bytes) self.init(factory: { cast(unsafeBitCast(cfnumber, to: NSNumber.self)) }) } @@ -1157,15 +1158,15 @@ extension CFNumber : _NSBridgeable { } internal func _CFSwiftNumberGetType(_ obj: CFTypeRef) -> CFNumberType { - return unsafeBitCast(obj, to: NSNumber.self)._cfNumberType() + return unsafeDowncast(obj, to: NSNumber.self)._cfNumberType() } internal func _CFSwiftNumberGetValue(_ obj: CFTypeRef, _ valuePtr: UnsafeMutableRawPointer, _ type: CFNumberType) -> Bool { - return unsafeBitCast(obj, to: NSNumber.self)._getValue(valuePtr, forType: type) + return unsafeDowncast(obj, to: NSNumber.self)._getValue(valuePtr, forType: type) } internal func _CFSwiftNumberGetBoolValue(_ obj: CFTypeRef) -> Bool { - return unsafeBitCast(obj, to: NSNumber.self).boolValue + return unsafeDowncast(obj, to: NSNumber.self).boolValue } protocol _NSNumberCastingWithoutBridging { diff --git a/Sources/Foundation/NSObjCRuntime.swift b/Sources/Foundation/NSObjCRuntime.swift index 6906171191..95d939b83f 100644 --- a/Sources/Foundation/NSObjCRuntime.swift +++ b/Sources/Foundation/NSObjCRuntime.swift @@ -328,7 +328,7 @@ internal let _NSClassesRenamedByObjCAPINotes: [(class: AnyClass, objCName: Strin return map }() -fileprivate var mapFromObjCNameToKnownName: [String: String] = { +fileprivate let mapFromObjCNameToKnownName: [String: String] = { var map: [String: String] = [:] for entry in _NSClassesRenamedByObjCAPINotesInNetworkingOrXML { map[entry.objCName] = entry.swiftName @@ -336,7 +336,7 @@ fileprivate var mapFromObjCNameToKnownName: [String: String] = { return map }() -fileprivate var mapFromKnownNameToObjCName: [String: String] = { +fileprivate let mapFromKnownNameToObjCName: [String: String] = { var map: [String: String] = [:] for entry in _NSClassesRenamedByObjCAPINotesInNetworkingOrXML { map[entry.swiftName] = entry.objCName @@ -344,7 +344,7 @@ fileprivate var mapFromKnownNameToObjCName: [String: String] = { return map }() -fileprivate var mapFromObjCNameToClass: [String: AnyClass] = { +fileprivate let mapFromObjCNameToClass: [String: AnyClass] = { var map: [String: AnyClass] = [:] for entry in _NSClassesRenamedByObjCAPINotes { map[entry.objCName] = entry.class @@ -352,7 +352,7 @@ fileprivate var mapFromObjCNameToClass: [String: AnyClass] = { return map }() -fileprivate var mapFromSwiftClassNameToObjCName: [String: String] = { +fileprivate let mapFromSwiftClassNameToObjCName: [String: String] = { var map: [String: String] = [:] for entry in _NSClassesRenamedByObjCAPINotes { map[String(reflecting: entry.class)] = entry.objCName diff --git a/Sources/Foundation/NSRegularExpression.swift b/Sources/Foundation/NSRegularExpression.swift index 8b4376e742..3647f54dd9 100644 --- a/Sources/Foundation/NSRegularExpression.swift +++ b/Sources/Foundation/NSRegularExpression.swift @@ -28,10 +28,7 @@ extension NSRegularExpression { } open class NSRegularExpression: NSObject, NSCopying, NSSecureCoding, @unchecked Sendable { - internal var _internalStorage: AnyObject - internal final var _internal: _CFRegularExpression { - unsafeBitCast(_internalStorage, to: _CFRegularExpression.self) - } + internal var _internal: _CFRegularExpression open override func copy() -> Any { return copy(with: nil) @@ -85,7 +82,7 @@ open class NSRegularExpression: NSObject, NSCopying, NSSecureCoding, @unchecked var error: Unmanaged? let opt = _CFRegularExpressionOptions(rawValue: options.rawValue) if let regex = _CFRegularExpressionCreate(kCFAllocatorSystemDefault, pattern._cfObject, opt, &error) { - _internalStorage = regex + _internal = regex } else { throw error!.takeRetainedValue()._nsObject } diff --git a/Sources/Foundation/NSString.swift b/Sources/Foundation/NSString.swift index cc037a7756..2d71fe2d07 100644 --- a/Sources/Foundation/NSString.swift +++ b/Sources/Foundation/NSString.swift @@ -9,6 +9,7 @@ @_implementationOnly import CoreFoundation +internal import Synchronization public typealias unichar = UInt16 @@ -135,17 +136,18 @@ public struct StringTransform: Equatable, Hashable, RawRepresentable, Sendable { } +// NSCache is marked as non-Sendable, but it actually does have locking in our implementation +fileprivate nonisolated(unsafe) let regularExpressionCache: NSCache = { + let cache = NSCache() + cache.name = "NSRegularExpressionCache" + cache.countLimit = 10 + return cache +}() + internal func _createRegexForPattern(_ pattern: String, _ options: NSRegularExpression.Options) -> NSRegularExpression? { - struct local { - static let __NSRegularExpressionCache: NSCache = { - let cache = NSCache() - cache.name = "NSRegularExpressionCache" - cache.countLimit = 10 - return cache - }() - } + let key = "\(options):\(pattern)" - if let regex = local.__NSRegularExpressionCache.object(forKey: key._nsObject) { + if let regex = regularExpressionCache.object(forKey: key._nsObject) { return regex } @@ -153,7 +155,8 @@ internal func _createRegexForPattern(_ pattern: String, _ options: NSRegularExpr return nil } - local.__NSRegularExpressionCache.setObject(regex, forKey: key._nsObject) + regularExpressionCache.setObject(regex, forKey: key._nsObject) + return regex } @@ -1026,32 +1029,31 @@ extension NSString { return convertedLen != len ? 0 : numBytes } - public class var availableStringEncodings: UnsafePointer { - struct once { - static let encodings: UnsafePointer = { - let cfEncodings = CFStringGetListOfAvailableEncodings()! - var idx = 0 - var numEncodings = 0 - - while cfEncodings.advanced(by: idx).pointee != kCFStringEncodingInvalidId { - idx += 1 - numEncodings += 1 - } - - let theEncodingList = UnsafeMutablePointer.allocate(capacity: numEncodings + 1) - theEncodingList.advanced(by: numEncodings).pointee = 0 // Terminator - - numEncodings -= 1 - while numEncodings >= 0 { - theEncodingList.advanced(by: numEncodings).pointee = - numericCast(CFStringConvertEncodingToNSStringEncoding(cfEncodings.advanced(by: numEncodings).pointee)) - numEncodings -= 1 - } - - return UnsafePointer(theEncodingList) - }() + private static nonisolated(unsafe) let _availableStringEncodings : UnsafePointer = { + let cfEncodings = CFStringGetListOfAvailableEncodings()! + var idx = 0 + var numEncodings = 0 + + while cfEncodings.advanced(by: idx).pointee != kCFStringEncodingInvalidId { + idx += 1 + numEncodings += 1 } - return once.encodings + + let theEncodingList = UnsafeMutablePointer.allocate(capacity: numEncodings + 1) + theEncodingList.advanced(by: numEncodings).pointee = 0 // Terminator + + numEncodings -= 1 + while numEncodings >= 0 { + theEncodingList.advanced(by: numEncodings).pointee = + numericCast(CFStringConvertEncodingToNSStringEncoding(cfEncodings.advanced(by: numEncodings).pointee)) + numEncodings -= 1 + } + + return UnsafePointer(theEncodingList) + }() + + public class var availableStringEncodings: UnsafePointer { + return _availableStringEncodings } public class func localizedName(of encoding: UInt) -> String { @@ -1279,7 +1281,7 @@ extension NSString { } public convenience init?(utf8String nullTerminatedCString: UnsafePointer) { - guard let str = String(validatingUTF8: nullTerminatedCString) else { return nil } + guard let str = String(validatingCString: nullTerminatedCString) else { return nil } self.init(str) } @@ -1296,7 +1298,8 @@ extension NSString { let cf = (loc as! NSLocale)._cfObject str = CFStringCreateWithFormatAndArguments(kCFAllocatorSystemDefault, unsafeBitCast(cf, to: CFDictionary.self), format._cfObject, argList) } else if type(of: loc) === NSDictionary.self { - str = CFStringCreateWithFormatAndArguments(kCFAllocatorSystemDefault, unsafeBitCast(loc, to: CFDictionary.self), format._cfObject, argList) + let dict = (loc as! NSDictionary)._cfObject + str = CFStringCreateWithFormatAndArguments(kCFAllocatorSystemDefault, dict, format._cfObject, argList) } else { fatalError("locale parameter must be a NSLocale or a NSDictionary") } diff --git a/Sources/Foundation/NSStringAPI.swift b/Sources/Foundation/NSStringAPI.swift index 7eeb93c62e..26e99c48e8 100644 --- a/Sources/Foundation/NSStringAPI.swift +++ b/Sources/Foundation/NSStringAPI.swift @@ -137,7 +137,7 @@ extension String { /// Creates a string by copying the data from a given /// null-terminated C array of UTF8-encoded bytes. public init?(utf8String bytes: UnsafePointer) { - if let str = String(validatingUTF8: bytes) { + if let str = String(validatingCString: bytes) { self = str return } @@ -152,16 +152,20 @@ extension String { /// null-terminated array of UTF8-encoded bytes. @_alwaysEmitIntoClient public init?(utf8String bytes: [CChar]) { - // the stdlib's validatingUTF8 [CChar] overload checks for null termination. - if let str = String(validatingUTF8: bytes) { - self = str - return - } guard let nullPosition = bytes.firstIndex(of: 0) else { fatalError( "input of String.init(utf8String:) must be null-terminated" ) } + guard nullPosition != bytes.startIndex else { + self = "" + return + } + let substrBeforeNull = bytes[bytes.startIndex.., encoding enc: Encoding) { if enc == .utf8 || enc == .ascii { - if let str = String(validatingUTF8: cString) { + if let str = String(validatingCString: cString) { if enc == .utf8 || str._guts._isContiguousASCII { self = str return @@ -304,9 +308,19 @@ extension String { /// in a given array, interpreted according to a given encoding. @_alwaysEmitIntoClient public init?(cString: [CChar], encoding enc: Encoding) { + guard let nullPosition = cString.firstIndex(of: 0) else { + fatalError( + "input of String.init(cString:encoding:) must be null-terminated" + ) + } + if enc == .utf8 || enc == .ascii { - // the stdlib's validatingUTF8 [CChar] overload checks for null termination. - if let str = String(validatingUTF8: cString) { + guard nullPosition != cString.startIndex else { + self = "" + return + } + let substrBeforeNull = cString[cString.startIndex..(nil) static var current: _NSNonfileURLContentLoading { - if let external = _NSNonfileURLContentLoader.external { - return external - } else { - guard let type = _typeByName(_SwiftFoundationNetworkingModuleName + "._NSNonfileURLContentLoader") as? _NSNonfileURLContentLoading.Type else { - fatalError("You must link or load module \(_SwiftFoundationNetworkingModuleName) to load non-file: URL content using String(contentsOf:…), Data(contentsOf:…), etc.") + external.withLock { + if let external = $0 { + return external + } else { + guard let type = _typeByName(_SwiftFoundationNetworkingModuleName + "._NSNonfileURLContentLoader") as? _NSNonfileURLContentLoading.Type else { + fatalError("You must link or load module \(_SwiftFoundationNetworkingModuleName) to load non-file: URL content using String(contentsOf:…), Data(contentsOf:…), etc.") + } + + let result = type.init() + $0 = result + return result } - - let result = type.init() - _NSNonfileURLContentLoader.external = result - return result } } } @@ -410,31 +413,31 @@ internal enum _NSNonfileURLContentLoader { extension _NSCFXMLBridgeForFoundationXMLUseOnly : Sendable { } public struct _NSCFXMLBridgeForFoundationXMLUseOnly { - public var originalBridge: UnsafeMutableRawPointer - public var CFArrayGetCount: UnsafeMutableRawPointer - public var CFArrayGetValueAtIndex: UnsafeMutableRawPointer - public var CFErrorCreate: UnsafeMutableRawPointer - public var CFStringCreateWithCString: UnsafeMutableRawPointer - public var CFStringCreateMutable: UnsafeMutableRawPointer - public var CFStringAppend: UnsafeMutableRawPointer - public var CFStringAppendCString: UnsafeMutableRawPointer - public var CFStringGetLength: UnsafeMutableRawPointer - public var CFStringGetMaximumSizeForEncoding: UnsafeMutableRawPointer - public var CFStringGetCString: UnsafeMutableRawPointer - public var CFDataCreateWithBytesNoCopy: UnsafeMutableRawPointer - public var CFRelease: UnsafeMutableRawPointer - public var CFStringCreateWithBytes: UnsafeMutableRawPointer - public var CFArrayCreateMutable: UnsafeMutableRawPointer - public var CFArrayAppendValue: UnsafeMutableRawPointer - public var CFDataGetLength: UnsafeMutableRawPointer - public var CFDataGetBytePtr: UnsafeMutableRawPointer - public var CFDictionaryCreateMutable: UnsafeMutableRawPointer - public var CFDictionarySetValue: UnsafeMutableRawPointer - public var kCFAllocatorSystemDefault: UnsafeMutableRawPointer - public var kCFAllocatorNull: UnsafeMutableRawPointer - public var kCFCopyStringDictionaryKeyCallBacks: UnsafeMutableRawPointer - public var kCFTypeDictionaryValueCallBacks: UnsafeMutableRawPointer - public var kCFErrorLocalizedDescriptionKey: UnsafeMutableRawPointer + public let originalBridge: UnsafeMutableRawPointer + public let CFArrayGetCount: UnsafeMutableRawPointer + public let CFArrayGetValueAtIndex: UnsafeMutableRawPointer + public let CFErrorCreate: UnsafeMutableRawPointer + public let CFStringCreateWithCString: UnsafeMutableRawPointer + public let CFStringCreateMutable: UnsafeMutableRawPointer + public let CFStringAppend: UnsafeMutableRawPointer + public let CFStringAppendCString: UnsafeMutableRawPointer + public let CFStringGetLength: UnsafeMutableRawPointer + public let CFStringGetMaximumSizeForEncoding: UnsafeMutableRawPointer + public let CFStringGetCString: UnsafeMutableRawPointer + public let CFDataCreateWithBytesNoCopy: UnsafeMutableRawPointer + public let CFRelease: UnsafeMutableRawPointer + public let CFStringCreateWithBytes: UnsafeMutableRawPointer + public let CFArrayCreateMutable: UnsafeMutableRawPointer + public let CFArrayAppendValue: UnsafeMutableRawPointer + public let CFDataGetLength: UnsafeMutableRawPointer + public let CFDataGetBytePtr: UnsafeMutableRawPointer + public let CFDictionaryCreateMutable: UnsafeMutableRawPointer + public let CFDictionarySetValue: UnsafeMutableRawPointer + public let kCFAllocatorSystemDefault: UnsafeMutableRawPointer + public let kCFAllocatorNull: UnsafeMutableRawPointer + public let kCFCopyStringDictionaryKeyCallBacks: UnsafeMutableRawPointer + public let kCFTypeDictionaryValueCallBacks: UnsafeMutableRawPointer + public let kCFErrorLocalizedDescriptionKey: UnsafeMutableRawPointer public init() { self.originalBridge = UnsafeMutableRawPointer(&__NSCFXMLBridgeUntyped) diff --git a/Sources/Foundation/NSURL.swift b/Sources/Foundation/NSURL.swift index 8727617fc6..6af73f16ea 100644 --- a/Sources/Foundation/NSURL.swift +++ b/Sources/Foundation/NSURL.swift @@ -1057,7 +1057,7 @@ extension NSURL : _StructTypeBridgeable { internal func _CFSwiftURLCopyResourcePropertyForKey(_ url: CFTypeRef, _ key: CFString, _ valuePointer: UnsafeMutablePointer?>?, _ errorPointer: UnsafeMutablePointer?>?) -> _DarwinCompatibleBoolean { do { let key = URLResourceKey(rawValue: key._swiftObject) - let values = try unsafeBitCast(url, to: NSURL.self).resourceValues(forKeys: [ key ]) + let values = try unsafeDowncast(url, to: NSURL.self).resourceValues(forKeys: [ key ]) let value = values[key] if let value = value { @@ -1087,7 +1087,7 @@ internal func _CFSwiftURLCopyResourcePropertiesForKeys(_ url: CFTypeRef, _ keys: } } - let result = try unsafeBitCast(url, to: NSURL.self).resourceValues(forKeys: swiftKeys) + let result = try unsafeDowncast(url, to: NSURL.self).resourceValues(forKeys: swiftKeys) let finalDictionary = NSMutableDictionary() for entry in result { @@ -1108,7 +1108,7 @@ internal func _CFSwiftURLCopyResourcePropertiesForKeys(_ url: CFTypeRef, _ keys: internal func _CFSwiftURLSetResourcePropertyForKey(_ url: CFTypeRef, _ key: CFString, _ value: CFTypeRef?, _ errorPointer: UnsafeMutablePointer?>?) -> _DarwinCompatibleBoolean { do { let key = URLResourceKey(rawValue: key._swiftObject) - try unsafeBitCast(url, to: NSURL.self).setResourceValue(__SwiftValue.fetch(value), forKey: key) + try unsafeDowncast(url, to: NSURL.self).setResourceValue(__SwiftValue.fetch(value), forKey: key) return true } catch { @@ -1132,7 +1132,7 @@ internal func _CFSwiftURLSetResourcePropertiesForKeys(_ url: CFTypeRef, _ proper } } - try unsafeBitCast(url, to: NSURL.self).setResourceValues(swiftValues) + try unsafeDowncast(url, to: NSURL.self).setResourceValues(swiftValues) return true } catch { if let errorPointer = errorPointer { @@ -1146,20 +1146,20 @@ internal func _CFSwiftURLSetResourcePropertiesForKeys(_ url: CFTypeRef, _ proper internal func _CFSwiftURLClearResourcePropertyCacheForKey(_ url: CFTypeRef, _ key: CFString) { let swiftKey = URLResourceKey(rawValue: key._swiftObject) - unsafeBitCast(url, to: NSURL.self).removeCachedResourceValue(forKey: swiftKey) + unsafeDowncast(url, to: NSURL.self).removeCachedResourceValue(forKey: swiftKey) } internal func _CFSwiftURLClearResourcePropertyCache(_ url: CFTypeRef) { - unsafeBitCast(url, to: NSURL.self).removeAllCachedResourceValues() + unsafeDowncast(url, to: NSURL.self).removeAllCachedResourceValues() } internal func _CFSwiftSetTemporaryResourceValueForKey(_ url: CFTypeRef, _ key: CFString, _ value: CFTypeRef) { - unsafeBitCast(url, to: NSURL.self).setTemporaryResourceValue(__SwiftValue.fetch(value), forKey: URLResourceKey(rawValue: key._swiftObject)) + unsafeDowncast(url, to: NSURL.self).setTemporaryResourceValue(__SwiftValue.fetch(value), forKey: URLResourceKey(rawValue: key._swiftObject)) } internal func _CFSwiftURLResourceIsReachable(_ url: CFTypeRef, _ errorPointer: UnsafeMutablePointer?>?) -> _DarwinCompatibleBoolean { do { - let reachable = try unsafeBitCast(url, to: NSURL.self).checkResourceIsReachable() + let reachable = try unsafeDowncast(url, to: NSURL.self).checkResourceIsReachable() return reachable ? true : false } catch { if let errorPointer = errorPointer { diff --git a/Sources/Foundation/NSValue.swift b/Sources/Foundation/NSValue.swift index fad1680a1e..3c4e406bb2 100644 --- a/Sources/Foundation/NSValue.swift +++ b/Sources/Foundation/NSValue.swift @@ -77,11 +77,11 @@ open class NSValue : NSObject, NSCopying, NSSecureCoding, NSCoding, @unchecked S extension NSValue : _Factory {} internal protocol _Factory { - init(factory: () -> Self) + init(factory: @Sendable () -> Self) } extension _Factory { - init(factory: () -> Self) { + init(factory: @Sendable () -> Self) { self = factory() } } diff --git a/Sources/Foundation/NotificationQueue.swift b/Sources/Foundation/NotificationQueue.swift index 2f83c57e1a..b10701a618 100644 --- a/Sources/Foundation/NotificationQueue.swift +++ b/Sources/Foundation/NotificationQueue.swift @@ -53,7 +53,7 @@ open class NotificationQueue: NSObject { // The NSNotificationQueue instance is associated with current thread. // The _notificationQueueList represents a list of notification queues related to the current thread. - private static var _notificationQueueList = NSThreadSpecific() + private static nonisolated(unsafe) var _notificationQueueList = NSThreadSpecific() internal static var notificationQueueList: NotificationQueueList { return _notificationQueueList.get() { return NSMutableArray() @@ -61,7 +61,7 @@ open class NotificationQueue: NSObject { } // The default notification queue for the current thread. - private static var _defaultQueue = NSThreadSpecific() + private static nonisolated(unsafe) var _defaultQueue = NSThreadSpecific() open class var `default`: NotificationQueue { return _defaultQueue.get() { return NotificationQueue(notificationCenter: NotificationCenter.default) diff --git a/Sources/Foundation/Operation.swift b/Sources/Foundation/Operation.swift index 4b41911977..f70f41b940 100644 --- a/Sources/Foundation/Operation.swift +++ b/Sources/Foundation/Operation.swift @@ -8,7 +8,7 @@ // #if canImport(Dispatch) -import Dispatch +@preconcurrency import Dispatch internal let _NSOperationIsFinished = "isFinished" internal let _NSOperationIsFinishedAlternate = "finished" @@ -81,7 +81,7 @@ open class Operation : NSObject, @unchecked Sendable { internal var __dependencies = [Operation]() internal var __downDependencies = Set>() internal var __unfinishedDependencyCount: Int = 0 - internal var __completion: (() -> Void)? + internal var __completion: (@Sendable () -> Void)? internal var __name: String? internal var __schedule: DispatchWorkItem? internal var __state: __NSOperationState = .initialized @@ -562,7 +562,7 @@ open class Operation : NSObject, @unchecked Sendable { } - open var completionBlock: (() -> Void)? { + open var completionBlock: (@Sendable () -> Void)? { get { _lock() defer { _unlock() } @@ -575,6 +575,7 @@ open class Operation : NSObject, @unchecked Sendable { } } + @available(*, noasync, message: "Use completionBlock or a dependent Operation instead") open func waitUntilFinished() { __waitCondition.lock() while !isFinished { @@ -638,7 +639,7 @@ extension Operation { case high = 4 case veryHigh = 8 - internal static var barrier = 12 + internal static let barrier = 12 internal static let priorities = [ Operation.QueuePriority.barrier, Operation.QueuePriority.veryHigh.rawValue, @@ -651,8 +652,8 @@ extension Operation { } open class BlockOperation : Operation, @unchecked Sendable { - var _block: (() -> Void)? - var _executionBlocks: [() -> Void]? + var _block: (@Sendable () -> Void)? + var _executionBlocks: [@Sendable () -> Void]? public override init() { @@ -678,11 +679,11 @@ open class BlockOperation : Operation, @unchecked Sendable { } } - open var executionBlocks: [() -> Void] { + open var executionBlocks: [@Sendable () -> Void] { get { _lock() defer { _unlock() } - var blocks = [() -> Void]() + var blocks = [@Sendable () -> Void]() if let existing = _block { blocks.append(existing) } @@ -694,7 +695,7 @@ open class BlockOperation : Operation, @unchecked Sendable { } open override func main() { - var blocks = [() -> Void]() + var blocks = [@Sendable () -> Void]() _lock() if let existing = _block { blocks.append(existing) @@ -950,7 +951,7 @@ open class OperationQueue : NSObject, ProgressReporting, @unchecked Sendable { return queue } - static internal var _currentQueue = NSThreadSpecific() + static internal nonisolated(unsafe) var _currentQueue = NSThreadSpecific() internal func _schedule(_ op: Operation) { op._state = .starting @@ -1261,6 +1262,7 @@ open class OperationQueue : NSObject, ProgressReporting, @unchecked Sendable { _addOperations([op], barrier: false) } + @available(*, noasync, message: "Use addBarrierBlock or a dependent Operation instead") open func addOperations(_ ops: [Operation], waitUntilFinished wait: Bool) { _addOperations(ops, barrier: false) if wait { @@ -1392,6 +1394,7 @@ open class OperationQueue : NSObject, ProgressReporting, @unchecked Sendable { } } + @available(*, noasync, message: "Use completionBlock or a dependent Operation instead") open func waitUntilAllOperationsAreFinished() { var ops = _operations(includingBarriers: true) while 0 < ops.count { diff --git a/Sources/Foundation/Port.swift b/Sources/Foundation/Port.swift index 1f0d298aab..d9f1201dda 100644 --- a/Sources/Foundation/Port.swift +++ b/Sources/Foundation/Port.swift @@ -8,6 +8,7 @@ // @_implementationOnly import CoreFoundation +internal import Synchronization // MARK: Port and related types @@ -596,18 +597,17 @@ open class SocketPort : Port { self.init(remoteWithProtocolFamily: PF_INET, socketType: FOUNDATION_SOCK_STREAM, protocol: FOUNDATION_IPPROTO_TCP, address: data) } - private static let remoteSocketCoresLock = NSLock() - private static var remoteSocketCores: [Signature: Core] = [:] + private static let remoteSocketCores = Mutex<[Signature: Core]>([:]) static private func retainedCore(for signature: Signature) -> Core { - return SocketPort.remoteSocketCoresLock.synchronized { - if let core = SocketPort.remoteSocketCores[signature] { + return SocketPort.remoteSocketCores.withLock { + if let core = $0[signature] { return core } else { let core = Core(isUniqued: true) core.signature = signature - SocketPort.remoteSocketCores[signature] = core + $0[signature] = core return core } @@ -654,8 +654,8 @@ open class SocketPort : Port { } if let signatureToRemove = signatureToRemove { - SocketPort.remoteSocketCoresLock.synchronized { - _ = SocketPort.remoteSocketCores.removeValue(forKey: signatureToRemove) + SocketPort.remoteSocketCores.withLock { + _ = $0.removeValue(forKey: signatureToRemove) } } } @@ -1055,18 +1055,19 @@ open class SocketPort : Port { private static let maximumTimeout: TimeInterval = 86400 - private static let sendingSocketsLock = NSLock() - private static var sendingSockets: [SocketKind: CFSocket] = [:] + private static let sendingSockets = Mutex<[SocketKind: CFSocket]>([:]) private final func sendingSocket(for port: SocketPort, before time: TimeInterval) -> CFSocket? { let signature = port.core.signature! let socketKind = signature.socketKind - var context = CFSocketContext() + // Uses a pointer value for comparison only + nonisolated(unsafe) var context = CFSocketContext() context.info = Unmanaged.passUnretained(self).toOpaque() + nonisolated(unsafe) let nonisolatedSelf = self return core.lock.synchronized { - if let connector = core.connectors[signature], CFSocketIsValid(connector) { + if let connector = nonisolatedSelf.core.connectors[signature], CFSocketIsValid(connector) { return connector } else { if signature.socketType == FOUNDATION_SOCK_STREAM { @@ -1077,7 +1078,7 @@ open class SocketPort : Port { } if CFSocketIsValid(connector) && CFSocketConnectToAddress(connector, address._cfObject, timeout) == CFSocketError(0) { - core.connectors[signature] = connector + nonisolatedSelf.core.connectors[signature] = connector self.addToLoopsAssumingLockHeld(connector) return connector } else { @@ -1085,20 +1086,20 @@ open class SocketPort : Port { } } } else { - return SocketPort.sendingSocketsLock.synchronized { + return SocketPort.sendingSockets.withLock { var result: CFSocket? - if signature.socketKind == core.signature.socketKind, - let receiver = core.receiver, CFSocketIsValid(receiver) { + if signature.socketKind == nonisolatedSelf.core.signature.socketKind, + let receiver = nonisolatedSelf.core.receiver, CFSocketIsValid(receiver) { result = receiver - } else if let socket = SocketPort.sendingSockets[socketKind], CFSocketIsValid(socket) { + } else if let socket = $0[socketKind], CFSocketIsValid(socket) { result = socket } if result == nil, let sender = CFSocketCreate(nil, socketKind.protocolFamily, socketKind.socketType, socketKind.protocol, CFOptionFlags(kCFSocketNoCallBack), nil, &context), CFSocketIsValid(sender) { - SocketPort.sendingSockets[socketKind] = sender + $0[socketKind] = sender result = sender } diff --git a/Sources/Foundation/Process.swift b/Sources/Foundation/Process.swift index bad92d2374..7a49932004 100644 --- a/Sources/Foundation/Process.swift +++ b/Sources/Foundation/Process.swift @@ -20,6 +20,8 @@ import struct WinSDK.HANDLE import Darwin #endif +internal import Synchronization + extension Process { public enum TerminationReason : Int, Sendable { case exit @@ -47,9 +49,12 @@ private func WTERMSIG(_ status: Int32) -> Int32 { return status & 0x7f } -private var managerThreadRunLoop : RunLoop? = nil -private var managerThreadRunLoopIsRunning = false -private var managerThreadRunLoopIsRunningCondition = NSCondition() +// Protected by 'Once' below in `setup` +private nonisolated(unsafe) var managerThreadRunLoop : RunLoop? = nil + +// Protected by managerThreadRunLoopIsRunningCondition +private nonisolated(unsafe) var managerThreadRunLoopIsRunning = false +private let managerThreadRunLoopIsRunningCondition = NSCondition() internal let kCFSocketNoCallBack: CFOptionFlags = 0 // .noCallBack cannot be used because empty option flags are imported as unavailable. internal let kCFSocketAcceptCallBack = CFSocketCallBackType.acceptCallBack.rawValue @@ -222,13 +227,10 @@ private func quoteWindowsCommandLine(_ commandLine: [String]) -> String { open class Process: NSObject, @unchecked Sendable { private static func setup() { - struct Once { - static var done = false - static let lock = NSLock() - } + let once = Mutex(false) - Once.lock.synchronized { - if !Once.done { + once.withLock { + if !$0 { let thread = Thread { managerThreadRunLoop = RunLoop.current var emptySourceContext = CFRunLoopSourceContext() @@ -261,7 +263,7 @@ open class Process: NSObject, @unchecked Sendable { managerThreadRunLoopIsRunningCondition.wait() } managerThreadRunLoopIsRunningCondition.unlock() - Once.done = true + $0 = true } } } diff --git a/Sources/Foundation/Progress.swift b/Sources/Foundation/Progress.swift index 0e9552807f..a5a21439a5 100644 --- a/Sources/Foundation/Progress.swift +++ b/Sources/Foundation/Progress.swift @@ -40,7 +40,7 @@ open class Progress : NSObject, @unchecked Sendable { // This is set once, but after initialization private var _portionOfParent : Int64 - static private var _tsdKey = "_Foundation_CurrentProgressKey" + static private let _tsdKey = "_Foundation_CurrentProgressKey" /// The instance of `Progress` associated with the current thread by a previous invocation of `becomeCurrent(withPendingUnitCount:)`, if any. /// @@ -274,7 +274,7 @@ open class Progress : NSObject, @unchecked Sendable { /// A closure to be called when `cancel` is called. /// /// The closure will be called even when the function is called on an ancestor of the receiver. Your closure won't be called on any particular queue. If it must do work on a specific queue then it should schedule that work on that queue. - open var cancellationHandler: (() -> Void)? { + open var cancellationHandler: (@Sendable () -> Void)? { didSet { guard let handler = cancellationHandler else { return } // If we're already cancelled, then invoke it - asynchronously @@ -289,7 +289,7 @@ open class Progress : NSObject, @unchecked Sendable { /// A closure to be called when pause is called. /// /// The closure will be called even when the function is called on an ancestor of the receiver. Your closure won't be called on any particular queue. If it must do work on a specific queue then it should schedule that work on that queue. - open var pausingHandler: (() -> Void)? { + open var pausingHandler: (@Sendable () -> Void)? { didSet { guard let handler = pausingHandler else { return } // If we're already paused, then invoke it - asynchronously @@ -305,7 +305,7 @@ open class Progress : NSObject, @unchecked Sendable { /// A closure to be called when resume is called. /// /// The closure will be called even when the function is called on an ancestor of the receiver. Your closure won't be called on any particular queue. If it must do work on a specific queue then it should schedule that work on that queue. - open var resumingHandler: (() -> Void)? + open var resumingHandler: (@Sendable () -> Void)? /// Returns `true` if the progress is indeterminate. /// diff --git a/Sources/Foundation/RunLoop.swift b/Sources/Foundation/RunLoop.swift index ad7d79a558..66f629e0d3 100644 --- a/Sources/Foundation/RunLoop.swift +++ b/Sources/Foundation/RunLoop.swift @@ -83,7 +83,8 @@ open class RunLoop: NSObject { set { _cfRunLoopStorage = newValue } } - internal static var _mainRunLoop: RunLoop = { + // TODO: We need some kind API to expose only the Sendable part of the main run loop + internal static nonisolated(unsafe) var _mainRunLoop: RunLoop = { let cfObject: CFRunLoop! = CFRunLoopGetMain() #if os(Windows) // Enable the main runloop on Windows to process the Windows UI events. @@ -98,6 +99,7 @@ open class RunLoop: NSObject { _cfRunLoopStorage = cfObject } + @available(*, noasync) open class var current: RunLoop { return _CFRunLoopGet2(CFRunLoopGetCurrent()) as! RunLoop } @@ -148,8 +150,10 @@ open class RunLoop: NSObject { let shouldStartMonitoring = monitoredPortObservers[aPort] == nil if shouldStartMonitoring { - monitoredPortObservers[aPort] = NotificationCenter.default.addObserver(forName: Port.didBecomeInvalidNotification, object: aPort, queue: nil, using: { [weak self] (notification) in - self?.portDidInvalidate(aPort) + nonisolated(unsafe) let strongNonisolatedSelf = self + nonisolated(unsafe) let nonisolatedPort = aPort + monitoredPortObservers[aPort] = NotificationCenter.default.addObserver(forName: Port.didBecomeInvalidNotification, object: aPort, queue: nil, using: { (notification) in + strongNonisolatedSelf.portDidInvalidate(nonisolatedPort) }) } @@ -215,6 +219,7 @@ open class RunLoop: NSObject { return Date(timeIntervalSinceReferenceDate: nextTimerFireAbsoluteTime) } + @available(*, noasync) open func acceptInput(forMode mode: String, before limitDate: Date) { if _cfRunLoop !== CFRunLoopGetCurrent() { return @@ -225,15 +230,17 @@ open class RunLoop: NSObject { } extension RunLoop { - + @available(*, noasync) public func run() { while run(mode: .default, before: Date.distantFuture) { } } + @available(*, noasync) public func run(until limitDate: Date) { while run(mode: .default, before: limitDate) && limitDate.timeIntervalSinceReferenceDate > CFAbsoluteTimeGetCurrent() { } } + @available(*, noasync) public func run(mode: RunLoop.Mode, before limitDate: Date) -> Bool { if _cfRunLoop !== CFRunLoopGetCurrent() { return false @@ -338,7 +345,7 @@ extension RunLoop { // @available(*, deprecated, message: "For XCTest use only.") public class _Observer { fileprivate let _cfObserverStorage: AnyObject - fileprivate var cfObserver: CFRunLoopObserver { unsafeBitCast(_cfObserverStorage, to: CFRunLoopObserver.self) } + fileprivate var cfObserver: CFRunLoopObserver { unsafeDowncast(_cfObserverStorage, to: CFRunLoopObserver.self) } fileprivate init(activities: _Activities, repeats: Bool, order: Int, handler: @escaping (_Activity) -> Void) { self._cfObserverStorage = CFRunLoopObserverCreateWithHandler(kCFAllocatorSystemDefault, CFOptionFlags(activities.rawValue), repeats, CFIndex(order), { (cfObserver, cfActivity) in diff --git a/Sources/Foundation/Thread.swift b/Sources/Foundation/Thread.swift index 7e2854a5c7..b72ad92e77 100644 --- a/Sources/Foundation/Thread.swift +++ b/Sources/Foundation/Thread.swift @@ -81,7 +81,8 @@ extension Thread : Sendable { } open class Thread : NSObject { - static internal var _currentThread = NSThreadSpecific() + static internal nonisolated(unsafe) var _currentThread = NSThreadSpecific() + @available(*, noasync) open class var current: Thread { return Thread._currentThread.get() { if Thread.isMainThread { @@ -101,7 +102,7 @@ open class Thread : NSObject { } // !!! NSThread's mainThread property is incorrectly exported as "main", which conflicts with its "main" method. - private static let _mainThread: Thread = { + private static nonisolated(unsafe) let _mainThread: Thread = { var thread = Thread(thread: _CFMainPThread) thread._status = .executing return thread @@ -124,6 +125,7 @@ open class Thread : NSObject { return true } + @available(*, noasync) open class func sleep(until date: Date) { #if os(Windows) var hTimer: HANDLE = CreateWaitableTimerW(nil, true, nil) @@ -161,6 +163,7 @@ open class Thread : NSObject { #endif } + @available(*, noasync) open class func sleep(forTimeInterval interval: TimeInterval) { #if os(Windows) var hTimer: HANDLE = CreateWaitableTimerW(nil, true, nil) @@ -196,6 +199,7 @@ open class Thread : NSObject { #endif } + @available(*, noasync) open class func exit() { Thread.current._status = .finished #if os(Windows) @@ -299,11 +303,19 @@ open class Thread : NSObject { return "" } #else + // Result is null-terminated guard _CFThreadGetName(&buf, Int32(buf.count)) == 0 else { return "" } #endif - return String(cString: buf) + guard let firstNull = buf.firstIndex(of: 0) else { + return "" + } + if firstNull == buf.startIndex { + return "" + } else { + return String(validating: buf[buf.startIndex.. Void) -> Timer { let timer = Timer(fire: Date(timeIntervalSinceNow: interval), interval: interval, repeats: repeats, block: block) CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer._timer!, kCFRunLoopDefaultMode) diff --git a/Sources/Foundation/UserDefaults.swift b/Sources/Foundation/UserDefaults.swift index f975256d63..6b95707f62 100644 --- a/Sources/Foundation/UserDefaults.swift +++ b/Sources/Foundation/UserDefaults.swift @@ -8,9 +8,10 @@ // @_implementationOnly import CoreFoundation +internal import Synchronization -private var registeredDefaults = [String: Any]() -private var sharedDefaults = UserDefaults() +private let registeredDefaults = Mutex<[String: Any]>([:]) +private nonisolated(unsafe) var sharedDefaults = UserDefaults() // the default one is thread safe, at least fileprivate func bridgeFromNSCFTypeIfNeeded(_ value: Any) -> Any { let object = value as AnyObject @@ -109,7 +110,9 @@ open class UserDefaults: NSObject { } func getFromRegistered() -> Any? { - return UserDefaults._unboxingNSNumbers(registeredDefaults[defaultName]) + registeredDefaults.withLock { + UserDefaults._unboxingNSNumbers($0[defaultName]) + } } guard let anObj = CFPreferencesCopyAppValue(defaultName._cfObject, suite?._cfObject ?? kCFPreferencesCurrentApplication) else { @@ -321,7 +324,11 @@ open class UserDefaults: NSObject { } open func register(defaults registrationDictionary: [String : Any]) { - registeredDefaults.merge(registrationDictionary.mapValues(bridgeFromNSCFTypeIfNeeded), uniquingKeysWith: { $1 }) + // The 'Any' values here must be Property List types, which are all Sendable + nonisolated(unsafe) let copy = registrationDictionary + registeredDefaults.withLock { + $0.merge(copy.mapValues(bridgeFromNSCFTypeIfNeeded), uniquingKeysWith: { $1 }) + } } open func addSuite(named suiteName: String) { @@ -336,7 +343,7 @@ open class UserDefaults: NSObject { } private func _dictionaryRepresentation(includingVolatileDomains: Bool) -> [String: Any] { - let registeredDefaultsIfAllowed = includingVolatileDomains ? registeredDefaults : [:] + let registeredDefaultsIfAllowed = includingVolatileDomains ? registeredDefaults.withLock { $0 } : [:] let defaultsFromDiskCF = CFPreferencesCopyMultiple(nil, suite?._cfObject ?? kCFPreferencesCurrentApplication, kCFPreferencesCurrentUser, kCFPreferencesAnyHost) let defaultsFromDiskWithNumbersBoxed = __SwiftValue.fetch(defaultsFromDiskCF) as? [String: Any] ?? [:] @@ -354,7 +361,8 @@ open class UserDefaults: NSObject { } } - private static let _parsedArgumentsDomain: [String: Any] = UserDefaults._parseArguments(ProcessInfo.processInfo.arguments) + // We know, but cannot prove statically, that the 'Any' here is only property list types, which are all Sendable + private static nonisolated(unsafe) let _parsedArgumentsDomain: [String: Any] = UserDefaults._parseArguments(ProcessInfo.processInfo.arguments) private var _volatileDomains: [String: [String: Any]] = [:] private let _volatileDomainsLock = NSLock() diff --git a/Sources/FoundationNetworking/HTTPCookieStorage.swift b/Sources/FoundationNetworking/HTTPCookieStorage.swift index 4c10667f63..8881f59eee 100644 --- a/Sources/FoundationNetworking/HTTPCookieStorage.swift +++ b/Sources/FoundationNetworking/HTTPCookieStorage.swift @@ -13,6 +13,7 @@ import SwiftFoundation import Foundation #endif +internal import Synchronization /*! @enum HTTPCookie.AcceptPolicy @@ -42,8 +43,7 @@ extension HTTPCookie { open class HTTPCookieStorage: NSObject, @unchecked Sendable { private static let sharedStorage = HTTPCookieStorage(cookieStorageName: "shared") - private static var sharedCookieStorages: [String: HTTPCookieStorage] = [:] //for group storage containers - private static let sharedSyncQ = DispatchQueue(label: "org.swift.HTTPCookieStorage.sharedSyncQ") + private static let sharedCookieStorages = Mutex<[String: HTTPCookieStorage]>([:]) //for group storage containers /* only modified in init */ private var cookieFilePath: String? @@ -151,10 +151,10 @@ open class HTTPCookieStorage: NSObject, @unchecked Sendable { method with the same identifier will return the same cookie storage instance. */ open class func sharedCookieStorage(forGroupContainerIdentifier identifier: String) -> HTTPCookieStorage { - return sharedSyncQ.sync { - guard let cookieStorage = sharedCookieStorages[identifier] else { + sharedCookieStorages.withLock { + guard let cookieStorage = $0[identifier] else { let newCookieStorage = HTTPCookieStorage(cookieStorageName: identifier) - sharedCookieStorages[identifier] = newCookieStorage + $0[identifier] = newCookieStorage return newCookieStorage } return cookieStorage diff --git a/Sources/FoundationNetworking/URLCache.swift b/Sources/FoundationNetworking/URLCache.swift index 5898355cf4..1284d6d6bf 100644 --- a/Sources/FoundationNetworking/URLCache.swift +++ b/Sources/FoundationNetworking/URLCache.swift @@ -13,6 +13,8 @@ import SwiftFoundation import Foundation #endif +internal import Synchronization + internal extension NSLock { func performLocked(_ block: () throws -> T) rethrows -> T { lock(); defer { unlock() } @@ -195,8 +197,7 @@ open class CachedURLResponse : NSObject, NSCopying, @unchecked Sendable { open class URLCache : NSObject, @unchecked Sendable { - private static let sharedLock = NSLock() - private static var _shared: URLCache? + private static let _shared = Mutex(nil) /*! @method sharedURLCache @@ -217,19 +218,19 @@ open class URLCache : NSObject, @unchecked Sendable { */ open class var shared: URLCache { get { - return sharedLock.performLocked { - if let shared = _shared { + return _shared.withLock { + if let shared = $0 { return shared } let shared = URLCache(memoryCapacity: 4 * 1024 * 1024, diskCapacity: 20 * 1024 * 1024, diskPath: nil) - _shared = shared + $0 = shared return shared } } set { - sharedLock.performLocked { - _shared = newValue + _shared.withLock { + $0 = newValue } } } @@ -665,7 +666,7 @@ open class URLCache : NSObject, @unchecked Sendable { storeCachedResponse(cachedResponse, for: request) } - open func getCachedResponse(for dataTask: URLSessionDataTask, completionHandler: @escaping (CachedURLResponse?) -> Void) { + open func getCachedResponse(for dataTask: URLSessionDataTask, completionHandler: @Sendable @escaping (CachedURLResponse?) -> Void) { guard let request = dataTask.currentRequest else { completionHandler(nil) return diff --git a/Sources/FoundationNetworking/URLProtocol.swift b/Sources/FoundationNetworking/URLProtocol.swift index 7c12ddceaf..5538e8991b 100644 --- a/Sources/FoundationNetworking/URLProtocol.swift +++ b/Sources/FoundationNetworking/URLProtocol.swift @@ -13,6 +13,8 @@ import SwiftFoundation import Foundation #endif +internal import Synchronization + /*! @header URLProtocol.h @@ -167,8 +169,7 @@ extension URLProtocol : Sendable { } */ open class URLProtocol : NSObject { - private static var _registeredProtocolClasses = [AnyClass]() - private static var _classesLock = NSLock() + private static let _registeredProtocolClasses = Mutex<[AnyClass]>([]) //TODO: The right way to do this is using URLProtocol.property(forKey:in) and URLProtocol.setProperty(_:forKey:in) var properties: [URLProtocol._PropertyKey: Any] = [:] @@ -376,13 +377,11 @@ open class URLProtocol : NSObject { */ open class func registerClass(_ protocolClass: AnyClass) -> Bool { if protocolClass is URLProtocol.Type { - _classesLock.lock() - guard !_registeredProtocolClasses.contains(where: { $0 === protocolClass }) else { - _classesLock.unlock() - return true + _registeredProtocolClasses.withLock { + if !$0.contains(where: { $0 === protocolClass }) { + $0.append(protocolClass) + } } - _registeredProtocolClasses.append(protocolClass) - _classesLock.unlock() return true } return false @@ -391,24 +390,22 @@ open class URLProtocol : NSObject { internal class func getProtocolClass(protocols: [AnyClass], request: URLRequest) -> AnyClass? { // Registered protocols are consulted in reverse order. // This behaviour makes the latest registered protocol to be consulted first - _classesLock.lock() - let protocolClasses = protocols - for protocolClass in protocolClasses { - let urlProtocolClass: AnyClass = protocolClass - guard let urlProtocol = urlProtocolClass as? URLProtocol.Type else { fatalError() } - if urlProtocol.canInit(with: request) { - _classesLock.unlock() - return urlProtocol + _registeredProtocolClasses.withLock { _ in + // TODO: It appears this code does not access any data protected within the lock. Still, we lock for compatibility with previous code. + let protocolClasses = protocols + for protocolClass in protocolClasses { + let urlProtocolClass: AnyClass = protocolClass + guard let urlProtocol = urlProtocolClass as? URLProtocol.Type else { fatalError() } + if urlProtocol.canInit(with: request) { + return urlProtocol + } } + return nil } - _classesLock.unlock() - return nil } internal class func getProtocols() -> [AnyClass]? { - _classesLock.lock() - defer { _classesLock.unlock() } - return _registeredProtocolClasses + _registeredProtocolClasses.withLock { $0 } } /*! @method unregisterClass: @@ -418,11 +415,11 @@ open class URLProtocol : NSObject { @param protocolClass The class to unregister. */ open class func unregisterClass(_ protocolClass: AnyClass) { - _classesLock.lock() - if let idx = _registeredProtocolClasses.firstIndex(where: { $0 === protocolClass }) { - _registeredProtocolClasses.remove(at: idx) + _registeredProtocolClasses.withLock { + if let idx = $0.firstIndex(where: { $0 === protocolClass }) { + $0.remove(at: idx) + } } - _classesLock.unlock() } open class func canInit(with task: URLSessionTask) -> Bool { diff --git a/Sources/FoundationNetworking/URLSession/HTTP/HTTPURLProtocol.swift b/Sources/FoundationNetworking/URLSession/HTTP/HTTPURLProtocol.swift index a3050722a8..90a196114e 100644 --- a/Sources/FoundationNetworking/URLSession/HTTP/HTTPURLProtocol.swift +++ b/Sources/FoundationNetworking/URLSession/HTTP/HTTPURLProtocol.swift @@ -413,23 +413,21 @@ internal class _HTTPURLProtocol: _NativeProtocol { if request.isTimeoutIntervalSet { timeoutInterval = Int(request.timeoutInterval) * 1000 } - let timeoutHandler = DispatchWorkItem { [weak self] in - guard let self = self, let task = self.task else { - fatalError("Timeout on a task that doesn't exist") - } //this guard must always pass - + // Access to self is protected by the work item executing on the correct queue + nonisolated(unsafe) let nonisolatedSelf = self + let timeoutHandler = DispatchWorkItem { // If a timeout occurred while waiting for a redirect completion handler to be called by // the delegate then terminate the task but DONT set the error to NSURLErrorTimedOut. // This matches Darwin. - if case .waitingForRedirectCompletionHandler(response: let response,_) = self.internalState { - task.response = response - self.easyHandle.timeoutTimer = nil - self.internalState = .taskCompleted + if case .waitingForRedirectCompletionHandler(response: let response,_) = nonisolatedSelf.internalState { + nonisolatedSelf.task!.response = response + nonisolatedSelf.easyHandle.timeoutTimer = nil + nonisolatedSelf.internalState = .taskCompleted } else { - self.internalState = .transferFailed + nonisolatedSelf.internalState = .transferFailed let urlError = URLError(_nsError: NSError(domain: NSURLErrorDomain, code: NSURLErrorTimedOut, userInfo: nil)) - self.completeTask(withError: urlError) - self.client?.urlProtocol(self, didFailWithError: urlError) + nonisolatedSelf.completeTask(withError: urlError) + nonisolatedSelf.client?.urlProtocol(self, didFailWithError: urlError) } } @@ -486,11 +484,13 @@ internal class _HTTPURLProtocol: _NativeProtocol { // before we call delegate API self.internalState = .waitingForRedirectCompletionHandler(response: response, bodyDataDrain: bodyDataDrain) // We need this ugly cast in order to be able to support `URLSessionTask.init()` + let task = self.task! + // Self state is protected by the dispatch queue + nonisolated(unsafe) let nonisolatedSelf = self session.delegateQueue.addOperation { - delegate.urlSession(session, task: self.task!, willPerformHTTPRedirection: response as! HTTPURLResponse, newRequest: request) { [weak self] (request: URLRequest?) in - guard let self = self else { return } - self.task?.workQueue.async { - self.didCompleteRedirectCallback(request) + delegate.urlSession(session, task: task, willPerformHTTPRedirection: response as! HTTPURLResponse, newRequest: request) { (request: URLRequest?) in + task.workQueue.async { + nonisolatedSelf.didCompleteRedirectCallback(request) } } } @@ -686,7 +686,7 @@ internal class _HTTPURLProtocol: _NativeProtocol { } } -fileprivate var userAgentString: String = { +fileprivate let userAgentString: String = { // Darwin uses something like this: "xctest (unknown version) CFNetwork/760.4.2 Darwin/15.4.0 (x86_64)" let info = ProcessInfo.processInfo let name = info.processName diff --git a/Sources/FoundationNetworking/URLSession/Message.swift b/Sources/FoundationNetworking/URLSession/Message.swift index 4af28f6a0f..09d1019aa8 100644 --- a/Sources/FoundationNetworking/URLSession/Message.swift +++ b/Sources/FoundationNetworking/URLSession/Message.swift @@ -116,5 +116,6 @@ struct _Delimiters { static let DoubleQuote = UnicodeScalar(0x22)! static let Equals = UnicodeScalar(0x3d)! /// *Separators* according to RFC 2616 - static let Separators = NSCharacterSet(charactersIn: "()<>@,;:\\\"/[]?={} \t") + // Sendable-safe due to immutability + static nonisolated(unsafe) let Separators = NSCharacterSet(charactersIn: "()<>@,;:\\\"/[]?={} \t") } diff --git a/Sources/FoundationNetworking/URLSession/NativeProtocol.swift b/Sources/FoundationNetworking/URLSession/NativeProtocol.swift index 22931c8018..ad6836dc9a 100644 --- a/Sources/FoundationNetworking/URLSession/NativeProtocol.swift +++ b/Sources/FoundationNetworking/URLSession/NativeProtocol.swift @@ -414,16 +414,17 @@ internal class _NativeProtocol: URLProtocol, _EasyHandleDelegate { // Check if the cached response is good to use: if let cachedResponse = cachedResponse, canRespondFromCache(using: cachedResponse) { self.internalState = .fulfillingFromCache(cachedResponse) + nonisolated(unsafe) let nonisolatedSelf = self task?.workQueue.async { - self.client?.urlProtocol(self, cachedResponseIsValid: cachedResponse) - self.client?.urlProtocol(self, didReceive: cachedResponse.response, cacheStoragePolicy: .notAllowed) + nonisolatedSelf.client?.urlProtocol(nonisolatedSelf, cachedResponseIsValid: cachedResponse) + nonisolatedSelf.client?.urlProtocol(nonisolatedSelf, didReceive: cachedResponse.response, cacheStoragePolicy: .notAllowed) if !cachedResponse.data.isEmpty { - self.client?.urlProtocol(self, didLoad: cachedResponse.data) + nonisolatedSelf.client?.urlProtocol(nonisolatedSelf, didLoad: cachedResponse.data) } - self.client?.urlProtocolDidFinishLoading(self) + nonisolatedSelf.client?.urlProtocolDidFinishLoading(nonisolatedSelf) - self.internalState = .taskCompleted + nonisolatedSelf.internalState = .taskCompleted } } else { @@ -512,11 +513,12 @@ extension _NativeProtocol { guard let s = task?.session as? URLSession else { fatalError() } + + nonisolated(unsafe) let nonisolatedSelf = self s.delegateQueue.addOperation { - delegate.urlSession(s, dataTask: dt, didReceive: response, completionHandler: { [weak self] disposition in - guard let task = self else { return } - self?.task?.workQueue.async { - task.didCompleteResponseCallback(disposition: disposition) + delegate.urlSession(s, dataTask: dt, didReceive: response, completionHandler: { disposition in + nonisolatedSelf.task?.workQueue.async { + nonisolatedSelf.didCompleteResponseCallback(disposition: disposition) } }) } diff --git a/Sources/FoundationNetworking/URLSession/URLSession.swift b/Sources/FoundationNetworking/URLSession/URLSession.swift index da2e279a43..5ee6265855 100644 --- a/Sources/FoundationNetworking/URLSession/URLSession.swift +++ b/Sources/FoundationNetworking/URLSession/URLSession.swift @@ -168,6 +168,8 @@ import SwiftFoundation import Foundation #endif +internal import Synchronization + extension URLSession { public enum DelayedRequestDisposition : Sendable { case cancel @@ -176,13 +178,10 @@ extension URLSession { } } -fileprivate let globalVarSyncQ = DispatchQueue(label: "org.swift.Foundation.URLSession.GlobalVarSyncQ") -fileprivate var sessionCounter = Int32(0) +fileprivate let sessionCounter = Atomic(0) fileprivate func nextSessionIdentifier() -> Int32 { - return globalVarSyncQ.sync { - sessionCounter += 1 - return sessionCounter - } + let (_, new) = sessionCounter.add(1, ordering: .relaxed) + return new } public let NSURLSessionTransferSizeUnknown: Int64 = -1 @@ -684,37 +683,15 @@ internal extension URLSession { } } -fileprivate struct Lock: @unchecked Sendable { - let stateLock: ManagedBuffer - init(initialState: State) { - stateLock = .create(minimumCapacity: 1) { buffer in - buffer.withUnsafeMutablePointerToElements { lock in - lock.initialize(to: .init()) - } - return initialState - } - } - - func withLock(_ body: @Sendable (inout State) throws -> R) rethrows -> R where R : Sendable { - return try stateLock.withUnsafeMutablePointers { header, lock in - lock.pointee.lock() - defer { - lock.pointee.unlock() - } - return try body(&header.pointee) - } - } -} - fileprivate extension URLSession { final class CancelState: Sendable { struct State { var isCancelled: Bool var task: URLSessionTask? } - let lock: Lock + + let lock = Mutex(State(isCancelled: false, task: nil)) init() { - lock = Lock(initialState: State(isCancelled: false, task: nil)) } func cancel() { diff --git a/Sources/FoundationNetworking/URLSession/URLSessionTask.swift b/Sources/FoundationNetworking/URLSession/URLSessionTask.swift index c3e2660417..4968494b35 100644 --- a/Sources/FoundationNetworking/URLSession/URLSessionTask.swift +++ b/Sources/FoundationNetworking/URLSession/URLSessionTask.swift @@ -223,11 +223,12 @@ open class URLSessionTask : NSObject, NSCopying, @unchecked Sendable { } if let session = actualSession, let delegate = self.delegate { + nonisolated(unsafe) let nonisolatedCompletion = completion delegate.urlSession(session, task: self) { (stream) in if let stream = stream { - completion(.stream(stream)) + nonisolatedCompletion(.stream(stream)) } else { - completion(.none) + nonisolatedCompletion(.none) } } } else { @@ -726,10 +727,10 @@ open class URLSessionWebSocketTask : URLSessionTask, @unchecked Sendable { } } - private var sendBuffer = [(Message, (Error?) -> Void)]() + private var sendBuffer = [(Message, @Sendable (Error?) -> Void)]() private var receiveBuffer = [Message]() - private var receiveCompletionHandlers = [(Result) -> Void]() - private var pongCompletionHandlers = [(Error?) -> Void]() + private var receiveCompletionHandlers = [@Sendable (Result) -> Void]() + private var pongCompletionHandlers = [@Sendable (Error?) -> Void]() private var closeMessage: (CloseCode, Data)? = nil internal var protocolPicked: String? = nil @@ -765,7 +766,7 @@ open class URLSessionWebSocketTask : URLSessionTask, @unchecked Sendable { } } - open func sendPing(pongReceiveHandler: @escaping (Error?) -> Void) { + open func sendPing(pongReceiveHandler: @Sendable @escaping (Error?) -> Void) { self.workQueue.async { self._getProtocol { urlProtocol in // The combination of locking in getProtocol and dispatching to the work queue let us use the normally non-Sendable URLProtocol @@ -1190,9 +1191,10 @@ extension _ProtocolClient : URLProtocolClient { } case .dataCompletionHandler(let completion), .dataCompletionHandlerWithTaskDelegate(let completion, _): - let dataCompletion = { + nonisolated(unsafe) let nonisolatedURLProtocol = urlProtocol + let dataCompletion : @Sendable () -> () = { guard task.state != .completed else { return } - completion(urlProtocol.properties[URLProtocol._PropertyKey.responseData] as? Data ?? Data(), task.response, nil) + completion(nonisolatedURLProtocol.properties[URLProtocol._PropertyKey.responseData] as? Data ?? Data(), task.response, nil) task.state = .completed session.workQueue.async { session.taskRegistry.remove(task) @@ -1207,9 +1209,10 @@ extension _ProtocolClient : URLProtocolClient { } case .downloadCompletionHandler(let completion), .downloadCompletionHandlerWithTaskDelegate(let completion, _): - let downloadCompletion = { + nonisolated(unsafe) let nonisolatedURLProtocol = urlProtocol + let downloadCompletion : @Sendable () -> () = { guard task.state != .completed else { return } - completion(urlProtocol.properties[URLProtocol._PropertyKey.temporaryFileURL] as? URL, task.response, nil) + completion(nonisolatedURLProtocol.properties[URLProtocol._PropertyKey.temporaryFileURL] as? URL, task.response, nil) task.state = .completed session.workQueue.async { session.taskRegistry.remove(task) @@ -1346,7 +1349,7 @@ extension _ProtocolClient : URLProtocolClient { } case .dataCompletionHandler(let completion), .dataCompletionHandlerWithTaskDelegate(let completion, _): - let dataCompletion = { + let dataCompletion : @Sendable () -> () = { guard task.state != .completed else { return } completion(nil, nil, error) task.state = .completed @@ -1363,7 +1366,7 @@ extension _ProtocolClient : URLProtocolClient { } case .downloadCompletionHandler(let completion), .downloadCompletionHandlerWithTaskDelegate(let completion, _): - let downloadCompletion = { + let downloadCompletion : @Sendable () -> () = { guard task.state != .completed else { return } completion(nil, nil, error) task.state = .completed diff --git a/Sources/FoundationNetworking/URLSession/libcurl/EasyHandle.swift b/Sources/FoundationNetworking/URLSession/libcurl/EasyHandle.swift index cbbe60db81..8c4437cac1 100644 --- a/Sources/FoundationNetworking/URLSession/libcurl/EasyHandle.swift +++ b/Sources/FoundationNetworking/URLSession/libcurl/EasyHandle.swift @@ -68,6 +68,9 @@ internal func xferInfoFunctionSupported() -> Bool { /// /// - Note: All code assumes that it is being called on a single thread / /// `Dispatch` only -- it is intentionally **not** thread safe. +@available(*, unavailable) +extension _EasyHandle : Sendable { } + internal final class _EasyHandle { let rawHandle = CFURLSessionEasyHandleInit() weak var delegate: _EasyHandleDelegate? diff --git a/Sources/FoundationNetworking/URLSession/libcurl/MultiHandle.swift b/Sources/FoundationNetworking/URLSession/libcurl/MultiHandle.swift index 44389c0b2b..67f77f4b57 100644 --- a/Sources/FoundationNetworking/URLSession/libcurl/MultiHandle.swift +++ b/Sources/FoundationNetworking/URLSession/libcurl/MultiHandle.swift @@ -317,6 +317,7 @@ fileprivate extension URLSession._MultiHandle { completedTransfer(forEasyHandle: handle, easyCode: code) } while true } + /// Transfer completed. func completedTransfer(forEasyHandle handle: CFURLSessionEasyHandle, easyCode: CFURLSessionEasyCode) { // Look up the matching wrapper: @@ -329,12 +330,14 @@ fileprivate extension URLSession._MultiHandle { if let errorCode = easyHandle.urlErrorCode(for: easyCode) { var errorDescription: String = "" if easyHandle.errorBuffer[0] == 0 { - let description = CFURLSessionEasyCodeDescription(easyCode)! - errorDescription = NSString(bytes: UnsafeMutableRawPointer(mutating: description), length: strlen(description), encoding: String.Encoding.utf8.rawValue)! as String + let description = CFURLSessionEasyCodeDescription(easyCode)! + errorDescription = NSString(bytes: UnsafeMutableRawPointer(mutating: description), length: strlen(description), encoding: String.Encoding.utf8.rawValue)! as String } else { - errorDescription = String(cString: easyHandle.errorBuffer) + if let firstNull = easyHandle.errorBuffer.firstIndex(of: 0), firstNull != easyHandle.errorBuffer.startIndex { + errorDescription = String(validating: easyHandle.errorBuffer[easyHandle.errorBuffer.startIndex.. XMLNode? { diff --git a/Sources/XCTest/Private/PrintObserver.swift b/Sources/XCTest/Private/PrintObserver.swift index fcce3a234b..fd19f2c383 100644 --- a/Sources/XCTest/Private/PrintObserver.swift +++ b/Sources/XCTest/Private/PrintObserver.swift @@ -11,6 +11,10 @@ // Prints test progress to stdout. // +#if canImport(Glibc) +@preconcurrency import Glibc +#endif + /// Prints textual representations of each XCTestObservation event to stdout. /// Mirrors the Apple XCTest output exactly. internal class PrintObserver: XCTestObservation { diff --git a/Sources/XCTest/Private/SourceLocation.swift b/Sources/XCTest/Private/SourceLocation.swift index 679d70924c..9c46c24dbe 100644 --- a/Sources/XCTest/Private/SourceLocation.swift +++ b/Sources/XCTest/Private/SourceLocation.swift @@ -16,7 +16,7 @@ internal struct SourceLocation { /// Represents an "unknown" source location, with default values, which may be used as a fallback /// when a real source location may not be known. - static var unknown: SourceLocation = { + static let unknown: SourceLocation = { return SourceLocation(file: "", line: 0) }() diff --git a/Sources/XCTest/Private/WaiterManager.swift b/Sources/XCTest/Private/WaiterManager.swift index f705165fe8..b37d0022b6 100644 --- a/Sources/XCTest/Private/WaiterManager.swift +++ b/Sources/XCTest/Private/WaiterManager.swift @@ -10,7 +10,7 @@ // WaiterManager.swift // -internal protocol ManageableWaiter: AnyObject, Equatable { +internal protocol ManageableWaiter: AnyObject, Equatable, Sendable { var isFinished: Bool { get } // Invoked on `XCTWaiter.subsystemQueue` diff --git a/Sources/XCTest/Public/Asynchronous/XCTWaiter.swift b/Sources/XCTest/Public/Asynchronous/XCTWaiter.swift index e1270394c9..b4f2f6f70f 100644 --- a/Sources/XCTest/Public/Asynchronous/XCTWaiter.swift +++ b/Sources/XCTest/Public/Asynchronous/XCTWaiter.swift @@ -71,7 +71,7 @@ public extension XCTWaiterDelegate { /// Waiters can be used without a delegate or any association with a test case instance. This allows test /// support libraries to provide convenience methods for waiting without having to pass test cases through /// those APIs. -open class XCTWaiter { +open class XCTWaiter: @unchecked Sendable { /// Values returned by a waiter when it completes, times out, or is interrupted due to another waiter /// higher in the call stack timing out. @@ -132,7 +132,8 @@ open class XCTWaiter { } set { dispatchPrecondition(condition: .notOnQueue(XCTWaiter.subsystemQueue)) - XCTWaiter.subsystemQueue.async { self._delegate = newValue } + nonisolated(unsafe) let nonisolatedNewValue = newValue + XCTWaiter.subsystemQueue.async { self._delegate = nonisolatedNewValue } } } @@ -283,8 +284,8 @@ open class XCTWaiter { // This function operates by blocking a background thread instead of one owned by libdispatch or by the // Swift runtime (as used by Swift concurrency.) To ensure we use a thread owned by neither subsystem, use // Foundation's Thread.detachNewThread(_:). - Thread.detachNewThread { [self] in - let result = wait(for: expectations, timeout: timeout, enforceOrder: enforceOrder, file: file, line: line) + Thread.detachNewThread { + let result = self.wait(for: expectations, timeout: timeout, enforceOrder: enforceOrder, file: file, line: line) continuation.resume(returning: result) } } @@ -398,8 +399,10 @@ open class XCTWaiter { } if let delegateBlock = delegateBlock, let delegate = _delegate { + nonisolated(unsafe) let nonisolatedDelegate = delegate + nonisolated(unsafe) let nonisolatedDelegateBlock = delegateBlock delegateQueue.async { - delegateBlock(delegate) + nonisolatedDelegateBlock(nonisolatedDelegate) } } } diff --git a/Sources/XCTest/Public/Asynchronous/XCTestCase+Asynchronous.swift b/Sources/XCTest/Public/Asynchronous/XCTestCase+Asynchronous.swift index b9935ff728..895ff1de51 100644 --- a/Sources/XCTest/Public/Asynchronous/XCTestCase+Asynchronous.swift +++ b/Sources/XCTest/Public/Asynchronous/XCTestCase+Asynchronous.swift @@ -41,7 +41,8 @@ public extension XCTestCase { /// these environments. To ensure compatibility of tests between /// swift-corelibs-xctest and Apple XCTest, it is not recommended to pass /// explicit values for `file` and `line`. - @preconcurrency @MainActor + // TODO: Having issues with runtime use of MainActor annotated tests on Linux - causes a precondition failure in a dynamic cast + //@preconcurrency @MainActor func waitForExpectations(timeout: TimeInterval, file: StaticString = #file, line: Int = #line, handler: XCWaitCompletionHandler? = nil) { precondition(Thread.isMainThread, "\(#function) must be called on the main thread") if currentWaiter != nil { diff --git a/Sources/XCTest/Public/Asynchronous/XCTestExpectation.swift b/Sources/XCTest/Public/Asynchronous/XCTestExpectation.swift index 16564dd9cf..28ae28217a 100644 --- a/Sources/XCTest/Public/Asynchronous/XCTestExpectation.swift +++ b/Sources/XCTest/Public/Asynchronous/XCTestExpectation.swift @@ -13,7 +13,7 @@ /// Expectations represent specific conditions in asynchronous testing. open class XCTestExpectation: @unchecked Sendable { - private static var currentMonotonicallyIncreasingToken: UInt64 = 0 + private static nonisolated(unsafe) var currentMonotonicallyIncreasingToken: UInt64 = 0 private static func queue_nextMonotonicallyIncreasingToken() -> UInt64 { dispatchPrecondition(condition: .onQueue(XCTWaiter.subsystemQueue)) currentMonotonicallyIncreasingToken += 1 diff --git a/Sources/XCTest/Public/XCTestCase+Performance.swift b/Sources/XCTest/Public/XCTestCase+Performance.swift index 401fb5e9c1..5dab0cfd77 100644 --- a/Sources/XCTest/Public/XCTestCase+Performance.swift +++ b/Sources/XCTest/Public/XCTestCase+Performance.swift @@ -25,7 +25,7 @@ public struct XCTPerformanceMetric : RawRepresentable, Equatable, Hashable { public extension XCTPerformanceMetric { /// Records wall clock time in seconds between `startMeasuring`/`stopMeasuring`. - static let wallClockTime = XCTPerformanceMetric(rawValue: WallClockTimeMetric.name) + static nonisolated(unsafe) let wallClockTime = XCTPerformanceMetric(rawValue: WallClockTimeMetric.name) } /// The following methods are called from within a test method to carry out diff --git a/Sources/XCTest/Public/XCTestCase.swift b/Sources/XCTest/Public/XCTestCase.swift index 4d734cd895..b6aac0ec27 100644 --- a/Sources/XCTest/Public/XCTestCase.swift +++ b/Sources/XCTest/Public/XCTestCase.swift @@ -25,7 +25,7 @@ public typealias XCTestCaseEntry = (testCaseClass: XCTestCase.Type, allTests: [( // A global pointer to the currently running test case. This is required in // order for XCTAssert functions to report failures. -internal var XCTCurrentTestCase: XCTestCase? +internal nonisolated(unsafe) var XCTCurrentTestCase: XCTestCase? /// An instance of this class represents an individual test case which can be /// run by the framework. This class is normally subclassed and extended with @@ -48,7 +48,8 @@ open class XCTestCase: XCTest { return 1 } - @MainActor + // TODO: Having issues with runtime use of MainActor annotated tests on Linux - causes a precondition failure in a dynamic cast + //@MainActor internal var currentWaiter: XCTWaiter? /// The set of expectations made upon this test case. @@ -228,8 +229,9 @@ open class XCTestCase: XCTest { do { if #available(macOS 12.0, *) { + nonisolated(unsafe) let nonisolatedSelf = self try awaitUsingExpectation { - try await self.setUp() + try await nonisolatedSelf.setUp() } } } catch { @@ -274,8 +276,9 @@ open class XCTestCase: XCTest { do { if #available(macOS 12.0, *) { + nonisolated(unsafe) let nonisolatedSelf = self try awaitUsingExpectation { - try await self.tearDown() + try await nonisolatedSelf.tearDown() } } } catch { @@ -340,15 +343,18 @@ func awaitUsingExpectation( let expectation = XCTestExpectation(description: "async test completion") let thrownErrorWrapper = ThrownErrorWrapper() - Task { + nonisolated(unsafe) let nonisolatedClosure = closure + let work : @Sendable () async -> () = { defer { expectation.fulfill() } do { - try await closure() + try await nonisolatedClosure() } catch { thrownErrorWrapper.error = error } } + + Task(priority: nil, operation: work) _ = XCTWaiter.wait(for: [expectation], timeout: asyncTestTimeout) diff --git a/Sources/XCTest/Public/XCTestObservationCenter.swift b/Sources/XCTest/Public/XCTestObservationCenter.swift index 540956dd5c..38c9d7249f 100644 --- a/Sources/XCTest/Public/XCTestObservationCenter.swift +++ b/Sources/XCTest/Public/XCTestObservationCenter.swift @@ -11,7 +11,7 @@ // Notification center for test run progress events. // -private let _sharedCenter: XCTestObservationCenter = XCTestObservationCenter() +private nonisolated(unsafe) let _sharedCenter: XCTestObservationCenter = XCTestObservationCenter() /// Provides a registry for objects wishing to be informed about progress /// during the course of a test run. Observers must implement the diff --git a/Sources/_CFXMLInterface/CFXMLInterface.c b/Sources/_CFXMLInterface/CFXMLInterface.c index 379acbb4d1..1c54de3ec9 100644 --- a/Sources/_CFXMLInterface/CFXMLInterface.c +++ b/Sources/_CFXMLInterface/CFXMLInterface.c @@ -36,76 +36,76 @@ CF_INLINE struct _NSCFXMLBridgeStrong __CFSwiftXMLParserBridgeGetStronglyTyped() libxml2 does not have nullability annotations and does not import well into swift when given potentially differing versions of the library that might be installed on the host operating system. This is a simple C wrapper to simplify some of that interface layer to libxml2. */ -CFIndex _kCFXMLInterfaceRecover = XML_PARSE_RECOVER; -CFIndex _kCFXMLInterfaceNoEnt = XML_PARSE_NOENT; -CFIndex _kCFXMLInterfaceDTDLoad = XML_PARSE_DTDLOAD; -CFIndex _kCFXMLInterfaceDTDAttr = XML_PARSE_DTDATTR; -CFIndex _kCFXMLInterfaceDTDValid = XML_PARSE_DTDVALID; -CFIndex _kCFXMLInterfaceNoError = XML_PARSE_NOERROR; -CFIndex _kCFXMLInterfaceNoWarning = XML_PARSE_NOWARNING; -CFIndex _kCFXMLInterfacePedantic = XML_PARSE_PEDANTIC; -CFIndex _kCFXMLInterfaceNoBlanks = XML_PARSE_NOBLANKS; -CFIndex _kCFXMLInterfaceSAX1 = XML_PARSE_SAX1; -CFIndex _kCFXMLInterfaceXInclude = XML_PARSE_XINCLUDE; -CFIndex _kCFXMLInterfaceNoNet = XML_PARSE_NONET; -CFIndex _kCFXMLInterfaceNoDict = XML_PARSE_NODICT; -CFIndex _kCFXMLInterfaceNSClean = XML_PARSE_NSCLEAN; -CFIndex _kCFXMLInterfaceNoCdata = XML_PARSE_NOCDATA; -CFIndex _kCFXMLInterfaceNoXIncnode = XML_PARSE_NOXINCNODE; -CFIndex _kCFXMLInterfaceCompact = XML_PARSE_COMPACT; -CFIndex _kCFXMLInterfaceOld10 = XML_PARSE_OLD10; -CFIndex _kCFXMLInterfaceNoBasefix = XML_PARSE_NOBASEFIX; -CFIndex _kCFXMLInterfaceHuge = XML_PARSE_HUGE; -CFIndex _kCFXMLInterfaceOldsax = XML_PARSE_OLDSAX; -CFIndex _kCFXMLInterfaceIgnoreEnc = XML_PARSE_IGNORE_ENC; -CFIndex _kCFXMLInterfaceBigLines = XML_PARSE_BIG_LINES; - -CFIndex _kCFXMLTypeInvalid = 0; -CFIndex _kCFXMLTypeDocument = XML_DOCUMENT_NODE; -CFIndex _kCFXMLTypeElement = XML_ELEMENT_NODE; -CFIndex _kCFXMLTypeAttribute = XML_ATTRIBUTE_NODE; -CFIndex _kCFXMLTypeProcessingInstruction = XML_PI_NODE; -CFIndex _kCFXMLTypeComment = XML_COMMENT_NODE; -CFIndex _kCFXMLTypeText = XML_TEXT_NODE; -CFIndex _kCFXMLTypeCDataSection = XML_CDATA_SECTION_NODE; -CFIndex _kCFXMLTypeDTD = XML_DTD_NODE; -CFIndex _kCFXMLDocTypeHTML = XML_DOC_HTML; -CFIndex _kCFXMLTypeNamespace = 22; // libxml2 does not define namespaces as nodes, so we have to fake it - -CFIndex _kCFXMLDTDNodeTypeEntity = XML_ENTITY_DECL; -CFIndex _kCFXMLDTDNodeTypeAttribute = XML_ATTRIBUTE_DECL; -CFIndex _kCFXMLDTDNodeTypeElement = XML_ELEMENT_DECL; -CFIndex _kCFXMLDTDNodeTypeNotation = XML_NOTATION_NODE; - -CFIndex _kCFXMLDTDNodeElementTypeUndefined = XML_ELEMENT_TYPE_UNDEFINED; -CFIndex _kCFXMLDTDNodeElementTypeEmpty = XML_ELEMENT_TYPE_EMPTY; -CFIndex _kCFXMLDTDNodeElementTypeAny = XML_ELEMENT_TYPE_ANY; -CFIndex _kCFXMLDTDNodeElementTypeMixed = XML_ELEMENT_TYPE_MIXED; -CFIndex _kCFXMLDTDNodeElementTypeElement = XML_ELEMENT_TYPE_ELEMENT; - -CFIndex _kCFXMLDTDNodeEntityTypeInternalGeneral = XML_INTERNAL_GENERAL_ENTITY; -CFIndex _kCFXMLDTDNodeEntityTypeExternalGeneralParsed = XML_EXTERNAL_GENERAL_PARSED_ENTITY; -CFIndex _kCFXMLDTDNodeEntityTypeExternalGeneralUnparsed = XML_EXTERNAL_GENERAL_UNPARSED_ENTITY; -CFIndex _kCFXMLDTDNodeEntityTypeInternalParameter = XML_INTERNAL_PARAMETER_ENTITY; -CFIndex _kCFXMLDTDNodeEntityTypeExternalParameter = XML_EXTERNAL_PARAMETER_ENTITY; -CFIndex _kCFXMLDTDNodeEntityTypeInternalPredefined = XML_INTERNAL_PREDEFINED_ENTITY; - -CFIndex _kCFXMLDTDNodeAttributeTypeCData = XML_ATTRIBUTE_CDATA; -CFIndex _kCFXMLDTDNodeAttributeTypeID = XML_ATTRIBUTE_ID; -CFIndex _kCFXMLDTDNodeAttributeTypeIDRef = XML_ATTRIBUTE_IDREF; -CFIndex _kCFXMLDTDNodeAttributeTypeIDRefs = XML_ATTRIBUTE_IDREFS; -CFIndex _kCFXMLDTDNodeAttributeTypeEntity = XML_ATTRIBUTE_ENTITY; -CFIndex _kCFXMLDTDNodeAttributeTypeEntities = XML_ATTRIBUTE_ENTITIES; -CFIndex _kCFXMLDTDNodeAttributeTypeNMToken = XML_ATTRIBUTE_NMTOKEN; -CFIndex _kCFXMLDTDNodeAttributeTypeNMTokens = XML_ATTRIBUTE_NMTOKENS; -CFIndex _kCFXMLDTDNodeAttributeTypeEnumeration = XML_ATTRIBUTE_ENUMERATION; -CFIndex _kCFXMLDTDNodeAttributeTypeNotation = XML_ATTRIBUTE_NOTATION; - -CFIndex _kCFXMLNodePreserveWhitespace = 1 << 25; -CFIndex _kCFXMLNodeCompactEmptyElement = 1 << 2; -CFIndex _kCFXMLNodePrettyPrint = 1 << 17; -CFIndex _kCFXMLNodeLoadExternalEntitiesNever = 1 << 19; -CFIndex _kCFXMLNodeLoadExternalEntitiesAlways = 1 << 14; +const CFIndex _kCFXMLInterfaceRecover = XML_PARSE_RECOVER; +const CFIndex _kCFXMLInterfaceNoEnt = XML_PARSE_NOENT; +const CFIndex _kCFXMLInterfaceDTDLoad = XML_PARSE_DTDLOAD; +const CFIndex _kCFXMLInterfaceDTDAttr = XML_PARSE_DTDATTR; +const CFIndex _kCFXMLInterfaceDTDValid = XML_PARSE_DTDVALID; +const CFIndex _kCFXMLInterfaceNoError = XML_PARSE_NOERROR; +const CFIndex _kCFXMLInterfaceNoWarning = XML_PARSE_NOWARNING; +const CFIndex _kCFXMLInterfacePedantic = XML_PARSE_PEDANTIC; +const CFIndex _kCFXMLInterfaceNoBlanks = XML_PARSE_NOBLANKS; +const CFIndex _kCFXMLInterfaceSAX1 = XML_PARSE_SAX1; +const CFIndex _kCFXMLInterfaceXInclude = XML_PARSE_XINCLUDE; +const CFIndex _kCFXMLInterfaceNoNet = XML_PARSE_NONET; +const CFIndex _kCFXMLInterfaceNoDict = XML_PARSE_NODICT; +const CFIndex _kCFXMLInterfaceNSClean = XML_PARSE_NSCLEAN; +const CFIndex _kCFXMLInterfaceNoCdata = XML_PARSE_NOCDATA; +const CFIndex _kCFXMLInterfaceNoXIncnode = XML_PARSE_NOXINCNODE; +const CFIndex _kCFXMLInterfaceCompact = XML_PARSE_COMPACT; +const CFIndex _kCFXMLInterfaceOld10 = XML_PARSE_OLD10; +const CFIndex _kCFXMLInterfaceNoBasefix = XML_PARSE_NOBASEFIX; +const CFIndex _kCFXMLInterfaceHuge = XML_PARSE_HUGE; +const CFIndex _kCFXMLInterfaceOldsax = XML_PARSE_OLDSAX; +const CFIndex _kCFXMLInterfaceIgnoreEnc = XML_PARSE_IGNORE_ENC; +const CFIndex _kCFXMLInterfaceBigLines = XML_PARSE_BIG_LINES; + +const CFIndex _kCFXMLTypeInvalid = 0; +const CFIndex _kCFXMLTypeDocument = XML_DOCUMENT_NODE; +const CFIndex _kCFXMLTypeElement = XML_ELEMENT_NODE; +const CFIndex _kCFXMLTypeAttribute = XML_ATTRIBUTE_NODE; +const CFIndex _kCFXMLTypeProcessingInstruction = XML_PI_NODE; +const CFIndex _kCFXMLTypeComment = XML_COMMENT_NODE; +const CFIndex _kCFXMLTypeText = XML_TEXT_NODE; +const CFIndex _kCFXMLTypeCDataSection = XML_CDATA_SECTION_NODE; +const CFIndex _kCFXMLTypeDTD = XML_DTD_NODE; +const CFIndex _kCFXMLDocTypeHTML = XML_DOC_HTML; +const CFIndex _kCFXMLTypeNamespace = 22; // libxml2 does not define namespaces as nodes, so we have to fake it + +const CFIndex _kCFXMLDTDNodeTypeEntity = XML_ENTITY_DECL; +const CFIndex _kCFXMLDTDNodeTypeAttribute = XML_ATTRIBUTE_DECL; +const CFIndex _kCFXMLDTDNodeTypeElement = XML_ELEMENT_DECL; +const CFIndex _kCFXMLDTDNodeTypeNotation = XML_NOTATION_NODE; + +const CFIndex _kCFXMLDTDNodeElementTypeUndefined = XML_ELEMENT_TYPE_UNDEFINED; +const CFIndex _kCFXMLDTDNodeElementTypeEmpty = XML_ELEMENT_TYPE_EMPTY; +const CFIndex _kCFXMLDTDNodeElementTypeAny = XML_ELEMENT_TYPE_ANY; +const CFIndex _kCFXMLDTDNodeElementTypeMixed = XML_ELEMENT_TYPE_MIXED; +const CFIndex _kCFXMLDTDNodeElementTypeElement = XML_ELEMENT_TYPE_ELEMENT; + +const CFIndex _kCFXMLDTDNodeEntityTypeInternalGeneral = XML_INTERNAL_GENERAL_ENTITY; +const CFIndex _kCFXMLDTDNodeEntityTypeExternalGeneralParsed = XML_EXTERNAL_GENERAL_PARSED_ENTITY; +const CFIndex _kCFXMLDTDNodeEntityTypeExternalGeneralUnparsed = XML_EXTERNAL_GENERAL_UNPARSED_ENTITY; +const CFIndex _kCFXMLDTDNodeEntityTypeInternalParameter = XML_INTERNAL_PARAMETER_ENTITY; +const CFIndex _kCFXMLDTDNodeEntityTypeExternalParameter = XML_EXTERNAL_PARAMETER_ENTITY; +const CFIndex _kCFXMLDTDNodeEntityTypeInternalPredefined = XML_INTERNAL_PREDEFINED_ENTITY; + +const CFIndex _kCFXMLDTDNodeAttributeTypeCData = XML_ATTRIBUTE_CDATA; +const CFIndex _kCFXMLDTDNodeAttributeTypeID = XML_ATTRIBUTE_ID; +const CFIndex _kCFXMLDTDNodeAttributeTypeIDRef = XML_ATTRIBUTE_IDREF; +const CFIndex _kCFXMLDTDNodeAttributeTypeIDRefs = XML_ATTRIBUTE_IDREFS; +const CFIndex _kCFXMLDTDNodeAttributeTypeEntity = XML_ATTRIBUTE_ENTITY; +const CFIndex _kCFXMLDTDNodeAttributeTypeEntities = XML_ATTRIBUTE_ENTITIES; +const CFIndex _kCFXMLDTDNodeAttributeTypeNMToken = XML_ATTRIBUTE_NMTOKEN; +const CFIndex _kCFXMLDTDNodeAttributeTypeNMTokens = XML_ATTRIBUTE_NMTOKENS; +const CFIndex _kCFXMLDTDNodeAttributeTypeEnumeration = XML_ATTRIBUTE_ENUMERATION; +const CFIndex _kCFXMLDTDNodeAttributeTypeNotation = XML_ATTRIBUTE_NOTATION; + +const CFIndex _kCFXMLNodePreserveWhitespace = 1 << 25; +const CFIndex _kCFXMLNodeCompactEmptyElement = 1 << 2; +const CFIndex _kCFXMLNodePrettyPrint = 1 << 17; +const CFIndex _kCFXMLNodeLoadExternalEntitiesNever = 1 << 19; +const CFIndex _kCFXMLNodeLoadExternalEntitiesAlways = 1 << 14; // We define this structure because libxml2's "notation" node does not contain the fields // nearly all other libxml2 node fields contain, that we use extensively. diff --git a/Sources/_CFXMLInterface/include/CFXMLInterface.h b/Sources/_CFXMLInterface/include/CFXMLInterface.h index b51004517e..ebfb3b3fdc 100644 --- a/Sources/_CFXMLInterface/include/CFXMLInterface.h +++ b/Sources/_CFXMLInterface/include/CFXMLInterface.h @@ -25,70 +25,70 @@ CF_IMPLICIT_BRIDGING_ENABLED CF_ASSUME_NONNULL_BEGIN CF_EXTERN_C_BEGIN -extern CFIndex _kCFXMLInterfaceRecover; -extern CFIndex _kCFXMLInterfaceNoEnt; -extern CFIndex _kCFXMLInterfaceDTDLoad; -extern CFIndex _kCFXMLInterfaceDTDAttr; -extern CFIndex _kCFXMLInterfaceDTDValid; -extern CFIndex _kCFXMLInterfaceNoError; -extern CFIndex _kCFXMLInterfaceNoWarning; -extern CFIndex _kCFXMLInterfacePedantic; -extern CFIndex _kCFXMLInterfaceNoBlanks; -extern CFIndex _kCFXMLInterfaceSAX1; -extern CFIndex _kCFXMLInterfaceXInclude; -extern CFIndex _kCFXMLInterfaceNoNet; -extern CFIndex _kCFXMLInterfaceNoDict; -extern CFIndex _kCFXMLInterfaceNSClean; -extern CFIndex _kCFXMLInterfaceNoCdata; -extern CFIndex _kCFXMLInterfaceNoXIncnode; -extern CFIndex _kCFXMLInterfaceCompact; -extern CFIndex _kCFXMLInterfaceOld10; -extern CFIndex _kCFXMLInterfaceNoBasefix; -extern CFIndex _kCFXMLInterfaceHuge; -extern CFIndex _kCFXMLInterfaceOldsax; -extern CFIndex _kCFXMLInterfaceIgnoreEnc; -extern CFIndex _kCFXMLInterfaceBigLines; - -extern CFIndex _kCFXMLTypeInvalid; -extern CFIndex _kCFXMLTypeDocument; -extern CFIndex _kCFXMLTypeElement; -extern CFIndex _kCFXMLTypeAttribute; -extern CFIndex _kCFXMLTypeProcessingInstruction; -extern CFIndex _kCFXMLTypeComment; -extern CFIndex _kCFXMLTypeText; -extern CFIndex _kCFXMLTypeCDataSection; -extern CFIndex _kCFXMLTypeDTD; -extern CFIndex _kCFXMLDocTypeHTML; -extern CFIndex _kCFXMLTypeNamespace; - -extern CFIndex _kCFXMLDTDNodeTypeEntity; -extern CFIndex _kCFXMLDTDNodeTypeAttribute; -extern CFIndex _kCFXMLDTDNodeTypeElement; -extern CFIndex _kCFXMLDTDNodeTypeNotation; - -extern CFIndex _kCFXMLDTDNodeElementTypeUndefined; -extern CFIndex _kCFXMLDTDNodeElementTypeEmpty; -extern CFIndex _kCFXMLDTDNodeElementTypeAny; -extern CFIndex _kCFXMLDTDNodeElementTypeMixed; -extern CFIndex _kCFXMLDTDNodeElementTypeElement; - -extern CFIndex _kCFXMLDTDNodeEntityTypeInternalGeneral; -extern CFIndex _kCFXMLDTDNodeEntityTypeExternalGeneralParsed; -extern CFIndex _kCFXMLDTDNodeEntityTypeExternalGeneralUnparsed; -extern CFIndex _kCFXMLDTDNodeEntityTypeInternalParameter; -extern CFIndex _kCFXMLDTDNodeEntityTypeExternalParameter; -extern CFIndex _kCFXMLDTDNodeEntityTypeInternalPredefined; - -extern CFIndex _kCFXMLDTDNodeAttributeTypeCData; -extern CFIndex _kCFXMLDTDNodeAttributeTypeID; -extern CFIndex _kCFXMLDTDNodeAttributeTypeIDRef; -extern CFIndex _kCFXMLDTDNodeAttributeTypeIDRefs; -extern CFIndex _kCFXMLDTDNodeAttributeTypeEntity; -extern CFIndex _kCFXMLDTDNodeAttributeTypeEntities; -extern CFIndex _kCFXMLDTDNodeAttributeTypeNMToken; -extern CFIndex _kCFXMLDTDNodeAttributeTypeNMTokens; -extern CFIndex _kCFXMLDTDNodeAttributeTypeEnumeration; -extern CFIndex _kCFXMLDTDNodeAttributeTypeNotation; +extern const CFIndex _kCFXMLInterfaceRecover; +extern const CFIndex _kCFXMLInterfaceNoEnt; +extern const CFIndex _kCFXMLInterfaceDTDLoad; +extern const CFIndex _kCFXMLInterfaceDTDAttr; +extern const CFIndex _kCFXMLInterfaceDTDValid; +extern const CFIndex _kCFXMLInterfaceNoError; +extern const CFIndex _kCFXMLInterfaceNoWarning; +extern const CFIndex _kCFXMLInterfacePedantic; +extern const CFIndex _kCFXMLInterfaceNoBlanks; +extern const CFIndex _kCFXMLInterfaceSAX1; +extern const CFIndex _kCFXMLInterfaceXInclude; +extern const CFIndex _kCFXMLInterfaceNoNet; +extern const CFIndex _kCFXMLInterfaceNoDict; +extern const CFIndex _kCFXMLInterfaceNSClean; +extern const CFIndex _kCFXMLInterfaceNoCdata; +extern const CFIndex _kCFXMLInterfaceNoXIncnode; +extern const CFIndex _kCFXMLInterfaceCompact; +extern const CFIndex _kCFXMLInterfaceOld10; +extern const CFIndex _kCFXMLInterfaceNoBasefix; +extern const CFIndex _kCFXMLInterfaceHuge; +extern const CFIndex _kCFXMLInterfaceOldsax; +extern const CFIndex _kCFXMLInterfaceIgnoreEnc; +extern const CFIndex _kCFXMLInterfaceBigLines; + +extern const CFIndex _kCFXMLTypeInvalid; +extern const CFIndex _kCFXMLTypeDocument; +extern const CFIndex _kCFXMLTypeElement; +extern const CFIndex _kCFXMLTypeAttribute; +extern const CFIndex _kCFXMLTypeProcessingInstruction; +extern const CFIndex _kCFXMLTypeComment; +extern const CFIndex _kCFXMLTypeText; +extern const CFIndex _kCFXMLTypeCDataSection; +extern const CFIndex _kCFXMLTypeDTD; +extern const CFIndex _kCFXMLDocTypeHTML; +extern const CFIndex _kCFXMLTypeNamespace; + +extern const CFIndex _kCFXMLDTDNodeTypeEntity; +extern const CFIndex _kCFXMLDTDNodeTypeAttribute; +extern const CFIndex _kCFXMLDTDNodeTypeElement; +extern const CFIndex _kCFXMLDTDNodeTypeNotation; + +extern const CFIndex _kCFXMLDTDNodeElementTypeUndefined; +extern const CFIndex _kCFXMLDTDNodeElementTypeEmpty; +extern const CFIndex _kCFXMLDTDNodeElementTypeAny; +extern const CFIndex _kCFXMLDTDNodeElementTypeMixed; +extern const CFIndex _kCFXMLDTDNodeElementTypeElement; + +extern const CFIndex _kCFXMLDTDNodeEntityTypeInternalGeneral; +extern const CFIndex _kCFXMLDTDNodeEntityTypeExternalGeneralParsed; +extern const CFIndex _kCFXMLDTDNodeEntityTypeExternalGeneralUnparsed; +extern const CFIndex _kCFXMLDTDNodeEntityTypeInternalParameter; +extern const CFIndex _kCFXMLDTDNodeEntityTypeExternalParameter; +extern const CFIndex _kCFXMLDTDNodeEntityTypeInternalPredefined; + +extern const CFIndex _kCFXMLDTDNodeAttributeTypeCData; +extern const CFIndex _kCFXMLDTDNodeAttributeTypeID; +extern const CFIndex _kCFXMLDTDNodeAttributeTypeIDRef; +extern const CFIndex _kCFXMLDTDNodeAttributeTypeIDRefs; +extern const CFIndex _kCFXMLDTDNodeAttributeTypeEntity; +extern const CFIndex _kCFXMLDTDNodeAttributeTypeEntities; +extern const CFIndex _kCFXMLDTDNodeAttributeTypeNMToken; +extern const CFIndex _kCFXMLDTDNodeAttributeTypeNMTokens; +extern const CFIndex _kCFXMLDTDNodeAttributeTypeEnumeration; +extern const CFIndex _kCFXMLDTDNodeAttributeTypeNotation; typedef struct _xmlParserInput *_CFXMLInterfaceParserInput; typedef struct _xmlParserCtxt *_CFXMLInterfaceParserContext; @@ -327,7 +327,7 @@ struct _NSXMLParserBridge { const unsigned char *SystemID); }; -CF_EXPORT struct _NSXMLParserBridge __CFSwiftXMLParserBridge; +CF_EXPORT struct _NSXMLParserBridge __CFSwiftXMLParserBridge __attribute__((swift_attr("nonisolated(unsafe)"))); CF_EXTERN_C_END diff --git a/Tests/Foundation/FTPServer.swift b/Tests/Foundation/FTPServer.swift index 3fcb1128d5..bc3753baae 100644 --- a/Tests/Foundation/FTPServer.swift +++ b/Tests/Foundation/FTPServer.swift @@ -17,14 +17,14 @@ import Dispatch import Darwin #endif -public class ServerSemaphore { +final class ServerSemaphore : Sendable { let dispatchSemaphore = DispatchSemaphore(value: 0) - public func wait(timeout: DispatchTime) -> DispatchTimeoutResult { + func wait(timeout: DispatchTime) -> DispatchTimeoutResult { return dispatchSemaphore.wait(timeout: timeout) } - public func signal() { + func signal() { dispatchSemaphore.signal() } } @@ -79,11 +79,12 @@ class _FTPSocket { _ = try attempt("bind", valid: isZero, bind(dataSocket, addr, socklen_t(MemoryLayout.size))) _ = try attempt("listen", valid: isZero, listen(dataSocket, SOMAXCONN)) // Open the data port asynchronously. Port should be opened before ESPV header communication. + nonisolated(unsafe) let nonisolatedSelf = self DispatchQueue(label: "delay").async { do { var sockLen = socklen_t(MemoryLayout.size) - self.dataSocket = try self.attempt("accept", valid: self.isNotMinusOne, accept(self.dataSocket, addr, &sockLen)) - self.dataSocketPort = sa1.sin_port + nonisolatedSelf.dataSocket = try nonisolatedSelf.attempt("accept", valid: nonisolatedSelf.isNotMinusOne, accept(nonisolatedSelf.dataSocket, addr, &sockLen)) + nonisolatedSelf.dataSocketPort = sa1.sin_port } catch { NSLog("Could not open data port.") } @@ -160,20 +161,20 @@ class _FTPServer { socket = try _FTPSocket(port: port) } - public class func create(port: UInt16) throws -> _FTPServer { + class func create(port: UInt16) throws -> _FTPServer { return try _FTPServer(port: port) } - public func listen(notify: ServerSemaphore) throws { + func listen(notify: ServerSemaphore) throws { try socket.acceptConnection(notify: notify) } - public func stop() { + func stop() { socket.shutdown() } // parse header information and respond accordingly - public func parseHeaderData() throws { + func parseHeaderData() throws { let saveData = """ FTP implementation to test FTP upload, download and data tasks. Instead of sending a file, @@ -222,29 +223,29 @@ class _FTPServer { } } - public func respondWithRawData(with string: String) throws { + func respondWithRawData(with string: String) throws { try self.socket.writeRawData(string.data(using: String.Encoding.utf8)!) } - public func respondWithData(with data: Data) throws -> Int32 { + func respondWithData(with data: Data) throws -> Int32 { return try self.socket.writeRawData(socket: data) } - public func readDataOnDataSocket() throws -> String { + func readDataOnDataSocket() throws -> String { return try self.socket.readDataOnDataSocket() } } -public class TestFTPURLSessionServer { +class TestFTPURLSessionServer { let ftpServer: _FTPServer - public init (port: UInt16) throws { + init (port: UInt16) throws { ftpServer = try _FTPServer.create(port: port) } - public func start(started: ServerSemaphore) throws { + func start(started: ServerSemaphore) throws { started.signal() try ftpServer.listen(notify: started) } - public func parseHeaderAndRespond() throws { + func parseHeaderAndRespond() throws { try ftpServer.parseHeaderData() } @@ -258,7 +259,7 @@ public class TestFTPURLSessionServer { } class LoopbackFTPServerTest: XCTestCase { - static var serverPort: Int = -1 + nonisolated(unsafe) static var serverPort: Int = -1 override class func setUp() { super.setUp() diff --git a/Tests/Foundation/FixtureValues.swift b/Tests/Foundation/FixtureValues.swift index 0a8799f369..86f329c03f 100644 --- a/Tests/Foundation/FixtureValues.swift +++ b/Tests/Foundation/FixtureValues.swift @@ -28,7 +28,7 @@ extension Calendar { } enum Fixtures { - static let mutableAttributedString = TypedFixture("NSMutableAttributedString") { + static nonisolated(unsafe) let mutableAttributedString = TypedFixture("NSMutableAttributedString") { let string = NSMutableAttributedString(string: "0123456789") // Should have: .xyyzzxyx. @@ -53,17 +53,17 @@ enum Fixtures { return string } - static let attributedString = TypedFixture("NSAttributedString") { + static nonisolated(unsafe) let attributedString = TypedFixture("NSAttributedString") { return NSAttributedString(attributedString: try Fixtures.mutableAttributedString.make()) } // ===== ByteCountFormatter ===== - static let byteCountFormatterDefault = TypedFixture("ByteCountFormatter-Default") { + static nonisolated(unsafe) let byteCountFormatterDefault = TypedFixture("ByteCountFormatter-Default") { return ByteCountFormatter() } - static let byteCountFormatterAllFieldsSet = TypedFixture("ByteCountFormatter-AllFieldsSet") { + static nonisolated(unsafe) let byteCountFormatterAllFieldsSet = TypedFixture("ByteCountFormatter-AllFieldsSet") { let f = ByteCountFormatter() f.allowedUnits = [.useBytes, .useKB] @@ -83,7 +83,7 @@ enum Fixtures { // ===== DateIntervalFormatter ===== - static let dateIntervalFormatterDefault = TypedFixture("DateIntervalFormatter-Default") { + static nonisolated(unsafe) let dateIntervalFormatterDefault = TypedFixture("DateIntervalFormatter-Default") { let dif = DateIntervalFormatter() let calendar = Calendar.neutral @@ -95,7 +95,7 @@ enum Fixtures { return dif } - static let dateIntervalFormatterValuesSetWithoutTemplate = TypedFixture("DateIntervalFormatter-ValuesSetWithoutTemplate") { + static nonisolated(unsafe) let dateIntervalFormatterValuesSetWithoutTemplate = TypedFixture("DateIntervalFormatter-ValuesSetWithoutTemplate") { let dif = DateIntervalFormatter() var calendar = Calendar.neutral @@ -111,7 +111,7 @@ enum Fixtures { return dif } - static let dateIntervalFormatterValuesSetWithTemplate = TypedFixture("DateIntervalFormatter-ValuesSetWithTemplate") { + static nonisolated(unsafe) let dateIntervalFormatterValuesSetWithTemplate = TypedFixture("DateIntervalFormatter-ValuesSetWithTemplate") { let dif = DateIntervalFormatter() var calendar = Calendar.neutral @@ -128,14 +128,14 @@ enum Fixtures { // ===== ISO8601DateFormatter ===== - static let iso8601FormatterDefault = TypedFixture("ISO8601DateFormatter-Default") { + static nonisolated(unsafe) let iso8601FormatterDefault = TypedFixture("ISO8601DateFormatter-Default") { let idf = ISO8601DateFormatter() idf.timeZone = Calendar.neutral.timeZone return idf } - static let iso8601FormatterOptionsSet = TypedFixture("ISO8601DateFormatter-OptionsSet") { + static nonisolated(unsafe) let iso8601FormatterOptionsSet = TypedFixture("ISO8601DateFormatter-OptionsSet") { let idf = ISO8601DateFormatter() idf.timeZone = Calendar.neutral.timeZone @@ -146,7 +146,7 @@ enum Fixtures { // ===== NSTextCheckingResult ===== - static let textCheckingResultSimpleRegex = TypedFixture("NSTextCheckingResult-SimpleRegex") { + static nonisolated(unsafe) let textCheckingResultSimpleRegex = TypedFixture("NSTextCheckingResult-SimpleRegex") { let string = "aaa" let regexp = try NSRegularExpression(pattern: "aaa", options: []) let result = try XCTUnwrap(regexp.matches(in: string, range: NSRange(string.startIndex ..< string.endIndex, in: string)).first) @@ -155,7 +155,7 @@ enum Fixtures { } - static let textCheckingResultExtendedRegex = TypedFixture("NSTextCheckingResult-ExtendedRegex") { + static nonisolated(unsafe) let textCheckingResultExtendedRegex = TypedFixture("NSTextCheckingResult-ExtendedRegex") { let string = "aaaaaa" let regexp = try NSRegularExpression(pattern: "a(a(a(a(a(a)))))", options: []) let result = try XCTUnwrap(regexp.matches(in: string, range: NSRange(string.startIndex ..< string.endIndex, in: string)).first) @@ -163,7 +163,7 @@ enum Fixtures { return result } - static let textCheckingResultComplexRegex = TypedFixture("NSTextCheckingResult-ComplexRegex") { + static nonisolated(unsafe) let textCheckingResultComplexRegex = TypedFixture("NSTextCheckingResult-ComplexRegex") { let string = "aaaaaaaaa" let regexp = try NSRegularExpression(pattern: "a(a(a(a(a(a(a(a(a))))))))", options: []) let result = try XCTUnwrap(regexp.matches(in: string, range: NSRange(string.startIndex ..< string.endIndex, in: string)).first) @@ -173,15 +173,15 @@ enum Fixtures { // ===== NSIndexSet ===== - static let indexSetEmpty = TypedFixture("NSIndexSet-Empty") { + static nonisolated(unsafe) let indexSetEmpty = TypedFixture("NSIndexSet-Empty") { return NSIndexSet(indexesIn: NSMakeRange(0, 0)) } - static let indexSetOneRange = TypedFixture("NSIndexSet-OneRange") { + static nonisolated(unsafe) let indexSetOneRange = TypedFixture("NSIndexSet-OneRange") { return NSIndexSet(indexesIn: NSMakeRange(0, 50)) } - static let indexSetManyRanges = TypedFixture("NSIndexSet-ManyRanges") { + static nonisolated(unsafe) let indexSetManyRanges = TypedFixture("NSIndexSet-ManyRanges") { let indexSet = NSMutableIndexSet() indexSet.add(in: NSMakeRange(0, 50)) indexSet.add(in: NSMakeRange(100, 50)) @@ -190,29 +190,29 @@ enum Fixtures { return indexSet.copy() as! NSIndexSet } - static let mutableIndexSetEmpty = TypedFixture("NSMutableIndexSet-Empty") { + static nonisolated(unsafe) let mutableIndexSetEmpty = TypedFixture("NSMutableIndexSet-Empty") { return (try Fixtures.indexSetEmpty.make()).mutableCopy() as! NSMutableIndexSet } - static let mutableIndexSetOneRange = TypedFixture("NSMutableIndexSet-OneRange") { + static nonisolated(unsafe) let mutableIndexSetOneRange = TypedFixture("NSMutableIndexSet-OneRange") { return (try Fixtures.indexSetOneRange.make()).mutableCopy() as! NSMutableIndexSet } - static let mutableIndexSetManyRanges = TypedFixture("NSMutableIndexSet-ManyRanges") { + static nonisolated(unsafe) let mutableIndexSetManyRanges = TypedFixture("NSMutableIndexSet-ManyRanges") { return (try Fixtures.indexSetManyRanges.make()).mutableCopy() as! NSMutableIndexSet } // ===== NSIndexPath ===== - static let indexPathEmpty = TypedFixture("NSIndexPath-Empty") { + static nonisolated(unsafe) let indexPathEmpty = TypedFixture("NSIndexPath-Empty") { return NSIndexPath() } - static let indexPathOneIndex = TypedFixture("NSIndexPath-OneIndex") { + static nonisolated(unsafe) let indexPathOneIndex = TypedFixture("NSIndexPath-OneIndex") { return NSIndexPath(index: 52) } - static let indexPathManyIndices = TypedFixture("NSIndexPath-ManyIndices") { + static nonisolated(unsafe) let indexPathManyIndices = TypedFixture("NSIndexPath-ManyIndices") { var indexPath = IndexPath() indexPath.append([4, 8, 15, 16, 23, 42]) return indexPath as NSIndexPath @@ -220,32 +220,32 @@ enum Fixtures { // ===== NSSet, NSMutableSet ===== - static let setOfNumbers = TypedFixture("NSSet-Numbers") { + static nonisolated(unsafe) let setOfNumbers = TypedFixture("NSSet-Numbers") { let numbers = [1, 2, 3, 4, 5].map { NSNumber(value: $0) } return NSSet(array: numbers) } - static let setEmpty = TypedFixture("NSSet-Empty") { + static nonisolated(unsafe) let setEmpty = TypedFixture("NSSet-Empty") { return NSSet() } - static let mutableSetOfNumbers = TypedFixture("NSMutableSet-Numbers") { + static nonisolated(unsafe) let mutableSetOfNumbers = TypedFixture("NSMutableSet-Numbers") { let numbers = [1, 2, 3, 4, 5].map { NSNumber(value: $0) } return NSMutableSet(array: numbers) } - static let mutableSetEmpty = TypedFixture("NSMutableSet-Empty") { + static nonisolated(unsafe) let mutableSetEmpty = TypedFixture("NSMutableSet-Empty") { return NSMutableSet() } // ===== NSCountedSet ===== - static let countedSetOfNumbersAppearingOnce = TypedFixture("NSCountedSet-NumbersAppearingOnce") { + static nonisolated(unsafe) let countedSetOfNumbersAppearingOnce = TypedFixture("NSCountedSet-NumbersAppearingOnce") { let numbers = [1, 2, 3, 4, 5].map { NSNumber(value: $0) } return NSCountedSet(array: numbers) } - static let countedSetOfNumbersAppearingSeveralTimes = TypedFixture("NSCountedSet-NumbersAppearingSeveralTimes") { + static nonisolated(unsafe) let countedSetOfNumbersAppearingSeveralTimes = TypedFixture("NSCountedSet-NumbersAppearingSeveralTimes") { let numbers = [1, 2, 3, 4, 5].map { NSNumber(value: $0) } let set = NSCountedSet() for _ in 0 ..< 5 { @@ -256,75 +256,75 @@ enum Fixtures { return set } - static let countedSetEmpty = TypedFixture("NSCountedSet-Empty") { + static nonisolated(unsafe) let countedSetEmpty = TypedFixture("NSCountedSet-Empty") { return NSCountedSet() } // ===== NSCharacterSet, NSMutableCharacterSet ===== - static let characterSetEmpty = TypedFixture("NSCharacterSet-Empty") { + static nonisolated(unsafe) let characterSetEmpty = TypedFixture("NSCharacterSet-Empty") { return NSCharacterSet() } - static let characterSetRange = TypedFixture("NSCharacterSet-Range") { + static nonisolated(unsafe) let characterSetRange = TypedFixture("NSCharacterSet-Range") { return NSCharacterSet(range: NSMakeRange(0, 255)) } - static let characterSetString = TypedFixture("NSCharacterSet-String") { + static nonisolated(unsafe) let characterSetString = TypedFixture("NSCharacterSet-String") { return NSCharacterSet(charactersIn: "abcdefghijklmnopqrstuvwxyz") } - static let characterSetBitmap = TypedFixture("NSCharacterSet-Bitmap") { + static nonisolated(unsafe) let characterSetBitmap = TypedFixture("NSCharacterSet-Bitmap") { let someSet = NSCharacterSet(charactersIn: "abcdefghijklmnopqrstuvwxyz") return NSCharacterSet(bitmapRepresentation: someSet.bitmapRepresentation) } - static let characterSetBuiltin = TypedFixture("NSCharacterSet-Builtin") { + static nonisolated(unsafe) let characterSetBuiltin = TypedFixture("NSCharacterSet-Builtin") { return NSCharacterSet.alphanumerics as NSCharacterSet } // ===== NSOrderedSet, NSMutableOrderedSet ===== - static let orderedSetOfNumbers = TypedFixture("NSOrderedSet-Numbers") { + static nonisolated(unsafe) let orderedSetOfNumbers = TypedFixture("NSOrderedSet-Numbers") { let numbers = [1, 2, 3, 4, 5].map { NSNumber(value: $0) } return NSOrderedSet(array: numbers) } - static let orderedSetEmpty = TypedFixture("NSOrderedSet-Empty") { + static nonisolated(unsafe) let orderedSetEmpty = TypedFixture("NSOrderedSet-Empty") { return NSOrderedSet() } - static let mutableOrderedSetOfNumbers = TypedFixture("NSMutableOrderedSet-Numbers") { + static nonisolated(unsafe) let mutableOrderedSetOfNumbers = TypedFixture("NSMutableOrderedSet-Numbers") { let numbers = [1, 2, 3, 4, 5].map { NSNumber(value: $0) } return NSMutableOrderedSet(array: numbers) } - static let mutableOrderedSetEmpty = TypedFixture("NSMutableOrderedSet-Empty") { + static nonisolated(unsafe) let mutableOrderedSetEmpty = TypedFixture("NSMutableOrderedSet-Empty") { return NSMutableOrderedSet() } // ===== NSMeasurement ===== - static let zeroMeasurement = TypedFixture("NSMeasurement-Zero") { + static nonisolated(unsafe) let zeroMeasurement = TypedFixture("NSMeasurement-Zero") { let noUnit = Unit(symbol: "") return NSMeasurement(doubleValue: 0, unit: noUnit) } - static let lengthMeasurement = TypedFixture("NSMeasurement-Length") { + static nonisolated(unsafe) let lengthMeasurement = TypedFixture("NSMeasurement-Length") { return NSMeasurement(doubleValue: 45, unit: UnitLength.miles) } - static let frequencyMeasurement = TypedFixture("NSMeasurement-Frequency") { + static nonisolated(unsafe) let frequencyMeasurement = TypedFixture("NSMeasurement-Frequency") { return NSMeasurement(doubleValue: 1400, unit: UnitFrequency.megahertz) } - static let angleMeasurement = TypedFixture("NSMeasurement-Angle") { + static nonisolated(unsafe) let angleMeasurement = TypedFixture("NSMeasurement-Angle") { return NSMeasurement(doubleValue: 90, unit: UnitAngle.degrees) } // ===== Fixture list ===== - static let _listOfAllFixtures: [AnyFixture] = [ + static nonisolated(unsafe) let _listOfAllFixtures: [AnyFixture] = [ AnyFixture(Fixtures.mutableAttributedString), AnyFixture(Fixtures.attributedString), AnyFixture(Fixtures.byteCountFormatterDefault), @@ -370,11 +370,11 @@ enum Fixtures { // This ensures that we do not have fixtures with duplicate identifiers: - static var all: [AnyFixture] { + static nonisolated(unsafe) var all: [AnyFixture] { return Array(Fixtures.allFixturesByIdentifier.values) } - static var allFixturesByIdentifier: [String: AnyFixture] = { + static nonisolated(unsafe) var allFixturesByIdentifier: [String: AnyFixture] = { let keysAndValues = Fixtures._listOfAllFixtures.map { ($0.identifier, $0) } return Dictionary(keysAndValues, uniquingKeysWith: { _, _ in fatalError("No two keys should be the same in fixtures. Double-check keys in FixtureValues.swift to make sure they're all unique.") }) }() diff --git a/Tests/Foundation/HTTPServer.swift b/Tests/Foundation/HTTPServer.swift index aac4369f2e..ec0fd78c1b 100644 --- a/Tests/Foundation/HTTPServer.swift +++ b/Tests/Foundation/HTTPServer.swift @@ -40,8 +40,8 @@ private func debugLog(_ msg: String) { } public let globalDispatchQueue = DispatchQueue.global() -public let dispatchQueueMake: (String) -> DispatchQueue = { DispatchQueue.init(label: $0) } -public let dispatchGroupMake: () -> DispatchGroup = DispatchGroup.init +public let dispatchQueueMake: @Sendable (String) -> DispatchQueue = { DispatchQueue.init(label: $0) } +public let dispatchGroupMake: @Sendable () -> DispatchGroup = DispatchGroup.init struct _HTTPUtils { static let CRLF = "\r\n" @@ -1082,7 +1082,7 @@ struct ServerError : Error { extension ServerError : CustomStringConvertible { var description: String { - let s = String(validatingUTF8: strerror(errno)) ?? "" + let s = String(validatingCString: strerror(errno)) ?? "" return "\(operation) failed: \(s) (\(_code))" } } @@ -1106,10 +1106,10 @@ extension LoopbackServerTest { class LoopbackServerTest : XCTestCase { private static let staticSyncQ = DispatchQueue(label: "org.swift.TestFoundation.HTTPServer.StaticSyncQ") - private static var _serverPort: Int = -1 - private static var _serverActive = false - private static var testServer: _HTTPServer? = nil - private static var _options: Options = .default + nonisolated(unsafe) private static var _serverPort: Int = -1 + nonisolated(unsafe) private static var _serverActive = false + nonisolated(unsafe) private static var testServer: _HTTPServer? = nil + nonisolated(unsafe) private static var _options: Options = .default static var options: Options { get { @@ -1157,7 +1157,7 @@ class LoopbackServerTest : XCTestCase { while serverActive { do { - let httpServer = try testServer!.listen() + nonisolated(unsafe) let httpServer = try testServer!.listen() @Sendable func handleRequest() { let subServer = TestURLSessionServer(httpServer: httpServer) diff --git a/Tests/Foundation/TestDateIntervalFormatter.swift b/Tests/Foundation/TestDateIntervalFormatter.swift index 61278e005f..87f6fe9e3a 100644 --- a/Tests/Foundation/TestDateIntervalFormatter.swift +++ b/Tests/Foundation/TestDateIntervalFormatter.swift @@ -46,7 +46,7 @@ extension String { } class TestDateIntervalFormatter: XCTestCase { - private var formatter: DateIntervalFormatter! + private nonisolated(unsafe) var formatter: DateIntervalFormatter! override func setUp() { super.setUp() diff --git a/Tests/Foundation/TestFileHandle.swift b/Tests/Foundation/TestFileHandle.swift index 5c4ac2d3c8..f754361483 100644 --- a/Tests/Foundation/TestFileHandle.swift +++ b/Tests/Foundation/TestFileHandle.swift @@ -467,8 +467,9 @@ class TestFileHandle : XCTestCase { func test_readToEndOfFileInBackgroundAndNotify() { let handle = createFileHandle() + nonisolated(unsafe) let nonisolatedSelf = self let done = expectation(forNotification: .NSFileHandleReadToEndOfFileCompletion, object: handle, notificationCenter: .default) { (notification) -> Bool in - XCTAssertEqual(notification.userInfo as? [String: AnyHashable], [NSFileHandleNotificationDataItem: self.content], "User info was incorrect") + XCTAssertEqual(notification.userInfo as? [String: AnyHashable], [NSFileHandleNotificationDataItem: nonisolatedSelf.content], "User info was incorrect") return true } diff --git a/Tests/Foundation/TestJSONSerialization.swift b/Tests/Foundation/TestJSONSerialization.swift index d1f957d203..b155deb982 100644 --- a/Tests/Foundation/TestJSONSerialization.swift +++ b/Tests/Foundation/TestJSONSerialization.swift @@ -67,7 +67,6 @@ extension TestJSONSerialization { case data case stream } - static var objectType = ObjectType.data func test_deserialize_emptyObject_withData() { deserialize_emptyObject(objectType: .data) diff --git a/Tests/Foundation/TestMeasurement.swift b/Tests/Foundation/TestMeasurement.swift index ec93bf1832..1cf5ad2b28 100644 --- a/Tests/Foundation/TestMeasurement.swift +++ b/Tests/Foundation/TestMeasurement.swift @@ -17,8 +17,8 @@ class CustomUnit: Unit { super.init(coder: aDecoder) } - public static let bugs = CustomUnit(symbol: "bug") - public static let features = CustomUnit(symbol: "feature") + public static nonisolated(unsafe) let bugs = CustomUnit(symbol: "bug") + public static nonisolated(unsafe) let features = CustomUnit(symbol: "feature") } #endif diff --git a/Tests/Foundation/TestNotificationCenter.swift b/Tests/Foundation/TestNotificationCenter.swift index b65a3d46f4..5dc442a910 100644 --- a/Tests/Foundation/TestNotificationCenter.swift +++ b/Tests/Foundation/TestNotificationCenter.swift @@ -7,6 +7,8 @@ // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // +final class NotificationCenterDummyObject : NSObject, Sendable { } + class TestNotificationCenter : XCTestCase { func test_defaultCenter() { let defaultCenter1 = NotificationCenter.default @@ -25,11 +27,11 @@ class TestNotificationCenter : XCTestCase { func test_postNotification() { let notificationCenter = NotificationCenter() let notificationName = Notification.Name(rawValue: "test_postNotification_name") - var flag = false - let dummyObject = NSObject() + nonisolated(unsafe) var flag = false + let dummyObject = NotificationCenterDummyObject() let observer = notificationCenter.addObserver(forName: notificationName, object: dummyObject, queue: nil) { notification in XCTAssertEqual(notificationName, notification.name) - XCTAssertTrue(dummyObject === notification.object as? NSObject) + XCTAssertTrue(dummyObject === notification.object as? NotificationCenterDummyObject) flag = true } @@ -43,9 +45,9 @@ class TestNotificationCenter : XCTestCase { func test_postNotificationForObject() { let notificationCenter = NotificationCenter() let notificationName = Notification.Name(rawValue: "test_postNotificationForObject_name") - var flag = true - let dummyObject = NSObject() - let dummyObject2 = NSObject() + nonisolated(unsafe) var flag = true + let dummyObject = NotificationCenterDummyObject() + let dummyObject2 = NotificationCenterDummyObject() let observer = notificationCenter.addObserver(forName: notificationName, object: dummyObject, queue: nil) { notification in flag = false } @@ -59,17 +61,17 @@ class TestNotificationCenter : XCTestCase { func test_postMultipleNotifications() { let notificationCenter = NotificationCenter() let notificationName = Notification.Name(rawValue: "test_postMultipleNotifications_name") - var flag1 = false + nonisolated(unsafe) var flag1 = false let observer1 = notificationCenter.addObserver(forName: notificationName, object: nil, queue: nil) { _ in flag1 = true } - var flag2 = true + nonisolated(unsafe) var flag2 = true let observer2 = notificationCenter.addObserver(forName: notificationName, object: nil, queue: nil) { _ in flag2 = false } - var flag3 = false + nonisolated(unsafe) var flag3 = false let observer3 = notificationCenter.addObserver(forName: notificationName, object: nil, queue: nil) { _ in flag3 = true } @@ -89,17 +91,17 @@ class TestNotificationCenter : XCTestCase { let notificationCenter = NotificationCenter() let notificationName = Notification.Name(rawValue: "test_addObserverForNilName_name") let invalidNotificationName = Notification.Name(rawValue: "test_addObserverForNilName_name_invalid") - var flag1 = false + nonisolated(unsafe) var flag1 = false let observer1 = notificationCenter.addObserver(forName: notificationName, object: nil, queue: nil) { _ in flag1 = true } - var flag2 = true + nonisolated(unsafe) var flag2 = true let observer2 = notificationCenter.addObserver(forName: invalidNotificationName, object: nil, queue: nil) { _ in flag2 = false } - var flag3 = false + nonisolated(unsafe) var flag3 = false let observer3 = notificationCenter.addObserver(forName: nil, object: nil, queue: nil) { _ in flag3 = true } @@ -117,7 +119,7 @@ class TestNotificationCenter : XCTestCase { func test_removeObserver() { let notificationCenter = NotificationCenter() let notificationName = Notification.Name(rawValue: "test_removeObserver_name") - var flag = true + nonisolated(unsafe) var flag = true let observer = notificationCenter.addObserver(forName: notificationName, object: nil, queue: nil) { _ in flag = false } @@ -150,8 +152,8 @@ class TestNotificationCenter : XCTestCase { let name = Notification.Name(rawValue: "\(#function)_name") let notificationCenter = NotificationCenter() let operationQueue = OperationQueue() - var flag1 = false - var flag2 = false + nonisolated(unsafe) var flag1 = false + nonisolated(unsafe) var flag2 = false _ = notificationCenter.addObserver(forName: name, object: nil, queue: operationQueue) { _ in XCTAssertEqual(OperationQueue.current, operationQueue) diff --git a/Tests/Foundation/TestNotificationQueue.swift b/Tests/Foundation/TestNotificationQueue.swift index 1b61a6601e..85f3f76010 100644 --- a/Tests/Foundation/TestNotificationQueue.swift +++ b/Tests/Foundation/TestNotificationQueue.swift @@ -11,7 +11,7 @@ final class DummyObject : NSObject, Sendable { } class TestNotificationQueue : XCTestCase { func test_defaultQueue() { - let defaultQueue1 = NotificationQueue.default + nonisolated(unsafe) let defaultQueue1 = NotificationQueue.default let defaultQueue2 = NotificationQueue.default XCTAssertEqual(defaultQueue1, defaultQueue2) @@ -26,7 +26,7 @@ class TestNotificationQueue : XCTestCase { let notificationName = Notification.Name(rawValue: "test_postNowWithoutCoalescing") let dummyObject = DummyObject() let notification = Notification(name: notificationName, object: dummyObject) - var numberOfCalls = 0 + nonisolated(unsafe) var numberOfCalls = 0 let obs = NotificationCenter.default.addObserver(forName: notificationName, object: dummyObject, queue: nil) { notification in numberOfCalls += 1 } @@ -40,7 +40,7 @@ class TestNotificationQueue : XCTestCase { let notificationName = Notification.Name(rawValue: "test_postNowToDefaultQueueWithCoalescingOnName") let dummyObject = DummyObject() let notification = Notification(name: notificationName, object: dummyObject) - var numberOfCalls = 0 + nonisolated(unsafe) var numberOfCalls = 0 let obs = NotificationCenter.default.addObserver(forName: notificationName, object: dummyObject, queue: nil) { notification in numberOfCalls += 1 } @@ -57,7 +57,7 @@ class TestNotificationQueue : XCTestCase { let notificationName = Notification.Name(rawValue: "test_postNowToCustomQueue") let dummyObject = DummyObject() let notification = Notification(name: notificationName, object: dummyObject) - var numberOfCalls = 0 + nonisolated(unsafe) var numberOfCalls = 0 let notificationCenter = NotificationCenter() let obs = notificationCenter.addObserver(forName: notificationName, object: dummyObject, queue: nil) { notification in numberOfCalls += 1 @@ -71,7 +71,7 @@ class TestNotificationQueue : XCTestCase { func test_postNowForDefaultRunLoopMode() { let notificationName = Notification.Name(rawValue: "test_postNowToDefaultQueueWithCoalescingOnName") let dummyObject = DummyObject() - var numberOfCalls = 0 + nonisolated(unsafe) var numberOfCalls = 0 let obs = NotificationCenter.default.addObserver(forName: notificationName, object: dummyObject, queue: nil) { notification in numberOfCalls += 1 } @@ -103,7 +103,7 @@ class TestNotificationQueue : XCTestCase { let notificationName = Notification.Name(rawValue: "test_postAsapToDefaultQueue") let dummyObject = DummyObject() let notification = Notification(name: notificationName, object: dummyObject) - var numberOfCalls = 0 + nonisolated(unsafe) var numberOfCalls = 0 let obs = NotificationCenter.default.addObserver(forName: notificationName, object: dummyObject, queue: nil) { notification in numberOfCalls += 1 } @@ -119,7 +119,7 @@ class TestNotificationQueue : XCTestCase { // Check coalescing on name and object let notificationName = Notification.Name(rawValue: "test_postAsapToDefaultQueueWithCoalescingOnNameAndSender") let notification = Notification(name: notificationName, object: DummyObject()) - var numberOfCalls = 0 + nonisolated(unsafe) var numberOfCalls = 0 let obs = NotificationCenter.default.addObserver(forName: notificationName, object: notification.object, queue: nil) { notification in numberOfCalls += 1 } @@ -137,12 +137,12 @@ class TestNotificationQueue : XCTestCase { // Check coalescing on name or sender let notificationName = Notification.Name(rawValue: "test_postAsapToDefaultQueueWithCoalescingOnNameOrSender") let notification1 = Notification(name: notificationName, object: DummyObject()) - var numberOfNameCoalescingCalls = 0 + nonisolated(unsafe) var numberOfNameCoalescingCalls = 0 let obs1 = NotificationCenter.default.addObserver(forName: notificationName, object: notification1.object, queue: nil) { notification in numberOfNameCoalescingCalls += 1 } let notification2 = Notification(name: notificationName, object: DummyObject()) - var numberOfObjectCoalescingCalls = 0 + nonisolated(unsafe) var numberOfObjectCoalescingCalls = 0 let obs2 = NotificationCenter.default.addObserver(forName: notificationName, object: notification2.object, queue: nil) { notification in numberOfObjectCoalescingCalls += 1 } @@ -171,7 +171,7 @@ class TestNotificationQueue : XCTestCase { let notificationName = Notification.Name(rawValue: "test_postIdleToDefaultQueue") let dummyObject = DummyObject() let notification = Notification(name: notificationName, object: dummyObject) - var numberOfCalls = 0 + nonisolated(unsafe) var numberOfCalls = 0 let obs = NotificationCenter.default.addObserver(forName: notificationName, object: dummyObject, queue: nil) { notification in numberOfCalls += 1 @@ -185,7 +185,7 @@ class TestNotificationQueue : XCTestCase { func test_notificationQueueLifecycle() { // check that notificationqueue is associated with current thread. when the thread is destroyed, the queue should be deallocated as well - weak var notificationQueue: NotificationQueue? + nonisolated(unsafe) weak var notificationQueue: NotificationQueue? self.executeInBackgroundThread() { let nq = NotificationQueue(notificationCenter: NotificationCenter()) @@ -207,7 +207,7 @@ class TestNotificationQueue : XCTestCase { waitForExpectations(timeout: 0.1) } - private func executeInBackgroundThread(_ operation: @escaping () -> Void) { + private func executeInBackgroundThread(_ operation: @Sendable @escaping () -> Void) { let e = expectation(description: "Background Execution") let bgThread = Thread() { operation() diff --git a/Tests/Foundation/TestOperationQueue.swift b/Tests/Foundation/TestOperationQueue.swift index 6de3ff56af..4bb912f1af 100644 --- a/Tests/Foundation/TestOperationQueue.swift +++ b/Tests/Foundation/TestOperationQueue.swift @@ -70,8 +70,9 @@ class TestOperationQueue : XCTestCase { } func test_isExecutingWorks() { - class _OperationBox { - var operation: Operation? + final class _OperationBox : Sendable { + // Only mutate this before kicking off the operation + nonisolated(unsafe) var operation: Operation? init() { self.operation = nil } @@ -700,8 +701,9 @@ class TestOperationQueue : XCTestCase { let didRunOp1 = expectation(description: "Did run first operation") let didRunOp2 = expectation(description: "Did run second operation") + nonisolated(unsafe) let nonisolatedSelf = self queue.addOperation { - self.wait(for: [didRunOp2], timeout: 0.2) + nonisolatedSelf.wait(for: [didRunOp2], timeout: 0.2) didRunOp1.fulfill() } queue.addOperation { @@ -722,9 +724,10 @@ class TestOperationQueue : XCTestCase { let didRunOp1Completion = expectation(description: "Did run first operation completion") let didRunOp1Dependency = expectation(description: "Did run first operation dependency") let didRunOp2 = expectation(description: "Did run second operation") - + + nonisolated(unsafe) let nonisolatedSelf = self let op1 = BlockOperation { - self.wait(for: [didRunOp1Dependency, didRunOp2], timeout: 0.2) + nonisolatedSelf.wait(for: [didRunOp1Dependency, didRunOp2], timeout: 0.2) didRunOp1.fulfill() } op1.completionBlock = { diff --git a/Tests/Foundation/TestProcess.swift b/Tests/Foundation/TestProcess.swift index 45830874dc..ca6b91cf72 100644 --- a/Tests/Foundation/TestProcess.swift +++ b/Tests/Foundation/TestProcess.swift @@ -452,7 +452,7 @@ class TestProcess : XCTestCase { #if os(Windows) throw XCTSkip("Windows does not have signals") #else - let helper = _SignalHelperRunner() + nonisolated(unsafe) let helper = _SignalHelperRunner() do { try helper.start() } catch { @@ -879,34 +879,33 @@ class _SignalHelperRunner { process.arguments = ["--signal-test"] process.standardOutput = outputPipe.fileHandleForWriting - outputPipe.fileHandleForReading.readabilityHandler = { [weak self] fh in - if let strongSelf = self { - let newLine = UInt8(ascii: "\n") - - strongSelf.bytesIn.append(fh.availableData) - if strongSelf.bytesIn.isEmpty { - return - } - // Split the incoming data into lines. - while let index = strongSelf.bytesIn.firstIndex(of: newLine) { - if index >= strongSelf.bytesIn.startIndex { - // don't include the newline when converting to string - let line = String(data: strongSelf.bytesIn[strongSelf.bytesIn.startIndex..= nonisolatedSelf.bytesIn.startIndex { + // don't include the newline when converting to string + let line = String(data: nonisolatedSelf.bytesIn[nonisolatedSelf.bytesIn.startIndex.. 0) } - func test_gzippedDownloadTask() { + func test_gzippedDownloadTask() async { let urlString = "http://127.0.0.1:\(TestURLSession.serverPort)/gzipped-response" let url = URL(string: urlString)! let d = DownloadTask(testCase: self, description: "GET \(urlString): gzipped response") @@ -369,7 +370,7 @@ class TestURLSession: LoopbackServerTest { } } - func test_finishTasksAndInvalidate() { + func test_finishTasksAndInvalidate() async { let urlString = "http://127.0.0.1:\(TestURLSession.serverPort)/Nepal" let invalidateExpectation = expectation(description: "Session invalidation") let delegate = SessionDelegate(invalidateExpectation: invalidateExpectation) @@ -385,7 +386,7 @@ class TestURLSession: LoopbackServerTest { waitForExpectations(timeout: 12) } - func test_taskError() { + func test_taskError() async { let urlString = "http://127.0.0.0:999999/Nepal" let url = URL(string: urlString)! let session = URLSession(configuration: URLSessionConfiguration.default, @@ -420,7 +421,7 @@ class TestURLSession: LoopbackServerTest { } // This test is buggy because the server could respond before the task is cancelled. - func test_cancelTask() { + func test_cancelTask() async { let urlString = "http://127.0.0.1:\(TestURLSession.serverPort)/Peru" var urlRequest = URLRequest(url: URL(string: urlString)!) urlRequest.setValue("2.0", forHTTPHeaderField: "X-Pause") @@ -431,7 +432,7 @@ class TestURLSession: LoopbackServerTest { waitForExpectations(timeout: 12) } - func test_unhandledURLProtocol() { + func test_unhandledURLProtocol() async { let urlString = "foobar://127.0.0.1:\(TestURLSession.serverPort)/Nepal" let url = URL(string: urlString)! let session = URLSession(configuration: URLSessionConfiguration.default, @@ -454,7 +455,7 @@ class TestURLSession: LoopbackServerTest { } } - func test_requestToNilURL() { + func test_requestToNilURL() async { let urlString = "http://127.0.0.1:\(TestURLSession.serverPort)/Nepal" let url = URL(string: urlString)! let session = URLSession(configuration: URLSessionConfiguration.default, @@ -479,7 +480,7 @@ class TestURLSession: LoopbackServerTest { } } - func test_suspendResumeTask() throws { + func test_suspendResumeTask() async throws { throw XCTSkip("This test is disabled (occasionally breaks)") #if false let urlString = "http://127.0.0.1:\(TestURLSession.serverPort)/get" @@ -520,7 +521,7 @@ class TestURLSession: LoopbackServerTest { } - func test_verifyRequestHeaders() { + func test_verifyRequestHeaders() async { let config = URLSessionConfiguration.default config.timeoutIntervalForRequest = 5 let urlString = "http://127.0.0.1:\(TestURLSession.serverPort)/requestHeaders" @@ -546,7 +547,7 @@ class TestURLSession: LoopbackServerTest { // Verify httpAdditionalHeaders from session configuration are added to the request // and whether it is overriden by Request.allHTTPHeaderFields. - func test_verifyHttpAdditionalHeaders() { + func test_verifyHttpAdditionalHeaders() async { let config = URLSessionConfiguration.default config.timeoutIntervalForRequest = 5 config.httpAdditionalHeaders = ["header2": "svalue2", "header3": "svalue3", "header4": "svalue4"] @@ -574,7 +575,7 @@ class TestURLSession: LoopbackServerTest { waitForExpectations(timeout: 30) } - func test_taskTimeout() { + func test_taskTimeout() async { let config = URLSessionConfiguration.default config.timeoutIntervalForRequest = 5 let urlString = "http://127.0.0.1:\(TestURLSession.serverPort)/Peru" @@ -590,7 +591,7 @@ class TestURLSession: LoopbackServerTest { waitForExpectations(timeout: 30) } - func test_httpTimeout() { + func test_httpTimeout() async { let config = URLSessionConfiguration.default config.timeoutIntervalForRequest = 10 let urlString = "http://127.0.0.1:\(TestURLSession.serverPort)/Peru" @@ -607,7 +608,7 @@ class TestURLSession: LoopbackServerTest { waitForExpectations(timeout: 30) } - func test_connectTimeout() { + func test_connectTimeout() async throws { // Reconfigure http server for this specific scenario: // a slow request keeps web server busy, while other // request times out on connection attempt. @@ -636,7 +637,7 @@ class TestURLSession: LoopbackServerTest { XCTAssertEqual((error as? URLError)?.code, .timedOut, "Task should fail with URLError.timedOut error") } slowTask.resume() - Thread.sleep(forTimeInterval: 0.1) // Give slow task some time to start + try await Task.sleep(nanoseconds: 100_000_000) // Give slow task some time to start fastTask.resume() waitForExpectations(timeout: 30) @@ -647,7 +648,7 @@ class TestURLSession: LoopbackServerTest { Self.startServer() } - func test_repeatedRequestsStress() throws { + func test_repeatedRequestsStress() async throws { // TODO: try disabling curl connection cache to force socket close early. Or create several url sessions (they have cleanup in deinit) let config = URLSessionConfiguration.default @@ -655,10 +656,10 @@ class TestURLSession: LoopbackServerTest { let session = URLSession(configuration: config, delegate: nil, delegateQueue: nil) let req = URLRequest(url: URL(string: urlString)!) - var requestsLeft = 3000 + nonisolated(unsafe) var requestsLeft = 3000 let expect = expectation(description: "\(requestsLeft) x GET \(urlString)") - func doRequests(completion: @escaping () -> Void) { + @Sendable func doRequests(completion: @Sendable @escaping () -> Void) { // We only care about completion of one of the tasks, // so we could move to next cycle. // Some overlapping would happen and that's what we @@ -676,7 +677,7 @@ class TestURLSession: LoopbackServerTest { task3.resume() } - func checkCountAndRunNext() { + @Sendable func checkCountAndRunNext() { guard requestsLeft > 0 else { expect.fulfill() return @@ -690,7 +691,7 @@ class TestURLSession: LoopbackServerTest { waitForExpectations(timeout: 30) } - func test_httpRedirectionWithCode300() throws { + func test_httpRedirectionWithCode300() async throws { let statusCode = 300 for method in httpMethods { let testMethod = "\(method) request with statusCode \(statusCode)" @@ -732,7 +733,7 @@ class TestURLSession: LoopbackServerTest { } } - func test_httpRedirectionWithCode301_302() throws { + func test_httpRedirectionWithCode301_302() async throws { for statusCode in 301...302 { for method in httpMethods { let testMethod = "\(method) request with statusCode \(statusCode)" @@ -779,7 +780,7 @@ class TestURLSession: LoopbackServerTest { } } - func test_httpRedirectionWithCode303() throws { + func test_httpRedirectionWithCode303() async throws { let statusCode = 303 for method in httpMethods { let testMethod = "\(method) request with statusCode \(statusCode)" @@ -813,7 +814,7 @@ class TestURLSession: LoopbackServerTest { } } - func test_httpRedirectionWithCode304() throws { + func test_httpRedirectionWithCode304() async throws { let statusCode = 304 for method in httpMethods { let testMethod = "\(method) request with statusCode \(statusCode)" @@ -843,7 +844,7 @@ class TestURLSession: LoopbackServerTest { } } - func test_httpRedirectionWithCode305_308() throws { + func test_httpRedirectionWithCode305_308() async throws { for statusCode in 305...308 { for method in httpMethods { let testMethod = "\(method) request with statusCode \(statusCode)" @@ -888,7 +889,7 @@ class TestURLSession: LoopbackServerTest { } } - func test_httpRedirectDontFollowUsingNil() throws { + func test_httpRedirectDontFollowUsingNil() async throws { let statusCode = 302 for method in httpMethods { let testMethod = "\(method) request with statusCode \(statusCode)" @@ -947,7 +948,7 @@ class TestURLSession: LoopbackServerTest { } } - func test_httpRedirectDontFollowIgnoringHandler() throws { + func test_httpRedirectDontFollowIgnoringHandler() async throws { let statusCode = 302 for method in httpMethods { let testMethod = "\(method) request with statusCode \(statusCode)" @@ -975,7 +976,7 @@ class TestURLSession: LoopbackServerTest { } } - func test_httpRedirectionWithCompleteRelativePath() { + func test_httpRedirectionWithCompleteRelativePath() async { let urlString = "http://127.0.0.1:\(TestURLSession.serverPort)/UnitedStates" let url = URL(string: urlString)! let d = HTTPRedirectionDataTask(with: expectation(description: "GET \(urlString): with HTTP redirection")) @@ -983,7 +984,7 @@ class TestURLSession: LoopbackServerTest { waitForExpectations(timeout: 12) } - func test_httpRedirectionWithInCompleteRelativePath() { + func test_httpRedirectionWithInCompleteRelativePath() async { let urlString = "http://127.0.0.1:\(TestURLSession.serverPort)/UnitedKingdom" let url = URL(string: urlString)! let d = HTTPRedirectionDataTask(with: expectation(description: "GET \(urlString): with HTTP redirection")) @@ -991,7 +992,7 @@ class TestURLSession: LoopbackServerTest { waitForExpectations(timeout: 12) } - func test_httpRedirectionWithDefaultPort() { + func test_httpRedirectionWithDefaultPort() async { let urlString = "http://127.0.0.1:\(TestURLSession.serverPort)/redirect-with-default-port" let url = URL(string: urlString)! let d = HTTPRedirectionDataTask(with: expectation(description: "GET \(urlString): with HTTP redirection")) @@ -999,7 +1000,7 @@ class TestURLSession: LoopbackServerTest { waitForExpectations(timeout: 12) } - func test_httpRedirectionWithEncodedQuery() { + func test_httpRedirectionWithEncodedQuery() async { let location = "echo-query%3Fparam%3Dfoo" // "echo-query?param=foo" url encoded let urlString = "http://127.0.0.1:\(TestURLSession.serverPort)/303?location=\(location)" let url = URL(string: urlString)! @@ -1015,7 +1016,7 @@ class TestURLSession: LoopbackServerTest { } // temporarily disabled (https://bugs.swift.org/browse/SR-5751) - func test_httpRedirectionTimeout() { + func test_httpRedirectionTimeout() async { let urlString = "http://127.0.0.1:\(TestURLSession.serverPort)/UnitedStates" var req = URLRequest(url: URL(string: urlString)!) req.timeoutInterval = 3 @@ -1035,7 +1036,7 @@ class TestURLSession: LoopbackServerTest { waitForExpectations(timeout: 12) } - func test_httpRedirectionChainInheritsTimeoutInterval() throws { + func test_httpRedirectionChainInheritsTimeoutInterval() async throws { throw XCTSkip("This test is disabled (https://bugs.swift.org/browse/SR-14433)") #if false let redirectCount = 4 @@ -1065,7 +1066,7 @@ class TestURLSession: LoopbackServerTest { #endif } - func test_httpRedirectionExceededMaxRedirects() throws { + func test_httpRedirectionExceededMaxRedirects() async throws { throw XCTSkip("This test is disabled (https://bugs.swift.org/browse/SR-14433)") #if false let expectedMaxRedirects = 20 @@ -1110,7 +1111,7 @@ class TestURLSession: LoopbackServerTest { #endif } - func test_willPerformRedirect() throws { + func test_willPerformRedirect() async throws { let urlString = "http://127.0.0.1:\(TestURLSession.serverPort)/redirect/1" let url = try XCTUnwrap(URL(string: urlString)) let redirectURL = try XCTUnwrap(URL(string: "http://127.0.0.1:\(TestURLSession.serverPort)/jsonBody")) @@ -1136,7 +1137,7 @@ class TestURLSession: LoopbackServerTest { waitForExpectations(timeout: 5) } - func test_httpNotFound() throws { + func test_httpNotFound() async throws { let urlString = "http://127.0.0.1:\(TestURLSession.serverPort)/404" let url = try XCTUnwrap(URL(string: urlString)) @@ -1164,7 +1165,7 @@ class TestURLSession: LoopbackServerTest { } } - func test_http0_9SimpleResponses() throws { + func test_http0_9SimpleResponses() async throws { throw XCTSkip("This test is disabled (breaks on Ubuntu 20.04)") #if false for brokenCity in ["Pompeii", "Sodom"] { @@ -1194,7 +1195,7 @@ class TestURLSession: LoopbackServerTest { #endif } - func test_outOfRangeButCorrectlyFormattedHTTPCode() { + func test_outOfRangeButCorrectlyFormattedHTTPCode() async { let brokenCity = "Kameiros" let urlString = "http://127.0.0.1:\(TestURLSession.serverPort)/LandOfTheLostCities/\(brokenCity)" let url = URL(string: urlString)! @@ -1220,7 +1221,7 @@ class TestURLSession: LoopbackServerTest { waitForExpectations(timeout: 12) } - func test_missingContentLengthButStillABody() { + func test_missingContentLengthButStillABody() async { let brokenCity = "Myndus" let urlString = "http://127.0.0.1:\(TestURLSession.serverPort)/LandOfTheLostCities/\(brokenCity)" let url = URL(string: urlString)! @@ -1247,7 +1248,7 @@ class TestURLSession: LoopbackServerTest { } - func test_illegalHTTPServerResponses() { + func test_illegalHTTPServerResponses() async { for brokenCity in ["Gomorrah", "Dinavar", "Kuhikugu"] { let urlString = "http://127.0.0.1:\(TestURLSession.serverPort)/LandOfTheLostCities/\(brokenCity)" let url = URL(string: urlString)! @@ -1268,7 +1269,7 @@ class TestURLSession: LoopbackServerTest { } } - func test_dataTaskWithSharedDelegate() { + func test_dataTaskWithSharedDelegate() async { let urlString0 = "http://127.0.0.1:\(TestURLSession.serverPort)/Nepal" let sharedDelegate = SharedDelegate(dataCompletionExpectation: expectation(description: "GET \(urlString0)")) let session = URLSession(configuration: .default, delegate: sharedDelegate, delegateQueue: nil) @@ -1280,7 +1281,7 @@ class TestURLSession: LoopbackServerTest { waitForExpectations(timeout: 20) } - func test_simpleUploadWithDelegate() { + func test_simpleUploadWithDelegate() async { let delegate = HTTPUploadDelegate() let session = URLSession(configuration: .default, delegate: delegate, delegateQueue: nil) let urlString = "http://127.0.0.1:\(TestURLSession.serverPort)/upload" @@ -1297,7 +1298,7 @@ class TestURLSession: LoopbackServerTest { } - func test_requestWithEmptyBody() throws { + func test_requestWithEmptyBody() async throws { for method in httpMethods { let urlString = "http://127.0.0.1:\(TestURLSession.serverPort)/" + method.lowercased() let url = try XCTUnwrap(URL(string: urlString)) @@ -1353,7 +1354,7 @@ class TestURLSession: LoopbackServerTest { } } - func test_requestWithNonEmptyBody() throws { + func test_requestWithNonEmptyBody() async throws { throw XCTSkip("This test is disabled (started failing for no readily available reason)") #if false let bodyData = try XCTUnwrap("This is a request body".data(using: .utf8)) @@ -1436,7 +1437,7 @@ class TestURLSession: LoopbackServerTest { } - func test_concurrentRequests() throws { + func test_concurrentRequests() async throws { throw XCTSkip("This test is disabled (Intermittent SEGFAULT: rdar://84519512)") #if false let tasks = 10 @@ -1478,7 +1479,7 @@ class TestURLSession: LoopbackServerTest { } } - func test_disableCookiesStorage() { + func test_disableCookiesStorage() async { let config = URLSessionConfiguration.default config.timeoutIntervalForRequest = 5 config.httpCookieAcceptPolicy = HTTPCookie.AcceptPolicy.never @@ -1505,7 +1506,7 @@ class TestURLSession: LoopbackServerTest { XCTAssertEqual(cookies?.count, 0) } - func test_cookiesStorage() { + func test_cookiesStorage() async { let config = URLSessionConfiguration.default config.timeoutIntervalForRequest = 5 emptyCookieStorage(storage: config.httpCookieStorage) @@ -1530,7 +1531,7 @@ class TestURLSession: LoopbackServerTest { XCTAssertEqual(cookies?.count, 1) } - func test_redirectionWithSetCookies() { + func test_redirectionWithSetCookies() async { let config = URLSessionConfiguration.default config.timeoutIntervalForRequest = 5 emptyCookieStorage(storage: config.httpCookieStorage) @@ -1554,7 +1555,7 @@ class TestURLSession: LoopbackServerTest { waitForExpectations(timeout: 30) } - func test_previouslySetCookiesAreSentInLaterRequests() { + func test_previouslySetCookiesAreSentInLaterRequests() async { let config = URLSessionConfiguration.default config.timeoutIntervalForRequest = 5 emptyCookieStorage(storage: config.httpCookieStorage) @@ -1598,7 +1599,7 @@ class TestURLSession: LoopbackServerTest { waitForExpectations(timeout: 30) } - func test_cookieStorageForEphemeralConfiguration() { + func test_cookieStorageForEphemeralConfiguration() async { let config = URLSessionConfiguration.ephemeral config.timeoutIntervalForRequest = 5 emptyCookieStorage(storage: config.httpCookieStorage) @@ -1623,7 +1624,7 @@ class TestURLSession: LoopbackServerTest { XCTAssertEqual(cookies2?.count, 0) } - func test_setCookieHeadersCanBeIgnored() { + func test_setCookieHeadersCanBeIgnored() async { let config = URLSessionConfiguration.default config.timeoutIntervalForRequest = 5 config.httpShouldSetCookies = false @@ -1669,7 +1670,7 @@ class TestURLSession: LoopbackServerTest { } // Validate that the properties are correctly set - func test_initURLSessionConfiguration() { + func test_initURLSessionConfiguration() async { let config = URLSessionConfiguration.default config.requestCachePolicy = .useProtocolCachePolicy config.timeoutIntervalForRequest = 30 @@ -1702,7 +1703,7 @@ class TestURLSession: LoopbackServerTest { XCTAssertEqual(config.shouldUseExtendedBackgroundIdleMode, true) } - func test_basicAuthRequest() { + func test_basicAuthRequest() async { let urlString = "http://127.0.0.1:\(TestURLSession.serverPort)/auth/basic" let url = URL(string: urlString)! let d = DataTask(with: expectation(description: "GET \(urlString): with a delegate")) @@ -1711,7 +1712,7 @@ class TestURLSession: LoopbackServerTest { } /* Test for SR-8970 to verify that content-type header is not added to post with empty body */ - func test_postWithEmptyBody() { + func test_postWithEmptyBody() async { let config = URLSessionConfiguration.default config.timeoutIntervalForRequest = 5 let urlString = "http://127.0.0.1:\(TestURLSession.serverPort)/emptyPost" @@ -1729,7 +1730,7 @@ class TestURLSession: LoopbackServerTest { waitForExpectations(timeout: 30) } - func test_basicAuthWithUnauthorizedHeader() { + func test_basicAuthWithUnauthorizedHeader() async { let urlString = "http://127.0.0.1:\(TestURLSession.serverPort)/unauthorized" let url = URL(string: urlString)! let expect = expectation(description: "GET \(urlString): with a completion handler") @@ -1743,7 +1744,7 @@ class TestURLSession: LoopbackServerTest { waitForExpectations(timeout: 12, handler: nil) } - func test_checkErrorTypeAfterInvalidateAndCancel() throws { + func test_checkErrorTypeAfterInvalidateAndCancel() async throws { let urlString = "http://127.0.0.1:\(TestURLSession.serverPort)/country.txt" let url = try XCTUnwrap(URL(string: urlString)) var urlRequest = URLRequest(url: url) @@ -1767,7 +1768,7 @@ class TestURLSession: LoopbackServerTest { waitForExpectations(timeout: 5) } - func test_taskCountAfterInvalidateAndCancel() throws { + func test_taskCountAfterInvalidateAndCancel() async throws { let expect = expectation(description: "Check task count after invalidateAndCancel") let session = URLSession(configuration: .default) @@ -1797,15 +1798,15 @@ class TestURLSession: LoopbackServerTest { waitForExpectations(timeout: 5) } - func test_sessionDelegateAfterInvalidateAndCancel() { + func test_sessionDelegateAfterInvalidateAndCancel() async throws { let delegate = SessionDelegate() let session = URLSession(configuration: .default, delegate: delegate, delegateQueue: nil) session.invalidateAndCancel() - Thread.sleep(forTimeInterval: 2) + try await Task.sleep(nanoseconds: 2_000_000_000) XCTAssertNil(session.delegate) } - func test_sessionDelegateCalledIfTaskDelegateDoesNotImplement() throws { + func test_sessionDelegateCalledIfTaskDelegateDoesNotImplement() async throws { let expectation = XCTestExpectation(description: "task finished") let delegate = SessionDelegate(with: expectation) let session = URLSession(configuration: .default, delegate: delegate, delegateQueue: nil) @@ -1817,10 +1818,10 @@ class TestURLSession: LoopbackServerTest { task.delegate = EmptyTaskDelegate() task.resume() - wait(for: [expectation], timeout: 5) + await fulfillment(of: [expectation], timeout: 5) } - func test_getAllTasks() throws { + func test_getAllTasks() async throws { throw XCTSkip("This test is disabled (this causes later ones to crash)") #if false let expect = expectation(description: "Tasks URLSession.getAllTasks") @@ -1871,7 +1872,7 @@ class TestURLSession: LoopbackServerTest { #endif } - func test_getTasksWithCompletion() throws { + func test_getTasksWithCompletion() async throws { throw XCTSkip("This test is disabled (Flaky tests)") #if false let expect = expectation(description: "Test URLSession.getTasksWithCompletion") @@ -1922,7 +1923,7 @@ class TestURLSession: LoopbackServerTest { #endif } - func test_noDoubleCallbackWhenCancellingAndProtocolFailsFast() throws { + func test_noDoubleCallbackWhenCancellingAndProtocolFailsFast() async throws { throw XCTSkip("This test is disabled (Crashes nondeterministically: https://bugs.swift.org/browse/SR-11310)") #if false let urlString = "failfast://bogus" @@ -1954,7 +1955,7 @@ class TestURLSession: LoopbackServerTest { #endif } - func test_cancelledTasksCannotBeResumed() throws { + func test_cancelledTasksCannotBeResumed() async throws { throw XCTSkip("This test is disabled (breaks on Ubuntu 18.04)") #if false let url = try XCTUnwrap(URL(string: "http://127.0.0.1:\(TestURLSession.serverPort)/Nepal")) @@ -1973,7 +1974,7 @@ class TestURLSession: LoopbackServerTest { waitForExpectations(timeout: 1) #endif } - func test_invalidResumeDataForDownloadTask() throws { + func test_invalidResumeDataForDownloadTask() async throws { throw XCTSkip("This test is disabled (Crashes nondeterministically: https://bugs.swift.org/browse/SR-11353)") #if false let done = expectation(description: "Invalid resume data for download task (with completion block)") @@ -2000,7 +2001,7 @@ class TestURLSession: LoopbackServerTest { #endif } - func test_simpleUploadWithDelegateProvidingInputStream() throws { + func test_simpleUploadWithDelegateProvidingInputStream() async throws { throw XCTSkip("This test is disabled (Times out frequently: https://bugs.swift.org/browse/SR-11343)") #if false let fileData = Data(count: 16 * 1024) @@ -2015,7 +2016,7 @@ class TestURLSession: LoopbackServerTest { completionHandler(InputStream(data: fileData)) } delegate.runUploadTask(with: request, timeoutInterval: 4) - waitForExpectations(timeout: 5) + await waitForExpectations(timeout: 5) let httpResponse = delegate.response as? HTTPURLResponse let callBacks: [String] diff --git a/Tests/Foundation/TestUserDefaults.swift b/Tests/Foundation/TestUserDefaults.swift index 28aa2c277a..35bc368c26 100644 --- a/Tests/Foundation/TestUserDefaults.swift +++ b/Tests/Foundation/TestUserDefaults.swift @@ -390,7 +390,7 @@ class TestUserDefaults : XCTestCase { let done = expectation(description: "All notifications have fired.") - var countOfFiredNotifications = 0 + nonisolated(unsafe) var countOfFiredNotifications = 0 let expectedNotificationCount = 3 let observer = NotificationCenter.default.addObserver(forName: UserDefaults.didChangeNotification, object: nil, queue: .main) { (_) in From 0901f2109b91a12aa71b022a423a4debed58cd8f Mon Sep 17 00:00:00 2001 From: Tony Parker Date: Mon, 8 Jul 2024 14:50:39 -0700 Subject: [PATCH 03/15] Remove Sendable annotation from NSAffineTransform --- Sources/Foundation/AffineTransform.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Sources/Foundation/AffineTransform.swift b/Sources/Foundation/AffineTransform.swift index 82ed957248..baf1768267 100644 --- a/Sources/Foundation/AffineTransform.swift +++ b/Sources/Foundation/AffineTransform.swift @@ -412,7 +412,10 @@ public struct NSAffineTransformStruct : Sendable { } } -open class NSAffineTransform: NSObject, @unchecked Sendable { +@available(*, unavailable) +extension NSAffineTransform : Sendable { } + +open class NSAffineTransform: NSObject { // Internal only for testing. internal var affineTransform: AffineTransform From 85dd6b925b92da890f6196563881ff8e26aa3707 Mon Sep 17 00:00:00 2001 From: Tony Parker Date: Fri, 12 Jul 2024 16:20:38 -0700 Subject: [PATCH 04/15] Cleanup some unused types in Utilities and use a Mutex instead of NSLock --- .../include/ForSwiftFoundationOnly.h | 2 ++ Tests/Foundation/TestProcess.swift | 14 ++++----- Tests/Foundation/TestURLSessionFTP.swift | 9 +++--- Tests/Foundation/Utilities.swift | 29 ------------------- 4 files changed, 13 insertions(+), 41 deletions(-) diff --git a/Sources/CoreFoundation/include/ForSwiftFoundationOnly.h b/Sources/CoreFoundation/include/ForSwiftFoundationOnly.h index 315dbc23ba..0a92fe22b2 100644 --- a/Sources/CoreFoundation/include/ForSwiftFoundationOnly.h +++ b/Sources/CoreFoundation/include/ForSwiftFoundationOnly.h @@ -59,6 +59,8 @@ #if TARGET_OS_LINUX #include +#include +#include #endif #if TARGET_OS_ANDROID diff --git a/Tests/Foundation/TestProcess.swift b/Tests/Foundation/TestProcess.swift index ca6b91cf72..3754cab9f3 100644 --- a/Tests/Foundation/TestProcess.swift +++ b/Tests/Foundation/TestProcess.swift @@ -580,25 +580,23 @@ class TestProcess : XCTestCase { task.executableURL = url task.arguments = [] let stdoutPipe = Pipe() - let dataLock = NSLock() + let stdoutData = Mutex(Data()) task.standardOutput = stdoutPipe - // protected by the task.waitUntilExit() - nonisolated(unsafe) var stdoutData = Data() stdoutPipe.fileHandleForReading.readabilityHandler = { fh in - dataLock.synchronized { - stdoutData.append(fh.availableData) + stdoutData.withLock { + $0.append(fh.availableData) } } try task.run() task.waitUntilExit() stdoutPipe.fileHandleForReading.readabilityHandler = nil - try dataLock.synchronized { + try stdoutData.withLock { if let d = try stdoutPipe.fileHandleForReading.readToEnd() { - stdoutData.append(d) + $0.append(d) } - XCTAssertEqual(String(data: stdoutData, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines), "No files specified.") + XCTAssertEqual(String(data: $0, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines), "No files specified.") } } diff --git a/Tests/Foundation/TestURLSessionFTP.swift b/Tests/Foundation/TestURLSessionFTP.swift index 0504422bca..37f8e2cb9b 100644 --- a/Tests/Foundation/TestURLSessionFTP.swift +++ b/Tests/Foundation/TestURLSessionFTP.swift @@ -9,6 +9,8 @@ #if !os(Windows) +import Synchronization + class TestURLSessionFTP : LoopbackFTPServerTest { let saveString = """ FTP implementation to test FTP @@ -65,11 +67,10 @@ class FTPDataTask : NSObject, @unchecked Sendable { var responseReceivedExpectation: XCTestExpectation? var hasTransferCompleted = false - private var errorLock = NSLock() - private var _error = false + private let _error = Mutex(false) public var error: Bool { - get { errorLock.synchronized { _error } } - set { errorLock.synchronized { _error = newValue } } + get { _error.withLock { $0 } } + set { _error.withLock { $0 = newValue } } } init(with expectation: XCTestExpectation) { diff --git a/Tests/Foundation/Utilities.swift b/Tests/Foundation/Utilities.swift index 3f2249b84f..e0f6e0fb5d 100644 --- a/Tests/Foundation/Utilities.swift +++ b/Tests/Foundation/Utilities.swift @@ -640,35 +640,6 @@ extension String { } } -extension FileHandle: @retroactive TextOutputStream { - public func write(_ string: String) { - write(Data(string.utf8)) - } - - struct EncodedOutputStream: TextOutputStream { - let fileHandle: FileHandle - let encoding: String.Encoding - - init(_ fileHandle: FileHandle, encoding: String.Encoding) { - self.fileHandle = fileHandle - self.encoding = encoding - } - - func write(_ string: String) { - fileHandle.write(string.data(using: encoding)!) - } - } -} - -extension NSLock { - public func synchronized(_ closure: () throws -> T) rethrows -> T { - self.lock() - defer { self.unlock() } - return try closure() - } -} - - // Create a uniquely named temporary directory, pass the URL and path to a closure then remove the directory afterwards. public func withTemporaryDirectory(functionName: String = #function, block: (URL, String) throws -> R) throws -> R { From 12ee5c8701166c7b1781d221ac78a49a00d4c9ee Mon Sep 17 00:00:00 2001 From: Tony Parker Date: Mon, 15 Jul 2024 14:58:33 -0700 Subject: [PATCH 05/15] Remove some warnings for Windows builds --- .../include/ForSwiftFoundationOnly.h | 1 + Sources/Foundation/FileManager+Win32.swift | 6 +- Sources/Foundation/Host.swift | 4 +- Sources/Foundation/Thread.swift | 60 ++++++++++--------- 4 files changed, 38 insertions(+), 33 deletions(-) diff --git a/Sources/CoreFoundation/include/ForSwiftFoundationOnly.h b/Sources/CoreFoundation/include/ForSwiftFoundationOnly.h index 0a92fe22b2..a2ba56cd95 100644 --- a/Sources/CoreFoundation/include/ForSwiftFoundationOnly.h +++ b/Sources/CoreFoundation/include/ForSwiftFoundationOnly.h @@ -35,6 +35,7 @@ #define NOMINMAX #define VC_EXTRALEAN #define WIN32_LEAN_AND_MEAN +#define _CRT_NONSTDC_NO_DEPRECATE #include #elif !TARGET_OS_WASI #include diff --git a/Sources/Foundation/FileManager+Win32.swift b/Sources/Foundation/FileManager+Win32.swift index 2bf47e7370..c4d2ceaff9 100644 --- a/Sources/Foundation/FileManager+Win32.swift +++ b/Sources/Foundation/FileManager+Win32.swift @@ -108,7 +108,7 @@ extension FileManager { var wszVolumeName: [WCHAR] = Array(repeating: 0, count: Int(MAX_PATH)) - var hVolumes: HANDLE = FindFirstVolumeW(&wszVolumeName, DWORD(wszVolumeName.count)) + let hVolumes: HANDLE = FindFirstVolumeW(&wszVolumeName, DWORD(wszVolumeName.count)) guard hVolumes != INVALID_HANDLE_VALUE else { return nil } defer { FindVolumeClose(hVolumes) } @@ -350,7 +350,7 @@ extension FileManager { guard let _lastReturned else { return firstValidItem() } if _lastReturned.hasDirectoryPath && (level == 0 || !_options.contains(.skipsSubdirectoryDescendants)) { - try walk(directory: _lastReturned) { entry, attributes in + walk(directory: _lastReturned) { entry, attributes in if entry == "." || entry == ".." { return } if _options.contains(.skipsHiddenFiles) && attributes & FILE_ATTRIBUTE_HIDDEN == FILE_ATTRIBUTE_HIDDEN { return @@ -386,7 +386,7 @@ extension FileManager.NSPathDirectoryEnumerator { internal func _nextObject() -> Any? { guard let url = innerEnumerator.nextObject() as? URL else { return nil } - let path: String? = try? baseURL.withUnsafeNTPath { pwszBasePath in + let path: String? = baseURL.withUnsafeNTPath { pwszBasePath in let dwBaseAttrs = GetFileAttributesW(pwszBasePath) if dwBaseAttrs == INVALID_FILE_ATTRIBUTES { return nil } diff --git a/Sources/Foundation/Host.swift b/Sources/Foundation/Host.swift index 5652b7d256..5d3d745ed3 100644 --- a/Sources/Foundation/Host.swift +++ b/Sources/Foundation/Host.swift @@ -135,7 +135,7 @@ open class Host: NSObject { var ulResult: ULONG = GetAdaptersAddresses(ULONG(AF_UNSPEC), 0, nil, nil, &ulSize) - var arAdapters: UnsafeMutableRawPointer = + let arAdapters: UnsafeMutableRawPointer = UnsafeMutableRawPointer.allocate(byteCount: Int(ulSize), alignment: 1) defer { arAdapters.deallocate() } @@ -150,7 +150,7 @@ open class Host: NSObject { while pAdapter != nil { // print("Adapter: \(String(cString: pAdapter!.pointee.AdapterName))") - var arAddresses: UnsafeMutablePointer = + let arAddresses: UnsafeMutablePointer = pAdapter!.pointee.FirstUnicastAddress var pAddress: UnsafeMutablePointer? = diff --git a/Sources/Foundation/Thread.swift b/Sources/Foundation/Thread.swift index b72ad92e77..eac41098e0 100644 --- a/Sources/Foundation/Thread.swift +++ b/Sources/Foundation/Thread.swift @@ -128,7 +128,7 @@ open class Thread : NSObject { @available(*, noasync) open class func sleep(until date: Date) { #if os(Windows) - var hTimer: HANDLE = CreateWaitableTimerW(nil, true, nil) + let hTimer: HANDLE = CreateWaitableTimerW(nil, true, nil) if hTimer == HANDLE(bitPattern: 0) { fatalError("unable to create timer: \(GetLastError())") } defer { CloseHandle(hTimer) } @@ -166,7 +166,7 @@ open class Thread : NSObject { @available(*, noasync) open class func sleep(forTimeInterval interval: TimeInterval) { #if os(Windows) - var hTimer: HANDLE = CreateWaitableTimerW(nil, true, nil) + let hTimer: HANDLE = CreateWaitableTimerW(nil, true, nil) // FIXME(compnerd) how to check that hTimer is not NULL? defer { CloseHandle(hTimer) } @@ -410,37 +410,41 @@ open class Thread : NSObject { let hProcess: HANDLE = GetCurrentProcess() SymSetOptions(DWORD(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS)) if !SymInitializeW(hProcess, nil, true) { - return [] + return [] } return backtraceAddresses { (addresses, count) in - var symbols: [String] = [] - - let addresses: UnsafeMutableBufferPointer = - UnsafeMutableBufferPointer(start: addresses, count: count) - withUnsafeTemporaryAllocation(byteCount: MemoryLayout.size + 127, - alignment: 8) { buffer in - let pSymbolInfo: UnsafeMutablePointer = + var symbols: [String] = [] + + let addresses: UnsafeMutableBufferPointer = + UnsafeMutableBufferPointer(start: addresses, count: count) + withUnsafeTemporaryAllocation(byteCount: MemoryLayout.size + 127, + alignment: 8) { buffer in + let pSymbolInfo: UnsafeMutablePointer = buffer.baseAddress!.assumingMemoryBound(to: SYMBOL_INFO.self) - - for address in addresses { - pSymbolInfo.pointee.SizeOfStruct = + + for address in addresses { + pSymbolInfo.pointee.SizeOfStruct = ULONG(MemoryLayout.size) - pSymbolInfo.pointee.MaxNameLen = 128 - - var dwDisplacement: DWORD64 = 0 - if SymFromAddr(hProcess, DWORD64(UInt(bitPattern: address)), - &dwDisplacement, &pSymbolInfo.pointee) { - symbols.append(String(unsafeUninitializedCapacity: Int(pSymbolInfo.pointee.NameLen) + 1) { - strncpy($0.baseAddress, &pSymbolInfo.pointee.Name, $0.count) - return $0.count - }) - } else { - symbols.append("\(address)") - } + pSymbolInfo.pointee.MaxNameLen = 128 + + var dwDisplacement: DWORD64 = 0 + if SymFromAddr(hProcess, DWORD64(UInt(bitPattern: address)), + &dwDisplacement, &pSymbolInfo.pointee) { + symbols.append(String(unsafeUninitializedCapacity: Int(pSymbolInfo.pointee.NameLen) + 1) { + strncpy_s($0.baseAddress, $0.count, &pSymbolInfo.pointee.Name, $0.count) + return $0.count + }) + } else { + if let address { + symbols.append("\(address)") + } else { + symbols.append("") + } + } + } } - } - - return symbols + + return symbols } #else return backtraceAddresses({ (addrs, count) in From 55a1e2694dd33a47e5416e754aa6ce3854f14c25 Mon Sep 17 00:00:00 2001 From: Tony Parker Date: Tue, 16 Jul 2024 07:59:26 -0700 Subject: [PATCH 06/15] Add version 6 and warning flags to CMake file --- CMakeLists.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0a671aa2d2..011b2f108e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -112,6 +112,7 @@ list(APPEND _Foundation_common_build_flags "-Wno-unused-function" "-Wno-microsoft-enum-forward-reference" "-Wno-int-conversion" + "-Wno-switch" "-fblocks") if(NOT "${CMAKE_C_SIMULATE_ID}" STREQUAL "MSVC") @@ -139,8 +140,11 @@ endif() # Swift build flags (Foundation, FoundationNetworking, FoundationXML) set(_Foundation_swift_build_flags) list(APPEND _Foundation_swift_build_flags + "-swift-version 6" "-DDEPLOYMENT_RUNTIME_SWIFT" - "-DSWIFT_CORELIBS_FOUNDATION_HAS_THREADS") + "-DSWIFT_CORELIBS_FOUNDATION_HAS_THREADS" + "-Xfrontend" + "-require-explicit-sendable") if(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR CMAKE_SYSTEM_NAME STREQUAL "Android") list(APPEND _Foundation_common_build_flags From fe1d193c98bce8ba0d9e8d9055f97c3e422ad296 Mon Sep 17 00:00:00 2001 From: Tony Parker Date: Tue, 16 Jul 2024 12:00:01 -0700 Subject: [PATCH 07/15] Adapt to some changes in how unchecked Sendable conformance is declared --- Sources/Foundation/AffineTransform.swift | 2 +- Sources/Foundation/ByteCountFormatter.swift | 2 +- Sources/Foundation/CharacterSet.swift | 4 +-- .../DispatchData+DataProtocol.swift | 2 +- Sources/Foundation/EnergyFormatter.swift | 2 +- Sources/Foundation/FileManager.swift | 6 ---- Sources/Foundation/Formatter.swift | 4 +-- Sources/Foundation/Host.swift | 2 +- Sources/Foundation/ISO8601DateFormatter.swift | 2 +- Sources/Foundation/JSONSerialization.swift | 2 +- Sources/Foundation/LengthFormatter.swift | 33 +++++++++++-------- Sources/Foundation/MassFormatter.swift | 27 ++++++++------- Sources/Foundation/MeasurementFormatter.swift | 2 +- Sources/Foundation/NSArray.swift | 5 +-- Sources/Foundation/NSAttributedString.swift | 5 +-- Sources/Foundation/NSCFString.swift | 6 ---- Sources/Foundation/NSCache.swift | 2 +- Sources/Foundation/NSCalendar.swift | 2 +- Sources/Foundation/NSCharacterSet.swift | 5 +-- Sources/Foundation/NSCoder.swift | 4 +-- .../Foundation/NSComparisonPredicate.swift | 3 -- Sources/Foundation/NSCompoundPredicate.swift | 3 -- Sources/Foundation/NSData.swift | 5 +-- Sources/Foundation/NSDateComponents.swift | 2 +- Sources/Foundation/NSDictionary.swift | 5 +-- Sources/Foundation/NSEnumerator.swift | 4 +-- Sources/Foundation/NSExpression.swift | 2 +- Sources/Foundation/NSIndexPath.swift | 2 +- Sources/Foundation/NSIndexSet.swift | 5 +-- Sources/Foundation/NSKeyedArchiver.swift | 2 +- Sources/Foundation/NSKeyedUnarchiver.swift | 2 +- Sources/Foundation/NSMeasurement.swift | 2 +- Sources/Foundation/NSNotification.swift | 2 +- Sources/Foundation/NSObject.swift | 9 ++--- Sources/Foundation/NSOrderedSet.swift | 5 +-- .../Foundation/NSPersonNameComponents.swift | 2 +- Sources/Foundation/NSPredicate.swift | 2 +- Sources/Foundation/NSSet.swift | 8 +---- Sources/Foundation/NSSortDescriptor.swift | 2 +- Sources/Foundation/NSString.swift | 5 +-- Sources/Foundation/NSSwiftRuntime.swift | 2 +- Sources/Foundation/NSTextCheckingResult.swift | 4 +-- Sources/Foundation/NSTimeZone.swift | 2 +- Sources/Foundation/NSURLComponents.swift | 2 +- Sources/Foundation/Notification.swift | 2 +- Sources/Foundation/NotificationQueue.swift | 2 +- Sources/Foundation/Port.swift | 12 +++---- Sources/Foundation/PortMessage.swift | 2 +- Sources/Foundation/Progress.swift | 2 +- .../PropertyListSerialization.swift | 2 +- Sources/Foundation/RunLoop.swift | 4 +-- Sources/Foundation/Scanner.swift | 2 +- Sources/Foundation/Stream.swift | 8 ++--- Sources/Foundation/Thread.swift | 2 +- Sources/Foundation/Timer.swift | 2 +- Sources/Foundation/Unit.swift | 5 +-- Sources/Foundation/UserDefaults.swift | 2 +- .../FoundationNetworking/NSURLRequest.swift | 5 +-- .../FoundationNetworking/URLProtocol.swift | 2 +- Sources/FoundationXML/XMLDTD.swift | 3 -- Sources/FoundationXML/XMLDTDNode.swift | 3 -- Sources/FoundationXML/XMLDocument.swift | 3 -- Sources/FoundationXML/XMLElement.swift | 3 -- Sources/FoundationXML/XMLNode.swift | 2 +- Sources/FoundationXML/XMLParser.swift | 2 +- 65 files changed, 102 insertions(+), 169 deletions(-) diff --git a/Sources/Foundation/AffineTransform.swift b/Sources/Foundation/AffineTransform.swift index baf1768267..425c9dacbb 100644 --- a/Sources/Foundation/AffineTransform.swift +++ b/Sources/Foundation/AffineTransform.swift @@ -413,7 +413,7 @@ public struct NSAffineTransformStruct : Sendable { } @available(*, unavailable) -extension NSAffineTransform : Sendable { } +extension NSAffineTransform : @unchecked Sendable { } open class NSAffineTransform: NSObject { // Internal only for testing. diff --git a/Sources/Foundation/ByteCountFormatter.swift b/Sources/Foundation/ByteCountFormatter.swift index 75ecc4e908..1fe8683c4e 100644 --- a/Sources/Foundation/ByteCountFormatter.swift +++ b/Sources/Foundation/ByteCountFormatter.swift @@ -40,7 +40,7 @@ extension ByteCountFormatter { } @available(*, unavailable) -extension ByteCountFormatter : Sendable { } +extension ByteCountFormatter : @unchecked Sendable { } open class ByteCountFormatter : Formatter { public override init() { diff --git a/Sources/Foundation/CharacterSet.swift b/Sources/Foundation/CharacterSet.swift index 83b2b2d03e..0faeceef29 100644 --- a/Sources/Foundation/CharacterSet.swift +++ b/Sources/Foundation/CharacterSet.swift @@ -18,7 +18,7 @@ private func _utfRangeToNSRange(_ inRange : ClosedRange) -> NSRan return NSRange(location: Int(inRange.lowerBound.value), length: Int(inRange.upperBound.value - inRange.lowerBound.value + 1)) } -internal final class _SwiftNSCharacterSet : NSCharacterSet, _SwiftNativeFoundationType, @unchecked Sendable { +internal final class _SwiftNSCharacterSet : NSCharacterSet, _SwiftNativeFoundationType { internal typealias ImmutableType = NSCharacterSet internal typealias MutableType = NSMutableCharacterSet @@ -114,7 +114,7 @@ public struct CharacterSet : ReferenceConvertible, Equatable, Hashable, SetAlgeb internal typealias ImmutableType = SwiftNSWrapping.ImmutableType internal typealias MutableType = SwiftNSWrapping.MutableType - internal var _wrapped : _SwiftNSCharacterSet + internal nonisolated(unsafe) var _wrapped : _SwiftNSCharacterSet // MARK: Init methods diff --git a/Sources/Foundation/DispatchData+DataProtocol.swift b/Sources/Foundation/DispatchData+DataProtocol.swift index 001b202baa..42237c8065 100644 --- a/Sources/Foundation/DispatchData+DataProtocol.swift +++ b/Sources/Foundation/DispatchData+DataProtocol.swift @@ -14,7 +14,7 @@ import Dispatch @available(*, unavailable) -extension DispatchData.Region : Sendable { } +extension DispatchData.Region : @unchecked Sendable { } extension DispatchData : DataProtocol { public typealias Regions = [Region] diff --git a/Sources/Foundation/EnergyFormatter.swift b/Sources/Foundation/EnergyFormatter.swift index 64b09e30cb..4a0aa1d5d5 100644 --- a/Sources/Foundation/EnergyFormatter.swift +++ b/Sources/Foundation/EnergyFormatter.swift @@ -61,7 +61,7 @@ extension EnergyFormatter { } @available(*, unavailable) -extension EnergyFormatter : Sendable { } +extension EnergyFormatter : @unchecked Sendable { } open class EnergyFormatter: Formatter { diff --git a/Sources/Foundation/FileManager.swift b/Sources/Foundation/FileManager.swift index 999d6bc756..8c3e23c537 100644 --- a/Sources/Foundation/FileManager.swift +++ b/Sources/Foundation/FileManager.swift @@ -608,12 +608,6 @@ extension FileAttributeKey { internal static let _accessDate = FileAttributeKey(rawValue: "org.swift.Foundation.FileAttributeKey._accessDate") } -@available(*, unavailable) -extension FileManager.DirectoryEnumerator : Sendable { } - -@available(*, unavailable) -extension FileManager.NSPathDirectoryEnumerator : Sendable { } - extension FileManager { open class DirectoryEnumerator : NSEnumerator { diff --git a/Sources/Foundation/Formatter.swift b/Sources/Foundation/Formatter.swift index 2387514cdf..f798b0d741 100644 --- a/Sources/Foundation/Formatter.swift +++ b/Sources/Foundation/Formatter.swift @@ -43,9 +43,7 @@ extension Formatter { } } -@available(*, unavailable) -extension Formatter : Sendable { } - +//@_nonSendable open class Formatter : NSObject, NSCopying, NSCoding { public override init() { diff --git a/Sources/Foundation/Host.swift b/Sources/Foundation/Host.swift index 5d3d745ed3..6c4f5291f6 100644 --- a/Sources/Foundation/Host.swift +++ b/Sources/Foundation/Host.swift @@ -56,7 +56,7 @@ import WinSDK #endif @available(*, unavailable) -extension Host : Sendable { } +extension Host : @unchecked Sendable { } open class Host: NSObject { enum ResolveType { diff --git a/Sources/Foundation/ISO8601DateFormatter.swift b/Sources/Foundation/ISO8601DateFormatter.swift index c8c7bdaf52..6afe9da8ca 100644 --- a/Sources/Foundation/ISO8601DateFormatter.swift +++ b/Sources/Foundation/ISO8601DateFormatter.swift @@ -49,7 +49,7 @@ extension ISO8601DateFormatter { } @available(*, unavailable) -extension ISO8601DateFormatter : Sendable { } +extension ISO8601DateFormatter : @unchecked Sendable { } open class ISO8601DateFormatter : Formatter, NSSecureCoding { diff --git a/Sources/Foundation/JSONSerialization.swift b/Sources/Foundation/JSONSerialization.swift index 34c24d2490..dd9e73d1bb 100644 --- a/Sources/Foundation/JSONSerialization.swift +++ b/Sources/Foundation/JSONSerialization.swift @@ -54,7 +54,7 @@ extension JSONSerialization { */ @available(*, unavailable) -extension JSONSerialization : Sendable { } +extension JSONSerialization : @unchecked Sendable { } open class JSONSerialization : NSObject { diff --git a/Sources/Foundation/LengthFormatter.swift b/Sources/Foundation/LengthFormatter.swift index 490bd60953..bab7435a8e 100644 --- a/Sources/Foundation/LengthFormatter.swift +++ b/Sources/Foundation/LengthFormatter.swift @@ -21,7 +21,7 @@ extension LengthFormatter { } @available(*, unavailable) -extension LengthFormatter : Sendable { } +extension LengthFormatter : @unchecked Sendable { } open class LengthFormatter : Formatter { @@ -57,11 +57,20 @@ open class LengthFormatter : Formatter { // Format a number in meters to a localized string with the locale-appropriate unit and an appropriate scale (e.g. 4.3m = 14.1ft in the US locale). open func string(fromMeters numberInMeters: Double) -> String { + let unitLength: [Unit:UnitLength] = [.millimeter:.millimeters, + .centimeter:.centimeters, + .meter:.meters, + .kilometer:.kilometers, + .inch:.inches, + .foot:.feet, + .yard:.yards, + .mile:.miles] + //Convert to the locale-appropriate unit let unitFromMeters = unit(fromMeters: numberInMeters) //Map the unit to UnitLength type for conversion later - let unitLengthFromMeters = LengthFormatter.unitLength[unitFromMeters]! + let unitLengthFromMeters = unitLength[unitFromMeters]! //Create a measurement object based on the value in meters let meterMeasurement = Measurement(value:numberInMeters, unit: .meters) @@ -99,13 +108,21 @@ open class LengthFormatter : Formatter { // Return the locale-appropriate unit, the same unit used by -stringFromMeters:. open func unitString(fromMeters numberInMeters: Double, usedUnit unitp: UnsafeMutablePointer?) -> String { + let unitLength: [Unit:UnitLength] = [.millimeter:.millimeters, + .centimeter:.centimeters, + .meter:.meters, + .kilometer:.kilometers, + .inch:.inches, + .foot:.feet, + .yard:.yards, + .mile:.miles] //Convert to the locale-appropriate unit let unitFromMeters = unit(fromMeters: numberInMeters) unitp?.pointee = unitFromMeters //Map the unit to UnitLength type for conversion later - let unitLengthFromMeters = LengthFormatter.unitLength[unitFromMeters]! + let unitLengthFromMeters = unitLength[unitFromMeters]! //Create a measurement object based on the value in meters let meterMeasurement = Measurement(value:numberInMeters, unit: .meters) @@ -155,16 +172,6 @@ open class LengthFormatter : Formatter { } } - /// Maps LengthFormatter.Unit enum to UnitLength class. Used for measurement conversion. - private static let unitLength: [Unit:UnitLength] = [.millimeter:.millimeters, - .centimeter:.centimeters, - .meter:.meters, - .kilometer:.kilometers, - .inch:.inches, - .foot:.feet, - .yard:.yards, - .mile:.miles] - /// Maps a unit to its short symbol. Reuses strings from UnitLength wherever possible. private static let shortSymbol: [Unit: String] = [.millimeter:UnitLength.millimeters.symbol, .centimeter:UnitLength.centimeters.symbol, diff --git a/Sources/Foundation/MassFormatter.swift b/Sources/Foundation/MassFormatter.swift index 7cd1753b9a..3a3dd4ebcb 100644 --- a/Sources/Foundation/MassFormatter.swift +++ b/Sources/Foundation/MassFormatter.swift @@ -19,7 +19,7 @@ extension MassFormatter { } @available(*, unavailable) -extension MassFormatter : Sendable { } +extension MassFormatter : @unchecked Sendable { } open class MassFormatter : Formatter { @@ -69,11 +69,17 @@ open class MassFormatter : Formatter { // Format a number in kilograms to a localized string with the locale-appropriate unit and an appropriate scale (e.g. 1.2kg = 2.64lb in the US locale). open func string(fromKilograms numberInKilograms: Double) -> String { + let unitMass: [Unit: UnitMass] = [.gram: .grams, + .kilogram: .kilograms, + .ounce: .ounces, + .pound: .pounds, + .stone: .stones] + //Convert to the locale-appropriate unit let unitFromKilograms = convertedUnit(fromKilograms: numberInKilograms) //Map the unit to UnitMass type for conversion later - let unitMassFromKilograms = MassFormatter.unitMass[unitFromKilograms]! + let unitMassFromKilograms = unitMass[unitFromKilograms]! //Create a measurement object based on the value in kilograms let kilogramMeasurement = Measurement(value:numberInKilograms, unit: .kilograms) @@ -104,12 +110,18 @@ open class MassFormatter : Formatter { // Return the locale-appropriate unit, the same unit used by -stringFromKilograms:. open func unitString(fromKilograms numberInKilograms: Double, usedUnit unitp: UnsafeMutablePointer?) -> String { + let unitMass: [Unit: UnitMass] = [.gram: .grams, + .kilogram: .kilograms, + .ounce: .ounces, + .pound: .pounds, + .stone: .stones] + //Convert to the locale-appropriate unit let unitFromKilograms = convertedUnit(fromKilograms: numberInKilograms) unitp?.pointee = unitFromKilograms //Map the unit to UnitMass type for conversion later - let unitMassFromKilograms = MassFormatter.unitMass[unitFromKilograms]! + let unitMassFromKilograms = unitMass[unitFromKilograms]! //Create a measurement object based on the value in kilograms let kilogramMeasurement = Measurement(value:numberInKilograms, unit: .kilograms) @@ -198,14 +210,7 @@ open class MassFormatter : Formatter { /// The number of pounds in 1 stone private static let poundsPerStone = 14.0 - - /// Maps MassFormatter.Unit enum to UnitMass class. Used for measurement conversion. - private static let unitMass: [Unit: UnitMass] = [.gram: .grams, - .kilogram: .kilograms, - .ounce: .ounces, - .pound: .pounds, - .stone: .stones] - + /// Maps a unit to its short symbol. Reuses strings from UnitMass. private static let shortSymbol: [Unit: String] = [.gram: UnitMass.grams.symbol, .kilogram: UnitMass.kilograms.symbol, diff --git a/Sources/Foundation/MeasurementFormatter.swift b/Sources/Foundation/MeasurementFormatter.swift index ae177bcfc9..6c55914de8 100644 --- a/Sources/Foundation/MeasurementFormatter.swift +++ b/Sources/Foundation/MeasurementFormatter.swift @@ -8,7 +8,7 @@ // @available(*, unavailable) -extension MeasurementFormatter : Sendable { } +extension MeasurementFormatter : @unchecked Sendable { } @available(*, unavailable, message: "Not supported in swift-corelibs-foundation") open class MeasurementFormatter : Formatter, NSSecureCoding { diff --git a/Sources/Foundation/NSArray.swift b/Sources/Foundation/NSArray.swift index 2f2089a8b5..5d4699e3c2 100644 --- a/Sources/Foundation/NSArray.swift +++ b/Sources/Foundation/NSArray.swift @@ -10,7 +10,7 @@ @_implementationOnly import CoreFoundation @available(*, unavailable) -extension NSArray : Sendable { } +extension NSArray : @unchecked Sendable { } @available(*, unavailable) extension NSArray.Iterator : Sendable { } @@ -752,9 +752,6 @@ public struct NSBinarySearchingOptions : OptionSet, Sendable { public static let insertionIndex = NSBinarySearchingOptions(rawValue: 1 << 10) } -@available(*, unavailable) -extension NSMutableArray : Sendable { } - open class NSMutableArray : NSArray { open func add(_ anObject: Any) { diff --git a/Sources/Foundation/NSAttributedString.swift b/Sources/Foundation/NSAttributedString.swift index 9202708152..c83818d720 100644 --- a/Sources/Foundation/NSAttributedString.swift +++ b/Sources/Foundation/NSAttributedString.swift @@ -47,7 +47,7 @@ extension NSAttributedString.Key: _ObjectiveCBridgeable { public typealias NSAttributedStringKey = NSAttributedString.Key @available(*, unavailable) -extension NSAttributedString : Sendable { } +extension NSAttributedString : @unchecked Sendable { } open class NSAttributedString: NSObject, NSCopying, NSMutableCopying, NSSecureCoding { @@ -435,9 +435,6 @@ extension NSAttributedString { } -@available(*, unavailable) -extension NSMutableAttributedString : Sendable { } - open class NSMutableAttributedString : NSAttributedString { open func replaceCharacters(in range: NSRange, with str: String) { diff --git a/Sources/Foundation/NSCFString.swift b/Sources/Foundation/NSCFString.swift index 69468b3145..40ee579f42 100644 --- a/Sources/Foundation/NSCFString.swift +++ b/Sources/Foundation/NSCFString.swift @@ -10,9 +10,6 @@ @_implementationOnly import CoreFoundation -@available(*, unavailable) -extension _NSCFString : Sendable { } - @usableFromInline internal class _NSCFString : NSMutableString { required init(characters: UnsafePointer, length: Int) { @@ -61,9 +58,6 @@ internal class _NSCFString : NSMutableString { } } -@available(*, unavailable) -extension _NSCFConstantString : Sendable { } - @usableFromInline internal final class _NSCFConstantString : _NSCFString { internal var _ptr : UnsafePointer { diff --git a/Sources/Foundation/NSCache.swift b/Sources/Foundation/NSCache.swift index fda8d9ecd7..844d0a9c30 100644 --- a/Sources/Foundation/NSCache.swift +++ b/Sources/Foundation/NSCache.swift @@ -54,7 +54,7 @@ fileprivate class NSCacheKey: NSObject { } @available(*, unavailable) -extension NSCache : Sendable { } +extension NSCache : @unchecked Sendable { } open class NSCache : NSObject { diff --git a/Sources/Foundation/NSCalendar.swift b/Sources/Foundation/NSCalendar.swift index 80835bfd64..20774d22d9 100644 --- a/Sources/Foundation/NSCalendar.swift +++ b/Sources/Foundation/NSCalendar.swift @@ -220,7 +220,7 @@ extension NSCalendar.Identifier { } @available(*, unavailable) -extension NSCalendar : Sendable { } +extension NSCalendar : @unchecked Sendable { } open class NSCalendar : NSObject, NSCopying, NSSecureCoding { var _calendar: Calendar diff --git a/Sources/Foundation/NSCharacterSet.swift b/Sources/Foundation/NSCharacterSet.swift index c36ea39eaa..6e153906ba 100644 --- a/Sources/Foundation/NSCharacterSet.swift +++ b/Sources/Foundation/NSCharacterSet.swift @@ -61,7 +61,7 @@ fileprivate extension String { } @available(*, unavailable) -extension NSCharacterSet : Sendable { } +extension NSCharacterSet : @unchecked Sendable { } open class NSCharacterSet : NSObject, NSCopying, NSMutableCopying, NSSecureCoding { typealias CFType = CFCharacterSet @@ -394,9 +394,6 @@ open class NSCharacterSet : NSObject, NSCopying, NSMutableCopying, NSSecureCodin } } -@available(*, unavailable) -extension NSMutableCharacterSet : Sendable { } - open class NSMutableCharacterSet : NSCharacterSet { open func addCharacters(in aRange: NSRange) { diff --git a/Sources/Foundation/NSCoder.swift b/Sources/Foundation/NSCoder.swift index 24c27334fc..283b805d74 100644 --- a/Sources/Foundation/NSCoder.swift +++ b/Sources/Foundation/NSCoder.swift @@ -80,9 +80,6 @@ public protocol NSSecureCoding : NSCoding { static var supportsSecureCoding: Bool { get } } -@available(*, unavailable) -extension NSCoder : Sendable { } - /// The `NSCoder` abstract class declares the interface used by concrete /// subclasses to transfer objects and other values between memory and some /// other format. This capability provides the basis for archiving (where @@ -103,6 +100,7 @@ extension NSCoder : Sendable { } /// is normally of the same class as the object that was originally encoded into /// the stream. An object can change its class when encoded, however; this is /// described in Archives and Serializations Programming Guide. +//@_nonSendable open class NSCoder : NSObject { internal var _pendingBuffers = Array<(UnsafeMutableRawPointer, Int)>() diff --git a/Sources/Foundation/NSComparisonPredicate.swift b/Sources/Foundation/NSComparisonPredicate.swift index a0d1063a55..c3a35da465 100644 --- a/Sources/Foundation/NSComparisonPredicate.swift +++ b/Sources/Foundation/NSComparisonPredicate.swift @@ -7,9 +7,6 @@ // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // -@available(*, unavailable) -extension NSComparisonPredicate : Sendable { } - // Comparison predicates are predicates which do some form of comparison between the results of two expressions and return a BOOL. They take an operator, a left expression, and a right expression, and return the result of invoking the operator with the results of evaluating the expressions. @available(*, deprecated, message: "NSExpression and classes that rely on its functionality are unsupported in swift-corelibs-foundation: NSComparisonPredicate is unavailable.") open class NSComparisonPredicate : NSPredicate { diff --git a/Sources/Foundation/NSCompoundPredicate.swift b/Sources/Foundation/NSCompoundPredicate.swift index 0e30e5fe0b..15cd108f46 100644 --- a/Sources/Foundation/NSCompoundPredicate.swift +++ b/Sources/Foundation/NSCompoundPredicate.swift @@ -18,9 +18,6 @@ extension NSCompoundPredicate { } } -@available(*, unavailable) -extension NSCompoundPredicate : Sendable { } - open class NSCompoundPredicate : NSPredicate { public init(type: LogicalType, subpredicates: [NSPredicate]) { if type == .not && subpredicates.isEmpty { diff --git a/Sources/Foundation/NSData.swift b/Sources/Foundation/NSData.swift index f5c25697fc..ae54f971ec 100644 --- a/Sources/Foundation/NSData.swift +++ b/Sources/Foundation/NSData.swift @@ -32,7 +32,7 @@ private let __kCFUseAllocator: CFOptionFlags = 3 private let __kCFDontDeallocate: CFOptionFlags = 4 @available(*, unavailable) -extension NSData : Sendable { } +extension NSData : @unchecked Sendable { } @available(*, unavailable) extension NSData.NSDataReadResult : Sendable { } @@ -935,9 +935,6 @@ extension CFData : _NSBridgeable, _SwiftBridgeable { // MARK: - -@available(*, unavailable) -extension NSMutableData : Sendable { } - open class NSMutableData : NSData { internal final var _cfMutableObject: CFMutableData { return unsafeBitCast(self, to: CFMutableData.self) } diff --git a/Sources/Foundation/NSDateComponents.swift b/Sources/Foundation/NSDateComponents.swift index 6ba39e552a..93462d7fca 100644 --- a/Sources/Foundation/NSDateComponents.swift +++ b/Sources/Foundation/NSDateComponents.swift @@ -32,7 +32,7 @@ public let NSDateComponentUndefined: Int = Int.max @available(*, unavailable) -extension NSDateComponents : Sendable { } +extension NSDateComponents : @unchecked Sendable { } open class NSDateComponents: NSObject, NSCopying, NSSecureCoding { internal var _components: DateComponents diff --git a/Sources/Foundation/NSDictionary.swift b/Sources/Foundation/NSDictionary.swift index 854368d5ef..5cf7a45fef 100644 --- a/Sources/Foundation/NSDictionary.swift +++ b/Sources/Foundation/NSDictionary.swift @@ -36,7 +36,7 @@ fileprivate func getDescription(of object: Any) -> String? { } @available(*, unavailable) -extension NSDictionary : Sendable { } +extension NSDictionary : @unchecked Sendable { } @available(*, unavailable) extension NSDictionary.Iterator : Sendable { } @@ -607,9 +607,6 @@ extension Dictionary : _NSBridgeable { internal var _cfObject: CFDictionary { return _nsObject._cfObject } } -@available(*, unavailable) -extension NSMutableDictionary : Sendable { } - open class NSMutableDictionary : NSDictionary { open func removeObject(forKey aKey: Any) { diff --git a/Sources/Foundation/NSEnumerator.swift b/Sources/Foundation/NSEnumerator.swift index 751938e568..cf3683b28a 100644 --- a/Sources/Foundation/NSEnumerator.swift +++ b/Sources/Foundation/NSEnumerator.swift @@ -7,12 +7,10 @@ // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // -@available(*, unavailable) -extension NSEnumerator : Sendable { } - @available(*, unavailable) extension NSEnumerator.Iterator : Sendable { } +//@_nonSendable open class NSEnumerator : NSObject { open func nextObject() -> Any? { diff --git a/Sources/Foundation/NSExpression.swift b/Sources/Foundation/NSExpression.swift index 159c232051..3a44cf685c 100644 --- a/Sources/Foundation/NSExpression.swift +++ b/Sources/Foundation/NSExpression.swift @@ -30,7 +30,7 @@ extension NSExpression { } @available(*, unavailable) -extension NSExpression : Sendable { } +extension NSExpression : @unchecked Sendable { } @available(*, deprecated, message: "NSExpression is not available in swift-corelibs-foundation") open class NSExpression : NSObject, NSCopying { diff --git a/Sources/Foundation/NSIndexPath.swift b/Sources/Foundation/NSIndexPath.swift index 0cb838c91a..0f1f81d400 100644 --- a/Sources/Foundation/NSIndexPath.swift +++ b/Sources/Foundation/NSIndexPath.swift @@ -8,7 +8,7 @@ // @available(*, unavailable) -extension NSIndexPath : Sendable { } +extension NSIndexPath : @unchecked Sendable { } open class NSIndexPath : NSObject, NSCopying, NSSecureCoding { diff --git a/Sources/Foundation/NSIndexSet.swift b/Sources/Foundation/NSIndexSet.swift index 7c8ecd6c32..80458c9361 100644 --- a/Sources/Foundation/NSIndexSet.swift +++ b/Sources/Foundation/NSIndexSet.swift @@ -60,7 +60,7 @@ internal func __NSIndexSetIndexOfRangeContainingIndex(_ indexSet: NSIndexSet, _ } @available(*, unavailable) -extension NSIndexSet : Sendable { } +extension NSIndexSet : @unchecked Sendable { } open class NSIndexSet : NSObject, NSCopying, NSMutableCopying, NSSecureCoding { // all instance variables are private @@ -605,9 +605,6 @@ extension NSIndexSet : Sequence { } } -@available(*, unavailable) -extension NSMutableIndexSet : Sendable { } - open class NSMutableIndexSet : NSIndexSet { open func add(_ indexSet: IndexSet) { diff --git a/Sources/Foundation/NSKeyedArchiver.swift b/Sources/Foundation/NSKeyedArchiver.swift index f3d4054b29..9bd8ce9174 100644 --- a/Sources/Foundation/NSKeyedArchiver.swift +++ b/Sources/Foundation/NSKeyedArchiver.swift @@ -38,7 +38,7 @@ internal let NSPropertyListClasses : [AnyClass] = [ ] @available(*, unavailable) -extension NSKeyedArchiver : Sendable { } +extension NSKeyedArchiver : @unchecked Sendable { } /// `NSKeyedArchiver`, a concrete subclass of `NSCoder`, provides a way to encode objects /// (and scalar values) into an architecture-independent format that can be stored in a file. diff --git a/Sources/Foundation/NSKeyedUnarchiver.swift b/Sources/Foundation/NSKeyedUnarchiver.swift index f6eff38d08..7f6509b8fc 100644 --- a/Sources/Foundation/NSKeyedUnarchiver.swift +++ b/Sources/Foundation/NSKeyedUnarchiver.swift @@ -11,7 +11,7 @@ internal import Synchronization @available(*, unavailable) -extension NSKeyedUnarchiver : Sendable { } +extension NSKeyedUnarchiver : @unchecked Sendable { } open class NSKeyedUnarchiver : NSCoder { enum InternalError: Error { diff --git a/Sources/Foundation/NSMeasurement.swift b/Sources/Foundation/NSMeasurement.swift index 3b341822aa..874b12a115 100644 --- a/Sources/Foundation/NSMeasurement.swift +++ b/Sources/Foundation/NSMeasurement.swift @@ -11,7 +11,7 @@ //===----------------------------------------------------------------------===// @available(*, unavailable) -extension NSMeasurement : Sendable { } +extension NSMeasurement : @unchecked Sendable { } open class NSMeasurement : NSObject, NSCopying, NSSecureCoding { open private(set) var unit: Unit diff --git a/Sources/Foundation/NSNotification.swift b/Sources/Foundation/NSNotification.swift index f1392513f3..7d08ba995f 100644 --- a/Sources/Foundation/NSNotification.swift +++ b/Sources/Foundation/NSNotification.swift @@ -8,7 +8,7 @@ // @available(*, unavailable) -extension NSNotification : Sendable { } +extension NSNotification : @unchecked Sendable { } open class NSNotification: NSObject, NSCopying, NSCoding { public struct Name : RawRepresentable, Equatable, Hashable, Sendable { diff --git a/Sources/Foundation/NSObject.swift b/Sources/Foundation/NSObject.swift index 089849964a..9c37ffcc81 100644 --- a/Sources/Foundation/NSObject.swift +++ b/Sources/Foundation/NSObject.swift @@ -85,7 +85,7 @@ extension NSObjectProtocol { } @available(*, unavailable) -extension NSZone : Sendable { } +extension NSZone : @unchecked Sendable { } public struct NSZone : ExpressibleByNilLiteral { @@ -152,8 +152,9 @@ extension NSMutableCopying { } /// The root class of most Foundation class hierarchies. +//@_nonSendable open class NSObject : NSObjectProtocol, Equatable, Hashable { - // Important: add no ivars here. It will subvert the careful layout of subclasses that bridge into CF. + // Important: add no ivars here. It will subvert the careful layout of subclasses that bridge into CF. /// Implemented by subclasses to initialize a new object immediately after memory /// for it has been allocated. @@ -410,7 +411,3 @@ extension NSObject : CustomDebugStringConvertible { extension NSObject : CustomStringConvertible { } - -@available(*, unavailable) -extension NSObject : Sendable { } - diff --git a/Sources/Foundation/NSOrderedSet.swift b/Sources/Foundation/NSOrderedSet.swift index f91d3bcd61..db76a98654 100644 --- a/Sources/Foundation/NSOrderedSet.swift +++ b/Sources/Foundation/NSOrderedSet.swift @@ -10,7 +10,7 @@ /**************** Immutable Ordered Set ****************/ @available(*, unavailable) -extension NSOrderedSet : Sendable { } +extension NSOrderedSet : @unchecked Sendable { } open class NSOrderedSet: NSObject, NSCopying, NSMutableCopying, NSSecureCoding, ExpressibleByArrayLiteral { @@ -394,9 +394,6 @@ open class NSOrderedSet: NSObject, NSCopying, NSMutableCopying, NSSecureCoding, /**************** Mutable Ordered Set ****************/ -@available(*, unavailable) -extension NSMutableOrderedSet : Sendable { } - open class NSMutableOrderedSet: NSOrderedSet { fileprivate var _mutableStorage: NSMutableSet diff --git a/Sources/Foundation/NSPersonNameComponents.swift b/Sources/Foundation/NSPersonNameComponents.swift index 6ac9cf4e99..04fdfa17d3 100644 --- a/Sources/Foundation/NSPersonNameComponents.swift +++ b/Sources/Foundation/NSPersonNameComponents.swift @@ -8,7 +8,7 @@ // @available(*, unavailable) -extension NSPersonNameComponents : Sendable { } +extension NSPersonNameComponents : @unchecked Sendable { } open class NSPersonNameComponents : NSObject, NSCopying, NSSecureCoding { diff --git a/Sources/Foundation/NSPredicate.swift b/Sources/Foundation/NSPredicate.swift index 12151c2c28..1ff1a073aa 100644 --- a/Sources/Foundation/NSPredicate.swift +++ b/Sources/Foundation/NSPredicate.swift @@ -8,7 +8,7 @@ // @available(*, unavailable) -extension NSPredicate : Sendable { } +extension NSPredicate : @unchecked Sendable { } // Predicates wrap some combination of expressions and operators and when evaluated return a BOOL. diff --git a/Sources/Foundation/NSSet.swift b/Sources/Foundation/NSSet.swift index 5962394811..273d79c43e 100644 --- a/Sources/Foundation/NSSet.swift +++ b/Sources/Foundation/NSSet.swift @@ -11,7 +11,7 @@ @_implementationOnly import CoreFoundation @available(*, unavailable) -extension NSSet : Sendable { } +extension NSSet : @unchecked Sendable { } open class NSSet : NSObject, NSCopying, NSMutableCopying, NSSecureCoding, NSCoding { private let _cfinfo = _CFInfo(typeID: CFSetGetTypeID()) @@ -369,9 +369,6 @@ extension NSSet: CustomReflectable { } } -@available(*, unavailable) -extension NSMutableSet : Sendable { } - open class NSMutableSet : NSSet { open func add(_ object: Any) { @@ -464,9 +461,6 @@ open class NSMutableSet : NSSet { /**************** Counted Set ****************/ -@available(*, unavailable) -extension NSCountedSet : Sendable { } - open class NSCountedSet : NSMutableSet { // Note: in 5.0 and earlier, _table contained the object's exact count. // In 5.1 and earlier, it contains the count minus one. This allows us to have a quick 'is this set just like a regular NSSet' flag (if this table is empty, then all objects in it exist at most once in it.) diff --git a/Sources/Foundation/NSSortDescriptor.swift b/Sources/Foundation/NSSortDescriptor.swift index 9945fb379d..2fa688c189 100644 --- a/Sources/Foundation/NSSortDescriptor.swift +++ b/Sources/Foundation/NSSortDescriptor.swift @@ -10,7 +10,7 @@ @_implementationOnly import CoreFoundation @available(*, unavailable) -extension NSSortDescriptor : Sendable { } +extension NSSortDescriptor : @unchecked Sendable { } // In swift-corelibs-foundation, key-value coding is not available. Since encoding and decoding a NSSortDescriptor requires interpreting key paths, NSSortDescriptor does not conform to NSCoding or NSSecureCoding in swift-corelibs-foundation only. open class NSSortDescriptor: NSObject, NSCopying { diff --git a/Sources/Foundation/NSString.swift b/Sources/Foundation/NSString.swift index 2d71fe2d07..79ec002d5b 100644 --- a/Sources/Foundation/NSString.swift +++ b/Sources/Foundation/NSString.swift @@ -203,7 +203,7 @@ internal func isAParagraphSeparatorTypeCharacter(_ ch: unichar) -> Bool { } @available(*, unavailable) -extension NSString : Sendable { } +extension NSString : @unchecked Sendable { } open class NSString : NSObject, NSCopying, NSMutableCopying, NSSecureCoding, NSCoding { private let _cfinfo = _CFInfo(typeID: CFStringGetTypeID()) @@ -1451,9 +1451,6 @@ extension NSString { extension NSString : ExpressibleByStringLiteral { } -@available(*, unavailable) -extension NSMutableString : Sendable { } - open class NSMutableString : NSString { open func replaceCharacters(in range: NSRange, with aString: String) { guard type(of: self) === NSString.self || type(of: self) === NSMutableString.self else { diff --git a/Sources/Foundation/NSSwiftRuntime.swift b/Sources/Foundation/NSSwiftRuntime.swift index 90e751a96e..04dff319cc 100644 --- a/Sources/Foundation/NSSwiftRuntime.swift +++ b/Sources/Foundation/NSSwiftRuntime.swift @@ -94,7 +94,7 @@ extension ObjCBool : CustomStringConvertible { #endif @available(*, unavailable) -extension __NSCFType : Sendable { } +extension __NSCFType : @unchecked Sendable { } @usableFromInline internal class __NSCFType : NSObject { diff --git a/Sources/Foundation/NSTextCheckingResult.swift b/Sources/Foundation/NSTextCheckingResult.swift index fa8528915a..12af725525 100644 --- a/Sources/Foundation/NSTextCheckingResult.swift +++ b/Sources/Foundation/NSTextCheckingResult.swift @@ -20,7 +20,7 @@ extension NSTextCheckingResult { } @available(*, unavailable) -extension NSTextCheckingResult : Sendable { } +extension NSTextCheckingResult : @unchecked Sendable { } open class NSTextCheckingResult: NSObject, NSCopying, NSSecureCoding { @@ -291,7 +291,7 @@ extension NSTextCheckingResult { } @available(*, unavailable) -extension NSOrthography : Sendable { } +extension NSOrthography : @unchecked Sendable { } @available(*, deprecated, message: "NSOrthography is not available in swift-corelibs-foundation") open class NSOrthography: NSObject, NSCopying, NSSecureCoding { diff --git a/Sources/Foundation/NSTimeZone.swift b/Sources/Foundation/NSTimeZone.swift index 80f8947700..7555663644 100644 --- a/Sources/Foundation/NSTimeZone.swift +++ b/Sources/Foundation/NSTimeZone.swift @@ -11,7 +11,7 @@ @_spi(SwiftCorelibsFoundation) @_exported import FoundationEssentials @available(*, unavailable) -extension NSTimeZone : Sendable { } +extension NSTimeZone : @unchecked Sendable { } open class NSTimeZone : NSObject, NSCopying, NSSecureCoding, NSCoding { var _timeZone: TimeZone diff --git a/Sources/Foundation/NSURLComponents.swift b/Sources/Foundation/NSURLComponents.swift index 30564159f3..a39cada368 100644 --- a/Sources/Foundation/NSURLComponents.swift +++ b/Sources/Foundation/NSURLComponents.swift @@ -10,7 +10,7 @@ @_implementationOnly import CoreFoundation @available(*, unavailable) -extension NSURLComponents : Sendable { } +extension NSURLComponents : @unchecked Sendable { } open class NSURLComponents: NSObject, NSCopying { private let _componentsStorage: AnyObject! diff --git a/Sources/Foundation/Notification.swift b/Sources/Foundation/Notification.swift index 78dff8e7e6..936d28caf7 100644 --- a/Sources/Foundation/Notification.swift +++ b/Sources/Foundation/Notification.swift @@ -11,7 +11,7 @@ //===----------------------------------------------------------------------===// @available(*, unavailable) -extension Notification : Sendable { } +extension Notification : @unchecked Sendable { } /** `Notification` encapsulates information broadcast to observers via a `NotificationCenter`. diff --git a/Sources/Foundation/NotificationQueue.swift b/Sources/Foundation/NotificationQueue.swift index b10701a618..830f1a945f 100644 --- a/Sources/Foundation/NotificationQueue.swift +++ b/Sources/Foundation/NotificationQueue.swift @@ -29,7 +29,7 @@ extension NotificationQueue { } @available(*, unavailable) -extension NotificationQueue : Sendable { } +extension NotificationQueue : @unchecked Sendable { } open class NotificationQueue: NSObject { diff --git a/Sources/Foundation/Port.swift b/Sources/Foundation/Port.swift index d9f1201dda..56b4a6516b 100644 --- a/Sources/Foundation/Port.swift +++ b/Sources/Foundation/Port.swift @@ -18,9 +18,7 @@ extension Port { public static let didBecomeInvalidNotification = NSNotification.Name(rawValue: "NSPortDidBecomeInvalidNotification") } -@available(*, unavailable) -extension Port : Sendable { } - +//@_nonSendable open class Port : NSObject, NSCopying { /// On Darwin, you can invoke `Port()` directly to produce a `MessagePort`. Since `MessagePort` is not available in swift-corelibs-foundation, you should not invoke this initializer directly. Subclasses of `Port` can delegate to this initializer safely. public override init() { @@ -80,10 +78,10 @@ open class MessagePort: Port {} open class NSMachPort: Port {} @available(*, unavailable) -extension MessagePort : Sendable { } +extension MessagePort : @unchecked Sendable { } @available(*, unavailable) -extension NSMachPort : Sendable { } +extension NSMachPort : @unchecked Sendable { } extension PortDelegate { func handle(_ message: PortMessage) { } @@ -99,7 +97,7 @@ public protocol PortDelegate: AnyObject { open class SocketPort: Port {} @available(*, unavailable) -extension SocketPort : Sendable { } +extension SocketPort : @unchecked Sendable { } #else @@ -407,7 +405,7 @@ fileprivate func __NSFireSocketDatagram(_ socket: CFSocket?, _ type: CFSocketCal } @available(*, unavailable) -extension SocketPort : Sendable { } +extension SocketPort : @unchecked Sendable { } open class SocketPort : Port { struct SocketKind: Hashable { diff --git a/Sources/Foundation/PortMessage.swift b/Sources/Foundation/PortMessage.swift index ba466c085d..c69a06bbb2 100644 --- a/Sources/Foundation/PortMessage.swift +++ b/Sources/Foundation/PortMessage.swift @@ -8,7 +8,7 @@ // @available(*, unavailable) -extension PortMessage : Sendable { } +extension PortMessage : @unchecked Sendable { } open class PortMessage : NSObject, NSCopying { public init(sendPort: Port?, receivePort replyPort: Port?, components: [AnyObject]?) { diff --git a/Sources/Foundation/Progress.swift b/Sources/Foundation/Progress.swift index a5a21439a5..8a948975f5 100644 --- a/Sources/Foundation/Progress.swift +++ b/Sources/Foundation/Progress.swift @@ -532,7 +532,7 @@ public struct ProgressUserInfoKey : RawRepresentable, Equatable, Hashable, Senda } @available(*, unavailable) -extension _ProgressTSD : Sendable { } +extension _ProgressTSD : @unchecked Sendable { } fileprivate class _ProgressTSD : NSObject { /// The thread's default progress. diff --git a/Sources/Foundation/PropertyListSerialization.swift b/Sources/Foundation/PropertyListSerialization.swift index 47738de70d..5c56c9b3ec 100644 --- a/Sources/Foundation/PropertyListSerialization.swift +++ b/Sources/Foundation/PropertyListSerialization.swift @@ -30,7 +30,7 @@ extension PropertyListSerialization { } @available(*, unavailable) -extension PropertyListSerialization : Sendable { } +extension PropertyListSerialization : @unchecked Sendable { } open class PropertyListSerialization : NSObject { diff --git a/Sources/Foundation/RunLoop.swift b/Sources/Foundation/RunLoop.swift index 66f629e0d3..a03dab976d 100644 --- a/Sources/Foundation/RunLoop.swift +++ b/Sources/Foundation/RunLoop.swift @@ -50,7 +50,7 @@ extension RunLoop.Mode { #if !canImport(Dispatch) @available(*, unavailable) -extension RunLoop : Sendable { } +extension RunLoop : @unchecked Sendable { } open class RunLoop: NSObject { @available(*, unavailable, message: "RunLoop is not available on WASI") @@ -74,7 +74,7 @@ internal func _NSRunLoopNew(_ cf: CFRunLoop) -> Unmanaged { } @available(*, unavailable) -extension RunLoop : Sendable { } +extension RunLoop : @unchecked Sendable { } open class RunLoop: NSObject { internal var _cfRunLoopStorage : AnyObject! diff --git a/Sources/Foundation/Scanner.swift b/Sources/Foundation/Scanner.swift index 0b82d51965..e8e70f6267 100644 --- a/Sources/Foundation/Scanner.swift +++ b/Sources/Foundation/Scanner.swift @@ -8,7 +8,7 @@ // @available(*, unavailable) -extension Scanner : Sendable { } +extension Scanner : @unchecked Sendable { } open class Scanner: NSObject, NSCopying { internal var _scanString: String diff --git a/Sources/Foundation/Stream.swift b/Sources/Foundation/Stream.swift index e7a4863f0e..b04caf2edf 100644 --- a/Sources/Foundation/Stream.swift +++ b/Sources/Foundation/Stream.swift @@ -53,11 +53,9 @@ extension Stream { } } -@available(*, unavailable) -extension Stream : Sendable { } - // Stream is an abstract class encapsulating the common API to InputStream and OutputStream. // Subclassers of InputStream and OutputStream must also implement these methods. +//@_nonSendable open class Stream: NSObject { public override init() { @@ -103,7 +101,7 @@ open class Stream: NSObject { } @available(*, unavailable) -extension InputStream : Sendable { } +extension InputStream : @unchecked Sendable { } // InputStream is an abstract class representing the base functionality of a read stream. // Subclassers are required to implement these methods. @@ -181,7 +179,7 @@ open class InputStream: Stream { } @available(*, unavailable) -extension OutputStream : Sendable { } +extension OutputStream : @unchecked Sendable { } // OutputStream is an abstract class representing the base functionality of a write stream. // Subclassers are required to implement these methods. diff --git a/Sources/Foundation/Thread.swift b/Sources/Foundation/Thread.swift index eac41098e0..5e79579c62 100644 --- a/Sources/Foundation/Thread.swift +++ b/Sources/Foundation/Thread.swift @@ -77,7 +77,7 @@ private func NSThreadStart(_ context: UnsafeMutableRawPointer?) -> UnsafeMutable } @available(*, unavailable) -extension Thread : Sendable { } +extension Thread : @unchecked Sendable { } open class Thread : NSObject { diff --git a/Sources/Foundation/Timer.swift b/Sources/Foundation/Timer.swift index 51c2a47f31..0d57425553 100644 --- a/Sources/Foundation/Timer.swift +++ b/Sources/Foundation/Timer.swift @@ -16,7 +16,7 @@ internal func __NSFireTimer(_ timer: CFRunLoopTimer?, info: UnsafeMutableRawPoin } @available(*, unavailable) -extension Timer : Sendable { } +extension Timer : @unchecked Sendable { } open class Timer : NSObject { internal final var _cfObject: CFRunLoopTimer { diff --git a/Sources/Foundation/Unit.swift b/Sources/Foundation/Unit.swift index c71ff52318..400651e78d 100644 --- a/Sources/Foundation/Unit.swift +++ b/Sources/Foundation/Unit.swift @@ -148,7 +148,7 @@ internal class NSUnitConverterReciprocal : UnitConverter, NSSecureCoding, @unche */ @available(*, unavailable) -extension Unit : Sendable { } +extension Unit : @unchecked Sendable { } open class Unit : NSObject, NSCopying, NSSecureCoding { @@ -195,9 +195,6 @@ open class Unit : NSObject, NSCopying, NSSecureCoding { } } -@available(*, unavailable) -extension Dimension : Sendable { } - open class Dimension : Unit { diff --git a/Sources/Foundation/UserDefaults.swift b/Sources/Foundation/UserDefaults.swift index 6b95707f62..32649b2390 100644 --- a/Sources/Foundation/UserDefaults.swift +++ b/Sources/Foundation/UserDefaults.swift @@ -19,7 +19,7 @@ fileprivate func bridgeFromNSCFTypeIfNeeded(_ value: Any) -> Any { } @available(*, unavailable) -extension UserDefaults : Sendable { } +extension UserDefaults : @unchecked Sendable { } open class UserDefaults: NSObject { static private func _isValueAllowed(_ nonbridgedValue: Any) -> Bool { diff --git a/Sources/FoundationNetworking/NSURLRequest.swift b/Sources/FoundationNetworking/NSURLRequest.swift index 99bd1c6b1f..cdda7bcadd 100644 --- a/Sources/FoundationNetworking/NSURLRequest.swift +++ b/Sources/FoundationNetworking/NSURLRequest.swift @@ -97,7 +97,7 @@ extension NSURLRequest { } @available(*, unavailable) -extension NSURLRequest : Sendable { } +extension NSURLRequest : @unchecked Sendable { } /// An `NSURLRequest` object represents a URL load request in a /// manner independent of protocol and URL scheme. @@ -381,9 +381,6 @@ open class NSURLRequest : NSObject, NSSecureCoding, NSCopying, NSMutableCopying } } -@available(*, unavailable) -extension NSMutableURLRequest : Sendable { } - /// An `NSMutableURLRequest` object represents a mutable URL load /// request in a manner independent of protocol and URL scheme. /// diff --git a/Sources/FoundationNetworking/URLProtocol.swift b/Sources/FoundationNetworking/URLProtocol.swift index 5538e8991b..b59159e219 100644 --- a/Sources/FoundationNetworking/URLProtocol.swift +++ b/Sources/FoundationNetworking/URLProtocol.swift @@ -157,7 +157,7 @@ internal class _ProtocolClient : NSObject, @unchecked Sendable { } @available(*, unavailable) -extension URLProtocol : Sendable { } +extension URLProtocol : @unchecked Sendable { } /*! @class NSURLProtocol diff --git a/Sources/FoundationXML/XMLDTD.swift b/Sources/FoundationXML/XMLDTD.swift index 788a2840a6..089c1a088b 100644 --- a/Sources/FoundationXML/XMLDTD.swift +++ b/Sources/FoundationXML/XMLDTD.swift @@ -14,9 +14,6 @@ import Foundation #endif @_implementationOnly import _CFXMLInterface -@available(*, unavailable) -extension XMLDTD : Sendable { } - /*! @class XMLDTD @abstract Defines the order, repetition, and allowable values for a document diff --git a/Sources/FoundationXML/XMLDTDNode.swift b/Sources/FoundationXML/XMLDTDNode.swift index bcd1615cc3..421d83793d 100644 --- a/Sources/FoundationXML/XMLDTDNode.swift +++ b/Sources/FoundationXML/XMLDTDNode.swift @@ -66,9 +66,6 @@ extension XMLDTDNode { } } -@available(*, unavailable) -extension XMLDTDNode : Sendable { } - /*! @class XMLDTDNode @abstract The nodes that are exclusive to a DTD diff --git a/Sources/FoundationXML/XMLDocument.swift b/Sources/FoundationXML/XMLDocument.swift index 3634471219..d5b94ff64d 100644 --- a/Sources/FoundationXML/XMLDocument.swift +++ b/Sources/FoundationXML/XMLDocument.swift @@ -56,9 +56,6 @@ extension XMLDocument { } } -@available(*, unavailable) -extension XMLDocument : Sendable { } - /*! @class XMLDocument @abstract An XML Document diff --git a/Sources/FoundationXML/XMLElement.swift b/Sources/FoundationXML/XMLElement.swift index 066ce686d1..9355e8a704 100644 --- a/Sources/FoundationXML/XMLElement.swift +++ b/Sources/FoundationXML/XMLElement.swift @@ -14,9 +14,6 @@ import Foundation #endif @_implementationOnly import _CFXMLInterface -@available(*, unavailable) -extension XMLElement : Sendable { } - /*! @class XMLElement @abstract An XML element diff --git a/Sources/FoundationXML/XMLNode.swift b/Sources/FoundationXML/XMLNode.swift index d913582755..d2c83ffd36 100644 --- a/Sources/FoundationXML/XMLNode.swift +++ b/Sources/FoundationXML/XMLNode.swift @@ -33,7 +33,7 @@ import Foundation // NSXMLNodePrettyPrint @available(*, unavailable) -extension XMLNode : Sendable { } +extension XMLNode : @unchecked Sendable { } /*! @class NSXMLNode diff --git a/Sources/FoundationXML/XMLParser.swift b/Sources/FoundationXML/XMLParser.swift index 40d57b334e..d89d0ee1f4 100644 --- a/Sources/FoundationXML/XMLParser.swift +++ b/Sources/FoundationXML/XMLParser.swift @@ -394,7 +394,7 @@ internal func _structuredErrorFunc(_ interface: _CFXMLInterface, error: _CFXMLIn } @available(*, unavailable) -extension XMLParser : Sendable { } +extension XMLParser : @unchecked Sendable { } open class XMLParser : NSObject { private var _handler: _CFXMLInterfaceSAXHandler From 3bc5799dc471ac900694e32ab3dc47c1ebb43c3b Mon Sep 17 00:00:00 2001 From: Tony Parker Date: Mon, 22 Jul 2024 08:29:34 -0700 Subject: [PATCH 08/15] Update swift-foundation dependency --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 6d1fb959d9..c12026c4dd 100644 --- a/Package.swift +++ b/Package.swift @@ -91,7 +91,7 @@ var dependencies: [Package.Dependency] { from: "0.0.9"), .package( url: "https://github.com/apple/swift-foundation", - revision: "35d896ab47ab5e487cfce822fbe40d2b278c51d6") + revision: "d59046871c6b69a13595f18d334afa1553e0ba50") ] } } From 1e8bd726902c0c4bf422eb0a5d40e1196a15d779 Mon Sep 17 00:00:00 2001 From: Tony Parker Date: Mon, 22 Jul 2024 15:38:06 -0700 Subject: [PATCH 09/15] Update locking strategy for DateFormatter and NumberFormatter - keep the CF type inside the lock --- Sources/Foundation/DateFormatter.swift | 1056 ++++++----- Sources/Foundation/NSDecimalNumber.swift | 2 +- Sources/Foundation/NSNumber.swift | 4 +- Sources/Foundation/NSSwiftRuntime.swift | 2 +- Sources/Foundation/NumberFormatter.swift | 2046 +++++++++++++--------- Sources/Foundation/Unit.swift | 12 +- 6 files changed, 1804 insertions(+), 1318 deletions(-) diff --git a/Sources/Foundation/DateFormatter.swift b/Sources/Foundation/DateFormatter.swift index 4889319f5a..39ac88cd71 100644 --- a/Sources/Foundation/DateFormatter.swift +++ b/Sources/Foundation/DateFormatter.swift @@ -12,87 +12,558 @@ internal import Synchronization open class DateFormatter : Formatter, @unchecked Sendable { - typealias CFType = CFDateFormatter - private let _formatter: Mutex = .init(nil) - - private final var _cfObject: CFType { - _formatter.withLock { cfDateFormatter in - guard let obj = cfDateFormatter else { - let dateStyle = CFDateFormatterStyle(rawValue: CFIndex(self.dateStyle.rawValue))! - let timeStyle = CFDateFormatterStyle(rawValue: CFIndex(self.timeStyle.rawValue))! + private let _lock: Mutex = .init(.init()) + + public override init() { + super.init() + } + + // Consumes state + private convenience init(state: State) { + self.init() + nonisolated(unsafe) let consumedState = state + _lock.withLock { $0 = consumedState } + } + + open override func copy(with zone: NSZone? = nil) -> Any { + return _lock.withLock { state in + // Zone is not Sendable, so just ignore it here + let copy = state.copy() + return DateFormatter(state: copy) + } + } + + public required init?(coder: NSCoder) { + super.init(coder: coder) + } + + final class State { + private var _formatter : CFDateFormatter? = nil + + func copy(with zone: NSZone? = nil) -> State { + let copied = State() + + func __copy(_ keyPath: ReferenceWritableKeyPath) { + copied[keyPath: keyPath] = self[keyPath: keyPath] + } + + __copy(\.formattingContext) + __copy(\.dateStyle) + __copy(\.timeStyle) + __copy(\._locale) + __copy(\.generatesCalendarDates) + __copy(\._timeZone) + __copy(\._calendar) + __copy(\.isLenient) + __copy(\._twoDigitStartDate) + __copy(\._eraSymbols) + __copy(\._monthSymbols) + __copy(\._shortMonthSymbols) + __copy(\._weekdaySymbols) + __copy(\._shortWeekdaySymbols) + __copy(\._amSymbol) + __copy(\._pmSymbol) + __copy(\._longEraSymbols) + __copy(\._veryShortMonthSymbols) + __copy(\._standaloneMonthSymbols) + __copy(\._shortStandaloneMonthSymbols) + __copy(\._veryShortStandaloneMonthSymbols) + __copy(\._veryShortWeekdaySymbols) + __copy(\._standaloneWeekdaySymbols) + __copy(\._shortStandaloneWeekdaySymbols) + __copy(\._veryShortStandaloneWeekdaySymbols) + __copy(\._quarterSymbols) + __copy(\._shortQuarterSymbols) + __copy(\._standaloneQuarterSymbols) + __copy(\._shortStandaloneQuarterSymbols) + __copy(\._gregorianStartDate) + __copy(\.doesRelativeDateFormatting) + + // The last is `_dateFormat` because setting `dateStyle` and `timeStyle` make it `nil`. + __copy(\._dateFormat) + + return copied + } + + func formatter() -> CFDateFormatter { + guard let obj = _formatter else { + let dateStyle = CFDateFormatterStyle(rawValue: CFIndex(dateStyle.rawValue))! + let timeStyle = CFDateFormatterStyle(rawValue: CFIndex(timeStyle.rawValue))! let obj = CFDateFormatterCreate(kCFAllocatorSystemDefault, locale._cfObject, dateStyle, timeStyle)! _setFormatterAttributes(obj) if let dateFormat = _dateFormat { CFDateFormatterSetFormat(obj, dateFormat._cfObject) } - cfDateFormatter = obj + _formatter = obj return obj } return obj } - } + + private func _reset() { + _formatter = nil + } + + // MARK: - + + var formattingContext: Context = .unknown // default is NSFormattingContextUnknown + + internal func _setFormatterAttributes(_ formatter: CFDateFormatter) { + _setFormatterAttribute(formatter, attributeName: kCFDateFormatterIsLenient, value: isLenient._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFDateFormatterTimeZone, value: _timeZone?._cfObject) + if let ident = _calendar?.identifier { + _setFormatterAttribute(formatter, attributeName: kCFDateFormatterCalendarName, value: ident._cfCalendarIdentifier._cfObject) + } else { + _setFormatterAttribute(formatter, attributeName: kCFDateFormatterCalendarName, value: nil) + } + _setFormatterAttribute(formatter, attributeName: kCFDateFormatterTwoDigitStartDate, value: _twoDigitStartDate?._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFDateFormatterDefaultDate, value: defaultDate?._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFDateFormatterCalendar, value: _calendar?._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFDateFormatterEraSymbols, value: _eraSymbols?._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFDateFormatterMonthSymbols, value: _monthSymbols?._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFDateFormatterShortMonthSymbols, value: _shortMonthSymbols?._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFDateFormatterWeekdaySymbols, value: _weekdaySymbols?._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFDateFormatterShortWeekdaySymbols, value: _shortWeekdaySymbols?._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFDateFormatterAMSymbol, value: _amSymbol?._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFDateFormatterPMSymbol, value: _pmSymbol?._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFDateFormatterLongEraSymbols, value: _longEraSymbols?._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFDateFormatterVeryShortMonthSymbols, value: _veryShortMonthSymbols?._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFDateFormatterStandaloneMonthSymbols, value: _standaloneMonthSymbols?._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFDateFormatterShortStandaloneMonthSymbols, value: _shortStandaloneMonthSymbols?._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFDateFormatterVeryShortStandaloneMonthSymbols, value: _veryShortStandaloneMonthSymbols?._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFDateFormatterVeryShortWeekdaySymbols, value: _veryShortWeekdaySymbols?._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFDateFormatterStandaloneWeekdaySymbols, value: _standaloneWeekdaySymbols?._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFDateFormatterShortStandaloneWeekdaySymbols, value: _shortStandaloneWeekdaySymbols?._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFDateFormatterVeryShortStandaloneWeekdaySymbols, value: _veryShortStandaloneWeekdaySymbols?._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFDateFormatterQuarterSymbols, value: _quarterSymbols?._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFDateFormatterShortQuarterSymbols, value: _shortQuarterSymbols?._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFDateFormatterStandaloneQuarterSymbols, value: _standaloneQuarterSymbols?._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFDateFormatterShortStandaloneQuarterSymbols, value: _shortStandaloneQuarterSymbols?._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFDateFormatterGregorianStartDate, value: _gregorianStartDate?._cfObject) + } + + internal final func _setFormatterAttribute(_ formatter: CFDateFormatter, attributeName: CFString, value: AnyObject?) { + if let value = value { + CFDateFormatterSetProperty(formatter, attributeName, value) + } + } - public override init() { - super.init() - } + private var _dateFormat: String? { willSet { _reset() } } + var dateFormat: String! { + get { + guard let format = _dateFormat else { + return CFDateFormatterGetFormat(formatter())._swiftObject + } + return format + } + set { + _dateFormat = newValue + } + } - public required init?(coder: NSCoder) { - super.init(coder: coder) - } + var dateStyle: Style = .none { + willSet { + _dateFormat = nil + } + } - open override func copy(with zone: NSZone? = nil) -> Any { - let copied = DateFormatter() - - func __copy(_ keyPath: ReferenceWritableKeyPath) { - copied[keyPath: keyPath] = self[keyPath: keyPath] - } - - __copy(\.formattingContext) - __copy(\.dateStyle) - __copy(\.timeStyle) - __copy(\._locale) - __copy(\.generatesCalendarDates) - __copy(\._timeZone) - __copy(\._calendar) - __copy(\.isLenient) - __copy(\._twoDigitStartDate) - __copy(\._eraSymbols) - __copy(\._monthSymbols) - __copy(\._shortMonthSymbols) - __copy(\._weekdaySymbols) - __copy(\._shortWeekdaySymbols) - __copy(\._amSymbol) - __copy(\._pmSymbol) - __copy(\._longEraSymbols) - __copy(\._veryShortMonthSymbols) - __copy(\._standaloneMonthSymbols) - __copy(\._shortStandaloneMonthSymbols) - __copy(\._veryShortStandaloneMonthSymbols) - __copy(\._veryShortWeekdaySymbols) - __copy(\._standaloneWeekdaySymbols) - __copy(\._shortStandaloneWeekdaySymbols) - __copy(\._veryShortStandaloneWeekdaySymbols) - __copy(\._quarterSymbols) - __copy(\._shortQuarterSymbols) - __copy(\._standaloneQuarterSymbols) - __copy(\._shortStandaloneQuarterSymbols) - __copy(\._gregorianStartDate) - __copy(\.doesRelativeDateFormatting) - - // The last is `_dateFormat` because setting `dateStyle` and `timeStyle` make it `nil`. - __copy(\._dateFormat) - - return copied - } - - open var formattingContext: Context = .unknown // default is NSFormattingContextUnknown - - @available(*, unavailable, renamed: "date(from:)") - func getObjectValue(_ obj: UnsafeMutablePointer?, - for string: String, - range rangep: UnsafeMutablePointer?) throws { - NSUnsupported() + var timeStyle: Style = .none { + willSet { + _dateFormat = nil + } + } + + /*@NSCopying*/ internal var _locale: Locale? { willSet { _reset() } } + var locale: Locale! { + get { + guard let locale = _locale else { return .current } + return locale + } + set { + _locale = newValue + } + } + + var generatesCalendarDates = false { willSet { _reset() } } + + /*@NSCopying*/ internal var _timeZone: TimeZone? { willSet { _reset() } } + var timeZone: TimeZone! { + get { + guard let tz = _timeZone else { + // The returned value is a CFTimeZone + let property = CFDateFormatterCopyProperty(formatter(), kCFDateFormatterTimeZone) + let propertyTZ = unsafeBitCast(property, to: CFTimeZone.self) + return propertyTZ._swiftObject + } + return tz + } + set { + _timeZone = newValue + } + } + + /*@NSCopying*/ internal var _calendar: Calendar! { willSet { _reset() } } + var calendar: Calendar! { + get { + guard let calendar = _calendar else { + // The returned value is a CFCalendar + let property = CFDateFormatterCopyProperty(formatter(), kCFDateFormatterCalendar) + let propertyCalendar = unsafeBitCast(property, to: CFCalendar.self) + return propertyCalendar._swiftObject + } + return calendar + } + set { + _calendar = newValue + } + } + + var isLenient = false { willSet { _reset() } } + + /*@NSCopying*/ internal var _twoDigitStartDate: Date? { willSet { _reset() } } + var twoDigitStartDate: Date? { + get { + guard let startDate = _twoDigitStartDate else { + return (CFDateFormatterCopyProperty(formatter(), kCFDateFormatterTwoDigitStartDate) as? NSDate)?._swiftObject + } + return startDate + } + set { + _twoDigitStartDate = newValue + } + } + + /*@NSCopying*/ var defaultDate: Date? { willSet { _reset() } } + + internal var _eraSymbols: [String]? { willSet { _reset() } } + var eraSymbols: [String] { + get { + guard let symbols = _eraSymbols else { + let cfSymbols = CFDateFormatterCopyProperty(formatter(), kCFDateFormatterEraSymbols) as! NSArray + return cfSymbols.allObjects as! [String] + } + return symbols + } + set { + _eraSymbols = newValue + } + } + + internal var _monthSymbols: [String]? { willSet { _reset() } } + var monthSymbols: [String] { + get { + guard let symbols = _monthSymbols else { + let cfSymbols = CFDateFormatterCopyProperty(formatter(), kCFDateFormatterMonthSymbols) as! NSArray + return cfSymbols.allObjects as! [String] + } + return symbols + } + set { + _monthSymbols = newValue + } + } + + internal var _shortMonthSymbols: [String]? { willSet { _reset() } } + var shortMonthSymbols: [String] { + get { + guard let symbols = _shortMonthSymbols else { + let cfSymbols = CFDateFormatterCopyProperty(formatter(), kCFDateFormatterShortMonthSymbols) as! NSArray + return cfSymbols.allObjects as! [String] + } + return symbols + } + set { + _shortMonthSymbols = newValue + } + } + + + internal var _weekdaySymbols: [String]? { willSet { _reset() } } + var weekdaySymbols: [String] { + get { + guard let symbols = _weekdaySymbols else { + let cfSymbols = CFDateFormatterCopyProperty(formatter(), kCFDateFormatterWeekdaySymbols) as! NSArray + return cfSymbols.allObjects as! [String] + } + return symbols + } + set { + _weekdaySymbols = newValue + } + } + + internal var _shortWeekdaySymbols: [String]? { willSet { _reset() } } + var shortWeekdaySymbols: [String] { + get { + guard let symbols = _shortWeekdaySymbols else { + let cfSymbols = CFDateFormatterCopyProperty(formatter(), kCFDateFormatterShortWeekdaySymbols) as! NSArray + return cfSymbols.allObjects as! [String] + } + return symbols + } + set { + _shortWeekdaySymbols = newValue + } + } + + internal var _amSymbol: String? { willSet { _reset() } } + var amSymbol: String { + get { + guard let symbol = _amSymbol else { + return (CFDateFormatterCopyProperty(formatter(), kCFDateFormatterAMSymbol) as! NSString)._swiftObject + } + return symbol + } + set { + _amSymbol = newValue + } + } + + internal var _pmSymbol: String? { willSet { _reset() } } + var pmSymbol: String { + get { + guard let symbol = _pmSymbol else { + return (CFDateFormatterCopyProperty(formatter(), kCFDateFormatterPMSymbol) as! NSString)._swiftObject + } + return symbol + } + set { + _pmSymbol = newValue + } + } + + internal var _longEraSymbols: [String]? { willSet { _reset() } } + var longEraSymbols: [String] { + get { + guard let symbols = _longEraSymbols else { + let cfSymbols = CFDateFormatterCopyProperty(formatter(), kCFDateFormatterLongEraSymbols) as! NSArray + return cfSymbols.allObjects as! [String] + } + return symbols + } + set { + _longEraSymbols = newValue + } + } + + internal var _veryShortMonthSymbols: [String]? { willSet { _reset() } } + var veryShortMonthSymbols: [String] { + get { + guard let symbols = _veryShortMonthSymbols else { + let cfSymbols = CFDateFormatterCopyProperty(formatter(), kCFDateFormatterVeryShortMonthSymbols) as! NSArray + return cfSymbols.allObjects as! [String] + } + return symbols + } + set { + _veryShortMonthSymbols = newValue + } + } + + internal var _standaloneMonthSymbols: [String]? { willSet { _reset() } } + var standaloneMonthSymbols: [String] { + get { + guard let symbols = _standaloneMonthSymbols else { + let cfSymbols = CFDateFormatterCopyProperty(formatter(), kCFDateFormatterStandaloneMonthSymbols) as! NSArray + return cfSymbols.allObjects as! [String] + } + return symbols + } + set { + _standaloneMonthSymbols = newValue + } + } + + internal var _shortStandaloneMonthSymbols: [String]? { willSet { _reset() } } + var shortStandaloneMonthSymbols: [String] { + get { + guard let symbols = _shortStandaloneMonthSymbols else { + let cfSymbols = CFDateFormatterCopyProperty(formatter(), kCFDateFormatterShortStandaloneMonthSymbols) as! NSArray + return cfSymbols.allObjects as! [String] + } + return symbols + } + set { + _shortStandaloneMonthSymbols = newValue + } + } + + internal var _veryShortStandaloneMonthSymbols: [String]? { willSet { _reset() } } + var veryShortStandaloneMonthSymbols: [String] { + get { + guard let symbols = _veryShortStandaloneMonthSymbols else { + let cfSymbols = CFDateFormatterCopyProperty(formatter(), kCFDateFormatterVeryShortStandaloneMonthSymbols) as! NSArray + return cfSymbols.allObjects as! [String] + } + return symbols + } + set { + _veryShortStandaloneMonthSymbols = newValue + } + } + + internal var _veryShortWeekdaySymbols: [String]? { willSet { _reset() } } + var veryShortWeekdaySymbols: [String] { + get { + guard let symbols = _veryShortWeekdaySymbols else { + let cfSymbols = CFDateFormatterCopyProperty(formatter(), kCFDateFormatterVeryShortWeekdaySymbols) as! NSArray + return cfSymbols.allObjects as! [String] + } + return symbols + } + set { + _veryShortWeekdaySymbols = newValue + } + } + + internal var _standaloneWeekdaySymbols: [String]? { willSet { _reset() } } + var standaloneWeekdaySymbols: [String] { + get { + guard let symbols = _standaloneWeekdaySymbols else { + let cfSymbols = CFDateFormatterCopyProperty(formatter(), kCFDateFormatterStandaloneWeekdaySymbols) as! NSArray + return cfSymbols.allObjects as! [String] + } + return symbols + } + set { + _standaloneWeekdaySymbols = newValue + } + } + + internal var _shortStandaloneWeekdaySymbols: [String]? { willSet { _reset() } } + var shortStandaloneWeekdaySymbols: [String] { + get { + guard let symbols = _shortStandaloneWeekdaySymbols else { + let cfSymbols = CFDateFormatterCopyProperty(formatter(), kCFDateFormatterShortStandaloneWeekdaySymbols) as! NSArray + return cfSymbols.allObjects as! [String] + } + return symbols + } + set { + _shortStandaloneWeekdaySymbols = newValue + } + } + + internal var _veryShortStandaloneWeekdaySymbols: [String]? { willSet { _reset() } } + var veryShortStandaloneWeekdaySymbols: [String] { + get { + guard let symbols = _veryShortStandaloneWeekdaySymbols else { + let cfSymbols = CFDateFormatterCopyProperty(formatter(), kCFDateFormatterVeryShortStandaloneWeekdaySymbols) as! NSArray + return cfSymbols.allObjects as! [String] + } + return symbols + } + set { + _veryShortStandaloneWeekdaySymbols = newValue + } + } + + internal var _quarterSymbols: [String]? { willSet { _reset() } } + var quarterSymbols: [String] { + get { + guard let symbols = _quarterSymbols else { + let cfSymbols = CFDateFormatterCopyProperty(formatter(), kCFDateFormatterQuarterSymbols) as! NSArray + return cfSymbols.allObjects as! [String] + } + return symbols + } + set { + _quarterSymbols = newValue + } + } + + internal var _shortQuarterSymbols: [String]? { willSet { _reset() } } + var shortQuarterSymbols: [String] { + get { + guard let symbols = _shortQuarterSymbols else { + let cfSymbols = CFDateFormatterCopyProperty(formatter(), kCFDateFormatterShortQuarterSymbols) as! NSArray + return cfSymbols.allObjects as! [String] + } + return symbols + } + set { + _shortQuarterSymbols = newValue + } + } + + internal var _standaloneQuarterSymbols: [String]? { willSet { _reset() } } + var standaloneQuarterSymbols: [String] { + get { + guard let symbols = _standaloneQuarterSymbols else { + let cfSymbols = CFDateFormatterCopyProperty(formatter(), kCFDateFormatterStandaloneQuarterSymbols) as! NSArray + return cfSymbols.allObjects as! [String] + } + return symbols + } + set { + _standaloneQuarterSymbols = newValue + } + } + + internal var _shortStandaloneQuarterSymbols: [String]? { willSet { _reset() } } + var shortStandaloneQuarterSymbols: [String] { + get { + guard let symbols = _shortStandaloneQuarterSymbols else { + let cfSymbols = CFDateFormatterCopyProperty(formatter(), kCFDateFormatterShortStandaloneQuarterSymbols) as! NSArray + return cfSymbols.allObjects as! [String] + } + return symbols + } + set { + _shortStandaloneQuarterSymbols = newValue + } + } + + internal var _gregorianStartDate: Date? { willSet { _reset() } } + var gregorianStartDate: Date? { + get { + guard let startDate = _gregorianStartDate else { + return (CFDateFormatterCopyProperty(formatter(), kCFDateFormatterGregorianStartDate) as? NSDate)?._swiftObject + } + return startDate + } + set { + _gregorianStartDate = newValue + } + } + + var doesRelativeDateFormatting = false { willSet { _reset() } } + + // MARK: - + + func string(for obj: Any) -> String? { + guard let date = obj as? Date else { return nil } + return string(from: date) + } + + func string(from date: Date) -> String { + return CFDateFormatterCreateStringWithDate(kCFAllocatorSystemDefault, formatter(), date._cfObject)._swiftObject + } + + func date(from string: String) -> Date? { + var range = CFRange(location: 0, length: string.length) + let date = withUnsafeMutablePointer(to: &range) { (rangep: UnsafeMutablePointer) -> Date? in + guard let res = CFDateFormatterCreateDateFromString(kCFAllocatorSystemDefault, formatter(), string._cfObject, rangep) else { + return nil + } + return res._swiftObject + } + + // range.length is updated with the last position of the input string that was parsed + guard let swiftRange = Range(NSRange(range), in: string) else { + fatalError("Incorrect range \(range) in \(string)") + } + + // Apple DateFormatter implementation returns nil + // if non-whitespace sharacters are left after parsed content. + let remainder = String(string[swiftRange.upperBound...]) + let characterSet = CharacterSet(charactersIn: remainder) + guard CharacterSet.whitespaces.isSuperset(of: characterSet) else { + return nil + } + return date + } } open override func string(for obj: Any) -> String? { @@ -101,31 +572,11 @@ open class DateFormatter : Formatter, @unchecked Sendable { } open func string(from date: Date) -> String { - return CFDateFormatterCreateStringWithDate(kCFAllocatorSystemDefault, _cfObject, date._cfObject)._swiftObject + _lock.withLock { $0.string(from: date) } } open func date(from string: String) -> Date? { - var range = CFRange(location: 0, length: string.length) - let date = withUnsafeMutablePointer(to: &range) { (rangep: UnsafeMutablePointer) -> Date? in - guard let res = CFDateFormatterCreateDateFromString(kCFAllocatorSystemDefault, _cfObject, string._cfObject, rangep) else { - return nil - } - return res._swiftObject - } - - // range.length is updated with the last position of the input string that was parsed - guard let swiftRange = Range(NSRange(range), in: string) else { - fatalError("Incorrect range \(range) in \(string)") - } - - // Apple DateFormatter implementation returns nil - // if non-whitespace sharacters are left after parsed content. - let remainder = String(string[swiftRange.upperBound...]) - let characterSet = CharacterSet(charactersIn: remainder) - guard CharacterSet.whitespaces.isSuperset(of: characterSet) else { - return nil - } - return date + _lock.withLock { $0.date(from: string) } } open class func localizedString(from date: Date, dateStyle dstyle: Style, timeStyle tstyle: Style) -> String { @@ -148,432 +599,167 @@ open class DateFormatter : Formatter, @unchecked Sendable { } } - private func _reset() { - _formatter.withLock { - $0 = nil - } - } - - internal final func _setFormatterAttributes(_ formatter: CFDateFormatter) { - _setFormatterAttribute(formatter, attributeName: kCFDateFormatterIsLenient, value: isLenient._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFDateFormatterTimeZone, value: _timeZone?._cfObject) - if let ident = _calendar?.identifier { - _setFormatterAttribute(formatter, attributeName: kCFDateFormatterCalendarName, value: ident._cfCalendarIdentifier._cfObject) - } else { - _setFormatterAttribute(formatter, attributeName: kCFDateFormatterCalendarName, value: nil) - } - _setFormatterAttribute(formatter, attributeName: kCFDateFormatterTwoDigitStartDate, value: _twoDigitStartDate?._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFDateFormatterDefaultDate, value: defaultDate?._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFDateFormatterCalendar, value: _calendar?._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFDateFormatterEraSymbols, value: _eraSymbols?._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFDateFormatterMonthSymbols, value: _monthSymbols?._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFDateFormatterShortMonthSymbols, value: _shortMonthSymbols?._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFDateFormatterWeekdaySymbols, value: _weekdaySymbols?._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFDateFormatterShortWeekdaySymbols, value: _shortWeekdaySymbols?._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFDateFormatterAMSymbol, value: _amSymbol?._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFDateFormatterPMSymbol, value: _pmSymbol?._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFDateFormatterLongEraSymbols, value: _longEraSymbols?._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFDateFormatterVeryShortMonthSymbols, value: _veryShortMonthSymbols?._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFDateFormatterStandaloneMonthSymbols, value: _standaloneMonthSymbols?._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFDateFormatterShortStandaloneMonthSymbols, value: _shortStandaloneMonthSymbols?._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFDateFormatterVeryShortStandaloneMonthSymbols, value: _veryShortStandaloneMonthSymbols?._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFDateFormatterVeryShortWeekdaySymbols, value: _veryShortWeekdaySymbols?._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFDateFormatterStandaloneWeekdaySymbols, value: _standaloneWeekdaySymbols?._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFDateFormatterShortStandaloneWeekdaySymbols, value: _shortStandaloneWeekdaySymbols?._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFDateFormatterVeryShortStandaloneWeekdaySymbols, value: _veryShortStandaloneWeekdaySymbols?._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFDateFormatterQuarterSymbols, value: _quarterSymbols?._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFDateFormatterShortQuarterSymbols, value: _shortQuarterSymbols?._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFDateFormatterStandaloneQuarterSymbols, value: _standaloneQuarterSymbols?._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFDateFormatterShortStandaloneQuarterSymbols, value: _shortStandaloneQuarterSymbols?._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFDateFormatterGregorianStartDate, value: _gregorianStartDate?._cfObject) - } - - internal final func _setFormatterAttribute(_ formatter: CFDateFormatter, attributeName: CFString, value: AnyObject?) { - if let value = value { - CFDateFormatterSetProperty(formatter, attributeName, value) - } - } - - private var _dateFormat: String? { willSet { _reset() } } + // MARK: - + open var dateFormat: String! { - get { - guard let format = _dateFormat else { - return CFDateFormatterGetFormat(_cfObject)._swiftObject - } - return format - } - set { - _dateFormat = newValue - } + get { _lock.withLock { $0.dateFormat } } + set { _lock.withLock { $0.dateFormat = newValue } } } - open var dateStyle: Style = .none { - willSet { - _dateFormat = nil - } + open var dateStyle: Style { + get { _lock.withLock { $0.dateStyle } } + set { _lock.withLock { $0.dateStyle = newValue } } } - open var timeStyle: Style = .none { - willSet { - _dateFormat = nil - } + open var timeStyle: Style { + get { _lock.withLock { $0.timeStyle } } + set { _lock.withLock { $0.timeStyle = newValue } } } - /*@NSCopying*/ internal var _locale: Locale? { willSet { _reset() } } open var locale: Locale! { - get { - guard let locale = _locale else { return .current } - return locale - } - set { - _locale = newValue - } + get { _lock.withLock { $0.locale } } + set { _lock.withLock { $0.locale = newValue } } } - open var generatesCalendarDates = false { willSet { _reset() } } + open var generatesCalendarDates: Bool { + get { _lock.withLock { $0.generatesCalendarDates } } + set { _lock.withLock { $0.generatesCalendarDates = newValue } } + } - /*@NSCopying*/ internal var _timeZone: TimeZone? { willSet { _reset() } } open var timeZone: TimeZone! { - get { - guard let tz = _timeZone else { - // The returned value is a CFTimeZone - let property = CFDateFormatterCopyProperty(_cfObject, kCFDateFormatterTimeZone) - let propertyTZ = unsafeBitCast(property, to: CFTimeZone.self) - return propertyTZ._swiftObject - } - return tz - } - set { - _timeZone = newValue - } + get { _lock.withLock { $0.timeZone } } + set { _lock.withLock { $0.timeZone = newValue } } } - /*@NSCopying*/ internal var _calendar: Calendar! { willSet { _reset() } } open var calendar: Calendar! { - get { - guard let calendar = _calendar else { - // The returned value is a CFCalendar - let property = CFDateFormatterCopyProperty(_cfObject, kCFDateFormatterCalendar) - let propertyCalendar = unsafeBitCast(property, to: CFCalendar.self) - return propertyCalendar._swiftObject - } - return calendar - } - set { - _calendar = newValue - } + get { _lock.withLock { $0.calendar } } + set { _lock.withLock { $0.calendar = newValue } } } - open var isLenient = false { willSet { _reset() } } + open var isLenient: Bool { + get { _lock.withLock { $0.isLenient } } + set { _lock.withLock { $0.isLenient = newValue } } + } - /*@NSCopying*/ internal var _twoDigitStartDate: Date? { willSet { _reset() } } open var twoDigitStartDate: Date? { - get { - guard let startDate = _twoDigitStartDate else { - return (CFDateFormatterCopyProperty(_cfObject, kCFDateFormatterTwoDigitStartDate) as? NSDate)?._swiftObject - } - return startDate - } - set { - _twoDigitStartDate = newValue - } + get { _lock.withLock { $0.twoDigitStartDate } } + set { _lock.withLock { $0.twoDigitStartDate = newValue } } + } + + open var defaultDate: Date? { + get { _lock.withLock { $0.defaultDate } } + set { _lock.withLock { $0.defaultDate = newValue } } } - /*@NSCopying*/ open var defaultDate: Date? { willSet { _reset() } } - - internal var _eraSymbols: [String]? { willSet { _reset() } } open var eraSymbols: [String] { - get { - guard let symbols = _eraSymbols else { - let cfSymbols = CFDateFormatterCopyProperty(_cfObject, kCFDateFormatterEraSymbols) as! NSArray - return cfSymbols.allObjects as! [String] - } - return symbols - } - set { - _eraSymbols = newValue - } + get { _lock.withLock { $0.eraSymbols } } + set { _lock.withLock { $0.eraSymbols = newValue } } } - - internal var _monthSymbols: [String]? { willSet { _reset() } } + open var monthSymbols: [String] { - get { - guard let symbols = _monthSymbols else { - let cfSymbols = CFDateFormatterCopyProperty(_cfObject, kCFDateFormatterMonthSymbols) as! NSArray - return cfSymbols.allObjects as! [String] - } - return symbols - } - set { - _monthSymbols = newValue - } + get { _lock.withLock { $0.monthSymbols } } + set { _lock.withLock { $0.monthSymbols = newValue } } } - internal var _shortMonthSymbols: [String]? { willSet { _reset() } } open var shortMonthSymbols: [String] { - get { - guard let symbols = _shortMonthSymbols else { - let cfSymbols = CFDateFormatterCopyProperty(_cfObject, kCFDateFormatterShortMonthSymbols) as! NSArray - return cfSymbols.allObjects as! [String] - } - return symbols - } - set { - _shortMonthSymbols = newValue - } + get { _lock.withLock { $0.shortMonthSymbols } } + set { _lock.withLock { $0.shortMonthSymbols = newValue } } } - - internal var _weekdaySymbols: [String]? { willSet { _reset() } } open var weekdaySymbols: [String] { - get { - guard let symbols = _weekdaySymbols else { - let cfSymbols = CFDateFormatterCopyProperty(_cfObject, kCFDateFormatterWeekdaySymbols) as! NSArray - return cfSymbols.allObjects as! [String] - } - return symbols - } - set { - _weekdaySymbols = newValue - } + get { _lock.withLock { $0.weekdaySymbols } } + set { _lock.withLock { $0.weekdaySymbols = newValue } } } - internal var _shortWeekdaySymbols: [String]? { willSet { _reset() } } open var shortWeekdaySymbols: [String] { - get { - guard let symbols = _shortWeekdaySymbols else { - let cfSymbols = CFDateFormatterCopyProperty(_cfObject, kCFDateFormatterShortWeekdaySymbols) as! NSArray - return cfSymbols.allObjects as! [String] - } - return symbols - } - set { - _shortWeekdaySymbols = newValue - } + get { _lock.withLock { $0.shortWeekdaySymbols } } + set { _lock.withLock { $0.shortWeekdaySymbols = newValue } } } - internal var _amSymbol: String? { willSet { _reset() } } open var amSymbol: String { - get { - guard let symbol = _amSymbol else { - return (CFDateFormatterCopyProperty(_cfObject, kCFDateFormatterAMSymbol) as! NSString)._swiftObject - } - return symbol - } - set { - _amSymbol = newValue - } + get { _lock.withLock { $0.amSymbol } } + set { _lock.withLock { $0.amSymbol = newValue } } } - internal var _pmSymbol: String? { willSet { _reset() } } open var pmSymbol: String { - get { - guard let symbol = _pmSymbol else { - return (CFDateFormatterCopyProperty(_cfObject, kCFDateFormatterPMSymbol) as! NSString)._swiftObject - } - return symbol - } - set { - _pmSymbol = newValue - } + get { _lock.withLock { $0.pmSymbol } } + set { _lock.withLock { $0.pmSymbol = newValue } } } - internal var _longEraSymbols: [String]? { willSet { _reset() } } open var longEraSymbols: [String] { - get { - guard let symbols = _longEraSymbols else { - let cfSymbols = CFDateFormatterCopyProperty(_cfObject, kCFDateFormatterLongEraSymbols) as! NSArray - return cfSymbols.allObjects as! [String] - } - return symbols - } - set { - _longEraSymbols = newValue - } + get { _lock.withLock { $0.longEraSymbols } } + set { _lock.withLock { $0.longEraSymbols = newValue } } } - internal var _veryShortMonthSymbols: [String]? { willSet { _reset() } } open var veryShortMonthSymbols: [String] { - get { - guard let symbols = _veryShortMonthSymbols else { - let cfSymbols = CFDateFormatterCopyProperty(_cfObject, kCFDateFormatterVeryShortMonthSymbols) as! NSArray - return cfSymbols.allObjects as! [String] - } - return symbols - } - set { - _veryShortMonthSymbols = newValue - } + get { _lock.withLock { $0.veryShortMonthSymbols } } + set { _lock.withLock { $0.veryShortMonthSymbols = newValue } } } - internal var _standaloneMonthSymbols: [String]? { willSet { _reset() } } open var standaloneMonthSymbols: [String] { - get { - guard let symbols = _standaloneMonthSymbols else { - let cfSymbols = CFDateFormatterCopyProperty(_cfObject, kCFDateFormatterStandaloneMonthSymbols) as! NSArray - return cfSymbols.allObjects as! [String] - } - return symbols - } - set { - _standaloneMonthSymbols = newValue - } + get { _lock.withLock { $0.standaloneMonthSymbols } } + set { _lock.withLock { $0.standaloneMonthSymbols = newValue } } } - internal var _shortStandaloneMonthSymbols: [String]? { willSet { _reset() } } open var shortStandaloneMonthSymbols: [String] { - get { - guard let symbols = _shortStandaloneMonthSymbols else { - let cfSymbols = CFDateFormatterCopyProperty(_cfObject, kCFDateFormatterShortStandaloneMonthSymbols) as! NSArray - return cfSymbols.allObjects as! [String] - } - return symbols - } - set { - _shortStandaloneMonthSymbols = newValue - } + get { _lock.withLock { $0.shortStandaloneMonthSymbols } } + set { _lock.withLock { $0.shortStandaloneMonthSymbols = newValue } } } - internal var _veryShortStandaloneMonthSymbols: [String]? { willSet { _reset() } } open var veryShortStandaloneMonthSymbols: [String] { - get { - guard let symbols = _veryShortStandaloneMonthSymbols else { - let cfSymbols = CFDateFormatterCopyProperty(_cfObject, kCFDateFormatterVeryShortStandaloneMonthSymbols) as! NSArray - return cfSymbols.allObjects as! [String] - } - return symbols - } - set { - _veryShortStandaloneMonthSymbols = newValue - } + get { _lock.withLock { $0.veryShortStandaloneMonthSymbols } } + set { _lock.withLock { $0.veryShortStandaloneMonthSymbols = newValue } } } - internal var _veryShortWeekdaySymbols: [String]? { willSet { _reset() } } open var veryShortWeekdaySymbols: [String] { - get { - guard let symbols = _veryShortWeekdaySymbols else { - let cfSymbols = CFDateFormatterCopyProperty(_cfObject, kCFDateFormatterVeryShortWeekdaySymbols) as! NSArray - return cfSymbols.allObjects as! [String] - } - return symbols - } - set { - _veryShortWeekdaySymbols = newValue - } + get { _lock.withLock { $0.veryShortWeekdaySymbols } } + set { _lock.withLock { $0.veryShortWeekdaySymbols = newValue } } } - internal var _standaloneWeekdaySymbols: [String]? { willSet { _reset() } } open var standaloneWeekdaySymbols: [String] { - get { - guard let symbols = _standaloneWeekdaySymbols else { - let cfSymbols = CFDateFormatterCopyProperty(_cfObject, kCFDateFormatterStandaloneWeekdaySymbols) as! NSArray - return cfSymbols.allObjects as! [String] - } - return symbols - } - set { - _standaloneWeekdaySymbols = newValue - } + get { _lock.withLock { $0.standaloneWeekdaySymbols } } + set { _lock.withLock { $0.standaloneWeekdaySymbols = newValue } } } - internal var _shortStandaloneWeekdaySymbols: [String]? { willSet { _reset() } } open var shortStandaloneWeekdaySymbols: [String] { - get { - guard let symbols = _shortStandaloneWeekdaySymbols else { - let cfSymbols = CFDateFormatterCopyProperty(_cfObject, kCFDateFormatterShortStandaloneWeekdaySymbols) as! NSArray - return cfSymbols.allObjects as! [String] - } - return symbols - } - set { - _shortStandaloneWeekdaySymbols = newValue - } + get { _lock.withLock { $0.shortStandaloneWeekdaySymbols } } + set { _lock.withLock { $0.shortStandaloneWeekdaySymbols = newValue } } } - - internal var _veryShortStandaloneWeekdaySymbols: [String]? { willSet { _reset() } } + open var veryShortStandaloneWeekdaySymbols: [String] { - get { - guard let symbols = _veryShortStandaloneWeekdaySymbols else { - let cfSymbols = CFDateFormatterCopyProperty(_cfObject, kCFDateFormatterVeryShortStandaloneWeekdaySymbols) as! NSArray - return cfSymbols.allObjects as! [String] - } - return symbols - } - set { - _veryShortStandaloneWeekdaySymbols = newValue - } + get { _lock.withLock { $0.veryShortStandaloneWeekdaySymbols } } + set { _lock.withLock { $0.veryShortStandaloneWeekdaySymbols = newValue } } } - internal var _quarterSymbols: [String]? { willSet { _reset() } } open var quarterSymbols: [String] { - get { - guard let symbols = _quarterSymbols else { - let cfSymbols = CFDateFormatterCopyProperty(_cfObject, kCFDateFormatterQuarterSymbols) as! NSArray - return cfSymbols.allObjects as! [String] - } - return symbols - } - set { - _quarterSymbols = newValue - } + get { _lock.withLock { $0.quarterSymbols } } + set { _lock.withLock { $0.quarterSymbols = newValue } } } - - internal var _shortQuarterSymbols: [String]? { willSet { _reset() } } + open var shortQuarterSymbols: [String] { - get { - guard let symbols = _shortQuarterSymbols else { - let cfSymbols = CFDateFormatterCopyProperty(_cfObject, kCFDateFormatterShortQuarterSymbols) as! NSArray - return cfSymbols.allObjects as! [String] - } - return symbols - } - set { - _shortQuarterSymbols = newValue - } + get { _lock.withLock { $0.shortQuarterSymbols } } + set { _lock.withLock { $0.shortQuarterSymbols = newValue } } } - internal var _standaloneQuarterSymbols: [String]? { willSet { _reset() } } open var standaloneQuarterSymbols: [String] { - get { - guard let symbols = _standaloneQuarterSymbols else { - let cfSymbols = CFDateFormatterCopyProperty(_cfObject, kCFDateFormatterStandaloneQuarterSymbols) as! NSArray - return cfSymbols.allObjects as! [String] - } - return symbols - } - set { - _standaloneQuarterSymbols = newValue - } + get { _lock.withLock { $0.standaloneQuarterSymbols } } + set { _lock.withLock { $0.standaloneQuarterSymbols = newValue } } } - internal var _shortStandaloneQuarterSymbols: [String]? { willSet { _reset() } } open var shortStandaloneQuarterSymbols: [String] { - get { - guard let symbols = _shortStandaloneQuarterSymbols else { - let cfSymbols = CFDateFormatterCopyProperty(_cfObject, kCFDateFormatterShortStandaloneQuarterSymbols) as! NSArray - return cfSymbols.allObjects as! [String] - } - return symbols - } - set { - _shortStandaloneQuarterSymbols = newValue - } + get { _lock.withLock { $0.shortStandaloneQuarterSymbols } } + set { _lock.withLock { $0.shortStandaloneQuarterSymbols = newValue } } } - internal var _gregorianStartDate: Date? { willSet { _reset() } } open var gregorianStartDate: Date? { - get { - guard let startDate = _gregorianStartDate else { - return (CFDateFormatterCopyProperty(_cfObject, kCFDateFormatterGregorianStartDate) as? NSDate)?._swiftObject - } - return startDate - } - set { - _gregorianStartDate = newValue - } + get { _lock.withLock { $0.gregorianStartDate } } + set { _lock.withLock { $0.gregorianStartDate = newValue } } } - open var doesRelativeDateFormatting = false { willSet { _reset() } } + open var doesRelativeDateFormatting: Bool { + get { _lock.withLock { $0.doesRelativeDateFormatting } } + set { _lock.withLock { $0.doesRelativeDateFormatting = newValue } } + } } extension DateFormatter { diff --git a/Sources/Foundation/NSDecimalNumber.swift b/Sources/Foundation/NSDecimalNumber.swift index 52b822e8ad..b669769c77 100644 --- a/Sources/Foundation/NSDecimalNumber.swift +++ b/Sources/Foundation/NSDecimalNumber.swift @@ -407,7 +407,7 @@ open class NSDecimalNumber : NSNumber, @unchecked Sendable { return false } - override var _swiftValueOfOptimalType: Any { + override var _swiftValueOfOptimalType: (Any & Sendable) { return decimal } } diff --git a/Sources/Foundation/NSNumber.swift b/Sources/Foundation/NSNumber.swift index fc035b380a..65ee17bc18 100644 --- a/Sources/Foundation/NSNumber.swift +++ b/Sources/Foundation/NSNumber.swift @@ -663,7 +663,7 @@ open class NSNumber : NSValue, @unchecked Sendable { } } - internal var _swiftValueOfOptimalType: Any { + internal var _swiftValueOfOptimalType: (Any & Sendable) { if self === kCFBooleanTrue { return true } else if self === kCFBooleanFalse { @@ -1170,7 +1170,7 @@ internal func _CFSwiftNumberGetBoolValue(_ obj: CFTypeRef) -> Bool { } protocol _NSNumberCastingWithoutBridging { - var _swiftValueOfOptimalType: Any { get } + var _swiftValueOfOptimalType: (Any & Sendable) { get } } extension NSNumber: _NSNumberCastingWithoutBridging {} diff --git a/Sources/Foundation/NSSwiftRuntime.swift b/Sources/Foundation/NSSwiftRuntime.swift index 04dff319cc..03176c17cf 100644 --- a/Sources/Foundation/NSSwiftRuntime.swift +++ b/Sources/Foundation/NSSwiftRuntime.swift @@ -383,7 +383,7 @@ extension Array { internal typealias _DarwinCompatibleBoolean = Bool #endif -public protocol _NSNonfileURLContentLoading: AnyObject { +public protocol _NSNonfileURLContentLoading: AnyObject, Sendable { init() func contentsOf(url: URL) throws -> (result: NSData, textEncodingNameIfAvailable: String?) } diff --git a/Sources/Foundation/NumberFormatter.swift b/Sources/Foundation/NumberFormatter.swift index d3d6665511..b77ca56da2 100644 --- a/Sources/Foundation/NumberFormatter.swift +++ b/Sources/Foundation/NumberFormatter.swift @@ -43,15 +43,132 @@ extension NumberFormatter { } open class NumberFormatter : Formatter, @unchecked Sendable { - typealias CFType = CFNumberFormatter - private let _formatter: Mutex = .init(nil) + private let _lock: Mutex = .init(.init()) + + public override init() { + super.init() + } + + public required init?(coder: NSCoder) { + super.init(coder: coder) + } + + // Consumes state + private convenience init(state: State) { + self.init() + nonisolated(unsafe) let consumedState = state + _lock.withLock { $0 = consumedState } + } + + open override func copy(with zone: NSZone? = nil) -> Any { + return _lock.withLock { state in + // Zone is not Sendable, so just ignore it here + let copy = state.copy() + return NumberFormatter(state: copy) + } + } + + open class func localizedString(from num: NSNumber, number nstyle: Style) -> String { + let numberFormatter = NumberFormatter() + numberFormatter.numberStyle = nstyle + return numberFormatter.string(for: num)! + } - private final var _cfFormatter: CFType { - _formatter.withLock { _currentCfFormatter in - if let obj = _currentCfFormatter { + final class State { + private var _formatter: CFNumberFormatter? = nil + + // MARK: - + + func copy(with zone: NSZone? = nil) -> State { + let copied = State() + + func __copy(_ keyPath: ReferenceWritableKeyPath) { + copied[keyPath: keyPath] = self[keyPath: keyPath] + } + + func __copy(_ keyPath: ReferenceWritableKeyPath) where T: NSCopying { + copied[keyPath: keyPath] = self[keyPath: keyPath].copy(with: zone) as! T + } + + func __copy(_ keyPath: ReferenceWritableKeyPath) where T: NSCopying { + copied[keyPath: keyPath] = self[keyPath: keyPath]?.copy(with: zone) as! T? + } + + __copy(\.formattingContext) + __copy(\._numberStyle) + __copy(\._locale) + __copy(\._generatesDecimalNumbers) + __copy(\._textAttributesForNegativeValues) + __copy(\._textAttributesForPositiveValues) + __copy(\._allowsFloats) + __copy(\._decimalSeparator) + __copy(\._alwaysShowsDecimalSeparator) + __copy(\._currencyDecimalSeparator) + __copy(\._usesGroupingSeparator) + __copy(\._groupingSeparator) + __copy(\._zeroSymbol) + __copy(\._textAttributesForZero) + __copy(\._nilSymbol) + __copy(\._textAttributesForNil) + __copy(\._notANumberSymbol) + __copy(\._textAttributesForNotANumber) + __copy(\._positiveInfinitySymbol) + __copy(\._textAttributesForPositiveInfinity) + __copy(\._negativeInfinitySymbol) + __copy(\._textAttributesForNegativeInfinity) + __copy(\._positivePrefix) + __copy(\._positiveSuffix) + __copy(\._negativePrefix) + __copy(\._negativeSuffix) + __copy(\._currencyCode) + __copy(\._currencySymbol) + __copy(\._internationalCurrencySymbol) + __copy(\._percentSymbol) + __copy(\._perMillSymbol) + __copy(\._minusSign) + __copy(\._plusSign) + __copy(\._exponentSymbol) + __copy(\._groupingSize) + __copy(\._secondaryGroupingSize) + __copy(\._multiplier) + __copy(\._formatWidth) + __copy(\._paddingCharacter) + __copy(\._paddingPosition) + __copy(\._roundingMode) + __copy(\._roundingIncrement) + __copy(\._minimumIntegerDigits) + __copy(\._maximumIntegerDigits) + __copy(\._minimumFractionDigits) + __copy(\._maximumFractionDigits) + __copy(\._minimum) + __copy(\._maximum) + __copy(\._currencyGroupingSeparator) + __copy(\._lenient) + __copy(\._usesSignificantDigits) + __copy(\._minimumSignificantDigits) + __copy(\._maximumSignificantDigits) + __copy(\._partialStringValidationEnabled) + __copy(\._hasThousandSeparators) + __copy(\._thousandSeparator) + __copy(\._localizesFormat) + __copy(\._positiveFormat) + __copy(\._negativeFormat) + __copy(\._roundingBehavior) + + return copied + } + + // MARK: - + + func _reset() { + _formatter = nil + } + + func formatter() -> CFNumberFormatter { + if let obj = _formatter { return obj } else { - let numberStyle = CFNumberFormatterStyle(rawValue: CFIndex(self.numberStyle.rawValue))! + let numberStyle = CFNumberFormatterStyle(rawValue: CFIndex(_numberStyle.rawValue))! let obj = CFNumberFormatterCreate(kCFAllocatorSystemDefault, locale._cfObject, numberStyle)! _setFormatterAttributes(obj) @@ -63,1065 +180,1252 @@ open class NumberFormatter : Formatter, @unchecked Sendable { } CFNumberFormatterSetFormat(obj, format._cfObject) } - _currentCfFormatter = obj + _formatter = obj return obj } } - } - - open override func copy(with zone: NSZone? = nil) -> Any { - let copied = NumberFormatter() - - func __copy(_ keyPath: ReferenceWritableKeyPath) { - copied[keyPath: keyPath] = self[keyPath: keyPath] - } - - func __copy(_ keyPath: ReferenceWritableKeyPath) where T: NSCopying { - copied[keyPath: keyPath] = self[keyPath: keyPath].copy(with: zone) as! T - } - - func __copy(_ keyPath: ReferenceWritableKeyPath) where T: NSCopying { - copied[keyPath: keyPath] = self[keyPath: keyPath]?.copy(with: zone) as! T? - } - - __copy(\.formattingContext) - __copy(\._numberStyle) - __copy(\._locale) - __copy(\._generatesDecimalNumbers) - __copy(\._textAttributesForNegativeValues) - __copy(\._textAttributesForPositiveValues) - __copy(\._allowsFloats) - __copy(\._decimalSeparator) - __copy(\._alwaysShowsDecimalSeparator) - __copy(\._currencyDecimalSeparator) - __copy(\._usesGroupingSeparator) - __copy(\._groupingSeparator) - __copy(\._zeroSymbol) - __copy(\._textAttributesForZero) - __copy(\._nilSymbol) - __copy(\._textAttributesForNil) - __copy(\._notANumberSymbol) - __copy(\._textAttributesForNotANumber) - __copy(\._positiveInfinitySymbol) - __copy(\._textAttributesForPositiveInfinity) - __copy(\._negativeInfinitySymbol) - __copy(\._textAttributesForNegativeInfinity) - __copy(\._positivePrefix) - __copy(\._positiveSuffix) - __copy(\._negativePrefix) - __copy(\._negativeSuffix) - __copy(\._currencyCode) - __copy(\._currencySymbol) - __copy(\._internationalCurrencySymbol) - __copy(\._percentSymbol) - __copy(\._perMillSymbol) - __copy(\._minusSign) - __copy(\._plusSign) - __copy(\._exponentSymbol) - __copy(\._groupingSize) - __copy(\._secondaryGroupingSize) - __copy(\._multiplier) - __copy(\._formatWidth) - __copy(\._paddingCharacter) - __copy(\._paddingPosition) - __copy(\._roundingMode) - __copy(\._roundingIncrement) - __copy(\._minimumIntegerDigits) - __copy(\._maximumIntegerDigits) - __copy(\._minimumFractionDigits) - __copy(\._maximumFractionDigits) - __copy(\._minimum) - __copy(\._maximum) - __copy(\._currencyGroupingSeparator) - __copy(\._lenient) - __copy(\._usesSignificantDigits) - __copy(\._minimumSignificantDigits) - __copy(\._maximumSignificantDigits) - __copy(\._partialStringValidationEnabled) - __copy(\._hasThousandSeparators) - __copy(\._thousandSeparator) - __copy(\._localizesFormat) - __copy(\._positiveFormat) - __copy(\._negativeFormat) - __copy(\._attributedStringForZero) - __copy(\._attributedStringForNotANumber) - __copy(\._roundingBehavior) - - return copied - } - - // this is for NSUnitFormatter - - open var formattingContext: Context = .unknown // default is NSFormattingContextUnknown - - @available(*, unavailable, renamed: "number(from:)") - func getObjectValue(_ obj: UnsafeMutablePointer?, - for string: String, - range rangep: UnsafeMutablePointer?) throws { - NSUnsupported() - } - - open override func string(for obj: Any) -> String? { - //we need to allow Swift's numeric types here - Int, Double et al. - guard let number = __SwiftValue.store(obj) as? NSNumber else { return nil } - return string(from: number) - } - - // Even though NumberFormatter responds to the usual Formatter methods, - // here are some convenience methods which are a little more obvious. - open func string(from number: NSNumber) -> String? { - return CFNumberFormatterCreateStringWithNumber(kCFAllocatorSystemDefault, _cfFormatter, number._cfObject)._swiftObject - } - - open func number(from string: String) -> NSNumber? { - var range = CFRange(location: 0, length: string.length) - let number = withUnsafeMutablePointer(to: &range) { (rangePointer: UnsafeMutablePointer) -> NSNumber? in - - let parseOption = allowsFloats ? 0 : CFNumberFormatterOptionFlags.parseIntegersOnly.rawValue - let result = CFNumberFormatterCreateNumberFromString(kCFAllocatorSystemDefault, _cfFormatter, string._cfObject, rangePointer, parseOption) - - return result?._nsObject + + private func _setFormatterAttributes(_ formatter: CFNumberFormatter) { + if numberStyle == .currency { + // Prefer currencySymbol, then currencyCode then locale.currencySymbol + if let symbol = _currencySymbol { + _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterCurrencySymbol, value: symbol._cfObject) + } else if let code = _currencyCode, code.count == 3 { + _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterCurrencyCode, value: code._cfObject) + } else { + _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterCurrencySymbol, value: locale.currencySymbol?._cfObject) + } + } + if numberStyle == .currencyISOCode { + let code = _currencyCode ?? _currencySymbol ?? locale.currencyCode ?? locale.currencySymbol + _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterCurrencyCode, value: code?._cfObject) + } + _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterDecimalSeparator, value: _decimalSeparator?._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterCurrencyDecimalSeparator, value: _currencyDecimalSeparator?._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterAlwaysShowDecimalSeparator, value: _alwaysShowsDecimalSeparator._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterGroupingSeparator, value: _groupingSeparator?._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterUseGroupingSeparator, value: usesGroupingSeparator._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterPercentSymbol, value: _percentSymbol?._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterZeroSymbol, value: _zeroSymbol?._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterNaNSymbol, value: _notANumberSymbol?._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterInfinitySymbol, value: _positiveInfinitySymbol._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterMinusSign, value: _minusSign?._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterPlusSign, value: _plusSign?._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterExponentSymbol, value: _exponentSymbol?._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterMinIntegerDigits, value: _minimumIntegerDigits?._bridgeToObjectiveC()._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterMaxIntegerDigits, value: _maximumIntegerDigits?._bridgeToObjectiveC()._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterMinFractionDigits, value: _minimumFractionDigits?._bridgeToObjectiveC()._cfObject) + if minimumFractionDigits <= 0 { + _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterMaxFractionDigits, value: maximumFractionDigits._bridgeToObjectiveC()._cfObject) + } + _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterGroupingSize, value: groupingSize._bridgeToObjectiveC()._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterSecondaryGroupingSize, value: _secondaryGroupingSize._bridgeToObjectiveC()._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterRoundingMode, value: _roundingMode.rawValue._bridgeToObjectiveC()._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterRoundingIncrement, value: _roundingIncrement?._cfObject) + + _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterFormatWidth, value: _formatWidth?._bridgeToObjectiveC()._cfObject) + if self.formatWidth > 0 { + _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterPaddingCharacter, value: _paddingCharacter?._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterPaddingPosition, value: _paddingPosition.rawValue._bridgeToObjectiveC()._cfObject) + } else { + _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterPaddingCharacter, value: ""._cfObject) + } + _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterMultiplier, value: multiplier?._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterPositivePrefix, value: _positivePrefix?._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterPositiveSuffix, value: _positiveSuffix?._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterNegativePrefix, value: _negativePrefix?._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterNegativeSuffix, value: _negativeSuffix?._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterPerMillSymbol, value: _percentSymbol?._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterInternationalCurrencySymbol, value: _internationalCurrencySymbol?._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterCurrencyGroupingSeparator, value: _currencyGroupingSeparator?._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterIsLenient, value: _lenient._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterUseSignificantDigits, value: usesSignificantDigits._cfObject) + if usesSignificantDigits { + _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterMinSignificantDigits, value: minimumSignificantDigits._bridgeToObjectiveC()._cfObject) + _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterMaxSignificantDigits, value: maximumSignificantDigits._bridgeToObjectiveC()._cfObject) + } } - return number - } - - open class func localizedString(from num: NSNumber, number nstyle: Style) -> String { - let numberFormatter = NumberFormatter() - numberFormatter.numberStyle = nstyle - return numberFormatter.string(for: num)! - } - - private func _reset() { - _formatter.withLock { - $0 = nil + + private func _setFormatterAttribute(_ formatter: CFNumberFormatter, attributeName: CFString, value: AnyObject?) { + if let value = value { + CFNumberFormatterSetProperty(formatter, attributeName, value) + } + } + + private func _getFormatterAttribute(attributeName: CFString) -> String? { + // This will only be a constant CFString + nonisolated(unsafe) let nonisolatedAttributeName = attributeName + return CFNumberFormatterCopyProperty(formatter(), nonisolatedAttributeName) as? String } - } - private final func _setFormatterAttributes(_ formatter: CFNumberFormatter) { - if numberStyle == .currency { - // Prefer currencySymbol, then currencyCode then locale.currencySymbol - if let symbol = _currencySymbol { - _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterCurrencySymbol, value: symbol._cfObject) - } else if let code = _currencyCode, code.count == 3 { - _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterCurrencyCode, value: code._cfObject) - } else { - _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterCurrencySymbol, value: locale.currencySymbol?._cfObject) + private func getFormatterComponents() -> (String?, String?) { + guard let format = CFNumberFormatterGetFormat(formatter())?._swiftObject else { + return (nil, nil) + } + let components = format.components(separatedBy: ";") + let positive = _positiveFormat ?? components.first ?? "#" + let negative = _negativeFormat ?? components.last ?? "#" + return (positive, negative) + } + + // MARK: - Properties + + private var _numberStyle: Style = .none + var numberStyle: Style { + get { + return _numberStyle + } + + set { + _reset() + _numberStyle = newValue } - } - if numberStyle == .currencyISOCode { - let code = _currencyCode ?? _currencySymbol ?? locale.currencyCode ?? locale.currencySymbol - _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterCurrencyCode, value: code?._cfObject) } - _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterDecimalSeparator, value: _decimalSeparator?._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterCurrencyDecimalSeparator, value: _currencyDecimalSeparator?._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterAlwaysShowDecimalSeparator, value: _alwaysShowsDecimalSeparator._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterGroupingSeparator, value: _groupingSeparator?._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterUseGroupingSeparator, value: usesGroupingSeparator._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterPercentSymbol, value: _percentSymbol?._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterZeroSymbol, value: _zeroSymbol?._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterNaNSymbol, value: _notANumberSymbol?._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterInfinitySymbol, value: _positiveInfinitySymbol._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterMinusSign, value: _minusSign?._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterPlusSign, value: _plusSign?._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterExponentSymbol, value: _exponentSymbol?._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterMinIntegerDigits, value: _minimumIntegerDigits?._bridgeToObjectiveC()._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterMaxIntegerDigits, value: _maximumIntegerDigits?._bridgeToObjectiveC()._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterMinFractionDigits, value: _minimumFractionDigits?._bridgeToObjectiveC()._cfObject) - if minimumFractionDigits <= 0 { - _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterMaxFractionDigits, value: maximumFractionDigits._bridgeToObjectiveC()._cfObject) + + private var _locale: Locale = Locale.current + var locale: Locale! { + get { + return _locale + } + set { + _reset() + _locale = newValue + } } - _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterGroupingSize, value: groupingSize._bridgeToObjectiveC()._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterSecondaryGroupingSize, value: _secondaryGroupingSize._bridgeToObjectiveC()._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterRoundingMode, value: _roundingMode.rawValue._bridgeToObjectiveC()._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterRoundingIncrement, value: _roundingIncrement?._cfObject) - - _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterFormatWidth, value: _formatWidth?._bridgeToObjectiveC()._cfObject) - if self.formatWidth > 0 { - _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterPaddingCharacter, value: _paddingCharacter?._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterPaddingPosition, value: _paddingPosition.rawValue._bridgeToObjectiveC()._cfObject) - } else { - _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterPaddingCharacter, value: ""._cfObject) + + private var _generatesDecimalNumbers: Bool = false + var generatesDecimalNumbers: Bool { + get { + return _generatesDecimalNumbers + } + set { + _reset() + _generatesDecimalNumbers = newValue + } } - _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterMultiplier, value: multiplier?._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterPositivePrefix, value: _positivePrefix?._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterPositiveSuffix, value: _positiveSuffix?._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterNegativePrefix, value: _negativePrefix?._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterNegativeSuffix, value: _negativeSuffix?._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterPerMillSymbol, value: _percentSymbol?._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterInternationalCurrencySymbol, value: _internationalCurrencySymbol?._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterCurrencyGroupingSeparator, value: _currencyGroupingSeparator?._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterIsLenient, value: _lenient._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterUseSignificantDigits, value: usesSignificantDigits._cfObject) - if usesSignificantDigits { - _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterMinSignificantDigits, value: minimumSignificantDigits._bridgeToObjectiveC()._cfObject) - _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterMaxSignificantDigits, value: maximumSignificantDigits._bridgeToObjectiveC()._cfObject) + + private var _textAttributesForNegativeValues: [String : (Any & Sendable)]? + var textAttributesForNegativeValues: [String : (Any & Sendable)]? { + get { + return _textAttributesForNegativeValues + } + set { + _reset() + _textAttributesForNegativeValues = newValue + } } - } - - private final func _setFormatterAttribute(_ formatter: CFNumberFormatter, attributeName: CFString, value: AnyObject?) { - if let value = value { - CFNumberFormatterSetProperty(formatter, attributeName, value) + + private var _textAttributesForPositiveValues: [String : (Any & Sendable)]? + var textAttributesForPositiveValues: [String : (Any & Sendable)]? { + get { + return _textAttributesForPositiveValues + } + set { + _reset() + _textAttributesForPositiveValues = newValue + } } - } - - private final func _getFormatterAttribute(_ formatter: CFNumberFormatter, attributeName: CFString) -> String? { - return CFNumberFormatterCopyProperty(formatter, attributeName) as? String - } - - // Attributes of a NumberFormatter. Many attributes have default values but if they are set by the caller - // the new value needs to be retained even if the .numberStyle is changed. Attributes are backed by an optional - // to indicate to use the default value (if nil) or the caller-supplied value (if not nil). - private func defaultMinimumIntegerDigits() -> Int { - switch numberStyle { - case .ordinal, .spellOut, .currencyPlural: - return 0 - - case .none, .currency, .currencyISOCode, .currencyAccounting, .decimal, .percent, .scientific: - return 1 + + private var _allowsFloats: Bool = true + var allowsFloats: Bool { + get { + return _allowsFloats + } + set { + _reset() + _allowsFloats = newValue + } } - } - - private func defaultMaximumIntegerDigits() -> Int { - switch numberStyle { - case .none: - return 42 - - case .ordinal, .spellOut, .currencyPlural: - return 0 - - case .currency, .currencyISOCode, .currencyAccounting, .decimal, .percent: - return 2_000_000_000 - - case .scientific: - return 1 + + private var _decimalSeparator: String! + var decimalSeparator: String! { + get { + return _decimalSeparator ?? _getFormatterAttribute(attributeName: kCFNumberFormatterDecimalSeparator) + } + set { + _reset() + _decimalSeparator = newValue + } + } + + private var _alwaysShowsDecimalSeparator: Bool = false + var alwaysShowsDecimalSeparator: Bool { + get { + return _alwaysShowsDecimalSeparator + } + set { + _reset() + _alwaysShowsDecimalSeparator = newValue + } + } + + private var _currencyDecimalSeparator: String! + var currencyDecimalSeparator: String! { + get { + return _currencyDecimalSeparator ?? _getFormatterAttribute(attributeName: kCFNumberFormatterCurrencyDecimalSeparator) + } + set { + _reset() + _currencyDecimalSeparator = newValue + } + } + + private var _usesGroupingSeparator: Bool? + var usesGroupingSeparator: Bool { + get { + return _usesGroupingSeparator ?? defaultUsesGroupingSeparator() + } + set { + _reset() + _usesGroupingSeparator = newValue + } + } + + private var _groupingSeparator: String! + var groupingSeparator: String! { + get { + return _groupingSeparator ?? _getFormatterAttribute(attributeName: kCFNumberFormatterGroupingSeparator) + } + set { + _reset() + _groupingSeparator = newValue + } + } + + private var _zeroSymbol: String? + var zeroSymbol: String? { + get { + return _zeroSymbol + } + set { + _reset() + _zeroSymbol = newValue + } + } + + private var _textAttributesForZero: [String : (Any & Sendable)]? + var textAttributesForZero: [String : (Any & Sendable)]? { + get { + return _textAttributesForZero + } + set { + _reset() + _textAttributesForZero = newValue + } + } + + private var _nilSymbol: String = "" + var nilSymbol: String { + get { + return _nilSymbol + } + set { + _reset() + _nilSymbol = newValue + } + } + + private var _textAttributesForNil: [String : (Any & Sendable)]? + var textAttributesForNil: [String : (Any & Sendable)]? { + get { + return _textAttributesForNil + } + set { + _reset() + _textAttributesForNil = newValue + } + } + + private var _notANumberSymbol: String! + var notANumberSymbol: String! { + get { + return _notANumberSymbol ?? _getFormatterAttribute(attributeName: kCFNumberFormatterNaNSymbol) + } + set { + _reset() + _notANumberSymbol = newValue + } + } + + private var _textAttributesForNotANumber: [String : (Any & Sendable)]? + var textAttributesForNotANumber: [String : (Any & Sendable)]? { + get { + return _textAttributesForNotANumber + } + set { + _reset() + _textAttributesForNotANumber = newValue + } + } + + private var _positiveInfinitySymbol: String = "+∞" + var positiveInfinitySymbol: String { + get { + return _positiveInfinitySymbol + } + set { + _reset() + _positiveInfinitySymbol = newValue + } + } + + private var _textAttributesForPositiveInfinity: [String : (Any & Sendable)]? + var textAttributesForPositiveInfinity: [String : (Any & Sendable)]? { + get { + return _textAttributesForPositiveInfinity + } + set { + _reset() + _textAttributesForPositiveInfinity = newValue + } + } + + private var _negativeInfinitySymbol: String = "-∞" + var negativeInfinitySymbol: String { + get { + return _negativeInfinitySymbol + } + set { + _reset() + _negativeInfinitySymbol = newValue + } + } + + private var _textAttributesForNegativeInfinity: [String : (Any & Sendable)]? + var textAttributesForNegativeInfinity: [String : (Any & Sendable)]? { + get { + return _textAttributesForNegativeInfinity + } + set { + _reset() + _textAttributesForNegativeInfinity = newValue + } + } + + private var _positivePrefix: String! + var positivePrefix: String! { + get { + return _positivePrefix ?? _getFormatterAttribute(attributeName: kCFNumberFormatterPositivePrefix) + } + set { + _reset() + _positivePrefix = newValue + } + } + + private var _positiveSuffix: String! + var positiveSuffix: String! { + get { + return _positiveSuffix ?? _getFormatterAttribute(attributeName: kCFNumberFormatterPositiveSuffix) + } + set { + _reset() + _positiveSuffix = newValue + } + } + + private var _negativePrefix: String! + var negativePrefix: String! { + get { + return _negativePrefix ?? _getFormatterAttribute(attributeName: kCFNumberFormatterNegativePrefix) + } + set { + _reset() + _negativePrefix = newValue + } + } + + private var _negativeSuffix: String! + var negativeSuffix: String! { + get { + return _negativeSuffix ?? _getFormatterAttribute(attributeName: kCFNumberFormatterNegativeSuffix) + } + set { + _reset() + _negativeSuffix = newValue + } + } + + private var _currencyCode: String! + var currencyCode: String! { + get { + return _currencyCode ?? _getFormatterAttribute(attributeName: kCFNumberFormatterCurrencyCode) + } + set { + _reset() + _currencyCode = newValue + } + } + + private var _currencySymbol: String! + var currencySymbol: String! { + get { + return _currencySymbol ?? _getFormatterAttribute(attributeName: kCFNumberFormatterCurrencySymbol) + } + set { + _reset() + _currencySymbol = newValue + } + } + + private var _internationalCurrencySymbol: String! + var internationalCurrencySymbol: String! { + get { + return _internationalCurrencySymbol ?? _getFormatterAttribute(attributeName: kCFNumberFormatterInternationalCurrencySymbol) + } + set { + _reset() + _internationalCurrencySymbol = newValue + } + } + + private var _percentSymbol: String! + var percentSymbol: String! { + get { + return _percentSymbol ?? _getFormatterAttribute(attributeName: kCFNumberFormatterPercentSymbol) ?? "%" + } + set { + _reset() + _percentSymbol = newValue + } + } + + private var _perMillSymbol: String! + var perMillSymbol: String! { + get { + return _perMillSymbol ?? _getFormatterAttribute(attributeName: kCFNumberFormatterPerMillSymbol) + } + set { + _reset() + _perMillSymbol = newValue + } + } + + private var _minusSign: String! + var minusSign: String! { + get { + return _minusSign ?? _getFormatterAttribute(attributeName: kCFNumberFormatterMinusSign) + } + set { + _reset() + _minusSign = newValue + } + } + + private var _plusSign: String! + var plusSign: String! { + get { + return _plusSign ?? _getFormatterAttribute(attributeName: kCFNumberFormatterPlusSign) + } + set { + _reset() + _plusSign = newValue + } + } + + private var _exponentSymbol: String! + var exponentSymbol: String! { + get { + return _exponentSymbol ?? _getFormatterAttribute(attributeName: kCFNumberFormatterExponentSymbol) + } + set { + _reset() + _exponentSymbol = newValue + } + } + + private var _groupingSize: Int? + var groupingSize: Int { + get { + return _groupingSize ?? defaultGroupingSize() + } + set { + _reset() + _groupingSize = newValue + } + } + + private var _secondaryGroupingSize: Int = 0 + var secondaryGroupingSize: Int { + get { + return _secondaryGroupingSize + } + set { + _reset() + _secondaryGroupingSize = newValue + } + } + + private var _multiplier: NSNumber? + var multiplier: NSNumber? { + get { + return _multiplier ?? defaultMultiplier() + } + set { + _reset() + _multiplier = newValue + } + } + + private var _formatWidth: Int? + var formatWidth: Int { + get { + return _formatWidth ?? defaultFormatWidth() + } + set { + _reset() + _formatWidth = newValue + } + } + + private var _paddingCharacter: String! = " " + var paddingCharacter: String! { + get { + return _paddingCharacter ?? _getFormatterAttribute(attributeName: kCFNumberFormatterPaddingCharacter) + } + set { + _reset() + _paddingCharacter = newValue + } + } + + private var _paddingPosition: PadPosition = .beforePrefix + var paddingPosition: PadPosition { + get { + return _paddingPosition + } + set { + _reset() + _paddingPosition = newValue + } + } + + private var _roundingMode: RoundingMode = .halfEven + var roundingMode: RoundingMode { + get { + return _roundingMode + } + set { + _reset() + _roundingMode = newValue + } + } + + private var _roundingIncrement: NSNumber! = 0 + var roundingIncrement: NSNumber! { + get { + return _roundingIncrement + } + set { + _reset() + _roundingIncrement = newValue + } + } + + // Use an optional for _minimumIntegerDigits to track if the value is + // set BEFORE the .numberStyle is changed. This allows preserving a setting + // of 0. + private var _minimumIntegerDigits: Int? + var minimumIntegerDigits: Int { + get { + return _minimumIntegerDigits ?? defaultMinimumIntegerDigits() + } + set { + _reset() + _minimumIntegerDigits = newValue + } + } + + private var _maximumIntegerDigits: Int? + var maximumIntegerDigits: Int { + get { + return _maximumIntegerDigits ?? defaultMaximumIntegerDigits() + } + set { + _reset() + _maximumIntegerDigits = newValue + } + } + + private var _minimumFractionDigits: Int? + var minimumFractionDigits: Int { + get { + return _minimumFractionDigits ?? defaultMinimumFractionDigits() + } + set { + _reset() + _minimumFractionDigits = newValue + } + } + + private var _maximumFractionDigits: Int? + var maximumFractionDigits: Int { + get { + return _maximumFractionDigits ?? defaultMaximumFractionDigits() + } + set { + _reset() + _maximumFractionDigits = newValue + } + } + + private var _minimum: NSNumber? + var minimum: NSNumber? { + get { + return _minimum + } + set { + _reset() + _minimum = newValue + } + } + + private var _maximum: NSNumber? + var maximum: NSNumber? { + get { + return _maximum + } + set { + _reset() + _maximum = newValue + } + } + + private var _currencyGroupingSeparator: String! + var currencyGroupingSeparator: String! { + get { + return _currencyGroupingSeparator ?? _getFormatterAttribute(attributeName: kCFNumberFormatterCurrencyGroupingSeparator) + } + set { + _reset() + _currencyGroupingSeparator = newValue + } + } + + private var _lenient: Bool = false + var isLenient: Bool { + get { + return _lenient + } + set { + _reset() + _lenient = newValue + } + } + + private var _usesSignificantDigits: Bool? + var usesSignificantDigits: Bool { + get { + return _usesSignificantDigits ?? false + } + set { + _reset() + _usesSignificantDigits = newValue + } + } + + private var _minimumSignificantDigits: Int? + var minimumSignificantDigits: Int { + get { + return _minimumSignificantDigits ?? defaultMinimumSignificantDigits() + } + set { + _reset() + _usesSignificantDigits = true + _minimumSignificantDigits = newValue + if _maximumSignificantDigits == nil && newValue > defaultMinimumSignificantDigits() { + _maximumSignificantDigits = (newValue < 1000) ? 999 : newValue + } + } + } + + private var _maximumSignificantDigits: Int? + var maximumSignificantDigits: Int { + get { + return _maximumSignificantDigits ?? defaultMaximumSignificantDigits() + } + set { + _reset() + _usesSignificantDigits = true + _maximumSignificantDigits = newValue + } + } + + private var _partialStringValidationEnabled: Bool = false + var isPartialStringValidationEnabled: Bool { + get { + return _partialStringValidationEnabled + } + set { + _reset() + _partialStringValidationEnabled = newValue + } + } + + private var _hasThousandSeparators: Bool = false + var hasThousandSeparators: Bool { + get { + return _hasThousandSeparators + } + set { + _reset() + _hasThousandSeparators = newValue + } + } + + private var _thousandSeparator: String! + var thousandSeparator: String! { + get { + return _thousandSeparator + } + set { + _reset() + _thousandSeparator = newValue + } + } + + private var _localizesFormat: Bool = true + var localizesFormat: Bool { + get { + return _localizesFormat + } + set { + _reset() + _localizesFormat = newValue + } + } + + private var _positiveFormat: String! + var positiveFormat: String! { + get { + return getFormatterComponents().0 + } + set { + _reset() + _positiveFormat = newValue + } } - } - private func defaultMinimumFractionDigits() -> Int { - switch numberStyle { - case .none, .ordinal, .spellOut, .currencyPlural, .decimal, .percent, .scientific: - return 0 + private var _negativeFormat: String! + var negativeFormat: String! { + get { + return getFormatterComponents().1 + } + set { + _reset() + _negativeFormat = newValue + } + } - case .currency, .currencyISOCode, .currencyAccounting: - return 2 + private var _roundingBehavior: NSDecimalNumberHandler = NSDecimalNumberHandler.default + var roundingBehavior: NSDecimalNumberHandler { + get { + return _roundingBehavior + } + set { + _reset() + _roundingBehavior = newValue + } + } + + private func getZeroFormat() -> String { + return string(from: 0) ?? "0" } - } - private func defaultMaximumFractionDigits() -> Int { - switch numberStyle { - case .none, .ordinal, .spellOut, .currencyPlural, .percent, .scientific: - return 0 + var format: String { + get { + let (p, n) = getFormatterComponents() + let z = _zeroSymbol ?? getZeroFormat() + return "\(p ?? "(null)");\(z);\(n ?? "(null)")" + } + set { + // Special case empty string + if newValue == "" { + _positiveFormat = "" + _negativeFormat = "-" + _zeroSymbol = "0" + _reset() + } else { + let components = newValue.components(separatedBy: ";") + let count = components.count + guard count <= 3 else { return } + _reset() + + _positiveFormat = components.first ?? "" + if count == 1 { + _negativeFormat = "-\(_positiveFormat ?? "")" + } + else if count == 2 { + _negativeFormat = components[1] + _zeroSymbol = getZeroFormat() + } + else if count == 3 { + _zeroSymbol = components[1] + _negativeFormat = components[2] + } - case .currency, .currencyISOCode, .currencyAccounting: - return 2 + if _negativeFormat == nil { + _negativeFormat = getFormatterComponents().1 + } - case .decimal: - return 3 + if _zeroSymbol == nil { + _zeroSymbol = getZeroFormat() + } + } + } } - } - - private func defaultMinimumSignificantDigits() -> Int { - switch numberStyle { - case .ordinal, .spellOut, .currencyPlural: - return 0 - case .currency, .none, .currencyISOCode, .currencyAccounting, .decimal, .percent, .scientific: - return -1 + // this is for NSUnitFormatter + + var formattingContext: Context = .unknown // default is NSFormattingContextUnknown + + func string(for obj: Any) -> String? { + //we need to allow Swift's numeric types here - Int, Double et al. + guard let number = __SwiftValue.store(obj) as? NSNumber else { return nil } + return string(from: number) + } + + // Even though NumberFormatter responds to the usual Formatter methods, + // here are some convenience methods which are a little more obvious. + func string(from number: NSNumber) -> String? { + return CFNumberFormatterCreateStringWithNumber(kCFAllocatorSystemDefault, formatter(), number._cfObject)._swiftObject + } + + func number(from string: String) -> NSNumber? { + var range = CFRange(location: 0, length: string.length) + let number = withUnsafeMutablePointer(to: &range) { (rangePointer: UnsafeMutablePointer) -> NSNumber? in + + let parseOption = allowsFloats ? 0 : CFNumberFormatterOptionFlags.parseIntegersOnly.rawValue + let result = CFNumberFormatterCreateNumberFromString(kCFAllocatorSystemDefault, formatter(), string._cfObject, rangePointer, parseOption) + + return result?._nsObject + } + return number + } + + // Attributes of a NumberFormatter. Many attributes have default values but if they are set by the caller + // the new value needs to be retained even if the .numberStyle is changed. Attributes are backed by an optional + // to indicate to use the default value (if nil) or the caller-supplied value (if not nil). + private func defaultMinimumIntegerDigits() -> Int { + switch numberStyle { + case .ordinal, .spellOut, .currencyPlural: + return 0 + + case .none, .currency, .currencyISOCode, .currencyAccounting, .decimal, .percent, .scientific: + return 1 + } } - } - - private func defaultMaximumSignificantDigits() -> Int { - switch numberStyle { - case .none, .currency, .currencyISOCode, .currencyAccounting, .decimal, .percent, .scientific: - return -1 - - case .ordinal, .spellOut, .currencyPlural: - return 0 + + private func defaultMaximumIntegerDigits() -> Int { + switch numberStyle { + case .none: + return 42 + + case .ordinal, .spellOut, .currencyPlural: + return 0 + + case .currency, .currencyISOCode, .currencyAccounting, .decimal, .percent: + return 2_000_000_000 + + case .scientific: + return 1 + } } - } - - private func defaultUsesGroupingSeparator() -> Bool { - switch numberStyle { - case .none, .scientific, .spellOut, .ordinal, .currencyPlural: - return false - - case .decimal, .currency, .percent, .currencyAccounting, .currencyISOCode: - return true + + private func defaultMinimumFractionDigits() -> Int { + switch numberStyle { + case .none, .ordinal, .spellOut, .currencyPlural, .decimal, .percent, .scientific: + return 0 + + case .currency, .currencyISOCode, .currencyAccounting: + return 2 + } } - } - - private func defaultGroupingSize() -> Int { - switch numberStyle { - case .none, .ordinal, .spellOut, .currencyPlural, .scientific: - return 0 - - case .currency, .currencyISOCode, .currencyAccounting, .decimal, .percent: - return 3 + + private func defaultMaximumFractionDigits() -> Int { + switch numberStyle { + case .none, .ordinal, .spellOut, .currencyPlural, .percent, .scientific: + return 0 + + case .currency, .currencyISOCode, .currencyAccounting: + return 2 + + case .decimal: + return 3 + } } - } - - private func defaultMultiplier() -> NSNumber? { - switch numberStyle { - case .percent: return NSNumber(100) - default: return nil + + private func defaultMinimumSignificantDigits() -> Int { + switch numberStyle { + case .ordinal, .spellOut, .currencyPlural: + return 0 + + case .currency, .none, .currencyISOCode, .currencyAccounting, .decimal, .percent, .scientific: + return -1 + } } - } - - private func defaultFormatWidth() -> Int { - switch numberStyle { - case .ordinal, .spellOut, .currencyPlural: - return 0 - - case .none, .decimal, .currency, .percent, .scientific, .currencyISOCode, .currencyAccounting: - return -1 + + private func defaultMaximumSignificantDigits() -> Int { + switch numberStyle { + case .none, .currency, .currencyISOCode, .currencyAccounting, .decimal, .percent, .scientific: + return -1 + + case .ordinal, .spellOut, .currencyPlural: + return 0 + } + } + + private func defaultUsesGroupingSeparator() -> Bool { + switch numberStyle { + case .none, .scientific, .spellOut, .ordinal, .currencyPlural: + return false + + case .decimal, .currency, .percent, .currencyAccounting, .currencyISOCode: + return true + } + } + + private func defaultGroupingSize() -> Int { + switch numberStyle { + case .none, .ordinal, .spellOut, .currencyPlural, .scientific: + return 0 + + case .currency, .currencyISOCode, .currencyAccounting, .decimal, .percent: + return 3 + } + } + + private func defaultMultiplier() -> NSNumber? { + switch numberStyle { + case .percent: return NSNumber(100) + default: return nil + } + } + + private func defaultFormatWidth() -> Int { + switch numberStyle { + case .ordinal, .spellOut, .currencyPlural: + return 0 + + case .none, .decimal, .currency, .percent, .scientific, .currencyISOCode, .currencyAccounting: + return -1 + } } } + + // MARK: - + + open func string(from number: NSNumber) -> String? { + _lock.withLock { $0.string(from: number) } + } - private var _numberStyle: Style = .none + open func number(from string: String) -> NSNumber? { + _lock.withLock { $0.number(from: string) } + } + + open override func string(for obj: Any) -> String? { + //we need to allow Swift's numeric types here - Int, Double et al. + guard let number = __SwiftValue.store(obj) as? NSNumber else { return nil } + return string(from: number) + } + open var numberStyle: Style { - get { - return _numberStyle - } - - set { - _reset() - _numberStyle = newValue - } + get { _lock.withLock { $0.numberStyle } } + set { _lock.withLock { $0.numberStyle = newValue } } } - private var _locale: Locale = Locale.current - /*@NSCopying*/ open var locale: Locale! { - get { - return _locale - } - set { - _reset() - _locale = newValue - } + open var locale: Locale! { + get { _lock.withLock { $0.locale } } + set { _lock.withLock { $0.locale = newValue } } } - private var _generatesDecimalNumbers: Bool = false open var generatesDecimalNumbers: Bool { - get { - return _generatesDecimalNumbers - } - set { - _reset() - _generatesDecimalNumbers = newValue - } + get { _lock.withLock { $0.generatesDecimalNumbers } } + set { _lock.withLock { $0.generatesDecimalNumbers = newValue } } } - private var _textAttributesForNegativeValues: [String : Any]? - open var textAttributesForNegativeValues: [String : Any]? { - get { - return _textAttributesForNegativeValues - } - set { - _reset() - _textAttributesForNegativeValues = newValue - } + open var textAttributesForNegativeValues: [String : (Any & Sendable)]? { + get { _lock.withLock { $0.textAttributesForNegativeValues } } + set { _lock.withLock { $0.textAttributesForNegativeValues = newValue } } } - private var _textAttributesForPositiveValues: [String : Any]? - open var textAttributesForPositiveValues: [String : Any]? { - get { - return _textAttributesForPositiveValues - } - set { - _reset() - _textAttributesForPositiveValues = newValue - } + open var textAttributesForPositiveValues: [String : (Any & Sendable)]? { + get { _lock.withLock { $0.textAttributesForPositiveValues } } + set { _lock.withLock { $0.textAttributesForPositiveValues = newValue } } } - private var _allowsFloats: Bool = true open var allowsFloats: Bool { - get { - return _allowsFloats - } - set { - _reset() - _allowsFloats = newValue - } + get { _lock.withLock { $0.allowsFloats } } + set { _lock.withLock { $0.allowsFloats = newValue } } } - private var _decimalSeparator: String! open var decimalSeparator: String! { - get { - return _decimalSeparator ?? _getFormatterAttribute(_cfFormatter, attributeName: kCFNumberFormatterDecimalSeparator) - } - set { - _reset() - _decimalSeparator = newValue - } + get { _lock.withLock { $0.decimalSeparator } } + set { _lock.withLock { $0.decimalSeparator = newValue } } } - private var _alwaysShowsDecimalSeparator: Bool = false open var alwaysShowsDecimalSeparator: Bool { - get { - return _alwaysShowsDecimalSeparator - } - set { - _reset() - _alwaysShowsDecimalSeparator = newValue - } + get { _lock.withLock { $0.alwaysShowsDecimalSeparator } } + set { _lock.withLock { $0.alwaysShowsDecimalSeparator = newValue } } } - private var _currencyDecimalSeparator: String! open var currencyDecimalSeparator: String! { - get { - return _currencyDecimalSeparator ?? _getFormatterAttribute(_cfFormatter, attributeName: kCFNumberFormatterCurrencyDecimalSeparator) - } - set { - _reset() - _currencyDecimalSeparator = newValue - } + get { _lock.withLock { $0.currencyDecimalSeparator } } + set { _lock.withLock { $0.currencyDecimalSeparator = newValue } } } - private var _usesGroupingSeparator: Bool? open var usesGroupingSeparator: Bool { - get { - return _usesGroupingSeparator ?? defaultUsesGroupingSeparator() - } - set { - _reset() - _usesGroupingSeparator = newValue - } + get { _lock.withLock { $0.usesGroupingSeparator } } + set { _lock.withLock { $0.usesGroupingSeparator = newValue } } } - private var _groupingSeparator: String! open var groupingSeparator: String! { - get { - return _groupingSeparator ?? _getFormatterAttribute(_cfFormatter, attributeName: kCFNumberFormatterGroupingSeparator) - } - set { - _reset() - _groupingSeparator = newValue - } + get { _lock.withLock { $0.groupingSeparator } } + set { _lock.withLock { $0.groupingSeparator = newValue } } } - private var _zeroSymbol: String? open var zeroSymbol: String? { - get { - return _zeroSymbol - } - set { - _reset() - _zeroSymbol = newValue - } + get { _lock.withLock { $0.zeroSymbol } } + set { _lock.withLock { $0.zeroSymbol = newValue } } } - private var _textAttributesForZero: [String : Any]? - open var textAttributesForZero: [String : Any]? { - get { - return _textAttributesForZero - } - set { - _reset() - _textAttributesForZero = newValue - } + open var textAttributesForZero: [String : (Any & Sendable)]? { + get { _lock.withLock { $0.textAttributesForZero } } + set { _lock.withLock { $0.textAttributesForZero = newValue } } } - private var _nilSymbol: String = "" open var nilSymbol: String { - get { - return _nilSymbol - } - set { - _reset() - _nilSymbol = newValue - } + get { _lock.withLock { $0.nilSymbol } } + set { _lock.withLock { $0.nilSymbol = newValue } } } - private var _textAttributesForNil: [String : Any]? - open var textAttributesForNil: [String : Any]? { - get { - return _textAttributesForNil - } - set { - _reset() - _textAttributesForNil = newValue - } + open var textAttributesForNil: [String : (Any & Sendable)]? { + get { _lock.withLock { $0.textAttributesForNil } } + set { _lock.withLock { $0.textAttributesForNil = newValue } } } - private var _notANumberSymbol: String! open var notANumberSymbol: String! { - get { - return _notANumberSymbol ?? _getFormatterAttribute(_cfFormatter, attributeName: kCFNumberFormatterNaNSymbol) - } - set { - _reset() - _notANumberSymbol = newValue - } + get { _lock.withLock { $0.notANumberSymbol } } + set { _lock.withLock { $0.notANumberSymbol = newValue } } } - private var _textAttributesForNotANumber: [String : Any]? - open var textAttributesForNotANumber: [String : Any]? { - get { - return _textAttributesForNotANumber - } - set { - _reset() - _textAttributesForNotANumber = newValue - } + open var textAttributesForNotANumber: [String : (Any & Sendable)]? { + get { _lock.withLock { $0.textAttributesForNotANumber } } + set { _lock.withLock { $0.textAttributesForNotANumber = newValue } } } - private var _positiveInfinitySymbol: String = "+∞" open var positiveInfinitySymbol: String { - get { - return _positiveInfinitySymbol - } - set { - _reset() - _positiveInfinitySymbol = newValue - } + get { _lock.withLock { $0.positiveInfinitySymbol } } + set { _lock.withLock { $0.positiveInfinitySymbol = newValue } } } - private var _textAttributesForPositiveInfinity: [String : Any]? - open var textAttributesForPositiveInfinity: [String : Any]? { - get { - return _textAttributesForPositiveInfinity - } - set { - _reset() - _textAttributesForPositiveInfinity = newValue - } + open var textAttributesForPositiveInfinity: [String : (Any & Sendable)]? { + get { _lock.withLock { $0.textAttributesForPositiveInfinity } } + set { _lock.withLock { $0.textAttributesForPositiveInfinity = newValue } } } - private var _negativeInfinitySymbol: String = "-∞" open var negativeInfinitySymbol: String { - get { - return _negativeInfinitySymbol - } - set { - _reset() - _negativeInfinitySymbol = newValue - } + get { _lock.withLock { $0.negativeInfinitySymbol } } + set { _lock.withLock { $0.negativeInfinitySymbol = newValue } } } - private var _textAttributesForNegativeInfinity: [String : Any]? - open var textAttributesForNegativeInfinity: [String : Any]? { - get { - return _textAttributesForNegativeInfinity - } - set { - _reset() - _textAttributesForNegativeInfinity = newValue - } + open var textAttributesForNegativeInfinity: [String : (Any & Sendable)]? { + get { _lock.withLock { $0.textAttributesForNegativeInfinity } } + set { _lock.withLock { $0.textAttributesForNegativeInfinity = newValue } } } - private var _positivePrefix: String! open var positivePrefix: String! { - get { - return _positivePrefix ?? _getFormatterAttribute(_cfFormatter, attributeName: kCFNumberFormatterPositivePrefix) - } - set { - _reset() - _positivePrefix = newValue - } + get { _lock.withLock { $0.positivePrefix } } + set { _lock.withLock { $0.positivePrefix = newValue } } } - private var _positiveSuffix: String! open var positiveSuffix: String! { - get { - return _positiveSuffix ?? _getFormatterAttribute(_cfFormatter, attributeName: kCFNumberFormatterPositiveSuffix) - } - set { - _reset() - _positiveSuffix = newValue - } + get { _lock.withLock { $0.positiveSuffix } } + set { _lock.withLock { $0.positiveSuffix = newValue } } } - private var _negativePrefix: String! open var negativePrefix: String! { - get { - return _negativePrefix ?? _getFormatterAttribute(_cfFormatter, attributeName: kCFNumberFormatterNegativePrefix) - } - set { - _reset() - _negativePrefix = newValue - } + get { _lock.withLock { $0.negativePrefix } } + set { _lock.withLock { $0.negativePrefix = newValue } } } - private var _negativeSuffix: String! open var negativeSuffix: String! { - get { - return _negativeSuffix ?? _getFormatterAttribute(_cfFormatter, attributeName: kCFNumberFormatterNegativeSuffix) - } - set { - _reset() - _negativeSuffix = newValue - } + get { _lock.withLock { $0.negativeSuffix } } + set { _lock.withLock { $0.negativeSuffix = newValue } } } - private var _currencyCode: String! open var currencyCode: String! { - get { - return _currencyCode ?? _getFormatterAttribute(_cfFormatter, attributeName: kCFNumberFormatterCurrencyCode) - } - set { - _reset() - _currencyCode = newValue - } + get { _lock.withLock { $0.currencyCode } } + set { _lock.withLock { $0.currencyCode = newValue } } } - private var _currencySymbol: String! open var currencySymbol: String! { - get { - return _currencySymbol ?? _getFormatterAttribute(_cfFormatter, attributeName: kCFNumberFormatterCurrencySymbol) - } - set { - _reset() - _currencySymbol = newValue - } + get { _lock.withLock { $0.currencySymbol } } + set { _lock.withLock { $0.currencySymbol = newValue } } } - private var _internationalCurrencySymbol: String! open var internationalCurrencySymbol: String! { - get { - return _internationalCurrencySymbol ?? _getFormatterAttribute(_cfFormatter, attributeName: kCFNumberFormatterInternationalCurrencySymbol) - } - set { - _reset() - _internationalCurrencySymbol = newValue - } + get { _lock.withLock { $0.internationalCurrencySymbol } } + set { _lock.withLock { $0.internationalCurrencySymbol = newValue } } } - private var _percentSymbol: String! open var percentSymbol: String! { - get { - return _percentSymbol ?? _getFormatterAttribute(_cfFormatter, attributeName: kCFNumberFormatterPercentSymbol) ?? "%" - } - set { - _reset() - _percentSymbol = newValue - } + get { _lock.withLock { $0.percentSymbol } } + set { _lock.withLock { $0.percentSymbol = newValue } } } - private var _perMillSymbol: String! open var perMillSymbol: String! { - get { - return _perMillSymbol ?? _getFormatterAttribute(_cfFormatter, attributeName: kCFNumberFormatterPerMillSymbol) - } - set { - _reset() - _perMillSymbol = newValue - } + get { _lock.withLock { $0.perMillSymbol } } + set { _lock.withLock { $0.perMillSymbol = newValue } } } - private var _minusSign: String! open var minusSign: String! { - get { - return _minusSign ?? _getFormatterAttribute(_cfFormatter, attributeName: kCFNumberFormatterMinusSign) - } - set { - _reset() - _minusSign = newValue - } + get { _lock.withLock { $0.minusSign } } + set { _lock.withLock { $0.minusSign = newValue } } } - private var _plusSign: String! open var plusSign: String! { - get { - return _plusSign ?? _getFormatterAttribute(_cfFormatter, attributeName: kCFNumberFormatterPlusSign) - } - set { - _reset() - _plusSign = newValue - } + get { _lock.withLock { $0.plusSign } } + set { _lock.withLock { $0.plusSign = newValue } } } - private var _exponentSymbol: String! open var exponentSymbol: String! { - get { - return _exponentSymbol ?? _getFormatterAttribute(_cfFormatter, attributeName: kCFNumberFormatterExponentSymbol) - } - set { - _reset() - _exponentSymbol = newValue - } + get { _lock.withLock { $0.exponentSymbol } } + set { _lock.withLock { $0.exponentSymbol = newValue } } } - private var _groupingSize: Int? open var groupingSize: Int { - get { - return _groupingSize ?? defaultGroupingSize() - } - set { - _reset() - _groupingSize = newValue - } + get { _lock.withLock { $0.groupingSize } } + set { _lock.withLock { $0.groupingSize = newValue } } } - private var _secondaryGroupingSize: Int = 0 open var secondaryGroupingSize: Int { - get { - return _secondaryGroupingSize - } - set { - _reset() - _secondaryGroupingSize = newValue - } + get { _lock.withLock { $0.secondaryGroupingSize } } + set { _lock.withLock { $0.secondaryGroupingSize = newValue } } } - private var _multiplier: NSNumber? - /*@NSCopying*/ open var multiplier: NSNumber? { - get { - return _multiplier ?? defaultMultiplier() - } - set { - _reset() - _multiplier = newValue - } + open var multiplier: NSNumber? { + get { _lock.withLock { $0.multiplier } } + set { _lock.withLock { $0.multiplier = newValue } } } - private var _formatWidth: Int? open var formatWidth: Int { - get { - return _formatWidth ?? defaultFormatWidth() - } - set { - _reset() - _formatWidth = newValue - } + get { _lock.withLock { $0.formatWidth } } + set { _lock.withLock { $0.formatWidth = newValue } } } - private var _paddingCharacter: String! = " " open var paddingCharacter: String! { - get { - return _paddingCharacter ?? _getFormatterAttribute(_cfFormatter, attributeName: kCFNumberFormatterPaddingCharacter) - } - set { - _reset() - _paddingCharacter = newValue - } + get { _lock.withLock { $0.paddingCharacter } } + set { _lock.withLock { $0.paddingCharacter = newValue } } } - private var _paddingPosition: PadPosition = .beforePrefix open var paddingPosition: PadPosition { - get { - return _paddingPosition - } - set { - _reset() - _paddingPosition = newValue - } + get { _lock.withLock { $0.paddingPosition } } + set { _lock.withLock { $0.paddingPosition = newValue } } } - private var _roundingMode: RoundingMode = .halfEven open var roundingMode: RoundingMode { - get { - return _roundingMode - } - set { - _reset() - _roundingMode = newValue - } + get { _lock.withLock { $0.roundingMode } } + set { _lock.withLock { $0.roundingMode = newValue } } } - private var _roundingIncrement: NSNumber! = 0 - /*@NSCopying*/ open var roundingIncrement: NSNumber! { - get { - return _roundingIncrement - } - set { - _reset() - _roundingIncrement = newValue - } + open var roundingIncrement: NSNumber! { + get { _lock.withLock { $0.roundingIncrement } } + set { _lock.withLock { $0.roundingIncrement = newValue } } } - // Use an optional for _minimumIntegerDigits to track if the value is - // set BEFORE the .numberStyle is changed. This allows preserving a setting - // of 0. - private var _minimumIntegerDigits: Int? open var minimumIntegerDigits: Int { - get { - return _minimumIntegerDigits ?? defaultMinimumIntegerDigits() - } - set { - _reset() - _minimumIntegerDigits = newValue - } + get { _lock.withLock { $0.minimumIntegerDigits } } + set { _lock.withLock { $0.minimumIntegerDigits = newValue } } } - private var _maximumIntegerDigits: Int? open var maximumIntegerDigits: Int { - get { - return _maximumIntegerDigits ?? defaultMaximumIntegerDigits() - } - set { - _reset() - _maximumIntegerDigits = newValue - } + get { _lock.withLock { $0.maximumIntegerDigits } } + set { _lock.withLock { $0.maximumIntegerDigits = newValue } } } - private var _minimumFractionDigits: Int? open var minimumFractionDigits: Int { - get { - return _minimumFractionDigits ?? defaultMinimumFractionDigits() - } - set { - _reset() - _minimumFractionDigits = newValue - } + get { _lock.withLock { $0.minimumFractionDigits } } + set { _lock.withLock { $0.minimumFractionDigits = newValue } } } - private var _maximumFractionDigits: Int? open var maximumFractionDigits: Int { - get { - return _maximumFractionDigits ?? defaultMaximumFractionDigits() - } - set { - _reset() - _maximumFractionDigits = newValue - } + get { _lock.withLock { $0.maximumFractionDigits } } + set { _lock.withLock { $0.maximumFractionDigits = newValue } } } - private var _minimum: NSNumber? - /*@NSCopying*/ open var minimum: NSNumber? { - get { - return _minimum - } - set { - _reset() - _minimum = newValue - } + open var minimum: NSNumber? { + get { _lock.withLock { $0.minimum } } + set { _lock.withLock { $0.minimum = newValue } } } - private var _maximum: NSNumber? - /*@NSCopying*/ open var maximum: NSNumber? { - get { - return _maximum - } - set { - _reset() - _maximum = newValue - } + open var maximum: NSNumber? { + get { _lock.withLock { $0.maximum } } + set { _lock.withLock { $0.maximum = newValue } } } - private var _currencyGroupingSeparator: String! open var currencyGroupingSeparator: String! { - get { - return _currencyGroupingSeparator ?? _getFormatterAttribute(_cfFormatter, attributeName: kCFNumberFormatterCurrencyGroupingSeparator) - } - set { - _reset() - _currencyGroupingSeparator = newValue - } + get { _lock.withLock { $0.currencyGroupingSeparator } } + set { _lock.withLock { $0.currencyGroupingSeparator = newValue } } } - private var _lenient: Bool = false open var isLenient: Bool { - get { - return _lenient - } - set { - _reset() - _lenient = newValue - } + get { _lock.withLock { $0.isLenient } } + set { _lock.withLock { $0.isLenient = newValue } } } - private var _usesSignificantDigits: Bool? open var usesSignificantDigits: Bool { - get { - return _usesSignificantDigits ?? false - } - set { - _reset() - _usesSignificantDigits = newValue - } + get { _lock.withLock { $0.usesSignificantDigits } } + set { _lock.withLock { $0.usesSignificantDigits = newValue } } } - private var _minimumSignificantDigits: Int? open var minimumSignificantDigits: Int { - get { - return _minimumSignificantDigits ?? defaultMinimumSignificantDigits() - } - set { - _reset() - _usesSignificantDigits = true - _minimumSignificantDigits = newValue - if _maximumSignificantDigits == nil && newValue > defaultMinimumSignificantDigits() { - _maximumSignificantDigits = (newValue < 1000) ? 999 : newValue - } - } + get { _lock.withLock { $0.minimumSignificantDigits } } + set { _lock.withLock { $0.minimumSignificantDigits = newValue } } } - private var _maximumSignificantDigits: Int? open var maximumSignificantDigits: Int { - get { - return _maximumSignificantDigits ?? defaultMaximumSignificantDigits() - } - set { - _reset() - _usesSignificantDigits = true - _maximumSignificantDigits = newValue - } + get { _lock.withLock { $0.maximumSignificantDigits } } + set { _lock.withLock { $0.maximumSignificantDigits = newValue } } } - private var _partialStringValidationEnabled: Bool = false open var isPartialStringValidationEnabled: Bool { - get { - return _partialStringValidationEnabled - } - set { - _reset() - _partialStringValidationEnabled = newValue - } + get { _lock.withLock { $0.isPartialStringValidationEnabled } } + set { _lock.withLock { $0.isPartialStringValidationEnabled = newValue } } } - private var _hasThousandSeparators: Bool = false open var hasThousandSeparators: Bool { - get { - return _hasThousandSeparators - } - set { - _reset() - _hasThousandSeparators = newValue - } + get { _lock.withLock { $0.hasThousandSeparators } } + set { _lock.withLock { $0.hasThousandSeparators = newValue } } } - private var _thousandSeparator: String! open var thousandSeparator: String! { - get { - return _thousandSeparator - } - set { - _reset() - _thousandSeparator = newValue - } + get { _lock.withLock { $0.thousandSeparator } } + set { _lock.withLock { $0.thousandSeparator = newValue } } } - private var _localizesFormat: Bool = true open var localizesFormat: Bool { - get { - return _localizesFormat - } - set { - _reset() - _localizesFormat = newValue - } - } - - private func getFormatterComponents() -> (String?, String?) { - guard let format = CFNumberFormatterGetFormat(_cfFormatter)?._swiftObject else { - return (nil, nil) - } - let components = format.components(separatedBy: ";") - let positive = _positiveFormat ?? components.first ?? "#" - let negative = _negativeFormat ?? components.last ?? "#" - return (positive, negative) - } - - private func getZeroFormat() -> String { - return string(from: 0) ?? "0" + get { _lock.withLock { $0.localizesFormat } } + set { _lock.withLock { $0.localizesFormat = newValue } } } open var format: String { - get { - let (p, n) = getFormatterComponents() - let z = _zeroSymbol ?? getZeroFormat() - return "\(p ?? "(null)");\(z);\(n ?? "(null)")" - } - set { - // Special case empty string - if newValue == "" { - _positiveFormat = "" - _negativeFormat = "-" - _zeroSymbol = "0" - _reset() - } else { - let components = newValue.components(separatedBy: ";") - let count = components.count - guard count <= 3 else { return } - _reset() - - _positiveFormat = components.first ?? "" - if count == 1 { - _negativeFormat = "-\(_positiveFormat ?? "")" - } - else if count == 2 { - _negativeFormat = components[1] - _zeroSymbol = getZeroFormat() - } - else if count == 3 { - _zeroSymbol = components[1] - _negativeFormat = components[2] - } - - if _negativeFormat == nil { - _negativeFormat = getFormatterComponents().1 - } - - if _zeroSymbol == nil { - _zeroSymbol = getZeroFormat() - } - } - } + get { _lock.withLock { $0.format } } + set { _lock.withLock { $0.format = newValue } } } - private var _positiveFormat: String! open var positiveFormat: String! { - get { - return getFormatterComponents().0 - } - set { - _reset() - _positiveFormat = newValue - } + get { _lock.withLock { $0.positiveFormat } } + set { _lock.withLock { $0.positiveFormat = newValue } } } - private var _negativeFormat: String! open var negativeFormat: String! { - get { - return getFormatterComponents().1 - } - set { - _reset() - _negativeFormat = newValue - } - } - - private var _attributedStringForZero: NSAttributedString = NSAttributedString(string: "0") - /*@NSCopying*/ open var attributedStringForZero: NSAttributedString { - get { - return _attributedStringForZero - } - set { - _reset() - _attributedStringForZero = newValue - } + get { _lock.withLock { $0.negativeFormat } } + set { _lock.withLock { $0.negativeFormat = newValue } } } - private var _attributedStringForNil: NSAttributedString = NSAttributedString(string: "") - /*@NSCopying*/ open var attributedStringForNil: NSAttributedString { - get { - return _attributedStringForNil - } - set { - _reset() - _attributedStringForNil = newValue - } - } - - private var _attributedStringForNotANumber: NSAttributedString = NSAttributedString(string: "NaN") - /*@NSCopying*/ open var attributedStringForNotANumber: NSAttributedString { - get { - return _attributedStringForNotANumber - } - set { - _reset() - _attributedStringForNotANumber = newValue - } - } - - private var _roundingBehavior: NSDecimalNumberHandler = NSDecimalNumberHandler.default - /*@NSCopying*/ open var roundingBehavior: NSDecimalNumberHandler { - get { - return _roundingBehavior - } - set { - _reset() - _roundingBehavior = newValue - } + open var roundingBehavior: NSDecimalNumberHandler { + get { _lock.withLock { $0.roundingBehavior } } + set { _lock.withLock { $0.roundingBehavior = newValue } } } } diff --git a/Sources/Foundation/Unit.swift b/Sources/Foundation/Unit.swift index 400651e78d..0a4505d561 100644 --- a/Sources/Foundation/Unit.swift +++ b/Sources/Foundation/Unit.swift @@ -147,13 +147,9 @@ internal class NSUnitConverterReciprocal : UnitConverter, NSSecureCoding, @unche NSUnit is the base class for all unit types (dimensional and dimensionless). */ -@available(*, unavailable) -extension Unit : @unchecked Sendable { } -open class Unit : NSObject, NSCopying, NSSecureCoding { - - - open private(set) var symbol: String +open class Unit : NSObject, NSCopying, NSSecureCoding, @unchecked Sendable { + public let symbol: String public required init(symbol: String) { @@ -195,10 +191,10 @@ open class Unit : NSObject, NSCopying, NSSecureCoding { } } -open class Dimension : Unit { +open class Dimension : Unit, @unchecked Sendable { - open private(set) var converter: UnitConverter + public let converter: UnitConverter public required init(symbol: String, converter: UnitConverter) { self.converter = converter From a9622d0c69cb03faab5273ea653a2aada80deac2 Mon Sep 17 00:00:00 2001 From: Tony Parker Date: Mon, 22 Jul 2024 16:24:01 -0700 Subject: [PATCH 10/15] Add annotations for Core and CFSocketRef --- Sources/Foundation/Port.swift | 4 +++- Sources/Foundation/UserDefaults.swift | 10 +++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Sources/Foundation/Port.swift b/Sources/Foundation/Port.swift index 56b4a6516b..a002270ec5 100644 --- a/Sources/Foundation/Port.swift +++ b/Sources/Foundation/Port.swift @@ -407,6 +407,8 @@ fileprivate func __NSFireSocketDatagram(_ socket: CFSocket?, _ type: CFSocketCal @available(*, unavailable) extension SocketPort : @unchecked Sendable { } +extension CFSocket : @unchecked Sendable { } + open class SocketPort : Port { struct SocketKind: Hashable { var protocolFamily: Int32 @@ -474,7 +476,7 @@ open class SocketPort : Port { } } - class Core { + class Core : @unchecked Sendable { fileprivate let isUniqued: Bool fileprivate var signature: Signature! diff --git a/Sources/Foundation/UserDefaults.swift b/Sources/Foundation/UserDefaults.swift index 32649b2390..6db0331ee4 100644 --- a/Sources/Foundation/UserDefaults.swift +++ b/Sources/Foundation/UserDefaults.swift @@ -10,7 +10,7 @@ @_implementationOnly import CoreFoundation internal import Synchronization -private let registeredDefaults = Mutex<[String: Any]>([:]) +private let registeredDefaults = Mutex<[String: (Any & Sendable)]>([:]) private nonisolated(unsafe) var sharedDefaults = UserDefaults() // the default one is thread safe, at least fileprivate func bridgeFromNSCFTypeIfNeeded(_ value: Any) -> Any { @@ -342,14 +342,14 @@ open class UserDefaults: NSObject { return _dictionaryRepresentation(includingVolatileDomains: true) } - private func _dictionaryRepresentation(includingVolatileDomains: Bool) -> [String: Any] { + private func _dictionaryRepresentation(includingVolatileDomains: Bool) -> [String: (Any & Sendable)] { let registeredDefaultsIfAllowed = includingVolatileDomains ? registeredDefaults.withLock { $0 } : [:] let defaultsFromDiskCF = CFPreferencesCopyMultiple(nil, suite?._cfObject ?? kCFPreferencesCurrentApplication, kCFPreferencesCurrentUser, kCFPreferencesAnyHost) - let defaultsFromDiskWithNumbersBoxed = __SwiftValue.fetch(defaultsFromDiskCF) as? [String: Any] ?? [:] + let defaultsFromDiskWithNumbersBoxed = __SwiftValue.fetch(defaultsFromDiskCF) as? [String: (Any & Sendable)] ?? [:] if registeredDefaultsIfAllowed.isEmpty { - return UserDefaults._unboxingNSNumbers(defaultsFromDiskWithNumbersBoxed) as! [String: Any] + return UserDefaults._unboxingNSNumbers(defaultsFromDiskWithNumbersBoxed) as! [String: (Any & Sendable)] } else { var allDefaults = registeredDefaultsIfAllowed @@ -357,7 +357,7 @@ open class UserDefaults: NSObject { allDefaults[key] = value } - return UserDefaults._unboxingNSNumbers(allDefaults) as! [String: Any] + return UserDefaults._unboxingNSNumbers(allDefaults) as! [String: (Any & Sendable)] } } From 5cb23370d3c87372f0b634ede709257d8145f89e Mon Sep 17 00:00:00 2001 From: Tony Parker Date: Mon, 22 Jul 2024 16:24:13 -0700 Subject: [PATCH 11/15] Resolve a warning in TestDecimal --- Tests/Foundation/TestDecimal.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tests/Foundation/TestDecimal.swift b/Tests/Foundation/TestDecimal.swift index 8cdeba9a16..eedc4fea3e 100644 --- a/Tests/Foundation/TestDecimal.swift +++ b/Tests/Foundation/TestDecimal.swift @@ -73,7 +73,6 @@ class TestDecimal: XCTestCase { */ let fr = Locale(identifier: "fr_FR") - let greatestFiniteMagnitude = "3402823669209384634633746074317682114550000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" XCTAssertEqual("0", NSDecimalNumber(decimal: Decimal()).description(withLocale: fr)) XCTAssertEqual("1000", NSDecimalNumber(decimal: Decimal(1000)).description(withLocale: fr)) @@ -86,6 +85,8 @@ class TestDecimal: XCTestCase { // Disabled pending decision about size of Decimal mantissa /* + let greatestFiniteMagnitude = "3402823669209384634633746074317682114550000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + XCTAssertEqual(greatestFiniteMagnitude, NSDecimalNumber(decimal: Decimal.greatestFiniteMagnitude).description(withLocale: fr)) XCTAssertEqual("0,00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001", NSDecimalNumber(decimal: Decimal.leastNormalMagnitude).description(withLocale: fr)) XCTAssertEqual("0,00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001", NSDecimalNumber(decimal: Decimal.leastNonzeroMagnitude).description(withLocale: fr)) From c2bc79a8d35e9ea3c14b36030662a40785697071 Mon Sep 17 00:00:00 2001 From: Tony Parker Date: Tue, 23 Jul 2024 10:58:26 -0700 Subject: [PATCH 12/15] Use noncopyable State type for DateFormatter and NumberFormatter --- Sources/Foundation/DateFormatter.swift | 118 ++++++++-------- Sources/Foundation/NumberFormatter.swift | 166 +++++++++++------------ 2 files changed, 146 insertions(+), 138 deletions(-) diff --git a/Sources/Foundation/DateFormatter.swift b/Sources/Foundation/DateFormatter.swift index 39ac88cd71..760e776dae 100644 --- a/Sources/Foundation/DateFormatter.swift +++ b/Sources/Foundation/DateFormatter.swift @@ -18,11 +18,18 @@ open class DateFormatter : Formatter, @unchecked Sendable { super.init() } - // Consumes state - private convenience init(state: State) { + private convenience init(state: consuming sending State) { self.init() - nonisolated(unsafe) let consumedState = state - _lock.withLock { $0 = consumedState } + + // work around issue that state needs to be reinitialized after consuming + struct Wrapper : ~Copyable, @unchecked Sendable { + var value: State? = nil + } + var w = Wrapper(value: consume state) + + _lock.withLock { + $0 = w.value.take()! + } } open override func copy(with zone: NSZone? = nil) -> Any { @@ -37,56 +44,57 @@ open class DateFormatter : Formatter, @unchecked Sendable { super.init(coder: coder) } - final class State { - private var _formatter : CFDateFormatter? = nil + struct State : ~Copyable { + class Box { + var formatter: CFDateFormatter? + init() {} + } + + private var _formatter = Box() - func copy(with zone: NSZone? = nil) -> State { - let copied = State() - - func __copy(_ keyPath: ReferenceWritableKeyPath) { - copied[keyPath: keyPath] = self[keyPath: keyPath] - } - - __copy(\.formattingContext) - __copy(\.dateStyle) - __copy(\.timeStyle) - __copy(\._locale) - __copy(\.generatesCalendarDates) - __copy(\._timeZone) - __copy(\._calendar) - __copy(\.isLenient) - __copy(\._twoDigitStartDate) - __copy(\._eraSymbols) - __copy(\._monthSymbols) - __copy(\._shortMonthSymbols) - __copy(\._weekdaySymbols) - __copy(\._shortWeekdaySymbols) - __copy(\._amSymbol) - __copy(\._pmSymbol) - __copy(\._longEraSymbols) - __copy(\._veryShortMonthSymbols) - __copy(\._standaloneMonthSymbols) - __copy(\._shortStandaloneMonthSymbols) - __copy(\._veryShortStandaloneMonthSymbols) - __copy(\._veryShortWeekdaySymbols) - __copy(\._standaloneWeekdaySymbols) - __copy(\._shortStandaloneWeekdaySymbols) - __copy(\._veryShortStandaloneWeekdaySymbols) - __copy(\._quarterSymbols) - __copy(\._shortQuarterSymbols) - __copy(\._standaloneQuarterSymbols) - __copy(\._shortStandaloneQuarterSymbols) - __copy(\._gregorianStartDate) - __copy(\.doesRelativeDateFormatting) + func copy(with zone: NSZone? = nil) -> sending State { + var copied = State() + + copied.formattingContext = formattingContext + copied.dateStyle = dateStyle + copied.timeStyle = timeStyle + copied._locale = _locale + copied.generatesCalendarDates = generatesCalendarDates + copied._timeZone = _timeZone + copied._calendar = _calendar + copied.isLenient = isLenient + copied._twoDigitStartDate = _twoDigitStartDate + copied._eraSymbols = _eraSymbols + copied._monthSymbols = _monthSymbols + copied._shortMonthSymbols = _shortMonthSymbols + copied._weekdaySymbols = _weekdaySymbols + copied._shortWeekdaySymbols = _shortWeekdaySymbols + copied._amSymbol = _amSymbol + copied._pmSymbol = _pmSymbol + copied._longEraSymbols = _longEraSymbols + copied._veryShortMonthSymbols = _veryShortMonthSymbols + copied._standaloneMonthSymbols = _standaloneMonthSymbols + copied._shortStandaloneMonthSymbols = _shortStandaloneMonthSymbols + copied._veryShortStandaloneMonthSymbols = _veryShortStandaloneMonthSymbols + copied._veryShortWeekdaySymbols = _veryShortWeekdaySymbols + copied._standaloneWeekdaySymbols = _standaloneWeekdaySymbols + copied._shortStandaloneWeekdaySymbols = _shortStandaloneWeekdaySymbols + copied._veryShortStandaloneWeekdaySymbols = _veryShortStandaloneWeekdaySymbols + copied._quarterSymbols = _quarterSymbols + copied._shortQuarterSymbols = _shortQuarterSymbols + copied._standaloneQuarterSymbols = _standaloneQuarterSymbols + copied._shortStandaloneQuarterSymbols = _shortStandaloneQuarterSymbols + copied._gregorianStartDate = _gregorianStartDate + copied.doesRelativeDateFormatting = doesRelativeDateFormatting // The last is `_dateFormat` because setting `dateStyle` and `timeStyle` make it `nil`. - __copy(\._dateFormat) + copied._dateFormat = _dateFormat return copied } func formatter() -> CFDateFormatter { - guard let obj = _formatter else { + guard let obj = _formatter.formatter else { let dateStyle = CFDateFormatterStyle(rawValue: CFIndex(dateStyle.rawValue))! let timeStyle = CFDateFormatterStyle(rawValue: CFIndex(timeStyle.rawValue))! @@ -95,14 +103,14 @@ open class DateFormatter : Formatter, @unchecked Sendable { if let dateFormat = _dateFormat { CFDateFormatterSetFormat(obj, dateFormat._cfObject) } - _formatter = obj + _formatter.formatter = obj return obj } return obj } - private func _reset() { - _formatter = nil + private mutating func _reset() { + _formatter.formatter = nil } // MARK: - @@ -143,7 +151,7 @@ open class DateFormatter : Formatter, @unchecked Sendable { _setFormatterAttribute(formatter, attributeName: kCFDateFormatterGregorianStartDate, value: _gregorianStartDate?._cfObject) } - internal final func _setFormatterAttribute(_ formatter: CFDateFormatter, attributeName: CFString, value: AnyObject?) { + internal func _setFormatterAttribute(_ formatter: CFDateFormatter, attributeName: CFString, value: AnyObject?) { if let value = value { CFDateFormatterSetProperty(formatter, attributeName, value) } @@ -174,7 +182,7 @@ open class DateFormatter : Formatter, @unchecked Sendable { } } - /*@NSCopying*/ internal var _locale: Locale? { willSet { _reset() } } + internal var _locale: Locale? { willSet { _reset() } } var locale: Locale! { get { guard let locale = _locale else { return .current } @@ -187,7 +195,7 @@ open class DateFormatter : Formatter, @unchecked Sendable { var generatesCalendarDates = false { willSet { _reset() } } - /*@NSCopying*/ internal var _timeZone: TimeZone? { willSet { _reset() } } + internal var _timeZone: TimeZone? { willSet { _reset() } } var timeZone: TimeZone! { get { guard let tz = _timeZone else { @@ -203,7 +211,7 @@ open class DateFormatter : Formatter, @unchecked Sendable { } } - /*@NSCopying*/ internal var _calendar: Calendar! { willSet { _reset() } } + internal var _calendar: Calendar! { willSet { _reset() } } var calendar: Calendar! { get { guard let calendar = _calendar else { @@ -221,7 +229,7 @@ open class DateFormatter : Formatter, @unchecked Sendable { var isLenient = false { willSet { _reset() } } - /*@NSCopying*/ internal var _twoDigitStartDate: Date? { willSet { _reset() } } + internal var _twoDigitStartDate: Date? { willSet { _reset() } } var twoDigitStartDate: Date? { get { guard let startDate = _twoDigitStartDate else { @@ -234,7 +242,7 @@ open class DateFormatter : Formatter, @unchecked Sendable { } } - /*@NSCopying*/ var defaultDate: Date? { willSet { _reset() } } + var defaultDate: Date? { willSet { _reset() } } internal var _eraSymbols: [String]? { willSet { _reset() } } var eraSymbols: [String] { diff --git a/Sources/Foundation/NumberFormatter.swift b/Sources/Foundation/NumberFormatter.swift index b77ca56da2..80a62d7fe8 100644 --- a/Sources/Foundation/NumberFormatter.swift +++ b/Sources/Foundation/NumberFormatter.swift @@ -53,11 +53,18 @@ open class NumberFormatter : Formatter, @unchecked Sendable { super.init(coder: coder) } - // Consumes state - private convenience init(state: State) { + private convenience init(state: consuming sending State) { self.init() - nonisolated(unsafe) let consumedState = state - _lock.withLock { $0 = consumedState } + + // work around issue that state needs to be reinitialized after consuming + struct Wrapper : ~Copyable, @unchecked Sendable { + var value: State? = nil + } + var w = Wrapper(value: consume state) + + _lock.withLock { + $0 = w.value.take()! + } } open override func copy(with zone: NSZone? = nil) -> Any { @@ -74,86 +81,79 @@ open class NumberFormatter : Formatter, @unchecked Sendable { return numberFormatter.string(for: num)! } - final class State { - private var _formatter: CFNumberFormatter? = nil + struct State : ~Copyable { + class Box { + var formatter: CFNumberFormatter? + init() {} + } + + private var _formatter = Box() // MARK: - func copy(with zone: NSZone? = nil) -> State { - let copied = State() - - func __copy(_ keyPath: ReferenceWritableKeyPath) { - copied[keyPath: keyPath] = self[keyPath: keyPath] - } - - func __copy(_ keyPath: ReferenceWritableKeyPath) where T: NSCopying { - copied[keyPath: keyPath] = self[keyPath: keyPath].copy(with: zone) as! T - } - - func __copy(_ keyPath: ReferenceWritableKeyPath) where T: NSCopying { - copied[keyPath: keyPath] = self[keyPath: keyPath]?.copy(with: zone) as! T? - } - - __copy(\.formattingContext) - __copy(\._numberStyle) - __copy(\._locale) - __copy(\._generatesDecimalNumbers) - __copy(\._textAttributesForNegativeValues) - __copy(\._textAttributesForPositiveValues) - __copy(\._allowsFloats) - __copy(\._decimalSeparator) - __copy(\._alwaysShowsDecimalSeparator) - __copy(\._currencyDecimalSeparator) - __copy(\._usesGroupingSeparator) - __copy(\._groupingSeparator) - __copy(\._zeroSymbol) - __copy(\._textAttributesForZero) - __copy(\._nilSymbol) - __copy(\._textAttributesForNil) - __copy(\._notANumberSymbol) - __copy(\._textAttributesForNotANumber) - __copy(\._positiveInfinitySymbol) - __copy(\._textAttributesForPositiveInfinity) - __copy(\._negativeInfinitySymbol) - __copy(\._textAttributesForNegativeInfinity) - __copy(\._positivePrefix) - __copy(\._positiveSuffix) - __copy(\._negativePrefix) - __copy(\._negativeSuffix) - __copy(\._currencyCode) - __copy(\._currencySymbol) - __copy(\._internationalCurrencySymbol) - __copy(\._percentSymbol) - __copy(\._perMillSymbol) - __copy(\._minusSign) - __copy(\._plusSign) - __copy(\._exponentSymbol) - __copy(\._groupingSize) - __copy(\._secondaryGroupingSize) - __copy(\._multiplier) - __copy(\._formatWidth) - __copy(\._paddingCharacter) - __copy(\._paddingPosition) - __copy(\._roundingMode) - __copy(\._roundingIncrement) - __copy(\._minimumIntegerDigits) - __copy(\._maximumIntegerDigits) - __copy(\._minimumFractionDigits) - __copy(\._maximumFractionDigits) - __copy(\._minimum) - __copy(\._maximum) - __copy(\._currencyGroupingSeparator) - __copy(\._lenient) - __copy(\._usesSignificantDigits) - __copy(\._minimumSignificantDigits) - __copy(\._maximumSignificantDigits) - __copy(\._partialStringValidationEnabled) - __copy(\._hasThousandSeparators) - __copy(\._thousandSeparator) - __copy(\._localizesFormat) - __copy(\._positiveFormat) - __copy(\._negativeFormat) - __copy(\._roundingBehavior) + var copied = State() + + copied.formattingContext = formattingContext + copied._numberStyle = _numberStyle + copied._locale = _locale + copied._generatesDecimalNumbers = _generatesDecimalNumbers + copied._textAttributesForNegativeValues = _textAttributesForNegativeValues + copied._textAttributesForPositiveValues = _textAttributesForPositiveValues + copied._allowsFloats = _allowsFloats + copied._decimalSeparator = _decimalSeparator + copied._alwaysShowsDecimalSeparator = _alwaysShowsDecimalSeparator + copied._currencyDecimalSeparator = _currencyDecimalSeparator + copied._usesGroupingSeparator = _usesGroupingSeparator + copied._groupingSeparator = _groupingSeparator + copied._zeroSymbol = _zeroSymbol + copied._textAttributesForZero = _textAttributesForZero + copied._nilSymbol = _nilSymbol + copied._textAttributesForNil = _textAttributesForNil + copied._notANumberSymbol = _notANumberSymbol + copied._textAttributesForNotANumber = _textAttributesForNotANumber + copied._positiveInfinitySymbol = _positiveInfinitySymbol + copied._textAttributesForPositiveInfinity = _textAttributesForPositiveInfinity + copied._negativeInfinitySymbol = _negativeInfinitySymbol + copied._textAttributesForNegativeInfinity = _textAttributesForNegativeInfinity + copied._positivePrefix = _positivePrefix + copied._positiveSuffix = _positiveSuffix + copied._negativePrefix = _negativePrefix + copied._negativeSuffix = _negativeSuffix + copied._currencyCode = _currencyCode + copied._currencySymbol = _currencySymbol + copied._internationalCurrencySymbol = _internationalCurrencySymbol + copied._percentSymbol = _percentSymbol + copied._perMillSymbol = _perMillSymbol + copied._minusSign = _minusSign + copied._plusSign = _plusSign + copied._exponentSymbol = _exponentSymbol + copied._groupingSize = _groupingSize + copied._secondaryGroupingSize = _secondaryGroupingSize + copied._multiplier = _multiplier + copied._formatWidth = _formatWidth + copied._paddingCharacter = _paddingCharacter + copied._paddingPosition = _paddingPosition + copied._roundingMode = _roundingMode + copied._roundingIncrement = _roundingIncrement + copied._minimumIntegerDigits = _minimumIntegerDigits + copied._maximumIntegerDigits = _maximumIntegerDigits + copied._minimumFractionDigits = _minimumFractionDigits + copied._maximumFractionDigits = _maximumFractionDigits + copied._minimum = _minimum + copied._maximum = _maximum + copied._currencyGroupingSeparator = _currencyGroupingSeparator + copied._lenient = _lenient + copied._usesSignificantDigits = _usesSignificantDigits + copied._minimumSignificantDigits = _minimumSignificantDigits + copied._maximumSignificantDigits = _maximumSignificantDigits + copied._partialStringValidationEnabled = _partialStringValidationEnabled + copied._hasThousandSeparators = _hasThousandSeparators + copied._thousandSeparator = _thousandSeparator + copied._localizesFormat = _localizesFormat + copied._positiveFormat = _positiveFormat + copied._negativeFormat = _negativeFormat + copied._roundingBehavior = _roundingBehavior return copied } @@ -161,11 +161,11 @@ open class NumberFormatter : Formatter, @unchecked Sendable { // MARK: - func _reset() { - _formatter = nil + _formatter.formatter = nil } func formatter() -> CFNumberFormatter { - if let obj = _formatter { + if let obj = _formatter.formatter { return obj } else { let numberStyle = CFNumberFormatterStyle(rawValue: CFIndex(_numberStyle.rawValue))! @@ -180,7 +180,7 @@ open class NumberFormatter : Formatter, @unchecked Sendable { } CFNumberFormatterSetFormat(obj, format._cfObject) } - _formatter = obj + _formatter.formatter = obj return obj } } From b48b7690a90eb3d85f7c65271dce1eafbd2db851 Mon Sep 17 00:00:00 2001 From: Tony Parker Date: Wed, 24 Jul 2024 08:19:21 -0700 Subject: [PATCH 13/15] Mark DirectoryEnumerator as non-Sendable --- Sources/Foundation/FileManager.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Sources/Foundation/FileManager.swift b/Sources/Foundation/FileManager.swift index 8c3e23c537..a5d75820df 100644 --- a/Sources/Foundation/FileManager.swift +++ b/Sources/Foundation/FileManager.swift @@ -608,6 +608,9 @@ extension FileAttributeKey { internal static let _accessDate = FileAttributeKey(rawValue: "org.swift.Foundation.FileAttributeKey._accessDate") } +@available(*, unavailable) +extension FileManager.DirectoryEnumerator : Sendable { } + extension FileManager { open class DirectoryEnumerator : NSEnumerator { From d068ef622e5eea29a20dad6d51e877db2524098f Mon Sep 17 00:00:00 2001 From: Tony Parker Date: Wed, 24 Jul 2024 10:19:41 -0700 Subject: [PATCH 14/15] Work around compiler crashes when using ~Copyable types --- Sources/Foundation/NumberFormatter.swift | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/Sources/Foundation/NumberFormatter.swift b/Sources/Foundation/NumberFormatter.swift index 80a62d7fe8..39effe97b3 100644 --- a/Sources/Foundation/NumberFormatter.swift +++ b/Sources/Foundation/NumberFormatter.swift @@ -53,17 +53,10 @@ open class NumberFormatter : Formatter, @unchecked Sendable { super.init(coder: coder) } - private convenience init(state: consuming sending State) { + private convenience init(state: State) { self.init() - - // work around issue that state needs to be reinitialized after consuming - struct Wrapper : ~Copyable, @unchecked Sendable { - var value: State? = nil - } - var w = Wrapper(value: consume state) - _lock.withLock { - $0 = w.value.take()! + $0 = state } } @@ -81,7 +74,8 @@ open class NumberFormatter : Formatter, @unchecked Sendable { return numberFormatter.string(for: num)! } - struct State : ~Copyable { + // This class is not Sendable, but marking it as such was the only way to work around compiler crashes while attempting to use `~Copyable` like `DateFormatter` does. + final class State : @unchecked Sendable { class Box { var formatter: CFNumberFormatter? init() {} @@ -92,7 +86,7 @@ open class NumberFormatter : Formatter, @unchecked Sendable { // MARK: - func copy(with zone: NSZone? = nil) -> State { - var copied = State() + let copied = State() copied.formattingContext = formattingContext copied._numberStyle = _numberStyle @@ -230,6 +224,7 @@ open class NumberFormatter : Formatter, @unchecked Sendable { } else { _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterPaddingCharacter, value: ""._cfObject) } + _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterMultiplier, value: multiplier?._cfObject) _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterPositivePrefix, value: _positivePrefix?._cfObject) _setFormatterAttribute(formatter, attributeName: kCFNumberFormatterPositiveSuffix, value: _positiveSuffix?._cfObject) From e16b39a4fd2c129bb7f8fce4bd992094aceadf14 Mon Sep 17 00:00:00 2001 From: Tony Parker Date: Wed, 24 Jul 2024 10:20:00 -0700 Subject: [PATCH 15/15] Clarify comment of _nonSendable with a message explaining the remaining warning --- Sources/Foundation/Formatter.swift | 2 +- Sources/Foundation/NSCoder.swift | 2 +- Sources/Foundation/NSEnumerator.swift | 2 +- Sources/Foundation/NSObject.swift | 2 +- Sources/Foundation/Port.swift | 2 +- Sources/Foundation/Stream.swift | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Sources/Foundation/Formatter.swift b/Sources/Foundation/Formatter.swift index f798b0d741..8c744f7f92 100644 --- a/Sources/Foundation/Formatter.swift +++ b/Sources/Foundation/Formatter.swift @@ -43,7 +43,7 @@ extension Formatter { } } -//@_nonSendable +//@_nonSendable - TODO: Mark with attribute to indicate this pure abstract class defers Sendable annotation to its subclasses. open class Formatter : NSObject, NSCopying, NSCoding { public override init() { diff --git a/Sources/Foundation/NSCoder.swift b/Sources/Foundation/NSCoder.swift index 283b805d74..61fd20c442 100644 --- a/Sources/Foundation/NSCoder.swift +++ b/Sources/Foundation/NSCoder.swift @@ -100,7 +100,7 @@ public protocol NSSecureCoding : NSCoding { /// is normally of the same class as the object that was originally encoded into /// the stream. An object can change its class when encoded, however; this is /// described in Archives and Serializations Programming Guide. -//@_nonSendable +//@_nonSendable - TODO: Mark with attribute to indicate this pure abstract class defers Sendable annotation to its subclasses. open class NSCoder : NSObject { internal var _pendingBuffers = Array<(UnsafeMutableRawPointer, Int)>() diff --git a/Sources/Foundation/NSEnumerator.swift b/Sources/Foundation/NSEnumerator.swift index cf3683b28a..1add2e0126 100644 --- a/Sources/Foundation/NSEnumerator.swift +++ b/Sources/Foundation/NSEnumerator.swift @@ -10,7 +10,7 @@ @available(*, unavailable) extension NSEnumerator.Iterator : Sendable { } -//@_nonSendable +//@_nonSendable - TODO: Mark with attribute to indicate this pure abstract class defers Sendable annotation to its subclasses. open class NSEnumerator : NSObject { open func nextObject() -> Any? { diff --git a/Sources/Foundation/NSObject.swift b/Sources/Foundation/NSObject.swift index 9c37ffcc81..c7856bf655 100644 --- a/Sources/Foundation/NSObject.swift +++ b/Sources/Foundation/NSObject.swift @@ -152,7 +152,7 @@ extension NSMutableCopying { } /// The root class of most Foundation class hierarchies. -//@_nonSendable +//@_nonSendable - TODO: Mark with attribute to indicate this pure abstract class defers Sendable annotation to its subclasses. open class NSObject : NSObjectProtocol, Equatable, Hashable { // Important: add no ivars here. It will subvert the careful layout of subclasses that bridge into CF. diff --git a/Sources/Foundation/Port.swift b/Sources/Foundation/Port.swift index a002270ec5..c1f78c14a4 100644 --- a/Sources/Foundation/Port.swift +++ b/Sources/Foundation/Port.swift @@ -18,7 +18,7 @@ extension Port { public static let didBecomeInvalidNotification = NSNotification.Name(rawValue: "NSPortDidBecomeInvalidNotification") } -//@_nonSendable +//@_nonSendable - TODO: Mark with attribute to indicate this pure abstract class defers Sendable annotation to its subclasses. open class Port : NSObject, NSCopying { /// On Darwin, you can invoke `Port()` directly to produce a `MessagePort`. Since `MessagePort` is not available in swift-corelibs-foundation, you should not invoke this initializer directly. Subclasses of `Port` can delegate to this initializer safely. public override init() { diff --git a/Sources/Foundation/Stream.swift b/Sources/Foundation/Stream.swift index b04caf2edf..ab19ce2997 100644 --- a/Sources/Foundation/Stream.swift +++ b/Sources/Foundation/Stream.swift @@ -55,7 +55,7 @@ extension Stream { // Stream is an abstract class encapsulating the common API to InputStream and OutputStream. // Subclassers of InputStream and OutputStream must also implement these methods. -//@_nonSendable +//@_nonSendable - TODO: Mark with attribute to indicate this pure abstract class defers Sendable annotation to its subclasses. open class Stream: NSObject { public override init() {