import cpp

/**
 * Gets the enclosing element of statement `s`.
 */
cached
Element stmtEnclosingElement(Stmt s) {
  result.(Function).getEntryPoint() = s or
  result = stmtEnclosingElement(s.getParent()) or
  result = exprEnclosingElement(s.getParent())
}

/**
 * Gets the enclosing element of expression `e`.
 */
// The `pragma[nomagic]` is a workaround to prevent this cached stage (and all
// subsequent stages) from being evaluated twice. See QL-888. It has the effect
// of making the `Conversion` class predicate get the same optimization in all
// queries.
pragma[nomagic]
cached
Element exprEnclosingElement(Expr e) {
  result = exprEnclosingElement(e.getParent())
  or
  result = stmtEnclosingElement(e.getParent())
  or
  result.(Function) = e.getParent()
  or
  result = exprEnclosingElement(e.(Conversion).getExpr())
  or
  exists(Initializer i |
    i.getExpr() = e and
    if exists(i.getEnclosingStmt())
    then result = stmtEnclosingElement(i.getEnclosingStmt())
    else
      if i.getDeclaration() instanceof Parameter
      then result = i.getDeclaration().(Parameter).getFunction()
      else result = i.getDeclaration()
  )
  or
  exists(Expr anc |
    expr_ancestor(unresolveElement(e), unresolveElement(anc)) and result = exprEnclosingElement(anc)
  )
  or
  exists(Stmt anc |
    expr_ancestor(unresolveElement(e), unresolveElement(anc)) and result = stmtEnclosingElement(anc)
  )
  or
  exists(DeclarationEntry de |
    expr_ancestor(unresolveElement(e), unresolveElement(de)) and
    if exists(DeclStmt ds | de = ds.getADeclarationEntry())
    then
      exists(DeclStmt ds |
        de = ds.getADeclarationEntry() and
        result = stmtEnclosingElement(ds)
      )
    else result = de.getDeclaration()
  )
}
