[clang] [clang][CUDA/HIP] Fix parsing of `operator<<<...>` (PR #218384)
Mariya Podchishchaeva via cfe-commits
cfe-commits at lists.llvm.org
Tue Sep 8 06:30:10 PDT 2026
https://github.com/Fznamznon updated https://github.com/llvm/llvm-project/pull/218384
>From 233982bacdb13ade41716feae01536d8de88c5a3 Mon Sep 17 00:00:00 2001
From: Mariya Podchishchaeva <Mariya.Podchishchaeva at amd.com>
Date: Fri, 21 Aug 2026 11:56:47 -0500
Subject: [PATCH 1/7] [clang][CUDA/HIP] Fix parsing of `operator<<<...>`
In CUDA mode Lexer merges <<< into a single token and Parser fails to
recognize that it is actually a template specialization of operator<<.
Split <<< following operator token to << and <.
---
clang/include/clang/Lex/Lexer.h | 2 +-
clang/lib/Parse/ParseExprCXX.cpp | 24 +++++++++++++++++++++
clang/test/Parser/cuda-kernel-call-c++11.cu | 17 +++++++++++++++
3 files changed, 42 insertions(+), 1 deletion(-)
diff --git a/clang/include/clang/Lex/Lexer.h b/clang/include/clang/Lex/Lexer.h
index b042e5fb088fa..f96d4d72feb1a 100644
--- a/clang/include/clang/Lex/Lexer.h
+++ b/clang/include/clang/Lex/Lexer.h
@@ -395,7 +395,7 @@ class Lexer : public PreprocessorLexer {
const LangOptions &LangOpts);
/// Get the physical length (including trigraphs and escaped newlines) of the
- /// first \p Characters characters of the token starting at TokStart.
+ /// first \p CharNo characters of the token starting at TokStart.
static unsigned getTokenPrefixLength(SourceLocation TokStart,
unsigned CharNo,
const SourceManager &SM,
diff --git a/clang/lib/Parse/ParseExprCXX.cpp b/clang/lib/Parse/ParseExprCXX.cpp
index f9a0dcc7d53af..883da4907b8eb 100644
--- a/clang/lib/Parse/ParseExprCXX.cpp
+++ b/clang/lib/Parse/ParseExprCXX.cpp
@@ -2485,6 +2485,30 @@ bool Parser::ParseUnqualifiedIdOperator(CXXScopeSpec &SS, bool EnteringContext,
Actions.CodeCompletion().CodeCompleteOperatorName(getCurScope());
return true;
}
+ case tok::lesslessless: {
+ // For CUDA, the Lexer will greedily merge all three <<< in operator<<<
+ // which, in fact, can be a valid template specialization of operator<<,
+ // and will never be a valid kernel launch expression, so split.
+ bool CachingTokens = PP.IsPreviousCachedToken(Tok);
+ // If there was a cache, we should update it when doing token split.
+ // The code below never does.
+ assert(!CachingTokens && "No cache expected");
+
+ SourceLocation TokLoc = Tok.getLocation();
+ unsigned LessLessLength = Lexer::getTokenPrefixLength(
+ TokLoc, /*CharNo=*/2, PP.getSourceManager(), getLangOpts());
+
+ SourceLocation LessLoc = PP.SplitToken(TokLoc, LessLessLength);
+ unsigned OldLength = Tok.getLength();
+
+ Tok.setKind(tok::less);
+ Tok.setLength(OldLength - LessLessLength);
+ Tok.setLocation(LessLoc);
+
+ SymbolLocations[SymbolIdx++] = TokLoc;
+ Op = OO_LessLess;
+ break;
+ }
default:
break;
diff --git a/clang/test/Parser/cuda-kernel-call-c++11.cu b/clang/test/Parser/cuda-kernel-call-c++11.cu
index ef71e2a9acf45..f2fd8ab06ce88 100644
--- a/clang/test/Parser/cuda-kernel-call-c++11.cu
+++ b/clang/test/Parser/cuda-kernel-call-c++11.cu
@@ -1,4 +1,5 @@
// RUN: %clang_cc1 -fsyntax-only -std=c++11 -verify %s
+// RUN: %clang_cc1 -fsyntax-only -std=c++11 -verify -x hip %s
template<typename T=int> struct S {};
template<typename> void f();
@@ -33,3 +34,19 @@ template<typename ...T>
void bar(T... args) {
S<S<V<void(T)...>>> s7;
}
+
+template <typename T, typename T1> void operator<<(T, T1);
+
+struct S1 {};
+
+template <> void operator<<<>(S1, S1);
+
+class C {
+public:
+ template <typename T> void operator<<(T) {}
+};
+
+void foobar() {
+ C CC;
+ CC.operator<<<int>(1);
+}
>From e8e0c20f18b1ebae88006aa4daedd6e0a76913f0 Mon Sep 17 00:00:00 2001
From: Mariya Podchishchaeva <Mariya.Podchishchaeva at amd.com>
Date: Tue, 25 Aug 2026 05:32:37 -0500
Subject: [PATCH 2/7] Fix source location, fix cache, add test
---
clang/lib/Parse/ParseExprCXX.cpp | 22 +++++++++++++--------
clang/test/Parser/cuda-kernel-call-c++11.cu | 20 +++++++++++++++++++
2 files changed, 34 insertions(+), 8 deletions(-)
diff --git a/clang/lib/Parse/ParseExprCXX.cpp b/clang/lib/Parse/ParseExprCXX.cpp
index 883da4907b8eb..a25d814d326c4 100644
--- a/clang/lib/Parse/ParseExprCXX.cpp
+++ b/clang/lib/Parse/ParseExprCXX.cpp
@@ -2489,26 +2489,32 @@ bool Parser::ParseUnqualifiedIdOperator(CXXScopeSpec &SS, bool EnteringContext,
// For CUDA, the Lexer will greedily merge all three <<< in operator<<<
// which, in fact, can be a valid template specialization of operator<<,
// and will never be a valid kernel launch expression, so split.
- bool CachingTokens = PP.IsPreviousCachedToken(Tok);
- // If there was a cache, we should update it when doing token split.
- // The code below never does.
- assert(!CachingTokens && "No cache expected");
SourceLocation TokLoc = Tok.getLocation();
unsigned LessLessLength = Lexer::getTokenPrefixLength(
TokLoc, /*CharNo=*/2, PP.getSourceManager(), getLangOpts());
- SourceLocation LessLoc = PP.SplitToken(TokLoc, LessLessLength);
+ SourceLocation LessLessLoc = PP.SplitToken(TokLoc, LessLessLength);
+ Token LessLess = Tok;
+ LessLess.setLocation(LessLessLoc);
+ LessLess.setKind(tok::lessless);
+ LessLess.setLength(LessLessLength);
+
unsigned OldLength = Tok.getLength();
Tok.setKind(tok::less);
Tok.setLength(OldLength - LessLessLength);
- Tok.setLocation(LessLoc);
+ Tok.setLocation(TokLoc.getLocWithOffset(LessLessLength));
- SymbolLocations[SymbolIdx++] = TokLoc;
+ // Update the cache if there is any.
+ bool CachingTokens = PP.IsPreviousCachedToken(Tok);
+ if (CachingTokens)
+ PP.ReplacePreviousCachedToken({LessLess});
+
+ SymbolLocations[SymbolIdx++] = LessLessLoc;
Op = OO_LessLess;
break;
- }
+ }
default:
break;
diff --git a/clang/test/Parser/cuda-kernel-call-c++11.cu b/clang/test/Parser/cuda-kernel-call-c++11.cu
index f2fd8ab06ce88..9223be4e1812a 100644
--- a/clang/test/Parser/cuda-kernel-call-c++11.cu
+++ b/clang/test/Parser/cuda-kernel-call-c++11.cu
@@ -1,5 +1,6 @@
// RUN: %clang_cc1 -fsyntax-only -std=c++11 -verify %s
// RUN: %clang_cc1 -fsyntax-only -std=c++11 -verify -x hip %s
+// RUN: not %clang_cc1 %s -DSLOC_CHECK 2>&1 | FileCheck %s --strict-whitespace
template<typename T=int> struct S {};
template<typename> void f();
@@ -49,4 +50,23 @@ public:
void foobar() {
C CC;
CC.operator<<<int>(1);
+ CC.template operator<<<int>(1);
+#ifdef SLOC_CHECK
+ // We split <<< into a << followed by a <, check that < has right source
+ // location.
+ CC.operator<<<int;
+ // CHECK: error: expected '>'
+ // CHECK-NEXT: CC.operator<<<int;
+ // CHECK-NEXT: ^
+ // CHECK-NEXT: to match this '<'
+ // CHECK-NEXT: CC.operator<<<int;
+ // CHECK-NEXT: ^
+ CC.template operator<<<int;
+ // CHECK: error: expected '>'
+ // CHECK-NEXT: CC.template operator<<<int;
+ // CHECK-NEXT: ^
+ // CHECK-NEXT: to match this '<'
+ // CHECK-NEXT: CC.template operator<<<int;
+ // CHECK-NEXT: ^
+#endif
}
>From ad58f4031f0b0be0d88f413ab53bda6c37a7bbff Mon Sep 17 00:00:00 2001
From: Mariya Podchishchaeva <Mariya.Podchishchaeva at amd.com>
Date: Tue, 25 Aug 2026 06:13:28 -0500
Subject: [PATCH 3/7] Fix bad test
---
clang/test/Parser/cuda-kernel-call-c++11.cu | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/clang/test/Parser/cuda-kernel-call-c++11.cu b/clang/test/Parser/cuda-kernel-call-c++11.cu
index 9223be4e1812a..4ea91d8477e40 100644
--- a/clang/test/Parser/cuda-kernel-call-c++11.cu
+++ b/clang/test/Parser/cuda-kernel-call-c++11.cu
@@ -1,6 +1,6 @@
// RUN: %clang_cc1 -fsyntax-only -std=c++11 -verify %s
// RUN: %clang_cc1 -fsyntax-only -std=c++11 -verify -x hip %s
-// RUN: not %clang_cc1 %s -DSLOC_CHECK 2>&1 | FileCheck %s --strict-whitespace
+// RUN: not %clang_cc1 -fsyntax-only %s -DSLOC_CHECK 2>&1 | FileCheck %s --strict-whitespace
template<typename T=int> struct S {};
template<typename> void f();
@@ -55,18 +55,18 @@ void foobar() {
// We split <<< into a << followed by a <, check that < has right source
// location.
CC.operator<<<int;
- // CHECK: error: expected '>'
+ // CHECK: [[@LINE-1]]:20: error: expected '>'
// CHECK-NEXT: CC.operator<<<int;
// CHECK-NEXT: ^
- // CHECK-NEXT: to match this '<'
+ // CHECK-NEXT: [[@LINE-4]]:16: note: to match this '<'
// CHECK-NEXT: CC.operator<<<int;
// CHECK-NEXT: ^
CC.template operator<<<int;
- // CHECK: error: expected '>'
+ // CHECK: [[@LINE-1]]:29: error: expected '>'
// CHECK-NEXT: CC.template operator<<<int;
// CHECK-NEXT: ^
- // CHECK-NEXT: to match this '<'
+ // CHECK-NEXT: [[@LINE-4]]:25: note: to match this '<'
// CHECK-NEXT: CC.template operator<<<int;
- // CHECK-NEXT: ^
+ // CHECK-NEXT: ^
#endif
}
>From 4d80f7436780077368bcae253153bc02e92f0062 Mon Sep 17 00:00:00 2001
From: Mariya Podchishchaeva <Mariya.Podchishchaeva at amd.com>
Date: Tue, 8 Sep 2026 08:21:17 -0500
Subject: [PATCH 4/7] Fix cache again
---
clang/lib/Parse/ParseExprCXX.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/clang/lib/Parse/ParseExprCXX.cpp b/clang/lib/Parse/ParseExprCXX.cpp
index a25d814d326c4..59e49e7d8696a 100644
--- a/clang/lib/Parse/ParseExprCXX.cpp
+++ b/clang/lib/Parse/ParseExprCXX.cpp
@@ -2502,14 +2502,14 @@ bool Parser::ParseUnqualifiedIdOperator(CXXScopeSpec &SS, bool EnteringContext,
unsigned OldLength = Tok.getLength();
+ bool CachingTokens = PP.IsPreviousCachedToken(Tok);
Tok.setKind(tok::less);
Tok.setLength(OldLength - LessLessLength);
Tok.setLocation(TokLoc.getLocWithOffset(LessLessLength));
// Update the cache if there is any.
- bool CachingTokens = PP.IsPreviousCachedToken(Tok);
if (CachingTokens)
- PP.ReplacePreviousCachedToken({LessLess});
+ PP.ReplacePreviousCachedToken({LessLess, Tok});
SymbolLocations[SymbolIdx++] = LessLessLoc;
Op = OO_LessLess;
>From 4be85e7d690a0655dbdb3d515bbb9ddf7fd0af48 Mon Sep 17 00:00:00 2001
From: Mariya Podchishchaeva <Mariya.Podchishchaeva at amd.com>
Date: Tue, 8 Sep 2026 08:21:40 -0500
Subject: [PATCH 5/7] Fix tentative parsing too
---
clang/lib/Parse/ParseTentative.cpp | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/clang/lib/Parse/ParseTentative.cpp b/clang/lib/Parse/ParseTentative.cpp
index 07d45925e892e..c71ce09267f8a 100644
--- a/clang/lib/Parse/ParseTentative.cpp
+++ b/clang/lib/Parse/ParseTentative.cpp
@@ -858,6 +858,13 @@ Parser::TPResult Parser::TryParseOperatorId() {
}
break;
+ case tok::lesslessless:
+ // In CUDA/HIP mode the lexer merges <<< into a single token. Inside
+ // operator<<<T> this can only be operator<< followed by a template-arg <,
+ // so treat it as a valid operator-function-id during tentative parsing.
+ ConsumeToken();
+ return TPResult::True;
+
default:
break;
}
>From df41100a7fc512b7668b69bd9ee67ddf0dd62f53 Mon Sep 17 00:00:00 2001
From: Mariya Podchishchaeva <Mariya.Podchishchaeva at amd.com>
Date: Tue, 8 Sep 2026 08:22:02 -0500
Subject: [PATCH 6/7] Move the test to a separate file
---
clang/test/Parser/cuda-kernel-call-c++11.cu | 37 -------------
.../test/Parser/cuda-operator-lesslessless.cu | 53 +++++++++++++++++++
2 files changed, 53 insertions(+), 37 deletions(-)
create mode 100644 clang/test/Parser/cuda-operator-lesslessless.cu
diff --git a/clang/test/Parser/cuda-kernel-call-c++11.cu b/clang/test/Parser/cuda-kernel-call-c++11.cu
index 4ea91d8477e40..ef71e2a9acf45 100644
--- a/clang/test/Parser/cuda-kernel-call-c++11.cu
+++ b/clang/test/Parser/cuda-kernel-call-c++11.cu
@@ -1,6 +1,4 @@
// RUN: %clang_cc1 -fsyntax-only -std=c++11 -verify %s
-// RUN: %clang_cc1 -fsyntax-only -std=c++11 -verify -x hip %s
-// RUN: not %clang_cc1 -fsyntax-only %s -DSLOC_CHECK 2>&1 | FileCheck %s --strict-whitespace
template<typename T=int> struct S {};
template<typename> void f();
@@ -35,38 +33,3 @@ template<typename ...T>
void bar(T... args) {
S<S<V<void(T)...>>> s7;
}
-
-template <typename T, typename T1> void operator<<(T, T1);
-
-struct S1 {};
-
-template <> void operator<<<>(S1, S1);
-
-class C {
-public:
- template <typename T> void operator<<(T) {}
-};
-
-void foobar() {
- C CC;
- CC.operator<<<int>(1);
- CC.template operator<<<int>(1);
-#ifdef SLOC_CHECK
- // We split <<< into a << followed by a <, check that < has right source
- // location.
- CC.operator<<<int;
- // CHECK: [[@LINE-1]]:20: error: expected '>'
- // CHECK-NEXT: CC.operator<<<int;
- // CHECK-NEXT: ^
- // CHECK-NEXT: [[@LINE-4]]:16: note: to match this '<'
- // CHECK-NEXT: CC.operator<<<int;
- // CHECK-NEXT: ^
- CC.template operator<<<int;
- // CHECK: [[@LINE-1]]:29: error: expected '>'
- // CHECK-NEXT: CC.template operator<<<int;
- // CHECK-NEXT: ^
- // CHECK-NEXT: [[@LINE-4]]:25: note: to match this '<'
- // CHECK-NEXT: CC.template operator<<<int;
- // CHECK-NEXT: ^
-#endif
-}
diff --git a/clang/test/Parser/cuda-operator-lesslessless.cu b/clang/test/Parser/cuda-operator-lesslessless.cu
new file mode 100644
index 0000000000000..0c884910be6d8
--- /dev/null
+++ b/clang/test/Parser/cuda-operator-lesslessless.cu
@@ -0,0 +1,53 @@
+// RUN: %clang_cc1 -fsyntax-only -verify %s
+// RUN: %clang_cc1 -fsyntax-only -verify -x hip %s
+// RUN: not %clang_cc1 -fsyntax-only %s -DSLOC_CHECK 2>&1 | FileCheck %s
+
+// Make sure operator followed by <<< is parsed as << and < since int CUDA/HIP
+// it can never be a kernel launch expression.
+
+template <typename T, typename T1> void operator<<(T, T1); // expected-error {{overloaded 'operator<<' must have at least one parameter of class or enumeration type}} \
+ // expected-note {{candidate template ignored: substitution failure [with T = int, T1 = int]}}
+
+struct S1 {};
+
+template <> void operator<<<>(S1, S1);
+
+class C {
+public:
+ template <typename T> void operator<<(T) {}
+};
+
+void foobar() {
+ C CC;
+ CC.operator<<<int>(1);
+ CC.template operator<<<int>(1);
+#ifdef SLOC_CHECK
+ // In CUDA/HIP mode <<< is a single token that gets split into << and <.
+ // Verify that the < retains the correct source location after the split.
+ CC.operator<<<int;
+ // CHECK: [[@LINE-1]]:20: error: expected '>'
+ // CHECK-NEXT: CC.operator<<<int;
+ // CHECK-NEXT: ^
+ // CHECK-NEXT: [[@LINE-4]]:16: note: to match this '<'
+ // CHECK-NEXT: CC.operator<<<int;
+ // CHECK-NEXT: ^
+ CC.template operator<<<int;
+ // CHECK: [[@LINE-1]]:29: error: expected '>'
+ // CHECK-NEXT: CC.template operator<<<int;
+ // CHECK-NEXT: ^
+ // CHECK-NEXT: [[@LINE-4]]:25: note: to match this '<'
+ // CHECK-NEXT: CC.template operator<<<int;
+ // CHECK-NEXT: ^
+#endif
+}
+
+// Verify TryParseOperatorId handles tok::lesslessless as well, so the invalid
+// code below produces clearer errors.
+template<typename T> int operator<<(int, T) { return 0; } // expected-error {{overloaded 'operator<<' must have at least one parameter of class or enumeration type}} \
+ // expected-note {{candidate template ignored: substitution failure [with T = int]}}
+
+void test() {
+ int(operator<<<int>(1, 2)); // expected-error {{no matching function for call to 'operator<<'}} \
+ // expected-note {{in instantiation of function template specialization 'operator<<<int>' requested here}} \
+ // expected-note {{in instantiation of function template specialization 'operator<<<int, int>' requested here}}
+}
>From 1f5a188968a50dbe29df56e43319b42631cfd9c3 Mon Sep 17 00:00:00 2001
From: Mariya Podchishchaeva <Mariya.Podchishchaeva at amd.com>
Date: Tue, 8 Sep 2026 08:29:11 -0500
Subject: [PATCH 7/7] Fix comments
---
clang/test/Parser/cuda-operator-lesslessless.cu | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/test/Parser/cuda-operator-lesslessless.cu b/clang/test/Parser/cuda-operator-lesslessless.cu
index 0c884910be6d8..f745f724a8782 100644
--- a/clang/test/Parser/cuda-operator-lesslessless.cu
+++ b/clang/test/Parser/cuda-operator-lesslessless.cu
@@ -2,7 +2,7 @@
// RUN: %clang_cc1 -fsyntax-only -verify -x hip %s
// RUN: not %clang_cc1 -fsyntax-only %s -DSLOC_CHECK 2>&1 | FileCheck %s
-// Make sure operator followed by <<< is parsed as << and < since int CUDA/HIP
+// Make sure operator followed by <<< is parsed as << and < since in CUDA/HIP
// it can never be a kernel launch expression.
template <typename T, typename T1> void operator<<(T, T1); // expected-error {{overloaded 'operator<<' must have at least one parameter of class or enumeration type}} \
More information about the cfe-commits
mailing list