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); }