//== SemaOpenACCAtomic.cpp - Semantic Analysis for OpenACC Atomic Construct===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
/// \file
/// This file implements semantic analysis for the OpenACC atomic construct.
///
//===----------------------------------------------------------------------===//

#include "clang/AST/ExprCXX.h"
#include "clang/Basic/DiagnosticSema.h"
#include "clang/Sema/SemaOpenACC.h"

#include <optional>

using namespace clang;

namespace {

class AtomicOperandChecker {
  SemaOpenACC &SemaRef;
  OpenACCAtomicKind AtKind;
  SourceLocation AtomicDirLoc;
  StmtResult AssocStmt;

  // Do a diagnostic, which sets the correct error, then displays passed note.
  bool DiagnoseInvalidAtomic(SourceLocation Loc, PartialDiagnostic NoteDiag) {
    SemaRef.Diag(AtomicDirLoc, diag::err_acc_invalid_atomic)
        << (AtKind != OpenACCAtomicKind::None) << AtKind;
    SemaRef.Diag(Loc, NoteDiag);
    return true;
  }

  // Create a replacement recovery expr in case we find an error here.  This
  // allows us to ignore this during template instantiation so we only get a
  // single error.
  StmtResult getRecoveryExpr() {
    if (!AssocStmt.isUsable())
      return AssocStmt;

    if (!SemaRef.getASTContext().getLangOpts().RecoveryAST)
      return StmtError();

    Expr *E = dyn_cast<Expr>(AssocStmt.get());
    QualType T = E ? E->getType() : SemaRef.getASTContext().DependentTy;

    return RecoveryExpr::Create(SemaRef.getASTContext(), T,
                                AssocStmt.get()->getBeginLoc(),
                                AssocStmt.get()->getEndLoc(),
                                E ? ArrayRef<Expr *>{E} : ArrayRef<Expr *>{});
  }

  // OpenACC 3.3 2.12: 'expr' is an expression with scalar type.
  bool CheckOperandExpr(const Expr *E, PartialDiagnostic PD) {
    QualType ExprTy = E->getType();

    // Scalar allowed, plus we allow instantiation dependent to support
    // templates.
    if (ExprTy->isInstantiationDependentType() || ExprTy->isScalarType())
      return false;

    return DiagnoseInvalidAtomic(E->getExprLoc(),
                                 PD << diag::OACCLValScalar::Scalar << ExprTy);
  }

  // OpenACC 3.3 2.12: 'x' and 'v' (as applicable) are boht l-value expressoins
  // with scalar type.
  bool CheckOperandVariable(const Expr *E, PartialDiagnostic PD) {
    if (CheckOperandExpr(E, PD))
      return true;

    if (E->isLValue())
      return false;

    return DiagnoseInvalidAtomic(E->getExprLoc(),
                                 PD << diag::OACCLValScalar::LVal);
  }

  Expr *RequireExpr(Stmt *Stmt, PartialDiagnostic ExpectedNote) {
    if (Expr *E = dyn_cast<Expr>(Stmt))
      return E->IgnoreImpCasts();

    DiagnoseInvalidAtomic(Stmt->getBeginLoc(), ExpectedNote);
    return nullptr;
  }

  // A struct to hold the return the inner components of any operands, which
  // allows for compound checking.
  struct BinaryOpInfo {
    const Expr *FoundExpr = nullptr;
    const Expr *LHS = nullptr;
    const Expr *RHS = nullptr;
    BinaryOperatorKind Operator;
  };

  struct UnaryOpInfo {
    const Expr *FoundExpr = nullptr;
    const Expr *SubExpr = nullptr;
    UnaryOperatorKind Operator;

    bool IsIncrementOp() {
      return Operator == UO_PostInc || Operator == UO_PreInc;
    }
  };

