[clang] [clang] Add the candiscard attribute to suppress nodiscard (PR #154943)
via cfe-commits
cfe-commits at lists.llvm.org
Fri Aug 22 06:20:00 PDT 2025
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: None (halbi2)
<details>
<summary>Changes</summary>
Fulfills the requirement that @<!-- -->huixie90 and @<!-- -->philnik777 stated in #<!-- -->139651 that Clang will have a way to disable [[nodiscard]] on a function by function basis.
This will allow us finally to resume fixing #<!-- -->130656 via #<!-- -->139651.
---
Full diff: https://github.com/llvm/llvm-project/pull/154943.diff
9 Files Affected:
- (modified) clang/docs/ReleaseNotes.rst (+4)
- (modified) clang/include/clang/Basic/Attr.td (+8)
- (modified) clang/include/clang/Basic/AttrDocs.td (+38)
- (modified) clang/lib/AST/Expr.cpp (+22-9)
- (modified) clang/test/Misc/pragma-attribute-supported-attributes-list.test (+1)
- (modified) clang/test/Sema/c2x-nodiscard.c (+17-1)
- (modified) clang/test/SemaCXX/warn-unused-result.cpp (+39)
- (modified) clang/test/SemaObjC/attr-nodiscard.m (+10)
- (modified) clang/test/SemaObjCXX/attr-nodiscard.mm (+16)
``````````diff
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 0d85b6f426995..f9d9b4355cef7 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -199,6 +199,10 @@ Removed Compiler Flags
Attribute Changes in Clang
--------------------------
+- A new attribute ``[[clang::candiscard]]`` can be applied to a function returning a nodiscard type
+ to suppress the nodiscard warning on that function in particular. Also, it can be applied to
+ a typedef alias to suppress the nodiscard warning on all functions returning values of the
+ typedef type.
Improvements to Clang's diagnostics
-----------------------------------
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 29364c5903d31..c92a962802594 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -3646,6 +3646,14 @@ def Unavailable : InheritableAttr {
let MeaningfulToClassTemplateDefinition = 1;
}
+def CanDiscard : InheritableAttr {
+ let Spellings = [CXX11<"clang", "candiscard">,
+ GCC<"candiscard">];
+ let Subjects = SubjectList<[ObjCMethod, FunctionLike, TypedefName]>;
+ let Documentation = [CanDiscardDocs];
+ let SimpleHandler = 1;
+}
+
def DiagnoseIf : InheritableAttr {
// Does not have a [[]] spelling because this attribute requires the ability
// to parse function arguments but the attribute is not written in the type
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index b9405c5fc86c1..524bcb0e74cab 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -2452,6 +2452,44 @@ use the annotated ``[[nodiscard]]`` constructor or result in an annotated type.
}];
}
+def CanDiscardDocs : Documentation {
+ let Category = DocCatFunction;
+ let Heading = "candiscard";
+ let Content = [{
+A function whose return type is marked with ``[[nodiscard]]`` generally cannot have
+its return value discarded, even though this may be safe in some rare situations.
+Clang allows an individual function to be marked with ``[[clang::candiscard]]``
+or ``__attribute__((candiscard))`` to override the effect of a ``[[nodiscard]]``
+return type.
+
+.. code-block:: c++
+
+ struct [[nodiscard]] error_info { /*...*/ };
+ error_info enable_missile_safety_mode();
+ [[clang::candiscard]] error_info reload_missiles();
+
+ void test_missiles() {
+ enable_missile_safety_mode(); // diagnoses
+ reload_missiles(); // does not diagnose
+ }
+
+Also, a type alias can be marked with ``[[clang::candiscard]]`` to mask the
+effect of ``[[nodiscard]]`` on the underlying type.
+
+.. code-block:: c++
+
+ struct [[nodiscard]] error_info { /*...*/ };
+ using informational_error_info [[clang::candiscard]] = error_info;
+ error_info enable_missile_safety_mode();
+ informational_error_info reload_missiles();
+
+ void test_missiles() {
+ enable_missile_safety_mode(); // diagnoses
+ reload_missiles(); // does not diagnose
+ }
+ }];
+}
+
def FallthroughDocs : Documentation {
let Category = DocCatStmt;
let Heading = "fallthrough";
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index 340de6d4be934..ee1a3e5f4961b 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -1632,21 +1632,34 @@ QualType CallExpr::getCallReturnType(const ASTContext &Ctx) const {
std::pair<const NamedDecl *, const WarnUnusedResultAttr *>
Expr::getUnusedResultAttrImpl(const Decl *Callee, QualType ReturnType) {
- // If the callee is marked nodiscard, return that attribute
- if (Callee != nullptr)
+ // If the callee is marked nodiscard, return that attribute for the diagnostic.
+ // If the callee is marked candiscard, do not diagnose.
+ // If seen on the same level, candiscard beats nodiscard.
+ if (Callee != nullptr) {
+ if (const auto *A = Callee->getAttr<CanDiscardAttr>())
+ return {nullptr, nullptr};
if (const auto *A = Callee->getAttr<WarnUnusedResultAttr>())
return {nullptr, A};
+ }
- // If the return type is a struct, union, or enum that is marked nodiscard,
- // then return the return type attribute.
- if (const TagDecl *TD = ReturnType->getAsTagDecl())
- if (const auto *A = TD->getAttr<WarnUnusedResultAttr>())
- return {TD, A};
-
+ // Walk the return type's (chain of) type aliases. The first alias
+ // that is marked either nodiscard or candiscard ends the walk.
for (const auto *TD = ReturnType->getAs<TypedefType>(); TD;
- TD = TD->desugar()->getAs<TypedefType>())
+ TD = TD->desugar()->getAs<TypedefType>()) {
+ if (const auto *A = TD->getDecl()->getAttr<CanDiscardAttr>())
+ return {nullptr, nullptr};
if (const auto *A = TD->getDecl()->getAttr<WarnUnusedResultAttr>())
return {TD->getDecl(), A};
+ }
+
+ // Check whether the return type's class declaration is marked nodiscard.
+ if (const TagDecl *TD = ReturnType->getAsTagDecl()) {
+ if (const auto *A = TD->getAttr<CanDiscardAttr>())
+ return {nullptr, nullptr};
+ if (const auto *A = TD->getAttr<WarnUnusedResultAttr>())
+ return {TD, A};
+ }
+
return {nullptr, nullptr};
}
diff --git a/clang/test/Misc/pragma-attribute-supported-attributes-list.test b/clang/test/Misc/pragma-attribute-supported-attributes-list.test
index 37ff33e5a1523..38a771156c3cc 100644
--- a/clang/test/Misc/pragma-attribute-supported-attributes-list.test
+++ b/clang/test/Misc/pragma-attribute-supported-attributes-list.test
@@ -48,6 +48,7 @@
// CHECK-NEXT: CallableWhen (SubjectMatchRule_function_is_member)
// CHECK-NEXT: Callback (SubjectMatchRule_function)
// CHECK-NEXT: CalledOnce (SubjectMatchRule_variable_is_parameter)
+// CHECK-NEXT: CanDiscard (SubjectMatchRule_objc_method, SubjectMatchRule_hasType_functionType, SubjectMatchRule_type_alias)
// CHECK-NEXT: Capability (SubjectMatchRule_record, SubjectMatchRule_type_alias)
// CHECK-NEXT: CarriesDependency (SubjectMatchRule_variable_is_parameter, SubjectMatchRule_objc_method, SubjectMatchRule_function)
// CHECK-NEXT: Cleanup (SubjectMatchRule_variable_is_local)
diff --git a/clang/test/Sema/c2x-nodiscard.c b/clang/test/Sema/c2x-nodiscard.c
index 852c74721693b..07d76485c3702 100644
--- a/clang/test/Sema/c2x-nodiscard.c
+++ b/clang/test/Sema/c2x-nodiscard.c
@@ -65,15 +65,31 @@ void GH104391() {
M; // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
}
+struct S4 get_s_ignored(void) __attribute__((candiscard));
+enum E2 get_e_ignored(void) __attribute__((candiscard));
+typedef __attribute__((candiscard)) enum E2 EIgnored;
+EIgnored get_e_ignored2();
+
+void f4(void) {
+ get_s_ignored();
+ get_e_ignored();
+ get_e_ignored2();
+}
+
[[nodiscard]] typedef int NoDInt; // expected-warning {{'[[nodiscard]]' attribute ignored when applied to a typedef}}
typedef __attribute__((warn_unused)) int WUInt; // expected-warning {{'warn_unused' attribute only applies to structs, unions, and classes}}
typedef __attribute__((warn_unused_result)) int WURInt;
+typedef __attribute__((candiscard)) WURInt WURIntIgnored;
NoDInt get_nodint();
WUInt get_wuint();
WURInt get_wurint();
+WURIntIgnored get_wurint_ignored();
+WURIntIgnored get_wurint_ignored2() __attribute__((candiscard));
-void f4(void) {
+void f5(void) {
get_nodint(); // no warning because attribute is ignored
get_wuint(); // no warning because attribute is ignored
get_wurint(); // expected-warning {{ignoring return value of type 'WURInt' declared with 'warn_unused_result' attribute}}
+ get_wurint_ignored(); // no warning
+ get_wurint_ignored2(); // no warning
}
diff --git a/clang/test/SemaCXX/warn-unused-result.cpp b/clang/test/SemaCXX/warn-unused-result.cpp
index 098817729efb1..48963449155a4 100644
--- a/clang/test/SemaCXX/warn-unused-result.cpp
+++ b/clang/test/SemaCXX/warn-unused-result.cpp
@@ -425,19 +425,36 @@ struct [[gnu::warn_unused_result]] WarnUnusedResult {
WarnUnusedResult(const char*);
};
+using NoDIgnored [[clang::candiscard]] = NoDiscard;
+using WUIgnored [[clang::candiscard]] = WarnUnused;
+using WURIgnored [[clang::candiscard]] = WarnUnusedResult;
+
NoDiscard return_nodiscard();
WarnUnused return_warnunused();
WarnUnusedResult return_warnunusedresult();
+NoDIgnored return_nodiscard_ignored();
+WUIgnored return_warnunused_ignored();
+WURIgnored return_warnunusedresult_ignored();
+[[clang::candiscard]] NoDiscard return_nodiscard_ignored2();
+[[clang::candiscard]] WarnUnused return_warnunused_ignored2();
+[[clang::candiscard]] WarnUnusedResult return_warnunusedresult_ignored2();
NoDiscard (*p_return_nodiscard)();
WarnUnused (*p_return_warnunused)();
WarnUnusedResult (*p_return_warnunusedresult)();
+NoDIgnored (*p_return_nodiscard_ignored)();
+WUIgnored (*p_return_warnunused_ignored)();
+WURIgnored (*p_return_warnunusedresult_ignored)();
+[[clang::candiscard]] NoDiscard (*p_return_nodiscard_ignored2)();
+[[clang::candiscard]] WarnUnused (*p_return_warnunused_ignored2)();
+[[clang::candiscard]] WarnUnusedResult (*p_return_warnunusedresult_ignored2)();
NoDiscard (*(*pp_return_nodiscard)())();
WarnUnused (*(*pp_return_warnunused)())();
WarnUnusedResult (*(*pp_return_warnunusedresult)())();
template <class T> T from_a_template();
+template <class T> [[clang::candiscard]] T from_a_template_ignored();
void test() {
// Unused but named variables
@@ -474,11 +491,23 @@ void test() {
return_nodiscard(); // expected-warning {{ignoring return value of type 'NoDiscard' declared with 'nodiscard' attribute}}
return_warnunused(); // no warning
return_warnunusedresult(); // expected-warning {{ignoring return value of type 'WarnUnusedResult' declared with 'gnu::warn_unused_result' attribute}}
+ return_nodiscard_ignored(); // no warning
+ return_warnunused_ignored(); // no warning
+ return_warnunusedresult_ignored(); // no warning
+ return_nodiscard_ignored2(); // no warning
+ return_warnunused_ignored2(); // no warning
+ return_warnunusedresult_ignored2(); // no warning
// Function pointer return values
p_return_nodiscard(); // expected-warning {{ignoring return value of type 'NoDiscard' declared with 'nodiscard' attribute}}
p_return_warnunused(); // no warning
p_return_warnunusedresult(); // expected-warning {{ignoring return value of type 'WarnUnusedResult' declared with 'gnu::warn_unused_result' attribute}}
+ p_return_nodiscard_ignored(); // no warning
+ p_return_warnunused_ignored(); // no warning
+ p_return_warnunusedresult_ignored(); // no warning
+ p_return_nodiscard_ignored2(); // no warning
+ p_return_warnunused_ignored2(); // no warning
+ p_return_warnunusedresult_ignored2(); // no warning
// Function pointer expression return values
pp_return_nodiscard()(); // expected-warning {{ignoring return value of type 'NoDiscard' declared with 'nodiscard' attribute}}
@@ -489,6 +518,16 @@ void test() {
from_a_template<NoDiscard>(); // expected-warning {{ignoring return value of type 'NoDiscard' declared with 'nodiscard' attribute}}
from_a_template<WarnUnused>(); // no warning
from_a_template<WarnUnusedResult>(); // expected-warning {{ignoring return value of type 'WarnUnusedResult' declared with 'gnu::warn_unused_result' attribute}}
+
+ // In a template instantiation the information about the typedef is lost,
+ // so the candiscard attribute is lost, so the diagnostic is not suppressed
+ from_a_template<NoDIgnored>(); // expected-warning {{ignoring return value of type 'NoDiscard' declared with 'nodiscard' attribute}}
+ from_a_template<WUIgnored>(); // no warning
+ from_a_template<WURIgnored>(); // expected-warning {{ignoring return value of type 'WarnUnusedResult' declared with 'gnu::warn_unused_result' attribute}}
+
+ from_a_template_ignored<NoDiscard>(); // no warning
+ from_a_template_ignored<WarnUnused>(); // no warning
+ from_a_template_ignored<WarnUnusedResult>(); // no warning
}
} // namespace candiscard
diff --git a/clang/test/SemaObjC/attr-nodiscard.m b/clang/test/SemaObjC/attr-nodiscard.m
index 26bbd247d4a3d..71bb2bb355220 100644
--- a/clang/test/SemaObjC/attr-nodiscard.m
+++ b/clang/test/SemaObjC/attr-nodiscard.m
@@ -6,6 +6,8 @@
[[nodiscard]] typedef int NI; // expected-warning {{'[[nodiscard]]' attribute ignored when applied to a typedef}}
typedef __attribute__((warn_unused_result)) int WUR;
+typedef __attribute__((candiscard)) struct expected EIgnored;
+typedef __attribute__((candiscard)) WUR WURIgnored;
@interface INTF
- (int) a [[nodiscard]];
@@ -13,10 +15,15 @@ + (int) b [[nodiscard]];
- (struct expected) c;
+ (struct expected) d;
- (E) e;
+- (EIgnored) e_ignored;
+- (E) e_ignored2 __attribute__((candiscard));
+ (E) f;
- (void) g [[nodiscard]]; // expected-warning {{attribute 'nodiscard' cannot be applied to Objective-C method without return value}}
- (NI) h;
+- (NI) h_ignored __attribute__((candiscard));
- (WUR) i;
+- (WURIgnored) i_ignored;
+- (WUR) i_ignored2 __attribute__((candiscard));
@end
void foo(INTF *a) {
@@ -28,5 +35,8 @@ void foo(INTF *a) {
[INTF f]; // expected-warning {{ignoring return value of type 'expected' declared with 'nodiscard' attribute}}
[a g]; // no warning because g returns void
[a h]; // no warning because attribute is ignored when applied to a typedef
+ [a h_ignored]; // no warning
[a i]; // expected-warning {{ignoring return value of type 'WUR' declared with 'warn_unused_result' attribute}}
+ [a i_ignored]; // no warning
+ [a i_ignored2]; // no warning
}
diff --git a/clang/test/SemaObjCXX/attr-nodiscard.mm b/clang/test/SemaObjCXX/attr-nodiscard.mm
index 18d829632e428..7a4e101049093 100644
--- a/clang/test/SemaObjCXX/attr-nodiscard.mm
+++ b/clang/test/SemaObjCXX/attr-nodiscard.mm
@@ -8,16 +8,26 @@
using NI [[nodiscard]] = int; // expected-warning {{'[[nodiscard]]' attribute ignored when applied to a typedef}}
using WURI [[clang::warn_unused_result]] = int;
+using EIgnored [[clang::candiscard]] = E;
+using NIIgnored [[clang::candiscard]] = NI;
+using WURIgnored [[clang::candiscard]] = WURI;
+
@interface INTF
- (int) a [[nodiscard]];
+ (int) b [[nodiscard]];
- (expected<int>) c;
+ (expected<int>) d;
- (E) e;
+- (EIgnored) e_ignored;
+- (E) e_ignored2 [[clang::candiscard]];
+ (E) f;
- (void) g [[nodiscard]]; // expected-warning {{attribute 'nodiscard' cannot be applied to Objective-C method without return value}}
- (NI) h;
+- (NIIgnored) h_ignored;
+- (NI) h_ignored2 [[clang::candiscard]];
- (WURI) i;
+- (WURIgnored) i_ignored;
+- (WURI) i_ignored2 [[clang::candiscard]];
@end
void foo(INTF *a) {
@@ -26,8 +36,14 @@ void foo(INTF *a) {
[a c]; // expected-warning {{ignoring return value of type 'expected<int>' declared with 'nodiscard' attribute}}
[INTF d]; // expected-warning {{ignoring return value of type 'expected<int>' declared with 'nodiscard' attribute}}
[a e]; // expected-warning {{ignoring return value of type 'expected<int>' declared with 'nodiscard' attribute}}
+ [a e_ignored]; // no warning
+ [a e_ignored2]; // no warning
[INTF f]; // expected-warning {{ignoring return value of type 'expected<int>' declared with 'nodiscard' attribute}}
[a g]; // no warning because g returns void
[a h]; // no warning because attribute is ignored
+ [a h_ignored]; // no warning
+ [a h_ignored2]; // no warning
[a i]; // expected-warning {{ignoring return value of type 'WURI' declared with 'clang::warn_unused_result' attribute}}
+ [a i_ignored]; // no warning
+ [a i_ignored2]; // no warning
}
``````````
</details>
https://github.com/llvm/llvm-project/pull/154943
More information about the cfe-commits
mailing list