Skip to content

Commit

Permalink
AutoloadSourceLocator - give all found identifiers in an autoloaded file
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed May 26, 2020
1 parent 423fd9b commit 25c7791
Show file tree
Hide file tree
Showing 12 changed files with 168 additions and 55 deletions.
26 changes: 13 additions & 13 deletions src/Analyser/NodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -531,20 +531,20 @@ private function processStmtNode(
if (!$betterReflectionClass instanceof \Roave\BetterReflection\Reflection\ReflectionClass) {
throw new \PHPStan\ShouldNotHappenException();
}
$classScope = $scope->enterClass(
new ClassReflection(
$this->reflectionProvider,
$this->fileTypeMapper,
$this->classReflectionExtensionRegistryProvider->getRegistry()->getPropertiesClassReflectionExtensions(),
$this->classReflectionExtensionRegistryProvider->getRegistry()->getMethodsClassReflectionExtensions(),
$betterReflectionClass->getName(),
new ReflectionClass($betterReflectionClass),
null,
null,
null,
sprintf('%s:%d', $scope->getFile(), $stmt->getStartLine())
)
$classReflection = new ClassReflection(
$this->reflectionProvider,
$this->fileTypeMapper,
$this->classReflectionExtensionRegistryProvider->getRegistry()->getPropertiesClassReflectionExtensions(),
$this->classReflectionExtensionRegistryProvider->getRegistry()->getMethodsClassReflectionExtensions(),
$betterReflectionClass->getName(),
new ReflectionClass($betterReflectionClass),
null,
null,
null,
sprintf('%s:%d', $scope->getFile(), $stmt->getStartLine())
);
$this->reflectionProvider->hasClass($classReflection->getName());
$classScope = $scope->enterClass($classReflection);
} elseif ($stmt instanceof Class_) {
if ($stmt->name === null) {
throw new \PHPStan\ShouldNotHappenException();
Expand Down
134 changes: 110 additions & 24 deletions src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
use Roave\BetterReflection\SourceLocator\Ast\Strategy\NodeToReflection;
use Roave\BetterReflection\SourceLocator\Located\LocatedSource;
use Roave\BetterReflection\SourceLocator\Type\SourceLocator;
use function array_key_exists;
use function file_exists;
use function restore_error_handler;

Expand All @@ -40,6 +41,18 @@ class AutoloadSourceLocator implements SourceLocator

private static ?FileNodesFetcher $currentFileNodesFetcher = null;

/** @var array<string, FetchedNode<\PhpParser\Node\Stmt\ClassLike>> */
private array $classNodes = [];

/** @var array<string, FetchedNode<\PhpParser\Node\Stmt\Function_>> */
private array $functionNodes = [];

/** @var array<int, FetchedNode<\PhpParser\Node\Stmt\Const_|\PhpParser\Node\Expr\FuncCall>> */
private array $constantNodes = [];

/** @var array<string, \Roave\BetterReflection\SourceLocator\Located\LocatedSource> */
private array $locatedSourcesByFile = [];

/**
* Note: the constructor has been made a 0-argument constructor because `\stream_wrapper_register`
* is a piece of trash, and doesn't accept instances, just class names.
Expand All @@ -63,6 +76,16 @@ public function locateIdentifier(Reflector $reflector, Identifier $identifier):
{
if ($identifier->isFunction()) {
$functionName = $identifier->getName();
$loweredFunctionName = strtolower($functionName);
if (array_key_exists($loweredFunctionName, $this->functionNodes)) {
$nodeToReflection = new NodeToReflection();
return $nodeToReflection->__invoke(
$reflector,
$this->functionNodes[$loweredFunctionName]->getNode(),
$this->locatedSourcesByFile[$this->functionNodes[$loweredFunctionName]->getFileName()],
$this->functionNodes[$loweredFunctionName]->getNamespace()
);
}
if (!function_exists($functionName)) {
return null;
}
Expand All @@ -79,6 +102,51 @@ public function locateIdentifier(Reflector $reflector, Identifier $identifier):

if ($identifier->isConstant()) {
$constantName = $identifier->getName();
$nodeToReflection = new NodeToReflection();
foreach ($this->constantNodes as $stmtConst) {
if ($stmtConst->getNode() instanceof FuncCall) {
$constantReflection = $nodeToReflection->__invoke(
$reflector,
$stmtConst->getNode(),
$this->locatedSourcesByFile[$stmtConst->getFileName()],
$stmtConst->getNamespace()
);
if ($constantReflection === null) {
continue;
}
if (!$constantReflection instanceof ReflectionConstant) {
throw new \PHPStan\ShouldNotHappenException();
}
if ($constantReflection->getName() !== $identifier->getName()) {
continue;
}

return $constantReflection;
}

foreach (array_keys($stmtConst->getNode()->consts) as $i) {
$constantReflection = $nodeToReflection->__invoke(
$reflector,
$stmtConst->getNode(),
$this->locatedSourcesByFile[$stmtConst->getFileName()],
$stmtConst->getNamespace(),
$i
);
if ($constantReflection === null) {
continue;
}
if (!$constantReflection instanceof ReflectionConstant) {
throw new \PHPStan\ShouldNotHappenException();
}
if ($constantReflection->getName() !== $identifier->getName()) {
continue;
}

return $constantReflection;
}
}


if (!defined($constantName)) {
return null;
}
Expand All @@ -102,6 +170,17 @@ public function locateIdentifier(Reflector $reflector, Identifier $identifier):
return null;
}

$loweredClassName = strtolower($identifier->getName());
if (array_key_exists($loweredClassName, $this->classNodes)) {
$nodeToReflection = new NodeToReflection();
return $nodeToReflection->__invoke(
$reflector,
$this->classNodes[$loweredClassName]->getNode(),
$this->locatedSourcesByFile[$this->classNodes[$loweredClassName]->getFileName()],
$this->classNodes[$loweredClassName]->getNamespace()
);
}

$locateResult = $this->locateClassByName($identifier->getName());
if ($locateResult === null) {
return null;
Expand All @@ -114,36 +193,43 @@ public function locateIdentifier(Reflector $reflector, Identifier $identifier):
private function findReflection(Reflector $reflector, string $file, Identifier $identifier): ?Reflection
{
$result = $this->fileNodesFetcher->fetchNodes($file);
$this->locatedSourcesByFile[$file] = $result->getLocatedSource();
foreach ($result->getClassNodes() as $className => $fetchedClassNode) {
$this->classNodes[$className] = $fetchedClassNode;
}
foreach ($result->getFunctionNodes() as $functionName => $fetchedFunctionNode) {
$this->functionNodes[$functionName] = $fetchedFunctionNode;
}
foreach ($result->getConstantNodes() as $fetchedConstantNode) {
$this->constantNodes[] = $fetchedConstantNode;
}

$nodeToReflection = new NodeToReflection();
if ($identifier->isClass()) {
foreach ($result->getClassNodes() as $fetchedFunctionNode) {
$reflection = $nodeToReflection->__invoke(
$reflector,
$fetchedFunctionNode->getNode(),
$result->getLocatedSource(),
$fetchedFunctionNode->getNamespace()
);
if ($reflection === null) {
continue;
}

return $reflection;
$identifierName = strtolower($identifier->getName());
if (!array_key_exists($identifierName, $this->classNodes)) {
return null;
}

return $nodeToReflection->__invoke(
$reflector,
$this->classNodes[$identifierName]->getNode(),
$result->getLocatedSource(),
$this->classNodes[$identifierName]->getNamespace()
);
}
if ($identifier->isFunction()) {
foreach ($result->getFunctionNodes() as $fetchedFunctionNode) {
$reflection = $nodeToReflection->__invoke(
$reflector,
$fetchedFunctionNode->getNode(),
$result->getLocatedSource(),
$fetchedFunctionNode->getNamespace()
);
if ($reflection === null) {
continue;
}

return $reflection;
$identifierName = strtolower($identifier->getName());
if (!array_key_exists($identifierName, $this->functionNodes)) {
return null;
}

return $nodeToReflection->__invoke(
$reflector,
$this->functionNodes[$identifierName]->getNode(),
$result->getLocatedSource(),
$this->functionNodes[$identifierName]->getNamespace()
);
}

return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class OptimizedDirectorySourceLocator implements SourceLocator
private array $functionNodes = [];

/** @var array<string, \Roave\BetterReflection\SourceLocator\Located\LocatedSource> */
private array $locatedSourcesByFile;
private array $locatedSourcesByFile = [];

public function __construct(
FileNodesFetcher $fileNodesFetcher,
Expand Down
3 changes: 2 additions & 1 deletion src/Testing/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
use PHPStan\Reflection\BetterReflection\Reflector\MemoizingFunctionReflector;
use PHPStan\Reflection\BetterReflection\SourceLocator\AutoloadSourceLocator;
use PHPStan\Reflection\BetterReflection\SourceLocator\ComposerJsonAndInstalledJsonSourceLocatorMaker;
use PHPStan\Reflection\BetterReflection\SourceLocator\FileNodesFetcher;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\FunctionReflectionFactory;
use PHPStan\Reflection\Mixin\MixinMethodsClassReflectionExtension;
Expand Down Expand Up @@ -429,7 +430,7 @@ public static function getReflectors(): array
});
$reflectionSourceStubber = new ReflectionSourceStubber();
$locators[] = new PhpInternalSourceLocator($astLocator, new PhpStormStubsSourceStubber($phpParser));
$locators[] = new AutoloadSourceLocator($astLocator);
$locators[] = new AutoloadSourceLocator(self::getContainer()->getByType(FileNodesFetcher::class));
$locators[] = new PhpInternalSourceLocator($astLocator, $reflectionSourceStubber);
$locators[] = new EvaledCodeSourceLocator($astLocator, $reflectionSourceStubber);
$sourceLocator = new MemoizingSourceLocator(new AggregateSourceLocator($locators));
Expand Down
14 changes: 10 additions & 4 deletions tests/PHPStan/Analyser/AnalyserIntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,14 @@ public function testExtendingUnknownClass(): void
{
$errors = $this->runAnalyse(__DIR__ . '/data/extending-unknown-class.php');
$this->assertCount(1, $errors);
$this->assertSame(5, $errors[0]->getLine());
$this->assertSame('Class ExtendingUnknownClass\Foo extends unknown class ExtendingUnknownClass\Bar.', $errors[0]->getMessage());

if (self::$useStaticReflectionProvider) {
$this->assertSame(5, $errors[0]->getLine());
$this->assertSame('Class ExtendingUnknownClass\Foo extends unknown class ExtendingUnknownClass\Bar.', $errors[0]->getMessage());
} else {
$this->assertNull($errors[0]->getLine());
$this->assertSame('Class ExtendingUnknownClass\Bar not found and could not be autoloaded.', $errors[0]->getMessage());
}
}

public function testExtendingKnownClassWithCheck(): void
Expand Down Expand Up @@ -201,11 +207,11 @@ public function testTwoSameClassesInSingleFile(): void

$error = $errors[1];
$this->assertSame('Property TwoSame\Foo::$prop (int) does not accept default value of type string.', $error->getMessage());
$this->assertSame(17, $error->getLine());
$this->assertSame(18, $error->getLine());

$error = $errors[2];
$this->assertSame('Property TwoSame\Foo::$prop2 (int) does not accept default value of type string.', $error->getMessage());
$this->assertSame(20, $error->getLine());
$this->assertSame(21, $error->getLine());
}

/**
Expand Down
1 change: 0 additions & 1 deletion tests/PHPStan/Analyser/NodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9018,7 +9018,6 @@ public function testDynamicConstants(
string $expression
): void
{
require_once __DIR__ . '/data/dynamic-constant.php';
$this->assertTypes(
__DIR__ . '/data/dynamic-constant.php',
$description,
Expand Down
14 changes: 8 additions & 6 deletions tests/PHPStan/Analyser/data/two-same-classes.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ class Foo

}

class Foo
{
if (rand(0, 0)) {
class Foo
{

/** @var int */
private $prop = 'str';
/** @var int */
private $prop = 'str';

/** @var int */
private $prop2 = 'str';
/** @var int */
private $prop2 = 'str';

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@

use PHPStan\Testing\TestCase;
use Roave\BetterReflection\Reflector\ClassReflector;
use Roave\BetterReflection\Reflector\ConstantReflector;
use Roave\BetterReflection\Reflector\FunctionReflector;
use TestSingleFileSourceLocator\AFoo;

function testFunctionForLocator()
function testFunctionForLocator(): void // phpcs:disable
{

}
Expand All @@ -22,11 +23,28 @@ public function testAutoloadEverythingInFile(): void
$locator = new AutoloadSourceLocator(self::getContainer()->getByType(FileNodesFetcher::class));
$classReflector = new ClassReflector($locator);
$functionReflector = new FunctionReflector($locator, $classReflector);
$constantReflector = new ConstantReflector($locator, $classReflector);
$aFoo = $classReflector->reflect(AFoo::class);
$this->assertNotNull($aFoo->getFileName());
$this->assertSame('a.php', basename($aFoo->getFileName()));

$testFunctionReflection = $functionReflector->reflect('PHPStan\\Reflection\\BetterReflection\\SourceLocator\testFunctionForLocator');
$this->assertSame(__FILE__, $testFunctionReflection->getFileName());

$someConstant = $constantReflector->reflect('TestSingleFileSourceLocator\\SOME_CONSTANT');
$this->assertNotNull($someConstant->getFileName());
$this->assertSame('a.php', basename($someConstant->getFileName()));
$this->assertSame(1, $someConstant->getValue());

$anotherConstant = $constantReflector->reflect('TestSingleFileSourceLocator\\ANOTHER_CONSTANT');
$this->assertNotNull($anotherConstant->getFileName());
$this->assertSame('a.php', basename($anotherConstant->getFileName()));
$this->assertSame(2, $anotherConstant->getValue());

$doFooFunctionReflection = $functionReflector->reflect('TestSingleFileSourceLocator\\doFoo');
$this->assertSame('TestSingleFileSourceLocator\\doFoo', $doFooFunctionReflection->getName());
$this->assertNotNull($doFooFunctionReflection->getFileName());
$this->assertSame('a.php', basename($doFooFunctionReflection->getFileName()));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,7 @@ function doFoo()
{

}

define('TestSingleFileSourceLocator\\SOME_CONSTANT', 1);

const ANOTHER_CONSTANT = 2;
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ protected function getRule(): Rule

public function testClassWithWrongCase(): void
{
require_once __DIR__ . '/data/trait-use.php';
$this->analyse([__DIR__ . '/data/trait-use.php'], [
[
'Trait TraitUseCase\FooTrait referenced with incorrect case: TraitUseCase\FOOTrait.',
Expand Down
3 changes: 1 addition & 2 deletions tests/PHPStan/Rules/Generics/ClassTemplateTypeRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,7 @@ public function testRule(): void
24,
],
[
//'Class ClassTemplateType\Baz referenced with incorrect case: ClassTemplateType\baz.',
'PHPDoc tag @template T for class ClassTemplateType\Lorem has invalid bound type ClassTemplateType\baz.',
'Class ClassTemplateType\Baz referenced with incorrect case: ClassTemplateType\baz.',
32,
],
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ protected function getRule(): Rule

public function testRule(): void
{
require_once __DIR__ . '/data/function-signature-variance.php';
$this->analyse([__DIR__ . '/data/function-signature-variance.php'], [
[
'Template type T is declared as covariant, but occurs in contravariant position in parameter a of function FunctionSignatureVariance\f().',
Expand Down

0 comments on commit 25c7791

Please sign in to comment.