Repository: wemake-services/wemake-python-styleguide

Issue:
AttributeError: 'NoneType' object has no attribute 'lineno'
# Bug report

## What's wrong
When I run flake8 with wemake-python-styleguide 0.6.2, I see exception traceback. 
Something simular to https://github.com/wemake-services/wemake-python-styleguide/issues/423

### traceback

```
multiprocessing.pool.RemoteTraceback: 
"""
Traceback (most recent call last):
  File "/usr/lib/python3.7/multiprocessing/pool.py", line 121, in worker
    result = (True, func(*args, **kwds))
  File "/home/developer2/.local/share/virtualenvs/backend-0eAQt0K2/lib/python3.7/site-packages/flake8/checker.py", line 682, in _run_checks
    return checker.run_checks()
  File "/home/developer2/.local/share/virtualenvs/backend-0eAQt0K2/lib/python3.7/site-packages/flake8/checker.py", line 612, in run_checks
    self.run_ast_checks()
  File "/home/developer2/.local/share/virtualenvs/backend-0eAQt0K2/lib/python3.7/site-packages/flake8/checker.py", line 520, in run_ast_checks
    for (line_number, offset, text, check) in runner:
  File "/home/developer2/.local/share/virtualenvs/backend-0eAQt0K2/lib/python3.7/site-packages/wemake_python_styleguide/checker.py", line 178, in run
    yield from self._run_checks(self.visitors)
  File "/home/developer2/.local/share/virtualenvs/backend-0eAQt0K2/lib/python3.7/site-packages/wemake_python_styleguide/checker.py", line 166, in _run_checks
    visitor.run()
  File "/home/developer2/.local/share/virtualenvs/backend-0eAQt0K2/lib/python3.7/site-packages/wemake_python_styleguide/visitors/base.py", line 140, in run
    self.visit(self.tree)
  File "/usr/lib/python3.7/ast.py", line 262, in visit
    return visitor(node)
  File "/usr/lib/python3.7/ast.py", line 270, in generic_visit
    self.visit(item)
  File "/usr/lib/python3.7/ast.py", line 262, in visit
    return visitor(node)
  File "/home/developer2/.local/share/virtualenvs/backend-0eAQt0K2/lib/python3.7/site-packages/wemake_python_styleguide/visitors/ast/statements.py", line 237, in visit_ClassDef
    self.generic_visit(node)
  File "/usr/lib/python3.7/ast.py", line 270, in generic_visit
    self.visit(item)
  File "/usr/lib/python3.7/ast.py", line 262, in visit
    return visitor(node)
  File "/home/developer2/.local/share/virtualenvs/backend-0eAQt0K2/lib/python3.7/site-packages/wemake_python_styleguide/visitors/ast/statements.py", line 231, in visit_any_function
    self.generic_visit(node)
  File "/usr/lib/python3.7/ast.py", line 270, in generic_visit
    self.visit(item)
  File "/usr/lib/python3.7/ast.py", line 262, in visit
    return visitor(node)
  File "/usr/lib/python3.7/ast.py", line 272, in generic_visit
    self.visit(value)
  File "/usr/lib/python3.7/ast.py", line 262, in visit
    return visitor(node)
  File "/home/developer2/.local/share/virtualenvs/backend-0eAQt0K2/lib/python3.7/site-packages/wemake_python_styleguide/visitors/ast/statements.py", line 219, in visit_collection
    self._check_indentation(node, elements, extra_lines=1)
  File "/home/developer2/.local/share/virtualenvs/backend-0eAQt0K2/lib/python3.7/site-packages/wemake_python_styleguide/visitors/ast/statements.py", line 206, in _check_indentation
    extra_lines,
  File "/home/developer2/.local/share/virtualenvs/backend-0eAQt0K2/lib/python3.7/site-packages/wemake_python_styleguide/visitors/ast/statements.py", line 171, in _check_first_element
    if statement.lineno == node.lineno and not extra_lines:
AttributeError: 'NoneType' object has no attribute 'lineno'
"""

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/developer2/.local/share/virtualenvs/backend-0eAQt0K2/bin/flake8", line 11, in <module>
    sys.exit(main())
  File "/home/developer2/.local/share/virtualenvs/backend-0eAQt0K2/lib/python3.7/site-packages/flake8/main/cli.py", line 16, in main
    app.run(argv)
  File "/home/developer2/.local/share/virtualenvs/backend-0eAQt0K2/lib/python3.7/site-packages/flake8/main/application.py", line 412, in run
    self._run(argv)
  File "/home/developer2/.local/share/virtualenvs/backend-0eAQt0K2/lib/python3.7/site-packages/flake8/main/application.py", line 400, in _run
    self.run_checks()
  File "/home/developer2/.local/share/virtualenvs/backend-0eAQt0K2/lib/python3.7/site-packages/flake8/main/application.py", line 318, in run_checks
    self.file_checker_manager.run()
  File "/home/developer2/.local/share/virtualenvs/backend-0eAQt0K2/lib/python3.7/site-packages/flake8_per_file_ignores.py", line 33, in run
    orig_run(self)
  File "/home/developer2/.local/share/virtualenvs/backend-0eAQt0K2/lib/python3.7/site-packages/flake8/checker.py", line 338, in run
    self.run_parallel()
  File "/home/developer2/.local/share/virtualenvs/backend-0eAQt0K2/lib/python3.7/site-packages/flake8/checker.py", line 302, in run_parallel
    for ret in pool_map:
  File "/usr/lib/python3.7/multiprocessing/pool.py", line 774, in next
    raise value
AttributeError: 'NoneType' object has no attribute 'lineno'
```
## Code sample that causes this issue
```
class Mapper:
    @property
    def section(self):
        return {
            **self.phone,
            **self.workdays
        }
```