  std::optional<UnaryOpInfo> GetUnaryOperatorInfo(const Expr *E) {
    // If this is a simple unary operator, just return its details.
    if (const auto *UO = dyn_cast<UnaryOperator>(E))
      return UnaryOpInfo{UO, UO->getSubExpr()->IgnoreImpCasts(),
                         UO->getOpcode()};

    // This might be an overloaded operator or a dependent context, so make sure
    // we can get as many details out of this as we can.
    if (const auto *OpCall = dyn_cast<CXXOperatorCallExpr>(E)) {
      UnaryOpInfo Inf;
      Inf.FoundExpr = OpCall;

      switch (OpCall->getOperator()) {
      default:
        return std::nullopt;
      case OO_PlusPlus:
        Inf.Operator = OpCall->getNumArgs() == 1 ? UO_PreInc : UO_PostInc;
        break;
      case OO_MinusMinus:
        Inf.Operator = OpCall->getNumArgs() == 1 ? UO_PreDec : UO_PostDec;
        break;
      case OO_Amp:
        Inf.Operator = UO_AddrOf;
        break;
      case OO_Star:
        Inf.Operator = UO_Deref;
        break;
      case OO_Plus:
        Inf.Operator = UO_Plus;
        break;
      case OO_Minus:
        Inf.Operator = UO_Minus;
        break;
      case OO_Tilde:
        Inf.Operator = UO_Not;
        break;
      case OO_Exclaim:
        Inf.Operator = UO_LNot;
        break;
      case OO_Coawait:
        Inf.Operator = UO_Coawait;
        break;
      }

      // Some of the above can be both binary and unary operations, so make sure
      // we get the right one.
      if (Inf.Operator != UO_PostInc && Inf.Operator != UO_PostDec &&
          OpCall->getNumArgs() != 1)
        return std::nullopt;

      Inf.SubExpr = OpCall->getArg(0);
      return Inf;
    }
    return std::nullopt;
  }

  // Get a normalized version of a binary operator.
  std::optional<BinaryOpInfo> GetBinaryOperatorInfo(const Expr *E) {
    if (const auto *BO = dyn_cast<BinaryOperator>(E))
      return BinaryOpInfo{BO, BO->getLHS()->IgnoreImpCasts(),
                          BO->getRHS()->IgnoreImpCasts(), BO->getOpcode()};

    // In case this is an operator-call, which allows us to support overloaded
    // operators and dependent expression.
    if (const auto *OpCall = dyn_cast<CXXOperatorCallExpr>(E)) {
      BinaryOpInfo Inf;
      Inf.FoundExpr = OpCall;

      switch (OpCall->getOperator()) {
      default:
        return std::nullopt;
      case OO_Plus:
        Inf.Operator = BO_Add;
        break;
      case OO_Minus:
        Inf.Operator = BO_Sub;
        break;
      case OO_Star:
        Inf.Operator = BO_Mul;
        break;
      case OO_Slash:
        Inf.Operator = BO_Div;
        break;
      case OO_Percent:
        Inf.Operator = BO_Rem;
        break;
      case OO_Caret:
        Inf.Operator = BO_Xor;
        break;
      case OO_Amp:
        Inf.Operator = BO_And;
        break;
      case OO_Pipe:
        Inf.Operator = BO_Or;
        break;
      case OO_Equal:
        Inf.Operator = BO_Assign;
        break;
      case OO_Spaceship:
        Inf.Operator = BO_Cmp;
        break;
      case OO_Less:
        Inf.Operator = BO_LT;
        break;
      case OO_Greater:
        Inf.Operator = BO_GT;
        break;
      case OO_PlusEqual:
        Inf.Operator = BO_AddAssign;
        break;
      case OO_MinusEqual:
        Inf.Operator = BO_SubAssign;
        break;
      case OO_StarEqual:
        Inf.Operator = BO_MulAssign;
        break;
      case OO_SlashEqual:
        Inf.Operator = BO_DivAssign;
        break;
      case OO_PercentEqual:
        Inf.Operator = BO_RemAssign;
        break;
      case OO_CaretEqual:
        Inf.Operator = BO_XorAssign;
        break;
      case OO_AmpEqual:
        Inf.Operator = BO_AndAssign;
        break;
      case OO_PipeEqual:
        Inf.Operator = BO_OrAssign;
        break;
      case OO_LessLess:
        Inf.Operator = BO_Shl;
        break;
      case OO_GreaterGreater:
        Inf.Operator = BO_Shr;
        break;
      case OO_LessLessEqual:
        Inf.Operator = BO_ShlAssign;
        break;
      case OO_GreaterGreaterEqual:
        Inf.Operator = BO_ShrAssign;
        break;
      case OO_EqualEqual:
        Inf.Operator = BO_EQ;
        break;
      case OO_ExclaimEqual:
        Inf.Operator = BO_NE;
        break;
      case OO_LessEqual:
        Inf.Operator = BO_LE;
        break;
      case OO_GreaterEqual:
        Inf.Operator = BO_GE;
        break;
      case OO_AmpAmp:
        Inf.Operator = BO_LAnd;
        break;
      case OO_PipePipe:
        Inf.Operator = BO_LOr;
        break;
      case OO_Comma:
        Inf.Operator = BO_Comma;
        break;
      case OO_ArrowStar:
        Inf.Operator = BO_PtrMemI;
        break;
      }

      // This isn't a binary operator unless there are two arguments.
      if (OpCall->getNumArgs() != 2)
        return std::nullopt;

      // Callee is the call-operator, so we only need to extract the two
      // arguments here.
      Inf.LHS = OpCall->getArg(0)->IgnoreImpCasts();
      Inf.RHS = OpCall->getArg(1)->IgnoreImpCasts();
      return Inf;
    }

    return std::nullopt;
  }

