-
Notifications
You must be signed in to change notification settings - Fork 426
Commit
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Rules\Types; | ||
|
||
use PhpParser\Node; | ||
use PHPStan\Analyser\Scope; | ||
use PHPStan\Node\ClassPropertyNode; | ||
use PHPStan\Rules\Rule; | ||
use PHPStan\Rules\RuleError; | ||
use PHPStan\Rules\RuleErrorBuilder; | ||
use function array_merge; | ||
use function in_array; | ||
use function sprintf; | ||
|
||
/** | ||
* @implements Rule<Node> | ||
*/ | ||
class InvalidTypesInUnionRule implements Rule | ||
{ | ||
|
||
private const ONLY_STANDALONE_TYPES = [ | ||
'mixed', | ||
'never', | ||
'void', | ||
]; | ||
|
||
public function getNodeType(): string | ||
{ | ||
return Node::class; | ||
} | ||
|
||
public function processNode(Node $node, Scope $scope): array | ||
{ | ||
if (!$node instanceof Node\FunctionLike && !$node instanceof ClassPropertyNode) { | ||
return []; | ||
} | ||
|
||
if ($node instanceof Node\FunctionLike) { | ||
return $this->processFunctionLikeNode($node); | ||
Check failure on line 39 in src/Rules/Types/InvalidTypesInUnionRule.php GitHub Actions / PHPStan with result cache (8.2)
Check failure on line 39 in src/Rules/Types/InvalidTypesInUnionRule.php GitHub Actions / PHPStan with result cache (8.1)
Check failure on line 39 in src/Rules/Types/InvalidTypesInUnionRule.php GitHub Actions / PHPStan (8.2, ubuntu-latest)
Check failure on line 39 in src/Rules/Types/InvalidTypesInUnionRule.php GitHub Actions / PHPStan (8.1, ubuntu-latest)
Check failure on line 39 in src/Rules/Types/InvalidTypesInUnionRule.php GitHub Actions / PHPStan with result cache (8.3)
Check failure on line 39 in src/Rules/Types/InvalidTypesInUnionRule.php GitHub Actions / PHPStan (8.0, ubuntu-latest)
Check failure on line 39 in src/Rules/Types/InvalidTypesInUnionRule.php GitHub Actions / PHPStan (7.4, ubuntu-latest)
Check failure on line 39 in src/Rules/Types/InvalidTypesInUnionRule.php GitHub Actions / PHPStan (7.3, ubuntu-latest)
Check failure on line 39 in src/Rules/Types/InvalidTypesInUnionRule.php GitHub Actions / PHPStan (8.3, ubuntu-latest)
Check failure on line 39 in src/Rules/Types/InvalidTypesInUnionRule.php GitHub Actions / PHPStan (8.1, windows-latest)
Check failure on line 39 in src/Rules/Types/InvalidTypesInUnionRule.php GitHub Actions / PHPStan (8.3, windows-latest)
Check failure on line 39 in src/Rules/Types/InvalidTypesInUnionRule.php GitHub Actions / PHPStan (8.2, windows-latest)
Check failure on line 39 in src/Rules/Types/InvalidTypesInUnionRule.php GitHub Actions / PHPStan (7.2, ubuntu-latest)
Check failure on line 39 in src/Rules/Types/InvalidTypesInUnionRule.php GitHub Actions / PHPStan (7.4, windows-latest)
Check failure on line 39 in src/Rules/Types/InvalidTypesInUnionRule.php GitHub Actions / PHPStan (7.3, windows-latest)
Check failure on line 39 in src/Rules/Types/InvalidTypesInUnionRule.php GitHub Actions / PHPStan (8.0, windows-latest)
Check failure on line 39 in src/Rules/Types/InvalidTypesInUnionRule.php GitHub Actions / PHPStan (7.2, windows-latest)
|
||
} | ||
|
||
return $this->processClassPropertyNode($node); | ||
Check failure on line 42 in src/Rules/Types/InvalidTypesInUnionRule.php GitHub Actions / PHPStan with result cache (8.2)
Check failure on line 42 in src/Rules/Types/InvalidTypesInUnionRule.php GitHub Actions / PHPStan with result cache (8.1)
Check failure on line 42 in src/Rules/Types/InvalidTypesInUnionRule.php GitHub Actions / PHPStan (8.2, ubuntu-latest)
Check failure on line 42 in src/Rules/Types/InvalidTypesInUnionRule.php GitHub Actions / PHPStan (8.1, ubuntu-latest)
Check failure on line 42 in src/Rules/Types/InvalidTypesInUnionRule.php GitHub Actions / PHPStan with result cache (8.3)
Check failure on line 42 in src/Rules/Types/InvalidTypesInUnionRule.php GitHub Actions / PHPStan (8.0, ubuntu-latest)
Check failure on line 42 in src/Rules/Types/InvalidTypesInUnionRule.php GitHub Actions / PHPStan (7.4, ubuntu-latest)
Check failure on line 42 in src/Rules/Types/InvalidTypesInUnionRule.php GitHub Actions / PHPStan (7.3, ubuntu-latest)
Check failure on line 42 in src/Rules/Types/InvalidTypesInUnionRule.php GitHub Actions / PHPStan (8.3, ubuntu-latest)
Check failure on line 42 in src/Rules/Types/InvalidTypesInUnionRule.php GitHub Actions / PHPStan (8.1, windows-latest)
Check failure on line 42 in src/Rules/Types/InvalidTypesInUnionRule.php GitHub Actions / PHPStan (8.3, windows-latest)
Check failure on line 42 in src/Rules/Types/InvalidTypesInUnionRule.php GitHub Actions / PHPStan (8.2, windows-latest)
Check failure on line 42 in src/Rules/Types/InvalidTypesInUnionRule.php GitHub Actions / PHPStan (7.2, ubuntu-latest)
Check failure on line 42 in src/Rules/Types/InvalidTypesInUnionRule.php GitHub Actions / PHPStan (7.4, windows-latest)
Check failure on line 42 in src/Rules/Types/InvalidTypesInUnionRule.php GitHub Actions / PHPStan (7.3, windows-latest)
Check failure on line 42 in src/Rules/Types/InvalidTypesInUnionRule.php GitHub Actions / PHPStan (8.0, windows-latest)
Check failure on line 42 in src/Rules/Types/InvalidTypesInUnionRule.php GitHub Actions / PHPStan (7.2, windows-latest)
|
||
} | ||
|
||
/** | ||
* @return list<RuleError> | ||
*/ | ||
private function processFunctionLikeNode(Node\FunctionLike $functionLike): array | ||
{ | ||
$errors = []; | ||
|
||
foreach ($functionLike->getParams() as $param) { | ||
if (!$param->type instanceof Node\ComplexType) { | ||
continue; | ||
} | ||
|
||
$errors = array_merge($errors, $this->processComplexType($param->type)); | ||
} | ||
|
||
if ($functionLike->getReturnType() instanceof Node\ComplexType) { | ||
$errors = array_merge($errors, $this->processComplexType($functionLike->getReturnType())); | ||
} | ||
|
||
return $errors; | ||
} | ||
|
||
/** | ||
* @return list<RuleError> | ||
*/ | ||
private function processClassPropertyNode(ClassPropertyNode $classPropertyNode): array | ||
{ | ||
if (!$classPropertyNode->getNativeType() instanceof Node\ComplexType) { | ||
return []; | ||
} | ||
|
||
return $this->processComplexType($classPropertyNode->getNativeType()); | ||
} | ||
|
||
/** | ||
* @return list<RuleError> | ||
*/ | ||
private function processComplexType(Node\ComplexType $complexType): array | ||
{ | ||
if (!$complexType instanceof Node\UnionType && !$complexType instanceof Node\NullableType) { | ||
return []; | ||
} | ||
|
||
if ($complexType instanceof Node\UnionType) { | ||
foreach ($complexType->types as $type) { | ||
if ($type instanceof Node\Identifier && in_array($type->toString(), self::ONLY_STANDALONE_TYPES, true)) { | ||
return [ | ||
RuleErrorBuilder::message(sprintf('Type %s cannot be part of a union type declaration.', $type->toString())) | ||
->line($complexType->getLine()) | ||
->nonIgnorable() | ||
->build(), | ||
]; | ||
} | ||
} | ||
|
||
return []; | ||
} | ||
|
||
if ($complexType->type instanceof Node\Identifier && in_array($complexType->type->toString(), self::ONLY_STANDALONE_TYPES, true)) { | ||
return [ | ||
RuleErrorBuilder::message(sprintf('Type %s cannot be part of a nullable type declaration.', $complexType->type->toString())) | ||
->line($complexType->getLine()) | ||
->nonIgnorable() | ||
->build(), | ||
]; | ||
} | ||
|
||
return []; | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Rules\Types; | ||
|
||
use PHPStan\Rules\Rule; | ||
use PHPStan\Testing\RuleTestCase; | ||
|
||
/** | ||
* @extends RuleTestCase<InvalidTypesInUnionRule> | ||
*/ | ||
class InvalidTypesInUnionRuleTest extends RuleTestCase | ||
{ | ||
|
||
protected function getRule(): Rule | ||
{ | ||
return new InvalidTypesInUnionRule(); | ||
} | ||
|
||
public function testRuleOnUnionWithVoid(): void | ||
{ | ||
$this->analyse([__DIR__ . '/data/invalid-union-with-void.php'], [ | ||
[ | ||
'Type void cannot be part of a union type declaration.', | ||
11, | ||
], | ||
[ | ||
'Type void cannot be part of a nullable type declaration.', | ||
15, | ||
], | ||
]); | ||
} | ||
|
||
/** | ||
* @requires PHP 8.0 | ||
*/ | ||
public function testRuleOnUnionWithMixed(): void | ||
{ | ||
$this->analyse([__DIR__ . '/data/invalid-union-with-mixed.php'], [ | ||
[ | ||
'Type mixed cannot be part of a nullable type declaration.', | ||
9, | ||
], | ||
[ | ||
'Type mixed cannot be part of a union type declaration.', | ||
12, | ||
], | ||
[ | ||
'Type mixed cannot be part of a union type declaration.', | ||
16, | ||
], | ||
[ | ||
'Type mixed cannot be part of a union type declaration.', | ||
17, | ||
], | ||
[ | ||
'Type mixed cannot be part of a union type declaration.', | ||
22, | ||
], | ||
[ | ||
'Type mixed cannot be part of a nullable type declaration.', | ||
29, | ||
], | ||
[ | ||
'Type mixed cannot be part of a nullable type declaration.', | ||
29, | ||
], | ||
[ | ||
'Type mixed cannot be part of a nullable type declaration.', | ||
34, | ||
], | ||
]); | ||
} | ||
|
||
/** | ||
* @requires PHP 8.1 | ||
*/ | ||
public function testRuleOnUnionWithNever(): void | ||
{ | ||
$this->analyse([__DIR__ . '/data/invalid-union-with-never.php'], [ | ||
[ | ||
'Type never cannot be part of a nullable type declaration.', | ||
7, | ||
], | ||
[ | ||
'Type never cannot be part of a union type declaration.', | ||
16, | ||
], | ||
]); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
<?php declare(strict_types=1); | ||
|
||
namespace InvalidUnionWithMixed; | ||
|
||
class Foo | ||
{ | ||
|
||
public ?string $bar = null; | ||
public ?mixed $baz = null; | ||
|
||
public int|null $lorem = null; | ||
public mixed|string $ipsum = ''; | ||
|
||
public function dolor( | ||
string|int $sit, | ||
bool|mixed $amet | ||
): mixed|string | ||
{ | ||
return ''; | ||
} | ||
|
||
public function lorem(): mixed|string | ||
{ | ||
return ''; | ||
} | ||
|
||
} | ||
|
||
function funcWithMixed(mixed $a, ?mixed $b): ?mixed | ||
{ | ||
return $a; | ||
} | ||
|
||
static fn (int $a): ?mixed => $a; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
<?php declare(strict_types=1); | ||
|
||
namespace InvalidUnionWithNever; | ||
|
||
class Foo | ||
{ | ||
public function bar(string $a): ?never | ||
{ | ||
if ($a === null) { | ||
return null; | ||
} | ||
|
||
throw new \RuntimeException($a); | ||
} | ||
|
||
public function baz(string $b): never|string | ||
{ | ||
if ($b === '') { | ||
throw new \RuntimeException('Error.'); | ||
} | ||
|
||
return $b; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
<?php declare(strict_types=1); | ||
|
||
namespace InvalidUnionWithVoid; | ||
|
||
class Foo | ||
{ | ||
|
||
public function prepare( | ||
string $query, | ||
string ...$args | ||
): string|void | ||
{ | ||
} | ||
|
||
public function execute(string $query): ?void | ||
{ | ||
} | ||
|
||
} |