From: Andy Gibbs Date: Fri, 9 Nov 2012 13:24:30 +0000 (+0000) Subject: Improved support for removing the comma preceding __VA_ARGS__ where __VA_ARGS__ X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=02f0022dfced0d082d979ebfd9185211f4dd0496;p=clang Improved support for removing the comma preceding __VA_ARGS__ where __VA_ARGS__ is empty in a variadic macro expansion. This fixes a divergence in support for the ", ## __VA_ARGS__" GCC extension which differed in behaviour when in strict C99 mode (note: there is no change in behaviour has been made in the gnu99 mode that clang uses by default). In addition, there is improved support for the Microsoft alternative extension ", __VA_ARGS__". git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@167613 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/lib/Lex/TokenLexer.cpp b/lib/Lex/TokenLexer.cpp index 2e3e7c3721..59b747814a 100644 --- a/lib/Lex/TokenLexer.cpp +++ b/lib/Lex/TokenLexer.cpp @@ -119,6 +119,55 @@ void TokenLexer::destroy() { if (ActualArgs) ActualArgs->destroy(PP); } +/// Remove comma ahead of __VA_ARGS__, if present, according to compiler dialect +/// settings. Returns true if the comma is removed. +static bool MaybeRemoveCommaBeforeVaArgs(SmallVector &ResultToks, + bool &NextTokGetsSpace, + bool HasPasteOperator, + MacroInfo *Macro, unsigned MacroArgNo, + Preprocessor &PP) { + // Is the macro argument __VA_ARGS__? + if (!Macro->isVariadic() || MacroArgNo != Macro->getNumArgs()-1) + return false; + + // In Microsoft-compatibility mode, a comma is removed in the expansion + // of " ... , __VA_ARGS__ " if __VA_ARGS__ is empty. This extension is + // not supported by gcc. + if (!HasPasteOperator && !PP.getLangOpts().MicrosoftMode) + return false; + + // GCC removes the comma in the expansion of " ... , ## __VA_ARGS__ " if + // __VA_ARGS__ is empty, but not in strict C99 mode where there are no + // named arguments, where it remains. In all other modes, including C99 + // with GNU extensions, it is removed regardless of named arguments. + // Microsoft also appears to support this extension, unofficially. + if (PP.getLangOpts().C99 && !PP.getLangOpts().GNUMode + && Macro->getNumArgs() < 2) + return false; + + // Is a comma available to be removed? + if (ResultToks.empty() || !ResultToks.back().is(tok::comma)) + return false; + + // Issue an extension diagnostic for the paste operator. + if (HasPasteOperator) + PP.Diag(ResultToks.back().getLocation(), diag::ext_paste_comma); + + // Remove the comma. + ResultToks.pop_back(); + + // If the comma was right after another paste (e.g. "X##,##__VA_ARGS__"), + // then removal of the comma should produce a placemarker token (in C99 + // terms) which we model by popping off the previous ##, giving us a plain + // "X" when __VA_ARGS__ is empty. + if (!ResultToks.empty() && ResultToks.back().is(tok::hashhash)) + ResultToks.pop_back(); + + // Never add a space, even if the comma, ##, or arg had a space. + NextTokGetsSpace = false; + return true; +} + /// Expand the arguments of a function-like macro so that we can quickly /// return preexpanded tokens from Tokens. void TokenLexer::ExpandFunctionArguments() { @@ -199,6 +248,14 @@ void TokenLexer::ExpandFunctionArguments() { !ResultToks.empty() && ResultToks.back().is(tok::hashhash); bool PasteAfter = i+1 != e && Tokens[i+1].is(tok::hashhash); + // In Microsoft mode, remove the comma before __VA_ARGS__ to ensure there + // are no trailing commas if __VA_ARGS__ is empty. + if (!PasteBefore && ActualArgs->isVarargsElidedUse() && + MaybeRemoveCommaBeforeVaArgs(ResultToks, NextTokGetsSpace, + /*HasPasteOperator=*/false, + Macro, ArgNo, PP)) + continue; + // If it is not the LHS/RHS of a ## operator, we must pre-expand the // argument and substitute the expanded tokens into the result. This is // C99 6.10.3.1p1. @@ -321,23 +378,13 @@ void TokenLexer::ExpandFunctionArguments() { // If this is the __VA_ARGS__ token, and if the argument wasn't provided, // and if the macro had at least one real argument, and if the token before - // the ## was a comma, remove the comma. - if ((unsigned)ArgNo == Macro->getNumArgs()-1 && // is __VA_ARGS__ - ActualArgs->isVarargsElidedUse() && // Argument elided. - !ResultToks.empty() && ResultToks.back().is(tok::comma)) { - // Never add a space, even if the comma, ##, or arg had a space. - NextTokGetsSpace = false; - // Remove the paste operator, report use of the extension. - PP.Diag(ResultToks.back().getLocation(), diag::ext_paste_comma); - ResultToks.pop_back(); - - // If the comma was right after another paste (e.g. "X##,##__VA_ARGS__"), - // then removal of the comma should produce a placemarker token (in C99 - // terms) which we model by popping off the previous ##, giving us a plain - // "X" when __VA_ARGS__ is empty. - if (!ResultToks.empty() && ResultToks.back().is(tok::hashhash)) - ResultToks.pop_back(); - } + // the ## was a comma, remove the comma. This is a GCC extension which is + // disabled when using -std=c99. + if (ActualArgs->isVarargsElidedUse()) + MaybeRemoveCommaBeforeVaArgs(ResultToks, NextTokGetsSpace, + /*HasPasteOperator=*/true, + Macro, ArgNo, PP); + continue; } diff --git a/test/Preprocessor/macro_fn_comma_swallow2.c b/test/Preprocessor/macro_fn_comma_swallow2.c new file mode 100644 index 0000000000..93ab2b8366 --- /dev/null +++ b/test/Preprocessor/macro_fn_comma_swallow2.c @@ -0,0 +1,64 @@ +// Test the __VA_ARGS__ comma swallowing extensions of various compiler dialects. + +// RUN: %clang_cc1 -E %s | FileCheck -check-prefix=GCC -strict-whitespace %s +// RUN: %clang_cc1 -E -std=c99 %s | FileCheck -check-prefix=C99 -strict-whitespace %s +// RUN: %clang_cc1 -E -std=c11 %s | FileCheck -check-prefix=C99 -strict-whitespace %s +// RUN: %clang_cc1 -E -x c++ %s | FileCheck -check-prefix=GCC -strict-whitespace %s +// RUN: %clang_cc1 -E -std=gnu99 %s | FileCheck -check-prefix=GCC -strict-whitespace %s +// RUN: %clang_cc1 -E -fms-compatibility %s | FileCheck -check-prefix=MS -strict-whitespace %s +// RUN: %clang_cc1 -E -DNAMED %s | FileCheck -check-prefix=GCC -strict-whitespace %s +// RUN: %clang_cc1 -E -std=c99 -DNAMED %s | FileCheck -check-prefix=C99 -strict-whitespace %s + + +#ifndef NAMED +# define A(...) [ __VA_ARGS__ ] +# define B(...) [ , __VA_ARGS__ ] +# define C(...) [ , ## __VA_ARGS__ ] +# define D(A,...) [ A , ## __VA_ARGS__ ] +# define E(A,...) [ __VA_ARGS__ ## A ] +#else +// These are the GCC named argument versions of the C99-style variadic macros. +// Note that __VA_ARGS__ *may* be used as the name, this is not prohibited! +# define A(__VA_ARGS__...) [ __VA_ARGS__ ] +# define B(__VA_ARGS__...) [ , __VA_ARGS__ ] +# define C(__VA_ARGS__...) [ , ## __VA_ARGS__ ] +# define D(A,__VA_ARGS__...) [ A , ## __VA_ARGS__ ] +# define E(A,__VA_ARGS__...) [ __VA_ARGS__ ## A ] +#endif + + +1: A() B() C() D() E() +2: A(a) B(a) C(a) D(a) E(a) +3: A(,) B(,) C(,) D(,) E(,) +4: A(a,b,c) B(a,b,c) C(a,b,c) D(a,b,c) E(a,b,c) +5: A(a,b,) B(a,b,) C(a,b,) D(a,b,) + +// The GCC ", ## __VA_ARGS__" extension swallows the comma when followed by +// empty __VA_ARGS__. This extension does not apply in -std=c99 mode, but +// does apply in C++. +// +// GCC: 1: [ ] [ , ] [ ] [ ] [ ] +// GCC: 2: [ a ] [ , a ] [ ,a ] [ a ] [ a ] +// GCC: 3: [ , ] [ , , ] [ ,, ] [ , ] [ ] +// GCC: 4: [ a,b,c ] [ , a,b,c ] [ ,a,b,c ] [ a ,b,c ] [ b,ca ] +// GCC: 5: [ a,b, ] [ , a,b, ] [ ,a,b, ] [ a ,b, ] + +// Under C99 standard mode, the GCC ", ## __VA_ARGS__" extension *does not* +// swallow the comma when followed by empty __VA_ARGS__. +// +// C99: 1: [ ] [ , ] [ , ] [ ] [ ] +// C99: 2: [ a ] [ , a ] [ ,a ] [ a ] [ a ] +// C99: 3: [ , ] [ , , ] [ ,, ] [ , ] [ ] +// C99: 4: [ a,b,c ] [ , a,b,c ] [ ,a,b,c ] [ a ,b,c ] [ b,ca ] +// C99: 5: [ a,b, ] [ , a,b, ] [ ,a,b, ] [ a ,b, ] + +// Microsoft's extension is on ", __VA_ARGS__" (without explicit ##) where +// the comma is swallowed when followed by empty __VA_ARGS__. +// +// MS: 1: [ ] [ ] [ ] [ ] [ ] +// MS: 2: [ a ] [ , a ] [ ,a ] [ a ] [ a ] +// MS: 3: [ , ] [ , , ] [ ,, ] [ , ] [ ] +// MS: 4: [ a,b,c ] [ , a,b,c ] [ ,a,b,c ] [ a ,b,c ] [ b,ca ] +// MS: 5: [ a,b, ] [ , a,b, ] [ ,a,b, ] [ a ,b, ] + +// FIXME: Item 3(d) in MS output should be [ ] not [ , ]