Skip to content

Commit b9183d0

Browse files
authored
[Clang][Sema] Ensure that the selected candidate for a member function explicit specialization is more constrained than all others (#101721)
The selection of the most constrained candidate for member function explicit specializations introduced in #88963 does not check whether the selected candidate is more constrained than all other candidates, which can result in ambiguities being undiagnosed. This patch addresses the issue.
1 parent 6250313 commit b9183d0

File tree

4 files changed

+129
-68
lines changed

4 files changed

+129
-68
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ Bug Fixes to C++ Support
190190
- Fix init-capture packs having a size of one before being instantiated. (#GH63677)
191191
- Clang now preserves the unexpanded flag in a lambda transform used for pack expansion. (#GH56852), (#GH85667),
192192
(#GH99877).
193+
- Fixed a bug when diagnosing ambiguous explicit specializations of constrained member functions.
193194

194195
Bug Fixes to AST Handling
195196
^^^^^^^^^^^^^^^^^^^^^^^^^

clang/lib/Sema/SemaDecl.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7964,8 +7964,9 @@ NamedDecl *Sema::ActOnVariableDeclarator(
79647964
D.setRedeclaration(CheckVariableDeclaration(NewVD, Previous));
79657965
} else {
79667966
// If this is an explicit specialization of a static data member, check it.
7967-
if (IsMemberSpecialization && !IsVariableTemplateSpecialization &&
7968-
!NewVD->isInvalidDecl() && CheckMemberSpecialization(NewVD, Previous))
7967+
if (IsMemberSpecialization && !IsVariableTemplate &&
7968+
!IsVariableTemplateSpecialization && !NewVD->isInvalidDecl() &&
7969+
CheckMemberSpecialization(NewVD, Previous))
79697970
NewVD->setInvalidDecl();
79707971

79717972
// Merge the decl with the existing one if appropriate.
@@ -10466,7 +10467,7 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
1046610467
Previous))
1046710468
NewFD->setInvalidDecl();
1046810469
}
10469-
} else if (isMemberSpecialization && isa<CXXMethodDecl>(NewFD)) {
10470+
} else if (isMemberSpecialization && !FunctionTemplate) {
1047010471
if (CheckMemberSpecialization(NewFD, Previous))
1047110472
NewFD->setInvalidDecl();
1047210473
}

clang/lib/Sema/SemaTemplate.cpp

Lines changed: 51 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9051,7 +9051,8 @@ bool Sema::CheckFunctionTemplateSpecialization(
90519051

90529052
bool
90539053
Sema::CheckMemberSpecialization(NamedDecl *Member, LookupResult &Previous) {
9054-
assert(!isa<TemplateDecl>(Member) && "Only for non-template members");
9054+
assert(!Member->isTemplateDecl() && !Member->getDescribedTemplate() &&
9055+
"Only for non-template members");
90559056

90569057
// Try to find the member we are instantiating.
90579058
NamedDecl *FoundInstantiation = nullptr;
@@ -9062,51 +9063,79 @@ Sema::CheckMemberSpecialization(NamedDecl *Member, LookupResult &Previous) {
90629063
if (Previous.empty()) {
90639064
// Nowhere to look anyway.
90649065
} else if (FunctionDecl *Function = dyn_cast<FunctionDecl>(Member)) {
9065-
SmallVector<FunctionDecl *> Candidates;
9066-
bool Ambiguous = false;
9067-
for (LookupResult::iterator I = Previous.begin(), E = Previous.end();
9068-
I != E; ++I) {
9069-
CXXMethodDecl *Method =
9070-
dyn_cast<CXXMethodDecl>((*I)->getUnderlyingDecl());
9066+
UnresolvedSet<8> Candidates;
9067+
for (NamedDecl *Candidate : Previous) {
9068+
auto *Method = dyn_cast<CXXMethodDecl>(Candidate->getUnderlyingDecl());
9069+
// Ignore any candidates that aren't member functions.
90719070
if (!Method)
90729071
continue;
9072+
90739073
QualType Adjusted = Function->getType();
90749074
if (!hasExplicitCallingConv(Adjusted))
90759075
Adjusted = adjustCCAndNoReturn(Adjusted, Method->getType());
9076+
// Ignore any candidates with the wrong type.
90769077
// This doesn't handle deduced return types, but both function
90779078
// declarations should be undeduced at this point.
9079+
// FIXME: The exception specification should probably be ignored when
9080+
// comparing the types.
90789081
if (!Context.hasSameType(Adjusted, Method->getType()))
90799082
continue;
9083+
9084+
// Ignore any candidates with unsatisfied constraints.
90809085
if (ConstraintSatisfaction Satisfaction;
90819086
Method->getTrailingRequiresClause() &&
90829087
(CheckFunctionConstraints(Method, Satisfaction,
90839088
/*UsageLoc=*/Member->getLocation(),
90849089
/*ForOverloadResolution=*/true) ||
90859090
!Satisfaction.IsSatisfied))
90869091
continue;
9087-
Candidates.push_back(Method);
9088-
FunctionDecl *MoreConstrained =
9089-
Instantiation ? getMoreConstrainedFunction(
9090-
Method, cast<FunctionDecl>(Instantiation))
9091-
: Method;
9092-
if (!MoreConstrained) {
9093-
Ambiguous = true;
9094-
continue;
9092+
9093+
Candidates.addDecl(Candidate);
9094+
}
9095+
9096+
// If we have no viable candidates left after filtering, we are done.
9097+
if (Candidates.empty())
9098+
return false;
9099+
9100+
// Find the function that is more constrained than every other function it
9101+
// has been compared to.
9102+
UnresolvedSetIterator Best = Candidates.begin();
9103+
CXXMethodDecl *BestMethod = nullptr;
9104+
for (UnresolvedSetIterator I = Candidates.begin(), E = Candidates.end();
9105+
I != E; ++I) {
9106+
auto *Method = cast<CXXMethodDecl>(I->getUnderlyingDecl());
9107+
if (I == Best ||
9108+
getMoreConstrainedFunction(Method, BestMethod) == Method) {
9109+
Best = I;
9110+
BestMethod = Method;
90959111
}
9096-
if (MoreConstrained == Method) {
9097-
Ambiguous = false;
9098-
FoundInstantiation = *I;
9099-
Instantiation = Method;
9100-
InstantiatedFrom = Method->getInstantiatedFromMemberFunction();
9101-
MSInfo = Method->getMemberSpecializationInfo();
9112+
}
9113+
9114+
FoundInstantiation = *Best;
9115+
Instantiation = BestMethod;
9116+
InstantiatedFrom = BestMethod->getInstantiatedFromMemberFunction();
9117+
MSInfo = BestMethod->getMemberSpecializationInfo();
9118+
9119+
// Make sure the best candidate is more constrained than all of the others.
9120+
bool Ambiguous = false;
9121+
for (UnresolvedSetIterator I = Candidates.begin(), E = Candidates.end();
9122+
I != E; ++I) {
9123+
auto *Method = cast<CXXMethodDecl>(I->getUnderlyingDecl());
9124+
if (I != Best &&
9125+
getMoreConstrainedFunction(Method, BestMethod) != BestMethod) {
9126+
Ambiguous = true;
9127+
break;
91029128
}
91039129
}
9130+
91049131
if (Ambiguous) {
91059132
Diag(Member->getLocation(), diag::err_function_member_spec_ambiguous)
91069133
<< Member << (InstantiatedFrom ? InstantiatedFrom : Instantiation);
9107-
for (FunctionDecl *Candidate : Candidates)
9134+
for (NamedDecl *Candidate : Candidates) {
9135+
Candidate = Candidate->getUnderlyingDecl();
91089136
Diag(Candidate->getLocation(), diag::note_function_member_spec_matched)
91099137
<< Candidate;
9138+
}
91109139
return true;
91119140
}
91129141
} else if (isa<VarDecl>(Member)) {
Lines changed: 73 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,90 @@
11
// RUN: %clang_cc1 -std=c++20 -verify %s
22

3-
template<int I>
4-
concept C = I >= 4;
3+
namespace N0 {
4+
template<int I>
5+
concept C = I >= 4;
56

6-
template<int I>
7-
concept D = I < 8;
7+
template<int I>
8+
concept D = I < 8;
89

9-
template<int I>
10-
struct A {
11-
constexpr static int f() { return 0; }
12-
constexpr static int f() requires C<I> && D<I> { return 1; }
13-
constexpr static int f() requires C<I> { return 2; }
10+
template<int I>
11+
struct A {
12+
constexpr static int f() { return 0; }
13+
constexpr static int f() requires C<I> && D<I> { return 1; }
14+
constexpr static int f() requires C<I> { return 2; }
1415

15-
constexpr static int g() requires C<I> { return 0; } // #candidate-0
16-
constexpr static int g() requires D<I> { return 1; } // #candidate-1
16+
constexpr static int g() requires C<I> { return 0; } // #candidate-0
17+
constexpr static int g() requires D<I> { return 1; } // #candidate-1
1718

18-
constexpr static int h() requires C<I> { return 0; } // expected-note {{member declaration nearly matches}}
19-
};
19+
constexpr static int h() requires C<I> { return 0; } // expected-note {{member declaration nearly matches}}
20+
};
2021

21-
template<>
22-
constexpr int A<2>::f() { return 3; }
22+
template<>
23+
constexpr int A<2>::f() { return 3; }
2324

24-
template<>
25-
constexpr int A<4>::f() { return 4; }
25+
template<>
26+
constexpr int A<4>::f() { return 4; }
2627

27-
template<>
28-
constexpr int A<8>::f() { return 5; }
28+
template<>
29+
constexpr int A<8>::f() { return 5; }
2930

30-
static_assert(A<3>::f() == 0);
31-
static_assert(A<5>::f() == 1);
32-
static_assert(A<9>::f() == 2);
33-
static_assert(A<2>::f() == 3);
34-
static_assert(A<4>::f() == 4);
35-
static_assert(A<8>::f() == 5);
31+
static_assert(A<3>::f() == 0);
32+
static_assert(A<5>::f() == 1);
33+
static_assert(A<9>::f() == 2);
34+
static_assert(A<2>::f() == 3);
35+
static_assert(A<4>::f() == 4);
36+
static_assert(A<8>::f() == 5);
3637

37-
template<>
38-
constexpr int A<0>::g() { return 2; }
38+
template<>
39+
constexpr int A<0>::g() { return 2; }
3940

40-
template<>
41-
constexpr int A<8>::g() { return 3; }
41+
template<>
42+
constexpr int A<8>::g() { return 3; }
4243

43-
template<>
44-
constexpr int A<6>::g() { return 4; } // expected-error {{ambiguous member function specialization 'A<6>::g' of 'A::g'}}
45-
// expected-note@#candidate-0 {{member function specialization matches 'g'}}
46-
// expected-note@#candidate-1 {{member function specialization matches 'g'}}
44+
template<>
45+
constexpr int A<6>::g() { return 4; } // expected-error {{ambiguous member function specialization 'N0::A<6>::g' of 'N0::A::g'}}
46+
// expected-note@#candidate-0 {{member function specialization matches 'g'}}
47+
// expected-note@#candidate-1 {{member function specialization matches 'g'}}
4748

48-
static_assert(A<9>::g() == 0);
49-
static_assert(A<1>::g() == 1);
50-
static_assert(A<0>::g() == 2);
51-
static_assert(A<8>::g() == 3);
49+
static_assert(A<9>::g() == 0);
50+
static_assert(A<1>::g() == 1);
51+
static_assert(A<0>::g() == 2);
52+
static_assert(A<8>::g() == 3);
5253

53-
template<>
54-
constexpr int A<4>::h() { return 1; }
54+
template<>
55+
constexpr int A<4>::h() { return 1; }
5556

56-
template<>
57-
constexpr int A<0>::h() { return 2; } // expected-error {{out-of-line definition of 'h' does not match any declaration in 'A<0>'}}
57+
template<>
58+
constexpr int A<0>::h() { return 2; } // expected-error {{out-of-line definition of 'h' does not match any declaration in 'N0::A<0>'}}
5859

59-
static_assert(A<5>::h() == 0);
60-
static_assert(A<4>::h() == 1);
60+
static_assert(A<5>::h() == 0);
61+
static_assert(A<4>::h() == 1);
62+
} // namespace N0
63+
64+
namespace N1 {
65+
template<int I>
66+
concept C = I > 0;
67+
68+
template<int I>
69+
concept D = I > 1;
70+
71+
template<int I>
72+
concept E = I > 2;
73+
74+
template<int I>
75+
struct A {
76+
void f() requires C<I> && D<I>; // expected-note {{member function specialization matches 'f'}}
77+
void f() requires C<I> && E<I>; // expected-note {{member function specialization matches 'f'}}
78+
void f() requires C<I> && D<I> && true; // expected-note {{member function specialization matches 'f'}}
79+
80+
void g() requires C<I> && E<I>; // expected-note {{member function specialization matches 'g'}}
81+
void g() requires C<I> && D<I>; // expected-note {{member function specialization matches 'g'}}
82+
void g() requires C<I> && D<I> && true; // expected-note {{member function specialization matches 'g'}}
83+
};
84+
85+
template<>
86+
void A<3>::f(); // expected-error {{ambiguous member function specialization 'N1::A<3>::f' of 'N1::A::f'}}
87+
88+
template<>
89+
void A<3>::g(); // expected-error {{ambiguous member function specialization 'N1::A<3>::g' of 'N1::A::g'}}
90+
} // namespace N1

0 commit comments

Comments
 (0)