-
Notifications
You must be signed in to change notification settings - Fork 477
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve preg_split()
function ReturnType
#3757
Open
malsuke
wants to merge
18
commits into
phpstan:2.0.x
Choose a base branch
from
malsuke:feature/improve-preg-split-type
base: 2.0.x
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+190
−32
Open
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
6f8c0c0
feat improve preg_split type Extension
malsuke ca44a91
feat add test for varibles
malsuke 9c33a2a
feat add benevolent type to preg_split
malsuke 48c714d
feat new feat for flag
malsuke 5a0b989
feat improve for flag & non-empty-string
malsuke 05ac909
add test for PREG_SPLIT_DELIM_CAPTURE flag
malsuke 97ed353
add test case for nonEmptySubject
malsuke a95ed66
feat add if state for nonEmptySubject
malsuke 4031293
feat cleanup
malsuke 0a01610
feat cleanup
malsuke 043ed19
feat cleanup
malsuke 68da760
feat add is_int assertion
malsuke db052cc
feat fix test
malsuke a647277
feat fix test
malsuke b9c303a
fix cleanup
malsuke 319bcbb
fix cleanup
malsuke 9c1a389
fix cleanup
malsuke 8cb3030
fix cleanup loop
malsuke File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,17 +7,29 @@ | |
use PHPStan\Reflection\FunctionReflection; | ||
use PHPStan\TrinaryLogic; | ||
use PHPStan\Type\Accessory\AccessoryArrayListType; | ||
use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; | ||
use PHPStan\Type\Accessory\NonEmptyArrayType; | ||
use PHPStan\Type\ArrayType; | ||
use PHPStan\Type\BitwiseFlagHelper; | ||
use PHPStan\Type\Constant\ConstantArrayType; | ||
use PHPStan\Type\Constant\ConstantArrayTypeBuilder; | ||
use PHPStan\Type\Constant\ConstantBooleanType; | ||
use PHPStan\Type\Constant\ConstantIntegerType; | ||
use PHPStan\Type\Constant\ConstantStringType; | ||
use PHPStan\Type\DynamicFunctionReturnTypeExtension; | ||
use PHPStan\Type\ErrorType; | ||
use PHPStan\Type\IntegerRangeType; | ||
use PHPStan\Type\IntegerType; | ||
use PHPStan\Type\MixedType; | ||
use PHPStan\Type\StringType; | ||
use PHPStan\Type\Type; | ||
use PHPStan\Type\TypeCombinator; | ||
use PHPStan\Type\TypeUtils; | ||
use function count; | ||
use function is_array; | ||
use function is_int; | ||
use function preg_match; | ||
use function preg_split; | ||
use function strtolower; | ||
|
||
final class PregSplitDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension | ||
|
@@ -36,17 +48,132 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo | |
|
||
public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type | ||
{ | ||
$flagsArg = $functionCall->getArgs()[3] ?? null; | ||
$args = $functionCall->getArgs(); | ||
if (count($args) < 2) { | ||
return null; | ||
} | ||
$patternArg = $args[0]; | ||
$subjectArg = $args[1]; | ||
$limitArg = $args[2] ?? null; | ||
$flagArg = $args[3] ?? null; | ||
$patternType = $scope->getType($patternArg->value); | ||
$patternConstantTypes = $patternType->getConstantStrings(); | ||
$subjectType = $scope->getType($subjectArg->value); | ||
$subjectConstantTypes = $subjectType->getConstantStrings(); | ||
|
||
if ( | ||
count($patternConstantTypes) > 0 | ||
&& @preg_match($patternConstantTypes[0]->getValue(), '') === false | ||
) { | ||
return new ErrorType(); | ||
} | ||
|
||
$limits = []; | ||
if ($limitArg === null) { | ||
$limits = [-1]; | ||
} else { | ||
$limitType = $scope->getType($limitArg->value); | ||
foreach ($limitType->getConstantScalarValues() as $limit) { | ||
if (!is_int($limit)) { | ||
return new ErrorType(); | ||
} | ||
$limits[] = $limit; | ||
} | ||
} | ||
|
||
$flags = []; | ||
if ($flagArg === null) { | ||
$flags = [0]; | ||
} else { | ||
$flagType = $scope->getType($flagArg->value); | ||
foreach ($flagType->getConstantScalarValues() as $flag) { | ||
if (!is_int($flag)) { | ||
return new ErrorType(); | ||
} | ||
$flags[] = $flag; | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. By replacing it as follows, type checking within multiple Constant loops will no longer be necessary. $flags = [];
$flagType = $scope->getType($flagArg->value);
foreach ($flagType->getConstantScalarValues() as $flag) {
if (!is_int()) {
return new ErrorType();
}
$flags[] = $flag;
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. resolved 8cb3030 |
||
|
||
|
||
if (count($patternConstantTypes) === 0 || count($subjectConstantTypes) === 0) { | ||
$returnNonEmptyStrings = $flagArg !== null && $this->bitwiseFlagAnalyser->bitwiseOrContainsConstant($flagArg->value, $scope, 'PREG_SPLIT_NO_EMPTY')->yes(); | ||
if ($returnNonEmptyStrings) { | ||
$returnStringType = TypeCombinator::intersect( | ||
new StringType(), | ||
new AccessoryNonEmptyStringType(), | ||
); | ||
} else { | ||
$returnStringType = new StringType(); | ||
} | ||
|
||
if ($flagsArg !== null && $this->bitwiseFlagAnalyser->bitwiseOrContainsConstant($flagsArg->value, $scope, 'PREG_SPLIT_OFFSET_CAPTURE')->yes()) { | ||
$type = new ArrayType( | ||
new IntegerType(), | ||
new ConstantArrayType([new ConstantIntegerType(0), new ConstantIntegerType(1)], [new StringType(), IntegerRangeType::fromInterval(0, null)], [2], [], TrinaryLogic::createYes()), | ||
$capturedArrayType = new ConstantArrayType( | ||
[new ConstantIntegerType(0), new ConstantIntegerType(1)], | ||
[$returnStringType, IntegerRangeType::fromInterval(0, null)], | ||
[2], | ||
[], | ||
TrinaryLogic::createYes(), | ||
); | ||
return TypeCombinator::union(TypeCombinator::intersect($type, new AccessoryArrayListType()), new ConstantBooleanType(false)); | ||
|
||
$returnInternalValueType = $returnStringType; | ||
if ($flagArg !== null) { | ||
$flagState = $this->bitwiseFlagAnalyser->bitwiseOrContainsConstant($flagArg->value, $scope, 'PREG_SPLIT_OFFSET_CAPTURE'); | ||
if ($flagState->yes()) { | ||
$capturedArrayListType = TypeCombinator::intersect( | ||
new ArrayType(new IntegerType(), $capturedArrayType), | ||
new AccessoryArrayListType(), | ||
); | ||
|
||
if ($subjectType->isNonEmptyString()->yes()) { | ||
$capturedArrayListType = TypeCombinator::intersect($capturedArrayListType, new NonEmptyArrayType()); | ||
} | ||
|
||
return TypeUtils::toBenevolentUnion(TypeCombinator::union($capturedArrayListType, new ConstantBooleanType(false))); | ||
} | ||
if ($flagState->maybe()) { | ||
$returnInternalValueType = TypeCombinator::union(new StringType(), $capturedArrayType); | ||
} | ||
} | ||
|
||
$returnListType = TypeCombinator::intersect(new ArrayType(new MixedType(), $returnInternalValueType), new AccessoryArrayListType()); | ||
if ($subjectType->isNonEmptyString()->yes()) { | ||
$returnListType = TypeCombinator::intersect( | ||
$returnListType, | ||
new NonEmptyArrayType(), | ||
); | ||
} | ||
|
||
return TypeUtils::toBenevolentUnion(TypeCombinator::union($returnListType, new ConstantBooleanType(false))); | ||
} | ||
|
||
$resultTypes = []; | ||
foreach ($patternConstantTypes as $patternConstantType) { | ||
foreach ($subjectConstantTypes as $subjectConstantType) { | ||
foreach ($limits as $limit) { | ||
foreach ($flags as $flag) { | ||
$result = @preg_split($patternConstantType->getValue(), $subjectConstantType->getValue(), $limit, $flag); | ||
if ($result === false) { | ||
continue; | ||
} | ||
$constantArray = ConstantArrayTypeBuilder::createEmpty(); | ||
foreach ($result as $key => $value) { | ||
if (is_array($value)) { | ||
$valueConstantArray = ConstantArrayTypeBuilder::createEmpty(); | ||
$valueConstantArray->setOffsetValueType(new ConstantIntegerType(0), new ConstantStringType($value[0])); | ||
$valueConstantArray->setOffsetValueType(new ConstantIntegerType(1), new ConstantIntegerType($value[1])); | ||
$returnInternalValueType = $valueConstantArray->getArray(); | ||
} else { | ||
$returnInternalValueType = new ConstantStringType($value); | ||
} | ||
$constantArray->setOffsetValueType(new ConstantIntegerType($key), $returnInternalValueType); | ||
} | ||
|
||
$resultTypes[] = $constantArray->getArray(); | ||
} | ||
} | ||
} | ||
} | ||
|
||
return null; | ||
return TypeCombinator::union(...$resultTypes); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The other preg_ method are not using benevolent union, so I would think more consistent to not use a benevolent union here too.