Skip to content

Commit

Permalink
Bleeding edge - assign by ref - use parameter type when @param-out
Browse files Browse the repository at this point in the history
…is not present
  • Loading branch information
ondrejmirtes committed Feb 25, 2024
1 parent 840445a commit 27a952e
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 1 deletion.
1 change: 1 addition & 0 deletions conf/config.neon
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,7 @@ services:
treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain%
detectDeadTypeInMultiCatch: %featureToggles.detectDeadTypeInMultiCatch%
universalObjectCratesClasses: %universalObjectCratesClasses%
paramOutType: %featureToggles.paramOutType%

-
class: PHPStan\Analyser\ConstantResolver
Expand Down
25 changes: 25 additions & 0 deletions src/Analyser/NodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ public function __construct(
private readonly bool $implicitThrows,
private readonly bool $treatPhpDocTypesAsCertain,
private readonly bool $detectDeadTypeInMultiCatch,
private readonly bool $paramOutType,
)
{
$earlyTerminatingMethodNames = [];
Expand Down Expand Up @@ -3863,12 +3864,36 @@ private function processArgs(
$assignByReference = $parameters[$i]->passedByReference()->createsNewVariable();
if ($parameters[$i] instanceof ParameterReflectionWithPhpDocs && $parameters[$i]->getOutType() !== null) {
$byRefType = $parameters[$i]->getOutType();
} elseif (
$calleeReflection instanceof MethodReflection
&& !$calleeReflection->getDeclaringClass()->isBuiltin()
&& $this->paramOutType
) {
$byRefType = $parameters[$i]->getType();
} elseif (
$calleeReflection instanceof FunctionReflection
&& !$calleeReflection->isBuiltin()
&& $this->paramOutType
) {
$byRefType = $parameters[$i]->getType();
}
} elseif (count($parameters) > 0 && $parametersAcceptor->isVariadic()) {
$lastParameter = $parameters[count($parameters) - 1];
$assignByReference = $lastParameter->passedByReference()->createsNewVariable();
if ($lastParameter instanceof ParameterReflectionWithPhpDocs && $lastParameter->getOutType() !== null) {
$byRefType = $lastParameter->getOutType();
} elseif (
$calleeReflection instanceof MethodReflection
&& !$calleeReflection->getDeclaringClass()->isBuiltin()
&& $this->paramOutType
) {
$byRefType = $lastParameter->getType();
} elseif (
$calleeReflection instanceof FunctionReflection
&& !$calleeReflection->isBuiltin()
&& $this->paramOutType
) {
$byRefType = $lastParameter->getType();
}
}

Expand Down
1 change: 1 addition & 0 deletions src/Testing/RuleTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ private function getAnalyser(): Analyser
self::getContainer()->getParameter('exceptions')['implicitThrows'],
$this->shouldTreatPhpDocTypesAsCertain(),
self::getContainer()->getParameter('featureToggles')['detectDeadTypeInMultiCatch'],
self::getContainer()->getParameter('featureToggles')['paramOutType'],
);
$fileAnalyser = new FileAnalyser(
$this->createScopeFactory($reflectionProvider, $typeSpecifier),
Expand Down
1 change: 1 addition & 0 deletions src/Testing/TypeInferenceTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ public static function processFile(
true,
self::getContainer()->getParameter('treatPhpDocTypesAsCertain'),
self::getContainer()->getParameter('featureToggles')['detectDeadTypeInMultiCatch'],
self::getContainer()->getParameter('featureToggles')['paramOutType'],
);
$resolver->setAnalysedFiles(array_map(static fn (string $file): string => $fileHelper->normalizePath($file), array_merge([$file], static::getAdditionalAnalysedFiles())));

Expand Down
1 change: 1 addition & 0 deletions tests/PHPStan/Analyser/AnalyserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -663,6 +663,7 @@ private function createAnalyser(bool $reportUnmatchedIgnoredErrors, bool $enable
true,
$this->shouldTreatPhpDocTypesAsCertain(),
self::getContainer()->getParameter('featureToggles')['detectDeadTypeInMultiCatch'],
self::getContainer()->getParameter('featureToggles')['paramOutType'],
);
$lexer = new Lexer(['usedAttributes' => ['comments', 'startLine', 'endLine', 'startTokenPos', 'endTokenPos']]);
$fileAnalyser = new FileAnalyser(
Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7965,7 +7965,7 @@ public function dataPassedByReference(): array
'$matches',
],
[
'mixed',
'string',
'$s',
],
];
Expand Down
55 changes: 55 additions & 0 deletions tests/PHPStan/Analyser/data/param-out.php
Original file line number Diff line number Diff line change
Expand Up @@ -446,3 +446,58 @@ function fooIsCallable($x, bool $b)
is_callable($x, $b, $name);
assertType('callable-string', $name);
}

function noParamOut(string &$s): void
{

}

function noParamOutVariadic(string &...$s): void
{

}

function ($s): void {
assertType('mixed', $s);
noParamOut($s);
assertType('string', $s);
};

function ($s, $t): void {
assertType('mixed', $s);
assertType('mixed', $t);
noParamOutVariadic($s, $t);
assertType('string', $s);
assertType('string', $t);
};

class NoParamOutClass
{

function doFoo(string &$s): void
{

}

function doFooVariadic(string &...$s): void
{

}

}

function ($s): void {
assertType('mixed', $s);
$c = new NoParamOutClass();
$c->doFoo($s);
assertType('string', $s);
};

function ($s, $t): void {
assertType('mixed', $s);
assertType('mixed', $t);
$c = new NoParamOutClass();
$c->doFooVariadic($s, $t);
assertType('string', $s);
assertType('string', $t);
};

0 comments on commit 27a952e

Please sign in to comment.