[flang] rearrange arguments into dummy argument order

Original-commit: flang-compiler/f18@3bab0f55de1b66d38bd15a841459c3a36cf29d30
Reviewed-on: https://github.com/flang-compiler/f18/pull/219
Tree-same-pre-rewrite: false
diff --git a/flang/lib/evaluate/intrinsics.cc b/flang/lib/evaluate/intrinsics.cc
index b4406ca..486ea4b 100644
--- a/flang/lib/evaluate/intrinsics.cc
+++ b/flang/lib/evaluate/intrinsics.cc
@@ -19,6 +19,7 @@
 #include "../common/fortran.h"
 #include "../common/idioms.h"
 #include "../semantics/default-kinds.h"
+#include <algorithm>
 #include <map>
 #include <ostream>
 #include <sstream>
@@ -196,7 +197,7 @@
     "mask", AnyLogical, Rank::conformable, Optionality::optional};
 
 struct IntrinsicInterface {
-  static constexpr int maxArguments{7};
+  static constexpr int maxArguments{7};  // if not a MAX/MIN(...)
   const char *name{nullptr};
   IntrinsicDummyArgument dummy[maxArguments];
   TypePattern result;
@@ -760,14 +761,24 @@
   // Attempt to construct a 1-1 correspondence between the dummy arguments in
   // a particular intrinsic procedure's generic interface and the actual
   // arguments in a procedure reference.
-  ActualArgument *actualForDummy[maxArguments];
-  int dummies{0};
-  for (; dummies < maxArguments && dummy[dummies].keyword != nullptr;
-       ++dummies) {
-    actualForDummy[dummies] = nullptr;
+  std::size_t dummyArgPatterns{0};
+  for (; dummyArgPatterns < maxArguments &&
+       dummy[dummyArgPatterns].keyword != nullptr;
+       ++dummyArgPatterns) {
   }
+  std::vector<ActualArgument *> actualForDummy(dummyArgPatterns, nullptr);
+  // MAX and MIN (and others that map to them) allow their last argument to
+  // be repeated indefinitely.  The actualForDummy vector is sized
+  // and null-initialized to the non-repeated dummy argument count,
+  // but additional actual argument pointers can be pushed on it
+  // when this flag is set.
+  bool repeatLastDummy{dummyArgPatterns > 0 &&
+      dummy[dummyArgPatterns - 1].optionality == Optionality::repeats};
+  int missingActualArguments{0};
   for (std::optional<ActualArgument> &arg : arguments) {
-    if (arg.has_value()) {
+    if (!arg.has_value()) {
+      ++missingActualArguments;
+    } else {
       if (arg->isAlternateReturn) {
         messages.Say(
             "alternate return specifier not acceptable on call to intrinsic '%s'"_err_en_US,
@@ -775,29 +786,52 @@
         return std::nullopt;
       }
       bool found{false};
-      for (int dummyArgIndex{0}; dummyArgIndex < dummies; ++dummyArgIndex) {
-        if (actualForDummy[dummyArgIndex] == nullptr) {
-          if (!arg->keyword.has_value() ||
-              *arg->keyword == dummy[dummyArgIndex].keyword) {
-            actualForDummy[dummyArgIndex] = &*arg;
-            found = true;
-            break;
+      int slot{missingActualArguments};
+      for (std::size_t j{0}; j < dummyArgPatterns && !found; ++j) {
+        if (arg->keyword.has_value()) {
+          found = *arg->keyword == dummy[j].keyword;
+          if (found) {
+            if (const auto *previous{actualForDummy[j]}) {
+              if (previous->keyword.has_value()) {
+                messages.Say(*arg->keyword,
+                    "repeated keyword argument to intrinsic '%s'"_err_en_US,
+                    name);
+              } else {
+                messages.Say(*arg->keyword,
+                    "keyword argument to intrinsic '%s' was supplied "
+                    "positionally by an earlier actual argument"_err_en_US,
+                    name);
+              }
+              return std::nullopt;
+            }
           }
+        } else {
+          found = actualForDummy[j] == nullptr && slot-- == 0;
+        }
+        if (found) {
+          actualForDummy[j] = &*arg;
         }
       }
       if (!found) {
-        if (arg->keyword.has_value()) {
-          messages.Say(*arg->keyword,
-              "unknown keyword argument to intrinsic '%s'"_err_en_US, name);
+        if (repeatLastDummy && !arg->keyword.has_value()) {
+          // MAX/MIN argument after the 2nd
+          actualForDummy.push_back(&*arg);
         } else {
-          messages.Say(
-              "too many actual arguments for intrinsic '%s'"_err_en_US, name);
+          if (arg->keyword.has_value()) {
+            messages.Say(*arg->keyword,
+                "unknown keyword argument to intrinsic '%s'"_err_en_US, name);
+          } else {
+            messages.Say(
+                "too many actual arguments for intrinsic '%s'"_err_en_US, name);
+          }
+          return std::nullopt;
         }
-        return std::nullopt;
       }
     }
   }
 
+  std::size_t dummies{actualForDummy.size()};
+
   // Check types and kinds of the actual arguments against the intrinsic's
   // interface.  Ensure that two or more arguments that have to have the same
   // type and kind do so.  Check for missing non-optional arguments now, too.
@@ -805,13 +839,13 @@
   const IntrinsicDummyArgument *kindDummyArg{nullptr};
   const ActualArgument *kindArg{nullptr};
   bool hasDimArg{false};
-  for (int dummyArgIndex{0}; dummyArgIndex < dummies; ++dummyArgIndex) {
-    const IntrinsicDummyArgument &d{dummy[dummyArgIndex]};
+  for (std::size_t j{0}; j < dummies; ++j) {
+    const IntrinsicDummyArgument &d{dummy[std::min(j, dummyArgPatterns - 1)]};
     if (d.typePattern.kindCode == KindCode::kindArg) {
       CHECK(kindDummyArg == nullptr);
       kindDummyArg = &d;
     }
-    const ActualArgument *arg{actualForDummy[dummyArgIndex]};
+    const ActualArgument *arg{actualForDummy[j]};
     if (!arg) {
       if (d.optionality == Optionality::required) {
         messages.Say("missing mandatory '%s=' argument"_err_en_US, d.keyword);
@@ -895,9 +929,9 @@
   const ActualArgument *knownArg{nullptr};
   const ActualArgument *shapeArg{nullptr};
   int elementalRank{0};
-  for (int dummyArgIndex{0}; dummyArgIndex < dummies; ++dummyArgIndex) {
-    const IntrinsicDummyArgument &d{dummy[dummyArgIndex]};
-    if (const ActualArgument * arg{actualForDummy[dummyArgIndex]}) {
+  for (std::size_t j{0}; j < dummies; ++j) {
+    const IntrinsicDummyArgument &d{dummy[std::min(j, dummyArgPatterns - 1)]};
+    if (const ActualArgument * arg{actualForDummy[j]}) {
       if (arg->isAssumedRank && d.rank != Rank::anyOrAssumedRank) {
         messages.Say(
             "assumed-rank array cannot be used for '%s=' argument"_err_en_US,
@@ -1118,9 +1152,9 @@
 
   // Rearrange the actual arguments into dummy argument order.
   ActualArguments rearranged(dummies);
-  for (int j{0}; j < dummies; ++j) {
+  for (std::size_t j{0}; j < dummies; ++j) {
     if (ActualArgument * arg{actualForDummy[j]}) {
-      rearranged[j] = std::make_optional(std::move(*arg));
+      rearranged[j] = std::move(*arg);
     }
   }