Skip to content

Commit

Permalink
Encapsed string shouldn't lose unions of literals, like concat alread…
Browse files Browse the repository at this point in the history
…y does

#10945
#10946

500 limit taken from TypeCombiner
  • Loading branch information
kkmuffme committed May 5, 2024
1 parent 979cfc8 commit 5ec5de1
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,19 @@
use Psalm\Type\Atomic\TString;
use Psalm\Type\Union;

use function array_map;
use function array_merge;
use function array_unique;
use function count;
use function in_array;

/**
* @internal
*/
final class EncapsulatedStringAnalyzer
{
private const MAX_LITERALS = 500;

public static function analyze(
StatementsAnalyzer $statements_analyzer,
PhpParser\Node\Scalar\Encapsed $stmt,
Expand All @@ -43,16 +49,16 @@ public static function analyze(

$all_literals = true;

$literal_string = "";
$literal_strings = [];

foreach ($stmt->parts as $part) {
if (ExpressionAnalyzer::analyze($statements_analyzer, $part, $context) === false) {
return false;
}

if ($part instanceof EncapsedStringPart) {
if ($literal_string !== null) {
$literal_string .= $part->value;
if ($literal_strings !== null) {
$literal_strings = self::combineLiteral($literal_strings, $part->value);
}
$non_falsy = $non_falsy || $part->value;
$non_empty = $non_empty || $part->value !== "";
Expand Down Expand Up @@ -118,11 +124,22 @@ public static function analyze(
}
}

if ($literal_string !== null) {
if ($casted_part_type->isSingleLiteral()) {
$literal_string .= $casted_part_type->getSingleLiteral()->value;
if ($literal_strings !== null) {
if ($casted_part_type->allSpecificLiterals()) {
$new_literal_strings = [];
foreach ($casted_part_type->getLiteralStrings() as $literal_string_atomic) {
$new_literal_strings = array_merge(
$new_literal_strings,
self::combineLiteral($literal_strings, $literal_string_atomic->value),
);
}

$literal_strings = array_unique($new_literal_strings);
if (count($literal_strings) > self::MAX_LITERALS) {
$literal_strings = null;
}
} else {
$literal_string = null;
$literal_strings = null;
}
}

Expand Down Expand Up @@ -156,14 +173,14 @@ public static function analyze(
}
} else {
$all_literals = false;
$literal_string = null;
$literal_strings = null;
}
}

if ($non_empty || $non_falsy) {
if ($literal_string !== null) {
if ($literal_strings !== null && $literal_strings !== []) {
$stmt_type = new Union(
[Type::getAtomicStringFromLiteral($literal_string)],
array_map([Type::class, 'getAtomicStringFromLiteral'], $literal_strings),
['parent_nodes' => $parent_nodes],
);
} elseif ($all_literals) {
Expand Down Expand Up @@ -198,4 +215,28 @@ public static function analyze(

return true;
}

/**
* @param string[] $literal_strings
* @return non-empty-array<string>
*/
private static function combineLiteral(
array $literal_strings,
string $append
): array {
if ($literal_strings === []) {
return [$append];
}

if ($append === '') {
return $literal_strings;
}

$new_literal_strings = array();
foreach ($literal_strings as $literal_string) {
$new_literal_strings[] = "{$literal_string}{$append}";
}

return $new_literal_strings;
}
}
33 changes: 33 additions & 0 deletions tests/BinaryOperationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -977,6 +977,39 @@ function foo(int $s1): string {
return "Hello $s1 $s2";
}',
],
'encapsedWithUnionLiteralsKeepsLiteral' => [
'code' => '<?php
$foo = rand(0, 1) ? 0 : 2;
$encapsed = "{$foo}";',
'assertions' => ['$encapsed===' => "'0'|'2'"],
],
'encapsedWithUnionLiteralsKeepsLiteral2' => [
'code' => '<?php
/**
* @var "a"|"b" $a
* @var "hello"|"world"|"bye" $b
*/
$encapsed = "X{$a}Y{$b}Z";',
'assertions' => ['$encapsed===' => "'XaYbyeZ'|'XaYhelloZ'|'XaYworldZ'|'XbYbyeZ'|'XbYhelloZ'|'XbYworldZ'"],
],
'encapsedWithIntsKeepsLiteral' => [
'code' => '<?php
/**
* @var "a"|"b" $a
* @var 0|1|2 $b
*/
$encapsed = "{$a}{$b}";',
'assertions' => ['$encapsed===' => "'a0'|'a1'|'a2'|'b0'|'b1'|'b2'"],
],
'encapsedWithIntRangeKeepsLiteral' => [
'code' => '<?php
/**
* @var "a"|"b" $a
* @var int<0, 2> $b
*/
$encapsed = "{$a}{$b}";',
'assertions' => ['$encapsed===' => "'a0'|'a1'|'a2'|'b0'|'b1'|'b2'"],
],
'NumericStringIncrement' => [
'code' => '<?php
function scope(array $a): int|float {
Expand Down

0 comments on commit 5ec5de1

Please sign in to comment.