Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions doc/whatsnew/fragments/10559.new_check
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Add new checks for invalid uses of class patterns in ``match``.
* ``invalid-match-args-definition`` is emitted if ``__match_args__`` isn't a tuple of strings.
* ``too-many-positional-sub-patterns`` if there are more positional sub-patterns than specified in ``__match_args__``.
* ``multiple-class-sub-patterns`` if there are multiple sub-patterns for the same attribute.
Add new checks for invalid uses of class patterns in :keyword:`match`.
* :ref:`invalid-match-args-definition` is emitted if :py:data:`object.__match_args__` isn't a tuple of strings.
* :ref:`too-many-positional-sub-patterns` if there are more positional sub-patterns than specified in :py:data:`object.__match_args__`.
* :ref:`multiple-class-sub-patterns` if there are multiple sub-patterns for the same attribute.
Comment on lines +1 to +4
Copy link
Member Author

Choose a reason for hiding this comment

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

Ref #10559

Copy link
Member

Choose a reason for hiding this comment

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

Did you see #10568 ?

Copy link
Member Author

@cdce8p cdce8p Sep 22, 2025

Choose a reason for hiding this comment

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

Yes. It looks quite promising though I only skimmed through it so far.

I only modified these here since I added the new checks just before adding the first refs.


Refs #10559
2 changes: 1 addition & 1 deletion pylint/checkers/base/basic_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -717,7 +717,7 @@ def visit_assert(self, node: nodes.Assert) -> None:
match node.test:
case nodes.Tuple(elts=elts) if len(elts) > 0:
self.add_message("assert-on-tuple", node=node, confidence=HIGH)
case nodes.Const(value=str(val)):
case nodes.Const(value=str() as val):
Copy link
Member Author

Choose a reason for hiding this comment

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

I do plan to add a checker for these soon.

when = "never" if val else "always"
self.add_message("assert-on-string-literal", node=node, args=(when,))

