Repository: wemake-services/wemake-python-styleguide

Issue:
Forbid assigning to a slice
# Rule request

<!-- Thanks for coming up with a proposal! -->

## Thesis

<!-- What exactly do you propose? Please, provide code examples. -->

There might be some crazy examples of mutation, when assigning to a slice:

```python
my_list = [1, 2, 3]
my_list[0:3] = [0, 0, 0]
```

So, let's ban this.

However, we still allow regular index assignments:

```python
my_list = [1, 2, 3]
my_list[0] = [0, 0, 0]
```

Is ok.



Discussion:
hey, can i take this issue ? 

Yes, thanks a lot!
@parzuko are you working on this? if not would like to take this up.
@sudo-k-runner feel free to take it 🙂 
@sobolevn cool! kinda new to this, so would be great if I can get some pointers. Thanks!
I recommed to start with our `CONTRIBUTING.md` and then ask any specific questions you have 👍 
sure thing! Thanks 👍 

Resolving patch:
diff --git a/wemake_python_styleguide/violations/consistency.py b/wemake_python_styleguide/violations/consistency.py
--- a/wemake_python_styleguide/violations/consistency.py
+++ b/wemake_python_styleguide/violations/consistency.py
@@ -85,6 +85,7 @@
    UnpackingIterableToListViolation
    RawStringNotNeededViolation
    InconsistentComprehensionViolation
+   AssignToSliceViolation
 
 Consistency checks
 ------------------
@@ -151,6 +152,7 @@
 .. autoclass:: UnpackingIterableToListViolation
 .. autoclass:: RawStringNotNeededViolation
 .. autoclass:: InconsistentComprehensionViolation
+.. autoclass:: AssignToSliceViolation
 
 """
 
@@ -2297,7 +2299,47 @@ class InconsistentComprehensionViolation(TokenizeViolation):
         ]
 
     .. versionadded:: 0.15.0
+
     """
 
     error_template = 'Found an inconsistently structured comprehension'
     code = 361
+
+
+@final
+class AssignToSliceViolation(ASTViolation):
+    """
+    Forbid assignment to a subscript slice.
+
+    Reasoning:
+        Assingment to a slice may lead to a list changing its size
+        implicitly and strangely which makes it hard to spot bugs.
+
+    Solution:
+        Use explicit index assignment in place of slice assignment.
+
+    Why you may disable or inline-ignore this rule?
+
+    The quite common and useful example which violates this rule
+    is inplace list replacement via ``[:]`` - this helps
+    to keep the same object reference while it content could be completely
+    erased or replaced with the new one.
+
+    One more thing: slice assignment is the only way
+    for inplace array multiple replacement when you need that.
+
+    Example::
+
+        # Correct:
+        a[5] = 1
+
+        # Wrong:
+        a[1:3] = [1, 2]
+        a[slice(1)] = [1, 3]
+
+    .. versionadded:: 0.15.0
+
+    """
+
+    error_template = 'Found assignment to a subscript slice'
+    code = 362
diff --git a/wemake_python_styleguide/visitors/ast/subscripts.py b/wemake_python_styleguide/visitors/ast/subscripts.py
--- a/wemake_python_styleguide/visitors/ast/subscripts.py
+++ b/wemake_python_styleguide/visitors/ast/subscripts.py
@@ -23,9 +23,11 @@ def visit_Subscript(self, node: ast.Subscript) -> None:
 
         Raises:
             RedundantSubscriptViolation
+            AssignToSliceViolation
 
         """
         self._check_redundant_subscript(node)
+        self._check_slice_assignment(node)
         self.generic_visit(node)
 
     def _check_redundant_subscript(self, node: ast.Subscript) -> None:
@@ -58,6 +60,23 @@ def _check_redundant_subscript(self, node: ast.Subscript) -> None:
                 ),
             )
 
+    def _check_slice_assignment(self, node: ast.Subscript) -> None:
+        if not isinstance(node.ctx, ast.Store):
+            return
+
+        subscript_slice_assignment = isinstance(node.slice, ast.Slice)
+
+        slice_expr = get_slice_expr(node)
+        slice_function_assignment = (
+            isinstance(slice_expr, ast.Call) and
+            functions.given_function_called(slice_expr, {'slice'})
+        )
+
+        if subscript_slice_assignment or slice_function_assignment:
+            self.add_violation(
+                consistency.AssignToSliceViolation(node),
+            )
+
     def _is_none(self, component_value: ast.expr) -> bool:
         return (
             isinstance(component_value, ast.NameConstant) and


Test patch:
diff --git a/tests/fixtures/noqa/noqa.py b/tests/fixtures/noqa/noqa.py
--- a/tests/fixtures/noqa/noqa.py
+++ b/tests/fixtures/noqa/noqa.py
@@ -727,6 +727,10 @@ def set_attribute(self):  # noqa: WPS615
         anti_wps = ...
 
 
+a_list = [1, 2, 3, 4, 5]
+a_list[1:3] = [1, 2]  # noqa: WPS362
+a_list[slice(1)] = [1, 2]  # noqa: WPS362
+
 for element in range(10):
     try:  # noqa: WPS452
         my_print(1)
diff --git a/tests/test_checker/test_noqa.py b/tests/test_checker/test_noqa.py
--- a/tests/test_checker/test_noqa.py
+++ b/tests/test_checker/test_noqa.py
@@ -172,6 +172,7 @@
     'WPS359': 1,
     'WPS360': 1,
     'WPS361': 1,
+    'WPS362': 2,
 
     'WPS400': 0,  # defined in ignored violations.
     'WPS401': 0,  # logically unacceptable.
diff --git a/tests/test_visitors/test_ast/test_subscripts/test_slice_assignment.py b/tests/test_visitors/test_ast/test_subscripts/test_slice_assignment.py
new file mode 100644
--- /dev/null
+++ b/tests/test_visitors/test_ast/test_subscripts/test_slice_assignment.py
@@ -0,0 +1,58 @@
+import pytest
+
+from wemake_python_styleguide.violations.consistency import (
+    AssignToSliceViolation,
+)
+from wemake_python_styleguide.visitors.ast.subscripts import SubscriptVisitor
+
+usage_template = 'constant[{0}] = [1, 2, 3]'
+
+
+@pytest.mark.parametrize('expression', [
+    ':7',
+    '1:7:2',
+    '3:',
+    '3::',
+    '::2',
+    ':2:',
+    ':',
+    'slice(1)',
+    'slice()',
+    'slice(1,3)',
+])
+def test_slice_assignment(
+    assert_errors,
+    parse_ast_tree,
+    expression,
+    default_options,
+):
+    """Testing that slice assignments are forbidden."""
+    tree = parse_ast_tree(usage_template.format(expression))
+
+    visitor = SubscriptVisitor(default_options, tree=tree)
+    visitor.run()
+
+    assert_errors(visitor, [AssignToSliceViolation])
+
+
+@pytest.mark.parametrize('expression', [
+    '5',
+    '"string"',
+    'object',
+    'dict[key]',
+    'subslice[1:2]',
+    'func(1, 2)',
+])
+def test_regular_index_assignment(
+    assert_errors,
+    parse_ast_tree,
+    expression,
+    default_options,
+):
+    """Testing that regular index assignment is allowed."""
+    tree = parse_ast_tree(usage_template.format(expression))
+
+    visitor = SubscriptVisitor(default_options, tree=tree)
+    visitor.run()
+
+    assert_errors(visitor, [])