Skip to content

Commit f436abf

Browse files
committed
[PHP 8.5] Support for first class callables in constant expressions (initializers)
1 parent df5ede5 commit f436abf

File tree

5 files changed

+254
-132
lines changed

5 files changed

+254
-132
lines changed

src/Analyser/MutatingScope.php

Lines changed: 6 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -52,19 +52,16 @@
5252
use PHPStan\Php\PhpVersion;
5353
use PHPStan\Php\PhpVersionFactory;
5454
use PHPStan\Php\PhpVersions;
55-
use PHPStan\PhpDoc\Tag\TemplateTag;
5655
use PHPStan\Reflection\Assertions;
5756
use PHPStan\Reflection\AttributeReflection;
5857
use PHPStan\Reflection\AttributeReflectionFactory;
59-
use PHPStan\Reflection\Callables\CallableParametersAcceptor;
6058
use PHPStan\Reflection\Callables\SimpleImpurePoint;
6159
use PHPStan\Reflection\Callables\SimpleThrowPoint;
6260
use PHPStan\Reflection\ClassConstantReflection;
6361
use PHPStan\Reflection\ClassMemberReflection;
6462
use PHPStan\Reflection\ClassReflection;
6563
use PHPStan\Reflection\Dummy\DummyConstructorReflection;
6664
use PHPStan\Reflection\ExtendedMethodReflection;
67-
use PHPStan\Reflection\ExtendedParametersAcceptor;
6865
use PHPStan\Reflection\ExtendedPropertyReflection;
6966
use PHPStan\Reflection\FunctionReflection;
7067
use PHPStan\Reflection\InitializerExprContext;
@@ -1276,30 +1273,18 @@ private function resolveType(string $exprString, Expr $node): Type
12761273
} elseif ($node instanceof Node\Scalar\Float_) {
12771274
return $this->initializerExprTypeResolver->getType($node, InitializerExprContext::fromScope($this));
12781275
} elseif ($node instanceof Expr\CallLike && $node->isFirstClassCallable()) {
1279-
if ($node instanceof FuncCall) {
1280-
if ($node->name instanceof Name) {
1281-
if ($this->reflectionProvider->hasFunction($node->name, $this)) {
1282-
$function = $this->reflectionProvider->getFunction($node->name, $this);
1283-
return $this->createFirstClassCallable(
1284-
$function,
1285-
$function->getVariants(),
1286-
);
1287-
}
1288-
1289-
return new ObjectType(Closure::class);
1290-
}
1291-
1276+
if ($node instanceof FuncCall && $node->name instanceof Expr) {
12921277
$callableType = $this->getType($node->name);
12931278
if (!$callableType->isCallable()->yes()) {
12941279
return new ObjectType(Closure::class);
12951280
}
12961281

1297-
return $this->createFirstClassCallable(
1282+
return $this->initializerExprTypeResolver->createFirstClassCallable(
12981283
null,
12991284
$callableType->getCallableParametersAcceptors($this),
1285+
$this->nativeTypesPromoted,
13001286
);
13011287
}
1302-
13031288
if ($node instanceof MethodCall) {
13041289
if (!$node->name instanceof Node\Identifier) {
13051290
return new ObjectType(Closure::class);
@@ -1311,39 +1296,14 @@ private function resolveType(string $exprString, Expr $node): Type
13111296
return new ObjectType(Closure::class);
13121297
}
13131298

1314-
return $this->createFirstClassCallable(
1315-
$method,
1316-
$method->getVariants(),
1317-
);
1318-
}
1319-
1320-
if ($node instanceof Expr\StaticCall) {
1321-
if (!$node->class instanceof Name) {
1322-
return new ObjectType(Closure::class);
1323-
}
1324-
1325-
if (!$node->name instanceof Node\Identifier) {
1326-
return new ObjectType(Closure::class);
1327-
}
1328-
1329-
$classType = $this->resolveTypeByNameWithLateStaticBinding($node->class, $node->name);
1330-
$methodName = $node->name->toString();
1331-
if (!$classType->hasMethod($methodName)->yes()) {
1332-
return new ObjectType(Closure::class);
1333-
}
1334-
1335-
$method = $classType->getMethod($methodName, $this);
1336-
return $this->createFirstClassCallable(
1299+
return $this->initializerExprTypeResolver->createFirstClassCallable(
13371300
$method,
13381301
$method->getVariants(),
1302+
$this->nativeTypesPromoted,
13391303
);
13401304
}
13411305

1342-
if ($node instanceof New_) {
1343-
return new ErrorType();
1344-
}
1345-
1346-
throw new ShouldNotHappenException();
1306+
return $this->initializerExprTypeResolver->getFirstClassCallableType($node, InitializerExprContext::fromScope($this), $this->nativeTypesPromoted);
13471307
} elseif ($node instanceof Expr\Closure || $node instanceof Expr\ArrowFunction) {
13481308
$parameters = [];
13491309
$isVariadic = false;
@@ -2743,91 +2703,6 @@ private function issetCheckUndefined(Expr $expr): ?bool
27432703
return null;
27442704
}
27452705

2746-
/**
2747-
* @param ParametersAcceptor[] $variants
2748-
*/
2749-
private function createFirstClassCallable(
2750-
FunctionReflection|ExtendedMethodReflection|null $function,
2751-
array $variants,
2752-
): Type
2753-
{
2754-
$closureTypes = [];
2755-
2756-
foreach ($variants as $variant) {
2757-
$returnType = $variant->getReturnType();
2758-
if ($variant instanceof ExtendedParametersAcceptor) {
2759-
$returnType = $this->nativeTypesPromoted ? $variant->getNativeReturnType() : $returnType;
2760-
}
2761-
2762-
$templateTags = [];
2763-
foreach ($variant->getTemplateTypeMap()->getTypes() as $templateType) {
2764-
if (!$templateType instanceof TemplateType) {
2765-
continue;
2766-
}
2767-
$templateTags[$templateType->getName()] = new TemplateTag(
2768-
$templateType->getName(),
2769-
$templateType->getBound(),
2770-
$templateType->getDefault(),
2771-
$templateType->getVariance(),
2772-
);
2773-
}
2774-
2775-
$throwPoints = [];
2776-
$impurePoints = [];
2777-
$acceptsNamedArguments = TrinaryLogic::createYes();
2778-
$mustUseReturnValue = TrinaryLogic::createMaybe();
2779-
if ($variant instanceof CallableParametersAcceptor) {
2780-
$throwPoints = $variant->getThrowPoints();
2781-
$impurePoints = $variant->getImpurePoints();
2782-
$acceptsNamedArguments = $variant->acceptsNamedArguments();
2783-
$mustUseReturnValue = $variant->mustUseReturnValue();
2784-
} elseif ($function !== null) {
2785-
$returnTypeForThrow = $variant->getReturnType();
2786-
$throwType = $function->getThrowType();
2787-
if ($throwType === null) {
2788-
if ($returnTypeForThrow instanceof NeverType && $returnTypeForThrow->isExplicit()) {
2789-
$throwType = new ObjectType(Throwable::class);
2790-
}
2791-
}
2792-
2793-
if ($throwType !== null) {
2794-
if (!$throwType->isVoid()->yes()) {
2795-
$throwPoints[] = SimpleThrowPoint::createExplicit($throwType, true);
2796-
}
2797-
} else {
2798-
if (!(new ObjectType(Throwable::class))->isSuperTypeOf($returnTypeForThrow)->yes()) {
2799-
$throwPoints[] = SimpleThrowPoint::createImplicit();
2800-
}
2801-
}
2802-
2803-
$impurePoint = SimpleImpurePoint::createFromVariant($function, $variant);
2804-
if ($impurePoint !== null) {
2805-
$impurePoints[] = $impurePoint;
2806-
}
2807-
2808-
$acceptsNamedArguments = $function->acceptsNamedArguments();
2809-
$mustUseReturnValue = $function->mustUseReturnValue();
2810-
}
2811-
2812-
$parameters = $variant->getParameters();
2813-
$closureTypes[] = new ClosureType(
2814-
$parameters,
2815-
$returnType,
2816-
$variant->isVariadic(),
2817-
$variant->getTemplateTypeMap(),
2818-
$variant->getResolvedTemplateTypeMap(),
2819-
$variant instanceof ExtendedParametersAcceptor ? $variant->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(),
2820-
$templateTags,
2821-
$throwPoints,
2822-
$impurePoints,
2823-
acceptsNamedArguments: $acceptsNamedArguments,
2824-
mustUseReturnValue: $mustUseReturnValue,
2825-
);
2826-
}
2827-
2828-
return TypeCombinator::union(...$closureTypes);
2829-
}
2830-
28312706
/** @api */
28322707
public function getNativeType(Expr $expr): Type
28332708
{

0 commit comments

Comments
 (0)