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:
parent
8d7cdeb8ec
commit
4845bbbd49
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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'
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -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'
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user