mirror of
https://github.com/danog/psalm.git
synced 2024-11-30 04:39:00 +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:
parent
6e259bed03
commit
9a9f6d1856
2
.gitignore
vendored
2
.gitignore
vendored
@ -4,4 +4,6 @@ phpunit.xml
|
|||||||
composer.lock
|
composer.lock
|
||||||
.php_cs.cache
|
.php_cs.cache
|
||||||
.php_cs
|
.php_cs
|
||||||
|
.*.swp
|
||||||
|
.*.swo
|
||||||
/build/logs/
|
/build/logs/
|
||||||
|
@ -29,11 +29,13 @@ use Psalm\Type\Atomic\TFalse;
|
|||||||
use Psalm\Type\Atomic\TFloat;
|
use Psalm\Type\Atomic\TFloat;
|
||||||
use Psalm\Type\Atomic\TGenericParam;
|
use Psalm\Type\Atomic\TGenericParam;
|
||||||
use Psalm\Type\Atomic\TInt;
|
use Psalm\Type\Atomic\TInt;
|
||||||
|
use Psalm\Type\Atomic\TLiteralString;
|
||||||
use Psalm\Type\Atomic\TMixed;
|
use Psalm\Type\Atomic\TMixed;
|
||||||
use Psalm\Type\Atomic\TNamedObject;
|
use Psalm\Type\Atomic\TNamedObject;
|
||||||
use Psalm\Type\Atomic\TNull;
|
use Psalm\Type\Atomic\TNull;
|
||||||
use Psalm\Type\Atomic\TNumeric;
|
use Psalm\Type\Atomic\TNumeric;
|
||||||
use Psalm\Type\Reconciler;
|
use Psalm\Type\Reconciler;
|
||||||
|
use Psalm\Type\Union;
|
||||||
|
|
||||||
class BinaryOpChecker
|
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])]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ use Psalm\CodeLocation;
|
|||||||
use Psalm\StatementsSource;
|
use Psalm\StatementsSource;
|
||||||
use Psalm\Storage\FileStorage;
|
use Psalm\Storage\FileStorage;
|
||||||
use Psalm\Type;
|
use Psalm\Type;
|
||||||
|
use Psalm\Type\Atomic\TLiteralString;
|
||||||
|
|
||||||
class Union
|
class Union
|
||||||
{
|
{
|
||||||
@ -678,6 +679,42 @@ class Union
|
|||||||
return $type->type_params[count($type->type_params) - 1]->isSingle();
|
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 StatementsSource $source
|
||||||
* @param CodeLocation $code_location
|
* @param CodeLocation $code_location
|
||||||
|
@ -831,6 +831,13 @@ class TypeAlgebraTest extends TestCase
|
|||||||
echo $a === false ? "a" : "b";
|
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) {}',
|
if ($a !== $b) {}',
|
||||||
'error_message' => 'RedundantCondition',
|
'error_message' => 'RedundantCondition',
|
||||||
],
|
],
|
||||||
|
'stringConcatenationTrackedInvalid' => [
|
||||||
|
'<?php
|
||||||
|
$x = "a";
|
||||||
|
$x = "_" . $x;
|
||||||
|
$array = [$x => 2];
|
||||||
|
echo $array["other"];',
|
||||||
|
'error_message' => 'InvalidArrayOffset',
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user