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

Make the string concatenation of two known values into a known value (#717)

* Make the string concatenation of two known values into a known value

address review comments

* Add vim temporary files to .gitignore
This commit is contained in:
Tyson Andre 2018-05-08 16:11:18 -07:00 committed by Matthew Brown
parent 6e259bed03
commit 9a9f6d1856
4 changed files with 65 additions and 0 deletions

2
.gitignore vendored
View File

@ -4,4 +4,6 @@ phpunit.xml
composer.lock
.php_cs.cache
.php_cs
.*.swp
.*.swo
/build/logs/

View File

@ -29,11 +29,13 @@ use Psalm\Type\Atomic\TFalse;
use Psalm\Type\Atomic\TFloat;
use Psalm\Type\Atomic\TGenericParam;
use Psalm\Type\Atomic\TInt;
use Psalm\Type\Atomic\TLiteralString;
use Psalm\Type\Atomic\TMixed;
use Psalm\Type\Atomic\TNamedObject;
use Psalm\Type\Atomic\TNull;
use Psalm\Type\Atomic\TNumeric;
use Psalm\Type\Reconciler;
use Psalm\Type\Union;
class BinaryOpChecker
{
@ -1364,5 +1366,14 @@ class BinaryOpChecker
}
}
}
// When concatenating two known string literals (with only one possibility),
// put the concatenated string into $result_type
if ($left_type && $right_type && $left_type->isSingleStringLiteral() && $right_type->isSingleStringLiteral()) {
$literal = $left_type->getSingleStringLiteral() . $right_type->getSingleStringLiteral();
if (strlen($literal) <= 10000) {
// Limit these to 10000 bytes to avoid extremely large union types from repeated concatenations, etc
$result_type = new Union([new TLiteralString([$literal => true])]);
}
}
}
}

View File

@ -6,6 +6,7 @@ use Psalm\CodeLocation;
use Psalm\StatementsSource;
use Psalm\Storage\FileStorage;
use Psalm\Type;
use Psalm\Type\Atomic\TLiteralString;
class Union
{
@ -678,6 +679,42 @@ class Union
return $type->type_params[count($type->type_params) - 1]->isSingle();
}
/**
* @return bool true if this is a string literal with only one possible value
* TODO: Is there a better place for this?
*/
public function isSingleStringLiteral()
{
if (count($this->types) !== 1) {
return false;
}
$string_type = $this->types['string'] ?? null;
if (!($string_type instanceof TLiteralString)) {
return false;
}
return count($string_type->getValues()) === 1;
}
/**
* @return string the only string literal represented by this union type
* @throws \InvalidArgumentException if isSingleStringLiteral is false
*/
public function getSingleStringLiteral()
{
if (count($this->types) !== 1) {
throw new \InvalidArgumentException("Not a string literal");
}
$string_type = $this->types['string'] ?? null;
if (!($string_type instanceof TLiteralString)) {
throw new \InvalidArgumentException("Not a string literal");
}
$values = $string_type->getValues();
if (count($values) !== 1) {
throw new \InvalidArgumentException("Not a string literal");
}
return (string)key($values);
}
/**
* @param StatementsSource $source
* @param CodeLocation $code_location

View File

@ -831,6 +831,13 @@ class TypeAlgebraTest extends TestCase
echo $a === false ? "a" : "b";
}',
],
'stringConcatenationTrackedValid' => [
'<?php
$x = "a";
$x = "_" . $x;
$array = [$x => 2];
echo $array["_a"];',
],
];
}
@ -1012,6 +1019,14 @@ class TypeAlgebraTest extends TestCase
if ($a !== $b) {}',
'error_message' => 'RedundantCondition',
],
'stringConcatenationTrackedInvalid' => [
'<?php
$x = "a";
$x = "_" . $x;
$array = [$x => 2];
echo $array["other"];',
'error_message' => 'InvalidArrayOffset',
],
];
}
}