Repository: wemake-services/wemake-python-styleguide

Issue:
Forbid lambdas that return initial type values
# Rule request

<!-- Thanks for coming up with a proposal! -->

## Thesis

Forbid `lambda: ''`, `lambda: 0`, `lambda: []`, `lambda: 0.0`, etc.
These all can be replaced with `int`, `list`, `str`, and other simple type calls.

That's the original code I saw using this: https://github.com/numpy/numpy-stubs/blob/master/tests/test_stubs.py#L54

What we need to check:
- `lambda` has 0 args
- `lambda` returns default type value

## Reasoning

This is a refactoring rule.

<!--
Why do you think this is a good idea?

Please, include some strong points and considerations.
-->

<!--

  Love wemake-python-styleguide? ❤️
  Do you want your features to be implemented faster?

  Please consider supporting our collective:
👉  https://opencollective.com/wemake-python-styleguide/donate

-->



Discussion:
How about to forbid `lambda: fun()` as  well as it redundant?
We already forbid it, see `UselessLambdaViolation`
Great!
I wanna work on this!
@kyleseto2 you are on it! Thanks!
I didn't find any instances of lambda returning type values
That's correct! We don't have any of them in the source code.
Sorry I'm new to this. I dont think I fully understand the task
@kyleseto2 no problem. Thanks for your effort! 

Resolving patch:
diff --git a/wemake_python_styleguide/violations/best_practices.py b/wemake_python_styleguide/violations/best_practices.py
--- a/wemake_python_styleguide/violations/best_practices.py
+++ b/wemake_python_styleguide/violations/best_practices.py
@@ -65,6 +65,7 @@
    UnhashableTypeInHashViolation
    WrongKeywordConditionViolation
    WrongNamedKeywordViolation
+   ImplicitPrimitiveViolation
 
 Best practices
 --------------
@@ -115,6 +116,7 @@
 .. autoclass:: UnhashableTypeInHashViolation
 .. autoclass:: WrongKeywordConditionViolation
 .. autoclass:: WrongNamedKeywordViolation
+.. autoclass:: ImplicitPrimitiveViolation
 
 """
 
@@ -1747,6 +1749,7 @@ class WrongKeywordConditionViolation(ASTViolation):
     code = 444
 
 
+@final
 class WrongNamedKeywordViolation(ASTViolation):
     """
     Forbids to have wrong named keywords in starred dicts.
@@ -1772,3 +1775,33 @@ class WrongNamedKeywordViolation(ASTViolation):
 
     code = 445
     error_template = 'Found wrong named keyword in starred dict'
+
+
+@final
+class ImplicitPrimitiveViolation(ASTViolation):
+    """
+    Forbids to use implicit primitives in a form of ``lambda`` functions.
+
+    Reasoning:
+        When you use ``lambda`` that returns a primitive value
+        and takes no arguments, it means that
+        you should use a primitive type instead.
+
+    Solution:
+        Replace ``lambda`` with ``int``, ``float``,
+        ``list``, or any other primitive.
+
+    Example::
+
+        # Correct:
+        defaultdict(int)
+
+        # Wrong:
+        defaultdict(lambda: 0)
+
+    .. versionadded:: 0.13.0
+
+    """
+
+    code = 446
+    error_template = 'Found implicit primitive in a form of `lambda`'
diff --git a/wemake_python_styleguide/visitors/ast/functions.py b/wemake_python_styleguide/visitors/ast/functions.py
--- a/wemake_python_styleguide/visitors/ast/functions.py
+++ b/wemake_python_styleguide/visitors/ast/functions.py
@@ -1,6 +1,7 @@
 # -*- coding: utf-8 -*-
 
 import ast
+from contextlib import suppress
 from typing import ClassVar, Dict, List, Optional, Union
 
 from typing_extensions import final
@@ -21,6 +22,7 @@
 from wemake_python_styleguide.violations.best_practices import (
     BooleanPositionalArgumentViolation,
     ComplexDefaultValueViolation,
+    ImplicitPrimitiveViolation,
     StopIterationInsideGeneratorViolation,
     WrongFunctionCallViolation,
 )
@@ -290,17 +292,40 @@ def _check_generator(self, node: AnyFunctionDef) -> None:
 class UselessLambdaDefinitionVisitor(base.BaseNodeVisitor):
     """This visitor is used specifically for ``lambda`` functions."""
 
