Reworking trait scope run (#952)

* try reworking trait scope run

* load configs from extension installer

* add trait type

* load configs from extension installer in PHPStan internally in Rector too

* [ci-review] Rector Rectify

Co-authored-by: GitHub Action <action@github.com>
This commit is contained in:
Tomas Votruba 2021-10-04 23:11:10 +02:00 committed by GitHub
parent 6bfe013300
commit 1f63ed3e55
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 150 additions and 124 deletions

View File

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace Rector\Comments\Tests\CommentRemover;
namespace Rector\Tests\Comments\CommentRemover;
use Iterator;
use Rector\Comments\CommentRemover;

View File

@ -11,7 +11,7 @@ use Rector\Comments\NodeTraverser\CommentRemovingNodeTraverser;
use Rector\NodeTypeResolver\Node\AttributeKey;
/**
* @see \Rector\Comments\Tests\CommentRemover\CommentRemoverTest
* @see \Rector\Tests\Comments\CommentRemover\CommentRemoverTest
*/
final class CommentRemover
{

View File

@ -161,6 +161,7 @@ final class FamilyRelationsAnalyzer
}
}
/** @var string[] $ancestorNames */
return $ancestorNames;
}

View File

@ -11,11 +11,14 @@ use PHPStan\Dependency\DependencyResolver;
use PHPStan\DependencyInjection\Container;
use PHPStan\DependencyInjection\ContainerFactory;
use PHPStan\DependencyInjection\Type\OperatorTypeSpecifyingExtensionRegistryProvider;
use PHPStan\ExtensionInstaller\GeneratedConfig;
use PHPStan\File\FileHelper;
use PHPStan\PhpDoc\TypeNodeResolver;
use PHPStan\Reflection\ReflectionProvider;
use Rector\Core\Configuration\Option;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\NodeTypeResolver\Reflection\BetterReflection\SourceLocatorProvider\DynamicSourceLocatorProvider;
use ReflectionClass;
use Symplify\PackageBuilder\Parameter\ParameterProvider;
/**
@ -36,6 +39,9 @@ final class PHPStanServicesFactory
$additionalConfigFiles[] = __DIR__ . '/../../../config/phpstan/static-reflection.neon';
$additionalConfigFiles[] = __DIR__ . '/../../../config/phpstan/better-infer.neon';
$extensionConfigFiles = $this->resolveExtensionConfigs();
$additionalConfigFiles = array_merge($additionalConfigFiles, $extensionConfigFiles);
$existingAdditionalConfigFiles = array_filter($additionalConfigFiles, 'file_exists');
$this->container = $containerFactory->create(sys_get_temp_dir(), $existingAdditionalConfigFiles, []);
@ -112,4 +118,39 @@ final class PHPStanServicesFactory
{
return $this->container->getByType(DynamicSourceLocatorProvider::class);
}
/**
* @return string[]
*/
private function resolveExtensionConfigs(): array
{
// same logic as in PHPStan for extension installed - https://github.com/phpstan/phpstan-src/blob/5956ec4f6cd09c8d7db9466ed4e7f25706f37a43/src/Command/CommandHelper.php#L195-L222
if (! class_exists('PHPStan\ExtensionInstaller\GeneratedConfig')) {
return [];
}
$reflectionClass = new ReflectionClass(GeneratedConfig::class);
$generatedConfigClassFileName = $reflectionClass->getFileName();
if ($generatedConfigClassFileName === false) {
throw new ShouldNotHappenException();
}
$generatedConfigDirectory = dirname($generatedConfigClassFileName);
$extensionConfigFiles = [];
foreach (GeneratedConfig::EXTENSIONS as $extension) {
$fileNames = $extension['extra']['includes'] ?? [];
foreach ($fileNames as $fileName) {
$configFilePath = $generatedConfigDirectory . '/' . $extension['relative_install_path'] . '/' . $fileName;
if (! file_exists($configFilePath)) {
continue;
}
$extensionConfigFiles[] = $configFilePath;
}
}
return $extensionConfigFiles;
}
}

View File

@ -57,6 +57,7 @@ final class NodeScopeAndMetadataDecorator
$nodes = $nodeTraverser->traverse($nodes);
$smartFileInfo = $file->getSmartFileInfo();
$nodes = $this->phpStanNodeScopeResolver->processNodes($nodes, $smartFileInfo);
$nodeTraverserForPreservingName = new NodeTraverser();

View File

