Skip to content

Commit 0604d13

Browse files
authored
[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).
1 parent 71648a4 commit 0604d13

File tree

8 files changed

+125
-1
lines changed

8 files changed

+125
-1
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,11 @@ Attribute Changes in Clang
473473
- The ``hybrid_patchable`` attribute is now supported on ARM64EC targets. It can be used to specify
474474
that a function requires an additional x86-64 thunk, which may be patched at runtime.
475475

476+
- The attribute ``[[clang::no_specializations]]`` has been added to warn
477+
users that a specific template shouldn't be specialized. This is useful for
478+
e.g. standard library type traits, where adding a specialization results in
479+
undefined behaviour.
480+
476481
- ``[[clang::lifetimebound]]`` is now explicitly disallowed on explicit object member functions
477482
where they were previously silently ignored.
478483

clang/include/clang/Basic/Attr.td

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@ def NonParmVar : SubsetSubject<Var,
103103
def NonLocalVar : SubsetSubject<Var,
104104
[{!S->hasLocalStorage()}],
105105
"variables with non-local storage">;
106+
def VarTmpl : SubsetSubject<Var, [{S->getDescribedVarTemplate()}],
107+
"variable templates">;
108+
106109
def NonBitField : SubsetSubject<Field,
107110
[{!S->isBitField()}],
108111
"non-bit-field non-static data members">;
@@ -3428,6 +3431,15 @@ def DiagnoseIf : InheritableAttr {
34283431
let Documentation = [DiagnoseIfDocs];
34293432
}
34303433

3434+
def NoSpecializations : InheritableAttr {
3435+
let Spellings = [Clang<"no_specializations", /*AllowInC*/0>];
3436+
let Args = [StringArgument<"Message", 1>];
3437+
let Subjects = SubjectList<[ClassTmpl, FunctionTmpl, VarTmpl]>;
3438+
let Documentation = [NoSpecializationsDocs];
3439+
let MeaningfulToClassTemplateDefinition = 1;
3440+
let TemplateDependent = 1;
3441+
}
3442+
34313443
def ArcWeakrefUnavailable : InheritableAttr {
34323444
let Spellings = [Clang<"objc_arc_weak_reference_unavailable">];
34333445
let Subjects = SubjectList<[ObjCInterface], ErrorDiag>;

clang/include/clang/Basic/AttrDocs.td

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1155,6 +1155,15 @@ Query for this feature with ``__has_attribute(diagnose_if)``.
11551155
}];
11561156
}
11571157

