Skip to content

Commit 5819f3b

Browse files
Rework extension
1 parent a74b5f9 commit 5819f3b

File tree

9 files changed

+100
-99
lines changed

9 files changed

+100
-99
lines changed

build/baseline-8.0.neon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ parameters:
2626
path: ../src/Type/Php/StrSplitFunctionReturnTypeExtension.php
2727

2828
-
29-
message: "#^Strict comparison using \\=\\=\\= between list<string> and false will always evaluate to false\\.$#"
29+
message: "#^Strict comparison using \\=\\=\\= between non\\-empty\\-list\\<string\\> and false will always evaluate to false\\.$#"
3030
count: 1
3131
path: ../src/Type/Php/StrSplitFunctionReturnTypeExtension.php
3232

src/Type/Php/StrSplitFunctionReturnTypeExtension.php

Lines changed: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,17 @@
99
use PHPStan\ShouldNotHappenException;
1010
use PHPStan\TrinaryLogic;
1111
use PHPStan\Type\Accessory\AccessoryArrayListType;
12-
use PHPStan\Type\Accessory\AccessoryLowercaseStringType;
1312
use PHPStan\Type\Accessory\AccessoryNonEmptyStringType;
14-
use PHPStan\Type\Accessory\AccessoryUppercaseStringType;
1513
use PHPStan\Type\Accessory\NonEmptyArrayType;
1614
use PHPStan\Type\ArrayType;
1715
use PHPStan\Type\Constant\ConstantArrayType;
1816
use PHPStan\Type\Constant\ConstantBooleanType;
1917
use PHPStan\Type\Constant\ConstantIntegerType;
2018
use PHPStan\Type\Constant\ConstantStringType;
2119
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
20+
use PHPStan\Type\IntegerRangeType;
2221
use PHPStan\Type\IntegerType;
22+
use PHPStan\Type\NeverType;
2323
use PHPStan\Type\StringType;
2424
use PHPStan\Type\Type;
2525
use PHPStan\Type\TypeCombinator;
@@ -54,14 +54,15 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
5454

5555
if (count($functionCall->getArgs()) >= 2) {
5656
$splitLengthType = $scope->getType($functionCall->getArgs()[1]->value);
57-
if ($splitLengthType instanceof ConstantIntegerType) {
58-
$splitLength = $splitLengthType->getValue();
59-
if ($splitLength < 1) {
60-
return new ConstantBooleanType(false);
61-
}
62-
}
6357
} else {
64-
$splitLength = 1;
58+
$splitLengthType = new ConstantIntegerType(1);
59+
}
60+
61+
if ($splitLengthType instanceof ConstantIntegerType) {
62+
$splitLength = $splitLengthType->getValue();
63+
if ($splitLength < 1) {
64+
return new ConstantBooleanType(false);
65+
}
6566
}
6667

6768
$encoding = null;
@@ -70,21 +71,22 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
7071
$strings = $scope->getType($functionCall->getArgs()[2]->value)->getConstantStrings();
7172
$values = array_unique(array_map(static fn (ConstantStringType $encoding): string => $encoding->getValue(), $strings));
7273

73-
if (count($values) !== 1) {
74-
return null;
75-
}
76-
77-
$encoding = $values[0];
78-
if (!$this->isSupportedEncoding($encoding)) {
79-
return new ConstantBooleanType(false);
74+
if (count($values) === 1) {
75+
$encoding = $values[0];
76+
if (!$this->isSupportedEncoding($encoding)) {
77+
return $this->phpVersion->throwsValueErrorForInternalFunctions() ? new NeverType() : new ConstantBooleanType(false);
78+
}
8079
}
8180
} else {
8281
$encoding = mb_internal_encoding();
8382
}
8483
}
8584

