Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Tuples conform to Equatable, Comparable, and Ha...

Avatar for Yuta Saito Yuta Saito
January 22, 2021
280

Tuples conform to Equatable, Comparable, and Hashableの実装

Avatar for Yuta Saito

Yuta Saito

January 22, 2021
Tweet

Transcript

  1. ࠓ·Ͱͷ໰୊ let points = [(x: 128, y: 316), (x: 0,

    y: 0), (x: 100, y: 42)] let origin = (x: 0, y: 0) // error: type '(x: Int, y: Int)' cannot conform to 'Equatable'; // only struct/enum/class types can conform to protocols if points.contains(origin) { // do some serious calculations here } // error: type '(x: Int, y: Int)' cannot conform to 'Comparable'; // only struct/enum/class types can conform to protocols let sortedPoints = points.sorted() // error: type '(x: Int, y: Int)' cannot conform to 'Hashable'; // only struct/enum/class types can conform to protocols let uniquePoints = Set(points) 3
  2. λϓϧ͕͋Δͱࣗಈ࣮૷΋Ͱ͖ͳ͘ͳͬͯͨ struct Restaurant { let name: String let location: (latitude:

    Int, longitude: Int) } // error: type 'Restaurant' does not conform to protocol 'Equatable' extension Restaurant: Equatable {} // error: type 'Restaurant' does not conform to protocol 'Hashable' extension Restaurant: Hashable {} 4
  3. ࣮͸͢ͰʹΦʔόʔϩʔυ͸͋Δ SE-0015: Tuple comparison operators .gybͰίʔυੜ੒͞ΕͯΔɻ @warn_unused_result public func ==

    <A: Equatable, B: Equatable, C: Equatable>(lhs: (A,B,C), rhs: (A,B,C)) -> Bool { return lhs.0 == rhs.0 && lhs.1 == rhs.1 && lhs.2 == rhs.2 } @warn_unused_result public func < <A: Comparable, B: Comparable, C: Comparable>(lhs: (A,B,C), rhs: (A,B,C)) -> Bool { if lhs.0 != rhs.0 { return lhs.0 < rhs.0 } if lhs.1 != rhs.1 { return lhs.1 < rhs.1 } return lhs.2 < rhs.2 } ... 5
  4. func isEqual<T: Equatable>(_ lhs: T, _ rhs: T) -> Bool

    { lhs == rhs } // Ok, Int is Equatable thus the tuples are Equatable isEqual((1, 2, 3), (1, 2, 3)) // true struct EmptyStruct {} // error: type '(EmptyStruct, Int, Int)' does not conform to protocol 'Equatable' // note: value of type 'EmptyStruct' does not conform to protocol 'Equatable', // preventing conformance of '(EmptyStruct, Int, Int)' to 'Equatable' isEqual((EmptyStruct(), 1, 2), (EmptyStruct(), 1, 2)) // We don't take into account the labels for equality. isEqual((x: 0, y: 0), (0, 0)) // true 7
  5. func isLessThan<T: Comparable>(_ lhs: T, _ rhs: T) -> Bool

    { lhs < rhs } let origin = (x: 0, y: 0) let randomPoint = (x: Int.random(in: 1 ... 10), y: Int.random(in: 1 ... 10)) // In this case, the first element of origin is 0 and the first element // of randomPoint is somewhere between 1 and 10, so they are not equal. // origin's element is less than randomPoint's, thus true. isLessThan(origin, randomPoint) // true // We don't take into account the labels for comparison. isLessThan((x: 0, y: 0), (1, 0)) // true 8
  6. let points = [(x: 0, y: 0), (x: 1, y:

    2), (x: 0, y: 0)] let uniquePoints = Set(points) // Create a grid system to hold game entities. var grid = [(x: Int, y: Int): Entity]() for point in uniquePoints { grid[point]?.move(up: 10) } // We don't take into account the labels for hash value. (x: 0, y: 0).hashValue == (0, 0).hashValue // true grid[(x: 100, y: 200)] = Entity(name: "Pam") print(grid[(100, 200)]) // Entity(name: "Pam") 9
  7. func isEqual<T: Equatable>(_ lhs: T, _ rhs: T) -> Bool

    { lhs == rhs } _ = isEqual(1, 2) // false // ↓తͳײ͡ʹίϯύΠϧ͞ΕΔ func isEqual(_ lhs: T, _ rhs: T, type: T.Type, witnessTable: WitnessTable) -> Bool { let fn = witnessTable[Equatable.==] return fn(lhs, rhs) } let Int_Equatable_WitnessTable: WitnessTable = [ Equatable.==: Int.==, ] _ = isEqual(1, 2, Int.self, Int_Equatable_WitnessTable) // false 15
  8. ίϯύΠϧલ public struct Box<T> { public let value: T }

    extension Box: CustomStringConvertible where T: CustomStringConvertible { var description: String { value.description } } func stringify<T: CustomStringConvertible>(_ target: T) -> String { return target.description } func boxStringify<T: CustomStringConvertible>(_ target: T) -> String { return stringify(Box(value: target)) } _ = boxStringify(1) // "1" 17
  9. ίϯύΠϧޙ func stringify(_ target: T, type: T.Type, witnessTable: WitnessTable) ->

    String? { return witnessTable.description(target) } func boxStringify(_ target: T, type: T.Type, witnessTable: WitnessTable) -> String { let witnessTable = swift_getWitnessTable(Box<T>.self, CustomStringConvertible.self) return stringify(Box(value: target), Box<T>.self, witnessTable) } let Int_CustomStringConvertible_WitnessTable: WitnessTable = [ CustomStringConvertible.description: Int.description, ] _ = boxStringify(1, Int.self, Int_CustomStringConvertible_WitnessTable) // "1" 18
  10. swift_conformsToProtocol͸Ͳ͏΍ͬͯWitnessTableΛऔ ಘͯ͠Δͷʁ // ඪ४ϥΠϒϥϦ public protocol CustomStringConvertible { ... }

    public extension Int: CustomStringConvertible { ... } let Int_CustomStringConvertible_WitnessTable: WitnessTable = [ CustomStringConvertible.description: Int.description, ] let localConformances = [ Conformance( type: Int.self, conforming: CustomStringConvertible.self, witnessTable: Int_CustomStringConvertible_WitnessTable ), ... ] _registerProtocolConformances(localConformances) 19
  11. // ϥϯλΠϜϥΠϒϥϦ var globalConformances: [Conformance] = [] func _registerProtocolConformances(_ conformances:

    [Conformance]) { globalConformances.append(conformances) } func swift_getWitnessTable(_ type: Any.Type, _ proto: Protocol.Type) -> WitnessTable? { for conformance in globalConformances { if conformance.type == type && conformance.conforming == proto { return conformance.witnessTable } } return nil } https://github.com/apple/swift/blob/41f74e4/stdlib/public/runtime/ImageInspectionCommon.cpp 20
  12. खॻ͖ Tuple.== bool swift::_swift_tupleEquatable_equals(OpaqueValue *tuple1, OpaqueValue *tuple2, SWIFT_CONTEXT Metadata *swiftSelf,

    Metadata *Self, void *witnessTable) { auto tuple = cast<TupleTypeMetadata>(Self); auto table = reinterpret_cast<void**>(witnessTable); // Loop through all elements, and check if both tuples element is equal. for (size_t i = 0; i != tuple->NumElements; i += 1) { auto elt = tuple->getElement(i); // Get the element conformance from the private data in the witness table. auto conformance = reinterpret_cast<const WitnessTable *>(table[-1 - i]); auto value1 = reinterpret_cast<OpaqueValue *>( reinterpret_cast<char *>(tuple1) + elt.Offset); auto value2 = reinterpret_cast<OpaqueValue *>( reinterpret_cast<char *>(tuple2) + elt.Offset); // Grab the specific witness for this element type. auto equatableTable = reinterpret_cast<void * const *>(conformance); auto equalsWitness = equatableTable[WitnessTableFirstRequirementOffset]; auto equals = reinterpret_cast<StaticInfixWitness *>(equalsWitness); // Call the equal function auto result = equals(value1, value2, elt.Type, elt.Type, conformance); // If the values aren't equal, this tuple isn't equal. :) if (!result) return false; } // Otherwise this tuple has value equality with all elements. return true; } https://github.com/apple/swift/pull/28833/files#diff-c289d2fa8030a8b0648018ab48d060bb32554aa01b96399c406702089ef0033cR148-R184 22
  13. खॻ͖Protocol Conformance Descriptor __asm( #if defined(__ELF__) " .type __swift_tupleEquatable_private, @object\n"

    " .local __swift_tupleEquatable_private\n" " .comm __swift_tupleEquatable_private, 128, 16\n" " .protected " TUPLE_EQUATABLE_CONF "\n" " .type " TUPLE_EQUATABLE_CONF ", @object\n" " .section .rodata\n" #elif defined(__MACH__) " .zerofill __DATA, __bss, __swift_tupleEquatable_private, 128, 4\n" " .section __TEXT, __const\n" #elif defined(_WIN32) " .lcomm __swift_tupleEquatable_private, 128, 16\n" " .section .rdata, \"dr\"\n" #pragma comment(linker, "/EXPORT:_swift_tupleEquatable_conf,DATA") #endif " .globl " TUPLE_EQUATABLE_CONF "\n" " .p2align 2\n" TUPLE_EQUATABLE_CONF ":\n" // This is an indirectable relative reference to the GOT entry for the // Equatable protocol descriptor. " .long " INDIRECT_RELREF_GOTPCREL(EQUATABLE_DESCRIPTOR_SYMBOL) "\n" // 769 is the MetadataKind::Tuple " .long 769\n" // This indicates that we have no witness table pattern. We use a generic // witness table for builtin conformances. " .long 0\n" // 196640 are the ConformanceFlags with the type reference bit set to // MetadataKind, the has resilient witness bit, and the generic witness table // bit. " .long 196640\n" // This 1 is the ResilientWitnessesHeader indicating we have 1 resilient // witness. " .long 1\n" // This is an indirectable relative reference to the GOT entry for the // Equatable == method descriptor. " .long " INDIRECT_RELREF_GOTPCREL(EQUATABLE_EE_METHOD_DESCRIPTOR) "\n" // This is a direct relative reference to the equals witness defined below. " .long (" TUPLE_EQUATABLE_EQUALS ") - .\n" // The witness table size in words. " .short 0\n" // The witness table private size in words & requires instantiation. " .short 1\n" // The witness table instantiator function. " .long 0\n" // This is a direct relative reference to the private data for the // conformance. " .long __swift_tupleEquatable_private - .\n" #if defined(__ELF__) " .size " TUPLE_EQUATABLE_CONF ", 40\n" #endif ); 23
  14. extern "C" const ProtocolConformanceDescriptor _swift_tupleEquatable_conf; extern "C" const ProtocolConformanceDescriptor _swift_tupleComparable_conf;

    extern "C" const ProtocolConformanceDescriptor _swift_tupleHashable_conf; static const ProtocolConformanceDescriptor *getTupleConformanceDescriptor( const ProtocolDescriptor *protocol) { if (protocol == &EQUATABLE_DESCRIPTOR) { return &_swift_tupleEquatable_conf; } if (protocol == &COMPARABLE_DESCRIPTOR) { return &_swift_tupleComparable_conf; } if (protocol == &HASHABLE_DESCRIPTOR) { return &_swift_tupleHashable_conf; } return nullptr; } static const WitnessTable * swift_conformsToProtocolImpl(const Metadata *const type, const ProtocolDescriptor *protocol) { ... // If we're asking if a tuple conforms to a protocol, handle that here for // builtin conformances. if (auto tuple = dyn_cast<TupleTypeMetadata>(type)) { if (tupleConformsToProtocol(tuple, protocol)) { auto descriptor = getTupleConformanceDescriptor(protocol); C.cacheResult(type, protocol, descriptor->getWitnessTable(type), /*always cache*/ 0); } } ... } 24
  15. ੩తͳProtocol Conformance Descriptorͷࢀর std::string IRGenMangler::mangleProtocolConformanceDescriptor( const RootProtocolConformance *conformance) { //

    Builtin conformances are different because they don't use a mangled name // for their conformance descriptors. For now, we use predefined symbol names // just in case in the future we have some conflict between actual // conformances and these builtin ones. if (isa<BuiltinProtocolConformance>(conformance)) { auto &ctx = conformance->getType()->getASTContext(); if (conformance->getType()->is<TupleType>()) { auto equatable = ctx.getProtocol(KnownProtocolKind::Equatable); auto comparable = ctx.getProtocol(KnownProtocolKind::Comparable); auto hashable = ctx.getProtocol(KnownProtocolKind::Hashable); if (conformance->getProtocol() == equatable) { return "_swift_tupleEquatable_conf"; } if (conformance->getProtocol() == comparable) { return "_swift_tupleComparable_conf"; } if (conformance->getProtocol() == hashable) { return "_swift_tupleHashable_conf"; } llvm_unreachable("mangling conformance descriptor for unknown tuple \ protocol"); } llvm_unreachable("mangling conformance descriptor for unknown builtin type"); } ... } 25