Skip to content

Commit 7133400

Browse files
Rework
1 parent fe427cd commit 7133400

File tree

2 files changed

+50
-27
lines changed

2 files changed

+50
-27
lines changed

src/Type/Php/ArrayChangeKeyCaseFunctionReturnTypeExtension.php

Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
use PHPStan\Reflection\FunctionReflection;
88
use PHPStan\Type\Accessory\AccessoryArrayListType;
99
use PHPStan\Type\Accessory\AccessoryLowercaseStringType;
10+
use PHPStan\Type\Accessory\AccessoryNonEmptyStringType;
11+
use PHPStan\Type\Accessory\AccessoryNonFalsyStringType;
12+
use PHPStan\Type\Accessory\AccessoryNumericStringType;
1013
use PHPStan\Type\Accessory\NonEmptyArrayType;
1114
use PHPStan\Type\ArrayType;
1215
use PHPStan\Type\Constant\ConstantArrayTypeBuilder;
@@ -58,17 +61,8 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
5861
$newConstantArrayBuilder = ConstantArrayTypeBuilder::createEmpty();
5962
foreach ($constantArray->getKeyTypes() as $i => $keyType) {
6063
$valueType = $constantArray->getOffsetValueType($keyType);
61-
if ($keyType->isString()->yes()) {
62-
if (!isset($case)) {
63-
$keyType = TypeCombinator::union(
64-
new ConstantStringType(strtolower((string) $keyType->getValue())),
65-
new ConstantStringType(strtoupper((string) $keyType->getValue())),
66-
);
67-
} elseif ($case === CASE_LOWER) {
68-
$keyType = new ConstantStringType(strtolower((string) $keyType->getValue()));
69-
} else {
70-
$keyType = new ConstantStringType(strtoupper((string) $keyType->getValue()));
71-
}
64+
if ($keyType instanceof ConstantStringType) {
65+
$keyType = $this->mapConstantString($keyType, $case);
7266
}
7367

7468
$newConstantArrayBuilder->setOffsetValueType(
@@ -88,22 +82,30 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
8882
} else {
8983
$keysType = $arrayType->getIterableKeyType();
9084

91-
$keysType = TypeTraverser::map($keysType, static function (Type $type, callable $traverse) use ($case): Type {
85+
$keysType = TypeTraverser::map($keysType, function (Type $type, callable $traverse) use ($case): Type {
9286
if ($type instanceof UnionType) {
9387
return $traverse($type);
9488
}
9589

90+
if ($type instanceof ConstantStringType) {
91+
return $this->mapConstantString($type, $case);
92+
}
93+
9694
if ($type->isString()->yes()) {
9795
if ($case === CASE_LOWER) {
9896
return TypeCombinator::intersect($type, new AccessoryLowercaseStringType());
9997
} elseif ($type->isLowercaseString()->yes()) {
100-
return TypeCombinator::intersect(
101-
new StringType(),
102-
...array_filter(
103-
TypeUtils::getAccessoryTypes($type),
104-
static fn (Type $accessory): bool => !$accessory instanceof AccessoryLowercaseStringType,
105-
),
106-
);
98+
$types = [new StringType()];
99+
if ($type->isNonFalsyString()->yes()) {
100+
$types[] = new AccessoryNonFalsyStringType();
101+
} elseif ($type->isNonEmptyString()->yes()) {
102+
$types[] = new AccessoryNonEmptyStringType();
103+
}
104+
if ($type->isNumericString()->yes()) {
105+
$types[] = new AccessoryNumericStringType();
106+
}
107+
108+
return TypeCombinator::intersect(...$types);
107109
}
108110
}
109111

@@ -123,4 +125,18 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
123125
return $newArrayType;
124126
}
125127

128+
private function mapConstantString(ConstantStringType $type, ?int $case): Type
129+
{
130+
if ($case === CASE_LOWER) {
131+
return new ConstantStringType(strtolower($type->getValue()));
132+
} elseif ($case === CASE_UPPER) {
133+
return new ConstantStringType(strtoupper($type->getValue()));
134+
} else {
135+
return TypeCombinator::union(
136+
new ConstantStringType(strtolower($type->getValue())),
137+
new ConstantStringType(strtoupper($type->getValue())),
138+
);
139+
}
140+
}
141+
126142
}

tests/PHPStan/Analyser/nsrt/array-change-key-case.php

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,16 @@
77
class HelloWorld
88
{
99
/**
10-
* @param array<string> $arr1
11-
* @param array<string, string> $arr2
12-
* @param array<string|int, string> $arr3
13-
* @param array<int, string> $arr4
14-
* @param array<lowercase-string, string> $arr5
10+
* @param array<string> $arr1
11+
* @param array<string, string> $arr2
12+
* @param array<string|int, string> $arr3
13+
* @param array<int, string> $arr4
14+
* @param array<lowercase-string, string> $arr5
1515
* @param array<lowercase-string&non-falsy-string, string> $arr6
16-
* @param array{foo: 1, bar?: 2} $arr7
17-
* @param list<string> $list
18-
* @param non-empty-array<string> $nonEmpty
16+
* @param array{foo: 1, bar?: 2} $arr7
17+
* @param array<'foo'|'bar', string> $arr8
18+
* @param list<string> $list
19+
* @param non-empty-array<string> $nonEmpty
1920
*/
2021
public function sayHello(
2122
array $arr1,
@@ -25,6 +26,7 @@ public function sayHello(
2526
array $arr5,
2627
array $arr6,
2728
array $arr7,
29+
array $arr8,
2830
array $list,
2931
array $nonEmpty,
3032
int $case
@@ -64,6 +66,11 @@ public function sayHello(
6466
assertType('array{FOO: 1, BAR?: 2}', array_change_key_case($arr7, CASE_UPPER));
6567
assertType("non-empty-array<'BAR'|'bar'|'FOO'|'foo', 1|2>", array_change_key_case($arr7, $case));
6668

69+
assertType("array<'bar'|'foo', string>", array_change_key_case($arr8));
70+
assertType("array<'bar'|'foo', string>", array_change_key_case($arr8, CASE_LOWER));
71+
assertType("array<'BAR'|'FOO', string>", array_change_key_case($arr8, CASE_UPPER));
72+
assertType("array<'BAR'|'bar'|'FOO'|'foo', string>", array_change_key_case($arr8, $case));
73+
6774
assertType('list<string>', array_change_key_case($list));
6875
assertType('list<string>', array_change_key_case($list, CASE_LOWER));
6976
assertType('list<string>', array_change_key_case($list, CASE_UPPER));

0 commit comments

Comments
 (0)