Skip to content

Commit 806a27d

Browse files
Merge branch 'master' into patch-3
2 parents ec5d4c6 + b025bda commit 806a27d

21 files changed

+392
-71
lines changed

CHANGELOG.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,27 @@
22

33
## Next Release
44

5+
### Remove Support for targeting Python 3.8
6+
7+
Mypy now requires `--python-version 3.9` or greater. Support for only Python 3.8 is
8+
fully removed now. Given an unsupported version, mypy will default to the oldest
9+
supported one, currently 3.9.
10+
11+
This change is necessary because typeshed stopped supporting Python 3.8 after it
12+
reached its End of Life in October 2024.
13+
14+
Contributed by Marc Mueller
15+
(PR [19157](https://github.com/python/mypy/pull/19157), PR [19162](https://github.com/python/mypy/pull/19162)).
16+
17+
### Initial Support for Python 3.14
18+
19+
Mypy is now tested on 3.14 and mypyc works with 3.14.0b3 and later.
20+
Mypyc compiled wheels of mypy itself will be available for new versions after 3.14.0rc1 is released.
21+
22+
Note that not all new features might be supported just yet.
23+
24+
Contributed by Marc Mueller (PR [19164](https://github.com/python/mypy/pull/19164))
25+
526
### Deprecated Flag: \--force-uppercase-builtins
627

728
Mypy only supports Python 3.9+. The \--force-uppercase-builtins flag is now deprecated and a no-op. It will be removed in a future version.

mypy/checker.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2449,7 +2449,7 @@ def erase_override(t: Type) -> Type:
24492449
if not is_subtype(original_arg_type, erase_override(override_arg_type)):
24502450
context: Context = node
24512451
if isinstance(node, FuncDef) and not node.is_property:
2452-
arg_node = node.arguments[i + len(override.bound_args)]
2452+
arg_node = node.arguments[i + override.bound()]
24532453
if arg_node.line != -1:
24542454
context = arg_node
24552455
self.msg.argument_incompatible_with_supertype(
@@ -7697,9 +7697,13 @@ def get_isinstance_type(self, expr: Expression) -> list[TypeRange] | None:
76977697
types: list[TypeRange] = []
76987698
for typ in all_types:
76997699
if isinstance(typ, FunctionLike) and typ.is_type_obj():
7700-
# Type variables may be present -- erase them, which is the best
7701-
# we can do (outside disallowing them here).
7702-
erased_type = erase_typevars(typ.items[0].ret_type)
7700+
# If a type is generic, `isinstance` can only narrow its variables to Any.
7701+
any_parameterized = fill_typevars_with_any(typ.type_object())
7702+
# Tuples may have unattended type variables among their items
7703+
if isinstance(any_parameterized, TupleType):
7704+
erased_type = erase_typevars(any_parameterized)
7705+
else:
7706+
erased_type = any_parameterized
77037707
types.append(TypeRange(erased_type, is_upper_bound=False))
77047708
elif isinstance(typ, TypeType):
77057709
# Type[A] means "any type that is a subtype of A" rather than "precisely type A"

mypy/checkexpr.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4975,7 +4975,7 @@ def apply_type_arguments_to_callable(
49754975
tp.fallback,
49764976
name="tuple",
49774977
definition=tp.definition,
4978-
bound_args=tp.bound_args,
4978+
is_bound=tp.is_bound,
49794979
)
49804980
self.msg.incompatible_type_application(
49814981
min_arg_count, len(type_vars), len(args), ctx

mypy/checkmember.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -921,7 +921,7 @@ def analyze_var(
921921
bound_items = []
922922
for ct in call_type.items if isinstance(call_type, UnionType) else [call_type]:
923923
p_ct = get_proper_type(ct)
924-
if isinstance(p_ct, FunctionLike) and not p_ct.is_type_obj():
924+
if isinstance(p_ct, FunctionLike) and (not p_ct.bound() or var.is_property):
925925
item = expand_and_bind_callable(p_ct, var, itype, name, mx, is_trivial_self)
926926
else:
927927
item = expand_without_binding(ct, var, itype, original_itype, mx)
@@ -1498,6 +1498,6 @@ def bind_self_fast(method: F, original_type: Type | None = None) -> F:
14981498
arg_types=func.arg_types[1:],
14991499
arg_kinds=func.arg_kinds[1:],
15001500
arg_names=func.arg_names[1:],
1501-
bound_args=[original_type],
1501+
is_bound=True,
15021502
)
15031503
return cast(F, res)

mypy/fixup.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -271,9 +271,6 @@ def visit_callable_type(self, ct: CallableType) -> None:
271271
ct.ret_type.accept(self)
272272
for v in ct.variables:
273273
v.accept(self)
274-
for arg in ct.bound_args:
275-
if arg:
276-
arg.accept(self)
277274
if ct.type_guard is not None:
278275
ct.type_guard.accept(self)
279276
if ct.type_is is not None:

mypy/messages.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -644,8 +644,8 @@ def incompatible_argument(
644644
callee_name = callable_name(callee)
645645
if callee_name is not None:
646646
name = callee_name
647-
if callee.bound_args and callee.bound_args[0] is not None:
648-
base = format_type(callee.bound_args[0], self.options)
647+
if object_type is not None:
648+
base = format_type(object_type, self.options)
649649
else:
650650
base = extract_type(name)
651651

mypy/plugins/functools.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import mypy.plugin
99
import mypy.semanal
1010
from mypy.argmap import map_actuals_to_formals
11+
from mypy.erasetype import erase_typevars
1112
from mypy.nodes import (
1213
ARG_POS,
1314
ARG_STAR2,
@@ -312,7 +313,11 @@ def handle_partial_with_callee(ctx: mypy.plugin.FunctionContext, callee: Type) -
312313
special_sig="partial",
313314
)
314315

315-
ret = ctx.api.named_generic_type(PARTIAL, [ret_type])
316+
# Do not leak typevars from generic functions - they cannot be usable.
317+
# Keep them in the wrapped callable, but avoid `partial[SomeStrayTypeVar]`
318+
erased_ret_type = erase_typevars(ret_type, [tv.id for tv in fn_type.variables])
319+
320+
ret = ctx.api.named_generic_type(PARTIAL, [erased_ret_type])
316321
ret = ret.copy_with_extra_attr("__mypy_partial", partially_applied)
317322
if partially_applied.param_spec():
318323
assert ret.extra_attrs is not None # copy_with_extra_attr above ensures this

mypy/reachability.py

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -115,31 +115,44 @@ def infer_condition_value(expr: Expression, options: Options) -> int:
115115
MYPY_TRUE if true under mypy and false at runtime, MYPY_FALSE if
116116
false under mypy and true at runtime, else TRUTH_VALUE_UNKNOWN.
117117
"""
118+
if isinstance(expr, UnaryExpr) and expr.op == "not":
119+
positive = infer_condition_value(expr.expr, options)
120+
return inverted_truth_mapping[positive]
121+
118122
pyversion = options.python_version
119123
name = ""
120-
negated = False
121-
alias = expr
122-
if isinstance(alias, UnaryExpr):
123-
if alias.op == "not":
124-
expr = alias.expr
125-
negated = True
124+
126125
result = TRUTH_VALUE_UNKNOWN
127126
if isinstance(expr, NameExpr):
128127
name = expr.name
129128
elif isinstance(expr, MemberExpr):
130129
name = expr.name
131-
elif isinstance(expr, OpExpr) and expr.op in ("and", "or"):
130+
elif isinstance(expr, OpExpr):
131+
if expr.op not in ("or", "and"):
132+
return TRUTH_VALUE_UNKNOWN
133+
132134
left = infer_condition_value(expr.left, options)
133-
if (left in (ALWAYS_TRUE, MYPY_TRUE) and expr.op == "and") or (
134-
left in (ALWAYS_FALSE, MYPY_FALSE) and expr.op == "or"
135-
):
136-
# Either `True and <other>` or `False or <other>`: the result will
137-
# always be the right-hand-side.
138-
return infer_condition_value(expr.right, options)
139-
else:
140-
# The result will always be the left-hand-side (e.g. ALWAYS_* or
141-
# TRUTH_VALUE_UNKNOWN).
142-
return left
135+
right = infer_condition_value(expr.right, options)
136+
results = {left, right}
137+
if expr.op == "or":
138+
if ALWAYS_TRUE in results:
139+
return ALWAYS_TRUE
140+
elif MYPY_TRUE in results:
141+
return MYPY_TRUE
142+
elif left == right == MYPY_FALSE:
143+
return MYPY_FALSE
144+
elif results <= {ALWAYS_FALSE, MYPY_FALSE}:
145+
return ALWAYS_FALSE
146+
elif expr.op == "and":
147+
if ALWAYS_FALSE in results:
148+
return ALWAYS_FALSE
149+
elif MYPY_FALSE in results:
150+
return MYPY_FALSE
151+
elif left == right == ALWAYS_TRUE:
152+
return ALWAYS_TRUE
153+
elif results <= {ALWAYS_TRUE, MYPY_TRUE}:
154+
return MYPY_TRUE
155+
return TRUTH_VALUE_UNKNOWN
143156
else:
144157
result = consider_sys_version_info(expr, pyversion)
145158
if result == TRUTH_VALUE_UNKNOWN:
@@ -155,8 +168,6 @@ def infer_condition_value(expr: Expression, options: Options) -> int:
155168
result = ALWAYS_TRUE
156169
elif name in options.always_false:
157170
result = ALWAYS_FALSE
158-
if negated:
159-
result = inverted_truth_mapping[result]
160171
return result
161172

162173

mypy/server/astdiff.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,7 @@ def visit_callable_type(self, typ: CallableType) -> SnapshotItem:
460460
typ.is_type_obj(),
461461
typ.is_ellipsis_args,
462462
snapshot_types(typ.variables),
463+
typ.is_bound,
463464
)
464465

465466
def normalize_callable_variables(self, typ: CallableType) -> CallableType:

mypy/typeops.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ def type_object_type(info: TypeInfo, named_type: Callable[[str], Instance]) -> P
185185
arg_kinds=[ARG_STAR, ARG_STAR2],
186186
arg_names=["_args", "_kwds"],
187187
ret_type=any_type,
188+
is_bound=True,
188189
fallback=named_type("builtins.function"),
189190
)
190191
return class_callable(sig, info, fallback, None, is_new=False)
@@ -479,7 +480,7 @@ class B(A): pass
479480
arg_kinds=func.arg_kinds[1:],
480481
arg_names=func.arg_names[1:],
481482
variables=variables,
482-
bound_args=[original_type],
483+
is_bound=True,
483484
)
484485
return cast(F, res)
485486

0 commit comments

Comments
 (0)