Skip to content

Commit df5ede5

Browse files
committed
[PHP 8.5] Support for casts in constant (initializer) expressions
1 parent 57e2adf commit df5ede5

File tree

4 files changed

+90
-53
lines changed

4 files changed

+90
-53
lines changed

src/Analyser/MutatingScope.php

Lines changed: 4 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,6 @@
1111
use PhpParser\Node\Expr;
1212
use PhpParser\Node\Expr\Array_;
1313
use PhpParser\Node\Expr\BinaryOp;
14-
use PhpParser\Node\Expr\Cast\Bool_;
15-
use PhpParser\Node\Expr\Cast\Double;
16-
use PhpParser\Node\Expr\Cast\Int_;
17-
use PhpParser\Node\Expr\Cast\Object_;
1814
use PhpParser\Node\Expr\Cast\Unset_;
1915
use PhpParser\Node\Expr\ConstFetch;
2016
use PhpParser\Node\Expr\FuncCall;
@@ -124,7 +120,6 @@
124120
use PHPStan\Type\NonAcceptingNeverType;
125121
use PHPStan\Type\NonexistentParentClassType;
126122
use PHPStan\Type\NullType;
127-
use PHPStan\Type\ObjectShapeType;
128123
use PHPStan\Type\ObjectType;
129124
use PHPStan\Type\ObjectWithoutClassType;
130125
use PHPStan\Type\ParserNodeTypeToPHPStanType;
@@ -139,7 +134,6 @@
139134
use PHPStan\Type\UnionType;
140135
use PHPStan\Type\VerbosityLevel;
141136
use PHPStan\Type\VoidType;
142-
use stdClass;
143137
use Throwable;
144138
use ValueError;
145139
use function abs;
@@ -1765,55 +1759,12 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu
17651759

