|
5 | 5 | use Closure; |
6 | 6 | use Nette\Utils\Strings; |
7 | 7 | use PhpParser\Node\Arg; |
| 8 | +use PhpParser\Node\ComplexType; |
8 | 9 | use PhpParser\Node\Expr; |
9 | 10 | use PhpParser\Node\Expr\BinaryOp; |
10 | 11 | use PhpParser\Node\Expr\Cast\Array_; |
|
16 | 17 | use PhpParser\Node\Expr\FuncCall; |
17 | 18 | use PhpParser\Node\Expr\New_; |
18 | 19 | use PhpParser\Node\Expr\PropertyFetch; |
| 20 | +use PhpParser\Node\Expr\Variable; |
19 | 21 | use PhpParser\Node\Identifier; |
20 | 22 | use PhpParser\Node\Name; |
| 23 | +use PhpParser\Node\Param; |
21 | 24 | use PhpParser\Node\Scalar\Float_; |
22 | 25 | use PhpParser\Node\Scalar\Int_; |
23 | 26 | use PhpParser\Node\Scalar\MagicConst; |
|
36 | 39 | use PHPStan\Reflection\Callables\CallableParametersAcceptor; |
37 | 40 | use PHPStan\Reflection\Callables\SimpleImpurePoint; |
38 | 41 | use PHPStan\Reflection\Callables\SimpleThrowPoint; |
| 42 | +use PHPStan\Reflection\Native\NativeParameterReflection; |
39 | 43 | use PHPStan\Reflection\ReflectionProvider\ReflectionProviderProvider; |
40 | 44 | use PHPStan\ShouldNotHappenException; |
41 | 45 | use PHPStan\TrinaryLogic; |
|
68 | 72 | use PHPStan\Type\GeneralizePrecision; |
69 | 73 | use PHPStan\Type\Generic\GenericClassStringType; |
70 | 74 | use PHPStan\Type\Generic\TemplateType; |
| 75 | +use PHPStan\Type\Generic\TemplateTypeMap; |
71 | 76 | use PHPStan\Type\Generic\TemplateTypeVarianceMap; |
72 | 77 | use PHPStan\Type\IntegerRangeType; |
73 | 78 | use PHPStan\Type\IntegerType; |
74 | 79 | use PHPStan\Type\IntersectionType; |
75 | 80 | use PHPStan\Type\MixedType; |
76 | 81 | use PHPStan\Type\NeverType; |
| 82 | +use PHPStan\Type\NonexistentParentClassType; |
77 | 83 | use PHPStan\Type\NullType; |
78 | 84 | use PHPStan\Type\ObjectShapeType; |
79 | 85 | use PHPStan\Type\ObjectType; |
80 | 86 | use PHPStan\Type\ObjectWithoutClassType; |
| 87 | +use PHPStan\Type\ParserNodeTypeToPHPStanType; |
81 | 88 | use PHPStan\Type\StaticType; |
82 | 89 | use PHPStan\Type\StringType; |
83 | 90 | use PHPStan\Type\ThisType; |
|
106 | 113 | use function is_float; |
107 | 114 | use function is_int; |
108 | 115 | use function is_numeric; |
| 116 | +use function is_string; |
109 | 117 | use function max; |
110 | 118 | use function min; |
111 | 119 | use function sprintf; |
@@ -201,6 +209,56 @@ public function getType(Expr $expr, InitializerExprContext $context): Type |
201 | 209 | if ($expr instanceof Expr\CallLike && $expr->isFirstClassCallable()) { |
202 | 210 | return $this->getFirstClassCallableType($expr, $context, false); |
203 | 211 | } |
| 212 | + if ($expr instanceof Expr\Closure && $expr->static) { |
| 213 | + $parameters = []; |
| 214 | + $isVariadic = false; |
| 215 | + $firstOptionalParameterIndex = null; |
| 216 | + foreach ($expr->params as $i => $param) { |
| 217 | + $isOptionalCandidate = $param->default !== null || $param->variadic; |
| 218 | + |
| 219 | + if ($isOptionalCandidate) { |
| 220 | + if ($firstOptionalParameterIndex === null) { |
| 221 | + $firstOptionalParameterIndex = $i; |
| 222 | + } |
| 223 | + } else { |
| 224 | + $firstOptionalParameterIndex = null; |
| 225 | + } |
| 226 | + } |
| 227 | + |
| 228 | + foreach ($expr->params as $i => $param) { |
| 229 | + if ($param->variadic) { |
| 230 | + $isVariadic = true; |
| 231 | + } |
| 232 | + if (!$param->var instanceof Variable || !is_string($param->var->name)) { |
| 233 | + throw new ShouldNotHappenException(); |
| 234 | + } |
| 235 | + $parameters[] = new NativeParameterReflection( |
| 236 | + $param->var->name, |
| 237 | + $firstOptionalParameterIndex !== null && $i >= $firstOptionalParameterIndex, |
| 238 | + $this->getFunctionType($param->type, $this->isParameterValueNullable($param), false, $context), |
| 239 | + $param->byRef |
| 240 | + ? PassedByReference::createCreatesNewVariable() |
| 241 | + : PassedByReference::createNo(), |
| 242 | + $param->variadic, |
| 243 | + $param->default !== null ? $this->getType($param->default, $context) : null, |
| 244 | + ); |
| 245 | + } |
| 246 | + |
| 247 | + $returnType = new MixedType(false); |
| 248 | + if ($expr->returnType !== null) { |
| 249 | + $returnType = $this->getFunctionType($expr->returnType, false, false, $context); |
| 250 | + } |
| 251 | + |
| 252 | + return new ClosureType( |
| 253 | + $parameters, |
| 254 | + $returnType, |
| 255 | + $isVariadic, |
| 256 | + TemplateTypeMap::createEmpty(), |
| 257 | + TemplateTypeMap::createEmpty(), |
| 258 | + TemplateTypeVarianceMap::createEmpty(), |
| 259 | + acceptsNamedArguments: TrinaryLogic::createYes(), |
| 260 | + ); |
| 261 | + } |
204 | 262 | if ($expr instanceof Expr\ArrayDimFetch && $expr->dim !== null) { |
205 | 263 | $var = $this->getType($expr->var, $context); |
206 | 264 | $dim = $this->getType($expr->dim, $context); |
@@ -683,6 +741,67 @@ public function getCastType(Expr\Cast $expr, callable $getTypeCallback): Type |
683 | 741 | return new MixedType(); |
684 | 742 | } |
685 | 743 |
|
| 744 | + /** |
| 745 | + * @param Name|Identifier|ComplexType|null $type |
| 746 | + */ |
| 747 | + public function getFunctionType($type, bool $isNullable, bool $isVariadic, InitializerExprContext $context): Type |
| 748 | + { |
| 749 | + if ($isNullable) { |
| 750 | + return TypeCombinator::addNull( |
| 751 | + $this->getFunctionType($type, false, $isVariadic, $context), |
| 752 | + ); |
| 753 | + } |
| 754 | + if ($isVariadic) { |
| 755 | + if (!$this->phpVersion->supportsNamedArguments()) { |
| 756 | + return new ArrayType(new UnionType([new IntegerType(), new StringType()]), $this->getFunctionType( |
| 757 | + $type, |
| 758 | + false, |
| 759 | + false, |
| 760 | + $context, |
| 761 | + )); |
| 762 | + } |
| 763 | + |
| 764 | + return TypeCombinator::intersect(new ArrayType(new IntegerType(), $this->getFunctionType( |
| 765 | + $type, |
| 766 | + false, |
| 767 | + false, |
| 768 | + $context, |
| 769 | + )), new AccessoryArrayListType()); |
| 770 | + } |
| 771 | + |
| 772 | + if ($type instanceof Name) { |
| 773 | + $className = (string) $type; |
| 774 | + $lowercasedClassName = strtolower($className); |
| 775 | + if ($lowercasedClassName === 'parent') { |
| 776 | + $classReflection = null; |
| 777 | + if ($context->getClassName() !== null && $this->getReflectionProvider()->hasClass($context->getClassName())) { |
| 778 | + $classReflection = $this->getReflectionProvider()->getClass($context->getClassName()); |
| 779 | + } |
| 780 | + if ($classReflection !== null && $classReflection->getParentClass() !== null) { |
| 781 | + return new ObjectType($classReflection->getParentClass()->getName()); |
| 782 | + } |
| 783 | + |
| 784 | + return new NonexistentParentClassType(); |
| 785 | + } |
| 786 | + } |
| 787 | + |
| 788 | + $classReflection = null; |
| 789 | + if ($context->getClassName() !== null && $this->getReflectionProvider()->hasClass($context->getClassName())) { |
| 790 | + $classReflection = $this->getReflectionProvider()->getClass($context->getClassName()); |
| 791 | + } |
| 792 | + |
| 793 | + return ParserNodeTypeToPHPStanType::resolve($type, $classReflection); |
| 794 | + } |
| 795 | + |
| 796 | + private function isParameterValueNullable(Param $parameter): bool |
| 797 | + { |
| 798 | + if ($parameter->default instanceof ConstFetch) { |
| 799 | + return strtolower((string) $parameter->default->name) === 'null'; |
| 800 | + } |
| 801 | + |
| 802 | + return false; |
| 803 | + } |
| 804 | + |
686 | 805 | public function getFirstClassCallableType(Expr\CallLike $expr, InitializerExprContext $context, bool $nativeTypesPromoted): Type |
687 | 806 | { |
688 | 807 | if ($expr instanceof FuncCall) { |
|
0 commit comments