  // Checks a required assignment operation, but don't check the LHS or RHS,
  // callers have to do that here.
  std::optional<BinaryOpInfo> CheckAssignment(const Expr *E) {
    std::optional<BinaryOpInfo> Inf = GetBinaryOperatorInfo(E);

    if (!Inf) {
      DiagnoseInvalidAtomic(E->getExprLoc(),
                            SemaRef.PDiag(diag::note_acc_atomic_expr_must_be)
                                << diag::OACCAtomicExpr::Assign);
      return std::nullopt;
    }

    if (Inf->Operator != BO_Assign) {
      DiagnoseInvalidAtomic(Inf->FoundExpr->getExprLoc(),
                            SemaRef.PDiag(diag::note_acc_atomic_expr_must_be)
                                << diag::OACCAtomicExpr::Assign);
      return std::nullopt;
    }

    // Assignment always requires an lvalue/scalar on the LHS.
    if (CheckOperandVariable(
            Inf->LHS, SemaRef.PDiag(diag::note_acc_atomic_operand_lvalue_scalar)
                          << /*left=*/0 << diag::OACCAtomicOpKind::Assign))
      return std::nullopt;

    return Inf;
  }

  struct IDACInfo {
    bool Failed = false;
    enum ExprKindTy {
      Invalid,
      // increment/decrement ops.
      Unary,
      // v = x
      SimpleAssign,
      // x = expr
      ExprAssign,
      // x binop= expr
      CompoundAssign,
      // x = x binop expr
      // x = expr binop x
      AssignBinOp
    } ExprKind;

    // The variable referred to as 'x' in all of the grammar, such that it is
    // needed in compound statement checking of capture to check between the two
    // expressions.
    const Expr *X_Var = nullptr;

    static IDACInfo Fail() { return IDACInfo{true, Invalid, nullptr}; };
  };

  // Helper for CheckIncDecAssignCompoundAssign, does checks for inc/dec.
  IDACInfo CheckIncDec(UnaryOpInfo Inf) {

    if (!UnaryOperator::isIncrementDecrementOp(Inf.Operator)) {
      DiagnoseInvalidAtomic(
          Inf.FoundExpr->getExprLoc(),
          SemaRef.PDiag(diag::note_acc_atomic_unsupported_unary_operator));
      return IDACInfo::Fail();
    }
    bool Failed = CheckOperandVariable(
        Inf.SubExpr,
        SemaRef.PDiag(diag::note_acc_atomic_operand_lvalue_scalar)
            << /*none=*/2
            << (Inf.IsIncrementOp() ? diag::OACCAtomicOpKind::Inc
                                    : diag::OACCAtomicOpKind::Dec));
    // For increment/decrements, the subexpr is the 'x' (x++, ++x, etc).
    return IDACInfo{Failed, IDACInfo::Unary, Inf.SubExpr};
  }

