Skip to content

Conversation

@adityak2006
Copy link

Clang's type printer would verbosely print decltype(T{}) in diagnostics,
even though this is equivalent to just T. This made error messages
unnecessarily long and harder to read.

This commit modifies TypePrinter::printDecltypeBefore to detect this
specific pattern. It checks if the underlying expression of a
DecltypeType is a default CXXConstructExpr (i.e., T{}) and, if so,
prints the constructor's type (T) directly instead of the full
decltype.

Fixes #96638.

@github-actions
Copy link

github-actions bot commented Nov 9, 2025

Thank you for submitting a Pull Request (PR) to the LLVM Project!

This PR will be automatically labeled and the relevant teams will be notified.

If you wish to, you can add reviewers by using the "Reviewers" section on this page.

If this is not working for you, it is probably because you do not have write permissions for the repository. In which case you can instead tag reviewers by name in a comment by using @ followed by their GitHub username.

If you have received no comments on your PR for a week, you can request a review by "ping"ing the PR by adding a comment “Ping”. The common courtesy "ping" rate is once a week. Please remember that you are asking for valuable time from other developers.

If you have further questions, they may be answered by the LLVM GitHub User Guide.

You can also ask questions in a comment on this PR, on the LLVM Discord or on the forums.

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Nov 9, 2025
@llvmbot
Copy link
Member

llvmbot commented Nov 9, 2025

@llvm/pr-subscribers-clang

Author: Aditya Kolgane (adityak2006)

Changes

Clang's type printer would verbosely print decltype(T{}) in diagnostics,
even though this is equivalent to just T. This made error messages
unnecessarily long and harder to read.

This commit modifies TypePrinter::printDecltypeBefore to detect this
specific pattern. It checks if the underlying expression of a
DecltypeType is a default CXXConstructExpr (i.e., T{}) and, if so,
prints the constructor's type (T) directly instead of the full
decltype.

Fixes #96638.


Full diff: https://github.com/llvm/llvm-project/pull/167224.diff

5 Files Affected:

  • (modified) clang/include/clang/Basic/DiagnosticParseKinds.td (+2)
  • (modified) clang/lib/AST/TypePrinter.cpp (+13)
  • (modified) clang/lib/Parse/ParseDecl.cpp (+13-5)
  • (added) clang/test/Parser/cxx-auto-type-specifier.cpp (+13)
  • (added) clang/test/SemaCXX/decltype-diagnostic-print.cpp (+25)
diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td
index aa0ccb0c05101..d900001c8cde0 100644
--- a/clang/include/clang/Basic/DiagnosticParseKinds.td
+++ b/clang/include/clang/Basic/DiagnosticParseKinds.td
@@ -402,6 +402,8 @@ def err_requires_clause_on_declarator_not_declaring_a_function : Error<
   "trailing requires clause can only be used when declaring a function">;
 def err_requires_clause_inside_parens : Error<
   "trailing requires clause should be placed outside parentheses">;
+def err_auto_type_specifier:Error<
+  "'auto' cannot be combined with a type specifier in C++">;
 def ext_auto_storage_class : ExtWarn<
   "'auto' storage class specifier is not permitted in C++11, and will not "
   "be supported in future releases">, InGroup<DiagGroup<"auto-storage-class">>;
diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index c18b2eafc722c..510923229ea11 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -18,6 +18,7 @@
 #include "clang/AST/DeclObjC.h"
 #include "clang/AST/DeclTemplate.h"
 #include "clang/AST/Expr.h"
+#include "clang/AST/ExprCXX.h"
 #include "clang/AST/NestedNameSpecifier.h"
 #include "clang/AST/PrettyPrinter.h"
 #include "clang/AST/TemplateBase.h"
