Skip to content

Commit 23eae8e

Browse files
committed
Merge remote-tracking branch 'origin/2.1.x' into 2.2.x
2 parents f6dd1fa + 69d0151 commit 23eae8e

5 files changed

Lines changed: 113 additions & 1 deletion

File tree

.github/workflows/e2e-tests.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,8 +303,13 @@ jobs:
303303
cd e2e/report-ignores-without-comments
304304
OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan --error-format=raw")
305305
echo "$OUTPUT"
306-
../bashunit -a contains 'test.php:4:Ignore with identifier variable.undefined has no comment' "$OUTPUT"
306+
../bashunit -a contains 'test.php:4:Ignore with identifier variable.undefined has no comment.' "$OUTPUT"
307+
../bashunit -a contains 'test.php:4:Variable $undefined might not be defined.' "$OUTPUT"
307308
../bashunit -a not_contains 'test.php:7' "$OUTPUT"
309+
../bashunit -a contains 'test.php:10:Ignoring all errors on a line is not allowed.' "$OUTPUT"
310+
../bashunit -a contains 'test.php:10:Variable $yetAnotherUndefined might not be defined.' "$OUTPUT"
311+
../bashunit -a contains 'test.php:12:Ignoring all errors on a line is not allowed.' "$OUTPUT"
312+
../bashunit -a contains 'test.php:12:Variable $yetYetAnotherUndefined might not be defined.' "$OUTPUT"
308313
309314
steps:
310315
- name: Harden the runner (Audit all outbound calls)

e2e/report-ignores-without-comments/test.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,8 @@
55

66
// @phpstan-ignore variable.undefined (this one has a comment so no error)
77
echo $anotherUndefined;
8+
9+
// @phpstan-ignore-next-line
10+
echo $yetAnotherUndefined;
11+
12+
echo $yetYetAnotherUndefined; // @phpstan-ignore-line

src/Analyser/FileAnalyser.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,17 @@ public function analyseFile(
135135
foreach ($linesToIgnore as $ignoredFile => $lines) {
136136
foreach ($lines as $line => $identifiers) {
137137
if ($identifiers === null) {
138+
$fileErrors[] = (new Error(
139+
'Ignoring all errors on a line is not allowed.',
140+
$ignoredFile,
141+
$line,
142+
false,
143+
$ignoredFile,
144+
null,
145+
'Use @phpstan-ignore with an identifier and a comment (in parentheses).',
146+
))->withIdentifier('ignore.allLineErrors');
147+
unset($linesToIgnore[$ignoredFile][$line]);
148+
unset($unmatchedLineIgnores[$ignoredFile][$line]);
138149
continue;
139150
}
140151

@@ -152,6 +163,8 @@ public function analyseFile(
152163
null,
153164
'Explain why this ignore is necessary in parentheses after the identifier.',
154165
))->withIdentifier('ignore.noComment');
166+
unset($linesToIgnore[$ignoredFile][$line]);
167+
unset($unmatchedLineIgnores[$ignoredFile][$line]);
155168
}
156169
}
157170
}

src/Analyser/TypeSpecifier.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1403,6 +1403,45 @@ private function specifyTypesForCountFuncCall(
14031403
$resultTypes[] = TypeCombinator::intersect($arrayType, new NonEmptyArrayType());
14041404
}
14051405

1406+
if ($context->truthy() && $isConstantArray->yes() && $isList->yes()) {
1407+
$hasOptionalKeys = false;
1408+
foreach ($type->getConstantArrays() as $arrayType) {
1409+
if ($arrayType->getOptionalKeys() !== []) {
1410+
$hasOptionalKeys = true;
1411+
break;
1412+
}
1413+
}
1414+
1415+
if (!$hasOptionalKeys) {
1416+
$argExpr = $countFuncCall->getArgs()[0]->value;
1417+
$argExprString = $this->exprPrinter->printExpr($argExpr);
1418+
1419+
$sizeMin = null;
1420+
$sizeMax = null;
1421+
if ($sizeType instanceof ConstantIntegerType) {
1422+
$sizeMin = $sizeType->getValue();
1423+
$sizeMax = $sizeType->getValue();
1424+
} elseif ($sizeType instanceof IntegerRangeType) {
1425+
$sizeMin = $sizeType->getMin();
1426+
$sizeMax = $sizeType->getMax();
1427+
}
1428+
1429+
$sureTypes = [];
1430+
$sureNotTypes = [];
1431+
1432+
if ($sizeMin !== null && $sizeMin >= 1) {
1433+
$sureTypes[$argExprString] = [$argExpr, new HasOffsetValueType(new ConstantIntegerType($sizeMin - 1), new MixedType())];
1434+
}
1435+
if ($sizeMax !== null) {
1436+
$sureNotTypes[$argExprString] = [$argExpr, new HasOffsetValueType(new ConstantIntegerType($sizeMax), new MixedType())];
1437+
}
1438+
1439+
if ($sureTypes !== [] || $sureNotTypes !== []) {
1440+
return (new SpecifiedTypes($sureTypes, $sureNotTypes))->setRootExpr($rootExpr);
1441+
}
1442+
}
1443+
}
1444+
14061445
return $this->create($countFuncCall->getArgs()[0]->value, TypeCombinator::union(...$resultTypes), $context, $scope)->setRootExpr($rootExpr);
14071446
}
14081447

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug14301;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class Foo
8+
{
9+
/**
10+
* @param array{bool}|array{mixed, string|null, mixed} $row
11+
*/
12+
protected function testNotEquals(array $row): string
13+
{
14+
if (count($row) !== 1) {
15+
assertType('array{mixed, string|null, mixed}', $row);
16+
17+
[$field, $operator, $value] = $row;
18+
assertType('string|null', $operator);
19+
return $operator ?? '=';
20+
} else {
21+
assertType('array{bool}', $row);
22+
}
23+
24+
return '';
25+
}
26+
27+
/**
28+
* @param array{bool}|array{mixed, string|null, mixed} $row
29+
*/
30+
protected function testEquals(array $row): void
31+
{
32+
if (count($row) === 3) {
33+
assertType('array{mixed, string|null, mixed}', $row);
34+
} else {
35+
assertType('array{bool}', $row);
36+
}
37+
}
38+
39+
/**
40+
* @param array{bool}|array{mixed, string|null, mixed} $row
41+
*/
42+
protected function testEquals1(array $row): void
43+
{
44+
if (count($row) === 1) {
45+
assertType('array{bool}', $row);
46+
} else {
47+
assertType('array{mixed, string|null, mixed}', $row);
48+
}
49+
}
50+
}

0 commit comments

Comments
 (0)