swiftlang / swift

The Swift Programming Language
https://swift.org
Apache License 2.0
67.27k stars 10.33k forks source link

[SR-2817] selector mismatch with obj-c delegates + extensions #45421

Open swift-ci opened 7 years ago

swift-ci commented 7 years ago
Previous ID SR-2817
Radar rdar://28579406
Original Reporter cezarywojcik (JIRA User)
Type Bug
Additional Detail from JIRA | | | |------------------|-----------------| |Votes | 5 | |Component/s | | |Labels | Bug | |Assignee | None | |Priority | Medium | md5: 9c5b0f645140d6f33adb8b7c263e85dd

is duplicated by:

Issue Description:

Summary:
When protocol conformance functions are added within an extension that doesn't explicitly declare itself to conform to this protocol, you get an error that says that the obj-c selectors don't match. This is weird, but at least we get an error. Even worse is that if this happens in a subclass, it fails silently, which can cause hard-to-find errors.

Steps to Reproduce:
Case 1: There is an error:

belkadan commented 7 years ago

@DougGregor, you changed this, right?

swift-ci commented 7 years ago

Comment by Cezary Wojcik (JIRA)

Some context here: since this used to work in Swift 2.3, in the migration from 2.3 to 3.0, there are now a LOT of hard-to-find silent bugs that have been introduced as a result of the migration. It's pretty scary.

swift-ci commented 7 years ago

Comment by Jacek (JIRA)

+1 this is very unpleasant

swift-ci commented 7 years ago

Comment by Alex Matjuk (JIRA)

• 2nd case is not a bug because method is optional.
• 1st case could be fixed by adding @objc(collectionView:layout:sizeForItemAtIndexPath: ) attribute (as Xcode already suggests). But I agree such conversion should be applied automatically

swift-ci commented 7 years ago

Comment by Alex Matjuk (JIRA)

Actually bugs are:
1) no warning to add renaming @objc attribute when method is implemented inside class itself:

class Test: UIViewController, UICollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return CGSize(width: 150.0, height: 150.0)
    }
}

2) no warning to add renaming @objc attribute when method is implemented inside class extension which adds protocol conformance:

extension Test: UICollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return CGSize(width: 150.0, height: 150.0)
    }
}

3) no warning to add renaming @objc attribute in subclass both inside class itself

class Test: UIViewController, UICollectionViewDelegateFlowLayout {}

class TestSubclass: Test {
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return CGSize(width: 150.0, height: 150.0)
    }
}

or its extension

class Test: UIViewController, UICollectionViewDelegateFlowLayout {}

class TestSubclass: Test {}

extension TestSubclass  {
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return CGSize(width: 150.0, height: 150.0)
    }
}
swift-ci commented 7 years ago

Comment by Alex Matjuk (JIRA)

Is reproducible with any protocol which uses NS_SWIFT_NAME. E.g.

@protocol TestProtocol <NSObject>

- (void) objc_protocolMethod NS_SWIFT_NAME(swift_protocolMethod()) ;

@end
swift-ci commented 7 years ago

Comment by Daniel Resnick (JIRA)

This appears to be fixed in Swift 3.0.1 for normal subclasses, but is not fixed for generic subclasses:

class A: NSObject, UITableViewDelegate {}

class B: A {
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {}
}

class C<T>: A {
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {}
}

print(#selector(B.tableView(_:didSelectRowAt:))) // tableView:didSelectRowAtIndexPath:
print(#selector(C<Int>.tableView(_:didSelectRowAt:))) // tableView:didSelectRowAt: