-
Notifications
You must be signed in to change notification settings - Fork 12.2k
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] Improve support for explicit specializations of constrained member functions & member function templates #88963
Conversation
@llvm/pr-subscribers-clang Author: Krystian Stasiowski (sdkrystian) ChangesConsider the following snippet from the discussion of CWG2847 on the core reflector (godbolt link): template<typename T>
concept C = sizeof(T) <= sizeof(long);
template<typename T>
struct A
{
template<typename U>
void f(U) requires C<U>; // #<!-- -->1, declares a function template
void g() requires C<T>; // #<!-- -->2, declares a function
template<>
void f(char); // #<!-- -->3, an explicit specialization of a function template that declares a function
};
template<>
template<typename U>
void A<short>::f(U) requires C<U>; // #<!-- -->4, an explicit specialization of a function template that declares a function template
template<>
template<>
void A<int>::f(int); // #<!-- -->5, an explicit specialization of a function template that declares a function
template<>
void A<long>::g(); // #<!-- -->6, an explicit specialization of a function that declares a function A number of problems exist:
This patch resolves these issues by making the following changes:
In addition to these changes, Full diff: https://github.com/llvm/llvm-project/pull/88963.diff 5 Files Affected:
diff --git a/clang/lib/Sema/SemaConcept.cpp b/clang/lib/Sema/SemaConcept.cpp
index e00c972602829e..7bfec4e11f7aab 100644
--- a/clang/lib/Sema/SemaConcept.cpp
+++ b/clang/lib/Sema/SemaConcept.cpp
@@ -811,7 +811,7 @@ static const Expr *SubstituteConstraintExpressionWithoutSatisfaction(
// this may happen while we're comparing two templates' constraint
// equivalence.
LocalInstantiationScope ScopeForParameters(S);
- if (auto *FD = llvm::dyn_cast<FunctionDecl>(DeclInfo.getDecl()))
+ if (auto *FD = DeclInfo.getDecl()->getAsFunction())
for (auto *PVD : FD->parameters())
ScopeForParameters.InstantiatedLocal(PVD, PVD);
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 227ef564ba3e08..594cfc58d2226a 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -1303,6 +1303,8 @@ static bool IsOverloadOrOverrideImpl(Sema &SemaRef, FunctionDecl *New,
if (New->isMSVCRTEntryPoint())
return false;
+ NamedDecl *OldDecl = Old;
+ NamedDecl *NewDecl = New;
FunctionTemplateDecl *OldTemplate = Old->getDescribedFunctionTemplate();
FunctionTemplateDecl *NewTemplate = New->getDescribedFunctionTemplate();
@@ -1347,6 +1349,8 @@ static bool IsOverloadOrOverrideImpl(Sema &SemaRef, FunctionDecl *New,
// references to non-instantiated entities during constraint substitution.
// GH78101.
if (NewTemplate) {
+ OldDecl = OldTemplate;
+ NewDecl = NewTemplate;
// C++ [temp.over.link]p4:
// The signature of a function template consists of its function
// signature, its return type and its template parameter list. The names
@@ -1506,13 +1510,14 @@ static bool IsOverloadOrOverrideImpl(Sema &SemaRef, FunctionDecl *New,
}
}
- if (!UseOverrideRules) {
+ if (!UseOverrideRules &&
+ New->getTemplateSpecializationKind() != TSK_ExplicitSpecialization) {
Expr *NewRC = New->getTrailingRequiresClause(),
*OldRC = Old->getTrailingRequiresClause();
if ((NewRC != nullptr) != (OldRC != nullptr))
return true;
-
- if (NewRC && !SemaRef.AreConstraintExpressionsEqual(Old, OldRC, New, NewRC))
+ if (NewRC &&
+ !SemaRef.AreConstraintExpressionsEqual(OldDecl, OldRC, NewDecl, NewRC))
return true;
}
diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index 95171359f0ab17..3cb1ee1d5b795d 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -10338,6 +10338,25 @@ bool Sema::CheckFunctionTemplateSpecialization(
return false;
}
+static bool IsMoreConstrainedFunction(Sema &S, FunctionDecl *FD1,
+ FunctionDecl *FD2) {
+ if (FunctionDecl *MF = FD1->getInstantiatedFromMemberFunction())
+ FD1 = MF;
+ if (FunctionDecl *MF = FD2->getInstantiatedFromMemberFunction())
+ FD2 = MF;
+ llvm::SmallVector<const Expr *, 3> AC1, AC2;
+ FD1->getAssociatedConstraints(AC1);
+ FD2->getAssociatedConstraints(AC2);
+ bool AtLeastAsConstrained1, AtLeastAsConstrained2;
+ if (S.IsAtLeastAsConstrained(FD1, AC1, FD2, AC2, AtLeastAsConstrained1))
+ return false;
+ if (S.IsAtLeastAsConstrained(FD2, AC2, FD1, AC1, AtLeastAsConstrained2))
+ return false;
+ if (AtLeastAsConstrained1 == AtLeastAsConstrained2)
+ return false;
+ return AtLeastAsConstrained1;
+}
+
/// Perform semantic analysis for the given non-template member
/// specialization.
///
@@ -10372,15 +10391,26 @@ Sema::CheckMemberSpecialization(NamedDecl *Member, LookupResult &Previous) {
QualType Adjusted = Function->getType();
if (!hasExplicitCallingConv(Adjusted))
Adjusted = adjustCCAndNoReturn(Adjusted, Method->getType());
+ if (!Context.hasSameType(Adjusted, Method->getType()))
+ continue;
+ if (Method->getTrailingRequiresClause()) {
+ ConstraintSatisfaction Satisfaction;
+ if (CheckFunctionConstraints(Method, Satisfaction,
+ /*UsageLoc=*/Member->getLocation(),
+ /*ForOverloadResolution=*/true) ||
+ !Satisfaction.IsSatisfied)
+ continue;
+ if (Instantiation &&
+ !IsMoreConstrainedFunction(*this, Method,
+ cast<CXXMethodDecl>(Instantiation)))
+ continue;
+ }
// This doesn't handle deduced return types, but both function
// declarations should be undeduced at this point.
- if (Context.hasSameType(Adjusted, Method->getType())) {
- FoundInstantiation = *I;
- Instantiation = Method;
- InstantiatedFrom = Method->getInstantiatedFromMemberFunction();
- MSInfo = Method->getMemberSpecializationInfo();
- break;
- }
+ FoundInstantiation = *I;
+ Instantiation = Method;
+ InstantiatedFrom = Method->getInstantiatedFromMemberFunction();
+ MSInfo = Method->getMemberSpecializationInfo();
}
}
} else if (isa<VarDecl>(Member)) {
diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp
index 7cd428de0bb32d..09a7f34d651db2 100644
--- a/clang/lib/Sema/SemaTemplateInstantiate.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp
@@ -275,6 +275,10 @@ Response HandleFunction(Sema &SemaRef, const FunctionDecl *Function,
TemplateArgs->asArray(),
/*Final=*/false);
+ if (RelativeToPrimary &&
+ Function->getTemplateSpecializationKind() == TSK_ExplicitSpecialization)
+ return Response::UseNextDecl(Function);
+
// If this function was instantiated from a specialized member that is
// a function template, we're done.
assert(Function->getPrimaryTemplate() && "No function template?");
diff --git a/clang/test/CXX/temp/temp.spec/temp.expl.spec/p8.cpp b/clang/test/CXX/temp/temp.spec/temp.expl.spec/p8.cpp
new file mode 100644
index 00000000000000..87e10d10e4b453
--- /dev/null
+++ b/clang/test/CXX/temp/temp.spec/temp.expl.spec/p8.cpp
@@ -0,0 +1,74 @@
+// RUN: %clang_cc1 -std=c++20 -fsyntax-only -verify %s
+// expected-no-diagnostics
+
+template<typename T>
+concept C = sizeof(T) <= sizeof(long);
+
+template<typename T>
+struct A {
+ template<typename U>
+ void f(U) requires C<U>;
+
+ void g() requires C<T>;
+
+ template<typename U>
+ void h(U) requires C<T>;
+
+ constexpr int i() requires C<T> {
+ return 0;
+ }
+
+ constexpr int i() requires C<T> && true {
+ return 1;
+ }
+
+ template<>
+ void f(char);
+};
+
+template<>
+template<typename U>
+void A<short>::f(U) requires C<U>;
+
+template<>
+template<typename U>
+void A<short>::h(U) requires C<short>;
+
+template<>
+template<>
+void A<int>::f(int);
+
+template<>
+void A<long>::g();
+
+template<>
+constexpr int A<long>::i() {
+ return 2;
+}
+
+static_assert(A<long>().i() == 2);
+
+template<typename T>
+struct D {
+ template<typename U>
+ static constexpr int f(U);
+
+ template<typename U>
+ static constexpr int f(U) requires (sizeof(T) == 1);
+
+ template<>
+ constexpr int f(int) {
+ return 1;
+ }
+};
+
+template<>
+template<typename U>
+constexpr int D<signed char>::f(U) requires (sizeof(signed char) == 1) {
+ return 0;
+}
+
+static_assert(D<char>::f(0) == 1);
+static_assert(D<char[2]>::f(0) == 1);
+static_assert(D<signed char>::f(0) == 1);
+static_assert(D<signed char>::f(0.0) == 0);
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Approach/fix looks fine, other than release notes + tests, LGTM.
ede52d5
to
be79079
Compare
@@ -275,6 +275,13 @@ Response HandleFunction(Sema &SemaRef, const FunctionDecl *Function, | |||
TemplateArgs->asArray(), | |||
/*Final=*/false); | |||
|
|||
if (RelativeToPrimary && | |||
(Function->getTemplateSpecializationKind() == | |||
TSK_ExplicitSpecialization || |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this condition Function->getTemplateSpecializationKind() == TSK_ExplicitSpecialization
necessary? I removed it and all testcases passed. Could you please add a testcase to cover it which would fail without this condition?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jcsxky There already is a test case which will fail without the condition in this PR:
template<typename T>
struct D {
template<typename U>
static constexpr int f(U);
template<typename U>
static constexpr int f(U) requires (sizeof(T) == 1);
template<>
constexpr int f(int) {
return 1;
}
};
template<>
template<typename U>
constexpr int D<signed char>::f(U) requires (sizeof(signed char) == 1) {
return 0;
}
static_assert(D<signed char>::f(0) == 1); // fails
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This testcase runs OK before be79079 applied and it may not be related to the condition Function->getTemplateSpecializationKind() == TSK_ExplicitSpecialization
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sdkrystian but gcc fails on the testcase you provided.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
gcc does not support class scope explicit specializations.
be79079
to
1543f98
Compare
Added tests + diagnostics for ambiguous member specializations |
8667117
to
4e49bae
Compare
✅ With the latest revision this PR passed the C/C++ code formatter. |
2a471bb
to
d31a578
Compare
d31a578
to
4e49bae
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need a release note? Else LGTM.
@@ -5437,6 +5437,11 @@ def note_function_template_spec_matched : Note< | |||
def err_function_template_partial_spec : Error< | |||
"function template partial specialization is not allowed">; | |||
|
|||
def err_function_member_spec_ambiguous : Error< | |||
"ambiguous member function specialization of %q0">; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"ambiguous member function specialization of %q0">; | |
"ambiguous member function specialization of %0">; |
Oh, forgot about the release note :) I'll add one |
@erichkeane I added two release notes (one for the bug fix to constraint substitution, and one for allowing explicit specializations of constrained member functions). |
constexpr static int f() requires C<I> && D<I> { return 1; } | ||
constexpr static int f() requires C<I> { return 2; } | ||
|
||
constexpr static int g() requires C<I> { return 0; } // expected-note {{member function specialization matches 'g'}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, please use the bookmark tags for these notes, I already got confused here: )
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
happy now :)
…ined member functions & member function templates
e6b34fb
to
20c8a68
Compare
continue; | ||
} | ||
if (MoreConstrained == Method) { | ||
Ambiguous = false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't look right to me. Suppose we see declaration A
then B
then C
, and A
and B
are ambiguous and C
is more constrained than A
. This code will pick C
as the found instantiation, even though C
might not be more constrained than B
.
I think we need to do the same two-pass dance we do in overload resolution: first do a walk through the declarations tracking the best so far, then go through them again and make sure the best so far is actually better than everything else.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whoops, good catch! I'll open a patch to fix this :)
…n 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.
…n explicit specialization is more constrained than all others (llvm#101721) The selection of the most constrained candidate for member function explicit specializations introduced in llvm#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.
…n explicit specialization is more constrained than all others (llvm#101721) The selection of the most constrained candidate for member function explicit specializations introduced in llvm#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.
Consider the following snippet from the discussion of CWG2847 on the core reflector (godbolt link):
A number of problems exist:
#4
because the trailing requires-clause hasU
substituted with the wrong template parameter depth whenSema::AreConstraintExpressionsEqual
is called to determine whether it matches the trailing requires-clause of the implicitly instantiated function template.#5
because the function template specialization instantiated fromA<int>::f
has a trailing requires-clause, but#5
does not (nor can it have one as it isn't a templated function).#6
for the same reasons it rejects#5
.This patch resolves these issues by making the following changes:
#4
,Sema::AreConstraintExpressionsEqual
is passedFunctionTemplateDecl
s when comparing the trailing requires-clauses of#4
and the function template instantiated from#1
.#5
and#6
, the trailing requires-clauses are not compared for explicit specializations that declare functions.In addition to these changes,
CheckMemberSpecialization
now considers constraint satisfaction/constraint partial ordering when determining which member function is specialized by an explicit specialization of a member function for an implicit instantiation of a class template (we previously would select the first function that has the same type as the explicit specialization). With constraints taken under consideration, we match EDG's behavior for these declarations.Note: This still needs a release note + more tests... I'll work on that tomorrow.