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
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
^^^^^^^^^^^^^^^^^^^^^
/// 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.
/// 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; }
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();
goto DoneWithDeclSpec;
CXXScopeSpec SS;
+ if (TemplateInfo.TemplateParams)
+ SS.setTemplateParamLists(*TemplateInfo.TemplateParams);
Actions.RestoreNestedNameSpecifierAnnotation(Tok.getAnnotationValue(),
Tok.getAnnotationRange(),
SS);
&SS) &&
isConstructorDeclarator(/*Unqualified=*/false,
/*DeductionGuide=*/false,
- DS.isFriendSpecified()))
+ DS.isFriendSpecified(),
+ &TemplateInfo))
goto DoneWithDeclSpec;
// C++20 [temp.spec] 13.9/6.
assert(TemplateInfo.TemplateParams && "no template parameters");
TParams = MultiTemplateParamsArg(TemplateInfo.TemplateParams->data(),
TemplateInfo.TemplateParams->size());
+ SS.setTemplateParamLists(TParams);
}
if (!Name && TUK != Sema::TUK_Definition) {
}
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)) {
bool EnteringContext = D.getContext() == DeclaratorContext::File ||
D.getContext() == DeclaratorContext::Member;
CXXScopeSpec SS;
+ SS.setTemplateParamLists(D.getTemplateParameterLists());
ParseOptionalCXXScopeSpecifier(SS, /*ObjectType=*/nullptr,
/*ObjectHasErrors=*/false, EnteringContext);
ColonProtectionRAIIObject X(*this);
CXXScopeSpec Spec;
+ if (TemplateInfo.TemplateParams)
+ Spec.setTemplateParamLists(*TemplateInfo.TemplateParams);
+
bool HasValidSpec = true;
if (ParseOptionalCXXScopeSpecifier(Spec, /*ObjectType=*/nullptr,
/*ObjectHasErrors=*/false,
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.
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();
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}}
--- /dev/null
+// 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