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

Add more refined treatment of InvalidIterator

This commit is contained in:
Matthew Brown 2018-03-20 22:59:22 -04:00
parent 5384f193d1
commit b634e1a1b7
5 changed files with 69 additions and 10 deletions

View File

@ -190,6 +190,7 @@
<xs:element name="ParadoxicalCondition" type="IssueHandlerType" minOccurs="0" /> <xs:element name="ParadoxicalCondition" type="IssueHandlerType" minOccurs="0" />
<xs:element name="ParentNotFound" type="IssueHandlerType" minOccurs="0" /> <xs:element name="ParentNotFound" type="IssueHandlerType" minOccurs="0" />
<xs:element name="PossiblyFalseArgument" type="IssueHandlerType" minOccurs="0" /> <xs:element name="PossiblyFalseArgument" type="IssueHandlerType" minOccurs="0" />
<xs:element name="PossiblyFalseIterator" type="IssueHandlerType" minOccurs="0" />
<xs:element name="PossiblyFalseOperand" type="IssueHandlerType" minOccurs="0" /> <xs:element name="PossiblyFalseOperand" type="IssueHandlerType" minOccurs="0" />
<xs:element name="PossiblyFalsePropertyAssignmentValue" type="IssueHandlerType" minOccurs="0" /> <xs:element name="PossiblyFalsePropertyAssignmentValue" type="IssueHandlerType" minOccurs="0" />
<xs:element name="PossiblyFalseReference" type="IssueHandlerType" minOccurs="0" /> <xs:element name="PossiblyFalseReference" type="IssueHandlerType" minOccurs="0" />
@ -198,6 +199,7 @@
<xs:element name="PossiblyInvalidArrayAssignment" type="IssueHandlerType" minOccurs="0" /> <xs:element name="PossiblyInvalidArrayAssignment" type="IssueHandlerType" minOccurs="0" />
<xs:element name="PossiblyInvalidArrayOffset" type="IssueHandlerType" minOccurs="0" /> <xs:element name="PossiblyInvalidArrayOffset" type="IssueHandlerType" minOccurs="0" />
<xs:element name="PossiblyInvalidFunctionCall" type="IssueHandlerType" minOccurs="0" /> <xs:element name="PossiblyInvalidFunctionCall" type="IssueHandlerType" minOccurs="0" />
<xs:element name="PossiblyInvalidIterator" type="IssueHandlerType" minOccurs="0" />
<xs:element name="PossiblyInvalidMethodCall" type="IssueHandlerType" minOccurs="0" /> <xs:element name="PossiblyInvalidMethodCall" type="IssueHandlerType" minOccurs="0" />
<xs:element name="PossiblyInvalidOperand" type="IssueHandlerType" minOccurs="0" /> <xs:element name="PossiblyInvalidOperand" type="IssueHandlerType" minOccurs="0" />
<xs:element name="PossiblyInvalidPropertyAssignment" type="IssueHandlerType" minOccurs="0" /> <xs:element name="PossiblyInvalidPropertyAssignment" type="IssueHandlerType" minOccurs="0" />

View File

@ -1150,6 +1150,15 @@ function foo(string $s) : void {
} }
``` ```
### PossiblyFalseIterator
Emitted when trying to iterate over a value that may be `false`
```php
$arr = rand(0, 1) ? [1, 2, 3] : false;
foreach ($arr as $a) {}
```
### PossiblyFalseOperand ### PossiblyFalseOperand
Emitted when using a possibly `false` value as part of an operation (e.g. `+`, `.`, `^` etc.`) Emitted when using a possibly `false` value as part of an operation (e.g. `+`, `.`, `^` etc.`)
@ -1242,6 +1251,15 @@ $a = rand(0, 1) ? 5 : function() : int { return 5; };
$b = $a(); $b = $a();
``` ```
### PossiblyInvalidIterator
Emitted when trying to iterate over a value that may be invalid
```php
$arr = rand(0, 1) ? [1, 2, 3] : "hello";
foreach ($arr as $a) {}
```
### PossiblyInvalidMethodCall ### PossiblyInvalidMethodCall
Emitted when trying to call a method on a value that may not be an object Emitted when trying to call a method on a value that may not be an object

View File

