[static] Fix mixed type on property fetches and method calls (#1769)

This commit is contained in:
Tomas Votruba 2022-02-05 12:51:30 +01:00 committed by GitHub
parent fd7667b5aa
commit dae413184b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 92 additions and 71 deletions

View File

@ -8,6 +8,7 @@ use Nette\Utils\Json;
use Rector\ChangesReporting\Annotation\RectorsChangelogResolver;
use Rector\ChangesReporting\Contract\Output\OutputFormatterInterface;
use Rector\Core\ValueObject\Configuration;
use Rector\Core\ValueObject\Error\SystemError;
use Rector\Core\ValueObject\ProcessResult;
use Rector\Parallel\ValueObject\Bridge;
@ -69,7 +70,7 @@ final class JsonOutputFormatter implements OutputFormatterInterface
}
/**
* @param mixed[] $errors
* @param SystemError[] $errors
* @return mixed[]
*/
private function createErrorsData(array $errors): array
@ -82,7 +83,7 @@ final class JsonOutputFormatter implements OutputFormatterInterface
'file' => $error->getRelativeFilePath(),
];
if ($error->getRectorClass()) {
if ($error->getRectorClass() !== null) {
$errorDataJson['caused_by'] = $error->getRectorClass();
}

View File

@ -9,12 +9,8 @@ use PhpParser\Node\Expr;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Name;
use PHPStan\Type\MixedType;
use PHPStan\Type\Type;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\NodeTypeResolver\NodeTypeResolver;
use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType;
/**
* Utils for chain of MethodCall Node:
@ -24,7 +20,6 @@ final class FluentChainMethodCallNodeAnalyzer
{
public function __construct(
private readonly NodeNameResolver $nodeNameResolver,
private readonly NodeTypeResolver $nodeTypeResolver,
) {
}
@ -74,47 +69,6 @@ final class FluentChainMethodCallNodeAnalyzer
return $chainMethodCalls;
}
/**
* Checks "$this->someMethod()->anotherMethod()"
*
* @param string[] $methods
*/
public function isTypeAndChainCalls(Node $node, Type $type, array $methods): bool
{
if (! $node instanceof MethodCall) {
return false;
}
$rootMethodCall = $this->resolveRootMethodCall($node);
if (! $rootMethodCall instanceof MethodCall) {
return false;
}
$rootMethodCallVarType = $this->nodeTypeResolver->getType($rootMethodCall->var);
if (! $rootMethodCallVarType instanceof FullyQualifiedObjectType) {
return false;
}
// node chaining is in reverse order than code
$methods = array_reverse($methods);
foreach ($methods as $method) {
if (! $this->nodeNameResolver->isName($node->name, $method)) {
return false;
}
$node = $node->var;
}
$variableType = $this->nodeTypeResolver->getType($node);
if ($variableType instanceof MixedType) {
return false;
}
return $variableType->isSuperTypeOf($type)
->yes();
}
public function resolveRootExpr(MethodCall $methodCall): Expr | Name
{
$callerNode = $methodCall->var;

View File

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Rector\FileFormatter\EditorConfig;
use Idiosyncratic\EditorConfig\Declaration\Declaration;
use Idiosyncratic\EditorConfig\EditorConfig;
use Rector\Core\ValueObject\Application\File;
use Rector\FileFormatter\ValueObject\EditorConfigConfiguration;
@ -25,6 +26,8 @@ final class EditorConfigParser
EditorConfigConfigurationBuilder $editorConfigConfigurationBuilder
): EditorConfigConfiguration {
$smartFileInfo = $file->getSmartFileInfo();
/** @var Declaration[] $configuration */
$configuration = $this->editorConfig->getConfigForPath($smartFileInfo->getRealPath());
if (array_key_exists(EditorConfigOption::INDENT_STYLE, $configuration)) {

View File

@ -25,7 +25,7 @@ final class BinaryOpTreeRootLocator
public function findOperationRoot(Expr $expr, string $binaryOpClass): Expr
{
$parentNode = $expr->getAttribute(AttributeKey::PARENT_NODE);
if ($parentNode === null) {
if (! $parentNode instanceof Node) {
// No more parents so the Expr node must be root.
return $expr;
}
@ -36,6 +36,7 @@ final class BinaryOpTreeRootLocator
return $expr;
}
/** @var BinaryOp $parentNode */
$isParentARightAssociativeTree = $parentNode->right === $expr && $expr::class === $binaryOpClass;
if ($isParentARightAssociativeTree) {
// The Expr node is the right child of its parent but it is the desired operation (BinaryOp b c).

View File

@ -40,6 +40,7 @@ final class StatementNodeVisitor extends NodeVisitorAbstract
if (property_exists($node, 'stmts')) {
$previous = $node;
foreach ((array) $node->stmts as $stmt) {
/** @var Stmt $stmt */
$stmt->setAttribute(AttributeKey::PREVIOUS_STATEMENT, $previous);
$stmt->setAttribute(AttributeKey::CURRENT_STATEMENT, $stmt);
$previous = $stmt;

View File

@ -607,8 +607,9 @@ parameters:
- '#Parameter \#2 \$key of class PhpParser\\Node\\Expr\\ArrayItem constructor expects PhpParser\\Node\\Expr\|null, array<PhpParser\\Node\\Expr\>\|PhpParser\\Node\\Expr\|string\|null given#'
# broken on CI
- '#Anonymous variables in a method call can lead to false dead methods\. Make sure the variable type is known#'
- '#Anonymous variables in a property fetch can lead to false dead property\. Make sure the variable type is known#'
-
message: '#Anonymous variables in a method call can lead to false dead methods\. Make sure the variable type is known#'
path: packages/NodeNestingScope/FlowOfControlLocator.php
-
message: '#This property type might be inlined to PHP\. Do you have confidence it is correct\? Put it here#'

View File

@ -111,6 +111,10 @@ CODE_SAMPLE
if ($node instanceof FunctionLike) {
return $node->returnsByRef();
}
if (! $node instanceof Node) {
break;
}
}
return false;

View File

@ -4,7 +4,7 @@ declare(strict_types=1);
namespace Rector\CodingStyle\Rector\Stmt;
use PhpParser\Comment\Doc;
use PhpParser\Comment;
use PhpParser\Node;
use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\Catch_;
@ -127,6 +127,10 @@ CODE_SAMPLE
}
$nextNode = $node->getAttribute(AttributeKey::NEXT_NODE);
if (! $nextNode instanceof Node) {
return null;
}
if ($this->shouldSkip($nextNode)) {
return null;
}
@ -136,6 +140,7 @@ CODE_SAMPLE
$rangeLine = $line - $endLine;
if ($rangeLine > 1) {
/** @var Comment[]|null $comments */
$comments = $nextNode->getAttribute(AttributeKey::COMMENTS);
if ($this->hasNoComment($comments)) {
@ -147,6 +152,7 @@ CODE_SAMPLE
return null;
}
/** @var Comment[] $comments */
$line = $comments[0]->getLine();
$rangeLine = $line - $endLine;
@ -171,7 +177,7 @@ CODE_SAMPLE
}
/**
* @param null|Doc[] $comments
* @param Comment[]|null $comments
*/
private function hasNoComment(?array $comments): bool
{

View File

@ -192,6 +192,9 @@ CODE_SAMPLE
private function refactorUsedVariable(Assign $assign): ?Assign
{
$parentNode = $assign->getAttribute(AttributeKey::PARENT_NODE);
if (! $parentNode instanceof Node) {
return null;
}
$if = $parentNode->getAttribute(AttributeKey::NEXT_NODE);

View File

@ -12,7 +12,9 @@ use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\New_;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\Expression;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\Rector\AbstractRector;
use Rector\Naming\Naming\VariableNaming;
use Rector\NodeTypeResolver\Node\AttributeKey;
@ -87,6 +89,10 @@ CODE_SAMPLE
private function createVariable(Node $node): Variable
{
$currentStmt = $node->getAttribute(AttributeKey::CURRENT_STATEMENT);
if (! $currentStmt instanceof Stmt) {
throw new ShouldNotHappenException();
}
$scope = $currentStmt->getAttribute(AttributeKey::SCOPE);
return new Variable($this->variableNaming->createCountedValueName('object', $scope));

View File

@ -10,6 +10,7 @@ use PhpParser\Node\Expr\List_;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\Foreach_;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\Rector\AbstractRector;
use Rector\Naming\Naming\VariableNaming;
use Rector\NodeTypeResolver\Node\AttributeKey;
@ -79,6 +80,10 @@ CODE_SAMPLE
private function createVariable(Foreach_ $foreach): Variable
{
$currentStmt = $foreach->getAttribute(AttributeKey::CURRENT_STATEMENT);
if (! $currentStmt instanceof Node) {
throw new ShouldNotHappenException();
}
$scope = $currentStmt->getAttribute(AttributeKey::SCOPE);
return new Variable($this->variableNaming->createCountedValueName('arrayItem', $scope));

View File

@ -20,6 +20,7 @@ use PhpParser\Node\Scalar\LNumber;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\Return_;
use PhpParser\Node\Stmt\While_;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\Rector\AbstractRector;
use Rector\Naming\Naming\VariableNaming;
use Rector\NodeTypeResolver\Node\AttributeKey;
@ -128,6 +129,10 @@ CODE_SAMPLE
private function refactorForVariableLevels(FuncCall $funcCall): FuncCall
{
$currentStmt = $funcCall->getAttribute(AttributeKey::CURRENT_STATEMENT);
if (! $currentStmt instanceof Node) {
throw new ShouldNotHappenException();
}
$scope = $currentStmt->getAttribute(AttributeKey::SCOPE);
$funcVariable = new Variable($this->variableNaming->createCountedValueName('dirnameFunc', $scope));

View File

@ -11,6 +11,8 @@ use PhpParser\Node\Expr\Instanceof_;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Name;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Stmt;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\Rector\AbstractRector;
use Rector\Naming\Naming\VariableNaming;
use Rector\NodeCollector\BinaryOpConditionsCollector;
@ -90,6 +92,10 @@ CODE_SAMPLE
private function createVariable(Instanceof_ $instanceof): Variable
{
$currentStmt = $instanceof->getAttribute(AttributeKey::CURRENT_STATEMENT);
if (! $currentStmt instanceof Stmt) {
throw new ShouldNotHappenException();
}
$scope = $currentStmt->getAttribute(AttributeKey::SCOPE);
return new Variable($this->variableNaming->createCountedValueName('throwable', $scope));

View File

@ -18,6 +18,7 @@ use PhpParser\Node\Param;
use PhpParser\Node\Scalar\LNumber;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\Return_;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\NodeManipulator\IfManipulator;
use Rector\Core\Rector\AbstractRector;
use Rector\Naming\Naming\VariableNaming;
@ -94,6 +95,10 @@ CODE_SAMPLE
$anonymousFunction->stmts[1] = new Return_($ternary);
$currentStatement = $node->getAttribute(AttributeKey::CURRENT_STATEMENT);
if (! $currentStatement instanceof Node) {
throw new ShouldNotHappenException();
}
$scope = $currentStatement->getAttribute(AttributeKey::SCOPE);
$variableAssignName = $this->variableNaming->createCountedValueName('battleShipcompare', $scope);

View File

@ -143,6 +143,7 @@ CODE_SAMPLE
$comments = $param->getAttribute(AttributeKey::COMMENTS);
if (is_array($comments)) {
/** @var Comment[] $comments */
foreach ($comments as $comment) {
$attrGroupsPrint = str_replace($comment->getText(), '', $attrGroupsPrint);
}

View File

@ -15,6 +15,7 @@ use PhpParser\Node\Expr\New_;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\StaticPropertyFetch;
use PhpParser\Node\Expr\Variable;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\Rector\AbstractRector;
use Rector\Naming\Naming\VariableNaming;
use Rector\NodeTypeResolver\Node\AttributeKey;
@ -93,6 +94,10 @@ CODE_SAMPLE
$variable = $assign->var;
} else {
$currentStmt = $node->getAttribute(AttributeKey::CURRENT_STATEMENT);
if (! $currentStmt instanceof Node) {
throw new ShouldNotHappenException();
}
$scope = $currentStmt->getAttribute(AttributeKey::SCOPE);
$variable = new Variable($this->variableNaming->createCountedValueName('className', $scope));
$assign = new Assign($variable, $node->class);

View File

@ -128,7 +128,7 @@ CODE_SAMPLE
$callNodeClass = $callNode::class;
while ($parentNode) {
while ($parentNode instanceof Node) {
$usedNodes = $this->betterNodeFinder->find($parentNode, function (Node $node) use (
$callNodeClass,
$callNode

View File

@ -91,7 +91,7 @@ final class NonVariableToVariableOnFunctionCallRector extends AbstractRector imp
}
$currentScope = $scopeNode->getAttribute(AttributeKey::SCOPE);
if (! $currentScope instanceof Scope) {
if (! $currentScope instanceof MutatingScope) {
return null;
}
@ -105,6 +105,7 @@ final class NonVariableToVariableOnFunctionCallRector extends AbstractRector imp
$current = $node->getAttribute(AttributeKey::CURRENT_STATEMENT);
$currentStatement = $node->getAttribute(AttributeKey::CURRENT_STATEMENT);
$this->nodesToAddCollector->addNodeBeforeNode(
$replacements->getAssign(),
$current instanceof Return_ ? $current : $currentStatement
@ -166,7 +167,7 @@ final class NonVariableToVariableOnFunctionCallRector extends AbstractRector imp
private function getReplacementsFor(
Expr $expr,
Scope $scope,
MutatingScope $scope,
Closure|Class_|ClassMethod|Function_|Namespace_ $scopeNode
): VariableAssignPair {
if ($this->isAssign($expr)) {
@ -181,10 +182,8 @@ final class NonVariableToVariableOnFunctionCallRector extends AbstractRector imp
$variable = new Variable($variableName);
// add a new scope with this variable
if ($scope instanceof MutatingScope) {
$mutatingScope = $scope->assignExpression($variable, new MixedType());
$scopeNode->setAttribute(AttributeKey::SCOPE, $mutatingScope);
}
$mutatingScope = $scope->assignExpression($variable, new MixedType());
$scopeNode->setAttribute(AttributeKey::SCOPE, $mutatingScope);
return new VariableAssignPair($variable, new Assign($variable, $expr));
}

View File

@ -8,6 +8,7 @@ use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Stmt;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\ValueObject\PhpVersionFeature;
use Rector\NodeTypeResolver\Node\AttributeKey;
@ -72,7 +73,7 @@ CODE_SAMPLE
$node->args[1] = new Arg($resultVariable);
$expression = $node->getAttribute(AttributeKey::CURRENT_STATEMENT);
if ($expression === null) {
if (! $expression instanceof Stmt) {
return null;
}

View File

@ -134,7 +134,7 @@ final class ComplexNodeRemover
{
$assign = $expr->getAttribute(AttributeKey::PARENT_NODE);
while ($assign !== null && ! $assign instanceof Assign) {
while ($assign instanceof Node && ! $assign instanceof Assign) {
$assign = $assign->getAttribute(AttributeKey::PARENT_NODE);
}

View File

@ -10,6 +10,7 @@ use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Stmt\ClassLike;
use PhpParser\Node\Stmt\ClassMethod;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Type\ObjectType;
use Rector\Core\Rector\AbstractRector;
@ -100,6 +101,9 @@ CODE_SAMPLE
}
$scope = $classMethod->getAttribute(AttributeKey::SCOPE);
if (! $scope instanceof Scope) {
return null;
}
$classReflection = $scope->getClassReflection();
if (! $classReflection instanceof ClassReflection) {

View File

@ -10,6 +10,9 @@ use Symfony\Component\Config\FileLocatorInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
/**
* @property-read ContainerBuilder $container
*/
final class ConfigurableCallValuesCollectingPhpFileLoader extends PhpFileLoader
{
public function __construct(
@ -51,10 +54,6 @@ final class ConfigurableCallValuesCollectingPhpFileLoader extends PhpFileLoader
private function collectConfigureCallsFromJustImportedConfigurableRectorDefinitions(): void
{
foreach ($this->container->getDefinitions() as $class => $definition) {
if (! is_string($class)) {
continue;
}
if (! is_a($class, ConfigurableRectorInterface::class, true)) {
continue;
}

View File

@ -97,7 +97,7 @@ final class AssignManipulator
$previousNode = $node;
$parentNode = $node->getAttribute(AttributeKey::PARENT_NODE);
while ($parentNode !== null && ! $parentNode instanceof Expression) {
while ($parentNode instanceof Node && ! $parentNode instanceof Expression) {
if ($parentNode instanceof Assign && $this->nodeComparator->areNodesEqual(
$parentNode->var,
$previousNode

View File

@ -111,6 +111,7 @@ final class IfManipulator
while ($this->isIfWithOnlyStmtIf($currentIf)) {
$ifs[] = $currentIf;
/** @var If_ $currentIf */
$currentIf = $currentIf->stmts[0];
}
@ -198,6 +199,7 @@ final class IfManipulator
while ($this->isIfWithOnlyStmtIf($currentIf)) {
$ifs[] = $currentIf;
/** @var If_ $currentIf */
$currentIf = $currentIf->stmts[0];
}

View File

@ -310,24 +310,24 @@ final class BetterNodeFinder
public function findFirstPrevious(Node $node, callable $filter): ?Node
{
$node = $node instanceof Expression ? $node : $node->getAttribute(AttributeKey::CURRENT_STATEMENT);
if ($node === null) {
if (! $node instanceof Node) {
return null;
}
$foundNode = $this->findFirst([$node], $filter);
// we found what we need
if ($foundNode !== null) {
if ($foundNode instanceof Node) {
return $foundNode;
}
// move to previous expression
$previousStatement = $node->getAttribute(AttributeKey::PREVIOUS_STATEMENT);
if ($previousStatement !== null) {
if ($previousStatement instanceof Node) {
return $this->findFirstPrevious($previousStatement, $filter);
}
$parent = $node->getAttribute(AttributeKey::PARENT_NODE);
if ($parent === null) {
if (! $parent instanceof Node) {
return null;
}

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Rector\Core\PhpParser\Printer;
use Nette\Utils\Strings;
use PhpParser\Comment;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\Array_;
@ -458,6 +459,7 @@ final class BetterStandardPrinter extends Standard
return $content;
}
/** @var Comment[] $comments */
$comments = $expr->getAttribute(AttributeKey::COMMENTS) ?? [];
if ($comments === []) {

View File

@ -230,6 +230,7 @@ abstract class AbstractRector extends NodeVisitorAbstract implements PhpRectorIn
$this->printDebugApplying();
$originalAttributes = $node->getAttributes();
$originalNode ??= clone $node;
$node = $this->refactor($node);
@ -239,6 +240,7 @@ abstract class AbstractRector extends NodeVisitorAbstract implements PhpRectorIn
return null;
}
/** @var Node $originalNode */
if (is_array($node)) {
$this->createdByRule($node, $originalNode);

View File

@ -37,6 +37,11 @@ final class SystemError implements SerializableInterface
return $this->relativeFilePath . ':' . $this->line;
}
public function getRelativeFilePath(): ?string
{
return $this->relativeFilePath;
}
/**
* @return array{message: string, relative_file_path: string|null, line: int|null, rector_class: string|null}
*/