As part of adding [[]]-style attributes, C2x adds the token :: for use in scoped attribute names.
This patch adds corresponding support for that token in C to GCC. The token is supported both for C2x and for older gnu* standards (on the basis that extensions are normally supported in older gnu* versions; people will expect to be able to use [[]] attributes, before C2x is the default, without needing to use -std=gnu2x). There are no cases in older C standards where the token : can be followed by a token starting with : in syntactically valid sources; the only cases the :: token could break in older standard C thus are ones involving concatenation of pp-tokens where the result does not end up as tokens (e.g., gets stringized). In GNU C extensions, the main case where :: might appear in existing sources is in asm statements, and the C parser is thus made to handle it like two consecutive : tokens, which the C++ parser already does. A limited test of various positionings of :: in asm statements is added to the testsuite (in particular, to cover the syntax error when :: means too many colons but a single : would be OK), but existing tests cover a variety of styles there anyway. Technically there are cases in Objective-C and OpenMP for which this also changes how previously valid code is lexed: the objc-selector-arg syntax allows multiple consecutive : tokens (although I don't think they are particularly useful there), while OpenMP syntax includes array section syntax such as [:] which, before :: was a token, could also be written as [::> (there might be other OpenMP cases potentially affected, I didn't check all the OpenMP syntax in detail). I don't think either of those cases affects the basis for supporting the :: token in all -std=gnu* modes, or that there is any obvious need to special-case handling of CPP_SCOPE tokens for those constructs the way there is for asm statements. cpp_avoid_paste, which determines when spaces need adding between tokens in preprocessed output where there wouldn't otherwise be whitespace between them (e.g. if stringized), already inserts space between : and : unconditionally, rather than only for C++, so no change is needed there (but a C2x test is added that such space is indeed inserted). Bootstrapped with no regressions on x86-64-pc-linux-gnu. Applied to mainline. gcc/c: 2019-10-02 Joseph Myers <jos...@codesourcery.com> * c-parser.c (c_parser_asm_statement): Handle CPP_SCOPE like two CPP_COLON tokens. gcc/testsuite: 2019-10-02 Joseph Myers <jos...@codesourcery.com> * gcc.dg/asm-scope-1.c, gcc.dg/cpp/c11-scope-1.c, gcc.dg/cpp/c17-scope-1.c, gcc.dg/cpp/c2x-scope-1.c, gcc.dg/cpp/c2x-scope-2.c, gcc.dg/cpp/c90-scope-1.c, gcc.dg/cpp/c94-scope-1.c, gcc.dg/cpp/c99-scope-1.c, gcc.dg/cpp/gnu11-scope-1.c, gcc.dg/cpp/gnu17-scope-1.c, gcc.dg/cpp/gnu89-scope-1.c, gcc.dg/cpp/gnu99-scope-1.c: New tests. libcpp: 2019-10-02 Joseph Myers <jos...@codesourcery.com> * include/cpplib.h (struct cpp_options): Add member scope. * init.c (struct lang_flags, lang_defaults): Likewise. (cpp_set_lang): Set scope member of pfile. * lex.c (_cpp_lex_direct): Test CPP_OPTION (pfile, scope) not CPP_OPTION (pfile, cplusplus) for creating CPP_SCOPE tokens. Index: gcc/c/c-parser.c =================================================================== --- gcc/c/c-parser.c (revision 276414) +++ gcc/c/c-parser.c (working copy) @@ -6411,8 +6411,10 @@ The form with asm-goto-operands is valid if and only if the asm-qualifier-list contains goto, and is the only allowed form in that case. - Duplicate asm-qualifiers are not allowed. */ + Duplicate asm-qualifiers are not allowed. + The :: token is considered equivalent to two consecutive : tokens. */ + static tree c_parser_asm_statement (c_parser *parser) { @@ -6509,11 +6511,21 @@ nsections = 3 + is_goto; for (section = 0; section < nsections; ++section) { - if (!c_parser_require (parser, CPP_COLON, - is_goto - ? G_("expected %<:%>") - : G_("expected %<:%> or %<)%>"), - UNKNOWN_LOCATION, is_goto)) + if (c_parser_next_token_is (parser, CPP_SCOPE)) + { + ++section; + if (section == nsections) + { + c_parser_error (parser, "expected %<)%>"); + goto error_close_paren; + } + c_parser_consume_token (parser); + } + else if (!c_parser_require (parser, CPP_COLON, + is_goto + ? G_("expected %<:%>") + : G_("expected %<:%> or %<)%>"), + UNKNOWN_LOCATION, is_goto)) goto error_close_paren; /* Once past any colon, we're no longer a simple asm. */ @@ -6520,6 +6532,7 @@ simple = false; if ((!c_parser_next_token_is (parser, CPP_COLON) + && !c_parser_next_token_is (parser, CPP_SCOPE) && !c_parser_next_token_is (parser, CPP_CLOSE_PAREN)) || section == 3) switch (section) Index: gcc/testsuite/gcc.dg/asm-scope-1.c =================================================================== --- gcc/testsuite/gcc.dg/asm-scope-1.c (nonexistent) +++ gcc/testsuite/gcc.dg/asm-scope-1.c (working copy) @@ -0,0 +1,27 @@ +/* Test :: token handling in asm. */ +/* { dg-do compile } */ +/* { dg-options "-std=gnu2x" } */ + +void +f (void) +{ + asm (""); + asm ("" : ); + asm ("" : :); + asm ("" ::); + asm ("" : : :); + asm ("" :: :); + asm ("" : ::); + asm goto ("" : : : : lab); + asm goto ("" :: : : lab); + asm goto ("" : :: : lab); + asm goto ("" : : :: lab); + asm goto ("" :: :: lab); + lab: ; + /* Test errors when :: is at the end of asm and only one : allowed. */ + asm ("" : : ::); /* { dg-error "expected" } */ + asm ("" :: ::); /* { dg-error "expected" } */ + asm goto ("" : : : :: lab); /* { dg-error "expected" } */ + asm goto ("" :: : :: lab); /* { dg-error "expected" } */ + asm goto ("" : :: :: lab); /* { dg-error "expected" } */ +} Index: gcc/testsuite/gcc.dg/cpp/c11-scope-1.c =================================================================== --- gcc/testsuite/gcc.dg/cpp/c11-scope-1.c (nonexistent) +++ gcc/testsuite/gcc.dg/cpp/c11-scope-1.c (working copy) @@ -0,0 +1,8 @@ +/* Test :: token not in C11. */ +/* { dg-do preprocess } */ +/* { dg-options "-std=c11 -pedantic-errors" } */ + +#define CONCAT(x, y) x ## y + +CONCAT (:, :) /* { dg-error "does not give a valid preprocessing token" } */ +CONCAT (::, >) Index: gcc/testsuite/gcc.dg/cpp/c17-scope-1.c =================================================================== --- gcc/testsuite/gcc.dg/cpp/c17-scope-1.c (nonexistent) +++ gcc/testsuite/gcc.dg/cpp/c17-scope-1.c (working copy) @@ -0,0 +1,8 @@ +/* Test :: token not in C17. */ +/* { dg-do preprocess } */ +/* { dg-options "-std=c17 -pedantic-errors" } */ + +#define CONCAT(x, y) x ## y + +CONCAT (:, :) /* { dg-error "does not give a valid preprocessing token" } */ +CONCAT (::, >) Index: gcc/testsuite/gcc.dg/cpp/c2x-scope-1.c =================================================================== --- gcc/testsuite/gcc.dg/cpp/c2x-scope-1.c (nonexistent) +++ gcc/testsuite/gcc.dg/cpp/c2x-scope-1.c (working copy) @@ -0,0 +1,8 @@ +/* Test :: token in C2x. */ +/* { dg-do preprocess } */ +/* { dg-options "-std=c2x -pedantic-errors" } */ + +#define CONCAT(x, y) x ## y + +CONCAT (:, :) +CONCAT (::, >) /* { dg-error "does not give a valid preprocessing token" } */ Index: gcc/testsuite/gcc.dg/cpp/c2x-scope-2.c =================================================================== --- gcc/testsuite/gcc.dg/cpp/c2x-scope-2.c (nonexistent) +++ gcc/testsuite/gcc.dg/cpp/c2x-scope-2.c (working copy) @@ -0,0 +1,11 @@ +/* Test :: token in C2x: preprocessed output. */ +/* { dg-do preprocess } */ +/* { dg-options "-std=c2x -pedantic-errors -P" } */ + +#define COLON() : +#define TEST() ABC + +/* This must have a space inserted between the two ':' tokens in + preprocessed output. */ +TEST()COLON()COLON()TEST() +/* { dg-final { scan-file c2x-scope-2.i "ABC: :ABC" } } */ Index: gcc/testsuite/gcc.dg/cpp/c90-scope-1.c =================================================================== --- gcc/testsuite/gcc.dg/cpp/c90-scope-1.c (nonexistent) +++ gcc/testsuite/gcc.dg/cpp/c90-scope-1.c (working copy) @@ -0,0 +1,7 @@ +/* Test :: token not in C90. */ +/* { dg-do preprocess } */ +/* { dg-options "-std=c90 -pedantic-errors" } */ + +#define CONCAT(x, y) x ## y + +CONCAT (:, :) /* { dg-error "does not give a valid preprocessing token" } */ Index: gcc/testsuite/gcc.dg/cpp/c94-scope-1.c =================================================================== --- gcc/testsuite/gcc.dg/cpp/c94-scope-1.c (nonexistent) +++ gcc/testsuite/gcc.dg/cpp/c94-scope-1.c (working copy) @@ -0,0 +1,8 @@ +/* Test :: token not in C94. */ +/* { dg-do preprocess } */ +/* { dg-options "-std=iso9899:199409 -pedantic-errors" } */ + +#define CONCAT(x, y) x ## y + +CONCAT (:, :) /* { dg-error "does not give a valid preprocessing token" } */ +CONCAT (::, >) Index: gcc/testsuite/gcc.dg/cpp/c99-scope-1.c =================================================================== --- gcc/testsuite/gcc.dg/cpp/c99-scope-1.c (nonexistent) +++ gcc/testsuite/gcc.dg/cpp/c99-scope-1.c (working copy) @@ -0,0 +1,8 @@ +/* Test :: token not in C99. */ +/* { dg-do preprocess } */ +/* { dg-options "-std=c99 -pedantic-errors" } */ + +#define CONCAT(x, y) x ## y + +CONCAT (:, :) /* { dg-error "does not give a valid preprocessing token" } */ +CONCAT (::, >) Index: gcc/testsuite/gcc.dg/cpp/gnu11-scope-1.c =================================================================== --- gcc/testsuite/gcc.dg/cpp/gnu11-scope-1.c (nonexistent) +++ gcc/testsuite/gcc.dg/cpp/gnu11-scope-1.c (working copy) @@ -0,0 +1,8 @@ +/* Test :: token in gnu11. */ +/* { dg-do preprocess } */ +/* { dg-options "-std=gnu11 -pedantic-errors" } */ + +#define CONCAT(x, y) x ## y + +CONCAT (:, :) +CONCAT (::, >) /* { dg-error "does not give a valid preprocessing token" } */ Index: gcc/testsuite/gcc.dg/cpp/gnu17-scope-1.c =================================================================== --- gcc/testsuite/gcc.dg/cpp/gnu17-scope-1.c (nonexistent) +++ gcc/testsuite/gcc.dg/cpp/gnu17-scope-1.c (working copy) @@ -0,0 +1,8 @@ +/* Test :: token in gnu17. */ +/* { dg-do preprocess } */ +/* { dg-options "-std=gnu17 -pedantic-errors" } */ + +#define CONCAT(x, y) x ## y + +CONCAT (:, :) +CONCAT (::, >) /* { dg-error "does not give a valid preprocessing token" } */ Index: gcc/testsuite/gcc.dg/cpp/gnu89-scope-1.c =================================================================== --- gcc/testsuite/gcc.dg/cpp/gnu89-scope-1.c (nonexistent) +++ gcc/testsuite/gcc.dg/cpp/gnu89-scope-1.c (working copy) @@ -0,0 +1,8 @@ +/* Test :: token in gnu89. */ +/* { dg-do preprocess } */ +/* { dg-options "-std=gnu89 -pedantic-errors" } */ + +#define CONCAT(x, y) x ## y + +CONCAT (:, :) +CONCAT (::, >) /* { dg-error "does not give a valid preprocessing token" } */ Index: gcc/testsuite/gcc.dg/cpp/gnu99-scope-1.c =================================================================== --- gcc/testsuite/gcc.dg/cpp/gnu99-scope-1.c (nonexistent) +++ gcc/testsuite/gcc.dg/cpp/gnu99-scope-1.c (working copy) @@ -0,0 +1,8 @@ +/* Test :: token in gnu99. */ +/* { dg-do preprocess } */ +/* { dg-options "-std=gnu99 -pedantic-errors" } */ + +#define CONCAT(x, y) x ## y + +CONCAT (:, :) +CONCAT (::, >) /* { dg-error "does not give a valid preprocessing token" } */ Index: libcpp/include/cpplib.h =================================================================== --- libcpp/include/cpplib.h (revision 276414) +++ libcpp/include/cpplib.h (working copy) @@ -483,6 +483,9 @@ /* Nonzero for C++2a __VA_OPT__ feature. */ unsigned char va_opt; + /* Nonzero for the '::' token. */ + unsigned char scope; + /* Holds the name of the target (execution) character set. */ const char *narrow_charset; Index: libcpp/init.c =================================================================== --- libcpp/init.c (revision 276414) +++ libcpp/init.c (working copy) @@ -92,32 +92,33 @@ char trigraphs; char utf8_char_literals; char va_opt; + char scope; }; static const struct lang_flags lang_defaults[] = -{ /* c99 c++ xnum xid c11 std digr ulit rlit udlit bincst digsep trig u8chlit vaopt */ - /* GNUC89 */ { 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1 }, - /* GNUC99 */ { 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1 }, - /* GNUC11 */ { 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1 }, - /* GNUC17 */ { 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1 }, - /* GNUC2X */ { 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1 }, - /* STDC89 */ { 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0 }, - /* STDC94 */ { 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0 }, - /* STDC99 */ { 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0 }, - /* STDC11 */ { 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0 }, - /* STDC17 */ { 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0 }, - /* STDC2X */ { 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0 }, - /* GNUCXX */ { 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1 }, - /* CXX98 */ { 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0 }, - /* GNUCXX11 */ { 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1 }, - /* CXX11 */ { 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0 }, - /* GNUCXX14 */ { 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1 }, - /* CXX14 */ { 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 }, - /* GNUCXX17 */ { 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1 }, - /* CXX17 */ { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0 }, - /* GNUCXX2A */ { 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1 }, - /* CXX2A */ { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1 }, - /* ASM */ { 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } +{ /* c99 c++ xnum xid c11 std digr ulit rlit udlit bincst digsep trig u8chlit vaopt scope*/ + /* GNUC89 */ { 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1 }, + /* GNUC99 */ { 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1 }, + /* GNUC11 */ { 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1 }, + /* GNUC17 */ { 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1 }, + /* GNUC2X */ { 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1 }, + /* STDC89 */ { 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0 }, + /* STDC94 */ { 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0 }, + /* STDC99 */ { 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0 }, + /* STDC11 */ { 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0 }, + /* STDC17 */ { 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0 }, + /* STDC2X */ { 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1 }, + /* GNUCXX */ { 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1 }, + /* CXX98 */ { 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1 }, + /* GNUCXX11 */ { 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1 }, + /* CXX11 */ { 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1 }, + /* GNUCXX14 */ { 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1 }, + /* CXX14 */ { 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1 }, + /* GNUCXX17 */ { 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1 }, + /* CXX17 */ { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1 }, + /* GNUCXX2A */ { 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1 }, + /* CXX2A */ { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1 }, + /* ASM */ { 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }; /* Sets internal flags correctly for a given language. */ @@ -143,6 +144,7 @@ CPP_OPTION (pfile, trigraphs) = l->trigraphs; CPP_OPTION (pfile, utf8_char_literals) = l->utf8_char_literals; CPP_OPTION (pfile, va_opt) = l->va_opt; + CPP_OPTION (pfile, scope) = l->scope; } /* Initialize library global state. */ Index: libcpp/lex.c =================================================================== --- libcpp/lex.c (revision 276414) +++ libcpp/lex.c (working copy) @@ -3104,7 +3104,7 @@ case ':': result->type = CPP_COLON; - if (*buffer->cur == ':' && CPP_OPTION (pfile, cplusplus)) + if (*buffer->cur == ':' && CPP_OPTION (pfile, scope)) buffer->cur++, result->type = CPP_SCOPE; else if (*buffer->cur == '>' && CPP_OPTION (pfile, digraphs)) { -- Joseph S. Myers jos...@codesourcery.com