8685
$stringType = $scope->getType($functionCall->getArgs()[0]->value);
87-
if (isset($splitLength)) {
86+
if (
87+
isset($splitLength)
88+
&& ($functionReflection->getName() === 'str_split' || $encoding !== null)
89+
) {
8890
$constantStrings = $stringType->getConstantStrings();
8991
if (count($constantStrings) > 0) {
9092
$results = [];
@@ -109,19 +111,26 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
109111
if ($isInputNonEmptyString || $this->phpVersion->strSplitReturnsEmptyArray()) {
110112
$valueTypes[] = new AccessoryNonEmptyStringType();
111113
}
112-
if ($stringType->isLowercaseString()->yes()) {
113-
$valueTypes[] = new AccessoryLowercaseStringType();
114-
}
115-
if ($stringType->isUppercaseString()->yes()) {
116-
$valueTypes[] = new AccessoryUppercaseStringType();
117-
}
118114
$returnValueType = TypeCombinator::intersect(new StringType(), ...$valueTypes);
119115

120116
$returnType = AccessoryArrayListType::intersectWith(TypeCombinator::intersect(new ArrayType(new IntegerType(), $returnValueType)));
117+
if (
118+
// Non-empty-string will return an array with at least an element
119+
$isInputNonEmptyString
120+
// str_split('', 1) returns [''] on old PHP version and [] on new ones
121+
|| ($functionReflection->getName() === 'str_split' && !$this->phpVersion->strSplitReturnsEmptyArray())
122+
) {
123+
$returnType = TypeCombinator::intersect($returnType, new NonEmptyArrayType());
124+
}
125+
if (
126+
// Length parameter accepts int<1, max> or throws a ValueError/return false based on PHP Version.
127+
!$this->phpVersion->throwsValueErrorForInternalFunctions()
128+
&& !IntegerRangeType::fromInterval(1, null)->isSuperTypeOf($splitLengthType)->yes()
129+
) {
130+
$returnType = TypeCombinator::union($returnType, new ConstantBooleanType(false));
131+
}
121132

122-
return $isInputNonEmptyString || ($encoding === null && !$this->phpVersion->strSplitReturnsEmptyArray())
123-
? TypeCombinator::intersect($returnType, new NonEmptyArrayType())
124-
: $returnType;
133+
return $returnType;
125134
}
126135

127136
/**

tests/PHPStan/Analyser/data/mb-str-split-php80.php

Lines changed: 17 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -29,74 +29,72 @@ public function legacyTest(): void
2929
assertType('false', $mbStrSplitConstantStringWithFailureSplitLength);
3030

3131
$mbStrSplitConstantStringWithInvalidSplitLengthType = mb_str_split('abcdef', []);
32-
assertType('non-empty-list<lowercase-string&non-empty-string>', $mbStrSplitConstantStringWithInvalidSplitLengthType);
32+
assertType('non-empty-list<non-empty-string>', $mbStrSplitConstantStringWithInvalidSplitLengthType);
3333

3434
$mbStrSplitConstantStringWithVariableStringAndConstantSplitLength = mb_str_split(doFoo() ? 'abcdef' : 'ghijkl', 1);
3535
assertType("array{'a', 'b', 'c', 'd', 'e', 'f'}|array{'g', 'h', 'i', 'j', 'k', 'l'}", $mbStrSplitConstantStringWithVariableStringAndConstantSplitLength);
3636

3737
$mbStrSplitConstantStringWithVariableStringAndVariableSplitLength = mb_str_split(doFoo() ? 'abcdef' : 'ghijkl', doFoo() ? 1 : 2);
38-
assertType('non-empty-list<lowercase-string&non-empty-string>', $mbStrSplitConstantStringWithVariableStringAndVariableSplitLength);
38+
assertType('non-empty-list<non-empty-string>', $mbStrSplitConstantStringWithVariableStringAndVariableSplitLength);
3939

4040
$mbStrSplitConstantStringWithOneSplitLengthAndValidEncoding = mb_str_split('abcdef', 1, 'UTF-8');
4141
assertType("array{'a', 'b', 'c', 'd', 'e', 'f'}", $mbStrSplitConstantStringWithOneSplitLengthAndValidEncoding);
4242

4343
$mbStrSplitConstantStringWithOneSplitLengthAndInvalidEncoding = mb_str_split('abcdef', 1, 'FAKE');
44-
assertType('false', $mbStrSplitConstantStringWithOneSplitLengthAndInvalidEncoding);
44+
assertType('*NEVER*', $mbStrSplitConstantStringWithOneSplitLengthAndInvalidEncoding);
4545

4646
$mbStrSplitConstantStringWithOneSplitLengthAndVariableEncoding = mb_str_split('abcdef', 1, doFoo());
47-
assertType('list<string>', $mbStrSplitConstantStringWithOneSplitLengthAndVariableEncoding);
47+
assertType('non-empty-list<non-empty-string>', $mbStrSplitConstantStringWithOneSplitLengthAndVariableEncoding);
4848

4949
$mbStrSplitConstantStringWithGreaterSplitLengthThanStringLengthAndValidEncoding = mb_str_split('abcdef', 999, 'UTF-8');
5050
assertType("array{'abcdef'}", $mbStrSplitConstantStringWithGreaterSplitLengthThanStringLengthAndValidEncoding);
5151

5252
$mbStrSplitConstantStringWithGreaterSplitLengthThanStringLengthAndInvalidEncoding = mb_str_split('abcdef', 999, 'FAKE');
53-
assertType('false', $mbStrSplitConstantStringWithGreaterSplitLengthThanStringLengthAndInvalidEncoding);
53+
assertType('*NEVER*', $mbStrSplitConstantStringWithGreaterSplitLengthThanStringLengthAndInvalidEncoding);
5454

5555
$mbStrSplitConstantStringWithGreaterSplitLengthThanStringLengthAndVariableEncoding = mb_str_split('abcdef', 999, doFoo());
56-
assertType('list<string>', $mbStrSplitConstantStringWithGreaterSplitLengthThanStringLengthAndVariableEncoding);
56+
assertType('non-empty-list<non-empty-string>', $mbStrSplitConstantStringWithGreaterSplitLengthThanStringLengthAndVariableEncoding);
5757

5858
$mbStrSplitConstantStringWithFailureSplitLengthAndValidEncoding = mb_str_split('abcdef', 0, 'UTF-8');
59-
assertType('false', $mbStrSplitConstantStringWithFailureSplitLengthAndValidEncoding);
59+
assertType('*NEVER*', $mbStrSplitConstantStringWithFailureSplitLengthAndValidEncoding);
6060

6161
$mbStrSplitConstantStringWithFailureSplitLengthAndInvalidEncoding = mb_str_split('abcdef', 0, 'FAKE');
62-
assertType('false', $mbStrSplitConstantStringWithFailureSplitLengthAndInvalidEncoding);
62+
assertType('*NEVER*', $mbStrSplitConstantStringWithFailureSplitLengthAndInvalidEncoding);
6363

6464
$mbStrSplitConstantStringWithFailureSplitLengthAndVariableEncoding = mb_str_split('abcdef', 0, doFoo());
65-
assertType('false', $mbStrSplitConstantStringWithFailureSplitLengthAndVariableEncoding);
65+
assertType('*NEVER*', $mbStrSplitConstantStringWithFailureSplitLengthAndVariableEncoding);
6666

6767
$mbStrSplitConstantStringWithInvalidSplitLengthTypeAndValidEncoding = mb_str_split('abcdef', [], 'UTF-8');
68-
assertType('non-empty-list<lowercase-string&non-empty-string>', $mbStrSplitConstantStringWithInvalidSplitLengthTypeAndValidEncoding);
68+
assertType('non-empty-list<non-empty-string>', $mbStrSplitConstantStringWithInvalidSplitLengthTypeAndValidEncoding);
6969

7070
$mbStrSplitConstantStringWithInvalidSplitLengthTypeAndInvalidEncoding = mb_str_split('abcdef', [], 'FAKE');
71-
assertType('false', $mbStrSplitConstantStringWithInvalidSplitLengthTypeAndInvalidEncoding);
71+
assertType('*NEVER*', $mbStrSplitConstantStringWithInvalidSplitLengthTypeAndInvalidEncoding);
7272

7373
$mbStrSplitConstantStringWithInvalidSplitLengthTypeAndVariableEncoding = mb_str_split('abcdef', [], doFoo());
74-
assertType('list<string>', $mbStrSplitConstantStringWithInvalidSplitLengthTypeAndVariableEncoding);
74+
assertType('non-empty-list<non-empty-string>', $mbStrSplitConstantStringWithInvalidSplitLengthTypeAndVariableEncoding);
7575

7676
$mbStrSplitConstantStringWithVariableStringAndConstantSplitLengthAndValidEncoding = mb_str_split(doFoo() ? 'abcdef' : 'ghijkl', 1, 'UTF-8');
7777
assertType("array{'a', 'b', 'c', 'd', 'e', 'f'}|array{'g', 'h', 'i', 'j', 'k', 'l'}", $mbStrSplitConstantStringWithVariableStringAndConstantSplitLengthAndValidEncoding);
7878

7979
$mbStrSplitConstantStringWithVariableStringAndConstantSplitLengthAndInvalidEncoding = mb_str_split(doFoo() ? 'abcdef' : 'ghijkl', 1, 'FAKE');
80-
assertType('false', $mbStrSplitConstantStringWithVariableStringAndConstantSplitLengthAndInvalidEncoding);
80+
assertType('*NEVER*', $mbStrSplitConstantStringWithVariableStringAndConstantSplitLengthAndInvalidEncoding);
8181

8282
$mbStrSplitConstantStringWithVariableStringAndConstantSplitLengthAndVariableEncoding = mb_str_split(doFoo() ? 'abcdef' : 'ghijkl', 1, doFoo());
83-
assertType('list<string>', $mbStrSplitConstantStringWithVariableStringAndConstantSplitLengthAndVariableEncoding);
83+
assertType('non-empty-list<non-empty-string>', $mbStrSplitConstantStringWithVariableStringAndConstantSplitLengthAndVariableEncoding);
8484

8585
$mbStrSplitConstantStringWithVariableStringAndVariableSplitLengthAndValidEncoding = mb_str_split(doFoo() ? 'abcdef' : 'ghijkl', doFoo() ? 1 : 2, 'UTF-8');
86-
assertType('non-empty-list<lowercase-string&non-empty-string>', $mbStrSplitConstantStringWithVariableStringAndVariableSplitLengthAndValidEncoding);
86+
assertType('non-empty-list<non-empty-string>', $mbStrSplitConstantStringWithVariableStringAndVariableSplitLengthAndValidEncoding);
8787

8888
$mbStrSplitConstantStringWithVariableStringAndVariableSplitLengthAndInvalidEncoding = mb_str_split(doFoo() ? 'abcdef' : 'ghijkl', doFoo() ? 1 : 2, 'FAKE');
89-
assertType('false', $mbStrSplitConstantStringWithVariableStringAndVariableSplitLengthAndInvalidEncoding);
89+
assertType('*NEVER*', $mbStrSplitConstantStringWithVariableStringAndVariableSplitLengthAndInvalidEncoding);
9090

9191
$mbStrSplitConstantStringWithVariableStringAndVariableSplitLengthAndVariableEncoding = mb_str_split(doFoo() ? 'abcdef' : 'ghijkl', doFoo() ? 1 : 2, doFoo());
92-
assertType('list<string>', $mbStrSplitConstantStringWithVariableStringAndVariableSplitLengthAndVariableEncoding);
92+
assertType('non-empty-list<non-empty-string>', $mbStrSplitConstantStringWithVariableStringAndVariableSplitLengthAndVariableEncoding);
9393
}
9494

9595
/**
9696
* @param non-empty-string $nonEmptyString
9797
* @param non-falsy-string $nonFalsyString
98-
* @param lowercase-string $lowercaseString
99-
* @param uppercase-string $uppercaseString
10098
*/
10199
function doFoo(
102100
string $string,
@@ -109,13 +107,9 @@ function doFoo(
109107
assertType('list<string>', mb_str_split($string));
110108
assertType('non-empty-list<non-empty-string>', mb_str_split($nonEmptyString));
111109
assertType('non-empty-list<non-empty-string>', mb_str_split($nonFalsyString));
112-
assertType('list<lowercase-string>', mb_str_split($lowercaseString));
113-
assertType('list<uppercase-string>', mb_str_split($uppercaseString));
114110

115111
assertType('list<string>', mb_str_split($string, $integer));
116112
assertType('non-empty-list<non-empty-string>', mb_str_split($nonEmptyString, $integer));
117113
assertType('non-empty-list<non-empty-string>', mb_str_split($nonFalsyString, $integer));
118-
assertType('list<lowercase-string>', mb_str_split($lowercaseString, $integer));
119-
assertType('list<uppercase-string>', mb_str_split($uppercaseString, $integer));
120114
}
121115
}

0 commit comments

Comments
 (0)