Skip to content

Commit

Permalink
Fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
danog committed Dec 19, 2022
1 parent b691219 commit bde77f7
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 63 deletions.
37 changes: 37 additions & 0 deletions src/Psalm/Internal/Type/TypeCombination.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,22 @@
namespace Psalm\Internal\Type;

use Psalm\Type\Atomic;
use Psalm\Type\Atomic\TArrayKey;
use Psalm\Type\Atomic\TInt;
use Psalm\Type\Atomic\TIntRange;
use Psalm\Type\Atomic\TIterable;
use Psalm\Type\Atomic\TLiteralFloat;
use Psalm\Type\Atomic\TLiteralInt;
use Psalm\Type\Atomic\TLiteralString;
use Psalm\Type\Atomic\TNamedObject;
use Psalm\Type\Atomic\TObject;
use Psalm\Type\Atomic\TString;
use Psalm\Type\Atomic\TTemplateParam;
use Psalm\Type\Union;

use function is_int;
use function is_string;

/**
* @internal
*/
Expand Down Expand Up @@ -88,4 +95,34 @@ class TypeCombination

/** @var array<string, ?TNamedObject> */
public array $class_string_map_as_types = [];

/**
* @psalm-assert-if-true !null $this->objectlike_key_type
* @psalm-assert-if-true !null $this->objectlike_value_type
* @param array-key $k
*/
public function fallbackKeyContains($k): bool
{
if (!$this->objectlike_key_type) {
return false;
}
foreach ($this->objectlike_key_type->getAtomicTypes() as $t) {
if ($t instanceof TArrayKey) {
return true;
} elseif ($t instanceof TLiteralInt || $t instanceof TLiteralString) {
if ($t->value === $k) {
return true;
}
} elseif ($t instanceof TIntRange) {
if (is_int($k) && $t->contains($k)) {
return true;
}
} elseif ($t instanceof TString && is_string($k)) {
return true;
} elseif ($t instanceof TInt && is_int($k)) {
return true;
}
}
return false;
}
}
67 changes: 29 additions & 38 deletions src/Psalm/Internal/Type/TypeCombiner.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@
use function get_class;
use function is_int;
use function is_numeric;
use function is_string;
use function min;
use function strpos;
use function strtolower;
Expand Down Expand Up @@ -650,26 +649,12 @@ private static function scrapeTypeProperties(
$combination->objectlike_sealed = $combination->objectlike_sealed
&& $type->fallback_params === null;

if ($type->fallback_params) {
$combination->objectlike_key_type = Type::combineUnionTypes(
$type->fallback_params[0],
$combination->objectlike_key_type,
$codebase,
$overwrite_empty_array,
);
$combination->objectlike_value_type = Type::combineUnionTypes(
$type->fallback_params[1],
$combination->objectlike_value_type,
$codebase,
$overwrite_empty_array,
);
}

$has_defined_keys = false;

foreach ($type->properties as $candidate_property_name => $candidate_property_type) {
$value_type = $combination->objectlike_entries[$candidate_property_name] ?? null;


if (!$value_type) {
$combination->objectlike_entries[$candidate_property_name] = $candidate_property_type
->setPossiblyUndefined($existing_objectlike_entries
Expand All @@ -693,9 +678,33 @@ private static function scrapeTypeProperties(
$has_defined_keys = true;
}

if ($combination->fallbackKeyContains($candidate_property_name)) {
$combination->objectlike_entries[$candidate_property_name] = Type::combineUnionTypes(
$combination->objectlike_entries[$candidate_property_name],
$combination->objectlike_value_type,
$codebase,
$overwrite_empty_array,
);
}

unset($missing_entries[$candidate_property_name]);
}

if ($type->fallback_params) {
$combination->objectlike_key_type = Type::combineUnionTypes(
$type->fallback_params[0],
$combination->objectlike_key_type,
$codebase,
$overwrite_empty_array,
);
$combination->objectlike_value_type = Type::combineUnionTypes(
$type->fallback_params[1],
$combination->objectlike_value_type,
$codebase,
$overwrite_empty_array,
);
}

if (!$has_defined_keys) {
$combination->array_always_filled = false;
}
Expand All @@ -719,32 +728,14 @@ private static function scrapeTypeProperties(
->setPossiblyUndefined(true);
}

if ($type->fallback_params) {
if ($combination->objectlike_value_type) {
foreach ($missing_entries as $k => $_) {
foreach ($type->fallback_params[1]->getAtomicTypes() as $t) {
if ($t instanceof TArrayKey) {
break;
}
if ($t instanceof TString && is_string($k)) {
break;
}
if ($t instanceof TInt && is_int($k)) {
if ($t instanceof TIntRange && !$t->contains($k)) {
continue;
}
break;
}
if ($t instanceof TLiteralInt && $k === $t->value) {
break;
}
if ($t instanceof TLiteralString && $k === $t->value) {
break;
}
continue 2;
if (!$combination->fallbackKeyContains($k)) {
continue;
}
$combination->objectlike_entries[$k] = Type::combineUnionTypes(
$combination->objectlike_entries[$k],
$type->fallback_params[1],
$combination->objectlike_value_type,
$codebase,
$overwrite_empty_array,
);
Expand Down
25 changes: 0 additions & 25 deletions tests/ArgTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,31 +15,6 @@ class ArgTest extends TestCase
public function providerValidCodeParse(): iterable
{
return [
'arrayCombine' => [
'code' => '<?php
/**
* @return list{0, 0}|list<1>
*/
function ret() {
return [1, 1, 1];
}
',
],
'arrayCombine2' => [
'code' => '<?php
/**
* @return array{test1: 0, test2: 0}|list<1>
*/
function ret() {
return [1, 1, 1];
}
$result = ret();
',
'assertions' => [
'$result===' => 'array{0?: 1, test1?: 0, test2?: 0, ...<int<0, max>, 1>} ',
],
],
'argumentUnpackingLiteral' => [
'code' => '<?php
function add(int $a, int $b, int $c) : int {
Expand Down
51 changes: 51 additions & 0 deletions tests/ReturnTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,57 @@ class ReturnTypeTest extends TestCase
public function providerValidCodeParse(): iterable
{
return [
'arrayCombine' => [
'code' => '<?php
class a {}
/**
* @return list{0, 0}|list<a>
*/
function ret() {
return [new a, new a, new a];
}
$result = ret();
',
'assertions' => [
'$result===' => 'list{0?: 0|a, 1?: 0|a, ...<int<0, max>, a>}',
],
],
'arrayCombineInv' => [
'code' => '<?php
class a {}
/**
* @return list<a>|list{0, 0}
*/
function ret() {
return [new a, new a, new a];
}
$result = ret();
',
'assertions' => [
'$result===' => 'list{0?: 0|a, 1?: 0|a, ...<int<0, max>, a>}',
],
],
'arrayCombine2' => [
'code' => '<?php
class a {}
/**
* @return array{test1: 0, test2: 0}|list<a>
*/
function ret() {
return [1, 1, 1];
}
$result = ret();
',
'assertions' => [
'$result===' => 'array{0?: a, test1?: 0, test2?: 0, ...<int<0, max>, a>} ',
],
],
'returnTypeAfterUselessNullCheck' => [
'code' => '<?php
class One {}
Expand Down

0 comments on commit bde77f7

Please sign in to comment.