Skip to content

[Clang][Sema][Parse] Delay parsing of noexcept-specifiers in friend function declarations #90517

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

Merged
merged 5 commits into from
Apr 30, 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
1 change: 1 addition & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,7 @@ Bug Fixes to C++ Support
- Fix a bug on template partial specialization whose template parameter is `decltype(auto)`.
- Fix a bug on template partial specialization with issue on deduction of nontype template parameter
whose type is `decltype(auto)`. Fixes (#GH68885).
- Clang now correctly treats the noexcept-specifier of a friend function to be a complete-class context.

Bug Fixes to AST Handling
^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
10 changes: 5 additions & 5 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -4097,12 +4097,12 @@ class Sema final : public SemaBase {
SmallVectorImpl<QualType> &Exceptions,
FunctionProtoType::ExceptionSpecInfo &ESI);

/// Add an exception-specification to the given member function
/// (or member function template). The exception-specification was parsed
/// after the method itself was declared.
/// Add an exception-specification to the given member or friend function
/// (or function template). The exception-specification was parsed
/// after the function itself was declared.
void actOnDelayedExceptionSpecification(
Decl *Method, ExceptionSpecificationType EST,
SourceRange SpecificationRange, ArrayRef<ParsedType> DynamicExceptions,
Decl *D, ExceptionSpecificationType EST, SourceRange SpecificationRange,
ArrayRef<ParsedType> DynamicExceptions,
ArrayRef<SourceRange> DynamicExceptionRanges, Expr *NoexceptExpr);

class InheritedConstructorInfo;
Expand Down
20 changes: 14 additions & 6 deletions clang/lib/Parse/ParseDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7416,12 +7416,20 @@ void Parser::ParseFunctionDeclarator(Declarator &D,
std::optional<Sema::CXXThisScopeRAII> ThisScope;
InitCXXThisScopeForDeclaratorIfRelevant(D, DS, ThisScope);

// Parse exception-specification[opt].
// FIXME: Per [class.mem]p6, all exception-specifications at class scope
// should be delayed, including those for non-members (eg, friend
// declarations). But only applying this to member declarations is
// consistent with what other implementations do.
bool Delayed = D.isFirstDeclarationOfMember() &&
// C++ [class.mem.general]p8:
// A complete-class context of a class (template) is a
// - function body,
// - default argument,
// - default template argument,
// - noexcept-specifier, or
// - default member initializer
// within the member-specification of the class or class template.
//
// Parse exception-specification[opt]. If we are in the
// member-specification of a class or class template, this is a
// complete-class context and parsing of the noexcept-specifier should be
// delayed (even if this is a friend declaration).
bool Delayed = D.getContext() == DeclaratorContext::Member &&
D.isFunctionDeclaratorAFunctionDeclaration();
if (Delayed && Actions.isLibstdcxxEagerExceptionSpecHack(D) &&
GetLookAheadToken(0).is(tok::kw_noexcept) &&
Expand Down
40 changes: 20 additions & 20 deletions clang/lib/Sema/SemaDeclCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19172,40 +19172,40 @@ void Sema::checkExceptionSpecification(
}
}

void Sema::actOnDelayedExceptionSpecification(Decl *MethodD,
ExceptionSpecificationType EST,
SourceRange SpecificationRange,
ArrayRef<ParsedType> DynamicExceptions,
ArrayRef<SourceRange> DynamicExceptionRanges,
Expr *NoexceptExpr) {
if (!MethodD)
void Sema::actOnDelayedExceptionSpecification(
Decl *D, ExceptionSpecificationType EST, SourceRange SpecificationRange,
ArrayRef<ParsedType> DynamicExceptions,
ArrayRef<SourceRange> DynamicExceptionRanges, Expr *NoexceptExpr) {
if (!D)
return;

// Dig out the method we're referring to.
if (FunctionTemplateDecl *FunTmpl = dyn_cast<FunctionTemplateDecl>(MethodD))
MethodD = FunTmpl->getTemplatedDecl();
// Dig out the function we're referring to.
if (FunctionTemplateDecl *FTD = dyn_cast<FunctionTemplateDecl>(D))
D = FTD->getTemplatedDecl();

CXXMethodDecl *Method = dyn_cast<CXXMethodDecl>(MethodD);
if (!Method)
FunctionDecl *FD = dyn_cast<FunctionDecl>(D);
if (!FD)
return;

// Check the exception specification.
llvm::SmallVector<QualType, 4> Exceptions;
FunctionProtoType::ExceptionSpecInfo ESI;
checkExceptionSpecification(/*IsTopLevel*/true, EST, DynamicExceptions,
checkExceptionSpecification(/*IsTopLevel=*/true, EST, DynamicExceptions,
DynamicExceptionRanges, NoexceptExpr, Exceptions,
ESI);

// Update the exception specification on the function type.
Context.adjustExceptionSpec(Method, ESI, /*AsWritten*/true);
Context.adjustExceptionSpec(FD, ESI, /*AsWritten=*/true);

if (Method->isStatic())
checkThisInStaticMemberFunctionExceptionSpec(Method);
if (CXXMethodDecl *MD = dyn_cast<CXXMethodDecl>(D)) {
if (MD->isStatic())
checkThisInStaticMemberFunctionExceptionSpec(MD);

if (Method->isVirtual()) {
// Check overrides, which we previously had to delay.
for (const CXXMethodDecl *O : Method->overridden_methods())
CheckOverridingFunctionExceptionSpec(Method, O);
if (MD->isVirtual()) {
// Check overrides, which we previously had to delay.
for (const CXXMethodDecl *O : MD->overridden_methods())
CheckOverridingFunctionExceptionSpec(MD, O);
}
}
}

Expand Down
13 changes: 7 additions & 6 deletions clang/lib/Sema/SemaExceptionSpec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -258,13 +258,14 @@ Sema::UpdateExceptionSpec(FunctionDecl *FD,
}

static bool exceptionSpecNotKnownYet(const FunctionDecl *FD) {
auto *MD = dyn_cast<CXXMethodDecl>(FD);
if (!MD)
ExceptionSpecificationType EST =
FD->getType()->castAs<FunctionProtoType>()->getExceptionSpecType();
if (EST == EST_Unparsed)
return true;
else if (EST != EST_Unevaluated)
return false;

auto EST = MD->getType()->castAs<FunctionProtoType>()->getExceptionSpecType();
return EST == EST_Unparsed ||
(EST == EST_Unevaluated && MD->getParent()->isBeingDefined());
const DeclContext *DC = FD->getLexicalDeclContext();
return DC->isRecord() && cast<RecordDecl>(DC)->isBeingDefined();
}

static bool CheckEquivalentExceptionSpecImpl(
Expand Down
78 changes: 78 additions & 0 deletions clang/test/CXX/class/class.mem/class.mem.general/p8.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// RUN: %clang_cc1 -fsyntax-only -verify %s

namespace N0 {
struct A {
void f0() noexcept(x);
void g0() noexcept(y); // expected-error {{use of undeclared identifier 'y'}}

void f1() noexcept(A::x);
void g1() noexcept(A::y); // expected-error {{no member named 'y' in 'N0::A'}}

template<typename T>
void f2() noexcept(x);
template<typename T>
void g2() noexcept(y); // expected-error {{use of undeclared identifier 'y'}}

template<typename T>
void f3() noexcept(A::x);
template<typename T>
void g3() noexcept(A::y); // expected-error {{no member named 'y' in 'N0::A'}}

friend void f4() noexcept(x);
friend void g4() noexcept(y); // expected-error {{use of undeclared identifier 'y'}}

friend void f5() noexcept(A::x);
friend void g5() noexcept(A::y); // expected-error {{no member named 'y' in 'N0::A'}}

template<typename T>
friend void f6() noexcept(x);
template<typename T>
friend void g6() noexcept(y); // expected-error {{use of undeclared identifier 'y'}}

template<typename T>
friend void f7() noexcept(A::x);
template<typename T>
friend void g7() noexcept(A::y); // expected-error {{no member named 'y' in 'N0::A'}}

static constexpr bool x = true;
};
} // namespace N0

namespace N1 {
template<typename T>
struct A {
void f0() noexcept(x);
void g0() noexcept(y); // expected-error {{use of undeclared identifier 'y'}}

void f1() noexcept(A::x);
void g1() noexcept(A::y); // expected-error {{no member named 'y' in 'A<T>'}}

template<typename U>
void f2() noexcept(x);
template<typename U>
void g2() noexcept(y); // expected-error {{use of undeclared identifier 'y'}}

template<typename U>
void f3() noexcept(A::x);
template<typename U>
void g3() noexcept(A::y); // expected-error {{no member named 'y' in 'A<T>'}}

friend void f4() noexcept(x);
friend void g4() noexcept(y); // expected-error {{use of undeclared identifier 'y'}}

friend void f5() noexcept(A::x);
friend void g5() noexcept(A::y); // expected-error {{no member named 'y' in 'A<T>'}}

template<typename U>
friend void f6() noexcept(x);
template<typename U>
friend void g6() noexcept(y); // expected-error {{use of undeclared identifier 'y'}}

template<typename U>
friend void f7() noexcept(A::x);
template<typename U>
friend void g7() noexcept(A::y); // expected-error {{no member named 'y' in 'A<T>'}}

static constexpr bool x = true;
};
} // namespace N1
29 changes: 29 additions & 0 deletions clang/test/CXX/except/except.spec/p13-friend.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// RUN: %clang_cc1 -fexceptions -fcxx-exceptions -fsyntax-only -verify %s

namespace N0 {
void f() noexcept;
void g() noexcept;

struct A {
friend void f() noexcept;
friend void g() noexcept(x);

static constexpr bool x = true;
};
} // namespace N0

namespace N1 {
void f() noexcept;
void g();

template<typename T>
struct A {
friend void f() noexcept;
// FIXME: This error is emitted if no other errors occured (i.e. Sema::hasUncompilableErrorOccurred() is false).
friend void g() noexcept(x); // expected-error {{no member 'x' in 'N1::A<int>'; it has not yet been instantiated}}
// expected-note@-1 {{in instantiation of exception specification}}
static constexpr bool x = false; // expected-note {{not-yet-instantiated member is declared here}}
};

template struct A<int>; // expected-note {{in instantiation of template class}}
} // namespace N1