Method parameters should be contravariant, hence defining a covariant generic should raise a type error. However when using a covariant generic in a union pyright, mypy and pyre-check all do not report an error on the following code:
from typing import TypeVar, Generic, Any
T_co = TypeVar("T_co", covariant=True)
class Foo(Generic[T_co]):
def a(self, arg: T_co) -> Any: # Error, like expected as T_co is covariant.
...
def b(self, arg: int | T_co) -> Any: # No error, expected one
...
As all these type-checkers do not raise an error I wonder is this actually fine, shouldn't this also break type-safety? If it is fine can you explain to why, what differs from a pure covariant implementation that is definitely not safe, shouldn't I be able to break it as well? Or if its not safe, is there an explanation why all type-checkers have the same gap here?
It appears to be a missing feature.
In Mypy's case, there's a comment about this:
elif isinstance(arg_type, TypeVarType):
# Refuse covariant parameter type variables
# TODO: check recursively for inner type variables
if (
arg_type.variance == COVARIANT
and defn.name not in ("__init__", "__new__", "__post_init__")
and not is_private(defn.name) # private methods are not inherited
):
...
self.fail(message_registry.FUNCTION_PARAMETER_CANNOT_BE_COVARIANT, ctx)
It was added in 2016 and has been there ever since.
Pyright has a similar check, which originally prevented invalid nested types as well:
if (this._containsCovariantTypeVar(paramType, node.id, diag)) {
this._evaluator.addDiagnostic(
this._fileInfo.diagnosticRuleSet.reportGeneralTypeIssues,
// ...
);
}
This check was then changed a month later, to:
if (isTypeVar(paramType) && paramType.details.isCovariant && !paramType.details.isSynthesized) {
this._evaluator.addDiagnostic(
this._fileInfo.diagnosticRuleSet.reportGeneralTypeIssues,
// ...
);
}
The commit message specifically says that such unions would be allowed, but does not give a reason:
Made the check less strict for the use of covariant type vars within a function input parameter annotation. In particular, unions that contain covariant type vars are now permitted.