Skip to content

Add failing test cases #1049

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

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 62 additions & 49 deletions src/Analyser/TypeSpecifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -259,63 +259,76 @@ public function specifyTypesInCondition(
);
}

if ($context->true()) {
$type = TypeCombinator::intersect($scope->getType($expr->right), $scope->getType($expr->left));
$leftTypes = $this->create($expr->left, $type, $context, false, $scope);
$rightTypes = $this->create($expr->right, $type, $context, false, $scope);
$exprLeftType = $scope->getType($expr->left);
$exprRightType = $scope->getType($expr->right);

$identicalType = $scope->getType($expr);
if ($identicalType instanceof ConstantBooleanType && !$context->null()) {
$never = new NeverType();
$contextForTypes = $identicalType->getValue() ? $context->negate() : $context;
$leftTypes = $this->create($expr->left, $never, $contextForTypes, false, $scope);
$rightTypes = $this->create($expr->right, $never, $contextForTypes, false, $scope);
return $leftTypes->unionWith($rightTypes);
}

} elseif ($context->false()) {
$identicalType = $scope->getType($expr);
if ($identicalType instanceof ConstantBooleanType) {
$never = new NeverType();
$contextForTypes = $identicalType->getValue() ? $context->negate() : $context;
$leftTypes = $this->create($expr->left, $never, $contextForTypes, false, $scope);
$rightTypes = $this->create($expr->right, $never, $contextForTypes, false, $scope);
return $leftTypes->unionWith($rightTypes);
}
$types = null;

$exprLeftType = $scope->getType($expr->left);
$exprRightType = $scope->getType($expr->right);
if (
(
$exprLeftType instanceof ConstantType
&& !$expr->right instanceof Node\Scalar
) || $exprLeftType instanceof EnumCaseObjectType
) {
$types = $this->create(
$expr->right,
$exprLeftType,
$context,
false,
$scope,
);
}
if (
(
$exprRightType instanceof ConstantType
&& !$expr->left instanceof Node\Scalar
) || $exprRightType instanceof EnumCaseObjectType
) {
$leftType = $this->create(
$expr->left,
$exprRightType,
$context,
false,
$scope,
);
if ($types !== null) {
$types = $types->unionWith($leftType);
} else {
$types = $leftType;
}
}

$types = null;
if ($types !== null) {
return $types;
}

if (
(
$exprLeftType instanceof ConstantType
&& !$expr->right instanceof Node\Scalar
) || $exprLeftType instanceof EnumCaseObjectType
) {
$types = $this->create(
$expr->right,
$exprLeftType,
$context,
false,
$scope,
);
$furtherSpecificationPossible = static function (Expr $expr) use (&$furtherSpecificationPossible): bool {
if ($expr instanceof Expr\Variable) {
return true;
}
if (
(
$exprRightType instanceof ConstantType
&& !$expr->left instanceof Node\Scalar
) || $exprRightType instanceof EnumCaseObjectType
) {
$leftType = $this->create(
$expr->left,
$exprRightType,
$context,
false,
$scope,
);
if ($types !== null) {
$types = $types->unionWith($leftType);
} else {
$types = $leftType;
}

if ($expr instanceof ArrayDimFetch) {
return $furtherSpecificationPossible($expr->var);
}

if ($types !== null) {
return $types;
return false;
};

if ($furtherSpecificationPossible($expr->left) || $furtherSpecificationPossible($expr->right)) {
if ($context->true()) {
$type = TypeCombinator::intersect($scope->getType($expr->right), $scope->getType($expr->left));
$leftTypes = $this->create($expr->left, $type, $context, false, $scope);
$rightTypes = $this->create($expr->right, $type, $context, false, $scope);
return $leftTypes->unionWith($rightTypes);
}

return $this->create($expr->left, $exprLeftType, $context, false, $scope)->normalize($scope)
Expand Down
1 change: 0 additions & 1 deletion tests/PHPStan/Analyser/TypeSpecifierTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -554,7 +554,6 @@ public function dataCondition(): array
),
[
'$foo' => '123',
123 => '123',
],
['$foo' => '~123'],
],
Expand Down
10 changes: 10 additions & 0 deletions tests/PHPStan/Analyser/data/equal.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,14 @@ public function stdClass(\stdClass $a, \stdClass $b): void
assertType('stdClass', $b);
}

/**
* @param array{a: string, b: array{c: string|null}} $a
*/
public function arrayOffset(array $a): void
{
if (strlen($a['a']) > 0 && $a['a'] === $a['b']['c']) {
assertType('array{a: non-empty-string, b: array{c: non-empty-string}}', $a);
}
}

}
10 changes: 10 additions & 0 deletions tests/PHPStan/Analyser/data/identical.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,14 @@ public function foo(\stdClass $a, \stdClass $b): void
assertType('stdClass', $b);
}

