Repository: pydantic/pydantic

Issue:
Mypy Plugin causes errors when using `model_validator`
### Initial Checks

- [X] I confirm that I'm using Pydantic V2

### Description

This is closely related to #6277, which was apparently fixed by PR #6514.

Unfortunately, when you activate the [Pydantic Mypy plugin](https://docs.pydantic.dev/latest/integrations/mypy/), using a `@model_validator(mode="after")` still causes errors.

For the MRE, all that should be additionally required to reproduce it is activating the Mypy plugin, e.g. via the the `pyproject.toml`:

```toml
[tool.mypy]
plugins = ["pydantic.mypy"]
```

This was tested with Mypy `1.4.1` on the system described below and additionally in a Python `3.11.4` virtual environment on the same system.

### Example Code

```Python
from pydantic import BaseModel, model_validator


class Foo(BaseModel):
    x: int

    @model_validator(mode="after")  # error: Cannot infer function type argument  [misc]
    def print_x(self) -> "Foo":
        print(f"{self.x=}")
        return self  # error: Incompatible return value type (got "type[Foo]", expected "Foo")  [return-value]


Foo(x=1)  # prints "self.x=1"
```


### Python, Pydantic & OS Version

```Text
pydantic version: 2.0.3
        pydantic-core version: 2.3.0 release build profile
                 install path: /path/to/.venv/lib/python3.9/site-packages/pydantic
               python version: 3.9.17 (main, Jun 24 2023, 20:40:40)  [GCC 13.1.1 20230429]
                     platform: Linux-6.4.3-arch1-1-x86_64-with-glibc2.37
     optional deps. installed: ['typing-extensions']
```


Selected Assignee: @hramezani


Discussion:
cc @dmontagu 
Seems like the issue is here: https://github.com/pydantic/pydantic/blob/443a7e9ddfe4c56a65e1a6bf4024c4f4bfecf909/pydantic/mypy.py#L455-L472

It's telling mypy that the function is a classmethod, thus changing the first parameter to be `type[Self]`. Should be a fairly simple fix, I'll take a stab at it :)
Please do!

Resolving patch:
diff --git a/pydantic/mypy.py b/pydantic/mypy.py
--- a/pydantic/mypy.py
+++ b/pydantic/mypy.py
@@ -92,6 +92,7 @@
 MODEL_METACLASS_FULLNAME = 'pydantic._internal._model_construction.ModelMetaclass'
 FIELD_FULLNAME = 'pydantic.fields.Field'
 DATACLASS_FULLNAME = 'pydantic.dataclasses.dataclass'
+MODEL_VALIDATOR_FULLNAME = 'pydantic.functional_validators.model_validator'
 DECORATOR_FULLNAMES = {
     'pydantic.functional_validators.field_validator',
     'pydantic.functional_validators.model_validator',
@@ -467,6 +468,14 @@ def adjust_decorator_signatures(self) -> None:
                     isinstance(first_dec, CallExpr)
                     and isinstance(first_dec.callee, NameExpr)
                     and first_dec.callee.fullname in DECORATOR_FULLNAMES
+                    # @model_validator(mode="after") is an exception, it expects a regular method
+                    and not (
+                        first_dec.callee.fullname == MODEL_VALIDATOR_FULLNAME
+                        and any(
+                            first_dec.arg_names[i] == 'mode' and isinstance(arg, StrExpr) and arg.value == 'after'
+                            for i, arg in enumerate(first_dec.args)
+                        )
+                    )
                 ):
                     # TODO: Only do this if the first argument of the decorated function is `cls`
                     sym.node.func.is_class = True


Test patch:
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,5 +1,7 @@
 from typing import Any, ClassVar, Generic, List, Optional, TypeVar, Union
 
+from typing_extensions import Self
+
 from pydantic import BaseModel, ConfigDict, Field, create_model, field_validator, model_validator, validator
 from pydantic.dataclasses import dataclass
 
@@ -290,7 +292,11 @@ class MyModel(BaseModel):
 
         @model_validator(mode='before')
         @classmethod
-        def validate_values(cls, values: Any) -> Any:
+        def validate_before(cls, values: Any) -> Any:
             return values
 
+        @model_validator(mode='after')
+        def validate_after(self) -> Self:
+            return self
+
     MyModel(number=2)
