1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-27 12:55:26 +01:00

Infer literal string from encapsed (interpolated) string.

This commit is contained in:
AndrolGenhald 2022-06-24 17:24:34 -05:00
parent 9b4c8cb53f
commit 450409f045
2 changed files with 48 additions and 2 deletions

View File

@ -3,6 +3,7 @@
namespace Psalm\Internal\Analyzer\Statements\Expression; namespace Psalm\Internal\Analyzer\Statements\Expression;
use PhpParser; use PhpParser;
use PhpParser\Node\Scalar\EncapsedStringPart;
use Psalm\CodeLocation; use Psalm\CodeLocation;
use Psalm\Context; use Psalm\Context;
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer; use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
@ -29,6 +30,8 @@ class EncapsulatedStringAnalyzer
$all_literals = true; $all_literals = true;
$literal_string = "";
foreach ($stmt->parts as $part) { foreach ($stmt->parts as $part) {
if ($part instanceof PhpParser\Node\Scalar\EncapsedStringPart if ($part instanceof PhpParser\Node\Scalar\EncapsedStringPart
&& $part->value && $part->value
@ -42,7 +45,7 @@ class EncapsulatedStringAnalyzer
$part_type = $statements_analyzer->node_data->getType($part); $part_type = $statements_analyzer->node_data->getType($part);
if ($part_type) { if ($part_type !== null) {
$casted_part_type = CastAnalyzer::castStringAttempt( $casted_part_type = CastAnalyzer::castStringAttempt(
$statements_analyzer, $statements_analyzer,
$context, $context,
@ -54,6 +57,14 @@ class EncapsulatedStringAnalyzer
$all_literals = false; $all_literals = false;
} }
if ($literal_string !== null) {
if ($casted_part_type->isSingleLiteral()) {
$literal_string .= $casted_part_type->getSingleLiteral();
} else {
$literal_string = null;
}
}
if ($statements_analyzer->data_flow_graph if ($statements_analyzer->data_flow_graph
&& !in_array('TaintedInput', $statements_analyzer->getSuppressedIssues()) && !in_array('TaintedInput', $statements_analyzer->getSuppressedIssues())
) { ) {
@ -82,11 +93,20 @@ class EncapsulatedStringAnalyzer
} }
} }
} }
} elseif ($part instanceof EncapsedStringPart) {
if ($literal_string !== null) {
$literal_string .= $part->value;
}
} else {
$all_literals = false;
$literal_string = null;
} }
} }
if ($non_empty) { if ($non_empty) {
if ($all_literals) { if ($literal_string !== null) {
$new_type = Type::getString($literal_string);
} elseif ($all_literals) {
$new_type = new Union([new TNonEmptyNonspecificLiteralString()]); $new_type = new Union([new TNonEmptyNonspecificLiteralString()]);
} else { } else {
$new_type = new Union([new TNonEmptyString()]); $new_type = new Union([new TNonEmptyString()]);

View File

@ -1329,6 +1329,32 @@ class Union implements TypeNode
|| isset($this->types['true']); || isset($this->types['true']);
} }
public function isSingleLiteral(): bool
{
return count($this->types) === 1
&& count($this->literal_int_types)
+ count($this->literal_string_types)
+ count($this->literal_float_types) === 1
;
}
/**
* @return TLiteralInt|TLiteralString|TLiteralFloat
*/
public function getSingleLiteral()
{
if (!$this->isSingleLiteral()) {
throw new InvalidArgumentException("Not a single literal");
}
return ($literal = reset($this->literal_int_types)) !== false
? $literal
: (($literal = reset($this->literal_string_types)) !== false
? $literal
: reset($this->literal_float_types))
;
}
public function hasLiteralString(): bool public function hasLiteralString(): bool
{ {
return count($this->literal_string_types) > 0; return count($this->literal_string_types) > 0;