Skip to content
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
4 changes: 4 additions & 0 deletions src/Analyser/DirectInternalScopeFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace PHPStan\Analyser;

use PhpParser\Node;
use PHPStan\DependencyInjection\Type\DynamicReturnTypeExtensionRegistryProvider;
use PHPStan\DependencyInjection\Type\ExpressionTypeResolverExtensionRegistryProvider;
use PHPStan\Node\Printer\ExprPrinter;
Expand All @@ -19,6 +20,7 @@ final class DirectInternalScopeFactory implements InternalScopeFactory

/**
* @param int|array{min: int, max: int}|null $configPhpVersion
* @param callable(Node $node, Scope $scope): void|null $nodeCallback
*/
public function __construct(
private ReflectionProvider $reflectionProvider,
Expand All @@ -34,6 +36,7 @@ public function __construct(
private PhpVersion $phpVersion,
private AttributeReflectionFactory $attributeReflectionFactory,
private int|array|null $configPhpVersion,
private $nodeCallback,
private ConstantResolver $constantResolver,
)
{
Expand Down Expand Up @@ -75,6 +78,7 @@ public function create(
$this->phpVersion,
$this->attributeReflectionFactory,
$this->configPhpVersion,
$this->nodeCallback,
$declareStrictTypes,
$function,
$namespace,
Expand Down
65 changes: 65 additions & 0 deletions src/Analyser/DirectInternalScopeFactoryFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php declare(strict_types = 1);

namespace PHPStan\Analyser;

use PhpParser\Node;
use PHPStan\DependencyInjection\Type\DynamicReturnTypeExtensionRegistryProvider;
use PHPStan\DependencyInjection\Type\ExpressionTypeResolverExtensionRegistryProvider;
use PHPStan\Node\Printer\ExprPrinter;
use PHPStan\Parser\Parser;
use PHPStan\Php\PhpVersion;
use PHPStan\Reflection\AttributeReflectionFactory;
use PHPStan\Reflection\InitializerExprTypeResolver;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Rules\Properties\PropertyReflectionFinder;

final class DirectInternalScopeFactoryFactory implements InternalScopeFactoryFactory
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am getting java vibes.. where is the FactoryFactoryFactory :-)

{

/**
* @param int|array{min: int, max: int}|null $configPhpVersion
*/
public function __construct(
private ReflectionProvider $reflectionProvider,
private InitializerExprTypeResolver $initializerExprTypeResolver,
private DynamicReturnTypeExtensionRegistryProvider $dynamicReturnTypeExtensionRegistryProvider,
private ExpressionTypeResolverExtensionRegistryProvider $expressionTypeResolverExtensionRegistryProvider,
private ExprPrinter $exprPrinter,
private TypeSpecifier $typeSpecifier,
private PropertyReflectionFinder $propertyReflectionFinder,
private Parser $parser,
private NodeScopeResolver $nodeScopeResolver,
private RicherScopeGetTypeHelper $richerScopeGetTypeHelper,
private PhpVersion $phpVersion,
private AttributeReflectionFactory $attributeReflectionFactory,
private int|array|null $configPhpVersion,
private ConstantResolver $constantResolver,
)
{
}

/**
* @param callable(Node $node, Scope $scope): void|null $nodeCallback
*/
public function create(?callable $nodeCallback): DirectInternalScopeFactory
{
return new DirectInternalScopeFactory(
$this->reflectionProvider,
$this->initializerExprTypeResolver,
$this->dynamicReturnTypeExtensionRegistryProvider,
$this->expressionTypeResolverExtensionRegistryProvider,
$this->exprPrinter,
$this->typeSpecifier,
$this->propertyReflectionFinder,
$this->parser,
$this->nodeScopeResolver,
$this->richerScopeGetTypeHelper,
$this->phpVersion,
$this->attributeReflectionFactory,
$this->configPhpVersion,
$nodeCallback,
$this->constantResolver,
);
}

}
5 changes: 3 additions & 2 deletions src/Analyser/FileAnalyser.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ public function analyseFile(
$parserNodes = $this->parser->parseFile($file);
$linesToIgnore = $unmatchedLineIgnores = [$file => $this->getLinesToIgnoreFromTokens($parserNodes)];
$temporaryFileErrors = [];
$nodeCallback = function (Node $node, Scope $scope) use (&$fileErrors, &$fileCollectedData, &$fileDependencies, &$usedTraitFileDependencies, &$exportedNodes, $file, $ruleRegistry, $collectorRegistry, $outerNodeCallback, $analysedFiles, &$linesToIgnore, &$unmatchedLineIgnores, &$temporaryFileErrors, $parserNodes): void {
$nodeCallback = function (Node $node, $scope) use (&$fileErrors, &$fileCollectedData, &$fileDependencies, &$usedTraitFileDependencies, &$exportedNodes, $file, $ruleRegistry, $collectorRegistry, $outerNodeCallback, $analysedFiles, &$linesToIgnore, &$unmatchedLineIgnores, &$temporaryFileErrors, $parserNodes): void {
/** @var Scope&NodeCallbackInvoker $scope */
if ($node instanceof Node\Stmt\Trait_) {
foreach (array_keys($linesToIgnore[$file] ?? []) as $lineToIgnore) {
if ($lineToIgnore < $node->getStartLine() || $lineToIgnore > $node->getEndLine()) {
Expand Down Expand Up @@ -242,7 +243,7 @@ public function analyseFile(
}
};

$scope = $this->scopeFactory->create(ScopeContext::create($file));
$scope = $this->scopeFactory->create(ScopeContext::create($file), $nodeCallback);
$nodeCallback(new FileNode($parserNodes), $scope);
$this->nodeScopeResolver->processNodes(
$parserNodes,
Expand Down
17 changes: 17 additions & 0 deletions src/Analyser/InternalScopeFactoryFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php declare(strict_types = 1);

namespace PHPStan\Analyser;

use PhpParser\Node;

interface InternalScopeFactoryFactory
{

/**
* @param callable(Node $node, Scope $scope): void $nodeCallback
*/
public function create(
?callable $nodeCallback,
): InternalScopeFactory;

}
10 changes: 8 additions & 2 deletions src/Analyser/LazyInternalScopeFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

namespace PHPStan\Analyser;

use PHPStan\DependencyInjection\AutowiredService;
use PhpParser\Node;
use PHPStan\DependencyInjection\Container;
use PHPStan\DependencyInjection\GenerateFactory;
use PHPStan\DependencyInjection\Type\DynamicReturnTypeExtensionRegistryProvider;
use PHPStan\DependencyInjection\Type\ExpressionTypeResolverExtensionRegistryProvider;
use PHPStan\Node\Printer\ExprPrinter;
Expand All @@ -15,15 +16,19 @@
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Rules\Properties\PropertyReflectionFinder;

#[AutowiredService(as: InternalScopeFactory::class)]
#[GenerateFactory(interface: InternalScopeFactoryFactory::class, resultType: LazyInternalScopeFactory::class)]
final class LazyInternalScopeFactory implements InternalScopeFactory
{

/** @var int|array{min: int, max: int}|null */
private int|array|null $phpVersion;

/**
* @param callable(Node $node, Scope $scope): void|null $nodeCallback
*/
public function __construct(
private Container $container,
private $nodeCallback,
)
{
$this->phpVersion = $this->container->getParameter('phpVersion');
Expand Down Expand Up @@ -65,6 +70,7 @@ public function create(
$this->container->getByType(PhpVersion::class),
$this->container->getByType(AttributeReflectionFactory::class),
$this->phpVersion,
$this->nodeCallback,
$declareStrictTypes,
$function,
$namespace,
Expand Down
14 changes: 13 additions & 1 deletion src/Analyser/MutatingScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@
use const PHP_INT_MAX;
use const PHP_INT_MIN;

final class MutatingScope implements Scope
final class MutatingScope implements Scope, NodeCallbackInvoker
{

private const BOOLEAN_EXPRESSION_MAX_PROCESS_DEPTH = 4;
Expand All @@ -200,6 +200,7 @@ final class MutatingScope implements Scope

/**
* @param int|array{min: int, max: int}|null $configPhpVersion
* @param callable(Node $node, Scope $scope): void|null $nodeCallback
* @param array<string, ExpressionTypeHolder> $expressionTypes
* @param array<string, ConditionalExpressionHolder[]> $conditionalExpressions
* @param list<string> $inClosureBindScopeClasses
Expand All @@ -225,6 +226,7 @@ public function __construct(
private PhpVersion $phpVersion,
private AttributeReflectionFactory $attributeReflectionFactory,
private int|array|null $configPhpVersion,
private $nodeCallback = null,
private bool $declareStrictTypes = false,
private PhpFunctionFromParserNodeReflection|null $function = null,
?string $namespace = null,
Expand Down Expand Up @@ -6456,4 +6458,14 @@ public function getPhpVersion(): PhpVersions
return new PhpVersions(new ConstantIntegerType($this->phpVersion->getVersionId()));
}

public function invokeNodeCallback(Node $node): void
{
$nodeCallback = $this->nodeCallback;
if ($nodeCallback === null) {
throw new ShouldNotHappenException('Node callback is not present in this scope');
}

$nodeCallback($node, $this);
}

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

namespace PHPStan\Analyser;

use PhpParser\Node;

/**
* The interface NodeCallbackInvoker can be typehinted in 2nd parameter of Rule::processNode():
*
* ```php
* public function processNode(Node $node, Scope&NodeCallbackInvoker $scope): array
* ```
*
* It can be used to invoke rules for virtual made-up nodes.
*
* For example: You're writing a rule for a method with declaration like:
*
* ```php
* public static create(string $class, mixed ...$args)
* ```
*
* And you'd like to check `Factory::create(Foo::class, 1, 2, 3)` as if it were
* `new Foo(1, 2, 3)`.
*
* You can call `$scope->invokeNodeCallback(new New_(new Name($className), $args))`
*
* And PHPStan will call all the registered rules for New_, checking as if the instantiation
* is actually in the code.
Comment on lines +7 to +28
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh wow, this is awesome! 🚀

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I vaguely remember you're using FunctionCallParametersCheck somewhere, which is not part of BC promise. This would allow you to write simple and clean code instead 😊

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please note that this PHPDoc is lying a little bit. You don't need to change your processNode signature to take advantage of this. phpstan-src uses native intersection type which is downgraded to PHPDoc because of PHP 7.4+ support. Which means that unless you have your own PHPDoc in your custom rule, PHPStan will implicitly inherit the Rule interface PHPDoc and you can call $scope->invokeNodeCallback without changing the signature :)

*
* @api
*/
interface NodeCallbackInvoker
{

public function invokeNodeCallback(Node $node): void;

}
10 changes: 7 additions & 3 deletions src/Analyser/ScopeFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace PHPStan\Analyser;

use PhpParser\Node;
use PHPStan\DependencyInjection\AutowiredService;

/**
Expand All @@ -11,13 +12,16 @@
final class ScopeFactory
{

public function __construct(private InternalScopeFactory $internalScopeFactory)
public function __construct(private InternalScopeFactoryFactory $internalScopeFactoryFactory)
{
}

public function create(ScopeContext $context): MutatingScope
/**
* @param callable(Node $node, Scope $scope): void $nodeCallback
*/
public function create(ScopeContext $context, ?callable $nodeCallback = null): MutatingScope
{
return $this->internalScopeFactory->create($context);
return $this->internalScopeFactoryFactory->create($nodeCallback)->create($context);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ public function loadConfiguration(): void
$definition = $builder->addFactoryDefinition(null)
->setImplement($attribute->interface);

if ($attribute->resultType !== null) {
$definition->getResultDefinition()->setType($attribute->resultType);
}

$resultDefinition = $definition->getResultDefinition();
$this->processParameters($class->name, $resultDefinition, $autowiredParameters);
}
Expand Down
3 changes: 2 additions & 1 deletion src/DependencyInjection/GenerateFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ final class GenerateFactory

/**
* @param class-string $interface
* @param class-string $resultType
*/
public function __construct(public string $interface)
public function __construct(public string $interface, public ?string $resultType = null)
{
}

Expand Down
5 changes: 3 additions & 2 deletions src/Rules/Methods/OverridingMethodRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use PhpParser\Node;
use PhpParser\Node\Attribute;
use PHPStan\Analyser\NodeCallbackInvoker;
use PHPStan\Analyser\Scope;
use PHPStan\DependencyInjection\AutowiredParameter;
use PHPStan\DependencyInjection\RegisteredRule;
Expand Down Expand Up @@ -48,7 +49,7 @@ public function getNodeType(): string
return InClassMethodNode::class;
}

public function processNode(Node $node, Scope $scope): array
public function processNode(Node $node, Scope&NodeCallbackInvoker $scope): array
{
$method = $node->getMethodReflection();
$prototypeData = $this->methodPrototypeFinder->findPrototype($node->getClassReflection(), $method->getName());
Expand Down Expand Up @@ -323,7 +324,7 @@ private function filterOverrideAttribute(array $attrGroups): array
private function addErrors(
array $errors,
InClassMethodNode $classMethod,
Scope $scope,
Scope&NodeCallbackInvoker $scope,
): array
{
if (count($errors) > 0) {
Expand Down
3 changes: 2 additions & 1 deletion src/Rules/Playground/PromoteParameterRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace PHPStan\Rules\Playground;

use PhpParser\Node;
use PHPStan\Analyser\NodeCallbackInvoker;
use PHPStan\Analyser\Scope;
use PHPStan\DependencyInjection\Container;
use PHPStan\DependencyInjection\MissingServiceException;
Expand Down Expand Up @@ -87,7 +88,7 @@ private function getOriginalRule(): ?Rule
return $this->originalRule = $originalRule;
}

public function processNode(Node $node, Scope $scope): array
public function processNode(Node $node, Scope&NodeCallbackInvoker $scope): array
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this a testing-only change which we need to be cleaned up?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, we're calling another rule, which might need it. If you revert it, you get PHPStan error.

{
if ($this->parameterValue) {
return [];
Expand Down
3 changes: 2 additions & 1 deletion src/Rules/Rule.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace PHPStan\Rules;

use PhpParser\Node;
use PHPStan\Analyser\NodeCallbackInvoker;
use PHPStan\Analyser\Scope;

/**
Expand Down Expand Up @@ -34,6 +35,6 @@ public function getNodeType(): string;
* @param TNodeType $node
* @return list<IdentifierRuleError>
*/
public function processNode(Node $node, Scope $scope): array;
public function processNode(Node $node, Scope&NodeCallbackInvoker $scope): array;

}
3 changes: 2 additions & 1 deletion src/Testing/DelayedRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace PHPStan\Testing;

use PhpParser\Node;
use PHPStan\Analyser\NodeCallbackInvoker;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\DirectRegistry;
use PHPStan\Rules\IdentifierRuleError;
Expand Down Expand Up @@ -42,7 +43,7 @@ public function getDelayedErrors(): array
return $this->errors;
}

public function processNode(Node $node, Scope $scope): array
public function processNode(Node $node, Scope&NodeCallbackInvoker $scope): array
{
$nodeType = get_class($node);
foreach ($this->registry->getRules($nodeType) as $rule) {
Expand Down
4 changes: 2 additions & 2 deletions src/Testing/PHPStanTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
namespace PHPStan\Testing;

use PHPStan\Analyser\ConstantResolver;
use PHPStan\Analyser\DirectInternalScopeFactory;
use PHPStan\Analyser\DirectInternalScopeFactoryFactory;
use PHPStan\Analyser\Error;
use PHPStan\Analyser\NodeScopeResolver;
use PHPStan\Analyser\RicherScopeGetTypeHelper;
Expand Down Expand Up @@ -152,7 +152,7 @@ public static function createScopeFactory(ReflectionProvider $reflectionProvider
);

return new ScopeFactory(
new DirectInternalScopeFactory(
new DirectInternalScopeFactoryFactory(
$reflectionProvider,
$initializerExprTypeResolver,
$container->getByType(DynamicReturnTypeExtensionRegistryProvider::class),
Expand Down
Loading
Loading