Skip to content

Commit d975b94

Browse files
Fix wrong inference on preg_match != false
1 parent 98d927a commit d975b94

File tree

8 files changed

+69
-11
lines changed

8 files changed

+69
-11
lines changed

src/Analyser/TypeSpecifier.php

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -339,13 +339,15 @@ public function specifyTypesInCondition(
339339
&& count($expr->right->getArgs()) >= 3
340340
&& $expr->right->name instanceof Name
341341
&& in_array(strtolower((string) $expr->right->name), ['preg_match'], true)
342-
&& IntegerRangeType::fromInterval(0, null)->isSuperTypeOf($leftType)->yes()
342+
&& (
343+
IntegerRangeType::fromInterval(1, null)->isSuperTypeOf($leftType)->yes()
344+
|| ($expr instanceof Expr\BinaryOp\Smaller && IntegerRangeType::fromInterval(0, null)->isSuperTypeOf($leftType)->yes())
345+
)
343346
) {
344-
return $this->specifyTypesInCondition(
345-
$scope,
346-
new Expr\BinaryOp\NotIdentical($expr->right, new ConstFetch(new Name('false'))),
347-
$context,
348-
)->setRootExpr($expr);
347+
// 0 < preg_match or 1 <= preg_match becomes 1 === preg_match
348+
$newExpr = new Expr\BinaryOp\Identical($expr->right, new Node\Scalar\Int_(1));
349+
350+
return $this->specifyTypesInCondition($scope, $newExpr, $context)->setRootExpr($expr);
349351
}
350352

351353
if (

src/Type/Php/PregMatchTypeSpecifyingExtension.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,18 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n
5555
$flagsType = $scope->getType($flagsArg->value);
5656
}
5757

58+
if ($context->true() && $context->falsey()) {
59+
$wasMatched = TrinaryLogic::createMaybe();
60+
} elseif ($context->true()) {
61+
$wasMatched = TrinaryLogic::createYes();
62+
} else {
63+
$wasMatched = TrinaryLogic::createNo();
64+
}
65+
5866
if ($functionReflection->getName() === 'preg_match') {
59-
$matchedType = $this->regexShapeMatcher->matchExpr($patternArg->value, $flagsType, TrinaryLogic::createFromBoolean($context->true()), $scope);
67+
$matchedType = $this->regexShapeMatcher->matchExpr($patternArg->value, $flagsType, $wasMatched, $scope);
6068
} else {
61-
$matchedType = $this->regexShapeMatcher->matchAllExpr($patternArg->value, $flagsType, TrinaryLogic::createFromBoolean($context->true()), $scope);
69+
$matchedType = $this->regexShapeMatcher->matchAllExpr($patternArg->value, $flagsType, $wasMatched, $scope);
6270
}
6371
if ($matchedType === null) {
6472
return new SpecifiedTypes();

tests/PHPStan/Analyser/nsrt/bug-11293.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public function sayHello3(string $s): void
3030
public function sayHello4(string $s): void
3131
{
3232
if (preg_match('/data-(\d{6})\.json$/', $s, $matches) <= 0) {
33-
assertType('array{}', $matches);
33+
assertType('list{0?: string, 1?: non-falsy-string&numeric-string}', $matches);
3434

3535
return;
3636
}
@@ -41,7 +41,7 @@ public function sayHello4(string $s): void
4141
public function sayHello5(string $s): void
4242
{
4343
if (preg_match('/data-(\d{6})\.json$/', $s, $matches) < 1) {
44-
assertType('array{}', $matches);
44+
assertType('list{0?: string, 1?: non-falsy-string&numeric-string}', $matches);
4545

4646
return;
4747
}
@@ -52,7 +52,7 @@ public function sayHello5(string $s): void
5252
public function sayHello6(string $s): void
5353
{
5454
if (1 > preg_match('/data-(\d{6})\.json$/', $s, $matches)) {
55-
assertType('array{}', $matches);
55+
assertType('list{0?: string, 1?: non-falsy-string&numeric-string}', $matches);
5656

5757
return;
5858
}

tests/PHPStan/Rules/Comparison/BooleanAndConstantConditionRuleTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,13 @@ public function testBug5365(): void
432432
$this->analyse([__DIR__ . '/data/bug-5365.php'], []);
433433
}
434434

435+
public function testBug11908(): void
436+
{
437+
$this->treatPhpDocTypesAsCertain = true;
438+
$this->reportAlwaysTrueInLastCondition = true;
439+
$this->analyse([__DIR__ . '/data/bug-11908.php'], []);
440+
}
441+
435442
public function testBug8555(): void
436443
{
437444
$this->treatPhpDocTypesAsCertain = true;
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug11908;
4+
5+
$matches = false;
6+
if (preg_match('/a/', '', $matches) !== false && $matches) {
7+
var_export($matches);
8+
}

tests/PHPStan/Rules/Variables/IssetRuleTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,13 @@ public function testBug12771(): void
473473
$this->analyse([__DIR__ . '/data/bug-12771.php'], []);
474474
}
475475

476+
public function testBug11708(): void
477+
{
478+
$this->treatPhpDocTypesAsCertain = true;
479+
480+
$this->analyse([__DIR__ . '/data/bug-11708.php'], []);
481+
}
482+
476483
public function testIssetAfterRememberedConstructor(): void
477484
{
478485
$this->treatPhpDocTypesAsCertain = true;

tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,13 @@ public function testBug10577(): void
342342
$this->analyse([__DIR__ . '/data/bug-10577.php'], []);
343343
}
344344

345+
public function testBug11708(): void
346+
{
347+
$this->treatPhpDocTypesAsCertain = true;
348+
349+
$this->analyse([__DIR__ . '/data/bug-11708.php'], []);
350+
}
351+
345352
public function testBug10610(): void
346353
{
347354
$this->treatPhpDocTypesAsCertain = true;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug11708;
4+
5+
class HelloWorld
6+
{
7+
public function sayHello(): void
8+
{
9+
$xRequestStart = sprintf('t=%s', uniqid('fake_timestamp_'));
10+
11+
$matches = [];
12+
if (false === preg_match('/^t=(\d+)$/', (string) $xRequestStart, $matches)) {
13+
return;
14+
}
15+
16+
$requestStart = $matches[1] ?? null;
17+
$requestStart2 = isset($matches[1]);
18+
}
19+
}

0 commit comments

Comments
 (0)