Skip to content

Commit 71697c3

Browse files
Jumhyntheblixguy
authored andcommitted
Allow implicit self in escaping closures when self usage is unlikely to cause cycle (swiftlang#23934)
* WIP implementation * Cleanup implementation * Install backedge rather than storing array reference * Add diagnostics * Add missing parameter to ResultFinderForTypeContext constructor * Fix tests for correct fix-it language * Change to solution without backedge, change lookup behavior * Improve diagnostics for weak captures and captures under different names * Remove ghosts of implementations past * Address review comments * Reorder member variable initialization * Fix typos * Exclude value types from explicit self requirements * Add tests * Add implementation for AST lookup * Add tests * Begin addressing review comments * Re-enable AST scope lookup * Add fixme * Pull fix-its into a separate function * Remove capturedSelfContext tracking from type property initializers * Add const specifiers to arguments * Address review comments * Fix string literals * Refactor implicit self diagnostics * Add comment * Remove trailing whitespace * Add tests for capture list across multiple lines * Add additional test * Fix typo * Remove use of ?: to fix linux build * Remove second use of ?: * Rework logic for finding nested self contexts
1 parent 2cb6cdc commit 71697c3

22 files changed

+720
-193
lines changed

include/swift/AST/ASTScope.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,10 @@ class ASTScopeImpl {
479479
/// asked.
480480
virtual Optional<NullablePtr<DeclContext>> computeSelfDCForParent() const;
481481

482+
/// Returns the context that should be used when a nested scope (e.g. a
483+
/// closure) captures self explicitly.
484+
virtual NullablePtr<DeclContext> capturedSelfDC() const;
485+
482486
protected:
483487
/// Find either locals or members (no scope has both)
484488
/// \param history The scopes visited since the start of lookup (including
@@ -692,6 +696,15 @@ class Portion {
692696
/// to compute the selfDC from the history.
693697
static NullablePtr<DeclContext>
694698
computeSelfDC(ArrayRef<const ASTScopeImpl *> history);
699+
700+
/// If we find a lookup result that requires the dynamic implict self value,
701+
/// we need to check the nested scopes to see if any closures explicitly
702+
/// captured \c self. In that case, the appropriate selfDC is that of the
703+
/// innermost closure which captures a \c self value from one of this type's
704+
/// methods.
705+
static NullablePtr<DeclContext>
706+
checkNestedScopesForSelfCapture(ArrayRef<const ASTScopeImpl *> history,
707+
size_t start);
695708
};
696709

697710
/// Behavior specific to representing the trailing where clause of a
@@ -1449,6 +1462,13 @@ class ClosureParametersScope final : public AbstractClosureScope {
14491462
std::string getClassName() const override;
14501463
SourceRange
14511464
getSourceRangeOfThisASTNode(bool omitAssertions = false) const override;
1465+
1466+
/// Since explicit captures of \c self by closures enable the use of implicit
1467+
/// \c self, we need to make sure that the appropriate \c self is used as the
1468+
/// base decl for these uses (otherwise, the capture would be marked as
1469+
/// unused. \c ClosureParametersScope::capturedSelfDC() checks if we have such
1470+
/// a capture of self.
1471+
NullablePtr<DeclContext> capturedSelfDC() const override;
14521472

14531473
protected:
14541474
ASTScopeImpl *expandSpecifically(ScopeCreator &) override;

include/swift/AST/Decl.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,12 +334,15 @@ class alignas(1 << DeclAlignInBits) Decl {
334334
IsStatic : 1
335335
);
336336

337-
SWIFT_INLINE_BITFIELD(VarDecl, AbstractStorageDecl, 1+1+1+1+1+1,
337+
SWIFT_INLINE_BITFIELD(VarDecl, AbstractStorageDecl, 1+1+1+1+1+1+1,
338338
/// Encodes whether this is a 'let' binding.
339339
Introducer : 1,
340340

341341
/// Whether this declaration was an element of a capture list.
342342
IsCaptureList : 1,
343+
344+
/// Whether this declaration captures the 'self' param under the same name.
345+
IsSelfParamCapture : 1,
343346

344347
/// Whether this vardecl has an initial value bound to it in a way
345348
/// that isn't represented in the AST with an initializer in the pattern
@@ -5029,6 +5032,12 @@ class VarDecl : public AbstractStorageDecl {
50295032

50305033
/// Is this an element in a capture list?
50315034
bool isCaptureList() const { return Bits.VarDecl.IsCaptureList; }
5035+
5036+
/// Is this a capture of the self param?
5037+
bool isSelfParamCapture() const { return Bits.VarDecl.IsSelfParamCapture; }
5038+
void setIsSelfParamCapture(bool IsSelfParamCapture = true) {
5039+
Bits.VarDecl.IsSelfParamCapture = IsSelfParamCapture;
5040+
}
50325041

50335042
/// Return true if this vardecl has an initial value bound to it in a way
50345043
/// that isn't represented in the AST with an initializer in the pattern

include/swift/AST/DiagnosticsSema.def

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3244,11 +3244,20 @@ ERROR(self_assignment_var,none,
32443244
ERROR(self_assignment_prop,none,
32453245
"assigning a property to itself", ())
32463246
ERROR(property_use_in_closure_without_explicit_self,none,
3247-
"reference to property %0 in closure requires explicit 'self.' to make"
3248-
" capture semantics explicit", (Identifier))
3247+
"reference to property %0 in closure requires explicit use of 'self' to"
3248+
" make capture semantics explicit", (Identifier))
32493249
ERROR(method_call_in_closure_without_explicit_self,none,
3250-
"call to method %0 in closure requires explicit 'self.' to make"
3250+
"call to method %0 in closure requires explicit use of 'self' to make"
32513251
" capture semantics explicit", (Identifier))
3252+
NOTE(note_capture_self_explicitly,none,
3253+
"capture 'self' explicitly to enable implicit 'self' in this closure", ())
3254+
NOTE(note_reference_self_explicitly,none,
3255+
"reference 'self.' explicitly", ())
3256+
NOTE(note_other_self_capture,none,
3257+
"variable other than 'self' captured here under the name 'self' does not "
3258+
"enable implicit 'self'", ())
3259+
NOTE(note_self_captured_weakly,none,
3260+
"weak capture of 'self' here does not enable implicit 'self'", ())
32523261
ERROR(implicit_use_of_self_in_closure,none,
32533262
"implicit use of 'self' in closure; use 'self.' to make"
32543263
" capture semantics explicit", ())

include/swift/AST/Expr.h

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ namespace swift {
6565
class EnumElementDecl;
6666
class CallExpr;
6767
class KeyPathExpr;
68+
class CaptureListExpr;
6869

6970
enum class ExprKind : uint8_t {
7071
#define EXPR(Id, Parent) Id,
@@ -3582,6 +3583,18 @@ class SerializedAbstractClosureExpr : public SerializedLocalDeclContext {
35823583
/// \endcode
35833584
class ClosureExpr : public AbstractClosureExpr {
35843585

3586+
/// The range of the brackets of the capture list, if present.
3587+
SourceRange BracketRange;
3588+
3589+
/// The (possibly null) VarDecl captured by this closure with the literal name
3590+
/// "self". In order to recover this information at the time of name lookup,
3591+
/// we must be able to access it from the associated DeclContext.
3592+
/// Because the DeclContext inside a closure is the closure itself (and not
3593+
/// the CaptureListExpr which would normally maintain this sort of
3594+
/// information about captured variables), we need to have some way to access
3595+
/// this information directly on the ClosureExpr.
3596+
VarDecl *CapturedSelfDecl;
3597+
35853598
/// The location of the "throws", if present.
35863599
SourceLoc ThrowsLoc;
35873600

@@ -3598,16 +3611,16 @@ class ClosureExpr : public AbstractClosureExpr {
35983611
/// The body of the closure, along with a bit indicating whether it
35993612
/// was originally just a single expression.
36003613
llvm::PointerIntPair<BraceStmt *, 1, bool> Body;
3601-
36023614
public:
3603-
ClosureExpr(ParameterList *params, SourceLoc throwsLoc, SourceLoc arrowLoc,
3615+
ClosureExpr(SourceRange bracketRange, VarDecl *capturedSelfDecl,
3616+
ParameterList *params, SourceLoc throwsLoc, SourceLoc arrowLoc,
36043617
SourceLoc inLoc, TypeLoc explicitResultType,
36053618
unsigned discriminator, DeclContext *parent)
36063619
: AbstractClosureExpr(ExprKind::Closure, Type(), /*Implicit=*/false,
36073620
discriminator, parent),
3621+
BracketRange(bracketRange), CapturedSelfDecl(capturedSelfDecl),
36083622
ThrowsLoc(throwsLoc), ArrowLoc(arrowLoc), InLoc(inLoc),
3609-
ExplicitResultType(explicitResultType),
3610-
Body(nullptr) {
3623+
ExplicitResultType(explicitResultType), Body(nullptr) {
36113624
setParameterList(params);
36123625
Bits.ClosureExpr.HasAnonymousClosureVars = false;
36133626
}
@@ -3639,6 +3652,8 @@ class ClosureExpr : public AbstractClosureExpr {
36393652
/// explicitly-specified result type.
36403653
bool hasExplicitResultType() const { return ArrowLoc.isValid(); }
36413654

3655+
/// Retrieve the range of the \c '[' and \c ']' that enclose the capture list.
3656+
SourceRange getBracketRange() const { return BracketRange; }
36423657

36433658
/// Retrieve the location of the \c '->' for closures with an
36443659
/// explicit result type.
@@ -3704,6 +3719,14 @@ class ClosureExpr : public AbstractClosureExpr {
37043719
/// Is this a completely empty closure?
37053720
bool hasEmptyBody() const;
37063721

3722+
/// VarDecl captured by this closure under the literal name \c self , if any.
3723+
VarDecl *getCapturedSelfDecl() const { return CapturedSelfDecl; }
3724+
3725+
/// Whether this closure captures the \c self param in its body in such a
3726+
/// way that implicit \c self is enabled within its body (i.e. \c self is
3727+
/// captured non-weakly).
3728+
bool capturesSelfEnablingImplictSelf() const;
3729+
37073730
static bool classof(const Expr *E) {
37083731
return E->getKind() == ExprKind::Closure;
37093732
}
@@ -3778,6 +3801,8 @@ struct CaptureListEntry {
37783801
CaptureListEntry(VarDecl *Var, PatternBindingDecl *Init)
37793802
: Var(Var), Init(Init) {
37803803
}
3804+
3805+
bool isSimpleSelfCapture() const;
37813806
};
37823807

37833808
/// CaptureListExpr - This expression represents the capture list on an explicit

include/swift/AST/NameLookup.h

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,15 @@ struct LookupResultEntry {
7070
/// When finding \c bar() from the function body of \c foo(), \c BaseDC is
7171
/// the method \c foo().
7272
///
73-
/// \c BaseDC will be the method if \c self is needed for the lookup,
74-
/// and will be the type if not.
75-
/// In other words: If \c baseDC is a method, it means you found an instance
76-
/// member and you should add an implicit 'self.' (Each method has its own
77-
/// implicit self decl.) There's one other kind of non-method context that
78-
/// has a 'self.' -- a lazy property initializer, which unlike a non-lazy
79-
/// property can reference \c self) Hence: \code
73+
/// \c BaseDC will be the type if \c self is not needed for the lookup. If
74+
/// \c self is needed, \c baseDC will be either the method or a closure
75+
/// which explicitly captured \c self.
76+
/// In other words: If \c baseDC is a method or a closure, it means you
77+
/// found an instance member and you should add an implicit 'self.' (Each
78+
/// method has its own implicit self decl.) There's one other kind of
79+
/// non-method, non-closure context that has a 'self.' -- a lazy property
80+
/// initializer, which unlike a non-lazy property can reference \c self.
81+
/// \code
8082
/// class Outer {
8183
///   static func s()
8284
///   func i()

include/swift/Parse/Lexer.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,17 @@ class Lexer {
293293
/// resides.
294294
///
295295
/// \param Loc The source location of the beginning of a token.
296-
static Token getTokenAtLocation(const SourceManager &SM, SourceLoc Loc);
296+
///
297+
/// \param CRM How comments should be treated by the lexer. Default is to
298+
/// return the comments as tokens. This is needed in situations where
299+
/// detecting the next semantically meaningful token is required, such as
300+
/// the 'implicit self' diagnostic determining whether a capture list is
301+
/// empty (i.e., the opening bracket is immediately followed by a closing
302+
/// bracket, possibly with comments in between) in order to insert the
303+
/// appropriate fix-it.
304+
static Token getTokenAtLocation(
305+
const SourceManager &SM, SourceLoc Loc,
306+
CommentRetentionMode CRM = CommentRetentionMode::ReturnAsTokens);
297307

298308

299309
/// Retrieve the source location that points just past the

include/swift/Parse/Parser.h

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1514,6 +1514,8 @@ class Parser {
15141514
/// identifier (',' identifier)* func-signature-result? 'in'
15151515
/// \endverbatim
15161516
///
1517+
/// \param bracketRange The range of the brackets enclosing a capture list, if
1518+
/// present. Needed to offer fix-its for inserting 'self' into a capture list.
15171519
/// \param captureList The entries in the capture list.
15181520
/// \param params The parsed parameter list, or null if none was provided.
15191521
/// \param arrowLoc The location of the arrow, if present.
@@ -1522,12 +1524,14 @@ class Parser {
15221524
///
15231525
/// \returns true if an error occurred, false otherwise.
15241526
bool parseClosureSignatureIfPresent(
1525-
SmallVectorImpl<CaptureListEntry> &captureList,
1526-
ParameterList *&params,
1527-
SourceLoc &throwsLoc,
1528-
SourceLoc &arrowLoc,
1529-
TypeRepr *&explicitResultType,
1530-
SourceLoc &inLoc);
1527+
SourceRange &bracketRange,
1528+
SmallVectorImpl<CaptureListEntry> &captureList,
1529+
VarDecl *&capturedSelfParamDecl,
1530+
ParameterList *&params,
1531+
SourceLoc &throwsLoc,
1532+
SourceLoc &arrowLoc,
1533+
TypeRepr *&explicitResultType,
1534+
SourceLoc &inLoc);
15311535

15321536
Expr *parseExprAnonClosureArg();
15331537
ParserResult<Expr> parseExprList(tok LeftTok, tok RightTok,

lib/AST/ASTScopeLookup.cpp

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -517,12 +517,59 @@ GenericTypeOrExtensionWhereOrBodyPortion::computeSelfDC(
517517
while (i != 0) {
518518
Optional<NullablePtr<DeclContext>> maybeSelfDC =
519519
history[--i]->computeSelfDCForParent();
520-
if (maybeSelfDC)
521-
return *maybeSelfDC;
520+
if (maybeSelfDC) {
521+
// If we've found a selfDC, we'll definitely be returning something.
522+
// However, we may have captured 'self' somewhere down the tree, so we
523+
// can't return outright without checking the nested scopes.
524+
NullablePtr<DeclContext> nestedCapturedSelfDC =
525+
checkNestedScopesForSelfCapture(history, i);
526+
return nestedCapturedSelfDC ? nestedCapturedSelfDC : *maybeSelfDC;
527+
}
522528
}
523529
return nullptr;
524530
}
525531

532+
#pragma mark checkNestedScopesForSelfCapture
533+
534+
NullablePtr<DeclContext>
535+
GenericTypeOrExtensionWhereOrBodyPortion::checkNestedScopesForSelfCapture(
536+
ArrayRef<const ASTScopeImpl *> history, size_t start) {
537+
NullablePtr<DeclContext> innerCapturedSelfDC;
538+
// Start with the next scope down the tree.
539+
size_t j = start;
540+
541+
// Note: even though having this loop nested inside the while loop from
542+
// GenericTypeOrExtensionWhereOrBodyPortion::computeSelfDC may appear to
543+
// result in quadratic blowup, complexity actually remains linear with respect
544+
// to the size of history. This relies on the fact that
545+
// GenericTypeOrExtensionScope::computeSelfDCForParent returns a null pointer,
546+
// which will cause this method to bail out of the search early. Thus, this
547+
// method is called once per type body in the lookup history, and will not
548+
// end up re-checking the bodies of nested types that have already been
549+
// covered by earlier calls, so the total impact of this method across all
550+
// calls in a single lookup is O(n).
551+
while (j != 0) {
552+
auto *entry = history[--j];
553+
Optional<NullablePtr<DeclContext>> selfDCForParent =
554+
entry->computeSelfDCForParent();
555+
556+
// If we encounter a scope that should cause us to forget the self
557+
// context (such as a nested type), bail out and use whatever the
558+
// the last inner captured context was.
559+
if (selfDCForParent && (*selfDCForParent).isNull())
560+
break;
561+
562+
// Otherwise, if we have a captured self context for this scope, then
563+
// remember it since it is now the innermost scope we have encountered.
564+
NullablePtr<DeclContext> capturedSelfDC = entry->capturedSelfDC();
565+
if (!capturedSelfDC.isNull())
566+
innerCapturedSelfDC = entry->capturedSelfDC();
567+
568+
// Continue searching in the next scope down.
569+
}
570+
return innerCapturedSelfDC;
571+
}
572+
526573
#pragma mark compute isCascadingUse
527574

528575
Optional<bool> ASTScopeImpl::computeIsCascadingUse(
@@ -631,6 +678,23 @@ MethodBodyScope::computeSelfDCForParent() const {
631678
return NullablePtr<DeclContext>(decl);
632679
}
633680

681+
#pragma mark capturedSelfDC
682+
683+
// Closures may explicitly capture the self param, in which case the lookup
684+
// should use the closure as the context for implicit self lookups.
685+
686+
// By default, there is no such context to return.
687+
NullablePtr<DeclContext> ASTScopeImpl::capturedSelfDC() const {
688+
return NullablePtr<DeclContext>();
689+
}
690+
691+
// Closures track this information explicitly.
692+
NullablePtr<DeclContext> ClosureParametersScope::capturedSelfDC() const {
693+
if (closureExpr->capturesSelfEnablingImplictSelf())
694+
return NullablePtr<DeclContext>(closureExpr);
695+
return NullablePtr<DeclContext>();
696+
}
697+
634698
#pragma mark ifUnknownIsCascadingUseAccordingTo
635699

636700
static bool isCascadingUseAccordingTo(const DeclContext *const dc) {

lib/AST/Decl.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5249,6 +5249,7 @@ VarDecl::VarDecl(DeclKind kind, bool isStatic, VarDecl::Introducer introducer,
52495249
{
52505250
Bits.VarDecl.Introducer = unsigned(introducer);
52515251
Bits.VarDecl.IsCaptureList = isCaptureList;
5252+
Bits.VarDecl.IsSelfParamCapture = false;
52525253
Bits.VarDecl.IsDebuggerVar = false;
52535254
Bits.VarDecl.IsLazyStorageProperty = false;
52545255
Bits.VarDecl.HasNonPatternBindingInit = false;

lib/AST/Expr.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1179,6 +1179,17 @@ UnresolvedSpecializeExpr *UnresolvedSpecializeExpr::create(ASTContext &ctx,
11791179
UnresolvedParams, RAngleLoc);
11801180
}
11811181

1182+
bool CaptureListEntry::isSimpleSelfCapture() const {
1183+
if (Init->getPatternList().size() != 1)
1184+
return false;
1185+
if (auto *DRE = dyn_cast<DeclRefExpr>(Init->getInit(0)))
1186+
if (auto *VD = dyn_cast<VarDecl>(DRE->getDecl())) {
1187+
return (VD->isSelfParameter() || VD->isSelfParamCapture())
1188+
&& VD->getName() == Var->getName();
1189+
}
1190+
return false;
1191+
}
1192+
11821193
CaptureListExpr *CaptureListExpr::create(ASTContext &ctx,
11831194
ArrayRef<CaptureListEntry> captureList,
11841195
ClosureExpr *closureBody) {
@@ -1813,6 +1824,12 @@ bool ClosureExpr::hasEmptyBody() const {
18131824
return getBody()->empty();
18141825
}
18151826

1827+
bool ClosureExpr::capturesSelfEnablingImplictSelf() const {
1828+
if (auto *VD = getCapturedSelfDecl())
1829+
return VD->isSelfParamCapture() && !VD->getType()->is<WeakStorageType>();
1830+
return false;
1831+
}
1832+
18161833
FORWARD_SOURCE_LOCS_TO(AutoClosureExpr, Body)
18171834

18181835
void AutoClosureExpr::setBody(Expr *E) {

0 commit comments

Comments
 (0)