33from typing import cast
44
55from mypy .checker import TypeChecker
6- from mypy .nodes import CallExpr , Decorator , Expression , MypyFile
6+ from mypy .nodes import CallExpr , Decorator , Expression , MemberExpr , MypyFile
77from mypy .options import Options
8- from mypy .plugin import (
9- FunctionContext ,
10- MethodContext ,
11- Plugin ,
12- )
13- from mypy .types import (
14- CallableType ,
15- LiteralType ,
16- Type ,
17- )
8+ from mypy .plugin import AttributeContext , FunctionContext , MethodContext , Plugin
9+ from mypy .types import CallableType , Type
1810
1911from .defer import DeferralError
2012from .excluded_test_checker import ExcludedTestChecker
2113from .fixture import Fixture
2214from .fixture_manager import FixtureManager
2315from .fullname import Fullname
2416from .iterable_sequence_checker import IterableSequenceChecker
17+ from .mark_checker import MarkChecker
2518from .mock_call_checker import FunctionMockCallChecker , MethodMockCallChecker
2619from .test_body_ranges import TestBodyRanges
2720from .test_info import TestInfo
@@ -65,6 +58,23 @@ def module_to_dep(cls, module: str | Fullname) -> tuple[int, str, int]:
6558 module = str (module )
6659 return (10 , module , - 1 )
6760
61+ def get_attribute_hook (self , fullname : str ) -> Callable [[AttributeContext ], Type ] | None :
62+ if fullname .startswith ("_pytest.mark.structures.MarkGenerator" ):
63+ return self .check_mark
64+ return None
65+
66+ @classmethod
67+ def check_mark (cls , ctx : AttributeContext ) -> Type :
68+ if ctx .api .path == "test_samples/mark_test.py" :
69+ ...
70+ if (
71+ not ctx .is_lvalue
72+ and isinstance (checker := ctx .api , TypeChecker )
73+ and isinstance (expr := ctx .context , MemberExpr )
74+ ):
75+ MarkChecker (checker ).check_attribute (expr )
76+ return ctx .default_attr_type
77+
6878 def get_function_hook (self , fullname : str ) -> Callable [[FunctionContext ], Type ] | None :
6979 if fullname .startswith ("unittest.mock" ):
7080 return functools .partial (FunctionMockCallChecker .check_mock_calls , fullname = fullname )
@@ -126,7 +136,7 @@ def _check_pytest_structure(cls, ctx: MethodContext | FunctionContext) -> Type:
126136 cls ._update_return_type (ctx .default_return_type , ctx .api )
127137 if not Fixture .is_fixture_and_mark (ctx .context , checker = ctx .api ):
128138 if fixture := Fixture .from_decorator (ctx .context , checker = ctx .api ):
129- return cls . _fixture_type ( fixture , decorator = ctx .context , checker = ctx .api )
139+ return fixture . as_fixture_type ( decorator = ctx .context , checker = ctx .api )
130140 ignored_testnames = ExcludedTestChecker .ignored_test_names (
131141 ctx .api .tree .defs , ctx .api
132142 )
@@ -136,27 +146,6 @@ def _check_pytest_structure(cls, ctx: MethodContext | FunctionContext) -> Type:
136146 cls ._check_decorators (ctx .context , ctx .api )
137147 return ctx .default_return_type
138148
139- @classmethod
140- def _check_decorators (cls , node : Decorator , checker : TypeChecker ) -> None :
141- test_info = TestInfo .from_fn_def (node , checker = checker )
142- if test_info is not None :
143- test_info .check ()
144-
145- @classmethod
146- def _fixture_type (cls , fixture : Fixture , * , decorator : Decorator , checker : TypeChecker ) -> Type :
147- assert decorator .func .type is not None
148- return checker .named_generic_type (
149- f"{ TYPES_MODULE } .FixtureType" ,
150- [
151- LiteralType (fixture .scope , fallback = checker .named_type ("builtins.object" )),
152- decorator .func .type ,
153- LiteralType (
154- decorator .func .is_generator , fallback = checker .named_type ("builtins.object" )
155- ),
156- LiteralType (decorator .fullname , fallback = checker .named_type ("builtins.object" )),
157- ],
158- )
159-
160149 @classmethod
161150 def _update_return_type (cls , return_type : Type , checker : TypeChecker ) -> None :
162151 if (
@@ -165,6 +154,12 @@ def _update_return_type(cls, return_type: Type, checker: TypeChecker) -> None:
165154 ):
166155 return_type .fallback = checker .named_type (f"{ TYPES_MODULE } .Testable" )
167156
157+ @classmethod
158+ def _check_decorators (cls , node : Decorator , checker : TypeChecker ) -> None :
159+ test_info = TestInfo .from_fn_def (node , checker = checker )
160+ if test_info is not None :
161+ test_info .check ()
162+
168163
169164def plugin (version : str ) -> type [PytestPlugin ]:
170165 return PytestPlugin
0 commit comments