Repository: pydantic/pydantic

Issue:
Mypy plugin does not check the type of default and default_factory
### Checks

* [x] I added a descriptive title to this issue
* [x] I have searched (google, github) for similar issues and couldn't find anything
* [x] I have read and followed [the docs](https://pydantic-docs.helpmanual.io/) and still think this is a bug

<!-- Sorry to sound so draconian, but every second saved replying to issues is time spend improving pydantic :-) -->

# Bug

Output of `python -c "import pydantic.utils; print(pydantic.utils.version_info())"`:
```
pydantic version: 1.8.2
            pydantic compiled: False
                 install path: /Users/lucr/pydantic/pydantic
               python version: 3.9.5 (default, Nov  6 2021, 11:31:10)  [Clang 12.0.5 (clang-1205.0.22.11)]
                     platform: macOS-11.4-x86_64-i386-64bit
     optional deps. installed: ['devtools', 'dotenv', 'email-validator', 'typing-extensions']
```


```py
import pydantic

class ExampleMode(BaseModel):
   a: int = Field("test")
   b: list = Field(default_factory=set)
   c: int = Field("test", default_factory=set)
...
```

The model above should raise three `mypy` errors, since:
1. `a` is defined as an `int`, but its default is a string
2. `b` is defined as a `list`, but the default factory is a `set`
3. `c` will create a runtime error, since both default and default factory are present

I propose (will create PR shortly to show the desired behavior) to enhance the `get_function_hook` of the `mypy` plugin to make `mypy` solve the three errors above, by changing the `return_type` of the function depending on the argument type.




Discussion:

Resolving patch:
diff --git a/pydantic/mypy.py b/pydantic/mypy.py
--- a/pydantic/mypy.py
+++ b/pydantic/mypy.py
@@ -34,7 +34,14 @@
     Var,
 )
 from mypy.options import Options
-from mypy.plugin import CheckerPluginInterface, ClassDefContext, MethodContext, Plugin, SemanticAnalyzerPluginInterface
+from mypy.plugin import (
+    CheckerPluginInterface,
+    ClassDefContext,
+    FunctionContext,
+    MethodContext,
+    Plugin,
+    SemanticAnalyzerPluginInterface,
+)
 from mypy.plugins import dataclasses
 from mypy.semanal import set_callable_name  # type: ignore
 from mypy.server.trigger import make_wildcard_trigger
@@ -43,6 +50,7 @@
     CallableType,
     Instance,
     NoneType,
+    Overloaded,
     Type,
     TypeOfAny,
     TypeType,
@@ -94,6 +102,12 @@ def get_base_class_hook(self, fullname: str) -> 'Optional[Callable[[ClassDefCont
                 return self._pydantic_model_class_maker_callback
         return None
 
+    def get_function_hook(self, fullname: str) -> 'Optional[Callable[[FunctionContext], Type]]':
+        sym = self.lookup_fully_qualified(fullname)
+        if sym and sym.fullname == FIELD_FULLNAME:
+            return self._pydantic_field_callback
+        return None
+
     def get_method_hook(self, fullname: str) -> Optional[Callable[[MethodContext], Type]]:
         if fullname.endswith('.from_orm'):
             return from_orm_callback
@@ -108,6 +122,51 @@ def _pydantic_model_class_maker_callback(self, ctx: ClassDefContext) -> None:
         transformer = PydanticModelTransformer(ctx, self.plugin_config)
         transformer.transform()
 
