Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[clang] CTAD: implement the missing IsDeducible constraint for alias templates #89358

Merged
merged 2 commits into from
May 16, 2024

Conversation

hokein
Copy link
Collaborator

@hokein hokein commented Apr 19, 2024

Fixes #85192
Fixes #84492

This patch implements the "IsDeducible" constraint where the template arguments of the alias template can be deduced from the returned type of the synthesized deduction guide, per C++ [over.match.class.deduct]p4. In the implementation, we perform the deduction directly, which is more efficient than the way specified in the standard.

Also update relevant CTAD tests which were incorrectly compiled due to the missing constraint.

@hokein hokein requested a review from Endilll as a code owner April 19, 2024 09:17
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Apr 19, 2024
@llvmbot
Copy link
Collaborator

llvmbot commented Apr 19, 2024

@llvm/pr-subscribers-clang

Author: Haojian Wu (hokein)

Changes

Fixes #85192
Fixes #84492

This patch implements the "IsDeducible" constraint where the template arguments of the alias template can be deduced from the returned type of the synthesized deduction guide, per C++ [over.match.class.deduct]p4. In the implementation, we perform the deduction directly, which is more efficient than the way specified in the standard.

In this patch, we add a __is_deducible builtin type trait, it is useful for ad-hoc debugging, and testing.

Also update relevant CTAD tests which were incorrectly compiled due to the missing constraint.


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

9 Files Affected:

  • (modified) clang/include/clang/Basic/TokenKinds.def (+1)
  • (modified) clang/include/clang/Sema/Sema.h (+9)
  • (modified) clang/lib/Parse/ParseExprCXX.cpp (+10-6)
  • (modified) clang/lib/Sema/SemaExprCXX.cpp (+11)
  • (modified) clang/lib/Sema/SemaTemplate.cpp (+60-23)
  • (modified) clang/lib/Sema/SemaTemplateDeduction.cpp (+87)
  • (modified) clang/test/SemaCXX/cxx20-ctad-type-alias.cpp (+17-9)
  • (added) clang/test/SemaCXX/type-traits-is-deducible.cpp (+47)
  • (modified) clang/www/cxx_status.html (+1-7)
diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def
index a27fbed358a60c..74102f40539681 100644
--- a/clang/include/clang/Basic/TokenKinds.def
+++ b/clang/include/clang/Basic/TokenKinds.def
@@ -537,6 +537,7 @@ TYPE_TRAIT_1(__is_referenceable, IsReferenceable, KEYCXX)
 TYPE_TRAIT_1(__can_pass_in_regs, CanPassInRegs, KEYCXX)
 TYPE_TRAIT_2(__reference_binds_to_temporary, ReferenceBindsToTemporary, KEYCXX)
 TYPE_TRAIT_2(__reference_constructs_from_temporary, ReferenceConstructsFromTemporary, KEYCXX)
+TYPE_TRAIT_2(__is_deducible, IsDeducible, KEYCXX)
 
 // Embarcadero Expression Traits
 EXPRESSION_TRAIT(__is_lvalue_expr, IsLValueExpr, KEYCXX)
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 1e89dfc58d92b1..0d8477cf49aaf0 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -9591,6 +9591,15 @@ class Sema final : public SemaBase {
                           ArrayRef<TemplateArgument> TemplateArgs,
                           sema::TemplateDeductionInfo &Info);
 