  enum class SimpleAssignKind { None, Var, Expr };

  // Check an assignment, and ensure the RHS is either x binop expr or expr
  // binop x.
  // If AllowSimpleAssign, also allows v = x;
  IDACInfo CheckAssignmentWithBinOpOnRHS(BinaryOpInfo AssignInf,
                                         SimpleAssignKind SAK) {
    PartialDiagnostic PD =
        SemaRef.PDiag(diag::note_acc_atomic_operand_lvalue_scalar)
        << /*left=*/0 << diag::OACCAtomicOpKind::Assign;
    if (CheckOperandVariable(AssignInf.LHS, PD))
      return IDACInfo::Fail();

    std::optional<BinaryOpInfo> BinInf = GetBinaryOperatorInfo(AssignInf.RHS);

    if (!BinInf) {

      // Capture in a compound statement allows v = x assignment.  So make sure
      // we permit that here.
      if (SAK != SimpleAssignKind::None) {
        PartialDiagnostic PD =
            SemaRef.PDiag(diag::note_acc_atomic_operand_lvalue_scalar)
            << /*right=*/1 << diag::OACCAtomicOpKind::Assign;
        if (SAK == SimpleAssignKind::Var) {
          // In the var version, everywhere we allow v = x;, X is the RHS.
          return IDACInfo{CheckOperandVariable(AssignInf.RHS, PD),
                          IDACInfo::SimpleAssign, AssignInf.RHS};
        }
        assert(SAK == SimpleAssignKind::Expr);
        // In the expression version, supported by v=x; x = expr;, we need to
        // set to the LHS here.
        return IDACInfo{CheckOperandExpr(AssignInf.RHS, PD),
                        IDACInfo::ExprAssign, AssignInf.LHS};
      }

      DiagnoseInvalidAtomic(
          AssignInf.RHS->getExprLoc(),
          SemaRef.PDiag(diag::note_acc_atomic_expected_binop));

      return IDACInfo::Fail();
    }
    switch (BinInf->Operator) {
    default:
      DiagnoseInvalidAtomic(
          BinInf->FoundExpr->getExprLoc(),
          SemaRef.PDiag(diag::note_acc_atomic_unsupported_binary_operator));
      return IDACInfo::Fail();
      // binop is one of +, *, -, /, &, ^, |, <<, or >>
    case BO_Add:
    case BO_Mul:
    case BO_Sub:
    case BO_Div:
    case BO_And:
    case BO_Xor:
    case BO_Or:
    case BO_Shl:
    case BO_Shr:
      // Handle these outside of the switch.
      break;
    }

    llvm::FoldingSetNodeID LHS_ID, InnerLHS_ID, InnerRHS_ID;
    AssignInf.LHS->Profile(LHS_ID, SemaRef.getASTContext(),
                           /*Canonical=*/true);
    BinInf->LHS->Profile(InnerLHS_ID, SemaRef.getASTContext(),
                         /*Canonical=*/true);

    // This is X = X binop expr;
    // Check the RHS is an expression.
    if (LHS_ID == InnerLHS_ID)
      return IDACInfo{
          CheckOperandExpr(
              BinInf->RHS,
              SemaRef.PDiag(diag::note_acc_atomic_operand_lvalue_scalar
                            << /*right=*/1
                            << diag::OACCAtomicOpKind::CompoundAssign)),
          IDACInfo::AssignBinOp, AssignInf.LHS};

    BinInf->RHS->Profile(InnerRHS_ID, SemaRef.getASTContext(),
                         /*Canonical=*/true);
    // This is X = expr binop X;
    // Check the LHS is an expression
    if (LHS_ID == InnerRHS_ID)
      return IDACInfo{
          CheckOperandExpr(
              BinInf->LHS,
              SemaRef.PDiag(diag::note_acc_atomic_operand_lvalue_scalar)
                  << /*left=*/0 << diag::OACCAtomicOpKind::CompoundAssign),
          IDACInfo::AssignBinOp, AssignInf.LHS};

    // If nothing matches, error out.
    DiagnoseInvalidAtomic(BinInf->FoundExpr->getExprLoc(),
                          SemaRef.PDiag(diag::note_acc_atomic_mismatch_operand)
                              << AssignInf.LHS << BinInf->LHS << BinInf->RHS);
    return IDACInfo::Fail();
  }

