import semmle.code.cpp.Declaration
private import semmle.code.cpp.internal.ResolveClass

/**
 * A C++ friend declaration [N4140 11.3].
 * For example:
 *
 *   class A {
 *     friend void f(int);
 *     friend class X;
 *   };
 *
 *   void f(int x) { ... }
 *   class X { ... };
 */
class FriendDecl extends Declaration, @frienddecl {
  /**
   * Gets the location of this friend declaration. The result is the
   * location of the friend declaration itself, not the class or function
   * that it refers to. Note: to get the target of the friend declaration,
   * use `getFriend`.
   */
  override Location getADeclarationLocation() { result = this.getLocation() }

  /**
   * Implements the abstract method `Declaration.getDefinitionLocation`. A
   * friend declaration cannot be a definition because it is only a link to
   * another class or function. But we have to provide an implementation of
   * this method, so we use the location of the declaration as the location
   * of the definition. Note: to get the target of the friend declaration,
   * use `getFriend`.
   */
  override Location getDefinitionLocation() { result = this.getLocation() }

  /** Gets the location of this friend declaration. */
  override Location getLocation() { frienddecls(underlyingElement(this),_,_,result) }

  /** Gets a descriptive string for this friend declaration. */
  override string getName() {
    result = this.getDeclaringClass().getName() + "'s friend"
  }

  /**
   * Friend declarations do not have specifiers. It makes no difference
   * whether they are declared in a public, protected or private section of
   * the class.
   */
  override Specifier getASpecifier() { none() }

  /**
   * Gets the target of this friend declaration.
   * For example: `X` in `class A { friend class X }`.
   */
  AccessHolder getFriend() { frienddecls(underlyingElement(this),_,unresolveElement(result),_) }

  /**
   * Gets the declaring class (also known as the befriending class).
   * For example: `A` in `class A { friend class X }`.
   */
  Class getDeclaringClass() { frienddecls(underlyingElement(this),unresolveElement(result),_,_) }

  /* Holds if this declaration is a top-level declaration. */
  override predicate isTopLevel() { none() }
}
