From 83ab13f211c692570e48c7ca22f60ad4540b2cb9 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 27 Mar 2022 21:05:56 +0200 Subject: [PATCH 1/5] infer non-empty-string on substr() comparison with constant string infer non-empty-string on substr() comparison with constant string try reducing unnecessary work cs --- src/Analyser/TypeSpecifier.php | 21 +++++++ .../Analyser/NodeScopeResolverTest.php | 1 + .../non-empty-string-substr-specifying.php | 63 +++++++++++++++++++ 3 files changed, 85 insertions(+) create mode 100644 tests/PHPStan/Analyser/data/non-empty-string-substr-specifying.php diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 6221164a74..56a5ce36d3 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -242,6 +242,27 @@ public function specifyTypesInCondition( } } } + + if ( + $exprNode instanceof FuncCall + && $exprNode->name instanceof Name + && strtolower($exprNode->name->toString()) === 'substr' + && isset($exprNode->getArgs()[0]) + && $constantType instanceof ConstantStringType + && $constantType->getValue() !== '' + ) { + $argType = $scope->getType($exprNode->getArgs()[0]->value); + + if ($argType->isString()->yes() && !$argType->isNonEmptyString()->yes()) { + return $this->create( + $exprNode->getArgs()[0]->value, + TypeCombinator::intersect($argType, new AccessoryNonEmptyStringType()), + $context, + false, + $scope, + ); + } + } } $rightType = $scope->getType($expr->right); diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index b5f4d49854..06795798a9 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -911,6 +911,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/constant-array-type-identical.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/non-empty-string-str-containing-fns.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6609.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/non-empty-string-substr-specifying.php'); } /** diff --git a/tests/PHPStan/Analyser/data/non-empty-string-substr-specifying.php b/tests/PHPStan/Analyser/data/non-empty-string-substr-specifying.php new file mode 100644 index 0000000000..d6bf60cb27 --- /dev/null +++ b/tests/PHPStan/Analyser/data/non-empty-string-substr-specifying.php @@ -0,0 +1,63 @@ + Date: Mon, 16 May 2022 18:37:16 +0200 Subject: [PATCH 2/5] more tests --- .../data/non-empty-string-substr-specifying.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/PHPStan/Analyser/data/non-empty-string-substr-specifying.php b/tests/PHPStan/Analyser/data/non-empty-string-substr-specifying.php index d6bf60cb27..5bb90ebe62 100644 --- a/tests/PHPStan/Analyser/data/non-empty-string-substr-specifying.php +++ b/tests/PHPStan/Analyser/data/non-empty-string-substr-specifying.php @@ -52,6 +52,16 @@ public function nonEmptySubstr(string $s, int $offset, int $length): void } assertType('string', $s); + if (substr($s, 10) == 'hallo') { + assertType('non-empty-string', $s); + } + assertType('string', $s); + + if (substr($s, -10) == 'hallo') { + assertType('non-empty-string', $s); + } + assertType('string', $s); + $x = (substr($s, 10) === 'hallo'); assertType('string', $s); var_dump($x); From a5ad765a455277833c5dc8bdf7720c839aebd6be Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 16 May 2022 18:38:25 +0200 Subject: [PATCH 3/5] simplify --- src/Analyser/TypeSpecifier.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 56a5ce36d3..81df951003 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -253,7 +253,7 @@ public function specifyTypesInCondition( ) { $argType = $scope->getType($exprNode->getArgs()[0]->value); - if ($argType->isString()->yes() && !$argType->isNonEmptyString()->yes()) { + if ($argType->isString()->yes()) { return $this->create( $exprNode->getArgs()[0]->value, TypeCombinator::intersect($argType, new AccessoryNonEmptyStringType()), From d358ef71d00992715fae0fe4e2ab63f35fd00a73 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 16 May 2022 18:44:34 +0200 Subject: [PATCH 4/5] Revert "more tests" This reverts commit 995712c5c002f634775997564b18bd3da1830eb0. --- .../data/non-empty-string-substr-specifying.php | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/tests/PHPStan/Analyser/data/non-empty-string-substr-specifying.php b/tests/PHPStan/Analyser/data/non-empty-string-substr-specifying.php index 5bb90ebe62..d6bf60cb27 100644 --- a/tests/PHPStan/Analyser/data/non-empty-string-substr-specifying.php +++ b/tests/PHPStan/Analyser/data/non-empty-string-substr-specifying.php @@ -52,16 +52,6 @@ public function nonEmptySubstr(string $s, int $offset, int $length): void } assertType('string', $s); - if (substr($s, 10) == 'hallo') { - assertType('non-empty-string', $s); - } - assertType('string', $s); - - if (substr($s, -10) == 'hallo') { - assertType('non-empty-string', $s); - } - assertType('string', $s); - $x = (substr($s, 10) === 'hallo'); assertType('string', $s); var_dump($x); From f744af327596a9e138e914685ae10aa4bd73d6cf Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 17 May 2022 13:14:54 +0200 Subject: [PATCH 5/5] added yoda style tests --- .../data/non-empty-string-substr-specifying.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/PHPStan/Analyser/data/non-empty-string-substr-specifying.php b/tests/PHPStan/Analyser/data/non-empty-string-substr-specifying.php index d6bf60cb27..38ce338d10 100644 --- a/tests/PHPStan/Analyser/data/non-empty-string-substr-specifying.php +++ b/tests/PHPStan/Analyser/data/non-empty-string-substr-specifying.php @@ -11,11 +11,19 @@ public function nonEmptySubstr(string $s, int $offset, int $length): void assertType('non-empty-string', $s); } assertType('string', $s); + if ('hallo' === substr($s, 10)) { + assertType('non-empty-string', $s); + } + assertType('string', $s); if (substr($s, -10) === 'hallo') { assertType('non-empty-string', $s); } assertType('string', $s); + if ('hallo' === substr($s, -10)) { + assertType('non-empty-string', $s); + } + assertType('string', $s); if (substr($s, 10, 5) === 'hallo') { assertType('non-empty-string', $s); @@ -46,11 +54,19 @@ public function nonEmptySubstr(string $s, int $offset, int $length): void assertType('string', $s); } assertType('string', $s); + if ('' === substr($s, $offset, $length)) { + assertType('string', $s); + } + assertType('string', $s); if (substr($s, $offset, $length) == '') { assertType('string', $s); } assertType('string', $s); + if ('' == substr($s, $offset, $length)) { + assertType('string', $s); + } + assertType('string', $s); $x = (substr($s, 10) === 'hallo'); assertType('string', $s);