1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-30 04:39:00 +01:00

Warn when methods with typehints don’t return for all codepaths

This commit is contained in:
Matt Brown 2018-01-02 16:57:40 -05:00
parent fdff250c4a
commit 310f91ea81
5 changed files with 61 additions and 19 deletions

View File

@ -1015,14 +1015,49 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
/** @var PhpParser\Node\Stmt[] */
$function_stmts = $this->function->getStmts();
$inferred_return_types = EffectsAnalyser::getReturnTypes(
$inferred_return_type_parts = EffectsAnalyser::getReturnTypes(
$function_stmts,
$inferred_yield_types,
$ignore_nullable_issues,
true
);
$inferred_return_type = $inferred_return_types ? Type::combineTypes($inferred_return_types) : Type::getVoid();
if ($return_type
&& $return_type->from_docblock
&& ScopeChecker::getFinalControlActions($function_stmts) !== [ScopeChecker::ACTION_END]
&& !$inferred_yield_types
&& count($inferred_return_type_parts)
) {
// only add null if we have a return statement elsewhere and it wasn't void
foreach ($inferred_return_type_parts as $inferred_return_type_part) {
if (!$inferred_return_type_part instanceof Type\Atomic\TVoid) {
$inferred_return_type_parts[] = new Type\Atomic\TNull();
break;
}
}
}
if ($return_type
&& !$return_type->from_docblock
&& !$return_type->isVoid()
&& !$inferred_yield_types
&& ScopeChecker::getFinalControlActions($function_stmts) !== [ScopeChecker::ACTION_END]
) {
if (IssueBuffer::accepts(
new InvalidReturnType(
'Not all code paths of ' . $cased_method_id . ' end in a return statement',
$return_type_location
)
)) {
return false;
}
return null;
}
$inferred_return_type = $inferred_return_type_parts
? Type::combineTypes($inferred_return_type_parts)
: Type::getVoid();
$inferred_yield_type = $inferred_yield_types ? Type::combineTypes($inferred_yield_types) : null;
if ($inferred_yield_type) {
@ -1108,7 +1143,7 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
$method_id
);
if (!$inferred_return_types && !$inferred_yield_types) {
if (!$inferred_return_type_parts && !$inferred_yield_types) {
if ($declared_return_type->isVoid()) {
return null;
}

View File

@ -177,7 +177,7 @@ class ScopeChecker
}
}
if ($all_same) {
if ($all_same && $try_statement_actions !== [self::ACTION_NONE]) {
return $try_statement_actions;
}
}
@ -186,7 +186,7 @@ class ScopeChecker
}
}
$control_actions[] = SELF::ACTION_NONE;
$control_actions[] = self::ACTION_NONE;
return array_unique($control_actions);
}

View File

@ -162,20 +162,6 @@ class EffectsAnalyser
),
];
}
if (!$last_stmt instanceof PhpParser\Node\Stmt\Return_
&& Checker\ScopeChecker::getFinalControlActions($stmts) !== [Checker\ScopeChecker::ACTION_END]
&& !$yield_types
&& count($return_types)
) {
// only add null if we have a return statement elsewhere and it wasn't void
foreach ($return_types as $return_type) {
if (!$return_type instanceof Atomic\TVoid) {
$return_types[] = new Atomic\TNull();
break;
}
}
}
}
return $return_types;

View File

@ -624,6 +624,13 @@ class ReturnTypeTest extends TestCase
}',
'error_message' => 'MoreSpecificImplementedReturnType',
],
'returnTypehintRequiresExplicitReturn' => [
'<?php
function foo() : ?string {
if (rand(0, 1)) return "hello";
}',
'error_message' => 'InvalidReturnType',
],
];
}
}

View File

@ -48,7 +48,21 @@ class TryCatchTest extends TestCase
'$worked' => 'bool',
],
],
'alwaysReturnsBecauseCatchDoesNothing' => [
'<?php
function throws() : void {
throw new Exception("bad");
}
function foo() : string {
try {
throws();
} catch (Exception $e) {
// do nothing
}
return "hello";
}',
],
];
}