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

Only make directly-assertable things possibly-undefined

Ref #2724
This commit is contained in:
Matthew Brown 2020-02-01 10:58:13 -05:00
parent f2347303c9
commit 292f6e797f
2 changed files with 98 additions and 51 deletions

View File

@ -225,8 +225,10 @@ class Reconciler
$codebase,
$key,
$existing_types,
$new_types,
$code_location,
$has_isset || $has_inverted_isset,
$has_isset,
$has_inverted_isset,
$has_empty
);
@ -422,6 +424,7 @@ class Reconciler
*
* @param string $key
* @param array<string,Type\Union> $existing_keys
* @param array<string,mixed> $new_assertions
* @param string[][] $new_type_parts
*
* @return Type\Union|null
@ -430,8 +433,10 @@ class Reconciler
Codebase $codebase,
string $key,
array &$existing_keys,
array $new_assertions,
?CodeLocation $code_location,
bool $has_isset,
bool $has_inverted_isset,
bool $has_empty
) {
$key_parts = self::breakUpPathIntoParts($key);
@ -488,7 +493,11 @@ class Reconciler
$new_base_type_candidate = clone $existing_key_type_part->type_params[1];
if ($has_isset) {
if (($has_isset || $has_inverted_isset) && isset($new_assertions[$new_base_key])) {
if ($has_inverted_isset && $new_base_key === $key) {
$new_base_type_candidate->addType(new Type\Atomic\TNull);
}
$new_base_type_candidate->possibly_undefined = true;
}
} elseif ($existing_key_type_part instanceof Type\Atomic\TList) {
@ -498,7 +507,11 @@ class Reconciler
$new_base_type_candidate = clone $existing_key_type_part->type_param;
if ($has_isset) {
if (($has_isset || $has_inverted_isset) && isset($new_assertions[$new_base_key])) {
if ($has_inverted_isset && $new_base_key === $key) {
$new_base_type_candidate->addType(new Type\Atomic\TNull);
}
$new_base_type_candidate->possibly_undefined = true;
}
} elseif ($existing_key_type_part instanceof Type\Atomic\TNull) {

View File

@ -796,20 +796,6 @@ class IssetTest extends \Psalm\Tests\TestCase
}
}'
],
'SKIPPED-issetOnArrayOfArraysReturningString' => [
'<?php
function foo(int $i) : ?string {
/** @var array<array> */
$tokens = [];
if (!isset($tokens[$i]["a"])) {
/** @psalm-suppress PossiblyUndefinedArrayOffset */
return $tokens[$i]["a"];
}
return "hello";
}',
],
'issetOnArrayOfArraysReturningStringInElse' => [
'<?php
function foo(int $i) : string {
@ -823,6 +809,84 @@ class IssetTest extends \Psalm\Tests\TestCase
}
}',
],
'issetOnArrayOfObjectsAssertingOnIssetValue' => [
'<?php
class A {
public ?string $name = null;
}
function foo(int $i) : void {
/** @var array<int, A> */
$tokens = [];
if (isset($tokens[$i]->name) && $tokens[$i]->name === "hello") {}
}',
],
'issetOnArrayOfObjectsAssertingOnNotIssetValue' => [
'<?php
class A {
public ?string $name = null;
}
function foo(int $i) : void {
/** @var array<int, A> */
$tokens = [];
if (!isset($tokens[$i])) {
if (rand(0, 1)) {
if (rand(0, 1)) {
$tokens[$i] = new A();
} else {
return;
}
} else {
return;
}
}
echo $tokens[$i]->name;
}',
],
'issetOnArrayOfMixed' => [
'<?php
/**
* @psalm-suppress MixedArrayAccess
* @psalm-suppress MixedArgument
*/
function foo(int $i) : void {
/** @var array */
$tokens = [];
if (!isset($tokens[$i]["a"])) {
echo $tokens[$i]["b"];
}
}',
],
'issetOnArrayOfArrays' => [
'<?php
/**
* @psalm-suppress MixedArgument
*/
function foo(int $i) : void {
/** @var array<array> */
$tokens = [];
if (!isset($tokens[$i]["a"])) {
echo $tokens[$i]["b"];
}
}',
],
'issetOnArrayOfArrayOfStrings' => [
'<?php
function foo(int $i) : void {
/** @var array<int, array<string, string>> */
$tokens = [];
if (!isset($tokens[$i]["a"])) {
echo $tokens[$i]["b"];
}
}',
],
];
}
@ -902,47 +966,17 @@ class IssetTest extends \Psalm\Tests\TestCase
}',
'error_message' => 'TypeDoesNotContainType'
],
'issetOnArrayOfMixed' => [
'issetOnArrayOfArraysReturningString' => [
'<?php
/**
* @psalm-suppress PossiblyUndefinedStringArrayOffset
* @psalm-suppress MixedArrayAccess
* @psalm-suppress MixedArgument
*/
function foo(int $i) : void {
/** @var array */
$tokens = [];
if (!isset($tokens[$i]["a"])) {
echo $tokens[$i]["b"];
}
}',
'error_message' => 'PossiblyUndefinedArrayOffset',
],
'SKIPPED-issetOnArrayOfArrays' => [
'<?php
/**
* @psalm-suppress MixedArgument
*/
function foo(int $i) : void {
function foo(int $i) : ?string {
/** @var array<array> */
$tokens = [];
if (!isset($tokens[$i]["a"])) {
echo $tokens[$i]["b"];
return $tokens[$i]["a"];
}
}',
'error_message' => 'PossiblyUndefinedArrayOffset',
],
'SKIPPED-issetOnArrayOfArrayOfStrings' => [
'<?php
function foo(int $i) : void {
/** @var array<int, array<string, string>> */
$tokens = [];
if (!isset($tokens[$i]["a"])) {
echo $tokens[$i]["b"];
}
return "hello";
}',
'error_message' => 'PossiblyUndefinedArrayOffset',
],