swiftlang / swift

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

[SR-15468] Conditional protocol extension can't declare valid function #57773

Open groue opened 2 years ago

groue commented 2 years ago
Previous ID SR-15468
Radar rdar://problem/85204580
Original Reporter @groue
Type Bug
Environment Xcode Version 13.1 (13A1030d) and 13.2 beta
Additional Detail from JIRA | | | |------------------|-----------------| |Votes | 0 | |Component/s | Compiler | |Labels | Bug | |Assignee | None | |Priority | Medium | md5: ad5aa2c32125d9edc82a537a3c9db201

Issue Description:

Hello,

I don't quite know how to call this issue. I have a minimal reproducible case:

The compiler emits Type 'Self' does not conform to protocol 'R' on the declarations of the functions argument and returnValue. If should not, because the compiler was totally OK with the other functions noArgument and collectionArgument.

protocol P { associatedtype A = Void }        // 1
protocol R { associatedtype B }               // 2
extension P where Self: R { typealias A = B } // 3
extension P {                                 // 4
    func noArgument() {
        print("ok")
    }

    func collectionArgument<C>(_ c: C) where C: Collection, C.Element == A {
        print("ok")
    }

    // NOT OK: Type 'Self' does not conform to protocol 'R'
    // func argument(_ a: A) { print("ok") }

    // NOT OK: Type 'Self' does not conform to protocol 'R'
    // func returnValue() -> A { fatalError() }
}

struct S: P, R {
    typealias B = Int
}
S().noArgument()               // prints "ok"
S().collectionArgument([1, 2]) // prints "ok"
// S().argument(1)             // Can't call it
// S().returnValue()           // Can't call it

This is a minimum reproducible case, so it is pretty abstract.

It has real consequences, though. In the GRDB library, I would like to use Identifiable for R (line 2).

From Identifiable conformance, I want to derive a specific default value for the associated type P.A (line 3). In GRDB, it means that when a database record type conforms to Identifiable, ID is the type of its primary key.

This is supposed to enable extra methods on P that are automatically fed with `ID` type in both parameter and return position (line 4).

In practice, GRDB would add more conditions, but the sample code above is the smaller one I could build that reveals the issue.

I'm ready to answer further questions if necessary.

groue commented 2 years ago

Here is a more complete snippet, where P and Q are still pretty abstract, but everything else is what I need (the use of `identifiable` and the `Optional` unwrapping):

(Note that this code compiles and runs fine. I just can not declare the extra commented methods (and I wish I could):

protocol P { associatedtype A = Void }
protocol Q { }
protocol _OptionalProtocol { associatedtype Wrapped }
extension Optional: _OptionalProtocol { }
extension P where Self: Identifiable, ID: Q { typealias A = ID }
extension P where Self: Identifiable, ID: _OptionalProtocol, ID.Wrapped: Q { typealias A = ID.Wrapped }
extension P where A: Q {
    static func collectionArgument<C>(_ c: C) where C: Collection, C.Element == A {
        print("ok")
    }

    // NOT OK: Type 'Self' does not conform to protocol 'R'
    // func argument(_ a: A) { print("ok") }

    // NOT OK: Type 'Self' does not conform to protocol 'R'
    // func returnValue() -> A { fatalError() }
}

extension Int: Q { }

do {
    struct S: P, Identifiable {
        var id: Int
    }
    S.collectionArgument([1, 2])
}
do {
    struct S: P, Identifiable {
        var id: Int?
    }
    S.collectionArgument([1, 2]) // Only non-nil values allowed
}
slavapestov commented 2 years ago

Looks like name lookup is finding the typealias A and not the associatedtype A, even though the bounds on the typealias are not satisfied from that context.

slavapestov commented 2 years ago

@swift-ci create

groue commented 2 years ago

For the record, the Twitter conversation with @slavapestov: https://twitter.com/groue/status/1458111183513440259 (thank you!)

groue commented 2 years ago

@slavapestov, I could find a workaround, but immediately stumbled upon this other bug: https://bugs.swift.org/browse/SR-15473

The workaround is to replace, in the sample code of the first post:

-extension P where Self: R { typealias A = B }
+extension R where Self: P { typealias A = B }

I'm very sorry SR-15473 is another blocker.

groue commented 2 years ago

I was mislead. SR-15473 is not a bug. I'm just pursuing a goal that can't play well with classes.