Skip to content

Commit 14de8c6

Browse files
authored
Use __truediv__ for python2 with __future__ import, refs #11740 (#11787)
Now `__future__` imports do not leak in semanal.py. Closes #11740 Refs #11741 Refs #11276 Refs #11700
1 parent 115ac31 commit 14de8c6

File tree

5 files changed

+87
-10
lines changed

5 files changed

+87
-10
lines changed

mypy/build.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -2186,8 +2186,10 @@ def type_checker(self) -> TypeChecker:
21862186
if not self._type_checker:
21872187
assert self.tree is not None, "Internal error: must be called on parsed file only"
21882188
manager = self.manager
2189-
self._type_checker = TypeChecker(manager.errors, manager.modules, self.options,
2190-
self.tree, self.xpath, manager.plugin)
2189+
self._type_checker = TypeChecker(
2190+
manager.errors, manager.modules, self.options,
2191+
self.tree, self.xpath, manager.plugin,
2192+
)
21912193
return self._type_checker
21922194

21932195
def type_map(self) -> Dict[Expression, Type]:

mypy/checkexpr.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -2416,8 +2416,11 @@ def dangerous_comparison(self, left: Type, right: Type,
24162416

24172417
def get_operator_method(self, op: str) -> str:
24182418
if op == '/' and self.chk.options.python_version[0] == 2:
2419-
# TODO also check for "from __future__ import division"
2420-
return '__div__'
2419+
return (
2420+
'__truediv__'
2421+
if self.chk.tree.is_future_flag_set('division')
2422+
else '__div__'
2423+
)
24212424
else:
24222425
return operators.op_methods[op]
24232426

mypy/nodes.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,8 @@ class MypyFile(SymbolNode):
258258

259259
__slots__ = ('_fullname', 'path', 'defs', 'alias_deps',
260260
'is_bom', 'names', 'imports', 'ignored_lines', 'is_stub',
261-
'is_cache_skeleton', 'is_partial_stub_package', 'plugin_deps')
261+
'is_cache_skeleton', 'is_partial_stub_package', 'plugin_deps',
262+
'future_import_flags')
262263

263264
# Fully qualified module name
264265
_fullname: Bogus[str]
@@ -287,6 +288,8 @@ class MypyFile(SymbolNode):
287288
is_partial_stub_package: bool
288289
# Plugin-created dependencies
289290
plugin_deps: Dict[str, Set[str]]
291+
# Future imports defined in this file. Populated during semantic analysis.
292+
future_import_flags: Set[str]
290293

