[OpenACC]Reimplement 'for' loop checks for a loop construct

The 'loop' construct has some pretty strict checks as to what the for
loop associated with it has to look like. The previous implementation
was a little convoluted and missed implementing the 'condition'
checking.  Additionally, it did a bad job double-diagnosing with
templates.

This patch rewrites it in a way that does a much better job with the
double-diagnosing, and proeprly implements some checks for the
'condition' of the for loop.
diff --git a/clang/include/clang/Sema/SemaOpenACC.h b/clang/include/clang/Sema/SemaOpenACC.h
index 2c9134b..47947d9 100644
--- a/clang/include/clang/Sema/SemaOpenACC.h
+++ b/clang/include/clang/Sema/SemaOpenACC.h
@@ -125,38 +125,73 @@
   /// 'loop' clause enforcement, where this is 'blocked' by a compute construct.
   llvm::SmallVector<OpenACCReductionClause *> ActiveReductionClauses;
 
-  // Type to check the info about the 'for stmt'.
-  struct ForStmtBeginChecker {
+  // Type to check the 'for' (or range-for) statement for compatibility with the
+  // 'loop' directive.
+  class ForStmtBeginChecker {
     SemaOpenACC &SemaRef;
     SourceLocation ForLoc;
-    bool IsRangeFor = false;
-    std::optional<const CXXForRangeStmt *> RangeFor = nullptr;
-    const Stmt *Init = nullptr;
-    bool InitChanged = false;
-    std::optional<const Stmt *> Cond = nullptr;
-    std::optional<const Stmt *> Inc = nullptr;
+    bool IsInstantiation = false;
+
+    struct RangeForInfo {
+      const CXXForRangeStmt *Uninstantiated = nullptr;
+      const CXXForRangeStmt *CurrentVersion = nullptr;
+    };
+
+    struct ForInfo {
+      const Stmt *Init = nullptr;
+      const Stmt *Condition = nullptr;
+      const Stmt *Increment = nullptr;
+    };
+
+    struct CheckForInfo {
+      ForInfo Uninst;
+      ForInfo Current;
+    };
+
+    std::variant<RangeForInfo, CheckForInfo> Info;
     // Prevent us from checking 2x, which can happen with collapse & tile.
     bool AlreadyChecked = false;
 
-    ForStmtBeginChecker(SemaOpenACC &SemaRef, SourceLocation ForLoc,
-                        std::optional<const CXXForRangeStmt *> S)
-        : SemaRef(SemaRef), ForLoc(ForLoc), IsRangeFor(true), RangeFor(S) {}
+    void checkRangeFor();
 
+    bool checkForInit(const Stmt *InitStmt, const ValueDecl *&InitVar,
+                      bool Diag);
+    bool checkForCond(const Stmt *CondStmt, const ValueDecl *InitVar,
+                      bool Diag);
+    bool checkForInc(const Stmt *IncStmt, const ValueDecl *InitVar, bool Diag);
+
+    void checkFor();
+
+    //  void checkRangeFor(); ?? ERICH
+    //  const ValueDecl *checkInit();
+    //  void checkCond(const ValueDecl *Init);
+    //  void checkInc(const ValueDecl *Init);
+  public:
+    // Checking for non-instantiation version of a Range-for.
     ForStmtBeginChecker(SemaOpenACC &SemaRef, SourceLocation ForLoc,
-                        const Stmt *I, bool InitChanged,
-                        std::optional<const Stmt *> C,
-                        std::optional<const Stmt *> Inc)
-        : SemaRef(SemaRef), ForLoc(ForLoc), IsRangeFor(false), Init(I),
-          InitChanged(InitChanged), Cond(C), Inc(Inc) {}
-    // Do the checking for the For/Range-For. Currently this implements the 'not
-    // seq' restrictions only, and should be called either if we know we are a
-    // top-level 'for' (the one associated via associated-stmt), or extended via
-    // 'collapse'.
+                        const CXXForRangeStmt *RangeFor)
+        : SemaRef(SemaRef), ForLoc(ForLoc), IsInstantiation(false),
+          Info(RangeForInfo{nullptr, RangeFor}) {}
+    // Checking for an instantiation of the range-for.
+    ForStmtBeginChecker(SemaOpenACC &SemaRef, SourceLocation ForLoc,
+                        const CXXForRangeStmt *OldRangeFor,
+                        const CXXForRangeStmt *RangeFor)
+        : SemaRef(SemaRef), ForLoc(ForLoc), IsInstantiation(true),
+          Info(RangeForInfo{OldRangeFor, RangeFor}) {}
+    // Checking for a non-instantiation version of a traditional for.
+    ForStmtBeginChecker(SemaOpenACC &SemaRef, SourceLocation ForLoc,
+                        const Stmt *Init, const Stmt *Cond, const Stmt *Inc)
+        : SemaRef(SemaRef), ForLoc(ForLoc), IsInstantiation(false),
+          Info(CheckForInfo{{}, {Init, Cond, Inc}}) {}
+    // Checking for an instantiation version of a traditional for.
+    ForStmtBeginChecker(SemaOpenACC &SemaRef, SourceLocation ForLoc,
+                        const Stmt *OldInit, const Stmt *OldCond,
+                        const Stmt *OldInc, const Stmt *Init, const Stmt *Cond,
+                        const Stmt *Inc)
+        : SemaRef(SemaRef), ForLoc(ForLoc), IsInstantiation(true),
+          Info(CheckForInfo{{OldInit, OldCond, OldInc}, {Init, Cond, Inc}}) {}
+
     void check();
-
-    const ValueDecl *checkInit();
-    void checkCond();
-    void checkInc(const ValueDecl *Init);
   };
 
   /// Helper function for checking the 'for' and 'range for' stmts.
