Skip to content

Conversation

@Brennanium
Copy link
Contributor

@Brennanium Brennanium commented Nov 1, 2025

Fix for issue #583 by both constraining and opening the existential type and using stored reference instead of storage for generic functions

Making reference a non-optional constrained existential instead of Any? type seems to work for all of the test cases I could come up with, and keeping the pointee in the autoclosure as the reference in the enableDefaultImplementation<>(mutating:) init could possibly cause a retain cycle or something? But since we're already capturing the UnsafeMutablePointer for getters and setters anyways, it seemed like an ok thing try.

@Brennanium
Copy link
Contributor Author

Brennanium commented Nov 1, 2025

Given this example protocol:

protocol GenericProtocol<C> {
    associatedtype C
    associatedtype V
    func genericParameter<T>(value: T)
    func genericParameterWithAssociatedType<T>(value: T, theC: C, theV: V) -> V
}

Here's a diff of how it would generate before (with compile errors) vs how it generates now:

...
class DefaultImplCaller: GenericProtocol, @unchecked Sendable {
-    private let reference: Any
+    private let reference: () -> any GenericProtocol<C>

-    init<_CUCKOO$$GENERIC: GenericProtocol>(from defaultImpl: UnsafeMutablePointer<_CUCKOO$$GENERIC>, keeping reference: @escaping @autoclosure () -> Any?) where _CUCKOO$$GENERIC.C == C, _CUCKOO$$GENERIC.V == V {
+    init<_CUCKOO$$GENERIC: GenericProtocol>(from defaultImpl: UnsafeMutablePointer<_CUCKOO$$GENERIC>, keeping reference: @escaping @autoclosure () -> _CUCKOO$$GENERIC) where _CUCKOO$$GENERIC.C == C, _CUCKOO$$GENERIC.V == V {
        self.reference = reference
    
-        _storage$1$genericParameter = defaultImpl.pointee.genericParameter // error: Generic parameter 'T' could not be inferred
-        _storage$2$genericParameterWithAssociatedType = defaultImpl.pointee.genericParameterWithAssociatedType // error: Generic parameter 'T' could not be inferred
    }

-    private let _storage$1$genericParameter: (T) -> Void // error: Cannot find type 'T' in scope
    func genericParameter<T> (value p0: T) {
-        return _storage$1$genericParameter(p0)
+.       return reference().genericParameter(p0) 
    }
-    private let _storage$2$genericParameterWithAssociatedType: (T, C, V) -> V // error: Cannot find type 'T' in scope
    func genericParameterWithAssociatedType <T> (value p0: T, theC p1: C, theV p2: V) -> V {
+        func openExistential<_CUCKOO$$GENERIC: GenericProtocol<C>>(_ opened: _CUCKOO$$GENERIC) -> V {
+            return opened.genericAndAassociatedTypeParameters(value: p0, theC: p1, theV: p2 as! _CUCKOO$$GENERIC.V) as! V
+        }
+        return openExistential(reference())
-        return _storage$2$genericParameterWithAssociatedType(p0)
    }
}
...

Copy link
Collaborator

@MatyasKriz MatyasKriz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have some nitpicks about the code so that it fits into the rest of the project, but I wasn't able to run it locally yet (have to download iOS 26.1 runtime first). I'll finish the review then.

In the meantime, thanks for this PR! It fixes the thing I set out to do with addition of generics in the first place, but lacked the time to bring to fruition, so kudos. 🙂

@via-guy
Copy link

via-guy commented Nov 28, 2025

@Brennanium is there a way that this could also cover a protocol with same-type values? E.g.

protocol SpecificProtocol: GenericProtocol where C == String, V == Int {}

@Brennanium
Copy link
Contributor Author

@MatyasKriz Glad to help! 😊 I've addressed all your nitpick suggestions in a follow-up commit; let me know if you encounter any other issues when you manage to run it locally.

@Brennanium
Copy link
Contributor Author

@via-guy I looked into what it would take to get mocks generated for protocols with same-type requirements working, and unfortunately it appears to mostly be unrelated to the changes I'm making here in this PR. Tackling the problem would likely involve beefing up the Token.mergingInheritance(with:) logic which currently traverses all the inherited types only to collect the member tokens. You'd need to add some new complex behavior merging and diffing all of the inherited associated types + primary associated types and checking them against all the same-type requirements. It's probably doable, but I'd say out of the scope of this specific issue.

@MatyasKriz MatyasKriz merged commit b923209 into Brightify:master Dec 5, 2025
3 checks passed
@MatyasKriz
Copy link
Collaborator

@MatyasKriz Glad to help! 😊 I've addressed all your nitpick suggestions in a follow-up commit; let me know if you encounter any other issues when you manage to run it locally.

Perfect! I haven't found any blockers, so I've just merged this and will squash and release. Thanks for the help, @Brennanium!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants