Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think of a shorter name [[clang::discard]]? I have no preference for this。

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO discardable would be a good name, but I'm not super attached to any of the names so far.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

discardable looks good to me.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where did we land on a name? I'm not particularly attached to anything, but this discussion happened and I didn't see any response to it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m not aware of any discussion but perhaps I just missed it; discardable sgtm though.

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
-----------------------------------
Expand Down
7 changes: 7 additions & 0 deletions clang/include/clang/Basic/Attr.td
Original file line number Diff line number Diff line change
Expand Up @@ -3646,6 +3646,13 @@ def Unavailable : InheritableAttr {
let MeaningfulToClassTemplateDefinition = 1;
}

def CanDiscard : InheritableAttr {
let Spellings = [Clang<"candiscard">];
let Subjects = SubjectList<[ObjCMethod, FunctionLike, TypedefName]>;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we sure about this subject list? ObjCMethod seems odd here, and FunctionLike ALSO allows this to be added to function/mem function pointers/references/etc. I suspect this whole list should just be Function + TypedefName (though maybe Typedef and TypeAlias, as including Objective C ParamDecls seems odd here).

IF you leave any of the above, please make sure that ALL of the FunctionLike has well defined semantics/etc, and that the ObjectiveC types are tested/have well defined semantics.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the same subject list as [[nodiscard]]. Functions and function pointers are tested. What is the "etc."?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See: https://clang.llvm.org/doxygen/DeclBase_8cpp_source.html#l01188

IF you want to support all of those, we need to see tests for all of those.I was trying to help limit work by making this apply to functions ONLY, that way we can expand it if/when someone requests it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have added tests for member function pointers. Function references should work the same as functions and function pointers. I could not find any tests for warn_unused_result or nodiscard applied to blocks... should I start a new test.cpp file for blocks?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that seems reasonable. We should still have function reference tests all the same. IMO, I'm not sure the value is there for having this on pointers/references though.

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
Expand Down
38 changes: 38 additions & 0 deletions clang/include/clang/Basic/AttrDocs.td
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Comment on lines +2459 to +2463
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the motivation for exposing it as as a gnu attribute? afaik GCC does not have this behavior


.. 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";
Expand Down
31 changes: 22 additions & 9 deletions clang/lib/AST/Expr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>()) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make sure we have tests where this is done on a template alias? We want to make sure semantics for this are sensible there too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug #68456 prevents these attributes from working on alias templates

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};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
18 changes: 17 additions & 1 deletion clang/test/Sema/c2x-nodiscard.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
130 changes: 123 additions & 7 deletions clang/test/SemaCXX/warn-unused-result.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <class T> T from_a_template();
template <class T> [[clang::candiscard]] T from_a_template_ignored();

void test() {
struct A {
using NoDInt [[clang::warn_unused_result]] = int;
struct [[nodiscard]] NoDClass {};
template <class T> using NoDTemplate [[clang::warn_unused_result]] = T;
};

template <class T>
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 <class U> using NoDInt2 = typename U::NoDInt;
template <class U> using NoDIntIgnored2 [[clang::candiscard]] = typename U::NoDInt;
template <class U> using NoDClass2 = typename U::NoDClass;
template <class U> using NoDClassIgnored2 [[clang::candiscard]] = typename U::NoDClass;
};

A::NoDTemplate<int> return_nodtemplate();
B<A>::NoDInt return_nodint();
B<A>::NoDIntIgnored return_nodint_ignored();
B<A>::NoDClass return_nodclass();
B<A>::NoDClassIgnored return_nodclass_ignored();
B<A>::NoDInt2<A> return_nodint2();
B<A>::NoDIntIgnored2<A> return_nodint_ignored2();
B<A>::NoDClass2<A> return_nodclass2();
B<A>::NoDClassIgnored2<A> 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
Expand Down Expand Up @@ -471,14 +526,37 @@ void test() {
static_cast<WarnUnusedResult>(""); // 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}}
Expand All @@ -489,6 +567,44 @@ 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

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
Comment on lines +605 to +606
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why though? Have you considered warning in this case?

return_nodignored_nodiscard(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
}

} // namespace candiscard
10 changes: 10 additions & 0 deletions clang/test/SemaObjC/attr-nodiscard.m
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,24 @@

[[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]];
+ (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) {
Expand All @@ -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
}
16 changes: 16 additions & 0 deletions clang/test/SemaObjCXX/attr-nodiscard.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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
}