Skip to content

Commit 6207a03

Browse files
Mick235711Sirraide
authored andcommitted
[Clang] Improve diagnostic on [[nodiscard]] attribute (llvm#112521)
A follow-up to llvm#112289. When diagnosing an unused return value, if the diagnostic is triggered by an attribute attached to a type, the type name is now included in the diagnostic. --------- Co-authored-by: Sirraide <[email protected]>
1 parent ff01bdb commit 6207a03

File tree

9 files changed

+106
-85
lines changed

9 files changed

+106
-85
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,22 @@ Improvements to Clang's diagnostics
298298

299299
- Improve the ``-Wundefined-func-template`` warning when a function template is not instantiated due to being unreachable in modules.
300300

301+
- When diagnosing an unused return value of a type declared ``[[nodiscard]]``, the type
302+
itself is now included in the diagnostic.
303+
304+
- Clang will now prefer the ``[[nodiscard]]`` declaration on function declarations over ``[[nodiscard]]``
305+
declaration on the return type of a function. Previously, when both have a ``[[nodiscard]]`` declaration attached,
306+
the one on the return type would be preferred. This may affect the generated warning message:
307+
308+
.. code-block:: c++
309+
310+
struct [[nodiscard("Reason 1")]] S {};
311+
[[nodiscard("Reason 2")]] S getS();
312+
void use()
313+
{
314+
getS(); // Now diagnoses "Reason 2", previously diagnoses "Reason 1"
315+
}
316+
301317
Improvements to Clang's time-trace
302318
----------------------------------
303319

clang/include/clang/AST/Expr.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3182,12 +3182,14 @@ class CallExpr : public Expr {
31823182
QualType getCallReturnType(const ASTContext &Ctx) const;
31833183

31843184
/// Returns the WarnUnusedResultAttr that is either declared on the called
3185-
/// function, or its return type declaration.
3186-
const Attr *getUnusedResultAttr(const ASTContext &Ctx) const;
3185+
/// function, or its return type declaration, together with a NamedDecl that
3186+
/// refers to the declaration the attribute is attached onto.
3187+
std::pair<const NamedDecl *, const Attr *>
3188+
getUnusedResultAttr(const ASTContext &Ctx) const;
31873189

31883190
/// Returns true if this call expression should warn on unused results.
31893191
bool hasUnusedResultAttr(const ASTContext &Ctx) const {
3190-
return getUnusedResultAttr(Ctx) != nullptr;
3192+
return getUnusedResultAttr(Ctx).second != nullptr;
31913193
}
31923194

31933195
SourceLocation getRParenLoc() const { return RParenLoc; }

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9348,11 +9348,11 @@ def warn_unused_container_subscript_expr : Warning<
93489348
def warn_unused_call : Warning<
93499349
"ignoring return value of function declared with %0 attribute">,
93509350
InGroup<UnusedValue>;
9351-
def warn_unused_constructor : Warning<
9352-
"ignoring temporary created by a constructor declared with %0 attribute">,
9351+
def warn_unused_return_type : Warning<
9352+
"ignoring %select{return value|temporary}0 of type %2 declared with %1 attribute%select{|: %4}3">,
93539353
InGroup<UnusedValue>;
9354-
def warn_unused_constructor_msg : Warning<
9355-
"ignoring temporary created by a constructor declared with %0 attribute: %1">,
9354+
def warn_unused_constructor : Warning<
9355+
"ignoring temporary created by a constructor declared with %0 attribute%select{|: %2}1">,
93569356
InGroup<UnusedValue>;
93579357
def warn_side_effects_unevaluated_context : Warning<
93589358
"expression with side effects has no effect in an unevaluated context">,
@@ -9361,10 +9361,7 @@ def warn_side_effects_typeid : Warning<
93619361
"expression with side effects will be evaluated despite being used as an "
93629362
"operand to 'typeid'">, InGroup<PotentiallyEvaluatedExpression>;
93639363
def warn_unused_result : Warning<
9364-
"ignoring return value of function declared with %0 attribute">,
9365-
InGroup<UnusedResult>;
9366-
def warn_unused_result_msg : Warning<
9367-
"ignoring return value of function declared with %0 attribute: %1">,
9364+
"ignoring return value of function declared with %0 attribute%select{|: %2}1">,
93689365
InGroup<UnusedResult>;
93699366
def warn_unused_result_typedef_unsupported_spelling : Warning<
93709367
"'[[%select{nodiscard|gnu::warn_unused_result}0]]' attribute ignored when "

clang/lib/AST/Expr.cpp

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1618,22 +1618,24 @@ QualType CallExpr::getCallReturnType(const ASTContext &Ctx) const {
16181618
return FnType->getReturnType();
16191619
}
16201620

1621-
const Attr *CallExpr::getUnusedResultAttr(const ASTContext &Ctx) const {
1621+
std::pair<const NamedDecl *, const Attr *>
1622+
CallExpr::getUnusedResultAttr(const ASTContext &Ctx) const {
1623+
// If the callee is marked nodiscard, return that attribute
1624+
const Decl *D = getCalleeDecl();
1625+
if (const auto *A = D->getAttr<WarnUnusedResultAttr>())
1626+
return {nullptr, A};
1627+
16221628
// If the return type is a struct, union, or enum that is marked nodiscard,
16231629
// then return the return type attribute.
16241630
if (const TagDecl *TD = getCallReturnType(Ctx)->getAsTagDecl())
16251631
if (const auto *A = TD->getAttr<WarnUnusedResultAttr>())
1626-
return A;
1632+
return {TD, A};
16271633

16281634
for (const auto *TD = getCallReturnType(Ctx)->getAs<TypedefType>(); TD;
16291635
TD = TD->desugar()->getAs<TypedefType>())
16301636
if (const auto *A = TD->getDecl()->getAttr<WarnUnusedResultAttr>())
1631-
return A;
1632-
1633-
// Otherwise, see if the callee is marked nodiscard and return that attribute
1634-
// instead.
1635-
const Decl *D = getCalleeDecl();
1636-
return D ? D->getAttr<WarnUnusedResultAttr>() : nullptr;
1637+
return {TD->getDecl(), A};
1638+
return {nullptr, nullptr};
16371639
}
16381640

16391641
SourceLocation CallExpr::getBeginLoc() const {

clang/lib/Sema/SemaStmt.cpp

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -200,23 +200,30 @@ static bool DiagnoseUnusedComparison(Sema &S, const Expr *E) {
200200
return true;
201201
}
202202

203-
static bool DiagnoseNoDiscard(Sema &S, const WarnUnusedResultAttr *A,
204-
SourceLocation Loc, SourceRange R1,
205-
SourceRange R2, bool IsCtor) {
203+
static bool DiagnoseNoDiscard(Sema &S, const NamedDecl *OffendingDecl,
204+
const WarnUnusedResultAttr *A, SourceLocation Loc,
205+
SourceRange R1, SourceRange R2, bool IsCtor) {
206206
if (!A)
207207
return false;
208208
StringRef Msg = A->getMessage();
209209

210210
if (Msg.empty()) {
211+
if (OffendingDecl)
212+
return S.Diag(Loc, diag::warn_unused_return_type)
213+
<< IsCtor << A << OffendingDecl << false << R1 << R2;
211214
if (IsCtor)
212-
return S.Diag(Loc, diag::warn_unused_constructor) << A << R1 << R2;
213-
return S.Diag(Loc, diag::warn_unused_result) << A << R1 << R2;
215+
return S.Diag(Loc, diag::warn_unused_constructor)
216+
<< A << false << R1 << R2;
217+
return S.Diag(Loc, diag::warn_unused_result) << A << false << R1 << R2;
214218
}
215219

220+
if (OffendingDecl)
221+
return S.Diag(Loc, diag::warn_unused_return_type)
222+
<< IsCtor << A << OffendingDecl << true << Msg << R1 << R2;
216223
if (IsCtor)
217-
return S.Diag(Loc, diag::warn_unused_constructor_msg) << A << Msg << R1
218-
<< R2;
219-
return S.Diag(Loc, diag::warn_unused_result_msg) << A << Msg << R1 << R2;
224+
return S.Diag(Loc, diag::warn_unused_constructor)
225+
<< A << true << Msg << R1 << R2;
226+
return S.Diag(Loc, diag::warn_unused_result) << A << true << Msg << R1 << R2;
220227
}
221228

222229
namespace {
@@ -288,9 +295,10 @@ void DiagnoseUnused(Sema &S, const Expr *E, std::optional<unsigned> DiagID) {
288295
if (E->getType()->isVoidType())
289296
return;
290297

291-
if (DiagnoseNoDiscard(S, cast_or_null<WarnUnusedResultAttr>(
292-
CE->getUnusedResultAttr(S.Context)),
293-
Loc, R1, R2, /*isCtor=*/false))
298+
auto [OffendingDecl, A] = CE->getUnusedResultAttr(S.Context);
299+
if (DiagnoseNoDiscard(S, OffendingDecl,
300+
cast_or_null<WarnUnusedResultAttr>(A), Loc, R1, R2,
301+
/*isCtor=*/false))
294302
return;
295303

296304
// If the callee has attribute pure, const, or warn_unused_result, warn with
@@ -311,15 +319,20 @@ void DiagnoseUnused(Sema &S, const Expr *E, std::optional<unsigned> DiagID) {
311319
}
312320
} else if (const auto *CE = dyn_cast<CXXConstructExpr>(E)) {
313321
if (const CXXConstructorDecl *Ctor = CE->getConstructor()) {
322+
const NamedDecl *OffendingDecl = nullptr;
314323
const auto *A = Ctor->getAttr<WarnUnusedResultAttr>();
315-
A = A ? A : Ctor->getParent()->getAttr<WarnUnusedResultAttr>();
316-
if (DiagnoseNoDiscard(S, A, Loc, R1, R2, /*isCtor=*/true))
324+
if (!A) {
325+
OffendingDecl = Ctor->getParent();
326+
A = OffendingDecl->getAttr<WarnUnusedResultAttr>();
327+
}
328+
if (DiagnoseNoDiscard(S, OffendingDecl, A, Loc, R1, R2,
329+
/*isCtor=*/true))
317330
return;
318331
}
319332
} else if (const auto *ILE = dyn_cast<InitListExpr>(E)) {
320333
if (const TagDecl *TD = ILE->getType()->getAsTagDecl()) {
321334

322-
if (DiagnoseNoDiscard(S, TD->getAttr<WarnUnusedResultAttr>(), Loc, R1,
335+
if (DiagnoseNoDiscard(S, TD, TD->getAttr<WarnUnusedResultAttr>(), Loc, R1,
323336
R2, /*isCtor=*/false))
324337
return;
325338
}
@@ -334,8 +347,9 @@ void DiagnoseUnused(Sema &S, const Expr *E, std::optional<unsigned> DiagID) {
334347
}
335348
const ObjCMethodDecl *MD = ME->getMethodDecl();
336349
if (MD) {
337-
if (DiagnoseNoDiscard(S, MD->getAttr<WarnUnusedResultAttr>(), Loc, R1,
338-
R2, /*isCtor=*/false))
350+
if (DiagnoseNoDiscard(S, nullptr, MD->getAttr<WarnUnusedResultAttr>(),
351+
Loc, R1, R2,
352+
/*isCtor=*/false))
339353
return;
340354
}
341355
} else if (const PseudoObjectExpr *POE = dyn_cast<PseudoObjectExpr>(E)) {

clang/test/CXX/dcl.dcl/dcl.attr/dcl.attr.nodiscard/p2.cpp

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ E get_e();
1818
// cxx11-warning@-1 {{use of the 'nodiscard' attribute is a C++17 extension}}
1919

2020
void f() {
21-
get_s(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
21+
get_s(); // expected-warning {{ignoring return value of type 'S' declared with 'nodiscard' attribute}}
2222
get_i(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
2323
get_vi(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
24-
get_e(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
24+
get_e(); // expected-warning {{ignoring return value of type 'E' declared with 'nodiscard' attribute}}
2525

2626
// Okay, warnings are not encouraged
2727
get_s_ref();
@@ -55,10 +55,10 @@ void f() {
5555
fp3 three;
5656
fp2_alias four;
5757

58-
one(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
59-
two(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
60-
three(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
61-
four(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
58+
one(); // expected-warning {{ignoring return value of type 'E' declared with 'nodiscard' attribute}}
59+
two(); // expected-warning {{ignoring return value of type 'S' declared with 'nodiscard' attribute}}
60+
three(); // expected-warning {{ignoring return value of type 'S' declared with 'nodiscard' attribute}}
61+
four(); // expected-warning {{ignoring return value of type 'S' declared with 'nodiscard' attribute}}
6262

6363
// These are all okay because of the explicit cast to void.
6464
(void)one();
@@ -85,8 +85,8 @@ LaterReason get_later_reason();
8585
// cxx11-17-warning@-1 {{use of the 'nodiscard' attribute is a C++20 extension}}
8686

8787
void cxx20_use() {
88-
get_reason(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute: reason}}
89-
get_later_reason(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute: later reason}}
88+
get_reason(); // expected-warning {{ignoring return value of type 'ReasonStruct' declared with 'nodiscard' attribute: reason}}
89+
get_later_reason(); // expected-warning {{ignoring return value of type 'LaterReason' declared with 'nodiscard' attribute: later reason}}
9090
another_reason(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute: another reason}}
9191
conflicting_reason(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute: special reason}}
9292
}
@@ -116,20 +116,20 @@ void usage() {
116116
S('A'); // expected-warning {{ignoring temporary created by a constructor declared with 'nodiscard' attribute: Don't let that S-Char go!}}
117117
S(1);
118118
S(2.2);
119-
Y(); // expected-warning {{ignoring temporary created by a constructor declared with 'nodiscard' attribute: Don't throw me away either!}}
119+
Y(); // expected-warning {{ignoring temporary of type 'Y' declared with 'nodiscard' attribute: Don't throw me away either!}}
120120
S s;
121-
ConvertTo{}; // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute: Don't throw me away!}}
121+
ConvertTo{}; // expected-warning {{ignoring return value of type 'ConvertTo' declared with 'nodiscard' attribute: Don't throw me away!}}
122122

123123
// AST is different in C++17 mode. Before, a move ctor for ConvertTo is there
124124
// as well, hence the constructor warning.
125125

126-
// since-cxx17-warning@+2 {{ignoring return value of function declared with 'nodiscard' attribute: Don't throw me away!}}
127-
// cxx11-warning@+1 {{ignoring temporary created by a constructor declared with 'nodiscard' attribute: Don't throw me away!}}
126+
// since-cxx17-warning@+2 {{ignoring return value of type 'ConvertTo' declared with 'nodiscard' attribute: Don't throw me away!}}
127+
// cxx11-warning@+1 {{ignoring temporary of type 'ConvertTo' declared with 'nodiscard' attribute: Don't throw me away!}}
128128
(ConvertTo) s;
129129
(int)s; // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
130130
(S)'c'; // expected-warning {{ignoring temporary created by a constructor declared with 'nodiscard' attribute: Don't let that S-Char go!}}
131-
// since-cxx17-warning@+2 {{ignoring return value of function declared with 'nodiscard' attribute: Don't throw me away!}}
132-
// cxx11-warning@+1 {{ignoring temporary created by a constructor declared with 'nodiscard' attribute: Don't throw me away!}}
131+
// since-cxx17-warning@+2 {{ignoring return value of type 'ConvertTo' declared with 'nodiscard' attribute: Don't throw me away!}}
132+
// cxx11-warning@+1 {{ignoring temporary of type 'ConvertTo' declared with 'nodiscard' attribute: Don't throw me away!}}
133133
static_cast<ConvertTo>(s);
134134
static_cast<int>(s); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
135135
static_cast<double>(s); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute: Don't throw away as a double}}

clang/test/CXX/dcl.dcl/dcl.attr/dcl.attr.nodiscard/p3.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ namespace std_example {
88
error_info enable_missile_safety_mode();
99
void launch_missiles();
1010
void test_missiles() {
11-
enable_missile_safety_mode(); // expected-warning {{ignoring return value of function declared with 'nodiscard'}}
11+
enable_missile_safety_mode(); // expected-warning {{ignoring return value of type 'error_info' declared with 'nodiscard'}}
1212
launch_missiles();
1313
}
1414

clang/test/Sema/c2x-nodiscard.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,10 @@ enum E2 get_e(void);
3131
[[nodiscard]] int get_i(void);
3232

3333
void f2(void) {
34-
get_s(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
35-
get_s3(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute: Wrong}}
34+
get_s(); // expected-warning {{ignoring return value of type 'S4' declared with 'nodiscard' attribute}}
35+
get_s3(); // expected-warning {{ignoring return value of type 'S3' declared with 'nodiscard' attribute: Wrong}}
3636
get_i(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
37-
get_e(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
37+
get_e(); // expected-warning {{ignoring return value of type 'E2' declared with 'nodiscard' attribute}}
3838

3939
// Okay, warnings are not encouraged
4040
(void)get_s();
@@ -50,7 +50,7 @@ struct [[nodiscard]] error_info{
5050
struct error_info enable_missile_safety_mode(void);
5151
void launch_missiles(void);
5252
void test_missiles(void) {
53-
enable_missile_safety_mode(); // expected-warning {{ignoring return value of function declared with 'nodiscard'}}
53+
enable_missile_safety_mode(); // expected-warning {{ignoring return value of type 'error_info' declared with 'nodiscard'}}
5454
launch_missiles();
5555
}
5656

0 commit comments

Comments
 (0)