Skip to content

Commit 54815a4

Browse files
committed
Merge remote-tracking branch 'upstream/2.2.x' into generics-on-phpstan-types
2 parents 66dd071 + 5cd44d5 commit 54815a4

23 files changed

+1203
-316
lines changed

phpstan-baseline.neon

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1587,6 +1587,12 @@ parameters:
15871587
count: 1
15881588
path: src/Type/Php/FunctionExistsFunctionTypeSpecifyingExtension.php
15891589

1590+
-
1591+
rawMessage: 'Doing instanceof PHPStan\Type\Constant\ConstantArrayType is error-prone and deprecated. Use Type::getConstantArrays() instead.'
1592+
identifier: phpstanApi.instanceofType
1593+
count: 1
1594+
path: src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php
1595+
15901596
-
15911597
rawMessage: 'Doing instanceof PHPStan\Type\Constant\ConstantStringType is error-prone and deprecated. Use Type::getConstantStrings() instead.'
15921598
identifier: phpstanApi.instanceofType
@@ -1722,7 +1728,7 @@ parameters:
17221728
-
17231729
rawMessage: Doing instanceof PHPStan\Type\IntersectionType is error-prone and deprecated.
17241730
identifier: phpstanApi.instanceofType
1725-
count: 5
1731+
count: 6
17261732
path: src/Type/TypeCombinator.php
17271733

17281734
-

src/Analyser/MutatingScope.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3609,6 +3609,15 @@ private function createConditionalExpressions(
36093609
}
36103610

36113611
foreach ($variableTypeGuards as $guardExprString => $guardHolder) {
3612+
if (
3613+
array_key_exists($exprString, $theirExpressionTypes)
3614+
&& $theirExpressionTypes[$exprString]->getCertainty()->yes()
3615+
&& array_key_exists($guardExprString, $theirExpressionTypes)
3616+
&& $theirExpressionTypes[$guardExprString]->getCertainty()->yes()
3617+
&& !$guardHolder->getType()->isSuperTypeOf($theirExpressionTypes[$guardExprString]->getType())->no()
3618+
) {
3619+
continue;
3620+
}
36123621
$conditionalExpression = new ConditionalExpressionHolder([$guardExprString => $guardHolder], $holder);
36133622
$conditionalExpressions[$exprString][$conditionalExpression->getKey()] = $conditionalExpression;
36143623
}

