[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