Skip to content

Commit eba432a

Browse files
committed
check function argument names for subtypes
1 parent 4f6e90f commit eba432a

27 files changed

+156
-136
lines changed

.mypy/baseline.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1977,7 +1977,7 @@
19771977
"code": "explicit-override",
19781978
"column": 4,
19791979
"message": "Method \"visit_class_def\" is not using @override but is overriding a method in class \"mypy.visitor.NodeVisitor\"",
1980-
"offset": 432,
1980+
"offset": 435,
19811981
"src": "def visit_class_def(self, defn: ClassDef) -> None:",
19821982
"target": "mypy.checker.TypeChecker.visit_class_def"
19831983
},
@@ -2353,7 +2353,7 @@
23532353
"code": "explicit-override",
23542354
"column": 4,
23552355
"message": "Method \"visit_uninhabited_type\" is not using @override but is overriding a method in class \"mypy.type_visitor.BoolTypeQuery\"",
2356-
"offset": 197,
2356+
"offset": 192,
23572357
"src": "def visit_uninhabited_type(self, t: UninhabitedType) -> bool:",
23582358
"target": "mypy.checker.InvalidInferredTypes.visit_uninhabited_type"
23592359
},
@@ -10771,7 +10771,7 @@
1077110771
"code": "no-any-expr",
1077210772
"column": 67,
1077310773
"message": "Expression type contains \"Any\" (has type \"list[Any]\")",
10774-
"offset": 285,
10774+
"offset": 289,
1077510775
"src": "\"--package-root\", metavar=\"ROOT\", action=\"append\", default=[], help=argparse.SUPPRESS",
1077610776
"target": "mypy.main.process_options"
1077710777
},
@@ -11429,7 +11429,7 @@
1142911429
"code": "explicit-override",
1143011430
"column": 4,
1143111431
"message": "Method \"visit_unbound_type\" is not using @override but is overriding a method in class \"mypy.type_visitor.TypeVisitor\"",
11432-
"offset": 642,
11432+
"offset": 638,
1143311433
"src": "def visit_unbound_type(self, t: UnboundType) -> ProperType:",
1143411434
"target": "mypy.meet.TypeMeetVisitor.visit_unbound_type"
1143511435
},
@@ -14119,7 +14119,7 @@
1411914119
"code": "no-any-expr",
1412014120
"column": 18,
1412114121
"message": "Expression has type \"Any\"",
14122-
"offset": 122,
14122+
"offset": 125,
1412314123
"src": "MACHDEP = sysconfig.get_config_var(\"MACHDEP\")",
1412414124
"target": "mypy.options.Options.__init__"
1412514125
},
@@ -24737,7 +24737,7 @@
2473724737
"code": "no-any-explicit",
2473824738
"column": 4,
2473924739
"message": "Explicit \"Any\" is not allowed",
24740-
"offset": 749,
24740+
"offset": 747,
2474124741
"src": "def report(*args: Any) -> None:",
2474224742
"target": "mypy.subtypes.unify_generic_callable"
2474324743
},

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
- Narrow type on initial assignment (#547)
77
- Annotations in function bodies are not analyzed as evaluated (#564)
88
- Invalid `cast`s show an error (#573)
9+
- Argument names are validated for subtypes (#562)
910
### Enhancements
1011
- Show 'narrowed from' in `reveal_type` (#550)
1112
- `--color-output` is enabled by default (#531)

docs/source/based_features.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,3 +296,15 @@ Basedmypy handles type annotations in function bodies as unevaluated:
296296
297297
def f():
298298
a: list[int] # no error, this annotation isn't evaluated
299+
300+
Checked Argument Names
301+
----------------------
302+
303+
.. code-block::python
304+
305+
class A:
306+
def f(self, a: int): ...
307+
308+
class B(A):
309+
@override
310+
def f(self, b: int): ... # error: Signature of "f" incompatible with supertype "A"

mypy/checker.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2351,7 +2351,10 @@ def check_override(
23512351
# Use boolean variable to clarify code.
23522352
fail = False
23532353
op_method_wider_note = False
2354-
if not is_subtype(override, original, ignore_pos_arg_names=True):
2354+
2355+
if not is_subtype(
2356+
override, original, ignore_pos_arg_names=self.options.work_not_properly_function_names
2357+
):
23552358
fail = True
23562359
elif isinstance(override, Overloaded) and self.is_forward_op_method(name):
23572360
# Operator method overrides cannot extend the domain, as
@@ -2837,7 +2840,7 @@ class C(B, A[int]): ... # this is unsafe because...
28372840
call = find_member("__call__", first_type, first_type, is_operator=True)
28382841
if call and isinstance(second_type, FunctionLike):
28392842
second_sig = self.bind_and_map_method(second, second_type, ctx, base2)
2840-
ok = is_subtype(call, second_sig, ignore_pos_arg_names=True)
2843+
ok = is_subtype(call, second_sig)
28412844
elif isinstance(first_type, FunctionLike) and isinstance(second_type, FunctionLike):
28422845
if first_type.is_type_obj() and second_type.is_type_obj():
28432846
# For class objects only check the subtype relationship of the classes,
@@ -2850,7 +2853,7 @@ class C(B, A[int]): ... # this is unsafe because...
28502853
# First bind/map method types when necessary.
28512854
first_sig = self.bind_and_map_method(first, first_type, ctx, base1)
28522855
second_sig = self.bind_and_map_method(second, second_type, ctx, base2)
2853-
ok = is_subtype(first_sig, second_sig, ignore_pos_arg_names=True)
2856+
ok = is_subtype(first_sig, second_sig)
28542857
elif first_type and second_type:
28552858
if isinstance(first.node, Var):
28562859
first_type = expand_self_type(first.node, first_type, fill_typevars(ctx))
@@ -7754,12 +7757,7 @@ def is_more_general_arg_prefix(t: FunctionLike, s: FunctionLike) -> bool:
77547757

77557758
def is_same_arg_prefix(t: CallableType, s: CallableType) -> bool:
77567759
return is_callable_compatible(
7757-
t,
7758-
s,
7759-
is_compat=is_same_type,
7760-
ignore_return=True,
7761-
check_args_covariantly=True,
7762-
ignore_pos_arg_names=True,
7760+
t, s, is_compat=is_same_type, ignore_return=True, check_args_covariantly=True
77637761
)
77647762

77657763

mypy/main.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1139,6 +1139,10 @@ def add_invertible_flag(
11391139
)
11401140
# This undocumented feature exports limited line-level dependency information.
11411141
internals_group.add_argument("--export-ref-info", action="store_true", help=argparse.SUPPRESS)
1142+
# This undocumented feature makes callable subtypes with incorrect names count as valid (needed to check mypy itself)
1143+
internals_group.add_argument(
1144+
"--work-not-properly-function-names", action="store_true", help=argparse.SUPPRESS
1145+
)
11421146

11431147
report_group = parser.add_argument_group(
11441148
title="Report generation", description="Generate a report in the specified format."

mypy/meet.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -467,11 +467,7 @@ def _type_object_overlap(left: Type, right: Type) -> bool:
467467

468468
if isinstance(left, CallableType) and isinstance(right, CallableType):
469469
return is_callable_compatible(
470-
left,
471-
right,
472-
is_compat=_is_overlapping_types,
473-
ignore_pos_arg_names=True,
474-
allow_partial_overlap=True,
470+
left, right, is_compat=_is_overlapping_types, allow_partial_overlap=True
475471
)
476472
elif isinstance(left, CallableType):
477473
left = left.fallback

mypy/messages.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3026,7 +3026,7 @@ def get_conflict_protocol_types(
30263026
subtype = mypy.typeops.get_protocol_member(left, member, class_obj)
30273027
if not subtype:
30283028
continue
3029-
is_compat = is_subtype(subtype, supertype, ignore_pos_arg_names=True, options=options)
3029+
is_compat = is_subtype(subtype, supertype, options=options)
30303030
if IS_SETTABLE in get_member_flags(member, right):
30313031
is_compat = is_compat and is_subtype(supertype, subtype, options=options)
30323032
if not is_compat:

mypy/options.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,9 @@ class Options:
105105
"""Options collected from flags."""
106106

107107
def __init__(self) -> None:
108+
# stupid thing to make mypy project check properly
109+
self.work_not_properly_function_names = False
110+
108111
# Cache for clone_for_module()
109112
self._per_module_cache: dict[str, Options] | None = None
110113
# Despite the warnings about _per_module_cache being slow, this one might be good

mypy/subtypes.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1081,7 +1081,6 @@ def f(self) -> A: ...
10811081
for member in right.type.protocol_members:
10821082
if member in members_not_to_check:
10831083
continue
1084-
ignore_names = member != "__call__" # __call__ can be passed kwargs
10851084
# The third argument below indicates to what self type is bound.
10861085
# We always bind self to the subtype. (Similarly to nominal types).
10871086
supertype = get_proper_type(find_member(member, right, left))
@@ -1103,9 +1102,7 @@ def f(self) -> A: ...
11031102
# Nominal check currently ignores arg names
11041103
# NOTE: If we ever change this, be sure to also change the call to
11051104
# SubtypeVisitor.build_subtype_kind(...) down below.
1106-
is_compat = is_subtype(
1107-
subtype, supertype, ignore_pos_arg_names=ignore_names, options=options
1108-
)
1105+
is_compat = is_subtype(subtype, supertype, options=options)
11091106
else:
11101107
is_compat = is_proper_subtype(subtype, supertype)
11111108
if not is_compat:
@@ -1141,6 +1138,7 @@ def f(self) -> A: ...
11411138
if not proper_subtype:
11421139
# Nominal check currently ignores arg names, but __call__ is special for protocols
11431140
ignore_names = right.type.protocol_members != ["__call__"]
1141+
ignore_names = False
11441142
else:
11451143
ignore_names = False
11461144
subtype_kind = SubtypeVisitor.build_subtype_kind(

mypy/typeshed/stdlib/_weakrefset.pyi

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ class WeakSet(MutableSet[_T], Generic[_T]):
1616
def __init__(self, data: None = None) -> None: ...
1717
@overload
1818
def __init__(self, data: Iterable[_T]) -> None: ...
19-
def add(self, item: _T) -> None: ...
20-
def discard(self, item: _T) -> None: ...
19+
def add(self, item: _T) -> None: ... # type: ignore[override]
20+
def discard(self, item: _T) -> None: ... # type: ignore[override]
2121
def copy(self) -> Self: ...
22-
def remove(self, item: _T) -> None: ...
22+
def remove(self, item: _T) -> None: ... # type: ignore[override]
2323
def update(self, other: Iterable[_T]) -> None: ...
2424
def __contains__(self, item: object) -> bool: ...
2525
def __len__(self) -> int: ...

0 commit comments

Comments
 (0)