diff --git a/clang/lib/Sema/SemaOpenACC.cpp b/clang/lib/Sema/SemaOpenACC.cpp
index 74eee9b..fc191bd 100644
--- a/clang/lib/Sema/SemaOpenACC.cpp
+++ b/clang/lib/Sema/SemaOpenACC.cpp
@@ -944,6 +944,7 @@
   LoopInfo.TopLevelLoopSeen = true;
 
   if (CollapseInfo.CurCollapseCount && *CollapseInfo.CurCollapseCount > 0) {
+    // Check the format of this loop if it is affected by the collapse.
     C.check();
 
     // OpenACC 3.3 2.9.1:
@@ -969,6 +970,7 @@
   }
 
   if (TileInfo.CurTileCount && *TileInfo.CurTileCount > 0) {
+    // Check the format of this loop if it is affected by the tile.
     C.check();
 
     if (LoopInfo.CurLevelHasLoopAlready) {
@@ -1044,13 +1046,11 @@
       if (IsRandomAccessIteratorTag(ItrCategoryDecl))
         return true;
 
-      // We can also support types inherited from the
+      // We can also support tag-types inherited from the
       // random_access_iterator_tag.
-      for (CXXBaseSpecifier BS : ItrCategoryDecl->bases()) {
-
+      for (CXXBaseSpecifier BS : ItrCategoryDecl->bases())
         if (IsRandomAccessIteratorTag(BS.getType()->getAsCXXRecordDecl()))
           return true;
-      }
 
       return false;
     }
@@ -1058,13 +1058,404 @@
 
   return false;
 }
+const ValueDecl *getDeclFromExpr(const Expr *E) {
+  E = E->IgnoreParenImpCasts();
+  if (const auto *FE = dyn_cast<FullExpr>(E))
+    E = FE->getSubExpr();
 
+  E = E->IgnoreParenImpCasts();
+
+  if (!E)
+    return nullptr;
+  if (const auto *DRE = dyn_cast<DeclRefExpr>(E))
+    return dyn_cast<ValueDecl>(DRE->getDecl());
+
+  if (const auto *ME = dyn_cast<MemberExpr>(E))
+    if (isa<CXXThisExpr>(ME->getBase()->IgnoreParenImpCasts()))
+      return ME->getMemberDecl();
+
+  return nullptr;
+}
 } // namespace
 
+void SemaOpenACC::ForStmtBeginChecker::checkRangeFor() {
+  const RangeForInfo &RFI = std::get<RangeForInfo>(Info);
+  // If this hasn't changed since last instantiated we're done.
+  if (RFI.Uninstantiated == RFI.CurrentVersion)
+    return;
+
+  const DeclStmt *UninstRangeStmt =
+      IsInstantiation ? RFI.Uninstantiated->getBeginStmt() : nullptr;
+  const DeclStmt *RangeStmt = RFI.CurrentVersion->getBeginStmt();
+
+  // If this isn't the first time we've checked this loop, suppress any cases
+  // where we previously diagnosed.
+  if (UninstRangeStmt) {
+    const ValueDecl *InitVar =
+        cast<ValueDecl>(UninstRangeStmt->getSingleDecl());
+    QualType VarType = InitVar->getType().getNonReferenceType();
+
+    if (!isValidLoopVariableType(VarType))
+      return;
+  }
+
+  // In some dependent contexts, the autogenerated range statement doesn't get
+  // included until instantiation, so skip for now.
+  if (RangeStmt) {
+    const ValueDecl *InitVar = cast<ValueDecl>(RangeStmt->getSingleDecl());
+    QualType VarType = InitVar->getType().getNonReferenceType();
+
+    if (!isValidLoopVariableType(VarType)) {
+      SemaRef.Diag(InitVar->getBeginLoc(), diag::err_acc_loop_variable_type)
+          << SemaRef.LoopWithoutSeqInfo.Kind << VarType;
+      SemaRef.Diag(SemaRef.LoopWithoutSeqInfo.Loc,
+                   diag::note_acc_construct_here)
+          << SemaRef.LoopWithoutSeqInfo.Kind;
+      return;
+    }
+  }
+}
+bool SemaOpenACC::ForStmtBeginChecker::checkForInit(const Stmt *InitStmt,
+                                                    const ValueDecl *&InitVar,
+                                                    bool Diag) {
+  // Init statement is required.
+  if (!InitStmt) {
+    if (Diag) {
+      SemaRef.Diag(ForLoc, diag::err_acc_loop_variable)
+          << SemaRef.LoopWithoutSeqInfo.Kind;
+      SemaRef.Diag(SemaRef.LoopWithoutSeqInfo.Loc,
+                   diag::note_acc_construct_here)
+          << SemaRef.LoopWithoutSeqInfo.Kind;
+    }
+    return true;
+  }
+  auto DiagLoopVar = [this, Diag, InitStmt]() {
+    if (Diag) {
+      SemaRef.Diag(InitStmt->getBeginLoc(), diag::err_acc_loop_variable)
+          << SemaRef.LoopWithoutSeqInfo.Kind;
+      SemaRef.Diag(SemaRef.LoopWithoutSeqInfo.Loc,
+                   diag::note_acc_construct_here)
+          << SemaRef.LoopWithoutSeqInfo.Kind;
+    }
+    return true;
+  };
+
+  if (const auto *ExprTemp = dyn_cast<ExprWithCleanups>(InitStmt))
+    InitStmt = ExprTemp->getSubExpr();
+  if (const auto *E = dyn_cast<Expr>(InitStmt))
+    InitStmt = E->IgnoreParenImpCasts();
+
+  InitVar = nullptr;
+  if (const auto *BO = dyn_cast<BinaryOperator>(InitStmt)) {
+    // Allow assignment operator here.
+
+    if (!BO->isAssignmentOp())
+      return DiagLoopVar();
+
+    const Expr *LHS = BO->getLHS()->IgnoreParenImpCasts();
+    if (const auto *DRE = dyn_cast<DeclRefExpr>(LHS))
+      InitVar = DRE->getDecl();
+  } else if (const auto *DS = dyn_cast<DeclStmt>(InitStmt)) {
+    // Allow T t = <whatever>
+    if (!DS->isSingleDecl())
+      return DiagLoopVar();
+    InitVar = dyn_cast<ValueDecl>(DS->getSingleDecl());
+
+    // Ensure we have an initializer, unless this is a record/dependent type.
+    if (InitVar) {
+      if (!isa<VarDecl>(InitVar))
+        return DiagLoopVar();
+
+      if (!InitVar->getType()->isRecordType() &&
+          !InitVar->getType()->isDependentType() &&
+          !cast<VarDecl>(InitVar)->hasInit())
+        return DiagLoopVar();
+    }
+  } else if (auto *CE = dyn_cast<CXXOperatorCallExpr>(InitStmt)) {
+    // Allow assignment operator call.
+    if (CE->getOperator() != OO_Equal)
+      return DiagLoopVar();
+
+    const Expr *LHS = CE->getArg(0)->IgnoreParenImpCasts();
+    if (auto *DRE = dyn_cast<DeclRefExpr>(LHS)) {
+      InitVar = DRE->getDecl();
+    } else if (auto *ME = dyn_cast<MemberExpr>(LHS)) {
+      if (isa<CXXThisExpr>(ME->getBase()->IgnoreParenImpCasts()))
+        InitVar = ME->getMemberDecl();
+    }
+  }
+
+  // If after all of that, we haven't found a variable, give up.
+  if (!InitVar)
+    return DiagLoopVar();
+
+  InitVar = cast<ValueDecl>(InitVar->getCanonicalDecl());
+  QualType VarType = InitVar->getType().getNonReferenceType();
+
+  // Since we have one, all we need to do is ensure it is the right type.
+  if (!isValidLoopVariableType(VarType)) {
+    if (Diag) {
+      SemaRef.Diag(InitVar->getBeginLoc(), diag::err_acc_loop_variable_type)
+          << SemaRef.LoopWithoutSeqInfo.Kind << VarType;
+      SemaRef.Diag(SemaRef.LoopWithoutSeqInfo.Loc,
+                   diag::note_acc_construct_here)
+          << SemaRef.LoopWithoutSeqInfo.Kind;
+    }
+    return true;
+  }
+
+  return false;
+}
+
+bool SemaOpenACC::ForStmtBeginChecker::checkForCond(const Stmt *CondStmt,
+                                                    const ValueDecl *InitVar,
+                                                    bool Diag) {
+  // A condition statement is required.
+  if (!CondStmt) {
+    if (Diag) {
+      SemaRef.Diag(ForLoc, diag::err_acc_loop_terminating_condition)
+          << SemaRef.LoopWithoutSeqInfo.Kind;
+      SemaRef.Diag(SemaRef.LoopWithoutSeqInfo.Loc,
+                   diag::note_acc_construct_here)
+          << SemaRef.LoopWithoutSeqInfo.Kind;
+    }
+
+    return true;
+  }
+  auto DiagCondVar = [this, Diag, CondStmt] {
+    if (Diag) {
+      SemaRef.Diag(CondStmt->getBeginLoc(),
+                   diag::err_acc_loop_terminating_condition)
+          << SemaRef.LoopWithoutSeqInfo.Kind;
+      SemaRef.Diag(SemaRef.LoopWithoutSeqInfo.Loc,
+                   diag::note_acc_construct_here)
+          << SemaRef.LoopWithoutSeqInfo.Kind;
+    }
+    return true;
+  };
+
+  if (const auto *ExprTemp = dyn_cast<ExprWithCleanups>(CondStmt))
+    CondStmt = ExprTemp->getSubExpr();
+  if (const auto *E = dyn_cast<Expr>(CondStmt))
+    CondStmt = E->IgnoreParenImpCasts();
+
+  const ValueDecl *CondVar = nullptr;
+  if (const auto *BO = dyn_cast<BinaryOperator>(CondStmt)) {
+    switch (BO->getOpcode()) {
+    default:
+      return DiagCondVar();
+    case BO_EQ:
+    case BO_LT:
+    case BO_GT:
+    case BO_NE:
+    case BO_LE:
+    case BO_GE:
+      break;
+    }
+
+    // Assign the condition-var to the LHS.  If it either comes back null, or
+    // the LHS doesn't match the InitVar, assign it to the RHS so that 5 < N is
+    // allowed.
+    CondVar = getDeclFromExpr(BO->getLHS());
+    if (!CondVar ||
+        (InitVar && CondVar->getCanonicalDecl() != InitVar->getCanonicalDecl()))
+      CondVar = getDeclFromExpr(BO->getRHS());
+
+  } else if (const auto *CE = dyn_cast<CXXOperatorCallExpr>(CondStmt)) {
+    // Any of the comparison ops should be ok here, but we don't know how to
+    // handle spaceship, so disallow for now.
+    if (!CE->isComparisonOp() || CE->getOperator() == OO_Spaceship)
+      return DiagCondVar();
+
+    // Same logic here: Assign it to the LHS, unless the LHS comes back null or
+    // not equal to the init var.
+    CondVar = getDeclFromExpr(CE->getArg(0));
+    if (!CondVar ||
+        (InitVar &&
+         CondVar->getCanonicalDecl() != InitVar->getCanonicalDecl() &&
+         CE->getNumArgs() > 1))
+      CondVar = getDeclFromExpr(CE->getArg(1));
+  } else if (const auto *ME = dyn_cast<CXXMemberCallExpr>(CondStmt)) {
+    // Here there isn't much we can do besides hope it is the right variable.
+    // Codegen might have to just give up on figuring out trip count in this
+    // case?
+    CondVar = getDeclFromExpr(ME->getImplicitObjectArgument());
+  }
+
+  if (!CondVar)
+    return DiagCondVar();
+
+  // Don't consider this an error unless the init variable was properly set,
+  // else check to make sure they are the same variable.
+  if (InitVar && CondVar->getCanonicalDecl() != InitVar->getCanonicalDecl())
+    return DiagCondVar();
+
+  return false;
+}
+
+bool SemaOpenACC::ForStmtBeginChecker::checkForInc(const Stmt *IncStmt,
+                                                   const ValueDecl *InitVar,
+                                                   bool Diag) {
+  if (!IncStmt) {
+    if (Diag) {
+      SemaRef.Diag(ForLoc, diag::err_acc_loop_not_monotonic)
+          << SemaRef.LoopWithoutSeqInfo.Kind;
+      SemaRef.Diag(SemaRef.LoopWithoutSeqInfo.Loc,
+                   diag::note_acc_construct_here)
+          << SemaRef.LoopWithoutSeqInfo.Kind;
+    }
+    return true;
+  }
+  auto DiagIncVar = [this, Diag, IncStmt] {
+    if (Diag) {
+      SemaRef.Diag(IncStmt->getBeginLoc(), diag::err_acc_loop_not_monotonic)
+          << SemaRef.LoopWithoutSeqInfo.Kind;
+      SemaRef.Diag(SemaRef.LoopWithoutSeqInfo.Loc,
+                   diag::note_acc_construct_here)
+          << SemaRef.LoopWithoutSeqInfo.Kind;
+    }
+    return true;
+  };
+
+  if (const auto *ExprTemp = dyn_cast<ExprWithCleanups>(IncStmt))
+    IncStmt = ExprTemp->getSubExpr();
+  if (const auto *E = dyn_cast<Expr>(IncStmt))
+    IncStmt = E->IgnoreParenImpCasts();
+
+  const ValueDecl *IncVar = nullptr;
+  // Here we enforce the monotonically increase/decrease:
+  if (const auto *UO = dyn_cast<UnaryOperator>(IncStmt)) {
+    // Allow increment/decrement ops.
+    if (!UO->isIncrementDecrementOp())
+      return DiagIncVar();
+    IncVar = getDeclFromExpr(UO->getSubExpr());
+  } else if (const auto *BO = dyn_cast<BinaryOperator>(IncStmt)) {
+    switch (BO->getOpcode()) {
+    default:
+      return DiagIncVar();
+    case BO_AddAssign:
+    case BO_SubAssign:
+    case BO_MulAssign:
+    case BO_DivAssign:
+    case BO_Assign:
+      // += -= *= /= should all be fine here, this should be all of the
+      // 'monotonical' compound-assign ops.
+      // Assignment we just give up on, we could do better, and ensure that it
+      // is a binary/operator expr doing more work, but that seems like a lot
+      // of work for an error prone check.
+      break;
+    }
+    IncVar = getDeclFromExpr(BO->getLHS());
+  } else if (const auto *CE = dyn_cast<CXXOperatorCallExpr>(IncStmt)) {
+    switch (CE->getOperator()) {
+    default:
+      return DiagIncVar();
+    case OO_PlusPlus:
+    case OO_MinusMinus:
+    case OO_PlusEqual:
+    case OO_MinusEqual:
+    case OO_StarEqual:
+    case OO_SlashEqual:
+    case OO_Equal:
+      // += -= *= /= should all be fine here, this should be all of the
+      // 'monotonical' compound-assign ops.
+      // Assignment we just give up on, we could do better, and ensure that it
+      // is a binary/operator expr doing more work, but that seems like a
+      // lot of work for an error prone check.
+      break;
+    }
+
+    IncVar = getDeclFromExpr(CE->getArg(0));
+
+  } else if (const auto *ME = dyn_cast<CXXMemberCallExpr>(IncStmt)) {
+    IncVar = getDeclFromExpr(ME->getImplicitObjectArgument());
+    // We can't really do much for member expressions, other than hope they are
+    // doing the right thing, so give up here.
+  }
+
+  if (!IncVar)
+    return DiagIncVar();
+
+  // InitVar shouldn't be null unless there was an error, so don't diagnose if
+  // that is the case. Else we should ensure that it refers to the  loop
+  // value.
+  if (InitVar && IncVar->getCanonicalDecl() != InitVar->getCanonicalDecl())
+    return DiagIncVar();
+
+  return false;
+}
+
+void SemaOpenACC::ForStmtBeginChecker::checkFor() {
+  const CheckForInfo &CFI = std::get<CheckForInfo>(Info);
+
+  if (!IsInstantiation) {
+    // If this isn't an instantiation, we can just check all of these and
+    // diagnose.
+    const ValueDecl *CurInitVar = nullptr;
+    checkForInit(CFI.Current.Init, CurInitVar, /*Diag=*/true);
+    checkForCond(CFI.Current.Condition, CurInitVar, /*Diag=*/true);
+    checkForInc(CFI.Current.Increment, CurInitVar, /*DIag=*/true);
+  } else {
+    const ValueDecl *UninstInitVar = nullptr;
+    // Checking the 'init' section first. We have to always run both versions,
+    // at minimum with the 'diag' off, so that we can ensure we get the correct
+    // instantiation var for checking by later ones.
+    bool UninstInitFailed =
+        checkForInit(CFI.Uninst.Init, UninstInitVar, /*Diag=*/false);
+
+    // VarDecls are always rebuild because they are dependent, so we can do a
+    // little work to suppress some of the double checking based on whether the
+    // type is instantiation dependent. This is imperfect, but will get us most
+    // cases suppressed. Currently this only handles the 'T t =' case.
+    auto InitChanged = [=]() {
+      if (CFI.Uninst.Init == CFI.Current.Init)
+        return false;
+
+      QualType OldVDTy;
+      QualType NewVDTy;
+
+      if (const auto *DS = dyn_cast<DeclStmt>(CFI.Uninst.Init))
+        if (const VarDecl *VD = dyn_cast_if_present<VarDecl>(
+                DS->isSingleDecl() ? DS->getSingleDecl() : nullptr))
+          OldVDTy = VD->getType();
+      if (const auto *DS = dyn_cast<DeclStmt>(CFI.Current.Init))
+        if (const VarDecl *VD = dyn_cast_if_present<VarDecl>(
+                DS->isSingleDecl() ? DS->getSingleDecl() : nullptr))
+          NewVDTy = VD->getType();
+
+      if (OldVDTy.isNull() || NewVDTy.isNull())
+        return true;
+
+      return OldVDTy->isInstantiationDependentType() !=
+             NewVDTy->isInstantiationDependentType();
+    };
+
+    // Only diagnose the new 'init' if the previous version didn't fail, AND the
+    // current init changed meaningfully.
+    bool ShouldDiagNewInit = !UninstInitFailed && InitChanged();
+    const ValueDecl *CurInitVar = nullptr;
+    checkForInit(CFI.Current.Init, CurInitVar, /*Diag=*/ShouldDiagNewInit);
+
+    // Check the condition and increment only if the previous version passed,
+    // and this changed.
+    if (CFI.Uninst.Condition != CFI.Current.Condition &&
+        !checkForCond(CFI.Uninst.Condition, UninstInitVar, /*Diag=*/false))
+      checkForCond(CFI.Current.Condition, CurInitVar, /*Diag=*/true);
+    if (CFI.Uninst.Increment != CFI.Current.Increment &&
+        !checkForInc(CFI.Uninst.Increment, UninstInitVar, /*Diag=*/false))
+      checkForInc(CFI.Current.Increment, CurInitVar, /*Diag=*/true);
+  }
+}
+
 void SemaOpenACC::ForStmtBeginChecker::check() {
+  // If this isn't an active loop without a seq, immediately return, nothing to
+  // check.
   if (SemaRef.LoopWithoutSeqInfo.Kind == OpenACCDirectiveKind::Invalid)
     return;
 
+  // If we've already checked, because this is a 'top level' one (and asking
+  // again because 'tile' and 'collapse' might apply), just return, nothing to
+  // do here.
   if (AlreadyChecked)
     return;
   AlreadyChecked = true;
@@ -1084,251 +1475,10 @@
   // pointer, that the compiler generates to iterate the range.  it is not the
   // variable declared by the for loop.
 
-  if (IsRangeFor) {
-    // If the range-for is being instantiated and didn't change, don't
-    // re-diagnose.
-    if (!RangeFor.has_value())
-      return;
-    // For a range-for, we can assume everything is 'corect' other than the type
-    // of the iterator, so check that.
-    const DeclStmt *RangeStmt = (*RangeFor)->getBeginStmt();
+  if (std::holds_alternative<RangeForInfo>(Info))
+    return checkRangeFor();
 
-    // In some dependent contexts, the autogenerated range statement doesn't get
-    // included until instantiation, so skip for now.
-    if (!RangeStmt)
-      return;
-
-    const ValueDecl *InitVar = cast<ValueDecl>(RangeStmt->getSingleDecl());
-    QualType VarType = InitVar->getType().getNonReferenceType();
-    if (!isValidLoopVariableType(VarType)) {
-      SemaRef.Diag(InitVar->getBeginLoc(), diag::err_acc_loop_variable_type)
-          << SemaRef.LoopWithoutSeqInfo.Kind << VarType;
-      SemaRef.Diag(SemaRef.LoopWithoutSeqInfo.Loc,
-                   diag::note_acc_construct_here)
-          << SemaRef.LoopWithoutSeqInfo.Kind;
-    }
-    return;
-  }
-
-  // Else we are in normal 'ForStmt', so we can diagnose everything.
-  // We only have to check cond/inc if they have changed, but 'init' needs to
-  // just suppress its diagnostics if it hasn't changed.
-  const ValueDecl *InitVar = checkInit();
-  if (Cond.has_value())
-    checkCond();
-  if (Inc.has_value())
-    checkInc(InitVar);
-}
-const ValueDecl *SemaOpenACC::ForStmtBeginChecker::checkInit() {
-  if (!Init) {
-    if (InitChanged) {
-      SemaRef.Diag(ForLoc, diag::err_acc_loop_variable)
-          << SemaRef.LoopWithoutSeqInfo.Kind;
-      SemaRef.Diag(SemaRef.LoopWithoutSeqInfo.Loc,
-                   diag::note_acc_construct_here)
-          << SemaRef.LoopWithoutSeqInfo.Kind;
-    }
-    return nullptr;
-  }
-
-  auto DiagLoopVar = [&]() {
-    if (InitChanged) {
-      SemaRef.Diag(Init->getBeginLoc(), diag::err_acc_loop_variable)
-          << SemaRef.LoopWithoutSeqInfo.Kind;
-      SemaRef.Diag(SemaRef.LoopWithoutSeqInfo.Loc,
-                   diag::note_acc_construct_here)
-          << SemaRef.LoopWithoutSeqInfo.Kind;
-    }
-    return nullptr;
-  };
-
-  if (const auto *ExprTemp = dyn_cast<ExprWithCleanups>(Init))
-    Init = ExprTemp->getSubExpr();
-  if (const auto *E = dyn_cast<Expr>(Init))
-    Init = E->IgnoreParenImpCasts();
-
-  const ValueDecl *InitVar = nullptr;
-
-  if (const auto *BO = dyn_cast<BinaryOperator>(Init)) {
-    // Allow assignment operator here.
-
-    if (!BO->isAssignmentOp())
-      return DiagLoopVar();
-
-    const Expr *LHS = BO->getLHS()->IgnoreParenImpCasts();
-
-    if (const auto *DRE = dyn_cast<DeclRefExpr>(LHS))
-      InitVar = DRE->getDecl();
-  } else if (const auto *DS = dyn_cast<DeclStmt>(Init)) {
-    // Allow T t = <whatever>
-    if (!DS->isSingleDecl())
-      return DiagLoopVar();
-
-    InitVar = dyn_cast<ValueDecl>(DS->getSingleDecl());
-
-    // Ensure we have an initializer, unless this is a record/dependent type.
-
-    if (InitVar) {
-      if (!isa<VarDecl>(InitVar))
-        return DiagLoopVar();
-
-      if (!InitVar->getType()->isRecordType() &&
-          !InitVar->getType()->isDependentType() &&
-          !cast<VarDecl>(InitVar)->hasInit())
-        return DiagLoopVar();
-    }
-  } else if (auto *CE = dyn_cast<CXXOperatorCallExpr>(Init)) {
-    // Allow assignment operator call.
-    if (CE->getOperator() != OO_Equal)
-      return DiagLoopVar();
-
-    const Expr *LHS = CE->getArg(0)->IgnoreParenImpCasts();
-
-    if (auto *DRE = dyn_cast<DeclRefExpr>(LHS)) {
-      InitVar = DRE->getDecl();
-    } else if (auto *ME = dyn_cast<MemberExpr>(LHS)) {
-      if (isa<CXXThisExpr>(ME->getBase()->IgnoreParenImpCasts()))
-        InitVar = ME->getMemberDecl();
-    }
-  }
-
-  if (!InitVar)
-    return DiagLoopVar();
-
-  InitVar = cast<ValueDecl>(InitVar->getCanonicalDecl());
-  QualType VarType = InitVar->getType().getNonReferenceType();
-
-  // Since we have one, all we need to do is ensure it is the right type.
-  if (!isValidLoopVariableType(VarType)) {
-    if (InitChanged) {
-      SemaRef.Diag(InitVar->getBeginLoc(), diag::err_acc_loop_variable_type)
-          << SemaRef.LoopWithoutSeqInfo.Kind << VarType;
-      SemaRef.Diag(SemaRef.LoopWithoutSeqInfo.Loc,
-                   diag::note_acc_construct_here)
-          << SemaRef.LoopWithoutSeqInfo.Kind;
-    }
-    return nullptr;
-  }
-
-  return InitVar;
-}
-void SemaOpenACC::ForStmtBeginChecker::checkCond() {
-  if (!*Cond) {
-    SemaRef.Diag(ForLoc, diag::err_acc_loop_terminating_condition)
-        << SemaRef.LoopWithoutSeqInfo.Kind;
-    SemaRef.Diag(SemaRef.LoopWithoutSeqInfo.Loc, diag::note_acc_construct_here)
-        << SemaRef.LoopWithoutSeqInfo.Kind;
-  }
-  // Nothing else to do here.  we could probably do some additional work to look
-  // into the termination condition, but that error-prone.  For now, we don't
-  // implement anything other than 'there is a termination condition', and if
-  // codegen/MLIR comes up with some necessary restrictions, we can implement
-  // them here.
-}
-
-void SemaOpenACC::ForStmtBeginChecker::checkInc(const ValueDecl *Init) {
-
-  if (!*Inc) {
-    SemaRef.Diag(ForLoc, diag::err_acc_loop_not_monotonic)
-        << SemaRef.LoopWithoutSeqInfo.Kind;
-    SemaRef.Diag(SemaRef.LoopWithoutSeqInfo.Loc, diag::note_acc_construct_here)
-        << SemaRef.LoopWithoutSeqInfo.Kind;
-    return;
-  }
-  auto DiagIncVar = [this] {
-    SemaRef.Diag((*Inc)->getBeginLoc(), diag::err_acc_loop_not_monotonic)
-        << SemaRef.LoopWithoutSeqInfo.Kind;
-    SemaRef.Diag(SemaRef.LoopWithoutSeqInfo.Loc, diag::note_acc_construct_here)
-        << SemaRef.LoopWithoutSeqInfo.Kind;
-    return;
-  };
-
-  if (const auto *ExprTemp = dyn_cast<ExprWithCleanups>(*Inc))
-    Inc = ExprTemp->getSubExpr();
-  if (const auto *E = dyn_cast<Expr>(*Inc))
-    Inc = E->IgnoreParenImpCasts();
-
-  auto getDeclFromExpr = [](const Expr *E) -> const ValueDecl * {
-    E = E->IgnoreParenImpCasts();
-    if (const auto *FE = dyn_cast<FullExpr>(E))
-      E = FE->getSubExpr();
-
-    E = E->IgnoreParenImpCasts();
-
-    if (!E)
-      return nullptr;
-    if (const auto *DRE = dyn_cast<DeclRefExpr>(E))
-      return dyn_cast<ValueDecl>(DRE->getDecl());
-
-    if (const auto *ME = dyn_cast<MemberExpr>(E))
-      if (isa<CXXThisExpr>(ME->getBase()->IgnoreParenImpCasts()))
-        return ME->getMemberDecl();
-
-    return nullptr;
-  };
-
-  const ValueDecl *IncVar = nullptr;
-
-  // Here we enforce the monotonically increase/decrease:
-  if (const auto *UO = dyn_cast<UnaryOperator>(*Inc)) {
-    // Allow increment/decrement ops.
-    if (!UO->isIncrementDecrementOp())
-      return DiagIncVar();
-    IncVar = getDeclFromExpr(UO->getSubExpr());
-  } else if (const auto *BO = dyn_cast<BinaryOperator>(*Inc)) {
-    switch (BO->getOpcode()) {
-    default:
-      return DiagIncVar();
-    case BO_AddAssign:
-    case BO_SubAssign:
-    case BO_MulAssign:
-    case BO_DivAssign:
-    case BO_Assign:
-      // += -= *= /= should all be fine here, this should be all of the
-      // 'monotonical' compound-assign ops.
-      // Assignment we just give up on, we could do better, and ensure that it
-      // is a binary/operator expr doing more work, but that seems like a lot
-      // of work for an error prone check.
-      break;
-    }
-    IncVar = getDeclFromExpr(BO->getLHS());
-  } else if (const auto *CE = dyn_cast<CXXOperatorCallExpr>(*Inc)) {
-    switch (CE->getOperator()) {
-    default:
-      return DiagIncVar();
-    case OO_PlusPlus:
-    case OO_MinusMinus:
-    case OO_PlusEqual:
-    case OO_MinusEqual:
-    case OO_StarEqual:
-    case OO_SlashEqual:
-    case OO_Equal:
-      // += -= *= /= should all be fine here, this should be all of the
-      // 'monotonical' compound-assign ops.
-      // Assignment we just give up on, we could do better, and ensure that it
-      // is a binary/operator expr doing more work, but that seems like a lot
-      // of work for an error prone check.
-      break;
-    }
-
-    IncVar = getDeclFromExpr(CE->getArg(0));
-
-  } else if (const auto *ME = dyn_cast<CXXMemberCallExpr>(*Inc)) {
-    IncVar = getDeclFromExpr(ME->getImplicitObjectArgument());
-    // We can't really do much for member expressions, other than hope they are
-    // doing the right thing, so give up here.
-  }
-
-  if (!IncVar)
-    return DiagIncVar();
-
-  // InitVar shouldn't be null unless there was an error, so don't diagnose if
-  // that is the case. Else we should ensure that it refers to the  loop
-  // value.
-  if (Init && IncVar->getCanonicalDecl() != Init->getCanonicalDecl())
-    return DiagIncVar();
-
-  return;
+  return checkFor();
 }
 
 void SemaOpenACC::ActOnForStmtBegin(SourceLocation ForLoc, const Stmt *OldFirst,
@@ -1338,41 +1488,10 @@
   if (!getLangOpts().OpenACC)
     return;
 
-  std::optional<const Stmt *> S;
-  if (OldSecond == Second)
-    S = std::nullopt;
-  else
-    S = Second;
-  std::optional<const Stmt *> T;
-  if (OldThird == Third)
-    S = std::nullopt;
-  else
-    S = Third;
-
-  bool InitChanged = false;
-  if (OldFirst != First) {
-    InitChanged = true;
-
-    // VarDecls are always rebuild because they are dependent, so we can do a
-    // little work to suppress some of the double checking based on whether the
-    // type is instantiation dependent.
-    QualType OldVDTy;
-    QualType NewVDTy;
-    if (const auto *DS = dyn_cast<DeclStmt>(OldFirst))
-      if (const VarDecl *VD = dyn_cast_if_present<VarDecl>(
-              DS->isSingleDecl() ? DS->getSingleDecl() : nullptr))
-        OldVDTy = VD->getType();
-    if (const auto *DS = dyn_cast<DeclStmt>(First))
-      if (const VarDecl *VD = dyn_cast_if_present<VarDecl>(
-              DS->isSingleDecl() ? DS->getSingleDecl() : nullptr))
-        NewVDTy = VD->getType();
-
-    if (!OldVDTy.isNull() && !NewVDTy.isNull())
-      InitChanged = OldVDTy->isInstantiationDependentType() !=
-                    NewVDTy->isInstantiationDependentType();
-  }
-
-  ForStmtBeginChecker FSBC{*this, ForLoc, First, InitChanged, S, T};
+  ForStmtBeginChecker FSBC{*this,    ForLoc, OldFirst, OldSecond,
+                           OldThird, First,  Second,   Third};
+  // Check if this is the top-level 'for' for a 'loop'.  Else it will be checked
+  // as a part of the helper if a tile/collapse applies.
   if (!LoopInfo.TopLevelLoopSeen) {
     FSBC.check();
   }
@@ -1385,11 +1504,12 @@
   if (!getLangOpts().OpenACC)
     return;
 
-  ForStmtBeginChecker FSBC{*this,  ForLoc, First, /*InitChanged=*/true,
-                           Second, Third};
-  if (!LoopInfo.TopLevelLoopSeen) {
+  ForStmtBeginChecker FSBC{*this, ForLoc, First, Second, Third};
+
+  // Check if this is the top-level 'for' for a 'loop'.  Else it will be checked
+  // as a part of the helper if a tile/collapse applies.
+  if (!LoopInfo.TopLevelLoopSeen)
     FSBC.check();
-  }
 
   ForStmtBeginHelper(ForLoc, FSBC);
 }
