//===-- Clauses.cpp -- OpenMP clause handling -----------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "Clauses.h"

#include "flang/Common/idioms.h"
#include "flang/Evaluate/expression.h"
#include "flang/Optimizer/Builder/Todo.h"
#include "flang/Parser/parse-tree.h"
#include "flang/Semantics/expression.h"
#include "flang/Semantics/openmp-modifiers.h"
#include "flang/Semantics/symbol.h"

#include "llvm/Frontend/OpenMP/OMPConstants.h"

#include <list>
#include <optional>
#include <tuple>
#include <utility>
#include <variant>

namespace Fortran::lower::omp {
using SymbolWithDesignator = std::tuple<semantics::Symbol *, MaybeExpr>;

struct SymbolAndDesignatorExtractor {
  template <typename T>
  static T &&AsRvalueRef(T &&t) {
    return std::move(t);
  }
  template <typename T>
  static T AsRvalueRef(const T &t) {
    return t;
  }

  static semantics::Symbol *symbol_addr(const evaluate::SymbolRef &ref) {
    // Symbols cannot be created after semantic checks, so all symbol
    // pointers that are non-null must point to one of those pre-existing
    // objects. Throughout the code, symbols are often pointed to by
    // non-const pointers, so there is no harm in casting the constness
    // away.
    return const_cast<semantics::Symbol *>(&ref.get());
  }

  template <typename T>
  static SymbolWithDesignator visit(T &&) {
    // Use this to see missing overloads:
    // llvm::errs() << "NULL: " << __PRETTY_FUNCTION__ << '\n';
    return SymbolWithDesignator{};
  }

  template <typename T>
  static SymbolWithDesignator visit(const evaluate::Designator<T> &e) {
    return std::make_tuple(symbol_addr(*e.GetLastSymbol()),
                           evaluate::AsGenericExpr(AsRvalueRef(e)));
  }

  static SymbolWithDesignator visit(const evaluate::ProcedureDesignator &e) {
    return std::make_tuple(symbol_addr(*e.GetSymbol()), std::nullopt);
  }

  template <typename T>
  static SymbolWithDesignator visit(const evaluate::Expr<T> &e) {
    return Fortran::common::visit([](auto &&s) { return visit(s); }, e.u);
  }

