public function getType(Node $node) : Type
{
if ($node instanceof \PhpParser\Node\Expr\BinaryOp\BooleanAnd || $node instanceof \PhpParser\Node\Expr\BinaryOp\BooleanOr || $node instanceof \PhpParser\Node\Expr\BooleanNot || $node instanceof \PhpParser\Node\Expr\BinaryOp\LogicalXor) {
return new BooleanType(false);
}
if ($node instanceof Node\Expr\UnaryMinus || $node instanceof Node\Expr\UnaryPlus) {
return $this->getType($node->expr);
}
if ($node instanceof Node\Expr\BinaryOp\Mod) {
return new IntegerType(false);
}
if ($node instanceof Expr\BinaryOp\Concat) {
return new StringType(false);
}
if ($node instanceof Expr\BinaryOp\Spaceship) {
return new IntegerType(false);
}
if ($node instanceof Node\Expr\BinaryOp\Plus || $node instanceof Node\Expr\BinaryOp\Minus || $node instanceof Node\Expr\BinaryOp\Mul || $node instanceof Node\Expr\BinaryOp\Pow || $node instanceof Node\Expr\BinaryOp\Div || $node instanceof Node\Expr\AssignOp) {
if ($node instanceof Node\Expr\AssignOp) {
$left = $node->var;
$right = $node->expr;
} elseif ($node instanceof Node\Expr\BinaryOp) {
$left = $node->left;
$right = $node->right;
} else {
throw new \PHPStan\ShouldNotHappenException();
}
$leftType = $this->getType($left);
$rightType = $this->getType($right);
if ($leftType instanceof BooleanType) {
$leftType = new IntegerType($leftType->isNullable());
}
if ($rightType instanceof BooleanType) {
$rightType = new IntegerType($rightType->isNullable());
}
if ($node instanceof Expr\AssignOp\Div || $node instanceof Expr\BinaryOp\Div) {
if (!$leftType instanceof MixedType && !$rightType instanceof MixedType) {
return new FloatType(false);
}
}
if ($leftType instanceof FloatType && !$rightType instanceof MixedType || $rightType instanceof FloatType && !$leftType instanceof MixedType) {
return new FloatType(false);
}
if ($leftType instanceof IntegerType && $rightType instanceof IntegerType) {
return new IntegerType(false);
}
}
if ($node instanceof LNumber) {
return new IntegerType(false);
} elseif ($node instanceof ConstFetch) {
$constName = strtolower((string) $node->name);
if (in_array($constName, ['true', 'false'], true)) {
return new BooleanType(false);
}
if ($constName === 'null') {
return new NullType();
}
} elseif ($node instanceof String_) {
return new StringType(false);
} elseif ($node instanceof DNumber) {
return new FloatType(false);
} elseif ($node instanceof Expr\Closure) {
return new ObjectType('Closure', false);
} elseif ($node instanceof New_) {
if ($node->class instanceof Name) {
if (count($node->class->parts) === 1) {
if ($node->class->parts[0] === 'static') {
return new MixedType(false);
} elseif ($node->class->parts[0] === 'self') {
return new ObjectType($this->getClass(), false);
}
}
return new ObjectType((string) $node->class, false);
}
} elseif ($node instanceof Array_) {
$possiblyCallable = false;
if (count($node->items) === 2) {
$firstItem = $node->items[0]->value;
if (($this->getType($firstItem) instanceof ObjectType || $this->getType($firstItem) instanceof StringType) && $this->getType($node->items[1]->value) instanceof StringType) {
$possiblyCallable = true;
}
}
return new ArrayType($this->getCombinedType(array_map(function (Expr\ArrayItem $item) : Type {
return $this->getType($item->value);
}, $node->items)), false, true, $possiblyCallable);
} elseif ($node instanceof Int_) {
return new IntegerType(false);
} elseif ($node instanceof Bool_) {
return new BooleanType(false);
} elseif ($node instanceof Double) {
return new FloatType(false);
} elseif ($node instanceof \PhpParser\Node\Expr\Cast\String_) {
return new StringType(false);
} elseif ($node instanceof \PhpParser\Node\Expr\Cast\Array_) {
return new ArrayType(new MixedType(true), false);
} elseif ($node instanceof Object_) {
return new ObjectType('stdClass', false);
} elseif ($node instanceof Unset_) {
return new NullType();
} elseif ($node instanceof Node\Expr\ClassConstFetch) {
if ($node->class instanceof Name) {
$constantClass = (string) $node->class;
if ($constantClass === 'self') {
$constantClass = $this->getClass();
}
} else {
$constantClassType = $this->getType($node->class);
if ($constantClassType->getClass() !== null) {
$constantClass = $constantClassType->getClass();
}
}
if (isset($constantClass)) {
$constantName = $node->name;
if ($this->broker->hasClass($constantClass)) {
$constantClassReflection = $this->broker->getClass($constantClass);
if ($constantClassReflection->hasConstant($constantName)) {
$constant = $constantClassReflection->getConstant($constantName);
$typeFromValue = $this->getTypeFromValue($constant->getValue());
if ($typeFromValue !== null) {
return $typeFromValue;
}
}
}
}
}
$exprString = $this->printer->prettyPrintExpr($node);
if (isset($this->moreSpecificTypes[$exprString])) {
return $this->moreSpecificTypes[$exprString];
}
if ($node instanceof Variable && is_string($node->name)) {
if (!$this->hasVariableType($node->name)) {
return new MixedType(true);
}
return $this->getVariableType($node->name);
}
if ($node instanceof Expr\ArrayDimFetch && $node->dim !== null) {
$arrayType = $this->getType($node->var);
if ($arrayType instanceof ArrayType) {
return $arrayType->getItemType();
}
}
if ($node instanceof MethodCall && is_string($node->name)) {
$methodCalledOnType = $this->getType($node->var);
if ($methodCalledOnType->getClass() !== null && $this->broker->hasClass($methodCalledOnType->getClass())) {
$methodClassReflection = $this->broker->getClass($methodCalledOnType->getClass());
if (!$methodClassReflection->hasMethod($node->name)) {
return new MixedType(true);
}
$methodReflection = $methodClassReflection->getMethod($node->name);
foreach ($this->broker->getDynamicMethodReturnTypeExtensionsForClass($methodCalledOnType->getClass()) as $dynamicMethodReturnTypeExtension) {
if (!$dynamicMethodReturnTypeExtension->isMethodSupported($methodReflection)) {
continue;
}
return $dynamicMethodReturnTypeExtension->getTypeFromMethodCall($methodReflection, $node, $this);
}
if ($methodReflection->getReturnType() instanceof StaticType) {
if ($methodReflection->getReturnType()->isNullable()) {
return $methodCalledOnType->makeNullable();
}
return $methodCalledOnType;
}
return $methodReflection->getReturnType();
}
}
if ($node instanceof Expr\StaticCall && is_string($node->name) && $node->class instanceof Name) {
$calleeClass = $this->resolveName($node->class);
if ($calleeClass !== null && $this->broker->hasClass($calleeClass)) {
$staticMethodClassReflection = $this->broker->getClass($calleeClass);
if (!$staticMethodClassReflection->hasMethod($node->name)) {
return new MixedType(true);
}
$staticMethodReflection = $staticMethodClassReflection->getMethod($node->name);
if ($staticMethodReflection->getReturnType() instanceof StaticType) {
$nodeClassString = (string) $node->class;
if ($nodeClassString === 'parent' && $this->getClass() !== null) {
return new StaticType($this->getClass(), $staticMethodReflection->getReturnType()->isNullable());
}
$calleeType = new ObjectType($calleeClass, false);
if ($staticMethodReflection->getReturnType()->isNullable()) {
return $calleeType->makeNullable();
}
return $calleeType;
}
return $staticMethodReflection->getReturnType();
}
}
if ($node instanceof PropertyFetch && is_string($node->name)) {
$propertyFetchedOnType = $this->getType($node->var);
if ($propertyFetchedOnType->getClass() !== null && $this->broker->hasClass($propertyFetchedOnType->getClass())) {
$propertyClassReflection = $this->broker->getClass($propertyFetchedOnType->getClass());
if (!$propertyClassReflection->hasProperty($node->name)) {
return new MixedType(true);
}
return $propertyClassReflection->getProperty($node->name, $this)->getType();
}
}
if ($node instanceof Expr\StaticPropertyFetch && is_string($node->name) && $node->class instanceof Name) {
$staticPropertyHolderClass = $this->resolveName($node->class);
if ($staticPropertyHolderClass !== null && $this->broker->hasClass($staticPropertyHolderClass)) {
$staticPropertyClassReflection = $this->broker->getClass($staticPropertyHolderClass);
if (!$staticPropertyClassReflection->hasProperty($node->name)) {
return new MixedType(true);
}
return $staticPropertyClassReflection->getProperty($node->name, $this)->getType();
}
}
if ($node instanceof FuncCall && $node->name instanceof Name) {
$arrayFunctionsThatDependOnClosureReturnType = ['array_map' => 0, 'array_reduce' => 1];
$functionName = (string) $node->name;
if (isset($arrayFunctionsThatDependOnClosureReturnType[$functionName]) && isset($node->args[$arrayFunctionsThatDependOnClosureReturnType[$functionName]]) && $node->args[$arrayFunctionsThatDependOnClosureReturnType[$functionName]]->value instanceof Expr\Closure) {
$closure = $node->args[$arrayFunctionsThatDependOnClosureReturnType[$functionName]]->value;
$anonymousFunctionType = $this->getFunctionType($closure->returnType, $closure->returnType === null, false);
if ($functionName === 'array_reduce') {
return $anonymousFunctionType;
}
return new ArrayType($anonymousFunctionType, false);
}
$arrayFunctionsThatDependOnArgumentType = ['array_filter' => 0, 'array_unique' => 0, 'array_reverse' => 0];
if (isset($arrayFunctionsThatDependOnArgumentType[$functionName]) && isset($node->args[$arrayFunctionsThatDependOnArgumentType[$functionName]])) {
$argumentValue = $node->args[$arrayFunctionsThatDependOnArgumentType[$functionName]]->value;
return $this->getType($argumentValue);
}
if (!$this->broker->hasFunction($node->name, $this)) {
return new MixedType(true);
}
return $this->broker->getFunction($node->name, $this)->getReturnType();
}
return new MixedType(false);
}