Skip to content

Commit 2f22993

Browse files
committed
Merge remote-tracking branch 'upstream/master' into bugfix/gh-19312-setter-of-nothing
2 parents 25ea889 + b678d9f commit 2f22993

Some content is hidden

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

64 files changed

+1570
-293
lines changed

docs/source/generics.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -630,7 +630,7 @@ Let us illustrate this by few simple examples:
630630
631631
my_circles: list[Circle] = []
632632
add_one(my_circles) # This may appear safe, but...
633-
my_circles[-1].rotate() # ...this will fail, since my_circles[0] is now a Shape, not a Circle
633+
my_circles[0].rotate() # ...this will fail, since my_circles[0] is now a Shape, not a Circle
634634
635635
Another example of invariant type is ``dict``. Most mutable containers
636636
are invariant.

misc/perf_compare.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,13 @@ def heading(s: str) -> None:
3535
print()
3636

3737

38-
def build_mypy(target_dir: str) -> None:
38+
def build_mypy(target_dir: str, multi_file: bool) -> None:
3939
env = os.environ.copy()
4040
env["CC"] = "clang"
4141
env["MYPYC_OPT_LEVEL"] = "2"
4242
env["PYTHONHASHSEED"] = "1"
43+
if multi_file:
44+
env["MYPYC_MULTI_FILE"] = "1"
4345
cmd = [sys.executable, "setup.py", "--use-mypyc", "build_ext", "--inplace"]
4446
subprocess.run(cmd, env=env, check=True, cwd=target_dir)
4547

@@ -110,6 +112,12 @@ def main() -> None:
110112
action="store_true",
111113
help="measure incremental run (fully cached)",
112114
)
115+
parser.add_argument(
116+
"--multi-file",
117+
default=False,
118+
action="store_true",
119+
help="compile each mypy module to a separate C file (reduces RAM use)",
120+
)
113121
parser.add_argument(
114122
"--dont-setup",
115123
default=False,
@@ -127,9 +135,9 @@ def main() -> None:
127135
parser.add_argument(
128136
"-j",
129137
metavar="N",
130-
default=8,
138+
default=4,
131139
type=int,
132-
help="set maximum number of parallel builds (default=8)",
140+
help="set maximum number of parallel builds (default=4) -- high numbers require a lot of RAM!",
133141
)
134142
parser.add_argument(
135143
"-r",
@@ -155,6 +163,7 @@ def main() -> None:
155163
args = parser.parse_args()
156164
incremental: bool = args.incremental
157165
dont_setup: bool = args.dont_setup
166+
multi_file: bool = args.multi_file
158167
commits = args.commit
159168
num_runs: int = args.num_runs + 1
160169
max_workers: int = args.j
@@ -185,7 +194,9 @@ def main() -> None:
185194
print("(This will take a while...)")
186195

187196
with ThreadPoolExecutor(max_workers=max_workers) as executor:
188-
futures = [executor.submit(build_mypy, target_dir) for target_dir in target_dirs]
197+
futures = [
198+
executor.submit(build_mypy, target_dir, multi_file) for target_dir in target_dirs
199+
]
189200
for future in as_completed(futures):
190201
future.result()
191202

mypy/checker.py

Lines changed: 133 additions & 82 deletions
Large diffs are not rendered by default.

mypy/checkexpr.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,8 @@ class ExpressionChecker(ExpressionVisitor[Type], ExpressionCheckerSharedApi):
318318
strfrm_checker: StringFormatterChecker
319319
plugin: Plugin
320320

321+
_arg_infer_context_cache: ArgumentInferContext | None
322+
321323
def __init__(
322324
self,
323325
chk: mypy.checker.TypeChecker,
@@ -352,6 +354,8 @@ def __init__(
352354
self.is_callee = False
353355
type_state.infer_polymorphic = not self.chk.options.old_type_inference
354356

357+
self._arg_infer_context_cache = None
358+
355359
def reset(self) -> None:
356360
self.resolved_type = {}
357361

@@ -2277,9 +2281,11 @@ def infer_function_type_arguments_pass2(
22772281
return callee_type, inferred_args
22782282

22792283
def argument_infer_context(self) -> ArgumentInferContext:
2280-
return ArgumentInferContext(
2281-
self.chk.named_type("typing.Mapping"), self.chk.named_type("typing.Iterable")
2282-
)
2284+
if self._arg_infer_context_cache is None:
2285+
self._arg_infer_context_cache = ArgumentInferContext(
2286+
self.chk.named_type("typing.Mapping"), self.chk.named_type("typing.Iterable")
2287+
)
2288+
return self._arg_infer_context_cache
22832289

22842290
def get_arg_infer_passes(
22852291
self,
@@ -2338,10 +2344,10 @@ def apply_inferred_arguments(
23382344
# Report error if some of the variables could not be solved. In that
23392345
# case assume that all variables have type Any to avoid extra
23402346
# bogus error messages.
2341-
for i, inferred_type in enumerate(inferred_args):
2347+
for inferred_type, tv in zip(inferred_args, callee_type.variables):
23422348
if not inferred_type or has_erased_component(inferred_type):
23432349
# Could not infer a non-trivial type for a type variable.
2344-
self.msg.could_not_infer_type_arguments(callee_type, i + 1, context)
2350+
self.msg.could_not_infer_type_arguments(callee_type, tv, context)
23452351
inferred_args = [AnyType(TypeOfAny.from_error)] * len(inferred_args)
23462352
# Apply the inferred types to the function type. In this case the
23472353
# 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

0 commit comments

Comments
 (0)