Skip to content

Commit 854ad18

Browse files
committed
Make is_sub_path faster (#17962)
See #17948 - 1.01x faster on clean - 1.06x faster on long - 1.04x faster on openai - 1.26x faster on openai incremental
1 parent 50aa4ca commit 854ad18

File tree

3 files changed

+31
-11
lines changed

3 files changed

+31
-11
lines changed

mypy/build.py

+4-5
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
get_mypy_comments,
6060
hash_digest,
6161
is_stub_package_file,
62-
is_sub_path,
62+
is_sub_path_normabs,
6363
is_typeshed_file,
6464
module_prefix,
6565
read_py_file,
@@ -3528,10 +3528,9 @@ def is_silent_import_module(manager: BuildManager, path: str) -> bool:
35283528
if manager.options.no_silence_site_packages:
35293529
return False
35303530
# Silence errors in site-package dirs and typeshed
3531-
return any(
3532-
is_sub_path(path, dir)
3533-
for dir in manager.search_paths.package_path + manager.search_paths.typeshed_path
3534-
)
3531+
if any(is_sub_path_normabs(path, dir) for dir in manager.search_paths.package_path):
3532+
return True
3533+
return any(is_sub_path_normabs(path, dir) for dir in manager.search_paths.typeshed_path)
35353534

35363535

35373536
def write_undocumented_ref_info(

mypy/modulefinder.py

+7-2
Original file line numberDiff line numberDiff line change
@@ -668,10 +668,13 @@ def mypy_path() -> list[str]:
668668
def default_lib_path(
669669
data_dir: str, pyversion: tuple[int, int], custom_typeshed_dir: str | None
670670
) -> list[str]:
671-
"""Return default standard library search paths."""
671+
"""Return default standard library search paths. Guaranteed to be normalised."""
672+
673+
data_dir = os.path.abspath(data_dir)
672674
path: list[str] = []
673675

674676
if custom_typeshed_dir:
677+
custom_typeshed_dir = os.path.abspath(custom_typeshed_dir)
675678
typeshed_dir = os.path.join(custom_typeshed_dir, "stdlib")
676679
mypy_extensions_dir = os.path.join(custom_typeshed_dir, "stubs", "mypy-extensions")
677680
versions_file = os.path.join(typeshed_dir, "VERSIONS")
@@ -711,7 +714,7 @@ def default_lib_path(
711714

712715
@functools.lru_cache(maxsize=None)
713716
def get_search_dirs(python_executable: str | None) -> tuple[list[str], list[str]]:
714-
"""Find package directories for given python.
717+
"""Find package directories for given python. Guaranteed to return absolute paths.
715718
716719
This runs a subprocess call, which generates a list of the directories in sys.path.
717720
To avoid repeatedly calling a subprocess (which can be slow!) we
@@ -773,6 +776,7 @@ def compute_search_paths(
773776
root_dir = os.getenv("MYPY_TEST_PREFIX", None)
774777
if not root_dir:
775778
root_dir = os.path.dirname(os.path.dirname(__file__))
779+
root_dir = os.path.abspath(root_dir)
776780
lib_path.appendleft(os.path.join(root_dir, "test-data", "unit", "lib-stub"))
777781
# alt_lib_path is used by some tests to bypass the normal lib_path mechanics.
778782
# If we don't have one, grab directories of source files.
@@ -829,6 +833,7 @@ def compute_search_paths(
829833
return SearchPaths(
830834
python_path=tuple(reversed(python_path)),
831835
mypy_path=tuple(mypypath),
836+
# package_path and typeshed_path must be normalised and absolute via os.path.abspath
832837
package_path=tuple(sys_path + site_packages),
833838
typeshed_path=tuple(lib_path),
834839
)

mypy/util.py

+20-4
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import io
77
import json
88
import os
9-
import pathlib
109
import re
1110
import shutil
1211
import sys
@@ -419,9 +418,26 @@ def replace_object_state(
419418
pass
420419

421420

422-
def is_sub_path(path1: str, path2: str) -> bool:
423-
"""Given two paths, return if path1 is a sub-path of path2."""
424-
return pathlib.Path(path2) in pathlib.Path(path1).parents
421+
def is_sub_path_normabs(path: str, dir: str) -> bool:
422+
"""Given two paths, return if path is a sub-path of dir.
423+
424+
Moral equivalent of: Path(dir) in Path(path).parents
425+
426+
Similar to the pathlib version:
427+
- Treats paths case-sensitively
428+
- Does not fully handle unnormalised paths (e.g. paths with "..")
429+
- Does not handle a mix of absolute and relative paths
430+
Unlike the pathlib version:
431+
- Fast
432+
- On Windows, assumes input has been slash normalised
433+
- Handles even fewer unnormalised paths (e.g. paths with "." and "//")
434+
435+
As a result, callers should ensure that inputs have had os.path.abspath called on them
436+
(note that os.path.abspath will normalise)
437+
"""
438+
if not dir.endswith(os.sep):
439+
dir += os.sep
440+
return path.startswith(dir)
425441

426442

427443
if sys.platform == "linux" or sys.platform == "darwin":

0 commit comments

Comments
 (0)