From a549175edc411a8b4f82a775d732b988acc18e7b Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 11 Jul 2024 15:44:02 +0200 Subject: [PATCH 1/2] Allow overriding the PHP 7.2 support for PREG_UNMATCHED_AS_NULL --- src/Type/Php/RegexArrayShapeMatcher.php | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/Type/Php/RegexArrayShapeMatcher.php b/src/Type/Php/RegexArrayShapeMatcher.php index 794579695f..a029223161 100644 --- a/src/Type/Php/RegexArrayShapeMatcher.php +++ b/src/Type/Php/RegexArrayShapeMatcher.php @@ -40,7 +40,7 @@ public function __construct( { } - public function matchType(Type $patternType, ?Type $flagsType, TrinaryLogic $wasMatched): ?Type + public function matchType(Type $patternType, ?Type $flagsType, TrinaryLogic $wasMatched, bool $supportsUnmatchedAsNullOn72 = false): ?Type { if ($wasMatched->no()) { return new ConstantArrayType([], []); @@ -65,7 +65,7 @@ public function matchType(Type $patternType, ?Type $flagsType, TrinaryLogic $was $matchedTypes = []; foreach ($constantStrings as $constantString) { - $matched = $this->matchRegex($constantString->getValue(), $flags, $wasMatched); + $matched = $this->matchRegex($constantString->getValue(), $flags, $wasMatched, $supportsUnmatchedAsNullOn72); if ($matched === null) { return null; } @@ -83,7 +83,7 @@ public function matchType(Type $patternType, ?Type $flagsType, TrinaryLogic $was /** * @param int-mask|null $flags */ - private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched): ?Type + private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched, bool $supportsUnmatchedAsNullOn72): ?Type { $parseResult = $this->parseGroups($regex); if ($parseResult === null) { @@ -100,7 +100,7 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched $trailingOptionals++; } - $valueType = $this->getValueType($flags ?? 0); + $valueType = $this->getValueType($flags ?? 0, $supportsUnmatchedAsNullOn72); $onlyOptionalTopLevelGroup = $this->getOnlyOptionalTopLevelGroup($groupList); $onlyTopLevelAlternationId = $this->getOnlyTopLevelAlternationId($groupList); @@ -119,6 +119,7 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched $wasMatched, $trailingOptionals, $flags ?? 0, + $supportsUnmatchedAsNullOn72, ); return TypeCombinator::union( @@ -154,6 +155,7 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched $wasMatched, $trailingOptionals, $flags ?? 0, + $supportsUnmatchedAsNullOn72, ); $combiTypes[] = $combiType; @@ -177,6 +179,7 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched $wasMatched, $trailingOptionals, $flags ?? 0, + $supportsUnmatchedAsNullOn72, ); } @@ -239,6 +242,7 @@ private function buildArrayType( TrinaryLogic $wasMatched, int $trailingOptionals, int $flags, + bool $supportsUnmatchedAsNullOn72, ): Type { $builder = ConstantArrayTypeBuilder::createEmpty(); @@ -260,10 +264,10 @@ private function buildArrayType( } else { if ($i < $countGroups - $trailingOptionals) { $optional = false; - if ($this->containsUnmatchedAsNull($flags)) { + if ($this->containsUnmatchedAsNull($flags, $supportsUnmatchedAsNullOn72)) { $groupValueType = TypeCombinator::removeNull($groupValueType); } - } elseif ($this->containsUnmatchedAsNull($flags)) { + } elseif ($this->containsUnmatchedAsNull($flags, $supportsUnmatchedAsNullOn72)) { $optional = false; } else { $optional = $captureGroup->isOptional(); @@ -290,9 +294,9 @@ private function buildArrayType( return $builder->getArray(); } - private function containsUnmatchedAsNull(int $flags): bool + private function containsUnmatchedAsNull(int $flags, bool $supportsUnmatchedAsNullOn72): bool { - return ($flags & PREG_UNMATCHED_AS_NULL) !== 0 && $this->phpVersion->supportsPregUnmatchedAsNull(); + return ($flags & PREG_UNMATCHED_AS_NULL) !== 0 && ($supportsUnmatchedAsNullOn72 || $this->phpVersion->supportsPregUnmatchedAsNull()); } private function getKeyType(int|string $key): Type @@ -304,11 +308,11 @@ private function getKeyType(int|string $key): Type return new ConstantIntegerType($key); } - private function getValueType(int $flags): Type + private function getValueType(int $flags, bool $supportsUnmatchedAsNullOn72): Type { $valueType = new StringType(); $offsetType = IntegerRangeType::fromInterval(0, null); - if ($this->containsUnmatchedAsNull($flags)) { + if ($this->containsUnmatchedAsNull($flags, $supportsUnmatchedAsNullOn72)) { $valueType = TypeCombinator::addNull($valueType); // unmatched groups return -1 as offset $offsetType = IntegerRangeType::fromInterval(-1, null); From b4157dcb17bce5419f0646f72077239e1a9f6f77 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 12 Jul 2024 14:24:13 +0200 Subject: [PATCH 2/2] Embed new flag into $flagsType --- src/Type/Php/RegexArrayShapeMatcher.php | 44 ++++++++++++++----------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/src/Type/Php/RegexArrayShapeMatcher.php b/src/Type/Php/RegexArrayShapeMatcher.php index a029223161..49db1198db 100644 --- a/src/Type/Php/RegexArrayShapeMatcher.php +++ b/src/Type/Php/RegexArrayShapeMatcher.php @@ -32,6 +32,11 @@ final class RegexArrayShapeMatcher { + /** + * Pass this into $flagsType as well if the library supports emulating PREG_UNMATCHED_AS_NULL on PHP 7.2 and 7.3 + */ + public const PREG_UNMATCHED_AS_NULL_ON_72_73 = 2048; + private static ?Parser $parser = null; public function __construct( @@ -40,7 +45,7 @@ public function __construct( { } - public function matchType(Type $patternType, ?Type $flagsType, TrinaryLogic $wasMatched, bool $supportsUnmatchedAsNullOn72 = false): ?Type + public function matchType(Type $patternType, ?Type $flagsType, TrinaryLogic $wasMatched): ?Type { if ($wasMatched->no()) { return new ConstantArrayType([], []); @@ -53,19 +58,22 @@ public function matchType(Type $patternType, ?Type $flagsType, TrinaryLogic $was $flags = null; if ($flagsType !== null) { - if ( - !$flagsType instanceof ConstantIntegerType - || !in_array($flagsType->getValue(), [PREG_OFFSET_CAPTURE, PREG_UNMATCHED_AS_NULL, PREG_OFFSET_CAPTURE | PREG_UNMATCHED_AS_NULL], true) - ) { + if (!$flagsType instanceof ConstantIntegerType) { return null; } - $flags = $flagsType->getValue(); + /** @var int-mask $flags */ + $flags = $flagsType->getValue() & (PREG_OFFSET_CAPTURE | PREG_UNMATCHED_AS_NULL | self::PREG_UNMATCHED_AS_NULL_ON_72_73); + + // some other unsupported/unexpected flag was passed in + if ($flags !== $flagsType->getValue()) { + return null; + } } $matchedTypes = []; foreach ($constantStrings as $constantString) { - $matched = $this->matchRegex($constantString->getValue(), $flags, $wasMatched, $supportsUnmatchedAsNullOn72); + $matched = $this->matchRegex($constantString->getValue(), $flags, $wasMatched); if ($matched === null) { return null; } @@ -81,9 +89,9 @@ public function matchType(Type $patternType, ?Type $flagsType, TrinaryLogic $was } /** - * @param int-mask|null $flags + * @param int-mask|null $flags */ - private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched, bool $supportsUnmatchedAsNullOn72): ?Type + private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched): ?Type { $parseResult = $this->parseGroups($regex); if ($parseResult === null) { @@ -100,7 +108,7 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched $trailingOptionals++; } - $valueType = $this->getValueType($flags ?? 0, $supportsUnmatchedAsNullOn72); + $valueType = $this->getValueType($flags ?? 0); $onlyOptionalTopLevelGroup = $this->getOnlyOptionalTopLevelGroup($groupList); $onlyTopLevelAlternationId = $this->getOnlyTopLevelAlternationId($groupList); @@ -119,7 +127,6 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched $wasMatched, $trailingOptionals, $flags ?? 0, - $supportsUnmatchedAsNullOn72, ); return TypeCombinator::union( @@ -155,7 +162,6 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched $wasMatched, $trailingOptionals, $flags ?? 0, - $supportsUnmatchedAsNullOn72, ); $combiTypes[] = $combiType; @@ -179,7 +185,6 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched $wasMatched, $trailingOptionals, $flags ?? 0, - $supportsUnmatchedAsNullOn72, ); } @@ -242,7 +247,6 @@ private function buildArrayType( TrinaryLogic $wasMatched, int $trailingOptionals, int $flags, - bool $supportsUnmatchedAsNullOn72, ): Type { $builder = ConstantArrayTypeBuilder::createEmpty(); @@ -264,10 +268,10 @@ private function buildArrayType( } else { if ($i < $countGroups - $trailingOptionals) { $optional = false; - if ($this->containsUnmatchedAsNull($flags, $supportsUnmatchedAsNullOn72)) { + if ($this->containsUnmatchedAsNull($flags)) { $groupValueType = TypeCombinator::removeNull($groupValueType); } - } elseif ($this->containsUnmatchedAsNull($flags, $supportsUnmatchedAsNullOn72)) { + } elseif ($this->containsUnmatchedAsNull($flags)) { $optional = false; } else { $optional = $captureGroup->isOptional(); @@ -294,9 +298,9 @@ private function buildArrayType( return $builder->getArray(); } - private function containsUnmatchedAsNull(int $flags, bool $supportsUnmatchedAsNullOn72): bool + private function containsUnmatchedAsNull(int $flags): bool { - return ($flags & PREG_UNMATCHED_AS_NULL) !== 0 && ($supportsUnmatchedAsNullOn72 || $this->phpVersion->supportsPregUnmatchedAsNull()); + return ($flags & PREG_UNMATCHED_AS_NULL) !== 0 && (($flags & self::PREG_UNMATCHED_AS_NULL_ON_72_73) !== 0 || $this->phpVersion->supportsPregUnmatchedAsNull()); } private function getKeyType(int|string $key): Type @@ -308,11 +312,11 @@ private function getKeyType(int|string $key): Type return new ConstantIntegerType($key); } - private function getValueType(int $flags, bool $supportsUnmatchedAsNullOn72): Type + private function getValueType(int $flags): Type { $valueType = new StringType(); $offsetType = IntegerRangeType::fromInterval(0, null); - if ($this->containsUnmatchedAsNull($flags, $supportsUnmatchedAsNullOn72)) { + if ($this->containsUnmatchedAsNull($flags)) { $valueType = TypeCombinator::addNull($valueType); // unmatched groups return -1 as offset $offsetType = IntegerRangeType::fromInterval(-1, null);