Skip to content

Commit d4e33d0

Browse files
authored
FunctionType typing improvements (#243)
First, represent FunctionType as an enum.Enum. This is more about the data type's concept than any higher-level Enum features. Then use FunctionType so improve the typing of various dictionaries and function signatures. Lastly, use setattr() for our AST monkey patching. I tried a few other approaches here (using ast.(Async)FunctionDef subclasses, introducing a new protocol, etc.), but the monkey patching approach always came out the simplest and most obvious. Using setattr() avoids typing errors related to unknown direct attribute assignments.
1 parent 93dee19 commit d4e33d0

File tree

1 file changed

+26
-17
lines changed

1 file changed

+26
-17
lines changed

src/pep8ext_naming.py

+26-17
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
"""Checker of PEP-8 Naming Conventions."""
22
import ast
3+
import enum
34
from ast import iter_child_nodes
45
from collections import deque
5-
from collections.abc import Iterable, Sequence
6+
from collections.abc import Iterable, Iterator, Sequence
67
from fnmatch import fnmatchcase
78
from functools import partial
89
from itertools import chain
@@ -69,7 +70,8 @@ def __contains__(self, item: object, /) -> bool:
6970
return super().__contains__(item)
7071

7172

72-
class _FunctionType:
73+
@enum.unique
74+
class FunctionType(enum.Enum):
7375
CLASSMETHOD = 'classmethod'
7476
STATICMETHOD = 'staticmethod'
7577
FUNCTION = 'function'
@@ -94,11 +96,11 @@ class _FunctionType:
9496

9597

9698
def _build_decorator_to_type(classmethod_decorators, staticmethod_decorators):
97-
decorator_to_type = {}
99+
decorator_to_type: dict[str, FunctionType] = {}
98100
for decorator in classmethod_decorators:
99-
decorator_to_type[decorator] = _FunctionType.CLASSMETHOD
101+
decorator_to_type[decorator] = FunctionType.CLASSMETHOD
100102
for decorator in staticmethod_decorators:
101-
decorator_to_type[decorator] = _FunctionType.STATICMETHOD
103+
decorator_to_type[decorator] = FunctionType.STATICMETHOD
102104
return decorator_to_type
103105

104106

@@ -188,7 +190,7 @@ def tag_class_functions(self, cls_node):
188190
"""Tag functions if they are methods, classmethods, staticmethods"""
189191
# tries to find all 'old style decorators' like
190192
# m = staticmethod(m)
191-
late_decoration = {}
193+
late_decoration: dict[str, FunctionType] = {}
192194
for node in iter_child_nodes(cls_node):
193195
if not (isinstance(node, ast.Assign) and
194196
isinstance(node.value, ast.Call) and
@@ -213,24 +215,31 @@ def tag_class_functions(self, cls_node):
213215
self.set_function_nodes_types(
214216
iter_child_nodes(cls_node), ismetaclass, late_decoration)
215217

216-
def set_function_nodes_types(self, nodes, ismetaclass, late_decoration):
218+
def set_function_nodes_types(
219+
self,
220+
nodes: Iterator[ast.AST],
221+
ismetaclass: bool,
222+
late_decoration: dict[str, FunctionType],
223+
):
217224
# iterate over all functions and tag them
218225
for node in nodes:
219226
if type(node) in METHOD_CONTAINER_NODES:
220227
self.set_function_nodes_types(
221228
iter_child_nodes(node), ismetaclass, late_decoration)
222229
if not isinstance(node, FUNC_NODES):
223230
continue
224-
node.function_type = _FunctionType.METHOD
231+
setattr(node, 'function_type', FunctionType.METHOD)
225232
if node.name in CLASS_METHODS or ismetaclass:
226-
node.function_type = _FunctionType.CLASSMETHOD
233+
setattr(node, 'function_type', FunctionType.CLASSMETHOD)
227234
if node.name in late_decoration:
228-
node.function_type = late_decoration[node.name]
235+
setattr(node, 'function_type', late_decoration[node.name])
229236
elif node.decorator_list:
230237
for d in node.decorator_list:
231238
name = self.find_decorator_name(d)
232-
if name in self.decorator_to_type:
233-
node.function_type = self.decorator_to_type[name]
239+
if name is None:
240+
continue
241+
if function_type := self.decorator_to_type.get(name):
242+
setattr(node, 'function_type', function_type)
234243
break
235244

236245
@classmethod
@@ -320,18 +329,18 @@ def has_override_decorator(node):
320329
return False
321330

322331
def visit_functiondef(self, node, parents: Sequence, ignored: NameSet):
323-
function_type = getattr(node, 'function_type', _FunctionType.FUNCTION)
332+
function_type = getattr(node, 'function_type', FunctionType.FUNCTION)
324333
name = node.name
325334
if name in ignored:
326335
return
327336
if name in ('__dir__', '__getattr__'):
328337
return
329-
if (function_type != _FunctionType.FUNCTION
338+
if (function_type != FunctionType.FUNCTION
330339
and self.has_override_decorator(node)):
331340
return
332341
if name.lower() != name:
333342
yield self.err(node, 'N802', name=name)
334-
if (function_type == _FunctionType.FUNCTION
343+
if (function_type == FunctionType.FUNCTION
335344
and name[:2] == '__' and name[-2:] == '__'):
336345
yield self.err(node, 'N807', name=name)
337346

@@ -363,9 +372,9 @@ def visit_functiondef(self, node, parents: Sequence, ignored: NameSet):
363372
# for backwards compatibility.
364373
if args and (name := args[0].arg) and name not in ignored:
365374
function_type = getattr(node, 'function_type', None)
366-
if function_type == _FunctionType.METHOD and name != 'self':
375+
if function_type == FunctionType.METHOD and name != 'self':
367376
yield self.err(args[0], 'N805')
368-
elif function_type == _FunctionType.CLASSMETHOD and name != 'cls':
377+
elif function_type == FunctionType.CLASSMETHOD and name != 'cls':
369378
yield self.err(args[0], 'N804')
370379

371380
# Also add the special *arg and **kwarg arguments for the rest of the

0 commit comments

Comments
 (0)