[flang-commits] [flang] [Flang]Fix Free-Form Token Separation: Add error for identifiers without required separator (PR #175726)

via flang-commits flang-commits at lists.llvm.org
Wed Jan 21 00:06:04 PST 2026


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-flang-parser

Author: Urvi Rav (ravurvi20)

<details>
<summary>Changes</summary>

This patch updates Flang’s free-form parsing to correctly enforce token separation rules as specified in Section 6.3.2 [Fortran-doc](https://j3-fortran.org/doc/year/25/25-007r1.pdf). In free-form source, a blank is required to separate keywords from adjacent names/constants. The current behavior incorrectly permits constructs such as:
`real_a(10), integerb(b)`

With this change, Flang now emits a diagnostic to match the expected behavior.

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


5 Files Affected:

- (modified) flang/lib/Parser/Fortran-parsers.cpp (+10-10) 
- (modified) flang/lib/Parser/token-parsers.h (+30-5) 
- (added) flang/test/Parser/type-keyword-space.f90 (+31) 
- (modified) flang/test/Semantics/Inputs/modfile73-a.f90 (+4-4) 
- (modified) flang/test/Semantics/Inputs/modfile73-b.f90 (+2-2) 


``````````diff
diff --git a/flang/lib/Parser/Fortran-parsers.cpp b/flang/lib/Parser/Fortran-parsers.cpp
index 988db5450abc9..ae8b9baa7b587 100644
--- a/flang/lib/Parser/Fortran-parsers.cpp
+++ b/flang/lib/Parser/Fortran-parsers.cpp
@@ -205,24 +205,24 @@ TYPE_CONTEXT_PARSER("declaration type spec"_en_US,
 // Extensions: DOUBLE COMPLEX, BYTE
 TYPE_CONTEXT_PARSER("intrinsic type spec"_en_US,
     first(construct<IntrinsicTypeSpec>(integerTypeSpec),
-        construct<IntrinsicTypeSpec>(
-            construct<IntrinsicTypeSpec::Real>("REAL" >> maybe(kindSelector))),
+        construct<IntrinsicTypeSpec>(construct<IntrinsicTypeSpec::Real>(
+            "REAL"_kw >> maybe(kindSelector))),
         construct<IntrinsicTypeSpec>("DOUBLE PRECISION" >>
             construct<IntrinsicTypeSpec::DoublePrecision>()),
         construct<IntrinsicTypeSpec>(construct<IntrinsicTypeSpec::Complex>(
-            "COMPLEX" >> maybe(kindSelector))),
+            "COMPLEX"_kw >> maybe(kindSelector))),
         construct<IntrinsicTypeSpec>(construct<IntrinsicTypeSpec::Character>(
-            "CHARACTER" >> maybe(Parser<CharSelector>{}))),
+            "CHARACTER"_kw >> maybe(Parser<CharSelector>{}))),
         construct<IntrinsicTypeSpec>(construct<IntrinsicTypeSpec::Logical>(
-            "LOGICAL" >> maybe(kindSelector))),
+            "LOGICAL"_kw >> maybe(kindSelector))),
         construct<IntrinsicTypeSpec>(unsignedTypeSpec),
         extension<LanguageFeature::DoubleComplex>(
             "nonstandard usage: DOUBLE COMPLEX"_port_en_US,
             construct<IntrinsicTypeSpec>("DOUBLE COMPLEX"_sptok >>
                 construct<IntrinsicTypeSpec::DoubleComplex>())),
         extension<LanguageFeature::Byte>("nonstandard usage: BYTE"_port_en_US,
-            construct<IntrinsicTypeSpec>(construct<IntegerTypeSpec>(
-                "BYTE" >> construct<std::optional<KindSelector>>(pure(1)))))))
+            construct<IntrinsicTypeSpec>(construct<IntegerTypeSpec>("BYTE"_kw >>
+                construct<std::optional<KindSelector>>(pure(1)))))))
 
 // Extension: Vector type
 // VECTOR(intrinsic-type-spec) | __VECTOR_PAIR | __VECTOR_QUAD
