Skip to content

Commit 6117640

Browse files
committed
Fix more TypeSpecifier issues regarding ===
1 parent 10b295f commit 6117640

10 files changed

+274
-34
lines changed

src/Analyser/TypeSpecifier.php

+28-29
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
use PhpParser\Node\Expr\FuncCall;
1515
use PhpParser\Node\Expr\Instanceof_;
1616
use PhpParser\Node\Expr\MethodCall;
17-
use PhpParser\Node\Expr\New_;
1817
use PhpParser\Node\Expr\PropertyFetch;
1918
use PhpParser\Node\Expr\StaticCall;
2019
use PhpParser\Node\Expr\StaticPropertyFetch;
@@ -258,14 +257,7 @@ public function specifyTypesInCondition(
258257
$context,
259258
);
260259
}
261-
262-
if ($context->true()) {
263-
$type = TypeCombinator::intersect($scope->getType($expr->right), $scope->getType($expr->left));
264-
$leftTypes = $this->create($expr->left, $type, $context, false, $scope);
265-
$rightTypes = $this->create($expr->right, $type, $context, false, $scope);
266-
return $leftTypes->unionWith($rightTypes);
267-
268-
} elseif ($context->false()) {
260+
if ($context->false()) {
269261
$identicalType = $scope->getType($expr);
270262
if ($identicalType instanceof ConstantBooleanType) {
271263
$never = new NeverType();
@@ -274,18 +266,13 @@ public function specifyTypesInCondition(
274266
$rightTypes = $this->create($expr->right, $never, $contextForTypes, false, $scope);
275267
return $leftTypes->unionWith($rightTypes);
276268
}
269+
}
277270

278-
$exprLeftType = $scope->getType($expr->left);
279-
$exprRightType = $scope->getType($expr->right);
280-
281-
$types = null;
282-
283-
if (
284-
(
285-
$exprLeftType instanceof ConstantType
286-
&& !$expr->right instanceof Node\Scalar
287-
) || $exprLeftType instanceof EnumCaseObjectType
288-
) {
271+
$types = null;
272+
$exprLeftType = $scope->getType($expr->left);
273+
$exprRightType = $scope->getType($expr->right);
274+
if ($exprLeftType instanceof ConstantType || $exprLeftType instanceof EnumCaseObjectType) {
275+
if (!$expr->right instanceof Node\Scalar && !$expr->right instanceof Expr\Array_) {
289276
$types = $this->create(
290277
$expr->right,
291278
$exprLeftType,
@@ -294,12 +281,9 @@ public function specifyTypesInCondition(
294281
$scope,
295282
);
296283
}
297-
if (
298-
(
299-
$exprRightType instanceof ConstantType
300-
&& !$expr->left instanceof Node\Scalar
301-
) || $exprRightType instanceof EnumCaseObjectType
302-
) {
284+
}
285+
if ($exprRightType instanceof ConstantType || $exprRightType instanceof EnumCaseObjectType) {
286+
if ($types === null || (!$expr->left instanceof Node\Scalar && !$expr->left instanceof Expr\Array_)) {
303287
$leftType = $this->create(
304288
$expr->left,
305289
$exprRightType,
@@ -313,11 +297,26 @@ public function specifyTypesInCondition(
313297
$types = $leftType;
314298
}
315299
}
300+
}
301+
302+
if ($types !== null) {
303+
return $types;
304+
}
316305

317-
if ($types !== null) {
318-
return $types;
306+
$leftExprString = $this->printer->prettyPrintExpr($expr->left);
307+
$rightExprString = $this->printer->prettyPrintExpr($expr->right);
308+
if ($leftExprString === $rightExprString) {
309+
if (!$expr->left instanceof Expr\Variable || !$expr->right instanceof Expr\Variable) {
310+
return new SpecifiedTypes();
319311
}
312+
}
320313

314+
if ($context->true()) {
315+
$type = TypeCombinator::intersect($scope->getType($expr->right), $scope->getType($expr->left));
316+
$leftTypes = $this->create($expr->left, $type, $context, false, $scope);
317+
$rightTypes = $this->create($expr->right, $type, $context, false, $scope);
318+
return $leftTypes->unionWith($rightTypes);
319+
} elseif ($context->false()) {
321320
return $this->create($expr->left, $exprLeftType, $context, false, $scope)->normalize($scope)
322321
->intersectWith($this->create($expr->right, $exprRightType, $context, false, $scope)->normalize($scope));
323322
}
@@ -981,7 +980,7 @@ public function create(
981980
?Scope $scope = null,
982981
): SpecifiedTypes
983982
{
984-
if ($expr instanceof New_ || $expr instanceof Instanceof_ || $expr instanceof Expr\List_ || $expr instanceof VirtualNode) {
983+
if ($expr instanceof Instanceof_ || $expr instanceof Expr\List_ || $expr instanceof VirtualNode) {
985984
return new SpecifiedTypes();
986985
}
987986

tests/PHPStan/Analyser/TypeSpecifierTest.php

-1
Original file line numberDiff line numberDiff line change
@@ -554,7 +554,6 @@ public function dataCondition(): array
554554
),
555555
[
556556
'$foo' => '123',
557-
123 => '123',
558557
],
559558
['$foo' => '~123'],
560559
],

tests/PHPStan/Analyser/data/equal.php

+10
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,16 @@ public function stdClass(\stdClass $a, \stdClass $b): void
9999
assertType('stdClass', $b);
100100
}
101101

102+
/**
103+
* @param array{a: string, b: array{c: string|null}} $a
104+
*/
105+
public function arrayOffset(array $a): void
106+
{
107+
if (strlen($a['a']) > 0 && $a['a'] === $a['b']['c']) {
108+
assertType('array{a: non-empty-string, b: array{c: non-empty-string}}', $a);
109+
}
110+
}
111+
102112
}
103113

104114
class Bar

tests/PHPStan/Analyser/data/identical.php

+10
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,16 @@ public function foo(\stdClass $a, \stdClass $b): void
4141
assertType('stdClass', $b);
4242
}
4343

44+
/**
45+
* @param array{a: string, b: array{c: string|null}} $a
46+
*/
47+
public function arrayOffset(array $a): void
48+
{
49+
if (strlen($a['a']) > 0 && $a['a'] === $a['b']['c']) {
50+
assertType('array{a: non-empty-string, b: array{c: non-empty-string}}', $a);
51+
}
52+
}
53+
4454
}
4555

4656
class Bar

tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php

+4
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,10 @@ public function testRule(): void
163163
'Cannot access offset \'foo\' on array|int.',
164164
443,
165165
],
166+
[
167+
'Offset \'feature_pretty…\' does not exist on array{version: non-empty-string, commit: string|null, pretty_version: string|null, feature_version: non-empty-string, feature_pretty_version?: string|null}.',
168+
504,
169+
],
166170
]);
167171
}
168172

