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 ,
@@ -1792,17 +1792,29 @@ def check_callable_call(
17921792
17931793 arg_types = self .infer_arg_types_in_context (callee , args , arg_kinds , formal_to_actual )
17941794
1795- self .check_call_arguments (
1796- callee ,
1797- arg_types ,
1798- arg_kinds ,
1799- arg_names ,
1800- args ,
1801- formal_to_actual ,
1802- context ,
1803- callable_name ,
1804- object_type ,
1805- )
1795+ if not self ._detect_missing_positional_arg (
1796+ callee , arg_types , arg_kinds , args , context
1797+ ):
1798+ self .check_argument_count (
1799+ callee ,
1800+ arg_types ,
1801+ arg_kinds ,
1802+ arg_names ,
1803+ formal_to_actual ,
1804+ context ,
1805+ object_type ,
1806+ callable_name ,
1807+ )
1808+
1809+ self .check_argument_types (
1810+ arg_types ,
1811+ arg_kinds ,
1812+ args ,
1813+ callee ,
1814+ formal_to_actual ,
1815+ context ,
1816+ object_type = object_type ,
1817+ )
18061818
18071819 if (
18081820 callee .is_type_obj ()
@@ -2332,232 +2344,51 @@ def apply_inferred_arguments(
23322344 # arguments.
23332345 return self .apply_generic_arguments (callee_type , inferred_args , context )
23342346
2335- def check_call_arguments (
2347+ def _detect_missing_positional_arg (
23362348 self ,
23372349 callee : CallableType ,
23382350 arg_types : list [Type ],
23392351 arg_kinds : list [ArgKind ],
2340- arg_names : Sequence [str | None ] | None ,
23412352 args : list [Expression ],
2342- formal_to_actual : list [list [int ]],
23432353 context : Context ,
2344- callable_name : str | None ,
2345- object_type : Type | None ,
2346- ) -> None :
2347- """Check argument count and types, consolidating errors for missing positional args."""
2348- with self .msg .filter_errors ():
2349- _ , missing_positional = self .check_argument_count (
2350- callee ,
2351- arg_types ,
2352- arg_kinds ,
2353- arg_names ,
2354- formal_to_actual ,
2355- context ,
2356- object_type ,
2357- callable_name ,
2358- )
2359-
2360- if missing_positional :
2361- func_name = callable_name or callee .name or "function"
2362- if "." in func_name :
2363- func_name = func_name .split ("." )[- 1 ]
2364-
2365- shift_info = None
2366- num_positional_args = sum (1 for k in arg_kinds if k == nodes .ARG_POS )
2367- if num_positional_args >= 2 :
2368- shift_info = self .detect_shifted_positional_args (
2369- callee , arg_types , arg_kinds , missing_positional
2370- )
2371-
2372- with self .msg .filter_errors () as type_error_watcher :
2373- self .check_argument_types (
2374- arg_types ,
2375- arg_kinds ,
2376- args ,
2377- callee ,
2378- formal_to_actual ,
2379- context ,
2380- object_type = object_type ,
2381- )
2382- has_type_errors = type_error_watcher .has_new_errors ()
2383-
2384- if shift_info is not None :
2385- shift_position , param_name , expected_type , high_confidence = shift_info
2386- if high_confidence and param_name :
2387- positional_arg_types = [
2388- arg_types [i ] for i , k in enumerate (arg_kinds ) if k == nodes .ARG_POS
2389- ]
2390- actual_type = positional_arg_types [shift_position - 1 ]
2391- actual_str , expected_str = format_type_distinctly (
2392- actual_type , expected_type , options = self .chk .options
2393- )
2394- self .msg .fail (
2395- f'Argument { shift_position } to "{ func_name } " has incompatible type '
2396- f"{ actual_str } ; expected { expected_str } " ,
2397- context ,
2398- code = codes .CALL_ARG ,
2399- )
2400- else :
2401- self .check_argument_count (
2402- callee ,
2403- arg_types ,
2404- arg_kinds ,
2405- arg_names ,
2406- formal_to_actual ,
2407- context ,
2408- object_type ,
2409- callable_name ,
2410- )
2411- self .check_argument_types (
2412- arg_types ,
2413- arg_kinds ,
2414- args ,
2415- callee ,
2416- formal_to_actual ,
2417- context ,
2418- object_type = object_type ,
2419- )
2420- elif has_type_errors :
2421- self .check_argument_count (
2422- callee ,
2423- arg_types ,
2424- arg_kinds ,
2425- arg_names ,
2426- formal_to_actual ,
2427- context ,
2428- object_type ,
2429- callable_name ,
2430- )
2431- self .check_argument_types (
2432- arg_types ,
2433- arg_kinds ,
2434- args ,
2435- callee ,
2436- formal_to_actual ,
2437- context ,
2438- object_type = object_type ,
2439- )
2440- else :
2441- self .check_argument_count (
2442- callee ,
2443- arg_types ,
2444- arg_kinds ,
2445- arg_names ,
2446- formal_to_actual ,
2447- context ,
2448- object_type ,
2449- callable_name ,
2450- )
2451- else :
2452- self .check_argument_count (
2453- callee ,
2454- arg_types ,
2455- arg_kinds ,
2456- arg_names ,
2457- formal_to_actual ,
2458- context ,
2459- object_type ,
2460- callable_name ,
2461- )
2462- self .check_argument_types (
2463- arg_types ,
2464- arg_kinds ,
2465- args ,
2466- callee ,
2467- formal_to_actual ,
2468- context ,
2469- object_type = object_type ,
2470- )
2471-
2472- def detect_shifted_positional_args (
2473- self ,
2474- callee : CallableType ,
2475- actual_types : list [Type ],
2476- actual_kinds : list [ArgKind ],
2477- missing_positional : list [int ],
2478- ) -> tuple [int , str | None , Type , bool ] | None :
2479- """Detect if positional arguments are shifted due to a missing argument.
2354+ ) -> bool :
2355+ """Try to identify a single missing positional argument using type alignment.
24802356
2481- Returns (1-indexed position, param name, expected type, high_confidence) if a
2482- shift pattern is found, None otherwise. High confidence is set when the function
2483- has fixed parameters (no defaults, *args, or **kwargs) .
2357+ If the caller and callee are just positional arguments and exactly one arg is missing,
2358+ we scan left to right to find which argument skipped. If there is an error, report it
2359+ and return True, or return False to fall back to normal checking .
24842360 """
2485- if not missing_positional :
2486- return None
2487-
2488- # Only attempt shift detection when exactly one argument is missing.
2489- # When multiple arguments are missing, we should fall back to the original behavior.
2490- if len (missing_positional ) != 1 :
2491- return None
2492-
2493- has_star_args = any (k == nodes .ARG_STAR for k in callee .arg_kinds )
2494- has_star_kwargs = any (k == nodes .ARG_STAR2 for k in callee .arg_kinds )
2495- has_defaults = any (k == nodes .ARG_OPT for k in callee .arg_kinds )
2496- high_confidence = not has_star_args and not has_star_kwargs and not has_defaults
2497-
2498- positional_actual_types = [
2499- actual_types [i ] for i , k in enumerate (actual_kinds ) if k == nodes .ARG_POS
2500- ]
2501- if len (positional_actual_types ) < 2 :
2502- return None
2361+ if not all (k == ARG_POS for k in callee .arg_kinds ):
2362+ return False
2363+ if not all (k == ARG_POS for k in arg_kinds ):
2364+ return False
2365+ if len (arg_kinds ) != len (callee .arg_kinds ) - 1 :
2366+ return False
25032367
2504- positional_formal_types : list [Type ] = []
2505- positional_formal_names : list [str | None ] = []
2506- for i , kind in enumerate (callee .arg_kinds ):
2507- if kind .is_positional ():
2508- positional_formal_types .append (callee .arg_types [i ])
2509- positional_formal_names .append (callee .arg_names [i ])
2510-
2511- # Find first position where arg doesn't match but would match next position
2512- shift_position = None
2513- for i , actual_type in enumerate (positional_actual_types ):
2514- if i >= len (positional_formal_types ):
2515- break
2516- if is_subtype (actual_type , positional_formal_types [i ], options = self .chk .options ):
2517- continue
2518- next_idx = i + 1
2519- if next_idx >= len (positional_formal_types ):
2520- break
2521- if is_subtype (
2522- actual_type , positional_formal_types [next_idx ], options = self .chk .options
2523- ):
2524- shift_position = i
2368+ skip_idx : int | None = None
2369+ j = 0
2370+ for i in range (len (callee .arg_types )):
2371+ if j >= len (arg_types ):
2372+ skip_idx = i
25252373 break
2374+ if is_subtype (arg_types [j ], callee .arg_types [i ], options = self .chk .options ):
2375+ j += 1
2376+ elif skip_idx is None :
2377+ skip_idx = i
25262378 else :
2527- break
2528-
2529- if shift_position is None :
2530- return None
2379+ return False
25312380
2532- # Validate that all args would match if we inserted one at shift_position
2533- if not self ._validate_shift_insertion (
2534- positional_actual_types , positional_formal_types , shift_position
2535- ):
2536- return None
2381+ if skip_idx is None or j != len (arg_types ):
2382+ return False
25372383
2538- return (
2539- shift_position + 1 ,
2540- positional_formal_names [shift_position ],
2541- positional_formal_types [shift_position ],
2542- high_confidence ,
2543- )
2384+ param_name = callee .arg_names [skip_idx ]
2385+ callee_name = callable_name (callee )
2386+ if param_name is None or callee_name is None :
2387+ return False
25442388
2545- def _validate_shift_insertion (
2546- self , actual_types : list [Type ], formal_types : list [Type ], insert_position : int
2547- ) -> bool :
2548- """Check if inserting an argument at insert_position would fix type errors."""
2549- for i , actual_type in enumerate (actual_types ):
2550- if i < insert_position :
2551- if i >= len (formal_types ):
2552- return False
2553- expected = formal_types [i ]
2554- else :
2555- shifted_idx = i + 1
2556- if shifted_idx >= len (formal_types ):
2557- return False
2558- expected = formal_types [shifted_idx ]
2559- if not is_subtype (actual_type , expected , options = self .chk .options ):
2560- return False
2389+ msg = f'Missing positional argument "{ param_name } " in call to { callee_name } '
2390+ ctx = args [skip_idx ] if skip_idx < len (args ) else context
2391+ self .msg .fail (msg , ctx , code = codes .CALL_ARG )
25612392 return True
25622393
25632394 def check_argument_count (
@@ -2570,15 +2401,13 @@ def check_argument_count(
25702401 context : Context | None ,
25712402 object_type : Type | None = None ,
25722403 callable_name : str | None = None ,
2573- ) -> tuple [ bool , list [ int ]] :
2404+ ) -> bool :
25742405 """Check that there is a value for all required arguments to a function.
25752406
25762407 Also check that there are no duplicate values for arguments. Report found errors
25772408 using 'messages' if it's not None. If 'messages' is given, 'context' must also be given.
25782409
2579- Return a tuple of:
2580- - False if there were any errors, True otherwise
2581- - List of formal argument indices that are missing positional arguments
2410+ Return False if there were any errors. Otherwise return True
25822411 """
25832412 if context is None :
25842413 # Avoid "is None" checks
@@ -2596,15 +2425,12 @@ def check_argument_count(
25962425 callee , actual_types , actual_kinds , actual_names , all_actuals , context
25972426 )
25982427
2599- missing_positional : list [int ] = []
2600-
26012428 # Check for too many or few values for formals.
26022429 for i , kind in enumerate (callee .arg_kinds ):
26032430 mapped_args = formal_to_actual [i ]
26042431 if kind .is_required () and not mapped_args and not is_unexpected_arg_error :
26052432 # No actual for a mandatory formal
26062433 if kind .is_positional ():
2607- missing_positional .append (i )
26082434 self .msg .too_few_arguments (callee , context , actual_names )
26092435 if object_type and callable_name and "." in callable_name :
26102436 self .missing_classvar_callable_note (object_type , callable_name , context )
@@ -2643,7 +2469,7 @@ def check_argument_count(
26432469 if actual_kinds [mapped_args [0 ]] == nodes .ARG_STAR2 and paramspec_entries > 1 :
26442470 self .msg .fail ("ParamSpec.kwargs should only be passed once" , context )
26452471 ok = False
2646- return ok , missing_positional
2472+ return ok
26472473
26482474 def check_for_extra_actual_arguments (
26492475 self ,
@@ -3103,7 +2929,7 @@ def has_shape(typ: Type) -> bool:
31032929 matches .append (typ )
31042930 elif self .check_argument_count (
31052931 typ , arg_types , arg_kinds , arg_names , formal_to_actual , None
3106- )[ 0 ] :
2932+ ):
31072933 if args_have_var_arg and typ .is_var_arg :
31082934 star_matches .append (typ )
31092935 elif args_have_kw_arg and typ .is_kw_arg :
@@ -3476,7 +3302,7 @@ def erased_signature_similarity(
34763302 with self .msg .filter_errors ():
34773303 if not self .check_argument_count (
34783304 callee , arg_types , arg_kinds , arg_names , formal_to_actual , None
3479- )[ 0 ] :
3305+ ):
34803306 # Too few or many arguments -> no match.
34813307 return False
34823308
0 commit comments