Type check fails when instance is assigned, but not when passed directly to method

Here’s an MRE which has a generic Msg class that BaseHandler subclasses handle:

from abc import ABC
from dataclasses import dataclass
from typing import Generic, TypeVar

T = TypeVar('T')

class Msg(Generic[T]):
    data: T

class BaseHandler(Generic[T], ABC):
    def handle(self, msg: Msg[T]):

Add some classes to use in Msg:

class Foo:
    foo: bool

class Bar:
    bar: float

Add a handler which can handle both classes:

FooBar = Foo | Bar

class FooBarHandler(BaseHandler[FooBar]):
    def handle(self, msg: Msg[FooBar]):
        match (data := msg.data):
            case Foo():
                print('foo', data.foo)
            case Bar():
                print('foo', data.bar)

foobar_handler = FooBarHandler()

That all type checks :white_check_mark:

Now the problem: If I extract Msg to a variable:

msg = Msg(data=Foo())


error: Argument of type "Msg[Foo]" cannot be assigned to parameter "msg" of type "Msg[FooBar]" in function "handle"
    "Msg[Foo]" is incompatible with "Msg[FooBar]"
      Type parameter "T@Msg" is invariant, but "Foo" is not the same as "FooBar" (reportGeneralTypeIssues)


error: Argument 1 to "handle" of "FooBarHandler" has incompatible type "Msg[Foo]"; expected "Msg[Foo | Bar]"  [arg-type]

I can ‘workaround’ it by explicitly instantiating Msg with FooBar:

msg = Msg[FooBar](data=Foo())

But I feel like there should be a better way to let Python know that a Msg[Foo] is covariant with Msg[FooBar] in handle?

The Msg type has a writable data property. Therefore in general it needs to be invariant rather than covariant.