@@ -43,6 +43,51 @@ class {0}(metaclass=Meta):
43
43
"""
44
44
TYPING_MEMBERS = set (typing .__all__ )
45
45
46
+ TYPING_ALIAS = frozenset (
47
+ (
48
+ "typing.Hashable" ,
49
+ "typing.Awaitable" ,
50
+ "typing.Coroutine" ,
51
+ "typing.AsyncIterable" ,
52
+ "typing.AsyncIterator" ,
53
+ "typing.Iterable" ,
54
+ "typing.Iterator" ,
55
+ "typing.Reversible" ,
56
+ "typing.Sized" ,
57
+ "typing.Container" ,
58
+ "typing.Collection" ,
59
+ "typing.Callable" ,
60
+ "typing.AbstractSet" ,
61
+ "typing.MutableSet" ,
62
+ "typing.Mapping" ,
63
+ "typing.MutableMapping" ,
64
+ "typing.Sequence" ,
65
+ "typing.MutableSequence" ,
66
+ "typing.ByteString" ,
67
+ "typing.Tuple" ,
68
+ "typing.List" ,
69
+ "typing.Deque" ,
70
+ "typing.Set" ,
71
+ "typing.FrozenSet" ,
72
+ "typing.MappingView" ,
73
+ "typing.KeysView" ,
74
+ "typing.ItemsView" ,
75
+ "typing.ValuesView" ,
76
+ "typing.ContextManager" ,
77
+ "typing.AsyncContextManager" ,
78
+ "typing.Dict" ,
79
+ "typing.DefaultDict" ,
80
+ "typing.OrderedDict" ,
81
+ "typing.Counter" ,
82
+ "typing.ChainMap" ,
83
+ "typing.Generator" ,
84
+ "typing.AsyncGenerator" ,
85
+ "typing.Type" ,
86
+ "typing.Pattern" ,
87
+ "typing.Match" ,
88
+ )
89
+ )
90
+
46
91
47
92
def looks_like_typing_typevar_or_newtype (node ):
48
93
func = node .func
@@ -88,7 +133,13 @@ def infer_typing_attr(node, context=None):
88
133
except InferenceError as exc :
89
134
raise UseInferenceDefault from exc
90
135
91
- if not value .qname ().startswith ("typing." ):
136
+ if (
137
+ not value .qname ().startswith ("typing." )
138
+ or PY37
139
+ and value .qname () in TYPING_ALIAS
140
+ ):
141
+ # If typing subscript belongs to an alias
142
+ # (PY37+) handle it separately later.
92
143
raise UseInferenceDefault
93
144
94
145
node = extract_node (TYPING_TYPE_TEMPLATE .format (value .qname ().split ("." )[- 1 ]))
@@ -161,8 +212,6 @@ def full_raiser(origin_func, attr, *args, **kwargs):
161
212
else :
162
213
return origin_func (attr , * args , ** kwargs )
163
214
164
- if not isinstance (node , nodes .ClassDef ):
165
- raise TypeError ("The parameter type should be ClassDef" )
166
215
try :
167
216
node .getattr ("__class_getitem__" )
168
217
# If we are here, then we are sure to modify object that do have __class_getitem__ method (which origin is one the
@@ -179,52 +228,51 @@ def infer_typing_alias(
179
228
) -> typing .Optional [node_classes .NodeNG ]:
180
229
"""
181
230
Infers the call to _alias function
231
+ Insert ClassDef with same name as aliased class
232
+ in mro to simulate _GenericAlias.
182
233
183
234
:param node: call node
184
235
:param context: inference context
185
236
"""
237
+ if (
238
+ not isinstance (node .parent , nodes .Assign )
239
+ or not len (node .parent .targets ) == 1
240
+ or not isinstance (node .parent .targets [0 ], nodes .AssignName )
241
+ ):
242
+ return None
186
243
res = next (node .args [0 ].infer (context = ctx ))
244
+ assign_name = node .parent .targets [0 ]
187
245
246
+ class_def = nodes .ClassDef (
247
+ name = assign_name .name ,
248
+ lineno = assign_name .lineno ,
249
+ col_offset = assign_name .col_offset ,
250
+ parent = node .parent ,
251
+ )
188
252
if res != astroid .Uninferable and isinstance (res , nodes .ClassDef ):
189
- if not PY39 :
190
- # Here the node is a typing object which is an alias toward
191
- # the corresponding object of collection.abc module.
192
- # Before python3.9 there is no subscript allowed for any of the collections.abc objects.
193
- # The subscript ability is given through the typing._GenericAlias class
194
- # which is the metaclass of the typing object but not the metaclass of the inferred
195
- # collections.abc object.
196
- # Thus we fake subscript ability of the collections.abc object
197
- # by mocking the existence of a __class_getitem__ method.
198
- # We can not add `__getitem__` method in the metaclass of the object because
199
- # the metaclass is shared by subscriptable and not subscriptable object
200
- maybe_type_var = node .args [1 ]
201
- if not (
202
- isinstance (maybe_type_var , node_classes .Tuple )
203
- and not maybe_type_var .elts
204
- ):
205
- # The typing object is subscriptable if the second argument of the _alias function
206
- # is a TypeVar or a tuple of TypeVar. We could check the type of the second argument but
207
- # it appears that in the typing module the second argument is only TypeVar or a tuple of TypeVar or empty tuple.
208
- # This last value means the type is not Generic and thus cannot be subscriptable
209
- func_to_add = astroid .extract_node (CLASS_GETITEM_TEMPLATE )
210
- res .locals ["__class_getitem__" ] = [func_to_add ]
211
- else :
212
- # If we are here, then we are sure to modify object that do have __class_getitem__ method (which origin is one the
213
- # protocol defined in collections module) whereas the typing module consider it should not
214
- # We do not want __class_getitem__ to be found in the classdef
215
- _forbid_class_getitem_access (res )
216
- else :
217
- # Within python3.9 discrepencies exist between some collections.abc containers that are subscriptable whereas
218
- # corresponding containers in the typing module are not! This is the case at least for ByteString.
219
- # It is far more to complex and dangerous to try to remove __class_getitem__ method from all the ancestors of the
220
- # current class. Instead we raise an AttributeInferenceError if we try to access it.
221
- maybe_type_var = node .args [1 ]
222
- if isinstance (maybe_type_var , nodes .Const ) and maybe_type_var .value == 0 :
223
- # Starting with Python39 the _alias function is in fact instantiation of _SpecialGenericAlias class.
224
- # Thus the type is not Generic if the second argument of the call is equal to zero
225
- _forbid_class_getitem_access (res )
226
- return iter ([res ])
227
- return iter ([astroid .Uninferable ])
253
+ # Only add `res` as base if it's a `ClassDef`
254
+ # This isn't the case for `typing.Pattern` and `typing.Match`
255
+ class_def .postinit (bases = [res ], body = [], decorators = None )
256
+
257
+ maybe_type_var = node .args [1 ]
258
+ if (
259
+ not PY39
260
+ and not (
261
+ isinstance (maybe_type_var , node_classes .Tuple ) and not maybe_type_var .elts
262
+ )
263
+ or PY39
264
+ and isinstance (maybe_type_var , nodes .Const )
265
+ and maybe_type_var .value > 0
266
+ ):
267
+ # If typing alias is subscriptable, add `__class_getitem__` to ClassDef
268
+ func_to_add = astroid .extract_node (CLASS_GETITEM_TEMPLATE )
269
+ class_def .locals ["__class_getitem__" ] = [func_to_add ]
270
+ else :
271
+ # If not, make sure that `__class_getitem__` access is forbidden.
272
+ # This is an issue in cases where the aliased class implements it,
273
+ # but the typing alias doesn't. E.g. `typing.ByteString` for PY39+
274
+ _forbid_class_getitem_access (class_def )
275
+ return iter ([class_def ])
228
276
229
277
230
278
MANAGER .register_transform (
0 commit comments