alexander-shaposhnikov updated this revision to Diff 504025. alexander-shaposhnikov added a comment.
Release notes. P.S. To the best of my knowledge the current status is the following: with this patch the examples reported in the comments on https://github.com/llvm/llvm-project/issues/49620 start to compile with the exception of https://github.com/llvm/llvm-project/issues/49620#issuecomment-1387667143 . This also might be related to the issues reported on https://github.com/llvm/llvm-project/issues/60231 but I haven't investigated it yet. P.P.S. I'm planning to repeat all the testing and try to land this diff. Repository: rG LLVM Github Monorepo CHANGES SINCE LAST ACTION https://reviews.llvm.org/D145034/new/ https://reviews.llvm.org/D145034 Files: 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 clang/test/SemaTemplate/concepts-out-of-line-def.cpp
Index: clang/test/SemaTemplate/concepts-out-of-line-def.cpp =================================================================== --- /dev/null +++ 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 Index: clang/test/CXX/temp/temp.decls/temp.class.spec/temp.class.spec.mfunc/p1-neg.cpp =================================================================== --- clang/test/CXX/temp/temp.decls/temp.class.spec/temp.class.spec.mfunc/p1-neg.cpp +++ 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 @@ 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}} Index: clang/lib/Sema/SemaCXXScopeSpec.cpp =================================================================== --- clang/lib/Sema/SemaCXXScopeSpec.cpp +++ clang/lib/Sema/SemaCXXScopeSpec.cpp @@ -99,34 +99,52 @@ 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. Index: clang/lib/Parse/ParseDeclCXX.cpp =================================================================== --- clang/lib/Parse/ParseDeclCXX.cpp +++ clang/lib/Parse/ParseDeclCXX.cpp @@ -1674,6 +1674,9 @@ ColonProtectionRAIIObject X(*this); CXXScopeSpec Spec; + if (TemplateInfo.TemplateParams) + Spec.setTemplateParamLists(*TemplateInfo.TemplateParams); + bool HasValidSpec = true; if (ParseOptionalCXXScopeSpecifier(Spec, /*ObjectType=*/nullptr, /*ObjectHasErrors=*/false, Index: clang/lib/Parse/ParseDecl.cpp =================================================================== --- clang/lib/Parse/ParseDecl.cpp +++ clang/lib/Parse/ParseDecl.cpp @@ -3380,6 +3380,8 @@ goto DoneWithDeclSpec; CXXScopeSpec SS; + if (TemplateInfo.TemplateParams) + SS.setTemplateParamLists(*TemplateInfo.TemplateParams); Actions.RestoreNestedNameSpecifierAnnotation(Tok.getAnnotationValue(), Tok.getAnnotationRange(), SS); @@ -3475,7 +3477,8 @@ &SS) && isConstructorDeclarator(/*Unqualified=*/false, /*DeductionGuide=*/false, - DS.isFriendSpecified())) + DS.isFriendSpecified(), + &TemplateInfo)) goto DoneWithDeclSpec; // C++20 [temp.spec] 13.9/6. @@ -4952,6 +4955,7 @@ assert(TemplateInfo.TemplateParams && "no template parameters"); TParams = MultiTemplateParamsArg(TemplateInfo.TemplateParams->data(), TemplateInfo.TemplateParams->size()); + SS.setTemplateParamLists(TParams); } if (!Name && TUK != Sema::TUK_Definition) { @@ -5674,11 +5678,15 @@ } 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)) { @@ -6070,6 +6078,7 @@ bool EnteringContext = D.getContext() == DeclaratorContext::File || D.getContext() == DeclaratorContext::Member; CXXScopeSpec SS; + SS.setTemplateParamLists(D.getTemplateParameterLists()); ParseOptionalCXXScopeSpecifier(SS, /*ObjectType=*/nullptr, /*ObjectHasErrors=*/false, EnteringContext); Index: clang/include/clang/Sema/DeclSpec.h =================================================================== --- clang/include/clang/Sema/DeclSpec.h +++ clang/include/clang/Sema/DeclSpec.h @@ -62,9 +62,18 @@ /// 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 @@ 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(); Index: clang/include/clang/Parse/Parser.h =================================================================== --- clang/include/clang/Parse/Parser.h +++ clang/include/clang/Parse/Parser.h @@ -2513,7 +2513,8 @@ /// 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. Index: clang/docs/ReleaseNotes.rst =================================================================== --- clang/docs/ReleaseNotes.rst +++ clang/docs/ReleaseNotes.rst @@ -76,6 +76,8 @@ 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 ^^^^^^^^^^^^^^^^^^^^^
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits