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:
parent
fdff250c4a
commit
310f91ea81
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -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";
|
||||
}',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user