forked from python/mypy
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsemanal_classprop.py
172 lines (153 loc) · 7.04 KB
/
semanal_classprop.py
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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
"""Calculate some properties of classes.
These happen after semantic analysis and before type checking.
"""
from typing import List, Set, Optional
from typing_extensions import Final
from mypy.nodes import (
Node, TypeInfo, Var, Decorator, OverloadedFuncDef, SymbolTable, CallExpr, PromoteExpr,
)
from mypy.types import Instance, Type
from mypy.errors import Errors
from mypy.options import Options
# Hard coded type promotions (shared between all Python versions).
# These add extra ad-hoc edges to the subtyping relation. For example,
# int is considered a subtype of float, even though there is no
# subclass relationship.
TYPE_PROMOTIONS: Final = {
'builtins.int': 'float',
'builtins.float': 'complex',
}
# Hard coded type promotions for Python 3.
#
# Note that the bytearray -> bytes promotion is a little unsafe
# as some functions only accept bytes objects. Here convenience
# trumps safety.
TYPE_PROMOTIONS_PYTHON3: Final = TYPE_PROMOTIONS.copy()
TYPE_PROMOTIONS_PYTHON3.update({
'builtins.bytearray': 'bytes',
'builtins.memoryview': 'bytes',
})
# Hard coded type promotions for Python 2.
#
# These promotions are unsafe, but we are doing them anyway
# for convenience and also for Python 3 compatibility
# (bytearray -> str).
TYPE_PROMOTIONS_PYTHON2: Final = TYPE_PROMOTIONS.copy()
TYPE_PROMOTIONS_PYTHON2.update({
'builtins.str': 'unicode',
'builtins.bytearray': 'str',
'builtins.memoryview': 'str',
})
def calculate_class_abstract_status(typ: TypeInfo, is_stub_file: bool, errors: Errors) -> None:
"""Calculate abstract status of a class.
Set is_abstract of the type to True if the type has an unimplemented
abstract attribute. Also compute a list of abstract attributes.
Report error is required ABCMeta metaclass is missing.
"""
if typ.typeddict_type:
return # TypedDict can't be abstract
concrete: Set[str] = set()
abstract: List[str] = []
abstract_in_this_class: List[str] = []
if typ.is_newtype:
# Special case: NewTypes are considered as always non-abstract, so they can be used as:
# Config = NewType('Config', Mapping[str, str])
# default = Config({'cannot': 'modify'}) # OK
typ.abstract_attributes = []
return
for base in typ.mro:
for name, symnode in base.names.items():
node = symnode.node
if isinstance(node, OverloadedFuncDef):
# Unwrap an overloaded function definition. We can just
# check arbitrarily the first overload item. If the
# different items have a different abstract status, there
# should be an error reported elsewhere.
if node.items: # can be empty for invalid overloads
func: Optional[Node] = node.items[0]
else:
func = None
else:
func = node
if isinstance(func, Decorator):
fdef = func.func
if fdef.is_abstract and name not in concrete:
typ.is_abstract = True
abstract.append(name)
if base is typ:
abstract_in_this_class.append(name)
elif isinstance(node, Var):
if node.is_abstract_var and name not in concrete:
typ.is_abstract = True
abstract.append(name)
if base is typ:
abstract_in_this_class.append(name)
concrete.add(name)
# In stubs, abstract classes need to be explicitly marked because it is too
# easy to accidentally leave a concrete class abstract by forgetting to
# implement some methods.
typ.abstract_attributes = sorted(abstract)
if is_stub_file:
if typ.declared_metaclass and typ.declared_metaclass.type.fullname == 'abc.ABCMeta':
return
if typ.is_protocol:
return
if abstract and not abstract_in_this_class:
def report(message: str, severity: str) -> None:
errors.report(typ.line, typ.column, message, severity=severity)
attrs = ", ".join('"{}"'.format(attr) for attr in sorted(abstract))
report("Class {} has abstract attributes {}".format(typ.fullname, attrs), 'error')
report("If it is meant to be abstract, add 'abc.ABCMeta' as an explicit metaclass",
'note')
if typ.is_final and abstract:
attrs = ", ".join('"{}"'.format(attr) for attr in sorted(abstract))
errors.report(typ.line, typ.column,
"Final class {} has abstract attributes {}".format(typ.fullname, attrs))
def check_protocol_status(info: TypeInfo, errors: Errors) -> None:
"""Check that all classes in MRO of a protocol are protocols"""
if info.is_protocol:
for type in info.bases:
if not type.type.is_protocol and type.type.fullname != 'builtins.object':
def report(message: str, severity: str) -> None:
errors.report(info.line, info.column, message, severity=severity)
report('All bases of a protocol must be protocols', 'error')
def calculate_class_vars(info: TypeInfo) -> None:
"""Try to infer additional class variables.
Subclass attribute assignments with no type annotation are assumed
to be classvar if overriding a declared classvar from the base
class.
This must happen after the main semantic analysis pass, since
this depends on base class bodies having been fully analyzed.
"""
for name, sym in info.names.items():
node = sym.node
if isinstance(node, Var) and node.info and node.is_inferred and not node.is_classvar:
for base in info.mro[1:]:
member = base.names.get(name)
if (member is not None
and isinstance(member.node, Var)
and member.node.is_classvar):
node.is_classvar = True
def add_type_promotion(info: TypeInfo, module_names: SymbolTable, options: Options) -> None:
"""Setup extra, ad-hoc subtyping relationships between classes (promotion).
This includes things like 'int' being compatible with 'float'.
"""
defn = info.defn
promote_target: Optional[Type] = None
for decorator in defn.decorators:
if isinstance(decorator, CallExpr):
analyzed = decorator.analyzed
if isinstance(analyzed, PromoteExpr):
# _promote class decorator (undocumented feature).
promote_target = analyzed.type
if not promote_target:
promotions = (TYPE_PROMOTIONS_PYTHON3 if options.python_version[0] >= 3
else TYPE_PROMOTIONS_PYTHON2)
if defn.fullname in promotions:
target_sym = module_names.get(promotions[defn.fullname])
# With test stubs, the target may not exist.
if target_sym:
target_info = target_sym.node
assert isinstance(target_info, TypeInfo)
promote_target = Instance(target_info, [])
defn.info._promote = promote_target