  // Ensures that the expression is an increment/decrement, an assignment, or a
  // compound assignment. If its an assignment, allows the x binop expr/x binop
  // expr syntax. If it is a compound-assignment, allows any expr on the RHS.
  IDACInfo CheckIncDecAssignCompoundAssign(const Expr *E,
                                           SimpleAssignKind SAK) {
    std::optional<UnaryOpInfo> UInf = GetUnaryOperatorInfo(E);

    // If this is a unary operator, only increment/decrement are allowed, so get
    // unary operator, then check everything we can.
    if (UInf)
      return CheckIncDec(*UInf);

    std::optional<BinaryOpInfo> BinInf = GetBinaryOperatorInfo(E);

    // Unary or binary operator were the only choices, so error here.
    if (!BinInf) {
      DiagnoseInvalidAtomic(E->getExprLoc(),
                            SemaRef.PDiag(diag::note_acc_atomic_expr_must_be)
                                << diag::OACCAtomicExpr::UnaryCompAssign);
      return IDACInfo::Fail();
    }

    switch (BinInf->Operator) {
    default:
      DiagnoseInvalidAtomic(
          BinInf->FoundExpr->getExprLoc(),
          SemaRef.PDiag(
              diag::note_acc_atomic_unsupported_compound_binary_operator));
      return IDACInfo::Fail();
    case BO_Assign:
      return CheckAssignmentWithBinOpOnRHS(*BinInf, SAK);
    case BO_AddAssign:
    case BO_MulAssign:
    case BO_SubAssign:
    case BO_DivAssign:
    case BO_AndAssign:
    case BO_XorAssign:
    case BO_OrAssign:
    case BO_ShlAssign:
    case BO_ShrAssign: {
      PartialDiagnostic LPD =
          SemaRef.PDiag(diag::note_acc_atomic_operand_lvalue_scalar)
          << /*left=*/0 << diag::OACCAtomicOpKind::CompoundAssign;
      PartialDiagnostic RPD =
          SemaRef.PDiag(diag::note_acc_atomic_operand_lvalue_scalar)
          << /*right=*/1 << diag::OACCAtomicOpKind::CompoundAssign;
      // nothing to do other than check the variable expressions.
      // success or failure
      bool Failed = CheckOperandVariable(BinInf->LHS, LPD) ||
                    CheckOperandExpr(BinInf->RHS, RPD);

      return IDACInfo{Failed, IDACInfo::CompoundAssign, BinInf->LHS};
    }
    }
    llvm_unreachable("all binary operator kinds should be checked above");
  }

  StmtResult CheckRead() {
    Expr *AssocExpr = RequireExpr(
        AssocStmt.get(), SemaRef.PDiag(diag::note_acc_atomic_expr_must_be)
                             << diag::OACCAtomicExpr::Assign);

    if (!AssocExpr)
      return getRecoveryExpr();

    std::optional<BinaryOpInfo> AssignRes = CheckAssignment(AssocExpr);
    if (!AssignRes)
      return getRecoveryExpr();

    PartialDiagnostic PD =
        SemaRef.PDiag(diag::note_acc_atomic_operand_lvalue_scalar)
        << /*right=*/1 << diag::OACCAtomicOpKind::Assign;

    // Finally, check the RHS.
    if (CheckOperandVariable(AssignRes->RHS, PD))
      return getRecoveryExpr();

    return AssocStmt;
  }

