- 
                Notifications
    You must be signed in to change notification settings 
- Fork 15k
[Clang][Sema] Defer instantiation of exception specification until after partial ordering when determining primary template #82417
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
Conversation
| @llvm/pr-subscribers-clang Author: Krystian Stasiowski (sdkrystian) ChangesConsider the following: struct A {
  static constexpr bool x = true;
};
template<typename T, typename U>
void f(T, U) noexcept(T::y); // #<!-- -->1, error: no member named 'y' in 'A'
template<typename T, typename U>
void f(T, U*) noexcept(T::x); // #<!-- -->2
template<>
void f(A, int*) noexcept; // explicit specialization of #<!-- -->2We currently instantiate the exception specification of all candidate function template specializations when deducting template arguments for an explicit specialization, which results in a error despite  Assuming that "comparing declarations" means "determining whether the declarations correspond and declare the same entity" (per [[basic.scope.scope] p4](http://eel.is/c++draft/basic.scope.scope#4) and [[basic.link] p11.1](http://eel.is/c++draft/basic.link#11.1), respectively), the exception specification does not need to be instantiated until after partial ordering, at which point we determine whether the implicitly instantiated specialization and the explicit specialization declare the same entity (the determination of whether two functions/function templates correspond does not consider the exception specifications). This patch defers the instantiation of the exception specification until a single function template specialization is selected via partial ordering, matching the behavior of GCC, EDG, and MSVC. (Note: I'm working on a patch that eliminates the implicitly instantiated specialization of each candidate function template when determining the primary template of an explicit specialization, but I figured that this change to when exception specifications are instantiated would be better off as its own patch). Full diff: https://github.com/llvm/llvm-project/pull/82417.diff 4 Files Affected: 
 diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index 1a975a8d0a0df5..9c75438011010a 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -9706,9 +9706,17 @@ bool Sema::CheckFunctionTemplateSpecialization(
   if (Result == Candidates.end())
     return true;
 
-  // Ignore access information;  it doesn't figure into redeclaration checking.
   FunctionDecl *Specialization = cast<FunctionDecl>(*Result);
+  auto *SpecializationFPT =
+      Specialization->getType()->castAs<FunctionProtoType>();
+  // If the function has a dependent exception specification, resolve it after
+  // we have selected the primary template so we can check whether it matches.
+  if (getLangOpts().CPlusPlus17 &&
+      isUnresolvedExceptionSpec(SpecializationFPT->getExceptionSpecType()) &&
+      !ResolveExceptionSpec(FD->getLocation(), SpecializationFPT))
+    return true;
 
+  // Ignore access information;  it doesn't figure into redeclaration checking.
   FunctionTemplateSpecializationInfo *SpecInfo
     = Specialization->getTemplateSpecializationInfo();
   assert(SpecInfo && "Function template specialization info missing?");
diff --git a/clang/lib/Sema/SemaTemplateDeduction.cpp b/clang/lib/Sema/SemaTemplateDeduction.cpp
index 47cc22310c4eec..3c04dd030d5ebb 100644
--- a/clang/lib/Sema/SemaTemplateDeduction.cpp
+++ b/clang/lib/Sema/SemaTemplateDeduction.cpp
@@ -4632,11 +4632,10 @@ TemplateDeductionResult Sema::DeduceTemplateArguments(
                                                Info.getLocation()))
     return TemplateDeductionResult::MiscellaneousDeductionFailure;
 
-  // If the function has a dependent exception specification, resolve it now,
-  // so we can check that the exception specification matches.
+
   auto *SpecializationFPT =
       Specialization->getType()->castAs<FunctionProtoType>();
