Skip to content

Commit f0b26f9

Browse files
authored
Make Options work under mypyc (#5518)
This requires reworking a bunch of operations to not directly depend on the __dict__
1 parent 6bcaf40 commit f0b26f9

File tree

5 files changed

+77
-70
lines changed

5 files changed

+77
-70
lines changed

mypy/main.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from mypy.find_sources import create_source_list, InvalidSourceList
2020
from mypy.fscache import FileSystemCache
2121
from mypy.errors import CompileError
22-
from mypy.options import Options, BuildType
22+
from mypy.options import Options, BuildType, PER_MODULE_OPTIONS
2323
from mypy.report import reporter_classes
2424

2525
from mypy.version import __version__
@@ -1014,11 +1014,11 @@ def parse_config_file(options: Options, filename: Optional[str]) -> None:
10141014
print("%s: Per-module sections should not specify reports (%s)" %
10151015
(prefix, ', '.join(s + '_report' for s in sorted(report_dirs))),
10161016
file=sys.stderr)
1017-
if set(updates) - Options.PER_MODULE_OPTIONS:
1017+
if set(updates) - PER_MODULE_OPTIONS:
10181018
print("%s: Per-module sections should only specify per-module flags (%s)" %
1019-
(prefix, ', '.join(sorted(set(updates) - Options.PER_MODULE_OPTIONS))),
1019+
(prefix, ', '.join(sorted(set(updates) - PER_MODULE_OPTIONS))),
10201020
file=sys.stderr)
1021-
updates = {k: v for k, v in updates.items() if k in Options.PER_MODULE_OPTIONS}
1021+
updates = {k: v for k, v in updates.items() if k in PER_MODULE_OPTIONS}
10221022
globs = name[5:]
10231023
for glob in globs.split(','):
10241024
# For backwards compatibility, replace (back)slashes with dots.

mypy/options.py

Lines changed: 54 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -4,54 +4,59 @@
44
import sys
55

66
from typing import Dict, List, Mapping, Optional, Pattern, Set, Tuple
7+
MYPY = False
8+
if MYPY:
9+
from typing import ClassVar
710

811
from mypy import defaults
12+
from mypy.util import get_class_descriptors, replace_object_state
913

1014

1115
class BuildType:
12-
STANDARD = 0
13-
MODULE = 1
14-
PROGRAM_TEXT = 2
16+
STANDARD = 0 # type: ClassVar[int]
17+
MODULE = 1 # type: ClassVar[int]
18+
PROGRAM_TEXT = 2 # type: ClassVar[int]
19+
20+
21+
PER_MODULE_OPTIONS = {
22+
# Please keep this list sorted
23+
"always_false",
24+
"always_true",
25+
"check_untyped_defs",
26+
"debug_cache",
27+
"disallow_any_decorated",
28+
"disallow_any_explicit",
29+
"disallow_any_expr",
30+
"disallow_any_generics",
31+
"disallow_any_unimported",
32+
"disallow_incomplete_defs",
33+
"disallow_subclassing_any",
34+
"disallow_untyped_calls",
35+
"disallow_untyped_decorators",
36+
"disallow_untyped_defs",
37+
"follow_imports",
38+
"follow_imports_for_stubs",
39+
"ignore_errors",
40+
"ignore_missing_imports",
41+
"local_partial_types",
42+
"no_implicit_optional",
43+
"show_none_errors",
44+
"strict_boolean",
45+
"strict_optional",
46+
"strict_optional_whitelist",
47+
"warn_no_return",
48+
"warn_return_any",
49+
"warn_unused_ignores",
50+
}
51+
52+
OPTIONS_AFFECTING_CACHE = ((PER_MODULE_OPTIONS |
53+
{"quick_and_dirty", "platform", "bazel"})
54+
- {"debug_cache"})
1555

1656

1757
class Options:
1858
"""Options collected from flags."""
1959

20-
PER_MODULE_OPTIONS = {
21-
# Please keep this list sorted
22-
"always_false",
23-
"always_true",
24-
"check_untyped_defs",
25-
"debug_cache",
26-
"disallow_any_decorated",
27-
"disallow_any_explicit",
28-
"disallow_any_expr",
29-
"disallow_any_generics",
30-
"disallow_any_unimported",
31-
"disallow_incomplete_defs",
32-
"disallow_subclassing_any",
33-
"disallow_untyped_calls",
34-
"disallow_untyped_decorators",
35-
"disallow_untyped_defs",
36-
"follow_imports",
37-
"follow_imports_for_stubs",
38-
"ignore_errors",
39-
"ignore_missing_imports",
40-
"local_partial_types",
41-
"no_implicit_optional",
42-
"show_none_errors",
43-
"strict_boolean",
44-
"strict_optional",
45-
"strict_optional_whitelist",
46-
"warn_no_return",
47-
"warn_return_any",
48-
"warn_unused_ignores",
49-
}
50-
51-
OPTIONS_AFFECTING_CACHE = ((PER_MODULE_OPTIONS |
52-
{"quick_and_dirty", "platform", "bazel"})
53-
- {"debug_cache"})
54-
5560
def __init__(self) -> None:
5661
# Cache for clone_for_module()
5762
self.per_module_cache = None # type: Optional[Dict[str, Options]]
@@ -209,23 +214,23 @@ def __init__(self) -> None:
209214

210215
def snapshot(self) -> object:
211216
"""Produce a comparable snapshot of this Option"""
212-
d = dict(self.__dict__)
217+
# Under mypyc, we don't have a __dict__, so we need to do worse things.
218+
d = dict(getattr(self, '__dict__', ()))
219+
for k in get_class_descriptors(Options):
220+
if hasattr(self, k):
221+
d[k] = getattr(self, k)
213222
del d['per_module_cache']
214223
return d
215224

216-
def __eq__(self, other: object) -> bool:
217-
return self.__class__ == other.__class__ and self.__dict__ == other.__dict__
218-
219-
def __ne__(self, other: object) -> bool:
220-
return not self == other
221-
222225
def __repr__(self) -> str:
223226
return 'Options({})'.format(pprint.pformat(self.snapshot()))
224227

225228
def apply_changes(self, changes: Dict[str, object]) -> 'Options':
226229
new_options = Options()
227-
new_options.__dict__.update(self.__dict__)
228-
new_options.__dict__.update(changes)
230+
# Under mypyc, we don't have a __dict__, so we need to do worse things.
231+
replace_object_state(new_options, self, copy_dict=True)
232+
for key, value in changes.items():
233+
setattr(new_options, key, value)
229234
return new_options
230235

231236
def build_per_module_cache(self) -> None:
@@ -281,7 +286,7 @@ def clone_for_module(self, module: str) -> 'Options':
281286
"""
282287
if self.per_module_cache is None:
283288
self.build_per_module_cache()
284-
assert self.per_module_cache is not None
289+
assert self.per_module_cache is not None
285290

286291
# If the module just directly has a config entry, use it.
287292
if module in self.per_module_cache:
@@ -327,4 +332,4 @@ def compile_glob(self, s: str) -> Pattern[str]:
327332
return re.compile(expr + '\\Z')
328333

329334
def select_options_affecting_cache(self) -> Mapping[str, object]:
330-
return {opt: getattr(self, opt) for opt in self.OPTIONS_AFFECTING_CACHE}
335+
return {opt: getattr(self, opt) for opt in OPTIONS_AFFECTING_CACHE}

mypy/test/testargs.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def test_coherence(self) -> None:
2121
_, parsed_options = process_options([], require_targets=False)
2222
# FIX: test this too. Requires changing working dir to avoid finding 'setup.cfg'
2323
options.config_file = parsed_options.config_file
24-
assert_equal(options, parsed_options)
24+
assert_equal(options.snapshot(), parsed_options.snapshot())
2525

2626
def test_executable_inference(self) -> None:
2727
"""Test the --python-executable flag with --python-version"""

mypy/types.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1749,11 +1749,7 @@ def copy_type(t: Type) -> Type:
17491749
# and copying everything in with replace_object_state.
17501750
typ = type(t)
17511751
nt = typ.__new__(typ)
1752-
replace_object_state(nt, t)
1753-
# replace_object_state leaves the new object with the same
1754-
# __dict__ as the old, so make a copy.
1755-
if hasattr(nt, '__dict__'):
1756-
nt.__dict__ = nt.__dict__.copy()
1752+
replace_object_state(nt, t, copy_dict=True)
17571753
return nt
17581754

17591755

mypy/util.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,18 @@ def correct_relative_import(cur_mod_id: str,
196196
fields_cache = {} # type: Dict[Type[object], List[str]]
197197

198198

199-
def replace_object_state(new: object, old: object) -> None:
199+
def get_class_descriptors(cls: 'Type[object]') -> Sequence[str]:
200+
# Maintain a cache of type -> attributes defined by descriptors in the class
201+
# (that is, attributes from __slots__ and C extension classes)
202+
if cls not in fields_cache:
203+
members = inspect.getmembers(
204+
cls,
205+
lambda o: inspect.isgetsetdescriptor(o) or inspect.ismemberdescriptor(o))
206+
fields_cache[cls] = [x for x, y in members if x != '__weakref__']
207+
return fields_cache[cls]
208+
209+
210+
def replace_object_state(new: object, old: object, copy_dict: bool=False) -> None:
200211
"""Copy state of old node to the new node.
201212
202213
This handles cases where there is __dict__ and/or attribute descriptors
@@ -205,17 +216,12 @@ def replace_object_state(new: object, old: object) -> None:
205216
Assume that both objects have the same __class__.
206217
"""
207218
if hasattr(old, '__dict__'):
208-
new.__dict__ = old.__dict__
219+
if copy_dict:
220+
new.__dict__ = dict(old.__dict__)
221+
else:
222+
new.__dict__ = old.__dict__
209223

210-
cls = old.__class__
211-
# Maintain a cache of type -> attributes defined by descriptors in the class
212-
# (that is, attributes from __slots__ and C extension classes)
213-
if cls not in fields_cache:
214-
members = inspect.getmembers(
215-
cls,
216-
lambda o: inspect.isgetsetdescriptor(o) or inspect.ismemberdescriptor(o))
217-
fields_cache[cls] = [x for x, y in members if x != '__weakref__']
218-
for attr in fields_cache[cls]:
224+
for attr in get_class_descriptors(old.__class__):
219225
try:
220226
if hasattr(old, attr):
221227
setattr(new, attr, getattr(old, attr))

0 commit comments

Comments
 (0)