Skip to content

Commit 719a973

Browse files
committed
GNSR is dead, long live FNSR
1 parent 7d51e73 commit 719a973

File tree

6 files changed

+853
-2
lines changed

6 files changed

+853
-2
lines changed

src/Testing/RuleTestCase.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use PHPStan\Analyser\Analyser;
77
use PHPStan\Analyser\AnalyserResultFinalizer;
88
use PHPStan\Analyser\Error;
9+
use PHPStan\Analyser\Fiber\FiberNodeScopeResolver;
910
use PHPStan\Analyser\FileAnalyser;
1011
use PHPStan\Analyser\Generator\GeneratorNodeScopeResolver;
1112
use PHPStan\Analyser\Generator\GeneratorScopeFactory;
@@ -97,7 +98,13 @@ protected function createNodeScopeResolver(): NodeScopeResolver|GeneratorNodeSco
9798
$reflectionProvider = $this->createReflectionProvider();
9899
$typeSpecifier = $this->getTypeSpecifier();
99100

100-
return new NodeScopeResolver(
101+
$enableFnsr = getenv('PHPSTAN_FNSR');
102+
$className = NodeScopeResolver::class;
103+
if ($enableFnsr === '1') {
104+
$className = FiberNodeScopeResolver::class;
105+
}
106+
107+
return new $className(
101108
$reflectionProvider,
102109
self::getContainer()->getByType(InitializerExprTypeResolver::class),
103110
self::getReflector(),

src/Testing/TypeInferenceTestCase.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use PhpParser\Node;
66
use PhpParser\Node\Expr\StaticCall;
77
use PhpParser\Node\Name;
8+
use PHPStan\Analyser\Fiber\FiberNodeScopeResolver;
89
use PHPStan\Analyser\Generator\GeneratorNodeScopeResolver;
910
use PHPStan\Analyser\Generator\GeneratorScopeFactory;
1011
use PHPStan\Analyser\Generator\NodeHandler\VarAnnotationHelper;
@@ -71,7 +72,13 @@ protected static function createNodeScopeResolver(): NodeScopeResolver|Generator
7172
$reflectionProvider = self::createReflectionProvider();
7273
$typeSpecifier = $container->getService('typeSpecifier');
7374

74-
return new NodeScopeResolver(
75+
$enableFnsr = getenv('PHPSTAN_FNSR');
76+
$className = NodeScopeResolver::class;
77+
if ($enableFnsr === '1') {
78+
$className = FiberNodeScopeResolver::class;
79+
}
80+
81+
return new $className(
7582
$reflectionProvider,
7683
$container->getByType(InitializerExprTypeResolver::class),
7784
self::getReflector(),
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Analyser\Fiber;
4+
5+
use PhpParser\Node;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\Rules\IdentifierRuleError;
8+
use PHPStan\Rules\Rule;
9+
use PHPStan\Rules\RuleErrorBuilder;
10+
use PHPStan\Testing\RuleTestCase;
11+
use PHPStan\Type\VerbosityLevel;
12+
use PHPUnit\Framework\Attributes\DataProvider;
13+
use PHPUnit\Framework\Attributes\RequiresPhp;
14+
15+
/**
16+
* @extends RuleTestCase<Rule<Node>>
17+
*/
18+
#[RequiresPhp('>= 8.1')]
19+
class FiberNodeScopeResolverRuleTest extends RuleTestCase
20+
{
21+
22+
/** @var callable(Node, Scope): list<IdentifierRuleError> */
23+
private $ruleCallback;
24+
25+
protected function getRule(): Rule
26+
{
27+
return new class ($this->ruleCallback) implements Rule {
28+
29+
/**
30+
* @param callable(Node, Scope): list<IdentifierRuleError> $ruleCallback
31+
*/
32+
public function __construct(private $ruleCallback)
33+
{
34+
}
35+
36+
public function getNodeType(): string
37+
{
38+
return Node::class;
39+
}
40+
41+
public function processNode(Node $node, Scope $scope): array
42+
{
43+
return ($this->ruleCallback)($node, $scope);
44+
}
45+
46+
};
47+
}
48+
49+
public static function dataRule(): iterable
50+
{
51+
yield [
52+
static fn (Node $node, Scope $scope) => [],
53+
[],
54+
];
55+
yield [
56+
static function (Node $node, Scope $scope) {
57+
if (!$node instanceof Node\Expr\MethodCall) {
58+
return [];
59+
}
60+
61+
$arg0 = $scope->getType($node->getArgs()[0]->value);
62+
$arg0 = $scope->getType($node->getArgs()[0]->value); // on purpose to hit the cache
63+
64+
return [
65+
RuleErrorBuilder::message($arg0->describe(VerbosityLevel::precise()))->identifier('gnsr.rule')->build(),
66+
RuleErrorBuilder::message($scope->getType($node->getArgs()[1]->value)->describe(VerbosityLevel::precise()))->identifier('gnsr.rule')->build(),
67+
RuleErrorBuilder::message($scope->getType($node->getArgs()[2]->value)->describe(VerbosityLevel::precise()))->identifier('gnsr.rule')->build(),
68+
];
69+
},
70+
[
71+
['1', 21],
72+
['2', 21],
73+
['3', 21],
74+
],
75+
];
76+
yield [
77+
static function (Node $node, Scope $scope) {
78+
if (!$node instanceof Node\Expr\MethodCall) {
79+
return [];
80+
}
81+
82+
$synthetic = $scope->getType(new Node\Scalar\String_('foo'));
83+
$synthetic2 = $scope->getType(new Node\Scalar\String_('bar'));
84+
85+
return [
86+
RuleErrorBuilder::message($synthetic->describe(VerbosityLevel::precise()))->identifier('gnsr.rule')->build(),
87+
RuleErrorBuilder::message($synthetic2->describe(VerbosityLevel::precise()))->identifier('gnsr.rule')->build(),
88+
];
89+
},
90+
[
91+
['\'foo\'', 21],
92+
['\'bar\'', 21],
93+
],
94+
];
95+
}
96+
97+
/**
98+
* @param callable(Node, Scope): list<IdentifierRuleError> $ruleCallback
99+
* @param list<array{0: string, 1: int, 2?: string|null}> $expectedErrors
100+
* @return void
101+
*/
102+
#[DataProvider('dataRule')]
103+
public function testRule(callable $ruleCallback, array $expectedErrors): void
104+
{
105+
$this->ruleCallback = $ruleCallback;
106+
$this->analyse([__DIR__ . '/data/rule.php'], $expectedErrors);
107+
}
108+
109+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Analyser\Fiber;
4+
5+
use PHPStan\Testing\TypeInferenceTestCase;
6+
use PHPUnit\Framework\Attributes\DataProvider;
7+
use PHPUnit\Framework\Attributes\RequiresPhp;
8+
9+
#[RequiresPhp('>= 8.1')]
10+
class FiberNodeScopeResolverTest extends TypeInferenceTestCase
11+
{
12+
13+
public static function dataFileAsserts(): iterable
14+
{
15+
yield from self::gatherAssertTypes(__DIR__ . '/data/gnsr.php');
16+
}
17+
18+
/**
19+
* @param mixed ...$args
20+
*/
21+
#[DataProvider('dataFileAsserts')]
22+
public function testFileAsserts(
23+
string $assertType,
24+
string $file,
25+
...$args,
26+
): void
27+
{
28+
$this->assertFileAsserts($assertType, $file, ...$args);
29+
}
30+
31+
public static function getAdditionalConfigFiles(): array
32+
{
33+
return [
34+
__DIR__ . '/../../../../conf/bleedingEdge.neon',
35+
];
36+
}
37+
38+
}

0 commit comments

Comments
 (0)