From: Bart Van Assche <bvanassche@acm.org>
To: linux-sparse@vger.kernel.org
Cc: Linus Torvalds <torvalds@linux-foundation.org>,
Bart Van Assche <bvanassche@acm.org>
Subject: [PATCH 58/73] preprocessor: Add support for __VA_OPT__
Date: Mon, 9 Nov 2026 12:32:44 -0800 [thread overview]
Message-ID: <20261109203259.362845-59-bvanassche@acm.org> (raw)
In-Reply-To: <20261109203259.362845-1-bvanassche@acm.org>
Add support for the __VA_OPT__ functional macro replacement operator
introduced in C23. Expand the contents of __VA_OPT__(...) if and only if
variable arguments are passed to the variadic macro, eliminating trailing
delimiter issues in variadic macro expansion portably.
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
ident-list.h | 1 +
pre-process.c | 331 ++++++++++++++++++---
token.h | 3 +
tokenize.c | 10 +
validation/c23-va-opt.c | 36 +++
validation/preprocessor/c23-va-opt-error.c | 31 ++
validation/preprocessor/c23-va-opt.c | 73 +++++
7 files changed, 446 insertions(+), 39 deletions(-)
create mode 100644 validation/c23-va-opt.c
create mode 100644 validation/preprocessor/c23-va-opt-error.c
create mode 100644 validation/preprocessor/c23-va-opt.c
diff --git a/ident-list.h b/ident-list.h
index 3340f11641fa..b4e01e0a423c 100644
--- a/ident-list.h
+++ b/ident-list.h
@@ -80,6 +80,7 @@ IDENT(c_generic_selections);
IDENT(c_static_assert);
__IDENT(pragma_ident, "__pragma__", 0);
__IDENT(__VA_ARGS___ident, "__VA_ARGS__", 0);
+__IDENT(__VA_OPT___ident, "__VA_OPT__", 0);
__IDENT(__func___ident, "__func__", 0);
__IDENT(__FUNCTION___ident, "__FUNCTION__", 0);
__IDENT(__PRETTY_FUNCTION___ident, "__PRETTY_FUNCTION__", 0);
diff --git a/pre-process.c b/pre-process.c
index 9ebf09cffbdd..8eb70ca7a1a4 100644
--- a/pre-process.c
+++ b/pre-process.c
@@ -640,7 +640,7 @@ static struct token *dup_token(const struct token *token, struct position *strea
static struct token **copy(struct token **where, struct token *list, int *count)
{
int need_copy = --*count;
- while (!eof_token(list)) {
+ do {
struct token *token;
if (need_copy)
token = dup_token(list, &list->pos);
@@ -651,7 +651,7 @@ static struct token **copy(struct token **where, struct token *list, int *count)
*where = token;
where = &token->next;
list = list->next;
- }
+ } while (!eof_token(list));
*where = &eof_token_entry;
return where;
}
@@ -677,11 +677,26 @@ static int handle_kludge(const struct token **p, struct arg *args)
}
}
-static struct token **substitute(struct token **list, const struct token *body, struct arg *args)
+static inline int arg_empty(struct arg *arg)
+{
+ struct token *t = arg->expanded;
+
+ if (!t)
+ return 1;
+ while (token_type(t) == TOKEN_UNTAINT)
+ t = t->next;
+ return eof_token(t);
+}
+
+static struct token **substitute_tokens(struct token **list,
+ struct position *base_pos,
+ const struct token *body,
+ struct arg *args, int vararg_num)
{
- struct position *base_pos = &(*list)->pos;
int *count;
+ int one;
enum {Normal, Placeholder, Concat} state = Normal;
+ int extra_whitespace = 0;
for (; !eof_token(body); body = body->next) {
struct token *added, *arg;
@@ -712,9 +727,57 @@ static struct token **substitute(struct token **list, const struct token *body,
tail = &added->next;
break;
+ case TOKEN_STR_VA_OPT: {
+ struct token *inner = &eof_token_entry;
+
+ if (vararg_num >= 0 && !arg_empty(&args[vararg_num]))
+ substitute_tokens(&inner, base_pos,
+ body->target, args,
+ vararg_num);
+ added = stringify(inner);
+ added->pos.stream = base_pos->stream;
+ added->pos.line = base_pos->line;
+ added->pos.pos = base_pos->pos;
+ added->pos.newline = body->pos.newline;
+ added->pos.whitespace =
+ body->pos.whitespace || extra_whitespace;
+ extra_whitespace = 0;
+ tail = &added->next;
+ break;
+ }
+
+ case TOKEN_VA_OPT: {
+ struct token *inner = &eof_token_entry;
+
+ if (vararg_num >= 0 && !arg_empty(&args[vararg_num]))
+ substitute_tokens(&inner, base_pos,
+ body->target, args,
+ vararg_num);
+ if (eof_token(inner)) {
+ if (body->pos.whitespace && state != Concat &&
+ token_type(body->next) != TOKEN_CONCAT)
+ extra_whitespace = 1;
+ if (state == Concat)
+ state = Normal;
+ else if (token_type(body->next) == TOKEN_CONCAT)
+ state = Placeholder;
+ else
+ state = Normal;
+ continue;
+ }
+ arg = inner;
+ one = 1;
+ count = &one;
+ goto copy_arg;
+ }
+
case TOKEN_STR_ARGUMENT:
arg = args[body->argnum].str;
count = &args[body->argnum].n_str;
+ if (eof_token(arg)) {
+ state = Normal;
+ continue;
+ }
goto copy_arg;
case TOKEN_QUOTED_ARGUMENT:
@@ -740,6 +803,10 @@ static struct token **substitute(struct token **list, const struct token *body,
tail = copy(&added, arg, count);
added->pos.newline = body->pos.newline;
added->pos.whitespace = body->pos.whitespace;
+ if (extra_whitespace) {
+ added->pos.whitespace = 1;
+ extra_whitespace = 0;
+ }
break;
case TOKEN_CONCAT:
@@ -754,6 +821,10 @@ static struct token **substitute(struct token **list, const struct token *body,
if (token_type(body) == TOKEN_IDENT &&
added->ident->tainted)
added->pos.noexpand = 1;
+ if (extra_whitespace) {
+ added->pos.whitespace = 1;
+ extra_whitespace = 0;
+ }
tail = &added->next;
break;
}
@@ -776,6 +847,14 @@ static struct token **substitute(struct token **list, const struct token *body,
return list;
}
+static struct token **substitute(struct token **list, const struct token *body,
+ struct arg *args, int vararg_num)
+{
+ struct position base_pos = (*list)->pos;
+
+ return substitute_tokens(list, &base_pos, body, args, vararg_num);
+}
+
static bool parse_has_include(struct token **list, int how);
static bool parse_has_embed(struct token **list);
@@ -811,8 +890,14 @@ static int expand(struct token **list, struct symbol *sym)
struct token **tail;
struct token *expansion = sym->expansion;
int nargs = sym->arglist ? sym->arglist->count.normal : 0;
+ int vararg_num = (sym->arglist && sym->arglist->count.vararg) ?
+ (sym->arglist->count.normal - 1) :
+ -1;
struct arg args[nargs ? nargs : 1];
+ if (nargs)
+ memset(args, 0, nargs * sizeof(args[0]));
+
if (expanding->tainted) {
token->pos.noexpand = 1;
return 1;
@@ -855,7 +940,7 @@ static int expand(struct token **list, struct symbol *sym)
expanding->tainted = 1;
last = token->next;
- tail = substitute(list, expansion, args);
+ tail = substitute(list, expansion, args, vararg_num);
/*
* Note that it won't be eof - at least TOKEN_UNTAINT will be there.
* We still can lose the newline flag if the sucker expands to nothing,
@@ -2147,6 +2232,8 @@ static int handle_argv_include(struct stream *stream, struct token **list, struc
return handle_include_path(stream, list, token, 2);
}
+static int token_list_different(struct token *list1, struct token *list2);
+
static int token_different(struct token *t1, struct token *t2)
{
int different;
@@ -2164,6 +2251,10 @@ static int token_different(struct token *t1, struct token *t2)
case TOKEN_GNU_KLUDGE:
different = 0;
break;
+ case TOKEN_VA_OPT:
+ case TOKEN_STR_VA_OPT:
+ different = token_list_different(t1->target, t2->target);
+ break;
case TOKEN_NUMBER:
different = strcmp(t1->number, t2->number);
break;
@@ -2240,6 +2331,8 @@ static struct token *parse_arguments(struct token *list)
while (token_type(arg) == TOKEN_IDENT) {
if (arg->ident == &__VA_ARGS___ident)
goto Eva_args;
+ if (arg->ident == &__VA_OPT___ident)
+ goto Eva_opt;
if (!++count->normal)
goto Eargs;
next = arg->next;
@@ -2263,6 +2356,7 @@ static struct token *parse_arguments(struct token *list)
if (match_op(next->next, ')')) {
set_arg_count(next);
next->count.vararg = 1;
+ count->vararg = 1;
next = next->next;
arg->next->next = &eof_token_entry;
return next->next;
@@ -2290,6 +2384,7 @@ static struct token *parse_arguments(struct token *list)
goto Eargs;
set_arg_count(next);
next->count.vararg = 1;
+ count->vararg = 1;
next = next->next;
arg->next->next = &eof_token_entry;
return next;
@@ -2315,6 +2410,10 @@ Ebadstuff:
Enotclosed:
sparse_error(arg->pos, "missing ')' in macro parameter list");
return NULL;
+Eva_opt:
+ sparse_error(arg->pos,
+ "__VA_OPT__ cannot appear in a macro parameter list");
+ return NULL;
Eva_args:
sparse_error(arg->pos, "__VA_ARGS__ can only appear in the expansion of a C99 variadic macro");
return NULL;
@@ -2363,11 +2462,26 @@ static int try_arg(struct token *token, enum token_type type, struct token *argl
return 0;
}
+static struct token *handle_va_opt(struct token **p, struct token *arglist);
+static int parse_expansion_tokens(struct token **expansion_p,
+ struct token *arglist);
+
static struct token *handle_hash(struct token **p, struct token *arglist)
{
struct token *token = *p;
if (arglist) {
struct token *next = token->next;
+ if (token_type(next) == TOKEN_IDENT &&
+ next->ident == &__VA_OPT___ident) {
+ next = handle_va_opt(&token->next, arglist);
+ if (!next)
+ return NULL;
+ token_type(next) = TOKEN_STR_VA_OPT;
+ next->pos.whitespace = token->pos.whitespace;
+ __free_token(token);
+ *p = next;
+ return next;
+ }
if (!try_arg(next, TOKEN_STR_ARGUMENT, arglist))
goto Equote;
next->pos.whitespace = token->pos.whitespace;
@@ -2412,6 +2526,11 @@ static struct token *handle_hashhash(struct token *token, struct token *arglist)
t = handle_hash(&concat->next, arglist);
if (!t)
return NULL;
+ } else if (token_type(t) == TOKEN_IDENT &&
+ t->ident == &__VA_OPT___ident) {
+ t = handle_va_opt(&concat->next, arglist);
+ if (!t)
+ return NULL;
}
is_arg = try_arg(t, TOKEN_QUOTED_ARGUMENT, arglist);
@@ -2437,43 +2556,146 @@ Econcat:
return NULL;
}
-static struct token *parse_expansion(struct token *expansion, struct token *arglist, struct ident *name)
+static struct token *handle_va_opt(struct token **p, struct token *arglist)
{
- struct token *token = expansion;
+ struct token *token = *p;
+ struct token *lparen = token->next;
+ struct token *cur, *prev, *body;
+ int nesting = 1;
+
+ if (!arglist || !arglist->count.vararg) {
+ sparse_error(
+ token->pos,
+ "__VA_OPT__ can only appear in the expansion of a variadic macro");
+ return NULL;
+ }
+ if (!match_op(lparen, '(')) {
+ sparse_error(token->pos, "expected '(' after __VA_OPT__");
+ return NULL;
+ }
+ if (standard < STANDARD_C23 && Wpedantic)
+ warning(token->pos, "__VA_OPT__ is a C23 feature");
+
+ for (cur = arglist->next; !eof_token(cur); cur = cur->next->next) {
+ if (cur->next->count.vararg) {
+ if (!++cur->next->count.normal) {
+ sparse_error(
+ token->pos,
+ "too many instances of argument in body");
+ return NULL;
+ }
+ break;
+ }
+ }
+
+ prev = lparen;
+ for (cur = lparen->next; !eof_token(cur); prev = cur, cur = cur->next) {
+ if (token_type(cur) == TOKEN_IDENT &&
+ cur->ident == &__VA_OPT___ident) {
+ sparse_error(
+ cur->pos,
+ "__VA_OPT__ cannot appear within __VA_OPT__");
+ return NULL;
+ }
+ if (match_op(cur, '('))
+ nesting++;
+ else if (match_op(cur, ')')) {
+ if (!--nesting)
+ break;
+ }
+ }
+ if (nesting) {
+ sparse_error(token->pos, "unterminated __VA_OPT__");
+ return NULL;
+ }
+
+ /* cur is ')' */
+ if (lparen->next != cur && match_op(lparen->next, SPECIAL_HASHHASH)) {
+ sparse_error(
+ lparen->next->pos,
+ "'##' cannot appear at the beginning of __VA_OPT__");
+ return NULL;
+ }
+ if (prev != lparen && match_op(prev, SPECIAL_HASHHASH)) {
+ sparse_error(prev->pos,
+ "'##' cannot appear at the end of __VA_OPT__");
+ return NULL;
+ }
+
+ if (lparen->next == cur) {
+ body = &eof_token_entry;
+ } else {
+ body = lparen->next;
+ prev->next = &eof_token_entry;
+ if (parse_expansion_tokens(&body, arglist) < 0)
+ return NULL;
+ }
+
+ token_type(token) = TOKEN_VA_OPT;
+ token->target = body;
+ token->next = cur->next;
+ __free_token(lparen);
+ __free_token(cur);
+ return token;
+}
+
+static int parse_expansion_tokens(struct token **expansion_p,
+ struct token *arglist)
+{
+ struct token *token = *expansion_p;
struct token **p;
if (match_op(token, SPECIAL_HASHHASH))
goto Econcat;
- for (p = &expansion; !eof_token(token); p = &token->next, token = *p) {
+ for (p = expansion_p; !eof_token(token); p = &token->next, token = *p) {
if (match_op(token, '#')) {
token = handle_hash(p, arglist);
if (!token)
- return NULL;
+ return -1;
+ } else if (token_type(token) == TOKEN_IDENT &&
+ token->ident == &__VA_OPT___ident) {
+ token = handle_va_opt(p, arglist);
+ if (!token)
+ return -1;
}
if (match_op(token->next, SPECIAL_HASHHASH)) {
token = handle_hashhash(token, arglist);
if (!token)
- return NULL;
- } else {
+ return -1;
+ } else if (token_type(token) == TOKEN_IDENT) {
try_arg(token, TOKEN_MACRO_ARGUMENT, arglist);
}
if (token_type(token) == TOKEN_ERROR)
goto Earg;
}
+ return 0;
+
+Econcat:
+ sparse_error(token->pos,
+ "'##' cannot appear at the ends of macro expansion");
+ return -1;
+Earg:
+ sparse_error(token->pos, "too many instances of argument in body");
+ return -1;
+}
+
+static struct token *parse_expansion(struct token *expansion, struct token *arglist, struct ident *name)
+{
+ struct token *token;
+ struct token **p;
+
+ if (parse_expansion_tokens(&expansion, arglist) < 0)
+ return NULL;
+
+ for (p = &expansion; !eof_token(*p); p = &(*p)->next)
+ ;
token = alloc_token(&expansion->pos);
token_type(token) = TOKEN_UNTAINT;
token->ident = name;
token->next = *p;
*p = token;
return expansion;
-
-Econcat:
- sparse_error(token->pos, "'##' cannot appear at the ends of macro expansion");
- return NULL;
-Earg:
- sparse_error(token->pos, "too many instances of argument in body");
- return NULL;
}
static int do_define(struct position pos, struct token *token, struct ident *name,
@@ -2482,6 +2704,12 @@ static int do_define(struct position pos, struct token *token, struct ident *nam
struct symbol *sym;
int ret = 1;
+ if (name == &__VA_ARGS___ident || name == &__VA_OPT___ident) {
+ sparse_error(pos, "\"%s\" cannot be used as a macro name",
+ show_ident(name));
+ return 1;
+ }
+
expansion = parse_expansion(expansion, arglist, name);
if (!expansion)
return 1;
@@ -2656,6 +2884,13 @@ static int do_handle_undef(struct stream *stream, struct token **line, struct to
return 1;
}
+ if (left->ident == &__VA_ARGS___ident ||
+ left->ident == &__VA_OPT___ident) {
+ warning(token->pos, "undefining \"%s\"",
+ show_ident(left->ident));
+ return 1;
+ }
+
sym = lookup_symbol(left->ident, NS_MACRO | NS_UNDEF);
if (sym) {
if (attr < sym->attr)
@@ -3597,6 +3832,44 @@ static int is_VA_ARGS_token(struct token *token)
(token->ident == &__VA_ARGS___ident);
}
+static void dump_macro_tokens(struct token *token, struct token **args,
+ bool top_level)
+{
+ bool first = !top_level;
+
+ while (top_level ? token_type(token) != TOKEN_UNTAINT :
+ !eof_token(token)) {
+ struct token *next = token->next;
+
+ if (!first && token->pos.whitespace)
+ putchar(' ');
+ first = false;
+ switch (token_type(token)) {
+ case TOKEN_CONCAT:
+ printf("##");
+ break;
+ case TOKEN_STR_VA_OPT:
+ printf("#");
+ /* fall-through */
+ case TOKEN_VA_OPT:
+ printf("__VA_OPT__(");
+ dump_macro_tokens(token->target, args, false);
+ putchar(')');
+ break;
+ case TOKEN_STR_ARGUMENT:
+ printf("#");
+ /* fall-through */
+ case TOKEN_QUOTED_ARGUMENT:
+ case TOKEN_MACRO_ARGUMENT:
+ token = args[token->argnum];
+ /* fall-through */
+ default:
+ printf("%s", show_token(token));
+ }
+ token = next;
+ }
+}
+
static void dump_macro(struct symbol *sym)
{
int nargs = sym->arglist ? sym->arglist->count.normal : 0;
@@ -3622,27 +3895,7 @@ static void dump_macro(struct symbol *sym)
putchar(')');
}
- token = sym->expansion;
- while (token_type(token) != TOKEN_UNTAINT) {
- struct token *next = token->next;
- if (token->pos.whitespace)
- putchar(' ');
- switch (token_type(token)) {
- case TOKEN_CONCAT:
- printf("##");
- break;
- case TOKEN_STR_ARGUMENT:
- printf("#");
- /* fall-through */
- case TOKEN_QUOTED_ARGUMENT:
- case TOKEN_MACRO_ARGUMENT:
- token = args[token->argnum];
- /* fall-through */
- default:
- printf("%s", show_token(token));
- }
- token = next;
- }
+ dump_macro_tokens(sym->expansion, args, true);
putchar('\n');
}
diff --git a/token.h b/token.h
index b421f166f1f7..998d83a123c9 100644
--- a/token.h
+++ b/token.h
@@ -110,6 +110,8 @@ enum token_type {
TOKEN_QUOTED_ARGUMENT,
TOKEN_CONCAT,
TOKEN_GNU_KLUDGE,
+ TOKEN_VA_OPT,
+ TOKEN_STR_VA_OPT,
TOKEN_UNTAINT,
TOKEN_ARG_COUNT,
TOKEN_IF,
@@ -200,6 +202,7 @@ struct token {
int argnum;
struct argcount count;
char embedded[4];
+ struct token *target;
};
};
diff --git a/tokenize.c b/tokenize.c
index 4ac40e9e1506..775c54667af7 100644
--- a/tokenize.c
+++ b/tokenize.c
@@ -265,6 +265,12 @@ const char *show_token(const struct token *token)
sprintf(buffer, "<argcnt>");
return buffer;
+ case TOKEN_VA_OPT:
+ return "__VA_OPT__(...)";
+
+ case TOKEN_STR_VA_OPT:
+ return "#__VA_OPT__(...)";
+
case TOKEN_GNU_KLUDGE:
return ",";
@@ -321,6 +327,10 @@ const char *quote_token(const struct token *token)
case TOKEN_UTF8_STRING:
return quote_char(token->string->data,
token->string->length - 1, "u8", '"');
+ case TOKEN_VA_OPT:
+ return "__VA_OPT__(...)";
+ case TOKEN_STR_VA_OPT:
+ return "#__VA_OPT__(...)";
case TOKEN_GNU_KLUDGE:
return ",";
default:
diff --git a/validation/c23-va-opt.c b/validation/c23-va-opt.c
new file mode 100644
index 000000000000..bef475812fdd
--- /dev/null
+++ b/validation/c23-va-opt.c
@@ -0,0 +1,36 @@
+#define EMPTY
+#define COUNT_VA(...) 0 __VA_OPT__(+1)
+static_assert(COUNT_VA() == 0, "empty");
+static_assert(COUNT_VA(EMPTY) == 0, "empty macro");
+static_assert(COUNT_VA(x) == 1, "one");
+static_assert(COUNT_VA(x, y) == 1, "two");
+
+#define ADD_OPT(x, ...) (x __VA_OPT__(+__VA_ARGS__))
+static_assert(ADD_OPT(10) == 10, "add 1");
+static_assert(ADD_OPT(10, 20) == 30, "add 2");
+
+#define STR_OPT(...) #__VA_OPT__(__VA_ARGS__)
+static_assert(sizeof(STR_OPT()) == 1, "str empty");
+static_assert(sizeof(STR_OPT(EMPTY)) == 1, "str empty macro");
+static_assert(sizeof(STR_OPT(abc)) == 4, "str abc");
+
+#define PASTE_OPT(p, ...) p ## __VA_OPT__(_ok)
+enum { val = 10, val_ok = 20 };
+static_assert(PASTE_OPT(val) == 10, "paste empty");
+static_assert(PASTE_OPT(val, 1) == 20, "paste non-empty");
+
+static int foo(int a, ...)
+{
+ return a;
+}
+
+#define CALL_FOO(...) foo(0 __VA_OPT__(, __VA_ARGS__))
+static int test(void)
+{
+ return CALL_FOO() + CALL_FOO(1) + CALL_FOO(1, 2);
+}
+
+/*
+ * check-name: c23-va-opt-compile
+ * check-command: sparse -std=c23 -Wno-decl $file
+ */
diff --git a/validation/preprocessor/c23-va-opt-error.c b/validation/preprocessor/c23-va-opt-error.c
new file mode 100644
index 000000000000..c494bc6f2a16
--- /dev/null
+++ b/validation/preprocessor/c23-va-opt-error.c
@@ -0,0 +1,31 @@
+#define BAD0 __VA_OPT__(1)
+#define BAD1(x) __VA_OPT__(x)
+#define BAD2(...) __VA_OPT__
+#define BAD3(...) __VA_OPT__(1
+#define BAD4(...) __VA_OPT__(__VA_OPT__(1))
+#define BAD5(...) __VA_OPT__(##x)
+#define BAD6(...) __VA_OPT__(x##)
+#define BAD7(__VA_OPT__) 1
+#define __VA_OPT__ 1
+
+/*
+ * check-name: c23-va-opt-error
+ * check-command: sparse -std=c23 -E $file
+ *
+ * check-output-start
+
+
+ * check-output-end
+ *
+ * check-error-start
+preprocessor/c23-va-opt-error.c:1:14: error: __VA_OPT__ can only appear in the expansion of a variadic macro
+preprocessor/c23-va-opt-error.c:2:17: error: __VA_OPT__ can only appear in the expansion of a variadic macro
+preprocessor/c23-va-opt-error.c:3:19: error: expected '(' after __VA_OPT__
+preprocessor/c23-va-opt-error.c:4:19: error: unterminated __VA_OPT__
+preprocessor/c23-va-opt-error.c:5:30: error: __VA_OPT__ cannot appear within __VA_OPT__
+preprocessor/c23-va-opt-error.c:6:30: error: '##' cannot appear at the beginning of __VA_OPT__
+preprocessor/c23-va-opt-error.c:7:31: error: '##' cannot appear at the end of __VA_OPT__
+preprocessor/c23-va-opt-error.c:8:14: error: __VA_OPT__ cannot appear in a macro parameter list
+preprocessor/c23-va-opt-error.c:9:9: error: "__VA_OPT__" cannot be used as a macro name
+ * check-error-end
+ */
diff --git a/validation/preprocessor/c23-va-opt.c b/validation/preprocessor/c23-va-opt.c
new file mode 100644
index 000000000000..b2318faf298f
--- /dev/null
+++ b/validation/preprocessor/c23-va-opt.c
@@ -0,0 +1,73 @@
+#define LOG(fmt, ...) printf(fmt __VA_OPT__(, __VA_ARGS__))
+LOG("empty")
+LOG("empty", )
+LOG("one", 1)
+LOG("two", 1, 2)
+
+#define M(...) __VA_OPT__(1) 2 __VA_OPT__(3)
+M()
+M(a)
+
+#define N(...) __VA_OPT__(foo(bar(baz)))
+N()
+N(1)
+
+#define G(a, b, ...) a b __VA_OPT__(c)
+G(1, 2)
+G(1, 2, )
+G(1, 2, 3)
+
+#define EMPTY
+#define VA_ORDER1(...) __VA_ARGS__ __VA_OPT__(opt)
+#define VA_ORDER2(...) __VA_OPT__(opt) __VA_ARGS__
+VA_ORDER1()
+VA_ORDER2()
+VA_ORDER1(EMPTY)
+VA_ORDER2(EMPTY)
+
+#define K 42
+#define X hello
+#define S(a, ...) #__VA_OPT__(a K __VA_ARGS__)
+S(X)
+S(X, Y)
+S(X, K)
+
+#define PASTE_L(x, ...) x ## __VA_OPT__(_suffix)
+#define PASTE_R(y, ...) __VA_OPT__(prefix_) ## y
+PASTE_L(foo)
+PASTE_L(foo, 1)
+PASTE_R(bar)
+PASTE_R(bar, 1)
+
+#define FN(v) ((v) + 1)
+#define CALL(...) __VA_OPT__(FN)(10)
+CALL()
+CALL(1)
+
+/*
+ * check-name: c23-va-opt
+ * check-command: sparse -std=c23 -E $file
+ *
+ * check-output-start
+
+printf("empty" )
+printf("empty" )
+printf("one" , 1)
+printf("two" , 1, 2)
+2
+1 2 3
+foo(bar(baz))
+1 2
+1 2
+1 2 c
+""
+"hello K Y"
+"hello K 42"
+foo
+foo_suffix
+bar
+prefix_bar
+(10)
+((10) + 1)
+ * check-output-end
+ */
next prev parent reply other threads:[~2026-11-09 20:33 UTC|newest]
Thread overview: 74+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-09 20:31 Bart Van Assche
2026-11-09 20:31 ` [PATCH 01/73] Makefile: Pass '--' to rm in clean target to handle dash-prefixed files Bart Van Assche
2026-11-09 20:31 ` [PATCH 02/73] sparse-llvm: Use LLVMCreateBuilderForModule() with LLVM 24 and later Bart Van Assche
2026-11-09 20:31 ` [PATCH 03/73] typeof_unqual: Strip qualifiers and address space from types and arrays Bart Van Assche
2026-11-09 20:31 ` [PATCH 04/73] bitfield: Pack consecutive _Bool bitfields into byte containers Bart Van Assche
2026-11-09 20:31 ` [PATCH 05/73] allocate: Harden slab allocation bounds, zeroing, and mmap checks Bart Van Assche
2026-11-09 20:31 ` [PATCH 06/73] tokenize: Support 4096-byte identifiers and fix escape bounds Bart Van Assche
2026-11-09 20:31 ` [PATCH 07/73] pre-process: Fix macro expansion and include directive handling Bart Van Assche
2026-11-09 20:31 ` [PATCH 08/73] options, lib: Harden warning buffers and command-line option parsing Bart Van Assche
2026-11-09 20:31 ` [PATCH 09/73] builtin: Fix atomic builtin type checking and object_size bounds Bart Van Assche
2026-11-09 20:31 ` [PATCH 10/73] parse, show-parse: Fix attribute propagation, enum overflow, and bounds Bart Van Assche
2026-11-09 20:31 ` [PATCH 11/73] target-openrisc: Set target_openrisc.mach to MACH_OPENRISC Bart Van Assche
2026-11-09 20:31 ` [PATCH 12/73] evaluate: Fix pointer arithmetic, incomplete types, and qualifiers Bart Van Assche
2026-11-09 20:31 ` [PATCH 13/73] expand: Fix static initializer folding and floating-point casts Bart Van Assche
2026-11-09 20:32 ` [PATCH 14/73] inline: Copy union cast types and VLA bounds when inlining Bart Van Assche
2026-11-09 20:32 ` [PATCH 15/73] linearize, flow: Fix asm operands, switch case bounds, and DSE Bart Van Assche
2026-11-09 20:32 ` [PATCH 16/73] memops, ssa: Walk parent insns in reverse and skip mixed-size SSA Bart Van Assche
2026-11-09 20:32 ` [PATCH 17/73] simplify: Prevent overflow on LLONG_MIN negation and fix simplifications Bart Van Assche
2026-11-09 20:32 ` [PATCH 18/73] sparse: Gate context failure warning on Wcontext and catch underflow Bart Van Assche
2026-11-09 20:32 ` [PATCH 19/73] ast-model: Return AST_N_COLUMNS and inspect child nodes lazily Bart Van Assche
2026-11-09 20:32 ` [PATCH 20/73] c2xml: Guard against recursive symbols and free xmlGetProp() string Bart Van Assche
2026-11-09 20:32 ` [PATCH 21/73] compile-i386: Fix buffer bounds, unsigned '<=' codegen, and null checks Bart Van Assche
2026-11-09 20:32 ` [PATCH 22/73] ctags: Fix line comparison order and handle SYM_RESTRICT and SYM_LABEL Bart Van Assche
2026-11-09 20:32 ` [PATCH 23/73] dissect: Unwrap array element type via base_type() Bart Van Assche
2026-11-09 20:32 ` [PATCH 24/73] example: Fix show_op() rotating buffer dimensions Bart Van Assche
2026-11-09 20:32 ` [PATCH 25/73] graph: Check PSEUDO_SYM before dereferencing insn->func->sym Bart Van Assche
2026-11-09 20:32 ` [PATCH 26/73] scheck: Fix slice() upper bit index calculation Bart Van Assche
2026-11-09 20:32 ` [PATCH 27/73] sparse-llvm: Support arbitrary struct member counts and fix operand types Bart Van Assche
2026-11-09 20:32 ` [PATCH 28/73] inline: Duplicate generic expressions instead of evaluating them Bart Van Assche
2026-11-09 20:32 ` [PATCH 29/73] lib: Avoid mutating fmt before va_start() in warning() Bart Van Assche
2026-11-09 20:32 ` [PATCH 30/73] cse, canon: Cast pointers to uintptr_t before relational comparison Bart Van Assche
2026-11-09 20:32 ` [PATCH 31/73] context: Add "conditional context increment" model Bart Van Assche
2026-11-09 20:32 ` [PATCH 32/73] expand: Simplify signed constants early Bart Van Assche
2026-11-09 20:32 ` [PATCH 33/73] bitwise: Allow all-bits-set as a restricted value Bart Van Assche
2026-11-09 20:32 ` [PATCH 34/73] bitwise: Allow restricted ordered compares with unrestricted values Bart Van Assche
2026-11-09 20:32 ` [PATCH 35/73] bitwise: Keep unrestricted values unrestricted after a cast Bart Van Assche
2026-11-09 20:32 ` [PATCH 36/73] bitwise: Don't remove MOD_SPECIFIER from 'bitwise' types Bart Van Assche
2026-11-09 20:32 ` [PATCH 37/73] bitwise: Allow both regular casts and forced casts for restricted values Bart Van Assche
2026-11-09 20:32 ` [PATCH 38/73] clang-format: Add .clang-format configuration file Bart Van Assche
2026-11-09 20:32 ` [PATCH 39/73] options: Add support for -std=c23, -std=c2x and -std=gnu23 Bart Van Assche
2026-11-09 20:32 ` [PATCH 40/73] preprocessor: Add support for #elifdef and #elifndef Bart Van Assche
2026-11-09 20:32 ` [PATCH 41/73] preprocessor: Add support for __has_include and __has_include_next Bart Van Assche
2026-11-09 20:32 ` [PATCH 42/73] c23: Add support for static_assert, alignas, alignof, thread_local Bart Van Assche
2026-11-09 20:32 ` [PATCH 43/73] c23: Add support for bool, true, and false keywords Bart Van Assche
2026-11-09 20:32 ` [PATCH 44/73] c23: Add support for nullptr and nullptr_t Bart Van Assche
2026-11-09 20:32 ` [PATCH 45/73] c23: Add support for digit separators in numeric literals Bart Van Assche
2026-11-09 20:32 ` [PATCH 46/73] c23: Support unnamed parameters in function definitions Bart Van Assche
2026-11-09 20:32 ` [PATCH 47/73] c23: Support variadic functions without named parameters Bart Van Assche
2026-11-09 20:32 ` [PATCH 48/73] c23: Support labels before declarations and at block ends Bart Van Assche
2026-11-09 20:32 ` [PATCH 49/73] c23: Support auto type inference Bart Van Assche
2026-11-09 20:32 ` [PATCH 50/73] c23: Support constexpr object specifier Bart Van Assche
2026-11-09 20:32 ` [PATCH 51/73] c23: Support enumerations with fixed underlying types Bart Van Assche
2026-11-09 20:32 ` [PATCH 52/73] c23: Support standard [[]] attribute syntax Bart Van Assche
2026-11-09 20:32 ` [PATCH 53/73] preprocessor: Add support for __has_c_attribute Bart Van Assche
2026-11-09 20:32 ` [PATCH 54/73] c23: Support empty initializer braces {} Bart Van Assche
2026-11-09 20:32 ` [PATCH 55/73] preprocessor: Add support for __has_embed Bart Van Assche
2026-11-09 20:32 ` [PATCH 56/73] preprocessor: Add support for #embed directive Bart Van Assche
2026-11-09 20:32 ` [PATCH 57/73] c23: Add support for u8 character constants and string literals Bart Van Assche
2026-11-09 20:32 ` Bart Van Assche [this message]
2026-11-09 20:32 ` [PATCH 59/73] c23: Treat empty parameter list () as (void) and reject K&R functions Bart Van Assche
2026-11-09 20:32 ` [PATCH 60/73] c23: Support storage-class specifiers in compound literals Bart Van Assche
2026-11-09 20:32 ` [PATCH 61/73] c23: Add support for _BitInt(N) Bart Van Assche
2026-11-09 20:32 ` [PATCH 62/73] enum: Support machine mode attributes on enumeration types Bart Van Assche
2026-11-09 20:32 ` [PATCH 63/73] enum: Support __attribute__((packed)) " Bart Van Assche
2026-11-09 20:32 ` [PATCH 64/73] expand: Ignore top-level non-array address space in compare_types() Bart Van Assche
2026-11-09 20:32 ` [PATCH 65/73] builtin: Support __builtin_infer_alloc_token() as a size_t ICE Bart Van Assche
2026-11-09 20:32 ` [PATCH 66/73] builtin: Add support for Linux kernel compiler builtins Bart Van Assche
2026-11-09 20:32 ` [PATCH 67/73] builtin: Add support for __builtin_has_attribute() Bart Van Assche
2026-11-09 20:32 ` [PATCH 68/73] cleanup: Support __attribute__((cleanup(...))) in linearize and -Wcontext Bart Van Assche
2026-11-09 20:32 ` [PATCH 69/73] builtin: Do not evaluate __builtin_clz(0) and __builtin_ctz(0) Bart Van Assche
2026-11-09 20:32 ` [PATCH 70/73] pre-process: Support long token and string expansions dynamically Bart Van Assche
2026-11-09 20:32 ` [PATCH 71/73] x86: Undefine conflicting bitness macros when parsing -m flags Bart Van Assche
2026-11-09 20:32 ` [PATCH 72/73] evaluate: Accept conditional expressions with only one void side Bart Van Assche
2026-11-09 20:32 ` [PATCH 73/73] ci: Add Linux kernel GitHub Actions workflow and build script Bart Van Assche
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261109203259.362845-59-bvanassche@acm.org \
--to=bvanassche@acm.org \
--cc=linux-sparse@vger.kernel.org \
--cc=torvalds@linux-foundation.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox