blob: a5b80178815e5628c8d4460204130547b0285655 [file] [log] [blame]
// RUN: %target-swift-frontend -typecheck -verify -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<T: Hashable>: 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() {
if StructWithoutExplicitConformance(a: 1, b: "b") == StructWithoutExplicitConformance(a: 2, b: "a") { } // expected-error{{binary operator '==' cannot be applied to two 'StructWithoutExplicitConformance' operands}}
// expected-note @-1 {{overloads for '==' exist with these partially matching parameter lists: }}
}
// Structs with non-hashable/equatable stored properties don't derive conformance.
struct NotHashable {}
struct StructWithNonHashablePayload: Hashable { // expected-error 2 {{does not conform}}
let a: NotHashable
}
// ...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<T: Hashable>: Hashable {
let value: T
}
func genericHashable() {
if GenericHashable<String>(value: "a") == GenericHashable<String>(value: "b") { }
let _: Int = GenericHashable<String>(value: "c").hashValue
GenericHashable<String>(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<T: Equatable>: Hashable { // expected-error 2 {{does not conform to protocol 'Hashable'}}
let value: T
}
func genericNotHashable() {
if GenericNotHashable<String>(value: "a") == GenericNotHashable<String>(value: "b") { }
let gnh = GenericNotHashable<String>(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<String>' 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
}
extension NotExplicitlyHashableAndCannotDerive : Hashable {} // expected-error 2 {{does not conform}}
// 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 {{cannot be automatically synthesized in an extension in a different file to the type}}
// 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<T: Hashable>: Equatable {
let value: T
}
extension GenericHashIntoInExtension: Hashable {
func hash(into hasher: inout Hasher) {
hasher.combine(value)
}
}
func genericHashIntoInExtension() {
let _: Int = GenericHashIntoInExtension<String>(value: "a").hashValue
GenericHashIntoInExtension(value: "b").hash(into: &hasher)
}
// Conditional conformances should be able to be synthesized
struct GenericDeriveExtension<T> {
let value: T
}
extension GenericDeriveExtension: Equatable where T: Equatable {}
extension GenericDeriveExtension: Hashable where T: Hashable {}
// Incorrectly/insufficiently conditional shouldn't work
struct BadGenericDeriveExtension<T> {
let value: T
}
extension BadGenericDeriveExtension: Equatable {}
// expected-error@-1 {{type 'BadGenericDeriveExtension<T>' does not conform to protocol 'Equatable'}}
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<T>: Hashable {}
struct UnusedGenericDeriveExtension<T> {
let value: AlwaysHashable<T>
}
extension UnusedGenericDeriveExtension: Hashable {}
// Cross-file synthesis is still disallowed for conditional cases
extension GenericOtherFileNonconforming: Equatable where T: Equatable {}
// expected-error@-1{{implementation of 'Equatable' cannot be automatically synthesized in an extension in a different file to the type}}
// 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}}
}
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}}
}
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}}
}
// 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.
// <unknown>:0: error: unexpected error produced: invalid redeclaration of 'hashValue'
// <unknown>:0: error: unexpected note produced: candidate has non-matching type '(Foo, Foo) -> Bool'
// <unknown>:0: error: unexpected note produced: candidate has non-matching type '<T> (Generic<T>, Generic<T>) -> Bool'
// <unknown>:0: error: unexpected note produced: candidate has non-matching type '(InvalidCustomHashable, InvalidCustomHashable) -> Bool'
// <unknown>:0: error: unexpected note produced: candidate has non-matching type '(EnumToUseBeforeDeclaration, EnumToUseBeforeDeclaration) -> Bool'