Skip to content

Commit

Permalink
Added scope binding
Browse files Browse the repository at this point in the history
  • Loading branch information
dantleech committed Dec 6, 2016
1 parent 3ba940b commit eeffd3b
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 26 deletions.
33 changes: 29 additions & 4 deletions src/Reflection/ReflectionVariable.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use PhpParser\Node\Expr\Variable;
use BetterReflection\Reflection\ReflectionType;
use BetterReflection\Reflection\ReflectionVariable;
use BetterReflection\Reflection\ReflectionFunctionAbstract;

class ReflectionVariable
{
Expand All @@ -32,14 +33,27 @@ class ReflectionVariable
*/
private $type;

public static function createFromParamAndType(Param $param, ReflectionType $type): ReflectionVariable
/**
* @var ReflectionFunctionAbstract
*/
private $scopeReflection;

public static function createFromParamAndType(
Param $param,
ReflectionType $type,
ReflectionFunctionAbstract $scopeReflection = null
): ReflectionVariable
{
return self::createFromNodeAndType($param, $type);
}

public static function createFromVariableAndType(Variable $variable, ReflectionType $type): ReflectionVariable
public static function createFromVariableAndType(
Variable $variable,
ReflectionType $type,
ReflectionFunctionAbstract $scopeReflection = null
): ReflectionVariable
{
return self::createFromNodeAndType($variable, $type);
return self::createFromNodeAndType($variable, $type, $scopeReflection);
}

public function getName(): string
Expand Down Expand Up @@ -71,6 +85,12 @@ public function getEndPos(): int
return $this->endPos;
}

public function getScopeReflection(): ReflectionFunctionAbstract
{
return $this->scopeReflection;
}


/**
* Create a new reflection variable,
*
Expand All @@ -81,13 +101,18 @@ public function getEndPos(): int
* the node, which means that the Lexer should be configured to provide
* them.
*/
private static function createFromNodeAndType(NodeAbstract $node, ReflectionType $type): ReflectionVariable
private static function createFromNodeAndType(
NodeAbstract $node,
ReflectionType $type,
ReflectionFunctionAbstract $scopeReflection = null
): ReflectionVariable
{
$reflectionVariable = new self();
$reflectionVariable->name = $node->name;
$reflectionVariable->type = $type;
$reflectionVariable->startPos = $node->getAttribute('startFilePos');
$reflectionVariable->endPos = $node->getAttribute('endFilePos');
$reflectionVariable->scopeReflection = $scopeReflection;

return $reflectionVariable;
}
Expand Down
32 changes: 29 additions & 3 deletions src/Util/Visitor/VariableCollectionVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ class VariableCollectionVisitor extends NodeVisitorAbstract
*/
private $methodParamTypes = [];

/**
* @var array
*/
private $scopes = [];

public function __construct(CompilerContext $context, TypeResolver $typeResolver = null)
{
$this->context = $context;
Expand Down Expand Up @@ -79,7 +84,7 @@ public function beforeTraverse(array $nodes)
public function enterNode(Node $node)
{
if ($node instanceof FunctionLike) {
$this->processFunctionLike($node);
$this->scopes[] = $this->processFunctionLike($node);

return;
}
Expand All @@ -91,6 +96,15 @@ public function enterNode(Node $node)
}
}

public function leaveNode(Node $node)
{
if ($node instanceof FunctionLike) {
array_pop($this->scopes);

return;
}
}

private function processFunctionLike(FunctionLike $node)
{
// reset when we enter the class method scope
Expand All @@ -106,8 +120,11 @@ private function processFunctionLike(FunctionLike $node)
$this->methodParamTypes[$param->name] = $reflectionType;

// parameters count as available variables.
$this->variables[] = ReflectionVariable::createFromParamAndType($param, $reflectionType);
// TODO: Test
$this->variables[] = ReflectionVariable::createFromParamAndType($param, $reflectionType, $methodReflection);
}

return $methodReflection;
}

private function processAssign(Expr\Assign $node)
Expand All @@ -120,7 +137,7 @@ private function processAssign(Expr\Assign $node)

$type = $this->typeFromNode($node->expr);

$this->variables[] = ReflectionVariable::createFromVariableAndType($node->var, $type);
$this->variables[] = ReflectionVariable::createFromVariableAndType($node->var, $type, $this->getCurrentScope());
}

