Skip to content

Commit

Permalink
Resolve nested upper bounds correctly
Browse files Browse the repository at this point in the history
  • Loading branch information
klimick committed May 8, 2024
1 parent 40156a4 commit b5a0455
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 0 deletions.
25 changes: 25 additions & 0 deletions src/Psalm/Internal/Type/TemplateStandinTypeReplacer.php
Original file line number Diff line number Diff line change
Expand Up @@ -895,6 +895,31 @@ private static function handleTemplateParamStandin(
}

if ($add_lower_bound && $input_type && !$template_result->readonly) {
$replacement_type = TypeExpander::expandUnion(
$codebase,
$replacement_type,
$calling_class,
$calling_class,
null,
);

if ($depth < 10) {
$replacement_type = self::replace(
$replacement_type,
$template_result,
$codebase,
$statements_analyzer,
$input_type,
$input_arg_offset,
$calling_class,
$calling_function,
true,
true,
$bound_equality_classlike,
$depth + 1,
);
}

$matching_input_keys = [];

if (UnionTypeComparator::canBeContainedBy(
Expand Down
81 changes: 81 additions & 0 deletions tests/ClosureTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,43 @@ class ClosureTest extends TestCase
public function providerValidCodeParse(): iterable
{
return [
'resolveNestedUpperBoundsCorrectly' => [
'code' => '<?php
/** @template T */
final readonly class Value
{
/** @param T $value */
public function __construct(public mixed $value) {}
}
/**
* @template A
* @template B of Value<A>
*
* @param callable(B): B $function
* @return callable(B): B
*/
function doSomething(callable $function): callable
{
return $function;
}
/**
* @param Value<int> $v
* @return Value<int>
*/
function addOne(Value $v): Value
{
return new Value($v->value + 1);
}
$fn = doSomething(addOne(...));',
'assertions' => [
'$fn' => 'callable(Value<int>):Value<int>',
],
'ignored_issues' => [],
'php_version' => '8.2',
],
'byRefUseVar' => [
'code' => '<?php
$doNotContaminate = 123;
Expand Down Expand Up @@ -1027,6 +1064,50 @@ function &(): int {
public function providerInvalidCodeParse(): iterable
{
return [
'resolveNestedUpperBoundsCorrectly' => [
'code' => '<?php
/** @template T */
final readonly class Value
{
/** @param T $value */
public function __construct(public mixed $value) {}
}
/**
* @template A
* @template B of Value<A>
*
* @param callable(B): B $function
* @param B $_value
* @return callable(B): B
*/
function doSomething(callable $function, mixed $_value): callable
{
return $function;
}
/**
* @param Value<int> $v
* @return Value<int>
*/
function addOne(Value $v): Value
{
return new Value($v->value + 1);
}
/**
* @return Value<string>
*/
function getString(): Value
{
return new Value("...");
}
$_ = doSomething(addOne(...), getString());',
'error_message' => 'InvalidArgument',
'ignored_issues' => [],
'php_version' => '8.2',
],
'wrongArg' => [
'code' => '<?php
$bar = ["foo", "bar"];
Expand Down

0 comments on commit b5a0455

Please sign in to comment.