Skip to content

Commit

Permalink
Yield type check
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Mar 23, 2019
1 parent 37dd975 commit 83e1163
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 2 deletions.
5 changes: 5 additions & 0 deletions conf/config.level3.neon
Expand Up @@ -23,6 +23,8 @@ conditionalTags:
phpstan.rules.rule: %featureToggles.yieldRules%
PHPStan\Rules\Generators\YieldInGeneratorRule:
phpstan.rules.rule: %featureToggles.yieldRules%
PHPStan\Rules\Generators\YieldTypeRule:
phpstan.rules.rule: %featureToggles.yieldRules%

services:
-
Expand Down Expand Up @@ -63,6 +65,9 @@ services:
arguments:
reportMaybes: %reportMaybes%

-
class: PHPStan\Rules\Generators\YieldTypeRule

-
class: PHPStan\Rules\Methods\MethodSignatureRule
arguments:
Expand Down
2 changes: 0 additions & 2 deletions src/Rules/Generators/YieldFromTypeRule.php
Expand Up @@ -88,8 +88,6 @@ public function processNode(Node $node, Scope $scope): array
);
}

// TODO test

return $messages;
}

Expand Down
85 changes: 85 additions & 0 deletions src/Rules/Generators/YieldTypeRule.php
@@ -0,0 +1,85 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Generators;

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleLevelHelper;
use PHPStan\Type\IntegerType;
use PHPStan\Type\MixedType;
use PHPStan\Type\NullType;
use PHPStan\Type\VerbosityLevel;

class YieldTypeRule implements Rule
{

/** @var RuleLevelHelper */
private $ruleLevelHelper;

public function __construct(
RuleLevelHelper $ruleLevelHelper
)
{
$this->ruleLevelHelper = $ruleLevelHelper;
}

public function getNodeType(): string
{
return Node\Expr\Yield_::class;
}

/**
* @param Node\Expr\Yield_ $node
* @param Scope $scope
* @return string[]
*/
public function processNode(Node $node, Scope $scope): array
{
$anonymousFunctionReturnType = $scope->getAnonymousFunctionReturnType();
$scopeFunction = $scope->getFunction();
if ($anonymousFunctionReturnType !== null) {
$returnType = $anonymousFunctionReturnType;
} elseif ($scopeFunction !== null) {
$returnType = ParametersAcceptorSelector::selectSingle($scopeFunction->getVariants())->getReturnType();
} else {
return []; // already reported by YieldInGeneratorRule
}

if ($returnType instanceof MixedType) {
return [];
}

if ($node->key === null) {
$keyType = new IntegerType();
} else {
$keyType = $scope->getType($node->key);
}

if ($node->value === null) {
$valueType = new NullType();
} else {
$valueType = $scope->getType($node->value);
}

$messages = [];
if (!$this->ruleLevelHelper->accepts($returnType->getIterableKeyType(), $keyType, $scope->isDeclareStrictTypes())) {
$messages[] = sprintf(
'Generator expects key type %s, %s given.',
$returnType->getIterableKeyType()->describe(VerbosityLevel::typeOnly()),
$keyType->describe(VerbosityLevel::typeOnly())
);
}
if (!$this->ruleLevelHelper->accepts($returnType->getIterableValueType(), $valueType, $scope->isDeclareStrictTypes())) {
$messages[] = sprintf(
'Generator expects value type %s, %s given.',
$returnType->getIterableValueType()->describe(VerbosityLevel::typeOnly()),
$valueType->describe(VerbosityLevel::typeOnly())
);
}

return $messages;
}

}
47 changes: 47 additions & 0 deletions tests/PHPStan/Rules/Generators/YieldTypeRuleTest.php
@@ -0,0 +1,47 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Generators;

use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleLevelHelper;
use PHPStan\Testing\RuleTestCase;

class YieldTypeRuleTest extends RuleTestCase
{

protected function getRule(): Rule
{
return new YieldTypeRule(new RuleLevelHelper($this->createBroker(), true, false, true));
}

public function testRule(): void
{
$this->analyse([__DIR__ . '/data/yield.php'], [
[
'Generator expects value type int, string given.',
14,
],
[
'Generator expects key type string, int given.',
15,
],
[
'Generator expects value type int, null given.',
15,
],
[
'Generator expects key type string, int given.',
16,
],
[
'Generator expects key type string, int given.',
17,
],
[
'Generator expects value type int, string given.',
17,
],
]);
}

}
20 changes: 20 additions & 0 deletions tests/PHPStan/Rules/Generators/data/yield.php
@@ -0,0 +1,20 @@
<?php

namespace YieldTypeRuleTest;

class Foo
{

/**
* @return \Generator<string, int>
*/
public function doFoo(): \Generator
{
yield 'foo' => 1;
yield 'foo' => 'bar';
yield;
yield 1;
yield 'foo';
}

}

0 comments on commit 83e1163

Please sign in to comment.