diff --git a/tests/mypy/outputs/1.0.1/mypy-default_ini/plugin_success.py b/tests/mypy/outputs/1.0.1/mypy-default_ini/plugin_success.py
--- a/tests/mypy/outputs/1.0.1/mypy-default_ini/plugin_success.py
+++ b/tests/mypy/outputs/1.0.1/mypy-default_ini/plugin_success.py
@@ -1,5 +1,7 @@
 from typing import Any, ClassVar, Generic, List, Optional, TypeVar, Union
 
+from typing_extensions import Self
+
 from pydantic import BaseModel, ConfigDict, Field, create_model, field_validator, model_validator, validator
 from pydantic.dataclasses import dataclass
 
@@ -296,7 +298,11 @@ class MyModel(BaseModel):
 
         @model_validator(mode='before')
         @classmethod
-        def validate_values(cls, values: Any) -> Any:
+        def validate_before(cls, values: Any) -> Any:
             return values
 
+        @model_validator(mode='after')
+        def validate_after(self) -> Self:
+            return self
+
     MyModel(number=2)
diff --git a/tests/mypy/outputs/1.0.1/mypy-plugin-strict_ini/plugin_success.py b/tests/mypy/outputs/1.0.1/mypy-plugin-strict_ini/plugin_success.py
--- a/tests/mypy/outputs/1.0.1/mypy-plugin-strict_ini/plugin_success.py
+++ b/tests/mypy/outputs/1.0.1/mypy-plugin-strict_ini/plugin_success.py
@@ -1,5 +1,7 @@
 from typing import Any, ClassVar, Generic, List, Optional, TypeVar, Union
 
+from typing_extensions import Self
+
 from pydantic import BaseModel, ConfigDict, Field, create_model, field_validator, model_validator, validator
 from pydantic.dataclasses import dataclass
 
@@ -292,7 +294,11 @@ class MyModel(BaseModel):
 
         @model_validator(mode='before')
         @classmethod
-        def validate_values(cls, values: Any) -> Any:
+        def validate_before(cls, values: Any) -> Any:
             return values
 
+        @model_validator(mode='after')
+        def validate_after(self) -> Self:
+            return self
+
     MyModel(number=2)
diff --git a/tests/mypy/outputs/1.0.1/mypy-plugin_ini/plugin_success.py b/tests/mypy/outputs/1.0.1/mypy-plugin_ini/plugin_success.py
--- a/tests/mypy/outputs/1.0.1/mypy-plugin_ini/plugin_success.py
+++ b/tests/mypy/outputs/1.0.1/mypy-plugin_ini/plugin_success.py
@@ -1,5 +1,7 @@
 from typing import Any, ClassVar, Generic, List, Optional, TypeVar, Union
 
+from typing_extensions import Self
+
 from pydantic import BaseModel, ConfigDict, Field, create_model, field_validator, model_validator, validator
 from pydantic.dataclasses import dataclass
 
@@ -290,7 +292,11 @@ class MyModel(BaseModel):
 
         @model_validator(mode='before')
         @classmethod
-        def validate_values(cls, values: Any) -> Any:
+        def validate_before(cls, values: Any) -> Any:
             return values
 
+        @model_validator(mode='after')
+        def validate_after(self) -> Self:
+            return self
+
     MyModel(number=2)
diff --git a/tests/mypy/outputs/1.0.1/pyproject-plugin-strict_toml/plugin_success.py b/tests/mypy/outputs/1.0.1/pyproject-plugin-strict_toml/plugin_success.py
--- a/tests/mypy/outputs/1.0.1/pyproject-plugin-strict_toml/plugin_success.py
+++ b/tests/mypy/outputs/1.0.1/pyproject-plugin-strict_toml/plugin_success.py
@@ -1,5 +1,7 @@
 from typing import Any, ClassVar, Generic, List, Optional, TypeVar, Union
 
+from typing_extensions import Self
+
 from pydantic import BaseModel, ConfigDict, Field, create_model, field_validator, model_validator, validator
 from pydantic.dataclasses import dataclass
 
@@ -292,7 +294,11 @@ class MyModel(BaseModel):
 
         @model_validator(mode='before')
         @classmethod
-        def validate_values(cls, values: Any) -> Any:
+        def validate_before(cls, values: Any) -> Any:
             return values
 
+        @model_validator(mode='after')
+        def validate_after(self) -> Self:
+            return self
+
     MyModel(number=2)
