Skip to content

Commit

Permalink
Merge pull request #6392 from orklah/control-action-test
Browse files Browse the repository at this point in the history
  • Loading branch information
weirdan committed Sep 4, 2021
2 parents de4b344 + ea50e5d commit 3a3fde3
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 3 deletions.
16 changes: 16 additions & 0 deletions src/Psalm/Internal/Analyzer/ScopeAnalyzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,22 @@ function ($action) {
return $action !== self::ACTION_NONE;
}
);

if ($stmt instanceof PhpParser\Node\Stmt\While_
&& $nodes
&& ($stmt_expr_type = $nodes->getType($stmt->cond))
&& $stmt_expr_type->isAlwaysTruthy()
) {
//infinite while loop that only return don't have an exit path
$have_exit_path = (bool)array_diff(
$control_actions,
[self::ACTION_END, self::ACTION_RETURN]
);

if (!$have_exit_path) {
return array_values(array_unique($control_actions));
}
}
}

if ($stmt instanceof PhpParser\Node\Stmt\TryCatch) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ function (Type\Union $_): bool {
$cond_type = $statements_analyzer->node_data->getType($cond);

if ($cond_type !== null) {
if ($cond_type->isFalse()) {
if ($cond_type->isAlwaysFalsy()) {
if ($cond_type->from_docblock) {
if (IssueBuffer::accepts(
new DocblockTypeContradiction(
Expand Down
119 changes: 119 additions & 0 deletions src/Psalm/Type/Union.php
Original file line number Diff line number Diff line change
Expand Up @@ -958,11 +958,130 @@ public function isFalse(): bool
return count($this->types) === 1 && isset($this->types['false']);
}

public function isAlwaysFalsy(): bool
{
foreach ($this->getAtomicTypes() as $atomic_type) {
if ($atomic_type instanceof Type\Atomic\TFalse) {
continue;
}

if ($atomic_type instanceof Type\Atomic\TLiteralInt && $atomic_type->value === 0) {
continue;
}

if ($atomic_type instanceof Type\Atomic\TLiteralFloat && $atomic_type->value === 0.0) {
continue;
}

if ($atomic_type instanceof Type\Atomic\TLiteralString &&
($atomic_type->value === '' || $atomic_type->value === '0')
) {
continue;
}

if ($atomic_type instanceof Type\Atomic\TNull) {
continue;
}

if ($atomic_type instanceof TTemplateParam && $atomic_type->as->isAlwaysFalsy()) {
continue;
}

if ($atomic_type instanceof Type\Atomic\TIntRange &&
$atomic_type->min_bound === 0 &&
$atomic_type->max_bound === 0
) {
continue;
}

//we can't be sure the type is always falsy
return false;
}

return true;
}

public function isTrue(): bool
{
return count($this->types) === 1 && isset($this->types['true']);
}

public function isAlwaysTruthy(): bool
{
foreach ($this->getAtomicTypes() as $atomic_type) {
if ($atomic_type instanceof Type\Atomic\TTrue) {
continue;
}

if ($atomic_type instanceof Type\Atomic\TLiteralInt && $atomic_type->value !== 0) {
continue;
}

if ($atomic_type instanceof Type\Atomic\TLiteralFloat && $atomic_type->value !== 0.0) {
continue;
}

if ($atomic_type instanceof Type\Atomic\TLiteralString &&
($atomic_type->value !== '' && $atomic_type->value !== '0')
) {
continue;
}

if ($atomic_type instanceof Type\Atomic\TNonFalsyString) {
continue;
}

if ($atomic_type instanceof Type\Atomic\TNonEmptyArray) {
continue;
}

if ($atomic_type instanceof Type\Atomic\TNonEmptyList) {
continue;
}

if ($atomic_type instanceof Type\Atomic\TObject) {
continue;
}

if ($atomic_type instanceof Type\Atomic\TNamedObject) {
continue;
}

/*if ($atomic_type instanceof Type\Atomic\TIntRange && !$atomic_type->contains(0)) {
continue;
}*/

if ($atomic_type instanceof Type\Atomic\TPositiveInt) {
continue;
}

if ($atomic_type instanceof Type\Atomic\TLiteralClassString) {
continue;
}

if ($atomic_type instanceof Type\Atomic\TClassString) {
continue;
}

if ($atomic_type instanceof Type\Atomic\TKeyedArray) {
foreach ($atomic_type->properties as $property) {
if ($property->possibly_undefined === false) {
continue;
}
}
}

if ($atomic_type instanceof TTemplateParam && $atomic_type->as->isAlwaysTruthy()) {
continue;
}

//we can't be sure the type is always truthy
return false;
}

return true;
}

public function isVoid(): bool
{
return isset($this->types['void']);
Expand Down
40 changes: 40 additions & 0 deletions tests/Loop/WhileTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -708,6 +708,46 @@ function bar(A $a): void {
if ($a->foo !== null) {}
}'
],
'whileTrueDontHaveExitPathForReturn' => [
'<?php
function getResultWithRetry(): string
{
while (new stdClass) {
return "";
}
}'
],
'ComplexWhileTrueDontHaveExitPathForReturn' => [
'<?php
class Test {
private int $retryAttempts = 10;
private function getResult(): string
{
// return tring or throw exception whatever
throw new Exception();
}
private function getResultWithRetry(): string
{
$attempt = 1;
while (true) {
try {
return $this->getResult();
} catch (Throwable $exception) {
if ($attempt >= $this->retryAttempts) {
throw $exception;
}
$attempt++;
continue;
}
}
}
}'
],
];
}

Expand Down
4 changes: 2 additions & 2 deletions tests/TypeReconciliation/AssignmentInConditionalTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ function foo(string $str): ?int {
'repeatedSet' => [
'<?php
function foo(): void {
if ($a = rand(0, 1) ? "" : null) {
if ($a = rand(0, 1) ? "1" : null) {
return;
}
Expand All @@ -399,7 +399,7 @@ function foo(): void {
'repeatedSetInsideWhile' => [
'<?php
function foo(): void {
if ($a = rand(0, 1) ? "" : null) {
if ($a = rand(0, 1) ? "1" : null) {
return;
} else {
while (rand(0, 1)) {
Expand Down
7 changes: 7 additions & 0 deletions tests/TypeReconciliation/ConditionalTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3046,6 +3046,13 @@ function foo(Exception $e) : void {
}',
'error_message' => 'TypeDoesNotContainType',
],
'falsyValuesInIf' => [
'<?php
if (0) {
echo 123;
}',
'error_message' => 'TypeDoesNotContainType',
],
];
}
}

0 comments on commit 3a3fde3

Please sign in to comment.