mirror of
https://github.com/danog/psalm.git
synced 2025-01-21 21:31:13 +01:00
Make the length of possible string unions unlimited in conditionals
Fixes #1103
This commit is contained in:
parent
04b3fd2bb5
commit
c378079db8
@ -306,9 +306,14 @@ class SwitchAnalyzer
|
||||
}
|
||||
}
|
||||
|
||||
$case_stmts = $case->stmts;
|
||||
$continue_case_equality_expr = false;
|
||||
|
||||
$case_stmts = array_merge($switch_scope->leftover_statements, $case_stmts);
|
||||
if ($case->stmts) {
|
||||
$case_stmts = array_merge($switch_scope->leftover_statements, $case->stmts);
|
||||
} else {
|
||||
$continue_case_equality_expr = count($switch_scope->leftover_statements) === 1;
|
||||
$case_stmts = $switch_scope->leftover_statements;
|
||||
}
|
||||
|
||||
if (!$has_leaving_statements && !$is_last) {
|
||||
if (!$case_equality_expr) {
|
||||
@ -330,12 +335,19 @@ class SwitchAnalyzer
|
||||
)
|
||||
: $case_equality_expr;
|
||||
|
||||
$case_if_stmt = new PhpParser\Node\Stmt\If_(
|
||||
$switch_scope->leftover_case_equality_expr,
|
||||
['stmts' => $case_stmts]
|
||||
);
|
||||
if ($continue_case_equality_expr
|
||||
&& $switch_scope->leftover_statements[0] instanceof PhpParser\Node\Stmt\If_
|
||||
) {
|
||||
$case_if_stmt = $switch_scope->leftover_statements[0];
|
||||
$case_if_stmt->cond = $switch_scope->leftover_case_equality_expr;
|
||||
} else {
|
||||
$case_if_stmt = new PhpParser\Node\Stmt\If_(
|
||||
$switch_scope->leftover_case_equality_expr,
|
||||
['stmts' => $case_stmts]
|
||||
);
|
||||
|
||||
$switch_scope->leftover_statements = [$case_if_stmt];
|
||||
$switch_scope->leftover_statements = [$case_if_stmt];
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ class ArrayAnalyzer
|
||||
}
|
||||
|
||||
if ($item_key_type) {
|
||||
$item_key_type = Type::combineUnionTypes($key_type, $item_key_type);
|
||||
$item_key_type = Type::combineUnionTypes($key_type, $item_key_type, false, 30);
|
||||
} else {
|
||||
$item_key_type = $key_type;
|
||||
}
|
||||
@ -126,7 +126,12 @@ class ArrayAnalyzer
|
||||
}
|
||||
|
||||
if ($item_value_type) {
|
||||
$item_value_type = Type::combineUnionTypes($item->value->inferredType, clone $item_value_type);
|
||||
$item_value_type = Type::combineUnionTypes(
|
||||
$item->value->inferredType,
|
||||
clone $item_value_type,
|
||||
false,
|
||||
30
|
||||
);
|
||||
} else {
|
||||
$item_value_type = $item->value->inferredType;
|
||||
}
|
||||
|
@ -1056,7 +1056,12 @@ class StatementsAnalyzer extends SourceAnalyzer implements StatementsSource
|
||||
|
||||
if ($single_item_key_type) {
|
||||
if ($item_key_type) {
|
||||
$item_key_type = Type::combineUnionTypes($single_item_key_type, $item_key_type);
|
||||
$item_key_type = Type::combineUnionTypes(
|
||||
$single_item_key_type,
|
||||
$item_key_type,
|
||||
false,
|
||||
30
|
||||
);
|
||||
} else {
|
||||
$item_key_type = $single_item_key_type;
|
||||
}
|
||||
@ -1120,7 +1125,12 @@ class StatementsAnalyzer extends SourceAnalyzer implements StatementsSource
|
||||
}
|
||||
|
||||
if ($item_value_type) {
|
||||
$item_value_type = Type::combineUnionTypes($single_item_value_type, $item_value_type);
|
||||
$item_value_type = Type::combineUnionTypes(
|
||||
$single_item_value_type,
|
||||
$item_value_type,
|
||||
false,
|
||||
30
|
||||
);
|
||||
} else {
|
||||
$item_value_type = $single_item_value_type;
|
||||
}
|
||||
|
@ -73,12 +73,17 @@ class TypeCombination
|
||||
* - and `array + array<string> = array<mixed>`
|
||||
*
|
||||
* @param array<Atomic> $types
|
||||
* @param int $literal_limit any greater number of literal types than this
|
||||
* will be merged to a scalar
|
||||
*
|
||||
* @return Union
|
||||
* @psalm-suppress TypeCoercion
|
||||
*/
|
||||
public static function combineTypes(array $types, bool $overwrite_empty_array = false)
|
||||
{
|
||||
public static function combineTypes(
|
||||
array $types,
|
||||
bool $overwrite_empty_array = false,
|
||||
int $literal_limit = 500
|
||||
) {
|
||||
if (in_array(null, $types, true)) {
|
||||
return Type::getMixed();
|
||||
}
|
||||
@ -108,7 +113,7 @@ class TypeCombination
|
||||
foreach ($types as $type) {
|
||||
$from_docblock = $from_docblock || $type->from_docblock;
|
||||
|
||||
$result = self::scrapeTypeProperties($type, $combination, $overwrite_empty_array);
|
||||
$result = self::scrapeTypeProperties($type, $combination, $overwrite_empty_array, $literal_limit);
|
||||
|
||||
if ($type instanceof TNull) {
|
||||
$has_null = true;
|
||||
@ -308,7 +313,8 @@ class TypeCombination
|
||||
private static function scrapeTypeProperties(
|
||||
Atomic $type,
|
||||
TypeCombination $combination,
|
||||
bool $overwrite_empty_array
|
||||
bool $overwrite_empty_array,
|
||||
int $literal_limit
|
||||
) {
|
||||
if ($type instanceof TMixed) {
|
||||
if ($type->from_isset || $type instanceof TEmptyMixed) {
|
||||
@ -395,7 +401,7 @@ class TypeCombination
|
||||
} else {
|
||||
if ($type instanceof TString) {
|
||||
if ($type instanceof TLiteralString) {
|
||||
if ($combination->strings !== null && count($combination->strings) < 30) {
|
||||
if ($combination->strings !== null && count($combination->strings) < $literal_limit) {
|
||||
$combination->strings[] = $type;
|
||||
} else {
|
||||
$combination->strings = null;
|
||||
@ -426,7 +432,7 @@ class TypeCombination
|
||||
}
|
||||
} elseif ($type instanceof TInt) {
|
||||
if ($type instanceof TLiteralInt) {
|
||||
if ($combination->ints !== null && count($combination->ints) < 30) {
|
||||
if ($combination->ints !== null && count($combination->ints) < $literal_limit) {
|
||||
$combination->ints[] = $type;
|
||||
} else {
|
||||
$combination->ints = null;
|
||||
@ -438,7 +444,7 @@ class TypeCombination
|
||||
}
|
||||
} elseif ($type instanceof TFloat) {
|
||||
if ($type instanceof TLiteralFloat) {
|
||||
if ($combination->floats !== null && count($combination->floats) < 30) {
|
||||
if ($combination->floats !== null && count($combination->floats) < $literal_limit) {
|
||||
$combination->floats[] = $type;
|
||||
} else {
|
||||
$combination->floats = null;
|
||||
|
@ -944,13 +944,16 @@ abstract class Type
|
||||
*
|
||||
* @param Union $type_1
|
||||
* @param Union $type_2
|
||||
* @param int $literal_limit any greater number of literal types than this
|
||||
* will be merged to a scalar
|
||||
*
|
||||
* @return Union
|
||||
*/
|
||||
public static function combineUnionTypes(
|
||||
Union $type_1,
|
||||
Union $type_2,
|
||||
bool $overwrite_empty_array = false
|
||||
bool $overwrite_empty_array = false,
|
||||
int $literal_limit = 500
|
||||
) {
|
||||
if ($type_1->isVanillaMixed() || $type_2->isVanillaMixed()) {
|
||||
$combined_type = Type::getMixed();
|
||||
@ -972,7 +975,8 @@ abstract class Type
|
||||
array_values($type_1->getTypes()),
|
||||
array_values($type_2->getTypes())
|
||||
),
|
||||
$overwrite_empty_array
|
||||
$overwrite_empty_array,
|
||||
$literal_limit
|
||||
);
|
||||
|
||||
if (!$type_1->initialized || !$type_2->initialized) {
|
||||
|
@ -517,6 +517,45 @@ class SwitchTypeTest extends TestCase
|
||||
'$a' => 'bool',
|
||||
],
|
||||
],
|
||||
'moreThan30Cases' => [
|
||||
'<?php
|
||||
function f(string $a) : void {
|
||||
switch ($a) {
|
||||
case "a":
|
||||
case "b":
|
||||
case "c":
|
||||
case "d":
|
||||
case "e":
|
||||
case "f":
|
||||
case "g":
|
||||
case "h":
|
||||
case "i":
|
||||
case "j":
|
||||
case "k":
|
||||
case "l":
|
||||
case "m":
|
||||
case "n":
|
||||
case "o":
|
||||
case "p":
|
||||
case "q":
|
||||
case "r":
|
||||
case "s":
|
||||
case "t":
|
||||
case "u":
|
||||
case "v":
|
||||
case "w":
|
||||
case "x":
|
||||
case "y":
|
||||
case "z":
|
||||
case "A":
|
||||
case "B":
|
||||
case "C":
|
||||
case "D":
|
||||
case "E":
|
||||
return;
|
||||
}
|
||||
}',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user