// RUN: %target-swift-frontend -typecheck -verify -verify-ignore-unrelated -primary-file %s %S/Inputs/struct_equatable_hashable_other.swift -verify-ignore-unknown -swift-version 4 var hasher = Hasher() struct Point: Hashable { let x: Int let y: Int } func point() { if Point(x: 1, y: 2) == Point(x: 2, y: 1) { } let _: Int = Point(x: 3, y: 5).hashValue Point(x: 3, y: 5).hash(into: &hasher) Point(x: 1, y: 2) == Point(x: 2, y: 1) // expected-warning {{result of operator '==' is unused}} } struct Pair: Hashable { let first: T let second: T func same() -> Bool { return first == second } } func pair() { let p1 = Pair(first: "a", second: "b") let p2 = Pair(first: "a", second: "c") if p1 == p2 { } let _: Int = p1.hashValue p1.hash(into: &hasher) } func localStruct() -> Bool { struct Local: Equatable { let v: Int } return Local(v: 5) == Local(v: 4) } //------------------------------------------------------------------------------ // Verify compiler can derive hash(into:) implementation from hashValue struct CustomHashValue: Hashable { let x: Int let y: Int static func ==(x: CustomHashValue, y: CustomHashValue) -> Bool { return true } func hash(into hasher: inout Hasher) {} } func customHashValue() { if CustomHashValue(x: 1, y: 2) == CustomHashValue(x: 2, y: 3) { } let _: Int = CustomHashValue(x: 1, y: 2).hashValue CustomHashValue(x: 1, y: 2).hash(into: &hasher) } //------------------------------------------------------------------------------ // Verify compiler can derive hashValue implementation from hash(into:) struct CustomHashInto: Hashable { let x: Int let y: Int func hash(into hasher: inout Hasher) { hasher.combine(x) hasher.combine(y) } static func ==(x: CustomHashInto, y: CustomHashInto) -> Bool { return true } } func customHashInto() { if CustomHashInto(x: 1, y: 2) == CustomHashInto(x: 2, y: 3) { } let _: Int = CustomHashInto(x: 1, y: 2).hashValue CustomHashInto(x: 1, y: 2).hash(into: &hasher) } // Check use of an struct's synthesized members before the struct is actually declared. struct UseStructBeforeDeclaration { let eqValue = StructToUseBeforeDeclaration(v: 4) == StructToUseBeforeDeclaration(v: 5) let hashValue = StructToUseBeforeDeclaration(v: 1).hashValue let hashInto: (inout Hasher) -> Void = StructToUseBeforeDeclaration(v: 1).hash(into:) } struct StructToUseBeforeDeclaration: Hashable { let v: Int } func getFromOtherFile() -> AlsoFromOtherFile { return AlsoFromOtherFile(v: 4) } func overloadFromOtherFile() -> YetAnotherFromOtherFile { return YetAnotherFromOtherFile(v: 1.2) } func overloadFromOtherFile() -> Bool { return false } func useStructBeforeDeclaration() { // Check structs from another file in the same module. if FromOtherFile(v: "a") == FromOtherFile(v: "b") {} let _: Int = FromOtherFile(v: "c").hashValue FromOtherFile(v: "d").hash(into: &hasher) if AlsoFromOtherFile(v: 3) == getFromOtherFile() {} if YetAnotherFromOtherFile(v: 1.9) == overloadFromOtherFile() {} } // Even if the struct has only equatable/hashable members, it's not synthesized // implicitly. struct StructWithoutExplicitConformance { let a: Int let b: String } func structWithoutExplicitConformance() { // FIXME(rdar://problem/64844584) - on iOS simulator this diagnostic is flaky // This diagnostic is about `Equatable` because it's considered the best possible solution among other ones for operator `==`. if StructWithoutExplicitConformance(a: 1, b: "b") == StructWithoutExplicitConformance(a: 2, b: "a") { } // expected-error@-1 {{requires that 'StructWithoutExplicitConformance' conform to 'Equatable'}} } // Structs with non-hashable/equatable stored properties don't derive conformance. struct NotHashable {} struct StructWithNonHashablePayload: Hashable { // expected-error 2 {{does not conform}} expected-note {{add stubs for conformance}} let a: NotHashable // expected-note {{stored property type 'NotHashable' does not conform to protocol 'Hashable', preventing synthesized conformance of 'StructWithNonHashablePayload' to 'Hashable'}} // expected-note@-1 {{stored property type 'NotHashable' does not conform to protocol 'Equatable', preventing synthesized conformance of 'StructWithNonHashablePayload' to 'Equatable'}} } // ...but computed properties and static properties are not considered. struct StructIgnoresComputedProperties: Hashable { var a: Int var b: String static var staticComputed = NotHashable() var computed: NotHashable { return NotHashable() } } func structIgnoresComputedProperties() { if StructIgnoresComputedProperties(a: 1, b: "a") == StructIgnoresComputedProperties(a: 2, b: "c") {} let _: Int = StructIgnoresComputedProperties(a: 3, b: "p").hashValue StructIgnoresComputedProperties(a: 4, b: "q").hash(into: &hasher) } // Structs should be able to derive conformances based on the conformances of // their generic arguments. struct GenericHashable: Hashable { let value: T } func genericHashable() { if GenericHashable(value: "a") == GenericHashable(value: "b") { } let _: Int = GenericHashable(value: "c").hashValue GenericHashable(value: "c").hash(into: &hasher) } // But it should be an error if the generic argument doesn't have the necessary // constraints to satisfy the conditions for derivation. struct GenericNotHashable: Hashable { // expected-error 2 {{does not conform to protocol 'Hashable'}} let value: T // expected-note 2 {{stored property type 'T' does not conform to protocol 'Hashable', preventing synthesized conformance of 'GenericNotHashable' to 'Hashable'}} } func genericNotHashable() { if GenericNotHashable(value: "a") == GenericNotHashable(value: "b") { } let gnh = GenericNotHashable(value: "b") let _: Int = gnh.hashValue // No error. hashValue is always synthesized, even if Hashable derivation fails gnh.hash(into: &hasher) // expected-error {{value of type 'GenericNotHashable' has no member 'hash'}} } // Synthesis can be from an extension... struct StructConformsInExtension { let v: Int } extension StructConformsInExtension : Equatable {} // and explicit conformance in an extension should also work. public struct StructConformsAndImplementsInExtension { let v: Int } extension StructConformsAndImplementsInExtension : Equatable { public static func ==(lhs: StructConformsAndImplementsInExtension, rhs: StructConformsAndImplementsInExtension) -> Bool { return true } } // No explicit conformance and it cannot be derived. struct NotExplicitlyHashableAndCannotDerive { let v: NotHashable // expected-note {{stored property type 'NotHashable' does not conform to protocol 'Hashable', preventing synthesized conformance of 'NotExplicitlyHashableAndCannotDerive' to 'Hashable'}} // expected-note@-1 {{stored property type 'NotHashable' does not conform to protocol 'Equatable', preventing synthesized conformance of 'NotExplicitlyHashableAndCannotDerive' to 'Equatable'}} } extension NotExplicitlyHashableAndCannotDerive : Hashable {} // expected-error 2 {{does not conform}} expected-note {{add stubs for conformance}} // A struct with no stored properties trivially derives conformance. struct NoStoredProperties: Hashable {} // Verify that conformance (albeit manually implemented) can still be added to // a type in a different file. extension OtherFileNonconforming: Hashable { static func ==(lhs: OtherFileNonconforming, rhs: OtherFileNonconforming) -> Bool { return true } func hash(into hasher: inout Hasher) {} } // ...but synthesis in a type defined in another file doesn't work yet. extension YetOtherFileNonconforming: Equatable {} // expected-error {{extension outside of file declaring struct 'YetOtherFileNonconforming' prevents automatic synthesis of '==' for protocol 'Equatable'}} expected-note {{add stubs for conformance}} // Verify that we can add Hashable conformance in an extension by only // implementing hash(into:) struct StructConformsAndImplementsHashIntoInExtension: Equatable { let v: String } extension StructConformsAndImplementsHashIntoInExtension: Hashable { func hash(into hasher: inout Hasher) { hasher.combine(v) } } func structConformsAndImplementsHashIntoInExtension() { let _: Int = StructConformsAndImplementsHashIntoInExtension(v: "a").hashValue StructConformsAndImplementsHashIntoInExtension(v: "b").hash(into: &hasher) } struct GenericHashIntoInExtension: Equatable { let value: T } extension GenericHashIntoInExtension: Hashable { func hash(into hasher: inout Hasher) { hasher.combine(value) } } func genericHashIntoInExtension() { let _: Int = GenericHashIntoInExtension(value: "a").hashValue GenericHashIntoInExtension(value: "b").hash(into: &hasher) } // Conditional conformances should be able to be synthesized struct GenericDeriveExtension { let value: T } extension GenericDeriveExtension: Equatable where T: Equatable {} extension GenericDeriveExtension: Hashable where T: Hashable {} // Incorrectly/insufficiently conditional shouldn't work struct BadGenericDeriveExtension { let value: T // expected-note {{stored property type 'T' does not conform to protocol 'Hashable', preventing synthesized conformance of 'BadGenericDeriveExtension' to 'Hashable'}} // expected-note@-1 {{stored property type 'T' does not conform to protocol 'Equatable', preventing synthesized conformance of 'BadGenericDeriveExtension' to 'Equatable'}} } extension BadGenericDeriveExtension: Equatable {} // expected-error@-1 {{type 'BadGenericDeriveExtension' does not conform to protocol 'Equatable'}} // expected-note@-2 {{add stubs for conformance}} extension BadGenericDeriveExtension: Hashable where T: Equatable {} // expected-error@-1 {{type 'BadGenericDeriveExtension' does not conform to protocol 'Hashable'}} // But some cases don't need to be conditional, even if they look similar to the // above struct AlwaysHashable: Hashable {} struct UnusedGenericDeriveExtension { let value: AlwaysHashable } extension UnusedGenericDeriveExtension: Hashable {} // Cross-file synthesis is still disallowed for conditional cases extension GenericOtherFileNonconforming: Equatable where T: Equatable {} // expected-error@-1{{extension outside of file declaring generic struct 'GenericOtherFileNonconforming' prevents automatic synthesis of '==' for protocol 'Equatable'}} // expected-note@-2 {{add stubs for conformance}} // rdar://problem/41852654 // There is a conformance to Equatable (or at least, one that implies Equatable) // in the same file as the type, so the synthesis is okay. Both orderings are // tested, to catch choosing extensions based on the order of the files, etc. protocol ImplierMain: Equatable {} struct ImpliedMain: ImplierMain {} extension ImpliedOther: ImplierMain {} // Hashable conformances that rely on a manual implementation of `hashValue` // should produce a deprecation warning. struct OldSchoolStruct: Hashable { static func ==(left: OldSchoolStruct, right: OldSchoolStruct) -> Bool { return true } var hashValue: Int { return 42 } // expected-warning@-1{{'Hashable.hashValue' is deprecated as a protocol requirement; conform type 'OldSchoolStruct' to 'Hashable' by implementing 'hash(into:)' instead}}{{documentation-file=deprecated-declaration}} } enum OldSchoolEnum: Hashable { case foo case bar static func ==(left: OldSchoolEnum, right: OldSchoolEnum) -> Bool { return true } var hashValue: Int { return 23 } // expected-warning@-1{{'Hashable.hashValue' is deprecated as a protocol requirement; conform type 'OldSchoolEnum' to 'Hashable' by implementing 'hash(into:)' instead}}{{documentation-file=deprecated-declaration}} } class OldSchoolClass: Hashable { static func ==(left: OldSchoolClass, right: OldSchoolClass) -> Bool { return true } var hashValue: Int { return -9000 } // expected-warning@-1{{'Hashable.hashValue' is deprecated as a protocol requirement; conform type 'OldSchoolClass' to 'Hashable' by implementing 'hash(into:)' instead}}{{documentation-file=deprecated-declaration}} } // However, it's okay to implement `hashValue` as long as `hash(into:)` is also // provided. struct MixedStruct: Hashable { static func ==(left: MixedStruct, right: MixedStruct) -> Bool { return true } func hash(into hasher: inout Hasher) {} var hashValue: Int { return 42 } } enum MixedEnum: Hashable { case foo case bar static func ==(left: MixedEnum, right: MixedEnum) -> Bool { return true } func hash(into hasher: inout Hasher) {} var hashValue: Int { return 23 } } class MixedClass: Hashable { static func ==(left: MixedClass, right: MixedClass) -> Bool { return true } func hash(into hasher: inout Hasher) {} var hashValue: Int { return -9000 } } // Ensure equatable and hashable works with weak/unowned properties as well struct Foo: Equatable, Hashable { weak var foo: Bar? unowned var bar: Bar } class Bar { let bar: String init(bar: String) { self.bar = bar } } extension Bar: Equatable, Hashable { static func == (lhs: Bar, rhs: Bar) -> Bool { return lhs.bar == rhs.bar } func hash(into hasher: inout Hasher) {} } // FIXME: Remove -verify-ignore-unknown. // :0: error: unexpected error produced: invalid redeclaration of 'hashValue' // :0: error: unexpected note produced: candidate has non-matching type '(Foo, Foo) -> Bool' // :0: error: unexpected note produced: candidate has non-matching type ' (Generic, Generic) -> Bool' // :0: error: unexpected note produced: candidate has non-matching type '(InvalidCustomHashable, InvalidCustomHashable) -> Bool' // :0: error: unexpected note produced: candidate has non-matching type '(EnumToUseBeforeDeclaration, EnumToUseBeforeDeclaration) -> Bool'