Skip to content

Commit c5bfadb

Browse files
authored
Prevent TypeCombinator::intersect() calls in MutatingScope-> specifyExpressionType() (#5048)
1 parent d0b7d5e commit c5bfadb

File tree

9 files changed

+51
-16
lines changed

9 files changed

+51
-16
lines changed

src/Analyser/Ignore/IgnoredErrorHelper.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,13 +106,18 @@ public function initialize(): IgnoredErrorHelperResult
106106
continue;
107107
}
108108

109+
$reportUnmatched = (bool) ($uniquedExpandedIgnoreErrors[$key]['reportUnmatched'] ?? $this->reportUnmatchedIgnoredErrors);
110+
if (!$reportUnmatched) {
111+
$reportUnmatched = $ignoreError['reportUnmatched'] ?? $this->reportUnmatchedIgnoredErrors;
112+
}
113+
109114
$uniquedExpandedIgnoreErrors[$key] = [
110115
'message' => $ignoreError['message'] ?? null,
111116
'rawMessage' => $ignoreError['rawMessage'] ?? null,
112117
'path' => $ignoreError['path'],
113118
'identifier' => $ignoreError['identifier'] ?? null,
114119
'count' => ($uniquedExpandedIgnoreErrors[$key]['count'] ?? 1) + ($ignoreError['count'] ?? 1),
115-
'reportUnmatched' => ($uniquedExpandedIgnoreErrors[$key]['reportUnmatched'] ?? $this->reportUnmatchedIgnoredErrors) || ($ignoreError['reportUnmatched'] ?? $this->reportUnmatchedIgnoredErrors),
120+
'reportUnmatched' => $reportUnmatched,
116121
];
117122
}
118123

src/Analyser/MutatingScope.php

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1857,7 +1857,10 @@ private function getTypeFromArrayDimFetch(
18571857
return $offsetAccessibleType;
18581858
}
18591859