@ -13,6 +13,8 @@ use Psalm\Exception\DocblockParseException;
use Psalm\Issue\InvalidDocblock; use Psalm\Issue\InvalidDocblock;
use Psalm\Issue\InvalidIterator; use Psalm\Issue\InvalidIterator;
use Psalm\Issue\NullIterator; use Psalm\Issue\NullIterator;
use Psalm\Issue\PossiblyFalseIterator;
use Psalm\Issue\PossiblyInvalidIterator;
use Psalm\Issue\PossiblyNullIterator; use Psalm\Issue\PossiblyNullIterator;
use Psalm\Issue\RawObjectIteration; use Psalm\Issue\RawObjectIteration;
use Psalm\IssueBuffer; use Psalm\IssueBuffer;
@ -87,7 +89,7 @@ class ForeachChecker
} }
} elseif ($iterator_type->isFalsable() && !$iterator_type->ignore_falsable_issues) { } elseif ($iterator_type->isFalsable() && !$iterator_type->ignore_falsable_issues) {
if (IssueBuffer::accepts( if (IssueBuffer::accepts(
new InvalidIterator( new PossiblyFalseIterator(
'Cannot iterate over falsable var ' . $iterator_type, 'Cannot iterate over falsable var ' . $iterator_type,
new CodeLocation($statements_checker->getSource(), $stmt->expr) new CodeLocation($statements_checker->getSource(), $stmt->expr)
), ),
@ -97,11 +99,15 @@ class ForeachChecker
} }
} }
$has_valid_iterator = false;
$invalid_iterator_types = [];
foreach ($iterator_type->getTypes() as $iterator_type) { foreach ($iterator_type->getTypes() as $iterator_type) {
// if it's an empty array, we cannot iterate over it // if it's an empty array, we cannot iterate over it
if ($iterator_type instanceof Type\Atomic\TArray if ($iterator_type instanceof Type\Atomic\TArray
&& $iterator_type->type_params[1]->isEmpty() && $iterator_type->type_params[1]->isEmpty()
) { ) {
$has_valid_iterator = true;
continue; continue;
} }
@ -129,27 +135,22 @@ class ForeachChecker
} else { } else {
$key_type = Type::combineUnionTypes($key_type, $key_type_part); $key_type = Type::combineUnionTypes($key_type, $key_type_part);
} }
$has_valid_iterator = true;
continue; continue;
} }
if ($iterator_type instanceof Type\Atomic\Scalar || if ($iterator_type instanceof Type\Atomic\Scalar ||
$iterator_type instanceof Type\Atomic\TVoid $iterator_type instanceof Type\Atomic\TVoid
) { ) {
if (IssueBuffer::accepts( $invalid_iterator_types[] = $iterator_type->getKey();
new InvalidIterator(
'Cannot iterate over ' . $iterator_type->getKey(),
new CodeLocation($statements_checker->getSource(), $stmt->expr)
),
$statements_checker->getSuppressedIssues()
)) {
return false;
}
$value_type = Type::getMixed(); $value_type = Type::getMixed();
} elseif ($iterator_type instanceof Type\Atomic\TObject || } elseif ($iterator_type instanceof Type\Atomic\TObject ||
$iterator_type instanceof Type\Atomic\TMixed || $iterator_type instanceof Type\Atomic\TMixed ||
$iterator_type instanceof Type\Atomic\TEmpty $iterator_type instanceof Type\Atomic\TEmpty
) { ) {
$has_valid_iterator = true;
$value_type = Type::getMixed(); $value_type = Type::getMixed();
} elseif ($iterator_type instanceof Type\Atomic\TNamedObject) { } elseif ($iterator_type instanceof Type\Atomic\TNamedObject) {
if ($iterator_type->value !== 'Traversable' && if ($iterator_type->value !== 'Traversable' &&
@ -165,6 +166,8 @@ class ForeachChecker
} }
} }
$has_valid_iterator = true;
if ($iterator_type instanceof Type\Atomic\TGenericObject && if ($iterator_type instanceof Type\Atomic\TGenericObject &&
(strtolower($iterator_type->value) === 'iterable' || (strtolower($iterator_type->value) === 'iterable' ||
strtolower($iterator_type->value) === 'traversable' || strtolower($iterator_type->value) === 'traversable' ||
@ -263,6 +266,30 @@ class ForeachChecker
} }
} }
} }
if ($invalid_iterator_types) {
if ($has_valid_iterator) {
if (IssueBuffer::accepts(
new PossiblyInvalidIterator(
'Cannot iterate over ' . $invalid_iterator_types[0],
new CodeLocation($statements_checker->getSource(), $stmt->expr)
),
$statements_checker->getSuppressedIssues()
)) {
return false;
}
} else {
if (IssueBuffer::accepts(
new InvalidIterator(
'Cannot iterate over ' . $invalid_iterator_types[0],
new CodeLocation($statements_checker->getSource(), $stmt->expr)
),
$statements_checker->getSuppressedIssues()
)) {
return false;
}
}
}
} }
if ($stmt->keyVar && $stmt->keyVar instanceof PhpParser\Node\Expr\Variable && is_string($stmt->keyVar->name)) { if ($stmt->keyVar && $stmt->keyVar instanceof PhpParser\Node\Expr\Variable && is_string($stmt->keyVar->name)) {

View File

@ -0,0 +1,6 @@
<?php
namespace Psalm\Issue;
class PossiblyFalseIterator extends CodeIssue
{
}

View File

@ -0,0 +1,6 @@
<?php
namespace Psalm\Issue;
class PossiblyInvalidIterator extends CodeIssue
{
}