Skip to content

Commit 07f40fc

Browse files
Merge branch 'master' into for-loop-len-cache
2 parents f67f358 + 07d4a1b commit 07f40fc

22 files changed

+349
-60
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,11 @@ Related PRs:
170170
* Enable ANSI color codes for dmypy client in Windows (wyattscarpenter, PR [19088](https://github.com/python/mypy/pull/19088))
171171
* Extend special case for context-based type variable inference to unions in return position (Stanislav Terliakov, PR [18976](https://github.com/python/mypy/pull/18976))
172172

173+
### Mypy 1.17.1
174+
* Retain `None` as constraints bottom if no bottoms were provided (Stanislav Terliakov, PR [19485](https://github.com/python/mypy/pull/19485))
175+
* Fix "ignored exception in `hasattr`" in dmypy (Stanislav Terliakov, PR [19428](https://github.com/python/mypy/pull/19428))
176+
* Prevent a crash when InitVar is redefined with a method in a subclass (Stanislav Terliakov, PR [19453](https://github.com/python/mypy/pull/19453))
177+
173178
### Acknowledgements
174179

175180
Thanks to all mypy contributors who contributed to this release:

mypy/checkexpr.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6043,7 +6043,7 @@ def accept(
60436043
# We cannot use cache inside lambdas, because they skip immediate type
60446044
# context, and use enclosing one, see infer_lambda_type_using_context().
60456045
# TODO: consider using cache for more expression kinds.
6046-
elif isinstance(node, (CallExpr, ListExpr, TupleExpr, OpExpr)) and not (
6046+
elif isinstance(node, (CallExpr, ListExpr, TupleExpr, DictExpr, OpExpr)) and not (
60476047
self.in_lambda_expr or self.chk.current_node_deferred
60486048
):
60496049
if (node, type_context) in self.expr_cache:

mypy/test/testcheck.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import os
66
import re
77
import sys
8+
import tempfile
9+
from pathlib import Path
810

911
from mypy import build
1012
from mypy.build import Graph
@@ -46,15 +48,16 @@
4648
if sys.version_info < (3, 13):
4749
typecheck_files.remove("check-python313.test")
4850

49-
# Special tests for platforms with case-insensitive filesystems.
50-
if sys.platform not in ("darwin", "win32"):
51-
typecheck_files.remove("check-modules-case.test")
52-
5351

5452
class TypeCheckSuite(DataSuite):
5553
files = typecheck_files
5654

5755
def run_case(self, testcase: DataDrivenTestCase) -> None:
56+
if os.path.basename(testcase.file) == "check-modules-case.test":
57+
with tempfile.NamedTemporaryFile(prefix="test", dir=".") as temp_file:
58+
temp_path = Path(temp_file.name)
59+
if not temp_path.with_name(temp_path.name.upper()).exists():
60+
pytest.skip("File system is not case‐insensitive")
5861
if lxml is None and os.path.basename(testcase.file) == "check-reports.test":
5962
pytest.skip("Cannot import lxml. Is it installed?")
6063
incremental = (

mypy/test/testfscache.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
import os
66
import shutil
7-
import sys
87
import tempfile
98
import unittest
109

@@ -83,7 +82,7 @@ def test_isfile_case_other_directory(self) -> None:
8382
assert self.isfile_case(os.path.join(other, "other_dir.py"))
8483
assert not self.isfile_case(os.path.join(other, "Other_Dir.py"))
8584
assert not self.isfile_case(os.path.join(other, "bar.py"))
86-
if sys.platform in ("win32", "darwin"):
85+
if os.path.exists(os.path.join(other, "PKG/other_dir.py")):
8786
# We only check case for directories under our prefix, and since
8887
# this path is not under the prefix, case difference is fine.
8988
assert self.isfile_case(os.path.join(other, "PKG/other_dir.py"))

mypyc/build.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
from mypy.util import write_junit_xml
3737
from mypyc.annotate import generate_annotated_html
3838
from mypyc.codegen import emitmodule
39-
from mypyc.common import RUNTIME_C_FILES, shared_lib_name
39+
from mypyc.common import IS_FREE_THREADED, RUNTIME_C_FILES, shared_lib_name
4040
from mypyc.errors import Errors
4141
from mypyc.ir.pprint import format_modules
4242
from mypyc.namegen import exported_name
@@ -176,9 +176,15 @@ def generate_c_extension_shim(
176176
cname = "%s.c" % full_module_name.replace(".", os.sep)
177177
cpath = os.path.join(dir_name, cname)
178178

179+
if IS_FREE_THREADED:
180+
# We use multi-phase init in free-threaded builds to enable free threading.
181+
shim_name = "module_shim_no_gil_multiphase.tmpl"
182+
else:
183+
shim_name = "module_shim.tmpl"
184+
179185
# We load the C extension shim template from a file.
180186
# (So that the file could be reused as a bazel template also.)
181-
with open(os.path.join(include_dir(), "module_shim.tmpl")) as f:
187+
with open(os.path.join(include_dir(), shim_name)) as f:
182188
shim_template = f.read()
183189

184190
write_file(

mypyc/codegen/emitmodule.py

Lines changed: 97 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -784,28 +784,15 @@ def generate_shared_lib_init(self, emitter: Emitter) -> None:
784784
assert self.group_name is not None
785785

786786
emitter.emit_line()
787+
788+
short_name = shared_lib_name(self.group_name).split(".")[-1]
789+
787790
emitter.emit_lines(
788-
"PyMODINIT_FUNC PyInit_{}(void)".format(
789-
shared_lib_name(self.group_name).split(".")[-1]
790-
),
791+
f"static int exec_{short_name}(PyObject *module)",
791792
"{",
792-
(
793-
'static PyModuleDef def = {{ PyModuleDef_HEAD_INIT, "{}", NULL, -1, NULL, NULL }};'.format(
794-
shared_lib_name(self.group_name)
795-
)
796-
),
797793
"int res;",
798794
"PyObject *capsule;",
799795
"PyObject *tmp;",
800-
"static PyObject *module;",
801-
"if (module) {",
802-
"Py_INCREF(module);",
803-
"return module;",
804-
"}",
805-
"module = PyModule_Create(&def);",
806-
"if (!module) {",
807-
"goto fail;",
808-
"}",
809796
"",
810797
)
811798

@@ -827,15 +814,26 @@ def generate_shared_lib_init(self, emitter: Emitter) -> None:
827814

828815
for mod in self.modules:
829816
name = exported_name(mod)
817+
if self.multi_phase_init:
818+
capsule_func_prefix = "CPyExec_"
819+
capsule_name_prefix = "exec_"
820+
emitter.emit_line(f"extern int CPyExec_{name}(PyObject *);")
821+
else:
822+
capsule_func_prefix = "CPyInit_"
823+
capsule_name_prefix = "init_"
824+
emitter.emit_line(f"extern PyObject *CPyInit_{name}(void);")
830825
emitter.emit_lines(
831-
f"extern PyObject *CPyInit_{name}(void);",
832-
'capsule = PyCapsule_New((void *)CPyInit_{}, "{}.init_{}", NULL);'.format(
833-
name, shared_lib_name(self.group_name), name
826+
'capsule = PyCapsule_New((void *){}{}, "{}.{}{}", NULL);'.format(
827+
capsule_func_prefix,
828+
name,
829+
shared_lib_name(self.group_name),
830+
capsule_name_prefix,
831+
name,
834832
),
835833
"if (!capsule) {",
836834
"goto fail;",
837835
"}",
838-
f'res = PyObject_SetAttrString(module, "init_{name}", capsule);',
836+
f'res = PyObject_SetAttrString(module, "{capsule_name_prefix}{name}", capsule);',
839837
"Py_DECREF(capsule);",
840838
"if (res < 0) {",
841839
"goto fail;",
@@ -861,7 +859,56 @@ def generate_shared_lib_init(self, emitter: Emitter) -> None:
861859
"",
862860
)
863861

864-
emitter.emit_lines("return module;", "fail:", "Py_XDECREF(module);", "return NULL;", "}")
862+
emitter.emit_lines("return 0;", "fail:", "return -1;", "}")
863+
864+
if self.multi_phase_init:
865+
emitter.emit_lines(
866+
f"static PyModuleDef_Slot slots_{short_name}[] = {{",
867+
f"{{Py_mod_exec, exec_{short_name}}},",
868+
"{Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},",
869+
"{Py_mod_gil, Py_MOD_GIL_NOT_USED},",
870+
"{0, NULL},",
871+
"};",
872+
)
873+
874+
size = 0 if self.multi_phase_init else -1
875+
emitter.emit_lines(
876+
f"static PyModuleDef module_def_{short_name} = {{",
877+
"PyModuleDef_HEAD_INIT,",
878+
f'.m_name = "{shared_lib_name(self.group_name)}",',
879+
".m_doc = NULL,",
880+
f".m_size = {size},",
881+
".m_methods = NULL,",
882+
)
883+
if self.multi_phase_init:
884+
emitter.emit_line(f".m_slots = slots_{short_name},")
885+
emitter.emit_line("};")
886+
887+
if self.multi_phase_init:
888+
emitter.emit_lines(
889+
f"PyMODINIT_FUNC PyInit_{short_name}(void) {{",
890+
f"return PyModuleDef_Init(&module_def_{short_name});",
891+
"}",
892+
)
893+
else:
894+
emitter.emit_lines(
895+
f"PyMODINIT_FUNC PyInit_{short_name}(void) {{",
896+
"static PyObject *module = NULL;",
897+
"if (module) {",
898+
"Py_INCREF(module);",
899+
"return module;",
900+
"}",
901+
f"module = PyModule_Create(&module_def_{short_name});",
902+
"if (!module) {",
903+
"return NULL;",
904+
"}",
905+
f"if (exec_{short_name}(module) < 0) {{",
906+
"Py_DECREF(module);",
907+
"return NULL;",
908+
"}",
909+
"return module;",
910+
"}",
911+
)
865912

866913
def generate_globals_init(self, emitter: Emitter) -> None:
867914
emitter.emit_lines(
@@ -887,16 +934,22 @@ def generate_globals_init(self, emitter: Emitter) -> None:
887934
def generate_module_def(self, emitter: Emitter, module_name: str, module: ModuleIR) -> None:
888935
"""Emit the PyModuleDef struct for a module and the module init function."""
889936
module_prefix = emitter.names.private_name(module_name)
890-
self.emit_module_exec_func(emitter, module_name, module_prefix, module)
891-
if self.multi_phase_init:
892-
self.emit_module_def_slots(emitter, module_prefix)
893937
self.emit_module_methods(emitter, module_name, module_prefix, module)
894-
self.emit_module_def_struct(emitter, module_name, module_prefix)
895-
self.emit_module_init_func(emitter, module_name, module_prefix)
938+
self.emit_module_exec_func(emitter, module_name, module_prefix, module)
896939

897-
def emit_module_def_slots(self, emitter: Emitter, module_prefix: str) -> None:
940+
# If using multi-phase init and a shared lib, parts of module definition
941+
# will happen in the shim modules, so we skip some steps here.
942+
if not (self.multi_phase_init and self.use_shared_lib):
943+
if self.multi_phase_init:
944+
self.emit_module_def_slots(emitter, module_prefix, module_name)
945+
self.emit_module_def_struct(emitter, module_name, module_prefix)
946+
self.emit_module_init_func(emitter, module_name, module_prefix)
947+
948+
def emit_module_def_slots(
949+
self, emitter: Emitter, module_prefix: str, module_name: str
950+
) -> None:
898951
name = f"{module_prefix}_slots"
899-
exec_name = f"{module_prefix}_exec"
952+
exec_name = f"CPyExec_{exported_name(module_name)}"
900953

901954
emitter.emit_line(f"static PyModuleDef_Slot {name}[] = {{")
902955
emitter.emit_line(f"{{Py_mod_exec, {exec_name}}},")
@@ -951,7 +1004,7 @@ def emit_module_def_struct(
9511004
"0, /* size of per-interpreter state of the module */",
9521005
f"{module_prefix}module_methods,",
9531006
)
954-
if self.multi_phase_init:
1007+
if self.multi_phase_init and not self.use_shared_lib:
9551008
slots_name = f"{module_prefix}_slots"
9561009
emitter.emit_line(f"{slots_name}, /* m_slots */")
9571010
else:
@@ -962,15 +1015,16 @@ def emit_module_def_struct(
9621015
def emit_module_exec_func(
9631016
self, emitter: Emitter, module_name: str, module_prefix: str, module: ModuleIR
9641017
) -> None:
965-
"""Emit the module init function.
1018+
"""Emit the module exec function.
9661019
967-
If we are compiling just one module, this will be the C API init
968-
function. If we are compiling 2+ modules, we generate a shared
1020+
If we are compiling just one module, this will be the normal C API
1021+
exec function. If we are compiling 2+ modules, we generate a shared
9691022
library for the modules and shims that call into the shared
970-
library, and in this case we use an internal module initialized
971-
function that will be called by the shim.
1023+
library, and in this case the shared module defines an internal
1024+
exec function for each module and these will be called by the shims
1025+
via Capsules.
9721026
"""
973-
declaration = f"static int {module_prefix}_exec(PyObject *module)"
1027+
declaration = f"int CPyExec_{exported_name(module_name)}(PyObject *module)"
9741028
module_static = self.module_internal_static_name(module_name, emitter)
9751029
emitter.emit_lines(declaration, "{")
9761030
emitter.emit_line("PyObject* modname = NULL;")
@@ -987,6 +1041,12 @@ def emit_module_exec_func(
9871041
" goto fail;",
9881042
)
9891043

1044+
if self.multi_phase_init:
1045+
emitter.emit_lines(
1046+
f"if (PyModule_AddFunctions(module, {module_prefix}module_methods) < 0)",
1047+
" goto fail;",
1048+
)
1049+
9901050
# HACK: Manually instantiate generated classes here
9911051
type_structs: list[str] = []
9921052
for cl in module.classes:
@@ -1038,7 +1098,7 @@ def emit_module_init_func(
10381098
emitter.emit_line("}")
10391099
return
10401100

1041-
exec_func = f"{module_prefix}_exec"
1101+
exec_func = f"CPyExec_{exported_name(module_name)}"
10421102

10431103
# Store the module reference in a static and return it when necessary.
10441104
# This is separate from the *global* reference to the module that will

mypyc/irbuild/builder.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,10 @@ def new_tuple(self, items: list[Value], line: int) -> Value:
425425
def debug_print(self, toprint: str | Value) -> None:
426426
return self.builder.debug_print(toprint)
427427

428+
def set_immortal_if_free_threaded(self, v: Value, line: int) -> None:
429+
"""Make an object immortal on free-threaded builds (to avoid contention)."""
430+
self.builder.set_immortal_if_free_threaded(v, line)
431+
428432
# Helpers for IR building
429433

430434
def add_to_non_ext_dict(
@@ -434,6 +438,10 @@ def add_to_non_ext_dict(
434438
key_unicode = self.load_str(key)
435439
self.primitive_op(dict_set_item_op, [non_ext.dict, key_unicode, val], line)
436440

441+
# It's important that accessing class dictionary items from multiple threads
442+
# doesn't cause contention.
443+
self.builder.set_immortal_if_free_threaded(val, line)
444+
437445
def gen_import(self, id: str, line: int) -> None:
438446
self.imports[id] = None
439447

mypyc/irbuild/classdef.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,9 @@ def finalize(self, ir: ClassIR) -> None:
262262
non_ext_class = load_non_ext_class(self.builder, ir, self.non_ext, self.cdef.line)
263263
non_ext_class = load_decorated_class(self.builder, self.cdef, non_ext_class)
264264

265+
# Try to avoid contention when using free threading.
266+
self.builder.set_immortal_if_free_threaded(non_ext_class, self.cdef.line)
267+
265268
# Save the decorated class
266269
self.builder.add(
267270
InitStatic(non_ext_class, self.cdef.name, self.builder.module_name, NAMESPACE_TYPE)
@@ -449,6 +452,11 @@ def allocate_class(builder: IRBuilder, cdef: ClassDef) -> Value:
449452
)
450453
# Create the class
451454
tp = builder.call_c(pytype_from_template_op, [template, tp_bases, modname], cdef.line)
455+
456+
# Set type object to be immortal if free threaded, as otherwise reference count contention
457+
# can cause a big performance hit.
458+
builder.set_immortal_if_free_threaded(tp, cdef.line)
459+
452460
# Immediately fix up the trait vtables, before doing anything with the class.
453461
ir = builder.mapper.type_to_ir[cdef.info]
454462
if not ir.is_trait and not ir.builtin_base:

mypyc/irbuild/ll_builder.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from __future__ import annotations
88

9+
import sys
910
from collections.abc import Sequence
1011
from typing import Callable, Final, Optional
1112

@@ -16,6 +17,7 @@
1617
from mypyc.common import (
1718
BITMAP_BITS,
1819
FAST_ISINSTANCE_MAX_SUBCLASSES,
20+
IS_FREE_THREADED,
1921
MAX_LITERAL_SHORT_INT,
2022
MAX_SHORT_INT,
2123
MIN_LITERAL_SHORT_INT,
@@ -164,6 +166,7 @@
164166
fast_isinstance_op,
165167
none_object_op,
166168
not_implemented_op,
169+
set_immortal_op,
167170
var_object_size,
168171
)
169172
from mypyc.primitives.registry import (
@@ -2322,6 +2325,11 @@ def new_tuple_with_length(self, length: Value, line: int) -> Value:
23222325
def int_to_float(self, n: Value, line: int) -> Value:
23232326
return self.primitive_op(int_to_float_op, [n], line)
23242327

2328+
def set_immortal_if_free_threaded(self, v: Value, line: int) -> None:
2329+
"""Make an object immortal on free-threaded builds (to avoid contention)."""
2330+
if IS_FREE_THREADED and sys.version_info >= (3, 14):
2331+
self.primitive_op(set_immortal_op, [v], line)
2332+
23252333
# Internal helpers
23262334

23272335
def decompose_union_helper(

mypyc/lib-rt/CPy.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -931,6 +931,10 @@ PyObject *CPy_GetANext(PyObject *aiter);
931931
void CPy_SetTypeAliasTypeComputeFunction(PyObject *alias, PyObject *compute_value);
932932
void CPyTrace_LogEvent(const char *location, const char *line, const char *op, const char *details);
933933

934+
#if CPY_3_14_FEATURES
935+
void CPy_SetImmortal(PyObject *obj);
936+
#endif
937+
934938
#ifdef __cplusplus
935939
}
936940
#endif

0 commit comments

Comments
 (0)