Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
use PHPStan\Analyser\NodeScopeResolver;
use PHPStan\DependencyInjection\AutowiredService;
use PHPStan\Node\Expr\GetOffsetValueTypeExpr;
use PHPStan\Type\NullType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;

/**
* @implements ExprHandler<GetOffsetValueTypeExpr>
Expand Down Expand Up @@ -42,7 +44,14 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex

public function resolveType(MutatingScope $scope, Expr $expr): Type
{
return $scope->getType($expr->getVar())->getOffsetValueType($scope->getType($expr->getDim()));
$varType = $scope->getType($expr->getVar());
$dimType = $scope->getType($expr->getDim());
$offsetValueType = $varType->getOffsetValueType($dimType);
if (!$varType->isArray()->no() && !$varType->hasOffsetValueType($dimType)->yes()) {
$offsetValueType = TypeCombinator::union($offsetValueType, new NullType());
}

return $offsetValueType;
}

}
2 changes: 1 addition & 1 deletion src/Node/CollectedDataNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public function __construct(private array $collectedData, private bool $onlyFile
* @template TCollector of Collector<Node, TValue>
* @template TValue
* @param class-string<TCollector> $collectorType
* @return array<string, list<TValue>>
* @return array<string, non-empty-list<TValue>>
*/
public function get(string $collectorType): array
{
Expand Down
2 changes: 1 addition & 1 deletion src/Reflection/ConstructorsHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public function getConstructors(ClassReflection $classReflection): array
$nativeReflection = $classReflection->getNativeReflection();
foreach ($this->additionalConstructors as $additionalConstructor) {
[$className, $methodName] = explode('::', $additionalConstructor);
if (!$nativeReflection->hasMethod($methodName)) {
if ($methodName === null || !$nativeReflection->hasMethod($methodName)) {
continue;
}
$nativeMethod = $nativeReflection->getMethod($methodName);
Expand Down
28 changes: 14 additions & 14 deletions tests/PHPStan/Analyser/nsrt/array-destructuring.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,18 +109,18 @@ function (\stdClass $obj) {
assertType('*ERROR*', $foreachNestedNeverList);
assertType('1|4', $u1);
assertType('2|\'bar\'', $u2);
assertType('3', $u3);
assertType('3|null', $u3);
assertType('1|4', $foreachU1);
assertType('2|\'bar\'', $foreachU2);
assertType('3', $foreachU3);
assertType('string', $firstStringArray);
assertType('string', $secondStringArray);
assertType('3|null', $foreachU3);
assertType('string|null', $firstStringArray);
assertType('string|null', $secondStringArray);
assertType('non-empty-string', $thirdStringArray);
assertType('string', $fourthStringArray);
assertType('string', $firstStringArrayList);
assertType('string', $secondStringArrayList);
assertType('string|null', $fourthStringArray);
assertType('string|null', $firstStringArrayList);
assertType('string|null', $secondStringArrayList);
assertType('non-empty-string', $thirdStringArrayList);
assertType('string', $fourthStringArrayList);
assertType('string|null', $fourthStringArrayList);
assertType('non-empty-string', $firstStringArrayForeach);
assertType('non-empty-string', $secondStringArrayForeach);
assertType('non-empty-string', $thirdStringArrayForeach);
Expand All @@ -130,7 +130,7 @@ function (\stdClass $obj) {
assertType('non-empty-string', $thirdStringArrayForeachList);
assertType('non-empty-string', $fourthStringArrayForeachList);
assertType('lowercase-string&uppercase-string', $dateArray['Y']);
assertType('lowercase-string&uppercase-string', $dateArray['m']);
assertType('(lowercase-string&uppercase-string)|null', $dateArray['m']);
assertType('int', $dateArray['d']);
assertType('lowercase-string&uppercase-string', $intArrayForRewritingFirstElement[0]);
assertType('int', $intArrayForRewritingFirstElement[1]);
Expand All @@ -141,12 +141,12 @@ function (\stdClass $obj) {
assertType('1', $assocOne);
assertType('*ERROR*', $assocNonExistent);
assertType('true', $dynamicAssocKey);
assertType('\'123\'|true', $dynamicAssocStrings);
assertType('1|\'123\'|\'foo\'|true', $dynamicAssocMixed);
assertType('\'123\'|true|null', $dynamicAssocStrings);
assertType('1|\'123\'|\'foo\'|true|null', $dynamicAssocMixed);
assertType('true', $dynamicAssocKeyForeach);
assertType('\'123\'|true', $dynamicAssocStringsForeach);
assertType('1|\'123\'|\'foo\'|true', $dynamicAssocMixedForeach);
assertType('string', $stringFromIterable);
assertType('\'123\'|true|null', $dynamicAssocStringsForeach);
assertType('1|\'123\'|\'foo\'|true|null', $dynamicAssocMixedForeach);
assertType('string|null', $stringFromIterable);
assertType('string', $stringWithVarAnnotation);
assertType('string', $stringWithVarAnnotationInForeach);
};
77 changes: 77 additions & 0 deletions tests/PHPStan/Analyser/nsrt/bug-10854.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php

namespace Bug10854;

use function PHPStan\Testing\assertType;

function listFromExplode(string $input): void
{
list($a, $b) = explode('-', $input);
assertType('string', $a);
assertType('string|null', $b);
}

function shortListFromExplode(string $input): void
{
[$a, $b] = explode('-', $input);
assertType('string', $a);
assertType('string|null', $b);
}

/**
* @param list<string> $list
*/
function listFromGenericList(array $list): void
{
[$a, $b] = $list;
assertType('string|null', $a);
assertType('string|null', $b);
}

/**
* @param array<int, string> $arr
*/
function listFromGenericArray(array $arr): void
{
[$a, $b] = $arr;
assertType('string|null', $a);
assertType('string|null', $b);
}

/**
* @param non-empty-list<string> $list
*/
function listFromNonEmptyList(array $list): void
{
[$a, $b] = $list;
assertType('string', $a);
assertType('string|null', $b);
}

function listFromConstantArray(): void
{
$arr = [1, 'foo', true];
[$a, $b, $c] = $arr;
assertType('1', $a);
assertType("'foo'", $b);
assertType('true', $c);
}

/**
* @param array{0: string, 1?: string} $arr
*/
function listFromOptionalKeys(array $arr): void
{
[$a, $b] = $arr;
assertType('string', $a);
assertType('string|null', $b);
}

function nullCoalesceAfterList(string $input): void
{
[$a, $b] = explode('-', $input);
$x = $a ?? 'default';
$y = $b ?? 'default';
assertType('string', $x);
assertType('string', $y);
}
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/nsrt/bug-8127.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public function getNodeType(): string
public function processNode(\PhpParser\Node $node, Scope $scope): array
{
$sinkCollectorData = $node->get(SinkCollector::class);
assertType("array<string, list<array{string, 'html'|'input'|'sql', string, int}>>", $sinkCollectorData);
assertType("array<string, non-empty-list<array{string, 'html'|'input'|'sql', string, int}>>", $sinkCollectorData);

return [];
}
Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/nsrt/collected-data.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class Foo

public function doFoo(CollectedDataNode $node): void
{
assertType('array<string, list<int>>', $node->get(TestCollector::class));
assertType('array<string, non-empty-list<int>>', $node->get(TestCollector::class));
}

}
11 changes: 10 additions & 1 deletion tests/PHPStan/Reflection/ReflectionProviderGoldenTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ private static function generateSymbolDescription(string $symbol): string
{
[$type, $name] = explode(' ', $symbol);

if ($name === '') {
if ($name === null || $name === '') {
throw new ShouldNotHappenException();
}

Expand Down Expand Up @@ -288,6 +288,10 @@ private static function generateClassMethodDescription(string $classMethodName):
{
[$className, $methodName] = explode('::', $classMethodName);

if ($methodName === null) {
throw new ShouldNotHappenException();
}

$reflectionProvider = self::getContainer()->getByType(ReflectionProvider::class);

if (! $reflectionProvider->hasClass($className)) {
Expand Down Expand Up @@ -429,6 +433,11 @@ private static function generateVariantsDescription(string $name, array $variant
private static function generateClassPropertyDescription(string $propertyName): string
{
[$className, $propertyName] = explode('::', $propertyName);

if ($propertyName === null) {
throw new ShouldNotHappenException();
}

// remove $
$propertyName = substr($propertyName, 1);

Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2493,7 +2493,7 @@ public function testRectorDoWhileVarIssue(): void
$this->checkUnionTypes = true;
$this->analyse([__DIR__ . '/data/rector-do-while-var-issue.php'], [
[
'Parameter #1 $cls of method RectorDoWhileVarIssue\Foo::processCharacterClass() expects string, int|string given.',
'Parameter #1 $cls of method RectorDoWhileVarIssue\Foo::processCharacterClass() expects string, int|string|null given.',
24,
],
]);
Expand Down
22 changes: 22 additions & 0 deletions tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -459,4 +459,26 @@ public function testBug14393(): void
]);
}

public function testBug10854(): void
{
$this->analyse([__DIR__ . '/data/bug-10854.php'], [
[
'Variable $a on left side of ?? always exists and is not nullable.',
10,
],
[
'Variable $a on left side of ?? always exists and is not nullable.',
17,
],
[
'Variable $a on left side of ?? always exists and is not nullable.',
37,
],
[
'Variable $a on left side of ??= always exists and is not nullable.',
44,
],
]);
}

}
55 changes: 55 additions & 0 deletions tests/PHPStan/Rules/Variables/data/bug-10854.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php declare(strict_types = 1);

namespace Bug10854NullCoalesce;

class Foo
{
public function doFoo(string $input): void
{
@list($a, $b) = explode('-', $input);
$x = $a ?? 'default'; // no error - $a might be null
$y = $b ?? 'default'; // no error - $b might be null
}

public function doBar(string $input): void
{
@[$a, $b] = explode('-', $input);
$x = $a ?? 'default'; // no error
$y = $b ?? 'default'; // no error
}

/**
* @param list<string> $list
*/
public function doBaz(array $list): void
{
[$a, $b] = $list;
$x = $a ?? 'default'; // no error
$y = $b ?? 'default'; // no error
}

/**
* @param array{0: string, 1?: string} $arr
*/
public function doQux(array $arr): void
{
[$a, $b] = $arr;
$x = $a ?? 'default'; // $a is always string from required key 0
$y = $b ?? 'default'; // no error - key 1 is optional
}

public function coalesceAssign(string $input): void
{
[$a, $b] = explode('-', $input);
$a ??= 'default'; // $a is always string from non-empty-list index 0
$b ??= 'default'; // no error - $b might be null
}

public function issetAfterList(string $input): void
{
[$a, $b] = explode('-', $input);
if (isset($b)) { // no error - $b might be null/undefined
echo $b;
}
}
}
5 changes: 5 additions & 0 deletions tests/PHPStan/Testing/TypeInferenceTestCaseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace PHPStan\Testing;

use PHPStan\File\FileHelper;
use PHPStan\ShouldNotHappenException;
use PHPUnit\Framework\AssertionFailedError;
use PHPUnit\Framework\Attributes\DataProvider;
use function array_values;
Expand Down Expand Up @@ -117,6 +118,10 @@ public function testVariableOrOffsetDescription(): void

[$variableAssert, $offsetAssert] = array_values(self::gatherAssertTypes($filePath));

if ($variableAssert === null || $offsetAssert === null) {
throw new ShouldNotHappenException();
}

$this->assertSame('variable $context', $variableAssert[4]);
$this->assertSame("offset 'email'", $offsetAssert[4]);
}
Expand Down
Loading