Skip to content

Commit f061a39

Browse files
authored
[Clang][Sema][Parse] Delay parsing of noexcept-specifiers in friend function declarations (#90517)
According to [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. When testing #90152, it came to my attention that we do _not_ consider the _noexcept-specifier_ of a friend function declaration to be a complete-class context (something which the Microsoft standard library depends on). Although a comment states that this is "consistent with what other implementations do", the only other implementation that exhibits this behavior is GCC (MSVC and EDG both late-parse the _noexcept-specifier_). This patch changes _noexcept-specifiers_ of friend function declarations to be late parsed, which is in agreement with the standard & majority of implementations. Pre-#90152, our existing implementation falls "in between" the implementation consensus: within non-template classes, we would not find latter declared members (qualified and unqualified), while within class templates we would not find latter declared member when named with a unqualified name, we would find members named with a qualified name (even when lookup context is the current instantiation). Therefore, this _shouldn't_ be a breaking change -- any code that didn't compile will continue to not compile (since a _noexcept-specifier_ is not part of the deduction substitution loci (see [temp.deduct.general] p7), and any code which did compile should continue to do so.
1 parent 7662f95 commit f061a39

File tree

7 files changed

+154
-37
lines changed

7 files changed

+154
-37
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,7 @@ Bug Fixes to C++ Support
614614
- Fix a bug on template partial specialization whose template parameter is `decltype(auto)`.
615615
- Fix a bug on template partial specialization with issue on deduction of nontype template parameter
616616
whose type is `decltype(auto)`. Fixes (#GH68885).
617+
- Clang now correctly treats the noexcept-specifier of a friend function to be a complete-class context.
617618

618619
Bug Fixes to AST Handling
619620
^^^^^^^^^^^^^^^^^^^^^^^^^

clang/include/clang/Sema/Sema.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4097,12 +4097,12 @@ class Sema final : public SemaBase {
40974097
SmallVectorImpl<QualType> &Exceptions,
40984098
FunctionProtoType::ExceptionSpecInfo &ESI);
40994099

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

41084108
class InheritedConstructorInfo;

clang/lib/Parse/ParseDecl.cpp

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7416,12 +7416,20 @@ void Parser::ParseFunctionDeclarator(Declarator &D,
74167416
std::optional<Sema::CXXThisScopeRAII> ThisScope;
74177417
InitCXXThisScopeForDeclaratorIfRelevant(D, DS, ThisScope);
74187418

7419-
// Parse exception-specification[opt].
7420-
// FIXME: Per [class.mem]p6, all exception-specifications at class scope
7421-
// should be delayed, including those for non-members (eg, friend
7422-
// declarations). But only applying this to member declarations is
7423-
// consistent with what other implementations do.
7424-
bool Delayed = D.isFirstDeclarationOfMember() &&
7419+
// C++ [class.mem.general]p8:
7420+
// A complete-class context of a class (template) is a
7421+
// - function body,
7422+
// - default argument,
7423+
// - default template argument,
7424+
// - noexcept-specifier, or
7425+
// - default member initializer
7426+
// within the member-specification of the class or class template.
7427+
//
7428+
// Parse exception-specification[opt]. If we are in the
7429+
// member-specification of a class or class template, this is a
7430+
// complete-class context and parsing of the noexcept-specifier should be
7431+
// delayed (even if this is a friend declaration).
7432+
bool Delayed = D.getContext() == DeclaratorContext::Member &&
74257433
D.isFunctionDeclaratorAFunctionDeclaration();
74267434
if (Delayed && Actions.isLibstdcxxEagerExceptionSpecHack(D) &&
74277435
GetLookAheadToken(0).is(tok::kw_noexcept) &&

clang/lib/Sema/SemaDeclCXX.cpp

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19172,40 +19172,40 @@ void Sema::checkExceptionSpecification(
1917219172
}
1917319173
}
1917419174

19175-
void Sema::actOnDelayedExceptionSpecification(Decl *MethodD,
19176-
ExceptionSpecificationType EST,
19177-
SourceRange SpecificationRange,
19178-
ArrayRef<ParsedType> DynamicExceptions,
19179-
ArrayRef<SourceRange> DynamicExceptionRanges,
19180-
Expr *NoexceptExpr) {
19181-
if (!MethodD)
19175+
void Sema::actOnDelayedExceptionSpecification(
19176+
Decl *D, ExceptionSpecificationType EST, SourceRange SpecificationRange,
19177+
ArrayRef<ParsedType> DynamicExceptions,
19178+
ArrayRef<SourceRange> DynamicExceptionRanges, Expr *NoexceptExpr) {
19179+
if (!D)
1918219180
return;
1918319181

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

19188-
CXXMethodDecl *Method = dyn_cast<CXXMethodDecl>(MethodD);
19189-
if (!Method)
19186+
FunctionDecl *FD = dyn_cast<FunctionDecl>(D);
19187+
if (!FD)
1919019188
return;
1919119189

1919219190
// Check the exception specification.
1919319191
llvm::SmallVector<QualType, 4> Exceptions;
1919419192
FunctionProtoType::ExceptionSpecInfo ESI;
19195-
checkExceptionSpecification(/*IsTopLevel*/true, EST, DynamicExceptions,
19193+
checkExceptionSpecification(/*IsTopLevel=*/true, EST, DynamicExceptions,
1919619194
DynamicExceptionRanges, NoexceptExpr, Exceptions,
1919719195
ESI);
1919819196

1919919197
// Update the exception specification on the function type.
19200-
Context.adjustExceptionSpec(Method, ESI, /*AsWritten*/true);
19198+
Context.adjustExceptionSpec(FD, ESI, /*AsWritten=*/true);
1920119199

19202-
if (Method->isStatic())
19203-
checkThisInStaticMemberFunctionExceptionSpec(Method);
19200+
if (CXXMethodDecl *MD = dyn_cast<CXXMethodDecl>(D)) {
19201+
if (MD->isStatic())
19202+
checkThisInStaticMemberFunctionExceptionSpec(MD);
1920419203

19205-
if (Method->isVirtual()) {
19206-
// Check overrides, which we previously had to delay.
19207-
for (const CXXMethodDecl *O : Method->overridden_methods())
19208-
CheckOverridingFunctionExceptionSpec(Method, O);
19204+
if (MD->isVirtual()) {
19205+
// Check overrides, which we previously had to delay.
19206+
for (const CXXMethodDecl *O : MD->overridden_methods())
19207+
CheckOverridingFunctionExceptionSpec(MD, O);
19208+
}
1920919209
}
1921019210
}
1921119211

clang/lib/Sema/SemaExceptionSpec.cpp

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -258,13 +258,14 @@ Sema::UpdateExceptionSpec(FunctionDecl *FD,
258258
}
259259

260260
static bool exceptionSpecNotKnownYet(const FunctionDecl *FD) {
261-
auto *MD = dyn_cast<CXXMethodDecl>(FD);
262-
if (!MD)
261+
ExceptionSpecificationType EST =
262+
FD->getType()->castAs<FunctionProtoType>()->getExceptionSpecType();
263+
if (EST == EST_Unparsed)
264+
return true;
265+
else if (EST != EST_Unevaluated)
263266
return false;
264-
265-
auto EST = MD->getType()->castAs<FunctionProtoType>()->getExceptionSpecType();
266-
return EST == EST_Unparsed ||
267-
(EST == EST_Unevaluated && MD->getParent()->isBeingDefined());
267+
const DeclContext *DC = FD->getLexicalDeclContext();
268+
return DC->isRecord() && cast<RecordDecl>(DC)->isBeingDefined();
268269
}
269270

270271
static bool CheckEquivalentExceptionSpecImpl(
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// RUN: %clang_cc1 -fsyntax-only -verify %s
2+
3+
namespace N0 {
4+
struct A {
5+
void f0() noexcept(x);
6+
void g0() noexcept(y); // expected-error {{use of undeclared identifier 'y'}}
7+
8+
void f1() noexcept(A::x);
9+
void g1() noexcept(A::y); // expected-error {{no member named 'y' in 'N0::A'}}
10+
11+
template<typename T>
12+
void f2() noexcept(x);
13+
template<typename T>
14+
void g2() noexcept(y); // expected-error {{use of undeclared identifier 'y'}}
15+
16+
template<typename T>
17+
void f3() noexcept(A::x);
18+
template<typename T>
19+
void g3() noexcept(A::y); // expected-error {{no member named 'y' in 'N0::A'}}
20+
21+
friend void f4() noexcept(x);
22+
friend void g4() noexcept(y); // expected-error {{use of undeclared identifier 'y'}}
23+
24+
friend void f5() noexcept(A::x);
25+
friend void g5() noexcept(A::y); // expected-error {{no member named 'y' in 'N0::A'}}
26+
27+
template<typename T>
28+
friend void f6() noexcept(x);
29+
template<typename T>
30+
friend void g6() noexcept(y); // expected-error {{use of undeclared identifier 'y'}}
31+
32+
template<typename T>
33+
friend void f7() noexcept(A::x);
34+
template<typename T>
35+
friend void g7() noexcept(A::y); // expected-error {{no member named 'y' in 'N0::A'}}
36+
37+
static constexpr bool x = true;
38+
};
39+
} // namespace N0
40+
41+
namespace N1 {
42+
template<typename T>
43+
struct A {
44+
void f0() noexcept(x);
45+
void g0() noexcept(y); // expected-error {{use of undeclared identifier 'y'}}
46+
47+
void f1() noexcept(A::x);
48+
void g1() noexcept(A::y); // expected-error {{no member named 'y' in 'A<T>'}}
49+
50+
template<typename U>
51+
void f2() noexcept(x);
52+
template<typename U>
53+
void g2() noexcept(y); // expected-error {{use of undeclared identifier 'y'}}
54+
55+
template<typename U>
56+
void f3() noexcept(A::x);
57+
template<typename U>
58+
void g3() noexcept(A::y); // expected-error {{no member named 'y' in 'A<T>'}}
59+
60+
friend void f4() noexcept(x);
61+
friend void g4() noexcept(y); // expected-error {{use of undeclared identifier 'y'}}
62+
63+
friend void f5() noexcept(A::x);
64+
friend void g5() noexcept(A::y); // expected-error {{no member named 'y' in 'A<T>'}}
65+
66+
template<typename U>
67+
friend void f6() noexcept(x);
68+
template<typename U>
69+
friend void g6() noexcept(y); // expected-error {{use of undeclared identifier 'y'}}
70+
71+
template<typename U>
72+
friend void f7() noexcept(A::x);
73+
template<typename U>
74+
friend void g7() noexcept(A::y); // expected-error {{no member named 'y' in 'A<T>'}}
75+
76+
static constexpr bool x = true;
77+
};
78+
} // namespace N1
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// RUN: %clang_cc1 -fexceptions -fcxx-exceptions -fsyntax-only -verify %s
2+
3+
namespace N0 {
4+
void f() noexcept;
5+
void g() noexcept;
6+
7+
struct A {
8+
friend void f() noexcept;
9+
friend void g() noexcept(x);
10+
11+
static constexpr bool x = true;
12+
};
13+
} // namespace N0
14+
15+
namespace N1 {
16+
void f() noexcept;
17+
void g();
18+
19+
template<typename T>
20+
struct A {
21+
friend void f() noexcept;
22+
// FIXME: This error is emitted if no other errors occured (i.e. Sema::hasUncompilableErrorOccurred() is false).
23+
friend void g() noexcept(x); // expected-error {{no member 'x' in 'N1::A<int>'; it has not yet been instantiated}}
24+
// expected-note@-1 {{in instantiation of exception specification}}
25+
static constexpr bool x = false; // expected-note {{not-yet-instantiated member is declared here}}
26+
};
27+
28+
template struct A<int>; // expected-note {{in instantiation of template class}}
29+
} // namespace N1

0 commit comments

Comments
 (0)