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-6851] Implementing Collection and SetAlgebra on generic class type parameter makes isEmpty ambiguous #49400

Closed
rnapier opened this issue Jan 26, 2018 · 2 comments
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself type checker Area → compiler: Semantic analysis

Comments

@rnapier
Copy link

rnapier commented Jan 26, 2018

Previous ID SR-6851
Radar None
Original Reporter @rnapier
Type Bug
Status Resolved
Resolution Duplicate
Environment

Swift 4.0.3

Additional Detail from JIRA
Votes 0
Component/s Compiler
Labels Bug, TypeChecker
Assignee None
Priority Medium

md5: d4deef45bd5f6d17d61beca23bde4b9d

duplicates:

  • SR-6481 Statically linked stdlib fails for cross-compilation from OSX to Ubuntu

Issue Description:

Given a generic type implementing both Collection and SetAlgebra, a class-restricted type parameter, and parameterizing over a protocol, the definition compiles, but use of `isEmpty` is ambiguous.

//
//  ClassSet.swift
//  audio
//
//  Created by Rob Napier on 1/25/18.
//  Copyright © 2018 Jaybird LLC. All rights reserved.
//

/// Unordered set of unique class objects. Does not require Equatable or Hashable.
struct ClassSet<Element> where Element: AnyObject {
    fileprivate var storage: [ObjectIdentifier: Element] = [:]
    init() {}
}

extension ClassSet: Equatable {
    static func == (lhs: ClassSet<Element>, rhs: ClassSet<Element>) -> Bool {
        return Set(lhs.storage.keys) == Set(rhs.storage.keys)
    }
}

extension ClassSet: SetAlgebra {
    var isEmpty: Bool { return storage.isEmpty }

    func contains(_ member: Element) -> Bool {
        return storage[ObjectIdentifier(member)] != nil
    }

    func union(_ other: ClassSet<Element>) -> ClassSet<Element> {
        var result = self
        result.formUnion(other)
        return result
    }

    func intersection(_ other: ClassSet<Element>) -> ClassSet<Element> {
        var result = self
        result.formIntersection(other)
        return result
    }

    func symmetricDifference(_ other: ClassSet<Element>) -> ClassSet<Element> {
        var result = self
        result.formSymmetricDifference(other)
        return result
    }

    @discardableResult
    mutating func insert(_ newMember: Element) -> (inserted: Bool, memberAfterInsert: Element) {
        let key = ObjectIdentifier(newMember)
        if let existing = storage[key] {
            return (false, existing)
        } else {
            storage[key] = newMember
            return (true, newMember)
        }
    }

    @discardableResult
    mutating func remove(_ member: Element) -> Element? {
        return storage.removeValue(forKey: ObjectIdentifier(member))
    }

    @discardableResult
    mutating func update(with newMember: Element) -> Element? {
        let key = ObjectIdentifier(newMember)
        let oldValue = storage[ObjectIdentifier(newMember)]
        storage[key] = newMember
        return oldValue
    }

    mutating func formUnion(_ other: ClassSet<Element>) {
        storage.merge(other.storage) { (current, _) in current }
    }

    mutating func formIntersection(_ other: ClassSet<Element>) {
        for missingKey in storage.keys where !other.storage.keys.contains(missingKey) {
            storage.removeValue(forKey: missingKey)
        }
    }

    mutating func formSymmetricDifference(_ other: ClassSet<Element>) {
        for member in other {
            if contains(member) {
                remove(member)
            } else {
                insert(member)
            }
        }
    }
}

extension ClassSet: Collection {
    typealias Storage = [ObjectIdentifier: Element]
    typealias Index = Storage.Index
    var startIndex: Index { return storage.startIndex }
    var endIndex: Index { return storage.endIndex }

    subscript(position: Index) -> Element {
        return storage[position].value
    }

    func index(after i: Index) -> Index {
        return storage.index(after: i)
    }
}

class C {}
let cs = ClassSet<C>() // ok
cs.isEmpty  // true

protocol P: AnyObject {}
let ps = ClassSet<P>() // ok
ps.isEmpty  // ambiguous
@belkadan
Copy link
Contributor

The bug here is that you shouldn't be allowed to use a protocol. I know, I know, that's exactly what you were trying to solve, but a protocol has a different representation than AnyObject, and so the generic code won't do the right thing.

@DougGregor, you already started looking into this, right?

@DougGregor
Copy link
Member

Jordan is correct; ClassSet<P> is ill-formed. It's correctly rejected on master and 4.1 as of yesterday, per https://bugs.swift.org/browse/SR-6841 .

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
This issue was closed.
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 type checker Area → compiler: Semantic analysis
Projects
None yet
Development

No branches or pull requests

3 participants