1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-26 12:24:49 +01:00

Fix #4782 - don’t replace closure types with upper bounds when replacing class param types

This commit is contained in:
Matt Brown 2020-12-05 11:58:55 -05:00 committed by Daniil Gentili
parent 4f9449b742
commit 8bfb0412e7
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
4 changed files with 61 additions and 17 deletions

View File

@ -257,15 +257,26 @@ class ArgumentAnalyzer
);
if ($class_generic_params) {
$empty_generic_params = [];
// here we're replacing the param types and arg types with the bound
// class template params.
//
// For example, if we're operating on a class Foo with params TKey and TValue,
// and we're calling a method "add(TKey $key, TValue $value)" on an instance
// of that class where we know that TKey is int and TValue is string, then we
// want to replace the substitute the expected values so it's as if we were actually
// calling "add(int $key, string $value)"
$readonly_template_result = new TemplateResult($class_generic_params, []);
$empty_template_result = new TemplateResult($class_generic_params, $empty_generic_params);
// This flag ensures that the template results will never be written to
// It also supercedes the `$add_upper_bounds` flag so that closure params
// dont get overwritten
$readonly_template_result->readonly = true;
$arg_value_type = $statements_analyzer->node_data->getType($arg->value);
$param_type = TemplateStandinTypeReplacer::replace(
$param_type,
$empty_template_result,
$readonly_template_result,
$codebase,
$statements_analyzer,
$arg_value_type,
@ -275,7 +286,7 @@ class ArgumentAnalyzer
$arg_type = TemplateStandinTypeReplacer::replace(
$arg_type,
$empty_template_result,
$readonly_template_result,
$codebase,
$statements_analyzer,
$arg_value_type,

View File

@ -22,6 +22,13 @@ class TemplateResult
*/
public $lower_bounds = [];
/**
* If set to true then we shouldn't update the template bounds
*
* @var bool
*/
public $readonly = false;
/**
* @var list<Union>
*/

View File

@ -619,6 +619,7 @@ class TemplateStandinTypeReplacer
);
if ($input_type
&& !$template_result->readonly
&& (
$atomic_type->as->isMixed()
|| !$codebase
@ -686,20 +687,23 @@ class TemplateStandinTypeReplacer
);
}
foreach ($atomic_types as $atomic_type) {
foreach ($atomic_types as &$atomic_type) {
if ($atomic_type instanceof Atomic\TNamedObject
|| $atomic_type instanceof Atomic\TTemplateParam
|| $atomic_type instanceof Atomic\TIterable
|| $atomic_type instanceof Atomic\TObjectWithProperties
) {
$atomic_type->extra_types = $extra_types;
} elseif ($atomic_type instanceof Atomic\TObject && $extra_types) {
$atomic_type = \reset($extra_types);
$atomic_type->extra_types = \array_slice($extra_types, 1);
}
}
return $atomic_types;
}
if ($add_upper_bound && $input_type) {
if ($add_upper_bound && $input_type && !$template_result->readonly) {
$matching_input_keys = [];
if ($codebase
@ -782,7 +786,7 @@ class TemplateStandinTypeReplacer
$atomic_types[] = $class_string;
if ($input_type) {
if ($input_type && !$template_result->readonly) {
$valid_input_atomic_types = [];
foreach ($input_type->getAtomicTypes() as $input_atomic_type) {

View File

@ -2394,21 +2394,18 @@ class ClassTemplateTest extends TestCase
'intersectOnTOfObject' => [
'<?php
/**
* @psalm-template InterceptedObjectType of object
* @psalm-template TO of object
*/
interface AccessInterceptorInterface
{
interface A {
/**
* @psalm-param Closure(
* InterceptedObjectType&AccessInterceptorInterface
* ) : mixed $prefixInterceptor
* @psalm-param Closure(TO&A):mixed $c
*/
public function setMethodPrefixInterceptor(Closure $prefixInterceptor = null) : void;
public function setClosure(Closure $c): void;
}
function foo(AccessInterceptorInterface $i) : void {
$i->setMethodPrefixInterceptor(
function(AccessInterceptorInterface $i) : string {
function foo(A $i) : void {
$i->setClosure(
function(A $i) : string {
return "hello";
}
);
@ -3801,6 +3798,31 @@ class ClassTemplateTest extends TestCase
false,
'8.0'
],
'bindClosureParamAccurately' => [
'<?php
/**
* @template TKey
* @template TValue
*/
interface Collection {
/**
* @template T
* @param Closure(TValue):T $func
* @return Collection<TKey,T>
*/
public function map(Closure $func);
}
/**
* @param Collection<int, string> $c
*/
function f(Collection $c): void {
$fn = function(int $_p): bool { return true; };
$c->map($fn);
}',
'error_message' => 'InvalidScalarArgument',
],
];
}
}