1860-
if (!$offsetAccessibleType->isArray()->yes() && (new ObjectType(ArrayAccess::class))->isSuperTypeOf($offsetAccessibleType)->yes()) {
1860+
if (
1861+
!$offsetAccessibleType->isArray()->yes()
1862+
&& (new ObjectType(ArrayAccess::class))->isSuperTypeOf($offsetAccessibleType)->yes()
1863+
) {
18611864
return $this->getType(
18621865
new MethodCall(
18631866
$arrayDimFetch->var,
@@ -3368,11 +3371,15 @@ public function specifyExpressionType(Expr $expr, Type $type, Type $nativeType,
33683371
$dimType = $scope->getType($expr->dim)->toArrayKey();
33693372
if ($dimType->isInteger()->yes() || $dimType->isString()->yes()) {
33703373
$exprVarType = $scope->getType($expr->var);
3371-
if (!$exprVarType instanceof MixedType && !$exprVarType->isArray()->no()) {
3372-
if ($dimType->isInteger()->yes()) {
3373-
$varType = TypeCombinator::intersect($exprVarType, StaticTypeFactory::intOffsetAccessibleType());
3374-
} else {
3375-
$varType = TypeCombinator::intersect($exprVarType, StaticTypeFactory::generalOffsetAccessibleType());
3374+
$isArray = $exprVarType->isArray();
3375+
if (!$exprVarType instanceof MixedType && !$isArray->no()) {
3376+
$varType = $exprVarType;
3377+
if (!$isArray->yes()) {
3378+
if ($dimType->isInteger()->yes()) {
3379+
$varType = TypeCombinator::intersect($exprVarType, StaticTypeFactory::intOffsetAccessibleType());
3380+
} else {
3381+
$varType = TypeCombinator::intersect($exprVarType, StaticTypeFactory::generalOffsetAccessibleType());
3382+
}
33763383
}
33773384

33783385
if ($dimType instanceof ConstantIntegerType || $dimType instanceof ConstantStringType) {
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php // lint < 8.0
2+
3+
namespace Bug11518TypesPhp7;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
/**
8+
* @param mixed[] $a
9+
* @return array{thing: mixed}
10+
* */
11+
function blah(array $a): array
12+
{
13+
if (!array_key_exists('thing', $a)) {
14+
$a['thing'] = 'bla';
15+
assertType('non-empty-array<mixed>&hasOffsetValue(\'thing\', \'bla\')', $a);
16+
} else {
17+
assertType('non-empty-array&hasOffset(\'thing\')', $a);
18+
}
19+
20+
assertType('non-empty-array&hasOffsetValue(\'thing\', mixed)', $a);
21+
22+
return $a;
23+
}

tests/PHPStan/Analyser/nsrt/bug-11518-types.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<?php
1+
<?php // lint >= 8.0
22

33
namespace Bug11518Types;
44

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,6 @@ private function getResultSettings(array $settings): array
134134
function foo(array $array): void {
135135
$array['bar'] = 'string';
136136

137-
assertType("non-empty-array&hasOffsetValue('bar', 'string')", $array);
137+
assertType("non-empty-array<mixed>&hasOffsetValue('bar', 'string')", $array);
138138
}
139139
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,17 +47,17 @@ public function doFoo(): void
4747
}
4848
}
4949

50-
assertType("non-empty-array&hasOffsetValue('authors', mixed)", $this->config);
50+
assertType("non-empty-array<mixed>&hasOffsetValue('authors', mixed)", $this->config);
5151
assertType("mixed", $this->config['authors']);
5252

5353
if (empty($this->config['authors'])) {
5454
unset($this->config['authors']);
5555
assertType("array<mixed~'authors', mixed>", $this->config);
5656
} else {
57-
assertType("non-empty-array&hasOffsetValue('authors', mixed~(0|0.0|''|'0'|array{}|false|null))", $this->config);
57+
assertType("non-empty-array<mixed>&hasOffsetValue('authors', mixed~(0|0.0|''|'0'|array{}|false|null))", $this->config);
5858
}
5959

60-
assertType("array", $this->config);
60+
assertType("array<mixed>", $this->config);
6161
}
6262
}
6363

tests/PHPStan/Analyser/nsrt/superglobals.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,14 @@ public function canBeOverwritten(): void
3131
public function canBePartlyOverwritten(): void
3232
{
3333
$GLOBALS['foo'] = 'foo';
34-
assertType("non-empty-array&hasOffsetValue('foo', 'foo')", $GLOBALS);
34+
assertType("non-empty-array<mixed>&hasOffsetValue('foo', 'foo')", $GLOBALS);
3535
assertNativeType("non-empty-array<mixed>&hasOffsetValue('foo', 'foo')", $GLOBALS);
3636
}
3737

3838
public function canBeNarrowed(): void
3939
{
4040
if (isset($GLOBALS['foo'])) {
41-
assertType("non-empty-array&hasOffsetValue('foo', mixed~null)", $GLOBALS);
41+
assertType("non-empty-array<mixed>&hasOffsetValue('foo', mixed~null)", $GLOBALS);
4242
assertNativeType("non-empty-array<mixed>&hasOffset('foo')", $GLOBALS); // https://github.com/phpstan/phpstan/issues/8395
4343
} else {
4444
assertType('array<mixed>', $GLOBALS);

tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1318,7 +1318,7 @@ public function testBug2911(): void
13181318
{
13191319
$this->analyse([__DIR__ . '/data/bug-2911.php'], [
13201320
[
1321-
'Parameter #1 $array of function Bug2911\bar expects array{bar: string}, non-empty-array given.',
1321+
'Parameter #1 $array of function Bug2911\bar expects array{bar: string}, non-empty-array<mixed> given.',
13221322
23,
13231323
],
13241324
]);

tests/PHPStan/Rules/Functions/data/bug-7156.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ function foobar2(mixed $data): void
3232
throw new \RuntimeException();
3333
}
3434

35-
assertType("non-empty-array&hasOffsetValue('value', string)", $data);
35+
assertType("non-empty-array<mixed, mixed>&hasOffsetValue('value', string)", $data);
3636

3737
foo($data);
3838
}

0 commit comments

Comments
 (0)