Skip to content

Commit 7636cdc

Browse files
Merge branch 'master' into fix_ternary_option1
2 parents ddfc574 + 5a78607 commit 7636cdc

35 files changed

+1055
-295
lines changed

.github/workflows/test.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,6 @@ jobs:
3737
toxenv: py
3838
tox_extra_args: "-n 4"
3939
test_mypyc: true
40-
- name: Test suite with py39-windows-64
41-
python: '3.9'
42-
os: windows-latest
43-
toxenv: py39
44-
tox_extra_args: "-n 4"
4540
- name: Test suite with py310-ubuntu
4641
python: '3.10'
4742
os: ubuntu-24.04-arm
@@ -64,6 +59,11 @@ jobs:
6459
toxenv: py
6560
tox_extra_args: "-n 4"
6661
test_mypyc: true
62+
- name: Test suite with py313-windows-64
63+
python: '3.13'
64+
os: windows-latest
65+
toxenv: py
66+
tox_extra_args: "-n 4"
6767

6868
- name: Test suite with py314-dev-ubuntu
6969
python: '3.14-dev'

mypy/checker.py

Lines changed: 129 additions & 42 deletions
Large diffs are not rendered by default.

mypy/checkexpr.py

Lines changed: 54 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,9 @@ def __init__(
359359
] = {}
360360
self.in_lambda_expr = False
361361

