mirror of
https://github.com/danog/psalm.git
synced 2024-11-26 20:34:47 +01:00
bugfix: do not extend the type - only narrow down
Signed-off-by: Maximilian Bösing <2189546+boesing@users.noreply.github.com>
This commit is contained in:
parent
ed1bb8a9a6
commit
7e033d8051
@ -796,6 +796,7 @@ class CallAnalyzer
|
||||
} elseif (isset($context->vars_in_scope[$assertion_var_id])) {
|
||||
$other_type = $context->vars_in_scope[$assertion_var_id];
|
||||
if (self::isNewTypeNarrowingDownOldType($other_type, $union)) {
|
||||
$union = self::createUnionIntersectionFromOldType($union, $other_type);
|
||||
foreach ($union->getAtomicTypes() as $atomic_type) {
|
||||
if ($assertion_type instanceof TTemplateParam
|
||||
&& $assertion_type->as->getId() === $atomic_type->getId()
|
||||
@ -1154,25 +1155,49 @@ class CallAnalyzer
|
||||
return false;
|
||||
}
|
||||
|
||||
$old_atomic_type = $old_type->getSingleAtomic();
|
||||
foreach ($new_type->getAtomicTypes() as $new_atomic_type) {
|
||||
if ($new_atomic_type->equals($old_atomic_type, false)) {
|
||||
// Old type is one of the new types and thus, the old type must not be modified
|
||||
return false;
|
||||
}
|
||||
// Do not hassle around with single literals as they supposed to be more accurate than any new type assertion
|
||||
if ($old_type->isSingleFloatLiteral()
|
||||
|| $old_type->isSingleIntLiteral()
|
||||
|| $old_type->isSingleStringLiteral()
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Literals should always replace non-literals
|
||||
if (!$old_type->containsAnyLiteral() &&
|
||||
(
|
||||
($old_type->isString() && $new_type->allStringLiterals())
|
||||
|| ($old_type->isInt() && $new_type->allIntLiterals())
|
||||
|| ($old_type->isFloat() && $new_type->allFloatLiterals())
|
||||
)
|
||||
if (($old_type->isString() && $new_type->allStringLiterals())
|
||||
|| ($old_type->isInt() && $new_type->allIntLiterals())
|
||||
|| ($old_type->isFloat() && $new_type->allFloatLiterals())
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should kick all literals within `new_type` which are not part of the already known `old_type`.
|
||||
* So lets say we already know that the old type is one of "a", "b" or "c".
|
||||
* If another assertion takes place to determine if the value is either "a", "c" or "d", we can kick "d" as that
|
||||
* won't be possible.
|
||||
*/
|
||||
private static function createUnionIntersectionFromOldType(Union $new_type, Union $old_type): Union
|
||||
{
|
||||
if (!$new_type->allLiterals() || !$old_type->allLiterals()) {
|
||||
return $new_type;
|
||||
}
|
||||
|
||||
$equal_atomic_types = [];
|
||||
|
||||
foreach ($new_type->getAtomicTypes() as $new_atomic_type) {
|
||||
foreach ($old_type->getAtomicTypes() as $old_atomic_type) {
|
||||
if (!$new_atomic_type->equals($old_atomic_type, false)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$equal_atomic_types[] = $new_atomic_type;
|
||||
}
|
||||
}
|
||||
|
||||
return new Union($equal_atomic_types);
|
||||
}
|
||||
}
|
||||
|
@ -90,7 +90,6 @@ class AssertAnnotationTest extends TestCase
|
||||
$this->analyzeFile('somefile.php', new Context());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return iterable<string,array{code:string,assertions?:array<string,string>,ignored_issues?:list<string>}>
|
||||
*/
|
||||
@ -2082,6 +2081,9 @@ class AssertAnnotationTest extends TestCase
|
||||
/** @var string $anotherString */
|
||||
$anotherString;
|
||||
|
||||
/** @var null|string $nullableString */
|
||||
$nullableString;
|
||||
|
||||
/** @var mixed $maybeInt */
|
||||
$maybeInt;
|
||||
/** @var mixed $maybeFloat */
|
||||
@ -2093,11 +2095,22 @@ class AssertAnnotationTest extends TestCase
|
||||
assertOneOf($anotherString, ["a", "b", "c"]);
|
||||
consumeLiteralStringValue($anotherString);
|
||||
|
||||
assertOneOf($nullableString, ["a", "b", "c"]);
|
||||
assertOneOf($nullableString, ["a", "c"]);
|
||||
|
||||
assertOneOf($maybeInt, [1, 2, 3]);
|
||||
consumeAnyIntegerValue($maybeInt);
|
||||
|
||||
assertOneOf($maybeFloat, [1.5, 2.5, 3.5]);
|
||||
consumeAnyFloatValue($maybeFloat);
|
||||
|
||||
/** @var "a"|"b"|"c" $abc */
|
||||
$abc;
|
||||
|
||||
/** @param "a"|"b" $aOrB */
|
||||
function consumeAOrB(string $aOrB): void {}
|
||||
assertOneOf($abc, ["a", "b"]);
|
||||
consumeAOrB($abc);
|
||||
'
|
||||
],
|
||||
];
|
||||
|
Loading…
Reference in New Issue
Block a user