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][Sema] Defer instantiation of exception specification until after partial ordering when determining primary template #82417

Merged
merged 1 commit into from
Feb 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -9707,6 +9707,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 =
sdkrystian marked this conversation as resolved.
Show resolved Hide resolved
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
}


Loading