Linux SPARSE checker discussions
 help / color / mirror / Atom feed
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
+ */

  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