src/Internal/AgentDetector.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,14 @@ final class AgentDetector
1010
{
1111

1212
public const ENV_VARS = [
13+
'AUGMENT_AGENT',
14+
'AMP_CURRENT_THREAD_ID',
1315
'AI_AGENT',
1416
'CURSOR_TRACE_ID',
1517
'CURSOR_AGENT',
1618
'GEMINI_CLI',
1719
'CODEX_SANDBOX',
20+
'CODEX_THREAD_ID',
1821
'AUGMENT_AGENT',
1922
'OPENCODE_CLIENT',
2023
'OPENCODE',

src/Rules/Methods/ParentMethodHelper.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ public function collectParentMethods(string $methodName, ClassReflection $class)
5353
continue;
5454
}
5555

56+
// Skip traits that inherited the method from a sub-trait
57+
// The actual declaring trait will be processed separately
58+
if ($methodReflection->getBetterReflection()->getDeclaringClass()->getName() !== $trait->getName()) {
59+
continue;
60+
}
61+
5662
$declaringTrait = $trait->getNativeMethod($methodName)->getDeclaringClass();
5763
$parentMethods[] = [
5864
$this->phpClassReflectionExtension->createUserlandMethodReflection(

src/Type/Constant/ConstantArrayType.php

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,23 @@ public function __construct(
122122
$this->isList = $isList;
123123
}
124124

125+
/**
126+
* @param list<ConstantIntegerType|ConstantStringType> $keyTypes
127+
* @param array<int, Type> $valueTypes
128+
* @param non-empty-list<int> $nextAutoIndexes
129+
* @param int[] $optionalKeys
130+
*/
131+
protected function recreate(
132+
array $keyTypes,
133+
array $valueTypes,
134+
array $nextAutoIndexes = [0],
135+
array $optionalKeys = [],
136+
?TrinaryLogic $isList = null,
137+
): self
138+
{
139+
return new self($keyTypes, $valueTypes, $nextAutoIndexes, $optionalKeys, $isList);
140+
}
141+
125142
public function getConstantArrays(): array
126143
{
127144
return [$this];
@@ -765,7 +782,7 @@ public function unsetOffset(Type $offsetType, bool $preserveListCertainty = fals
765782
return new NeverType();
766783
}
767784

768-
return new self($newKeyTypes, $newValueTypes, $this->nextAutoIndexes, $newOptionalKeys, $newIsList);
785+
return $this->recreate($newKeyTypes, $newValueTypes, $this->nextAutoIndexes, $newOptionalKeys, $newIsList);
769786
}
770787

771788
return $this;
@@ -812,7 +829,7 @@ public function unsetOffset(Type $offsetType, bool $preserveListCertainty = fals
812829
return new NeverType();
813830
}
814831

815-
return new self($this->keyTypes, $this->valueTypes, $this->nextAutoIndexes, $optionalKeys, $newIsList);
832+
return $this->recreate($this->keyTypes, $this->valueTypes, $this->nextAutoIndexes, $optionalKeys, $newIsList);
816833
}
817834

818835
$optionalKeys = $this->optionalKeys;
@@ -842,7 +859,7 @@ public function unsetOffset(Type $offsetType, bool $preserveListCertainty = fals
842859
return new NeverType();
843860
}
844861

845-
return new self($this->keyTypes, $this->valueTypes, $this->nextAutoIndexes, $optionalKeys, $newIsList);
862+
return $this->recreate($this->keyTypes, $this->valueTypes, $this->nextAutoIndexes, $optionalKeys, $newIsList);
846863
}
847864

848865
/**
@@ -1066,7 +1083,7 @@ public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $pre
10661083

10671084
if ($length === 0 || ($offset < 0 && $length < 0 && $offset - $length >= 0)) {
10681085
// 0 / 0, 3 / 0 or e.g. -3 / -3 or -3 / -4 and so on never extract anything
1069-
return new self([], []);
1086+
return $this->recreate([], []);
10701087
}
10711088

10721089
if ($length < 0) {
@@ -1370,7 +1387,7 @@ private function removeLastElements(int $length): self
13701387
$optionalKeysRemoved--;
13711388
}
13721389

1373-
return new self(
1390+
return $this->recreate(
13741391
$keyTypes,
13751392
$valueTypes,
13761393
$nextAutoindexes,
@@ -1474,7 +1491,7 @@ public function generalizeValues(): self
14741491
$valueTypes[] = $valueType->generalize(GeneralizePrecision::lessSpecific());
14751492
}
14761493

1477-
return new self($this->keyTypes, $valueTypes, $this->nextAutoIndexes, $this->optionalKeys, $this->isList);
1494+
return $this->recreate($this->keyTypes, $valueTypes, $this->nextAutoIndexes, $this->optionalKeys, $this->isList);
14781495
}
14791496

14801497
private function degradeToGeneralArray(): Type
@@ -1522,7 +1539,7 @@ private function getKeysOrValuesArray(array $types): self
15221539
static fn (int $i): ConstantIntegerType => new ConstantIntegerType($i),
15231540
array_keys($types),
15241541
);
1525-
return new self($keyTypes, $types, $autoIndexes, $this->optionalKeys, TrinaryLogic::createYes());
1542+
return $this->recreate($keyTypes, $types, $autoIndexes, $this->optionalKeys, TrinaryLogic::createYes());
15261543
}
15271544

15281545
$keyTypes = [];
@@ -1551,7 +1568,7 @@ private function getKeysOrValuesArray(array $types): self
15511568
$maxIndex++;
15521569
}
15531570

1554-
return new self($keyTypes, $valueTypes, $autoIndexes, $optionalKeys, TrinaryLogic::createYes());
1571+
return $this->recreate($keyTypes, $valueTypes, $autoIndexes, $optionalKeys, TrinaryLogic::createYes());
15551572
}
15561573

15571574
public function describe(VerbosityLevel $level): string
@@ -1716,7 +1733,7 @@ public function traverse(callable $cb): Type
17161733
return $this;
17171734
}
17181735

1719-
return new self($this->keyTypes, $valueTypes, $this->nextAutoIndexes, $this->optionalKeys, $this->isList);
1736+
return $this->recreate($this->keyTypes, $valueTypes, $this->nextAutoIndexes, $this->optionalKeys, $this->isList);
17201737
}
17211738

17221739
public function traverseSimultaneously(Type $right, callable $cb): Type
@@ -1742,7 +1759,7 @@ public function traverseSimultaneously(Type $right, callable $cb): Type
17421759
return $this;
17431760
}
17441761

1745-
return new self($this->keyTypes, $valueTypes, $this->nextAutoIndexes, $this->optionalKeys, $this->isList);
1762+
return $this->recreate($this->keyTypes, $valueTypes, $this->nextAutoIndexes, $this->optionalKeys, $this->isList);
17461763
}
17471764

17481765
public function isKeysSupersetOf(self $otherArray): bool
@@ -1820,7 +1837,7 @@ public function mergeWith(self $otherArray): self
18201837
$nextAutoIndexes = array_values(array_unique(array_merge($this->nextAutoIndexes, $otherArray->nextAutoIndexes)));
18211838
sort($nextAutoIndexes);
18221839

1823-
return new self($this->keyTypes, $valueTypes, $nextAutoIndexes, $optionalKeys, $this->isList->and($otherArray->isList));
1840+
return $this->recreate($this->keyTypes, $valueTypes, $nextAutoIndexes, $optionalKeys, $this->isList->and($otherArray->isList));
18241841
}
18251842

18261843
/**
@@ -1874,7 +1891,7 @@ public function makeOffsetRequired(Type $offsetType): self
18741891
}
18751892

18761893
if (count($this->optionalKeys) !== count($optionalKeys)) {
1877-
return new self($this->keyTypes, $this->valueTypes, $this->nextAutoIndexes, array_values($optionalKeys), $this->isList);
1894+
return $this->recreate($this->keyTypes, $this->valueTypes, $this->nextAutoIndexes, array_values($optionalKeys), $this->isList);
18781895
}
18791896

18801897
break;
@@ -1893,7 +1910,7 @@ public function makeList(): Type
18931910
return new NeverType();
18941911
}
18951912

1896-
return new self($this->keyTypes, $this->valueTypes, $this->nextAutoIndexes, $this->optionalKeys, TrinaryLogic::createYes());
1913+
return $this->recreate($this->keyTypes, $this->valueTypes, $this->nextAutoIndexes, $this->optionalKeys, TrinaryLogic::createYes());
18971914
}
18981915

18991916
public function toPhpDocNode(): TypeNode

src/Type/Generic/TemplateConstantArrayType.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
namespace PHPStan\Type\Generic;
44

5+
use PHPStan\TrinaryLogic;
56
use PHPStan\Type\Constant\ConstantArrayType;
7+
use PHPStan\Type\Constant\ConstantIntegerType;
8+
use PHPStan\Type\Constant\ConstantStringType;
69
use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait;
710
use PHPStan\Type\Type;
811

@@ -35,4 +38,28 @@ public function __construct(
3538
$this->default = $default;
3639
}
3740

41+
/**
42+
* @param list<ConstantIntegerType|ConstantStringType> $keyTypes
43+
* @param array<int, Type> $valueTypes
44+
* @param non-empty-list<int> $nextAutoIndexes
45+
* @param int[] $optionalKeys
46+
*/
47+
protected function recreate(
48+
array $keyTypes,
49+
array $valueTypes,
50+
array $nextAutoIndexes = [0],
51+
array $optionalKeys = [],
52+
?TrinaryLogic $isList = null,
53+
): ConstantArrayType
54+
{
55+
return new self(
56+
$this->scope,
57+
$this->strategy,
58+
$this->variance,
59+
$this->name,
60+
new ConstantArrayType($keyTypes, $valueTypes, $nextAutoIndexes, $optionalKeys, $isList),
61+
$this->default,
62+
);
63+
}
64+
3865
}

