Skip to content

Commit ed7dba5

Browse files
authored
Merge branch 'master' into feature/st-handle-dunder-hash-consistently
2 parents 236294f + f49a88f commit ed7dba5

File tree

109 files changed

+3705
-1937
lines changed

Some content is hidden

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

109 files changed

+3705
-1937
lines changed

.github/workflows/mypy_primer.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ jobs:
2828
runs-on: ubuntu-latest
2929
strategy:
3030
matrix:
31-
shard-index: [0, 1, 2, 3, 4]
31+
shard-index: [0, 1, 2, 3, 4, 5]
3232
fail-fast: false
3333
timeout-minutes: 60
3434
steps:
@@ -63,7 +63,7 @@ jobs:
6363
mypy_primer \
6464
--repo mypy_to_test \
6565
--new $GITHUB_SHA --old base_commit \
66-
--num-shards 5 --shard-index ${{ matrix.shard-index }} \
66+
--num-shards 6 --shard-index ${{ matrix.shard-index }} \
6767
--debug \
6868
--additional-flags="--debug-serialize" \
6969
--output concise \

misc/profile_self_check.py renamed to misc/profile_check.py

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
"""Compile mypy using mypyc and profile self-check using perf.
1+
"""Compile mypy using mypyc and profile type checking using perf.
2+
3+
By default does a self check.
24
35
Notes:
46
- Only Linux is supported for now (TODO: add support for other profilers)
@@ -23,6 +25,8 @@
2325
CFLAGS="-O2 -g -fno-omit-frame-pointer"
2426
"""
2527

28+
from __future__ import annotations
29+
2630
import argparse
2731
import glob
2832
import os
@@ -41,24 +45,28 @@
4145
TARGET_DIR = "mypy.profile.tmpdir"
4246

4347

44-
def _profile_self_check(target_dir: str) -> None:
48+
def _profile_type_check(target_dir: str, code: str | None) -> None:
4549
cache_dir = os.path.join(target_dir, ".mypy_cache")
4650
if os.path.exists(cache_dir):
4751
shutil.rmtree(cache_dir)
48-
files = []
49-
for pat in "mypy/*.py", "mypy/*/*.py", "mypyc/*.py", "mypyc/test/*.py":
50-
files.extend(glob.glob(pat))
51-
self_check_cmd = ["python", "-m", "mypy", "--config-file", "mypy_self_check.ini"] + files
52-
cmdline = ["perf", "record", "-g"] + self_check_cmd
52+
args = []
53+
if code is None:
54+
args.extend(["--config-file", "mypy_self_check.ini"])
55+
for pat in "mypy/*.py", "mypy/*/*.py", "mypyc/*.py", "mypyc/test/*.py":
56+
args.extend(glob.glob(pat))
57+
else:
58+
args.extend(["-c", code])
59+
check_cmd = ["python", "-m", "mypy"] + args
60+
cmdline = ["perf", "record", "-g"] + check_cmd
5361
t0 = time.time()
5462
subprocess.run(cmdline, cwd=target_dir, check=True)
5563
elapsed = time.time() - t0
5664
print(f"{elapsed:.2f}s elapsed")
5765

5866

59-
def profile_self_check(target_dir: str) -> None:
67+
def profile_type_check(target_dir: str, code: str | None) -> None:
6068
try:
61-
_profile_self_check(target_dir)
69+
_profile_type_check(target_dir, code)
6270
except subprocess.CalledProcessError:
6371
print("\nProfiling failed! You may missing some permissions.")
6472
print("\nThis may help (note that it has security implications):")
@@ -92,7 +100,7 @@ def main() -> None:
92100
check_requirements()
93101

94102
parser = argparse.ArgumentParser(
95-
description="Compile mypy and profile self checking using 'perf'."
103+
description="Compile mypy and profile type checking using 'perf' (by default, self check)."
96104
)
97105
parser.add_argument(
98106
"--multi-file",
@@ -102,9 +110,17 @@ def main() -> None:
102110
parser.add_argument(
103111
"--skip-compile", action="store_true", help="use compiled mypy from previous run"
104112
)
113+
parser.add_argument(
114+
"-c",
115+
metavar="CODE",
116+
default=None,
117+
type=str,
118+
help="profile type checking Python code fragment instead of mypy self-check",
119+
)
105120
args = parser.parse_args()
106121
multi_file: bool = args.multi_file
107122
skip_compile: bool = args.skip_compile
123+
code: str | None = args.c
108124

109125
target_dir = TARGET_DIR
110126

@@ -116,7 +132,7 @@ def main() -> None:
116132
elif not os.path.isdir(target_dir):
117133
sys.exit("error: Can't find compile mypy from previous run -- can't use --skip-compile")
118134

119-
profile_self_check(target_dir)
135+
profile_type_check(target_dir, code)
120136

121137
print()
122138
print('NOTE: Compile CPython using CFLAGS="-O2 -g -fno-omit-frame-pointer" for good results')

mypy/checker.py

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -618,7 +618,7 @@ def accept_loop(
618618
if on_enter_body is not None:
619619
on_enter_body()
620620

621-
with IterationErrorWatcher(self.msg.errors, iter_errors) as watcher:
621+
with IterationErrorWatcher(self.msg.errors, iter_errors):
622622
self.accept(body)
623623

624624
partials_new = sum(len(pts.map) for pts in self.partial_types)
@@ -641,10 +641,7 @@ def accept_loop(
641641
if iter == 20:
642642
raise RuntimeError("Too many iterations when checking a loop")
643643

644-
for error_info in watcher.yield_error_infos():
645-
self.msg.fail(*error_info[:2], code=error_info[2])
646-
for note_info in watcher.yield_note_infos(self.options):
647-
self.note(*note_info)
644+
self.msg.iteration_dependent_errors(iter_errors)
648645

649646
# If exit_condition is set, assume it must be False on exit from the loop:
650647
if exit_condition:
@@ -3044,7 +3041,7 @@ def is_noop_for_reachability(self, s: Statement) -> bool:
30443041
if isinstance(s.expr, EllipsisExpr):
30453042
return True
30463043
elif isinstance(s.expr, CallExpr):
3047-
with self.expr_checker.msg.filter_errors():
3044+
with self.expr_checker.msg.filter_errors(filter_revealed_type=True):
30483045
typ = get_proper_type(
30493046
self.expr_checker.accept(
30503047
s.expr, allow_none_return=True, always_allow_any=True
@@ -3144,7 +3141,7 @@ def check_assignment(
31443141
else:
31453142
self.check_getattr_method(signature, lvalue, name)
31463143

3147-
if name == "__slots__":
3144+
if name == "__slots__" and self.scope.active_class() is not None:
31483145
typ = lvalue_type or self.expr_checker.accept(rvalue)
31493146
self.check_slots_definition(typ, lvalue)
31503147
if name == "__match_args__" and inferred is not None:
@@ -3323,6 +3320,12 @@ def get_variable_type_context(self, inferred: Var, rvalue: Expression) -> Type |
33233320
type_contexts.append(base_type)
33243321
# Use most derived supertype as type context if available.
33253322
if not type_contexts:
3323+
if inferred.name == "__slots__" and self.scope.active_class() is not None:
3324+
str_type = self.named_type("builtins.str")
3325+
return self.named_generic_type("typing.Iterable", [str_type])
3326+
if inferred.name == "__all__" and self.scope.is_top_level():
3327+
str_type = self.named_type("builtins.str")
3328+
return self.named_generic_type("typing.Sequence", [str_type])
33263329
return None
33273330
candidate = type_contexts[0]
33283331
for other in type_contexts:
@@ -4984,7 +4987,7 @@ def visit_try_stmt(self, s: TryStmt) -> None:
49844987
if s.finally_body:
49854988
# First we check finally_body is type safe on all abnormal exit paths
49864989
iter_errors = IterationDependentErrors()
4987-
with IterationErrorWatcher(self.msg.errors, iter_errors) as watcher:
4990+
with IterationErrorWatcher(self.msg.errors, iter_errors):
49884991
self.accept(s.finally_body)
49894992

49904993
if s.finally_body:
@@ -5001,13 +5004,9 @@ def visit_try_stmt(self, s: TryStmt) -> None:
50015004
# that follows the try statement.)
50025005
assert iter_errors is not None
50035006
if not self.binder.is_unreachable():
5004-
with IterationErrorWatcher(self.msg.errors, iter_errors) as watcher:
5007+
with IterationErrorWatcher(self.msg.errors, iter_errors):
50055008
self.accept(s.finally_body)
5006-
5007-
for error_info in watcher.yield_error_infos():
5008-
self.msg.fail(*error_info[:2], code=error_info[2])
5009-
for note_info in watcher.yield_note_infos(self.options):
5010-
self.msg.note(*note_info)
5009+
self.msg.iteration_dependent_errors(iter_errors)
50115010

50125011
def visit_try_without_finally(self, s: TryStmt, try_frame: bool) -> None:
50135012
"""Type check a try statement, ignoring the finally block.
@@ -7295,7 +7294,7 @@ def named_type(self, name: str) -> Instance:
72957294
if isinstance(node, TypeAlias):
72967295
assert isinstance(node.target, Instance) # type: ignore[misc]
72977296
node = node.target.type
7298-
assert isinstance(node, TypeInfo)
7297+
assert isinstance(node, TypeInfo), node
72997298
any_type = AnyType(TypeOfAny.from_omitted_generics)
73007299
return Instance(node, [any_type] * len(node.defn.type_vars))
73017300

