[clang] 44eee65 - [AST] Print NTTP args as string-literals when possible

Zhihao Yuan via cfe-commits cfe-commits at lists.llvm.org
Tue Mar 1 17:37:01 PST 2022


Author: Zhihao Yuan
Date: 2022-03-01T19:34:27-06:00
New Revision: 44eee659f1c530a684fa2e57a983d962b5980620

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

LOG: [AST] Print NTTP args as string-literals when possible

C++20 non-type template parameter prints `MyType<{{116, 104, 105, 115}}>` when the code is as simple as `MyType<"this">`. This patch prints `MyType<{"this"}>`, with one layer of braces preserved for the intermediate structural type to trigger CTAD.

`StringLiteral` handles this case, but `StringLiteral` inside `APValue` code looks like a circular dependency. The proposed patch implements a cheap strategy to emit string literals in diagnostic messages only when they are readable and fall back to integer sequences.

Reviewed By: aaron.ballman

Differential Revision: https://reviews.llvm.org/D115031

Added: 
    clang/test/SemaCXX/cxx2a-nttp-printing.cpp
    clang/test/SemaTemplate/temp_arg_string_printing.cpp

Modified: 
    clang/include/clang/AST/DeclTemplate.h
    clang/include/clang/AST/PrettyPrinter.h
    clang/include/clang/Basic/CharInfo.h
    clang/lib/AST/APValue.cpp
    clang/lib/AST/ASTDiagnostic.cpp
    clang/lib/AST/DeclTemplate.cpp
    clang/lib/AST/Expr.cpp
    clang/lib/AST/StmtPrinter.cpp
    clang/lib/AST/TemplateBase.cpp
    clang/lib/Sema/Sema.cpp
    clang/unittests/AST/TypePrinterTest.cpp

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/AST/DeclTemplate.h b/clang/include/clang/AST/DeclTemplate.h
index 319e605a8a1c5..0459ee8fb6164 100644
--- a/clang/include/clang/AST/DeclTemplate.h
+++ b/clang/include/clang/AST/DeclTemplate.h
@@ -3313,10 +3313,12 @@ class TemplateParamObjectDecl : public ValueDecl,
 
   /// Print this object as an equivalent expression.
   void printAsExpr(llvm::raw_ostream &OS) const;
+  void printAsExpr(llvm::raw_ostream &OS, const PrintingPolicy &Policy) const;
 
   /// Print this object as an initializer suitable for a variable of the
   /// object's type.
   void printAsInit(llvm::raw_ostream &OS) const;
+  void printAsInit(llvm::raw_ostream &OS, const PrintingPolicy &Policy) const;
 
   const APValue &getValue() const { return Value; }
 

diff  --git a/clang/include/clang/AST/PrettyPrinter.h b/clang/include/clang/AST/PrettyPrinter.h
index fd40328d8dcf7..54cb57227f7a0 100644
--- a/clang/include/clang/AST/PrettyPrinter.h
+++ b/clang/include/clang/AST/PrettyPrinter.h
@@ -74,7 +74,7 @@ struct PrintingPolicy {
         SuppressImplicitBase(false), FullyQualifiedName(false),
         PrintCanonicalTypes(false), PrintInjectedClassNameWithArguments(true),
         UsePreferredNames(true), AlwaysIncludeTypeForTemplateArgument(false),
-        CleanUglifiedParameters(false) {}
+        CleanUglifiedParameters(false), EntireContentsOfLargeArray(true) {}
 
   /// Adjust this printing policy for cases where it's known that we're
   /// printing C++ code (for instance, if AST dumping reaches a C++-only
@@ -286,6 +286,10 @@ struct PrintingPolicy {
   /// This only affects parameter names, and so describes a compatible API.
   unsigned CleanUglifiedParameters : 1;
 
+  /// Whether to print the entire array initializers, especially on non-type
+  /// template parameters, no matter how many elements there are.
+  unsigned EntireContentsOfLargeArray : 1;
+
   /// Callbacks to use to allow the behavior of printing to be customized.
   const PrintingCallbacks *Callbacks = nullptr;
 };

diff  --git a/clang/include/clang/Basic/CharInfo.h b/clang/include/clang/Basic/CharInfo.h
index c751b6a005e28..7d41193835089 100644
--- a/clang/include/clang/Basic/CharInfo.h
+++ b/clang/include/clang/Basic/CharInfo.h
@@ -38,15 +38,16 @@ namespace charinfo {
   };
 } // end namespace charinfo
 