src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php

Lines changed: 70 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616
use PHPStan\Reflection\FunctionReflection;
1717
use PHPStan\Type\Accessory\NonEmptyArrayType;
1818
use PHPStan\Type\ArrayType;
19+
use PHPStan\Type\Constant\ConstantArrayType;
1920
use PHPStan\Type\FunctionTypeSpecifyingExtension;
2021
use PHPStan\Type\MixedType;
22+
use PHPStan\Type\Type;
2123
use PHPStan\Type\TypeCombinator;
2224
use function count;
2325
use function strtolower;
@@ -113,25 +115,18 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n
113115
}
114116

115117
$specifiedTypes = new SpecifiedTypes();
116-
if (
117-
$context->true()
118-
|| (
119-
$context->false()
120-
&& count($arrayValueType->getFiniteTypes()) > 0
121-
&& count($needleType->getFiniteTypes()) > 0
122-
&& $arrayType->isIterableAtLeastOnce()->yes()
123-
)
124-
) {
118+
$narrowingValueType = $this->computeNeedleNarrowingType($context, $needleType, $arrayType, $arrayValueType);
119+
if ($narrowingValueType !== null) {
125120
$specifiedTypes = $this->typeSpecifier->create(
126121
$needleExpr,
127-
$arrayValueType,
122+
$narrowingValueType,
128123
$context,
129124
$scope,
130125
);
131126
if ($needleExpr instanceof AlwaysRememberedExpr) {
132127
$specifiedTypes = $specifiedTypes->unionWith($this->typeSpecifier->create(
133128
$needleExpr->getExpr(),
134-
$arrayValueType,
129+
$narrowingValueType,
135130
$context,
136131
$scope,
137132
));
@@ -171,4 +166,68 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n
171166
return $specifiedTypes;
172167
}
173168

169+
/**
170+
* Computes the type to narrow the needle against, or null if no narrowing should occur.
171+
* In true context, returns the array value type directly.
172+
* In false context, returns only the values guaranteed to be in every possible variant of the array.
173+
*/
174+
private function computeNeedleNarrowingType(TypeSpecifierContext $context, Type $needleType, Type $arrayType, Type $arrayValueType): ?Type
175+
{
176+
if ($context->true()) {
177+
return $arrayValueType;
178+
}
179+
180+
if (
181+
!$context->false()
182+
|| count($needleType->getFiniteTypes()) === 0
183+
|| !$arrayType->isIterableAtLeastOnce()->yes()
184+
) {
185+
return null;
186+
}
187+
188+
$arrays = $arrayType->getArrays();
189+
$guaranteedValueTypePerArray = [];
190+
foreach ($arrays as $array) {
191+
if ($array instanceof ConstantArrayType) {
192+
$innerGuaranteeValueType = [];
193+
foreach ($array->getValueTypes() as $i => $valueType) {
194+
if ($array->isOptionalKey($i)) {
195+
continue;
196+
}
197+
198+
$finiteTypes = $valueType->getFiniteTypes();
199+
if (count($finiteTypes) !== 1) {
200+
continue;
201+
}
202+
203+
$innerGuaranteeValueType[] = $finiteTypes[0];
204+
}
205+
206+
if (count($innerGuaranteeValueType) === 0) {
207+
return null;
208+
}
209+
210+
$guaranteedValueTypePerArray[] = TypeCombinator::union(...$innerGuaranteeValueType);
211+
} else {
212+
$finiteValueType = $array->getIterableValueType()->getFiniteTypes();
213+
if (count($finiteValueType) !== 1) {
214+
return null;
215+
}
216+
217+
$guaranteedValueTypePerArray[] = $finiteValueType[0];
218+
}
219+
}
220+
221+
if (count($guaranteedValueTypePerArray) === 0) {
222+
return null;
223+
}
224+
225+
$guaranteedValueType = TypeCombinator::intersect(...$guaranteedValueTypePerArray);
226+
if (count($guaranteedValueType->getFiniteTypes()) === 0) {
227+
return null;
228+
}
229+
230+
return $guaranteedValueType;
231+
}
232+
174233
}

0 commit comments

Comments
 (0)