|
15 | 15 | use PHPStan\Type\BooleanType; |
16 | 16 | use PHPStan\Type\Constant\ConstantArrayType; |
17 | 17 | use PHPStan\Type\Constant\ConstantBooleanType; |
| 18 | +use PHPStan\Type\Constant\ConstantIntegerType; |
| 19 | +use PHPStan\Type\Constant\ConstantStringType; |
18 | 20 | use PHPStan\Type\FloatType; |
19 | 21 | use PHPStan\Type\IntegerType; |
20 | 22 | use PHPStan\Type\MixedType; |
@@ -107,7 +109,7 @@ public function refactor(Node $node): ?Node |
107 | 109 | return null; |
108 | 110 | } |
109 | 111 |
|
110 | | - if ($this->shouldSkipConstantArrayType($firstArgValue)) { |
| 112 | + if ($this->shouldSkipConstantArrayType($firstArgValue, $args[1]->value)) { |
111 | 113 | return null; |
112 | 114 | } |
113 | 115 |
|
@@ -161,15 +163,38 @@ private function shouldSkipLooseComparison(array $args): bool |
161 | 163 | return $args[0]->value instanceof String_ && is_numeric($args[0]->value->value); |
162 | 164 | } |
163 | 165 |
|
164 | | - private function shouldSkipConstantArrayType(Expr $expr): bool |
| 166 | + private function shouldSkipConstantArrayType(Expr $expectedExpr, Expr $actualExpr): bool |
165 | 167 | { |
166 | | - $type = $this->nodeTypeResolver->getNativeType($expr); |
| 168 | + $expectedType = $this->nodeTypeResolver->getNativeType($expectedExpr); |
167 | 169 |
|
168 | | - if (! $type instanceof ConstantArrayType) { |
| 170 | + if (! $expectedType instanceof ConstantArrayType) { |
169 | 171 | return false; |
170 | 172 | } |
171 | 173 |
|
172 | | - return $this->hasNonScalarType($type); |
| 174 | + if ($this->hasNonScalarType($expectedType)) { |
| 175 | + return true; |
| 176 | + } |
| 177 | + |
| 178 | + // assertSame() compares arrays strictly, including key order, while assertEquals() ignores it; |
| 179 | + // only safe to narrow when the actual value is a constant array with the very same keys in the same order |
| 180 | + $actualType = $this->nodeTypeResolver->getNativeType($actualExpr); |
| 181 | + if (! $actualType instanceof ConstantArrayType) { |
| 182 | + return true; |
| 183 | + } |
| 184 | + |
| 185 | + return ! $this->hasSameKeyOrder($expectedType, $actualType); |
| 186 | + } |
| 187 | + |
| 188 | + private function hasSameKeyOrder(ConstantArrayType $expectedType, ConstantArrayType $actualType): bool |
| 189 | + { |
| 190 | + $expectedKeyTypes = $expectedType->getKeyTypes(); |
| 191 | + $actualKeyTypes = $actualType->getKeyTypes(); |
| 192 | + |
| 193 | + if (count($expectedKeyTypes) !== count($actualKeyTypes)) { |
| 194 | + return false; |
| 195 | + } |
| 196 | + |
| 197 | + return array_all($expectedKeyTypes, fn (ConstantIntegerType|ConstantStringType $expectedKeyType, $position): bool => $expectedKeyType->equals($actualKeyTypes[$position])); |
173 | 198 | } |
174 | 199 |
|
175 | 200 | private function hasNonScalarType(ConstantArrayType $constantArrayType): bool |
|
0 commit comments