vagrant
vagrant

Reputation: 988

Do not understand "Member '<func>' cannot be used on value of type 'any <type>'; consider using a generic constraint instead" error

Having a problem with a protocol & generics that I am just not able to quite get a handle on.

In the code below, marked by ERROR HERE comment, I am getting the following error:

Member 'protocolMethod' cannot be used on value of type 'any Protocol1'; consider using a generic constraint instead

I assume it is complaining mainly because of the type of the item parameter is at least in some way unresolved? And I am unfortunately not finding the suggestion helpful as I don't see how a generic constraint is going to help here (at least to my understanding of them).

Honestly I kind of feel like I might just be asking too much of Swift here.

Anybody perhaps see what the problem is or have suggestions for trying to resolve this?

Added 12/26/22 - As further background, what caused the error to appear was adding the item param to the protocolMethod method on the protocol, which pretty much shows it is the core of the issue.

protocol Protocol1
{
    associatedtype DataItem
    func protocolMethod(item : DataItem)
}

protocol Protocol2 {
    associatedtype AType1: Hashable
    //...
}

class Class1<Type1: Protocol2>: NSObject
{
    
    typealias Item = Type1.AType1
    
    var delegate : (any Protocol1)?
     
    private func method1(item: Item)
    {
        delegate?.protocolMethod(item : item)  //ERROR HERE
    }
}

(using latest Xcode)

Upvotes: 16

Views: 9509

Answers (2)

Kiryl Famin
Kiryl Famin

Reputation: 337

Why does the error occur

If the error didn't happen, you could set any var delegate: (any Protocol1)? even though you specify the type of Item on Class1 initialization.

This means you could initialize Class1 with some conformant to Protocol2:

struct IntProtocol2Conformant: Protocol2 {
    typealias AType1 = Int
}

let obj = Class1<MyProtocol2Type>()

Note, that obj.Item is Int.

After, you could swap your delegate to any Protocol1 conformant, not just Int:

struct StringProtocol1Conformant: Protocol1 {
    typealias DataItem = String
    protocolMethod(item : DataItem)
}

obj.delegate = StringProtocol2Conformant()

So now your Item is still Int and you could call

obj.method1(item: 42)

Of course it would lead to another error because your delegate is supposed to get a String now.

The key mistake

You're allowing delegate to be any Protocol1 while Item is set to Type1.AType1 on initialization, there's no connection between it and a delegate you set after.

Solution

Strictly specifying that Delegate.DataItem == Type1.AType1 on initialization. Now you can only use a delegate that has the same DataItem as your Type1.AType1:

class Class1<Type1: Protocol2, Delegate: Protocol1>: NSObject where Delegate.DataItem == Type1.AType1 {
    
    typealias Item = Type1.AType1
    
    var delegate: Delegate?
    
    private func method1(item: Item) {
        delegate?.protocolMethod(item: item)
    }
}

Upvotes: 0

AnderCover
AnderCover

Reputation: 2661

You get this error because when you write:

var delegate : (any Protocol1)?

You don't give the compiler any information about what DataItem is. It can be anything, and it should not matter in your Class1 implementation.

But when you write:

private func method1(item: Item)
{
    delegate?.protocolMethod(item : item)  //ERROR HERE
}

You are trying to pass an object of type Type1.AType1 as an argument of a method that expects a DataItem, and since delegate can be any Protocol1 you have no guarantee that they are the same.

The error message actually proposes a solution:

Member 'protocolMethod' cannot be used on value of type 'any Protocol1'; consider using a generic constraint instead

You could add another type parameter to Class1 and add a generic constraint to tell the compiler that the AType1 and DataItem need to be equal:

class Class1<Type1: Protocol1, Type2: Protocol2>: NSObject where Type1.DataItem == Type2.AType1
{
    
    typealias Item = Type2.AType1
    
    var delegate : Type1?
     
    private func method1(item: Item)
    {
        delegate?.protocolMethod(item : item) 
    }
}

Upvotes: 11

Related Questions