Skip to content

Commit

Permalink
[Clang][Sema] Defer instantiation of exception specification until af…
Browse files Browse the repository at this point in the history
…ter partial ordering when determining primary template (llvm#82417)

Consider 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); // rust-lang#2

template<>
void f(A, int*) noexcept; // explicit specialization of rust-lang#2
```

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 `#1` not
being selected by partial ordering as the most specialized template.
According to [except.spec] p13:
> An exception specification is considered to be needed when: 
> - [...]
> - the exception specification is compared to that of another
declaration (e.g., an explicit specialization or an overriding virtual
function);

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: see https://godbolt.org/z/Ebb6GTcWE.
  • Loading branch information
sdkrystian authored Feb 26, 2024
1 parent 83feb84 commit 9cfb138
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 21 deletions.
2 changes: 2 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,8 @@ Bug Fixes to C++ Support
a requires-clause lie at the same depth as those of the surrounding lambda. This,
in turn, results in the wrong template argument substitution during constraint checking.
(`#78524 <https://github.com/llvm/llvm-project/issues/78524>`_)
- Clang no longer instantiates the exception specification of discarded candidate function
templates when determining the primary template of an explicit specialization.

Bug Fixes to AST Handling
^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
34 changes: 34 additions & 0 deletions clang/lib/Sema/SemaTemplate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9709,6 +9709,40 @@ bool Sema::CheckFunctionTemplateSpecialization(
// Ignore access information; it doesn't figure into redeclaration checking.
FunctionDecl *Specialization = cast<FunctionDecl>(*Result);

// C++23 [except.spec]p13:
// An exception specification is considered to be needed when:
// - [...]
// - the exception specification is compared to that of another declaration
// (e.g., an explicit specialization or an overriding virtual function);
// - [...]
//
// The exception specification of a defaulted function is evaluated as
// described above only when needed; similarly, the noexcept-specifier of a
// specialization of a function template or member function of a class
// template is instantiated only when needed.
//
// The standard doesn't specify what the "comparison with another declaration"
// entails, nor the exact circumstances in which it occurs. Moreover, it does
// not state which properties of an explicit specialization must match the
// primary template.
//
// We assume that an explicit specialization must correspond with (per
// [basic.scope.scope]p4) and declare the same entity as (per [basic.link]p8)
// the declaration produced by substitution into the function template.
//
// Since the determination whether two function declarations correspond does
// not consider exception specification, we only need to instantiate it once
// we determine the primary template when comparing types per
// [basic.link]p11.1.
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;

FunctionTemplateSpecializationInfo *SpecInfo
= Specialization->getTemplateSpecializationInfo();
assert(SpecInfo && "Function template specialization info missing?");
Expand Down
14 changes: 6 additions & 8 deletions clang/lib/Sema/SemaTemplateDeduction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4632,11 +4632,9 @@ 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;
Expand All @@ -4662,11 +4660,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;
Expand Down
74 changes: 74 additions & 0 deletions clang/test/CXX/except/except.spec/p13.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// RUN: %clang_cc1 -fexceptions -fcxx-exceptions -fsyntax-only -verify %s

struct A {
static constexpr bool x = true;
};

namespace N0 {

template<typename T, typename U>
void f(T, U) noexcept(T::y); // #1

template<typename T, typename U> // #2
void f(T, U*) noexcept(T::x);

// Deduction should succeed for both candidates, and #2 should be selected as the primary template.
// Only the exception specification of #2 should be instantiated.
template<>
void f(A, int*) noexcept;

}

namespace N1 {

template<typename T, typename U>
void f(T, U) noexcept(T::x); // #1

template<typename T, typename U>
void f(T, U*) noexcept(T::y); // #2
// expected-error@-1 {{no member named 'y' in 'A'}}

// Deduction should succeed for both candidates, and #2 should be selected as the primary template.
// Only the exception specification of #2 should be instantiated.
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}}
}

namespace N2 {

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'}}

}
14 changes: 1 addition & 13 deletions clang/test/SemaTemplate/class-template-noexcept.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
}


0 comments on commit 9cfb138

Please sign in to comment.