]> granicus.if.org Git - clang/commitdiff
[c++20] Implement tweaked __VA_OPT__ rules from P1042R1:
authorRichard Smith <richard-llvm@metafoo.co.uk>
Sat, 4 May 2019 06:46:18 +0000 (06:46 +0000)
committerRichard Smith <richard-llvm@metafoo.co.uk>
Sat, 4 May 2019 06:46:18 +0000 (06:46 +0000)
 * __VA_OPT__ is expanded if the *expanded* __VA_ARGS__ is non-empty,
   not if the original argument contained no tokens.
 * Placemarkers at the start and end of __VA_OPT__ are retained just
   long enough to paste them with adjacent ## operators. We never paste
   "across" a discarded placemarker.

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@359964 91177308-0d34-0410-b5e6-96231b3b80d8

include/clang/Lex/MacroArgs.h
include/clang/Lex/VariadicMacroSupport.h
lib/Lex/MacroArgs.cpp
lib/Lex/TokenLexer.cpp
test/Preprocessor/macro_vaopt_expand.cpp
test/Preprocessor/macro_vaopt_p1042r1.cpp [new file with mode: 0644]
www/cxx_status.html

index c2ba4eb2c38fe9158a4a18a8bf5b455c390ea4d2..8806f2d8c6567fec787b0fc3fa390b217ad1ed2f 100644 (file)
@@ -112,18 +112,19 @@ public:
   bool isVarargsElidedUse() const { return VarargsElided; }
 
   /// Returns true if the macro was defined with a variadic (ellipsis) parameter
-  /// AND was invoked with at least one token supplied as a variadic argument.
+  /// AND was invoked with at least one token supplied as a variadic argument
+  /// (after pre-expansion).
   ///
   /// \code
   ///   #define F(a)  a
   ///   #define V(a, ...) __VA_OPT__(a)
-  ///   F()    <-- returns false on this invocation.
-  ///   V(,a)  <-- returns true on this invocation.
-  ///   V(,)   <-- returns false on this invocation.
+  ///   F()     <-- returns false on this invocation.
+  ///   V(,a)   <-- returns true on this invocation.
+  ///   V(,)    <-- returns false on this invocation.
+  ///   V(,F()) <-- returns false on this invocation.
   /// \endcode
   ///
-
-  bool invokedWithVariadicArgument(const MacroInfo *const MI) const;
+  bool invokedWithVariadicArgument(const MacroInfo *const MI, Preprocessor &PP);
 
   /// StringifyArgument - Implement C99 6.10.3.2p2, converting a sequence of
   /// tokens into the literal string token that should be produced by the C #
index 4274a4ddbdb369c22de8ab75a5e004d994d17a05..989e0ac703c9b7cb674ff3939d4b4dbad0e70989 100644 (file)
@@ -113,6 +113,8 @@ namespace clang {
       UnmatchedOpeningParens.push_back(LParenLoc);
     }
 
+    /// Are we at the top level within the __VA_OPT__?
+    bool isAtTopLevel() const { return UnmatchedOpeningParens.size() == 1; }
   };
 
   /// A class for tracking whether we're inside a VA_OPT during a
