Skip to content

Commit 6cf0ab2

Browse files
staabmondrejmirtes
authored andcommitted
Implement CastObjectHandler
1 parent d91b0f2 commit 6cf0ab2

File tree

3 files changed

+90
-29
lines changed

3 files changed

+90
-29
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Analyser\Generator\ExprHandler;
4+
5+
use Generator;
6+
use PhpParser\Node\Expr;
7+
use PhpParser\Node\Stmt;
8+
use PHPStan\Analyser\ExpressionContext;
9+
use PHPStan\Analyser\Generator\ExprAnalysisRequest;
10+
use PHPStan\Analyser\Generator\ExprAnalysisResult;
11+
use PHPStan\Analyser\Generator\ExprHandler;
12+
use PHPStan\Analyser\Generator\GeneratorScope;
13+
use PHPStan\Analyser\SpecifiedTypes;
14+
use PHPStan\DependencyInjection\AutowiredService;
15+
use PHPStan\Reflection\InitializerExprTypeResolver;
16+
17+
/**
18+
* @implements ExprHandler<Expr\Cast\Object_>
19+
*/
20+
#[AutowiredService]
21+
final class CastObjectHandler implements ExprHandler
22+
{
23+
24+
public function __construct(private InitializerExprTypeResolver $initializerExprTypeResolver)
25+
{
26+
}
27+
28+
public function supports(Expr $expr): bool
29+
{
30+
return $expr instanceof Expr\Cast\Object_;
31+
}
32+
33+
public function analyseExpr(
34+
Stmt $stmt,
35+
Expr $expr,
36+
GeneratorScope $scope,
37+
ExpressionContext $context,
38+
?callable $alternativeNodeCallback,
39+
): Generator
40+
{
41+
$exprResult = yield new ExprAnalysisRequest($stmt, $expr->expr, $scope, $context->enterDeep(), $alternativeNodeCallback);
42+
43+
return new ExprAnalysisResult(
44+
$this->initializerExprTypeResolver->getCastObjectType($exprResult->type),
45+
$this->initializerExprTypeResolver->getCastObjectType($exprResult->nativeType),
46+
$scope,
47+
hasYield: false,
48+
isAlwaysTerminating: false,
49+
throwPoints: [],
50+
impurePoints: [],
51+
specifiedTruthyTypes: new SpecifiedTypes(),
52+
specifiedFalseyTypes: new SpecifiedTypes(),
53+
specifiedNullTypes: new SpecifiedTypes(),
54+
);
55+
}
56+
57+
}

src/Reflection/InitializerExprTypeResolver.php

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -702,43 +702,47 @@ public function getCastType(Expr\Cast $expr, callable $getTypeCallback): Type
702702
return $getTypeCallback($expr->expr)->toArray();
703703
}
704704
if ($expr instanceof Object_) {
705-
$castToObject = static function (Type $type): Type {
706-
$constantArrays = $type->getConstantArrays();
707-
if (count($constantArrays) > 0) {
708-
$objects = [];
709-
foreach ($constantArrays as $constantArray) {
710-
$properties = [];
711-
$optionalProperties = [];
712-
foreach ($constantArray->getKeyTypes() as $i => $keyType) {
713-
$valueType = $constantArray->getValueTypes()[$i];
714-
$optional = $constantArray->isOptionalKey($i);
715-
if ($optional) {
716-
$optionalProperties[] = $keyType->getValue();
717-
}
718-
$properties[$keyType->getValue()] = $valueType;
719-
}
705+
return $this->getCastObjectType($getTypeCallback($expr->expr));
706+
}
720707

721-
$objects[] = TypeCombinator::intersect(new ObjectShapeType($properties, $optionalProperties), new ObjectType(stdClass::class));
708+
return new MixedType();
709+
}
710+
711+
public function getCastObjectType(Type $exprType): Type
712+
{
713+
$castToObject = static function (Type $type): Type {
714+
$constantArrays = $type->getConstantArrays();
715+
if (count($constantArrays) > 0) {
716+
$objects = [];
717+
foreach ($constantArrays as $constantArray) {
718+
$properties = [];
719+
$optionalProperties = [];
720+
foreach ($constantArray->getKeyTypes() as $i => $keyType) {
721+
$valueType = $constantArray->getValueTypes()[$i];
722+
$optional = $constantArray->isOptionalKey($i);
723+
if ($optional) {
724+
$optionalProperties[] = $keyType->getValue();
725+
}
726+
$properties[$keyType->getValue()] = $valueType;
722727
}
723728

724-
return TypeCombinator::union(...$objects);
725-
}
726-
if ($type->isObject()->yes()) {
727-
return $type;
729+
$objects[] = TypeCombinator::intersect(new ObjectShapeType($properties, $optionalProperties), new ObjectType(stdClass::class));
728730
}
729731

730-
return new ObjectType('stdClass');
731-
};
732-
733-
$exprType = $getTypeCallback($expr->expr);
734-
if ($exprType instanceof UnionType) {
735-
return TypeCombinator::union(...array_map($castToObject, $exprType->getTypes()));
732+
return TypeCombinator::union(...$objects);
736733
}
734+
if ($type->isObject()->yes()) {
735+
return $type;
736+
}
737+
738+
return new ObjectType('stdClass');
739+
};
737740

738-
return $castToObject($exprType);
741+
if ($exprType instanceof UnionType) {
742+
return TypeCombinator::union(...array_map($castToObject, $exprType->getTypes()));
739743
}
740744

741-
return new MixedType();
745+
return $castToObject($exprType);
742746
}
743747

744748
/**

tests/PHPStan/Analyser/Generator/data/gnsr.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ function doCast() {
241241

242242
assertType('1', (int) $a);
243243
assertType("array{'1'}", (array) $a);
244-
// assertType("array{'1'}", (object) $a);
244+
assertType('stdClass', (object) $a);
245245
assertType('1.0', (double) $a);
246246
assertType('true', (bool) $a);
247247
assertType("'1'", (string) $a);

0 commit comments

Comments
 (0)