Skip to content

Commit

Permalink
Bleeding edge - RuntimeReflectionInstantiationRule
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Jun 17, 2022
1 parent 99ddeaf commit 5363066
Show file tree
Hide file tree
Showing 5 changed files with 262 additions and 0 deletions.
4 changes: 4 additions & 0 deletions conf/config.level0.neon
Expand Up @@ -14,6 +14,8 @@ conditionalTags:
phpstan.rules.rule: %featureToggles.consistentConstructor%
PHPStan\Rules\Api\RuntimeReflectionFunctionRule:
phpstan.rules.rule: %featureToggles.runtimeReflectionRules%
PHPStan\Rules\Api\RuntimeReflectionInstantiationRule:
phpstan.rules.rule: %featureToggles.runtimeReflectionRules%

rules:
- PHPStan\Rules\Api\ApiInstantiationRule
Expand Down Expand Up @@ -82,6 +84,8 @@ services:
class: PHPStan\Rules\Api\NodeConnectingVisitorAttributesRule
-
class: PHPStan\Rules\Api\RuntimeReflectionFunctionRule
-
class: PHPStan\Rules\Api\RuntimeReflectionInstantiationRule
-
class: PHPStan\Rules\Classes\ExistingClassInClassExtendsRule
tags:
Expand Down
25 changes: 25 additions & 0 deletions phpstan-baseline.neon
Expand Up @@ -169,6 +169,16 @@ parameters:
count: 1
path: src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php

-
message: "#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#"
count: 1
path: src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php

-
message: "#^Creating new ReflectionFunction is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#"
count: 1
path: src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php

-
message: "#^Parameter \\#2 \\$node of method PHPStan\\\\BetterReflection\\\\SourceLocator\\\\Ast\\\\Strategy\\\\NodeToReflection\\:\\:__invoke\\(\\) expects PhpParser\\\\Node\\\\Expr\\\\ArrowFunction\\|PhpParser\\\\Node\\\\Expr\\\\Closure\\|PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\Class_\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Enum_\\|PhpParser\\\\Node\\\\Stmt\\\\Function_\\|PhpParser\\\\Node\\\\Stmt\\\\Interface_\\|PhpParser\\\\Node\\\\Stmt\\\\Trait_, PhpParser\\\\Node\\\\Stmt\\\\ClassLike given\\.$#"
count: 1
Expand All @@ -189,6 +199,21 @@ parameters:
count: 1
path: src/Reflection/BetterReflection/SourceLocator/OptimizedSingleFileSourceLocator.php

-
message: "#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#"
count: 1
path: src/Reflection/BetterReflection/SourceLocator/ReflectionClassSourceLocator.php

-
message: "#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#"
count: 1
path: src/Reflection/BetterReflection/SourceLocator/RewriteClassAliasSourceLocator.php

-
message: "#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#"
count: 1
path: src/Reflection/BetterReflection/SourceLocator/SkipClassAliasSourceLocator.php

-
message: "#^Method PHPStan\\\\Reflection\\\\ClassReflection\\:\\:getCacheKey\\(\\) should return string but returns string\\|null\\.$#"
count: 1
Expand Down
98 changes: 98 additions & 0 deletions src/Rules/Api/RuntimeReflectionInstantiationRule.php
@@ -0,0 +1,98 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Api;

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use ReflectionClass;
use ReflectionClassConstant;
use ReflectionEnum;
use ReflectionEnumBackedCase;
use ReflectionExtension;
use ReflectionFiber;
use ReflectionFunction;
use ReflectionGenerator;
use ReflectionMethod;
use ReflectionObject;
use ReflectionParameter;
use ReflectionProperty;
use ReflectionZendExtension;
use function array_keys;
use function in_array;
use function sprintf;
use function strpos;

