Repository: typeddjango/django-stubs

Issue:
AssertionError when using models.Manager
# Bug report

<!--
Hi, thanks for submitting a bug. We appreciate that.

But, we will need some information about what's wrong to help you.
-->

## What's wrong
I can't seem to create a minimal example demonstrating this, but I'm getting this on one of my projects.

If I change a custom queryset to become a custom manager by changing what it inherits from and changing `objects = CustomQuerySet.as_manager()` to `objects = CustomManager()` I get the following.

```
Traceback (most recent call last):
  File "c:\users\dusti\.pyenv\pyenv-win\versions\3.8.0-amd64\lib\runpy.py", line 192, in _run_module_as_main
    return _run_code(code, main_globals, None,
    exec(code, run_globals)
  File "mypy\semanal.py", line 4679, in accept
  File "mypy\nodes.py", line 939, in accept
  File "mypy\semanal.py", line 1001, in visit_class_def
  File "mypy\semanal.py", line 1078, in analyze_class
  File "mypy\semanal.py", line 1087, in analyze_class_body_common
  File "mypy\semanal.py", line 1147, in apply_class_plugin_hooks
  File "c:\users\dusti\appdata\local\pypoetry\cache\virtualenvs\vehicles-caet0sxz-py3.8\lib\site-packages\mypy_django_plugin\main.py", line 37, in transform_model_class
    process_model_class(ctx, django_context)
  File "c:\users\dusti\appdata\local\pypoetry\cache\virtualenvs\vehicles-caet0sxz-py3.8\lib\site-packages\mypy_django_plugin\transformers\models.py", line 357, in process_model_class
    initializer_cls(ctx, django_context).run()
  File "c:\users\dusti\appdata\local\pypoetry\cache\virtualenvs\vehicles-caet0sxz-py3.8\lib\site-packages\mypy_django_plugin\transformers\models.py", line 71, in run
    self.run_with_model_cls(model_cls)
  File "c:\users\dusti\appdata\local\pypoetry\cache\virtualenvs\vehicles-caet0sxz-py3.8\lib\site-packages\mypy_django_plugin\transformers\models.py", line 230, in run_with_model_cls
    custom_manager_type = self.create_new_model_parametrized_manager(custom_model_manager_name,
  File "c:\users\dusti\appdata\local\pypoetry\cache\virtualenvs\vehicles-caet0sxz-py3.8\lib\site-packages\mypy_django_plugin\transformers\models.py", line 183, in create_new_model_parametrized_manager
    helpers.copy_method_to_another_class(new_cls_def_context,
  File "c:\users\dusti\appdata\local\pypoetry\cache\virtualenvs\vehicles-caet0sxz-py3.8\lib\site-packages\mypy_django_plugin\lib\helpers.py", line 346, in copy_method_to_another_class
    assert bound_return_type is not None
AssertionError:
```

- OS: Win10
- `python` version: 3.8
- `django` version: 3.0.6
- `mypy` version: .770
- `django-stubs` version: 1.5.0



Discussion:
I'm running into this bug too, under the same circumstances: changed a `QuerySet` to a `Manager`
Yep, I'm getting the same issue here.

`Python` 3.6.8
`Django` 2.2.3
`mypy` 0.770
`django-stubs` 1.5.0
@dmwyatt @davepeck @adithyabsk Could some of you guys support me with an example code causing the bug occur, preferably together with Mypy config file and the command you're using? I'm trying to reproduce this bug but with no success so far.
Or even with a reproduction repo 😉 
I've experienced a similar traceback. I've created a reproduction repo: https://github.com/danifus/djstubs-reproduce

Steps:
- `cd` to the directory containing `mypy.ini`
- run `mypy . --tb`

Gives me:
```
./test_app/models.py:10: error: INTERNAL ERROR -- Please try using mypy master on Github:
https://mypy.rtfd.io/en/latest/common_issues.html#using-a-development-mypy-build
Please report a bug at https://github.com/python/mypy/issues
version: 0.770
Traceback (most recent call last):
  File "mypy/semanal.py", line 4679, in accept
  File "mypy/nodes.py", line 939, in accept
  File "mypy/semanal.py", line 1001, in visit_class_def
  File "mypy/semanal.py", line 1078, in analyze_class
  File "mypy/semanal.py", line 1087, in analyze_class_body_common
  File "mypy/semanal.py", line 1147, in apply_class_plugin_hooks
  File "<venv_dir>/mypytest/lib/python3.7/site-packages/mypy_django_plugin/main.py", line 37, in transform_model_class
    process_model_class(ctx, django_context)
  File "<venv_dir>/mypytest/lib/python3.7/site-packages/mypy_django_plugin/transformers/models.py", line 357, in process_model_class
    initializer_cls(ctx, django_context).run()
  File "<venv_dir>/mypytest/lib/python3.7/site-packages/mypy_django_plugin/transformers/models.py", line 71, in run
    self.run_with_model_cls(model_cls)
  File "<venv_dir>/mypytest/lib/python3.7/site-packages/mypy_django_plugin/transformers/models.py", line 231, in run_with_model_cls
    base_manager_info=manager_info)
  File "<venv_dir>mypytest/lib/python3.7/site-packages/mypy_django_plugin/transformers/models.py", line 186, in create_new_model_parametrized_manager
    method_node=sym.node)
  File "<venv_dir>/mypytest/lib/python3.7/site-packages/mypy_django_plugin/lib/helpers.py", line 355, in copy_method_to_another_class
    assert bound_arg_type is not None
AssertionError:
./test_app/models.py:10: : note: use --pdb to drop into pdb
```