+    # We contain unhashable types here, so we use a tuple:
+    _primitive_values = (
+        0,
+        False,
+        '',
+        b'',
+        [],
+        {},
+    ) + ((), )  # TODO: there's a bug in WPS317
+
     def visit_Lambda(self, node: ast.Lambda) -> None:
         """
         Checks if ``lambda`` functions are defined correctly.
 
         Raises:
             UselessLambdaViolation
+            ImplicitPrimitiveViolation
 
         """
         self._check_useless_lambda(node)
+        self._check_implicit_primitive(node)
         self.generic_visit(node)
 
+    def _check_implicit_primitive(self, node: ast.Lambda) -> None:
+        arguments = functions.get_all_arguments(node)
+        if arguments:
+            # We return from this check, since `lambda` has some arguments.
+            return
+
+        with suppress(ValueError):
+            return_value = ast.literal_eval(node.body)
+            if return_value in self._primitive_values:
+                self.add_violation(ImplicitPrimitiveViolation(node))
+
     def _check_useless_lambda(self, node: ast.Lambda) -> None:
         if not isinstance(node.body, ast.Call):
             return


Test patch:
diff --git a/tests/fixtures/noqa.py b/tests/fixtures/noqa.py
--- a/tests/fixtures/noqa.py
+++ b/tests/fixtures/noqa.py
@@ -328,6 +328,7 @@ def some_other_function():
 print(type(file_obj) == int)  # noqa: WPS516
 
 print(*[], **{'@': 1})  # noqa: WPS517, WPS445
+print(lambda: 0)  # noqa: WPS446
 
 for range_len in range(len(file_obj)):  # noqa: WPS518
     print(range_len)
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
@@ -167,6 +167,7 @@
     'WPS443': 1,
     'WPS444': 1,
     'WPS445': 1,
+    'WPS446': 1,
 
     'WPS500': 1,
     'WPS501': 1,
diff --git a/tests/test_visitors/test_ast/test_functions/test_implicit_primitive.py b/tests/test_visitors/test_ast/test_functions/test_implicit_primitive.py
new file mode 100644
--- /dev/null
+++ b/tests/test_visitors/test_ast/test_functions/test_implicit_primitive.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+
+import pytest
+
+from wemake_python_styleguide.violations.best_practices import (
+    ImplicitPrimitiveViolation,
+)
+from wemake_python_styleguide.visitors.ast.functions import (
+    UselessLambdaDefinitionVisitor,
+)
+
+
+@pytest.mark.parametrize('code', [
+    'lambda x: 0',
+    'lambda *x: []',
+    'lambda **x: ()',
+    'lambda x, y: 0',
+    'lambda: 1',
+    'lambda x=1: 0',
+    'lambda: [0]',
+    'lambda: [some]',
+])
+def test_correct_lambda(
+    assert_errors,
+    parse_ast_tree,
+    code,
+    default_options,
+):
+    """Testing that isinstance is callable with correct types."""
+    tree = parse_ast_tree(code)
+
+    visitor = UselessLambdaDefinitionVisitor(default_options, tree=tree)
+    visitor.run()
+
+    assert_errors(visitor, [])
+
+
+@pytest.mark.parametrize('code', [
+    'lambda: 0',
+    'lambda: []',
+    'lambda: ()',
+    'lambda: 0.0',
+    'lambda: 0j',
+    'lambda: False',
+    'lambda: {}',  # noqa: P103
+])
+def test_wrong_lambda(
+    assert_errors,
+    parse_ast_tree,
+    code,
+    default_options,
+):
+    """Testing that isinstance is callable with correct types."""
+    tree = parse_ast_tree(code)
+
+    visitor = UselessLambdaDefinitionVisitor(default_options, tree=tree)
+    visitor.run()
+
+    assert_errors(visitor, [ImplicitPrimitiveViolation])
diff --git a/tests/test_visitors/test_ast/test_functions/test_useless_lambda.py b/tests/test_visitors/test_ast/test_functions/test_useless_lambda.py
--- a/tests/test_visitors/test_ast/test_functions/test_useless_lambda.py
+++ b/tests/test_visitors/test_ast/test_functions/test_useless_lambda.py
@@ -40,7 +40,7 @@ def test_incorrect_lambda_definition(
 
 
 valid_calls = (
-    '[]',
+    '[1, 2, 3]',
     'method',
     'obj.attr',
     'obj.call()',