+    def _pydantic_field_callback(self, ctx: FunctionContext) -> 'Type':
+        """
+        Extract the type of the `default` argument from the Field function, and use it as the return type.
+
+        In particular:
+        * Check whether the default and default_factory argument is specified.
+        * Output an error if both are specified.
+        * Retrieve the type of the argument which is specified, and use it as return type for the function.
+        """
+        default_any_type = ctx.default_return_type
+
+        assert ctx.callee_arg_names[0] == 'default', '"default" is no longer first argument in Field()'
+        assert ctx.callee_arg_names[1] == 'default_factory', '"default_factory" is no longer second argument in Field()'
+        default_args = ctx.args[0]
+        default_factory_args = ctx.args[1]
+
+        if default_args and default_factory_args:
+            error_default_and_default_factory_specified(ctx.api, ctx.context)
+            return default_any_type
+
+        if default_args:
+            default_type = ctx.arg_types[0][0]
+            default_arg = default_args[0]
+
+            # Fallback to default Any type if the field is required
+            if not isinstance(default_arg, EllipsisExpr):
+                return default_type
+
+        elif default_factory_args:
+            default_factory_type = ctx.arg_types[1][0]
+
+            # Functions which use `ParamSpec` can be overloaded, exposing the callable's types as a parameter
+            # Pydantic calls the default factory without any argument, so we retrieve the first item
+            if isinstance(default_factory_type, Overloaded):
+                if float(mypy_version) > 0.910:
+                    default_factory_type = default_factory_type.items[0]
+                else:
+                    # Mypy0.910 exposes the items of overloaded types in a function
+                    default_factory_type = default_factory_type.items()[0]  # type: ignore[operator]
+
+            if isinstance(default_factory_type, CallableType):
+                return default_factory_type.ret_type
+
+        return default_any_type
+
 
 class PydanticPluginConfig:
     __slots__ = ('init_forbid_extra', 'init_typed', 'warn_required_dynamic_aliases', 'warn_untyped_fields')
@@ -588,6 +647,7 @@ def setdefault(self, key: str, value: Any) -> None:
 ERROR_ALIAS = ErrorCode('pydantic-alias', 'Dynamic alias disallowed', 'Pydantic')
 ERROR_UNEXPECTED = ErrorCode('pydantic-unexpected', 'Unexpected behavior', 'Pydantic')
 ERROR_UNTYPED = ErrorCode('pydantic-field', 'Untyped field disallowed', 'Pydantic')
+ERROR_FIELD_DEFAULTS = ErrorCode('pydantic-field', 'Invalid Field defaults', 'Pydantic')
 
 
 def error_from_orm(model_name: str, api: CheckerPluginInterface, context: Context) -> None:
@@ -614,6 +674,10 @@ def error_untyped_fields(api: SemanticAnalyzerPluginInterface, context: Context)
     api.fail('Untyped fields disallowed', context, code=ERROR_UNTYPED)
 
 
