diff --git a/mypy/semanal.py b/mypy/semanal.py index ec4d32aefeb9..663a7b010953 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1826,8 +1826,8 @@ def apply_class_plugin_hooks(self, defn: ClassDef) -> None: def get_fullname_for_hook(self, expr: Expression) -> str | None: if isinstance(expr, CallExpr): - return self.get_fullname_for_hook(expr.callee) - elif isinstance(expr, IndexExpr): + expr = expr.callee + if isinstance(expr, IndexExpr): return self.get_fullname_for_hook(expr.base) elif isinstance(expr, RefExpr): if expr.fullname: diff --git a/test-data/unit/check-custom-plugin.test b/test-data/unit/check-custom-plugin.test index 9a0668f98c21..ad04cf7f61f4 100644 --- a/test-data/unit/check-custom-plugin.test +++ b/test-data/unit/check-custom-plugin.test @@ -1029,3 +1029,50 @@ reveal_type(1) # N: Revealed type is "Literal[1]?" [file mypy.ini] \[mypy] plugins=/test-data/unit/plugins/custom_errorcode.py + +[case testClassDecoratorHook2] +# flags: --config-file tmp/mypy.ini +from typing import Any, TypeVar +T = TypeVar('T') + +def my_decorator(t: Any) -> Any: + raise + +@my_decorator +class C: + pass + +obj = C() +reveal_type(obj.magic) # N: Revealed type is "builtins.str" +[file mypy.ini] +\[mypy] +plugins=/test-data/unit/plugins/class_decorator_hook_2.py + +[typing fixtures/typing-medium.pyi] + +[case testClassDecoratorHook2_incorrect] +# flags: --config-file tmp/mypy.ini +from typing import Any, TypeVar +T = TypeVar('T') + +def my_decorator(t: Any) -> Any: + raise + +@my_decorator()() +class C: + pass + +obj = C() +_ = obj.magic +[out] +main:8: error: invalid syntax + +[out version>=3.9] +main:8: error: Missing positional argument "t" in call to "my_decorator" +main:13: error: "C" has no attribute "magic" + +[file mypy.ini] +\[mypy] +plugins=/test-data/unit/plugins/class_decorator_hook_2.py + +[typing fixtures/typing-medium.pyi] diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test index 8a50e7124d05..9d656af017c4 100644 --- a/test-data/unit/check-dataclasses.test +++ b/test-data/unit/check-dataclasses.test @@ -2515,3 +2515,22 @@ a: MyDataclass b = [a, a] # trigger joining the types [builtins fixtures/dataclasses.pyi] + +[case testDataclassesInvalidDecorator] +from dataclasses import dataclass + +@dataclass()() +class C: + a: str + +reveal_type(C) + +[out] +main:3: error: invalid syntax + +[out version>=3.9] +main:3: error: Too few arguments +main:4: error: Cannot instantiate type "Type[Never]" +main:7: note: Revealed type is "def () -> __main__.C" + +[builtins fixtures/dataclasses.pyi] diff --git a/test-data/unit/plugins/class_decorator_hook_2.py b/test-data/unit/plugins/class_decorator_hook_2.py new file mode 100644 index 000000000000..ceeb83802443 --- /dev/null +++ b/test-data/unit/plugins/class_decorator_hook_2.py @@ -0,0 +1,22 @@ +from __future__ import annotations + +from typing import Callable + +from mypy.plugin import ClassDefContext, Plugin +from mypy.plugins.common import add_attribute_to_class + + +class MyPlugin(Plugin): + def get_class_decorator_hook_2(self, fullname: str) -> Callable[[ClassDefContext], bool] | None: + if fullname == "__main__.my_decorator": + return transform_hook + return None + + +def transform_hook(ctx: ClassDefContext) -> bool: + add_attribute_to_class(ctx.api, ctx.cls, 'magic', ctx.api.named_type('builtins.str')) + return True + + +def plugin(version: str) -> type[MyPlugin]: + return MyPlugin