@@ -1400,14 +1520,11 @@
   if (!getLangOpts().OpenACC)
     return;
 
-  std::optional<const CXXForRangeStmt *> RF;
-
-  if (OldRangeFor == RangeFor)
-    RF = std::nullopt;
-  else
-    RF = cast<CXXForRangeStmt>(RangeFor);
-
-  ForStmtBeginChecker FSBC{*this, ForLoc, RF};
+  ForStmtBeginChecker FSBC{*this, ForLoc,
+                           cast_if_present<CXXForRangeStmt>(OldRangeFor),
+                           cast_if_present<CXXForRangeStmt>(RangeFor)};
+  // Check if this is the top-level 'for' for a 'loop'.  Else it will be checked
+  // as a part of the helper if a tile/collapse applies.
   if (!LoopInfo.TopLevelLoopSeen) {
     FSBC.check();
   }
@@ -1419,10 +1536,14 @@
   if (!getLangOpts().OpenACC)
     return;
 
-  ForStmtBeginChecker FSBC{*this, ForLoc, cast<CXXForRangeStmt>(RangeFor)};
-  if (!LoopInfo.TopLevelLoopSeen) {
+  ForStmtBeginChecker FSBC = {*this, ForLoc,
+                              cast_if_present<CXXForRangeStmt>(RangeFor)};
+
+  // Check if this is the top-level 'for' for a 'loop'.  Else it will be checked
+  // as a part of the helper if a tile/collapse applies.
+  if (!LoopInfo.TopLevelLoopSeen)
     FSBC.check();
-  }
+
   ForStmtBeginHelper(ForLoc, FSBC);
 }
 
