/** * @param \PhpParser\Node\Expr\Cast $node * @param \PHPStan\Analyser\Scope $scope * @return string[] errors */ public function processNode(Node $node, Scope $scope) : array { if ($node instanceof Object_) { return []; } $expressionType = $scope->getType($node->expr); if ($expressionType->isNullable()) { return []; } $castType = $scope->getType($node); if (get_class($expressionType) === get_class($castType)) { return [sprintf('Casting to %s something that\'s already %s.', $castType->describe(), $expressionType->describe())]; } return []; }
/** * @param \PhpParser\Node\Expr\BinaryOp $node * @param \PHPStan\Analyser\Scope $scope * @return string[] errors */ public function processNode(Node $node, Scope $scope) : array { if (!$node instanceof Node\Expr\BinaryOp\Identical && !$node instanceof Node\Expr\BinaryOp\NotIdentical) { return []; } $leftType = $scope->getType($node->left); $rightType = $scope->getType($node->right); if ($leftType instanceof MixedType || $rightType instanceof MixedType || $leftType instanceof NullType || $rightType instanceof NullType) { return []; } if (get_class($leftType) !== get_class($rightType)) { return [sprintf('Strict comparison using %s between %s and %s will always evaluate to false.', $node instanceof Node\Expr\BinaryOp\Identical ? '===' : '!==', $leftType->describe(), $rightType->describe())]; } return []; }
/** * @param \PhpParser\Node\Expr\Assign $node * @param \PHPStan\Analyser\Scope $scope * @return string[] */ public function processNode(\PhpParser\Node $node, Scope $scope) : array { if (!$node->var instanceof ArrayDimFetch) { return []; } $assignedToType = $scope->getType($node->var->var); if (!$assignedToType instanceof ArrayType) { return []; } if ($assignedToType->isItemTypeInferredFromLiteralArray()) { return []; } $assignedValueType = $scope->getType($node->expr); if (!$assignedToType->getItemType()->accepts($assignedValueType)) { return [sprintf('Array (%s) does not accept %s.', $assignedToType->describe(), $assignedValueType->describe())]; } return []; }
/** * @param \PhpParser\Node\Expr\ClassConstFetch $node * @param \PHPStan\Analyser\Scope $scope * @return string[] */ public function processNode(Node $node, Scope $scope) : array { $class = $node->class; if ($class instanceof \PhpParser\Node\Name) { $className = (string) $class; } else { $classType = $scope->getType($class); if ($classType->getClass() !== null) { $className = $classType->getClass(); } else { return []; } } if ($className === 'self' || $className === 'static') { if ($scope->getClass() === null && !$scope->isInAnonymousClass()) { return [sprintf('Using %s outside of class scope.', $className)]; } if ($className === 'static') { return []; } if ($className === 'self') { $className = $scope->getClass(); } } $constantName = $node->name; if ($scope->getClass() !== null && $className === 'parent') { $currentClassReflection = $this->broker->getClass($scope->getClass()); if ($currentClassReflection->getParentClass() === false) { return [sprintf('Access to parent::%s but %s does not extend any class.', $constantName, $scope->getClass())]; } $className = $currentClassReflection->getParentClass()->getName(); } if (!$this->broker->hasClass($className)) { return [sprintf('Class %s not found.', $className)]; } if ($constantName === 'class') { return []; } $classReflection = $this->broker->getClass($className); if (!$classReflection->hasConstant($constantName)) { return [sprintf('Access to undefined constant %s::%s.', $classReflection->getName(), $constantName)]; } $constantReflection = $classReflection->getConstant($constantName); if (!$scope->canAccessConstant($constantReflection)) { return [sprintf('Cannot access constant %s::%s from current scope.', $constantReflection->getDeclaringClass()->getName(), $constantName)]; } return []; }
/** * @param \PHPStan\Analyser\Scope $scope * @param \PHPStan\Type\Type $returnType * @param \PhpParser\Node\Expr|null $returnValue * @param string $emptyReturnStatementMessage * @param string $voidMessage * @param string $typeMismatchMessage * @return string[] */ public function checkReturnType(Scope $scope, Type $returnType, Expr $returnValue = null, string $emptyReturnStatementMessage, string $voidMessage, string $typeMismatchMessage) : array { if ($returnValue === null) { if ($returnType instanceof VoidType || $returnType instanceof MixedType) { return []; } return [sprintf($emptyReturnStatementMessage, $returnType->describe())]; } $returnValueType = $scope->getType($returnValue); if ($returnType instanceof VoidType) { return [sprintf($voidMessage, $returnValueType->describe())]; } if (!$returnType->accepts($returnValueType)) { return [sprintf($typeMismatchMessage, $returnType->describe(), $returnValueType->describe())]; } return []; }
/** * @param \PhpParser\Node\Expr\PropertyFetch|\PhpParser\Node\Expr\StaticPropertyFetch $propertyFetch * @param \PHPStan\Analyser\Scope $scope * @return string|null */ private function describeProperty($propertyFetch, Scope $scope) { if ($propertyFetch instanceof Node\Expr\PropertyFetch) { if (!is_string($propertyFetch->name)) { return null; } $propertyHolderType = $scope->getType($propertyFetch->var); if ($propertyHolderType->getClass() === null) { return null; } return sprintf('Property %s::$%s', $propertyHolderType->getClass(), $propertyFetch->name); } elseif ($propertyFetch instanceof Node\Expr\StaticPropertyFetch) { if (!$propertyFetch->class instanceof Node\Name || !is_string($propertyFetch->name)) { return null; } return sprintf('Static property %s::$%s', $scope->resolveName($propertyFetch->class), $propertyFetch->name); } return null; }
/** * @param \PhpParser\Node\Expr\MethodCall $node * @param \PHPStan\Analyser\Scope $scope * @return string[] */ public function processNode(Node $node, Scope $scope) : array { if (!is_string($node->name)) { return []; } if ($this->checkThisOnly && !$this->ruleLevelHelper->isThis($node->var)) { return []; } $type = $scope->getType($node->var); if (!$type->canCallMethods()) { return [sprintf('Cannot call method %s() on %s.', $node->name, $type->describe())]; } $methodClass = $type->getClass(); if ($methodClass === null) { return []; } $name = (string) $node->name; if (!$this->broker->hasClass($methodClass)) { return [sprintf('Call to method %s() on an unknown class %s.', $name, $methodClass)]; } $methodClassReflection = $this->broker->getClass($methodClass); if (!$methodClassReflection->hasMethod($name)) { $parentClassReflection = $methodClassReflection->getParentClass(); while ($parentClassReflection !== false) { if ($parentClassReflection->hasMethod($name)) { return [sprintf('Call to private method %s() of parent class %s.', $parentClassReflection->getMethod($name)->getName(), $parentClassReflection->getName())]; } $parentClassReflection = $parentClassReflection->getParentClass(); } return [sprintf('Call to an undefined method %s::%s().', $methodClassReflection->getName(), $name)]; } $methodReflection = $methodClassReflection->getMethod($name); $messagesMethodName = $methodReflection->getDeclaringClass()->getName() . '::' . $methodReflection->getName() . '()'; if (!$scope->canCallMethod($methodReflection)) { return [sprintf('Cannot call method %s from current scope.', $messagesMethodName)]; } $errors = $this->check->check($methodReflection, $node, ['Method ' . $messagesMethodName . ' invoked with %d parameter, %d required.', 'Method ' . $messagesMethodName . ' invoked with %d parameters, %d required.', 'Method ' . $messagesMethodName . ' invoked with %d parameter, at least %d required.', 'Method ' . $messagesMethodName . ' invoked with %d parameters, at least %d required.', 'Method ' . $messagesMethodName . ' invoked with %d parameter, %d-%d required.', 'Method ' . $messagesMethodName . ' invoked with %d parameters, %d-%d required.']); if ($methodReflection->getName() !== $name) { $errors[] = sprintf('Call to method %s with incorrect case: %s', $messagesMethodName, $name); } return $errors; }
/** * @param \PhpParser\Node\Stmt\Property $node * @param \PHPStan\Analyser\Scope $scope * @return string[] */ public function processNode(Node $node, Scope $scope) : array { if ($scope->getClass() === null || !$this->broker->hasClass($scope->getClass())) { return []; } $classReflection = $this->broker->getClass($scope->getClass()); $errors = []; foreach ($node->props as $property) { if ($property->default === null) { continue; } $propertyReflection = $classReflection->getProperty($property->name); $propertyType = $propertyReflection->getType(); $defaultValueType = $scope->getType($property->default); if ($propertyType->accepts($defaultValueType)) { continue; } $errors[] = sprintf('%s %s::$%s (%s) does not accept default value of type %s.', $node->isStatic() ? 'Static property' : 'Property', $scope->getClass(), $property->name, $propertyType->describe(), $defaultValueType->describe()); } return $errors; }
/** * @param \PhpParser\Node\Expr\PropertyFetch $node * @param \PHPStan\Analyser\Scope $scope * @return string[] */ public function processNode(\PhpParser\Node $node, Scope $scope) : array { if (!is_string($node->name)) { return []; } if ($this->checkThisOnly && !$this->ruleLevelHelper->isThis($node->var)) { return []; } $type = $scope->getType($node->var); if (!$type->canAccessProperties()) { return [sprintf('Cannot access property $%s on %s.', $node->name, $type->describe())]; } $propertyClass = $type->getClass(); if ($propertyClass === null) { return []; } $name = (string) $node->name; if (!$this->broker->hasClass($propertyClass)) { return [sprintf('Access to property $%s on an unknown class %s.', $name, $propertyClass)]; } $propertyClassReflection = $this->broker->getClass($propertyClass); if (!$propertyClassReflection->hasProperty($name)) { if ($scope->isSpecified($node)) { return []; } $parentClassReflection = $propertyClassReflection->getParentClass(); while ($parentClassReflection !== false) { if ($parentClassReflection->hasProperty($name)) { return [sprintf('Access to private property $%s of parent class %s.', $name, $parentClassReflection->getName())]; } $parentClassReflection = $parentClassReflection->getParentClass(); } return [sprintf('Access to an undefined property %s::$%s.', $propertyClass, $name)]; } $propertyReflection = $propertyClassReflection->getProperty($name, $scope); if (!$scope->canAccessProperty($propertyReflection)) { return [sprintf('Cannot access property %s::$%s from current scope.', $propertyReflection->getDeclaringClass()->getName(), $name)]; } return []; }
/** * @param \PhpParser\Node\Expr\ClassConstFetch $node * @param \PHPStan\Analyser\Scope $scope * @return string[] */ public function processNode(Node $node, Scope $scope) : array { $class = $node->class; if ($class instanceof \PhpParser\Node\Name) { $className = (string) $class; } else { $classType = $scope->getType($class); if ($classType->getClass() !== null) { $className = $classType->getClass(); } else { return []; } } if ($className === 'self' || $className === 'static') { if ($scope->getClass() === null && !$scope->isInAnonymousClass()) { return [sprintf('Using %s outside of class scope.', $className)]; } if ($className === 'static') { return []; } if ($className === 'self') { $className = $scope->getClass(); } } if (!$this->broker->hasClass($className)) { return [sprintf('Class %s not found.', $className)]; } $constantName = $node->name; if ($constantName === 'class') { return []; } $classReflection = $this->broker->getClass($className); if (!$classReflection->hasConstant($constantName)) { return [sprintf('Access to undefined constant %s::%s.', $className, $constantName)]; } return []; }
private function hasStatementEarlyTermination(Node $statement, Scope $scope) : bool { if ($statement instanceof Throw_ || $statement instanceof Return_ || $statement instanceof Continue_ || $statement instanceof Break_ || $statement instanceof Exit_) { return true; } elseif ($statement instanceof MethodCall && count($this->earlyTerminatingMethodCalls) > 0) { if (!is_string($statement->name)) { return false; } $methodCalledOnType = $scope->getType($statement->var); if ($methodCalledOnType->getClass() === null) { return false; } if (!$this->broker->hasClass($methodCalledOnType->getClass())) { return false; } $classReflection = $this->broker->getClass($methodCalledOnType->getClass()); foreach (array_merge([$methodCalledOnType->getClass()], $classReflection->getParentClassesNames()) as $className) { if (!isset($this->earlyTerminatingMethodCalls[$className])) { continue; } return in_array($statement->name, $this->earlyTerminatingMethodCalls[$className], true); } return false; } return false; }