https://github.com/halbi2 updated https://github.com/llvm/llvm-project/pull/154943
>From bb04ff2cf154b1c5547f7d5236e8fe769b9a54dd Mon Sep 17 00:00:00 2001 From: halbi2 <hehira...@gmail.com> Date: Fri, 22 Aug 2025 09:13:17 -0400 Subject: [PATCH 1/2] [clang] Add the candiscard attribute to suppress nodiscard Fulfills the requirement that @huixie90 stated in #139651 that Clang should have a way to disable [[nodiscard]] on a function by function basis. This will allow us finally to resume fixing #130656. --- clang/docs/ReleaseNotes.rst | 4 ++ clang/include/clang/Basic/Attr.td | 8 ++++ clang/include/clang/Basic/AttrDocs.td | 38 ++++++++++++++++++ clang/lib/AST/Expr.cpp | 31 ++++++++++----- ...a-attribute-supported-attributes-list.test | 1 + clang/test/Sema/c2x-nodiscard.c | 18 ++++++++- clang/test/SemaCXX/warn-unused-result.cpp | 39 +++++++++++++++++++ clang/test/SemaObjC/attr-nodiscard.m | 10 +++++ clang/test/SemaObjCXX/attr-nodiscard.mm | 16 ++++++++ 9 files changed, 155 insertions(+), 10 deletions(-) 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 } >From d9c99df1b0342cf7e160286fde988785270ccd83 Mon Sep 17 00:00:00 2001 From: halbi2 <hehira...@gmail.com> Date: Fri, 22 Aug 2025 09:24:19 -0400 Subject: [PATCH 2/2] fix formatting --- clang/lib/AST/Expr.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp index ee1a3e5f4961b..a479dca6674a4 100644 --- a/clang/lib/AST/Expr.cpp +++ b/clang/lib/AST/Expr.cpp @@ -1632,8 +1632,8 @@ 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 for the diagnostic. - // If the callee is marked candiscard, do not diagnose. + // 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>()) _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits