8000 Add better way to report use of returned None's · python/mypy@f83cc53 · GitHub
[go: up one dir, main page]

Skip to content

Commit f83cc53

Browse files
committed
Add better way to report use of returned None's
1 parent f7af028 commit f83cc53

File tree

4 files changed

+48
-15
lines changed

4 files changed

+48
-15
lines changed

mypy/checker.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1709,7 +1709,12 @@ def try_infer_partial_type_from_indexed_assignment(
17091709
del partial_types[var]
17101710

17111711
def visit_expression_stmt(self, s: ExpressionStmt) -> None:
1712-
self.expr_checker.accept(s.expr)
1712+
# Special-case call exprs so we can support warnings on return value of
1713+
# None-returning functions
1714+
if isinstance(s.expr, CallExpr):
1715+
self.expr_checker.accept(s.expr, allow_none_return=True)
1716+
else:
1717+
self.expr_checker.accept(s.expr)
17131718

17141719
def visit_return_stmt(self, s: ReturnStmt) -> None:
17151720
"""Type check a return statement."""
@@ -1730,8 +1735,12 @@ def check_return_stmt(self, s: ReturnStmt) -> None:
17301735
return
17311736

17321737
if s.expr:
1738+
is_lambda = isinstance(self.scope.top_function(), FuncExpr)
1739+
declared_none_return = isinstance(return_type, NoneTyp)
17331740
# Return with a value.
1734-
typ = self.expr_checker.accept(s.expr, return_type)
1741+
typ = self.expr_checker.accept(s.expr,
1742+
return_type,
1743+
allow_none_return=is_lambda or declared_none_return)
17351744
# Returning a value of type Any is always fine.
17361745
if isinstance(typ, AnyType):
17371746
# (Unless you asked to be warned in that case, and the
@@ -1740,10 +1749,10 @@ def check_return_stmt(self, s: ReturnStmt) -> None:
17401749
self.warn(messages.RETURN_ANY.format(return_type), s)
17411750
return
17421751

1743-
if self.is_unusable_type(return_type):
1744-
# Lambdas are allowed to have a unusable returns.
1752+
if declared_none_return:
1753+
# Lambdas are allowed to have None returns.
17451754
# Functions returning a value of type None are allowed to have a None return.
1746-
if isinstance(self.scope.top_function(), FuncExpr) or isinstance(typ, NoneTyp):
1755+
if is_lambda or isinstance(typ, NoneTyp):
17471756
return
17481757
self.fail(messages.NO_RETURN_VALUE_EXPECTED, s)
17491758
else:
@@ -2072,7 +2081,7 @@ def visit_del_stmt(self, s: DelStmt) -> None:
20722081
m.line = s.line
20732082
c = CallExpr(m, [e.index], [nodes.ARG_POS], [None])
20742083
c.line = s.line
2075-
c.accept(self.expr_checker)
2084+
self.expr_checker.accept(c, allow_none_return=True)
20762085
else:
20772086
s.expr.accept(self.expr_checker)
20782087
for elt in flatten(s.expr):

mypy/checkexpr.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ def analyze_var_ref(self, var: Var, context: Context) -> Type:
173173
else:
174174
return val
175175

176-
def visit_call_expr(self, e: CallExpr) -> Type:
176+
def visit_call_expr(self, e: CallExpr, allow_none_return: bool = False) -> Type:
177177
"""Type check a call expression."""
178178
if e.analyzed:
179179
# It's really a special form that only looks like a call.
@@ -192,6 +192,8 @@ def visit_call_expr(self, e: CallExpr) -> Type:
192192
ret_type = self.check_call_expr_with_callee_type(callee_type, e)
193193
if isinstance(ret_type, UninhabitedType):
194194
self.chk.binder.unreachable()
195+
if not allow_none_return and isinstance(ret_type, NoneTyp):
196+
self.chk.msg.does_not_return_value(callee_type, e)
195197
return ret_type
196198

197199
def check_typeddict_call(self, callee: TypedDictType,
@@ -1516,13 +1518,12 @@ def visit_enum_index_expr(self, enum_type: TypeInfo, index: Expression,
15161518

15171519
def visit_cast_expr(self, expr: CastExpr) -> Type:
15181520
"""Type check a cast expression."""
1519-
source_type = self.accept(expr.expr, type_context=AnyType())
1521+
source_type = self.accept(expr.expr, type_context=AnyType(), allow_none_return=True)
15201522
target_type = expr.type
15211523
if self.chk.options.warn_redundant_casts and is_same_type(source_type, target_type):
15221524
self.msg.redundant_cast(target_type, expr)
15231525
return target_type
15241526

1525-
15261527
def visit_reveal_type_expr(self, expr: RevealTypeExpr) -> Type:
15271528
"""Type check a reveal_type expression."""
15281529
revealed_type = self.accept(expr.expr, type_context=self.type_context[-1])
@@ -2006,11 +2007,21 @@ def visit_backquote_expr(self, e: BackquoteExpr) -> Type:
20062007
# Helpers
20072008
#
20082009

2009-
def accept(self, node: Expression, type_context: Type = None) -> Type:
2010-
"""Type check a node in the given type context."""
2010+
def accept(self,
2011+
node: Expression,
2012+
type_context: Type = None,
2013+
allow_none_return: bool = False
2014+
) -> Type:
2015+
"""Type check a node in the given type context. If allow_none_return
2016+
is True and this expression is a call, allow it to return None. This
2017+
applies only to this expression and not any subexpressions.
2018+
"""
20112019
self.type_context.append(type_context)
20122020
try:
2013-
typ = node.accept(self)
2021+
if allow_none_return and isinstance(node, CallExpr):
2022+
typ = self.visit_call_expr(node, allow_none_return=True)
2023+
else:
2024+
typ = node.accept(self)
20142025
except Exception as err:
20152026
report_internal_error(err, self.chk.errors.file,
20162027
node.line, self.chk.errors, self.chk.options)

mypy/messages.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -616,9 +616,13 @@ def duplicate_argument_value(self, callee: CallableType, index: int,
616616
format(capitalize(callable_name(callee)),
617617
callee.arg_names[index]), context)
618618

619-
def does_not_return_value(self, unusable_type: Type, context: Context) -> None:
619+
def does_not_return_value(self, callee_type: Type, context: Context) -> None:
620620
"""Report an error about use of an unusable type."""
621-
self.fail('Function does not return a value', context)
621+
if isinstance(callee_type, FunctionLike) and callee_type.get_name() is not None:
622+
self.fail('{} does not return a value'.format(
623+
capitalize(callee_type.get_name())), context)
624+
else:
625+
self.fail('Function does not return a value', context)
622626

623627
def deleted_as_rvalue(self, typ: DeletedType, context: Context) -> None:
624628
"""Report an error about using an deleted type as an rvalue."""

mypy/types.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,9 @@ def items(self) -> List['CallableType']: pass
506506
@abstractmethod
507507
def with_name(self, name: str) -> 'FunctionLike': pass
508508

509+
@abstractmethod
510+
def get_name(self) -> str: pass
511+
509512
# Corresponding instance type (e.g. builtins.type)
510513
fallback = None # type: Instance
511514

@@ -632,6 +635,9 @@ def with_name(self, name: str) -> 'CallableType':
632635
"""Return a copy of this type with the specified name."""
633636
return self.copy_modified(ret_type=self.ret_type, name=name)
634637

638+
def get_name(self) -> str:
639+
return self.name
640+
635641
def max_fixed_args(self) -> int:
636642
n = len(self.arg_types)
637643
if self.is_var_arg:
@@ -778,7 +784,7 @@ def items(self) -> List[CallableType]:
778784
return self._items
779785

780786
def name(self) -> str:
781-
return self._items[0].name
787+
return self.get_name()
782788

783789
def is_type_obj(self) -> bool:
784790
# All the items must have the same type object status, so it's
@@ -796,6 +802,9 @@ def with_name(self, name: str) -> 'Overloaded':
796802
ni.append(it.with_name(name))
797803
return Overloaded(ni)
798804

805+
def get_name(self) -> str:
806+
return self._items[0].name
807+
799808
def accept(self, visitor: 'TypeVisitor[T]') -> T:
800809
return visitor.visit_overloaded(self)
801810

0 commit comments

Comments
 (0)
0