Expand Down
2 changes: 1 addition & 1 deletion pylint/checkers/base/comparison_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ def _check_nan_comparison(
def _is_float_nan(node: nodes.NodeNG) -> bool:
try:
match node:
case nodes.Call(args=[nodes.Const(value=str(value))]) if (
case nodes.Call(args=[nodes.Const(value=str() as value)]) if (
value.lower() == "nan"
):
return node.inferred()[0].pytype() == "builtins.float" # type: ignore[no-any-return]
Expand Down
1 change: 1 addition & 0 deletions pylint/checkers/base/name_checker/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from typing import TYPE_CHECKING

import astroid
import astroid.bases
Comment on lines 19 to +20
Copy link
Member Author

Choose a reason for hiding this comment

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

astroid.bases is referenced in this file. It doesn't fail currently likely because other files also import it. Just add it here as well for good measure.

from astroid import nodes
from astroid.typing import InferenceResult

Expand Down
3 changes: 2 additions & 1 deletion pylint/checkers/classes/class_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from typing import TYPE_CHECKING, Any, NamedTuple, TypeAlias

import astroid
import astroid.objects
Copy link
Member Author

Choose a reason for hiding this comment

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

Same here.

from astroid import bases, nodes, util
from astroid.nodes import LocalsDictNodeNG
from astroid.typing import SuccessfulInferenceResult
Expand Down Expand Up @@ -1641,7 +1642,7 @@ def _check_slots_elt(
match inferred:
case util.UninferableBase():
continue
case nodes.Const(value=str(value)) if value:
case nodes.Const(value=str() as value) if value:
pass
case _:
self.add_message(
Expand Down
4 changes: 2 additions & 2 deletions pylint/checkers/refactoring/refactoring_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from typing import TYPE_CHECKING, Any, NamedTuple, TypeAlias, cast

import astroid
import astroid.objects
from astroid import bases, nodes
from astroid.util import UninferableBase

Expand Down Expand Up @@ -1125,7 +1126,6 @@ def _check_consider_using_generator(self, node: nodes.Call) -> None:
inside_comp = f"({inside_comp})"
inside_comp += ", "
inside_comp += ", ".join(kw.as_string() for kw in node.keywords)
call_name = node.func.name
if call_name in {"any", "all"}:
self.add_message(
"use-a-generator",
Expand Down Expand Up @@ -2043,7 +2043,7 @@ def _is_node_return_ended(self, node: nodes.NodeNG) -> bool:
return any(
self._is_node_return_ended(_child) for _child in all_but_handler
) and all(self._is_node_return_ended(_child) for _child in handlers)
case nodes.Assert(test=nodes.Const(value=value)) if not value:
case nodes.Assert(test=nodes.Const(value=False | 0)):
# consider assert False as a return node
return True
Comment on lines -2046 to 2048
Copy link
Member Author

Choose a reason for hiding this comment

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

Guess it's not enough to handle just assert False. Pytest also uses assert 0. Technically assert None or assert "" and similar could also be used, though I don't think they are common enough.

# recurses on the children of the node
Expand Down
5 changes: 2 additions & 3 deletions pylint/checkers/typecheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -1755,10 +1755,9 @@ def _check_invalid_sequence_index(self, subscript: nodes.Subscript) -> None:
if index_type is None or isinstance(index_type, util.UninferableBase):
return None
match index_type:
case nodes.Const():
case nodes.Const(value=int()):
# Constants must be of type int
if isinstance(index_type.value, int):
return None
return None
case astroid.Instance():
# Instance values must be int, slice, or have an __index__ method
if index_type.pytype() in {"builtins.int", "builtins.slice"}:
Expand Down
3 changes: 1 addition & 2 deletions pylint/checkers/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1704,8 +1704,7 @@ def is_test_condition(
parent: nodes.NodeNG | None = None,
) -> bool:
"""Returns true if the given node is being tested for truthiness."""
parent = parent or node.parent
match parent:
match parent := parent or node.parent:
case nodes.While() | nodes.If() | nodes.IfExp() | nodes.Assert():
return node is parent.test or parent.test.parent_of(node)
case nodes.Comprehension():
Expand Down
4 changes: 2 additions & 2 deletions pylint/checkers/variables.py
Original file line number Diff line number Diff line change
Expand Up @@ -1038,7 +1038,7 @@ def _check_loop_finishes_via_except(
"""
if not other_node_try_except.orelse:
return False
closest_loop: None | (nodes.For | nodes.While) = (
closest_loop: nodes.For | nodes.While | None = (
utils.get_node_first_ancestor_of_type(node, (nodes.For, nodes.While))
)
if closest_loop is None:
Expand Down Expand Up @@ -3034,7 +3034,7 @@ def _store_type_annotation_node(self, type_annotation: nodes.NodeNG) -> None:
return

match type_annotation.value:
case nodes.Attribute(expr=nodes.Name(name=n)) if n == TYPING_MODULE:
case nodes.Attribute(expr=nodes.Name(name=name)) if name == TYPING_MODULE:
self._type_annotation_names.append(TYPING_MODULE)
return

Expand Down
4 changes: 1 addition & 3 deletions pylint/extensions/_check_docs_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,7 @@ def get_setters_property_name(node: nodes.FunctionDef) -> str | None:
decorators = node.decorators.nodes if node.decorators else []
for decorator in decorators:
match decorator:
case nodes.Attribute(attrname=attrname, expr=nodes.Name(name=name)) if (
attrname == "setter"
):
case nodes.Attribute(attrname="setter", expr=nodes.Name(name=name)):
return name # type: ignore[no-any-return]
return None

Expand Down
5 changes: 2 additions & 3 deletions pylint/extensions/no_self_use.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,8 @@ def leave_functiondef(self, node: nodes.FunctionDef) -> None:

def _has_bare_super_call(fundef_node: nodes.FunctionDef) -> bool:
for call in fundef_node.nodes_of_class(nodes.Call):
func = call.func
match func:
case nodes.Name(name="super") if not call.args:
match call:
case nodes.Call(func=nodes.Name(name="super"), args=[]):
return True
return False

Expand Down
6 changes: 3 additions & 3 deletions pylint/extensions/private_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,9 +196,9 @@ def _populate_type_annotations_annotation(
or a Subscript e.g. `Optional[type]` or an Attribute, e.g. `pylint.lint.linter`.
"""
match node:
case nodes.Name() if node.name not in all_used_type_annotations:
all_used_type_annotations[node.name] = True
return node.name # type: ignore[no-any-return]
case nodes.Name(name=name) if name not in all_used_type_annotations:
all_used_type_annotations[name] = True
return name # type: ignore[no-any-return]
case nodes.Subscript(): # e.g. Optional[List[str]]
# slice is the next nested type
self._populate_type_annotations_annotation(
Expand Down
4 changes: 2 additions & 2 deletions pylint/extensions/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from __future__ import annotations

from typing import TYPE_CHECKING, NamedTuple
from typing import TYPE_CHECKING, NamedTuple, TypeGuard

import astroid.bases
from astroid import nodes
Expand Down Expand Up @@ -299,7 +299,7 @@ def visit_subscript(self, node: nodes.Subscript) -> None:
@staticmethod
def _is_deprecated_union_annotation(
annotation: nodes.NodeNG, union_name: str
) -> bool:
) -> TypeGuard[nodes.Subscript]:
match annotation:
case nodes.Subscript(value=nodes.Name(name=name)):
return name == union_name # type: ignore[no-any-return]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def func_a():
if a6 is None:
...

# Previous unrelate note should not match
# Previous unrelated note should not match
print("")
if a7:
...
Expand Down
Loading