https://github.com/vangthao95 updated https://github.com/llvm/llvm-project/pull/214247
>From 45060280c3e0e65d6d3a894355b8524bce545591 Mon Sep 17 00:00:00 2001 From: Vang Thao <[email protected]> Date: Tue, 4 Aug 2026 23:24:28 -0400 Subject: [PATCH 1/2] [clang][HIP][CUDA] Disambiguate lambdas from Microsoft attributes With -fms-extensions, a CUDA/HIP lambda capture list can be consumed as a Microsoft attribute list. This causes attributed lambdas used in direct initialization to be misparsed as function declarators. Tentatively inspect the tokens following the capture list and any trailing attributes to identify CUDA/HIP lambdas before parsing Microsoft attributes. Add host and device regression coverage for capture forms and preserve valid Microsoft attribute parsing. Assisted by: Cursor / Claude Opus 4.8 --- clang/include/clang/Parse/Parser.h | 10 +++- clang/lib/Parse/ParseExprCXX.cpp | 17 +++++++ clang/test/Parser/ms-lambda-capture.hip | 61 +++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 clang/test/Parser/ms-lambda-capture.hip diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h index 08b67cb08cdd5..9b4cca05832de 100644 --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -2341,8 +2341,10 @@ class Parser : public CodeCompletionHandler { bool MaybeParseMicrosoftAttributes(ParsedAttributes &Attrs) { bool AttrsParsed = false; + // A '[' may begin a Microsoft attribute or a C++ lambda, so only parse it + // as an attribute after confirming it does not start a lambda. if ((getLangOpts().MicrosoftExt || getLangOpts().HLSL) && - Tok.is(tok::l_square)) { + Tok.is(tok::l_square) && !startsLambdaNotMicrosoftAttribute()) { ParsedAttributes AttrsWithRange(AttrFactory); ParseMicrosoftAttributes(AttrsWithRange); AttrsParsed = !AttrsWithRange.empty(); @@ -4718,6 +4720,12 @@ class Parser : public CodeCompletionHandler { /// If we are not looking at a lambda expression, returns ExprError(). ExprResult TryParseLambdaExpression(); + /// Returns true if the current '[' begins a CUDA/HIP lambda rather than a + /// Microsoft '[]' attribute (enabled under -fms-extensions or HLSL). + /// Restricted to CUDA/HIP, the only mode that allows attributes immediately + /// after a lambda's capture list. + bool startsLambdaNotMicrosoftAttribute(); + /// Parse a lambda introducer. /// \param Intro A LambdaIntroducer filled in with information about the /// contents of the lambda-introducer. diff --git a/clang/lib/Parse/ParseExprCXX.cpp b/clang/lib/Parse/ParseExprCXX.cpp index 06ff45da413ca..a50d968c2a550 100644 --- a/clang/lib/Parse/ParseExprCXX.cpp +++ b/clang/lib/Parse/ParseExprCXX.cpp @@ -748,6 +748,23 @@ ExprResult Parser::TryParseLambdaExpression() { return ParseLambdaExpressionAfterIntroducer(Intro); } +bool Parser::startsLambdaNotMicrosoftAttribute() { + // Restricted to CUDA/HIP, the only mode that allows attributes immediately + // after a lambda's capture list. + if (!getLangOpts().CUDA || Tok.isNot(tok::l_square)) + return false; + + // Skip the '[...]' and any trailing attributes (e.g. CUDA/HIP's + // '__device__'). A lambda then continues with '(', '{' or '<', while an + // attribute is followed by the declaration it applies to (e.g. '[propget] + // int get()'). + RevertingTentativeParsingAction TPA(*this); + ConsumeBracket(); + if (!SkipUntil(tok::r_square, StopAtSemi) || !TrySkipAttributes()) + return false; + return Tok.isOneOf(tok::l_paren, tok::l_brace, tok::less); +} + bool Parser::ParseLambdaIntroducer(LambdaIntroducer &Intro, LambdaIntroducerTentativeParse *Tentative) { if (Tentative) diff --git a/clang/test/Parser/ms-lambda-capture.hip b/clang/test/Parser/ms-lambda-capture.hip new file mode 100644 index 0000000000000..414913308c27a --- /dev/null +++ b/clang/test/Parser/ms-lambda-capture.hip @@ -0,0 +1,61 @@ +// RUN: %clang_cc1 -fms-extensions -DMS_EXTENSIONS -fsyntax-only -verify %s +// RUN: %clang_cc1 -fsyntax-only -verify %s +// RUN: %clang_cc1 -fcuda-is-device -fms-extensions -DMS_EXTENSIONS -fsyntax-only -verify %s +// RUN: %clang_cc1 -fcuda-is-device -fsyntax-only -verify %s +// expected-no-diagnostics + +// A '[' can begin a Microsoft attribute list under -fms-extensions, but it can +// also begin a C++ lambda-introducer. A lambda passed to a constructor via CTAD +// must not have its capture list consumed as a (possibly empty) Microsoft +// attribute, which previously misparsed the initializer as a function +// declarator under -fms-extensions. +// +// A lambda and a Microsoft attribute are told apart by what follows the +// (possibly attributed) '[...]' introducer. A lambda continues with a parameter +// list or body, whereas a Microsoft attribute is followed by the declaration +// it appertains to. + +#include "Inputs/cuda.h" + +template <typename F> struct Wrapper { + F f; + __host__ Wrapper(F fn) : f(fn) {} +}; +template <typename F> Wrapper(F) -> Wrapper<F>; + +void empty_and_defaults() { + Wrapper a([] __device__ () {}); + Wrapper b([=] __device__ () {}); + Wrapper c([&] __device__ () {}); + Wrapper d([] __device__ {}); + (void)a; (void)b; (void)c; (void)d; +} + +void named_captures() { + int values[] = {1}; + int x = 0; + Wrapper a([x] __device__ () { return x; }); + Wrapper b([&x] __device__ () { x++; }); + Wrapper c([y = x] __device__ () { return y; }); + Wrapper d([x, &x2 = x] __device__ () { return x + x2; }); + Wrapper e([a = values[0], b = [] { return 2; }()] __device__ () { + return a + b; + }); + (void)a; (void)b; (void)c; (void)d; (void)e; +} + +struct S { + int m = 0; + void g() { + Wrapper a([this] __device__ () { return m; }); + Wrapper b([*this] __device__ () { return m; }); + (void)a; (void)b; + } +}; + +#ifdef MS_EXTENSIONS +struct MicrosoftAttributeControls { + [] static int empty; + [propget] int get(); +}; +#endif >From 63629d4c7a9bb3dfb1c6686aa910d6d51302abc7 Mon Sep 17 00:00:00 2001 From: Vang Thao <[email protected]> Date: Thu, 6 Aug 2026 20:38:03 -0400 Subject: [PATCH 2/2] Recognize c++23 lambda forms --- clang/include/clang/Parse/Parser.h | 3 +-- clang/lib/Parse/ParseExprCXX.cpp | 24 +++++++++++++------ clang/test/Parser/ms-lambda-capture.hip | 32 +++++++++++++++++++++++-- 3 files changed, 48 insertions(+), 11 deletions(-) diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h index 9b4cca05832de..f00428ba67e66 100644 --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -4722,8 +4722,7 @@ class Parser : public CodeCompletionHandler { /// Returns true if the current '[' begins a CUDA/HIP lambda rather than a /// Microsoft '[]' attribute (enabled under -fms-extensions or HLSL). - /// Restricted to CUDA/HIP, the only mode that allows attributes immediately - /// after a lambda's capture list. + /// Restricted to CUDA/HIP. bool startsLambdaNotMicrosoftAttribute(); /// Parse a lambda introducer. diff --git a/clang/lib/Parse/ParseExprCXX.cpp b/clang/lib/Parse/ParseExprCXX.cpp index a50d968c2a550..ef8ef65c209ab 100644 --- a/clang/lib/Parse/ParseExprCXX.cpp +++ b/clang/lib/Parse/ParseExprCXX.cpp @@ -749,20 +749,30 @@ ExprResult Parser::TryParseLambdaExpression() { } bool Parser::startsLambdaNotMicrosoftAttribute() { - // Restricted to CUDA/HIP, the only mode that allows attributes immediately - // after a lambda's capture list. + // Restricted to CUDA/HIP. if (!getLangOpts().CUDA || Tok.isNot(tok::l_square)) return false; // Skip the '[...]' and any trailing attributes (e.g. CUDA/HIP's - // '__device__'). A lambda then continues with '(', '{' or '<', while an - // attribute is followed by the declaration it applies to (e.g. '[propget] - // int get()'). + // '__device__'). A lambda then continues with a parameter list, body, + // explicit template parameter list, or lambda declarator. An attribute is + // followed by the declaration it applies to (e.g. '[propget] int get()'). RevertingTentativeParsingAction TPA(*this); ConsumeBracket(); - if (!SkipUntil(tok::r_square, StopAtSemi) || !TrySkipAttributes()) + if (!SkipUntil(tok::r_square, StopAtSemi | StopAtCodeCompletion) || + !TrySkipAttributes()) return false; - return Tok.isOneOf(tok::l_paren, tok::l_brace, tok::less); + + // C++23 allows omitting '()' before 'mutable', 'constexpr', 'consteval', and + // 'static'. + while (Tok.isOneOf(tok::kw_mutable, tok::kw_constexpr, tok::kw_consteval, + tok::kw_static)) + ConsumeToken(); + if (!TrySkipAttributes()) + return false; + + return Tok.isOneOf(tok::l_paren, tok::l_brace, tok::less, tok::arrow, + tok::kw_noexcept); } bool Parser::ParseLambdaIntroducer(LambdaIntroducer &Intro, diff --git a/clang/test/Parser/ms-lambda-capture.hip b/clang/test/Parser/ms-lambda-capture.hip index 414913308c27a..7644412fd80fa 100644 --- a/clang/test/Parser/ms-lambda-capture.hip +++ b/clang/test/Parser/ms-lambda-capture.hip @@ -1,6 +1,8 @@ // RUN: %clang_cc1 -fms-extensions -DMS_EXTENSIONS -fsyntax-only -verify %s +// RUN: %clang_cc1 -std=c++23 -fms-extensions -DMS_EXTENSIONS -fsyntax-only -verify %s // RUN: %clang_cc1 -fsyntax-only -verify %s // RUN: %clang_cc1 -fcuda-is-device -fms-extensions -DMS_EXTENSIONS -fsyntax-only -verify %s +// RUN: %clang_cc1 -std=c++23 -fcuda-is-device -fms-extensions -DMS_EXTENSIONS -fsyntax-only -verify %s // RUN: %clang_cc1 -fcuda-is-device -fsyntax-only -verify %s // expected-no-diagnostics @@ -12,8 +14,9 @@ // // A lambda and a Microsoft attribute are told apart by what follows the // (possibly attributed) '[...]' introducer. A lambda continues with a parameter -// list or body, whereas a Microsoft attribute is followed by the declaration -// it appertains to. +// list, body, template parameter list, or lambda declarator ending in an +// unambiguous continuation. A Microsoft attribute is followed by the +// declaration it appertains to. #include "Inputs/cuda.h" @@ -31,6 +34,26 @@ void empty_and_defaults() { (void)a; (void)b; (void)c; (void)d; } +#if __cplusplus >= 202302L +void omitted_parameter_list() { + Wrapper a([] __device__ noexcept {}); + Wrapper b([] __device__ -> int { return 1; }); + Wrapper c([] __device__ mutable {}); + Wrapper d([] __device__ constexpr {}); + Wrapper e([] __device__ consteval {}); + Wrapper f([] __device__ static {}); + Wrapper g([] __device__ mutable constexpr noexcept -> int { return 1; }); + Wrapper h([] __device__ mutable [[]] {}); + (void)a; (void)b; (void)c; (void)d; + (void)e; (void)f; (void)g; (void)h; +} + +void explicit_template_parameter_list() { + Wrapper a([] __device__ <typename T>(T x) { return x; }); + (void)a; +} +#endif + void named_captures() { int values[] = {1}; int x = 0; @@ -57,5 +80,10 @@ struct S { struct MicrosoftAttributeControls { [] static int empty; [propget] int get(); +#if __cplusplus >= 202302L + [] mutable int member; + [] static constexpr int constant = 1; + [] consteval int immediate(); +#endif }; #endif _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
