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 10 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
11 changes: 10 additions & 1 deletion include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -356,12 +356,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 @@ -4980,6 +4983,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 @@ -3024,11 +3024,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
28 changes: 24 additions & 4 deletions include/swift/AST/Expr.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,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 @@ -3557,6 +3558,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 @@ -3573,16 +3586,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 @@ -3614,6 +3627,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 @@ -3679,6 +3694,9 @@ 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; }

static bool classof(const Expr *E) {
return E->getKind() == ExprKind::Closure;
}
Expand Down Expand Up @@ -3746,6 +3764,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 @@ -73,13 +73,15 @@ namespace swift {
/// 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)
/// Hence: \code
/// class Outer {
///   static func s()
///   func i()
Expand Down
7 changes: 6 additions & 1 deletion include/swift/Parse/Lexer.h
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,12 @@ class Lexer {
/// resides.
///
/// \param Loc The source location of the beginning of a token.
static Token getTokenAtLocation(const SourceManager &SM, SourceLoc Loc);
///
/// \param CRM The how comments should be treated by the lexer. Default is to
/// return the comments as tokens.
static Token getTokenAtLocation(
const SourceManager &SM, SourceLoc Loc,
CommentRetentionMode CRM = CommentRetentionMode::ReturnAsTokens);


/// Retrieve the source location that points just past the
Expand Down
12 changes: 6 additions & 6 deletions include/swift/Parse/Parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -1407,6 +1407,8 @@ class Parser {
/// identifier (',' identifier)* func-signature-result? 'in'
/// \endverbatim
///
/// \param bracketRange The range of the brackets enclosing a capture list, if
/// present
/// \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 @@ -1415,12 +1417,10 @@ 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
3 changes: 2 additions & 1 deletion lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1526,7 +1526,7 @@ VarDecl *PatternBindingDecl::getSingleVar() const {
return getPatternList()[0].getPattern()->getSingleVar();
return nullptr;
}

bool VarDecl::isInitExposedToClients() const {
auto parent = dyn_cast<NominalTypeDecl>(getDeclContext());
if (!parent) return false;
Expand Down Expand Up @@ -4963,6 +4963,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
11 changes: 11 additions & 0 deletions lib/AST/Expr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1228,6 +1228,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
7 changes: 7 additions & 0 deletions lib/AST/NameLookup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,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
25 changes: 22 additions & 3 deletions lib/AST/UnqualifiedLookup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,13 @@ namespace {
bool recordedIsCascadingUse = false;
unsigned resultsSizeBeforeLocalsPass = ~0;

private:
// When performing a lookup, we may come across a capture of 'self'. We will
// need to remember the DeclContext of the innermost captured self so that
// it can be used as the base DeclContext if we find a lookup result in the
// enclosing type.
DeclContext *capturedSelfContext;

public:
// clang-format off
UnqualifiedLookupFactory(DeclName Name,
Expand Down Expand Up @@ -463,6 +470,7 @@ UnqualifiedLookupFactory::UnqualifiedLookupFactory(
options(options),
isOriginallyTypeLookup(options.contains(Flags::TypeLookup)),
baseNLOptions(computeBaseNLOptions(options, isOriginallyTypeLookup)),
capturedSelfContext(nullptr),
#ifdef NDEBUG
Consumer(Name, Results, isOriginallyTypeLookup),
#else
Expand Down Expand Up @@ -693,9 +701,12 @@ void UnqualifiedLookupFactory::lookupNamesIntroducedByMemberFunction(
// If we're not in the body of the function (for example, we
// might be type checking a default argument expression and
// performing name lookup from there), the base declaration
// is the nominal type, not 'self'.
// is the nominal type, not 'self'. If we've captured self
// somewhere down the tree, we should use that as the context
// for lookup.
DeclContext *const BaseDC =
isOutsideBodyOfFunction(AFD) ? fnDeclContext : AFD;
isOutsideBodyOfFunction(AFD) ? fnDeclContext
: capturedSelfContext ?: AFD;
// If we are inside of a method, check to see if there are any ivars in
// scope, and if so, whether this is a reference to one of them.
// FIXME: We should persist this information between lookups.
Expand Down Expand Up @@ -725,8 +736,16 @@ void UnqualifiedLookupFactory::lookupNamesIntroducedByPureFunction(

void UnqualifiedLookupFactory::lookupNamesIntroducedByClosure(
AbstractClosureExpr *ACE, Optional<bool> isCascadingUse) {
if (auto *CE = dyn_cast<ClosureExpr>(ACE))
if (auto *CE = dyn_cast<ClosureExpr>(ACE)) {
lookForLocalVariablesIn(CE);
// If we don't already have a captured self context, and this closure
// captures the self param (not weakly, so that implicit self is available),
// remember that.
if (capturedSelfContext == nullptr)
if (auto *VD = CE->getCapturedSelfDecl())
if (VD->isSelfParamCapture() && !VD->getType()->is<WeakStorageType>())
capturedSelfContext = CE;
}
ifNotDoneYet([&] {
// clang-format off
finishLookingInContext(
Expand Down
5 changes: 3 additions & 2 deletions lib/Parse/Lexer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2489,7 +2489,8 @@ void Lexer::lexImpl() {
}
}

Token Lexer::getTokenAtLocation(const SourceManager &SM, SourceLoc Loc) {
Token Lexer::getTokenAtLocation(const SourceManager &SM, SourceLoc Loc,
CommentRetentionMode CRM) {
// Don't try to do anything with an invalid location.
if (!Loc.isValid())
return Token();
Expand All @@ -2508,7 +2509,7 @@ Token Lexer::getTokenAtLocation(const SourceManager &SM, SourceLoc Loc) {
// (making this option irrelevant), or the caller lexed comments and
// we need to lex just the comment token.
Lexer L(FakeLangOpts, SM, BufferID, nullptr, LexerMode::Swift,
HashbangMode::Allowed, CommentRetentionMode::ReturnAsTokens);
HashbangMode::Allowed, CRM);
L.restoreState(State(Loc));
return L.peekNextToken();
}
Expand Down
Loading