diff --git a/clang/test/SemaOpenACC/combined-construct.cpp b/clang/test/SemaOpenACC/combined-construct.cpp
index b0fd05e..e7ca0ee 100644
--- a/clang/test/SemaOpenACC/combined-construct.cpp
+++ b/clang/test/SemaOpenACC/combined-construct.cpp
@@ -176,8 +176,8 @@
   for(int f;;);
 
 #pragma acc kernels loop
-  // expected-error@+6 2{{OpenACC 'kernels loop' construct must have initialization clause in canonical form ('var = init' or 'T var = init'}}
-  // expected-note@-2 2{{'kernels loop' construct is here}}
+  // expected-error@+6{{OpenACC 'kernels loop' construct must have initialization clause in canonical form ('var = init' or 'T var = init'}}
+  // expected-note@-2{{'kernels loop' construct is here}}
   // expected-error@+4{{OpenACC 'kernels loop' construct must have a terminating condition}}
   // expected-note@-4{{'kernels loop' construct is here}}
   // expected-error@+2{{OpenACC 'kernels loop' variable must monotonically increase or decrease ('++', '--', or compound assignment)}}
@@ -255,8 +255,8 @@
   for( i = 0;;);
 
 #pragma acc kernels loop
-  // expected-error@+6 2{{OpenACC 'kernels loop' construct must have initialization clause in canonical form ('var = init' or 'T var = init'}}
-  // expected-note@-2 2{{'kernels loop' construct is here}}
+  // expected-error@+6{{OpenACC 'kernels loop' construct must have initialization clause in canonical form ('var = init' or 'T var = init'}}
+  // expected-note@-2{{'kernels loop' construct is here}}
   // expected-error@+4{{OpenACC 'kernels loop' construct must have a terminating condition}}
   // expected-note@-4{{'kernels loop' construct is here}}
   // expected-error@+2{{OpenACC 'kernels loop' variable must monotonically increase or decrease ('++', '--', or compound assignment)}}