-/// Returns true if this is an ASCII character.
+/// Returns true if a byte is an ASCII character.
 LLVM_READNONE inline bool isASCII(char c) {
   return static_cast<unsigned char>(c) <= 127;
 }
 
 LLVM_READNONE inline bool isASCII(unsigned char c) { return c <= 127; }
 
-/// Returns true if this is an ASCII character.
+/// Returns true if a codepoint is an ASCII character.
 LLVM_READNONE inline bool isASCII(uint32_t c) { return c <= 127; }
+LLVM_READNONE inline bool isASCII(int64_t c) { return 0 <= c && c <= 127; }
 
 /// Returns true if this is a valid first character of a C identifier,
 /// which is [a-zA-Z_].
@@ -162,6 +163,44 @@ LLVM_READONLY inline bool isRawStringDelimBody(unsigned char c) {
                           CHAR_DIGIT|CHAR_UNDER|CHAR_RAWDEL)) != 0;
 }
 
+enum class EscapeChar {
+  Single = 1,
+  Double = 2,
+  SingleAndDouble = static_cast<int>(Single) | static_cast<int>(Double),
+};
+
+/// Return C-style escaped string for special characters, or an empty string if
+/// there is no such mapping.
+template <EscapeChar Opt, class CharT>
+LLVM_READONLY inline auto escapeCStyle(CharT Ch) -> StringRef {
+  switch (Ch) {
+  case '\\':
+    return "\\\\";
+  case '\'':
+    if ((static_cast<int>(Opt) & static_cast<int>(EscapeChar::Single)) == 0)
+      break;
+    return "\\'";
+  case '"':
+    if ((static_cast<int>(Opt) & static_cast<int>(EscapeChar::Double)) == 0)
+      break;
+    return "\\\"";
+  case '\a':
+    return "\\a";
+  case '\b':
+    return "\\b";
+  case '\f':
+    return "\\f";
+  case '\n':
+    return "\\n";
+  case '\r':
+    return "\\r";
+  case '\t':
+    return "\\t";
+  case '\v':
+    return "\\v";
+  }
+  return {};
+}
 
 /// Converts the given ASCII character to its lowercase equivalent.
 ///

diff  --git a/clang/lib/AST/APValue.cpp b/clang/lib/AST/APValue.cpp
index ef333c7711663..9a09da774a469 100644
--- a/clang/lib/AST/APValue.cpp
+++ b/clang/lib/AST/APValue.cpp
@@ -625,6 +625,67 @@ static double GetApproxValue(const llvm::APFloat &F) {
   return V.convertToDouble();
 }
 
