Skip to content

Commit c7452b5

Browse files
authored
Merge branch 'master' into coveruntime
2 parents 5974152 + b678d9f commit c7452b5

File tree

14 files changed

+466
-24
lines changed

14 files changed

+466
-24
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: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@
107107
OperatorAssignmentStmt,
108108
OpExpr,
109109
OverloadedFuncDef,
110+
OverloadPart,
110111
PassStmt,
111112
PromoteExpr,
112113
RaiseStmt,
@@ -407,6 +408,11 @@ def __init__(
407408
# argument through various `checker` and `checkmember` functions.
408409
self._is_final_def = False
409410

411+
# Track when we enter an overload implementation. Some checks should not be applied
412+
# to the implementation signature when specific overloads are available.
413+
# Use `enter_overload_impl` to modify.
414+
self.overload_impl_stack: list[OverloadPart] = []
415+
410416
# This flag is set when we run type-check or attribute access check for the purpose
411417
# of giving a note on possibly missing "await". It is used to avoid infinite recursion.
412418
self.checking_missing_await = False
@@ -709,7 +715,8 @@ def _visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None:
709715
if num_abstract not in (0, len(defn.items)):
710716
self.fail(message_registry.INCONSISTENT_ABSTRACT_OVERLOAD, defn)
711717
if defn.impl:
712-
defn.impl.accept(self)
718+
with self.enter_overload_impl(defn.impl):
719+
defn.impl.accept(self)
713720
if not defn.is_property:
714721
self.check_overlapping_overloads(defn)
715722
if defn.type is None:
@@ -752,6 +759,14 @@ def _visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None:
752759
self.check_explicit_override_decorator(defn, found_method_base_classes, defn.impl)
753760
self.check_inplace_operator_method(defn)
754761

762+
@contextmanager
763+
def enter_overload_impl(self, impl: OverloadPart) -> Iterator[None]:
764+
self.overload_impl_stack.append(impl)
765+
try:
766+
yield
767+
finally:
768+
assert self.overload_impl_stack.pop() == impl
769+
755770
def extract_callable_type(self, inner_type: Type | None, ctx: Context) -> CallableType | None:
756771
"""Get type as seen by an overload item caller."""
757772
inner_type = get_proper_type(inner_type)
@@ -1278,7 +1293,11 @@ def check_func_def(
12781293
)
12791294

12801295
if name: # Special method names
1281-
if defn.info and self.is_reverse_op_method(name):
1296+
if (
1297+
defn.info
1298+
and self.is_reverse_op_method(name)
1299+
and defn not in self.overload_impl_stack
1300+
):
12821301
self.check_reverse_op_method(item, typ, name, defn)
12831302
elif name in ("__getattr__", "__getattribute__"):
12841303
self.check_getattr_method(typ, defn, name)
@@ -6166,6 +6185,7 @@ def find_isinstance_check_helper(
61666185
self.lookup_type(expr),
61676186
[TypeRange(node.callee.type_is, is_upper_bound=False)],
61686187
expr,
6188+
consider_runtime_isinstance=False,
61696189
),
61706190
)
61716191
elif isinstance(node, ComparisonExpr):
@@ -7593,11 +7613,19 @@ def conditional_types_with_intersection(
75937613
type_ranges: list[TypeRange] | None,
75947614
ctx: Context,
75957615
default: None = None,
7616+
*,
7617+
consider_runtime_isinstance: bool = True,
75967618
) -> tuple[Type | None, Type | None]: ...
75977619

75987620
@overload
75997621
def conditional_types_with_intersection(
7600-
self, expr_type: Type, type_ranges: list[TypeRange] | None, ctx: Context, default: Type
7622+
self,
7623+
expr_type: Type,
7624+
type_ranges: list[TypeRange] | None,
7625+
ctx: Context,
7626+
default: Type,
7627+
*,
7628+
consider_runtime_isinstance: bool = True,
76017629
) -> tuple[Type, Type]: ...
76027630