<!-- Describe how it should work. -->

## System information

### flake8 information

```
{
  "dependencies": [
    {
      "dependency": "setuptools",
      "version": "40.6.2"
    }
  ],
  "platform": {
    "python_implementation": "CPython",
    "python_version": "3.7.1",
    "system": "Linux"
  },
  "plugins": [
    {
      "is_local": false,
      "plugin": "flake8-bandit",
      "version": "v1.0.2"
    },
    {
      "is_local": false,
      "plugin": "flake8-broken-line",
      "version": "0.1.0"
    },
    {
      "is_local": false,
      "plugin": "flake8-bugbear",
      "version": "18.8.0"
    },
    {
      "is_local": false,
      "plugin": "flake8-comprehensions",
      "version": "1.4.1"
    },
    {
      "is_local": false,
      "plugin": "flake8-debugger",
      "version": "3.1.0"
    },
    {
      "is_local": false,
      "plugin": "flake8-docstrings",
      "version": "1.3.0, pydocstyle: 3.0.0"
    },
    {
      "is_local": false,
      "plugin": "flake8-eradicate",
      "version": "0.1.1"
    },
    {
      "is_local": false,
      "plugin": "flake8-print",
      "version": "3.1.0"
    },
    {
      "is_local": false,
      "plugin": "flake8-string-format",
      "version": "0.2.3"
    },
    {
      "is_local": false,
      "plugin": "flake8-type-annotations",
      "version": "0.1.0"
    },
    {
      "is_local": false,
      "plugin": "flake8_builtins",
      "version": "1.4.1"
    },
    {
      "is_local": false,
      "plugin": "flake8_coding",
      "version": "1.3.1"
    },
    {
      "is_local": false,
      "plugin": "flake8_commas",
      "version": "2.0.0"
    },
    {
      "is_local": false,
      "plugin": "flake8_isort",
      "version": "2.3"
    },
    {
      "is_local": false,
      "plugin": "flake8_pep3101",
      "version": "1.2.1"
    },
    {
      "is_local": false,
      "plugin": "flake8_quotes",
      "version": "1.0.0"
    },
    {
      "is_local": false,
      "plugin": "logging-format",
      "version": "0.5.0"
    },
    {
      "is_local": false,
      "plugin": "mccabe",
      "version": "0.6.1"
    },
    {
      "is_local": false,
      "plugin": "naming",
      "version": "0.7.0"
    },
    {
      "is_local": false,
      "plugin": "per-file-ignores",
      "version": "0.6"
    },
    {
      "is_local": false,
      "plugin": "pycodestyle",
      "version": "2.4.0"
    },
    {
      "is_local": false,
      "plugin": "pyflakes",
      "version": "2.0.0"
    },
    {
      "is_local": false,
      "plugin": "wemake-python-styleguide",
      "version": "0.6.2"
    }
  ],
  "version": "3.6.0"
}
```

