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