  StmtResult CheckWrite() {
    Expr *AssocExpr = RequireExpr(
        AssocStmt.get(), SemaRef.PDiag(diag::note_acc_atomic_expr_must_be)
                             << diag::OACCAtomicExpr::Assign);

    if (!AssocExpr)
      return getRecoveryExpr();

    std::optional<BinaryOpInfo> AssignRes = CheckAssignment(AssocExpr);
    if (!AssignRes)
      return getRecoveryExpr();

    PartialDiagnostic PD =
        SemaRef.PDiag(diag::note_acc_atomic_operand_lvalue_scalar)
        << /*right=*/1 << diag::OACCAtomicOpKind::Assign;

    // Finally, check the RHS.
    if (CheckOperandExpr(AssignRes->RHS, PD))
      return getRecoveryExpr();

    return AssocStmt;
  }

  StmtResult CheckUpdate() {
    Expr *AssocExpr = RequireExpr(
        AssocStmt.get(), SemaRef.PDiag(diag::note_acc_atomic_expr_must_be)
                             << diag::OACCAtomicExpr::UnaryCompAssign);

    if (!AssocExpr ||
        CheckIncDecAssignCompoundAssign(AssocExpr, SimpleAssignKind::None)
            .Failed)
      return getRecoveryExpr();

    return AssocStmt;
  }

  const Expr *IgnoreBeforeCompare(const Expr *E) {
    return E->IgnoreParenImpCasts()->IgnoreParenNoopCasts(
        SemaRef.getASTContext());
  }

  bool CheckVarRefsSame(IDACInfo::ExprKindTy FirstKind, const Expr *FirstX,
                        IDACInfo::ExprKindTy SecondKind, const Expr *SecondX) {
    llvm::FoldingSetNodeID First_ID, Second_ID;
    FirstX->Profile(First_ID, SemaRef.getASTContext(), /*Canonical=*/true);
    SecondX->Profile(Second_ID, SemaRef.getASTContext(), /*Canonical=*/true);

    if (First_ID == Second_ID)
      return false;

    PartialDiagnostic PD =
        SemaRef.PDiag(diag::note_acc_atomic_mismatch_compound_operand)
        << FirstKind << FirstX << SecondKind << SecondX;

    return DiagnoseInvalidAtomic(SecondX->getExprLoc(), PD);
  }