AttributeError: 'NoneType' object has no attribute 'lineno'
# Bug report

## What's wrong
When I run flake8 with wemake-python-styleguide 0.6.2, I see exception traceback. 
Something simular to https://github.com/wemake-services/wemake-python-styleguide/issues/423

### traceback

```
multiprocessing.pool.RemoteTraceback: 
"""
Traceback (most recent call last):
  File "/usr/lib/python3.7/multiprocessing/pool.py", line 121, in worker
    result = (True, func(*args, **kwds))
  File "/home/developer2/.local/share/virtualenvs/backend-0eAQt0K2/lib/python3.7/site-packages/flake8/checker.py", line 682, in _run_checks
    return checker.run_checks()
  File "/home/developer2/.local/share/virtualenvs/backend-0eAQt0K2/lib/python3.7/site-packages/flake8/checker.py", line 612, in run_checks
    self.run_ast_checks()
  File "/home/developer2/.local/share/virtualenvs/backend-0eAQt0K2/lib/python3.7/site-packages/flake8/checker.py", line 520, in run_ast_checks
    for (line_number, offset, text, check) in runner:
  File "/home/developer2/.local/share/virtualenvs/backend-0eAQt0K2/lib/python3.7/site-packages/wemake_python_styleguide/checker.py", line 178, in run
    yield from self._run_checks(self.visitors)
  File "/home/developer2/.local/share/virtualenvs/backend-0eAQt0K2/lib/python3.7/site-packages/wemake_python_styleguide/checker.py", line 166, in _run_checks
    visitor.run()
  File "/home/developer2/.local/share/virtualenvs/backend-0eAQt0K2/lib/python3.7/site-packages/wemake_python_styleguide/visitors/base.py", line 140, in run
    self.visit(self.tree)
  File "/usr/lib/python3.7/ast.py", line 262, in visit
    return visitor(node)
  File "/usr/lib/python3.7/ast.py", line 270, in generic_visit
    self.visit(item)
  File "/usr/lib/python3.7/ast.py", line 262, in visit
    return visitor(node)
  File "/home/developer2/.local/share/virtualenvs/backend-0eAQt0K2/lib/python3.7/site-packages/wemake_python_styleguide/visitors/ast/statements.py", line 237, in visit_ClassDef
    self.generic_visit(node)
  File "/usr/lib/python3.7/ast.py", line 270, in generic_visit
    self.visit(item)
  File "/usr/lib/python3.7/ast.py", line 262, in visit
    return visitor(node)
  File "/home/developer2/.local/share/virtualenvs/backend-0eAQt0K2/lib/python3.7/site-packages/wemake_python_styleguide/visitors/ast/statements.py", line 231, in visit_any_function
    self.generic_visit(node)
  File "/usr/lib/python3.7/ast.py", line 270, in generic_visit
    self.visit(item)
  File "/usr/lib/python3.7/ast.py", line 262, in visit
    return visitor(node)
  File "/usr/lib/python3.7/ast.py", line 272, in generic_visit
    self.visit(value)
  File "/usr/lib/python3.7/ast.py", line 262, in visit
    return visitor(node)
  File "/home/developer2/.local/share/virtualenvs/backend-0eAQt0K2/lib/python3.7/site-packages/wemake_python_styleguide/visitors/ast/statements.py", line 219, in visit_collection
    self._check_indentation(node, elements, extra_lines=1)
  File "/home/developer2/.local/share/virtualenvs/backend-0eAQt0K2/lib/python3.7/site-packages/wemake_python_styleguide/visitors/ast/statements.py", line 206, in _check_indentation
    extra_lines,
  File "/home/developer2/.local/share/virtualenvs/backend-0eAQt0K2/lib/python3.7/site-packages/wemake_python_styleguide/visitors/ast/statements.py", line 171, in _check_first_element
    if statement.lineno == node.lineno and not extra_lines:
AttributeError: 'NoneType' object has no attribute 'lineno'
"""

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/developer2/.local/share/virtualenvs/backend-0eAQt0K2/bin/flake8", line 11, in <module>
    sys.exit(main())
  File "/home/developer2/.local/share/virtualenvs/backend-0eAQt0K2/lib/python3.7/site-packages/flake8/main/cli.py", line 16, in main
    app.run(argv)
  File "/home/developer2/.local/share/virtualenvs/backend-0eAQt0K2/lib/python3.7/site-packages/flake8/main/application.py", line 412, in run
    self._run(argv)
  File "/home/developer2/.local/share/virtualenvs/backend-0eAQt0K2/lib/python3.7/site-packages/flake8/main/application.py", line 400, in _run
    self.run_checks()
  File "/home/developer2/.local/share/virtualenvs/backend-0eAQt0K2/lib/python3.7/site-packages/flake8/main/application.py", line 318, in run_checks
    self.file_checker_manager.run()
  File "/home/developer2/.local/share/virtualenvs/backend-0eAQt0K2/lib/python3.7/site-packages/flake8_per_file_ignores.py", line 33, in run
    orig_run(self)
  File "/home/developer2/.local/share/virtualenvs/backend-0eAQt0K2/lib/python3.7/site-packages/flake8/checker.py", line 338, in run
    self.run_parallel()
  File "/home/developer2/.local/share/virtualenvs/backend-0eAQt0K2/lib/python3.7/site-packages/flake8/checker.py", line 302, in run_parallel
    for ret in pool_map:
  File "/usr/lib/python3.7/multiprocessing/pool.py", line 774, in next
    raise value
AttributeError: 'NoneType' object has no attribute 'lineno'
```
## Code sample that causes this issue
```
class Mapper:
    @property
    def section(self):
        return {
            **self.phone,
            **self.workdays
        }
```

