Skip to content

Commit c601be9

Browse files
usx95ChuanqiXu9
andauthored
[coroutines] Introduce [[clang::coro_return_type]] and [[clang::coro_wrapper]] (#71945)
First step in the implementation of [RFC](https://discourse.llvm.org/t/rfc-lifetime-bound-check-for-parameters-of-coroutines/74253) ([final approved doc](https://docs.google.com/document/d/1hkfXHuvIW1Yv5LI-EIkpWzdWgIoUlzO6Zv_KJpknQzM/edit)). This introduces the concepts of a **coroutine return type** and explicit **coroutine wrapper** functions. --------- Co-authored-by: Chuanqi Xu <[email protected]>
1 parent 2310066 commit c601be9

File tree

8 files changed

+247
-2
lines changed

8 files changed

+247
-2
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,11 @@ Attribute Changes in Clang
306306
to reduce the size of the destroy functions for coroutines which are known to
307307
be destroyed after having reached the final suspend point.
308308

309+
- Clang now introduced ``[[clang::coro_return_type]]`` and ``[[clang::coro_wrapper]]``
310+
attributes. A function returning a type marked with ``[[clang::coro_return_type]]``
311+
should be a coroutine. A non-coroutine function marked with ``[[clang::coro_wrapper]]``
312+
is still allowed to return the such a type. This is helpful for analyzers to recognize coroutines from the function signatures.
313+
309314
Improvements to Clang's diagnostics
310315
-----------------------------------
311316
- Clang constexpr evaluator now prints template arguments when displaying

clang/include/clang/Basic/Attr.td

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1094,6 +1094,22 @@ def CoroOnlyDestroyWhenComplete : InheritableAttr {
10941094
let SimpleHandler = 1;
10951095
}
10961096

1097+
def CoroReturnType : InheritableAttr {
1098+
let Spellings = [Clang<"coro_return_type">];
1099+
let Subjects = SubjectList<[CXXRecord]>;
1100+
let LangOpts = [CPlusPlus];
1101+
let Documentation = [CoroReturnTypeAndWrapperDoc];
1102+
let SimpleHandler = 1;
1103+
}
1104+
1105+
def CoroWrapper : InheritableAttr {
1106+
let Spellings = [Clang<"coro_wrapper">];
1107+
let Subjects = SubjectList<[Function]>;
1108+
let LangOpts = [CPlusPlus];
1109+
let Documentation = [CoroReturnTypeAndWrapperDoc];
1110+
let SimpleHandler = 1;
1111+
}
1112+
10971113
// OSObject-based attributes.
10981114
def OSConsumed : InheritableParamAttr {
10991115
let Spellings = [Clang<"os_consumed">];

clang/include/clang/Basic/AttrDocs.td

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7482,3 +7482,61 @@ generation of the other destruction cases, optimizing the above `foo.destroy` to
74827482

74837483
}];
74847484
}
7485+
7486+
7487+
def CoroReturnTypeAndWrapperDoc : Documentation {
7488+
let Category = DocCatDecl;
7489+
let Content = [{
7490+
The ``[[clang::coro_return_type]]`` attribute is used to help static analyzers to recognize
7491+
coroutines from the function signatures.
7492+
7493+
The ``coro_return_type`` attribute should be marked on a C++ class to mark it as
7494+
a **coroutine return type (CRT)**.
7495+
7496+
A function ``R func(P1, .., PN)`` has a coroutine return type (CRT) ``R`` if ``R``
7497+
is marked by ``[[clang::coro_return_type]]`` and ``R`` has a promise type associated to it
7498+
(i.e., std​::​coroutine_traits<R, P1, .., PN>​::​promise_type is a valid promise type).
7499+
7500+
If the return type of a function is a ``CRT`` then the function must be a coroutine.
7501+
Otherwise the program is invalid. It is allowed for a non-coroutine to return a ``CRT``
7502+
if the function is marked with ``[[clang::coro_wrapper]]``.
7503+
7504+
The ``[[clang::coro_wrapper]]`` attribute should be marked on a C++ function to mark it as
7505+
a **coroutine wrapper**. A coroutine wrapper is a function which returns a ``CRT``,
7506+
is not a coroutine itself and is marked with ``[[clang::coro_wrapper]]``.
7507+
7508+
Clang will enforce that all functions that return a ``CRT`` are either coroutines or marked
7509+
with ``[[clang::coro_wrapper]]``. Clang will enforce this with an error.
7510+
7511+
From a language perspective, it is not possible to differentiate between a coroutine and a
7512+
function returning a CRT by merely looking at the function signature.
7513+
7514+
Coroutine wrappers, in particular, are susceptible to capturing
7515+
references to temporaries and other lifetime issues. This allows to avoid such lifetime
7516+
issues with coroutine wrappers.
7517+
7518+
For example,
7519+
7520+
.. code-block:: c++
7521+
7522+
// This is a CRT.
7523+
template <typename T> struct [[clang::coro_return_type]] Task {
7524+
using promise_type = some_promise_type;
7525+
};
7526+
7527+
Task<int> increment(int a) { co_return a + 1; } // Fine. This is a coroutine.
7528+
Task<int> foo() { return increment(1); } // Error. foo is not a coroutine.
7529+
7530+
// Fine for a coroutine wrapper to return a CRT.
7531+
Task<int> [[clang::coro_wrapper]] foo() { return increment(1); }
7532+
7533+
void bar() {
7534+
// Invalid. This intantiates a function which returns a CRT but is not marked as
7535+
// a coroutine wrapper.
7536+
std::function<Task<int>(int)> f = increment;
7537+
}
7538+
7539+
Note: ``a_promise_type::get_return_object`` is exempted from this analysis as it is a necessary
7540+
implementation detail of any coroutine library.
7541+
}];
7542+
}

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11591,6 +11591,10 @@ def err_conflicting_aligned_options : Error <
1159111591
def err_coro_invalid_addr_of_label : Error<
1159211592
"the GNU address of label extension is not allowed in coroutines."
1159311593
>;
11594+
def err_coroutine_return_type : Error<
11595+
"function returns a type %0 makred with [[clang::coro_return_type]] but is neither a coroutine nor a coroutine wrapper; "
11596+
"non-coroutines should be marked with [[clang::coro_wrapper]] to allow returning coroutine return type"
11597+
>;
1159411598
} // end of coroutines issue category
1159511599

