Skip to content

Commit ab48233

Browse files
Fix bitwise operators on mixed
1 parent 99001bc commit ab48233

File tree

5 files changed

+120
-6
lines changed

5 files changed

+120
-6
lines changed

src/Reflection/InitializerExprTypeResolver.php

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1008,9 +1008,21 @@ public function getBitwiseAndTypeFromTypes(Type $leftType, Type $rightType): Typ
10081008
$rightType = $this->optimizeScalarType($rightType);
10091009
}
10101010

1011-
if ($leftType->isString()->yes() && $rightType->isString()->yes()) {
1011+
if ($leftType instanceof MixedType && $rightType instanceof MixedType) {
1012+
return new BenevolentUnionType([new IntegerType(), new StringType()]);
1013+
}
1014+
1015+
$leftIsString = $leftType->isString();
1016+
$rightIsString = $rightType->isString();
1017+
if (
1018+
($leftIsString->yes() || $leftType instanceof MixedType)
1019+
&& ($rightIsString->yes() || $rightType instanceof MixedType)
1020+
) {
10121021
return new StringType();
10131022
}
1023+
if ($leftIsString->maybe() && $rightIsString->maybe()) {
1024+
return new ErrorType();
1025+
}
10141026

10151027
$leftNumberType = $leftType->toNumber();
10161028
$rightNumberType = $rightType->toNumber();
@@ -1082,9 +1094,21 @@ public function getBitwiseOrTypeFromTypes(Type $leftType, Type $rightType): Type
10821094
$rightType = $this->optimizeScalarType($rightType);
10831095
}
10841096

