Skip to content

Commit bb04ff2

Browse files
committed
[clang] Add the candiscard attribute to suppress nodiscard
Fulfills the requirement that @huixie90 stated in llvm#139651 that Clang should have a way to disable [[nodiscard]] on a function by function basis. This will allow us finally to resume fixing llvm#130656.
1 parent d807beb commit bb04ff2

File tree

9 files changed

+155
-10
lines changed

9 files changed

+155
-10
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,10 @@ Removed Compiler Flags
199199

200200
Attribute Changes in Clang
201201
--------------------------
202+
- A new attribute ``[[clang::candiscard]]`` can be applied to a function returning a nodiscard type
203+
to suppress the nodiscard warning on that function in particular. Also, it can be applied to
204+
a typedef alias to suppress the nodiscard warning on all functions returning values of the
205+
typedef type.
202206

203207
Improvements to Clang's diagnostics
204208
-----------------------------------

clang/include/clang/Basic/Attr.td

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3646,6 +3646,14 @@ def Unavailable : InheritableAttr {
36463646
let MeaningfulToClassTemplateDefinition = 1;
36473647
}
36483648

3649+
def CanDiscard : InheritableAttr {
3650+
let Spellings = [CXX11<"clang", "candiscard">,
3651+
GCC<"candiscard">];
3652+
let Subjects = SubjectList<[ObjCMethod, FunctionLike, TypedefName]>;
3653+
let Documentation = [CanDiscardDocs];
3654+
let SimpleHandler = 1;
3655+
}
3656+
36493657
def DiagnoseIf : InheritableAttr {
36503658
// Does not have a [[]] spelling because this attribute requires the ability
36513659
// to parse function arguments but the attribute is not written in the type

clang/include/clang/Basic/AttrDocs.td

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2452,6 +2452,44 @@ use the annotated ``[[nodiscard]]`` constructor or result in an annotated type.
24522452
}];
24532453
}
24542454