+def error_default_and_default_factory_specified(api: CheckerPluginInterface, context: Context) -> None:
+    api.fail('Field default and default_factory cannot be specified together', context, code=ERROR_FIELD_DEFAULTS)
+
+
 def add_method(
     ctx: ClassDefContext,
     name: str,


Test patch:
diff --git a/tests/mypy/modules/plugin_fail.py b/tests/mypy/modules/plugin_fail.py
--- a/tests/mypy/modules/plugin_fail.py
+++ b/tests/mypy/modules/plugin_fail.py
@@ -1,4 +1,4 @@
-from typing import Any, Generic, Optional, Set, TypeVar, Union
+from typing import Any, Generic, List, Optional, Set, TypeVar, Union
 
 from pydantic import BaseModel, BaseSettings, Extra, Field
 from pydantic.dataclasses import dataclass
@@ -226,3 +226,25 @@ class Config:
 
 inheriting2 = InheritingModel2(x=1, y='c')
 inheriting2.y = 'd'
+
+
+def _default_factory() -> str:
+    ...
+
+
+test: List[str] = []
+
+
+class FieldDefaultTestingModel(BaseModel):
+    # Default
+    e: int = Field(None)
+    f: int = None
+
+    # Default factory
+    g: str = Field(default_factory=set)
+    h: int = Field(default_factory=_default_factory)
+    i: List[int] = Field(default_factory=list)
+    l: str = Field(default_factory=3)
+
+    # Default and default factory
+    m: int = Field(default=1, default_factory=list)
diff --git a/tests/mypy/modules/plugin_success.py b/tests/mypy/modules/plugin_success.py
--- a/tests/mypy/modules/plugin_success.py
+++ b/tests/mypy/modules/plugin_success.py
@@ -1,4 +1,4 @@
-from typing import ClassVar, Generic, Optional, TypeVar, Union
+from typing import ClassVar, Generic, List, Optional, TypeVar, Union
 
 from pydantic import BaseModel, BaseSettings, Field, create_model, validator
 from pydantic.dataclasses import dataclass
@@ -193,3 +193,26 @@ class Response(GenericModel, Generic[T]):
 
 
 response = Response[Model](data=model, error=None)
+
+
+def _default_factory_str() -> str:
+    ...
+
+
+def _default_factory_list() -> List[int]:
+    ...
+
+
+class FieldDefaultTestingModel(BaseModel):
+    # Required
+    a: int
+    b: int = Field()
+    c: int = Field(...)
+
+    # Default
+    d: int = Field(1)
+
+    # Default factory
+    g: List[int] = Field(default_factory=_default_factory_list)
+    h: str = Field(default_factory=_default_factory_str)
+    i: str = Field(default_factory=lambda: 'test')
diff --git a/tests/mypy/outputs/plugin-fail-strict.txt b/tests/mypy/outputs/plugin-fail-strict.txt
--- a/tests/mypy/outputs/plugin-fail-strict.txt
+++ b/tests/mypy/outputs/plugin-fail-strict.txt
@@ -9,6 +9,7 @@
 63: error: Invalid value for "Config.orm_mode"  [pydantic-config]
 74: error: Incompatible types in assignment (expression has type "ellipsis", variable has type "int")  [assignment]
 77: error: Untyped fields disallowed  [pydantic-field]
+83: error: Incompatible types in assignment (expression has type "None", variable has type "int")  [assignment]
 84: error: Untyped fields disallowed  [pydantic-field]
 87: error: Missing named argument "a" for "DefaultTestingModel"  [call-arg]
 87: error: Missing named argument "b" for "DefaultTestingModel"  [call-arg]
@@ -33,3 +34,10 @@
 186: error: Unexpected keyword argument "z" for "AliasGeneratorModel2"  [call-arg]
 189: error: Name "Missing" is not defined  [name-defined]
 219: error: Property "y" defined in "FrozenModel" is read-only  [misc]
+240: error: Incompatible types in assignment (expression has type "None", variable has type "int")  [assignment]
+241: error: Incompatible types in assignment (expression has type "None", variable has type "int")  [assignment]
+244: error: Incompatible types in assignment (expression has type "Set[_T]", variable has type "str")  [assignment]
+245: error: Incompatible types in assignment (expression has type "str", variable has type "int")  [assignment]
+246: error: Incompatible types in assignment (expression has type "List[_T]", variable has type "List[int]")  [assignment]
+247: error: Argument "default_factory" to "Field" has incompatible type "int"; expected "Optional[Callable[[], Any]]"  [arg-type]
+250: error: Field default and default_factory cannot be specified together  [pydantic-field]
diff --git a/tests/mypy/outputs/plugin-fail.txt b/tests/mypy/outputs/plugin-fail.txt
--- a/tests/mypy/outputs/plugin-fail.txt
+++ b/tests/mypy/outputs/plugin-fail.txt
@@ -8,6 +8,7 @@
 58: error: Invalid value for "Config.orm_mode"  [pydantic-config]
 63: error: Invalid value for "Config.orm_mode"  [pydantic-config]
 74: error: Incompatible types in assignment (expression has type "ellipsis", variable has type "int")  [assignment]
+83: error: Incompatible types in assignment (expression has type "None", variable has type "int")  [assignment]
 87: error: Missing named argument "a" for "DefaultTestingModel"  [call-arg]
 87: error: Missing named argument "b" for "DefaultTestingModel"  [call-arg]
 87: error: Missing named argument "c" for "DefaultTestingModel"  [call-arg]
@@ -22,3 +23,10 @@
 182: error: Unused "type: ignore" comment
 189: error: Name "Missing" is not defined  [name-defined]
 219: error: Property "y" defined in "FrozenModel" is read-only  [misc]
+240: error: Incompatible types in assignment (expression has type "None", variable has type "int")  [assignment]
+241: error: Incompatible types in assignment (expression has type "None", variable has type "int")  [assignment]
+244: error: Incompatible types in assignment (expression has type "Set[_T]", variable has type "str")  [assignment]
+245: error: Incompatible types in assignment (expression has type "str", variable has type "int")  [assignment]
+246: error: Incompatible types in assignment (expression has type "List[_T]", variable has type "List[int]")  [assignment]
+247: error: Argument "default_factory" to "Field" has incompatible type "int"; expected "Optional[Callable[[], Any]]"  [arg-type]
+250: error: Field default and default_factory cannot be specified together  [pydantic-field]