Skip to content

Commit 96d250c

Browse files
authored
Merge branch 'python:master' into missing-args-callable
2 parents 4badf10 + 7b4f631 commit 96d250c

33 files changed

+1018
-61
lines changed

docs/source/command_line.rst

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -749,8 +749,19 @@ of the above sections.
749749

750750
.. option:: --strict
751751

752-
This flag mode enables all optional error checking flags. You can see the
753-
list of flags enabled by strict mode in the full :option:`mypy --help` output.
752+
This flag mode enables a defined subset of optional error-checking flags.
753+
This subset primarily includes checks for inadvertent type unsoundness (i.e
754+
strict will catch type errors as long as intentional methods like type ignore
755+
or casting were not used.)
756+
757+
Note: the :option:`--warn-unreachable` flag
758+
is not automatically enabled by the strict flag.
759+
760+
The strict flag does not take precedence over other strict-related flags.
761+
Directly specifying a flag of alternate behavior will override the
762+
behavior of strict, regardless of the order in which they are passed.
763+
You can see the list of flags enabled by strict mode in the full
764+
:option:`mypy --help` output.
754765

755766
Note: the exact list of flags enabled by running :option:`--strict` may change
756767
over time.

mypy/checkexpr.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4732,8 +4732,8 @@ def visit_cast_expr(self, expr: CastExpr) -> Type:
47324732
options = self.chk.options
47334733
if (
47344734
options.warn_redundant_casts
4735-
and not isinstance(get_proper_type(target_type), AnyType)
4736-
and source_type == target_type
4735+
and not is_same_type(target_type, AnyType(TypeOfAny.special_form))
4736+
and is_same_type(source_type, target_type)
47374737
):
47384738
self.msg.redundant_cast(target_type, expr)
47394739
if options.disallow_any_unimported and has_any_from_unimported_type(target_type):

mypy/dmypy/client.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from mypy.dmypy_os import alive, kill
2121
from mypy.dmypy_util import DEFAULT_STATUS_FILE, receive, send
2222
from mypy.ipc import IPCClient, IPCException
23+
from mypy.main import RECURSION_LIMIT
2324
from mypy.util import check_python_version, get_terminal_width, should_force_color
2425
from mypy.version import __version__
2526

@@ -268,6 +269,10 @@ class BadStatus(Exception):
268269
def main(argv: list[str]) -> None:
269270
"""The code is top-down."""
270271
check_python_version("dmypy")
272+
273+
# set recursion limit consistent with mypy/main.py
274+
sys.setrecursionlimit(RECURSION_LIMIT)
275+
271276
args = parser.parse_args(argv)
272277
if not args.action:
273278
parser.print_usage()

mypy/fastparse.py

