Skip to content

Commit 27a952e

Browse files
committed
Bleeding edge - assign by ref - use parameter type when @param-out is not present
1 parent 840445a commit 27a952e

File tree

7 files changed

+85
-1
lines changed

7 files changed

+85
-1
lines changed

conf/config.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,7 @@ services:
495495
treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain%
496496
detectDeadTypeInMultiCatch: %featureToggles.detectDeadTypeInMultiCatch%
497497
universalObjectCratesClasses: %universalObjectCratesClasses%
498+
paramOutType: %featureToggles.paramOutType%
498499

499500
-
500501
class: PHPStan\Analyser\ConstantResolver

src/Analyser/NodeScopeResolver.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ public function __construct(
243243
private readonly bool $implicitThrows,
244244
private readonly bool $treatPhpDocTypesAsCertain,
245245
private readonly bool $detectDeadTypeInMultiCatch,
246+
private readonly bool $paramOutType,
246247
)
247248
{
248249
$earlyTerminatingMethodNames = [];
@@ -3863,12 +3864,36 @@ private function processArgs(
38633864
$assignByReference = $parameters[$i]->passedByReference()->createsNewVariable();
38643865
if ($parameters[$i] instanceof ParameterReflectionWithPhpDocs && $parameters[$i]->getOutType() !== null) {
38653866
$byRefType = $parameters[$i]->getOutType();
3867+
} elseif (
3868+
$calleeReflection instanceof MethodReflection
3869+
&& !$calleeReflection->getDeclaringClass()->isBuiltin()
3870+
&& $this->paramOutType
3871+
) {
3872+
$byRefType = $parameters[$i]->getType();
3873+
} elseif (
3874+
$calleeReflection instanceof FunctionReflection
3875+
&& !$calleeReflection->isBuiltin()
3876+
&& $this->paramOutType
3877+
) {
3878+
$byRefType = $parameters[$i]->getType();
38663879
}
38673880
} elseif (count($parameters) > 0 && $parametersAcceptor->isVariadic()) {
38683881
$lastParameter = $parameters[count($parameters) - 1];
38693882
$assignByReference = $lastParameter->passedByReference()->createsNewVariable();
38703883
if ($lastParameter instanceof ParameterReflectionWithPhpDocs && $lastParameter->getOutType() !== null) {
38713884
$byRefType = $lastParameter->getOutType();
3885+
} elseif (
3886+
$calleeReflection instanceof MethodReflection
3887+
&& !$calleeReflection->getDeclaringClass()->isBuiltin()
3888+
&& $this->paramOutType
3889+
) {
3890+
$byRefType = $lastParameter->getType();
3891+
} elseif (
3892+
$calleeReflection instanceof FunctionReflection
3893+
&& !$calleeReflection->isBuiltin()
3894+
&& $this->paramOutType
3895+
) {
3896+
$byRefType = $lastParameter->getType();
38723897
}
38733898
}
38743899

src/Testing/RuleTestCase.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ private function getAnalyser(): Analyser
103103
self::getContainer()->getParameter('exceptions')['implicitThrows'],
104104
$this->shouldTreatPhpDocTypesAsCertain(),
105105
self::getContainer()->getParameter('featureToggles')['detectDeadTypeInMultiCatch'],
106+
self::getContainer()->getParameter('featureToggles')['paramOutType'],
106107
);
107108
$fileAnalyser = new FileAnalyser(
108109
$this->createScopeFactory($reflectionProvider, $typeSpecifier),

src/Testing/TypeInferenceTestCase.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ public static function processFile(
7070
true,
7171
self::getContainer()->getParameter('treatPhpDocTypesAsCertain'),
7272
self::getContainer()->getParameter('featureToggles')['detectDeadTypeInMultiCatch'],
73+
self::getContainer()->getParameter('featureToggles')['paramOutType'],
7374
);
7475
$resolver->setAnalysedFiles(array_map(static fn (string $file): string => $fileHelper->normalizePath($file), array_merge([$file], static::getAdditionalAnalysedFiles())));
7576

tests/PHPStan/Analyser/AnalyserTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,7 @@ private function createAnalyser(bool $reportUnmatchedIgnoredErrors, bool $enable
663663
true,
664664
$this->shouldTreatPhpDocTypesAsCertain(),
665665
self::getContainer()->getParameter('featureToggles')['detectDeadTypeInMultiCatch'],
666+
self::getContainer()->getParameter('featureToggles')['paramOutType'],
666667
);
667668
$lexer = new Lexer(['usedAttributes' => ['comments', 'startLine', 'endLine', 'startTokenPos', 'endTokenPos']]);
668669
$fileAnalyser = new FileAnalyser(

tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7965,7 +7965,7 @@ public function dataPassedByReference(): array
79657965
'$matches',
79667966
],
79677967
[
7968-
'mixed',
7968+
'string',
79697969
'$s',
79707970
],
79717971
];

tests/PHPStan/Analyser/data/param-out.php

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,3 +446,58 @@ function fooIsCallable($x, bool $b)
446446
is_callable($x, $b, $name);
447447
assertType('callable-string', $name);
448448
}
449+
450+
function noParamOut(string &$s): void
451+
{
452+
453+
}
454+
455+
function noParamOutVariadic(string &...$s): void
456+
{
457+
458+
}
459+
460+
function ($s): void {
461+
assertType('mixed', $s);
462+
noParamOut($s);
463+
assertType('string', $s);
464+
};
465+
466+
function ($s, $t): void {
467+
assertType('mixed', $s);
468+
assertType('mixed', $t);
469+
noParamOutVariadic($s, $t);
470+
assertType('string', $s);
471+
assertType('string', $t);
472+
};
473+
474+
class NoParamOutClass
475+
{
476+
477+
function doFoo(string &$s): void
478+
{
479+
480+
}
481+
482+
function doFooVariadic(string &...$s): void
483+
{
484+
485+
}
486+
487+
}
488+
489+
function ($s): void {
490+
assertType('mixed', $s);
491+
$c = new NoParamOutClass();
492+
$c->doFoo($s);
493+
assertType('string', $s);
494+
};
495+
496+
function ($s, $t): void {
497+
assertType('mixed', $s);
498+
assertType('mixed', $t);
499+
$c = new NoParamOutClass();
500+
$c->doFooVariadic($s, $t);
501+
assertType('string', $s);
502+
assertType('string', $t);
503+
};

0 commit comments

Comments
 (0)