@@ -7314,7 +7313,7 @@ def lookup_typeinfo(self, fullname: str) -> TypeInfo:
73147313
# Assume that the name refers to a class.
73157314
sym = self.lookup_qualified(fullname)
73167315
node = sym.node
7317-
assert isinstance(node, TypeInfo)
7316+
assert isinstance(node, TypeInfo), node
73187317
return node
73197318

73207319
def type_type(self) -> Instance:
@@ -7905,6 +7904,9 @@ def has_valid_attribute(self, typ: Type, name: str) -> bool:
79057904
def get_expression_type(self, node: Expression, type_context: Type | None = None) -> Type:
79067905
return self.expr_checker.accept(node, type_context=type_context)
79077906

7907+
def is_defined_in_stub(self, typ: Instance, /) -> bool:
7908+
return self.modules[typ.type.module_name].is_stub
7909+
79087910
def check_deprecated(self, node: Node | None, context: Context) -> None:
79097911
"""Warn if deprecated and not directly imported with a `from` statement."""
79107912
if isinstance(node, Decorator):

mypy/checker_shared.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,10 @@ def checking_await_set(self) -> Iterator[None]:
277277
def get_precise_awaitable_type(self, typ: Type, local_errors: ErrorWatcher) -> Type | None:
278278
raise NotImplementedError
279279

280+
@abstractmethod
281+
def is_defined_in_stub(self, typ: Instance, /) -> bool:
282+
raise NotImplementedError
283+
280284

281285
class CheckerScope:
282286
# We keep two stacks combined, to maintain the relative order
@@ -334,6 +338,10 @@ def current_self_type(self) -> Instance | TupleType | None:
334338
return fill_typevars(item)
335339
return None
336340

341+
def is_top_level(self) -> bool:
342+
"""Is current scope top-level (no classes or functions)?"""
343+
return len(self.stack) == 1
344+
337345
@contextmanager
338346
def push_function(self, item: FuncItem) -> Iterator[None]:
339347
self.stack.append(item)

mypy/checkpattern.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -796,9 +796,9 @@ def get_var(expr: Expression) -> Var:
796796
Warning: this in only true for expressions captured by a match statement.
797797
Don't call it from anywhere else
798798
"""
799-
assert isinstance(expr, NameExpr)
799+
assert isinstance(expr, NameExpr), expr
800800
node = expr.node
801-
assert isinstance(node, Var)
801+
assert isinstance(node, Var), node
802802
return node
803803

804804

0 commit comments

Comments
 (0)