Skip to content

Commit 1052a60

Browse files
authored
Merge pull request #62738 from xedin/runtime_attr_method_thunks
[Sema] RuntimeMetadata: Add support for mutating/async methods and functions via a thunk
2 parents d38271c + 60486e6 commit 1052a60

4 files changed

+373
-61
lines changed

lib/Sema/TypeCheckRuntimeMetadataAttr.cpp

Lines changed: 183 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@
1414
//
1515
//===----------------------------------------------------------------------===//
1616

17+
#include "TypeCheckConcurrency.h"
1718
#include "TypeChecker.h"
1819
#include "swift/AST/ASTContext.h"
1920
#include "swift/AST/ASTMangler.h"
21+
#include "swift/AST/ActorIsolation.h"
2022
#include "swift/AST/Attr.h"
2123
#include "swift/AST/Decl.h"
2224
#include "swift/AST/Initializer.h"
@@ -46,6 +48,184 @@ ValueDecl::getRuntimeDiscoverableAttributeGenerator(CustomAttr *attr) const {
4648
return std::make_pair(body, init->getType()->mapTypeOutOfContext());
4749
}
4850

51+
static TypeRepr *buildTypeRepr(DeclContext *typeContext) {
52+
assert(typeContext->isTypeContext());
53+
54+
SmallVector<ComponentIdentTypeRepr *, 2> components;
55+
56+
auto &ctx = typeContext->getASTContext();
57+
DeclContext *DC = typeContext;
58+
while (!DC->isModuleContext()) {
59+
auto *NTD = DC->getSelfNominalTypeDecl();
60+
// Only contiguous chains of nominals and extensions thereof.
61+
if (!NTD)
62+
break;
63+
64+
auto *component = new (ctx) SimpleIdentTypeRepr(
65+
/*Loc=*/DeclNameLoc(), NTD->createNameRef());
66+
67+
// Resolve the component right away, instead of
68+
// involving name lookup. This plays well with
69+
// the fact that initializer is anchored on a
70+
// source file.
71+
component->setValue(NTD, NTD->getDeclContext());
72+
73+
components.push_back(component);
74+
DC = NTD->getDeclContext();
75+
}
76+
77+
// Reverse the components to form a valid outer-to-inner name sequence.
78+
std::reverse(components.begin(), components.end());
79+
80+
if (components.size() == 1)
81+
return components.front();
82+
83+
return CompoundIdentTypeRepr::create(ctx, components);
84+
}
85+
86+
/// Synthesizes a closure thunk that forwards all of the arguments
87+
/// to the underlying method. This is required to support mutating
88+
/// methods and create uniform signatures where for instance methods
89+
/// first parameter is always `self` (with or without `inout`).
90+
static ClosureExpr *synthesizeMethodThunk(DeclContext *thunkDC,
91+
NominalTypeDecl *nominal,
92+
FuncDecl *method) {
93+
auto &ctx = method->getASTContext();
94+
95+
// If this is a method, let's form a thunk so that attribute initializer
96+
// gets `([inout] T, Argument, ...) -> Result` signature.
97+
auto *funcParams = method->getParameters();
98+
99+
SmallVector<ParamDecl *, 4> closureParams;
100+
101+
NullablePtr<ParamDecl> selfParam;
102+
if (!method->isStatic()) {
103+
auto *self = ParamDecl::createImplicit(
104+
ctx,
105+
/*argumentName=*/Identifier(),
106+
/*parameterName=*/ctx.Id_self,
107+
/*type=*/nominal->getDeclaredInterfaceType(), thunkDC,
108+
method->isMutating() ? ParamSpecifier::InOut : ParamSpecifier::Default);
109+
110+
// This is very important for the solver, without a type repr
111+
// it would create a type variable and attempt infer the type
112+
// from the body.
113+
self->setTypeRepr(buildTypeRepr(nominal));
114+
115+
closureParams.push_back(self);
116+
selfParam = self;
117+
}
118+
119+
if (funcParams) {
120+
unsigned anonIdx = 0;
121+
for (auto *param : *funcParams) {
122+
auto name = param->getParameterName();
123+
// Cannot leave parameter anonymous because it would be
124+
// referenced in the body.
125+
if (name.empty())
126+
name = ctx.getIdentifier((Twine("$anon") + Twine(anonIdx++)).str());
127+
128+
auto *closureParam = ParamDecl::createImplicit(
129+
ctx,
130+
/*argumentName=*/Identifier(),
131+
/*parameterName=*/name, param->getInterfaceType(), thunkDC,
132+
param->getSpecifier());
133+
closureParam->setTypeRepr(param->getTypeRepr());
134+
135+
closureParams.push_back(closureParam);
136+
}
137+
}
138+
139+
// return self.<func>(<arguments>)
140+
SmallVector<ASTNode, 2> body;
141+
{
142+
NullablePtr<Expr> baseExpr;
143+
if (method->isStatic()) {
144+
baseExpr =
145+
TypeExpr::createImplicit(nominal->getDeclaredInterfaceType(), ctx);
146+
} else {
147+
baseExpr = new (ctx) DeclRefExpr({selfParam.get()}, /*Loc=*/DeclNameLoc(),
148+
/*implicit=*/true);
149+
}
150+
151+
auto *memberRef = new (ctx) MemberRefExpr(
152+
baseExpr.get(), /*dotLoc=*/SourceLoc(), {method}, /*loc=*/DeclNameLoc(),
153+
/*Implicit=*/true);
154+
155+
SmallVector<Argument, 4> arguments;
156+
if (funcParams) {
157+
for (unsigned i = 0, n = funcParams->size(); i != n; ++i) {
158+
const auto *param = funcParams->get(i);
159+
160+
Expr *argExpr = new (ctx)
161+
DeclRefExpr({closureParams[method->isStatic() ? i : i + 1]},
162+
/*Loc=*/DeclNameLoc(),
163+
/*implicit=*/true);
164+
165+
if (param->isInOut()) {
166+
argExpr = new (ctx) InOutExpr(/*operLoc=*/SourceLoc(), argExpr,
167+
Type(), /*implicit=*/true);
168+
}
169+
170+
arguments.push_back(
171+
{/*labelLoc=*/SourceLoc(), param->getArgumentName(), argExpr});
172+
}
173+
}
174+
175+
Expr *call = CallExpr::createImplicit(
176+
ctx, memberRef, ArgumentList::createImplicit(ctx, arguments));
177+
178+
bool isAsync = false;
179+
bool isThrows = method->hasThrows();
180+
181+
switch (getActorIsolation(method)) {
182+
case ActorIsolation::Unspecified:
183+
case ActorIsolation::Independent: {
184+
isAsync = method->hasAsync();
185+
break;
186+
}
187+
188+
case ActorIsolation::ActorInstance: {
189+
isAsync = true;
190+
isThrows |= nominal->isDistributedActor();
191+
break;
192+
}
193+
194+
case ActorIsolation::GlobalActor:
195+
isAsync = true;
196+
LLVM_FALLTHROUGH;
197+
case ActorIsolation::GlobalActorUnsafe: {
198+
break;
199+
}
200+
}
201+
202+
if (isAsync)
203+
call = AwaitExpr::createImplicit(ctx, /*awaitLoc=*/SourceLoc(), call);
204+
205+
if (isThrows)
206+
call = TryExpr::createImplicit(ctx, /*tryLoc=*/SourceLoc(), call);
207+
208+
body.push_back(new (ctx) ReturnStmt(/*ReturnLoc=*/SourceLoc(), call,
209+
/*implicit=*/true));
210+
}
211+
212+
DeclAttributes attrs;
213+
auto *closure = new (ctx) ClosureExpr(
214+
attrs, /*bracketRange=*/SourceRange(),
215+
/*capturedSelf=*/nullptr, ParameterList::create(ctx, closureParams),
216+
/*asyncLoc=*/SourceLoc(),
217+
/*throwsLoc=*/SourceLoc(),
218+
/*arrowLoc=*/SourceLoc(),
219+
/*inLoc=*/SourceLoc(),
220+
/*explicitResultType=*/nullptr, thunkDC);
221+
222+
closure->setBody(BraceStmt::createImplicit(ctx, body),
223+
/*isSingleExpr=*/true);
224+
closure->setImplicit();
225+
226+
return closure;
227+
}
228+
49229
Expr *SynthesizeRuntimeMetadataAttrGenerator::evaluate(
50230
Evaluator &evaluator, CustomAttr *attr, ValueDecl *attachedTo) const {
51231
auto &ctx = attachedTo->getASTContext();
@@ -77,13 +257,7 @@ Expr *SynthesizeRuntimeMetadataAttrGenerator::evaluate(
77257
DotSelfExpr(metatype, /*dot=*/SourceLoc(), /*self=*/SourceLoc());
78258
} else if (auto *func = dyn_cast<FuncDecl>(attachedTo)) {
79259
if (auto *nominal = func->getDeclContext()->getSelfNominalTypeDecl()) {
80-
auto *baseExpr =
81-
TypeExpr::createImplicit(nominal->getDeclaredInterfaceType(), ctx);
82-
83-
// Form an initializer call passing in the function reference
84-
initArgument = new (ctx) MemberRefExpr(baseExpr, /*dotLoc=*/SourceLoc(),
85-
{func}, /*loc=*/DeclNameLoc(),
86-
/*Implicit=*/true);
260+
initArgument = synthesizeMethodThunk(initContext, nominal, func);
87261
} else {
88262
initArgument = new (ctx)
89263
DeclRefExpr({func}, /*Loc=*/DeclNameLoc(), /*implicit=*/true);
@@ -102,44 +276,7 @@ Expr *SynthesizeRuntimeMetadataAttrGenerator::evaluate(
102276
// Build a type repr for base of the key path, since attribute
103277
// could be attached to an inner type, we need to go up decl
104278
// contexts and add every parent type.
105-
{
106-
SmallVector<ComponentIdentTypeRepr *, 2> baseNameComponents;
107-
108-
auto *DC = var->getDeclContext();
109-
while (!DC->isModuleContext()) {
110-
auto *NTD = DC->getSelfNominalTypeDecl();
111-
// Only contiguous chains of nominals and extensions thereof.
112-
if (!NTD)
113-
break;
114-
115-
auto *component = new (ctx) SimpleIdentTypeRepr(
116-
/*Loc=*/DeclNameLoc(), NTD->createNameRef());
117-
118-
// Resolve the component right away, instead of
119-
// involving name lookup. This plays well with
120-
// the fact that initializer is anchored on a
121-
// source file.
122-
component->setValue(NTD, NTD->getDeclContext());
123-
124-
baseNameComponents.push_back(component);
125-
DC = NTD->getDeclContext();
126-
}
127-
128-
// Reverse the components to form a valid outer-to-inner name sequence.
129-
std::reverse(baseNameComponents.begin(), baseNameComponents.end());
130-
131-
// Set the 'root' of the key path to the newly build base name.
132-
// We cannot do this via `parsedRoot` because it has strict
133-
// rules about leading-dot.
134-
TypeRepr *rootName = nullptr;
135-
if (baseNameComponents.size() == 1) {
136-
rootName = baseNameComponents.front();
137-
} else {
138-
rootName = CompoundIdentTypeRepr::create(ctx, baseNameComponents);
139-
}
140-
141-
keyPath->setRootType(rootName);
142-
}
279+
keyPath->setRootType(buildTypeRepr(var->getDeclContext()));
143280

144281
initArgument = keyPath;
145282
}
@@ -177,6 +314,7 @@ Expr *SynthesizeRuntimeMetadataAttrGenerator::evaluate(
177314
return nullptr;
178315

179316
TypeChecker::contextualizeInitializer(initContext, result);
317+
checkInitializerActorIsolation(initContext, result);
180318
TypeChecker::checkInitializerEffects(initContext, result);
181319

182320
return result;

0 commit comments

Comments
 (0)