Skip to content

Commit dd62ad9

Browse files
committed
Add conformance tests for basic generic spec
1 parent 8aa8f8b commit dd62ad9

File tree

3 files changed

+240
-1
lines changed

3 files changed

+240
-1
lines changed

conformance/tests/generics_basic.py

+156
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
"""
2+
Tests for basic usage of generics.
3+
"""
4+
5+
# Specification: https://typing.readthedocs.io/en/latest/spec/generics.html#introduction
6+
7+
from __future__ import annotations
8+
9+
from collections.abc import Sequence
10+
from typing import Any, Generic, TypeVar, assert_type
11+
12+
T = TypeVar('T')
13+
14+
# > Generics can be parameterized by using a factory available in
15+
# > ``typing`` called ``TypeVar``.
16+
17+
def first(l: Sequence[T]) -> T:
18+
return l[0]
19+
20+
21+
def test_first(seq_int: Sequence[int], seq_str: Sequence[str]) -> None:
22+
assert_type(first(seq_int), int)
23+
assert_type(first(seq_str), str)
24+
25+
# > ``TypeVar`` supports constraining parametric types to a fixed set of
26+
# > possible types
27+
28+
AnyStr = TypeVar('AnyStr', str, bytes)
29+
30+
def concat(x: AnyStr, y: AnyStr) -> AnyStr:
31+
return x + y
32+
33+
def test_concat(s: str, b: bytes, a: Any) -> None:
34+
concat(s, s) # OK
35+
concat(b, b) # OK
36+
concat(s, b) # Type error
37+
concat(b, s) # Type error
38+
39+
concat(s, a) # OK
40+
concat(a, b) # OK
41+
42+
# > Specifying a single constraint is disallowed.
43+
44+
BadConstraint1 = TypeVar('BadConstraint', str) # Type error
45+
46+
# > Note: those types cannot be parameterized by type variables
47+
48+
BadConstraint2 = TypeVar('BadConstraint', str, T) # Type error
49+
50+
# > Subtypes of types constrained by a type variable should be treated
51+
# > as their respective explicitly listed base types in the context of the
52+
# > type variable.
53+
54+
class MyStr(str): ...
55+
56+
def test_concat_subtype(s: str, b: bytes, a: Any, m: MyStr) -> None:
57+
assert_type(concat(m, m), str)
58+
assert_type(concat(m, s), str)
59+
concat(m, b) # Type error
60+
61+
assert_type(concat(m, a), str)
62+
assert_type(concat(a, m), str)
63+
64+
# Specification: https://typing.readthedocs.io/en/latest/spec/generics.html#user-defined-generic-classes
65+
66+
# > You can include a ``Generic`` base class to define a user-defined class
67+
# > as generic.
68+
69+
from logging import Logger
70+
from collections.abc import Iterable
71+
72+
class LoggedVar(Generic[T]):
73+
def __init__(self, value: T, name: str, logger: Logger) -> None:
74+
self.name = name
75+
self.logger = logger
76+
self.value = value
77+
78+
def set(self, new: T) -> None:
79+
self.log('Set ' + repr(self.value))
80+
self.value = new
81+
82+
def get(self) -> T:
83+
self.log('Get ' + repr(self.value))
84+
return self.value
85+
86+
def log(self, message: str) -> None:
87+
self.logger.info('{}: {}'.format(self.name, message))
88+
89+
90+
def zero_all_vars(vars: Iterable[LoggedVar[int]]) -> None:
91+
for var in vars:
92+
var.set(0)
93+
assert_type(var.get(), int)
94+
95+
96+
# > A generic type can have any number of type variables, and type variables
97+
# > may be constrained.
98+
99+
T = TypeVar('T')
100+
S = TypeVar('S')
101+
102+
class Pair1(Generic[T, S]):
103+
...
104+
105+
# > Each type variable argument to ``Generic`` must be distinct.
106+
107+
class Pair2(Generic[T, T]): # Type error
108+
...
109+
110+
# > The ``Generic[T]`` base class is redundant in simple cases where you
111+
# > subclass some other generic class and specify type variables for its
112+
# > parameters.
113+
114+
from collections.abc import Iterator
115+
116+
class MyIter1(Iterator[T]):
117+
...
118+
119+
class MyIter2(Iterator[T], Generic[T]):
120+
...
121+
122+
def test_my_iter(m1: MyIter1[int], m2: MyIter2[int]):
123+
assert_type(next(m1), int)
124+
assert_type(next(m2), int)
125+
126+
# > You can use multiple inheritance with ``Generic``
127+
128+
from collections.abc import Sized, Container
129+
130+
T = TypeVar('T')
131+
132+
class LinkedList(Sized, Generic[T]):
133+
...
134+
135+
K = TypeVar('K')
136+
V = TypeVar('V')
137+
138+
class MyMapping(Iterable[tuple[K, V]], Container[tuple[K, V]], Generic[K, V]):
139+
...
140+
141+
# > Subclassing a generic class without specifying type parameters assumes
142+
# > ``Any`` for each position. In the following example, ``MyIterable``
143+
# > is not generic but implicitly inherits from ``Iterable[Any]``::
144+
145+
class MyIterableAny(Iterable): # Same as Iterable[Any]
146+
...
147+
148+
def test_my_iterable_any(m: MyIterableAny):
149+
assert_type(iter(m), Iterator[Any])
150+
151+
# > Generic metaclasses are not supported
152+
153+
class GenericMeta(type, Generic[T]): ...
154+
155+
class GenericMetaInstance(metaclass=GenericMeta[T]): # Type error
156+
...