@@ -273,8 +273,8 @@
   for( int j ;;);
 
 #pragma acc serial loop
-  // expected-error@+6 2{{OpenACC 'serial loop' construct must have initialization clause in canonical form ('var = init' or 'T var = init'}}
-  // expected-note@-2 2{{'serial loop' construct is here}}
+  // expected-error@+6{{OpenACC 'serial loop' construct must have initialization clause in canonical form ('var = init' or 'T var = init'}}
+  // expected-note@-2{{'serial loop' construct is here}}
   // expected-error@+4{{OpenACC 'serial loop' construct must have a terminating condition}}
   // expected-note@-4{{'serial loop' construct is here}}
   // expected-error@+2{{OpenACC 'serial loop' variable must monotonically increase or decrease ('++', '--', or compound assignment)}}
@@ -342,8 +342,8 @@
   for(auto X : Array);
 
 #pragma acc kernels loop
-  // expected-error@+2 2{{loop variable of loop associated with an OpenACC 'kernels loop' construct must be of integer, pointer, or random-access-iterator type (is 'SomeIterator')}}
-  // expected-note@-2 2{{'kernels loop' construct is here}}
+  // expected-error@+2{{loop variable of loop associated with an OpenACC 'kernels loop' construct must be of integer, pointer, or random-access-iterator type (is 'SomeIterator')}}
+  // expected-note@-2{{'kernels loop' construct is here}}
   for(auto X : HasIteratorCollection{});
 
 #pragma acc serial loop
