From 3c02f031cc929251e3a35b8384d25b00b06674d7 Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Mon, 15 Jan 2024 21:30:39 -0800 Subject: [PATCH] Add conformance tests for basic generic spec (#1553) --- conformance/results/mypy/generics_basic.toml | 17 ++ .../results/mypy/generics_scoping.toml | 27 +++ conformance/results/pyre/generics_basic.toml | 18 ++ .../results/pyre/generics_scoping.toml | 18 ++ .../results/pyright/generics_basic.toml | 18 ++ .../results/pyright/generics_scoping.toml | 14 ++ .../results/pytype/generics_basic.toml | 41 +++++ .../results/pytype/generics_scoping.toml | 45 +++++ conformance/tests/generics_basic.py | 168 ++++++++++++++++++ conformance/tests/generics_scoping.py | 86 +++++++++ docs/spec/generics.rst | 2 +- 11 files changed, 453 insertions(+), 1 deletion(-) create mode 100644 conformance/results/mypy/generics_basic.toml create mode 100644 conformance/results/mypy/generics_scoping.toml create mode 100644 conformance/results/pyre/generics_basic.toml create mode 100644 conformance/results/pyre/generics_scoping.toml create mode 100644 conformance/results/pyright/generics_basic.toml create mode 100644 conformance/results/pyright/generics_scoping.toml create mode 100644 conformance/results/pytype/generics_basic.toml create mode 100644 conformance/results/pytype/generics_scoping.toml create mode 100644 conformance/tests/generics_basic.py create mode 100644 conformance/tests/generics_scoping.py diff --git a/conformance/results/mypy/generics_basic.toml b/conformance/results/mypy/generics_basic.toml new file mode 100644 index 00000000..0b16997d --- /dev/null +++ b/conformance/results/mypy/generics_basic.toml @@ -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) +""" diff --git a/conformance/results/mypy/generics_scoping.toml b/conformance/results/mypy/generics_scoping.toml new file mode 100644 index 00000000..ca04ba69 --- /dev/null +++ b/conformance/results/mypy/generics_scoping.toml @@ -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) +""" diff --git a/conformance/results/pyre/generics_basic.toml b/conformance/results/pyre/generics_basic.toml new file mode 100644 index 00000000..302d57ee --- /dev/null +++ b/conformance/results/pyre/generics_basic.toml @@ -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__`. +""" diff --git a/conformance/results/pyre/generics_scoping.toml b/conformance/results/pyre/generics_scoping.toml new file mode 100644 index 00000000..ae95728b --- /dev/null +++ b/conformance/results/pyre/generics_scoping.toml @@ -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`. +""" diff --git a/conformance/results/pyright/generics_basic.toml b/conformance/results/pyright/generics_basic.toml new file mode 100644 index 00000000..63f0660b --- /dev/null +++ b/conformance/results/pyright/generics_basic.toml @@ -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) +""" diff --git a/conformance/results/pyright/generics_scoping.toml b/conformance/results/pyright/generics_scoping.toml new file mode 100644 index 00000000..a273ef28 --- /dev/null +++ b/conformance/results/pyright/generics_scoping.toml @@ -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) +""" diff --git a/conformance/results/pytype/generics_basic.toml b/conformance/results/pytype/generics_basic.toml new file mode 100644 index 00000000..b0382606 --- /dev/null +++ b/conformance/results/pytype/generics_basic.toml @@ -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 : Invalid TypeVar: the number of constraints must be 0 or more than 1 [invalid-typevar] +File "generics_basic.py", line 48, in : 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 : 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] +""" diff --git a/conformance/results/pytype/generics_scoping.toml b/conformance/results/pytype/generics_scoping.toml new file mode 100644 index 00000000..0eab45fc --- /dev/null +++ b/conformance/results/pytype/generics_scoping.toml @@ -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 : 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 : 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 : Invalid type annotation 'T' for global_var1 [invalid-annotation] + TypeVar(s) 'T' not in scope +File "generics_scoping.py", line 85, in : Invalid type annotation 'List[T]' for global_var2 [invalid-annotation] + TypeVar(s) 'T' not in scope +""" diff --git a/conformance/tests/generics_basic.py b/conformance/tests/generics_basic.py new file mode 100644 index 00000000..6822f945 --- /dev/null +++ b/conformance/tests/generics_basic.py @@ -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 + ... diff --git a/conformance/tests/generics_scoping.py b/conformance/tests/generics_scoping.py new file mode 100644 index 00000000..7f692334 --- /dev/null +++ b/conformance/tests/generics_scoping.py @@ -0,0 +1,86 @@ +# Specification: https://typing.readthedocs.io/en/latest/spec/generics.html#scoping-rules-for-type-variables + +from typing import TypeVar, Generic, Iterable, TypeAlias, assert_type + +# > A type variable used in a generic function could be inferred to represent +# > different types in the same code block. +T = TypeVar('T') + +def fun_1(x: T) -> T: ... # T here +def fun_2(x: T) -> T: ... # and here could be different + +assert_type(fun_1(1), int) +assert_type(fun_2('a'), str) + +# > A type variable used in a method of a generic class that coincides +# > with one of the variables that parameterize this class is always bound +# > to that variable. + +class MyClass(Generic[T]): + def meth_1(self, x: T) -> T: ... # T here + def meth_2(self, x: T) -> T: ... # and here are always the same + +a: MyClass[int] = MyClass() +a.meth_1(1) # OK +a.meth_2('a') # Type error + +# > A type variable used in a method that does not match any of the variables +# > that parameterize the class makes this method a generic function in that +# > variable. + +S = TypeVar("S") + +class Foo(Generic[T]): + def method(self, x: T, y: S) -> S: + ... + +x: Foo[int] = Foo() +assert_type(x.method(0, "abc"), str) +assert_type(x.method(0, b"abc"), bytes) + +# > Unbound type variables should not appear in the bodies of generic functions, +# > or in the class bodies apart from method definitions. + +def fun_3(x: T) -> list[T]: + y: list[T] = [] # OK + z: list[S] = [] # Type error + return y + +class Bar(Generic[T]): + an_attr: list[S] = [] # Type error + + def do_something(self, x: S) -> S: # OK + ... + +# A generic class definition that appears inside a generic function +# should not use type variables that parameterize the generic function. + +def fun_4(x: T) -> list[T]: + a_list: list[T] = [] # OK + + class MyGeneric(Generic[T]): # Type error + ... + + return a_list + +# > A generic class nested in another generic class cannot use the same type +# > variables. The scope of the type variables of the outer class +# > doesn't cover the inner one + +class Outer(Generic[T]): + class Bad(Iterable[T]): # Type error + ... + class AlsoBad: + x: list[T] # Type error + + class Inner(Iterable[S]): # OK + ... + attr: Inner[T] # OK + + alias: TypeAlias = list[T] # Type error + + +# Test unbound type variables at global scope +global_var1: T # Type error +global_var2: list[T] = [] # Type error +list[T]() # Type error diff --git a/docs/spec/generics.rst b/docs/spec/generics.rst index f63446ba..6af51adc 100644 --- a/docs/spec/generics.rst +++ b/docs/spec/generics.rst @@ -272,7 +272,7 @@ However, there are some special cases in the static typechecking context: # this is also an error an_attr: list[S] = [] - def do_something(x: S) -> S: # this is OK though + def do_something(self, x: S) -> S: # this is OK though ... * A generic class definition that appears inside a generic function