/**
* @param array{a: string, b: array{c: string|null}} $a
*/
public function arrayOffset(array $a): void
{
if (strlen($a['a']) > 0 && $a['a'] === $a['b']['c']) {
assertType('array{a: non-empty-string, b: array{c: non-empty-string}}', $a);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,10 @@ public function testRule(): void
'Cannot access offset \'foo\' on array|int.',
443,
],
[
'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}.',
504,
],
]);
}

Expand Down
23 changes: 23 additions & 0 deletions tests/PHPStan/Rules/Arrays/data/nonexistent-offset.php
Original file line number Diff line number Diff line change
Expand Up @@ -485,3 +485,26 @@ function test($array): void {
}

}

/**
* @phpstan-type Version array{version: string, commit: string|null, pretty_version: string|null, feature_version?: string|null, feature_pretty_version?: string|null}
*/
class VersionGuesser
{
/**
* @param array $versionData
*
* @phpstan-param Version $versionData
*
* @return array
* @phpstan-return Version
*/
private function postprocess(array $versionData): array
{
if (!empty($versionData['feature_version']) && $versionData['feature_version'] === $versionData['version'] && $versionData['feature_pretty_version'] === $versionData['pretty_version']) {
unset($versionData['feature_version'], $versionData['feature_pretty_version']);
}

return $versionData;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,54 @@ public function testRule(): void
'Call to method ImpossibleMethodCall\Foo::isSame() with *NEVER* and stdClass will always evaluate to false.',
84,
],
[
'Call to method ImpossibleMethodCall\Foo::isSame() with \'foo\' and \'foo\' will always evaluate to true.',
101,
],
[
'Call to method ImpossibleMethodCall\Foo::isNotSame() with \'foo\' and \'foo\' will always evaluate to false.',
104,
],
[
'Call to method ImpossibleMethodCall\Foo::isSame() with array{} and array{} will always evaluate to true.',
113,
],
[
'Call to method ImpossibleMethodCall\Foo::isNotSame() with array{} and array{} will always evaluate to false.',
116,
],
[
'Call to method ImpossibleMethodCall\Foo::isSame() with array{1, 3} and array{1, 3} will always evaluate to true.',
119,
],
[
'Call to method ImpossibleMethodCall\Foo::isNotSame() with array{1, 3} and array{1, 3} will always evaluate to false.',
122,
],
[
'Call to method ImpossibleMethodCall\Foo::isSame() with 1 and stdClass will always evaluate to false.',
126,
],
[
'Call to method ImpossibleMethodCall\Foo::isNotSame() with 1 and stdClass will always evaluate to true.',
130,
],
[
'Call to method ImpossibleMethodCall\Foo::isSame() with \'1\' and stdClass will always evaluate to false.',
133,
],
[
'Call to method ImpossibleMethodCall\Foo::isNotSame() with \'1\' and stdClass will always evaluate to true.',
136,
],
[
'Call to method ImpossibleMethodCall\Foo::isSame() with array{\'a\', \'b\'} and array{1, 2} will always evaluate to false.',
139,
],
[
'Call to method ImpossibleMethodCall\Foo::isNotSame() with array{\'a\', \'b\'} and array{1, 2} will always evaluate to true.',
142,
],
]);
}

Expand Down
63 changes: 63 additions & 0 deletions tests/PHPStan/Rules/Comparison/data/impossible-method-call.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,74 @@ public function doDolor(\stdClass $std1, \stdClass $std2)

}
}
if ($this->isSame(self::createStdClass('a'), self::createStdClass('a'))) {

}
if ($this->isNotSame(self::createStdClass('b'), self::createStdClass('b'))) {

}
if ($this->isSame(self::returnFoo('a'), self::returnFoo('a'))) {

}
if ($this->isNotSame(self::returnFoo('b'), self::returnFoo('b'))) {

}
if ($this->isSame(self::createStdClass('a')->foo, self::createStdClass('a')->foo)) {

}
if ($this->isNotSame(self::createStdClass('b')->foo, self::createStdClass('b')->foo)) {

}
if ($this->isSame([], [])) {

}
if ($this->isNotSame([], [])) {

}
if ($this->isSame([1, 3], [1, 3])) {

}
if ($this->isNotSame([1, 3], [1, 3])) {

}
$std3 = new \stdClass();
if ($this->isSame(1, $std3)) {

}
$std4 = new \stdClass();
if ($this->isNotSame(1, $std4)) {

}
if ($this->isSame('1', new \stdClass())) {

}
if ($this->isNotSame('1', new \stdClass())) {

}
if ($this->isSame(['a', 'b'], [1, 2])) {

}
if ($this->isNotSame(['a', 'b'], [1, 2])) {

}
}

public function nullableInt(): ?int
{

}

public static function createStdClass(string $foo): \stdClass
{
return new \stdClass();
}

/**
* @return 'foo'
*/
public static function returnFoo(string $foo): string
{
return 'foo';
}

}