Skip to content

Commit

Permalink
Constant array cast to object is converted to object shape
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Apr 6, 2023
1 parent ccabc69 commit 07bb4aa
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 3 deletions.
6 changes: 3 additions & 3 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ parameters:

-
message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#"
count: 2
count: 3
path: src/Analyser/MutatingScope.php

-
Expand Down Expand Up @@ -1183,15 +1183,15 @@ parameters:
#^Call to deprecated method getInstance\\(\\) of class PHPStan\\\\Broker\\\\Broker\\:
Use PHPStan\\\\Reflection\\\\ReflectionProviderStaticAccessor instead$#
"""
count: 1
count: 2
path: src/Type/ObjectShapeType.php

-
message: """
#^Call to deprecated method getUniversalObjectCratesClasses\\(\\) of class PHPStan\\\\Broker\\\\Broker\\:
Inject %%universalObjectCratesClasses%% parameter instead\\.$#
"""
count: 1
count: 2
path: src/Type/ObjectShapeType.php

-
Expand Down
25 changes: 25 additions & 0 deletions src/Analyser/MutatingScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@
use PHPStan\Type\NeverType;
use PHPStan\Type\NonexistentParentClassType;
use PHPStan\Type\NullType;
use PHPStan\Type\ObjectShapeType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\ParserNodeTypeToPHPStanType;
use PHPStan\Type\StaticType;
Expand All @@ -108,6 +109,7 @@
use PHPStan\Type\UnionType;
use PHPStan\Type\VerbosityLevel;
use PHPStan\Type\VoidType;
use stdClass;
use Throwable;
use function abs;
use function array_key_exists;
Expand Down Expand Up @@ -1370,6 +1372,29 @@ private function resolveType(string $exprString, Expr $node): Type
return $this->initializerExprTypeResolver->getType($node, InitializerExprContext::fromScope($this));
} elseif ($node instanceof Object_) {
$castToObject = static function (Type $type): Type {
if (count($type->getConstantArrays()) > 0) {
$objects = [];
foreach ($type->getConstantArrays() as $constantArray) {
$properties = [];
$optionalProperties = [];
foreach ($constantArray->getKeyTypes() as $i => $keyType) {
if (!$keyType instanceof ConstantStringType) {
// an object with integer properties is >weird<
continue;
}
$valueType = $constantArray->getValueTypes()[$i];
$optional = $constantArray->isOptionalKey($i);
if ($optional) {
$optionalProperties[] = $keyType->getValue();
}
$properties[$keyType->getValue()] = $valueType;
}

$objects[] = TypeCombinator::intersect(new ObjectShapeType($properties, $optionalProperties), new ObjectType(stdClass::class));
}

return TypeCombinator::union(...$objects);
}
if ($type->isObject()->yes()) {
return $type;
}
Expand Down
13 changes: 13 additions & 0 deletions src/Type/ObjectShapeType.php
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,19 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult
return $type->isAcceptedWithReasonBy($this, $strictTypes);
}

$reflectionProvider = ReflectionProviderStaticAccessor::getInstance();
foreach ($type->getObjectClassReflections() as $classReflection) {
if (!UniversalObjectCratesClassReflectionExtension::isUniversalObjectCrate(
$reflectionProvider,
Broker::getInstance()->getUniversalObjectCratesClasses(),
$classReflection,
)) {
continue;
}

return AcceptsResult::createMaybe();
}

$result = AcceptsResult::createYes();
$scope = new OutOfClassScope();
foreach ($this->properties as $propertyName => $propertyType) {
Expand Down
12 changes: 12 additions & 0 deletions tests/PHPStan/Analyser/data/object-shape.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,16 @@ public function doFoo2(object $o): void
assertType('object{foo: ObjectShape\Foo, bar: int, baz?: string}', $o);
}

public function doBaz(): void
{
assertType('object{}&stdClass', (object) []);

$a = ['bar' => 2];
if (rand(0, 1)) {
$a['foo'] = 1;
}

assertType('object{bar: 2, foo?: 1}&stdClass', (object) $a);
}

}
19 changes: 19 additions & 0 deletions tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2912,6 +2912,10 @@ public function testObjectShapes(): void
$this->checkUnionTypes = true;
$this->checkExplicitMixed = true;
$this->analyse([__DIR__ . '/data/object-shapes.php'], [
[
'Parameter #1 $o of method ObjectShapesAcceptance\Foo::doBar() expects object{foo: int, bar: string}, stdClass given.',
13,
],
[
'Parameter #1 $o of method ObjectShapesAcceptance\Foo::doBar() expects object{foo: int, bar: string}, Exception given.',
14,
Expand All @@ -2931,6 +2935,21 @@ public function testObjectShapes(): void
'Parameter #1 $std of method ObjectShapesAcceptance\Foo::requireStdClass() expects stdClass, object{foo: string, bar: int} given.',
40,
],
[
'Parameter #1 $o of method ObjectShapesAcceptance\Foo::doBar() expects object{foo: int, bar: string}, object{foo: string, bar: int}&stdClass given.',
43,
'Property ($foo) type int does not accept type string.',
],
[
'Parameter #1 $o of method ObjectShapesAcceptance\Foo::doBar() expects object{foo: int, bar: string}, object given.',
53,
'• object might not have property $foo.
• object might not have property $bar.',
],
[
'Parameter #1 $o of method ObjectShapesAcceptance\Foo::doBar() expects object{foo: int, bar: string}, stdClass given.',
54,
],
]);
}

Expand Down
9 changes: 9 additions & 0 deletions tests/PHPStan/Rules/Methods/data/object-shapes.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,20 @@ public function doBaz(
$this->doBar($q);

$this->requireStdClass($o);
$this->requireStdClass((object) []);
$this->doBar((object) ['foo' => 1, 'bar' => 'bar']); // OK
$this->doBar((object) ['foo' => 'foo', 'bar' => 1]); // Error
}

public function requireStdClass(stdClass $std): void
{

}

public function acceptsObject(object $o): void
{
$this->doBar($o);
$this->doBar(new \stdClass());
}

}

0 comments on commit 07bb4aa

Please sign in to comment.