Repository: llvm/llvm-project
Pull request #131042: [flang][openacc] Make OpenACC block construct parse errors less verbose.
Author: login: akuhlens
Labels: name: flang:driver
name: flang
description: Flang issues not falling into any other category
name: openacc
name: flang:parser
Created: 2025-03-12T22:33:43Z | Merged: 2025-03-26T19:36:05Z

This PR does reduces the verbosity of parser errors for OpenACC block constructs that do not parse correctly because they are missing their trailing end block directive by:
- Removing the redundant error messages created by parsing 3 different styles of directive tokens.
- Providing a general mechanism of configuring the max number of contexts printed for every syntax error.
- Not printing less specific contexts that are at the same location.

Prior to the changes:
```
$ flang -fc1 -fopenacc -fsyntax-only flang/test/Parser/acc-data-statement.f90 2>&1 | tee acc-data-statement.prior.log | wc -l
262
```
[acc-data-statement.prior.log](https://github.com/user-attachments/files/19298165/acc-data-statement.prior.log)

```
$ flang -fc1 -fopenacc -fsyntax-only flang/test/Parser/acc-data-statement.f90 2>&1 | tee acc-data-statement.prior.log | wc -l
73
```
[acc-data-statement.post.log](https://github.com/user-attachments/files/19298181/acc-data-statement.post.log)





Diff:
diff --git a/flang/lib/Parser/message.cpp b/flang/lib/Parser/message.cpp
index 69e4814bf246c..799998c54b531 100644
--- a/flang/lib/Parser/message.cpp
+++ b/flang/lib/Parser/message.cpp
@@ -16,6 +16,7 @@
 #include <cstdio>
 #include <cstring>
 #include <string>
+#include <tuple>
 #include <vector>
 
 namespace Fortran::parser {
@@ -272,19 +273,52 @@ static llvm::raw_ostream::Colors PrefixColor(Severity severity) {
   return llvm::raw_ostream::SAVEDCOLOR;
 }
 
+static constexpr int MAX_CONTEXTS_EMITTED{2};
+static constexpr bool OMIT_SHARED_CONTEXTS{true};
+
 void Message::Emit(llvm::raw_ostream &o, const AllCookedSources &allCooked,
     bool echoSourceLine) const {
   std::optional<ProvenanceRange> provenanceRange{GetProvenanceRange(allCooked)};
   const AllSources &sources{allCooked.allSources()};
   sources.EmitMessage(o, provenanceRange, ToString(), Prefix(severity()),
       PrefixColor(severity()), echoSourceLine);
+  // Refers to whether the attachment in the loop below is a context, but can't
+  // be declared inside the loop because the previous iteration's
+  // attachment->attachmentIsContext_ indicates this.
   bool isContext{attachmentIsContext_};
+  int contextsEmitted{0};
+  // Emit attachments.
   for (const Message *attachment{attachment_.get()}; attachment;
-       attachment = attachment->attachment_.get()) {
+      isContext = attachment->attachmentIsContext_,
+      attachment = attachment->attachment_.get()) {
     Severity severity = isContext ? Severity::Context : attachment->severity();
-    sources.EmitMessage(o, attachment->GetProvenanceRange(allCooked),
-        attachment->ToString(), Prefix(severity), PrefixColor(severity),
-        echoSourceLine);
+    auto emitAttachment = [&]() {
+      sources.EmitMessage(o, attachment->GetProvenanceRange(allCooked),
+          attachment->ToString(), Prefix(severity), PrefixColor(severity),
+          echoSourceLine);
+    };
+
+    if (isContext) {
+      // Truncate the number of contexts emitted.
+      if (contextsEmitted < MAX_CONTEXTS_EMITTED) {
+        emitAttachment();
+        ++contextsEmitted;
+      }
+      if constexpr (OMIT_SHARED_CONTEXTS) {
+        // Skip less specific contexts at the same location.
+        for (const Message *next_attachment{attachment->attachment_.get()};
+            next_attachment && next_attachment->attachmentIsContext_ &&
+            next_attachment->AtSameLocation(*attachment);
+            next_attachment = next_attachment->attachment_.get()) {
+          attachment = next_attachment;
+        }
+        // NB, this loop increments `attachment` one more time after the
+        // previous loop is done advancing it to the last context at the same
+        // location.
+      }
+    } else {
+      emitAttachment();
+    }
   }
 }
 
@@ -298,7 +332,7 @@ bool Message::operator==(const Message &that) const {
   }
   const Message *thatAttachment{that.attachment_.get()};
   for (const Message *attachment{attachment_.get()}; attachment;
-       attachment = attachment->attachment_.get()) {
+      attachment = attachment->attachment_.get()) {
     if (!thatAttachment || !attachment->AtSameLocation(*thatAttachment) ||
         attachment->ToString() != thatAttachment->ToString() ||
         attachment->severity() != thatAttachment->severity()) {
diff --git a/flang/lib/Parser/openacc-parsers.cpp b/flang/lib/Parser/openacc-parsers.cpp
index c78676664e0a3..fb731ee52cbba 100644
--- a/flang/lib/Parser/openacc-parsers.cpp
+++ b/flang/lib/Parser/openacc-parsers.cpp
@@ -19,9 +19,16 @@
 // OpenACC Directives and Clauses
 namespace Fortran::parser {
 
+// Only need to handle ! line comments because prescanning normalizes the
+// other types of line comments from fixed form.
 constexpr auto startAccLine{skipStuffBeforeStatement >>
-    ("!$ACC "_sptok || "C$ACC "_sptok || "*$ACC "_sptok)};
-constexpr auto endAccLine{space >> endOfLine};
+    withMessage(
+        "expected OpenACC directive sentinel: !$ACC, C$ACC, or *$ACC"_err_en_US,
+        "!$ACC "_sptok)};
+constexpr auto endAccLine{space >>
+    recovery(
+        withMessage("expected end of OpenACC directive"_err_en_US, endOfLine),
+        SkipTo<'\n'>{} || ok)};
 
 // Autogenerated clauses parser. Information is taken from ACC.td and the
 // parser is generated by tablegen.
@@ -221,11 +228,18 @@ TYPE_PARSER(sourced(construct<AccBeginBlockDirective>(
     sourced(Parser<AccBlockDirective>{}), Parser<AccClauseList>{})))
 
 TYPE_PARSER(startAccLine >> sourced(construct<AccEndBlockDirective>("END"_tok >>
-                                sourced(Parser<AccBlockDirective>{}))))
+                                recovery(sourced(Parser<AccBlockDirective>{}),
+                                    construct<AccBlockDirective>(pure(
+                                        llvm::acc::Directive::ACCD_data))))))
 
 TYPE_PARSER(construct<OpenACCBlockConstruct>(
     Parser<AccBeginBlockDirective>{} / endAccLine, block,
-    Parser<AccEndBlockDirective>{} / endAccLine))
+    // NB, This allows mismatched directives, but semantics checks that they
+    // match.
+    recovery(withMessage("expected OpenACC end block directive"_err_en_US,
+                 attempt(Parser<AccEndBlockDirective>{} / endAccLine)),
+        construct<AccEndBlockDirective>(construct<AccBlockDirective>(
+            pure(llvm::acc::Directive::ACCD_data))))))
 
 // Standalone constructs
 TYPE_PARSER(construct<OpenACCStandaloneConstruct>(
@@ -249,8 +263,11 @@ TYPE_PARSER(sourced(construct<OpenACCEndConstruct>(
 TYPE_CONTEXT_PARSER("OpenACC construct"_en_US,
     startAccLine >>
         withMessage("expected OpenACC directive"_err_en_US,
-            first(construct<OpenACCConstruct>(Parser<OpenACCBlockConstruct>{}),
+            // Combined constructs before block constructs so we try to match
+            // the longest possible match first.
+            first(
                 construct<OpenACCConstruct>(Parser<OpenACCCombinedConstruct>{}),
+                construct<OpenACCConstruct>(Parser<OpenACCBlockConstruct>{}),
                 construct<OpenACCConstruct>(Parser<OpenACCLoopConstruct>{}),
                 construct<OpenACCConstruct>(
                     Parser<OpenACCStandaloneConstruct>{}),
diff --git a/flang/test/Driver/debug-parsing-log.f90 b/flang/test/Driver/debug-parsing-log.f90
index 7297163109450..fdf52071ab956 100644
--- a/flang/test/Driver/debug-parsing-log.f90
+++ b/flang/test/Driver/debug-parsing-log.f90
@@ -2,24 +2,14 @@
 
 ! Below are just few lines extracted from the dump. The actual output is much _much_ bigger.
 
-! CHECK: {{.*[/\\]}}debug-parsing-log.f90:25:1: IMPLICIT statement
+! CHECK: {{.*[/\\]}}debug-parsing-log.f90:15:1: IMPLICIT statement
 ! CHECK-NEXT:  END PROGRAM
 ! CHECK-NEXT:  ^
 ! CHECK-NEXT:  fail 3
-! CHECK-NEXT: {{.*[/\\]}}debug-parsing-log.f90:25:1: error: expected 'IMPLICIT NONE'
+! CHECK-NEXT: {{.*[/\\]}}debug-parsing-log.f90:15:1: error: expected 'IMPLICIT NONE'
 ! CHECK-NEXT:   END PROGRAM
 ! CHECK-NEXT:   ^
-! CHECK-NEXT: {{.*[/\\]}}debug-parsing-log.f90:25:1: in the context: IMPLICIT statement
+! CHECK-NEXT: {{.*[/\\]}}debug-parsing-log.f90:15:1: in the context: IMPLICIT statement
 ! CHECK-NEXT:   END PROGRAM
 ! CHECK-NEXT:   ^
-! CHECK-NEXT: {{.*[/\\]}}debug-parsing-log.f90:25:1: in the context: implicit part
-! CHECK-NEXT:   END PROGRAM
-! CHECK-NEXT:   ^
-! CHECK-NEXT: {{.*[/\\]}}debug-parsing-log.f90:25:1: in the context: specification part
-! CHECK-NEXT:   END PROGRAM
-! CHECK-NEXT:   ^
-! CHECK-NEXT: {{.*[/\\]}}debug-parsing-log.f90:25:1: in the context: main program
-! CHECK-NEXT:   END PROGRAM
-! CHECK-NEXT:   ^
-
 END PROGRAM
diff --git a/flang/test/Parser/acc-data-statement.f90 b/flang/test/Parser/acc-data-statement.f90
new file mode 100644
index 0000000000000..40c76b2561b24
--- /dev/null
+++ b/flang/test/Parser/acc-data-statement.f90
@@ -0,0 +1,199 @@
+! RUN: not %flang_fc1 -fsyntax-only -fopenacc %s 2>&1 | FileCheck %s
+program acc_data_test
+    implicit none
+    integer :: a(100), b(100), c(100), d(100)
+    integer :: i, s ! FIXME: if s is named sum you get semantic errors.
+
+    ! Positive tests
+
+    ! Basic data construct in program body
+    !$acc data copy(a, b) create(c)
+    a = 1
+    b = 2
+    c = a + b
+    !$acc end data
+    print *, "After first data region"
+
+    ! Data construct within IF block
+    if (.true.) then
+        !$acc data copyout(a)
+        a = a + 1
+        !$acc end data
+        print *, "Inside if block"
+    end if
+
+    ! Data construct within DO loop
+    do i = 1, 10
+        !$acc data present(a)
+        a(i) = a(i) * 2
+        !$acc end data
+        print *, "Loop iteration", i
+    end do
+
+    ! Nested data constructs
+    !$acc data copyin(a)
+    s = 0
+    !$acc data copy(s)
+    s = s + 1
+    !$acc end data
+    print *, "After nested data"
+    !$acc end data
+
+    ! Negative tests  
+    ! Basic data construct in program body
+    !$acc data copy(a, b) create(d) bogus()
+    !CHECK: acc-data-statement.f90:
+    !CHECK-SAME: error: expected end of OpenACC directive
+    !CHECK-NEXT: !$acc data copy(a, b) create(d) bogus()
+    !CHECK-NEXT: ^
+    !CHECK-NEXT: in the context: OpenACC construct
+    !CHECK-NEXT: !$acc data copy(a, b) create(d) bogus()
+    !CHECK-NEXT: ^
+    !CHECK-NEXT: in the context: execution part
+    !CHECK-NEXT: !$acc data copy(a, b) create(c)
+    !CHECK-NEXT: ^
+    a = 1
+    b = 2
+    d = a + b
+!   !$acc end data
+    print *, "After first data region"
+
+    ! Data construct within IF block
+    if (.true.) then
+        !$acc data copyout(a)
+        a = a + 1
+!       !$acc end data
+        print *, "Inside if block"
+        !CHECK: acc-data-statement.f90:
+        !CHECK-SAME: error: expected OpenACC end block directive
+        !CHECK-NEXT: end if
+        !CHECK-NEXT: ^ 
+        !CHECK-NEXT: in the context: OpenACC construct
+        !CHECK-NEXT: !$acc data copyout(a)
+        !CHECK-NEXT: ^
+        !CHECK-NEXT: in the context: IF construct
+        !CHECK-NEXT: if (.true.) then
+        !CHECK-NEXT: ^
+    end if
+
+    ! Data construct within DO loop
+    do i = 1, 10
+        !$acc data present(a)
+        a(i) = a(i) * 2
+!       !$acc end data
+        print *, "Loop iteration", i
+        !CHECK: acc-data-statement.f90:
+        !CHECK-SAME: error: expected OpenACC end block directive
+        !CHECK-NEXT: end do
+        !CHECK-NEXT: ^ 
+        !CHECK-NEXT: in the context: OpenACC construct
+        !CHECK-NEXT: !$acc data present(a)
+        !CHECK-NEXT: ^
+        !CHECK-NEXT: in the context: DO construct
+        !CHECK-NEXT: do i = 1, 10
+        !CHECK-NEXT: ^
+    end do
+
+    ! Nested data constructs
+    !$acc data copyin(a)
+    s = 0
+    !$acc data copy(s)
+    s = s + 1
+!   !$acc end data
+    print *, "After nested data"
+    !$acc end data  I forgot to comment this out.
+    !CHECK: acc-data-statement.f90:
+    !CHECK-SAME: error: expected end of OpenACC directive
+    !CHECK-NEXT: !$acc end data  I forgot to comment this out.
+    !CHECK-NEXT: ^
+    !CHECK-NEXT: in the context: OpenACC construct
+    !CHECK-NEXT: !$acc data copy(s)
+    !CHECK-NEXT: ^
+    !CHECK-NEXT: in the context: OpenACC construct
+    !CHECK-NEXT: !$acc data copyin(a)
+    !CHECK-NEXT: ^
+    print *, "Program finished"
+
+    !CHECK: acc-data-statement.f90:
+    !CHECK-SAME: error: expected OpenACC end block directive
+    !CHECK-NEXT: contains
+    !CHECK-NEXT: ^
+    !CHECK-NEXT: in the context: OpenACC construct
+    !CHECK-NEXT: !$acc data copyin(a)
+    !CHECK-NEXT: ^
+    !CHECK-NEXT: in the context: OpenACC construct
+    !CHECK-NEXT: !$acc data copy(a, b) create(d) bogus()
+    !CHECK-NEXT: ^
+    !CHECK: acc-data-statement.f90:
+    !CHECK-SAME: error: expected OpenACC end block directive
+    !CHECK-NEXT: contains
+    !CHECK-NEXT: ^
+    !CHECK-NEXT: in the context: OpenACC construct
+    !CHECK-NEXT: $acc data copy(a, b) create(d) bogus()
+    !CHECK-NEXT: ^
+    !CHECK-NEXT: in the context: execution part
+    !CHECK-NEXT: !$acc data copy(a, b) create(c)
+    !CHECK-NEXT: ^
+contains
+    subroutine positive_process_array(x)
+        integer, intent(inout) :: x(:)
+        
+        ! Data construct in subroutine
+        !$acc data copy(x)
+        x = x + 1
+        !$acc end data
+        print *, "Subroutine finished"
+    end subroutine
+
+    function positive_compute_sum(x) result(total)
+        integer, intent(in) :: x(:)
+        integer :: total
+        
+        ! Data construct in function
+        !$acc data copyin(x) copy(total)
+        total = sum(x)
+        !$acc end data
+        print *, "Function finished"
+    end function
+    
+    subroutine negative_process_array(x)
+        integer, intent(inout) :: x(:)
+        
+        ! Data construct in subroutine
+        !$acc data copy(x)
+        x = x + 1
+!       !$acc end data
+        print *, "Subroutine finished"
+        !CHECK: acc-data-statement.f90:
+        !CHECK-SAME: error: expected OpenACC end block directive
+        !CHECK-NEXT: end subroutine
+        !CHECK-NEXT: ^
+        !CHECK-NEXT: in the context: OpenACC construct
+        !CHECK-NEXT: !$acc data copy(x)
+        !CHECK-NEXT: ^
+        !CHECK-NEXT: in the context: SUBROUTINE subprogram
+        !CHECK-NEXT: subroutine negative_process_array(x)
+        !CHECK-NEXT: ^
+    end subroutine
+
+    function negative_compute_sum(x) result(total)
+        integer, intent(in) :: x(:)
+        integer :: total
+        total = sum(x)
+        ! Data construct in function
+        !$acc data copyin(x) copy(total)
+        total = total + x
+!       !$acc end data
+        print *, "Function finished"
+        !CHECK: acc-data-statement.f90:
+        !CHECK-SAME: error: expected OpenACC end block directive
+        !CHECK-NEXT: end function
+        !CHECK-NEXT: ^ 
+        !CHECK-NEXT: in the context: OpenACC construct
+        !CHECK-NEXT: !$acc data copyin(x) copy(total)
+        !CHECK-NEXT: ^
+        !CHECK-NEXT: in the context: execution part
+        !CHECK-NEXT: total = sum(x)
+        !CHECK-NEXT: ^
+    end function
+end program acc_data_test
\ No newline at end of file
diff --git a/flang/test/Parser/acc.f b/flang/test/Parser/acc.f
new file mode 100644
index 0000000000000..b0c3927772568
--- /dev/null
+++ b/flang/test/Parser/acc.f
@@ -0,0 +1,96 @@
+! RUN: %flang_fc1 -fsyntax-only -fopenacc %s 2>&1
+C Test file for OpenACC directives in fixed-form Fortran
+      PROGRAM ACCTEST
+      IMPLICIT NONE
+      INTEGER :: N, I, J
+      PARAMETER (N=100)
+      REAL :: A(N), B(N), C(N), D(N)
+      REAL :: SUM
+
+C Initialize arrays
+      DO I = 1, N
+         A(I) = I * 1.0
+         B(I) = I * 2.0
+         C(I) = 0.0
+         D(I) = 1.0
+      END DO
+
+C Basic data construct using C$ACC
+C$ACC DATA COPYIN(A,B) COPYOUT(C)
+      DO I = 1, N
+         C(I) = A(I) + B(I)
+      END DO
+C$ACC END DATA
+
+* Parallel construct with loop using *$ACC
+*$ACC PARALLEL PRESENT(A,B,C)
+*$ACC LOOP
+      DO I = 1, N
+         C(I) = C(I) * 2.0
+      END DO
+*$ACC END PARALLEL
+
+C Nested loops with collapse - C$ACC style
+C$ACC PARALLEL LOOP COLLAPSE(2)
+      DO I = 1, N
+         DO J = 1, N
+            A(J) = A(J) + B(J)
+         END DO
+      END DO
+C$ACC END PARALLEL LOOP
+
+* Combined parallel loop with reduction - *$ACC style
+      SUM = 0.0
+*$ACC PARALLEL LOOP REDUCTION(+:SUM)
+      DO I = 1, N
+         SUM = SUM + C(I)
+      END DO
+*$ACC END PARALLEL LOOP
+
+C Kernels construct - C$ACC with continuation
+C$ACC KERNELS 
+C$ACC+ COPYOUT(A)
+      DO I = 1, N
+         A(I) = A(I) * 2.0
+      END DO
+C$ACC END KERNELS
+
+* Data construct with update - *$ACC with continuation
+*$ACC DATA COPY(B)
+*$ACC+ PRESENT(D)
+      B(1) = 999.0
+*$ACC UPDATE HOST(B(1:1))
+      PRINT *, 'B(1) = ', B(1)
+*$ACC END DATA
+
+C Mixed style directives in nested constructs
+C$ACC DATA COPY(A,B,C)
+*$ACC PARALLEL LOOP
+      DO I = 1, N
+         A(I) = B(I) + C(I)
+      END DO
+*$ACC END PARALLEL LOOP
+C$ACC END DATA
+
+* Subroutine call within data region - *$ACC style
+*$ACC DATA COPY(A,B,C)
+      CALL SUB1(A, B, C, N)
+*$ACC END DATA
+
+      PRINT *, 'Sum = ', SUM
+      END PROGRAM
+
+C Subroutine with mixed ACC directive styles
+      SUBROUTINE SUB1(X, Y, Z, M)
+      INTEGER M, I
+      REAL X(M), Y(M), Z(M)
+
+*$ACC PARALLEL PRESENT(X,Y)
+C$ACC LOOP PRIVATE(I)
+      DO I = 1, M
+         Z(I) = X(I) + Y(I)
+      END DO
+C$ACC END LOOP
+*$ACC END PARALLEL
+      RETURN
+      END SUBROUTINE 
\ No newline at end of file


Reviews:
user: login: klausler
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T22:44:52Z
user: login: akuhlens
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T00:06:31Z
user: login: akuhlens
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T00:09:08Z
user: login: klausler
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T00:15:04Z
user: login: klausler
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T21:15:59Z
user: login: akuhlens
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T22:30:07Z
user: login: klausler
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T23:18:48Z
user: login: akuhlens
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T19:37:36Z
user: login: akuhlens
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T19:37:58Z
user: login: akuhlens
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T23:58:47Z
user: login: klausler
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T00:12:13Z
user: login: akuhlens
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T15:56:51Z
user: login: akuhlens
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T15:59:14Z

Review comments:
diff_hunk: @@ -19,8 +19,12 @@
 // OpenACC Directives and Clauses
 namespace Fortran::parser {
 
+// Only need to handle ! line comments because prescanning normalizes the
+// other types of line comments from fixed form.
 constexpr auto startAccLine{skipStuffBeforeStatement >>
-    ("!$ACC "_sptok || "C$ACC "_sptok || "*$ACC "_sptok)};
+    withMessage(
+        "expected OpenACC comment '!$ACC' (free-form), 'C$ACC', or '*$ACC' (fixed-form)"_err_en_US,
path: flang/lib/Parser/openacc-parsers.cpp
user: login: klausler
body: "directive sentinel" would be more precise than "comment"
created_at: 2025-03-12T22:41:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -225,7 +229,8 @@ TYPE_PARSER(startAccLine >> sourced(construct<AccEndBlockDirective>("END"_tok >>
 
 TYPE_PARSER(construct<OpenACCBlockConstruct>(
     Parser<AccBeginBlockDirective>{} / endAccLine, block,
-    Parser<AccEndBlockDirective>{} / endAccLine))
+    withMessage("expected OpenACC end block directive"_err_en_US,
path: flang/lib/Parser/openacc-parsers.cpp
user: login: klausler
body: still needs error recovery here, I think
created_at: 2025-03-12T22:43:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -12,14 +12,14 @@
 ! CHECK-NEXT: {{.*[/\\]}}debug-parsing-log.f90:25:1: in the context: IMPLICIT statement
 ! CHECK-NEXT:   END PROGRAM
 ! CHECK-NEXT:   ^
-! CHECK-NEXT: {{.*[/\\]}}debug-parsing-log.f90:25:1: in the context: implicit part
-! CHECK-NEXT:   END PROGRAM
-! CHECK-NEXT:   ^
-! CHECK-NEXT: {{.*[/\\]}}debug-parsing-log.f90:25:1: in the context: specification part
-! CHECK-NEXT:   END PROGRAM
-! CHECK-NEXT:   ^
-! CHECK-NEXT: {{.*[/\\]}}debug-parsing-log.f90:25:1: in the context: main program
-! CHECK-NEXT:   END PROGRAM
-! CHECK-NEXT:   ^
+
path: flang/test/Driver/debug-parsing-log.f90
user: login: klausler
body: can you delete the lines instead of replacing them with blank ones?
created_at: 2025-03-12T22:44:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -225,7 +229,8 @@ TYPE_PARSER(startAccLine >> sourced(construct<AccEndBlockDirective>("END"_tok >>
 
 TYPE_PARSER(construct<OpenACCBlockConstruct>(
     Parser<AccBeginBlockDirective>{} / endAccLine, block,
-    Parser<AccEndBlockDirective>{} / endAccLine))
+    withMessage("expected OpenACC end block directive"_err_en_US,
path: flang/lib/Parser/openacc-parsers.cpp
user: login: akuhlens
body: What case is the error recovery here suppose to improve? I played around with it a little and couldn't get any improvement. My understanding would be the recovery code gets back to a know good state. I think the recovery code in executionPart already does this, but I am not certain.
created_at: 2025-03-13T00:06:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -98,7 +98,7 @@ constexpr auto progUnitEndStmt{
                  "PROCEDURE"_tok || "MODULE"_tok || "SUBMODULE"_tok ||
                  "PROGRAM"_tok || "BLOCK DATA"_tok)};
 constexpr auto constructEndStmtErrorRecovery{
-    !progUnitEndStmt >> ("END"_tok >> SkipTo<'\n'>{} || ok)};
+    !progUnitEndStmt >> (("!$ACC "_sptok >> "END"_tok) || "END"_tok >> SkipTo<'\n'>{} || ok)};
path: flang/lib/Parser/stmt-parser.h
user: login: akuhlens
body: This was mistakenly added, and I will back it out.
created_at: 2025-03-13T00:09:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -225,7 +229,8 @@ TYPE_PARSER(startAccLine >> sourced(construct<AccEndBlockDirective>("END"_tok >>
 
 TYPE_PARSER(construct<OpenACCBlockConstruct>(
     Parser<AccBeginBlockDirective>{} / endAccLine, block,
-    Parser<AccEndBlockDirective>{} / endAccLine))
+    withMessage("expected OpenACC end block directive"_err_en_US,
path: flang/lib/Parser/openacc-parsers.cpp
user: login: klausler
body: Without explicit error recovery here, a missing `AccEndBlockDirective` will cause the whole `OpenACCBlockConstruct` production to fail.  It might be better to recover from a missing `AccEndBlockDirective` by complaining that it was missing, and then still succeeding on the whole production.

The explicit error recovery could also go into the parser for `AccEndBlockDirective`.
created_at: 2025-03-13T00:15:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -272,19 +272,51 @@ static llvm::raw_ostream::Colors PrefixColor(Severity severity) {
   return llvm::raw_ostream::SAVEDCOLOR;
 }
 
+// TODO: Make these configurable, based on verbosity level.
+const int MAX_CONTEXTS_EMITTED = 2;
path: flang/lib/Parser/message.cpp
user: login: klausler
body: These should be `static constexpr`, not externally visible `const`,  and initialized with braces.
created_at: 2025-03-17T21:08:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -272,19 +272,51 @@ static llvm::raw_ostream::Colors PrefixColor(Severity severity) {
   return llvm::raw_ostream::SAVEDCOLOR;
 }
 
+// TODO: Make these configurable, based on verbosity level.
+const int MAX_CONTEXTS_EMITTED = 2;
+const bool OMIT_SHARED_CONTEXTS = true;
+
 void Message::Emit(llvm::raw_ostream &o, const AllCookedSources &allCooked,
     bool echoSourceLine) const {
   std::optional<ProvenanceRange> provenanceRange{GetProvenanceRange(allCooked)};
   const AllSources &sources{allCooked.allSources()};
   sources.EmitMessage(o, provenanceRange, ToString(), Prefix(severity()),
       PrefixColor(severity()), echoSourceLine);
+  // Always refers to if the attachment in the loop below is a context.
path: flang/lib/Parser/message.cpp
user: login: klausler
body: "whether", not "if".
created_at: 2025-03-17T21:09:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -272,19 +272,51 @@ static llvm::raw_ostream::Colors PrefixColor(Severity severity) {
   return llvm::raw_ostream::SAVEDCOLOR;
 }
 
+// TODO: Make these configurable, based on verbosity level.
+const int MAX_CONTEXTS_EMITTED = 2;
+const bool OMIT_SHARED_CONTEXTS = true;
+
 void Message::Emit(llvm::raw_ostream &o, const AllCookedSources &allCooked,
     bool echoSourceLine) const {
   std::optional<ProvenanceRange> provenanceRange{GetProvenanceRange(allCooked)};
   const AllSources &sources{allCooked.allSources()};
   sources.EmitMessage(o, provenanceRange, ToString(), Prefix(severity()),
       PrefixColor(severity()), echoSourceLine);
+  // Always refers to if the attachment in the loop below is a context.
   bool isContext{attachmentIsContext_};
+  int contextsEmitted{0};
+  // Emit attachments.
   for (const Message *attachment{attachment_.get()}; attachment;
-       attachment = attachment->attachment_.get()) {
+      isContext = attachment->attachmentIsContext_,
+      attachment = attachment->attachment_.get()) {
     Severity severity = isContext ? Severity::Context : attachment->severity();
-    sources.EmitMessage(o, attachment->GetProvenanceRange(allCooked),
-        attachment->ToString(), Prefix(severity), PrefixColor(severity),
-        echoSourceLine);
+    auto emitAttachment = [&]() {
+      sources.EmitMessage(o, attachment->GetProvenanceRange(allCooked),
+          attachment->ToString(), Prefix(severity), PrefixColor(severity),
+          echoSourceLine);
+    };
+
+    if (isContext) {
+      // Truncate the number of contexts emitted.
+      if (contextsEmitted < MAX_CONTEXTS_EMITTED) {
+        emitAttachment();
+        contextsEmitted += 1;
path: flang/lib/Parser/message.cpp
user: login: klausler
body: `++contextsEmitted;`
created_at: 2025-03-17T21:09:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -272,19 +272,51 @@ static llvm::raw_ostream::Colors PrefixColor(Severity severity) {
   return llvm::raw_ostream::SAVEDCOLOR;
 }
 
+// TODO: Make these configurable, based on verbosity level.
+const int MAX_CONTEXTS_EMITTED = 2;
+const bool OMIT_SHARED_CONTEXTS = true;
+
 void Message::Emit(llvm::raw_ostream &o, const AllCookedSources &allCooked,
     bool echoSourceLine) const {
   std::optional<ProvenanceRange> provenanceRange{GetProvenanceRange(allCooked)};
   const AllSources &sources{allCooked.allSources()};
   sources.EmitMessage(o, provenanceRange, ToString(), Prefix(severity()),
       PrefixColor(severity()), echoSourceLine);
+  // Always refers to if the attachment in the loop below is a context.
   bool isContext{attachmentIsContext_};
+  int contextsEmitted{0};
+  // Emit attachments.
   for (const Message *attachment{attachment_.get()}; attachment;
-       attachment = attachment->attachment_.get()) {
+      isContext = attachment->attachmentIsContext_,
+      attachment = attachment->attachment_.get()) {
     Severity severity = isContext ? Severity::Context : attachment->severity();
-    sources.EmitMessage(o, attachment->GetProvenanceRange(allCooked),
-        attachment->ToString(), Prefix(severity), PrefixColor(severity),
-        echoSourceLine);
+    auto emitAttachment = [&]() {
+      sources.EmitMessage(o, attachment->GetProvenanceRange(allCooked),
+          attachment->ToString(), Prefix(severity), PrefixColor(severity),
+          echoSourceLine);
+    };
+
+    if (isContext) {
+      // Truncate the number of contexts emitted.
+      if (contextsEmitted < MAX_CONTEXTS_EMITTED) {
+        emitAttachment();
+        contextsEmitted += 1;
+      }
+      if (OMIT_SHARED_CONTEXTS) {
path: flang/lib/Parser/message.cpp
user: login: klausler
body: `if constexpr`
created_at: 2025-03-17T21:10:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -19,9 +19,16 @@
 // OpenACC Directives and Clauses
 namespace Fortran::parser {
 
+// Only need to handle ! line comments because prescanning normalizes the
+// other types of line comments from fixed form.
 constexpr auto startAccLine{skipStuffBeforeStatement >>
-    ("!$ACC "_sptok || "C$ACC "_sptok || "*$ACC "_sptok)};
-constexpr auto endAccLine{space >> endOfLine};
+    withMessage(
+        "expected OpenACC directive sentinal: !$ACC (free-form) / C$ACC or *$ACC (fixed-form)"_err_en_US,
path: flang/lib/Parser/openacc-parsers.cpp
user: login: klausler
body: It's spelled "sentinel".
created_at: 2025-03-17T21:11:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -221,11 +228,19 @@ TYPE_PARSER(sourced(construct<AccBeginBlockDirective>(
     sourced(Parser<AccBlockDirective>{}), Parser<AccClauseList>{})))
 
 TYPE_PARSER(startAccLine >> sourced(construct<AccEndBlockDirective>("END"_tok >>
-                                sourced(Parser<AccBlockDirective>{}))))
+                                sourced(recovery(Parser<AccBlockDirective>{},
+                                    construct<AccBlockDirective>(pure(
+                                        llvm::acc::Directive::ACCD_data)))))))
 
 TYPE_PARSER(construct<OpenACCBlockConstruct>(
     Parser<AccBeginBlockDirective>{} / endAccLine, block,
-    Parser<AccEndBlockDirective>{} / endAccLine))
+    // TODO: This still allows mismatched directives.
+    recovery(withMessage("expected OpenACC end block directive"_err_en_US,
+                 attempt(Parser<AccEndBlockDirective>{} / endAccLine)),
+        // TODO: Is there a simpler way to build this?
+        sourced(construct<AccEndBlockDirective>(
path: flang/lib/Parser/openacc-parsers.cpp
user: login: klausler
body: Are you sure that you want `sourced` here?  It's going to create `AccEndBlockDirective` and `AccBlockDirective` class instances whose `source` data members will point to something that isn't such a thing, and that might cause semantic error messages on those parse tree nodes to point to arbitrary locations in the source file.
created_at: 2025-03-17T21:15:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -221,11 +228,19 @@ TYPE_PARSER(sourced(construct<AccBeginBlockDirective>(
     sourced(Parser<AccBlockDirective>{}), Parser<AccClauseList>{})))
 
 TYPE_PARSER(startAccLine >> sourced(construct<AccEndBlockDirective>("END"_tok >>
-                                sourced(Parser<AccBlockDirective>{}))))
+                                sourced(recovery(Parser<AccBlockDirective>{},
+                                    construct<AccBlockDirective>(pure(
+                                        llvm::acc::Directive::ACCD_data)))))))
 
 TYPE_PARSER(construct<OpenACCBlockConstruct>(
     Parser<AccBeginBlockDirective>{} / endAccLine, block,
-    Parser<AccEndBlockDirective>{} / endAccLine))
+    // TODO: This still allows mismatched directives.
+    recovery(withMessage("expected OpenACC end block directive"_err_en_US,
+                 attempt(Parser<AccEndBlockDirective>{} / endAccLine)),
+        // TODO: Is there a simpler way to build this?
+        sourced(construct<AccEndBlockDirective>(
path: flang/lib/Parser/openacc-parsers.cpp
user: login: akuhlens
body: No, I will remove the calls to source.
created_at: 2025-03-17T22:30:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -272,19 +272,51 @@ static llvm::raw_ostream::Colors PrefixColor(Severity severity) {
   return llvm::raw_ostream::SAVEDCOLOR;
 }
 
+// TODO: Make these configurable, based on verbosity level.
+static constexpr int MAX_CONTEXTS_EMITTED{2};
+static constexpr bool OMIT_SHARED_CONTEXTS{true};
+
 void Message::Emit(llvm::raw_ostream &o, const AllCookedSources &allCooked,
     bool echoSourceLine) const {
   std::optional<ProvenanceRange> provenanceRange{GetProvenanceRange(allCooked)};
   const AllSources &sources{allCooked.allSources()};
   sources.EmitMessage(o, provenanceRange, ToString(), Prefix(severity()),
       PrefixColor(severity()), echoSourceLine);
+  // Always refers to whether the attachment in the loop below is a context.
   bool isContext{attachmentIsContext_};
+  int contextsEmitted{0};
+  // Emit attachments.
   for (const Message *attachment{attachment_.get()}; attachment;
-       attachment = attachment->attachment_.get()) {
+      isContext = attachment->attachmentIsContext_,
path: flang/lib/Parser/message.cpp
user: login: klausler
body: Should this assignment be `isContext |= ...`?
created_at: 2025-03-17T23:17:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -19,9 +19,16 @@
 // OpenACC Directives and Clauses
 namespace Fortran::parser {
 
+// Only need to handle ! line comments because prescanning normalizes the
+// other types of line comments from fixed form.
 constexpr auto startAccLine{skipStuffBeforeStatement >>
-    ("!$ACC "_sptok || "C$ACC "_sptok || "*$ACC "_sptok)};
-constexpr auto endAccLine{space >> endOfLine};
+    withMessage(
+        "expected OpenACC directive sentinel: !$ACC (free-form) / C$ACC or *$ACC (fixed-form)"_err_en_US,
path: flang/lib/Parser/openacc-parsers.cpp
user: login: klausler
body: Note that `!$ACC` works in both free form and fixed form source.
created_at: 2025-03-17T23:18:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -272,19 +272,51 @@ static llvm::raw_ostream::Colors PrefixColor(Severity severity) {
   return llvm::raw_ostream::SAVEDCOLOR;
 }
 
+// TODO: Make these configurable, based on verbosity level.
+static constexpr int MAX_CONTEXTS_EMITTED{2};
+static constexpr bool OMIT_SHARED_CONTEXTS{true};
+
 void Message::Emit(llvm::raw_ostream &o, const AllCookedSources &allCooked,
     bool echoSourceLine) const {
   std::optional<ProvenanceRange> provenanceRange{GetProvenanceRange(allCooked)};
   const AllSources &sources{allCooked.allSources()};
   sources.EmitMessage(o, provenanceRange, ToString(), Prefix(severity()),
       PrefixColor(severity()), echoSourceLine);
+  // Always refers to whether the attachment in the loop below is a context.
   bool isContext{attachmentIsContext_};
+  int contextsEmitted{0};
+  // Emit attachments.
   for (const Message *attachment{attachment_.get()}; attachment;
-       attachment = attachment->attachment_.get()) {
+      isContext = attachment->attachmentIsContext_,
path: flang/lib/Parser/message.cpp
user: login: akuhlens
body: I think it could be. I didn't find anywhere in the codebase that elucidates the invariants of contexts as an abstraction. Right now it seemed to me that any time a context was attached to a message that attatchmentIsContext on the message is set to true. I don't think there is any time where non-contexts are attached to contexts, which would necessitate this change, but I do think it would harden the code for future development. So I will make this change. 
created_at: 2025-03-18T19:37:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -19,9 +19,16 @@
 // OpenACC Directives and Clauses
 namespace Fortran::parser {
 
+// Only need to handle ! line comments because prescanning normalizes the
+// other types of line comments from fixed form.
 constexpr auto startAccLine{skipStuffBeforeStatement >>
-    ("!$ACC "_sptok || "C$ACC "_sptok || "*$ACC "_sptok)};
-constexpr auto endAccLine{space >> endOfLine};
+    withMessage(
+        "expected OpenACC directive sentinel: !$ACC (free-form) / C$ACC or *$ACC (fixed-form)"_err_en_US,
path: flang/lib/Parser/openacc-parsers.cpp
user: login: akuhlens
body: Will do.

created_at: 2025-03-18T19:37:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -272,19 +272,51 @@ static llvm::raw_ostream::Colors PrefixColor(Severity severity) {
   return llvm::raw_ostream::SAVEDCOLOR;
 }
 
+// TODO: Make these configurable, based on verbosity level.
+static constexpr int MAX_CONTEXTS_EMITTED{2};
+static constexpr bool OMIT_SHARED_CONTEXTS{true};
+
 void Message::Emit(llvm::raw_ostream &o, const AllCookedSources &allCooked,
     bool echoSourceLine) const {
   std::optional<ProvenanceRange> provenanceRange{GetProvenanceRange(allCooked)};
   const AllSources &sources{allCooked.allSources()};
   sources.EmitMessage(o, provenanceRange, ToString(), Prefix(severity()),
       PrefixColor(severity()), echoSourceLine);
+  // Always refers to whether the attachment in the loop below is a context.
   bool isContext{attachmentIsContext_};
+  int contextsEmitted{0};
+  // Emit attachments.
   for (const Message *attachment{attachment_.get()}; attachment;
-       attachment = attachment->attachment_.get()) {
+      isContext = attachment->attachmentIsContext_,
path: flang/lib/Parser/message.cpp
user: login: akuhlens
body: Actually after thinking about it, I think the invariants are simpler without. I can do this is you really want, but I think it just complicates something that isn't really specified anywhere.
created_at: 2025-03-18T23:58:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -272,19 +272,51 @@ static llvm::raw_ostream::Colors PrefixColor(Severity severity) {
   return llvm::raw_ostream::SAVEDCOLOR;
 }
 
+// TODO: Make these configurable, based on verbosity level.
+static constexpr int MAX_CONTEXTS_EMITTED{2};
+static constexpr bool OMIT_SHARED_CONTEXTS{true};
+
 void Message::Emit(llvm::raw_ostream &o, const AllCookedSources &allCooked,
     bool echoSourceLine) const {
   std::optional<ProvenanceRange> provenanceRange{GetProvenanceRange(allCooked)};
   const AllSources &sources{allCooked.allSources()};
   sources.EmitMessage(o, provenanceRange, ToString(), Prefix(severity()),
       PrefixColor(severity()), echoSourceLine);
+  // Always refers to whether the attachment in the loop below is a context.
path: flang/lib/Parser/message.cpp
user: login: klausler
body: I think that `attachmentIsContext_` here refers to `*this`, not to its first attachment.  Things would be more clear if you omitted the `isContext` variable and just used `attachment->attachmentIsContext_` below, or moved this Boolean flag variable into that loop and initialized it to `attachment->attachmentIsContext_` on each iteration.
created_at: 2025-03-19T00:07:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -221,11 +228,18 @@ TYPE_PARSER(sourced(construct<AccBeginBlockDirective>(
     sourced(Parser<AccBlockDirective>{}), Parser<AccClauseList>{})))
 
 TYPE_PARSER(startAccLine >> sourced(construct<AccEndBlockDirective>("END"_tok >>
-                                sourced(Parser<AccBlockDirective>{}))))
+                                sourced(recovery(Parser<AccBlockDirective>{},
+                                    construct<AccBlockDirective>(pure(
+                                        llvm::acc::Directive::ACCD_data)))))))
 
 TYPE_PARSER(construct<OpenACCBlockConstruct>(
     Parser<AccBeginBlockDirective>{} / endAccLine, block,
-    Parser<AccEndBlockDirective>{} / endAccLine))
+    // TODO: This still allows mismatched directives.
+    recovery(withMessage("expected OpenACC end block directive"_err_en_US,
+                 attempt(Parser<AccEndBlockDirective>{} / endAccLine)),
+        // TODO: Is there a simpler way to build this?
path: flang/lib/Parser/openacc-parsers.cpp
user: login: klausler
body: Please resolve as many of your TODO comments as you can rather than merging them as unresolved problems.

For this one, it might be the case that it would be clearer to move the `pure` call to the top level of the recovery production, i.e. `pure(AccEndBlockDirective{AccBlockDirective{llvm::acc::Directive::ACCD_data}})`.
created_at: 2025-03-19T00:11:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -272,19 +272,51 @@ static llvm::raw_ostream::Colors PrefixColor(Severity severity) {
   return llvm::raw_ostream::SAVEDCOLOR;
 }
 
+// TODO: Make these configurable, based on verbosity level.
+static constexpr int MAX_CONTEXTS_EMITTED{2};
+static constexpr bool OMIT_SHARED_CONTEXTS{true};
+
 void Message::Emit(llvm::raw_ostream &o, const AllCookedSources &allCooked,
     bool echoSourceLine) const {
   std::optional<ProvenanceRange> provenanceRange{GetProvenanceRange(allCooked)};
   const AllSources &sources{allCooked.allSources()};
   sources.EmitMessage(o, provenanceRange, ToString(), Prefix(severity()),
       PrefixColor(severity()), echoSourceLine);
+  // Always refers to whether the attachment in the loop below is a context.
path: flang/lib/Parser/message.cpp
user: login: akuhlens
body: https://github.com/llvm/llvm-project/blob/480202f0d16f7dbc5c650aea6e8dfd9eca5b999d/flang/include/flang/Parser/message.h#L273
created_at: 2025-03-19T15:56:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -272,19 +272,51 @@ static llvm::raw_ostream::Colors PrefixColor(Severity severity) {
   return llvm::raw_ostream::SAVEDCOLOR;
 }
 
+// TODO: Make these configurable, based on verbosity level.
+static constexpr int MAX_CONTEXTS_EMITTED{2};
+static constexpr bool OMIT_SHARED_CONTEXTS{true};
+
 void Message::Emit(llvm::raw_ostream &o, const AllCookedSources &allCooked,
     bool echoSourceLine) const {
   std::optional<ProvenanceRange> provenanceRange{GetProvenanceRange(allCooked)};
   const AllSources &sources{allCooked.allSources()};
   sources.EmitMessage(o, provenanceRange, ToString(), Prefix(severity()),
       PrefixColor(severity()), echoSourceLine);
+  // Always refers to whether the attachment in the loop below is a context.
path: flang/lib/Parser/message.cpp
user: login: akuhlens
body: Ah! I didn't know you could do the second option. I will look into that. This comment was created because I too thought that that variable would be talking about this* initially, but it does not according to SetContext above.

created_at: 2025-03-19T15:59:14Z
author_association: CONTRIBUTOR

PR comments:
user: login: llvmbot
created_at: 2025-03-12T22:34:15Z
body: 
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-flang-parser

@llvm/pr-subscribers-flang-driver

Author: Andre Kuhlenschmidt (akuhlens)

<details>
<summary>Changes</summary>

This PR does reduces the verbosity of parser errors for OpenACC block constructs that do not parse correctly because they are missing their trailing end block directive by:
- Removing the redundant error messages created by parsing 3 different styles of directive tokens.
- Providing a general mechanism of configuring the max number of contexts printed for every syntax error.
- Not printing less specific contexts that are at the same location.

Prior to the changes:
```
$ flang -fc1 -fopenacc -fsyntax-only flang/test/Parser/acc-data-statement.f90 2&gt;&amp;1 | tee acc-data-statement.prior.log | wc -l
520
```
[acc-data-statement.prior.log](https://github.com/user-attachments/files/19218484/acc-data-statement.prior.log)
```
$ flang -fc1 -fopenacc -fsyntax-only flang/test/Parser/acc-data-statement.f90 2&gt;&amp;1 | tee acc-data-statement.prior.log | wc -l
118
```
[acc-data-statement.post.log](https://github.com/user-attachments/files/19218483/acc-data-statement.post.log)

Note there are still several bugs in the parsing that I would like to fix, but I am uploading this to talk with @<!-- -->klausler about where to look for those.
- Off by one error in location for some open acc data directives. Probably caused by me modifying the "!$ACC" token, but I don't know how to fix it.
- Duplicated error messages. Under IF and DO constructs
- Multiple parses for the same error message
- Flagging one problem as an error in specification part of subroutine.


---
Full diff: https://github.com/llvm/llvm-project/pull/131042.diff


5 Files Affected:

- (modified) flang/lib/Parser/message.cpp (+35-5) 
- (modified) flang/lib/Parser/openacc-parsers.cpp (+7-2) 
- (modified) flang/test/Driver/debug-parsing-log.f90 (+9-9) 
- (added) flang/test/Parser/acc-data-statement.f90 (+245) 
- (added) flang/test/Parser/acc.f (+96) 


``````````diff
diff --git a/flang/lib/Parser/message.cpp b/flang/lib/Parser/message.cpp
index 69e4814bf246c..0b8d300189a1b 100644
--- a/flang/lib/Parser/message.cpp
+++ b/flang/lib/Parser/message.cpp
@@ -272,6 +272,10 @@ static llvm::raw_ostream::Colors PrefixColor(Severity severity) {
   return llvm::raw_ostream::SAVEDCOLOR;
 }
 
+// FIXME: Make these configurable, based on verbosity level.
+const int MAX_CONTEXTS_EMITTED = 2;
+const bool OMIT_SHARED_CONTEXTS = true;
+
 void Message::Emit(llvm::raw_ostream &o, const AllCookedSources &allCooked,
     bool echoSourceLine) const {
   std::optional<ProvenanceRange> provenanceRange{GetProvenanceRange(allCooked)};
@@ -279,12 +283,38 @@ void Message::Emit(llvm::raw_ostream &o, const AllCookedSources &allCooked,
   sources.EmitMessage(o, provenanceRange, ToString(), Prefix(severity()),
       PrefixColor(severity()), echoSourceLine);
   bool isContext{attachmentIsContext_};
+  int contextsEmitted{isContext ? 1 : 0};
+  // Emit attachments.
   for (const Message *attachment{attachment_.get()}; attachment;
-       attachment = attachment->attachment_.get()) {
+      attachment = attachment->attachment_.get()) {
     Severity severity = isContext ? Severity::Context : attachment->severity();
-    sources.EmitMessage(o, attachment->GetProvenanceRange(allCooked),
-        attachment->ToString(), Prefix(severity), PrefixColor(severity),
-        echoSourceLine);
+    auto emitAttachment = [&]() {
+      sources.EmitMessage(o, attachment->GetProvenanceRange(allCooked),
+          attachment->ToString(), Prefix(severity), PrefixColor(severity),
+          echoSourceLine);
+    };
+    // TODO isContext is not used correctly here.
+    if (attachment->attachmentIsContext_) {
+      // Truncate the number of contexts emitted.
+      if (contextsEmitted <= MAX_CONTEXTS_EMITTED) {
+        emitAttachment();
+        contextsEmitted += 1;
+      }
+      if (OMIT_SHARED_CONTEXTS) {
+        // Skip less specific contexts at the same location.
+        for (const Message *next_attachment{attachment->attachment_.get()};
+            next_attachment && next_attachment->attachmentIsContext_ &&
+            next_attachment->AtSameLocation(*attachment);
+            next_attachment = next_attachment->attachment_.get()) {
+          attachment = next_attachment;
+        }
+        // NB, this loop increments `attachment` one more time after the
+        // previous loop is done advancing it to the last context at the same
+        // location.
+      }
+    } else {
+      emitAttachment();
+    }
   }
 }
 
@@ -298,7 +328,7 @@ bool Message::operator==(const Message &that) const {
   }
   const Message *thatAttachment{that.attachment_.get()};
   for (const Message *attachment{attachment_.get()}; attachment;
-       attachment = attachment->attachment_.get()) {
+      attachment = attachment->attachment_.get()) {
     if (!thatAttachment || !attachment->AtSameLocation(*thatAttachment) ||
         attachment->ToString() != thatAttachment->ToString() ||
         attachment->severity() != thatAttachment->severity()) {
diff --git a/flang/lib/Parser/openacc-parsers.cpp b/flang/lib/Parser/openacc-parsers.cpp
index c78676664e0a3..6739bcc035fc2 100644
--- a/flang/lib/Parser/openacc-parsers.cpp
+++ b/flang/lib/Parser/openacc-parsers.cpp
@@ -19,8 +19,12 @@
 // OpenACC Directives and Clauses
 namespace Fortran::parser {
 
+// Only need to handle ! line comments because prescanning normalizes the
+// other types of line comments from fixed form.
 constexpr auto startAccLine{skipStuffBeforeStatement >>
-    ("!$ACC "_sptok || "C$ACC "_sptok || "*$ACC "_sptok)};
+    withMessage(
+        "expected OpenACC comment '!$ACC' (free-form), 'C$ACC', or '*$ACC' (fixed-form)"_err_en_US,
+        "!$ACC "_sptok)};
 constexpr auto endAccLine{space >> endOfLine};
 
 // Autogenerated clauses parser. Information is taken from ACC.td and the
@@ -225,7 +229,8 @@ TYPE_PARSER(startAccLine >> sourced(construct<AccEndBlockDirective>("END"_tok >>
 
 TYPE_PARSER(construct<OpenACCBlockConstruct>(
     Parser<AccBeginBlockDirective>{} / endAccLine, block,
-    Parser<AccEndBlockDirective>{} / endAccLine))
+    withMessage("expected OpenACC end block directive"_err_en_US,
+        Parser<AccEndBlockDirective>{} / endAccLine)))
 
 // Standalone constructs
 TYPE_PARSER(construct<OpenACCStandaloneConstruct>(
diff --git a/flang/test/Driver/debug-parsing-log.f90 b/flang/test/Driver/debug-parsing-log.f90
index 7297163109450..4e56add386ef8 100644
--- a/flang/test/Driver/debug-parsing-log.f90
+++ b/flang/test/Driver/debug-parsing-log.f90
@@ -12,14 +12,14 @@
 ! CHECK-NEXT: {{.*[/\\]}}debug-parsing-log.f90:25:1: in the context: IMPLICIT statement
 ! CHECK-NEXT:   END PROGRAM
 ! CHECK-NEXT:   ^
-! CHECK-NEXT: {{.*[/\\]}}debug-parsing-log.f90:25:1: in the context: implicit part
-! CHECK-NEXT:   END PROGRAM
-! CHECK-NEXT:   ^
-! CHECK-NEXT: {{.*[/\\]}}debug-parsing-log.f90:25:1: in the context: specification part
-! CHECK-NEXT:   END PROGRAM
-! CHECK-NEXT:   ^
-! CHECK-NEXT: {{.*[/\\]}}debug-parsing-log.f90:25:1: in the context: main program
-! CHECK-NEXT:   END PROGRAM
-! CHECK-NEXT:   ^
+
+
+
+
+
+
+
+
+
 
 END PROGRAM
diff --git a/flang/test/Parser/acc-data-statement.f90 b/flang/test/Parser/acc-data-statement.f90
new file mode 100644
index 0000000000000..1e369a0db3780
--- /dev/null
+++ b/flang/test/Parser/acc-data-statement.f90
@@ -0,0 +1,245 @@
+! RUN: not %flang_fc1 -fsyntax-only -fopenacc %s 2>&1 | FileCheck %s
+program acc_data_test
+    implicit none
+    integer :: a(100), b(100), c(100), d(100)
+    integer :: i, s ! FIXME: if s is named sum you get semantic errors.
+
+    ! Positive tests
+
+    ! Basic data construct in program body
+    !$acc data copy(a, b) create(c)
+    a = 1
+    b = 2
+    c = a + b
+    !$acc end data
+    print *, "After first data region"
+
+    ! Data construct within IF block
+    if (.true.) then
+        !$acc data copyout(a)
+        a = a + 1
+        !$acc end data
+        print *, "Inside if block"
+    end if
+
+    ! Data construct within DO loop
+    do i = 1, 10
+        !$acc data present(a)
+        a(i) = a(i) * 2
+        !$acc end data
+        print *, "Loop iteration", i
+    end do
+
+    ! Nested data constructs
+    !$acc data copyin(a)
+    s = 0
+    !$acc data copy(s)
+    s = s + 1
+    !$acc end data
+    print *, "After nested data"
+    !$acc end data
+
+    ! Negative tests  
+    ! Basic data construct in program body
+    !$acc data copy(a, b) create(d)
+    a = 1
+    b = 2
+    d = a + b
+!   !$acc end data
+    print *, "After first data region"
+
+    ! Data construct within IF block
+    if (.true.) then
+        !$acc data copyout(a)
+        a = a + 1
+!       !$acc end data
+        print *, "Inside if block"
+        ! First error in the file.
+        !CHECK: acc-data-statement.f90:
+        !CHECK-SAME: [[ELINE1:[0-9]+]]:{{[0-9]+}}:
+        !CHECK-SAME: error: expected OpenACC end block directive
+        !CHECK-NEXT: end if
+        !CHECK-NEXT: ^ 
+        !CHECK-NEXT: in the context: OpenACC construct
+        !CHECK-NEXT: !$acc data copyout(a)
+        !CHECK-NEXT: ^
+        !CHECK-NEXT: in the context: IF construct
+        !CHECK-NEXT: if (.true.) then
+        !CHECK-NEXT: ^
+        !CHECK-NEXT: error: expected OpenACC end block directive
+        !CHECK-NEXT: end if
+        !CHECK-NEXT: ^ 
+        !CHECK-NEXT: in the context: OpenACC construct
+        !CHECK-NEXT: !$acc data copyout(a)
+        !CHECK-NEXT: ^
+        !CHECK-NEXT: in the context: IF construct
+        !CHECK-NEXT: if (.true.) then
+        !CHECK-NEXT: ^
+    end if
+
+    ! Data construct within DO loop
+    do i = 1, 10
+        !$acc data present(a)
+        a(i) = a(i) * 2
+!       !$acc end data
+        print *, "Loop iteration", i
+        !CHECK: acc-data-statement.f90:
+        !CHECK-NOT:  [[ELINE1]]
+        !CHECK-SAME: [[ELINE2:[0-9]+]]:{{[0-9]+}}:
+        !CHECK-SAME: error: expected OpenACC end block directive
+        !CHECK-NEXT: end do
+        !CHECK-NEXT: ^ 
+        !CHECK-NEXT: in the context: OpenACC construct
+        !CHECK-NEXT: !$acc data present(a)
+        !CHECK-NEXT: ^
+        !CHECK-NEXT: in the context: DO construct
+        !CHECK-NEXT: do i = 1, 10
+        !CHECK-NEXT: ^
+        !CHECK-NEXT: error: expected OpenACC end block directive
+        !CHECK-NEXT: end do 
+        !CHECK-NEXT: ^ 
+        !CHECK-NEXT: in the context: OpenACC construct
+        !CHECK-NEXT: !$acc data present(a)
+        !CHECK-NEXT: ^
+        !CHECK-NEXT: in the context: DO construct
+        !CHECK-NEXT: do i = 1, 10
+        !CHECK-NEXT: ^
+    end do
+
+    ! Nested data constructs
+    !$acc data copyin(a)
+    s = 0
+    !$acc data copy(s)
+    s = s + 1
+!   !$acc end data
+    print *, "After nested data"
+!   !$acc end data
+
+    print *, "Program finished"
+    !CHECK: acc-data-statement.f90:
+    !CHECK-NOT:  [[ELINE2]]
+    !CHECK-SAME: [[ELINE3:[0-9]+]]:{{[0-9]+}}:
+    !CHECK-SAME: error: expected OpenACC end block directive
+    !CHECK-NEXT: contains
+    !CHECK-NEXT: ^ 
+    !CHECK-NEXT: in the context: OpenACC construct
+    !CHECK-NEXT: !$acc data copy(s)
+    !CHECK-NEXT: ^
+    !CHECK-NEXT: in the context: execution part
+    !CHECK-NEXT: !$acc data copy(a, b) create(c)
+    !CHECK-NEXT: ^
+    !CHECK-NEXT: error: expected OpenACC end block directive
+    !CHECK-NEXT: contains
+    !CHECK-NEXT: ^
+    !CHECK-NEXT: in the context: OpenACC construct
+    !CHECK-NEXT: !$acc data copy(s)
+    !CHECK-NEXT: ^
+    !CHECK-NEXT: in the context: OpenACC construct
+    !CHECK-NEXT: !$acc data copyin(a)
+    !CHECK-NEXT: ^
+    !CHECK-NEXT: error: expected OpenACC end block directive
+    !CHECK-NEXT: contains
+    !CHECK-NEXT: ^
+    !CHECK-NEXT: in the context: OpenACC construct
+    !CHECK-NEXT: !$acc data copyin(a)
+    !CHECK-NEXT: ^
+    !CHECK-NEXT: in the context: execution part
+    !CHECK-NEXT: !$acc data copy(a, b) create(c)
+    !CHECK-NEXT: ^
+    !CHECK-NEXT: error: expected OpenACC end block directive
+    !CHECK-NEXT: contains
+    !CHECK-NEXT: ^
+    !CHECK-NEXT: in the context: OpenACC construct
+    !CHECK-NEXT: !$acc data copy(s)
+    !CHECK-NEXT: ^
+    !CHECK-NEXT: in the context: OpenACC construct
+    !CHECK-NEXT: !$acc data copy(a, b) create(d)
+    !CHECK-NEXT: ^
+    !CHECK-NEXT: error: expected OpenACC end block directive
+    !CHECK-NEXT: contains
+    !CHECK-NEXT: ^
+    !CHECK-NEXT: in the context: OpenACC construct
+    !CHECK-NEXT: !$acc data copy(s)
+    !CHECK-NEXT: ^
+    !CHECK-NEXT: in the context: OpenACC construct
+    !CHECK-NEXT: !$acc data copyin(a)
+    !CHECK-NEXT: ^
+    !CHECK-NEXT: error: expected OpenACC end block directive
+    !CHECK-NEXT: contains
+    !CHECK-NEXT: ^ 
+    !CHECK-NEXT: in the context: OpenACC construct
+    !CHECK-NEXT: !$acc data copyin(a)
+    !CHECK-NEXT: ^
+    !CHECK-NEXT: in the context: OpenACC construct
+    !CHECK-NEXT: !$acc data copy(a, b) create(d)
+    !CHECK-NEXT: ^
+    !CHECK-NEXT: error: expected OpenACC end block directive
+    !CHECK-NEXT: contains
+    !CHECK-NEXT: ^ 
+    !CHECK-NEXT: in the context: OpenACC construct
+    !CHECK-NEXT: !$acc data copy(a, b) create(d)
+    !CHECK-NEXT: ^
+    !CHECK-NEXT: in the context: execution part
+    !CHECK-NEXT: !$acc data copy(a, b) create(c)
+    !CHECK-NEXT: ^
+contains
+    subroutine positive_process_array(x)
+        integer, intent(inout) :: x(:)
+        
+        ! Data construct in subroutine
+        !$acc data copy(x)
+        x = x + 1
+        !$acc end data
+        print *, "Subroutine finished"
+    end subroutine
+
+    function positive_compute_sum(x) result(total)
+        integer, intent(in) :: x(:)
+        integer :: total
+        
+        ! Data construct in function
+        !$acc data copyin(x) copy(total)
+        total = sum(x)
+        !$acc end data
+        print *, "Function finished"
+    end function
+    
+    subroutine negative_process_array(x)
+        integer, intent(inout) :: x(:)
+        
+        ! Data construct in subroutine
+        !$acc data copy(x)
+        x = x + 1
+!       !$acc end data
+        print *, "Subroutine finished"
+        !CHECK: error: expected OpenACC directive
+        !CHECK-NEXT: !$acc data copy(x)
+        !CHECK-NEXT: ^ 
+        !CHECK-NEXT: in the context: specification construct
+        !CHECK-NEXT: !$acc data copy(x)
+        !CHECK-NEXT: ^
+        !CHECK-NEXT: in the context: specification part
+        !CHECK-NEXT: integer, intent(inout) :: x(:)
+        !CHECK-NEXT: ^
+    end subroutine
+
+    function negative_compute_sum(x) result(total)
+        integer, intent(in) :: x(:)
+        integer :: total
+        total = sum(x)
+        ! Data construct in function
+        !$acc data copyin(x) copy(total)
+        total = total + x
+!       !$acc end data
+        print *, "Function finished"
+        !CHECK: error: expected OpenACC end block directive
+        !CHECK-NEXT: end function
+        !CHECK-NEXT: ^ 
+        !CHECK-NEXT: in the context: OpenACC construct
+        !CHECK-NEXT: !$acc data copyin(x) copy(total)
+        !CHECK-NEXT: ^
+        !CHECK-NEXT: in the context: execution part
+        !CHECK-NEXT: total = sum(x)
+        !CHECK-NEXT: ^
+    end function
+end program acc_data_test
\ No newline at end of file
diff --git a/flang/test/Parser/acc.f b/flang/test/Parser/acc.f
new file mode 100644
index 0000000000000..b0c3927772568
--- /dev/null
+++ b/flang/test/Parser/acc.f
@@ -0,0 +1,96 @@
+! RUN: %flang_fc1 -fsyntax-only -fopenacc %s 2>&1
+C Test file for OpenACC directives in fixed-form Fortran
+      PROGRAM ACCTEST
+      IMPLICIT NONE
+      INTEGER :: N, I, J
+      PARAMETER (N=100)
+      REAL :: A(N), B(N), C(N), D(N)
+      REAL :: SUM
+
+C Initialize arrays
+      DO I = 1, N
+         A(I) = I * 1.0
+         B(I) = I * 2.0
+         C(I) = 0.0
+         D(I) = 1.0
+      END DO
+
+C Basic data construct using C$ACC
+C$ACC DATA COPYIN(A,B) COPYOUT(C)
+      DO I = 1, N
+         C(I) = A(I) + B(I)
+      END DO
+C$ACC END DATA
+
+* Parallel construct with loop using *$ACC
+*$ACC PARALLEL PRESENT(A,B,C)
+*$ACC LOOP
+      DO I = 1, N
+         C(I) = C(I) * 2.0
+      END DO
+*$ACC END PARALLEL
+
+C Nested loops with collapse - C$ACC style
+C$ACC PARALLEL LOOP COLLAPSE(2)
+      DO I = 1, N
+         DO J = 1, N
+            A(J) = A(J) + B(J)
+         END DO
+      END DO
+C$ACC END PARALLEL LOOP
+
+* Combined parallel loop with reduction - *$ACC style
+      SUM = 0.0
+*$ACC PARALLEL LOOP REDUCTION(+:SUM)
+      DO I = 1, N
+         SUM = SUM + C(I)
+      END DO
+*$ACC END PARALLEL LOOP
+
+C Kernels construct - C$ACC with continuation
+C$ACC KERNELS 
+C$ACC+ COPYOUT(A)
+      DO I = 1, N
+         A(I) = A(I) * 2.0
+      END DO
+C$ACC END KERNELS
+
+* Data construct with update - *$ACC with continuation
+*$ACC DATA COPY(B)
+*$ACC+ PRESENT(D)
+      B(1) = 999.0
+*$ACC UPDATE HOST(B(1:1))
+      PRINT *, 'B(1) = ', B(1)
+*$ACC END DATA
+
+C Mixed style directives in nested constructs
+C$ACC DATA COPY(A,B,C)
+*$ACC PARALLEL LOOP
+      DO I = 1, N
+         A(I) = B(I) + C(I)
+      END DO
+*$ACC END PARALLEL LOOP
+C$ACC END DATA
+
+* Subroutine call within data region - *$ACC style
+*$ACC DATA COPY(A,B,C)
+      CALL SUB1(A, B, C, N)
+*$ACC END DATA
+
+      PRINT *, 'Sum = ', SUM
+      END PROGRAM
+
+C Subroutine with mixed ACC directive styles
+      SUBROUTINE SUB1(X, Y, Z, M)
+      INTEGER M, I
+      REAL X(M), Y(M), Z(M)
+
+*$ACC PARALLEL PRESENT(X,Y)
+C$ACC LOOP PRIVATE(I)
+      DO I = 1, M
+         Z(I) = X(I) + Y(I)
+      END DO
+C$ACC END LOOP
+*$ACC END PARALLEL
+      RETURN
+      END SUBROUTINE 
\ No newline at end of file

``````````

</details>

author_association: MEMBER
user: login: llvmbot
created_at: 2025-03-12T22:34:16Z
body: 
<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-openacc

Author: Andre Kuhlenschmidt (akuhlens)

<details>
<summary>Changes</summary>

This PR does reduces the verbosity of parser errors for OpenACC block constructs that do not parse correctly because they are missing their trailing end block directive by:
- Removing the redundant error messages created by parsing 3 different styles of directive tokens.
- Providing a general mechanism of configuring the max number of contexts printed for every syntax error.
- Not printing less specific contexts that are at the same location.

Prior to the changes:
```
$ flang -fc1 -fopenacc -fsyntax-only flang/test/Parser/acc-data-statement.f90 2&gt;&amp;1 | tee acc-data-statement.prior.log | wc -l
520
```
[acc-data-statement.prior.log](https://github.com/user-attachments/files/19218484/acc-data-statement.prior.log)
```
$ flang -fc1 -fopenacc -fsyntax-only flang/test/Parser/acc-data-statement.f90 2&gt;&amp;1 | tee acc-data-statement.prior.log | wc -l
118
```
[acc-data-statement.post.log](https://github.com/user-attachments/files/19218483/acc-data-statement.post.log)

Note there are still several bugs in the parsing that I would like to fix, but I am uploading this to talk with @<!-- -->klausler about where to look for those.
- Off by one error in location for some open acc data directives. Probably caused by me modifying the "!$ACC" token, but I don't know how to fix it.
- Duplicated error messages. Under IF and DO constructs
- Multiple parses for the same error message
- Flagging one problem as an error in specification part of subroutine.


---
Full diff: https://github.com/llvm/llvm-project/pull/131042.diff


5 Files Affected:

- (modified) flang/lib/Parser/message.cpp (+35-5) 
- (modified) flang/lib/Parser/openacc-parsers.cpp (+7-2) 
- (modified) flang/test/Driver/debug-parsing-log.f90 (+9-9) 
- (added) flang/test/Parser/acc-data-statement.f90 (+245) 
- (added) flang/test/Parser/acc.f (+96) 


``````````diff
diff --git a/flang/lib/Parser/message.cpp b/flang/lib/Parser/message.cpp
index 69e4814bf246c..0b8d300189a1b 100644
--- a/flang/lib/Parser/message.cpp
+++ b/flang/lib/Parser/message.cpp
@@ -272,6 +272,10 @@ static llvm::raw_ostream::Colors PrefixColor(Severity severity) {
   return llvm::raw_ostream::SAVEDCOLOR;
 }
 
+// FIXME: Make these configurable, based on verbosity level.
+const int MAX_CONTEXTS_EMITTED = 2;
+const bool OMIT_SHARED_CONTEXTS = true;
+
 void Message::Emit(llvm::raw_ostream &o, const AllCookedSources &allCooked,
     bool echoSourceLine) const {
   std::optional<ProvenanceRange> provenanceRange{GetProvenanceRange(allCooked)};
@@ -279,12 +283,38 @@ void Message::Emit(llvm::raw_ostream &o, const AllCookedSources &allCooked,
   sources.EmitMessage(o, provenanceRange, ToString(), Prefix(severity()),
       PrefixColor(severity()), echoSourceLine);
   bool isContext{attachmentIsContext_};
+  int contextsEmitted{isContext ? 1 : 0};
+  // Emit attachments.
   for (const Message *attachment{attachment_.get()}; attachment;
-       attachment = attachment->attachment_.get()) {
+      attachment = attachment->attachment_.get()) {
     Severity severity = isContext ? Severity::Context : attachment->severity();
-    sources.EmitMessage(o, attachment->GetProvenanceRange(allCooked),
-        attachment->ToString(), Prefix(severity), PrefixColor(severity),
-        echoSourceLine);
+    auto emitAttachment = [&]() {
+      sources.EmitMessage(o, attachment->GetProvenanceRange(allCooked),
+          attachment->ToString(), Prefix(severity), PrefixColor(severity),
+          echoSourceLine);
+    };
+    // TODO isContext is not used correctly here.
+    if (attachment->attachmentIsContext_) {
+      // Truncate the number of contexts emitted.
+      if (contextsEmitted <= MAX_CONTEXTS_EMITTED) {
+        emitAttachment();
+        contextsEmitted += 1;
+      }
+      if (OMIT_SHARED_CONTEXTS) {
+        // Skip less specific contexts at the same location.
+        for (const Message *next_attachment{attachment->attachment_.get()};
+            next_attachment && next_attachment->attachmentIsContext_ &&
+            next_attachment->AtSameLocation(*attachment);
+            next_attachment = next_attachment->attachment_.get()) {
+          attachment = next_attachment;
+        }
+        // NB, this loop increments `attachment` one more time after the
+        // previous loop is done advancing it to the last context at the same
+        // location.
+      }
+    } else {
+      emitAttachment();
+    }
   }
 }
 
@@ -298,7 +328,7 @@ bool Message::operator==(const Message &that) const {
   }
   const Message *thatAttachment{that.attachment_.get()};
   for (const Message *attachment{attachment_.get()}; attachment;
-       attachment = attachment->attachment_.get()) {
+      attachment = attachment->attachment_.get()) {
     if (!thatAttachment || !attachment->AtSameLocation(*thatAttachment) ||
         attachment->ToString() != thatAttachment->ToString() ||
         attachment->severity() != thatAttachment->severity()) {
diff --git a/flang/lib/Parser/openacc-parsers.cpp b/flang/lib/Parser/openacc-parsers.cpp
index c78676664e0a3..6739bcc035fc2 100644
--- a/flang/lib/Parser/openacc-parsers.cpp
+++ b/flang/lib/Parser/openacc-parsers.cpp
@@ -19,8 +19,12 @@
 // OpenACC Directives and Clauses
 namespace Fortran::parser {
 
+// Only need to handle ! line comments because prescanning normalizes the
+// other types of line comments from fixed form.
 constexpr auto startAccLine{skipStuffBeforeStatement >>
-    ("!$ACC "_sptok || "C$ACC "_sptok || "*$ACC "_sptok)};
+    withMessage(
+        "expected OpenACC comment '!$ACC' (free-form), 'C$ACC', or '*$ACC' (fixed-form)"_err_en_US,
+        "!$ACC "_sptok)};
 constexpr auto endAccLine{space >> endOfLine};
 
 // Autogenerated clauses parser. Information is taken from ACC.td and the
@@ -225,7 +229,8 @@ TYPE_PARSER(startAccLine >> sourced(construct<AccEndBlockDirective>("END"_tok >>
 
 TYPE_PARSER(construct<OpenACCBlockConstruct>(
     Parser<AccBeginBlockDirective>{} / endAccLine, block,
-    Parser<AccEndBlockDirective>{} / endAccLine))
+    withMessage("expected OpenACC end block directive"_err_en_US,
+        Parser<AccEndBlockDirective>{} / endAccLine)))
 
 // Standalone constructs
 TYPE_PARSER(construct<OpenACCStandaloneConstruct>(
diff --git a/flang/test/Driver/debug-parsing-log.f90 b/flang/test/Driver/debug-parsing-log.f90
index 7297163109450..4e56add386ef8 100644
--- a/flang/test/Driver/debug-parsing-log.f90
+++ b/flang/test/Driver/debug-parsing-log.f90
@@ -12,14 +12,14 @@
 ! CHECK-NEXT: {{.*[/\\]}}debug-parsing-log.f90:25:1: in the context: IMPLICIT statement
 ! CHECK-NEXT:   END PROGRAM
 ! CHECK-NEXT:   ^
-! CHECK-NEXT: {{.*[/\\]}}debug-parsing-log.f90:25:1: in the context: implicit part
-! CHECK-NEXT:   END PROGRAM
-! CHECK-NEXT:   ^
-! CHECK-NEXT: {{.*[/\\]}}debug-parsing-log.f90:25:1: in the context: specification part
-! CHECK-NEXT:   END PROGRAM
-! CHECK-NEXT:   ^
-! CHECK-NEXT: {{.*[/\\]}}debug-parsing-log.f90:25:1: in the context: main program
-! CHECK-NEXT:   END PROGRAM
-! CHECK-NEXT:   ^
+
+
+
+
+
+
+
+
+
 
 END PROGRAM
diff --git a/flang/test/Parser/acc-data-statement.f90 b/flang/test/Parser/acc-data-statement.f90
new file mode 100644
index 0000000000000..1e369a0db3780
--- /dev/null
+++ b/flang/test/Parser/acc-data-statement.f90
@@ -0,0 +1,245 @@
+! RUN: not %flang_fc1 -fsyntax-only -fopenacc %s 2>&1 | FileCheck %s
+program acc_data_test
+    implicit none
+    integer :: a(100), b(100), c(100), d(100)
+    integer :: i, s ! FIXME: if s is named sum you get semantic errors.
+
+    ! Positive tests
+
+    ! Basic data construct in program body
+    !$acc data copy(a, b) create(c)
+    a = 1
+    b = 2
+    c = a + b
+    !$acc end data
+    print *, "After first data region"
+
+    ! Data construct within IF block
+    if (.true.) then
+        !$acc data copyout(a)
+        a = a + 1
+        !$acc end data
+        print *, "Inside if block"
+    end if
+
+    ! Data construct within DO loop
+    do i = 1, 10
+        !$acc data present(a)
+        a(i) = a(i) * 2
+        !$acc end data
+        print *, "Loop iteration", i
+    end do
+
+    ! Nested data constructs
+    !$acc data copyin(a)
+    s = 0
+    !$acc data copy(s)
+    s = s + 1
+    !$acc end data
+    print *, "After nested data"
+    !$acc end data
+
+    ! Negative tests  
+    ! Basic data construct in program body
+    !$acc data copy(a, b) create(d)
+    a = 1
+    b = 2
+    d = a + b
+!   !$acc end data
+    print *, "After first data region"
+
+    ! Data construct within IF block
+    if (.true.) then
+        !$acc data copyout(a)
+        a = a + 1
+!       !$acc end data
+        print *, "Inside if block"
+        ! First error in the file.
+        !CHECK: acc-data-statement.f90:
+        !CHECK-SAME: [[ELINE1:[0-9]+]]:{{[0-9]+}}:
+        !CHECK-SAME: error: expected OpenACC end block directive
+        !CHECK-NEXT: end if
+        !CHECK-NEXT: ^ 
+        !CHECK-NEXT: in the context: OpenACC construct
+        !CHECK-NEXT: !$acc data copyout(a)
+        !CHECK-NEXT: ^
+        !CHECK-NEXT: in the context: IF construct
+        !CHECK-NEXT: if (.true.) then
+        !CHECK-NEXT: ^
+        !CHECK-NEXT: error: expected OpenACC end block directive
+        !CHECK-NEXT: end if
+        !CHECK-NEXT: ^ 
+        !CHECK-NEXT: in the context: OpenACC construct
+        !CHECK-NEXT: !$acc data copyout(a)
+        !CHECK-NEXT: ^
+        !CHECK-NEXT: in the context: IF construct
+        !CHECK-NEXT: if (.true.) then
+        !CHECK-NEXT: ^
+    end if
+
+    ! Data construct within DO loop
+    do i = 1, 10
+        !$acc data present(a)
+        a(i) = a(i) * 2
+!       !$acc end data
+        print *, "Loop iteration", i
+        !CHECK: acc-data-statement.f90:
+        !CHECK-NOT:  [[ELINE1]]
+        !CHECK-SAME: [[ELINE2:[0-9]+]]:{{[0-9]+}}:
+        !CHECK-SAME: error: expected OpenACC end block directive
+        !CHECK-NEXT: end do
+        !CHECK-NEXT: ^ 
+        !CHECK-NEXT: in the context: OpenACC construct
+        !CHECK-NEXT: !$acc data present(a)
+        !CHECK-NEXT: ^
+        !CHECK-NEXT: in the context: DO construct
+        !CHECK-NEXT: do i = 1, 10
+        !CHECK-NEXT: ^
+        !CHECK-NEXT: error: expected OpenACC end block directive
+        !CHECK-NEXT: end do 
+        !CHECK-NEXT: ^ 
+        !CHECK-NEXT: in the context: OpenACC construct
+        !CHECK-NEXT: !$acc data present(a)
+        !CHECK-NEXT: ^
+        !CHECK-NEXT: in the context: DO construct
+        !CHECK-NEXT: do i = 1, 10
+        !CHECK-NEXT: ^
+    end do
+
+    ! Nested data constructs
+    !$acc data copyin(a)
+    s = 0
+    !$acc data copy(s)
+    s = s + 1
+!   !$acc end data
+    print *, "After nested data"
+!   !$acc end data
+
+    print *, "Program finished"
+    !CHECK: acc-data-statement.f90:
+    !CHECK-NOT:  [[ELINE2]]
+    !CHECK-SAME: [[ELINE3:[0-9]+]]:{{[0-9]+}}:
+    !CHECK-SAME: error: expected OpenACC end block directive
+    !CHECK-NEXT: contains
+    !CHECK-NEXT: ^ 
+    !CHECK-NEXT: in the context: OpenACC construct
+    !CHECK-NEXT: !$acc data copy(s)
+    !CHECK-NEXT: ^
+    !CHECK-NEXT: in the context: execution part
+    !CHECK-NEXT: !$acc data copy(a, b) create(c)
+    !CHECK-NEXT: ^
+    !CHECK-NEXT: error: expected OpenACC end block directive
+    !CHECK-NEXT: contains
+    !CHECK-NEXT: ^
+    !CHECK-NEXT: in the context: OpenACC construct
+    !CHECK-NEXT: !$acc data copy(s)
+    !CHECK-NEXT: ^
+    !CHECK-NEXT: in the context: OpenACC construct
+    !CHECK-NEXT: !$acc data copyin(a)
+    !CHECK-NEXT: ^
+    !CHECK-NEXT: error: expected OpenACC end block directive
+    !CHECK-NEXT: contains
+    !CHECK-NEXT: ^
+    !CHECK-NEXT: in the context: OpenACC construct
+    !CHECK-NEXT: !$acc data copyin(a)
+    !CHECK-NEXT: ^
+    !CHECK-NEXT: in the context: execution part
+    !CHECK-NEXT: !$acc data copy(a, b) create(c)
+    !CHECK-NEXT: ^
+    !CHECK-NEXT: error: expected OpenACC end block directive
+    !CHECK-NEXT: contains
+    !CHECK-NEXT: ^
+    !CHECK-NEXT: in the context: OpenACC construct
+    !CHECK-NEXT: !$acc data copy(s)
+    !CHECK-NEXT: ^
+    !CHECK-NEXT: in the context: OpenACC construct
+    !CHECK-NEXT: !$acc data copy(a, b) create(d)
+    !CHECK-NEXT: ^
+    !CHECK-NEXT: error: expected OpenACC end block directive
+    !CHECK-NEXT: contains
+    !CHECK-NEXT: ^
+    !CHECK-NEXT: in the context: OpenACC construct
+    !CHECK-NEXT: !$acc data copy(s)
+    !CHECK-NEXT: ^
+    !CHECK-NEXT: in the context: OpenACC construct
+    !CHECK-NEXT: !$acc data copyin(a)
+    !CHECK-NEXT: ^
+    !CHECK-NEXT: error: expected OpenACC end block directive
+    !CHECK-NEXT: contains
+    !CHECK-NEXT: ^ 
+    !CHECK-NEXT: in the context: OpenACC construct
+    !CHECK-NEXT: !$acc data copyin(a)
+    !CHECK-NEXT: ^
+    !CHECK-NEXT: in the context: OpenACC construct
+    !CHECK-NEXT: !$acc data copy(a, b) create(d)
+    !CHECK-NEXT: ^
+    !CHECK-NEXT: error: expected OpenACC end block directive
+    !CHECK-NEXT: contains
+    !CHECK-NEXT: ^ 
+    !CHECK-NEXT: in the context: OpenACC construct
+    !CHECK-NEXT: !$acc data copy(a, b) create(d)
+    !CHECK-NEXT: ^
+    !CHECK-NEXT: in the context: execution part
+    !CHECK-NEXT: !$acc data copy(a, b) create(c)
+    !CHECK-NEXT: ^
+contains
+    subroutine positive_process_array(x)
+        integer, intent(inout) :: x(:)
+        
+        ! Data construct in subroutine
+        !$acc data copy(x)
+        x = x + 1
+        !$acc end data
+        print *, "Subroutine finished"
+    end subroutine
+
+    function positive_compute_sum(x) result(total)
+        integer, intent(in) :: x(:)
+        integer :: total
+        
+        ! Data construct in function
+        !$acc data copyin(x) copy(total)
+        total = sum(x)
+        !$acc end data
+        print *, "Function finished"
+    end function
+    
+    subroutine negative_process_array(x)
+        integer, intent(inout) :: x(:)
+        
+        ! Data construct in subroutine
+        !$acc data copy(x)
+        x = x + 1
+!       !$acc end data
+        print *, "Subroutine finished"
+        !CHECK: error: expected OpenACC directive
+        !CHECK-NEXT: !$acc data copy(x)
+        !CHECK-NEXT: ^ 
+        !CHECK-NEXT: in the context: specification construct
+        !CHECK-NEXT: !$acc data copy(x)
+        !CHECK-NEXT: ^
+        !CHECK-NEXT: in the context: specification part
+        !CHECK-NEXT: integer, intent(inout) :: x(:)
+        !CHECK-NEXT: ^
+    end subroutine
+
+    function negative_compute_sum(x) result(total)
+        integer, intent(in) :: x(:)
+        integer :: total
+        total = sum(x)
+        ! Data construct in function
+        !$acc data copyin(x) copy(total)
+        total = total + x
+!       !$acc end data
+        print *, "Function finished"
+        !CHECK: error: expected OpenACC end block directive
+        !CHECK-NEXT: end function
+        !CHECK-NEXT: ^ 
+        !CHECK-NEXT: in the context: OpenACC construct
+        !CHECK-NEXT: !$acc data copyin(x) copy(total)
+        !CHECK-NEXT: ^
+        !CHECK-NEXT: in the context: execution part
+        !CHECK-NEXT: total = sum(x)
+        !CHECK-NEXT: ^
+    end function
+end program acc_data_test
\ No newline at end of file
diff --git a/flang/test/Parser/acc.f b/flang/test/Parser/acc.f
new file mode 100644
index 0000000000000..b0c3927772568
--- /dev/null
+++ b/flang/test/Parser/acc.f
@@ -0,0 +1,96 @@
+! RUN: %flang_fc1 -fsyntax-only -fopenacc %s 2>&1
+C Test file for OpenACC directives in fixed-form Fortran
+      PROGRAM ACCTEST
+      IMPLICIT NONE
+      INTEGER :: N, I, J
+      PARAMETER (N=100)
+      REAL :: A(N), B(N), C(N), D(N)
+      REAL :: SUM
+
+C Initialize arrays
+      DO I = 1, N
+         A(I) = I * 1.0
+         B(I) = I * 2.0
+         C(I) = 0.0
+         D(I) = 1.0
+      END DO
+
+C Basic data construct using C$ACC
+C$ACC DATA COPYIN(A,B) COPYOUT(C)
+      DO I = 1, N
+         C(I) = A(I) + B(I)
+      END DO
+C$ACC END DATA
+
+* Parallel construct with loop using *$ACC
+*$ACC PARALLEL PRESENT(A,B,C)
+*$ACC LOOP
+      DO I = 1, N
+         C(I) = C(I) * 2.0
+      END DO
+*$ACC END PARALLEL
+
+C Nested loops with collapse - C$ACC style
+C$ACC PARALLEL LOOP COLLAPSE(2)
+      DO I = 1, N
+         DO J = 1, N
+            A(J) = A(J) + B(J)
+         END DO
+      END DO
+C$ACC END PARALLEL LOOP
+
+* Combined parallel loop with reduction - *$ACC style
+      SUM = 0.0
+*$ACC PARALLEL LOOP REDUCTION(+:SUM)
+      DO I = 1, N
+         SUM = SUM + C(I)
+      END DO
+*$ACC END PARALLEL LOOP
+
+C Kernels construct - C$ACC with continuation
+C$ACC KERNELS 
+C$ACC+ COPYOUT(A)
+      DO I = 1, N
+         A(I) = A(I) * 2.0
+      END DO
+C$ACC END KERNELS
+
+* Data construct with update - *$ACC with continuation
+*$ACC DATA COPY(B)
+*$ACC+ PRESENT(D)
+      B(1) = 999.0
+*$ACC UPDATE HOST(B(1:1))
+      PRINT *, 'B(1) = ', B(1)
+*$ACC END DATA
+
+C Mixed style directives in nested constructs
+C$ACC DATA COPY(A,B,C)
+*$ACC PARALLEL LOOP
+      DO I = 1, N
+         A(I) = B(I) + C(I)
+      END DO
+*$ACC END PARALLEL LOOP
+C$ACC END DATA
+
+* Subroutine call within data region - *$ACC style
+*$ACC DATA COPY(A,B,C)
+      CALL SUB1(A, B, C, N)
+*$ACC END DATA
+
+      PRINT *, 'Sum = ', SUM
+      END PROGRAM
+
+C Subroutine with mixed ACC directive styles
+      SUBROUTINE SUB1(X, Y, Z, M)
+      INTEGER M, I
+      REAL X(M), Y(M), Z(M)
+
+*$ACC PARALLEL PRESENT(X,Y)
+C$ACC LOOP PRIVATE(I)
+      DO I = 1, M
+         Z(I) = X(I) + Y(I)
+      END DO
+C$ACC END LOOP
+*$ACC END PARALLEL
+      RETURN
+      END SUBROUTINE 
\ No newline at end of file

``````````

</details>

author_association: MEMBER
user: login: github-actions[bot]
created_at: 2025-03-13T00:11:02Z
body: <!--LLVM CODE FORMAT COMMENT: {clang-format}-->

:white_check_mark: With the latest revision this PR passed the C/C++ code formatter.
author_association: NONE