tests/PHPStan/Rules/Arrays/data/nonexistent-offset.php

+23
Original file line numberDiff line numberDiff line change
@@ -485,3 +485,26 @@ function test($array): void {
485485
}
486486

487487
}
488+
489+
/**
490+
* @phpstan-type Version array{version: string, commit: string|null, pretty_version: string|null, feature_version?: string|null, feature_pretty_version?: string|null}
491+
*/
492+
class VersionGuesser
493+
{
494+
/**
495+
* @param array $versionData
496+
*
497+
* @phpstan-param Version $versionData
498+
*
499+
* @return array
500+
* @phpstan-return Version
501+
*/
502+
private function postprocess(array $versionData): array
503+
{
504+
if (!empty($versionData['feature_version']) && $versionData['feature_version'] === $versionData['version'] && $versionData['feature_pretty_version'] === $versionData['pretty_version']) {
505+
unset($versionData['feature_version'], $versionData['feature_pretty_version']);
506+
}
507+
508+
return $versionData;
509+
}
510+
}

tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleEqualsTest.php

+32
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,38 @@ public function testRule(): void
6868
'Call to method ImpossibleMethodCall\Foo::isNotSame() with stdClass and stdClass will always evaluate to false.',
6969
81,
7070
],
71+
[
72+
'Call to method ImpossibleMethodCall\Foo::isSame() with \'foo\' and \'foo\' will always evaluate to true.',
73+
101,
74+
],
75+
[
76+
'Call to method ImpossibleMethodCall\Foo::isNotSame() with \'foo\' and \'foo\' will always evaluate to false.',
77+
104,
78+
],
79+
[
80+
'Call to method ImpossibleMethodCall\Foo::isSame() with array{} and array{} will always evaluate to true.',
81+
113,
82+
],
83+
[
84+
'Call to method ImpossibleMethodCall\Foo::isNotSame() with array{} and array{} will always evaluate to false.',
85+
116,
86+
],
87+
[
88+
'Call to method ImpossibleMethodCall\Foo::isSame() with \'\' and \'\' will always evaluate to true.',
89+
174,
90+
],
91+
[
92+
'Call to method ImpossibleMethodCall\Foo::isNotSame() with \'\' and \'\' will always evaluate to false.',
93+
175,
94+
],
95+
[
96+
'Call to method ImpossibleMethodCall\Foo::isSame() with 1 and 1 will always evaluate to true.',
97+
191,
98+
],
99+
[
100+
'Call to method ImpossibleMethodCall\Foo::isNotSame() with 2 and 2 will always evaluate to false.',
101+
194,
102+
],
71103
]);
72104
}
73105

tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php

+70-2
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,76 @@ public function testRule(): void
7777
81,
7878
],
7979
[
80-
'Call to method ImpossibleMethodCall\Foo::isSame() with *NEVER* and stdClass will always evaluate to false.',
81-
84,
80+
'Call to method ImpossibleMethodCall\Foo::isSame() with \'foo\' and \'foo\' will always evaluate to true.',
81+
101,
82+
],
83+
[
84+
'Call to method ImpossibleMethodCall\Foo::isNotSame() with \'foo\' and \'foo\' will always evaluate to false.',
85+
104,
86+
],
87+
[
88+
'Call to method ImpossibleMethodCall\Foo::isSame() with array{} and array{} will always evaluate to true.',
89+
113,
90+
],
91+
[
92+
'Call to method ImpossibleMethodCall\Foo::isNotSame() with array{} and array{} will always evaluate to false.',
93+
116,
94+
],
95+
[
96+
'Call to method ImpossibleMethodCall\Foo::isSame() with array{1, 3} and array{1, 3} will always evaluate to true.',
97+
119,
98+
],
99+
[
100+
'Call to method ImpossibleMethodCall\Foo::isNotSame() with array{1, 3} and array{1, 3} will always evaluate to false.',
101+
122,
102+
],
103+
[
104+
'Call to method ImpossibleMethodCall\Foo::isSame() with 1 and stdClass will always evaluate to false.',
105+
126,
106+
],
107+
[
108+
'Call to method ImpossibleMethodCall\Foo::isNotSame() with 1 and stdClass will always evaluate to true.',
109+
130,
110+
],
111+
[
112+
'Call to method ImpossibleMethodCall\Foo::isSame() with \'1\' and stdClass will always evaluate to false.',
113+
133,
114+
],
115+
[
116+
'Call to method ImpossibleMethodCall\Foo::isNotSame() with \'1\' and stdClass will always evaluate to true.',
117+
136,
118+
],
119+
[
120+
'Call to method ImpossibleMethodCall\Foo::isSame() with array{\'a\', \'b\'} and array{1, 2} will always evaluate to false.',
121+
139,
122+
],
123+
[
124+
'Call to method ImpossibleMethodCall\Foo::isNotSame() with array{\'a\', \'b\'} and array{1, 2} will always evaluate to true.',
125+
142,
126+
],
127+
[
128+
'Call to method ImpossibleMethodCall\Foo::isSame() with stdClass and \'1\' will always evaluate to false.',
129+
145,
130+
],
131+
[
132+
'Call to method ImpossibleMethodCall\Foo::isNotSame() with stdClass and \'1\' will always evaluate to true.',
133+
148,
134+
],
135+
[
136+
'Call to method ImpossibleMethodCall\Foo::isSame() with \'\' and \'\' will always evaluate to true.',
137+
174,
138+
],
139+
[
140+
'Call to method ImpossibleMethodCall\Foo::isNotSame() with \'\' and \'\' will always evaluate to false.',
141+
175,
142+
],
143+
[
144+
'Call to method ImpossibleMethodCall\Foo::isSame() with 1 and 1 will always evaluate to true.',
145+
191,
146+
],
147+
[
148+
'Call to method ImpossibleMethodCall\Foo::isNotSame() with 2 and 2 will always evaluate to false.',
149+
194,
82150
],
83151
]);
84152
}

tests/PHPStan/Rules/Comparison/data/TestMethodTypeSpecifyingExtensions.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ public function specifyTypes(
9999
$node->args[0]->value,
100100
$node->args[1]->value
101101
),
102-
TypeSpecifierContext::createTruthy()
102+
$context
103103
);
104104
}
105105

@@ -144,7 +144,7 @@ public function specifyTypes(
144144
$node->args[0]->value,
145145
$node->args[1]->value
146146
),
147-
TypeSpecifierContext::createTruthy()
147+
$context
148148
);
149149
}
150150

0 commit comments

Comments
 (0)