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-6841] SetAlgebra prevents enforcing AnyObject constraint #49390

Closed
rnapier opened this issue Jan 25, 2018 · 3 comments
Closed

[SR-6841] SetAlgebra prevents enforcing AnyObject constraint #49390

rnapier opened this issue Jan 25, 2018 · 3 comments
Assignees
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior.

Comments

@rnapier
Copy link

rnapier commented Jan 25, 2018

Previous ID SR-6841
Radar rdar://problem/36884025
Original Reporter @rnapier
Type Bug
Status Resolved
Resolution Done
Environment

Apple Swift version 4.0.3 (swiftlang-900.0.74.1 clang-900.0.39.2)

OS X 10.13.2

Xcode 9.2

Additional Detail from JIRA
Votes 0
Component/s
Labels Bug
Assignee @DougGregor
Priority Medium

md5: 1987236c74d9f90f8c472c5250128184

Issue Description:

A generic struct that is constrained to an AnyObject type parameter, and also conforms to SetAlgebra, will cause the compiler to miss non-conforming type parameters during initialization, leading to spurious errors and crashes on later use.

Consider the following type. Most of this is just required SetAlgebra conformance. The important parts are: conforms to SetAlgebra, and Element is constrained to AnyObject (i.e. must be a class).

struct ClassSet<Element: AnyObject>: SetAlgebra {
    fileprivate var storage: [ObjectIdentifier: Element] = [:]
    init() {}
    var isEmpty: Bool { return storage.isEmpty }

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

    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
    }

    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)
        }
    }

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

    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>) {
        let keys = Set(storage.keys).symmetricDifference(other.storage.keys)
        for missingKey in storage.keys where !keys.contains(missingKey) {
            storage.removeValue(forKey: missingKey)
        }
    }
}

When constructed with a class, this type behaves as expected:

var s1 = ClassSet<NSString>()
s1.update(with: "") // nil
s1.isEmpty  // false

However, when constructed with a struct, the compiler does not generate an error at the point of construction, but does generate spurious errors later.

var s2 = ClassSet<String>()  // no error
//s2.update(with: "")  // cannot use mutating member on immutable value
s2.isEmpty  // true

When constructed with an array literal, it will crash rather than generating a compiler error.

var s3: ClassSet<String> = [""] // EXC_BAD_ACCESS
@jckarter
Copy link
Member

@swift-ci create

@DougGregor
Copy link
Member

Terrifying. We're simply not checking this constraint:

https://github.com/apple/swift/blob/master/lib/Sema/TypeCheckGeneric.cpp#L1383-L1387

@DougGregor
Copy link
Member

4.1 branch PR: #14178

@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.
Projects
None yet
Development

No branches or pull requests

3 participants