@@ -241,13 +241,13 @@ TYPE_PARSER(construct<IntrinsicVectorTypeSpec>("VECTOR" >>
     parenthesized(construct<VectorElementType>(integerTypeSpec) ||
         construct<VectorElementType>(unsignedTypeSpec) ||
         construct<VectorElementType>(construct<IntrinsicTypeSpec::Real>(
-            "REAL" >> maybe(kindSelector))))))
+            "REAL"_kw >> maybe(kindSelector))))))
 
 // UNSIGNED type
-TYPE_PARSER(construct<UnsignedTypeSpec>("UNSIGNED" >> maybe(kindSelector)))
+TYPE_PARSER(construct<UnsignedTypeSpec>("UNSIGNED"_kw >> maybe(kindSelector)))
 
 // R705 integer-type-spec -> INTEGER [kind-selector]
-TYPE_PARSER(construct<IntegerTypeSpec>("INTEGER" >> maybe(kindSelector)))
+TYPE_PARSER(construct<IntegerTypeSpec>("INTEGER"_kw >> maybe(kindSelector)))
 
 // R706 kind-selector -> ( [KIND =] scalar-int-constant-expr )
 // Legacy extension: kind-selector -> * digit-string
diff --git a/flang/lib/Parser/token-parsers.h b/flang/lib/Parser/token-parsers.h
index 3e0c59b89d964..11941d0a638f5 100644
--- a/flang/lib/Parser/token-parsers.h
+++ b/flang/lib/Parser/token-parsers.h
@@ -85,7 +85,12 @@ inline void MissingSpace(ParseState &state) {
   }
 }
 
-struct SpaceCheck {
+// SpaceCheck verifies proper spacing after keywords.
+// When IsTypeKeyword is true, emits an error for type keywords (REAL, INTEGER,
+// etc.) that cannot be followed by an identifier without a separator.
+// When IsTypeKeyword is false, calls MissingSpace which emits a portability
+// warning for general keywords that can be combined (e.g., ENDSUBROUTINE).
+template <bool IsTypeKeyword = false> struct SpaceCheck {
   using resultType = Success;
   constexpr SpaceCheck() {}
   static std::optional<Success> Parse(ParseState &state) {
@@ -96,13 +101,21 @@ struct SpaceCheck {
         return space.Parse(state);
       }
       if (IsLegalInIdentifier(ch)) {
-        MissingSpace(state);
+        if constexpr (IsTypeKeyword) {
+          if (!state.inFixedForm()) {
+            state.Say(
+                "A blank space or a separator (::) is required after the type keyword"_err_en_US);
+          }
+        } else {
+          MissingSpace(state);
+        }
       }
     }
     return {Success{}};
   }
 };
-constexpr SpaceCheck spaceCheck;
+constexpr SpaceCheck<> spaceCheck;
+constexpr SpaceCheck<true> typeKeywordSpaceCheck;
 
 // Matches a token string.  Spaces in the token string denote where
 // spaces may appear in the source; they can be made mandatory for
@@ -115,7 +128,10 @@ constexpr SpaceCheck spaceCheck;
 // when a string literal appears before the sequencing operator >> or
 // after the sequencing operator /.  The literal "..."_id parses a
 // token that cannot be a prefix of a longer identifier.
