mirror of
https://github.com/danog/psalm.git
synced 2024-11-30 04:39:00 +01:00
Accept partial match of template type
This commit is contained in:
parent
c82834ebe4
commit
1bf1a6e46b
@ -392,7 +392,8 @@ class TypeAnalyzer
|
|||||||
Type\Union $input_type,
|
Type\Union $input_type,
|
||||||
Type\Union $container_type,
|
Type\Union $container_type,
|
||||||
$ignore_null = false,
|
$ignore_null = false,
|
||||||
$ignore_false = false
|
$ignore_false = false,
|
||||||
|
array &$matching_input_keys = []
|
||||||
) {
|
) {
|
||||||
if ($container_type->hasMixed()) {
|
if ($container_type->hasMixed()) {
|
||||||
return true;
|
return true;
|
||||||
@ -425,12 +426,12 @@ class TypeAnalyzer
|
|||||||
if (($is_atomic_contained_by && !$atomic_comparison_result->to_string_cast)
|
if (($is_atomic_contained_by && !$atomic_comparison_result->to_string_cast)
|
||||||
|| $atomic_comparison_result->type_coerced_from_mixed
|
|| $atomic_comparison_result->type_coerced_from_mixed
|
||||||
) {
|
) {
|
||||||
return true;
|
$matching_input_keys[$input_type_part->getKey()] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return !!$matching_input_keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2475,9 +2475,15 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse
|
|||||||
if (!isset($param_type_mapping[$token_body])) {
|
if (!isset($param_type_mapping[$token_body])) {
|
||||||
$template_name = 'TGeneratedFromParam' . $j;
|
$template_name = 'TGeneratedFromParam' . $j;
|
||||||
|
|
||||||
|
$template_function_id = 'fn-' . strtolower($cased_function_id);
|
||||||
|
|
||||||
|
$template_as_type = $param_storage->type
|
||||||
|
? clone $param_storage->type
|
||||||
|
: Type::getMixed();
|
||||||
|
|
||||||
$storage->template_types[$template_name] = [
|
$storage->template_types[$template_name] = [
|
||||||
'fn-' . strtolower($cased_function_id) => [
|
$template_function_id => [
|
||||||
$param_storage->type ? clone $param_storage->type : Type::getMixed()
|
$template_as_type
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -2485,6 +2491,14 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse
|
|||||||
= $storage->template_types[$template_name];
|
= $storage->template_types[$template_name];
|
||||||
|
|
||||||
$param_type_mapping[$token_body] = $template_name;
|
$param_type_mapping[$token_body] = $template_name;
|
||||||
|
|
||||||
|
$param_storage->type = new Type\Union([
|
||||||
|
new Type\Atomic\TTemplateParam(
|
||||||
|
$template_name,
|
||||||
|
$template_as_type,
|
||||||
|
$template_function_id
|
||||||
|
)
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// spaces are allowed before $foo in get(string $foo) magic method
|
// spaces are allowed before $foo in get(string $foo) magic method
|
||||||
|
@ -478,19 +478,34 @@ class UnionTemplateHandler
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$matching_input_keys = [];
|
||||||
|
|
||||||
if ($input_type
|
if ($input_type
|
||||||
&& (
|
&& (
|
||||||
$atomic_type->as->isMixed()
|
$atomic_type->as->isMixed()
|
||||||
|| !$codebase
|
|| !$codebase
|
||||||
|| TypeAnalyzer::isContainedBy(
|
|| TypeAnalyzer::canBeContainedBy(
|
||||||
$codebase,
|
$codebase,
|
||||||
$input_type,
|
$input_type,
|
||||||
$atomic_type->as
|
$atomic_type->as,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
$matching_input_keys
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
$generic_param = clone $input_type;
|
$generic_param = clone $input_type;
|
||||||
|
|
||||||
|
if ($matching_input_keys) {
|
||||||
|
$generic_param_keys = \array_keys($generic_param->getAtomicTypes());
|
||||||
|
|
||||||
|
foreach ($generic_param_keys as $atomic_key) {
|
||||||
|
if (!isset($matching_input_keys[$atomic_key])) {
|
||||||
|
$generic_param->removeType($atomic_key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ($was_nullable && $generic_param->isNullable() && !$generic_param->isNull()) {
|
if ($was_nullable && $generic_param->isNullable() && !$generic_param->isNull()) {
|
||||||
$generic_param->removeType('null');
|
$generic_param->removeType('null');
|
||||||
}
|
}
|
||||||
|
@ -1256,6 +1256,17 @@ class FunctionCallTest extends TestCase
|
|||||||
'$a' => 'int',
|
'$a' => 'int',
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
'jsonEncodeString' => [
|
||||||
|
'<?php
|
||||||
|
$a = json_decode("hello");
|
||||||
|
$b = json_decode("hello", true);
|
||||||
|
$c = json_decode("hello", false);',
|
||||||
|
[
|
||||||
|
'$a' => 'array<int, array<int, array<array-key, mixed>|null|scalar|stdClass>|null|scalar|stdClass>|null|scalar|stdClass',
|
||||||
|
'$b' => 'array<array-key, array<array-key, array<array-key, mixed>|null|scalar>|null|scalar>|null|scalar',
|
||||||
|
'$c' => 'array<int, array<int, array<array-key, mixed>|null|scalar|stdClass>|null|scalar|stdClass>|null|scalar|stdClass',
|
||||||
|
]
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,7 +139,9 @@ class ConditionalReturnTypeTest extends TestCase
|
|||||||
$int = add(3, 5);
|
$int = add(3, 5);
|
||||||
$float1 = add(2.5, 3);
|
$float1 = add(2.5, 3);
|
||||||
$float2 = add(2.7, 3.1);
|
$float2 = add(2.7, 3.1);
|
||||||
$float3 = add(3, 3.5);',
|
$float3 = add(3, 3.5);
|
||||||
|
/** @psalm-suppress PossiblyNullArgument */
|
||||||
|
$int = add(rand(0, 1) ? null : 1, 1);',
|
||||||
[
|
[
|
||||||
'$int' => 'int',
|
'$int' => 'int',
|
||||||
'$float1' => 'float',
|
'$float1' => 'float',
|
||||||
@ -147,6 +149,25 @@ class ConditionalReturnTypeTest extends TestCase
|
|||||||
'$float3' => 'float',
|
'$float3' => 'float',
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
'possiblyNullArgumentStillMatchesType' => [
|
||||||
|
'<?php
|
||||||
|
/**
|
||||||
|
* @template T as int|float
|
||||||
|
* @param T $a
|
||||||
|
* @param T $b
|
||||||
|
* @return int|float
|
||||||
|
* @psalm-return (T is int ? int : float)
|
||||||
|
*/
|
||||||
|
function add($a, $b) {
|
||||||
|
return $a + $b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @psalm-suppress PossiblyNullArgument */
|
||||||
|
$int = add(rand(0, 1) ? null : 1, 4);',
|
||||||
|
[
|
||||||
|
'$int' => 'int',
|
||||||
|
]
|
||||||
|
],
|
||||||
'nestedClassConstantConditionalComparison' => [
|
'nestedClassConstantConditionalComparison' => [
|
||||||
'<?php
|
'<?php
|
||||||
class A {
|
class A {
|
||||||
|
@ -1114,6 +1114,25 @@ class FunctionTemplateTest extends TestCase
|
|||||||
'$a' => 'iterable<mixed, string>'
|
'$a' => 'iterable<mixed, string>'
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
'possiblyNullMatchesTemplateType' => [
|
||||||
|
'<?php
|
||||||
|
/**
|
||||||
|
* @template T as object
|
||||||
|
* @param T $o
|
||||||
|
* @return T
|
||||||
|
*/
|
||||||
|
function takesObject(object $o) : object {
|
||||||
|
return $o;
|
||||||
|
}
|
||||||
|
|
||||||
|
class A {}
|
||||||
|
|
||||||
|
/** @psalm-suppress PossiblyNullArgument */
|
||||||
|
$a = takesObject(rand(0, 1) ? new A() : null);',
|
||||||
|
[
|
||||||
|
'$a' => 'A',
|
||||||
|
]
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user