mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
runtime: make _SwiftNativeNSError use the Hashable conformance, if available
If the Swift error wrapped in a _SwiftNativeNSError box conforms to Hashable, the box now uses the Swift's conformance to Hashable. Part of rdar://problem/27574348.
This commit is contained in:
@@ -98,3 +98,22 @@ public protocol Hashable : _Hashable, Equatable {
|
||||
var hashValue: Int { get }
|
||||
}
|
||||
|
||||
public enum _RuntimeHelpers {}
|
||||
|
||||
extension _RuntimeHelpers {
|
||||
@_silgen_name("swift_stdlib_Hashable_isEqual_indirect")
|
||||
public static func Hashable_isEqual_indirect<T : Hashable>(
|
||||
_ lhs: UnsafePointer<T>,
|
||||
_ rhs: UnsafePointer<T>
|
||||
) -> Bool {
|
||||
return lhs.pointee == rhs.pointee
|
||||
}
|
||||
|
||||
@_silgen_name("swift_stdlib_Hashable_hashValue_indirect")
|
||||
public static func Hashable_hashValue_indirect<T : Hashable>(
|
||||
_ value: UnsafePointer<T>
|
||||
) -> Int {
|
||||
return value.pointee.hashValue
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,12 +15,11 @@
|
||||
#include "swift/Runtime/Concurrent.h"
|
||||
#include "swift/Runtime/Debug.h"
|
||||
#include "swift/Runtime/Metadata.h"
|
||||
#include "../runtime/Private.h"
|
||||
#include "Private.h"
|
||||
#include "SwiftHashableSupport.h"
|
||||
|
||||
using namespace swift;
|
||||
|
||||
/// The name demangles to "protocol descriptor for Swift.Hashable".
|
||||
extern "C" const ProtocolDescriptor _TMps8Hashable;
|
||||
using namespace swift::hashable_support;
|
||||
|
||||
namespace {
|
||||
struct HashableConformanceKey {
|
||||
@@ -36,6 +35,9 @@ struct HashableConformanceEntry {
|
||||
|
||||
/// The highest (closest to the root) type in the superclass chain
|
||||
/// that conforms to `Hashable`.
|
||||
///
|
||||
/// Always non-NULL. We don't cache negative responses so that we
|
||||
/// don't have to deal with cache invalidation.
|
||||
const Metadata *baseTypeThatConformsToHashable;
|
||||
|
||||
HashableConformanceEntry(HashableConformanceKey key,
|
||||
@@ -59,16 +61,26 @@ struct HashableConformanceEntry {
|
||||
};
|
||||
} // end unnamed namesapce
|
||||
|
||||
// FIXME(performance): consider merging this cache into the regular
|
||||
// protocol conformance cache.
|
||||
static Lazy<ConcurrentMap<HashableConformanceEntry>> HashableConformances;
|
||||
|
||||
/// Find the base type that introduces the `Hashable` conformance.
|
||||
///
|
||||
/// - Precondition: `type` conforms to `Hashable` (not checked).
|
||||
static const Metadata *findHashableBaseType(const Metadata *type) {
|
||||
template<bool KnownToConformToHashable>
|
||||
LLVM_ATTRIBUTE_ALWAYS_INLINE
|
||||
static const Metadata *findHashableBaseTypeImpl(const Metadata *type) {
|
||||
// Check the cache first.
|
||||
if (HashableConformanceEntry *entry =
|
||||
HashableConformances->find(HashableConformanceKey{type})) {
|
||||
return entry->baseTypeThatConformsToHashable;
|
||||
}
|
||||
if (!KnownToConformToHashable &&
|
||||
!swift_conformsToProtocol(type, &_TMps8Hashable)) {
|
||||
// Don't cache the negative response because we don't invalidate
|
||||
// this cache when a new conformance is loaded dynamically.
|
||||
return nullptr;
|
||||
}
|
||||
// By this point, `type` is known to conform to `Hashable`.
|
||||
|
||||
const Metadata *baseTypeThatConformsToHashable = type;
|
||||
while (true) {
|
||||
const Metadata *superclass =
|
||||
@@ -84,6 +96,26 @@ static const Metadata *findHashableBaseType(const Metadata *type) {
|
||||
return baseTypeThatConformsToHashable;
|
||||
}
|
||||
|
||||
/// Find the base type that introduces the `Hashable` conformance.
|
||||
/// Because the provided type is known to conform to `Hashable`, this
|
||||
/// function always returns non-null.
|
||||
///
|
||||
/// - Precondition: `type` conforms to `Hashable` (not checked).
|
||||
const Metadata *swift::hashable_support::findHashableBaseTypeOfHashableType(
|
||||
const Metadata *type) {
|
||||
auto result =
|
||||
findHashableBaseTypeImpl</*KnownToConformToHashable=*/ true>(type);
|
||||
assert(result && "Known-hashable types should have a `Hashable` conformance.");
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Find the base type that introduces the `Hashable` conformance.
|
||||
/// If `type` does not conform to `Hashable`, `nullptr` is returned.
|
||||
const Metadata *swift::hashable_support::findHashableBaseType(
|
||||
const Metadata *type) {
|
||||
return findHashableBaseTypeImpl</*KnownToConformToHashable=*/ false>(type);
|
||||
}
|
||||
|
||||
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_INTERFACE
|
||||
extern "C" void _swift_stdlib_makeAnyHashableUsingDefaultRepresentation(
|
||||
const OpaqueValue *value,
|
||||
@@ -104,7 +136,8 @@ extern "C" void _swift_stdlib_makeAnyHashableUpcastingToHashableBaseType(
|
||||
case MetadataKind::ObjCClassWrapper:
|
||||
case MetadataKind::ForeignClass: {
|
||||
_swift_stdlib_makeAnyHashableUsingDefaultRepresentation(
|
||||
value, anyHashableResultPointer, findHashableBaseType(type),
|
||||
value, anyHashableResultPointer,
|
||||
findHashableBaseTypeOfHashableType(type),
|
||||
hashableWT);
|
||||
return;
|
||||
}
|
||||
@@ -34,6 +34,7 @@ else()
|
||||
endif()
|
||||
|
||||
set(swift_runtime_sources
|
||||
AnyHashableSupport.cpp
|
||||
Casting.cpp
|
||||
CygwinPort.cpp
|
||||
Demangle.cpp
|
||||
|
||||
@@ -1642,11 +1642,6 @@ extern "C" const ProtocolDescriptor _TMps5Error;
|
||||
static const WitnessTable *findErrorWitness(const Metadata *srcType) {
|
||||
return swift_conformsToProtocol(srcType, &_TMps5Error);
|
||||
}
|
||||
|
||||
static const Metadata *getNSErrorMetadata() {
|
||||
return SWIFT_LAZY_CONSTANT(
|
||||
swift_getObjCClassMetadata((const ClassMetadata *)getNSErrorClass()));
|
||||
}
|
||||
#endif
|
||||
|
||||
/// Perform a dynamic cast from an existential type to some kind of
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
|
||||
#include "swift/Runtime/Metadata.h"
|
||||
#include "swift/Runtime/HeapObject.h"
|
||||
#include "SwiftHashableSupport.h"
|
||||
#include <atomic>
|
||||
#if SWIFT_OBJC_INTEROP
|
||||
# include <CoreFoundation/CoreFoundation.h>
|
||||
@@ -70,12 +71,25 @@ struct SwiftError : SwiftErrorHeader {
|
||||
// Core Foundation's refcounting scheme.
|
||||
|
||||
/// The type of Swift error value contained in the box.
|
||||
/// This is only available for native Swift errors.
|
||||
/// This member is only available for native Swift errors.
|
||||
const Metadata *type;
|
||||
/// The Error witness table.
|
||||
/// This is only available for native Swift errors.
|
||||
|
||||
/// The witness table for `Error` conformance.
|
||||
/// This member is only available for native Swift errors.
|
||||
const WitnessTable *errorConformance;
|
||||
|
||||
/// The base type that introduces the `Hashable` conformance.
|
||||
/// This member is only available for native Swift errors.
|
||||
/// This member is lazily-initialized.
|
||||
/// Instead of using it directly, call `getHashableBaseType()`.
|
||||
mutable std::atomic<const Metadata *> hashableBaseType;
|
||||
|
||||
/// The witness table for `Hashable` conformance.
|
||||
/// This member is only available for native Swift errors.
|
||||
/// This member is lazily-initialized.
|
||||
/// Instead of using it directly, call `getHashableConformance()`.
|
||||
mutable std::atomic<const hashable_support::HashableWitnessTable *> hashableConformance;
|
||||
|
||||
/// Get a pointer to the value contained inside the indirectly-referenced
|
||||
/// box reference.
|
||||
static const OpaqueValue *getIndirectValue(const SwiftError * const *ptr) {
|
||||
@@ -129,6 +143,14 @@ struct SwiftError : SwiftErrorHeader {
|
||||
const WitnessTable *getErrorConformance() const { return errorConformance; }
|
||||
#endif
|
||||
|
||||
/// Get the base type that conforms to `Hashable`.
|
||||
/// Returns NULL if the type does not conform.
|
||||
const Metadata *getHashableBaseType() const;
|
||||
|
||||
/// Get the `Hashable` protocol witness table for the contained type.
|
||||
/// Returns NULL if the type does not conform.
|
||||
const hashable_support::HashableWitnessTable *getHashableConformance() const;
|
||||
|
||||
// Don't copy or move, please.
|
||||
SwiftError(const SwiftError &) = delete;
|
||||
SwiftError(SwiftError &&) = delete;
|
||||
@@ -202,6 +224,9 @@ bool tryDynamicCastNSErrorToValue(OpaqueValue *dest,
|
||||
/// Get the NSError Objective-C class.
|
||||
Class getNSErrorClass();
|
||||
|
||||
/// Get the NSError metadata.
|
||||
const Metadata *getNSErrorMetadata();
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace swift
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#include <Foundation/Foundation.h>
|
||||
|
||||
using namespace swift;
|
||||
using namespace swift::hashable_support;
|
||||
|
||||
/// A subclass of NSError used to represent bridged native Swift errors.
|
||||
/// This type cannot be subclassed, and should not ever be instantiated
|
||||
@@ -97,12 +98,59 @@ using namespace swift;
|
||||
return getNSErrorClass();
|
||||
}
|
||||
|
||||
// Note: We support comparing cases of `@objc` enums defined in Swift to
|
||||
// pure `NSError`s. They should compare equal as long as the domain and
|
||||
// code match. Equal values should have equal hash values. Thus, we can't
|
||||
// use the Swift hash value computation that comes from the `Hashable`
|
||||
// conformance if one exists, and we must use the `NSError` hashing
|
||||
// algorithm.
|
||||
//
|
||||
// So we are not overriding the `hash` method, even though we are
|
||||
// overriding `isEqual:`.
|
||||
|
||||
- (BOOL)isEqual:(id)other {
|
||||
auto self_ = (const SwiftError *)self;
|
||||
auto other_ = (const SwiftError *)other;
|
||||
assert(!self_->isPureNSError());
|
||||
|
||||
if (self == other) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
if (!other) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (other_->isPureNSError()) {
|
||||
return [super isEqual:other];
|
||||
}
|
||||
|
||||
auto hashableBaseType = self_->getHashableBaseType();
|
||||
if (!hashableBaseType || other_->getHashableBaseType() != hashableBaseType) {
|
||||
return [super isEqual:other];
|
||||
}
|
||||
|
||||
auto hashableConformance = self_->getHashableConformance();
|
||||
if (!hashableConformance) {
|
||||
return [super isEqual:other];
|
||||
}
|
||||
|
||||
return swift_stdlib_Hashable_isEqual_indirect(
|
||||
self_->getValue(), other_->getValue(), hashableBaseType,
|
||||
hashableConformance);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
Class swift::getNSErrorClass() {
|
||||
return SWIFT_LAZY_CONSTANT([NSError class]);
|
||||
}
|
||||
|
||||
const Metadata *swift::getNSErrorMetadata() {
|
||||
return SWIFT_LAZY_CONSTANT(
|
||||
swift_getObjCClassMetadata((const ClassMetadata *)getNSErrorClass()));
|
||||
}
|
||||
|
||||
static Class getSwiftNativeNSErrorClass() {
|
||||
return SWIFT_LAZY_CONSTANT([_SwiftNativeNSError class]);
|
||||
}
|
||||
@@ -141,6 +189,8 @@ _swift_allocError_(const Metadata *type,
|
||||
// Initialize the Swift type metadata.
|
||||
instance->type = type;
|
||||
instance->errorConformance = errorConformance;
|
||||
instance->hashableBaseType = nullptr;
|
||||
instance->hashableConformance = nullptr;
|
||||
|
||||
auto valueBytePtr = reinterpret_cast<char*>(instance) + valueOffset;
|
||||
auto valuePtr = reinterpret_cast<OpaqueValue*>(valueBytePtr);
|
||||
@@ -195,12 +245,22 @@ static const WitnessTable *getNSErrorConformanceToError() {
|
||||
auto TheWitnessTable = SWIFT_LAZY_CONSTANT(dlsym(RTLD_DEFAULT,
|
||||
"_TWPCSo7CFErrors5Error10Foundation"));
|
||||
assert(TheWitnessTable &&
|
||||
"Foundation overlay not loaded, or CFError: Error conformance "
|
||||
"Foundation overlay not loaded, or 'CFError : Error' conformance "
|
||||
"not available");
|
||||
|
||||
return reinterpret_cast<const WitnessTable *>(TheWitnessTable);
|
||||
}
|
||||
|
||||
static const HashableWitnessTable *getNSErrorConformanceToHashable() {
|
||||
auto TheWitnessTable = SWIFT_LAZY_CONSTANT(dlsym(RTLD_DEFAULT,
|
||||
"__TWPCSo8NSObjects8Hashable10ObjectiveC"));
|
||||
assert(TheWitnessTable &&
|
||||
"ObjectiveC overlay not loaded, or 'NSObject : Hashable' conformance "
|
||||
"not available");
|
||||
|
||||
return reinterpret_cast<const HashableWitnessTable *>(TheWitnessTable);
|
||||
}
|
||||
|
||||
bool SwiftError::isPureNSError() const {
|
||||
auto TheSwiftNativeNSError = getSwiftNativeNSErrorClass();
|
||||
// We can do an exact type check; _SwiftNativeNSError shouldn't be subclassed
|
||||
@@ -223,6 +283,47 @@ const WitnessTable *SwiftError::getErrorConformance() const {
|
||||
return errorConformance;
|
||||
}
|
||||
|
||||
const Metadata *SwiftError::getHashableBaseType() const {
|
||||
if (isPureNSError()) {
|
||||
return getNSErrorMetadata();
|
||||
}
|
||||
if (auto type = hashableBaseType.load(std::memory_order_acquire)) {
|
||||
if (reinterpret_cast<uintptr_t>(type) == 1) {
|
||||
return nullptr;
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
const Metadata *expectedType = nullptr;
|
||||
const Metadata *hashableBaseType = findHashableBaseType(type);
|
||||
this->hashableBaseType.compare_exchange_strong(
|
||||
expectedType, hashableBaseType ? hashableBaseType
|
||||
: reinterpret_cast<const Metadata *>(1),
|
||||
std::memory_order_acq_rel);
|
||||
return type;
|
||||
}
|
||||
|
||||
const HashableWitnessTable *SwiftError::getHashableConformance() const {
|
||||
if (isPureNSError()) {
|
||||
return getNSErrorConformanceToHashable();
|
||||
}
|
||||
if (auto wt = hashableConformance.load(std::memory_order_acquire)) {
|
||||
if (reinterpret_cast<uintptr_t>(wt) == 1) {
|
||||
return nullptr;
|
||||
}
|
||||
return wt;
|
||||
}
|
||||
|
||||
const HashableWitnessTable *expectedWT = nullptr;
|
||||
const HashableWitnessTable *wt =
|
||||
reinterpret_cast<const HashableWitnessTable *>(
|
||||
swift_conformsToProtocol(type, &_TMps8Hashable));
|
||||
hashableConformance.compare_exchange_strong(
|
||||
expectedWT, wt ? wt : reinterpret_cast<const HashableWitnessTable *>(1),
|
||||
std::memory_order_acq_rel);
|
||||
return wt;
|
||||
}
|
||||
|
||||
/// Extract a pointer to the value, the type metadata, and the Error
|
||||
/// protocol witness from an error object.
|
||||
///
|
||||
|
||||
@@ -17,13 +17,15 @@
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "swift/Runtime/Config.h"
|
||||
|
||||
#if !SWIFT_OBJC_INTEROP
|
||||
|
||||
#include <stdio.h>
|
||||
#include "swift/Runtime/Debug.h"
|
||||
#include "ErrorObject.h"
|
||||
#include "Private.h"
|
||||
|
||||
#if !SWIFT_OBJC_INTEROP
|
||||
|
||||
using namespace swift;
|
||||
|
||||
/// Determine the size and alignment of an Error box containing the given
|
||||
|
||||
53
stdlib/public/runtime/SwiftHashableSupport.h
Normal file
53
stdlib/public/runtime/SwiftHashableSupport.h
Normal file
@@ -0,0 +1,53 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This source file is part of the Swift.org open source project
|
||||
//
|
||||
// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See http://swift.org/LICENSE.txt for license information
|
||||
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef SWIFT_RUNTIME_SWIFT_HASHABLE_SUPPORT_H
|
||||
#define SWIFT_RUNTIME_SWIFT_HASHABLE_SUPPORT_H
|
||||
|
||||
#include "swift/Runtime/Metadata.h"
|
||||
#include <stdint.h>
|
||||
|
||||
namespace swift {
|
||||
namespace hashable_support {
|
||||
|
||||
/// The name demangles to "protocol descriptor for Swift.Hashable".
|
||||
extern "C" const ProtocolDescriptor _TMps8Hashable;
|
||||
|
||||
struct HashableWitnessTable;
|
||||
|
||||
/// Calls `Equatable.==` through a `Hashable` (not Equatable!) witness
|
||||
/// table.
|
||||
extern "C" bool swift_stdlib_Hashable_isEqual_indirect(
|
||||
const void *lhsValue, const void *rhsValue, const Metadata *type,
|
||||
const HashableWitnessTable *wt);
|
||||
|
||||
/// Calls `Hashable.hashValue.get` through a `Hashable` witness table.
|
||||
extern "C" intptr_t swift_stdlib_Hashable_hashValue_indirect(
|
||||
const void *value, const Metadata *type, const HashableWitnessTable *wt);
|
||||
|
||||
/// Find the base type that introduces the `Hashable` conformance.
|
||||
/// Because the provided type is known to conform to `Hashable`, this
|
||||
/// function always returns non-null.
|
||||
///
|
||||
/// - Precondition: `type` conforms to `Hashable` (not checked).
|
||||
const Metadata *findHashableBaseTypeOfHashableType(
|
||||
const Metadata *type);
|
||||
|
||||
/// Find the base type that introduces the `Hashable` conformance.
|
||||
/// If `type` does not conform to `Hashable`, `nullptr` is returned.
|
||||
const Metadata *findHashableBaseType(const Metadata *type);
|
||||
|
||||
} // namespace hashable_support
|
||||
} // namespace swift
|
||||
|
||||
#endif
|
||||
|
||||
@@ -17,7 +17,6 @@ else()
|
||||
endif()
|
||||
|
||||
add_swift_library(swiftStdlibStubs OBJECT_LIBRARY TARGET_LIBRARY
|
||||
AnyHashableSupport.cpp
|
||||
Assert.cpp
|
||||
CommandLine.cpp
|
||||
GlobalObjects.cpp
|
||||
|
||||
@@ -27,6 +27,7 @@ if(("${SWIFT_HOST_VARIANT_SDK}" STREQUAL "${SWIFT_PRIMARY_VARIANT_SDK}") AND
|
||||
Mutex.cpp
|
||||
Enum.cpp
|
||||
Refcounting.cpp
|
||||
Stdlib.cpp
|
||||
${PLATFORM_SOURCES}
|
||||
|
||||
# The runtime tests link to internal runtime symbols, which aren't exported
|
||||
|
||||
26
unittests/runtime/Stdlib.cpp
Normal file
26
unittests/runtime/Stdlib.cpp
Normal file
@@ -0,0 +1,26 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This source file is part of the Swift.org open source project
|
||||
//
|
||||
// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See http://swift.org/LICENSE.txt for license information
|
||||
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "swift/Runtime/Metadata.h"
|
||||
|
||||
using namespace swift;
|
||||
|
||||
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_INTERFACE
|
||||
extern "C" void _swift_stdlib_makeAnyHashableUsingDefaultRepresentation(
|
||||
const OpaqueValue *value,
|
||||
const void *anyHashableResultPointer,
|
||||
const Metadata *T,
|
||||
const WitnessTable *hashableWT
|
||||
) {
|
||||
abort();
|
||||
}
|
||||
|
||||
@@ -115,6 +115,46 @@ AnyHashableTests.test("AnyHashable(${wrapped}).base") {
|
||||
% end
|
||||
% end
|
||||
|
||||
AnyHashableTests.test("AnyHashable(mixed minimal hashables)/Hashable") {
|
||||
var xs: [AnyHashable] = []
|
||||
|
||||
% for wrapped in ['MinimalHashableValue', 'MinimalHashableClass']:
|
||||
xs += (0...5).flatMap {
|
||||
[ ${wrapped}($0, identity: 0),
|
||||
${wrapped}($0, identity: 1) ].map(AnyHashable.init)
|
||||
}
|
||||
% end
|
||||
|
||||
% for wrapped in ['GenericMinimalHashableValue', 'GenericMinimalHashableClass']:
|
||||
${wrapped}_equalImpl.value = {
|
||||
(lhs, rhs) in
|
||||
if let lhs = lhs as? OpaqueValue<Int>,
|
||||
let rhs = rhs as? OpaqueValue<Int> {
|
||||
return lhs.value == rhs.value
|
||||
}
|
||||
return (lhs as! LifetimeTracked) == (rhs as! LifetimeTracked)
|
||||
}
|
||||
${wrapped}_hashValueImpl.value = {
|
||||
payload in
|
||||
if let x = payload as? OpaqueValue<Int> {
|
||||
return x.value
|
||||
}
|
||||
return (payload as! LifetimeTracked).value
|
||||
}
|
||||
% end
|
||||
|
||||
% for wrapped in ['GenericMinimalHashableValue', 'GenericMinimalHashableClass']:
|
||||
% for payload in [ 'OpaqueValue<Int>', 'LifetimeTracked' ]:
|
||||
xs += (0...5).flatMap {
|
||||
[ ${wrapped}(${payload}($0), identity: 0),
|
||||
${wrapped}(${payload}($0), identity: 1) ].map(AnyHashable.init)
|
||||
}
|
||||
% end
|
||||
% end
|
||||
|
||||
checkHashable(xs, equalityOracle: { $0 / 2 == $1 / 2 })
|
||||
}
|
||||
|
||||
% for (kw, name) in [
|
||||
% ('class', 'Class'),
|
||||
% ('struct', 'PODStruct'),
|
||||
@@ -606,7 +646,14 @@ enum MinimalHashableRCSwiftError : Error, Hashable {
|
||||
case caseC(LifetimeTracked)
|
||||
|
||||
var hashValue: Int {
|
||||
return 0
|
||||
switch self {
|
||||
case .caseA:
|
||||
return 10
|
||||
case .caseB:
|
||||
return 20
|
||||
case .caseC:
|
||||
return 30
|
||||
}
|
||||
}
|
||||
|
||||
static func == (
|
||||
@@ -672,15 +719,35 @@ AnyHashableTests.test("AnyHashable(_SwiftNativeNSError(MinimalHashablePODSwiftEr
|
||||
.caseB, .caseB,
|
||||
.caseC, .caseC,
|
||||
]
|
||||
let nsErrors: [NSError] = swiftErrors.map { $0 as NSError }
|
||||
let nsErrors: [NSError] = swiftErrors.flatMap {
|
||||
swiftError -> [NSError] in
|
||||
let bridgedNSError = swiftError as NSError
|
||||
return [
|
||||
bridgedNSError,
|
||||
NSError(domain: bridgedNSError.domain, code: bridgedNSError.code)
|
||||
]
|
||||
}
|
||||
expectEqual(
|
||||
.objCClassWrapper,
|
||||
SwiftRuntime.metadataKind(of: nsErrors.first!))
|
||||
SwiftRuntime.metadataKind(of: nsErrors[0]))
|
||||
expectEqual("_SwiftNativeNSError", String(describing: type(of: nsErrors[0])))
|
||||
checkHashable(nsErrors, equalityOracle: { $0 / 2 == $1 / 2 })
|
||||
|
||||
func equalityOracle(_ lhs: Int, _ rhs: Int) -> Bool {
|
||||
// Swift errors compare equal to the `NSError`s that have the same domain
|
||||
// and code.
|
||||
return lhs / 4 == rhs / 4
|
||||
}
|
||||
|
||||
checkHashable(nsErrors, equalityOracle: equalityOracle)
|
||||
checkHashable(
|
||||
nsErrors.map(AnyHashable.init),
|
||||
equalityOracle: { $0 / 2 == $1 / 2 })
|
||||
equalityOracle: equalityOracle)
|
||||
|
||||
// FIXME(id-as-any): run `checkHashable` on an array of mixed
|
||||
// `AnyHashable(MinimalHashablePODSwiftError)` and
|
||||
// `AnyHashable(_SwiftNativeNSError(MinimalHashablePODSwiftError))`. For
|
||||
// this to succeed, we need to eagerly bridge Swift errors into the Swift
|
||||
// representation when wrapped in `AnyHashable`.
|
||||
}
|
||||
|
||||
AnyHashableTests.test("AnyHashable(_SwiftNativeNSError(MinimalHashablePODSwiftError)).base") {
|
||||
@@ -697,23 +764,59 @@ AnyHashableTests.test("AnyHashable(_SwiftNativeNSError(MinimalHashableRCSwiftErr
|
||||
.caseC(LifetimeTracked(1)), .caseC(LifetimeTracked(1)),
|
||||
.caseC(LifetimeTracked(2)), .caseC(LifetimeTracked(2)),
|
||||
]
|
||||
let nsErrors: [NSError] = swiftErrors.map { $0 as NSError }
|
||||
let nsErrors: [NSError] = swiftErrors.flatMap {
|
||||
swiftError -> [NSError] in
|
||||
let bridgedNSError = swiftError as NSError
|
||||
return [
|
||||
bridgedNSError,
|
||||
NSError(domain: bridgedNSError.domain, code: bridgedNSError.code)
|
||||
]
|
||||
}
|
||||
|
||||
expectEqual(
|
||||
.objCClassWrapper,
|
||||
SwiftRuntime.metadataKind(of: nsErrors.first!))
|
||||
SwiftRuntime.metadataKind(of: nsErrors[0]))
|
||||
expectEqual("_SwiftNativeNSError", String(describing: type(of: nsErrors[0])))
|
||||
expectFailure {
|
||||
// FIXME(id-as-any): make NSError bridging consistent with Swift's notion
|
||||
// of hashing and equality.
|
||||
checkHashable(nsErrors, equalityOracle: { $0 / 2 == $1 / 2 })
|
||||
|
||||
expectEqual(
|
||||
.objCClassWrapper,
|
||||
SwiftRuntime.metadataKind(of: nsErrors[1]))
|
||||
expectEqual("NSError", String(describing: type(of: nsErrors[1])))
|
||||
|
||||
func equalityOracle(_ lhs: Int, _ rhs: Int) -> Bool {
|
||||
// Equality of bridged Swift errors takes the payload into account, so for
|
||||
// a fixed X, Y, all `.caseX(LifetimeTracked(Y))` compare equal.
|
||||
// They also compare equal to the `NSError`s that have the same domain
|
||||
// and code.
|
||||
if lhs / 4 == rhs / 4 {
|
||||
return true
|
||||
}
|
||||
expectFailure {
|
||||
// FIXME(id-as-any): make NSError bridging consistent with Swift's notion
|
||||
// of hashing and equality.
|
||||
// `NSError`s that have the same domain and code as a bridged Swift
|
||||
// error compare equal to all Swift errors with the same domain and code
|
||||
// regardless of the payload.
|
||||
if (lhs % 2 == 1 || rhs % 2 == 1) && (lhs / 8 == rhs / 8) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// FIXME: transitivity is broken because pure `NSError`s can compare equal to
|
||||
// Swift errors with payloads just based on the domain and code, and Swift
|
||||
// errors with payloads don't compare equal when payloads differ.
|
||||
checkHashable(
|
||||
nsErrors,
|
||||
equalityOracle: equalityOracle,
|
||||
allowBrokenTransitivity: true)
|
||||
checkHashable(
|
||||
nsErrors.map(AnyHashable.init),
|
||||
equalityOracle: { $0 / 2 == $1 / 2 })
|
||||
}
|
||||
equalityOracle: equalityOracle,
|
||||
allowBrokenTransitivity: true)
|
||||
|
||||
// FIXME(id-as-any): run `checkHashable` on an array of mixed
|
||||
// `AnyHashable(MinimalHashableRCSwiftError)` and
|
||||
// `AnyHashable(_SwiftNativeNSError(MinimalHashableRCSwiftError))`. For
|
||||
// this to succeed, we need to eagerly bridge Swift errors into the Swift
|
||||
// representation when wrapped in `AnyHashable`.
|
||||
}
|
||||
|
||||
AnyHashableTests.test("AnyHashable(_SwiftNativeNSError(MinimalHashableRCSwiftError)).base") {
|
||||
|
||||
Reference in New Issue
Block a user