<!-- Describe how it should work. -->

## System information

### flake8 information

```
{
  "dependencies": [
    {
      "dependency": "setuptools",
      "version": "40.6.2"
    }
  ],
  "platform": {
    "python_implementation": "CPython",
    "python_version": "3.7.1",
    "system": "Linux"
  },
  "plugins": [
    {
      "is_local": false,
      "plugin": "flake8-bandit",
      "version": "v1.0.2"
    },
    {
      "is_local": false,
      "plugin": "flake8-broken-line",
      "version": "0.1.0"
    },
    {
      "is_local": false,
      "plugin": "flake8-bugbear",
      "version": "18.8.0"
    },
    {
      "is_local": false,
      "plugin": "flake8-comprehensions",
      "version": "1.4.1"
    },
    {
      "is_local": false,
      "plugin": "flake8-debugger",
      "version": "3.1.0"
    },
    {
      "is_local": false,
      "plugin": "flake8-docstrings",
      "version": "1.3.0, pydocstyle: 3.0.0"
    },
    {
      "is_local": false,
      "plugin": "flake8-eradicate",
      "version": "0.1.1"
    },
    {
      "is_local": false,
      "plugin": "flake8-print",
      "version": "3.1.0"
    },
    {
      "is_local": false,
      "plugin": "flake8-string-format",
      "version": "0.2.3"
    },
    {
      "is_local": false,
      "plugin": "flake8-type-annotations",
      "version": "0.1.0"
    },
    {
      "is_local": false,
      "plugin": "flake8_builtins",
      "version": "1.4.1"
    },
    {
      "is_local": false,
      "plugin": "flake8_coding",
      "version": "1.3.1"
    },
    {
      "is_local": false,
      "plugin": "flake8_commas",
      "version": "2.0.0"
    },
    {
      "is_local": false,
      "plugin": "flake8_isort",
      "version": "2.3"
    },
    {
      "is_local": false,
      "plugin": "flake8_pep3101",
      "version": "1.2.1"
    },
    {
      "is_local": false,
      "plugin": "flake8_quotes",
      "version": "1.0.0"
    },
    {
      "is_local": false,
      "plugin": "logging-format",
      "version": "0.5.0"
    },
    {
      "is_local": false,
      "plugin": "mccabe",
      "version": "0.6.1"
    },
    {
      "is_local": false,
      "plugin": "naming",
      "version": "0.7.0"
    },
    {
      "is_local": false,
      "plugin": "per-file-ignores",
      "version": "0.6"
    },
    {
      "is_local": false,
      "plugin": "pycodestyle",
      "version": "2.4.0"
    },
    {
      "is_local": false,
      "plugin": "pyflakes",
      "version": "2.0.0"
    },
    {
      "is_local": false,
      "plugin": "wemake-python-styleguide",
      "version": "0.6.2"
    }
  ],
  "version": "3.6.0"
}
```



