diff --git a/doc/requirements.txt b/doc/requirements.txt index 4d20cfbb48..289105e5c1 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -3,3 +3,4 @@ sphinx-reredirects<1 towncrier~=23.6 furo==2023.5.20 -e . +astroid @ git+https://github.com/pylint-dev/astroid.git@cf8763a2b8e897ec7c8389906f3cb13714300cd2 diff --git a/doc/whatsnew/fragments/529.performance b/doc/whatsnew/fragments/529.performance new file mode 100644 index 0000000000..69f4a4f1d8 --- /dev/null +++ b/doc/whatsnew/fragments/529.performance @@ -0,0 +1,4 @@ +``pylint`` runs (at least) ~5% faster after improvements to ``astroid`` +that make better use of the inference cache. + +Refs pylint-dev/astroid#529 diff --git a/pylint/checkers/design_analysis.py b/pylint/checkers/design_analysis.py index bea1ef53cc..f8c1d5c6c9 100644 --- a/pylint/checkers/design_analysis.py +++ b/pylint/checkers/design_analysis.py @@ -440,11 +440,20 @@ def visit_classdef(self, node: nodes.ClassDef) -> None: args=(nb_parents, self.linter.config.max_parents), ) - if len(node.instance_attrs) > self.linter.config.max_attributes: + # Something at inference time is modifying instance_attrs to add + # properties from parent classes. Given how much we cache inference + # results, mutating instance_attrs can become a real mess. Filter + # them out here until the root cause is solved. + # https://github.com/pylint-dev/astroid/issues/2273 + root = node.root() + filtered_attrs = [ + k for (k, v) in node.instance_attrs.items() if v[0].root() is root + ] + if len(filtered_attrs) > self.linter.config.max_attributes: self.add_message( "too-many-instance-attributes", node=node, - args=(len(node.instance_attrs), self.linter.config.max_attributes), + args=(len(filtered_attrs), self.linter.config.max_attributes), ) @only_required_for_messages("too-few-public-methods", "too-many-public-methods") diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index f95bf4c2ed..aff1ceceac 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -12,11 +12,10 @@ import re import shlex import sys -import types -from collections.abc import Callable, Iterable, Iterator, Sequence +from collections.abc import Callable, Iterable from functools import cached_property, singledispatch from re import Pattern -from typing import TYPE_CHECKING, Any, Literal, TypeVar, Union +from typing import TYPE_CHECKING, Any, Literal, Union import astroid import astroid.exceptions @@ -64,8 +63,6 @@ nodes.ClassDef, ] -_T = TypeVar("_T") - STR_FORMAT = {"builtins.str.format"} ASYNCIO_COROUTINE = "asyncio.coroutines.coroutine" BUILTIN_TUPLE = "builtins.tuple" @@ -105,24 +102,6 @@ class VERSION_COMPATIBLE_OVERLOAD: VERSION_COMPATIBLE_OVERLOAD_SENTINEL = VERSION_COMPATIBLE_OVERLOAD() -def _unflatten(iterable: Iterable[_T]) -> Iterator[_T]: - for index, elem in enumerate(iterable): - if isinstance(elem, Sequence) and not isinstance(elem, str): - yield from _unflatten(elem) - elif elem and not index: - # We're interested only in the first element. - yield elem # type: ignore[misc] - - -def _flatten_container(iterable: Iterable[_T]) -> Iterator[_T]: - # Flatten nested containers into a single iterable - for item in iterable: - if isinstance(item, (list, tuple, types.GeneratorType)): - yield from _flatten_container(item) - else: - yield item - - def _is_owner_ignored( owner: SuccessfulInferenceResult, attrname: str | None, @@ -1899,16 +1878,13 @@ def visit_with(self, node: nodes.With) -> None: # Retrieve node from all previously visited nodes in the # inference history - context_path_names: Iterator[Any] = filter( - None, _unflatten(context.path) - ) - inferred_paths = _flatten_container( - safe_infer(path) for path in context_path_names - ) - for inferred_path in inferred_paths: + for inferred_path, _ in context.path: if not inferred_path: continue - scope = inferred_path.scope() + if isinstance(inferred_path, nodes.Call): + scope = safe_infer(inferred_path.func) + else: + scope = inferred_path.scope() if not isinstance(scope, nodes.FunctionDef): continue if decorated_with( diff --git a/pyproject.toml b/pyproject.toml index f81a7933ed..dae9cfed5b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ dependencies = [ # Also upgrade requirements_test_min.txt. # Pinned to dev of second minor update to allow editable installs and fix primer issues, # see https://github.com/pylint-dev/astroid/issues/1341 - "astroid>=3.0.0a8,<=3.1.0-dev0", + "astroid @ git+https://github.com/pylint-dev/astroid.git@cf8763a2b8e897ec7c8389906f3cb13714300cd2", "isort>=4.2.5,<6", "mccabe>=0.6,<0.8", "tomli>=1.1.0;python_version<'3.11'", diff --git a/requirements_test_min.txt b/requirements_test_min.txt index 7df78810f2..b2b928f98d 100644 --- a/requirements_test_min.txt +++ b/requirements_test_min.txt @@ -1,6 +1,6 @@ -e .[testutils,spelling] # astroid dependency is also defined in pyproject.toml -astroid==3.0.0a8 # Pinned to a specific version for tests +astroid @ git+https://github.com/pylint-dev/astroid.git@cf8763a2b8e897ec7c8389906f3cb13714300cd2 typing-extensions~=4.7 py~=1.11.0 pytest~=7.4