76037631
def conditional_types_with_intersection(
@@ -7606,8 +7634,15 @@ def conditional_types_with_intersection(
76067634
type_ranges: list[TypeRange] | None,
76077635
ctx: Context,
76087636
default: Type | None = None,
7637+
*,
7638+
consider_runtime_isinstance: bool = True,
76097639
) -> tuple[Type | None, Type | None]:
7610-
initial_types = conditional_types(expr_type, type_ranges, default)
7640+
initial_types = conditional_types(
7641+
expr_type,
7642+
type_ranges,
7643+
default,
7644+
consider_runtime_isinstance=consider_runtime_isinstance,
7645+
)
76117646
# For some reason, doing "yes_map, no_map = conditional_types_to_typemaps(...)"
76127647
# doesn't work: mypyc will decide that 'yes_map' is of type None if we try.
76137648
yes_type: Type | None = initial_types[0]
@@ -7919,18 +7954,30 @@ def visit_type_var(self, t: TypeVarType) -> None:
79197954

79207955
@overload
79217956
def conditional_types(
7922-
current_type: Type, proposed_type_ranges: list[TypeRange] | None, default: None = None
7957+
current_type: Type,
7958+
proposed_type_ranges: list[TypeRange] | None,
7959+
default: None = None,
7960+
*,
7961+
consider_runtime_isinstance: bool = True,
79237962
) -> tuple[Type | None, Type | None]: ...
79247963

79257964

79267965
@overload
79277966
def conditional_types(
7928-
current_type: Type, proposed_type_ranges: list[TypeRange] | None, default: Type
7967+
current_type: Type,
7968+
proposed_type_ranges: list[TypeRange] | None,
7969+
default: Type,
7970+
*,
7971+
consider_runtime_isinstance: bool = True,
79297972
) -> tuple[Type, Type]: ...
79307973

79317974

79327975
def conditional_types(
7933-
current_type: Type, proposed_type_ranges: list[TypeRange] | None, default: Type | None = None
7976+
current_type: Type,
7977+
proposed_type_ranges: list[TypeRange] | None,
7978+
default: Type | None = None,
7979+
*,
7980+
consider_runtime_isinstance: bool = True,
79347981
) -> tuple[Type | None, Type | None]:
79357982
"""Takes in the current type and a proposed type of an expression.
79367983
@@ -7972,7 +8019,11 @@ def conditional_types(
79728019
if not type_range.is_upper_bound
79738020
]
79748021
)
7975-
remaining_type = restrict_subtype_away(current_type, proposed_precise_type)
8022+
remaining_type = restrict_subtype_away(
8023+
current_type,
8024+
proposed_precise_type,
8025+
consider_runtime_isinstance=consider_runtime_isinstance,
8026+
)
79768027
return proposed_type, remaining_type
79778028
else:
79788029
# An isinstance check, but we don't understand the type

mypy/checkexpr.py

Lines changed: 9 additions & 3 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,

mypy/errors.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -848,6 +848,8 @@ def generate_unused_ignore_errors(self, file: str) -> None:
848848
code=codes.UNUSED_IGNORE,
849849
blocker=False,
850850
only_once=False,
851+
origin=(self.file, [line]),
852+
target=self.target_module,
851853
)
852854
self._add_error_info(file, info)
853855

@@ -899,6 +901,8 @@ def generate_ignore_without_code_errors(
899901
code=codes.IGNORE_WITHOUT_CODE,
900902
blocker=False,
901903
only_once=False,
904+
origin=(self.file, [line]),
905+
target=self.target_module,
902906
)
903907
self._add_error_info(file, info)
904908

mypy/server/update.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -668,6 +668,8 @@ def restore(ids: list[str]) -> None:
668668
state.type_check_first_pass()
669669
state.type_check_second_pass()
670670
state.detect_possibly_undefined_vars()
671+
state.generate_unused_ignore_notes()
672+
state.generate_ignore_without_code_notes()
671673
t2 = time.time()
672674
state.finish_passes()
673675
t3 = time.time()

mypy/stubtest.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2062,7 +2062,7 @@ def warning_callback(msg: str) -> None:
20622062
if args.generate_allowlist:
20632063
generated_allowlist.add(error.object_desc)
20642064
continue
2065-
print(error.get_description(concise=args.concise))
2065+
safe_print(error.get_description(concise=args.concise))
20662066
error_count += 1
20672067

20682068
# Print unused allowlist entries
@@ -2102,6 +2102,19 @@ def warning_callback(msg: str) -> None:
21022102
return exit_code
21032103

21042104

2105+
def safe_print(text: str) -> None:
2106+
"""Print a text replacing chars not representable in stdout encoding."""
2107+
# If `sys.stdout` encoding is not the same as out (usually UTF8) string,
2108+
# if may cause painful crashes. I don't want to reconfigure `sys.stdout`
2109+
# to do `errors = "replace"` as that sounds scary.
2110+
out_encoding = sys.stdout.encoding
2111+
if out_encoding is not None:
2112+
# Can be None if stdout is replaced (including our own tests). This should be
2113+
# safe to omit if the actual stream doesn't care about encoding.
2114+
text = text.encode(out_encoding, errors="replace").decode(out_encoding, errors="replace")
2115+
print(text)
2116+
2117+
21052118
def parse_options(args: list[str]) -> _Arguments:
21062119
parser = argparse.ArgumentParser(
21072120
description="Compares stubs to objects introspected from the runtime."

mypy/subtypes.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2073,7 +2073,7 @@ def try_restrict_literal_union(t: UnionType, s: Type) -> list[Type] | None:
20732073
return new_items
20742074

20752075

2076-
def restrict_subtype_away(t: Type, s: Type) -> Type:
2076+
def restrict_subtype_away(t: Type, s: Type, *, consider_runtime_isinstance: bool = True) -> Type:
20772077
"""Return t minus s for runtime type assertions.
20782078
20792079
If we can't determine a precise result, return a supertype of the
@@ -2087,16 +2087,27 @@ def restrict_subtype_away(t: Type, s: Type) -> Type:
20872087
new_items = try_restrict_literal_union(p_t, s)
20882088
if new_items is None:
20892089
new_items = [
2090-
restrict_subtype_away(item, s)
2090+
restrict_subtype_away(
2091+
item, s, consider_runtime_isinstance=consider_runtime_isinstance
2092+
)
20912093
for item in p_t.relevant_items()
2092-
if (isinstance(get_proper_type(item), AnyType) or not covers_at_runtime(item, s))
20932094
]
2094-
return UnionType.make_union(new_items)
2095+
return UnionType.make_union(
2096+
[item for item in new_items if not isinstance(get_proper_type(item), UninhabitedType)]
2097+
)
20952098
elif isinstance(p_t, TypeVarType):
20962099
return p_t.copy_modified(upper_bound=restrict_subtype_away(p_t.upper_bound, s))
2097-
elif covers_at_runtime(t, s):
2098-
return UninhabitedType()
2100+
2101+
if consider_runtime_isinstance:
2102+
if covers_at_runtime(t, s):
2103+
return UninhabitedType()
2104+
else:
2105+
return t
20992106
else:
2107+
if is_proper_subtype(t, s, ignore_promotions=True):
2108+
return UninhabitedType()
2109+
if is_proper_subtype(t, s, ignore_promotions=True, erase_instances=True):
2110+
return UninhabitedType()
21002111
return t
21012112

21022113

mypy/suggestions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -852,7 +852,7 @@ def visit_instance(self, t: Instance) -> str:
852852
if self.module:
853853
parts = obj.split(".") # need to split the object part if it is a nested class
854854
tree = self.graph[self.module].tree
855-
if tree and parts[0] in tree.names:
855+
if tree and parts[0] in tree.names and mod not in tree.names:
856856
mod = self.module
857857

858858
if (mod, obj) == ("builtins", "tuple"):

0 commit comments

Comments
 (0)