Skip to content

Commit f1a2722

Browse files
author
Konstantin Ignatov
committed
Ensure builtin modules are from typeshed sooner
It should work now with custom-typeshed-dir. Fixes #1876
1 parent 074f8f8 commit f1a2722

File tree

7 files changed

+76
-18
lines changed

7 files changed

+76
-18
lines changed

build-requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
-r mypy-requirements.txt
2+
types-setuptools
23
types-typed-ast>=1.5.0,<1.6.0

mypy-requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
typing_extensions>=3.10
22
mypy_extensions>=0.4.3
3+
importlib_resources; python_version<'3.7'
34
typed_ast>=1.4.0,<2; python_version<'3.8'
45
tomli>=1.1.0; python_version<'3.11'

mypy/build.py

+27-13
Original file line numberDiff line numberDiff line change
@@ -657,6 +657,33 @@ def __init__(
657657
self.find_module_cache = FindModuleCache(
658658
self.search_paths, self.fscache, self.options, source_set=self.source_set
659659
)
660+
for module in CORE_BUILTIN_MODULES:
661+
if options.use_builtins_fixtures:
662+
continue
663+
if module == "_importlib_modulespec":
664+
continue
665+
path = self.find_module_cache.find_module(module)
666+
if not isinstance(path, str):
667+
raise CompileError(
668+
[f"Failed to find builtin module {module}, perhaps typeshed is broken?"]
669+
)
670+
if is_typeshed_file(path):
671+
continue
672+
if is_stub_package_file(path):
673+
continue
674+
if options.custom_typeshed_dir is not None:
675+
# Check if module lives under custom_typeshed_dir subtree
676+
custom_typeshed_dir = os.path.abspath(options.custom_typeshed_dir)
677+
if os.path.commonpath((path, custom_typeshed_dir)) == custom_typeshed_dir:
678+
continue
679+
680+
raise CompileError(
681+
[
682+
f'mypy: "{os.path.relpath(path)}" shadows library module "{module}"',
683+
f'note: A user-defined top-level module with name "{module}" is not supported',
684+
]
685+
)
686+
660687
self.metastore = create_metastore(options)
661688

662689
# a mapping from source files to their corresponding shadow files
@@ -2583,19 +2610,6 @@ def find_module_and_diagnose(
25832610
if is_sub_path(result, dir):
25842611
# Silence errors in site-package dirs and typeshed
25852612
follow_imports = "silent"
2586-
if (
2587-
id in CORE_BUILTIN_MODULES
2588-
and not is_typeshed_file(result)
2589-
and not is_stub_package_file(result)
2590-
and not options.use_builtins_fixtures
2591-
and not options.custom_typeshed_dir
2592-
):
2593-
raise CompileError(
2594-
[
2595-
f'mypy: "{os.path.relpath(result)}" shadows library module "{id}"',
2596-
f'note: A user-defined top-level module with name "{id}" is not supported',
2597-
]
2598-
)
25992613
return (result, follow_imports)
26002614
else:
26012615
# Could not find a module. Typically the reason is a

mypy/test/testgraph.py

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ def test_scc(self) -> None:
4141
def _make_manager(self) -> BuildManager:
4242
errors = Errors()
4343
options = Options()
44+
options.use_builtins_fixtures = True
4445
fscache = FileSystemCache()
4546
search_paths = SearchPaths((), (), (), ())
4647
manager = BuildManager(

mypy/util.py

+18-3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@
88
import shutil
99
import sys
1010
import time
11+
12+
try:
13+
from importlib import resources as importlib_resources # type: ignore[attr-defined]
14+
except ImportError: # <python3.7
15+
import importlib_resources # type: ignore
16+
1117
from typing import (
1218
IO,
1319
Callable,
@@ -36,6 +42,13 @@
3642

3743
T = TypeVar("T")
3844

45+
with importlib_resources.path(
46+
"mypy", # mypy-c doesn't support __package__
47+
"py.typed", # a marker file for type information, we assume typeshed to live in the same dir
48+
) as _resource:
49+
TYPESHED_DIR: Final = str(_resource.parent / "typeshed")
50+
51+
3952
ENCODING_RE: Final = re.compile(rb"([ \t\v]*#.*(\r\n?|\n))??[ \t\v]*#.*coding[:=][ \t]*([-\w.]+)")
4053

4154
DEFAULT_SOURCE_OFFSET: Final = 4
@@ -772,15 +785,17 @@ def format_error(
772785

773786

774787
def is_typeshed_file(file: str) -> bool:
775-
# gross, but no other clear way to tell
776-
return "typeshed" in os.path.abspath(file).split(os.sep)
788+
try:
789+
return os.path.commonpath((TYPESHED_DIR, os.path.abspath(file))) == TYPESHED_DIR
790+
except ValueError: # Different drives on Windows
791+
return False
777792

778793

779794
def is_stub_package_file(file: str) -> bool:
780795
# Use hacky heuristics to check whether file is part of a PEP 561 stub package.
781796
if not file.endswith(".pyi"):
782797
return False
783-
return any(component.endswith("-stubs") for component in os.path.abspath(file).split(os.sep))
798+
return any(component.endswith("-stubs") for component in os.path.split(os.path.abspath(file)))
784799

785800

786801
def unnamed_function(name: Optional[str]) -> bool:

mypyc/build.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545

4646
try:
4747
# Import setuptools so that it monkey-patch overrides distutils
48-
import setuptools # type: ignore # noqa
48+
import setuptools # noqa
4949
except ImportError:
5050
if sys.version_info >= (3, 12):
5151
# Raise on Python 3.12, since distutils will go away forever
@@ -61,7 +61,7 @@ def get_extension() -> Type["Extension"]:
6161
if not use_setuptools:
6262
from distutils.core import Extension
6363
else:
64-
from setuptools import Extension # type: ignore # noqa
64+
from setuptools import Extension # noqa
6565

6666
return Extension
6767

test-data/unit/cmdline.test

+26
Original file line numberDiff line numberDiff line change
@@ -1439,3 +1439,29 @@ b\.c \d+
14391439
# cmd: mypy --enable-incomplete-features a.py
14401440
[file a.py]
14411441
pass
1442+
1443+
[case testShadowTypingModuleEarlyLoad]
1444+
# cmd: mypy dir
1445+
[file dir/__init__.py]
1446+
from typing import Union
1447+
1448+
def foo(a: Union[int, str]) -> str:
1449+
return str
1450+
[file typing.py]
1451+
# Since this file will be picked by mypy itself, we need it to be a fully-working typing
1452+
# A bare minimum would be NamedTuple and TypedDict, which are used in runtime,
1453+
# everything else technically can be just mocked.
1454+
import sys
1455+
import os
1456+
del sys.modules["typing"]
1457+
path = sys.path
1458+
try:
1459+
sys.path.remove(os.getcwd())
1460+
except ValueError:
1461+
sys.path.remove("") # python 3.6
1462+
from typing import *
1463+
sys.path = path
1464+
[out]
1465+
mypy: "typing.py" shadows library module "typing"
1466+
note: A user-defined top-level module with name "typing" is not supported
1467+
== Return code: 2

0 commit comments

Comments
 (0)