Skip to content

Commit 20b60b5

Browse files
syastrovilevkivskyi
authored andcommitted
Invoke get_dynamic_class_hook on method calls (python#7990)
Fixes python#7266 Note: a RefExpr (including MemberExpr) with a non-None full name should always take precedence.
1 parent fffb4b4 commit 20b60b5

File tree

3 files changed

+66
-3
lines changed

3 files changed

+66
-3
lines changed

mypy/semanal.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2194,9 +2194,15 @@ def apply_dynamic_class_hook(self, s: AssignmentStmt) -> None:
21942194
if not isinstance(lval, NameExpr) or not isinstance(s.rvalue, CallExpr):
21952195
return
21962196
call = s.rvalue
2197-
if not isinstance(call.callee, RefExpr):
2198-
return
2199-
fname = call.callee.fullname
2197+
fname = None
2198+
if isinstance(call.callee, RefExpr):
2199+
fname = call.callee.fullname
2200+
# check if method call
2201+
if fname is None and isinstance(call.callee, MemberExpr):
2202+
callee_expr = call.callee.expr
2203+
if isinstance(callee_expr, RefExpr) and callee_expr.fullname:
2204+
method_name = call.callee.name
2205+
fname = callee_expr.fullname + '.' + method_name
22002206
if fname:
22012207
hook = self.plugin.get_dynamic_class_hook(fname)
22022208
if hook:

test-data/unit/check-custom-plugin.test

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,35 @@ class Instr(Generic[T]): ...
516516
\[mypy]
517517
plugins=<ROOT>/test-data/unit/plugins/dyn_class.py
518518

519+
[case testDynamicClassHookFromClassMethod]
520+
# flags: --config-file tmp/mypy.ini
521+
522+
from mod import QuerySet, Manager
523+
524+
MyManager = Manager.from_queryset(QuerySet)
525+
526+
reveal_type(MyManager()) # N: Revealed type is '__main__.MyManager'
527+
reveal_type(MyManager().attr) # N: Revealed type is 'builtins.str'
528+
529+
def func(manager: MyManager) -> None:
530+
reveal_type(manager) # N: Revealed type is '__main__.MyManager'
531+
reveal_type(manager.attr) # N: Revealed type is 'builtins.str'
532+
533+
func(MyManager())
534+
535+
[file mod.py]
536+
from typing import Generic, TypeVar, Type
537+
class QuerySet:
538+
attr: str
539+
class Manager:
540+
@classmethod
541+
def from_queryset(cls, queryset_cls: Type[QuerySet]): ...
542+
543+
[builtins fixtures/classmethod.pyi]
544+
[file mypy.ini]
545+
\[mypy]
546+
plugins=<ROOT>/test-data/unit/plugins/dyn_class_from_method.py
547+
519548
[case testBaseClassPluginHookWorksIncremental]
520549
# flags: --config-file tmp/mypy.ini
521550
import a
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from mypy.nodes import (Block, ClassDef, GDEF, SymbolTable, SymbolTableNode, TypeInfo)
2+
from mypy.plugin import DynamicClassDefContext, Plugin
3+
from mypy.types import Instance
4+
5+
6+
class DynPlugin(Plugin):
7+
def get_dynamic_class_hook(self, fullname):
8+
if 'from_queryset' in fullname:
9+
return add_info_hook
10+
return None
11+
12+
13+
def add_info_hook(ctx: DynamicClassDefContext):
14+
class_def = ClassDef(ctx.name, Block([]))
15+
class_def.fullname = ctx.api.qualified_name(ctx.name)
16+
17+
info = TypeInfo(SymbolTable(), class_def, ctx.api.cur_mod_id)
18+
class_def.info = info
19+
queryset_type_fullname = ctx.call.args[0].fullname
20+
queryset_info = ctx.api.lookup_fully_qualified_or_none(queryset_type_fullname).node # type: TypeInfo
21+
obj = ctx.api.builtin_type('builtins.object')
22+
info.mro = [info, queryset_info, obj.type]
23+
info.bases = [Instance(queryset_info, [])]
24+
ctx.api.add_symbol_table_node(ctx.name, SymbolTableNode(GDEF, info))
25+
26+
27+
def plugin(version):
28+
return DynPlugin

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy