-
Notifications
You must be signed in to change notification settings - Fork 554
Fix #9908: isset condition doesn't refine type #4962
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
Changes from all commits
ea9daa5
0e82075
db73d44
0bf28e7
cac5e07
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -920,6 +920,52 @@ | |
| return $exprType; | ||
| } | ||
|
|
||
| if ( | ||
| $issetExpr instanceof ArrayDimFetch | ||
| && $issetExpr->dim !== null | ||
| ) { | ||
| $varType = $scope->getType($issetExpr->var); | ||
| if (!$varType instanceof MixedType) { | ||
| $dimType = $scope->getType($issetExpr->dim); | ||
|
|
||
| if ($dimType instanceof ConstantIntegerType || $dimType instanceof ConstantStringType) { | ||
| $types = $varType instanceof UnionType ? $varType->getTypes() : [$varType]; | ||
| $typesToRemove = []; | ||
| foreach ($types as $innerType) { | ||
| $hasOffset = $innerType->hasOffsetValueType($dimType); | ||
| if (!$hasOffset->yes() || !$innerType->getOffsetValueType($dimType)->isNull()->no()) { | ||
|
Check warning on line 936 in src/Analyser/TypeSpecifier.php
|
||
|
Contributor
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. It miss some case with |
||
| continue; | ||
| } | ||
|
|
||
| $typesToRemove[] = $innerType; | ||
| } | ||
|
|
||
| if ($typesToRemove !== []) { | ||
| $typeToRemove = TypeCombinator::union(...$typesToRemove); | ||
| $result = $this->create( | ||
| $issetExpr->var, | ||
| $typeToRemove, | ||
| TypeSpecifierContext::createFalse(), | ||
| $scope, | ||
| )->setRootExpr($expr); | ||
|
|
||
| if ($scope->hasExpressionType($issetExpr->var)->maybe()) { | ||
| $result = $result->unionWith( | ||
| $this->create( | ||
| new IssetExpr($issetExpr->var), | ||
| new NullType(), | ||
| TypeSpecifierContext::createTruthy(), | ||
| $scope, | ||
| )->setRootExpr($expr), | ||
| ); | ||
| } | ||
|
|
||
| return $result; | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return new SpecifiedTypes(); | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| <?php declare(strict_types = 1); | ||
|
|
||
| namespace Bug10544; | ||
|
|
||
| use function PHPStan\Testing\assertType; | ||
|
|
||
| class Boo { | ||
| /** | ||
| * @param array{check1:bool,boo:string}|array{check2:bool,foo:string} $param | ||
| */ | ||
| public function foo(array $param): string { | ||
| if (isset($param['check1'])) { | ||
| assertType('array{check1: bool, boo: string}', $param); | ||
| return $param['boo']; | ||
| } | ||
| assertType('array{check2: bool, foo: string}', $param); | ||
| return $param['foo']; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| <?php declare(strict_types = 1); | ||
|
|
||
| namespace Bug12401; | ||
|
|
||
| use function PHPStan\Testing\assertType; | ||
|
|
||
| /** | ||
| * @param array{a: string, b: string}|array{c: string} $data | ||
| */ | ||
| function test(array $data): void { | ||
| if (isset($data['a'])) { | ||
| assertType('array{a: string, b: string}', $data); | ||
| } else { | ||
| assertType('array{c: string}', $data); | ||
| } | ||
|
|
||
| if (isset($data['a'])) { | ||
| assertType('array{a: string, b: string}', $data); | ||
| } elseif (isset($data['c'])) { | ||
| assertType('array{c: string}', $data); | ||
| } else { | ||
| assertType('*NEVER*', $data); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| <?php declare(strict_types = 1); | ||
|
|
||
| namespace Bug5128; | ||
|
|
||
| use function PHPStan\Testing\assertType; | ||
|
|
||
| /** | ||
| * @param array{a: string}|array{b: string} $array | ||
| */ | ||
| function a(array $array): string { | ||
| if (isset($array['a'])) { | ||
| assertType('array{a: string}', $array); | ||
| return $array['a']; | ||
| } | ||
|
|
||
| assertType('array{b: string}', $array); | ||
| return $array['b']; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| <?php declare(strict_types = 1); | ||
|
|
||
| namespace Bug8724; | ||
|
|
||
| use function PHPStan\Testing\assertType; | ||
|
|
||
| /** | ||
| * @phpstan-type NumberFilterStructure array{ | ||
| * type: 'equals'|'notEqual'|'lessThan'|'lessThanOrEqual'|'greaterThan'|'greaterThanOrEqual'|'inRange'|'blank'|'notBlank', | ||
| * } | ||
| * @phpstan-type CombinedNumberFilterStructure array{ | ||
| * operator: 'AND'|'OR' | ||
| * } | ||
| */ | ||
| class HelloWorld | ||
| { | ||
| /** @param NumberFilterStructure|CombinedNumberFilterStructure $filter */ | ||
| public function test(array $filter): void | ||
| { | ||
| if (isset($filter['operator'])) { | ||
| assertType("array{operator: 'AND'|'OR'}", $filter); | ||
| return; | ||
| } | ||
|
|
||
| assertType("array{type: 'blank'|'equals'|'greaterThan'|'greaterThanOrEqual'|'inRange'|'lessThan'|'lessThanOrEqual'|'notBlank'|'notEqual'}", $filter); | ||
| echo $filter['type']; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| <?php declare(strict_types = 1); | ||
|
|
||
| namespace Bug9908; | ||
|
|
||
| use function PHPStan\Testing\assertType; | ||
|
|
||
| class HelloWorld | ||
| { | ||
| public function test(): void | ||
| { | ||
| $a = []; | ||
| if (rand() % 2) { | ||
| $a = ['bar' => 'string']; | ||
| } | ||
|
|
||
| if (isset($a['bar'])) { | ||
| $a['bar'] = 1; | ||
| } | ||
|
|
||
| assertType("array{}|array{bar: 1}", $a); | ||
| } | ||
|
|
||
| /** | ||
| * @param array{bar?: int} $foo | ||
| */ | ||
| public function sayHello(array $foo): void | ||
| { | ||
| echo 'Hello' . print_r($foo, true); | ||
| } | ||
|
|
||
| public function test2(): void | ||
| { | ||
| $a = []; | ||
| if (rand() % 2) { | ||
| $a = ['bar' => 'string']; | ||
| } | ||
|
|
||
| if (isset($a['bar'])) { | ||
| $a['bar'] = 1; | ||
| } | ||
|
|
||
| $this->sayHello($a); | ||
| } | ||
| } |
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.
I wonder if we have to explicitely check those instanceof.
I think it could work for every dimType.
but this seems to be the same strategy than
phpstan-src/src/Analyser/TypeSpecifier.php
Line 961 in c2da2e8
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.
at the quoted
TypeSpecifierlocation we need theif ($dimType instanceof ConstantIntegerType || $dimType instanceof ConstantStringType) {because
HasOffsetTypeconstructor types