@@ -10,7 +10,6 @@ use std::sync::Arc;
1010
1111use itertools:: Itertools ;
1212use pyrefly_python:: dunder;
13- use pyrefly_python:: module_name:: ModuleName ;
1413use pyrefly_python:: module_path:: ModuleStyle ;
1514use pyrefly_types:: dimension:: ShapeError ;
1615use pyrefly_types:: function:: BodyKind ;
@@ -29,7 +28,6 @@ use pyrefly_util::visit::Visit;
2928use ruff_python_ast:: Arguments ;
3029use ruff_python_ast:: Expr ;
3130use ruff_python_ast:: ExprCall ;
32- use ruff_python_ast:: ExprStringLiteral ;
3331use ruff_python_ast:: name:: Name ;
3432use ruff_text_size:: Ranged ;
3533use ruff_text_size:: TextRange ;
@@ -75,7 +73,6 @@ use crate::types::function::FunctionKind;
7573use crate :: types:: keywords:: KwCall ;
7674use crate :: types:: keywords:: TypeMap ;
7775use crate :: types:: literal:: Lit ;
78- use crate :: types:: module:: ModuleType ;
7976use crate :: types:: type_var:: Restriction ;
8077use crate :: types:: typed_dict:: TypedDict ;
8178use crate :: types:: types:: AnyStyle ;
@@ -404,6 +401,9 @@ impl<'ctx, 'answer, Ans: LookupAnswer> AnswersSolver<'ctx, 'answer, Ans> {
404401
405402 fn as_call_target_impl ( & self , ty : Type , quantified : Option < Quantified > ) -> CallTargetLookup {
406403 match ty {
404+ Type :: UnittestMock ( mock) => {
405+ self . as_call_target_impl ( Type :: ClassType ( mock. class ) , quantified)
406+ }
407407 Type :: Callable ( c) => {
408408 CallTargetLookup :: Ok ( Box :: new ( CallTarget :: Callable ( TargetWithTParams ( None , * c) ) ) )
409409 }
@@ -3109,7 +3109,8 @@ impl<'ctx, 'answer, Ans: LookupAnswer> AnswersSolver<'ctx, 'answer, Ans> {
31093109 )
31103110 } else {
31113111 self . expand_mut ( & mut callee. ty ) ;
3112- self . check_unittest_mock_patch_target ( & callee. ty , & callee. expr . arguments , errors) ;
3112+ let unittest_mock_patch_target =
3113+ self . check_unittest_mock_patch_target ( & callee. ty , & callee. expr . arguments , errors) ;
31133114
31143115 let call = CallWithTypes :: new ( ) ;
31153116 let ( args, kws) = if callee. ty . is_union ( ) {
@@ -3151,6 +3152,8 @@ impl<'ctx, 'answer, Ans: LookupAnswer> AnswersSolver<'ctx, 'answer, Ans> {
31513152 errors,
31523153 ) ;
31533154 match ty. callee_kind ( ) {
3155+ _ if ty. toplevel_func_metadata ( ) . is_some_and ( |metadata| metadata. flags . is_unittest_mock_assertion ) =>
3156+ self . call_unittest_mock_assertion ( ty, & args, & kws, callee. expr , errors) ,
31543157 Some ( CalleeKind :: Function ( FunctionKind :: AssertType ) ) => self
31553158 . call_assert_type (
31563159 & callee. expr . arguments . args ,
@@ -3428,95 +3431,24 @@ impl<'ctx, 'answer, Ans: LookupAnswer> AnswersSolver<'ctx, 'answer, Ans> {
34283431 }
34293432 } } ) ;
34303433 // TypeIs and TypeGuard functions return bool at runtime
3431- match result {
3434+ let result = match result {
34323435 Type :: TypeIs ( _) | Type :: TypeGuard ( _) => {
34333436 self . heap . mk_class_type ( self . stdlib . bool ( ) . clone ( ) )
34343437 }
34353438 other => other,
3439+ } ;
3440+ match unittest_mock_patch_target {
3441+ Some ( target) => {
3442+ self . specialize_unittest_mock_patch ( result, target, & callee. expr . arguments )
3443+ }
3444+ None => result,
34363445 }
34373446 } ;
34383447
34393448 let result = self . apply_django_annotate_call ( result, django_annotate_call) ;
34403449 self . apply_polars_call_specialization ( result, polars_call)
34413450 }
34423451
3443- fn check_unittest_mock_patch_target (
3444- & self ,
3445- callee_ty : & Type ,
3446- arguments : & Arguments ,
3447- errors : & ErrorCollector ,
3448- ) {
3449- let Type :: ClassType ( cls) = callee_ty else {
3450- return ;
3451- } ;
3452- if !cls. has_qname ( "unittest.mock" , "_patcher" ) {
3453- return ;
3454- }
3455- if arguments. args . len ( ) > 1 || arguments. keywords . iter ( ) . any ( |kw| kw. arg . is_none ( ) ) {
3456- return ;
3457- }
3458- if arguments
3459- . find_keyword ( "create" )
3460- . is_some_and ( |kw| !matches ! ( & kw. value, Expr :: BooleanLiteral ( lit) if !lit. value) )
3461- {
3462- return ;
3463- }
3464- let Some ( target_expr) = arguments. find_argument_value ( "target" , 0 ) else {
3465- return ;
3466- } ;
3467- let Expr :: StringLiteral ( ExprStringLiteral { value, .. } ) = target_expr else {
3468- return ;
3469- } ;
3470- let range = target_expr. range ( ) ;
3471- let target = value. to_str ( ) ;
3472- let parts: Vec < & str > = target. split ( '.' ) . collect ( ) ;
3473- if parts. len ( ) < 2 || parts. iter ( ) . any ( |p| p. is_empty ( ) ) {
3474- return ;
3475- }
3476-
3477- let Some ( ( module_prefix_len, module) ) = ( 1 ..parts. len ( ) ) . rev ( ) . find_map ( |i| {
3478- let candidate = ModuleName :: from_str ( & parts[ ..i] . join ( "." ) ) ;
3479- ( candidate == self . module ( ) . name ( )
3480- || self . exports . module_exists ( candidate) . finding ( ) . is_some ( ) )
3481- . then_some ( ( i, candidate) )
3482- } ) else {
3483- // The target may be resolved dynamically at runtime, so only check paths rooted in a
3484- // module that is available in the current environment.
3485- return ;
3486- } ;
3487-
3488- let attrs = & parts[ module_prefix_len..] ;
3489- let mut base_ty = ModuleType :: new_as ( module) . to_type ( self . heap ) ;
3490- for ( i, attr) in attrs. iter ( ) . enumerate ( ) {
3491- let name = Name :: new ( * attr) ;
3492- // Patching a public builtin name on a module always succeeds: `mock.patch` sets
3493- // `create=True` itself in that case, overriding whatever the caller passed. So
3494- // `patch("mod.open")` is legal even when `mod` defines no `open` of its own.
3495- // Documented at
3496- // https://docs.python.org/3/library/unittest.mock.html#unittest.mock.patch ("If you
3497- // are patching builtins in a module then you don't need to pass `create=True`").
3498- if i + 1 == attrs. len ( )
3499- && base_ty. as_module ( ) . is_some ( )
3500- && !attr. starts_with ( '_' )
3501- && self . exports . export_exists ( ModuleName :: builtins ( ) , & name)
3502- && !self
3503- . exports
3504- . is_implicit_reexport ( ModuleName :: builtins ( ) , & name)
3505- {
3506- return ;
3507- }
3508- base_ty = self . type_of_attr_get (
3509- & base_ty,
3510- & name,
3511- range,
3512- errors,
3513- ErrorKind :: MissingAttributePatchTarget ,
3514- None ,
3515- "unittest.mock.patch target" ,
3516- ) ;
3517- }
3518- }
3519-
35203452 fn call_inspect_unwrap ( & self , x : & ExprCall , errors : & ErrorCollector ) -> Option < Type > {
35213453 if x. arguments . args . len ( ) != 1 || matches ! ( x. arguments. args[ 0 ] , Expr :: Starred ( _) ) {
35223454 return None ;
@@ -3591,7 +3523,6 @@ impl<'ctx, 'answer, Ans: LookupAnswer> AnswersSolver<'ctx, 'answer, Ans> {
35913523 result
35923524 } )
35933525 }
3594-
35953526 pub fn freeform_call_infer (
35963527 & self ,
35973528 ty : Type ,
0 commit comments