@@ -1321,6 +1322,18 @@ void TypePrinter::printTypeOfBefore(const TypeOfType *T, raw_ostream &OS) {
 void TypePrinter::printTypeOfAfter(const TypeOfType *T, raw_ostream &OS) {}
 
 void TypePrinter::printDecltypeBefore(const DecltypeType *T, raw_ostream &OS) {
+  // Check for the 'decltype(T{})' pattern and simplify it to 'T'.
+  if (const Expr *E = T->getUnderlyingExpr()) {
+    E = E->IgnoreParenImpCasts();
+    if (const CXXConstructExpr *CE = dyn_cast<CXXConstructExpr>(E)) {
+      // Check if it's a default constructor (T{}) and not a list init (T{...})
+      if (CE->getNumArgs() == 0 && !CE->isListInitialization()) {
+        CE->getType().print(OS, Policy);
+        spaceBeforePlaceHolder(OS);
+        return; // Skip the default 'decltype(...)' printing
+      }
+    }
+  }
   OS << "decltype(";
   if (const Expr *E = T->getUnderlyingExpr()) {
     PrintingPolicy ExprPolicy = Policy;
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index 5fcb659768655..65ed4db468b65 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -4095,11 +4095,19 @@ void Parser::ParseDeclarationSpecifiers(
     case tok::kw_auto:
       if (getLangOpts().CPlusPlus11 || getLangOpts().C23) {
         if (isKnownToBeTypeSpecifier(GetLookAheadToken(1))) {
-          isInvalid = DS.SetStorageClassSpec(Actions, DeclSpec::SCS_auto, Loc,
-                                             PrevSpec, DiagID, Policy);
-          if (!isInvalid && !getLangOpts().C23)
-            Diag(Tok, diag::ext_auto_storage_class)
-              << FixItHint::CreateRemoval(DS.getStorageClassSpecLoc());
+          // auto cannot be combined with a type specifier in C++ (except C23).
+          if (getLangOpts().CPlusPlus && !getLangOpts().C23) {
+            if (!PrevSpec) {
+              PrevSpec = "";
+            }
+            isInvalid = true;
+            DiagID = diag::err_auto_type_specifier;
+          } else {
+            // In C23, 'auto' followed by a type specifier is a storage class specifier.
+            isInvalid = DS.SetStorageClassSpec(Actions, DeclSpec::SCS_auto, Loc, PrevSpec, DiagID, Policy);
+          }
+
+
         } else
           isInvalid = DS.SetTypeSpecType(DeclSpec::TST_auto, Loc, PrevSpec,
                                          DiagID, Policy);
diff --git a/clang/test/Parser/cxx-auto-type-specifier.cpp b/clang/test/Parser/cxx-auto-type-specifier.cpp
new file mode 100644
index 0000000000000..d8bce4b7170cf
--- /dev/null
+++ b/clang/test/Parser/cxx-auto-type-specifier.cpp
@@ -0,0 +1,13 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -std=c++11 %s
+// RUN: %clang_cc1 -fsyntax-only -verify -std=c++14 %s
+// RUN: %clang_cc1 -fsyntax-only -verify -std=c++17 %s
+// RUN: %clang_cc1 -fsyntax-only -verify -std=c++20 %s
+// RUN: %clang_cc1 -fsyntax-only -verify -std=c++23 %s
+// RUN: %clang_cc1 -fsyntax-only -verify=c23 -std=c23 -x c %s
+
+// Test that 'auto' cannot be combined with a type specifier in C++.
+void f() {
+  auto int x = 1; // expected-error {{'auto' cannot be combined with a type specifier in C++}}
+}
+
+// c23-no-diagnostics
diff --git a/clang/test/SemaCXX/decltype-diagnostic-print.cpp b/clang/test/SemaCXX/decltype-diagnostic-print.cpp
new file mode 100644
index 0000000000000..c4b075ef4ea33
--- /dev/null
+++ b/clang/test/SemaCXX/decltype-diagnostic-print.cpp
@@ -0,0 +1,25 @@
+// clang/test/SemaCXX/decltype-diagnostic-print.cpp
+// RUN: %clang_cc1 -std=c++11 -fsyntax-only -verify %s
+
+template <typename T>
+struct TestAssert {
+  // This static_assert will fail, forcing the compiler to print the name
+  // of the template instantiation in its diagnostic "note".
+  static_assert(sizeof(T) == 0, "Static assert to check type printing");
+};
+
+struct MySimpleType {};
+struct MyOtherType {};
+
+void test() {
+  // This will fail the static_assert.
+  TestAssert<decltype(MySimpleType{})> test1;
+  // expected-error@6 {{Static assert to check type printing}}
+  // expected-note@16 {{in instantiation of template class 'TestAssert<MySimpleType>' requested here}}
+  // CHECK-NOT: decltype(MySimpleType{})
+
+  TestAssert<decltype(MyOtherType{})> test2;
+  // expected-error@6 {{Static assert to check type printing}}
+  // expected-note@22 {{in instantiation of template class 'TestAssert<MyOtherType>' requested here}}
+  // CHECK-NOT: decltype(MyOtherType{})
+}
\ No newline at end of file

Clang's type printer would verbosely print `decltype(T{})` in diagnostics,
even though this is equivalent to just `T`. This made error messages
unnecessarily long and harder to read.

This commit modifies `TypePrinter::printDecltypeBefore` to detect this
specific pattern. It checks if the underlying expression of a
`DecltypeType` is a default `CXXConstructExpr` (i.e., `T{}`) and, if so,
prints the constructor's type (`T`) directly instead of the full
`decltype`.

Fixes llvm#96638.
Copy link
Contributor

@zyn0217 zyn0217 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't a correct fix to me because there are other clients beyond diagnostics which might need the source fidelity for other purposes. E.g. when debugging we don't want unnecessary simplification because they can hide the underlying bugs.

Besides, if you are using an AI, please state it very explicitly in order to help us evaluate the review priority. Thanks!

@adityak2006 adityak2006 closed this Nov 9, 2025
@adityak2006 adityak2006 reopened this Nov 9, 2025
@adityak2006 adityak2006 closed this Nov 9, 2025
@adityak2006 adityak2006 deleted the decltype-diagnostic branch November 9, 2025 17:11
@adityak2006 adityak2006 restored the decltype-diagnostic branch November 9, 2025 17:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Optimize decltype(T{}) to T in compiler error messages

3 participants