Repository navigation
Description
What happens
A class decorated with a decorator whose return annotation is Any (or that is untyped) comes out of the parser with an unknown class type and no attributed bases. The same class without the decorator, or with a decorator typed (cls: T) -> T, attributes normally.
Minimal project (django, djangorestframework provisioned so APIView resolves):
from typing import Any, TypeVar
from rest_framework.views import APIView
T = TypeVar("T")
def any_deco(cls: Any = None) -> Any:
return cls
def typed_deco(cls: T) -> T:
return cls
class PlainView(APIView):
def get(self, request): ...
@any_deco
class AnyDecoView(APIView):
def get(self, request): ...
@typed_deco
class TypedDecoView(APIView):
def get(self, request): ...
class Base(APIView):
pass
@any_deco
class AnyDecoChild(Base):
def post(self, request): ...org.openrewrite.java.search.FindClassHierarchy over the resulting LST:
| className | superclass |
|---|---|
| probe.views.PlainView | rest_framework.views.APIView |
<unknown> |
|
| probe.views.TypedDecoView | rest_framework.views.APIView |
| probe.views.Base | rest_framework.views.APIView |
<unknown> |
The two <unknown> rows are AnyDecoView and AnyDecoChild. Any recipe that asks TypeUtils.isAssignableTo("rest_framework.views.APIView", classDecl.getImplements().get(0).getType()) gets false for them.
Why it matters
This is the dominant shape in real Django code bases. In getsentry/sentry, 192 of 208 endpoint classes under src/sentry/api/endpoints are decorated with @cell_silo_endpoint / @region_silo_endpoint / @control_silo_endpoint, and every one of those decorators is declared (decorated_obj: Any = None, ...) -> Any in src/sentry/api/base.py. Over that package, the class hierarchy table lists 990 class declarations, of which 315 are decorated, and the decorated ones all come back as <unknown> with no supertypes. A recipe looking for APIView subclasses finds the 16 undecorated base classes and misses the endpoints.
Where it likely is
visit_ClassDef in _parser_visitor.py sets the declaration's type from self._type_mapping.type(node) and each base from __convert_type(base). ty infers the bound name of a decorated class as the decorator's return type, so when that is Any both the class type and, apparently, the base expressions' types are lost. The class definition itself still has a class literal type in ty regardless of what the decorator returns; attributing the ClassDef (and its bases) from the definition rather than from the bound symbol would keep AnyDecoView typed as probe.views.AnyDecoView with APIView as its supertype, the way the undecorated declaration is.
Repro built with rewrite-python from the openrewrite PyPI package 8.91.4 and ty-types 0.0.78.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsNo status
Activity