Skip to content

Commit

Permalink
Support MatchStar naming (#2892)
Browse files Browse the repository at this point in the history
  • Loading branch information
sobolevn authored Mar 25, 2024
1 parent 4b45d31 commit 3cf1b69
Show file tree
Hide file tree
Showing 6 changed files with 34 additions and 5 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ Semantic versioning in our case means:
- Fixes `DuplicateExceptionViolation` to work with `TryStar`
- Fixes `TryExceptMultipleReturnPathViolation` to work with `TryStar`
- Fixes `IncorrectExceptOrderViolation` to work with `TryStar`
- Fixes that `MatchStar` was not checked in pattern matching name assignments

### Misc

Expand Down
7 changes: 7 additions & 0 deletions tests/test_visitors/test_ast/test_naming/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,12 @@ def container():
...
"""

match_star = """
match some_value:
case [*{0}]:
...
"""

# This is the only case where we don't allow unused variables.
match_as_explicit = """
match some_value:
Expand Down Expand Up @@ -246,6 +252,7 @@ def container():
match_variable,
match_as_explicit,
match_inner,
match_star,
}

_FOREIGN_NAMING_PATTERNS = frozenset((
Expand Down
6 changes: 6 additions & 0 deletions wemake_python_styleguide/compat/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
if sys.version_info >= (3, 10): # pragma: py-lt-310
from ast import Match as Match
from ast import MatchAs as MatchAs
from ast import MatchStar as MatchStar
from ast import match_case as match_case
else: # pragma: py-gte-310
class Match(ast.stmt):
Expand All @@ -26,6 +27,11 @@ class MatchAs(ast.AST):
name: Optional[str] # noqa: WPS110
pattern: Optional[ast.AST]

class MatchStar(ast.AST):
"""Used to declare `[*rest]` and `{**rest}` patterns."""

name: Optional[str]

if sys.version_info >= (3, 11): # pragma: py-lt-311
from ast import TryStar as TryStar
else: # pragma: py-gte-311
Expand Down
5 changes: 4 additions & 1 deletion wemake_python_styleguide/compat/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@

from typing_extensions import TypeAlias

from wemake_python_styleguide.compat.nodes import TryStar
from wemake_python_styleguide.compat.nodes import MatchAs, MatchStar, TryStar

#: When used with `visit_Try` and visit_TryStar`.
AnyTry: TypeAlias = Union[ast.Try, TryStar]

#: Used when named matches are needed.
NamedMatch: TypeAlias = Union[MatchAs, MatchStar]
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 @@ -1655,7 +1655,7 @@ class BlockAndLocalOverlapViolation(ASTViolation):
2. Functions and async functions definitions
3. Classes, methods, and async methods definitions
4. For and async for loops variables
5. Except for block exception aliases
5. Except block exception aliases
We allow local variables to overlap themselves,
we forbid block variables to overlap themselves.
Expand Down
18 changes: 15 additions & 3 deletions wemake_python_styleguide/visitors/ast/naming/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from typing_extensions import final

from wemake_python_styleguide.compat.functions import get_assign_targets
from wemake_python_styleguide.compat.nodes import MatchAs
from wemake_python_styleguide.compat.types import NamedMatch
from wemake_python_styleguide.constants import (
SPECIAL_ARGUMENT_NAMES_WHITELIST,
UNREADABLE_CHARACTER_COMBINATIONS,
Expand Down Expand Up @@ -231,6 +231,10 @@ def _ensure_case(self, node: AnyAssign, name: str) -> None:
'visit_AsyncFunctionDef',
'visit_Lambda',
))
@alias('visit_named_match', (
'visit_MatchStar',
'visit_MatchAs',
))
class WrongNameVisitor(BaseNodeVisitor):
"""Performs checks based on variable names."""

Expand Down Expand Up @@ -281,8 +285,16 @@ def visit_ClassDef(self, node: ast.ClassDef) -> None:
self._class_based_validator.check_attribute_names(node)
self.generic_visit(node)

def visit_MatchAs(self, node: MatchAs) -> None: # pragma: py-lt-310
"""Check pattern matching in a form of `case ... as NAME`."""
def visit_named_match(self, node: NamedMatch) -> None: # pragma: py-lt-310
"""
Check pattern matching.
In a form of
- `case ... as NAME`
- `case [*NAME]`
- `case {**NAME}`
"""
if node.name:
self._regular_validator.check_name(node, node.name)
self.generic_visit(node)
Expand Down

0 comments on commit 3cf1b69

Please sign in to comment.