diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index bbd489a844..1149594fcd 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -1400,6 +1400,11 @@ private function resolveType(Expr $node): Type return new ErrorType(); } + if (($leftType->isString()->yes() && !$leftType->isNumericString()->yes()) + || ($rightType->isString()->yes() && !$rightType->isNumericString()->yes())) { + return new ErrorType(); + } + $leftNumberType = $leftType->toNumber(); $rightNumberType = $rightType->toNumber(); diff --git a/src/Type/Accessory/AccessoryLiteralStringType.php b/src/Type/Accessory/AccessoryLiteralStringType.php index a271bb2a07..ad874199e6 100644 --- a/src/Type/Accessory/AccessoryLiteralStringType.php +++ b/src/Type/Accessory/AccessoryLiteralStringType.php @@ -164,6 +164,11 @@ public function toArray(): Type ); } + public function isString(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + public function isNumericString(): TrinaryLogic { return TrinaryLogic::createMaybe(); diff --git a/src/Type/Accessory/AccessoryNonEmptyStringType.php b/src/Type/Accessory/AccessoryNonEmptyStringType.php index b8f50792e1..048a1d946e 100644 --- a/src/Type/Accessory/AccessoryNonEmptyStringType.php +++ b/src/Type/Accessory/AccessoryNonEmptyStringType.php @@ -160,6 +160,11 @@ public function toArray(): Type ); } + public function isString(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + public function isNumericString(): TrinaryLogic { return TrinaryLogic::createMaybe(); diff --git a/src/Type/Accessory/AccessoryNumericStringType.php b/src/Type/Accessory/AccessoryNumericStringType.php index 75d044bb7e..1a2123b833 100644 --- a/src/Type/Accessory/AccessoryNumericStringType.php +++ b/src/Type/Accessory/AccessoryNumericStringType.php @@ -156,6 +156,11 @@ public function toArray(): Type ); } + public function isString(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + public function isNumericString(): TrinaryLogic { return TrinaryLogic::createYes(); diff --git a/src/Type/Accessory/HasOffsetType.php b/src/Type/Accessory/HasOffsetType.php index edf56ef6b0..5804ded061 100644 --- a/src/Type/Accessory/HasOffsetType.php +++ b/src/Type/Accessory/HasOffsetType.php @@ -134,6 +134,11 @@ public function isArray(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function isString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + public function isNumericString(): TrinaryLogic { return TrinaryLogic::createMaybe(); diff --git a/src/Type/Accessory/NonEmptyArrayType.php b/src/Type/Accessory/NonEmptyArrayType.php index 57094d492f..9c4419d69b 100644 --- a/src/Type/Accessory/NonEmptyArrayType.php +++ b/src/Type/Accessory/NonEmptyArrayType.php @@ -139,6 +139,11 @@ public function isArray(): TrinaryLogic return TrinaryLogic::createYes(); } + public function isString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isNumericString(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/ArrayType.php b/src/Type/ArrayType.php index cf6dda2f9f..82272a1a3e 100644 --- a/src/Type/ArrayType.php +++ b/src/Type/ArrayType.php @@ -199,6 +199,11 @@ public function isArray(): TrinaryLogic return TrinaryLogic::createYes(); } + public function isString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isNumericString(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/CallableType.php b/src/Type/CallableType.php index 5b96b285bc..d13f571e2e 100644 --- a/src/Type/CallableType.php +++ b/src/Type/CallableType.php @@ -304,6 +304,11 @@ public function isArray(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function isString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + public function isNumericString(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/ClassStringType.php b/src/Type/ClassStringType.php index aa79e138dd..f37d5d3ff2 100644 --- a/src/Type/ClassStringType.php +++ b/src/Type/ClassStringType.php @@ -62,6 +62,11 @@ public function isSuperTypeOf(Type $type): TrinaryLogic return TrinaryLogic::createNo(); } + public function isString(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + public function isNumericString(): TrinaryLogic { return TrinaryLogic::createMaybe(); diff --git a/src/Type/ClosureType.php b/src/Type/ClosureType.php index f2ccf89e93..ed82d34117 100644 --- a/src/Type/ClosureType.php +++ b/src/Type/ClosureType.php @@ -381,6 +381,11 @@ public function isArray(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isNumericString(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/Constant/ConstantStringType.php b/src/Type/Constant/ConstantStringType.php index 8fdab07c8d..1407a7f66e 100644 --- a/src/Type/Constant/ConstantStringType.php +++ b/src/Type/Constant/ConstantStringType.php @@ -240,6 +240,11 @@ public function toFloat(): Type return new ConstantFloatType((float) $this->value); } + public function isString(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + public function isNumericString(): TrinaryLogic { return TrinaryLogic::createFromBoolean(is_numeric($this->getValue())); diff --git a/src/Type/FloatType.php b/src/Type/FloatType.php index 9b14721832..790752ed57 100644 --- a/src/Type/FloatType.php +++ b/src/Type/FloatType.php @@ -118,6 +118,11 @@ public function isArray(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isNumericString(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index 87c80d8b6f..90144aa5e1 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -349,6 +349,11 @@ public function isArray(): TrinaryLogic return $this->intersectResults(static fn (Type $type): TrinaryLogic => $type->isArray()); } + public function isString(): TrinaryLogic + { + return $this->intersectResults(static fn (Type $type): TrinaryLogic => $type->isString()); + } + public function isNumericString(): TrinaryLogic { return $this->intersectResults(static fn (Type $type): TrinaryLogic => $type->isNumericString()); diff --git a/src/Type/IterableType.php b/src/Type/IterableType.php index 8eeb62d74a..a9e4c7e0d1 100644 --- a/src/Type/IterableType.php +++ b/src/Type/IterableType.php @@ -228,6 +228,11 @@ public function isArray(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function isString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isNumericString(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/JustNullableTypeTrait.php b/src/Type/JustNullableTypeTrait.php index f3f2847495..e1c10120b1 100644 --- a/src/Type/JustNullableTypeTrait.php +++ b/src/Type/JustNullableTypeTrait.php @@ -58,6 +58,11 @@ public function isArray(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isNumericString(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index 7c130546a3..1213e3fc42 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -405,6 +405,11 @@ public function isArray(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function isString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + public function isNumericString(): TrinaryLogic { return TrinaryLogic::createMaybe(); diff --git a/src/Type/NeverType.php b/src/Type/NeverType.php index 0fa866a387..440de1a262 100644 --- a/src/Type/NeverType.php +++ b/src/Type/NeverType.php @@ -231,6 +231,11 @@ public function isArray(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isNumericString(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/NullType.php b/src/Type/NullType.php index 29f15f0b20..f86b990074 100644 --- a/src/Type/NullType.php +++ b/src/Type/NullType.php @@ -175,6 +175,11 @@ public function isArray(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isNumericString(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index a615e24977..671bb56205 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -779,6 +779,11 @@ public function isArray(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isNumericString(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/StaticType.php b/src/Type/StaticType.php index 6d681bd720..18599c356e 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -332,6 +332,11 @@ public function isArray(): TrinaryLogic return $this->getStaticObjectType()->isArray(); } + public function isString(): TrinaryLogic + { + return $this->getStaticObjectType()->isString(); + } + public function isNumericString(): TrinaryLogic { return $this->getStaticObjectType()->isNumericString(); diff --git a/src/Type/StrictMixedType.php b/src/Type/StrictMixedType.php index f98580057f..09e00d0bd0 100644 --- a/src/Type/StrictMixedType.php +++ b/src/Type/StrictMixedType.php @@ -151,6 +151,11 @@ public function isArray(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isNumericString(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/StringType.php b/src/Type/StringType.php index cf47790a72..8abcad2769 100644 --- a/src/Type/StringType.php +++ b/src/Type/StringType.php @@ -133,6 +133,11 @@ public function toArray(): Type ); } + public function isString(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + public function isNumericString(): TrinaryLogic { return TrinaryLogic::createMaybe(); diff --git a/src/Type/Traits/ObjectTypeTrait.php b/src/Type/Traits/ObjectTypeTrait.php index 40f6ef6a60..be20a00f5e 100644 --- a/src/Type/Traits/ObjectTypeTrait.php +++ b/src/Type/Traits/ObjectTypeTrait.php @@ -105,6 +105,11 @@ public function isArray(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isNumericString(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/Type.php b/src/Type/Type.php index 24a17d63c5..5c0b727d7c 100644 --- a/src/Type/Type.php +++ b/src/Type/Type.php @@ -98,6 +98,8 @@ public function isSmallerThan(Type $otherType): TrinaryLogic; public function isSmallerThanOrEqual(Type $otherType): TrinaryLogic; + public function isString(): TrinaryLogic; + public function isNumericString(): TrinaryLogic; public function isNonEmptyString(): TrinaryLogic; diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index d7cd5380ae..b3a95c4231 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -391,6 +391,11 @@ public function isArray(): TrinaryLogic return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->isArray()); } + public function isString(): TrinaryLogic + { + return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->isString()); + } + public function isNumericString(): TrinaryLogic { return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->isNumericString()); diff --git a/src/Type/VoidType.php b/src/Type/VoidType.php index 0c40218abe..166ca12ee4 100644 --- a/src/Type/VoidType.php +++ b/src/Type/VoidType.php @@ -100,6 +100,11 @@ public function isArray(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isNumericString(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 088619e961..3535aac6dc 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -704,6 +704,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6500.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6488.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6624.php'); } /** diff --git a/tests/PHPStan/Analyser/data/bug-6624.php b/tests/PHPStan/Analyser/data/bug-6624.php new file mode 100644 index 0000000000..bbdf3b9395 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-6624.php @@ -0,0 +1,31 @@ +