-
-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement array shapes for Preg::match $matches by-ref parameter (#25)
* Implement array shapes for `Preg::match` $matches by-ref parameter * declare conflict with phpstan < 1.11.6 * Fork off phpstan CI in another job * Get rid of phpunit bridge Co-authored-by: Markus Staab <[email protected]>
- Loading branch information
Showing
13 changed files
with
333 additions
and
43 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
# composer/pcre PHPStan extensions | ||
# | ||
# These can be reused by third party packages by including 'vendor/composer/pcre/extension.neon' | ||
# in your phpstan config | ||
|
||
conditionalTags: | ||
Composer\Pcre\PHPStan\PregMatchParameterOutTypeExtension: | ||
phpstan.staticMethodParameterOutTypeExtension: %featureToggles.narrowPregMatches% | ||
Composer\Pcre\PHPStan\PregMatchTypeSpecifyingExtension: | ||
phpstan.typeSpecifier.staticMethodTypeSpecifyingExtension: %featureToggles.narrowPregMatches% | ||
|
||
services: | ||
- | ||
class: Composer\Pcre\PHPStan\PregMatchParameterOutTypeExtension | ||
- | ||
class: Composer\Pcre\PHPStan\PregMatchTypeSpecifyingExtension |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
<?php declare(strict_types=1); | ||
|
||
namespace Composer\Pcre\PHPStan; | ||
|
||
use PHPStan\Analyser\Scope; | ||
use PHPStan\Type\Constant\ConstantIntegerType; | ||
use PHPStan\Type\TypeCombinator; | ||
use PHPStan\Type\Type; | ||
use PhpParser\Node\Arg; | ||
|
||
final class PregMatchFlags | ||
{ | ||
static public function getType(?Arg $flagsArg, Scope $scope): ?Type | ||
{ | ||
if ($flagsArg === null) { | ||
return new ConstantIntegerType(PREG_UNMATCHED_AS_NULL); | ||
} | ||
|
||
$flagsType = $scope->getType($flagsArg->value); | ||
|
||
$constantScalars = $flagsType->getConstantScalarValues(); | ||
if ($constantScalars === []) { | ||
return null; | ||
} | ||
|
||
$internalFlagsTypes = []; | ||
foreach ($flagsType->getConstantScalarValues() as $constantScalarValue) { | ||
if (!is_int($constantScalarValue)) { | ||
return null; | ||
} | ||
|
||
$internalFlagsTypes[] = new ConstantIntegerType($constantScalarValue | PREG_UNMATCHED_AS_NULL); | ||
} | ||
return TypeCombinator::union(...$internalFlagsTypes); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
<?php declare(strict_types=1); | ||
|
||
namespace Composer\Pcre\PHPStan; | ||
|
||
use Composer\Pcre\Preg; | ||
use PhpParser\Node\Expr\StaticCall; | ||
use PHPStan\Analyser\Scope; | ||
use PHPStan\Reflection\MethodReflection; | ||
use PHPStan\Reflection\ParameterReflection; | ||
use PHPStan\TrinaryLogic; | ||
use PHPStan\Type\Php\RegexArrayShapeMatcher; | ||
use PHPStan\Type\StaticMethodParameterOutTypeExtension; | ||
use PHPStan\Type\Type; | ||
|
||
final class PregMatchParameterOutTypeExtension implements StaticMethodParameterOutTypeExtension | ||
{ | ||
/** | ||
* @var RegexArrayShapeMatcher | ||
*/ | ||
private $regexShapeMatcher; | ||
|
||
public function __construct( | ||
RegexArrayShapeMatcher $regexShapeMatcher | ||
) | ||
{ | ||
$this->regexShapeMatcher = $regexShapeMatcher; | ||
} | ||
|
||
public function isStaticMethodSupported(MethodReflection $methodReflection, ParameterReflection $parameter): bool | ||
{ | ||
return | ||
$methodReflection->getDeclaringClass()->getName() === Preg::class | ||
&& $methodReflection->getName() === 'match' | ||
&& $parameter->getName() === 'matches'; | ||
} | ||
|
||
public function getParameterOutTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, ParameterReflection $parameter, Scope $scope): ?Type | ||
{ | ||
$args = $methodCall->getArgs(); | ||
$patternArg = $args[0] ?? null; | ||
$matchesArg = $args[2] ?? null; | ||
$flagsArg = $args[3] ?? null; | ||
|
||
if ( | ||
$patternArg === null || $matchesArg === null | ||
) { | ||
return null; | ||
} | ||
|
||
$flagsType = PregMatchFlags::getType($flagsArg, $scope); | ||
if ($flagsType === null) { | ||
return null; | ||
} | ||
$patternType = $scope->getType($patternArg->value); | ||
|
||
return $this->regexShapeMatcher->matchType($patternType, $flagsType, TrinaryLogic::createMaybe()); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
<?php declare(strict_types=1); | ||
|
||
namespace Composer\Pcre\PHPStan; | ||
|
||
use Composer\Pcre\Preg; | ||
use PhpParser\Node\Expr\StaticCall; | ||
use PHPStan\Analyser\Scope; | ||
use PHPStan\Analyser\SpecifiedTypes; | ||
use PHPStan\Analyser\TypeSpecifier; | ||
use PHPStan\Analyser\TypeSpecifierAwareExtension; | ||
use PHPStan\Analyser\TypeSpecifierContext; | ||
use PHPStan\Reflection\MethodReflection; | ||
use PHPStan\TrinaryLogic; | ||
use PHPStan\Type\Php\RegexArrayShapeMatcher; | ||
use PHPStan\Type\StaticMethodTypeSpecifyingExtension; | ||
|
||
final class PregMatchTypeSpecifyingExtension implements StaticMethodTypeSpecifyingExtension, TypeSpecifierAwareExtension | ||
{ | ||
/** | ||
* @var TypeSpecifier | ||
*/ | ||
private $typeSpecifier; | ||
|
||
/** | ||
* @var RegexArrayShapeMatcher | ||
*/ | ||
private $regexShapeMatcher; | ||
|
||
public function __construct(RegexArrayShapeMatcher $regexShapeMatcher) | ||
{ | ||
$this->regexShapeMatcher = $regexShapeMatcher; | ||
} | ||
|
||
public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void | ||
{ | ||
$this->typeSpecifier = $typeSpecifier; | ||
} | ||
|
||
public function getClass(): string | ||
{ | ||
return Preg::class; | ||
} | ||
|
||
public function isStaticMethodSupported(MethodReflection $methodReflection, StaticCall $node, TypeSpecifierContext $context): bool | ||
{ | ||
return $methodReflection->getName() === 'match' && !$context->null(); | ||
} | ||
|
||
public function specifyTypes(MethodReflection $methodReflection, StaticCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes | ||
{ | ||
$args = $node->getArgs(); | ||
$patternArg = $args[0] ?? null; | ||
$matchesArg = $args[2] ?? null; | ||
$flagsArg = $args[3] ?? null; | ||
|
||
if ( | ||
$patternArg === null || $matchesArg === null | ||
) { | ||
return new SpecifiedTypes(); | ||
} | ||
|
||
$flagsType = PregMatchFlags::getType($flagsArg, $scope); | ||
if ($flagsType === null) { | ||
return new SpecifiedTypes(); | ||
} | ||
$patternType = $scope->getType($patternArg->value); | ||
|
||
$matchedType = $this->regexShapeMatcher->matchType($patternType, $flagsType, TrinaryLogic::createFromBoolean($context->true())); | ||
if ($matchedType === null) { | ||
return new SpecifiedTypes(); | ||
} | ||
|
||
$overwrite = false; | ||
if ($context->false()) { | ||
$overwrite = true; | ||
$context = $context->negate(); | ||
} | ||
|
||
return $this->typeSpecifier->create( | ||
$matchesArg->value, | ||
$matchedType, | ||
$context, | ||
$overwrite, | ||
$scope, | ||
$node | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of composer/pcre. | ||
* | ||
* (c) Composer <https://github.com/composer> | ||
* | ||
* For the full copyright and license information, please view | ||
* the LICENSE file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Composer\Pcre\PHPStanTests; | ||
|
||
use PHPStan\Testing\TypeInferenceTestCase; | ||
|
||
/** | ||
* Run with "vendor/bin/phpunit --testsuite phpstan" | ||
* | ||
* This is excluded by default to avoid side effects with the library tests | ||
* | ||
* @group phpstan | ||
*/ | ||
class TypeInferenceTest extends TypeInferenceTestCase | ||
{ | ||
/** | ||
* @return iterable<mixed> | ||
*/ | ||
public function dataFileAsserts(): iterable | ||
{ | ||
yield from $this->gatherAssertTypesFromDirectory(__DIR__ . '/nsrt'); | ||
} | ||
|
||
/** | ||
* @dataProvider dataFileAsserts | ||
* @param mixed ...$args | ||
*/ | ||
public function testFileAsserts( | ||
string $assertType, | ||
string $file, | ||
...$args | ||
): void | ||
{ | ||
$this->assertFileAsserts($assertType, $file, ...$args); | ||
} | ||
|
||
public static function getAdditionalConfigFiles(): array | ||
{ | ||
return [ | ||
'phar://' . __DIR__ . '/../../vendor/phpstan/phpstan/phpstan.phar/conf/bleedingEdge.neon', | ||
__DIR__ . '/../../extension.neon', | ||
]; | ||
} | ||
} |
Oops, something went wrong.