Skip to content

Commit 535d6e0

Browse files
Merge branch 'master' into exp_order
2 parents b52d7ab + bed188f commit 535d6e0

File tree

12 files changed

+82
-38
lines changed

12 files changed

+82
-38
lines changed

docs/source/command_line.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1159,7 +1159,7 @@ format into the specified directory.
11591159
Enabling incomplete/experimental features
11601160
*****************************************
11611161

1162-
.. option:: --enable-incomplete-feature {InlineTypedDict,PreciseTupleTypes}
1162+
.. option:: --enable-incomplete-feature {InlineTypedDict,PreciseTupleTypes,TypeForm}
11631163

11641164
Some features may require several mypy releases to implement, for example
11651165
due to their complexity, potential for backwards incompatibility, or
@@ -1214,6 +1214,9 @@ List of currently incomplete/experimental features:
12141214
# Without PreciseTupleTypes: tuple[int, ...]
12151215
# With PreciseTupleTypes: tuple[()] | tuple[int] | tuple[int, int]
12161216
1217+
* ``TypeForm``: this feature enables ``TypeForm``, as described in
1218+
`PEP 747 – Annotating Type Forms <https://peps.python.org/pep-0747/>_`.
1219+
12171220

12181221
Miscellaneous
12191222
*************

mypy/checker.py

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@
198198
from mypy.types import (
199199
ANY_STRATEGY,
200200
MYPYC_NATIVE_INT_NAMES,
201+
NOT_IMPLEMENTED_TYPE_NAMES,
201202
OVERLOAD_NAMES,
202203
AnyType,
203204
BoolTypeQuery,
@@ -4449,7 +4450,7 @@ def infer_variable_type(
44494450
# partial type which will be made more specific later. A partial type
44504451
# gets generated in assignment like 'x = []' where item type is not known.
44514452
if name.name != "_" and not self.infer_partial_type(name, lvalue, init_type):
4452-
self.msg.need_annotation_for_var(name, context, self.options.python_version)
4453+
self.msg.need_annotation_for_var(name, context, self.options)
44534454
self.set_inference_error_fallback_type(name, lvalue, init_type)
44544455
elif (
44554456
isinstance(lvalue, MemberExpr)
@@ -4459,7 +4460,7 @@ def infer_variable_type(
44594460
and not is_same_type(self.inferred_attribute_types[lvalue.def_var], init_type)
44604461
):
44614462
# Multiple, inconsistent types inferred for an attribute.
4462-
self.msg.need_annotation_for_var(name, context, self.options.python_version)
4463+
self.msg.need_annotation_for_var(name, context, self.options)
44634464
name.type = AnyType(TypeOfAny.from_error)
44644465
else:
44654466
# Infer type of the target.
@@ -4656,9 +4657,7 @@ def check_simple_assignment(
46564657
rvalue, type_context=lvalue_type, always_allow_any=always_allow_any
46574658
)
46584659
if not is_valid_inferred_type(rvalue_type, self.options) and inferred is not None:
4659-
self.msg.need_annotation_for_var(
4660-
inferred, context, self.options.python_version
4661-
)
4660+
self.msg.need_annotation_for_var(inferred, context, self.options)
46624661
rvalue_type = rvalue_type.accept(SetNothingToAny())
46634662

46644663
if (
@@ -4976,10 +4975,7 @@ def check_return_stmt(self, s: ReturnStmt) -> None:
49764975
)
49774976
# Treat NotImplemented as having type Any, consistent with its
49784977
# definition in typeshed prior to python/typeshed#4222.
4979-
if (
4980-
isinstance(typ, Instance)
4981-
and typ.type.fullname == "builtins._NotImplementedType"
4982-
):
4978+
if isinstance(typ, Instance) and typ.type.fullname in NOT_IMPLEMENTED_TYPE_NAMES:
49834979
typ = AnyType(TypeOfAny.special_form)
49844980

49854981
if defn.is_async_generator:
@@ -5138,7 +5134,11 @@ def type_check_raise(self, e: Expression, s: RaiseStmt, optional: bool = False)
51385134
# https://github.com/python/mypy/issues/11089
51395135
self.expr_checker.check_call(typ, [], [], e)
51405136

5141-
if isinstance(typ, Instance) and typ.type.fullname == "builtins._NotImplementedType":
5137+
if (isinstance(typ, Instance) and typ.type.fullname in NOT_IMPLEMENTED_TYPE_NAMES) or (
5138+
isinstance(e, CallExpr)
5139+
and isinstance(e.callee, RefExpr)
5140+
and e.callee.fullname == "builtins.NotImplemented"
5141+
):
51425142
self.fail(
51435143
message_registry.INVALID_EXCEPTION.with_additional_msg(
51445144
'; did you mean "NotImplementedError"?'
@@ -7680,7 +7680,7 @@ def enter_partial_types(
76807680
var.type = NoneType()
76817681
else:
76827682
if var not in self.partial_reported and not permissive:
7683-
self.msg.need_annotation_for_var(var, context, self.options.python_version)
7683+
self.msg.need_annotation_for_var(var, context, self.options)
76847684
self.partial_reported.add(var)
76857685
if var.type:
76867686
fixed = fixup_partial_type(var.type)
@@ -7707,9 +7707,7 @@ def handle_partial_var_type(
77077707
if in_scope:
77087708
context = partial_types[node]
77097709
if is_local or not self.options.allow_untyped_globals:
7710-
self.msg.need_annotation_for_var(
7711-
node, context, self.options.python_version
7712-
)
7710+
self.msg.need_annotation_for_var(node, context, self.options)
77137711
self.partial_reported.add(node)
77147712
else:
77157713
# Defer the node -- we might get a better type in the outer scope

mypy/checkexpr.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5099,7 +5099,10 @@ def fast_container_type(
50995099
self.resolved_type[e] = NoneType()
51005100
return None
51015101
ct = self.chk.named_generic_type(container_fullname, [vt])
5102-
self.resolved_type[e] = ct
5102+
if not self.in_lambda_expr:
5103+
# We cannot cache results in lambdas - their bodies can be accepted in
5104+
# error-suppressing watchers too early
5105+
self.resolved_type[e] = ct
51035106
return ct
51045107

51055108
def _first_or_join_fast_item(self, items: list[Type]) -> Type | None:
@@ -5334,7 +5337,10 @@ def fast_dict_type(self, e: DictExpr) -> Type | None:
53345337
self.resolved_type[e] = NoneType()
53355338
return None
53365339
dt = self.chk.named_generic_type("builtins.dict", [kt, vt])
5337-
self.resolved_type[e] = dt
5340+
if not self.in_lambda_expr:
5341+
# We cannot cache results in lambdas - their bodies can be accepted in
5342+
# error-suppressing watchers too early
5343+
self.resolved_type[e] = dt
53385344
return dt
53395345

53405346
def check_typeddict_literal_in_context(

mypy/main.py

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -256,10 +256,19 @@ def _fill_text(self, text: str, width: int, indent: str) -> str:
256256
if "\n" in text:
257257
# Assume we want to manually format the text
258258
return super()._fill_text(text, width, indent)
259-
else:
260-
# Assume we want argparse to manage wrapping, indenting, and
261-
# formatting the text for us.
262-
return argparse.HelpFormatter._fill_text(self, text, width, indent)
259+
# Format the text like argparse, but overflow rather than
260+
# breaking long words (like URLs)
261+
text = self._whitespace_matcher.sub(" ", text).strip()
262+
import textwrap
263+
264+
return textwrap.fill(
265+
text,
266+
width,
267+
initial_indent=indent,
268+
subsequent_indent=indent,
269+
break_on_hyphens=False,
270+
break_long_words=False,
271+
)
263272

264273

265274
# Define pairs of flag prefixes with inverse meaning.
@@ -544,10 +553,15 @@ def add_invertible_flag(
544553
# Feel free to add subsequent sentences that add additional details.
545554
# 3. If you cannot think of a meaningful description for a new group, omit it entirely.
546555
# (E.g. see the "miscellaneous" sections).
547-
# 4. The group description should end with a period (unless the last line is a link). If you
548-
# do end the group description with a link, omit the 'http://' prefix. (Some links are too
549-
# long and will break up into multiple lines if we include that prefix, so for consistency
550-
# we omit the prefix on all links.)
556+
# 4. The text of the group description should end with a period, optionally followed
557+
# by a documentation reference (URL).
558+
# 5. If you want to include a documentation reference, place it at the end of the
559+
# description. Feel free to open with a brief reference ("See also:", "For more
560+
# information:", etc.), followed by a space, then the entire URL including
561+
# "https://" scheme identifier and fragment ("#some-target-heading"), if any.
562+
# Do not end with a period (or any other characters not part of the URL).
563+
# URLs longer than the available terminal width will overflow without being
564+
# broken apart. This facilitates both URL detection, and manual copy-pasting.
551565

552566
general_group = parser.add_argument_group(title="Optional arguments")
553567
general_group.add_argument(
@@ -1034,7 +1048,7 @@ def add_invertible_flag(
10341048
"Mypy caches type information about modules into a cache to "
10351049
"let you speed up future invocations of mypy. Also see "
10361050
"mypy's daemon mode: "
1037-
"mypy.readthedocs.io/en/stable/mypy_daemon.html#mypy-daemon",
1051+
"https://mypy.readthedocs.io/en/stable/mypy_daemon.html#mypy-daemon",
10381052
)
10391053
incremental_group.add_argument(
10401054
"-i", "--incremental", action="store_true", help=argparse.SUPPRESS
@@ -1278,7 +1292,7 @@ def add_invertible_flag(
12781292
code_group = parser.add_argument_group(
12791293
title="Running code",
12801294
description="Specify the code you want to type check. For more details, see "
1281-
"mypy.readthedocs.io/en/stable/running_mypy.html#running-mypy",
1295+
"https://mypy.readthedocs.io/en/stable/running_mypy.html#running-mypy",
12821296
)
12831297
add_invertible_flag(
12841298
"--explicit-package-bases",

mypy/messages.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1803,18 +1803,17 @@ def unimported_type_becomes_any(self, prefix: str, typ: Type, ctx: Context) -> N
18031803
)
18041804

18051805
def need_annotation_for_var(
1806-
self, node: SymbolNode, context: Context, python_version: tuple[int, int] | None = None
1806+
self, node: SymbolNode, context: Context, options: Options | None = None
18071807
) -> None:
18081808
hint = ""
1809-
pep604_supported = not python_version or python_version >= (3, 10)
18101809
# type to recommend the user adds
18111810
recommended_type = None
18121811
# Only gives hint if it's a variable declaration and the partial type is a builtin type
1813-
if python_version and isinstance(node, Var) and isinstance(node.type, PartialType):
1812+
if options and isinstance(node, Var) and isinstance(node.type, PartialType):
18141813
type_dec = "<type>"
18151814
if not node.type.type:
18161815
# partial None
1817-
if pep604_supported:
1816+
if options.use_or_syntax():
18181817
recommended_type = f"{type_dec} | None"
18191818
else:
18201819
recommended_type = f"Optional[{type_dec}]"

mypy/suggestions.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -890,7 +890,8 @@ def visit_typeddict_type(self, t: TypedDictType) -> str:
890890

891891
def visit_union_type(self, t: UnionType) -> str:
892892
if len(t.items) == 2 and is_overlapping_none(t):
893-
return f"Optional[{remove_optional(t).accept(self)}]"
893+
s = remove_optional(t).accept(self)
894+
return f"{s} | None" if self.options.use_or_syntax() else f"Optional[{s}]"
894895
else:
895896
return super().visit_union_type(t)
896897

mypy/test/testcheck.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ def run_case_once(
140140
options.hide_error_codes = False
141141
if "abstract" not in testcase.file:
142142
options.allow_empty_bodies = not testcase.name.endswith("_no_empty")
143-
if "union-error" not in testcase.file:
143+
if "union-error" not in testcase.file and "Pep604" not in testcase.name:
144144
options.force_union_syntax = True
145145

146146
if incremental_step and options.incremental:

mypy/types.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,8 @@
200200

201201
ELLIPSIS_TYPE_NAMES: Final = ("builtins.ellipsis", "types.EllipsisType")
202202

203+
NOT_IMPLEMENTED_TYPE_NAMES: Final = ("builtins._NotImplementedType", "types.NotImplementedType")
204+
203205
# A placeholder used for Bogus[...] parameters
204206
_dummy: Final[Any] = object()
205207

test-data/unit/check-inference-context.test

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -709,6 +709,20 @@ f(
709709
A(), r=B())
710710
[builtins fixtures/isinstance.pyi]
711711

712+
[case testLambdaWithFastContainerType]
713+
from collections.abc import Callable
714+
from typing import Never, TypeVar
715+
716+
T = TypeVar("T")
717+
718+
def f(a: Callable[[], T]) -> None: ...
719+
720+
def foo(x: str) -> Never: ...
721+
722+
f(lambda: [foo(0)]) # E: Argument 1 to "foo" has incompatible type "int"; expected "str"
723+
f(lambda: {"x": foo(0)}) # E: Argument 1 to "foo" has incompatible type "int"; expected "str"
724+
[builtins fixtures/tuple.pyi]
725+
712726

713727
-- Overloads + generic functions
714728
-- -----------------------------

test-data/unit/check-inference.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3548,7 +3548,7 @@ if x:
35483548
[builtins fixtures/dict.pyi]
35493549

35503550
[case testSuggestPep604AnnotationForPartialNone]
3551-
# flags: --local-partial-types --python-version 3.10
3551+
# flags: --local-partial-types --python-version 3.10 --no-force-union-syntax
35523552
x = None # E: Need type annotation for "x" (hint: "x: <type> | None = ...")
35533553

35543554
[case testTupleContextFromIterable]

0 commit comments

Comments
 (0)