Skip to content

Commit 32fe871

Browse files
authored
Improve dynamic return type extension for get_post() (#113)
1 parent 0aeacde commit 32fe871

File tree

2 files changed

+80
-27
lines changed

2 files changed

+80
-27
lines changed

src/GetPostDynamicFunctionReturnTypeExtension.php

Lines changed: 63 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,15 @@
1212
use PhpParser\Node\Expr\FuncCall;
1313
use PHPStan\Analyser\Scope;
1414
use PHPStan\Reflection\FunctionReflection;
15-
use PHPStan\Reflection\ParametersAcceptorSelector;
16-
use PHPStan\Type\Type;
1715
use PHPStan\Type\ArrayType;
18-
use PHPStan\Type\StringType;
16+
use PHPStan\Type\Constant\ConstantStringType;
1917
use PHPStan\Type\IntegerType;
2018
use PHPStan\Type\MixedType;
21-
use PHPStan\Type\ObjectType;
2219
use PHPStan\Type\NullType;
20+
use PHPStan\Type\ObjectType;
21+
use PHPStan\Type\StringType;
22+
use PHPStan\Type\Type;
2323
use PHPStan\Type\TypeCombinator;
24-
use PHPStan\Type\Constant\ConstantStringType;
2524

2625
class GetPostDynamicFunctionReturnTypeExtension implements \PHPStan\Type\DynamicFunctionReturnTypeExtension
2726
{
@@ -30,35 +29,76 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo
3029
return in_array($functionReflection->getName(), ['get_post', 'get_page_by_path'], true);
3130
}
3231

33-
// phpcs:ignore SlevomatCodingStandard.Functions.UnusedParameter
32+
/**
33+
* @see https://developer.wordpress.org/reference/functions/get_post/
34+
* @see https://developer.wordpress.org/reference/functions/get_page_by_path/
35+
*/
3436
public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type
3537
{
3638
$output = 'OBJECT';
3739
$args = $functionCall->getArgs();
3840

39-
if (count($functionCall->args) >= 2) {
40-
$argumentType = $scope->getType($args[1]->value);
41-
41+
$returnType = self::objectType();
42+
if (count($args) >= 2) {
4243
// When called with an $output that isn't a constant string, return default return type
43-
if (! $argumentType instanceof ConstantStringType) {
44-
return ParametersAcceptorSelector::selectFromArgs(
45-
$scope,
46-
$args,
47-
$functionReflection->getVariants()
48-
)->getReturnType();
44+
if (! $scope->getType($args[1]->value) instanceof ConstantStringType) {
45+
$returnType = self::defaultType();
46+
}
47+
if ($args[1]->value instanceof ConstFetch) {
48+
$output = $args[1]->value->name->getLast();
49+
switch ($output) {
50+
case 'ARRAY_A':
51+
$returnType = self::associativeArrayType();
52+
break;
53+
case 'ARRAY_N':
54+
$returnType = self::numericArrayType();
55+
break;
56+
}
4957
}
5058
}
5159

52-
if (count($args) >= 2 && $args[1]->value instanceof ConstFetch) {
53-
$output = $args[1]->value->name->getLast();
54-
}
55-
if ($output === 'ARRAY_A') {
56-
return TypeCombinator::union(new ArrayType(new StringType(), new MixedType()), new NullType());
60+
if ($functionReflection->getName() !== 'get_post') {
61+
return $returnType;
5762
}
58-
if ($output === 'ARRAY_N') {
59-
return TypeCombinator::union(new ArrayType(new IntegerType(), new MixedType()), new NullType());
63+
64+
$firstArgType = count($args) > 0 ? $scope->getType($args[0]->value) : new NullType();
65+
if ($firstArgType instanceof ObjectType && $firstArgType->isInstanceOf('WP_Post')->yes()) {
66+
$returnType = TypeCombinator::remove($returnType, new NullType());
6067
}
6168

62-
return TypeCombinator::union(new ObjectType('WP_Post'), new NullType());
69+
return $returnType;
70+
}
71+
72+
protected static function objectType(): Type
73+
{
74+
return TypeCombinator::union(
75+
new ObjectType('WP_Post'),
76+
new NullType()
77+
);
78+
}
79+
80+
protected static function associativeArrayType(): Type
81+
{
82+
return TypeCombinator::union(
83+
new ArrayType(new StringType(), new MixedType()),
84+
new NullType()
85+
);
86+
}
87+
88+
protected static function numericArrayType(): Type
89+
{
90+
return TypeCombinator::union(
91+
new ArrayType(new IntegerType(), new MixedType()),
92+
new NullType()
93+
);
94+
}
95+
96+
protected static function defaultType(): Type
97+
{
98+
return TypeCombinator::union(
99+
self::objectType(),
100+
self::associativeArrayType(),
101+
self::numericArrayType()
102+
);
63103
}
64104
}

tests/data/get_post.php

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,29 @@
66

77
use function PHPStan\Testing\assertType;
88

9+
/** @var \WP_Post $wpPostType */
10+
$wpPostType = new \stdClass();
11+
912
// Default output
13+
assertType('WP_Post|null', get_post());
1014
assertType('WP_Post|null', get_post(1));
1115
assertType('WP_Post|null', get_post(1, OBJECT));
12-
assertType('WP_Post|null', get_post(1, 'Hello'));
13-
14-
// Unknown output
15-
assertType('array|\WP_Post|null', get_post(1, $_GET['foo']));
16+
assertType('array<int|string, mixed>|WP_Post|null', get_post(1, $_GET['foo']));
17+
assertType('WP_Post', get_post($wpPostType));
18+
assertType('WP_Post', get_post($wpPostType, OBJECT));
19+
assertType('array<int|string, mixed>|WP_Post', get_post($wpPostType, $_GET['foo']));
20+
assertType('WP_Post|null', get_page_by_path('page/path'));
21+
assertType('WP_Post|null', get_page_by_path('page/path', OBJECT));
22+
assertType('array<int|string, mixed>|WP_Post|null', get_page_by_path('page/path', $_GET['foo']));
1623

1724
// Associative array output
25+
assertType('array<string, mixed>|null', get_post(null, ARRAY_A));
1826
assertType('array<string, mixed>|null', get_post(1, ARRAY_A));
27+
assertType('array<string, mixed>', get_post($wpPostType, ARRAY_A));
28+
assertType('array<string, mixed>|null', get_page_by_path('page/path', ARRAY_A));
1929

2030
// Numeric array output
31+
assertType('array<int, mixed>|null', get_post(null, ARRAY_N));
2132
assertType('array<int, mixed>|null', get_post(1, ARRAY_N));
33+
assertType('array<int, mixed>', get_post($wpPostType, ARRAY_N));
34+
assertType('array<int, mixed>|null', get_page_by_path('page/path', ARRAY_N));

0 commit comments

Comments
 (0)