Skip to content

ThrowsPhpDocInheritanceRule respects methodThrowTypeDeclarations #26

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

Merged
merged 1 commit into from
Oct 1, 2018
Merged
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
5 changes: 4 additions & 1 deletion extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ services:
class: Pepakriz\PHPStanExceptionRules\CheckedExceptionService(%exceptionRules.checkedExceptions%, %exceptionRules.uncheckedExceptions%)

-
class: Pepakriz\PHPStanExceptionRules\DefaultThrowTypeExtension(%exceptionRules.methodThrowTypeDeclarations%, %exceptionRules.functionThrowTypeDeclarations%)
class: Pepakriz\PHPStanExceptionRules\DefaultThrowTypeService(%exceptionRules.methodThrowTypeDeclarations%, %exceptionRules.functionThrowTypeDeclarations%)

-
class: Pepakriz\PHPStanExceptionRules\DefaultThrowTypeExtension
tags:
- exceptionRules.dynamicMethodThrowTypeExtension
- exceptionRules.dynamicStaticMethodThrowTypeExtension
Expand Down
84 changes: 8 additions & 76 deletions src/DefaultThrowTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,75 +9,29 @@
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\VoidType;
use function array_map;
use function count;

class DefaultThrowTypeExtension implements DynamicFunctionThrowTypeExtension, DynamicMethodThrowTypeExtension, DynamicConstructorThrowTypeExtension, DynamicStaticMethodThrowTypeExtension
{

/**
* @var Type[][]
* @var DefaultThrowTypeService
*/
private $methodThrowTypes = [];
private $defaultThrowTypeService;

/**
* @var Type[]
*/
private $functionThrowTypes = [];

/**
* @param string[][][] $methodThrowTypes
* @param string[][] $functionThrowTypes
*/
public function __construct(
array $methodThrowTypes,
array $functionThrowTypes
DefaultThrowTypeService $defaultThrowTypeService
)
{
foreach ($methodThrowTypes as $className => $methods) {
foreach ($methods as $methodName => $throwTypes) {
if (count($throwTypes) === 0) {
$this->methodThrowTypes[$className][$methodName] = new VoidType();
continue;
}

$this->methodThrowTypes[$className][$methodName] = TypeCombinator::union(
...array_map(static function (string $throwType): ObjectType {
return new ObjectType($throwType);
}, $throwTypes)
);
}
}

foreach ($functionThrowTypes as $functionName => $throwTypes) {
if (count($throwTypes) === 0) {
$this->functionThrowTypes[$functionName] = new VoidType();
continue;
}

$this->functionThrowTypes[$functionName] = TypeCombinator::union(
...array_map(static function (string $throwType): ObjectType {
return new ObjectType($throwType);
}, $throwTypes)
);
}
$this->defaultThrowTypeService = $defaultThrowTypeService;
}

/**
* @throws UnsupportedFunctionException
*/
public function getThrowTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type
{
$functionName = $functionReflection->getName();
if (!isset($this->functionThrowTypes[$functionName])) {
throw new UnsupportedFunctionException();
}

return $this->functionThrowTypes[$functionName];
return $this->defaultThrowTypeService->getFunctionThrowType($functionReflection);
}

/**
Expand All @@ -86,7 +40,7 @@ public function getThrowTypeFromFunctionCall(FunctionReflection $functionReflect
*/
public function getThrowTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
{
return $this->getMethodThrowType($methodReflection);
return $this->defaultThrowTypeService->getMethodThrowType($methodReflection);
}

/**
Expand All @@ -95,37 +49,15 @@ public function getThrowTypeFromMethodCall(MethodReflection $methodReflection, M
*/
public function getThrowTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): Type
{
return $this->getMethodThrowType($methodReflection);
return $this->defaultThrowTypeService->getMethodThrowType($methodReflection);
}

/**
* @throws UnsupportedClassException
*/
public function getThrowTypeFromConstructor(MethodReflection $methodReflection, New_ $newNode, Scope $scope): Type
{
try {
return $this->getMethodThrowType($methodReflection);
} catch (UnsupportedFunctionException $e) {
throw new UnsupportedClassException();
}
}

/**
* @throws UnsupportedClassException
* @throws UnsupportedFunctionException
*/
private function getMethodThrowType(MethodReflection $methodReflection): Type
{
$className = $methodReflection->getDeclaringClass()->getName();
if (!isset($this->methodThrowTypes[$className])) {
throw new UnsupportedClassException();
}

if (!isset($this->methodThrowTypes[$className][$methodReflection->getName()])) {
throw new UnsupportedFunctionException();
}

return $this->methodThrowTypes[$className][$methodReflection->getName()];
return $this->defaultThrowTypeService->getConstructorThrowType($methodReflection);
}

}
108 changes: 108 additions & 0 deletions src/DefaultThrowTypeService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<?php declare(strict_types = 1);

namespace Pepakriz\PHPStanExceptionRules;

use PHPStan\Reflection\FunctionReflection;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\VoidType;
use function array_map;
use function count;

