mirror of
https://github.com/danog/psalm.git
synced 2024-11-26 20:34:47 +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 $container_type,
|
||||
$ignore_null = false,
|
||||
$ignore_false = false
|
||||
$ignore_false = false,
|
||||
array &$matching_input_keys = []
|
||||
) {
|
||||
if ($container_type->hasMixed()) {
|
||||
return true;
|
||||
@ -425,12 +426,12 @@ class TypeAnalyzer
|
||||
if (($is_atomic_contained_by && !$atomic_comparison_result->to_string_cast)
|
||||
|| $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])) {
|
||||
$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] = [
|
||||
'fn-' . strtolower($cased_function_id) => [
|
||||
$param_storage->type ? clone $param_storage->type : Type::getMixed()
|
||||
$template_function_id => [
|
||||
$template_as_type
|
||||
],
|
||||
];
|
||||
|
||||
@ -2485,6 +2491,14 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse
|
||||
= $storage->template_types[$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
|
||||
|
@ -478,19 +478,34 @@ class UnionTemplateHandler
|
||||
}
|
||||
}
|
||||
|
||||
$matching_input_keys = [];
|
||||
|
||||
if ($input_type
|
||||
&& (
|
||||
$atomic_type->as->isMixed()
|
||||
|| !$codebase
|
||||
|| TypeAnalyzer::isContainedBy(
|
||||
|| TypeAnalyzer::canBeContainedBy(
|
||||
$codebase,
|
||||
$input_type,
|
||||
$atomic_type->as
|
||||
$atomic_type->as,
|
||||
false,
|
||||
false,
|
||||
$matching_input_keys
|
||||
)
|
||||
)
|
||||
) {
|
||||
$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()) {
|
||||
$generic_param->removeType('null');
|
||||
}
|
||||
|
@ -1256,6 +1256,17 @@ class FunctionCallTest extends TestCase
|
||||
'$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);
|
||||
$float1 = add(2.5, 3);
|
||||
$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',
|
||||
'$float1' => 'float',
|
||||
@ -147,6 +149,25 @@ class ConditionalReturnTypeTest extends TestCase
|
||||
'$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' => [
|
||||
'<?php
|
||||
class A {
|
||||
|
@ -1114,6 +1114,25 @@ class FunctionTemplateTest extends TestCase
|
||||
'$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