Skip to content

Commit 5120c4e

Browse files
committed
Merge remote-tracking branch 'upstream/master' into bugfix/pythongh-18901-reachability
2 parents 7d2b466 + 4b46d09 commit 5120c4e

Some content is hidden

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

44 files changed

+1064
-55
lines changed

mypy/checker.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -750,6 +750,9 @@ def _visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None:
750750
defn.is_explicit_override
751751
and not found_method_base_classes
752752
and found_method_base_classes is not None
753+
# If the class has Any fallback, we can't be certain that a method
754+
# is really missing - it might come from unfollowed import.
755+
and not defn.info.fallback_to_any
753756
):
754757
self.msg.no_overridable_method(defn.name, defn)
755758
self.check_explicit_override_decorator(defn, found_method_base_classes, defn.impl)
@@ -5285,12 +5288,15 @@ def visit_decorator_inner(
52855288
# For overloaded functions/properties we already checked override for overload as a whole.
52865289
if allow_empty or skip_first_item:
52875290
return
5288-
if e.func.info and not e.func.is_dynamic() and not e.is_overload:
5291+
if e.func.info and not e.is_overload:
52895292
found_method_base_classes = self.check_method_override(e)
52905293
if (
52915294
e.func.is_explicit_override
52925295
and not found_method_base_classes
52935296
and found_method_base_classes is not None
5297+
# If the class has Any fallback, we can't be certain that a method
5298+
# is really missing - it might come from unfollowed import.
5299+
and not e.func.info.fallback_to_any
52945300
):
52955301
self.msg.no_overridable_method(e.func.name, e.func)
52965302
self.check_explicit_override_decorator(e.func, found_method_base_classes)

mypy/checkexpr.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4096,7 +4096,7 @@ def lookup_definer(typ: Instance, attr_name: str) -> str | None:
40964096
results = []
40974097
for name, method, obj, arg in variants:
40984098
with self.msg.filter_errors(save_filtered_errors=True) as local_errors:
4099-
result = self.check_method_call(op_name, obj, method, [arg], [ARG_POS], context)
4099+
result = self.check_method_call(name, obj, method, [arg], [ARG_POS], context)
41004100
if local_errors.has_new_errors():
41014101
errors.append(local_errors.filtered_errors())
41024102
results.append(result)
@@ -4696,8 +4696,8 @@ def visit_cast_expr(self, expr: CastExpr) -> Type:
46964696
options = self.chk.options
46974697
if (
46984698
options.warn_redundant_casts
4699-
and not isinstance(get_proper_type(target_type), AnyType)
4700-
and source_type == target_type
4699+
and not is_same_type(target_type, AnyType(TypeOfAny.special_form))
4700+
and is_same_type(source_type, target_type)
47014701
):
47024702
self.msg.redundant_cast(target_type, expr)
47034703
if options.disallow_any_unimported and has_any_from_unimported_type(target_type):
@@ -6297,7 +6297,13 @@ def narrow_type_from_binder(
62976297
known_type, restriction, prohibit_none_typevar_overlap=True
62986298
):
62996299
return None
6300-
return narrow_declared_type(known_type, restriction)
6300+
narrowed = narrow_declared_type(known_type, restriction)
6301+
if isinstance(get_proper_type(narrowed), UninhabitedType):
6302+
# If we hit this case, it means that we can't reliably mark the code as
6303+
# unreachable, but the resulting type can't be expressed in type system.
6304+
# Falling back to restriction is more intuitive in most cases.
6305+
return restriction
6306+
return narrowed
63016307
return known_type
63026308

63036309
def has_abstract_type_part(self, caller_type: ProperType, callee_type: ProperType) -> bool:

mypy/main.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1128,6 +1128,11 @@ def add_invertible_flag(
11281128
report_group.add_argument(
11291129
"-a", dest="mypyc_annotation_file", type=str, default=None, help=argparse.SUPPRESS
11301130
)
1131+
# Hidden mypyc feature: do not write any C files (keep existing ones and assume they exist).
1132+
# This can be useful when debugging mypyc bugs.
1133+
report_group.add_argument(
1134+
"--skip-c-gen", dest="mypyc_skip_c_generation", action="store_true", help=argparse.SUPPRESS
1135+
)
11311136

11321137
other_group = parser.add_argument_group(title="Miscellaneous")
11331138
other_group.add_argument("--quickstart-file", help=argparse.SUPPRESS)
@@ -1444,9 +1449,7 @@ def set_strict_flags() -> None:
14441449
process_cache_map(parser, special_opts, options)
14451450

14461451
# Process --strict-bytes
1447-
if options.strict_bytes:
1448-
options.disable_bytearray_promotion = True
1449-
options.disable_memoryview_promotion = True
1452+
options.process_strict_bytes()
14501453

14511454
# An explicitly specified cache_fine_grained implies local_partial_types
14521455
# (because otherwise the cache is not compatible with dmypy)

mypy/modulefinder.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -506,21 +506,24 @@ def _find_module(self, id: str, use_typeshed: bool) -> ModuleSearchResult:
506506
dir_prefix = base_dir
507507
for _ in range(len(components) - 1):
508508
dir_prefix = os.path.dirname(dir_prefix)
509+
510+
# Stubs-only packages always take precedence over py.typed packages
511+
path_stubs = f"{base_path}-stubs{sepinit}.pyi"
512+
if fscache.isfile_case(path_stubs, dir_prefix):
513+
if verify and not verify_module(fscache, id, path_stubs, dir_prefix):
514+
near_misses.append((path_stubs, dir_prefix))
515+
else:
516+
return path_stubs
517+
509518
# Prefer package over module, i.e. baz/__init__.py* over baz.py*.
510519
for extension in PYTHON_EXTENSIONS:
511520
path = base_path + sepinit + extension
512-
path_stubs = base_path + "-stubs" + sepinit + extension
513521
if fscache.isfile_case(path, dir_prefix):
514522
has_init = True
515523
if verify and not verify_module(fscache, id, path, dir_prefix):
516524
near_misses.append((path, dir_prefix))
517525
continue
518526
return path
519-
elif fscache.isfile_case(path_stubs, dir_prefix):
520-
if verify and not verify_module(fscache, id, path_stubs, dir_prefix):
521-
near_misses.append((path_stubs, dir_prefix))
522-
continue
523-
return path_stubs
524527

525528
# In namespace mode, register a potential namespace package
526529
if self.options and self.options.namespace_packages:

mypy/options.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,9 @@ def __init__(self) -> None:
408408

409409
# Output html file for mypyc -a
410410
self.mypyc_annotation_file: str | None = None
411+
# Skip writing C output files, but perform all other steps of a build (allows
412+
# preserving manual tweaks to generated C file)
413+
self.mypyc_skip_c_generation = False
411414

412415
def use_lowercase_names(self) -> bool:
413416
if self.python_version >= (3, 9):
@@ -463,6 +466,16 @@ def process_incomplete_features(
463466
if feature in COMPLETE_FEATURES:
464467
warning_callback(f"Warning: {feature} is already enabled by default")
465468

469+
def process_strict_bytes(self) -> None:
470+
# Sync `--strict-bytes` and `--disable-{bytearray,memoryview}-promotion`
471+
if self.strict_bytes:
472+
# backwards compatibility
473+
self.disable_bytearray_promotion = True
474+
self.disable_memoryview_promotion = True
475+
elif self.disable_bytearray_promotion and self.disable_memoryview_promotion:
476+
# forwards compatibility
477+
self.strict_bytes = True
478+
466479
def apply_changes(self, changes: dict[str, object]) -> Options:
467480
# Note: effects of this method *must* be idempotent.
468481
new_options = Options()

mypy/semanal.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6382,6 +6382,8 @@ class C:
63826382
if node.name not in self.globals:
63836383
return True
63846384
global_node = self.globals[node.name]
6385+
if not self.is_textually_before_class(global_node.node):
6386+
return True
63856387
return not self.is_type_like(global_node.node)
63866388
return False
63876389

@@ -6409,6 +6411,13 @@ def is_textually_before_statement(self, node: SymbolNode) -> bool:
64096411
else:
64106412
return line_diff > 0
64116413

6414+
def is_textually_before_class(self, node: SymbolNode | None) -> bool:
6415+
"""Similar to above, but check if a node is defined before current class."""
6416+
assert self.type is not None
6417+
if node is None:
6418+
return False
6419+
return node.line < self.type.defn.line
6420+
64126421
def is_overloaded_item(self, node: SymbolNode, statement: Statement) -> bool:
64136422
"""Check whether the function belongs to the overloaded variants"""
64146423
if isinstance(node, OverloadedFuncDef) and isinstance(statement, FuncDef):

mypy/stubgen.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -920,13 +920,20 @@ def visit_assignment_stmt(self, o: AssignmentStmt) -> None:
920920
continue
921921
if (
922922
isinstance(lvalue, NameExpr)
923-
and not self.is_private_name(lvalue.name)
924-
# it is never an alias with explicit annotation
925-
and not o.unanalyzed_type
926923
and self.is_alias_expression(o.rvalue)
924+
and not self.is_private_name(lvalue.name)
927925
):
928-
self.process_typealias(lvalue, o.rvalue)
929-
continue
926+
is_explicit_type_alias = (
927+
o.unanalyzed_type and getattr(o.type, "name", None) == "TypeAlias"
928+
)
929+
if is_explicit_type_alias:
930+
self.process_typealias(lvalue, o.rvalue, is_explicit_type_alias=True)
931+
continue
932+
933+
if not o.unanalyzed_type:
934+
self.process_typealias(lvalue, o.rvalue)
935+
continue
936+
930937
if isinstance(lvalue, (TupleExpr, ListExpr)):
931938
items = lvalue.items
932939
if isinstance(o.unanalyzed_type, TupleType): # type: ignore[misc]
@@ -1139,9 +1146,15 @@ def is_alias_expression(self, expr: Expression, top_level: bool = True) -> bool:
11391146
else:
11401147
return False
11411148

1142-
def process_typealias(self, lvalue: NameExpr, rvalue: Expression) -> None:
1149+
def process_typealias(
1150+
self, lvalue: NameExpr, rvalue: Expression, is_explicit_type_alias: bool = False
1151+
) -> None:
11431152
p = AliasPrinter(self)
1144-
self.add(f"{self._indent}{lvalue.name} = {rvalue.accept(p)}\n")
1153+
if is_explicit_type_alias:
1154+
self.import_tracker.require_name("TypeAlias")
1155+
self.add(f"{self._indent}{lvalue.name}: TypeAlias = {rvalue.accept(p)}\n")
1156+
else:
1157+
self.add(f"{self._indent}{lvalue.name} = {rvalue.accept(p)}\n")
11451158
self.record_name(lvalue.name)
11461159
self._vars[-1].append(lvalue.name)
11471160

mypy/stubtest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2003,6 +2003,7 @@ def warning_callback(msg: str) -> None:
20032003
options.process_incomplete_features(
20042004
error_callback=error_callback, warning_callback=warning_callback
20052005
)
2006+
options.process_strict_bytes()
20062007

20072008
try:
20082009
modules = build_stubs(modules, options, find_submodules=not args.check_typeshed)

mypy/subtypes.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1091,6 +1091,11 @@ def visit_type_type(self, left: TypeType) -> bool:
10911091
right = self.right
10921092
if isinstance(right, TypeType):
10931093
return self._is_subtype(left.item, right.item)
1094+
if isinstance(right, Overloaded) and right.is_type_obj():
1095+
# Same as in other direction: if it's a constructor callable, all
1096+
# items should belong to the same class' constructor, so it's enough
1097+
# to check one of them.
1098+
return self._is_subtype(left, right.items[0])
10941099
if isinstance(right, CallableType):
10951100
if self.proper_subtype and not right.is_type_obj():
10961101
# We can't accept `Type[X]` as a *proper* subtype of Callable[P, X]

mypy/test/testmodulefinder.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,9 @@ def test__packages_with_ns(self) -> None:
195195
("pkg_typed.b", self.path("pkg_typed", "b", "__init__.py")),
196196
("pkg_typed.b.c", self.path("pkg_typed", "b", "c.py")),
197197
("pkg_typed.a.a_var", ModuleNotFoundReason.NOT_FOUND),
198+
# Regular package with py.typed, bundled stubs, and external stubs-only package
199+
("pkg_typed_w_stubs", self.path("pkg_typed_w_stubs-stubs", "__init__.pyi")),
200+
("pkg_typed_w_stubs.spam", self.path("pkg_typed_w_stubs-stubs", "spam.pyi")),
198201
# Regular package without py.typed
199202
("pkg_untyped", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS),
200203
("pkg_untyped.a", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS),
@@ -250,6 +253,9 @@ def test__packages_without_ns(self) -> None:
250253
("pkg_typed.b", self.path("pkg_typed", "b", "__init__.py")),
251254
("pkg_typed.b.c", self.path("pkg_typed", "b", "c.py")),
252255
("pkg_typed.a.a_var", ModuleNotFoundReason.NOT_FOUND),
256+
# Regular package with py.typed, bundled stubs, and external stubs-only package
257+
("pkg_typed_w_stubs", self.path("pkg_typed_w_stubs-stubs", "__init__.pyi")),
258+
("pkg_typed_w_stubs.spam", self.path("pkg_typed_w_stubs-stubs", "spam.pyi")),
253259
# Regular package without py.typed
254260
("pkg_untyped", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS),
255261
("pkg_untyped.a", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS),

mypyc/analysis/dataflow.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
Cast,
1818
ComparisonOp,
1919
ControlOp,
20+
DecRef,
2021
Extend,
2122
Float,
2223
FloatComparisonOp,
@@ -25,6 +26,7 @@
2526
GetAttr,
2627
GetElementPtr,
2728
Goto,
29+
IncRef,
2830
InitStatic,
2931
Integer,
3032
IntOp,
@@ -77,12 +79,11 @@ def __str__(self) -> str:
7779
return f"exits: {exits}\nsucc: {self.succ}\npred: {self.pred}"
7880

7981

80-
def get_cfg(blocks: list[BasicBlock]) -> CFG:
82+
def get_cfg(blocks: list[BasicBlock], *, use_yields: bool = False) -> CFG:
8183
"""Calculate basic block control-flow graph.
8284
83-
The result is a dictionary like this:
84-
85-
basic block index -> (successors blocks, predecesssor blocks)
85+
If use_yields is set, then we treat returns inserted by yields as gotos
86+
instead of exits.
8687
"""
8788
succ_map = {}
8889
pred_map: dict[BasicBlock, list[BasicBlock]] = {}
@@ -92,7 +93,10 @@ def get_cfg(blocks: list[BasicBlock]) -> CFG:
9293
isinstance(op, ControlOp) for op in block.ops[:-1]
9394
), "Control-flow ops must be at the end of blocks"
9495

95-
succ = list(block.terminator.targets())
96+
if use_yields and isinstance(block.terminator, Return) and block.terminator.yield_target:
97+
succ = [block.terminator.yield_target]
98+
else:
99+
succ = list(block.terminator.targets())
96100
if not succ:
97101
exits.add(block)
98102

@@ -474,6 +478,12 @@ def visit_assign_multi(self, op: AssignMulti) -> GenAndKill[Value]:
474478
def visit_set_mem(self, op: SetMem) -> GenAndKill[Value]:
475479
return non_trivial_sources(op), set()
476480

481+
def visit_inc_ref(self, op: IncRef) -> GenAndKill[Value]:
482+
return set(), set()
483+
484+
def visit_dec_ref(self, op: DecRef) -> GenAndKill[Value]:
485+
return set(), set()
486+
477487

478488
def analyze_live_regs(blocks: list[BasicBlock], cfg: CFG) -> AnalysisResult[Value]:
479489
"""Calculate live registers at each CFG location.

mypyc/build.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,8 @@ def mypyc_build(
452452
cfilenames = []
453453
for cfile, ctext in cfiles:
454454
cfile = os.path.join(compiler_options.target_dir, cfile)
455-
write_file(cfile, ctext)
455+
if not options.mypyc_skip_c_generation:
456+
write_file(cfile, ctext)
456457
if os.path.splitext(cfile)[1] == ".c":
457458
cfilenames.append(cfile)
458459

mypyc/codegen/emitmodule.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
from mypyc.transform.flag_elimination import do_flag_elimination
6262
from mypyc.transform.lower import lower_ir
6363
from mypyc.transform.refcount import insert_ref_count_opcodes
64+
from mypyc.transform.spill import insert_spills
6465
from mypyc.transform.uninit import insert_uninit_checks
6566

6667
# All of the modules being compiled are divided into "groups". A group
@@ -228,6 +229,12 @@ def compile_scc_to_ir(
228229
if errors.num_errors > 0:
229230
return modules
230231

232+
env_user_functions = {}
233+
for module in modules.values():
234+
for cls in module.classes:
235+
if cls.env_user_function:
236+
env_user_functions[cls.env_user_function] = cls
237+
231238
for module in modules.values():
232239
for fn in module.functions:
233240
# Insert uninit checks.
@@ -236,6 +243,10 @@ def compile_scc_to_ir(
236243
insert_exception_handling(fn)
237244
# Insert refcount handling.
238245
insert_ref_count_opcodes(fn)
246+
247+
if fn in env_user_functions:
248+
insert_spills(fn, env_user_functions[fn])
249+
239250
# Switch to lower abstraction level IR.
240251
lower_ir(fn, compiler_options)
241252
# Perform optimizations.

mypyc/doc/dev-intro.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,25 @@ Test cases can also have a `[out]` section, which specifies the
386386
expected contents of stdout the test case should produce. New test
387387
cases should prefer assert statements to `[out]` sections.
388388

389+
### Adding Debug Prints and Editing Generated C
390+
391+
Sometimes it's helpful to add some debug prints or other debugging helpers
392+
to the generated C code. You can run mypyc using `--skip-c-gen` to skip the C
393+
generation step, so all manual changes to C files are preserved. Here is
394+
an example of how to use the workflow:
395+
396+
* Compile some file you want to debug: `python -m mypyc foo.py`.
397+
* Add debug prints to the generated C in `build/__native.c`.
398+
* Run the same compilation command line again, but add `--skip-c-gen`:
399+
`python -m mypyc --skip-c-gen foo.py`. This will only rebuild the
400+
binaries.
401+
* Run the compiled code, including your changes: `python -c 'import foo'`.
402+
You should now see the output from the debug prints you added.
403+
404+
This can also be helpful if you want to quickly experiment with different
405+
implementation techniques, without having to first figure out how to
406+
modify mypyc to generate the desired C code.
407+
389408
### Debugging Segfaults
390409

391410
If you experience a segfault, it's recommended to use a debugger that supports

0 commit comments

Comments
 (0)