Skip to content

Allow implicit self in escaping closures when self usage is unlikely to cause cycle #23934

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 37 commits into from
Dec 20, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
1555a1f
WIP implementation
Jumhyn Apr 3, 2019
43dd857
Cleanup implementation
Jumhyn Apr 5, 2019
1d3c2d6
Install backedge rather than storing array reference
Jumhyn Apr 7, 2019
adf6589
Add diagnostics
Jumhyn Apr 10, 2019
4804725
Add missing parameter to ResultFinderForTypeContext constructor
Jumhyn Jul 21, 2019
509036a
Fix tests for correct fix-it language
Jumhyn Jul 21, 2019
18b31c7
Change to solution without backedge, change lookup behavior
Jumhyn Jul 29, 2019
3b35891
Improve diagnostics for weak captures and captures under different names
Jumhyn Aug 4, 2019
df5f6a1
Remove ghosts of implementations past
Jumhyn Aug 4, 2019
6c657fb
Address review comments
Jumhyn Aug 17, 2019
7406101
Merge remote-tracking branch 'origin/master' into implicit-self-capture
Jumhyn Aug 17, 2019
638a621
Reorder member variable initialization
Jumhyn Aug 18, 2019
05d79f8
Fix typos
Jumhyn Aug 18, 2019
84f6131
Exclude value types from explicit self requirements
Jumhyn Oct 2, 2019
a5d7da0
Merge remote-tracking branch 'origin/master' into implicit-self-capture
Jumhyn Oct 3, 2019
a7e7d19
Add tests
Jumhyn Oct 3, 2019
9eb87a5
Add implementation for AST lookup
Jumhyn Oct 17, 2019
898f6c2
Add tests
Jumhyn Oct 17, 2019
93be3a9
Begin addressing review comments
Jumhyn Oct 18, 2019
fa77977
Re-enable AST scope lookup
Jumhyn Oct 18, 2019
6ccc6d0
Merge remote-tracking branch 'origin/master' into implicit-self-capture
Jumhyn Oct 18, 2019
84363aa
Add fixme
Jumhyn Oct 18, 2019
89dd842
Pull fix-its into a separate function
Jumhyn Oct 18, 2019
2a27245
Remove capturedSelfContext tracking from type property initializers
Jumhyn Oct 18, 2019
c897186
Add const specifiers to arguments
Jumhyn Oct 18, 2019
f8856f8
Address review comments
Jumhyn Oct 19, 2019
48f63f4
Fix string literals
Jumhyn Oct 20, 2019
9a72c14
Refactor implicit self diagnostics
Jumhyn Oct 20, 2019
2806afb
Add comment
Jumhyn Oct 20, 2019
2905610
Remove trailing whitespace
Jumhyn Oct 20, 2019
7246603
Add tests for capture list across multiple lines
Jumhyn Oct 20, 2019
d191ac2
Add additional test
Jumhyn Oct 20, 2019
b9f9589
Fix typo
Jumhyn Oct 20, 2019
20cb90e
Remove use of ?: to fix linux build
Jumhyn Oct 30, 2019
ddd1ce8
Remove second use of ?:
Jumhyn Oct 31, 2019
0b07312
Merge remote-tracking branch 'origin/master' into implicit-self-capture
Jumhyn Nov 27, 2019
558c942
Rework logic for finding nested self contexts
Jumhyn Dec 16, 2019
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
20 changes: 20 additions & 0 deletions include/swift/AST/ASTScope.h
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,10 @@ class ASTScopeImpl {
/// asked.
virtual Optional<NullablePtr<DeclContext>> computeSelfDCForParent() const;

/// Returns the context that should be used when a nested scope (e.g. a
/// closure) captures self explicitly.
virtual NullablePtr<DeclContext> capturedSelfDC() const;

protected:
/// Find either locals or members (no scope has both)
/// \param history The scopes visited since the start of lookup (including
Expand Down Expand Up @@ -692,6 +696,15 @@ class Portion {
/// to compute the selfDC from the history.
static NullablePtr<DeclContext>
computeSelfDC(ArrayRef<const ASTScopeImpl *> history);

/// If we find a lookup result that requires the dynamic implict self value,
/// we need to check the nested scopes to see if any closures explicitly
/// captured \c self. In that case, the appropriate selfDC is that of the
/// innermost closure which captures a \c self value from one of this type's
/// methods.
static NullablePtr<DeclContext>
checkNestedScopesForSelfCapture(ArrayRef<const ASTScopeImpl *> history,
size_t start);
};

/// Behavior specific to representing the trailing where clause of a
Expand Down Expand Up @@ -1449,6 +1462,13 @@ class ClosureParametersScope final : public AbstractClosureScope {
std::string getClassName() const override;
SourceRange
getSourceRangeOfThisASTNode(bool omitAssertions = false) const override;

/// Since explicit captures of \c self by closures enable the use of implicit
/// \c self, we need to make sure that the appropriate \c self is used as the
/// base decl for these uses (otherwise, the capture would be marked as
/// unused. \c ClosureParametersScope::capturedSelfDC() checks if we have such
/// a capture of self.
NullablePtr<DeclContext> capturedSelfDC() const override;

protected:
ASTScopeImpl *expandSpecifically(ScopeCreator &) override;
Expand Down
11 changes: 10 additions & 1 deletion include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -334,12 +334,15 @@ class alignas(1 << DeclAlignInBits) Decl {
IsStatic : 1
);

SWIFT_INLINE_BITFIELD(VarDecl, AbstractStorageDecl, 1+1+1+1+1+1,
SWIFT_INLINE_BITFIELD(VarDecl, AbstractStorageDecl, 1+1+1+1+1+1+1,
/// Encodes whether this is a 'let' binding.
Introducer : 1,

/// Whether this declaration was an element of a capture list.
IsCaptureList : 1,

/// Whether this declaration captures the 'self' param under the same name.
IsSelfParamCapture : 1,

/// Whether this vardecl has an initial value bound to it in a way
/// that isn't represented in the AST with an initializer in the pattern
Expand Down Expand Up @@ -4995,6 +4998,12 @@ class VarDecl : public AbstractStorageDecl {

/// Is this an element in a capture list?
bool isCaptureList() const { return Bits.VarDecl.IsCaptureList; }

/// Is this a capture of the self param?
bool isSelfParamCapture() const { return Bits.VarDecl.IsSelfParamCapture; }
void setIsSelfParamCapture(bool IsSelfParamCapture = true) {
Bits.VarDecl.IsSelfParamCapture = IsSelfParamCapture;
}

/// Return true if this vardecl has an initial value bound to it in a way
/// that isn't represented in the AST with an initializer in the pattern
Expand Down
15 changes: 12 additions & 3 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -3158,11 +3158,20 @@ ERROR(self_assignment_var,none,
ERROR(self_assignment_prop,none,
"assigning a property to itself", ())
ERROR(property_use_in_closure_without_explicit_self,none,
"reference to property %0 in closure requires explicit 'self.' to make"
" capture semantics explicit", (Identifier))
"reference to property %0 in closure requires explicit use of 'self' to"
" make capture semantics explicit", (Identifier))
ERROR(method_call_in_closure_without_explicit_self,none,
"call to method %0 in closure requires explicit 'self.' to make"
"call to method %0 in closure requires explicit use of 'self' to make"
" capture semantics explicit", (Identifier))
NOTE(note_capture_self_explicitly,none,
"capture 'self' explicitly to enable implicit 'self' in this closure", ())
NOTE(note_reference_self_explicitly,none,
"reference 'self.' explicitly", ())
NOTE(note_other_self_capture,none,
"variable other than 'self' captured here under the name 'self' does not "
"enable implicit 'self'", ())
NOTE(note_self_captured_weakly,none,
"weak capture of 'self' here does not enable implicit 'self'", ())
ERROR(implicit_use_of_self_in_closure,none,
"implicit use of 'self' in closure; use 'self.' to make"
" capture semantics explicit", ())
Expand Down
33 changes: 29 additions & 4 deletions include/swift/AST/Expr.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ namespace swift {
class EnumElementDecl;
class CallExpr;
class KeyPathExpr;
class CaptureListExpr;

enum class ExprKind : uint8_t {
#define EXPR(Id, Parent) Id,
Expand Down Expand Up @@ -3551,6 +3552,18 @@ class SerializedAbstractClosureExpr : public SerializedLocalDeclContext {
/// \endcode
class ClosureExpr : public AbstractClosureExpr {

/// The range of the brackets of the capture list, if present.
SourceRange BracketRange;

/// The (possibly null) VarDecl captured by this closure with the literal name
/// "self". In order to recover this information at the time of name lookup,
/// we must be able to access it from the associated DeclContext.
/// Because the DeclContext inside a closure is the closure itself (and not
/// the CaptureListExpr which would normally maintain this sort of
/// information about captured variables), we need to have some way to access
/// this information directly on the ClosureExpr.
VarDecl *CapturedSelfDecl;

/// The location of the "throws", if present.
SourceLoc ThrowsLoc;

Expand All @@ -3567,16 +3580,16 @@ class ClosureExpr : public AbstractClosureExpr {
/// The body of the closure, along with a bit indicating whether it
/// was originally just a single expression.
llvm::PointerIntPair<BraceStmt *, 1, bool> Body;

public:
ClosureExpr(ParameterList *params, SourceLoc throwsLoc, SourceLoc arrowLoc,
ClosureExpr(SourceRange bracketRange, VarDecl *capturedSelfDecl,
ParameterList *params, SourceLoc throwsLoc, SourceLoc arrowLoc,
SourceLoc inLoc, TypeLoc explicitResultType,
unsigned discriminator, DeclContext *parent)
: AbstractClosureExpr(ExprKind::Closure, Type(), /*Implicit=*/false,
discriminator, parent),
BracketRange(bracketRange), CapturedSelfDecl(capturedSelfDecl),
ThrowsLoc(throwsLoc), ArrowLoc(arrowLoc), InLoc(inLoc),
ExplicitResultType(explicitResultType),
Body(nullptr) {
ExplicitResultType(explicitResultType), Body(nullptr) {
setParameterList(params);
Bits.ClosureExpr.HasAnonymousClosureVars = false;
}
Expand Down Expand Up @@ -3608,6 +3621,8 @@ class ClosureExpr : public AbstractClosureExpr {
/// explicitly-specified result type.
bool hasExplicitResultType() const { return ArrowLoc.isValid(); }

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

/// Retrieve the location of the \c '->' for closures with an
/// explicit result type.
Expand Down Expand Up @@ -3673,6 +3688,14 @@ class ClosureExpr : public AbstractClosureExpr {
/// Is this a completely empty closure?
bool hasEmptyBody() const;

/// VarDecl captured by this closure under the literal name \c self , if any.
VarDecl *getCapturedSelfDecl() const { return CapturedSelfDecl; }

/// Whether this closure captures the \c self param in its body in such a
/// way that implicit \c self is enabled within its body (i.e. \c self is
/// captured non-weakly).
bool capturesSelfEnablingImplictSelf() const;

static bool classof(const Expr *E) {
return E->getKind() == ExprKind::Closure;
}
Expand Down Expand Up @@ -3746,6 +3769,8 @@ struct CaptureListEntry {
CaptureListEntry(VarDecl *Var, PatternBindingDecl *Init)
: Var(Var), Init(Init) {
}

bool isSimpleSelfCapture() const;
};

/// CaptureListExpr - This expression represents the capture list on an explicit
Expand Down
16 changes: 9 additions & 7 deletions include/swift/AST/NameLookup.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,15 @@ struct LookupResultEntry {
/// When finding \c bar() from the function body of \c foo(), \c BaseDC is
/// the method \c foo().
///
/// \c BaseDC will be the method if \c self is needed for the lookup,
/// and will be the type if not.
/// In other words: If \c baseDC is a method, it means you found an instance
/// member and you should add an implicit 'self.' (Each method has its own
/// implicit self decl.) There's one other kind of non-method context that
/// has a 'self.' -- a lazy property initializer, which unlike a non-lazy
/// property can reference \c self) Hence: \code
/// \c BaseDC will be the type if \c self is not needed for the lookup. If
/// \c self is needed, \c baseDC will be either the method or a closure
/// which explicitly captured \c self.
/// In other words: If \c baseDC is a method or a closure, it means you
/// found an instance member and you should add an implicit 'self.' (Each
/// method has its own implicit self decl.) There's one other kind of
/// non-method, non-closure context that has a 'self.' -- a lazy property
/// initializer, which unlike a non-lazy property can reference \c self.
/// \code
/// class Outer {
///   static func s()
///   func i()
Expand Down
12 changes: 11 additions & 1 deletion include/swift/Parse/Lexer.h
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,17 @@ class Lexer {
/// resides.
///
/// \param Loc The source location of the beginning of a token.
static Token getTokenAtLocation(const SourceManager &SM, SourceLoc Loc);
///
/// \param CRM How comments should be treated by the lexer. Default is to
/// return the comments as tokens. This is needed in situations where
/// detecting the next semantically meaningful token is required, such as
/// the 'implicit self' diagnostic determining whether a capture list is
/// empty (i.e., the opening bracket is immediately followed by a closing
/// bracket, possibly with comments in between) in order to insert the
/// appropriate fix-it.
static Token getTokenAtLocation(
const SourceManager &SM, SourceLoc Loc,
CommentRetentionMode CRM = CommentRetentionMode::ReturnAsTokens);


/// Retrieve the source location that points just past the
Expand Down
16 changes: 10 additions & 6 deletions include/swift/Parse/Parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -1446,6 +1446,8 @@ class Parser {
/// identifier (',' identifier)* func-signature-result? 'in'
/// \endverbatim
///
/// \param bracketRange The range of the brackets enclosing a capture list, if
/// present. Needed to offer fix-its for inserting 'self' into a capture list.
/// \param captureList The entries in the capture list.
/// \param params The parsed parameter list, or null if none was provided.
/// \param arrowLoc The location of the arrow, if present.
Expand All @@ -1454,12 +1456,14 @@ class Parser {
///
/// \returns true if an error occurred, false otherwise.
bool parseClosureSignatureIfPresent(
SmallVectorImpl<CaptureListEntry> &captureList,
ParameterList *&params,
SourceLoc &throwsLoc,
SourceLoc &arrowLoc,
TypeRepr *&explicitResultType,
SourceLoc &inLoc);
SourceRange &bracketRange,
SmallVectorImpl<CaptureListEntry> &captureList,
VarDecl *&capturedSelfParamDecl,
ParameterList *&params,
SourceLoc &throwsLoc,
SourceLoc &arrowLoc,
TypeRepr *&explicitResultType,
SourceLoc &inLoc);

Expr *parseExprAnonClosureArg();
ParserResult<Expr> parseExprList(tok LeftTok, tok RightTok,
Expand Down
68 changes: 66 additions & 2 deletions lib/AST/ASTScopeLookup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -517,12 +517,59 @@ GenericTypeOrExtensionWhereOrBodyPortion::computeSelfDC(
while (i != 0) {
Optional<NullablePtr<DeclContext>> maybeSelfDC =
history[--i]->computeSelfDCForParent();
if (maybeSelfDC)
return *maybeSelfDC;
if (maybeSelfDC) {
// If we've found a selfDC, we'll definitely be returning something.
// However, we may have captured 'self' somewhere down the tree, so we
// can't return outright without checking the nested scopes.
NullablePtr<DeclContext> nestedCapturedSelfDC =
checkNestedScopesForSelfCapture(history, i);
return nestedCapturedSelfDC ? nestedCapturedSelfDC : *maybeSelfDC;
}
}
return nullptr;
}

#pragma mark checkNestedScopesForSelfCapture

NullablePtr<DeclContext>
GenericTypeOrExtensionWhereOrBodyPortion::checkNestedScopesForSelfCapture(
ArrayRef<const ASTScopeImpl *> history, size_t start) {
NullablePtr<DeclContext> innerCapturedSelfDC;
// Start with the next scope down the tree.
size_t j = start;

// Note: even though having this loop nested inside the while loop from
// GenericTypeOrExtensionWhereOrBodyPortion::computeSelfDC may appear to
// result in quadratic blowup, complexity actually remains linear with respect
// to the size of history. This relies on the fact that
// GenericTypeOrExtensionScope::computeSelfDCForParent returns a null pointer,
// which will cause this method to bail out of the search early. Thus, this
// method is called once per type body in the lookup history, and will not
// end up re-checking the bodies of nested types that have already been
// covered by earlier calls, so the total impact of this method across all
// calls in a single lookup is O(n).
while (j != 0) {
auto *entry = history[--j];
Optional<NullablePtr<DeclContext>> selfDCForParent =
entry->computeSelfDCForParent();

// If we encounter a scope that should cause us to forget the self
// context (such as a nested type), bail out and use whatever the
// the last inner captured context was.
if (selfDCForParent && (*selfDCForParent).isNull())
break;

// Otherwise, if we have a captured self context for this scope, then
// remember it since it is now the innermost scope we have encountered.
NullablePtr<DeclContext> capturedSelfDC = entry->capturedSelfDC();
if (!capturedSelfDC.isNull())
innerCapturedSelfDC = entry->capturedSelfDC();

// Continue searching in the next scope down.
}
return innerCapturedSelfDC;
}

#pragma mark compute isCascadingUse

Optional<bool> ASTScopeImpl::computeIsCascadingUse(
Expand Down Expand Up @@ -631,6 +678,23 @@ MethodBodyScope::computeSelfDCForParent() const {
return NullablePtr<DeclContext>(decl);
}

#pragma mark capturedSelfDC

// Closures may explicitly capture the self param, in which case the lookup
// should use the closure as the context for implicit self lookups.

// By default, there is no such context to return.
NullablePtr<DeclContext> ASTScopeImpl::capturedSelfDC() const {
return NullablePtr<DeclContext>();
}

// Closures track this information explicitly.
NullablePtr<DeclContext> ClosureParametersScope::capturedSelfDC() const {
if (closureExpr->capturesSelfEnablingImplictSelf())
return NullablePtr<DeclContext>(closureExpr);
return NullablePtr<DeclContext>();
}

#pragma mark ifUnknownIsCascadingUseAccordingTo

static bool isCascadingUseAccordingTo(const DeclContext *const dc) {
Expand Down
1 change: 1 addition & 0 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5230,6 +5230,7 @@ VarDecl::VarDecl(DeclKind kind, bool isStatic, VarDecl::Introducer introducer,
{
Bits.VarDecl.Introducer = unsigned(introducer);
Bits.VarDecl.IsCaptureList = isCaptureList;
Bits.VarDecl.IsSelfParamCapture = false;
Bits.VarDecl.IsDebuggerVar = false;
Bits.VarDecl.IsLazyStorageProperty = false;
Bits.VarDecl.HasNonPatternBindingInit = false;
Expand Down
17 changes: 17 additions & 0 deletions lib/AST/Expr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1180,6 +1180,17 @@ UnresolvedSpecializeExpr *UnresolvedSpecializeExpr::create(ASTContext &ctx,
UnresolvedParams, RAngleLoc);
}

bool CaptureListEntry::isSimpleSelfCapture() const {
if (Init->getPatternList().size() != 1)
return false;
if (auto *DRE = dyn_cast<DeclRefExpr>(Init->getInit(0)))
if (auto *VD = dyn_cast<VarDecl>(DRE->getDecl())) {
return (VD->isSelfParameter() || VD->isSelfParamCapture())
&& VD->getName() == Var->getName();
}
return false;
}

CaptureListExpr *CaptureListExpr::create(ASTContext &ctx,
ArrayRef<CaptureListEntry> captureList,
ClosureExpr *closureBody) {
Expand Down Expand Up @@ -1824,6 +1835,12 @@ bool ClosureExpr::hasEmptyBody() const {
return getBody()->empty();
}

bool ClosureExpr::capturesSelfEnablingImplictSelf() const {
if (auto *VD = getCapturedSelfDecl())
return VD->isSelfParamCapture() && !VD->getType()->is<WeakStorageType>();
return false;
}

FORWARD_SOURCE_LOCS_TO(AutoClosureExpr, Body)

void AutoClosureExpr::setBody(Expr *E) {
Expand Down
7 changes: 7 additions & 0 deletions lib/AST/NameLookup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,13 @@ ValueDecl *LookupResultEntry::getBaseDecl() const {
return selfDecl;
}

if (auto *CE = dyn_cast<ClosureExpr>(BaseDC)) {
auto *selfDecl = CE->getCapturedSelfDecl();
assert(selfDecl);
assert(selfDecl->isSelfParamCapture());
return selfDecl;
}

auto *nominalDecl = BaseDC->getSelfNominalTypeDecl();
assert(nominalDecl);
return nominalDecl;
Expand Down
Loading