Skip to content

Commit a16c7ae

Browse files
clxmstaabondrejmirtes
authored andcommitted
support variable constant flags in preg_split
1 parent 27b2c91 commit a16c7ae

File tree

2 files changed

+45
-8
lines changed

2 files changed

+45
-8
lines changed

src/Type/Php/PregSplitDynamicReturnTypeExtension.php

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
namespace PHPStan\Type\Php;
44

55
use PhpParser\Node\Arg;
6+
use PhpParser\Node\Expr;
7+
use PhpParser\Node\Expr\BinaryOp\BitwiseOr;
68
use PhpParser\Node\Expr\FuncCall;
79
use PhpParser\Node\Name;
810
use PHPStan\Analyser\Scope;
@@ -15,6 +17,7 @@
1517
use PHPStan\Type\Constant\ConstantBooleanType;
1618
use PHPStan\Type\Constant\ConstantIntegerType;
1719
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
20+
use PHPStan\Type\IntegerRangeType;
1821
use PHPStan\Type\IntegerType;
1922
use PHPStan\Type\StringType;
2023
use PHPStan\Type\Type;
@@ -46,7 +49,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
4649
if ($this->hasFlag($this->getConstant('PREG_SPLIT_OFFSET_CAPTURE'), $flagsArg, $scope)) {
4750
$type = new ArrayType(
4851
new IntegerType(),
49-
new ConstantArrayType([new ConstantIntegerType(0), new ConstantIntegerType(1)], [new StringType(), new IntegerType()]),
52+
new ConstantArrayType([new ConstantIntegerType(0), new ConstantIntegerType(1)], [new StringType(), IntegerRangeType::fromInterval(0, null)]),
5053
);
5154
return TypeCombinator::union($type, new ConstantBooleanType(false));
5255
}
@@ -55,13 +58,25 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
5558
}
5659

5760

58-
private function hasFlag(int $flag, ?Arg $expression, Scope $scope): bool
61+
private function hasFlag(int $flag, ?Arg $arg, Scope $scope): bool
5962
{
60-
if ($expression === null) {
63+
if ($arg === null) {
6164
return false;
6265
}
6366

64-
$type = $scope->getType($expression->value);
67+
return $this->isConstantFlag($flag, $arg->value, $scope);
68+
}
69+
70+
private function isConstantFlag(int $flag, Expr $expression, Scope $scope): bool
71+
{
72+
if ($expression instanceof BitwiseOr) {
73+
$left = $expression->left;
74+
$right = $expression->right;
75+
76+
return $this->isConstantFlag($flag, $left, $scope) || $this->isConstantFlag($flag, $right, $scope);
77+
}
78+
79+
$type = $scope->getType($expression);
6580
return $type instanceof ConstantIntegerType && ($type->getValue() & $flag) === $flag;
6681
}
6782

tests/PHPStan/Analyser/data/preg_split.php

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,29 @@
22

33
use function PHPStan\Testing\assertType;
44

5-
assertType('array<int, string>|false', preg_split('/-/', '1-2-3'));
6-
assertType('array<int, string>|false', preg_split('/-/', '1-2-3', -1, PREG_SPLIT_NO_EMPTY));
7-
assertType('array<int, array{string, int}>|false', preg_split('/-/', '1-2-3', -1, PREG_SPLIT_OFFSET_CAPTURE));
8-
assertType('array<int, array{string, int}>|false', preg_split('/-/', '1-2-3', -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE));
5+
class HelloWorld
6+
{
7+
public function doFoo()
8+
{
9+
assertType('array<int, string>|false', preg_split('/-/', '1-2-3'));
10+
assertType('array<int, string>|false', preg_split('/-/', '1-2-3', -1, PREG_SPLIT_NO_EMPTY));
11+
assertType('array<int, array{string, int<0, max>}>|false', preg_split('/-/', '1-2-3', -1, PREG_SPLIT_OFFSET_CAPTURE));
12+
assertType('array<int, array{string, int<0, max>}>|false', preg_split('/-/', '1-2-3', -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE));
13+
}
14+
15+
/**
16+
* @param string $pattern
17+
* @param string $subject
18+
* @param int $limit
19+
* @param int $flags PREG_SPLIT_NO_EMPTY or PREG_SPLIT_DELIM_CAPTURE
20+
* @return list<array{string, int}>
21+
* @phpstan-return list<array{string, int<0, max>}>
22+
*/
23+
public static function splitWithOffset($pattern, $subject, $limit = -1, $flags = 0)
24+
{
25+
assertType('array<int, array{string, int<0, max>}>|false', preg_split($pattern, $subject, $limit, $flags | PREG_SPLIT_OFFSET_CAPTURE));
26+
assertType('array<int, array{string, int<0, max>}>|false', preg_split($pattern, $subject, $limit, PREG_SPLIT_OFFSET_CAPTURE | $flags));
27+
28+
assertType('array<int, array{string, int<0, max>}>|false', preg_split($pattern, $subject, $limit, PREG_SPLIT_OFFSET_CAPTURE | $flags | PREG_SPLIT_NO_EMPTY));
29+
}
30+
}

0 commit comments

Comments
 (0)