Skip to content

Commit fd5bf8b

Browse files
Merge branch 'master' into patch-3
2 parents 0ff5841 + 0755a61 commit fd5bf8b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+1360
-272
lines changed

mypy/checker.py

Lines changed: 95 additions & 76 deletions
Large diffs are not rendered by default.

mypy/checkexpr.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2338,10 +2338,10 @@ def apply_inferred_arguments(
23382338
# Report error if some of the variables could not be solved. In that
23392339
# case assume that all variables have type Any to avoid extra
23402340
# bogus error messages.
2341-
for i, inferred_type in enumerate(inferred_args):
2341+
for inferred_type, tv in zip(inferred_args, callee_type.variables):
23422342
if not inferred_type or has_erased_component(inferred_type):
23432343
# Could not infer a non-trivial type for a type variable.
2344-
self.msg.could_not_infer_type_arguments(callee_type, i + 1, context)
2344+
self.msg.could_not_infer_type_arguments(callee_type, tv, context)
23452345
inferred_args = [AnyType(TypeOfAny.from_error)] * len(inferred_args)
23462346
# Apply the inferred types to the function type. In this case the
23472347
# return type must be CallableType, since we give the right number of type

mypy/checkmember.py

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@
4545
freeze_all_type_vars,
4646
function_type,
4747
get_all_type_vars,
48-
get_type_vars,
4948
make_simplified_union,
5049
supported_self_type,
5150
tuple_fallback,
@@ -371,8 +370,6 @@ def analyze_instance_member_access(
371370
signature, mx.self_type, method.is_class, mx.context, name, mx.msg
372371
)
373372
signature = bind_self(signature, mx.self_type, is_classmethod=method.is_class)
374-
# TODO: should we skip these steps for static methods as well?
375-
# Since generic static methods should not be allowed.
376373
typ = map_instance_to_supertype(typ, method.info)
377374
member_type = expand_type_by_instance(signature, typ)
378375
freeze_all_type_vars(member_type)
@@ -1198,34 +1195,42 @@ def analyze_class_attribute_access(
11981195

11991196
if isinstance(node.node, Var):
12001197
assert isuper is not None
1198+
object_type = get_proper_type(mx.self_type)
12011199
# Check if original variable type has type variables. For example:
12021200
# class C(Generic[T]):
12031201
# x: T
12041202
# C.x # Error, ambiguous access
12051203
# C[int].x # Also an error, since C[int] is same as C at runtime
12061204
# Exception is Self type wrapped in ClassVar, that is safe.
1205+
prohibit_self = not node.node.is_classvar
12071206
def_vars = set(node.node.info.defn.type_vars)
1208-
if not node.node.is_classvar and node.node.info.self_type:
1207+
if prohibit_self and node.node.info.self_type:
12091208
def_vars.add(node.node.info.self_type)
1210-
# TODO: should we include ParamSpec etc. here (i.e. use get_all_type_vars)?
1211-
typ_vars = set(get_type_vars(t))
1212-
if def_vars & typ_vars:
1213-
# Exception: access on Type[...], including first argument of class methods is OK.
1214-
if not isinstance(get_proper_type(mx.original_type), TypeType) or node.implicit:
1215-
if node.node.is_classvar:
1216-
message = message_registry.GENERIC_CLASS_VAR_ACCESS
1217-
else:
1218-
message = message_registry.GENERIC_INSTANCE_VAR_CLASS_ACCESS
1219-
mx.fail(message)
1209+
# Exception: access on Type[...], including first argument of class methods is OK.
1210+
prohibit_generic = not isinstance(object_type, TypeType) or node.implicit
1211+
if prohibit_generic and def_vars & set(get_all_type_vars(t)):
1212+
if node.node.is_classvar:
1213+
message = message_registry.GENERIC_CLASS_VAR_ACCESS
1214+
else:
1215+
message = message_registry.GENERIC_INSTANCE_VAR_CLASS_ACCESS
1216+
mx.fail(message)
12201217
t = expand_self_type_if_needed(t, mx, node.node, itype, is_class=True)
1218+
t = expand_type_by_instance(t, isuper)
12211219
# Erase non-mapped variables, but keep mapped ones, even if there is an error.
12221220
# In the above example this means that we infer following types:
12231221
# C.x -> Any
12241222
# C[int].x -> int
1225-
t = erase_typevars(expand_type_by_instance(t, isuper), {tv.id for tv in def_vars})
1226-
1227-
is_classmethod = (is_decorated and cast(Decorator, node.node).func.is_class) or (
1228-
isinstance(node.node, SYMBOL_FUNCBASE_TYPES) and node.node.is_class
1223+
if prohibit_generic:
1224+
erase_vars = set(itype.type.defn.type_vars)
1225+
if prohibit_self and itype.type.self_type:
1226+
erase_vars.add(itype.type.self_type)
1227+
t = erase_typevars(t, {tv.id for tv in erase_vars})
1228+
1229+
is_classmethod = (
1230+
(is_decorated and cast(Decorator, node.node).func.is_class)
1231+
or (isinstance(node.node, SYMBOL_FUNCBASE_TYPES) and node.node.is_class)
1232+
or isinstance(node.node, Var)
1233+
and node.node.is_classmethod
12291234
)
12301235
is_staticmethod = (is_decorated and cast(Decorator, node.node).func.is_static) or (
12311236
isinstance(node.node, SYMBOL_FUNCBASE_TYPES) and node.node.is_static
@@ -1237,7 +1242,12 @@ def analyze_class_attribute_access(
12371242
is_trivial_self = node.node.func.is_trivial_self and not node.node.decorators
12381243
elif isinstance(node.node, (FuncDef, OverloadedFuncDef)):
12391244
is_trivial_self = node.node.is_trivial_self
1240-
if isinstance(t, FunctionLike) and is_classmethod and not is_trivial_self:
1245+
if (
1246+
isinstance(t, FunctionLike)
1247+
and is_classmethod
1248+
and not is_trivial_self
1249+
and not t.bound()
1250+
):
12411251
t = check_self_arg(t, mx.self_type, False, mx.context, name, mx.msg)
12421252
t = add_class_tvars(
12431253
t,
@@ -1406,7 +1416,7 @@ class B(A[str]): pass
14061416
tvars = original_vars if original_vars is not None else []
14071417
if not mx.preserve_type_var_ids:
14081418
t = freshen_all_functions_type_vars(t)
1409-
if is_classmethod:
1419+
if is_classmethod and not t.is_bound:
14101420
if is_trivial_self:
14111421
t = bind_self_fast(t, mx.self_type)
14121422
else:

mypy/constraints.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1335,6 +1335,11 @@ def visit_tuple_type(self, template: TupleType) -> list[Constraint]:
13351335
res.extend(
13361336
infer_constraints(template_items[i], actual_items[i], self.direction)
13371337
)
1338+
res.extend(
1339+
infer_constraints(
1340+
template.partial_fallback, actual.partial_fallback, self.direction
1341+
)
1342+
)
13381343
return res
13391344
elif isinstance(actual, AnyType):
13401345
return self.infer_against_any(template.items, actual)

mypy/errors.py

Lines changed: 83 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44
import sys
55
import traceback
66
from collections import defaultdict
7-
from collections.abc import Iterable
7+
from collections.abc import Iterable, Iterator
8+
from itertools import chain
89
from typing import Callable, Final, NoReturn, Optional, TextIO, TypeVar
910
from typing_extensions import Literal, Self, TypeAlias as _TypeAlias
1011

1112
from mypy import errorcodes as codes
1213
from mypy.error_formatter import ErrorFormatter
1314
from mypy.errorcodes import IMPORT, IMPORT_NOT_FOUND, IMPORT_UNTYPED, ErrorCode, mypy_error_codes
15+
from mypy.nodes import Context
1416
from mypy.options import Options
1517
from mypy.scope import Scope
1618
from mypy.util import DEFAULT_SOURCE_OFFSET, is_typeshed_file
@@ -219,23 +221,43 @@ def filtered_errors(self) -> list[ErrorInfo]:
219221
return self._filtered
220222

221223

222-
class LoopErrorWatcher(ErrorWatcher):
223-
"""Error watcher that filters and separately collects `unreachable` errors,
224-
`redundant-expr` and `redundant-casts` errors, and revealed types when analysing
225-
loops iteratively to help avoid making too-hasty reports."""
224+
class IterationDependentErrors:
225+
"""An `IterationDependentErrors` instance serves to collect the `unreachable`,
226+
`redundant-expr`, and `redundant-casts` errors, as well as the revealed types,
227+
handled by the individual `IterationErrorWatcher` instances sequentially applied to
228+
the same code section."""
226229

227-
# Meaning of the tuple items: ErrorCode, message, line, column, end_line, end_column:
228-
uselessness_errors: set[tuple[ErrorCode, str, int, int, int, int]]
230+
# One set of `unreachable`, `redundant-expr`, and `redundant-casts` errors per
231+
# iteration step. Meaning of the tuple items: ErrorCode, message, line, column,
232+
# end_line, end_column.
233+
uselessness_errors: list[set[tuple[ErrorCode, str, int, int, int, int]]]
229234

230-
# Meaning of the tuple items: function_or_member, line, column, end_line, end_column:
235+
# One set of unreachable line numbers per iteration step. Not only the lines where
236+
# the error report occurs but really all unreachable lines.
237+
unreachable_lines: list[set[int]]
238+
239+
# One set of revealed types for each `reveal_type` statement. Each created set can
240+
# grow during the iteration. Meaning of the tuple items: function_or_member, line,
241+
# column, end_line, end_column:
231242
revealed_types: dict[tuple[str | None, int, int, int, int], set[str]]
232243

233-
# Not only the lines where the error report occurs but really all unreachable lines:
234-
unreachable_lines: set[int]
244+
def __init__(self) -> None:
245+
self.uselessness_errors = []
246+
self.unreachable_lines = []
247+
self.revealed_types = defaultdict(set)
248+
249+
250+
class IterationErrorWatcher(ErrorWatcher):
251+
"""Error watcher that filters and separately collects `unreachable` errors,
252+
`redundant-expr` and `redundant-casts` errors, and revealed types when analysing
253+
code sections iteratively to help avoid making too-hasty reports."""
254+
255+
iteration_dependent_errors: IterationDependentErrors
235256

236257
def __init__(
237258
self,
238259
errors: Errors,
260+
iteration_dependent_errors: IterationDependentErrors,
239261
*,
240262
filter_errors: bool | Callable[[str, ErrorInfo], bool] = False,
241263
save_filtered_errors: bool = False,
@@ -247,31 +269,71 @@ def __init__(
247269
save_filtered_errors=save_filtered_errors,
248270
filter_deprecated=filter_deprecated,
249271
)
250-
self.uselessness_errors = set()
251-
self.unreachable_lines = set()
252-
self.revealed_types = defaultdict(set)
272+
self.iteration_dependent_errors = iteration_dependent_errors
273+
iteration_dependent_errors.uselessness_errors.append(set())
274+
iteration_dependent_errors.unreachable_lines.append(set())
253275

254276
def on_error(self, file: str, info: ErrorInfo) -> bool:
277+
"""Filter out the "iteration-dependent" errors and notes and store their
278+
information to handle them after iteration is completed."""
279+
280+
iter_errors = self.iteration_dependent_errors
255281

256282
if info.code in (codes.UNREACHABLE, codes.REDUNDANT_EXPR, codes.REDUNDANT_CAST):
257-
self.uselessness_errors.add(
283+
iter_errors.uselessness_errors[-1].add(
258284
(info.code, info.message, info.line, info.column, info.end_line, info.end_column)
259285
)
260286
if info.code == codes.UNREACHABLE:
261-
self.unreachable_lines.update(range(info.line, info.end_line + 1))
287+
iter_errors.unreachable_lines[-1].update(range(info.line, info.end_line + 1))
262288
return True
263289

264290
if info.code == codes.MISC and info.message.startswith("Revealed type is "):
265291
key = info.function_or_member, info.line, info.column, info.end_line, info.end_column
266292
types = info.message.split('"')[1]
267293
if types.startswith("Union["):
268-
self.revealed_types[key].update(types[6:-1].split(", "))
294+
iter_errors.revealed_types[key].update(types[6:-1].split(", "))
269295
else:
270-
self.revealed_types[key].add(types)
296+
iter_errors.revealed_types[key].add(types)
271297
return True
272298

273299
return super().on_error(file, info)
274300

301+
def yield_error_infos(self) -> Iterator[tuple[str, Context, ErrorCode]]:
302+
"""Report only those `unreachable`, `redundant-expr`, and `redundant-casts`
303+
errors that could not be ruled out in any iteration step."""
304+
305+
persistent_uselessness_errors = set()
306+
iter_errors = self.iteration_dependent_errors
307+
for candidate in set(chain(*iter_errors.uselessness_errors)):
308+
if all(
309+
(candidate in errors) or (candidate[2] in lines)
310+
for errors, lines in zip(
311+
iter_errors.uselessness_errors, iter_errors.unreachable_lines
312+
)
313+
):
314+
persistent_uselessness_errors.add(candidate)
315+
for error_info in persistent_uselessness_errors:
316+
context = Context(line=error_info[2], column=error_info[3])
317+
context.end_line = error_info[4]
318+
context.end_column = error_info[5]
319+
yield error_info[1], context, error_info[0]
320+
321+
def yield_note_infos(self, options: Options) -> Iterator[tuple[str, Context]]:
322+
"""Yield all types revealed in at least one iteration step."""
323+
324+
for note_info, types in self.iteration_dependent_errors.revealed_types.items():
325+
sorted_ = sorted(types, key=lambda typ: typ.lower())
326+
if len(types) == 1:
327+
revealed = sorted_[0]
328+
elif options.use_or_syntax():
329+
revealed = " | ".join(sorted_)
330+
else:
331+
revealed = f"Union[{', '.join(sorted_)}]"
332+
context = Context(line=note_info[1], column=note_info[2])
333+
context.end_line = note_info[3]
334+
context.end_column = note_info[4]
335+
yield f'Revealed type is "{revealed}"', context
336+
275337

276338
class Errors:
277339
"""Container for compile errors.
@@ -786,6 +848,8 @@ def generate_unused_ignore_errors(self, file: str) -> None:
786848
code=codes.UNUSED_IGNORE,
787849
blocker=False,
788850
only_once=False,
851+
origin=(self.file, [line]),
852+
target=self.target_module,
789853
)
790854
self._add_error_info(file, info)
791855

@@ -837,6 +901,8 @@ def generate_ignore_without_code_errors(
837901
code=codes.IGNORE_WITHOUT_CODE,
838902
blocker=False,
839903
only_once=False,
904+
origin=(self.file, [line]),
905+
target=self.target_module,
840906
)
841907
self._add_error_info(file, info)
842908

mypy/message_registry.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,6 @@ def with_additional_msg(self, info: str) -> ErrorMessage:
253253
'Cannot override class variable (previously declared on base class "{}") with instance '
254254
"variable"
255255
)
256-
CLASS_VAR_WITH_TYPEVARS: Final = "ClassVar cannot contain type variables"
257256
CLASS_VAR_WITH_GENERIC_SELF: Final = "ClassVar cannot contain Self type in generic classes"
258257
CLASS_VAR_OUTSIDE_OF_CLASS: Final = "ClassVar can only be used for assignments in class body"
259258

@@ -353,6 +352,10 @@ def with_additional_msg(self, info: str) -> ErrorMessage:
353352
"TypeVar constraint type cannot be parametrized by type variables", codes.MISC
354353
)
355354

355+
TYPE_VAR_REDECLARED_IN_NESTED_CLASS: Final = ErrorMessage(
356+
'Type variable "{}" is bound by an outer class', codes.VALID_TYPE
357+
)
358+
356359
TYPE_ALIAS_WITH_YIELD_EXPRESSION: Final = ErrorMessage(
357360
"Yield expression cannot be used within a type alias", codes.SYNTAX
358361
)

mypy/messages.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1370,11 +1370,14 @@ def incompatible_type_application(
13701370
self.fail(f"Type application has too few types ({s})", context)
13711371

13721372
def could_not_infer_type_arguments(
1373-
self, callee_type: CallableType, n: int, context: Context
1373+
self, callee_type: CallableType, tv: TypeVarLikeType, context: Context
13741374
) -> None:
13751375
callee_name = callable_name(callee_type)
1376-
if callee_name is not None and n > 0:
1377-
self.fail(f"Cannot infer type argument {n} of {callee_name}", context)
1376+
if callee_name is not None:
1377+
self.fail(
1378+
f"Cannot infer value of type parameter {format_type(tv, self.options)} of {callee_name}",
1379+
context,
1380+
)
13781381
if callee_name == "<dict>":
13791382
# Invariance in key type causes more of these errors than we would want.
13801383
self.note(

mypy/nodes.py

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,8 @@ def __init__(self) -> None:
508508
self.info = FUNC_NO_INFO
509509
self.is_property = False
510510
self.is_class = False
511+
# Is this a `@staticmethod` (explicit or implicit)?
512+
# Note: use has_self_or_cls_argument to check if there is `self` or `cls` argument
511513
self.is_static = False
512514
self.is_final = False
513515
self.is_explicit_override = False
@@ -524,6 +526,15 @@ def name(self) -> str:
524526
def fullname(self) -> str:
525527
return self._fullname
526528

529+
@property
530+
def has_self_or_cls_argument(self) -> bool:
531+
"""If used as a method, does it have an argument for method binding (`self`, `cls`)?
532+
533+
This is true for `__new__` even though `__new__` does not undergo method binding,
534+
because we still usually assume that `cls` corresponds to the enclosing class.
535+
"""
536+
return not self.is_static or self.name == "__new__"
537+
527538

528539
OverloadPart: _TypeAlias = Union["FuncDef", "Decorator"]
529540

@@ -3371,15 +3382,25 @@ def calculate_metaclass_type(self) -> mypy.types.Instance | None:
33713382
return declared
33723383
if self._fullname == "builtins.type":
33733384
return mypy.types.Instance(self, [])
3374-
candidates = [
3375-
s.declared_metaclass
3376-
for s in self.mro
3377-
if s.declared_metaclass is not None and s.declared_metaclass.type is not None
3378-
]
3379-
for c in candidates:
3380-
if all(other.type in c.type.mro for other in candidates):
3381-
return c
3382-
return None
3385+
3386+
winner = declared
3387+
for super_class in self.mro[1:]:
3388+
super_meta = super_class.declared_metaclass
3389+
if super_meta is None or super_meta.type is None:
3390+
continue
3391+
if winner is None:
3392+
winner = super_meta
3393+
continue
3394+
if winner.type.has_base(super_meta.type.fullname):
3395+
continue
3396+
if super_meta.type.has_base(winner.type.fullname):
3397+
winner = super_meta
3398+
continue
3399+
# metaclass conflict
3400+
winner = None
3401+
break
3402+
3403+
return winner
33833404

33843405
def is_metaclass(self, *, precise: bool = False) -> bool:
33853406
return (

0 commit comments

Comments
 (0)