@ -7,7 +7,6 @@ namespace Rector\NodeTypeResolver\NodeTypeResolver;
use PhpParser\Node;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Stmt\ClassLike;
use PhpParser\Node\Stmt\Trait_;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\MixedType;
@ -17,7 +16,6 @@ use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\Contract\NodeTypeResolverInterface;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\NodeTypeResolver\NodeTypeResolver;
use Rector\NodeTypeResolver\PHPStan\Collector\TraitNodeScopeCollector;
use Symfony\Contracts\Service\Attribute\Required;
/**
@ -29,7 +27,6 @@ final class PropertyFetchTypeResolver implements NodeTypeResolverInterface
public function __construct(
private NodeNameResolver $nodeNameResolver,
private TraitNodeScopeCollector $traitNodeScopeCollector,
private ReflectionProvider $reflectionProvider
) {
}
@ -61,16 +58,6 @@ final class PropertyFetchTypeResolver implements NodeTypeResolverInterface
/** @var Scope|null $scope */
$scope = $node->getAttribute(AttributeKey::SCOPE);
if (! $scope instanceof Scope) {
$classNode = $node->getAttribute(AttributeKey::CLASS_NODE);
if ($classNode instanceof Trait_) {
/** @var string $traitName */
$traitName = $classNode->getAttribute(AttributeKey::CLASS_NAME);
$scope = $this->traitNodeScopeCollector->getScopeForTrait($traitName);
}
}
if (! $scope instanceof Scope) {
$classNode = $node->getAttribute(AttributeKey::CLASS_NODE);
// fallback to class, since property fetches are not scoped by PHPStan

View File

@ -7,7 +7,6 @@ namespace Rector\NodeTypeResolver\NodeTypeResolver;
use PhpParser\Node;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Param;
use PhpParser\Node\Stmt\Trait_;
use PHPStan\Analyser\Scope;
use PHPStan\Type\MixedType;
use PHPStan\Type\Type;
@ -15,7 +14,6 @@ use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\Contract\NodeTypeResolverInterface;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\NodeTypeResolver\PHPStan\Collector\TraitNodeScopeCollector;
/**
* @see \Rector\Tests\NodeTypeResolver\PerNodeTypeResolver\VariableTypeResolver\VariableTypeResolverTest
@ -29,7 +27,6 @@ final class VariableTypeResolver implements NodeTypeResolverInterface
public function __construct(
private NodeNameResolver $nodeNameResolver,
private TraitNodeScopeCollector $traitNodeScopeCollector,
private PhpDocInfoFactory $phpDocInfoFactory
) {
}
@ -85,18 +82,6 @@ final class VariableTypeResolver implements NodeTypeResolverInterface
return $nodeScope;
}
// is node in trait
$classLike = $variable->getAttribute(AttributeKey::CLASS_NODE);
if ($classLike instanceof Trait_) {
/** @var string $traitName */
$traitName = $variable->getAttribute(AttributeKey::CLASS_NAME);
$traitNodeScope = $this->traitNodeScopeCollector->getScopeForTrait($traitName);
if ($traitNodeScope !== null) {
return $traitNodeScope;
}
}
return $this->resolveFromParentNodes($variable);
}

View File

@ -1,30 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\NodeTypeResolver\PHPStan\Collector;
use PHPStan\Analyser\Scope;
final class TraitNodeScopeCollector
{
/**
* @var array<string, Scope>
*/
private array $scopeByTraitNodeHash = [];
public function addForTrait(string $traitName, Scope $scope): void
{
// probably set from another class
if (isset($this->scopeByTraitNodeHash[$traitName])) {
return;
}
$this->scopeByTraitNodeHash[$traitName] = $scope;
}
public function getScopeForTrait(string $traitName): ?Scope
{
return $this->scopeByTraitNodeHash[$traitName] ?? null;
}
}

View File

