Skip to content

Commit d55c4f2

Browse files
committed
ConstantArrayType - preserve array being a list
1 parent 97f0039 commit d55c4f2

File tree

3 files changed

+98
-1
lines changed

3 files changed

+98
-1
lines changed

src/Type/Php/ArrayMapFunctionReturnTypeExtension.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use PHPStan\Analyser\Scope;
77
use PHPStan\Reflection\FunctionReflection;
88
use PHPStan\Reflection\ParametersAcceptorSelector;
9+
use PHPStan\Type\Accessory\AccessoryArrayListType;
910
use PHPStan\Type\Accessory\NonEmptyArrayType;
1011
use PHPStan\Type\ArrayType;
1112
use PHPStan\Type\Constant\ConstantArrayTypeBuilder;
@@ -75,7 +76,11 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
7576
$constantArray->isOptionalKey($i),
7677
);
7778
}
78-
$arrayTypes[] = $returnedArrayBuilder->getArray();
79+
$returnedArray = $returnedArrayBuilder->getArray();
80+
if ($constantArray->isList()->yes()) {
81+
$returnedArray = AccessoryArrayListType::intersectWith($returnedArray);
82+
}
83+
$arrayTypes[] = $returnedArray;
7984
}
8085

8186
$mappedArrayType = TypeCombinator::union(...$arrayTypes);

tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -934,6 +934,11 @@ public function testLists(): void
934934
]);
935935
}
936936

937+
public function testConditionalListRule(): void
938+
{
939+
$this->analyse([__DIR__ . '/data/return-list-rule.php'], []);
940+
}
941+
937942
public function testBug6856(): void
938943
{
939944
$this->analyse([__DIR__ . '/data/bug-6856.php'], []);
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<?php
2+
3+
namespace ReturnListRule;
4+
5+
use PhpParser\Node;
6+
use PhpParser\Node\Expr;
7+
use PhpParser\Node\Expr\BinaryOp;
8+
use PHPStan\Analyser\Scope;
9+
use PHPStan\Rules\Rule;
10+
use PHPStan\Rules\RuleError;
11+
use PHPStan\Rules\RuleErrorBuilder;
12+
use PHPStan\Type\Type;
13+
use PHPStan\Type\VerbosityLevel;
14+
/**
15+
* @implements Rule<BinaryOp>
16+
*/
17+
class BinaryOpEnumValueRule implements Rule
18+
{
19+
20+
/** @var class-string<BinaryOp> */
21+
private string $className;
22+
23+
/**
24+
* @param class-string $operator
25+
*/
26+
public function __construct(string $operator, ?string $okMessage = null)
27+
{
28+
$this->className = $operator;
29+
}
30+
31+
public function getNodeType(): string
32+
{
33+
return $this->className;
34+
}
35+
36+
/**
37+
* @param BinaryOp $node
38+
* @return list<RuleError>
39+
*/
40+
public function processNode(Node $node, Scope $scope): array
41+
{
42+
$leftType = $scope->getType($node->left);
43+
$rightType = $scope->getType($node->right);
44+
$isDirectCompareType = true;
45+
46+
if (!$this->isEnumWithValue($leftType) || !$this->isEnumWithValue($rightType)) {
47+
$isDirectCompareType = false;
48+
}
49+
50+
$errors = [];
51+
$leftError = $this->processOpExpression($node->left, $leftType, $node->getOperatorSigil());
52+
$rightError = $this->processOpExpression($node->right, $rightType, $node->getOperatorSigil());
53+
54+
if ($leftError !== null) {
55+
$errors[] = $leftError;
56+
}
57+
58+
if ($rightError !== null && $rightError !== $leftError) {
59+
$errors[] = $rightError;
60+
}
61+
62+
if (!$isDirectCompareType && $errors === []) {
63+
return [];
64+
}
65+
66+
if ($isDirectCompareType && $errors === []) {
67+
$errors[] = sprintf(
68+
'Cannot compare %s to %s',
69+
$leftType->describe(VerbosityLevel::typeOnly()),
70+
$rightType->describe(VerbosityLevel::typeOnly()),
71+
);
72+
}
73+
74+
return array_map(static fn (string $message) => RuleErrorBuilder::message($message)->build(), $errors);
75+
}
76+
77+
private function processOpExpression(Expr $expression, Type $expressionType, string $sigil): ?string
78+
{
79+
return null;
80+
}
81+
82+
private function isEnumWithValue(Type $type): bool
83+
{
84+
return false;
85+
}
86+
87+
}

0 commit comments

Comments
 (0)