class DefaultThrowTypeService
{

/**
* @var Type[][]
*/
private $methodThrowTypes = [];

/**
* @var Type[]
*/
private $functionThrowTypes = [];

/**
* @param string[][][] $methodThrowTypes
* @param string[][] $functionThrowTypes
*/
public function __construct(
array $methodThrowTypes,
array $functionThrowTypes
)
{
foreach ($methodThrowTypes as $className => $methods) {
foreach ($methods as $methodName => $throwTypes) {
if (count($throwTypes) === 0) {
$this->methodThrowTypes[$className][$methodName] = new VoidType();
continue;
}

$this->methodThrowTypes[$className][$methodName] = TypeCombinator::union(
...array_map(static function (string $throwType): ObjectType {
return new ObjectType($throwType);
}, $throwTypes)
);
}
}

foreach ($functionThrowTypes as $functionName => $throwTypes) {
if (count($throwTypes) === 0) {
$this->functionThrowTypes[$functionName] = new VoidType();
continue;
}

$this->functionThrowTypes[$functionName] = TypeCombinator::union(
...array_map(static function (string $throwType): ObjectType {
return new ObjectType($throwType);
}, $throwTypes)
);
}
}

/**
* @throws UnsupportedFunctionException
*/
public function getFunctionThrowType(FunctionReflection $functionReflection): Type
{
$functionName = $functionReflection->getName();
if (!isset($this->functionThrowTypes[$functionName])) {
throw new UnsupportedFunctionException();
}

return $this->functionThrowTypes[$functionName];
}

/**
* @throws UnsupportedClassException
*/
public function getConstructorThrowType(MethodReflection $methodReflection): Type
{
try {
return $this->getMethodThrowType($methodReflection);
} catch (UnsupportedFunctionException $e) {
throw new UnsupportedClassException();
}
}

/**
* @throws UnsupportedClassException
* @throws UnsupportedFunctionException
*/
public function getMethodThrowType(MethodReflection $methodReflection): Type
{
$className = $methodReflection->getDeclaringClass()->getName();
if (!isset($this->methodThrowTypes[$className])) {
throw new UnsupportedClassException();
}

if (!isset($this->methodThrowTypes[$className][$methodReflection->getName()])) {
throw new UnsupportedFunctionException();
}

return $this->methodThrowTypes[$className][$methodReflection->getName()];
}

}
17 changes: 16 additions & 1 deletion src/Rules/ThrowsPhpDocInheritanceRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
namespace Pepakriz\PHPStanExceptionRules\Rules;

use Pepakriz\PHPStanExceptionRules\CheckedExceptionService;
use Pepakriz\PHPStanExceptionRules\DefaultThrowTypeService;
use Pepakriz\PHPStanExceptionRules\UnsupportedClassException;
use Pepakriz\PHPStanExceptionRules\UnsupportedFunctionException;
use PhpParser\Node;
use PhpParser\Node\Stmt\ClassMethod;
use PHPStan\Analyser\Scope;
Expand All @@ -29,6 +32,11 @@ class ThrowsPhpDocInheritanceRule implements Rule
*/
private $checkedExceptionService;

/**
* @var DefaultThrowTypeService
*/
private $defaultThrowTypeService;

/**
* @var FileTypeMapper
*/
Expand All @@ -41,11 +49,13 @@ class ThrowsPhpDocInheritanceRule implements Rule

public function __construct(
CheckedExceptionService $checkedExceptionService,
DefaultThrowTypeService $defaultThrowTypeService,
FileTypeMapper $fileTypeMapper,
Broker $broker
)
{
$this->checkedExceptionService = $checkedExceptionService;
$this->defaultThrowTypeService = $defaultThrowTypeService;
$this->fileTypeMapper = $fileTypeMapper;
$this->broker = $broker;
}
Expand Down Expand Up @@ -116,7 +126,12 @@ public function processNode(Node $node, Scope $scope): array
continue;
}

$parentThrowType = $methodReflection->getThrowType();
try {
$parentThrowType = $this->defaultThrowTypeService->getMethodThrowType($methodReflection);
} catch (UnsupportedClassException | UnsupportedFunctionException $e) {
$parentThrowType = $methodReflection->getThrowType();
}

if ($parentThrowType === null) {
$messages[] = sprintf(
'PHPDoc tag @throws with type %s is not compatible with parent',
Expand Down
18 changes: 18 additions & 0 deletions tests/src/Rules/ThrowsPhpDocInheritanceRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
namespace Pepakriz\PHPStanExceptionRules\Rules;

use Pepakriz\PHPStanExceptionRules\CheckedExceptionService;
use Pepakriz\PHPStanExceptionRules\DefaultThrowTypeService;
use Pepakriz\PHPStanExceptionRules\Rules\Data\InheritanceOverriding\BaseThrowsAnnotations;
use Pepakriz\PHPStanExceptionRules\Rules\Data\InheritanceOverriding\ConcreteException;
use Pepakriz\PHPStanExceptionRules\RuleTestCase;
use PHPStan\Rules\Rule;
use PHPStan\Type\FileTypeMapper;
Expand All @@ -19,6 +22,16 @@ protected function getRule(): Rule
RuntimeException::class,
]
),
new DefaultThrowTypeService([
BaseThrowsAnnotations::class => [
'foo' => [
RuntimeException::class,
],
'bar' => [
ConcreteException::class,
],
],
], []),
self::getContainer()->getByType(FileTypeMapper::class),
$this->createBroker()
);
Expand All @@ -34,4 +47,9 @@ public function testInheritanceWithInterfaces(): void
$this->analyse(__DIR__ . '/data/throws-inheritance-interfaces.php');
}

public function testInheritanceWithOverriding(): void
{
$this->analyse(__DIR__ . '/data/throws-inheritance-overriding.php');
}

}
46 changes: 46 additions & 0 deletions tests/src/Rules/data/throws-inheritance-overriding.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php declare(strict_types = 1);

namespace Pepakriz\PHPStanExceptionRules\Rules\Data\InheritanceOverriding;

use RuntimeException;

class ConcreteException extends RuntimeException {};

abstract class BaseThrowsAnnotations
{

public function foo(): void
{

}

/**
* @throws RuntimeException
*/
public function bar(): void
{

}

}

class ThrowsAnnotations extends BaseThrowsAnnotations
{

/**
* @throws RuntimeException
*/
public function foo(): void
{

}

/**
* @throws ConcreteException
*/
public function bar(): void
{

}

}