1159611600
let CategoryName = "Documentation Issue" in {

clang/include/clang/Sema/Sema.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11189,6 +11189,12 @@ class Sema final {
1118911189
bool buildCoroutineParameterMoves(SourceLocation Loc);
1119011190
VarDecl *buildCoroutinePromise(SourceLocation Loc);
1119111191
void CheckCompletedCoroutineBody(FunctionDecl *FD, Stmt *&Body);
11192+
11193+
// As a clang extension, enforces that a non-coroutine function must be marked
11194+
// with [[clang::coro_wrapper]] if it returns a type marked with
11195+
// [[clang::coro_return_type]].
11196+
// Expects that FD is not a coroutine.
11197+
void CheckCoroutineWrapper(FunctionDecl *FD);
1119211198
/// Lookup 'coroutine_traits' in std namespace and std::experimental
1119311199
/// namespace. The namespace found is recorded in Namespace.
1119411200
ClassTemplateDecl *lookupCoroutineTraits(SourceLocation KwLoc,

clang/lib/Sema/SemaDecl.cpp

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "clang/AST/CXXInheritance.h"
1818
#include "clang/AST/CharUnits.h"
1919
#include "clang/AST/CommentDiagnostic.h"
20+
#include "clang/AST/Decl.h"
2021
#include "clang/AST/DeclCXX.h"
2122
#include "clang/AST/DeclObjC.h"
2223
#include "clang/AST/DeclTemplate.h"
@@ -26,6 +27,7 @@
2627
#include "clang/AST/NonTrivialTypeVisitor.h"
2728
#include "clang/AST/Randstruct.h"
2829
#include "clang/AST/StmtCXX.h"
30+
#include "clang/AST/Type.h"
2931
#include "clang/Basic/Builtins.h"
3032
#include "clang/Basic/HLSLRuntime.h"
3133
#include "clang/Basic/PartialDiagnostic.h"
@@ -15814,6 +15816,20 @@ static void diagnoseImplicitlyRetainedSelf(Sema &S) {
1581415816
<< FixItHint::CreateInsertion(P.first, "self->");
1581515817
}
1581615818

15819+
void Sema::CheckCoroutineWrapper(FunctionDecl *FD) {
15820+
if (!FD)
15821+
return;
15822+
RecordDecl *RD = FD->getReturnType()->getAsRecordDecl();
15823+
if (!RD || !RD->getUnderlyingDecl()->hasAttr<CoroReturnTypeAttr>())
15824+
return;
15825+
// Allow `get_return_object()`.
15826+
if (FD->getDeclName().isIdentifier() &&
15827+
FD->getName().equals("get_return_object") && FD->param_empty())
15828+
return;
15829+
if (!FD->hasAttr<CoroWrapperAttr>())
15830+
Diag(FD->getLocation(), diag::err_coroutine_return_type) << RD;
15831+
}
15832+
1581715833
Decl *Sema::ActOnFinishFunctionBody(Decl *dcl, Stmt *Body,
1581815834
bool IsInstantiation) {
1581915835
FunctionScopeInfo *FSI = getCurFunction();
@@ -15825,8 +15841,12 @@ Decl *Sema::ActOnFinishFunctionBody(Decl *dcl, Stmt *Body,
1582515841
sema::AnalysisBasedWarnings::Policy WP = AnalysisWarnings.getDefaultPolicy();
1582615842
sema::AnalysisBasedWarnings::Policy *ActivePolicy = nullptr;
1582715843

15828-
if (getLangOpts().Coroutines && FSI->isCoroutine())
15829-
CheckCompletedCoroutineBody(FD, Body);
15844+
if (getLangOpts().Coroutines) {
15845+
if (FSI->isCoroutine())
15846+
CheckCompletedCoroutineBody(FD, Body);
15847+
else
15848+
CheckCoroutineWrapper(FD);
15849+
}
1583015850

1583115851
{
1583215852
// Do not call PopExpressionEvaluationContext() if it is a lambda because

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@
5757
// CHECK-NEXT: ConsumableSetOnRead (SubjectMatchRule_record)
5858
// CHECK-NEXT: Convergent (SubjectMatchRule_function)
5959
// CHECK-NEXT: CoroOnlyDestroyWhenComplete (SubjectMatchRule_record)
60+
// CHECK-NEXT: CoroReturnType (SubjectMatchRule_record)
61+
// CHECK-NEXT: CoroWrapper
6062
// CHECK-NEXT: CountedBy (SubjectMatchRule_field)
6163
// CHECK-NEXT: DLLExport (SubjectMatchRule_function, SubjectMatchRule_variable, SubjectMatchRule_record, SubjectMatchRule_objc_interface)
6264
// CHECK-NEXT: DLLImport (SubjectMatchRule_function, SubjectMatchRule_variable, SubjectMatchRule_record, SubjectMatchRule_objc_interface)
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// RUN: %clang_cc1 -triple x86_64-apple-darwin9 %s -std=c++20 -fsyntax-only -verify -Wall -Wextra
2+
#include "Inputs/std-coroutine.h"
3+
4+
using std::suspend_always;
5+
using std::suspend_never;
6+
7+
8+
template <typename T> struct [[clang::coro_return_type]] Gen {
9+
struct promise_type {
10+
Gen<T> get_return_object() {
11+
return {};
12+
}
13+
suspend_always initial_suspend();
14+
suspend_always final_suspend() noexcept;
15+
void unhandled_exception();
16+
void return_value(T t);
17+
18+
template <typename U>
19+
auto await_transform(const Gen<U> &) {
20+
struct awaitable {
21+
bool await_ready() noexcept { return false; }
22+
void await_suspend(std::coroutine_handle<>) noexcept {}
23+
U await_resume() noexcept { return {}; }
24+
};
25+
return awaitable{};
26+
}
27+
};
28+
};
29+
30+
Gen<int> foo_coro(int b);
31+
Gen<int> foo_coro(int b) { co_return b; }
32+
33+
[[clang::coro_wrapper]] Gen<int> marked_wrapper1(int b) { return foo_coro(b); }
34+
35+
// expected-error@+1 {{neither a coroutine nor a coroutine wrapper}}
36+
Gen<int> non_marked_wrapper(int b) { return foo_coro(b); }
37+
38+
namespace using_decl {
39+
template <typename T> using Co = Gen<T>;
40+
41+
[[clang::coro_wrapper]] Co<int> marked_wrapper1(int b) { return foo_coro(b); }
42+
43+
// expected-error@+1 {{neither a coroutine nor a coroutine wrapper}}
44+
Co<int> non_marked_wrapper(int b) { return foo_coro(b); }
45+
} // namespace using_decl
46+
47+
namespace lambdas {
48+
#define CORO_WRAPPER \
49+
_Pragma("clang diagnostic push") \
50+
_Pragma("clang diagnostic ignored \"-Wc++23-extensions\"") \
51+
[[clang::coro_wrapper]] \
52+
_Pragma("clang diagnostic pop")
53+
54+
void foo() {
55+
auto coro_lambda = []() -> Gen<int> {
56+
co_return 1;
57+
};
58+
// expected-error@+1 {{neither a coroutine nor a coroutine wrapper}}
59+
auto not_allowed_wrapper = []() -> Gen<int> {
60+
return foo_coro(1);
61+
};
62+
auto allowed_wrapper = [] CORO_WRAPPER() -> Gen<int> {
63+
return foo_coro(1);
64+
};
65+
}
66+
67+
Gen<int> coro_containing_lambda() {
68+
// expected-error@+1 {{neither a coroutine nor a coroutine wrapper}}
69+
auto wrapper_lambda = []() -> Gen<int> {
70+
return foo_coro(1);
71+
};
72+
co_return co_await wrapper_lambda();
73+
}
74+
} // namespace lambdas
75+
76+
namespace std_function {
77+
namespace std {
78+
template <typename> class function;
79+
80+
template <typename ReturnValue, typename... Args>
81+
class function<ReturnValue(Args...)> {
82+
public:
83+
template <typename T> function &operator=(T) {}
84+
template <typename T> function(T) {}
85+
// expected-error@+1 {{neither a coroutine nor a coroutine wrapper}}
86+
ReturnValue operator()(Args... args) const {
87+
return callable_->Invoke(args...); // expected-note {{in instantiation of member}}
88+
}
89+
90+
private:
91+
class Callable {
92+
public:
93+
// expected-error@+1 {{neither a coroutine nor a coroutine wrapper}}
94+
ReturnValue Invoke(Args...) const { return {}; }
95+
};
96+
Callable* callable_;
97+
};
98+
} // namespace std
99+
100+
void use_std_function() {
101+
std::function<int(bool)> foo = [](bool b) { return b ? 1 : 2; };
102+
// expected-error@+1 {{neither a coroutine nor a coroutine wrapper}}
103+
std::function<Gen<int>(bool)> test1 = [](bool b) {
104+
return foo_coro(b);
105+
};
106+
std::function<Gen<int>(bool)> test2 = [](bool) -> Gen<int> {
107+
co_return 1;
108+
};
109+
std::function<Gen<int>(bool)> test3 = foo_coro;
110+
111+
foo(true); // Fine.
112+
test1(true); // expected-note 2 {{in instantiation of member}}
113+
test2(true);
114+
test3(true);
115+
}
116+
} // namespace std_function
117+
118+
// different_promise_type
119+
class [[clang::coro_return_type]] Task{};
120+
struct my_promise_type {
121+
Task get_return_object() {
122+
return {};
123+
}
124+
suspend_always initial_suspend();
125+
suspend_always final_suspend() noexcept;
126+
void unhandled_exception();
127+
};
128+
namespace std {
129+
template<> class coroutine_traits<Task, int> {
130+
using promise_type = my_promise_type;
131+
};
132+
} // namespace std
133+
// expected-error@+1 {{neither a coroutine nor a coroutine wrapper}}
134+
Task foo(int) { return Task{}; }

0 commit comments

Comments
 (0)