Skip to content

Commit 4629de4

Browse files
authored
Use checkmember.py to check variable overrides (#18847)
Fixes #5803 Fixes #18695 Fixes #17513 Fixes #13194 Fixes #12126 This is the first PR towards #7724, some notes: * I add a new generic `suppress_errors` flag to `MemberContext` mostly as a performance optimization, but it should also be handy in the following PRs * I noticed some inconsistencies with how we handle variable inference (e.g. we don't infer type at all if rvalue type is not compatible with superclass type). After all I decided to remove some of them, as it makes implementation of this PR simpler. * I added a bunch of TODOs, most of those will be addressed in following PRs. * A while ago we agreed that an explicit `Callable[...]` annotation in class body means how the type looks on an _instance_, but the override check used to handle this inconsistently (I add few `reveal_type()`s to tests to illustrate this).
1 parent 7846464 commit 4629de4

9 files changed

+274
-214
lines changed

mypy/checker.py

+133-140
Large diffs are not rendered by default.

mypy/checkmember.py

+47-38
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,12 @@ def __init__(
9393
original_type: Type,
9494
context: Context,
9595
chk: mypy.checker.TypeChecker,
96-
self_type: Type | None,
96+
self_type: Type | None = None,
9797
module_symbol_table: SymbolTable | None = None,
9898
no_deferral: bool = False,
9999
is_self: bool = False,
100100
rvalue: Expression | None = None,
101+
suppress_errors: bool = False,
101102
) -> None:
102103
self.is_lvalue = is_lvalue
103104
self.is_super = is_super
@@ -113,13 +114,18 @@ def __init__(
113114
if rvalue is not None:
114115
assert is_lvalue
115116
self.rvalue = rvalue
117+
self.suppress_errors = suppress_errors
116118

117119
def named_type(self, name: str) -> Instance:
118120
return self.chk.named_type(name)
119121

120122
def not_ready_callback(self, name: str, context: Context) -> None:
121123
self.chk.handle_cannot_determine_type(name, context)
122124

125+
def fail(self, msg: str) -> None:
126+
if not self.suppress_errors:
127+
self.msg.fail(msg, self.context)
128+
123129
def copy_modified(
124130
self,
125131
*,
@@ -138,6 +144,7 @@ def copy_modified(
138144
module_symbol_table=self.module_symbol_table,
139145
no_deferral=self.no_deferral,
140146
rvalue=self.rvalue,
147+
suppress_errors=self.suppress_errors,
141148
)
142149
if self_type is not None:
143150
mx.self_type = self_type
@@ -165,6 +172,7 @@ def analyze_member_access(
165172
no_deferral: bool = False,
166173
is_self: bool = False,
167174
rvalue: Expression | None = None,
175+
suppress_errors: bool = False,
168176
) -> Type:
169177
"""Return the type of attribute 'name' of 'typ'.
170178
@@ -191,6 +199,11 @@ def analyze_member_access(
191199
192200
'rvalue' can be provided optionally to infer better setter type when is_lvalue is True,
193201
most notably this helps for descriptors with overloaded __set__() method.
202+
203+
'suppress_errors' will skip any logic that is only needed to generate error messages.
204+
Note that this more of a performance optimization, one should not rely on this to not
205+
show any messages, as some may be show e.g. by callbacks called here,
206+
use msg.filter_errors(), if needed.
194207
"""
195208
mx = MemberContext(
196209
is_lvalue=is_lvalue,
@@ -204,6 +217,7 @@ def analyze_member_access(
204217
no_deferral=no_deferral,
205218
is_self=is_self,
206219
rvalue=rvalue,
220+
suppress_errors=suppress_errors,
207221
)
208222
result = _analyze_member_access(name, typ, mx, override_info)
209223
possible_literal = get_proper_type(result)
@@ -251,7 +265,8 @@ def _analyze_member_access(
251265
)
252266
return _analyze_member_access(name, typ.upper_bound, mx, override_info)
253267
elif isinstance(typ, DeletedType):
254-
mx.msg.deleted_as_rvalue(typ, mx.context)
268+
if not mx.suppress_errors:
269+
mx.msg.deleted_as_rvalue(typ, mx.context)
255270
return AnyType(TypeOfAny.from_error)
256271
return report_missing_attribute(mx.original_type, typ, name, mx)
257272

@@ -280,6 +295,8 @@ def report_missing_attribute(
280295
mx: MemberContext,
281296
override_info: TypeInfo | None = None,
282297
) -> Type:
298+
if mx.suppress_errors:
299+
return AnyType(TypeOfAny.from_error)
283300
error_code = mx.msg.has_no_attr(original_type, typ, name, mx.context, mx.module_symbol_table)
284301
if not mx.msg.prefer_simple_messages():
285302
if may_be_awaitable_attribute(name, typ, mx, override_info):
@@ -297,7 +314,7 @@ def analyze_instance_member_access(
297314
if name == "__init__" and not mx.is_super:
298315
# Accessing __init__ in statically typed code would compromise
299316
# type safety unless used via super().
300-
mx.msg.fail(message_registry.CANNOT_ACCESS_INIT, mx.context)
317+
mx.fail(message_registry.CANNOT_ACCESS_INIT)
301318
return AnyType(TypeOfAny.from_error)
302319

303320
# The base object has an instance type.
@@ -310,13 +327,14 @@ def analyze_instance_member_access(
310327
state.find_occurrences
311328
and info.name == state.find_occurrences[0]
312329
and name == state.find_occurrences[1]
330+
and not mx.suppress_errors
313331
):
314332
mx.msg.note("Occurrence of '{}.{}'".format(*state.find_occurrences), mx.context)
315333

316334
# Look up the member. First look up the method dictionary.
317335
method = info.get_method(name)
318336
if method and not isinstance(method, Decorator):
319-
if mx.is_super:
337+
if mx.is_super and not mx.suppress_errors:
320338
validate_super_call(method, mx)
321339

322340
if method.is_property:
@@ -327,7 +345,7 @@ def analyze_instance_member_access(
327345
mx.chk.warn_deprecated(items[1], mx.context)
328346
return analyze_var(name, getter.var, typ, mx)
329347

330-
if mx.is_lvalue:
348+
if mx.is_lvalue and not mx.suppress_errors:
331349
mx.msg.cant_assign_to_method(mx.context)
332350
if not isinstance(method, OverloadedFuncDef):
333351
signature = function_type(method, mx.named_type("builtins.function"))
@@ -361,7 +379,6 @@ def validate_super_call(node: FuncBase, mx: MemberContext) -> None:
361379
unsafe_super = False
362380
if isinstance(node, FuncDef) and node.is_trivial_body:
363381
unsafe_super = True
364-
impl = node
365382
elif isinstance(node, OverloadedFuncDef):
366383
if node.impl:
367384
impl = node.impl if isinstance(node.impl, FuncDef) else node.impl.func
@@ -505,7 +522,7 @@ def analyze_member_var_access(
505522
if isinstance(vv, Decorator):
506523
# The associated Var node of a decorator contains the type.
507524
v = vv.var
508-
if mx.is_super:
525+
if mx.is_super and not mx.suppress_errors:
509526
validate_super_call(vv.func, mx)
510527

511528
if isinstance(vv, TypeInfo):
@@ -603,7 +620,7 @@ def analyze_member_var_access(
603620
if not itype.extra_attrs.mod_name:
604621
return itype.extra_attrs.attrs[name]
605622

606-
if mx.is_super:
623+
if mx.is_super and not mx.suppress_errors:
607624
mx.msg.undefined_in_superclass(name, mx.context)
608625
return AnyType(TypeOfAny.from_error)
609626
else:
@@ -669,11 +686,10 @@ def analyze_descriptor_access(descriptor_type: Type, mx: MemberContext) -> Type:
669686

670687
dunder_get = descriptor_type.type.get_method("__get__")
671688
if dunder_get is None:
672-
mx.msg.fail(
689+
mx.fail(
673690
message_registry.DESCRIPTOR_GET_NOT_CALLABLE.format(
674691
descriptor_type.str_with_options(mx.msg.options)
675-
),
676-
mx.context,
692+
)
677693
)
678694
return AnyType(TypeOfAny.from_error)
679695

@@ -732,11 +748,10 @@ def analyze_descriptor_access(descriptor_type: Type, mx: MemberContext) -> Type:
732748
return inferred_dunder_get_type
733749

734750
if not isinstance(inferred_dunder_get_type, CallableType):
735-
mx.msg.fail(
751+
mx.fail(
736752
message_registry.DESCRIPTOR_GET_NOT_CALLABLE.format(
737753
descriptor_type.str_with_options(mx.msg.options)
738-
),
739-
mx.context,
754+
)
740755
)
741756
return AnyType(TypeOfAny.from_error)
742757

@@ -747,11 +762,10 @@ def analyze_descriptor_assign(descriptor_type: Instance, mx: MemberContext) -> T
747762
instance_type = get_proper_type(mx.self_type)
748763
dunder_set = descriptor_type.type.get_method("__set__")
749764
if dunder_set is None:
750-
mx.chk.fail(
765+
mx.fail(
751766
message_registry.DESCRIPTOR_SET_NOT_CALLABLE.format(
752767
descriptor_type.str_with_options(mx.msg.options)
753-
),
754-
mx.context,
768+
).value
755769
)
756770
return AnyType(TypeOfAny.from_error)
757771

@@ -851,11 +865,11 @@ def analyze_var(
851865
if typ:
852866
if isinstance(typ, PartialType):
853867
return mx.chk.handle_partial_var_type(typ, mx.is_lvalue, var, mx.context)
854-
if mx.is_lvalue and var.is_property and not var.is_settable_property:
855-
# TODO allow setting attributes in subclass (although it is probably an error)
856-
mx.msg.read_only_property(name, itype.type, mx.context)
857-
if mx.is_lvalue and var.is_classvar:
858-
mx.msg.cant_assign_to_classvar(name, mx.context)
868+
if mx.is_lvalue and not mx.suppress_errors:
869+
if var.is_property and not var.is_settable_property:
870+
mx.msg.read_only_property(name, itype.type, mx.context)
871+
if var.is_classvar:
872+
mx.msg.cant_assign_to_classvar(name, mx.context)
859873
t = freshen_all_functions_type_vars(typ)
860874
t = expand_self_type_if_needed(t, mx, var, original_itype)
861875
t = expand_type_by_instance(t, itype)
@@ -875,11 +889,10 @@ def analyze_var(
875889
call_type = typ
876890

877891
if isinstance(call_type, FunctionLike) and not call_type.is_type_obj():
878-
if mx.is_lvalue:
879-
if var.is_property:
880-
if not var.is_settable_property:
881-
mx.msg.read_only_property(name, itype.type, mx.context)
882-
else:
892+
if mx.is_lvalue and not mx.suppress_errors:
893+
if var.is_property and not var.is_settable_property:
894+
mx.msg.read_only_property(name, itype.type, mx.context)
895+
elif not var.is_property:
883896
mx.msg.cant_assign_to_method(mx.context)
884897

885898
if not var.is_staticmethod:
@@ -1073,22 +1086,20 @@ def analyze_class_attribute_access(
10731086

10741087
is_decorated = isinstance(node.node, Decorator)
10751088
is_method = is_decorated or isinstance(node.node, FuncBase)
1076-
if mx.is_lvalue:
1089+
if mx.is_lvalue and not mx.suppress_errors:
10771090
if is_method:
10781091
mx.msg.cant_assign_to_method(mx.context)
10791092
if isinstance(node.node, TypeInfo):
1080-
mx.msg.fail(message_registry.CANNOT_ASSIGN_TO_TYPE, mx.context)
1093+
mx.fail(message_registry.CANNOT_ASSIGN_TO_TYPE)
10811094

10821095
# Refuse class attribute access if slot defined
10831096
if info.slots and name in info.slots:
1084-
mx.msg.fail(message_registry.CLASS_VAR_CONFLICTS_SLOTS.format(name), mx.context)
1097+
mx.fail(message_registry.CLASS_VAR_CONFLICTS_SLOTS.format(name))
10851098

10861099
# If a final attribute was declared on `self` in `__init__`, then it
10871100
# can't be accessed on the class object.
10881101
if node.implicit and isinstance(node.node, Var) and node.node.is_final:
1089-
mx.msg.fail(
1090-
message_registry.CANNOT_ACCESS_FINAL_INSTANCE_ATTR.format(node.node.name), mx.context
1091-
)
1102+
mx.fail(message_registry.CANNOT_ACCESS_FINAL_INSTANCE_ATTR.format(node.node.name))
10921103

10931104
# An assignment to final attribute on class object is also always an error,
10941105
# independently of types.
@@ -1146,7 +1157,7 @@ def analyze_class_attribute_access(
11461157
message = message_registry.GENERIC_CLASS_VAR_ACCESS
11471158
else:
11481159
message = message_registry.GENERIC_INSTANCE_VAR_CLASS_ACCESS
1149-
mx.msg.fail(message, mx.context)
1160+
mx.fail(message)
11501161
t = expand_self_type_if_needed(t, mx, node.node, itype, is_class=True)
11511162
# Erase non-mapped variables, but keep mapped ones, even if there is an error.
11521163
# In the above example this means that we infer following types:
@@ -1176,9 +1187,7 @@ def analyze_class_attribute_access(
11761187
return AnyType(TypeOfAny.special_form)
11771188

11781189
if isinstance(node.node, TypeVarExpr):
1179-
mx.msg.fail(
1180-
message_registry.CANNOT_USE_TYPEVAR_AS_EXPRESSION.format(info.name, name), mx.context
1181-
)
1190+
mx.fail(message_registry.CANNOT_USE_TYPEVAR_AS_EXPRESSION.format(info.name, name))
11821191
return AnyType(TypeOfAny.from_error)
11831192

11841193
# TODO: some logic below duplicates analyze_ref_expr in checkexpr.py
@@ -1267,7 +1276,7 @@ def analyze_typeddict_access(
12671276
typ, mx.context.index, setitem=True
12681277
)
12691278
assigned_readonly_keys = typ.readonly_keys & key_names
1270-
if assigned_readonly_keys:
1279+
if assigned_readonly_keys and not mx.suppress_errors:
12711280
mx.msg.readonly_keys_mutated(assigned_readonly_keys, context=mx.context)
12721281
else:
12731282
# It can also be `a.__setitem__(...)` direct call.

0 commit comments

Comments
 (0)