From: Alexander Shaposhnikov Date: Fri, 10 Mar 2023 09:10:37 +0000 (+0000) Subject: [Clang][Sema] Start fixing handling of out-of-line definitions of constrained templates X-Git-Tag: upstream/17.0.6~15275 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=421c098b32bd50122de8de03a71092c7f36994eb;p=platform%2Fupstream%2Fllvm.git [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 --- diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 332431e..8d880a4 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 96963fb..65111b1 100644 --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -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. diff --git a/clang/include/clang/Sema/DeclSpec.h b/clang/include/clang/Sema/DeclSpec.h index 69fe2c5..b0bf87d 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 void SomeType::some_method() {} +/// If CXXScopeSpec refers to SomeType then TemplateParamLists will contain +/// a single element referring to template . + class CXXScopeSpec { SourceRange Range; NestedNameSpecifierLocBuilder Builder; + ArrayRef 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 L) { + TemplateParamLists = L; + } + ArrayRef 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 3106571..da84da0 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 c2403f0..037bc86 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 1ef83ea..759ed6f 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( 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 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()) { // 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 59253db..49f289d 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 struct A; -template // expected-note{{previous template declaration}} +template struct A { void f0(); void f1(); @@ -15,11 +15,10 @@ struct A { 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 // expected-error{{too many template parameters}} -void A::f0() { } +// FIXME: We should produce diagnostics pointing out the +// non-matching candidates. +template +void A::f0() { } // expected-error{{does not refer into a class, class template or class template partial specialization}} template void A::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 0000000..222b78e --- /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 +concept Concept = (sizeof(T) >= 2 * sizeof(int)); + +struct XY { + int x; + int y; +}; + +namespace members { + +template struct S { + static constexpr int primary(); +}; + +template constexpr int S::primary() { + return PRIMARY; +}; + +template struct S { + static constexpr int specialization(); +}; + +template + requires(sizeof(T) == sizeof(int)) +struct S { + static constexpr int specialization(); +}; + +template constexpr int S::specialization() { + return SPECIALIZATION_CONCEPT; +} + +template + requires(sizeof(T) == sizeof(int)) +constexpr int S::specialization() { + return SPECIALIZATION_REQUIRES; +} + +static_assert(S::primary() == PRIMARY); +static_assert(S::specialization() == SPECIALIZATION_CONCEPT); +static_assert(S::specialization() == SPECIALIZATION_REQUIRES); + +} // namespace members + +namespace enumerations { + +template struct S { + enum class E : int; +}; + +template enum class S::E { Value = PRIMARY }; + +template struct S { + enum class E : int; +}; + +template +enum class S::E { + Value = SPECIALIZATION_CONCEPT +}; + +template + requires(sizeof(T) == sizeof(int)) +struct S { + enum class E : int; +}; + +template + requires(sizeof(T) == sizeof(int)) +enum class S::E { + Value = SPECIALIZATION_REQUIRES +}; + +static_assert(static_cast(S::E::Value) == PRIMARY); +static_assert(static_cast(S::E::Value) == + SPECIALIZATION_CONCEPT); +static_assert(static_cast(S::E::Value) == + SPECIALIZATION_REQUIRES); + +} // namespace enumerations + +namespace multiple_template_parameter_lists { + +template +struct S { + template + static constexpr int primary(Inner); +}; + +template +template +constexpr int S::primary(Inner) { + return PRIMARY; +}; + +template +struct S { + template + static constexpr int specialization(Inner); +}; + +template +template +constexpr int S::specialization(Inner) { return SPECIALIZATION_CONCEPT; } + +template + requires(sizeof(Outer) == sizeof(int)) +struct S { + template + static constexpr int specialization(Inner); +}; + +template + requires(sizeof(Outer) == sizeof(int)) +template +constexpr int S::specialization(Inner) { return SPECIALIZATION_REQUIRES; } + +static_assert(S::primary("str") == PRIMARY); +static_assert(S::specialization("str") == SPECIALIZATION_CONCEPT); +static_assert(S::specialization("str") == SPECIALIZATION_REQUIRES); + +} // namespace multiple_template_parameter_lists