作者:nagyistg
项目:pha
/**
* @param Context $context
* The context in which the structural element lives
*
* @param string $name,
* The name of the typed structural element
*
* @param UnionType $type,
* A '|' delimited set of types satisfyped by this
* typed structural element.
*
* @param int $flags,
* The flags property contains node specific flags. It is
* always defined, but for most nodes it is always zero.
* ast\kind_uses_flags() can be used to determine whether
* a certain kind has a meaningful flags value.
*/
public function __construct(Context $context, string $name, UnionType $type, int $flags)
{
$this->context = $context;
$this->name = $name;
$this->type = $type;
$this->flags = $flags;
$this->setIsInternal($context->isInternal());
}
作者:ets
项目:pha
public function testSimple()
{
$context = new Context();
$context_namespace = $context->withNamespace('\\A');
$context_class = $context_namespace->withScope(new ClassScope($context_namespace->getScope(), FullyQualifiedClassName::fromFullyQualifiedString('\\A\\B')));
$context_method = $context_namespace->withScope(new FunctionLikeScope($context_namespace->getScope(), FullyQualifiedMethodName::fromFullyQualifiedString('\\A\\b::c')));
$this->assertTrue(!empty($context));
$this->assertTrue(!empty($context_namespace));
$this->assertTrue(!empty($context_class));
$this->assertTrue(!empty($context_method));
}
作者:ablyle
项目:pha
public function testSimple()
{
$context = new Context();
$context_namespace = $context->withNamespace('\\A');
$context_class = $context_namespace->withClassFQSEN(FullyQualifiedClassName::fromFullyQualifiedString('\\A\\B'));
$context_method = $context_namespace->withMethodFQSEN(FullyQualifiedMethodName::fromFullyQualifiedString('\\A\\b::c'));
$this->assertTrue(!empty($context));
$this->assertTrue(!empty($context_namespace));
$this->assertTrue(!empty($context_class));
$this->assertTrue(!empty($context_method));
}
作者:themario
项目:pha
/**
* @param Node $node
* A node to parse
*
* @return Context
* A new or an unchanged context resulting from
* parsing the node
*/
public function visitVar(Node $node) : Context
{
$variable_name = AST::variableName($node);
// Check to see if the variable already exists
if ($this->context->getScope()->hasVariableWithName($variable_name)) {
$variable = $this->context->getScope()->getVariableWithName($variable_name);
// If we're assigning to an array element then we don't
// know what the constitutation of the parameter is
// outside of the scope of this assignment, so we add to
// its union type rather than replace it.
if ($this->is_dim_assignment) {
$variable->getUnionType()->addUnionType($this->right_type);
} else {
// If the variable isn't a pass-by-reference paramter
// we clone it so as to not disturb its previous types
// as we replace it.
if (!($variable instanceof Parameter && $variable->isPassByReference())) {
$variable = clone $variable;
}
$variable->setUnionType($this->right_type);
}
$this->context->addScopeVariable($variable);
return $this->context;
}
$variable = Variable::fromNodeInContext($this->assignment_node, $this->context, $this->code_base);
// Set that type on the variable
$variable->getUnionType()->addUnionType($this->right_type);
// Note that we're not creating a new scope, just
// adding variables to the existing scope
$this->context->addScopeVariable($variable);
return $this->context;
}
作者:zhangf91
项目:pha
/**
* @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'];
$class = null;
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);
} else {
// Get the list of viable class types for the
// variable
$union_type = AST::varUnionType($this->context, $var)->nonNativeTypes()->nonGenericArrayTypes();
if ($union_type->isEmpty()) {
return '';
}
$class_fqsen = $union_type->head()->asFQSEN();
if (!$this->code_base->hasClassWithFQSEN($class_fqsen)) {
return '';
}
$class = $this->code_base->getClassByFQSEN($class_fqsen);
}
$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 '';
}
作者:ablyle
项目:pha
/**
* Perform some backwards compatibility checks on a node
*
* @return void
*/
public function analyzeBackwardCompatibility()
{
if (!Config::get()->backward_compatibility_checks) {
return;
}
if (empty($this->node->children['expr'])) {
return;
}
if ($this->node->kind === \ast\AST_STATIC_CALL || $this->node->kind === \ast\AST_METHOD_CALL) {
return;
}
$llnode = $this->node;
if ($this->node->kind !== \ast\AST_DIM) {
if (!$this->node->children['expr'] instanceof Node) {
return;
}
if ($this->node->children['expr']->kind !== \ast\AST_DIM) {
(new ContextNode($this->code_base, $this->context, $this->node->children['expr']))->analyzeBackwardCompatibility();
return;
}
$temp = $this->node->children['expr']->children['expr'];
$llnode = $this->node->children['expr'];
$lnode = $temp;
} else {
$temp = $this->node->children['expr'];
$lnode = $temp;
}
if (!($temp->kind == \ast\AST_PROP || $temp->kind == \ast\AST_STATIC_PROP)) {
return;
}
while ($temp instanceof Node && ($temp->kind == \ast\AST_PROP || $temp->kind == \ast\AST_STATIC_PROP)) {
$llnode = $lnode;
$lnode = $temp;
// Lets just hope the 0th is the expression
// we want
$temp = array_values($temp->children)[0];
}
if (!$temp instanceof Node) {
return;
}
// Foo::$bar['baz'](); is a problem
// Foo::$bar['baz'] is not
if ($lnode->kind === \ast\AST_STATIC_PROP && $this->node->kind !== \ast\AST_CALL) {
return;
}
// $this->$bar['baz']; is a problem
// $this->bar['baz'] is not
if ($lnode->kind === \ast\AST_PROP && !$lnode->children['prop'] instanceof Node && !$llnode->children['prop'] instanceof Node) {
return;
}
if (($lnode->children['prop'] instanceof Node && $lnode->children['prop']->kind == \ast\AST_VAR || !empty($lnode->children['class']) && $lnode->children['class'] instanceof Node && ($lnode->children['class']->kind == \ast\AST_VAR || $lnode->children['class']->kind == \ast\AST_NAME) || !empty($lnode->children['expr']) && $lnode->children['expr'] instanceof Node && ($lnode->children['expr']->kind == \ast\AST_VAR || $lnode->children['expr']->kind == \ast\AST_NAME)) && ($temp->kind == \ast\AST_VAR || $temp->kind == \ast\AST_NAME)) {
$ftemp = new \SplFileObject($this->context->getFile());
$ftemp->seek($this->node->lineno - 1);
$line = $ftemp->current();
unset($ftemp);
if (strpos($line, '}[') === false || strpos($line, ']}') === false || strpos($line, '>{') === false) {
Issue::maybeEmit($this->code_base, $this->context, Issue::CompatiblePHP7, $this->node->lineno ?? 0);
}
}
}
作者:hslatma
项目:pha
/**
* @param Context $context
* The context in which the FQSEN string was found
*
* @param $fqsen_string
* An FQSEN string like '\Namespace\Class'
*/
public static function fromStringInContext(string $fqsen_string, Context $context) : FullyQualifiedGlobalStructuralElement
{
// Check to see if we're fully qualified
if (0 === strpos($fqsen_string, '\\')) {
return static::fromFullyQualifiedString($fqsen_string);
}
// Split off the alternate ID
$parts = explode(',', $fqsen_string);
$fqsen_string = $parts[0];
$alternate_id = (int) ($parts[1] ?? 0);
assert(is_int($alternate_id), "Alternate must be an integer in {$fqsen_string}");
$parts = explode('\\', $fqsen_string);
$name = array_pop($parts);
assert(!empty($name), "The name cannot be empty in {$fqsen_string}");
// Check for a name map
if ($context->hasNamespaceMapFor(static::getNamespaceMapType(), $name)) {
return $context->getNamespaceMapFor(static::getNamespaceMapType(), $name);
}
$namespace = implode('\\', array_filter($parts));
// n.b.: Functions must override this method because
// they don't prefix the namespace for naked
// calls
if (empty($namespace)) {
$namespace = $context->getNamespace();
}
return static::make($namespace, $name, $alternate_id);
}
作者:themario
项目:pha
/**
* Visit a node with kind `\ast\AST_METHOD_CALL`
*
* @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 UnionType
* The set of types that are possibly produced by the
* given node
*/
public function visitMethodCall(Node $node) : UnionType
{
$class_name = AST::classNameFromNode($this->context, $this->code_base, $node);
if (empty($class_name)) {
return new UnionType();
}
$class_fqsen = FullyQualifiedClassName::fromstringInContext($class_name, $this->context);
assert($this->code_base->hasClassWithFQSEN($class_fqsen), "Class {$class_fqsen} must exist");
$clazz = $this->code_base->getClassByFQSEN($class_fqsen);
$method_name = $node->children['method'];
// Give up on any complicated nonsense where the
// method name is a variable such as in
// `$variable->$function_name()`.
if ($method_name instanceof Node) {
return new UnionType();
}
// Method names can some times turn up being
// other method calls.
assert(is_string($method_name), "Method name must be a string. Something else given.");
if (!$clazz->hasMethodWithName($this->code_base, $method_name)) {
Log::err(Log::EUNDEF, "call to undeclared method {$class_fqsen}->{$method_name}()", $this->context->getFile(), $node->lineno);
return new UnionType();
}
$method = $clazz->getMethodByNameInContext($this->code_base, $method_name, $this->context);
return $method->getUnionType();
}
作者:tpun
项目:pha
/**
* @param Node $node
* A node to parse
*
* @return void
*/
public function visitClosure(Decl $node)
{
try {
$method = (new ContextNode($this->code_base, $this->context->withLineNumberStart($node->lineno ?? 0), $node))->getClosure();
$method->addReference($this->context);
} catch (\Exception $exception) {
// Swallow it
}
}
作者:mgonya
项目:pha
/**
* @return bool
* False if the class name doesn't point to a known class
*/
private function classExistsOrIsNative(Node $node) : bool
{
if ($this->classExists()) {
return true;
}
$type = UnionType::fromStringInContext($this->class_name, $this->context);
if ($type->isNativeType()) {
return true;
}
Log::err(Log::EUNDEF, "reference to undeclared class {$this->class_fqsen}", $this->context->getFile(), $node->lineno);
return false;
}
作者:hslatma
项目:pha
/**
* @param Context $context
* The context in which the FQSEN string was found
*
* @param $fqsen_string
* An FQSEN string like '\Namespace\Class::methodName'
*
* @return FullyQualifiedMethodName
*/
public static function fromStringInContext(string $fqsen_string, Context $context)
{
// Test to see if we have a class defined
if (false === strpos($fqsen_string, '::')) {
$fully_qualified_class_name = $context->getClassFQSEN();
} else {
assert(false !== strpos($fqsen_string, '::'), "Fully qualified class element lacks '::' delimiter in {$fqsen_string}.");
list($class_name_string, $fqsen_string) = explode('::', $fqsen_string);
$fully_qualified_class_name = FullyQualifiedClassName::fromStringInContext($class_name_string, $context);
}
// Split off the alternate ID
$parts = explode(',', $fqsen_string);
$name = $parts[0];
$alternate_id = (int) ($parts[1] ?? 0);
assert(is_int($alternate_id), "Alternate must be an integer in {$fqsen_string}");
return static::make($fully_qualified_class_name, $name, $alternate_id);
}
作者:hslatma
项目:pha
/**
* @param Node $node
* A node to parse
*
* @return Context
* A new or an unchanged context resulting from
* parsing the node
*/
public function visitVar(Node $node) : Context
{
$variable_name = AST::variableName($node);
// Check to see if the variable already exists
if ($this->context->getScope()->hasVariableWithName($variable_name)) {
$variable = $this->context->getScope()->getVariableWithName($variable_name);
$variable->setUnionType($this->right_type);
$this->context->addScopeVariable($variable);
return $this->context;
}
$variable = Variable::fromNodeInContext($this->assignment_node, $this->context, $this->code_base);
// Set that type on the variable
$variable->getUnionType()->addUnionType($this->right_type);
// Note that we're not creating a new scope, just
// adding variables to the existing scope
$this->context->addScopeVariable($variable);
return $this->context;
}
作者:gitter-badge
项目:pha
private function visitClassNode(Node $node) : UnionType
{
// Things of the form `new $class_name();`
if ($node->kind == \ast\AST_VAR) {
return new UnionType();
}
// Anonymous class of form `new class { ... }`
if ($node->kind == \ast\AST_CLASS && $node->flags & \ast\flags\CLASS_ANONYMOUS) {
// Generate a stable name for the anonymous class
$anonymous_class_name = (new ContextNode($this->code_base, $this->context, $node))->getUnqualifiedNameForAnonymousClass();
// Turn that into a fully qualified name
$fqsen = FullyQualifiedClassName::fromStringInContext($anonymous_class_name, $this->context);
// Turn that into a union type
return Type::fromFullyQualifiedString((string) $fqsen)->asUnionType();
}
// Things of the form `new $method->name()`
if ($node->kind !== \ast\AST_NAME) {
return new UnionType();
}
// Get the name of the class
$class_name = $node->children['name'];
// If this is a straight-forward class name, recurse into the
// class node and get its type
if (!Type::isSelfTypeString($class_name)) {
// TODO: does anyone else call this method?
return self::unionTypeFromClassNode($this->code_base, $this->context, $node);
}
// This is a self-referential node
if (!$this->context->isInClassScope()) {
Log::err(Log::ESTATIC, "Cannot access {$class_name} when not in a class scope", $this->context->getFile(), $node->lineno);
return new UnionType();
}
// Reference to a parent class
if ($class_name === 'parent') {
$class = $this->context->getClassInScope($this->code_base);
if (!$class->hasParentClassFQSEN()) {
Log::err(Log::ESTATIC, "Reference to parent of parentless class {$class->getFQSEN()}", $this->context->getFile(), $node->lineno);
return new UnionType();
}
return Type::fromFullyQualifiedString((string) $class->getParentClassFQSEN())->asUnionType();
}
return Type::fromFullyQualifiedString((string) $this->context->getClassFQSEN())->asUnionType();
}
作者:kangko
项目:pha
/**
* @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
{
// Things of the form `new $class_name();`
if ($node->children['class']->kind == \ast\AST_VAR) {
return '';
}
// Anonymous class
// $v = new class { ... }
if ($node->children['class']->kind == \ast\AST_CLASS && $node->children['class']->flags & \ast\flags\CLASS_ANONYMOUS) {
return (new ContextNode($this->code_base, $this->context, $node->children['class']))->getUnqualifiedNameForAnonymousClass();
}
// Things of the form `new $method->name()`
if ($node->children['class']->kind !== \ast\AST_NAME) {
return '';
}
$class_name = $node->children['class']->children['name'];
if (!in_array($class_name, ['self', 'static', 'parent'])) {
return (string) UnionTypeVisitor::unionTypeFromClassNode($this->code_base, $this->context, $node->children['class']);
}
if (!$this->context->isInClassScope()) {
Log::err(Log::ESTATIC, "Cannot access {$class_name}:: when no class scope is active", $this->context->getFile(), $node->lineno);
return '';
}
if ($class_name == 'static') {
return (string) $this->context->getClassFQSEN();
}
if ($class_name == 'self') {
if ($this->context->isGlobalScope()) {
assert(false, "Unimplemented branch is required for {$this->context}");
} else {
return (string) $this->context->getClassFQSEN();
}
}
if ($class_name == 'parent') {
$clazz = $this->context->getClassInScope($this->code_base);
if (!$clazz->hasParentClassFQSEN()) {
return '';
}
return (string) $clazz->getParentClassFQSEN();
}
return '';
}
作者:ablyle
项目:pha
/**
* @param Node $node
* A node to parse
*
* @return Context
* A new or an unchanged context resulting from
* parsing the node
*/
public function visitInstanceof(Node $node) : Context
{
// Only look at things of the form
// `$variable instanceof ClassName`
if ($node->children['expr']->kind !== \ast\AST_VAR) {
return $this->context;
}
try {
// Get the variable we're operating on
$variable = (new ContextNode($this->code_base, $this->context, $node->children['expr']))->getVariable();
// Get the type that we're checking it against
$type = UnionType::fromNode($this->context, $this->code_base, $node->children['class']);
// Make a copy of the variable
$variable = clone $variable;
// Add the type to the variable
$variable->getUnionType()->addUnionType($type);
// Overwrite the variable with its new type
$this->context->addScopeVariable($variable);
} catch (\Exception $exception) {
// Swallow it
}
return $this->context;
}
作者:hslatma
项目:pha
/**
* Visit a node with kind `\ast\AST_NAMESPACE`
*
* @param Node $node
* A node to parse
*
* @return Context
* A new or an unchanged context resulting from
* parsing the node
*/
public function visitNamespace(Node $node) : Context
{
$namespace = '\\' . (string) $node->children['name'];
return $this->context->withNamespace($namespace);
}
作者:hslatma
项目:pha
/**
* @param string $name
* The name of the property
*
* @param Context $context
* The context of the caller requesting the property
*
* @return Property
* A property with the given name
*
* @throws AccessException
* An exception may be thrown if the caller does not
* have access to the given property from the given
* context
*/
public function getPropertyByNameInContext(CodeBase $code_base, string $name, Context $context) : Property
{
$property = $code_base->getProperty($this->getFQSEN(), $name);
// If we're getting the property from outside of this
// class and the property isn't public and we don't
// have a getter or setter, emit an access error
if ((!$context->hasClassFQSEN() || $context->getClassFQSEN() != $this->getFQSEN()) && !$property->isPublic() && !$this->hasMethodWithName($code_base, '__get') && !$this->hasMethodWithName($code_base, '__set')) {
if ($property->isPrivate()) {
throw new AccessException("Cannot access private property {$this->getFQSEN()}::\${$property->getName()}");
}
if ($property->isProtected()) {
throw new AccessException("Cannot access protected property {$this->getFQSEN()}::\${$property->getName()}");
}
}
return $property;
}
作者:mgonya
项目:pha
/**
* @param CodeBase $code_base
* The global code base
*
* @param Method $method
* The method we're analyzing arguments for
*
* @param Node $node
* The node holding the method call we're looking at
*
* @param Context $context
* The context in which we see the call
*
* @return null
*
* @see \Phan\Deprecated\Pass2::arglist_type_check
* Formerly `function arglist_type_check`
*/
private static function analyzeParameterList(CodeBase $code_base, Method $method, Node $node, Context $context)
{
foreach ($node->children ?? [] as $i => $argument) {
// Get the parameter associated with this argument
$parameter = $method->getParameterList()[$i] ?? null;
// This issue should be caught elsewhere
if (!$parameter) {
continue;
}
// If this is a pass-by-reference parameter, make sure
// we're passing an allowable argument
if ($parameter->isPassByReference()) {
if (!$argument instanceof \ast\Node || $argument->kind != \ast\AST_VAR && $argument->kind != \ast\AST_DIM && $argument->kind != \ast\AST_PROP && $argument->kind != \ast\AST_STATIC_PROP) {
Log::err(Log::ETYPE, "Only variables can be passed by reference at arg#" . ($i + 1) . " of {$method->getFQSEN()}()", $context->getFile(), $node->lineno);
} else {
$variable_name = AST::variableName($argument);
if ($argument->kind == \ast\AST_STATIC_PROP) {
if (in_array($variable_name, ['self', 'static', 'parent'])) {
Log::err(Log::ESTATIC, "Using {$variable_name}:: when not in object context", $context->getFile(), $argument->lineno);
}
}
}
}
// Get the type of the argument. We'll check it against
// the parameter in a moment
$argument_type = UnionType::fromNode($context, $code_base, $argument);
// Expand it to include all parent types up the chain
$argument_type_expanded = $argument_type->asExpandedTypes($code_base);
/* TODO see issue #42
If argument is an object and it has a String union type,
then we need to ignore that in strict_types=1 mode.
if ($argument instanceof \ast\Node) {
if(!empty($argument->children['class'])) {
// arg is an object
if ($method->getContext()->getStrictTypes()) {
...
}
}
}
or maybe UnionType::fromNode should check strict_types and
not return the string union type
or we shouldn't add the string type at all when a class
has a __toString() and instead set a flag and check that
instead
*/
// Check the method to see if it has the correct
// parameter types. If not, keep hunting through
// alternates of the method until we find one that
// takes the correct types
$alternate_parameter = null;
$alternate_found = false;
foreach ($method->alternateGenerator($code_base) as $alternate_id => $alternate_method) {
if (empty($alternate_method->getParameterList()[$i])) {
continue;
}
// Get the parameter associated with this argument
$alternate_parameter = $alternate_method->getParameterList()[$i] ?? null;
// Expand the types to find all parents and traits
$alternate_parameter_type_expanded = $alternate_parameter->getUnionType()->asExpandedTypes($code_base);
// See if the argument can be cast to the
// parameter
if ($argument_type_expanded->canCastToUnionType($alternate_parameter_type_expanded)) {
$alternate_found = true;
break;
}
}
if (!$alternate_found) {
$parameter_name = $alternate_parameter ? $alternate_parameter->getName() : 'unknown';
$parameter_type = $alternate_parameter ? $alternate_parameter->getUnionType() : 'unknown';
if ($method->getContext()->isInternal()) {
Log::err(Log::ETYPE, "arg#" . ($i + 1) . "({$parameter_name}) is " . "{$argument_type_expanded} but {$method->getFQSEN()}() " . "takes {$parameter_type}", $context->getFile(), $node->lineno);
} else {
Log::err(Log::ETYPE, "arg#" . ($i + 1) . "({$parameter_name}) is " . "{$argument_type_expanded} but {$method->getFQSEN()}() " . "takes {$parameter_type} " . "defined at {$method->getContext()->getFile()}:{$method->getContext()->getLineNumberStart()}", $context->getFile(), $node->lineno);
}
}
}
}
作者:Jvbzephi
项目:pha
/**
* @param CodeBase $code_base
* A code base needs to be passed in because we require
* it to be initialized before any classes or files are
* loaded.
*
* @param Context $context
* The context in which this node exists
*
* @param Node $node
* A node to parse and scan for errors
*
* @return Context
* The context from within the node is returned
*/
public static function analyzeNodeInContext(CodeBase $code_base, Context $context, Node $node, Node $parent_node = null, int $depth = 0) : Context
{
// Visit the given node populating the code base
// with anything we learn and get a new context
// indicating the state of the world within the
// given node
$node_context = (new PreOrderAnalysisVisitor($code_base, $context->withLineNumberStart($node->lineno ?? 0)))($node);
assert(!empty($context), 'Context cannot be null');
// We collect all child context so that the
// PostOrderAnalysisVisitor can optionally operate on
// them
$child_context_list = [];
$child_context = $node_context;
// With a context that is inside of the node passed
// to this method, we analyze all children of the
// node.
foreach ($node->children ?? [] as $child_node) {
// Skip any non Node children.
if (!$child_node instanceof Node) {
continue;
}
if (!self::shouldVisit($child_node)) {
$child_context->withLineNumberStart($child_node->lineno ?? 0);
continue;
}
// All nodes but conditionals pass context to
// their siblings. Child nodes of conditionals
// operate in a context independent of eachother
switch ($child_node->kind) {
case \ast\AST_IF_ELEM:
$child_context = $node_context;
break;
}
// Step into each child node and get an
// updated context for the node
$child_context = self::analyzeNodeInContext($code_base, $child_context->withLineNumberStart($child_node->lineno ?? 0), $child_node, $node, $depth + 1);
$child_context_list[] = $child_context;
}
// For if statements, we need to merge the contexts
// of all child context into a single scope based
// on any possible branching structure
$node_context = (new ContextMergeVisitor($code_base, $node_context, $child_context_list))($node);
// Now that we know all about our context (like what
// 'self' means), we can analyze statements like
// assignments and method calls.
$node_context = (new PostOrderAnalysisVisitor($code_base, $node_context->withLineNumberStart($node->lineno ?? 0), $parent_node))($node);
// When coming out of a scoped element, we pop the
// context to be the incoming context. Otherwise,
// we pass our new context up to our parent
switch ($node->kind) {
case \ast\AST_CLASS:
case \ast\AST_METHOD:
case \ast\AST_FUNC_DECL:
case \ast\AST_CLOSURE:
return $context;
default:
return $node_context;
}
}
作者:black-silenc
项目:pha
/**
* @param Node $node
* A node to parse
*
* @return Context
* A new or an unchanged context resulting from
* parsing the node
*/
public function visitIf(Node $node) : Context
{
// Get the list of scopes for each branch of the
// conditional
$scope_list = array_map(function (Context $context) {
return $context->getScope();
}, $this->child_context_list);
$has_else = array_reduce($node->children ?? [], function (bool $carry, $child_node) {
return $carry || $child_node instanceof Node && empty($child_node->children['cond']);
}, false);
// If we're not guaranteed to hit at least one
// branch, mark the incoming scope as a possibility
if (!$has_else) {
$scope_list[] = $this->context->getScope();
}
// If there weren't multiple branches, continue on
// as if the conditional never happened
if (count($scope_list) < 2) {
return array_values($this->child_context_list)[0];
}
// Get a list of all variables in all scopes
$variable_map = [];
foreach ($scope_list as $scope) {
foreach ($scope->getVariableMap() as $name => $variable) {
$variable_map[$name] = $variable;
}
}
// A function that determins if a variable is defined on
// every branch
$is_defined_on_all_branches = function (string $variable_name) use($scope_list) {
return array_reduce($scope_list, function (bool $has_variable, Scope $scope) use($variable_name) {
return $has_variable && $scope->hasVariableWithName($variable_name);
}, true);
};
// Get the intersection of all types for all versions of
// the variable from every side of the branch
$common_union_type = function (string $variable_name) use($scope_list) {
// Get a list of all variables with the given name from
// each scope
$variable_list = array_filter(array_map(function (Scope $scope) use($variable_name) {
if (!$scope->hasVariableWithName($variable_name)) {
return null;
}
return $scope->getVariableWithName($variable_name);
}, $scope_list));
// Get the list of types for each version of the variable
$type_set_list = array_map(function (Variable $variable) : Set {
return $variable->getUnionType()->getTypeSet();
}, $variable_list);
if (count($type_set_list) < 2) {
return new UnionType($type_set_list[0] ?? []);
}
return new UnionType(Set::intersectAll($type_set_list));
};
$scope = new Scope();
foreach ($variable_map as $name => $variable) {
// Skip variables that are only partially defined
if (!$is_defined_on_all_branches($name)) {
continue;
}
// Limit the type of the variable to the subset
// of types that are common to all branches
$variable = clone $variable;
$variable->setUnionType($common_union_type($name));
// Add the variable to the outgoing scope
$scope->addVariable($variable);
}
// print '<'.implode("\t", $scope_list) . "\n";
// print '>'.$scope."\n";
// Set the new scope with only the variables and types
// that are common to all branches
return $this->context->withScope($scope);
}