[flang][OpenMP] Frontend support for BEGIN/END METADIRECTIVE (#194402)

This implements parsing of BEGIN/END METADIRECTIVE, plus a minimal
semantic check for the association of a directive in a WHEN/OTHERWISE
clauses.

The same semantic checks for the context selectors apply here as in the
case of a standalone METADIRECTIVE.
This commit is contained in:
Krzysztof Parzyszek
2026-04-28 15:55:16 -05:00
committed by GitHub
parent b7d6438450
commit 2fce8c9d9e
12 changed files with 287 additions and 47 deletions

View File

@@ -586,6 +586,7 @@ public:
NODE_ENUM(OmpDefaultmapClause, ImplicitBehavior)
NODE(parser, OmpDeleteModifier)
NODE_ENUM(OmpDeleteModifier, Value)
NODE(parser, OmpDelimitedMetadirectiveDirective)
NODE(parser, OmpDependClause)
NODE(OmpDependClause, TaskDep)
NODE(OmpDependClause::TaskDep, Modifier)

View File

@@ -5082,7 +5082,7 @@ struct OmpClauseList {
// --- Directives and constructs
struct OmpDirectiveSpecification {
ENUM_CLASS(Flag, DeprecatedSyntax, CrossesLabelDo)
ENUM_CLASS(Flag, DeprecatedSyntax, CrossesLabelDo, ExplicitBegin)
using Flags = common::EnumSet<Flag, Flag_enumSize>;
TUPLE_CLASS_BOILERPLATE(OmpDirectiveSpecification);
@@ -5133,6 +5133,11 @@ struct OmpMetadirectiveDirective {
OmpMetadirectiveDirective, OmpDirectiveSpecification);
};
struct OmpDelimitedMetadirectiveDirective : public OmpBlockConstruct {
INHERITED_TUPLE_CLASS_BOILERPLATE(
OmpDelimitedMetadirectiveDirective, OmpBlockConstruct);
};
// Ref: [5.1:89-90], [5.2:216]
//
// nothing-directive ->
@@ -5468,7 +5473,7 @@ struct OpenMPConstruct {
OpenMPSectionConstruct, OpenMPLoopConstruct, OmpBlockConstruct,
OpenMPAtomicConstruct, OmpAllocateDirective, OpenMPDispatchConstruct,
OpenMPUtilityConstruct, OpenMPAllocatorsConstruct, OpenMPAssumeConstruct,
OpenMPCriticalConstruct>
OpenMPCriticalConstruct, OmpDelimitedMetadirectiveDirective>
u;
};

View File

@@ -4544,6 +4544,14 @@ static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
lowerAtomic(converter, symTable, semaCtx, eval, construct);
}
static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
semantics::SemanticsContext &semaCtx,
lower::pft::Evaluation &eval,
const parser::OmpDelimitedMetadirectiveDirective &meta) {
TODO(converter.getCurrentLocation(),
"OpenMP BEGIN/END METADIRECTIVE lowering");
}
static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
semantics::SemanticsContext &semaCtx,
lower::pft::Evaluation &eval,

View File