  static void verify(const SymbolWithDesignator &sd) {
    const semantics::Symbol *symbol = std::get<0>(sd);
    assert(symbol && "Expecting symbol");
    auto &maybeDsg = std::get<1>(sd);
    if (!maybeDsg)
      return; // Symbol with no designator -> OK
    std::optional<evaluate::DataRef> maybeRef =
        evaluate::ExtractDataRef(*maybeDsg);
    if (maybeRef) {
      if (&maybeRef->GetLastSymbol() == symbol)
        return; // Symbol with a designator for it -> OK
      llvm_unreachable("Expecting designator for given symbol");
    } else {
      // This could still be a Substring or ComplexPart, but at least Substring
      // is not allowed in OpenMP.
#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
      maybeDsg->dump();
#endif
      llvm_unreachable("Expecting DataRef designator");
    }
  }
};

SymbolWithDesignator getSymbolAndDesignator(const MaybeExpr &expr) {
  if (!expr)
    return SymbolWithDesignator{};
  return Fortran::common::visit(
      [](auto &&s) { return SymbolAndDesignatorExtractor::visit(s); }, expr->u);
}

Object makeObject(const parser::Name &name,
                  semantics::SemanticsContext &semaCtx) {
  assert(name.symbol && "Expecting Symbol");
  return Object{name.symbol, std::nullopt};
}

Object makeObject(const parser::Designator &dsg,
                  semantics::SemanticsContext &semaCtx) {
  evaluate::ExpressionAnalyzer ea{semaCtx};
  SymbolWithDesignator sd = getSymbolAndDesignator(ea.Analyze(dsg));
  SymbolAndDesignatorExtractor::verify(sd);
  return Object{std::get<0>(sd), std::move(std::get<1>(sd))};
}

Object makeObject(const parser::StructureComponent &comp,
                  semantics::SemanticsContext &semaCtx) {
  evaluate::ExpressionAnalyzer ea{semaCtx};
  SymbolWithDesignator sd = getSymbolAndDesignator(ea.Analyze(comp));
  SymbolAndDesignatorExtractor::verify(sd);
  return Object{std::get<0>(sd), std::move(std::get<1>(sd))};
}

Object makeObject(const parser::OmpObject &object,
                  semantics::SemanticsContext &semaCtx) {
  // If object is a common block, expression analyzer won't be able to
  // do anything.
  if (const auto *name = std::get_if<parser::Name>(&object.u)) {
    assert(name->symbol && "Expecting Symbol");
    return Object{name->symbol, std::nullopt};
  }
  // OmpObject is std::variant<Designator, /*common block*/ Name>;
  return makeObject(std::get<parser::Designator>(object.u), semaCtx);
}

std::optional<Object> getBaseObject(const Object &object,
                                    semantics::SemanticsContext &semaCtx) {
  // If it's just the symbol, then there is no base.
  if (!object.ref())
    return std::nullopt;

  auto maybeRef = evaluate::ExtractDataRef(*object.ref());
  if (!maybeRef)
    return std::nullopt;

  evaluate::DataRef ref = *maybeRef;

  if (std::get_if<evaluate::SymbolRef>(&ref.u)) {
    return std::nullopt;
  } else if (auto *comp = std::get_if<evaluate::Component>(&ref.u)) {
    const evaluate::DataRef &base = comp->base();
    return Object{
        SymbolAndDesignatorExtractor::symbol_addr(base.GetLastSymbol()),
        evaluate::AsGenericExpr(
            SymbolAndDesignatorExtractor::AsRvalueRef(base))};
  } else if (auto *arr = std::get_if<evaluate::ArrayRef>(&ref.u)) {
    const evaluate::NamedEntity &base = arr->base();
    evaluate::ExpressionAnalyzer ea{semaCtx};
    if (auto *comp = base.UnwrapComponent()) {
      return Object{SymbolAndDesignatorExtractor::symbol_addr(comp->symbol()),
                    ea.Designate(evaluate::DataRef{
                        SymbolAndDesignatorExtractor::AsRvalueRef(*comp)})};
    } else if (base.UnwrapSymbolRef()) {
      return std::nullopt;
    }
  } else {
    assert(std::holds_alternative<evaluate::CoarrayRef>(ref.u) &&
           "Unexpected variant alternative");
    llvm_unreachable("Coarray reference not supported at the moment");
  }
  return std::nullopt;
}

// Helper macros
#define MAKE_EMPTY_CLASS(cls, from_cls)                                        \
  cls make(const parser::OmpClause::from_cls &,                                \
           semantics::SemanticsContext &) {                                    \
    static_assert(cls::EmptyTrait::value);                                     \
    return cls{};                                                              \
  }                                                                            \
  [[maybe_unused]] extern int xyzzy_semicolon_absorber

#define MAKE_INCOMPLETE_CLASS(cls, from_cls)                                   \
  cls make(const parser::OmpClause::from_cls &,                                \
           semantics::SemanticsContext &) {                                    \
    static_assert(cls::IncompleteTrait::value);                                \
    return cls{};                                                              \
  }                                                                            \
  [[maybe_unused]] extern int xyzzy_semicolon_absorber

#define MS(x, y) CLAUSET_SCOPED_ENUM_MEMBER_CONVERT(x, y)
#define MU(x, y) CLAUSET_UNSCOPED_ENUM_MEMBER_CONVERT(x, y)

namespace clause {
MAKE_EMPTY_CLASS(AcqRel, AcqRel);
MAKE_EMPTY_CLASS(Acquire, Acquire);
MAKE_EMPTY_CLASS(Capture, Capture);
MAKE_EMPTY_CLASS(Compare, Compare);
MAKE_EMPTY_CLASS(DynamicAllocators, DynamicAllocators);
MAKE_EMPTY_CLASS(Full, Full);
MAKE_EMPTY_CLASS(Inbranch, Inbranch);
MAKE_EMPTY_CLASS(Mergeable, Mergeable);
MAKE_EMPTY_CLASS(Nogroup, Nogroup);
MAKE_EMPTY_CLASS(NoOpenmp, NoOpenmp);
MAKE_EMPTY_CLASS(NoOpenmpRoutines, NoOpenmpRoutines);
MAKE_EMPTY_CLASS(NoParallelism, NoParallelism);
MAKE_EMPTY_CLASS(Notinbranch, Notinbranch);
MAKE_EMPTY_CLASS(Nowait, Nowait);
MAKE_EMPTY_CLASS(OmpxAttribute, OmpxAttribute);
MAKE_EMPTY_CLASS(OmpxBare, OmpxBare);
MAKE_EMPTY_CLASS(Read, Read);
MAKE_EMPTY_CLASS(Relaxed, Relaxed);
MAKE_EMPTY_CLASS(Release, Release);
MAKE_EMPTY_CLASS(ReverseOffload, ReverseOffload);
MAKE_EMPTY_CLASS(SeqCst, SeqCst);
MAKE_EMPTY_CLASS(Simd, Simd);
MAKE_EMPTY_CLASS(Threads, Threads);
MAKE_EMPTY_CLASS(UnifiedAddress, UnifiedAddress);
MAKE_EMPTY_CLASS(UnifiedSharedMemory, UnifiedSharedMemory);
MAKE_EMPTY_CLASS(Unknown, Unknown);
MAKE_EMPTY_CLASS(Untied, Untied);
MAKE_EMPTY_CLASS(Weak, Weak);
MAKE_EMPTY_CLASS(Write, Write);

// Artificial clauses
MAKE_EMPTY_CLASS(CancellationConstructType, CancellationConstructType);
MAKE_EMPTY_CLASS(Depobj, Depobj);
MAKE_EMPTY_CLASS(Flush, Flush);
MAKE_EMPTY_CLASS(MemoryOrder, MemoryOrder);
MAKE_EMPTY_CLASS(Threadprivate, Threadprivate);

MAKE_INCOMPLETE_CLASS(AdjustArgs, AdjustArgs);
MAKE_INCOMPLETE_CLASS(AppendArgs, AppendArgs);
MAKE_INCOMPLETE_CLASS(Match, Match);
// MAKE_INCOMPLETE_CLASS(Otherwise, );   // missing-in-parser
MAKE_INCOMPLETE_CLASS(When, When);

List<IteratorSpecifier>
makeIteratorSpecifiers(const parser::OmpIteratorSpecifier &inp,
                       semantics::SemanticsContext &semaCtx) {
  List<IteratorSpecifier> specifiers;

  auto &[begin, end, step] = std::get<parser::SubscriptTriplet>(inp.t).t;
  assert(begin && end && "Expecting begin/end values");
  evaluate::ExpressionAnalyzer ea{semaCtx};

  MaybeExpr rbegin{ea.Analyze(*begin)}, rend{ea.Analyze(*end)};
  MaybeExpr rstep;
  if (step)
    rstep = ea.Analyze(*step);

  assert(rbegin && rend && "Unable to get range bounds");
  Range range{{*rbegin, *rend, rstep}};

  auto &tds = std::get<parser::TypeDeclarationStmt>(inp.t);
  auto &entities = std::get<std::list<parser::EntityDecl>>(tds.t);
  for (const parser::EntityDecl &ed : entities) {
    auto &name = std::get<parser::ObjectName>(ed.t);
    assert(name.symbol && "Expecting symbol for iterator variable");
    auto *stype = name.symbol->GetType();
    assert(stype && "Expecting symbol type");
    IteratorSpecifier spec{{evaluate::DynamicType::From(*stype),
                            makeObject(name, semaCtx), range}};
    specifiers.emplace_back(std::move(spec));
  }

  return specifiers;
}

Iterator makeIterator(const parser::OmpIterator &inp,
                      semantics::SemanticsContext &semaCtx) {
  Iterator iterator;
  for (auto &&spec : inp.v)
    llvm::append_range(iterator, makeIteratorSpecifiers(spec, semaCtx));
  return iterator;
}

DefinedOperator makeDefinedOperator(const parser::DefinedOperator &inp,
                                    semantics::SemanticsContext &semaCtx) {
  CLAUSET_ENUM_CONVERT( //
      convert, parser::DefinedOperator::IntrinsicOperator,
      DefinedOperator::IntrinsicOperator,
      // clang-format off
      MS(Add,      Add)
      MS(AND,      AND)
      MS(Concat,   Concat)
      MS(Divide,   Divide)
      MS(EQ,       EQ)
      MS(EQV,      EQV)
      MS(GE,       GE)
      MS(GT,       GT)
      MS(NOT,      NOT)
      MS(LE,       LE)
      MS(LT,       LT)
      MS(Multiply, Multiply)
      MS(NE,       NE)
      MS(NEQV,     NEQV)
      MS(OR,       OR)
      MS(Power,    Power)
      MS(Subtract, Subtract)
      // clang-format on
  );

  return Fortran::common::visit(
      common::visitors{
          [&](const parser::DefinedOpName &s) {
            return DefinedOperator{
                DefinedOperator::DefinedOpName{makeObject(s.v, semaCtx)}};
          },
          [&](const parser::DefinedOperator::IntrinsicOperator &s) {
            return DefinedOperator{convert(s)};
          },
      },
      inp.u);
}

ProcedureDesignator
makeProcedureDesignator(const parser::ProcedureDesignator &inp,
                        semantics::SemanticsContext &semaCtx) {
  return ProcedureDesignator{Fortran::common::visit(
      common::visitors{
          [&](const parser::Name &t) { return makeObject(t, semaCtx); },
          [&](const parser::ProcComponentRef &t) {
            return makeObject(t.v.thing, semaCtx);
          },
      },
      inp.u)};
}

ReductionOperator
makeReductionOperator(const parser::OmpReductionIdentifier &inp,
                      semantics::SemanticsContext &semaCtx) {
  return Fortran::common::visit(
      common::visitors{
          [&](const parser::DefinedOperator &s) {
            return ReductionOperator{makeDefinedOperator(s, semaCtx)};
          },
          [&](const parser::ProcedureDesignator &s) {
            return ReductionOperator{makeProcedureDesignator(s, semaCtx)};
          },
      },
      inp.u);
}

clause::DependenceType makeDepType(const parser::OmpDependenceType &inp) {
  switch (inp.v) {
  case parser::OmpDependenceType::Value::Sink:
    return clause::DependenceType::Sink;
  case parser::OmpDependenceType::Value::Source:
    return clause::DependenceType::Source;
  }
  llvm_unreachable("Unexpected dependence type");
}

clause::DependenceType makeDepType(const parser::OmpTaskDependenceType &inp) {
  switch (inp.v) {
  case parser::OmpTaskDependenceType::Value::Depobj:
    return clause::DependenceType::Depobj;
  case parser::OmpTaskDependenceType::Value::In:
    return clause::DependenceType::In;
  case parser::OmpTaskDependenceType::Value::Inout:
    return clause::DependenceType::Inout;
  case parser::OmpTaskDependenceType::Value::Inoutset:
    return clause::DependenceType::Inoutset;
  case parser::OmpTaskDependenceType::Value::Mutexinoutset:
    return clause::DependenceType::Mutexinoutset;
  case parser::OmpTaskDependenceType::Value::Out:
    return clause::DependenceType::Out;
  }
  llvm_unreachable("Unexpected task dependence type");
}

clause::Prescriptiveness
makePrescriptiveness(parser::OmpPrescriptiveness::Value v) {
  switch (v) {
  case parser::OmpPrescriptiveness::Value::Strict:
    return clause::Prescriptiveness::Strict;
  }
  llvm_unreachable("Unexpected prescriptiveness");
}

// --------------------------------------------------------------------
// Actual clauses. Each T (where tomp::T exists in ClauseT) has its "make".

Absent make(const parser::OmpClause::Absent &inp,
            semantics::SemanticsContext &semaCtx) {
  llvm_unreachable("Unimplemented: absent");
}

// AcqRel: empty
// Acquire: empty
// AdjustArgs: incomplete

Affinity make(const parser::OmpClause::Affinity &inp,
              semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::OmpAffinityClause
  auto &mods = semantics::OmpGetModifiers(inp.v);
  auto *m0 = semantics::OmpGetUniqueModifier<parser::OmpIterator>(mods);
  auto &t1 = std::get<parser::OmpObjectList>(inp.v.t);

  auto &&maybeIter =
      m0 ? makeIterator(*m0, semaCtx) : std::optional<Iterator>{};

  return Affinity{{/*Iterator=*/std::move(maybeIter),
                   /*LocatorList=*/makeObjects(t1, semaCtx)}};
}

Align make(const parser::OmpClause::Align &inp,
           semantics::SemanticsContext &semaCtx) {
  // inp -> empty
  llvm_unreachable("Empty: align");
}

Aligned make(const parser::OmpClause::Aligned &inp,
             semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::OmpAlignedClause
  auto &mods = semantics::OmpGetModifiers(inp.v);
  auto &t0 = std::get<parser::OmpObjectList>(inp.v.t);
  auto *m1 = semantics::OmpGetUniqueModifier<parser::OmpAlignment>(mods);

  return Aligned{{
      /*Alignment=*/maybeApplyToV(makeExprFn(semaCtx), m1),
      /*List=*/makeObjects(t0, semaCtx),
  }};
}

Allocate make(const parser::OmpClause::Allocate &inp,
              semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::OmpAllocateClause
  auto &mods = semantics::OmpGetModifiers(inp.v);
  auto *m0 = semantics::OmpGetUniqueModifier<parser::OmpAlignModifier>(mods);
  auto *m1 =
      semantics::OmpGetUniqueModifier<parser::OmpAllocatorComplexModifier>(
          mods);
  auto *m2 =
      semantics::OmpGetUniqueModifier<parser::OmpAllocatorSimpleModifier>(mods);
  auto &t1 = std::get<parser::OmpObjectList>(inp.v.t);

  auto makeAllocator = [&](auto *mod) -> std::optional<Allocator> {
    if (mod)
      return Allocator{makeExpr(mod->v, semaCtx)};
    return std::nullopt;
  };

  auto makeAlign = [&](const parser::ScalarIntExpr &expr) {
    return Align{makeExpr(expr, semaCtx)};
  };

  auto maybeAllocator = m1 ? makeAllocator(m1) : makeAllocator(m2);
  return Allocate{{/*AllocatorComplexModifier=*/std::move(maybeAllocator),
                   /*AlignModifier=*/maybeApplyToV(makeAlign, m0),
                   /*List=*/makeObjects(t1, semaCtx)}};
}

Allocator make(const parser::OmpClause::Allocator &inp,
               semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::ScalarIntExpr
  return Allocator{/*Allocator=*/makeExpr(inp.v, semaCtx)};
}

// AppendArgs: incomplete

At make(const parser::OmpClause::At &inp,
        semantics::SemanticsContext &semaCtx) {
  // inp -> empty
  llvm_unreachable("Empty: at");
}

// Never called, but needed for using "make" as a Clause visitor.
// See comment about "requires" clauses in Clauses.h.
AtomicDefaultMemOrder make(const parser::OmpClause::AtomicDefaultMemOrder &inp,
                           semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::OmpAtomicDefaultMemOrderClause
  CLAUSET_ENUM_CONVERT( //
      convert, common::OmpAtomicDefaultMemOrderType,
      AtomicDefaultMemOrder::MemoryOrder,
      // clang-format off
      MS(AcqRel,   AcqRel)
      MS(Relaxed,  Relaxed)
      MS(SeqCst,   SeqCst)
      // clang-format on
  );

  return AtomicDefaultMemOrder{/*MemoryOrder=*/convert(inp.v.v)};
}

Bind make(const parser::OmpClause::Bind &inp,
          semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::OmpBindClause
  using wrapped = parser::OmpBindClause;

  CLAUSET_ENUM_CONVERT( //
      convert, wrapped::Binding, Bind::Binding,
      // clang-format off
      MS(Teams, Teams)
      MS(Parallel, Parallel)
      MS(Thread, Thread)
      // clang-format on
  );

  return Bind{/*Binding=*/convert(inp.v.v)};
}

// CancellationConstructType: empty
// Capture: empty

Collapse make(const parser::OmpClause::Collapse &inp,
              semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::ScalarIntConstantExpr
  return Collapse{/*N=*/makeExpr(inp.v, semaCtx)};
}

// Compare: empty

Contains make(const parser::OmpClause::Contains &inp,
              semantics::SemanticsContext &semaCtx) {
  llvm_unreachable("Unimplemented: contains");
}

Copyin make(const parser::OmpClause::Copyin &inp,
            semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::OmpObjectList
  return Copyin{/*List=*/makeObjects(inp.v, semaCtx)};
}

Copyprivate make(const parser::OmpClause::Copyprivate &inp,
                 semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::OmpObjectList
  return Copyprivate{/*List=*/makeObjects(inp.v, semaCtx)};
}

Default make(const parser::OmpClause::Default &inp,
             semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::OmpDefaultClause
  using wrapped = parser::OmpDefaultClause;

  CLAUSET_ENUM_CONVERT( //
      convert, wrapped::DataSharingAttribute, Default::DataSharingAttribute,
      // clang-format off
      MS(Firstprivate, Firstprivate)
      MS(None,         None)
      MS(Private,      Private)
      MS(Shared,       Shared)
      // clang-format on
  );

  return Default{/*DataSharingAttribute=*/convert(inp.v.v)};
}

Defaultmap make(const parser::OmpClause::Defaultmap &inp,
                semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::OmpDefaultmapClause
  using wrapped = parser::OmpDefaultmapClause;

  CLAUSET_ENUM_CONVERT( //
      convert1, wrapped::ImplicitBehavior, Defaultmap::ImplicitBehavior,
      // clang-format off
      MS(Alloc,        Alloc)
      MS(To,           To)
      MS(From,         From)
      MS(Tofrom,       Tofrom)
      MS(Firstprivate, Firstprivate)
      MS(None,         None)
      MS(Default,      Default)
      // MS(, Present)  missing-in-parser
      // clang-format on
  );

  CLAUSET_ENUM_CONVERT( //
      convert2, parser::OmpVariableCategory::Value,
      Defaultmap::VariableCategory,
      // clang-format off
      MS(Aggregate,    Aggregate)
      MS(All,          All)
      MS(Allocatable,  Allocatable)
      MS(Pointer,      Pointer)
      MS(Scalar,       Scalar)
      // clang-format on
  );

  auto &mods = semantics::OmpGetModifiers(inp.v);
  auto &t0 = std::get<wrapped::ImplicitBehavior>(inp.v.t);
  auto *t1 = semantics::OmpGetUniqueModifier<parser::OmpVariableCategory>(mods);

  auto category = t1 ? convert2(t1->v) : Defaultmap::VariableCategory::All;
  return Defaultmap{{/*ImplicitBehavior=*/convert1(t0),
                     /*VariableCategory=*/category}};
}

Doacross makeDoacross(const parser::OmpDoacross &doa,
                      semantics::SemanticsContext &semaCtx) {
  // Iteration is the equivalent of parser::OmpIteration
  using Iteration = Doacross::Vector::value_type; // LoopIterationT

  auto visitSource = [&](const parser::OmpDoacross::Source &) {
    return Doacross{{/*DependenceType=*/Doacross::DependenceType::Source,
                     /*Vector=*/{}}};
  };

  auto visitSink = [&](const parser::OmpDoacross::Sink &s) {
    using IterOffset = parser::OmpIterationOffset;
    auto convert2 = [&](const parser::OmpIteration &v) {
      auto &t0 = std::get<parser::Name>(v.t);
      auto &t1 = std::get<std::optional<IterOffset>>(v.t);

      auto convert3 = [&](const IterOffset &u) {
        auto &s0 = std::get<parser::DefinedOperator>(u.t);
        auto &s1 = std::get<parser::ScalarIntConstantExpr>(u.t);
        return Iteration::Distance{
            {makeDefinedOperator(s0, semaCtx), makeExpr(s1, semaCtx)}};
      };
      return Iteration{{makeObject(t0, semaCtx), maybeApply(convert3, t1)}};
    };
    return Doacross{{/*DependenceType=*/Doacross::DependenceType::Sink,
                     /*Vector=*/makeList(s.v.v, convert2)}};
  };

  return common::visit(common::visitors{visitSink, visitSource}, doa.u);
}

Depend make(const parser::OmpClause::Depend &inp,
            semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::OmpDependClause
  using wrapped = parser::OmpDependClause;
  using Variant = decltype(Depend::u);

  auto visitTaskDep = [&](const wrapped::TaskDep &s) -> Variant {
    auto &mods = semantics::OmpGetModifiers(s);
    auto *m0 = semantics::OmpGetUniqueModifier<parser::OmpIterator>(mods);
    auto *m1 =
        semantics::OmpGetUniqueModifier<parser::OmpTaskDependenceType>(mods);
    auto &t1 = std::get<parser::OmpObjectList>(s.t);
    assert(m1 && "expecting task dependence type");

    auto &&maybeIter =
        m0 ? makeIterator(*m0, semaCtx) : std::optional<Iterator>{};
    return Depend::TaskDep{{/*DependenceType=*/makeDepType(*m1),
                            /*Iterator=*/std::move(maybeIter),
                            /*LocatorList=*/makeObjects(t1, semaCtx)}};
  };

  return Depend{common::visit( //
      common::visitors{
          // Doacross
          [&](const parser::OmpDoacross &s) -> Variant {
            return makeDoacross(s, semaCtx);
          },
          // Depend::TaskDep
          visitTaskDep,
      },
      inp.v.u)};
}

// Depobj: empty

Destroy make(const parser::OmpClause::Destroy &inp,
             semantics::SemanticsContext &semaCtx) {
  // inp.v -> std::optional<OmpDestroyClause>
  auto &&maybeObject = maybeApply(
      [&](const parser::OmpDestroyClause &c) {
        return makeObject(c.v, semaCtx);
      },
      inp.v);

  return Destroy{/*DestroyVar=*/std::move(maybeObject)};
}

Detach make(const parser::OmpClause::Detach &inp,
            semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::OmpDetachClause
  return Detach{makeObject(inp.v.v, semaCtx)};
}

Device make(const parser::OmpClause::Device &inp,
            semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::OmpDeviceClause
  CLAUSET_ENUM_CONVERT( //
      convert, parser::OmpDeviceModifier::Value, Device::DeviceModifier,
      // clang-format off
      MS(Ancestor,   Ancestor)
      MS(Device_Num, DeviceNum)
      // clang-format on
  );

  auto &mods = semantics::OmpGetModifiers(inp.v);
  auto *m0 = semantics::OmpGetUniqueModifier<parser::OmpDeviceModifier>(mods);
  auto &t1 = std::get<parser::ScalarIntExpr>(inp.v.t);
  return Device{{/*DeviceModifier=*/maybeApplyToV(convert, m0),
                 /*DeviceDescription=*/makeExpr(t1, semaCtx)}};
}

DeviceType make(const parser::OmpClause::DeviceType &inp,
                semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::OmpDeviceTypeClause
  using wrapped = parser::OmpDeviceTypeClause;

  CLAUSET_ENUM_CONVERT( //
      convert, wrapped::DeviceTypeDescription,
      DeviceType::DeviceTypeDescription,
      // clang-format off
      MS(Any,    Any)
      MS(Host,   Host)
      MS(Nohost, Nohost)
      // clang-format om
  );
  return DeviceType{/*DeviceTypeDescription=*/convert(inp.v.v)};
}

DistSchedule make(const parser::OmpClause::DistSchedule &inp,
                  semantics::SemanticsContext &semaCtx) {
  // inp.v -> std::optional<parser::ScalarIntExpr>
  return DistSchedule{{/*Kind=*/DistSchedule::Kind::Static,
                       /*ChunkSize=*/maybeApply(makeExprFn(semaCtx), inp.v)}};
}

Doacross make(const parser::OmpClause::Doacross &inp,
              semantics::SemanticsContext &semaCtx) {
  // inp.v -> OmpDoacrossClause
  return makeDoacross(inp.v.v, semaCtx);
}

// DynamicAllocators: empty

Enter make(const parser::OmpClause::Enter &inp,
           semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::OmpObjectList
  return Enter{makeObjects(/*List=*/inp.v, semaCtx)};
}

Exclusive make(const parser::OmpClause::Exclusive &inp,
               semantics::SemanticsContext &semaCtx) {
  // inp -> empty
  llvm_unreachable("Empty: exclusive");
}

Fail make(const parser::OmpClause::Fail &inp,
          semantics::SemanticsContext &semaCtx) {
  // inp -> empty
  llvm_unreachable("Empty: fail");
}

Filter make(const parser::OmpClause::Filter &inp,
            semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::ScalarIntExpr
  return Filter{/*ThreadNum=*/makeExpr(inp.v, semaCtx)};
}

Final make(const parser::OmpClause::Final &inp,
           semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::ScalarLogicalExpr
  return Final{/*Finalize=*/makeExpr(inp.v, semaCtx)};
}

Firstprivate make(const parser::OmpClause::Firstprivate &inp,
                  semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::OmpObjectList
  return Firstprivate{/*List=*/makeObjects(inp.v, semaCtx)};
}

// Flush: empty

From make(const parser::OmpClause::From &inp,
          semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::OmpFromClause
  CLAUSET_ENUM_CONVERT( //
      convert, parser::OmpExpectation::Value, From::Expectation,
      // clang-format off
      MS(Present, Present)
      // clang-format on
  );

  auto &mods = semantics::OmpGetModifiers(inp.v);
  auto *t0 = semantics::OmpGetUniqueModifier<parser::OmpExpectation>(mods);
  auto *t1 = semantics::OmpGetUniqueModifier<parser::OmpMapper>(mods);
  auto *t2 = semantics::OmpGetUniqueModifier<parser::OmpIterator>(mods);
  auto &t3 = std::get<parser::OmpObjectList>(inp.v.t);

  auto mappers = [&]() -> std::optional<List<Mapper>> {
    if (t1)
      return List<Mapper>{Mapper{makeObject(t1->v, semaCtx)}};
    return std::nullopt;
  }();

  auto iterator = [&]() -> std::optional<Iterator> {
    if (t2)
      return makeIterator(*t2, semaCtx);
    return std::nullopt;
  }();

  return From{{/*Expectation=*/maybeApplyToV(convert, t0),
               /*Mappers=*/std::move(mappers),
               /*Iterator=*/std::move(iterator),
               /*LocatorList=*/makeObjects(t3, semaCtx)}};
}

// Full: empty

Grainsize make(const parser::OmpClause::Grainsize &inp,
               semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::OmpGrainsizeClause
  auto &mods = semantics::OmpGetModifiers(inp.v);
  auto *m0 = semantics::OmpGetUniqueModifier<parser::OmpPrescriptiveness>(mods);
  auto &t1 = std::get<parser::ScalarIntExpr>(inp.v.t);
  return Grainsize{
      {/*Prescriptiveness=*/maybeApplyToV(makePrescriptiveness, m0),
       /*Grainsize=*/makeExpr(t1, semaCtx)}};
}

HasDeviceAddr make(const parser::OmpClause::HasDeviceAddr &inp,
                   semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::OmpObjectList
  return HasDeviceAddr{/*List=*/makeObjects(inp.v, semaCtx)};
}

Hint make(const parser::OmpClause::Hint &inp,
          semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::ConstantExpr
  return Hint{/*HintExpr=*/makeExpr(inp.v, semaCtx)};
}

Holds make(const parser::OmpClause::Holds &inp,
           semantics::SemanticsContext &semaCtx) {
  llvm_unreachable("Unimplemented: holds");
}

If make(const parser::OmpClause::If &inp,
        semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::OmpIfClause
  auto &mods = semantics::OmpGetModifiers(inp.v);
  auto *m0 =
      semantics::OmpGetUniqueModifier<parser::OmpDirectiveNameModifier>(mods);
  auto &t1 = std::get<parser::ScalarLogicalExpr>(inp.v.t);
  return If{
      {/*DirectiveNameModifier=*/maybeApplyToV([](auto &&s) { return s; }, m0),
       /*IfExpression=*/makeExpr(t1, semaCtx)}};
}

// Inbranch: empty

Inclusive make(const parser::OmpClause::Inclusive &inp,
               semantics::SemanticsContext &semaCtx) {
  // inp -> empty
  llvm_unreachable("Empty: inclusive");
}

Indirect make(const parser::OmpClause::Indirect &inp,
              semantics::SemanticsContext &semaCtx) {
  // inp -> empty
  llvm_unreachable("Empty: indirect");
}

Init make(const parser::OmpClause::Init &inp,
          semantics::SemanticsContext &semaCtx) {
  // inp -> empty
  llvm_unreachable("Empty: init");
}

// Initializer: missing-in-parser

InReduction make(const parser::OmpClause::InReduction &inp,
                 semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::OmpInReductionClause
  auto &t0 = std::get<parser::OmpReductionIdentifier>(inp.v.t);
  auto &t1 = std::get<parser::OmpObjectList>(inp.v.t);
  return InReduction{
      {/*ReductionIdentifiers=*/{makeReductionOperator(t0, semaCtx)},
       /*List=*/makeObjects(t1, semaCtx)}};
}

IsDevicePtr make(const parser::OmpClause::IsDevicePtr &inp,
                 semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::OmpObjectList
  return IsDevicePtr{/*List=*/makeObjects(inp.v, semaCtx)};
}

Lastprivate make(const parser::OmpClause::Lastprivate &inp,
                 semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::OmpLastprivateClause
  CLAUSET_ENUM_CONVERT( //
      convert, parser::OmpLastprivateModifier::Value,
      Lastprivate::LastprivateModifier,
      // clang-format off
      MS(Conditional, Conditional)
      // clang-format on
  );

  auto &mods = semantics::OmpGetModifiers(inp.v);
  auto *m0 =
      semantics::OmpGetUniqueModifier<parser::OmpLastprivateModifier>(mods);
  auto &t1 = std::get<parser::OmpObjectList>(inp.v.t);

  return Lastprivate{{/*LastprivateModifier=*/maybeApplyToV(convert, m0),
                      /*List=*/makeObjects(t1, semaCtx)}};
}

Linear make(const parser::OmpClause::Linear &inp,
            semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::OmpLinearClause
  using wrapped = parser::OmpLinearClause;

  CLAUSET_ENUM_CONVERT( //
      convert, parser::OmpLinearModifier::Value, Linear::LinearModifier,
      // clang-format off
      MS(Ref,  Ref)
      MS(Val,  Val)
      MS(Uval, Uval)
      // clang-format on
  );

  using Tuple = decltype(Linear::t);

  return Linear{Fortran::common::visit(
      common::visitors{
          [&](const wrapped::WithModifier &s) -> Tuple {
            return {
                /*StepSimpleModifier=*/std::nullopt,
                /*StepComplexModifier=*/maybeApply(makeExprFn(semaCtx), s.step),
                /*LinearModifier=*/convert(s.modifier.v),
                /*List=*/makeList(s.names, makeObjectFn(semaCtx))};
          },
          [&](const wrapped::WithoutModifier &s) -> Tuple {
            return {
                /*StepSimpleModifier=*/maybeApply(makeExprFn(semaCtx), s.step),
                /*StepComplexModifier=*/std::nullopt,
                /*LinearModifier=*/std::nullopt,
                /*List=*/makeList(s.names, makeObjectFn(semaCtx))};
          },
      },
      inp.v.u)};
}

Link make(const parser::OmpClause::Link &inp,
          semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::OmpObjectList
  return Link{/*List=*/makeObjects(inp.v, semaCtx)};
}

Map make(const parser::OmpClause::Map &inp,
         semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::OmpMapClause
  CLAUSET_ENUM_CONVERT( //
      convert1, parser::OmpMapType::Value, Map::MapType,
      // clang-format off
      MS(Alloc,    Alloc)
      MS(Delete,   Delete)
      MS(From,     From)
      MS(Release,  Release)
      MS(To,       To)
      MS(Tofrom,   Tofrom)
      // clang-format on
  );

  CLAUSET_ENUM_CONVERT( //
      convert2, parser::OmpMapTypeModifier::Value, Map::MapTypeModifier,
      // clang-format off
      MS(Always,    Always)
      MS(Close,     Close)
      MS(Ompx_Hold, OmpxHold)
      MS(Present,   Present)
      // clang-format on
  );

  auto &mods = semantics::OmpGetModifiers(inp.v);
  auto *t1 = semantics::OmpGetUniqueModifier<parser::OmpMapper>(mods);
  auto *t2 = semantics::OmpGetUniqueModifier<parser::OmpIterator>(mods);
  auto *t3 = semantics::OmpGetUniqueModifier<parser::OmpMapType>(mods);
  auto &t4 = std::get<parser::OmpObjectList>(inp.v.t);

  auto mappers = [&]() -> std::optional<List<Mapper>> {
    if (t1)
      return List<Mapper>{Mapper{makeObject(t1->v, semaCtx)}};
    return std::nullopt;
  }();

  auto iterator = [&]() -> std::optional<Iterator> {
    if (t2)
      return makeIterator(*t2, semaCtx);
    return std::nullopt;
  }();

  auto type = [&]() -> std::optional<Map::MapType> {
    if (t3)
      return convert1(t3->v);
    return Map::MapType::Tofrom;
  }();

  Map::MapTypeModifiers typeMods;
  for (auto *typeMod :
       semantics::OmpGetRepeatableModifier<parser::OmpMapTypeModifier>(mods)) {
    typeMods.push_back(convert2(typeMod->v));
  }
  std::optional<Map::MapTypeModifiers> maybeTypeMods{};
  if (!typeMods.empty())
    maybeTypeMods = std::move(typeMods);

  return Map{{/*MapType=*/std::move(type),
              /*MapTypeModifiers=*/std::move(maybeTypeMods),
              /*Mapper=*/std::move(mappers), /*Iterator=*/std::move(iterator),
              /*LocatorList=*/makeObjects(t4, semaCtx)}};
}

// Match: incomplete
// MemoryOrder: empty
// Mergeable: empty

Message make(const parser::OmpClause::Message &inp,
             semantics::SemanticsContext &semaCtx) {
  // inp -> empty
  llvm_unreachable("Empty: message");
}

Nocontext make(const parser::OmpClause::Nocontext &inp,
               semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::ScalarLogicalExpr
  return Nocontext{/*DoNotUpdateContext=*/makeExpr(inp.v, semaCtx)};
}

// Nogroup: empty

Nontemporal make(const parser::OmpClause::Nontemporal &inp,
                 semantics::SemanticsContext &semaCtx) {
  // inp.v -> std::list<parser::Name>
  return Nontemporal{/*List=*/makeList(inp.v, makeObjectFn(semaCtx))};
}

// NoOpenmp: empty
// NoOpenmpRoutines: empty
// NoParallelism: empty
// Notinbranch: empty

Novariants make(const parser::OmpClause::Novariants &inp,
                semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::ScalarLogicalExpr
  return Novariants{/*DoNotUseVariant=*/makeExpr(inp.v, semaCtx)};
}

// Nowait: empty

NumTasks make(const parser::OmpClause::NumTasks &inp,
              semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::OmpNumTasksClause
  auto &mods = semantics::OmpGetModifiers(inp.v);
  auto *m0 = semantics::OmpGetUniqueModifier<parser::OmpPrescriptiveness>(mods);
  auto &t1 = std::get<parser::ScalarIntExpr>(inp.v.t);
  return NumTasks{{/*Prescriptiveness=*/maybeApplyToV(makePrescriptiveness, m0),
                   /*NumTasks=*/makeExpr(t1, semaCtx)}};
}

NumTeams make(const parser::OmpClause::NumTeams &inp,
              semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::ScalarIntExpr
  List<NumTeams::Range> v{{{/*LowerBound=*/std::nullopt,
                            /*UpperBound=*/makeExpr(inp.v, semaCtx)}}};
  return NumTeams{/*List=*/v};
}

NumThreads make(const parser::OmpClause::NumThreads &inp,
                semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::ScalarIntExpr
  return NumThreads{/*Nthreads=*/makeExpr(inp.v, semaCtx)};
}

// OmpxAttribute: empty
// OmpxBare: empty

OmpxDynCgroupMem make(const parser::OmpClause::OmpxDynCgroupMem &inp,
                      semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::ScalarIntExpr
  return OmpxDynCgroupMem{makeExpr(inp.v, semaCtx)};
}

Order make(const parser::OmpClause::Order &inp,
           semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::OmpOrderClause
  using wrapped = parser::OmpOrderClause;

  CLAUSET_ENUM_CONVERT( //
      convert1, parser::OmpOrderModifier::Value, Order::OrderModifier,
      // clang-format off
      MS(Reproducible,   Reproducible)
      MS(Unconstrained,  Unconstrained)
      // clang-format on
  );

  CLAUSET_ENUM_CONVERT( //
      convert2, wrapped::Ordering, Order::Ordering,
      // clang-format off
      MS(Concurrent, Concurrent)
      // clang-format on
  );

  auto &mods = semantics::OmpGetModifiers(inp.v);
  auto *t0 = semantics::OmpGetUniqueModifier<parser::OmpOrderModifier>(mods);
  auto &t1 = std::get<wrapped::Ordering>(inp.v.t);

  return Order{{/*OrderModifier=*/maybeApplyToV(convert1, t0),
                /*Ordering=*/convert2(t1)}};
}

Ordered make(const parser::OmpClause::Ordered &inp,
             semantics::SemanticsContext &semaCtx) {
  // inp.v -> std::optional<parser::ScalarIntConstantExpr>
  return Ordered{/*N=*/maybeApply(makeExprFn(semaCtx), inp.v)};
}

// Otherwise: incomplete, missing-in-parser

Partial make(const parser::OmpClause::Partial &inp,
             semantics::SemanticsContext &semaCtx) {
  // inp.v -> std::optional<parser::ScalarIntConstantExpr>
  return Partial{/*UnrollFactor=*/maybeApply(makeExprFn(semaCtx), inp.v)};
}

Priority make(const parser::OmpClause::Priority &inp,
              semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::ScalarIntExpr
  return Priority{/*PriorityValue=*/makeExpr(inp.v, semaCtx)};
}

Private make(const parser::OmpClause::Private &inp,
             semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::OmpObjectList
  return Private{/*List=*/makeObjects(inp.v, semaCtx)};
}

ProcBind make(const parser::OmpClause::ProcBind &inp,
              semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::OmpProcBindClause
  using wrapped = parser::OmpProcBindClause;

  CLAUSET_ENUM_CONVERT( //
      convert, wrapped::AffinityPolicy, ProcBind::AffinityPolicy,
      // clang-format off
      MS(Close,    Close)
      MS(Master,   Master)
      MS(Spread,   Spread)
      MS(Primary,  Primary)
      // clang-format on
  );
  return ProcBind{/*AffinityPolicy=*/convert(inp.v.v)};
}

// Read: empty

Reduction make(const parser::OmpClause::Reduction &inp,
               semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::OmpReductionClause
  CLAUSET_ENUM_CONVERT( //
      convert, parser::OmpReductionModifier::Value,
      Reduction::ReductionModifier,
      // clang-format off
      MS(Inscan,  Inscan)
      MS(Task,    Task)
      MS(Default, Default)
      // clang-format on
  );

  auto &mods = semantics::OmpGetModifiers(inp.v);
  auto *t0 =
      semantics::OmpGetUniqueModifier<parser::OmpReductionModifier>(mods);
  auto *t1 =
      semantics::OmpGetUniqueModifier<parser::OmpReductionIdentifier>(mods);
  auto &t2 = std::get<parser::OmpObjectList>(inp.v.t);
  assert(t1 && "OmpReductionIdentifier is required");

  return Reduction{
      {/*ReductionModifier=*/maybeApplyToV(convert, t0),
       /*ReductionIdentifiers=*/{makeReductionOperator(*t1, semaCtx)},
       /*List=*/makeObjects(t2, semaCtx)}};
}

// Relaxed: empty
// Release: empty
// ReverseOffload: empty

Safelen make(const parser::OmpClause::Safelen &inp,
             semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::ScalarIntConstantExpr
  return Safelen{/*Length=*/makeExpr(inp.v, semaCtx)};
}

Schedule make(const parser::OmpClause::Schedule &inp,
              semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::OmpScheduleClause
  using wrapped = parser::OmpScheduleClause;

  CLAUSET_ENUM_CONVERT( //
      convert1, wrapped::Kind, Schedule::Kind,
      // clang-format off
      MS(Static,   Static)
      MS(Dynamic,  Dynamic)
      MS(Guided,   Guided)
      MS(Auto,     Auto)
      MS(Runtime,  Runtime)
      // clang-format on
  );

  CLAUSET_ENUM_CONVERT( //
      convert2, parser::OmpOrderingModifier::Value, Schedule::OrderingModifier,
      // clang-format off
      MS(Monotonic,    Monotonic)
      MS(Nonmonotonic, Nonmonotonic)
      // clang-format on
  );

  CLAUSET_ENUM_CONVERT( //
      convert3, parser::OmpChunkModifier::Value, Schedule::ChunkModifier,
      // clang-format off
      MS(Simd, Simd)
      // clang-format on
  );

  auto &mods = semantics::OmpGetModifiers(inp.v);
  auto *t0 = semantics::OmpGetUniqueModifier<parser::OmpOrderingModifier>(mods);
  auto *t1 = semantics::OmpGetUniqueModifier<parser::OmpChunkModifier>(mods);
  auto &t2 = std::get<wrapped::Kind>(inp.v.t);
  auto &t3 = std::get<std::optional<parser::ScalarIntExpr>>(inp.v.t);

  return Schedule{{/*Kind=*/convert1(t2),
                   /*OrderingModifier=*/maybeApplyToV(convert2, t0),
                   /*ChunkModifier=*/maybeApplyToV(convert3, t1),
                   /*ChunkSize=*/maybeApply(makeExprFn(semaCtx), t3)}};
}

// SeqCst: empty

Severity make(const parser::OmpClause::Severity &inp,
              semantics::SemanticsContext &semaCtx) {
  // inp -> empty
  llvm_unreachable("Empty: severity");
}

Shared make(const parser::OmpClause::Shared &inp,
            semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::OmpObjectList
  return Shared{/*List=*/makeObjects(inp.v, semaCtx)};
}

// Simd: empty

Simdlen make(const parser::OmpClause::Simdlen &inp,
             semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::ScalarIntConstantExpr
  return Simdlen{/*Length=*/makeExpr(inp.v, semaCtx)};
}

Sizes make(const parser::OmpClause::Sizes &inp,
           semantics::SemanticsContext &semaCtx) {
  // inp.v -> std::list<parser::ScalarIntExpr>
  return Sizes{/*SizeList=*/makeList(inp.v, makeExprFn(semaCtx))};
}

Permutation make(const parser::OmpClause::Permutation &inp,
                 semantics::SemanticsContext &semaCtx) {
  // inp.v -> std::list<parser::ScalarIntConstantExpr>
  return Permutation{/*ArgList=*/makeList(inp.v, makeExprFn(semaCtx))};
}

TaskReduction make(const parser::OmpClause::TaskReduction &inp,
                   semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::OmpReductionClause
  auto &mods = semantics::OmpGetModifiers(inp.v);
  auto *t0 =
      semantics::OmpGetUniqueModifier<parser::OmpReductionIdentifier>(mods);
  auto &t1 = std::get<parser::OmpObjectList>(inp.v.t);
  assert(t0 && "OmpReductionIdentifier is required");

  return TaskReduction{
      {/*ReductionIdentifiers=*/{makeReductionOperator(*t0, semaCtx)},
       /*List=*/makeObjects(t1, semaCtx)}};
}

ThreadLimit make(const parser::OmpClause::ThreadLimit &inp,
                 semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::ScalarIntExpr
  return ThreadLimit{/*Threadlim=*/makeExpr(inp.v, semaCtx)};
}

// Threadprivate: empty
// Threads: empty

To make(const parser::OmpClause::To &inp,
        semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::OmpToClause
  CLAUSET_ENUM_CONVERT( //
      convert, parser::OmpExpectation::Value, To::Expectation,
      // clang-format off
      MS(Present, Present)
      // clang-format on
  );

  auto &mods = semantics::OmpGetModifiers(inp.v);
  auto *t0 = semantics::OmpGetUniqueModifier<parser::OmpExpectation>(mods);
  auto *t1 = semantics::OmpGetUniqueModifier<parser::OmpMapper>(mods);
  auto *t2 = semantics::OmpGetUniqueModifier<parser::OmpIterator>(mods);
  auto &t3 = std::get<parser::OmpObjectList>(inp.v.t);

  auto mappers = [&]() -> std::optional<List<Mapper>> {
    if (t1)
      return List<Mapper>{Mapper{makeObject(t1->v, semaCtx)}};
    return std::nullopt;
  }();

  auto iterator = [&]() -> std::optional<Iterator> {
    if (t2)
      return makeIterator(*t2, semaCtx);
    return std::nullopt;
  }();

  return To{{/*Expectation=*/maybeApplyToV(convert, t0),
             /*Mappers=*/{std::move(mappers)},
             /*Iterator=*/std::move(iterator),
             /*LocatorList=*/makeObjects(t3, semaCtx)}};
}

// UnifiedAddress: empty
// UnifiedSharedMemory: empty

Uniform make(const parser::OmpClause::Uniform &inp,
             semantics::SemanticsContext &semaCtx) {
  // inp.v -> std::list<parser::Name>
  return Uniform{/*ParameterList=*/makeList(inp.v, makeObjectFn(semaCtx))};
}

// Unknown: empty
// Untied: empty

Update make(const parser::OmpClause::Update &inp,
            semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::OmpUpdateClause
  auto depType =
      common::visit([](auto &&s) { return makeDepType(s); }, inp.v.u);
  return Update{/*DependenceType=*/depType};
}

Use make(const parser::OmpClause::Use &inp,
         semantics::SemanticsContext &semaCtx) {
  // inp -> empty
  llvm_unreachable("Empty: use");
}

UseDeviceAddr make(const parser::OmpClause::UseDeviceAddr &inp,
                   semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::OmpObjectList
  return UseDeviceAddr{/*List=*/makeObjects(inp.v, semaCtx)};
}

UseDevicePtr make(const parser::OmpClause::UseDevicePtr &inp,
                  semantics::SemanticsContext &semaCtx) {
  // inp.v -> parser::OmpObjectList
  return UseDevicePtr{/*List=*/makeObjects(inp.v, semaCtx)};
}

UsesAllocators make(const parser::OmpClause::UsesAllocators &inp,
                    semantics::SemanticsContext &semaCtx) {
  // inp -> empty
  llvm_unreachable("Empty: uses_allocators");
}

// Weak: empty
// When: incomplete
// Write: empty
} // namespace clause

Clause makeClause(const parser::OmpClause &cls,
                  semantics::SemanticsContext &semaCtx) {
  return Fortran::common::visit(
      [&](auto &&s) {
        return makeClause(cls.Id(), clause::make(s, semaCtx), cls.source);
      },
      cls.u);
}

List<Clause> makeClauses(const parser::OmpClauseList &clauses,
                         semantics::SemanticsContext &semaCtx) {
  return makeList(clauses.v, [&](const parser::OmpClause &s) {
    return makeClause(s, semaCtx);
  });
}

bool transferLocations(const List<Clause> &from, List<Clause> &to) {
  bool allDone = true;

  for (Clause &clause : to) {
    if (!clause.source.empty())
      continue;
    auto found =
        llvm::find_if(from, [&](const Clause &c) { return c.id == clause.id; });
    // This is not completely accurate, but should be good enough for now.
    // It can be improved in the future if necessary, but in cases of
    // synthesized clauses getting accurate location may be impossible.
    if (found != from.end()) {
      clause.source = found->source;
    } else {
      // Found a clause that won't have "source".
      allDone = false;
    }
  }

  return allDone;
}

} // namespace Fortran::lower::omp
