Kaos
Kaos

Reputation: 1004

Protocol implementation class type check

I haven't been able to figure out if this is at all possible.

Given the following code, I'd like mypy to report error for both cases:

from dataclasses import dataclass
from typing import Generic, Protocol, Type, TypeVar

T = TypeVar("T")

class MyProto(Protocol):
    attr: str

class Impl:
    attr: int  # wrong type on purpose for this example

# This util class is an attempt to illustrate what I'm trying to achieve, an answer may very well
# indicate changes to it, if required, to make it work as desired.
@dataclass
class Util(Generic[T]):
   proto: T
   impl: Type[T]

# Case 1
# The following is type checked, and correctly reports the error when the implementation does not
# satisfy the protocol.
C: Type[MyProto] = Impl 

# Case 2
# Where as this is accepted regardless, but I'd like it to report error in case Impl does not
# satisfy MyProto.
util = Util(MyProto, Impl)

Example output:

$ python -m mypy t.py
t.py:22:20: error: Incompatible types in assignment (expression has type "Type[Impl]", variable has type "Type[MyProto]")  [assignment]
    C: Type[MyProto] = Impl 
                       ^
Found 1 error in 1 file (checked 1 source file)

The use case is, to be able to register dynamically implementations and their protocols they support, while still being statically type checked. Hope that makes sense.

Update

Ok, so I've found that what I have above ought to work, as-is, if only Protocol had a type representation that would actually work. Now, T is inferred as object rather than MyProto, as that is the least common denominator of the two types passed to Util() as it were. However, if I explicitly declare that T should be MyProto, I get closer:

util = Util[MyProto](MyProto, Impl)

Gives me what I want, almost:

[...]
t.py:42:22: error: Argument 1 to "Util" has incompatible type "Type[MyProto]"; expected "MyProto"  [arg-type]
    util = Util[MyProto](MyProto, Impl)
                         ^
t.py:42:31: error: Argument 2 to "Util" has incompatible type "Type[Impl]"; expected "Type[MyProto]"  [arg-type]
    util = Util[MyProto](MyProto, Impl)
                                  ^

The error about argument 1 is wrong, the expected type is right, the reported type of the argument is wrong, it should be just MyProto or however Protocol classes should be represented by the type system.

The error about argument 2 is correct, and goes away if I fix the Impl so it matches the protocol.

So, when it comes to protocols, the lack of proper type representation and the logic for determining the LCD between two types seems to be broken.

Upvotes: 3

Views: 1484

Answers (1)

Kaos
Kaos

Reputation: 1004

Funny thing that I would find the answer, first after posting the question here (spent a full day struggling with this, until I finally got nudged in the right direction).

# tweaked Util class, thus:
@dataclass
class Util(Generic[T]):
   proto: Type
   impl: Type[T]

# then using it as such, with an explicit generic type:
util = Util[MyProto](MyProto, Impl)
t.py:27:31: error: Argument 2 to "Util" has incompatible type "Type[Impl]"; expected "Type[MyProto]"  [arg-type]
    util = Util[MyProto](MyProto, Impl)
                                  ^
Found 2 errors in 1 file (checked 1 source file)

Not the most elegant however, so if any one figures out how to avoid duplicating MyProto when constructing the Util instance, would be much appreciated.

Also noticed I get a much better error message from mypy if I pass in an instance of Impl, rather than just the class.

t.py:27:31: error: Argument 2 to "Util" has incompatible type "Impl"; expected "MyProto"  [arg-type]
    util = Util[MyProto](MyProto, Impl())
                                  ^
t.py:27:31: note: Following member(s) of "Impl" have conflicts:
t.py:27:31: note:     attr: expected "str", got "int"

Upvotes: 2

Related Questions