diff --git a/tests/mypy/outputs/1.0.1/pyproject-plugin_toml/plugin_success.py b/tests/mypy/outputs/1.0.1/pyproject-plugin_toml/plugin_success.py
--- a/tests/mypy/outputs/1.0.1/pyproject-plugin_toml/plugin_success.py
+++ b/tests/mypy/outputs/1.0.1/pyproject-plugin_toml/plugin_success.py
@@ -1,5 +1,7 @@
 from typing import Any, ClassVar, Generic, List, Optional, TypeVar, Union
 
+from typing_extensions import Self
+
 from pydantic import BaseModel, ConfigDict, Field, create_model, field_validator, model_validator, validator
 from pydantic.dataclasses import dataclass
 
@@ -290,7 +292,11 @@ class MyModel(BaseModel):
 
         @model_validator(mode='before')
         @classmethod
-        def validate_values(cls, values: Any) -> Any:
+        def validate_before(cls, values: Any) -> Any:
             return values
 
+        @model_validator(mode='after')
+        def validate_after(self) -> Self:
+            return self
+
     MyModel(number=2)
diff --git a/tests/mypy/outputs/1.1.1/mypy-default_ini/plugin_success.py b/tests/mypy/outputs/1.1.1/mypy-default_ini/plugin_success.py
--- a/tests/mypy/outputs/1.1.1/mypy-default_ini/plugin_success.py
+++ b/tests/mypy/outputs/1.1.1/mypy-default_ini/plugin_success.py
@@ -1,5 +1,7 @@
 from typing import Any, ClassVar, Generic, List, Optional, TypeVar, Union
 
+from typing_extensions import Self
+
 from pydantic import BaseModel, ConfigDict, Field, create_model, field_validator, model_validator, validator
 from pydantic.dataclasses import dataclass
 
@@ -297,7 +299,11 @@ class MyModel(BaseModel):
 
         @model_validator(mode='before')
         @classmethod
-        def validate_values(cls, values: Any) -> Any:
+        def validate_before(cls, values: Any) -> Any:
             return values
 
+        @model_validator(mode='after')
+        def validate_after(self) -> Self:
+            return self
+
     MyModel(number=2)
diff --git a/tests/mypy/outputs/1.2.0/mypy-default_ini/plugin_success.py b/tests/mypy/outputs/1.2.0/mypy-default_ini/plugin_success.py
--- a/tests/mypy/outputs/1.2.0/mypy-default_ini/plugin_success.py
+++ b/tests/mypy/outputs/1.2.0/mypy-default_ini/plugin_success.py
@@ -1,5 +1,7 @@
 from typing import Any, ClassVar, Generic, List, Optional, TypeVar, Union
 
+from typing_extensions import Self
+
 from pydantic import BaseModel, ConfigDict, Field, create_model, field_validator, model_validator, validator
 from pydantic.dataclasses import dataclass
 
@@ -295,7 +297,11 @@ class MyModel(BaseModel):
 
         @model_validator(mode='before')
         @classmethod
-        def validate_values(cls, values: Any) -> Any:
+        def validate_before(cls, values: Any) -> Any:
             return values
 
+        @model_validator(mode='after')
+        def validate_after(self) -> Self:
+            return self
+
     MyModel(number=2)
diff --git a/tests/mypy/outputs/1.4.1/mypy-default_ini/plugin_success.py b/tests/mypy/outputs/1.4.1/mypy-default_ini/plugin_success.py
--- a/tests/mypy/outputs/1.4.1/mypy-default_ini/plugin_success.py
+++ b/tests/mypy/outputs/1.4.1/mypy-default_ini/plugin_success.py
@@ -1,5 +1,7 @@
 from typing import Any, ClassVar, Generic, List, Optional, TypeVar, Union
 
+from typing_extensions import Self
+
 from pydantic import BaseModel, ConfigDict, Field, create_model, field_validator, model_validator, validator
 from pydantic.dataclasses import dataclass
 
@@ -295,7 +297,11 @@ class MyModel(BaseModel):
 
         @model_validator(mode='before')
         @classmethod
-        def validate_values(cls, values: Any) -> Any:
+        def validate_before(cls, values: Any) -> Any:
             return values
 
+        @model_validator(mode='after')
+        def validate_after(self) -> Self:
+            return self
+
     MyModel(number=2)