3333from mypy .maptype import map_instance_to_supertype
3434from mypy .meet import is_overlapping_types , narrow_declared_type
3535from mypy .message_registry import ErrorMessage
36- from mypy .messages import MessageBuilder , format_type , format_type_distinctly
36+ from mypy .messages import MessageBuilder , callable_name , format_type
3737from mypy .nodes import (
3838 ARG_NAMED ,
3939 ARG_POS ,
@@ -1798,17 +1798,29 @@ def check_callable_call(
17981798
17991799 arg_types = self .infer_arg_types_in_context (callee , args , arg_kinds , formal_to_actual )
18001800
1801- self .check_call_arguments (
1802- callee ,
1803- arg_types ,
1804- arg_kinds ,
1805- arg_names ,
1806- args ,
1807- formal_to_actual ,
1808- context ,
1809- callable_name ,
1810- object_type ,
1811- )
1801+ if not self ._detect_missing_positional_arg (
1802+ callee , arg_types , arg_kinds , args , context
1803+ ):
1804+ self .check_argument_count (
1805+ callee ,
1806+ arg_types ,
1807+ arg_kinds ,
1808+ arg_names ,
1809+ formal_to_actual ,
1810+ context ,
1811+ object_type ,
1812+ callable_name ,
1813+ )
1814+
1815+ self .check_argument_types (
1816+ arg_types ,
1817+ arg_kinds ,
1818+ args ,
1819+ callee ,
1820+ formal_to_actual ,
1821+ context ,
1822+ object_type = object_type ,
1823+ )
18121824
18131825 if (
18141826 callee .is_type_obj ()
@@ -2337,232 +2349,51 @@ def apply_inferred_arguments(
23372349 # arguments.
23382350 return self .apply_generic_arguments (callee_type , inferred_args , context )
23392351
2340- def check_call_arguments (
2352+ def _detect_missing_positional_arg (
23412353 self ,
23422354 callee : CallableType ,
23432355 arg_types : list [Type ],
23442356 arg_kinds : list [ArgKind ],
2345- arg_names : Sequence [str | None ] | None ,
23462357 args : list [Expression ],
2347- formal_to_actual : list [list [int ]],
23482358 context : Context ,
2349- callable_name : str | None ,
2350- object_type : Type | None ,
2351- ) -> None :
2352- """Check argument count and types, consolidating errors for missing positional args."""
2353- with self .msg .filter_errors ():
2354- _ , missing_positional = self .check_argument_count (
2355- callee ,
2356- arg_types ,
2357- arg_kinds ,
2358- arg_names ,
2359- formal_to_actual ,
2360- context ,
2361- object_type ,
2362- callable_name ,
2363- )
2364-
2365- if missing_positional :
2366- func_name = callable_name or callee .name or "function"
2367- if "." in func_name :
2368- func_name = func_name .split ("." )[- 1 ]
2369-
2370- shift_info = None
2371- num_positional_args = sum (1 for k in arg_kinds if k == nodes .ARG_POS )
2372- if num_positional_args >= 2 :
2373- shift_info = self .detect_shifted_positional_args (
2374- callee , arg_types , arg_kinds , missing_positional
2375- )
2376-
2377- with self .msg .filter_errors () as type_error_watcher :
2378- self .check_argument_types (
2379- arg_types ,
2380- arg_kinds ,
2381- args ,
2382- callee ,
2383- formal_to_actual ,
2384- context ,
2385- object_type = object_type ,
2386- )
2387- has_type_errors = type_error_watcher .has_new_errors ()
2388-
2389- if shift_info is not None :
2390- shift_position , param_name , expected_type , high_confidence = shift_info
2391- if high_confidence and param_name :
2392- positional_arg_types = [
2393- arg_types [i ] for i , k in enumerate (arg_kinds ) if k == nodes .ARG_POS
2394- ]
2395- actual_type = positional_arg_types [shift_position - 1 ]
2396- actual_str , expected_str = format_type_distinctly (
2397- actual_type , expected_type , options = self .chk .options
2398- )
2399- self .msg .fail (
2400- f'Argument { shift_position } to "{ func_name } " has incompatible type '
2401- f"{ actual_str } ; expected { expected_str } " ,
2402- context ,
2403- code = codes .CALL_ARG ,
2404- )
2405- else :
2406- self .check_argument_count (
2407- callee ,
2408- arg_types ,
2409- arg_kinds ,
2410- arg_names ,
2411- formal_to_actual ,
2412- context ,
2413- object_type ,
2414- callable_name ,
2415- )
2416- self .check_argument_types (
2417- arg_types ,
2418- arg_kinds ,
2419- args ,
2420- callee ,
2421- formal_to_actual ,
2422- context ,
2423- object_type = object_type ,
2424- )
2425- elif has_type_errors :
2426- self .check_argument_count (
2427- callee ,
2428- arg_types ,
2429- arg_kinds ,
2430- arg_names ,
2431- formal_to_actual ,
2432- context ,
2433- object_type ,
2434- callable_name ,
2435- )
2436- self .check_argument_types (
2437- arg_types ,
2438- arg_kinds ,
2439- args ,
2440- callee ,
2441- formal_to_actual ,
2442- context ,
2443- object_type = object_type ,
2444- )
2445- else :
2446- self .check_argument_count (
2447- callee ,
2448- arg_types ,
2449- arg_kinds ,
2450- arg_names ,
2451- formal_to_actual ,
2452- context ,
2453- object_type ,
2454- callable_name ,
2455- )
2456- else :
2457- self .check_argument_count (
2458- callee ,
2459- arg_types ,
2460- arg_kinds ,
2461- arg_names ,
2462- formal_to_actual ,
2463- context ,
2464- object_type ,
2465- callable_name ,
2466- )
2467- self .check_argument_types (
2468- arg_types ,
2469- arg_kinds ,
2470- args ,
2471- callee ,
2472- formal_to_actual ,
2473- context ,
2474- object_type = object_type ,
2475- )
2476-
2477- def detect_shifted_positional_args (
2478- self ,
2479- callee : CallableType ,
2480- actual_types : list [Type ],
2481- actual_kinds : list [ArgKind ],
2482- missing_positional : list [int ],
2483- ) -> tuple [int , str | None , Type , bool ] | None :
2484- """Detect if positional arguments are shifted due to a missing argument.
2359+ ) -> bool :
2360+ """Try to identify a single missing positional argument using type alignment.
24852361
2486- Returns (1-indexed position, param name, expected type, high_confidence) if a
2487- shift pattern is found, None otherwise. High confidence is set when the function
2488- has fixed parameters (no defaults, *args, or **kwargs) .
2362+ If the caller and callee are just positional arguments and exactly one arg is missing,
2363+ we scan left to right to find which argument skipped. If there is an error, report it
2364+ and return True, or return False to fall back to normal checking .
24892365 """
2490- if not missing_positional :
2491- return None
2492-
2493- # Only attempt shift detection when exactly one argument is missing.
2494- # When multiple arguments are missing, we should fall back to the original behavior.
2495- if len (missing_positional ) != 1 :
2496- return None
2497-
2498- has_star_args = any (k == nodes .ARG_STAR for k in callee .arg_kinds )
2499- has_star_kwargs = any (k == nodes .ARG_STAR2 for k in callee .arg_kinds )
2500- has_defaults = any (k == nodes .ARG_OPT for k in callee .arg_kinds )
2501- high_confidence = not has_star_args and not has_star_kwargs and not has_defaults
2502-
2503- positional_actual_types = [
2504- actual_types [i ] for i , k in enumerate (actual_kinds ) if k == nodes .ARG_POS
2505- ]
2506- if len (positional_actual_types ) < 2 :
2507- return None
2366+ if not all (k == ARG_POS for k in callee .arg_kinds ):
2367+ return False
2368+ if not all (k == ARG_POS for k in arg_kinds ):
2369+ return False
2370+ if len (arg_kinds ) != len (callee .arg_kinds ) - 1 :
2371+ return False
25082372
2509- positional_formal_types : list [Type ] = []
2510- positional_formal_names : list [str | None ] = []
2511- for i , kind in enumerate (callee .arg_kinds ):
2512- if kind .is_positional ():
2513- positional_formal_types .append (callee .arg_types [i ])
2514- positional_formal_names .append (callee .arg_names [i ])
2515-
2516- # Find first position where arg doesn't match but would match next position
2517- shift_position = None
2518- for i , actual_type in enumerate (positional_actual_types ):
2519- if i >= len (positional_formal_types ):
2520- break
2521- if is_subtype (actual_type , positional_formal_types [i ], options = self .chk .options ):
2522- continue
2523- next_idx = i + 1
2524- if next_idx >= len (positional_formal_types ):
2525- break
2526- if is_subtype (
2527- actual_type , positional_formal_types [next_idx ], options = self .chk .options
2528- ):
2529- shift_position = i
2373+ skip_idx : int | None = None
2374+ j = 0
2375+ for i in range (len (callee .arg_types )):
2376+ if j >= len (arg_types ):
2377+ skip_idx = i
25302378 break
2379+ if is_subtype (arg_types [j ], callee .arg_types [i ], options = self .chk .options ):
2380+ j += 1
2381+ elif skip_idx is None :
2382+ skip_idx = i
25312383 else :
2532- break
2533-
2534- if shift_position is None :
2535- return None
2384+ return False
25362385
2537- # Validate that all args would match if we inserted one at shift_position
2538- if not self ._validate_shift_insertion (
2539- positional_actual_types , positional_formal_types , shift_position
2540- ):
2541- return None
2386+ if skip_idx is None or j != len (arg_types ):
2387+ return False
25422388
2543- return (
2544- shift_position + 1 ,
2545- positional_formal_names [shift_position ],
2546- positional_formal_types [shift_position ],
2547- high_confidence ,
2548- )
2389+ param_name = callee .arg_names [skip_idx ]
2390+ callee_name = callable_name (callee )
2391+ if param_name is None or callee_name is None :
2392+ return False
25492393
2550- def _validate_shift_insertion (
2551- self , actual_types : list [Type ], formal_types : list [Type ], insert_position : int
2552- ) -> bool :
2553- """Check if inserting an argument at insert_position would fix type errors."""
2554- for i , actual_type in enumerate (actual_types ):
2555- if i < insert_position :
2556- if i >= len (formal_types ):
2557- return False
2558- expected = formal_types [i ]
2559- else :
2560- shifted_idx = i + 1
2561- if shifted_idx >= len (formal_types ):
2562- return False
2563- expected = formal_types [shifted_idx ]
2564- if not is_subtype (actual_type , expected , options = self .chk .options ):
2565- return False
2394+ msg = f'Missing positional argument "{ param_name } " in call to { callee_name } '
2395+ ctx = args [skip_idx ] if skip_idx < len (args ) else context
2396+ self .msg .fail (msg , ctx , code = codes .CALL_ARG )
25662397 return True
25672398
25682399 def check_argument_count (
@@ -2575,15 +2406,13 @@ def check_argument_count(
25752406 context : Context | None ,
25762407 object_type : Type | None = None ,
25772408 callable_name : str | None = None ,
2578- ) -> tuple [ bool , list [ int ]] :
2409+ ) -> bool :
25792410 """Check that there is a value for all required arguments to a function.
25802411
25812412 Also check that there are no duplicate values for arguments. Report found errors
25822413 using 'messages' if it's not None. If 'messages' is given, 'context' must also be given.
25832414
2584- Return a tuple of:
2585- - False if there were any errors, True otherwise
2586- - List of formal argument indices that are missing positional arguments
2415+ Return False if there were any errors. Otherwise return True
25872416 """
25882417 if context is None :
25892418 # Avoid "is None" checks
@@ -2601,15 +2430,12 @@ def check_argument_count(
26012430 callee , actual_types , actual_kinds , actual_names , all_actuals , context
26022431 )
26032432
2604- missing_positional : list [int ] = []
2605-
26062433 # Check for too many or few values for formals.
26072434 for i , kind in enumerate (callee .arg_kinds ):
26082435 mapped_args = formal_to_actual [i ]
26092436 if kind .is_required () and not mapped_args and not is_unexpected_arg_error :
26102437 # No actual for a mandatory formal
26112438 if kind .is_positional ():
2612- missing_positional .append (i )
26132439 self .msg .too_few_arguments (callee , context , actual_names )
26142440 if object_type and callable_name and "." in callable_name :
26152441 self .missing_classvar_callable_note (object_type , callable_name , context )
@@ -2648,7 +2474,7 @@ def check_argument_count(
26482474 if actual_kinds [mapped_args [0 ]] == nodes .ARG_STAR2 and paramspec_entries > 1 :
26492475 self .msg .fail ("ParamSpec.kwargs should only be passed once" , context )
26502476 ok = False
2651- return ok , missing_positional
2477+ return ok
26522478
26532479 def check_for_extra_actual_arguments (
26542480 self ,
@@ -3108,7 +2934,7 @@ def has_shape(typ: Type) -> bool:
31082934 matches .append (typ )
31092935 elif self .check_argument_count (
31102936 typ , arg_types , arg_kinds , arg_names , formal_to_actual , None
3111- )[ 0 ] :
2937+ ):
31122938 if args_have_var_arg and typ .is_var_arg :
31132939 star_matches .append (typ )
31142940 elif args_have_kw_arg and typ .is_kw_arg :
@@ -3481,7 +3307,7 @@ def erased_signature_similarity(
34813307 with self .msg .filter_errors ():
34823308 if not self .check_argument_count (
34833309 callee , arg_types , arg_kinds , arg_names , formal_to_actual , None
3484- )[ 0 ] :
3310+ ):
34853311 # Too few or many arguments -> no match.
34863312 return False
34873313
0 commit comments