@@ -355,8 +355,8 @@
 
   RandAccessIterator f;
 #pragma acc serial loop
-  // expected-error@+2 2{{OpenACC 'serial loop' construct must have initialization clause in canonical form ('var = init' or 'T var = init'}}
-  // expected-note@-2 2{{'serial loop' construct is here}}
+  // expected-error@+2{{OpenACC 'serial loop' construct must have initialization clause in canonical form ('var = init' or 'T var = init'}}
+  // expected-note@-2{{'serial loop' construct is here}}
   for(f;f != end;f++);
 
 #pragma acc kernels loop
diff --git a/clang/test/SemaOpenACC/loop-construct.cpp b/clang/test/SemaOpenACC/loop-construct.cpp
index 5616cac..7509811 100644
--- a/clang/test/SemaOpenACC/loop-construct.cpp
+++ b/clang/test/SemaOpenACC/loop-construct.cpp
@@ -167,8 +167,8 @@
   for(int f;;);
 
 #pragma acc loop
-  // expected-error@+6 2{{OpenACC 'loop' construct must have initialization clause in canonical form ('var = init' or 'T var = init'}}
-  // expected-note@-2 2{{'loop' construct is here}}
+  // expected-error@+6{{OpenACC 'loop' construct must have initialization clause in canonical form ('var = init' or 'T var = init'}}
+  // expected-note@-2{{'loop' construct is here}}
   // expected-error@+4{{OpenACC 'loop' construct must have a terminating condition}}
   // expected-note@-4{{'loop' construct is here}}
   // expected-error@+2{{OpenACC 'loop' variable must monotonically increase or decrease ('++', '--', or compound assignment)}}
