Skip to content

Commit

Permalink
Add conformance tests for basic generic spec (#1553)
Browse files Browse the repository at this point in the history
  • Loading branch information
hauntsaninja authored Jan 16, 2024
1 parent 0665b39 commit 3c02f03
Show file tree
Hide file tree
Showing 11 changed files with 453 additions and 1 deletion.
17 changes: 17 additions & 0 deletions conformance/results/mypy/generics_basic.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
conformant = "Pass"
output = """
generics_basic.py:36: error: Value of type variable "AnyStr" of "concat" cannot be "Sequence[object]" [type-var]
generics_basic.py:37: error: Value of type variable "AnyStr" of "concat" cannot be "Sequence[object]" [type-var]
generics_basic.py:44: error: TypeVar cannot have only a single constraint [misc]
generics_basic.py:48: error: Type variable "generics_basic.T" is unbound [valid-type]
generics_basic.py:48: note: (Hint: Use "Generic[T]" or "Protocol[T]" base class to bind "T" inside a class)
generics_basic.py:48: note: (Hint: Use "T" in function signature to bind "T" inside a function)
generics_basic.py:59: error: Value of type variable "AnyStr" of "concat" cannot be "Sequence[object]" [type-var]
generics_basic.py:107: error: Duplicate type variables in Generic[...] or Protocol[...] [misc]
generics_basic.py:140: error: Invalid index type "int" for "MyMap1[str, int]"; expected type "str" [index]
generics_basic.py:141: error: Invalid index type "int" for "MyMap2[int, str]"; expected type "str" [index]
generics_basic.py:167: error: Dynamic metaclass not supported for "GenericMetaInstance" [misc]
generics_basic.py:167: error: Type variable "generics_basic.T" is unbound [valid-type]
generics_basic.py:167: note: (Hint: Use "Generic[T]" or "Protocol[T]" base class to bind "T" inside a class)
generics_basic.py:167: note: (Hint: Use "T" in function signature to bind "T" inside a function)
"""
27 changes: 27 additions & 0 deletions conformance/results/mypy/generics_scoping.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
conformant = "Partial"
notes = """
False negative on generic class nested within generic class with same type variable.
"""
output = """
generics_scoping.py:25: error: Argument 1 to "meth_2" of "MyClass" has incompatible type "str"; expected "int" [arg-type]
generics_scoping.py:46: error: Type variable "generics_scoping.S" is unbound [valid-type]
generics_scoping.py:46: note: (Hint: Use "Generic[S]" or "Protocol[S]" base class to bind "S" inside a class)
generics_scoping.py:46: note: (Hint: Use "S" in function signature to bind "S" inside a function)
generics_scoping.py:50: error: Type variable "generics_scoping.S" is unbound [valid-type]
generics_scoping.py:50: note: (Hint: Use "Generic[S]" or "Protocol[S]" base class to bind "S" inside a class)
generics_scoping.py:50: note: (Hint: Use "S" in function signature to bind "S" inside a function)
generics_scoping.py:61: error: Free type variable expected in Generic[...] [misc]
generics_scoping.py:74: error: Type variable "generics_scoping.T" is unbound [valid-type]
generics_scoping.py:74: note: (Hint: Use "Generic[T]" or "Protocol[T]" base class to bind "T" inside a class)
generics_scoping.py:74: note: (Hint: Use "T" in function signature to bind "T" inside a function)
generics_scoping.py:80: error: Can't use bound type variable "T" to define generic alias [valid-type]
generics_scoping.py:84: error: Type variable "generics_scoping.T" is unbound [valid-type]
generics_scoping.py:84: note: (Hint: Use "Generic[T]" or "Protocol[T]" base class to bind "T" inside a class)
generics_scoping.py:84: note: (Hint: Use "T" in function signature to bind "T" inside a function)
generics_scoping.py:85: error: Type variable "generics_scoping.T" is unbound [valid-type]
generics_scoping.py:85: note: (Hint: Use "Generic[T]" or "Protocol[T]" base class to bind "T" inside a class)
generics_scoping.py:85: note: (Hint: Use "T" in function signature to bind "T" inside a function)
generics_scoping.py:86: error: Type variable "generics_scoping.T" is unbound [valid-type]
generics_scoping.py:86: note: (Hint: Use "Generic[T]" or "Protocol[T]" base class to bind "T" inside a class)
generics_scoping.py:86: note: (Hint: Use "T" in function signature to bind "T" inside a function)
"""
18 changes: 18 additions & 0 deletions conformance/results/pyre/generics_basic.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
conformant = "Partial"
notes = """
False positives in examples using constrained type variables.
False negative in custom map example.
False positive using `iter`.
False negative for generic metaclass.
"""
output = """
generics_basic.py:31:4 Incompatible return type [7]: Expected `Variable[AnyStr <: [str, bytes]]` but got `bytes`.
generics_basic.py:31:15 Incompatible parameter type [6]: In call `bytes.__add__`, for 1st positional argument, expected `Union[array[typing.Any], bytearray, bytes, _CData, memoryview, mmap, PickleBuffer]` but got `Variable[AnyStr <: [str, bytes]]`.
generics_basic.py:36:14 Incompatible parameter type [6]: In call `concat`, for 2nd positional argument, expected `Variable[AnyStr <: [str, bytes]]` but got `bytes`.
generics_basic.py:37:14 Incompatible parameter type [6]: In call `concat`, for 2nd positional argument, expected `Variable[AnyStr <: [str, bytes]]` but got `str`.
generics_basic.py:44:0 Invalid type [31]: TypeVar can't have a single explicit constraint. Did you mean `bound=str`?
generics_basic.py:48:0 Invalid type [31]: Expression `Variable[BadConstraint2 <: [str, Variable[generics_basic.T]]]` is not a valid type. Type variables cannot contain other type variables in their constraints.
generics_basic.py:59:14 Incompatible parameter type [6]: In call `concat`, for 2nd positional argument, expected `Variable[AnyStr <: [str, bytes]]` but got `bytes`.
generics_basic.py:107:0 Duplicate type variables [59]: Duplicate type variable `T` in Generic[...].
generics_basic.py:161:25 Undefined attribute [16]: `typing.Iterator` has no attribute `__getitem__`.
"""
18 changes: 18 additions & 0 deletions conformance/results/pyre/generics_scoping.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
conformant = "Partial"
notes = """
False negative on generic class nested within generic function with same type variable.
False negative on generic class nested within generic class with same type variable.
"""
output = """
generics_scoping.py:25:9 Incompatible parameter type [6]: In call `MyClass.meth_2`, for 1st positional argument, expected `int` but got `str`.
generics_scoping.py:46:7 Invalid type variable [34]: The type variable `Variable[S]` isn't present in the function's parameters.
generics_scoping.py:50:13 Invalid type variable [34]: The current class isn't generic with respect to the type variable `Variable[S]`. To reference the type variable, you can modify the class to inherit from `typing.Generic[S]`.
generics_scoping.py:70:0 Uninitialized attribute [13]: Attribute `attr` is declared in class `Outer` to have type `Outer.Inner[Variable[T]]` but is never initialized.
generics_scoping.py:73:4 Uninitialized attribute [13]: Attribute `x` is declared in class `Outer.AlsoBad` to have type `typing.List[Variable[T]]` but is never initialized.
generics_scoping.py:74:11 Invalid type variable [34]: The current class isn't generic with respect to the type variable `Variable[T]`. To reference the type variable, you can modify the class to inherit from `typing.Generic[T]`.
generics_scoping.py:80:4 Incompatible attribute type [8]: Attribute `alias` declared in class `Outer` has type `TypeAlias` but is used as type `Type[List[Variable[_T]]]`.
generics_scoping.py:80:28 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Type[Variable[_T]]` but got `TypeVar`.
generics_scoping.py:84:13 Invalid type variable [34]: The type variable `Variable[T]` can only be used to annotate generic classes or functions.
generics_scoping.py:85:13 Invalid type variable [34]: The type variable `Variable[T]` can only be used to annotate generic classes or functions.
generics_scoping.py:86:5 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Type[Variable[_T]]` but got `TypeVar`.
"""
18 changes: 18 additions & 0 deletions conformance/results/pyright/generics_basic.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
conformant = "Pass"
output = """
generics_basic.py:36:15 - error: Argument of type "bytes" cannot be assigned to parameter "y" of type "AnyStr@concat" in function "concat"
  "bytes" is incompatible with "str" (reportGeneralTypeIssues)
generics_basic.py:37:15 - error: Argument of type "str" cannot be assigned to parameter "y" of type "AnyStr@concat" in function "concat"
  "str" is incompatible with "bytes" (reportGeneralTypeIssues)
generics_basic.py:44:44 - error: TypeVar must have at least two constrained types (reportGeneralTypeIssues)
generics_basic.py:48:49 - error: Type variable "T" has no meaning in this context (reportGeneralTypeIssues)
generics_basic.py:48:49 - error: TypeVar constraint type cannot be generic
generics_basic.py:59:15 - error: Argument of type "bytes" cannot be assigned to parameter "y" of type "AnyStr@concat" in function "concat"
  "bytes" is incompatible with "str" (reportGeneralTypeIssues)
generics_basic.py:107:24 - error: Type arguments for "Generic" must be unique
generics_basic.py:140:5 - error: Argument of type "Literal[0]" cannot be assigned to parameter "__key" of type "str" in function "__getitem__"
  "Literal[0]" is incompatible with "str" (reportGeneralTypeIssues)
generics_basic.py:141:5 - error: Argument of type "Literal[0]" cannot be assigned to parameter "__key" of type "str" in function "__getitem__"
  "Literal[0]" is incompatible with "str" (reportGeneralTypeIssues)
generics_basic.py:167:37 - error: Metaclass cannot be generic (reportGeneralTypeIssues)
"""
14 changes: 14 additions & 0 deletions conformance/results/pyright/generics_scoping.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
conformant = "Pass"
output = """
generics_scoping.py:25:10 - error: Argument of type "Literal['a']" cannot be assigned to parameter "x" of type "int" in function "meth_2"
  "Literal['a']" is incompatible with "int" (reportGeneralTypeIssues)
generics_scoping.py:46:13 - error: Type variable "S" has no meaning in this context (reportGeneralTypeIssues)
generics_scoping.py:50:19 - error: Type variable "S" has no meaning in this context (reportGeneralTypeIssues)
generics_scoping.py:61:29 - error: TypeVar "T" is already in use by an outer scope (reportGeneralTypeIssues)
generics_scoping.py:71:24 - error: TypeVar "T" is already in use by an outer scope (reportGeneralTypeIssues)
generics_scoping.py:74:17 - error: Type variable "T" has no meaning in this context (reportGeneralTypeIssues)
generics_scoping.py:80:5 - error: Generic type alias within class cannot use bound type variables T
generics_scoping.py:84:14 - error: Type variable "T" has no meaning in this context (reportGeneralTypeIssues)
generics_scoping.py:85:19 - error: Type variable "T" has no meaning in this context (reportGeneralTypeIssues)
generics_scoping.py:86:6 - error: Type variable "T" has no meaning in this context (reportGeneralTypeIssues)
"""
41 changes: 41 additions & 0 deletions conformance/results/pytype/generics_basic.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
conformant = "Partial"
notes = """
False positives in examples using constrained type variables.
False negative for generic metaclass.
"""
output = """
File "generics_basic.py", line 31, in concat: bad return type [bad-return-type]
Expected: MyStr
Actually returned: str
Called from (traceback):
line 57, in test_concat_subtype
File "generics_basic.py", line 36, in test_concat: Function concat was called with the wrong arguments [wrong-arg-types]
Expected: (x, y: str)
Actually passed: (x, y: bytes)
File "generics_basic.py", line 37, in test_concat: Function concat was called with the wrong arguments [wrong-arg-types]
Expected: (x, y: bytes)
Actually passed: (x, y: str)
File "generics_basic.py", line 44, in <module>: Invalid TypeVar: the number of constraints must be 0 or more than 1 [invalid-typevar]
File "generics_basic.py", line 48, in <module>: Invalid TypeVar: constraint cannot contain TypeVars [invalid-typevar]
File "generics_basic.py", line 57, in test_concat_subtype: MyStr [assert-type]
Expected: str
Actual: MyStr
File "generics_basic.py", line 58, in test_concat_subtype: Function concat was called with the wrong arguments [wrong-arg-types]
Expected: (x, y: MyStr)
Actually passed: (x, y: str)
File "generics_basic.py", line 58, in test_concat_subtype: Any [assert-type]
Expected: str
Actual: Any
File "generics_basic.py", line 59, in test_concat_subtype: Function concat was called with the wrong arguments [wrong-arg-types]
Expected: (x, y: MyStr)
Actually passed: (x, y: bytes)
File "generics_basic.py", line 107, in <module>: Invalid type annotation 'Generic' [invalid-annotation]
Parameters to Generic[...] must all be unique
File "generics_basic.py", line 140, in test_my_map: unsupported operand type(s) for item retrieval: MyMap1[str, int] and int [unsupported-operands]
Function __getitem__ on MyMap1[str, int] expects str
File "generics_basic.py", line 141, in test_my_map: unsupported operand type(s) for item retrieval: MyMap2[int, str] and int [unsupported-operands]
Function __getitem__ on MyMap2[int, str] expects str
File "generics_basic.py", line 161, in test_my_iterable_any: Iterator[nothing] [assert-type]
Expected: Iterator
Actual: Iterator[nothing]
"""
45 changes: 45 additions & 0 deletions conformance/results/pytype/generics_scoping.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
conformant = "Pass"
output = """
File "generics_scoping.py", line 9, in fun_1: bad return type [bad-return-type]
Expected: int
Actually returned: None
Called from (traceback):
line 12, in current file
File "generics_scoping.py", line 10, in fun_2: bad return type [bad-return-type]
Expected: str
Actually returned: None
Called from (traceback):
line 13, in current file
File "generics_scoping.py", line 20, in meth_1: bad return type [bad-return-type]
Expected: int
Actually returned: None
Called from (traceback):
line 24, in current file
File "generics_scoping.py", line 25, in <module>: Function MyClass.meth_2 was called with the wrong arguments [wrong-arg-types]
Expected: (self, x: int)
Actually passed: (self, x: str)
File "generics_scoping.py", line 35, in method: bad return type [bad-return-type]
Expected: str
Actually returned: None
Called from (traceback):
line 38, in current file
File "generics_scoping.py", line 35, in method: bad return type [bad-return-type]
Expected: bytes
Actually returned: None
Called from (traceback):
line 39, in current file
File "generics_scoping.py", line 46, in fun_3: Invalid type annotation 'List[S]' for z [invalid-annotation]
TypeVar(s) 'S' not in scope for method 'fun_3'
File "generics_scoping.py", line 50, in Bar: Invalid type annotation 'List[S]' for an_attr [invalid-annotation]
TypeVar(s) 'S' not in scope for class 'Bar'
File "generics_scoping.py", line 59, in fun_4: Invalid type annotation 'T' [invalid-annotation]
Function [fun_4] and its nested generic class [MyGeneric] cannot use the same type variable T
File "generics_scoping.py", line 70, in <module>: Invalid type annotation 'Outer' [invalid-annotation]
Generic class [Outer] and its nested generic class [Bad] cannot use the same type variable T.
File "generics_scoping.py", line 74, in AlsoBad: Invalid type annotation 'List[T]' for x [invalid-annotation]
TypeVar(s) 'T' not in scope for class 'Outer.AlsoBad'
File "generics_scoping.py", line 84, in <module>: Invalid type annotation 'T' for global_var1 [invalid-annotation]
TypeVar(s) 'T' not in scope
File "generics_scoping.py", line 85, in <module>: Invalid type annotation 'List[T]' for global_var2 [invalid-annotation]
TypeVar(s) 'T' not in scope
"""
168 changes: 168 additions & 0 deletions conformance/tests/generics_basic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
"""
Tests for basic usage of generics.
"""

# Specification: https://typing.readthedocs.io/en/latest/spec/generics.html#introduction

from __future__ import annotations

from collections.abc import Sequence
from typing import Any, Generic, TypeVar, assert_type

T = TypeVar('T')

# > Generics can be parameterized by using a factory available in
# > ``typing`` called ``TypeVar``.

def first(l: Sequence[T]) -> T:
return l[0]


def test_first(seq_int: Sequence[int], seq_str: Sequence[str]) -> None:
assert_type(first(seq_int), int)
assert_type(first(seq_str), str)

# > ``TypeVar`` supports constraining parametric types to a fixed set of
# > possible types

AnyStr = TypeVar('AnyStr', str, bytes)

def concat(x: AnyStr, y: AnyStr) -> AnyStr:
return x + y

def test_concat(s: str, b: bytes, a: Any) -> None:
concat(s, s) # OK
concat(b, b) # OK
concat(s, b) # Type error
concat(b, s) # Type error

concat(s, a) # OK
concat(a, b) # OK

# > Specifying a single constraint is disallowed.

BadConstraint1 = TypeVar('BadConstraint1', str) # Type error

# > Note: those types cannot be parameterized by type variables

BadConstraint2 = TypeVar('BadConstraint2', str, T) # Type error

# > Subtypes of types constrained by a type variable should be treated
# > as their respective explicitly listed base types in the context of the
# > type variable.

class MyStr(str): ...

def test_concat_subtype(s: str, b: bytes, a: Any, m: MyStr) -> None:
assert_type(concat(m, m), str)
assert_type(concat(m, s), str)
concat(m, b) # Type error

# TODO: should these be str or Any?
# reveal_type(concat(m, a))
# reveal_type(concat(a, m))

# Specification: https://typing.readthedocs.io/en/latest/spec/generics.html#user-defined-generic-classes

# > You can include a ``Generic`` base class to define a user-defined class
# > as generic.

from logging import Logger
from collections.abc import Iterable

class LoggedVar(Generic[T]):
def __init__(self, value: T, name: str, logger: Logger) -> None:
self.name = name
self.logger = logger
self.value = value

def set(self, new: T) -> None:
self.log('Set ' + repr(self.value))
self.value = new

def get(self) -> T:
self.log('Get ' + repr(self.value))
return self.value

def log(self, message: str) -> None:
self.logger.info('{}: {}'.format(self.name, message))


def zero_all_vars(vars: Iterable[LoggedVar[int]]) -> None:
for var in vars:
var.set(0)
assert_type(var.get(), int)


# > A generic type can have any number of type variables, and type variables
# > may be constrained.

S = TypeVar('S')

class Pair1(Generic[T, S]):
...

# > Each type variable argument to ``Generic`` must be distinct.

class Pair2(Generic[T, T]): # Type error
...

# > The ``Generic[T]`` base class is redundant in simple cases where you
# > subclass some other generic class and specify type variables for its
# > parameters.

from collections.abc import Iterator, Mapping

class MyIter1(Iterator[T]):
...

class MyIter2(Iterator[T], Generic[T]):
...

def test_my_iter(m1: MyIter1[int], m2: MyIter2[int]):
assert_type(next(m1), int)
assert_type(next(m2), int)


K = TypeVar("K")
V = TypeVar("V")

class MyMap1(Mapping[K, V], Generic[K, V]):
...

class MyMap2(Mapping[K, V], Generic[V, K]):
...

def test_my_map(m1: MyMap1[str, int], m2: MyMap2[int, str]):
assert_type(m1["key"], int)
assert_type(m2["key"], int)

m1[0] # Type error
m2[0] # Type error

# > You can use multiple inheritance with ``Generic``

from collections.abc import Sized, Container

class LinkedList(Sized, Generic[T]):
...

class MyMapping(Iterable[tuple[K, V]], Container[tuple[K, V]], Generic[K, V]):
...

# > Subclassing a generic class without specifying type parameters assumes
# > ``Any`` for each position. In the following example, ``MyIterable``
# > is not generic but implicitly inherits from ``Iterable[Any]``::

class MyIterableAny(Iterable): # Same as Iterable[Any]
...

def test_my_iterable_any(m: MyIterableAny):
assert_type(iter(m), Iterator[Any])

# > Generic metaclasses are not supported

class GenericMeta(type, Generic[T]): ...

class GenericMetaInstance(metaclass=GenericMeta[T]): # Type error
...
Loading

0 comments on commit 3c02f03

Please sign in to comment.