conformance/tests/generics_scoping.py

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# Specification: https://typing.readthedocs.io/en/latest/spec/generics.html#scoping-rules-for-type-variables
2+
3+
# TODO: write PEP 695 versions
4+
5+
from typing import TypeVar, Generic, Iterable, assert_type
6+
7+
# > A type variable used in a generic function could be inferred to represent
8+
# > different types in the same code block.
9+
T = TypeVar('T')
10+
11+
def fun_1(x: T) -> T: ... # T here
12+
def fun_2(x: T) -> T: ... # and here could be different
13+
14+
assert_type(fun_1(1), int)
15+
assert_type(fun_2('a'), str)
16+
17+
# > A type variable used in a method of a generic class that coincides
18+
# > with one of the variables that parameterize this class is always bound
19+
# > to that variable.
20+
21+
class MyClass(Generic[T]):
22+
def meth_1(self, x: T) -> T: ... # T here
23+
def meth_2(self, x: T) -> T: ... # and here are always the same
24+
25+
a: MyClass[int] = MyClass()
26+
a.meth_1(1)
27+
a.meth_2('a') # Type error
28+
29+
# > A type variable used in a method that does not match any of the variables
30+
# > that parameterize the class makes this method a generic function in that
31+
# > variable.
32+
33+
S = TypeVar("S")
34+
35+
class Foo(Generic[T]):
36+
def method(self, x: T, y: S) -> S:
37+
...
38+
39+
x: Foo[int] = Foo()
40+
assert_type(x.method(0, "abc"), str)
41+
assert_type(x.method(0, b"abc"), bytes)
42+
43+
# > Unbound type variables should not appear in the bodies of generic functions,
44+
# > or in the class bodies apart from method definitions.
45+
46+
def a_fun(x: T) -> list[T]:
47+
y: list[T] = [] # OK
48+
z: list[S] = [] # Type error
49+
return y
50+
51+
class Bar(Generic[T]):
52+
an_attr: list[S] = [] # Type error
53+
54+
def do_something(self, x: S) -> S: # OK
55+
...
56+
57+
# A generic class definition that appears inside a generic function
58+
# should not use type variables that parameterize the generic function.
59+
60+
def a_fun(x: T) -> list[T]:
61+
a_list: list[T] = [] # OK
62+
63+
class MyGeneric(Generic[T]): # Type error
64+
...
65+
66+
return a_list
67+
68+
# > A generic class nested in another generic class cannot use the same type
69+
# > variables. The scope of the type variables of the outer class
70+
# > doesn't cover the inner one
71+
72+
T = TypeVar('T')
73+
S = TypeVar('S')
74+
75+
class Outer(Generic[T]):
76+
class Bad(Iterable[T]): # Type error
77+
...
78+
class AlsoBad:
79+
x: list[T] # Type error
80+
81+
class Inner(Iterable[S]): # OK
82+
...
83+
attr: Inner[T] # OK

docs/spec/generics.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ However, there are some special cases in the static typechecking context:
272272
# this is also an error
273273
an_attr: list[S] = []
274274

275-
def do_something(x: S) -> S: # this is OK though
275+
def do_something(self, x: S) -> S: # this is OK though
276276
...
277277

278278
* A generic class definition that appears inside a generic function

0 commit comments

Comments
 (0)