diff --git a/src/Reflection/InitializerExprTypeResolver.php b/src/Reflection/InitializerExprTypeResolver.php index 384b945d17..c76661d6d2 100644 --- a/src/Reflection/InitializerExprTypeResolver.php +++ b/src/Reflection/InitializerExprTypeResolver.php @@ -1008,9 +1008,21 @@ public function getBitwiseAndTypeFromTypes(Type $leftType, Type $rightType): Typ $rightType = $this->optimizeScalarType($rightType); } - if ($leftType->isString()->yes() && $rightType->isString()->yes()) { + if ($leftType instanceof MixedType && $rightType instanceof MixedType) { + return new BenevolentUnionType([new IntegerType(), new StringType()]); + } + + $leftIsString = $leftType->isString(); + $rightIsString = $rightType->isString(); + if ( + ($leftIsString->yes() || $leftType instanceof MixedType) + && ($rightIsString->yes() || $rightType instanceof MixedType) + ) { return new StringType(); } + if ($leftIsString->maybe() && $rightIsString->maybe()) { + return new ErrorType(); + } $leftNumberType = $leftType->toNumber(); $rightNumberType = $rightType->toNumber(); @@ -1082,9 +1094,21 @@ public function getBitwiseOrTypeFromTypes(Type $leftType, Type $rightType): Type $rightType = $this->optimizeScalarType($rightType); } - if ($leftType->isString()->yes() && $rightType->isString()->yes()) { + if ($leftType instanceof MixedType && $rightType instanceof MixedType) { + return new BenevolentUnionType([new IntegerType(), new StringType()]); + } + + $leftIsString = $leftType->isString(); + $rightIsString = $rightType->isString(); + if ( + ($leftIsString->yes() || $leftType instanceof MixedType) + && ($rightIsString->yes() || $rightType instanceof MixedType) + ) { return new StringType(); } + if ($leftIsString->maybe() && $rightIsString->maybe()) { + return new ErrorType(); + } if (TypeCombinator::union($leftType->toNumber(), $rightType->toNumber()) instanceof ErrorType) { return new ErrorType(); @@ -1146,9 +1170,21 @@ public function getBitwiseXorTypeFromTypes(Type $leftType, Type $rightType): Typ $rightType = $this->optimizeScalarType($rightType); } - if ($leftType->isString()->yes() && $rightType->isString()->yes()) { + if ($leftType instanceof MixedType && $rightType instanceof MixedType) { + return new BenevolentUnionType([new IntegerType(), new StringType()]); + } + + $leftIsString = $leftType->isString(); + $rightIsString = $rightType->isString(); + if ( + ($leftIsString->yes() || $leftType instanceof MixedType) + && ($rightIsString->yes() || $rightType instanceof MixedType) + ) { return new StringType(); } + if ($leftIsString->maybe() && $rightIsString->maybe()) { + return new ErrorType(); + } if (TypeCombinator::union($leftType->toNumber(), $rightType->toNumber()) instanceof ErrorType) { return new ErrorType(); diff --git a/tests/PHPStan/Analyser/Generator/data/gnsr.php b/tests/PHPStan/Analyser/Generator/data/gnsr.php index c6bddf33ff..a5324af90c 100644 --- a/tests/PHPStan/Analyser/Generator/data/gnsr.php +++ b/tests/PHPStan/Analyser/Generator/data/gnsr.php @@ -110,7 +110,7 @@ public function doBitwiseNot($a, int $b): void public function doBitwiseAnd($a, $b, int $c, int $d): void { assertType('int', $a & $b); - assertNativeType('int', $a & $b); + assertNativeType('(int|string)', $a & $b); assertType('1', 1 & 1); assertNativeType('1', 1 & 1); assertType('int', $c & $d); @@ -125,7 +125,7 @@ public function doBitwiseAnd($a, $b, int $c, int $d): void public function doBitwiseOr($a, $b, int $c, int $d): void { assertType('int', $a | $b); - assertNativeType('int', $a | $b); + assertNativeType('(int|string)', $a | $b); assertType('1', 1 | 1); assertNativeType('1', 1 | 1); assertType('int', $c | $d); @@ -140,7 +140,7 @@ public function doBitwiseOr($a, $b, int $c, int $d): void public function doBitwiseXor($a, $b, int $c, int $d): void { assertType('int', $a ^ $b); - assertNativeType('int', $a ^ $b); + assertNativeType('(int|string)', $a ^ $b); assertType('0', 1 ^ 1); assertNativeType('0', 1 ^ 1); assertType('int', $c ^ $d); diff --git a/tests/PHPStan/Analyser/nsrt/bitwise.php b/tests/PHPStan/Analyser/nsrt/bitwise.php new file mode 100644 index 0000000000..6acac4237c --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bitwise.php @@ -0,0 +1,63 @@ +analyse([__DIR__ . '/data/bug-13784.php'], []); } + public function testBug8094(): void + { + $this->analyse([__DIR__ . '/data/bug-8094.php'], []); + } + public function testBug13556(): void { $this->checkExplicitMixed = true; diff --git a/tests/PHPStan/Rules/Functions/data/bug-8094.php b/tests/PHPStan/Rules/Functions/data/bug-8094.php new file mode 100644 index 0000000000..620ffd0597 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-8094.php @@ -0,0 +1,10 @@ +