17661760
} elseif ($node instanceof Array_) {
17671761
return $this->initializerExprTypeResolver->getArrayType($node, fn (Expr $expr): Type => $this->getType($expr));
1768-
} elseif ($node instanceof Int_) {
1769-
return $this->getType($node->expr)->toInteger();
1770-
} elseif ($node instanceof Bool_) {
1771-
return $this->getType($node->expr)->toBoolean();
1772-
} elseif ($node instanceof Double) {
1773-
return $this->getType($node->expr)->toFloat();
1774-
} elseif ($node instanceof Node\Expr\Cast\String_) {
1775-
return $this->getType($node->expr)->toString();
1776-
} elseif ($node instanceof Node\Expr\Cast\Array_) {
1777-
return $this->getType($node->expr)->toArray();
1778-
} elseif ($node instanceof Node\Scalar\MagicConst) {
1779-
return $this->initializerExprTypeResolver->getType($node, InitializerExprContext::fromScope($this));
1780-
} elseif ($node instanceof Object_) {
1781-
$castToObject = static function (Type $type): Type {
1782-
$constantArrays = $type->getConstantArrays();
1783-
if (count($constantArrays) > 0) {
1784-
$objects = [];
1785-
foreach ($constantArrays as $constantArray) {
1786-
$properties = [];
1787-
$optionalProperties = [];
1788-
foreach ($constantArray->getKeyTypes() as $i => $keyType) {
1789-
$valueType = $constantArray->getValueTypes()[$i];
1790-
$optional = $constantArray->isOptionalKey($i);
1791-
if ($optional) {
1792-
$optionalProperties[] = $keyType->getValue();
1793-
}
1794-
$properties[$keyType->getValue()] = $valueType;
1795-
}
1796-
1797-
$objects[] = TypeCombinator::intersect(new ObjectShapeType($properties, $optionalProperties), new ObjectType(stdClass::class));
1798-
}
1799-
1800-
return TypeCombinator::union(...$objects);
1801-
}
1802-
if ($type->isObject()->yes()) {
1803-
return $type;
1804-
}
1805-
1806-
return new ObjectType('stdClass');
1807-
};
1808-
1809-
$exprType = $this->getType($node->expr);
1810-
if ($exprType instanceof UnionType) {
1811-
return TypeCombinator::union(...array_map($castToObject, $exprType->getTypes()));
1812-
}
1813-
1814-
return $castToObject($exprType);
18151762
} elseif ($node instanceof Unset_) {
18161763
return new NullType();
1764+
} elseif ($node instanceof Expr\Cast) {
1765+
return $this->initializerExprTypeResolver->getCastType($node, fn (Expr $expr): Type => $this->getType($expr));
1766+
} elseif ($node instanceof Node\Scalar\MagicConst) {
1767+
return $this->initializerExprTypeResolver->getType($node, InitializerExprContext::fromScope($this));
18171768
} elseif ($node instanceof Expr\PostInc || $node instanceof Expr\PostDec) {
18181769
return $this->getType($node->var);
18191770
} elseif ($node instanceof Expr\PreInc || $node instanceof Expr\PreDec) {

src/Reflection/InitializerExprTypeResolver.php

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
use PhpParser\Node\Arg;
77
use PhpParser\Node\Expr;
88
use PhpParser\Node\Expr\BinaryOp;
9+
use PhpParser\Node\Expr\Cast\Array_;
10+
use PhpParser\Node\Expr\Cast\Bool_;
11+
use PhpParser\Node\Expr\Cast\Double;
12+
use PhpParser\Node\Expr\Cast\Object_;
913
use PhpParser\Node\Expr\ClassConstFetch;
1014
use PhpParser\Node\Expr\ConstFetch;
1115
use PhpParser\Node\Expr\PropertyFetch;
@@ -61,6 +65,7 @@
6165
use PHPStan\Type\MixedType;
6266
use PHPStan\Type\NeverType;
6367
use PHPStan\Type\NullType;
68+
use PHPStan\Type\ObjectShapeType;
6469
use PHPStan\Type\ObjectType;
6570
use PHPStan\Type\ObjectWithoutClassType;
6671
use PHPStan\Type\StaticType;
@@ -74,8 +79,10 @@
7479
use PHPStan\Type\TypeUtils;
7580
use PHPStan\Type\TypeWithClassName;
7681
use PHPStan\Type\UnionType;
82+
use stdClass;
7783
use function array_key_exists;
7884
use function array_keys;
85+
use function array_map;
7986
use function array_merge;
8087
use function assert;
8188
use function ceil;
@@ -177,6 +184,9 @@ public function getType(Expr $expr, InitializerExprContext $context): Type
177184
if ($expr instanceof Expr\Array_) {
178185
return $this->getArrayType($expr, fn (Expr $expr): Type => $this->getType($expr, $context));
179186
}
187+
if ($expr instanceof Expr\Cast) {
188+
return $this->getCastType($expr, fn (Expr $expr): Type => $this->getType($expr, $context));
189+
}
180190
if ($expr instanceof Expr\ArrayDimFetch && $expr->dim !== null) {
181191
$var = $this->getType($expr->var, $context);
182192
$dim = $this->getType($expr->dim, $context);
@@ -599,6 +609,66 @@ public function getArrayType(Expr\Array_ $expr, callable $getTypeCallback): Type
599609
return $arrayType;
600610
}
601611

612+
/**
613+
* @param callable(Expr): Type $getTypeCallback
614+
*/
615+
public function getCastType(Expr\Cast $expr, callable $getTypeCallback): Type
616+
{
617+
if ($expr instanceof \PhpParser\Node\Expr\Cast\Int_) {
618+
return $getTypeCallback($expr->expr)->toInteger();
619+
}
620+
if ($expr instanceof Bool_) {
621+
return $getTypeCallback($expr->expr)->toBoolean();
622+
}
623+
if ($expr instanceof Double) {
624+
return $getTypeCallback($expr->expr)->toFloat();
625+
}
626+
if ($expr instanceof \PhpParser\Node\Expr\Cast\String_) {
627+
return $getTypeCallback($expr->expr)->toString();
628+
}
629+
if ($expr instanceof Array_) {
630+
return $getTypeCallback($expr->expr)->toArray();
631+
}
632+
if ($expr instanceof Object_) {
633+
$castToObject = static function (Type $type): Type {
634+
$constantArrays = $type->getConstantArrays();
635+
if (count($constantArrays) > 0) {
636+
$objects = [];
637+
foreach ($constantArrays as $constantArray) {
638+
$properties = [];
639+
$optionalProperties = [];
640+
foreach ($constantArray->getKeyTypes() as $i => $keyType) {
641+
$valueType = $constantArray->getValueTypes()[$i];
642+
$optional = $constantArray->isOptionalKey($i);
643+
if ($optional) {
644+
$optionalProperties[] = $keyType->getValue();
645+
}
646+
$properties[$keyType->getValue()] = $valueType;
647+
}
648+
649+
$objects[] = TypeCombinator::intersect(new ObjectShapeType($properties, $optionalProperties), new ObjectType(stdClass::class));
650+
}
651+
652+
return TypeCombinator::union(...$objects);
653+
}
654+
if ($type->isObject()->yes()) {
655+
return $type;
656+
}
657+
658+
return new ObjectType('stdClass');
659+
};
660+
661+
$exprType = $getTypeCallback($expr->expr);
662+
if ($exprType instanceof UnionType) {
663+
return TypeCombinator::union(...array_map($castToObject, $exprType->getTypes()));
664+
}
665+
666+
return $castToObject($exprType);
667+
}
668+
669+
return new MixedType();
670+
}
671+
602672
/**
603673
* @param callable(Expr): Type $getTypeCallback
604674
*/
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php // lint >= 8.5
2+
3+
namespace CastInInitializer;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
const FOO = (bool) 1;
8+
9+
assertType('true', FOO);
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php // lint >= 8.5
2+
3+
namespace CastInInitializer;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
assertType('true', FOO);

0 commit comments

Comments
 (0)