/** * @param Node $node * A node to parse * * @return Context * A new or an unchanged context resulting from * parsing the node */ public function visitCall(Node $node) : Context { $expression = $node->children['expr']; (new ContextNode($this->code_base, $this->context, $node))->analyzeBackwardCompatibility(); foreach ($node->children['args']->children ?? [] as $arg_node) { if ($arg_node instanceof Node) { (new ContextNode($this->code_base, $this->context, $arg_node))->analyzeBackwardCompatibility(); } } if ($expression->kind == \ast\AST_VAR) { $variable_name = (new ContextNode($this->code_base, $this->context, $expression))->getVariableName(); if (empty($variable_name)) { return $this->context; } // $var() - hopefully a closure, otherwise we don't know if ($this->context->getScope()->hasVariableWithName($variable_name)) { $variable = $this->context->getScope()->getVariableWithName($variable_name); $union_type = $variable->getUnionType(); if ($union_type->isEmpty()) { return $this->context; } foreach ($union_type->getTypeSet() as $type) { if (!$type instanceof CallableType) { continue; } $closure_fqsen = FullyQualifiedFunctionName::fromFullyQualifiedString((string) $type->asFQSEN()); if ($this->code_base->hasMethod($closure_fqsen)) { // Get the closure $method = $this->code_base->getMethod($closure_fqsen); // Check the call for paraemter and argument types $this->analyzeCallToMethod($this->code_base, $method, $node); } } } } else { if ($expression->kind == \ast\AST_NAME) { try { $method = (new ContextNode($this->code_base, $this->context, $expression))->getFunction($expression->children['name'] ?? $expression->children['method']); } catch (IssueException $exception) { $exception->getIssueInstance()(); return $this->context; } // Check the call for paraemter and argument types $this->analyzeCallToMethod($this->code_base, $method, $node); } else { if ($expression->kind == \ast\AST_CALL || $expression->kind == \ast\AST_STATIC_CALL || $expression->kind == \ast\AST_NEW || $expression->kind == \ast\AST_METHOD_CALL) { $class_list = (new ContextNode($this->code_base, $this->context, $expression))->getClassList(); foreach ($class_list as $class) { if (!$class->hasMethodWithName($this->code_base, '__invoke')) { continue; } $method = $class->getMethodByNameInContext($this->code_base, '__invoke', $this->context); // Check the call for paraemter and argument types $this->analyzeCallToMethod($this->code_base, $method, $node); } } } } return $this->context; }
/** * Visit a node with kind `\ast\AST_STATIC_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 visitStaticCall(Node $node) : UnionType { try { $class_name = AST::classNameFromNode($this->context, $this->code_base, $node); } catch (TypeException $exception) { return new UnionType(); } if (!$class_name) { return new UnionType(); } $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."); $method_fqsen = FullyQualifiedMethodName::make(FullyQualifiedClassName::fromStringInContext($class_name, $this->context), $method_name); if (!$this->code_base->hasMethod($method_fqsen)) { return new UnionType(); } $method = $this->code_base->getMethod($method_fqsen); return $method->getUnionType(); }
/** * @return Method */ public function getClosure() : Func { $closure_fqsen = FullyQualifiedFunctionName::fromClosureInContext($this->context); if (!$this->code_base->hasMethod($closure_fqsen)) { throw new CodeBaseException($closure_fqsen, "Could not find closure {$closure_fqsen}"); } return $this->code_base->getMethod($closure_fqsen); }
/** * @param Node $node * A node to parse * * @return Context * A new or an unchanged context resulting from * parsing the node */ public function visitCall(Node $node) : Context { $expression = $node->children['expr']; if (Config::get()->backward_compatibility_checks) { AST::backwardCompatibilityCheck($this->context, $node); foreach ($node->children['args']->children as $arg_node) { if ($arg_node instanceof Node) { AST::backwardCompatibilityCheck($this->context, $arg_node); } } } if ($expression->kind == \ast\AST_NAME) { try { $method = AST::functionFromNameInContext($expression->children['name'], $this->context, $this->code_base); } catch (CodeBaseException $exception) { Log::err(Log::EUNDEF, $exception->getMessage(), $this->context->getFile(), $node->lineno); return $this->context; } // Check the call for paraemter and argument types $this->analyzeCallToMethod($this->code_base, $method, $node); } else { if ($expression->kind == \ast\AST_VAR) { $variable_name = AST::variableName($expression); if (empty($variable_name)) { return $this->context; } // $var() - hopefully a closure, otherwise we don't know if ($this->context->getScope()->hasVariableWithName($variable_name)) { $variable = $this->context->getScope()->getVariableWithName($variable_name); $union_type = $variable->getUnionType(); if ($union_type->isEmpty()) { return $this->context; } $type = $union_type->head(); if (!$type instanceof CallableType) { return $this->context; } $closure_fqsen = FullyQualifiedFunctionName::fromFullyQualifiedString((string) $type->asFQSEN()); if ($this->code_base->hasMethod($closure_fqsen)) { // Get the closure $method = $this->code_base->getMethod($closure_fqsen); // Check the call for paraemter and argument types $this->analyzeCallToMethod($this->code_base, $method, $node); } } } } return $this->context; }
/** * Check to see if the given Clazz is a duplicate * * @return null */ public static function analyzeDuplicateFunction(CodeBase $code_base, FunctionInterface $method) { $fqsen = $method->getFQSEN(); if (!$fqsen->isAlternate()) { return; } $original_fqsen = $fqsen->getCanonicalFQSEN(); if (!$code_base->hasMethod($original_fqsen)) { return; } $original_method = $code_base->getMethod($original_fqsen); $method_name = $method->getName(); if ($original_method->isInternal()) { Issue::emit(Issue::RedefineFunctionInternal, $method->getFileRef()->getFile(), $method->getFileRef()->getLineNumberStart(), $method_name, $method->getFileRef()->getFile(), $method->getFileRef()->getLineNumberStart()); } else { Issue::emit(Issue::RedefineFunction, $method->getFileRef()->getFile(), $method->getFileRef()->getLineNumberStart(), $method_name, $method->getFileRef()->getFile(), $method->getFileRef()->getLineNumberStart(), $original_method->getFileRef()->getFile(), $original_method->getFileRef()->getLineNumberStart()); } }
/** * @param string $function_name * The name of the function we'd like to look up * * @param bool $is_function_declaration * This must be set to true if we're getting a function * that is being declared and false if we're getting a * function being called. * * @return Method * A method with the given name in the given context * * @throws CodeBaseExtension * An exception is thrown if we can't find the given * function */ public function getFunction(string $function_name, bool $is_function_declaration = false) : Method { if ($is_function_declaration) { $function_fqsen = FullyQualifiedFunctionName::make($this->context->getNamespace(), $function_name); } else { $function_fqsen = FullyQualifiedFunctionName::make($this->context->getNamespace(), $function_name); // If it doesn't exist in the local namespace, try it // in the global namespace if (!$this->code_base->hasMethod($function_fqsen)) { $function_fqsen = FullyQualifiedFunctionName::fromStringInContext($function_name, $this->context); } } // Make sure the method we're calling actually exists if (!$this->code_base->hasMethod($function_fqsen)) { throw new CodeBaseException($function_fqsen, "call to undefined function {$function_fqsen}()"); } $method = $this->code_base->getMethod($function_fqsen); return $method; }
/** * Check to see if the given Clazz is a duplicate * * @return null */ public static function analyzeDuplicateFunction(CodeBase $code_base, Method $method) { $fqsen = $method->getFQSEN(); if (!$fqsen->isAlternate()) { return; } $original_fqsen = $fqsen->getCanonicalFQSEN(); if (!$code_base->hasMethod($original_fqsen)) { return; } $original_method = $code_base->getMethod($original_fqsen); $method_name = $method->getName(); if ('internal' === $original_method->getContext()->getFile()) { // If its in an conditional and the original is an // internal method, presume its all OK. if ($method->getContext()->getIsConditional()) { return; } Log::err(Log::EREDEF, "Function {$method_name} defined at {$method->getContext()->getFile()}:{$method->getContext()->getLineNumberStart()} was previously defined internally", $method->getContext()->getFile(), $method->getContext()->getLineNumberStart()); } else { Log::err(Log::EREDEF, "Function {$method_name} defined at {$method->getContext()->getFile()}:{$method->getContext()->getLineNumberStart()} was previously defined at {$original_method->getContext()->getFile()}:{$original_method->getContext()->getLineNumberStart()}", $method->getContext()->getFile(), $method->getContext()->getLineNumberStart()); } }
/** * @return Method[] * The set of all alternates to this method */ public function alternateGenerator(CodeBase $code_base) : \Generator { $alternate_id = 0; $fqsen = $this->getFQSEN(); while ($code_base->hasMethod($fqsen)) { (yield $code_base->getMethod($fqsen)); $fqsen = $fqsen->withAlternateId(++$alternate_id); } }
/** * @return Method * The method with the given name */ public function getMethodByNameInContext(CodeBase $code_base, string $name, Context $context) : Method { $method_fqsen = FullyQualifiedMethodName::make($this->getFQSEN(), $name); if (!$code_base->hasMethod($method_fqsen)) { if ('__construct' === $name) { // Create a default constructor if its requested // but doesn't exist yet $default_constructor = Method::defaultConstructorForClassInContext($this, $this->getContext()->withClassFQSEN($this->getFQSEN())); $this->addMethod($code_base, $default_constructor); return $default_constructor; } throw new CodeBaseException("Method with name {$name} does not exist for class {$this->getFQSEN()}."); } return $code_base->getMethod($method_fqsen); }
/** * @param string $function_name * The name of the function we'd like to look up * * @param Context $context * The context in which we found the reference to the * given function name * * @param CodeBase $code_base * The global code base holding all state * * @param bool $is_function_declaration * This must be set to true if we're getting a function * that is being declared and false if we're getting a * function being called. * * @return Method * A method with the given name in the given context * * @throws CodeBaseExtension * An exception is thrown if we can't find the given * function */ public static function functionFromNameInContext(string $function_name, Context $context, CodeBase $code_base, bool $is_function_declaration = false) : Method { if ($is_function_declaration) { $function_fqsen = FullyQualifiedFunctionName::make($context->getNamespace(), $function_name); } else { $function_fqsen = FullyQualifiedFunctionName::fromStringInContext($function_name, $context); } // Make sure the method we're calling actually exists if (!$code_base->hasMethod($function_fqsen)) { throw new CodeBaseException("call to undefined function {$function_fqsen}()"); } $method = $code_base->getMethod($function_fqsen); return $method; }