public function testFullyQualifiedMethodName() { $this->assertFQSENEqual(FullyQualifiedMethodName::make(FullyQualifiedClassName::make('\\Name\\Space', 'a'), 'f'), '\\Name\\Space\\a::f'); $this->assertFQSENEqual(FullyQualifiedMethodName::fromFullyQualifiedString('\\Name\\a::f'), '\\Name\\a::f'); $this->assertFQSENEqual(FullyQualifiedMethodName::fromFullyQualifiedString('Name\\a::f'), '\\Name\\a::f'); $this->assertFQSENEqual(FullyQualifiedMethodName::fromFullyQualifiedString('\\Name\\Space\\a::f,2'), '\\Name\\Space\\a::f,2'); $this->assertFQSENEqual(FullyQualifiedMethodName::fromFullyQualifiedString('\\Name\\Space\\a,1::f,2'), '\\Name\\Space\\a,1::f,2'); $this->assertFQSENEqual(FullyQualifiedMethodName::fromStringInContext('a::methodName', $this->context), '\\a::methodname'); }
/** * @return Method * A default constructor for the given class */ public static function defaultConstructorForClassInContext(Clazz $clazz, Context $context, CodeBase $code_base) : Method { $method_fqsen = FullyQualifiedMethodName::make($clazz->getFQSEN(), '__construct'); $method = new Method($context, '__construct', $clazz->getUnionType(), 0, $method_fqsen); if ($clazz->hasMethodWithName($code_base, $clazz->getName())) { $old_style_constructor = $clazz->getMethodByName($code_base, $clazz->getName()); $method->setParameterList($old_style_constructor->getParameterList()); $method->setNumberOfRequiredParameters($old_style_constructor->getNumberOfRequiredParameters()); $method->setNumberOfOptionalParameters($old_style_constructor->getNumberOfOptionalParameters()); } return $method; }
/** * @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 Node|string $method_name * Either then name of the method or a node that * produces the name of the method. * * @param bool $is_static * Set to true if this is a static method call * * @return Method * A method with the given name on the class referenced * from the given node * * @throws NodeException * An exception is thrown if we can't understand the node * * @throws CodeBaseExtension * An exception is thrown if we can't find the given * method * * @throws TypeException * An exception may be thrown if the only viable candidate * is a non-class type. * * @throws IssueException */ public function getMethod($method_name, bool $is_static) : Method { if ($method_name instanceof Node) { // The method_name turned out to be a variable. // There isn't much we can do to figure out what // it's referring to. throw new NodeException($method_name, "Unexpected method node"); } assert(is_string($method_name), "Method name must be a string. Found non-string at {$this->context}"); try { $class_list = (new ContextNode($this->code_base, $this->context, $this->node->children['expr'] ?? $this->node->children['class']))->getClassList(); } catch (CodeBaseException $exception) { throw new IssueException(Issue::fromType(Issue::UndeclaredClassMethod)($this->context->getFile(), $this->node->lineno ?? 0, [$method_name, (string) $exception->getFQSEN()])); } // If there were no classes on the left-type, figure // out what we were trying to call the method on // and send out an error. if (empty($class_list)) { $union_type = UnionTypeVisitor::unionTypeFromClassNode($this->code_base, $this->context, $this->node->children['expr'] ?? $this->node->children['class']); if (!$union_type->isEmpty() && $union_type->isNativeType() && !$union_type->hasAnyType([MixedType::instance(), ObjectType::instance(), StringType::instance()]) && !(Config::get()->null_casts_as_any_type && $union_type->hasType(NullType::instance()))) { throw new IssueException(Issue::fromType(Issue::NonClassMethodCall)($this->context->getFile(), $this->node->lineno ?? 0, [$method_name, (string) $union_type])); } throw new NodeException($this->node, "Can't figure out method call for {$method_name}"); } // Hunt to see if any of them have the method we're // looking for foreach ($class_list as $i => $class) { if ($class->hasMethodWithName($this->code_base, $method_name)) { return $class->getMethodByNameInContext($this->code_base, $method_name, $this->context); } else { if ($class->hasMethodWithName($this->code_base, '__call')) { return $class->getMethodByNameInContext($this->code_base, '__call', $this->context); } } } // Figure out an FQSEN for the method we couldn't find $method_fqsen = FullyQualifiedMethodName::make($class_list[0]->getFQSEN(), $method_name); if ($is_static) { throw new IssueException(Issue::fromType(Issue::UndeclaredStaticMethod)($this->context->getFile(), $this->node->lineno ?? 0, [(string) $method_fqsen])); } throw new IssueException(Issue::fromType(Issue::UndeclaredMethod)($this->context->getFile(), $this->node->lineno ?? 0, [(string) $method_fqsen])); }
/** * @return Method * A default constructor for the given class */ public static function defaultConstructorForClassInContext(Clazz $clazz, Context $context) : Method { $method = new Method($context, '__construct', $clazz->getUnionType(), 0); $method->setFQSEN(FullyQualifiedMethodName::make($clazz->getFQSEN(), '__construct')); return $method; }
/** * @return FullyQualifiedMethodName * A fully-qualified method name */ public function withMethodName(string $method_name, int $method_alternate_id = 0) : FullyQualifiedMethodName { return FullyQualifiedMethodName::make($this, $method_name, $method_alternate_id); }
/** * 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 { $class_name = AST::classNameFromNode($this->context, $this->code_base, $node); // assert(!empty($class_name), 'Class name cannot be empty'); 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(); }
/** * @param Node|string $method_name_or_node * Either then name of the method or a node that * produces the name of the method. * * @param bool $is_static * Set to true if this is a static method call * * @return Method * A method with the given name on the class referenced * from the given node * * @throws NodeException * An exception is thrown if we can't understand the node * * @throws CodeBaseExtension * An exception is thrown if we can't find the given * method * * @throws TypeException * An exception may be thrown if the only viable candidate * is a non-class type. */ public function getMethod($method_name_or_node, bool $is_static) : Method { $clazz = $this->getClass(); if ($method_name_or_node instanceof Node) { // TODO: The method_name turned out to // be a variable. We'd have to look // that up to figure out what the // string is, but thats a drag. throw new NodeException($method_name_or_node, "Unexpected method node"); } $method_name = $method_name_or_node; assert(is_string($method_name), "Method name must be a string. Found non-string at {$this->context}"); if (!$clazz->hasMethodWithName($this->code_base, $method_name)) { $method_fqsen = FullyQualifiedMethodName::make($clazz->getFQSEN(), $method_name); if ($is_static) { throw new CodeBaseException($method_fqsen, "static call to undeclared method {$method_fqsen}"); } else { throw new CodeBaseException($method_fqsen, "call to undeclared method {$method_fqsen}"); } } $method = $clazz->getMethodByNameInContext($this->code_base, $method_name, $this->context); return $method; }
/** * @param Node|string $method_name * Either then name of the method or a node that * produces the name of the method. * * @param bool $is_static * Set to true if this is a static method call * * @return Method * A method with the given name on the class referenced * from the given node * * @throws NodeException * An exception is thrown if we can't understand the node * * @throws CodeBaseExtension * An exception is thrown if we can't find the given * method * * @throws TypeException * An exception may be thrown if the only viable candidate * is a non-class type. */ public function getMethod($method_name, bool $is_static) : Method { if ($method_name instanceof Node) { // The method_name turned out to be a variable. // There isn't much we can do to figure out what // it's referring to. throw new NodeException($method_name, "Unexpected method node"); } assert(is_string($method_name), "Method name must be a string. Found non-string at {$this->context}"); try { $class_list = (new ContextNode($this->code_base, $this->context, $this->node->children['expr'] ?? $this->node->children['class']))->getClassList(); } catch (CodeBaseException $exception) { // We can give a more explicit message throw new CodeBaseException($exception->getFQSEN(), "Can't access method {$method_name} from undeclared class {$exception->getFQSEN()}"); } // If there were no classes on the left-type, figure // out what we were trying to call the method on // and send out an error. if (empty($class_list)) { $union_type = UnionTypeVisitor::unionTypeFromClassNode($this->code_base, $this->context, $this->node->children['expr'] ?? $this->node->children['class']); if (!$union_type->isEmpty() && $union_type->isNativeType() && !$union_type->hasType(MixedType::instance())) { throw new TypeException("Calling method on non-class type {$union_type}"); } throw new NodeException($this->node, "Can't figure out method call for {$method_name}"); } // Hunt to see if any of them have the method we're // looking for foreach ($class_list as $i => $class) { if ($class->hasMethodWithName($this->code_base, $method_name)) { return $class->getMethodByNameInContext($this->code_base, $method_name, $this->context); } } // Figure out an FQSEN for the method we couldn't find $method_fqsen = FullyQualifiedMethodName::make($class_list[0]->getFQSEN(), $method_name); if ($is_static) { throw new CodeBaseException($method_fqsen, "static call to undeclared method {$method_fqsen}"); } throw new CodeBaseException($method_fqsen, "call to undeclared method {$method_fqsen}"); }