Skip to content

Commit

Permalink
closes #31 - getting types for methods called on new instances
Browse files Browse the repository at this point in the history
  • Loading branch information
morria committed Dec 11, 2015
1 parent a320b0e commit bee8702
Show file tree
Hide file tree
Showing 10 changed files with 294 additions and 199 deletions.
267 changes: 267 additions & 0 deletions src/Phan/Analyze/ClassName/MethodCallVisitor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
<?php declare(strict_types=1);
namespace Phan\Analyze\ClassName;

use \Phan\Analyze\ClassNameVisitor;
use \Phan\CodeBase;
use \Phan\Debug;
use \Phan\Exception\AccessException;
use \Phan\Language\AST;
use \Phan\Language\AST\Element;
use \Phan\Language\AST\KindVisitorImplementation;
use \Phan\Language\Context;
use \Phan\Language\FQSEN\FullyQualifiedClassName;
use \Phan\Language\UnionType;
use \Phan\Log;
use \ast\Node;

/**
* A visitor that can extract a class name from a few
* types of nodes
*/
class MethodCallVisitor extends KindVisitorImplementation {

/**
* @var Context
* The context of the current execution
*/
private $context;

/**
* @var CodeBase
*/
private $code_base;

/**
* @param Context $context
* The context of the current execution
*
* @param CodeBase $code_base
*/
public function __construct(Context $context, CodeBase $code_base) {
$this->context = $context;
$this->code_base = $code_base;
}

/**
* Default visitor for node kinds that do not have
* an overriding method
*
* @param Node $node
* A node of the type indicated by the method name that we'd
* like to figure out the type that it produces.
*
* @return string
* The class name represented by the given call
*/
public function visit(Node $node) : string {
return '';
}

/**
* @param Node $node
* A node of the type indicated by the method name that we'd
* like to figure out the type that it produces.
*
* @return string
* The class name represented by the given call
*/
public function visitNew(Node $node) : string {
return (new Element($node))->acceptKindVisitor(
new ClassNameVisitor(
$this->context,
$this->code_base
)
);
}

/**
* @param Node $node
* A node of the type indicated by the method name that we'd
* like to figure out the type that it produces.
*
* @return string
* The class name represented by the given call
*/
public function visitVar(Node $node) : string {
// $$var->method()
if(($node->children['name'] instanceof Node)) {
return '';
}

// $this->method()
if($node->children['name'] == 'this') {
if(!$this->context->isInClassScope()) {
Log::err(
Log::ESTATIC,
'Using $this when not in object context',
$this->context->getFile(),
$node->lineno
);
return '';
}

return (string)$this->context->getClassFQSEN();
}

$variable_name = $node->children['name'];

if (!$this->context->getScope()->hasVariableWithName(
$variable_name
)) {
// Got lost, couldn't find the variable in the current scope
// If it really isn't defined, it will be caught by the
// undefined var error
return '';
}

$variable =
$this->context->getScope()->getVariableWithName($variable_name);

// Hack - loop through the possible types of the var and assume
// first found class is correct
foreach($variable->getUnionType()->nonGenericArrayTypes()->getTypeList() as $type) {
$child_class_fqsen =
FullyQualifiedClassName::fromStringInContext(
(string)$type,
$this->context
);

if ($this->code_base->hasClassWithFQSEN($child_class_fqsen)) {
return (string)FullyQualifiedClassName::fromStringInContext(
(string)$type,
$this->context
);
}
}

// Could not find name
return '';
}

/**
* @param Node $node
* A node of the type indicated by the method name that we'd
* like to figure out the type that it produces.
*
* @return string
* The class name represented by the given call
*/
public function visitProp(Node $node) : string {
if(!($node->children['expr']->kind == \ast\AST_VAR
&& !($node->children['expr']->children['name'] instanceof Node))
) {
return '';
}

// $var->prop->method()
$var = $node->children['expr'];

if($var->children['name'] == 'this') {

// If we're not in a class scope, 'this' won't work
if(!$this->context->isInClassScope()) {
Log::err(
Log::ESTATIC,
'Using $this when not in object context',
$this->context->getFile(),
$node->lineno
);

return '';
}

// $this->$node->method()
if($node->children['prop'] instanceof Node) {
// Too hard. Giving up.
return '';
}

$class = $this->context->getClassInScope(
$this->code_base
);


$property_name = $node->children['prop'];

if (!$class->hasPropertyWithName(
$this->code_base,
$property_name
)) {
// If we can't find the property, there's
// no type. Thie issue should be caught
// elsewhere.
return '';
}

try {
$property = $class->getPropertyByNameInContext(
$this->code_base,
$property_name,
$this->context
);
} catch (AccessException $exception) {
Log::err(
Log::EACCESS,
$exception->getMessage(),
$this->context->getFile(),
$node->lineno
);

return '';
}

$union_type = $property->getUnionType()
->nonNativeTypes();

if ($union_type->isEmpty()) {
// If we don't have a type on the property we
// can't figure out the class type.
return '';
} else {
// Return the first type on the property
// that could be a reference to a class
return (string)$union_type->head()->asFQSEN();
}

// No such property was found, or none were classes
// that could be found
return '';
}

return '';
}

/**
* @param Node $node
* A node of the type indicated by the method name that we'd
* like to figure out the type that it produces.
*
* @return string
* The class name represented by the given call
*/
public function visitMethodCall(Node $node) : string {
// Get the type returned by the first method
// call.
$union_type = UnionType::fromNode(
$this->context,
$this->code_base,
$node
);

// Find the subset of types that are viable
// classes
$viable_class_types = $union_type
->nonNativeTypes()
->nonGenericArrayTypes();

// If there are no non-native types, give up
if ($viable_class_types->isEmpty()) {
return '';
}

// Return the first non-native type in the
// list and hope its a class
return (string)$viable_class_types->head();
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?php declare(strict_types=1);
namespace Phan\Analyze;
namespace Phan\Analyze\ClassName;

use \Phan\CodeBase;
use \Phan\Debug;
Expand All @@ -16,7 +16,7 @@
* A visitor that can extract a class name from a few
* types of nodes
*/
class ClassNameValidationVisitor
class ValidationVisitor
extends KindVisitorImplementation
{
/**
Expand Down
Loading

0 comments on commit bee8702

Please sign in to comment.