@@ -135,7 +137,8 @@ namespace clang {
 
     unsigned StringifyBefore : 1;
     unsigned CharifyBefore : 1;
-
+    unsigned BeginsWithPlaceholder : 1;
+    unsigned EndsWithPlaceholder : 1;
 
     bool hasStringifyBefore() const {
       assert(!isReset() &&
@@ -151,7 +154,8 @@ namespace clang {
   public:
     VAOptExpansionContext(Preprocessor &PP)
         : VAOptDefinitionContext(PP), LeadingSpaceForStringifiedToken(false),
-          StringifyBefore(false), CharifyBefore(false) {
+          StringifyBefore(false), CharifyBefore(false),
+          BeginsWithPlaceholder(false), EndsWithPlaceholder(false) {
       SyntheticEOFToken.startToken();
       SyntheticEOFToken.setKind(tok::eof);
     }
@@ -162,6 +166,8 @@ namespace clang {
       LeadingSpaceForStringifiedToken = false;
       StringifyBefore = false;
       CharifyBefore = false;
+      BeginsWithPlaceholder = false;
+      EndsWithPlaceholder = false;
     }
 
     const Token &getEOFTok() const { return SyntheticEOFToken; }
@@ -174,7 +180,23 @@ namespace clang {
       LeadingSpaceForStringifiedToken = HasLeadingSpace;
     }
 
+    void hasPlaceholderAfterHashhashAtStart() { BeginsWithPlaceholder = true; }
+    void hasPlaceholderBeforeRParen() {
+      if (isAtTopLevel())
+        EndsWithPlaceholder = true;
+    }
+
 
+    bool beginsWithPlaceholder() const {
+      assert(!isReset() &&
+             "Must only be called if the state has not been reset");
+      return BeginsWithPlaceholder;
+    }
+    bool endsWithPlaceholder() const {
+      assert(!isReset() &&
+             "Must only be called if the state has not been reset");
+      return EndsWithPlaceholder;
+    }
 
     bool hasCharifyBefore() const {
       assert(!isReset() &&
index dd0b79cccc29a8d7a597ebe680aefc4fe9e6c96b..06e3add154881f6ab2a088c4812263e8944b04dd 100644 (file)
@@ -135,15 +135,12 @@ const Token *MacroArgs::getUnexpArgument(unsigned Arg) const {
   return Result;
 }
 
-// This function assumes that the variadic arguments are the tokens
-// corresponding to the last parameter (ellipsis) - and since tokens are
-// separated by the 'eof' token, if that is the only token corresponding to that
-// last parameter, we know no variadic arguments were supplied.
-bool MacroArgs::invokedWithVariadicArgument(const MacroInfo *const MI) const {
+bool MacroArgs::invokedWithVariadicArgument(const MacroInfo *const MI,
+                                            Preprocessor &PP) {
   if (!MI->isVariadic())
     return false;
   const int VariadicArgIndex = getNumMacroArguments() - 1;
-  return getUnexpArgument(VariadicArgIndex)->isNot(tok::eof);
+  return getPreExpArgument(VariadicArgIndex, PP).front().isNot(tok::eof);
 }
 
 /// ArgNeedsPreexpansion - If we can prove that the argument won't be affected
index b0d5286a4f247fb68713b26cc66b195ac6edf90f..9d132a545c8803241237fb5ad68cbd890b88f973 100644 (file)
@@ -243,8 +243,7 @@ void TokenLexer::ExpandFunctionArguments() {
   // we install the newly expanded sequence as the new 'Tokens' list.
   bool MadeChange = false;
 
-  const bool CalledWithVariadicArguments =
-      ActualArgs->invokedWithVariadicArgument(Macro);
+  Optional<bool> CalledWithVariadicArguments;
 
   VAOptExpansionContext VCtx(PP);
 
@@ -291,7 +290,12 @@ void TokenLexer::ExpandFunctionArguments() {
       // this token. Note sawClosingParen() returns true only if the r_paren matches
       // the closing r_paren of the __VA_OPT__.
       if (!Tokens[I].is(tok::r_paren) || !VCtx.sawClosingParen()) {
-        if (!CalledWithVariadicArguments) {
+        // Lazily expand __VA_ARGS__ when we see the first __VA_OPT__.
+        if (!CalledWithVariadicArguments.hasValue()) {
+          CalledWithVariadicArguments =
+              ActualArgs->invokedWithVariadicArgument(Macro, PP);
+        }
+        if (!*CalledWithVariadicArguments) {
           // Skip this token.
           continue;
         }
@@ -314,8 +318,8 @@ void TokenLexer::ExpandFunctionArguments() {
           stringifyVAOPTContents(ResultToks, VCtx,
                                  /*ClosingParenLoc*/ Tokens[I].getLocation());
 
-        } else if (/*No tokens within VAOPT*/ !(
-            ResultToks.size() - VCtx.getNumberOfTokensPriorToVAOpt())) {
+        } else if (/*No tokens within VAOPT*/
+                   ResultToks.size() == VCtx.getNumberOfTokensPriorToVAOpt()) {
           // Treat VAOPT as a placemarker token.  Eat either the '##' before the
           // RHS/VAOPT (if one exists, suggesting that the LHS (if any) to that
           // hashhash was not a placemarker) or the '##'
@@ -326,6 +330,26 @@ void TokenLexer::ExpandFunctionArguments() {
           } else if ((I + 1 != E) && Tokens[I + 1].is(tok::hashhash)) {
             ++I; // Skip the following hashhash.
           }
+        } else {
+          // If there's a ## before the __VA_OPT__, we might have discovered
+          // that the __VA_OPT__ begins with a placeholder. We delay action on
+          // that to now to avoid messing up our stashed count of tokens before
+          // __VA_OPT__.
+          if (VCtx.beginsWithPlaceholder()) {
+            assert(VCtx.getNumberOfTokensPriorToVAOpt() > 0 &&
+                   ResultToks.size() >= VCtx.getNumberOfTokensPriorToVAOpt() &&
+                   ResultToks[VCtx.getNumberOfTokensPriorToVAOpt() - 1].is(
+                       tok::hashhash) &&
+                   "no token paste before __VA_OPT__");
+            ResultToks.erase(ResultToks.begin() +
+                             VCtx.getNumberOfTokensPriorToVAOpt() - 1);
+          }
+          // If the expansion of __VA_OPT__ ends with a placeholder, eat any
+          // following '##' token.
+          if (VCtx.endsWithPlaceholder() && I + 1 != E &&
+              Tokens[I + 1].is(tok::hashhash)) {
+            ++I;
+          }
         }
         VCtx.reset();
         // We processed __VA_OPT__'s closing paren (and the exit out of
@@ -386,6 +410,7 @@ void TokenLexer::ExpandFunctionArguments() {
       !ResultToks.empty() && ResultToks.back().is(tok::hashhash);
     bool PasteBefore = I != 0 && Tokens[I-1].is(tok::hashhash);
     bool PasteAfter = I+1 != E && Tokens[I+1].is(tok::hashhash);
+    bool RParenAfter = I+1 != E && Tokens[I+1].is(tok::r_paren);
 
     assert((!NonEmptyPasteBefore || PasteBefore || VCtx.isInVAOpt()) &&
            "unexpected ## in ResultToks");
@@ -470,6 +495,18 @@ void TokenLexer::ExpandFunctionArguments() {
                                              NextTokGetsSpace);
         ResultToks[FirstResult].setFlagValue(Token::StartOfLine, false);
         NextTokGetsSpace = false;
+      } else {
+        // We're creating a placeholder token. Usually this doesn't matter,
+        // but it can affect paste behavior when at the start or end of a
+        // __VA_OPT__.
+        if (NonEmptyPasteBefore) {
+          // We're imagining a placeholder token is inserted here. If this is
+          // the first token in a __VA_OPT__ after a ##, delete the ##.
+          assert(VCtx.isInVAOpt() && "should only happen inside a __VA_OPT__");
+          VCtx.hasPlaceholderAfterHashhashAtStart();
+        }
+        if (RParenAfter)
+          VCtx.hasPlaceholderBeforeRParen();
       }
       continue;
     }
@@ -534,6 +571,9 @@ void TokenLexer::ExpandFunctionArguments() {
       continue;
     }
 
+    if (RParenAfter)
+      VCtx.hasPlaceholderBeforeRParen();
+
     // If this is on the RHS of a paste operator, we've already copied the
     // paste operator to the ResultToks list, unless the LHS was empty too.
     // Remove it.
@@ -547,6 +587,8 @@ void TokenLexer::ExpandFunctionArguments() {
       if (!VCtx.isInVAOpt() ||
           ResultToks.size() > VCtx.getNumberOfTokensPriorToVAOpt())
         ResultToks.pop_back();
+      else
+        VCtx.hasPlaceholderAfterHashhashAtStart();
     }
 
     // If this is the __VA_ARGS__ token, and if the argument wasn't provided,
index 52f18afb4e63bbb45337b1df945098afc6cd4dd2..7ec4f6128cfa8526a913c1b032d389263f54e32d 100644 (file)
 #define G(a,...)  __VA_OPT__(B a) ## 1\r
 26: F(,1)\r
 26_1: G(,1)\r
-// CHECK: 26: B1\r
-// CHECK: 26_1: B1\r
+// CHECK: 26: B 1\r
+// CHECK: 26_1: B 1\r
 #undef F\r
 #undef G\r
 \r
 27: F(,1)\r
 27_1: F(A0,1)\r
 28: G(,1)\r
-// CHECK: 27: B11\r
+// CHECK: 27: B 11\r
 // CHECK: 27_1: BexpandedA0 11\r
-// CHECK: 28: B11\r
+// CHECK: 28: B 11\r
 \r
 #undef F\r
 #undef G\r
diff --git a/test/Preprocessor/macro_vaopt_p1042r1.cpp b/test/Preprocessor/macro_vaopt_p1042r1.cpp
new file mode 100644 (file)
index 0000000..f12dd20
--- /dev/null
@@ -0,0 +1,30 @@
+ RUN: %clang_cc1 -E %s -pedantic -std=c++2a | FileCheck -strict-whitespace %s\r
+\r
+#define LPAREN() (\r
+#define G(Q) 42\r
+#define F1(R, X, ...)  __VA_OPT__(G R X) )\r
+1: int x = F1(LPAREN(), 0, <:-);\r
+// CHECK: 1: int x = 42;\r
+\r
+#define F2(...) f(0 __VA_OPT__(,) __VA_ARGS__)\r
+#define EMP\r
+2: F2(EMP)\r
+// CHECK: 2: f(0 )\r
+\r
+#define H3(X, ...) #__VA_OPT__(X##X X##X)\r
+3: H3(, 0)\r
+// CHECK: 3: ""\r
+\r
+#define H4(X, ...) __VA_OPT__(a X ## X) ## b\r
+4: H4(, 1)\r
+// CHECK: 4: a b\r
+\r
+#define H4B(X, ...) a ## __VA_OPT__(X ## X b)\r
+4B: H4B(, 1)\r
+// CHECK: 4B: a b\r
+\r
+#define H5A(...) __VA_OPT__()/**/__VA_OPT__()\r
+#define H5B(X) a ## X ## b\r
+#define H5C(X) H5B(X)\r
+5: H5C(H5A())\r
+// CHECK: 5: ab\r
index 5306c3b988b060bb78eb05a4e6896098728af500..2c06f0eb43539c9bc2361e7a8da81c618d0e4766 100755 (executable)
@@ -858,7 +858,7 @@ as the draft C++2a standard evolves.
     </tr>
       <tr> <!-- from Rapperswil -->
         <td><a href="http://wg21.link/p1042r1">P1042R1</a></td>
-        <td class="partial" align="center">Partial</td>
+        <td class="svn" align="center">SVN</td>
       </tr>
     <tr>
       <td>Designated initializers</td>