mirror of
https://github.com/danog/psalm.git
synced 2024-11-30 04:39:00 +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) {
|
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);
|
$arg_value_type = $statements_analyzer->node_data->getType($arg->value);
|
||||||
|
|
||||||
$param_type = TemplateStandinTypeReplacer::replace(
|
$param_type = TemplateStandinTypeReplacer::replace(
|
||||||
$param_type,
|
$param_type,
|
||||||
$empty_template_result,
|
$readonly_template_result,
|
||||||
$codebase,
|
$codebase,
|
||||||
$statements_analyzer,
|
$statements_analyzer,
|
||||||
$arg_value_type,
|
$arg_value_type,
|
||||||
@ -275,7 +286,7 @@ class ArgumentAnalyzer
|
|||||||
|
|
||||||
$arg_type = TemplateStandinTypeReplacer::replace(
|
$arg_type = TemplateStandinTypeReplacer::replace(
|
||||||
$arg_type,
|
$arg_type,
|
||||||
$empty_template_result,
|
$readonly_template_result,
|
||||||
$codebase,
|
$codebase,
|
||||||
$statements_analyzer,
|
$statements_analyzer,
|
||||||
$arg_value_type,
|
$arg_value_type,
|
||||||
|
@ -22,6 +22,13 @@ class TemplateResult
|
|||||||
*/
|
*/
|
||||||
public $lower_bounds = [];
|
public $lower_bounds = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set to true then we shouldn't update the template bounds
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
public $readonly = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var list<Union>
|
* @var list<Union>
|
||||||
*/
|
*/
|
||||||
|
@ -619,6 +619,7 @@ class TemplateStandinTypeReplacer
|
|||||||
);
|
);
|
||||||
|
|
||||||
if ($input_type
|
if ($input_type
|
||||||
|
&& !$template_result->readonly
|
||||||
&& (
|
&& (
|
||||||
$atomic_type->as->isMixed()
|
$atomic_type->as->isMixed()
|
||||||
|| !$codebase
|
|| !$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
|
if ($atomic_type instanceof Atomic\TNamedObject
|
||||||
|| $atomic_type instanceof Atomic\TTemplateParam
|
|| $atomic_type instanceof Atomic\TTemplateParam
|
||||||
|| $atomic_type instanceof Atomic\TIterable
|
|| $atomic_type instanceof Atomic\TIterable
|
||||||
|| $atomic_type instanceof Atomic\TObjectWithProperties
|
|| $atomic_type instanceof Atomic\TObjectWithProperties
|
||||||
) {
|
) {
|
||||||
$atomic_type->extra_types = $extra_types;
|
$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;
|
return $atomic_types;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($add_upper_bound && $input_type) {
|
if ($add_upper_bound && $input_type && !$template_result->readonly) {
|
||||||
$matching_input_keys = [];
|
$matching_input_keys = [];
|
||||||
|
|
||||||
if ($codebase
|
if ($codebase
|
||||||
@ -782,7 +786,7 @@ class TemplateStandinTypeReplacer
|
|||||||
|
|
||||||
$atomic_types[] = $class_string;
|
$atomic_types[] = $class_string;
|
||||||
|
|
||||||
if ($input_type) {
|
if ($input_type && !$template_result->readonly) {
|
||||||
$valid_input_atomic_types = [];
|
$valid_input_atomic_types = [];
|
||||||
|
|
||||||
foreach ($input_type->getAtomicTypes() as $input_atomic_type) {
|
foreach ($input_type->getAtomicTypes() as $input_atomic_type) {
|
||||||
|
@ -2394,21 +2394,18 @@ class ClassTemplateTest extends TestCase
|
|||||||
'intersectOnTOfObject' => [
|
'intersectOnTOfObject' => [
|
||||||
'<?php
|
'<?php
|
||||||
/**
|
/**
|
||||||
* @psalm-template InterceptedObjectType of object
|
* @psalm-template TO of object
|
||||||
*/
|
*/
|
||||||
interface AccessInterceptorInterface
|
interface A {
|
||||||
{
|
|
||||||
/**
|
/**
|
||||||
* @psalm-param Closure(
|
* @psalm-param Closure(TO&A):mixed $c
|
||||||
* InterceptedObjectType&AccessInterceptorInterface
|
|
||||||
* ) : mixed $prefixInterceptor
|
|
||||||
*/
|
*/
|
||||||
public function setMethodPrefixInterceptor(Closure $prefixInterceptor = null) : void;
|
public function setClosure(Closure $c): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function foo(AccessInterceptorInterface $i) : void {
|
function foo(A $i) : void {
|
||||||
$i->setMethodPrefixInterceptor(
|
$i->setClosure(
|
||||||
function(AccessInterceptorInterface $i) : string {
|
function(A $i) : string {
|
||||||
return "hello";
|
return "hello";
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -3801,6 +3798,31 @@ class ClassTemplateTest extends TestCase
|
|||||||
false,
|
false,
|
||||||
'8.0'
|
'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