Author: Mariya Podchishchaeva
Date: 2026-09-09T12:55:41+02:00
New Revision: accf16eec44ec81449fe8cbf0ddd53c1ff567247

URL: 
https://github.com/llvm/llvm-project/commit/accf16eec44ec81449fe8cbf0ddd53c1ff567247
DIFF: 
https://github.com/llvm/llvm-project/commit/accf16eec44ec81449fe8cbf0ddd53c1ff567247.diff

LOG: [clang][CUDA/HIP] Fix parsing of `operator<<<...>` (#218384)

In CUDA mode Lexer merges `<<<` into a single token and Parser fails to
recognize that it is actually a template specialization of `operator<<`
and not a CUDA kernel call expression. Split `<<<` following operator
token to `<<` and `<`.

Added: 
    clang/test/Parser/cuda-operator-lesslessless.cu

Modified: 
    clang/include/clang/Lex/Lexer.h
    clang/lib/Parse/ParseExprCXX.cpp
    clang/lib/Parse/ParseTentative.cpp

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/Lex/Lexer.h b/clang/include/clang/Lex/Lexer.h
index 92bd25888a7b6..b3d8ccdd2d0e3 100644
--- a/clang/include/clang/Lex/Lexer.h
+++ b/clang/include/clang/Lex/Lexer.h
@@ -397,7 +397,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 63f7938c6fc64..860c069e18fca 100644
--- a/clang/lib/Parse/ParseExprCXX.cpp
+++ b/clang/lib/Parse/ParseExprCXX.cpp
@@ -2484,6 +2484,36 @@ 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.
+
+      SourceLocation TokLoc = Tok.getLocation();
+      unsigned LessLessLength = Lexer::getTokenPrefixLength(
+          TokLoc, /*CharNo=*/2, PP.getSourceManager(), getLangOpts());
+
+      SourceLocation LessLessLoc = PP.SplitToken(TokLoc, LessLessLength);
+      Token LessLess = Tok;
+      LessLess.setLocation(LessLessLoc);
+      LessLess.setKind(tok::lessless);
+      LessLess.setLength(LessLessLength);
+
+      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.
+      if (CachingTokens)
+        PP.ReplacePreviousCachedToken({LessLess, Tok});
+
+      SymbolLocations[SymbolIdx++] = LessLessLoc;
+      Op = OO_LessLess;
+      break;
+    }
 
     default:
       break;

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;
   }

diff  --git a/clang/test/Parser/cuda-operator-lesslessless.cu 
b/clang/test/Parser/cuda-operator-lesslessless.cu
new file mode 100644
index 0000000000000..f745f724a8782
--- /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 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}} \
+                                                            // 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}}
+}


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to