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..81d262988aac2 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -3646,6 +3646,13 @@ def Unavailable : InheritableAttr { let MeaningfulToClassTemplateDefinition = 1; } +def CanDiscard : InheritableAttr { + let Spellings = [Clang<"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..a479dca6674a4 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 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()) + return {nullptr, nullptr}; if (const auto *A = Callee->getAttr()) 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()) - 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(); TD; - TD = TD->desugar()->getAs()) + TD = TD->desugar()->getAs()) { + if (const auto *A = TD->getDecl()->getAttr()) + return {nullptr, nullptr}; if (const auto *A = TD->getDecl()->getAttr()) 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()) + return {nullptr, nullptr}; + if (const auto *A = TD->getAttr()) + 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..f9c676faa5464 100644 --- a/clang/test/SemaCXX/warn-unused-result.cpp +++ b/clang/test/SemaCXX/warn-unused-result.cpp @@ -425,21 +425,76 @@ 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 T from_a_template(); +template [[clang::candiscard]] T from_a_template_ignored(); -void test() { +struct A { + using NoDInt [[clang::warn_unused_result]] = int; + struct [[nodiscard]] NoDClass {}; + template using NoDTemplate [[clang::warn_unused_result]] = T; +}; + +template +struct B { + using NoDInt = typename T::NoDInt; + using NoDIntIgnored [[clang::candiscard]] = typename T::NoDInt; + using NoDClass = typename T::NoDClass; + using NoDClassIgnored [[clang::candiscard]] = typename T::NoDClass; + template using NoDInt2 = typename U::NoDInt; + template using NoDIntIgnored2 [[clang::candiscard]] = typename U::NoDInt; + template using NoDClass2 = typename U::NoDClass; + template using NoDClassIgnored2 [[clang::candiscard]] = typename U::NoDClass; +}; + +A::NoDTemplate return_nodtemplate(); +B::NoDInt return_nodint(); +B::NoDIntIgnored return_nodint_ignored(); +B::NoDClass return_nodclass(); +B::NoDClassIgnored return_nodclass_ignored(); +B::NoDInt2 return_nodint2(); +B::NoDIntIgnored2 return_nodint_ignored2(); +B::NoDClass2 return_nodclass2(); +B::NoDClassIgnored2 return_nodclass_ignored2(); + +NoDiscard (A::*mp_return_nodiscard)(); +WarnUnused (A::*mp_return_warnunused)(); +WarnUnusedResult (A::*mp_return_warnunusedresult)(); +NoDIgnored (A::*mp_return_nodiscard_ignored)(); +WUIgnored (A::*mp_return_warnunused_ignored)(); +WURIgnored (A::*mp_return_warnunusedresult_ignored)(); +[[clang::candiscard]] NoDiscard (A::*mp_return_nodiscard_ignored2)(); +[[clang::candiscard]] WarnUnused (A::*mp_return_warnunused_ignored2)(); +[[clang::candiscard]] WarnUnusedResult (A::*mp_return_warnunusedresult_ignored2)(); + +void test(A *pa) { // Unused but named variables NoDiscard unused_variable1(1); // no warning NoDiscard unused_variable2(""); // no warning @@ -471,14 +526,37 @@ void test() { static_cast(""); // expected-warning {{ignoring temporary of type 'WarnUnusedResult' declared with 'gnu::warn_unused_result' attribute}} // Function return values - 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(); // 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(); // 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 + + // Member function pointer return values + (pa->*mp_return_nodiscard)(); // expected-warning {{ignoring return value of type 'NoDiscard' declared with 'nodiscard' attribute}} + (pa->*mp_return_warnunused)(); // no warning + (pa->*mp_return_warnunusedresult)(); // expected-warning {{ignoring return value of type 'WarnUnusedResult' declared with 'gnu::warn_unused_result' attribute}} + (pa->*mp_return_nodiscard_ignored)(); // no warning + (pa->*mp_return_warnunused_ignored)(); // no warning + (pa->*mp_return_warnunusedresult_ignored)(); // no warning + (pa->*mp_return_nodiscard_ignored2)(); // no warning + (pa->*mp_return_warnunused_ignored2)(); // no warning + (pa->*mp_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 +567,44 @@ void test() { from_a_template(); // expected-warning {{ignoring return value of type 'NoDiscard' declared with 'nodiscard' attribute}} from_a_template(); // no warning from_a_template(); // 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(); // expected-warning {{ignoring return value of type 'NoDiscard' declared with 'nodiscard' attribute}} + from_a_template(); // no warning + from_a_template(); // expected-warning {{ignoring return value of type 'WarnUnusedResult' declared with 'gnu::warn_unused_result' attribute}} + + from_a_template_ignored(); // no warning + from_a_template_ignored(); // no warning + from_a_template_ignored(); // no warning + + return_nodint(); // expected-warning {{ignoring return value of type 'NoDInt' declared with 'clang::warn_unused_result' attribute}} + return_nodint_ignored(); // no warning + return_nodclass(); // expected-warning {{ignoring return value of type 'NoDClass' declared with 'nodiscard' attribute}} + return_nodclass_ignored(); // no warning + + // In an alias template the information about the typedef is lost, + // so the diagnostic is not issued. GH#68456 + return_nodtemplate(); + + // In an alias template the information about the typedef is lost, + // so the diagnostic is not suppressed. GH#68456 + return_nodint2(); // expected-warning {{ignoring return value of type 'NoDInt' declared with 'clang::warn_unused_result' attribute}} + return_nodint_ignored2(); // expected-warning {{ignoring return value of type 'NoDInt' declared with 'clang::warn_unused_result' attribute}} + return_nodclass2(); // expected-warning {{ignoring return value of type 'NoDClass' declared with 'nodiscard' attribute}} + return_nodclass_ignored2(); // expected-warning {{ignoring return value of type 'NoDClass' declared with 'nodiscard' attribute}} +} + +using BothAttributes [[clang::warn_unused_result, clang::candiscard]] = int; + +BothAttributes return_bothattributes1(); +[[nodiscard, clang::candiscard]] int return_bothattributes2(); +[[nodiscard]] NoDIgnored return_nodignored_nodiscard(); + +void testBothAttributes() { + return_bothattributes1(); // no warning because candiscard takes priority + return_bothattributes2(); // no warning because candiscard takes priority + return_nodignored_nodiscard(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}} } } // 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) c; + (expected) 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' declared with 'nodiscard' attribute}} [INTF d]; // expected-warning {{ignoring return value of type 'expected' declared with 'nodiscard' attribute}} [a e]; // expected-warning {{ignoring return value of type 'expected' declared with 'nodiscard' attribute}} + [a e_ignored]; // no warning + [a e_ignored2]; // no warning [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 + [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 }