Skip to content

Commit

Permalink
Support function and closure
Browse files Browse the repository at this point in the history
  • Loading branch information
janedbal committed Sep 7, 2023
1 parent 7962984 commit ed1b2ca
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 5 deletions.
33 changes: 28 additions & 5 deletions src/Rule/ForbidReturnValueInYieldingMethodRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,19 @@
use Generator;
use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Node\ClosureReturnStatementsNode;
use PHPStan\Node\MethodReturnStatementsNode;
use PHPStan\Node\ReturnStatementsNode;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleError;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Type\MixedType;
use PHPStan\Type\Type;
use function in_array;

/**
* @implements Rule<MethodReturnStatementsNode>
* @implements Rule<ReturnStatementsNode>
*/
class ForbidReturnValueInYieldingMethodRule implements Rule
{
Expand All @@ -27,11 +31,11 @@ public function __construct(bool $reportRegardlessOfReturnType)

public function getNodeType(): string
{
return MethodReturnStatementsNode::class;
return ReturnStatementsNode::class;
}

/**
* @param MethodReturnStatementsNode $node
* @param ReturnStatementsNode $node
* @return list<RuleError>
*/
public function processNode(
Expand All @@ -44,7 +48,7 @@ public function processNode(
}

if (!$this->reportRegardlessOfReturnType) {
$methodReturnType = ParametersAcceptorSelector::selectSingle($node->getMethodReflection()->getVariants())->getReturnType();
$methodReturnType = $this->getReturnType($node, $scope);

if (in_array(Generator::class, $methodReturnType->getObjectClassNames(), true)) {
return [];
Expand All @@ -61,7 +65,11 @@ public function processNode(
? 'this approach is denied'
: 'but this method is not marked to return Generator';

$errors[] = RuleErrorBuilder::message("Returned value from yielding method can be accessed only via Generator::getReturn, $suffix.")
$callType = $node instanceof MethodReturnStatementsNode // @phpstan-ignore-line ignore bc promise
? 'method'
: 'function';

$errors[] = RuleErrorBuilder::message("Returned value from yielding $callType can be accessed only via Generator::getReturn, $suffix.")
->line($returnNode->getLine())
->build();
}
Expand All @@ -70,4 +78,19 @@ public function processNode(
return $errors;
}

private function getReturnType(ReturnStatementsNode $node, Scope $scope): Type
{
$methodReflection = $scope->getFunction();

if ($node instanceof ClosureReturnStatementsNode) { // @phpstan-ignore-line ignore bc promise
return $scope->getFunctionType($node->getClosureExpr()->getReturnType(), false, false);
}

if ($methodReflection !== null) {
return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
}

return new MixedType();
}

}
20 changes: 20 additions & 0 deletions tests/Rule/data/ForbidReturnValueInYieldingMethodRule/normal.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,23 @@ public function returnGenerator(): \Generator {
return;
}
}

function returnIterable(): iterable {
yield 1;
return 2; // error: Returned value from yielding function can be accessed only via Generator::getReturn, but this method is not marked to return Generator.
}

function returnGenerator(): \Generator {
yield 1;
return 2;
}

function (): iterable {
yield 1;
return 2; // error: Returned value from yielding function can be accessed only via Generator::getReturn, but this method is not marked to return Generator.
};

function (): \Generator {
yield 1;
return 2;
};

0 comments on commit ed1b2ca

Please sign in to comment.