291294
def __init__(self,
292295
defs: List[Statement],
@@ -309,6 +312,7 @@ def __init__(self,
309312
self.is_stub = False
310313
self.is_cache_skeleton = False
311314
self.is_partial_stub_package = False
315+
self.future_import_flags = set()
312316

313317
def local_definitions(self) -> Iterator[Definition]:
314318
"""Return all definitions within the module (including nested).
@@ -331,13 +335,17 @@ def accept(self, visitor: NodeVisitor[T]) -> T:
331335
def is_package_init_file(self) -> bool:
332336
return len(self.path) != 0 and os.path.basename(self.path).startswith('__init__.')
333337

338+
def is_future_flag_set(self, flag: str) -> bool:
339+
return flag in self.future_import_flags
340+
334341
def serialize(self) -> JsonDict:
335342
return {'.class': 'MypyFile',
336343
'_fullname': self._fullname,
337344
'names': self.names.serialize(self._fullname),
338345
'is_stub': self.is_stub,
339346
'path': self.path,
340347
'is_partial_stub_package': self.is_partial_stub_package,
348+
'future_import_flags': list(self.future_import_flags),
341349
}
342350

343351
@classmethod
@@ -350,6 +358,7 @@ def deserialize(cls, data: JsonDict) -> 'MypyFile':
350358
tree.path = data['path']
351359
tree.is_partial_stub_package = data['is_partial_stub_package']
352360
tree.is_cache_skeleton = True
361+
tree.future_import_flags = set(data['future_import_flags'])
353362
return tree
354363

355364

mypy/semanal.py

+4-5
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,6 @@ class SemanticAnalyzer(NodeVisitor[None],
226226
errors: Errors # Keeps track of generated errors
227227
plugin: Plugin # Mypy plugin for special casing of library features
228228
statement: Optional[Statement] = None # Statement/definition being analyzed
229-
future_import_flags: Set[str]
230229

231230
# Mapping from 'async def' function definitions to their return type wrapped as a
232231
# 'Coroutine[Any, Any, T]'. Used to keep track of whether a function definition's
@@ -291,8 +290,6 @@ def __init__(self,
291290
# current SCC or top-level function.
292291
self.deferral_debug_context: List[Tuple[str, int]] = []
293292

294-
self.future_import_flags: Set[str] = set()
295-
296293
# mypyc doesn't properly handle implementing an abstractproperty
297294
# with a regular attribute so we make them properties
298295
@property
@@ -5374,10 +5371,12 @@ def parse_bool(self, expr: Expression) -> Optional[bool]:
53745371

53755372
def set_future_import_flags(self, module_name: str) -> None:
53765373
if module_name in FUTURE_IMPORTS:
5377-
self.future_import_flags.add(FUTURE_IMPORTS[module_name])
5374+
self.modules[self.cur_mod_id].future_import_flags.add(
5375+
FUTURE_IMPORTS[module_name],
5376+
)
53785377

53795378
def is_future_flag_set(self, flag: str) -> bool:
5380-
return flag in self.future_import_flags
5379+
return self.modules[self.cur_mod_id].is_future_flag_set(flag)
53815380

53825381

53835382
class HasPlaceholders(TypeQuery[bool]):

test-data/unit/check-expressions.test

+64
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,70 @@ class C:
202202
pass
203203
[builtins fixtures/tuple.pyi]
204204

205+
[case testDivPython2]
206+
# flags: --python-version 2.7
207+
class A(object):
208+
def __div__(self, other):
209+
# type: (A, str) -> str
210+
return 'a'
211+
212+
a = A()
213+
reveal_type(a / 'b') # N: Revealed type is "builtins.str"
214+
a / 1 # E: Unsupported operand types for / ("A" and "int")
215+
[builtins fixtures/bool.pyi]
216+
217+
[case testDivPython2FutureImport]
218+
# flags: --python-version 2.7
219+
from __future__ import division
220+
221+
class A(object):
222+
def __truediv__(self, other):
223+
# type: (A, str) -> str
224+
return 'a'
225+
226+
a = A()
227+
reveal_type(a / 'b') # N: Revealed type is "builtins.str"
228+
a / 1 # E: Unsupported operand types for / ("A" and "int")
229+
[builtins fixtures/bool.pyi]
230+
231+
[case testDivPython2FutureImportNotLeaking]
232+
# flags: --python-version 2.7
233+
import m1
234+
235+
[file m1.py]
236+
import m2
237+
238+
class A(object):
239+
def __div__(self, other):
240+
# type: (A, str) -> str
241+
return 'a'
242+
243+
a = A()
244+
reveal_type(a / 'b') # N: Revealed type is "builtins.str"
245+
a / 1 # E: Unsupported operand types for / ("A" and "int")
246+
[file m2.py]
247+
from __future__ import division
248+
[builtins fixtures/bool.pyi]
249+
250+
[case testDivPython2FutureImportNotLeaking2]
251+
# flags: --python-version 2.7
252+
import m1
253+
254+
[file m1.py]
255+
import m2
256+
257+
class A(object):
258+
def __div__(self, other):
259+
# type: (A, str) -> str
260+
return 'a'
261+
262+
a = A()
263+
reveal_type(a / 'b') # N: Revealed type is "builtins.str"
264+
a / 1 # E: Unsupported operand types for / ("A" and "int")
265+
[file m2.py]
266+
# empty
267+
[builtins fixtures/bool.pyi]
268+
205269
[case testIntDiv]
206270
a, b, c = None, None, None # type: (A, B, C)
207271
if int():

0 commit comments

Comments
 (0)