Skip to content

Commit 0f33c79

Browse files
Merge branch 'master' into fix_variance_polymorphic_constructor
2 parents ab680d1 + c6b40df commit 0f33c79

25 files changed

+363
-80
lines changed

docs/source/conf.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,9 +278,9 @@
278278
intersphinx_mapping = {
279279
"python": ("https://docs.python.org/3", None),
280280
"attrs": ("https://www.attrs.org/en/stable/", None),
281-
"cython": ("https://docs.cython.org/en/latest", None),
281+
"cython": ("https://cython.readthedocs.io/en/stable", None),
282282
"monkeytype": ("https://monkeytype.readthedocs.io/en/latest", None),
283-
"setuptools": ("https://setuptools.readthedocs.io/en/latest", None),
283+
"setuptools": ("https://setuptools.pypa.io/en/latest", None),
284284
}
285285

286286

misc/log_trace_check.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
"""Compile mypy using mypyc with trace logging enabled, and collect a trace.
2+
3+
The trace log can be used to analyze low-level performance bottlenecks.
4+
5+
By default does a self check as the workload.
6+
7+
This works on all supported platforms, unlike some of our other performance tools.
8+
"""
9+
10+
from __future__ import annotations
11+
12+
import argparse
13+
import glob
14+
import os
15+
import shutil
16+
import subprocess
17+
import sys
18+
import time
19+
20+
from perf_compare import build_mypy, clone
21+
22+
# Generated files, including binaries, go under this directory to avoid overwriting user state.
23+
TARGET_DIR = "mypy.log_trace.tmpdir"
24+
25+
26+
def perform_type_check(target_dir: str, code: str | None) -> None:
27+
cache_dir = os.path.join(target_dir, ".mypy_cache")
28+
if os.path.exists(cache_dir):
29+
shutil.rmtree(cache_dir)
30+
args = []
31+
if code is None:
32+
args.extend(["--config-file", "mypy_self_check.ini"])
33+
for pat in "mypy/*.py", "mypy/*/*.py", "mypyc/*.py", "mypyc/test/*.py":
34+
args.extend(glob.glob(pat))
35+
else:
36+
args.extend(["-c", code])
37+
check_cmd = ["python", "-m", "mypy"] + args
38+
t0 = time.time()
39+
subprocess.run(check_cmd, cwd=target_dir, check=True)
40+
elapsed = time.time() - t0
41+
print(f"{elapsed:.2f}s elapsed")
42+
43+
44+
def main() -> None:
45+
parser = argparse.ArgumentParser(
46+
description="Compile mypy and collect a trace log while type checking (by default, self check)."
47+
)
48+
parser.add_argument(
49+
"--multi-file",
50+
action="store_true",
51+
help="compile mypy into one C file per module (to reduce RAM use during compilation)",
52+
)
53+
parser.add_argument(
54+
"--skip-compile", action="store_true", help="use compiled mypy from previous run"
55+
)
56+
parser.add_argument(
57+
"-c",
58+
metavar="CODE",
59+
default=None,
60+
type=str,
61+
help="type check Python code fragment instead of mypy self-check",
62+
)
63+
args = parser.parse_args()
64+
multi_file: bool = args.multi_file
65+
skip_compile: bool = args.skip_compile
66+
code: str | None = args.c
67+
68+
target_dir = TARGET_DIR
69+
70+
if not skip_compile:
71+
clone(target_dir, "HEAD")
72+
73+
print(f"Building mypy in {target_dir} with trace logging enabled...")
74+
build_mypy(target_dir, multi_file, log_trace=True, opt_level="0")
75+
elif not os.path.isdir(target_dir):
76+
sys.exit("error: Can't find compile mypy from previous run -- can't use --skip-compile")
77+
78+
perform_type_check(target_dir, code)
79+
80+
trace_fnam = os.path.join(target_dir, "mypyc_trace.txt")
81+
print(f"Generated event trace log in {trace_fnam}")
82+
83+
84+
if __name__ == "__main__":
85+
main()

