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

Improve solution to extended type juggling

This commit is contained in:
Matthew Brown 2019-10-29 19:30:19 +00:00
parent 8d7cdeb8ec
commit 4845bbbd49
4 changed files with 134 additions and 69 deletions

View File

@ -130,9 +130,6 @@ class TypeAnalyzer
if ($union_comparison_result) {
$atomic_comparison_result = new TypeComparisonResult();
$atomic_comparison_result->to_string_cast = false;
$atomic_comparison_result->type_coerced = false;
$atomic_comparison_result->type_coerced_from_mixed = false;
} else {
$atomic_comparison_result = null;
}
@ -1873,16 +1870,44 @@ class TypeAnalyzer
continue;
}
$param_comparison_result = new TypeComparisonResult();
if (!self::isContainedBy(
$codebase,
$input_param,
$container_param,
$input_param->ignore_nullable_issues,
$input_param->ignore_falsable_issues,
$atomic_comparison_result,
$param_comparison_result,
$allow_interface_equality
)) {
$all_types_contain = false;
$atomic_comparison_result->type_coerced
= $param_comparison_result->type_coerced === true
&& $atomic_comparison_result->type_coerced !== false;
$atomic_comparison_result->type_coerced_from_mixed
= $param_comparison_result->type_coerced_from_mixed === true
&& $atomic_comparison_result->type_coerced_from_mixed !== false;
$atomic_comparison_result->type_coerced_from_as_mixed
= $param_comparison_result->type_coerced_from_as_mixed === true
&& $atomic_comparison_result->type_coerced_from_as_mixed !== false;
$atomic_comparison_result->to_string_cast
= $param_comparison_result->to_string_cast === true
&& $atomic_comparison_result->to_string_cast !== false;
$atomic_comparison_result->type_coerced_from_scalar
= $param_comparison_result->type_coerced_from_scalar === true
&& $atomic_comparison_result->type_coerced_from_scalar !== false;
$atomic_comparison_result->scalar_type_match_found
= $param_comparison_result->scalar_type_match_found === true
&& $atomic_comparison_result->scalar_type_match_found !== false;
if (!$param_comparison_result->type_coerced_from_as_mixed) {
$all_types_contain = false;
}
} elseif (!$input_type_part instanceof TIterable
&& !$container_param->hasTemplate()
&& !$input_param->hasTemplate()
@ -1911,7 +1936,7 @@ class TypeAnalyzer
$input_param,
$container_param->ignore_nullable_issues,
$container_param->ignore_falsable_issues,
$atomic_comparison_result,
$param_comparison_result,
$allow_interface_equality
) || $atomic_comparison_result->type_coerced
) {
@ -2040,8 +2065,6 @@ class TypeAnalyzer
}
}
$any_scalar_param_match = false;
foreach ($input_type_part->type_params as $i => $input_param) {
if ($i > 1) {
break;
@ -2076,35 +2099,35 @@ class TypeAnalyzer
$allow_interface_equality
)
) {
if ($param_comparison_result->type_coerced !== null) {
$atomic_comparison_result->type_coerced = $param_comparison_result->type_coerced;
}
$atomic_comparison_result->type_coerced
= $param_comparison_result->type_coerced === true
&& $atomic_comparison_result->type_coerced !== false;
if ($param_comparison_result->type_coerced_from_mixed !== null) {
$atomic_comparison_result->type_coerced_from_mixed
= $param_comparison_result->type_coerced_from_mixed;
}
$atomic_comparison_result->type_coerced_from_mixed
= $param_comparison_result->type_coerced_from_mixed === true
&& $atomic_comparison_result->type_coerced_from_mixed !== false;
if ($param_comparison_result->to_string_cast !== null) {
$atomic_comparison_result->to_string_cast = $param_comparison_result->to_string_cast;
}
$atomic_comparison_result->type_coerced_from_as_mixed
= $param_comparison_result->type_coerced_from_as_mixed === true
&& $atomic_comparison_result->type_coerced_from_as_mixed !== false;
if ($param_comparison_result->type_coerced_from_scalar !== null) {
$atomic_comparison_result->type_coerced_from_scalar
= $param_comparison_result->type_coerced_from_scalar;
}
$atomic_comparison_result->to_string_cast
= $param_comparison_result->to_string_cast === true
&& $atomic_comparison_result->to_string_cast !== false;
$all_types_contain = false;
$atomic_comparison_result->type_coerced_from_scalar
= $param_comparison_result->type_coerced_from_scalar === true
&& $atomic_comparison_result->type_coerced_from_scalar !== false;
if ($param_comparison_result->scalar_type_match_found) {
$any_scalar_param_match = true;
$atomic_comparison_result->scalar_type_match_found
= $param_comparison_result->scalar_type_match_found === true
&& $atomic_comparison_result->scalar_type_match_found !== false;
if (!$param_comparison_result->type_coerced_from_as_mixed) {
$all_types_contain = false;
}
}
}
if ($any_scalar_param_match) {
$atomic_comparison_result->scalar_type_match_found = true;
}
}
if ($container_type_part instanceof Type\Atomic\TNonEmptyArray

View File

@ -444,9 +444,10 @@ class TypeCombination
}
if ($combination->extra_types) {
$combination->extra_types = array_values(
self::combineTypes(array_values($combination->extra_types), $codebase)->getTypes()
);
$combination->extra_types = self::combineTypes(
array_values($combination->extra_types),
$codebase
)->getTypes();
}
foreach ($combination->builtin_type_params as $generic_type => $generic_type_params) {

View File

@ -2324,6 +2324,17 @@ class ClassTemplateExtendsTest extends TestCase
*/
interface Either{}
/**
* @template L
* @template-implements Either<L, mixed>
*/
final class Left implements Either {
/**
* @param L $value
*/
public function __construct($value) {}
}
/**
* @template R
* @template-implements Either<mixed,R>
@ -2335,12 +2346,17 @@ class ClassTemplateExtendsTest extends TestCase
public function __construct($value) {}
}
class A {}
class B {}
/**
* @return Either<B,B>
* @return Either<A,B>
*/
function result(bool $a = true): Either {
function result() {
if (rand(0, 1)) {
return new Left(new A());
}
return new Right(new B());
}'
],
@ -3163,6 +3179,32 @@ class ClassTemplateExtendsTest extends TestCase
class Foo extends DateTimeImmutable {}',
'error_message' => 'InvalidDocblock'
],
'invalidReturnParamType' => [
'<?php
/**
* @template L
* @template R
*/
interface Either {}
/**
* @template L
* @template-implements Either<L,mixed>
*/
class Left implements Either {
/** @param L $value */
public function __construct($value) { }
}
class A {}
class B {}
/** @return Either<A,B> */
function result(): Either {
return new Left(new B());
}',
'error_message' => 'InvalidReturnStatement'
],
];
}
}