@ -16,12 +16,12 @@ use PHPStan\AnalysedCodeException;
use PHPStan\Analyser\MutatingScope;
use PHPStan\Analyser\NodeScopeResolver;
use PHPStan\Analyser\Scope;
use PHPStan\Analyser\ScopeContext;
use PHPStan\BetterReflection\Reflection\Exception\NotAnInterfaceReflection;
use PHPStan\BetterReflection\Reflector\ClassReflector;
use PHPStan\BetterReflection\SourceLocator\Type\AggregateSourceLocator;
use PHPStan\BetterReflection\SourceLocator\Type\SourceLocator;
use PHPStan\Node\UnreachableStatementNode;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\ObjectType;
use Rector\Caching\Detector\ChangedFilesDetector;
@ -30,8 +30,8 @@ use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\PhpParser\Node\BetterNodeFinder;
use Rector\Core\StaticReflection\SourceLocator\ParentAttributeSourceLocator;
use Rector\Core\StaticReflection\SourceLocator\RenamedClassesSourceLocator;
use Rector\Core\Stubs\DummyTraitClass;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\NodeTypeResolver\PHPStan\Collector\TraitNodeScopeCollector;
use Rector\NodeTypeResolver\PHPStan\Scope\NodeVisitor\RemoveDeepChainMethodCallNodeVisitor;
use Symplify\PackageBuilder\Reflection\PrivatesAccessor;
use Symplify\SmartFileSystem\SmartFileInfo;
@ -62,7 +62,6 @@ final class PHPStanNodeScopeResolver
private ReflectionProvider $reflectionProvider,
private RemoveDeepChainMethodCallNodeVisitor $removeDeepChainMethodCallNodeVisitor,
private ScopeFactory $scopeFactory,
private TraitNodeScopeCollector $traitNodeScopeCollector,
private PrivatesAccessor $privatesAccessor,
private RenamedClassesSourceLocator $renamedClassesSourceLocator,
private ParentAttributeSourceLocator $parentAttributeSourceLocator,
@ -81,26 +80,26 @@ final class PHPStanNodeScopeResolver
$scope = $this->scopeFactory->createFromFile($smartFileInfo);
// skip chain method calls, performance issue: https://github.com/phpstan/phpstan/issues/254
$nodeCallback = function (Node $node, Scope $scope): void {
// traversing trait inside class that is using it scope (from referenced) - the trait traversed by Rector is different (directly from parsed file)
if ($scope->isInTrait()) {
// has just entereted trait, to avoid adding it for ever ynode
$parentNode = $node->getAttribute(AttributeKey::PARENT_NODE);
if ($parentNode instanceof Trait_) {
/** @var ClassReflection $classReflection */
$classReflection = $scope->getTraitReflection();
$traitName = $classReflection->getName();
$nodeCallback = function (Node $node, MutatingScope $scope) use (&$nodeCallback): void {
if ($node instanceof Trait_) {
$traitName = $this->resolveClassName($node);
$this->traitNodeScopeCollector->addForTrait($traitName, $scope);
}
$traitReflectionClass = $this->reflectionProvider->getClass($traitName);
$scopeContext = $this->createDummyClassScopeContext($scope);
$traitScope = clone $scope;
$this->privatesAccessor->setPrivateProperty($traitScope, 'context', $scopeContext);
$traitScope = $traitScope->enterTrait($traitReflectionClass);
$this->nodeScopeResolver->processStmtNodes($node, $node->stmts, $traitScope, $nodeCallback);
return;
}
// the class reflection is resolved AFTER entering to class node
// so we need to get it from the first after this one
if ($node instanceof Class_ || $node instanceof Interface_) {
/** @var Scope $scope */
/** @var MutatingScope $scope */
$scope = $this->resolveClassOrInterfaceScope($node, $scope);
}
@ -217,24 +216,25 @@ final class PHPStanNodeScopeResolver
$nodeTraverser->traverse($nodes);
}
private function resolveClassOrInterfaceScope(Class_ | Interface_ $classLike, Scope $scope): Scope
{
private function resolveClassOrInterfaceScope(
Class_ | Interface_ $classLike,
MutatingScope $mutatingScope
): Scope {
$className = $this->resolveClassName($classLike);
// is anonymous class? - not possible to enter it since PHPStan 0.12.33, see https://github.com/phpstan/phpstan-src/commit/e87fb0ec26f9c8552bbeef26a868b1e5d8185e91
if ($classLike instanceof Class_ && Strings::match($className, self::ANONYMOUS_CLASS_START_REGEX)) {
$classReflection = $this->reflectionProvider->getAnonymousClassReflection($classLike, $scope);
$classReflection = $this->reflectionProvider->getAnonymousClassReflection($classLike, $mutatingScope);
} elseif (! $this->reflectionProvider->hasClass($className)) {
return $scope;
return $mutatingScope;
} else {
$classReflection = $this->reflectionProvider->getClass($className);
}
/** @var MutatingScope $scope */
return $scope->enterClass($classReflection);
return $mutatingScope->enterClass($classReflection);
}
private function resolveClassName(Class_ | Interface_ $classLike): string
private function resolveClassName(Class_ | Interface_ | Trait_ $classLike): string
{
if (property_exists($classLike, 'namespacedName')) {
return (string) $classLike->namespacedName;
@ -293,4 +293,16 @@ final class PHPStanNodeScopeResolver
]);
$this->privatesAccessor->setPrivateProperty($classReflector, 'sourceLocator', $aggregateSourceLocator);
}
private function createDummyClassScopeContext(MutatingScope $mutatingScope): ScopeContext
{
// this has to be faked, because trait PHPStan does not traverse trait without a class
/** @var ScopeContext $scopeContext */
$scopeContext = $this->privatesAccessor->getPrivateProperty($mutatingScope, 'context');
$dummyClassReflection = $this->reflectionProvider->getClass(DummyTraitClass::class);
// faking a class reflection
return ScopeContext::create($scopeContext->getFile())
->enterClass($dummyClassReflection);
}
}

View File

@ -29,7 +29,11 @@ trait MultipleFilesChangedTrait
$fixturePath = $this->getFixtureTempDirectory() . '/' . $fixtureFileInfo->getFilename();
$this->createFixtureDir($fixturePath);
$fixtureContent = $originalContent;
if (trim($expectedContent)) {
/** @var string $expectedContent */
$trimmedExpectedContent = trim($expectedContent);
if ($trimmedExpectedContent !== '') {
$fixtureContent .= $separator . $expectedContent;
}

View File

@ -8,12 +8,3 @@ parameters:
# see https://github.com/rectorphp/rector/issues/3490#issue-634342324
featureToggles:
disableRuntimeReflectionProvider: true
includes:
- vendor/symplify/phpstan-rules/packages/symfony/config/services.neon
- vendor/rector/phpstan-rules/config/config.neon
- vendor/symplify/phpstan-extensions/config/config.neon
- vendor/symplify/phpstan-rules/config/services/services.neon
- vendor/symplify/phpstan-rules/packages/object-calisthenics/config/object-calisthenics-rules.neon
- vendor/symplify/phpstan-rules/packages/cognitive-complexity/config/cognitive-complexity-services.neon

View File

@ -184,7 +184,10 @@ parameters:
# class_exists is forbidden to enforce static reflection, but in a compiler pass we want runtime autoloading
-
message: '#Function "class_exists\(\)" cannot be used/left in the code#'
path: src/DependencyInjection/CompilerPass/VerifyRectorServiceExistsCompilerPass.php
paths:
- src/DependencyInjection/CompilerPass/VerifyRectorServiceExistsCompilerPass.php
# for config class reflection
- packages/NodeTypeResolver/DependencyInjection/PHPStanServicesFactory.php
# known values from other methods
-
@ -505,3 +508,6 @@ parameters:
- '#(.*?) but returns array<int, PhpParser\\Node\\Arg\|PhpParser\\Node\\VariadicPlaceholder\>#'
- '#Parameter "\w+" cannot have default value#'
# scope & mutating scope mish-mash
- '#Parameter \#4 \$nodeCallback of method PHPStan\\Analyser\\NodeScopeResolver\:\:processStmtNodes\(\) expects callable\(PhpParser\\Node, PHPStan\\Analyser\\Scope\)\: void, Closure\(PhpParser\\Node, PHPStan\\Analyser\\MutatingScope\)\: void given#'

View File

@ -2,14 +2,6 @@
namespace Rector\Tests\Arguments\Rector\ClassMethod\ArgumentAdderRector\Fixture;
class Value
{
public function doSomething()
{
}
}
class SomeProperty
{
public $value;

View File

@ -0,0 +1,43 @@
<?php
namespace Rector\Tests\Php74\Rector\Property\TypedPropertyRector\Fixture;
trait JsonBodyTrait
{
private $body;
public function setJsonBody(array $body): self
{
$this->body = $body;
return $this;
}
public function getJsonBody(): ?array
{
return $this->body;
}
}
?>
-----
<?php
namespace Rector\Tests\Php74\Rector\Property\TypedPropertyRector\Fixture;
trait JsonBodyTrait
{
private ?array $body = null;
public function setJsonBody(array $body): self
{
$this->body = $body;
return $this;
}
public function getJsonBody(): ?array
{
return $this->body;
}
}
?>

View File

@ -1,23 +0,0 @@
<?php
namespace Rector\Tests\Php74\Rector\Property\TypedPropertyRector\Fixture;
trait JsonBodyTrait
{
private $body;
public function setJsonBody( array $body ): self {
$this->body = $body;
return $this;
}
public function getJsonBody(): ?array {
return $this->body;
}
}
class UsingTraig
{
use JsonBodyTrait;
}

View File

@ -70,6 +70,10 @@ CODE_SAMPLE
return null;
}
if ($scope->isInTrait()) {
return null;
}
$classReflection = $scope->getClassReflection();
if (! $classReflection instanceof ClassReflection) {
return null;

View File

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Rector\Core\Stubs;
/**
* Used for faking class reflection for trait reflections
*/
final class DummyTraitClass
{
}