362+
self._literal_true: Instance | None = None
363+
self._literal_false: Instance | None = None
364+
362365
def reset(self) -> None:
363366
self.resolved_type = {}
364367
self.expr_cache.clear()
@@ -1071,7 +1074,7 @@ def check_typeddict_call_with_kwargs(
10711074

10721075
# We don't show any errors, just infer types in a generic TypedDict type,
10731076
# a custom error message will be given below, if there are errors.
1074-
with self.msg.filter_errors(), self.chk.local_type_map():
1077+
with self.msg.filter_errors(), self.chk.local_type_map:
10751078
orig_ret_type, _ = self.check_callable_call(
10761079
infer_callee,
10771080
# We use first expression for each key to infer type variables of a generic
@@ -1436,7 +1439,7 @@ def is_generic_decorator_overload_call(
14361439
return None
14371440
if not isinstance(get_proper_type(callee_type.ret_type), CallableType):
14381441
return None
1439-
with self.chk.local_type_map():
1442+
with self.chk.local_type_map:
14401443
with self.msg.filter_errors():
14411444
arg_type = get_proper_type(self.accept(args[0], type_context=None))
14421445
if isinstance(arg_type, Overloaded):
@@ -2718,6 +2721,7 @@ def check_overload_call(
27182721
# for example, when we have a fallback alternative that accepts an unrestricted
27192722
# typevar. See https://github.com/python/mypy/issues/4063 for related discussion.
27202723
erased_targets: list[CallableType] | None = None
2724+
inferred_types: list[Type] | None = None
27212725
unioned_result: tuple[Type, Type] | None = None
27222726

27232727
# Determine whether we need to encourage union math. This should be generally safe,
@@ -2745,13 +2749,14 @@ def check_overload_call(
27452749
# Record if we succeeded. Next we need to see if maybe normal procedure
27462750
# gives a narrower type.
27472751
if unioned_return:
2748-
returns, inferred_types = zip(*unioned_return)
2752+
returns = [u[0] for u in unioned_return]
2753+
inferred_types = [u[1] for u in unioned_return]
27492754
# Note that we use `combine_function_signatures` instead of just returning
27502755
# a union of inferred callables because for example a call
27512756
# Union[int -> int, str -> str](Union[int, str]) is invalid and
27522757
# we don't want to introduce internal inconsistencies.
27532758
unioned_result = (
2754-
make_simplified_union(list(returns), context.line, context.column),
2759+
make_simplified_union(returns, context.line, context.column),
27552760
self.combine_function_signatures(get_proper_types(inferred_types)),
27562761
)
27572762

@@ -2766,19 +2771,26 @@ def check_overload_call(
27662771
object_type,
27672772
context,
27682773
)
2769-
# If any of checks succeed, stop early.
2774+
# If any of checks succeed, perform deprecation tests and stop early.
27702775
if inferred_result is not None and unioned_result is not None:
27712776
# Both unioned and direct checks succeeded, choose the more precise type.
27722777
if (
27732778
is_subtype(inferred_result[0], unioned_result[0])
27742779
and not isinstance(get_proper_type(inferred_result[0]), AnyType)
27752780
and not none_type_var_overlap
27762781
):
2777-
return inferred_result
2778-
return unioned_result
2779-
elif unioned_result is not None:
2782+
unioned_result = None
2783+
else:
2784+
inferred_result = None
2785+
if unioned_result is not None:
2786+
if inferred_types is not None:
2787+
for inferred_type in inferred_types:
2788+
if isinstance(c := get_proper_type(inferred_type), CallableType):
2789+
self.chk.warn_deprecated(c.definition, context)
27802790
return unioned_result
2781-
elif inferred_result is not None:
2791+
if inferred_result is not None:
2792+
if isinstance(c := get_proper_type(inferred_result[1]), CallableType):
2793+
self.chk.warn_deprecated(c.definition, context)
27822794
return inferred_result
27832795

27842796
# Step 4: Failure. At this point, we know there is no match. We fall back to trying
@@ -2916,7 +2928,7 @@ def infer_overload_return_type(
29162928
for typ in plausible_targets:
29172929
assert self.msg is self.chk.msg
29182930
with self.msg.filter_errors() as w:
2919-
with self.chk.local_type_map() as m:
2931+
with self.chk.local_type_map as m:
29202932
ret_type, infer_type = self.check_call(
29212933
callee=typ,
29222934
args=args,
@@ -2932,8 +2944,6 @@ def infer_overload_return_type(
29322944
# check for ambiguity due to 'Any' below.
29332945
if not args_contain_any:
29342946
self.chk.store_types(m)
2935-
if isinstance(infer_type, ProperType) and isinstance(infer_type, CallableType):
2936-
self.chk.warn_deprecated(infer_type.definition, context)
29372947
return ret_type, infer_type
29382948
p_infer_type = get_proper_type(infer_type)
29392949
if isinstance(p_infer_type, CallableType):
@@ -2970,11 +2980,6 @@ def infer_overload_return_type(
29702980
else:
29712981
# Success! No ambiguity; return the first match.
29722982
self.chk.store_types(type_maps[0])
2973-
inferred_callable = inferred_types[0]
2974-
if isinstance(inferred_callable, ProperType) and isinstance(
2975-
inferred_callable, CallableType
2976-
):
2977-
self.chk.warn_deprecated(inferred_callable.definition, context)
29782983
return return_types[0], inferred_types[0]
29792984

29802985
def overload_erased_call_targets(
@@ -3427,11 +3432,19 @@ def infer_literal_expr_type(self, value: LiteralValue, fallback_name: str) -> Ty
34273432
if self.is_literal_context():
34283433
return LiteralType(value=value, fallback=typ)
34293434
else:
3430-
return typ.copy_modified(
3431-
last_known_value=LiteralType(
3432-
value=value, fallback=typ, line=typ.line, column=typ.column
3433-
)
3434-
)
3435+
if value is True:
3436+
if self._literal_true is None:
3437+
self._literal_true = typ.copy_modified(
3438+
last_known_value=LiteralType(value=value, fallback=typ)
3439+
)
3440+
return self._literal_true
3441+
if value is False:
3442+
if self._literal_false is None:
3443+
self._literal_false = typ.copy_modified(
3444+
last_known_value=LiteralType(value=value, fallback=typ)
3445+
)
3446+
return self._literal_false
3447+
return typ.copy_modified(last_known_value=LiteralType(value=value, fallback=typ))
34353448

34363449
def concat_tuples(self, left: TupleType, right: TupleType) -> TupleType:
34373450
"""Concatenate two fixed length tuples."""
@@ -5349,20 +5362,21 @@ def visit_dict_expr(self, e: DictExpr) -> Type:
53495362
# an error, but returns the TypedDict type that matches the literal it found
53505363
# that would cause a second error when that TypedDict type is returned upstream
53515364
# to avoid the second error, we always return TypedDict type that was requested
5352-
typeddict_contexts = self.find_typeddict_context(self.type_context[-1], e)
5365+
typeddict_contexts, exhaustive = self.find_typeddict_context(self.type_context[-1], e)
53535366
if typeddict_contexts:
5354-
if len(typeddict_contexts) == 1:
5367+
if len(typeddict_contexts) == 1 and exhaustive:
53555368
return self.check_typeddict_literal_in_context(e, typeddict_contexts[0])
53565369
# Multiple items union, check if at least one of them matches cleanly.
53575370
for typeddict_context in typeddict_contexts:
5358-
with self.msg.filter_errors() as err, self.chk.local_type_map() as tmap:
5371+
with self.msg.filter_errors() as err, self.chk.local_type_map as tmap:
53595372
ret_type = self.check_typeddict_literal_in_context(e, typeddict_context)
53605373
if err.has_new_errors():
53615374
continue
53625375
self.chk.store_types(tmap)
53635376
return ret_type
53645377
# No item matched without an error, so we can't unambiguously choose the item.
5365-
self.msg.typeddict_context_ambiguous(typeddict_contexts, e)
5378+
if exhaustive:
5379+
self.msg.typeddict_context_ambiguous(typeddict_contexts, e)
53665380

53675381
# fast path attempt
53685382
dt = self.fast_dict_type(e)
@@ -5424,22 +5438,29 @@ def visit_dict_expr(self, e: DictExpr) -> Type:
54245438

54255439
def find_typeddict_context(
54265440
self, context: Type | None, dict_expr: DictExpr
5427-
) -> list[TypedDictType]:
5441+
) -> tuple[list[TypedDictType], bool]:
5442+
"""Extract `TypedDict` members of the enclosing context.
5443+
5444+
Returns:
5445+
a 2-tuple, (found_candidates, is_exhaustive)
5446+
"""
54285447
context = get_proper_type(context)
54295448
if isinstance(context, TypedDictType):
5430-
return [context]
5449+
return [context], True
54315450
elif isinstance(context, UnionType):
54325451
items = []
5452+
exhaustive = True
54335453
for item in context.items:
5434-
item_contexts = self.find_typeddict_context(item, dict_expr)
5454+
item_contexts, item_exhaustive = self.find_typeddict_context(item, dict_expr)
54355455
for item_context in item_contexts:
54365456
if self.match_typeddict_call_with_dict(
54375457
item_context, dict_expr.items, dict_expr
54385458
):
54395459
items.append(item_context)
5440-
return items
5460+
exhaustive = exhaustive and item_exhaustive
5461+
return items, exhaustive
54415462
# No TypedDict type in context.
5442-
return []
5463+
return [], False
54435464

54445465
def visit_lambda_expr(self, e: LambdaExpr) -> Type:
54455466
"""Type check lambda expression."""
@@ -6038,15 +6059,12 @@ def accept(
60386059

60396060
def accept_maybe_cache(self, node: Expression, type_context: Type | None = None) -> Type:
60406061
binder_version = self.chk.binder.version
6041-
# Micro-optimization: inline local_type_map() as it is somewhat slow in mypyc.
6042-
type_map: dict[Expression, Type] = {}
6043-
self.chk._type_maps.append(type_map)
60446062
with self.msg.filter_errors(filter_errors=True, save_filtered_errors=True) as msg:
6045-
typ = node.accept(self)
6063+
with self.chk.local_type_map as type_map:
6064+
typ = node.accept(self)
60466065
messages = msg.filtered_errors()
60476066
if binder_version == self.chk.binder.version and not self.chk.current_node_deferred:
60486067
self.expr_cache[(node, type_context)] = (binder_version, typ, messages, type_map)
6049-
self.chk._type_maps.pop()
60506068
self.chk.store_types(type_map)
60516069
self.msg.add_errors(messages)
60526070
return typ

mypy/checkmember.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -976,8 +976,15 @@ def expand_and_bind_callable(
976976
freeze_all_type_vars(expanded)
977977
if not var.is_property:
978978
return expanded
979-
# TODO: a decorated property can result in Overloaded here.
980-
assert isinstance(expanded, CallableType)
979+
if isinstance(expanded, Overloaded):
980+
# Legacy way to store settable properties is with overloads. Also in case it is
981+
# an actual overloaded property, selecting first item that passed check_self_arg()
982+
# is a good approximation, long-term we should use check_call() inference below.
983+
if not expanded.items:
984+
# A broken overload, error should be already reported.
985+
return AnyType(TypeOfAny.from_error)
986+
expanded = expanded.items[0]
987+
assert isinstance(expanded, CallableType), expanded
981988
if var.is_settable_property and mx.is_lvalue and var.setter_type is not None:
982989
if expanded.variables:
983990
type_ctx = mx.rvalue or TempNode(AnyType(TypeOfAny.special_form), context=mx.context)

mypy/config_parser.py

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,15 @@
1717

1818
from collections.abc import Mapping, MutableMapping, Sequence
1919
from typing import Any, Callable, Final, TextIO, Union
20-
from typing_extensions import TypeAlias as _TypeAlias
20+
from typing_extensions import Never, TypeAlias
2121

2222
from mypy import defaults
2323
from mypy.options import PER_MODULE_OPTIONS, Options
2424

25-
_CONFIG_VALUE_TYPES: _TypeAlias = Union[
25+
_CONFIG_VALUE_TYPES: TypeAlias = Union[
2626
str, bool, int, float, dict[str, str], list[str], tuple[int, int]
2727
]
28-
_INI_PARSER_CALLABLE: _TypeAlias = Callable[[Any], _CONFIG_VALUE_TYPES]
28+
_INI_PARSER_CALLABLE: TypeAlias = Callable[[Any], _CONFIG_VALUE_TYPES]
2929

3030

3131
class VersionTypeError(argparse.ArgumentTypeError):
@@ -60,14 +60,31 @@ def parse_version(v: str | float) -> tuple[int, int]:
6060
return major, minor
6161

6262

63-
def try_split(v: str | Sequence[str], split_regex: str = "[,]") -> list[str]:
64-
"""Split and trim a str or list of str into a list of str"""
63+
def try_split(v: str | Sequence[str] | object, split_regex: str = ",") -> list[str]:
64+
"""Split and trim a str or sequence (eg: list) of str into a list of str.
65+
If an element of the input is not str, a type error will be raised."""
66+
67+
def complain(x: object, additional_info: str = "") -> Never:
68+
raise argparse.ArgumentTypeError(
69+
f"Expected a list or a stringified version thereof, but got: '{x}', of type {type(x).__name__}.{additional_info}"
70+
)
71+
6572
if isinstance(v, str):
6673
items = [p.strip() for p in re.split(split_regex, v)]
6774
if items and items[-1] == "":
6875
items.pop(-1)
6976
return items
70-
return [p.strip() for p in v]
77+
elif isinstance(v, Sequence):
78+
return [
79+
(
80+
p.strip()
81+
if isinstance(p, str)
82+
else complain(p, additional_info=" (As an element of the list.)")
83+
)
84+
for p in v
85+
]
86+
else:
87+
complain(v)
7188

7289

7390
def validate_codes(codes: list[str]) -> list[str]:

mypy/fastparse.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2234,7 +2234,7 @@ def visit_index_expr(self, e: IndexExpr) -> None:
22342234
pass
22352235

22362236
def visit_member_expr(self, e: MemberExpr) -> None:
2237-
if self.lvalue:
2237+
if self.lvalue and isinstance(e.expr, NameExpr):
22382238
self.found = True
22392239

22402240

mypy/fixup.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -181,8 +181,7 @@ def visit_overloaded_func_def(self, o: OverloadedFuncDef) -> None:
181181
if isinstance(o.type, Overloaded):
182182
# For error messages we link the original definition for each item.
183183
for typ, item in zip(o.type.items, o.items):
184-
if isinstance(item, Decorator):
185-
typ.definition = item.func
184+
typ.definition = item
186185

187186
def visit_decorator(self, d: Decorator) -> None:
188187
if self.current_info is not None:
@@ -193,8 +192,9 @@ def visit_decorator(self, d: Decorator) -> None:
193192
d.var.accept(self)
194193
for node in d.decorators:
195194
node.accept(self)
196-
if isinstance(d.var.type, ProperType) and isinstance(d.var.type, CallableType):
197-
d.var.type.definition = d.func
195+
typ = d.var.type
196+
if isinstance(typ, ProperType) and isinstance(typ, CallableType):
197+
typ.definition = d.func
198198

199199
def visit_class_def(self, c: ClassDef) -> None:
200200
for v in c.type_vars:

0 commit comments

Comments
 (0)