@@ -246,8 +246,8 @@
   for( i = 0;;);
 
 #pragma acc loop
-  // expected-error@+6 2{{OpenACC 'loop' construct must have initialization clause in canonical form ('var = init' or 'T var = init'}}
-  // expected-note@-2 2{{'loop' construct is here}}
+  // expected-error@+6{{OpenACC 'loop' construct must have initialization clause in canonical form ('var = init' or 'T var = init'}}
+  // expected-note@-2{{'loop' construct is here}}
   // expected-error@+4{{OpenACC 'loop' construct must have a terminating condition}}
   // expected-note@-4{{'loop' construct is here}}
   // expected-error@+2{{OpenACC 'loop' variable must monotonically increase or decrease ('++', '--', or compound assignment)}}
@@ -264,8 +264,8 @@
   for( int j ;;);
 
 #pragma acc loop
-  // expected-error@+6 2{{OpenACC 'loop' construct must have initialization clause in canonical form ('var = init' or 'T var = init'}}
-  // expected-note@-2 2{{'loop' construct is here}}
+  // expected-error@+6{{OpenACC 'loop' construct must have initialization clause in canonical form ('var = init' or 'T var = init'}}
+  // expected-note@-2{{'loop' construct is here}}
   // expected-error@+4{{OpenACC 'loop' construct must have a terminating condition}}
   // expected-note@-4{{'loop' construct is here}}
   // expected-error@+2{{OpenACC 'loop' variable must monotonically increase or decrease ('++', '--', or compound assignment)}}
@@ -333,8 +333,8 @@
   for(auto X : Array);
 
 #pragma acc loop
-  // expected-error@+2 2{{loop variable of loop associated with an OpenACC 'loop' construct must be of integer, pointer, or random-access-iterator type (is 'SomeIterator')}}
-  // expected-note@-2 2{{'loop' construct is here}}
+  // expected-error@+2{{loop variable of loop associated with an OpenACC 'loop' construct must be of integer, pointer, or random-access-iterator type (is 'SomeIterator')}}
+  // expected-note@-2{{'loop' construct is here}}
   for(auto X : HasIteratorCollection{});
 
 #pragma acc loop
@@ -346,8 +346,8 @@
 
   RandAccessIterator f;
 #pragma acc loop
-  // expected-error@+2 2{{OpenACC 'loop' construct must have initialization clause in canonical form ('var = init' or 'T var = init'}}
-  // expected-note@-2 2{{'loop' construct is here}}
+  // expected-error@+2{{OpenACC 'loop' construct must have initialization clause in canonical form ('var = init' or 'T var = init'}}
+  // expected-note@-2{{'loop' construct is here}}
   for(f;f != end;f++);
 
 #pragma acc loop
@@ -373,6 +373,25 @@
 
 #pragma acc loop
   for(f = 0;f != end;f+=1);
+
+#pragma acc loop
+  for (int i = 0; 5 >= i; ++i);
+
+  int otherI;
+  // expected-error@+3{{OpenACC 'loop' construct must have a terminating condition}}
+  // expected-note@+1{{'loop' construct is here}}
+#pragma acc loop
+  for (int i = 0; otherI != 5; ++i);
+
+  // expected-error@+3{{OpenACC 'loop' construct must have a terminating condition}}
+  // expected-note@+1{{'loop' construct is here}}
+#pragma acc loop
+  for (int i = 0; i != 5 && i < 3; ++i);
+
+  // expected-error@+3{{OpenACC 'loop' variable must monotonically increase or decrease}}
+  // expected-note@+1{{'loop' construct is here}}
+#pragma acc loop
+  for (int i = 0; i != 5; ++f);
 }
 
 void inst() {