public function testFullyQualifiedFunctionName() { $this->assertFQSENEqual(FullyQualifiedFunctionName::make('\\Name\\Space', 'g'), '\\Name\\Space\\g'); $this->assertFQSENEqual(FullyQualifiedFunctionName::make('', 'g'), '\\g'); $this->assertFQSENEqual(FullyQualifiedGlobalConstantName::make('', 'g'), '\\g'); $this->assertFQSENEqual(FullyQualifiedFunctionName::fromFullyQualifiedString('\\g'), '\\g'); $this->assertFQSENEqual(FullyQualifiedFunctionName::fromStringInContext('g', $this->context), '\\g'); }
/** * @return Func[] * One or more (alternate) methods begotten from * reflection info and internal method data */ public static function functionListFromReflectionFunction(CodeBase $code_base, \ReflectionFunction $reflection_function) : array { $context = new Context(); $parts = explode('\\', $reflection_function->getName()); $method_name = array_pop($parts); $namespace = '\\' . implode('\\', $parts); $fqsen = FullyQualifiedFunctionName::make($namespace, $method_name); $function = new Func($context, $fqsen->getName(), new UnionType(), 0, $fqsen); $function->setNumberOfRequiredParameters($reflection_function->getNumberOfRequiredParameters()); $function->setNumberOfOptionalParameters($reflection_function->getNumberOfParameters() - $reflection_function->getNumberOfRequiredParameters()); return self::functionListFromFunction($function, $code_base); }
/** * Add any functions from the FunctionSignatureMap that aren't * defined in this version of PHP to the code base * * @return void */ private function addUndefinedFunctionSignatures() { $function_signature_map = UnionType::internalFunctionSignatureMap(); foreach ($function_signature_map as $function_name => $signature) { $fqsen = FullyQualifiedFunctionName::make('\\', $function_name); // If we already loaded the function, skip it if ($this->hasMethod($fqsen)) { continue; } // Add each method returned for the signature foreach (Method::methodListFromSignature($this, $fqsen, $signature) as $method) { $this->addMethod($method); } } }
/** * @return Method[] * One or more (alternate) methods begotten from * reflection info and internal method data */ public static function methodListFromReflectionFunction(CodeBase $code_base, \ReflectionFunction $reflection_function) : array { $number_of_required_parameters = $reflection_function->getNumberOfRequiredParameters(); $number_of_optional_parameters = $reflection_function->getNumberOfParameters() - $number_of_required_parameters; $context = new Context(); $parts = explode('\\', $reflection_function->getName()); $method_name = array_pop($parts); $namespace = '\\' . implode('\\', $parts); $fqsen = FullyQualifiedFunctionName::make($namespace, $method_name); $method = new Method($context, $fqsen->getName(), new UnionType(), 0, $number_of_required_parameters, $number_of_optional_parameters); $method->setFQSEN($fqsen); return self::methodListFromMethod($method, $code_base); }
/** * @return array * A map from alias to target */ private function aliasTargetMapFromUseNode(Node $node, string $prefix = '') : array { assert($node->kind == \ast\AST_USE, 'Method takes AST_USE nodes'); $map = []; foreach ($node->children ?? [] as $child_node) { $target = $child_node->children['name']; if (empty($child_node->children['alias'])) { if (($pos = strrpos($target, '\\')) !== false) { $alias = substr($target, $pos + 1); } else { $alias = $target; } } else { $alias = $child_node->children['alias']; } if ($node->flags == T_FUNCTION) { $parts = explode('\\', $target); $function_name = array_pop($parts); $target = FullyQualifiedFunctionName::make(implode('\\', $parts), $function_name); } else { $target = FullyQualifiedClassName::fromFullyQualifiedString($prefix . '\\' . $target); } $map[$alias] = [$child_node->flags, $target]; } return $map; }
/** * @param string $scope * The scope of the method or function * * @param string $name * The name of the method (with an optional alternate id) * * @return bool */ private function hasMethodWithScopeAndName(string $scope, string $name) { if (!empty($this->method_map[$scope][$name])) { return true; } // For elements in the root namespace, check to see if // there's a static method signature for something that // hasn't been loaded into memory yet and create a // method out of it as its requested if ('\\' == $scope) { $function_signature_map = UnionType::internalFunctionSignatureMap(); $fqsen = FullyQualifiedFunctionName::make($scope, $name); if (!empty($function_signature_map[$name])) { $signature = $function_signature_map[$name]; // Add each method returned for the signature foreach (FunctionFactory::functionListFromSignature($this, $fqsen, $signature) as $method) { $this->addMethod($method); } return true; } } if (Database::isEnabled()) { // Otherwise, check the database try { MethodModel::read(Database::get(), $scope . '|' . $name); return true; } catch (NotFoundException $exception) { return false; } } else { return false; } }
/** * @return array * A map from alias to target */ private function aliasTargetMapFromUseNode(Node $node, string $prefix = '') : array { assert($node->kind == \ast\AST_USE, 'Method takes AST_USE nodes'); $map = []; foreach ($node->children ?? [] as $child_node) { $target = $child_node->children['name']; if (empty($child_node->children['alias'])) { if (($pos = strrpos($target, '\\')) !== false) { $alias = substr($target, $pos + 1); } else { $alias = $target; } } else { $alias = $child_node->children['alias']; } // if AST_USE does not have any flags set, then its AST_USE_ELEM // children will (this will be for AST_GROUP_USE) if ($node->flags !== 0) { $target_node = $node; } else { $target_node = $child_node; } if ($target_node->flags == T_FUNCTION) { $parts = explode('\\', $target); $function_name = array_pop($parts); $target = FullyQualifiedFunctionName::make($prefix . '\\' . implode('\\', $parts), $function_name); } else { if ($target_node->flags == T_CONST) { $parts = explode('\\', $target); $name = array_pop($parts); $target = FullyQualifiedGlobalConstantName::make($prefix . '\\' . implode('\\', $parts), $name); } else { $target = FullyQualifiedClassName::fromFullyQualifiedString($prefix . '\\' . $target); } } $map[$alias] = [$target_node->flags, $target]; } return $map; }
/** * @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 FunctionInterface * A method with the given name in the given context * * @throws IssueException * An exception is thrown if we can't find the given * function */ public function getFunction(string $function_name, bool $is_function_declaration = false) : FunctionInterface { 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->hasFunctionWithFQSEN($function_fqsen)) { $function_fqsen = FullyQualifiedFunctionName::fromStringInContext($function_name, $this->context); } } // Make sure the method we're calling actually exists if (!$this->code_base->hasFunctionWithFQSEN($function_fqsen)) { throw new IssueException(Issue::fromType(Issue::UndeclaredFunction)($this->context->getFile(), $this->node->lineno ?? 0, ["{$function_fqsen}()"])); } return $this->code_base->getFunctionByFQSEN($function_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::make($context->getNamespace(), $function_name); // If it doesn't exist in the local namespace, try it // in the global namespace if (!$code_base->hasMethod($function_fqsen)) { $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; }
/** * Visit a node with kind `\ast\AST_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 visitCall(Node $node) : UnionType { if ($node->children['expr']->kind !== \ast\AST_NAME) { // Things like `$func()` return new UnionType(); } $function_name = $node->children['expr']->children['name']; $function_fqsen = null; // If its not fully qualified if ($node->children['expr']->flags & \ast\flags\NAME_NOT_FQ) { // Check to see if we have a mapped name if ($this->context->hasNamespaceMapFor(T_FUNCTION, $function_name)) { $function_fqsen = $this->context->getNamespaceMapFor(T_FUNCTION, $function_name); } else { $function_fqsen = FullyQualifiedFunctionName::fromStringInContext($function_name, $this->context); } // If the name is fully qualified } else { $function_fqsen = FullyQualifiedFunctionName::fromFullyQualifiedString($function_name); } // If the function doesn't exist, check to see if its // a call to a builtin method if (!$this->code_base->hasMethod($function_fqsen)) { $function_fqsen = FullyQualifiedFunctionName::make('', $function_name); } if (!$this->code_base->hasMethod($function_fqsen)) { // Missing internal (bulitin) method. return new UnionType(); } $function = $this->code_base->getMethod($function_fqsen); // If this is an internal function, see if we can get // its types from the static dataset. if ($function->getContext()->isInternal() && $function->getUnionType()->isEmpty()) { $map = UnionType::internalFunctionSignatureMapForFQSEN($function_fqsen); return $map[$function_name] ?? new UnionType(); } return $function->getUnionType(); }