-template <bool MandatoryFreeFormSpace = false, bool MustBeComplete = false>
+// The literal "..."_kw parses a type keyword that requires a space
+// before an identifier in free form (emits error).
+template <bool MandatoryFreeFormSpace = false, bool MustBeComplete = false,
+    bool IsTypeKeyword = false>
 class TokenStringMatch {
 public:
   using resultType = Success;
@@ -168,7 +184,11 @@ class TokenStringMatch {
     }
     state.set_anyTokenMatched();
     if (IsLegalInIdentifier(p[-1])) {
-      return spaceCheck.Parse(state);
+      if constexpr (IsTypeKeyword) {
+        return typeKeywordSpaceCheck.Parse(state);
+      } else {
+        return spaceCheck.Parse(state);
+      }
     } else {
       return space.Parse(state);
     }
@@ -193,6 +213,11 @@ constexpr TokenStringMatch<false, true> operator""_id(
   return {str, n};
 }
 
+constexpr TokenStringMatch<false, false, true> operator""_kw(
+    const char str[], std::size_t n) {
+  return {str, n};
+}
+
 template <class PA>
 inline constexpr std::enable_if_t<std::is_class_v<PA>,
     SequenceParser<TokenStringMatch<>, PA>>
diff --git a/flang/test/Parser/type-keyword-space.f90 b/flang/test/Parser/type-keyword-space.f90
new file mode 100644
index 0000000000000..dc46a5dedca58
--- /dev/null
+++ b/flang/test/Parser/type-keyword-space.f90
@@ -0,0 +1,31 @@
+! RUN: not %flang_fc1 -fsyntax-only %s 2>&1 | FileCheck %s
+
+! CHECK: error: A blank space or a separator (::) is required after the type keyword
+! CHECK: reala
+program test_real
+  reala(10)
+end program
+
+! CHECK: error: A blank space or a separator (::) is required after the type keyword
+! CHECK: integerb
+subroutine test_integer
+  integerb(20)
+end subroutine
+
+! CHECK: error: A blank space or a separator (::) is required after the type keyword  
+! CHECK: logicalflag
+subroutine test_logical
+  logicalflag
+end subroutine
+
+! CHECK: error: A blank space or a separator (::) is required after the type keyword
+! CHECK: complexz
+subroutine test_complex
+  complexz
+end subroutine
+
+! CHECK: error: A blank space or a separator (::) is required after the type keyword
+! CHECK: characterstr
+subroutine test_character
+  characterstr
+end subroutine
diff --git a/flang/test/Semantics/Inputs/modfile73-a.f90 b/flang/test/Semantics/Inputs/modfile73-a.f90
index ea247c98cdff8..7e20feefb7421 100644
--- a/flang/test/Semantics/Inputs/modfile73-a.f90
+++ b/flang/test/Semantics/Inputs/modfile73-a.f90
@@ -1,7 +1,7 @@
 MODULE modfile73a
 PUBLIC re_alloc, defaults
-integersp
-integerselected_real_kind0
+integer sp
+integer selected_real_kind0
 integer:: i8b = selected_int_kind(8)
 interface
    subroutine alloc_error_report_interf(str,code)
@@ -13,12 +13,12 @@ subroutine alloc_memory_event_interf(bytes,name)
 procedure()alloc_memory_event
   interface de_alloc
   end interface
-  charactercharacter, DEFAULT_ROUTINE
+  character character, DEFAULT_ROUTINE
   type allocDefaults
     logical copy
     logical shrink
     integer imin
-    characterroutine
+    character routine
   end type
   type(allocDefaults)DEFAULT
   integer IERR
diff --git a/flang/test/Semantics/Inputs/modfile73-b.f90 b/flang/test/Semantics/Inputs/modfile73-b.f90
index 743299599c7bd..414f4fb141a29 100644
--- a/flang/test/Semantics/Inputs/modfile73-b.f90
+++ b/flang/test/Semantics/Inputs/modfile73-b.f90
@@ -1,6 +1,6 @@
 module modfile73ba
   use modfile73a, only: re_alloc, de_alloc
-  charactermod_name
+  character mod_name
   type lData1D
      integer refCount
      character   id
@@ -42,7 +42,7 @@ subroutine delete_Data(a1d_data)
 
 module modfile73bb
   use modfile73a, only: re_alloc, de_alloc
-  charactermod_name
+  character mod_name
   type lData1D
      integer refCount
      character   id

``````````

</details>


https://github.com/llvm/llvm-project/pull/175726


More information about the flang-commits mailing list