+  /// Deduce the template arguments of the given template from \p FromType.
+  /// Used to implement the IsDeducible constraint for alias CTAD per C++
+  /// [over.match.class.deduct]p4.
+  ///
+  /// It only supports class or type alias templates.
+  TemplateDeductionResult
+  DeduceTemplateArgumentsFromType(TemplateDecl *TD, QualType FromType,
+                                  sema::TemplateDeductionInfo &Info);
+
   TemplateDeductionResult DeduceTemplateArguments(
       TemplateParameterList *TemplateParams, ArrayRef<TemplateArgument> Ps,
       ArrayRef<TemplateArgument> As, sema::TemplateDeductionInfo &Info,
diff --git a/clang/lib/Parse/ParseExprCXX.cpp b/clang/lib/Parse/ParseExprCXX.cpp
index 0d2ad980696fcc..af4e205eeff803 100644
--- a/clang/lib/Parse/ParseExprCXX.cpp
+++ b/clang/lib/Parse/ParseExprCXX.cpp
@@ -3906,14 +3906,18 @@ ExprResult Parser::ParseTypeTrait() {
   BalancedDelimiterTracker Parens(*this, tok::l_paren);
   if (Parens.expectAndConsume())
     return ExprError();
-
+  TypeTrait TTKind = TypeTraitFromTokKind(Kind);
   SmallVector<ParsedType, 2> Args;
   do {
     // Parse the next type.
-    TypeResult Ty = ParseTypeName(/*SourceRange=*/nullptr,
-                                  getLangOpts().CPlusPlus
-                                      ? DeclaratorContext::TemplateTypeArg
-                                      : DeclaratorContext::TypeName);
+    TypeResult Ty = ParseTypeName(
+        /*SourceRange=*/nullptr,
+        getLangOpts().CPlusPlus
+            // For __is_deducible type trait, the first argument is a template
+            // specification type without template argument lists.
+            ? (TTKind == BTT_IsDeducible ? DeclaratorContext::TemplateArg
+                                         : DeclaratorContext::TemplateTypeArg)
+            : DeclaratorContext::TypeName);
     if (Ty.isInvalid()) {
       Parens.skipToEnd();
       return ExprError();
@@ -3937,7 +3941,7 @@ ExprResult Parser::ParseTypeTrait() {
 
   SourceLocation EndLoc = Parens.getCloseLocation();
 
-  return Actions.ActOnTypeTrait(TypeTraitFromTokKind(Kind), Loc, Args, EndLoc);
+  return Actions.ActOnTypeTrait(TTKind, Loc, Args, EndLoc);
 }
 
 /// ParseArrayTypeTrait - Parse the built-in array type-trait
diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index 7582cbd75fec05..0833a985b48b88 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -6100,6 +6100,17 @@ static bool EvaluateBinaryTypeTrait(Sema &Self, TypeTrait BTT, const TypeSourceI
                               tok::kw___is_pointer_interconvertible_base_of);
 
     return Self.IsPointerInterconvertibleBaseOf(Lhs, Rhs);
+  }
+  case BTT_IsDeducible: {
+    if (const auto *TSTToBeDeduced =
+            LhsT->getAs<DeducedTemplateSpecializationType>()) {
+      sema::TemplateDeductionInfo Info(KeyLoc);
+      return Self.DeduceTemplateArgumentsFromType(
+                 TSTToBeDeduced->getTemplateName().getAsTemplateDecl(), RhsT,
+                 Info) == TemplateDeductionResult::Success;
+    }
+    // FIXME: emit a diagnostic.
+    return false;
   }
     default: llvm_unreachable("not a BTT");
   }
diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index d4976f9d0d11d8..12a4ac05835557 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -18,6 +18,7 @@
 #include "clang/AST/ExprCXX.h"
 #include "clang/AST/RecursiveASTVisitor.h"
 #include "clang/AST/TemplateName.h"
+#include "clang/AST/Type.h"
 #include "clang/AST/TypeVisitor.h"
 #include "clang/Basic/Builtins.h"
 #include "clang/Basic/DiagnosticSema.h"
@@ -2780,6 +2781,41 @@ Expr *transformRequireClause(Sema &SemaRef, FunctionTemplateDecl *FTD,
   return E.getAs<Expr>();
 }
 
+// Build the associated constraints for the alias deduction guides.
+// C++ [over.match.class.deduct]p3.3:
+//   The associated constraints ([temp.constr.decl]) are the conjunction of the
+//   associated constraints of g and a constraint that is satisfied if and only
+//   if the arguments of A are deducible (see below) from the return type.
+Expr *
+buildAssociatedConstraints(Sema &SemaRef, TypeAliasTemplateDecl *AliasTemplate,
+                           FunctionTemplateDecl *FTD,
+                           llvm::ArrayRef<TemplateArgument> TransformedArgs,
+                           QualType ReturnType) {
+  auto &Context = SemaRef.Context;
+  TemplateName TN(AliasTemplate);
+  auto TST = Context.getDeducedTemplateSpecializationType(TN, QualType(), true);
+  // Build the IsDeducible constraint.
+  SmallVector<TypeSourceInfo *> IsDeducibleTypeTraitArgs = {
+      Context.getTrivialTypeSourceInfo(TST),
+      Context.getTrivialTypeSourceInfo(ReturnType)};
+  Expr *IsDeducible = TypeTraitExpr::Create(
+      Context, Context.getLogicalOperationType(), AliasTemplate->getLocation(),
+      TypeTrait::BTT_IsDeducible, IsDeducibleTypeTraitArgs,
+      AliasTemplate->getLocation(), false);
+
+  // Substitute new template parameters into requires-clause if present.
+  if (auto *TransformedRC =
+          transformRequireClause(SemaRef, FTD, TransformedArgs)) {
+    auto Conjunction = SemaRef.BuildBinOp(
+        SemaRef.getCurScope(), SourceLocation{}, BinaryOperatorKind::BO_LAnd,
+        TransformedRC, IsDeducible);
+    if (Conjunction.isInvalid())
+      return nullptr;
+    return Conjunction.get();
+  }
+  return IsDeducible;
+}
+
 std::pair<TemplateDecl *, llvm::ArrayRef<TemplateArgument>>
 getRHSTemplateDeclAndArgs(Sema &SemaRef, TypeAliasTemplateDecl *AliasTemplate) {
   // Unwrap the sugared ElaboratedType.
@@ -2962,19 +2998,6 @@ void DeclareImplicitDeductionGuidesForTypeAlias(
           Context.getCanonicalTemplateArgument(
               Context.getInjectedTemplateArg(NewParam));
     }
-    // Substitute new template parameters into requires-clause if present.
-    Expr *RequiresClause =
-        transformRequireClause(SemaRef, F, TemplateArgsForBuildingFPrime);
-    // FIXME: implement the is_deducible constraint per C++
-    // [over.match.class.deduct]p3.3:
-    //    ... and a constraint that is satisfied if and only if the arguments
-    //    of A are deducible (see below) from the return type.
-    auto *FPrimeTemplateParamList = TemplateParameterList::Create(
-        Context, AliasTemplate->getTemplateParameters()->getTemplateLoc(),
-        AliasTemplate->getTemplateParameters()->getLAngleLoc(),
-        FPrimeTemplateParams,
-        AliasTemplate->getTemplateParameters()->getRAngleLoc(),
-        /*RequiresClause=*/RequiresClause);
 
     // To form a deduction guide f' from f, we leverage clang's instantiation
     // mechanism, we construct a template argument list where the template
@@ -3019,6 +3042,18 @@ void DeclareImplicitDeductionGuidesForTypeAlias(
     if (auto *FPrime = SemaRef.InstantiateFunctionDeclaration(
             F, TemplateArgListForBuildingFPrime, AliasTemplate->getLocation(),
             Sema::CodeSynthesisContext::BuildingDeductionGuides)) {
+      Expr *RequireClause = buildAssociatedConstraints(
+          SemaRef, AliasTemplate, F, TemplateArgsForBuildingFPrime,
+          FPrime->getReturnType());
+      if (!RequireClause)
+        continue;
+      auto *FPrimeTemplateParamList = TemplateParameterList::Create(
+          Context, AliasTemplate->getTemplateParameters()->getTemplateLoc(),
+          AliasTemplate->getTemplateParameters()->getLAngleLoc(),
+          FPrimeTemplateParams,
+          AliasTemplate->getTemplateParameters()->getRAngleLoc(),
+          RequireClause);
+
       auto *GG = cast<CXXDeductionGuideDecl>(FPrime);
       buildDeductionGuide(SemaRef, AliasTemplate, FPrimeTemplateParamList,
                           GG->getCorrespondingConstructor(),
@@ -3072,16 +3107,6 @@ FunctionTemplateDecl *DeclareAggregateDeductionGuideForTypeAlias(
             SemaRef.Context.getInjectedTemplateArg(NewParam));
     TransformedTemplateParams.push_back(NewParam);
   }
-  // FIXME: implement the is_deducible constraint per C++
-  // [over.match.class.deduct]p3.3.
-  Expr *TransformedRequiresClause = transformRequireClause(
-      SemaRef, RHSDeductionGuide, TransformedTemplateArgs);
-  auto *TransformedTemplateParameterList = TemplateParameterList::Create(
-      SemaRef.Context, AliasTemplate->getTemplateParameters()->getTemplateLoc(),
-      AliasTemplate->getTemplateParameters()->getLAngleLoc(),
-      TransformedTemplateParams,
-      AliasTemplate->getTemplateParameters()->getRAngleLoc(),
-      TransformedRequiresClause);
   auto *TransformedTemplateArgList = TemplateArgumentList::CreateCopy(
       SemaRef.Context, TransformedTemplateArgs);
 
@@ -3089,6 +3114,18 @@ FunctionTemplateDecl *DeclareAggregateDeductionGuideForTypeAlias(
           RHSDeductionGuide, TransformedTemplateArgList,
           AliasTemplate->getLocation(),
           Sema::CodeSynthesisContext::BuildingDeductionGuides)) {
+    Expr *RequireClause = buildAssociatedConstraints(
+        SemaRef, AliasTemplate, RHSDeductionGuide, TransformedTemplateArgs,
+        TransformedDeductionGuide->getReturnType());
+    if (!RequireClause)
+      return nullptr;
+    auto *TransformedTemplateParameterList = TemplateParameterList::Create(
+        SemaRef.Context,
+        AliasTemplate->getTemplateParameters()->getTemplateLoc(),
+        AliasTemplate->getTemplateParameters()->getLAngleLoc(),
+        TransformedTemplateParams,
+        AliasTemplate->getTemplateParameters()->getRAngleLoc(), RequireClause);
+
     auto *GD =
         llvm::dyn_cast<clang::CXXDeductionGuideDecl>(TransformedDeductionGuide);
     FunctionTemplateDecl *Result = buildDeductionGuide(
diff --git a/clang/lib/Sema/SemaTemplateDeduction.cpp b/clang/lib/Sema/SemaTemplateDeduction.cpp
index 0b6375001f5326..942c7343163e24 100644
--- a/clang/lib/Sema/SemaTemplateDeduction.cpp
+++ b/clang/lib/Sema/SemaTemplateDeduction.cpp
@@ -3139,6 +3139,40 @@ static TemplateDeductionResult FinishTemplateArgumentDeduction(
 
   return TemplateDeductionResult::Success;
 }
+/// Complete template argument deduction for DeduceTemplateArgumentsFromType.
+/// FIXME: this is mostly duplicated with the above two versions. Deduplicate
+/// the three implementations.
+static TemplateDeductionResult FinishTemplateArgumentDeduction(
+    Sema &S, TemplateDecl *TD,
+    SmallVectorImpl<DeducedTemplateArgument> &Deduced,
+    TemplateDeductionInfo &Info) {
+  // Unevaluated SFINAE context.
+  EnterExpressionEvaluationContext Unevaluated(
+      S, Sema::ExpressionEvaluationContext::Unevaluated);
+  Sema::SFINAETrap Trap(S);
+
+  Sema::ContextRAII SavedContext(S, getAsDeclContextOrEnclosing(TD));
+
+  // C++ [temp.deduct.type]p2:
+  //   [...] or if any template argument remains neither deduced nor
+  //   explicitly specified, template argument deduction fails.
+  SmallVector<TemplateArgument, 4> SugaredBuilder, CanonicalBuilder;
+  if (auto Result = ConvertDeducedTemplateArguments(
+          S, TD, /*IsPartialOrdering=*/false, Deduced, Info, SugaredBuilder,
+          CanonicalBuilder);
+      Result != TemplateDeductionResult::Success)
+    return Result;
+
+  if (Trap.hasErrorOccurred())
+    return TemplateDeductionResult::SubstitutionFailure;
+
+  if (auto Result = CheckDeducedArgumentConstraints(S, TD, SugaredBuilder,
+                                                    CanonicalBuilder, Info);
+      Result != TemplateDeductionResult::Success)
+    return Result;
+
+  return TemplateDeductionResult::Success;
+}
 
 /// Perform template argument deduction to determine whether the given template
 /// arguments match the given class or variable template partial specialization
@@ -3207,6 +3241,59 @@ Sema::DeduceTemplateArguments(VarTemplatePartialSpecializationDecl *Partial,
   return ::DeduceTemplateArguments(*this, Partial, TemplateArgs, Info);
 }
 
+TemplateDeductionResult
+Sema::DeduceTemplateArgumentsFromType(TemplateDecl *TD, QualType FromType,
+                                      sema::TemplateDeductionInfo &Info) {
+  if (TD->isInvalidDecl())
+    return TemplateDeductionResult::Invalid;
+
+  QualType PType;
+  if (const auto *CTD = dyn_cast<ClassTemplateDecl>(TD)) {
+    // Use the InjectedClassNameType.
+    PType = Context.getTypeDeclType(CTD->getTemplatedDecl());
+  } else if (const auto *AliasTemplate = dyn_cast<TypeAliasTemplateDecl>(TD)) {
+    PType = AliasTemplate->getTemplatedDecl()
+                ->getUnderlyingType()
+                .getCanonicalType();
+  } else {
+    // FIXME: emit a diagnostic, we only only support alias and class templates.
+    return TemplateDeductionResult::Invalid;
+  }
+
+  // Unevaluated SFINAE context.
+  EnterExpressionEvaluationContext Unevaluated(
+      *this, Sema::ExpressionEvaluationContext::Unevaluated);
+  SFINAETrap Trap(*this);
+
+  // This deduction has no relation to any outer instantiation we might be
+  // performing.
+  LocalInstantiationScope InstantiationScope(*this);
+
+  SmallVector<DeducedTemplateArgument> Deduced(
+      TD->getTemplateParameters()->size());
+  SmallVector<TemplateArgument> PArgs = {TemplateArgument(PType)};
+  SmallVector<TemplateArgument> AArgs = {TemplateArgument(FromType)};
+  if (auto DeducedResult = DeduceTemplateArguments(
+          TD->getTemplateParameters(), PArgs, AArgs, Info, Deduced, false);
+      DeducedResult != TemplateDeductionResult::Success) {
+    return DeducedResult;
+  }
+
+  SmallVector<TemplateArgument, 4> DeducedArgs(Deduced.begin(), Deduced.end());
+  InstantiatingTemplate Inst(*this, Info.getLocation(), TD, DeducedArgs, Info);
+  if (Inst.isInvalid())
+    return TemplateDeductionResult::InstantiationDepth;
+
+  if (Trap.hasErrorOccurred())
+    return TemplateDeductionResult::SubstitutionFailure;
+
+  TemplateDeductionResult Result;
+  runWithSufficientStackSpace(Info.getLocation(), [&] {
+    Result = ::FinishTemplateArgumentDeduction(*this, TD, Deduced, Info);
+  });
+  return Result;
+}
+
 /// Determine whether the given type T is a simple-template-id type.
 static bool isSimpleTemplateIdType(QualType T) {
   if (const TemplateSpecializationType *Spec
diff --git a/clang/test/SemaCXX/cxx20-ctad-type-alias.cpp b/clang/test/SemaCXX/cxx20-ctad-type-alias.cpp
index 6f04264a655ad5..478f1d21c1865d 100644
--- a/clang/test/SemaCXX/cxx20-ctad-type-alias.cpp
+++ b/clang/test/SemaCXX/cxx20-ctad-type-alias.cpp
@@ -109,10 +109,12 @@ struct Foo {
 };
 
 template <typename X, int Y>
-using Bar = Foo<X, sizeof(X)>;
+using Bar = Foo<X, sizeof(X)>; // expected-note {{candidate template ignored: couldn't infer template argument 'X'}} \
+                               // expected-note {{candidate template ignored: constraints not satisfied [with X = int]}} \
+                               // expected-note {{because '__is_deducible(Bar, Foo<int, 4UL>)' evaluated to false}}
 
-// FIXME: we should reject this case? GCC rejects it, MSVC accepts it.
-Bar s = {{1}};
+
+Bar s = {{1}}; // expected-error {{no viable constructor or deduction guide }}
 }  // namespace test9
 
 namespace test10 {
@@ -133,9 +135,13 @@ A a(2);  // Foo<int*>
 namespace test11 {
 struct A {};
 template<class T> struct Foo { T c; };
-template<class X, class Y=A> using AFoo = Foo<Y>;
+template<class X, class Y=A>
+using AFoo = Foo<Y>; // expected-note {{candidate template ignored: could not match 'Foo<type-parameter-0-0>' against 'int'}} \
+                    // expected-note {{candidate template ignored: constraints not satisfied [with T = int]}} \
+                    // expected-note {{because '__is_deducible(AFoo, Foo<int>)' evaluated to false}} \
+                    // expected-note {{candidate function template not viable: requires 0 arguments, but 1 was provided}}
 
-AFoo s = {1};
+AFoo s = {1}; // expected-error {{no viable constructor or deduction guide for deduction of template arguments of 'AFoo'}}
 } // namespace test11
 
 namespace test12 {
@@ -190,13 +196,15 @@ template <class T> struct Foo { Foo(T); };
 
 template<class V> using AFoo = Foo<V *>;
 template<typename> concept False = false;
-template<False W> using BFoo = AFoo<W>;
+template<False W>
+using BFoo = AFoo<W>; // expected-note {{candidate template ignored: constraints not satisfied [with V = int]}} \
+                      // expected-note {{because '__is_deducible(BFoo, Foo<int *>)' evaluated to false}} \
+                      // expected-note {{candidate template ignored: could not match 'Foo<type-parameter-0-0 *>' against 'int *'}}
 int i = 0;
 AFoo a1(&i); // OK, deduce Foo<int *>
 
-// FIXME: we should reject this case as the W is not deduced from the deduced
-// type Foo<int *>.
-BFoo b2(&i); 
+// the W is not deduced from the deduced type Foo<int *>.
+BFoo b2(&i); // expected-error {{no viable constructor or deduction guide for deduction of template arguments of 'BFoo'}}
 } // namespace test15
 
 namespace test16 {
diff --git a/clang/test/SemaCXX/type-traits-is-deducible.cpp b/clang/test/SemaCXX/type-traits-is-deducible.cpp
new file mode 100644
index 00000000000000..1a9ba19fb6035d
--- /dev/null
+++ b/clang/test/SemaCXX/type-traits-is-deducible.cpp
@@ -0,0 +1,47 @@
+// RUN: %clang_cc1 -fsyntax-only -std=c++20 -verify %s 
+// expected-no-diagnostics
+
+template<typename T>
+struct Foo {};
+static_assert(__is_deducible(Foo, Foo<int>));
+static_assert(!__is_deducible(Foo, int));
+
+template <class T>
+using AFoo1 = Foo<T*>;
+static_assert(__is_deducible(AFoo1, Foo<int*>));
+static_assert(!__is_deducible(AFoo1, Foo<int>));
+
+template <class T>
+using AFoo2 = Foo<int>;
+static_assert(!__is_deducible(AFoo2, Foo<int>));
+
+// default template argument counts.
+template <class T = double>
+using AFoo3 = Foo<int>;
+static_assert(__is_deducible(AFoo3, Foo<int>));
+
+
+template <int N>
+struct Bar { int k = N; };
+static_assert(__is_deducible(Bar, Bar<1>));
+
+template <int N>
+using ABar1 = Bar<N>;
+static_assert(__is_deducible(ABar1, Bar<3>));
+template <int N>
+using ABar2 = Bar<1>;
+static_assert(!__is_deducible(ABar2, Bar<1>));
+
+
+template <typename T>
+class Forward;
+static_assert(__is_deducible(Forward, Forward<int>));
+template <typename T>
+using AForward = Forward<T>;
+static_assert(__is_deducible(AForward, Forward<int>));
+
+
+template <class T, T N>
+using AArrary = int[N];
+static_assert (__is_deducible(AArrary, int[42]));
+static_assert (!__is_deducible(AArrary, double[42]));
diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html
index c233171e63c811..8b7c51a5610a90 100755
--- a/clang/www/cxx_status.html
+++ b/clang/www/cxx_status.html
@@ -868,13 +868,7 @@ <h2 id="cxx20">C++20 implementation status</h2>
     <tr>
       <td>Class template argument deduction for alias templates</td>
       <td><a href="https://wg21.link/p1814r0">P1814R0</a></td>
-      <td class="partial" align="center">
-        <details>
-          <summary>Clang 19 (Partial)</summary>
-          The associated constraints (over.match.class.deduct#3.3) for the
-          synthesized deduction guides are not yet implemented.
-        </details>
-      </td>
+      <td class="partial" align="center">Clang 19 (Partial)</td>
     </tr>
     <tr>
       <td>Permit conversions to arrays of unknown bound</td>

@hokein
Copy link
Collaborator Author

hokein commented Apr 19, 2024

Regarding the __is_deducible type trait, GCC also provides one, but it was hidden from users and only used for internal CTAD implementation. I'm not sure if we should follow the same strategy in clang, ideas?

Copy link
Collaborator

@erichkeane erichkeane left a comment

Choose a reason for hiding this comment

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

Needs a release note, and I think we actually DO have to do those diagnostics here.

TSTToBeDeduced->getTemplateName().getAsTemplateDecl(), RhsT,
Info) == TemplateDeductionResult::Success;
}
// FIXME: emit a diagnostic.
Copy link
Collaborator

Choose a reason for hiding this comment

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

I dont think we can leave this as a fixme.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

implemented a new diagnostic for bad argument types for the builtin.

->getUnderlyingType()
.getCanonicalType();
} else {
// FIXME: emit a diagnostic, we only only support alias and class templates.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It turns out we will never hit other cases in this code path (we will reject the var/function templates when parsing the type trait expr). I changed it to assert.

@hokein
Copy link
Collaborator Author

hokein commented Apr 22, 2024

Needs a release note, and I think we actually DO have to do those diagnostics here.

Added a release note for the new builtin.

@cor3ntin
Copy link
Contributor

Regarding the __is_deducible type trait, GCC also provides one, but it was hidden from users and only used for internal CTAD implementation. I'm not sure if we should follow the same strategy in clang, ideas?

I have mixed feeling. What do you think @AaronBallman ?

@AaronBallman
Copy link
Collaborator

Regarding the __is_deducible type trait, GCC also provides one, but it was hidden from users and only used for internal CTAD implementation. I'm not sure if we should follow the same strategy in clang, ideas?

I have mixed feeling. What do you think @AaronBallman ?

Personally, I do not like exposing type traits that aren't for use with the STL. One idea would be to remove the type trait from TokenKinds.def and instead manually add BTT_IsDeducible to:

along with a comment explaining that this is for internal use only rather than be exposed to users. (You'd have to see if there are other places using TYPE_TRAIT_2 that might need adjustment as well.) Then we can remove the release note, error checking can become assertions, etc.

synthesized deduction guides are not yet implemented.
</details>
</td>
<td class="partial" align="center">Clang 19 (Partial)</td>
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why is this still partial?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

With this patch, this feature is completed. However, I suspect that there maybe issues, and it needs some time to be productized. Therefore, I hesitate to say that it is "ready" (although I'm not sure what the common practice is).

Copy link
Contributor

Choose a reason for hiding this comment

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

Can you add back a details saying that the feature macro has not been updated?
However, i suspect that we do want to finalize the feature for Clang 19.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Added it back.

hokein added a commit that referenced this pull request Apr 22, 2024
The argument types are not modeled as children of TypeTraitExpr,
therefore they are not dumped with the default implementation.

Dumping them is really useful for ad-hoc debugging, context #89358
@hokein
Copy link
Collaborator Author

hokein commented Apr 23, 2024

Regarding the __is_deducible type trait, GCC also provides one, but it was hidden from users and only used for internal CTAD implementation. I'm not sure if we should follow the same strategy in clang, ideas?

I have mixed feeling. What do you think @AaronBallman ?

Personally, I do not like exposing type traits that aren't for use with the STL.

This is a valid argument. This type trait is supposed to be clang-internal only and should not be used in other places. The main benefit of exposing it is for testing purposes, and I personally found it to be really useful for debugging.

One idea would be to remove the type trait from TokenKinds.def and instead manually add BTT_IsDeducible to:

along with a comment explaining that this is for internal use only rather than be exposed to users. (You'd have to see if there are other places using TYPE_TRAIT_2 that might need adjustment as well.) Then we can remove the release note, error checking can become assertions, etc.

This approach is doable technically, but it feels hacky and fragile. What if we emit an error (or warning) diagnostic and reject the code when we parse the __is_deducible type trait?

@cor3ntin
Copy link
Contributor

This approach is doable technically, but it feels hacky and fragile. What if we emit an error (or warning) diagnostic and reject the code when we parse the __is_deducible type trait?

Why do you think it is fragile? I think a comment around BTT_IsDeducible would take care of that

@hokein
Copy link
Collaborator Author

hokein commented Apr 23, 2024

This approach is doable technically, but it feels hacky and fragile. What if we emit an error (or warning) diagnostic and reject the code when we parse the __is_deducible type trait?

Why do you think it is fragile? I think a comment around BTT_IsDeducible would take care of that

This is not a single-place change, we have to duplicate the BTT_IsDeducible manual change for at least 4 places:

@cor3ntin
Copy link
Contributor

This approach is doable technically, but it feels hacky and fragile. What if we emit an error (or warning) diagnostic and reject the code when we parse the __is_deducible type trait?

Why do you think it is fragile? I think a comment around BTT_IsDeducible would take care of that

This is not a single-place change, we have to duplicate the BTT_IsDeducible manual change for at least 4 places:

Something like that seem to be fine

// IsDeducible is only used internally by clang and is not exposed to source code
TYPE_TRAIT_2(/**/, IsDeducible, KEYCXX)

@hokein hokein force-pushed the ctad-is-deducible branch 2 times, most recently from 5020665 to c11f11b Compare April 24, 2024 07:51
Copy link

github-actions bot commented Apr 24, 2024

✅ With the latest revision this PR passed the C/C++ code formatter.

@hokein
Copy link
Collaborator Author

hokein commented Apr 24, 2024

OK, I have hidden the __is_deducible trait as suggested. Please take another look.

@cor3ntin
Copy link
Contributor

OK, I have hidden the __is_deducible trait as suggested. Please take another look.

Sorry, I think my suggestion was not clear enough.
did you try to put TYPE_TRAIT_2(/**/, IsDeducible, KEYCXX) in TokenKinds.def (and then other headers should NOT be modified)

@hokein
Copy link
Collaborator Author

hokein commented Apr 24, 2024

OK, I have hidden the __is_deducible trait as suggested. Please take another look.

Sorry, I think my suggestion was not clear enough. did you try to put TYPE_TRAIT_2(/**/, IsDeducible, KEYCXX) in TokenKinds.def (and then other headers should NOT be modified)

Ah, I see. I haven't tried that, but one downside I can see is that it will make diagnostics for CTAD worse, we need to spell the type trait name in error messages when the __is_deducible trait fails.

@cor3ntin
Copy link
Contributor

Ah, I see. I haven't tried that, but one downside I can see is that it will make diagnostics for CTAD worse, we need to spell the type trait name in error messages when the __is_deducible trait fails.

I think we should have a custom diag for that anyway
"is not deducible from" is better than " __is_deducible<> is false"

@hokein
Copy link
Collaborator Author

hokein commented Apr 24, 2024

Ah, I see. I haven't tried that, but one downside I can see is that it will make diagnostics for CTAD worse, we need to spell the type trait name in error messages when the __is_deducible trait fails.

I think we should have a custom diag for that anyway "is not deducible from" is better than " __is_deducible<> is false"

I think this is one of the symptoms, missing the trait name in the AST dump is another symptom.

And I'm not aware of an easy way to customize a diagnostic within clang's template-constraint check mechanism. One implementation I can think of is to manual call the DeduceTemplateArgumentsFromType in the function overload resolution for the alias CTAD, and then emit a custom diagnostic, but it feels awkward.

@hokein
Copy link
Collaborator Author

hokein commented Apr 26, 2024

It appears that the consensus is to avoid exposing the internal __is_deducible type trait to end-users, as there are no compelling use cases and it's not expected to be used in libc++. This also aligns with the approach taken by GCC.

Regarding implementation, the current tablegen-based mechanism doesn't offer an optimal solution for defining internal-only builtins. Several options:

  1. Making manual changes in the TypeTrait structure code, as proposed by @AaronBallman. While feasible, this approach may be somewhat hacky and would require modifications in multiple places (please see the current implementation, which involves four locations).
  2. Following the standard defining-builtin approach but emitting a diagnostic warning when Clang parses __is_deducible. This option seems to strike the right balance, as it provides a compromise between adherence to clang's standard implementation and user guidance. Additionally, it would allow for dedicated lit tests for this type trait.
  3. Following the standard approach but omitting the type trait name in the TYPE_TRAIT_2 macro, as suggested by @cor3ntin. However, this approach will affect components that need to print the type-trait name (e.g., AST dump, diagnostics).

Opinions and thoughts would be appreciated, @AaronBallman, @cor3ntin, @erichkeane

@cor3ntin
Copy link
Contributor

@hokein Independently of the direction taken I'd like to see a better diagnostic than "atomic constraint using an undocumented/cryptic trait that is not in the code is not satisfied". So when we try to print atomic constraints, we should do something more user friendly for is_deducible. (note_atomic_constraint_evaluated_to_false in diagnoseWellFormedUnsatisfiedConstraintExpr AFAICT).
It might be a bit ad-hoc, but I think it's worth doing

@hokein
Copy link
Collaborator Author

hokein commented Apr 29, 2024

@hokein Independently of the direction taken I'd like to see a better diagnostic than "atomic constraint using an undocumented/cryptic trait that is not in the code is not satisfied".
So when we try to print atomic constraints, we should do something more user friendly for is_deducible. (note_atomic_constraint_evaluated_to_false in diagnoseWellFormedUnsatisfiedConstraintExpr AFAICT). It might be a bit ad-hoc, but I think it's worth doing

I agree with you -- having a well-described diagnostic message is better and clearer. I'm happy to improve it once we settle on the final implementation approach (the current diagnostic because '__is_deducible(AFoo, Foo<int>)' evaluated to false seems okay to me. GCC also emits similar diagnostics).

By the way, other parts of this patch are ready for review.

@cor3ntin
Copy link
Contributor

I agree with you -- having a well-described diagnostic message is better and clearer. I'm happy to improve it once we settle on the final implementation approach (the current diagnostic because '__is_deducible(AFoo, Foo)' evaluated to false seems okay to me. GCC also emits similar diagnostics).

Well, if we agree on that, the only thing to do for approach 3 is to deal with "anonymous" traits in ast dump and similar, which seems to be a fairly bounded effort!

@hokein
Copy link
Collaborator Author

hokein commented May 7, 2024

I agree with you -- having a well-described diagnostic message is better and clearer. I'm happy to improve it once we settle on the final implementation approach (the current diagnostic because '__is_deducible(AFoo, Foo)' evaluated to false seems okay to me. GCC also emits similar diagnostics).

Well, if we agree on that, the only thing to do for approach 3 is to deal with "anonymous" traits in ast dump and similar, which seems to be a fairly bounded effort!

I was worried about the potential need to handle these issues in various places. However, thinking more about it today, I think we can address them locally in the getTraitSpelling, which seems like a more favorable approach.

I've now updated the patch to implement approach 3. Please take a look. Note that I haven't addressed the diagnostic improvement part yet; I plan to handle that in a separate follow-up patch.

@@ -27,7 +27,8 @@ enum TypeTrait {
,
#define TYPE_TRAIT_2(Spelling, Name, Key) BTT_##Name,
#include "clang/Basic/TokenKinds.def"
BTT_Last = UTT_Last // BTT_Last == last BTT_XX in the enum.
BTT_Last = UTT_Last
Copy link
Contributor

Choose a reason for hiding this comment

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

Why remove the comment?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

oops, this is not intended. This was left when I reverted some changes.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Reverted the change.

Comment on lines +3344 to +3343
TemplateDeductionResult
Sema::DeduceTemplateArgumentsFromType(TemplateDecl *TD, QualType FromType,
Copy link
Contributor

Choose a reason for hiding this comment

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

We have a lot of DeduceTemplateArguments function so I think we want to add a comment here describing what that function does / when it is used.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This function is public (the documentation and comments are in Sema.h header).

/// Complete template argument deduction for DeduceTemplateArgumentsFromType.
/// FIXME: this is mostly duplicated with the above two versions. Deduplicate
/// the three implementations.
Copy link
Contributor

Choose a reason for hiding this comment

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

Is that something you would be willing to do in a follow up patch?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It is in my TODO list, but its priority is currently low, so I don't anticipate addressing it in the near future. This seems like a good candidate for a "GoodFirst" issue, so I'm thinking we can file an issue in case someone wants to contribute.

FunctionTemplateDecl *FTD,
llvm::ArrayRef<TemplateArgument> TransformedArgs,
QualType ReturnType) {
auto &Context = SemaRef.Context;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
auto &Context = SemaRef.Context;
(const?) ASTContext &Context = SemaRef.Context;

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not seeing a change here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Oops, it looks like I forgot to push one commit.

@@ -18,6 +18,7 @@
#include "clang/AST/ExprCXX.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/AST/TemplateName.h"
#include "clang/AST/Type.h"
Copy link
Contributor

Choose a reason for hiding this comment

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

Probably not a needed change

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

TSTToBeDeduced->getTemplateName().getAsTemplateDecl(), RhsT,
Info) == TemplateDeductionResult::Success;
}
assert("Expect to see DeducedTemplateSpecializationType!");
Copy link
Contributor

Choose a reason for hiding this comment

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

that is always true (you probably mean false && "").
But I would remove the if, use cast<DeducedTemplateSpecializationType>(LhsT) that will assert for you)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Switched to cast.

template<False W> using BFoo = AFoo<W>;
template<False W>
using BFoo = AFoo<W>; // expected-note {{candidate template ignored: constraints not satisfied [with V = int]}} \
// expected-note {{because '__is_deducible(BFoo, Foo<int *>)' evaluated to false}} \
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we want to add code in this PR to improve the diagnostic

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Do you think this is a blocker for landing this PR? The current state is acceptable to me, although not ideal. I plan to send out a follow-up patch to address it.

Copy link
Contributor

Choose a reason for hiding this comment

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

I certainly think it's not a good user-facing diagnostic, and i don't think the fix would be difficult
but doing it separately seems reasonable. WDYT @AaronBallman?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'd prefer to do it in a follow up (this patch is quite large already). Added a FIXME.

Copy link
Collaborator Author

@hokein hokein left a comment

Choose a reason for hiding this comment

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

Replied some review comments. I haven't updated the code yet (plan to do it after #90961)

@@ -27,7 +27,8 @@ enum TypeTrait {
,
#define TYPE_TRAIT_2(Spelling, Name, Key) BTT_##Name,
#include "clang/Basic/TokenKinds.def"
BTT_Last = UTT_Last // BTT_Last == last BTT_XX in the enum.
BTT_Last = UTT_Last
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

oops, this is not intended. This was left when I reverted some changes.

/// Complete template argument deduction for DeduceTemplateArgumentsFromType.
/// FIXME: this is mostly duplicated with the above two versions. Deduplicate
/// the three implementations.
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It is in my TODO list, but its priority is currently low, so I don't anticipate addressing it in the near future. This seems like a good candidate for a "GoodFirst" issue, so I'm thinking we can file an issue in case someone wants to contribute.

Comment on lines +3344 to +3343
TemplateDeductionResult
Sema::DeduceTemplateArgumentsFromType(TemplateDecl *TD, QualType FromType,
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This function is public (the documentation and comments are in Sema.h header).

template<False W> using BFoo = AFoo<W>;
template<False W>
using BFoo = AFoo<W>; // expected-note {{candidate template ignored: constraints not satisfied [with V = int]}} \
// expected-note {{because '__is_deducible(BFoo, Foo<int *>)' evaluated to false}} \
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Do you think this is a blocker for landing this PR? The current state is acceptable to me, although not ideal. I plan to send out a follow-up patch to address it.

Copy link
Contributor

@cor3ntin cor3ntin left a comment

Choose a reason for hiding this comment

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

I'll approve that modulo nit but i think we want

  • An issue to keep track of the duplication in FinishTemplateArgumentDeduction
  • A follow up PR to improve diagnostics

Thanks!

FunctionTemplateDecl *FTD,
llvm::ArrayRef<TemplateArgument> TransformedArgs,
QualType ReturnType) {
auto &Context = SemaRef.Context;
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not seeing a change here?

@hokein
Copy link
Collaborator Author

hokein commented May 15, 2024

Thanks for the review.

I'll approve that modulo nit but i think we want

  • An issue to keep track of the duplication in FinishTemplateArgumentDeduction

Filed #92224.

  • A follow up PR to improve diagnostics

Filed #92225, and assigned to myself. Will do it.

Thanks!

…templates.

Fixes llvm#85192
Fixes llvm#84492

- rebase to main
- add release note for __is_deducible
- implement diagnostics for bad argument types for __is_deducible

Don't expose __is_deducible trait.

Refine the implementation of hiding __is_deducible type trait.

Apply approach 3.
@hokein hokein force-pushed the ctad-is-deducible branch 3 times, most recently from 2d8720a to c4067b0 Compare May 15, 2024 12:01
@hokein hokein merged commit a960573 into llvm:main May 16, 2024
4 checks passed
@hokein hokein deleted the ctad-is-deducible branch May 16, 2024 11:01
mub-at-arm pushed a commit to mub-at-arm/llvm-project that referenced this pull request May 16, 2024
…templates (llvm#89358)

Fixes llvm#85192
Fixes llvm#84492

This patch implements the "IsDeducible" constraint where the template
arguments of the alias template can be deduced from the returned type of
the synthesized deduction guide, per C++ [over.match.class.deduct]p4. In
the implementation, we perform the deduction directly, which is more
efficient than the way specified in the standard.

Also update relevant CTAD tests which were incorrectly compiled due to
the missing constraint.
MaskRay added a commit that referenced this pull request May 16, 2024
The dictionary entry `=""` is invalid.
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
5 participants