From a0d6ec24abfeb283dbf3d7df6135faa65bcc7e82 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 29 May 2026 15:25:58 +0200 Subject: [PATCH] Split preg-match nsrt test by PHP/PHPStan version On PHP 7.2/7.3 PHPStan 2.x cannot be installed (it requires PHP >= 7.4), so composer falls back to PHPStan 1.12 which infers `string` rather than `non-falsy-string` for these matches. preg-match.php was the only nsrt file without a `// lint` directive, so it ran everywhere and failed on 7.2/7.3. Gate preg-match.php to `// lint >= 7.4` (keeping the non-falsy-string expectations for PHPStan 2.x) and add preg-match-php7.2.php with `// lint < 7.4` holding the plain-string expectations for PHPStan 1.12, mirroring the existing preg-replace-callback split. Co-Authored-By: Claude Opus 4.8 (1M context) --- tests/PHPStanTests/nsrt/preg-match-php7.2.php | 139 ++++++++++++++++++ tests/PHPStanTests/nsrt/preg-match.php | 2 +- 2 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStanTests/nsrt/preg-match-php7.2.php diff --git a/tests/PHPStanTests/nsrt/preg-match-php7.2.php b/tests/PHPStanTests/nsrt/preg-match-php7.2.php new file mode 100644 index 0000000..36a61b1 --- /dev/null +++ b/tests/PHPStanTests/nsrt/preg-match-php7.2.php @@ -0,0 +1,139 @@ +£|€)\d+/', $s, $matches)) { + assertType('array{0: string, currency: \'£\'|\'€\', 1: \'£\'|\'€\'}', $matches); + } else { + assertType('array{}', $matches); + } + assertType('array{}|array{0: string, currency: \'£\'|\'€\', 1: \'£\'|\'€\'}', $matches); +} + +function doMatchStrictGroups(string $s): void +{ + if (Preg::matchStrictGroups('/Price: /i', $s, $matches)) { + assertType('array{string}', $matches); + } else { + assertType('array{}', $matches); + } + assertType('array{}|array{string}', $matches); + + if (Preg::matchStrictGroups('/Price: (£|€)\d+/', $s, $matches)) { + assertType('array{string, \'£\'|\'€\'}', $matches); + } else { + assertType('array{}', $matches); + } + assertType('array{}|array{string, \'£\'|\'€\'}', $matches); + + if (Preg::isMatchStrictGroups('/Price: (?£|€)\d+/', $s, $matches)) { + assertType('array{0: string, test: \'£\'|\'€\', 1: \'£\'|\'€\'}', $matches); + } else { + assertType('array{}', $matches); + } + assertType('array{}|array{0: string, test: \'£\'|\'€\', 1: \'£\'|\'€\'}', $matches); +} + +function doMatchStrictGroupsUnsafe(string $s): void +{ + if (Preg::isMatchStrictGroups('{Configure Command(?: *| *=> *)(.*)(?:|$)}m', $s, $matches)) { + // does not error because the match group might be empty but is not optional + assertType('array{string, string}', $matches); + } + + // should error as it is unsafe due to the optional group 1 + Regex::matchStrictGroups('{Configure Command(?: *| *=> *)(.*)?(?:|$)}m', $s); + + if (Preg::matchAllStrictGroups('{((?.)?z)}m', $s, $matches)) { + // should error as it is unsafe due to the optional group foo/2 + } + + if (Preg::isMatchStrictGroups('{'.$s.'}', $s, $matches)) { + // should error as it is unsafe due not being introspectable with the dynamic string + } +} + +function doMatchAllStrictGroups(string $s): void +{ + if (Preg::matchAllStrictGroups('/Price: /i', $s, $matches)) { + assertType('array{list}', $matches); + } else { + assertType('array{}', $matches); + } + assertType('array{}|array{list}', $matches); + + if (Preg::matchAllStrictGroups('/Price: (£|€)\d+/', $s, $matches)) { + assertType('array{list, list<\'£\'|\'€\'>}', $matches); + } else { + assertType('array{}', $matches); + } + assertType('array{}|array{list, list<\'£\'|\'€\'>}', $matches); + + if (Preg::isMatchAllStrictGroups('/Price: (?£|€)\d+/', $s, $matches)) { + assertType('array{0: list, test: list<\'£\'|\'€\'>, 1: list<\'£\'|\'€\'>}', $matches); + } else { + assertType('array{}', $matches); + } + assertType('array{}|array{0: list, test: list<\'£\'|\'€\'>, 1: list<\'£\'|\'€\'>}', $matches); + + if (Preg::isMatchAllStrictGroups('/Price: (?£|€)?\d+/', $s, $matches)) { + assertType('array{0: list, test: list<\'£\'|\'€\'>, 1: list<\'£\'|\'€\'>}', $matches); + } +} + +// disabled until https://github.com/phpstan/phpstan-src/pull/3185 can be resolved +// +//function identicalMatch(string $s): void +//{ +// if (Preg::match('/Price: /i', $s, $matches) === 1) { +// assertType('array{string}', $matches); +// } else { +// assertType('array{}', $matches); +// } +// assertType('array{}|array{string}', $matches); +//} +// +//function equalMatch(string $s): void +//{ +// if (Preg::match('/Price: /i', $s, $matches) == 1) { +// assertType('array{string}', $matches); +// } else { +// assertType('array{}', $matches); +// } +// assertType('array{}|array{string}', $matches); +//} diff --git a/tests/PHPStanTests/nsrt/preg-match.php b/tests/PHPStanTests/nsrt/preg-match.php index e04c419..519070c 100644 --- a/tests/PHPStanTests/nsrt/preg-match.php +++ b/tests/PHPStanTests/nsrt/preg-match.php @@ -1,4 +1,4 @@ -= 7.4 namespace PregMatchShapes;