From b520b553dea76bf32762190c571bdf49487530c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Wed, 26 Jan 2022 10:03:22 +0100 Subject: [PATCH] Add regression test for #5679 --- ChangeLog | 5 ++ doc/whatsnew/2.13.rst | 5 ++ .../max_inferable_limit_for_classes/main.py | 38 +++++++++ .../nodes/roles.py | 82 +++++++++++++++++++ .../other_funcs.py | 31 +++++++ tests/test_self.py | 21 +++++ 6 files changed, 182 insertions(+) create mode 100644 tests/regrtest_data/max_inferable_limit_for_classes/main.py create mode 100644 tests/regrtest_data/max_inferable_limit_for_classes/nodes/roles.py create mode 100644 tests/regrtest_data/max_inferable_limit_for_classes/other_funcs.py diff --git a/ChangeLog b/ChangeLog index 3ee22bbffe..2c4157e7d1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -203,6 +203,11 @@ Release date: TBA Closes #5557 +* Fixed a crash on ``__init__`` nodes when the attribute was previously uninferable due to a cache + limit size. This limit can be hit when the inheritance pattern of a class (and therefore of the ``__init__`` attribute) is very large. + + Closes #5679 + * Fix ``unnecessary_dict_index_lookup`` false positive when deleting a dictionary's entry. Closes #4716 diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index d96776f2b7..7a9300ac35 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -129,6 +129,11 @@ Other Changes Closes #5713 +* Fixed a crash on ``__init__`` nodes when the attribute was previously uninferable due to a cache + limit size. This limit can be hit when the inheritance pattern of a class (and therefore of the ``__init__`` attribute) is very large. + + Closes #5679 + * Fixed extremely long processing of long lines with comma's. Closes #5483 diff --git a/tests/regrtest_data/max_inferable_limit_for_classes/main.py b/tests/regrtest_data/max_inferable_limit_for_classes/main.py new file mode 100644 index 0000000000..2588d916fe --- /dev/null +++ b/tests/regrtest_data/max_inferable_limit_for_classes/main.py @@ -0,0 +1,38 @@ +"""This example is based on sqlalchemy. + +See https://github.com/PyCQA/pylint/issues/5679 +""" +from other_funcs import FromClause + +from .nodes import roles + + +class HasMemoized(object): + ... + + +class Generative(HasMemoized): + ... + + +class ColumnElement( + roles.ColumnArgumentOrKeyRole, + roles.BinaryElementRole, + roles.OrderByRole, + roles.ColumnsClauseRole, + roles.LimitOffsetRole, + roles.DMLColumnRole, + roles.DDLConstraintColumnRole, + roles.StatementRole, + Generative, +): + ... + + +class FunctionElement(ColumnElement, FromClause): + ... + + +class months_between(FunctionElement): + def __init__(self): + super().__init__() diff --git a/tests/regrtest_data/max_inferable_limit_for_classes/nodes/roles.py b/tests/regrtest_data/max_inferable_limit_for_classes/nodes/roles.py new file mode 100644 index 0000000000..2f58f1b508 --- /dev/null +++ b/tests/regrtest_data/max_inferable_limit_for_classes/nodes/roles.py @@ -0,0 +1,82 @@ +class SQLRole(object): + ... + + +class UsesInspection(object): + ... + + +class AllowsLambdaRole(object): + ... + + +class ColumnArgumentRole(SQLRole): + ... + + +class ColumnArgumentOrKeyRole(ColumnArgumentRole): + ... + + +class ColumnListRole(SQLRole): + ... + + +class ColumnsClauseRole(AllowsLambdaRole, UsesInspection, ColumnListRole): + ... + + +class LimitOffsetRole(SQLRole): + ... + + +class ByOfRole(ColumnListRole): + ... + + +class OrderByRole(AllowsLambdaRole, ByOfRole): + ... + + +class StructuralRole(SQLRole): + ... + + +class ExpressionElementRole(SQLRole): + ... + + +class BinaryElementRole(ExpressionElementRole): + ... + + +class JoinTargetRole(AllowsLambdaRole, UsesInspection, StructuralRole): + ... + + +class FromClauseRole(ColumnsClauseRole, JoinTargetRole): + ... + + +class StrictFromClauseRole(FromClauseRole): + ... + + +class AnonymizedFromClauseRole(StrictFromClauseRole): + ... + + +class ReturnsRowsRole(SQLRole): + ... + + +class StatementRole(SQLRole): + ... + + +class DMLColumnRole(SQLRole): + ... + + +class DDLConstraintColumnRole(SQLRole): + ... diff --git a/tests/regrtest_data/max_inferable_limit_for_classes/other_funcs.py b/tests/regrtest_data/max_inferable_limit_for_classes/other_funcs.py new file mode 100644 index 0000000000..f737fbf5a8 --- /dev/null +++ b/tests/regrtest_data/max_inferable_limit_for_classes/other_funcs.py @@ -0,0 +1,31 @@ +from operator import attrgetter + +from .nodes import roles + + +class HasCacheKey(object): + ... + + +class HasMemoized(object): + ... + + +class MemoizedHasCacheKey(HasCacheKey, HasMemoized): + ... + + +class ClauseElement(MemoizedHasCacheKey): + ... + + +class ReturnsRows(roles.ReturnsRowsRole, ClauseElement): + ... + + +class Selectable(ReturnsRows): + ... + + +class FromClause(roles.AnonymizedFromClauseRole, Selectable): + c = property(attrgetter("columns")) diff --git a/tests/test_self.py b/tests/test_self.py index 444a5076b9..c1c929e51d 100644 --- a/tests/test_self.py +++ b/tests/test_self.py @@ -1290,3 +1290,24 @@ def test_regex_paths_csv_validator() -> None: with pytest.raises(SystemExit) as ex: Run(["--ignore-paths", "test", join(HERE, "regrtest_data", "empty.py")]) assert ex.value.code == 0 + + @staticmethod + def test_max_inferred_for_complicated_class_hierarchy() -> None: + """Regression test for a crash reported in https://github.com/PyCQA/pylint/issues/5679. + + The class hierarchy of 'sqlalchemy' is so intricate that it becomes uninferable with + the standard max_inferred of 100. We used to crash when this happened. + """ + with pytest.raises(SystemExit) as ex: + Run( + [ + join( + HERE, + "regrtest_data", + "max_inferable_limit_for_classes", + "main.py", + ), + ] + ) + # Error code should not include bit-value 1 for crash + assert not ex.value.code % 2