[clang] 421c098 - [Clang][Sema] Start fixing handling of out-of-line definitions of constrained templates
Alexander Shaposhnikov via cfe-commits
cfe-commits at lists.llvm.org
Fri Mar 10 01:21:51 PST 2023
Author: Alexander Shaposhnikov
Date: 2023-03-10T09:21:09Z
New Revision: 421c098b32bd50122de8de03a71092c7f36994eb
URL: https://github.com/llvm/llvm-project/commit/421c098b32bd50122de8de03a71092c7f36994eb
DIFF: https://github.com/llvm/llvm-project/commit/421c098b32bd50122de8de03a71092c7f36994eb.diff
LOG: [Clang][Sema] Start fixing handling of out-of-line definitions of constrained templates
This diff starts fixing our handling of out-of-line definitions of constrained templates.
Initially it was motivated by https://github.com/llvm/llvm-project/issues/49620 and
https://github.com/llvm/llvm-project/issues/60231.
In particular, this diff adjusts Sema::computeDeclContext to work properly in the case of
constrained template parameters.
Test plan:
1/ ninja check-all
2/ Bootstrapped Clang passes all the tests
3/ Internal testing
Differential revision: https://reviews.llvm.org/D145034
Added:
clang/test/SemaTemplate/concepts-out-of-line-def.cpp
Modified:
clang/docs/ReleaseNotes.rst
clang/include/clang/Parse/Parser.h
clang/include/clang/Sema/DeclSpec.h
clang/lib/Parse/ParseDecl.cpp
clang/lib/Parse/ParseDeclCXX.cpp
clang/lib/Sema/SemaCXXScopeSpec.cpp
clang/test/CXX/temp/temp.decls/temp.class.spec/temp.class.spec.mfunc/p1-neg.cpp
Removed:
################################################################################
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 332431e08ce07..8d880a4fba266 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -78,6 +78,8 @@ C++ Language Changes
C++20 Feature Support
^^^^^^^^^^^^^^^^^^^^^
+- Support for out-of-line definitions of constrained templates has been improved.
+ This partially fixes `https://github.com/llvm/llvm-project/issues/49620`.
C++2b Feature Support
^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index 96963fb6aa807..65111b1ac6b36 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -2513,7 +2513,8 @@ class Parser : public CodeCompletionHandler {
/// this is a constructor declarator.
bool isConstructorDeclarator(
bool Unqualified, bool DeductionGuide = false,
- DeclSpec::FriendSpecified IsFriend = DeclSpec::FriendSpecified::No);
+ DeclSpec::FriendSpecified IsFriend = DeclSpec::FriendSpecified::No,
+ const ParsedTemplateInfo *TemplateInfo = nullptr);
/// Specifies the context in which type-id/expression
/// disambiguation will occur.
diff --git a/clang/include/clang/Sema/DeclSpec.h b/clang/include/clang/Sema/DeclSpec.h
index 69fe2c541607b..b0bf87dc18d79 100644
--- a/clang/include/clang/Sema/DeclSpec.h
+++ b/clang/include/clang/Sema/DeclSpec.h
@@ -62,9 +62,18 @@ namespace clang {
/// often used as if it meant "present".
///
/// The actual scope is described by getScopeRep().
+///
+/// If the kind of getScopeRep() is TypeSpec then TemplateParamLists may be empty
+/// or contain the template parameter lists attached to the current declaration.
+/// Consider the following example:
+/// template <class T> void SomeType<T>::some_method() {}
+/// If CXXScopeSpec refers to SomeType<T> then TemplateParamLists will contain
+/// a single element referring to template <class T>.
+
class CXXScopeSpec {
SourceRange Range;
NestedNameSpecifierLocBuilder Builder;
+ ArrayRef<TemplateParameterList *> TemplateParamLists;
public:
SourceRange getRange() const { return Range; }
@@ -74,6 +83,13 @@ class CXXScopeSpec {
SourceLocation getBeginLoc() const { return Range.getBegin(); }
SourceLocation getEndLoc() const { return Range.getEnd(); }
+ void setTemplateParamLists(ArrayRef<TemplateParameterList *> L) {
+ TemplateParamLists = L;
+ }
+ ArrayRef<TemplateParameterList *> getTemplateParamLists() const {
+ return TemplateParamLists;
+ }
+
/// Retrieve the representation of the nested-name-specifier.
NestedNameSpecifier *getScopeRep() const {
return Builder.getRepresentation();
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index 3106571728692..da84da04e43d0 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -3380,6 +3380,8 @@ void Parser::ParseDeclarationSpecifiers(
goto DoneWithDeclSpec;
CXXScopeSpec SS;
+ if (TemplateInfo.TemplateParams)
+ SS.setTemplateParamLists(*TemplateInfo.TemplateParams);
Actions.RestoreNestedNameSpecifierAnnotation(Tok.getAnnotationValue(),
Tok.getAnnotationRange(),
SS);
@@ -3475,7 +3477,8 @@ void Parser::ParseDeclarationSpecifiers(
&SS) &&
isConstructorDeclarator(/*Unqualified=*/false,
/*DeductionGuide=*/false,
- DS.isFriendSpecified()))
+ DS.isFriendSpecified(),
+ &TemplateInfo))
goto DoneWithDeclSpec;
// C++20 [temp.spec] 13.9/6.
@@ -4957,6 +4960,7 @@ void Parser::ParseEnumSpecifier(SourceLocation StartLoc, DeclSpec &DS,
assert(TemplateInfo.TemplateParams && "no template parameters");
TParams = MultiTemplateParamsArg(TemplateInfo.TemplateParams->data(),
TemplateInfo.TemplateParams->size());
+ SS.setTemplateParamLists(TParams);
}
if (!Name && TUK != Sema::TUK_Definition) {
@@ -5679,11 +5683,15 @@ bool Parser::isDeclarationSpecifier(
}
bool Parser::isConstructorDeclarator(bool IsUnqualified, bool DeductionGuide,
- DeclSpec::FriendSpecified IsFriend) {
+ DeclSpec::FriendSpecified IsFriend,
+ const ParsedTemplateInfo *TemplateInfo) {
TentativeParsingAction TPA(*this);
// Parse the C++ scope specifier.
CXXScopeSpec SS;
+ if (TemplateInfo && TemplateInfo->TemplateParams)
+ SS.setTemplateParamLists(*TemplateInfo->TemplateParams);
+
if (ParseOptionalCXXScopeSpecifier(SS, /*ObjectType=*/nullptr,
/*ObjectHasErrors=*/false,
/*EnteringContext=*/true)) {
@@ -6075,6 +6083,7 @@ void Parser::ParseDeclaratorInternal(Declarator &D,
bool EnteringContext = D.getContext() == DeclaratorContext::File ||
D.getContext() == DeclaratorContext::Member;
CXXScopeSpec SS;
+ SS.setTemplateParamLists(D.getTemplateParameterLists());
ParseOptionalCXXScopeSpecifier(SS, /*ObjectType=*/nullptr,
/*ObjectHasErrors=*/false, EnteringContext);
diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp
index c2403f0d6f439..037bc869c5a19 100644
--- a/clang/lib/Parse/ParseDeclCXX.cpp
+++ b/clang/lib/Parse/ParseDeclCXX.cpp
@@ -1676,6 +1676,9 @@ void Parser::ParseClassSpecifier(tok::TokenKind TagTokKind,
ColonProtectionRAIIObject X(*this);
CXXScopeSpec Spec;
+ if (TemplateInfo.TemplateParams)
+ Spec.setTemplateParamLists(*TemplateInfo.TemplateParams);
+
bool HasValidSpec = true;
if (ParseOptionalCXXScopeSpecifier(Spec, /*ObjectType=*/nullptr,
/*ObjectHasErrors=*/false,
diff --git a/clang/lib/Sema/SemaCXXScopeSpec.cpp b/clang/lib/Sema/SemaCXXScopeSpec.cpp
index 1ef83ea52e730..759ed6f352c81 100644
--- a/clang/lib/Sema/SemaCXXScopeSpec.cpp
+++ b/clang/lib/Sema/SemaCXXScopeSpec.cpp
@@ -99,34 +99,52 @@ DeclContext *Sema::computeDeclContext(const CXXScopeSpec &SS,
if (ClassTemplateDecl *ClassTemplate
= dyn_cast_or_null<ClassTemplateDecl>(
SpecType->getTemplateName().getAsTemplateDecl())) {
- QualType ContextType
- = Context.getCanonicalType(QualType(SpecType, 0));
-
- // If the type of the nested name specifier is the same as the
- // injected class name of the named class template, we're entering
- // into that class template definition.
- QualType Injected
- = ClassTemplate->getInjectedClassNameSpecialization();
- if (Context.hasSameType(Injected, ContextType))
- return ClassTemplate->getTemplatedDecl();
+ QualType ContextType =
+ Context.getCanonicalType(QualType(SpecType, 0));
+
+ // FIXME: The fallback on the search of partial
+ // specialization using ContextType should be eventually removed since
+ // it doesn't handle the case of constrained template parameters
+ // correctly. Currently removing this fallback would change the
+ // diagnostic output for invalid code in a number of tests.
+ ClassTemplatePartialSpecializationDecl *PartialSpec = nullptr;
+ ArrayRef<TemplateParameterList *> TemplateParamLists =
+ SS.getTemplateParamLists();
+ if (!TemplateParamLists.empty()) {
+ unsigned Depth = ClassTemplate->getTemplateParameters()->getDepth();
+ auto L = find_if(TemplateParamLists,
+ [Depth](TemplateParameterList *TPL) {
+ return TPL->getDepth() == Depth;
+ });
+ if (L != TemplateParamLists.end()) {
+ void *Pos = nullptr;
+ PartialSpec = ClassTemplate->findPartialSpecialization(
+ SpecType->template_arguments(), *L, Pos);
+ }
+ } else {
+ PartialSpec = ClassTemplate->findPartialSpecialization(ContextType);
+ }
- // If the type of the nested name specifier is the same as the
- // type of one of the class template's class template partial
- // specializations, we're entering into the definition of that
- // class template partial specialization.
- if (ClassTemplatePartialSpecializationDecl *PartialSpec
- = ClassTemplate->findPartialSpecialization(ContextType)) {
+ if (PartialSpec) {
// A declaration of the partial specialization must be visible.
// We can always recover here, because this only happens when we're
// entering the context, and that can't happen in a SFINAE context.
- assert(!isSFINAEContext() &&
- "partial specialization scope specifier in SFINAE context?");
+ assert(!isSFINAEContext() && "partial specialization scope "
+ "specifier in SFINAE context?");
if (!hasReachableDefinition(PartialSpec))
diagnoseMissingImport(SS.getLastQualifierNameLoc(), PartialSpec,
MissingImportKind::PartialSpecialization,
- /*Recover*/true);
+ true);
return PartialSpec;
}
+
+ // If the type of the nested name specifier is the same as the
+ // injected class name of the named class template, we're entering
+ // into that class template definition.
+ QualType Injected =
+ ClassTemplate->getInjectedClassNameSpecialization();
+ if (Context.hasSameType(Injected, ContextType))
+ return ClassTemplate->getTemplatedDecl();
}
} else if (const RecordType *RecordT = NNSType->getAs<RecordType>()) {
// The nested name specifier refers to a member of a class template.
diff --git a/clang/test/CXX/temp/temp.decls/temp.class.spec/temp.class.spec.mfunc/p1-neg.cpp b/clang/test/CXX/temp/temp.decls/temp.class.spec/temp.class.spec.mfunc/p1-neg.cpp
index 59253db3c5297..49f289dbe2d6a 100644
--- a/clang/test/CXX/temp/temp.decls/temp.class.spec/temp.class.spec.mfunc/p1-neg.cpp
+++ b/clang/test/CXX/temp/temp.decls/temp.class.spec/temp.class.spec.mfunc/p1-neg.cpp
@@ -3,7 +3,7 @@
template<typename T, int N>
struct A;
-template<typename T> // expected-note{{previous template declaration}}
+template<typename T>
struct A<T*, 2> {
void f0();
void f1();
@@ -15,11 +15,10 @@ struct A<int, 1> {
void g0();
};
-// FIXME: We should probably give more precise diagnostics here, but the
-// diagnostics we give aren't terrible.
-// FIXME: why not point to the first parameter that's "too many"?
-template<typename T, int N> // expected-error{{too many template parameters}}
-void A<T*, 2>::f0() { }
+// FIXME: We should produce diagnostics pointing out the
+// non-matching candidates.
+template<typename T, int N>
+void A<T*, 2>::f0() { } // expected-error{{does not refer into a class, class template or class template partial specialization}}
template<typename T, int N>
void A<T, N>::f1() { } // expected-error{{out-of-line definition}}
diff --git a/clang/test/SemaTemplate/concepts-out-of-line-def.cpp b/clang/test/SemaTemplate/concepts-out-of-line-def.cpp
new file mode 100644
index 0000000000000..222b78e0d22f7
--- /dev/null
+++ b/clang/test/SemaTemplate/concepts-out-of-line-def.cpp
@@ -0,0 +1,129 @@
+// RUN: %clang_cc1 -std=c++20 -verify %s
+// expected-no-diagnostics
+
+static constexpr int PRIMARY = 0;
+static constexpr int SPECIALIZATION_CONCEPT = 1;
+static constexpr int SPECIALIZATION_REQUIRES = 2;
+
+template <class T>
+concept Concept = (sizeof(T) >= 2 * sizeof(int));
+
+struct XY {
+ int x;
+ int y;
+};
+
+namespace members {
+
+template <class T, class U> struct S {
+ static constexpr int primary();
+};
+
+template <class T, class U> constexpr int S<T, U>::primary() {
+ return PRIMARY;
+};
+
+template <Concept C, class U> struct S<C, U> {
+ static constexpr int specialization();
+};
+
+template <class T, class U>
+ requires(sizeof(T) == sizeof(int))
+struct S<T, U> {
+ static constexpr int specialization();
+};
+
+template <Concept C, class U> constexpr int S<C, U>::specialization() {
+ return SPECIALIZATION_CONCEPT;
+}
+
+template <class T, class U>
+ requires(sizeof(T) == sizeof(int))
+constexpr int S<T, U>::specialization() {
+ return SPECIALIZATION_REQUIRES;
+}
+
+static_assert(S<char, double>::primary() == PRIMARY);
+static_assert(S<XY, double>::specialization() == SPECIALIZATION_CONCEPT);
+static_assert(S<int, double>::specialization() == SPECIALIZATION_REQUIRES);
+
+} // namespace members
+
+namespace enumerations {
+
+template <class T, class U> struct S {
+ enum class E : int;
+};
+
+template <class T, class U> enum class S<T, U>::E { Value = PRIMARY };
+
+template <Concept C, class U> struct S<C, U> {
+ enum class E : int;
+};
+
+template <Concept C, class U>
+enum class S<C, U>::E {
+ Value = SPECIALIZATION_CONCEPT
+};
+
+template <class T, class U>
+ requires(sizeof(T) == sizeof(int))
+struct S<T, U> {
+ enum class E : int;
+};
+
+template <class T, class U>
+ requires(sizeof(T) == sizeof(int))
+enum class S<T, U>::E {
+ Value = SPECIALIZATION_REQUIRES
+};
+
+static_assert(static_cast<int>(S<char, double>::E::Value) == PRIMARY);
+static_assert(static_cast<int>(S<XY, double>::E::Value) ==
+ SPECIALIZATION_CONCEPT);
+static_assert(static_cast<int>(S<int, double>::E::Value) ==
+ SPECIALIZATION_REQUIRES);
+
+} // namespace enumerations
+
+namespace multiple_template_parameter_lists {
+
+template <class Outer>
+struct S {
+ template <class Inner>
+ static constexpr int primary(Inner);
+};
+
+template <class Outer>
+template <class Inner>
+constexpr int S<Outer>::primary(Inner) {
+ return PRIMARY;
+};
+
+template <Concept Outer>
+struct S<Outer> {
+ template <class Inner>
+ static constexpr int specialization(Inner);
+};
+
+template <Concept Outer>
+template <class Inner>
+constexpr int S<Outer>::specialization(Inner) { return SPECIALIZATION_CONCEPT; }
+
+template <class Outer>
+ requires(sizeof(Outer) == sizeof(int))
+struct S<Outer> {
+ template <class Inner>
+ static constexpr int specialization(Inner);
+};
+
+template <class Outer>
+ requires(sizeof(Outer) == sizeof(int))
+template <class Inner>
+constexpr int S<Outer>::specialization(Inner) { return SPECIALIZATION_REQUIRES; }
+
+static_assert(S<char>::primary("str") == PRIMARY);
+static_assert(S<XY>::specialization("str") == SPECIALIZATION_CONCEPT);
+static_assert(S<int>::specialization("str") == SPECIALIZATION_REQUIRES);
+
+} // namespace multiple_template_parameter_lists
More information about the cfe-commits
mailing list