Discussion:

Resolving patch:
diff --git a/wemake_python_styleguide/logics/collections.py b/wemake_python_styleguide/logics/collections.py
new file mode 100644
--- /dev/null
+++ b/wemake_python_styleguide/logics/collections.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+
+import ast
+from typing import List, Sequence
+
+
+def normalize_dict_elements(node: ast.Dict) -> Sequence[ast.AST]:
+    """
+    Normalizes ``dict`` elements and enforces consistent order.
+
+    We had a problem that some ``dict`` objects might not have some keys.
+
+    Example::
+
+        some_dict = {**one, **two}
+
+    This ``dict`` contains two values and zero keys.
+    This function will normalize this structure to use
+    values instead of missing keys.
+
+    See also:
+        https://github.com/wemake-services/wemake-python-styleguide/issues/450
+
+    """
+    elements: List[ast.AST] = []
+    for dict_key, dict_value in zip(node.keys, node.values):
+        if dict_key is None:
+            elements.append(dict_value)
+        else:
+            elements.append(dict_key)
+    return elements
diff --git a/wemake_python_styleguide/visitors/ast/statements.py b/wemake_python_styleguide/visitors/ast/statements.py
--- a/wemake_python_styleguide/visitors/ast/statements.py
+++ b/wemake_python_styleguide/visitors/ast/statements.py
@@ -3,6 +3,7 @@
 import ast
 from typing import ClassVar, List, Optional, Sequence, Union
 
+from wemake_python_styleguide.logics.collections import normalize_dict_elements
 from wemake_python_styleguide.logics.functions import get_all_arguments
 from wemake_python_styleguide.logics.nodes import get_parent, is_doc_string
 from wemake_python_styleguide.types import AnyFunctionDef, AnyNodes, final
@@ -218,7 +219,10 @@ def _check_indentation(
 
     def visit_collection(self, node: AnyCollection) -> None:
         """Checks how collection items indentation."""
-        elements = node.keys if isinstance(node, ast.Dict) else node.elts
+        if isinstance(node, ast.Dict):
+            elements = normalize_dict_elements(node)
+        else:
+            elements = node.elts
         self._check_indentation(node, elements, extra_lines=1)
         self.generic_visit(node)
 


Test patch:
diff --git a/tests/test_visitors/test_ast/test_statements/test_parameters_indentation/test_collection_indentation.py b/tests/test_visitors/test_ast/test_statements/test_parameters_indentation/test_collection_indentation.py
--- a/tests/test_visitors/test_ast/test_statements/test_parameters_indentation/test_collection_indentation.py
+++ b/tests/test_visitors/test_ast/test_statements/test_parameters_indentation/test_collection_indentation.py
@@ -99,6 +99,18 @@
 }
 """
 
+correct_simple_regression450 = """
+was_crashing = {**some_other_dict}
+"""
+
+correct_multiline_regression450 = """
+was_crashing = {
+    **some_other_dict,
+    **some_very_other_dict,
+    **third_dict,
+}
+"""
+
 
 # Wrong:
 
@@ -198,6 +210,13 @@
 }
 """
 
+wrong_regression450 = """
+some_dict = {
+    **one,
+    **two, **three,
+}
+"""
+
 
 @pytest.mark.parametrize('code', [
     correct_multiline_string,
@@ -214,6 +233,10 @@
     correct_next_line_set,
     correct_next_line_tuple,
     correct_nested_collections,
+
+    # Regressions:
+    correct_simple_regression450,
+    correct_multiline_regression450,
 ])
 def test_correct_collection_indentation(
     assert_errors,
@@ -247,6 +270,9 @@ def test_correct_collection_indentation(
     wrong_dict_indentation2,
     wrong_dict_indentation3,
     wrong_dict_indentation4,
+
+    # Regressions:
+    wrong_regression450,
 ])
 def test_wrong_collection_indentation(
     assert_errors,