1085-
if ($leftType->isString()->yes() && $rightType->isString()->yes()) {
1097+
if ($leftType instanceof MixedType && $rightType instanceof MixedType) {
1098+
return new BenevolentUnionType([new IntegerType(), new StringType()]);
1099+
}
1100+
1101+
$leftIsString = $leftType->isString();
1102+
$rightIsString = $rightType->isString();
1103+
if (
1104+
($leftIsString->yes() || $leftType instanceof MixedType)
1105+
&& ($rightIsString->yes() || $rightType instanceof MixedType)
1106+
) {
10861107
return new StringType();
10871108
}
1109+
if ($leftIsString->maybe() && $rightIsString->maybe()) {
1110+
return new ErrorType();
1111+
}
10881112

10891113
if (TypeCombinator::union($leftType->toNumber(), $rightType->toNumber()) instanceof ErrorType) {
10901114
return new ErrorType();
@@ -1146,9 +1170,21 @@ public function getBitwiseXorTypeFromTypes(Type $leftType, Type $rightType): Typ
11461170
$rightType = $this->optimizeScalarType($rightType);
11471171
}
11481172

1149-
if ($leftType->isString()->yes() && $rightType->isString()->yes()) {
1173+
if ($leftType instanceof MixedType && $rightType instanceof MixedType) {
1174+
return new BenevolentUnionType([new IntegerType(), new StringType()]);
1175+
}
1176+
1177+
$leftIsString = $leftType->isString();
1178+
$rightIsString = $rightType->isString();
1179+
if (
1180+
($leftIsString->yes() || $leftType instanceof MixedType)
1181+
&& ($rightIsString->yes() || $rightType instanceof MixedType)
1182+
) {
11501183
return new StringType();
11511184
}
1185+
if ($leftIsString->maybe() && $rightIsString->maybe()) {
1186+
return new ErrorType();
1187+
}
11521188

11531189
if (TypeCombinator::union($leftType->toNumber(), $rightType->toNumber()) instanceof ErrorType) {
11541190
return new ErrorType();

tests/PHPStan/Analyser/Generator/data/gnsr.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ public function doBitwiseNot($a, int $b): void
110110
public function doBitwiseAnd($a, $b, int $c, int $d): void
111111
{
112112
assertType('int', $a & $b);
113-
assertNativeType('int', $a & $b);
113+
assertNativeType('(int|string)', $a & $b);
114114
assertType('1', 1 & 1);
115115
assertNativeType('1', 1 & 1);
116116
assertType('int', $c & $d);
@@ -125,7 +125,7 @@ public function doBitwiseAnd($a, $b, int $c, int $d): void
125125
public function doBitwiseOr($a, $b, int $c, int $d): void
126126
{
127127
assertType('int', $a | $b);
128-
assertNativeType('int', $a | $b);
128+
assertNativeType('(int|string)', $a | $b);
129129
assertType('1', 1 | 1);
130130
assertNativeType('1', 1 | 1);
131131
assertType('int', $c | $d);
@@ -140,7 +140,7 @@ public function doBitwiseOr($a, $b, int $c, int $d): void
140140
public function doBitwiseXor($a, $b, int $c, int $d): void
141141
{
142142
assertType('int', $a ^ $b);
143-
assertNativeType('int', $a ^ $b);
143+
assertNativeType('(int|string)', $a ^ $b);
144144
assertType('0', 1 ^ 1);
145145
assertNativeType('0', 1 ^ 1);
146146
assertType('int', $c ^ $d);
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
namespace Bitwise;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
/**
8+
* @param string|int $stringOrInt
9+
* @param mixed $mixed
10+
*/
11+
function test(int $int, string $string, $stringOrInt, $mixed) : void
12+
{
13+
assertType('int', $int & $int);
14+
assertType('*ERROR*', $int & $string);
15+
assertType('*ERROR*', $int & $stringOrInt);
16+
assertType('int', $int & $mixed);
17+
assertType('*ERROR*', $string & $int);
18+
assertType('string', $string & $string);
19+
assertType('*ERROR*', $string & $stringOrInt);
20+
assertType('string', $string & $mixed);
21+
assertType('*ERROR*', $stringOrInt & $int);
22+
assertType('*ERROR*', $stringOrInt & $string);
23+
assertType('*ERROR*', $stringOrInt & $stringOrInt);
24+
assertType('*ERROR*', $stringOrInt & $mixed);
25+
assertType('int', $mixed & $int);
26+
assertType('string', $mixed & $string);
27+
assertType('*ERROR*', $mixed & $stringOrInt);
28+
assertType('(int|string)', $mixed & $mixed);
29+
30+
assertType('int', $int | $int);
31+
assertType('*ERROR*', $int | $string);
32+
assertType('*ERROR*', $int | $stringOrInt);
33+
assertType('int', $int | $mixed);
34+
assertType('*ERROR*', $string | $int);
35+
assertType('string', $string | $string);
36+
assertType('*ERROR*', $string | $stringOrInt);
37+
assertType('string', $string | $mixed);
38+
assertType('*ERROR*', $stringOrInt | $int);
39+
assertType('*ERROR*', $stringOrInt | $string);
40+
assertType('*ERROR*', $stringOrInt | $stringOrInt);
41+
assertType('*ERROR*', $stringOrInt | $mixed);
42+
assertType('int', $mixed | $int);
43+
assertType('string', $mixed | $string);
44+
assertType('*ERROR*', $mixed | $stringOrInt);
45+
assertType('(int|string)', $mixed | $mixed);
46+
47+
assertType('int', $int ^ $int);
48+
assertType('*ERROR*', $int ^ $string);
49+
assertType('*ERROR*', $int ^ $stringOrInt);
50+
assertType('int', $int ^ $mixed);
51+
assertType('*ERROR*', $string ^ $int);
52+
assertType('string', $string ^ $string);
53+
assertType('*ERROR*', $string ^ $stringOrInt);
54+
assertType('string', $string ^ $mixed);
55+
assertType('*ERROR*', $stringOrInt ^ $int);
56+
assertType('*ERROR*', $stringOrInt ^ $string);
57+
assertType('*ERROR*', $stringOrInt ^ $stringOrInt);
58+
assertType('*ERROR*', $stringOrInt ^ $mixed);
59+
assertType('int', $mixed ^ $int);
60+
assertType('string', $mixed ^ $string);
61+
assertType('*ERROR*', $mixed ^ $stringOrInt);
62+
assertType('(int|string)', $mixed ^ $mixed);
63+
}

tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2404,6 +2404,11 @@ public function testBug13784(): void
24042404
$this->analyse([__DIR__ . '/data/bug-13784.php'], []);
24052405
}
24062406

2407+
public function testBug8094(): void
2408+
{
2409+
$this->analyse([__DIR__ . '/data/bug-8094.php'], []);
2410+
}
2411+
24072412
public function testBug13556(): void
24082413
{
24092414
$this->checkExplicitMixed = true;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Bug8094;
4+
5+
function mask_encode($data, $mask)
6+
{
7+
$mask = substr($mask, 0, strlen($data));
8+
$data ^= $mask;
9+
return(base64_encode($data));
10+
}

0 commit comments

Comments
 (0)