Skip to content

Commit 7f8f9cc

Browse files
committed
Allow undefined variables passed into by-ref parameters only if the type is nullable
1 parent f71da02 commit 7f8f9cc

File tree

3 files changed

+71
-5
lines changed

3 files changed

+71
-5
lines changed

src/Analyser/NodeScopeResolver.php

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3797,6 +3797,7 @@ private function processArgs(
37973797
foreach ($args as $i => $arg) {
37983798
$assignByReference = false;
37993799
$parameter = null;
3800+
$parameterType = null;
38003801
if (isset($parameters) && $parametersAcceptor !== null) {
38013802
if (isset($parameters[$i])) {
38023803
$assignByReference = $parameters[$i]->passedByReference()->createsNewVariable();
@@ -3810,9 +3811,22 @@ private function processArgs(
38103811
}
38113812
}
38123813

3814+
$lookForUnset = false;
38133815
if ($assignByReference) {
38143816
if ($arg->value instanceof Variable) {
3815-
$scope = $this->lookForSetAllowedUndefinedExpressions($scope, $arg->value);
3817+
$isBuiltin = false;
3818+
if ($calleeReflection instanceof FunctionReflection && $calleeReflection->isBuiltin()) {
3819+
$isBuiltin = true;
3820+
} elseif ($calleeReflection instanceof ExtendedMethodReflection && $calleeReflection->getDeclaringClass()->isBuiltin()) {
3821+
$isBuiltin = true;
3822+
}
3823+
if (
3824+
$isBuiltin
3825+
|| ($parameterType === null || !$parameterType->isNull()->no())
3826+
) {
3827+
$scope = $this->lookForSetAllowedUndefinedExpressions($scope, $arg->value);
3828+
$lookForUnset = true;
3829+
}
38163830
}
38173831
}
38183832

@@ -3839,10 +3853,8 @@ private function processArgs(
38393853
$result = $this->processExprNode($stmt, $arg->value, $scopeToPass, $nodeCallback, $context->enterDeep());
38403854
}
38413855
$scope = $result->getScope();
3842-
if ($assignByReference) {
3843-
if ($arg->value instanceof Variable) {
3844-
$scope = $this->lookForUnsetAllowedUndefinedExpressions($scope, $arg->value);
3845-
}
3856+
if ($assignByReference && $lookForUnset) {
3857+
$scope = $this->lookForUnsetAllowedUndefinedExpressions($scope, $arg->value);
38463858
}
38473859

38483860
if ($calleeReflection !== null) {

tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1042,4 +1042,22 @@ public function testBug10418(): void
10421042
$this->analyse([__DIR__ . '/data/bug-10418.php'], []);
10431043
}
10441044

1045+
public function testPassByReferenceIntoNotNullable(): void
1046+
{
1047+
if (PHP_VERSION_ID < 80000) {
1048+
$this->markTestSkipped('Test requires PHP 8.0.');
1049+
}
1050+
1051+
$this->cliArgumentsVariablesRegistered = true;
1052+
$this->polluteScopeWithLoopInitialAssignments = true;
1053+
$this->checkMaybeUndefinedVariables = true;
1054+
$this->polluteScopeWithAlwaysIterableForeach = true;
1055+
$this->analyse([__DIR__ . '/data/pass-by-reference-into-not-nullable.php'], [
1056+
[
1057+
'Undefined variable: $three',
1058+
32,
1059+
],
1060+
]);
1061+
}
1062+
10451063
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php // lint >= 8.0
2+
3+
namespace PassByReferenceIntoNotNullable;
4+
5+
class Foo
6+
{
7+
8+
public function doFooNoType(&$test)
9+
{
10+
11+
}
12+
13+
public function doFooMixedType(mixed &$test)
14+
{
15+
16+
}
17+
18+
public function doFooIntType(int &$test)
19+
{
20+
21+
}
22+
23+
public function doFooNullableType(?int &$test)
24+
{
25+
26+
}
27+
28+
public function test()
29+
{
30+
$this->doFooNoType($one);
31+
$this->doFooMixedType($two);
32+
$this->doFooIntType($three);
33+
$this->doFooNullableType($four);
34+
}
35+
36+
}

0 commit comments

Comments
 (0)