Skip to content

Commit 56a30c2

Browse files
committed
Different approach for previous commit
I did not like that `mixed` was narrowed to `iterable` inside foreach which created a bunch of new errors on level < 9. Also the attempt to restore iteratee type after foreach analysis is a flawed approach IMHO, and should be avoided.
1 parent 252390f commit 56a30c2

File tree

7 files changed

+39
-70
lines changed

7 files changed

+39
-70
lines changed

src/Analyser/NodeScopeResolver.php

Lines changed: 12 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,6 @@
181181
use PHPStan\Type\Generic\TemplateTypeVarianceMap;
182182
use PHPStan\Type\IntegerType;
183183
use PHPStan\Type\IntersectionType;
184-
use PHPStan\Type\IterableType;
185184
use PHPStan\Type\MixedType;
186185
use PHPStan\Type\NeverType;
187186
use PHPStan\Type\NullType;
@@ -1251,19 +1250,6 @@ private function processStmtNode(
12511250
$exprType = $scope->getType($stmt->expr);
12521251
$isIterableAtLeastOnce = $exprType->isIterableAtLeastOnce();
12531252
if ($exprType->isIterable()->no() || $isIterableAtLeastOnce->maybe()) {
1254-
$foreachType = $this->getForeachIterateeType();
1255-
if (
1256-
!$foreachType->isSuperTypeOf($exprType)->yes()
1257-
&& $finalScope->getType($stmt->expr)->equals($foreachType)
1258-
) {
1259-
// restore iteratee type, in case the type was narrowed while entering the foreach
1260-
$finalScope = $finalScope->assignExpression(
1261-
$stmt->expr,
1262-
$exprType,
1263-
$scope->getNativeType($stmt->expr),
1264-
);
1265-
}
1266-
12671253
$finalScope = $finalScope->mergeWith($scope->filterByTruthyValue(new BooleanOr(
12681254
new BinaryOp\Identical(
12691255
$stmt->expr,
@@ -6078,16 +6064,18 @@ private function produceArrayDimFetchAssignValueToWrite(array $dimFetchStack, ar
60786064
}
60796065
$valueToWrite = $offsetValueType->setExistingOffsetValueType($offsetType, $valueToWrite);
60806066

6081-
if ($hasOffsetType !== null) {
6082-
$valueToWrite = TypeCombinator::intersect(
6083-
$valueToWrite,
6084-
$hasOffsetType,
6085-
);
6086-
} elseif ($valueToWrite->isArray()->yes()) {
6087-
$valueToWrite = TypeCombinator::intersect(
6088-
$valueToWrite,
6089-
new NonEmptyArrayType(),
6090-
);
6067+
if ($valueToWrite->isArray()->yes()) {
6068+
if ($hasOffsetType !== null) {
6069+
$valueToWrite = TypeCombinator::intersect(
6070+
$valueToWrite,
6071+
$hasOffsetType,
6072+
);
6073+
} else {
6074+
$valueToWrite = TypeCombinator::intersect(
6075+
$valueToWrite,
6076+
new NonEmptyArrayType(),
6077+
);
6078+
}
60916079
}
60926080

60936081
} else {
@@ -6327,32 +6315,12 @@ private function processVarAnnotation(MutatingScope $scope, array $variableNames
63276315
return $scope;
63286316
}
63296317

6330-
private function getForeachIterateeType(): Type
6331-
{
6332-
return new IterableType(new MixedType(), new MixedType());
6333-
}
6334-
63356318
private function enterForeach(MutatingScope $scope, MutatingScope $originalScope, Foreach_ $stmt): MutatingScope
63366319
{
63376320
if ($stmt->expr instanceof Variable && is_string($stmt->expr->name)) {
63386321
$scope = $this->processVarAnnotation($scope, [$stmt->expr->name], $stmt);
63396322
}
63406323

6341-
// narrow the iteratee type to those supported by foreach
6342-
$foreachType = $this->getForeachIterateeType();
6343-
$scope = $scope->specifyExpressionType(
6344-
$stmt->expr,
6345-
TypeCombinator::intersect(
6346-
$scope->getType($stmt->expr),
6347-
$foreachType,
6348-
),
6349-
TypeCombinator::intersect(
6350-
$scope->getNativeType($stmt->expr),
6351-
$foreachType,
6352-
),
6353-
TrinaryLogic::createYes(),
6354-
);
6355-
63566324
$iterateeType = $originalScope->getType($stmt->expr);
63576325
if (
63586326
($stmt->valueVar instanceof Variable && is_string($stmt->valueVar->name))

src/Type/MixedType.php

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -169,14 +169,7 @@ public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $uni
169169

170170
public function setExistingOffsetValueType(Type $offsetType, Type $valueType): Type
171171
{
172-
$types = [
173-
new ArrayType(new MixedType(), new MixedType()),
174-
new ObjectType(ArrayAccess::class),
175-
];
176-
if (!$offsetType->isInteger()->no()) {
177-
$types[] = new StringType();
178-
}
179-
return TypeCombinator::union(...$types);
172+
return new self($this->isExplicitMixed);
180173
}
181174

182175
public function unsetOffset(Type $offsetType): Type

tests/PHPStan/Analyser/nsrt/bug-13270a.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public function test(array $data): void
1616
foreach($data as $k => $v) {
1717
assertType('non-empty-array<mixed>', $data);
1818
$data[$k]['a'] = true;
19-
assertType("non-empty-array<(non-empty-array&hasOffsetValue('a', true))|(ArrayAccess&hasOffsetValue('a', true))>", $data);
19+
assertType("non-empty-array<mixed>", $data);
2020
foreach($data[$k] as $val) {
2121
}
2222
}
@@ -51,4 +51,11 @@ public function doFoo(
5151
$i2['a'] = true;
5252
assertType('*ERROR*', $i2);
5353
}
54+
55+
public function mixedIntoForeach(mixed $m): void
56+
{
57+
foreach ($m as $k => $v) {
58+
assertType('mixed~array{}', $m);
59+
}
60+
}
5461
}

tests/PHPStan/Analyser/nsrt/bug-13270b.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@ public function parseData(array $data): array
1717
if (!array_key_exists('priceWithVat', $data['price'])) {
1818
$data['price']['priceWithVat'] = null;
1919
}
20-
assertType("(non-empty-array&hasOffsetValue('priceWithVat', mixed))|(ArrayAccess&hasOffsetValue('priceWithVat', null))", $data['price']);
20+
assertType("mixed", $data['price']);
2121
if (!array_key_exists('priceWithoutVat', $data['price'])) {
2222
$data['price']['priceWithoutVat'] = null;
2323
}
24+
assertType('mixed', $data['price']);
2425
}
2526
return $data;
2627
}

tests/PHPStan/Analyser/nsrt/bug-13312.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ function foo(array $arr): void {
3535
function fooBar(mixed $mixed): void {
3636
assertType('mixed', $mixed);
3737
foreach ($mixed as $v) {
38-
assertType('iterable', $mixed); // could be non-empty-array|Traversable
38+
assertType('mixed~array{}', $mixed);
3939
}
4040
assertType('mixed', $mixed);
4141

tests/PHPStan/Analyser/nsrt/composer-array-bug.php

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,26 +18,26 @@ public function doFoo(): void
1818
if (!empty($this->config['authors'])) {
1919
assertType("mixed~(0|0.0|''|'0'|array{}|false|null)", $this->config['authors']);
2020
foreach ($this->config['authors'] as $key => $author) {
21-
assertType("iterable", $this->config['authors']);
21+
assertType("mixed", $this->config['authors']);
2222

2323
if (!is_array($author)) {
2424
$this->errors[] = 'authors.'.$key.' : should be an array, '.gettype($author).' given';
25-
assertType("iterable", $this->config['authors']);
25+
assertType("mixed", $this->config['authors']);
2626
unset($this->config['authors'][$key]);
27-
assertType("iterable", $this->config['authors']);
27+
assertType("mixed", $this->config['authors']);
2828
continue;
2929
}
30-
assertType("iterable", $this->config['authors']);
30+
assertType("mixed", $this->config['authors']);
3131
foreach (['homepage', 'email', 'name', 'role'] as $authorData) {
3232
if (isset($author[$authorData]) && !is_string($author[$authorData])) {
3333
$this->errors[] = 'authors.'.$key.'.'.$authorData.' : invalid value, must be a string';
3434
unset($this->config['authors'][$key][$authorData]);
3535
}
3636
}
3737
if (isset($author['homepage'])) {
38-
assertType("iterable", $this->config['authors']);
38+
assertType("mixed", $this->config['authors']);
3939
unset($this->config['authors'][$key]['homepage']);
40-
assertType("iterable", $this->config['authors']);
40+
assertType("mixed", $this->config['authors']);
4141
}
4242
if (isset($author['email']) && !filter_var($author['email'], FILTER_VALIDATE_EMAIL)) {
4343
unset($this->config['authors'][$key]['email']);
@@ -47,8 +47,8 @@ public function doFoo(): void
4747
}
4848
}
4949

50-
assertType("non-empty-array&hasOffsetValue('authors', mixed~(0|0.0|''|'0'|array{}|false|null))", $this->config);
51-
assertType("mixed~(0|0.0|''|'0'|array{}|false|null)", $this->config['authors']);
50+
assertType("non-empty-array&hasOffsetValue('authors', mixed)", $this->config);
51+
assertType("mixed", $this->config['authors']);
5252

5353
if (empty($this->config['authors'])) {
5454
unset($this->config['authors']);
@@ -57,7 +57,7 @@ public function doFoo(): void
5757
assertType("non-empty-array&hasOffsetValue('authors', mixed~(0|0.0|''|'0'|array{}|false|null))", $this->config);
5858
}
5959

60-
assertType("non-empty-array&hasOffsetValue('authors', mixed~(0|0.0|''|'0'|array{}|false|null))", $this->config);
60+
assertType("array", $this->config);
6161
}
6262
}
6363

tests/PHPStan/Analyser/nsrt/composer-non-empty-array-after-unset.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ public function doFoo()
1515
if (!empty($this->config['authors'])) {
1616
assertType("mixed~(0|0.0|''|'0'|array{}|false|null)", $this->config['authors']);
1717
foreach ($this->config['authors'] as $key => $author) {
18-
assertType("iterable", $this->config['authors']);
18+
assertType("mixed", $this->config['authors']);
1919
if (!is_array($author)) {
2020
unset($this->config['authors'][$key]);
21-
assertType("iterable", $this->config['authors']);
21+
assertType("mixed", $this->config['authors']);
2222
continue;
2323
}
2424
foreach (['homepage', 'email', 'name', 'role'] as $authorData) {
@@ -33,13 +33,13 @@ public function doFoo()
3333
unset($this->config['authors'][$key]['email']);
3434
}
3535
if (empty($this->config['authors'][$key])) {
36-
assertType("iterable", $this->config['authors']);
36+
assertType("mixed", $this->config['authors']);
3737
unset($this->config['authors'][$key]);
38-
assertType("iterable", $this->config['authors']);
38+
assertType("mixed", $this->config['authors']);
3939
}
40-
assertType("iterable", $this->config['authors']);
40+
assertType("mixed", $this->config['authors']);
4141
}
42-
assertType("mixed~(0|0.0|''|'0'|array{}|false|null)", $this->config['authors']);
42+
assertType("mixed", $this->config['authors']);
4343
}
4444
}
4545

0 commit comments

Comments
 (0)