View File

@ -1830,6 +1830,40 @@ class ClassTemplateTest extends TestCase
[],
['TooManyTemplateParams']
],
'coerceEmptyArrayToGeneral' => [
'<?php
/** @template T */
class Foo
{
/** @param \Closure(string):T $closure */
public function __construct($closure) {}
}
class Bar
{
/** @var Foo<array> */
private $FooArray;
public function __construct()
{
$this->FooArray = new Foo(function(string $s): array {
/** @psalm-suppress MixedAssignment */
$json = \json_decode($s, true);
if (! \is_array($json)) {
return [];
}
return $json;
});
takesFooArray($this->FooArray);
}
}
/** @param Foo<array> $_ */
function takesFooArray($_): void {}',
],
];
}
@ -2229,41 +2263,6 @@ class ClassTemplateTest extends TestCase
function expectsShape($_): void {}',
'error_message' => 'MixedArgumentTypeCoercion'
],
'coerceEmptyArrayToGeneral' => [
'<?php
/** @template T */
class Foo
{
/** @param \Closure(string):T $closure */
public function __construct($closure) {}
}
class Bar
{
/** @var Foo<array> */
private $FooArray;
public function __construct()
{
$this->FooArray = new Foo(function(string $s): array {
/** @psalm-suppress MixedAssignment */
$json = \json_decode($s, true);
if (! \is_array($json)) {
return [];
}
return $json;
});
takesEmpty($this->FooArray);
}
}
/** @param Foo<array<empty, empty>> $_ */
function takesEmpty($_): void {}',
'error_message' => 'MixedArgumentTypeCoercion'
],
];
}
}