1158+
def NoSpecializationsDocs : Documentation {
1159+
let Category = DocCatDecl;
1160+
let Content = [{
1161+
``[[clang::no_specializations]]`` can be applied to function, class, or variable
1162+
templates which should not be explicitly specialized by users. This is primarily
1163+
used to diagnose user specializations of standard library type traits.
1164+
}];
1165+
}
1166+
11581167
def PassObjectSizeDocs : Documentation {
11591168
let Category = DocCatVariable; // Technically it's a parameter doc, but eh.
11601169
let Heading = "pass_object_size, pass_dynamic_object_size";

clang/include/clang/Basic/DiagnosticGroups.td

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1589,4 +1589,3 @@ def ExplicitSpecializationStorageClass : DiagGroup<"explicit-specialization-stor
15891589

15901590
// A warning for options that enable a feature that is not yet complete
15911591
def ExperimentalOption : DiagGroup<"experimental-option">;
1592-

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5445,6 +5445,10 @@ def note_dependent_function_template_spec_discard_reason : Note<
54455445
"candidate ignored: %select{not a function template|"
54465446
"not a member of the enclosing %select{class template|"
54475447
"namespace; did you mean to explicitly qualify the specialization?}1}0">;
5448+
def warn_invalid_specialization : Warning<
5449+
"%0 cannot be specialized%select{|: %2}1">,
5450+
DefaultError, InGroup<DiagGroup<"invalid-specialization">>;
5451+
def note_marked_here : Note<"marked %0 here">;
54485452

54495453
// C++ class template specializations and out-of-line definitions
54505454
def err_template_spec_needs_header : Error<

clang/lib/Sema/SemaDeclAttr.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1212,6 +1212,14 @@ static void handlePreferredName(Sema &S, Decl *D, const ParsedAttr &AL) {
12121212
<< TT->getDecl();
12131213
}
12141214

1215+
static void handleNoSpecializations(Sema &S, Decl *D, const ParsedAttr &AL) {
1216+
StringRef Message;
1217+
if (AL.getNumArgs() != 0)
1218+
S.checkStringLiteralArgumentAttr(AL, 0, Message);
1219+
D->getDescribedTemplate()->addAttr(
1220+
NoSpecializationsAttr::Create(S.Context, Message, AL));
1221+
}
1222+
12151223
bool Sema::isValidPointerAttrType(QualType T, bool RefOkay) {
12161224
if (T->isDependentType())
12171225
return true;
@@ -6913,6 +6921,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
69136921
case ParsedAttr::AT_PreferredName:
69146922
handlePreferredName(S, D, AL);
69156923
break;
6924+
case ParsedAttr::AT_NoSpecializations:
6925+
handleNoSpecializations(S, D, AL);
6926+
break;
69166927
case ParsedAttr::AT_Section:
69176928
handleSectionAttr(S, D, AL);
69186929
break;

clang/lib/Sema/SemaTemplate.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4157,6 +4157,13 @@ DeclResult Sema::ActOnVarTemplateSpecialization(
41574157
<< IsPartialSpecialization;
41584158
}
41594159

4160+
if (const auto *DSA = VarTemplate->getAttr<NoSpecializationsAttr>()) {
4161+
auto Message = DSA->getMessage();
4162+
Diag(TemplateNameLoc, diag::warn_invalid_specialization)
4163+
<< VarTemplate << !Message.empty() << Message;
4164+
Diag(DSA->getLoc(), diag::note_marked_here) << DSA;
4165+
}
4166+
41604167
// Check for unexpanded parameter packs in any of the template arguments.
41614168
for (unsigned I = 0, N = TemplateArgs.size(); I != N; ++I)
41624169
if (DiagnoseUnexpandedParameterPack(TemplateArgs[I],
@@ -8291,6 +8298,13 @@ DeclResult Sema::ActOnClassTemplateSpecialization(
82918298
return true;
82928299
}
82938300

8301+
if (const auto *DSA = ClassTemplate->getAttr<NoSpecializationsAttr>()) {
8302+
auto Message = DSA->getMessage();
8303+
Diag(TemplateNameLoc, diag::warn_invalid_specialization)
8304+
<< ClassTemplate << !Message.empty() << Message;
8305+
Diag(DSA->getLoc(), diag::note_marked_here) << DSA;
8306+
}
8307+
82948308
if (S->isTemplateParamScope())
82958309
EnterTemplatedContext(S, ClassTemplate->getTemplatedDecl());
82968310

@@ -9175,6 +9189,14 @@ bool Sema::CheckFunctionTemplateSpecialization(
91759189
// Ignore access information; it doesn't figure into redeclaration checking.
91769190
FunctionDecl *Specialization = cast<FunctionDecl>(*Result);
91779191

9192+
if (const auto *PT = Specialization->getPrimaryTemplate();
9193+
const auto *DSA = PT->getAttr<NoSpecializationsAttr>()) {
9194+
auto Message = DSA->getMessage();
9195+
Diag(FD->getLocation(), diag::warn_invalid_specialization)
9196+
<< PT << !Message.empty() << Message;
9197+
Diag(DSA->getLoc(), diag::note_marked_here) << DSA;
9198+
}
9199+
91789200
// C++23 [except.spec]p13:
91799201
// An exception specification is considered to be needed when:
91809202
// - [...]
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// RUN: %clang_cc1 %s -verify
2+
3+
#if !__has_cpp_attribute(clang::no_specializations)
4+
# error
5+
#endif
6+
7+
struct [[clang::no_specializations]] S {}; // expected-warning {{'no_specializations' attribute only applies to class templates, function templates, and variable templates}}
8+
9+
template <class T, class U>
10+
struct [[clang::no_specializations]] is_same { // expected-note 2 {{marked 'no_specializations' here}}
11+
static constexpr bool value = __is_same(T, U);
12+
};
13+
14+
template <class T>
15+
using alias [[clang::no_specializations]] = T; // expected-warning {{'no_specializations' attribute only applies to class templates, function templates, and variable templates}}
16+
17+
template <>
18+
struct is_same<int, char> {}; // expected-error {{'is_same' cannot be specialized}}
19+
20+
template <class>
21+
struct Template {};
22+
23+
template <class T>
24+
struct is_same<Template<T>, Template <T>> {}; // expected-error {{'is_same' cannot be specialized}}
25+
26+
bool test_instantiation1 = is_same<int, int>::value;
27+
28+
template <class T, class U>
29+
[[clang::no_specializations]] inline constexpr bool is_same_v = __is_same(T, U); // expected-note 2 {{marked 'no_specializations' here}}
30+
31+
template <>
32+
inline constexpr bool is_same_v<int, char> = false; // expected-error {{'is_same_v' cannot be specialized}}
33+
34+
template <class T>
35+
inline constexpr bool is_same_v<Template <T>, Template <T>> = true; // expected-error {{'is_same_v' cannot be specialized}}
36+
37+
bool test_instantiation2 = is_same_v<int, int>;
38+
39+
template <class T>
40+
struct [[clang::no_specializations("specializing type traits results in undefined behaviour")]] is_trivial { // expected-note {{marked 'no_specializations' here}}
41+
static constexpr bool value = __is_trivial(T);
42+
};
43+
44+
template <>
45+
struct is_trivial<int> {}; // expected-error {{'is_trivial' cannot be specialized: specializing type traits results in undefined behaviour}}
46+
47+
template <class T>
48+
[[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}}
49+
50+
template <>
51+
inline constexpr bool is_trivial_v<int> = false; // expected-error {{'is_trivial_v' cannot be specialized: specializing type traits results in undefined behaviour}}
52+
53+
template <class T>
54+
struct Partial {};
55+
56+
template <class T>
57+
struct [[clang::no_specializations]] Partial<Template <T>> {}; // expected-warning {{'no_specializations' attribute only applies to class templates, function templates, and variable templates}}
58+
59+
template <class T>
60+
[[clang::no_specializations]] void func(); // expected-note {{marked 'no_specializations' here}}
61+
62+
template <> void func<int>(); // expected-error {{'func' cannot be specialized}}

0 commit comments

Comments
 (0)