Skip to content

Commit

Permalink
Fix pattern matching and block overlaps (#2893)
Browse files Browse the repository at this point in the history
  • Loading branch information
sobolevn authored Mar 25, 2024
1 parent 3cf1b69 commit 884be48
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 5 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ Semantic versioning in our case means:
- Fixes `TryExceptMultipleReturnPathViolation` to work with `TryStar`
- Fixes `IncorrectExceptOrderViolation` to work with `TryStar`
- Fixes that `MatchStar` was not checked in pattern matching name assignments
- Fixes pattern matching support
in `BlockAndLocalOverlapViolation` and `OuterScopeShadowingViolation`

### Misc

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import pytest

from wemake_python_styleguide.compat.constants import PY310
from wemake_python_styleguide.violations.best_practices import (
OuterScopeShadowingViolation,
)
Expand Down Expand Up @@ -254,6 +255,22 @@ def function():
...
"""

match_as_overlap = """
import some
def function():
match ...:
case 1 as some: ...
"""

match_star_overlap = """
import some
def function():
match ...:
case [*some]: ...
"""


@pytest.mark.parametrize('code', [
correct_for_loop1,
Expand Down Expand Up @@ -300,6 +317,20 @@ def test_variable_used_correctly(
constant_overlap5,
constant_overlap6,
walrus_overlap,
pytest.param(
match_as_overlap,
marks=pytest.mark.skipif(
not PY310,
reason='Pattern matching was added in Python 3.10',
),
),
pytest.param(
match_star_overlap,
marks=pytest.mark.skipif(
not PY310,
reason='Pattern matching was added in Python 3.10',
),
),
])
def test_outer_variable_shadow(
assert_errors,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import pytest

from wemake_python_styleguide.compat.constants import PY310
from wemake_python_styleguide.violations.best_practices import (
BlockAndLocalOverlapViolation,
)
Expand Down Expand Up @@ -74,6 +75,27 @@ def context():
...
"""

import_and_match_as = """
import overlap
match ...:
case 1 as overlap: ...
"""

import_and_match_as_implicit = """
import overlap
match ...:
case overlap: ...
"""

import_and_match_star = """
import overlap
match ...:
case [1, *overlap]: ...
"""

# Correct:

unused_variables_overlap1 = """
Expand Down Expand Up @@ -121,6 +143,27 @@ def context():
loop_and_loop1,
loop_and_loop2,
import_and_walrus,
pytest.param(
import_and_match_as,
marks=pytest.mark.skipif(
not PY310,
reason='Pattern matching was added in Python 3.10',
),
),
pytest.param(
import_and_match_as_implicit,
marks=pytest.mark.skipif(
not PY310,
reason='Pattern matching was added in Python 3.10',
),
),
pytest.param(
import_and_match_star,
marks=pytest.mark.skipif(
not PY310,
reason='Pattern matching was added in Python 3.10',
),
),
])
def test_block_overlap(
assert_errors,
Expand Down
2 changes: 1 addition & 1 deletion wemake_python_styleguide/violations/best_practices.py
Original file line number Diff line number Diff line change
Expand Up @@ -1738,7 +1738,7 @@ class OuterScopeShadowingViolation(ASTViolation):
Solution:
Use different names and do not allow scoping.
Use different names.
Example::
Expand Down
16 changes: 12 additions & 4 deletions wemake_python_styleguide/visitors/ast/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from typing_extensions import TypeAlias, final

from wemake_python_styleguide.compat.aliases import ForNodes, WithNodes
from wemake_python_styleguide.compat.types import NamedMatch
from wemake_python_styleguide.logic.naming.name_nodes import flat_variable_names
from wemake_python_styleguide.logic.nodes import get_context, get_parent
from wemake_python_styleguide.logic.scopes import defs, predicates
Expand Down Expand Up @@ -33,13 +34,23 @@
_ScopePredicate: TypeAlias = Callable[[ast.AST, Set[str]], bool]
_NamePredicate: TypeAlias = Callable[[ast.AST], bool]

#: Named nodes.
_NamedNode: TypeAlias = Union[
AnyFunctionDef,
ast.ClassDef,
ast.ExceptHandler,
NamedMatch,
]


@final
@decorators.alias('visit_named_nodes', (
'visit_FunctionDef',
'visit_AsyncFunctionDef',
'visit_ClassDef',
'visit_ExceptHandler',
'visit_MatchAs',
'visit_MatchStar',
))
@decorators.alias('visit_any_for', (
'visit_For',
Expand Down Expand Up @@ -83,10 +94,7 @@ class BlockVariableVisitor(base.BaseNodeVisitor):

# Blocks:

def visit_named_nodes(
self,
node: Union[AnyFunctionDef, ast.ClassDef, ast.ExceptHandler],
) -> None:
def visit_named_nodes(self, node: _NamedNode) -> None:
"""Visits block nodes that have ``.name`` property."""
names = {node.name} if node.name else set()
self._scope(node, names, is_local=False)
Expand Down

0 comments on commit 884be48

Please sign in to comment.