1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-22 05:41:20 +01:00

Merge pull request #6639 from orklah/refine-TKeyedArray-with-iterable

Refine TKeyedArray with iterable
This commit is contained in:
orklah 2021-10-11 16:09:00 +02:00 committed by GitHub
commit d08845900b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 65 additions and 0 deletions

View File

@ -278,6 +278,9 @@ class AssertionReconciler extends \Psalm\Type\Reconciler
}
/**
* This method is called when SimpleAssertionReconciler was not enough. It receives the existing type, the assertion
* and also a new type created from the assertion string.
*
* @param 0|1|2 $failed_reconciliation
* @param string[] $suppressed_issues
* @param array<string, array<string, Type\Union>> $template_type_map
@ -583,7 +586,13 @@ class AssertionReconciler extends \Psalm\Type\Reconciler
}
/**
* This method receives two types. The goal is to use datas in the new type to reduce the existing_type to a more
* precise version. For example: new is `array<int>` old is `list<mixed>` so the result is `list<int>`
*
* @param array<string, array<string, Type\Union>> $template_type_map
*
* @psalm-suppress ComplexMethod we'd probably want to extract specific handling blocks at the end and also allow
* early return once a specific case has been handled
*/
private static function filterTypeWithAnother(
Codebase $codebase,
@ -736,6 +745,7 @@ class AssertionReconciler extends \Psalm\Type\Reconciler
continue;
}
//we filter both types of standard iterables
if (($new_type_part instanceof Type\Atomic\TGenericObject
|| $new_type_part instanceof Type\Atomic\TArray
|| $new_type_part instanceof Type\Atomic\TIterable)
@ -791,6 +801,7 @@ class AssertionReconciler extends \Psalm\Type\Reconciler
}
}
//we filter the second part of a list with the second part of standard iterables
if (($new_type_part instanceof Type\Atomic\TArray
|| $new_type_part instanceof Type\Atomic\TIterable)
&& $existing_type_part instanceof Type\Atomic\TList
@ -838,6 +849,54 @@ class AssertionReconciler extends \Psalm\Type\Reconciler
}
}
//we filter each property of a Keyed Array with the second part of standard iterables
if (($new_type_part instanceof Type\Atomic\TArray
|| $new_type_part instanceof Type\Atomic\TIterable)
&& $existing_type_part instanceof Type\Atomic\TKeyedArray
) {
$has_any_param_match = false;
$new_param = $new_type_part->type_params[1];
foreach ($existing_type_part->properties as $property_key => $existing_param) {
$has_param_match = true;
$new_param = self::filterTypeWithAnother(
$codebase,
$existing_param,
$new_param,
$template_type_map,
$has_param_match,
$any_scalar_type_match_found
);
if ($template_type_map) {
TemplateInferredTypeReplacer::replace(
$new_param,
new TemplateResult([], $template_type_map),
$codebase
);
}
if ($has_param_match
&& $existing_type_part->properties[$property_key]->getId() !== $new_param->getId()
) {
$existing_type_part->properties[$property_key] = $new_param;
if (!$has_local_match) {
$has_any_param_match = true;
}
}
}
$existing_type->bustCache();
if ($has_any_param_match) {
$has_local_match = true;
$matching_atomic_types[] = $existing_type_part;
$atomic_comparison_results->type_coerced = true;
}
}
//These partial match wouldn't have been handled by AtomicTypeComparator
$new_range = null;
if ($new_type_part instanceof Atomic\TIntRange && $existing_type_part instanceof Atomic\TPositiveInt) {

View File

@ -45,6 +45,11 @@ use function min;
use function strpos;
use function substr;
/**
* This class receives a known type and an assertion (probably coming from AssertionFinder). The goal is to refine
* the known type using the assertion. For example: old type is `int` assertion is `>5` result is `int<6, max>`.
* Complex reconciliation takes part in AssertionReconciler if this class couldn't handle the reconciliation
*/
class SimpleAssertionReconciler extends \Psalm\Type\Reconciler
{
/**

View File

@ -141,6 +141,7 @@ class ReconcilerTest extends \Psalm\Tests\TestCase
'iterableAndNotObject' => ['array<int, string>', '!object', 'iterable<int, string>'],
'boolNotEmptyIsTrue' => ['true', '!empty', 'bool'],
'interfaceAssertionOnClassInterfaceUnion' => ['SomeInterface|SomeInterface&SomeClass', 'SomeInterface', 'SomeClass|SomeInterface'],
'filterKeyedArrayWithIterable' => ['array{some: string}', 'iterable<string>', 'array{some: mixed}'],
];
}