Skip to content

Commit

Permalink
Process expression assignments other than Variable in by-ref parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Sep 10, 2024
1 parent 68f6e25 commit d3a2a92
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 11 deletions.
33 changes: 22 additions & 11 deletions src/Analyser/NodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -4767,18 +4767,29 @@ private function processArgs(
}

$argValue = $arg->value;
if ($argValue instanceof Variable && is_string($argValue->name)) {
if ($argValue->name !== 'this') {
$paramOutType = $this->getParameterOutExtensionsType($callLike, $calleeReflection, $currentParameter, $scope);
if ($paramOutType !== null) {
$byRefType = $paramOutType;
}

$nodeCallback(new VariableAssignNode($argValue, new TypeExpr($byRefType), false), $scope);
$scope = $scope->assignVariable($argValue->name, $byRefType, new MixedType());
if (!$argValue instanceof Variable || $argValue->name !== 'this') {
$paramOutType = $this->getParameterOutExtensionsType($callLike, $calleeReflection, $currentParameter, $scope);
if ($paramOutType !== null) {
$byRefType = $paramOutType;
}
} else {
$scope = $scope->invalidateExpression($argValue);

$result = $this->processAssignVar(
$scope,
$stmt,
$argValue,
new TypeExpr($byRefType),
static function (Node $node, Scope $scope) use ($nodeCallback): void {
if (!$node instanceof PropertyAssignNode && !$node instanceof VariableAssignNode) {
return;
}

$nodeCallback($node, $scope);
},
$context,
static fn (MutatingScope $scope): ExpressionResult => new ExpressionResult($scope, false, [], []),
true,
);
$scope = $result->getScope();
}
} elseif ($calleeReflection !== null && $calleeReflection->hasSideEffects()->yes()) {
$argType = $scope->getType($arg->value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,21 @@ public function testTypesAssignedToProperties(): void
'Property PropertiesAssignedTypes\AppendToArrayAccess::$collection2 (ArrayAccess<int, string>&Countable) does not accept Countable.',
376,
],
[
'Property PropertiesAssignedTypes\ParamOutAssign::$foo (list<string>) does not accept string.',
400,
'string is not a list.',
],
[
'Property PropertiesAssignedTypes\ParamOutAssign::$foo2 (list<list<string>>) does not accept string.',
410,
'string is not a list.',
],
[
'Property PropertiesAssignedTypes\ParamOutAssign::$foo2 (list<list<string>>) does not accept non-empty-list<list<string>|string>.',
415,
'list<string>|string might not be a list.',
],
]);
}

Expand Down
45 changes: 45 additions & 0 deletions tests/PHPStan/Rules/Properties/data/properties-assigned-types.php
Original file line number Diff line number Diff line change
Expand Up @@ -376,3 +376,48 @@ public function foo(): void
$this->collection2[] = 2;
}
}

class ParamOutAssign
{

/** @var list<string> */
private $foo;

/** @var list<list<string>> */
private $foo2;

/**
* @param mixed $a
* @param-out string $a
*/
public function paramOut(&$a): void
{

}

public function doFoo(): void
{
$this->paramOut($this->foo);
}

public function doFoo2(): void
{
$this->paramOut($this->foo[0]);
}

public function doBar(): void
{
$this->paramOut($this->foo2);
}

public function doBar2(): void
{
$this->paramOut($this->foo2[0]);
}

public function doBar3(): void
{
$this->paramOut($this->foo2[0][0]);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,9 @@ public function testRule(): void
]);
}

public function testBug11667(): void
{
$this->analyse([__DIR__ . '/data/bug-11667.php'], []);
}

}
40 changes: 40 additions & 0 deletions tests/PHPStan/Rules/TooWideTypehints/data/bug-11667.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

namespace Bug11667;

final class HelloWorld {
/** @var list<string>|null */
private $matches = null;

public function match(string $string): void {
preg_match('/Hello (\w+)/', $string, $this->matches);
}

/** @return list<string>|null */
public function get(): ?array {
return $this->matches;
}
}

final class HelloWorld2 {
/** @var list<string>|null */
private $matches = null;

public function match(string $string): void {
$this->paramOut($this->matches);
}

/**
* @param mixed $a
* @param-out list<string> $a
*/
public function paramOut(&$a): void
{

}

/** @return list<string>|null */
public function get(): ?array {
return $this->matches;
}
}

0 comments on commit d3a2a92

Please sign in to comment.