  StmtResult CheckCapture() {
    if (const auto *CmpdStmt = dyn_cast<CompoundStmt>(AssocStmt.get())) {
      auto *const *BodyItr = CmpdStmt->body().begin();
      PartialDiagnostic PD = SemaRef.PDiag(diag::note_acc_atomic_expr_must_be)
                             << diag::OACCAtomicExpr::UnaryCompAssign;
      // If we don't have at least 1 statement, error.
      if (BodyItr == CmpdStmt->body().end()) {
        DiagnoseInvalidAtomic(CmpdStmt->getBeginLoc(), PD);
        return getRecoveryExpr();
      }

      // First Expr can be inc/dec, assign, or compound assign.
      Expr *FirstExpr = RequireExpr(*BodyItr, PD);
      if (!FirstExpr)
        return getRecoveryExpr();

      IDACInfo FirstExprResults =
          CheckIncDecAssignCompoundAssign(FirstExpr, SimpleAssignKind::Var);
      if (FirstExprResults.Failed)
        return getRecoveryExpr();

      ++BodyItr;

      // If we don't have second statement, error.
      if (BodyItr == CmpdStmt->body().end()) {
        DiagnoseInvalidAtomic(CmpdStmt->getEndLoc(), PD);
        return getRecoveryExpr();
      }

      Expr *SecondExpr = RequireExpr(*BodyItr, PD);
      if (!SecondExpr)
        return getRecoveryExpr();

      assert(FirstExprResults.ExprKind != IDACInfo::Invalid);

      switch (FirstExprResults.ExprKind) {
      case IDACInfo::Invalid:
      case IDACInfo::ExprAssign:
        llvm_unreachable("Should have error'ed out by now");
      case IDACInfo::Unary:
      case IDACInfo::CompoundAssign:
      case IDACInfo::AssignBinOp: {
        // Everything but simple-assign can only be followed by a simple
        // assignment.
        std::optional<BinaryOpInfo> AssignRes = CheckAssignment(SecondExpr);
        if (!AssignRes)
          return getRecoveryExpr();

        PartialDiagnostic PD =
            SemaRef.PDiag(diag::note_acc_atomic_operand_lvalue_scalar)
            << /*right=*/1 << diag::OACCAtomicOpKind::Assign;

        if (CheckOperandVariable(AssignRes->RHS, PD))
          return getRecoveryExpr();

        if (CheckVarRefsSame(FirstExprResults.ExprKind,
                             IgnoreBeforeCompare(FirstExprResults.X_Var),
                             IDACInfo::SimpleAssign,
                             IgnoreBeforeCompare(AssignRes->RHS)))
          return getRecoveryExpr();
        break;
      }
      case IDACInfo::SimpleAssign: {
        // If the first was v = x, anything but simple expression is allowed.
        IDACInfo SecondExprResults =
            CheckIncDecAssignCompoundAssign(SecondExpr, SimpleAssignKind::Expr);
        if (SecondExprResults.Failed)
          return getRecoveryExpr();

        if (CheckVarRefsSame(FirstExprResults.ExprKind,
                             IgnoreBeforeCompare(FirstExprResults.X_Var),
                             SecondExprResults.ExprKind,
                             IgnoreBeforeCompare(SecondExprResults.X_Var)))
          return getRecoveryExpr();
        break;
      }
      }
      ++BodyItr;
      if (BodyItr != CmpdStmt->body().end()) {
        DiagnoseInvalidAtomic(
            (*BodyItr)->getBeginLoc(),
            SemaRef.PDiag(diag::note_acc_atomic_too_many_stmts));
        return getRecoveryExpr();
      }
    } else {
      // This check doesn't need to happen if it is a compound stmt.
      Expr *AssocExpr = RequireExpr(
          AssocStmt.get(), SemaRef.PDiag(diag::note_acc_atomic_expr_must_be)
                               << diag::OACCAtomicExpr::Assign);
      if (!AssocExpr)
        return getRecoveryExpr();

      // First, we require an assignment.
      std::optional<BinaryOpInfo> AssignRes = CheckAssignment(AssocExpr);

      if (!AssignRes)
        return getRecoveryExpr();

      if (CheckIncDecAssignCompoundAssign(AssignRes->RHS,
                                          SimpleAssignKind::None)
              .Failed)
        return getRecoveryExpr();
    }

    return AssocStmt;
  }

public:
  AtomicOperandChecker(SemaOpenACC &S, OpenACCAtomicKind AtKind,
                       SourceLocation DirLoc, StmtResult AssocStmt)
      : SemaRef(S), AtKind(AtKind), AtomicDirLoc(DirLoc), AssocStmt(AssocStmt) {
  }

  StmtResult Check() {

    switch (AtKind) {
    case OpenACCAtomicKind::Read:
      return CheckRead();
    case OpenACCAtomicKind::Write:
      return CheckWrite();
    case OpenACCAtomicKind::None:
    case OpenACCAtomicKind::Update:
      return CheckUpdate();
    case OpenACCAtomicKind::Capture:
      return CheckCapture();
    }
    llvm_unreachable("Unhandled atomic kind?");
  }
};
} // namespace

StmtResult SemaOpenACC::CheckAtomicAssociatedStmt(SourceLocation AtomicDirLoc,
                                                  OpenACCAtomicKind AtKind,
                                                  StmtResult AssocStmt) {
  if (!AssocStmt.isUsable())
    return AssocStmt;

  if (isa<RecoveryExpr>(AssocStmt.get()))
    return AssocStmt;

  AtomicOperandChecker Checker{*this, AtKind, AtomicDirLoc, AssocStmt};
  return Checker.Check();
}
