Skip to content

[Clang] Minimal support for availability attributes on partial specializations #138426

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,9 @@ Bug Fixes to Attribute Support
``__attribute__((unused))`` are still ignored after the definition, though
this behavior may be relaxed in the future). (#GH135481)

- Clang will warn if a complete type specializes a deprecated partial specialization.
(#GH44496)

Bug Fixes to C++ Support
^^^^^^^^^^^^^^^^^^^^^^^^

Expand Down
9 changes: 7 additions & 2 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -2388,9 +2388,14 @@ class Sema final : public SemaBase {
void DiagnoseAvailabilityOfDecl(NamedDecl *D, ArrayRef<SourceLocation> Locs,
const ObjCInterfaceDecl *UnknownObjCClass,
bool ObjCPropertyAccess,
bool AvoidPartialAvailabilityChecks = false,
ObjCInterfaceDecl *ClassReceiver = nullptr);
bool AvoidPartialAvailabilityChecks,
ObjCInterfaceDecl *ClassReceiver);

void DiagnoseAvailabilityOfDecl(NamedDecl *D, ArrayRef<SourceLocation> Locs);

std::pair<AvailabilityResult, const NamedDecl *>
ShouldDiagnoseAvailabilityOfDecl(const NamedDecl *D, std::string *Message,
ObjCInterfaceDecl *ClassReceiver);
///@}

//
Expand Down
29 changes: 18 additions & 11 deletions clang/lib/Sema/SemaAvailability.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,9 @@ static const AvailabilityAttr *getAttrForPlatform(ASTContext &Context,
/// the availability attribute that is selected.
/// \param ClassReceiver If we're checking the method of a class message
/// send, the class. Otherwise nullptr.
static std::pair<AvailabilityResult, const NamedDecl *>
ShouldDiagnoseAvailabilityOfDecl(Sema &S, const NamedDecl *D,
std::string *Message,
ObjCInterfaceDecl *ClassReceiver) {
std::pair<AvailabilityResult, const NamedDecl *>
Sema::ShouldDiagnoseAvailabilityOfDecl(const NamedDecl *D, std::string *Message,
ObjCInterfaceDecl *ClassReceiver) {
AvailabilityResult Result = D->getAvailability(Message);

// For typedefs, if the typedef declaration appears available look
Expand Down Expand Up @@ -147,12 +146,12 @@ ShouldDiagnoseAvailabilityOfDecl(Sema &S, const NamedDecl *D,

// For +new, infer availability from -init.
if (const auto *MD = dyn_cast<ObjCMethodDecl>(D)) {
if (S.ObjC().NSAPIObj && ClassReceiver) {
if (ObjC().NSAPIObj && ClassReceiver) {
ObjCMethodDecl *Init = ClassReceiver->lookupInstanceMethod(
S.ObjC().NSAPIObj->getInitSelector());
ObjC().NSAPIObj->getInitSelector());
if (Init && Result == AR_Available && MD->isClassMethod() &&
MD->getSelector() == S.ObjC().NSAPIObj->getNewSelector() &&
MD->definedInNSObject(S.getASTContext())) {
MD->getSelector() == ObjC().NSAPIObj->getNewSelector() &&
MD->definedInNSObject(getASTContext())) {
Result = Init->getAvailability(Message);
D = Init;
}
Expand All @@ -162,7 +161,6 @@ ShouldDiagnoseAvailabilityOfDecl(Sema &S, const NamedDecl *D,
return {Result, D};
}


/// whether we should emit a diagnostic for \c K and \c DeclVersion in
/// the context of \c Ctx. For example, we should emit an unavailable diagnostic
/// in a deprecated context, but not the other way around.
Expand Down Expand Up @@ -876,7 +874,7 @@ void DiagnoseUnguardedAvailability::DiagnoseDeclAvailability(
AvailabilityResult Result;
const NamedDecl *OffendingDecl;
std::tie(Result, OffendingDecl) =
ShouldDiagnoseAvailabilityOfDecl(SemaRef, D, nullptr, ReceiverClass);
SemaRef.ShouldDiagnoseAvailabilityOfDecl(D, nullptr, ReceiverClass);
if (Result != AR_Available) {
// All other diagnostic kinds have already been handled in
// DiagnoseAvailabilityOfDecl.
Expand Down Expand Up @@ -1112,12 +1110,13 @@ void Sema::DiagnoseAvailabilityOfDecl(NamedDecl *D,
bool ObjCPropertyAccess,
bool AvoidPartialAvailabilityChecks,
ObjCInterfaceDecl *ClassReceiver) {

std::string Message;
AvailabilityResult Result;
const NamedDecl* OffendingDecl;
// See if this declaration is unavailable, deprecated, or partial.
std::tie(Result, OffendingDecl) =
ShouldDiagnoseAvailabilityOfDecl(*this, D, &Message, ClassReceiver);
ShouldDiagnoseAvailabilityOfDecl(D, &Message, ClassReceiver);
if (Result == AR_Available)
return;

Expand Down Expand Up @@ -1146,3 +1145,11 @@ void Sema::DiagnoseAvailabilityOfDecl(NamedDecl *D,
EmitAvailabilityWarning(*this, Result, D, OffendingDecl, Message, Locs,
UnknownObjCClass, ObjCPDecl, ObjCPropertyAccess);
}

void Sema::DiagnoseAvailabilityOfDecl(NamedDecl *D,
ArrayRef<SourceLocation> Locs) {
DiagnoseAvailabilityOfDecl(D, Locs, /*UnknownObjCClass=*/nullptr,
/*ObjCPropertyAccess=*/false,
/*AvoidPartialAvailabilityChecks=*/false,
/*ClassReceiver=*/nullptr);
}
18 changes: 17 additions & 1 deletion clang/lib/Sema/SemaTemplateInstantiate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4094,16 +4094,32 @@ bool Sema::InstantiateClassTemplateSpecialization(
if (ClassTemplateSpec->isInvalidDecl())
return true;

bool HadAvaibilityWarning =
ShouldDiagnoseAvailabilityOfDecl(ClassTemplateSpec, nullptr, nullptr)
.first != AR_Available;

ActionResult<CXXRecordDecl *> Pattern =
getPatternForClassTemplateSpecialization(*this, PointOfInstantiation,
ClassTemplateSpec, TSK,
PrimaryStrictPackMatch);

if (!Pattern.isUsable())
return Pattern.isInvalid();

return InstantiateClass(
bool Err = InstantiateClass(
PointOfInstantiation, ClassTemplateSpec, Pattern.get(),
getTemplateInstantiationArgs(ClassTemplateSpec), TSK, Complain);

// If we haven't already warn on avaibility, consider the avaibility
// attributes of the partial specialization.
// Note that - because we need to have deduced the partial specialization -
// We can only emit these warnings when the specialization is instantiated.
if (!Err && !HadAvaibilityWarning) {
assert(ClassTemplateSpec->getTemplateSpecializationKind() !=
TSK_Undeclared);
DiagnoseAvailabilityOfDecl(ClassTemplateSpec, PointOfInstantiation);
}
return Err;
}

void
Expand Down
69 changes: 68 additions & 1 deletion clang/test/CXX/dcl.dcl/dcl.attr/dcl.attr.deprecated/p1.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ template <typename T> struct [[deprecated]] B;//expected-note {{'B<int>' has bee
B<int> *q2; // expected-warning {{'B<int>' is deprecated}}
B<float> *r2; // expected-warning {{'B<float>' is deprecated}}

template <typename T>
template <typename T>
T some_func(T t) {
struct [[deprecated]] FunS{}; // expected-note {{'FunS' has been explicitly marked deprecated here}}
FunS f;// expected-warning {{'FunS' is deprecated}}
Expand All @@ -72,3 +72,70 @@ template <class B1> struct B {

template struct B<A>; // expected-note {{requested here}}
} // namespace GH58547


namespace GH44496 {


template <typename T>
class function { };
template <typename A>
class __attribute__((deprecated)) function<void(A)> { };
// expected-note@-1 {{'function<void (int)>' has been explicitly marked deprecated here}}

void test() {
[[maybe_unused]] function<void(int)> f; // expected-warning{{'function<void (int)>' is deprecated}}
}

template <class> struct my_template {
using type = void;
};

template <class T>
struct [[deprecated("primary")]] deprecated { // #deprecated-primary-marked-here
using type = T;
};

template <class T>
struct my_template<volatile T> : deprecated<T> {}; // #deprecated-primary-base

template <class T>
struct [[deprecated("specialization")]] my_template<const T> : deprecated<const T> {}; // #my_template-explicit-here


template <class T> using my_template_t = typename my_template<T>::type; // #deprecated-my-template-alias

// We cannot warn on X because no instantiation has taken place yet
using X = my_template<volatile int>;

// Because we already warn on the attribute on the plimary template, we ignore the attribute on the specialization
using Y = my_template_t<const int>;
// expected-warning@#deprecated-primary-base {{'deprecated<int>' is deprecated: primary}} \
// expected-note@-1 {{in instantiation of template type alias}} \
// expected-note@#deprecated-primary-marked-here {{has been explicitly marked deprecated here}}

using Z = my_template_t<volatile int>;
// expected-warning@#deprecated-my-template-alias {{'my_template<const int>' is deprecated: specialization}} \
// expected-note@#my_template-explicit-here {{'my_template<const int>' has been explicitly marked deprecated here}} \
// expected-note@#deprecated-my-template-alias {{in instantiation of template class 'GH44496::my_template<volatile int>' requested here}} \
// expected-note@-1 {{in instantiation of template type alias 'my_template_t' requested here}}

template <class T>
struct primary_not_deprecated {
using type = T;
};
template <class T>
struct [[deprecated("specialization")]] primary_not_deprecated<volatile T> : deprecated<T> {};
// expected-note@-1 {{'primary_not_deprecated<volatile int>' has been explicitly marked deprecated here}}

// We cannot warn on S1 because no instantiation has taken place yet
using S1 = primary_not_deprecated<volatile int>;


using S2 = primary_not_deprecated<volatile int>;

X x;
Z z;
S2 s2;
// expected-warning@-1 {{'primary_not_deprecated<volatile int>' is deprecated: specialization}}
}