Skip to content

Commit db2ae44

Browse files
committed
Use checkmember.py to check multiple inheritance
1 parent b6a662c commit db2ae44

File tree

3 files changed

+50
-97
lines changed

3 files changed

+50
-97
lines changed

mypy/checker.py

Lines changed: 48 additions & 93 deletions
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

Lines changed: 1 addition & 4 deletions
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

Lines changed: 1 addition & 0 deletions
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)