Skip to content

Commit f7b108f

Browse files
committed
refactored code
1 parent a432f38 commit f7b108f

3 files changed

Lines changed: 86 additions & 244 deletions

File tree

mypy/checkexpr.py

Lines changed: 60 additions & 234 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
from mypy.maptype import map_instance_to_supertype
3434
from mypy.meet import is_overlapping_types, narrow_declared_type
3535
from 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
3737
from 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

test-data/unit/check-columns.test

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,14 @@ main:2:10:2:17: error: Incompatible types in assignment (expression has type "st
408408
main:6:3:7:1: error: Argument 1 to "f" has incompatible type "int"; expected "str"
409409
main:8:1:8:4: error: Value of type "int" is not indexable
410410

411+
[case testColumnsMissingPositionalArgShiftDetected]
412+
def f(x: int, y: str, z: bytes, aa: int) -> None: ...
413+
f(1, b'x', 1) # E:6: Missing positional argument "y" in call to "f"
414+
def g(x: int, y: str, z: bytes) -> None: ...
415+
g("hello", b'x') # E:3: Missing positional argument "x" in call to "g"
416+
g(1, "hello") # E:1: Missing positional argument "z" in call to "g"
417+
[builtins fixtures/primitives.pyi]
418+
411419
[case testEndColumnsWithTooManyTypeVars]
412420
# flags: --pretty
413421
import typing

0 commit comments

Comments
 (0)