Skip to content

Commit c59e4f9

Browse files
committed
refactored code
1 parent f307c28 commit c59e4f9

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,
@@ -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

test-data/unit/check-columns.test

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

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

0 commit comments

Comments
 (0)