[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
This commit is contained in:
Alexander Shaposhnikov 2023-03-10 09:10:37 +00:00
parent bf00eda69f
commit 421c098b32
8 changed files with 208 additions and 31 deletions

View File

@ -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
^^^^^^^^^^^^^^^^^^^^^

View File

@ -2513,7 +2513,8 @@ private:
/// 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.

View File

@ -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 @@ public:
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();

View File

@ -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);

View File

@ -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,

View File

@ -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));
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 (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?");
if (!hasReachableDefinition(PartialSpec))
diagnoseMissingImport(SS.getLastQualifierNameLoc(), PartialSpec,
MissingImportKind::PartialSpecialization,
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();
QualType Injected =
ClassTemplate->getInjectedClassNameSpecialization();
if (Context.hasSameType(Injected, ContextType))
return ClassTemplate->getTemplatedDecl();
// 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)) {
// 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?");
if (!hasReachableDefinition(PartialSpec))
diagnoseMissingImport(SS.getLastQualifierNameLoc(), PartialSpec,
MissingImportKind::PartialSpecialization,
/*Recover*/true);
return PartialSpec;
}
}
} else if (const RecordType *RecordT = NNSType->getAs<RecordType>()) {
// The nested name specifier refers to a member of a class template.

View File

@ -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}}

View File

@ -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