Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SR-8672] Hashable struct in function #51186

Closed
swift-ci opened this issue Aug 31, 2018 · 5 comments
Closed

[SR-8672] Hashable struct in function #51186

swift-ci opened this issue Aug 31, 2018 · 5 comments
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself conformances Feature → protocol: protocol conformances duplicate Resolution: Duplicates another issue nested types Feature: nested types operators Feature: operators type checker Area → compiler: Semantic analysis unexpected behavior Bug: Unexpected behavior or incorrect output

Comments

@swift-ci
Copy link
Collaborator

swift-ci commented Aug 31, 2018

Previous ID SR-8672
Radar rdar://problem/43963469
Original Reporter Max (JIRA User)
Type Bug
Additional Detail from JIRA
Votes 0
Component/s Compiler
Labels Bug, TypeChecker
Assignee None
Priority Medium

md5: 7b3ca9a11387440e866eac5a2c2b0cdc

relates to:

  • SR-13040 Set collection not working as expected
  • SR-8696 Types nested in functions and their operator functions.

Issue Description:

Link to Swift forum discussion : link

Hello ! I would like to have some clarifications about why Set does not interact properly with a Hashable struct declared in a function.

Here I have a struct Hello that conforms to Hashable and only needs its id for the hashValue. Because structs need to have Equatable custom conformance to have custom hash behavior, so a == function is declared.
I first declare a Set with an identifier 123 and the I try to insert another struct having the same identifier with different "value". It does not insert the new element because it is considered as the same by the equality.

struct Hello: Hashable {
    let id: Int
    let value: String    var hashValue: Int {
        return id.hashValue
    }    init(id: Int, value: String) {
        self.id = id
        self.value = value
    }    static func == (lhs: Hello, rhs: Hello) -> Bool {
        return lhs.id == rhs.id
    }
}

func testFunc() {
    var set = Set<Hello>([Hello(id: 123, value: "Hello")])
    // Set is now `[Hello #&#8203;1 in __lldb_expr_104.testFunc() -> ()(id: 123, value: "Hello")]`
    set.insert(Hello(id: 123, value: "World"))
    // Set is still `[Hello #&#8203;1 in __lldb_expr_104.testFunc() -> ()(id: 123, value: "Hello")]`.
}

testFunc()

Here I have a struct Hello that conforms to Hashable and only needs its id for the hashValue and it is declared in a function. The struct is the same as the previous example but it does not work, the set keep inserting it while the id is the same. The custom comparison operand is not used so I suspect that the default implementation is called instead.

func testFunc() {    

    struct Hello: Hashable {
        let id: Int
        let value: String        
        var hashValue: Int {
            return id.hashValue
        }

        init(id: Int, value: String) {
            self.id = id
            self.value = value
        }

        static func == (lhs: Hello, rhs: Hello) -> Bool {
            return lhs.id == rhs.id
        }
    }

    var set = Set<Hello>([Hello(id: 123, value: "Hello")])
    // Set is now `[Hello #&#8203;1 in __lldb_expr_104.testFunc() -> ()(id: 123, value: "Hello")]`
    set.insert(Hello(id: 123, value: "World"))
    // Set is now [Hello #&#8203;1 in __lldb_expr_115.testFunc() -> ()(id: 123, value: "Hello"), Hello #&#8203;1 in __lldb_expr_115.testFunc() -> ()(id: 123, value: "World")]
}

testFunc()
@airspeedswift
Copy link
Member

I think the problem lies in the Equatable (over-)synthesis. If you put a fatalError in both hashValue and ==, it will trap inside hashValue� but not inside ==.

@belkadan
Copy link
Contributor

belkadan commented Sep 1, 2018

@swift-ci create

@rudkx
Copy link
Member

rudkx commented Sep 5, 2018

It looks like we derive an == even if one was provided if the type is defined inside of a function. That derived function ends up in the witness table:

func equal<T: Equatable>(_ lhs: T, _ rhs: T) -> Bool {
  return lhs == rhs
}

func test() {
  struct Pair : Equatable {
    var key: Int
    var value: String

    static func == (_ lhs: Pair, _ rhs: Pair) -> Bool {
      // Yes, only compare the keys - does not guarantee substitutability of the two values.
      return lhs.key == rhs.key
    }
  }

  let lhs = Pair(key: 1, value: "hi")
  let rhs = Pair(key: 1, value: "bye")

  print(equal(lhs, rhs))
}

test()

@jepers
Copy link

jepers commented Sep 5, 2018

I bumped into the same issue and found that it has to do with type scope operators not working if the type is moved into function scope:

For example, this works:

struct S {
    var v: Int
    static func +(lhs: S, rhs: S) -> S { return S(v: lhs.v + rhs.v) }
}
func foo() {
    print(S(v: 123) + S(v: 456)) // 579
}
foo()

But this doesn't:

func foo() {
    struct S {
        var v: Int
        static func +(lhs: S, rhs: S) -> S { return S(v: lhs.v + rhs.v) }
    }
    print(S(v: 123) + S(v: 456)) // ERROR: Binary operator '+' cannot be applied to two 'S' operands
}
foo()

I first encountered this by not being able to conform a function body struct to Hashable, but the root of that issue seems to be what I described above. I found this bug via related forum posts to my post here: https://forums.swift.org/t/type-scope-operators-doesnt-work-if-type-is-declared-in-function-scope-bug/15849

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
@AnthonyLatsis
Copy link
Collaborator

Duplicate of #45682

@AnthonyLatsis AnthonyLatsis marked this as a duplicate of #45682 Jun 7, 2022
@AnthonyLatsis AnthonyLatsis added duplicate Resolution: Duplicates another issue conformances Feature → protocol: protocol conformances operators Feature: operators nested types Feature: nested types unexpected behavior Bug: Unexpected behavior or incorrect output labels Feb 22, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself conformances Feature → protocol: protocol conformances duplicate Resolution: Duplicates another issue nested types Feature: nested types operators Feature: operators type checker Area → compiler: Semantic analysis unexpected behavior Bug: Unexpected behavior or incorrect output
Projects
None yet
Development

No branches or pull requests

6 participants