-
Notifications
You must be signed in to change notification settings - Fork 290
Expand file tree
/
Copy pathclassdef.py
More file actions
104 lines (80 loc) · 3.56 KB
/
classdef.py
File metadata and controls
104 lines (80 loc) · 3.56 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
"""Class definitions in pyi files."""
import collections
from typing import Dict, Optional, List, Tuple
from pytype.pyi.types import ParseError # pylint: disable=g-importing-member
from pytype.pytd import pytd
from pytype.pytd import pytd_utils
from pytype.pytd.parse import node as pytd_node
from typed_ast import ast3
_TYPED_DICT_ALIASES = ("typing.TypedDict", "typing_extensions.TypedDict")
def get_parents(
bases: List[ast3.AST]
) -> Tuple[List[pytd_node.Node], Optional[int]]:
"""Collect base classes and namedtuple index."""
parents = []
namedtuple_index = None
for i, p in enumerate(bases):
if _is_parameterized_protocol(p):
# From PEP 544: "`Protocol[T, S, ...]` is allowed as a shorthand for
# `Protocol, Generic[T, S, ...]`."
# https://www.python.org/dev/peps/pep-0544/#generic-protocols
parents.append(p.base_type)
parents.append(p.Replace(base_type=pytd.NamedType("typing.Generic")))
elif isinstance(p, pytd.NamedType) and p.name == "typing.NamedTuple":
if namedtuple_index is not None:
raise ParseError("cannot inherit from bare NamedTuple more than once")
namedtuple_index = i
parents.append(p)
elif isinstance(p, pytd.Type):
parents.append(p)
else:
msg = "Unexpected class base:" + p
raise ParseError(msg)
return parents, namedtuple_index
def get_metaclass(keywords: List[ast3.AST], parents: List[pytd_node.Node]):
"""Scan keywords for a metaclass."""
for k in keywords:
keyword, value = k.arg, k.value
if keyword not in ("metaclass", "total"):
raise ParseError("Unexpected classdef kwarg %r" % keyword)
elif keyword == "total" and not any(
isinstance(parent, pytd.NamedType) and
pytd_utils.MatchesFullName(parent, _TYPED_DICT_ALIASES)
for parent in parents):
raise ParseError(
"'total' allowed as classdef kwarg only for TypedDict subclasses")
if keyword == "metaclass":
return value
return None
def get_decorators(decorators: List[str], type_map: Dict[str, pytd_node.Node]):
"""Process a class decorator list."""
# Drop the @type_check_only decorator from classes
# TODO(mdemello): Workaround for the bug that typing.foo class decorators
# don't add the import, since typing.type_check_only is the only one.
decorators = [x for x in decorators if x != "type_check_only"]
# Check for some function/method-only decorators
nonclass = {"property", "classmethod", "staticmethod", "overload"}
unsupported_decorators = set(decorators) & nonclass
if unsupported_decorators:
raise ParseError("Unsupported class decorators: %s" % ", ".join(
unsupported_decorators))
# Convert decorators to named types. These are wrapped as aliases because we
# otherwise do not allow referencing functions as types.
return [pytd.Alias(d, type_map.get(d) or pytd.NamedType(d))
for d in decorators]
def check_for_duplicate_defs(methods, constants, aliases) -> None:
"""Check a class's list of definitions for duplicates."""
all_names = (list(set(f.name for f in methods)) +
[c.name for c in constants] +
[a.name for a in aliases])
duplicates = [name
for name, count in collections.Counter(all_names).items()
if count >= 2]
if duplicates:
raise ParseError(
"Duplicate class-level identifier(s): " + ", ".join(duplicates))
#------------------------------------------------------
# pytd utils
def _is_parameterized_protocol(t) -> bool:
return (isinstance(t, pytd.GenericType) and
t.base_type.name == "typing.Protocol")