Author: Nikolas Klauser Date: 2024-11-28T10:13:18+01:00 New Revision: 0604d13790b20f6b385507bb63c62aa87162da9b
URL: https://github.com/llvm/llvm-project/commit/0604d13790b20f6b385507bb63c62aa87162da9b DIFF: https://github.com/llvm/llvm-project/commit/0604d13790b20f6b385507bb63c62aa87162da9b.diff LOG: [Clang] Add [[clang::no_specializations]] (#101469) This can be used to inform users when a template should not be specialized. For example, this is the case for the standard type traits (except for `common_type` and `common_reference`, which have more complicated rules). Added: clang/test/SemaCXX/attr-no-specializations.cpp Modified: clang/docs/ReleaseNotes.rst clang/include/clang/Basic/Attr.td clang/include/clang/Basic/AttrDocs.td clang/include/clang/Basic/DiagnosticGroups.td clang/include/clang/Basic/DiagnosticSemaKinds.td clang/lib/Sema/SemaDeclAttr.cpp clang/lib/Sema/SemaTemplate.cpp Removed: ################################################################################ diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 70227a6248afad..2ecd19bdc39448 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -473,6 +473,11 @@ Attribute Changes in Clang - The ``hybrid_patchable`` attribute is now supported on ARM64EC targets. It can be used to specify that a function requires an additional x86-64 thunk, which may be patched at runtime. +- The attribute ``[[clang::no_specializations]]`` has been added to warn + users that a specific template shouldn't be specialized. This is useful for + e.g. standard library type traits, where adding a specialization results in + undefined behaviour. + - ``[[clang::lifetimebound]]`` is now explicitly disallowed on explicit object member functions where they were previously silently ignored. diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index b055cbd769bb50..425b72d4729f13 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -103,6 +103,9 @@ def NonParmVar : SubsetSubject<Var, def NonLocalVar : SubsetSubject<Var, [{!S->hasLocalStorage()}], "variables with non-local storage">; +def VarTmpl : SubsetSubject<Var, [{S->getDescribedVarTemplate()}], + "variable templates">; + def NonBitField : SubsetSubject<Field, [{!S->isBitField()}], "non-bit-field non-static data members">; @@ -3428,6 +3431,15 @@ def DiagnoseIf : InheritableAttr { let Documentation = [DiagnoseIfDocs]; } +def NoSpecializations : InheritableAttr { + let Spellings = [Clang<"no_specializations", /*AllowInC*/0>]; + let Args = [StringArgument<"Message", 1>]; + let Subjects = SubjectList<[ClassTmpl, FunctionTmpl, VarTmpl]>; + let Documentation = [NoSpecializationsDocs]; + let MeaningfulToClassTemplateDefinition = 1; + let TemplateDependent = 1; +} + def ArcWeakrefUnavailable : InheritableAttr { let Spellings = [Clang<"objc_arc_weak_reference_unavailable">]; let Subjects = SubjectList<[ObjCInterface], ErrorDiag>; diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td index aafd4449e47004..9617687ac69caf 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -1155,6 +1155,15 @@ Query for this feature with ``__has_attribute(diagnose_if)``. }]; } +def NoSpecializationsDocs : Documentation { + let Category = DocCatDecl; + let Content = [{ +``[[clang::no_specializations]]`` can be applied to function, class, or variable +templates which should not be explicitly specialized by users. This is primarily +used to diagnose user specializations of standard library type traits. + }]; +} + def PassObjectSizeDocs : Documentation { let Category = DocCatVariable; // Technically it's a parameter doc, but eh. let Heading = "pass_object_size, pass_dynamic_object_size"; diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td index df9bf94b5d0398..8f3c4872e67608 100644 --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -1589,4 +1589,3 @@ def ExplicitSpecializationStorageClass : DiagGroup<"explicit-specialization-stor // A warning for options that enable a feature that is not yet complete def ExperimentalOption : DiagGroup<"experimental-option">; - diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index a6c5c5806c33f2..316374f92b7daf 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -5445,6 +5445,10 @@ def note_dependent_function_template_spec_discard_reason : Note< "candidate ignored: %select{not a function template|" "not a member of the enclosing %select{class template|" "namespace; did you mean to explicitly qualify the specialization?}1}0">; +def warn_invalid_specialization : Warning< + "%0 cannot be specialized%select{|: %2}1">, + DefaultError, InGroup<DiagGroup<"invalid-specialization">>; +def note_marked_here : Note<"marked %0 here">; // C++ class template specializations and out-of-line definitions def err_template_spec_needs_header : Error< diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index 54712c43866fb6..4fd8ef6dbebf84 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -1212,6 +1212,14 @@ static void handlePreferredName(Sema &S, Decl *D, const ParsedAttr &AL) { << TT->getDecl(); } +static void handleNoSpecializations(Sema &S, Decl *D, const ParsedAttr &AL) { + StringRef Message; + if (AL.getNumArgs() != 0) + S.checkStringLiteralArgumentAttr(AL, 0, Message); + D->getDescribedTemplate()->addAttr( + NoSpecializationsAttr::Create(S.Context, Message, AL)); +} + bool Sema::isValidPointerAttrType(QualType T, bool RefOkay) { if (T->isDependentType()) return true; @@ -6913,6 +6921,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL, case ParsedAttr::AT_PreferredName: handlePreferredName(S, D, AL); break; + case ParsedAttr::AT_NoSpecializations: + handleNoSpecializations(S, D, AL); + break; case ParsedAttr::AT_Section: handleSectionAttr(S, D, AL); break; diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp index f6ca06fefb4912..5e7a3c8484c88f 100644 --- a/clang/lib/Sema/SemaTemplate.cpp +++ b/clang/lib/Sema/SemaTemplate.cpp @@ -4157,6 +4157,13 @@ DeclResult Sema::ActOnVarTemplateSpecialization( << IsPartialSpecialization; } + if (const auto *DSA = VarTemplate->getAttr<NoSpecializationsAttr>()) { + auto Message = DSA->getMessage(); + Diag(TemplateNameLoc, diag::warn_invalid_specialization) + << VarTemplate << !Message.empty() << Message; + Diag(DSA->getLoc(), diag::note_marked_here) << DSA; + } + // Check for unexpanded parameter packs in any of the template arguments. for (unsigned I = 0, N = TemplateArgs.size(); I != N; ++I) if (DiagnoseUnexpandedParameterPack(TemplateArgs[I], @@ -8291,6 +8298,13 @@ DeclResult Sema::ActOnClassTemplateSpecialization( return true; } + if (const auto *DSA = ClassTemplate->getAttr<NoSpecializationsAttr>()) { + auto Message = DSA->getMessage(); + Diag(TemplateNameLoc, diag::warn_invalid_specialization) + << ClassTemplate << !Message.empty() << Message; + Diag(DSA->getLoc(), diag::note_marked_here) << DSA; + } + if (S->isTemplateParamScope()) EnterTemplatedContext(S, ClassTemplate->getTemplatedDecl()); @@ -9175,6 +9189,14 @@ bool Sema::CheckFunctionTemplateSpecialization( // Ignore access information; it doesn't figure into redeclaration checking. FunctionDecl *Specialization = cast<FunctionDecl>(*Result); + if (const auto *PT = Specialization->getPrimaryTemplate(); + const auto *DSA = PT->getAttr<NoSpecializationsAttr>()) { + auto Message = DSA->getMessage(); + Diag(FD->getLocation(), diag::warn_invalid_specialization) + << PT << !Message.empty() << Message; + Diag(DSA->getLoc(), diag::note_marked_here) << DSA; + } + // C++23 [except.spec]p13: // An exception specification is considered to be needed when: // - [...] diff --git a/clang/test/SemaCXX/attr-no-specializations.cpp b/clang/test/SemaCXX/attr-no-specializations.cpp new file mode 100644 index 00000000000000..64f6a764166052 --- /dev/null +++ b/clang/test/SemaCXX/attr-no-specializations.cpp @@ -0,0 +1,62 @@ +// RUN: %clang_cc1 %s -verify + +#if !__has_cpp_attribute(clang::no_specializations) +# error +#endif + +struct [[clang::no_specializations]] S {}; // expected-warning {{'no_specializations' attribute only applies to class templates, function templates, and variable templates}} + +template <class T, class U> +struct [[clang::no_specializations]] is_same { // expected-note 2 {{marked 'no_specializations' here}} + static constexpr bool value = __is_same(T, U); +}; + +template <class T> +using alias [[clang::no_specializations]] = T; // expected-warning {{'no_specializations' attribute only applies to class templates, function templates, and variable templates}} + +template <> +struct is_same<int, char> {}; // expected-error {{'is_same' cannot be specialized}} + +template <class> +struct Template {}; + +template <class T> +struct is_same<Template<T>, Template <T>> {}; // expected-error {{'is_same' cannot be specialized}} + +bool test_instantiation1 = is_same<int, int>::value; + +template <class T, class U> +[[clang::no_specializations]] inline constexpr bool is_same_v = __is_same(T, U); // expected-note 2 {{marked 'no_specializations' here}} + +template <> +inline constexpr bool is_same_v<int, char> = false; // expected-error {{'is_same_v' cannot be specialized}} + +template <class T> +inline constexpr bool is_same_v<Template <T>, Template <T>> = true; // expected-error {{'is_same_v' cannot be specialized}} + +bool test_instantiation2 = is_same_v<int, int>; + +template <class T> +struct [[clang::no_specializations("specializing type traits results in undefined behaviour")]] is_trivial { // expected-note {{marked 'no_specializations' here}} + static constexpr bool value = __is_trivial(T); +}; + +template <> +struct is_trivial<int> {}; // expected-error {{'is_trivial' cannot be specialized: specializing type traits results in undefined behaviour}} + +template <class T> +[[clang::no_specializations("specializing type traits results in undefined behaviour")]] inline constexpr bool is_trivial_v = __is_trivial(T); // expected-note {{marked 'no_specializations' here}} + +template <> +inline constexpr bool is_trivial_v<int> = false; // expected-error {{'is_trivial_v' cannot be specialized: specializing type traits results in undefined behaviour}} + +template <class T> +struct Partial {}; + +template <class T> +struct [[clang::no_specializations]] Partial<Template <T>> {}; // expected-warning {{'no_specializations' attribute only applies to class templates, function templates, and variable templates}} + +template <class T> +[[clang::no_specializations]] void func(); // expected-note {{marked 'no_specializations' here}} + +template <> void func<int>(); // expected-error {{'func' cannot be specialized}} _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits