Skip to content

Commit b6a662c

Browse files
authored
Use checkmember.py to check method override (#18870)
This is a second "large" PR towards #7724. Here I actually expect a smaller fallout than for variables, since methods are usually less tricky, but let's see.
1 parent d6cb14f commit b6a662c

File tree

7 files changed

+80
-149
lines changed

7 files changed

+80
-149
lines changed

mypy/checker.py

+53-118
Original file line numberDiff line numberDiff line change
@@ -2134,40 +2134,17 @@ def check_method_or_accessor_override_for_base(
21342134
return None
21352135
return found_base_method
21362136

2137-
def check_setter_type_override(
2138-
self, defn: OverloadedFuncDef, base_attr: SymbolTableNode, base: TypeInfo
2139-
) -> None:
2137+
def check_setter_type_override(self, defn: OverloadedFuncDef, base: TypeInfo) -> None:
21402138
"""Check override of a setter type of a mutable attribute.
21412139
21422140
Currently, this should be only called when either base node or the current node
21432141
is a custom settable property (i.e. where setter type is different from getter type).
21442142
Note that this check is contravariant.
21452143
"""
2146-
base_node = base_attr.node
2147-
assert isinstance(base_node, (OverloadedFuncDef, Var))
2148-
original_type, is_original_setter = get_raw_setter_type(base_node)
2149-
if isinstance(base_node, Var):
2150-
expanded_type = map_type_from_supertype(original_type, defn.info, base)
2151-
original_type = get_proper_type(
2152-
expand_self_type(base_node, expanded_type, fill_typevars(defn.info))
2153-
)
2154-
else:
2155-
assert isinstance(original_type, ProperType)
2156-
assert isinstance(original_type, CallableType)
2157-
original_type = self.bind_and_map_method(base_attr, original_type, defn.info, base)
2158-
assert isinstance(original_type, CallableType)
2159-
if is_original_setter:
2160-
original_type = original_type.arg_types[0]
2161-
else:
2162-
original_type = original_type.ret_type
2163-
2164-
typ, is_setter = get_raw_setter_type(defn)
2165-
assert isinstance(typ, ProperType) and isinstance(typ, CallableType)
2166-
typ = bind_self(typ, self.scope.active_self_type())
2167-
if is_setter:
2168-
typ = typ.arg_types[0]
2169-
else:
2170-
typ = typ.ret_type
2144+
typ, _ = self.node_type_from_base(defn, defn.info, setter_type=True)
2145+
original_type, _ = self.node_type_from_base(defn, base, setter_type=True)
2146+
# The caller should handle deferrals.
2147+
assert typ is not None and original_type is not None
21712148

21722149
if not is_subtype(original_type, typ):
21732150
self.msg.incompatible_setter_override(defn.items[1], typ, original_type, base)
@@ -2192,28 +2169,19 @@ def check_method_override_for_base_with_name(
21922169
context = defn.func
21932170

21942171
# Construct the type of the overriding method.
2195-
# TODO: this logic is much less complete than similar one in checkmember.py
21962172
if isinstance(defn, (FuncDef, OverloadedFuncDef)):
2197-
typ: Type = self.function_type(defn)
21982173
override_class_or_static = defn.is_class or defn.is_static
2199-
override_class = defn.is_class
22002174
else:
2201-
assert defn.var.is_ready
2202-
assert defn.var.type is not None
2203-
typ = defn.var.type
22042175
override_class_or_static = defn.func.is_class or defn.func.is_static
2205-
override_class = defn.func.is_class
2206-
typ = get_proper_type(typ)
2207-
if isinstance(typ, FunctionLike) and not is_static(context):
2208-
typ = bind_self(typ, self.scope.active_self_type(), is_classmethod=override_class)
2209-
# Map the overridden method type to subtype context so that
2210-
# it can be checked for compatibility.
2211-
original_type = get_proper_type(base_attr.type)
2176+
typ, _ = self.node_type_from_base(defn, defn.info)
2177+
assert typ is not None
2178+
22122179
original_node = base_attr.node
22132180
# `original_type` can be partial if (e.g.) it is originally an
22142181
# instance variable from an `__init__` block that becomes deferred.
22152182
supertype_ready = True
2216-
if original_type is None or isinstance(original_type, PartialType):
2183+
original_type, _ = self.node_type_from_base(defn, base, name_override=name)
2184+
if original_type is None:
22172185
supertype_ready = False
22182186
if self.pass_num < self.last_pass:
22192187
# If there are passes left, defer this node until next pass,
@@ -2255,7 +2223,7 @@ def check_method_override_for_base_with_name(
22552223
# supertype is not known precisely.
22562224
if supertype_ready:
22572225
always_allow_covariant = True
2258-
self.check_setter_type_override(defn, base_attr, base)
2226+
self.check_setter_type_override(defn, base)
22592227

22602228
if isinstance(original_node, (FuncDef, OverloadedFuncDef)):
22612229
original_class_or_static = original_node.is_class or original_node.is_static
@@ -2265,41 +2233,24 @@ def check_method_override_for_base_with_name(
22652233
else:
22662234
original_class_or_static = False # a variable can't be class or static
22672235

2268-
if isinstance(original_type, FunctionLike):
2269-
original_type = self.bind_and_map_method(base_attr, original_type, defn.info, base)
2270-
if original_node and is_property(original_node):
2271-
original_type = get_property_type(original_type)
2272-
2273-
if isinstance(original_node, Var):
2274-
expanded_type = map_type_from_supertype(original_type, defn.info, base)
2275-
expanded_type = expand_self_type(
2276-
original_node, expanded_type, fill_typevars(defn.info)
2277-
)
2278-
original_type = get_proper_type(expanded_type)
2236+
typ = get_proper_type(typ)
2237+
original_type = get_proper_type(original_type)
22792238

2280-
if is_property(defn):
2281-
inner: FunctionLike | None
2282-
if isinstance(typ, FunctionLike):
2283-
inner = typ
2284-
else:
2285-
inner = self.extract_callable_type(typ, context)
2286-
if inner is not None:
2287-
typ = inner
2288-
typ = get_property_type(typ)
2289-
if (
2290-
isinstance(original_node, Var)
2291-
and not original_node.is_final
2292-
and (not original_node.is_property or original_node.is_settable_property)
2293-
and isinstance(defn, Decorator)
2294-
):
2295-
# We only give an error where no other similar errors will be given.
2296-
if not isinstance(original_type, AnyType):
2297-
self.msg.fail(
2298-
"Cannot override writeable attribute with read-only property",
2299-
# Give an error on function line to match old behaviour.
2300-
defn.func,
2301-
code=codes.OVERRIDE,
2302-
)
2239+
if (
2240+
is_property(defn)
2241+
and isinstance(original_node, Var)
2242+
and not original_node.is_final
2243+
and (not original_node.is_property or original_node.is_settable_property)
2244+
and isinstance(defn, Decorator)
2245+
):
2246+
# We only give an error where no other similar errors will be given.
2247+
if not isinstance(original_type, AnyType):
2248+
self.msg.fail(
2249+
"Cannot override writeable attribute with read-only property",
2250+
# Give an error on function line to match old behaviour.
2251+
defn.func,
2252+
code=codes.OVERRIDE,
2253+
)
23032254

23042255
if isinstance(original_type, AnyType) or isinstance(typ, AnyType):
23052256
pass
@@ -3412,7 +3363,7 @@ def get_variable_type_context(self, inferred: Var, rvalue: Expression) -> Type |
34123363
# For inference within class body, get supertype attribute as it would look on
34133364
# a class object for lambdas overriding methods, etc.
34143365
base_node = base.names[inferred.name].node
3415-
base_type, _ = self.lvalue_type_from_base(
3366+
base_type, _ = self.node_type_from_base(
34163367
inferred,
34173368
base,
34183369
is_class=is_method(base_node)
@@ -3523,7 +3474,7 @@ def check_compatibility_all_supers(self, lvalue: RefExpr, rvalue: Expression) ->
35233474
rvalue_type = self.expr_checker.accept(rvalue, lvalue_node.type)
35243475
actual_lvalue_type = lvalue_node.type
35253476
lvalue_node.type = rvalue_type
3526-
lvalue_type, _ = self.lvalue_type_from_base(lvalue_node, lvalue_node.info)
3477+
lvalue_type, _ = self.node_type_from_base(lvalue_node, lvalue_node.info)
35273478
if lvalue_node.is_inferred and not lvalue_node.explicit_self_type:
35283479
lvalue_node.type = actual_lvalue_type
35293480

@@ -3542,7 +3493,7 @@ def check_compatibility_all_supers(self, lvalue: RefExpr, rvalue: Expression) ->
35423493
if is_private(lvalue_node.name):
35433494
continue
35443495

3545-
base_type, base_node = self.lvalue_type_from_base(lvalue_node, base)
3496+
base_type, base_node = self.node_type_from_base(lvalue_node, base)
35463497
custom_setter = is_custom_settable_property(base_node)
35473498
if isinstance(base_type, PartialType):
35483499
base_type = None
@@ -3561,7 +3512,7 @@ def check_compatibility_all_supers(self, lvalue: RefExpr, rvalue: Expression) ->
35613512
# base classes are also incompatible
35623513
return
35633514
if lvalue_type and custom_setter:
3564-
base_type, _ = self.lvalue_type_from_base(
3515+
base_type, _ = self.node_type_from_base(
35653516
lvalue_node, base, setter_type=True
35663517
)
35673518
# Setter type for a custom property must be ready if
@@ -3612,26 +3563,33 @@ def check_compatibility_super(
36123563
)
36133564
return ok
36143565

3615-
def lvalue_type_from_base(
3616-
self, expr_node: Var, base: TypeInfo, setter_type: bool = False, is_class: bool = False
3566+
def node_type_from_base(
3567+
self,
3568+
node: SymbolNode,
3569+
base: TypeInfo,
3570+
*,
3571+
setter_type: bool = False,
3572+
is_class: bool = False,
3573+
name_override: str | None = None,
36173574
) -> tuple[Type | None, SymbolNode | None]:
3618-
"""Find a type for a variable name in base class.
3575+
"""Find a type for a name in base class.
36193576
36203577
Return the type found and the corresponding node defining the name or None
36213578
for both if the name is not defined in base or the node type is not known (yet).
36223579
The type returned is already properly mapped/bound to the subclass.
36233580
If setter_type is True, return setter types for settable properties (otherwise the
36243581
getter type is returned).
36253582
"""
3626-
expr_name = expr_node.name
3627-
base_var = base.names.get(expr_name)
3583+
name = name_override or node.name
3584+
base_node = base.names.get(name)
36283585

36293586
# TODO: defer current node if the superclass node is not ready.
36303587
if (
3631-
not base_var
3632-
or not base_var.type
3633-
or isinstance(base_var.type, PartialType)
3634-
and base_var.type.type is not None
3588+
not base_node
3589+
or isinstance(base_node.node, Var)
3590+
and not base_node.type
3591+
or isinstance(base_node.type, PartialType)
3592+
and base_node.type.type is not None
36353593
):
36363594
return None, None
36373595

@@ -3645,9 +3603,9 @@ def lvalue_type_from_base(
36453603
mx = MemberContext(
36463604
is_lvalue=setter_type,
36473605
is_super=False,
3648-
is_operator=mypy.checkexpr.is_operator_method(expr_name),
3606+
is_operator=mypy.checkexpr.is_operator_method(name),
36493607
original_type=self_type,
3650-
context=expr_node,
3608+
context=node,
36513609
chk=self,
36523610
suppress_errors=True,
36533611
)
@@ -3656,11 +3614,11 @@ def lvalue_type_from_base(
36563614
if is_class:
36573615
fallback = instance.type.metaclass_type or mx.named_type("builtins.type")
36583616
base_type = analyze_class_attribute_access(
3659-
instance, expr_name, mx, mcs_fallback=fallback, override_info=base
3617+
instance, name, mx, mcs_fallback=fallback, override_info=base
36603618
)
36613619
else:
3662-
base_type = analyze_instance_member_access(expr_name, instance, mx, base)
3663-
return base_type, base_var.node
3620+
base_type = analyze_instance_member_access(name, instance, mx, base)
3621+
return base_type, base_node.node
36643622

36653623
def check_compatibility_classvar_super(
36663624
self, node: Var, base: TypeInfo, base_node: Node | None
@@ -8965,29 +8923,6 @@ def is_custom_settable_property(defn: SymbolNode | None) -> bool:
89658923
return not is_same_type(get_property_type(get_proper_type(var.type)), setter_type)
89668924

89678925

8968-
def get_raw_setter_type(defn: OverloadedFuncDef | Var) -> tuple[Type, bool]:
8969-
"""Get an effective original setter type for a node.
8970-
8971-
For a variable it is simply its type. For a property it is the type
8972-
of the setter method (if not None), or the getter method (used as fallback
8973-
for the plugin generated properties).
8974-
Return the type and a flag indicating that we didn't fall back to getter.
8975-
"""
8976-
if isinstance(defn, Var):
8977-
# This function should not be called if the var is not ready.
8978-
assert defn.type is not None
8979-
return defn.type, True
8980-
first_item = defn.items[0]
8981-
assert isinstance(first_item, Decorator)
8982-
var = first_item.var
8983-
# This function may be called on non-custom properties, so we need
8984-
# to handle the situation when it is synthetic (plugin generated).
8985-
if var.setter_type is not None:
8986-
return var.setter_type, True
8987-
assert var.type is not None
8988-
return var.type, False
8989-
8990-
89918926
def get_property_type(t: ProperType) -> ProperType:
89928927
if isinstance(t, CallableType):
89938928
return get_proper_type(t.ret_type)

mypy/checkmember.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,8 @@ def analyze_member_var_access(
560560
elif isinstance(v, MypyFile):
561561
mx.chk.module_refs.add(v.fullname)
562562
return mx.chk.expr_checker.module_type(v)
563+
elif isinstance(v, TypeVarExpr):
564+
return mx.chk.named_type("typing.TypeVar")
563565
elif (
564566
not v
565567
and name not in ["__getattr__", "__setattr__", "__getattribute__"]
@@ -884,9 +886,8 @@ def analyze_var(
884886
if isinstance(typ, FunctionLike) and not typ.is_type_obj():
885887
call_type = typ
886888
elif var.is_property:
887-
call_type = get_proper_type(
888-
_analyze_member_access("__call__", typ, mx.copy_modified(self_type=typ))
889-
)
889+
deco_mx = mx.copy_modified(original_type=typ, self_type=typ, is_lvalue=False)
890+
call_type = get_proper_type(_analyze_member_access("__call__", typ, deco_mx))
890891
else:
891892
call_type = typ
892893

test-data/unit/check-classes.test

+6-4
Original file line numberDiff line numberDiff line change
@@ -7982,25 +7982,25 @@ class Parent:
79827982
class Child(Parent):
79837983
def foo(self, val: int) -> int: # E: Signature of "foo" incompatible with supertype "Parent" \
79847984
# N: Superclass: \
7985-
# N: None \
7985+
# N: <typing special form> \
79867986
# N: Subclass: \
79877987
# N: def foo(self, val: int) -> int
79887988
return val
79897989
def bar(self, val: str) -> str: # E: Signature of "bar" incompatible with supertype "Parent" \
79907990
# N: Superclass: \
7991-
# N: None \
7991+
# N: def __init__(self) -> bar \
79927992
# N: Subclass: \
79937993
# N: def bar(self, val: str) -> str
79947994
return val
79957995
def baz(self, val: float) -> float: # E: Signature of "baz" incompatible with supertype "Parent" \
79967996
# N: Superclass: \
7997-
# N: None \
7997+
# N: Module \
79987998
# N: Subclass: \
79997999
# N: def baz(self, val: float) -> float
80008000
return val
80018001
def foobar(self) -> bool: # E: Signature of "foobar" incompatible with supertype "Parent" \
80028002
# N: Superclass: \
8003-
# N: None \
8003+
# N: TypeVar \
80048004
# N: Subclass: \
80058005
# N: def foobar(self) -> bool
80068006
return False
@@ -8013,6 +8013,8 @@ a: int = child.foo(1)
80138013
b: str = child.bar("abc")
80148014
c: float = child.baz(3.4)
80158015
d: bool = child.foobar()
8016+
[builtins fixtures/module.pyi]
8017+
[typing fixtures/typing-full.pyi]
80168018

80178019
[case testGenericTupleTypeCreation]
80188020
from typing import Generic, Tuple, TypeVar

test-data/unit/check-functions.test

+6-6
Original file line numberDiff line numberDiff line change
@@ -2819,6 +2819,8 @@ class Child(Base):
28192819
@decorator
28202820
def foo(self) -> int:
28212821
return 42
2822+
reveal_type(Child().foo) # N: Revealed type is "builtins.int"
2823+
Child().foo = 1 # E: Property "foo" defined in "Child" is read-only
28222824

28232825
reveal_type(Child().foo) # N: Revealed type is "builtins.int"
28242826

@@ -2835,15 +2837,13 @@ class not_a_decorator:
28352837
def __init__(self, fn): ...
28362838

28372839
class BadChild2(Base):
2840+
# Override error not shown as accessing 'foo' on BadChild2 returns Any.
28382841
@property
28392842
@not_a_decorator
2840-
def foo(self) -> int: # E: "not_a_decorator" not callable \
2841-
# E: Signature of "foo" incompatible with supertype "Base" \
2842-
# N: Superclass: \
2843-
# N: int \
2844-
# N: Subclass: \
2845-
# N: not_a_decorator
2843+
def foo(self) -> int:
28462844
return 42
2845+
reveal_type(BadChild2().foo) # E: "not_a_decorator" not callable \
2846+
# N: Revealed type is "Any"
28472847
[builtins fixtures/property.pyi]
28482848

28492849
[case explicitOverride]

test-data/unit/check-plugin-attrs.test

+4-4
Original file line numberDiff line numberDiff line change
@@ -990,10 +990,10 @@ class C(A, B): pass
990990
@attr.s
991991
class D(A): pass
992992

993-
reveal_type(A.__lt__) # N: Revealed type is "def [_AT] (self: _AT`5, other: _AT`5) -> builtins.bool"
994-
reveal_type(B.__lt__) # N: Revealed type is "def [_AT] (self: _AT`6, other: _AT`6) -> builtins.bool"
995-
reveal_type(C.__lt__) # N: Revealed type is "def [_AT] (self: _AT`7, other: _AT`7) -> builtins.bool"
996-
reveal_type(D.__lt__) # N: Revealed type is "def [_AT] (self: _AT`8, other: _AT`8) -> builtins.bool"
993+
reveal_type(A.__lt__) # N: Revealed type is "def [_AT] (self: _AT`29, other: _AT`29) -> builtins.bool"
994+
reveal_type(B.__lt__) # N: Revealed type is "def [_AT] (self: _AT`30, other: _AT`30) -> builtins.bool"
995+
reveal_type(C.__lt__) # N: Revealed type is "def [_AT] (self: _AT`31, other: _AT`31) -> builtins.bool"
996+
reveal_type(D.__lt__) # N: Revealed type is "def [_AT] (self: _AT`32, other: _AT`32) -> builtins.bool"
997997

998998
A() < A()
999999
B() < B()

0 commit comments

Comments
 (0)