+static bool TryPrintAsStringLiteral(raw_ostream &Out,
+                                    const PrintingPolicy &Policy,
+                                    const ArrayType *ATy,
+                                    ArrayRef<APValue> Inits) {
+  if (Inits.empty())
+    return false;
+
+  QualType Ty = ATy->getElementType();
+  if (!Ty->isAnyCharacterType())
+    return false;
+
+  // Nothing we can do about a sequence that is not null-terminated
+  if (!Inits.back().getInt().isZero())
+    return false;
+  else
+    Inits = Inits.drop_back();
+
+  llvm::SmallString<40> Buf;
+  Buf.push_back('"');
+
+  // Better than printing a two-digit sequence of 10 integers.
+  constexpr size_t MaxN = 36;
+  StringRef Ellipsis;
+  if (Inits.size() > MaxN && !Policy.EntireContentsOfLargeArray) {
+    Ellipsis = "[...]";
+    Inits =
+        Inits.take_front(std::min(MaxN - Ellipsis.size() / 2, Inits.size()));
+  }
+
+  for (auto &Val : Inits) {
+    int64_t Char64 = Val.getInt().getExtValue();
+    if (!isASCII(Char64))
+      return false; // Bye bye, see you in integers.
+    auto Ch = static_cast<unsigned char>(Char64);
+    // The diagnostic message is 'quoted'
+    StringRef Escaped = escapeCStyle<EscapeChar::SingleAndDouble>(Ch);
+    if (Escaped.empty()) {
+      if (!isPrintable(Ch))
+        return false;
+      Buf.emplace_back(Ch);
+    } else {
+      Buf.append(Escaped);
+    }
+  }
+
+  Buf.append(Ellipsis);
+  Buf.push_back('"');
+
+  if (Ty->isWideCharType())
+    Out << 'L';
+  else if (Ty->isChar8Type())
+    Out << "u8";
+  else if (Ty->isChar16Type())
+    Out << 'u';
+  else if (Ty->isChar32Type())
+    Out << 'U';
+
+  Out << Buf;
+  return true;
+}
+
 void APValue::printPretty(raw_ostream &Out, const ASTContext &Ctx,
                           QualType Ty) const {
   printPretty(Out, Ctx.getPrintingPolicy(), Ty, &Ctx);
@@ -795,17 +856,23 @@ void APValue::printPretty(raw_ostream &Out, const PrintingPolicy &Policy,
   }
   case APValue::Array: {
     const ArrayType *AT = Ty->castAsArrayTypeUnsafe();
+    unsigned N = getArrayInitializedElts();
+    if (N != 0 && TryPrintAsStringLiteral(Out, Policy, AT,
+                                          {&getArrayInitializedElt(0), N}))
+      return;
     QualType ElemTy = AT->getElementType();
     Out << '{';
-    if (unsigned N = getArrayInitializedElts()) {
-      getArrayInitializedElt(0).printPretty(Out, Policy, ElemTy, Ctx);
-      for (unsigned I = 1; I != N; ++I) {
+    unsigned I = 0;
+    switch (N) {
+    case 0:
+      for (; I != N; ++I) {
         Out << ", ";
-        if (I == 10) {
-          // Avoid printing out the entire contents of large arrays.
-          Out << "...";
-          break;
+        if (I == 10 && !Policy.EntireContentsOfLargeArray) {
+          Out << "...}";
+          return;
         }
+        LLVM_FALLTHROUGH;
+      default:
         getArrayInitializedElt(I).printPretty(Out, Policy, ElemTy, Ctx);
       }
     }

diff  --git a/clang/lib/AST/ASTDiagnostic.cpp b/clang/lib/AST/ASTDiagnostic.cpp
index 724ede272fbfa..9f5e5ef1e96e6 100644
--- a/clang/lib/AST/ASTDiagnostic.cpp
+++ b/clang/lib/AST/ASTDiagnostic.cpp
@@ -1874,7 +1874,7 @@ class TemplateDiff {
         // FIXME: Diffing the APValue would be neat.
         // FIXME: Suppress this and use the full name of the declaration if the
         // parameter is a pointer or reference.
-        TPO->printAsInit(OS);
+        TPO->printAsInit(OS, Policy);
         return;
       }
       VD->printName(OS);

diff  --git a/clang/lib/AST/DeclTemplate.cpp b/clang/lib/AST/DeclTemplate.cpp
index d9ff3517a589c..3b4ed3107e5a8 100644
--- a/clang/lib/AST/DeclTemplate.cpp
+++ b/clang/lib/AST/DeclTemplate.cpp
@@ -1514,12 +1514,20 @@ void TemplateParamObjectDecl::printName(llvm::raw_ostream &OS) const {
 }
 
 void TemplateParamObjectDecl::printAsExpr(llvm::raw_ostream &OS) const {
-  const ASTContext &Ctx = getASTContext();
-  getType().getUnqualifiedType().print(OS, Ctx.getPrintingPolicy());
-  printAsInit(OS);
+  printAsExpr(OS, getASTContext().getPrintingPolicy());
+}
+
+void TemplateParamObjectDecl::printAsExpr(llvm::raw_ostream &OS,
+                                          const PrintingPolicy &Policy) const {
+  getType().getUnqualifiedType().print(OS, Policy);
+  printAsInit(OS, Policy);
 }
 
 void TemplateParamObjectDecl::printAsInit(llvm::raw_ostream &OS) const {
-  const ASTContext &Ctx = getASTContext();
-  getValue().printPretty(OS, Ctx, getType());
+  printAsInit(OS, getASTContext().getPrintingPolicy());
+}
+
+void TemplateParamObjectDecl::printAsInit(llvm::raw_ostream &OS,
+                                          const PrintingPolicy &Policy) const {
+  getValue().printPretty(OS, Policy, getType(), &getASTContext());
 }

diff  --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index 45e94847caee8..e7cf33ef512ce 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -960,40 +960,10 @@ void CharacterLiteral::print(unsigned Val, CharacterKind Kind,
     break;
   }
 
-  switch (Val) {
-  case '\\':
-    OS << "'\\\\'";
-    break;
-  case '\'':
-    OS << "'\\''";
-    break;
-  case '\a':
-    // TODO: K&R: the meaning of '\\a' is 
diff erent in traditional C
-    OS << "'\\a'";
-    break;
-  case '\b':
-    OS << "'\\b'";
-    break;
-  // Nonstandard escape sequence.
-  /*case '\e':
-    OS << "'\\e'";
-    break;*/
-  case '\f':
-    OS << "'\\f'";
-    break;
-  case '\n':
-    OS << "'\\n'";
-    break;
-  case '\r':
-    OS << "'\\r'";
-    break;
-  case '\t':
-    OS << "'\\t'";
-    break;
-  case '\v':
-    OS << "'\\v'";
-    break;
-  default:
+  StringRef Escaped = escapeCStyle<EscapeChar::Single>(Val);
+  if (!Escaped.empty()) {
+    OS << "'" << Escaped << "'";
+  } else {
     // A character literal might be sign-extended, which
     // would result in an invalid \U escape sequence.
     // FIXME: multicharacter literals such as '\xFF\xFF\xFF\xFF'
@@ -1163,8 +1133,9 @@ void StringLiteral::outputString(raw_ostream &OS) const {
 
   unsigned LastSlashX = getLength();
   for (unsigned I = 0, N = getLength(); I != N; ++I) {
-    switch (uint32_t Char = getCodeUnit(I)) {
-    default:
+    uint32_t Char = getCodeUnit(I);
+    StringRef Escaped = escapeCStyle<EscapeChar::Double>(Char);
+    if (Escaped.empty()) {
       // FIXME: Convert UTF-8 back to codepoints before rendering.
 
       // Convert UTF-16 surrogate pairs back to codepoints before rendering.
@@ -1192,7 +1163,7 @@ void StringLiteral::outputString(raw_ostream &OS) const {
           for (/**/; Shift >= 0; Shift -= 4)
             OS << Hex[(Char >> Shift) & 15];
           LastSlashX = I;
-          break;
+          continue;
         }
 
         if (Char > 0xffff)
@@ -1205,7 +1176,7 @@ void StringLiteral::outputString(raw_ostream &OS) const {
            << Hex[(Char >>  8) & 15]
            << Hex[(Char >>  4) & 15]
            << Hex[(Char >>  0) & 15];
-        break;
+        continue;
       }
 
       // If we used \x... for the previous character, and this character is a
@@ -1230,17 +1201,9 @@ void StringLiteral::outputString(raw_ostream &OS) const {
            << (char)('0' + ((Char >> 6) & 7))
            << (char)('0' + ((Char >> 3) & 7))
            << (char)('0' + ((Char >> 0) & 7));
-      break;
-    // Handle some common non-printable cases to make dumps prettier.
-    case '\\': OS << "\\\\"; break;
-    case '"': OS << "\\\""; break;
-    case '\a': OS << "\\a"; break;
-    case '\b': OS << "\\b"; break;
-    case '\f': OS << "\\f"; break;
-    case '\n': OS << "\\n"; break;
-    case '\r': OS << "\\r"; break;
-    case '\t': OS << "\\t"; break;
-    case '\v': OS << "\\v"; break;
+    } else {
+      // Handle some common non-printable cases to make dumps prettier.
+      OS << Escaped;
     }
   }
   OS << '"';

diff  --git a/clang/lib/AST/StmtPrinter.cpp b/clang/lib/AST/StmtPrinter.cpp
index a6aa9fe45b027..1cee3a1b4650c 100644
--- a/clang/lib/AST/StmtPrinter.cpp
+++ b/clang/lib/AST/StmtPrinter.cpp
@@ -1023,7 +1023,7 @@ void StmtPrinter::VisitDeclRefExpr(DeclRefExpr *Node) {
     return;
   }
   if (const auto *TPOD = dyn_cast<TemplateParamObjectDecl>(Node->getDecl())) {
-    TPOD->printAsExpr(OS);
+    TPOD->printAsExpr(OS, Policy);
     return;
   }
   if (NestedNameSpecifier *Qualifier = Node->getQualifier())

diff  --git a/clang/lib/AST/TemplateBase.cpp b/clang/lib/AST/TemplateBase.cpp
index 619ce42f9dd1d..3418401517c84 100644
--- a/clang/lib/AST/TemplateBase.cpp
+++ b/clang/lib/AST/TemplateBase.cpp
@@ -434,7 +434,7 @@ void TemplateArgument::print(const PrintingPolicy &Policy, raw_ostream &Out,
     NamedDecl *ND = getAsDecl();
     if (getParamTypeForDecl()->isRecordType()) {
       if (auto *TPO = dyn_cast<TemplateParamObjectDecl>(ND)) {
-        TPO->printAsInit(Out);
+        TPO->printAsInit(Out, Policy);
         break;
       }
     }

diff  --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index db3eda622639f..f1dc02d0b6dd9 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -114,6 +114,9 @@ PrintingPolicy Sema::getPrintingPolicy(const ASTContext &Context,
     }
   }
 
+  // Shorten the data output if needed
+  Policy.EntireContentsOfLargeArray = false;
+
   return Policy;
 }
 

diff  --git a/clang/test/SemaCXX/cxx2a-nttp-printing.cpp b/clang/test/SemaCXX/cxx2a-nttp-printing.cpp
new file mode 100644
index 0000000000000..f1441551e408c
--- /dev/null
+++ b/clang/test/SemaCXX/cxx2a-nttp-printing.cpp
@@ -0,0 +1,34 @@
+// RUN: %clang_cc1 %s -std=c++20 -fsyntax-only -verify
+
+template <int N> struct Str {
+  constexpr Str(char const (&s)[N]) { __builtin_memcpy(value, s, N); }
+  char value[N];
+};
+
+template <Str V> class ASCII {};
+
+void Foo(ASCII<"this nontype template argument is too long to print">); // expected-note {{no known conversion from 'ASCII<{"this nontype template argument is too long"}>' to 'ASCII<{"this nontype template argument is too long to print"}>'}}
+void Bar(ASCII<"this nttp argument is too short">);                     // expected-note {{no known conversion from 'ASCII<{{119, 97, 105, 116, 32, 97, 32, 115, 27, 99, 111, 110, 100, 0}}>' to 'ASCII<{"this nttp argument is too short"}>'}}
+void Meow(ASCII<"what|">);                                              // expected-note {{no known conversion from 'ASCII<{"what??!"}>' to 'ASCII<{"what|"}>' for 1st argument}}
+
+void test_ascii() {
+  ASCII<"this nontype template argument"
+        " is too long">
+      a;
+  Foo(a);                 // expected-error {{no matching function}}
+  decltype(a)::display(); // expected-error {{no member named 'display' in 'ASCII<{"this nontype template argument is [...]"}>'}}
+}
+
+void test_non_ascii() {
+  ASCII<"wait a s\033cond"> a;
+  Bar(a);                 // expected-error {{no matching function}}
+  decltype(a)::display(); // expected-error {{no member named 'display' in 'ASCII<{{119, 97, 105, 116, 32, 97, 32, 115, 27, 99, ...}}>'}}
+}
+
+// The dialects (C++20 and above) that accept string literals as non-type
+// template arguments do not support trigraphs.
+void test_trigraph() {
+  ASCII<"what??!"> a;     // expected-warning {{trigraph ignored}}
+  Meow(a);                // expected-error {{no matching function}}
+  decltype(a)::display(); // expected-error {{no member named 'display' in 'ASCII<{"what??!"}>'}}
+}

diff  --git a/clang/test/SemaTemplate/temp_arg_string_printing.cpp b/clang/test/SemaTemplate/temp_arg_string_printing.cpp
new file mode 100644
index 0000000000000..5b73ef7edb7b6
--- /dev/null
+++ b/clang/test/SemaTemplate/temp_arg_string_printing.cpp
@@ -0,0 +1,129 @@
+// RUN: %clang_cc1 -std=c++20 -fsyntax-only -ast-print %s | FileCheck %s
+
+using size_t = __SIZE_TYPE__;
+static_assert(__has_builtin(__make_integer_seq));
+
+template <class T, T... I> class idx_seq {};
+template <size_t N> using make_idx_seq = __make_integer_seq<idx_seq, size_t, N>;
+
+template <class CharT, size_t N>
+struct Str {
+  constexpr Str(CharT const (&s)[N]) : Str(s, make_idx_seq<N>()) {}
+  CharT value[N];
+
+private:
+  template <size_t... I>
+  constexpr Str(CharT const (&s)[N], idx_seq<size_t, I...>) : value{s[I]...} {}
+};
+
+template <Str> class ASCII {};
+
+void not_string() {
+  // CHECK{LITERAL}: ASCII<{{9, -1, 42}}>
+  new ASCII<(int[]){9, -1, 42}>;
+  // CHECK{LITERAL}: ASCII<{{3.140000e+00, 0.000000e+00, 4.200000e+01}}>
+  new ASCII<(double[]){3.14, 0., 42.}>;
+}
+
+void narrow() {
+  // CHECK{LITERAL}: ASCII<{""}>
+  new ASCII<"">;
+  // CHECK{LITERAL}: ASCII<{"the quick brown fox jumps"}>
+  new ASCII<"the quick brown fox jumps">;
+  // CHECK{LITERAL}: ASCII<{"OVER THE LAZY DOG 0123456789"}>
+  new ASCII<"OVER THE LAZY DOG 0123456789">;
+  // CHECK{LITERAL}: ASCII<{"\\`~!@#$%^&*()_+-={}[]|\'\";:,.<>?/"}>
+  new ASCII<R"(\`~!@#$%^&*()_+-={}[]|'";:,.<>?/)">;
+  // CHECK{LITERAL}: ASCII<{{101, 115, 99, 97, 112, 101, 0, 0}}>
+  new ASCII<"escape\0">;
+  // CHECK{LITERAL}: ASCII<{"escape\r\n"}>
+  new ASCII<"escape\r\n">;
+  // CHECK{LITERAL}: ASCII<{"escape\\\t\f\v"}>
+  new ASCII<"escape\\\t\f\v">;
+  // CHECK{LITERAL}: ASCII<{"escape\a\bc"}>
+  new ASCII<"escape\a\b\c">;
+  // CHECK{LITERAL}: ASCII<{{110, 111, 116, 17, 0}}>
+  new ASCII<"not\x11">;
+  // CHECK{LITERAL}: ASCII<{{18, 20, 127, 16, 1, 32, 97, 98, 99, 0}}>
+  new ASCII<"\x12\x14\x7f\x10\x01 abc">;
+  // CHECK{LITERAL}: ASCII<{{18, 20, 127, 16, 1, 32, 97, 98, 99, 100, 0}}>
+  new ASCII<"\x12\x14\x7f\x10\x01 abcd">;
+  // CHECK{LITERAL}: ASCII<{"print more characters as string"}>
+  new ASCII<"print more characters as string">;
+  // CHECK{LITERAL}: ASCII<{"print more characters as string, no uplimit"}>
+  new ASCII<"print more characters as string, no uplimit">;
+}
+
+void wide() {
+  // CHECK{LITERAL}: ASCII<{L""}>
+  new ASCII<L"">;
+  // CHECK{LITERAL}: ASCII<{L"the quick brown fox jumps"}>
+  new ASCII<L"the quick brown fox jumps">;
+  // CHECK{LITERAL}: ASCII<{L"OVER THE LAZY DOG 0123456789"}>
+  new ASCII<L"OVER THE LAZY DOG 0123456789">;
+  // CHECK{LITERAL}: ASCII<{L"\\`~!@#$%^&*()_+-={}[]|\'\";:,.<>?/"}>
+  new ASCII<LR"(\`~!@#$%^&*()_+-={}[]|'";:,.<>?/)">;
+  // CHECK{LITERAL}: ASCII<{{101, 115, 99, 97, 112, 101, 0, 0}}>
+  new ASCII<L"escape\0">;
+  // CHECK{LITERAL}: ASCII<{L"escape\r\n"}>
+  new ASCII<L"escape\r\n">;
+  // CHECK{LITERAL}: ASCII<{L"escape\\\t\f\v"}>
+  new ASCII<L"escape\\\t\f\v">;
+  // CHECK{LITERAL}: ASCII<{L"escape\a\bc"}>
+  new ASCII<L"escape\a\b\c">;
+  // CHECK{LITERAL}: ASCII<{{110, 111, 116, 17, 0}}>
+  new ASCII<L"not\x11">;
+  // CHECK{LITERAL}: ASCII<{{18, 20, 255, 22909, 136, 32, 97, 98, 99, 0}}>
+  new ASCII<L"\x12\x14\xff\x597d\x88 abc">;
+  // CHECK{LITERAL}: ASCII<{{18, 20, 255, 22909, 136, 32, 97, 98, 99, 100, 0}}>
+  new ASCII<L"\x12\x14\xff\x597d\x88 abcd">;
+  // CHECK{LITERAL}: ASCII<{L"print more characters as string"}>
+  new ASCII<L"print more characters as string">;
+  // CHECK{LITERAL}: ASCII<{L"print more characters as string, no uplimit"}>
+  new ASCII<L"print more characters as string, no uplimit">;
+}
+
+void utf8() {
+  // CHECK{LITERAL}: ASCII<{u8""}>
+  new ASCII<u8"">;
+  // CHECK{LITERAL}: ASCII<{u8"\\`~!@#$%^&*()_+-={}[]|\'\";:,.<>?/"}>
+  new ASCII<u8R"(\`~!@#$%^&*()_+-={}[]|'";:,.<>?/)">;
+  // CHECK{LITERAL}: ASCII<{{101, 115, 99, 97, 112, 101, 0, 0}}>
+  new ASCII<u8"escape\0">;
+  // CHECK{LITERAL}: ASCII<{u8"escape\r\n"}>
+  new ASCII<u8"escape\r\n">;
+  // CHECK{LITERAL}: ASCII<{{229, 165, 189, 239, 191, 189, 0}}>
+  new ASCII<u8"\u597d\ufffd">;
+  // CHECK{LITERAL}: ASCII<{u8"print more characters as string, no uplimit"}>
+  new ASCII<u8"print more characters as string, no uplimit">;
+}
+
+void utf16() {
+  // CHECK{LITERAL}: ASCII<{u""}>
+  new ASCII<u"">;
+  // CHECK{LITERAL}: ASCII<{u"\\`~!@#$%^&*()_+-={}[]|\'\";:,.<>?/"}>
+  new ASCII<uR"(\`~!@#$%^&*()_+-={}[]|'";:,.<>?/)">;
+  // CHECK{LITERAL}: ASCII<{{101, 115, 99, 97, 112, 101, 0, 0}}>
+  new ASCII<u"escape\0">;
+  // CHECK{LITERAL}: ASCII<{u"escape\r\n"}>
+  new ASCII<u"escape\r\n">;
+  // CHECK{LITERAL}: ASCII<{{22909, 65533, 0}}>
+  new ASCII<u"\u597d\ufffd">;
+  // CHECK{LITERAL}: ASCII<{u"print more characters as string, no uplimit"}>
+  new ASCII<u"print more characters as string, no uplimit">;
+}
+
+void utf32() {
+  // CHECK{LITERAL}: ASCII<{U""}>
+  new ASCII<U"">;
+  // CHECK{LITERAL}: ASCII<{U"\\`~!@#$%^&*()_+-={}[]|\'\";:,.<>?/"}>
+  new ASCII<UR"(\`~!@#$%^&*()_+-={}[]|'";:,.<>?/)">;
+  // CHECK{LITERAL}: ASCII<{{101, 115, 99, 97, 112, 101, 0, 0}}>
+  new ASCII<U"escape\0">;
+  // CHECK{LITERAL}: ASCII<{U"escape\r\n"}>
+  new ASCII<U"escape\r\n">;
+  // CHECK{LITERAL}: ASCII<{{22909, 131358, 0}}>
+  new ASCII<U"\u597d\U0002011E">;
+  // CHECK{LITERAL}: ASCII<{U"print more characters as string, no uplimit"}>
+  new ASCII<U"print more characters as string, no uplimit">;
+}

diff  --git a/clang/unittests/AST/TypePrinterTest.cpp b/clang/unittests/AST/TypePrinterTest.cpp
index 8500d518d25fe..7b44b1ecece24 100644
--- a/clang/unittests/AST/TypePrinterTest.cpp
+++ b/clang/unittests/AST/TypePrinterTest.cpp
@@ -80,3 +80,34 @@ TEST(TypePrinter, ParamsUglified) {
                                  varDecl(hasType(qualType().bind("id"))),
                                  "const f<Tp &> *", Clean));
 }
+
+TEST(TypePrinter, TemplateIdWithNTTP) {
+  constexpr char Code[] = R"cpp(
+    template <int N>
+    struct Str {
+      constexpr Str(char const (&s)[N]) { __builtin_memcpy(value, s, N); }
+      char value[N];
+    };
+    template <Str> class ASCII {};
+
+    ASCII<"this nontype template argument is too long to print"> x;
+  )cpp";
+  auto Matcher = classTemplateSpecializationDecl(
+      hasName("ASCII"), has(cxxConstructorDecl(
+                            isMoveConstructor(),
+                            has(parmVarDecl(hasType(qualType().bind("id")))))));
+
+  ASSERT_TRUE(PrintedTypeMatches(
+      Code, {"-std=c++20"}, Matcher,
+      R"(ASCII<{"this nontype template argument is [...]"}> &&)",
+      [](PrintingPolicy &Policy) {
+        Policy.EntireContentsOfLargeArray = false;
+      }));
+
+  ASSERT_TRUE(PrintedTypeMatches(
+      Code, {"-std=c++20"}, Matcher,
+      R"(ASCII<{"this nontype template argument is too long to print"}> &&)",
+      [](PrintingPolicy &Policy) {
+        Policy.EntireContentsOfLargeArray = true;
+      }));
+}


        


More information about the cfe-commits mailing list