-  if (getLangOpts().CPlusPlus17 &&
+  if (IsAddressOfFunction && getLangOpts().CPlusPlus17 &&
       isUnresolvedExceptionSpec(SpecializationFPT->getExceptionSpecType()) &&
       !ResolveExceptionSpec(Info.getLocation(), SpecializationFPT))
     return TemplateDeductionResult::MiscellaneousDeductionFailure;
@@ -4662,11 +4661,11 @@ TemplateDeductionResult Sema::DeduceTemplateArguments(
   // specialization with respect to arguments of compatible pointer to function
   // types, template argument deduction fails.
   if (!ArgFunctionType.isNull()) {
-    if (IsAddressOfFunction
-            ? !isSameOrCompatibleFunctionType(
-                  Context.getCanonicalType(SpecializationType),
-                  Context.getCanonicalType(ArgFunctionType))
-            : !Context.hasSameType(SpecializationType, ArgFunctionType)) {
+    if (IsAddressOfFunction ? !isSameOrCompatibleFunctionType(
+                                  Context.getCanonicalType(SpecializationType),
+                                  Context.getCanonicalType(ArgFunctionType))
+                            : !Context.hasSameFunctionTypeIgnoringExceptionSpec(
+                                  SpecializationType, ArgFunctionType)) {
       Info.FirstArg = TemplateArgument(SpecializationType);
       Info.SecondArg = TemplateArgument(ArgFunctionType);
       return TemplateDeductionResult::NonDeducedMismatch;
diff --git a/clang/test/CXX/except/except.spec/p13.cpp b/clang/test/CXX/except/except.spec/p13.cpp
new file mode 100644
index 00000000000000..6a4f699ed340d2
--- /dev/null
+++ b/clang/test/CXX/except/except.spec/p13.cpp
@@ -0,0 +1,38 @@
+// RUN: %clang_cc1 -fexceptions -fcxx-exceptions -fsyntax-only -verify %s
+
+struct A {
+  static constexpr bool x = true;
+};
+
+template<typename T, typename U>
+void f(T, U) noexcept(T::x);
+
+template<typename T, typename U>
+void f(T, U*) noexcept(T::x);
+
+template<typename T, typename U>
+void f(T, U**) noexcept(T::y); // expected-error {{no member named 'y' in 'A'}}
+
+template<typename T, typename U>
+void f(T, U***) noexcept(T::x);
+
+template<>
+void f(A, int*) noexcept; // expected-note {{previous declaration is here}}
+
+template<>
+void f(A, int*); // expected-error {{'f<A, int>' is missing exception specification 'noexcept'}}
+
+template<>
+void f(A, int**) noexcept; // expected-error {{exception specification in declaration does not match previous declaration}}
+                           // expected-note@-1 {{in instantiation of exception specification for 'f<A, int>' requested here}}
+                           // expected-note@-2 {{previous declaration is here}}
+
+// FIXME: Exception specification is currently set to EST_None if instantiation fails.
+template<>
+void f(A, int**);
+
+template<>
+void f(A, int***) noexcept; // expected-note {{previous declaration is here}}
+
+template<>
+void f(A, int***); // expected-error {{'f<A, int>' is missing exception specification 'noexcept'}}
diff --git a/clang/test/SemaTemplate/class-template-noexcept.cpp b/clang/test/SemaTemplate/class-template-noexcept.cpp
index 5c4ac090f3166d..14d2e36bc0bfae 100644
--- a/clang/test/SemaTemplate/class-template-noexcept.cpp
+++ b/clang/test/SemaTemplate/class-template-noexcept.cpp
@@ -2,9 +2,7 @@
 // RUN: %clang_cc1 -std=c++11 -verify %s
 // RUN: %clang_cc1 -std=c++17 -verify %s
 // RUN: %clang_cc1 -std=c++1z -verify %s
-#if __cplusplus >= 201703
-// expected-no-diagnostics
-#endif
+
 class A {
 public:
   static const char X;
@@ -14,19 +12,9 @@ const char A::X = 0;
 template<typename U> void func() noexcept(U::X);
 
 template<class... B, char x>
-#if __cplusplus >= 201703
-void foo(void(B...) noexcept(x)) {} 
-#else
 void foo(void(B...) noexcept(x)) {} // expected-note{{candidate template ignored}}
-#endif
 
 void bar()
 {
-#if __cplusplus >= 201703
-  foo(func<A>);
-#else
   foo(func<A>);	// expected-error{{no matching function for call}}
-#endif	
 }
-
-
 | 
7e12eab    to
    16b6ce3      
    Compare
  
    There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1 nit, else LGTM. We should probably have a release note as well.
3966e98    to
    e669903      
    Compare
  
    | @erichkeane Added release note, relevant standardese quote, and some exposition. | 
…ter partial ordering when determining primary template
e669903    to
    06fe8f5      
    Compare
  
    | Also added a couple more tests since the diagnostics currently depend on whether instantiation occurs. | 
Consider the following:
We currently instantiate the exception specification of all candidate function template specializations when deducting template arguments for an explicit specialization, which results in a error despite
#1not being selected by partial ordering as the most specialized template. According to [except.spec] p13:Assuming that "comparing declarations" means "determining whether the declarations correspond and declare the same entity" (per [basic.scope.scope] p4 and [basic.link] p11.1, respectively), the exception specification does not need to be instantiated until after partial ordering, at which point we determine whether the implicitly instantiated specialization and the explicit specialization declare the same entity (the determination of whether two functions/function templates correspond does not consider the exception specifications).
This patch defers the instantiation of the exception specification until a single function template specialization is selected via partial ordering, matching the behavior of GCC, EDG, and MSVC.
(Note: I'm working on a patch that eliminates the implicitly instantiated specialization of each candidate function template when determining the primary template of an explicit specialization, but I figured that this change to when exception specifications are instantiated would be better off as its own patch).