From 14a2e6d73588e4664af63a53d557de5b612b7e6a Mon Sep 17 00:00:00 2001 From: phpstan-bot <79867460+phpstan-bot@users.noreply.github.com> Date: Sat, 6 Jun 2026 14:32:46 +0000 Subject: [PATCH 1/9] Implement `AccessoryDecimalIntegerStringType::tryRemove()` and preserve accessories when re-unioning `'0'` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `AccessoryDecimalIntegerStringType` previously used `NonRemoveableTypeTrait`, so removing the constant string `'0'` from a `decimal-int-string` left the type unchanged. `"0"` is the only falsy decimal integer string, so removing it now yields `decimal-int-string&non-falsy-string` (mirrors the existing handling in `AccessoryNonEmptyStringType`/`AccessoryNumericStringType`). - Generalized the `'0' | non-falsy-string` union simplification in `TypeCombinator::compareTypesInUnion()`. The old code matched only the bare `non-falsy-string` via `describe()` string comparison, so `'0' | (numeric-string&non-falsy-string)` and the decimal-int-string variant were left as unsimplified unions. New helper `downgradeNonFalsyStringToNonEmpty()` drops the `AccessoryNonFalsyStringType`, keeps every other accessory, and only re-adds `AccessoryNonEmptyStringType` when the remaining accessories do not already guarantee non-emptiness — so the round trip collapses back to `decimal-int-string` / `numeric-string` / `non-empty-string`. - Probed sibling accessory string types: `AccessoryLowercaseStringType`, `AccessoryUppercaseStringType` and `AccessoryLiteralStringType` can still be the empty string, so removing `'0'` must not make them non-falsy — they correctly remain `NonRemoveableTypeTrait`. The inverse `non-decimal-int-string` does not contain `'0'`, so its removal is a no-op. --- phpstan-baseline.neon | 2 +- .../AccessoryDecimalIntegerStringType.php | 19 +++++- src/Type/TypeCombinator.php | 58 +++++++++++++----- .../Analyser/nsrt/decimal-int-string.php | 60 +++++++++++++++++++ 4 files changed, 122 insertions(+), 17 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 256cd87c441..403d4ed0e11 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1662,7 +1662,7 @@ parameters: - rawMessage: Doing instanceof PHPStan\Type\IntersectionType is error-prone and deprecated. identifier: phpstanApi.instanceofType - count: 6 + count: 7 path: src/Type/TypeCombinator.php - diff --git a/src/Type/Accessory/AccessoryDecimalIntegerStringType.php b/src/Type/Accessory/AccessoryDecimalIntegerStringType.php index d965eb0ebc9..df299b85b65 100644 --- a/src/Type/Accessory/AccessoryDecimalIntegerStringType.php +++ b/src/Type/Accessory/AccessoryDecimalIntegerStringType.php @@ -27,12 +27,12 @@ use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\NonIterableTypeTrait; use PHPStan\Type\Traits\NonObjectTypeTrait; -use PHPStan\Type\Traits\NonRemoveableTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; +use function count; /** * This accessory type is coupled with `Type::isDecimalIntegerString()` method. @@ -55,7 +55,6 @@ class AccessoryDecimalIntegerStringType implements CompoundType, AccessoryType use NonIterableTypeTrait; use UndecidedComparisonCompoundTypeTrait; use NonGenericTypeTrait; - use NonRemoveableTypeTrait; /** @api */ public function __construct(private bool $inverse = false) @@ -203,6 +202,22 @@ public function unsetOffset(Type $offsetType): Type return new ErrorType(); } + public function tryRemove(Type $typeToRemove): ?Type + { + if ($this->inverse) { + return null; + } + + // `"0"` is the only falsy decimal integer string, so removing it + // from a (non-inverse) decimal-int-string makes it non-falsy. + $constantStrings = $typeToRemove->getConstantStrings(); + if (count($constantStrings) === 1 && $constantStrings[0]->getValue() === '0') { + return new IntersectionType([new StringType(), $this, new AccessoryNonFalsyStringType()]); + } + + return null; + } + public function toNumber(): Type { if ($this->inverse) { diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index b3a0e1b7579..2420e80112a 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -7,6 +7,7 @@ use PHPStan\Type\Accessory\AccessoryDecimalIntegerStringType; use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; +use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; use PHPStan\Type\Accessory\AccessoryType; use PHPStan\Type\Accessory\AccessoryUppercaseStringType; use PHPStan\Type\Accessory\HasOffsetType; @@ -600,13 +601,9 @@ private static function compareTypesInUnion(Type $a, Type $b): ?array } if ($a->getValue() === '0') { - $description = $b->describe(VerbosityLevel::value()); - if ($description === 'non-falsy-string') { - return [null, new IntersectionType([ - new StringType(), - new AccessoryNonEmptyStringType(), - ...self::getAccessoryCaseStringTypes($b), - ])]; + $nonEmpty = self::downgradeNonFalsyStringToNonEmpty($b); + if ($nonEmpty !== null) { + return [null, $nonEmpty]; } } } @@ -625,13 +622,9 @@ private static function compareTypesInUnion(Type $a, Type $b): ?array } if ($b->getValue() === '0') { - $description = $a->describe(VerbosityLevel::value()); - if ($description === 'non-falsy-string') { - return [new IntersectionType([ - new StringType(), - new AccessoryNonEmptyStringType(), - ...self::getAccessoryCaseStringTypes($a), - ]), null]; + $nonEmpty = self::downgradeNonFalsyStringToNonEmpty($a); + if ($nonEmpty !== null) { + return [$nonEmpty, null]; } } } @@ -673,6 +666,43 @@ private static function getAccessoryCaseStringTypes(Type $type): array return $accessory; } + /** + * Turns a non-falsy-string type into its non-empty-string counterpart by + * downgrading the non-falsy accessory while preserving every other accessory + * (numeric-string, decimal-int-string, lowercase-string, …). Used to simplify + * `'0' | non-falsy-string-X` back to `non-empty-string-X`, since `"0"` is the + * only value that separates the two. Returns null when $type is not a + * non-constant non-falsy-string built from an intersection. + */ + private static function downgradeNonFalsyStringToNonEmpty(Type $type): ?Type + { + if (!$type instanceof IntersectionType || $type->getConstantStrings() !== []) { + return null; + } + + $newTypes = []; + $found = false; + foreach ($type->getTypes() as $innerType) { + if ($innerType instanceof AccessoryNonFalsyStringType) { + $found = true; + continue; + } + + $newTypes[] = $innerType; + } + + if (!$found) { + return null; + } + + $withoutNonFalsy = self::intersect(...$newTypes); + if ($withoutNonFalsy->isNonEmptyString()->yes()) { + return $withoutNonFalsy; + } + + return self::intersect($withoutNonFalsy, new AccessoryNonEmptyStringType()); + } + private static function removeDecimalIntStringAccessory(Type $type): Type { if (!$type instanceof IntersectionType) { diff --git a/tests/PHPStan/Analyser/nsrt/decimal-int-string.php b/tests/PHPStan/Analyser/nsrt/decimal-int-string.php index fe5c5fdd529..93eeee80eac 100644 --- a/tests/PHPStan/Analyser/nsrt/decimal-int-string.php +++ b/tests/PHPStan/Analyser/nsrt/decimal-int-string.php @@ -55,4 +55,64 @@ public function emptyStringIsNonDecimal(string $s): void } } + /** + * @param decimal-int-string $s + */ + public function removingZeroMakesNonFalsy(string $s): void + { + if ($s !== '0') { + assertType('decimal-int-string&non-falsy-string', $s); + } else { + assertType("'0'", $s); + } + + if ($s != '0') { + assertType('decimal-int-string&non-falsy-string', $s); + } + + if ($s) { + assertType('decimal-int-string&non-falsy-string', $s); + } else { + assertType("'0'", $s); + } + } + + /** + * @param non-decimal-int-string $s + */ + public function removingZeroFromNonDecimal(string $s): void + { + // '0' is a decimal-int-string, so it is not part of non-decimal-int-string + if ($s !== '0') { + assertType('non-decimal-int-string', $s); + } + } + + /** + * @param decimal-int-string $s + */ + public function unionWithZeroRoundTrips(string $s): void + { + $t = $s !== '0' ? $s : '0'; + assertType('decimal-int-string', $t); + } + + /** + * @param numeric-string $s + */ + public function numericUnionWithZeroRoundTrips(string $s): void + { + $t = $s !== '0' ? $s : '0'; + assertType('numeric-string', $t); + } + + /** + * @param non-empty-string $s + */ + public function nonEmptyUnionWithZeroRoundTrips(string $s): void + { + $t = $s !== '0' ? $s : '0'; + assertType('non-empty-string', $t); + } + } From db9291d66843215b3b761047c311319e185e8f38 Mon Sep 17 00:00:00 2001 From: phpstan-bot Date: Sat, 6 Jun 2026 14:43:05 +0000 Subject: [PATCH 2/9] Assert removing '0' from numeric-string yields numeric-string&non-falsy-string Co-Authored-By: Claude Opus 4.8 --- tests/PHPStan/Analyser/nsrt/decimal-int-string.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/PHPStan/Analyser/nsrt/decimal-int-string.php b/tests/PHPStan/Analyser/nsrt/decimal-int-string.php index 93eeee80eac..7ec124a5998 100644 --- a/tests/PHPStan/Analyser/nsrt/decimal-int-string.php +++ b/tests/PHPStan/Analyser/nsrt/decimal-int-string.php @@ -77,6 +77,18 @@ public function removingZeroMakesNonFalsy(string $s): void } } + /** + * @param numeric-string $s + */ + public function removingZeroMakesNumericNonFalsy(string $s): void + { + if ($s !== '0') { + assertType('non-falsy-string&numeric-string', $s); + } else { + assertType("'0'", $s); + } + } + /** * @param non-decimal-int-string $s */ From 8acbfdf06b9b27a55b933be6c201bfefd453c41c Mon Sep 17 00:00:00 2001 From: phpstan-bot Date: Sat, 6 Jun 2026 14:43:46 +0000 Subject: [PATCH 3/9] Add numeric-string narrowing and uppercase/lowercase round-trip tests for removing '0' Co-Authored-By: Claude Opus 4.8 --- .../Analyser/nsrt/decimal-int-string.php | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/tests/PHPStan/Analyser/nsrt/decimal-int-string.php b/tests/PHPStan/Analyser/nsrt/decimal-int-string.php index 7ec124a5998..1528ecacca9 100644 --- a/tests/PHPStan/Analyser/nsrt/decimal-int-string.php +++ b/tests/PHPStan/Analyser/nsrt/decimal-int-string.php @@ -109,6 +109,18 @@ public function unionWithZeroRoundTrips(string $s): void assertType('decimal-int-string', $t); } + /** + * @param numeric-string $s + */ + public function removingZeroFromNumericMakesNonFalsy(string $s): void + { + if ($s !== '0') { + assertType('non-falsy-string&numeric-string', $s); + } else { + assertType("'0'", $s); + } + } + /** * @param numeric-string $s */ @@ -127,4 +139,46 @@ public function nonEmptyUnionWithZeroRoundTrips(string $s): void assertType('non-empty-string', $t); } + /** + * @param uppercase-string $s + */ + public function removingZeroFromUppercase(string $s): void + { + // '' is a falsy uppercase-string too, so removing '0' alone does not + // make an uppercase-string non-falsy. + if ($s !== '0') { + assertType('uppercase-string', $s); + } + } + + /** + * @param uppercase-string $s + */ + public function uppercaseUnionWithZeroRoundTrips(string $s): void + { + $t = $s !== '0' ? $s : '0'; + assertType('uppercase-string', $t); + } + + /** + * @param lowercase-string $s + */ + public function removingZeroFromLowercase(string $s): void + { + // '' is a falsy lowercase-string too, so removing '0' alone does not + // make a lowercase-string non-falsy. + if ($s !== '0') { + assertType('lowercase-string', $s); + } + } + + /** + * @param lowercase-string $s + */ + public function lowercaseUnionWithZeroRoundTrips(string $s): void + { + $t = $s !== '0' ? $s : '0'; + assertType('lowercase-string', $t); + } + } From 6746dc63bd8ed2f29fe05b60449b8506e4907631 Mon Sep 17 00:00:00 2001 From: phpstan-bot Date: Sat, 6 Jun 2026 14:44:12 +0000 Subject: [PATCH 4/9] Drop duplicate numeric-string narrowing test after rebase Co-Authored-By: Claude Opus 4.8 --- tests/PHPStan/Analyser/nsrt/decimal-int-string.php | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/tests/PHPStan/Analyser/nsrt/decimal-int-string.php b/tests/PHPStan/Analyser/nsrt/decimal-int-string.php index 1528ecacca9..171e8972e92 100644 --- a/tests/PHPStan/Analyser/nsrt/decimal-int-string.php +++ b/tests/PHPStan/Analyser/nsrt/decimal-int-string.php @@ -109,18 +109,6 @@ public function unionWithZeroRoundTrips(string $s): void assertType('decimal-int-string', $t); } - /** - * @param numeric-string $s - */ - public function removingZeroFromNumericMakesNonFalsy(string $s): void - { - if ($s !== '0') { - assertType('non-falsy-string&numeric-string', $s); - } else { - assertType("'0'", $s); - } - } - /** * @param numeric-string $s */ From 4af99bd1536844c4be1d47394665ac8aa4b0e732 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 7 Jun 2026 09:58:31 +0200 Subject: [PATCH 5/9] simplify --- src/Type/Accessory/AccessoryDecimalIntegerStringType.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Type/Accessory/AccessoryDecimalIntegerStringType.php b/src/Type/Accessory/AccessoryDecimalIntegerStringType.php index df299b85b65..fc6d9f2fea1 100644 --- a/src/Type/Accessory/AccessoryDecimalIntegerStringType.php +++ b/src/Type/Accessory/AccessoryDecimalIntegerStringType.php @@ -16,6 +16,7 @@ use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantIntegerType; +use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\ErrorType; use PHPStan\Type\FloatType; use PHPStan\Type\GeneralizePrecision; @@ -210,8 +211,7 @@ public function tryRemove(Type $typeToRemove): ?Type // `"0"` is the only falsy decimal integer string, so removing it // from a (non-inverse) decimal-int-string makes it non-falsy. - $constantStrings = $typeToRemove->getConstantStrings(); - if (count($constantStrings) === 1 && $constantStrings[0]->getValue() === '0') { + if ($typeToRemove->isSuperTypeOf(new ConstantStringType('0'))->yes()) { return new IntersectionType([new StringType(), $this, new AccessoryNonFalsyStringType()]); } From f5c365ec16cc8b48073eeb88d80f76658a0a5af6 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 7 Jun 2026 10:07:50 +0200 Subject: [PATCH 6/9] simplify --- src/Type/Accessory/AccessoryDecimalIntegerStringType.php | 1 - src/Type/TypeCombinator.php | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Type/Accessory/AccessoryDecimalIntegerStringType.php b/src/Type/Accessory/AccessoryDecimalIntegerStringType.php index fc6d9f2fea1..fa528ced8da 100644 --- a/src/Type/Accessory/AccessoryDecimalIntegerStringType.php +++ b/src/Type/Accessory/AccessoryDecimalIntegerStringType.php @@ -33,7 +33,6 @@ use PHPStan\Type\TypeCombinator; use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; -use function count; /** * This accessory type is coupled with `Type::isDecimalIntegerString()` method. diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index 2420e80112a..77d9e9caad6 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -676,7 +676,7 @@ private static function getAccessoryCaseStringTypes(Type $type): array */ private static function downgradeNonFalsyStringToNonEmpty(Type $type): ?Type { - if (!$type instanceof IntersectionType || $type->getConstantStrings() !== []) { + if (!$type instanceof IntersectionType || $type->isNonFalsyString()->no()) { return null; } From 5783598d811830d5c5e351f08e3d2c33060d66dd Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 7 Jun 2026 10:24:37 +0200 Subject: [PATCH 7/9] simplify --- phpstan-baseline.neon | 6 ++++++ src/Type/Accessory/AccessoryDecimalIntegerStringType.php | 4 +--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 403d4ed0e11..9640c1af44e 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -777,6 +777,12 @@ parameters: count: 1 path: src/Type/Accessory/AccessoryArrayListType.php + - + rawMessage: 'Doing instanceof PHPStan\Type\Constant\ConstantStringType is error-prone and deprecated. Use Type::getConstantStrings() instead.' + identifier: phpstanApi.instanceofType + count: 1 + path: src/Type/Accessory/AccessoryDecimalIntegerStringType.php + - rawMessage: Doing instanceof PHPStan\Type\IntersectionType is error-prone and deprecated. identifier: phpstanApi.instanceofType diff --git a/src/Type/Accessory/AccessoryDecimalIntegerStringType.php b/src/Type/Accessory/AccessoryDecimalIntegerStringType.php index fa528ced8da..df5059700c5 100644 --- a/src/Type/Accessory/AccessoryDecimalIntegerStringType.php +++ b/src/Type/Accessory/AccessoryDecimalIntegerStringType.php @@ -208,9 +208,7 @@ public function tryRemove(Type $typeToRemove): ?Type return null; } - // `"0"` is the only falsy decimal integer string, so removing it - // from a (non-inverse) decimal-int-string makes it non-falsy. - if ($typeToRemove->isSuperTypeOf(new ConstantStringType('0'))->yes()) { + if ($typeToRemove instanceof ConstantStringType && $typeToRemove->getValue() === '0') { return new IntersectionType([new StringType(), $this, new AccessoryNonFalsyStringType()]); } From a9e2c094785e86bc9626c795412a78db2b91cb88 Mon Sep 17 00:00:00 2001 From: phpstan-bot Date: Sun, 7 Jun 2026 08:32:38 +0000 Subject: [PATCH 8/9] Use Type::getConstantStrings() instead of instanceof in accessory tryRemove() Co-Authored-By: Claude Opus 4.8 --- phpstan-baseline.neon | 16 ---------------- .../AccessoryDecimalIntegerStringType.php | 4 ++-- .../Accessory/AccessoryNonEmptyStringType.php | 4 ++-- .../Accessory/AccessoryNumericStringType.php | 4 ++-- 4 files changed, 6 insertions(+), 22 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 9640c1af44e..98a4b0341cc 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -777,12 +777,6 @@ parameters: count: 1 path: src/Type/Accessory/AccessoryArrayListType.php - - - rawMessage: 'Doing instanceof PHPStan\Type\Constant\ConstantStringType is error-prone and deprecated. Use Type::getConstantStrings() instead.' - identifier: phpstanApi.instanceofType - count: 1 - path: src/Type/Accessory/AccessoryDecimalIntegerStringType.php - - rawMessage: Doing instanceof PHPStan\Type\IntersectionType is error-prone and deprecated. identifier: phpstanApi.instanceofType @@ -801,11 +795,6 @@ parameters: count: 1 path: src/Type/Accessory/AccessoryLowercaseStringType.php - - - rawMessage: 'Doing instanceof PHPStan\Type\Constant\ConstantStringType is error-prone and deprecated. Use Type::getConstantStrings() instead.' - identifier: phpstanApi.instanceofType - count: 1 - path: src/Type/Accessory/AccessoryNonEmptyStringType.php - rawMessage: Doing instanceof PHPStan\Type\IntersectionType is error-prone and deprecated. @@ -819,11 +808,6 @@ parameters: count: 1 path: src/Type/Accessory/AccessoryNonFalsyStringType.php - - - rawMessage: 'Doing instanceof PHPStan\Type\Constant\ConstantStringType is error-prone and deprecated. Use Type::getConstantStrings() instead.' - identifier: phpstanApi.instanceofType - count: 1 - path: src/Type/Accessory/AccessoryNumericStringType.php - rawMessage: Doing instanceof PHPStan\Type\IntersectionType is error-prone and deprecated. diff --git a/src/Type/Accessory/AccessoryDecimalIntegerStringType.php b/src/Type/Accessory/AccessoryDecimalIntegerStringType.php index df5059700c5..24f86431b7c 100644 --- a/src/Type/Accessory/AccessoryDecimalIntegerStringType.php +++ b/src/Type/Accessory/AccessoryDecimalIntegerStringType.php @@ -16,7 +16,6 @@ use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantIntegerType; -use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\ErrorType; use PHPStan\Type\FloatType; use PHPStan\Type\GeneralizePrecision; @@ -208,7 +207,8 @@ public function tryRemove(Type $typeToRemove): ?Type return null; } - if ($typeToRemove instanceof ConstantStringType && $typeToRemove->getValue() === '0') { + $constantStrings = $typeToRemove->getConstantStrings(); + if (count($constantStrings) === 1 && $constantStrings[0]->getValue() === '0') { return new IntersectionType([new StringType(), $this, new AccessoryNonFalsyStringType()]); } diff --git a/src/Type/Accessory/AccessoryNonEmptyStringType.php b/src/Type/Accessory/AccessoryNonEmptyStringType.php index 25feb9270a5..b616212baa1 100644 --- a/src/Type/Accessory/AccessoryNonEmptyStringType.php +++ b/src/Type/Accessory/AccessoryNonEmptyStringType.php @@ -13,7 +13,6 @@ use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantIntegerType; -use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\ErrorType; use PHPStan\Type\FloatType; use PHPStan\Type\GeneralizePrecision; @@ -377,7 +376,8 @@ public function generalize(GeneralizePrecision $precision): Type public function tryRemove(Type $typeToRemove): ?Type { - if ($typeToRemove instanceof ConstantStringType && $typeToRemove->getValue() === '0') { + $constantStrings = $typeToRemove->getConstantStrings(); + if (count($constantStrings) === 1 && $constantStrings[0]->getValue() === '0') { return new AccessoryNonFalsyStringType(); } diff --git a/src/Type/Accessory/AccessoryNumericStringType.php b/src/Type/Accessory/AccessoryNumericStringType.php index 8d6065205ef..9b89e2f37be 100644 --- a/src/Type/Accessory/AccessoryNumericStringType.php +++ b/src/Type/Accessory/AccessoryNumericStringType.php @@ -14,7 +14,6 @@ use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantIntegerType; -use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\ErrorType; use PHPStan\Type\FloatType; use PHPStan\Type\GeneralizePrecision; @@ -386,7 +385,8 @@ public function generalize(GeneralizePrecision $precision): Type public function tryRemove(Type $typeToRemove): ?Type { - if ($typeToRemove instanceof ConstantStringType && $typeToRemove->getValue() === '0') { + $constantStrings = $typeToRemove->getConstantStrings(); + if (count($constantStrings) === 1 && $constantStrings[0]->getValue() === '0') { return new IntersectionType([new StringType(), $this, new AccessoryNonFalsyStringType()]); } From d323c7a1c43508bbf84bf080440d94c0c5e36499 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 7 Jun 2026 10:48:36 +0200 Subject: [PATCH 9/9] Revert "Use Type::getConstantStrings() instead of instanceof in accessory tryRemove()" This reverts commit a9e2c094785e86bc9626c795412a78db2b91cb88. --- phpstan-baseline.neon | 16 ++++++++++++++++ .../AccessoryDecimalIntegerStringType.php | 4 ++-- .../Accessory/AccessoryNonEmptyStringType.php | 4 ++-- .../Accessory/AccessoryNumericStringType.php | 4 ++-- 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 98a4b0341cc..9640c1af44e 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -777,6 +777,12 @@ parameters: count: 1 path: src/Type/Accessory/AccessoryArrayListType.php + - + rawMessage: 'Doing instanceof PHPStan\Type\Constant\ConstantStringType is error-prone and deprecated. Use Type::getConstantStrings() instead.' + identifier: phpstanApi.instanceofType + count: 1 + path: src/Type/Accessory/AccessoryDecimalIntegerStringType.php + - rawMessage: Doing instanceof PHPStan\Type\IntersectionType is error-prone and deprecated. identifier: phpstanApi.instanceofType @@ -795,6 +801,11 @@ parameters: count: 1 path: src/Type/Accessory/AccessoryLowercaseStringType.php + - + rawMessage: 'Doing instanceof PHPStan\Type\Constant\ConstantStringType is error-prone and deprecated. Use Type::getConstantStrings() instead.' + identifier: phpstanApi.instanceofType + count: 1 + path: src/Type/Accessory/AccessoryNonEmptyStringType.php - rawMessage: Doing instanceof PHPStan\Type\IntersectionType is error-prone and deprecated. @@ -808,6 +819,11 @@ parameters: count: 1 path: src/Type/Accessory/AccessoryNonFalsyStringType.php + - + rawMessage: 'Doing instanceof PHPStan\Type\Constant\ConstantStringType is error-prone and deprecated. Use Type::getConstantStrings() instead.' + identifier: phpstanApi.instanceofType + count: 1 + path: src/Type/Accessory/AccessoryNumericStringType.php - rawMessage: Doing instanceof PHPStan\Type\IntersectionType is error-prone and deprecated. diff --git a/src/Type/Accessory/AccessoryDecimalIntegerStringType.php b/src/Type/Accessory/AccessoryDecimalIntegerStringType.php index 24f86431b7c..df5059700c5 100644 --- a/src/Type/Accessory/AccessoryDecimalIntegerStringType.php +++ b/src/Type/Accessory/AccessoryDecimalIntegerStringType.php @@ -16,6 +16,7 @@ use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantIntegerType; +use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\ErrorType; use PHPStan\Type\FloatType; use PHPStan\Type\GeneralizePrecision; @@ -207,8 +208,7 @@ public function tryRemove(Type $typeToRemove): ?Type return null; } - $constantStrings = $typeToRemove->getConstantStrings(); - if (count($constantStrings) === 1 && $constantStrings[0]->getValue() === '0') { + if ($typeToRemove instanceof ConstantStringType && $typeToRemove->getValue() === '0') { return new IntersectionType([new StringType(), $this, new AccessoryNonFalsyStringType()]); } diff --git a/src/Type/Accessory/AccessoryNonEmptyStringType.php b/src/Type/Accessory/AccessoryNonEmptyStringType.php index b616212baa1..25feb9270a5 100644 --- a/src/Type/Accessory/AccessoryNonEmptyStringType.php +++ b/src/Type/Accessory/AccessoryNonEmptyStringType.php @@ -13,6 +13,7 @@ use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantIntegerType; +use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\ErrorType; use PHPStan\Type\FloatType; use PHPStan\Type\GeneralizePrecision; @@ -376,8 +377,7 @@ public function generalize(GeneralizePrecision $precision): Type public function tryRemove(Type $typeToRemove): ?Type { - $constantStrings = $typeToRemove->getConstantStrings(); - if (count($constantStrings) === 1 && $constantStrings[0]->getValue() === '0') { + if ($typeToRemove instanceof ConstantStringType && $typeToRemove->getValue() === '0') { return new AccessoryNonFalsyStringType(); } diff --git a/src/Type/Accessory/AccessoryNumericStringType.php b/src/Type/Accessory/AccessoryNumericStringType.php index 9b89e2f37be..8d6065205ef 100644 --- a/src/Type/Accessory/AccessoryNumericStringType.php +++ b/src/Type/Accessory/AccessoryNumericStringType.php @@ -14,6 +14,7 @@ use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantIntegerType; +use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\ErrorType; use PHPStan\Type\FloatType; use PHPStan\Type\GeneralizePrecision; @@ -385,8 +386,7 @@ public function generalize(GeneralizePrecision $precision): Type public function tryRemove(Type $typeToRemove): ?Type { - $constantStrings = $typeToRemove->getConstantStrings(); - if (count($constantStrings) === 1 && $constantStrings[0]->getValue() === '0') { + if ($typeToRemove instanceof ConstantStringType && $typeToRemove->getValue() === '0') { return new IntersectionType([new StringType(), $this, new AccessoryNonFalsyStringType()]); }