Let me know if you need anything else to help this along.
Not sure if it is the minimal reproduction repo but it resembles the structure that triggered this for me
@danifus Thanks, getting to work
@davepeck @danifus @adithyabsk @dmwyatt 

Changing 

```
class ManagerA(models.Manager):

    def do_something(self, other_obj: "ModelB"):
        return None


class ModelA(models.Model):
    title = models.TextField()

    objects = ManagerA()


class ModelB(models.Model):
    movie = models.TextField()
```
to

```
class ModelB(models.Model):
    movie = models.TextField()

class ManagerA(models.Manager):

    def do_something(self, other_obj: "ModelB"):
        return None


class ModelA(models.Model):
    title = models.TextField()

    objects = ManagerA()
```

helped 😄 You have to specify a class before using it as type hint. Let me know if it works for you.
@kszmigiel can this be solved using `.defer()`? We need to be sure that the order is not important.
Otherwise, we would have a lot of bug reports like this one.
@kszmigiel That's the workaround I'm currently using but as it is a [forward reference](https://mypy.readthedocs.io/en/stable/kinds_of_types.html#class-name-forward-references) to `ModelB` in `def do_something(self, other_obj: "ModelB"):`, `ModelB` should be able to be defined after it is used as a mypy type.
Looks to be a little related to: https://github.com/typeddjango/django-stubs/issues/344

They mention that `mypy==0.750` and `django-stubs==1.3.2` worked for their deferred types. The same appears to be true here, this bug is not triggered in the reproduction repo with those versions.
Git bisect points to https://github.com/typeddjango/django-stubs/commit/1196336e3b238a67115548bda805465bd37ece93 as the commit that introduced this issue.


@danifus thanks a lot for the crime scene investigation! 🚓 

My wild guess that https://github.com/python/mypy/issues/7071 can possibly solve this issue. 

Resolving patch:
diff --git a/mypy_django_plugin/lib/helpers.py b/mypy_django_plugin/lib/helpers.py
--- a/mypy_django_plugin/lib/helpers.py
+++ b/mypy_django_plugin/lib/helpers.py
@@ -343,6 +343,7 @@ def copy_method_to_another_class(ctx: ClassDefContext, self_type: Instance,
     arguments = []
     bound_return_type = semanal_api.anal_type(method_type.ret_type,
                                               allow_placeholder=True)
+
     assert bound_return_type is not None
 
     if isinstance(bound_return_type, PlaceholderNode):
@@ -352,6 +353,10 @@ def copy_method_to_another_class(ctx: ClassDefContext, self_type: Instance,
                                                      method_type.arg_types[1:],
                                                      method_node.arguments[1:]):
         bound_arg_type = semanal_api.anal_type(arg_type, allow_placeholder=True)
+        if bound_arg_type is None and not semanal_api.final_iteration:
+            semanal_api.defer()
+            return
+
         assert bound_arg_type is not None
 
         if isinstance(bound_arg_type, PlaceholderNode):


Test patch:
diff --git a/test-data/typecheck/managers/test_managers.yml b/test-data/typecheck/managers/test_managers.yml
--- a/test-data/typecheck/managers/test_managers.yml
+++ b/test-data/typecheck/managers/test_managers.yml
@@ -335,3 +335,25 @@
                     objects = MyManager()
                 class ChildUser(models.Model):
                     objects = MyManager()
+
+-   case: custom_manager_annotate_method_before_type_declaration 
+    main: |
+        from myapp.models import ModelA, ModelB, ManagerA
+        reveal_type(ModelA.objects)  # N: Revealed type is 'myapp.models.ModelA_ManagerA1[myapp.models.ModelA]'
+        reveal_type(ModelA.objects.do_something)  # N: Revealed type is 'def (other_obj: myapp.models.ModelB) -> builtins.str'
+    installed_apps:
+        - myapp
+    files:
+        -   path: myapp/__init__.py
+        -   path: myapp/models.py
+            content: |
+                from django.db import models
+                class ManagerA(models.Manager):
+                    def do_something(self, other_obj: "ModelB") -> str:
+                        return 'test'
+                class ModelA(models.Model):
+                    title = models.TextField()
+                    objects = ManagerA()
+                class ModelB(models.Model):
+                    movie = models.TextField()
+