Skip to content

Commit fcabf19

Browse files
authored
Use checkmember.py to check multiple inheritance (#18876)
This is the third "major" PR towards #7724 This one is mostly straightforward. I tried to preserve the existing logic about mutable overrides (to minimize fallout), as currently we e.g. don't use the covariant mutable override error code here. In future we can separately "synchronize" mutable override logic across variable override, method override, and multiple inheritance code paths (as currently all three are subtly different).
1 parent 1214a74 commit fcabf19

File tree

3 files changed

+50
-97
lines changed

3 files changed

+50
-97
lines changed

mypy/checker.py

+48-93
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from mypy.erasetype import erase_type, erase_typevars, remove_instance_last_known_values
2525
from mypy.errorcodes import TYPE_VAR, UNUSED_AWAITABLE, UNUSED_COROUTINE, ErrorCode
2626
from mypy.errors import Errors, ErrorWatcher, report_internal_error
27-
from mypy.expandtype import expand_self_type, expand_type
27+
from mypy.expandtype import expand_type
2828
from mypy.literals import Key, extract_var_from_literal_hash, literal, literal_hash
2929
from mypy.maptype import map_instance_to_supertype
3030
from mypy.meet import is_overlapping_erased_types, is_overlapping_types, meet_types
@@ -161,7 +161,6 @@
161161
is_literal_type_like,
162162
is_singleton_type,
163163
make_simplified_union,
164-
map_type_from_supertype,
165164
true_only,
166165
try_expanding_sum_type_to_union,
167166
try_getting_int_literals_from_type,
@@ -2141,8 +2140,8 @@ def check_setter_type_override(self, defn: OverloadedFuncDef, base: TypeInfo) ->
21412140
is a custom settable property (i.e. where setter type is different from getter type).
21422141
Note that this check is contravariant.
21432142
"""
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)
2143+
typ, _ = self.node_type_from_base(defn.name, defn.info, defn, setter_type=True)
2144+
original_type, _ = self.node_type_from_base(defn.name, base, defn, setter_type=True)
21462145
# The caller should handle deferrals.
21472146
assert typ is not None and original_type is not None
21482147

@@ -2173,14 +2172,14 @@ def check_method_override_for_base_with_name(
21732172
override_class_or_static = defn.is_class or defn.is_static
21742173
else:
21752174
override_class_or_static = defn.func.is_class or defn.func.is_static
2176-
typ, _ = self.node_type_from_base(defn, defn.info)
2175+
typ, _ = self.node_type_from_base(defn.name, defn.info, defn)
21772176
assert typ is not None
21782177

21792178
original_node = base_attr.node
21802179
# `original_type` can be partial if (e.g.) it is originally an
21812180
# instance variable from an `__init__` block that becomes deferred.
21822181
supertype_ready = True
2183-
original_type, _ = self.node_type_from_base(defn, base, name_override=name)
2182+
original_type, _ = self.node_type_from_base(name, base, defn)
21842183
if original_type is None:
21852184
supertype_ready = False
21862185
if self.pass_num < self.last_pass:
@@ -2321,51 +2320,6 @@ def check_method_override_for_base_with_name(
23212320
)
23222321
return False
23232322

2324-
def bind_and_map_method(
2325-
self, sym: SymbolTableNode, typ: FunctionLike, sub_info: TypeInfo, super_info: TypeInfo
2326-
) -> FunctionLike:
2327-
"""Bind self-type and map type variables for a method.
2328-
2329-
Arguments:
2330-
sym: a symbol that points to method definition
2331-
typ: method type on the definition
2332-
sub_info: class where the method is used
2333-
super_info: class where the method was defined
2334-
"""
2335-
if isinstance(sym.node, (FuncDef, OverloadedFuncDef, Decorator)) and not is_static(
2336-
sym.node
2337-
):
2338-
if isinstance(sym.node, Decorator):
2339-
is_class_method = sym.node.func.is_class
2340-
else:
2341-
is_class_method = sym.node.is_class
2342-
2343-
mapped_typ = cast(FunctionLike, map_type_from_supertype(typ, sub_info, super_info))
2344-
active_self_type = fill_typevars(sub_info)
2345-
if isinstance(mapped_typ, Overloaded):
2346-
# If we have an overload, filter to overloads that match the self type.
2347-
# This avoids false positives for concrete subclasses of generic classes,
2348-
# see testSelfTypeOverrideCompatibility for an example.
2349-
filtered_items = []
2350-
for item in mapped_typ.items:
2351-
if not item.arg_types:
2352-
filtered_items.append(item)
2353-
item_arg = item.arg_types[0]
2354-
if isinstance(item_arg, TypeVarType):
2355-
item_arg = item_arg.upper_bound
2356-
if is_subtype(active_self_type, item_arg):
2357-
filtered_items.append(item)
2358-
# If we don't have any filtered_items, maybe it's always a valid override
2359-
# of the superclass? However if you get to that point you're in murky type
2360-
# territory anyway, so we just preserve the type and have the behaviour match
2361-
# that of older versions of mypy.
2362-
if filtered_items:
2363-
mapped_typ = Overloaded(filtered_items)
2364-
2365-
return bind_self(mapped_typ, active_self_type, is_class_method)
2366-
else:
2367-
return cast(FunctionLike, map_type_from_supertype(typ, sub_info, super_info))
2368-
23692323
def get_op_other_domain(self, tp: FunctionLike) -> Type | None:
23702324
if isinstance(tp, CallableType):
23712325
if tp.arg_kinds and tp.arg_kinds[0] == ARG_POS:
@@ -2882,6 +2836,7 @@ def check_multiple_inheritance(self, typ: TypeInfo) -> None:
28822836
self.check_compatibility(name, base, base2, typ)
28832837

28842838
def determine_type_of_member(self, sym: SymbolTableNode) -> Type | None:
2839+
# TODO: this duplicates both checkmember.py and analyze_ref_expr(), delete.
28852840
if sym.type is not None:
28862841
return sym.type
28872842
if isinstance(sym.node, SYMBOL_FUNCBASE_TYPES):
@@ -2901,7 +2856,6 @@ def determine_type_of_member(self, sym: SymbolTableNode) -> Type | None:
29012856
# Suppress any errors, they will be given when analyzing the corresponding node.
29022857
# Here we may have incorrect options and location context.
29032858
return self.expr_checker.alias_type_in_runtime_context(sym.node, ctx=sym.node)
2904-
# TODO: handle more node kinds here.
29052859
return None
29062860

29072861
def check_compatibility(
@@ -2932,50 +2886,47 @@ class C(B, A[int]): ... # this is unsafe because...
29322886
return
29332887
first = base1.names[name]
29342888
second = base2.names[name]
2935-
first_type = get_proper_type(self.determine_type_of_member(first))
2936-
second_type = get_proper_type(self.determine_type_of_member(second))
2889+
# Specify current_class explicitly as this function is called after leaving the class.
2890+
first_type, _ = self.node_type_from_base(name, base1, ctx, current_class=ctx)
2891+
second_type, _ = self.node_type_from_base(name, base2, ctx, current_class=ctx)
29372892

29382893
# TODO: use more principled logic to decide is_subtype() vs is_equivalent().
29392894
# We should rely on mutability of superclass node, not on types being Callable.
29402895
# (in particular handle settable properties with setter type different from getter).
29412896

2942-
# start with the special case that Instance can be a subtype of FunctionLike
2943-
call = None
2944-
if isinstance(first_type, Instance):
2945-
call = find_member("__call__", first_type, first_type, is_operator=True)
2946-
if call and isinstance(second_type, FunctionLike):
2947-
second_sig = self.bind_and_map_method(second, second_type, ctx, base2)
2948-
ok = is_subtype(call, second_sig, ignore_pos_arg_names=True)
2949-
elif isinstance(first_type, FunctionLike) and isinstance(second_type, FunctionLike):
2950-
if first_type.is_type_obj() and second_type.is_type_obj():
2897+
p_first_type = get_proper_type(first_type)
2898+
p_second_type = get_proper_type(second_type)
2899+
if isinstance(p_first_type, FunctionLike) and isinstance(p_second_type, FunctionLike):
2900+
if p_first_type.is_type_obj() and p_second_type.is_type_obj():
29512901
# For class objects only check the subtype relationship of the classes,
29522902
# since we allow incompatible overrides of '__init__'/'__new__'
29532903
ok = is_subtype(
2954-
left=fill_typevars_with_any(first_type.type_object()),
2955-
right=fill_typevars_with_any(second_type.type_object()),
2904+
left=fill_typevars_with_any(p_first_type.type_object()),
2905+
right=fill_typevars_with_any(p_second_type.type_object()),
29562906
)
29572907
else:
2958-
# First bind/map method types when necessary.
2959-
first_sig = self.bind_and_map_method(first, first_type, ctx, base1)
2960-
second_sig = self.bind_and_map_method(second, second_type, ctx, base2)
2961-
ok = is_subtype(first_sig, second_sig, ignore_pos_arg_names=True)
2908+
assert first_type and second_type
2909+
ok = is_subtype(first_type, second_type, ignore_pos_arg_names=True)
29622910
elif first_type and second_type:
2963-
if isinstance(first.node, Var):
2964-
first_type = get_proper_type(map_type_from_supertype(first_type, ctx, base1))
2965-
first_type = expand_self_type(first.node, first_type, fill_typevars(ctx))
2966-
if isinstance(second.node, Var):
2967-
second_type = get_proper_type(map_type_from_supertype(second_type, ctx, base2))
2968-
second_type = expand_self_type(second.node, second_type, fill_typevars(ctx))
2969-
ok = is_equivalent(first_type, second_type)
2970-
if not ok:
2971-
second_node = base2[name].node
2911+
if second.node is not None and not self.is_writable_attribute(second.node):
2912+
ok = is_subtype(first_type, second_type)
2913+
else:
2914+
ok = is_equivalent(first_type, second_type)
2915+
if ok:
29722916
if (
2973-
isinstance(second_type, FunctionLike)
2974-
and second_node is not None
2975-
and is_property(second_node)
2917+
first.node
2918+
and second.node
2919+
and self.is_writable_attribute(second.node)
2920+
and is_property(first.node)
2921+
and isinstance(first.node, Decorator)
2922+
and not isinstance(p_second_type, AnyType)
29762923
):
2977-
second_type = get_property_type(second_type)
2978-
ok = is_subtype(first_type, second_type)
2924+
self.msg.fail(
2925+
f'Cannot override writeable attribute "{name}" in base "{base2.name}"'
2926+
f' with read-only property in base "{base1.name}"',
2927+
ctx,
2928+
code=codes.OVERRIDE,
2929+
)
29792930
else:
29802931
if first_type is None:
29812932
self.msg.cannot_determine_type_in_base(name, base1.name, ctx)
@@ -3364,8 +3315,9 @@ def get_variable_type_context(self, inferred: Var, rvalue: Expression) -> Type |
33643315
# a class object for lambdas overriding methods, etc.
33653316
base_node = base.names[inferred.name].node
33663317
base_type, _ = self.node_type_from_base(
3367-
inferred,
3318+
inferred.name,
33683319
base,
3320+
inferred,
33693321
is_class=is_method(base_node)
33703322
or isinstance(base_node, Var)
33713323
and not is_instance_var(base_node),
@@ -3474,7 +3426,7 @@ def check_compatibility_all_supers(self, lvalue: RefExpr, rvalue: Expression) ->
34743426
rvalue_type = self.expr_checker.accept(rvalue, lvalue_node.type)
34753427
actual_lvalue_type = lvalue_node.type
34763428
lvalue_node.type = rvalue_type
3477-
lvalue_type, _ = self.node_type_from_base(lvalue_node, lvalue_node.info)
3429+
lvalue_type, _ = self.node_type_from_base(lvalue_node.name, lvalue_node.info, lvalue)
34783430
if lvalue_node.is_inferred and not lvalue_node.explicit_self_type:
34793431
lvalue_node.type = actual_lvalue_type
34803432

@@ -3493,7 +3445,7 @@ def check_compatibility_all_supers(self, lvalue: RefExpr, rvalue: Expression) ->
34933445
if is_private(lvalue_node.name):
34943446
continue
34953447

3496-
base_type, base_node = self.node_type_from_base(lvalue_node, base)
3448+
base_type, base_node = self.node_type_from_base(lvalue_node.name, base, lvalue)
34973449
custom_setter = is_custom_settable_property(base_node)
34983450
if isinstance(base_type, PartialType):
34993451
base_type = None
@@ -3513,7 +3465,7 @@ def check_compatibility_all_supers(self, lvalue: RefExpr, rvalue: Expression) ->
35133465
return
35143466
if lvalue_type and custom_setter:
35153467
base_type, _ = self.node_type_from_base(
3516-
lvalue_node, base, setter_type=True
3468+
lvalue_node.name, base, lvalue, setter_type=True
35173469
)
35183470
# Setter type for a custom property must be ready if
35193471
# the getter type is ready.
@@ -3565,12 +3517,13 @@ def check_compatibility_super(
35653517

35663518
def node_type_from_base(
35673519
self,
3568-
node: SymbolNode,
3520+
name: str,
35693521
base: TypeInfo,
3522+
context: Context,
35703523
*,
35713524
setter_type: bool = False,
35723525
is_class: bool = False,
3573-
name_override: str | None = None,
3526+
current_class: TypeInfo | None = None,
35743527
) -> tuple[Type | None, SymbolNode | None]:
35753528
"""Find a type for a name in base class.
35763529
@@ -3580,20 +3533,22 @@ def node_type_from_base(
35803533
If setter_type is True, return setter types for settable properties (otherwise the
35813534
getter type is returned).
35823535
"""
3583-
name = name_override or node.name
35843536
base_node = base.names.get(name)
35853537

35863538
# TODO: defer current node if the superclass node is not ready.
35873539
if (
35883540
not base_node
3589-
or isinstance(base_node.node, Var)
3541+
or isinstance(base_node.node, (Var, Decorator))
35903542
and not base_node.type
35913543
or isinstance(base_node.type, PartialType)
35923544
and base_node.type.type is not None
35933545
):
35943546
return None, None
35953547

3596-
self_type = self.scope.current_self_type()
3548+
if current_class is None:
3549+
self_type = self.scope.current_self_type()
3550+
else:
3551+
self_type = fill_typevars(current_class)
35973552
assert self_type is not None, "Internal error: base lookup outside class"
35983553
if isinstance(self_type, TupleType):
35993554
instance = tuple_fallback(self_type)
@@ -3605,7 +3560,7 @@ def node_type_from_base(
36053560
is_super=False,
36063561
is_operator=mypy.checkexpr.is_operator_method(name),
36073562
original_type=self_type,
3608-
context=node,
3563+
context=context,
36093564
chk=self,
36103565
suppress_errors=True,
36113566
)

test-data/unit/check-abstract.test

+1-4
Original file line numberDiff line numberDiff line change
@@ -990,7 +990,6 @@ class Mixin:
990990
class C(Mixin, A):
991991
pass
992992
[builtins fixtures/property.pyi]
993-
[out]
994993

995994
[case testMixinSubtypedProperty]
996995
class X:
@@ -1006,7 +1005,6 @@ class Mixin:
10061005
class C(Mixin, A):
10071006
pass
10081007
[builtins fixtures/property.pyi]
1009-
[out]
10101008

10111009
[case testMixinTypedPropertyReversed]
10121010
class A:
@@ -1015,10 +1013,9 @@ class A:
10151013
return "no"
10161014
class Mixin:
10171015
foo = "foo"
1018-
class C(A, Mixin): # E: Definition of "foo" in base class "A" is incompatible with definition in base class "Mixin"
1016+
class C(A, Mixin): # E: Cannot override writeable attribute "foo" in base "Mixin" with read-only property in base "A"
10191017
pass
10201018
[builtins fixtures/property.pyi]
1021-
[out]
10221019

10231020
-- Special cases
10241021
-- -------------

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

+1
Original file line numberDiff line numberDiff line change
@@ -1836,6 +1836,7 @@ class B:
18361836
class AB(A, B):
18371837
pass
18381838
[builtins fixtures/plugin_attrs.pyi]
1839+
[typing fixtures/typing-full.pyi]
18391840

18401841
[case testAttrsForwardReferenceInTypeVarBound]
18411842
from typing import TypeVar, Generic

0 commit comments

Comments
 (0)