misc/perf_compare.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,22 @@ def heading(s: str) -> None:
3737
print()
3838

3939

40-
def build_mypy(target_dir: str, multi_file: bool, *, cflags: str | None = None) -> None:
40+
def build_mypy(
41+
target_dir: str,
42+
multi_file: bool,
43+
*,
44+
cflags: str | None = None,
45+
log_trace: bool = False,
46+
opt_level: str = "2",
47+
) -> None:
4148
env = os.environ.copy()
4249
env["CC"] = "clang"
43-
env["MYPYC_OPT_LEVEL"] = "2"
50+
env["MYPYC_OPT_LEVEL"] = opt_level
4451
env["PYTHONHASHSEED"] = "1"
4552
if multi_file:
4653
env["MYPYC_MULTI_FILE"] = "1"
54+
if log_trace:
55+
env["MYPYC_LOG_TRACE"] = "1"
4756
if cflags is not None:
4857
env["CFLAGS"] = cflags
4958
cmd = [sys.executable, "setup.py", "--use-mypyc", "build_ext", "--inplace"]

mypy/checker.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5577,6 +5577,8 @@ def infer_variable_types_from_type_maps(
55775577
previous_type, _, _ = self.check_lvalue(expr)
55785578
if previous_type is not None:
55795579
already_exists = True
5580+
if isinstance(expr.node, Var) and expr.node.is_final:
5581+
self.msg.cant_assign_to_final(expr.name, False, expr)
55805582
if self.check_subtype(
55815583
typ,
55825584
previous_type,

mypy/checkexpr.py

Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1671,6 +1671,10 @@ def check_call(
16711671
object_type,
16721672
original_type=callee,
16731673
)
1674+
elif isinstance(callee, UninhabitedType):
1675+
ret = UninhabitedType()
1676+
ret.ambiguous = callee.ambiguous
1677+
return callee, ret
16741678
else:
16751679
return self.msg.not_callable(callee, context), AnyType(TypeOfAny.from_error)
16761680

@@ -1751,14 +1755,6 @@ def check_callable_call(
17511755
return AnyType(TypeOfAny.from_error), callee
17521756
seen_unpack = True
17531757

1754-
formal_to_actual = map_actuals_to_formals(
1755-
arg_kinds,
1756-
arg_names,
1757-
callee.arg_kinds,
1758-
callee.arg_names,
1759-
lambda i: self.accept(args[i]),
1760-
)
1761-
17621758
# This is tricky: return type may contain its own type variables, like in
17631759
# def [S] (S) -> def [T] (T) -> tuple[S, T], so we need to update their ids
17641760
# to avoid possible id clashes if this call itself appears in a generic
@@ -1769,27 +1765,29 @@ def check_callable_call(
17691765
freeze_all_type_vars(fresh_ret_type)
17701766
callee = callee.copy_modified(ret_type=fresh_ret_type)
17711767

1768+
if callee.is_generic():
1769+
callee = freshen_function_type_vars(callee)
1770+
callee = self.infer_function_type_arguments_using_context(callee, context)
1771+
1772+
formal_to_actual = map_actuals_to_formals(
1773+
arg_kinds,
1774+
arg_names,
1775+
callee.arg_kinds,
1776+
callee.arg_names,
1777+
lambda i: self.accept(args[i]),
1778+
)
1779+
17721780
if callee.is_generic():
17731781
need_refresh = any(
17741782
isinstance(v, (ParamSpecType, TypeVarTupleType)) for v in callee.variables
17751783
)
1776-
callee = freshen_function_type_vars(callee)
1777-
callee = self.infer_function_type_arguments_using_context(callee, context)
1778-
if need_refresh:
1779-
# Argument kinds etc. may have changed due to
1780-
# ParamSpec or TypeVarTuple variables being replaced with an arbitrary
1781-
# number of arguments; recalculate actual-to-formal map
1782-
formal_to_actual = map_actuals_to_formals(
1783-
arg_kinds,
1784-
arg_names,
1785-
callee.arg_kinds,
1786-
callee.arg_names,
1787-
lambda i: self.accept(args[i]),
1788-
)
17891784
callee = self.infer_function_type_arguments(
17901785
callee, args, arg_kinds, arg_names, formal_to_actual, need_refresh, context
17911786
)
17921787
if need_refresh:
1788+
# Argument kinds etc. may have changed due to
1789+
# ParamSpec or TypeVarTuple variables being replaced with an arbitrary
1790+
# number of arguments; recalculate actual-to-formal map
17931791
formal_to_actual = map_actuals_to_formals(
17941792
arg_kinds,
17951793
arg_names,
@@ -2254,6 +2252,11 @@ def infer_function_type_arguments_pass2(
22542252
if isinstance(arg, (NoneType, UninhabitedType)) or has_erased_component(arg):
22552253
inferred_args[i] = None
22562254
callee_type = self.apply_generic_arguments(callee_type, inferred_args, context)
2255+
2256+
if not callee_type.is_generic():
2257+
# Fast path, second pass can't give new information.
2258+
return callee_type, []
2259+
22572260
if need_refresh:
22582261
formal_to_actual = map_actuals_to_formals(
22592262
arg_kinds,
@@ -3465,7 +3468,7 @@ def visit_op_expr(self, e: OpExpr) -> Type:
34653468
# It's actually a type expression X | Y.
34663469
return self.accept(e.analyzed)
34673470
if e.op == "and" or e.op == "or":
3468-
return self.check_boolean_op(e, e)
3471+
return self.check_boolean_op(e)
34693472
if e.op == "*" and isinstance(e.left, ListExpr):
34703473
# Expressions of form [...] * e get special type inference.
34713474
return self.check_list_multiply(e)
@@ -4252,20 +4255,18 @@ def check_op(
42524255
context=context,
42534256
)
42544257

4255-
def check_boolean_op(self, e: OpExpr, context: Context) -> Type:
4258+
def check_boolean_op(self, e: OpExpr) -> Type:
42564259
"""Type check a boolean operation ('and' or 'or')."""
42574260

42584261
# A boolean operation can evaluate to either of the operands.
42594262

4260-
# We use the current type context to guide the type inference of of
4263+
# We use the current type context to guide the type inference of
42614264
# the left operand. We also use the left operand type to guide the type
42624265
# inference of the right operand so that expressions such as
42634266
# '[1] or []' are inferred correctly.
42644267
ctx = self.type_context[-1]
42654268
left_type = self.accept(e.left, ctx)
4266-
expanded_left_type = try_expanding_sum_type_to_union(
4267-
self.accept(e.left, ctx), "builtins.bool"
4268-
)
4269+
expanded_left_type = try_expanding_sum_type_to_union(left_type, "builtins.bool")
42694270

42704271
assert e.op in ("and", "or") # Checked by visit_op_expr
42714272

mypy/checkmember.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
TypeVarLikeType,
7070
TypeVarTupleType,
7171
TypeVarType,
72+
UninhabitedType,
7273
UnionType,
7374
get_proper_type,
7475
)
@@ -268,6 +269,10 @@ def _analyze_member_access(
268269
if not mx.suppress_errors:
269270
mx.msg.deleted_as_rvalue(typ, mx.context)
270271
return AnyType(TypeOfAny.from_error)
272+
elif isinstance(typ, UninhabitedType):
273+
attr_type = UninhabitedType()
274+
attr_type.ambiguous = typ.ambiguous
275+
return attr_type
271276
return report_missing_attribute(mx.original_type, typ, name, mx)
272277

273278

@@ -951,7 +956,7 @@ def expand_and_bind_callable(
951956
) -> Type:
952957
if not mx.preserve_type_var_ids:
953958
functype = freshen_all_functions_type_vars(functype)
954-
typ = get_proper_type(expand_self_type(var, functype, mx.original_type))
959+
typ = get_proper_type(expand_self_type(var, functype, mx.self_type))
955960
assert isinstance(typ, FunctionLike)
956961
if is_trivial_self:
957962
typ = bind_self_fast(typ, mx.self_type)

mypy/constraints.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1110,7 +1110,10 @@ def visit_callable_type(self, template: CallableType) -> list[Constraint]:
11101110
# like U -> U, should be Callable[..., Any], but if U is a self-type, we can
11111111
# allow it to leak, to be later bound to self. A bunch of existing code
11121112
# depends on this old behaviour.
1113-
and not any(tv.id.is_self() for tv in cactual.variables)
1113+
and not (
1114+
any(tv.id.is_self() for tv in cactual.variables)
1115+
and template.is_ellipsis_args
1116+
)
11141117
):
11151118
# If the actual callable is generic, infer constraints in the opposite
11161119
# direction, and indicate to the solver there are extra type variables

mypy/expandtype.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from __future__ import annotations
22

3-
from collections.abc import Iterable, Mapping, Sequence
3+
from collections.abc import Iterable, Mapping
44
from typing import Final, TypeVar, cast, overload
55

66
from mypy.nodes import ARG_STAR, FakeInfo, Var
@@ -209,7 +209,11 @@ def visit_erased_type(self, t: ErasedType) -> Type:
209209
return t
210210

211211
def visit_instance(self, t: Instance) -> Type:
212-
args = self.expand_types_with_unpack(list(t.args))
212+
if len(t.args) == 0:
213+
# TODO: Why do we need to create a copy here?
214+
return t.copy_modified()
215+
216+
args = self.expand_type_tuple_with_unpack(t.args)
213217

214218
if isinstance(t.type, FakeInfo):
215219
# The type checker expands function definitions and bodies
@@ -431,7 +435,7 @@ def visit_overloaded(self, t: Overloaded) -> Type:
431435
items.append(new_item)
432436
return Overloaded(items)
433437

434-
def expand_types_with_unpack(self, typs: Sequence[Type]) -> list[Type]:
438+
def expand_type_list_with_unpack(self, typs: list[Type]) -> list[Type]:
435439
"""Expands a list of types that has an unpack."""
436440
items: list[Type] = []
437441
for item in typs:
@@ -441,8 +445,19 @@ def expand_types_with_unpack(self, typs: Sequence[Type]) -> list[Type]:
441445
items.append(item.accept(self))
442446
return items
443447

448+
def expand_type_tuple_with_unpack(self, typs: tuple[Type, ...]) -> list[Type]:
449+
"""Expands a tuple of types that has an unpack."""
450+
# Micro-optimization: Specialized variant of expand_type_list_with_unpack
451+
items: list[Type] = []
452+
for item in typs:
453+
if isinstance(item, UnpackType) and isinstance(item.type, TypeVarTupleType):
454+
items.extend(self.expand_unpack(item))
455+
else:
456+
items.append(item.accept(self))
457+
return items
458+
444459
def visit_tuple_type(self, t: TupleType) -> Type:
445-
items = self.expand_types_with_unpack(t.items)
460+
items = self.expand_type_list_with_unpack(t.items)
446461
if len(items) == 1:
447462
# Normalize Tuple[*Tuple[X, ...]] -> Tuple[X, ...]
448463
item = items[0]
@@ -510,7 +525,7 @@ def visit_type_type(self, t: TypeType) -> Type:
510525
def visit_type_alias_type(self, t: TypeAliasType) -> Type:
511526
# Target of the type alias cannot contain type variables (not bound by the type
512527
# alias itself), so we just expand the arguments.
513-
args = self.expand_types_with_unpack(t.args)
528+
args = self.expand_type_list_with_unpack(t.args)
514529
# TODO: normalize if target is Tuple, and args are [*tuple[X, ...]]?
515530
return t.copy_modified(args=args)
516531

0 commit comments

Comments
 (0)