Skip to content

Commit 9f33053

Browse files
Merge branch 'master' into fix_match_callable_2
2 parents d1c10c1 + 07d4a1b commit 9f33053

25 files changed

+417
-79
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: 62 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5069,31 +5069,56 @@ def fast_container_type(
50695069
module-level constant definitions.
50705070
50715071
Limitations:
5072+
50725073
- no active type context
5074+
- at least one item
50735075
- no star expressions
5074-
- the joined type of all entries must be an Instance or Tuple type
5076+
- not after deferral
5077+
- either exactly one distinct type inside,
5078+
or the joined type of all entries is an Instance or Tuple type,
50755079
"""
50765080
ctx = self.type_context[-1]
5077-
if ctx:
5081+
if ctx or not e.items:
5082+
return None
5083+
if self.chk.current_node_deferred:
5084+
# Guarantees that all items will be Any, we'll reject it anyway.
50785085
return None
50795086
rt = self.resolved_type.get(e, None)
50805087
if rt is not None:
50815088
return rt if isinstance(rt, Instance) else None
50825089
values: list[Type] = []
5090+
# Preserve join order while avoiding O(n) lookups at every iteration
5091+
values_set: set[Type] = set()
50835092
for item in e.items:
50845093
if isinstance(item, StarExpr):
50855094
# fallback to slow path
50865095
self.resolved_type[e] = NoneType()
50875096
return None
5088-
values.append(self.accept(item))
5089-
vt = join.join_type_list(values)
5090-
if not allow_fast_container_literal(vt):
5097+
5098+
typ = self.accept(item)
5099+
if typ not in values_set:
5100+
values.append(typ)
5101+
values_set.add(typ)
5102+
5103+
vt = self._first_or_join_fast_item(values)
5104+
if vt is None:
50915105
self.resolved_type[e] = NoneType()
50925106
return None
50935107
ct = self.chk.named_generic_type(container_fullname, [vt])
50945108
self.resolved_type[e] = ct
50955109
return ct
50965110

5111+
def _first_or_join_fast_item(self, items: list[Type]) -> Type | None:
5112+
if len(items) == 1 and not self.chk.current_node_deferred:
5113+
return items[0]
5114+
typ = join.join_type_list(items)
5115+
if not allow_fast_container_literal(typ):
5116+
# TODO: This is overly strict, many other types can be joined safely here.
5117+
# However, our join implementation isn't bug-free, and some joins may produce
5118+
# undesired `Any`s or even more surprising results.
5119+
return None
5120+
return typ
5121+
50975122
def check_lst_expr(self, e: ListExpr | SetExpr | TupleExpr, fullname: str, tag: str) -> Type:
50985123
# fast path
50995124
t = self.fast_container_type(e, fullname)
@@ -5254,18 +5279,30 @@ def fast_dict_type(self, e: DictExpr) -> Type | None:
52545279
module-level constant definitions.
52555280
52565281
Limitations:
5282+
52575283
- no active type context
5284+
- at least one item
52585285
- only supported star expressions are other dict instances
5259-
- the joined types of all keys and values must be Instance or Tuple types
5286+
- either exactly one distinct type (keys and values separately) inside,
5287+
or the joined type of all entries is an Instance or Tuple type
52605288
"""
52615289
ctx = self.type_context[-1]
5262-
if ctx:
5290+
if ctx or not e.items:
52635291
return None
5292+
5293+
if self.chk.current_node_deferred:
5294+
# Guarantees that all items will be Any, we'll reject it anyway.
5295+
return None
5296+
52645297
rt = self.resolved_type.get(e, None)
52655298
if rt is not None:
52665299
return rt if isinstance(rt, Instance) else None
5300+
52675301
keys: list[Type] = []
52685302
values: list[Type] = []
5303+
# Preserve join order while avoiding O(n) lookups at every iteration
5304+
keys_set: set[Type] = set()
5305+
values_set: set[Type] = set()
52695306
stargs: tuple[Type, Type] | None = None
52705307
for key, value in e.items:
52715308
if key is None:
@@ -5280,13 +5317,25 @@ def fast_dict_type(self, e: DictExpr) -> Type | None:
52805317
self.resolved_type[e] = NoneType()
52815318
return None
52825319
else:
5283-
keys.append(self.accept(key))
5284-
values.append(self.accept(value))
5285-
kt = join.join_type_list(keys)
5286-
vt = join.join_type_list(values)
5287-
if not (allow_fast_container_literal(kt) and allow_fast_container_literal(vt)):
5320+
key_t = self.accept(key)
5321+
if key_t not in keys_set:
5322+
keys.append(key_t)
5323+
keys_set.add(key_t)
5324+
value_t = self.accept(value)
5325+
if value_t not in values_set:
5326+
values.append(value_t)
5327+
values_set.add(value_t)
5328+
5329+
kt = self._first_or_join_fast_item(keys)
5330+
if kt is None:
52885331
self.resolved_type[e] = NoneType()
52895332
return None
5333+
5334+
vt = self._first_or_join_fast_item(values)
5335+
if vt is None:
5336+
self.resolved_type[e] = NoneType()
5337+
return None
5338+
52905339
if stargs and (stargs[0] != kt or stargs[1] != vt):
52915340
self.resolved_type[e] = NoneType()
52925341
return None
@@ -5994,7 +6043,7 @@ def accept(
59946043
# We cannot use cache inside lambdas, because they skip immediate type
59956044
# context, and use enclosing one, see infer_lambda_type_using_context().
59966045
# TODO: consider using cache for more expression kinds.
5997-
elif isinstance(node, (CallExpr, ListExpr, TupleExpr)) and not (
6046+
elif isinstance(node, (CallExpr, ListExpr, TupleExpr, DictExpr, OpExpr)) and not (
59986047
self.in_lambda_expr or self.chk.current_node_deferred
59996048
):
60006049
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(

0 commit comments

Comments
 (0)