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 ()
@@ -2338,232 +2350,51 @@ def apply_inferred_arguments(
23382350 # arguments.
23392351 return self .apply_generic_arguments (callee_type , inferred_args , context )
23402352
2341- def check_call_arguments (
2353+ def _detect_missing_positional_arg (
23422354 self ,
23432355 callee : CallableType ,
23442356 arg_types : list [Type ],
23452357 arg_kinds : list [ArgKind ],
2346- arg_names : Sequence [str | None ] | None ,
23472358 args : list [Expression ],
2348- formal_to_actual : list [list [int ]],
23492359 context : Context ,
2350- callable_name : str | None ,
2351- object_type : Type | None ,
2352- ) -> None :
2353- """Check argument count and types, consolidating errors for missing positional args."""
2354- with self .msg .filter_errors ():
2355- _ , missing_positional = self .check_argument_count (
2356- callee ,
2357- arg_types ,
2358- arg_kinds ,
2359- arg_names ,
2360- formal_to_actual ,
2361- context ,
2362- object_type ,
2363- callable_name ,
2364- )
2365-
2366- if missing_positional :
2367- func_name = callable_name or callee .name or "function"
2368- if "." in func_name :
2369- func_name = func_name .split ("." )[- 1 ]
2370-
2371- shift_info = None
2372- num_positional_args = sum (1 for k in arg_kinds if k == nodes .ARG_POS )
2373- if num_positional_args >= 2 :
2374- shift_info = self .detect_shifted_positional_args (
2375- callee , arg_types , arg_kinds , missing_positional
2376- )
2377-
2378- with self .msg .filter_errors () as type_error_watcher :
2379- self .check_argument_types (
2380- arg_types ,
2381- arg_kinds ,
2382- args ,
2383- callee ,
2384- formal_to_actual ,
2385- context ,
2386- object_type = object_type ,
2387- )
2388- has_type_errors = type_error_watcher .has_new_errors ()
2389-
2390- if shift_info is not None :
2391- shift_position , param_name , expected_type , high_confidence = shift_info
2392- if high_confidence and param_name :
2393- positional_arg_types = [
2394- arg_types [i ] for i , k in enumerate (arg_kinds ) if k == nodes .ARG_POS
2395- ]
2396- actual_type = positional_arg_types [shift_position - 1 ]
2397- actual_str , expected_str = format_type_distinctly (
2398- actual_type , expected_type , options = self .chk .options
2399- )
2400- self .msg .fail (
2401- f'Argument { shift_position } to "{ func_name } " has incompatible type '
2402- f"{ actual_str } ; expected { expected_str } " ,
2403- context ,
2404- code = codes .CALL_ARG ,
2405- )
2406- else :
2407- self .check_argument_count (
2408- callee ,
2409- arg_types ,
2410- arg_kinds ,
2411- arg_names ,
2412- formal_to_actual ,
2413- context ,
2414- object_type ,
2415- callable_name ,
2416- )
2417- self .check_argument_types (
2418- arg_types ,
2419- arg_kinds ,
2420- args ,
2421- callee ,
2422- formal_to_actual ,
2423- context ,
2424- object_type = object_type ,
2425- )
2426- elif has_type_errors :
2427- self .check_argument_count (
2428- callee ,
2429- arg_types ,
2430- arg_kinds ,
2431- arg_names ,
2432- formal_to_actual ,
2433- context ,
2434- object_type ,
2435- callable_name ,
2436- )
2437- self .check_argument_types (
2438- arg_types ,
2439- arg_kinds ,
2440- args ,
2441- callee ,
2442- formal_to_actual ,
2443- context ,
2444- object_type = object_type ,
2445- )
2446- else :
2447- self .check_argument_count (
2448- callee ,
2449- arg_types ,
2450- arg_kinds ,
2451- arg_names ,
2452- formal_to_actual ,
2453- context ,
2454- object_type ,
2455- callable_name ,
2456- )
2457- else :
2458- self .check_argument_count (
2459- callee ,
2460- arg_types ,
2461- arg_kinds ,
2462- arg_names ,
2463- formal_to_actual ,
2464- context ,
2465- object_type ,
2466- callable_name ,
2467- )
2468- self .check_argument_types (
2469- arg_types ,
2470- arg_kinds ,
2471- args ,
2472- callee ,
2473- formal_to_actual ,
2474- context ,
2475- object_type = object_type ,
2476- )
2477-
2478- def detect_shifted_positional_args (
2479- self ,
2480- callee : CallableType ,
2481- actual_types : list [Type ],
2482- actual_kinds : list [ArgKind ],
2483- missing_positional : list [int ],
2484- ) -> tuple [int , str | None , Type , bool ] | None :
2485- """Detect if positional arguments are shifted due to a missing argument.
2360+ ) -> bool :
2361+ """Try to identify a single missing positional argument using type alignment.
24862362
2487- Returns (1-indexed position, param name, expected type, high_confidence) if a
2488- shift pattern is found, None otherwise. High confidence is set when the function
2489- has fixed parameters (no defaults, *args, or **kwargs) .
2363+ If the caller and callee are just positional arguments and exactly one arg is missing,
2364+ we scan left to right to find which argument skipped. If there is an error, report it
2365+ and return True, or return False to fall back to normal checking .
24902366 """
2491- if not missing_positional :
2492- return None
2493-
2494- # Only attempt shift detection when exactly one argument is missing.
2495- # When multiple arguments are missing, we should fall back to the original behavior.
2496- if len (missing_positional ) != 1 :
2497- return None
2498-
2499- has_star_args = any (k == nodes .ARG_STAR for k in callee .arg_kinds )
2500- has_star_kwargs = any (k == nodes .ARG_STAR2 for k in callee .arg_kinds )
2501- has_defaults = any (k == nodes .ARG_OPT for k in callee .arg_kinds )
2502- high_confidence = not has_star_args and not has_star_kwargs and not has_defaults
2503-
2504- positional_actual_types = [
2505- actual_types [i ] for i , k in enumerate (actual_kinds ) if k == nodes .ARG_POS
2506- ]
2507- if len (positional_actual_types ) < 2 :
2508- return None
2367+ if not all (k == ARG_POS for k in callee .arg_kinds ):
2368+ return False
2369+ if not all (k == ARG_POS for k in arg_kinds ):
2370+ return False
2371+ if len (arg_kinds ) != len (callee .arg_kinds ) - 1 :
2372+ return False
25092373
2510- positional_formal_types : list [Type ] = []
2511- positional_formal_names : list [str | None ] = []
2512- for i , kind in enumerate (callee .arg_kinds ):
2513- if kind .is_positional ():
2514- positional_formal_types .append (callee .arg_types [i ])
2515- positional_formal_names .append (callee .arg_names [i ])
2516-
2517- # Find first position where arg doesn't match but would match next position
2518- shift_position = None
2519- for i , actual_type in enumerate (positional_actual_types ):
2520- if i >= len (positional_formal_types ):
2521- break
2522- if is_subtype (actual_type , positional_formal_types [i ], options = self .chk .options ):
2523- continue
2524- next_idx = i + 1
2525- if next_idx >= len (positional_formal_types ):
2526- break
2527- if is_subtype (
2528- actual_type , positional_formal_types [next_idx ], options = self .chk .options
2529- ):
2530- shift_position = i
2374+ skip_idx : int | None = None
2375+ j = 0
2376+ for i in range (len (callee .arg_types )):
2377+ if j >= len (arg_types ):
2378+ skip_idx = i
25312379 break
2380+ if is_subtype (arg_types [j ], callee .arg_types [i ], options = self .chk .options ):
2381+ j += 1
2382+ elif skip_idx is None :
2383+ skip_idx = i
25322384 else :
2533- break
2534-
2535- if shift_position is None :
2536- return None
2385+ return False
25372386
2538- # Validate that all args would match if we inserted one at shift_position
2539- if not self ._validate_shift_insertion (
2540- positional_actual_types , positional_formal_types , shift_position
2541- ):
2542- return None
2387+ if skip_idx is None or j != len (arg_types ):
2388+ return False
25432389
2544- return (
2545- shift_position + 1 ,
2546- positional_formal_names [shift_position ],
2547- positional_formal_types [shift_position ],
2548- high_confidence ,
2549- )
2390+ param_name = callee .arg_names [skip_idx ]
2391+ callee_name = callable_name (callee )
2392+ if param_name is None or callee_name is None :
2393+ return False
25502394
2551- def _validate_shift_insertion (
2552- self , actual_types : list [Type ], formal_types : list [Type ], insert_position : int
2553- ) -> bool :
2554- """Check if inserting an argument at insert_position would fix type errors."""
2555- for i , actual_type in enumerate (actual_types ):
2556- if i < insert_position :
2557- if i >= len (formal_types ):
2558- return False
2559- expected = formal_types [i ]
2560- else :
2561- shifted_idx = i + 1
2562- if shifted_idx >= len (formal_types ):
2563- return False
2564- expected = formal_types [shifted_idx ]
2565- if not is_subtype (actual_type , expected , options = self .chk .options ):
2566- return False
2395+ msg = f'Missing positional argument "{ param_name } " in call to { callee_name } '
2396+ ctx = args [skip_idx ] if skip_idx < len (args ) else context
2397+ self .msg .fail (msg , ctx , code = codes .CALL_ARG )
25672398 return True
25682399
25692400 def check_argument_count (
@@ -2576,15 +2407,13 @@ def check_argument_count(
25762407 context : Context | None ,
25772408 object_type : Type | None = None ,
25782409 callable_name : str | None = None ,
2579- ) -> tuple [ bool , list [ int ]] :
2410+ ) -> bool :
25802411 """Check that there is a value for all required arguments to a function.
25812412
25822413 Also check that there are no duplicate values for arguments. Report found errors
25832414 using 'messages' if it's not None. If 'messages' is given, 'context' must also be given.
25842415
2585- Return a tuple of:
2586- - False if there were any errors, True otherwise
2587- - List of formal argument indices that are missing positional arguments
2416+ Return False if there were any errors. Otherwise return True
25882417 """
25892418 if context is None :
25902419 # Avoid "is None" checks
@@ -2602,15 +2431,12 @@ def check_argument_count(
26022431 callee , actual_types , actual_kinds , actual_names , all_actuals , context
26032432 )
26042433
2605- missing_positional : list [int ] = []
2606-
26072434 # Check for too many or few values for formals.
26082435 for i , kind in enumerate (callee .arg_kinds ):
26092436 mapped_args = formal_to_actual [i ]
26102437 if kind .is_required () and not mapped_args and not is_unexpected_arg_error :
26112438 # No actual for a mandatory formal
26122439 if kind .is_positional ():
2613- missing_positional .append (i )
26142440 self .msg .too_few_arguments (callee , context , actual_names )
26152441 if object_type and callable_name and "." in callable_name :
26162442 self .missing_classvar_callable_note (object_type , callable_name , context )
@@ -2649,7 +2475,7 @@ def check_argument_count(
26492475 if actual_kinds [mapped_args [0 ]] == nodes .ARG_STAR2 and paramspec_entries > 1 :
26502476 self .msg .fail ("ParamSpec.kwargs should only be passed once" , context )
26512477 ok = False
2652- return ok , missing_positional
2478+ return ok
26532479
26542480 def check_for_extra_actual_arguments (
26552481 self ,
@@ -3109,7 +2935,7 @@ def has_shape(typ: Type) -> bool:
31092935 matches .append (typ )
31102936 elif self .check_argument_count (
31112937 typ , arg_types , arg_kinds , arg_names , formal_to_actual , None
3112- )[ 0 ] :
2938+ ):
31132939 if args_have_var_arg and typ .is_var_arg :
31142940 star_matches .append (typ )
31152941 elif args_have_kw_arg and typ .is_kw_arg :
@@ -3482,7 +3308,7 @@ def erased_signature_similarity(
34823308 with self .msg .filter_errors ():
34833309 if not self .check_argument_count (
34843310 callee , arg_types , arg_kinds , arg_names , formal_to_actual , None
3485- )[ 0 ] :
3311+ ):
34863312 # Too few or many arguments -> no match.
34873313 return False
34883314
0 commit comments