Skip to content

Commit 8279fa6

Browse files
authored
Conformance tests and spec change for generic type erasure (#1589)
1 parent ebb1a42 commit 8279fa6

File tree

6 files changed

+106
-4
lines changed

6 files changed

+106
-4
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
conformant = "Partial"
2+
notes = """
3+
Infers Node[Never] instead of Node[Any] when argument is not provided.
4+
False negative on instance attribute access on type(node).
5+
"""
6+
output = """
7+
generics_type_erasure.py:17: error: Expression is of type "Node[Never]", not "Node[Any]" [assert-type]
8+
generics_type_erasure.py:20: error: Expression is of type Never, not "Any" [assert-type]
9+
generics_type_erasure.py:36: error: Argument 1 to "Node" has incompatible type "str"; expected "int | None" [arg-type]
10+
generics_type_erasure.py:38: error: Argument 1 to "Node" has incompatible type "int"; expected "str | None" [arg-type]
11+
generics_type_erasure.py:40: error: Access to generic instance variables via class is ambiguous [misc]
12+
generics_type_erasure.py:41: error: Access to generic instance variables via class is ambiguous [misc]
13+
generics_type_erasure.py:42: error: Access to generic instance variables via class is ambiguous [misc]
14+
generics_type_erasure.py:43: error: Access to generic instance variables via class is ambiguous [misc]
15+
"""
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
conformant = "Partial"
2+
notes = """
3+
Doesn't allow using Node[Any] in assert_type expression.
4+
False negatives on instance attribute access on the type.
5+
"""
6+
output = """
7+
generics_type_erasure.py:11:0 Uninitialized attribute [13]: Attribute `label` is declared in class `Node` to have type `Variable[T]` but is never initialized.
8+
generics_type_erasure.py:17:25 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Type[Variable[T]]` but got `object`.
9+
generics_type_erasure.py:36:15 Incompatible parameter type [6]: In call `Node.__init__`, for 1st positional argument, expected `Optional[int]` but got `str`.
10+
generics_type_erasure.py:38:15 Incompatible parameter type [6]: In call `Node.__init__`, for 1st positional argument, expected `Optional[str]` but got `int`.
11+
"""
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
conformant = "Partial"
2+
notes = """
3+
False negatives on instance attribute access on the type.
4+
"""
5+
output = """
6+
generics_type_erasure.py:36:16 - error: Argument of type "Literal['']" cannot be assigned to parameter "label" of type "int | None" in function "__init__"
7+
  Type "Literal['']" cannot be assigned to type "int | None"
8+
    "Literal['']" is incompatible with "int"
9+
    "Literal['']" is incompatible with "None" (reportGeneralTypeIssues)
10+
generics_type_erasure.py:38:16 - error: Argument of type "Literal[0]" cannot be assigned to parameter "label" of type "str | None" in function "__init__"
11+
  Type "Literal[0]" cannot be assigned to type "str | None"
12+
    "Literal[0]" is incompatible with "str"
13+
    "Literal[0]" is incompatible with "None" (reportGeneralTypeIssues)
14+
generics_type_erasure.py:42:6 - error: Cannot assign member "label" for type "type[Node[T@Node]]"
15+
  Expression of type "Literal[1]" cannot be assigned to member "label" of class "Node[T@Node]"
16+
    Member "__set__" is unknown
17+
    Type "Literal[1]" cannot be assigned to type "T@Node" (reportGeneralTypeIssues)
18+
"""
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
conformant = "Unsupported"
2+
output = """
3+
NotImplementedError: ParameterizedClass
4+
"""
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Specification: https://typing.readthedocs.io/en/latest/spec/generics.html#instantiating-generic-classes-and-type-erasure
2+
3+
from typing import Any, TypeVar, Generic, assert_type
4+
5+
T = TypeVar("T")
6+
7+
# > If the constructor (__init__ or __new__) uses T in its signature, and a
8+
# > corresponding argument value is passed, the type of the corresponding
9+
# > argument(s) is substituted. Otherwise, Any is assumed.
10+
11+
class Node(Generic[T]):
12+
label: T
13+
def __init__(self, label: T | None = None) -> None: ...
14+
15+
assert_type(Node(''), Node[str])
16+
assert_type(Node(0), Node[int])
17+
assert_type(Node(), Node[Any])
18+
19+
assert_type(Node(0).label, int)
20+
assert_type(Node().label, Any)
21+
22+
# > In case the inferred type uses [Any] but the intended type is more specific,
23+
# > you can use an annotation to force the type of the variable, e.g.:
24+
25+
n1: Node[int] = Node()
26+
assert_type(n1, Node[int])
27+
n2: Node[str] = Node()
28+
assert_type(n2, Node[str])
29+
30+
n3 = Node[int]()
31+
assert_type(n3, Node[int])
32+
n4 = Node[str]()
33+
assert_type(n4, Node[str])
34+
35+
n5 = Node[int](0) # OK
36+
n6 = Node[int]("") # Type error
37+
n7 = Node[str]("") # OK
38+
n8 = Node[str](0) # Type error
39+
40+
Node[int].label = 1 # Type error
41+
Node[int].label # Type error
42+
Node.label = 1 # Type error
43+
Node.label # Type error
44+
type(n1).label # Type error
45+
assert_type(n1.label, int)
46+
assert_type(Node[int]().label, int)
47+
n1.label = 1 # OK
48+
49+
# > [...] generic versions of concrete collections can be instantiated:
50+
51+
from typing import DefaultDict
52+
53+
data = DefaultDict[int, bytes]()
54+
assert_type(data[0], bytes)

docs/spec/generics.rst

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -333,15 +333,15 @@ argument(s) is substituted. Otherwise, ``Any`` is assumed. Example::
333333

334334
class Node(Generic[T]):
335335
x: T # Instance attribute (see below)
336-
def __init__(self, label: T = None) -> None:
336+
def __init__(self, label: T | None = None) -> None:
337337
...
338338

339339
x = Node('') # Inferred type is Node[str]
340340
y = Node(0) # Inferred type is Node[int]
341341
z = Node() # Inferred type is Node[Any]
342342

343343
In case the inferred type uses ``[Any]`` but the intended type is more
344-
specific, you can use a type comment (see below) to force the type of
344+
specific, you can use an annotation (see below) to force the type of
345345
the variable, e.g.::
346346

347347
# (continued from previous example)
@@ -373,8 +373,8 @@ class instance that does not have an instance attribute with the same name::
373373
Node.x = 1 # Error
374374
Node.x # Error
375375
type(p).x # Error
376-
p.x # Ok (evaluates to None)
377-
Node[int]().x # Ok (evaluates to None)
376+
p.x # Ok (evaluates to int)
377+
Node[int]().x # Ok (evaluates to int)
378378
p.x = 1 # Ok, but assigning to instance attribute
379379

380380
Generic versions of abstract collections like ``Mapping`` or ``Sequence``

0 commit comments

Comments
 (0)