Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release branch 2.15.3 #7477

2 changes: 1 addition & 1 deletion .github/workflows/changelog.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:

env:
# Also change CACHE_VERSION in the other workflows
CACHE_VERSION: 26
CACHE_VERSION: 27
DEFAULT_PYTHON: "3.10"

jobs:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/checks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:
pull_request: ~

env:
CACHE_VERSION: 26
CACHE_VERSION: 27
DEFAULT_PYTHON: "3.10"
PRE_COMMIT_CACHE: ~/.cache/pre-commit

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/primer-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ on:
- ".github/workflows/primer-test.yaml"

env:
CACHE_VERSION: 26
CACHE_VERSION: 27

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/primer_comment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ on:

env:
# This needs to be the SAME as in the Main and PR job
CACHE_VERSION: 26
CACHE_VERSION: 27

permissions:
contents: read
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/primer_run_main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ concurrency:

env:
# This needs to be the SAME as in the PR and comment job
CACHE_VERSION: 26
CACHE_VERSION: 27

jobs:
run-primer:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/primer_run_pr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ concurrency:

env:
# This needs to be the SAME as in the Main and comment job
CACHE_VERSION: 26
CACHE_VERSION: 27

jobs:
run-primer:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ on:
- doc/data/messages/**

env:
CACHE_VERSION: 26
CACHE_VERSION: 27

jobs:
tests-linux:
Expand Down
24 changes: 24 additions & 0 deletions doc/whatsnew/2/2.15/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,30 @@ Marc Byrne became a maintainer, welcome to the team !

.. towncrier release notes start

What's new in Pylint 2.15.3?
----------------------------
Release date: 2022-09-19


- Fixed a crash in the ``unhashable-member`` checker when using a ``lambda`` as a dict key.

Closes #7453 (`#7453 <https://github.com/PyCQA/pylint/issues/7453>`_)
- Fix a crash in the ``modified-iterating-dict`` checker involving instance attributes.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't we fix the new lines here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not on the maintenance branch :)


Closes #7461 (`#7461 <https://github.com/PyCQA/pylint/issues/7461>`_)
- ``invalid-class-object`` does not crash anymore when ``__class__`` is assigned alongside another variable.

Closes #7467 (`#7467 <https://github.com/PyCQA/pylint/issues/7467>`_)
- Fix false positive for ``global-variable-not-assigned`` when a global variable is re-assigned via an ``ImportFrom`` node.

Closes #4809 (`#4809 <https://github.com/PyCQA/pylint/issues/4809>`_)
- Fix false positive for ``undefined-loop-variable`` in ``for-else`` loops that use a function
having a return type annotation of ``NoReturn`` or ``Never``.

Closes #7311 (`#7311 <https://github.com/PyCQA/pylint/issues/7311>`_)
- ``--help-msg`` now accepts a comma-separated list of message IDs again.

Closes #7471 (`#7471 <https://github.com/PyCQA/pylint/issues/7471>`_)

What's new in Pylint 2.15.2?
----------------------------
Expand Down
2 changes: 1 addition & 1 deletion pylint/__pkginfo__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

from __future__ import annotations

__version__ = "2.15.2"
__version__ = "2.15.3"


def get_numversion_from_version(v: str) -> tuple[int, int, int]:
Expand Down
13 changes: 12 additions & 1 deletion pylint/checkers/classes/class_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -1558,7 +1558,18 @@ def visit_assignattr(self, node: nodes.AssignAttr) -> None:
def _check_invalid_class_object(self, node: nodes.AssignAttr) -> None:
if not node.attrname == "__class__":
return
inferred = safe_infer(node.parent.value)
if isinstance(node.parent, nodes.Tuple):
class_index = -1
for i, elt in enumerate(node.parent.elts):
if hasattr(elt, "attrname") and elt.attrname == "__class__":
class_index = i
if class_index == -1:
# This should not happen because we checked that the node name
# is '__class__' earlier, but let's not be too confident here
return # pragma: no cover
inferred = safe_infer(node.parent.parent.value.elts[class_index])
else:
inferred = safe_infer(node.parent.value)
if (
isinstance(inferred, nodes.ClassDef)
or inferred is astroid.Uninferable
Expand Down
12 changes: 8 additions & 4 deletions pylint/checkers/modified_iterating_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ def _is_node_assigns_subscript_name(node: nodes.NodeNG) -> bool:
)

def _modified_iterating_list_cond(
self, node: nodes.NodeNG, iter_obj: nodes.NodeNG
self, node: nodes.NodeNG, iter_obj: nodes.Name | nodes.Attribute
) -> bool:
if not self._is_node_expr_that_calls_attribute_name(node):
return False
Expand All @@ -141,7 +141,7 @@ def _modified_iterating_list_cond(
)

def _modified_iterating_dict_cond(
self, node: nodes.NodeNG, iter_obj: nodes.NodeNG
self, node: nodes.NodeNG, iter_obj: nodes.Name | nodes.Attribute
) -> bool:
if not self._is_node_assigns_subscript_name(node):
return False
Expand All @@ -159,10 +159,14 @@ def _modified_iterating_dict_cond(
return False
if infer_val != utils.safe_infer(iter_obj):
return False
return node.targets[0].value.name == iter_obj.name
if isinstance(iter_obj, nodes.Attribute):
iter_obj_name = iter_obj.attrname
else:
iter_obj_name = iter_obj.name
return node.targets[0].value.name == iter_obj_name

def _modified_iterating_set_cond(
self, node: nodes.NodeNG, iter_obj: nodes.NodeNG
self, node: nodes.NodeNG, iter_obj: nodes.Name | nodes.Attribute
) -> bool:
if not self._is_node_expr_that_calls_attribute_name(node):
return False
Expand Down
2 changes: 2 additions & 0 deletions pylint/checkers/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1942,6 +1942,8 @@ def is_hashable(node: nodes.NodeNG) -> bool:
for inferred in node.infer():
if inferred is astroid.Uninferable:
return True
if not hasattr(inferred, "igetattr"):
return True
hash_fn = next(inferred.igetattr("__hash__"))
if hash_fn.parent is inferred:
return True
Expand Down
46 changes: 37 additions & 9 deletions pylint/checkers/variables.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,20 @@
from typing import TYPE_CHECKING, Any, NamedTuple

import astroid
from astroid import extract_node, nodes
from astroid import bases, extract_node, nodes
from astroid.typing import InferenceResult

from pylint.checkers import BaseChecker, utils
from pylint.checkers.utils import (
in_type_checking_block,
is_postponed_evaluation_enabled,
)
from pylint.constants import PY39_PLUS, TYPING_TYPE_CHECKS_GUARDS
from pylint.constants import (
PY39_PLUS,
TYPING_NEVER,
TYPING_NORETURN,
TYPING_TYPE_CHECKS_GUARDS,
)
from pylint.interfaces import CONTROL_FLOW, HIGH, INFERENCE, INFERENCE_FAILURE
from pylint.typing import MessageDefinitionTuple

Expand Down Expand Up @@ -1311,7 +1316,8 @@ def visit_global(self, node: nodes.Global) -> None:
assign_nodes = []

not_defined_locally_by_import = not any(
isinstance(local, nodes.Import) for local in locals_.get(name, ())
isinstance(local, (nodes.Import, nodes.ImportFrom))
for local in locals_.get(name, ())
)
if (
not utils.is_reassigned_after_current(node, name)
Expand Down Expand Up @@ -2244,13 +2250,35 @@ def _loopvar_name(self, node: astroid.Name) -> None:
if not isinstance(assign, nodes.For):
self.add_message("undefined-loop-variable", args=node.name, node=node)
return
if any(
isinstance(
for else_stmt in assign.orelse:
if isinstance(
else_stmt, (nodes.Return, nodes.Raise, nodes.Break, nodes.Continue)
)
for else_stmt in assign.orelse
):
return
):
return
# TODO: 2.16: Consider using RefactoringChecker._is_function_def_never_returning
if isinstance(else_stmt, nodes.Expr) and isinstance(
else_stmt.value, nodes.Call
):
inferred_func = utils.safe_infer(else_stmt.value.func)
if (
isinstance(inferred_func, nodes.FunctionDef)
and inferred_func.returns
):
inferred_return = utils.safe_infer(inferred_func.returns)
if isinstance(
inferred_return, nodes.FunctionDef
) and inferred_return.qname() in {
*TYPING_NORETURN,
*TYPING_NEVER,
"typing._SpecialForm",
}:
return
# typing_extensions.NoReturn returns a _SpecialForm
if (
isinstance(inferred_return, bases.Instance)
and inferred_return.qname() == "typing._SpecialForm"
):
return

maybe_walrus = utils.get_node_first_ancestor_of_type(node, nodes.NamedExpr)
if maybe_walrus:
Expand Down
6 changes: 5 additions & 1 deletion pylint/config/callback_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,11 @@ def __call__(
option_string: str | None = "--help-msg",
) -> None:
assert isinstance(values, (list, tuple))
self.run.linter.msgs_store.help_message(values)
values_to_print: list[str] = []
for msg in values:
assert isinstance(msg, str)
values_to_print += utils._check_csv(msg)
self.run.linter.msgs_store.help_message(values_to_print)
sys.exit(0)


Expand Down
13 changes: 13 additions & 0 deletions pylint/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,3 +155,16 @@ def _get_pylint_home() -> str:


PYLINT_HOME = _get_pylint_home()

TYPING_NORETURN = frozenset(
(
"typing.NoReturn",
"typing_extensions.NoReturn",
)
)
TYPING_NEVER = frozenset(
(
"typing.Never",
"typing_extensions.Never",
)
)
7 changes: 1 addition & 6 deletions pylint/extensions/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
only_required_for_messages,
safe_infer,
)
from pylint.constants import TYPING_NORETURN
from pylint.interfaces import INFERENCE

if TYPE_CHECKING:
Expand Down Expand Up @@ -75,12 +76,6 @@ class TypingAlias(NamedTuple):

ALIAS_NAMES = frozenset(key.split(".")[1] for key in DEPRECATED_TYPING_ALIASES)
UNION_NAMES = ("Optional", "Union")
TYPING_NORETURN = frozenset(
(
"typing.NoReturn",
"typing_extensions.NoReturn",
)
)


class DeprecatedTypingAliasMsg(NamedTuple):
Expand Down
5 changes: 3 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,11 @@ requires-python = ">=3.7.2"
dependencies = [
"dill>=0.2",
"platformdirs>=2.2.0",
# Also upgrade requirements_test_min.txt if you are bumping astroid.
# Also upgrade requirements_test_min.txt and all the CACHE_VERSION in
# github actions if you are bumping astroid.
# Pinned to dev of second minor update to allow editable installs and fix primer issues,
# see https://github.com/PyCQA/astroid/issues/1341
"astroid>=2.12.9,<=2.14.0-dev0",
"astroid>=2.12.10,<=2.14.0-dev0",
"isort>=4.2.5,<6",
"mccabe>=0.6,<0.8",
"tomli>=1.1.0;python_version<'3.11'",
Expand Down
3 changes: 2 additions & 1 deletion requirements_test_min.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
-e .[testutils,spelling]
# astroid dependency is also defined in pyproject.toml
astroid==2.12.9 # Pinned to a specific version for tests
# You need to increment the CACHE_VERSION in github actions too
astroid==2.12.10 # Pinned to a specific version for tests
typing-extensions~=4.3
pytest~=7.1
pytest-benchmark~=3.4
Expand Down
2 changes: 1 addition & 1 deletion tbump.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
github_url = "https://github.com/PyCQA/pylint"

[version]
current = "2.15.2"
current = "2.15.3"
regex = '''
^(?P<major>0|[1-9]\d*)
\.
Expand Down
15 changes: 8 additions & 7 deletions tests/functional/g/globals.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,15 @@ def define_constant():


def global_with_import():
"""should only warn for global-statement"""
"""should only warn for global-statement when using `Import` node"""
global sys # [global-statement]
import sys # pylint: disable=import-outside-toplevel
import sys


def global_with_import_from():
"""should only warn for global-statement when using `ImportFrom` node"""
global namedtuple # [global-statement]
from collections import namedtuple


def global_no_assign():
Expand Down Expand Up @@ -75,11 +81,6 @@ def FUNC():

FUNC()

def func():
"""Overriding a global with an import should only throw a global statement error"""
global sys # [global-statement]

import sys

def override_class():
"""Overriding a class should only throw a global statement error"""
Expand Down
14 changes: 7 additions & 7 deletions tests/functional/g/globals.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ global-variable-not-assigned:23:4:23:14:other:Using global for 'HOP' but no assi
undefined-variable:24:10:24:13:other:Undefined variable 'HOP':UNDEFINED
global-variable-undefined:29:4:29:18:define_constant:Global variable 'SOMEVAR' undefined at the module level:UNDEFINED
global-statement:35:4:35:14:global_with_import:Using the global statement:UNDEFINED
global-variable-not-assigned:41:4:41:19:global_no_assign:Using global for 'CONSTANT' but no assignment is done:UNDEFINED
global-statement:47:4:47:19:global_del:Using the global statement:UNDEFINED
global-statement:54:4:54:19:global_operator_assign:Using the global statement:UNDEFINED
global-statement:61:4:61:19:global_function_assign:Using the global statement:UNDEFINED
global-statement:71:4:71:15:override_func:Using the global statement:UNDEFINED
global-statement:80:4:80:14:func:Using the global statement:UNDEFINED
global-statement:86:4:86:16:override_class:Using the global statement:UNDEFINED
global-statement:41:4:41:21:global_with_import_from:Using the global statement:UNDEFINED
global-variable-not-assigned:47:4:47:19:global_no_assign:Using global for 'CONSTANT' but no assignment is done:UNDEFINED
global-statement:53:4:53:19:global_del:Using the global statement:UNDEFINED
global-statement:60:4:60:19:global_operator_assign:Using the global statement:UNDEFINED
global-statement:67:4:67:19:global_function_assign:Using the global statement:UNDEFINED
global-statement:77:4:77:15:override_func:Using the global statement:UNDEFINED
global-statement:87:4:87:16:override_class:Using the global statement:UNDEFINED