@@ -1924,16 +1924,15 @@ TYPE_PARSER(construct<OmpMetadirectiveDirective>(
IsDirective(llvm::omp::Directive::OMPD_metadirective)) >=
OmpDirectiveSpecificationParser{}))
struct OmpBeginDirectiveParser {
struct OmpDirectiveParser {
using resultType = OmpDirectiveSpecification;
constexpr OmpBeginDirectiveParser(DirectiveSet dirs) : dirs_(dirs) {}
constexpr OmpBeginDirectiveParser(llvm::omp::Directive dir) {
dirs_.set(llvm::to_underlying(dir));
}
constexpr OmpDirectiveParser(DirectiveSet dirs) : dirs_(dirs) {}
constexpr OmpDirectiveParser(llvm::omp::Directive dir)
: dirs_({static_cast<unsigned>(llvm::to_underlying(dir))}) {}
std::optional<resultType> Parse(ParseState &state) const {
auto &&p{predicated(Parser<OmpDirectiveName>{}, IsMemberOf(dirs_)) >=
auto p{predicated(Parser<OmpDirectiveName>{}, IsMemberOf(dirs_)) >=
OmpDirectiveSpecificationParser{}};
return p.Parse(state);
}
@@ -1942,18 +1941,61 @@ private:
DirectiveSet dirs_;
};
// Parse the directive that begins a construct. In some cases the directive
// has to be preceded with an explicit "BEGIN", in other cases the begin is
// assumed to be implicit. This parser is invoked after the OpenMP sentinel
// has been consumed.
// Note: Even if OMPD_begin_somename exists, the directive(s) to parse should
// use the non-begin id, i.e. OMPD_somename.
struct OmpBeginDirectiveParser {
using resultType = OmpDirectiveSpecification;
constexpr OmpBeginDirectiveParser(DirectiveSet dirs, bool implicit = true)
: dparser_(dirs), implicit_(implicit) {}
constexpr OmpBeginDirectiveParser(
llvm::omp::Directive dir, bool implicit = true)
: dparser_(dir), implicit_(implicit) {}
std::optional<resultType> Parse(ParseState &state) const {
if (implicit_) {
return dparser_.Parse(state);
}
if (auto &&beginToken{verbatim("BEGIN"_sptok).Parse(state)}) {
if (auto &&dirSpec{dparser_.Parse(state)}) {
// Extend the "source" on both the OmpDirectiveName and the
// OmpDirectiveNameSpecification.
CharBlock &nameSource{std::get<OmpDirectiveName>(dirSpec->t).source};
nameSource.ExtendToCover(beginToken->source);
dirSpec->source.ExtendToCover(beginToken->source);
std::get<OmpDirectiveSpecification::Flags>(dirSpec->t)
.set(OmpDirectiveSpecification::Flag::ExplicitBegin);
return std::move(*dirSpec);
}
}
return std::nullopt;
}
private:
OmpDirectiveParser dparser_;
bool implicit_;
};
// Parse the directive that end a construct. In all cases the directive
// must be preceded with an explicit "END". This parser is invoked directly
// from other construct parsers, so it must handle the OpenMP sentinel.
// Note: Even if OMPD_end_somename exists, the directive(s) to parse should
// use the non-end id, i.e. OMPD_somename.
struct OmpEndDirectiveParser {
using resultType = OmpDirectiveSpecification;
constexpr OmpEndDirectiveParser(DirectiveSet dirs) : dirs_(dirs) {}
constexpr OmpEndDirectiveParser(llvm::omp::Directive dir) {
dirs_.set(llvm::to_underlying(dir));
}
constexpr OmpEndDirectiveParser(DirectiveSet dirs) : dparser_(dirs) {}
constexpr OmpEndDirectiveParser(llvm::omp::Directive dir) : dparser_(dir) {}
std::optional<resultType> Parse(ParseState &state) const {
if (startOmpLine.Parse(state)) {
if (auto endToken{verbatim("END"_sptok).Parse(state)}) {
if (auto &&spec{OmpBeginDirectiveParser(dirs_).Parse(state)}) {
if (auto &&spec{dparser_.Parse(state)}) {
// Extend the "source" on both the OmpDirectiveName and the
// OmpDirectiveNameSpecification.
CharBlock &nameSource{std::get<OmpDirectiveName>(spec->t).source};
@@ -1967,16 +2009,18 @@ struct OmpEndDirectiveParser {
}
private:
DirectiveSet dirs_;
OmpDirectiveParser dparser_;
};
struct OmpStatementConstructParser {
using resultType = OmpBlockConstruct;
constexpr OmpStatementConstructParser(llvm::omp::Directive dir) : dir_(dir) {}
constexpr OmpStatementConstructParser(
llvm::omp::Directive dir, bool implicit = true)
: dir_(dir), implicit_(implicit) {}
std::optional<resultType> Parse(ParseState &state) const {
if (auto begin{OmpBeginDirectiveParser(dir_).Parse(state)}) {
if (auto begin{OmpBeginDirectiveParser(dir_, implicit_).Parse(state)}) {
Block body;
if (auto stmt{attempt(validEPC).Parse(state)}) {
body.emplace_back(std::move(*stmt));
@@ -1994,15 +2038,18 @@ struct OmpStatementConstructParser {
private:
llvm::omp::Directive dir_;
bool implicit_;
};
struct OmpBlockConstructParser {
using resultType = OmpBlockConstruct;
constexpr OmpBlockConstructParser(llvm::omp::Directive dir) : dir_(dir) {}
constexpr OmpBlockConstructParser(
llvm::omp::Directive dir, bool implicit = true)
: dir_(dir), implicit_(implicit) {}
std::optional<resultType> Parse(ParseState &state) const {
if (auto &&begin{OmpBeginDirectiveParser(dir_).Parse(state)}) {
if (auto &&begin{OmpBeginDirectiveParser(dir_, implicit_).Parse(state)}) {
if (IsStandaloneOrdered(*begin)) {
return std::nullopt;
}
@@ -2030,19 +2077,21 @@ struct OmpBlockConstructParser {
private:
llvm::omp::Directive dir_;
bool implicit_;
};
struct OmpLoopConstructParser {
using resultType = OpenMPLoopConstruct;
constexpr OmpLoopConstructParser(DirectiveSet dirs) : dirs_(dirs) {}
constexpr OmpLoopConstructParser(DirectiveSet dirs, bool implicit = true)
: dirs_(dirs), implicit_(implicit) {}
std::optional<resultType> Parse(ParseState &state) const {
auto ompLoopConstruct{asBlock(predicated(executionPartConstruct,
[](auto &epc) { return Unwrap<OpenMPLoopConstruct>(epc); }))};
auto loopItem{LoopNestParser{} || ompLoopConstruct};
if (auto &&begin{OmpBeginDirectiveParser(dirs_).Parse(state)}) {
if (auto &&begin{OmpBeginDirectiveParser(dirs_, implicit_).Parse(state)}) {
auto loopDir{begin->DirId()};
auto assoc{llvm::omp::getDirectiveAssociation(loopDir)};
if (assoc == llvm::omp::Association::LoopNest) {
@@ -2078,6 +2127,7 @@ struct OmpLoopConstructParser {
private:
DirectiveSet dirs_;
bool implicit_;
};
struct OmpDeclarativeAllocateParser {
@@ -2457,6 +2507,10 @@ TYPE_PARSER( //
MakeBlockConstruct(llvm::omp::Directive::OMPD_workdistribute))
#undef MakeBlockConstruct
TYPE_PARSER(sourced(
construct<OmpDelimitedMetadirectiveDirective>(OmpBlockConstructParser{
llvm::omp::Directive::OMPD_metadirective, /*implicit=*/false})))
// OMP SECTIONS Directive
static constexpr DirectiveSet GetSectionsDirectives() {
using Directive = llvm::omp::Directive;
@@ -2493,8 +2547,12 @@ static bool IsExecutionPart(const OmpDirectiveName &name) {
return name.IsExecutionPart();
}
TYPE_PARSER(construct<OpenMPExecDirective>(
startOmpLine >> predicated(Parser<OmpDirectiveName>{}, IsExecutionPart)))
TYPE_PARSER(construct<OpenMPExecDirective>(startOmpLine >>
first( //
predicated(Parser<OmpDirectiveName>{}, IsExecutionPart),
// begin/end metadirective
predicated("BEGIN"_sptok >> Parser<OmpDirectiveName>{},
IsDirective(llvm::omp::Directive::OMPD_metadirective)))))
TYPE_CONTEXT_PARSER("OpenMP construct"_en_US,
startOmpLine >>
@@ -2512,7 +2570,9 @@ TYPE_CONTEXT_PARSER("OpenMP construct"_en_US,
construct<OpenMPConstruct>(Parser<OpenMPDispatchConstruct>{}),
construct<OpenMPConstruct>(Parser<OpenMPAllocatorsConstruct>{}),
construct<OpenMPConstruct>(Parser<OpenMPAssumeConstruct>{}),
construct<OpenMPConstruct>(Parser<OpenMPCriticalConstruct>{}))))
construct<OpenMPConstruct>(Parser<OpenMPCriticalConstruct>{}),
construct<OpenMPConstruct>(
Parser<OmpDelimitedMetadirectiveDirective>{}))))
static constexpr DirectiveSet GetLoopDirectives() {
using Directive = llvm::omp::Directive;
@@ -2571,7 +2631,7 @@ static constexpr DirectiveSet GetAllDirectives() { //
TYPE_PARSER(construct<OpenMPMisplacedEndDirective>(
OmpEndDirectiveParser{GetAllDirectives()}))
TYPE_PARSER( //
startOmpLine >> sourced(construct<OpenMPInvalidDirective>(
!OmpDirectiveNameParser{} >> SkipTo<'\n'>{})))
TYPE_PARSER(startOmpLine >>
sourced(construct<OpenMPInvalidDirective>(
maybe("BEGIN"_sptok) >> !OmpDirectiveNameParser{} >> SkipTo<'\n'>{})))
} // namespace Fortran::parser

View File

@@ -2201,6 +2201,10 @@ public:
void Unparse(const OmpBeginDirective &x) {
BeginOpenMP();
Word("!$OMP ");
auto flags{std::get<OmpDirectiveSpecification::Flags>(x.t)};
if (flags.test(OmpDirectiveSpecification::Flag::ExplicitBegin)) {
Word("BEGIN ");
}
Walk(static_cast<const OmpDirectiveSpecification &>(x));
Put("\n");
EndOpenMP();

View File

@@ -534,6 +534,45 @@ void OmpStructureChecker::CheckTraitSimd(
}
}
void OmpStructureChecker::Enter(const parser::OmpDirectiveSpecification &x) {
// OmpDirectiveSpecification exists on its own only in clauses on
// METADIRECTIVE.
// In other cases it's a part of other constructs that handle directive
// context stack by themselves.
if (!GetDirectiveNest(MetadirectiveNest)) {
return;
}
llvm::omp::Directive dirId{x.DirId()};
if (const parser::OpenMPConstruct *meta{GetCurrentConstruct()}) {
if (parser::Unwrap<parser::OmpDelimitedMetadirectiveDirective>(meta->u)) {
unsigned version{context_.langOptions().OpenMPVersion};
switch (llvm::omp::getDirectiveAssociation(dirId)) {
case llvm::omp::Association::Block:
case llvm::omp::Association::LoopNest:
case llvm::omp::Association::LoopSeq:
break;
default:
if (dirId != llvm::omp::Directive::OMPD_nothing) {
context_.Say(x.DirName().source,
"A directive in BEGIN %s should have a corresponding end-directive"_err_en_US,
parser::omp::GetUpperName(
llvm::omp::Directive::OMPD_metadirective, version));
}
}
}
}
PushContextAndClauseSets(
std::get<parser::OmpDirectiveName>(x.t).source, dirId);
}
void OmpStructureChecker::Leave(const parser::OmpDirectiveSpecification &x) {
if (GetDirectiveNest(MetadirectiveNest)) {
dirContext_.pop_back();
}
}
void OmpStructureChecker::Enter(const parser::OmpMetadirectiveDirective &x) {
EnterDirectiveNest(MetadirectiveNest);
PushContextAndClauseSets(
@@ -545,4 +584,14 @@ void OmpStructureChecker::Leave(const parser::OmpMetadirectiveDirective &) {
dirContext_.pop_back();
}
void OmpStructureChecker::Enter(
const parser::OmpDelimitedMetadirectiveDirective &x) {
PushContextAndClauseSets(x.source, llvm::omp::Directive::OMPD_metadirective);
}
void OmpStructureChecker::Leave(
const parser::OmpDelimitedMetadirectiveDirective &) {
dirContext_.pop_back();
}
} // namespace Fortran::semantics

View File

@@ -864,22 +864,6 @@ void OmpStructureChecker::Enter(const parser::OmpClause::DynGroupprivate &x) {
OmpVerifyModifiers(x.v, llvm::omp::OMPC_dyn_groupprivate, source, context_);
}
void OmpStructureChecker::Enter(const parser::OmpDirectiveSpecification &x) {
// OmpDirectiveSpecification exists on its own only in METADIRECTIVE.
// In other cases it's a part of other constructs that handle directive
// context stack by themselves.
if (GetDirectiveNest(MetadirectiveNest)) {
PushContextAndClauseSets(
std::get<parser::OmpDirectiveName>(x.t).source, x.DirId());
}
}
void OmpStructureChecker::Leave(const parser::OmpDirectiveSpecification &) {
if (GetDirectiveNest(MetadirectiveNest)) {
dirContext_.pop_back();
}
}
template <typename Checker> struct DirectiveSpellingVisitor {
using Directive = llvm::omp::Directive;
@@ -1396,12 +1380,27 @@ void OmpStructureChecker::ChecksOnOrderedAsBlock() {
}
}
void OmpStructureChecker::Leave(const parser::OmpBeginDirective &) {
switch (GetContext().directive) {
void OmpStructureChecker::Enter(const parser::OmpBeginDirective &x) {
switch (x.DirId()) {
case llvm::omp::Directive::OMPD_metadirective:
// Delimited METADIRECTIVE
EnterDirectiveNest(MetadirectiveNest);
break;
default:
break;
}
}
void OmpStructureChecker::Leave(const parser::OmpBeginDirective &x) {
switch (x.DirId()) {
case llvm::omp::Directive::OMPD_ordered:
// [5.1] 2.19.9 Ordered Construct Restriction
ChecksOnOrderedAsBlock();
break;
case llvm::omp::Directive::OMPD_metadirective:
// Delimited METADIRECTIVE
ExitDirectiveNest(MetadirectiveNest);
break;
default:
break;
}
@@ -3433,7 +3432,7 @@ void OmpStructureChecker::Leave(const parser::OmpEndDirective &x) {
// 2. Checks on clauses which fall under 'struct OmpClause' from parse-tree.h.
// 3. Checks on clauses which are not in 'struct OmpClause' from parse-tree.h.
void OmpStructureChecker::Leave(const parser::OmpClauseList &) {
void OmpStructureChecker::Leave(const parser::OmpClauseList &x) {
unsigned version{context_.langOptions().OpenMPVersion};
// 2.7.1 Loop Construct Restriction

View File

@@ -113,6 +113,7 @@ public:
void Leave(const parser::OpenMPInteropConstruct &);
void Enter(const parser::OmpBlockConstruct &);
void Leave(const parser::OmpBlockConstruct &);
void Enter(const parser::OmpBeginDirective &);
void Leave(const parser::OmpBeginDirective &);
void Enter(const parser::OmpEndDirective &);
void Leave(const parser::OmpEndDirective &);
@@ -175,6 +176,8 @@ public:
void Enter(const parser::OmpMetadirectiveDirective &);
void Leave(const parser::OmpMetadirectiveDirective &);
void Enter(const parser::OmpDelimitedMetadirectiveDirective &);
void Leave(const parser::OmpDelimitedMetadirectiveDirective &);
void Enter(const parser::OmpContextSelector &);
void Leave(const parser::OmpContextSelector &);

View File

@@ -469,8 +469,19 @@ public:
}
template <typename A> void Walk(const A &x) { parser::Walk(x, *this); }
template <typename A> bool Pre(const A &) { return true; }
template <typename A> void Post(const A &) {}
// Normally the catch-all Pre/Post functions are templates taking
// "const T &". For a class D derived from B, and an explicit overload
// of Pre(const B &), a call to Pre(D) will select the template instead
// of the base clase overload.
// Force user-defined conversion from any const-reference, to make sure
// that the Pre(AbsorbAnyReference) and Post(AbsorbAnyReference) overloads
// will be worse than derived-to-base conversions. This will, for example,
// invoke Pre(const OmpBlockConstruct &) for directives derived from it.
struct AbsorbAnyReference {
template <typename T> AbsorbAnyReference(const T &) {}
};
bool Pre(AbsorbAnyReference) { return true; }
void Post(AbsorbAnyReference) {}
bool Pre(const parser::SpecificationPart &) {
partStack_.push_back(PartKind::SpecificationPart);

View File

@@ -0,0 +1,12 @@
! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %s 2>&1 | FileCheck %s
! CHECK: not yet implemented: OpenMP BEGIN/END METADIRECTIVE lowering
subroutine test_begin_metadirective
integer :: x
x = 0
!$omp begin metadirective &
!$omp & when(implementation={vendor(llvm)}: parallel) &
!$omp & otherwise(nothing)
x = 1
!$omp end metadirective
end subroutine

View File

@@ -0,0 +1,80 @@
!RUN: %flang_fc1 -fdebug-unparse -fopenmp -fopenmp-version=52 %s | FileCheck --ignore-case --check-prefix="UNPARSE" %s
!RUN: %flang_fc1 -fdebug-dump-parse-tree -fopenmp -fopenmp-version=52 %s | FileCheck --check-prefix="PARSE-TREE" %s
subroutine f00
!$omp begin metadirective
continue
!$omp end metadirective
end
!UNPARSE: SUBROUTINE f00
!UNPARSE: !$OMP BEGIN METADIRECTIVE
!UNPARSE: CONTINUE
!UNPARSE: !$OMP END METADIRECTIVE
!UNPARSE: END SUBROUTINE
!PARSE-TREE: ExecutionPartConstruct -> ExecutableConstruct -> OpenMPConstruct -> OmpDelimitedMetadirectiveDirective
!PARSE-TREE: | OmpBeginDirective
!PARSE-TREE: | | OmpDirectiveName -> llvm::omp::Directive = metadirective
!PARSE-TREE: | | OmpClauseList ->
!PARSE-TREE: | | Flags = {ExplicitBegin}
!PARSE-TREE: | Block
!PARSE-TREE: | OmpEndDirective
!PARSE-TREE: | | OmpDirectiveName -> llvm::omp::Directive = metadirective
!PARSE-TREE: | | OmpClauseList ->
!PARSE-TREE: | | Flags = {}
subroutine f01(s)
integer :: i
integer :: s
s = 0
!$omp begin metadirective &
!$omp & when(user={condition(.true.)}: parallel do reduction(+: s)) &
!$omp & otherwise(do)
do i = 1, 10
s = s + i
end do
!$omp end metadirective
end
!UNPARSE: SUBROUTINE f01 (s)
!UNPARSE: INTEGER i
!UNPARSE: INTEGER s
!UNPARSE: s=0_4
!UNPARSE: !$OMP BEGIN METADIRECTIVE WHEN(USER={CONDITION(.true._4)}: PARALLEL DO REDUCTION(+: s)&
!UNPARSE: !$OMP&) OTHERWISE(DO)
!UNPARSE: DO i=1_4,10_4
!UNPARSE: s=s+i
!UNPARSE: END DO
!UNPARSE: !$OMP END METADIRECTIVE
!UNPARSE: END SUBROUTINE
!PARSE-TREE: ExecutionPartConstruct -> ExecutableConstruct -> OpenMPConstruct -> OmpDelimitedMetadirectiveDirective
!PARSE-TREE: | OmpBeginDirective
!PARSE-TREE: | | OmpDirectiveName -> llvm::omp::Directive = metadirective
!PARSE-TREE: | | OmpClauseList -> OmpClause -> When -> OmpWhenClause
!PARSE-TREE: | | | Modifier -> OmpContextSelectorSpecification -> OmpTraitSetSelector
!PARSE-TREE: | | | | OmpTraitSetSelectorName -> Value = User
!PARSE-TREE: | | | | OmpTraitSelector
!PARSE-TREE: | | | | | OmpTraitSelectorName -> Value = Condition
!PARSE-TREE: | | | | | Properties
!PARSE-TREE: | | | | | | OmpTraitProperty -> Scalar -> Expr = '.true._4'
!PARSE-TREE: | | | | | | | LiteralConstant -> LogicalLiteralConstant
!PARSE-TREE: | | | | | | | | bool = 'true'
!PARSE-TREE: | | | OmpDirectiveSpecification
!PARSE-TREE: | | | | OmpDirectiveName -> llvm::omp::Directive = parallel do
!PARSE-TREE: | | | | OmpClauseList -> OmpClause -> Reduction -> OmpReductionClause
!PARSE-TREE: | | | | | Modifier -> OmpReductionIdentifier -> DefinedOperator -> IntrinsicOperator = Add
!PARSE-TREE: | | | | | OmpObjectList -> OmpObject -> Designator -> DataRef -> Name = 's'
!PARSE-TREE: | | | | Flags = {}
!PARSE-TREE: | | OmpClause -> Otherwise -> OmpOtherwiseClause -> OmpDirectiveSpecification
!PARSE-TREE: | | | OmpDirectiveName -> llvm::omp::Directive = do
!PARSE-TREE: | | | OmpClauseList ->
!PARSE-TREE: | | | Flags = {}
!PARSE-TREE: | | Flags = {ExplicitBegin}
!PARSE-TREE: | Block
!PARSE-TREE: | OmpEndDirective
!PARSE-TREE: | | OmpDirectiveName -> llvm::omp::Directive = metadirective
!PARSE-TREE: | | OmpClauseList ->
!PARSE-TREE: | | Flags = {}

View File

@@ -0,0 +1,8 @@
!RUN: %python %S/../test_errors.py %s %flang -fopenmp -fopenmp-version=52
subroutine f00
!ERROR: A directive in BEGIN METADIRECTIVE should have a corresponding end-directive
!$omp begin metadirective when(user={condition(.true.)}: taskwait)
continue
!$omp end metadirective
end