/**
* @implements Rule<Node\Expr\New_>
*/
class RuntimeReflectionInstantiationRule implements Rule
{

public function __construct(private ReflectionProvider $reflectionProvider)
{
}

public function getNodeType(): string
{
return Node\Expr\New_::class;
}

public function processNode(Node $node, Scope $scope): array
{
if (!$node->class instanceof Node\Name) {
return [];
}

$className = $scope->resolveName($node->class);
if (!$this->reflectionProvider->hasClass($className)) {
return [];
}

$classReflection = $this->reflectionProvider->getClass($className);
if (!in_array($classReflection->getName(), [
ReflectionMethod::class,
ReflectionClass::class,
ReflectionClassConstant::class,
ReflectionEnum::class,
ReflectionEnumBackedCase::class,
ReflectionZendExtension::class,
ReflectionExtension::class,
ReflectionFunction::class,
ReflectionObject::class,
ReflectionParameter::class,
ReflectionProperty::class,
ReflectionGenerator::class,
ReflectionFiber::class,
], true)) {
return [];
}

if (!$scope->isInClass()) {
return [];
}

$scopeClassReflection = $scope->getClassReflection();
$hasPhpStanInterface = false;
foreach (array_keys($scopeClassReflection->getInterfaces()) as $interfaceName) {
if (strpos($interfaceName, 'PHPStan\\') !== 0) {
continue;
}

$hasPhpStanInterface = true;
}

if (!$hasPhpStanInterface) {
return [];
}

return [
RuleErrorBuilder::message(
sprintf('Creating new %s is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine. Use objects retrieved from ReflectionProvider instead.', $classReflection->getName()),
)->build(),
];
}

}
77 changes: 77 additions & 0 deletions tests/PHPStan/Rules/Api/RuntimeReflectionInstantiationRuleTest.php
@@ -0,0 +1,77 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Api;

use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;

/**
* @extends RuleTestCase<RuntimeReflectionInstantiationRule>
*/
class RuntimeReflectionInstantiationRuleTest extends RuleTestCase
{

protected function getRule(): Rule
{
return new RuntimeReflectionInstantiationRule($this->createReflectionProvider());
}

public function testRule(): void
{
$this->analyse([__DIR__ . '/data/runtime-reflection-instantiation.php'], [
[
'Creating new ReflectionMethod is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine. Use objects retrieved from ReflectionProvider instead.',
43,
],
[
'Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine. Use objects retrieved from ReflectionProvider instead.',
44,
],
[
'Creating new ReflectionClassConstant is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine. Use objects retrieved from ReflectionProvider instead.',
45,
],
[
'Creating new ReflectionEnum is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine. Use objects retrieved from ReflectionProvider instead.',
46,
],
[
'Creating new ReflectionEnumBackedCase is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine. Use objects retrieved from ReflectionProvider instead.',
47,
],
[
'Creating new ReflectionZendExtension is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine. Use objects retrieved from ReflectionProvider instead.',
48,
],
[
'Creating new ReflectionExtension is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine. Use objects retrieved from ReflectionProvider instead.',
49,
],
[
'Creating new ReflectionFunction is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine. Use objects retrieved from ReflectionProvider instead.',
50,
],
[
'Creating new ReflectionObject is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine. Use objects retrieved from ReflectionProvider instead.',
51,
],
[
'Creating new ReflectionParameter is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine. Use objects retrieved from ReflectionProvider instead.',
52,
],
[
'Creating new ReflectionProperty is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine. Use objects retrieved from ReflectionProvider instead.',
53,
],
[
'Creating new ReflectionGenerator is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine. Use objects retrieved from ReflectionProvider instead.',
54,
],
[
'Creating new ReflectionFiber is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine. Use objects retrieved from ReflectionProvider instead.',
55,
],
]);
}

}
58 changes: 58 additions & 0 deletions tests/PHPStan/Rules/Api/data/runtime-reflection-instantiation.php
@@ -0,0 +1,58 @@
<?php

namespace RuntimeReflectionInstantiation;

use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\Type;

class Foo
{

public function doFoo(object $o): void
{
new \stdClass();
new \ReflectionMethod($o, 'foo');
}

}

class Bar implements DynamicMethodReturnTypeExtension
{

public function getClass(): string
{
// TODO: Implement getClass() method.
}

public function isMethodSupported(MethodReflection $methodReflection): bool
{
// TODO: Implement isMethodSupported() method.
}

public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type
{
// TODO: Implement getTypeFromMethodCall() method.
}

public function doFoo(object $o, \Fiber $f, \Generator $g): void
{
new \stdClass();
new \ReflectionMethod($o, 'foo');
new \ReflectionClass(\stdClass::class);
new \ReflectionClassConstant(\stdClass::class, 'foo');
new \ReflectionEnum(\stdClass::class);
new \ReflectionEnumBackedCase(\stdClass::class, 'foo');
new \ReflectionZendExtension('foo');
new \ReflectionExtension('foo');
new \ReflectionFunction('foo');
new \ReflectionObject($o);
new \ReflectionParameter('foo', 'foo');
new \ReflectionProperty(\stdClass::class, 'foo');
new \ReflectionGenerator($g);
new \ReflectionFiber($f);
}

}

0 comments on commit 5363066

Please sign in to comment.