Skip to content
Open
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
49 changes: 48 additions & 1 deletion src/Analyser/ExprHandler/AssignHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,18 @@
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\ConstantTypeHelper;
use PHPStan\Type\ErrorType;
use PHPStan\Type\Generic\TemplateType;
use PHPStan\Type\IntegerRangeType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\MixedType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\OffsetAccessType;
use PHPStan\Type\StaticTypeFactory;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\TypeUtils;
use TypeError;
use function array_key_exists;
use function array_key_last;
use function array_merge;
use function array_pop;
Expand Down Expand Up @@ -439,7 +442,28 @@
$offsetValueType = $varType;
$offsetNativeValueType = $varNativeType;

[$valueToWrite, $additionalExpressions] = $this->produceArrayDimFetchAssignValueToWrite($dimFetchStack, $offsetTypes, $offsetValueType, $valueToWrite, $scope);
$dependentPreserved = false;
if (
count($dimFetchStack) === 1
&& count($offsetTypes) === 1
&& $offsetTypes[0][0] !== null
&& TypeUtils::containsTemplateType($offsetTypes[0][0])
) {
$rawValueType = $this->getRawExpressionType($scope, $assignedExpr);
if (
$rawValueType instanceof OffsetAccessType
&& $this->isSameTemplateOffset($rawValueType->getAccessedOffset(), $offsetTypes[0][0])
&& $rawValueType->getAccessedType()->isSuperTypeOf($offsetValueType)->yes()

Check warning on line 456 in src/Analyser/ExprHandler/AssignHandler.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.4, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ if ( $rawValueType instanceof OffsetAccessType && $this->isSameTemplateOffset($rawValueType->getAccessedOffset(), $offsetTypes[0][0]) - && $rawValueType->getAccessedType()->isSuperTypeOf($offsetValueType)->yes() + && !$rawValueType->getAccessedType()->isSuperTypeOf($offsetValueType)->no() ) { $dependentPreserved = true; $additionalExpressions = [[$dimFetchStack[0], $valueToWrite]];

Check warning on line 456 in src/Analyser/ExprHandler/AssignHandler.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.4, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\IsSuperTypeOfCalleeAndArgumentMutator": @@ @@ if ( $rawValueType instanceof OffsetAccessType && $this->isSameTemplateOffset($rawValueType->getAccessedOffset(), $offsetTypes[0][0]) - && $rawValueType->getAccessedType()->isSuperTypeOf($offsetValueType)->yes() + && $offsetValueType->isSuperTypeOf($rawValueType->getAccessedType())->yes() ) { $dependentPreserved = true; $additionalExpressions = [[$dimFetchStack[0], $valueToWrite]];

Check warning on line 456 in src/Analyser/ExprHandler/AssignHandler.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.3, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ if ( $rawValueType instanceof OffsetAccessType && $this->isSameTemplateOffset($rawValueType->getAccessedOffset(), $offsetTypes[0][0]) - && $rawValueType->getAccessedType()->isSuperTypeOf($offsetValueType)->yes() + && !$rawValueType->getAccessedType()->isSuperTypeOf($offsetValueType)->no() ) { $dependentPreserved = true; $additionalExpressions = [[$dimFetchStack[0], $valueToWrite]];

Check warning on line 456 in src/Analyser/ExprHandler/AssignHandler.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.3, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\IsSuperTypeOfCalleeAndArgumentMutator": @@ @@ if ( $rawValueType instanceof OffsetAccessType && $this->isSameTemplateOffset($rawValueType->getAccessedOffset(), $offsetTypes[0][0]) - && $rawValueType->getAccessedType()->isSuperTypeOf($offsetValueType)->yes() + && $offsetValueType->isSuperTypeOf($rawValueType->getAccessedType())->yes() ) { $dependentPreserved = true; $additionalExpressions = [[$dimFetchStack[0], $valueToWrite]];
) {
$dependentPreserved = true;
$additionalExpressions = [[$dimFetchStack[0], $valueToWrite]];
$valueToWrite = $offsetValueType;
}
}

if (!$dependentPreserved) {
[$valueToWrite, $additionalExpressions] = $this->produceArrayDimFetchAssignValueToWrite($dimFetchStack, $offsetTypes, $offsetValueType, $valueToWrite, $scope);
}

if (!$offsetValueType->equals($offsetNativeValueType) || !$valueToWrite->equals($nativeValueToWrite)) {
[$nativeValueToWrite, $additionalNativeExpressions] = $this->produceArrayDimFetchAssignValueToWrite($dimFetchStack, $offsetNativeTypes, $offsetNativeValueType, $nativeValueToWrite, $scope);
Expand Down Expand Up @@ -1258,4 +1282,27 @@
return false;
}

private function isSameTemplateOffset(Type $a, Type $b): bool
{
if ($a->equals($b)) {
return true;
}

if ($a instanceof TemplateType && $b instanceof TemplateType) {
return $a->getScope()->equals($b->getScope()) && $a->getName() === $b->getName();
}

return false;
}

private function getRawExpressionType(MutatingScope $scope, Expr $expr): ?Type
{
$key = $scope->getNodeKey($expr);
if (!array_key_exists($key, $scope->expressionTypes)) {
return null;
}

return $scope->expressionTypes[$key]->getType();
}

}
10 changes: 10 additions & 0 deletions src/Type/OffsetAccessType.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@ public function __construct(
{
}

public function getAccessedType(): Type
{
return $this->type;
}

public function getAccessedOffset(): Type
{
return $this->offset;
}

public function getReferencedClasses(): array
{
return array_merge(
Expand Down
6 changes: 1 addition & 5 deletions tests/PHPStan/Analyser/AnalyserIntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -824,9 +824,8 @@ public function testBug7215(): void

public function testBug7094(): void
{
// false positive
$errors = $this->runAnalyse(__DIR__ . '/data/bug-7094.php');
$this->assertCount(6, $errors);
$this->assertCount(5, $errors);

$this->assertSame('Parameter #2 $val of method Bug7094\Foo::setAttribute() contains unresolvable type.', $errors[0]->getMessage());
$this->assertSame(74, $errors[0]->getLine());
Expand All @@ -838,9 +837,6 @@ public function testBug7094(): void
$this->assertSame(78, $errors[3]->getLine());
$this->assertSame('Return type of call to method Bug7094\Foo::getAttribute() contains unresolvable type.', $errors[4]->getMessage());
$this->assertSame(79, $errors[4]->getLine());

$this->assertSame('Parameter #1 $attr of method Bug7094\Foo::setAttributes() expects array{foo?: string, bar?: 5|6|7, baz?: bool}, non-empty-array<\'bar\'|\'baz\'|\'foo\'|K of string, 5|6|7|bool|string> given.', $errors[5]->getMessage());
$this->assertSame(29, $errors[5]->getLine());
}

#[RequiresPhp('>= 8.0.0')]
Expand Down
64 changes: 64 additions & 0 deletions tests/PHPStan/Analyser/nsrt/bug-7380.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php declare(strict_types = 1);

namespace Bug7380;

use function PHPStan\Testing\assertType;

/**
* @phpstan-type Attrs array{foo?: string, bar?: 5|6|7, baz?: bool}
*/
final class Foo {

/**
* @template K of key-of<Attrs>
* @param K $key
* @param Attrs[K] $val
*/
public function setAttribute(string $key, mixed $val): void
{
$attr = $this->getAttributes();
$attr[$key] = $val;
assertType('array{foo?: string, bar?: 5|6|7, baz?: bool}', $attr);
$this->setAttributes($attr);
}

/** @return Attrs */
public function getAttributes(): array
{
return [];
}

/** @param Attrs $attr */
public function setAttributes(array $attr): void
{
}
}

/**
* @template T of array<string, mixed>
*/
final class GenericBar {

/**
* @template K of key-of<T>
* @param K $key
* @param T[K] $val
*/
public function setAttribute(string $key, mixed $val): void
{
$attr = $this->getAttributes();
$attr[$key] = $val;
$this->setAttributes($attr);
}

/** @return T */
public function getAttributes(): array
{
return [];
}

/** @param T $attr */
public function setAttributes(array $attr): void
{
}
}
Loading