private function typeFromNode(Node $expr): ReflectionType
Expand Down Expand Up @@ -305,4 +322,13 @@ private function reflectionTypeForUnknown()
{
return $this->reflectionTypeFromString('mixed', false);
}

private function getCurrentScope()
{
if (empty($this->scopes)) {
return null;
}

return end($this->scopes);
}
}
106 changes: 87 additions & 19 deletions test/unit/Util/Visitor/VariableCollectionVisitorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
use BetterReflection\SourceLocator\Type\AutoloadSourceLocator;
use BetterReflection\SourceLocator\Type\EvaledCodeSourceLocator;
use BetterReflection\NodeCompiler\CompilerContext;
use BetterReflection\Reflection\ReflectionFunction;
use BetterReflection\Reflection\ReflectionMethod;

/**
* @covers \BetterReflection\Util\Visitor\VariableCollectionVisitor
Expand Down Expand Up @@ -289,13 +291,96 @@ public function getClassThree()
];
}

public function reflectionScopeBindingProvider()
{
return [
[
<<<'EOT'
EOT
,
]
];
}

public function testMethodReflectionScopeBinding()
{
$source = <<<'EOT'
function methodOne()
{
$foobar = 'string';
$barfoo = 'string';
}
function methodTwo()
{
$booboo = 'string';
}
EOT
;

$expectedVariableScopeNames = [
'foobar' => 'methodOne',
'barfoo' => 'methodOne',
'booboo' => 'methodTwo',
];

$variables = $this->getVariablesForSource($source);

foreach ($expectedVariableScopeNames as $expectedVariableName => $variableScopeName) {

foreach ($variables as $variable) {

if ($expectedVariableName === $variable->getName()) {
$this->assertInstanceOf(ReflectionMethod::class, $variable->getScopeReflection());
$this->assertEquals($variableScopeName, $variable->getScopeReflection()->getName());

continue 2;
}
}

$this->fail(sprintf('Could not find expected variable "%s"', $expectedVariableName));
}
}

public function testFunctionReflectionScopeBinding()
{
$this->markTestIncomplete();
}


/**
* @param Node[] $statements
* @param int $expectedReturns
*
* @dataProvider variableCollectionProvider
*/
public function testVariableCollectionProvider($source, $expectedVariables)
{
$variables = $this->getVariablesForSource($source);

foreach ($expectedVariables as $index => $expectedVariable) {
list($expectedName, $expectedType) = $expectedVariable;
$this->assertArrayHasKey($index, $variables);
$actual = $variables[$index];
$this->assertInstanceOf(ReflectionVariable::class, $actual);
$this->assertEquals($expectedName, $actual->getName());
$this->assertEquals($expectedType, (string) $actual->getType());
}
}

private function getVariablesForSource(string $source)
{
$context = $this->createCompilerContextFromSource($source);
$visitor = new VariableCollectionVisitor($context);

$traverser = new NodeTraverser();
$traverser->addVisitor($visitor);
$traverser->traverse([ $context->getSelf()->getAst() ]);

return $visitor->getVariables();
}

private function createCompilerContextFromSource(string $source)
{
$source = str_replace(':content:', $source, <<<'EOT'
<?php
Expand All @@ -307,7 +392,7 @@ class Foobar
:content:
}
EOT
);
);

$sourceLocator = new AggregateSourceLocator([
new StringSourceLocator($source),
Expand All @@ -316,24 +401,7 @@ class Foobar

$reflector = new ClassReflector($sourceLocator);
$reflection = $reflector->reflect('BetterReflectionTest\Util\Visitor\Fixtures\Foobar');
$context = new CompilerContext($reflector, $reflection);

$visitor = new VariableCollectionVisitor($context);

$traverser = new NodeTraverser();
$traverser->addVisitor($visitor);
$traverser->traverse([ $reflection->getAst() ]);

$variables = $visitor->getVariables();

foreach ($expectedVariables as $index => $expectedVariable) {
list($expectedName, $expectedType) = $expectedVariable;
$this->assertArrayHasKey($index, $variables);
$actual = $variables[$index];
$this->assertInstanceOf(ReflectionVariable::class, $actual);
$this->assertEquals($expectedName, $actual->getName());
$this->assertEquals($expectedType, (string) $actual->getType());
}
return new CompilerContext($reflector, $reflection);
}
}

0 comments on commit eeffd3b

Please sign in to comment.