Lines changed: 72 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,29 @@ def parse(
239239
strip_function_bodies=strip_function_bodies,
240240
path=fnam,
241241
).visit(ast)
242+
243+
except RecursionError as e:
244+
# For very complex expressions it is possible to hit recursion limit
245+
# before reaching a leaf node.
246+
# Should reject at top level instead at bottom, since bottom would already
247+
# be at the threshold of the recursion limit, and may fail again later.
248+
# E.G. x1+x2+x3+...+xn -> BinOp(left=BinOp(left=BinOp(left=...
249+
try:
250+
# But to prove that is the cause of this particular recursion error,
251+
# try to walk the tree using builtin visitor
252+
ast3.NodeVisitor().visit(ast)
253+
except RecursionError:
254+
errors.report(
255+
-1, -1, "Source expression too complex to parse", blocker=False, code=codes.MISC
256+
)
257+
258+
tree = MypyFile([], [], False, {})
259+
260+
else:
261+
# re-raise original recursion error if it *can* be unparsed,
262+
# maybe this is some other issue that shouldn't be silenced/misdirected
263+
raise e
264+
242265
except SyntaxError as e:
243266
message = e.msg
244267
if feature_version > sys.version_info.minor and message.startswith("invalid syntax"):
@@ -381,7 +404,7 @@ def __init__(
381404
def note(self, msg: str, line: int, column: int) -> None:
382405
self.errors.report(line, column, msg, severity="note", code=codes.SYNTAX)
383406

384-
def fail(self, msg: ErrorMessage, line: int, column: int, blocker: bool = True) -> None:
407+
def fail(self, msg: ErrorMessage, line: int, column: int, blocker: bool) -> None:
385408
if blocker or not self.options.ignore_errors:
386409
# Make sure self.errors reflects any type ignores that we have parsed
387410
self.errors.set_file_ignored_lines(
@@ -406,6 +429,7 @@ def visit(self, node: AST | None) -> Any:
406429
method = "visit_" + node.__class__.__name__
407430
visitor = getattr(self, method)
408431
self.visitor_cache[typeobj] = visitor
432+
409433
return visitor(node)
410434

411435
def set_line(self, node: N, n: AstNode) -> N:
@@ -921,7 +945,12 @@ def do_func_def(
921945
):
922946
if n.returns:
923947
# PEP 484 disallows both type annotations and type comments
924-
self.fail(message_registry.DUPLICATE_TYPE_SIGNATURES, lineno, n.col_offset)
948+
self.fail(
949+
message_registry.DUPLICATE_TYPE_SIGNATURES,
950+
lineno,
951+
n.col_offset,
952+
blocker=False,
953+
)
925954
arg_types = [
926955
(
927956
a.type_annotation
@@ -933,7 +962,12 @@ def do_func_def(
933962
else:
934963
# PEP 484 disallows both type annotations and type comments
935964
if n.returns or any(a.type_annotation is not None for a in args):
936-
self.fail(message_registry.DUPLICATE_TYPE_SIGNATURES, lineno, n.col_offset)
965+
self.fail(
966+
message_registry.DUPLICATE_TYPE_SIGNATURES,
967+
lineno,
968+
n.col_offset,
969+
blocker=False,
970+
)
937971
translated_args: list[Type] = TypeConverter(
938972
self.errors, line=lineno, override_column=n.col_offset
939973
).translate_expr_list(func_type_ast.argtypes)
@@ -948,7 +982,7 @@ def do_func_def(
948982
except SyntaxError:
949983
stripped_type = n.type_comment.split("#", 2)[0].strip()
950984
err_msg = message_registry.TYPE_COMMENT_SYNTAX_ERROR_VALUE.format(stripped_type)
951-
self.fail(err_msg, lineno, n.col_offset)
985+
self.fail(err_msg, lineno, n.col_offset, blocker=False)
952986
if n.type_comment and n.type_comment[0] not in ["(", "#"]:
953987
self.note(
954988
"Suggestion: wrap argument types in parentheses", lineno, n.col_offset
@@ -970,7 +1004,12 @@ def do_func_def(
9701004
func_type = None
9711005
if any(arg_types) or return_type:
9721006
if len(arg_types) != 1 and any(isinstance(t, EllipsisType) for t in arg_types):
973-
self.fail(message_registry.ELLIPSIS_WITH_OTHER_TYPEARGS, lineno, n.col_offset)
1007+
self.fail(
1008+
message_registry.ELLIPSIS_WITH_OTHER_TYPEARGS,
1009+
lineno,
1010+
n.col_offset,
1011+
blocker=False,
1012+
)
9741013
elif len(arg_types) > len(arg_kinds):
9751014
self.fail(
9761015
message_registry.TYPE_SIGNATURE_TOO_MANY_ARGS,
@@ -1097,7 +1136,12 @@ def make_argument(
10971136
annotation = arg.annotation
10981137
type_comment = arg.type_comment
10991138
if annotation is not None and type_comment is not None:
1100-
self.fail(message_registry.DUPLICATE_TYPE_SIGNATURES, arg.lineno, arg.col_offset)
1139+
self.fail(
1140+
message_registry.DUPLICATE_TYPE_SIGNATURES,
1141+
arg.lineno,
1142+
arg.col_offset,
1143+
blocker=False,
1144+
)
11011145
arg_type = None
11021146
if annotation is not None:
11031147
arg_type = TypeConverter(self.errors, line=arg.lineno).visit(annotation)
@@ -1118,7 +1162,7 @@ def make_argument(
11181162
return argument
11191163

11201164
def fail_arg(self, msg: str, arg: ast3.arg) -> None:
1121-
self.fail(ErrorMessage(msg), arg.lineno, arg.col_offset)
1165+
self.fail(ErrorMessage(msg), arg.lineno, arg.col_offset, blocker=True)
11221166

11231167
# ClassDef(identifier name,
11241168
# expr* bases,
@@ -1164,18 +1208,21 @@ def validate_type_param(self, type_param: ast_TypeVar) -> None:
11641208
message_registry.TYPE_VAR_YIELD_EXPRESSION_IN_BOUND,
11651209
type_param.lineno,
11661210
type_param.col_offset,
1211+
blocker=True,
11671212
)
11681213
if isinstance(incorrect_expr, ast3.NamedExpr):
11691214
self.fail(
11701215
message_registry.TYPE_VAR_NAMED_EXPRESSION_IN_BOUND,
11711216
type_param.lineno,
11721217
type_param.col_offset,
1218+
blocker=True,
11731219
)
11741220
if isinstance(incorrect_expr, ast3.Await):
11751221
self.fail(
11761222
message_registry.TYPE_VAR_AWAIT_EXPRESSION_IN_BOUND,
11771223
type_param.lineno,
11781224
type_param.col_offset,
1225+
blocker=True,
11791226
)
11801227

11811228
def translate_type_params(self, type_params: list[Any]) -> list[TypeParam]:
@@ -1790,11 +1837,26 @@ def validate_type_alias(self, n: ast_TypeAlias) -> None:
17901837
if incorrect_expr is None:
17911838
return
17921839
if isinstance(incorrect_expr, (ast3.Yield, ast3.YieldFrom)):
1793-
self.fail(message_registry.TYPE_ALIAS_WITH_YIELD_EXPRESSION, n.lineno, n.col_offset)
1840+
self.fail(
1841+
message_registry.TYPE_ALIAS_WITH_YIELD_EXPRESSION,
1842+
n.lineno,
1843+
n.col_offset,
1844+
blocker=True,
1845+
)
17941846
if isinstance(incorrect_expr, ast3.NamedExpr):
1795-
self.fail(message_registry.TYPE_ALIAS_WITH_NAMED_EXPRESSION, n.lineno, n.col_offset)
1847+
self.fail(
1848+
message_registry.TYPE_ALIAS_WITH_NAMED_EXPRESSION,
1849+
n.lineno,
1850+
n.col_offset,
1851+
blocker=True,
1852+
)
17961853
if isinstance(incorrect_expr, ast3.Await):
1797-
self.fail(message_registry.TYPE_ALIAS_WITH_AWAIT_EXPRESSION, n.lineno, n.col_offset)
1854+
self.fail(
1855+
message_registry.TYPE_ALIAS_WITH_AWAIT_EXPRESSION,
1856+
n.lineno,
1857+
n.col_offset,
1858+
blocker=True,
1859+
)
17981860

17991861
# TypeAlias(identifier name, type_param* type_params, expr value)
18001862
def visit_TypeAlias(self, n: ast_TypeAlias) -> TypeAliasStmt | AssignmentStmt:

mypy/main.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242

4343
orig_stat: Final = os.stat
4444
MEM_PROFILE: Final = False # If True, dump memory profile
45+
RECURSION_LIMIT: Final = 2**14
4546

4647

4748
def stat_proxy(path: str) -> os.stat_result:
@@ -76,7 +77,7 @@ def main(
7677
util.check_python_version("mypy")
7778
t0 = time.time()
7879
# To log stat() calls: os.stat = stat_proxy
79-
sys.setrecursionlimit(2**14)
80+
sys.setrecursionlimit(RECURSION_LIMIT)
8081
if args is None:
8182
args = sys.argv[1:]
8283

@@ -1127,6 +1128,11 @@ def add_invertible_flag(
11271128
report_group.add_argument(
11281129
"-a", dest="mypyc_annotation_file", type=str, default=None, help=argparse.SUPPRESS
11291130
)
1131+
# Hidden mypyc feature: do not write any C files (keep existing ones and assume they exist).
1132+
# This can be useful when debugging mypyc bugs.
1133+
report_group.add_argument(
1134+
"--skip-c-gen", dest="mypyc_skip_c_generation", action="store_true", help=argparse.SUPPRESS
1135+
)
11301136

11311137
other_group = parser.add_argument_group(title="Miscellaneous")
11321138
other_group.add_argument("--quickstart-file", help=argparse.SUPPRESS)

mypy/options.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,9 @@ def __init__(self) -> None:
408408

409409
# Output html file for mypyc -a
410410
self.mypyc_annotation_file: str | None = None
411+
# Skip writing C output files, but perform all other steps of a build (allows
412+
# preserving manual tweaks to generated C file)
413+
self.mypyc_skip_c_generation = False
411414

412415
def use_lowercase_names(self) -> bool:
413416
if self.python_version >= (3, 9):

mypy/plugins/dataclasses.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -359,12 +359,12 @@ def transform(self) -> bool:
359359

360360
if decorator_arguments["frozen"]:
361361
if any(not parent["frozen"] for parent in parent_decorator_arguments):
362-
self._api.fail("Cannot inherit frozen dataclass from a non-frozen one", info)
362+
self._api.fail("Frozen dataclass cannot inherit from a non-frozen dataclass", info)
363363
self._propertize_callables(attributes, settable=False)
364364
self._freeze(attributes)
365365
else:
366366
if any(parent["frozen"] for parent in parent_decorator_arguments):
367-
self._api.fail("Cannot inherit non-frozen dataclass from a frozen one", info)
367+
self._api.fail("Non-frozen dataclass cannot inherit from a frozen dataclass", info)
368368
self._propertize_callables(attributes)
369369

370370
if decorator_arguments["slots"]:

mypy/semanal.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5591,7 +5591,7 @@ def visit_type_alias_stmt(self, s: TypeAliasStmt) -> None:
55915591
self.msg.unimported_type_becomes_any("Type alias target", res, s)
55925592
res = make_any_non_unimported(res)
55935593
eager = self.is_func_scope()
5594-
if isinstance(res, ProperType) and isinstance(res, Instance) and not res.args:
5594+
if isinstance(res, ProperType) and isinstance(res, Instance):
55955595
fix_instance(res, self.fail, self.note, disallow_any=False, options=self.options)
55965596
alias_node = TypeAlias(
55975597
res,

mypy/subtypes.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1091,6 +1091,11 @@ def visit_type_type(self, left: TypeType) -> bool:
10911091
right = self.right
10921092
if isinstance(right, TypeType):
10931093
return self._is_subtype(left.item, right.item)
1094+
if isinstance(right, Overloaded) and right.is_type_obj():
1095+
# Same as in other direction: if it's a constructor callable, all
1096+
# items should belong to the same class' constructor, so it's enough
1097+
# to check one of them.
1098+
return self._is_subtype(left, right.items[0])
10941099
if isinstance(right, CallableType):
10951100
if self.proper_subtype and not right.is_type_obj():
10961101
# We can't accept `Type[X]` as a *proper* subtype of Callable[P, X]

mypyc/analysis/dataflow.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
Cast,
1818
ComparisonOp,
1919
ControlOp,
20+
DecRef,
2021
Extend,
2122
Float,
2223
FloatComparisonOp,
@@ -25,6 +26,7 @@
2526
GetAttr,
2627
GetElementPtr,
2728
Goto,
29+
IncRef,
2830
InitStatic,
2931
Integer,
3032
IntOp,
@@ -77,12 +79,11 @@ def __str__(self) -> str:
7779
return f"exits: {exits}\nsucc: {self.succ}\npred: {self.pred}"
7880

7981

80-
def get_cfg(blocks: list[BasicBlock]) -> CFG:
82+
def get_cfg(blocks: list[BasicBlock], *, use_yields: bool = False) -> CFG:
8183
"""Calculate basic block control-flow graph.
8284
83-
The result is a dictionary like this:
84-
85-
basic block index -> (successors blocks, predecesssor blocks)
85+
If use_yields is set, then we treat returns inserted by yields as gotos
86+
instead of exits.
8687
"""
8788
succ_map = {}
8889
pred_map: dict[BasicBlock, list[BasicBlock]] = {}
@@ -92,7 +93,10 @@ def get_cfg(blocks: list[BasicBlock]) -> CFG:
9293
isinstance(op, ControlOp) for op in block.ops[:-1]
9394
), "Control-flow ops must be at the end of blocks"
9495

95-
succ = list(block.terminator.targets())
96+
if use_yields and isinstance(block.terminator, Return) and block.terminator.yield_target:
97+
succ = [block.terminator.yield_target]
98+
else:
99+
succ = list(block.terminator.targets())
96100
if not succ:
97101
exits.add(block)
98102

@@ -474,6 +478,12 @@ def visit_assign_multi(self, op: AssignMulti) -> GenAndKill[Value]:
474478
def visit_set_mem(self, op: SetMem) -> GenAndKill[Value]:
475479
return non_trivial_sources(op), set()
476480

481+
def visit_inc_ref(self, op: IncRef) -> GenAndKill[Value]:
482+
return set(), set()
483+
484+
def visit_dec_ref(self, op: DecRef) -> GenAndKill[Value]:
485+
return set(), set()
486+
477487

478488
def analyze_live_regs(blocks: list[BasicBlock], cfg: CFG) -> AnalysisResult[Value]:
479489
"""Calculate live registers at each CFG location.

0 commit comments

Comments
 (0)