[clang] [Sema] Fast-path simple plain auto deduction in DeduceAutoType (PR #188196)

via cfe-commits cfe-commits at lists.llvm.org
Sun Mar 29 21:30:27 PDT 2026


https://github.com/heturing updated https://github.com/llvm/llvm-project/pull/188196

>From 4ca2a7eebfad0e82887e0ce06718cd593216f04e Mon Sep 17 00:00:00 2001
From: heturing <32864610+heturing at users.noreply.github.com>
Date: Mon, 23 Mar 2026 22:00:32 -0600
Subject: [PATCH 1/3] [Sema] Add a fast path for simple plain auto deduction

---
 clang/lib/Sema/SemaTemplateDeduction.cpp      |  19 +++-
 .../SemaCXX/special-case-auto-deduction.cpp   | 105 ++++++++++++++++++
 2 files changed, 123 insertions(+), 1 deletion(-)
 create mode 100644 clang/test/SemaCXX/special-case-auto-deduction.cpp

diff --git a/clang/lib/Sema/SemaTemplateDeduction.cpp b/clang/lib/Sema/SemaTemplateDeduction.cpp
index 477af31def50e..2bde052c9ddc3 100644
--- a/clang/lib/Sema/SemaTemplateDeduction.cpp
+++ b/clang/lib/Sema/SemaTemplateDeduction.cpp
@@ -5256,6 +5256,24 @@ Sema::DeduceAutoType(TypeLoc Type, Expr *Init, QualType &Result,
 
     DeducedType = getDecltypeForExpr(Init);
     assert(!DeducedType.isNull());
+  } else if (!InitList && !AT->isGNUAutoType() && !AT->isConstrained() &&
+             Context.hasSameType(Type.getType(), Context.AutoDeductTy) &&
+             !Init->getType()->isSpecificBuiltinType(BuiltinType::Overload) &&
+             Init->getType().isCanonical() &&
+             !Init->getType()->isObjCObjectPointerType()) {
+    // Fast-path a subset of plain unconstrained `auto` deduction for
+    // non-init-list cases with canonical initializer types. For these cases,
+    // the deduced type can be computed directly from the initializer type by
+    // removing references, applying array/function decay, and dropping
+    // top-level cv-qualifiers.
+    QualType Ty = Init->getType();
+    Ty = Ty.getNonReferenceType();
+
+    if (Ty->isArrayType() || Ty->isFunctionType())
+      Ty = Context.getDecayedType(Ty);
+
+    Ty = Ty.getLocalUnqualifiedType();
+    DeducedType = Ty;
   } else {
     LocalInstantiationScope InstScope(*this);
 
@@ -5319,7 +5337,6 @@ Sema::DeduceAutoType(TypeLoc Type, Expr *Init, QualType &Result,
           TDK != TemplateDeductionResult::Success)
         return TDK;
     }
-
     // Could be null if somehow 'auto' appears in a non-deduced context.
     if (Deduced[0].getKind() != TemplateArgument::Type)
       return TemplateDeductionResult::Incomplete;
diff --git a/clang/test/SemaCXX/special-case-auto-deduction.cpp b/clang/test/SemaCXX/special-case-auto-deduction.cpp
new file mode 100644
index 0000000000000..ece3b4a9f8c80
--- /dev/null
+++ b/clang/test/SemaCXX/special-case-auto-deduction.cpp
@@ -0,0 +1,105 @@
+// RUN: %clangxx -std=c++20 -fsyntax-only %s
+
+#include <initializer_list>
+
+// Plain `auto` cases covered by the fast path.
+int i = 0;
+int &r = i;
+auto a = r;
+static_assert(__is_same(decltype(a), int));
+
+const int ci = 0;
+auto b = ci;
+static_assert(__is_same(decltype(b), int));
+
+const int &cr = ci;
+auto c = cr;
+static_assert(__is_same(decltype(c), int));
+
+int arr[3];
+auto arr_decay = arr;
+static_assert(__is_same(decltype(arr_decay), int *));
+
+void foo();
+auto func_decay = foo;
+static_assert(__is_same(decltype(func_decay), void (*)(void)));
+
+int *const p = nullptr;
+auto ptr_top_const_removed = p;
+static_assert(__is_same(decltype(ptr_top_const_removed), int *));
+
+const int *q = nullptr;
+auto ptr_pointee_const_preserved = q;
+static_assert(__is_same(decltype(ptr_pointee_const_preserved), const int *));
+
+int arr2[3] = {0, 1, 2};
+int (&rarr)[3] = arr2;
+auto array_ref_decay = rarr;
+static_assert(__is_same(decltype(array_ref_decay), int *));
+
+auto str_decay = "abc";
+static_assert(__is_same(decltype(str_decay), const char *));
+
+int arr3[2][3] = {{1, 2, 3}, {4, 5, 6}};
+auto multi_arr_decay = arr3;
+static_assert(__is_same(decltype(multi_arr_decay), int (*)[3]));
+
+volatile int vi = 0;
+auto volatile_value = vi;
+static_assert(__is_same(decltype(volatile_value), int));
+
+volatile int *vp = nullptr;
+auto volatile_ptr = vp;
+static_assert(__is_same(decltype(volatile_ptr), volatile int *));
+
+// Non-fast-path init-list case should remain unchanged.
+auto ilist = {1, 2, 3};
+static_assert(__is_same(decltype(ilist), std::initializer_list<int>));
+
+// Reference-valued initializers.
+int j = 1;
+int &lr = j;
+int &&rr = 2;
+
+auto ref_lvalue = lr;
+static_assert(__is_same(decltype(ref_lvalue), int));
+
+// A named rvalue reference is still an lvalue expression.
+auto ref_named_rvalue = rr;
+static_assert(__is_same(decltype(ref_named_rvalue), int));
+
+const int cj = 3;
+const int &clr = cj;
+auto ref_const_lvalue = clr;
+static_assert(__is_same(decltype(ref_const_lvalue), int));
+
+const int &&crr = 4;
+auto ref_const_rvalue = crr;
+static_assert(__is_same(decltype(ref_const_rvalue), int));
+
+void (&func_ref)() = foo;
+auto func_ref_decay = func_ref;
+static_assert(__is_same(decltype(func_ref_decay), void (*)(void)));
+
+// Adjacent `const auto` cases should remain unchanged.
+const auto ca1 = 0;
+static_assert(__is_same(decltype(ca1), const int));
+
+int i2 = 1;
+int &lr2 = i2;
+const auto ca2 = lr2;
+static_assert(__is_same(decltype(ca2), const int));
+
+const int ci2 = 2;
+const auto ca3 = ci2;
+static_assert(__is_same(decltype(ca3), const int));
+
+int arr4[3] = {1, 2, 3};
+const auto ca4 = arr4;
+static_assert(__is_same(decltype(ca4), int *const));
+
+void qux();
+const auto ca5 = qux;
+static_assert(__is_same(decltype(ca5), void (*const)(void)));
+
+

>From 3a5fef3640af9e41d18d43b7b27a202848fef833 Mon Sep 17 00:00:00 2001
From: Jiaqi He <heturing at gmail.com>
Date: Wed, 25 Mar 2026 00:30:22 -0600
Subject: [PATCH 2/3] [Sema] Extend fast path for auto deduction

Extend the Sema::DeduceAutoType fast path to cover more simple `auto`
deduction cases.

In particular, this supports non-canonical initializer types,
top-level-cv-qualified `auto`, and single-level `auto*` declarators,
while keeping ObjC and OpenCL cases on the existing slow path.

Also add tests for class, union, enum, and `__restrict__` pointer cases.
---
 clang/lib/Sema/SemaTemplateDeduction.cpp      |  62 +++--
 .../SemaCXX/special-case-auto-deduction.cpp   | 247 ++++++++++++++----
 2 files changed, 239 insertions(+), 70 deletions(-)

diff --git a/clang/lib/Sema/SemaTemplateDeduction.cpp b/clang/lib/Sema/SemaTemplateDeduction.cpp
index 2bde052c9ddc3..8336239942c0f 100644
--- a/clang/lib/Sema/SemaTemplateDeduction.cpp
+++ b/clang/lib/Sema/SemaTemplateDeduction.cpp
@@ -5243,9 +5243,13 @@ Sema::DeduceAutoType(TypeLoc Type, Expr *Init, QualType &Result,
   // Deduce type of TemplParam in Func(Init)
   SmallVector<DeducedTemplateArgument, 1> Deduced;
   Deduced.resize(1);
-
   SmallVector<OriginalCallArg, 4> OriginalCallArgs;
 
+  bool CanTryFastPath =
+      !InitList &&
+      !Init->getType()->isSpecificBuiltinType(BuiltinType::Overload) &&
+      !getLangOpts().ObjC && !getLangOpts().OpenCL;
+
   QualType DeducedType;
   // If this is a 'decltype(auto)' specifier, do the decltype dance.
   if (AT->isDecltypeAuto()) {
@@ -5256,25 +5260,41 @@ Sema::DeduceAutoType(TypeLoc Type, Expr *Init, QualType &Result,
 
     DeducedType = getDecltypeForExpr(Init);
     assert(!DeducedType.isNull());
-  } else if (!InitList && !AT->isGNUAutoType() && !AT->isConstrained() &&
-             Context.hasSameType(Type.getType(), Context.AutoDeductTy) &&
-             !Init->getType()->isSpecificBuiltinType(BuiltinType::Overload) &&
-             Init->getType().isCanonical() &&
-             !Init->getType()->isObjCObjectPointerType()) {
-    // Fast-path a subset of plain unconstrained `auto` deduction for
-    // non-init-list cases with canonical initializer types. For these cases,
-    // the deduced type can be computed directly from the initializer type by
-    // removing references, applying array/function decay, and dropping
-    // top-level cv-qualifiers.
-    QualType Ty = Init->getType();
-    Ty = Ty.getNonReferenceType();
-
-    if (Ty->isArrayType() || Ty->isFunctionType())
-      Ty = Context.getDecayedType(Ty);
-
-    Ty = Ty.getLocalUnqualifiedType();
-    DeducedType = Ty;
-  } else {
+  } else if (CanTryFastPath) {
+    // Fast-path a subset of `auto` deduction for non-init-list cases in
+    // non-ObjC and non-OpenCL language modes. For these cases, the deduced
+    // type can be computed directly from the initializer type by removing
+    // references, applying array/function decay, and dropping top-level
+    // cv-qualifiers. For single-level `auto*` declarators, the deduced type
+    // is the pointee type of the processed initializer type.
+
+    QualType TypeTy = Type.getType();
+    bool IsPlainOrTopLevelCvAuto = Context.hasSameType(
+        TypeTy.getLocalUnqualifiedType(), Context.getAutoDeductType());
+    bool IsSimpleAutoStar =
+        TypeTy->isPointerType() &&
+        Context.hasSameType(TypeTy->getPointeeType().getLocalUnqualifiedType(),
+                            Context.getAutoDeductType());
+
+    QualType ProcessedInitTy = Init->getType().getNonReferenceType();
+    if (ProcessedInitTy->isArrayType() || ProcessedInitTy->isFunctionType())
+      ProcessedInitTy = Context.getDecayedType(ProcessedInitTy);
+
+    ProcessedInitTy = ProcessedInitTy.getUnqualifiedType();
+    bool CanUsePointerFastPath =
+        IsSimpleAutoStar && ProcessedInitTy->isPointerType();
+
+    if (IsPlainOrTopLevelCvAuto) {
+      DeducedType = ProcessedInitTy;
+      assert(!DeducedType.isNull());
+    } else if (CanUsePointerFastPath) {
+      DeducedType = ProcessedInitTy->getPointeeType();
+      assert(!DeducedType.isNull());
+    }
+  }
+
+  // Auto deduction with template
+  if (DeducedType.isNull()) {
     LocalInstantiationScope InstScope(*this);
 
     // Build template<class TemplParam> void Func(FuncParam);
@@ -5336,6 +5356,7 @@ Sema::DeduceAutoType(TypeLoc Type, Expr *Init, QualType &Result,
               /*Decomposed=*/false, /*ArgIdx=*/0, /*TDF=*/0, FailedTSC);
           TDK != TemplateDeductionResult::Success)
         return TDK;
+          
     }
     // Could be null if somehow 'auto' appears in a non-deduced context.
     if (Deduced[0].getKind() != TemplateArgument::Type)
@@ -5357,6 +5378,7 @@ Sema::DeduceAutoType(TypeLoc Type, Expr *Init, QualType &Result,
     }
     DeducedType = Context.getCommonSugaredType(Result, DeducedType);
   }
+  
 
   if (AT->isConstrained() && !IgnoreConstraints &&
       CheckDeducedPlaceholderConstraints(
diff --git a/clang/test/SemaCXX/special-case-auto-deduction.cpp b/clang/test/SemaCXX/special-case-auto-deduction.cpp
index ece3b4a9f8c80..96c5f2f06adc1 100644
--- a/clang/test/SemaCXX/special-case-auto-deduction.cpp
+++ b/clang/test/SemaCXX/special-case-auto-deduction.cpp
@@ -1,87 +1,142 @@
-// RUN: %clangxx -std=c++20 -fsyntax-only %s
+// RUN: %clangxx -std=c++20 -fsyntax-only -Xclang -verify %s
 
 #include <initializer_list>
 
-// Plain `auto` cases covered by the fast path.
+
+// Plain auto tests
 int i = 0;
-int &r = i;
-auto a = r;
+auto a = i;
 static_assert(__is_same(decltype(a), int));
 
 const int ci = 0;
 auto b = ci;
 static_assert(__is_same(decltype(b), int));
 
-const int &cr = ci;
-auto c = cr;
+volatile int vi = 0;
+auto c = vi;
 static_assert(__is_same(decltype(c), int));
 
+int &r = i;
+auto d = r;
+static_assert(__is_same(decltype(d), int));
+
+const int &cr = ci;
+auto e = cr;
+static_assert(__is_same(decltype(e), int));
+
+int &&rr = 1;
+auto f = rr;
+static_assert(__is_same(decltype(f), int));
+
+
+// Plain auto with array / function
+
 int arr[3];
 auto arr_decay = arr;
 static_assert(__is_same(decltype(arr_decay), int *));
 
+int arr2[2][3];
+auto arr2_decay = arr2;
+static_assert(__is_same(decltype(arr2_decay), int (*)[3]));
+
+int (&rarr)[3] = arr;
+auto rarr_decay = rarr;
+static_assert(__is_same(decltype(rarr_decay), int *));
+
 void foo();
 auto func_decay = foo;
 static_assert(__is_same(decltype(func_decay), void (*)(void)));
 
-int *const p = nullptr;
-auto ptr_top_const_removed = p;
-static_assert(__is_same(decltype(ptr_top_const_removed), int *));
-
-const int *q = nullptr;
-auto ptr_pointee_const_preserved = q;
-static_assert(__is_same(decltype(ptr_pointee_const_preserved), const int *));
-
-int arr2[3] = {0, 1, 2};
-int (&rarr)[3] = arr2;
-auto array_ref_decay = rarr;
-static_assert(__is_same(decltype(array_ref_decay), int *));
+void (&func_ref)() = foo;
+auto func_ref_decay = func_ref;
+static_assert(__is_same(decltype(func_ref_decay), void (*)(void)));
 
 auto str_decay = "abc";
 static_assert(__is_same(decltype(str_decay), const char *));
 
-int arr3[2][3] = {{1, 2, 3}, {4, 5, 6}};
-auto multi_arr_decay = arr3;
-static_assert(__is_same(decltype(multi_arr_decay), int (*)[3]));
 
-volatile int vi = 0;
-auto volatile_value = vi;
-static_assert(__is_same(decltype(volatile_value), int));
+// pointer qualifier
 
-volatile int *vp = nullptr;
-auto volatile_ptr = vp;
-static_assert(__is_same(decltype(volatile_ptr), volatile int *));
+int *ip = nullptr;
+auto p1 = ip;
+static_assert(__is_same(decltype(p1), int *));
 
-// Non-fast-path init-list case should remain unchanged.
-auto ilist = {1, 2, 3};
-static_assert(__is_same(decltype(ilist), std::initializer_list<int>));
+int *const ipc = nullptr;
+auto p2 = ipc;
+static_assert(__is_same(decltype(p2), int *));
 
-// Reference-valued initializers.
-int j = 1;
-int &lr = j;
-int &&rr = 2;
+const int *cip = nullptr;
+auto p3 = cip;
+static_assert(__is_same(decltype(p3), const int *));
 
-auto ref_lvalue = lr;
-static_assert(__is_same(decltype(ref_lvalue), int));
+volatile int *vip = nullptr;
+auto p4 = vip;
+static_assert(__is_same(decltype(p4), volatile int *));
 
-// A named rvalue reference is still an lvalue expression.
-auto ref_named_rvalue = rr;
-static_assert(__is_same(decltype(ref_named_rvalue), int));
+int * __restrict__ rp = nullptr;
+auto p5 = rp;
+static_assert(__is_same(decltype(p5), int *));
 
-const int cj = 3;
-const int &clr = cj;
-auto ref_const_lvalue = clr;
-static_assert(__is_same(decltype(ref_const_lvalue), int));
+const int * __restrict__ crp = nullptr;
+auto p6 = crp;
+static_assert(__is_same(decltype(p6), const int *));
 
-const int &&crr = 4;
-auto ref_const_rvalue = crr;
-static_assert(__is_same(decltype(ref_const_rvalue), int));
+// non-canonical type
+using Animal = int;
+Animal animal = 0;
+auto t1 = animal;
+static_assert(__is_same(decltype(t1), Animal));
 
-void (&func_ref)() = foo;
-auto func_ref_decay = func_ref;
-static_assert(__is_same(decltype(func_ref_decay), void (*)(void)));
+using AnimalPtr = int *;
+AnimalPtr ap = nullptr;
+auto t2 = ap;
+static_assert(__is_same(decltype(t2), AnimalPtr));
+
+using ConstInt = const int;
+ConstInt cx = 0;
+auto t3 = cx;
+static_assert(__is_same(decltype(t3), int));
+
+using IntArray3 = int[3];
+IntArray3 ta = {1, 2, 3};
+auto t4 = ta;
+static_assert(__is_same(decltype(t4), int *));
+
+using FuncTy = void();
+FuncTy &fr = foo;
+auto t5 = fr;
+static_assert(__is_same(decltype(t5), void (*)(void)));
+
+// class / union / enum
+
+struct MyStruct {
+  int x;
+};
+MyStruct sv{1};
+auto class_value = sv;
+static_assert(__is_same(decltype(class_value), MyStruct));
+
+union MyUnion {
+  int i;
+  float f;
+};
+MyUnion uv{};
+auto union_value = uv;
+static_assert(__is_same(decltype(union_value), MyUnion));
+
+enum MyEnum { EnumA, EnumB };
+MyEnum ev = EnumA;
+auto enum_value = ev;
+static_assert(__is_same(decltype(enum_value), MyEnum));
+
+enum class MyScopedEnum : unsigned long { X, Y };
+MyScopedEnum sev = MyScopedEnum::X;
+auto scoped_enum_value = sev;
+static_assert(__is_same(decltype(scoped_enum_value), MyScopedEnum));
+
+
+// const auto
 
-// Adjacent `const auto` cases should remain unchanged.
 const auto ca1 = 0;
 static_assert(__is_same(decltype(ca1), const int));
 
@@ -102,4 +157,96 @@ void qux();
 const auto ca5 = qux;
 static_assert(__is_same(decltype(ca5), void (*const)(void)));
 
+using AliasConstInt = const int;
+AliasConstInt aci = 3;
+const auto ca6 = aci;
+static_assert(__is_same(decltype(ca6), const int));
+
+// auto *
+
+int *ip1 = nullptr;
+auto *ap1 = ip1;
+static_assert(__is_same(decltype(ap1), int *));
+
+const int *cip1 = nullptr;
+auto *ap2 = cip1;
+static_assert(__is_same(decltype(ap2), const int *));
+
+int arr5[3];
+auto *ap3 = arr5;
+static_assert(__is_same(decltype(ap3), int *));
+
+void f1();
+auto *ap4 = f1;
+static_assert(__is_same(decltype(ap4), void (*)(void)));
+
+using Animal2 = int;
+Animal2 *ap5 = nullptr;
+auto *ap6 = ap5;
+static_assert(__is_same(decltype(ap6), Animal2 *));
+
+
+// const auto *
+
+int *ip2 = nullptr;
+const auto *cp1 = ip2;
+static_assert(__is_same(decltype(cp1), const int *));
+
+const int *cip2 = nullptr;
+const auto *cp2 = cip2;
+static_assert(__is_same(decltype(cp2), const int *));
+
+int arr6[3];
+const auto *cp3 = arr6;
+static_assert(__is_same(decltype(cp3), const int *));
+
+using Animal3 = int;
+Animal3 *ap7 = nullptr;
+const auto *cp4 = ap7;
+static_assert(__is_same(decltype(cp4), const Animal3 *));
+
+
+// auto ** / const auto **
+
+int **pp1 = nullptr;
+auto **dp1 = pp1;
+static_assert(__is_same(decltype(dp1), int **));
+
+const int **pp2 = nullptr;
+auto **dp2 = pp2;
+static_assert(__is_same(decltype(dp2), const int **));
+
+const int *pbase = nullptr;
+const int **pp3 = &pbase;
+const auto **dp3 = pp3;
+static_assert(__is_same(decltype(dp3), const int **));
+
+using Animal4 = int;
+Animal4 **pp4 = nullptr;
+auto **dp4 = pp4;
+static_assert(__is_same(decltype(dp4), Animal4 **));
+
+
+// init-list
+
+auto ilist = {1, 2, 3};
+static_assert(__is_same(decltype(ilist), std::initializer_list<int>));
+
+// untouched case
+
+
+int x = 0;
+auto *bad = x; // expected-error {{variable 'bad' with type 'auto *' has incompatible initializer of type 'int'}}
+
+int y = 0;
+auto &ref = y;
+static_assert(__is_same(decltype(ref), int &));
+
+
+int *p = nullptr;
+auto *const pc = p;
+static_assert(__is_same(decltype(pc), int * const));
+
+const auto *const cpc = p;
+static_assert(__is_same(decltype(cpc), const int * const));
 

>From fdc8443c928686bf24236ad33a98533c36afb5c4 Mon Sep 17 00:00:00 2001
From: Jiaqi He <heturing at gmail.com>
Date: Sun, 29 Mar 2026 19:41:42 -0600
Subject: [PATCH 3/3] [Sema] Add a fast path for additional simple auto
 deduction cases

---
 clang/docs/ReleaseNotes.rst                   |  5 ++
 clang/lib/Sema/SemaTemplateDeduction.cpp      | 58 ++++++++++++++++---
 .../SemaCXX/special-case-auto-deduction.cpp   | 19 +++++-
 3 files changed, 72 insertions(+), 10 deletions(-)

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 7ed56ce6ae6a6..edb270fca2a17 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -161,6 +161,11 @@ Non-comprehensive list of changes in this release
 - Deprecated float types support from ``__builtin_elementwise_max`` and
   ``__builtin_elementwise_min``.
 
+- Improved the performance of ``auto`` deduction by adding the fast path
+  for additional simple cases, including non-canonical initializer types,
+  top-level-cv-qualified ``auto``, and single-level ``auto*`` declarators in
+  supported language modes.
+
 New Compiler Flags
 ------------------
 - New option ``-fms-anonymous-structs`` / ``-fno-ms-anonymous-structs`` added
diff --git a/clang/lib/Sema/SemaTemplateDeduction.cpp b/clang/lib/Sema/SemaTemplateDeduction.cpp
index 8336239942c0f..0e63817463f42 100644
--- a/clang/lib/Sema/SemaTemplateDeduction.cpp
+++ b/clang/lib/Sema/SemaTemplateDeduction.cpp
@@ -5250,6 +5250,10 @@ Sema::DeduceAutoType(TypeLoc Type, Expr *Init, QualType &Result,
       !Init->getType()->isSpecificBuiltinType(BuiltinType::Overload) &&
       !getLangOpts().ObjC && !getLangOpts().OpenCL;
 
+#ifndef NDEBUG
+  bool FastPathUsed = false;
+#endif
+
   QualType DeducedType;
   // If this is a 'decltype(auto)' specifier, do the decltype dance.
   if (AT->isDecltypeAuto()) {
@@ -5268,13 +5272,14 @@ Sema::DeduceAutoType(TypeLoc Type, Expr *Init, QualType &Result,
     // cv-qualifiers. For single-level `auto*` declarators, the deduced type
     // is the pointee type of the processed initializer type.
 
-    QualType TypeTy = Type.getType();
+    QualType DeclaredTy = Type.getType();
     bool IsPlainOrTopLevelCvAuto = Context.hasSameType(
-        TypeTy.getLocalUnqualifiedType(), Context.getAutoDeductType());
+        DeclaredTy.getLocalUnqualifiedType(), Context.getAutoDeductType());
     bool IsSimpleAutoStar =
-        TypeTy->isPointerType() &&
-        Context.hasSameType(TypeTy->getPointeeType().getLocalUnqualifiedType(),
-                            Context.getAutoDeductType());
+        DeclaredTy->isPointerType() &&
+        Context.hasSameType(
+            DeclaredTy->getPointeeType().getLocalUnqualifiedType(),
+            Context.getAutoDeductType());
 
     QualType ProcessedInitTy = Init->getType().getNonReferenceType();
     if (ProcessedInitTy->isArrayType() || ProcessedInitTy->isFunctionType())
@@ -5286,15 +5291,32 @@ Sema::DeduceAutoType(TypeLoc Type, Expr *Init, QualType &Result,
 
     if (IsPlainOrTopLevelCvAuto) {
       DeducedType = ProcessedInitTy;
-      assert(!DeducedType.isNull());
+#ifndef NDEBUG
+      FastPathUsed = true;
+#endif
     } else if (CanUsePointerFastPath) {
       DeducedType = ProcessedInitTy->getPointeeType();
-      assert(!DeducedType.isNull());
+      Qualifiers ProcessedInitQuals = DeducedType.getLocalQualifiers();
+      Qualifiers TypeQuals = DeclaredTy->getPointeeType().getLocalQualifiers();
+      ProcessedInitQuals.removeCVRQualifiers(TypeQuals.getCVRQualifiers());
+      DeducedType = Context.getQualifiedType(
+          DeducedType.getLocalUnqualifiedType(), ProcessedInitQuals);
+#ifndef NDEBUG
+      FastPathUsed = true;
+#endif
     }
   }
 
   // Auto deduction with template
+  // When assertions are enabled, check that the fast path deduces the same
+  // canonical type as the slow path.
+#ifndef NDEBUG
+  if (DeducedType.isNull() || FastPathUsed) {
+    QualType FastPathDeducedType = DeducedType;
+#else
   if (DeducedType.isNull()) {
+#endif
+
     LocalInstantiationScope InstScope(*this);
 
     // Build template<class TemplParam> void Func(FuncParam);
@@ -5363,6 +5385,28 @@ Sema::DeduceAutoType(TypeLoc Type, Expr *Init, QualType &Result,
       return TemplateDeductionResult::Incomplete;
     DeducedType = Deduced[0].getAsType();
 
+#ifndef NDEBUG
+    if (FastPathUsed) {
+      // Ignore differences due only to QualType sugar.
+      if (FastPathDeducedType.getCanonicalType() !=
+          DeducedType.getCanonicalType()) {
+        llvm::errs() << "Deducing: ";
+        Type.dump();
+        Init->dump();
+        llvm::errs() << "Fast-path deduced type: ";
+        FastPathDeducedType.dump();
+
+        llvm::errs() << "Slow-path deduced type: ";
+        DeducedType.dump();
+      }
+
+      assert(FastPathDeducedType.getCanonicalType() ==
+                 DeducedType.getCanonicalType() &&
+             "fast path auto deduction produced a different deduced type than "
+             "the template-deduction path");
+    }
+#endif
+
     if (InitList) {
       DeducedType = BuildStdInitializerList(DeducedType, Loc);
       if (DeducedType.isNull())
diff --git a/clang/test/SemaCXX/special-case-auto-deduction.cpp b/clang/test/SemaCXX/special-case-auto-deduction.cpp
index 96c5f2f06adc1..59c6d3103a7a8 100644
--- a/clang/test/SemaCXX/special-case-auto-deduction.cpp
+++ b/clang/test/SemaCXX/special-case-auto-deduction.cpp
@@ -1,6 +1,12 @@
-// RUN: %clangxx -std=c++20 -fsyntax-only -Xclang -verify %s
+// RUN: %clang_cc1 -std=c++20 -fsyntax-only -verify %s
 
-#include <initializer_list>
+namespace std {
+template <typename T>
+struct initializer_list {
+  const T *begin;
+  const T *end;
+};
+}
 
 
 // Plain auto tests
@@ -185,6 +191,14 @@ Animal2 *ap5 = nullptr;
 auto *ap6 = ap5;
 static_assert(__is_same(decltype(ap6), Animal2 *));
 
+volatile int *vp2 = nullptr;
+const auto *cp5 = vp2;
+static_assert(__is_same(decltype(cp5), const volatile int *));
+
+const volatile int *cvp = nullptr;
+const auto *cp6 = cvp;
+static_assert(__is_same(decltype(cp6), const volatile int *));
+
 
 // const auto *
 
@@ -234,7 +248,6 @@ static_assert(__is_same(decltype(ilist), std::initializer_list<int>));
 
 // untouched case
 
-
 int x = 0;
 auto *bad = x; // expected-error {{variable 'bad' with type 'auto *' has incompatible initializer of type 'int'}}
 



More information about the cfe-commits mailing list