django-stubs icon indicating copy to clipboard operation
django-stubs copied to clipboard

WithAnnotations using a bound TypeVar causes MyPy to crash

Open yrd opened this issue 3 years ago • 2 comments

Bug report

MyPy seems to fail with a recursion error when WithAnnotations is used with a generic type variable as an argument. Here is a small example:

from typing import TypedDict, TypeVar

from django.db import models
from django_stubs_ext import WithAnnotations


class DoubleAnnotations(TypedDict):
    double: int


_Content = TypeVar("_Content", bound="Record", covariant=True)


class RecordManager(models.Manager):
    @staticmethod
    def with_double(
        queryset: models.QuerySet[_Content],
    ) -> models.QuerySet[WithAnnotations[_Content, DoubleAnnotations]]:
        return queryset.annotate(double=models.F("value") * 2)


class Record(models.Model):
    value = models.IntegerField()

    objects = RecordManager()

The idea is that the manager provides a static method which annotates an existing queryset in some way (in my actual project, the annotation is way larger, hence it is refactored into a reusable method). But this should also work for querysets on subclasses of the model, hence the generic. Once a generic is involved, MyPy seems to crash:

Traceback
...
  File "mypy/typeanal.py", line 887, in analyze_type
  File "mypy/types.py", line 535, in accept
  File "mypy/typeanal.py", line 164, in visit_unbound_type
  File "mypy/typeanal.py", line 200, in visit_unbound_type_nonoptional
  File "/nix/store/jxhqaq4crx0mhp1mgaqq9w1n393sbr8r-python3-3.9.6-env/lib/python3.9/site-packages/mypy_django_plugin/transformers/models.py", line 403, in handle_annotated_type
    return ctx.api.analyze_type(ctx.type)
  File "mypy/typeanal.py", line 887, in analyze_type
  File "mypy/types.py", line 535, in accept
  File "mypy/typeanal.py", line 164, in visit_unbound_type
  File "mypy/typeanal.py", line 200, in visit_unbound_type_nonoptional
  File "/nix/store/jxhqaq4crx0mhp1mgaqq9w1n393sbr8r-python3-3.9.6-env/lib/python3.9/site-packages/mypy_django_plugin/transformers/models.py", line 403, in handle_annotated_type
    return ctx.api.analyze_type(ctx.type)
  File "mypy/typeanal.py", line 887, in analyze_type
  File "mypy/types.py", line 535, in accept
  File "mypy/typeanal.py", line 164, in visit_unbound_type
  File "mypy/typeanal.py", line 200, in visit_unbound_type_nonoptional
  File "/nix/store/jxhqaq4crx0mhp1mgaqq9w1n393sbr8r-python3-3.9.6-env/lib/python3.9/site-packages/mypy_django_plugin/transformers/models.py", line 403, in handle_annotated_type
    return ctx.api.analyze_type(ctx.type)
  File "mypy/typeanal.py", line 887, in analyze_type
  File "mypy/types.py", line 535, in accept
  File "mypy/typeanal.py", line 164, in visit_unbound_type
  File "mypy/typeanal.py", line 200, in visit_unbound_type_nonoptional
  File "/nix/store/jxhqaq4crx0mhp1mgaqq9w1n393sbr8r-python3-3.9.6-env/lib/python3.9/site-packages/mypy_django_plugin/transformers/models.py", line 403, in handle_annotated_type
    return ctx.api.analyze_type(ctx.type)
  File "mypy/typeanal.py", line 887, in analyze_type
  File "mypy/types.py", line 535, in accept
  File "mypy/typeanal.py", line 164, in visit_unbound_type
  File "mypy/typeanal.py", line 200, in visit_unbound_type_nonoptional
  File "/nix/store/jxhqaq4crx0mhp1mgaqq9w1n393sbr8r-python3-3.9.6-env/lib/python3.9/site-packages/mypy_django_plugin/transformers/models.py", line 403, in handle_annotated_type
    return ctx.api.analyze_type(ctx.type)
  File "mypy/typeanal.py", line 887, in analyze_type
  File "mypy/types.py", line 535, in accept
  File "mypy/typeanal.py", line 164, in visit_unbound_type
  File "mypy/typeanal.py", line 200, in visit_unbound_type_nonoptional
  File "/nix/store/jxhqaq4crx0mhp1mgaqq9w1n393sbr8r-python3-3.9.6-env/lib/python3.9/site-packages/mypy_django_plugin/transformers/models.py", line 399, in handle_annotated_type
    type_arg = ctx.api.analyze_type(args[0])
  File "mypy/typeanal.py", line 887, in analyze_type
  File "mypy/types.py", line 535, in accept
  File "mypy/typeanal.py", line 164, in visit_unbound_type
  File "mypy/typeanal.py", line 198, in visit_unbound_type_nonoptional
  File "mypy/plugin.py", line 762, in get_type_analyze_hook
  File "mypy/plugin.py", line 806, in _find_hook
  File "mypy/plugin.py", line 762, in <lambda>
  File "/nix/store/jxhqaq4crx0mhp1mgaqq9w1n393sbr8r-python3-3.9.6-env/lib/python3.9/site-packages/mypy_django_plugin/main.py", line 332, in get_type_analyze_hook
    if fullname in (
RecursionError: maximum recursion depth exceeded in comparison
test.py:15: : note: use --pdb to drop into pdb

System information

  • OS: NixOS
  • python version: 3.9.6
  • django version: 3.2.9
  • mypy version: 0.920+dev
  • django-stubs version: 1.9.0
  • django-stubs-ext version: 1.9.0

yrd avatar Dec 11 '21 09:12 yrd

I am badly blocked by this issue (how WithAnnotations doesn't play nice with TypeVars and creating custom Managers/QuerySets etc). WithAnnotations is a time-losing honeypot at the moment

felixmeziere avatar Jul 05 '22 20:07 felixmeziere

See https://github.com/typeddjango/django-stubs/issues/1046 and https://github.com/typeddjango/django-stubs/issues/1049

felixmeziere avatar Jul 05 '22 20:07 felixmeziere