2455+
def CanDiscardDocs : Documentation {
2456+
let Category = DocCatFunction;
2457+
let Heading = "candiscard";
2458+
let Content = [{
2459+
A function whose return type is marked with ``[[nodiscard]]`` generally cannot have
2460+
its return value discarded, even though this may be safe in some rare situations.
2461+
Clang allows an individual function to be marked with ``[[clang::candiscard]]``
2462+
or ``__attribute__((candiscard))`` to override the effect of a ``[[nodiscard]]``
2463+
return type.
2464+
2465+
.. code-block:: c++
2466+
2467+
struct [[nodiscard]] error_info { /*...*/ };
2468+
error_info enable_missile_safety_mode();
2469+
[[clang::candiscard]] error_info reload_missiles();
2470+
2471+
void test_missiles() {
2472+
enable_missile_safety_mode(); // diagnoses
2473+
reload_missiles(); // does not diagnose
2474+
}
2475+
2476+
Also, a type alias can be marked with ``[[clang::candiscard]]`` to mask the
2477+
effect of ``[[nodiscard]]`` on the underlying type.
2478+
2479+
.. code-block:: c++
2480+
2481+
struct [[nodiscard]] error_info { /*...*/ };
2482+
using informational_error_info [[clang::candiscard]] = error_info;
2483+
error_info enable_missile_safety_mode();
2484+
informational_error_info reload_missiles();
2485+
2486+
void test_missiles() {
2487+
enable_missile_safety_mode(); // diagnoses
2488+
reload_missiles(); // does not diagnose
2489+
}
2490+
}];
2491+
}
2492+
24552493
def FallthroughDocs : Documentation {
24562494
let Category = DocCatStmt;
24572495
let Heading = "fallthrough";

clang/lib/AST/Expr.cpp

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1632,21 +1632,34 @@ QualType CallExpr::getCallReturnType(const ASTContext &Ctx) const {
16321632

16331633
std::pair<const NamedDecl *, const WarnUnusedResultAttr *>
16341634
Expr::getUnusedResultAttrImpl(const Decl *Callee, QualType ReturnType) {
1635-
// If the callee is marked nodiscard, return that attribute
1636-
if (Callee != nullptr)
1635+
// If the callee is marked nodiscard, return that attribute for the diagnostic.
1636+
// If the callee is marked candiscard, do not diagnose.
1637+
// If seen on the same level, candiscard beats nodiscard.
1638+
if (Callee != nullptr) {
1639+
if (const auto *A = Callee->getAttr<CanDiscardAttr>())
1640+
return {nullptr, nullptr};
16371641
if (const auto *A = Callee->getAttr<WarnUnusedResultAttr>())
16381642
return {nullptr, A};
1643+
}
16391644

1640-
// If the return type is a struct, union, or enum that is marked nodiscard,
1641-
// then return the return type attribute.
1642-
if (const TagDecl *TD = ReturnType->getAsTagDecl())
1643-
if (const auto *A = TD->getAttr<WarnUnusedResultAttr>())
1644-
return {TD, A};
1645-
1645+
// Walk the return type's (chain of) type aliases. The first alias
1646+
// that is marked either nodiscard or candiscard ends the walk.
16461647
for (const auto *TD = ReturnType->getAs<TypedefType>(); TD;
1647-
TD = TD->desugar()->getAs<TypedefType>())
1648+
TD = TD->desugar()->getAs<TypedefType>()) {
1649+
if (const auto *A = TD->getDecl()->getAttr<CanDiscardAttr>())
1650+
return {nullptr, nullptr};
16481651
if (const auto *A = TD->getDecl()->getAttr<WarnUnusedResultAttr>())
16491652
return {TD->getDecl(), A};
1653+
}
1654+
1655+
// Check whether the return type's class declaration is marked nodiscard.
1656+
if (const TagDecl *TD = ReturnType->getAsTagDecl()) {
1657+
if (const auto *A = TD->getAttr<CanDiscardAttr>())
1658+
return {nullptr, nullptr};
1659+
if (const auto *A = TD->getAttr<WarnUnusedResultAttr>())
1660+
return {TD, A};
1661+
}
1662+
16501663
return {nullptr, nullptr};
16511664
}
16521665

clang/test/Misc/pragma-attribute-supported-attributes-list.test

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
// CHECK-NEXT: CallableWhen (SubjectMatchRule_function_is_member)
4949
// CHECK-NEXT: Callback (SubjectMatchRule_function)
5050
// CHECK-NEXT: CalledOnce (SubjectMatchRule_variable_is_parameter)
51+
// CHECK-NEXT: CanDiscard (SubjectMatchRule_objc_method, SubjectMatchRule_hasType_functionType, SubjectMatchRule_type_alias)
5152
// CHECK-NEXT: Capability (SubjectMatchRule_record, SubjectMatchRule_type_alias)
5253
// CHECK-NEXT: CarriesDependency (SubjectMatchRule_variable_is_parameter, SubjectMatchRule_objc_method, SubjectMatchRule_function)
5354
// CHECK-NEXT: Cleanup (SubjectMatchRule_variable_is_local)

clang/test/Sema/c2x-nodiscard.c

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,15 +65,31 @@ void GH104391() {
6565
M; // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
6666
}
6767

68+
struct S4 get_s_ignored(void) __attribute__((candiscard));
69+
enum E2 get_e_ignored(void) __attribute__((candiscard));
70+
typedef __attribute__((candiscard)) enum E2 EIgnored;
71+
EIgnored get_e_ignored2();
72+
73+
void f4(void) {
74+
get_s_ignored();
75+
get_e_ignored();
76+
get_e_ignored2();
77+
}
78+
6879
[[nodiscard]] typedef int NoDInt; // expected-warning {{'[[nodiscard]]' attribute ignored when applied to a typedef}}
6980
typedef __attribute__((warn_unused)) int WUInt; // expected-warning {{'warn_unused' attribute only applies to structs, unions, and classes}}
7081
typedef __attribute__((warn_unused_result)) int WURInt;
82+
typedef __attribute__((candiscard)) WURInt WURIntIgnored;
7183
NoDInt get_nodint();
7284
WUInt get_wuint();
7385
WURInt get_wurint();
86+
WURIntIgnored get_wurint_ignored();
87+
WURIntIgnored get_wurint_ignored2() __attribute__((candiscard));
7488

75-
void f4(void) {
89+
void f5(void) {
7690
get_nodint(); // no warning because attribute is ignored
7791
get_wuint(); // no warning because attribute is ignored
7892
get_wurint(); // expected-warning {{ignoring return value of type 'WURInt' declared with 'warn_unused_result' attribute}}
93+
get_wurint_ignored(); // no warning
94+
get_wurint_ignored2(); // no warning
7995
}

clang/test/SemaCXX/warn-unused-result.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,19 +425,36 @@ struct [[gnu::warn_unused_result]] WarnUnusedResult {
425425
WarnUnusedResult(const char*);
426426
};
427427

428+
using NoDIgnored [[clang::candiscard]] = NoDiscard;
429+
using WUIgnored [[clang::candiscard]] = WarnUnused;
430+
using WURIgnored [[clang::candiscard]] = WarnUnusedResult;
431+
428432
NoDiscard return_nodiscard();
429433
WarnUnused return_warnunused();
430434
WarnUnusedResult return_warnunusedresult();
435+
NoDIgnored return_nodiscard_ignored();
436+
WUIgnored return_warnunused_ignored();
437+
WURIgnored return_warnunusedresult_ignored();
438+
[[clang::candiscard]] NoDiscard return_nodiscard_ignored2();
439+
[[clang::candiscard]] WarnUnused return_warnunused_ignored2();
440+
[[clang::candiscard]] WarnUnusedResult return_warnunusedresult_ignored2();
431441

432442
NoDiscard (*p_return_nodiscard)();
433443
WarnUnused (*p_return_warnunused)();
434444
WarnUnusedResult (*p_return_warnunusedresult)();
445+
NoDIgnored (*p_return_nodiscard_ignored)();
446+
WUIgnored (*p_return_warnunused_ignored)();
447+
WURIgnored (*p_return_warnunusedresult_ignored)();
448+
[[clang::candiscard]] NoDiscard (*p_return_nodiscard_ignored2)();
449+
[[clang::candiscard]] WarnUnused (*p_return_warnunused_ignored2)();
450+
[[clang::candiscard]] WarnUnusedResult (*p_return_warnunusedresult_ignored2)();
435451

436452
NoDiscard (*(*pp_return_nodiscard)())();
437453
WarnUnused (*(*pp_return_warnunused)())();
438454
WarnUnusedResult (*(*pp_return_warnunusedresult)())();
439455

440456
template <class T> T from_a_template();
457+
template <class T> [[clang::candiscard]] T from_a_template_ignored();
441458

442459
void test() {
443460
// Unused but named variables
@@ -474,11 +491,23 @@ void test() {
474491
return_nodiscard(); // expected-warning {{ignoring return value of type 'NoDiscard' declared with 'nodiscard' attribute}}
475492
return_warnunused(); // no warning
476493
return_warnunusedresult(); // expected-warning {{ignoring return value of type 'WarnUnusedResult' declared with 'gnu::warn_unused_result' attribute}}
494+
return_nodiscard_ignored(); // no warning
495+
return_warnunused_ignored(); // no warning
496+
return_warnunusedresult_ignored(); // no warning
497+
return_nodiscard_ignored2(); // no warning
498+
return_warnunused_ignored2(); // no warning
499+
return_warnunusedresult_ignored2(); // no warning
477500

478501
// Function pointer return values
479502
p_return_nodiscard(); // expected-warning {{ignoring return value of type 'NoDiscard' declared with 'nodiscard' attribute}}
480503
p_return_warnunused(); // no warning
481504
p_return_warnunusedresult(); // expected-warning {{ignoring return value of type 'WarnUnusedResult' declared with 'gnu::warn_unused_result' attribute}}
505+
p_return_nodiscard_ignored(); // no warning
506+
p_return_warnunused_ignored(); // no warning
507+
p_return_warnunusedresult_ignored(); // no warning
508+
p_return_nodiscard_ignored2(); // no warning
509+
p_return_warnunused_ignored2(); // no warning
510+
p_return_warnunusedresult_ignored2(); // no warning
482511

483512
// Function pointer expression return values
484513
pp_return_nodiscard()(); // expected-warning {{ignoring return value of type 'NoDiscard' declared with 'nodiscard' attribute}}
@@ -489,6 +518,16 @@ void test() {
489518
from_a_template<NoDiscard>(); // expected-warning {{ignoring return value of type 'NoDiscard' declared with 'nodiscard' attribute}}
490519
from_a_template<WarnUnused>(); // no warning
491520
from_a_template<WarnUnusedResult>(); // expected-warning {{ignoring return value of type 'WarnUnusedResult' declared with 'gnu::warn_unused_result' attribute}}
521+
522+
// In a template instantiation the information about the typedef is lost,
523+
// so the candiscard attribute is lost, so the diagnostic is not suppressed
524+
from_a_template<NoDIgnored>(); // expected-warning {{ignoring return value of type 'NoDiscard' declared with 'nodiscard' attribute}}
525+
from_a_template<WUIgnored>(); // no warning
526+
from_a_template<WURIgnored>(); // expected-warning {{ignoring return value of type 'WarnUnusedResult' declared with 'gnu::warn_unused_result' attribute}}
527+
528+
from_a_template_ignored<NoDiscard>(); // no warning
529+
from_a_template_ignored<WarnUnused>(); // no warning
530+
from_a_template_ignored<WarnUnusedResult>(); // no warning
492531
}
493532

494533
} // namespace candiscard

clang/test/SemaObjC/attr-nodiscard.m

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,24 @@
66

77
[[nodiscard]] typedef int NI; // expected-warning {{'[[nodiscard]]' attribute ignored when applied to a typedef}}
88
typedef __attribute__((warn_unused_result)) int WUR;
9+
typedef __attribute__((candiscard)) struct expected EIgnored;
10+
typedef __attribute__((candiscard)) WUR WURIgnored;
911

1012
@interface INTF
1113
- (int) a [[nodiscard]];
1214
+ (int) b [[nodiscard]];
1315
- (struct expected) c;
1416
+ (struct expected) d;
1517
- (E) e;
18+
- (EIgnored) e_ignored;
19+
- (E) e_ignored2 __attribute__((candiscard));
1620
+ (E) f;
1721
- (void) g [[nodiscard]]; // expected-warning {{attribute 'nodiscard' cannot be applied to Objective-C method without return value}}
1822
- (NI) h;
23+
- (NI) h_ignored __attribute__((candiscard));
1924
- (WUR) i;
25+
- (WURIgnored) i_ignored;
26+
- (WUR) i_ignored2 __attribute__((candiscard));
2027
@end
2128

2229
void foo(INTF *a) {
@@ -28,5 +35,8 @@ void foo(INTF *a) {
2835
[INTF f]; // expected-warning {{ignoring return value of type 'expected' declared with 'nodiscard' attribute}}
2936
[a g]; // no warning because g returns void
3037
[a h]; // no warning because attribute is ignored when applied to a typedef
38+
[a h_ignored]; // no warning
3139
[a i]; // expected-warning {{ignoring return value of type 'WUR' declared with 'warn_unused_result' attribute}}
40+
[a i_ignored]; // no warning
41+
[a i_ignored2]; // no warning
3242
}

clang/test/SemaObjCXX/attr-nodiscard.mm

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,26 @@
88
using NI [[nodiscard]] = int; // expected-warning {{'[[nodiscard]]' attribute ignored when applied to a typedef}}
99
using WURI [[clang::warn_unused_result]] = int;
1010

11+
using EIgnored [[clang::candiscard]] = E;
12+
using NIIgnored [[clang::candiscard]] = NI;
13+
using WURIgnored [[clang::candiscard]] = WURI;
14+
1115
@interface INTF
1216
- (int) a [[nodiscard]];
1317
+ (int) b [[nodiscard]];
1418
- (expected<int>) c;
1519
+ (expected<int>) d;
1620
- (E) e;
21+
- (EIgnored) e_ignored;
22+
- (E) e_ignored2 [[clang::candiscard]];
1723
+ (E) f;
1824
- (void) g [[nodiscard]]; // expected-warning {{attribute 'nodiscard' cannot be applied to Objective-C method without return value}}
1925
- (NI) h;
26+
- (NIIgnored) h_ignored;
27+
- (NI) h_ignored2 [[clang::candiscard]];
2028
- (WURI) i;
29+
- (WURIgnored) i_ignored;
30+
- (WURI) i_ignored2 [[clang::candiscard]];
2131
@end
2232

2333
void foo(INTF *a) {
@@ -26,8 +36,14 @@ void foo(INTF *a) {
2636
[a c]; // expected-warning {{ignoring return value of type 'expected<int>' declared with 'nodiscard' attribute}}
2737
[INTF d]; // expected-warning {{ignoring return value of type 'expected<int>' declared with 'nodiscard' attribute}}
2838
[a e]; // expected-warning {{ignoring return value of type 'expected<int>' declared with 'nodiscard' attribute}}
39+
[a e_ignored]; // no warning
40+
[a e_ignored2]; // no warning
2941
[INTF f]; // expected-warning {{ignoring return value of type 'expected<int>' declared with 'nodiscard' attribute}}
3042
[a g]; // no warning because g returns void
3143
[a h]; // no warning because attribute is ignored
44+
[a h_ignored]; // no warning
45+
[a h_ignored2]; // no warning
3246
[a i]; // expected-warning {{ignoring return value of type 'WURI' declared with 'clang::warn_unused_result' attribute}}
47+
[a i_ignored]; // no warning
48+
[a i_ignored2]; // no warning
3349
}

0 commit comments

Comments
 (0)