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:
parent
4f9449b742
commit
8bfb0412e7
@ -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
|
||||
// don’t 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,
|
||||
|
@ -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>
|
||||
*/
|
||||
|
@ -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) {
|
||||
|
@ -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',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user