Skip to content

Commit

Permalink
Updated spec and conformance test to reflect that a type variable tha… (
Browse files Browse the repository at this point in the history
#1576)

* Updated spec and conformance test to reflect that a type variable that is defined as covariant or contravariant should not generate an error when bound to a generic function or type alias. Variance has no meaning in this context, so it should be ignored. This brings the spec in alignment with the behavior of the major type checkers.

* Added comment as per PR suggestion.

* Reran tests.

* Fixed a botched merge that undid a previous PR.
  • Loading branch information
erictraut authored Jan 13, 2024
1 parent 8fd71d2 commit a17d8a3
Show file tree
Hide file tree
Showing 6 changed files with 39 additions and 56 deletions.
13 changes: 6 additions & 7 deletions conformance/results/mypy/generics_variance.toml
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
conformant = "Partial"
notes = """
Does not reject a function-scoped TypeVar that is marked as covariant or contravariant.
Does not reject use of class-scoped TypeVar used in a base class when variance is incompatible.
"""
output = """
generics_variance.py:14: error: TypeVar cannot be both covariant and contravariant [misc]
generics_variance.py:78: error: Variance of TypeVar "T_co" incompatible with variance in parent type [type-var]
generics_variance.py:82: error: Variance of TypeVar "T_contra" incompatible with variance in parent type [type-var]
generics_variance.py:94: error: Variance of TypeVar "T_contra" incompatible with variance in parent type [type-var]
generics_variance.py:106: error: Variance of TypeVar "T_co" incompatible with variance in parent type [type-var]
generics_variance.py:126: error: Variance of TypeVar "T_co" incompatible with variance in parent type [type-var]
generics_variance.py:132: error: Variance of TypeVar "T_contra" incompatible with variance in parent type [type-var]
generics_variance.py:77: error: Variance of TypeVar "T_co" incompatible with variance in parent type [type-var]
generics_variance.py:81: error: Variance of TypeVar "T_contra" incompatible with variance in parent type [type-var]
generics_variance.py:93: error: Variance of TypeVar "T_contra" incompatible with variance in parent type [type-var]
generics_variance.py:105: error: Variance of TypeVar "T_co" incompatible with variance in parent type [type-var]
generics_variance.py:125: error: Variance of TypeVar "T_co" incompatible with variance in parent type [type-var]
generics_variance.py:131: error: Variance of TypeVar "T_contra" incompatible with variance in parent type [type-var]
"""
13 changes: 6 additions & 7 deletions conformance/results/pyre/generics_variance.toml
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
conformant = "Partial"
notes = """
Does not reject a TypeVar that is defined as both covariant and contravariant.
Does not reject a function-scoped TypeVar that is marked as covariant or contravariant.
Does not reject use of class-scoped TypeVar used in a base class when variance is incompatible.
"""
output = """
generics_variance.py:78:0 Invalid type variance [46]: The type variable `Variable[T_co](covariant)` is incompatible with parent class type variable `Variable[T]` because subclasses cannot use more permissive type variables than their superclasses.
generics_variance.py:82:0 Invalid type variance [46]: The type variable `Variable[T_contra](contravariant)` is incompatible with parent class type variable `Variable[T]` because subclasses cannot use more permissive type variables than their superclasses.
generics_variance.py:94:0 Invalid type variance [46]: The type variable `Variable[T_contra](contravariant)` is incompatible with parent class type variable `Variable[T_co](covariant)` because subclasses cannot use more permissive type variables than their superclasses.
generics_variance.py:106:0 Invalid type variance [46]: The type variable `Variable[T_co](covariant)` is incompatible with parent class type variable `Variable[T_contra](contravariant)` because subclasses cannot use more permissive type variables than their superclasses.
generics_variance.py:126:0 Invalid type variance [46]: The type variable `Variable[T_co](covariant)` is incompatible with parent class type variable `Variable[T_contra](contravariant)` because subclasses cannot use more permissive type variables than their superclasses.
generics_variance.py:132:0 Invalid type variance [46]: The type variable `Variable[T_contra](contravariant)` is incompatible with parent class type variable `Variable[T_co](covariant)` because subclasses cannot use more permissive type variables than their superclasses.
generics_variance.py:77:0 Invalid type variance [46]: The type variable `Variable[T_co](covariant)` is incompatible with parent class type variable `Variable[T]` because subclasses cannot use more permissive type variables than their superclasses.
generics_variance.py:81:0 Invalid type variance [46]: The type variable `Variable[T_contra](contravariant)` is incompatible with parent class type variable `Variable[T]` because subclasses cannot use more permissive type variables than their superclasses.
generics_variance.py:93:0 Invalid type variance [46]: The type variable `Variable[T_contra](contravariant)` is incompatible with parent class type variable `Variable[T_co](covariant)` because subclasses cannot use more permissive type variables than their superclasses.
generics_variance.py:105:0 Invalid type variance [46]: The type variable `Variable[T_co](covariant)` is incompatible with parent class type variable `Variable[T_contra](contravariant)` because subclasses cannot use more permissive type variables than their superclasses.
generics_variance.py:125:0 Invalid type variance [46]: The type variable `Variable[T_co](covariant)` is incompatible with parent class type variable `Variable[T_contra](contravariant)` because subclasses cannot use more permissive type variables than their superclasses.
generics_variance.py:131:0 Invalid type variance [46]: The type variable `Variable[T_contra](contravariant)` is incompatible with parent class type variable `Variable[T_co](covariant)` because subclasses cannot use more permissive type variables than their superclasses.
"""
29 changes: 13 additions & 16 deletions conformance/results/pyright/generics_variance.toml
Original file line number Diff line number Diff line change
@@ -1,31 +1,28 @@
conformant = "Partial"
notes = """
Does not reject a function-scoped TypeVar that is marked as covariant or contravariant.
"""
conformant = "Pass"
output = """
generics_variance.py:14:50 - error: TypeVar cannot be both covariant and contravariant
generics_variance.py:78:18 - error: Type "T_co@Class1" cannot be assigned to type variable "T@Inv"
generics_variance.py:77:18 - error: Type "T_co@Class1" cannot be assigned to type variable "T@Inv"
  Variance of type argument "T_co@Class1" is incompatible with base class "Inv" (reportGeneralTypeIssues)
generics_variance.py:82:18 - error: Type "T_contra@Class2" cannot be assigned to type variable "T@Inv"
generics_variance.py:81:18 - error: Type "T_contra@Class2" cannot be assigned to type variable "T@Inv"
  Variance of type argument "T_contra@Class2" is incompatible with base class "Inv" (reportGeneralTypeIssues)
generics_variance.py:94:20 - error: Type "T_contra@Co_Child3" cannot be assigned to type variable "T_co@Co"
generics_variance.py:93:20 - error: Type "T_contra@Co_Child3" cannot be assigned to type variable "T_co@Co"
  Variance of type argument "T_contra@Co_Child3" is incompatible with base class "Co" (reportGeneralTypeIssues)
generics_variance.py:106:28 - error: Type "T_co@Contra_Child3" cannot be assigned to type variable "T_contra@Contra"
generics_variance.py:105:28 - error: Type "T_co@Contra_Child3" cannot be assigned to type variable "T_contra@Contra"
  Variance of type argument "T_co@Contra_Child3" is incompatible with base class "Contra" (reportGeneralTypeIssues)
generics_variance.py:114:28 - error: Type "Co[T_co@Contra_Child5]" cannot be assigned to type variable "T_contra@Contra"
generics_variance.py:113:28 - error: Type "Co[T_co@Contra_Child5]" cannot be assigned to type variable "T_contra@Contra"
  Variance of type argument "Co[T_co@Contra_Child5]" is incompatible with base class "Contra" (reportGeneralTypeIssues)
generics_variance.py:127:20 - error: Type "T_co@CoContra_Child2" cannot be assigned to type variable "T_contra@CoContra"
generics_variance.py:126:20 - error: Type "T_co@CoContra_Child2" cannot be assigned to type variable "T_contra@CoContra"
  Variance of type argument "T_co@CoContra_Child2" is incompatible with base class "CoContra" (reportGeneralTypeIssues)
generics_variance.py:133:14 - error: Type "T_contra@CoContra_Child3" cannot be assigned to type variable "T_co@CoContra"
generics_variance.py:132:14 - error: Type "T_contra@CoContra_Child3" cannot be assigned to type variable "T_co@CoContra"
  Variance of type argument "T_contra@CoContra_Child3" is incompatible with base class "CoContra" (reportGeneralTypeIssues)
generics_variance.py:143:24 - error: Type "Co[T_co@CoContra_Child5]" cannot be assigned to type variable "T_contra@CoContra"
generics_variance.py:142:24 - error: Type "Co[T_co@CoContra_Child5]" cannot be assigned to type variable "T_contra@CoContra"
  Variance of type argument "Co[T_co@CoContra_Child5]" is incompatible with base class "CoContra" (reportGeneralTypeIssues)
generics_variance.py:164:33 - error: Type "Co[Contra[T_contra@CoToContraToContra]]" cannot be assigned to type variable "T_contra@Contra"
generics_variance.py:163:33 - error: Type "Co[Contra[T_contra@CoToContraToContra]]" cannot be assigned to type variable "T_contra@Contra"
  Variance of type argument "Co[Contra[T_contra@CoToContraToContra]]" is incompatible with base class "Contra" (reportGeneralTypeIssues)
generics_variance.py:168:37 - error: Type "Contra[Contra[T_co@ContraToContraToContra]]" cannot be assigned to type variable "T_contra@Contra"
generics_variance.py:167:37 - error: Type "Contra[Contra[T_co@ContraToContraToContra]]" cannot be assigned to type variable "T_contra@Contra"
  Variance of type argument "Contra[Contra[T_co@ContraToContraToContra]]" is incompatible with base class "Contra" (reportGeneralTypeIssues)
generics_variance.py:192:43 - error: Could not specialize type "Contra_TA[T_contra@Contra_TA]"
generics_variance.py:191:43 - error: Could not specialize type "Contra_TA[T_contra@Contra_TA]"
  Variance of type argument "Co_TA[Contra_TA[T_contra@CoToContraToContra_WithTA]]" is incompatible with "T_contra@Contra_TA"
generics_variance.py:197:15 - error: Could not specialize type "Contra_TA[T_contra@Contra_TA]"
generics_variance.py:196:15 - error: Could not specialize type "Contra_TA[T_contra@Contra_TA]"
  Variance of type argument "Contra_TA[Contra_TA[T_co@ContraToContraToContra_WithTA]]" is incompatible with "T_contra@Contra_TA"
"""
2 changes: 1 addition & 1 deletion conformance/results/results.html

Large diffs are not rendered by default.

7 changes: 3 additions & 4 deletions conformance/tests/generics_variance.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,9 @@ def dump_employee(e: E) -> E:
B_co = TypeVar("B_co", covariant=True)


# > Variance is only applicable to generic types; generic functions do not
# > have this property. The latter should be defined using only type variables
# > without covariant or contravariant keyword arguments.
def bad_func(x: list[B_co]) -> B_co: # Type checker error
# > Variance has no meaning, and should therefore be ignored by type checkers,
# > if a type variable is bound to a generic function or type alias.
def func(x: list[B_co]) -> B_co: # OK
...


Expand Down
31 changes: 10 additions & 21 deletions docs/spec/generics.rst
Original file line number Diff line number Diff line change
Expand Up @@ -562,32 +562,21 @@ mutable collection classes (e.g. ``MutableMapping`` and
a contravariant type is the ``Generator`` type, which is contravariant
in the ``send()`` argument type (see below).

Note: Covariance or contravariance is *not* a property of a type variable,
but a property of a generic class defined using this variable.
Variance is only applicable to generic types; generic functions
do not have this property. The latter should be defined using only
type variables without ``covariant`` or ``contravariant`` keyword arguments.
For example, the following example is
fine::
Variance is meaningful only when a type variable is bound to a generic class.
Variance has no meaning, and should therefore be ignored by type checkers, if
a type variable is bound to a generic function or type alias.

from typing import TypeVar

class Employee: ...

class Manager(Employee): ...

E = TypeVar('E', bound=Employee)
For example::

def dump_employee(e: E) -> None: ...
T = TypeVar('T', covariant=True)

dump_employee(Manager()) # OK
class A(Generic[T]): # T is covariant in this context
...

while the following is prohibited::
def f(x: T) -> None: # Variance of T is meaningless in this context
...

B_co = TypeVar('B_co', covariant=True)

def bad_func(x: B_co) -> B_co: # Flagged as error by a type checker
...
Alias = list[T] | set[T] # Variance of T is meaningless in this context

.. _`paramspec`:

Expand Down

0 comments on commit a17d8a3

Please sign in to comment.